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.
379 lines
12 KiB
379 lines
12 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, the envelope, its rest and its split, the
|
|
// data of datekeys.capsule written and read, and the registry of
|
|
// locator.Standard. 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/datekey.dart';
|
|
import 'package:datekeys/src/errors.dart';
|
|
import 'package:datekeys/src/extension.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),
|
|
];
|
|
final parseBases = [
|
|
for (final h in v['parse_bases']! as List<Object?>) fromHex(h! as String),
|
|
];
|
|
final datas = [
|
|
for (final h in v['datas']! as List<Object?>) fromHex(h! 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('the locator of an extension opens with the default registry, as '
|
|
'OpenLocator', () {
|
|
for (final c in rows(v, 'open_info')) {
|
|
final s = c[2];
|
|
final info = CapsuleInfo(
|
|
dateKey: DateKey(c[0]! as String, c[1]! as int),
|
|
sealed: s == null ? null : sealedBases[s as int],
|
|
);
|
|
Locator? l;
|
|
expect(
|
|
errorText(() => l = info.openLocator(releaseOf(v, c[3]! as int))),
|
|
textOf(v, c[4]),
|
|
reason: '$c',
|
|
);
|
|
if (c[5] != null) expect(summaryOf(l!), expandSummary(c[5]));
|
|
}
|
|
});
|
|
|
|
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);
|
|
});
|
|
|
|
test('toExtension writes the data of datekeys.capsule as Info.Extension', () {
|
|
for (final c in rows(v, 'info')) {
|
|
final sealed = switch (c[3]) {
|
|
null => null,
|
|
final int i => sealedBases[i],
|
|
final String h => fromHex(h),
|
|
final Json z => Uint8List(z['zeros']! as int),
|
|
_ => throw StateError('${c[3]}'),
|
|
};
|
|
final info = CapsuleInfo(
|
|
note: c[0]! as String,
|
|
dateKey: DateKey(c[1]! as String, c[2]! as int),
|
|
sealed: sealed,
|
|
);
|
|
Extension? x;
|
|
expect(
|
|
errorText(() => x = info.toExtension()),
|
|
textOf(v, c[4]),
|
|
reason: '${c.take(3)}',
|
|
);
|
|
if (x != null) {
|
|
expect([x!.id, x!.version], [capsuleExtensionId, 1]);
|
|
expect([x!.data!.length, sha256Hex(x!.data!)], [c[5], c[6]]);
|
|
// A reader reads it back.
|
|
final back = parseCapsuleInfo(x!);
|
|
expect([back.note, back.dateKey], [info.note, info.dateKey]);
|
|
expect(back.sealed, info.sealed);
|
|
}
|
|
}
|
|
});
|
|
|
|
test('parseCapsuleInfo reads the data of datekeys.capsule as ParseInfo, '
|
|
'with ERR_EXTENSION_DATA_INVALID only', () {
|
|
for (final c in rows(v, 'parse')) {
|
|
final b = c[2]! as int;
|
|
final data = b < 0 ? null : applyLocatorEdits(parseBases[b], c[3]);
|
|
CapsuleInfo? info;
|
|
final x = Extension(c[0]! as String, c[1]! as int, data);
|
|
final want = textOf(v, c[4]);
|
|
expect(errorText(() => info = parseCapsuleInfo(x)), want, reason: '$c');
|
|
if (want.isNotEmpty) {
|
|
expect(
|
|
() => parseCapsuleInfo(x),
|
|
throwsA(
|
|
isA<DateKeysException>().having(
|
|
(e) => e.code,
|
|
'code',
|
|
ErrorCode.extensionDataInvalid,
|
|
),
|
|
),
|
|
);
|
|
expect(checkCapsuleData(x), isA<DateKeysException>());
|
|
continue;
|
|
}
|
|
expect(checkCapsuleData(x), isNull);
|
|
expect(
|
|
[info!.note, compactDateKey(info!.dateKey), info!.dateKey.round],
|
|
[c[5], c[6], c[7]],
|
|
);
|
|
expect(info!.sealed == null ? null : sha256Hex(info!.sealed!), c[8]);
|
|
}
|
|
});
|
|
|
|
test('the registry of locator.Standard checks the data of datekeys.capsule '
|
|
'in a .dkk, and an encoder only its presence', () {
|
|
// The default registry of the library is locator.Standard of Go, and
|
|
// without ValidateCapsule it is the extension.Standard of Go's writers.
|
|
const std = StandardExtensions();
|
|
const presence = StandardExtensions(validateCapsule: null);
|
|
expect(std.validateCapsule, checkCapsuleData);
|
|
for (final c in rows(v, 'registry')) {
|
|
final d = c[2]! as int;
|
|
final x = Extension(
|
|
c[0]! as String,
|
|
c[1]! as int,
|
|
d < 0 ? null : datas[d],
|
|
);
|
|
expect(
|
|
[
|
|
for (final u in checkNoncritical([x], std, ExtensionObject.accessKey))
|
|
u.error.message,
|
|
],
|
|
[for (final i in c[3]! as List<Object?>) textOf(v, i)],
|
|
reason: '$c',
|
|
);
|
|
final texts = [
|
|
errorText(() => checkCritical([x], std, ExtensionObject.accessKey)),
|
|
errorText(
|
|
() => checkWrite(
|
|
std,
|
|
ExtensionObject.accessKey,
|
|
ExtensionArray.noncritical,
|
|
[x],
|
|
),
|
|
),
|
|
errorText(
|
|
() => checkWrite(
|
|
std,
|
|
ExtensionObject.accessKey,
|
|
ExtensionArray.critical,
|
|
[x],
|
|
),
|
|
),
|
|
errorText(
|
|
() => checkWrite(
|
|
presence,
|
|
ExtensionObject.accessKey,
|
|
ExtensionArray.noncritical,
|
|
[x],
|
|
),
|
|
),
|
|
];
|
|
expect(texts, [for (final i in c.sublist(4)) textOf(v, i)], reason: '$c');
|
|
}
|
|
});
|
|
}
|
|
|
|
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));
|
|
});
|
|
}
|