diff options
| author | cirakg <cirakg@cirakg.xyz> | 2026-10-18 23:44:18 +0200 |
|---|---|---|
| committer | cirakg <cirakg@cirakg.xyz> | 2026-10-18 23:44:18 +0200 |
| commit | e47ae684101981a7c20f3270994a61944059c825 (patch) | |
| tree | c2baab8baafa1be446dad7f224f1a1cf68493825 /source/util/Jobs.mc | |
| parent | b70cde45b67e1f6bb65d598d25c1a5184bdab520 (diff) | |
feat(crypto): add SHA-512, HMAC, PBKDF2, HKDF, AES-256 records and self-test
- Sha512: FIPS 180-4 in Monkey C (no native SHA-512), rotations inlined
with precomputed masks; constants derived from the primes, not typed.
- Hmac: RFC 2104 over SHA-1/256/512 (native HMAC only supports SHA-256).
- Kdf: resumable PBKDF2-HMAC-SHA256 job with calibration bounds
(2,000-200,000 iterations) and HKDF-SHA256.
- Aes: CBC with PKCS#7 over the native cipher; AES-256 chosen.
- SealedBox: encrypt-then-MAC records (HKDF-split keys, length-prefixed
aad, constant-time tag check before decryption).
- SelfTest: known-answer tests for every primitive at start-up.
- Bytes (wipe, constant-time equal), Clock (2038-safe widening, trusted
clock), Jobs (bounded steps against the watchdog, timer runner).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'source/util/Jobs.mc')
| -rw-r--r-- | source/util/Jobs.mc | 78 |
1 files changed, 78 insertions, 0 deletions
diff --git a/source/util/Jobs.mc b/source/util/Jobs.mc new file mode 100644 index 0000000..d7342e3 --- /dev/null +++ b/source/util/Jobs.mc @@ -0,0 +1,78 @@ +// Author: cirakg & claude ai +// Summary: Resumable jobs: long work split into small steps so no single event trips the watchdog. +// +// Connect IQ kills code that runs too long without returning to the system +// ("Watchdog Tripped"). The device files allow 120,000 bytecodes per event on +// the Forerunner 255/255S and 240,000 on the other targets; the simulator does +// not enforce this in test runs, so the only protection is design: anything +// that can take long (key derivation, SHA-512 batches, 2D barcode encoding, +// large imports) is a Job whose step() does a bounded amount of work. +// +// JobRunner drives a job from a timer, one step per tick, and calls back when +// it is done. Tests call Jobs.runToCompletion() instead, synchronously. + +import Toybox.Lang; +import Toybox.Timer; + +module Jobs { + + // Base class for resumable work. + class Job { + + function initialize() { + } + + // Do at most `budget` units of work. Returns true when the job is done. + function step(budget as Number) as Boolean { + return true; + } + } + + // Run a job to the end without yielding. Only for tests and work that is + // known to be small. + function runToCompletion(job as Job, budget as Number) as Void { + while (!job.step(budget)) { + } + } + + // Drives a job from a timer: one step of `budget` units every TICK_MS. + class JobRunner { + + // Pause between steps, so the UI stays responsive. + static const TICK_MS = 50; + + private var _job as Job; + private var _budget as Number; + private var _onDone as Method() as Void; + private var _timer as Timer.Timer?; + + function initialize(job as Job, budget as Number, onDone as Method() as Void) { + _job = job; + _budget = budget; + _onDone = onDone; + } + + // Start stepping; the first step runs on the first tick. + function start() as Void { + var timer = new Timer.Timer(); + timer.start(method(:onTick), TICK_MS, true); + _timer = timer; + } + + // Stop without finishing (for example, when the user leaves the screen). + function cancel() as Void { + var timer = _timer; + if (timer != null) { + timer.stop(); + _timer = null; + } + } + + function onTick() as Void { + if (_job.step(_budget)) { + cancel(); + _onDone.invoke(); + } + } + } +} |
