What the official SDK says when it seals, from datekeys-go aefc8f6, without the CLI. lib/src/annex.dart ports annex.go: recoveryAnnex, the text of datekeys.RecoveryAnnex that the official SDK saves next to each .dkc (spec §62.1, rule 27), §79 of the specification under a title with its version and SHA-256; and recoveryAnnexSuffix, ".recuperacion.txt". Dart has no go:embed, so the text is the constant of lib/src/recovery_annex.g.dart, which tool/recovery_annex_copy.dart writes from the vendored annex/recovery.md once it matches its SOURCE.json: a multi-line string that escapes the backslash, the dollar sign, the quote and, as Unicode escapes, every rune but the line feed that Go's strconv.IsPrint rejects, with the SHA-256 of its bytes, recoveryAnnexSha256, internal, for the tests compiled to JavaScript. lib/src/profile.dart ports status.go: ProfileStatus, active, readOnly and compromised, with the names of Go's Status.String, and profileStatusOf, the state of the profile of a profile_hash and whether this release knows it, from a table where Quicknet is active (spec §71). An unknown profile is active, Go's zero Status, and not known. lib/datekeys.dart exports the four. test/annex_test.dart, also on Node.js, checks what TestRecoveryAnnex checks of the text and the SHA-256 of its bytes; test/annex_vm_test.dart, that the constant is annex/recovery.md byte for byte and that the file is §79 of the specification at the commit of annex/SOURCE.json under the title and the paragraph of TestRecoveryAnnex; formats_objects_test.dart runs the cases of TestStatus. README and CHANGELOG for them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>v0.15
parent
8eed96f449
commit
e2296b02f0
@ -0,0 +1,15 @@
|
||||
/// The recovery annex that the official SDK saves next to each .dkc (spec
|
||||
/// §62.1, rule 27), as annex.go of datekeys-go: [recoveryAnnex], its text,
|
||||
/// and [recoveryAnnexSuffix], the end of the name of its file.
|
||||
///
|
||||
/// Dart has no go:embed: the text is the constant of recovery_annex.g.dart,
|
||||
/// which tool/recovery_annex_copy.dart writes from annex/recovery.md, the
|
||||
/// copy of annex/ of datekeys-go that tool/sync_testdata.dart vendors.
|
||||
library;
|
||||
|
||||
export 'recovery_annex.g.dart' show recoveryAnnex;
|
||||
|
||||
/// What the official SDK appends to the name of a .dkc to name the file of
|
||||
/// its recovery annex, `carta.dkc.recuperacion.txt`, as RecoveryAnnexSuffix
|
||||
/// of Go. The annex is in Spanish, as the specification.
|
||||
const recoveryAnnexSuffix = '.recuperacion.txt';
|
||||
@ -0,0 +1,44 @@
|
||||
// The recovery annex of lib/src/annex.dart, as TestRecoveryAnnex of
|
||||
// datekeys-go: recoveryAnnex holds §79 of the specification of specVersion,
|
||||
// from its title to its subsection 79.8, names the version, and ends as the
|
||||
// specification does, without a CR; and its UTF-8 bytes have the SHA-256 that
|
||||
// tool/recovery_annex_copy.dart took from annex/recovery.md, so that the
|
||||
// constant compiled to JavaScript is the file too. It reads no file: it runs
|
||||
// on the VM and compiled to JavaScript. test/annex_vm_test.dart compares the
|
||||
// constant with the file and the file with §79 of the specification.
|
||||
library;
|
||||
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:datekeys/datekeys.dart';
|
||||
import 'package:datekeys/src/recovery_annex.g.dart' show recoveryAnnexSha256;
|
||||
import 'package:datekeys/src/sha256.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
void main() {
|
||||
test('the annex is §79 of the specification $specVersion', () {
|
||||
for (final s in [
|
||||
'## 79. Anexo informativo: recuperación sin software DateKeys',
|
||||
'### 79.8 Lo que el anexo no comprueba',
|
||||
'DateKeys v$specVersion,',
|
||||
]) {
|
||||
expect(recoveryAnnex, contains(s));
|
||||
}
|
||||
expect(recoveryAnnex, endsWith('conforme.\n'));
|
||||
expect(recoveryAnnex, isNot(contains('\r')));
|
||||
expect(
|
||||
recoveryAnnex,
|
||||
startsWith(
|
||||
'# Cómo abrir una cápsula DateKeys sin software de DateKeys\n\n',
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
test('its UTF-8 bytes are annex/recovery.md, by their SHA-256', () {
|
||||
expect(toHex(sha256(utf8.encode(recoveryAnnex))), recoveryAnnexSha256);
|
||||
});
|
||||
|
||||
test('its file is the name of the .dkc and .recuperacion.txt', () {
|
||||
expect(recoveryAnnexSuffix, '.recuperacion.txt');
|
||||
});
|
||||
}
|
||||
@ -0,0 +1,77 @@
|
||||
// recoveryAnnex is annex/recovery.md byte for byte, the copy that
|
||||
// tool/recovery_annex_copy.dart writes after each sync of annex/. And
|
||||
// annex/recovery.md is §79 of the specification of specVersion under the
|
||||
// title and the paragraph of TestRecoveryAnnex of datekeys-go, which names the
|
||||
// version and the SHA-256 of the specification: the specification is read
|
||||
// from the Go repository next to this one, at the commit that annex/SOURCE.json
|
||||
// pins, as test/errors_spec_test.dart reads it.
|
||||
@TestOn('vm')
|
||||
library;
|
||||
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:datekeys/datekeys.dart';
|
||||
import 'package:datekeys/src/recovery_annex.g.dart' show recoveryAnnexSha256;
|
||||
import 'package:datekeys/src/sha256.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
import '../tool/sync_testdata.dart' as vendored;
|
||||
|
||||
void main() {
|
||||
final root = Directory.current;
|
||||
final repo = Directory('../datekeys-go');
|
||||
|
||||
test('recoveryAnnex is annex/recovery.md byte for byte', () {
|
||||
final file = File('${root.path}/annex/recovery.md').readAsBytesSync();
|
||||
expect(utf8.encode(recoveryAnnex), file);
|
||||
expect(toHex(sha256(file)), recoveryAnnexSha256);
|
||||
});
|
||||
|
||||
test(
|
||||
'annex/recovery.md is §79 of the specification, as TestRecoveryAnnex',
|
||||
() {
|
||||
final commit = vendored.check(root, tree: vendored.annexTree).commit;
|
||||
final path =
|
||||
'$commit:spec/DateKeys_Protocol_Specification_v$specVersion.md';
|
||||
final r = Process.runSync(
|
||||
'git',
|
||||
['-C', repo.path, 'show', path],
|
||||
stdoutEncoding: null,
|
||||
stderrEncoding: utf8,
|
||||
);
|
||||
expect(r.exitCode, 0, reason: '${r.stderr}');
|
||||
final spec = r.stdout as List<int>;
|
||||
expect(
|
||||
File('${root.path}/annex/recovery.md').readAsStringSync(),
|
||||
annexOf(spec),
|
||||
);
|
||||
},
|
||||
skip: repo.existsSync()
|
||||
? false
|
||||
: '../datekeys-go is missing: the spec is read from it',
|
||||
);
|
||||
}
|
||||
|
||||
// The text of RecoveryAnnex for the specification spec, as recoveryAnnex of
|
||||
// annex_test.go of Go: its §79, from its title to the end of the document,
|
||||
// under a title of its own and a paragraph that names the version and the
|
||||
// SHA-256 of spec.
|
||||
String annexOf(List<int> spec) {
|
||||
final text = utf8.decode(spec);
|
||||
final i = text.indexOf('\n## 79. ');
|
||||
if (i < 0) throw StateError('the specification $specVersion has no §79');
|
||||
// strings.TrimRight(text[i+1:], " \n")
|
||||
var end = text.length;
|
||||
while (end > i + 1 && (text[end - 1] == ' ' || text[end - 1] == '\n')) {
|
||||
end--;
|
||||
}
|
||||
return '# Cómo abrir una cápsula DateKeys sin software de DateKeys\n\n'
|
||||
'Este texto acompaña a una cápsula del tiempo de DateKeys, un fichero '
|
||||
'`.dkc`: dice cómo abrirla, llegada su fecha, sin ningún software de '
|
||||
'DateKeys, por si ya no existe. Es el anexo informativo §79 de la '
|
||||
'especificación del protocolo DateKeys v$specVersion, cuyo texto tiene '
|
||||
'el SHA-256 ${toHex(sha256(spec))}. Es el mismo para toda cápsula: no '
|
||||
'lleva ningún dato de esta.\n\n'
|
||||
'${text.substring(i + 1, end)}\n';
|
||||
}
|
||||
@ -0,0 +1,93 @@
|
||||
// Writes lib/src/recovery_annex.g.dart: the text of annex/recovery.md, the
|
||||
// recovery annex that the official SDK saves next to each .dkc (spec §62.1,
|
||||
// rule 27), as the Dart constant recoveryAnnex, since Dart has no go:embed;
|
||||
// and its SHA-256, recoveryAnnexSha256, for the tests compiled to
|
||||
// JavaScript. test/annex_vm_test.dart checks that the constant is the file
|
||||
// byte for byte. Run it after every sync of annex/:
|
||||
//
|
||||
// dart run tool/recovery_annex_copy.dart
|
||||
//
|
||||
// It writes only an annex/ that matches its SOURCE.json, the check of
|
||||
// tool/sync_testdata.dart. The text goes in a multi-line string, each line of
|
||||
// the file a line of the source: a backslash, a dollar sign and a quote are
|
||||
// escaped, and so is, as a Unicode escape, every rune but the line feed that
|
||||
// Go's strconv.IsPrint rejects, such as a CR, a tab, a no-break space or a
|
||||
// control of bidirectional text, so that no character of the text is
|
||||
// invisible in the source and the line ends of a checkout never reach it.
|
||||
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:crypto/crypto.dart';
|
||||
import 'package:datekeys/src/bytes.dart' show isPrint;
|
||||
|
||||
import 'sync_testdata.dart' as vendored;
|
||||
|
||||
const _lineFeed = 0x0a;
|
||||
const _backslash = 0x5c;
|
||||
const _escaped = {_backslash, 0x24, 0x27}; // \ $ '
|
||||
|
||||
void main() {
|
||||
final root = File(Platform.script.toFilePath()).parent.parent;
|
||||
final r = vendored.check(root, tree: vendored.annexTree);
|
||||
if (r.errors.isNotEmpty) {
|
||||
throw StateError('annex/ does not match its SOURCE.json: ${r.errors}');
|
||||
}
|
||||
final bytes = File('${root.path}/annex/recovery.md').readAsBytesSync();
|
||||
final text = utf8.decode(bytes);
|
||||
if (!_sameBytes(utf8.encode(text), bytes)) {
|
||||
throw StateError('annex/recovery.md does not decode to its own bytes');
|
||||
}
|
||||
File('${root.path}/lib/src/recovery_annex.g.dart').writeAsStringSync(
|
||||
'// Generated by tool/recovery_annex_copy.dart from annex/recovery.md,\n'
|
||||
'// the recovery annex of datekeys-go. Do not edit: sync annex/ and run\n'
|
||||
'// the tool again.\n'
|
||||
'\n'
|
||||
'/// The text that the official SDK saves next to each .dkc (spec §62.1,\n'
|
||||
'/// rule 27), as RecoveryAnnex of datekeys-go: the informative annex of\n'
|
||||
'/// the specification on how to open a capsule without DateKeys software\n'
|
||||
'/// (spec §79), in Spanish, under a title that names the version of the\n'
|
||||
'/// specification and the SHA-256 of its text. It is the same for every\n'
|
||||
'/// capsule and holds nothing of one. Its UTF-8 bytes are\n'
|
||||
'/// annex/recovery.md of the Go reference, which checks it against §79 of\n'
|
||||
'/// its specification; the official SDK writes them as they are, to the\n'
|
||||
'/// name of the .dkc followed by recoveryAnnexSuffix.\n'
|
||||
"const recoveryAnnex = '''\n"
|
||||
"${_literal(text)}''';\n"
|
||||
'\n'
|
||||
'/// The SHA-256 of the UTF-8 bytes of [recoveryAnnex], for the tests\n'
|
||||
'/// that run compiled to JavaScript, where no file can be read.\n'
|
||||
'///\n'
|
||||
'/// Internal: lib/datekeys.dart does not export it.\n'
|
||||
'const recoveryAnnexSha256 =\n'
|
||||
" '${sha256.convert(bytes)}';\n",
|
||||
);
|
||||
}
|
||||
|
||||
// The body of a multi-line Dart string, without its quotes, whose value is
|
||||
// text.
|
||||
String _literal(String text) {
|
||||
final out = StringBuffer();
|
||||
for (final r in text.runes) {
|
||||
if (_escaped.contains(r)) {
|
||||
out
|
||||
..writeCharCode(_backslash)
|
||||
..writeCharCode(r);
|
||||
} else if (r == _lineFeed || isPrint(r)) {
|
||||
out.writeCharCode(r);
|
||||
} else {
|
||||
out
|
||||
..writeCharCode(_backslash)
|
||||
..write('u{${r.toRadixString(16)}}');
|
||||
}
|
||||
}
|
||||
return out.toString();
|
||||
}
|
||||
|
||||
bool _sameBytes(List<int> a, List<int> b) {
|
||||
if (a.length != b.length) return false;
|
||||
for (var i = 0; i < a.length; i++) {
|
||||
if (a[i] != b[i]) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
Loading…
Reference in new issue