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.
235 lines
7.9 KiB
235 lines
7.9 KiB
// The locator and the envelope against Go (spec §44.1), from the part of
|
|
// locator_vectors.json that locator_vectors.g.dart holds, so that it runs on
|
|
// the VM and compiled to JavaScript: the padding of every base, plaintexts
|
|
// valid and broken, Marshal at every limit, sealed locators opened with
|
|
// their release and with others, and the envelope, its rest and its
|
|
// split. locator_vm_test.dart runs every case of the files.
|
|
library;
|
|
|
|
import 'dart:typed_data';
|
|
|
|
import 'package:datekeys/src/bytes.dart';
|
|
import 'package:datekeys/src/locator.dart';
|
|
import 'package:test/test.dart';
|
|
|
|
import 'locator_support.dart';
|
|
import 'vectors/locator_vectors.g.dart';
|
|
|
|
/// The cases of the locator, shared with locator_vm_test.dart, which runs
|
|
/// them on the whole file.
|
|
void locatorCases(Json v) {
|
|
final envelope = v['envelope']! as Json;
|
|
final plainBases = [
|
|
for (final h in v['plaintext_bases']! as List<Object?>)
|
|
fromHex(h! as String),
|
|
];
|
|
final sealedBases = [
|
|
for (final s in (v['sealed_bases']! as List<Object?>).cast<Json>())
|
|
fromHex(s['sealed']! as String),
|
|
];
|
|
Locator base({
|
|
List<LocatorAddress>? addresses,
|
|
List<int>? key,
|
|
List<int>? header,
|
|
List<int>? restDigest,
|
|
int? restSize,
|
|
List<int>? capsuleDigest,
|
|
}) => Locator(
|
|
addresses:
|
|
addresses ?? [LocatorAddress('https://ejemplo.org/foto.jpg', 3000)],
|
|
envelopeKey: key ?? fromHex(envelope['key']! as String),
|
|
envelopeHeader: header ?? fromHex(envelope['header']! as String),
|
|
restDigest: restDigest ?? fromHex(envelope['rest_digest']! as String),
|
|
restSize: restSize ?? envelope['rest_size']! as int,
|
|
capsuleDigest:
|
|
capsuleDigest ?? fromHex(envelope['capsule_digest']! as String),
|
|
);
|
|
|
|
test('locatorPlaintextLength gives the least multiple that key 6 fills, '
|
|
'on every base from -4100 to 16484', () {
|
|
var n = 0;
|
|
for (final r in rows(v, 'padding')) {
|
|
for (var b = r[0]! as int; b <= (r[1]! as int); b++) {
|
|
expect(locatorPlaintextLength(b), r[2], reason: '$b');
|
|
n++;
|
|
}
|
|
}
|
|
expect(n, 4100 + 1 + 16484);
|
|
});
|
|
|
|
test('unmarshalLocator reads plaintexts as Unmarshal', () {
|
|
for (final c in rows(v, 'plaintexts')) {
|
|
final b = applyLocatorEdits(plainBases[c[0]! as int], c[1]);
|
|
Locator? l;
|
|
expect(
|
|
errorText(() => l = unmarshalLocator(b)),
|
|
textOf(v, c[2]),
|
|
reason: '${c[1]}',
|
|
);
|
|
if (c[3] != null) {
|
|
expect(summaryOf(l!), expandSummary(c[3]), reason: '${c[1]}');
|
|
// As FuzzUnmarshal of Go: an address that a reader uses passes the
|
|
// rules, and has a host to show.
|
|
for (final a in l!.usable) {
|
|
checkAddressUri(a.uri);
|
|
expect(a.host, isNotEmpty);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
test('marshal writes the plaintext of Marshal, or its error', () {
|
|
for (final c in rows(v, 'marshal')) {
|
|
final l = base(
|
|
addresses: [
|
|
for (final a in (c[0]! as List<Object?>).cast<List<Object?>>())
|
|
LocatorAddress(uriOf(a[0]), a[1]! as int),
|
|
],
|
|
header: c[1] == null ? null : fromHex(c[1]! as String),
|
|
restSize: c[2]! as int,
|
|
);
|
|
Uint8List? b;
|
|
expect(errorText(() => b = l.marshal()), textOf(v, c[3]), reason: '$c');
|
|
if (b != null) {
|
|
expect([b!.length, sha256Hex(b!)], [c[4], c[5]], reason: '$c');
|
|
// It reads back as it was written.
|
|
expect(summaryOf(unmarshalLocator(b!)), summaryOf(l));
|
|
}
|
|
}
|
|
});
|
|
|
|
test('openLocator opens a sealed locator with the release of its round, '
|
|
'as Open', () {
|
|
for (final c in rows(v, 'open')) {
|
|
final round = c[1]! as int;
|
|
final b = applyLocatorEdits(sealedBases[c[0]! as int], c[4]);
|
|
Locator? l;
|
|
expect(
|
|
errorText(
|
|
() => l = openLocator(
|
|
profileOf(c[3]! as String),
|
|
round,
|
|
openRelease(v, round, c[2]),
|
|
b,
|
|
),
|
|
),
|
|
textOf(v, c[5]),
|
|
reason: '$c',
|
|
);
|
|
if (c[6] != null) {
|
|
expect(summaryOf(l!), expandSummary(c[6]), reason: '$c');
|
|
}
|
|
}
|
|
});
|
|
|
|
test('openEnvelope checks the rest and the .dkc as OpenEnvelope', () {
|
|
final rest = fromHex(envelope['rest']! as String);
|
|
final header = fromHex(envelope['header']! as String);
|
|
for (final c in rows(v, 'envelopes')) {
|
|
final l = base(
|
|
key: c[1] == '' ? null : fromHex(c[1]! as String),
|
|
header: applyLocatorEdits(header, c[2]),
|
|
restDigest: c[3] == '' ? null : fromHex(c[3]! as String),
|
|
restSize: c[4]! as int,
|
|
capsuleDigest: c[5] == '' ? null : fromHex(c[5]! as String),
|
|
);
|
|
Uint8List? dkc;
|
|
final r = applyLocatorEdits(rest, c[6]);
|
|
expect(
|
|
errorText(() => dkc = l.openEnvelope(r)),
|
|
textOf(v, c[7]),
|
|
reason: '${c[0]}',
|
|
);
|
|
if (dkc != null) {
|
|
expect(sha256Hex(dkc!), c[8]);
|
|
expect(toHex(dkc!), envelope['dkc']);
|
|
}
|
|
}
|
|
});
|
|
|
|
test('restIn reads rest_size bytes from the offset, as RestIn', () {
|
|
final resources = [fromHex(v['rest_in_resource']! as String), Uint8List(0)];
|
|
for (final c in rows(v, 'rest_in')) {
|
|
final l = base(restSize: c[2]! as int);
|
|
Uint8List? r;
|
|
expect(
|
|
errorText(() => r = l.restIn(resources[c[0]! as int], c[1]! as int)),
|
|
textOf(v, c[3]),
|
|
reason: '$c',
|
|
);
|
|
if (r != null) expect(sha256Hex(r!), c[4]);
|
|
}
|
|
});
|
|
|
|
test('hideRest appends the rest, as Hide', () {
|
|
for (final c in rows(v, 'hide')) {
|
|
final (:file, :offset) = hideRest(
|
|
fromHex(c[0]! as String),
|
|
fromHex(c[1]! as String),
|
|
);
|
|
expect([toHex(file), offset], [c[2], c[3]]);
|
|
}
|
|
});
|
|
|
|
test('splitEnvelope splits an age file where NewEnvelope does', () {
|
|
final key = fromHex(envelope['key']! as String);
|
|
final dkc = fromHex(envelope['dkc']! as String);
|
|
for (final c in rows(v, 'split')) {
|
|
final file = fromHex(c[0]! as String);
|
|
({Locator locator, Uint8List rest})? s;
|
|
expect(
|
|
errorText(() => s = splitEnvelope(file, key, dkc)),
|
|
textOf(v, c[1]),
|
|
reason: '$c',
|
|
);
|
|
if (s != null) {
|
|
final end = c[2]! as int;
|
|
expect(toHex(s!.locator.envelopeHeader), toHex(file.sublist(0, end)));
|
|
expect(toHex(s!.rest), toHex(file.sublist(end)));
|
|
expect(s!.locator.addresses, isEmpty);
|
|
}
|
|
}
|
|
// The envelope of NewEnvelope: its locator is the one of the vectors.
|
|
final rows0 = rows(v, 'split').first;
|
|
final s = splitEnvelope(fromHex(rows0[0]! as String), key, dkc);
|
|
expect(toHex(s.locator.envelopeHeader), envelope['header']);
|
|
expect(toHex(s.rest), envelope['rest']);
|
|
expect(toHex(s.locator.restDigest), envelope['rest_digest']);
|
|
expect(s.locator.restSize, envelope['rest_size']);
|
|
expect(toHex(s.locator.capsuleDigest), envelope['capsule_digest']);
|
|
expect(s.locator.openEnvelope(s.rest), dkc);
|
|
});
|
|
}
|
|
|
|
void main() {
|
|
final v = decodeJson(locatorVectorsJson);
|
|
group('the part of locator_vectors.json', () => locatorCases(v));
|
|
|
|
test('a Locator has the types of Go: keys and digests of 32 bytes and no '
|
|
'negative size or offset', () {
|
|
final k = Uint8List(32);
|
|
Locator make({int key = 32, int size = 0}) => Locator(
|
|
addresses: const [],
|
|
envelopeKey: Uint8List(key),
|
|
envelopeHeader: [1],
|
|
restDigest: k,
|
|
restSize: size,
|
|
capsuleDigest: k,
|
|
);
|
|
make();
|
|
expect(() => make(key: 31), throwsArgumentError);
|
|
expect(() => make(size: -1), throwsArgumentError);
|
|
expect(() => LocatorAddress('https://a.org/', -1), throwsArgumentError);
|
|
expect(() => make().restIn([1, 2], -1), throwsArgumentError);
|
|
// An envelope without addresses yet does not marshal.
|
|
expect(
|
|
errorText(() => make().marshal()),
|
|
'locator: 0 addresses, not 1 to 8',
|
|
);
|
|
final l = make().withAddresses([LocatorAddress('https://a.org/', 5)]);
|
|
expect(l.addresses.single.offset, 5);
|
|
l.wipe();
|
|
expect(l.envelopeKey, Uint8List(32));
|
|
});
|
|
}
|