SMP protocol commands encoding/decoding (#111)
* SMP protocol commands encoding/decoding * change "var" to type * Parser `word` method now returns null if the word is empty * refactor Parser `word` method * move Parser `end` getter * add linter rules, move linter options to root * remove omit_local_variable_types linter rule * ci: only build haskell on changes
This commit is contained in:
parent
fec99d526a
commit
a39cd2990f
@ -7,6 +7,9 @@ on:
|
|||||||
- v5
|
- v5
|
||||||
tags:
|
tags:
|
||||||
- "v*"
|
- "v*"
|
||||||
|
paths:
|
||||||
|
- haskell/**
|
||||||
|
- .github/workflows/build_haskell.yml
|
||||||
pull_request:
|
pull_request:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
@ -23,7 +23,8 @@ linter:
|
|||||||
# producing the lint.
|
# producing the lint.
|
||||||
rules:
|
rules:
|
||||||
# avoid_print: false # Uncomment to disable the `avoid_print` rule
|
# avoid_print: false # Uncomment to disable the `avoid_print` rule
|
||||||
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
|
prefer_double_quotes: true
|
||||||
|
constant_identifier_names: false
|
||||||
|
|
||||||
# Additional information about this file can be found at
|
# Additional information about this file can be found at
|
||||||
# https://dart.dev/guides/language/analysis-options
|
# https://dart.dev/guides/language/analysis-options
|
||||||
|
44
packages/analysis_options.yaml
Normal file
44
packages/analysis_options.yaml
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
include: package:lints/recommended.yaml
|
||||||
|
|
||||||
|
linter:
|
||||||
|
rules:
|
||||||
|
prefer_double_quotes: true
|
||||||
|
constant_identifier_names: false
|
||||||
|
always_declare_return_types: true
|
||||||
|
avoid_dynamic_calls: true
|
||||||
|
avoid_empty_else: true
|
||||||
|
avoid_relative_lib_imports: true
|
||||||
|
avoid_shadowing_type_parameters: true
|
||||||
|
avoid_slow_async_io: true
|
||||||
|
avoid_types_as_parameter_names: true
|
||||||
|
await_only_futures: true
|
||||||
|
camel_case_extensions: true
|
||||||
|
camel_case_types: true
|
||||||
|
cancel_subscriptions: true
|
||||||
|
curly_braces_in_flow_control_structures: true
|
||||||
|
directives_ordering: true
|
||||||
|
empty_catches: true
|
||||||
|
hash_and_equals: true
|
||||||
|
iterable_contains_unrelated_type: true
|
||||||
|
list_remove_unrelated_type: true
|
||||||
|
no_adjacent_strings_in_list: true
|
||||||
|
no_duplicate_case_values: true
|
||||||
|
package_api_docs: true
|
||||||
|
package_prefixed_library_names: true
|
||||||
|
prefer_generic_function_type_aliases: true
|
||||||
|
prefer_is_empty: true
|
||||||
|
prefer_is_not_empty: true
|
||||||
|
prefer_iterable_whereType: true
|
||||||
|
prefer_typing_uninitialized_variables: true
|
||||||
|
sort_child_properties_last: true
|
||||||
|
test_types_in_equals: true
|
||||||
|
throw_in_finally: true
|
||||||
|
unawaited_futures: true
|
||||||
|
unnecessary_import: true
|
||||||
|
unnecessary_null_aware_assignments: true
|
||||||
|
unnecessary_statements: true
|
||||||
|
unnecessary_type_check: true
|
||||||
|
unrelated_type_equality_checks: true
|
||||||
|
unsafe_html: true
|
||||||
|
use_full_hex_values_for_flutter_colors: true
|
||||||
|
valid_regexps: true
|
10
packages/simplexmq/.gitignore
vendored
Normal file
10
packages/simplexmq/.gitignore
vendored
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
# Files and directories created by pub.
|
||||||
|
.dart_tool/
|
||||||
|
.packages
|
||||||
|
|
||||||
|
# Conventional directory for build outputs.
|
||||||
|
build/
|
||||||
|
|
||||||
|
# Omit committing pubspec.lock for library packages; see
|
||||||
|
# https://dart.dev/guides/libraries/private-files#pubspeclock.
|
||||||
|
pubspec.lock
|
3
packages/simplexmq/CHANGELOG.md
Normal file
3
packages/simplexmq/CHANGELOG.md
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
## 1.0.0
|
||||||
|
|
||||||
|
- Initial version.
|
39
packages/simplexmq/README.md
Normal file
39
packages/simplexmq/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/example/simplexmq_example.dart
Normal file
6
packages/simplexmq/example/simplexmq_example.dart
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
// import 'package:simplexmq/simplexmq.dart';
|
||||||
|
|
||||||
|
// void main() {
|
||||||
|
// var awesome = Awesome();
|
||||||
|
// print('awesome: ${awesome.isAwesome}');
|
||||||
|
// }
|
8
packages/simplexmq/lib/simplexmq.dart
Normal file
8
packages/simplexmq/lib/simplexmq.dart
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
/// Support for doing something awesome.
|
||||||
|
///
|
||||||
|
/// More dartdocs go here.
|
||||||
|
library simplexmq;
|
||||||
|
|
||||||
|
export "src/protocol.dart";
|
||||||
|
|
||||||
|
// TODO: Export any libraries intended for clients of this package.
|
132
packages/simplexmq/lib/src/buffer.dart
Normal file
132
packages/simplexmq/lib/src/buffer.dart
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
import "dart:typed_data";
|
||||||
|
|
||||||
|
Uint8List encodeAscii(String s) => Uint8List.fromList(s.codeUnits);
|
||||||
|
|
||||||
|
String decodeAscii(Uint8List b) => String.fromCharCodes(b);
|
||||||
|
|
||||||
|
Uint8List concat(Uint8List b1, Uint8List b2) {
|
||||||
|
final a = Uint8List(b1.length + b2.length);
|
||||||
|
a.setAll(0, b1);
|
||||||
|
a.setAll(b1.length, b2);
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
|
T fold<T, E>(List<E> xs, T Function(T, E) combine, T initial) {
|
||||||
|
T res = initial;
|
||||||
|
for (final x in xs) {
|
||||||
|
res = combine(res, x);
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
Uint8List concatN(List<Uint8List> bs) {
|
||||||
|
final aLen = fold(bs, (int size, Uint8List b) => size + b.length, 0);
|
||||||
|
final a = Uint8List(aLen);
|
||||||
|
fold(bs, (int offset, Uint8List b) {
|
||||||
|
a.setAll(offset, b);
|
||||||
|
return offset + b.length;
|
||||||
|
}, 0);
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
|
final charSpace = " ".codeUnitAt(0);
|
||||||
|
final charEqual = "=".codeUnitAt(0);
|
||||||
|
final empty = Uint8List(0);
|
||||||
|
|
||||||
|
Uint8List unwords(Uint8List b1, Uint8List b2) {
|
||||||
|
final a = Uint8List(b1.length + b2.length + 1);
|
||||||
|
a.setAll(0, b1);
|
||||||
|
a[b1.length] = charSpace;
|
||||||
|
a.setAll(b1.length + 1, b2);
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
|
Uint8List unwordsN(List<Uint8List> bs) {
|
||||||
|
int i = bs.length;
|
||||||
|
int size = bs.length - 1;
|
||||||
|
while (i > 0) {
|
||||||
|
size += bs[--i].length;
|
||||||
|
}
|
||||||
|
final a = Uint8List(size);
|
||||||
|
|
||||||
|
int offset = 0;
|
||||||
|
for (i = 0; i < bs.length - 1; i++) {
|
||||||
|
final b = bs[i];
|
||||||
|
a.setAll(offset, b);
|
||||||
|
offset += b.length;
|
||||||
|
a[offset++] = charSpace;
|
||||||
|
}
|
||||||
|
a.setAll(offset, bs[i]);
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
|
final _base64chars = Uint8List.fromList(
|
||||||
|
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
|
||||||
|
.codeUnits);
|
||||||
|
|
||||||
|
List<int?> __base64lookup() {
|
||||||
|
final a = List<int?>.filled(256, null);
|
||||||
|
for (int i = 0; i < _base64chars.length; i++) {
|
||||||
|
a[_base64chars[i]] = i;
|
||||||
|
}
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
|
final _base64lookup = __base64lookup();
|
||||||
|
|
||||||
|
Uint8List encode64(Uint8List a) {
|
||||||
|
final len = a.length;
|
||||||
|
final b64len = (len / 3).ceil() * 4;
|
||||||
|
final b64 = Uint8List(b64len);
|
||||||
|
|
||||||
|
int j = 0;
|
||||||
|
for (int i = 0; i < len; i += 3) {
|
||||||
|
final e1 = i + 1 < len ? a[i + 1] : 0;
|
||||||
|
final e2 = i + 2 < len ? a[i + 2] : 0;
|
||||||
|
b64[j++] = _base64chars[a[i] >> 2];
|
||||||
|
b64[j++] = _base64chars[((a[i] & 3) << 4) | (e1 >> 4)];
|
||||||
|
b64[j++] = _base64chars[((e1 & 15) << 2) | (e2 >> 6)];
|
||||||
|
b64[j++] = _base64chars[e2 & 63];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (len % 3 != 0) b64[b64len - 1] = charEqual;
|
||||||
|
if (len % 3 == 1) b64[b64len - 2] = charEqual;
|
||||||
|
|
||||||
|
return b64;
|
||||||
|
}
|
||||||
|
|
||||||
|
Uint8List? decode64(Uint8List b64) {
|
||||||
|
int len = b64.length;
|
||||||
|
if (len % 4 != 0) return null;
|
||||||
|
int bLen = (len * 3) >> 2;
|
||||||
|
|
||||||
|
if (b64[len - 1] == charEqual) {
|
||||||
|
len--;
|
||||||
|
bLen--;
|
||||||
|
if (b64[len - 1] == charEqual) {
|
||||||
|
len--;
|
||||||
|
bLen--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final bytes = Uint8List(bLen);
|
||||||
|
|
||||||
|
int i = 0;
|
||||||
|
int pos = 0;
|
||||||
|
while (i < len) {
|
||||||
|
final enc1 = _base64lookup[b64[i++]];
|
||||||
|
final enc2 = i < len ? _base64lookup[b64[i++]] : 0;
|
||||||
|
final enc3 = i < len ? _base64lookup[b64[i++]] : 0;
|
||||||
|
final enc4 = i < len ? _base64lookup[b64[i++]] : 0;
|
||||||
|
if (enc1 == null || enc2 == null || enc3 == null || enc4 == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
bytes[pos++] = (enc1 << 2) | (enc2 >> 4);
|
||||||
|
int p = pos++;
|
||||||
|
if (p < bLen) bytes[p] = ((enc2 & 15) << 4) | (enc3 >> 2);
|
||||||
|
p = pos++;
|
||||||
|
if (p < bLen) bytes[p] = ((enc3 & 3) << 6) | (enc4 & 63);
|
||||||
|
}
|
||||||
|
|
||||||
|
return bytes;
|
||||||
|
}
|
136
packages/simplexmq/lib/src/parser.dart
Normal file
136
packages/simplexmq/lib/src/parser.dart
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
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("/");
|
||||||
|
|
||||||
|
class Parser {
|
||||||
|
final Uint8List _s;
|
||||||
|
int _pos = 0;
|
||||||
|
bool _fail = false;
|
||||||
|
Parser(this._s);
|
||||||
|
|
||||||
|
bool get fail => _fail;
|
||||||
|
bool get end => _pos >= _s.length;
|
||||||
|
|
||||||
|
// only calls `parse` if the parser did not previously fail
|
||||||
|
T? _run<T>(T? Function() parse) {
|
||||||
|
if (_fail || _pos >= _s.length) {
|
||||||
|
_fail = true;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
final res = parse();
|
||||||
|
if (res == null) _fail = true;
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
// takes a required number of bytes
|
||||||
|
Uint8List? take(int len) => _run(() {
|
||||||
|
final end = _pos + len;
|
||||||
|
if (end > _s.length) return null;
|
||||||
|
final res = _s.sublist(_pos, end);
|
||||||
|
_pos = end;
|
||||||
|
return res;
|
||||||
|
});
|
||||||
|
|
||||||
|
// takes chars (> 0) while condition is true; function isAlphaNum or isDigit can be used
|
||||||
|
Uint8List? takeWhile1(bool Function(int) f) => _run(() {
|
||||||
|
final pos = _pos;
|
||||||
|
while (f(_s[_pos])) {
|
||||||
|
_pos++;
|
||||||
|
}
|
||||||
|
return _pos > pos ? _s.sublist(pos, _pos) : null;
|
||||||
|
});
|
||||||
|
|
||||||
|
// takes the non-empty word until the first space or until the end of the string
|
||||||
|
Uint8List? word() => _run(() {
|
||||||
|
int pos = _s.indexOf(charSpace, _pos);
|
||||||
|
if (pos == -1) pos = _s.length;
|
||||||
|
if (pos <= _pos) return null;
|
||||||
|
final res = _s.sublist(_pos, pos);
|
||||||
|
_pos = pos;
|
||||||
|
return res;
|
||||||
|
});
|
||||||
|
|
||||||
|
bool? str(Uint8List s) => _run(() {
|
||||||
|
for (int i = 0, j = _pos; i < s.length; i++, j++) {
|
||||||
|
if (s[i] != _s[j]) return null;
|
||||||
|
}
|
||||||
|
_pos += s.length;
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
// takes space
|
||||||
|
bool? space() => _run(() {
|
||||||
|
if (_s[_pos] == charSpace) {
|
||||||
|
_pos++;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
int? decimal() => _run(() {
|
||||||
|
final s = takeWhile1(isDigit);
|
||||||
|
if (s == null) return null;
|
||||||
|
int n = 0;
|
||||||
|
for (int i = 0; i < s.length; i++) {
|
||||||
|
n *= 10;
|
||||||
|
n += s[i] - char0;
|
||||||
|
}
|
||||||
|
return n;
|
||||||
|
});
|
||||||
|
|
||||||
|
DateTime? datetime() => _run(() {
|
||||||
|
final s = word();
|
||||||
|
if (s != null) return DateTime.tryParse(decodeAscii(s));
|
||||||
|
});
|
||||||
|
|
||||||
|
// takes base-64 encoded string and returns decoded binary
|
||||||
|
Uint8List? base64() => _run(() {
|
||||||
|
bool tryCharEqual() {
|
||||||
|
final ok = _pos < _s.length && _s[_pos] == charEqual;
|
||||||
|
if (ok) _pos++;
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
final pos = _pos;
|
||||||
|
int c;
|
||||||
|
do {
|
||||||
|
c = _s[_pos];
|
||||||
|
} while ((isAlphaNum(c) || c == charPlus || c == charSlash) &&
|
||||||
|
++_pos < _s.length);
|
||||||
|
|
||||||
|
if (tryCharEqual()) tryCharEqual();
|
||||||
|
return _pos > pos ? decode64(_s.sublist(pos, _pos)) : null;
|
||||||
|
});
|
||||||
|
|
||||||
|
// takes one of the binary tags and returns its key
|
||||||
|
T? someStr<T>(BinaryTags<T> ts) => _run(() {
|
||||||
|
outer:
|
||||||
|
for (final t in ts.entries) {
|
||||||
|
final s = t.value;
|
||||||
|
for (int i = 0, j = _pos; i < s.length; i++, j++) {
|
||||||
|
if (s[i] != _s[j]) continue outer;
|
||||||
|
}
|
||||||
|
_pos += s.length;
|
||||||
|
return t.key;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isDigit(int c) => c >= char0 && c <= char9;
|
||||||
|
|
||||||
|
bool isAlphaNum(int c) =>
|
||||||
|
(c >= char0 && c <= char9) ||
|
||||||
|
(c >= charLowerA && c <= charLowerZ) ||
|
||||||
|
(c >= charUpperA && c <= charUpperZ);
|
268
packages/simplexmq/lib/src/protocol.dart
Normal file
268
packages/simplexmq/lib/src/protocol.dart
Normal file
@ -0,0 +1,268 @@
|
|||||||
|
import "dart:typed_data";
|
||||||
|
import "buffer.dart";
|
||||||
|
import "parser.dart";
|
||||||
|
|
||||||
|
abstract class SMPCommand {
|
||||||
|
Uint8List serialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class ClientCommand extends SMPCommand {}
|
||||||
|
|
||||||
|
abstract class BrokerCommand extends SMPCommand {}
|
||||||
|
|
||||||
|
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");
|
||||||
|
|
||||||
|
enum SMPCmdTag {
|
||||||
|
NEW,
|
||||||
|
SUB,
|
||||||
|
KEY,
|
||||||
|
ACK,
|
||||||
|
OFF,
|
||||||
|
DEL,
|
||||||
|
SEND,
|
||||||
|
PING,
|
||||||
|
IDS,
|
||||||
|
MSG,
|
||||||
|
END,
|
||||||
|
OK,
|
||||||
|
ERR,
|
||||||
|
PONG,
|
||||||
|
}
|
||||||
|
|
||||||
|
final BinaryTags<SMPCmdTag> smpCmdTags = {
|
||||||
|
SMPCmdTag.NEW: cNEW,
|
||||||
|
SMPCmdTag.SUB: cSUB,
|
||||||
|
SMPCmdTag.KEY: cKEY,
|
||||||
|
SMPCmdTag.ACK: cACK,
|
||||||
|
SMPCmdTag.OFF: cOFF,
|
||||||
|
SMPCmdTag.DEL: cDEL,
|
||||||
|
SMPCmdTag.SEND: cSEND,
|
||||||
|
SMPCmdTag.PING: cPING,
|
||||||
|
SMPCmdTag.IDS: cIDS,
|
||||||
|
SMPCmdTag.MSG: cMSG,
|
||||||
|
SMPCmdTag.END: cEND,
|
||||||
|
SMPCmdTag.OK: cOK,
|
||||||
|
SMPCmdTag.ERR: cERR,
|
||||||
|
SMPCmdTag.PONG: cPONG,
|
||||||
|
};
|
||||||
|
|
||||||
|
class NEW extends ClientCommand {
|
||||||
|
final Uint8List rcvPubKey;
|
||||||
|
NEW(this.rcvPubKey);
|
||||||
|
@override
|
||||||
|
Uint8List serialize() => unwords(cNEW, serializePubKey(rcvPubKey));
|
||||||
|
}
|
||||||
|
|
||||||
|
class SUB extends ClientCommand {
|
||||||
|
@override
|
||||||
|
Uint8List serialize() => cSUB;
|
||||||
|
}
|
||||||
|
|
||||||
|
class KEY extends ClientCommand {
|
||||||
|
final Uint8List sndPubKey;
|
||||||
|
KEY(this.sndPubKey);
|
||||||
|
@override
|
||||||
|
Uint8List serialize() => unwords(cKEY, serializePubKey(sndPubKey));
|
||||||
|
}
|
||||||
|
|
||||||
|
class ACK extends ClientCommand {
|
||||||
|
@override
|
||||||
|
Uint8List serialize() => cACK;
|
||||||
|
}
|
||||||
|
|
||||||
|
class OFF extends ClientCommand {
|
||||||
|
@override
|
||||||
|
Uint8List serialize() => cOFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
class DEL extends ClientCommand {
|
||||||
|
@override
|
||||||
|
Uint8List serialize() => cDEL;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Uint8List> serializeMsg(Uint8List msg) =>
|
||||||
|
[encodeAscii(msg.length.toString()), msg, empty];
|
||||||
|
|
||||||
|
class SEND extends ClientCommand {
|
||||||
|
final Uint8List msgBody;
|
||||||
|
SEND(this.msgBody);
|
||||||
|
@override
|
||||||
|
Uint8List serialize() => unwordsN([cSEND, ...serializeMsg(msgBody)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
class PING extends ClientCommand {
|
||||||
|
@override
|
||||||
|
Uint8List serialize() => cPING;
|
||||||
|
}
|
||||||
|
|
||||||
|
class IDS extends BrokerCommand {
|
||||||
|
final Uint8List rcvId;
|
||||||
|
final Uint8List sndId;
|
||||||
|
IDS(this.rcvId, this.sndId) : super();
|
||||||
|
@override
|
||||||
|
Uint8List serialize() => unwordsN([cIDS, encode64(rcvId), encode64(sndId)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
class MSG extends BrokerCommand {
|
||||||
|
final Uint8List msgId;
|
||||||
|
final DateTime ts;
|
||||||
|
final Uint8List msgBody;
|
||||||
|
MSG(this.msgId, this.ts, this.msgBody);
|
||||||
|
@override
|
||||||
|
Uint8List serialize() => unwordsN([
|
||||||
|
cMSG,
|
||||||
|
encode64(msgId),
|
||||||
|
encodeAscii(ts.toIso8601String()),
|
||||||
|
...serializeMsg(msgBody)
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
class END extends BrokerCommand {
|
||||||
|
@override
|
||||||
|
Uint8List serialize() => cEND;
|
||||||
|
}
|
||||||
|
|
||||||
|
class OK extends BrokerCommand {
|
||||||
|
@override
|
||||||
|
Uint8List serialize() => cOK;
|
||||||
|
}
|
||||||
|
|
||||||
|
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"),
|
||||||
|
};
|
||||||
|
|
||||||
|
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"),
|
||||||
|
};
|
||||||
|
|
||||||
|
class ERR extends BrokerCommand {
|
||||||
|
final ErrorType err;
|
||||||
|
final CmdErrorType? cmdErr;
|
||||||
|
ERR(this.err)
|
||||||
|
: cmdErr = err == ErrorType.CMD
|
||||||
|
? throw ArgumentError("CMD error should be created with ERR.CMD")
|
||||||
|
: null;
|
||||||
|
ERR.cmd(this.cmdErr) : err = ErrorType.CMD;
|
||||||
|
@override
|
||||||
|
Uint8List serialize() {
|
||||||
|
final _err = errorTags[err]!;
|
||||||
|
return cmdErr == null
|
||||||
|
? unwords(cERR, _err)
|
||||||
|
: unwordsN([cERR, _err, cmdErrorTags[cmdErr!]!]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class PONG extends BrokerCommand {
|
||||||
|
@override
|
||||||
|
Uint8List serialize() => cPONG;
|
||||||
|
}
|
||||||
|
|
||||||
|
final Map<SMPCmdTag, SMPCommand? Function(Parser p)> smpCmdParsers = {
|
||||||
|
SMPCmdTag.NEW: (p) {
|
||||||
|
p.space();
|
||||||
|
final key = pubKeyP(p);
|
||||||
|
if (key != null) return NEW(key);
|
||||||
|
},
|
||||||
|
SMPCmdTag.SUB: (_) => SUB(),
|
||||||
|
SMPCmdTag.KEY: (p) {
|
||||||
|
p.space();
|
||||||
|
final key = pubKeyP(p);
|
||||||
|
if (key != null) return KEY(key);
|
||||||
|
},
|
||||||
|
SMPCmdTag.ACK: (_) => ACK(),
|
||||||
|
SMPCmdTag.OFF: (_) => OFF(),
|
||||||
|
SMPCmdTag.DEL: (_) => DEL(),
|
||||||
|
SMPCmdTag.SEND: (p) {
|
||||||
|
p.space();
|
||||||
|
final msg = messageP(p);
|
||||||
|
if (msg != null) return SEND(msg);
|
||||||
|
},
|
||||||
|
SMPCmdTag.PING: (_) => PING(),
|
||||||
|
SMPCmdTag.IDS: (p) {
|
||||||
|
p.space();
|
||||||
|
final rId = p.base64();
|
||||||
|
p.space();
|
||||||
|
final sId = p.base64();
|
||||||
|
if (rId != null && sId != null) return IDS(rId, sId);
|
||||||
|
},
|
||||||
|
SMPCmdTag.MSG: (p) {
|
||||||
|
p.space();
|
||||||
|
final msgId = p.base64();
|
||||||
|
p.space();
|
||||||
|
final ts = p.datetime();
|
||||||
|
p.space();
|
||||||
|
final msg = messageP(p);
|
||||||
|
if (msgId != null && ts != null && msg != null) return MSG(msgId, ts, msg);
|
||||||
|
},
|
||||||
|
SMPCmdTag.END: (_) => END(),
|
||||||
|
SMPCmdTag.OK: (_) => OK(),
|
||||||
|
SMPCmdTag.ERR: (p) {
|
||||||
|
p.space();
|
||||||
|
final err = p.someStr(errorTags);
|
||||||
|
if (err == ErrorType.CMD) {
|
||||||
|
p.space();
|
||||||
|
final cmdErr = p.someStr(cmdErrorTags);
|
||||||
|
if (cmdErr != null) return ERR.cmd(cmdErr);
|
||||||
|
} else if (err != null) {
|
||||||
|
return ERR(err);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
SMPCmdTag.PONG: (_) => PONG(),
|
||||||
|
};
|
||||||
|
|
||||||
|
SMPCommand? smpCommandP(Parser p) {
|
||||||
|
final cmd = p.someStr(smpCmdTags);
|
||||||
|
return p.fail ? null : smpCmdParsers[cmd]!(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
SMPCommand? parseSMPCommand(Uint8List s) {
|
||||||
|
final p = Parser(s);
|
||||||
|
final cmd = smpCommandP(p);
|
||||||
|
if (cmd != null && p.end) return cmd;
|
||||||
|
}
|
||||||
|
|
||||||
|
Uint8List? pubKeyP(Parser p) {
|
||||||
|
p.str(rsaPrefix);
|
||||||
|
return p.base64();
|
||||||
|
}
|
||||||
|
|
||||||
|
Uint8List? messageP(Parser p) {
|
||||||
|
final len = p.decimal();
|
||||||
|
p.space();
|
||||||
|
Uint8List? msg;
|
||||||
|
if (len != null) msg = p.take(len);
|
||||||
|
p.space();
|
||||||
|
return p.fail ? null : msg;
|
||||||
|
}
|
15
packages/simplexmq/pubspec.yaml
Normal file
15
packages/simplexmq/pubspec.yaml
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
name: simplexmq
|
||||||
|
description: A starting point for Dart libraries or applications.
|
||||||
|
version: 0.0.1
|
||||||
|
# homepage: https://www.example.com
|
||||||
|
|
||||||
|
environment:
|
||||||
|
sdk: '>=2.14.0 <3.0.0'
|
||||||
|
|
||||||
|
|
||||||
|
# dependencies:
|
||||||
|
# path: ^1.8.0
|
||||||
|
|
||||||
|
dev_dependencies:
|
||||||
|
lints: ^1.0.0
|
||||||
|
test: ^1.16.0
|
66
packages/simplexmq/test/buffer_test.dart
Normal file
66
packages/simplexmq/test/buffer_test.dart
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
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]);
|
||||||
|
|
||||||
|
class Base64Test {
|
||||||
|
final String description;
|
||||||
|
final String binary;
|
||||||
|
final String base64;
|
||||||
|
|
||||||
|
Base64Test(this.binary, this.base64) : description = binary;
|
||||||
|
Base64Test.withDescription(this.description, this.binary, this.base64);
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group("ascii encoding/decoding", () {
|
||||||
|
test("encodeAscii", () {
|
||||||
|
expect(encodeAscii("hello123"), hello123);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("decodeAscii", () {
|
||||||
|
expect(decodeAscii(hello123), "hello123");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group("base-64 encoding/decoding", () {
|
||||||
|
String allBinaryChars() {
|
||||||
|
final a = Uint8List(256);
|
||||||
|
for (var i = 0; i < 256; i++) {
|
||||||
|
a[i] = i;
|
||||||
|
}
|
||||||
|
return decodeAscii(a);
|
||||||
|
}
|
||||||
|
|
||||||
|
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.withDescription(
|
||||||
|
"all binary chars",
|
||||||
|
allBinaryChars(),
|
||||||
|
"AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn+AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq+wsbKztLW2t7i5uru8vb6/wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t/g4eLj5OXm5+jp6uvs7e7v8PHy8/T19vf4+fr7/P3+/w==",
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
test("encode64", () {
|
||||||
|
for (final t in base64tests) {
|
||||||
|
expect(encode64(encodeAscii(t.binary)), encodeAscii(t.base64));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
105
packages/simplexmq/test/protocol_test.dart
Normal file
105
packages/simplexmq/test/protocol_test.dart
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
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", () {
|
||||||
|
Null Function() parseSerialize(SMPCommand cmd) => () {
|
||||||
|
final s = cmd.serialize();
|
||||||
|
expect(parseSMPCommand(s)?.serialize(), s);
|
||||||
|
expect(parseSMPCommand(concat(s, Uint8List.fromList([charSpace]))),
|
||||||
|
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(
|
||||||
|
"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", () {
|
||||||
|
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("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("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("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"));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
240
pubspec.lock
240
pubspec.lock
@ -1,6 +1,27 @@
|
|||||||
# Generated by pub
|
# Generated by pub
|
||||||
# See https://dart.dev/tools/pub/glossary#lockfile
|
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||||
packages:
|
packages:
|
||||||
|
_fe_analyzer_shared:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: _fe_analyzer_shared
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "27.0.0"
|
||||||
|
analyzer:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: analyzer
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.4.0"
|
||||||
|
args:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: args
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.3.0"
|
||||||
async:
|
async:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -29,6 +50,13 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.1"
|
version: "1.3.1"
|
||||||
|
cli_util:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: cli_util
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.3.4"
|
||||||
clock:
|
clock:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -43,6 +71,27 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.15.0"
|
version: "1.15.0"
|
||||||
|
convert:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: convert
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.1"
|
||||||
|
coverage:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: coverage
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.3"
|
||||||
|
crypto:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: crypto
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.1"
|
||||||
cupertino_icons:
|
cupertino_icons:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -57,6 +106,13 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.2.0"
|
version: "1.2.0"
|
||||||
|
file:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: file
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "6.1.2"
|
||||||
flutter:
|
flutter:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description: flutter
|
description: flutter
|
||||||
@ -74,6 +130,48 @@ packages:
|
|||||||
description: flutter
|
description: flutter
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.0"
|
version: "0.0.0"
|
||||||
|
frontend_server_client:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: frontend_server_client
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.2"
|
||||||
|
glob:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: glob
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.2"
|
||||||
|
http_multi_server:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: http_multi_server
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.1"
|
||||||
|
http_parser:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: http_parser
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "4.0.0"
|
||||||
|
io:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: io
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.3"
|
||||||
|
js:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: js
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.6.3"
|
||||||
lints:
|
lints:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -81,6 +179,13 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.1"
|
version: "1.0.1"
|
||||||
|
logging:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: logging
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.2"
|
||||||
matcher:
|
matcher:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -95,6 +200,27 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.7.0"
|
version: "1.7.0"
|
||||||
|
mime:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: mime
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.0"
|
||||||
|
node_preamble:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: node_preamble
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.1"
|
||||||
|
package_config:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: package_config
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.2"
|
||||||
path:
|
path:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -102,11 +228,74 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.8.0"
|
version: "1.8.0"
|
||||||
|
pedantic:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: pedantic
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.11.1"
|
||||||
|
pool:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: pool
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.5.0"
|
||||||
|
pub_semver:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: pub_semver
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.0"
|
||||||
|
shelf:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shelf
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.2.0"
|
||||||
|
shelf_packages_handler:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shelf_packages_handler
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.0"
|
||||||
|
shelf_static:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shelf_static
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.0"
|
||||||
|
shelf_web_socket:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shelf_web_socket
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.1"
|
||||||
sky_engine:
|
sky_engine:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description: flutter
|
description: flutter
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.99"
|
version: "0.0.99"
|
||||||
|
source_map_stack_trace:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: source_map_stack_trace
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.0"
|
||||||
|
source_maps:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: source_maps
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.10.10"
|
||||||
source_span:
|
source_span:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -142,6 +331,13 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.2.0"
|
version: "1.2.0"
|
||||||
|
test:
|
||||||
|
dependency: "direct dev"
|
||||||
|
description:
|
||||||
|
name: test
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.17.10"
|
||||||
test_api:
|
test_api:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -149,6 +345,13 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.4.2"
|
version: "0.4.2"
|
||||||
|
test_core:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: test_core
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.4.0"
|
||||||
typed_data:
|
typed_data:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -163,5 +366,40 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.0"
|
version: "2.1.0"
|
||||||
|
vm_service:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: vm_service
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "7.3.0"
|
||||||
|
watcher:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: watcher
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.1"
|
||||||
|
web_socket_channel:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: web_socket_channel
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.0"
|
||||||
|
webkit_inspection_protocol:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: webkit_inspection_protocol
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.0"
|
||||||
|
yaml:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: yaml
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "3.1.0"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=2.12.0 <3.0.0"
|
dart: ">=2.14.0 <3.0.0"
|
||||||
|
@ -18,7 +18,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
|||||||
version: 1.0.0+1
|
version: 1.0.0+1
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=2.12.0 <3.0.0"
|
sdk: ">=2.14.0 <3.0.0"
|
||||||
|
|
||||||
# Dependencies specify other packages that your package needs in order to work.
|
# Dependencies specify other packages that your package needs in order to work.
|
||||||
# To automatically upgrade your package dependencies to the latest versions
|
# To automatically upgrade your package dependencies to the latest versions
|
||||||
@ -45,6 +45,7 @@ dev_dependencies:
|
|||||||
# package. See that file for information about deactivating specific lint
|
# package. See that file for information about deactivating specific lint
|
||||||
# rules and activating additional ones.
|
# rules and activating additional ones.
|
||||||
flutter_lints: ^1.0.0
|
flutter_lints: ^1.0.0
|
||||||
|
test: ^1.17.10
|
||||||
|
|
||||||
# For information on the generic Dart part of this file, see the
|
# For information on the generic Dart part of this file, see the
|
||||||
# following page: https://dart.dev/tools/pub/pubspec
|
# following page: https://dart.dev/tools/pub/pubspec
|
||||||
|
@ -5,26 +5,26 @@
|
|||||||
// gestures. You can also use WidgetTester to find child widgets in the widget
|
// gestures. You can also use WidgetTester to find child widgets in the widget
|
||||||
// tree, read text, and verify that the values of widget properties are correct.
|
// tree, read text, and verify that the values of widget properties are correct.
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import "package:flutter/material.dart";
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import "package:flutter_test/flutter_test.dart";
|
||||||
|
|
||||||
import 'package:simplex_chat/main.dart';
|
import "package:simplex_chat/main.dart";
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
|
testWidgets("Counter increments smoke test", (WidgetTester tester) async {
|
||||||
// Build our app and trigger a frame.
|
// Build our app and trigger a frame.
|
||||||
await tester.pumpWidget(const MyApp());
|
await tester.pumpWidget(const MyApp());
|
||||||
|
|
||||||
// Verify that our counter starts at 0.
|
// Verify that our counter starts at 0.
|
||||||
expect(find.text('0'), findsOneWidget);
|
expect(find.text("0"), findsOneWidget);
|
||||||
expect(find.text('1'), findsNothing);
|
expect(find.text("1"), findsNothing);
|
||||||
|
|
||||||
// Tap the '+' icon and trigger a frame.
|
// Tap the "+" icon and trigger a frame.
|
||||||
await tester.tap(find.byIcon(Icons.add));
|
await tester.tap(find.byIcon(Icons.add));
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
|
|
||||||
// Verify that our counter has incremented.
|
// Verify that our counter has incremented.
|
||||||
expect(find.text('0'), findsNothing);
|
expect(find.text("0"), findsNothing);
|
||||||
expect(find.text('1'), findsOneWidget);
|
expect(find.text("1"), findsOneWidget);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user