1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
|
// Author: cirakg & claude ai
// Summary: Tests for the encrypted barcode matrix cache: round trip, packed format, runs, keys of locked entries, invalidation and tampering.
import Toybox.Lang;
import Toybox.Test;
(:test)
function codeCacheRoundTripsAndInvalidates(logger as Test.Logger) as Boolean {
var store = new MemoryBackend();
var vault = new Vault(store, new FakeTime());
var e = StorageFixtures.code("QR card");
e.type = Codes.QR;
var id = vault.add(e);
var qr = Qr.encode("cache me", 1, 0, -1) as Qr.MatrixCode;
var matrix = new Codes.Matrix(qr.size, qr.size, 4, qr.modules);
vault.saveCodeCache(id, matrix.toBytes());
var back = Codes.Matrix.fromBytes(vault.loadCodeCache(id) as ByteArray);
var same = back != null && back.width == qr.size && back.quiet == 4 && Bytes.equal(back.modules, qr.modules);
// The cache is sealed: tampering gives no cache rather than a wrong code.
var stored = store.raw("x." + id) as ByteArray;
stored[stored.size() - 1] = stored[stored.size() - 1] ^ 1;
var tampered = vault.loadCodeCache(id) == null;
vault.saveCodeCache(id, matrix.toBytes());
var opened = vault.open(id);
vault.update(opened);
opened.wipeSecrets();
var clearedByUpdate = store.getValue("x." + id) == null;
vault.saveCodeCache(id, matrix.toBytes());
vault.remove(id);
return same && tampered && clearedByUpdate && store.getValue("x." + id) == null;
}
// A locked entry's cache uses the master key: unreadable while locked.
(:test)
function codeCacheOfLockedEntriesNeedsTheSession(logger as Test.Logger) as Boolean {
var vault = new Vault(new MemoryBackend(), new FakeTime());
StorageFixtures.setPin(vault, StorageFixtures.PIN);
var e = StorageFixtures.code("Locked QR");
e.type = Codes.QR;
e.locked = true;
var id = vault.add(e);
vault.saveCodeCache(id, [3, 1, 4, 1, 0xE0]b);
vault.lock();
var code = 0;
try {
vault.loadCodeCache(id);
} catch (err instanceof VaultError) {
code = err.code;
}
StorageFixtures.unlock(vault, StorageFixtures.PIN);
var back = vault.loadCodeCache(id);
var pdf = new Codes.Matrix(3, 2, 2, [1, 0, 1, 0, 1, 1]b);
pdf.rowHeight = 3;
var round = Codes.Matrix.fromBytes(pdf.toBytes());
return code == VaultError.LOCKED && back != null && Bytes.equal(back, [3, 1, 4, 1, 0xE0]b)
&& Codes.Matrix.fromBytes([2, 0, 3, 0, 1, 4, 1, 0xE0]b) != null && Codes.Matrix.fromBytes([2, 0, 3, 0, 1, 4]b) == null
&& round != null && round.rowHeight == 3 && Bytes.equal(round.modules, [1, 0, 1, 0, 1, 1]b);
}
// Format 2 holds widths over 255 (wide PDF417); a 0.7 cache (one width
// byte) is refused, so the code is encoded again.
(:test)
function codeCachePackedFormat(logger as Test.Logger) as Boolean {
var bits = new [300 * 2]b;
for (var i = 0; i < bits.size(); i++) {
bits[i] = (i * 7 / 3) % 2;
}
var wide = new Codes.Matrix(300, 2, 2, bits);
var bytes = wide.toBytes();
var back = Codes.Matrix.fromBytes(bytes);
var oldFormat = Codes.Packed.parse([3, 1, 4, 1, 0xE0]b);
var wrongSize = Codes.Packed.parse(bytes.slice(0, bytes.size() - 1));
return bytes[0] == 2 && back != null && back.width == 300 && back.height == 2 && Bytes.equal(back.modules, bits)
&& oldFormat == null && wrongSize == null && Codes.Packed.parse(null) == null;
}
// The runs drawn on screen are exactly the dark modules, row by row,
// including runs across byte boundaries and whole dark or light bytes.
(:test)
function codeRunsCoverExactlyTheDarkModules(logger as Test.Logger) as Boolean {
var codes = [Qr.encode("runs, runs, runs", 1, 0, -1) as Qr.MatrixCode];
var matrices = [new Codes.Matrix(codes[0].size, codes[0].size, 4, codes[0].modules)] as Array<Codes.Matrix>;
var stripes = new [37 * 5]b;
for (var i = 0; i < stripes.size(); i++) {
stripes[i] = (i % 37) < 20 || (i % 37) > 33 ? 1 : 0;
}
matrices.add(new Codes.Matrix(37, 5, 1, stripes));
for (var c = 0; c < matrices.size(); c++) {
var m = matrices[c];
var finder = new Codes.RunFinder(Codes.Packed.parse(m.toBytes()) as Codes.Packed);
Jobs.runToCompletion(finder, 1);
var drawn = new [m.width * m.height]b;
var runs = finder.result.runs;
for (var i = 0; i < runs.size(); i++) {
var r = runs[i];
var y = r >> 20;
var x = (r >> 10) & 0x3FF;
for (var k = 0; k < (r & 0x3FF); k++) {
if (drawn[y * m.width + x + k] == 1) {
logger.error("matrix " + c + ": run " + i + " overlaps");
return false;
}
drawn[y * m.width + x + k] = 1;
}
}
if (!Bytes.equal(drawn, m.modules)) {
logger.error("matrix " + c + ": runs differ from the modules");
return false;
}
}
return true;
}
|