feat: Add moxxmpp_socket

This commit is contained in:
PapaTutuWawa 2022-11-05 14:10:10 +01:00
parent 3fd0f0ac23
commit 376a33434f
9 changed files with 368 additions and 0 deletions

View File

@ -0,0 +1,4 @@
# This is a generated file; do not edit or check into version control.
moxdns=/home/alexander/.pub-cache/hosted/git.polynom.me%47api%47packages%47Moxxy%47pub%47/moxdns-0.1.4/
moxdns_android=/home/alexander/.pub-cache/hosted/git.polynom.me%47api%47packages%47Moxxy%47pub%47/moxdns_android-0.1.4/
moxdns_linux=/home/alexander/.pub-cache/hosted/git.polynom.me%47api%47packages%47Moxxy%47pub%47/moxdns_linux-0.1.4/

View File

@ -0,0 +1 @@
{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[],"android":[{"name":"moxdns_android","path":"/home/alexander/.pub-cache/hosted/git.polynom.me%47api%47packages%47Moxxy%47pub%47/moxdns_android-0.1.4/","native_build":true,"dependencies":[]}],"macos":[],"linux":[{"name":"moxdns_linux","path":"/home/alexander/.pub-cache/hosted/git.polynom.me%47api%47packages%47Moxxy%47pub%47/moxdns_linux-0.1.4/","native_build":true,"dependencies":[]}],"windows":[],"web":[]},"dependencyGraph":[{"name":"moxdns","dependencies":["moxdns_android","moxdns_linux"]},{"name":"moxdns_android","dependencies":["moxdns"]},{"name":"moxdns_linux","dependencies":["moxdns"]}],"date_created":"2022-11-05 14:01:31.714716","version":"3.3.3"}

View File

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

21
moxxmpp_socket/LICENSE Normal file
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.

7
moxxmpp_socket/README.md Normal file
View File

@ -0,0 +1,7 @@
# moxxmpp_socket
A socket for moxxmpp that does SRV resolution.
## License
See `./LICENSE`.

View File

@ -0,0 +1,3 @@
library moxxmpp_socket;
export 'src/socket.dart';

View File

@ -0,0 +1,21 @@
import 'package:moxdns/moxdns.dart';
/// Sorts the SRV records according to priority and weight.
int srvRecordSortComparator(SrvRecord a, SrvRecord 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,286 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:logging/logging.dart';
import 'package:moxdns/moxdns.dart';
import 'package:moxxmpp/moxxmpp.dart';
import 'package:moxxmpp_socket/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();
}
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 MoxdnsPlugin.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;
_socket = await SecureSocket.connect(
srv.target,
srv.port,
timeout: const Duration(seconds: 5),
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 MoxdnsPlugin.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,22 @@
name: moxxmpp_socket
description: A socket for moxxmpp that resolves SRV records
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'
flutter: '>=2.13.0-0.1'
dependencies:
moxdns:
hosted: https://git.polynom.me/api/packages/Moxxy/pub
version: 0.1.4
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