diff options
| -rw-r--r-- | CHANGELOG.md | 2 | ||||
| -rwxr-xr-x | docker/scripts/app-diag.sh | 2 | ||||
| -rw-r--r-- | docs/PERFORMANCE.md | 1 | ||||
| -rw-r--r-- | source/import/OtpAuth.mc | 29 | ||||
| -rw-r--r-- | source/util/StressTest.mc | 27 |
5 files changed, 49 insertions, 12 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 42571b4..43fd4af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,6 +38,8 @@ All notable changes to this project are documented here. The format follows changes shown; later ones keep waiting. - With storage full, every correct PIN stopped the app while promoting pending entries; it now reports "Storage full" and continues. +- Pasting about 100 `otpauth://` links tripped the watchdog: splitting the + text looped over every character in one event. ### Tests diff --git a/docker/scripts/app-diag.sh b/docker/scripts/app-diag.sh index 773fd51..f1e1fd5 100755 --- a/docker/scripts/app-diag.sh +++ b/docker/scripts/app-diag.sh @@ -24,7 +24,7 @@ ciq_require_key # The stress test takes a few minutes per device (about 3,500 job steps at # 50 ms on the 512 KB watches). WATCHDOG_TIMEOUT_S=1200 -STRESS_ITEMS=19 +STRESS_ITEMS=20 if [[ "${kind}" == watchdog ]]; then for device in "$@"; do diff --git a/docs/PERFORMANCE.md b/docs/PERFORMANCE.md index d261125..bf5d97b 100644 --- a/docs/PERFORMANCE.md +++ b/docs/PERFORMANCE.md @@ -57,6 +57,7 @@ What this means: unlocking stays at about 1 s because 2,000 iterations is also t | Replacing a code with 4 KB of contents, queued / direct | not tested | passes, 735 / 727 steps | | A 4 KB replacement queued by 0.9.0, read, kept at the PIN and confirmed | not tested | passes, 757 steps | | A 1,024-character secret replacement / 4,096 spaces as code contents | not tested | passes, 10 / 12 steps | +| Splitting 100 pasted `otpauth://` links (13,576 characters) | trips | passes, one event | Not covered yet: opening a 4 KB code (`toUtf8` in one event, about 100,000 bytecodes) and the Add OTP form's secret decoding without a length limit; both are near the Forerunner 255S's limit, not the 70's. diff --git a/source/import/OtpAuth.mc b/source/import/OtpAuth.mc index cdf0f9c..f5c487b 100644 --- a/source/import/OtpAuth.mc +++ b/source/import/OtpAuth.mc @@ -21,28 +21,35 @@ module OtpAuth { const MIGRATION_PREFIX = "otpauth-migration://"; // The URIs in text, split at every "otpauth://". Text before the first - // one is ignored here; the importer reports it. + // one is ignored here; the importer reports it. Runs in one event, so + // it uses only native searches: a VM loop over every character of 100 + // pasted links tripped the watchdog. function splitUris(text as String) as Array<String> { var uris = [] as Array<String>; - var at = Text.findIgnoreCase(text, PREFIX, 0); - while (at >= 0) { - var next = Text.findIgnoreCase(text, PREFIX, at + PREFIX.length()); - var end = next >= 0 ? next : text.length(); - uris.add(firstWord(text.substring(at, end) as String)); + var lower = text.toLower(); + var at = lower.find(PREFIX); + while (at != null) { + var from = at + PREFIX.length(); + var found = (lower.substring(from, lower.length()) as String).find(PREFIX); + var next = found != null ? from + found : null; + uris.add(firstWord(text.substring(at, next != null ? next : text.length()) as String)); at = next; } return uris; } // Text up to the first whitespace (a pasted URI never contains any). + // Native searches, no loop over the characters (see splitUris). function firstWord(text as String) as String { - var chars = text.toCharArray(); - for (var i = 0; i < chars.size(); i++) { - if (Text.isSpace(chars[i])) { - return text.substring(0, i) as String; + var end = text.length(); + var spaces = [" ", "\t", "\n", "\r"]; + for (var i = 0; i < spaces.size(); i++) { + var found = text.find(spaces[i]); + if (found != null && found < end) { + end = found; } } - return text; + return end == text.length() ? text : text.substring(0, end) as String; } // One URI as an import item (entry, problem or skip). diff --git a/source/util/StressTest.mc b/source/util/StressTest.mc index a9d9293..8956c4b 100644 --- a/source/util/StressTest.mc +++ b/source/util/StressTest.mc @@ -130,6 +130,10 @@ module StressTest { // queued (ManageRun), then confirmed (ChangeQueueJob). // * SPACES: an unlocked code entry and a REPLACE of `material` spaces // (refused: "Contents: missing"). + // * URIS: no vault. Setup builds 100 pasted otpauth:// links (about 150 + // characters each) in steps of ten; the measured run is the + // Importer's text work in one event, done twice: OtpAuth.splitUris + // and the detect step's Text.trim + Text.findIgnoreCase. class VaultCase extends Jobs.Job { static const UNLOCK = 0; static const COUNT = 1; @@ -139,6 +143,8 @@ module StressTest { static const LEGACY = 5; static const SECRET = 6; static const SPACES = 7; + static const URIS = 8; + static const LINKS = 100; // Unlocked entries, one queued item each (ChangeQueue.MAX_ITEMS). static const LOCKS = 16; static const PIN = "1234"; @@ -167,6 +173,8 @@ module StressTest { 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>>; + // URIS: the pasted text. + private var _text as String = ""; function initialize(label as String, type as Number, pendingCount as Number, materialBytes as Number) { Job.initialize(); @@ -198,6 +206,16 @@ module StressTest { // One bounded setup step; true when every vault is ready. private function setup(budget as Number) as Boolean { + if (kind == URIS) { + var secret = repeat("ABCDEFGH", 4); + for (var i = 0; i < 10; i++) { + var n = (_n + 1).toString(); + _text += "otpauth://totp/Issuer" + n + ":account" + n + "@example.com?secret=" + secret + + "&issuer=Issuer" + n + "&algorithm=SHA1&digits=6&period=30\n"; + _n++; + } + return _n >= LINKS; + } var v = _vaults.size() > 0 ? _vaults[_vaults.size() - 1] : null; if (_phase == 0) { v = new Vault(new RamBackend(), new TimeSource()); @@ -340,6 +358,14 @@ module StressTest { // The measured run. private function measure(budget as Number) as Boolean { + if (kind == URIS) { + for (var i = 0; i < MARGIN; i++) { + var trimmed = Text.trim(_text); + var first = Text.findIgnoreCase(trimmed, OtpAuth.MIGRATION_PREFIX, 0) + Text.findIgnoreCase(trimmed, OtpAuth.PREFIX, 0); + result = " uris=" + OtpAuth.splitUris(trimmed).size() + " chars=" + _text.length() + " first=" + first; + } + return true; + } if (kind == COUNT) { for (var i = 0; i < MARGIN; i++) { ChangeQueue.changed(); @@ -508,6 +534,7 @@ module StressTest { 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), + new VaultCase("uris_100", VaultCase.URIS, 0, 0), // 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>; |
