Compare commits
	
		
			3 Commits
		
	
	
		
			d7723615fe
			...
			ed49212f5a
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| ed49212f5a | |||
| ad1242c47d | |||
| 890fcfb506 | 
| @ -29,22 +29,35 @@ import 'package:moxxmpp/src/xeps/xep_0352.dart'; | |||||||
| import 'package:synchronized/synchronized.dart'; | import 'package:synchronized/synchronized.dart'; | ||||||
| import 'package:uuid/uuid.dart'; | import 'package:uuid/uuid.dart'; | ||||||
| 
 | 
 | ||||||
|  | /// The states the XmppConnection can be in | ||||||
| enum XmppConnectionState { | enum XmppConnectionState { | ||||||
|  |   /// The XmppConnection instance is not connected to the server. This is either the | ||||||
|  |   /// case before connecting or after disconnecting. | ||||||
|   notConnected, |   notConnected, | ||||||
|  | 
 | ||||||
|  |   /// We are currently trying to connect to the server. | ||||||
|   connecting, |   connecting, | ||||||
|  | 
 | ||||||
|  |   /// We are currently connected to the server. | ||||||
|   connected, |   connected, | ||||||
|  | 
 | ||||||
|  |   /// We have received an unrecoverable error and the server killed the connection | ||||||
|   error |   error | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | /// Metadata for [XmppConnection.sendStanza]. | ||||||
| enum StanzaFromType { | enum StanzaFromType { | ||||||
|   // Add the full JID to the stanza as the from attribute |   /// Add the full JID to the stanza as the from attribute | ||||||
|   full, |   full, | ||||||
|   // Add the bare JID to the stanza as the from attribute | 
 | ||||||
|  |   /// Add the bare JID to the stanza as the from attribute | ||||||
|   bare, |   bare, | ||||||
|   // Add no JID as the from attribute | 
 | ||||||
|   none |   /// Add no JID as the from attribute | ||||||
|  |   none, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | /// Nonza describing the XMPP stream header. | ||||||
| class StreamHeaderNonza extends XMLNode { | class StreamHeaderNonza extends XMLNode { | ||||||
|   StreamHeaderNonza(String serverDomain) : super( |   StreamHeaderNonza(String serverDomain) : super( | ||||||
|       tag: 'stream:stream', |       tag: 'stream:stream', | ||||||
| @ -59,6 +72,7 @@ class StreamHeaderNonza extends XMLNode { | |||||||
|     ); |     ); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | /// The result of an awaited connection. | ||||||
| class XmppConnectionResult { | class XmppConnectionResult { | ||||||
|   const XmppConnectionResult( |   const XmppConnectionResult( | ||||||
|     this.success, |     this.success, | ||||||
| @ -67,16 +81,24 @@ class XmppConnectionResult { | |||||||
|     } |     } | ||||||
|   ); |   ); | ||||||
| 
 | 
 | ||||||
|  |   /// True if the connection was successful. False if it failed for any reason. | ||||||
|   final bool success; |   final bool success; | ||||||
|  | 
 | ||||||
|   // If a connection attempt fails, i.e. success is false, then this indicates the |   // If a connection attempt fails, i.e. success is false, then this indicates the | ||||||
|   // reason the connection failed. |   // reason the connection failed. | ||||||
|   final XmppError? error; |   final XmppError? error; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | /// A surrogate key for awaiting stanzas. | ||||||
| @immutable | @immutable | ||||||
| class _StanzaAwaitableData { | class _StanzaAwaitableData { | ||||||
|   const _StanzaAwaitableData(this.sentTo, this.id); |   const _StanzaAwaitableData(this.sentTo, this.id); | ||||||
|  | 
 | ||||||
|  |   /// The JID the original stanza was sent to. We expect the result to come from the | ||||||
|  |   /// same JID. | ||||||
|   final String sentTo; |   final String sentTo; | ||||||
|  | 
 | ||||||
|  |   /// The ID of the original stanza. We expect the result to have the same ID. | ||||||
|   final String id; |   final String id; | ||||||
| 
 | 
 | ||||||
|   @override |   @override | ||||||
| @ -90,10 +112,8 @@ class _StanzaAwaitableData { | |||||||
|   } |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | /// This class is a connection to the server. | ||||||
| class XmppConnection { | class XmppConnection { | ||||||
|   /// [_socket] is for debugging purposes. |  | ||||||
|   /// [connectionPingDuration] is the duration after which a ping will be sent to keep |  | ||||||
|   /// the connection open. Defaults to 15 minutes. |  | ||||||
|   XmppConnection( |   XmppConnection( | ||||||
|     ReconnectionPolicy reconnectionPolicy, |     ReconnectionPolicy reconnectionPolicy, | ||||||
|     this._socket, |     this._socket, | ||||||
| @ -101,26 +121,7 @@ class XmppConnection { | |||||||
|       this.connectionPingDuration = const Duration(minutes: 3), |       this.connectionPingDuration = const Duration(minutes: 3), | ||||||
|       this.connectingTimeout = const Duration(minutes: 2), |       this.connectingTimeout = const Duration(minutes: 2), | ||||||
|     } |     } | ||||||
|   ) : |   ) : _reconnectionPolicy = reconnectionPolicy { | ||||||
|     _connectionState = XmppConnectionState.notConnected, |  | ||||||
|     _routingState = RoutingState.preConnection, |  | ||||||
|     _eventStreamController = StreamController.broadcast(), |  | ||||||
|     _resource = '', |  | ||||||
|     _streamBuffer = XmlStreamBuffer(), |  | ||||||
|     _uuid = const Uuid(), |  | ||||||
|     _awaitingResponse = {}, |  | ||||||
|     _awaitingResponseLock = Lock(), |  | ||||||
|     _xmppManagers = {}, |  | ||||||
|     _incomingStanzaHandlers = List.empty(growable: true), |  | ||||||
|     _incomingPreStanzaHandlers = List.empty(growable: true), |  | ||||||
|     _outgoingPreStanzaHandlers = List.empty(growable: true), |  | ||||||
|     _outgoingPostStanzaHandlers = List.empty(growable: true), |  | ||||||
|     _reconnectionPolicy = reconnectionPolicy, |  | ||||||
|     _featureNegotiators = {}, |  | ||||||
|     _streamFeatures = List.empty(growable: true), |  | ||||||
|     _negotiationLock = Lock(), |  | ||||||
|     _isAuthenticated = false, |  | ||||||
|     _log = Logger('XmppConnection') { |  | ||||||
|       // Allow the reconnection policy to perform reconnections by itself |       // Allow the reconnection policy to perform reconnections by itself | ||||||
|       _reconnectionPolicy.register( |       _reconnectionPolicy.register( | ||||||
|         _attemptReconnection, |         _attemptReconnection, | ||||||
| @ -134,69 +135,103 @@ class XmppConnection { | |||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|   /// Connection properties |   /// The state that the connection is currently in | ||||||
|   /// |   XmppConnectionState _connectionState = XmppConnectionState.notConnected; | ||||||
|   /// The state that the connection currently is in | 
 | ||||||
|   XmppConnectionState _connectionState; |  | ||||||
|   /// The socket that we are using for the connection and its data stream |   /// The socket that we are using for the connection and its data stream | ||||||
|   final BaseSocketWrapper _socket; |   final BaseSocketWrapper _socket; | ||||||
|  | 
 | ||||||
|  |   /// The data stream of the socket | ||||||
|   late final Stream<String> _socketStream; |   late final Stream<String> _socketStream; | ||||||
|   /// Account settings | 
 | ||||||
|  | 
 | ||||||
|  |   /// Connection settings | ||||||
|   late ConnectionSettings _connectionSettings; |   late ConnectionSettings _connectionSettings; | ||||||
|  | 
 | ||||||
|   /// A policy on how to reconnect  |   /// A policy on how to reconnect  | ||||||
|   final ReconnectionPolicy _reconnectionPolicy; |   final ReconnectionPolicy _reconnectionPolicy; | ||||||
|   /// A list of stanzas we are tracking with its corresponding critical section |  | ||||||
|   final Map<_StanzaAwaitableData, Completer<XMLNode>> _awaitingResponse; |  | ||||||
|   final Lock _awaitingResponseLock; |  | ||||||
| 
 | 
 | ||||||
|   /// Helpers |   /// A list of stanzas we are tracking with its corresponding critical section lock | ||||||
|   /// |   final Map<_StanzaAwaitableData, Completer<XMLNode>> _awaitingResponse = {}; | ||||||
|   final List<StanzaHandler> _incomingStanzaHandlers; |   final Lock _awaitingResponseLock = Lock(); | ||||||
|   final List<StanzaHandler> _incomingPreStanzaHandlers; |    | ||||||
|   final List<StanzaHandler> _outgoingPreStanzaHandlers; |   /// Sorted list of handlers that we call or incoming and outgoing stanzas | ||||||
|   final List<StanzaHandler> _outgoingPostStanzaHandlers; |   final List<StanzaHandler> _incomingStanzaHandlers = List.empty(growable: true); | ||||||
|   final StreamController<XmppEvent> _eventStreamController; |   final List<StanzaHandler> _incomingPreStanzaHandlers = List.empty(growable: true); | ||||||
|   final Map<String, XmppManagerBase> _xmppManagers; |   final List<StanzaHandler> _outgoingPreStanzaHandlers = List.empty(growable: true); | ||||||
|  |   final List<StanzaHandler> _outgoingPostStanzaHandlers = List.empty(growable: true); | ||||||
|  |   final StreamController<XmppEvent> _eventStreamController = StreamController.broadcast(); | ||||||
|  |   final Map<String, XmppManagerBase> _xmppManagers = {}; | ||||||
|    |    | ||||||
|   /// Stream properties |  | ||||||
|   /// |  | ||||||
|   /// Disco info we got after binding a resource (xmlns) |   /// Disco info we got after binding a resource (xmlns) | ||||||
|   final List<String> _serverFeatures = List.empty(growable: true); |   final List<String> _serverFeatures = List.empty(growable: true); | ||||||
|  | 
 | ||||||
|   /// The buffer object to keep split up stanzas together |   /// The buffer object to keep split up stanzas together | ||||||
|   final XmlStreamBuffer _streamBuffer; |   final XmlStreamBuffer _streamBuffer = XmlStreamBuffer(); | ||||||
|  | 
 | ||||||
|   /// UUID object to generate stanza and origin IDs |   /// UUID object to generate stanza and origin IDs | ||||||
|   final Uuid _uuid; |   final Uuid _uuid = const Uuid(); | ||||||
|  | 
 | ||||||
|   /// The time between sending a ping to keep the connection open |   /// The time between sending a ping to keep the connection open | ||||||
|   // TODO(Unknown): Only start the timer if we did not send a stanza after n seconds |   // TODO(Unknown): Only start the timer if we did not send a stanza after n seconds | ||||||
|   final Duration connectionPingDuration; |   final Duration connectionPingDuration; | ||||||
|  | 
 | ||||||
|   /// The time that we may spent in the "connecting" state |   /// The time that we may spent in the "connecting" state | ||||||
|   final Duration connectingTimeout; |   final Duration connectingTimeout; | ||||||
|  | 
 | ||||||
|   /// The current state of the connection handling state machine. |   /// The current state of the connection handling state machine. | ||||||
|   RoutingState _routingState; |   RoutingState _routingState = RoutingState.preConnection; | ||||||
|  | 
 | ||||||
|   /// The currently bound resource or '' if none has been bound yet. |   /// The currently bound resource or '' if none has been bound yet. | ||||||
|   String _resource; |   String _resource = ''; | ||||||
|  | 
 | ||||||
|   /// True if we are authenticated. False if not. |   /// True if we are authenticated. False if not. | ||||||
|   bool _isAuthenticated; |   bool _isAuthenticated = false; | ||||||
|  | 
 | ||||||
|   /// Timer for the keep-alive ping. |   /// Timer for the keep-alive ping. | ||||||
|   Timer? _connectionPingTimer; |   Timer? _connectionPingTimer; | ||||||
|  | 
 | ||||||
|   /// Timer for the connecting timeout |   /// Timer for the connecting timeout | ||||||
|   Timer? _connectingTimeoutTimer; |   Timer? _connectingTimeoutTimer; | ||||||
|  | 
 | ||||||
|   /// Completers for certain actions |   /// Completers for certain actions | ||||||
|   // ignore: use_late_for_private_fields_and_variables |   // ignore: use_late_for_private_fields_and_variables | ||||||
|   Completer<XmppConnectionResult>? _connectionCompleter; |   Completer<XmppConnectionResult>? _connectionCompleter; | ||||||
|  | 
 | ||||||
|   /// Controls whether an XmppSocketClosureEvent triggers a reconnection. |   /// Controls whether an XmppSocketClosureEvent triggers a reconnection. | ||||||
|   bool _socketClosureTriggersReconnect = true; |   bool _socketClosureTriggersReconnect = true; | ||||||
| 
 | 
 | ||||||
|   /// Negotiators |   /// Negotiators | ||||||
|   final Map<String, XmppFeatureNegotiatorBase> _featureNegotiators; |   final Map<String, XmppFeatureNegotiatorBase> _featureNegotiators = {}; | ||||||
|   XmppFeatureNegotiatorBase? _currentNegotiator; |   XmppFeatureNegotiatorBase? _currentNegotiator; | ||||||
|   final List<XMLNode> _streamFeatures; |   final List<XMLNode> _streamFeatures = List.empty(growable: true); | ||||||
|   /// Prevent data from being passed to _currentNegotiator.negotiator while the negotiator |   /// Prevent data from being passed to _currentNegotiator.negotiator while the negotiator | ||||||
|   /// is still running. |   /// is still running. | ||||||
|   final Lock _negotiationLock; |   final Lock _negotiationLock = Lock(); | ||||||
|    |    | ||||||
|   /// Misc |   /// The logger for the class | ||||||
|   final Logger _log; |   final Logger _log = Logger('XmppConnection'); | ||||||
|  | 
 | ||||||
|  |   /// A value indicating whether a connection attempt is currently running or not | ||||||
|  |   bool _isConnectionRunning = false; | ||||||
|  |   final Lock _connectionRunningLock = Lock(); | ||||||
|  | 
 | ||||||
|  |   /// Enters the critical section for accessing [XmppConnection._isConnectionRunning] | ||||||
|  |   /// and does the following: | ||||||
|  |   /// - if _isConnectionRunning is false, set it to true and return false. | ||||||
|  |   /// - if _isConnectionRunning is true, return true. | ||||||
|  |   Future<bool> _testAndSetIsConnectionRunning() async => _connectionRunningLock.synchronized(() { | ||||||
|  |     if (!_isConnectionRunning) { | ||||||
|  |       _isConnectionRunning = true; | ||||||
|  |       return false; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return true; | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   /// Enters the critical section for accessing [XmppConnection._isConnectionRunning] | ||||||
|  |   /// and sets it to false. | ||||||
|  |   Future<void> _resetIsConnectionRunning() async => _connectionRunningLock.synchronized(() => _isConnectionRunning = false); | ||||||
|    |    | ||||||
|   ReconnectionPolicy get reconnectionPolicy => _reconnectionPolicy; |   ReconnectionPolicy get reconnectionPolicy => _reconnectionPolicy; | ||||||
|    |    | ||||||
| @ -353,6 +388,11 @@ class XmppConnection { | |||||||
| 
 | 
 | ||||||
|   /// Attempts to reconnect to the server by following an exponential backoff. |   /// Attempts to reconnect to the server by following an exponential backoff. | ||||||
|   Future<void> _attemptReconnection() async { |   Future<void> _attemptReconnection() async { | ||||||
|  |     if (await _testAndSetIsConnectionRunning()) { | ||||||
|  |       _log.warning('_attemptReconnection is called but connection attempt is already running. Ignoring...'); | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     _log.finest('_attemptReconnection: Setting state to notConnected'); |     _log.finest('_attemptReconnection: Setting state to notConnected'); | ||||||
|     await _setConnectionState(XmppConnectionState.notConnected); |     await _setConnectionState(XmppConnectionState.notConnected); | ||||||
|     _log.finest('_attemptReconnection: Done'); |     _log.finest('_attemptReconnection: Done'); | ||||||
| @ -388,6 +428,7 @@ class XmppConnection { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     await _setConnectionState(XmppConnectionState.error); |     await _setConnectionState(XmppConnectionState.error); | ||||||
|  |     await _resetIsConnectionRunning(); | ||||||
|     await _reconnectionPolicy.onFailure(); |     await _reconnectionPolicy.onFailure(); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
| @ -785,6 +826,7 @@ class XmppConnection { | |||||||
|   /// a disco sweep among other things. |   /// a disco sweep among other things. | ||||||
|   Future<void> _onNegotiationsDone() async { |   Future<void> _onNegotiationsDone() async { | ||||||
|     // Set the connection state |     // Set the connection state | ||||||
|  |     await _resetIsConnectionRunning(); | ||||||
|     await _setConnectionState(XmppConnectionState.connected); |     await _setConnectionState(XmppConnectionState.connected); | ||||||
| 
 | 
 | ||||||
|     // Resolve the connection completion future |     // Resolve the connection completion future | ||||||
| @ -968,9 +1010,10 @@ class XmppConnection { | |||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   /// To be called when we lost the network connection. |   /// To be called when we lost the network connection. | ||||||
|   void _onNetworkConnectionLost() { |   Future<void> _onNetworkConnectionLost() async { | ||||||
|     _socket.close(); |     _socket.close(); | ||||||
|     _setConnectionState(XmppConnectionState.notConnected); |     await _resetIsConnectionRunning(); | ||||||
|  |     await _setConnectionState(XmppConnectionState.notConnected); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   /// Attempt to gracefully close the session |   /// Attempt to gracefully close the session | ||||||
| @ -1011,11 +1054,12 @@ class XmppConnection { | |||||||
|    |    | ||||||
|   /// Like [connect] but the Future resolves when the resource binding is either done or |   /// Like [connect] but the Future resolves when the resource binding is either done or | ||||||
|   /// SASL has failed. |   /// SASL has failed. | ||||||
|   Future<XmppConnectionResult> connectAwaitable({ String? lastResource }) { |   Future<XmppConnectionResult> connectAwaitable({ String? lastResource }) async { | ||||||
|     _runPreConnectionAssertions(); |     _runPreConnectionAssertions(); | ||||||
|  |     await _resetIsConnectionRunning(); | ||||||
|     _connectionCompleter = Completer(); |     _connectionCompleter = Completer(); | ||||||
|     _log.finest('Calling connect() from connectAwaitable'); |     _log.finest('Calling connect() from connectAwaitable'); | ||||||
|     connect(lastResource: lastResource); |     await connect(lastResource: lastResource); | ||||||
|     return _connectionCompleter!.future; |     return _connectionCompleter!.future; | ||||||
|   } |   } | ||||||
|   |   | ||||||
| @ -1027,6 +1071,7 @@ class XmppConnection { | |||||||
|     } |     } | ||||||
|      |      | ||||||
|     _runPreConnectionAssertions(); |     _runPreConnectionAssertions(); | ||||||
|  |     await _resetIsConnectionRunning(); | ||||||
|     _reconnectionPolicy.setShouldReconnect(true); |     _reconnectionPolicy.setShouldReconnect(true); | ||||||
|      |      | ||||||
|     if (lastResource != null) { |     if (lastResource != null) { | ||||||
|  | |||||||
| @ -1,10 +1,11 @@ | |||||||
| import 'package:meta/meta.dart'; | import 'package:meta/meta.dart'; | ||||||
| 
 | 
 | ||||||
|  | /// Represents a Jabber ID in parsed form. | ||||||
| @immutable | @immutable | ||||||
| class JID { | class JID { | ||||||
| 
 |  | ||||||
|   const JID(this.local, this.domain, this.resource); |   const JID(this.local, this.domain, this.resource); | ||||||
| 
 | 
 | ||||||
|  |   /// Parses the string [jid] into a JID instance. | ||||||
|   factory JID.fromString(String jid) { |   factory JID.fromString(String jid) { | ||||||
|     // Algorithm taken from here: https://blog.samwhited.com/2021/02/xmpp-addresses/ |     // Algorithm taken from here: https://blog.samwhited.com/2021/02/xmpp-addresses/ | ||||||
|     var localPart = ''; |     var localPart = ''; | ||||||
| @ -43,12 +44,29 @@ class JID { | |||||||
|   final String domain; |   final String domain; | ||||||
|   final String resource; |   final String resource; | ||||||
| 
 | 
 | ||||||
|  |   /// Returns true if the JID is bare. | ||||||
|   bool isBare() => resource.isEmpty; |   bool isBare() => resource.isEmpty; | ||||||
|  | 
 | ||||||
|  |   /// Returns true if the JID is full. | ||||||
|   bool isFull() => resource.isNotEmpty; |   bool isFull() => resource.isNotEmpty; | ||||||
| 
 | 
 | ||||||
|  |   /// Converts the JID into a bare JID. | ||||||
|   JID toBare() => JID(local, domain, ''); |   JID toBare() => JID(local, domain, ''); | ||||||
|  | 
 | ||||||
|  |   /// Converts the JID into one with a resource part of [resource]. | ||||||
|   JID withResource(String resource) => JID(local, domain, resource); |   JID withResource(String resource) => JID(local, domain, resource); | ||||||
| 
 | 
 | ||||||
|  |   /// Compares the JID with [other]. This function assumes that JID and [other] | ||||||
|  |   /// are bare, i.e. only the domain- and localparts are compared. If [ensureBare] | ||||||
|  |   /// is optionally set to true, then [other] MUST be bare. Otherwise, false is returned. | ||||||
|  |   bool bareCompare(JID other, { bool ensureBare = false }) { | ||||||
|  |     if (ensureBare && !other.isBare()) return false; | ||||||
|  | 
 | ||||||
|  |     return local == other.local && domain == other.domain; | ||||||
|  |   } | ||||||
|  |    | ||||||
|  |   /// Converts to JID instance into its string representation of | ||||||
|  |   /// localpart@domainpart/resource. | ||||||
|   @override |   @override | ||||||
|   String toString() { |   String toString() { | ||||||
|     var result = ''; |     var result = ''; | ||||||
|  | |||||||
| @ -4,27 +4,33 @@ import 'package:logging/logging.dart'; | |||||||
| import 'package:meta/meta.dart'; | import 'package:meta/meta.dart'; | ||||||
| import 'package:synchronized/synchronized.dart'; | import 'package:synchronized/synchronized.dart'; | ||||||
| 
 | 
 | ||||||
| abstract class ReconnectionPolicy { | /// A callback function to be called when the connection to the server has been lost. | ||||||
|  | typedef ConnectionLostCallback = Future<void> Function(); | ||||||
| 
 | 
 | ||||||
|   ReconnectionPolicy() | /// A function that, when called, causes the XmppConnection to connect to the server, if | ||||||
|     : _shouldAttemptReconnection = false, | /// another reconnection is not already running. | ||||||
|       _isReconnecting = false, | typedef PerformReconnectFunction = Future<void> Function(); | ||||||
|       _isReconnectingLock = Lock(); | 
 | ||||||
|  | abstract class ReconnectionPolicy { | ||||||
|   /// Function provided by XmppConnection that allows the policy |   /// Function provided by XmppConnection that allows the policy | ||||||
|   /// to perform a reconnection. |   /// to perform a reconnection. | ||||||
|   Future<void> Function()? performReconnect; |   PerformReconnectFunction? performReconnect; | ||||||
|  | 
 | ||||||
|   /// Function provided by XmppConnection that allows the policy |   /// Function provided by XmppConnection that allows the policy | ||||||
|   /// to say that we lost the connection. |   /// to say that we lost the connection. | ||||||
|   void Function()? triggerConnectionLost; |   ConnectionLostCallback? triggerConnectionLost; | ||||||
|  | 
 | ||||||
|   /// Indicate if should try to reconnect. |   /// Indicate if should try to reconnect. | ||||||
|   bool _shouldAttemptReconnection; |   bool _shouldAttemptReconnection = false; | ||||||
|  | 
 | ||||||
|   /// Indicate if a reconnection attempt is currently running. |   /// Indicate if a reconnection attempt is currently running. | ||||||
|   bool _isReconnecting; |   bool _isReconnecting = false; | ||||||
|  | 
 | ||||||
|   /// And the corresponding lock |   /// And the corresponding lock | ||||||
|   final Lock _isReconnectingLock; |   final Lock _isReconnectingLock = Lock(); | ||||||
|    |    | ||||||
|   /// Called by XmppConnection to register the policy. |   /// Called by XmppConnection to register the policy. | ||||||
|   void register(Future<void> Function() performReconnect, void Function() triggerConnectionLost) { |   void register(PerformReconnectFunction performReconnect, ConnectionLostCallback triggerConnectionLost) { | ||||||
|     this.performReconnect = performReconnect; |     this.performReconnect = performReconnect; | ||||||
|     this.triggerConnectionLost = triggerConnectionLost; |     this.triggerConnectionLost = triggerConnectionLost; | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -12,10 +12,17 @@ import 'package:moxxmpp/src/stringxml.dart'; | |||||||
| import 'package:moxxmpp/src/xeps/xep_0030/xep_0030.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_0297.dart'; | ||||||
| 
 | 
 | ||||||
|  | /// This manager class implements support for XEP-0280. | ||||||
| class CarbonsManager extends XmppManagerBase { | class CarbonsManager extends XmppManagerBase { | ||||||
|   CarbonsManager() : super(); |   CarbonsManager() : super(); | ||||||
|  | 
 | ||||||
|  |   /// Indicates that message carbons are enabled. | ||||||
|   bool _isEnabled = false; |   bool _isEnabled = false; | ||||||
|  | 
 | ||||||
|  |   /// Indicates that the server supports message carbons. | ||||||
|   bool _supported = false; |   bool _supported = false; | ||||||
|  | 
 | ||||||
|  |   /// Indicates that we know that [CarbonsManager._supported] is accurate. | ||||||
|   bool _gotSupported = false; |   bool _gotSupported = false; | ||||||
|    |    | ||||||
|   @override |   @override | ||||||
| @ -102,9 +109,14 @@ class CarbonsManager extends XmppManagerBase { | |||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   /// Send a request to the server, asking it to enable Message Carbons. | ||||||
|  |   /// | ||||||
|  |   /// Returns true if carbons were enabled. False, if not. | ||||||
|   Future<bool> enableCarbons() async { |   Future<bool> enableCarbons() async { | ||||||
|     final result = await getAttributes().sendStanza( |     final attrs = getAttributes(); | ||||||
|  |     final result = await attrs.sendStanza( | ||||||
|       Stanza.iq( |       Stanza.iq( | ||||||
|  |         to: attrs.getFullJID().toBare().toString(), | ||||||
|         type: 'set', |         type: 'set', | ||||||
|         children: [ |         children: [ | ||||||
|           XMLNode.xmlns( |           XMLNode.xmlns( | ||||||
| @ -129,6 +141,9 @@ class CarbonsManager extends XmppManagerBase { | |||||||
|     return true; |     return true; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   /// Send a request to the server, asking it to disable Message Carbons. | ||||||
|  |   /// | ||||||
|  |   /// Returns true if carbons were disabled. False, if not. | ||||||
|   Future<bool> disableCarbons() async { |   Future<bool> disableCarbons() async { | ||||||
|     final result = await getAttributes().sendStanza( |     final result = await getAttributes().sendStanza( | ||||||
|       Stanza.iq( |       Stanza.iq( | ||||||
| @ -164,7 +179,14 @@ class CarbonsManager extends XmppManagerBase { | |||||||
|     _isEnabled = true; |     _isEnabled = true; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   /// Checks if a carbon sent by [senderJid] is valid to prevent vulnerabilities like | ||||||
|  |   /// the ones listed at https://xmpp.org/extensions/xep-0280.html#security. | ||||||
|  |   /// | ||||||
|  |   /// Returns true if the carbon is valid. Returns false if not. | ||||||
|   bool isCarbonValid(JID senderJid) { |   bool isCarbonValid(JID senderJid) { | ||||||
|     return _isEnabled && senderJid == getAttributes().getConnectionSettings().jid.toBare(); |     return _isEnabled && getAttributes().getFullJID().bareCompare( | ||||||
|  |       senderJid, | ||||||
|  |       ensureBare: true, | ||||||
|  |     ); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,6 +1,5 @@ | |||||||
| import 'package:moxxmpp/moxxmpp.dart'; | import 'package:moxxmpp/moxxmpp.dart'; | ||||||
| import 'package:test/test.dart'; | import 'package:test/test.dart'; | ||||||
| 
 |  | ||||||
| void main() { | void main() { | ||||||
|   test('Parse a full JID', () { |   test('Parse a full JID', () { | ||||||
|     final jid = JID.fromString('test@server/abc'); |     final jid = JID.fromString('test@server/abc'); | ||||||
| @ -42,4 +41,15 @@ void main() { | |||||||
|   test('Parse resource with a slash', () { |   test('Parse resource with a slash', () { | ||||||
|     expect(JID.fromString('hallo@welt.example./test/welt') == JID('hallo', 'welt.example', 'test/welt'), true); |     expect(JID.fromString('hallo@welt.example./test/welt') == JID('hallo', 'welt.example', 'test/welt'), true); | ||||||
|   }); |   }); | ||||||
|  | 
 | ||||||
|  |   test('bareCompare', () { | ||||||
|  |     final jid1 = JID('hallo', 'welt', 'lol'); | ||||||
|  |     final jid2 = JID('hallo', 'welt', ''); | ||||||
|  |     final jid3 = JID('hallo', 'earth', 'true'); | ||||||
|  | 
 | ||||||
|  |     expect(jid1.bareCompare(jid2), true); | ||||||
|  |     expect(jid2.bareCompare(jid1), true); | ||||||
|  |     expect(jid2.bareCompare(jid1, ensureBare: true), false); | ||||||
|  |     expect(jid2.bareCompare(jid3), false); | ||||||
|  |   }); | ||||||
| } | } | ||||||
|  | |||||||
| @ -4,33 +4,33 @@ import '../helpers/xmpp.dart'; | |||||||
| 
 | 
 | ||||||
| void main() { | void main() { | ||||||
|   test("Test if we're vulnerable against CVE-2020-26547 style vulnerabilities", () async { |   test("Test if we're vulnerable against CVE-2020-26547 style vulnerabilities", () async { | ||||||
|       final attributes = XmppManagerAttributes( |     final attributes = XmppManagerAttributes( | ||||||
|         sendStanza: (stanza, { StanzaFromType addFrom = StanzaFromType.full, bool addId = true, bool retransmitted = false, bool awaitable = true, bool encrypted = false }) async { |       sendStanza: (stanza, { StanzaFromType addFrom = StanzaFromType.full, bool addId = true, bool retransmitted = false, bool awaitable = true, bool encrypted = false }) async { | ||||||
|           // ignore: avoid_print |         // ignore: avoid_print | ||||||
|           print('==> ${stanza.toXml()}'); |         print('==> ${stanza.toXml()}'); | ||||||
|           return XMLNode(tag: 'iq', attributes: { 'type': 'result' }); |         return XMLNode(tag: 'iq', attributes: { 'type': 'result' }); | ||||||
|         }, |       }, | ||||||
|         sendNonza: (nonza) {}, |       sendNonza: (nonza) {}, | ||||||
|         sendEvent: (event) {}, |       sendEvent: (event) {}, | ||||||
|         getManagerById: getManagerNullStub, |       getManagerById: getManagerNullStub, | ||||||
|         getConnectionSettings: () => ConnectionSettings( |       getConnectionSettings: () => ConnectionSettings( | ||||||
|           jid: JID.fromString('bob@xmpp.example'), |         jid: JID.fromString('bob@xmpp.example'), | ||||||
|           password: 'password', |         password: 'password', | ||||||
|           useDirectTLS: true, |         useDirectTLS: true, | ||||||
|           allowPlainAuth: false, |         allowPlainAuth: false, | ||||||
|         ), |       ), | ||||||
|         isFeatureSupported: (_) => false, |       isFeatureSupported: (_) => false, | ||||||
|         getFullJID: () => JID.fromString('bob@xmpp.example/uwu'), |       getFullJID: () => JID.fromString('bob@xmpp.example/uwu'), | ||||||
|         getSocket: () => StubTCPSocket(play: []), |       getSocket: () => StubTCPSocket(play: []), | ||||||
|         getConnection: () => XmppConnection(TestingReconnectionPolicy(), StubTCPSocket(play: [])), |       getConnection: () => XmppConnection(TestingReconnectionPolicy(), StubTCPSocket(play: [])), | ||||||
|         getNegotiatorById: getNegotiatorNullStub, |       getNegotiatorById: getNegotiatorNullStub, | ||||||
|       ); |     ); | ||||||
|       final manager = CarbonsManager(); |     final manager = CarbonsManager(); | ||||||
|       manager.register(attributes); |     manager.register(attributes); | ||||||
|       await manager.enableCarbons(); |     await manager.enableCarbons(); | ||||||
| 
 | 
 | ||||||
|       expect(manager.isCarbonValid(JID.fromString('mallory@evil.example')), false); |     expect(manager.isCarbonValid(JID.fromString('mallory@evil.example')), false); | ||||||
|       expect(manager.isCarbonValid(JID.fromString('bob@xmpp.example')), true); |     expect(manager.isCarbonValid(JID.fromString('bob@xmpp.example')), true); | ||||||
|       expect(manager.isCarbonValid(JID.fromString('bob@xmpp.example/abc')), false); |     expect(manager.isCarbonValid(JID.fromString('bob@xmpp.example/abc')), false); | ||||||
|   }); |   }); | ||||||
| } | } | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user