diff --git a/examples_dart/bin/component.dart b/examples_dart/bin/component.dart index 051751e..41d60ec 100644 --- a/examples_dart/bin/component.dart +++ b/examples_dart/bin/component.dart @@ -30,24 +30,26 @@ class EchoMessageManager extends XmppManagerBase { StanzaHandlerData state, ) async { final body = stanza.firstTag('body'); - if (body == null) return state.copyWith(done: true); + if (body == null) return state..done = true; final bodyText = body.innerText(); await getAttributes().sendStanza( - Stanza.message( - to: stanza.from, - children: [ - XMLNode( - tag: 'body', - text: 'Hello, ${stanza.from}! You said "$bodyText"', - ), - ], + StanzaDetails( + Stanza.message( + to: stanza.from, + children: [ + XMLNode( + tag: 'body', + text: 'Hello, ${stanza.from}! You said "$bodyText"', + ), + ], + ), + awaitable: false, ), - awaitable: false, ); - return state.copyWith(done: true); + return state..done = true; } } diff --git a/examples_dart/bin/omemo_client.dart b/examples_dart/bin/omemo_client.dart new file mode 100644 index 0000000..5241d03 --- /dev/null +++ b/examples_dart/bin/omemo_client.dart @@ -0,0 +1,116 @@ +import 'package:chalkdart/chalk.dart'; +import 'package:cli_repl/cli_repl.dart'; +import 'package:example_dart/arguments.dart'; +import 'package:example_dart/socket.dart'; +import 'package:logging/logging.dart'; +import 'package:moxxmpp/moxxmpp.dart'; +import 'package:omemo_dart/omemo_dart.dart' as omemo; + +void main(List args) async { + // Set up logging + Logger.root.level = Level.ALL; + Logger.root.onRecord.listen((record) { + // ignore: avoid_print + print( + '[${record.level.name}] (${record.loggerName}) ${record.time}: ${record.message}', + ); + }); + + final parser = ArgumentParser() + ..parser.addOption('to', help: 'The JID to send messages to'); + final options = parser.handleArguments(args); + if (options == null) { + return; + } + + // Connect + final jid = parser.jid; + final to = JID.fromString(options['to']! as String).toBare(); + final connection = XmppConnection( + TestingReconnectionPolicy(), + AlwaysConnectedConnectivityManager(), + ClientToServerNegotiator(), + ExampleTCPSocketWrapper(parser.srvRecord), + )..connectionSettings = parser.connectionSettings; + + // Generate OMEMO data + omemo.OmemoManager? oom; + final moxxmppOmemo = OmemoManager( + () async => oom!, + (toJid, _) async => toJid == to, + ); + oom = omemo.OmemoManager( + await omemo.OmemoDevice.generateNewDevice(jid.toString(), opkAmount: 5), + omemo.BlindTrustBeforeVerificationTrustManager(), + moxxmppOmemo.sendEmptyMessageImpl, + moxxmppOmemo.fetchDeviceList, + moxxmppOmemo.fetchDeviceBundle, + moxxmppOmemo.subscribeToDeviceListImpl, + moxxmppOmemo.publishDeviceImpl, + ); + final deviceId = await oom.getDeviceId(); + Logger.root.info('Our device id: $deviceId'); + + // Register the managers and negotiators + await connection.registerManagers([ + PresenceManager(), + DiscoManager([]), + PubSubManager(), + MessageManager(), + moxxmppOmemo, + ]); + await connection.registerFeatureNegotiators([ + SaslPlainNegotiator(), + ResourceBindingNegotiator(), + StartTlsNegotiator(), + SaslScramNegotiator(10, '', '', ScramHashType.sha1), + ]); + + // Set up event handlers + connection.asBroadcastStream().listen((event) { + if (event is MessageEvent) { + Logger.root.info(event.id); + Logger.root.info(event.extensions.keys.toList()); + + final body = event.encryptionError != null + ? chalk.red('Failed to decrypt message: ${event.encryptionError}') + : chalk.green(event.get()?.body ?? ''); + print('[${event.from.toString()}] $body'); + } + }); + + // Connect + Logger.root.info('Connecting...'); + final result = + await connection.connect(shouldReconnect: false, waitUntilLogin: true); + if (!result.isType()) { + Logger.root.severe('Authentication failed!'); + return; + } + Logger.root.info('Connected.'); + + // Publish our bundle + Logger.root.info('Publishing bundle'); + final device = await oom.getDevice(); + final omemoResult = await moxxmppOmemo.publishBundle(await device.toBundle()); + if (!omemoResult.isType()) { + Logger.root.severe( + 'Failed to publish OMEMO bundle: ${omemoResult.get()}'); + return; + } + + final repl = Repl(prompt: '> '); + await for (final line in repl.runAsync()) { + await connection + .getManagerById(messageManager)! + .sendMessage( + to, + TypedMap.fromList([ + MessageBodyData(line), + ]), + ); + } + + // Disconnect + await connection.disconnect(); +} diff --git a/examples_dart/lib/arguments.dart b/examples_dart/lib/arguments.dart new file mode 100644 index 0000000..94a8f10 --- /dev/null +++ b/examples_dart/lib/arguments.dart @@ -0,0 +1,84 @@ +import 'package:args/args.dart'; +import 'package:chalkdart/chalk.dart'; +import 'package:moxxmpp/moxxmpp.dart'; +import 'package:moxxmpp_socket_tcp/moxxmpp_socket_tcp.dart'; + +extension StringToInt on String { + int toInt() => int.parse(this); +} + +/// A wrapper around [ArgParser] for providing convenience functions and standard parameters +/// to the examples. +class ArgumentParser { + ArgumentParser() { + parser + ..addOption('jid', help: 'The JID to connect as') + ..addOption('password', help: 'The password to use for authenticating') + ..addOption('host', + help: + 'The host address to connect to (By default uses the domain part of the JID)') + ..addOption('port', help: 'The port to connect to') + ..addOption('xmpps-srv', + help: + 'Inject a SRV record for _xmpps-client._tcp. Format: ,,,') + ..addFlag('help', + abbr: 'h', + negatable: false, + defaultsTo: false, + help: 'Show this help text'); + } + + /// The [ArgParser] that handles parsing the arguments. + final ArgParser parser = ArgParser(); + + /// The parsed options. Only valid after calling [handleArguments]. + late ArgResults options; + + ArgResults? handleArguments(List args) { + options = parser.parse(args); + if (options['help']!) { + print(parser.usage); + return null; + } + + if (options['jid'] == null) { + print(chalk.red('No JID specified')); + print(parser.usage); + return null; + } + + if (options['password'] == null) { + print(chalk.red('No password specified')); + print(parser.usage); + return null; + } + + return options; + } + + /// The JID to connect as. + JID get jid => JID.fromString(options['jid']!).toBare(); + + /// Construct connection settings from the parsed options. + ConnectionSettings get connectionSettings => ConnectionSettings( + jid: jid, + password: options['password']!, + host: options['host'], + port: (options['port'] as String?)?.toInt(), + ); + + /// Construct an xmpps-client SRV record for injection, if specified. + MoxSrvRecord? get srvRecord { + if (options['xmpps-srv'] == null) { + return null; + } + + final parts = options['xmpps-srv']!.split(','); + return MoxSrvRecord( + int.parse(parts[0]), + int.parse(parts[1]), + parts[2], + int.parse(parts[3]), + ); + } +} diff --git a/examples_dart/lib/socket.dart b/examples_dart/lib/socket.dart new file mode 100644 index 0000000..3031050 --- /dev/null +++ b/examples_dart/lib/socket.dart @@ -0,0 +1,22 @@ +import 'package:moxxmpp_socket_tcp/moxxmpp_socket_tcp.dart'; + +/// A simple socket for examples that allows injection of SRV records (since +/// we cannot use moxdns here). +class ExampleTCPSocketWrapper extends TCPSocketWrapper { + ExampleTCPSocketWrapper(this.srvRecord); + + /// A potential SRV record to inject for testing. + final MoxSrvRecord? srvRecord; + + @override + bool onBadCertificate(dynamic certificate, String domain) { + return true; + } + + @override + Future> srvQuery(String domain, bool dnssec) async { + return [ + if (srvRecord != null) srvRecord!, + ]; + } +} diff --git a/examples_dart/pubspec.yaml b/examples_dart/pubspec.yaml index 6d95fe3..180d0a7 100644 --- a/examples_dart/pubspec.yaml +++ b/examples_dart/pubspec.yaml @@ -1,12 +1,14 @@ name: example_dart -description: A sample command-line application. +description: A collection of samples for moxxmpp. version: 1.0.0 -# homepage: https://www.example.com environment: sdk: '>=2.18.0 <3.0.0' dependencies: + args: 2.4.1 + chalkdart: 2.0.9 + cli_repl: 0.2.3 logging: ^1.0.2 moxxmpp: hosted: https://git.polynom.me/api/packages/Moxxy/pub @@ -14,12 +16,19 @@ dependencies: moxxmpp_socket_tcp: hosted: https://git.polynom.me/api/packages/Moxxy/pub version: 0.3.1 + omemo_dart: + hosted: https://git.polynom.me/api/packages/PapaTutuWawa/pub + version: ^0.5.1 dependency_overrides: moxxmpp: path: ../packages/moxxmpp moxxmpp_socket_tcp: path: ../packages/moxxmpp_socket_tcp + omemo_dart: + git: + url: https://github.com/PapaTutuWawa/omemo_dart.git + rev: 49c7e114e6cf80dcde55fbbd218bba3182045862 dev_dependencies: lints: ^2.0.0 diff --git a/integration_tests/create_users.sh b/integration_tests/create_users.sh new file mode 100644 index 0000000..89b3d9f --- /dev/null +++ b/integration_tests/create_users.sh @@ -0,0 +1,2 @@ +prosodyctl --config ./prosody.cfg.lua register testuser1 localhost abc123 +prosodyctl --config ./prosody.cfg.lua register testuser2 localhost abc123 \ No newline at end of file diff --git a/packages/moxxmpp/CHANGELOG.md b/packages/moxxmpp/CHANGELOG.md index 12185d0..f653e13 100644 --- a/packages/moxxmpp/CHANGELOG.md +++ b/packages/moxxmpp/CHANGELOG.md @@ -16,6 +16,8 @@ - **BREAKING**: `MessageEvent` now makes use of `TypedMap`. - **BREAKING**: Removed `PresenceReceivedEvent`. Use a manager registering handlers with priority greater than `[PresenceManager.presenceHandlerPriority]` instead. - **BREAKING**: `ChatState.toString()` is now `ChatState.toName()` +- **BREAKING**: Overriding `BaseOmemoManager` is no longer required. `OmemoManager` now takes callback methods instead. +- Removed `ErrorResponseDiscoError` from the possible XEP-0030 errors. ## 0.3.1 diff --git a/packages/moxxmpp/lib/moxxmpp.dart b/packages/moxxmpp/lib/moxxmpp.dart index 7a87953..55c910c 100644 --- a/packages/moxxmpp/lib/moxxmpp.dart +++ b/packages/moxxmpp/lib/moxxmpp.dart @@ -38,7 +38,6 @@ export 'package:moxxmpp/src/settings.dart'; export 'package:moxxmpp/src/socket.dart'; export 'package:moxxmpp/src/stanza.dart'; export 'package:moxxmpp/src/stringxml.dart'; -export 'package:moxxmpp/src/types/result.dart'; export 'package:moxxmpp/src/util/typed_map.dart'; export 'package:moxxmpp/src/xeps/staging/extensible_file_thumbnails.dart'; export 'package:moxxmpp/src/xeps/staging/fast.dart'; diff --git a/packages/moxxmpp/lib/src/connection.dart b/packages/moxxmpp/lib/src/connection.dart index 413d8a0..6d3527b 100644 --- a/packages/moxxmpp/lib/src/connection.dart +++ b/packages/moxxmpp/lib/src/connection.dart @@ -25,11 +25,9 @@ import 'package:moxxmpp/src/settings.dart'; import 'package:moxxmpp/src/socket.dart'; import 'package:moxxmpp/src/stanza.dart'; import 'package:moxxmpp/src/stringxml.dart'; -import 'package:moxxmpp/src/types/result.dart'; import 'package:moxxmpp/src/util/queue.dart'; import 'package:moxxmpp/src/util/typed_map.dart'; import 'package:moxxmpp/src/xeps/xep_0030/xep_0030.dart'; -import 'package:moxxmpp/src/xeps/xep_0198/types.dart'; import 'package:moxxmpp/src/xeps/xep_0198/xep_0198.dart'; import 'package:moxxmpp/src/xeps/xep_0352.dart'; import 'package:synchronized/synchronized.dart'; @@ -479,6 +477,7 @@ class XmppConnection { newStanza, TypedMap(), encrypted: details.encrypted, + shouldEncrypt: details.shouldEncrypt, forceEncryption: details.forceEncryption, ), ); @@ -533,15 +532,14 @@ class XmppConnection { // Run post-send handlers _log.fine('Running post stanza handlers..'); - final extensions = TypedMap() - ..set(StreamManagementData(details.excludeFromStreamManagement)); await _runOutgoingPostStanzaHandlers( newStanza, initial: StanzaHandlerData( false, false, newStanza, - extensions, + details.postSendExtensions ?? TypedMap(), + encrypted: data.encrypted, ), ); _log.fine('Done'); @@ -736,6 +734,13 @@ class XmppConnection { : ''; _log.finest('<== $prefix${incomingPreHandlers.stanza.toXml()}'); + if (incomingPreHandlers.skip) { + _log.fine( + 'Not processing stanza (${incomingPreHandlers.stanza.tag}, ${incomingPreHandlers.stanza.id}) due to skip=true.', + ); + return; + } + final awaited = await _stanzaAwaiter.onData( incomingPreHandlers.stanza, connectionSettings.jid.toBare(), @@ -753,6 +758,7 @@ class XmppConnection { incomingPreHandlers.stanza, incomingPreHandlers.extensions, encrypted: incomingPreHandlers.encrypted, + encryptionError: incomingPreHandlers.encryptionError, cancelReason: incomingPreHandlers.cancelReason, ), ); diff --git a/packages/moxxmpp/lib/src/events.dart b/packages/moxxmpp/lib/src/events.dart index 9c0b8b3..21e12c2 100644 --- a/packages/moxxmpp/lib/src/events.dart +++ b/packages/moxxmpp/lib/src/events.dart @@ -70,9 +70,9 @@ class MessageEvent extends XmppEvent { MessageEvent( this.from, this.to, - this.id, this.encrypted, this.extensions, { + this.id, this.type, this.error, this.encryptionError, @@ -85,7 +85,7 @@ class MessageEvent extends XmppEvent { final JID to; /// The id attribute of the message. - final String id; + final String? id; /// The type attribute of the message. final String? type; diff --git a/packages/moxxmpp/lib/src/managers/base.dart b/packages/moxxmpp/lib/src/managers/base.dart index dca035b..ab61ed3 100644 --- a/packages/moxxmpp/lib/src/managers/base.dart +++ b/packages/moxxmpp/lib/src/managers/base.dart @@ -47,7 +47,6 @@ abstract class XmppManagerBase { final result = await dm!.discoInfoQuery( _managerAttributes.getConnectionSettings().jid.toDomain(), - shouldEncrypt: false, ); if (result.isType()) { return false; diff --git a/packages/moxxmpp/lib/src/managers/data.dart b/packages/moxxmpp/lib/src/managers/data.dart index 6d8a5f2..924f8aa 100644 --- a/packages/moxxmpp/lib/src/managers/data.dart +++ b/packages/moxxmpp/lib/src/managers/data.dart @@ -13,12 +13,20 @@ class StanzaHandlerData { this.encryptionError, this.encrypted = false, this.forceEncryption = false, + this.shouldEncrypt = true, + this.skip = false, }); /// Indicates to the runner that processing is now done. This means that all /// pre-processing is done and no other handlers should be consulted. bool done; + /// Only useful in combination with [done] = true: When [skip] is set to true and + /// this [StanzaHandlerData] object is returned from a IncomingPreStanzaHandler, then + /// moxxmpp will skip checking whether the stanza was awaited and will not run any actual + /// IncomingStanzaHandler callbacks. + bool skip; + /// Indicates to the runner that processing is to be cancelled and no further handlers /// should run. The stanza also will not be sent. bool cancel; @@ -33,7 +41,7 @@ class StanzaHandlerData { /// absolutely necessary, e.g. with Message Carbons or OMEMO. Stanza stanza; - /// Whether the stanza was received encrypted + /// Whether the stanza is already encrypted bool encrypted; // If true, forces the encryption manager to encrypt to the JID, even if it @@ -42,6 +50,10 @@ class StanzaHandlerData { // to the JID anyway. bool forceEncryption; + /// Flag indicating whether a E2EE implementation should encrypt the stanza (true) + /// or not (false). + bool shouldEncrypt; + /// Additional data from other managers. final TypedMap extensions; } diff --git a/packages/moxxmpp/lib/src/managers/handlers.dart b/packages/moxxmpp/lib/src/managers/handlers.dart index f35a849..f38d944 100644 --- a/packages/moxxmpp/lib/src/managers/handlers.dart +++ b/packages/moxxmpp/lib/src/managers/handlers.dart @@ -1,4 +1,4 @@ -import 'package:moxlib/moxlib.dart'; +import 'package:collection/collection.dart'; import 'package:moxxmpp/src/managers/data.dart'; import 'package:moxxmpp/src/namespaces.dart'; import 'package:moxxmpp/src/stanza.dart'; @@ -100,10 +100,10 @@ class StanzaHandler extends Handler { matches &= firstTag?.xmlns == tagXmlns; } } else if (tagXmlns != null) { - matches &= listContains( - node.children, - (XMLNode node_) => node_.attributes['xmlns'] == tagXmlns, - ); + matches &= node.children.firstWhereOrNull( + (XMLNode node_) => node_.attributes['xmlns'] == tagXmlns, + ) != + null; } return matches; diff --git a/packages/moxxmpp/lib/src/message.dart b/packages/moxxmpp/lib/src/message.dart index 00bfbe7..c6727a7 100644 --- a/packages/moxxmpp/lib/src/message.dart +++ b/packages/moxxmpp/lib/src/message.dart @@ -73,16 +73,23 @@ class MessageManager extends XmppManagerBase { Future isSupported() async => true; Future _onMessage( - Stanza _, + Stanza stanza, StanzaHandlerData state, ) async { + final body = stanza.firstTag('body'); + if (body != null) { + state.extensions.set( + MessageBodyData(body.innerText()), + ); + } + getAttributes().sendEvent( MessageEvent( JID.fromString(state.stanza.attributes['from']! as String), JID.fromString(state.stanza.attributes['to']! as String), - state.stanza.attributes['id']! as String, state.encrypted, state.extensions, + id: state.stanza.attributes['id'] as String?, type: state.stanza.attributes['type'] as String?, error: StanzaError.fromStanza(state.stanza), encryptionError: state.encryptionError, diff --git a/packages/moxxmpp/lib/src/negotiators/negotiator.dart b/packages/moxxmpp/lib/src/negotiators/negotiator.dart index 600e872..5e5c378 100644 --- a/packages/moxxmpp/lib/src/negotiators/negotiator.dart +++ b/packages/moxxmpp/lib/src/negotiators/negotiator.dart @@ -1,3 +1,4 @@ +import 'package:collection/collection.dart'; import 'package:meta/meta.dart'; import 'package:moxlib/moxlib.dart'; import 'package:moxxmpp/src/connection.dart'; @@ -8,7 +9,6 @@ import 'package:moxxmpp/src/managers/base.dart'; import 'package:moxxmpp/src/settings.dart'; import 'package:moxxmpp/src/socket.dart'; import 'package:moxxmpp/src/stringxml.dart'; -import 'package:moxxmpp/src/types/result.dart'; /// The state a negotiator is currently in enum NegotiatorState { @@ -117,8 +117,7 @@ abstract class XmppFeatureNegotiatorBase { /// Returns true if a feature in [features], which are the children of the /// nonza, can be negotiated. Otherwise, returns false. bool matchesFeature(List features) { - return firstWhereOrNull( - features, + return features.firstWhereOrNull( (XMLNode feature) => feature.attributes['xmlns'] == negotiatingXmlns, ) != null; diff --git a/packages/moxxmpp/lib/src/presence.dart b/packages/moxxmpp/lib/src/presence.dart index f248601..d850f24 100644 --- a/packages/moxxmpp/lib/src/presence.dart +++ b/packages/moxxmpp/lib/src/presence.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'package:moxlib/moxlib.dart'; import 'package:moxxmpp/src/events.dart'; import 'package:moxxmpp/src/jid.dart'; import 'package:moxxmpp/src/managers/base.dart'; @@ -10,7 +11,8 @@ import 'package:moxxmpp/src/negotiators/namespaces.dart'; import 'package:moxxmpp/src/negotiators/negotiator.dart'; import 'package:moxxmpp/src/stanza.dart'; import 'package:moxxmpp/src/stringxml.dart'; -import 'package:moxxmpp/src/types/result.dart'; +import 'package:moxxmpp/src/util/typed_map.dart'; +import 'package:moxxmpp/src/xeps/xep_0198/types.dart'; /// A function that will be called when presence, outside of subscription request /// management, will be sent. Useful for managers that want to add [XMLNode]s to said @@ -156,7 +158,9 @@ class PresenceManager extends XmppManagerBase { ), awaitable: false, bypassQueue: true, - excludeFromStreamManagement: true, + postSendExtensions: TypedMap.fromList([ + const StreamManagementData(true, null), + ]), ), ); } diff --git a/packages/moxxmpp/lib/src/rfcs/rfc_6120/resource_binding.dart b/packages/moxxmpp/lib/src/rfcs/rfc_6120/resource_binding.dart index 2ebb737..3f7afb4 100644 --- a/packages/moxxmpp/lib/src/rfcs/rfc_6120/resource_binding.dart +++ b/packages/moxxmpp/lib/src/rfcs/rfc_6120/resource_binding.dart @@ -1,10 +1,10 @@ +import 'package:moxlib/moxlib.dart'; import 'package:moxxmpp/src/jid.dart'; import 'package:moxxmpp/src/managers/namespaces.dart'; import 'package:moxxmpp/src/namespaces.dart'; import 'package:moxxmpp/src/negotiators/namespaces.dart'; import 'package:moxxmpp/src/negotiators/negotiator.dart'; import 'package:moxxmpp/src/stringxml.dart'; -import 'package:moxxmpp/src/types/result.dart'; import 'package:moxxmpp/src/xeps/xep_0198/xep_0198.dart'; import 'package:uuid/uuid.dart'; diff --git a/packages/moxxmpp/lib/src/rfcs/rfc_6120/sasl/negotiator.dart b/packages/moxxmpp/lib/src/rfcs/rfc_6120/sasl/negotiator.dart index e0cef18..5ef0a86 100644 --- a/packages/moxxmpp/lib/src/rfcs/rfc_6120/sasl/negotiator.dart +++ b/packages/moxxmpp/lib/src/rfcs/rfc_6120/sasl/negotiator.dart @@ -1,4 +1,4 @@ -import 'package:moxlib/moxlib.dart'; +import 'package:collection/collection.dart'; import 'package:moxxmpp/src/namespaces.dart'; import 'package:moxxmpp/src/negotiators/negotiator.dart'; import 'package:moxxmpp/src/stringxml.dart'; @@ -13,15 +13,13 @@ abstract class SaslNegotiator extends XmppFeatureNegotiatorBase { @override bool matchesFeature(List features) { // Is SASL advertised? - final mechanisms = firstWhereOrNull( - features, + final mechanisms = features.firstWhereOrNull( (XMLNode feature) => feature.attributes['xmlns'] == saslXmlns, ); if (mechanisms == null) return false; // Is SASL PLAIN advertised? - return firstWhereOrNull( - mechanisms.children, + return mechanisms.children.firstWhereOrNull( (XMLNode mechanism) => mechanism.text == mechanismName, ) != null; diff --git a/packages/moxxmpp/lib/src/rfcs/rfc_6120/sasl/plain.dart b/packages/moxxmpp/lib/src/rfcs/rfc_6120/sasl/plain.dart index 573762c..e839a4d 100644 --- a/packages/moxxmpp/lib/src/rfcs/rfc_6120/sasl/plain.dart +++ b/packages/moxxmpp/lib/src/rfcs/rfc_6120/sasl/plain.dart @@ -1,12 +1,12 @@ import 'dart:convert'; import 'package:logging/logging.dart'; +import 'package:moxlib/moxlib.dart'; import 'package:moxxmpp/src/events.dart'; import 'package:moxxmpp/src/negotiators/namespaces.dart'; import 'package:moxxmpp/src/negotiators/negotiator.dart'; import 'package:moxxmpp/src/rfcs/rfc_6120/sasl/errors.dart'; import 'package:moxxmpp/src/rfcs/rfc_6120/sasl/nonza.dart'; import 'package:moxxmpp/src/stringxml.dart'; -import 'package:moxxmpp/src/types/result.dart'; import 'package:moxxmpp/src/xeps/xep_0388/negotiators.dart'; import 'package:moxxmpp/src/xeps/xep_0388/xep_0388.dart'; import 'package:saslprep/saslprep.dart'; diff --git a/packages/moxxmpp/lib/src/rfcs/rfc_6120/sasl/scram.dart b/packages/moxxmpp/lib/src/rfcs/rfc_6120/sasl/scram.dart index 9ea570b..e753c53 100644 --- a/packages/moxxmpp/lib/src/rfcs/rfc_6120/sasl/scram.dart +++ b/packages/moxxmpp/lib/src/rfcs/rfc_6120/sasl/scram.dart @@ -2,6 +2,7 @@ import 'dart:convert'; import 'dart:math' show Random; import 'package:cryptography/cryptography.dart'; import 'package:logging/logging.dart'; +import 'package:moxlib/moxlib.dart'; import 'package:moxxmpp/src/events.dart'; import 'package:moxxmpp/src/namespaces.dart'; import 'package:moxxmpp/src/negotiators/namespaces.dart'; @@ -10,7 +11,6 @@ import 'package:moxxmpp/src/rfcs/rfc_6120/sasl/errors.dart'; import 'package:moxxmpp/src/rfcs/rfc_6120/sasl/kv.dart'; import 'package:moxxmpp/src/rfcs/rfc_6120/sasl/nonza.dart'; import 'package:moxxmpp/src/stringxml.dart'; -import 'package:moxxmpp/src/types/result.dart'; import 'package:moxxmpp/src/xeps/xep_0388/negotiators.dart'; import 'package:moxxmpp/src/xeps/xep_0388/xep_0388.dart'; import 'package:random_string/random_string.dart'; diff --git a/packages/moxxmpp/lib/src/rfcs/rfc_6120/starttls.dart b/packages/moxxmpp/lib/src/rfcs/rfc_6120/starttls.dart index b8e07d1..bb92d40 100644 --- a/packages/moxxmpp/lib/src/rfcs/rfc_6120/starttls.dart +++ b/packages/moxxmpp/lib/src/rfcs/rfc_6120/starttls.dart @@ -1,9 +1,9 @@ import 'package:logging/logging.dart'; +import 'package:moxlib/moxlib.dart'; import 'package:moxxmpp/src/namespaces.dart'; import 'package:moxxmpp/src/negotiators/namespaces.dart'; import 'package:moxxmpp/src/negotiators/negotiator.dart'; import 'package:moxxmpp/src/stringxml.dart'; -import 'package:moxxmpp/src/types/result.dart'; enum _StartTlsState { ready, requested } diff --git a/packages/moxxmpp/lib/src/roster/roster.dart b/packages/moxxmpp/lib/src/roster/roster.dart index c6d7758..61fa837 100644 --- a/packages/moxxmpp/lib/src/roster/roster.dart +++ b/packages/moxxmpp/lib/src/roster/roster.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'package:collection/collection.dart'; import 'package:meta/meta.dart'; +import 'package:moxlib/moxlib.dart'; import 'package:moxxmpp/src/jid.dart'; import 'package:moxxmpp/src/managers/attributes.dart'; import 'package:moxxmpp/src/managers/base.dart'; @@ -14,7 +15,6 @@ import 'package:moxxmpp/src/roster/errors.dart'; import 'package:moxxmpp/src/roster/state.dart'; import 'package:moxxmpp/src/stanza.dart'; import 'package:moxxmpp/src/stringxml.dart'; -import 'package:moxxmpp/src/types/result.dart'; @immutable class XmppRosterItem { diff --git a/packages/moxxmpp/lib/src/stanza.dart b/packages/moxxmpp/lib/src/stanza.dart index 0b07f8d..2e5804b 100644 --- a/packages/moxxmpp/lib/src/stanza.dart +++ b/packages/moxxmpp/lib/src/stanza.dart @@ -1,5 +1,7 @@ +import 'package:moxxmpp/src/managers/data.dart'; import 'package:moxxmpp/src/namespaces.dart'; import 'package:moxxmpp/src/stringxml.dart'; +import 'package:moxxmpp/src/util/typed_map.dart'; /// A description of a stanza to send. class StanzaDetails { @@ -7,10 +9,11 @@ class StanzaDetails { this.stanza, { this.addId = true, this.awaitable = true, + this.shouldEncrypt = true, this.encrypted = false, this.forceEncryption = false, this.bypassQueue = false, - this.excludeFromStreamManagement = false, + this.postSendExtensions, }); /// The stanza to send. @@ -22,9 +25,16 @@ class StanzaDetails { /// Track the stanza to allow awaiting its response. final bool awaitable; + final bool forceEncryption; + + /// Flag indicating whether the stanza that is sent is already encrypted (true) + /// or not (false). This is only useful for E2EE implementations that have to + /// send heartbeats that must bypass themselves. final bool encrypted; - final bool forceEncryption; + /// Tells an E2EE implementation, if available, to encrypt the stanza (true) or + /// ignore the stanza (false). + final bool shouldEncrypt; /// Bypasses being put into the queue. Useful for sending stanzas that must go out /// now, where it's okay if it does not get sent. @@ -34,31 +44,60 @@ class StanzaDetails { /// This makes the Stream Management implementation, when available, ignore the stanza, /// meaning that it gets counted but excluded from resending. /// This should never have to be set to true. - final bool excludeFromStreamManagement; + final TypedMap? postSendExtensions; } -/// A simple description of the element that may be inside a stanza -class StanzaError { - StanzaError(this.type, this.error); - String type; - String error; +/// A general error type for errors. +abstract class StanzaError { + static StanzaError? fromXMLNode(XMLNode node) { + final error = node.firstTag('error'); + if (error == null) { + return null; + } + + final specificError = error.firstTagByXmlns(fullStanzaXmlns); + if (specificError == null) { + return UnknownStanzaError(); + } + + switch (specificError.tag) { + case RemoteServerNotFoundError.tag: + return RemoteServerNotFoundError(); + case RemoteServerTimeoutError.tag: + return RemoteServerTimeoutError(); + case ServiceUnavailableError.tag: + return ServiceUnavailableError(); + } + + return UnknownStanzaError(); + } - /// Returns a StanzaError if [stanza] contains a element. If not, returns - /// null. static StanzaError? fromStanza(Stanza stanza) { - final error = stanza.firstTag('error'); - if (error == null) return null; - - final stanzaError = error.firstTagByXmlns(fullStanzaXmlns); - if (stanzaError == null) return null; - - return StanzaError( - error.attributes['type']! as String, - stanzaError.tag, - ); + return fromXMLNode(stanza); } } +/// Recipient does not provide a given service. +/// https://xmpp.org/rfcs/rfc6120.html#stanzas-error-conditions-service-unavailable +class ServiceUnavailableError extends StanzaError { + static const tag = 'service-unavailable'; +} + +/// Could not connect to the remote server. +/// https://xmpp.org/rfcs/rfc6120.html#stanzas-error-conditions-remote-server-not-found +class RemoteServerNotFoundError extends StanzaError { + static const tag = 'remote-server-not-found'; +} + +/// The connection to the remote server timed out. +/// https://xmpp.org/rfcs/rfc6120.html#stanzas-error-conditions-remote-server-timeout +class RemoteServerTimeoutError extends StanzaError { + static const tag = 'remote-server-timeout'; +} + +/// An unknown error. +class UnknownStanzaError extends StanzaError {} + class Stanza extends XMLNode { // ignore: use_super_parameters Stanza({ diff --git a/packages/moxxmpp/lib/src/types/result.dart b/packages/moxxmpp/lib/src/types/result.dart deleted file mode 100644 index a277740..0000000 --- a/packages/moxxmpp/lib/src/types/result.dart +++ /dev/null @@ -1,16 +0,0 @@ -class Result { - const Result(this._data) - : assert( - _data is T || _data is V, - 'Invalid data type: Must be either $T or $V', - ); - final dynamic _data; - - bool isType() => _data is S; - - S get() { - assert(_data is S, 'Data is not $S'); - - return _data as S; - } -} diff --git a/packages/moxxmpp/lib/src/util/queue.dart b/packages/moxxmpp/lib/src/util/queue.dart index b209bc4..203da8d 100644 --- a/packages/moxxmpp/lib/src/util/queue.dart +++ b/packages/moxxmpp/lib/src/util/queue.dart @@ -33,7 +33,7 @@ class AsyncStanzaQueue { this._canSendCallback, ); - /// The lock for accessing [AsyncStanzaQueue._lock] and [AsyncStanzaQueue._running]. + /// The lock for accessing [AsyncStanzaQueue._queue]. final Lock _lock = Lock(); /// The actual job queue. @@ -44,22 +44,15 @@ class AsyncStanzaQueue { final CanSendCallback _canSendCallback; - /// Indicates whether we are currently executing a job. - bool _running = false; - @visibleForTesting Queue get queue => _queue; - @visibleForTesting - bool get isRunning => _running; - /// Adds a job [entry] to the queue. Future enqueueStanza(StanzaQueueEntry entry) async { await _lock.synchronized(() async { _queue.add(entry); - if (!_running && _queue.isNotEmpty && await _canSendCallback()) { - _running = true; + if (_queue.isNotEmpty && await _canSendCallback()) { unawaited( _runJob(_queue.removeFirst()), ); @@ -79,8 +72,6 @@ class AsyncStanzaQueue { unawaited( _runJob(_queue.removeFirst()), ); - } else { - _running = false; } }); } @@ -90,7 +81,6 @@ class AsyncStanzaQueue { await _lock.synchronized(() { if (_queue.isNotEmpty) { - _running = true; unawaited( _runJob(_queue.removeFirst()), ); diff --git a/packages/moxxmpp/lib/src/util/typed_map.dart b/packages/moxxmpp/lib/src/util/typed_map.dart index f6a096e..b2775b9 100644 --- a/packages/moxxmpp/lib/src/util/typed_map.dart +++ b/packages/moxxmpp/lib/src/util/typed_map.dart @@ -20,4 +20,6 @@ class TypedMap { /// Return the object of type [T] from the map, if it has been stored. T? get() => _data[T] as T?; + + Iterable get keys => _data.keys; } diff --git a/packages/moxxmpp/lib/src/xeps/staging/fast.dart b/packages/moxxmpp/lib/src/xeps/staging/fast.dart index 3a873fd..c1ad724 100644 --- a/packages/moxxmpp/lib/src/xeps/staging/fast.dart +++ b/packages/moxxmpp/lib/src/xeps/staging/fast.dart @@ -1,11 +1,11 @@ import 'package:collection/collection.dart'; import 'package:logging/logging.dart'; +import 'package:moxlib/moxlib.dart'; import 'package:moxxmpp/src/events.dart'; import 'package:moxxmpp/src/namespaces.dart'; import 'package:moxxmpp/src/negotiators/namespaces.dart'; import 'package:moxxmpp/src/negotiators/negotiator.dart'; import 'package:moxxmpp/src/stringxml.dart'; -import 'package:moxxmpp/src/types/result.dart'; import 'package:moxxmpp/src/xeps/xep_0388/negotiators.dart'; import 'package:moxxmpp/src/xeps/xep_0388/xep_0388.dart'; diff --git a/packages/moxxmpp/lib/src/xeps/xep_0004.dart b/packages/moxxmpp/lib/src/xeps/xep_0004.dart index 27ea054..7b4a684 100644 --- a/packages/moxxmpp/lib/src/xeps/xep_0004.dart +++ b/packages/moxxmpp/lib/src/xeps/xep_0004.dart @@ -1,4 +1,4 @@ -import 'package:moxlib/moxlib.dart'; +import 'package:collection/collection.dart'; import 'package:moxxmpp/src/namespaces.dart'; import 'package:moxxmpp/src/stringxml.dart'; @@ -80,7 +80,7 @@ class DataForm { final List> items; DataFormField? getFieldByVar(String varAttr) { - return firstWhereOrNull(fields, (field) => field.varAttr == varAttr); + return fields.firstWhereOrNull((field) => field.varAttr == varAttr); } XMLNode toXml() { diff --git a/packages/moxxmpp/lib/src/xeps/xep_0030/errors.dart b/packages/moxxmpp/lib/src/xeps/xep_0030/errors.dart index f11717d..bab7e09 100644 --- a/packages/moxxmpp/lib/src/xeps/xep_0030/errors.dart +++ b/packages/moxxmpp/lib/src/xeps/xep_0030/errors.dart @@ -1,7 +1,10 @@ -abstract class DiscoError {} +import 'package:moxxmpp/src/stanza.dart'; +/// Base type for disco-related errors. +abstract class DiscoError extends StanzaError {} + +/// An unspecified error that is not covered by another [DiscoError]. class UnknownDiscoError extends DiscoError {} +/// The received disco response is invalid in some shape or form. class InvalidResponseDiscoError extends DiscoError {} - -class ErrorResponseDiscoError extends DiscoError {} diff --git a/packages/moxxmpp/lib/src/xeps/xep_0030/xep_0030.dart b/packages/moxxmpp/lib/src/xeps/xep_0030/xep_0030.dart index 903304e..96a854e 100644 --- a/packages/moxxmpp/lib/src/xeps/xep_0030/xep_0030.dart +++ b/packages/moxxmpp/lib/src/xeps/xep_0030/xep_0030.dart @@ -1,5 +1,6 @@ import 'dart:async'; import 'package:meta/meta.dart'; +import 'package:moxlib/moxlib.dart'; import 'package:moxxmpp/src/events.dart'; import 'package:moxxmpp/src/jid.dart'; import 'package:moxxmpp/src/managers/base.dart'; @@ -9,7 +10,6 @@ import 'package:moxxmpp/src/managers/namespaces.dart'; import 'package:moxxmpp/src/namespaces.dart'; import 'package:moxxmpp/src/stanza.dart'; import 'package:moxxmpp/src/stringxml.dart'; -import 'package:moxxmpp/src/types/result.dart'; import 'package:moxxmpp/src/util/wait.dart'; import 'package:moxxmpp/src/xeps/xep_0030/cache.dart'; import 'package:moxxmpp/src/xeps/xep_0030/errors.dart'; @@ -44,11 +44,11 @@ class DiscoManager extends XmppManagerBase { final Map _discoInfoCache = {}; /// The tracker for tracking disco#info queries that are in flight. - final WaitForTracker> + final WaitForTracker> _discoInfoTracker = WaitForTracker(); /// The tracker for tracking disco#info queries that are in flight. - final WaitForTracker>> + final WaitForTracker>> _discoItemsTracker = WaitForTracker(); /// Cache lock @@ -67,7 +67,7 @@ class DiscoManager extends XmppManagerBase { List get features => _features; @visibleForTesting - WaitForTracker> + WaitForTracker> get infoTracker => _discoInfoTracker; @override @@ -231,7 +231,7 @@ class DiscoManager extends XmppManagerBase { Future _exitDiscoInfoCriticalSection( DiscoCacheKey key, - Result result, + Result result, bool shouldCache, ) async { await _cacheLock.synchronized(() async { @@ -252,10 +252,10 @@ class DiscoManager extends XmppManagerBase { /// /// [shouldCache] indicates whether the successful result of the disco#info query /// should be cached (true) or not(false). - Future> discoInfoQuery( + Future> discoInfoQuery( JID entity, { String? node, - bool shouldEncrypt = true, + bool shouldEncrypt = false, bool shouldCache = true, }) async { DiscoInfo? info; @@ -263,7 +263,7 @@ class DiscoManager extends XmppManagerBase { final ecm = getAttributes() .getManagerById(entityCapabilitiesManager); final ffuture = await _cacheLock - .synchronized>?>?>( + .synchronized>?>?>( () async { // Check if we already know what the JID supports if (_discoInfoCache.containsKey(cacheKey)) { @@ -294,19 +294,21 @@ class DiscoManager extends XmppManagerBase { final stanza = (await getAttributes().sendStanza( StanzaDetails( buildDiscoInfoQueryStanza(entity, node), - encrypted: !shouldEncrypt, + shouldEncrypt: shouldEncrypt, ), ))!; - final query = stanza.firstTag('query'); - if (query == null) { - final result = Result(InvalidResponseDiscoError()); + + // Error handling + if (stanza.attributes['type'] == 'error') { + final result = + Result(StanzaError.fromXMLNode(stanza)); await _exitDiscoInfoCriticalSection(cacheKey, result, shouldCache); return result; } - if (stanza.attributes['type'] == 'error') { - //final error = stanza.firstTag('error'); - final result = Result(ErrorResponseDiscoError()); + final query = stanza.firstTag('query'); + if (query == null) { + final result = Result(InvalidResponseDiscoError()); await _exitDiscoInfoCriticalSection(cacheKey, result, shouldCache); return result; } @@ -322,10 +324,10 @@ class DiscoManager extends XmppManagerBase { } /// Sends a disco items query to the (full) jid [entity], optionally with node=[node]. - Future>> discoItemsQuery( + Future>> discoItemsQuery( JID entity, { String? node, - bool shouldEncrypt = true, + bool shouldEncrypt = false, }) async { final key = DiscoCacheKey(entity, node); final future = await _discoItemsTracker.waitFor(key); @@ -340,19 +342,18 @@ class DiscoManager extends XmppManagerBase { ), ))!; - final query = stanza.firstTag('query'); - if (query == null) { + // Error handling + if (stanza.attributes['type'] == 'error') { final result = - Result>(InvalidResponseDiscoError()); + Result>(StanzaError.fromXMLNode(stanza)); await _discoItemsTracker.resolve(key, result); return result; } - if (stanza.attributes['type'] == 'error') { - //final error = stanza.firstTag('error'); - //print("Disco Items error: " + error.toXml()); + final query = stanza.firstTag('query'); + if (query == null) { final result = - Result>(ErrorResponseDiscoError()); + Result>(InvalidResponseDiscoError()); await _discoItemsTracker.resolve(key, result); return result; } @@ -419,7 +420,7 @@ class DiscoManager extends XmppManagerBase { /// [entity] supports the disco feature [feature]. If not, returns false. Future supportsFeature(JID entity, String feature) async { final info = await discoInfoQuery(entity); - if (info.isType()) return false; + if (info.isType()) return false; return info.get().features.contains(feature); } diff --git a/packages/moxxmpp/lib/src/xeps/xep_0054.dart b/packages/moxxmpp/lib/src/xeps/xep_0054.dart index e38d3d0..90c76e8 100644 --- a/packages/moxxmpp/lib/src/xeps/xep_0054.dart +++ b/packages/moxxmpp/lib/src/xeps/xep_0054.dart @@ -1,3 +1,4 @@ +import 'package:moxlib/moxlib.dart'; import 'package:moxxmpp/src/events.dart'; import 'package:moxxmpp/src/jid.dart'; import 'package:moxxmpp/src/managers/base.dart'; @@ -7,7 +8,6 @@ import 'package:moxxmpp/src/managers/namespaces.dart'; import 'package:moxxmpp/src/namespaces.dart'; import 'package:moxxmpp/src/stanza.dart'; import 'package:moxxmpp/src/stringxml.dart'; -import 'package:moxxmpp/src/types/result.dart'; abstract class VCardError {} diff --git a/packages/moxxmpp/lib/src/xeps/xep_0060/xep_0060.dart b/packages/moxxmpp/lib/src/xeps/xep_0060/xep_0060.dart index c840c37..9dcae8b 100644 --- a/packages/moxxmpp/lib/src/xeps/xep_0060/xep_0060.dart +++ b/packages/moxxmpp/lib/src/xeps/xep_0060/xep_0060.dart @@ -1,5 +1,5 @@ -import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:meta/meta.dart'; +import 'package:moxlib/moxlib.dart'; import 'package:moxxmpp/src/events.dart'; import 'package:moxxmpp/src/jid.dart'; import 'package:moxxmpp/src/managers/base.dart'; @@ -9,7 +9,6 @@ import 'package:moxxmpp/src/managers/namespaces.dart'; import 'package:moxxmpp/src/namespaces.dart'; import 'package:moxxmpp/src/stanza.dart'; import 'package:moxxmpp/src/stringxml.dart'; -import 'package:moxxmpp/src/types/result.dart'; import 'package:moxxmpp/src/xeps/xep_0004.dart'; import 'package:moxxmpp/src/xeps/xep_0030/errors.dart'; import 'package:moxxmpp/src/xeps/xep_0030/types.dart'; @@ -202,6 +201,7 @@ class PubSubManager extends XmppManagerBase { ), ], ), + shouldEncrypt: false, ), ))!; @@ -245,6 +245,7 @@ class PubSubManager extends XmppManagerBase { ), ], ), + shouldEncrypt: false, ), ))!; @@ -329,6 +330,7 @@ class PubSubManager extends XmppManagerBase { ) ], ), + shouldEncrypt: false, ), ))!; if (result.attributes['type'] != 'result') { @@ -419,6 +421,7 @@ class PubSubManager extends XmppManagerBase { ) ], ), + shouldEncrypt: false, ), ))!; @@ -471,6 +474,7 @@ class PubSubManager extends XmppManagerBase { ), ], ), + shouldEncrypt: false, ), ))!; @@ -521,6 +525,7 @@ class PubSubManager extends XmppManagerBase { ), ], ), + shouldEncrypt: false, ), ))!; if (form.attributes['type'] != 'result') { @@ -550,6 +555,7 @@ class PubSubManager extends XmppManagerBase { ), ], ), + shouldEncrypt: false, ), ))!; if (submit.attributes['type'] != 'result') { @@ -580,6 +586,7 @@ class PubSubManager extends XmppManagerBase { ), ], ), + shouldEncrypt: false, ), ))!; @@ -624,6 +631,7 @@ class PubSubManager extends XmppManagerBase { ), ], ), + shouldEncrypt: false, ), ))!; diff --git a/packages/moxxmpp/lib/src/xeps/xep_0084.dart b/packages/moxxmpp/lib/src/xeps/xep_0084.dart index 17bdd88..08ffef4 100644 --- a/packages/moxxmpp/lib/src/xeps/xep_0084.dart +++ b/packages/moxxmpp/lib/src/xeps/xep_0084.dart @@ -1,11 +1,11 @@ import 'dart:convert'; +import 'package:moxlib/moxlib.dart'; import 'package:moxxmpp/src/events.dart'; import 'package:moxxmpp/src/jid.dart'; import 'package:moxxmpp/src/managers/base.dart'; import 'package:moxxmpp/src/managers/namespaces.dart'; import 'package:moxxmpp/src/namespaces.dart'; import 'package:moxxmpp/src/stringxml.dart'; -import 'package:moxxmpp/src/types/result.dart'; import 'package:moxxmpp/src/xeps/xep_0030/errors.dart'; import 'package:moxxmpp/src/xeps/xep_0030/types.dart'; import 'package:moxxmpp/src/xeps/xep_0030/xep_0030.dart'; @@ -225,7 +225,6 @@ class UserAvatarManager extends XmppManagerBase { final response = await disco.discoItemsQuery( jid, node: userAvatarDataXmlns, - shouldEncrypt: false, ); if (response.isType()) return Result(UnknownAvatarError()); diff --git a/packages/moxxmpp/lib/src/xeps/xep_0198/negotiator.dart b/packages/moxxmpp/lib/src/xeps/xep_0198/negotiator.dart index 0f3176e..3468efb 100644 --- a/packages/moxxmpp/lib/src/xeps/xep_0198/negotiator.dart +++ b/packages/moxxmpp/lib/src/xeps/xep_0198/negotiator.dart @@ -1,12 +1,12 @@ import 'package:collection/collection.dart'; import 'package:logging/logging.dart'; +import 'package:moxlib/moxlib.dart'; import 'package:moxxmpp/src/events.dart'; import 'package:moxxmpp/src/managers/namespaces.dart'; import 'package:moxxmpp/src/namespaces.dart'; import 'package:moxxmpp/src/negotiators/namespaces.dart'; import 'package:moxxmpp/src/negotiators/negotiator.dart'; import 'package:moxxmpp/src/stringxml.dart'; -import 'package:moxxmpp/src/types/result.dart'; import 'package:moxxmpp/src/xeps/xep_0198/nonzas.dart'; import 'package:moxxmpp/src/xeps/xep_0198/state.dart'; import 'package:moxxmpp/src/xeps/xep_0198/xep_0198.dart'; @@ -113,7 +113,7 @@ class StreamManagementNegotiator extends Sasl2FeatureNegotiator // We have to do this because we otherwise get a stanza stuck in the queue, // thus spamming the server on every nonza we receive. // ignore: cascade_invocations - await sm.setState(StreamManagementState(0, 0)); + await sm.setState(const StreamManagementState(0, 0)); await sm.commitState(); _resumeFailed = true; diff --git a/packages/moxxmpp/lib/src/xeps/xep_0198/state.dart b/packages/moxxmpp/lib/src/xeps/xep_0198/state.dart index 4df8158..d282eec 100644 --- a/packages/moxxmpp/lib/src/xeps/xep_0198/state.dart +++ b/packages/moxxmpp/lib/src/xeps/xep_0198/state.dart @@ -1,18 +1,43 @@ -import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:meta/meta.dart'; -part 'state.freezed.dart'; -part 'state.g.dart'; +const _smNotSpecified = Object(); -@freezed -class StreamManagementState with _$StreamManagementState { - factory StreamManagementState( - int c2s, - int s2c, { - String? streamResumptionLocation, - String? streamResumptionId, - }) = _StreamManagementState; +@immutable +class StreamManagementState { + const StreamManagementState( + this.c2s, + this.s2c, { + this.streamResumptionLocation, + this.streamResumptionId, + }); - // JSON - factory StreamManagementState.fromJson(Map json) => - _$StreamManagementStateFromJson(json); + /// The counter of stanzas sent from the client to the server. + final int c2s; + + /// The counter of stanzas sent from the server to the client. + final int s2c; + + /// If set, the server's preferred location for resumption. + final String? streamResumptionLocation; + + /// If set, the token to allow using stream resumption. + final String? streamResumptionId; + + StreamManagementState copyWith({ + Object c2s = _smNotSpecified, + Object s2c = _smNotSpecified, + Object? streamResumptionLocation = _smNotSpecified, + Object? streamResumptionId = _smNotSpecified, + }) { + return StreamManagementState( + c2s != _smNotSpecified ? c2s as int : this.c2s, + s2c != _smNotSpecified ? s2c as int : this.s2c, + streamResumptionLocation: streamResumptionLocation != _smNotSpecified + ? streamResumptionLocation as String? + : this.streamResumptionLocation, + streamResumptionId: streamResumptionId != _smNotSpecified + ? streamResumptionId as String? + : this.streamResumptionId, + ); + } } diff --git a/packages/moxxmpp/lib/src/xeps/xep_0198/state.freezed.dart b/packages/moxxmpp/lib/src/xeps/xep_0198/state.freezed.dart deleted file mode 100644 index dedf565..0000000 --- a/packages/moxxmpp/lib/src/xeps/xep_0198/state.freezed.dart +++ /dev/null @@ -1,217 +0,0 @@ -// coverage:ignore-file -// GENERATED CODE - DO NOT MODIFY BY HAND -// ignore_for_file: type=lint -// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark - -part of 'state.dart'; - -// ************************************************************************** -// FreezedGenerator -// ************************************************************************** - -T _$identity(T value) => value; - -final _privateConstructorUsedError = UnsupportedError( - 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); - -StreamManagementState _$StreamManagementStateFromJson( - Map json) { - return _StreamManagementState.fromJson(json); -} - -/// @nodoc -mixin _$StreamManagementState { - int get c2s => throw _privateConstructorUsedError; - int get s2c => throw _privateConstructorUsedError; - String? get streamResumptionLocation => throw _privateConstructorUsedError; - String? get streamResumptionId => throw _privateConstructorUsedError; - - Map toJson() => throw _privateConstructorUsedError; - @JsonKey(ignore: true) - $StreamManagementStateCopyWith get copyWith => - throw _privateConstructorUsedError; -} - -/// @nodoc -abstract class $StreamManagementStateCopyWith<$Res> { - factory $StreamManagementStateCopyWith(StreamManagementState value, - $Res Function(StreamManagementState) then) = - _$StreamManagementStateCopyWithImpl<$Res, StreamManagementState>; - @useResult - $Res call( - {int c2s, - int s2c, - String? streamResumptionLocation, - String? streamResumptionId}); -} - -/// @nodoc -class _$StreamManagementStateCopyWithImpl<$Res, - $Val extends StreamManagementState> - implements $StreamManagementStateCopyWith<$Res> { - _$StreamManagementStateCopyWithImpl(this._value, this._then); - - // ignore: unused_field - final $Val _value; - // ignore: unused_field - final $Res Function($Val) _then; - - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? c2s = null, - Object? s2c = null, - Object? streamResumptionLocation = freezed, - Object? streamResumptionId = freezed, - }) { - return _then(_value.copyWith( - c2s: null == c2s - ? _value.c2s - : c2s // ignore: cast_nullable_to_non_nullable - as int, - s2c: null == s2c - ? _value.s2c - : s2c // ignore: cast_nullable_to_non_nullable - as int, - streamResumptionLocation: freezed == streamResumptionLocation - ? _value.streamResumptionLocation - : streamResumptionLocation // ignore: cast_nullable_to_non_nullable - as String?, - streamResumptionId: freezed == streamResumptionId - ? _value.streamResumptionId - : streamResumptionId // ignore: cast_nullable_to_non_nullable - as String?, - ) as $Val); - } -} - -/// @nodoc -abstract class _$$_StreamManagementStateCopyWith<$Res> - implements $StreamManagementStateCopyWith<$Res> { - factory _$$_StreamManagementStateCopyWith(_$_StreamManagementState value, - $Res Function(_$_StreamManagementState) then) = - __$$_StreamManagementStateCopyWithImpl<$Res>; - @override - @useResult - $Res call( - {int c2s, - int s2c, - String? streamResumptionLocation, - String? streamResumptionId}); -} - -/// @nodoc -class __$$_StreamManagementStateCopyWithImpl<$Res> - extends _$StreamManagementStateCopyWithImpl<$Res, _$_StreamManagementState> - implements _$$_StreamManagementStateCopyWith<$Res> { - __$$_StreamManagementStateCopyWithImpl(_$_StreamManagementState _value, - $Res Function(_$_StreamManagementState) _then) - : super(_value, _then); - - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? c2s = null, - Object? s2c = null, - Object? streamResumptionLocation = freezed, - Object? streamResumptionId = freezed, - }) { - return _then(_$_StreamManagementState( - null == c2s - ? _value.c2s - : c2s // ignore: cast_nullable_to_non_nullable - as int, - null == s2c - ? _value.s2c - : s2c // ignore: cast_nullable_to_non_nullable - as int, - streamResumptionLocation: freezed == streamResumptionLocation - ? _value.streamResumptionLocation - : streamResumptionLocation // ignore: cast_nullable_to_non_nullable - as String?, - streamResumptionId: freezed == streamResumptionId - ? _value.streamResumptionId - : streamResumptionId // ignore: cast_nullable_to_non_nullable - as String?, - )); - } -} - -/// @nodoc -@JsonSerializable() -class _$_StreamManagementState implements _StreamManagementState { - _$_StreamManagementState(this.c2s, this.s2c, - {this.streamResumptionLocation, this.streamResumptionId}); - - factory _$_StreamManagementState.fromJson(Map json) => - _$$_StreamManagementStateFromJson(json); - - @override - final int c2s; - @override - final int s2c; - @override - final String? streamResumptionLocation; - @override - final String? streamResumptionId; - - @override - String toString() { - return 'StreamManagementState(c2s: $c2s, s2c: $s2c, streamResumptionLocation: $streamResumptionLocation, streamResumptionId: $streamResumptionId)'; - } - - @override - bool operator ==(dynamic other) { - return identical(this, other) || - (other.runtimeType == runtimeType && - other is _$_StreamManagementState && - (identical(other.c2s, c2s) || other.c2s == c2s) && - (identical(other.s2c, s2c) || other.s2c == s2c) && - (identical( - other.streamResumptionLocation, streamResumptionLocation) || - other.streamResumptionLocation == streamResumptionLocation) && - (identical(other.streamResumptionId, streamResumptionId) || - other.streamResumptionId == streamResumptionId)); - } - - @JsonKey(ignore: true) - @override - int get hashCode => Object.hash( - runtimeType, c2s, s2c, streamResumptionLocation, streamResumptionId); - - @JsonKey(ignore: true) - @override - @pragma('vm:prefer-inline') - _$$_StreamManagementStateCopyWith<_$_StreamManagementState> get copyWith => - __$$_StreamManagementStateCopyWithImpl<_$_StreamManagementState>( - this, _$identity); - - @override - Map toJson() { - return _$$_StreamManagementStateToJson( - this, - ); - } -} - -abstract class _StreamManagementState implements StreamManagementState { - factory _StreamManagementState(final int c2s, final int s2c, - {final String? streamResumptionLocation, - final String? streamResumptionId}) = _$_StreamManagementState; - - factory _StreamManagementState.fromJson(Map json) = - _$_StreamManagementState.fromJson; - - @override - int get c2s; - @override - int get s2c; - @override - String? get streamResumptionLocation; - @override - String? get streamResumptionId; - @override - @JsonKey(ignore: true) - _$$_StreamManagementStateCopyWith<_$_StreamManagementState> get copyWith => - throw _privateConstructorUsedError; -} diff --git a/packages/moxxmpp/lib/src/xeps/xep_0198/state.g.dart b/packages/moxxmpp/lib/src/xeps/xep_0198/state.g.dart deleted file mode 100644 index 22c28b4..0000000 --- a/packages/moxxmpp/lib/src/xeps/xep_0198/state.g.dart +++ /dev/null @@ -1,25 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'state.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -_$_StreamManagementState _$$_StreamManagementStateFromJson( - Map json) => - _$_StreamManagementState( - json['c2s'] as int, - json['s2c'] as int, - streamResumptionLocation: json['streamResumptionLocation'] as String?, - streamResumptionId: json['streamResumptionId'] as String?, - ); - -Map _$$_StreamManagementStateToJson( - _$_StreamManagementState instance) => - { - 'c2s': instance.c2s, - 's2c': instance.s2c, - 'streamResumptionLocation': instance.streamResumptionLocation, - 'streamResumptionId': instance.streamResumptionId, - }; diff --git a/packages/moxxmpp/lib/src/xeps/xep_0198/types.dart b/packages/moxxmpp/lib/src/xeps/xep_0198/types.dart index 903417a..754ebc8 100644 --- a/packages/moxxmpp/lib/src/xeps/xep_0198/types.dart +++ b/packages/moxxmpp/lib/src/xeps/xep_0198/types.dart @@ -1,8 +1,39 @@ +import 'package:meta/meta.dart'; import 'package:moxxmpp/src/managers/data.dart'; +import 'package:moxxmpp/src/stanza.dart'; class StreamManagementData implements StanzaHandlerExtension { - const StreamManagementData(this.exclude); + const StreamManagementData(this.exclude, this.queueId); /// Whether the stanza should be exluded from the StreamManagement's resend queue. final bool exclude; + + /// The ID to use when queuing the stanza. + final int? queueId; + + /// If we resend a stanza, then we will have [queueId] set, so we should skip + /// incrementing the C2S counter. + bool get shouldCountStanza => queueId == null; +} + +/// A queue element for keeping track of stanzas to (potentially) resend. +@immutable +class SMQueueEntry { + const SMQueueEntry(this.stanza, this.encrypted); + + /// The actual stanza. + final Stanza stanza; + + /// Flag indicating whether the stanza was encrypted before sending. + final bool encrypted; + + @override + bool operator ==(Object other) { + return other is SMQueueEntry && + other.stanza == stanza && + other.encrypted == encrypted; + } + + @override + int get hashCode => stanza.hashCode ^ encrypted.hashCode; } diff --git a/packages/moxxmpp/lib/src/xeps/xep_0198/xep_0198.dart b/packages/moxxmpp/lib/src/xeps/xep_0198/xep_0198.dart index 9ed647f..9daf2fc 100644 --- a/packages/moxxmpp/lib/src/xeps/xep_0198/xep_0198.dart +++ b/packages/moxxmpp/lib/src/xeps/xep_0198/xep_0198.dart @@ -11,6 +11,7 @@ import 'package:moxxmpp/src/namespaces.dart'; import 'package:moxxmpp/src/negotiators/namespaces.dart'; import 'package:moxxmpp/src/stanza.dart'; import 'package:moxxmpp/src/stringxml.dart'; +import 'package:moxxmpp/src/util/typed_map.dart'; import 'package:moxxmpp/src/xeps/xep_0198/errors.dart'; import 'package:moxxmpp/src/xeps/xep_0198/negotiator.dart'; import 'package:moxxmpp/src/xeps/xep_0198/nonzas.dart'; @@ -28,10 +29,10 @@ class StreamManagementManager extends XmppManagerBase { }) : super(smManager); /// The queue of stanzas that are not (yet) acked - final Map _unackedStanzas = {}; + final Map _unackedStanzas = {}; /// Commitable state of the StreamManagementManager - StreamManagementState _state = StreamManagementState(0, 0); + StreamManagementState _state = const StreamManagementState(0, 0); /// Mutex lock for _state final Lock _stateLock = Lock(); @@ -61,7 +62,7 @@ class StreamManagementManager extends XmppManagerBase { /// Functions for testing @visibleForTesting - Map getUnackedStanzas() => _unackedStanzas; + Map getUnackedStanzas() => _unackedStanzas; @visibleForTesting Future getPendingAcks() async { @@ -306,6 +307,10 @@ class StreamManagementManager extends XmppManagerBase { if (_pendingAcks > 0) { // Prevent diff from becoming negative final diff = max(_state.c2s - h, 0); + + logger.finest( + 'Setting _pendingAcks to $diff (was $_pendingAcks before): max(${_state.c2s} - $h, 0)', + ); _pendingAcks = diff; // Reset the timer @@ -336,15 +341,18 @@ class StreamManagementManager extends XmppManagerBase { final attrs = getAttributes(); final sequences = _unackedStanzas.keys.toList()..sort(); for (final height in sequences) { + logger.finest('Unacked stanza: height $height, h $h'); + // Do nothing if the ack does not concern this stanza if (height > h) continue; - final stanza = _unackedStanzas[height]!; + logger.finest('Removing stanza with height $height'); + final entry = _unackedStanzas[height]!; _unackedStanzas.remove(height); // Create a StanzaAckedEvent if the stanza is correct - if (shouldTriggerAckedEvent(stanza)) { - attrs.sendEvent(StanzaAckedEvent(stanza)); + if (shouldTriggerAckedEvent(entry.stanza)) { + attrs.sendEvent(StanzaAckedEvent(entry.stanza)); } } @@ -401,13 +409,29 @@ class StreamManagementManager extends XmppManagerBase { StanzaHandlerData state, ) async { if (isStreamManagementEnabled()) { - await _incrementC2S(); + final smData = state.extensions.get(); + logger.finest('Should count stanza: ${smData?.shouldCountStanza}'); + if (smData?.shouldCountStanza ?? true) { + await _incrementC2S(); + } - if (state.extensions.get()?.exclude ?? false) { + if (smData?.exclude ?? false) { return state; } - _unackedStanzas[_state.c2s] = stanza; + int queueId; + if (smData?.queueId != null) { + logger.finest('Reusing queue id ${smData!.queueId}'); + queueId = smData.queueId!; + } else { + queueId = await _stateLock.synchronized(() => _state.c2s); + } + + _unackedStanzas[queueId] = SMQueueEntry( + stanza, + // Prevent an E2EE message being encrypted again + state.encrypted, + ); await _sendAckRequest(); } @@ -415,16 +439,23 @@ class StreamManagementManager extends XmppManagerBase { } Future _resendStanzas() async { - final stanzas = _unackedStanzas.values.toList(); - _unackedStanzas.clear(); - - for (final stanza in stanzas) { - logger - .finest('Resending ${stanza.tag} with id ${stanza.attributes["id"]}'); + final queueCopy = _unackedStanzas.entries.toList(); + for (final entry in queueCopy) { + logger.finest( + 'Resending ${entry.value.stanza.tag} with id ${entry.value.stanza.attributes["id"]}', + ); await getAttributes().sendStanza( StanzaDetails( - stanza, + entry.value.stanza, + postSendExtensions: TypedMap.fromList([ + StreamManagementData( + false, + entry.key, + ), + ]), awaitable: false, + // Prevent an E2EE message being encrypted again + encrypted: entry.value.encrypted, ), ); } diff --git a/packages/moxxmpp/lib/src/xeps/xep_0352.dart b/packages/moxxmpp/lib/src/xeps/xep_0352.dart index 718d805..e2d73f7 100644 --- a/packages/moxxmpp/lib/src/xeps/xep_0352.dart +++ b/packages/moxxmpp/lib/src/xeps/xep_0352.dart @@ -1,10 +1,10 @@ +import 'package:moxlib/moxlib.dart'; import 'package:moxxmpp/src/managers/base.dart'; import 'package:moxxmpp/src/managers/namespaces.dart'; import 'package:moxxmpp/src/namespaces.dart'; import 'package:moxxmpp/src/negotiators/namespaces.dart'; import 'package:moxxmpp/src/negotiators/negotiator.dart'; import 'package:moxxmpp/src/stringxml.dart'; -import 'package:moxxmpp/src/types/result.dart'; import 'package:moxxmpp/src/xeps/xep_0386.dart'; class CSIActiveNonza extends XMLNode { diff --git a/packages/moxxmpp/lib/src/xeps/xep_0363/xep_0363.dart b/packages/moxxmpp/lib/src/xeps/xep_0363/xep_0363.dart index b63ffab..7b7507e 100644 --- a/packages/moxxmpp/lib/src/xeps/xep_0363/xep_0363.dart +++ b/packages/moxxmpp/lib/src/xeps/xep_0363/xep_0363.dart @@ -1,3 +1,4 @@ +import 'package:collection/collection.dart'; import 'package:meta/meta.dart'; import 'package:moxlib/moxlib.dart'; import 'package:moxxmpp/src/events.dart'; @@ -7,7 +8,6 @@ import 'package:moxxmpp/src/managers/namespaces.dart'; import 'package:moxxmpp/src/namespaces.dart'; import 'package:moxxmpp/src/stanza.dart'; import 'package:moxxmpp/src/stringxml.dart'; -import 'package:moxxmpp/src/types/result.dart'; import 'package:moxxmpp/src/xeps/xep_0030/errors.dart'; import 'package:moxxmpp/src/xeps/xep_0030/types.dart'; import 'package:moxxmpp/src/xeps/xep_0030/xep_0030.dart'; @@ -58,10 +58,10 @@ class HttpFileUploadManager extends XmppManagerBase { /// Returns whether the entity provided an identity that tells us that we can ask it /// for an HTTP upload slot. bool _containsFileUploadIdentity(DiscoInfo info) { - return listContains( - info.identities, - (Identity id) => id.category == 'store' && id.type == 'file', - ); + return info.identities.firstWhereOrNull( + (Identity id) => id.category == 'store' && id.type == 'file', + ) != + null; } /// Extract the maximum filesize in octets from the disco response. Returns null diff --git a/packages/moxxmpp/lib/src/xeps/xep_0384/types.dart b/packages/moxxmpp/lib/src/xeps/xep_0384/types.dart index 038598c..c21ffc6 100644 --- a/packages/moxxmpp/lib/src/xeps/xep_0384/types.dart +++ b/packages/moxxmpp/lib/src/xeps/xep_0384/types.dart @@ -1,3 +1,4 @@ +import 'package:moxxmpp/src/managers/data.dart'; import 'package:omemo_dart/omemo_dart.dart'; /// A simple wrapper class for defining elements that should not be encrypted. @@ -13,9 +14,16 @@ class DoNotEncrypt { /// An encryption error caused by OMEMO. class OmemoEncryptionError { - const OmemoEncryptionError(this.jids, this.devices); + const OmemoEncryptionError(this.deviceEncryptionErrors); - /// See omemo_dart's EncryptionResult for info on these fields. - final Map jids; - final Map devices; + /// See omemo_dart's EncryptionResult for info on this field. + final Map> deviceEncryptionErrors; +} + +class OmemoData extends StanzaHandlerExtension { + OmemoData(this.newRatchets, this.replacedRatchets); + + final Map> newRatchets; + + final Map> replacedRatchets; } diff --git a/packages/moxxmpp/lib/src/xeps/xep_0384/xep_0384.dart b/packages/moxxmpp/lib/src/xeps/xep_0384/xep_0384.dart index bdf618d..cdddcd8 100644 --- a/packages/moxxmpp/lib/src/xeps/xep_0384/xep_0384.dart +++ b/packages/moxxmpp/lib/src/xeps/xep_0384/xep_0384.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'dart:convert'; import 'package:meta/meta.dart'; +import 'package:moxlib/moxlib.dart'; import 'package:moxxmpp/src/events.dart'; import 'package:moxxmpp/src/jid.dart'; import 'package:moxxmpp/src/managers/base.dart'; @@ -10,13 +11,11 @@ import 'package:moxxmpp/src/managers/namespaces.dart'; import 'package:moxxmpp/src/namespaces.dart'; import 'package:moxxmpp/src/stanza.dart'; import 'package:moxxmpp/src/stringxml.dart'; -import 'package:moxxmpp/src/types/result.dart'; import 'package:moxxmpp/src/xeps/xep_0030/errors.dart'; import 'package:moxxmpp/src/xeps/xep_0030/types.dart'; import 'package:moxxmpp/src/xeps/xep_0030/xep_0030.dart'; import 'package:moxxmpp/src/xeps/xep_0060/errors.dart'; import 'package:moxxmpp/src/xeps/xep_0060/xep_0060.dart'; -import 'package:moxxmpp/src/xeps/xep_0203.dart'; import 'package:moxxmpp/src/xeps/xep_0280.dart'; import 'package:moxxmpp/src/xeps/xep_0334.dart'; import 'package:moxxmpp/src/xeps/xep_0380.dart'; @@ -24,9 +23,21 @@ import 'package:moxxmpp/src/xeps/xep_0384/crypto.dart'; import 'package:moxxmpp/src/xeps/xep_0384/errors.dart'; import 'package:moxxmpp/src/xeps/xep_0384/helpers.dart'; import 'package:moxxmpp/src/xeps/xep_0384/types.dart'; -import 'package:omemo_dart/omemo_dart.dart'; +import 'package:omemo_dart/omemo_dart.dart' as omemo; import 'package:xml/xml.dart'; +/// A callback that is executed whenever we need to acquire the OmemoManager backing +/// the manager. +typedef GetOmemoManagerCallback = Future Function(); + +/// A callback for figuring out whether a stanza should be encrypted or not. Note that +/// returning true here does not necessarily mean that a stanza gets encrypted because +/// handlers can indicate that a stanza should not be encrypted, e.g. PubSub. +typedef ShouldEncryptStanzaCallback = Future Function( + JID toJid, + Stanza stanza, +); + const _doNotEncryptList = [ // XEP-0033 DoNotEncrypt('addresses', extendedAddressingXmlns), @@ -43,8 +54,15 @@ const _doNotEncryptList = [ DoNotEncrypt('stanza-id', stableIdXmlns), ]; -abstract class BaseOmemoManager extends XmppManagerBase { - BaseOmemoManager() : super(omemoManager); +class OmemoManager extends XmppManagerBase { + OmemoManager(this._getOmemoManager, this._shouldEncryptStanza) + : super(omemoManager); + + /// Callback for getting the [omemo.OmemoManager]. + final GetOmemoManagerCallback _getOmemoManager; + + /// Callback for checking whether a stanza should be encrypted or not. + final ShouldEncryptStanzaCallback _shouldEncryptStanza; // TODO(Unknown): Technically, this is not always true @override @@ -113,22 +131,19 @@ abstract class BaseOmemoManager extends XmppManagerBase { } // Tell the OmemoManager - (await getOmemoManager()).onDeviceListUpdate(jid.toString(), ids); + await (await _getOmemoManager()).onDeviceListUpdate(jid.toString(), ids); // Generate an event getAttributes().sendEvent(OmemoDeviceListUpdatedEvent(jid, ids)); } } - @visibleForOverriding - Future getOmemoManager(); + /// Wrapper around using getSessionManager and then calling getDeviceId on it. + Future _getDeviceId() async => (await _getOmemoManager()).getDeviceId(); /// Wrapper around using getSessionManager and then calling getDeviceId on it. - Future _getDeviceId() async => (await getOmemoManager()).getDeviceId(); - - /// Wrapper around using getSessionManager and then calling getDeviceId on it. - Future _getDeviceBundle() async { - final om = await getOmemoManager(); + Future _getDeviceBundle() async { + final om = await _getOmemoManager(); final device = await om.getDevice(); return device.toBundle(); } @@ -199,53 +214,45 @@ abstract class BaseOmemoManager extends XmppManagerBase { } XMLNode _buildEncryptedElement( - EncryptionResult result, + omemo.EncryptionResult result, String recipientJid, int deviceId, ) { final keyElements = >{}; - for (final key in result.encryptedKeys) { - final keyElement = XMLNode( - tag: 'key', - attributes: { - 'rid': '${key.rid}', - 'kex': key.kex ? 'true' : 'false', - }, - text: key.value, - ); - - if (keyElements.containsKey(key.jid)) { - keyElements[key.jid]!.add(keyElement); - } else { - keyElements[key.jid] = [keyElement]; - } + for (final keys in result.encryptedKeys.entries) { + keyElements[keys.key] = keys.value + .map( + (ek) => XMLNode( + tag: 'key', + attributes: { + 'rid': ek.rid.toString(), + if (ek.kex) 'kex': 'true', + }, + text: ek.value, + ), + ) + .toList(); } final keysElements = keyElements.entries.map((entry) { return XMLNode( tag: 'keys', - attributes: { + attributes: { 'jid': entry.key, }, children: entry.value, ); }).toList(); - var payloadElement = []; - if (result.ciphertext != null) { - payloadElement = [ - XMLNode( - tag: 'payload', - text: base64.encode(result.ciphertext!), - ), - ]; - } - return XMLNode.xmlns( tag: 'encrypted', xmlns: omemoXmlns, children: [ - ...payloadElement, + if (result.ciphertext != null) + XMLNode( + tag: 'payload', + text: base64Encode(result.ciphertext!), + ), XMLNode( tag: 'header', attributes: { @@ -259,7 +266,7 @@ abstract class BaseOmemoManager extends XmppManagerBase { /// For usage with omemo_dart's OmemoManager. Future sendEmptyMessageImpl( - EncryptionResult result, + omemo.EncryptionResult result, String toJid, ) async { await getAttributes().sendStanza( @@ -288,7 +295,7 @@ abstract class BaseOmemoManager extends XmppManagerBase { /// Send a heartbeat message to [jid]. Future sendOmemoHeartbeat(String jid) async { - final om = await getOmemoManager(); + final om = await _getOmemoManager(); await om.sendOmemoHeartbeat(jid); } @@ -301,17 +308,22 @@ abstract class BaseOmemoManager extends XmppManagerBase { } /// For usage with omemo_dart's OmemoManager - Future fetchDeviceBundle(String jid, int id) async { + Future fetchDeviceBundle(String jid, int id) async { final result = await retrieveDeviceBundle(JID.fromString(jid), id); if (result.isType()) return null; - return result.get(); + return result.get(); } Future _onOutgoingStanza( Stanza stanza, StanzaHandlerData state, ) async { + if (!state.shouldEncrypt) { + logger.finest('Not encrypting since state.shouldEncrypt is false'); + return state; + } + if (state.encrypted) { logger.finest('Not encrypting since state.encrypted is true'); return state; @@ -324,7 +336,7 @@ abstract class BaseOmemoManager extends XmppManagerBase { } final toJid = JID.fromString(stanza.to!).toBare(); - final shouldEncryptResult = await shouldEncryptStanza(toJid, stanza); + final shouldEncryptResult = await _shouldEncryptStanza(toJid, stanza); if (!shouldEncryptResult && !state.forceEncryption) { logger.finest( 'Not encrypting stanza for $toJid: Both shouldEncryptStanza and forceEncryption are false.', @@ -351,29 +363,29 @@ abstract class BaseOmemoManager extends XmppManagerBase { .getManagerById(carbonsManager) ?.isEnabled ?? false; - final om = await getOmemoManager(); + final om = await _getOmemoManager(); + final encryptToJids = [ + toJid.toString(), + if (carbonsEnabled) getAttributes().getFullJID().toBare().toString(), + ]; final result = await om.onOutgoingStanza( - OmemoOutgoingStanza( - [ - toJid.toString(), - if (carbonsEnabled) getAttributes().getFullJID().toBare().toString(), - ], + omemo.OmemoOutgoingStanza( + encryptToJids, _buildEnvelope(toEncrypt, toJid.toString()), ), ); logger.finest('Encryption done'); - if (!result.isSuccess(2)) { + if (!result.canSend) { return state ..cancel = true // If we have no device list for toJid, then the contact most likely does not // support OMEMO:2 - ..cancelReason = result.jidEncryptionErrors[toJid.toString()] - is NoKeyMaterialAvailableException + ..cancelReason = result.deviceEncryptionErrors[toJid.toString()]!.first + .error is omemo.NoKeyMaterialAvailableError ? OmemoNotSupportedForContactException() : UnknownOmemoError() ..encryptionError = OmemoEncryptionError( - result.jidEncryptionErrors, result.deviceEncryptionErrors, ); } @@ -401,53 +413,45 @@ abstract class BaseOmemoManager extends XmppManagerBase { ..encrypted = true; } - /// This function is called whenever a message is to be encrypted. If it returns true, - /// then the message will be encrypted. If it returns false, the message won't be - /// encrypted. - @visibleForOverriding - Future shouldEncryptStanza(JID toJid, Stanza stanza); - Future _onIncomingStanza( Stanza stanza, StanzaHandlerData state, ) async { - final encrypted = stanza.firstTag('encrypted', xmlns: omemoXmlns); - if (encrypted == null) return state; if (stanza.from == null) return state; + final encrypted = stanza.firstTag('encrypted', xmlns: omemoXmlns)!; final fromJid = JID.fromString(stanza.from!).toBare(); final header = encrypted.firstTag('header')!; - final payloadElement = encrypted.firstTag('payload'); - final keys = List.empty(growable: true); + final ourJid = getAttributes().getFullJID(); + final ourJidString = ourJid.toBare().toString(); + final keys = List.empty(growable: true); for (final keysElement in header.findTags('keys')) { + // We only care about our own JID final jid = keysElement.attributes['jid']! as String; - for (final key in keysElement.findTags('key')) { - keys.add( - EncryptedKey( - jid, - int.parse(key.attributes['rid']! as String), - key.innerText(), - key.attributes['kex'] == 'true', - ), - ); + if (jid != ourJidString) { + continue; } + + keys.addAll( + keysElement.findTags('key').map( + (key) => omemo.EncryptedKey( + int.parse(key.attributes['rid']! as String), + key.innerText(), + key.attributes['kex'] == 'true', + ), + ), + ); } - final ourJid = getAttributes().getFullJID(); final sid = int.parse(header.attributes['sid']! as String); - - final om = await getOmemoManager(); + final om = await _getOmemoManager(); final result = await om.onIncomingStanza( - OmemoIncomingStanza( + omemo.OmemoIncomingStanza( fromJid.toString(), sid, - state.extensions - .get() - ?.timestamp - .millisecondsSinceEpoch ?? - DateTime.now().millisecondsSinceEpoch, keys, - payloadElement?.innerText(), + encrypted.firstTag('payload')?.innerText(), + false, ), ); @@ -464,6 +468,7 @@ abstract class BaseOmemoManager extends XmppManagerBase { .toList(); } + logger.finest('Got payload: ${result.payload != null}'); if (result.payload != null) { XMLNode envelope; try { @@ -481,6 +486,8 @@ abstract class BaseOmemoManager extends XmppManagerBase { // Do not add forbidden elements from the envelope envelopeChildren.where(shouldEncryptElement), ); + + logger.finest('Adding children: ${envelopeChildren.map((c) => c.tag)}'); } else { logger.warning('Invalid envelope element: No element'); } @@ -490,6 +497,15 @@ abstract class BaseOmemoManager extends XmppManagerBase { } } + // Ignore heartbeat messages + if (stanza.tag == 'message' && encrypted.firstTag('payload') == null) { + logger.finest('Received empty OMEMO message. Ending processing early.'); + return state + ..encrypted = true + ..skip = true + ..done = true; + } + return state ..encrypted = true ..stanza = Stanza( @@ -500,6 +516,12 @@ abstract class BaseOmemoManager extends XmppManagerBase { children: children, tag: stanza.tag, attributes: Map.from(stanza.attributes), + ) + ..extensions.set( + OmemoData( + result.newRatchets, + result.replacedRatchets, + ), ); } @@ -532,7 +554,7 @@ abstract class BaseOmemoManager extends XmppManagerBase { /// Retrieve all device bundles for the JID [jid]. /// /// On success, returns a list of devices. On failure, returns am OmemoError. - Future>> retrieveDeviceBundles( + Future>> retrieveDeviceBundles( JID jid, ) async { // TODO(Unknown): Should we query the device list first? @@ -553,7 +575,7 @@ abstract class BaseOmemoManager extends XmppManagerBase { /// Retrieves a bundle from entity [jid] with the device id [deviceId]. /// /// On success, returns the device bundle. On failure, returns an OmemoError. - Future> retrieveDeviceBundle( + Future> retrieveDeviceBundle( JID jid, int deviceId, ) async { @@ -569,7 +591,9 @@ abstract class BaseOmemoManager extends XmppManagerBase { /// nodes. /// /// On success, returns true. On failure, returns an OmemoError. - Future> publishBundle(OmemoBundle bundle) async { + Future> publishBundle( + omemo.OmemoBundle bundle, + ) async { final attrs = getAttributes(); final pm = attrs.getManagerById(pubsubManager)!; final bareJid = attrs.getFullJID().toBare(); @@ -636,6 +660,11 @@ abstract class BaseOmemoManager extends XmppManagerBase { await pm.subscribe(JID.fromString(jid), omemoDevicesXmlns); } + /// Implementation for publishing our device [device]. + Future publishDeviceImpl(omemo.OmemoDevice device) async { + await publishBundle(await device.toBundle()); + } + /// Attempts to find out if [jid] supports omemo:2. /// /// On success, returns whether [jid] has published a device list and device bundles. diff --git a/packages/moxxmpp/lib/src/xeps/xep_0386.dart b/packages/moxxmpp/lib/src/xeps/xep_0386.dart index 4f61add..ad2fbfa 100644 --- a/packages/moxxmpp/lib/src/xeps/xep_0386.dart +++ b/packages/moxxmpp/lib/src/xeps/xep_0386.dart @@ -1,10 +1,10 @@ import 'package:collection/collection.dart'; import 'package:meta/meta.dart'; +import 'package:moxlib/moxlib.dart'; import 'package:moxxmpp/src/namespaces.dart'; import 'package:moxxmpp/src/negotiators/namespaces.dart'; import 'package:moxxmpp/src/negotiators/negotiator.dart'; import 'package:moxxmpp/src/stringxml.dart'; -import 'package:moxxmpp/src/types/result.dart'; import 'package:moxxmpp/src/xeps/xep_0388/negotiators.dart'; import 'package:moxxmpp/src/xeps/xep_0388/xep_0388.dart'; diff --git a/packages/moxxmpp/lib/src/xeps/xep_0388/negotiators.dart b/packages/moxxmpp/lib/src/xeps/xep_0388/negotiators.dart index 948930f..24af826 100644 --- a/packages/moxxmpp/lib/src/xeps/xep_0388/negotiators.dart +++ b/packages/moxxmpp/lib/src/xeps/xep_0388/negotiators.dart @@ -1,7 +1,7 @@ +import 'package:moxlib/moxlib.dart'; import 'package:moxxmpp/src/negotiators/negotiator.dart'; import 'package:moxxmpp/src/rfcs/rfc_6120/sasl/negotiator.dart'; import 'package:moxxmpp/src/stringxml.dart'; -import 'package:moxxmpp/src/types/result.dart'; /// A special type of [XmppFeatureNegotiatorBase] that is aware of SASL2. abstract class Sasl2FeatureNegotiator extends XmppFeatureNegotiatorBase { diff --git a/packages/moxxmpp/lib/src/xeps/xep_0388/xep_0388.dart b/packages/moxxmpp/lib/src/xeps/xep_0388/xep_0388.dart index e871c03..9e56694 100644 --- a/packages/moxxmpp/lib/src/xeps/xep_0388/xep_0388.dart +++ b/packages/moxxmpp/lib/src/xeps/xep_0388/xep_0388.dart @@ -1,10 +1,10 @@ +import 'package:moxlib/moxlib.dart'; import 'package:moxxmpp/src/jid.dart'; import 'package:moxxmpp/src/namespaces.dart'; import 'package:moxxmpp/src/negotiators/namespaces.dart'; import 'package:moxxmpp/src/negotiators/negotiator.dart'; import 'package:moxxmpp/src/rfcs/rfc_6120/sasl/errors.dart'; import 'package:moxxmpp/src/stringxml.dart'; -import 'package:moxxmpp/src/types/result.dart'; import 'package:moxxmpp/src/xeps/xep_0388/errors.dart'; import 'package:moxxmpp/src/xeps/xep_0388/negotiators.dart'; import 'package:moxxmpp/src/xeps/xep_0388/user_agent.dart'; diff --git a/packages/moxxmpp/lib/src/xeps/xep_0447.dart b/packages/moxxmpp/lib/src/xeps/xep_0447.dart index 36bf848..d29ce5d 100644 --- a/packages/moxxmpp/lib/src/xeps/xep_0447.dart +++ b/packages/moxxmpp/lib/src/xeps/xep_0447.dart @@ -1,4 +1,4 @@ -import 'package:moxlib/moxlib.dart'; +import 'package:collection/collection.dart'; import 'package:moxxmpp/src/managers/base.dart'; import 'package:moxxmpp/src/managers/data.dart'; import 'package:moxxmpp/src/managers/handlers.dart'; @@ -114,8 +114,7 @@ class StatelessFileSharingData implements StanzaHandlerExtension { } StatelessFileSharingUrlSource? getFirstUrlSource() { - return firstWhereOrNull( - sources, + return sources.firstWhereOrNull( (StatelessFileSharingSource source) => source is StatelessFileSharingUrlSource, ) as StatelessFileSharingUrlSource?; diff --git a/packages/moxxmpp/lib/src/xeps/xep_0448.dart b/packages/moxxmpp/lib/src/xeps/xep_0448.dart index 545bd3d..a6a829b 100644 --- a/packages/moxxmpp/lib/src/xeps/xep_0448.dart +++ b/packages/moxxmpp/lib/src/xeps/xep_0448.dart @@ -1,5 +1,5 @@ import 'dart:convert'; -import 'package:moxlib/moxlib.dart'; +import 'package:collection/collection.dart'; import 'package:moxxmpp/src/namespaces.dart'; import 'package:moxxmpp/src/stringxml.dart'; import 'package:moxxmpp/src/xeps/xep_0300.dart'; @@ -54,8 +54,7 @@ class StatelessFileSharingEncryptedSource extends StatelessFileSharingSource { final sources = element.firstTag('sources', xmlns: sfsXmlns)!.children; // Find the first URL source - final source = firstWhereOrNull( - sources, + final source = sources.firstWhereOrNull( (XMLNode child) => child.tag == 'url-data' && child.attributes['xmlns'] == urlDataXmlns, )!; diff --git a/packages/moxxmpp/lib/src/xeps/xep_0449.dart b/packages/moxxmpp/lib/src/xeps/xep_0449.dart index b32aa3c..26bfc3c 100644 --- a/packages/moxxmpp/lib/src/xeps/xep_0449.dart +++ b/packages/moxxmpp/lib/src/xeps/xep_0449.dart @@ -1,4 +1,5 @@ import 'dart:convert'; +import 'package:moxlib/moxlib.dart'; import 'package:moxxmpp/src/jid.dart'; import 'package:moxxmpp/src/managers/base.dart'; import 'package:moxxmpp/src/managers/data.dart'; @@ -9,7 +10,6 @@ import 'package:moxxmpp/src/namespaces.dart'; import 'package:moxxmpp/src/rfcs/rfc_4790.dart'; import 'package:moxxmpp/src/stanza.dart'; import 'package:moxxmpp/src/stringxml.dart'; -import 'package:moxxmpp/src/types/result.dart'; import 'package:moxxmpp/src/util/typed_map.dart'; import 'package:moxxmpp/src/xeps/xep_0060/errors.dart'; import 'package:moxxmpp/src/xeps/xep_0060/xep_0060.dart'; diff --git a/packages/moxxmpp/lib/src/xeps/xep_0461.dart b/packages/moxxmpp/lib/src/xeps/xep_0461.dart index 644b98a..989e3a1 100644 --- a/packages/moxxmpp/lib/src/xeps/xep_0461.dart +++ b/packages/moxxmpp/lib/src/xeps/xep_0461.dart @@ -1,4 +1,4 @@ -import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:meta/meta.dart'; import 'package:moxxmpp/src/jid.dart'; import 'package:moxxmpp/src/managers/base.dart'; import 'package:moxxmpp/src/managers/data.dart'; diff --git a/packages/moxxmpp/pubspec.yaml b/packages/moxxmpp/pubspec.yaml index 3198a24..e57a52b 100644 --- a/packages/moxxmpp/pubspec.yaml +++ b/packages/moxxmpp/pubspec.yaml @@ -10,18 +10,16 @@ environment: dependencies: collection: ^1.16.0 cryptography: ^2.0.5 - freezed: ^2.1.0+1 - freezed_annotation: ^2.1.0 hex: ^0.2.0 json_serializable: ^6.3.1 logging: ^1.0.2 meta: ^1.7.0 moxlib: hosted: https://git.polynom.me/api/packages/Moxxy/pub - version: ^0.1.5 + version: ^0.2.0 omemo_dart: hosted: https://git.polynom.me/api/packages/PapaTutuWawa/pub - version: ^0.4.3 + version: ^0.5.1 random_string: ^2.3.1 saslprep: ^1.0.2 synchronized: ^3.0.0+2 @@ -32,3 +30,10 @@ dev_dependencies: build_runner: ^2.1.11 test: ^1.16.0 very_good_analysis: ^3.0.1 + +# TODO: Remove once we release 0.5.1 +dependency_overrides: + omemo_dart: + git: + url: https://github.com/PapaTutuWawa/omemo_dart.git + rev: 49c7e114e6cf80dcde55fbbd218bba3182045862 diff --git a/packages/moxxmpp/test/async_queue_test.dart b/packages/moxxmpp/test/async_queue_test.dart index 301172b..3b71fb6 100644 --- a/packages/moxxmpp/test/async_queue_test.dart +++ b/packages/moxxmpp/test/async_queue_test.dart @@ -30,7 +30,6 @@ void main() { await Future.delayed(const Duration(seconds: 1)); expect(queue.queue.length, 2); - expect(queue.isRunning, false); }); test('Test sending', () async { @@ -58,7 +57,6 @@ void main() { await Future.delayed(const Duration(seconds: 1)); expect(queue.queue.length, 0); - expect(queue.isRunning, false); }); test('Test partial sending and resuming', () async { @@ -89,12 +87,10 @@ void main() { await Future.delayed(const Duration(seconds: 1)); expect(queue.queue.length, 1); - expect(queue.isRunning, false); canRun = true; await queue.restart(); await Future.delayed(const Duration(seconds: 1)); expect(queue.queue.length, 0); - expect(queue.isRunning, false); }); } diff --git a/packages/moxxmpp/test/negotiator_test.dart b/packages/moxxmpp/test/negotiator_test.dart index 15de453..622cad5 100644 --- a/packages/moxxmpp/test/negotiator_test.dart +++ b/packages/moxxmpp/test/negotiator_test.dart @@ -1,6 +1,8 @@ +import 'package:moxlib/moxlib.dart'; import 'package:moxxmpp/moxxmpp.dart'; import 'package:moxxmpp/src/parser.dart'; import 'package:test/test.dart'; + import 'helpers/logging.dart'; const exampleXmlns1 = 'im:moxxmpp:example1'; diff --git a/packages/moxxmpp/test/xeps/xep_0060_test.dart b/packages/moxxmpp/test/xeps/xep_0060_test.dart index da31d14..0aaf10b 100644 --- a/packages/moxxmpp/test/xeps/xep_0060_test.dart +++ b/packages/moxxmpp/test/xeps/xep_0060_test.dart @@ -1,3 +1,4 @@ +import 'package:moxlib/moxlib.dart'; import 'package:moxxmpp/moxxmpp.dart'; import 'package:test/test.dart'; diff --git a/packages/moxxmpp/test/xeps/xep_0115_test.dart b/packages/moxxmpp/test/xeps/xep_0115_test.dart index 10ef205..46c073b 100644 --- a/packages/moxxmpp/test/xeps/xep_0115_test.dart +++ b/packages/moxxmpp/test/xeps/xep_0115_test.dart @@ -1,3 +1,4 @@ +import 'package:moxlib/moxlib.dart'; import 'package:moxxmpp/moxxmpp.dart'; import 'package:test/test.dart'; diff --git a/packages/moxxmpp/test/xeps/xep_0198_test.dart b/packages/moxxmpp/test/xeps/xep_0198_test.dart index 2ad54c4..146f803 100644 --- a/packages/moxxmpp/test/xeps/xep_0198_test.dart +++ b/packages/moxxmpp/test/xeps/xep_0198_test.dart @@ -1,5 +1,6 @@ import 'dart:async'; import 'package:moxxmpp/moxxmpp.dart'; +import 'package:moxxmpp/src/xeps/xep_0198/types.dart'; import 'package:test/test.dart'; import '../helpers/logging.dart'; import '../helpers/xmpp.dart'; @@ -42,10 +43,10 @@ Future runOutgoingStanzaHandlers( } } -XmppManagerAttributes mkAttributes(void Function(Stanza) callback) { +XmppManagerAttributes mkAttributes(void Function(StanzaDetails) callback) { return XmppManagerAttributes( sendStanza: (StanzaDetails details) async { - callback(details.stanza); + callback(details); return Stanza.message(); }, @@ -506,7 +507,7 @@ void main() { StreamManagementNegotiator()..resource = 'test-resource', ]); await conn.getManagerById(smManager)!.setState( - StreamManagementState( + const StreamManagementState( 10, 10, streamResumptionId: 'id-1', @@ -605,7 +606,7 @@ void main() { StreamManagementNegotiator()..resource = 'abc123', ]); await conn.getManagerById(smManager)!.setState( - StreamManagementState( + const StreamManagementState( 10, 10, streamResumptionId: 'id-1', @@ -750,7 +751,7 @@ void main() { StreamManagementNegotiator()..resource = 'abc123', ]); await conn.getManagerById(smManager)!.setState( - StreamManagementState( + const StreamManagementState( 10, 10, streamResumptionId: 'id-1', @@ -1069,4 +1070,49 @@ void main() { expect(smn.streamEnablementFailed, true); expect(conn.resource, 'test-resource'); }); + + test('Test resending state changes', () async { + final manager = StreamManagementManager(); + final attributes = mkAttributes((details) async { + for (final handler in manager.getOutgoingPostStanzaHandlers()) { + await handler.callback( + details.stanza, + StanzaHandlerData( + false, + false, + stanza, + details.postSendExtensions ?? TypedMap(), + ), + ); + } + }); + manager.register(attributes); + + await manager.onXmppEvent( + StreamManagementEnabledEvent(resource: 'hallo'), + ); + + // Send a stanza 5 times + for (var i = 0; i < 5; i++) { + await runOutgoingStanzaHandlers(manager, stanza); + } + + // + await manager.runNonzaHandlers(mkAck(3)); + //expect(manager.getUnackedStanzas().length, 2); + final oldC2s = manager.state.c2s; + final oldQueue = Map.from(manager.getUnackedStanzas()); + + // Disconnect and reconnect + await manager.onXmppEvent(ConnectingEvent()); + await manager.onXmppEvent(StreamResumedEvent(h: 3)); + + expect(manager.state.c2s, oldC2s); + expect(manager.getUnackedStanzas(), oldQueue); + + // Now they get acked + await manager.runNonzaHandlers(mkAck(5)); + expect(manager.getUnackedStanzas().length, 0); + expect(manager.state.c2s, 5); + }); } diff --git a/packages/moxxmpp/test/xeps/xep_0388_test.dart b/packages/moxxmpp/test/xeps/xep_0388_test.dart index f11e98a..8fa81ab 100644 --- a/packages/moxxmpp/test/xeps/xep_0388_test.dart +++ b/packages/moxxmpp/test/xeps/xep_0388_test.dart @@ -1,6 +1,8 @@ import 'package:collection/collection.dart'; +import 'package:moxlib/moxlib.dart'; import 'package:moxxmpp/moxxmpp.dart'; import 'package:test/test.dart'; + import '../helpers/logging.dart'; import '../helpers/xmpp.dart'; diff --git a/packages/moxxmpp_socket_tcp/integration_test/badxmpp_certificate_test.dart b/packages/moxxmpp_socket_tcp/integration_test/badxmpp_certificate_test.dart index 6a7f8d9..25e7aa9 100644 --- a/packages/moxxmpp_socket_tcp/integration_test/badxmpp_certificate_test.dart +++ b/packages/moxxmpp_socket_tcp/integration_test/badxmpp_certificate_test.dart @@ -17,25 +17,15 @@ Future _runTest(String domain) async { final connection = XmppConnection( TestingReconnectionPolicy(), AlwaysConnectedConnectivityManager(), + ClientToServerNegotiator(), socket, - ); + )..connectionSettings = ConnectionSettings( + jid: JID.fromString('testuser@$domain'), + password: 'abc123', + ); await connection.registerFeatureNegotiators([ StartTlsNegotiator(), ]); - await connection.registerManagers([ - DiscoManager([]), - RosterManager(TestingRosterStateManager('', [])), - MessageManager(), - PresenceManager(), - ]); - - connection.setConnectionSettings( - ConnectionSettings( - jid: JID.fromString('testuser@$domain'), - password: 'abc123', - useDirectTLS: true, - ), - ); final result = await connection.connect( shouldReconnect: false, diff --git a/packages/moxxmpp_socket_tcp/integration_test/failure_reconnection_test.dart b/packages/moxxmpp_socket_tcp/integration_test/failure_reconnection_test.dart index 5ede85d..d9eb4d0 100644 --- a/packages/moxxmpp_socket_tcp/integration_test/failure_reconnection_test.dart +++ b/packages/moxxmpp_socket_tcp/integration_test/failure_reconnection_test.dart @@ -18,17 +18,15 @@ void main() { final connection = XmppConnection( TestingSleepReconnectionPolicy(10), AlwaysConnectedConnectivityManager(), + ClientToServerNegotiator(), TCPSocketWrapper(), - ); + )..connectionSettings = ConnectionSettings( + jid: JID.fromString('testuser@no-sasl.badxmpp.eu'), + password: 'abc123', + ); await connection.registerFeatureNegotiators([ StartTlsNegotiator(), ]); - await connection.registerManagers([ - DiscoManager([]), - RosterManager(TestingRosterStateManager('', [])), - MessageManager(), - PresenceManager(), - ]); connection.asBroadcastStream().listen((event) { if (event is ConnectionStateChangedEvent) { if (event.state == XmppConnectionState.error) { @@ -37,14 +35,6 @@ void main() { } }); - connection.setConnectionSettings( - ConnectionSettings( - jid: JID.fromString('testuser@no-sasl.badxmpp.eu'), - password: 'abc123', - useDirectTLS: true, - ), - ); - final result = await connection.connect( shouldReconnect: false, waitUntilLogin: true, @@ -68,17 +58,15 @@ void main() { final connection = XmppConnection( TestingReconnectionPolicy(), AlwaysConnectedConnectivityManager(), + ClientToServerNegotiator(), TCPSocketWrapper(), - ); + )..connectionSettings = ConnectionSettings( + jid: JID.fromString('testuser@no-sasl.badxmpp.eu'), + password: 'abc123', + ); await connection.registerFeatureNegotiators([ StartTlsNegotiator(), ]); - await connection.registerManagers([ - DiscoManager([]), - RosterManager(TestingRosterStateManager('', [])), - MessageManager(), - PresenceManager(), - ]); connection.asBroadcastStream().listen((event) { if (event is ConnectionStateChangedEvent) { if (event.state == XmppConnectionState.error) { @@ -87,14 +75,6 @@ void main() { } }); - connection.setConnectionSettings( - ConnectionSettings( - jid: JID.fromString('testuser@no-sasl.badxmpp.eu'), - password: 'abc123', - useDirectTLS: true, - ), - ); - final result = await connection.connect( shouldReconnect: false, waitUntilLogin: true, diff --git a/packages/moxxmpp_socket_tcp/pubspec.yaml b/packages/moxxmpp_socket_tcp/pubspec.yaml index 1999f9a..380f343 100644 --- a/packages/moxxmpp_socket_tcp/pubspec.yaml +++ b/packages/moxxmpp_socket_tcp/pubspec.yaml @@ -12,7 +12,13 @@ dependencies: meta: ^1.6.0 moxxmpp: hosted: https://git.polynom.me/api/packages/Moxxy/pub - version: ^0.3.0 + version: ^0.4.0 + +dependency_overrides: + moxxmpp: + git: + url: https://codeberg.org/moxxy/moxxmpp.git + rev: 05e3d804a4036e9cd93fd27473a1e970fda3c3fc dev_dependencies: lints: ^2.0.0 diff --git a/packages/moxxmpp_socket_tcp/pubspec_overrides.yaml b/packages/moxxmpp_socket_tcp/pubspec_overrides.yaml index 98626b5..ad07205 100644 --- a/packages/moxxmpp_socket_tcp/pubspec_overrides.yaml +++ b/packages/moxxmpp_socket_tcp/pubspec_overrides.yaml @@ -1,4 +1,4 @@ # melos_managed_dependency_overrides: moxxmpp dependency_overrides: moxxmpp: - path: ../moxxmpp + path: ../moxxmpp \ No newline at end of file