From c2f62e29675029e148be9f6576ee060200f6da30 Mon Sep 17 00:00:00 2001 From: "Alexander \"PapaTutuWawa" Date: Sat, 17 Jun 2023 21:28:54 +0200 Subject: [PATCH 01/15] feat(xep): Implement an OMEMO example client --- examples_dart/bin/component.dart | 24 +-- examples_dart/bin/omemo_client.dart | 140 ++++++++++++++++++ examples_dart/pubspec.yaml | 5 + integration_tests/create_users.sh | 2 + packages/moxxmpp/lib/src/connection.dart | 9 ++ packages/moxxmpp/lib/src/events.dart | 4 +- packages/moxxmpp/lib/src/managers/data.dart | 14 +- packages/moxxmpp/lib/src/message.dart | 11 +- packages/moxxmpp/lib/src/stanza.dart | 10 +- packages/moxxmpp/lib/src/util/queue.dart | 6 +- packages/moxxmpp/lib/src/util/typed_map.dart | 2 + .../lib/src/xeps/xep_0030/xep_0030.dart | 6 +- .../lib/src/xeps/xep_0060/xep_0060.dart | 9 ++ .../moxxmpp/lib/src/xeps/xep_0384/types.dart | 4 +- .../lib/src/xeps/xep_0384/xep_0384.dart | 117 ++++++++------- packages/moxxmpp/pubspec.yaml | 5 +- packages/moxxmpp/test/async_queue_test.dart | 4 - .../moxxmpp_socket_tcp/pubspec_overrides.yaml | 2 +- 18 files changed, 285 insertions(+), 89 deletions(-) create mode 100644 examples_dart/bin/omemo_client.dart create mode 100644 integration_tests/create_users.sh 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..183f999 --- /dev/null +++ b/examples_dart/bin/omemo_client.dart @@ -0,0 +1,140 @@ +import 'package:args/args.dart'; +import 'package:chalkdart/chalk.dart'; +import 'package:cli_repl/cli_repl.dart'; +import 'package:logging/logging.dart'; +import 'package:moxxmpp/moxxmpp.dart'; +import 'package:moxxmpp_socket_tcp/moxxmpp_socket_tcp.dart'; +import 'package:omemo_dart/omemo_dart.dart' as omemo; + +class TestingOmemoManager extends BaseOmemoManager { + TestingOmemoManager(this._encryptToJid); + + final JID _encryptToJid; + + late omemo.OmemoManager manager; + + @override + Future getOmemoManager() async { + return manager; + } + + @override + Future shouldEncryptStanza(JID toJid, Stanza stanza) async { + return toJid.toBare() == _encryptToJid; + } +} + +class TestingTCPSocketWrapper extends TCPSocketWrapper { + @override + bool onBadCertificate(dynamic certificate, String domain) { + return true; + } +} + +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 = ArgParser() + ..addOption('jid') + ..addOption('password') + ..addOption('host') + ..addOption('port') + ..addOption('to'); + final options = parser.parse(args); + + // Connect + final jid = JID.fromString(options['jid']! as String); + final to = JID.fromString(options['to']! as String).toBare(); + final portString = options['port'] as String?; + final connection = XmppConnection( + TestingReconnectionPolicy(), + AlwaysConnectedConnectivityManager(), + ClientToServerNegotiator(), + TestingTCPSocketWrapper(), + )..connectionSettings = ConnectionSettings( + jid: jid, + password: options['password']! as String, + host: options['host'] as String?, + port: portString != null ? int.parse(portString) : null, + ); + + // Generate OMEMO data + final moxxmppOmemo = TestingOmemoManager(to); + final omemoManager = omemo.OmemoManager( + await omemo.OmemoDevice.generateNewDevice(jid.toString(), opkAmount: 5), + omemo.BlindTrustBeforeVerificationTrustManager(), + moxxmppOmemo.sendEmptyMessageImpl, + moxxmppOmemo.fetchDeviceList, + moxxmppOmemo.fetchDeviceBundle, + moxxmppOmemo.subscribeToDeviceListImpl, + ); + moxxmppOmemo.manager = omemoManager; + final deviceId = await omemoManager.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 moxxmppOmemo.manager.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/pubspec.yaml b/examples_dart/pubspec.yaml index 6d95fe3..5f4e285 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,8 @@ dependencies: moxxmpp_socket_tcp: hosted: https://git.polynom.me/api/packages/Moxxy/pub version: 0.3.1 + omemo_dart: + path: ../../../Personal/omemo_dart 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/lib/src/connection.dart b/packages/moxxmpp/lib/src/connection.dart index 413d8a0..6a0442d 100644 --- a/packages/moxxmpp/lib/src/connection.dart +++ b/packages/moxxmpp/lib/src/connection.dart @@ -479,6 +479,7 @@ class XmppConnection { newStanza, TypedMap(), encrypted: details.encrypted, + shouldEncrypt: details.shouldEncrypt, forceEncryption: details.forceEncryption, ), ); @@ -736,6 +737,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 +761,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/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/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/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/util/queue.dart b/packages/moxxmpp/lib/src/util/queue.dart index b209bc4..b10047b 100644 --- a/packages/moxxmpp/lib/src/util/queue.dart +++ b/packages/moxxmpp/lib/src/util/queue.dart @@ -50,16 +50,12 @@ class AsyncStanzaQueue { @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()), ); 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/xep_0030/xep_0030.dart b/packages/moxxmpp/lib/src/xeps/xep_0030/xep_0030.dart index 903304e..98679de 100644 --- a/packages/moxxmpp/lib/src/xeps/xep_0030/xep_0030.dart +++ b/packages/moxxmpp/lib/src/xeps/xep_0030/xep_0030.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_0060/xep_0060.dart b/packages/moxxmpp/lib/src/xeps/xep_0060/xep_0060.dart index c840c37..a6d590d 100644 --- a/packages/moxxmpp/lib/src/xeps/xep_0060/xep_0060.dart +++ b/packages/moxxmpp/lib/src/xeps/xep_0060/xep_0060.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_0384/types.dart b/packages/moxxmpp/lib/src/xeps/xep_0384/types.dart index 038598c..9a6c4b8 100644 --- a/packages/moxxmpp/lib/src/xeps/xep_0384/types.dart +++ b/packages/moxxmpp/lib/src/xeps/xep_0384/types.dart @@ -16,6 +16,6 @@ class OmemoEncryptionError { const OmemoEncryptionError(this.jids, this.devices); /// See omemo_dart's EncryptionResult for info on these fields. - final Map jids; - final Map devices; + final Map jids; + final Map devices; } 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..e02a014 100644 --- a/packages/moxxmpp/lib/src/xeps/xep_0384/xep_0384.dart +++ b/packages/moxxmpp/lib/src/xeps/xep_0384/xep_0384.dart @@ -24,7 +24,7 @@ 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'; const _doNotEncryptList = [ @@ -113,7 +113,7 @@ 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)); @@ -121,13 +121,13 @@ abstract class BaseOmemoManager extends XmppManagerBase { } @visibleForOverriding - Future getOmemoManager(); + 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 _getDeviceBundle() async { + Future _getDeviceBundle() async { final om = await getOmemoManager(); final device = await om.getDevice(); return device.toBundle(); @@ -199,53 +199,43 @@ 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( + for (final keys in result.encryptedKeys.entries) { + keyElements[keys.key] = keys.value.map((ek) => XMLNode( tag: 'key', - attributes: { - 'rid': '${key.rid}', - 'kex': key.kex ? 'true' : 'false', + attributes: { + 'rid': ek.rid.toString(), + if (ek.kex) + 'kex': 'true', }, - text: key.value, - ); - - if (keyElements.containsKey(key.jid)) { - keyElements[key.jid]!.add(keyElement); - } else { - keyElements[key.jid] = [keyElement]; - } + 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 +249,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( @@ -301,17 +291,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; @@ -352,29 +347,31 @@ abstract class BaseOmemoManager extends XmppManagerBase { ?.isEnabled ?? false; 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, + // TODO + ..encryptionError = const OmemoEncryptionError( + {}, + {}, ); } @@ -411,20 +408,19 @@ abstract class BaseOmemoManager extends XmppManagerBase { 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); + // TODO: Only extract our own keys by the JID attribute + final keys = List.empty(growable: true); for (final keysElement in header.findTags('keys')) { final jid = keysElement.attributes['jid']! as String; for (final key in keysElement.findTags('key')) { keys.add( - EncryptedKey( - jid, + omemo.EncryptedKey( int.parse(key.attributes['rid']! as String), key.innerText(), key.attributes['kex'] == 'true', @@ -438,7 +434,7 @@ abstract class BaseOmemoManager extends XmppManagerBase { final om = await getOmemoManager(); final result = await om.onIncomingStanza( - OmemoIncomingStanza( + omemo.OmemoIncomingStanza( fromJid.toString(), sid, state.extensions @@ -448,6 +444,7 @@ abstract class BaseOmemoManager extends XmppManagerBase { DateTime.now().millisecondsSinceEpoch, keys, payloadElement?.innerText(), + false, ), ); @@ -464,6 +461,7 @@ abstract class BaseOmemoManager extends XmppManagerBase { .toList(); } + logger.finest('Got payload: ${result.payload != null}'); if (result.payload != null) { XMLNode envelope; try { @@ -481,6 +479,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 +490,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 +541,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 +562,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 +578,7 @@ 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(); @@ -642,7 +651,7 @@ abstract class BaseOmemoManager extends XmppManagerBase { /// On failure, returns an OmemoError. Future> supportsOmemo(JID jid) async { final dm = getAttributes().getManagerById(discoManager)!; - final items = await dm.discoItemsQuery(jid.toBare()); + final items = await dm.discoItemsQuery(jid.toBare(), shouldEncrypt: false); if (items.isType()) return Result(UnknownOmemoError()); diff --git a/packages/moxxmpp/pubspec.yaml b/packages/moxxmpp/pubspec.yaml index 3198a24..e7aeb68 100644 --- a/packages/moxxmpp/pubspec.yaml +++ b/packages/moxxmpp/pubspec.yaml @@ -18,10 +18,9 @@ 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 + path: ../../../../Personal/omemo_dart 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_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 From 3cb5a568ce4a58c5953113e1221526e476a88c05 Mon Sep 17 00:00:00 2001 From: "Alexander \"PapaTutuWawa" Date: Sat, 17 Jun 2023 21:45:00 +0200 Subject: [PATCH 02/15] feat(xep,core): Migrate to moxlib's Result type --- packages/moxxmpp/lib/moxxmpp.dart | 1 - packages/moxxmpp/lib/src/connection.dart | 1 - packages/moxxmpp/lib/src/managers/base.dart | 1 - .../moxxmpp/lib/src/managers/handlers.dart | 10 +++--- .../lib/src/negotiators/negotiator.dart | 5 ++- packages/moxxmpp/lib/src/presence.dart | 2 +- .../src/rfcs/rfc_6120/resource_binding.dart | 2 +- .../src/rfcs/rfc_6120/sasl/negotiator.dart | 8 ++--- .../lib/src/rfcs/rfc_6120/sasl/plain.dart | 2 +- .../lib/src/rfcs/rfc_6120/sasl/scram.dart | 2 +- .../lib/src/rfcs/rfc_6120/starttls.dart | 2 +- packages/moxxmpp/lib/src/roster/roster.dart | 2 +- packages/moxxmpp/lib/src/types/result.dart | 16 --------- packages/moxxmpp/lib/src/util/queue.dart | 8 +---- .../moxxmpp/lib/src/xeps/staging/fast.dart | 2 +- packages/moxxmpp/lib/src/xeps/xep_0004.dart | 4 +-- .../lib/src/xeps/xep_0030/xep_0030.dart | 2 +- packages/moxxmpp/lib/src/xeps/xep_0054.dart | 2 +- .../lib/src/xeps/xep_0060/xep_0060.dart | 2 +- packages/moxxmpp/lib/src/xeps/xep_0084.dart | 3 +- .../lib/src/xeps/xep_0198/negotiator.dart | 2 +- packages/moxxmpp/lib/src/xeps/xep_0352.dart | 2 +- .../lib/src/xeps/xep_0363/xep_0363.dart | 10 +++--- .../lib/src/xeps/xep_0384/xep_0384.dart | 33 ++++++++++--------- packages/moxxmpp/lib/src/xeps/xep_0386.dart | 2 +- .../lib/src/xeps/xep_0388/negotiators.dart | 2 +- .../lib/src/xeps/xep_0388/xep_0388.dart | 2 +- packages/moxxmpp/lib/src/xeps/xep_0447.dart | 5 ++- packages/moxxmpp/lib/src/xeps/xep_0448.dart | 5 ++- packages/moxxmpp/lib/src/xeps/xep_0449.dart | 2 +- packages/moxxmpp/test/negotiator_test.dart | 2 ++ packages/moxxmpp/test/xeps/xep_0060_test.dart | 1 + packages/moxxmpp/test/xeps/xep_0115_test.dart | 1 + packages/moxxmpp/test/xeps/xep_0388_test.dart | 2 ++ 34 files changed, 63 insertions(+), 85 deletions(-) delete mode 100644 packages/moxxmpp/lib/src/types/result.dart 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 6a0442d..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'; 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/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/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/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 b10047b..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,9 +44,6 @@ class AsyncStanzaQueue { final CanSendCallback _canSendCallback; - /// Indicates whether we are currently executing a job. - bool _running = false; - @visibleForTesting Queue get queue => _queue; @@ -75,8 +72,6 @@ class AsyncStanzaQueue { unawaited( _runJob(_queue.removeFirst()), ); - } else { - _running = false; } }); } @@ -86,7 +81,6 @@ class AsyncStanzaQueue { await _lock.synchronized(() { if (_queue.isNotEmpty) { - _running = true; unawaited( _runJob(_queue.removeFirst()), ); 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 98679de..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'; 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 a6d590d..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'; 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/xep_0384.dart b/packages/moxxmpp/lib/src/xeps/xep_0384/xep_0384.dart index e02a014..b94537d 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,7 +11,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'; @@ -205,15 +205,18 @@ abstract class BaseOmemoManager extends XmppManagerBase { ) { final keyElements = >{}; 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(); + 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) { @@ -235,7 +238,6 @@ abstract class BaseOmemoManager extends XmppManagerBase { tag: 'payload', text: base64Encode(result.ciphertext!), ), - XMLNode( tag: 'header', attributes: { @@ -364,8 +366,8 @@ abstract class BaseOmemoManager extends XmppManagerBase { ..cancel = true // If we have no device list for toJid, then the contact most likely does not // support OMEMO:2 - ..cancelReason = result.deviceEncryptionErrors[toJid.toString()]!.first.error - is omemo.NoKeyMaterialAvailableError + ..cancelReason = result.deviceEncryptionErrors[toJid.toString()]!.first + .error is omemo.NoKeyMaterialAvailableError ? OmemoNotSupportedForContactException() : UnknownOmemoError() // TODO @@ -578,7 +580,8 @@ abstract class BaseOmemoManager extends XmppManagerBase { /// nodes. /// /// On success, returns true. On failure, returns an OmemoError. - Future> publishBundle(omemo.OmemoBundle bundle) async { + Future> publishBundle( + omemo.OmemoBundle bundle,) async { final attrs = getAttributes(); final pm = attrs.getManagerById(pubsubManager)!; final bareJid = attrs.getFullJID().toBare(); @@ -651,7 +654,7 @@ abstract class BaseOmemoManager extends XmppManagerBase { /// On failure, returns an OmemoError. Future> supportsOmemo(JID jid) async { final dm = getAttributes().getManagerById(discoManager)!; - final items = await dm.discoItemsQuery(jid.toBare(), shouldEncrypt: false); + final items = await dm.discoItemsQuery(jid.toBare()); if (items.isType()) return Result(UnknownOmemoError()); 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/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'; From 8252472faed71ddbd47c8b3a153e93045fed7ebd Mon Sep 17 00:00:00 2001 From: "Alexander \"PapaTutuWawa" Date: Sat, 17 Jun 2023 23:37:08 +0200 Subject: [PATCH 03/15] feat(xep): Adjust to omemo_dart changes --- .../moxxmpp/lib/src/xeps/xep_0384/types.dart | 7 ++- .../lib/src/xeps/xep_0384/xep_0384.dart | 44 +++++++++---------- 2 files changed, 23 insertions(+), 28 deletions(-) diff --git a/packages/moxxmpp/lib/src/xeps/xep_0384/types.dart b/packages/moxxmpp/lib/src/xeps/xep_0384/types.dart index 9a6c4b8..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 b94537d..bf3fc07 100644 --- a/packages/moxxmpp/lib/src/xeps/xep_0384/xep_0384.dart +++ b/packages/moxxmpp/lib/src/xeps/xep_0384/xep_0384.dart @@ -16,7 +16,6 @@ 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'; @@ -370,10 +369,8 @@ abstract class BaseOmemoManager extends XmppManagerBase { .error is omemo.NoKeyMaterialAvailableError ? OmemoNotSupportedForContactException() : UnknownOmemoError() - // TODO - ..encryptionError = const OmemoEncryptionError( - {}, - {}, + ..encryptionError = OmemoEncryptionError( + result.deviceEncryptionErrors, ); } @@ -415,37 +412,35 @@ abstract class BaseOmemoManager extends XmppManagerBase { final encrypted = stanza.firstTag('encrypted', xmlns: omemoXmlns)!; final fromJid = JID.fromString(stanza.from!).toBare(); final header = encrypted.firstTag('header')!; - final payloadElement = encrypted.firstTag('payload'); - // TODO: Only extract our own keys by the JID attribute + 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( - omemo.EncryptedKey( - 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 result = await om.onIncomingStanza( omemo.OmemoIncomingStanza( fromJid.toString(), sid, - state.extensions - .get() - ?.timestamp - .millisecondsSinceEpoch ?? - DateTime.now().millisecondsSinceEpoch, keys, - payloadElement?.innerText(), + encrypted.firstTag('payload')?.innerText(), false, ), ); @@ -581,7 +576,8 @@ abstract class BaseOmemoManager extends XmppManagerBase { /// /// On success, returns true. On failure, returns an OmemoError. Future> publishBundle( - omemo.OmemoBundle bundle,) async { + omemo.OmemoBundle bundle, + ) async { final attrs = getAttributes(); final pm = attrs.getManagerById(pubsubManager)!; final bareJid = attrs.getFullJID().toBare(); From 9fd2daabb215ee7ee31abf629cb364268a29b6c2 Mon Sep 17 00:00:00 2001 From: "Alexander \"PapaTutuWawa" Date: Sat, 17 Jun 2023 23:51:52 +0200 Subject: [PATCH 04/15] feat(xep): Adjust to more omemo_dart changes --- examples_dart/bin/omemo_client.dart | 34 ++++--------- .../lib/src/xeps/xep_0384/xep_0384.dart | 51 ++++++++++++------- 2 files changed, 43 insertions(+), 42 deletions(-) diff --git a/examples_dart/bin/omemo_client.dart b/examples_dart/bin/omemo_client.dart index 183f999..2aef2fd 100644 --- a/examples_dart/bin/omemo_client.dart +++ b/examples_dart/bin/omemo_client.dart @@ -6,24 +6,6 @@ import 'package:moxxmpp/moxxmpp.dart'; import 'package:moxxmpp_socket_tcp/moxxmpp_socket_tcp.dart'; import 'package:omemo_dart/omemo_dart.dart' as omemo; -class TestingOmemoManager extends BaseOmemoManager { - TestingOmemoManager(this._encryptToJid); - - final JID _encryptToJid; - - late omemo.OmemoManager manager; - - @override - Future getOmemoManager() async { - return manager; - } - - @override - Future shouldEncryptStanza(JID toJid, Stanza stanza) async { - return toJid.toBare() == _encryptToJid; - } -} - class TestingTCPSocketWrapper extends TCPSocketWrapper { @override bool onBadCertificate(dynamic certificate, String domain) { @@ -66,17 +48,21 @@ void main(List args) async { ); // Generate OMEMO data - final moxxmppOmemo = TestingOmemoManager(to); - final omemoManager = omemo.OmemoManager( + 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, ); - moxxmppOmemo.manager = omemoManager; - final deviceId = await omemoManager.getDeviceId(); + final deviceId = await oom.getDeviceId(); Logger.root.info('Our device id: $deviceId'); // Register the managers and negotiators @@ -85,7 +71,7 @@ void main(List args) async { DiscoManager([]), PubSubManager(), MessageManager(), - moxxmppOmemo, + moxxmppOmemo, ]); await connection.registerFeatureNegotiators([ SaslPlainNegotiator(), @@ -118,7 +104,7 @@ void main(List args) async { // Publish our bundle Logger.root.info('Publishing bundle'); - final device = await moxxmppOmemo.manager.getDevice(); + 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()}'); 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 bf3fc07..2ea82c3 100644 --- a/packages/moxxmpp/lib/src/xeps/xep_0384/xep_0384.dart +++ b/packages/moxxmpp/lib/src/xeps/xep_0384/xep_0384.dart @@ -26,6 +26,18 @@ import 'package:moxxmpp/src/xeps/xep_0384/types.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), @@ -42,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 @@ -112,22 +131,19 @@ abstract class BaseOmemoManager extends XmppManagerBase { } // Tell the OmemoManager - await (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(); + Future _getDeviceId() async => (await _getOmemoManager()).getDeviceId(); /// Wrapper around using getSessionManager and then calling getDeviceId on it. Future _getDeviceBundle() async { - final om = await getOmemoManager(); + final om = await _getOmemoManager(); final device = await om.getDevice(); return device.toBundle(); } @@ -279,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); } @@ -320,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.', @@ -347,7 +363,7 @@ 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(), @@ -397,12 +413,6 @@ 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, @@ -434,7 +444,7 @@ abstract class BaseOmemoManager extends XmppManagerBase { } final sid = int.parse(header.attributes['sid']! as String); - final om = await getOmemoManager(); + final om = await _getOmemoManager(); final result = await om.onIncomingStanza( omemo.OmemoIncomingStanza( fromJid.toString(), @@ -644,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. From fbbe4131487812abd9b95ac00a85477d89fbb061 Mon Sep 17 00:00:00 2001 From: "Alexander \"PapaTutuWawa" Date: Sun, 18 Jun 2023 20:59:54 +0200 Subject: [PATCH 05/15] feat(example): Improve the example code --- examples_dart/bin/omemo_client.dart | 64 +++++++++++++++++------------ examples_dart/lib/socket.dart | 22 ++++++++++ 2 files changed, 60 insertions(+), 26 deletions(-) create mode 100644 examples_dart/lib/socket.dart diff --git a/examples_dart/bin/omemo_client.dart b/examples_dart/bin/omemo_client.dart index 2aef2fd..53bf7e3 100644 --- a/examples_dart/bin/omemo_client.dart +++ b/examples_dart/bin/omemo_client.dart @@ -1,18 +1,12 @@ import 'package:args/args.dart'; import 'package:chalkdart/chalk.dart'; import 'package:cli_repl/cli_repl.dart'; +import 'package:example_dart/socket.dart'; import 'package:logging/logging.dart'; import 'package:moxxmpp/moxxmpp.dart'; import 'package:moxxmpp_socket_tcp/moxxmpp_socket_tcp.dart'; import 'package:omemo_dart/omemo_dart.dart' as omemo; -class TestingTCPSocketWrapper extends TCPSocketWrapper { - @override - bool onBadCertificate(dynamic certificate, String domain) { - return true; - } -} - void main(List args) async { // Set up logging Logger.root.level = Level.ALL; @@ -28,9 +22,23 @@ void main(List args) async { ..addOption('password') ..addOption('host') ..addOption('port') - ..addOption('to'); + ..addOption('to') + ..addOption('xmpps-srv'); final options = parser.parse(args); + // Parse a potential xmpps-client SRV record here. + // Format: --xmpps-srv ,,, + MoxSrvRecord? srvRecord; + if (options['xmpps-srv'] != null) { + final parts = (options['xmpps-srv']! as String).split(','); + srvRecord = MoxSrvRecord( + int.parse(parts[0]), + int.parse(parts[1]), + parts[2], + int.parse(parts[3]), + ); + } + // Connect final jid = JID.fromString(options['jid']! as String); final to = JID.fromString(options['to']! as String).toBare(); @@ -39,13 +47,13 @@ void main(List args) async { TestingReconnectionPolicy(), AlwaysConnectedConnectivityManager(), ClientToServerNegotiator(), - TestingTCPSocketWrapper(), + ExampleTCPSocketWrapper(srvRecord), )..connectionSettings = ConnectionSettings( - jid: jid, - password: options['password']! as String, - host: options['host'] as String?, - port: portString != null ? int.parse(portString) : null, - ); + jid: jid, + password: options['password']! as String, + host: options['host'] as String?, + port: portString != null ? int.parse(portString) : null, + ); // Generate OMEMO data omemo.OmemoManager? oom; @@ -71,7 +79,7 @@ void main(List args) async { DiscoManager([]), PubSubManager(), MessageManager(), - moxxmppOmemo, + moxxmppOmemo, ]); await connection.registerFeatureNegotiators([ SaslPlainNegotiator(), @@ -87,15 +95,16 @@ void main(List args) async { 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); + ? 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); + final result = + await connection.connect(shouldReconnect: false, waitUntilLogin: true); if (!result.isType()) { Logger.root.severe('Authentication failed!'); return; @@ -107,18 +116,21 @@ void main(List args) async { 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()}'); + 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), - ]), - ); + await connection + .getManagerById(messageManager)! + .sendMessage( + to, + TypedMap.fromList([ + MessageBodyData(line), + ]), + ); } // Disconnect 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!, + ]; + } +} From e3ca83670a9c1c55ed3ac1bc5b3b18987afef499 Mon Sep 17 00:00:00 2001 From: "Alexander \"PapaTutuWawa" Date: Sun, 18 Jun 2023 21:16:47 +0200 Subject: [PATCH 06/15] feat(example): Implement common argument parsing --- examples_dart/bin/omemo_client.dart | 40 ++++---------- examples_dart/lib/arguments.dart | 84 +++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+), 31 deletions(-) create mode 100644 examples_dart/lib/arguments.dart diff --git a/examples_dart/bin/omemo_client.dart b/examples_dart/bin/omemo_client.dart index 53bf7e3..5241d03 100644 --- a/examples_dart/bin/omemo_client.dart +++ b/examples_dart/bin/omemo_client.dart @@ -1,10 +1,9 @@ -import 'package:args/args.dart'; 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:moxxmpp_socket_tcp/moxxmpp_socket_tcp.dart'; import 'package:omemo_dart/omemo_dart.dart' as omemo; void main(List args) async { @@ -17,43 +16,22 @@ void main(List args) async { ); }); - final parser = ArgParser() - ..addOption('jid') - ..addOption('password') - ..addOption('host') - ..addOption('port') - ..addOption('to') - ..addOption('xmpps-srv'); - final options = parser.parse(args); - - // Parse a potential xmpps-client SRV record here. - // Format: --xmpps-srv ,,, - MoxSrvRecord? srvRecord; - if (options['xmpps-srv'] != null) { - final parts = (options['xmpps-srv']! as String).split(','); - srvRecord = MoxSrvRecord( - int.parse(parts[0]), - int.parse(parts[1]), - parts[2], - int.parse(parts[3]), - ); + 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 = JID.fromString(options['jid']! as String); + final jid = parser.jid; final to = JID.fromString(options['to']! as String).toBare(); - final portString = options['port'] as String?; final connection = XmppConnection( TestingReconnectionPolicy(), AlwaysConnectedConnectivityManager(), ClientToServerNegotiator(), - ExampleTCPSocketWrapper(srvRecord), - )..connectionSettings = ConnectionSettings( - jid: jid, - password: options['password']! as String, - host: options['host'] as String?, - port: portString != null ? int.parse(portString) : null, - ); + ExampleTCPSocketWrapper(parser.srvRecord), + )..connectionSettings = parser.connectionSettings; // Generate OMEMO data omemo.OmemoManager? oom; 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]), + ); + } +} From 9da6d319a32c6740ae0378da9c85fce4e7ae8fd4 Mon Sep 17 00:00:00 2001 From: "Alexander \"PapaTutuWawa" Date: Sun, 18 Jun 2023 21:30:35 +0200 Subject: [PATCH 07/15] feat(all): Use 0.5.0 of omemo_dart --- examples_dart/pubspec.yaml | 3 ++- packages/moxxmpp/pubspec.yaml | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/examples_dart/pubspec.yaml b/examples_dart/pubspec.yaml index 5f4e285..8478ba1 100644 --- a/examples_dart/pubspec.yaml +++ b/examples_dart/pubspec.yaml @@ -18,7 +18,8 @@ dependencies: hosted: https://git.polynom.me/api/packages/Moxxy/pub version: 0.3.1 omemo_dart: - path: ../../../Personal/omemo_dart + hosted: https://git.polynom.me/api/packages/PapaTutuWawa/pub + version: ^0.5.0 dependency_overrides: moxxmpp: diff --git a/packages/moxxmpp/pubspec.yaml b/packages/moxxmpp/pubspec.yaml index e7aeb68..6032af7 100644 --- a/packages/moxxmpp/pubspec.yaml +++ b/packages/moxxmpp/pubspec.yaml @@ -20,7 +20,8 @@ dependencies: hosted: https://git.polynom.me/api/packages/Moxxy/pub version: ^0.2.0 omemo_dart: - path: ../../../../Personal/omemo_dart + hosted: https://git.polynom.me/api/packages/PapaTutuWawa/pub + version: ^0.5.0 random_string: ^2.3.1 saslprep: ^1.0.2 synchronized: ^3.0.0+2 From 3621f2709ac6800d49de79066a2ba17f9fb04c46 Mon Sep 17 00:00:00 2001 From: "Alexander \"PapaTutuWawa" Date: Sun, 18 Jun 2023 21:57:53 +0200 Subject: [PATCH 08/15] chore(docs): Update changelog --- packages/moxxmpp/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) 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 From 2db44e2f512b7f1fd44aebb9e7731d422fe13f11 Mon Sep 17 00:00:00 2001 From: "Alexander \"PapaTutuWawa" Date: Mon, 19 Jun 2023 22:54:53 +0200 Subject: [PATCH 09/15] fix(xep): Fix resend behaviour leading to period disconnects It seems that we were expecting acks "in the future" for old stanzas. Also, this commit should prevent E2EE implementations from re-encrypting resent stanzas. Fixes #38. --- packages/moxxmpp/lib/src/connection.dart | 6 +- packages/moxxmpp/lib/src/presence.dart | 6 +- packages/moxxmpp/lib/src/stanza.dart | 6 +- .../moxxmpp/lib/src/xeps/xep_0198/types.dart | 33 +++++++++- .../lib/src/xeps/xep_0198/xep_0198.dart | 63 ++++++++++++++----- packages/moxxmpp/test/xeps/xep_0198_test.dart | 50 ++++++++++++++- 6 files changed, 138 insertions(+), 26 deletions(-) diff --git a/packages/moxxmpp/lib/src/connection.dart b/packages/moxxmpp/lib/src/connection.dart index 223551a..6d3527b 100644 --- a/packages/moxxmpp/lib/src/connection.dart +++ b/packages/moxxmpp/lib/src/connection.dart @@ -28,7 +28,6 @@ import 'package:moxxmpp/src/stringxml.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'; @@ -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'); diff --git a/packages/moxxmpp/lib/src/presence.dart b/packages/moxxmpp/lib/src/presence.dart index d53d349..d850f24 100644 --- a/packages/moxxmpp/lib/src/presence.dart +++ b/packages/moxxmpp/lib/src/presence.dart @@ -11,6 +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/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/stanza.dart b/packages/moxxmpp/lib/src/stanza.dart index 41db9f6..b7424b3 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 { @@ -11,7 +13,7 @@ class StanzaDetails { this.encrypted = false, this.forceEncryption = false, this.bypassQueue = false, - this.excludeFromStreamManagement = false, + this.postSendExtensions, }); /// The stanza to send. @@ -42,7 +44,7 @@ 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 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..3f11e28 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,7 +29,7 @@ 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); @@ -60,8 +61,8 @@ class StreamManagementManager extends XmppManagerBase { final Lock _ackLock = Lock(); /// Functions for testing - @visibleForTesting - Map getUnackedStanzas() => _unackedStanzas; + /// @visibleForTesting + 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/test/xeps/xep_0198_test.dart b/packages/moxxmpp/test/xeps/xep_0198_test.dart index 2ad54c4..7d86775 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(); }, @@ -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); + }); } From 30dca67fb6771e525f54c777cf2fd61ca19c85c5 Mon Sep 17 00:00:00 2001 From: "Alexander \"PapaTutuWawa" Date: Mon, 19 Jun 2023 23:11:58 +0200 Subject: [PATCH 10/15] feat(all): Remove freezed Fixes #43. --- .../lib/src/xeps/xep_0060/xep_0060.dart | 1 - .../lib/src/xeps/xep_0198/negotiator.dart | 2 +- .../moxxmpp/lib/src/xeps/xep_0198/state.dart | 53 +++-- .../lib/src/xeps/xep_0198/state.freezed.dart | 217 ------------------ .../lib/src/xeps/xep_0198/state.g.dart | 25 -- .../lib/src/xeps/xep_0198/xep_0198.dart | 2 +- packages/moxxmpp/lib/src/xeps/xep_0461.dart | 2 +- packages/moxxmpp/pubspec.yaml | 2 - packages/moxxmpp/test/xeps/xep_0198_test.dart | 6 +- 9 files changed, 45 insertions(+), 265 deletions(-) delete mode 100644 packages/moxxmpp/lib/src/xeps/xep_0198/state.freezed.dart delete mode 100644 packages/moxxmpp/lib/src/xeps/xep_0198/state.g.dart 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 661274c..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,4 +1,3 @@ -import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:meta/meta.dart'; import 'package:moxlib/moxlib.dart'; import 'package:moxxmpp/src/events.dart'; diff --git a/packages/moxxmpp/lib/src/xeps/xep_0198/negotiator.dart b/packages/moxxmpp/lib/src/xeps/xep_0198/negotiator.dart index d96f2e8..3468efb 100644 --- a/packages/moxxmpp/lib/src/xeps/xep_0198/negotiator.dart +++ b/packages/moxxmpp/lib/src/xeps/xep_0198/negotiator.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/xep_0198.dart b/packages/moxxmpp/lib/src/xeps/xep_0198/xep_0198.dart index 3f11e28..e294b3f 100644 --- a/packages/moxxmpp/lib/src/xeps/xep_0198/xep_0198.dart +++ b/packages/moxxmpp/lib/src/xeps/xep_0198/xep_0198.dart @@ -32,7 +32,7 @@ class StreamManagementManager extends XmppManagerBase { 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(); 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 6032af7..b6d7e95 100644 --- a/packages/moxxmpp/pubspec.yaml +++ b/packages/moxxmpp/pubspec.yaml @@ -10,8 +10,6 @@ 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 diff --git a/packages/moxxmpp/test/xeps/xep_0198_test.dart b/packages/moxxmpp/test/xeps/xep_0198_test.dart index 7d86775..146f803 100644 --- a/packages/moxxmpp/test/xeps/xep_0198_test.dart +++ b/packages/moxxmpp/test/xeps/xep_0198_test.dart @@ -507,7 +507,7 @@ void main() { StreamManagementNegotiator()..resource = 'test-resource', ]); await conn.getManagerById(smManager)!.setState( - StreamManagementState( + const StreamManagementState( 10, 10, streamResumptionId: 'id-1', @@ -606,7 +606,7 @@ void main() { StreamManagementNegotiator()..resource = 'abc123', ]); await conn.getManagerById(smManager)!.setState( - StreamManagementState( + const StreamManagementState( 10, 10, streamResumptionId: 'id-1', @@ -751,7 +751,7 @@ void main() { StreamManagementNegotiator()..resource = 'abc123', ]); await conn.getManagerById(smManager)!.setState( - StreamManagementState( + const StreamManagementState( 10, 10, streamResumptionId: 'id-1', From d35b9552590b9c7642110d9b9083a424c1c376ec Mon Sep 17 00:00:00 2001 From: "Alexander \"PapaTutuWawa" Date: Mon, 19 Jun 2023 23:15:29 +0200 Subject: [PATCH 11/15] fix(xep): Accidentally left getUnackedStanzas exposed --- packages/moxxmpp/lib/src/xeps/xep_0198/xep_0198.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 e294b3f..9daf2fc 100644 --- a/packages/moxxmpp/lib/src/xeps/xep_0198/xep_0198.dart +++ b/packages/moxxmpp/lib/src/xeps/xep_0198/xep_0198.dart @@ -61,7 +61,7 @@ class StreamManagementManager extends XmppManagerBase { final Lock _ackLock = Lock(); /// Functions for testing - /// @visibleForTesting + @visibleForTesting Map getUnackedStanzas() => _unackedStanzas; @visibleForTesting From b5efc2dfae81e648c08d5608b7b3d04e190ba82d Mon Sep 17 00:00:00 2001 From: "Alexander \"PapaTutuWawa" Date: Tue, 20 Jun 2023 16:35:51 +0200 Subject: [PATCH 12/15] feat(xep): Expose new/replaced ratchets in the MessageEvent --- packages/moxxmpp/lib/src/xeps/xep_0384/types.dart | 9 +++++++++ packages/moxxmpp/lib/src/xeps/xep_0384/xep_0384.dart | 6 ++++++ packages/moxxmpp/pubspec.yaml | 9 ++++++++- 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/packages/moxxmpp/lib/src/xeps/xep_0384/types.dart b/packages/moxxmpp/lib/src/xeps/xep_0384/types.dart index bb1331a..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. @@ -18,3 +19,11 @@ class OmemoEncryptionError { /// 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 2ea82c3..cdddcd8 100644 --- a/packages/moxxmpp/lib/src/xeps/xep_0384/xep_0384.dart +++ b/packages/moxxmpp/lib/src/xeps/xep_0384/xep_0384.dart @@ -516,6 +516,12 @@ class OmemoManager extends XmppManagerBase { children: children, tag: stanza.tag, attributes: Map.from(stanza.attributes), + ) + ..extensions.set( + OmemoData( + result.newRatchets, + result.replacedRatchets, + ), ); } diff --git a/packages/moxxmpp/pubspec.yaml b/packages/moxxmpp/pubspec.yaml index b6d7e95..e57a52b 100644 --- a/packages/moxxmpp/pubspec.yaml +++ b/packages/moxxmpp/pubspec.yaml @@ -19,7 +19,7 @@ dependencies: version: ^0.2.0 omemo_dart: hosted: https://git.polynom.me/api/packages/PapaTutuWawa/pub - version: ^0.5.0 + version: ^0.5.1 random_string: ^2.3.1 saslprep: ^1.0.2 synchronized: ^3.0.0+2 @@ -30,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 From 05e3d804a4036e9cd93fd27473a1e970fda3c3fc Mon Sep 17 00:00:00 2001 From: "Alexander \"PapaTutuWawa" Date: Tue, 20 Jun 2023 16:36:49 +0200 Subject: [PATCH 13/15] feat(example): Fix omemo_dart dependency --- examples_dart/pubspec.yaml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/examples_dart/pubspec.yaml b/examples_dart/pubspec.yaml index 8478ba1..180d0a7 100644 --- a/examples_dart/pubspec.yaml +++ b/examples_dart/pubspec.yaml @@ -1,7 +1,6 @@ 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' @@ -19,13 +18,17 @@ dependencies: version: 0.3.1 omemo_dart: hosted: https://git.polynom.me/api/packages/PapaTutuWawa/pub - version: ^0.5.0 + 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 From 77a1acb0e7bb6ae98853095a56e85e230642f21b Mon Sep 17 00:00:00 2001 From: "Alexander \"PapaTutuWawa" Date: Sun, 25 Jun 2023 12:47:47 +0200 Subject: [PATCH 14/15] fix(moxxmpp): Somewhat fix (and break) moxxmpp_socket_tcp integration tests --- .../badxmpp_certificate_test.dart | 20 +++------- .../failure_reconnection_test.dart | 40 +++++-------------- packages/moxxmpp_socket_tcp/pubspec.yaml | 8 +++- 3 files changed, 22 insertions(+), 46 deletions(-) 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 From a928c5c877cec46e19a074e8afe46bbfb9a925a1 Mon Sep 17 00:00:00 2001 From: "Alexander \"PapaTutuWawa" Date: Thu, 29 Jun 2023 21:12:30 +0200 Subject: [PATCH 15/15] feat(xep): Also potentially return "generic" stanza errors --- packages/moxxmpp/CHANGELOG.md | 1 + packages/moxxmpp/lib/src/stanza.dart | 63 ++++++++++++++----- .../moxxmpp/lib/src/xeps/xep_0030/errors.dart | 9 ++- .../lib/src/xeps/xep_0030/xep_0030.dart | 43 ++++++------- 4 files changed, 75 insertions(+), 41 deletions(-) diff --git a/packages/moxxmpp/CHANGELOG.md b/packages/moxxmpp/CHANGELOG.md index 3960a67..f653e13 100644 --- a/packages/moxxmpp/CHANGELOG.md +++ b/packages/moxxmpp/CHANGELOG.md @@ -17,6 +17,7 @@ - **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/src/stanza.dart b/packages/moxxmpp/lib/src/stanza.dart index b7424b3..2e5804b 100644 --- a/packages/moxxmpp/lib/src/stanza.dart +++ b/packages/moxxmpp/lib/src/stanza.dart @@ -47,28 +47,57 @@ class StanzaDetails { 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/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 09443d2..96a854e 100644 --- a/packages/moxxmpp/lib/src/xeps/xep_0030/xep_0030.dart +++ b/packages/moxxmpp/lib/src/xeps/xep_0030/xep_0030.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,7 +252,7 @@ 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 = false, @@ -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)) { @@ -297,16 +297,18 @@ class DiscoManager extends XmppManagerBase { 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,7 +324,7 @@ 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 = false, @@ -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); }