From f6abf3d5b57320551adaf2abcfe90775b7104857 Mon Sep 17 00:00:00 2001 From: "Alexander \"PapaTutuWawa" Date: Tue, 4 Apr 2023 15:48:26 +0200 Subject: [PATCH] feat(xep): Implement XEP-0114 --- integration_tests/prosody.cfg.lua | 4 + integration_tests/test/component_test.dart | 47 ++++++ integration_tests/test/sasl2_test.dart | 32 ++-- packages/moxxmpp/lib/moxxmpp.dart | 4 +- packages/moxxmpp/lib/src/connection.dart | 47 +----- .../moxxmpp/lib/src/connection_errors.dart | 12 ++ packages/moxxmpp/lib/src/handlers/base.dart | 121 ++++++++++++++ .../handler.dart => handlers/client.dart} | 152 ++++++------------ .../moxxmpp/lib/src/handlers/component.dart | 117 ++++++++++++++ packages/moxxmpp/lib/src/namespaces.dart | 3 + packages/moxxmpp/lib/src/settings.dart | 9 ++ packages/moxxmpp/test/component_test.dart | 48 ++++++ packages/moxxmpp/test/helpers/manager.dart | 2 +- packages/moxxmpp/test/negotiator_test.dart | 22 ++- packages/moxxmpp/test/stringxml_test.dart | 2 +- 15 files changed, 455 insertions(+), 167 deletions(-) create mode 100644 integration_tests/test/component_test.dart create mode 100644 packages/moxxmpp/lib/src/handlers/base.dart rename packages/moxxmpp/lib/src/{negotiators/handler.dart => handlers/client.dart} (62%) create mode 100644 packages/moxxmpp/lib/src/handlers/component.dart create mode 100644 packages/moxxmpp/test/component_test.dart diff --git a/integration_tests/prosody.cfg.lua b/integration_tests/prosody.cfg.lua index 29f1977..c562a1f 100644 --- a/integration_tests/prosody.cfg.lua +++ b/integration_tests/prosody.cfg.lua @@ -52,5 +52,9 @@ log = { pidfile = "/tmp/prosody.pid" +component_ports = { 8888 } +component_interfaces = { '127.0.0.1' } VirtualHost "localhost" +Component "component.localhost" + component_secret = "abc123" \ No newline at end of file diff --git a/integration_tests/test/component_test.dart b/integration_tests/test/component_test.dart new file mode 100644 index 0000000..af1a5da --- /dev/null +++ b/integration_tests/test/component_test.dart @@ -0,0 +1,47 @@ +import 'package:logging/logging.dart'; +import 'package:moxxmpp/moxxmpp.dart'; +import 'package:moxxmpp_socket_tcp/moxxmpp_socket_tcp.dart'; +import 'package:test/test.dart'; + +class TestingTCPSocketWrapper extends TCPSocketWrapper { + @override + bool onBadCertificate(dynamic certificate, String domain) { + return true; + } +} + +void main() { + Logger.root.level = Level.ALL; + Logger.root.onRecord.listen((record) { + // ignore: avoid_print + print( + '[${record.level.name}] (${record.loggerName}) ${record.time}: ${record.message}', + ); + }); + + test('Test connecting to prosody as a component', () async { + final conn = XmppConnection( + TestingReconnectionPolicy(), + AlwaysConnectedConnectivityManager(), + ComponentToServerNegotiator(), + TestingTCPSocketWrapper(), + )..connectionSettings = ConnectionSettings( + jid: JID.fromString('component.localhost'), + password: 'abc123', + useDirectTLS: false, + host: '127.0.0.1', + port: 8888, + ); + await conn.registerManagers([ + RosterManager(TestingRosterStateManager('', [])), + DiscoManager([]), + ]); + + final result = await conn.connect( + waitUntilLogin: true, + shouldReconnect: false, + enableReconnectOnSuccess: false, + ); + expect(result.isType(), true); + }); +} diff --git a/integration_tests/test/sasl2_test.dart b/integration_tests/test/sasl2_test.dart index edd278f..9248e9c 100644 --- a/integration_tests/test/sasl2_test.dart +++ b/integration_tests/test/sasl2_test.dart @@ -19,20 +19,19 @@ void main() { ); }); - test('Test authenticating against Prosody with SASL2, Bind2, and FAST', () async { + test('Test authenticating against Prosody with SASL2, Bind2, and FAST', + () async { final conn = XmppConnection( TestingReconnectionPolicy(), AlwaysConnectedConnectivityManager(), + ClientToServerNegotiator(), TestingTCPSocketWrapper(), - )..setConnectionSettings( - ConnectionSettings( - jid: JID.fromString('testuser@localhost'), - password: 'abc123', - useDirectTLS: false, - - host: '127.0.0.1', - port: 5222, - ), + )..connectionSettings = ConnectionSettings( + jid: JID.fromString('testuser@localhost'), + password: 'abc123', + useDirectTLS: false, + host: '127.0.0.1', + port: 5222, ); final csi = CSIManager(); await csi.setInactive(sendNonza: false); @@ -61,7 +60,16 @@ void main() { enableReconnectOnSuccess: false, ); expect(result.isType(), true); - expect(conn.getNegotiatorById(sasl2Negotiator)!.state, NegotiatorState.done); - expect(conn.getNegotiatorById(saslFASTNegotiator)!.fastToken != null, true,); + expect( + conn.getNegotiatorById(sasl2Negotiator)!.state, + NegotiatorState.done, + ); + expect( + conn + .getNegotiatorById(saslFASTNegotiator)! + .fastToken != + null, + true, + ); }); } diff --git a/packages/moxxmpp/lib/moxxmpp.dart b/packages/moxxmpp/lib/moxxmpp.dart index e2c26c4..7a93596 100644 --- a/packages/moxxmpp/lib/moxxmpp.dart +++ b/packages/moxxmpp/lib/moxxmpp.dart @@ -5,6 +5,9 @@ export 'package:moxxmpp/src/connection_errors.dart'; export 'package:moxxmpp/src/connectivity.dart'; export 'package:moxxmpp/src/errors.dart'; export 'package:moxxmpp/src/events.dart'; +export 'package:moxxmpp/src/handlers/base.dart'; +export 'package:moxxmpp/src/handlers/client.dart'; +export 'package:moxxmpp/src/handlers/component.dart'; export 'package:moxxmpp/src/iq.dart'; export 'package:moxxmpp/src/jid.dart'; export 'package:moxxmpp/src/managers/attributes.dart'; @@ -15,7 +18,6 @@ export 'package:moxxmpp/src/managers/namespaces.dart'; export 'package:moxxmpp/src/managers/priorities.dart'; export 'package:moxxmpp/src/message.dart'; export 'package:moxxmpp/src/namespaces.dart'; -export 'package:moxxmpp/src/negotiators/handler.dart'; export 'package:moxxmpp/src/negotiators/manager.dart'; export 'package:moxxmpp/src/negotiators/namespaces.dart'; export 'package:moxxmpp/src/negotiators/negotiator.dart'; diff --git a/packages/moxxmpp/lib/src/connection.dart b/packages/moxxmpp/lib/src/connection.dart index 78107f5..b1c1931 100644 --- a/packages/moxxmpp/lib/src/connection.dart +++ b/packages/moxxmpp/lib/src/connection.dart @@ -8,15 +8,13 @@ import 'package:moxxmpp/src/connection_errors.dart'; import 'package:moxxmpp/src/connectivity.dart'; import 'package:moxxmpp/src/errors.dart'; import 'package:moxxmpp/src/events.dart'; +import 'package:moxxmpp/src/handlers/base.dart'; import 'package:moxxmpp/src/iq.dart'; -import 'package:moxxmpp/src/jid.dart'; import 'package:moxxmpp/src/managers/attributes.dart'; import 'package:moxxmpp/src/managers/base.dart'; 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/handler.dart'; import 'package:moxxmpp/src/negotiators/namespaces.dart'; import 'package:moxxmpp/src/negotiators/negotiator.dart'; import 'package:moxxmpp/src/presence.dart'; @@ -63,23 +61,6 @@ enum StanzaFromType { none, } -/// Nonza describing the XMPP stream header. -class StreamHeaderNonza extends XMLNode { - StreamHeaderNonza(JID jid) - : super( - tag: 'stream:stream', - attributes: { - 'xmlns': stanzaXmlns, - 'version': '1.0', - 'xmlns:stream': streamXmlns, - 'to': jid.domain, - 'from': jid.toBare().toString(), - 'xml:lang': 'en', - }, - closeTag: false, - ); -} - /// This class is a connection to the server. class XmppConnection { XmppConnection( @@ -99,8 +80,9 @@ class XmppConnection { _negotiationsHandler.register( _onNegotiationsDone, handleError, - _sendStreamHeaders, () => _isAuthenticated, + sendRawXML, + () => connectionSettings, ); _socketStream = _socket.getDataStream(); @@ -768,7 +750,7 @@ class XmppConnection { /// Called whenever we receive data that has been parsed as XML. Future handleXmlStream(XmlStreamBufferObject event) async { if (event is XmlStreamBufferHeader) { - _negotiationsHandler.setStreamHeaderId(event.attributes['id']); + await _negotiationsHandler.negotiate(event); return; } @@ -799,11 +781,11 @@ class XmppConnection { // prevent this issue. await _negotiationLock.synchronized(() async { if (_routingState != RoutingState.negotiating) { - unawaited(handleXmlStream(XmlStreamBufferElement(node))); + unawaited(handleXmlStream(event)); return; } - await _negotiationsHandler.negotiate(node); + await _negotiationsHandler.negotiate(event); }); break; case RoutingState.handleStanzas: @@ -833,21 +815,6 @@ class XmppConnection { _eventStreamController.add(event); } - /// Sends a stream header to the socket. - void _sendStreamHeaders() { - _socket.write( - XMLNode( - tag: 'xml', - attributes: {'version': '1.0'}, - closeTag: false, - isDeclaration: true, - children: [ - StreamHeaderNonza(connectionSettings.jid), - ], - ).toXml(), - ); - } - /// Attempt to gracefully close the session Future disconnect() async { await _disconnect(state: XmppConnectionState.notConnected); @@ -955,7 +922,7 @@ class XmppConnection { await _setConnectionState(XmppConnectionState.connecting); _updateRoutingState(RoutingState.negotiating); _isAuthenticated = false; - _sendStreamHeaders(); + _negotiationsHandler.sendStreamHeader(); if (waitUntilLogin) { return _connectionCompleter!.future; diff --git a/packages/moxxmpp/lib/src/connection_errors.dart b/packages/moxxmpp/lib/src/connection_errors.dart index 5b5ca61..97f45cc 100644 --- a/packages/moxxmpp/lib/src/connection_errors.dart +++ b/packages/moxxmpp/lib/src/connection_errors.dart @@ -46,3 +46,15 @@ class NoAuthenticatorAvailableError extends XmppConnectionError { @override bool isRecoverable() => false; } + +/// Returned by the negotiation handler if unexpected data has been received +class UnexpectedDataError extends XmppConnectionError { + @override + bool isRecoverable() => false; +} + +/// Returned by the ComponentToServerNegotiator if the handshake is not successful. +class InvalidHandshakeCredentialsError extends XmppConnectionError { + @override + bool isRecoverable() => false; +} diff --git a/packages/moxxmpp/lib/src/handlers/base.dart b/packages/moxxmpp/lib/src/handlers/base.dart new file mode 100644 index 0000000..03bf820 --- /dev/null +++ b/packages/moxxmpp/lib/src/handlers/base.dart @@ -0,0 +1,121 @@ +import 'package:logging/logging.dart'; +import 'package:meta/meta.dart'; +import 'package:moxxmpp/src/buffer.dart'; +import 'package:moxxmpp/src/errors.dart'; +import 'package:moxxmpp/src/events.dart'; +import 'package:moxxmpp/src/negotiators/negotiator.dart'; +import 'package:moxxmpp/src/settings.dart'; +import 'package:moxxmpp/src/stringxml.dart'; + +/// A callback for when the [NegotiationsHandler] is done. +typedef NegotiationsDoneCallback = Future Function(); + +/// A callback for the case that an error occurs while negotiating. +typedef ErrorCallback = Future Function(XmppError); + +/// Return true if the current connection is authenticated. If not, return false. +typedef IsAuthenticatedFunction = bool Function(); + +/// Send a nonza on the stream +typedef SendNonzaFunction = void Function(XMLNode); + +/// Returns the connection settings. +typedef GetConnectionSettingsFunction = ConnectionSettings Function(); + +/// This class implements the stream feature negotiation for XmppConnection. +abstract class NegotiationsHandler { + @protected + late final Logger log; + + /// Map of all negotiators registered against the handler. + @protected + final Map negotiators = {}; + + /// Function that is called once the negotiator is done with its stream negotiations. + @protected + late final NegotiationsDoneCallback onNegotiationsDone; + + /// XmppConnection's handleError method. + @protected + late final ErrorCallback handleError; + + /// Returns true if the connection is authenticated. If not, returns false. + @protected + late final IsAuthenticatedFunction isAuthenticated; + + /// Send a nonza over the stream. + @protected + late final SendNonzaFunction sendNonza; + + /// Get the connection's settings. + @protected + late final GetConnectionSettingsFunction getConnectionSettings; + + /// The id included in the last stream header. + @protected + String? streamId; + + /// Set the id of the last stream header. + void setStreamHeaderId(String? id) { + streamId = id; + } + + /// Returns, if registered, a negotiator with id [id]. + T? getNegotiatorById(String id) => + negotiators[id] as T?; + + /// Register the parameters as the corresponding methods in this class. Also + /// initializes the logger. + void register( + NegotiationsDoneCallback onNegotiationsDone, + ErrorCallback handleError, + IsAuthenticatedFunction isAuthenticated, + SendNonzaFunction sendNonza, + GetConnectionSettingsFunction getConnectionSettings, + ) { + this.onNegotiationsDone = onNegotiationsDone; + this.handleError = handleError; + this.isAuthenticated = isAuthenticated; + this.sendNonza = sendNonza; + this.getConnectionSettings = getConnectionSettings; + log = Logger(toString()); + } + + /// Registers the negotiator [negotiator] against this negotiations handler. + void registerNegotiator(XmppFeatureNegotiatorBase negotiator); + + /// Sends the stream header. + void sendStreamHeader(); + + /// Runs the post-register callback of all negotiators. + Future runPostRegisterCallback() async { + for (final negotiator in negotiators.values) { + await negotiator.postRegisterCallback(); + } + } + + Future sendEventToNegotiators(XmppEvent event) async { + for (final negotiator in negotiators.values) { + await negotiator.onXmppEvent(event); + } + } + + /// Remove [feature] from the stream features we are currently negotiating. + void removeNegotiatingFeature(String feature) {} + + /// Resets all registered negotiators and the negotiation handler. + @mustCallSuper + void reset() { + streamId = null; + for (final negotiator in negotiators.values) { + negotiator.reset(); + } + } + + /// Called whenever the stream buffer outputs a new event [event]. + Future negotiate(XmlStreamBufferObject event) async { + if (event is XmlStreamBufferHeader) { + streamId = event.attributes['id']; + } + } +} diff --git a/packages/moxxmpp/lib/src/negotiators/handler.dart b/packages/moxxmpp/lib/src/handlers/client.dart similarity index 62% rename from packages/moxxmpp/lib/src/negotiators/handler.dart rename to packages/moxxmpp/lib/src/handlers/client.dart index 67608dc..eff1309 100644 --- a/packages/moxxmpp/lib/src/negotiators/handler.dart +++ b/packages/moxxmpp/lib/src/handlers/client.dart @@ -1,106 +1,27 @@ -import 'package:logging/logging.dart'; import 'package:meta/meta.dart'; +import 'package:moxxmpp/src/buffer.dart'; import 'package:moxxmpp/src/connection_errors.dart'; -import 'package:moxxmpp/src/errors.dart'; -import 'package:moxxmpp/src/events.dart'; +import 'package:moxxmpp/src/handlers/base.dart'; +import 'package:moxxmpp/src/jid.dart'; +import 'package:moxxmpp/src/namespaces.dart'; import 'package:moxxmpp/src/negotiators/negotiator.dart'; import 'package:moxxmpp/src/stringxml.dart'; -/// A callback for when the [NegotiationsHandler] is done. -typedef NegotiationsDoneCallback = Future Function(); - -/// A callback for the case that an error occurs while negotiating. -typedef ErrorCallback = Future Function(XmppError); - -/// Trigger stream headers to be sent -typedef SendStreamHeadersFunction = void Function(); - -/// Return true if the current connection is authenticated. If not, return false. -typedef IsAuthenticatedFunction = bool Function(); - -/// This class implements the stream feature negotiation for XmppConnection. -abstract class NegotiationsHandler { - @protected - late final Logger log; - - /// Map of all negotiators registered against the handler. - @protected - final Map negotiators = {}; - - /// Function that is called once the negotiator is done with its stream negotiations. - @protected - late final NegotiationsDoneCallback onNegotiationsDone; - - /// XmppConnection's handleError method. - @protected - late final ErrorCallback handleError; - - /// Sends stream headers in the stream. - @protected - late final SendStreamHeadersFunction sendStreamHeaders; - - /// Returns true if the connection is authenticated. If not, returns false. - @protected - late final IsAuthenticatedFunction isAuthenticated; - - /// The id included in the last stream header. - @protected - String? streamId; - - /// Set the id of the last stream header. - void setStreamHeaderId(String? id) { - streamId = id; - } - - /// Returns, if registered, a negotiator with id [id]. - T? getNegotiatorById(String id) => - negotiators[id] as T?; - - /// Register the parameters as the corresponding methods in this class. Also - /// initializes the logger. - void register( - NegotiationsDoneCallback onNegotiationsDone, - ErrorCallback handleError, - SendStreamHeadersFunction sendStreamHeaders, - IsAuthenticatedFunction isAuthenticated, - ) { - this.onNegotiationsDone = onNegotiationsDone; - this.handleError = handleError; - this.sendStreamHeaders = sendStreamHeaders; - this.isAuthenticated = isAuthenticated; - log = Logger(toString()); - } - - /// Registers the negotiator [negotiator] against this negotiations handler. - void registerNegotiator(XmppFeatureNegotiatorBase negotiator); - - /// Runs the post-register callback of all negotiators. - Future runPostRegisterCallback() async { - for (final negotiator in negotiators.values) { - await negotiator.postRegisterCallback(); - } - } - - Future sendEventToNegotiators(XmppEvent event) async { - for (final negotiator in negotiators.values) { - await negotiator.onXmppEvent(event); - } - } - - /// Remove [feature] from the stream features we are currently negotiating. - void removeNegotiatingFeature(String feature) {} - - /// Resets all registered negotiators and the negotiation handler. - @mustCallSuper - void reset() { - streamId = null; - for (final negotiator in negotiators.values) { - negotiator.reset(); - } - } - - /// Called whenever a new nonza [nonza] is received while negotiating. - Future negotiate(XMLNode nonza); +/// "Nonza" describing the XMPP stream header of a client-to-server connection. +class ClientStreamHeaderNonza extends XMLNode { + ClientStreamHeaderNonza(JID jid) + : super( + tag: 'stream:stream', + attributes: { + 'xmlns': stanzaXmlns, + 'version': '1.0', + 'xmlns:stream': streamXmlns, + 'to': jid.domain, + 'from': jid.toBare().toString(), + 'xml:lang': 'en', + }, + closeTag: false, + ); } /// This class implements the stream feature negotiation for usage in client to server @@ -134,6 +55,21 @@ class ClientToServerNegotiator extends NegotiationsHandler { }); } + @override + void sendStreamHeader() { + sendNonza( + XMLNode( + tag: 'xml', + attributes: {'version': '1.0'}, + closeTag: false, + isDeclaration: true, + children: [ + ClientStreamHeaderNonza(getConnectionSettings().jid), + ], + ), + ); + } + /// Returns true if all mandatory features in [features] have been negotiated. /// Otherwise returns false. bool _isMandatoryNegotiationDone(List features) { @@ -219,7 +155,7 @@ class ClientToServerNegotiator extends NegotiationsHandler { if (_currentNegotiator!.sendStreamHeaderWhenDone) { _currentNegotiator = null; _streamFeatures.clear(); - sendStreamHeaders(); + sendStreamHeader(); } else { removeNegotiatingFeature(_currentNegotiator!.negotiatingXmlns); _currentNegotiator = null; @@ -272,14 +208,16 @@ class ClientToServerNegotiator extends NegotiationsHandler { } @override - Future negotiate(XMLNode nonza) async { - if (nonza.tag == 'stream:features') { - // Store the received stream features - _streamFeatures - ..clear() - ..addAll(nonza.children); - } + Future negotiate(XmlStreamBufferObject event) async { + if (event is XmlStreamBufferElement) { + if (event.node.tag == 'stream:features') { + // Store the received stream features + _streamFeatures + ..clear() + ..addAll(event.node.children); + } - await _executeCurrentNegotiator(nonza); + await _executeCurrentNegotiator(event.node); + } } } diff --git a/packages/moxxmpp/lib/src/handlers/component.dart b/packages/moxxmpp/lib/src/handlers/component.dart new file mode 100644 index 0000000..540f17a --- /dev/null +++ b/packages/moxxmpp/lib/src/handlers/component.dart @@ -0,0 +1,117 @@ +import 'dart:convert'; +import 'package:cryptography/cryptography.dart'; +import 'package:hex/hex.dart'; +import 'package:moxxmpp/src/buffer.dart'; +import 'package:moxxmpp/src/connection_errors.dart'; +import 'package:moxxmpp/src/handlers/base.dart'; +import 'package:moxxmpp/src/jid.dart'; +import 'package:moxxmpp/src/namespaces.dart'; +import 'package:moxxmpp/src/negotiators/negotiator.dart'; +import 'package:moxxmpp/src/stringxml.dart'; + +/// Nonza describing the XMPP stream header. +class ComponentStreamHeaderNonza extends XMLNode { + ComponentStreamHeaderNonza(JID jid) + : assert(jid.isBare(), 'Component JID must be bare'), + super( + tag: 'stream:stream', + attributes: { + 'xmlns': componentAcceptXmlns, + 'xmlns:stream': streamXmlns, + 'to': jid.domain, + }, + closeTag: false, + ); +} + +/// The states the ComponentToServerNegotiator can be in. +enum ComponentToServerState { + /// No data has been sent or received yet + idle, + + /// Handshake has been sent + handshakeSent, +} + +/// The ComponentToServerNegotiator is a NegotiationsHandler that allows writing +/// components that adhere to XEP-0114. +class ComponentToServerNegotiator extends NegotiationsHandler { + ComponentToServerNegotiator(); + + /// The state the negotiation handler is currently in + ComponentToServerState _state = ComponentToServerState.idle; + + @override + void registerNegotiator(XmppFeatureNegotiatorBase negotiator) {} + + @override + void sendStreamHeader() { + sendNonza( + XMLNode( + tag: 'xml', + attributes: {'version': '1.0'}, + closeTag: false, + isDeclaration: true, + children: [ + ComponentStreamHeaderNonza(getConnectionSettings().jid), + ], + ), + ); + } + + Future _computeHandshake(String id) async { + final secret = getConnectionSettings().password; + return HEX.encode( + (await Sha1().hash(utf8.encode('$streamId$secret'))).bytes, + ); + } + + @override + Future negotiate(XmlStreamBufferObject event) async { + switch (_state) { + case ComponentToServerState.idle: + if (event is XmlStreamBufferHeader) { + streamId = event.attributes['id']; + assert( + streamId != null, + 'The server must respond with a stream header that contains an id', + ); + + _state = ComponentToServerState.handshakeSent; + sendNonza( + XMLNode( + tag: 'handshake', + text: await _computeHandshake(streamId!), + ), + ); + } else { + log.severe('Unexpected data received'); + await handleError(UnexpectedDataError()); + } + break; + case ComponentToServerState.handshakeSent: + if (event is XmlStreamBufferElement) { + if (event.node.tag == 'handshake' && + event.node.children.isEmpty && + event.node.attributes.isEmpty) { + log.info('Successfully authenticated as component'); + await onNegotiationsDone(); + } else { + log.warning('Handshake failed'); + await handleError(InvalidHandshakeCredentialsError()); + } + } else { + log.severe('Unexpected data received'); + await handleError(UnexpectedDataError()); + } + break; + } + } + + @override + void reset() { + _state = ComponentToServerState.idle; + + super.reset(); + } +} diff --git a/packages/moxxmpp/lib/src/namespaces.dart b/packages/moxxmpp/lib/src/namespaces.dart index 86b69b3..a483929 100644 --- a/packages/moxxmpp/lib/src/namespaces.dart +++ b/packages/moxxmpp/lib/src/namespaces.dart @@ -44,6 +44,9 @@ const userAvatarMetadataXmlns = 'urn:xmpp:avatar:metadata'; // XEP-0085 const chatStateXmlns = 'http://jabber.org/protocol/chatstates'; +// XEP-0114 +const componentAcceptXmlns = 'jabber:component:accept'; + // XEP-0115 const capsXmlns = 'http://jabber.org/protocol/caps'; diff --git a/packages/moxxmpp/lib/src/settings.dart b/packages/moxxmpp/lib/src/settings.dart index 177672b..a8f34e9 100644 --- a/packages/moxxmpp/lib/src/settings.dart +++ b/packages/moxxmpp/lib/src/settings.dart @@ -8,10 +8,19 @@ class ConnectionSettings { this.host, this.port, }); + + /// The JID to authenticate as. final JID jid; + + /// The password to use during authentication. final String password; + + /// Directly use TLS while connecting. Only effective if [host] and [port] are null. final bool useDirectTLS; + /// The host to connect to. Skips DNS resolution if specified. final String? host; + + /// The port to connect to. Skips DNS resolution if specified. final int? port; } diff --git a/packages/moxxmpp/test/component_test.dart b/packages/moxxmpp/test/component_test.dart new file mode 100644 index 0000000..f17d89b --- /dev/null +++ b/packages/moxxmpp/test/component_test.dart @@ -0,0 +1,48 @@ +import 'package:moxxmpp/moxxmpp.dart'; +import 'package:test/test.dart'; +import 'helpers/logging.dart'; +import 'helpers/xmpp.dart'; + +const exampleXmlns1 = 'im:moxxmpp:example1'; +const exampleNamespace1 = 'im.moxxmpp.test.example1'; +const exampleXmlns2 = 'im:moxxmpp:example2'; +const exampleNamespace2 = 'im.moxxmpp.test.example2'; + +void main() { + initLogger(); + + test('Test connecting as a component', () async { + final socket = StubTCPSocket([ + StringExpectation( + "", + ''' +'''), + StringExpectation( + 'ee8567f3b4c6e315345416b45ca2e47dbe921565', + '', + ), + ]); + final conn = XmppConnection( + TestingReconnectionPolicy(), + AlwaysConnectedConnectivityManager(), + ComponentToServerNegotiator(), + socket, + )..connectionSettings = ConnectionSettings( + jid: JID.fromString('component.example.org'), + password: 'abc123', + useDirectTLS: true, + ); + await conn.registerManagers([ + RosterManager(TestingRosterStateManager('', [])), + DiscoManager([]), + ]); + final result = await conn.connect( + waitUntilLogin: true, + ); + expect(result.isType(), true); + }); +} diff --git a/packages/moxxmpp/test/helpers/manager.dart b/packages/moxxmpp/test/helpers/manager.dart index 2876999..8a14855 100644 --- a/packages/moxxmpp/test/helpers/manager.dart +++ b/packages/moxxmpp/test/helpers/manager.dart @@ -1,10 +1,10 @@ import 'dart:async'; import 'package:moxxmpp/src/connection.dart'; import 'package:moxxmpp/src/connectivity.dart'; +import 'package:moxxmpp/src/handlers/client.dart'; import 'package:moxxmpp/src/jid.dart'; import 'package:moxxmpp/src/managers/attributes.dart'; import 'package:moxxmpp/src/managers/base.dart'; -import 'package:moxxmpp/src/negotiators/handler.dart'; import 'package:moxxmpp/src/reconnect.dart'; import 'package:moxxmpp/src/settings.dart'; import 'package:moxxmpp/src/socket.dart'; diff --git a/packages/moxxmpp/test/negotiator_test.dart b/packages/moxxmpp/test/negotiator_test.dart index 98becc0..12a2160 100644 --- a/packages/moxxmpp/test/negotiator_test.dart +++ b/packages/moxxmpp/test/negotiator_test.dart @@ -1,7 +1,7 @@ import 'package:moxxmpp/moxxmpp.dart'; +import 'package:moxxmpp/src/buffer.dart'; import 'package:test/test.dart'; import 'helpers/logging.dart'; -//import 'helpers/xmpp.dart'; const exampleXmlns1 = 'im:moxxmpp:example1'; const exampleNamespace1 = 'im.moxxmpp.test.example1'; @@ -53,8 +53,13 @@ void main() { ..register( () async {}, (_) async {}, - () {}, () => false, + (_) {}, + () => ConnectionSettings( + jid: JID.fromString('test'), + password: 'abc123', + useDirectTLS: false, + ), ) ..registerNegotiator(StubNegotiator1()) ..registerNegotiator(StubNegotiator2()); @@ -67,20 +72,27 @@ void main() { ..register( () async {}, (_) async {}, - () {}, () => false, + (_) {}, + () => ConnectionSettings( + jid: JID.fromString('test'), + password: 'abc123', + useDirectTLS: false, + ), ) ..registerNegotiator(StubNegotiator1()) ..registerNegotiator(StubNegotiator2()); await negotiator.runPostRegisterCallback(); await negotiator.negotiate( - XMLNode.fromString( - ''' + XmlStreamBufferElement( + XMLNode.fromString( + ''' ''', + ), ), ); diff --git a/packages/moxxmpp/test/stringxml_test.dart b/packages/moxxmpp/test/stringxml_test.dart index 41aeb1c..8ec67a0 100644 --- a/packages/moxxmpp/test/stringxml_test.dart +++ b/packages/moxxmpp/test/stringxml_test.dart @@ -23,7 +23,7 @@ void main() { ); expect( - StreamHeaderNonza(JID.fromString('user@uwu.server')).toXml(), + ClientStreamHeaderNonza(JID.fromString('user@uwu.server')).toXml(), "", );