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_padding_test.dart

204 lines
7.3 KiB

// The padding of spec §29.1 (lib/src/padding.dart), as padding.test.ts of
// datekeys-ts: P of both rules against a statement of spec §29.1 in BigInt
// over the whole range up to L_MAX, at the lengths where 32-bit operations
// or a floating-point logarithm fail and next to 2^53, and the check of the
// plaintext, cut in pieces of every size. It reads no file: it runs on the
// VM and compiled to JavaScript, where an int is a double. The vectors of
// Go run in formats_vectors_test.dart (testdata/vectors/padding.json) and
// in the differential (formats_padding.json).
library;
import 'dart:typed_data';
import 'package:datekeys/src/errors.dart';
import 'package:datekeys/src/padding.dart';
import 'package:test/test.dart';
// Spec §29.1 in BigInt: bloque256 = 256·ceil(L/256), at least 256;
// reforzado = max(bloque256, Padmé), Padmé keeping the S + 1 most
// significant bits of L, E = bitlen(L) - 1 and S = bitlen(E).
BigInt reference(BigInt l, PaddingRule rule) {
if (l <= BigInt.from(256)) return BigInt.from(256);
final b256 = BigInt.from(256);
final block = (l + BigInt.from(255)) ~/ b256 * b256;
if (rule == PaddingRule.bloque256) return block;
final e = l.bitLength - 1;
final s = BigInt.from(e).bitLength;
final unit = BigInt.one << (e - s);
final padme = (l + unit - BigInt.one) ~/ unit * unit;
return padme > block ? padme : block;
}
// Lengths spread over every bit length, from a fixed xorshift generator in
// BigInt, so that the same lengths run on the VM and on the web.
Iterable<int> lengths(int count) sync* {
final mask64 = (BigInt.one << 64) - BigInt.one;
var x = BigInt.parse('9e3779b97f4a7c15', radix: 16);
final lMax = BigInt.from(maxPayloadLength + 1);
for (var i = 0; i < count; i++) {
x ^= x << 13 & mask64;
x ^= x >> 7;
x ^= x << 17 & mask64;
final bits = 1 + i % 53;
yield ((x & ((BigInt.one << bits) - BigInt.one)) % lMax).toInt();
}
}
void main() {
test('names the codes of spec §29.1, and no code for no padding', () {
expect([for (final r in PaddingRule.values) r.code], [1, 2]);
expect(
[for (final r in PaddingRule.values) r.name],
['bloque256', 'reforzado'],
);
expect([0, 1, 2, 3, 257].map(PaddingRule.fromCode).toList(), [
null,
PaddingRule.bloque256,
PaddingRule.reforzado,
null,
null,
]);
expect(maxPayloadLength, 9007199254740992 - 70368744177664);
});
test('counts bits exactly, up to 2^53, where a logarithm does not', () {
final values = [0, 1, 2, 255, 256, 4294967295, 4294967296];
values.addAll([562949953421311, 9007199254740991, 9007199254740992]);
for (final n in values) {
expect(bitLength(n), BigInt.from(n).bitLength, reason: '$n');
}
// Spec §29.1: log2(2^49 - 1) is 49 in IEEE 754, and E is not.
for (final l in [257, 78000, 562949953421311, maxPayloadLength]) {
final e = BigInt.from(l).bitLength - 1;
final s = BigInt.from(e).bitLength;
expect(padmeParameters(l), (e: e, s: s, lastBits: e - s));
}
expect(() => bitLength(-1), throwsRangeError);
});
test('agrees with spec §29.1 in BigInt over the whole range', () {
var n = 0;
for (final l in lengths(3000)) {
for (final rule in PaddingRule.values) {
final want = reference(BigInt.from(l), rule);
expect(BigInt.from(paddedLength(l, rule)), want, reason: '$l $rule');
}
n++;
}
expect(n, 3000);
});
test('is exact next to 2^53 and at the boundaries of 32 bits', () {
final ls = <int>[
for (var k = 0; k < 300; k++) maxPayloadLength - k,
for (var e = 8; e <= 52; e++) ...[
for (final d in [-257, -1, 0, 1, 255, 257])
if (_pow2(e) + d >= 0) _pow2(e) + d,
],
2113929216,
2113929217,
4227858432,
4227858433,
];
for (final l in ls) {
for (final rule in PaddingRule.values) {
final p = paddedLength(l, rule);
expect(BigInt.from(p), reference(BigInt.from(l), rule), reason: '$l');
expect(p % 256 == 0 && p >= l && p >= 256, isTrue, reason: '$l');
expect(p <= 9007199254740991, isTrue, reason: '$l');
}
}
});
test('rejects an L outside 0..L_MAX as an error without a code', () {
// Its text, that of Go, is in the differential (formats_padding.json).
for (final l in [-1, maxPayloadLength + 1, 9007199254740992]) {
for (final rule in PaddingRule.values) {
expect(() => paddedLength(l, rule), throwsArgumentError);
}
}
});
test('gives the length of PAYLOAD_AGE: one stanza, the nonce, a tag per '
'chunk of 64 KiB', () {
for (final n in [0, 256, 65536, 65537, 79872, maxPayloadLength]) {
final b = BigInt.from(n);
var chunks = (b + BigInt.from(65535)) ~/ BigInt.from(65536);
if (chunks < BigInt.one) chunks = BigInt.one;
final want = BigInt.from(184) + b + BigInt.from(16) * chunks;
expect(BigInt.from(payloadAgeLength(n)), want, reason: '$n');
}
});
group('PaddingCheck', () {
// The plaintext of a format 2 capsule: content of l bytes, zeros to p.
Uint8List plaintext(int l, int p) => Uint8List(p)..fillRange(0, l, 0x61);
String failure(void Function() body) {
try {
body();
} on DateKeysException catch (e) {
expect(e.code, ErrorCode.integrity);
return e.message;
}
return 'ok';
}
test('delivers the content and never the padding, in pieces of any '
'size', () {
for (final (l, p) in [(0, 256), (34, 256), (256, 256), (300, 512)]) {
for (final size in [1, 3, 64, 256, 1000]) {
final plain = plaintext(l, p);
final check = PaddingCheck(l, p);
final out = BytesBuilder();
for (var at = 0; at < p; at += size) {
out.add(
check.add(plain.sublist(at, at + size < p ? at + size : p)),
);
}
check.close();
expect(out.takeBytes(), plain.sublist(0, l));
expect(check.received, p);
}
}
});
// The texts of Go are in the differential (formats_padding.json): here,
// the offsets that each failure names.
test('fails on the first byte of padding that is not zero, or past P, '
'or short of P', () {
final plain = plaintext(10, 256)..[200] = 1;
final check = PaddingCheck(10, 256);
expect(check.add(plain.sublist(0, 100)), hasLength(10));
expect(
failure(() => check.add(plain.sublist(100))),
allOf(startsWith('capsule: PAYLOAD_AGE: byte 200 '), contains('zero')),
);
// The first byte past P fails, before the zero bytes after it.
final long = PaddingCheck(10, 256)..add(Uint8List(256));
expect(failure(long.close), 'ok');
expect(failure(() => long.add(Uint8List(1))), contains('P = 256'));
final short = PaddingCheck(10, 256)..add(Uint8List(255));
expect(
failure(short.close),
allOf(contains(' 255 bytes'), contains('P = 256')),
);
final none = PaddingCheck(10, 256);
expect(failure(none.close), contains(' 0 bytes'));
// Format 3: from L, after BODY, and with the name of another file.
final body = PaddingCheck(659, 768, start: 659, what: 'X');
expect(body.add(Uint8List(109)), isEmpty);
body.close();
expect(() => PaddingCheck(10, 9), throwsArgumentError);
});
});
}
int _pow2(int e) {
var p = 1;
for (var i = 0; i < e; i++) {
p *= 2;
}
return p;
}

Powered by TurnKey Linux.