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

271 lines
8.2 KiB

// The shared vectors of testdata/ that the formats read, as vectors.test.ts
// of datekeys-ts: dk1.json, quicknet_rounds.json, profile_quicknet.json,
// padding.json and the schemas block of cbor.json, each object through the
// decoder of its schema and written back to the same bytes.
@TestOn('vm')
library;
import 'dart:convert';
import 'dart:io';
import 'package:datekeys/src/accesskey.dart';
import 'package:datekeys/src/bytes.dart';
import 'package:datekeys/src/control.dart';
import 'package:datekeys/src/datekey.dart';
import 'package:datekeys/src/errors.dart';
import 'package:datekeys/src/framing.dart';
import 'package:datekeys/src/header.dart';
import 'package:datekeys/src/padding.dart';
import 'package:datekeys/src/profile.dart';
import 'package:datekeys/src/version.dart';
import 'package:test/test.dart';
typedef Json = Map<String, Object?>;
Json readJson(String path) => jsonDecode(File(path).readAsStringSync()) as Json;
List<Json> section(Json file, String name) =>
(file[name]! as List).cast<Json>();
/// The code of the [DateKeysException] that [body] throws, or `ok`.
String resultOf(void Function() body) {
try {
body();
return 'ok';
} on DateKeysException catch (e) {
return e.code.code;
}
}
void main() {
group('testdata/vectors/dk1.json', () {
final file = readJson('testdata/vectors/dk1.json');
final vectors = section(file, 'vectors');
test('is of this specification and holds valid and invalid strings', () {
expect(file['spec'], specVersion);
expect(vectors.where((v) => v.containsKey('dk1')), isNotEmpty);
expect(vectors.where((v) => v.containsKey('input')), isNotEmpty);
});
for (final v in vectors) {
test(v['name'], () {
if (v.containsKey('input')) {
expect(
resultOf(() => parseDateKey(v['input']! as String)),
v['error'],
);
return;
}
final d = parseDateKey(v['dk1']! as String);
expect([d.profileId, d.round], [v['network'], v['round']]);
expect(canonicalJson(d), v['canonical_json']);
expect(compactDateKey(d), v['dk1']);
expect(compactDateKey(d), 'dk1_${v['base64url']}');
});
}
});
group('testdata/vectors/quicknet_rounds.json', () {
final file = readJson('testdata/vectors/quicknet_rounds.json');
final p = quicknet();
test('is for the pinned Quicknet profile', () {
expect(file['profile'], quicknetId);
expect(section(file, 'vectors'), isNotEmpty);
});
for (final v in section(file, 'vectors')) {
test(v['name'], () {
final at = parseRfc3339(v['requested']! as String);
if (v.containsKey('error')) {
expect(resultOf(() => resolveDateKey(p, at)), v['error']);
return;
}
final d = resolveDateKey(p, at);
expect(d.round, v['round']);
expect(formatRfc3339Nano(unlockAt(d, p)!), v['effective']);
expect(resolveRfc3339(p, v['requested']! as String), d);
});
}
});
test('testdata/vectors/profile_quicknet.json', () {
final v = readJson('testdata/vectors/profile_quicknet.json');
final p = quicknet();
expect(
[
p.id,
p.provider,
p.network,
toHex(p.chainHash),
toHex(p.publicKey),
p.period,
p.genesisTime,
toHex(p.genesisSeed),
p.scheme,
],
[
v['profile_id'],
v['provider'],
v['network'],
v['chain_hash'],
v['public_key'],
v['period_seconds'],
v['genesis_time'],
v['genesis_seed'],
v['scheme'],
],
);
expect(toHex(canonicalCbor(p)), v['canonical_cbor']);
expect(toHex(profileHash(p)), v['profile_hash']);
expect(decodeProfile(fromHex(v['canonical_cbor']! as String)), p);
expect(
[quicknetCanonicalCbor, quicknetProfileHash],
[v['canonical_cbor'], v['profile_hash']],
);
});
group('testdata/vectors/padding.json', () {
final file = readJson('testdata/vectors/padding.json');
final vectors = section(file, 'vectors');
test('names L_MAX and covers the boundaries of spec §29.1', () {
expect(file['l_max'], maxPayloadLength);
final ls = vectors.map((v) => v['l']).toList();
for (final l in [
0,
256,
257,
2113929216,
2113929217,
4227858432,
4227858433,
4294967295,
4294967297,
562949953421311,
maxPayloadLength,
]) {
expect(ls, contains(l));
}
expect(file['rejected'], contains(maxPayloadLength + 1));
});
for (final v in vectors) {
test('L = ${v['l']}', () {
final l = v['l']! as int;
final b = paddedLength(l, PaddingRule.bloque256);
final r = paddedLength(l, PaddingRule.reforzado);
expect(
[b, r, payloadAgeLength(b), payloadAgeLength(r)],
[
v['bloque256'],
v['reforzado'],
v['payload_age_bloque256'],
v['payload_age_reforzado'],
],
);
if (l > 256) {
final (:e, :s, :lastBits) = padmeParameters(l);
expect([e, s, lastBits], [v['e'], v['s'], v['last_bits']]);
} else {
expect(v.containsKey('e'), isFalse);
}
});
}
test('rejects the lengths above L_MAX', () {
for (final l in (file['rejected']! as List).cast<int>()) {
for (final rule in PaddingRule.values) {
expect(
() => paddedLength(l, rule),
throwsA(
isA<ArgumentError>().having(
(e) => e.message,
'message',
'capsule: content of $l bytes exceeds L_MAX = '
'$maxPayloadLength',
),
),
);
}
}
});
});
group('testdata/vectors/cbor.json: the schemas', () {
final file = readJson('testdata/vectors/cbor.json');
final schemas = section(file, 'schemas');
// The decoder of each schema, for the capsule format of the vector; it
// returns the writer of the object it decoded.
final decoders = <String, Object Function() Function(List<int>, int)>{
'provider_profile': (b, _) {
final p = decodeProfile(b);
return () => canonicalCbor(p);
},
'public_header': (b, _) {
final h = decodeHeader(b);
return () => encodeHeader(h);
},
'control_cbor': (b, format) {
final f = CapsuleFormat.fromVersion(format)!;
final c = decodeControl(b, f);
return () => encodeControl(c, f);
},
'dkk_body': (b, _) {
final k = decodeAccessKeyBody(b);
return () => marshalAccessKeyBody(k);
},
};
const blocks = {
'provider_profile': 'provider_profile',
'public_header': 'public_header',
'control_cbor': 'control_cbor',
'dkk_body': 'dkk_body',
'verification_metadata': 'dkk_body',
'extension': 'public_header',
};
test('has every block, with CONTROL_CBOR in the three formats', () {
for (final block in blocks.keys) {
expect(
schemas.where((v) => v['block'] == block).length,
greaterThanOrEqualTo(5),
reason: block,
);
}
for (final v in schemas) {
expect(blocks[v['block']], v['schema'], reason: '${v['name']}');
expect(
v.containsKey('format'),
v['schema'] == 'control_cbor' &&
(v['name']! as String).startsWith('format '),
reason: '${v['name']}',
);
}
final controls = schemas.where((v) => v['schema'] == 'control_cbor');
for (final f in [2, 3]) {
expect(
controls.where((v) => v['format'] == f).length,
greaterThanOrEqualTo(5),
);
}
});
for (final v in schemas) {
test('${v['block']} (${v['schema']}): ${v['name']}', () {
final input = fromHex(v['hex']! as String);
Object Function()? write;
final result = resultOf(() {
write = decoders[v['schema']]!(input, (v['format'] as int?) ?? 1);
});
expect(result, v['result']);
// An accepted object is written back to the same bytes.
if (write != null) {
expect(toHex(write!() as List<int>), v['hex']);
}
});
}
});
}

Powered by TurnKey Linux.