Merge branch 'v5' into flutter-app
This commit is contained in:
commit
44892e5b03
3
.gitignore
vendored
3
.gitignore
vendored
@ -77,6 +77,8 @@ stack.yaml.lock
|
||||
.pub-cache/
|
||||
.pub/
|
||||
build/
|
||||
# Default behavior, only keep it for apps
|
||||
pubspec.lock
|
||||
|
||||
# Web
|
||||
lib/generated_plugin_registrant.dart
|
||||
@ -91,3 +93,4 @@ app.*.map.json
|
||||
/android/app/debug
|
||||
/android/app/profile
|
||||
/android/app/release
|
||||
|
||||
|
@ -2,7 +2,7 @@ include: package:lints/recommended.yaml
|
||||
|
||||
linter:
|
||||
rules:
|
||||
prefer_double_quotes: true
|
||||
prefer_single_quotes: true
|
||||
constant_identifier_names: false
|
||||
always_declare_return_types: true
|
||||
avoid_dynamic_calls: true
|
||||
|
2
packages/simplex_app/.gitignore
vendored
Normal file
2
packages/simplex_app/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
# Keep for apps
|
||||
!pubspec.lock
|
3
packages/simplexmq/.gitignore
vendored
3
packages/simplexmq/.gitignore
vendored
@ -1,3 +0,0 @@
|
||||
# Omit committing pubspec.lock for library packages; see
|
||||
# https://dart.dev/guides/libraries/private-files#pubspeclock.
|
||||
pubspec.lock
|
@ -3,6 +3,5 @@
|
||||
/// More dartdocs go here.
|
||||
library simplexmq;
|
||||
|
||||
export "src/protocol.dart";
|
||||
|
||||
// TODO: Export any libraries intended for clients of this package.
|
||||
export 'src/protocol.dart';
|
||||
export 'src/transport.dart' show Transport;
|
||||
|
@ -1,4 +1,4 @@
|
||||
import "dart:typed_data";
|
||||
import 'dart:typed_data';
|
||||
|
||||
Uint8List encodeAscii(String s) => Uint8List.fromList(s.codeUnits);
|
||||
|
||||
@ -29,8 +29,8 @@ Uint8List concatN(List<Uint8List> bs) {
|
||||
return a;
|
||||
}
|
||||
|
||||
final charSpace = " ".codeUnitAt(0);
|
||||
final charEqual = "=".codeUnitAt(0);
|
||||
final charSpace = ' '.codeUnitAt(0);
|
||||
final charEqual = '='.codeUnitAt(0);
|
||||
final empty = Uint8List(0);
|
||||
|
||||
Uint8List unwords(Uint8List b1, Uint8List b2) {
|
||||
@ -61,7 +61,7 @@ Uint8List unwordsN(List<Uint8List> bs) {
|
||||
}
|
||||
|
||||
final _base64chars = Uint8List.fromList(
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
|
||||
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
|
||||
.codeUnits);
|
||||
|
||||
List<int?> __base64lookup() {
|
||||
|
@ -1,18 +1,18 @@
|
||||
import "dart:typed_data";
|
||||
import "buffer.dart";
|
||||
import 'dart:typed_data';
|
||||
import 'buffer.dart';
|
||||
|
||||
typedef BinaryTags<T> = Map<T, Uint8List>;
|
||||
|
||||
int cc(String c) => c.codeUnitAt(0);
|
||||
|
||||
final char0 = cc("0");
|
||||
final char9 = cc("9");
|
||||
final charLowerA = cc("a");
|
||||
final charLowerZ = cc("z");
|
||||
final charUpperA = cc("A");
|
||||
final charUpperZ = cc("Z");
|
||||
final charPlus = cc("+");
|
||||
final charSlash = cc("/");
|
||||
final char0 = cc('0');
|
||||
final char9 = cc('9');
|
||||
final charLowerA = cc('a');
|
||||
final charLowerZ = cc('z');
|
||||
final charUpperA = cc('A');
|
||||
final charUpperZ = cc('Z');
|
||||
final charPlus = cc('+');
|
||||
final charSlash = cc('/');
|
||||
|
||||
class Parser {
|
||||
final Uint8List _s;
|
||||
|
@ -1,6 +1,6 @@
|
||||
import "dart:typed_data";
|
||||
import "buffer.dart";
|
||||
import "parser.dart";
|
||||
import 'dart:typed_data';
|
||||
import 'buffer.dart';
|
||||
import 'parser.dart';
|
||||
|
||||
abstract class SMPCommand {
|
||||
Uint8List serialize();
|
||||
@ -10,25 +10,25 @@ abstract class ClientCommand extends SMPCommand {}
|
||||
|
||||
abstract class BrokerCommand extends SMPCommand {}
|
||||
|
||||
final rsaPrefix = encodeAscii("rsa:");
|
||||
final rsaPrefix = encodeAscii('rsa:');
|
||||
|
||||
Uint8List serializePubKey(Uint8List rcvPubKey) =>
|
||||
concat(rsaPrefix, encode64(rcvPubKey));
|
||||
|
||||
final Uint8List cNEW = encodeAscii("NEW");
|
||||
final Uint8List cSUB = encodeAscii("SUB");
|
||||
final Uint8List cKEY = encodeAscii("KEY");
|
||||
final Uint8List cACK = encodeAscii("ACK");
|
||||
final Uint8List cOFF = encodeAscii("OFF");
|
||||
final Uint8List cDEL = encodeAscii("DEL");
|
||||
final Uint8List cSEND = encodeAscii("SEND");
|
||||
final Uint8List cPING = encodeAscii("PING");
|
||||
final Uint8List cIDS = encodeAscii("IDS");
|
||||
final Uint8List cMSG = encodeAscii("MSG");
|
||||
final Uint8List cEND = encodeAscii("END");
|
||||
final Uint8List cOK = encodeAscii("OK");
|
||||
final Uint8List cERR = encodeAscii("ERR");
|
||||
final Uint8List cPONG = encodeAscii("PONG");
|
||||
final Uint8List cNEW = encodeAscii('NEW');
|
||||
final Uint8List cSUB = encodeAscii('SUB');
|
||||
final Uint8List cKEY = encodeAscii('KEY');
|
||||
final Uint8List cACK = encodeAscii('ACK');
|
||||
final Uint8List cOFF = encodeAscii('OFF');
|
||||
final Uint8List cDEL = encodeAscii('DEL');
|
||||
final Uint8List cSEND = encodeAscii('SEND');
|
||||
final Uint8List cPING = encodeAscii('PING');
|
||||
final Uint8List cIDS = encodeAscii('IDS');
|
||||
final Uint8List cMSG = encodeAscii('MSG');
|
||||
final Uint8List cEND = encodeAscii('END');
|
||||
final Uint8List cOK = encodeAscii('OK');
|
||||
final Uint8List cERR = encodeAscii('ERR');
|
||||
final Uint8List cPONG = encodeAscii('PONG');
|
||||
|
||||
enum SMPCmdTag {
|
||||
NEW,
|
||||
@ -148,23 +148,23 @@ class OK extends BrokerCommand {
|
||||
enum ErrorType { BLOCK, CMD, AUTH, QUOTA, NO_MSG, INTERNAL }
|
||||
|
||||
final BinaryTags<ErrorType> errorTags = {
|
||||
ErrorType.BLOCK: encodeAscii("BLOCK"),
|
||||
ErrorType.CMD: encodeAscii("CMD"),
|
||||
ErrorType.AUTH: encodeAscii("AUTH"),
|
||||
ErrorType.QUOTA: encodeAscii("QUOTA"),
|
||||
ErrorType.NO_MSG: encodeAscii("NO_MSG"),
|
||||
ErrorType.INTERNAL: encodeAscii("INTERNAL"),
|
||||
ErrorType.BLOCK: encodeAscii('BLOCK'),
|
||||
ErrorType.CMD: encodeAscii('CMD'),
|
||||
ErrorType.AUTH: encodeAscii('AUTH'),
|
||||
ErrorType.QUOTA: encodeAscii('QUOTA'),
|
||||
ErrorType.NO_MSG: encodeAscii('NO_MSG'),
|
||||
ErrorType.INTERNAL: encodeAscii('INTERNAL'),
|
||||
};
|
||||
|
||||
enum CmdErrorType { PROHIBITED, KEY_SIZE, SYNTAX, NO_AUTH, HAS_AUTH, NO_QUEUE }
|
||||
|
||||
final BinaryTags<CmdErrorType> cmdErrorTags = {
|
||||
CmdErrorType.PROHIBITED: encodeAscii("PROHIBITED"),
|
||||
CmdErrorType.KEY_SIZE: encodeAscii("KEY_SIZE"),
|
||||
CmdErrorType.SYNTAX: encodeAscii("SYNTAX"),
|
||||
CmdErrorType.NO_AUTH: encodeAscii("NO_AUTH"),
|
||||
CmdErrorType.HAS_AUTH: encodeAscii("HAS_AUTH"),
|
||||
CmdErrorType.NO_QUEUE: encodeAscii("NO_QUEUE"),
|
||||
CmdErrorType.PROHIBITED: encodeAscii('PROHIBITED'),
|
||||
CmdErrorType.KEY_SIZE: encodeAscii('KEY_SIZE'),
|
||||
CmdErrorType.SYNTAX: encodeAscii('SYNTAX'),
|
||||
CmdErrorType.NO_AUTH: encodeAscii('NO_AUTH'),
|
||||
CmdErrorType.HAS_AUTH: encodeAscii('HAS_AUTH'),
|
||||
CmdErrorType.NO_QUEUE: encodeAscii('NO_QUEUE'),
|
||||
};
|
||||
|
||||
class ERR extends BrokerCommand {
|
||||
@ -172,7 +172,7 @@ class ERR extends BrokerCommand {
|
||||
final CmdErrorType? cmdErr;
|
||||
ERR(this.err)
|
||||
: cmdErr = err == ErrorType.CMD
|
||||
? throw ArgumentError("CMD error should be created with ERR.CMD")
|
||||
? throw ArgumentError('CMD error should be created with ERR.CMD')
|
||||
: null;
|
||||
ERR.cmd(this.cmdErr) : err = ErrorType.CMD;
|
||||
@override
|
||||
|
17
packages/simplexmq/lib/src/transport.dart
Normal file
17
packages/simplexmq/lib/src/transport.dart
Normal file
@ -0,0 +1,17 @@
|
||||
import 'dart:async';
|
||||
import 'dart:typed_data';
|
||||
|
||||
abstract class Transport {
|
||||
Future<Uint8List> read(int n);
|
||||
Future<void> write(Uint8List data);
|
||||
}
|
||||
|
||||
Stream<Uint8List> blockStream(Transport t, int blockSize) async* {
|
||||
try {
|
||||
while (true) {
|
||||
yield await t.read(blockSize);
|
||||
}
|
||||
} catch (e) {
|
||||
return;
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
name: simplexmq
|
||||
description: A starting point for Dart libraries or applications.
|
||||
version: 0.0.1
|
||||
# homepage: https://www.example.com
|
||||
publish_to: none
|
||||
|
||||
environment:
|
||||
sdk: '>=2.14.0 <3.0.0'
|
||||
|
@ -1,6 +1,6 @@
|
||||
import "dart:typed_data";
|
||||
import "package:simplexmq/src/buffer.dart";
|
||||
import "package:test/test.dart";
|
||||
import 'dart:typed_data';
|
||||
import 'package:simplexmq/src/buffer.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
final hello123 = Uint8List.fromList([104, 101, 108, 108, 111, 49, 50, 51]);
|
||||
|
||||
@ -14,17 +14,17 @@ class Base64Test {
|
||||
}
|
||||
|
||||
void main() {
|
||||
group("ascii encoding/decoding", () {
|
||||
test("encodeAscii", () {
|
||||
expect(encodeAscii("hello123"), hello123);
|
||||
group('ascii encoding/decoding', () {
|
||||
test('encodeAscii', () {
|
||||
expect(encodeAscii('hello123'), hello123);
|
||||
});
|
||||
|
||||
test("decodeAscii", () {
|
||||
expect(decodeAscii(hello123), "hello123");
|
||||
test('decodeAscii', () {
|
||||
expect(decodeAscii(hello123), 'hello123');
|
||||
});
|
||||
});
|
||||
|
||||
group("base-64 encoding/decoding", () {
|
||||
group('base-64 encoding/decoding', () {
|
||||
String allBinaryChars() {
|
||||
final a = Uint8List(256);
|
||||
for (var i = 0; i < 256; i++) {
|
||||
@ -34,33 +34,33 @@ void main() {
|
||||
}
|
||||
|
||||
final base64tests = [
|
||||
Base64Test("\x12\x34\x56\x78", "EjRWeA=="),
|
||||
Base64Test("hello123", "aGVsbG8xMjM="),
|
||||
Base64Test("Hello world", "SGVsbG8gd29ybGQ="),
|
||||
Base64Test("Hello worlds!", "SGVsbG8gd29ybGRzIQ=="),
|
||||
Base64Test("May", "TWF5"),
|
||||
Base64Test("Ma", "TWE="),
|
||||
Base64Test("M", "TQ=="),
|
||||
Base64Test('\x12\x34\x56\x78', 'EjRWeA=='),
|
||||
Base64Test('hello123', 'aGVsbG8xMjM='),
|
||||
Base64Test('Hello world', 'SGVsbG8gd29ybGQ='),
|
||||
Base64Test('Hello worlds!', 'SGVsbG8gd29ybGRzIQ=='),
|
||||
Base64Test('May', 'TWF5'),
|
||||
Base64Test('Ma', 'TWE='),
|
||||
Base64Test('M', 'TQ=='),
|
||||
Base64Test.withDescription(
|
||||
"all binary chars",
|
||||
'all binary chars',
|
||||
allBinaryChars(),
|
||||
"AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn+AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq+wsbKztLW2t7i5uru8vb6/wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t/g4eLj5OXm5+jp6uvs7e7v8PHy8/T19vf4+fr7/P3+/w==",
|
||||
'AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn+AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq+wsbKztLW2t7i5uru8vb6/wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t/g4eLj5OXm5+jp6uvs7e7v8PHy8/T19vf4+fr7/P3+/w==',
|
||||
),
|
||||
];
|
||||
|
||||
test("encode64", () {
|
||||
test('encode64', () {
|
||||
for (final t in base64tests) {
|
||||
expect(encode64(encodeAscii(t.binary)), encodeAscii(t.base64));
|
||||
}
|
||||
});
|
||||
|
||||
test("decode64", () {
|
||||
test('decode64', () {
|
||||
for (final t in base64tests) {
|
||||
expect(decode64(encodeAscii(t.base64)), encodeAscii(t.binary));
|
||||
}
|
||||
expect(decode64(encodeAscii("TWE")), null);
|
||||
expect(decode64(encodeAscii("TWE==")), null);
|
||||
expect(decode64(encodeAscii("TW!=")), null);
|
||||
expect(decode64(encodeAscii('TWE')), null);
|
||||
expect(decode64(encodeAscii('TWE==')), null);
|
||||
expect(decode64(encodeAscii('TW!=')), null);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -1,11 +1,11 @@
|
||||
import "dart:typed_data";
|
||||
import "package:simplexmq/src/buffer.dart";
|
||||
import "package:simplexmq/src/protocol.dart";
|
||||
import "package:test/test.dart";
|
||||
import 'dart:typed_data';
|
||||
import 'package:simplexmq/src/buffer.dart';
|
||||
import 'package:simplexmq/src/protocol.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
void main() {
|
||||
group("Parsing & serializing SMP commands", () {
|
||||
group("valid commands", () {
|
||||
group('Parsing & serializing SMP commands', () {
|
||||
group('valid commands', () {
|
||||
Null Function() parseSerialize(SMPCommand cmd) => () {
|
||||
final s = cmd.serialize();
|
||||
expect(parseSMPCommand(s)?.serialize(), s);
|
||||
@ -13,92 +13,92 @@ void main() {
|
||||
null);
|
||||
};
|
||||
|
||||
test("NEW", parseSerialize(NEW(encodeAscii("rsa:1234"))));
|
||||
test("SUB", parseSerialize(SUB()));
|
||||
test("KEY", parseSerialize(KEY(encodeAscii("rsa:1234"))));
|
||||
test("ACK", parseSerialize(ACK()));
|
||||
test("OFF", parseSerialize(OFF()));
|
||||
test("DEL", parseSerialize(DEL()));
|
||||
test("SEND", parseSerialize(SEND(encodeAscii("hello"))));
|
||||
test("PING", parseSerialize(PING()));
|
||||
test("IDS", parseSerialize(IDS(encodeAscii("abc"), encodeAscii("def"))));
|
||||
test('NEW', parseSerialize(NEW(encodeAscii('rsa:1234'))));
|
||||
test('SUB', parseSerialize(SUB()));
|
||||
test('KEY', parseSerialize(KEY(encodeAscii('rsa:1234'))));
|
||||
test('ACK', parseSerialize(ACK()));
|
||||
test('OFF', parseSerialize(OFF()));
|
||||
test('DEL', parseSerialize(DEL()));
|
||||
test('SEND', parseSerialize(SEND(encodeAscii('hello'))));
|
||||
test('PING', parseSerialize(PING()));
|
||||
test('IDS', parseSerialize(IDS(encodeAscii('abc'), encodeAscii('def'))));
|
||||
test(
|
||||
"MSG",
|
||||
parseSerialize(MSG(encodeAscii("fgh"), DateTime.now().toUtc(),
|
||||
encodeAscii("hello"))));
|
||||
test("END", parseSerialize(END()));
|
||||
test("OK", parseSerialize(OK()));
|
||||
test("ERR", parseSerialize(ERR(ErrorType.AUTH)));
|
||||
test("ERR CMD", parseSerialize(ERR.cmd(CmdErrorType.SYNTAX)));
|
||||
test("PONG", parseSerialize(PONG()));
|
||||
'MSG',
|
||||
parseSerialize(MSG(encodeAscii('fgh'), DateTime.now().toUtc(),
|
||||
encodeAscii('hello'))));
|
||||
test('END', parseSerialize(END()));
|
||||
test('OK', parseSerialize(OK()));
|
||||
test('ERR', parseSerialize(ERR(ErrorType.AUTH)));
|
||||
test('ERR CMD', parseSerialize(ERR.cmd(CmdErrorType.SYNTAX)));
|
||||
test('PONG', parseSerialize(PONG()));
|
||||
});
|
||||
|
||||
group("invalid commands", () {
|
||||
group('invalid commands', () {
|
||||
void Function() parseFailure(String s) =>
|
||||
() => expect(parseSMPCommand(encodeAscii(s)), null);
|
||||
|
||||
void Function() parseSuccess(String s) =>
|
||||
() => expect(parseSMPCommand(encodeAscii(s)) is SMPCommand, true);
|
||||
|
||||
group("NEW", () {
|
||||
test("ok", parseSuccess("NEW rsa:abcd"));
|
||||
test("no key", parseFailure("NEW"));
|
||||
test("invalid base64", parseFailure("NEW rsa:abc"));
|
||||
test("double space", parseFailure("NEW rsa:abcd"));
|
||||
group('NEW', () {
|
||||
test('ok', parseSuccess('NEW rsa:abcd'));
|
||||
test('no key', parseFailure('NEW'));
|
||||
test('invalid base64', parseFailure('NEW rsa:abc'));
|
||||
test('double space', parseFailure('NEW rsa:abcd'));
|
||||
});
|
||||
|
||||
group("KEY", () {
|
||||
test("ok", parseSuccess("KEY rsa:abcd"));
|
||||
test("no key", parseFailure("KEY"));
|
||||
test("invalid base64", parseFailure("KEY rsa:abc"));
|
||||
test("double space", parseFailure("KEY rsa:abcd"));
|
||||
group('KEY', () {
|
||||
test('ok', parseSuccess('KEY rsa:abcd'));
|
||||
test('no key', parseFailure('KEY'));
|
||||
test('invalid base64', parseFailure('KEY rsa:abc'));
|
||||
test('double space', parseFailure('KEY rsa:abcd'));
|
||||
});
|
||||
|
||||
group("SEND", () {
|
||||
test("ok", parseSuccess("SEND 5 hello "));
|
||||
test("no size", parseFailure("SEND hello "));
|
||||
test("incorrect size", parseFailure("SEND 6 hello "));
|
||||
test("no trailing space", parseFailure("SEND 5 hello"));
|
||||
test("double space 1", parseFailure("SEND 5 hello "));
|
||||
test("double space 2", parseFailure("SEND 5 hello "));
|
||||
group('SEND', () {
|
||||
test('ok', parseSuccess('SEND 5 hello '));
|
||||
test('no size', parseFailure('SEND hello '));
|
||||
test('incorrect size', parseFailure('SEND 6 hello '));
|
||||
test('no trailing space', parseFailure('SEND 5 hello'));
|
||||
test('double space 1', parseFailure('SEND 5 hello '));
|
||||
test('double space 2', parseFailure('SEND 5 hello '));
|
||||
});
|
||||
|
||||
group("IDS", () {
|
||||
test("ok", parseSuccess("IDS abcd efgh"));
|
||||
test("no IDs", parseFailure("IDS"));
|
||||
test("only one ID", parseFailure("IDS abcd"));
|
||||
test("invalid base64 1", parseFailure("IDS abc efgh"));
|
||||
test("invalid base64 2", parseFailure("IDS abcd efg"));
|
||||
test("double space 1", parseFailure("IDS abcd efgh"));
|
||||
test("double space 2", parseFailure("IDS abcd efgh"));
|
||||
group('IDS', () {
|
||||
test('ok', parseSuccess('IDS abcd efgh'));
|
||||
test('no IDs', parseFailure('IDS'));
|
||||
test('only one ID', parseFailure('IDS abcd'));
|
||||
test('invalid base64 1', parseFailure('IDS abc efgh'));
|
||||
test('invalid base64 2', parseFailure('IDS abcd efg'));
|
||||
test('double space 1', parseFailure('IDS abcd efgh'));
|
||||
test('double space 2', parseFailure('IDS abcd efgh'));
|
||||
});
|
||||
|
||||
group("MSG", () {
|
||||
final String ts = "2021-10-03T10:50:59.895Z";
|
||||
test("ok", parseSuccess("MSG abcd $ts 5 hello "));
|
||||
test("invalid base64", parseFailure("MSG abc $ts 5 hello "));
|
||||
test("invalid timestamp 1",
|
||||
parseFailure("MSG abc 2021-10-03T10:50:59.895 5 hello "));
|
||||
test("invalid timestamp 2",
|
||||
parseFailure("MSG abc 2021-14-03T10:50:59.895Z 5 hello "));
|
||||
test("no size", parseFailure("MSG abcd $ts hello "));
|
||||
test("incorrect size", parseFailure("MSG abcd $ts 6 hello "));
|
||||
test("no trailing space", parseFailure("MSG abcd $ts 5 hello"));
|
||||
test("double space 1", parseFailure("MSG abcd $ts 5 hello "));
|
||||
test("double space 2", parseFailure("MSG abcd $ts 5 hello "));
|
||||
test("double space 3", parseFailure("MSG abcd $ts 5 hello "));
|
||||
test("double space 4", parseFailure("MSG abcd $ts 5 hello "));
|
||||
group('MSG', () {
|
||||
final String ts = '2021-10-03T10:50:59.895Z';
|
||||
test('ok', parseSuccess('MSG abcd $ts 5 hello '));
|
||||
test('invalid base64', parseFailure('MSG abc $ts 5 hello '));
|
||||
test('invalid timestamp 1',
|
||||
parseFailure('MSG abc 2021-10-03T10:50:59.895 5 hello '));
|
||||
test('invalid timestamp 2',
|
||||
parseFailure('MSG abc 2021-14-03T10:50:59.895Z 5 hello '));
|
||||
test('no size', parseFailure('MSG abcd $ts hello '));
|
||||
test('incorrect size', parseFailure('MSG abcd $ts 6 hello '));
|
||||
test('no trailing space', parseFailure('MSG abcd $ts 5 hello'));
|
||||
test('double space 1', parseFailure('MSG abcd $ts 5 hello '));
|
||||
test('double space 2', parseFailure('MSG abcd $ts 5 hello '));
|
||||
test('double space 3', parseFailure('MSG abcd $ts 5 hello '));
|
||||
test('double space 4', parseFailure('MSG abcd $ts 5 hello '));
|
||||
});
|
||||
|
||||
group("ERR", () {
|
||||
test("ok 1", parseSuccess("ERR AUTH"));
|
||||
test("ok 2", parseSuccess("ERR CMD SYNTAX"));
|
||||
test("unknown error", parseFailure("ERR HELLO"));
|
||||
test("unknown CMD error", parseFailure("ERR CMD HELLO"));
|
||||
test("bad sub-error", parseFailure("ERR AUTH SYNTAX"));
|
||||
test("double space 1", parseFailure("ERR AUTH"));
|
||||
test("double space 2", parseFailure("ERR CMD SYNTAX"));
|
||||
test("double space 3", parseFailure("ERR CMD SYNTAX"));
|
||||
group('ERR', () {
|
||||
test('ok 1', parseSuccess('ERR AUTH'));
|
||||
test('ok 2', parseSuccess('ERR CMD SYNTAX'));
|
||||
test('unknown error', parseFailure('ERR HELLO'));
|
||||
test('unknown CMD error', parseFailure('ERR CMD HELLO'));
|
||||
test('bad sub-error', parseFailure('ERR AUTH SYNTAX'));
|
||||
test('double space 1', parseFailure('ERR AUTH'));
|
||||
test('double space 2', parseFailure('ERR CMD SYNTAX'));
|
||||
test('double space 3', parseFailure('ERR CMD SYNTAX'));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
3
packages/simplexmq_io/CHANGELOG.md
Normal file
3
packages/simplexmq_io/CHANGELOG.md
Normal file
@ -0,0 +1,3 @@
|
||||
## 1.0.0
|
||||
|
||||
- Initial version.
|
39
packages/simplexmq_io/README.md
Normal file
39
packages/simplexmq_io/README.md
Normal file
@ -0,0 +1,39 @@
|
||||
<!--
|
||||
This README describes the package. If you publish this package to pub.dev,
|
||||
this README's contents appear on the landing page for your package.
|
||||
|
||||
For information about how to write a good package README, see the guide for
|
||||
[writing package pages](https://dart.dev/guides/libraries/writing-package-pages).
|
||||
|
||||
For general information about developing packages, see the Dart guide for
|
||||
[creating packages](https://dart.dev/guides/libraries/create-library-packages)
|
||||
and the Flutter guide for
|
||||
[developing packages and plugins](https://flutter.dev/developing-packages).
|
||||
-->
|
||||
|
||||
TODO: Put a short description of the package here that helps potential users
|
||||
know whether this package might be useful for them.
|
||||
|
||||
## Features
|
||||
|
||||
TODO: List what your package can do. Maybe include images, gifs, or videos.
|
||||
|
||||
## Getting started
|
||||
|
||||
TODO: List prerequisites and provide or point to information on how to
|
||||
start using the package.
|
||||
|
||||
## Usage
|
||||
|
||||
TODO: Include short and useful examples for package users. Add longer examples
|
||||
to `/example` folder.
|
||||
|
||||
```dart
|
||||
const like = 'sample';
|
||||
```
|
||||
|
||||
## Additional information
|
||||
|
||||
TODO: Tell users more about the package: where to find more information, how to
|
||||
contribute to the package, how to file issues, what response they can expect
|
||||
from the package authors, and more.
|
6
packages/simplexmq_io/example/simplexmq_io_example.dart
Normal file
6
packages/simplexmq_io/example/simplexmq_io_example.dart
Normal file
@ -0,0 +1,6 @@
|
||||
// import 'package:simplexmq_io/simplexmq_io.dart';
|
||||
|
||||
// void main() {
|
||||
// var awesome = Awesome();
|
||||
// print('awesome: ${awesome.isAwesome}');
|
||||
// }
|
3
packages/simplexmq_io/lib/simplexmq_io.dart
Normal file
3
packages/simplexmq_io/lib/simplexmq_io.dart
Normal file
@ -0,0 +1,3 @@
|
||||
library simplexmq_io;
|
||||
|
||||
export 'src/socket.dart';
|
87
packages/simplexmq_io/lib/src/socket.dart
Normal file
87
packages/simplexmq_io/lib/src/socket.dart
Normal file
@ -0,0 +1,87 @@
|
||||
import 'dart:async';
|
||||
import 'dart:collection';
|
||||
import 'dart:io';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:simplexmq/simplexmq.dart';
|
||||
|
||||
class _STReaders {
|
||||
final Completer<Uint8List> completer = Completer();
|
||||
final int size;
|
||||
_STReaders(this.size);
|
||||
}
|
||||
|
||||
class SocketTransport implements Transport {
|
||||
final Socket _socket;
|
||||
late final StreamSubscription _subscription;
|
||||
// ignore: unused_field
|
||||
final Duration _timeout;
|
||||
|
||||
final int _bufferSize;
|
||||
Uint8List _buffer = Uint8List(0);
|
||||
final ListQueue<_STReaders> _readers = ListQueue(16);
|
||||
SocketTransport._new(this._socket, this._timeout, this._bufferSize);
|
||||
|
||||
static Future<SocketTransport> connect(String host, int port,
|
||||
{Duration timeout = const Duration(seconds: 1),
|
||||
int bufferSize = 16384}) async {
|
||||
final socket = await Socket.connect(host, port, timeout: timeout);
|
||||
final t = SocketTransport._new(socket, timeout, bufferSize);
|
||||
// ignore: cancel_subscriptions
|
||||
final subscription = socket.listen(t._onData,
|
||||
onError: (Object e) => t._finalize, onDone: t._finalize);
|
||||
t._subscription = subscription;
|
||||
return t;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Uint8List> read(int n) async {
|
||||
if (_readers.isEmpty && _buffer.length >= n) {
|
||||
final data = _buffer.sublist(0, n);
|
||||
_buffer = _buffer.sublist(n);
|
||||
return Future.value(data);
|
||||
}
|
||||
final r = _STReaders(n);
|
||||
_readers.add(r);
|
||||
return r.completer.future;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> write(Uint8List data) {
|
||||
_socket.add(data);
|
||||
return _socket.flush();
|
||||
}
|
||||
|
||||
void _onData(Uint8List data) {
|
||||
final b = Uint8List(_buffer.length + data.length);
|
||||
b.setAll(0, _buffer);
|
||||
b.setAll(_buffer.length, data);
|
||||
_buffer = b;
|
||||
while (_readers.isNotEmpty && _readers.first.size <= _buffer.length) {
|
||||
final r = _readers.removeFirst();
|
||||
final d = _buffer.sublist(0, r.size);
|
||||
r.completer.complete(d);
|
||||
_buffer = _buffer.sublist(r.size);
|
||||
}
|
||||
final overflow = _buffer.length >= _bufferSize;
|
||||
if (_subscription.isPaused && !overflow) {
|
||||
_subscription.resume();
|
||||
} else if (!_subscription.isPaused && overflow) {
|
||||
_subscription.pause();
|
||||
}
|
||||
}
|
||||
|
||||
void _finalize() {
|
||||
_subscription.cancel();
|
||||
_socket.destroy();
|
||||
while (_readers.isNotEmpty) {
|
||||
final r = _readers.removeFirst();
|
||||
r.completer.completeError(Exception('socket closed'));
|
||||
}
|
||||
}
|
||||
|
||||
/// Allow closing the client transport.
|
||||
void close() {
|
||||
_finalize();
|
||||
}
|
||||
}
|
22
packages/simplexmq_io/pubspec.yaml
Normal file
22
packages/simplexmq_io/pubspec.yaml
Normal file
@ -0,0 +1,22 @@
|
||||
name: simplexmq_io
|
||||
description: A starting point for Dart libraries or applications.
|
||||
version: 1.0.0
|
||||
publish_to: none
|
||||
|
||||
environment:
|
||||
sdk: '>=2.14.0 <3.0.0'
|
||||
|
||||
dependencies:
|
||||
simplexmq:
|
||||
git:
|
||||
url: git://github.com/simplex-chat/simplex-chat
|
||||
path: packages/simplexmq
|
||||
ref: ep/socket-transport
|
||||
|
||||
dependency_overrides:
|
||||
simplexmq:
|
||||
path: ../simplexmq
|
||||
|
||||
dev_dependencies:
|
||||
lints: ^1.0.0
|
||||
test: ^1.16.0
|
48
packages/simplexmq_io/test/simplexmq_io_test.dart
Normal file
48
packages/simplexmq_io/test/simplexmq_io_test.dart
Normal file
@ -0,0 +1,48 @@
|
||||
// ignore_for_file: prefer_double_quotes
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:simplexmq_io/simplexmq_io.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
const localhost = 'localhost';
|
||||
void main() {
|
||||
group('transport', () {
|
||||
Future<ServerSocket> startServer(
|
||||
void Function(Socket client) handleConnection) async {
|
||||
var server = await ServerSocket.bind(InternetAddress.anyIPv4, 0);
|
||||
server.listen(handleConnection);
|
||||
return server;
|
||||
}
|
||||
|
||||
test('simple write', () async {
|
||||
var completer = Completer<Uint8List>();
|
||||
|
||||
var server = await startServer((Socket client) {
|
||||
client.listen(
|
||||
(Uint8List data) async {
|
||||
completer.complete(data);
|
||||
},
|
||||
);
|
||||
});
|
||||
var transport = await SocketTransport.connect(localhost, server.port);
|
||||
await transport.write(Uint8List.fromList([1, 2, 3]));
|
||||
|
||||
expect(await completer.future, [1, 2, 3]);
|
||||
transport.close();
|
||||
await server.close();
|
||||
});
|
||||
|
||||
test('simple read', () async {
|
||||
var server = await startServer((Socket client) {
|
||||
client.add(Uint8List.fromList([1, 2, 3]));
|
||||
});
|
||||
var transport = await SocketTransport.connect(localhost, server.port);
|
||||
expect(await transport.read(3), [1, 2, 3]);
|
||||
transport.close();
|
||||
await server.close();
|
||||
});
|
||||
});
|
||||
}
|
Loading…
Reference in New Issue
Block a user