feat(xep): Implement SASL2 inline stream resumption
This commit is contained in:
parent
4e01d32e90
commit
51edb61443
@ -279,7 +279,7 @@ class XmppConnection {
|
|||||||
() => _socket,
|
() => _socket,
|
||||||
() => _isAuthenticated,
|
() => _isAuthenticated,
|
||||||
_setAuthenticated,
|
_setAuthenticated,
|
||||||
_setResource,
|
setResource,
|
||||||
_removeNegotiatingFeature,
|
_removeNegotiatingFeature,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -675,7 +675,8 @@ class XmppConnection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the resource of the connection
|
/// Sets the resource of the connection
|
||||||
void _setResource(String resource, {bool triggerEvent = true}) {
|
@visibleForTesting
|
||||||
|
void setResource(String resource, {bool triggerEvent = true}) {
|
||||||
_log.finest('Updating _resource to $resource');
|
_log.finest('Updating _resource to $resource');
|
||||||
_resource = resource;
|
_resource = resource;
|
||||||
|
|
||||||
@ -1134,9 +1135,7 @@ class XmppConnection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (lastResource != null) {
|
if (lastResource != null) {
|
||||||
_setResource(lastResource, triggerEvent: false);
|
setResource(lastResource, triggerEvent: false);
|
||||||
} else {
|
|
||||||
_setResource('', triggerEvent: false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_enableReconnectOnSuccess = enableReconnectOnSuccess;
|
_enableReconnectOnSuccess = enableReconnectOnSuccess;
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import 'package:collection/collection.dart';
|
|
||||||
import 'package:moxxmpp/src/jid.dart';
|
import 'package:moxxmpp/src/jid.dart';
|
||||||
import 'package:moxxmpp/src/namespaces.dart';
|
import 'package:moxxmpp/src/namespaces.dart';
|
||||||
import 'package:moxxmpp/src/negotiators/namespaces.dart';
|
import 'package:moxxmpp/src/negotiators/namespaces.dart';
|
||||||
@ -28,6 +27,11 @@ abstract class Sasl2FeatureNegotiator extends XmppFeatureNegotiatorBase {
|
|||||||
/// This method is only called when the previous <inline /> element contains an
|
/// This method is only called when the previous <inline /> element contains an
|
||||||
/// item with xmlns equal to [negotiatingXmlns].
|
/// item with xmlns equal to [negotiatingXmlns].
|
||||||
Future<Result<bool, NegotiatorError>> onSasl2Success(XMLNode response);
|
Future<Result<bool, NegotiatorError>> onSasl2Success(XMLNode response);
|
||||||
|
|
||||||
|
/// Called by the SASL2 negotiator to find out whether the negotiator is willing
|
||||||
|
/// to inline a feature. [features] is the list of elements inside the <inline />
|
||||||
|
/// element.
|
||||||
|
bool canInlineFeature(List<XMLNode> features);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A special type of [SaslNegotiator] that is aware of SASL2.
|
/// A special type of [SaslNegotiator] that is aware of SASL2.
|
||||||
@ -38,6 +42,11 @@ abstract class Sasl2AuthenticationNegotiator extends SaslNegotiator
|
|||||||
/// Perform a SASL step with [input] as the already parsed input data. Returns
|
/// Perform a SASL step with [input] as the already parsed input data. Returns
|
||||||
/// the base64-encoded response data.
|
/// the base64-encoded response data.
|
||||||
Future<String> getRawStep(String input);
|
Future<String> getRawStep(String input);
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool canInlineFeature(List<XMLNode> features) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class NoSASLMechanismSelectedError extends NegotiatorError {
|
class NoSASLMechanismSelectedError extends NegotiatorError {
|
||||||
@ -125,6 +134,8 @@ class Sasl2Negotiator extends XmppFeatureNegotiatorBase {
|
|||||||
|
|
||||||
/// The SASL2 <authentication /> element we received with the stream features.
|
/// The SASL2 <authentication /> element we received with the stream features.
|
||||||
XMLNode? _sasl2Data;
|
XMLNode? _sasl2Data;
|
||||||
|
final List<String> _activeSasl2Negotiators =
|
||||||
|
List<String>.empty(growable: true);
|
||||||
|
|
||||||
/// Register a SASL negotiator so that we can use that SASL implementation during
|
/// Register a SASL negotiator so that we can use that SASL implementation during
|
||||||
/// SASL2.
|
/// SASL2.
|
||||||
@ -141,18 +152,6 @@ class Sasl2Negotiator extends XmppFeatureNegotiatorBase {
|
|||||||
_featureNegotiators.add(negotiator);
|
_featureNegotiators.add(negotiator);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns true, if an item with xmlns of [xmlns] is contained inside [_sasl2Data]'s
|
|
||||||
/// <inline /> block. If not, returns false.
|
|
||||||
bool _isInliningPossible(String xmlns) {
|
|
||||||
final inline = _sasl2Data!.firstTag('inline');
|
|
||||||
if (inline == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return inline.children.firstWhereOrNull((child) => child.xmlns == xmlns) !=
|
|
||||||
null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool matchesFeature(List<XMLNode> features) {
|
bool matchesFeature(List<XMLNode> features) {
|
||||||
// Only do SASL2 when the socket is secure
|
// Only do SASL2 when the socket is secure
|
||||||
@ -185,12 +184,16 @@ class Sasl2Negotiator extends XmppFeatureNegotiatorBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Collect additional data by interested negotiators
|
// Collect additional data by interested negotiators
|
||||||
|
final inline = _sasl2Data!.firstTag('inline');
|
||||||
final children = List<XMLNode>.empty(growable: true);
|
final children = List<XMLNode>.empty(growable: true);
|
||||||
for (final negotiator in _featureNegotiators) {
|
if (inline != null && inline.children.isNotEmpty) {
|
||||||
if (_isInliningPossible(negotiator.negotiatingXmlns)) {
|
for (final negotiator in _featureNegotiators) {
|
||||||
children.addAll(
|
if (negotiator.canInlineFeature(inline.children)) {
|
||||||
await negotiator.onSasl2FeaturesReceived(_sasl2Data!),
|
_activeSasl2Negotiators.add(negotiator.id);
|
||||||
);
|
children.addAll(
|
||||||
|
await negotiator.onSasl2FeaturesReceived(_sasl2Data!),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -217,19 +220,18 @@ class Sasl2Negotiator extends XmppFeatureNegotiatorBase {
|
|||||||
case Sasl2State.authenticateSent:
|
case Sasl2State.authenticateSent:
|
||||||
if (nonza.tag == 'success') {
|
if (nonza.tag == 'success') {
|
||||||
// Tell the dependent negotiators about the result
|
// Tell the dependent negotiators about the result
|
||||||
// TODO(Unknown): This can be written in a better way
|
final negotiators = _featureNegotiators
|
||||||
for (final negotiator in _featureNegotiators) {
|
.where(
|
||||||
if (_isInliningPossible(negotiator.negotiatingXmlns)) {
|
(negotiator) => _activeSasl2Negotiators.contains(negotiator.id),
|
||||||
final result = await negotiator.onSasl2Success(nonza);
|
)
|
||||||
if (!result.isType<bool>()) {
|
.toList()
|
||||||
return Result(result.get<NegotiatorError>());
|
..add(_currentSaslNegotiator!);
|
||||||
}
|
for (final negotiator in negotiators) {
|
||||||
|
final result = await negotiator.onSasl2Success(nonza);
|
||||||
|
if (!result.isType<bool>()) {
|
||||||
|
return Result(result.get<NegotiatorError>());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
final result = await _currentSaslNegotiator!.onSasl2Success(nonza);
|
|
||||||
if (!result.isType<bool>()) {
|
|
||||||
return Result(result.get<NegotiatorError>());
|
|
||||||
}
|
|
||||||
|
|
||||||
// We're done
|
// We're done
|
||||||
attributes.setAuthenticated();
|
attributes.setAuthenticated();
|
||||||
@ -264,6 +266,7 @@ class Sasl2Negotiator extends XmppFeatureNegotiatorBase {
|
|||||||
_currentSaslNegotiator = null;
|
_currentSaslNegotiator = null;
|
||||||
_sasl2State = Sasl2State.idle;
|
_sasl2State = Sasl2State.idle;
|
||||||
_sasl2Data = null;
|
_sasl2Data = null;
|
||||||
|
_activeSasl2Negotiators.clear();
|
||||||
|
|
||||||
super.reset();
|
super.reset();
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
|
import 'package:collection/collection.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.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';
|
||||||
import 'package:moxxmpp/src/negotiators/namespaces.dart';
|
import 'package:moxxmpp/src/negotiators/namespaces.dart';
|
||||||
import 'package:moxxmpp/src/negotiators/negotiator.dart';
|
import 'package:moxxmpp/src/negotiators/negotiator.dart';
|
||||||
|
import 'package:moxxmpp/src/negotiators/sasl2.dart';
|
||||||
import 'package:moxxmpp/src/stringxml.dart';
|
import 'package:moxxmpp/src/stringxml.dart';
|
||||||
import 'package:moxxmpp/src/types/result.dart';
|
import 'package:moxxmpp/src/types/result.dart';
|
||||||
import 'package:moxxmpp/src/xeps/xep_0198/nonzas.dart';
|
import 'package:moxxmpp/src/xeps/xep_0198/nonzas.dart';
|
||||||
@ -23,27 +25,51 @@ enum _StreamManagementNegotiatorState {
|
|||||||
/// NOTE: The stream management negotiator requires that loadState has been called on the
|
/// NOTE: The stream management negotiator requires that loadState has been called on the
|
||||||
/// StreamManagementManager at least once before connecting, if stream resumption
|
/// StreamManagementManager at least once before connecting, if stream resumption
|
||||||
/// is wanted.
|
/// is wanted.
|
||||||
class StreamManagementNegotiator extends XmppFeatureNegotiatorBase {
|
class StreamManagementNegotiator extends Sasl2FeatureNegotiator {
|
||||||
StreamManagementNegotiator()
|
StreamManagementNegotiator()
|
||||||
: _state = _StreamManagementNegotiatorState.ready,
|
: super(10, false, smXmlns, streamManagementNegotiator);
|
||||||
_supported = false,
|
|
||||||
_resumeFailed = false,
|
|
||||||
_isResumed = false,
|
|
||||||
_log = Logger('StreamManagementNegotiator'),
|
|
||||||
super(10, false, smXmlns, streamManagementNegotiator);
|
|
||||||
_StreamManagementNegotiatorState _state;
|
|
||||||
bool _resumeFailed;
|
|
||||||
bool _isResumed;
|
|
||||||
|
|
||||||
final Logger _log;
|
/// Stream Management negotiation state.
|
||||||
|
_StreamManagementNegotiatorState _state =
|
||||||
|
_StreamManagementNegotiatorState.ready;
|
||||||
|
|
||||||
|
/// Flag indicating whether the resume failed (true) or succeeded (false).
|
||||||
|
bool _resumeFailed = false;
|
||||||
|
|
||||||
|
/// Flag indicating whether the current stream is resumed (true) or not (false).
|
||||||
|
bool _isResumed = false;
|
||||||
|
|
||||||
|
/// Logger
|
||||||
|
final Logger _log = Logger('StreamManagementNegotiator');
|
||||||
|
|
||||||
/// True if Stream Management is supported on this stream.
|
/// True if Stream Management is supported on this stream.
|
||||||
bool _supported;
|
bool _supported = false;
|
||||||
bool get isSupported => _supported;
|
bool get isSupported => _supported;
|
||||||
|
|
||||||
/// True if the current stream is resumed. False if not.
|
/// True if the current stream is resumed. False if not.
|
||||||
bool get isResumed => _isResumed;
|
bool get isResumed => _isResumed;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool canInlineFeature(List<XMLNode> features) {
|
||||||
|
final sm = attributes.getManagerById<StreamManagementManager>(smManager)!;
|
||||||
|
|
||||||
|
// We do not check here for authentication as enabling/resuming happens inline
|
||||||
|
// with the authentication.
|
||||||
|
if (sm.state.streamResumptionId != null && !_resumeFailed) {
|
||||||
|
// We can try to resume the stream or enable the stream
|
||||||
|
return features.firstWhereOrNull(
|
||||||
|
(child) => child.xmlns == smXmlns,
|
||||||
|
) !=
|
||||||
|
null;
|
||||||
|
} else {
|
||||||
|
// We can try to enable SM
|
||||||
|
return features.firstWhereOrNull(
|
||||||
|
(child) => child.tag == 'enable' && child.xmlns == smXmlns,
|
||||||
|
) !=
|
||||||
|
null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool matchesFeature(List<XMLNode> features) {
|
bool matchesFeature(List<XMLNode> features) {
|
||||||
final sm = attributes.getManagerById<StreamManagementManager>(smManager)!;
|
final sm = attributes.getManagerById<StreamManagementManager>(smManager)!;
|
||||||
@ -53,13 +79,37 @@ class StreamManagementNegotiator extends XmppFeatureNegotiatorBase {
|
|||||||
return super.matchesFeature(features) && attributes.isAuthenticated();
|
return super.matchesFeature(features) && attributes.isAuthenticated();
|
||||||
} else {
|
} else {
|
||||||
// We cannot do a stream resumption
|
// We cannot do a stream resumption
|
||||||
final br = attributes.getNegotiatorById(resourceBindingNegotiator);
|
|
||||||
return super.matchesFeature(features) &&
|
return super.matchesFeature(features) &&
|
||||||
br?.state == NegotiatorState.done &&
|
attributes.getConnection().resource.isNotEmpty &&
|
||||||
attributes.isAuthenticated();
|
attributes.isAuthenticated();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _onStreamResumptionFailed() async {
|
||||||
|
await attributes.sendEvent(StreamResumeFailedEvent());
|
||||||
|
final sm = attributes.getManagerById<StreamManagementManager>(smManager)!;
|
||||||
|
|
||||||
|
// We have to do this because we otherwise get a stanza stuck in the queue,
|
||||||
|
// thus spamming the server on every <a /> nonza we receive.
|
||||||
|
// ignore: cascade_invocations
|
||||||
|
await sm.setState(StreamManagementState(0, 0));
|
||||||
|
await sm.commitState();
|
||||||
|
|
||||||
|
_resumeFailed = true;
|
||||||
|
_isResumed = false;
|
||||||
|
_state = _StreamManagementNegotiatorState.ready;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _onStreamResumptionSuccessful(XMLNode resumed) async {
|
||||||
|
assert(resumed.tag == 'resumed', 'The correct element must be passed');
|
||||||
|
|
||||||
|
final h = int.parse(resumed.attributes['h']! as String);
|
||||||
|
await attributes.sendEvent(StreamResumedEvent(h: h));
|
||||||
|
|
||||||
|
_resumeFailed = false;
|
||||||
|
_isResumed = true;
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Result<NegotiatorState, NegotiatorError>> negotiate(
|
Future<Result<NegotiatorState, NegotiatorError>> negotiate(
|
||||||
XMLNode nonza,
|
XMLNode nonza,
|
||||||
@ -103,30 +153,14 @@ class StreamManagementNegotiator extends XmppFeatureNegotiatorBase {
|
|||||||
csi.restoreCSIState();
|
csi.restoreCSIState();
|
||||||
}
|
}
|
||||||
|
|
||||||
final h = int.parse(nonza.attributes['h']! as String);
|
await _onStreamResumptionSuccessful(nonza);
|
||||||
await attributes.sendEvent(StreamResumedEvent(h: h));
|
|
||||||
|
|
||||||
_resumeFailed = false;
|
|
||||||
_isResumed = true;
|
|
||||||
return const Result(NegotiatorState.skipRest);
|
return const Result(NegotiatorState.skipRest);
|
||||||
} else {
|
} else {
|
||||||
// We assume it is <failed />
|
// We assume it is <failed />
|
||||||
_log.info(
|
_log.info(
|
||||||
'Stream resumption failed. Expected <resumed />, got ${nonza.tag}, Proceeding with new stream...',
|
'Stream resumption failed. Expected <resumed />, got ${nonza.tag}, Proceeding with new stream...',
|
||||||
);
|
);
|
||||||
await attributes.sendEvent(StreamResumeFailedEvent());
|
await _onStreamResumptionFailed();
|
||||||
final sm =
|
|
||||||
attributes.getManagerById<StreamManagementManager>(smManager)!;
|
|
||||||
|
|
||||||
// We have to do this because we otherwise get a stanza stuck in the queue,
|
|
||||||
// thus spamming the server on every <a /> nonza we receive.
|
|
||||||
// ignore: cascade_invocations
|
|
||||||
await sm.setState(StreamManagementState(0, 0));
|
|
||||||
await sm.commitState();
|
|
||||||
|
|
||||||
_resumeFailed = true;
|
|
||||||
_isResumed = false;
|
|
||||||
_state = _StreamManagementNegotiatorState.ready;
|
|
||||||
return const Result(NegotiatorState.retryLater);
|
return const Result(NegotiatorState.retryLater);
|
||||||
}
|
}
|
||||||
case _StreamManagementNegotiatorState.enableRequested:
|
case _StreamManagementNegotiatorState.enableRequested:
|
||||||
@ -165,4 +199,60 @@ class StreamManagementNegotiator extends XmppFeatureNegotiatorBase {
|
|||||||
|
|
||||||
super.reset();
|
super.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<List<XMLNode>> onSasl2FeaturesReceived(XMLNode sasl2Features) async {
|
||||||
|
final inline = sasl2Features.firstTag('inline')!;
|
||||||
|
final resume = inline.firstTag('resume', xmlns: smXmlns);
|
||||||
|
|
||||||
|
if (resume == null) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
final sm = attributes.getManagerById<StreamManagementManager>(smManager)!;
|
||||||
|
final srid = sm.state.streamResumptionId;
|
||||||
|
final h = sm.state.s2c;
|
||||||
|
if (srid == null) {
|
||||||
|
_log.finest('No srid');
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
XMLNode.xmlns(
|
||||||
|
tag: 'resume',
|
||||||
|
xmlns: smXmlns,
|
||||||
|
attributes: {
|
||||||
|
'h': h.toString(),
|
||||||
|
'previd': srid,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Result<bool, NegotiatorError>> onSasl2Success(XMLNode response) async {
|
||||||
|
final resumed = response.firstTag('resumed', xmlns: smXmlns);
|
||||||
|
if (resumed == null) {
|
||||||
|
_log.warning('Inline stream resumption failed');
|
||||||
|
await _onStreamResumptionFailed();
|
||||||
|
state = NegotiatorState.retryLater;
|
||||||
|
return const Result(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
_log.finest('Inline stream resumption successful');
|
||||||
|
await _onStreamResumptionSuccessful(resumed);
|
||||||
|
state = NegotiatorState.skipRest;
|
||||||
|
|
||||||
|
attributes.removeNegotiatingFeature(smXmlns);
|
||||||
|
attributes.removeNegotiatingFeature(bindXmlns);
|
||||||
|
|
||||||
|
return const Result(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> postRegisterCallback() async {
|
||||||
|
attributes
|
||||||
|
.getNegotiatorById<Sasl2Negotiator>(sasl2Negotiator)
|
||||||
|
?.registerNegotiator(this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import 'package:collection/collection.dart';
|
||||||
import 'package:moxxmpp/src/namespaces.dart';
|
import 'package:moxxmpp/src/namespaces.dart';
|
||||||
import 'package:moxxmpp/src/negotiators/namespaces.dart';
|
import 'package:moxxmpp/src/negotiators/namespaces.dart';
|
||||||
import 'package:moxxmpp/src/negotiators/negotiator.dart';
|
import 'package:moxxmpp/src/negotiators/negotiator.dart';
|
||||||
@ -37,6 +38,14 @@ class Bind2Negotiator extends Sasl2FeatureNegotiator {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool canInlineFeature(List<XMLNode> features) {
|
||||||
|
return features.firstWhereOrNull(
|
||||||
|
(child) => child.tag == 'bind' && child.xmlns == bind2Xmlns,
|
||||||
|
) !=
|
||||||
|
null;
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Result<bool, NegotiatorError>> onSasl2Success(XMLNode response) async {
|
Future<Result<bool, NegotiatorError>> onSasl2Success(XMLNode response) async {
|
||||||
attributes.removeNegotiatingFeature(bindXmlns);
|
attributes.removeNegotiatingFeature(bindXmlns);
|
||||||
|
@ -788,4 +788,98 @@ void main() {
|
|||||||
expect(sm.streamResumed, true);
|
expect(sm.streamResumed, true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('Test SASL2 inline stream resumption', () async {
|
||||||
|
final fakeSocket = StubTCPSocket([
|
||||||
|
StringExpectation(
|
||||||
|
"<stream:stream xmlns='jabber:client' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' to='test.server' xml:lang='en'>",
|
||||||
|
'''
|
||||||
|
<stream:stream
|
||||||
|
xmlns="jabber:client"
|
||||||
|
version="1.0"
|
||||||
|
xmlns:stream="http://etherx.jabber.org/streams"
|
||||||
|
from="test.server"
|
||||||
|
xml:lang="en">
|
||||||
|
<stream:features xmlns="http://etherx.jabber.org/streams">
|
||||||
|
<mechanisms xmlns="urn:ietf:params:xml:ns:xmpp-sasl">
|
||||||
|
<mechanism>PLAIN</mechanism>
|
||||||
|
</mechanisms>
|
||||||
|
<authentication xmlns='urn:xmpp:sasl:2'>
|
||||||
|
<mechanism>PLAIN</mechanism>
|
||||||
|
<inline>
|
||||||
|
<resume xmlns="urn:xmpp:sm:3" />
|
||||||
|
</inline>
|
||||||
|
</authentication>
|
||||||
|
<bind xmlns="urn:ietf:params:xml:ns:xmpp-bind">
|
||||||
|
<required/>
|
||||||
|
</bind>
|
||||||
|
</stream:features>''',
|
||||||
|
),
|
||||||
|
StanzaExpectation(
|
||||||
|
"<authenticate xmlns='urn:xmpp:sasl:2' mechanism='PLAIN'><user-agent id='d4565fa7-4d72-4749-b3d3-740edbf87770'><software>moxxmpp</software><device>PapaTutuWawa's awesome device</device></user-agent><initial-response>AHBvbHlub21kaXZpc2lvbgBhYWFh</initial-response><resume xmlns='urn:xmpp:sm:3' previd='test-prev-id' h='2' /></authenticate>",
|
||||||
|
'''
|
||||||
|
<success xmlns='urn:xmpp:sasl:2'>
|
||||||
|
<authorization-identifier>polynomdivision@test.server</authorization-identifier>
|
||||||
|
<resumed xmlns='urn:xmpp:sm:3' h='25' previd='test-prev-id' />
|
||||||
|
</success>
|
||||||
|
''',
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
final sm = StreamManagementManager();
|
||||||
|
await sm.setState(
|
||||||
|
sm.state.copyWith(
|
||||||
|
c2s: 25,
|
||||||
|
s2c: 2,
|
||||||
|
streamResumptionId: 'test-prev-id',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
final conn = XmppConnection(
|
||||||
|
TestingReconnectionPolicy(),
|
||||||
|
AlwaysConnectedConnectivityManager(),
|
||||||
|
fakeSocket,
|
||||||
|
)
|
||||||
|
..setConnectionSettings(
|
||||||
|
ConnectionSettings(
|
||||||
|
jid: JID.fromString('polynomdivision@test.server'),
|
||||||
|
password: 'aaaa',
|
||||||
|
useDirectTLS: true,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
..setResource('test-resource', triggerEvent: false);
|
||||||
|
await conn.registerManagers([
|
||||||
|
RosterManager(TestingRosterStateManager('', [])),
|
||||||
|
DiscoManager([]),
|
||||||
|
sm,
|
||||||
|
]);
|
||||||
|
await conn.registerFeatureNegotiators([
|
||||||
|
SaslPlainNegotiator(),
|
||||||
|
ResourceBindingNegotiator(),
|
||||||
|
StreamManagementNegotiator(),
|
||||||
|
Sasl2Negotiator(
|
||||||
|
userAgent: const UserAgent(
|
||||||
|
id: 'd4565fa7-4d72-4749-b3d3-740edbf87770',
|
||||||
|
software: 'moxxmpp',
|
||||||
|
device: "PapaTutuWawa's awesome device",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
|
||||||
|
final result = await conn.connect(
|
||||||
|
waitUntilLogin: true,
|
||||||
|
shouldReconnect: false,
|
||||||
|
enableReconnectOnSuccess: false,
|
||||||
|
);
|
||||||
|
expect(result.isType<NegotiatorError>(), false);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
sm.state.c2s,
|
||||||
|
25,
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
sm.state.s2c,
|
||||||
|
2,
|
||||||
|
);
|
||||||
|
expect(conn.resource, 'test-resource');
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
@ -132,8 +132,7 @@ void main() {
|
|||||||
await conn.registerFeatureNegotiators([
|
await conn.registerFeatureNegotiators([
|
||||||
SaslPlainNegotiator(),
|
SaslPlainNegotiator(),
|
||||||
ResourceBindingNegotiator(),
|
ResourceBindingNegotiator(),
|
||||||
Bind2Negotiator()
|
Bind2Negotiator()..tag = 'moxxmpp',
|
||||||
..tag = 'moxxmpp',
|
|
||||||
Sasl2Negotiator(
|
Sasl2Negotiator(
|
||||||
userAgent: const UserAgent(
|
userAgent: const UserAgent(
|
||||||
id: 'd4565fa7-4d72-4749-b3d3-740edbf87770',
|
id: 'd4565fa7-4d72-4749-b3d3-740edbf87770',
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import 'package:collection/collection.dart';
|
||||||
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';
|
||||||
@ -16,6 +17,14 @@ class ExampleNegotiator extends Sasl2FeatureNegotiator {
|
|||||||
return const Result(NegotiatorState.done);
|
return const Result(NegotiatorState.done);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool canInlineFeature(List<XMLNode> features) {
|
||||||
|
return features.firstWhereOrNull(
|
||||||
|
(child) => child.xmlns == 'invalid:example:dont:use',
|
||||||
|
) !=
|
||||||
|
null;
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> postRegisterCallback() async {
|
Future<void> postRegisterCallback() async {
|
||||||
attributes
|
attributes
|
||||||
|
Loading…
Reference in New Issue
Block a user