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..8478ba1 100644 --- a/examples_dart/pubspec.yaml +++ b/examples_dart/pubspec.yaml @@ -7,6 +7,9 @@ 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,6 +17,9 @@ 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.0 dependency_overrides: moxxmpp: 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..3960a67 100644 --- a/packages/moxxmpp/CHANGELOG.md +++ b/packages/moxxmpp/CHANGELOG.md @@ -16,6 +16,7 @@ - **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. ## 0.3.1 diff --git a/packages/moxxmpp/lib/moxxmpp.dart b/packages/moxxmpp/lib/moxxmpp.dart index 10cbb94..221ca5e 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..223551a 100644 --- a/packages/moxxmpp/lib/src/connection.dart +++ b/packages/moxxmpp/lib/src/connection.dart @@ -25,7 +25,6 @@ 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'; @@ -479,6 +478,7 @@ class XmppConnection { newStanza, TypedMap(), encrypted: details.encrypted, + shouldEncrypt: details.shouldEncrypt, forceEncryption: details.forceEncryption, ), ); @@ -736,6 +736,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 +760,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 bae047e..9700ee6 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..d53d349 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,6 @@ 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'; /// 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 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..41db9f6 100644 --- a/packages/moxxmpp/lib/src/stanza.dart +++ b/packages/moxxmpp/lib/src/stanza.dart @@ -7,6 +7,7 @@ class StanzaDetails { this.stanza, { this.addId = true, this.awaitable = true, + this.shouldEncrypt = true, this.encrypted = false, this.forceEncryption = false, this.bypassQueue = false, @@ -22,9 +23,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. 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/xep_0030.dart b/packages/moxxmpp/lib/src/xeps/xep_0030/xep_0030.dart index 903304e..09443d2 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'; @@ -255,7 +255,7 @@ class DiscoManager extends XmppManagerBase { Future> discoInfoQuery( JID entity, { String? node, - bool shouldEncrypt = true, + bool shouldEncrypt = false, bool shouldCache = true, }) async { DiscoInfo? info; @@ -294,7 +294,7 @@ class DiscoManager extends XmppManagerBase { final stanza = (await getAttributes().sendStanza( StanzaDetails( buildDiscoInfoQueryStanza(entity, node), - encrypted: !shouldEncrypt, + shouldEncrypt: shouldEncrypt, ), ))!; final query = stanza.firstTag('query'); @@ -325,7 +325,7 @@ class DiscoManager extends XmppManagerBase { Future>> discoItemsQuery( JID entity, { String? node, - bool shouldEncrypt = true, + bool shouldEncrypt = false, }) async { final key = DiscoCacheKey(entity, node); final future = await _discoItemsTracker.waitFor(key); 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..661274c 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,6 @@ 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 +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/xeps/xep_0004.dart'; import 'package:moxxmpp/src/xeps/xep_0030/errors.dart'; import 'package:moxxmpp/src/xeps/xep_0030/types.dart'; @@ -202,6 +202,7 @@ class PubSubManager extends XmppManagerBase { ), ], ), + shouldEncrypt: false, ), ))!; @@ -245,6 +246,7 @@ class PubSubManager extends XmppManagerBase { ), ], ), + shouldEncrypt: false, ), ))!; @@ -329,6 +331,7 @@ class PubSubManager extends XmppManagerBase { ) ], ), + shouldEncrypt: false, ), ))!; if (result.attributes['type'] != 'result') { @@ -419,6 +422,7 @@ class PubSubManager extends XmppManagerBase { ) ], ), + shouldEncrypt: false, ), ))!; @@ -471,6 +475,7 @@ class PubSubManager extends XmppManagerBase { ), ], ), + shouldEncrypt: false, ), ))!; @@ -521,6 +526,7 @@ class PubSubManager extends XmppManagerBase { ), ], ), + shouldEncrypt: false, ), ))!; if (form.attributes['type'] != 'result') { @@ -550,6 +556,7 @@ class PubSubManager extends XmppManagerBase { ), ], ), + shouldEncrypt: false, ), ))!; if (submit.attributes['type'] != 'result') { @@ -580,6 +587,7 @@ class PubSubManager extends XmppManagerBase { ), ], ), + shouldEncrypt: false, ), ))!; @@ -624,6 +632,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..d96f2e8 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'; 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..bb1331a 100644 --- a/packages/moxxmpp/lib/src/xeps/xep_0384/types.dart +++ b/packages/moxxmpp/lib/src/xeps/xep_0384/types.dart @@ -13,9 +13,8 @@ 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; } 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..2ea82c3 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( @@ -532,7 +548,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 +569,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 +585,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 +654,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/pubspec.yaml b/packages/moxxmpp/pubspec.yaml index 3198a24..6032af7 100644 --- a/packages/moxxmpp/pubspec.yaml +++ b/packages/moxxmpp/pubspec.yaml @@ -18,10 +18,10 @@ dependencies: 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.0 random_string: ^2.3.1 saslprep: ^1.0.2 synchronized: ^3.0.0+2 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_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/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