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
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
|
// Author: cirakg & claude ai
// Summary: Watchdog stress build: the largest code of every 2D symbology, encoded, packed, turned into runs and drawn, at twice the normal steps per event; the PIN and change-queue work at the same margin.
//
// Two versions of this module exist; each build keeps exactly one:
// * (:no_stress), in every normal build: ON is false.
// * (:stress), only with stress.jungle (`make watchdog`): the app skips
// the vault and runs StressTest.Job instead of the list.
// Connect IQ stops code that runs too long in one event ("Watchdog
// Tripped"), but only in the simulator's app runs, never in unit tests. So
// this build runs every 2D encoder, the cache packer and the run finder on
// the biggest inputs the app accepts, with TWO job steps per runner step:
// a pass shows that each step uses at most half of the watchdog budget
// (120,000 bytecodes per event on the 512 KB watches, 240,000 elsewhere,
// where the runner already does two steps per tick). Codes that fit the
// screen are then drawn the way CodeView draws them (RunsPainter: straight
// from the runs, or from a bitmap painted in steps), in one onUpdate.
// Before the last code, VaultCase items measure the vault work after a PIN
// and on the change queue (LockJob, ChangeQueueJob, ManageRun replacing
// 4 KB of contents or a long secret, counting and describing the queue,
// 0.9.0 items) at the same margin.
// docker/scripts/app-diag.sh watchdog reads the DIAG stress lines.
import Toybox.Application;
import Toybox.Graphics;
import Toybox.Lang;
import Toybox.System;
import Toybox.WatchUi;
(:stress)
module StressTest {
const ON = true;
// Steps per runner step: the safety margin being tested.
const MARGIN = 2;
// The last code's text, which stays on the screen to be decoded.
const SCREEN_TEXT = "OTPW stress";
// One item: an encoder for the largest input of its kind, through
// Codes.DisplayJob (encode, pack, runs) like CodeView, MARGIN steps at a time.
class Item extends Jobs.Job {
var name as String;
var steps as Number = 0;
var job as Codes.DisplayJob;
var symbology as Number;
var painter as RunsPainter? = null;
// Highest memory use seen between steps.
var peak as Number = 0;
function initialize(label as String, type as Number, encoder as Codes.MatrixJob) {
Job.initialize();
name = label;
symbology = type;
job = new Codes.DisplayJob(encoder, null);
}
// The display job, then (when there are runs) the painter.
function step(budget as Number) as Boolean {
for (var i = 0; i < MARGIN; i++) {
steps++;
var used = System.getSystemStats().usedMemory;
peak = used > peak ? used : peak;
var p = painter;
if (p != null) {
if (p.step(budget)) {
return true;
}
} else if (job.step(budget)) {
var runs = job.runs;
if (runs == null) {
return true;
}
painter = new RunsPainter(runs, Codes.minModulePx(symbology));
}
}
return false;
}
}
// A RAM store for the vault cases. Values are kept as they are, without
// the copies Application.Storage (or the tests' MemoryBackend) makes, so
// the store adds no VM loops to what is measured, and two full vaults
// fit without the storage limits.
class RamBackend extends Persist.Backend {
var values as Dictionary<String, Application.Storage.ValueType> = {} as Dictionary<String, Application.Storage.ValueType>;
function initialize() {
Backend.initialize();
}
function getValue(key as String) as Application.Storage.ValueType {
return values[key] as Application.Storage.ValueType;
}
function setValue(key as String, value as Application.Storage.ValueType) as Void {
values[key] = value;
}
function deleteValue(key as String) as Void {
values.remove(key);
}
}
// One case of vault work after a PIN or on a queue change. Setup builds
// the vaults in small steps (one entry, queue item or KDF step each).
// The measured run then does the work at MARGIN: what runs in one event
// runs twice in it, and jobs run on two identical vaults (A and B), one
// step of each per step of this job, in stages (one job per vault each).
// * UNLOCK: each vault has a PIN, LOCKS unlocked entries with a queued
// "Require PIN" each, `pending` pending locked entries and a locked
// session. B's unlock derivation finishes during setup; A's runs in
// the measured run. Its last step finishes both unlocks, as
// PinView.onUnlockDone does; then the LockJobs run.
// * COUNT / DESCRIBE: one locked vault. COUNT's queue holds a REPLACE
// of a locked code with `material` bytes of checked contents and
// LOCKS - 1 LOCKs, and the work is ChangeQueue.changed() +
// countToConfirm(). DESCRIBE's holds LOCKS edits with names of the
// largest size, and the work is DescribeJob (QueueMenu), stepped on
// the one vault MARGIN times per step.
// * REPLACE_QUEUED: a locked code entry, a REPLACE with `material`
// digits queued by ManageRun (as the settings queue it) during
// setup; the measured run applies it with ChangeQueueJob.
// * REPLACE_DIRECT: an unlocked code entry, and the same REPLACE
// applied at once (ManageRun, not confirmed, as SettingsJob runs it).
// * LEGACY: a locked code entry and a 0.9.0 queue item (a bare box,
// aad "OTPW-Q-1") replacing it with `material` raw digits; locked,
// with both unlock derivations done in setup. Stages: DescribeJob
// (the menu), the unlock and LockJob, ChangeQueueJob ("Confirm all"
// with the menu's keys).
// * SECRET: a locked OTP entry, and a REPLACE with a valid Base32
// secret padded with spaces to ManageRun.MAX_SECRET_TEXT characters:
// queued (ManageRun), then confirmed (ChangeQueueJob).
// * SPACES: an unlocked code entry and a REPLACE of `material` spaces
// (refused: "Contents: missing").
class VaultCase extends Jobs.Job {
static const UNLOCK = 0;
static const COUNT = 1;
static const DESCRIBE = 2;
static const REPLACE_QUEUED = 3;
static const REPLACE_DIRECT = 4;
static const LEGACY = 5;
static const SECRET = 6;
static const SPACES = 7;
// Unlocked entries, one queued item each (ChangeQueue.MAX_ITEMS).
static const LOCKS = 16;
static const PIN = "1234";
var name as String;
var kind as Number;
var pending as Number;
var material as Number;
// False during setup, true in the measured run.
var measuring as Boolean = false;
var steps as Number = 0;
var measured as Number = 0;
var peak as Number = 0;
var result as String = "";
private var _vaults as Array<Vault> = [] as Array<Vault>;
private var _pins as Array<PinJob> = [] as Array<PinJob>;
private var _targets as Array<String> = [] as Array<String>;
private var _phase as Number = 0;
private var _n as Number = 0;
private var _job as PinJob? = null;
private var _setupRun as ManageRun? = null;
// The measured stage, its jobs (one per vault) and which are done.
private var _stage as Number = -1;
private var _jobs as Array<Jobs.Job> = [] as Array<Jobs.Job>;
private var _done as Array<Boolean> = [] as Array<Boolean>;
// LEGACY: the keys each vault's menu read.
private var _keys as Array<Array<String>> = [] as Array<Array<String>>;
function initialize(label as String, type as Number, pendingCount as Number, materialBytes as Number) {
Job.initialize();
name = label;
kind = type;
pending = pendingCount;
material = materialBytes;
}
function step(budget as Number) as Boolean {
var used = System.getSystemStats().usedMemory;
peak = used > peak ? used : peak;
if (measuring) {
measured++;
return measure(budget);
}
steps++;
return setup(budget);
}
private function vaultCount() as Number {
return kind == COUNT || kind == DESCRIBE ? 1 : 2;
}
// Entries per vault.
private function entryCount() as Number {
return kind >= REPLACE_QUEUED ? 1 : LOCKS;
}
// One bounded setup step; true when every vault is ready.
private function setup(budget as Number) as Boolean {
var v = _vaults.size() > 0 ? _vaults[_vaults.size() - 1] : null;
if (_phase == 0) {
v = new Vault(new RamBackend(), new TimeSource());
_vaults.add(v);
_targets = [] as Array<String>;
_job = v.beginSetPin(Bytes.fromUtf8(PIN), Kdf.MIN_ITERATIONS);
_phase = 1;
} else if (_phase == 1) {
if ((_job as PinJob).step(budget)) {
(v as Vault).finishSetPin(_job as PinJob);
_job = null;
_phase = 2;
_n = 0;
}
} else if (_phase == 2) {
_targets.add((v as Vault).add(newEntry()));
_n++;
if (_n == entryCount()) {
if (kind <= DESCRIBE || kind == LEGACY) {
(v as Vault).lock();
}
_phase = 3;
_n = 0;
}
} else if (_phase == 3) {
// Locked entries added while locked wait under "p." (pending).
if (_n < pending) {
(v as Vault).add(Fixtures.otp("Pending " + _n, "acct", "Stress", Entry.OTP_TOTP, Hashes.SHA1, 6, 30,
"12345678901234567890", true));
_n++;
} else {
_phase = 4;
_n = 0;
}
} else if (_phase == 4) {
if (queueStep(v as Vault)) {
if (kind != UNLOCK && kind != LEGACY) {
_phase = 0;
return _vaults.size() == vaultCount();
}
_pins.add((v as Vault).beginUnlock(Bytes.fromUtf8(PIN)));
// UNLOCK: the last vault (A) is unlocked in the measured run.
if (kind == UNLOCK && _vaults.size() == vaultCount()) {
return true;
}
_phase = 5;
}
} else if (_pins[_pins.size() - 1].step(budget)) {
_phase = 0;
return _vaults.size() == vaultCount();
}
return false;
}
private function newEntry() as Entry {
if (kind == SECRET) {
return Fixtures.otp("Bank", "acct", "Stress", Entry.OTP_TOTP, Hashes.SHA1, 6, 30, "12345678901234567890", true);
}
if (kind >= REPLACE_QUEUED || (kind != UNLOCK && _n == 0)) {
// The code a REPLACE targets; locked unless replaced directly.
var e = Fixtures.code("Card", Codes.QR, "1234567890");
e.locked = kind != REPLACE_DIRECT && kind != SPACES;
return e;
}
return Fixtures.otp("Stress " + _n, "acct", "Stress", Entry.OTP_TOTP, Hashes.SHA1, 6, 30, "12345678901234567890", false);
}
// The REPLACE of the replace cases, as ManageForm.read gives it.
private function replaceAction(v as Vault) as ManageAction {
var a = new ManageAction();
a.id = _targets[0];
a.action = ManageAction.REPLACE;
if (kind == SECRET) {
// 200 Base32 characters (125 bytes) in groups, spaces around.
var group = repeat(" ", 16) + "AAAA";
var groups = 200 / 4;
a.text = repeat(" ", ManageRun.MAX_SECRET_TEXT - groups * group.length()) + repeat(group, groups);
} else if (kind == SPACES) {
a.text = repeat(" ", material);
} else {
a.text = repeat("0123456789", (material + 9) / 10).substring(0, material) as String;
}
return a;
}
// One step of queueing; true when the vault's queue is complete.
private function queueStep(v as Vault) as Boolean {
if (kind == REPLACE_DIRECT || kind == SECRET || kind == SPACES) {
return true;
}
if (kind == LEGACY) {
// As 0.9.0 queued it: raw contents inside one box.
var old = new ManageAction();
old.id = _targets[0];
old.action = ManageAction.REPLACE;
old.label = "Card";
old.material = Bytes.fromUtf8(replaceAction(v).text as String);
var stored = ChangeQueue.items(v);
stored.add(SealedBox.sealKeep(v.keyManager.deviceKeys(), Bytes.fromUtf8(ChangeQueue.AAD_V1), old.toBytes(),
[] as Array<ByteArray>)); // setup: nothing to wipe
v.storage().setValue(ChangeQueue.KEY, stored);
ChangeQueue.changed();
return true;
}
if (kind == REPLACE_QUEUED) {
var run = _setupRun;
if (run == null) {
_setupRun = new ManageRun(v, replaceAction(v), false);
return false;
}
if (!run.step(1)) {
return false;
}
_setupRun = null;
if (!run.queued) {
result = " setup_problem=" + run.message;
}
return true;
}
var a = new ManageAction();
a.id = _targets[_n];
a.label = "Stress " + _n;
a.action = ManageAction.LOCK;
if (kind == DESCRIBE) {
a.action = ManageAction.EDIT;
a.name = repeat("N", Entry.MAX_NAME_BYTES);
a.account = repeat("A", Entry.MAX_NAME_BYTES);
a.group = repeat("G", Entry.MAX_GROUP_BYTES);
a.label = Forms.label(a.name, a.account);
} else if (kind == COUNT && _n == 0) {
// Checked contents, as ManageRun queues them.
a.action = ManageAction.REPLACE;
a.material = Bytes.fromUtf8(repeat("abcdefghij", (material + 9) / 10).substring(0, material) as String);
a.decoded = true;
}
ChangeQueue.add(v, a);
_n++;
return _n == LOCKS;
}
// The measured run.
private function measure(budget as Number) as Boolean {
if (kind == COUNT) {
for (var i = 0; i < MARGIN; i++) {
ChangeQueue.changed();
result = " to_confirm=" + ChangeQueue.countToConfirm(_vaults[0]);
}
return true;
}
if (_stage < 0) {
if (kind == UNLOCK) {
// A's derivation; its last step finishes both unlocks.
if (!_pins[_vaults.size() - 1].step(budget)) {
return false;
}
Diag.data("stress", "kdf_done name=" + name);
}
return nextStage();
}
var all = true;
for (var i = 0; i < _jobs.size(); i++) {
if (!_done[i]) {
_done[i] = _jobs[i].step(1);
all = all && _done[i];
}
}
if (!all) {
return false;
}
summary();
return nextStage();
}
// Start the next stage's jobs (A first); true when there is none.
private function nextStage() as Boolean {
_stage++;
var stages = kind == LEGACY ? 3 : (kind == SECRET ? 2 : 1);
if (_stage >= stages) {
return true;
}
var previous = _jobs;
_jobs = [] as Array<Jobs.Job>;
_done = [] as Array<Boolean>;
var last = _vaults.size() - 1;
for (var i = last; i >= 0; i--) {
var v = _vaults[i];
var job = new Jobs.Job(); // replaced below
if (kind == DESCRIBE) {
_jobs.add(new DescribeJob(v));
_done.add(false);
job = new DescribeJob(v);
} else if (kind == UNLOCK) {
result += " v" + i + "_unlocked=" + v.finishUnlock(_pins[i]);
job = new LockJob(v, true);
} else if (kind == REPLACE_QUEUED || (kind == SECRET && _stage == 1)) {
job = new ChangeQueueJob(v, null);
} else if (kind == LEGACY) {
if (_stage == 0) {
job = new DescribeJob(v);
} else if (_stage == 1) {
_keys.add((previous[last - i] as DescribeJob).keys);
result += " v" + i + "_unlocked=" + v.finishUnlock(_pins[i]);
job = new LockJob(v, true);
} else {
job = new ChangeQueueJob(v, _keys[last - i]);
}
} else {
job = new ManageRun(v, replaceAction(v), false);
}
_jobs.add(job as Jobs.Job);
_done.add(false);
}
return false;
}
private function summary() as Void {
var job = _jobs[0];
var v = _vaults[_vaults.size() - 1];
result += " [" + _stage + "]";
if (job instanceof LockJob) {
ChangeQueue.takeLockLines(); // as the PIN screen does when it closes
result += " lock_lines=" + job.lines.size() + " pending_left=" + v.pendingIds().size() + " ids=" + v.ids().size()
+ " queue_left=" + ChangeQueue.count(v);
} else if (job instanceof ChangeQueueJob) {
result += " lines=" + job.lines.size() + " first='" + job.lines[0] + "' queue_left=" + ChangeQueue.count(v);
} else if (job instanceof DescribeJob) {
result += " described=" + job.lines.size();
} else if (job instanceof ManageRun) {
result += " failed=" + job.failed + " queued=" + job.queued + " message='" + job.message + "'";
}
}
}
// The screen: progress while encoding, the runs once an item is done.
class StressView extends WatchUi.View {
var title as String = "Stress test";
var painter as RunsPainter? = null;
private var _first as Boolean = true;
function initialize() {
View.initialize();
}
function onUpdate(dc as Graphics.Dc) as Void {
dc.setColor(Graphics.COLOR_BLACK, Graphics.COLOR_WHITE);
dc.clear();
var p = painter;
if (p != null) {
var drawn = p.draw(dc);
Diag.data("stress", "drawn name=" + title + " runs=" + p.runs.runs.size() + " direct=" + p.direct()
+ " drawn=" + drawn);
painter = null;
} else {
dc.drawText(dc.getWidth() / 2, dc.getHeight() / 2, Graphics.FONT_XTINY, title, Graphics.TEXT_JUSTIFY_CENTER);
}
if (_first) {
_first = false;
Diag.firstFrame();
start();
}
}
}
var view as StressView? = null;
var items as Array<Item or VaultCase> = [] as Array<Item or VaultCase>;
var current as Item? = null;
var currentCase as VaultCase? = null;
var runner as Jobs.JobRunner? = null;
function initialView() as [WatchUi.Views] or [WatchUi.Views, WatchUi.InputDelegates] {
var v = new StressView();
view = v;
return [v];
}
// The largest inputs: QR version 40 (bytes) and a long digit string at
// level H, Data Matrix 144 x 144 (ASCII) and Base256, Aztec 32 layers
// (text and binary), PDF417 as big as this screen takes; then the
// largest Data Matrix and QR that fit the screen, to be drawn (on most
// watches the QR is drawn from a bitmap, RunsPainter's other path).
function start() as Void {
var letters = repeat("abcdefghij", 290);
var digits = repeat("0123456789", 80);
var binary = repeat("é€", 120);
var screen = System.getDeviceSettings();
var w = screen.screenWidth;
var h = screen.screenHeight;
var round = screen.screenShape == System.SCREEN_SHAPE_ROUND;
items = [
new Item("qr_v40", Codes.QR, new Qr.QrJob(letters, 0, 0, -1)),
new Item("qr_digits_h", Codes.QR, new Qr.QrJob(digits, 3, 0, -1)),
new Item("dm_144", Codes.DATAMATRIX, new DataMatrix.DataMatrixJob(letters.substring(0, 1500) as String)),
new Item("dm_base256", Codes.DATAMATRIX, new DataMatrix.DataMatrixJob(binary)),
new Item("aztec_text", Codes.AZTEC, new Aztec.AztecJob(letters.substring(0, 1400) as String, 0)),
new Item("aztec_binary", Codes.AZTEC, new Aztec.AztecJob(binary.substring(0, 160) as String, 3)),
new Item("pdf417_text", Codes.PDF417, new Pdf417.Pdf417Job(letters.substring(0, w > 300 ? 900 : 250) as String,
1, w, h, round)),
new Item("pdf417_digits", Codes.PDF417, new Pdf417.Pdf417Job(digits.substring(0, 400) as String, 1, w, h, round)),
new Item("dm_fit", Codes.DATAMATRIX, new DataMatrix.DataMatrixJob(letters.substring(0, largestDataMatrix(w, h, round))
as String)),
// Single-event vault work (VaultCase).
new VaultCase("queue_count_big", VaultCase.COUNT, 0, Entry.MAX_PAYLOAD_BYTES),
new VaultCase("queue_describe", VaultCase.DESCRIBE, 0, 0),
new VaultCase("unlock_locks", VaultCase.UNLOCK, 0, 0),
new VaultCase("unlock_pending", VaultCase.UNLOCK, Vault.MAX_ENTRIES - VaultCase.LOCKS, 0),
new VaultCase("replace_code_4k", VaultCase.REPLACE_QUEUED, 0, Entry.MAX_PAYLOAD_BYTES),
new VaultCase("replace_code_4k_direct", VaultCase.REPLACE_DIRECT, 0, Entry.MAX_PAYLOAD_BYTES),
new VaultCase("queue_legacy_4k", VaultCase.LEGACY, 0, Entry.MAX_PAYLOAD_BYTES),
new VaultCase("replace_secret_1k", VaultCase.SECRET, 0, 0),
new VaultCase("replace_spaces_4k", VaultCase.SPACES, 0, Entry.MAX_PAYLOAD_BYTES),
// Last, so it stays on the screen: app-diag.sh decodes it.
new Item("qr_fit", Codes.QR, new Qr.QrJob(SCREEN_TEXT, 1, largestQrVersion(w, h, round), -1))
] as Array<Item or VaultCase>;
Diag.data("stress", "start used_kb=" + System.getSystemStats().usedMemory / 1024);
next();
}
function next() as Void {
if (items.size() == 0) {
Diag.data("stress", "all done");
return;
}
var item = items[0];
items = items.slice(1, null) as Array<Item or VaultCase>;
if (item instanceof VaultCase) {
currentCase = item;
(view as StressView).title = item.name;
WatchUi.requestUpdate();
var r = new Jobs.JobRunner(item, Kdf.ITERATIONS_PER_STEP, new Lang.Method(StressTest, :onCaseReady));
runner = r;
r.start();
return;
}
current = item;
(view as StressView).title = item.name;
WatchUi.requestUpdate();
var r = new Jobs.JobRunner(item, 1, new Lang.Method(StressTest, :onItemDone));
runner = r;
r.start();
}
function onItemDone() as Void {
var item = current as Item;
var runs = item.job.runs;
Diag.data("stress", "done name=" + item.name + " steps=" + item.steps + " peak_kb=" + item.peak / 1024
+ " total_kb=" + System.getSystemStats().totalMemory / 1024 + " error=" + item.job.error
+ " size=" + (runs != null ? runs.width + "x" + runs.height : "-"));
(view as StressView).painter = item.painter;
WatchUi.requestUpdate();
// The next item starts in its own event, after the draw.
var r = new Jobs.JobRunner(new Jobs.Job(), 1, new Lang.Method(StressTest, :next));
runner = r;
r.start();
}
// Setup done: the measured run starts with a fresh runner, so its first
// event holds no setup step.
function onCaseReady() as Void {
var c = currentCase as VaultCase;
c.measuring = true;
Diag.data("stress", "start name=" + c.name + " setup_steps=" + c.steps + " used_kb=" + System.getSystemStats().usedMemory / 1024);
var r = new Jobs.JobRunner(c, Kdf.ITERATIONS_PER_STEP, new Lang.Method(StressTest, :onCaseDone));
runner = r;
r.start();
}
function onCaseDone() as Void {
var c = currentCase as VaultCase;
Diag.data("stress", "done name=" + c.name + " setup_steps=" + c.steps + " measured_steps=" + c.measured + " peak_kb=" + c.peak / 1024
+ " total_kb=" + System.getSystemStats().totalMemory / 1024 + c.result);
currentCase = null;
var r = new Jobs.JobRunner(new Jobs.Job(), 1, new Lang.Method(StressTest, :next));
runner = r;
r.start();
}
function repeat(text as String, times as Number) as String {
var out = "";
for (var i = 0; i < times; i++) {
out += text;
}
return out;
}
// The largest QR version that fits this screen at 2 px per module.
function largestQrVersion(w as Number, h as Number, round as Boolean) as Number {
for (var v = 40; v > 1; v--) {
var total = 17 + 4 * v + 8;
if (Codes.fits(total, total, 2, w, h, round)) {
return v;
}
}
return 1;
}
// An ASCII payload length that fills the largest Data Matrix fitting
// this screen at 2 px per module.
function largestDataMatrix(w as Number, h as Number, round as Boolean) as Number {
var best = 1;
for (var i = 0; i < DataMatrix.SIZES.size(); i++) {
var s = DataMatrix.SIZES[i] as Array<Number>;
if (Codes.fits(s[0] + 2, s[0] + 2, 2, w, h, round)) {
best = s[3];
}
}
return best;
}
}
(:no_stress)
module StressTest {
const ON = false;
function initialView() as [WatchUi.Views] or [WatchUi.Views, WatchUi.InputDelegates] {
return [new WatchUi.View()];
}
}
|