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/tool/open_bench.dart

241 lines
7.5 KiB

// Times the opening of datekeys-dart, stages 4c and 5c, on the VM and
// compiled to JavaScript: the opening in memory of a fixture of each kind
// (time_only, time_and_key with a .dkk, and format 3 with its files), each
// with the verification of its release; the opening of the two fixtures of
// format 3 whose security area holds a signature of alg 2 and a seal of
// seal_type 2, with the time of the evaluation of that area alone; and the
// throughput of a capsule of 64 MiB opened from a source in streaming, in
// format 1 and in format 3, which large_capsule.dart makes from the
// fixtures. It reads no file: the fixtures are those of
// test/vectors/open_vectors.g.dart and securitycms_vectors.g.dart.
//
// dart run tool/open_bench.dart
//
// dart compile js -O2 -o open_bench.js tool/open_bench.dart
// node -e "globalThis.self = globalThis; require('./open_bench.js')"
//
// ignore_for_file: avoid_print
import 'dart:convert';
import 'dart:typed_data';
import 'package:datekeys/datekeys.dart';
import '../test/large_capsule.dart';
import '../test/vectors/open_vectors.g.dart';
import '../test/vectors/securitycms_vectors.g.dart';
typedef Json = Map<String, Object?>;
final Json _fixtures = jsonDecode(openFixturesJson) as Json;
final Json _records = jsonDecode(openRecordsJson) as Json;
Uint8List fixture(String file) => fromHex(_fixtures[file]! as String);
Json record(String file) => _records[file]! as Json;
Release releaseOf(Json r) {
final rel = r['release']! as Json;
return Release(rel['round']! as int, fromHex(rel['signature']! as String));
}
/// A sink of bytes that counts them and keeps nothing.
final class Discard implements ByteSink {
int n = 0;
@override
void add(Uint8List bytes) => n += bytes.length;
@override
void close() {}
@override
void abort(Object reason) {}
}
/// A sink of files that counts their bytes and keeps nothing.
final class DiscardFiles implements FileSink {
int n = 0;
@override
void begin(Head head) {}
@override
ByteSink create(int i) => _Count(this);
@override
void commit() {}
@override
void abort(Object reason) {}
}
final class _Count implements ByteSink {
_Count(this.s);
final DiscardFiles s;
@override
void add(Uint8List bytes) => s.n += bytes.length;
@override
void close() {}
@override
void abort(Object reason) {}
}
OpenOptions optionsOf(
Json r, {
ByteSink? output,
FileSink? sink,
Uint8List? dkk,
}) => OpenOptions(
source: suppliedRelease(releaseOf(r)),
now: () => parseRfc3339(r['unlock_at']! as String),
accessKeyFile: dkk,
output: output,
sink: sink,
);
double median(List<double> xs) {
final s = [...xs]..sort();
return s[s.length ~/ 2];
}
Future<void> main() async {
final js = identical(1, 1.0);
final runs = js ? 5 : 15;
print(
'datekeys-dart, the opening, ${js ? 'compiled to JavaScript' : 'on the VM'}',
);
// The verification of a release is kept for the next opening of the same
// round: a release of another round between two openings makes each pay
// for its own pairing, as an application that opens one capsule does.
final other = record('time_only_extensions.dkc');
for (final (label, file, withDkk) in [
('time_only, format 1', 'time_only_extensions.dkc', false),
('time_and_key with a .dkk, format 1', 'time_and_key_portable.dkc', true),
(
'time_and_key with a .dkk, format 2',
'format2_time_and_key_recipients.dkc',
true,
),
('format 3, one file', 'format3_single.dkc', false),
(
'format 3, time_and_key with a .dkk',
'format3_time_and_key_portable.dkc',
true,
),
]) {
final r = record(file);
final dkc = fixture(file);
final dkk = withDkk ? fixture(r['access_key_file']! as String) : null;
final format3 = r['format'] == 3;
final times = <double>[];
for (var i = 0; i < runs; i++) {
final evict = releaseOf(r).round == releaseOf(other).round
? record('time_and_key_portable.dkc')
: other;
verifyRelease(quicknet(), releaseOf(evict).round, releaseOf(evict));
final sw = Stopwatch()..start();
final o = await openCapsule(
dkc,
format3
? optionsOf(r, sink: DiscardFiles(), dkk: dkk)
: optionsOf(r, output: Discard(), dkk: dkk),
);
times.add(sw.elapsedMicroseconds / 1000);
if (!o.ok) throw StateError('$label: ${o.error}');
}
print(' $label: ${median(times).toStringAsFixed(1)} ms');
}
// The fixtures with certificates: the opening, and inside it the
// evaluation of the security area by the default evaluator, the reader of
// CMS included.
final cms =
((jsonDecode(securityCmsFixturesJson) as Json)['fixtures']! as List)
.cast<Json>();
for (final (label, r) in [
('format 3, a signature of alg 2 by two signers, each sealed', cms[0]),
('format 3, a signature of alg 1 and a seal of seal_type 2', cms[1]),
]) {
final dkc = fromHex(r['dkc']! as String);
final times = <double>[];
final evaluations = <double>[];
for (var i = 0; i < runs; i++) {
verifyRelease(quicknet(), releaseOf(other).round, releaseOf(other));
final evaluation = Stopwatch();
final sw = Stopwatch()..start();
final o = await openCapsule(
dkc,
OpenOptions(
source: suppliedRelease(releaseOf(r)),
now: () => parseRfc3339(r['unlock_at']! as String),
sink: DiscardFiles(),
evaluator: (input) {
evaluation.start();
final v = evaluateSecurityInput(input);
evaluation.stop();
return v;
},
),
);
times.add(sw.elapsedMicroseconds / 1000);
evaluations.add(evaluation.elapsedMicroseconds / 1000);
final want = (r['verdicts']! as Json)['signature'];
if (!o.ok || o.verdicts?.signature?.code != want) {
throw StateError('$label: ${o.error} ${o.verdicts}');
}
}
print(
' $label: ${median(times).toStringAsFixed(1)} ms, of which the '
'security area ${median(evaluations).toStringAsFixed(1)} ms',
);
}
// Throughput: 64 MiB on the VM, 16 MiB on Node.js, from a source.
final mib = js ? 16 : 64;
final content = pattern(mib << 20);
final r1 = record('time_only_extensions.dkc');
final big1 = withContent1(
fixture('time_only_extensions.dkc'),
fromHex(r1['payload_identity']! as String),
content,
);
final r3 = record('format3_single.dkc');
final big3 = withFiles3(
fixture('format3_single.dkc'),
fromHex(r3['payload_identity']! as String),
releaseOf(r3),
[('grande.bin', content)],
);
for (final (label, dkc, r, format3) in [
('format 1', big1, r1, false),
('format 3, one file', big3, r3, true),
]) {
final times = <double>[];
for (var i = 0; i < 3; i++) {
final out = Discard();
final files = DiscardFiles();
final sw = Stopwatch()..start();
final o = await openCapsuleSource(
BytesSource(dkc),
// Never `c ? null : sink`: dart2js of Dart 3.13 loses the writes to
// an object that reaches a field that way, and reads its counter as
// 0 afterwards (see the README).
format3 ? optionsOf(r, sink: files) : optionsOf(r, output: out),
);
times.add(sw.elapsedMicroseconds / 1e6);
if (!o.ok || (format3 ? files.n : out.n) != content.length) {
throw StateError('$label: ${o.error}');
}
}
final s = median(times);
print(
' $mib MiB, $label, from a source: ${s.toStringAsFixed(2)} s, '
'${(mib / s).toStringAsFixed(1)} MiB/s',
);
}
}

Powered by TurnKey Linux.