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/locator_test.dart

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));
});
}

Powered by TurnKey Linux.