diff --git a/.gitignore b/.gitignore index 4a7f7174f..13645d2ca 100644 --- a/.gitignore +++ b/.gitignore @@ -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 + diff --git a/packages/analysis_options.yaml b/packages/analysis_options.yaml index 69bffee5b..0948b9fdc 100644 --- a/packages/analysis_options.yaml +++ b/packages/analysis_options.yaml @@ -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 diff --git a/packages/simplex_app/.gitignore b/packages/simplex_app/.gitignore new file mode 100644 index 000000000..8d69d6287 --- /dev/null +++ b/packages/simplex_app/.gitignore @@ -0,0 +1,2 @@ +# Keep for apps +!pubspec.lock \ No newline at end of file diff --git a/packages/simplexmq/.gitignore b/packages/simplexmq/.gitignore deleted file mode 100644 index e5208175c..000000000 --- a/packages/simplexmq/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -# Omit committing pubspec.lock for library packages; see -# https://dart.dev/guides/libraries/private-files#pubspeclock. -pubspec.lock diff --git a/packages/simplexmq/lib/simplexmq.dart b/packages/simplexmq/lib/simplexmq.dart index 5d1551352..2d08580d0 100644 --- a/packages/simplexmq/lib/simplexmq.dart +++ b/packages/simplexmq/lib/simplexmq.dart @@ -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; diff --git a/packages/simplexmq/lib/src/buffer.dart b/packages/simplexmq/lib/src/buffer.dart index c8865af8c..c700a95a5 100644 --- a/packages/simplexmq/lib/src/buffer.dart +++ b/packages/simplexmq/lib/src/buffer.dart @@ -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 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 bs) { } final _base64chars = Uint8List.fromList( - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" + 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' .codeUnits); List __base64lookup() { diff --git a/packages/simplexmq/lib/src/parser.dart b/packages/simplexmq/lib/src/parser.dart index a3538f663..0dc46f43c 100644 --- a/packages/simplexmq/lib/src/parser.dart +++ b/packages/simplexmq/lib/src/parser.dart @@ -1,18 +1,18 @@ -import "dart:typed_data"; -import "buffer.dart"; +import 'dart:typed_data'; +import 'buffer.dart'; typedef BinaryTags = Map; 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; diff --git a/packages/simplexmq/lib/src/protocol.dart b/packages/simplexmq/lib/src/protocol.dart index 7b44b1cf1..2e90d7684 100644 --- a/packages/simplexmq/lib/src/protocol.dart +++ b/packages/simplexmq/lib/src/protocol.dart @@ -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 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 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 diff --git a/packages/simplexmq/lib/src/transport.dart b/packages/simplexmq/lib/src/transport.dart new file mode 100644 index 000000000..93444d12a --- /dev/null +++ b/packages/simplexmq/lib/src/transport.dart @@ -0,0 +1,17 @@ +import 'dart:async'; +import 'dart:typed_data'; + +abstract class Transport { + Future read(int n); + Future write(Uint8List data); +} + +Stream blockStream(Transport t, int blockSize) async* { + try { + while (true) { + yield await t.read(blockSize); + } + } catch (e) { + return; + } +} diff --git a/packages/simplexmq/pubspec.yaml b/packages/simplexmq/pubspec.yaml index b82fce64c..ea5669f5f 100644 --- a/packages/simplexmq/pubspec.yaml +++ b/packages/simplexmq/pubspec.yaml @@ -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' diff --git a/packages/simplexmq/test/buffer_test.dart b/packages/simplexmq/test/buffer_test.dart index c4e425d0d..8fa050f33 100644 --- a/packages/simplexmq/test/buffer_test.dart +++ b/packages/simplexmq/test/buffer_test.dart @@ -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); }); }); } diff --git a/packages/simplexmq/test/protocol_test.dart b/packages/simplexmq/test/protocol_test.dart index 3a3f36f0d..0ecaea3b6 100644 --- a/packages/simplexmq/test/protocol_test.dart +++ b/packages/simplexmq/test/protocol_test.dart @@ -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')); }); }); }); diff --git a/packages/simplexmq_io/CHANGELOG.md b/packages/simplexmq_io/CHANGELOG.md new file mode 100644 index 000000000..effe43c82 --- /dev/null +++ b/packages/simplexmq_io/CHANGELOG.md @@ -0,0 +1,3 @@ +## 1.0.0 + +- Initial version. diff --git a/packages/simplexmq_io/README.md b/packages/simplexmq_io/README.md new file mode 100644 index 000000000..8b55e735b --- /dev/null +++ b/packages/simplexmq_io/README.md @@ -0,0 +1,39 @@ + + +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. diff --git a/packages/simplexmq_io/example/simplexmq_io_example.dart b/packages/simplexmq_io/example/simplexmq_io_example.dart new file mode 100644 index 000000000..acd05a745 --- /dev/null +++ b/packages/simplexmq_io/example/simplexmq_io_example.dart @@ -0,0 +1,6 @@ +// import 'package:simplexmq_io/simplexmq_io.dart'; + +// void main() { +// var awesome = Awesome(); +// print('awesome: ${awesome.isAwesome}'); +// } diff --git a/packages/simplexmq_io/lib/simplexmq_io.dart b/packages/simplexmq_io/lib/simplexmq_io.dart new file mode 100644 index 000000000..a3befe0cc --- /dev/null +++ b/packages/simplexmq_io/lib/simplexmq_io.dart @@ -0,0 +1,3 @@ +library simplexmq_io; + +export 'src/socket.dart'; diff --git a/packages/simplexmq_io/lib/src/socket.dart b/packages/simplexmq_io/lib/src/socket.dart new file mode 100644 index 000000000..c418ac397 --- /dev/null +++ b/packages/simplexmq_io/lib/src/socket.dart @@ -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 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 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 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 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(); + } +} diff --git a/packages/simplexmq_io/pubspec.yaml b/packages/simplexmq_io/pubspec.yaml new file mode 100644 index 000000000..e47cffcb9 --- /dev/null +++ b/packages/simplexmq_io/pubspec.yaml @@ -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 diff --git a/packages/simplexmq_io/test/simplexmq_io_test.dart b/packages/simplexmq_io/test/simplexmq_io_test.dart new file mode 100644 index 000000000..d7cc9c239 --- /dev/null +++ b/packages/simplexmq_io/test/simplexmq_io_test.dart @@ -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 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(); + + 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(); + }); + }); +}