Compare commits

..

No commits in common. "4d312b21000731ab80ce655890546ac54bc2d878" and "c88ab940c481e07056a7d872f149746eea2accbb" have entirely different histories.

8 changed files with 53 additions and 93 deletions

View File

@ -1,7 +1,3 @@
## 0.3.2
- **BREAKING**: Remove `lastResource` from `XmppConnection`'s `connect` method. Instead, set the `StreamManagementNegotiator`'s `resource` attribute instead. Since the resource can only really be restored by stream management, this is no issue.
## 0.3.1 ## 0.3.1
- Fix some issues with running moxxmpp as a component - Fix some issues with running moxxmpp as a component

View File

@ -10,7 +10,6 @@ import 'package:moxxmpp/src/errors.dart';
import 'package:moxxmpp/src/events.dart'; import 'package:moxxmpp/src/events.dart';
import 'package:moxxmpp/src/handlers/base.dart'; import 'package:moxxmpp/src/handlers/base.dart';
import 'package:moxxmpp/src/iq.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/attributes.dart';
import 'package:moxxmpp/src/managers/base.dart'; import 'package:moxxmpp/src/managers/base.dart';
import 'package:moxxmpp/src/managers/data.dart'; import 'package:moxxmpp/src/managers/data.dart';
@ -141,8 +140,6 @@ class XmppConnection {
RoutingState _routingState = RoutingState.preConnection; 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.
/// NOTE: A Using the empty string is okay since RFC7622 says that
/// the resource MUST NOT be zero octets.
String _resource = ''; String _resource = '';
String get resource => _resource; String get resource => _resource;
@ -173,13 +170,6 @@ class XmppConnection {
bool get isAuthenticated => _isAuthenticated; bool get isAuthenticated => _isAuthenticated;
/// Returns the JID we authenticate with and add the resource that we have bound.
JID _getJidWithResource() {
assert(_resource.isNotEmpty, 'The resource must not be empty');
return connectionSettings.jid.withResource(_resource);
}
/// Registers a list of [XmppManagerBase] sub-classes as managers on this connection. /// Registers a list of [XmppManagerBase] sub-classes as managers on this connection.
Future<void> registerManagers(List<XmppManagerBase> managers) async { Future<void> registerManagers(List<XmppManagerBase> managers) async {
for (final manager in managers) { for (final manager in managers) {
@ -191,7 +181,7 @@ class XmppConnection {
sendEvent: _sendEvent, sendEvent: _sendEvent,
getConnectionSettings: () => connectionSettings, getConnectionSettings: () => connectionSettings,
getManagerById: getManagerById, getManagerById: getManagerById,
getFullJID: _getJidWithResource, getFullJID: () => connectionSettings.jid.withResource(_resource),
getSocket: () => _socket, getSocket: () => _socket,
getConnection: () => this, getConnection: () => this,
getNegotiatorById: _negotiationsHandler.getNegotiatorById, getNegotiatorById: _negotiationsHandler.getNegotiatorById,
@ -242,7 +232,7 @@ class XmppConnection {
_sendEvent, _sendEvent,
_negotiationsHandler.getNegotiatorById, _negotiationsHandler.getNegotiatorById,
getManagerById, getManagerById,
_getJidWithResource, () => connectionSettings.jid.withResource(_resource),
() => _socket, () => _socket,
() => _isAuthenticated, () => _isAuthenticated,
_setAuthenticated, _setAuthenticated,
@ -305,11 +295,8 @@ class XmppConnection {
// Connect again // Connect again
// ignore: cascade_invocations // ignore: cascade_invocations
_log.finest('Calling _connectImpl() from _attemptReconnection'); _log.finest('Calling _connectImpl() from _attemptReconnection');
unawaited( await _connectImpl(
_connectImpl(
waitForConnection: true, waitForConnection: true,
shouldReconnect: false,
),
); );
} }
@ -444,7 +431,7 @@ class XmppConnection {
case StanzaFromType.full: case StanzaFromType.full:
{ {
stanza_ = stanza_.copyWith( stanza_ = stanza_.copyWith(
from: _getJidWithResource().toString(), from: connectionSettings.jid.withResource(_resource).toString(),
); );
} }
break; break;
@ -853,6 +840,7 @@ class XmppConnection {
/// The private implementation for [XmppConnection.connect]. The parameters have /// The private implementation for [XmppConnection.connect]. The parameters have
/// the same meaning as with [XmppConnection.connect]. /// the same meaning as with [XmppConnection.connect].
Future<Result<bool, XmppError>> _connectImpl({ Future<Result<bool, XmppError>> _connectImpl({
String? lastResource,
bool waitForConnection = false, bool waitForConnection = false,
bool shouldReconnect = true, bool shouldReconnect = true,
bool waitUntilLogin = false, bool waitUntilLogin = false,
@ -875,9 +863,11 @@ class XmppConnection {
_connectionCompleter = Completer(); _connectionCompleter = Completer();
} }
// Reset the resource. If we use stream resumption from XEP-0198, then the if (lastResource != null) {
// manager will set it on successful resumption. setResource(lastResource, triggerEvent: false);
} else {
setResource('', triggerEvent: false); setResource('', triggerEvent: false);
}
// If requested, wait until we have a network connection // If requested, wait until we have a network connection
if (waitForConnection) { if (waitForConnection) {
@ -924,6 +914,9 @@ class XmppConnection {
/// Start the connection process using the provided connection settings. /// Start the connection process using the provided connection settings.
/// ///
/// If [lastResource] is set, then its value is used as the connection's resource.
/// Useful for stream resumption.
///
/// [shouldReconnect] indicates whether the reconnection attempts should be /// [shouldReconnect] indicates whether the reconnection attempts should be
/// automatically performed after a fatal failure of any kind occurs. /// automatically performed after a fatal failure of any kind occurs.
/// ///
@ -938,12 +931,14 @@ class XmppConnection {
/// [enableReconnectOnSuccess] indicates that automatic reconnection is to be /// [enableReconnectOnSuccess] indicates that automatic reconnection is to be
/// enabled once the connection has been successfully established. /// enabled once the connection has been successfully established.
Future<Result<bool, XmppError>> connect({ Future<Result<bool, XmppError>> connect({
String? lastResource,
bool? shouldReconnect, bool? shouldReconnect,
bool waitForConnection = false, bool waitForConnection = false,
bool waitUntilLogin = false, bool waitUntilLogin = false,
bool enableReconnectOnSuccess = true, bool enableReconnectOnSuccess = true,
}) async { }) async {
final result = _connectImpl( final result = _connectImpl(
lastResource: lastResource,
shouldReconnect: shouldReconnect ?? !waitUntilLogin, shouldReconnect: shouldReconnect ?? !waitUntilLogin,
waitForConnection: waitForConnection, waitForConnection: waitForConnection,
waitUntilLogin: waitUntilLogin, waitUntilLogin: waitUntilLogin,

View File

@ -111,47 +111,31 @@ class RandomBackoffReconnectionPolicy extends ReconnectionPolicy {
/// Called when the backoff expired /// Called when the backoff expired
@visibleForTesting @visibleForTesting
Future<void> onTimerElapsed() async { Future<void> onTimerElapsed() async {
_log.finest('Timer elapsed. Waiting for lock...'); _log.fine('Timer elapsed. Waiting for lock...');
final shouldContinue = await _timerLock.synchronized(() async { await _timerLock.synchronized(() async {
_log.finest('Timer lock aquired');
if (_timer == null) {
_log.finest(
'The timer is already set to null. Doing nothing.',
);
return false;
}
if (!(await getIsReconnecting())) { if (!(await getIsReconnecting())) {
return false; return;
} }
if (!(await getShouldReconnect())) { if (!(await getShouldReconnect())) {
_log.finest( _log.fine(
'Should not reconnect. Stopping here.', 'Should not reconnect. Stopping here.',
); );
return false;
}
_timer?.cancel();
_timer = null;
return true;
});
if (!shouldContinue) {
return; return;
} }
_log.fine('Triggering reconnect'); _log.fine('Triggering reconnect');
_timer?.cancel();
_timer = null;
await performReconnect!(); await performReconnect!();
});
} }
@override @override
Future<void> reset() async { Future<void> reset() async {
_log.finest('Resetting internal state'); _log.finest('Resetting internal state');
await _timerLock.synchronized(() {
_timer?.cancel(); _timer?.cancel();
_timer = null; _timer = null;
});
await super.reset(); await super.reset();
} }
@ -160,11 +144,9 @@ class RandomBackoffReconnectionPolicy extends ReconnectionPolicy {
final seconds = final seconds =
Random().nextInt(_maxBackoffTime - _minBackoffTime) + _minBackoffTime; Random().nextInt(_maxBackoffTime - _minBackoffTime) + _minBackoffTime;
_log.finest('Failure occured. Starting random backoff with ${seconds}s'); _log.finest('Failure occured. Starting random backoff with ${seconds}s');
await _timerLock.synchronized(() {
_timer?.cancel(); _timer?.cancel();
_timer = Timer(Duration(seconds: seconds), onTimerElapsed); _timer = Timer(Duration(seconds: seconds), onTimerElapsed);
});
} }
@override @override

View File

@ -1,5 +1,6 @@
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'package:meta/meta.dart';
import 'package:moxxmpp/src/events.dart'; import 'package:moxxmpp/src/events.dart';
import 'package:moxxmpp/src/managers/namespaces.dart'; import 'package:moxxmpp/src/managers/namespaces.dart';
import 'package:moxxmpp/src/namespaces.dart'; import 'package:moxxmpp/src/namespaces.dart';
@ -59,7 +60,11 @@ class StreamManagementNegotiator extends Sasl2FeatureNegotiator
bool _inlineStreamEnablementRequested = false; bool _inlineStreamEnablementRequested = false;
/// Cached resource for stream resumption /// Cached resource for stream resumption
String resource = ''; String _resource = '';
@visibleForTesting
void setResource(String resource) {
_resource = resource;
}
@override @override
bool canInlineFeature(List<XMLNode> features) { bool canInlineFeature(List<XMLNode> features) {
@ -85,7 +90,7 @@ class StreamManagementNegotiator extends Sasl2FeatureNegotiator
@override @override
Future<void> onXmppEvent(XmppEvent event) async { Future<void> onXmppEvent(XmppEvent event) async {
if (event is ResourceBoundEvent) { if (event is ResourceBoundEvent) {
resource = event.resource; _resource = event.resource;
} }
} }
@ -95,9 +100,7 @@ class StreamManagementNegotiator extends Sasl2FeatureNegotiator
if (sm.state.streamResumptionId != null && !_resumeFailed) { if (sm.state.streamResumptionId != null && !_resumeFailed) {
// We could do Stream resumption // We could do Stream resumption
return super.matchesFeature(features) && return super.matchesFeature(features) && attributes.isAuthenticated();
attributes.isAuthenticated() &&
resource.isNotEmpty;
} else { } else {
// We cannot do a stream resumption // We cannot do a stream resumption
return super.matchesFeature(features) && return super.matchesFeature(features) &&
@ -119,23 +122,23 @@ class StreamManagementNegotiator extends Sasl2FeatureNegotiator
_resumeFailed = true; _resumeFailed = true;
_isResumed = false; _isResumed = false;
_state = _StreamManagementNegotiatorState.ready; _state = _StreamManagementNegotiatorState.ready;
resource = '';
attributes.setResource('', triggerEvent: false);
} }
Future<void> _onStreamResumptionSuccessful(XMLNode resumed) async { Future<void> _onStreamResumptionSuccessful(XMLNode resumed) async {
assert(resumed.tag == 'resumed', 'The correct element must be passed'); assert(resumed.tag == 'resumed', 'The correct element must be passed');
assert(
resource.isNotEmpty,
'The Stream Management Negotiator must know of the previous resource',
);
final h = int.parse(resumed.attributes['h']! as String); final h = int.parse(resumed.attributes['h']! as String);
await attributes.sendEvent(StreamResumedEvent(h: h)); await attributes.sendEvent(StreamResumedEvent(h: h));
_resumeFailed = false; _resumeFailed = false;
_isResumed = true; _isResumed = true;
attributes.setResource(resource);
if (attributes.getConnection().resource.isEmpty && _resource.isNotEmpty) {
attributes.setResource(_resource);
} else if (attributes.getConnection().resource.isNotEmpty &&
_resource.isEmpty) {
_resource = attributes.getConnection().resource;
}
} }
Future<void> _onStreamEnablementSuccessful(XMLNode enabled) async { Future<void> _onStreamEnablementSuccessful(XMLNode enabled) async {
@ -149,7 +152,7 @@ class StreamManagementNegotiator extends Sasl2FeatureNegotiator
await attributes.sendEvent( await attributes.sendEvent(
StreamManagementEnabledEvent( StreamManagementEnabledEvent(
resource: resource, resource: attributes.getFullJID().resource,
id: id, id: id,
location: enabled.attributes['location'] as String?, location: enabled.attributes['location'] as String?,
), ),
@ -194,12 +197,10 @@ class StreamManagementNegotiator extends Sasl2FeatureNegotiator
_log.finest('Stream Management resumption successful'); _log.finest('Stream Management resumption successful');
assert( assert(
resource.isNotEmpty, attributes.getFullJID().resource != '',
'Resume only works when we already have a resource bound and know about it', 'Resume only works when we already have a resource bound and know about it',
); );
// TODO(Unknown): Don't do this here. We trigger an event that the CSIManager
// can consume.
final csi = attributes.getManagerById(csiManager) as CSIManager?; final csi = attributes.getManagerById(csiManager) as CSIManager?;
if (csi != null) { if (csi != null) {
csi.restoreCSIState(); csi.restoreCSIState();

View File

@ -42,6 +42,7 @@ const _doNotEncryptList = [
DoNotEncrypt('stanza-id', stableIdXmlns), DoNotEncrypt('stanza-id', stableIdXmlns),
]; ];
@mustCallSuper
abstract class BaseOmemoManager extends XmppManagerBase { abstract class BaseOmemoManager extends XmppManagerBase {
BaseOmemoManager() : super(omemoManager); BaseOmemoManager() : super(omemoManager);

View File

@ -1,6 +1,6 @@
name: moxxmpp name: moxxmpp
description: A pure-Dart XMPP library description: A pure-Dart XMPP library
version: 0.3.2 version: 0.3.1
homepage: https://codeberg.org/moxxy/moxxmpp homepage: https://codeberg.org/moxxy/moxxmpp
publish_to: https://git.polynom.me/api/packages/Moxxy/pub publish_to: https://git.polynom.me/api/packages/Moxxy/pub

View File

@ -1,4 +1,3 @@
import 'dart:async';
import 'package:moxxmpp/moxxmpp.dart'; import 'package:moxxmpp/moxxmpp.dart';
import 'package:test/test.dart'; import 'package:test/test.dart';
import 'helpers/logging.dart'; import 'helpers/logging.dart';
@ -64,8 +63,6 @@ void main() {
counter++; counter++;
}); });
await policy.setShouldReconnect(true); await policy.setShouldReconnect(true);
// ignore: avoid_print
print('policy.setShouldReconnect(true) done');
// We have a failure // We have a failure
expect( expect(
@ -73,13 +70,9 @@ void main() {
true, true,
); );
await policy.onFailure(); await policy.onFailure();
// ignore: avoid_print
print('policy.onFailure() done');
unawaited(policy.onTimerElapsed()); await policy.onTimerElapsed();
unawaited(policy.onTimerElapsed()); await policy.onTimerElapsed();
await Future<void>.delayed(const Duration(seconds: 3));
expect(counter, 1); expect(counter, 1);
}); });
} }

View File

@ -514,7 +514,7 @@ void main() {
await conn.registerFeatureNegotiators([ await conn.registerFeatureNegotiators([
SaslPlainNegotiator(), SaslPlainNegotiator(),
ResourceBindingNegotiator(), ResourceBindingNegotiator(),
StreamManagementNegotiator()..resource = 'test-resource', StreamManagementNegotiator(),
]); ]);
await conn.getManagerById<StreamManagementManager>(smManager)!.setState( await conn.getManagerById<StreamManagementManager>(smManager)!.setState(
StreamManagementState( StreamManagementState(
@ -536,15 +536,6 @@ void main() {
.isStreamManagementEnabled(), .isStreamManagementEnabled(),
true, true,
); );
expect(conn.resource, 'MU29eEZn');
expect(
conn
.getNegotiatorById<StreamManagementNegotiator>(
streamManagementNegotiator,
)!
.resource,
'MU29eEZn',
);
}); });
test('Test a successful stream resumption', () async { test('Test a successful stream resumption', () async {
@ -613,7 +604,7 @@ void main() {
await conn.registerFeatureNegotiators([ await conn.registerFeatureNegotiators([
SaslPlainNegotiator(), SaslPlainNegotiator(),
ResourceBindingNegotiator(), ResourceBindingNegotiator(),
StreamManagementNegotiator()..resource = 'abc123', StreamManagementNegotiator(),
]); ]);
await conn.getManagerById<StreamManagementManager>(smManager)!.setState( await conn.getManagerById<StreamManagementManager>(smManager)!.setState(
StreamManagementState( StreamManagementState(
@ -624,6 +615,7 @@ void main() {
); );
await conn.connect( await conn.connect(
lastResource: 'abc123',
waitUntilLogin: true, waitUntilLogin: true,
); );
expect(fakeSocket.getState(), 4); expect(fakeSocket.getState(), 4);
@ -698,7 +690,7 @@ void main() {
await conn.registerFeatureNegotiators([ await conn.registerFeatureNegotiators([
SaslPlainNegotiator(), SaslPlainNegotiator(),
ResourceBindingNegotiator(), ResourceBindingNegotiator(),
StreamManagementNegotiator()..resource = 'test-resource', StreamManagementNegotiator()..setResource('test-resource'),
Sasl2Negotiator( Sasl2Negotiator(
userAgent: const UserAgent( userAgent: const UserAgent(
id: 'd4565fa7-4d72-4749-b3d3-740edbf87770', id: 'd4565fa7-4d72-4749-b3d3-740edbf87770',
@ -795,7 +787,7 @@ void main() {
await conn.registerFeatureNegotiators([ await conn.registerFeatureNegotiators([
SaslPlainNegotiator(), SaslPlainNegotiator(),
ResourceBindingNegotiator(), ResourceBindingNegotiator(),
StreamManagementNegotiator()..resource = 'test-resource', StreamManagementNegotiator()..setResource('test-resource'),
Bind2Negotiator(), Bind2Negotiator(),
Sasl2Negotiator( Sasl2Negotiator(
userAgent: const UserAgent( userAgent: const UserAgent(