diff --git a/packages/moxxmpp/lib/src/awaiter.dart b/packages/moxxmpp/lib/src/awaiter.dart index d135cb8..6ffcf16 100644 --- a/packages/moxxmpp/lib/src/awaiter.dart +++ b/packages/moxxmpp/lib/src/awaiter.dart @@ -3,8 +3,12 @@ import 'package:moxxmpp/src/stringxml.dart'; import 'package:synchronized/synchronized.dart'; /// (JID we sent a stanza to, the id of the sent stanza, the tag of the sent stanza). +// ignore: avoid_private_typedef_functions typedef _StanzaCompositeKey = (String?, String, String); +/// Callback function that returns the bare JID of the connection as a String. +typedef GetBareJidCallback = String Function(); + /// This class handles the await semantics for stanzas. Stanzas are given a "unique" /// key equal to the tuple (to, id, tag) with which their response is identified. /// @@ -14,6 +18,10 @@ typedef _StanzaCompositeKey = (String?, String, String); /// /// This class also handles some "edge cases" of RFC 6120, like an empty "from" attribute. class StanzaAwaiter { + StanzaAwaiter(this._bareJidCallback); + + final GetBareJidCallback _bareJidCallback; + /// The pending stanzas, identified by their surrogate key. final Map<_StanzaCompositeKey, Completer> _pending = {}; @@ -27,9 +35,12 @@ class StanzaAwaiter { /// /// Returns a future that might resolve to the response to the stanza. Future> addPending(String? to, String id, String tag) async { + // Check if we want to send a stanza to our bare JID and replace it with null. + final processedTo = to != null && to == _bareJidCallback() ? null : to; + final completer = await _lock.synchronized(() { final completer = Completer(); - _pending[(to, id, tag)] = completer; + _pending[(processedTo, id, tag)] = completer; return completer; }); @@ -43,8 +54,13 @@ class StanzaAwaiter { final id = stanza.attributes['id'] as String?; if (id == null) return false; + // Check if we want to send a stanza to our bare JID and replace it with null. + final from = stanza.attributes['from'] as String?; + final processedFrom = + from != null && from == _bareJidCallback() ? null : from; + final key = ( - stanza.attributes['from'] as String?, + processedFrom, id, stanza.tag, ); diff --git a/packages/moxxmpp/lib/src/connection.dart b/packages/moxxmpp/lib/src/connection.dart index 7584034..1cffafe 100644 --- a/packages/moxxmpp/lib/src/connection.dart +++ b/packages/moxxmpp/lib/src/connection.dart @@ -90,6 +90,9 @@ class XmppConnection { }, ); + _stanzaAwaiter = StanzaAwaiter( + () => connectionSettings.jid.toBare().toString(), + ); _incomingStanzaQueue = IncomingStanzaQueue(handleXmlStream, _stanzaAwaiter); _socketStream = _socket.getDataStream(); // TODO(Unknown): Handle on done @@ -125,7 +128,7 @@ class XmppConnection { final ConnectivityManager _connectivityManager; /// A helper for handling await semantics with stanzas - final StanzaAwaiter _stanzaAwaiter = StanzaAwaiter(); + late final StanzaAwaiter _stanzaAwaiter; /// Sorted list of handlers that we call or incoming and outgoing stanzas final List<_StanzaHandlerWrapper> _incomingStanzaHandlers = @@ -531,15 +534,12 @@ class XmppConnection { _log.finest('==> $prefix${newStanza.toXml()}'); if (details.awaitable) { - final isOwnJid = - data.stanza.to == connectionSettings.jid.toBare().toString(); - await _stanzaAwaiter .addPending( // A stanza with no to attribute is for direct processing by the server. As such, // we can correlate it by just *assuming* we have that attribute // (RFC 6120 Section 8.1.1.1) - isOwnJid ? null : data.stanza.to, + data.stanza.to, data.stanza.id!, data.stanza.tag, ) @@ -774,15 +774,8 @@ class XmppConnection { return; } - // In case the stanza came from our own bare Jid, remove it so that the stanza - // awaiter works correctly. - final isOwnJid = incomingPreHandlers.stanza.from == - connectionSettings.jid.toBare().toString(); - final ownJidStanza = isOwnJid - ? incomingPreHandlers.stanza.copyWith(from: null) - : incomingPreHandlers.stanza; final awaited = await _stanzaAwaiter.onData( - ownJidStanza, + incomingPreHandlers.stanza, ); if (awaited) { return; diff --git a/packages/moxxmpp/lib/src/util/incoming_queue.dart b/packages/moxxmpp/lib/src/util/incoming_queue.dart index abb9a4a..5b0c653 100644 --- a/packages/moxxmpp/lib/src/util/incoming_queue.dart +++ b/packages/moxxmpp/lib/src/util/incoming_queue.dart @@ -92,7 +92,6 @@ class IncomingStanzaQueue { } object as XMPPStreamElement; - // TODO: Check the from attribute to ensure that it is matched correctly. return _stanzaAwaiter.isAwaited(object.node); } } diff --git a/packages/moxxmpp/test/awaiter_test.dart b/packages/moxxmpp/test/awaiter_test.dart index 8367305..f9b1813 100644 --- a/packages/moxxmpp/test/awaiter_test.dart +++ b/packages/moxxmpp/test/awaiter_test.dart @@ -2,9 +2,12 @@ import 'package:moxxmpp/moxxmpp.dart'; import 'package:moxxmpp/src/awaiter.dart'; import 'package:test/test.dart'; +const bareJid = 'user4@example.org'; +String getBareJidCallback() => bareJid; + void main() { test('Test awaiting an awaited stanza with a from attribute', () async { - final awaiter = StanzaAwaiter(); + final awaiter = StanzaAwaiter(getBareJidCallback); // "Send" a stanza final future = await awaiter.addPending( @@ -39,7 +42,7 @@ void main() { }); test('Test awaiting an awaited stanza without a from attribute', () async { - final awaiter = StanzaAwaiter(); + final awaiter = StanzaAwaiter(getBareJidCallback); // "Send" a stanza final future = await awaiter.addPending(null, 'abc123', 'iq'); @@ -60,7 +63,7 @@ void main() { }); test('Test awaiting a stanza that was already awaited', () async { - final awaiter = StanzaAwaiter(); + final awaiter = StanzaAwaiter(getBareJidCallback); // "Send" a stanza final future = await awaiter.addPending(null, 'abc123', 'iq'); @@ -81,7 +84,7 @@ void main() { }); test('Test ignoring a stanza that has the wrong tag', () async { - final awaiter = StanzaAwaiter(); + final awaiter = StanzaAwaiter(getBareJidCallback); // "Send" a stanza final future = await awaiter.addPending(null, 'abc123', 'iq'); @@ -100,4 +103,31 @@ void main() { expect(result2, true); expect(await future, stanza); }); + + test('Sending a stanza to our bare JID', () async { + final awaiter = StanzaAwaiter(getBareJidCallback); + + // "Send" a stanza + final future = await awaiter.addPending(bareJid, 'abc123', 'iq'); + + // Receive the response. + final stanza = XMLNode.fromString(''); + await awaiter.onData(stanza); + expect(await future, stanza); + }); + + test( + 'Sending a stanza to our bare JID and receiving stanza with a from attribute', + () async { + final awaiter = StanzaAwaiter(getBareJidCallback); + + // "Send" a stanza + final future = await awaiter.addPending(bareJid, 'abc123', 'iq'); + + // Receive the response. + final stanza = + XMLNode.fromString(''); + await awaiter.onData(stanza); + expect(await future, stanza); + }); }