// Author: cirakg & claude ai // Summary: Runs the seeded fuzz corpus through the tokenizer, the Aegis reader and the importer: always a result or a typed error, never a crash. // // README 17.3. Each input must end in a definite state within a bounded // number of steps (no endless loop), and anything the importer stores must // be a valid entry. Watchdog limits aren't enforced in unit tests; the // per-step bounds are covered by the UI flow on the Forerunner 255S. import Toybox.Lang; import Toybox.Test; (:test) function fuzzTokenizerAlwaysTerminates(logger as Test.Logger) as Boolean { var inputs = FuzzCorpus.JSON; for (var i = 0; i < inputs.size(); i++) { var data = Bytes.fromHex(inputs[i] as String); var tok = new Json.Tokenizer(data); var finished = false; // Every token consumes at least one byte, so size + 2 calls suffice. for (var n = 0; n < data.size() + 2; n++) { var t = tok.next(); if (t == Json.T_END || t == Json.T_INCOMPLETE || t == Json.T_ERROR) { finished = true; break; } } if (!finished) { logger.error("tokenizer didn't finish on input " + i); return false; } } return true; } (:test) function fuzzAegisReaderGivesDefiniteResults(logger as Test.Logger) as Boolean { var inputs = FuzzCorpus.JSON; var done = 0; for (var i = 0; i < inputs.size(); i++) { var data = Bytes.fromHex(inputs[i] as String); var reader = new AegisReader(data); var status = AegisReader.RUNNING; for (var n = 0; n < data.size() + 2 && status == AegisReader.RUNNING; n++) { status = reader.step(); } if (status == AegisReader.RUNNING || (status == AegisReader.FAILED && reader.error == null)) { logger.error("input " + i + ": status " + status); return false; } if (status == AegisReader.DONE) { done++; for (var k = 0; k < reader.items.size(); k++) { var item = reader.items[k]; var e = item.entry; if (e == null ? item.problem == null && item.skipped == null : e.validate() != null) { logger.error("input " + i + " item " + k + " is neither valid nor explained"); return false; } } } } logger.debug("FUZZ aegis inputs=" + inputs.size() + " complete=" + done); return done > 0; } // The whole importer on every input that is valid text: a report that // explains everything, and only valid entries in the vault. (:test) function fuzzImporterNeverStoresInvalidEntries(logger as Test.Logger) as Boolean { var all = [] as Array; all.addAll(FuzzCorpus.JSON as Array); all.addAll(FuzzCorpus.URIS as Array); var imported = 0; for (var i = 0; i < all.size(); i++) { var text = Bytes.toUtf8(Bytes.fromHex(all[i])); if (text == null) { continue; } var vault = new Vault(new MemoryBackend(), new FakeTime()); var job = new Importer(vault, text, Importer.MODE_APPEND, false, 1800000000l); var finished = false; for (var n = 0; n < 5000; n++) { if (job.step(1)) { finished = true; break; } } var r = job.report; if (!finished || (r.failure == null && r.waitingChars < 0 && r.summary().find("Imported") != 0)) { logger.error("input " + i + " didn't finish cleanly: " + r.summary()); return false; } var ids = vault.ids(); for (var k = 0; k < ids.size(); k++) { var e = vault.open(ids[k]); var problem = e.validate(); e.wipeSecrets(); if (problem != null) { logger.error("input " + i + " stored an invalid entry: " + problem); return false; } } imported += ids.size(); } logger.debug("FUZZ importer inputs=" + all.size() + " entries_stored=" + imported); return imported > 0; }