refactor: Move packages into packages/

This commit is contained in:
2022-11-08 20:05:22 +01:00
parent 18afdf74e6
commit 7d3ec12b8b
108 changed files with 17 additions and 18 deletions

44
packages/moxxmpp_socket_tcp/.gitignore vendored Normal file
View File

@@ -0,0 +1,44 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
migrate_working_dir/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
**/doc/api/
**/ios/Flutter/.last_build_id
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
.packages
.pub-cache/
.pub/
/build/
# Symbolication related
app.*.symbols
# Obfuscation related
app.*.map.json
# Android Studio will place build artifacts here
/android/app/debug
/android/app/profile
/android/app/release

View File

@@ -0,0 +1,30 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled.
version:
revision: 18a827f3933c19f51862dde3fa472197683249d6
channel: stable
project_type: app
# Tracks metadata for the flutter migrate command
migration:
platforms:
- platform: root
create_revision: 18a827f3933c19f51862dde3fa472197683249d6
base_revision: 18a827f3933c19f51862dde3fa472197683249d6
- platform: linux
create_revision: 18a827f3933c19f51862dde3fa472197683249d6
base_revision: 18a827f3933c19f51862dde3fa472197683249d6
# User provided section
# List of Local paths (relative to this file) that should be
# ignored by the migrate tool.
#
# Files that are not part of the templates will be ignored by default.
unmanaged_files:
- 'lib/main.dart'
- 'ios/Runner.xcodeproj/project.pbxproj'

View File

@@ -0,0 +1,3 @@
## 1.0.0
- Initial version.

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2022 Alexander "PapaTutuWawa"
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,16 @@
# moxxmpp_socket
A socket for moxxmpp that implements the connection algorithm as specified by
[RFC6210](https://xmpp.org/rfcs/rfc6120.html) and [XEP-0368](https://xmpp.org/extensions/xep-0368.html),
while also supporting StartTLS and direct TLS.
In order to make this package independent of Flutter, I removed DNS SRV resolution from
the package. The `TCPSocketWrapper` contains a method called `srvQuery` that can be
overridden by the user. It takes the domain to query and a DNSSEC flag and is expected
to return the list of SRV records, encoded by `MoxSrvRecord` objects. To perform the
resolution, one can use any DNS library. A Flutter plugin implementing SRV resolution
is, for example, [moxdns](https://codeberg.org/moxxy/moxdns).
## License
See `./LICENSE`.

View File

@@ -0,0 +1,5 @@
include: ../analysis_options.yaml
analyzer:
exclude:
- "lib/src/generated/*.dart"

View File

@@ -0,0 +1,4 @@
library moxxmpp_socket_tcp;
export 'src/record.dart';
export 'src/socket.dart';

View File

@@ -0,0 +1,8 @@
/// A data class to represent a DNS SRV record.
class MoxSrvRecord {
MoxSrvRecord(this.priority, this.weight, this.target, this.port);
final int priority;
final int weight;
final int port;
final String target;
}

View File

@@ -0,0 +1,21 @@
import 'package:moxxmpp_socket_tcp/src/record.dart';
/// Sorts the SRV records according to priority and weight.
int srvRecordSortComparator(MoxSrvRecord a, MoxSrvRecord b) {
if (a.priority < b.priority) {
return -1;
} else {
if (a.priority > b.priority) {
return 1;
}
// a.priority == b.priority
if (a.weight < b.weight) {
return -1;
} else if (a.weight > b.weight) {
return 1;
} else {
return 0;
}
}
}

View File

@@ -0,0 +1,305 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:logging/logging.dart';
import 'package:meta/meta.dart';
import 'package:moxxmpp/moxxmpp.dart';
import 'package:moxxmpp_socket_tcp/src/record.dart';
import 'package:moxxmpp_socket_tcp/src/rfc_2782.dart';
/// TCP socket implementation for XmppConnection
class TCPSocketWrapper extends BaseSocketWrapper {
TCPSocketWrapper(this._logData)
: _log = Logger('TCPSocketWrapper'),
_dataStream = StreamController.broadcast(),
_eventStream = StreamController.broadcast(),
_secure = false,
_ignoreSocketClosure = false;
Socket? _socket;
bool _ignoreSocketClosure;
final StreamController<String> _dataStream;
final StreamController<XmppSocketEvent> _eventStream;
StreamSubscription<dynamic>? _socketSubscription;
final Logger _log;
final bool _logData;
bool _secure;
@override
bool isSecure() => _secure;
@override
bool whitespacePingAllowed() => true;
@override
bool managesKeepalives() => false;
/// Allow the socket to be destroyed by cancelling internal subscriptions.
void destroy() {
_socketSubscription?.cancel();
}
/// Called on connect to perform a SRV query against [domain]. If [dnssec] is true,
/// then DNSSEC validation should be performed.
///
/// Returns a list of SRV records. If none are available or an error occured, an empty
/// list is returned.
@visibleForOverriding
Future<List<MoxSrvRecord>> srvQuery(String domain, bool dnssec) async {
return <MoxSrvRecord>[];
}
bool _onBadCertificate(dynamic certificate, String domain) {
_log.fine('Bad certificate: ${certificate.toString()}');
//final isExpired = certificate.endValidity.isAfter(DateTime.now());
// TODO(Unknown): Either validate the certificate ourselves or use a platform native
// hostname verifier (or Dart adds it themselves)
return false;
}
Future<bool> _xep368Connect(String domain) async {
// TODO(Unknown): Maybe do DNSSEC one day
final results = await srvQuery('_xmpps-client._tcp.$domain', false);
if (results.isEmpty) {
return false;
}
results.sort(srvRecordSortComparator);
for (final srv in results) {
try {
_log.finest('Attempting secure connection to ${srv.target}:${srv.port}...');
_ignoreSocketClosure = true;
// Workaround: We cannot set the SNI directly when using SecureSocket.connect.
// instead, we connect using a regular socket and then secure it. This allows
// us to set the SNI to whatever we want.
final sock = await Socket.connect(
srv.target,
srv.port,
timeout: const Duration(seconds: 5),
);
_socket = await SecureSocket.secure(
sock,
host: domain,
supportedProtocols: const [ xmppClientALPNId ],
onBadCertificate: (cert) => _onBadCertificate(cert, domain),
);
_ignoreSocketClosure = false;
_secure = true;
_log.finest('Success!');
return true;
} on SocketException catch(e) {
_log.finest('Failure! $e');
_ignoreSocketClosure = false;
}
}
return false;
}
Future<bool> _rfc6120Connect(String domain) async {
// TODO(Unknown): Maybe do DNSSEC one day
final results = await srvQuery('_xmpp-client._tcp.$domain', false);
results.sort(srvRecordSortComparator);
for (final srv in results) {
try {
_log.finest('Attempting connection to ${srv.target}:${srv.port}...');
_ignoreSocketClosure = true;
_socket = await Socket.connect(
srv.target,
srv.port,
timeout: const Duration(seconds: 5),
);
_ignoreSocketClosure = false;
_log.finest('Success!');
return true;
} on SocketException catch(e) {
_log.finest('Failure! $e');
_ignoreSocketClosure = false;
continue;
}
}
return _rfc6120FallbackConnect(domain);
}
/// Connect to [host] with port [port] and returns true if the connection
/// was successfully established. Does not setup the streams as this has
/// to be done by the caller.
Future<bool> _hostPortConnect(String host, int port) async {
try {
_log.finest('Attempting fallback connection to $host:$port...');
_ignoreSocketClosure = true;
_socket = await Socket.connect(
host,
port,
timeout: const Duration(seconds: 5),
);
_log.finest('Success!');
return true;
} on SocketException catch(e) {
_log.finest('Failure! $e');
_ignoreSocketClosure = false;
return false;
}
}
/// Connect to [domain] using the default C2S port of XMPP. Returns
/// true if the connection was successful. Does not setup the streams
/// as [_rfc6120FallbackConnect] should only be called from
/// [_rfc6120Connect], which already sets the streams up on a successful
/// connection.
Future<bool> _rfc6120FallbackConnect(String domain) async {
return _hostPortConnect(domain, 5222);
}
@override
Future<bool> secure(String domain) async {
if (_secure) {
_log.warning('Connection is already marked as secure. Doing nothing');
return true;
}
if (_socket == null) {
_log.severe('Failed to secure socket since _socket is null');
return false;
}
_ignoreSocketClosure = true;
try {
_socket = await SecureSocket.secure(
_socket!,
supportedProtocols: const [ xmppClientALPNId ],
onBadCertificate: (cert) => _onBadCertificate(cert, domain),
);
_secure = true;
_ignoreSocketClosure = false;
_setupStreams();
return true;
} on SocketException {
_ignoreSocketClosure = false;
return false;
}
}
void _setupStreams() {
if (_socket == null) {
_log.severe('Failed to setup streams as _socket is null');
return;
}
_socketSubscription = _socket!.listen(
(List<int> event) {
final data = utf8.decode(event);
if (_logData) {
_log.finest('<== $data');
}
_dataStream.add(data);
},
onError: (Object error) {
_log.severe(error.toString());
_eventStream.add(XmppSocketErrorEvent(error));
},
);
// ignore: implicit_dynamic_parameter
_socket!.done.then((_) {
if (!_ignoreSocketClosure) {
_eventStream.add(XmppSocketClosureEvent());
}
});
}
@override
Future<bool> connect(String domain, { String? host, int? port }) async {
_ignoreSocketClosure = false;
_secure = false;
// Connection order:
// 1. host:port, if given
// 2. XEP-0368
// 3. RFC 6120
// 4. RFC 6120 fallback
if (host != null && port != null) {
_log.finest('Specific host and port given');
if (await _hostPortConnect(host, port)) {
_setupStreams();
return true;
}
}
if (await _xep368Connect(domain)) {
_setupStreams();
return true;
}
// NOTE: _rfc6120Connect already attempts the fallback
if (await _rfc6120Connect(domain)) {
_setupStreams();
return true;
}
return false;
}
@override
void close() {
if (_socketSubscription != null) {
_log.finest('Closing socket subscription');
_socketSubscription!.cancel();
}
if (_socket == null) {
_log.warning('Failed to close socket since _socket is null');
return;
}
_ignoreSocketClosure = true;
try {
_socket!.close();
} catch(e) {
_log.warning('Closing socket threw exception: $e');
}
_ignoreSocketClosure = false;
}
@override
Stream<String> getDataStream() => _dataStream.stream.asBroadcastStream();
@override
Stream<XmppSocketEvent> getEventStream() => _eventStream.stream.asBroadcastStream();
@override
void write(Object? data, { String? redact }) {
if (_socket == null) {
_log.severe('Failed to write to socket as _socket is null');
return;
}
if (data != null && data is String && _logData) {
if (redact != null) {
_log.finest('**> $redact');
} else {
_log.finest('==> $data');
}
}
try {
_socket!.write(data);
} on SocketException catch (e) {
_log.severe(e);
_eventStream.add(XmppSocketErrorEvent(e));
}
}
@override
void prepareDisconnect() {
_ignoreSocketClosure = true;
}
}

View File

@@ -0,0 +1,20 @@
name: moxxmpp_socket_tcp
description: A socket for moxxmpp using TCP that implements the RFC6120 connection algorithm and XEP-0368
version: 0.1.0
homepage: https://codeberg.org/moxxy/moxxmpp
publish_to: https://git.polynom.me/api/packages/Moxxy/pub
environment:
sdk: '>=2.18.0 <3.0.0'
dependencies:
logging: ^1.0.2
meta: ^1.6.0
moxxmpp:
hosted: https://git.polynom.me/api/packages/Moxxy/pub
version: 0.1.0
dev_dependencies:
lints: ^2.0.0
test: ^1.16.0
very_good_analysis: ^3.0.1

View File

@@ -0,0 +1,4 @@
# melos_managed_dependency_overrides: moxxmpp
dependency_overrides:
moxxmpp:
path: ../moxxmpp

View File

@@ -0,0 +1,30 @@
// This is a basic Flutter widget test.
//
// To perform an interaction with a widget in your test, use the WidgetTester
// utility in the flutter_test package. For example, you can send tap and scroll
// gestures. You can also use WidgetTester to find child widgets in the widget
// tree, read text, and verify that the values of widget properties are correct.
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:moxxmpp_socket/main.dart';
void main() {
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
// Build our app and trigger a frame.
await tester.pumpWidget(const MyApp());
// Verify that our counter starts at 0.
expect(find.text('0'), findsOneWidget);
expect(find.text('1'), findsNothing);
// Tap the '+' icon and trigger a frame.
await tester.tap(find.byIcon(Icons.add));
await tester.pump();
// Verify that our counter has incremented.
expect(find.text('0'), findsNothing);
expect(find.text('1'), findsOneWidget);
});
}