// 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) fromHex(h! as String), ]; final sealedBases = [ for (final s in (v['sealed_bases']! as List).cast()) fromHex(s['sealed']! as String), ]; Locator base({ List? addresses, List? key, List? header, List? restDigest, int? restSize, List? 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).cast>()) 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)); }); }