Files
Qlyra/lib/core/webpush/max_web_protocol.dart
sevenhill f74057ce9b
Build Android (FCM) / build-android-fcm (push) Canceled after 0s
Build Android / build-android (push) Canceled after 0s
Build iOS / build-ios (push) Canceled after 0s
Build Linux / build-linux (push) Canceled after 0s
Build macOS / build-macos (push) Canceled after 0s
Build Windows / build-windows (push) Canceled after 0s
Release (main) / android (oneme) (push) Canceled after 0s
Release (main) / android (qlyra) (push) Canceled after 0s
Release (main) / windows (push) Canceled after 0s
Release (main) / linux (push) Canceled after 0s
Release (main) / macos (push) Canceled after 0s
Release (main) / ios (push) Canceled after 0s
Release (main) / release (push) Canceled after 0s
Rebrand application as Qlyra
2026-08-30 13:16:17 +03:00

484 lines
13 KiB
Dart
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import 'dart:convert';
import 'dart:typed_data';
class MaxWebFrame {
final int cmd;
final int seq;
final int opcode;
final Object? payload;
const MaxWebFrame({
required this.cmd,
required this.seq,
required this.opcode,
this.payload,
});
bool get isOk => cmd == MaxWebCmd.ok;
bool get isError => cmd != MaxWebCmd.ok && cmd != MaxWebCmd.request;
}
abstract class MaxWebCmd {
static const int request = 0;
static const int ok = 1;
static const int notFound = 2;
static const int error = 3;
}
abstract class MaxWebFraming {
static const int protocolVersion = 10;
static const int headerSize = 10;
static const int maxPayloadSize = 0x00ffffff;
static const int maxDecompressedPayloadSize = 64 * 1024 * 1024;
static Uint8List encode({
required int cmd,
required int seq,
required int opcode,
Object? payload,
}) {
final body = payload == null ? Uint8List(0) : MaxMsgpack.encode(payload);
if (body.length > maxPayloadSize) {
throw ArgumentError.value(
body.length,
'payload',
'превышает допустимый размер кадра',
);
}
final frame = Uint8List(headerSize + body.length);
final view = ByteData.view(frame.buffer);
view.setUint8(0, protocolVersion);
view.setUint8(1, cmd);
view.setInt16(2, seq);
view.setInt16(4, opcode);
view.setUint8(6, 0);
view.setUint8(7, (body.length >> 16) & 0xFF);
view.setUint8(8, (body.length >> 8) & 0xFF);
view.setUint8(9, body.length & 0xFF);
frame.setRange(headerSize, frame.length, body);
return frame;
}
static MaxWebFrame decode(Uint8List frame) {
if (frame.length < headerSize) {
throw const FormatException('MaxWebFraming: кадр короче заголовка');
}
final view = ByteData.view(frame.buffer, frame.offsetInBytes, frame.length);
final cmd = view.getUint8(1);
final seq = view.getInt16(2);
final opcode = view.getInt16(4);
final compressionRatio = view.getUint8(6);
final length =
(view.getUint8(7) << 16) | (view.getUint8(8) << 8) | view.getUint8(9);
final expectedLength = headerSize + length;
if (frame.length != expectedLength) {
throw FormatException(
'MaxWebFraming: длина кадра ${frame.length} не совпадает с $expectedLength',
);
}
if (length <= 0) {
return MaxWebFrame(cmd: cmd, seq: seq, opcode: opcode);
}
var body = Uint8List.sublistView(frame, headerSize, headerSize + length);
if (compressionRatio > 0) {
body = Lz4Block.decompress(body, maxDecompressedPayloadSize);
}
return MaxWebFrame(
cmd: cmd,
seq: seq,
opcode: opcode,
payload: MaxMsgpack.decode(body),
);
}
}
abstract class Lz4Block {
static Uint8List decompress(Uint8List source, int maxOutputSize) {
if (maxOutputSize < 0) {
throw ArgumentError.value(maxOutputSize, 'maxOutputSize');
}
final output = <int>[];
var input = 0;
while (input < source.length) {
final token = source[input++];
var literalLength = token >> 4;
if (literalLength == 15) {
literalLength += _readLengthExtension(
source,
() => input,
(v) => input = v,
);
}
if (literalLength > source.length - input) {
throw const FormatException('Lz4Block: обрыв литералов');
}
if (literalLength > maxOutputSize - output.length) {
throw const FormatException('Lz4Block: литералы не помещаются');
}
output.addAll(
Uint8List.sublistView(source, input, input + literalLength),
);
input += literalLength;
if (input >= source.length) break;
if (source.length - input < 2) {
throw const FormatException('Lz4Block: обрыв смещения совпадения');
}
final offset = source[input] | (source[input + 1] << 8);
input += 2;
if (offset == 0 || offset > output.length) {
throw const FormatException('Lz4Block: неверное смещение совпадения');
}
var matchLength = token & 0x0F;
if (matchLength == 15) {
matchLength += _readLengthExtension(
source,
() => input,
(v) => input = v,
);
}
matchLength += 4;
if (matchLength > maxOutputSize - output.length) {
throw const FormatException('Lz4Block: совпадение не помещается');
}
for (var i = 0; i < matchLength; i++) {
output.add(output[output.length - offset]);
}
}
return Uint8List.fromList(output);
}
static int _readLengthExtension(
Uint8List source,
int Function() get,
void Function(int) set,
) {
var cursor = get();
var extra = 0;
while (true) {
if (cursor >= source.length) {
throw const FormatException('Lz4Block: обрыв в расширении длины');
}
final byte = source[cursor++];
extra += byte;
if (byte != 255) break;
}
set(cursor);
return extra;
}
}
abstract class MaxMsgpack {
static Uint8List encode(Object? value) {
final sink = BytesBuilder(copy: false);
_write(sink, value);
return sink.takeBytes();
}
static Object? decode(Uint8List bytes) => _Reader(bytes).read();
static void _write(BytesBuilder sink, Object? value) {
if (value == null) {
sink.addByte(0xC0);
} else if (value is bool) {
sink.addByte(value ? 0xC3 : 0xC2);
} else if (value is int) {
_writeInt(sink, value);
} else if (value is double) {
final buffer = ByteData(9)
..setUint8(0, 0xCB)
..setFloat64(1, value);
sink.add(buffer.buffer.asUint8List());
} else if (value is String) {
_writeString(sink, value);
} else if (value is Uint8List) {
_writeBinary(sink, value);
} else if (value is List) {
_writePrefix(sink, value.length, 0x90, 0xDC, 0xDD);
for (final item in value) {
_write(sink, item);
}
} else if (value is Map) {
_writePrefix(sink, value.length, 0x80, 0xDE, 0xDF);
value.forEach((key, item) {
_write(sink, key);
_write(sink, item);
});
} else {
throw ArgumentError(
'MaxMsgpack: неподдерживаемый тип ${value.runtimeType}',
);
}
}
static void _writePrefix(
BytesBuilder sink,
int length,
int fixBase,
int wide16,
int wide32,
) {
if (length < 16) {
sink.addByte(fixBase | length);
} else if (length < 0x10000) {
sink.addByte(wide16);
sink.add([(length >> 8) & 0xFF, length & 0xFF]);
} else {
sink.addByte(wide32);
sink.add([
(length >> 24) & 0xFF,
(length >> 16) & 0xFF,
(length >> 8) & 0xFF,
length & 0xFF,
]);
}
}
static void _writeString(BytesBuilder sink, String value) {
final utf8Bytes = utf8.encode(value);
final length = utf8Bytes.length;
if (length < 32) {
sink.addByte(0xA0 | length);
} else if (length < 0x100) {
sink.add([0xD9, length]);
} else if (length < 0x10000) {
sink.add([0xDA, (length >> 8) & 0xFF, length & 0xFF]);
} else {
sink.add([
0xDB,
(length >> 24) & 0xFF,
(length >> 16) & 0xFF,
(length >> 8) & 0xFF,
length & 0xFF,
]);
}
sink.add(utf8Bytes);
}
static void _writeBinary(BytesBuilder sink, Uint8List value) {
final length = value.length;
if (length < 0x100) {
sink.add([0xC4, length]);
} else if (length < 0x10000) {
sink.add([0xC5, (length >> 8) & 0xFF, length & 0xFF]);
} else {
sink.add([
0xC6,
(length >> 24) & 0xFF,
(length >> 16) & 0xFF,
(length >> 8) & 0xFF,
length & 0xFF,
]);
}
sink.add(value);
}
static void _writeInt(BytesBuilder sink, int value) {
if (value >= 0) {
if (value < 0x80) {
sink.addByte(value);
} else if (value < 0x100) {
sink.add([0xCC, value]);
} else if (value < 0x10000) {
sink.add([0xCD, (value >> 8) & 0xFF, value & 0xFF]);
} else if (value < 0x100000000) {
sink.add([
0xCE,
(value >> 24) & 0xFF,
(value >> 16) & 0xFF,
(value >> 8) & 0xFF,
value & 0xFF,
]);
} else {
final buffer = ByteData(9)
..setUint8(0, 0xCF)
..setUint64(1, value);
sink.add(buffer.buffer.asUint8List());
}
} else if (value >= -32) {
sink.addByte(0xE0 | (value + 32));
} else if (value >= -128) {
final buffer = ByteData(2)
..setUint8(0, 0xD0)
..setInt8(1, value);
sink.add(buffer.buffer.asUint8List());
} else if (value >= -32768) {
final buffer = ByteData(3)
..setUint8(0, 0xD1)
..setInt16(1, value);
sink.add(buffer.buffer.asUint8List());
} else if (value >= -2147483648) {
final buffer = ByteData(5)
..setUint8(0, 0xD2)
..setInt32(1, value);
sink.add(buffer.buffer.asUint8List());
} else {
final buffer = ByteData(9)
..setUint8(0, 0xD3)
..setInt64(1, value);
sink.add(buffer.buffer.asUint8List());
}
}
}
class _Reader {
_Reader(this._bytes)
: _view = ByteData.view(_bytes.buffer, _bytes.offsetInBytes, _bytes.length);
final Uint8List _bytes;
final ByteData _view;
int _cursor = 0;
Object? read() {
final byte = _u8();
if (byte <= 0x7F) return byte;
if (byte >= 0xE0) return byte - 256;
if (byte >= 0x80 && byte <= 0x8F) return _map(byte & 0x0F);
if (byte >= 0x90 && byte <= 0x9F) return _list(byte & 0x0F);
if (byte >= 0xA0 && byte <= 0xBF) return _string(byte & 0x1F);
switch (byte) {
case 0xC0:
return null;
case 0xC2:
return false;
case 0xC3:
return true;
case 0xC4:
return _binary(_u8());
case 0xC5:
return _binary(_u16());
case 0xC6:
return _binary(_u32());
case 0xCA:
final value = _view.getFloat32(_cursor);
_cursor += 4;
return value;
case 0xCB:
final value = _view.getFloat64(_cursor);
_cursor += 8;
return value;
case 0xCC:
return _u8();
case 0xCD:
return _u16();
case 0xCE:
return _u32();
case 0xCF:
final value = _view.getUint64(_cursor);
_cursor += 8;
return value;
case 0xD0:
final value = _view.getInt8(_cursor);
_cursor += 1;
return value;
case 0xD1:
final value = _view.getInt16(_cursor);
_cursor += 2;
return value;
case 0xD2:
final value = _view.getInt32(_cursor);
_cursor += 4;
return value;
case 0xD3:
final value = _view.getInt64(_cursor);
_cursor += 8;
return value;
case 0xD9:
return _string(_u8());
case 0xDA:
return _string(_u16());
case 0xDB:
return _string(_u32());
case 0xDC:
return _list(_u16());
case 0xDD:
return _list(_u32());
case 0xDE:
return _map(_u16());
case 0xDF:
return _map(_u32());
}
if (byte >= 0xD4 && byte <= 0xD8) return _ext(1 << (byte - 0xD4));
if (byte == 0xC7) return _ext(_u8());
if (byte == 0xC8) return _ext(_u16());
if (byte == 0xC9) return _ext(_u32());
throw FormatException(
'MaxMsgpack: неизвестный маркер 0x${byte.toRadixString(16)}',
);
}
static const int _numberExtType = 1;
Object? _ext(int length) {
final type = _u8();
final data = Uint8List.fromList(
Uint8List.sublistView(_bytes, _cursor, _cursor + length),
);
_cursor += length;
if (type != _numberExtType) return null;
return _Reader(data).read();
}
int _u8() => _bytes[_cursor++];
int _u16() {
final value = _view.getUint16(_cursor);
_cursor += 2;
return value;
}
int _u32() {
final value = _view.getUint32(_cursor);
_cursor += 4;
return value;
}
String _string(int length) {
final value = utf8.decode(
Uint8List.sublistView(_bytes, _cursor, _cursor + length),
allowMalformed: true,
);
_cursor += length;
return value;
}
Uint8List _binary(int length) {
final value = Uint8List.fromList(
Uint8List.sublistView(_bytes, _cursor, _cursor + length),
);
_cursor += length;
return value;
}
List<Object?> _list(int length) =>
List<Object?>.generate(length, (_) => read(), growable: false);
Map<Object?, Object?> _map(int length) {
final map = <Object?, Object?>{};
for (var i = 0; i < length; i++) {
final key = read();
map[key] = read();
}
return map;
}
}