Compare commits

...

2 Commits

26 changed files with 1491 additions and 1061 deletions

View File

@ -7,7 +7,7 @@ line-length=72
[title-trailing-punctuation] [title-trailing-punctuation]
[title-hard-tab] [title-hard-tab]
[title-match-regex] [title-match-regex]
regex=^((feat|fix|chore|refactor|docs|release|test)\((meta|tests|style|docs|xep|core)+(,(meta|tests|style|docs|xep|core))*\)|release): [A-Z0-9].*$ regex=^((feat|fix|chore|refactor|docs|release|test)\((meta|tests|style|docs|xep|core|example)+(,(meta|tests|style|docs|xep|core|example))*\)|release): [A-Z0-9].*$
[body-trailing-whitespace] [body-trailing-whitespace]

View File

@ -11,5 +11,3 @@ analyzer:
exclude: exclude:
- "**/*.g.dart" - "**/*.g.dart"
- "**/*.freezed.dart" - "**/*.freezed.dart"
- "test/"
- "integration_test/"

View File

@ -167,7 +167,7 @@ class XmppConnection {
/// 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<Result<bool, XmppConnectionError>>? _connectionCompleter; Completer<Result<bool, XmppError>>? _connectionCompleter;
/// Negotiators /// Negotiators
final Map<String, XmppFeatureNegotiatorBase> _featureNegotiators = {}; final Map<String, XmppFeatureNegotiatorBase> _featureNegotiators = {};
@ -896,7 +896,8 @@ class XmppConnection {
return; return;
} else { } else {
_log.severe( _log.severe(
'No negotiator could be picked while negotiations are not done'); 'No negotiator could be picked while negotiations are not done',
);
await _resetIsConnectionRunning(); await _resetIsConnectionRunning();
await handleError(NoAuthenticatorAvailableError()); await handleError(NoAuthenticatorAvailableError());
return; return;
@ -1132,7 +1133,7 @@ class XmppConnection {
); );
} }
Future<Result<bool, XmppConnectionError>> _connectImpl({ Future<Result<bool, XmppError>> _connectImpl({
String? lastResource, String? lastResource,
bool waitForConnection = false, bool waitForConnection = false,
bool shouldReconnect = true, bool shouldReconnect = true,
@ -1222,7 +1223,7 @@ 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, XmppConnectionError>> connect({ Future<Result<bool, XmppError>> connect({
String? lastResource, String? lastResource,
bool? shouldReconnect, bool? shouldReconnect,
bool waitForConnection = false, bool waitForConnection = false,

View File

@ -4,13 +4,28 @@ import 'package:test/test.dart';
void main() { void main() {
test('Test the async queue', () async { test('Test the async queue', () async {
final queue = AsyncQueue(); final queue = AsyncQueue();
int future1Finish = 0; var future1Finish = 0;
int future2Finish = 0; var future2Finish = 0;
int future3Finish = 0; var future3Finish = 0;
await queue.addJob(() => Future<void>.delayed(const Duration(seconds: 3), () => future1Finish = DateTime.now().millisecondsSinceEpoch)); await queue.addJob(
await queue.addJob(() => Future<void>.delayed(const Duration(seconds: 3), () => future2Finish = DateTime.now().millisecondsSinceEpoch)); () => Future<void>.delayed(
await queue.addJob(() => Future<void>.delayed(const Duration(seconds: 3), () => future3Finish = DateTime.now().millisecondsSinceEpoch)); const Duration(seconds: 3),
() => future1Finish = DateTime.now().millisecondsSinceEpoch,
),
);
await queue.addJob(
() => Future<void>.delayed(
const Duration(seconds: 3),
() => future2Finish = DateTime.now().millisecondsSinceEpoch,
),
);
await queue.addJob(
() => Future<void>.delayed(
const Duration(seconds: 3),
() => future3Finish = DateTime.now().millisecondsSinceEpoch,
),
);
await Future<void>.delayed(const Duration(seconds: 12)); await Future<void>.delayed(const Duration(seconds: 12));

View File

@ -3,28 +3,38 @@ import 'package:moxxmpp/src/awaiter.dart';
import 'package:test/test.dart'; import 'package:test/test.dart';
void main() { void main() {
final bareJid = JID('moxxmpp', 'server3.example', ''); const bareJid = JID('moxxmpp', 'server3.example', '');
test('Test awaiting an awaited stanza with a from attribute', () async { test('Test awaiting an awaited stanza with a from attribute', () async {
final awaiter = StanzaAwaiter(); final awaiter = StanzaAwaiter();
// "Send" a stanza // "Send" a stanza
final future = await awaiter.addPending('user1@server.example', 'abc123', 'iq'); final future = await awaiter.addPending(
'user1@server.example',
'abc123',
'iq',
);
// Receive the wrong answer // Receive the wrong answer
final result1 = await awaiter.onData( final result1 = await awaiter.onData(
XMLNode.fromString('<iq from="user3@server.example" id="abc123" type="result" />'), XMLNode.fromString(
'<iq from="user3@server.example" id="abc123" type="result" />',
),
bareJid, bareJid,
); );
expect(result1, false); expect(result1, false);
final result2 = await awaiter.onData( final result2 = await awaiter.onData(
XMLNode.fromString('<iq from="user1@server.example" id="lol" type="result" />'), XMLNode.fromString(
'<iq from="user1@server.example" id="lol" type="result" />',
),
bareJid, bareJid,
); );
expect(result2, false); expect(result2, false);
// Receive the correct answer // Receive the correct answer
final stanza = XMLNode.fromString('<iq from="user1@server.example" id="abc123" type="result" />'); final stanza = XMLNode.fromString(
'<iq from="user1@server.example" id="abc123" type="result" />',
);
final result3 = await awaiter.onData( final result3 = await awaiter.onData(
stanza, stanza,
bareJid, bareJid,
@ -45,7 +55,7 @@ void main() {
bareJid, bareJid,
); );
expect(result1, false); expect(result1, false);
// Receive the correct answer // Receive the correct answer
final stanza = XMLNode.fromString('<iq id="abc123" type="result" />'); final stanza = XMLNode.fromString('<iq id="abc123" type="result" />');
final result2 = await awaiter.onData( final result2 = await awaiter.onData(

View File

@ -1,5 +1,6 @@
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');
@ -29,23 +30,41 @@ void main() {
}); });
test('Equality', () { test('Equality', () {
expect(JID.fromString('hallo@welt/abc') == JID('hallo', 'welt', 'abc'), true); expect(
expect(JID.fromString('hallo@welt') == JID('hallo', 'welt', 'a'), false); JID.fromString('hallo@welt/abc') == const JID('hallo', 'welt', 'abc'),
true,
);
expect(
JID.fromString('hallo@welt') == const JID('hallo', 'welt', 'a'),
false,
);
}); });
test('Dot suffix at domain part', () { test('Dot suffix at domain part', () {
expect(JID.fromString('hallo@welt.example.') == JID('hallo', 'welt.example', ''), true); expect(
expect(JID.fromString('hallo@welt.example./test') == JID('hallo', 'welt.example', 'test'), true); JID.fromString('hallo@welt.example.') ==
const JID('hallo', 'welt.example', ''),
true,
);
expect(
JID.fromString('hallo@welt.example./test') ==
const JID('hallo', 'welt.example', 'test'),
true,
);
}); });
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') ==
const JID('hallo', 'welt.example', 'test/welt'),
true,
);
}); });
test('bareCompare', () { test('bareCompare', () {
final jid1 = JID('hallo', 'welt', 'lol'); const jid1 = JID('hallo', 'welt', 'lol');
final jid2 = JID('hallo', 'welt', ''); const jid2 = JID('hallo', 'welt', '');
final jid3 = JID('hallo', 'earth', 'true'); const jid3 = JID('hallo', 'earth', 'true');
expect(jid1.bareCompare(jid2), true); expect(jid1.bareCompare(jid2), true);
expect(jid2.bareCompare(jid1), true); expect(jid2.bareCompare(jid1), true);

View File

@ -9,24 +9,32 @@ const exampleXmlns2 = 'im:moxxmpp:example2';
const exampleNamespace2 = 'im.moxxmpp.test.example2'; const exampleNamespace2 = 'im.moxxmpp.test.example2';
class StubNegotiator1 extends XmppFeatureNegotiatorBase { class StubNegotiator1 extends XmppFeatureNegotiatorBase {
StubNegotiator1() : called = false, super(1, false, exampleXmlns1, exampleNamespace1); StubNegotiator1()
: called = false,
super(1, false, exampleXmlns1, exampleNamespace1);
bool called; bool called;
@override @override
Future<Result<NegotiatorState, NegotiatorError>> negotiate(XMLNode nonza) async { Future<Result<NegotiatorState, NegotiatorError>> negotiate(
XMLNode nonza,
) async {
called = true; called = true;
return const Result(NegotiatorState.done); return const Result(NegotiatorState.done);
} }
} }
class StubNegotiator2 extends XmppFeatureNegotiatorBase { class StubNegotiator2 extends XmppFeatureNegotiatorBase {
StubNegotiator2() : called = false, super(10, false, exampleXmlns2, exampleNamespace2); StubNegotiator2()
: called = false,
super(10, false, exampleXmlns2, exampleNamespace2);
bool called; bool called;
@override @override
Future<Result<NegotiatorState, NegotiatorError>> negotiate(XMLNode nonza) async { Future<Result<NegotiatorState, NegotiatorError>> negotiate(
XMLNode nonza,
) async {
called = true; called = true;
return const Result(NegotiatorState.done); return const Result(NegotiatorState.done);
} }
@ -53,12 +61,13 @@ void main() {
), ),
], ],
); );
final connection = XmppConnection( final connection = XmppConnection(
TestingReconnectionPolicy(), TestingReconnectionPolicy(),
AlwaysConnectedConnectivityManager(), AlwaysConnectedConnectivityManager(),
stubSocket, stubSocket,
)..registerFeatureNegotiators([ )
..registerFeatureNegotiators([
StubNegotiator1(), StubNegotiator1(),
StubNegotiator2(), StubNegotiator2(),
]) ])
@ -85,11 +94,13 @@ void main() {
expect(connection.getNextNegotiator(features)?.id, exampleNamespace2); expect(connection.getNextNegotiator(features)?.id, exampleNamespace2);
}); });
test('Test negotiating features with no stream restarts', () async { test('Test negotiating features with no stream restarts', () async {
await connection.connect(); await connection.connect();
await Future.delayed(const Duration(seconds: 3), () { await Future.delayed(const Duration(seconds: 3), () {
final negotiator1 = connection.getNegotiatorById<StubNegotiator1>(exampleNamespace1); final negotiator1 =
final negotiator2 = connection.getNegotiatorById<StubNegotiator2>(exampleNamespace2); connection.getNegotiatorById<StubNegotiator1>(exampleNamespace1);
final negotiator2 =
connection.getNegotiatorById<StubNegotiator2>(exampleNamespace2);
expect(negotiator1?.called, true); expect(negotiator1?.called, true);
expect(negotiator2?.called, true); expect(negotiator2?.called, true);
}); });

View File

@ -3,12 +3,11 @@ import 'package:test/test.dart';
void main() { void main() {
test('Test receiving a roster push', () async { test('Test receiving a roster push', () async {
final rs = TestingRosterStateManager(null, []); final rs = TestingRosterStateManager(null, [])..register((_) {});
rs.register((_) {});
await rs.handleRosterPush( await rs.handleRosterPush(
RosterPushResult( RosterPushResult(
XmppRosterItem( const XmppRosterItem(
jid: 'testuser@server.example', jid: 'testuser@server.example',
subscription: 'both', subscription: 'both',
), ),
@ -17,7 +16,10 @@ void main() {
); );
expect( expect(
rs.getRosterItems().indexWhere((item) => item.jid == 'testuser@server.example') != -1, rs
.getRosterItems()
.indexWhere((item) => item.jid == 'testuser@server.example') !=
-1,
true, true,
); );
expect(rs.loadCount, 1); expect(rs.loadCount, 1);
@ -26,7 +28,7 @@ void main() {
// Receive another roster push // Receive another roster push
await rs.handleRosterPush( await rs.handleRosterPush(
RosterPushResult( RosterPushResult(
XmppRosterItem( const XmppRosterItem(
jid: 'testuser2@server2.example', jid: 'testuser2@server2.example',
subscription: 'to', subscription: 'to',
), ),
@ -35,16 +37,19 @@ void main() {
); );
expect( expect(
rs.getRosterItems().indexWhere((item) => item.jid == 'testuser2@server2.example') != -1, rs
.getRosterItems()
.indexWhere((item) => item.jid == 'testuser2@server2.example') !=
-1,
true, true,
); );
expect(rs.loadCount, 1); expect(rs.loadCount, 1);
expect(rs.getRosterItems().length, 2); expect(rs.getRosterItems().length, 2);
// Remove one of the items // Remove one of the items
await rs.handleRosterPush( await rs.handleRosterPush(
RosterPushResult( RosterPushResult(
XmppRosterItem( const XmppRosterItem(
jid: 'testuser2@server2.example', jid: 'testuser2@server2.example',
subscription: 'remove', subscription: 'remove',
), ),
@ -53,34 +58,39 @@ void main() {
); );
expect( expect(
rs.getRosterItems().indexWhere((item) => item.jid == 'testuser2@server2.example') == -1, rs
.getRosterItems()
.indexWhere((item) => item.jid == 'testuser2@server2.example') ==
-1,
true, true,
); );
expect( expect(
rs.getRosterItems().indexWhere((item) => item.jid == 'testuser@server.example') != 1, rs
.getRosterItems()
.indexWhere((item) => item.jid == 'testuser@server.example') !=
1,
true, true,
); );
expect(rs.loadCount, 1); expect(rs.loadCount, 1);
expect(rs.getRosterItems().length, 1); expect(rs.getRosterItems().length, 1);
}); });
test('Test a roster fetch', () async { test('Test a roster fetch', () async {
final rs = TestingRosterStateManager(null, []); final rs = TestingRosterStateManager(null, [])..register((_) {});
rs.register((_) {});
// Fetch the roster // Fetch the roster
await rs.handleRosterFetch( await rs.handleRosterFetch(
RosterRequestResult( RosterRequestResult(
[ [
XmppRosterItem( const XmppRosterItem(
jid: 'testuser@server.example', jid: 'testuser@server.example',
subscription: 'both', subscription: 'both',
), ),
XmppRosterItem( const XmppRosterItem(
jid: 'testuser2@server2.example', jid: 'testuser2@server2.example',
subscription: 'to', subscription: 'to',
), ),
XmppRosterItem( const XmppRosterItem(
jid: 'testuser3@server3.example', jid: 'testuser3@server3.example',
subscription: 'from', subscription: 'from',
), ),
@ -91,48 +101,66 @@ void main() {
expect(rs.loadCount, 1); expect(rs.loadCount, 1);
expect(rs.getRosterItems().length, 3); expect(rs.getRosterItems().length, 3);
expect(rs.getRosterItems().indexWhere((item) => item.jid == 'testuser@server.example') != -1, true); expect(
expect(rs.getRosterItems().indexWhere((item) => item.jid == 'testuser2@server2.example') != -1, true); rs
expect(rs.getRosterItems().indexWhere((item) => item.jid == 'testuser3@server3.example') != -1, true); .getRosterItems()
.indexWhere((item) => item.jid == 'testuser@server.example') !=
-1,
true,
);
expect(
rs
.getRosterItems()
.indexWhere((item) => item.jid == 'testuser2@server2.example') !=
-1,
true,
);
expect(
rs
.getRosterItems()
.indexWhere((item) => item.jid == 'testuser3@server3.example') !=
-1,
true,
);
}); });
test('Test a roster fetch if we already have a roster', () async { test('Test a roster fetch if we already have a roster', () async {
XmppEvent? event; XmppEvent? event;
final rs = TestingRosterStateManager('aaaaa', [ final rs = TestingRosterStateManager('aaaaa', [
XmppRosterItem( const XmppRosterItem(
jid: 'testuser@server.example', jid: 'testuser@server.example',
subscription: 'both', subscription: 'both',
), ),
XmppRosterItem( const XmppRosterItem(
jid: 'testuser2@server2.example', jid: 'testuser2@server2.example',
subscription: 'to', subscription: 'to',
), ),
XmppRosterItem( const XmppRosterItem(
jid: 'testuser3@server3.example', jid: 'testuser3@server3.example',
subscription: 'from', subscription: 'from',
), ),
]); ])
rs.register((_event) { ..register((e) {
event = _event; event = e;
}); });
// Fetch the roster // Fetch the roster
await rs.handleRosterFetch( await rs.handleRosterFetch(
RosterRequestResult( RosterRequestResult(
[ [
XmppRosterItem( const XmppRosterItem(
jid: 'testuser@server.example', jid: 'testuser@server.example',
subscription: 'both', subscription: 'both',
), ),
XmppRosterItem( const XmppRosterItem(
jid: 'testuser2@server2.example', jid: 'testuser2@server2.example',
subscription: 'to', subscription: 'to',
), ),
XmppRosterItem( const XmppRosterItem(
jid: 'testuser3@server3.example', jid: 'testuser3@server3.example',
subscription: 'both', subscription: 'both',
), ),
XmppRosterItem( const XmppRosterItem(
jid: 'testuser4@server4.example', jid: 'testuser4@server4.example',
subscription: 'both', subscription: 'both',
), ),
@ -142,7 +170,7 @@ void main() {
); );
expect(event is RosterUpdatedEvent, true); expect(event is RosterUpdatedEvent, true);
final updateEvent = event as RosterUpdatedEvent; final updateEvent = event! as RosterUpdatedEvent;
expect(updateEvent.added.length, 1); expect(updateEvent.added.length, 1);
expect(updateEvent.added.first.jid, 'testuser4@server4.example'); expect(updateEvent.added.first.jid, 'testuser4@server4.example');

View File

@ -1,27 +1,30 @@
import 'package:moxxmpp/src/negotiators/sasl/kv.dart'; import 'package:moxxmpp/src/negotiators/sasl/kv.dart';
import 'package:moxxmpp/moxxmpp.dart';
import 'package:test/test.dart'; import 'package:test/test.dart';
void main() { void main() {
test('Test the Key-Value parser', () { test('Test the Key-Value parser', () {
final result1 = parseKeyValue('n,,n=user,r=fyko+d2lbbFgONRv9qkxdawL'); final result1 = parseKeyValue('n,,n=user,r=fyko+d2lbbFgONRv9qkxdawL');
expect(result1.length, 2); expect(result1.length, 2);
expect(result1['n']!, 'user'); expect(result1['n'], 'user');
expect(result1['r']!, 'fyko+d2lbbFgONRv9qkxdawL'); expect(result1['r'], 'fyko+d2lbbFgONRv9qkxdawL');
final result2 = parseKeyValue('r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,s=QSXCR+Q6sek8bf92,i=4096'); final result2 = parseKeyValue(
expect(result2.length, 3); 'r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,s=QSXCR+Q6sek8bf92,i=4096',
expect(result2['r']!, 'fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j'); );
expect(result2['s']!, 'QSXCR+Q6sek8bf92'); expect(result2.length, 3);
expect(result2['i']!, '4096'); expect(result2['r'], 'fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j');
expect(result2['s'], 'QSXCR+Q6sek8bf92');
expect(result2['i'], '4096');
}); });
test("Test the Key-Value parser with '=' as a value", () { test("Test the Key-Value parser with '=' as a value", () {
final result = parseKeyValue('c=biws,r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,p=v0X8v3Bz2T0CJGbJQyF0X+HI4Ts=,o=123'); final result = parseKeyValue(
expect(result.length, 4); 'c=biws,r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,p=v0X8v3Bz2T0CJGbJQyF0X+HI4Ts=,o=123',
expect(result['c']!, 'biws'); );
expect(result['r']!, 'fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j'); expect(result.length, 4);
expect(result['p']!, 'v0X8v3Bz2T0CJGbJQyF0X+HI4Ts='); expect(result['c'], 'biws');
expect(result['o']!, '123'); expect(result['r'], 'fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j');
expect(result['p'], 'v0X8v3Bz2T0CJGbJQyF0X+HI4Ts=');
expect(result['o'], '123');
}); });
} }

View File

@ -38,11 +38,19 @@ final scramSha256StreamFeatures = XMLNode(
void main() { void main() {
final fakeSocket = StubTCPSocket([]); final fakeSocket = StubTCPSocket([]);
test('Test SASL SCRAM-SHA-1', () async { test('Test SASL SCRAM-SHA-1', () async {
final negotiator = SaslScramNegotiator(0, 'n=user,r=fyko+d2lbbFgONRv9qkxdawL', 'fyko+d2lbbFgONRv9qkxdawL', ScramHashType.sha1); final negotiator = SaslScramNegotiator(
negotiator.register( 0,
'n=user,r=fyko+d2lbbFgONRv9qkxdawL',
'fyko+d2lbbFgONRv9qkxdawL',
ScramHashType.sha1,
)..register(
NegotiatorAttributes( NegotiatorAttributes(
(XMLNode _, {String? redact}) {}, (XMLNode _, {String? redact}) {},
() => ConnectionSettings(jid: JID.fromString('user@server'), password: 'pencil', useDirectTLS: true), () => ConnectionSettings(
jid: JID.fromString('user@server'),
password: 'pencil',
useDirectTLS: true,
),
(_) async {}, (_) async {},
getNegotiatorNullStub, getNegotiatorNullStub,
getManagerNullStub, getManagerNullStub,
@ -52,61 +60,90 @@ void main() {
), ),
); );
expect( expect(
HEX.encode(await negotiator.calculateSaltedPassword('QSXCR+Q6sek8bf92', 4096)), HEX.encode(
'1d96ee3a529b5a5f9e47c01f229a2cb8a6e15f7d', await negotiator.calculateSaltedPassword('QSXCR+Q6sek8bf92', 4096),
); ),
expect( '1d96ee3a529b5a5f9e47c01f229a2cb8a6e15f7d',
HEX.encode( );
await negotiator.calculateClientKey(HEX.decode('1d96ee3a529b5a5f9e47c01f229a2cb8a6e15f7d')), expect(
HEX.encode(
await negotiator.calculateClientKey(
HEX.decode('1d96ee3a529b5a5f9e47c01f229a2cb8a6e15f7d'),
), ),
'e234c47bf6c36696dd6d852b99aaa2ba26555728', ),
); 'e234c47bf6c36696dd6d852b99aaa2ba26555728',
const authMessage = 'n=user,r=fyko+d2lbbFgONRv9qkxdawL,r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,s=QSXCR+Q6sek8bf92,i=4096,c=biws,r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j'; );
expect( const authMessage =
HEX.encode( 'n=user,r=fyko+d2lbbFgONRv9qkxdawL,r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,s=QSXCR+Q6sek8bf92,i=4096,c=biws,r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j';
await negotiator.calculateClientSignature(authMessage, HEX.decode('e9d94660c39d65c38fbad91c358f14da0eef2bd6')), expect(
HEX.encode(
await negotiator.calculateClientSignature(
authMessage,
HEX.decode('e9d94660c39d65c38fbad91c358f14da0eef2bd6'),
), ),
'5d7138c486b0bfabdf49e3e2da8bd6e5c79db613', ),
); '5d7138c486b0bfabdf49e3e2da8bd6e5c79db613',
expect( );
HEX.encode( expect(
negotiator.calculateClientProof(HEX.decode('e234c47bf6c36696dd6d852b99aaa2ba26555728'), HEX.decode('5d7138c486b0bfabdf49e3e2da8bd6e5c79db613')), HEX.encode(
negotiator.calculateClientProof(
HEX.decode('e234c47bf6c36696dd6d852b99aaa2ba26555728'),
HEX.decode('5d7138c486b0bfabdf49e3e2da8bd6e5c79db613'),
), ),
'bf45fcbf7073d93d022466c94321745fe1c8e13b', ),
); 'bf45fcbf7073d93d022466c94321745fe1c8e13b',
expect( );
HEX.encode( expect(
await negotiator.calculateServerSignature(authMessage, HEX.decode('0fe09258b3ac852ba502cc62ba903eaacdbf7d31')), HEX.encode(
await negotiator.calculateServerSignature(
authMessage,
HEX.decode('0fe09258b3ac852ba502cc62ba903eaacdbf7d31'),
), ),
'ae617da6a57c4bbb2e0286568dae1d251905b0a4', ),
); 'ae617da6a57c4bbb2e0286568dae1d251905b0a4',
expect( );
HEX.encode( expect(
await negotiator.calculateServerKey(HEX.decode('1d96ee3a529b5a5f9e47c01f229a2cb8a6e15f7d')), HEX.encode(
await negotiator.calculateServerKey(
HEX.decode('1d96ee3a529b5a5f9e47c01f229a2cb8a6e15f7d'),
), ),
'0fe09258b3ac852ba502cc62ba903eaacdbf7d31', ),
); '0fe09258b3ac852ba502cc62ba903eaacdbf7d31',
expect( );
HEX.encode( expect(
negotiator.calculateClientProof( HEX.encode(
HEX.decode('e234c47bf6c36696dd6d852b99aaa2ba26555728'), negotiator.calculateClientProof(
HEX.decode('5d7138c486b0bfabdf49e3e2da8bd6e5c79db613'), HEX.decode('e234c47bf6c36696dd6d852b99aaa2ba26555728'),
), HEX.decode('5d7138c486b0bfabdf49e3e2da8bd6e5c79db613'),
), ),
'bf45fcbf7073d93d022466c94321745fe1c8e13b', ),
); 'bf45fcbf7073d93d022466c94321745fe1c8e13b',
);
expect(await negotiator.calculateChallengeResponse('cj1meWtvK2QybGJiRmdPTlJ2OXFreGRhd0wzcmZjTkhZSlkxWlZ2V1ZzN2oscz1RU1hDUitRNnNlazhiZjkyLGk9NDA5Ng=='), 'c=biws,r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,p=v0X8v3Bz2T0CJGbJQyF0X+HI4Ts='); expect(
await negotiator.calculateChallengeResponse(
'cj1meWtvK2QybGJiRmdPTlJ2OXFreGRhd0wzcmZjTkhZSlkxWlZ2V1ZzN2oscz1RU1hDUitRNnNlazhiZjkyLGk9NDA5Ng==',
),
'c=biws,r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,p=v0X8v3Bz2T0CJGbJQyF0X+HI4Ts=',
);
}); });
test('Test SASL SCRAM-SHA-256', () async { test('Test SASL SCRAM-SHA-256', () async {
String? lastMessage; String? lastMessage;
final negotiator = SaslScramNegotiator(0, 'n=user,r=rOprNGfwEbeRWgbNEkqO', 'rOprNGfwEbeRWgbNEkqO', ScramHashType.sha256); final negotiator = SaslScramNegotiator(
negotiator.register( 0,
'n=user,r=rOprNGfwEbeRWgbNEkqO',
'rOprNGfwEbeRWgbNEkqO',
ScramHashType.sha256,
)..register(
NegotiatorAttributes( NegotiatorAttributes(
(XMLNode n, {String? redact}) => lastMessage = n.innerText(), (XMLNode n, {String? redact}) => lastMessage = n.innerText(),
() => ConnectionSettings(jid: JID.fromString('user@server'), password: 'pencil', useDirectTLS: true), () => ConnectionSettings(
jid: JID.fromString('user@server'),
password: 'pencil',
useDirectTLS: true,
),
(_) async {}, (_) async {},
getNegotiatorNullStub, getNegotiatorNullStub,
getManagerNullStub, getManagerNullStub,
@ -116,29 +153,45 @@ void main() {
), ),
); );
await negotiator.negotiate(scramSha256StreamFeatures); await negotiator.negotiate(scramSha256StreamFeatures);
expect( expect(
utf8.decode(base64Decode(lastMessage!)), utf8.decode(base64Decode(lastMessage!)),
'n,,n=user,r=rOprNGfwEbeRWgbNEkqO', 'n,,n=user,r=rOprNGfwEbeRWgbNEkqO',
); );
await negotiator.negotiate(XMLNode.fromString("<challenge xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>cj1yT3ByTkdmd0ViZVJXZ2JORWtxTyVodllEcFdVYTJSYVRDQWZ1eEZJbGopaE5sRiRrMCxzPVcyMlphSjBTTlk3c29Fc1VFamI2Z1E9PSxpPTQwOTY=</challenge>"));
expect(
utf8.decode(base64Decode(lastMessage!)),
'c=biws,r=rOprNGfwEbeRWgbNEkqO%hvYDpWUa2RaTCAfuxFIlj)hNlF\$k0,p=dHzbZapWIk4jUhN+Ute9ytag9zjfMHgsqmmiz7AndVQ=',
);
final result = await negotiator.negotiate(XMLNode.fromString("<success xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>dj02cnJpVFJCaTIzV3BSUi93dHVwK21NaFVaVW4vZEI1bkxUSlJzamw5NUc0PQ==</success>")); await negotiator.negotiate(
XMLNode.fromString(
"<challenge xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>cj1yT3ByTkdmd0ViZVJXZ2JORWtxTyVodllEcFdVYTJSYVRDQWZ1eEZJbGopaE5sRiRrMCxzPVcyMlphSjBTTlk3c29Fc1VFamI2Z1E9PSxpPTQwOTY=</challenge>",
),
);
expect(
utf8.decode(base64Decode(lastMessage!)),
r'c=biws,r=rOprNGfwEbeRWgbNEkqO%hvYDpWUa2RaTCAfuxFIlj)hNlF$k0,p=dHzbZapWIk4jUhN+Ute9ytag9zjfMHgsqmmiz7AndVQ=',
);
expect(result.get<NegotiatorState>(), NegotiatorState.done); final result = await negotiator.negotiate(
XMLNode.fromString(
"<success xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>dj02cnJpVFJCaTIzV3BSUi93dHVwK21NaFVaVW4vZEI1bkxUSlJzamw5NUc0PQ==</success>",
),
);
expect(result.get<NegotiatorState>(), NegotiatorState.done);
}); });
test('Test a positive server signature check', () async { test('Test a positive server signature check', () async {
final negotiator = SaslScramNegotiator(0, 'n=user,r=fyko+d2lbbFgONRv9qkxdawL', 'fyko+d2lbbFgONRv9qkxdawL', ScramHashType.sha1); final negotiator = SaslScramNegotiator(
negotiator.register( 0,
'n=user,r=fyko+d2lbbFgONRv9qkxdawL',
'fyko+d2lbbFgONRv9qkxdawL',
ScramHashType.sha1,
)..register(
NegotiatorAttributes( NegotiatorAttributes(
(XMLNode _, {String? redact}) {}, (XMLNode _, {String? redact}) {},
() => ConnectionSettings(jid: JID.fromString('user@server'), password: 'pencil', useDirectTLS: true), () => ConnectionSettings(
jid: JID.fromString('user@server'),
password: 'pencil',
useDirectTLS: true,
),
(_) async {}, (_) async {},
getNegotiatorNullStub, getNegotiatorNullStub,
getManagerNullStub, getManagerNullStub,
@ -148,19 +201,35 @@ void main() {
), ),
); );
await negotiator.negotiate(scramSha1StreamFeatures); await negotiator.negotiate(scramSha1StreamFeatures);
await negotiator.negotiate(XMLNode.fromString("<challenge xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>cj1meWtvK2QybGJiRmdPTlJ2OXFreGRhd0wzcmZjTkhZSlkxWlZ2V1ZzN2oscz1RU1hDUitRNnNlazhiZjkyLGk9NDA5Ng==</challenge>")); await negotiator.negotiate(
final result = await negotiator.negotiate(XMLNode.fromString("<success xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>dj1ybUY5cHFWOFM3c3VBb1pXamE0ZEpSa0ZzS1E9</success>")); XMLNode.fromString(
"<challenge xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>cj1meWtvK2QybGJiRmdPTlJ2OXFreGRhd0wzcmZjTkhZSlkxWlZ2V1ZzN2oscz1RU1hDUitRNnNlazhiZjkyLGk9NDA5Ng==</challenge>",
),
);
final result = await negotiator.negotiate(
XMLNode.fromString(
"<success xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>dj1ybUY5cHFWOFM3c3VBb1pXamE0ZEpSa0ZzS1E9</success>",
),
);
expect(result.get<NegotiatorState>(), NegotiatorState.done); expect(result.get<NegotiatorState>(), NegotiatorState.done);
}); });
test('Test a negative server signature check', () async { test('Test a negative server signature check', () async {
final negotiator = SaslScramNegotiator(0, 'n=user,r=fyko+d2lbbFgONRv9qkxdawL', 'fyko+d2lbbFgONRv9qkxdawL', ScramHashType.sha1); final negotiator = SaslScramNegotiator(
negotiator.register( 0,
'n=user,r=fyko+d2lbbFgONRv9qkxdawL',
'fyko+d2lbbFgONRv9qkxdawL',
ScramHashType.sha1,
)..register(
NegotiatorAttributes( NegotiatorAttributes(
(XMLNode _, {String? redact}) {}, (XMLNode _, {String? redact}) {},
() => ConnectionSettings(jid: JID.fromString('user@server'), password: 'pencil', useDirectTLS: true), () => ConnectionSettings(
jid: JID.fromString('user@server'),
password: 'pencil',
useDirectTLS: true,
),
(_) async {}, (_) async {},
getNegotiatorNullStub, getNegotiatorNullStub,
getManagerNullStub, getManagerNullStub,
@ -170,23 +239,38 @@ void main() {
), ),
); );
var result; var result = await negotiator.negotiate(scramSha1StreamFeatures);
result = await negotiator.negotiate(scramSha1StreamFeatures); expect(result.isType<NegotiatorState>(), true);
expect(result.isType<NegotiatorState>(), true);
result = await negotiator.negotiate(XMLNode.fromString("<challenge xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>cj1meWtvK2QybGJiRmdPTlJ2OXFreGRhd0wzcmZjTkhZSlkxWlZ2V1ZzN2oscz1RU1hDUitRNnNlazhiZjkyLGk9NDA5Ng==</challenge>")); result = await negotiator.negotiate(
expect(result.isType<NegotiatorState>(), true); XMLNode.fromString(
"<challenge xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>cj1meWtvK2QybGJiRmdPTlJ2OXFreGRhd0wzcmZjTkhZSlkxWlZ2V1ZzN2oscz1RU1hDUitRNnNlazhiZjkyLGk9NDA5Ng==</challenge>",
),
);
expect(result.isType<NegotiatorState>(), true);
result = await negotiator.negotiate(XMLNode.fromString("<success xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>dj1zbUY5cHFWOFM3c3VBb1pXamE0ZEpSa0ZzS1E9</success>")); result = await negotiator.negotiate(
expect(result.isType<NegotiatorError>(), true); XMLNode.fromString(
"<success xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>dj1zbUY5cHFWOFM3c3VBb1pXamE0ZEpSa0ZzS1E9</success>",
),
);
expect(result.isType<NegotiatorError>(), true);
}); });
test('Test a resetting the SCRAM negotiator', () async { test('Test a resetting the SCRAM negotiator', () async {
final negotiator = SaslScramNegotiator(0, 'n=user,r=fyko+d2lbbFgONRv9qkxdawL', 'fyko+d2lbbFgONRv9qkxdawL', ScramHashType.sha1); final negotiator = SaslScramNegotiator(
negotiator.register( 0,
'n=user,r=fyko+d2lbbFgONRv9qkxdawL',
'fyko+d2lbbFgONRv9qkxdawL',
ScramHashType.sha1,
)..register(
NegotiatorAttributes( NegotiatorAttributes(
(XMLNode _, {String? redact}) {}, (XMLNode _, {String? redact}) {},
() => ConnectionSettings(jid: JID.fromString('user@server'), password: 'pencil', useDirectTLS: true), () => ConnectionSettings(
jid: JID.fromString('user@server'),
password: 'pencil',
useDirectTLS: true,
),
(_) async {}, (_) async {},
getNegotiatorNullStub, getNegotiatorNullStub,
getManagerNullStub, getManagerNullStub,
@ -196,16 +280,32 @@ void main() {
), ),
); );
await negotiator.negotiate(scramSha1StreamFeatures); await negotiator.negotiate(scramSha1StreamFeatures);
await negotiator.negotiate(XMLNode.fromString("<challenge xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>cj1meWtvK2QybGJiRmdPTlJ2OXFreGRhd0wzcmZjTkhZSlkxWlZ2V1ZzN2oscz1RU1hDUitRNnNlazhiZjkyLGk9NDA5Ng==</challenge>")); await negotiator.negotiate(
final result1 = await negotiator.negotiate(XMLNode.fromString("<success xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>dj1ybUY5cHFWOFM3c3VBb1pXamE0ZEpSa0ZzS1E9</success>")); XMLNode.fromString(
expect(result1.get<NegotiatorState>(), NegotiatorState.done); "<challenge xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>cj1meWtvK2QybGJiRmdPTlJ2OXFreGRhd0wzcmZjTkhZSlkxWlZ2V1ZzN2oscz1RU1hDUitRNnNlazhiZjkyLGk9NDA5Ng==</challenge>",
),
);
final result1 = await negotiator.negotiate(
XMLNode.fromString(
"<success xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>dj1ybUY5cHFWOFM3c3VBb1pXamE0ZEpSa0ZzS1E9</success>",
),
);
expect(result1.get<NegotiatorState>(), NegotiatorState.done);
// Reset and try again // Reset and try again
negotiator.reset(); negotiator.reset();
await negotiator.negotiate(scramSha1StreamFeatures); await negotiator.negotiate(scramSha1StreamFeatures);
await negotiator.negotiate(XMLNode.fromString("<challenge xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>cj1meWtvK2QybGJiRmdPTlJ2OXFreGRhd0wzcmZjTkhZSlkxWlZ2V1ZzN2oscz1RU1hDUitRNnNlazhiZjkyLGk9NDA5Ng==</challenge>")); await negotiator.negotiate(
final result2 = await negotiator.negotiate(XMLNode.fromString("<success xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>dj1ybUY5cHFWOFM3c3VBb1pXamE0ZEpSa0ZzS1E9</success>")); XMLNode.fromString(
expect(result2.get<NegotiatorState>(), NegotiatorState.done); "<challenge xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>cj1meWtvK2QybGJiRmdPTlJ2OXFreGRhd0wzcmZjTkhZSlkxWlZ2V1ZzN2oscz1RU1hDUitRNnNlazhiZjkyLGk9NDA5Ng==</challenge>",
),
);
final result2 = await negotiator.negotiate(
XMLNode.fromString(
"<success xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>dj1ybUY5cHFWOFM3c3VBb1pXamE0ZEpSa0ZzS1E9</success>",
),
);
expect(result2.get<NegotiatorState>(), NegotiatorState.done);
}); });
} }

View File

@ -1,89 +1,151 @@
import 'package:moxxmpp/moxxmpp.dart'; import 'package:moxxmpp/moxxmpp.dart';
import 'package:test/test.dart'; import 'package:test/test.dart';
final stanza1 = Stanza.iq(children: [ final stanza1 = Stanza.iq(
XMLNode.xmlns(tag: 'tag', xmlns: 'owo') children: [XMLNode.xmlns(tag: 'tag', xmlns: 'owo')],
],); );
final stanza2 = Stanza.message(children: [ final stanza2 = Stanza.message(
XMLNode.xmlns(tag: 'some-other-tag', xmlns: 'owo') children: [XMLNode.xmlns(tag: 'some-other-tag', xmlns: 'owo')],
],); );
void main() { void main() {
test('match all', () { test('match all', () {
final handler = StanzaHandler(callback: (stanza, _) async => StanzaHandlerData(true, false, null, stanza)); final handler = StanzaHandler(
callback: (stanza, _) async => StanzaHandlerData(
true,
false,
null,
stanza,
),
);
expect(handler.matches(Stanza.iq()), true); expect(handler.matches(Stanza.iq()), true);
expect(handler.matches(Stanza.message()), true); expect(handler.matches(Stanza.message()), true);
expect(handler.matches(Stanza.presence()), true); expect(handler.matches(Stanza.presence()), true);
expect(handler.matches(stanza1), true); expect(handler.matches(stanza1), true);
expect(handler.matches(stanza2), true); expect(handler.matches(stanza2), true);
}); });
test('xmlns matching', () { test('xmlns matching', () {
final handler = StanzaHandler( final handler = StanzaHandler(
callback: (stanza, _) async => StanzaHandlerData(true, false, null, stanza), callback: (stanza, _) async => StanzaHandlerData(
tagXmlns: 'owo', true,
); false,
null,
stanza,
),
tagXmlns: 'owo',
);
expect(handler.matches(Stanza.iq()), false); expect(handler.matches(Stanza.iq()), false);
expect(handler.matches(Stanza.message()), false); expect(handler.matches(Stanza.message()), false);
expect(handler.matches(Stanza.presence()), false); expect(handler.matches(Stanza.presence()), false);
expect(handler.matches(stanza1), true); expect(handler.matches(stanza1), true);
expect(handler.matches(stanza2), true); expect(handler.matches(stanza2), true);
}); });
test('stanzaTag matching', () { test('stanzaTag matching', () {
var run = false; var run = false;
final handler = StanzaHandler(callback: (stanza, _) async { final handler = StanzaHandler(
run = true; callback: (stanza, _) async {
return StanzaHandlerData(true, false, null, stanza); run = true;
}, stanzaTag: 'iq',); return StanzaHandlerData(
true,
false,
null,
stanza,
);
},
stanzaTag: 'iq',
);
expect(handler.matches(Stanza.iq()), true); expect(handler.matches(Stanza.iq()), true);
expect(handler.matches(Stanza.message()), false); expect(handler.matches(Stanza.message()), false);
expect(handler.matches(Stanza.presence()), false); expect(handler.matches(Stanza.presence()), false);
expect(handler.matches(stanza1), true); expect(handler.matches(stanza1), true);
expect(handler.matches(stanza2), false); expect(handler.matches(stanza2), false);
handler.callback(stanza2, StanzaHandlerData(false, false, null, stanza2)); handler.callback(
expect(run, true); stanza2,
StanzaHandlerData(
false,
false,
null,
stanza2,
),
);
expect(run, true);
}); });
test('tagName matching', () { test('tagName matching', () {
final handler = StanzaHandler( final handler = StanzaHandler(
callback: (stanza, _) async => StanzaHandlerData(true, false, null, stanza), callback: (stanza, _) async => StanzaHandlerData(
tagName: 'tag', true,
); false,
null,
stanza,
),
tagName: 'tag',
);
expect(handler.matches(Stanza.iq()), false); expect(handler.matches(Stanza.iq()), false);
expect(handler.matches(Stanza.message()), false); expect(handler.matches(Stanza.message()), false);
expect(handler.matches(Stanza.presence()), false); expect(handler.matches(Stanza.presence()), false);
expect(handler.matches(stanza1), true); expect(handler.matches(stanza1), true);
expect(handler.matches(stanza2), false); expect(handler.matches(stanza2), false);
}); });
test('combined matching', () { test('combined matching', () {
final handler = StanzaHandler( final handler = StanzaHandler(
callback: (stanza, _) async => StanzaHandlerData(true, false, null, stanza), callback: (stanza, _) async => StanzaHandlerData(
tagName: 'tag', true,
stanzaTag: 'iq', false,
tagXmlns: 'owo', null,
); stanza,
),
tagName: 'tag',
stanzaTag: 'iq',
tagXmlns: 'owo',
);
expect(handler.matches(Stanza.iq()), false); expect(handler.matches(Stanza.iq()), false);
expect(handler.matches(Stanza.message()), false); expect(handler.matches(Stanza.message()), false);
expect(handler.matches(Stanza.presence()), false); expect(handler.matches(Stanza.presence()), false);
expect(handler.matches(stanza1), true); expect(handler.matches(stanza1), true);
expect(handler.matches(stanza2), false); expect(handler.matches(stanza2), false);
}); });
test('sorting', () { test('sorting', () {
final handlerList = [ final handlerList = [
StanzaHandler(callback: (stanza, _) async => StanzaHandlerData(true, false, null, stanza), tagName: '1', priority: 100), StanzaHandler(
StanzaHandler(callback: (stanza, _) async => StanzaHandlerData(true, false, null, stanza), tagName: '2'), callback: (stanza, _) async => StanzaHandlerData(
StanzaHandler(callback: (stanza, _) async => StanzaHandlerData(true, false, null, stanza), tagName: '3', priority: 50) true,
]; false,
null,
stanza,
),
tagName: '1',
priority: 100,
),
StanzaHandler(
callback: (stanza, _) async => StanzaHandlerData(
true,
false,
null,
stanza,
),
tagName: '2',
),
StanzaHandler(
callback: (stanza, _) async => StanzaHandlerData(
true,
false,
null,
stanza,
),
tagName: '3',
priority: 50,
)
]..sort(stanzaHandlerSortComparator);
handlerList.sort(stanzaHandlerSortComparator); expect(handlerList[0].tagName, '1');
expect(handlerList[1].tagName, '3');
expect(handlerList[0].tagName, '1'); expect(handlerList[2].tagName, '2');
expect(handlerList[1].tagName, '3');
expect(handlerList[2].tagName, '2');
}); });
} }

View File

@ -5,33 +5,79 @@ import 'helpers/xml.dart';
void main() { void main() {
test('Test stringxml', () { test('Test stringxml', () {
final child = XMLNode(tag: 'uwu', attributes: { 'strength': 10 }); final child = XMLNode(tag: 'uwu', attributes: {'strength': 10});
final stanza = XMLNode.xmlns(tag: 'uwu-meter', xmlns: 'uwu', children: [ child ]); final stanza =
expect(XMLNode(tag: 'iq', attributes: {'xmlns': 'uwu'}).toXml(), "<iq xmlns='uwu' />"); XMLNode.xmlns(tag: 'uwu-meter', xmlns: 'uwu', children: [child]);
expect(XMLNode.xmlns(tag: 'iq', xmlns: 'uwu', attributes: {'how': 'uwu'}).toXml(), "<iq xmlns='uwu' how='uwu' />"); expect(
expect(stanza.toXml(), "<uwu-meter xmlns='uwu'><uwu strength=10 /></uwu-meter>"); XMLNode(tag: 'iq', attributes: {'xmlns': 'uwu'}).toXml(),
"<iq xmlns='uwu' />",
);
expect(
XMLNode.xmlns(tag: 'iq', xmlns: 'uwu', attributes: {'how': 'uwu'})
.toXml(),
"<iq xmlns='uwu' how='uwu' />",
);
expect(
stanza.toXml(),
"<uwu-meter xmlns='uwu'><uwu strength=10 /></uwu-meter>",
);
expect(StreamHeaderNonza('uwu.server').toXml(), "<stream:stream xmlns='jabber:client' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' to='uwu.server' xml:lang='en'>"); expect(
StreamHeaderNonza('uwu.server').toXml(),
"<stream:stream xmlns='jabber:client' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' to='uwu.server' xml:lang='en'>",
);
expect(XMLNode(tag: 'text', attributes: {}, text: 'hallo').toXml(), '<text>hallo</text>'); expect(
expect(XMLNode(tag: 'text', attributes: { 'world': 'no' }, text: 'hallo').toXml(), "<text world='no'>hallo</text>"); XMLNode(tag: 'text', attributes: {}, text: 'hallo').toXml(),
expect(XMLNode(tag: 'text', attributes: {}, text: 'hallo').toXml(), '<text>hallo</text>'); '<text>hallo</text>',
expect(XMLNode(tag: 'text', attributes: {}, text: 'test').innerText(), 'test'); );
expect(
XMLNode(tag: 'text', attributes: {'world': 'no'}, text: 'hallo').toXml(),
"<text world='no'>hallo</text>",
);
expect(
XMLNode(tag: 'text', attributes: {}, text: 'hallo').toXml(),
'<text>hallo</text>',
);
expect(
XMLNode(tag: 'text', attributes: {}, text: 'test').innerText(),
'test',
);
}); });
test('Test XmlElement', () { test('Test XmlElement', () {
expect(XMLNode.fromXmlElement(XmlDocument.parse("<root owo='uwu' />").firstElementChild!).toXml(), "<root owo='uwu' />"); expect(
XMLNode.fromXmlElement(
XmlDocument.parse("<root owo='uwu' />").firstElementChild!,
).toXml(),
"<root owo='uwu' />",
);
}); });
test('Test the find functions', () { test('Test the find functions', () {
final node1 = XMLNode.fromString('<message><a xmlns="a" /><body>Hallo</body></message>'); final node1 = XMLNode.fromString(
'<message><a xmlns="a" /><body>Hallo</body></message>',
);
expect(compareXMLNodes(node1.firstTag('body')!, XMLNode.fromString('<body>Hallo</body>')), true); expect(
expect(compareXMLNodes(node1.firstTagByXmlns('a')!, XMLNode.fromString('<a xmlns="a" />')), true); compareXMLNodes(
node1.firstTag('body')!,
XMLNode.fromString('<body>Hallo</body>'),
),
true,
);
expect(
compareXMLNodes(
node1.firstTagByXmlns('a')!,
XMLNode.fromString('<a xmlns="a" />'),
),
true,
);
}); });
test('Test compareXMLNodes', () { test('Test compareXMLNodes', () {
final node1 = XMLNode.fromString(''' final node1 = XMLNode.fromString(
'''
<iq type='set' id='0327c373-2e34-46bd-ab7f-1274a6f7095f' to='pubsub.server.example.org' from='testuser@example.org/MU29eEZn' xmlns='jabber:client'> <iq type='set' id='0327c373-2e34-46bd-ab7f-1274a6f7095f' to='pubsub.server.example.org' from='testuser@example.org/MU29eEZn' xmlns='jabber:client'>
<pubsub xmlns='http://jabber.org/protocol/pubsub'> <pubsub xmlns='http://jabber.org/protocol/pubsub'>
<publish node='princely_musings'> <publish node='princely_musings'>
@ -75,6 +121,12 @@ void main() {
</iq> </iq>
'''); ''');
expect(compareXMLNodes(node1, node2, ignoreId: true), false); expect(
compareXMLNodes(
node1,
node2,
),
false,
);
}); });
} }

View File

@ -1,30 +1,29 @@
import 'package:test/test.dart';
import 'package:moxxmpp/src/util/wait.dart'; import 'package:moxxmpp/src/util/wait.dart';
import 'package:test/test.dart';
void main() { void main() {
test('Test adding and resolving', () async { test('Test adding and resolving', () async {
// ID -> Milliseconds since epoch // ID -> Milliseconds since epoch
final tracker = WaitForTracker<int, int>(); final tracker = WaitForTracker<int, int>();
int r2 = 0; var r2 = 0;
int r3 = 0; var r3 = 0;
// Queue some jobs // Queue some jobs
final r1 = await tracker.waitFor(0); final r1 = await tracker.waitFor(0);
expect(r1, null); expect(r1, null);
tracker // ignore: unawaited_futures
.waitFor(0) tracker.waitFor(0).then((result) async {
.then((result) async { expect(result != null, true);
expect(result != null, true); r2 = await result!;
r2 = await result!; });
});
tracker // ignore: unawaited_futures
.waitFor(0) tracker.waitFor(0).then((result) async {
.then((result) async { expect(result != null, true);
expect(result != null, true); r3 = await result!;
r3 = await result!; });
});
final c = await tracker.waitFor(1); final c = await tracker.waitFor(1);
expect(c, null); expect(c, null);

View File

@ -3,11 +3,15 @@ import 'package:test/test.dart';
void main() { void main() {
test('Parsing', () { test('Parsing', () {
const testData = "<x xmlns='jabber:x:data' type='result'><field var='FORM_TYPE' type='hidden'><value>urn:xmpp:dataforms:softwareinfo</value></field><field var='ip_version' type='text-multi' ><value>ipv4</value><value>ipv6</value></field><field var='os'><value>Mac</value></field><field var='os_version'><value>10.5.1</value></field><field var='software'><value>Psi</value></field><field var='software_version'><value>0.11</value></field></x>"; const testData =
"<x xmlns='jabber:x:data' type='result'><field var='FORM_TYPE' type='hidden'><value>urn:xmpp:dataforms:softwareinfo</value></field><field var='ip_version' type='text-multi' ><value>ipv4</value><value>ipv6</value></field><field var='os'><value>Mac</value></field><field var='os_version'><value>10.5.1</value></field><field var='software'><value>Psi</value></field><field var='software_version'><value>0.11</value></field></x>";
final form = parseDataForm(XMLNode.fromString(testData)); final form = parseDataForm(XMLNode.fromString(testData));
expect(form.getFieldByVar('FORM_TYPE')?.values.first, 'urn:xmpp:dataforms:softwareinfo'); expect(
expect(form.getFieldByVar('ip_version')?.values, [ 'ipv4', 'ipv6' ]); form.getFieldByVar('FORM_TYPE')?.values.first,
'urn:xmpp:dataforms:softwareinfo',
);
expect(form.getFieldByVar('ip_version')?.values, ['ipv4', 'ipv6']);
expect(form.getFieldByVar('os')?.values.first, 'Mac'); expect(form.getFieldByVar('os')?.values.first, 'Mac');
expect(form.getFieldByVar('os_version')?.values.first, '10.5.1'); expect(form.getFieldByVar('os_version')?.values.first, '10.5.1');
expect(form.getFieldByVar('software')?.values.first, 'Psi'); expect(form.getFieldByVar('software')?.values.first, 'Psi');

View File

@ -28,7 +28,7 @@ void main() {
), ),
StringExpectation( StringExpectation(
"<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>AHBvbHlub21kaXZpc2lvbgBhYWFh</auth>", "<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>AHBvbHlub21kaXZpc2lvbgBhYWFh</auth>",
'<success xmlns="urn:ietf:params:xml:ns:xmpp-sasl" />' '<success xmlns="urn:ietf:params:xml:ns:xmpp-sasl" />',
), ),
StringExpectation( 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' to='test.server' xml:lang='en'>",
@ -64,54 +64,55 @@ void main() {
"<iq type='get' id='ec325efc-9924-4c48-93f8-ed34a2b0e5fc' to='romeo@montague.lit/orchard' from='polynomdivision@test.server/MU29eEZn' xmlns='jabber:client'><query xmlns='http://jabber.org/protocol/disco#info' /></iq>", "<iq type='get' id='ec325efc-9924-4c48-93f8-ed34a2b0e5fc' to='romeo@montague.lit/orchard' from='polynomdivision@test.server/MU29eEZn' xmlns='jabber:client'><query xmlns='http://jabber.org/protocol/disco#info' /></iq>",
'', '',
ignoreId: true, ignoreId: true,
adjustId: false,
), ),
], ],
); );
final XmppConnection conn = XmppConnection( final conn = XmppConnection(
TestingReconnectionPolicy(), TestingReconnectionPolicy(),
AlwaysConnectedConnectivityManager(), AlwaysConnectedConnectivityManager(),
fakeSocket, fakeSocket,
); )..setConnectionSettings(
conn.setConnectionSettings(ConnectionSettings( ConnectionSettings(
jid: JID.fromString('polynomdivision@test.server'), jid: JID.fromString('polynomdivision@test.server'),
password: 'aaaa', password: 'aaaa',
useDirectTLS: true, useDirectTLS: true,
),); ),
conn.registerManagers([ );
await conn.registerManagers([
PresenceManager(), PresenceManager(),
RosterManager(TestingRosterStateManager(null, [])), RosterManager(TestingRosterStateManager(null, [])),
DiscoManager([]), DiscoManager([]),
PingManager(), PingManager(),
EntityCapabilitiesManager('http://moxxmpp.example'), EntityCapabilitiesManager('http://moxxmpp.example'),
]); ]);
conn.registerFeatureNegotiators( conn.registerFeatureNegotiators([
[ SaslPlainNegotiator(),
SaslPlainNegotiator(), SaslScramNegotiator(10, '', '', ScramHashType.sha512),
SaslScramNegotiator(10, '', '', ScramHashType.sha512), ResourceBindingNegotiator(),
ResourceBindingNegotiator(), ]);
]
);
final disco = conn.getManagerById<DiscoManager>(discoManager)!; final disco = conn.getManagerById<DiscoManager>(discoManager)!;
await conn.connect(); await conn.connect();
await Future.delayed(const Duration(seconds: 3)); await Future<void>.delayed(const Duration(seconds: 3));
final jid = JID.fromString('romeo@montague.lit/orchard'); final jid = JID.fromString('romeo@montague.lit/orchard');
final result1 = disco.discoInfoQuery(jid.toString()); final result1 = disco.discoInfoQuery(jid.toString());
final result2 = disco.discoInfoQuery(jid.toString()); final result2 = disco.discoInfoQuery(jid.toString());
await Future.delayed(const Duration(seconds: 1)); await Future<void>.delayed(const Duration(seconds: 1));
expect( expect(
disco.infoTracker.getRunningTasks(DiscoCacheKey(jid.toString(), null)).length, disco.infoTracker
.getRunningTasks(DiscoCacheKey(jid.toString(), null))
.length,
1, 1,
); );
fakeSocket.injectRawXml("<iq type='result' id='${fakeSocket.lastId!}' from='romeo@montague.lit/orchard' to='polynomdivision@test.server/MU29eEZn' xmlns='jabber:client'><query xmlns='http://jabber.org/protocol/disco#info' /></iq>"); fakeSocket.injectRawXml(
"<iq type='result' id='${fakeSocket.lastId!}' from='romeo@montague.lit/orchard' to='polynomdivision@test.server/MU29eEZn' xmlns='jabber:client'><query xmlns='http://jabber.org/protocol/disco#info' /></iq>",
await Future.delayed(const Duration(seconds: 2)); );
await Future<void>.delayed(const Duration(seconds: 2));
expect(fakeSocket.getState(), 6); expect(fakeSocket.getState(), 6);
expect(await result1, await result2); expect(await result1, await result2);
expect(disco.infoTracker.hasTasksRunning(), false); expect(disco.infoTracker.hasTasksRunning(), false);

View File

@ -11,16 +11,18 @@ class StubbedDiscoManager extends DiscoManager {
final bool _itemError; final bool _itemError;
@override @override
Future<Result<DiscoError, DiscoInfo>> discoInfoQuery(String entity, { String? node, bool shouldEncrypt = true }) async { Future<Result<DiscoError, DiscoInfo>> discoInfoQuery(
String entity, {
String? node,
bool shouldEncrypt = true,
}) async {
final result = DiscoInfo.fromQuery( final result = DiscoInfo.fromQuery(
XMLNode.fromString( XMLNode.fromString('''
'''
<query xmlns='http://jabber.org/protocol/disco#info'> <query xmlns='http://jabber.org/protocol/disco#info'>
<identity category='pubsub' type='service' /> <identity category='pubsub' type='service' />
<feature var="http://jabber.org/protocol/pubsub" /> <feature var="http://jabber.org/protocol/pubsub" />
<feature var="http://jabber.org/protocol/pubsub#multi-items" /> <feature var="http://jabber.org/protocol/pubsub#multi-items" />
</query>''' </query>'''),
),
JID.fromString('pubsub.server.example.org'), JID.fromString('pubsub.server.example.org'),
); );
@ -28,7 +30,11 @@ class StubbedDiscoManager extends DiscoManager {
} }
@override @override
Future<Result<DiscoError, List<DiscoItem>>> discoItemsQuery(String entity, {String? node, bool shouldEncrypt = true}) async { Future<Result<DiscoError, List<DiscoItem>>> discoItemsQuery(
String entity, {
String? node,
bool shouldEncrypt = true,
}) async {
if (_itemError) { if (_itemError) {
return Result( return Result(
UnknownDiscoError(), UnknownDiscoError(),
@ -39,13 +45,15 @@ class StubbedDiscoManager extends DiscoManager {
); );
} }
} }
void main() { void main() {
initLogger(); initLogger();
test('Test pre-processing with pubsub#max_items when the server does not support it (1/2)', () async { test(
'Test pre-processing with pubsub#max_items when the server does not support it (1/2)',
() async {
final manager = PubSubManager(); final manager = PubSubManager();
final TestingManagerHolder tm = TestingManagerHolder(); final tm = TestingManagerHolder();
await tm.register(StubbedDiscoManager(false)); await tm.register(StubbedDiscoManager(false));
await tm.register(manager); await tm.register(manager);
@ -58,9 +66,11 @@ void main() {
expect(result.maxItems, '1'); expect(result.maxItems, '1');
}); });
test('Test pre-processing with pubsub#max_items when the server does not support it (2/2)', () async { test(
'Test pre-processing with pubsub#max_items when the server does not support it (2/2)',
() async {
final manager = PubSubManager(); final manager = PubSubManager();
final TestingManagerHolder tm = TestingManagerHolder(); final tm = TestingManagerHolder();
await tm.register(StubbedDiscoManager(true)); await tm.register(StubbedDiscoManager(true));
await tm.register(manager); await tm.register(manager);
@ -73,7 +83,9 @@ void main() {
expect(result.maxItems, '1'); expect(result.maxItems, '1');
}); });
test('Test publishing with pubsub#max_items when the server does not support it', () async { test(
'Test publishing with pubsub#max_items when the server does not support it',
() async {
final socket = StubTCPSocket.authenticated( final socket = StubTCPSocket.authenticated(
TestingManagerHolder.settings, TestingManagerHolder.settings,
[ [
@ -158,24 +170,26 @@ void main() {
RosterManager(TestingRosterStateManager(null, [])), RosterManager(TestingRosterStateManager(null, [])),
PingManager(), PingManager(),
]); ]);
connection..registerFeatureNegotiators([ connection
SaslPlainNegotiator(), ..registerFeatureNegotiators([
ResourceBindingNegotiator(), SaslPlainNegotiator(),
]) ResourceBindingNegotiator(),
..setConnectionSettings(TestingManagerHolder.settings); ])
..setConnectionSettings(TestingManagerHolder.settings);
await connection.connect( await connection.connect(
waitUntilLogin: true, waitUntilLogin: true,
); );
final item = XMLNode(tag: "test-item"); final item = XMLNode(tag: 'test-item');
final result = await connection.getManagerById<PubSubManager>(pubsubManager)!.publish( final result =
'pubsub.server.example.org', await connection.getManagerById<PubSubManager>(pubsubManager)!.publish(
'princely_musings', 'pubsub.server.example.org',
item, 'princely_musings',
id: 'current', item,
options: const PubSubPublishOptions(maxItems: 'max'), id: 'current',
); options: const PubSubPublishOptions(maxItems: 'max'),
);
expect(result.isType<bool>(), true); expect(result.isType<bool>(), true);
}); });
} }

View File

@ -5,20 +5,20 @@ import 'package:test/test.dart';
void main() { void main() {
test('Test XEP example', () async { test('Test XEP example', () async {
final data = DiscoInfo( final data = DiscoInfo(
[ const [
'http://jabber.org/protocol/caps', 'http://jabber.org/protocol/caps',
'http://jabber.org/protocol/disco#info', 'http://jabber.org/protocol/disco#info',
'http://jabber.org/protocol/disco#items', 'http://jabber.org/protocol/disco#items',
'http://jabber.org/protocol/muc' 'http://jabber.org/protocol/muc'
], ],
[ const [
Identity( Identity(
category: 'client', category: 'client',
type: 'pc', type: 'pc',
name: 'Exodus 0.9.1', name: 'Exodus 0.9.1',
) )
], ],
[], const [],
null, null,
JID.fromString('some@user.local/test'), JID.fromString('some@user.local/test'),
); );
@ -28,29 +28,30 @@ void main() {
}); });
test('Test complex generation example', () async { test('Test complex generation example', () async {
const extDiscoDataString = "<x xmlns='jabber:x:data' type='result'><field var='FORM_TYPE' type='hidden'><value>urn:xmpp:dataforms:softwareinfo</value></field><field var='ip_version' type='text-multi' ><value>ipv4</value><value>ipv6</value></field><field var='os'><value>Mac</value></field><field var='os_version'><value>10.5.1</value></field><field var='software'><value>Psi</value></field><field var='software_version'><value>0.11</value></field></x>"; const extDiscoDataString =
"<x xmlns='jabber:x:data' type='result'><field var='FORM_TYPE' type='hidden'><value>urn:xmpp:dataforms:softwareinfo</value></field><field var='ip_version' type='text-multi' ><value>ipv4</value><value>ipv6</value></field><field var='os'><value>Mac</value></field><field var='os_version'><value>10.5.1</value></field><field var='software'><value>Psi</value></field><field var='software_version'><value>0.11</value></field></x>";
final data = DiscoInfo( final data = DiscoInfo(
[ const [
'http://jabber.org/protocol/caps', 'http://jabber.org/protocol/caps',
'http://jabber.org/protocol/disco#info', 'http://jabber.org/protocol/disco#info',
'http://jabber.org/protocol/disco#items', 'http://jabber.org/protocol/disco#items',
'http://jabber.org/protocol/muc' 'http://jabber.org/protocol/muc'
], ],
[ const [
const Identity( Identity(
category: 'client', category: 'client',
type: 'pc', type: 'pc',
name: 'Psi 0.11', name: 'Psi 0.11',
lang: 'en', lang: 'en',
), ),
const Identity( Identity(
category: 'client', category: 'client',
type: 'pc', type: 'pc',
name: 'Ψ 0.11', name: 'Ψ 0.11',
lang: 'el', lang: 'el',
), ),
], ],
[ parseDataForm(XMLNode.fromString(extDiscoDataString)) ], [parseDataForm(XMLNode.fromString(extDiscoDataString))],
null, null,
JID.fromString('some@user.local/test'), JID.fromString('some@user.local/test'),
); );
@ -58,9 +59,9 @@ void main() {
final hash = await calculateCapabilityHash(data, Sha1()); final hash = await calculateCapabilityHash(data, Sha1());
expect(hash, 'q07IKJEyjvHSyhy//CH0CxmKi8w='); expect(hash, 'q07IKJEyjvHSyhy//CH0CxmKi8w=');
}); });
test('Test Gajim capability hash computation', () async { test('Test Gajim capability hash computation', () async {
// TODO: This one fails // TODO(Unknown): This one fails
/* /*
final data = DiscoInfo( final data = DiscoInfo(
features: [ features: [
@ -120,7 +121,7 @@ void main() {
test('Test Conversations hash computation', () async { test('Test Conversations hash computation', () async {
final data = DiscoInfo( final data = DiscoInfo(
[ const [
'eu.siacs.conversations.axolotl.devicelist+notify', 'eu.siacs.conversations.axolotl.devicelist+notify',
'http://jabber.org/protocol/caps', 'http://jabber.org/protocol/caps',
'http://jabber.org/protocol/chatstates', 'http://jabber.org/protocol/chatstates',
@ -152,14 +153,14 @@ void main() {
'urn:xmpp:receipts', 'urn:xmpp:receipts',
'urn:xmpp:time' 'urn:xmpp:time'
], ],
[ const [
Identity( Identity(
category: 'client', category: 'client',
type: 'phone', type: 'phone',
name: 'Conversations', name: 'Conversations',
) )
], ],
[], const [],
null, null,
JID.fromString('user@server.local/test'), JID.fromString('user@server.local/test'),
); );

View File

@ -3,21 +3,54 @@ import 'package:test/test.dart';
import '../helpers/logging.dart'; import '../helpers/logging.dart';
import '../helpers/xmpp.dart'; import '../helpers/xmpp.dart';
Future<void> runIncomingStanzaHandlers(StreamManagementManager man, Stanza stanza) async { Future<void> runIncomingStanzaHandlers(
StreamManagementManager man,
Stanza stanza,
) async {
for (final handler in man.getIncomingPreStanzaHandlers()) { for (final handler in man.getIncomingPreStanzaHandlers()) {
if (handler.matches(stanza)) await handler.callback(stanza, StanzaHandlerData(false, false, null, stanza)); if (handler.matches(stanza)) {
await handler.callback(
stanza,
StanzaHandlerData(
false,
false,
null,
stanza,
),
);
}
} }
} }
Future<void> runOutgoingStanzaHandlers(StreamManagementManager man, Stanza stanza) async { Future<void> runOutgoingStanzaHandlers(
StreamManagementManager man,
Stanza stanza,
) async {
for (final handler in man.getOutgoingPostStanzaHandlers()) { for (final handler in man.getOutgoingPostStanzaHandlers()) {
if (handler.matches(stanza)) await handler.callback(stanza, StanzaHandlerData(false, false, null, stanza)); if (handler.matches(stanza)) {
await handler.callback(
stanza,
StanzaHandlerData(
false,
false,
null,
stanza,
),
);
}
} }
} }
XmppManagerAttributes mkAttributes(void Function(Stanza) callback) { XmppManagerAttributes mkAttributes(void Function(Stanza) callback) {
return XmppManagerAttributes( return XmppManagerAttributes(
sendStanza: (stanza, { StanzaFromType addFrom = StanzaFromType.full, bool addId = true, bool awaitable = true, bool encrypted = false, bool forceEncryption = false, }) async { sendStanza: (
stanza, {
StanzaFromType addFrom = StanzaFromType.full,
bool addId = true,
bool awaitable = true,
bool encrypted = false,
bool forceEncryption = false,
}) async {
callback(stanza); callback(stanza);
return Stanza.message(); return Stanza.message();
@ -33,12 +66,22 @@ XmppManagerAttributes mkAttributes(void Function(Stanza) callback) {
isFeatureSupported: (_) => false, isFeatureSupported: (_) => false,
getFullJID: () => JID.fromString('hallo@example.server/uwu'), getFullJID: () => JID.fromString('hallo@example.server/uwu'),
getSocket: () => StubTCPSocket([]), getSocket: () => StubTCPSocket([]),
getConnection: () => XmppConnection(TestingReconnectionPolicy(), AlwaysConnectedConnectivityManager(), StubTCPSocket([])), getConnection: () => XmppConnection(
TestingReconnectionPolicy(),
AlwaysConnectedConnectivityManager(),
StubTCPSocket([]),
),
getNegotiatorById: getNegotiatorNullStub, getNegotiatorById: getNegotiatorNullStub,
); );
} }
XMLNode mkAck(int h) => XMLNode.xmlns(tag: 'a', xmlns: 'urn:xmpp:sm:3', attributes: { 'h': h.toString() }); XMLNode mkAck(int h) => XMLNode.xmlns(
tag: 'a',
xmlns: 'urn:xmpp:sm:3',
attributes: {
'h': h.toString(),
},
);
void main() { void main() {
initLogger(); initLogger();
@ -50,8 +93,7 @@ void main() {
test('Test stream with SM enablement', () async { test('Test stream with SM enablement', () async {
final attributes = mkAttributes((_) {}); final attributes = mkAttributes((_) {});
final manager = StreamManagementManager(); final manager = StreamManagementManager()..register(attributes);
manager.register(attributes);
// [...] // [...]
// <enable /> // <enabled /> // <enable /> // <enabled />
@ -77,10 +119,10 @@ void main() {
group('Acking', () { group('Acking', () {
test('Test completely clearing the queue', () async { test('Test completely clearing the queue', () async {
final attributes = mkAttributes((_) {}); final attributes = mkAttributes((_) {});
final manager = StreamManagementManager(); final manager = StreamManagementManager()..register(attributes);
manager.register(attributes);
await manager.onXmppEvent(StreamManagementEnabledEvent(resource: 'hallo')); await manager
.onXmppEvent(StreamManagementEnabledEvent(resource: 'hallo'));
// Send a stanza 5 times // Send a stanza 5 times
for (var i = 0; i < 5; i++) { for (var i = 0; i < 5; i++) {
@ -93,11 +135,12 @@ void main() {
}); });
test('Test partially clearing the queue', () async { test('Test partially clearing the queue', () async {
final attributes = mkAttributes((_) {}); final attributes = mkAttributes((_) {});
final manager = StreamManagementManager(); final manager = StreamManagementManager()..register(attributes);
manager.register(attributes);
await manager.onXmppEvent(
StreamManagementEnabledEvent(resource: 'hallo'),
);
await manager.onXmppEvent(StreamManagementEnabledEvent(resource: 'hallo'));
// Send a stanza 5 times // Send a stanza 5 times
for (var i = 0; i < 5; i++) { for (var i = 0; i < 5; i++) {
await runOutgoingStanzaHandlers(manager, stanza); await runOutgoingStanzaHandlers(manager, stanza);
@ -109,11 +152,12 @@ void main() {
}); });
test('Send an ack with h > c2s', () async { test('Send an ack with h > c2s', () async {
final attributes = mkAttributes((_) {}); final attributes = mkAttributes((_) {});
final manager = StreamManagementManager(); final manager = StreamManagementManager()..register(attributes);
manager.register(attributes);
await manager.onXmppEvent(
StreamManagementEnabledEvent(resource: 'hallo'),
);
await manager.onXmppEvent(StreamManagementEnabledEvent(resource: 'hallo'));
// Send a stanza 5 times // Send a stanza 5 times
for (var i = 0; i < 5; i++) { for (var i = 0; i < 5; i++) {
await runOutgoingStanzaHandlers(manager, stanza); await runOutgoingStanzaHandlers(manager, stanza);
@ -126,11 +170,12 @@ void main() {
}); });
test('Send an ack with h < c2s', () async { test('Send an ack with h < c2s', () async {
final attributes = mkAttributes((_) {}); final attributes = mkAttributes((_) {});
final manager = StreamManagementManager(); final manager = StreamManagementManager()..register(attributes);
manager.register(attributes);
await manager.onXmppEvent(
StreamManagementEnabledEvent(resource: 'hallo'),
);
await manager.onXmppEvent(StreamManagementEnabledEvent(resource: 'hallo'));
// Send a stanza 5 times // Send a stanza 5 times
for (var i = 0; i < 5; i++) { for (var i = 0; i < 5; i++) {
await runOutgoingStanzaHandlers(manager, stanza); await runOutgoingStanzaHandlers(manager, stanza);
@ -146,9 +191,10 @@ void main() {
group('Counting acks', () { group('Counting acks', () {
test('Sending all pending acks at once', () async { test('Sending all pending acks at once', () async {
final attributes = mkAttributes((_) {}); final attributes = mkAttributes((_) {});
final manager = StreamManagementManager(); final manager = StreamManagementManager()..register(attributes);
manager.register(attributes); await manager.onXmppEvent(
await manager.onXmppEvent(StreamManagementEnabledEvent(resource: 'hallo')); StreamManagementEnabledEvent(resource: 'hallo'),
);
// Send a stanza 5 times // Send a stanza 5 times
for (var i = 0; i < 5; i++) { for (var i = 0; i < 5; i++) {
@ -162,9 +208,10 @@ void main() {
}); });
test('Sending partial pending acks at once', () async { test('Sending partial pending acks at once', () async {
final attributes = mkAttributes((_) {}); final attributes = mkAttributes((_) {});
final manager = StreamManagementManager(); final manager = StreamManagementManager()..register(attributes);
manager.register(attributes); await manager.onXmppEvent(
await manager.onXmppEvent(StreamManagementEnabledEvent(resource: 'hallo')); StreamManagementEnabledEvent(resource: 'hallo'),
);
// Send a stanza 5 times // Send a stanza 5 times
for (var i = 0; i < 5; i++) { for (var i = 0; i < 5; i++) {
@ -177,12 +224,12 @@ void main() {
expect(await manager.getPendingAcks(), 2); expect(await manager.getPendingAcks(), 2);
}); });
test('Test counting incoming stanzas for which handlers end early', () async { test('Test counting incoming stanzas for which handlers end early',
final fakeSocket = StubTCPSocket( () async {
[ final fakeSocket = StubTCPSocket([
StringExpectation( 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' to='test.server' xml:lang='en'>",
''' '''
<stream:stream <stream:stream
xmlns="jabber:client" xmlns="jabber:client"
version="1.0" version="1.0"
@ -194,14 +241,14 @@ void main() {
<mechanism>PLAIN</mechanism> <mechanism>PLAIN</mechanism>
</mechanisms> </mechanisms>
</stream:features>''', </stream:features>''',
), ),
StringExpectation( StringExpectation(
"<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>AHBvbHlub21kaXZpc2lvbgBhYWFh</auth>", "<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>AHBvbHlub21kaXZpc2lvbgBhYWFh</auth>",
'<success xmlns="urn:ietf:params:xml:ns:xmpp-sasl" />' '<success xmlns="urn:ietf:params:xml:ns:xmpp-sasl" />',
), ),
StringExpectation( 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' to='test.server' xml:lang='en'>",
''' '''
<stream:stream <stream:stream
xmlns="jabber:client" xmlns="jabber:client"
version="1.0" version="1.0"
@ -219,31 +266,31 @@ void main() {
<sm xmlns="urn:xmpp:sm:3"/> <sm xmlns="urn:xmpp:sm:3"/>
</stream:features> </stream:features>
''', ''',
), ),
StanzaExpectation( StanzaExpectation(
'<iq xmlns="jabber:client" type="set" id="a"><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"/></iq>', '<iq xmlns="jabber:client" type="set" id="a"><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"/></iq>',
'<iq xmlns="jabber:client" type="result" id="a"><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"><jid>polynomdivision@test.server/MU29eEZn</jid></bind></iq>', '<iq xmlns="jabber:client" type="result" id="a"><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"><jid>polynomdivision@test.server/MU29eEZn</jid></bind></iq>',
ignoreId: true, ignoreId: true,
), ),
StringExpectation( StringExpectation(
"<enable xmlns='urn:xmpp:sm:3' resume='true' />", "<enable xmlns='urn:xmpp:sm:3' resume='true' />",
'<enabled xmlns="urn:xmpp:sm:3" id="some-long-sm-id" resume="true" />', '<enabled xmlns="urn:xmpp:sm:3" id="some-long-sm-id" resume="true" />',
), ),
] ]);
);
final XmppConnection conn = XmppConnection( final conn = XmppConnection(
TestingReconnectionPolicy(), TestingReconnectionPolicy(),
AlwaysConnectedConnectivityManager(), AlwaysConnectedConnectivityManager(),
fakeSocket, fakeSocket,
); )..setConnectionSettings(
conn.setConnectionSettings(ConnectionSettings( ConnectionSettings(
jid: JID.fromString('polynomdivision@test.server'), jid: JID.fromString('polynomdivision@test.server'),
password: 'aaaa', password: 'aaaa',
useDirectTLS: true, useDirectTLS: true,
),); ),
);
final sm = StreamManagementManager(); final sm = StreamManagementManager();
conn.registerManagers([ await conn.registerManagers([
PresenceManager(), PresenceManager(),
RosterManager(TestingRosterStateManager('', [])), RosterManager(TestingRosterStateManager('', [])),
DiscoManager([]), DiscoManager([]),
@ -252,20 +299,21 @@ void main() {
CarbonsManager()..forceEnable(), CarbonsManager()..forceEnable(),
EntityCapabilitiesManager('http://moxxmpp.example'), EntityCapabilitiesManager('http://moxxmpp.example'),
]); ]);
conn.registerFeatureNegotiators( conn.registerFeatureNegotiators([
[ SaslPlainNegotiator(),
SaslPlainNegotiator(), ResourceBindingNegotiator(),
ResourceBindingNegotiator(), StreamManagementNegotiator(),
StreamManagementNegotiator(), ]);
]
);
await conn.connect(); await conn.connect(
await Future.delayed(const Duration(seconds: 3)); waitUntilLogin: true,
expect(fakeSocket.getState(), 6); );
expect(fakeSocket.getState(), 5);
expect(await conn.getConnectionState(), XmppConnectionState.connected); expect(await conn.getConnectionState(), XmppConnectionState.connected);
expect( expect(
conn.getManagerById<StreamManagementManager>(smManager)!.isStreamManagementEnabled(), conn
.getManagerById<StreamManagementManager>(smManager)!
.isStreamManagementEnabled(),
true, true,
); );
@ -289,16 +337,15 @@ void main() {
</message> </message>
'''); ''');
await Future.delayed(const Duration(seconds: 2)); await Future<void>.delayed(const Duration(seconds: 2));
expect(sm.state.s2c, 1); expect(sm.state.s2c, 1);
}); });
test('Test counting incoming stanzas that are awaited', () async { test('Test counting incoming stanzas that are awaited', () async {
final fakeSocket = StubTCPSocket( final fakeSocket = StubTCPSocket([
[ StringExpectation(
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' to='test.server' xml:lang='en'>", '''
'''
<stream:stream <stream:stream
xmlns="jabber:client" xmlns="jabber:client"
version="1.0" version="1.0"
@ -310,14 +357,14 @@ void main() {
<mechanism>PLAIN</mechanism> <mechanism>PLAIN</mechanism>
</mechanisms> </mechanisms>
</stream:features>''', </stream:features>''',
), ),
StringExpectation( StringExpectation(
"<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>AHBvbHlub21kaXZpc2lvbgBhYWFh</auth>", "<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>AHBvbHlub21kaXZpc2lvbgBhYWFh</auth>",
'<success xmlns="urn:ietf:params:xml:ns:xmpp-sasl" />' '<success xmlns="urn:ietf:params:xml:ns:xmpp-sasl" />',
), ),
StringExpectation( 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' to='test.server' xml:lang='en'>",
''' '''
<stream:stream <stream:stream
xmlns="jabber:client" xmlns="jabber:client"
version="1.0" version="1.0"
@ -335,63 +382,64 @@ void main() {
<sm xmlns="urn:xmpp:sm:3"/> <sm xmlns="urn:xmpp:sm:3"/>
</stream:features> </stream:features>
''', ''',
), ),
StanzaExpectation( StanzaExpectation(
'<iq xmlns="jabber:client" type="set" id="a"><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"/></iq>', '<iq xmlns="jabber:client" type="set" id="a"><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"/></iq>',
'<iq xmlns="jabber:client" type="result" id="a"><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"><jid>polynomdivision@test.server/MU29eEZn</jid></bind></iq>', '<iq xmlns="jabber:client" type="result" id="a"><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"><jid>polynomdivision@test.server/MU29eEZn</jid></bind></iq>',
ignoreId: true, ignoreId: true,
), ),
StringExpectation( StringExpectation(
"<enable xmlns='urn:xmpp:sm:3' resume='true' />", "<enable xmlns='urn:xmpp:sm:3' resume='true' />",
'<enabled xmlns="urn:xmpp:sm:3" id="some-long-sm-id" resume="true" />', '<enabled xmlns="urn:xmpp:sm:3" id="some-long-sm-id" resume="true" />',
), ),
StringExpectation( // StringExpectation(
"<presence xmlns='jabber:client' from='polynomdivision@test.server/MU29eEZn'><show>chat</show><c xmlns='http://jabber.org/protocol/caps' hash='sha-1' node='http://moxxmpp.example' ver='3QvQ2RAy45XBDhArjxy/vEWMl+E=' /></presence>", // "<presence xmlns='jabber:client' from='polynomdivision@test.server/MU29eEZn'><show>chat</show></presence>",
'<iq type="result" />', // '<iq type="result" />',
), // ),
StanzaExpectation( StanzaExpectation(
"<iq to='user@example.com' type='get' id='a' xmlns='jabber:client' />", "<iq to='user@example.com' type='get' id='a' xmlns='jabber:client' />",
"<iq from='user@example.com' type='result' id='a' />", "<iq from='user@example.com' type='result' id='a' />",
ignoreId: true, ignoreId: true,
adjustId: true, adjustId: true,
), ),
] ]);
);
final XmppConnection conn = XmppConnection( final conn = XmppConnection(
TestingReconnectionPolicy(), TestingReconnectionPolicy(),
AlwaysConnectedConnectivityManager(), AlwaysConnectedConnectivityManager(),
fakeSocket, fakeSocket,
); )..setConnectionSettings(
conn.setConnectionSettings(ConnectionSettings( ConnectionSettings(
jid: JID.fromString('polynomdivision@test.server'), jid: JID.fromString('polynomdivision@test.server'),
password: 'aaaa', password: 'aaaa',
useDirectTLS: true, useDirectTLS: true,
),); ),
);
final sm = StreamManagementManager(); final sm = StreamManagementManager();
conn.registerManagers([ await conn.registerManagers([
PresenceManager(), PresenceManager(),
RosterManager(TestingRosterStateManager('', [])), RosterManager(TestingRosterStateManager('', [])),
DiscoManager([]), DiscoManager([]),
PingManager(), PingManager(),
sm, sm,
CarbonsManager()..forceEnable(), CarbonsManager()..forceEnable(),
EntityCapabilitiesManager('http://moxxmpp.example'), //EntityCapabilitiesManager('http://moxxmpp.example'),
]);
conn.registerFeatureNegotiators([
SaslPlainNegotiator(),
ResourceBindingNegotiator(),
StreamManagementNegotiator(),
]); ]);
conn.registerFeatureNegotiators(
[
SaslPlainNegotiator(),
ResourceBindingNegotiator(),
StreamManagementNegotiator(),
]
);
await conn.connect(); await conn.connect(
await Future.delayed(const Duration(seconds: 3)); waitUntilLogin: true,
expect(fakeSocket.getState(), 6); );
expect(fakeSocket.getState(), 5);
expect(await conn.getConnectionState(), XmppConnectionState.connected); expect(await conn.getConnectionState(), XmppConnectionState.connected);
expect( expect(
conn.getManagerById<StreamManagementManager>(smManager)!.isStreamManagementEnabled(), conn
.getManagerById<StreamManagementManager>(smManager)!
.isStreamManagementEnabled(),
true, true,
); );
@ -404,7 +452,7 @@ void main() {
addFrom: StanzaFromType.none, addFrom: StanzaFromType.none,
); );
expect(sm.state.s2c, 2); expect(sm.state.s2c, /*2*/ 1);
}); });
}); });
@ -412,12 +460,13 @@ void main() {
test('Stanza retransmission', () async { test('Stanza retransmission', () async {
var stanzaCount = 0; var stanzaCount = 0;
final attributes = mkAttributes((_) { final attributes = mkAttributes((_) {
stanzaCount++; stanzaCount++;
}); });
final manager = StreamManagementManager(); final manager = StreamManagementManager()..register(attributes);
manager.register(attributes);
await manager.onXmppEvent(StreamManagementEnabledEvent(resource: 'hallo')); await manager.onXmppEvent(
StreamManagementEnabledEvent(resource: 'hallo'),
);
// Send 5 stanzas // Send 5 stanzas
for (var i = 0; i < 5; i++) { for (var i = 0; i < 5; i++) {
@ -438,21 +487,22 @@ void main() {
test('Resumption with prior state', () async { test('Resumption with prior state', () async {
var stanzaCount = 0; var stanzaCount = 0;
final attributes = mkAttributes((_) { final attributes = mkAttributes((_) {
stanzaCount++; stanzaCount++;
}); });
final manager = StreamManagementManager(); final manager = StreamManagementManager()..register(attributes);
manager.register(attributes);
// [ ... ] // [ ... ]
await manager.onXmppEvent(StreamManagementEnabledEvent(resource: 'hallo')); await manager.onXmppEvent(
manager.setState(manager.state.copyWith(c2s: 150, s2c: 70)); StreamManagementEnabledEvent(resource: 'hallo'),
);
await manager.setState(manager.state.copyWith(c2s: 150, s2c: 70));
// Send some stanzas but don't ack them // Send some stanzas but don't ack them
for (var i = 0; i < 5; i++) { for (var i = 0; i < 5; i++) {
await runOutgoingStanzaHandlers(manager, stanza); await runOutgoingStanzaHandlers(manager, stanza);
} }
expect(manager.getUnackedStanzas().length, 5); expect(manager.getUnackedStanzas().length, 5);
// Lose connection // Lose connection
// [ Reconnect ] // [ Reconnect ]
await manager.onXmppEvent(StreamResumedEvent(h: 150)); await manager.onXmppEvent(StreamResumedEvent(h: 150));
@ -463,11 +513,10 @@ void main() {
group('Test the negotiator', () { group('Test the negotiator', () {
test('Test successful stream enablement', () async { test('Test successful stream enablement', () async {
final fakeSocket = StubTCPSocket( final fakeSocket = StubTCPSocket([
[ StringExpectation(
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' to='test.server' xml:lang='en'>", '''
'''
<stream:stream <stream:stream
xmlns="jabber:client" xmlns="jabber:client"
version="1.0" version="1.0"
@ -479,14 +528,14 @@ void main() {
<mechanism>PLAIN</mechanism> <mechanism>PLAIN</mechanism>
</mechanisms> </mechanisms>
</stream:features>''', </stream:features>''',
), ),
StringExpectation( StringExpectation(
"<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>AHBvbHlub21kaXZpc2lvbgBhYWFh</auth>", "<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>AHBvbHlub21kaXZpc2lvbgBhYWFh</auth>",
'<success xmlns="urn:ietf:params:xml:ns:xmpp-sasl" />' '<success xmlns="urn:ietf:params:xml:ns:xmpp-sasl" />',
), ),
StringExpectation( 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' to='test.server' xml:lang='en'>",
''' '''
<stream:stream <stream:stream
xmlns="jabber:client" xmlns="jabber:client"
version="1.0" version="1.0"
@ -504,61 +553,61 @@ void main() {
<sm xmlns="urn:xmpp:sm:3"/> <sm xmlns="urn:xmpp:sm:3"/>
</stream:features> </stream:features>
''', ''',
), ),
StanzaExpectation( StanzaExpectation(
'<iq xmlns="jabber:client" type="set" id="a"><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"/></iq>', '<iq xmlns="jabber:client" type="set" id="a"><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"/></iq>',
'<iq xmlns="jabber:client" type="result" id="a"><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"><jid>polynomdivision@test.server/MU29eEZn</jid></bind></iq>', '<iq xmlns="jabber:client" type="result" id="a"><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"><jid>polynomdivision@test.server/MU29eEZn</jid></bind></iq>',
ignoreId: true ignoreId: true,
), ),
StringExpectation( StringExpectation(
"<enable xmlns='urn:xmpp:sm:3' resume='true' />", "<enable xmlns='urn:xmpp:sm:3' resume='true' />",
'<enabled xmlns="urn:xmpp:sm:3" id="some-long-sm-id" resume="true" />' '<enabled xmlns="urn:xmpp:sm:3" id="some-long-sm-id" resume="true" />',
) )
] ]);
);
final XmppConnection conn = XmppConnection( final conn = XmppConnection(
TestingReconnectionPolicy(), TestingReconnectionPolicy(),
AlwaysConnectedConnectivityManager(), AlwaysConnectedConnectivityManager(),
fakeSocket, fakeSocket,
); )..setConnectionSettings(
conn.setConnectionSettings(ConnectionSettings( ConnectionSettings(
jid: JID.fromString('polynomdivision@test.server'), jid: JID.fromString('polynomdivision@test.server'),
password: 'aaaa', password: 'aaaa',
useDirectTLS: true, useDirectTLS: true,
),); ),
conn.registerManagers([ );
PresenceManager(), await conn.registerManagers([
RosterManager(TestingRosterStateManager('', [])), PresenceManager(),
DiscoManager([]), RosterManager(TestingRosterStateManager('', [])),
PingManager(), DiscoManager([]),
StreamManagementManager(), PingManager(),
StreamManagementManager(),
]); ]);
conn.registerFeatureNegotiators( conn.registerFeatureNegotiators([
[ SaslPlainNegotiator(),
SaslPlainNegotiator(), ResourceBindingNegotiator(),
ResourceBindingNegotiator(), StreamManagementNegotiator(),
StreamManagementNegotiator(), ]);
]
await conn.connect(
waitUntilLogin: true,
); );
await conn.connect(); expect(fakeSocket.getState(), 5);
await Future.delayed(const Duration(seconds: 3));
expect(fakeSocket.getState(), 6);
expect(await conn.getConnectionState(), XmppConnectionState.connected); expect(await conn.getConnectionState(), XmppConnectionState.connected);
expect( expect(
conn.getManagerById<StreamManagementManager>(smManager)!.isStreamManagementEnabled(), conn
.getManagerById<StreamManagementManager>(smManager)!
.isStreamManagementEnabled(),
true, true,
); );
}); });
test('Test a failed stream resumption', () async { test('Test a failed stream resumption', () async {
final fakeSocket = StubTCPSocket( final fakeSocket = StubTCPSocket([
[ StringExpectation(
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' to='test.server' xml:lang='en'>", '''
'''
<stream:stream <stream:stream
xmlns="jabber:client" xmlns="jabber:client"
version="1.0" version="1.0"
@ -570,14 +619,14 @@ void main() {
<mechanism>PLAIN</mechanism> <mechanism>PLAIN</mechanism>
</mechanisms> </mechanisms>
</stream:features>''', </stream:features>''',
), ),
StringExpectation( StringExpectation(
"<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>AHBvbHlub21kaXZpc2lvbgBhYWFh</auth>", "<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>AHBvbHlub21kaXZpc2lvbgBhYWFh</auth>",
'<success xmlns="urn:ietf:params:xml:ns:xmpp-sasl" />' '<success xmlns="urn:ietf:params:xml:ns:xmpp-sasl" />',
), ),
StringExpectation( 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' to='test.server' xml:lang='en'>",
''' '''
<stream:stream <stream:stream
xmlns="jabber:client" xmlns="jabber:client"
version="1.0" version="1.0"
@ -595,74 +644,71 @@ void main() {
<sm xmlns="urn:xmpp:sm:3"/> <sm xmlns="urn:xmpp:sm:3"/>
</stream:features> </stream:features>
''', ''',
), ),
StringExpectation( StringExpectation(
"<resume xmlns='urn:xmpp:sm:3' previd='id-1' h='10' />", "<resume xmlns='urn:xmpp:sm:3' previd='id-1' h='10' />",
"<failed xmlns='urn:xmpp:sm:3' h='another-sequence-number'><item-not-found xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/></failed>", "<failed xmlns='urn:xmpp:sm:3' h='another-sequence-number'><item-not-found xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/></failed>",
), ),
StanzaExpectation( StanzaExpectation(
'<iq xmlns="jabber:client" type="set" id="a"><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"/></iq>', '<iq xmlns="jabber:client" type="set" id="a"><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"/></iq>',
'<iq xmlns="jabber:client" type="result" id="a"><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"><jid>polynomdivision@test.server/MU29eEZn</jid></bind></iq>', '<iq xmlns="jabber:client" type="result" id="a"><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"><jid>polynomdivision@test.server/MU29eEZn</jid></bind></iq>',
ignoreId: true ignoreId: true,
), ),
StringExpectation( StringExpectation(
"<enable xmlns='urn:xmpp:sm:3' resume='true' />", "<enable xmlns='urn:xmpp:sm:3' resume='true' />",
'<enabled xmlns="urn:xmpp:sm:3" id="id-2" resume="true" />' '<enabled xmlns="urn:xmpp:sm:3" id="id-2" resume="true" />',
) )
] ]);
);
final XmppConnection conn = XmppConnection( final conn = XmppConnection(
TestingReconnectionPolicy(), TestingReconnectionPolicy(),
AlwaysConnectedConnectivityManager(), AlwaysConnectedConnectivityManager(),
fakeSocket, fakeSocket,
); )..setConnectionSettings(
conn.setConnectionSettings(ConnectionSettings( ConnectionSettings(
jid: JID.fromString('polynomdivision@test.server'), jid: JID.fromString('polynomdivision@test.server'),
password: 'aaaa', password: 'aaaa',
useDirectTLS: true, useDirectTLS: true,
),);
conn.registerManagers([
PresenceManager(),
RosterManager(TestingRosterStateManager('', [])),
DiscoManager([]),
PingManager(),
StreamManagementManager(),
]);
conn.registerFeatureNegotiators(
[
SaslPlainNegotiator(),
ResourceBindingNegotiator(),
StreamManagementNegotiator(),
]
);
conn.getManagerById<StreamManagementManager>(smManager)!
.setState(
StreamManagementState(
10,
10,
streamResumptionId: 'id-1',
), ),
); );
await conn.registerManagers([
PresenceManager(),
RosterManager(TestingRosterStateManager('', [])),
DiscoManager([]),
PingManager(),
StreamManagementManager(),
]);
conn.registerFeatureNegotiators([
SaslPlainNegotiator(),
ResourceBindingNegotiator(),
StreamManagementNegotiator(),
]);
await conn.getManagerById<StreamManagementManager>(smManager)!.setState(
StreamManagementState(
10,
10,
streamResumptionId: 'id-1',
),
);
await conn.connect(); await conn.connect(
await Future.delayed(const Duration(seconds: 3)); waitUntilLogin: true,
expect(fakeSocket.getState(), 7); );
expect(fakeSocket.getState(), 6);
expect(await conn.getConnectionState(), XmppConnectionState.connected); expect(await conn.getConnectionState(), XmppConnectionState.connected);
expect( expect(
conn conn
.getManagerById<StreamManagementManager>(smManager)! .getManagerById<StreamManagementManager>(smManager)!
.isStreamManagementEnabled(), .isStreamManagementEnabled(),
true, true,
); );
}); });
test('Test a successful stream resumption', () async { test('Test a successful stream resumption', () async {
final fakeSocket = StubTCPSocket( final fakeSocket = StubTCPSocket([
[ StringExpectation(
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' to='test.server' xml:lang='en'>", '''
'''
<stream:stream <stream:stream
xmlns="jabber:client" xmlns="jabber:client"
version="1.0" version="1.0"
@ -674,14 +720,14 @@ void main() {
<mechanism>PLAIN</mechanism> <mechanism>PLAIN</mechanism>
</mechanisms> </mechanisms>
</stream:features>''', </stream:features>''',
), ),
StringExpectation( StringExpectation(
"<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>AHBvbHlub21kaXZpc2lvbgBhYWFh</auth>", "<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>AHBvbHlub21kaXZpc2lvbgBhYWFh</auth>",
'<success xmlns="urn:ietf:params:xml:ns:xmpp-sasl" />' '<success xmlns="urn:ietf:params:xml:ns:xmpp-sasl" />',
), ),
StringExpectation( 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' to='test.server' xml:lang='en'>",
''' '''
<stream:stream <stream:stream
xmlns="jabber:client" xmlns="jabber:client"
version="1.0" version="1.0"
@ -699,55 +745,53 @@ void main() {
<sm xmlns="urn:xmpp:sm:3"/> <sm xmlns="urn:xmpp:sm:3"/>
</stream:features> </stream:features>
''', ''',
), ),
StringExpectation( StringExpectation(
"<resume xmlns='urn:xmpp:sm:3' previd='id-1' h='10' />", "<resume xmlns='urn:xmpp:sm:3' previd='id-1' h='10' />",
"<resumed xmlns='urn:xmpp:sm:3' h='id-1' h='12' />", "<resumed xmlns='urn:xmpp:sm:3' h='id-1' h='12' />",
), ),
] ]);
);
final XmppConnection conn = XmppConnection( final conn = XmppConnection(
TestingReconnectionPolicy(), TestingReconnectionPolicy(),
AlwaysConnectedConnectivityManager(), AlwaysConnectedConnectivityManager(),
fakeSocket, fakeSocket,
); )..setConnectionSettings(
conn.setConnectionSettings(ConnectionSettings( ConnectionSettings(
jid: JID.fromString('polynomdivision@test.server'), jid: JID.fromString('polynomdivision@test.server'),
password: 'aaaa', password: 'aaaa',
useDirectTLS: true, useDirectTLS: true,
),);
conn.registerManagers([
PresenceManager(),
RosterManager(TestingRosterStateManager('', [])),
DiscoManager([]),
PingManager(),
StreamManagementManager(),
]);
conn.registerFeatureNegotiators(
[
SaslPlainNegotiator(),
ResourceBindingNegotiator(),
StreamManagementNegotiator(),
]
);
conn.getManagerById<StreamManagementManager>(smManager)!
.setState(
StreamManagementState(
10,
10,
streamResumptionId: 'id-1',
), ),
); );
await conn.registerManagers([
PresenceManager(),
RosterManager(TestingRosterStateManager('', [])),
DiscoManager([]),
PingManager(),
StreamManagementManager(),
]);
conn.registerFeatureNegotiators([
SaslPlainNegotiator(),
ResourceBindingNegotiator(),
StreamManagementNegotiator(),
]);
await conn.getManagerById<StreamManagementManager>(smManager)!.setState(
StreamManagementState(
10,
10,
streamResumptionId: 'id-1',
),
);
await conn.connect(lastResource: 'abc123'); await conn.connect(
await Future.delayed(const Duration(seconds: 3), () async { lastResource: 'abc123',
expect(fakeSocket.getState(), 5); waitUntilLogin: true,
expect(await conn.getConnectionState(), XmppConnectionState.connected); );
final sm = conn.getManagerById<StreamManagementManager>(smManager)!; expect(fakeSocket.getState(), 4);
expect(sm.isStreamManagementEnabled(), true); expect(await conn.getConnectionState(), XmppConnectionState.connected);
expect(sm.streamResumed, true); final sm = conn.getManagerById<StreamManagementManager>(smManager)!;
}); expect(sm.isStreamManagementEnabled(), true);
expect(sm.streamResumed, true);
}); });
}); });
} }

View File

@ -3,12 +3,21 @@ import 'package:test/test.dart';
import '../helpers/xmpp.dart'; 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, bool forceEncryption = false, }) async { sendStanza: (
stanza, {
StanzaFromType addFrom = StanzaFromType.full,
bool addId = true,
bool retransmitted = false,
bool awaitable = true,
bool encrypted = false,
bool forceEncryption = 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) {},
@ -21,15 +30,27 @@ void main() {
isFeatureSupported: (_) => false, isFeatureSupported: (_) => false,
getFullJID: () => JID.fromString('bob@xmpp.example/uwu'), getFullJID: () => JID.fromString('bob@xmpp.example/uwu'),
getSocket: () => StubTCPSocket([]), getSocket: () => StubTCPSocket([]),
getConnection: () => XmppConnection(TestingReconnectionPolicy(), AlwaysConnectedConnectivityManager(), StubTCPSocket([])), getConnection: () => XmppConnection(
TestingReconnectionPolicy(),
AlwaysConnectedConnectivityManager(),
StubTCPSocket([]),
),
getNegotiatorById: getNegotiatorNullStub, getNegotiatorById: getNegotiatorNullStub,
); );
final manager = CarbonsManager(); final manager = CarbonsManager()..register(attributes);
manager.register(attributes);
await manager.enableCarbons(); await manager.enableCarbons();
expect(manager.isCarbonValid(JID.fromString('mallory@evil.example')), false); expect(
expect(manager.isCarbonValid(JID.fromString('bob@xmpp.example')), true); manager.isCarbonValid(JID.fromString('mallory@evil.example')),
expect(manager.isCarbonValid(JID.fromString('bob@xmpp.example/abc')), false); false,
);
expect(
manager.isCarbonValid(JID.fromString('bob@xmpp.example')),
true,
);
expect(
manager.isCarbonValid(JID.fromString('bob@xmpp.example/abc')),
false,
);
}); });
} }

View File

@ -6,7 +6,7 @@ class MockedCSINegotiator extends CSINegotiator {
MockedCSINegotiator(this._isSupported); MockedCSINegotiator(this._isSupported);
final bool _isSupported; final bool _isSupported;
@override @override
bool get isSupported => _isSupported; bool get isSupported => _isSupported;
} }
@ -31,58 +31,86 @@ void main() {
group('Test the XEP-0352 implementation', () { group('Test the XEP-0352 implementation', () {
test('Test setting the CSI state when CSI is unsupported', () { test('Test setting the CSI state when CSI is unsupported', () {
var nonzaSent = false; var nonzaSent = false;
final csi = CSIManager(); CSIManager()
csi.register( ..register(
XmppManagerAttributes( XmppManagerAttributes(
sendStanza: (_, { StanzaFromType addFrom = StanzaFromType.full, bool addId = true, bool retransmitted = false, bool awaitable = true, bool encrypted = false, bool forceEncryption = false, }) async => XMLNode(tag: 'hallo'), sendStanza: (
sendEvent: (event) {}, _, {
sendNonza: (nonza) { StanzaFromType addFrom = StanzaFromType.full,
nonzaSent = true; bool addId = true,
}, bool retransmitted = false,
getConnectionSettings: () => ConnectionSettings( bool awaitable = true,
jid: JID.fromString('some.user@example.server'), bool encrypted = false,
password: 'password', bool forceEncryption = false,
useDirectTLS: true, }) async =>
XMLNode(tag: 'hallo'),
sendEvent: (event) {},
sendNonza: (nonza) {
nonzaSent = true;
},
getConnectionSettings: () => ConnectionSettings(
jid: JID.fromString('some.user@example.server'),
password: 'password',
useDirectTLS: true,
),
getManagerById: getManagerNullStub,
getNegotiatorById: getUnsupportedCSINegotiator,
isFeatureSupported: (_) => false,
getFullJID: () => JID.fromString('some.user@example.server/aaaaa'),
getSocket: () => StubTCPSocket([]),
getConnection: () => XmppConnection(
TestingReconnectionPolicy(),
AlwaysConnectedConnectivityManager(),
StubTCPSocket([]),
),
), ),
getManagerById: getManagerNullStub, )
getNegotiatorById: getUnsupportedCSINegotiator, ..setActive()
isFeatureSupported: (_) => false, ..setInactive();
getFullJID: () => JID.fromString('some.user@example.server/aaaaa'),
getSocket: () => StubTCPSocket([]),
getConnection: () => XmppConnection(TestingReconnectionPolicy(), AlwaysConnectedConnectivityManager(), StubTCPSocket([])),
),
);
csi.setActive();
csi.setInactive();
expect(nonzaSent, false, reason: 'Expected that no nonza is sent'); expect(nonzaSent, false, reason: 'Expected that no nonza is sent');
}); });
test('Test setting the CSI state when CSI is supported', () { test('Test setting the CSI state when CSI is supported', () {
final csi = CSIManager(); CSIManager()
csi.register( ..register(
XmppManagerAttributes( XmppManagerAttributes(
sendStanza: (_, { StanzaFromType addFrom = StanzaFromType.full, bool addId = true, bool retransmitted = false, bool awaitable = true, bool encrypted = false, bool forceEncryption = false, }) async => XMLNode(tag: 'hallo'), sendStanza: (
sendEvent: (event) {}, _, {
sendNonza: (nonza) { StanzaFromType addFrom = StanzaFromType.full,
expect(nonza.attributes['xmlns'] == csiXmlns, true, reason: "Expected only nonzas with XMLNS '$csiXmlns'"); bool addId = true,
}, bool retransmitted = false,
getConnectionSettings: () => ConnectionSettings( bool awaitable = true,
jid: JID.fromString('some.user@example.server'), bool encrypted = false,
password: 'password', bool forceEncryption = false,
useDirectTLS: true, }) async =>
XMLNode(tag: 'hallo'),
sendEvent: (event) {},
sendNonza: (nonza) {
expect(
nonza.attributes['xmlns'] == csiXmlns,
true,
reason: "Expected only nonzas with XMLNS '$csiXmlns'",
);
},
getConnectionSettings: () => ConnectionSettings(
jid: JID.fromString('some.user@example.server'),
password: 'password',
useDirectTLS: true,
),
getManagerById: getManagerNullStub,
getNegotiatorById: getSupportedCSINegotiator,
isFeatureSupported: (_) => false,
getFullJID: () => JID.fromString('some.user@example.server/aaaaa'),
getSocket: () => StubTCPSocket([]),
getConnection: () => XmppConnection(
TestingReconnectionPolicy(),
AlwaysConnectedConnectivityManager(),
StubTCPSocket([]),
),
), ),
getManagerById: getManagerNullStub, )
getNegotiatorById: getSupportedCSINegotiator, ..setActive()
isFeatureSupported: (_) => false, ..setInactive();
getFullJID: () => JID.fromString('some.user@example.server/aaaaa'),
getSocket: () => StubTCPSocket([]),
getConnection: () => XmppConnection(TestingReconnectionPolicy(), AlwaysConnectedConnectivityManager(), StubTCPSocket([])),
),
);
csi.setActive();
csi.setInactive();
}); });
}); });
} }

View File

@ -29,26 +29,20 @@ void main() {
'Cookie': 'foo=bar; user=romeo', 'Cookie': 'foo=bar; user=romeo',
'X-Tracking': 'Base64String==' 'X-Tracking': 'Base64String=='
}; };
expect( expect(prepareHeaders(headers), {
prepareHeaders(headers), 'Authorization': 'Basic Base64String==',
{ 'Cookie': 'foo=bar; user=romeo',
'Authorization': 'Basic Base64String==', });
'Cookie': 'foo=bar; user=romeo',
}
);
}); });
test('remove newlines', () { test('remove newlines', () {
final headers = { final headers = {
'Authorization': '\n\nBasic Base64String==\n\n', 'Authorization': '\n\nBasic Base64String==\n\n',
'\nCookie\r\n': 'foo=bar; user=romeo', '\nCookie\r\n': 'foo=bar; user=romeo',
}; };
expect( expect(prepareHeaders(headers), {
prepareHeaders(headers), 'Authorization': 'Basic Base64String==',
{ 'Cookie': 'foo=bar; user=romeo',
'Authorization': 'Basic Base64String==', });
'Cookie': 'foo=bar; user=romeo',
}
);
}); });
}); });
} }

View File

@ -27,7 +27,13 @@ void main() {
'''), '''),
); );
expect(sfs.metadata.hashes['sha3-256'], '2XarmwTlNxDAMkvymloX3S5+VbylNrJt/l5QyPa+YoU='); expect(
expect(sfs.metadata.hashes['id-blake2b256'], '2AfMGH8O7UNPTvUVAM9aK13mpCY='); sfs.metadata.hashes['sha3-256'],
'2XarmwTlNxDAMkvymloX3S5+VbylNrJt/l5QyPa+YoU=',
);
expect(
sfs.metadata.hashes['id-blake2b256'],
'2AfMGH8O7UNPTvUVAM9aK13mpCY=',
);
}); });
} }

View File

@ -1,5 +1,5 @@
import 'package:test/test.dart';
import 'package:moxxmpp/moxxmpp.dart'; import 'package:moxxmpp/moxxmpp.dart';
import 'package:test/test.dart';
void main() { void main() {
test('Test parsing a large sticker pack', () { test('Test parsing a large sticker pack', () {

View File

@ -10,15 +10,18 @@ void main() {
}); });
test('Test building a multiline quote', () { test('Test building a multiline quote', () {
final quote = QuoteData.fromBodies('Hallo Welt\nHallo Erde', 'How are you?'); final quote = QuoteData.fromBodies(
'Hallo Welt\nHallo Erde',
'How are you?',
);
expect(quote.body, '> Hallo Welt\n> Hallo Erde\nHow are you?'); expect(quote.body, '> Hallo Welt\n> Hallo Erde\nHow are you?');
expect(quote.fallbackLength, 26); expect(quote.fallbackLength, 26);
}); });
test('Applying a singleline quote', () { test('Applying a singleline quote', () {
final body = '> Hallo Welt\nHello right back!'; const body = '> Hallo Welt\nHello right back!';
final reply = ReplyData( const reply = ReplyData(
to: '', to: '',
id: '', id: '',
start: 0, start: 0,
@ -30,8 +33,8 @@ void main() {
}); });
test('Applying a multiline quote', () { test('Applying a multiline quote', () {
final body = "> Hallo Welt\n> How are you?\nI'm fine.\nThank you!"; const body = "> Hallo Welt\n> How are you?\nI'm fine.\nThank you!";
final reply = ReplyData( const reply = ReplyData(
to: '', to: '',
id: '', id: '',
start: 0, start: 0,

View File

@ -10,22 +10,20 @@ void main() {
final buffer = XmlStreamBuffer(); final buffer = XmlStreamBuffer();
final controller = StreamController<String>(); final controller = StreamController<String>();
controller unawaited(
.stream controller.stream.transform(buffer).forEach((node) {
.transform(buffer)
.forEach((node) {
if (node.tag == 'childa') { if (node.tag == 'childa') {
childa = true; childa = true;
} else if (node.tag == 'childb') { } else if (node.tag == 'childb') {
childb = true; childb = true;
} }
}); }),
);
controller.add('<childa /><childb />'); controller.add('<childa /><childb />');
await Future.delayed(const Duration(seconds: 2), () { await Future<void>.delayed(const Duration(seconds: 2));
expect(childa, true); expect(childa, true);
expect(childb, true); expect(childb, true);
});
}); });
test('Test broken up Xml data', () async { test('Test broken up Xml data', () async {
var childa = false; var childa = false;
@ -34,23 +32,22 @@ void main() {
final buffer = XmlStreamBuffer(); final buffer = XmlStreamBuffer();
final controller = StreamController<String>(); final controller = StreamController<String>();
controller unawaited(
.stream controller.stream.transform(buffer).forEach((node) {
.transform(buffer)
.forEach((node) {
if (node.tag == 'childa') { if (node.tag == 'childa') {
childa = true; childa = true;
} else if (node.tag == 'childb') { } else if (node.tag == 'childb') {
childb = true; childb = true;
} }
}); }),
controller.add('<childa'); );
controller.add(' /><childb />'); controller
..add('<childa')
..add(' /><childb />');
await Future.delayed(const Duration(seconds: 2), () { await Future<void>.delayed(const Duration(seconds: 2));
expect(childa, true); expect(childa, true);
expect(childb, true); expect(childb, true);
});
}); });
test('Test closing the stream', () async { test('Test closing the stream', () async {
@ -60,23 +57,22 @@ void main() {
final buffer = XmlStreamBuffer(); final buffer = XmlStreamBuffer();
final controller = StreamController<String>(); final controller = StreamController<String>();
controller unawaited(
.stream controller.stream.transform(buffer).forEach((node) {
.transform(buffer)
.forEach((node) {
if (node.tag == 'childa') { if (node.tag == 'childa') {
childa = true; childa = true;
} else if (node.tag == 'childb') { } else if (node.tag == 'childb') {
childb = true; childb = true;
} }
}); }),
controller.add('<childa'); );
controller.add(' /><childb />'); controller
controller.add('</stream:stream>'); ..add('<childa')
..add(' /><childb />')
..add('</stream:stream>');
await Future.delayed(const Duration(seconds: 2), () { await Future<void>.delayed(const Duration(seconds: 2));
expect(childa, true); expect(childa, true);
expect(childb, true); expect(childb, true);
});
}); });
} }

View File

@ -5,32 +5,61 @@ import 'helpers/logging.dart';
import 'helpers/xmpp.dart'; import 'helpers/xmpp.dart';
/// Returns true if the roster manager triggeres an event for a given stanza /// Returns true if the roster manager triggeres an event for a given stanza
Future<bool> testRosterManager(String bareJid, String resource, String stanzaString) async { Future<bool> testRosterManager(
String bareJid,
String resource,
String stanzaString,
) async {
var eventTriggered = false; var eventTriggered = false;
final roster = RosterManager(TestingRosterStateManager('', [])); final roster = RosterManager(TestingRosterStateManager('', []))
roster.register(XmppManagerAttributes( ..register(
sendStanza: (_, { StanzaFromType addFrom = StanzaFromType.full, bool addId = true, bool retransmitted = false, bool awaitable = true, bool encrypted = false, bool forceEncryption = false, }) async => XMLNode(tag: 'hallo'), XmppManagerAttributes(
sendEvent: (event) { sendStanza: (
eventTriggered = true; _, {
}, StanzaFromType addFrom = StanzaFromType.full,
sendNonza: (_) {}, bool addId = true,
getConnectionSettings: () => ConnectionSettings( bool retransmitted = false,
jid: JID.fromString(bareJid), bool awaitable = true,
password: 'password', bool encrypted = false,
useDirectTLS: true, bool forceEncryption = false,
}) async =>
XMLNode(tag: 'hallo'),
sendEvent: (event) {
eventTriggered = true;
},
sendNonza: (_) {},
getConnectionSettings: () => ConnectionSettings(
jid: JID.fromString(bareJid),
password: 'password',
useDirectTLS: true,
),
getManagerById: getManagerNullStub,
getNegotiatorById: getNegotiatorNullStub,
isFeatureSupported: (_) => false,
getFullJID: () => JID.fromString('$bareJid/$resource'),
getSocket: () => StubTCPSocket([]),
getConnection: () => XmppConnection(
TestingReconnectionPolicy(),
AlwaysConnectedConnectivityManager(),
StubTCPSocket([]),
),
), ),
getManagerById: getManagerNullStub, );
getNegotiatorById: getNegotiatorNullStub,
isFeatureSupported: (_) => false,
getFullJID: () => JID.fromString('$bareJid/$resource'),
getSocket: () => StubTCPSocket([]),
getConnection: () => XmppConnection(TestingReconnectionPolicy(), AlwaysConnectedConnectivityManager(), StubTCPSocket([])),
),);
final stanza = Stanza.fromXMLNode(XMLNode.fromString(stanzaString)); final stanza = Stanza.fromXMLNode(XMLNode.fromString(stanzaString));
for (final handler in roster.getIncomingStanzaHandlers()) { for (final handler in roster.getIncomingStanzaHandlers()) {
if (handler.matches(stanza)) await handler.callback(stanza, StanzaHandlerData(false, false, null, stanza)); if (handler.matches(stanza)) {
} await handler.callback(
stanza,
StanzaHandlerData(
false,
false,
null,
stanza,
),
);
}
}
return eventTriggered; return eventTriggered;
} }
@ -39,11 +68,11 @@ void main() {
initLogger(); initLogger();
test('Test a successful login attempt with no SM', () async { test('Test a successful login attempt with no SM', () async {
final fakeSocket = StubTCPSocket( final fakeSocket = StubTCPSocket(
[ [
StringExpectation( 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' to='test.server' xml:lang='en'>",
''' '''
<stream:stream <stream:stream
xmlns="jabber:client" xmlns="jabber:client"
version="1.0" version="1.0"
@ -55,14 +84,14 @@ void main() {
<mechanism>PLAIN</mechanism> <mechanism>PLAIN</mechanism>
</mechanisms> </mechanisms>
</stream:features>''', </stream:features>''',
), ),
StringExpectation( StringExpectation(
"<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>AHBvbHlub21kaXZpc2lvbgBhYWFh</auth>", "<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>AHBvbHlub21kaXZpc2lvbgBhYWFh</auth>",
'<success xmlns="urn:ietf:params:xml:ns:xmpp-sasl" />' '<success xmlns="urn:ietf:params:xml:ns:xmpp-sasl" />',
), ),
StringExpectation( 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' to='test.server' xml:lang='en'>",
''' '''
<stream:stream <stream:stream
xmlns="jabber:client" xmlns="jabber:client"
version="1.0" version="1.0"
@ -80,82 +109,57 @@ void main() {
<sm xmlns="urn:xmpp:sm:3"/> <sm xmlns="urn:xmpp:sm:3"/>
</stream:features> </stream:features>
''', ''',
), ),
StanzaExpectation( StanzaExpectation(
'<iq xmlns="jabber:client" type="set" id="a"><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"/></iq>', '<iq xmlns="jabber:client" type="set" id="a"><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"/></iq>',
'<iq xmlns="jabber:client" type="result" id="a"><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"><jid>polynomdivision@test.server/MU29eEZn</jid></bind></iq>', '<iq xmlns="jabber:client" type="result" id="a"><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"><jid>polynomdivision@test.server/MU29eEZn</jid></bind></iq>',
ignoreId: true, ignoreId: true,
), ),
/* StanzaExpectation(
Expectation( "<enable xmlns='urn:xmpp:sm:3' resume='true' />",
XMLNode.xmlns( "<enabled xmlns='urn:xmpp:sm:3' id='some-long-sm-id' resume='true'/>",
tag: 'presence', ),
xmlns: 'jabber:client', ],
attributes: { 'from': 'polynomdivision@test.server/MU29eEZn' }, );
children: [ // TODO(Unknown): This test is broken since we query the server and enable carbons
XMLNode( final conn = XmppConnection(
tag: 'show', TestingReconnectionPolicy(),
text: 'chat', AlwaysConnectedConnectivityManager(),
), fakeSocket,
XMLNode.xmlns( )..setConnectionSettings(
tag: 'c', ConnectionSettings(
xmlns: 'http://jabber.org/protocol/caps',
attributes: {
// TODO: Somehow make the test ignore this attribute
'ver': 'QRTBC5cg/oYd+UOTYazSQR4zb/I=',
'node': 'http://moxxmpp.example',
'hash': 'sha-1'
},
)
],
),
XMLNode(
tag: 'presence',
),
),
*/
],
);
// TODO: This test is broken since we query the server and enable carbons
final XmppConnection conn = XmppConnection(
TestingReconnectionPolicy(),
AlwaysConnectedConnectivityManager(),
fakeSocket,
);
conn.setConnectionSettings(ConnectionSettings(
jid: JID.fromString('polynomdivision@test.server'), jid: JID.fromString('polynomdivision@test.server'),
password: 'aaaa', password: 'aaaa',
useDirectTLS: true, useDirectTLS: true,
),); ),
conn.registerManagers([
PresenceManager(),
RosterManager(TestingRosterStateManager('', [])),
DiscoManager([]),
PingManager(),
StreamManagementManager(),
EntityCapabilitiesManager('http://moxxmpp.example'),
]);
conn.registerFeatureNegotiators(
[
SaslPlainNegotiator(),
SaslScramNegotiator(10, '', '', ScramHashType.sha512),
ResourceBindingNegotiator(),
StreamManagementNegotiator(),
]
); );
await conn.registerManagers([
PresenceManager(),
RosterManager(TestingRosterStateManager('', [])),
DiscoManager([]),
PingManager(),
StreamManagementManager(),
EntityCapabilitiesManager('http://moxxmpp.example'),
]);
conn.registerFeatureNegotiators([
SaslPlainNegotiator(),
SaslScramNegotiator(10, '', '', ScramHashType.sha512),
ResourceBindingNegotiator(),
StreamManagementNegotiator(),
]);
await conn.connect(); await conn.connect(
await Future.delayed(const Duration(seconds: 3), () { waitUntilLogin: true,
expect(fakeSocket.getState(), /*6*/ 5); );
}); expect(fakeSocket.getState(), /*6*/ 5);
}); });
test('Test a failed SASL auth', () async { test('Test a failed SASL auth', () async {
final fakeSocket = StubTCPSocket( final fakeSocket = StubTCPSocket(
[ [
StringExpectation( 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' to='test.server' xml:lang='en'>",
''' '''
<stream:stream <stream:stream
xmlns="jabber:client" xmlns="jabber:client"
version="1.0" version="1.0"
@ -167,111 +171,55 @@ void main() {
<mechanism>PLAIN</mechanism> <mechanism>PLAIN</mechanism>
</mechanisms> </mechanisms>
</stream:features>''', </stream:features>''',
), ),
StringExpectation( StringExpectation(
"<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>AHBvbHlub21kaXZpc2lvbgBhYWFh</auth>", "<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>AHBvbHlub21kaXZpc2lvbgBhYWFh</auth>",
'<failure xmlns="urn:ietf:params:xml:ns:xmpp-sasl"><not-authorized /></failure>' '<failure xmlns="urn:ietf:params:xml:ns:xmpp-sasl"><not-authorized /></failure>',
), ),
], ],
); );
var receivedEvent = false; var receivedEvent = false;
final XmppConnection conn = XmppConnection( final conn = XmppConnection(
TestingReconnectionPolicy(), TestingReconnectionPolicy(),
AlwaysConnectedConnectivityManager(), AlwaysConnectedConnectivityManager(),
fakeSocket, fakeSocket,
); )..setConnectionSettings(
conn.setConnectionSettings(ConnectionSettings( ConnectionSettings(
jid: JID.fromString('polynomdivision@test.server'),
password: 'aaaa',
useDirectTLS: true,
),);
conn.registerManagers([
PresenceManager(),
RosterManager(TestingRosterStateManager('', [])),
DiscoManager([]),
PingManager(),
EntityCapabilitiesManager('http://moxxmpp.example'),
]);
conn.registerFeatureNegotiators([
SaslPlainNegotiator()
]);
conn.asBroadcastStream().listen((event) {
if (event is AuthenticationFailedEvent && event.saslError == 'not-authorized') {
receivedEvent = true;
}
});
await conn.connect();
await Future.delayed(const Duration(seconds: 3), () {
expect(receivedEvent, true);
});
});
test('Test another failed SASL auth', () 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>
</stream:features>''',
),
StringExpectation(
"<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>AHBvbHlub21kaXZpc2lvbgBhYWFh</auth>",
'<failure xmlns="urn:ietf:params:xml:ns:xmpp-sasl"><mechanism-too-weak /></failure>',
),
],
);
var receivedEvent = false;
final XmppConnection conn = XmppConnection(
TestingReconnectionPolicy(),
AlwaysConnectedConnectivityManager(),
fakeSocket,
);
conn.setConnectionSettings(ConnectionSettings(
jid: JID.fromString('polynomdivision@test.server'), jid: JID.fromString('polynomdivision@test.server'),
password: 'aaaa', password: 'aaaa',
useDirectTLS: true, useDirectTLS: true,
),); ),
conn.registerManagers([ );
PresenceManager(), await conn.registerManagers([
RosterManager(TestingRosterStateManager('', [])), PresenceManager(),
DiscoManager([]), RosterManager(TestingRosterStateManager('', [])),
PingManager(), DiscoManager([]),
EntityCapabilitiesManager('http://moxxmpp.example'), PingManager(),
]); EntityCapabilitiesManager('http://moxxmpp.example'),
conn.registerFeatureNegotiators([ ]);
SaslPlainNegotiator() conn.registerFeatureNegotiators([
]); SaslPlainNegotiator(),
]);
conn.asBroadcastStream().listen((event) { conn.asBroadcastStream().listen((event) {
if (event is AuthenticationFailedEvent && event.saslError == 'mechanism-too-weak') { if (event is AuthenticationFailedEvent &&
receivedEvent = true; event.saslError == 'not-authorized') {
} receivedEvent = true;
}); }
});
await conn.connect(); await conn.connect(
await Future.delayed(const Duration(seconds: 3), () { waitUntilLogin: true,
expect(receivedEvent, true); );
}); expect(receivedEvent, true);
}); });
/*test('Test choosing SCRAM-SHA-1', () async { test('Test another failed SASL auth', () async {
final fakeSocket = StubTCPSocket( final fakeSocket = StubTCPSocket(
play: [ [
StringExpectation( 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' to='test.server' xml:lang='en'>",
''' '''
<stream:stream <stream:stream
xmlns="jabber:client" xmlns="jabber:client"
version="1.0" version="1.0"
@ -281,92 +229,162 @@ void main() {
<stream:features xmlns="http://etherx.jabber.org/streams"> <stream:features xmlns="http://etherx.jabber.org/streams">
<mechanisms xmlns="urn:ietf:params:xml:ns:xmpp-sasl"> <mechanisms xmlns="urn:ietf:params:xml:ns:xmpp-sasl">
<mechanism>PLAIN</mechanism> <mechanism>PLAIN</mechanism>
<mechanism>SCRAM-SHA-1</mechanism>
</mechanisms> </mechanisms>
</stream:features>''', </stream:features>''',
), ),
// TODO(Unknown): This test is currently broken StringExpectation(
StringExpectation( "<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>AHBvbHlub21kaXZpc2lvbgBhYWFh</auth>",
"<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='SCRAM-SHA-1'>AHBvbHlub21kaXZpc2lvbgBhYWFh</auth>", '<failure xmlns="urn:ietf:params:xml:ns:xmpp-sasl"><mechanism-too-weak /></failure>',
"..." ),
) ],
], );
var receivedEvent = false;
final conn = XmppConnection(
TestingReconnectionPolicy(),
AlwaysConnectedConnectivityManager(),
fakeSocket,
)..setConnectionSettings(
ConnectionSettings(
jid: JID.fromString('polynomdivision@test.server'),
password: 'aaaa',
useDirectTLS: true,
),
); );
final XmppConnection conn = XmppConnection(TestingReconnectionPolicy(), fakeSocket); await conn.registerManagers([
conn.setConnectionSettings(ConnectionSettings( PresenceManager(),
jid: JID.fromString('polynomdivision@test.server'), RosterManager(TestingRosterStateManager('', [])),
password: 'aaaa', DiscoManager([]),
useDirectTLS: true, PingManager(),
),); EntityCapabilitiesManager('http://moxxmpp.example'),
conn.registerManagers([ ]);
PresenceManager('http://moxxmpp.example'), conn.registerFeatureNegotiators([SaslPlainNegotiator()]);
RosterManager(TestingRosterStateManager('', [])),
DiscoManager(),
PingManager(),
]);
conn.registerFeatureNegotiators([
SaslPlainNegotiator(),
SaslScramNegotiator(10, '', '', ScramHashType.sha1),
]);
await conn.connect(); conn.asBroadcastStream().listen((event) {
await Future.delayed(const Duration(seconds: 3), () { if (event is AuthenticationFailedEvent &&
expect(fakeSocket.getState(), 2); event.saslError == 'mechanism-too-weak') {
}); receivedEvent = true;
});*/ }
});
await conn.connect(
waitUntilLogin: true,
);
expect(receivedEvent, true);
});
group('Test roster pushes', () { group('Test roster pushes', () {
test('Test for a CVE-2015-8688 style vulnerability', () async { test('Test for a CVE-2015-8688 style vulnerability', () async {
var eventTriggered = false; var eventTriggered = false;
final roster = RosterManager(TestingRosterStateManager('', [])); final roster = RosterManager(TestingRosterStateManager('', []))
roster.register(XmppManagerAttributes( ..register(
sendStanza: (_, { StanzaFromType addFrom = StanzaFromType.full, bool addId = true, bool retransmitted = false, bool awaitable = true, bool encrypted = false, bool forceEncryption = false, }) async => XMLNode(tag: 'hallo'), XmppManagerAttributes(
sendEvent: (event) { sendStanza: (
eventTriggered = true; _, {
}, StanzaFromType addFrom = StanzaFromType.full,
sendNonza: (_) {}, bool addId = true,
getConnectionSettings: () => ConnectionSettings( bool retransmitted = false,
jid: JID.fromString('some.user@example.server'), bool awaitable = true,
password: 'password', bool encrypted = false,
useDirectTLS: true, bool forceEncryption = false,
), }) async =>
getManagerById: getManagerNullStub, XMLNode(tag: 'hallo'),
getNegotiatorById: getNegotiatorNullStub, sendEvent: (event) {
isFeatureSupported: (_) => false, eventTriggered = true;
getFullJID: () => JID.fromString('some.user@example.server/aaaaa'), },
getSocket: () => StubTCPSocket([]), sendNonza: (_) {},
getConnection: () => XmppConnection(TestingReconnectionPolicy(), AlwaysConnectedConnectivityManager(), StubTCPSocket([])), getConnectionSettings: () => ConnectionSettings(
),); jid: JID.fromString('some.user@example.server'),
password: 'password',
useDirectTLS: true,
),
getManagerById: getManagerNullStub,
getNegotiatorById: getNegotiatorNullStub,
isFeatureSupported: (_) => false,
getFullJID: () => JID.fromString('some.user@example.server/aaaaa'),
getSocket: () => StubTCPSocket([]),
getConnection: () => XmppConnection(
TestingReconnectionPolicy(),
AlwaysConnectedConnectivityManager(),
StubTCPSocket([]),
),
),
);
// NOTE: Based on https://gultsch.de/gajim_roster_push_and_message_interception.html // NOTE: Based on https://gultsch.de/gajim_roster_push_and_message_interception.html
// NOTE: Added a from attribute as a server would add it itself. // NOTE: Added a from attribute as a server would add it itself.
final maliciousStanza = Stanza.fromXMLNode(XMLNode.fromString("<iq type=\"set\" from=\"eve@siacs.eu/bbbbb\" to=\"some.user@example.server/aaaaa\"><query xmlns='jabber:iq:roster'><item subscription=\"both\" jid=\"eve@siacs.eu\" name=\"Bob\" /></query></iq>")); final maliciousStanza = Stanza.fromXMLNode(
XMLNode.fromString(
"<iq type=\"set\" from=\"eve@siacs.eu/bbbbb\" to=\"some.user@example.server/aaaaa\"><query xmlns='jabber:iq:roster'><item subscription=\"both\" jid=\"eve@siacs.eu\" name=\"Bob\" /></query></iq>",
),
);
for (final handler in roster.getIncomingStanzaHandlers()) { for (final handler in roster.getIncomingStanzaHandlers()) {
if (handler.matches(maliciousStanza)) await handler.callback(maliciousStanza, StanzaHandlerData(false, false, null, maliciousStanza)); if (handler.matches(maliciousStanza)) {
} await handler.callback(
maliciousStanza,
StanzaHandlerData(
false,
false,
null,
maliciousStanza,
),
);
}
}
expect(eventTriggered, false, reason: 'Was able to inject a malicious roster push'); expect(
}); eventTriggered,
test('The manager should accept pushes from our bare jid', () async { false,
final result = await testRosterManager('test.user@server.example', 'aaaaa', "<iq from='test.user@server.example' type='result' id='82c2aa1e-cac3-4f62-9e1f-bbe6b057daf3' to='test.user@server.example/aaaaa' xmlns='jabber:client'><query ver='64' xmlns='jabber:iq:roster'><item jid='some.other.user@server.example' subscription='to' /></query></iq>"); reason: 'Was able to inject a malicious roster push',
expect(result, true, reason: 'Roster pushes from our bare JID should be accepted'); );
}); });
test('The manager should accept pushes from a jid that, if the resource is stripped, is our bare jid', () async { test('The manager should accept pushes from our bare jid', () async {
final result1 = await testRosterManager('test.user@server.example', 'aaaaa', "<iq from='test.user@server.example/aaaaa' type='result' id='82c2aa1e-cac3-4f62-9e1f-bbe6b057daf3' to='test.user@server.example/aaaaa' xmlns='jabber:client'><query ver='64' xmlns='jabber:iq:roster'><item jid='some.other.user@server.example' subscription='to' /></query></iq>"); final result = await testRosterManager(
expect(result1, true, reason: 'Roster pushes should be accepted if the bare JIDs are the same'); 'test.user@server.example',
'aaaaa',
"<iq from='test.user@server.example' type='result' id='82c2aa1e-cac3-4f62-9e1f-bbe6b057daf3' to='test.user@server.example/aaaaa' xmlns='jabber:client'><query ver='64' xmlns='jabber:iq:roster'><item jid='some.other.user@server.example' subscription='to' /></query></iq>",
);
expect(
result,
true,
reason: 'Roster pushes from our bare JID should be accepted',
);
});
test(
'The manager should accept pushes from a jid that, if the resource is stripped, is our bare jid',
() async {
final result1 = await testRosterManager(
'test.user@server.example',
'aaaaa',
"<iq from='test.user@server.example/aaaaa' type='result' id='82c2aa1e-cac3-4f62-9e1f-bbe6b057daf3' to='test.user@server.example/aaaaa' xmlns='jabber:client'><query ver='64' xmlns='jabber:iq:roster'><item jid='some.other.user@server.example' subscription='to' /></query></iq>",
);
expect(
result1,
true,
reason:
'Roster pushes should be accepted if the bare JIDs are the same',
);
final result2 = await testRosterManager('test.user@server.example', 'aaaaa', "<iq from='test.user@server.example/bbbbb' type='result' id='82c2aa1e-cac3-4f62-9e1f-bbe6b057daf3' to='test.user@server.example/aaaaa' xmlns='jabber:client'><query ver='64' xmlns='jabber:iq:roster'><item jid='some.other.user@server.example' subscription='to' /></query></iq>"); final result2 = await testRosterManager(
expect(result2, true, reason: 'Roster pushes should be accepted if the bare JIDs are the same'); 'test.user@server.example',
}); 'aaaaa',
"<iq from='test.user@server.example/bbbbb' type='result' id='82c2aa1e-cac3-4f62-9e1f-bbe6b057daf3' to='test.user@server.example/aaaaa' xmlns='jabber:client'><query ver='64' xmlns='jabber:iq:roster'><item jid='some.other.user@server.example' subscription='to' /></query></iq>",
);
expect(
result2,
true,
reason:
'Roster pushes should be accepted if the bare JIDs are the same',
);
});
}); });
test('Test failing due to the server only allowing SASL PLAIN', () async { test('Test failing due to the server only allowing SASL PLAIN', () async {
final fakeSocket = StubTCPSocket( final fakeSocket = StubTCPSocket(
[ [
StringExpectation( StringExpectation(
"<stream:stream xmlns='jabber:client' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' to='example.org' xml:lang='en'>", "<stream:stream xmlns='jabber:client' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' to='example.org' xml:lang='en'>",
''' '''
<stream:stream <stream:stream
xmlns="jabber:client" xmlns="jabber:client"
version="1.0" version="1.0"
@ -378,28 +396,27 @@ void main() {
<mechanism>PLAIN</mechanism> <mechanism>PLAIN</mechanism>
</mechanisms> </mechanisms>
</stream:features>''', </stream:features>''',
), ),
], ],
); );
final conn = XmppConnection( final conn = XmppConnection(
TestingReconnectionPolicy(), TestingReconnectionPolicy(),
AlwaysConnectedConnectivityManager(), AlwaysConnectedConnectivityManager(),
fakeSocket, fakeSocket,
); );
conn.registerManagers([ await conn.registerManagers([
PresenceManager(), PresenceManager(),
RosterManager(TestingRosterStateManager('', [])), RosterManager(TestingRosterStateManager('', [])),
DiscoManager([]), DiscoManager([]),
PingManager(), PingManager(),
]); ]);
conn.registerFeatureNegotiators( conn
[ ..registerFeatureNegotiators([
// SaslPlainNegotiator(), // SaslPlainNegotiator(),
ResourceBindingNegotiator(), ResourceBindingNegotiator(),
] ])
); ..setConnectionSettings(
conn.setConnectionSettings(
ConnectionSettings( ConnectionSettings(
jid: JID.fromString('testuser@example.org'), jid: JID.fromString('testuser@example.org'),
password: 'abc123', password: 'abc123',
@ -407,10 +424,13 @@ void main() {
), ),
); );
final result = await conn.connect( final result = await conn.connect(
waitUntilLogin: true, waitUntilLogin: true,
); );
expect(result.isType<NoMatchingAuthenticationMechanismAvailableError>(), true); expect(
result.isType<NoMatchingAuthenticationMechanismAvailableError>(),
true,
);
}); });
} }