Stage 4c: steps 9 to 18, the opening of the three formats
lib/src/open.dart and open3.dart port Open and openBody of package capsule
of datekeys-go at c531e93, with the checks, codes, steps and texts of the
reference and the detail of each check: the .dkk of step 9.a, decoded or
still encoded, with its material, its critical extensions, its capsule_id
and its capsule_digest; at least one credential; the clock and the release,
with the rule of step 9 for the failures of its source (sourceFailure);
its verification at step 10; OUTER_TIME_AGE with the tlock stanza;
INNER_ACCESS_AGE with the X25519 identities, those of the .dkk and the key
of words, and the rules of the slots; CONTROL_CBOR, header_binding,
I_PAYLOAD and P; PAYLOAD_AGE streamed, with the padding of format 2
checked and never delivered, and in format 3 the frame of BODY, the area,
the head, each file to the sink with its SHA-256 and the padding, with the
precedence of spec §63; and the commit. A failure of age keeps the code of
the identity that reports it, or is ERR_INTEGRITY with the reason of its
phase, as classify of Go.
openCapsule opens a capsule in memory and openCapsuleSource one that a
ByteSource reads: the prefix of the inspection and the nonce of
PAYLOAD_AGE before the release, then pieces of 1 MiB. lib/src/sink.dart
has ByteSink, for the content of formats 1 and 2 and for each file, and
FileSink, for the files of format 3, as the dst and the Sink of Go, with
MemoryByteSink and MemoryFileSink. Nothing is presented as valid before
step 17 ends: the output is closed only then and aborted after any
failure, and the sink aborted after any failure that follows its begin
(spec §56).
The signature and the seal are stage 5: lib/src/verdicts.dart has the
verdicts and the SecurityEvaluator, given what newSecurityContext and
EvaluateSecurityIn of Go take, which never fails the opening; the default
evaluates nothing. OpenOptions.accept is Accept of Go. And
AgePayloadDecryptor.wipe clears the key of a STREAM left unread.
The tests run open_cases.json and the mutation corpus with the texts and
the checks of Go, in memory and from a source read in pieces; capsules of
several MiB made from the fixtures, for the streaming; a capsule with a
stanza for a key of words; and the caller, the sinks and the evaluator.
open_test.dart runs on Node.js too, with open_vectors.g.dart.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
3 days ago
|
|
|
// The head, the note, the inspection and the opening, on the VM and
|
|
|
|
|
// compiled to JavaScript: the parts of the vectors of Go that
|
|
|
|
|
// test/vectors/open_vectors.g.dart holds, with the small fixtures they edit,
|
|
|
|
|
// and what the API does with the caller: its errors, the output and the
|
|
|
|
|
// sinks, the evaluator of the security area and accept.
|
|
|
|
|
library;
|
|
|
|
|
|
|
|
|
|
import 'dart:convert';
|
|
|
|
|
import 'dart:typed_data';
|
|
|
|
|
|
|
|
|
|
import 'package:datekeys/datekeys.dart';
|
|
|
|
|
import 'package:test/test.dart';
|
|
|
|
|
|
|
|
|
|
import 'open_support.dart';
|
|
|
|
|
import 'tlock_support.dart' show applyEdits;
|
|
|
|
|
import 'vectors/open_vectors.g.dart';
|
|
|
|
|
|
|
|
|
|
final Map<String, Uint8List> _fixtures = {
|
|
|
|
|
for (final e in (jsonDecode(openFixturesJson) as Json).entries)
|
|
|
|
|
e.key: fromHex(e.value! as String),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Uint8List fixture(String file) => _fixtures[file]!;
|
|
|
|
|
|
|
|
|
|
final Json _cases = jsonDecode(openCasesJson) as Json;
|
|
|
|
|
|
|
|
|
|
/// The case of the part of open_cases.json named [name].
|
|
|
|
|
Json caseNamed(String name) => [
|
|
|
|
|
...(_cases['fixtures']! as List).cast<Json>(),
|
|
|
|
|
...(_cases['steps']! as List).cast<Json>(),
|
|
|
|
|
].firstWhere((c) => c['name'] == name);
|
|
|
|
|
|
|
|
|
|
/// The options of the opening of the case [name], with [output] and [sink].
|
|
|
|
|
OpenOptions optionsOf(
|
|
|
|
|
Json c, {
|
|
|
|
|
ByteSink? output,
|
|
|
|
|
FileSink? sink,
|
Stage 5b: the opening evaluates the security area as Go
OpenOptions.evaluator is now evaluateSecurityInput, so that opening a
capsule of format 3 gives the verdicts of Go at step 17: the signature of
alg 1 and every verdict of the form, with the author keys of the options,
and the signature of alg 2 and the seal of seal_type 2 not evaluated
until a reader of CMS is given. notEvaluated stays for a caller that
shows no verdict.
tool/open_go_vectors.go records the verdicts of each capsule of format 3
that opens, with their lines, the key and the label of alg 1 and the
earliest valid seal, and opens the fixtures signed with alg 1 also with
their author key saved, F3, and with another, F4. Every case of
open_cases.json and every case of the mutation corpus that opens gives
those verdicts and lines: format3_signed, format3_unsigned,
format3_signature_unsupported, format3_seal_unsupported,
format3_security_v2 and format3_note all of them, and format3_signed_cms
and format3_sealed the part that needs no reader of CMS.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
3 days ago
|
|
|
SecurityEvaluator? evaluator,
|
Stage 4c: steps 9 to 18, the opening of the three formats
lib/src/open.dart and open3.dart port Open and openBody of package capsule
of datekeys-go at c531e93, with the checks, codes, steps and texts of the
reference and the detail of each check: the .dkk of step 9.a, decoded or
still encoded, with its material, its critical extensions, its capsule_id
and its capsule_digest; at least one credential; the clock and the release,
with the rule of step 9 for the failures of its source (sourceFailure);
its verification at step 10; OUTER_TIME_AGE with the tlock stanza;
INNER_ACCESS_AGE with the X25519 identities, those of the .dkk and the key
of words, and the rules of the slots; CONTROL_CBOR, header_binding,
I_PAYLOAD and P; PAYLOAD_AGE streamed, with the padding of format 2
checked and never delivered, and in format 3 the frame of BODY, the area,
the head, each file to the sink with its SHA-256 and the padding, with the
precedence of spec §63; and the commit. A failure of age keeps the code of
the identity that reports it, or is ERR_INTEGRITY with the reason of its
phase, as classify of Go.
openCapsule opens a capsule in memory and openCapsuleSource one that a
ByteSource reads: the prefix of the inspection and the nonce of
PAYLOAD_AGE before the release, then pieces of 1 MiB. lib/src/sink.dart
has ByteSink, for the content of formats 1 and 2 and for each file, and
FileSink, for the files of format 3, as the dst and the Sink of Go, with
MemoryByteSink and MemoryFileSink. Nothing is presented as valid before
step 17 ends: the output is closed only then and aborted after any
failure, and the sink aborted after any failure that follows its begin
(spec §56).
The signature and the seal are stage 5: lib/src/verdicts.dart has the
verdicts and the SecurityEvaluator, given what newSecurityContext and
EvaluateSecurityIn of Go take, which never fails the opening; the default
evaluates nothing. OpenOptions.accept is Accept of Go. And
AgePayloadDecryptor.wipe clears the key of a STREAM left unread.
The tests run open_cases.json and the mutation corpus with the texts and
the checks of Go, in memory and from a source read in pieces; capsules of
several MiB made from the fixtures, for the streaming; a capsule with a
stanza for a key of words; and the caller, the sinks and the evaluator.
open_test.dart runs on Node.js too, with open_vectors.g.dart.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
3 days ago
|
|
|
Map<String, String> authorKeys = const {},
|
|
|
|
|
void Function(Verdicts v)? accept,
|
|
|
|
|
ReleaseSource? source,
|
|
|
|
|
}) {
|
|
|
|
|
final rel = c['release']! as Json;
|
|
|
|
|
final file = c['dkk_file'] as String?;
|
|
|
|
|
return OpenOptions(
|
|
|
|
|
source:
|
|
|
|
|
source ??
|
|
|
|
|
suppliedRelease(
|
|
|
|
|
Release(rel['round']! as int, fromHex(str(rel, 'signature'))),
|
|
|
|
|
),
|
|
|
|
|
now: () => parseRfc3339(str(c, 'now')),
|
|
|
|
|
accessKeyFile: file == null ? null : fromHex(file),
|
|
|
|
|
output: output,
|
|
|
|
|
sink: sink,
|
Stage 5b: the opening evaluates the security area as Go
OpenOptions.evaluator is now evaluateSecurityInput, so that opening a
capsule of format 3 gives the verdicts of Go at step 17: the signature of
alg 1 and every verdict of the form, with the author keys of the options,
and the signature of alg 2 and the seal of seal_type 2 not evaluated
until a reader of CMS is given. notEvaluated stays for a caller that
shows no verdict.
tool/open_go_vectors.go records the verdicts of each capsule of format 3
that opens, with their lines, the key and the label of alg 1 and the
earliest valid seal, and opens the fixtures signed with alg 1 also with
their author key saved, F3, and with another, F4. Every case of
open_cases.json and every case of the mutation corpus that opens gives
those verdicts and lines: format3_signed, format3_unsigned,
format3_signature_unsupported, format3_seal_unsupported,
format3_security_v2 and format3_note all of them, and format3_signed_cms
and format3_sealed the part that needs no reader of CMS.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
3 days ago
|
|
|
evaluator: evaluator ?? evaluateSecurityInput,
|
Stage 4c: steps 9 to 18, the opening of the three formats
lib/src/open.dart and open3.dart port Open and openBody of package capsule
of datekeys-go at c531e93, with the checks, codes, steps and texts of the
reference and the detail of each check: the .dkk of step 9.a, decoded or
still encoded, with its material, its critical extensions, its capsule_id
and its capsule_digest; at least one credential; the clock and the release,
with the rule of step 9 for the failures of its source (sourceFailure);
its verification at step 10; OUTER_TIME_AGE with the tlock stanza;
INNER_ACCESS_AGE with the X25519 identities, those of the .dkk and the key
of words, and the rules of the slots; CONTROL_CBOR, header_binding,
I_PAYLOAD and P; PAYLOAD_AGE streamed, with the padding of format 2
checked and never delivered, and in format 3 the frame of BODY, the area,
the head, each file to the sink with its SHA-256 and the padding, with the
precedence of spec §63; and the commit. A failure of age keeps the code of
the identity that reports it, or is ERR_INTEGRITY with the reason of its
phase, as classify of Go.
openCapsule opens a capsule in memory and openCapsuleSource one that a
ByteSource reads: the prefix of the inspection and the nonce of
PAYLOAD_AGE before the release, then pieces of 1 MiB. lib/src/sink.dart
has ByteSink, for the content of formats 1 and 2 and for each file, and
FileSink, for the files of format 3, as the dst and the Sink of Go, with
MemoryByteSink and MemoryFileSink. Nothing is presented as valid before
step 17 ends: the output is closed only then and aborted after any
failure, and the sink aborted after any failure that follows its begin
(spec §56).
The signature and the seal are stage 5: lib/src/verdicts.dart has the
verdicts and the SecurityEvaluator, given what newSecurityContext and
EvaluateSecurityIn of Go take, which never fails the opening; the default
evaluates nothing. OpenOptions.accept is Accept of Go. And
AgePayloadDecryptor.wipe clears the key of a STREAM left unread.
The tests run open_cases.json and the mutation corpus with the texts and
the checks of Go, in memory and from a source read in pieces; capsules of
several MiB made from the fixtures, for the streaming; a capsule with a
stanza for a key of words; and the caller, the sinks and the evaluator.
open_test.dart runs on Node.js too, with open_vectors.g.dart.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
3 days ago
|
|
|
authorKeys: authorKeys,
|
|
|
|
|
accept: accept,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void main() {
|
|
|
|
|
group('the vectors of Go', () {
|
|
|
|
|
for (final section in ['fixtures', 'steps']) {
|
|
|
|
|
final cases = (_cases[section]! as List).cast<Json>();
|
|
|
|
|
for (var i = 0; i < cases.length; i++) {
|
|
|
|
|
final c = cases[i];
|
|
|
|
|
test('$section: ${c['name']}', () async {
|
|
|
|
|
final dkc = capsuleOf(c, fixture);
|
|
|
|
|
expect(differences(c, await openCase(c, dkc)), isEmpty);
|
|
|
|
|
// From a source, every fourth: the others run on the VM.
|
|
|
|
|
if (i % 4 == 0) {
|
|
|
|
|
expect(
|
|
|
|
|
differences(c, await openCase(c, dkc, fromSource: true)),
|
|
|
|
|
isEmpty,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
test('the heads', () {
|
|
|
|
|
final h = jsonDecode(openHeadsJson) as Json;
|
|
|
|
|
checkDecodeCases((h['decode']! as List).cast<Json>());
|
|
|
|
|
checkEncodeCases((h['encode']! as List).cast<Json>());
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('the notes', () {
|
|
|
|
|
final n = jsonDecode(openNotesJson) as Json;
|
|
|
|
|
checkNoteCases(
|
|
|
|
|
(n['check']! as List).cast<Json>(),
|
|
|
|
|
(n['note']! as List).cast<Json>(),
|
|
|
|
|
(n['standard']! as List).cast<Json>(),
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('the inspection: the views and the mutations', () {
|
|
|
|
|
final v = jsonDecode(openInspectJson) as Json;
|
|
|
|
|
for (final c in (v['views']! as List).cast<Json>()) {
|
|
|
|
|
final r = inspectCapsule(
|
|
|
|
|
capsuleOf(c, fixture),
|
|
|
|
|
extensions: switch (c['registry']) {
|
|
|
|
|
'standard' => const StandardExtensions(),
|
|
|
|
|
'reject_all' => const RejectAll('not today'),
|
|
|
|
|
_ => null,
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
expect(
|
|
|
|
|
inspectJson(inspectView(r, file: 'capsule.dkc')),
|
|
|
|
|
c['view'],
|
|
|
|
|
reason: str(c, 'name'),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
final mutations = (v['mutations']! as List).cast<Json>();
|
|
|
|
|
expect(mutations, hasLength(greaterThan(300)));
|
|
|
|
|
for (final m in mutations) {
|
|
|
|
|
final dkc = applyEdits(fixture(str(m, 'base')), m['edits']! as List);
|
|
|
|
|
final r = inspectCapsule(dkc);
|
|
|
|
|
final c = r.checks.last;
|
|
|
|
|
expect(
|
|
|
|
|
[c.step, c.ok, c.error ?? 'ok', if (!c.ok) c.detail],
|
|
|
|
|
[
|
|
|
|
|
m['step'] ?? 8,
|
|
|
|
|
m['result'] == 'ok',
|
|
|
|
|
m['result'],
|
|
|
|
|
if (m['result'] != 'ok') m['text'],
|
|
|
|
|
],
|
|
|
|
|
reason: 'mutation ${m['index']}',
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
group('the caller', () {
|
|
|
|
|
final single = caseNamed('format3_single');
|
|
|
|
|
final keyed = caseNamed('time_and_key_recipients: the .dkk, decoded');
|
|
|
|
|
final plain = caseNamed('time_only_extensions');
|
|
|
|
|
|
|
|
|
|
test('gets an ArgumentError for options that do not go together, with '
|
|
|
|
|
'the output aborted', () async {
|
|
|
|
|
final dkc = capsuleOf(keyed, fixture);
|
|
|
|
|
final key = decodeAccessKey(fromHex(str(keyed, 'dkk')));
|
|
|
|
|
final out = RecordingOutput();
|
|
|
|
|
await expectLater(
|
|
|
|
|
openCapsule(
|
|
|
|
|
dkc,
|
|
|
|
|
OpenOptions(
|
|
|
|
|
source: suppliedRelease(),
|
|
|
|
|
now: () => Instant(0),
|
|
|
|
|
accessKey: key,
|
|
|
|
|
accessKeyFile: fromHex(str(keyed, 'dkk')),
|
|
|
|
|
output: out,
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
throwsArgumentError,
|
|
|
|
|
);
|
|
|
|
|
expect([out.aborted, out.closed], [isArgumentError, false]);
|
|
|
|
|
await expectLater(
|
|
|
|
|
openCapsule(
|
|
|
|
|
dkc,
|
|
|
|
|
OpenOptions(
|
|
|
|
|
source: suppliedRelease(),
|
|
|
|
|
now: () => Instant(0),
|
|
|
|
|
identities: [Uint8List(31)],
|
|
|
|
|
output: RecordingOutput(),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
throwsArgumentError,
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('needs the sink for format 3 and the output for formats 1 and 2, '
|
|
|
|
|
'right after step 2, before any request', () async {
|
|
|
|
|
final calls = CaseSource(null, null);
|
|
|
|
|
final dkc = capsuleOf(single, fixture);
|
|
|
|
|
final out = RecordingOutput();
|
|
|
|
|
await expectLater(
|
|
|
|
|
openCapsule(dkc, optionsOf(single, source: calls, output: out)),
|
|
|
|
|
throwsA(
|
|
|
|
|
isArgumentError.having(
|
|
|
|
|
(e) => '${e.message}',
|
|
|
|
|
'message',
|
|
|
|
|
'open: a format 3 capsule holds files: OpenOptions.sink is '
|
|
|
|
|
'required',
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
expect(
|
|
|
|
|
[calls.calls, out.aborted, out.log],
|
|
|
|
|
[
|
|
|
|
|
0,
|
|
|
|
|
isArgumentError,
|
|
|
|
|
['abort'],
|
|
|
|
|
],
|
|
|
|
|
);
|
|
|
|
|
// Steps 3 to 8 do not matter: a truncated PUBLIC_HEADER after a valid
|
|
|
|
|
// prelude.
|
|
|
|
|
await expectLater(
|
|
|
|
|
openCapsule(dkc.sublist(0, 20), optionsOf(single, source: calls)),
|
|
|
|
|
throwsArgumentError,
|
|
|
|
|
);
|
|
|
|
|
await expectLater(
|
|
|
|
|
openCapsule(
|
|
|
|
|
capsuleOf(plain, fixture),
|
|
|
|
|
optionsOf(plain, source: calls, sink: MemoryFileSink()),
|
|
|
|
|
),
|
|
|
|
|
throwsA(
|
|
|
|
|
isArgumentError.having(
|
|
|
|
|
(e) => '${e.message}',
|
|
|
|
|
'message',
|
|
|
|
|
'open: a capsule of format 1 or 2 holds one content: '
|
|
|
|
|
'OpenOptions.output is required',
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
expect(calls.calls, 0);
|
|
|
|
|
// Steps 1 and 2 fail first: no format, nothing needed.
|
|
|
|
|
final r = await openCapsule(dkc.sublist(0, 10), optionsOf(single));
|
|
|
|
|
expect([r.error?.code, r.checks.last.step], [ErrorCode.integrity, 1]);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('leaves the sink untouched in formats 1 and 2, and the output in '
|
|
|
|
|
'format 3', () async {
|
|
|
|
|
final sink = RecordingSink();
|
|
|
|
|
final out = RecordingOutput();
|
|
|
|
|
final r = await openCapsule(
|
|
|
|
|
capsuleOf(plain, fixture),
|
|
|
|
|
optionsOf(plain, output: out, sink: sink),
|
|
|
|
|
);
|
|
|
|
|
expect([r.ok, sink.log, out.closed], [true, isEmpty, true]);
|
|
|
|
|
expect(out.log.last, 'close');
|
|
|
|
|
final out3 = RecordingOutput();
|
|
|
|
|
final s = await openCapsule(
|
|
|
|
|
capsuleOf(single, fixture),
|
|
|
|
|
optionsOf(single, output: out3, sink: MemoryFileSink()),
|
|
|
|
|
);
|
|
|
|
|
expect([s.ok, out3.log], [true, isEmpty]);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('hands the sink the files in the order of the head, each closed '
|
|
|
|
|
'before the next, and commits last', () async {
|
|
|
|
|
final sink = RecordingSink();
|
|
|
|
|
final r = await openCapsule(
|
|
|
|
|
capsuleOf(single, fixture),
|
|
|
|
|
optionsOf(single, sink: sink),
|
|
|
|
|
);
|
|
|
|
|
expect(r.ok, isTrue);
|
|
|
|
|
expect(sink.log, [
|
|
|
|
|
'begin',
|
|
|
|
|
'create 0',
|
|
|
|
|
for (final l in sink.log.where((l) => l.startsWith('write 0 '))) l,
|
|
|
|
|
'close 0',
|
|
|
|
|
'commit',
|
|
|
|
|
]);
|
|
|
|
|
final written = sink.log
|
|
|
|
|
.where((l) => l.startsWith('write 0 '))
|
|
|
|
|
.map((l) => int.parse(l.split(' ')[2]))
|
|
|
|
|
.fold(0, (a, b) => a + b);
|
|
|
|
|
expect(written, r.head!.files.single.size);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('keeps the files in a MemoryFileSink, given once committed', () async {
|
|
|
|
|
final sink = MemoryFileSink();
|
|
|
|
|
final r = await openCapsule(
|
|
|
|
|
capsuleOf(single, fixture),
|
|
|
|
|
optionsOf(single, sink: sink),
|
|
|
|
|
);
|
|
|
|
|
expect(sink.head, same(r.head));
|
|
|
|
|
expect(sink.files!.single, hasLength(r.head!.files.single.size));
|
|
|
|
|
expect(
|
|
|
|
|
canonical(
|
Stage 5b: the opening evaluates the security area as Go
OpenOptions.evaluator is now evaluateSecurityInput, so that opening a
capsule of format 3 gives the verdicts of Go at step 17: the signature of
alg 1 and every verdict of the form, with the author keys of the options,
and the signature of alg 2 and the seal of seal_type 2 not evaluated
until a reader of CMS is given. notEvaluated stays for a caller that
shows no verdict.
tool/open_go_vectors.go records the verdicts of each capsule of format 3
that opens, with their lines, the key and the label of alg 1 and the
earliest valid seal, and opens the fixtures signed with alg 1 also with
their author key saved, F3, and with another, F4. Every case of
open_cases.json and every case of the mutation corpus that opens gives
those verdicts and lines: format3_signed, format3_unsigned,
format3_signature_unsupported, format3_seal_unsupported,
format3_security_v2 and format3_note all of them, and format3_signed_cms
and format3_sealed the part that needs no reader of CMS.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
3 days ago
|
|
|
filesOf(Outcome(r, RecordingOutput(), _asRecording(sink), 0, [])),
|
Stage 4c: steps 9 to 18, the opening of the three formats
lib/src/open.dart and open3.dart port Open and openBody of package capsule
of datekeys-go at c531e93, with the checks, codes, steps and texts of the
reference and the detail of each check: the .dkk of step 9.a, decoded or
still encoded, with its material, its critical extensions, its capsule_id
and its capsule_digest; at least one credential; the clock and the release,
with the rule of step 9 for the failures of its source (sourceFailure);
its verification at step 10; OUTER_TIME_AGE with the tlock stanza;
INNER_ACCESS_AGE with the X25519 identities, those of the .dkk and the key
of words, and the rules of the slots; CONTROL_CBOR, header_binding,
I_PAYLOAD and P; PAYLOAD_AGE streamed, with the padding of format 2
checked and never delivered, and in format 3 the frame of BODY, the area,
the head, each file to the sink with its SHA-256 and the padding, with the
precedence of spec §63; and the commit. A failure of age keeps the code of
the identity that reports it, or is ERR_INTEGRITY with the reason of its
phase, as classify of Go.
openCapsule opens a capsule in memory and openCapsuleSource one that a
ByteSource reads: the prefix of the inspection and the nonce of
PAYLOAD_AGE before the release, then pieces of 1 MiB. lib/src/sink.dart
has ByteSink, for the content of formats 1 and 2 and for each file, and
FileSink, for the files of format 3, as the dst and the Sink of Go, with
MemoryByteSink and MemoryFileSink. Nothing is presented as valid before
step 17 ends: the output is closed only then and aborted after any
failure, and the sink aborted after any failure that follows its begin
(spec §56).
The signature and the seal are stage 5: lib/src/verdicts.dart has the
verdicts and the SecurityEvaluator, given what newSecurityContext and
EvaluateSecurityIn of Go take, which never fails the opening; the default
evaluates nothing. OpenOptions.accept is Accept of Go. And
AgePayloadDecryptor.wipe clears the key of a STREAM left unread.
The tests run open_cases.json and the mutation corpus with the texts and
the checks of Go, in memory and from a source read in pieces; capsules of
several MiB made from the fixtures, for the streaming; a capsule with a
stanza for a key of words; and the caller, the sinks and the evaluator.
open_test.dart runs on Node.js too, with open_vectors.g.dart.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
3 days ago
|
|
|
),
|
|
|
|
|
canonical(single['files']),
|
|
|
|
|
);
|
|
|
|
|
expect(sink.aborted, isFalse);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('gives the evaluator what Go\'s newSecurityContext takes, once the '
|
|
|
|
|
'head is read, and never fails for it', () async {
|
|
|
|
|
final seen = <SecurityInput>[];
|
|
|
|
|
final dkc = capsuleOf(single, fixture);
|
|
|
|
|
final keys = {'dkauthor1x': 'Ana'};
|
|
|
|
|
final r = await openCapsule(
|
|
|
|
|
dkc,
|
|
|
|
|
optionsOf(
|
|
|
|
|
single,
|
|
|
|
|
sink: MemoryFileSink(),
|
|
|
|
|
authorKeys: keys,
|
|
|
|
|
evaluator: (input) {
|
|
|
|
|
seen.add(input);
|
|
|
|
|
// The control is whole while the evaluator runs.
|
|
|
|
|
expect(input.control.payloadIdentity.any((b) => b != 0), isTrue);
|
|
|
|
|
return Verdicts(
|
|
|
|
|
signature: Verdict.noSignature,
|
|
|
|
|
seal: Verdict.noSeal,
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
expect(r.ok, isTrue);
|
|
|
|
|
final input = seen.single;
|
|
|
|
|
final s = splitCapsule(dkc);
|
|
|
|
|
expect(
|
|
|
|
|
[
|
|
|
|
|
input.format,
|
|
|
|
|
input.roundTime,
|
|
|
|
|
input.authorKeys,
|
|
|
|
|
toHex(input.control.headerBinding),
|
|
|
|
|
decodeHead(input.head).files.single.path,
|
|
|
|
|
input.security.isNotEmpty,
|
|
|
|
|
input.security.length <= r.areaLen!,
|
|
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
CapsuleFormat.format3,
|
|
|
|
|
r.inspection.unlockAt,
|
|
|
|
|
keys,
|
|
|
|
|
toHex(headerBinding(s.preludeBytes, s.publicHeader)),
|
|
|
|
|
r.head!.files.single.path,
|
|
|
|
|
true,
|
|
|
|
|
true,
|
|
|
|
|
],
|
|
|
|
|
);
|
|
|
|
|
expect(
|
|
|
|
|
[r.verdicts!.signature, r.verdicts!.seal],
|
|
|
|
|
[Verdict.noSignature, Verdict.noSeal],
|
|
|
|
|
);
|
|
|
|
|
// I_PAYLOAD is wiped once the opening ends.
|
|
|
|
|
expect(input.control.payloadIdentity.every((b) => b == 0), isTrue);
|
|
|
|
|
|
|
|
|
|
// An evaluator that throws: the capsule opens, not evaluated.
|
|
|
|
|
final t = await openCapsule(
|
|
|
|
|
dkc,
|
|
|
|
|
optionsOf(
|
|
|
|
|
single,
|
|
|
|
|
sink: MemoryFileSink(),
|
|
|
|
|
evaluator: (_) => throw StateError('broken'),
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
expect(
|
|
|
|
|
[t.ok, t.verdicts!.evaluated, t.verdicts!.error],
|
|
|
|
|
[true, false, isStateError],
|
|
|
|
|
);
|
Stage 5b: the opening evaluates the security area as Go
OpenOptions.evaluator is now evaluateSecurityInput, so that opening a
capsule of format 3 gives the verdicts of Go at step 17: the signature of
alg 1 and every verdict of the form, with the author keys of the options,
and the signature of alg 2 and the seal of seal_type 2 not evaluated
until a reader of CMS is given. notEvaluated stays for a caller that
shows no verdict.
tool/open_go_vectors.go records the verdicts of each capsule of format 3
that opens, with their lines, the key and the label of alg 1 and the
earliest valid seal, and opens the fixtures signed with alg 1 also with
their author key saved, F3, and with another, F4. Every case of
open_cases.json and every case of the mutation corpus that opens gives
those verdicts and lines: format3_signed, format3_unsigned,
format3_signature_unsupported, format3_seal_unsupported,
format3_security_v2 and format3_note all of them, and format3_signed_cms
and format3_sealed the part that needs no reader of CMS.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
3 days ago
|
|
|
// The default evaluates as Go: an area without a signature or a seal
|
|
|
|
|
// is F0 and S0.
|
|
|
|
|
expect(
|
|
|
|
|
OpenOptions(
|
|
|
|
|
source: suppliedRelease(r.release!),
|
|
|
|
|
now: () => r.inspection.unlockAt!,
|
|
|
|
|
).evaluator,
|
|
|
|
|
same(evaluateSecurityInput),
|
|
|
|
|
);
|
Stage 4c: steps 9 to 18, the opening of the three formats
lib/src/open.dart and open3.dart port Open and openBody of package capsule
of datekeys-go at c531e93, with the checks, codes, steps and texts of the
reference and the detail of each check: the .dkk of step 9.a, decoded or
still encoded, with its material, its critical extensions, its capsule_id
and its capsule_digest; at least one credential; the clock and the release,
with the rule of step 9 for the failures of its source (sourceFailure);
its verification at step 10; OUTER_TIME_AGE with the tlock stanza;
INNER_ACCESS_AGE with the X25519 identities, those of the .dkk and the key
of words, and the rules of the slots; CONTROL_CBOR, header_binding,
I_PAYLOAD and P; PAYLOAD_AGE streamed, with the padding of format 2
checked and never delivered, and in format 3 the frame of BODY, the area,
the head, each file to the sink with its SHA-256 and the padding, with the
precedence of spec §63; and the commit. A failure of age keeps the code of
the identity that reports it, or is ERR_INTEGRITY with the reason of its
phase, as classify of Go.
openCapsule opens a capsule in memory and openCapsuleSource one that a
ByteSource reads: the prefix of the inspection and the nonce of
PAYLOAD_AGE before the release, then pieces of 1 MiB. lib/src/sink.dart
has ByteSink, for the content of formats 1 and 2 and for each file, and
FileSink, for the files of format 3, as the dst and the Sink of Go, with
MemoryByteSink and MemoryFileSink. Nothing is presented as valid before
step 17 ends: the output is closed only then and aborted after any
failure, and the sink aborted after any failure that follows its begin
(spec §56).
The signature and the seal are stage 5: lib/src/verdicts.dart has the
verdicts and the SecurityEvaluator, given what newSecurityContext and
EvaluateSecurityIn of Go take, which never fails the opening; the default
evaluates nothing. OpenOptions.accept is Accept of Go. And
AgePayloadDecryptor.wipe clears the key of a STREAM left unread.
The tests run open_cases.json and the mutation corpus with the texts and
the checks of Go, in memory and from a source read in pieces; capsules of
several MiB made from the fixtures, for the streaming; a capsule with a
stanza for a key of words; and the caller, the sinks and the evaluator.
open_test.dart runs on Node.js too, with open_vectors.g.dart.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
3 days ago
|
|
|
final d = await openCapsule(
|
|
|
|
|
dkc,
|
|
|
|
|
optionsOf(single, sink: MemoryFileSink()),
|
|
|
|
|
);
|
Stage 5b: the opening evaluates the security area as Go
OpenOptions.evaluator is now evaluateSecurityInput, so that opening a
capsule of format 3 gives the verdicts of Go at step 17: the signature of
alg 1 and every verdict of the form, with the author keys of the options,
and the signature of alg 2 and the seal of seal_type 2 not evaluated
until a reader of CMS is given. notEvaluated stays for a caller that
shows no verdict.
tool/open_go_vectors.go records the verdicts of each capsule of format 3
that opens, with their lines, the key and the label of alg 1 and the
earliest valid seal, and opens the fixtures signed with alg 1 also with
their author key saved, F3, and with another, F4. Every case of
open_cases.json and every case of the mutation corpus that opens gives
those verdicts and lines: format3_signed, format3_unsigned,
format3_signature_unsupported, format3_seal_unsupported,
format3_security_v2 and format3_note all of them, and format3_signed_cms
and format3_sealed the part that needs no reader of CMS.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
3 days ago
|
|
|
final want = single['verdicts']! as Json;
|
Stage 4c: steps 9 to 18, the opening of the three formats
lib/src/open.dart and open3.dart port Open and openBody of package capsule
of datekeys-go at c531e93, with the checks, codes, steps and texts of the
reference and the detail of each check: the .dkk of step 9.a, decoded or
still encoded, with its material, its critical extensions, its capsule_id
and its capsule_digest; at least one credential; the clock and the release,
with the rule of step 9 for the failures of its source (sourceFailure);
its verification at step 10; OUTER_TIME_AGE with the tlock stanza;
INNER_ACCESS_AGE with the X25519 identities, those of the .dkk and the key
of words, and the rules of the slots; CONTROL_CBOR, header_binding,
I_PAYLOAD and P; PAYLOAD_AGE streamed, with the padding of format 2
checked and never delivered, and in format 3 the frame of BODY, the area,
the head, each file to the sink with its SHA-256 and the padding, with the
precedence of spec §63; and the commit. A failure of age keeps the code of
the identity that reports it, or is ERR_INTEGRITY with the reason of its
phase, as classify of Go.
openCapsule opens a capsule in memory and openCapsuleSource one that a
ByteSource reads: the prefix of the inspection and the nonce of
PAYLOAD_AGE before the release, then pieces of 1 MiB. lib/src/sink.dart
has ByteSink, for the content of formats 1 and 2 and for each file, and
FileSink, for the files of format 3, as the dst and the Sink of Go, with
MemoryByteSink and MemoryFileSink. Nothing is presented as valid before
step 17 ends: the output is closed only then and aborted after any
failure, and the sink aborted after any failure that follows its begin
(spec §56).
The signature and the seal are stage 5: lib/src/verdicts.dart has the
verdicts and the SecurityEvaluator, given what newSecurityContext and
EvaluateSecurityIn of Go take, which never fails the opening; the default
evaluates nothing. OpenOptions.accept is Accept of Go. And
AgePayloadDecryptor.wipe clears the key of a STREAM left unread.
The tests run open_cases.json and the mutation corpus with the texts and
the checks of Go, in memory and from a source read in pieces; capsules of
several MiB made from the fixtures, for the streaming; a capsule with a
stanza for a key of words; and the caller, the sinks and the evaluator.
open_test.dart runs on Node.js too, with open_vectors.g.dart.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
3 days ago
|
|
|
expect(
|
Stage 5b: the opening evaluates the security area as Go
OpenOptions.evaluator is now evaluateSecurityInput, so that opening a
capsule of format 3 gives the verdicts of Go at step 17: the signature of
alg 1 and every verdict of the form, with the author keys of the options,
and the signature of alg 2 and the seal of seal_type 2 not evaluated
until a reader of CMS is given. notEvaluated stays for a caller that
shows no verdict.
tool/open_go_vectors.go records the verdicts of each capsule of format 3
that opens, with their lines, the key and the label of alg 1 and the
earliest valid seal, and opens the fixtures signed with alg 1 also with
their author key saved, F3, and with another, F4. Every case of
open_cases.json and every case of the mutation corpus that opens gives
those verdicts and lines: format3_signed, format3_unsigned,
format3_signature_unsupported, format3_seal_unsupported,
format3_security_v2 and format3_note all of them, and format3_signed_cms
and format3_sealed the part that needs no reader of CMS.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
3 days ago
|
|
|
[
|
|
|
|
|
d.verdicts!.signature?.code,
|
|
|
|
|
d.verdicts!.seal?.code,
|
|
|
|
|
d.verdicts!.lines,
|
|
|
|
|
],
|
|
|
|
|
[want['signature'], want['seal'], want['lines']],
|
Stage 4c: steps 9 to 18, the opening of the three formats
lib/src/open.dart and open3.dart port Open and openBody of package capsule
of datekeys-go at c531e93, with the checks, codes, steps and texts of the
reference and the detail of each check: the .dkk of step 9.a, decoded or
still encoded, with its material, its critical extensions, its capsule_id
and its capsule_digest; at least one credential; the clock and the release,
with the rule of step 9 for the failures of its source (sourceFailure);
its verification at step 10; OUTER_TIME_AGE with the tlock stanza;
INNER_ACCESS_AGE with the X25519 identities, those of the .dkk and the key
of words, and the rules of the slots; CONTROL_CBOR, header_binding,
I_PAYLOAD and P; PAYLOAD_AGE streamed, with the padding of format 2
checked and never delivered, and in format 3 the frame of BODY, the area,
the head, each file to the sink with its SHA-256 and the padding, with the
precedence of spec §63; and the commit. A failure of age keeps the code of
the identity that reports it, or is ERR_INTEGRITY with the reason of its
phase, as classify of Go.
openCapsule opens a capsule in memory and openCapsuleSource one that a
ByteSource reads: the prefix of the inspection and the nonce of
PAYLOAD_AGE before the release, then pieces of 1 MiB. lib/src/sink.dart
has ByteSink, for the content of formats 1 and 2 and for each file, and
FileSink, for the files of format 3, as the dst and the Sink of Go, with
MemoryByteSink and MemoryFileSink. Nothing is presented as valid before
step 17 ends: the output is closed only then and aborted after any
failure, and the sink aborted after any failure that follows its begin
(spec §56).
The signature and the seal are stage 5: lib/src/verdicts.dart has the
verdicts and the SecurityEvaluator, given what newSecurityContext and
EvaluateSecurityIn of Go take, which never fails the opening; the default
evaluates nothing. OpenOptions.accept is Accept of Go. And
AgePayloadDecryptor.wipe clears the key of a STREAM left unread.
The tests run open_cases.json and the mutation corpus with the texts and
the checks of Go, in memory and from a source read in pieces; capsules of
several MiB made from the fixtures, for the streaming; a capsule with a
stanza for a key of words; and the caller, the sinks and the evaluator.
open_test.dart runs on Node.js too, with open_vectors.g.dart.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
3 days ago
|
|
|
);
|
Stage 5b: the opening evaluates the security area as Go
OpenOptions.evaluator is now evaluateSecurityInput, so that opening a
capsule of format 3 gives the verdicts of Go at step 17: the signature of
alg 1 and every verdict of the form, with the author keys of the options,
and the signature of alg 2 and the seal of seal_type 2 not evaluated
until a reader of CMS is given. notEvaluated stays for a caller that
shows no verdict.
tool/open_go_vectors.go records the verdicts of each capsule of format 3
that opens, with their lines, the key and the label of alg 1 and the
earliest valid seal, and opens the fixtures signed with alg 1 also with
their author key saved, F3, and with another, F4. Every case of
open_cases.json and every case of the mutation corpus that opens gives
those verdicts and lines: format3_signed, format3_unsigned,
format3_signature_unsupported, format3_seal_unsupported,
format3_security_v2 and format3_note all of them, and format3_signed_cms
and format3_sealed the part that needs no reader of CMS.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
3 days ago
|
|
|
expect(want['signature'], 'F0');
|
Stage 4c: steps 9 to 18, the opening of the three formats
lib/src/open.dart and open3.dart port Open and openBody of package capsule
of datekeys-go at c531e93, with the checks, codes, steps and texts of the
reference and the detail of each check: the .dkk of step 9.a, decoded or
still encoded, with its material, its critical extensions, its capsule_id
and its capsule_digest; at least one credential; the clock and the release,
with the rule of step 9 for the failures of its source (sourceFailure);
its verification at step 10; OUTER_TIME_AGE with the tlock stanza;
INNER_ACCESS_AGE with the X25519 identities, those of the .dkk and the key
of words, and the rules of the slots; CONTROL_CBOR, header_binding,
I_PAYLOAD and P; PAYLOAD_AGE streamed, with the padding of format 2
checked and never delivered, and in format 3 the frame of BODY, the area,
the head, each file to the sink with its SHA-256 and the padding, with the
precedence of spec §63; and the commit. A failure of age keeps the code of
the identity that reports it, or is ERR_INTEGRITY with the reason of its
phase, as classify of Go.
openCapsule opens a capsule in memory and openCapsuleSource one that a
ByteSource reads: the prefix of the inspection and the nonce of
PAYLOAD_AGE before the release, then pieces of 1 MiB. lib/src/sink.dart
has ByteSink, for the content of formats 1 and 2 and for each file, and
FileSink, for the files of format 3, as the dst and the Sink of Go, with
MemoryByteSink and MemoryFileSink. Nothing is presented as valid before
step 17 ends: the output is closed only then and aborted after any
failure, and the sink aborted after any failure that follows its begin
(spec §56).
The signature and the seal are stage 5: lib/src/verdicts.dart has the
verdicts and the SecurityEvaluator, given what newSecurityContext and
EvaluateSecurityIn of Go take, which never fails the opening; the default
evaluates nothing. OpenOptions.accept is Accept of Go. And
AgePayloadDecryptor.wipe clears the key of a STREAM left unread.
The tests run open_cases.json and the mutation corpus with the texts and
the checks of Go, in memory and from a source read in pieces; capsules of
several MiB made from the fixtures, for the streaming; a capsule with a
stanza for a key of words; and the caller, the sinks and the evaluator.
open_test.dart runs on Node.js too, with open_vectors.g.dart.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
3 days ago
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('shows the verdicts to accept before step 18, which may refuse '
|
|
|
|
|
'them', () async {
|
|
|
|
|
final dkc = capsuleOf(single, fixture);
|
|
|
|
|
final verdicts = Verdicts(
|
|
|
|
|
signature: Verdict.signedOther,
|
|
|
|
|
seal: Verdict.noSeal,
|
|
|
|
|
authorKey: Uint8List(32),
|
|
|
|
|
);
|
|
|
|
|
Verdicts? shown;
|
|
|
|
|
final sink = RecordingSink();
|
|
|
|
|
final out = RecordingOutput();
|
|
|
|
|
final r = await openCapsule(
|
|
|
|
|
dkc,
|
|
|
|
|
optionsOf(
|
|
|
|
|
single,
|
|
|
|
|
sink: sink,
|
|
|
|
|
output: out,
|
|
|
|
|
evaluator: (_) => verdicts,
|
|
|
|
|
accept: (v) {
|
|
|
|
|
shown = v;
|
|
|
|
|
expect(sink.log, isNot(contains('commit')));
|
|
|
|
|
throw 'not signed by Ana';
|
|
|
|
|
},
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
expect(shown, same(verdicts));
|
|
|
|
|
expect(
|
|
|
|
|
[r.ok, r.error, r.refusal, r.head, r.verdicts],
|
|
|
|
|
[false, null, 'not signed by Ana', null, null],
|
|
|
|
|
);
|
|
|
|
|
expect(sink.state, 'aborted');
|
|
|
|
|
expect(out.aborted, 'not signed by Ana');
|
|
|
|
|
expect(
|
|
|
|
|
[r.checks.last.step, r.checks.last.ok, r.checks.last.detail],
|
|
|
|
|
[
|
|
|
|
|
17,
|
|
|
|
|
true,
|
|
|
|
|
'payload authenticated; the caller refused its verdicts and nothing '
|
|
|
|
|
'was published',
|
|
|
|
|
],
|
|
|
|
|
);
|
|
|
|
|
// Accepted, they are those of the result.
|
|
|
|
|
final a = await openCapsule(
|
|
|
|
|
dkc,
|
|
|
|
|
optionsOf(
|
|
|
|
|
single,
|
|
|
|
|
sink: MemoryFileSink(),
|
|
|
|
|
evaluator: (_) => verdicts,
|
|
|
|
|
accept: (v) {},
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
expect([a.ok, a.verdicts], [true, same(verdicts)]);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('does not wipe the credentials of the caller, and wipes the .dkk it '
|
|
|
|
|
'decodes', () async {
|
|
|
|
|
final dkc = capsuleOf(keyed, fixture);
|
|
|
|
|
final key = decodeAccessKey(fromHex(str(keyed, 'dkk')));
|
|
|
|
|
final material = Uint8List.fromList(key.material);
|
|
|
|
|
final identity = Uint8List.fromList(material);
|
|
|
|
|
final out = MemoryByteSink();
|
|
|
|
|
final rel = keyed['release']! as Json;
|
|
|
|
|
final r = await openCapsule(
|
|
|
|
|
dkc,
|
|
|
|
|
OpenOptions(
|
|
|
|
|
source: suppliedRelease(
|
|
|
|
|
Release(rel['round']! as int, fromHex(str(rel, 'signature'))),
|
|
|
|
|
),
|
|
|
|
|
now: () => parseRfc3339(str(keyed, 'now')),
|
|
|
|
|
accessKey: key,
|
|
|
|
|
identities: [identity],
|
|
|
|
|
output: out,
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
expect(r.ok, isTrue);
|
|
|
|
|
expect([key.material, identity], [material, material]);
|
|
|
|
|
expect(out.bytes, isNotNull);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
group('the sinks of memory', () {
|
|
|
|
|
test('MemoryByteSink gives its bytes once closed, and wipes them on '
|
|
|
|
|
'abort', () {
|
|
|
|
|
final s = MemoryByteSink();
|
|
|
|
|
final a = Uint8List.fromList([1, 2, 3]);
|
|
|
|
|
s.add(a);
|
|
|
|
|
s.add(Uint8List.fromList([4]));
|
|
|
|
|
expect(s.bytes, isNull);
|
|
|
|
|
s.close();
|
|
|
|
|
expect(s.bytes, [1, 2, 3, 4]);
|
|
|
|
|
expect(() => s.add(Uint8List(1)), throwsStateError);
|
|
|
|
|
final t = MemoryByteSink();
|
|
|
|
|
final b = Uint8List.fromList([9, 9]);
|
|
|
|
|
t.add(b);
|
|
|
|
|
t.abort('failed');
|
|
|
|
|
expect(
|
|
|
|
|
[t.bytes, t.abortReason, b],
|
|
|
|
|
[
|
|
|
|
|
null,
|
|
|
|
|
'failed',
|
|
|
|
|
[0, 0],
|
|
|
|
|
],
|
|
|
|
|
);
|
|
|
|
|
expect(t.close, throwsStateError);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('MemoryFileSink gives its files only once committed', () {
|
|
|
|
|
final s = MemoryFileSink();
|
|
|
|
|
final h = Head(
|
|
|
|
|
salt: Uint8List(32),
|
|
|
|
|
files: [
|
|
|
|
|
HeadFile(path: 'a', size: 1, start: 0, end: 1, sha256: Uint8List(32)),
|
|
|
|
|
HeadFile(path: 'b', size: 0, start: 1, end: 1, sha256: Uint8List(32)),
|
|
|
|
|
],
|
|
|
|
|
);
|
|
|
|
|
s.begin(h);
|
|
|
|
|
s.create(0)
|
|
|
|
|
..add(Uint8List.fromList([7]))
|
|
|
|
|
..close();
|
|
|
|
|
s.create(1).close();
|
|
|
|
|
expect([s.head, s.files], [null, null]);
|
|
|
|
|
s.commit();
|
|
|
|
|
expect(s.head, same(h));
|
|
|
|
|
expect(s.files, [
|
|
|
|
|
[7],
|
|
|
|
|
isEmpty,
|
|
|
|
|
]);
|
|
|
|
|
final t = MemoryFileSink()..begin(h);
|
|
|
|
|
final f = t.create(0)..add(Uint8List.fromList([5]));
|
|
|
|
|
t.abort('no');
|
|
|
|
|
expect([t.aborted, t.files, f.bytes], [true, null, null]);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
group('the sources', () {
|
|
|
|
|
test('BytesSource reads views of its bytes, and readRange keeps '
|
|
|
|
|
'reading', () async {
|
|
|
|
|
final b = Uint8List.fromList(List.generate(100, (i) => i));
|
|
|
|
|
final s = BytesSource(b);
|
|
|
|
|
expect(s.length, 100);
|
|
|
|
|
expect(await s.read(98, 10), [98, 99]);
|
|
|
|
|
expect(await s.read(100, 10), isEmpty);
|
|
|
|
|
final c = ChunkySource(b, 7);
|
|
|
|
|
final r = await readSource(c, 3, 50);
|
|
|
|
|
expect(r, List.generate(50, (i) => i + 3));
|
|
|
|
|
expect(c.reads, 8);
|
|
|
|
|
expect(await readSource(c, 90, 50), List.generate(10, (i) => 90 + i));
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('a source that gives nothing before its end is an error of the '
|
|
|
|
|
'caller', () async {
|
|
|
|
|
final r = await openCapsuleSource(
|
|
|
|
|
_Short(capsuleOf(singleCase(), fixture)),
|
|
|
|
|
optionsOf(singleCase(), sink: MemoryFileSink()),
|
|
|
|
|
).then<Object?>((o) => o, onError: (Object e) => e);
|
|
|
|
|
expect(r, isStateError);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Json singleCase() => caseNamed('format3_single');
|
|
|
|
|
|
|
|
|
|
/// readRange of source.dart, which lib/datekeys.dart does not export.
|
|
|
|
|
Future<Uint8List> readSource(ByteSource s, int offset, int n) async {
|
|
|
|
|
final out = BytesBuilder();
|
|
|
|
|
for (var at = offset; at < offset + n && at < s.length;) {
|
|
|
|
|
final piece = await s.read(at, offset + n - at);
|
|
|
|
|
if (piece.isEmpty) break;
|
|
|
|
|
out.add(piece);
|
|
|
|
|
at += piece.length;
|
|
|
|
|
}
|
|
|
|
|
return out.takeBytes();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// A source whose reads past its first 100 bytes give nothing.
|
|
|
|
|
final class _Short implements ByteSource {
|
|
|
|
|
_Short(this._b);
|
|
|
|
|
final Uint8List _b;
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
int get length => _b.length;
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
Future<Uint8List> read(int offset, int n) async {
|
|
|
|
|
if (offset >= 100) return Uint8List(0);
|
|
|
|
|
final end = offset + n < 100 ? offset + n : 100;
|
|
|
|
|
return Uint8List.sublistView(_b, offset, end);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// A RecordingSink with the files of [m], for filesOf.
|
|
|
|
|
RecordingSink _asRecording(MemoryFileSink m) {
|
|
|
|
|
final r = RecordingSink();
|
|
|
|
|
r.files = [for (final f in m.files!) BytesBuilder()..add(f)];
|
|
|
|
|
return r;
|
|
|
|
|
}
|