diff --git a/.gitlint b/.gitlint index 99d3cfb..b4ea8e9 100644 --- a/.gitlint +++ b/.gitlint @@ -7,7 +7,7 @@ line-length=72 [title-trailing-punctuation] [title-hard-tab] [title-match-regex] -regex=^((feat|fix|chore|refactor|docs|release|test)\((meta|tests|style|docs|xep|core|example)+(,(meta|tests|style|docs|xep|core|example))*\)|release): [A-Z0-9].*$ +regex=^((feat|fix|chore|refactor|docs|release|test)\((meta|tests|style|docs|xep|core|example|all)+(,(meta|tests|style|docs|xep|core|example|all))*\)|release): [A-Z0-9].*$ [body-trailing-whitespace] diff --git a/packages/moxxmpp/CHANGELOG.md b/packages/moxxmpp/CHANGELOG.md index e34e692..40ab55c 100644 --- a/packages/moxxmpp/CHANGELOG.md +++ b/packages/moxxmpp/CHANGELOG.md @@ -9,6 +9,8 @@ - **BREAKING**: Remove `DiscoManager.discoInfoCapHashQuery`. - **BREAKING**: The entity argument of `DiscoManager.discoInfoQuery` and `DiscoManager.discoItemsQuery` are now `JID` instead of `String`. - **BREAKING**: `PubSubManager` and `UserAvatarManager` now use `JID` instead of `String`. +- **BREAKING**: `XmppConnection.sendStanza` not only takes a `StanzaDetails` argument. +- Sent stanzas are not kept in a queue until sent. ## 0.3.1 diff --git a/packages/moxxmpp/lib/src/connection.dart b/packages/moxxmpp/lib/src/connection.dart index 3009050..6333961 100644 --- a/packages/moxxmpp/lib/src/connection.dart +++ b/packages/moxxmpp/lib/src/connection.dart @@ -420,12 +420,17 @@ class XmppConnection { .contains(await getConnectionState()); } - Future?> sendStanza2(StanzaDetails details) async { + /// Sends a stanza described by [details] to the server. Until sent, the stanza is + /// kept in a queue, that is flushed after going online again. If Stream Management + /// is active, stanza's acknowledgement is tracked. + // TODO(Unknown): if addId = false, the function crashes. + Future sendStanza(StanzaDetails details) async { assert( implies( - details.awaitable, - details.stanza.id != null && details.stanza.id!.isNotEmpty || - details.addId), + details.awaitable, + details.stanza.id != null && details.stanza.id!.isNotEmpty || + details.addId, + ), 'An awaitable stanza must have an id', ); @@ -550,131 +555,6 @@ class XmppConnection { _log.fine('Done'); } - /// Sends a [stanza] to the server. If stream management is enabled, then keeping track - /// of the stanza is taken care of. Returns a Future that resolves when we receive a - /// response to the stanza. - /// - /// If addFrom is true, then a 'from' attribute will be added to the stanza if - /// [stanza] has none. - /// If addId is true, then an 'id' attribute will be added to the stanza if [stanza] has - /// none. - // TODO(Unknown): if addId = false, the function crashes. - Future sendStanza( - Stanza stanza, { - StanzaFromType addFrom = StanzaFromType.full, - bool addId = true, - bool awaitable = true, - bool encrypted = false, - bool forceEncryption = false, - }) async { - assert( - implies(addId == false && stanza.id == null, !awaitable), - 'Cannot await a stanza with no id', - ); - - // Add extra data in case it was not set - var stanza_ = stanza; - if (addId && (stanza_.id == null || stanza_.id == '')) { - stanza_ = stanza.copyWith(id: generateId()); - } - if (addFrom != StanzaFromType.none && - (stanza_.from == null || stanza_.from == '')) { - switch (addFrom) { - case StanzaFromType.full: - { - stanza_ = stanza_.copyWith( - from: _getJidWithResource().toString(), - ); - } - break; - case StanzaFromType.bare: - { - stanza_ = stanza_.copyWith( - from: connectionSettings.jid.toBare().toString(), - ); - } - break; - case StanzaFromType.none: - break; - } - } - stanza_ = stanza_.copyWith( - xmlns: _negotiationsHandler.getStanzaNamespace(), - ); - - _log.fine('Running pre stanza handlers..'); - final data = await _runOutgoingPreStanzaHandlers( - stanza_, - initial: StanzaHandlerData( - false, - false, - null, - stanza_, - encrypted: encrypted, - forceEncryption: forceEncryption, - ), - ); - _log.fine('Done'); - - if (data.cancel) { - _log.fine('A stanza handler indicated that it wants to cancel sending.'); - await _sendEvent(StanzaSendingCancelledEvent(data)); - return Stanza( - tag: data.stanza.tag, - to: data.stanza.from, - from: data.stanza.to, - attributes: { - 'type': 'error', - if (data.stanza.id != null) 'id': data.stanza.id!, - }, - ); - } - - final prefix = data.encrypted ? '(Encrypted) ' : ''; - _log.finest('==> $prefix${stanza_.toXml()}'); - - final stanzaString = data.stanza.toXml(); - - // ignore: cascade_invocations - _log.fine('Attempting to acquire lock for ${data.stanza.id}...'); - // TODO(PapaTutuWawa): Handle this much more graceful - var future = Future.value(XMLNode(tag: 'not-used')); - if (awaitable) { - future = 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) - data.stanza.to ?? connectionSettings.jid.toBare().toString(), - data.stanza.id!, - data.stanza.tag, - ); - } - - // This uses the StreamManager to behave like a send queue - if (await _canSendData()) { - _socket.write(stanzaString); - - // Try to ack every stanza - // NOTE: Here we have send an Ack request nonza. This is now done by StreamManagementManager when receiving the StanzaSentEvent - } else { - _log.fine('_canSendData() returned false.'); - } - - _log.fine('Running post stanza handlers..'); - await _runOutgoingPostStanzaHandlers( - stanza_, - initial: StanzaHandlerData( - false, - false, - null, - stanza_, - ), - ); - _log.fine('Done'); - - return future; - } - /// Called when we timeout during connecting Future _onConnectingTimeout() async { _log.severe('Connection stuck in "connecting". Causing a reconnection...'); @@ -696,18 +576,11 @@ class XmppConnection { // Set the new routing state _updateRoutingState(RoutingState.handleStanzas); - // Set the connection state - await _setConnectionState(XmppConnectionState.connected); - // Enable reconnections if (_enableReconnectOnSuccess) { await _reconnectionPolicy.setShouldReconnect(true); } - // Resolve the connection completion future - _connectionCompleter?.complete(const Result(true)); - _connectionCompleter = null; - // Tell consumers of the event stream that we're done with stream feature // negotiations await _sendEvent( @@ -716,6 +589,16 @@ class XmppConnection { false, ), ); + + // Set the connection state + await _setConnectionState(XmppConnectionState.connected); + + // Resolve the connection completion future + _connectionCompleter?.complete(const Result(true)); + _connectionCompleter = null; + + // Flush the stanza send queue + await _stanzaQueue.restart(); } /// Sets the connection state to [state] and triggers an event of type diff --git a/packages/moxxmpp/lib/src/iq.dart b/packages/moxxmpp/lib/src/iq.dart index c20b6fe..33621aa 100644 --- a/packages/moxxmpp/lib/src/iq.dart +++ b/packages/moxxmpp/lib/src/iq.dart @@ -1,6 +1,7 @@ import 'package:moxxmpp/src/connection.dart'; import 'package:moxxmpp/src/managers/data.dart'; import 'package:moxxmpp/src/stanza.dart'; +import 'package:moxxmpp/src/util/queue.dart'; /// Bounce a stanza if it was not handled by any manager. [conn] is the connection object /// to use for sending the stanza. [data] is the StanzaHandlerData of the unhandled @@ -23,9 +24,11 @@ Future handleUnhandledStanza( ); await conn.sendStanza( - stanza, - awaitable: false, - forceEncryption: data.encrypted, + StanzaDetails( + stanza, + awaitable: false, + forceEncryption: data.encrypted, + ), ); } } diff --git a/packages/moxxmpp/lib/src/managers/attributes.dart b/packages/moxxmpp/lib/src/managers/attributes.dart index 508c7a8..dbd3521 100644 --- a/packages/moxxmpp/lib/src/managers/attributes.dart +++ b/packages/moxxmpp/lib/src/managers/attributes.dart @@ -6,8 +6,8 @@ import 'package:moxxmpp/src/managers/base.dart'; import 'package:moxxmpp/src/negotiators/negotiator.dart'; 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/util/queue.dart'; class XmppManagerAttributes { XmppManagerAttributes({ @@ -23,14 +23,7 @@ class XmppManagerAttributes { }); /// Send a stanza whose response can be awaited. - final Future Function( - Stanza stanza, { - StanzaFromType addFrom, - bool addId, - bool awaitable, - bool encrypted, - bool forceEncryption, - }) sendStanza; + final Future Function(StanzaDetails) sendStanza; /// Send a nonza. final void Function(XMLNode) sendNonza; diff --git a/packages/moxxmpp/lib/src/managers/base.dart b/packages/moxxmpp/lib/src/managers/base.dart index 351919d..54cc066 100644 --- a/packages/moxxmpp/lib/src/managers/base.dart +++ b/packages/moxxmpp/lib/src/managers/base.dart @@ -6,6 +6,7 @@ import 'package:moxxmpp/src/managers/data.dart'; import 'package:moxxmpp/src/managers/handlers.dart'; import 'package:moxxmpp/src/managers/namespaces.dart'; import 'package:moxxmpp/src/stringxml.dart'; +import 'package:moxxmpp/src/util/queue.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'; @@ -165,9 +166,11 @@ abstract class XmppManagerBase { ); await getAttributes().sendStanza( - stanza, - awaitable: false, - forceEncryption: data.encrypted, + StanzaDetails( + stanza, + awaitable: false, + forceEncryption: data.encrypted, + ), ); } } diff --git a/packages/moxxmpp/lib/src/message.dart b/packages/moxxmpp/lib/src/message.dart index 075ece0..b0de34b 100644 --- a/packages/moxxmpp/lib/src/message.dart +++ b/packages/moxxmpp/lib/src/message.dart @@ -8,6 +8,7 @@ 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/util/queue.dart'; import 'package:moxxmpp/src/xeps/staging/file_upload_notification.dart'; import 'package:moxxmpp/src/xeps/xep_0066.dart'; import 'package:moxxmpp/src/xeps/xep_0085.dart'; @@ -320,6 +321,11 @@ class MessageManager extends XmppManagerBase { ); } - getAttributes().sendStanza(stanza, awaitable: false); + getAttributes().sendStanza( + StanzaDetails( + stanza, + awaitable: false, + ), + ); } } diff --git a/packages/moxxmpp/lib/src/presence.dart b/packages/moxxmpp/lib/src/presence.dart index 0a73171..0fd8dff 100644 --- a/packages/moxxmpp/lib/src/presence.dart +++ b/packages/moxxmpp/lib/src/presence.dart @@ -7,10 +7,9 @@ import 'package:moxxmpp/src/managers/data.dart'; import 'package:moxxmpp/src/managers/handlers.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/stanza.dart'; import 'package:moxxmpp/src/stringxml.dart'; -import 'package:moxxmpp/src/xeps/xep_0198/negotiator.dart'; +import 'package:moxxmpp/src/util/queue.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 @@ -49,12 +48,8 @@ class PresenceManager extends XmppManagerBase { Future onXmppEvent(XmppEvent event) async { if (event is StreamNegotiationsDoneEvent) { // Send initial presence only when we have not resumed the stream - final sm = getAttributes().getNegotiatorById( - streamManagementNegotiator, - ); - final isResumed = sm?.isResumed ?? false; - if (!isResumed) { - unawaited(sendInitialPresence()); + if (!event.resumed) { + await sendInitialPresence(); } } } @@ -108,66 +103,82 @@ class PresenceManager extends XmppManagerBase { final attrs = getAttributes(); await attrs.sendStanza( - Stanza.presence( - from: attrs.getFullJID().toString(), - children: children, + StanzaDetails( + Stanza.presence( + from: attrs.getFullJID().toString(), + children: children, + ), + awaitable: false, + addId: false, ), - awaitable: false, - addId: false, ); } /// Send an unavailable presence with no 'to' attribute. void sendUnavailablePresence() { getAttributes().sendStanza( - Stanza.presence( - type: 'unavailable', + StanzaDetails( + Stanza.presence( + type: 'unavailable', + ), + awaitable: false, ), - addFrom: StanzaFromType.full, ); } /// Sends a subscription request to [to]. void sendSubscriptionRequest(String to) { getAttributes().sendStanza( - Stanza.presence( - type: 'subscribe', - to: to, + StanzaDetails( + Stanza.presence( + type: 'subscribe', + to: to, + ), + addFrom: StanzaFromType.none, + awaitable: false, ), - addFrom: StanzaFromType.none, ); } /// Sends an unsubscription request to [to]. void sendUnsubscriptionRequest(String to) { getAttributes().sendStanza( - Stanza.presence( - type: 'unsubscribe', - to: to, + StanzaDetails( + Stanza.presence( + type: 'unsubscribe', + to: to, + ), + addFrom: StanzaFromType.none, + awaitable: false, ), - addFrom: StanzaFromType.none, ); } /// Accept a presence subscription request for [to]. void sendSubscriptionRequestApproval(String to) { getAttributes().sendStanza( - Stanza.presence( - type: 'subscribed', - to: to, + StanzaDetails( + Stanza.presence( + type: 'subscribed', + to: to, + ), + addFrom: StanzaFromType.none, + awaitable: false, ), - addFrom: StanzaFromType.none, ); } /// Reject a presence subscription request for [to]. void sendSubscriptionRequestRejection(String to) { getAttributes().sendStanza( - Stanza.presence( - type: 'unsubscribed', - to: to, + StanzaDetails( + Stanza.presence( + type: 'unsubscribed', + to: to, + ), + addFrom: StanzaFromType.none, + awaitable: false, ), - addFrom: StanzaFromType.none, ); } } diff --git a/packages/moxxmpp/lib/src/roster/roster.dart b/packages/moxxmpp/lib/src/roster/roster.dart index d8c3f9d..e997956 100644 --- a/packages/moxxmpp/lib/src/roster/roster.dart +++ b/packages/moxxmpp/lib/src/roster/roster.dart @@ -15,6 +15,7 @@ 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'; +import 'package:moxxmpp/src/util/queue.dart'; @immutable class XmppRosterItem { @@ -235,14 +236,16 @@ class RosterManager extends XmppManagerBase { query.attributes['ver'] = rosterVersion; } - final response = await attrs.sendStanza( - Stanza.iq( - type: 'get', - children: [ - query, - ], + final response = (await attrs.sendStanza( + StanzaDetails( + Stanza.iq( + type: 'get', + children: [ + query, + ], + ), ), - ); + ))!; if (response.attributes['type'] != 'result') { logger.warning('Error requesting roster: ${response.toXml()}'); @@ -258,20 +261,22 @@ class RosterManager extends XmppManagerBase { Future> requestRosterPushes() async { final attrs = getAttributes(); - final result = await attrs.sendStanza( - Stanza.iq( - type: 'get', - children: [ - XMLNode.xmlns( - tag: 'query', - xmlns: rosterXmlns, - attributes: { - 'ver': await _stateManager.getRosterVersion() ?? '', - }, - ) - ], + final result = (await attrs.sendStanza( + StanzaDetails( + Stanza.iq( + type: 'get', + children: [ + XMLNode.xmlns( + tag: 'query', + xmlns: rosterXmlns, + attributes: { + 'ver': await _stateManager.getRosterVersion() ?? '', + }, + ) + ], + ), ), - ); + ))!; if (result.attributes['type'] != 'result') { logger.warning('Requesting roster pushes failed: ${result.toXml()}'); @@ -296,31 +301,33 @@ class RosterManager extends XmppManagerBase { List? groups, }) async { final attrs = getAttributes(); - final response = await attrs.sendStanza( - Stanza.iq( - type: 'set', - children: [ - XMLNode.xmlns( - tag: 'query', - xmlns: rosterXmlns, - children: [ - XMLNode( - tag: 'item', - attributes: { - 'jid': jid, - ...title == jid.split('@')[0] - ? {} - : {'name': title} - }, - children: (groups ?? []) - .map((group) => XMLNode(tag: 'group', text: group)) - .toList(), - ) - ], - ) - ], + final response = (await attrs.sendStanza( + StanzaDetails( + Stanza.iq( + type: 'set', + children: [ + XMLNode.xmlns( + tag: 'query', + xmlns: rosterXmlns, + children: [ + XMLNode( + tag: 'item', + attributes: { + 'jid': jid, + ...title == jid.split('@')[0] + ? {} + : {'name': title} + }, + children: (groups ?? []) + .map((group) => XMLNode(tag: 'group', text: group)) + .toList(), + ) + ], + ), + ], + ), ), - ); + ))!; if (response.attributes['type'] != 'result') { logger.severe('Error adding $jid to roster: $response'); @@ -334,26 +341,28 @@ class RosterManager extends XmppManagerBase { /// false otherwise. Future removeFromRoster(String jid) async { final attrs = getAttributes(); - final response = await attrs.sendStanza( - Stanza.iq( - type: 'set', - children: [ - XMLNode.xmlns( - tag: 'query', - xmlns: rosterXmlns, - children: [ - XMLNode( - tag: 'item', - attributes: { - 'jid': jid, - 'subscription': 'remove' - }, - ) - ], - ) - ], + final response = (await attrs.sendStanza( + StanzaDetails( + Stanza.iq( + type: 'set', + children: [ + XMLNode.xmlns( + tag: 'query', + xmlns: rosterXmlns, + children: [ + XMLNode( + tag: 'item', + attributes: { + 'jid': jid, + 'subscription': 'remove' + }, + ) + ], + ) + ], + ), ), - ); + ))!; if (response.attributes['type'] != 'result') { logger.severe('Failed to remove roster item: ${response.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 8558da3..70a68e6 100644 --- a/packages/moxxmpp/lib/src/xeps/xep_0030/xep_0030.dart +++ b/packages/moxxmpp/lib/src/xeps/xep_0030/xep_0030.dart @@ -10,6 +10,7 @@ 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/queue.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'; @@ -291,10 +292,12 @@ class DiscoManager extends XmppManagerBase { } } - final stanza = await getAttributes().sendStanza( - buildDiscoInfoQueryStanza(entity, node), - encrypted: !shouldEncrypt, - ); + final stanza = (await getAttributes().sendStanza( + StanzaDetails( + buildDiscoInfoQueryStanza(entity, node), + encrypted: !shouldEncrypt, + ), + ))!; final query = stanza.firstTag('query'); if (query == null) { final result = Result(InvalidResponseDiscoError()); @@ -331,10 +334,12 @@ class DiscoManager extends XmppManagerBase { return future; } - final stanza = await getAttributes().sendStanza( - buildDiscoItemsQueryStanza(entity, node: node), - encrypted: !shouldEncrypt, - ) as Stanza; + final stanza = (await getAttributes().sendStanza( + StanzaDetails( + buildDiscoItemsQueryStanza(entity, node: node), + encrypted: !shouldEncrypt, + ), + ))!; final query = stanza.firstTag('query'); if (query == null) { @@ -344,7 +349,7 @@ class DiscoManager extends XmppManagerBase { return result; } - if (stanza.type == 'error') { + if (stanza.attributes['type'] == 'error') { //final error = stanza.firstTag('error'); //print("Disco Items error: " + error.toXml()); final result = diff --git a/packages/moxxmpp/lib/src/xeps/xep_0054.dart b/packages/moxxmpp/lib/src/xeps/xep_0054.dart index c57c9bf..58a1e3c 100644 --- a/packages/moxxmpp/lib/src/xeps/xep_0054.dart +++ b/packages/moxxmpp/lib/src/xeps/xep_0054.dart @@ -8,6 +8,7 @@ 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/queue.dart'; abstract class VCardError {} @@ -103,19 +104,21 @@ class VCardManager extends XmppManagerBase { } Future> requestVCard(String jid) async { - final result = await getAttributes().sendStanza( - Stanza.iq( - to: jid, - type: 'get', - children: [ - XMLNode.xmlns( - tag: 'vCard', - xmlns: vCardTempXmlns, - ) - ], + final result = (await getAttributes().sendStanza( + StanzaDetails( + Stanza.iq( + to: jid, + type: 'get', + children: [ + XMLNode.xmlns( + tag: 'vCard', + xmlns: vCardTempXmlns, + ) + ], + ), + encrypted: true, ), - encrypted: true, - ); + ))!; if (result.attributes['type'] != 'result') { return Result(UnknownVCardError()); 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 bc66404..cc1776e 100644 --- a/packages/moxxmpp/lib/src/xeps/xep_0060/xep_0060.dart +++ b/packages/moxxmpp/lib/src/xeps/xep_0060/xep_0060.dart @@ -10,6 +10,7 @@ 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/queue.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'; @@ -181,27 +182,29 @@ class PubSubManager extends XmppManagerBase { Future> subscribe(String jid, String node) async { final attrs = getAttributes(); - final result = await attrs.sendStanza( - Stanza.iq( - type: 'set', - to: jid, - children: [ - XMLNode.xmlns( - tag: 'pubsub', - xmlns: pubsubXmlns, - children: [ - XMLNode( - tag: 'subscribe', - attributes: { - 'node': node, - 'jid': attrs.getFullJID().toBare().toString(), - }, - ), - ], - ), - ], + final result = (await attrs.sendStanza( + StanzaDetails( + Stanza.iq( + type: 'set', + to: jid, + children: [ + XMLNode.xmlns( + tag: 'pubsub', + xmlns: pubsubXmlns, + children: [ + XMLNode( + tag: 'subscribe', + attributes: { + 'node': node, + 'jid': attrs.getFullJID().toBare().toString(), + }, + ), + ], + ), + ], + ), ), - ); + ))!; if (result.attributes['type'] != 'result') { return Result(UnknownPubSubError()); @@ -222,27 +225,29 @@ class PubSubManager extends XmppManagerBase { Future> unsubscribe(String jid, String node) async { final attrs = getAttributes(); - final result = await attrs.sendStanza( - Stanza.iq( - type: 'set', - to: jid, - children: [ - XMLNode.xmlns( - tag: 'pubsub', - xmlns: pubsubXmlns, - children: [ - XMLNode( - tag: 'unsubscribe', - attributes: { - 'node': node, - 'jid': attrs.getFullJID().toBare().toString(), - }, - ), - ], - ), - ], + final result = (await attrs.sendStanza( + StanzaDetails( + Stanza.iq( + type: 'set', + to: jid, + children: [ + XMLNode.xmlns( + tag: 'pubsub', + xmlns: pubsubXmlns, + children: [ + XMLNode( + tag: 'unsubscribe', + attributes: { + 'node': node, + 'jid': attrs.getFullJID().toBare().toString(), + }, + ), + ], + ), + ], + ), ), - ); + ))!; if (result.attributes['type'] != 'result') { return Result(UnknownPubSubError()); @@ -293,38 +298,40 @@ class PubSubManager extends XmppManagerBase { pubOptions = await preprocessPublishOptions(jid, node, options); } - final result = await getAttributes().sendStanza( - Stanza.iq( - type: 'set', - to: jid.toString(), - children: [ - XMLNode.xmlns( - tag: 'pubsub', - xmlns: pubsubXmlns, - children: [ - XMLNode( - tag: 'publish', - attributes: {'node': node}, - children: [ - XMLNode( - tag: 'item', - attributes: id != null - ? {'id': id} - : {}, - children: [payload], - ) - ], - ), - if (pubOptions != null) + final result = (await getAttributes().sendStanza( + StanzaDetails( + Stanza.iq( + type: 'set', + to: jid.toString(), + children: [ + XMLNode.xmlns( + tag: 'pubsub', + xmlns: pubsubXmlns, + children: [ XMLNode( - tag: 'publish-options', - children: [pubOptions.toXml()], + tag: 'publish', + attributes: {'node': node}, + children: [ + XMLNode( + tag: 'item', + attributes: id != null + ? {'id': id} + : {}, + children: [payload], + ) + ], ), - ], - ) - ], + if (pubOptions != null) + XMLNode( + tag: 'publish-options', + children: [pubOptions.toXml()], + ), + ], + ) + ], + ), ), - ); + ))!; if (result.attributes['type'] != 'result') { final error = getPubSubError(result); @@ -395,21 +402,26 @@ class PubSubManager extends XmppManagerBase { String jid, String node, ) async { - final result = await getAttributes().sendStanza( - Stanza.iq( - type: 'get', - to: jid, - children: [ - XMLNode.xmlns( - tag: 'pubsub', - xmlns: pubsubXmlns, - children: [ - XMLNode(tag: 'items', attributes: {'node': node}), - ], - ) - ], + final result = (await getAttributes().sendStanza( + StanzaDetails( + Stanza.iq( + type: 'get', + to: jid, + children: [ + XMLNode.xmlns( + tag: 'pubsub', + xmlns: pubsubXmlns, + children: [ + XMLNode( + tag: 'items', + attributes: {'node': node}, + ), + ], + ) + ], + ), ), - ); + ))!; if (result.attributes['type'] != 'result') { return Result(getPubSubError(result)); @@ -436,30 +448,32 @@ class PubSubManager extends XmppManagerBase { String node, String id, ) async { - final result = await getAttributes().sendStanza( - Stanza.iq( - type: 'get', - to: jid, - children: [ - XMLNode.xmlns( - tag: 'pubsub', - xmlns: pubsubXmlns, - children: [ - XMLNode( - tag: 'items', - attributes: {'node': node}, - children: [ - XMLNode( - tag: 'item', - attributes: {'id': id}, - ), - ], - ), - ], - ), - ], + final result = (await getAttributes().sendStanza( + StanzaDetails( + Stanza.iq( + type: 'get', + to: jid, + children: [ + XMLNode.xmlns( + tag: 'pubsub', + xmlns: pubsubXmlns, + children: [ + XMLNode( + tag: 'items', + attributes: {'node': node}, + children: [ + XMLNode( + tag: 'item', + attributes: {'id': id}, + ), + ], + ), + ], + ), + ], + ), ), - ); + ))!; if (result.attributes['type'] != 'result') { return Result(getPubSubError(result)); @@ -488,53 +502,57 @@ class PubSubManager extends XmppManagerBase { final attrs = getAttributes(); // Request the form - final form = await attrs.sendStanza( - Stanza.iq( - type: 'get', - to: jid.toString(), - children: [ - XMLNode.xmlns( - tag: 'pubsub', - xmlns: pubsubOwnerXmlns, - children: [ - XMLNode( - tag: 'configure', - attributes: { - 'node': node, - }, - ), - ], - ), - ], + final form = (await attrs.sendStanza( + StanzaDetails( + Stanza.iq( + type: 'get', + to: jid.toString(), + children: [ + XMLNode.xmlns( + tag: 'pubsub', + xmlns: pubsubOwnerXmlns, + children: [ + XMLNode( + tag: 'configure', + attributes: { + 'node': node, + }, + ), + ], + ), + ], + ), ), - ); + ))!; if (form.attributes['type'] != 'result') { return Result(getPubSubError(form)); } - final submit = await attrs.sendStanza( - Stanza.iq( - type: 'set', - to: jid.toString(), - children: [ - XMLNode.xmlns( - tag: 'pubsub', - xmlns: pubsubOwnerXmlns, - children: [ - XMLNode( - tag: 'configure', - attributes: { - 'node': node, - }, - children: [ - options.toXml(), - ], - ), - ], - ), - ], + final submit = (await attrs.sendStanza( + StanzaDetails( + Stanza.iq( + type: 'set', + to: jid.toString(), + children: [ + XMLNode.xmlns( + tag: 'pubsub', + xmlns: pubsubOwnerXmlns, + children: [ + XMLNode( + tag: 'configure', + attributes: { + 'node': node, + }, + children: [ + options.toXml(), + ], + ), + ], + ), + ], + ), ), - ); + ))!; if (submit.attributes['type'] != 'result') { return Result(getPubSubError(form)); } @@ -543,28 +561,30 @@ class PubSubManager extends XmppManagerBase { } Future> delete(JID host, String node) async { - final request = await getAttributes().sendStanza( - Stanza.iq( - type: 'set', - to: host.toString(), - children: [ - XMLNode.xmlns( - tag: 'pubsub', - xmlns: pubsubOwnerXmlns, - children: [ - XMLNode( - tag: 'delete', - attributes: { - 'node': node, - }, - ), - ], - ), - ], + final request = (await getAttributes().sendStanza( + StanzaDetails( + Stanza.iq( + type: 'set', + to: host.toString(), + children: [ + XMLNode.xmlns( + tag: 'pubsub', + xmlns: pubsubOwnerXmlns, + children: [ + XMLNode( + tag: 'delete', + attributes: { + 'node': node, + }, + ), + ], + ), + ], + ), ), - ) as Stanza; + ))!; - if (request.type != 'result') { + if (request.attributes['type'] != 'result') { // TODO(Unknown): Be more specific return Result(UnknownPubSubError()); } @@ -577,36 +597,38 @@ class PubSubManager extends XmppManagerBase { String node, String itemId, ) async { - final request = await getAttributes().sendStanza( - Stanza.iq( - type: 'set', - to: host.toString(), - children: [ - XMLNode.xmlns( - tag: 'pubsub', - xmlns: pubsubXmlns, - children: [ - XMLNode( - tag: 'retract', - attributes: { - 'node': node, - }, - children: [ - XMLNode( - tag: 'item', - attributes: { - 'id': itemId, - }, - ), - ], - ), - ], - ), - ], + final request = (await getAttributes().sendStanza( + StanzaDetails( + Stanza.iq( + type: 'set', + to: host.toString(), + children: [ + XMLNode.xmlns( + tag: 'pubsub', + xmlns: pubsubXmlns, + children: [ + XMLNode( + tag: 'retract', + attributes: { + 'node': node, + }, + children: [ + XMLNode( + tag: 'item', + attributes: { + 'id': itemId, + }, + ), + ], + ), + ], + ), + ], + ), ), - ) as Stanza; + ))!; - if (request.type != 'result') { + if (request.attributes['type'] != 'result') { // TODO(Unknown): Be more specific return Result(UnknownPubSubError()); } diff --git a/packages/moxxmpp/lib/src/xeps/xep_0085.dart b/packages/moxxmpp/lib/src/xeps/xep_0085.dart index b3d8ea3..43831c7 100644 --- a/packages/moxxmpp/lib/src/xeps/xep_0085.dart +++ b/packages/moxxmpp/lib/src/xeps/xep_0085.dart @@ -5,6 +5,7 @@ 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/util/queue.dart'; enum ChatState { active, composing, paused, inactive, gone } @@ -111,10 +112,14 @@ class ChatStateManager extends XmppManagerBase { final tagName = state.toString().split('.').last; getAttributes().sendStanza( - Stanza.message( - to: to, - type: messageType, - children: [XMLNode.xmlns(tag: tagName, xmlns: chatStateXmlns)], + StanzaDetails( + Stanza.message( + to: to, + type: messageType, + children: [ + XMLNode.xmlns(tag: tagName, xmlns: chatStateXmlns), + ], + ), ), ); } diff --git a/packages/moxxmpp/lib/src/xeps/xep_0191.dart b/packages/moxxmpp/lib/src/xeps/xep_0191.dart index 3c310b7..9a05b7c 100644 --- a/packages/moxxmpp/lib/src/xeps/xep_0191.dart +++ b/packages/moxxmpp/lib/src/xeps/xep_0191.dart @@ -6,6 +6,7 @@ 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/util/queue.dart'; import 'package:moxxmpp/src/xeps/xep_0030/xep_0030.dart'; class BlockingManager extends XmppManagerBase { @@ -96,39 +97,43 @@ class BlockingManager extends XmppManagerBase { } Future block(List items) async { - final result = await getAttributes().sendStanza( - Stanza.iq( - type: 'set', - children: [ - XMLNode.xmlns( - tag: 'block', - xmlns: blockingXmlns, - children: items.map((item) { - return XMLNode( - tag: 'item', - attributes: {'jid': item}, - ); - }).toList(), - ) - ], + final result = (await getAttributes().sendStanza( + StanzaDetails( + Stanza.iq( + type: 'set', + children: [ + XMLNode.xmlns( + tag: 'block', + xmlns: blockingXmlns, + children: items.map((item) { + return XMLNode( + tag: 'item', + attributes: {'jid': item}, + ); + }).toList(), + ) + ], + ), ), - ); + ))!; return result.attributes['type'] == 'result'; } Future unblockAll() async { - final result = await getAttributes().sendStanza( - Stanza.iq( - type: 'set', - children: [ - XMLNode.xmlns( - tag: 'unblock', - xmlns: blockingXmlns, - ) - ], + final result = (await getAttributes().sendStanza( + StanzaDetails( + Stanza.iq( + type: 'set', + children: [ + XMLNode.xmlns( + tag: 'unblock', + xmlns: blockingXmlns, + ) + ], + ), ), - ); + ))!; return result.attributes['type'] == 'result'; } @@ -136,41 +141,45 @@ class BlockingManager extends XmppManagerBase { Future unblock(List items) async { assert(items.isNotEmpty, 'The list of items to unblock must be non-empty'); - final result = await getAttributes().sendStanza( - Stanza.iq( - type: 'set', - children: [ - XMLNode.xmlns( - tag: 'unblock', - xmlns: blockingXmlns, - children: items - .map( - (item) => XMLNode( - tag: 'item', - attributes: {'jid': item}, - ), - ) - .toList(), - ) - ], + final result = (await getAttributes().sendStanza( + StanzaDetails( + Stanza.iq( + type: 'set', + children: [ + XMLNode.xmlns( + tag: 'unblock', + xmlns: blockingXmlns, + children: items + .map( + (item) => XMLNode( + tag: 'item', + attributes: {'jid': item}, + ), + ) + .toList(), + ) + ], + ), ), - ); + ))!; return result.attributes['type'] == 'result'; } Future> getBlocklist() async { - final result = await getAttributes().sendStanza( - Stanza.iq( - type: 'get', - children: [ - XMLNode.xmlns( - tag: 'blocklist', - xmlns: blockingXmlns, - ) - ], + final result = (await getAttributes().sendStanza( + StanzaDetails( + Stanza.iq( + type: 'get', + children: [ + XMLNode.xmlns( + tag: 'blocklist', + xmlns: blockingXmlns, + ) + ], + ), ), - ); + ))!; final blocklist = result.firstTag('blocklist', xmlns: blockingXmlns)!; return blocklist 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 38a566b..c8547ec 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/queue.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'; @@ -414,7 +415,12 @@ class StreamManagementManager extends XmppManagerBase { _unackedStanzas.clear(); for (final stanza in stanzas) { - await getAttributes().sendStanza(stanza, awaitable: false); + await getAttributes().sendStanza( + StanzaDetails( + stanza, + awaitable: false, + ), + ); } } diff --git a/packages/moxxmpp/lib/src/xeps/xep_0280.dart b/packages/moxxmpp/lib/src/xeps/xep_0280.dart index f767f8b..7f7bc67 100644 --- a/packages/moxxmpp/lib/src/xeps/xep_0280.dart +++ b/packages/moxxmpp/lib/src/xeps/xep_0280.dart @@ -1,6 +1,5 @@ import 'package:logging/logging.dart'; import 'package:meta/meta.dart'; -import 'package:moxxmpp/src/connection.dart'; import 'package:moxxmpp/src/events.dart'; import 'package:moxxmpp/src/jid.dart'; import 'package:moxxmpp/src/managers/base.dart'; @@ -11,6 +10,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/queue.dart'; import 'package:moxxmpp/src/xeps/xep_0030/xep_0030.dart'; import 'package:moxxmpp/src/xeps/xep_0297.dart'; import 'package:moxxmpp/src/xeps/xep_0386.dart'; @@ -111,20 +111,20 @@ class CarbonsManager extends XmppManagerBase { /// Returns true if carbons were enabled. False, if not. Future enableCarbons() async { final attrs = getAttributes(); - final result = await attrs.sendStanza( - Stanza.iq( - to: attrs.getFullJID().toBare().toString(), - type: 'set', - children: [ - XMLNode.xmlns( - tag: 'enable', - xmlns: carbonsXmlns, - ) - ], + final result = (await attrs.sendStanza( + StanzaDetails( + Stanza.iq( + to: attrs.getFullJID().toBare().toString(), + type: 'set', + children: [ + XMLNode.xmlns( + tag: 'enable', + xmlns: carbonsXmlns, + ) + ], + ), ), - addFrom: StanzaFromType.full, - addId: true, - ); + ))!; if (result.attributes['type'] != 'result') { logger.warning('Failed to enable message carbons'); @@ -142,19 +142,19 @@ class CarbonsManager extends XmppManagerBase { /// /// Returns true if carbons were disabled. False, if not. Future disableCarbons() async { - final result = await getAttributes().sendStanza( - Stanza.iq( - type: 'set', - children: [ - XMLNode.xmlns( - tag: 'disable', - xmlns: carbonsXmlns, - ) - ], + final result = (await getAttributes().sendStanza( + StanzaDetails( + Stanza.iq( + type: 'set', + children: [ + XMLNode.xmlns( + tag: 'disable', + xmlns: carbonsXmlns, + ) + ], + ), ), - addFrom: StanzaFromType.full, - addId: true, - ); + ))!; if (result.attributes['type'] != 'result') { logger.warning('Failed to disable message carbons'); 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 541b924..fb0d191 100644 --- a/packages/moxxmpp/lib/src/xeps/xep_0363/xep_0363.dart +++ b/packages/moxxmpp/lib/src/xeps/xep_0363/xep_0363.dart @@ -8,6 +8,7 @@ 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/queue.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'; @@ -149,23 +150,25 @@ class HttpFileUploadManager extends XmppManagerBase { } final attrs = getAttributes(); - final response = await attrs.sendStanza( - Stanza.iq( - to: _entityJid.toString(), - type: 'get', - children: [ - XMLNode.xmlns( - tag: 'request', - xmlns: httpFileUploadXmlns, - attributes: { - 'filename': filename, - 'size': filesize.toString(), - ...contentType != null ? {'content-type': contentType} : {} - }, - ) - ], + final response = (await attrs.sendStanza( + StanzaDetails( + Stanza.iq( + to: _entityJid.toString(), + type: 'get', + children: [ + XMLNode.xmlns( + tag: 'request', + xmlns: httpFileUploadXmlns, + attributes: { + 'filename': filename, + 'size': filesize.toString(), + ...contentType != null ? {'content-type': contentType} : {} + }, + ) + ], + ), ), - ); + ))!; if (response.attributes['type']! != 'result') { logger.severe('Failed to request HTTP File Upload slot.'); 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 658b69d..1c32d1b 100644 --- a/packages/moxxmpp/lib/src/xeps/xep_0384/xep_0384.dart +++ b/packages/moxxmpp/lib/src/xeps/xep_0384/xep_0384.dart @@ -11,6 +11,7 @@ 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/queue.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'; @@ -262,24 +263,26 @@ abstract class BaseOmemoManager extends XmppManagerBase { String toJid, ) async { await getAttributes().sendStanza( - Stanza.message( - to: toJid, - type: 'chat', - children: [ - _buildEncryptedElement( - result, - toJid, - await _getDeviceId(), - ), + StanzaDetails( + Stanza.message( + to: toJid, + type: 'chat', + children: [ + _buildEncryptedElement( + result, + toJid, + await _getDeviceId(), + ), - // Add a storage hint in case this is a message - // Taken from the example at - // https://xmpp.org/extensions/xep-0384.html#message-structure-description. - MessageProcessingHint.store.toXml(), - ], + // Add a storage hint in case this is a message + // Taken from the example at + // https://xmpp.org/extensions/xep-0384.html#message-structure-description. + MessageProcessingHint.store.toXml(), + ], + ), + awaitable: false, + encrypted: true, ), - awaitable: false, - encrypted: true, ); } diff --git a/packages/moxxmpp/test/xeps/xep_0198_test.dart b/packages/moxxmpp/test/xeps/xep_0198_test.dart index d1f1058..89cd0eb 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/util/queue.dart'; import 'package:test/test.dart'; import '../helpers/logging.dart'; import '../helpers/xmpp.dart'; @@ -44,15 +45,8 @@ Future runOutgoingStanzaHandlers( XmppManagerAttributes mkAttributes(void Function(Stanza) callback) { return XmppManagerAttributes( - sendStanza: ( - stanza, { - StanzaFromType addFrom = StanzaFromType.full, - bool addId = true, - bool awaitable = true, - bool encrypted = false, - bool forceEncryption = false, - }) async { - callback(stanza); + sendStanza: (StanzaDetails details) async { + callback(details.stanza); return Stanza.message(); }, @@ -290,12 +284,8 @@ void main() { ); final sm = StreamManagementManager(); await conn.registerManagers([ - PresenceManager(), - RosterManager(TestingRosterStateManager('', [])), - DiscoManager([]), sm, CarbonsManager()..forceEnable(), - EntityCapabilitiesManager('http://moxxmpp.example'), ]); await conn.registerFeatureNegotiators([ SaslPlainNegotiator(), @@ -776,7 +766,11 @@ void main() { // Send a bogus stanza unawaited( - conn.sendStanza(Stanza.iq(to: 'localhost', type: 'get')), + conn.sendStanza( + StanzaDetails( + Stanza.iq(to: 'localhost', type: 'get'), + ), + ), ); await Future.delayed(const Duration(seconds: 5)); diff --git a/packages/moxxmpp/test/xeps/xep_0280_test.dart b/packages/moxxmpp/test/xeps/xep_0280_test.dart index 464c1f9..6ef7b67 100644 --- a/packages/moxxmpp/test/xeps/xep_0280_test.dart +++ b/packages/moxxmpp/test/xeps/xep_0280_test.dart @@ -1,4 +1,5 @@ import 'package:moxxmpp/moxxmpp.dart'; +import 'package:moxxmpp/src/util/queue.dart'; import 'package:test/test.dart'; import '../helpers/logging.dart'; import '../helpers/xmpp.dart'; @@ -9,17 +10,9 @@ void main() { test("Test if we're vulnerable against CVE-2020-26547 style vulnerabilities", () async { final attributes = XmppManagerAttributes( - sendStanza: ( - stanza, { - StanzaFromType addFrom = StanzaFromType.full, - bool addId = true, - bool retransmitted = false, - bool awaitable = true, - bool encrypted = false, - bool forceEncryption = false, - }) async { + sendStanza: (StanzaDetails details) async { // ignore: avoid_print - print('==> ${stanza.toXml()}'); + print('==> ${details.stanza.toXml()}'); return XMLNode(tag: 'iq', attributes: {'type': 'result'}); }, sendNonza: (nonza) {}, diff --git a/packages/moxxmpp/test/xmpp_test.dart b/packages/moxxmpp/test/xmpp_test.dart index 6a438e2..cbe157b 100644 --- a/packages/moxxmpp/test/xmpp_test.dart +++ b/packages/moxxmpp/test/xmpp_test.dart @@ -131,11 +131,7 @@ void main() { password: 'aaaa', ); await conn.registerManagers([ - PresenceManager(), - RosterManager(TestingRosterStateManager('', [])), - DiscoManager([]), StreamManagementManager(), - EntityCapabilitiesManager('http://moxxmpp.example'), ]); await conn.registerFeatureNegotiators([ SaslPlainNegotiator(),