You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
dateKeys-dart/test/cbor_reference.dart

224 lines
6.5 KiB

// An encoder and a decoder of generic CBOR values for the tests, as
// internal/cbortest of datekeys-go. They are written independently of
// lib/src/cbor.dart on purpose, with BigInt arithmetic and dart:convert:
// tests use them to build inputs that the codec cannot write, such as null,
// negative integers or a float inside an otherwise valid object, and as a
// second reading of the CBOR profile of spec §58 to check the codec against.
import 'dart:convert';
import 'dart:typed_data';
/// An encoded data item that [marshal] writes verbatim.
final class Raw {
const Raw(this.bytes);
final List<int> bytes;
}
/// A text string held as its bytes, as [unmarshal] returns it: dart:convert
/// would drop a leading U+FEFF.
final class Text {
const Text(this.bytes);
final Uint8List bytes;
}
/// The deterministic encoding of [v], which is one of: an int (a negative one
/// as major type 1) or a BigInt (unsigned), a String or a [Text], a
/// Uint8List, a bool, null, a List of values, a Map whose keys are ints or
/// BigInts (written in ascending order), or a [Raw].
Uint8List marshal(Object? v) {
final out = <int>[];
_append(out, v);
return Uint8List.fromList(out);
}
void _append(List<int> out, Object? v) {
switch (v) {
case null:
out.add(0xf6);
case final bool b:
out.add(b ? 0xf5 : 0xf4);
case final int i when i < 0:
_head(out, 1, BigInt.from(-(i + 1)));
case final int i:
_head(out, 0, BigInt.from(i));
case final BigInt i:
_head(out, 0, i);
case final Uint8List b:
_head(out, 2, BigInt.from(b.length));
out.addAll(b);
case final String s:
final b = utf8.encode(s);
_head(out, 3, BigInt.from(b.length));
out.addAll(b);
case final Text t:
_head(out, 3, BigInt.from(t.bytes.length));
out.addAll(t.bytes);
case final Raw r:
out.addAll(r.bytes);
case final List<Object?> l:
_head(out, 4, BigInt.from(l.length));
for (final x in l) {
_append(out, x);
}
case final Map<Object, Object?> m:
final keys = m.keys.toList()..sort((a, b) => _big(a).compareTo(_big(b)));
_head(out, 5, BigInt.from(m.length));
for (final k in keys) {
_head(out, 0, _big(k));
_append(out, m[k]);
}
default:
throw ArgumentError('cannot encode ${v.runtimeType}');
}
}
BigInt _big(Object k) => k is BigInt ? k : BigInt.from(k as int);
void _head(List<int> out, int major, BigInt arg) {
final m = major << 5;
if (arg < BigInt.from(24)) {
out.add(m | arg.toInt());
return;
}
final int n;
if (arg < BigInt.from(0x100)) {
out.add(m | 24);
n = 1;
} else if (arg < BigInt.from(0x10000)) {
out.add(m | 25);
n = 2;
} else if (arg < BigInt.one << 32) {
out.add(m | 26);
n = 4;
} else {
out.add(m | 27);
n = 8;
}
for (var i = n - 1; i >= 0; i--) {
out.add(((arg >> (8 * i)) & BigInt.from(0xff)).toInt());
}
}
/// The nesting that [unmarshal] follows at most.
const maxDepth = 1000;
/// Decodes exactly one data item of the CBOR profile of spec §58 into BigInt,
/// Uint8List, [Text], List and Map with BigInt keys in their order. It throws
/// a FormatException for every other major type, an indefinite length, a
/// head not in its shortest form, a map key that is not an unsigned integer
/// greater than the previous one, invalid UTF-8, truncation, trailing bytes
/// and nesting deeper than [maxDepth].
Object? unmarshal(List<int> b) {
final r = _Reader(b);
final v = r.value(0);
if (r.off != b.length) {
throw FormatException('${b.length - r.off} trailing bytes');
}
return v;
}
final class _Reader {
_Reader(this.b);
final List<int> b;
int off = 0;
(int, BigInt) head() {
if (off >= b.length) throw const FormatException('truncated');
final ib = b[off++];
final major = ib >> 5;
final info = ib & 0x1f;
if (major == 1 || major >= 6) throw FormatException('major type $major');
if (info < 24) return (major, BigInt.from(info));
if (info > 27) throw FormatException('additional information $info');
final n = 1 << (info - 24);
if (b.length - off < n) throw const FormatException('truncated');
var arg = BigInt.zero;
for (var i = 0; i < n; i++) {
arg = arg << 8 | BigInt.from(b[off + i]);
}
off += n;
if ((n == 1 && arg < BigInt.from(24)) ||
(n > 1 && arg >> (4 * n) == BigInt.zero)) {
throw FormatException('$arg not in its shortest form');
}
return (major, arg);
}
Uint8List bytes(BigInt n) {
if (n > BigInt.from(b.length - off)) {
throw const FormatException('truncated');
}
final s = Uint8List.fromList(b.sublist(off, off + n.toInt()));
off += n.toInt();
return s;
}
Object? value(int depth) {
final (major, arg) = head();
switch (major) {
case 0:
return arg;
case 2:
return bytes(arg);
case 3:
final s = bytes(arg);
// dart:convert is strict about everything but a leading U+FEFF,
// which it drops: the text keeps its bytes.
const Utf8Decoder().convert(s);
return Text(s);
}
if (depth >= maxDepth) throw const FormatException('nested too deep');
if (arg > BigInt.from(b.length - off)) {
throw const FormatException('truncated');
}
final count = arg.toInt();
if (major == 4) {
return [for (var i = 0; i < count; i++) value(depth + 1)];
}
final out = <BigInt, Object?>{};
BigInt? last;
for (var i = 0; i < count; i++) {
final (km, k) = head();
if (km != 0) throw FormatException('map key of major type $km');
if (last != null && k <= last) {
throw FormatException('map key $k after $last');
}
last = k;
out[k] = value(depth + 1);
}
return out;
}
}
/// The nesting depth of [v], a value of [marshal] or [unmarshal], and the
/// length of its longest string or container: bytes of a string, items of an
/// array, entries of a map.
(int, int) shape(Object? v) {
final Iterable<Object?> items;
switch (v) {
case final Uint8List b:
return (0, b.length);
case final String s:
return (0, utf8.encode(s).length);
case final Text t:
return (0, t.bytes.length);
case final List<Object?> l:
items = l;
case final Map<Object, Object?> m:
items = m.values;
default:
return (0, 0);
}
var depth = 0;
var length = items.length;
for (final x in items) {
final (d, l) = shape(x);
if (d > depth) depth = d;
if (l > length) length = l;
}
return (depth + 1, length);
}

Powered by TurnKey Linux.