aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile5
-rw-r--r--test/data/FuzzCorpus.mc115
-rw-r--r--test/golden/fenix7/about.pngbin3828 -> 4037 bytes
-rw-r--r--test/golden/fenix7/import_results.pngbin0 -> 4462 bytes
-rw-r--r--test/golden/fr255s/about.pngbin2532 -> 2385 bytes
-rw-r--r--test/golden/fr255s/import_results.pngbin0 -> 2864 bytes
-rw-r--r--test/golden/fr70/about.pngbin15633 -> 15606 bytes
-rw-r--r--test/golden/fr70/import_results.pngbin0 -> 13389 bytes
-rw-r--r--test/golden/fr965/about.pngbin17081 -> 15984 bytes
-rw-r--r--test/golden/fr965/import_results.pngbin0 -> 15853 bytes
-rw-r--r--test/golden/instinct3amoled45mm/about.pngbin13211 -> 13651 bytes
-rw-r--r--test/golden/instinct3amoled45mm/import_results.pngbin0 -> 14894 bytes
-rw-r--r--test/golden/venusq2/import_results.pngbin0 -> 4825 bytes
-rw-r--r--test/golden/venux1/import_results.pngbin0 -> 21995 bytes
-rw-r--r--test/import/AegisTest.mc128
-rw-r--r--test/import/FuzzTest.mc107
-rw-r--r--test/import/ImportSupport.mc76
-rw-r--r--test/import/ImporterTest.mc208
-rw-r--r--test/import/JsonTest.mc128
-rw-r--r--test/import/OtpAuthTest.mc99
-rw-r--r--tools/gen_fuzz_corpus.py114
-rw-r--r--tools/tests/test_gen_fuzz_corpus.py41
-rw-r--r--tools/ui_flow.py34
23 files changed, 1050 insertions, 5 deletions
diff --git a/Makefile b/Makefile
index b66408d..1f2fdba 100644
--- a/Makefile
+++ b/Makefile
@@ -72,7 +72,7 @@ DOCS_RUN = $(DOCKER) run --rm --init --label project=$(PROJECT) \
--read-only --tmpfs /tmp:rw,exec,nosuid,size=1g --shm-size 512m \
-v "$(ROOT)":/work -w /work --network none $(DOCS_IMAGE)
-.PHONY: help image docs-image sdk key build test test-tools sim screenshots screenshots-approve perf memtest icons vectors spike spike-sim \
+.PHONY: help image docs-image sdk key build test test-tools sim screenshots screenshots-approve perf memtest icons vectors corpus spike spike-sim \
lint package docs ci shell clean nuke lock products \
check-docker dirs need-image need-docs-image
@@ -136,6 +136,9 @@ products: dirs need-image ## Check manifest products against the downloaded devi
vectors: dirs need-image ## Regenerate test/data/Vectors.mc (cross-checked RFC/NIST test vectors)
$(DEV_OFFLINE) python3 tools/gen_vectors.py
+corpus: dirs need-image ## Regenerate test/data/FuzzCorpus.mc (seeded fuzz inputs for the import parsers)
+ $(DEV_OFFLINE) python3 tools/gen_fuzz_corpus.py
+
spike: dirs need-image ## Build the M1 settings spike (debug .prg + beta .iq) into build/spike
$(DEV_OFFLINE) build-spike.sh build
diff --git a/test/data/FuzzCorpus.mc b/test/data/FuzzCorpus.mc
new file mode 100644
index 0000000..57bdf8b
--- /dev/null
+++ b/test/data/FuzzCorpus.mc
@@ -0,0 +1,115 @@
+// Author: cirakg & claude ai
+// Summary: Seeded fuzz inputs for the import parsers (generated by tools/gen_fuzz_corpus.py; do not edit).
+//
+// Seed 4242. Hex because many inputs are not valid UTF-8. Fake data only.
+
+import Toybox.Lang;
+
+(:test)
+module FuzzCorpus {
+ const JSON = [
+ "7b2276657273696f6e223a20312c2022686561646572223a207b22736c6f7473223a206e756c6c2c2022706172616d73223a206e756c6c7d2c20226462223a207b2276657273696f6e223a20332c2022656e7472696573223a205b7b2274797065223a2022746f7470222c202275756964223a20227531222c20226e616d65223a2022616c696365222c2022697373756572223a2022476974487562222c20226e6f7465223a2022222c20226661766f72697465223a20747275652c202269636f6e223a206e756c6c2c2022696e666f223a207b22736563726574223a202247455a44474e425647593354514f4a5147455a44474e425647593354514f4a51222c2022616c676f223a202253484131222c2022646967697473223a20362c2022706572696f64223a2033307d2c202267726f757073223a205b226731225d7d2c207b2274797065223a2022686f7470222c20226e616d65223a2022625c753030653962222c2022697373756572223a202256504e222c2022696e666f223a207b22736563726574223a202247455a44474e425647593354514f4a5147455a44474e425647593354514f4a51222c2022616c676f223a2022534841323536222c2022646967697473223a20382c2022706572696f64223a2033302c2022636f756e746572223a20377d7d5d2c202267726f757073223a205b7b2275756964223a20226731222c20226e616d65223a2022576f726b227d5d7d7d", // lint: allow-literal
+ "7b2276657273696f6e223a20312c2022686561646572223a207b22736c756c6c2c2022706172616d73223a206e756c6c7d2c20226462223a207b2276657273696f6e223a20332c2022656e7472696573223a205b7b2274797065223a2022746f7470222c202275756964223a20227531222c20226e616d65223a2022616c696365222c2022697373756572223a2022476974487562222c20226e6f7465223a2022222c20226661766f72697465223a20747275652c202269636f6e223a206e756c6c2c2022696e666f223a207b22736563726574223a202247455a44474e425647593354514f4a5147455a44474e425647593354514f4a51222c2022616c676f223a202253484131222c2022646967697473223a20362c2022706572696f64223a2033307d2c202267726f757073223a205b226731225d7d2c207b2274797065223a2022686f74705c75222c20226e616d65223a2022625c753030653962222c2022697373756572223a202256504e222c2022696e666f223a207b22736563726574223a202247455a44474e425647593354514f4a5147455a44474e425647593354514f4a51222c2022616c676f223a2022534841323536222c2022646967697473223a20382c2022706572696f64223a2033302c2022636f756e746572223a20377d7d5d2c202267726f757073223a205b7b2275756964223a20226731222c20226e616d65223a2022576f726b227d5d7d7d", // lint: allow-literal
+ "7b2276657273696f6e223a20312c2022686561646572223aa07b22736c6f7473223a206e756c6c2c2022706172616d73223a206e756c6c7d2c20226462223a207b2276657273696f6e223a20332c2022656e7472696573223a205b7b2274797065223a2022746f7470222c202275756964223a20227531222c20226e616d65223a2022616c696365222c2022697373756572223a2022476974487562222c20226e6f7465223a2022222c20226661766f72697465223a20747275652c202269636f6e223a206e756c6c2c2022696e666f223a207b22736563726574223a202247455a44474e425647593354514f4a5147455a44474e425647593354514f4a51222c2022616c676f223a202253484131222c2022646967697473223a20362c2022706572696f64223a2033307d2c202267726f757073223a205b226731225d7d2c207b2274797065223a2022686f7470222c20226e616d65223a2022625c753030653962222c2022697373756572223a202256504e222c2022696e666f223a207b22736563726574223a202247455a44474e425647593354514f4a5147455a44474e425647593354514f4a51222c2022616c676f223a2022534841323536222c2022646967697473223a20382c2022706572696f64223a2033302c2022636f756e746572223a20377d7d5d2c202267726f757073223a205b7b2275756964223a20226731222c20226e616d65223a2022576f726b227d5d7d7d", // lint: allow-literal
+ "7b2276657273696f6e223a20312c2022686561646572223a207b22736c6f7473223a206e756c6c2c2022706172616d73223a206e756c6c7d2c20226462223a207b2276657273696f6e223a20332c2022656e7472696573223a205b7b2274797065223a2022746f7470222c202275756964223a20227531222c20226e616d65223a2022616c696365222c2022697373756572223a2022476974487562222c20226e6f7465223a2022222c20226661766f72697465223a20747275652c202269636f6e223a206e756c6c2c2022696e666f223a207b22736563726574223a202247455a44474e425647593354514f4a5147455a44474e425647593354514f4a51222c2022616c676f223a202253484131222c2022646967697473223a20362c2022706572696f64223a2033307d2c202267726f757073223a205b226731225d7d2c207b2274797065223a2022686f7470222c20226e616d65223a2022625c753030653962222c2022697373756572223a202256504e222c2022696e666f223a207b22736563726574223a202247455a44474e425647593354514f4a5147455a44474e425647593354514f4a51222c2022616c676f223a2022534841323536222c2022646967697473223a20382c2022706572696f64223a2033302c2022636f756e746572223a20375d2c202267726f757073223a205b7b2275756964223a20226731222c20226e616d65223a2022576f726b227d5d7d7d", // lint: allow-literal
+ "7b2276657273696f6e223a20312c2022686561646572223a207b22736c6f7473223a206e756c6c2c2022706172616d73223a206e756c6c7d2c20226462223a207b2276657273696f6e223a20332c2022656e7472696573223a205b7b2274797065223a2022746f7470222c202275756964223a20227531222c20226e616d65223a2022616c696365222c2022697373756572223a2022476974487562222c20226e6f7465223a2022222c20226661766f72697465223a20747275652c202269636f6e223a206e756c6c2c2022696e666f223a207b22736563726574223a202247455a44474e425647593354514f4a5147455a44474e425647593354514f4a51222c2022616c676f223a202253484131222c2022646967697473223a20362c2022706572696f64223a2033307d2c202267726f757073223a205b226731225d7d2c207b2274797065223a2022686f7470222c20226e616d65223a2022625c753030653962222c2022697373756572223a202256504e222c2022696e666f223a207b22736563726574223a202247455a44474e425647593354514f4a5147455a44474e425647593354514f4a51222c2022616c676f223a2022534841323536222c2022646967697473223a20382c2022706572696f64223a2033302c2022636f756e746572223a20377d7d5d2c202267726f757073223a205b7b2275756964223a20226731222c616d65223a2022576f726b227d5d7d7d", // lint: allow-literal
+ "7b2276657273696f6e223a20312c2022686561646572223a207b22736c6f7473223a206e756c6c2c2022706172616d73223a206e756c6c7d2c20226462223a207b2276657273696f6e223a20332c2022656e7472696573223a205b7b2274797065223a2022746f7470222c202275756964223a20227531222c20226e7977616d65223a2022616c696365222c2022697373756572223a2022476974487562222c20226e6f7465223a2022222c20226661766f72697465223a20747275652c202269636f6e223a206e756c6c2c2022696e666f223a207b22736563726574223a202247455a44474e425647593354514f4a5147455a44474e425647593354514f4a51222c2022616c676f223a202253484131222c2022646967697473223a20362c2022706572696f64223a2033307d2c202267726f757073223a205b226731225d7d2c207b2274797065223a2022686f7470222c20226e616d65223a2022625c753030653962222c2022697373756572223a202256504e222c2022696e666f223a207b22736563726574223a202247455a44474e425647593354514f4a5147455a44474e425647593354514f4a51222c2022616c676f223a2022534841323536222c2022646967697473223a20382c2022706572696f64223a2033302c2022636f756e746572223a20377d7d5d2c202267726f757073223a205b7b2275756964223a20226731222c20226e616d65223a2022576f726b227d5d7d7d", // lint: allow-literal
+ "7b2256acb676657273696f6e223a20312c2022686561646572223a207b22736c6f7473223a206e756c6c2c2022706172616d73223a206e756c6c7d2c20226462223a207b2276657273696f6e223a20332c2022656e7472696573223a205b7b2274797065223a2022746f7470222c202275756964223a20227531222c20226e616d65223a2022616c696365222c2022697373756572223a2022476974487562222c20226e6f7465223a2022222c20226661766f72697465223a20747275652c202269636f6e223a206e756c6c2c2022696e666f223a207b22736563726574223a202247455a44474e425647593354514f4a5147455a44474e425647593354514f4a51222c2022616c676f223a202253484131222c20226469676974", // lint: allow-literal
+ "7b2276657273696f6e223a20312c2022686561646572223a207b22736c6f7473223a206e756c6c2c2022706172616d73223a206e756c6c7d2c20226462223a207b2276657273696f6e223a20332c2022656e7472696573223a205b7b2274797065223a2022746f7470222c202275756964223a20227531222c20226e616d65223a2022616c696365222c2022697373756572223a2022476974487562222c20226e6f7465223a2022222c20226661766f72697465223a20747275652c202269636f6e223a206e756c6c2c2022696e666f223a207b22736563726574223a202247455a44474e425647593354514f4a5147455a44474e425647593354514f4a51222c2022616c676f223a202253484131222c2022646967697473223a20362c2022706572696f64223a2033307d2c202267726f757073223a205b226731225d7d2c207b2274797065223a2022686f7470222c20226e6731225d7d2c207b2274797070222c20226e616d65223a2022625c753030653962222c2022697373756572223a202256504e222c2022696e666f223a207b22736563726574223a202247455a44474e425647593354514f4a5147456e756c6c5a44474e425647593354514f4a51222c2022616c676f223a2022534841323536222c2022646967697473223a20382c2022706572696f64223a2033302c2022636f756e746572223a20377d7d5d2c202267726f757073223a205b7b2275756964223a20226731222c20226e616d65223a2022576f726b227d5d7d7d", // lint: allow-literal
+ "7b2276657273696f6e223a20312c2022686561646572223a207b22736c6f7473223a206e756c6c2c2022706172616d73223a206e756c6c7d2c20226462223a207b2276657273696f6e223a20332c20223d656e7472696573223a205b7b2274797065223a2022746f7470222c202275756964223a20227531222c20226e616d65223a2022616c696365222c2022697373756572223a2022476974487562222c20226e6f7465223a2022222c90f620226661766f72697465223a20747275652c202269636f6e223a206e756c6c2c2022696e666f223a207b22736563726574223a202247455a44474e425647593354514f4a5147455a44474e425647593354514f4a51222c2022616c676f223a202253484131222c2022646967697473223a20362c2022706572696f64223a2033307d2c202267726f757073223a205b226731225d7d2c207b2274797065223a2022686f7470222c20226e616d65223a2022625c753030653962222c2022697373756572223a202256504e222c2022696e666f223a207b22736563726574223a202247455a44474e425647593354514f4a5147455a44474e425647593354514f4a51222c2022616c676f223a2022534841323536222c2022646967697473223a20382c2022706572696f64223a2033302c2022636f756e746572223a20377d7d5d2c202267726f757073223a205b7b2275756964223a20226731222c20226e616d65223a2022576f726b227d5d7d7d", // lint: allow-literal
+ "7b2276657273696f6e223a20312c2022686561646572223a207b22736c6f7473223a206e756c6c2c2022706172616d73223a206e756c6c7d2c20226462223a207b2276657273696f6e223a223a206e756c6c7d2c20226462223a207b2276657273696f6e223a20332c2022656e7472696573223a205b7b2274797065223a2022746f", // lint: allow-literal
+ "7b2276657273696f6e223a20312c2022686561646572223a207b22736c6f7473223a206e756c6c2c2022706172616d73223a206e756c6c7d2c20226462223a207b2276657273696f6e223a20332c2022656e7472696573223a205b7b2274797065223a2022746f7470222c202275756964223a20227531222c20226e616d65223a2022616c696365222c2022697373756572223a2022476974487562222c20226e6f7465223a2022222c20226661766f72697465223a20747275652c202269636f6e223a206e756c6c2c2022696e666f223a207b22736563726574223a202247455a44474e425647593354514f4a5147455a44474e425647593354514f4a51222c2022616c676f223a202253484131222c2022646967697473223a20362c2022706572696f64223a2033307d2c202267726f757073223a205b226731225d7d2c207b2274797065223a2022686f7470222c20226e616d65223a2022625c753030653962222c2022697373756572223a202256504e222c2022696e666f223a207b22736563726574223a202247455a44474e425647593354514f4a5147455a44474e425647593354514f4a51222c2022616c676f223a2022534841323536222c2022646967697473223a20382c2022706572696f64223a2033302c2022636f756e746572223a20377d7d5d2c20226f756e746572223a20377d7d5d726f757073223a205b7b2275756964223a20226731222c20226e616d65223a2022576f726b227d5d7d7d", // lint: allow-literal
+ "7b2276657273696f6e223a20312c2022686561646572223a207b22736c6f7473223a206e756c6c2c2022706172616d73223a206e756c6c7d2c20226462223a207b2276657273696f6e223a20332c2022656e7472696573223a205b7b2274797065223a2022746f7470222c202275756964223a20227531222c20226e616d65223a2022616c696365222c2022697373756572223a2022476974487562222c20226e6f7465223a2022222c20226661766f72697465223a20747275652c202269636f6e223a206e756c6c2c2022696e666f223a207b22736563726574223a202247455a44474e425647593354514f4a5147455a44474e425647593354514f4a51222c2022616c676f223a202253484131222c2022646967697473223a20362c2022706572696f64223a2033307d2c202267726f757073223a205b226731225d7d2c207b2274797065223a2022686f7470222c20226e616d65223a2022625c753030653962222c2022697373756572223a202256504e222c2022696e666f223a207b22736563726574223a202247455a44474e425647593354514f4a5147455a44474e425647593354510f4a51222c2022616c676f223a2022534841323536222c2022646967697473223a20382c2022706572696f64223a2033302c2022636f756e746572223a20377d7d5d2c202267726f757073223a205b7b2275756964223a20226731222c20226e616d65223a2022576f726b227d5d7d7d", // lint: allow-literal
+ "7b2276657273696f6e223a20312c2022686561646572223a207b22736c6f7473223a206e756c6c2c2022706172616d73223a206e756c6c7d2c20226462223a207b2276657273696f6e223a20332c2022656e7472696573223a205b7b2274797065223a2022746f7470222c202275756964223a20227531222c20226e616d65223a2022616c696365222c2022697373756572223a2022476974487562222c20226e6f7465223a2022222c20226661766f72697465223a20747275652c202269636f6e223a206e756c6c2c2022696e666f223a207b22736563726574223a202247455a44474e425647593354514f4a5147455a44474e425647593354514f4a2241414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141412251222c2022616c676f223a202253484131222c2022646967697473223a20362c2022706572696f64223a2033307d2c202267726f757073223a205b226731225d7d2c207b2274797065223a2022686f7470222c20226e616d65223a2022625c753030653962222c2022697373756572223a202256504e222c2022696e666f223a207b22736563726574223a202247455a44474e425647593354514f4a5147455a44474e425647593354514f4a51222c2022616c676f223a2022534841323536222c2022646967697473223a20382c2022706572696f64223a2033302c2022636f756e746572223a20377d7d5d2c202267726f757073223a205b7b2275756964223a20226731222c20226e616d65223a2022576f726b227d5d7d7d", // lint: allow-literal
+ "7b2276657273696f6e223a20312c2022686561646572223a", // lint: allow-literal
+ "7b2276657273696f6e223a20312c2022686561646572223a207b22736c6f7473223a206e756c6c2c2022706172616d73223a206e756c6c7d2c20226462223a207b2276657273696f6e223a20332c2022656e7472696573223a205b7b2274797065223a2022746f7470222c202275756964223a20227531222c20226e616d65223a2022616c696365222c2022697373756572223a2022476974487562222c20226e6f7465223a2022222c20226661766f72697465223a20747275652c202269636f6e223a206e756c6c2c2022696e666f222a207b22736563726574223a202247455a44474e425647593354514f4a5147455a44474e425647593354514f4a51222c2022616c676f223a202253484131222c2022646967697473223a20362c2022706572696f64223a2033307d2c202267726f757073223a205b226731225d7d2c207b2274797065223a2022686f7470222c20226e616d65223a2022625c753030653962222c2022697373756572223a202256504e222c2022696e666f223a207b22736563726574223a202247455a44474e425647593354514f4a5147455a44474e425647593354514f4a51222c20226122534841323536222c2022646967697422534841323536222c2022646967697473223a20382c2022706572696f64223a2033302c2022636f756e746572223a20377d7d5d2c202267726f757073223a205b7b2275756964223a20226731222c20226e616d65223a2022576f726b227d5d7d7d", // lint: allow-literal
+ "7b2276657273696f6e223a20312c2022686561646572223a207b22736c6f7473223a206e756c6c2c2022706172616d73223a206e756c6c7d2c20226462223a207b2276657273696f6e223a20332c2022656e7472696573223a205b7b2274797065223a2022746f7470222c202275756964223a20227531222c20226e616d65223a2022616c696365222c2022697373756572223a2022476974487562222c20226e6f7465223a2022222c20226661766f72697465223a20747275652c202269636f6e223a206e756c6c2c2022696e666f223a207b22736563726574223a202247455a44474e425647593354514f4a5147455a44474e425647593354514f4a51222c2022616c676f223a202253484131222c2022646967697473223a20362c2022706572696f64223a2033307d2c202267726f757073223a205b226731225d7d2c207b2274797065223a2022686f7470222c20226e616d65223a2022625c753030653962222c2022697373756572223a202256504e222c2022696e666f223a207b22736563726574223a202247455a44474e425647593354514f4a5147455a44474e425647593354514f4a51222c202261676f223a2022534841323536222c2022646967697473223a20382c2022706572696f64223a2033302c2022636f756e746572223a20377d7d5d2c202267726f757073223a205b7b2275756964223a20226731222c20226e616d65223a2022576f726b227d5d7d7d", // lint: allow-literal
+ "7b2276657273696f6e223a20312c2022686561646572223a207b22736c6f7473223a206e756c6c2c2022706172616d73223a206e756c6c7d2c20226462223a207b2276657273696f6e223a20332c2022656e7472696573223a205b7b2274797065223a2022746f7470222c202275756964223d3a20227531222c20226e616d65223a2022616c696365222c2022697373756572223a2022476974487562222c20226e6f7465223a2022222c20226661766f72697465223a20747275652c202269636f6e223a206e756c6c2c2022696e666f223a207b22736563726574223a202247455a5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b44474e425647593354514f4a5147455a44474e425647593354514f4a51222c2022616c676f223a202253484131222c2022646967697473223a20362c2022706572696f64223a2033307d2c202267726f757073223a205b226731225d7d2c207b2274797065223a2022686f7470222c20226e616d65223a2022625c753030653962222c2022697373756572223a202256504e222c2022696e666f223a207b22736563726574223a202247455a44474e425647593354514f4a5147455a44474e425647593354514f4a51222c2022616c676f223a2022534841323536222c2022646967697473223a20382c2022706572696f64223a2033302c2022636f756e746572223a20377d7d5d2c202267726f757073223a205b7b2275756964223a20226731222c20226e616d6522", // lint: allow-literal
+ "7b22766572224141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141412273696f6e223a20312c2022686561646572223a207b", // lint: allow-literal
+ "7b2276657273696f6e223a20312c2022686561646572223a207b22736c6f7473223a206e756c6c2c2022706172616d73223a206e756c6c7d2c20226462223a207b2276657273696f6e223a20332c2022656e7472696573223a205b7b2274797065223a2022746f7470222c202275756964223a20227531222c20226e616d65223a2022616c696365222c2022697373756572223a2022476974487562222c20226e6f7465223a2022222c20226661766f72697465223a20747275652c202269636f6e223a206e756c6c2c2022696e666f223a207b22736563726574223a202247455a444754514f4a5147455a44474e425647593354514f4a51222c2022616c676f223a202253484131222c2022646967697473223a20362c2022706572696f64223a2033307d2c202267726f757073223a205b226731225d7d2c207b2274797065223a2022686f7470222c20226e616d65223a2022625c753030653962222c2022697373756572223a202256504e222c2022696e666f223a207b22736563726574223a202247455a44474e425647593354514f4a5147455a44474e425647593354514f4a51222c2022616c676f223a2022534841323536222c2022646967697473223a20382c2022706572696f64223a2033302c2022636f756e746572223a20377d7d5d2c202267726f757073223a205b7b2275756964223a20226731222c20226e616d65223a2022576f726b227d5d7d7d", // lint: allow-literal
+ "7b2276657273696f6e223a20312c20225c7564383364686561646572223a207b22736c6f7473223a206e756c6c2c2022706172616d73223a206e756c6c7d2c20226462223a207b2276657273696f6e223a20332c2022656e7472696573223a205b7b2274797065223a2022746f7470222c202275756964223a20227531222c20226e616d65223a2022616c696365222c202269737375655b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b72223a2022476974487562222c20226e6f7465223a2022222c20226661766f72697465223a20747275652c202269636f6e223a206e756c6c2c2022696e666f223a207b22736563726574223a202247455a44474e425647593354514f4a5147455a44474e425647593354514f4a51222c2022616c676f223a202253484131222c2022646967697473223a20362c2022706572696f64223a2033307d2c202267726f757073223a205b226731225d7d2c207b2274797065223a2022686f7470222c20226e616d65223a2022625c753030653962222c2022697373756572223a202256504e222c2022696e666f223a207b22736563726574223a202247455a44474e425647593354514f4a5147455a44474e425647593354514f4a51222c2022616c676f223a2022534841323536222c2022646967697473223a20382c2022706572696f64223a2033302c2022636f756e746572223a20377d7d5d2c202267726f757073223a205b7b2275756964223a20226731222c20226e616d65223a2022576f726b227d5d7d7d", // lint: allow-literal
+ "7b2276657273696f6e223a20312c2022686561646572223a207b22736c6f7473223a206e756c6c2c2022706172616d73223a206e756c6c7d2c20226462223a207b2276657273696f6e223a20332c2022656e7472696573223a205b7b2274797065223a2022746f7470222c202275756964223a20227531222c20226e616d65223a2022616c696365222c2022697373756572223a2022476974487562222c20226e6f7465223a2022222c20226661766f72697465223a20747275652c20225b5b5b5b5b5b5b5b5b5b5b5b69636f6e223a206e756c6c2c2022696e666f223a207b22736563726574223a202247455a44474e425647593354514f4a5147455a44474e425647593354514f4a51222c2022616c676f223a202253484131222c2022646967697473223a20362c2022706572696f64223a2033307d2c202267726f757073223a205b226731225d7d2c207b2274797065223a2022686f7470222c20226e616d65223a2022625c753030653962222c202269756572223a202256504e222c2022696e666f223a207b22736563726574223a202247455a44474e425647593354514f4a5147455a44474e425647593354514f4a51222c2022616c676f223a2022534841323536222c2022646967697473223a20382c2022706572696f64223a2033302c2022636f756e746572223a20377d7d5d2c202267726f757073223a205b7b2275756964223a20226731222c20226e616d65223a2022576f726b227d5d7d7d", // lint: allow-literal
+ "7b2276657273696f6e223a20312c2022686561646572223a207b22736c6f7473223a206e756c6c2c2022706172616d73223a206e756c6c7d2c20226462223a207b2276657273696f6e223a20332c2022656e7472696573223a205b7b2274797065223a2022746f7470222c202275756964223a20227531222c20226e616d65223a2022616c696365222c2022697373756572223a2022476974487562222c20226e6f7465223a2022222c20226661766f72697465223a20747275652c202269636f6e223a206e756c6c2c2022696e666f223a207b22736563726574223a202247455a44474e425647593354514f4a5147455a44474e425647593354514f4a51222c2022616c676f223a202253484131222c2022646967697473223a20362c2022706572696f64223a2033307d2c202267726f757073223a205b226731225d7d2c207b2274797065223a2022686f7470222c20226e616d65223a2022625c753030653962222c2022697373756572223a202256504e222c2022696e666f223a", // lint: allow-literal
+ "7b2276657273696f6e223a20312c2022686561646572223a207b22736c6f7473223a206e756c6c2c2022706172616d73223a206e756c6c7d2c20226462223a207b2276657273696f6e223a20332c2022656e7472696573223a205b7b2274797065223a2022746f7470222c202275756964223a20227531222c20226e616d65223a2022616c696365222c2022697373756572223a2022476974487562222c20226e6f7465223a2022222c20226661766f72697465223a20747275652c202269636f6e223a206e756c6c2c2022696e666f223a207b22736563726574223a202247455a44474e425647593354514f4a5147455a44474e425647593354514f4a51222c2022616c676f223a202253484131222c2022646967697473223a20362c2022706572696f64223a2033307d2c202267726f757073223a205b226731225d7d2c207b2274797065223a2022686f7470222c20226e616d65223a2022625c753030653962222c2022697373756572223a202256504e222c2022696e666f223a207b22736563726574223a202247455a44474e425647593354514f4a5147455a44474e425647593354514f4a51222c2022616c676f223a2022534841323536222c20", // lint: allow-literal
+ "7b2276657273696f6e223a20312c2022686561646572223a207b22736c6f7473223a206e756c6c2c2022706172616d73223a206e756c6c7d2c20226462223a207b2276657273696f6e223a20332c2022656e7472696573223a205b7b2274797065223a2022746f7470222c202275756964223a20227531222c20226e616d65223a2022616c696365222c2022697373756572223a2022476974487562222c20226e6f7465223a2022222c20226661766f72697465223a20747275652c202269636f6e223a206e756c6c2c2022696e666f223a207b22736563726574223a202247455a44474e425647593354514f4a5147455a44474e425647593354514f4a51222c2022616c676f223a202253484131222c2022646967697473223a20362c2022706572696f64223a2033307d2c202267726f757073223a205b226731225d7d2c207b2274797065223a2022686f7470222c20226e616d65223a2022625c753030653962222c2022697373756572223a202256504e222c2022696e666f223a207b22736563726574223a202247455a44474e425647593354514f4a5147455a44474e4257615647593354514f4a51222c2022616c676f223a2022534841323536222c2022646967697473223a20382c2022706572696f64223a2033302c2022636f756e746572223a20377d7d5d2c202267726f757073223a205b7b2275756964223a20226731222c20226e616d65223a2022576f726b227d5d7d7d", // lint: allow-literal
+ "7b2276657273696f6e223a20312c2022686561646572223a207b22736c6f7473223a206e756c6c2c2022706172616d73223a206e756c6c7d2c20226462223a207b2276657273696f6e223a20332c2022656e7472696573223a205b7b2274797065223a2022746f7470222c202275756964223a20227531222c20226e616d65223a2022616c696365222c2022697373756572223a2022476974487562222c20226e6f7465223a2022222c20226661766f72697465223a20747275652c202269636f6e223a206e756c6c2c2022696e666f223a207b22736563726574223a202247455a44474e425647593354514f4a5147455a44474e425647593354514f4a51222c2022616c676f223a202253484131222c2022646967697473223a20362c2022706572696f64223a2033307d2c202267726f757073223a205b226731225d7d2c207b2274797065223a2022686f7470222c20226e616d65223a2022625c753030653962222c2022697373756572223a202256504e222c2022696e666f223a207b22736563726574223a202247455a44474e425647593354514f4a5147415a44474e425647593354514f4a51222c2022616c676f223a2022534841323536222c2022646967697473223a20382c2022706572696f64223a2033302c2022636f756e746572223a20377d7d2241412241414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414122414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141225d2c202267726f757073223a205b7b2275756964223a20226731222c20226e616d65223a2022576f726b227d5d7d7d", // lint: allow-literal
+ "7b2276657273696f6e223a20312c2022686561646572223a207b22736c6f7473223a206e756c6c2c2022706172616d73223a206e756c6c7d2c202264622241414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141412241414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141224141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141415141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414122223a207b2276657273696f6e223a20332c2022656e7472696573223a205b7b2274797065223a2022746f7470222c202275756964223a20227531222c20226e616d65223a2022616c696365222c2022697373756572223a2022476974487562222c20226e6f7465223a2022222c20226661766f72697465223a20747275652c202269636f6e223a206e756c6c2c2022696e666f223a207b22736563726574223a202247455a44474e425647593354514f4a5147455a44474e425647593354514f4a51222c2022616c676f223a202253484131222c2022646967697473223a20362c2022706572696f64223a2033307d2c202267726f757073223a205b226731225d7d2c207b2274797065223a2022686f7470222c20226e616d65223a2022625c753030653962222c2022697373756572223a202256504e222c2022696e666f223a207b22736563726574223a202247455a44474e425647593354514f4a5147455a44474e425647593354514f4a51222c2022616c676f223a2022534841323536222c2022646967697473223a20382c2022706572696f64223a2033302c2022636f756e746572223a20377d7d5d2c202267726f757073223a205b7b2275756964223a20226731222c20226e616d65223a2022576f726b227d5d7d7d", // lint: allow-literal
+ "7b2276657273696f6e223a20312c2022686561646572223a207b22736c6f7473223a206e756c6c2c20226e756c6c706172616d73223a206e756c6c7d2c20226462223a207b2276657273696f6e223a20332c2022656e7472696573223a205b7b2274797065223a2022746f7470222c202275756964223a20227531222c20226e616d65223a205b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b22616c696365222c2022697373756572223a2022476974487562222c20226e6f7465223a2022222c20226661766f72697465223a20747275652c202269636f6e223a206e756c6c2c2022696e666f223a207b22736563726574223a202247455a44474e425647593354514f4a5147455a44474e425647593354514f4a51222c2022616c676f223a202253484131222c2022646967697473223a20362c2022706572696f64223a2033307d2c202267726f757073223a205b226731225d7d2c207b2274797065223a2022686f7470222c20226e616d65223a2022625c753030653962222c2022697373756572223a202256504e222c2022696e666f223a207b22736563726574223a202247455a44474e42564759333a54514f4a5147455a44474e425647593354514f4a51222c2022616c676f223a2022534841323536222c2022646967697473223a20382c2022706572696f64223a2033302c2022636f756e746572223a20377d7d5d2c202267726f757073223a205b7b2275756964223a20226731222c20226e616d65223a2022576f726b227d5d7d7d", // lint: allow-literal
+ "7b2276657273696f6e223a20312c2022686561646572223a207b22736c6f7473223a206e756c6c2c2022706172616d73223a206e756c6c7d2c20226462223a207b2276657273696f6e223a20332c2022656e7472696573223a205b7b2274797065223a2022746f7470225c75643833642c202275756964223a20227531222c20226e616d65223a2022616c696365222c2022697373756572223a2022476974487562222c20226e6f7465223a2022222c20226661766f72697465223a20747275652c202269636f6e223a206e756c6c2c2022696e666f223a207b22736563626574223a202247455a593354514f4a5147455a44474e425647593354514f4a51222c2022616c676f223a202253484131222c2022646967697473223a20362c2022706572696f64223a2033307d2c202267726f757073223a205b226731225d7d2c207b2274797065223a2022686f7470222c20226e616d65223a2022625c753030653962222c2022697373756572223a202256504e222c2022696e666f223a207b22736563726574223a202247455a44474e425647593354514f4a5147455a44474e425647593354514f4a51222c2022616c676f223a2022534841323536222c2022646967697473223a20382c2022706572696f64223a2033302c2022636f756e746572223a20377d7d5d2c202267726f757073223a205b7b2275756964223a20226731222c20226e616d65223a2022576f726b227d5d7d7d", // lint: allow-literal
+ "7b2276657273696f6e223a20312c2022686561646572223a207b22736c6f7473223a206e756c6c2c2022706172616d73223a206e756c6c7d2ce42a20226462223a207b2276657273696f6e223a20332c2022656e7472696573223a205b7b2274797065223a2022746f7470222c202275756964223a20227531222c20226e616d65223a2022616c696365222c2022697373756572223a2022476974487562222c20226e6f7465223a2022222c20226661766f72697465223a20747275652c202269636f6e223a206e756c6c2c2022696e666f223a207b22736563726574223a202247455a44474e425647593354514f4a5147455a44474e425647593354514f4a51222c2022616c676f223a202253484131222c2022646967697473223a20362c2022706572696f64223a2033307d2c202267726f757073223a205b226731225d7d2c207b2274797065797065223a2022686f7470222c20226e616d65223a2022625c753030653962222c2022697373756572223a202256504e222c2022696e666f223a207b22736563726574223a202247455a44474e425647593354514f4a5147455a44474e425647593354514f4a51222c2022616c676f22223a2022534841323536222c2022646967697473223a20382c2022706572696f64223a2033302c2022636f756e746572223a20377d7d5d2c202267726f757073223a205b7b2275756964223a20226731222c20226e616d65223a2022576f726b227d5d7d7d", // lint: allow-literal
+ "7b2276657273696f6e223a20312c2022686561646572223a207b22736c6f7473223a206e756c6c2c2022706172616d73223a206e756c6c7d2c20226462223a207b2276657273696f6e223a20332c2022656e7472696573223a205b7b2274797065223a2022746f7470222c2022753a20227531222c20226e616d65223a2022616c696365222c2022697373756572223a2022476974487562222c20226e6f7465223a2022222c20226661766f72697465223a20747275652c202269636f6e223a206e756c6c2c2022696e666f223a207b22736563726574223a202247455a44474e425647593354514f4a5147455a44474e425647593354514f4a51222c2022616c676f223a202253484131222c2022646967697473223a20362c2022706572696f64223a2033307d2c202267726f757073223a205b226731225d7d2c207b2274797065223a2022686f7470222c20226e616d65223a2022625c753030653962222c2022697373756572223a202256504e222c2022696e666f223a207b22736563726574223a202247455a44474e425647593354514f4a5147455a44474e425647593354514f4a51222c2022616c676f223a2022534841323536222c2022646967697473223a20382c2022706572696f64223a2033302c2022636f756e746572223a20377d7d5d2c202267726f757073223a205b7b2275756964223a20226731222c20226e616d65223a2022576f726b227d5d7d7d", // lint: allow-literal
+ "7b2276657220312c2022686561646572223a207b22736c6f7473223a206e756c6c2c2022706172616d73223a206e756c6c7d2c20226462223a207b2276657273696f6e223a20332c2022656e7472696573223a205b7b2274797065223a2022746f7470222c202275756964223a20227531222c20226e616d65223a2022616c696365222c2022697373756572223a2022476974487562222c20226e6f7465223a2022222c20226661766f72697465223a20747275652c202269636f6e223a206e756c6c2c2022696e666f223a207b22736563726574223a202247455a44474e425647593354514f4a5147455a44474e425647593354514f4a51222c2022616c676f223a202253484131222c2022646967697473223a20362c2022706572696f64223a2033307d2c202267726f757073223a205b226731225d7d2c207b2274797065223a2022686f7470222c20226e616d65223a2022625c753030653962222c2022697373756572223a202256504e222c2022696e666f223a207b22736563726574223a202247455a44474e425647593354514f4a5147455a44474e425647593354514f4a51222c2022616c676f223a2022534841323536222c2022646967697473223a20382c2022706572696f64223a2033302c2022636f756e746572223a20377d7d5d2c202267726f757073223a205b7b2275756964223a20226731222c20226e616d65223a2022576f726b227d5d7d7d", // lint: allow-literal
+ "7b2276657273696f6e223a20312c2022686561646572223a207b22736c6f7473223a206e756c6c2c2022706172616d73223a206e756c6c7d2c20226462223a207b2276657273696f6e223a20332c2022656e7472696573223a205b7b2274797065223a2022746f7470222c202275756964223a20227531222c20226e616d65223a2022616c696365222c2022697373756572223a2022476974487562222c20226e6f7465223a2022222c20226661766f72697465223a20747275652c202269636f6e223a206e756c6c2c2022696e666f223a207b22736563726574223a202247455a44474e425647593354514f4a5147455a44474e425647593354514f4a51222c2022616c676f223a202253485b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b4131222c2022646967697473223a20362c2022706572696f64223a2033307d2c202267726f757073223a205b226731225d7d2c207b2274797065223a2022686f7470222c20226e616d65223a2022625c753030653962222c2022697373756572223a202256504e222c2022696e666f223a207b22736563726574223a202247455a44474e425647593354514f4a5147455a44474e425647593354514f4a51222c2022616c676f223a2022534841323536222c2022646967697473223a20382c2022706572696f64223a2033302c2022636f756e746572223a20377d7d5d2c202267726f757073223a205b7b2275755b5b5b5b5b5b5b5b5b5b5b5b5b5b6964223a20226731222c20226e616d65223a2022576f726b227d5d7d7d7d5d7d7d", // lint: allow-literal
+ "7b2276657273696f6e223a20312c2022686561646572223a207b22736c6f7473223a206e756c6c2c2022706172616d73223a206e756c6c7d2c20226462223a207b2276657273696f6e223a20332c2022656e7472696573223a205a7b2274797065223a2022746f7470222c202275756964223a20227531222c20226e616d65223a2022616c696365222c2022697373756572223a202247695b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b74487562222c20226e6f7465223a2022222c20226661766f72697465223a20747275652c202269de9e047a636f6e223a206e756c6c2c2022696e666f223a207b22736563726574223a202247455a44474e425647593354514f4a5147455a44474e425647593354514f4a51222c2022616c676f223a202253484131222c2022646967697473223a20362c2022706572696f64223a2033307d2c202267726f757073223a205b226731225d7d2c207b2274797065223a2022686f7470222c20226e616d65223a2022625c753030653962222c2022697373756572223a202256504e222c2022696e666f223a207b22736563726574223a202247455a44474e425647593354514f4a5147455a44474e425647593354514f4a51222c2022616c676f223a2022534841323536222c2022646967697473223a20382c2022706572696f64223a2033302c2022636f756e746572223a20377d7d5d2c202267726f757073223a205b7b2275756964223a20226731222c20226e616d65223a2022576f726b227d5d7d7d", // lint: allow-literal
+ "7b2276657273696f6e223a20312c2022686561646572223a207b22736c6f7473223a206e756c6c2c2022706172616d73223a206e756c6c7d2c20226462223a207b2276657273696f6e223a20332c2022656e7472696573223a205b7b2274797065223a2022746f7470222c202275756964223a20227531222c20226e3d616d65223a2022616c696365222c2022697373756572223a2022476974487562222c20226e6f7465223a2022222c20226661766f72697465223a20747275652c202269636f6e223a206e756c6c2c2022696e666f223a207b22736563726574223a202247455a44474e425647593354514f4a5147455a44474e425647593354514f4a51222c2022616c676f223a202253484131222c2022646967697473223a20362c2022706572696f64223a2033307d2c202267726f757073223a205b226731225d7d2c207b2274797065223a2022686f7470222c20226e616d65223a2022625c753030653962222c2022697373756572223a202256504e222c2022696e666f223a207b22736563726574223a202247455a44474e425647593354514f4a5147455a44474e425647593354514f4a51222c2022616c676f223a2022534841323536222c2022646967697473223a3a20382c2022706572696f64223a2033302c2022636f756e746572223a20377d7d5d2c202267726f757073223a205b7b2275756964223a20226731222c20226e616d65223a2022576f726b227d5d7d7d", // lint: allow-literal
+ "7b2276657273696f6e223a20312c2022686561646572223a207b22736c6f7473223a206e756c6c2c2022706172616d73223a206e756c6c7d2c20226462223a207b2276657273696f6e223a20332c2022656e7472696573223a205b7b2274797065223a2022746f7470222c202275756964223a20227531222ce0e51b20226e616d65223a2022616c696365222c2022697373756572223a2022476974487562222c20226e6f7465223a2022222c20226661766f72697465223a20747275652c202269636f6e223a206e756c6c2c2022696e666f223a207b22736563726574223a202247455a44474e425647593354514f4a5147455a44474e425647593354514f4a5122", // lint: allow-literal
+ "7b2276657273696f6e223a20312c2022686561646572223a207b22736c6f7473223a206e756c6c2c2022706172616d73223a206e756c6c7d2c20226462223a207b2276657273696f6e223a20332c2022656e7472696573223a205b7b2274797065223a2022746f7470222c202275756964223a20227531222c20226e616d65223a2022616c696365222c2022692d897373756572223a2022476974487562222c20226e6f7465223a2022222c20226661766f72697465223a20747275652c202269636f6e223a206e756c6c2c2022696e666f223a207b22736563726574223a202247455a44474e425647593354514f4a5147455a44474e425647593354514f4a51222c2022616c676f223a202253484131222c2022646967697473223a20362c2022706572696f64223a2033307d2c202267726f757073223a205b226731225d7d2c207b2274797065223a2022686f7470222c20226e616d65223a2022625c753030653962222c2022697373756572223a202256504e222c2022696e666f223d3a207b22736563726574223a202247455a44474e425647593354514f4a5147455a44474e425647593354514f4a51222c2022616c676f223a2022534841323536222c2022646967697473223a20382c2022706572696f64223a2033302c2022636f756e746572223a20377d7d5d2c202267726f757073223a205b7b2275756964223a20226731222c20226e616d65223a2022576f726b227d5d7d7d", // lint: allow-literal
+ "7b2276657273696f6e223a20312c2022686561646572223a207b22736c6f7473223a206e756c6c2c2022706172616d73223a206e756c6c7d2c20226462223a207b2276657273696f6e223a20332c203c5b8a4122656e7472696573223a225b7b2274797065223a2022746f7470222c202275756964223a20227531222c20226e616d65223a2022616c696365222c2022697373756572223a2022476974487562222c20226e6f7465223a2022222c20226661766f72697465223a20747275652c202269636f6e223a206e756c6c2c2022696e666f223a207b22736563726574223a202247455a44474e425647593354514f4a5147455a44474e425647593354514f4a51222c2022616c676f223a202253484131222c2022646967697473223a20362c2022706572696f64223a2033307d2c202267726f7570735b5b5b5b5b5b5b5b5b5b223a205b226731225d7d2c207b2274797065223a2022686f7470222c20226e616d65223a2022625c753030653962222c2022697373756572223a202256504e222c2022696e666f223a207b22736563726574223a202247455a44474e425647593354514f4a5147455a44474e425647593354514f4a51222c2022616c676f223a2022534841323536222c2022646967697473223a20382c2022706572696f64223a2033302c2022636f756e746572223a20377d7d5d2c202267726f757073223a205b7b2275756964223a20226731222c20226e616d65223a2022576f726b227d5d7d7d", // lint: allow-literal
+ "7b2276657273696f6e223a20312c2022686561646572223a207b22736c6f7473223a206e756c6c2c2022706172616d73223a206e756c6c7d2c20226462223a207b2276657273696f6e223a20332c2022656e7472696573223a205b7b2274797065223a2022746f7470222c202275756964223a20227531222c20226e616d65223a2022616c696365222c2022697373756572223a2022476974487562222c20226e6f7465223a2022222c20226661766f72697465223a20747275652c202269636f6e223a206e756c6c2c2022696e666f223a207b22736563726574223a202247455a44474e425647593354514f4a5147455a44474e425647593354514f4a51222c2022616c676f223a202253484131222c2022646967697473223a20362c2022706572696f64222a3a2033307d2c202267726f757073223a205b226731225d7d2c207b2274797065223a2022686f7470222c20226e616d65223a2022625c753030653962222c2022697373756572223a202256504e222c2022696e666f223a207b22736563726574223a202247455a44474e425647593354514f4a5147455a44474e425647593354514f4a51222c2022616c676f223a2022534841323536222c2022646967697473223a20382c2022706572696f64223a2033302c2022636f756e746572223a20377d7d5d2c202267726f757073223a205b7b2275756964223a20226731222c20226e616d65223a2022576f726b227d5d7d7d", // lint: allow-literal
+ "7b2276657273696f6e223a20312c2022686561646572223a207b22736c6f7473223a206e756c5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b6c2c2022706172616d73223a206e756c6c7d2c20226462223a207b2276657273696f6e223a20332c2022656e7472696573223a205b7b2274797065223a2022746f7470222c202275756964223a20227531222c20226e616d65223a2022616c696365222c2022697373756572223a2022476974487562222c20226e6f7465223a2022222c20226661766f72697465223a20747275652c202269636f6e223a206e756c6c2c2022696e666f223a207b22736563726574223a202247455a44474e425647593354514f4a5147455a44474e425647593354514f4a51222c2022616c676f223a202253484131222c2022646967697473223a20362c2022706572696f64223a2033307d2c202267726f757073223a205b226731225d7d2c207b2274797065223a2022686f7470222c20226e616d65223a2022625c753030653962222c2022697373756572223a202256504e222c2022696e666f223a207b22736563726574223a202247455a44474e425647593354514f4a5147455a44474e425647593354514f4a51222c2022616c676f223a2022534841323536222c2022645b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b6967697473223a20382c2022706572696f64223a2033302c2022636f756e746572223a20377d7d5d2c5d202267726f757073223a205b7b2275756964223a20226731222c20226e616d65223a2022576f726b227d5d7d7d", // lint: allow-literal
+ "7b2276657273696f6e223a20312c2022686561646572223a207b22736c6f7473223a206e756c6c2c2022706172616d73223a206e756c6c7d2c20226462223a207b2276657273696f6e223a20332c2022656e7472696573223a205b7b2274797065223a2022746f74702224202275756964223a20227531222c20226e616d65223a2022616c696365222c2022697373756572223a2022476974487562222c20226e6f7465223a2022222c20226661766f72697465223a20747275652c202269636f6e223a206e756c6c2c2022696e666f223a207b22736563726574223a202247455a44474e425647593354514f4a5147455a44474e425647593354514f4a51222c2022616c676f223a202253484131222c2022646967697473223a20362c2022706572696f64223a2033307d2c202267726f757073223a205b226731225d7d2c207b2274797065223a2022686f7470222c20226e616d65223a2022625c753030653962222c2022697373756572223a202256504e222c2022696e666f223a207b22736563726574223a202247455a44474e425647593354514f4a5147455a44474e425647593354514f4a51222c2022616c676f223a2022534841323536222c2022646967697473223a20382c2022706572696f64223a2033302c2022636f756e746572223a20377d7d5d2c202267726f757073223a205b7b2275756964223a20226731222c20226e616d65223a2022576f726b227d5d7d7d", // lint: allow-literal
+ "7b2276657273696f6e223a20312c2022686561646572223a207b22736c6f7473223a206e756c6c2c2022706172616d73223a206e756c6c7d2c20226462223a207b2276657273696f6e223a20332c2022656e7472696573223a205b7b2274797065223a2022746f7470222c202275756964223a20227531222c20226e616d65223a2022616c696365222c2022697373756572223a20224769744875627465223a2022222c20226661766f72697465223a20747275652c202269636f6e223a206e756c6c2c2022696e666f223a207b22736563726574223a202247455a44474e425647593354514f4a5147455a44474e425647593354514f4a51222c2022616c676f223a202253484131222c2022646967697473223a20362c2022706572696f64223a2033307d2c202267726f757073223a205b226731225d7d2c207b2274797065223a2022686f7470222c20226e616d65223a2022625c753030653962222c2022697373756572223a202256504e222c2022696e666f6563726574223a202247455a44474e425647593354514f4a5147455a44474e425647593354514f4a51222c2022616c676f223a2022534841323536222c2022646967697473223a20382c2022706572696f64223a2033302c2022636f756e746572223a20377d7d5d2c202267726f757073223a205b7b2275756964223a20226731222c20226e616d65223a2022576f726b227d5d7d7d", // lint: allow-literal
+ "7b2276657273696f6e223a20312c2022686561646572223a207b22736c6f7473223a206e756c6c2c2022706172616d73223a206e756c6c7d2c20226462223a207b2276657273696f6e223a20332c2022656e7472696573223a205b7b2274797065223a2022746f7470222c202275756964223a20227531222c20226e616d65223a2022616c696365222c2022697373756572223a2022476974487562222c20226e6f7465223a2022222c20226661766f72697465223a20747275652c202269636b6e223a206e756c6c2c2022696e666f223a207b22736563726574223a202247455a44474e425647593354514f4a5147455a44474e425647593354514f4a51222c2022616c676f223a202253484131222c2022646967697473223a20362c2022706572696f64223a2033307d2c202267726f757073223a205b226731225d7d2c207b2274797065223a2022686f7470222c20226e616d65223a2022625c753030653962222c2022697373756572223a202256504e222c2022696e666f223a207b22736563726574223a202247455a44474e425647593354514f4a5147455a44474e425647593354514f4a51222c2022616c676f223a2022534841323536222c2022646967697473223a20382c2022706572696f64223a2033302c2022636f756e746572223a20377d7d5d2c202267726f757073223a205b7b2275756964223a20226731222c20226e616d65223a2022576f726b227d5d7d7d", // lint: allow-literal
+ "7b2276657273696f6e223a20312c2022686561646572223a207b22736c6f7473223a206e756c6c2c2022706172616d73223a206e756c6c7d2c20226462223a207b2276657273696f6e223a20332c2022656e7472696573223a205b7b227479706522322022746f7470222c202275756964223a20227531222c20226e616d65223a2022616c696365222c2022697373756572223a2022476974487562222c20226e6f7465223a2022222c20226661766f6e756c6c72697465223a20747275652c202269636f6e223a206e756c6c2c2022696e666f223a207b22736563726574223a202247455a44474e425647593354514f4a5147455a44474e425647593354514f4a51222c2022616c676f223a202253484131222c2022646967697473223a20362c2022706572696f64223a2033307d2c202267726f757073223a205b226731225d7d2c207b2274797065223a2022686f7470222c20226e616d65223a2022625c753030653962222c2022697373756572223a202256504e222c2022696e666f223a207b22736563726574223a202247455a44474e425647593354514f4a5147455a44474e425647593354514f4a51222c2022616c676f223a2022534841323536222c2022646967697473223a20382c2022706572696f64223a2033302c2022636f756e746572223a20377d7d5d2c202267726f757073223a205b7b2275756964223a20226731222c20226e616d65223a2022576f726b227d5d7d7d", // lint: allow-literal
+ "7b2276657273696f6e223a20312c2022686561646572223a207b22736c6f7473223a206e756c6c2c2022706172616d73223a206e756c6c7d2c20226462223a207b2276657273696f6e223a20332c2022656e7472696573223a205b7b2274797065223a2022746f7470222c202275756964223a20227531222c20226e616d65223a2022616c696365222c2022697373756572223a2022476974487562222c20226e6f7465223a2022222c20226661766f72697465223a20747275652c202269636f6e223a206e756c6c2c2022696e666f223a207b22736563726574223a202247455a4447", // lint: allow-literal
+ "7b2276657273696f6e223a20312c2022686561646572223a207b22736c6f7473223a206e756c6c2c2022706172616d73223a206e756c6c7d2c20226462223a207b2276657273696f6e223a20332c2022656e7472696573223a205b7b2274797065223a2022746f7470222c202275756964223a20227531222c20226e616d65223a2022616c696365222c2022697373756572223a2022476974487562222c20226e6f7465223a2022222c20226661766f72697465223a20747275652c202269636f6e223a206e756c6c2c2022696e666f223a207b22736563726574223a202247455a44474e425647593354514f4a5147455a44474e425647593354514f4a51222c2022616c676f223a202253484131222c2022646967697473223a20362c2022706572696f64223a2033307d2c202267726f757073223a205b226731225d7d2c207b2274797065223a2022686f7470222c20226e616d65223a2022625c753030653962222c2022697373756572223a202256504e222c2022696e666f223a207b22736563726574223a202247455a44474e425647593354514f4a5147455a44474e425647593354514f4a51222c2022616c476f223a2022534841323536222c2022646967697473223a20382c2022706572696f64223a2033302c2022636f756e746572223a20377d7d5d2c202267726f757073223a205b7b2275756964223a20226731222c20226e616d65223a2022576f726b227d5d7d7d", // lint: allow-literal
+ "7b2276657273696f6e223a20312c2022686561646572223a207b22736c5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b6f7473223a206e756c6c2c2022706172616d73223a206e756c6c7d2c20226462223a207b2276657273696f6e223a20332c2022656e7472696573223a205b7b2274797065223a2022746f7470222c202275756964223a20227531222c20226e616d65223a2022616c696365222c2022697373756572223a2022476974487562222c20226e6f7465223a2022222c20226661766f72697465223a20747275652c202269636f6e223a206e756c6c2c2022696e666f22223a207b22736563726574223a202247455a44474e425647593354514f4a5147455a444722414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141224e425647593354514f4a51222c2022616c676f223a202253484131222c2022646967697473223a20362c2022706572696f64223a2033307d2c202267726f757073223a205b226731225d7d2c207b2274797065223a2022686f7470222c20226e616d65223a2022625c753030653962222c2022697373756572223a202256504e222c2022696e666f223a207b22736563726574223a202247455a44474e425647593354514f4a5147455a44474e425647593354514f4a51222c2022616c676f223a2022534841323536222c2022646967697473223a20382c2022706572696f64223a2033302c2022636f756e746572223a20377d7d5d2c202267726f757073223a205b7b2275756964223a20226731222c20226e616d65223a2022576f726b227d5d7d7d", // lint: allow-literal
+ "7b2276657273696f6e223a20312c2022686561646572223a207b22736c6f7473223a206e756c6c2c2022706172616d73223a206e756c6c7d2c20226462223a207b2276657273696f6e223a20332c2022656e7472696573223a205b7b2274797065223a2022746f7470222c202275756964223a20227531222c20226e616d65223a2022616c696365222c2022697373756572223a2022476974487562222c20226e6f7465223a2022222c20226661766f72697465223a20747275652c202269636f6e223a206e756c6c2c2022696e666f223a207b22736563726574223a202247455a44474e425647593354514f4a5147455a44474e425647593354514f4a51222c2022616c676f223a202253484131222c2022646967697473223a20362c2022706572696f64223a2033307d2c202267726f757073223a205b226731225d7d2c205b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b7b2274797065223a2022686f7470222c20226e616d65223a2022625c753030653962222c2022697373756572223a202256504e222c2022696e666f223a207b22736563726574223a202247455a44474e425647593354514f4a5147455a44474e425647593354514f4a51222c2022616c676f223a2022534841323536222c2022646967697473223a20382c2022706572696f64223a2033302c2022636f756e746572223a20377d7d5d2c2022673d726f757073223a205b7b2275756964223a20226731222c20226e616d65223a2022576f726b227d5d7d7d", // lint: allow-literal
+ "7b2276657273696f6e223a20312c2022686561646572223a207b22736c6f7473223a206e756c6c2c2022706172616d73223a206e756c6c7d2c20226462223a207b2276657273696f6e223a20332c2022656e7472224141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414122696573223a205b7b2274797065223a2022746f7470222c202275756964223a20227531222c20226e616d65223a2022616c696365222c2022697373756572223a2022476974487562222c20226e6f7465223a2022222c20226661766f72697465223a20747275652c202269636f6e223a206e756c6c2c2022696e666f223a207b22736563726574223a202247455a44474e425647593354514f4a5147455a44474e425647593354514f4a51222c2022616c676f223a202253484131222c2022646967697473223a20362c2022706572696f64223a2033307d2c202267726f757073223a205b2267202267726f757073223a205b226731225d7d2c207b2274797065223a2022686f7470222c20226e616d65223a2022625c753030653962222c686f7470222c20226e616d65223a2022625c753030653962222c2022697373756572223a202256504e222c2022696e666f223a207b22736563726574223a202247455a44474e425647593354514f4a5147455a44474e425647593354514f4a51222c2022616c676f223a2022534841323536222c2022646967697473223a20382c2022706572696f64223a2033302c2022636f756e746572223a20377d7d5d2c202267726f757073223a205b7b2275756964223a20226731222c20226e616d65223a2022576f726b227d5d7d7d", // lint: allow-literal
+ "7b2276657273696f6e223a20312c2022686561646572223a207b22736c6f7473223a206e756c6c2c2022706172616d73223a206e756c6c7d2c20226462223a207b2276657273696f6e223a20332c2022656e7472696573223a205b7b2274797065223a2022746f7470222c202275756964223a20227531222c20226e616d65223a2022616c696365222c2022697373756572223a2022476974487562222c20226e6f7465223a2022222c20226661766f72697465223a20747275652c202269636f6e223a206e756c6c2c2022696e666f223a207b22736563726574223a202247455a44474e425647593354514f4a5147455a44474e425647593354514f4a51222c2022616c676f223a202253484131222c2022646967697473223a20362c2022706572696f64223a2033307d2c202267726f757073223a205b226731225d7d2c207b2274797065223a2022686f7470222c20226e616d65223a2022625c753030653962222c2022697373756570223a202256504e222c207b22736563726574223a202247455a44474e425647593354514f4a5147455a44474e425647593354514f4a51222c2022616c676f223a2022534841323536222c2022646967697473223a20382c2022706572696f64223a2033302c2022636f756e746572223a20377d7d5d2c202267726f757073223a205b7b2275756964223a20226731222c20226e616d65223a2022576f726b227d5d7d7d", // lint: allow-literal
+ "7b2276657273696f6e223a20312c2022686561646572223a207b22736c6f7473223a206e756c6c2c2022706172616d73223a206e756c6c7d2c20226462223a207b2276657273696f6e223a20332c2022656e7472696573223a205b7b2274797065223a2022746f7470222c202275756964223a20227531222c20226e616d65223a2022616c696365222c2022697373756572223a2022476974487562222c20226e6f7465223a2022222c20226661766f72697465223a20747275652c202269636f6e223a206e756c6c2c2022696e666f223a207b22736563726574223a202247455a44474e425647593354514f4a5147455a44474e425647593354514f4a51222c2022616c676f223a202253484131222c2022646967697473223a20362c2022706572696f64223a2033307d2c202267726f757073223a205b226731225d7d2c207b2274797065223a2022686f7470222c20226e616d65223a2022625c753030653962222c2022697373756572223a202256504e222c2022696e666f223a207b22736563726574223a202247455a44474e425647593354514f4a5147455a44474e425647593354514f4a51222c2022616c676f223a2022534841323536222c2022642241414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141226967697473223a20382c2022706572696f64223a2033302c2022636f756e746572223a20377d7d5d2c202267726f757073223a205b7b2275756964223a20226731222c20226e616d65223a2022576f726b227d5d7d7d" // lint: allow-literal
+ ];
+
+ const URIS = [
+ "6f7470617574683a2f2f746f74702f4769744875623a616c6963652534306578616d706c652e636f6d3f7365637265743d47455a44474e425647593354514f4a5147455a44474e425647593354514f4a51266973737565723d476974487562266469676974733d36", // lint: allow-literal
+ "6f7470617574683a2f2f686f74702f56504e3a626f623f7365637265743d47455a44474e425647593354514f4a5147455a44474e425647593354514f4a5126636f756e7465723d353b616c676f726974686d3d534841323536", // lint: allow-literal
+ "6f7470617574683a2f2f737465616d2f537465616d3a6361726f6c3f7365637265743d47455a44474e425647593354514f4a5147455a44474e425647593354514f4a51", // lint: allow-literal
+ "6f7470617574683a2f2f686f74702f56504e3a626f623f7365637265743d47455a44474e425647593354514f4a5147455a44474e425647593354514f4a5126636f756e7465723d353b616c676f726974686d3d535841323536", // lint: allow-literal
+ "6f7470617574683a2f2f746f74702f4769744875623a616c6963652534306578616d706c652e636f6d3f7365637265743d47455a44574e425647593354514f4a5147455a44474e425647593354514f4a51266973737565723d4769746973737565723d476974487562266469676974733d36", // lint: allow-literal
+ "6f7470617574683a2f2f686f74702f56504e3a626f623f736526637265743d47455a44474e425647593354514f4a5147455a44474e425647593354514f4a5126636f756e7465723d353b616c676f726974686d3d534841224141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141", // lint: allow-literal
+ "6f747061", // lint: allow-literal
+ "6f7470613a2f2f686f74702f56504e3a626f623f7365637265743d47455a44474e425647593354514f5a44474e4256475933544a5126636f756e7465723d353b616c676f726974686d3d534841323536", // lint: allow-literal
+ "6f7470617574683a2f2f686f74702f56504e3a626f623f7365637265743d47455a44474e425647593354514f4a5147455a44474e425647593354514f4a51266365723d353b616c676f726974686d3d534841323536", // lint: allow-literal
+ "6f7470b8617574683a2f2f747f74702f476974", // lint: allow-literal
+ "6f7470617574683a2f2f746f74702f4769744875623a616c6963652534302c6578616d706c652e636f6d3f7365637265743d47455aac0b44474e425647593354514f4a5147455a44474e425647593354514f4a51266973737565723d476974487562", // lint: allow-literal
+ "6f7470617525fb737465616d2f537465616d3a6361726f6c3f7365637265743d47455a44474e425647593354514f4a5147455a44474e425647593354514f4a51", // lint: allow-literal
+ "6f7470617574683a2f2f746f74702f4769744875623a616c6963652534306578616d224141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414122706c652e636f6d3f7365637265743d47455a44474e425647593354514f4a5147455a44474e42564759224141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141223354514f4a51266973737565723d476974487562266469676974733d36", // lint: allow-literal
+ "6f7470617574683a2f2f746f74702f4769744875623a616c6963652534306578616d706c252e636f6d3f7365637265743d47455a44474e4256475933545111e8b9004f4a5147455a44474e425647593354514f4a51266973737565723d476974487562266469676974733d36", // lint: allow-literal
+ "", // lint: allow-literal
+ "6f7470617574683a2f2f746f7470703bd42f4769744875623a616c6963652534306578616d706c652e636f6d3f7365637265743d47455a44474e425647593354514f4a5147455a44474e425647593354514f4a51266973737565723d476974487562266469676974733d36", // lint: allow-literal
+ "6f7470617574683a2f2f686f74623f7365637265743d47455a44474e425647593354514f4a5147455a44474e425647593354514f4a5126636f756e7465723d353b616c676f726974686d3d534841323536", // lint: allow-literal
+ "6f747244af70617574683a2f2f737465616d2f537465616d3a6361726f6c3f7365637265743d47455a44474e425647593354514f4a5147455a6144474e425647593354514f4a51", // lint: allow-literal
+ "6f7470617574683a2f2f737465616d2f537465616d3a6361726f6c3f7365637265743d47455a44474e42564f4a5147455a44474e425647593354514f4a224141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141412251", // lint: allow-literal
+ "6f7470617574683ae0e32f2f737465616d2f6561", // lint: allow-literal
+ "6f747061757422683a2f2f73683a2f2f737465616d2f537465616d3a6361726f6c3f7365637265743d47455a44474e425647593354514f4a5147455a44474e425647593354514f4a51", // lint: allow-literal
+ "6f7470617574683a2f2f686f74702f56504e3a626f623f736563723a626f623f7365637265743d47455a44474e425647593354514f4a5147455a44474e425647593354514f22414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141afed4141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141224a5126636f756e7465723d353b616c676f726974686d3d534841323536", // lint: allow-literal
+ "6f742d70617574683a2f2f686f74702f56504e3a626f623f7365637265743d47455a44474e425647593354514f4a5147455a44474e425647593354514f4a5126636f756e7465723d353b616c676f7269746e7465723d353b616c676f726974686d3d534841323536", // lint: allow-literal
+ "6f7470617574683a2f2f746f74702f4769744875623a616c6963652534306578616d706c652e636f6d3f7365637265743d47455a44474e425647593354514f4a5147455a44475b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b4e425647593354514f4a51266973737565723d476974487562266469676974733d36", // lint: allow-literal
+ "6f7470617574", // lint: allow-literal
+ "6f7470617574683a2f2f746f74702f4769744875623a616c6963652534306578616d706c652e636f6d3f7365637265743d47455a44474e4256475933455a44474e425647593354514f4a51266973737565723d476974487562266469676974733d36", // lint: allow-literal
+ "6f7470617574683a2f2f746f74702f4769744875623a616c6963652534306578616d706c652e636f6d3f7365637265743d47455a44474e425647593354514f4a5147455a44474e425647593354514f4a51266973a3b5737565723d476974487562266469676974733d36", // lint: allow-literal
+ "6f7470617574683a2f2f686f74702f56504e3a626f623f7365637265743d4745", // lint: allow-literal
+ "6f7470617574683a2f2f686f74702f56504e3a626f623f7365637265743d47455a44474e425647623f7365637265743d47455a44474e425647593354514f4a5147455a44474e425647593354514f4a5126636f756e7465723d353b616c676f72697468593354514f4a5126636f756e7465723d353b616c676f726974686d3d534841323536", // lint: allow-literal
+ "6f747061757468cda2736f3a2f2f737465616d2f537465616d3a6361726f6c3f73656372228bb7eb65743d47455a44474e425647593354514f4a5147455a44474e425647593354514f4a51", // lint: allow-literal
+ "6f7470617574683a2f2f686f74702f56504e3a626f623f7365637265743d47455a44474e425647593354514f4a5147455a44474e425647593354514f4a5126636f756e475147455a44474e425647593356514f4a5126636f756e47593354514f4a5126636f756e7465723d353b616c676f726974686d3d534841323536", // lint: allow-literal
+ "6f7470617574683a2f2f746f74702f4769744875623a616c69636525345b5b5b5b5b5b5b5b5b5b5b5b5b306578616d706c652e636f6d3f7365637265743d47455a", // lint: allow-literal
+ "6f5b5b5b5b5b4b5b5b5b5b5b5b5b5b747061", // lint: allow-literal
+ "6f2f2f686f74702f56504e3a626f623f7365637265743d47455a44474e425647593354514f4a514745", // lint: allow-literal
+ "6f7470617574683a2f2f686f74702f56504e3a626f623f7365637265743d47455a44474efb7d00425647593354514f4a5147455a44474e425647593354514f4a5126636f756e7465723d353b616c676f726974686d3d5348413235364a5126636f756e7465723d353b616c676f726974686d3d534841323536", // lint: allow-literal
+ "6f74706175e87b49f374683a2f2f746f74702f4769744875623a616c6963652534306578616d706c652e636f6d3f7365637265743d47455a44474e425647593354514f4a5147455a5c44474e425647593354514f4a51266973737565", // lint: allow-literal
+ "6f7470617574683a", // lint: allow-literal
+ "6f7470617574683a2f2f746f74702f4769744875623a616c6963652534306578616d706c6571032e636f6d652e636f6d", // lint: allow-literal
+ "6f7470617574683aaf2f746f74702f4769744875623a616c6963652534306578616d706c652e636f6d3f7365637265743d47455a44474e425647593354514f4a5147455a44474e425647593354514f4a51266973737565723d476974487562266469676974733d36", // lint: allow-literal
+ "6f7470617574683a2f2f742241414141414141414141414141414141414141414141414141414141", // lint: allow-literal
+ "6f7470617574683a2f2f746f74702f4769744875623a616c6963652534306578616d706c652e636e5b5b5b5b5b5b5b5b5b5b5b6d3f7365637265743d47455a44474e425647593354514f4a5147455a44474e425647593354514f4a51266973737565723d476974487562266469676974733d36", // lint: allow-literal
+ "6f7470617574683a2f2f746f74702f4769744875623a616c6963652534306578616d706c652e636f6d3f7365637265743d47455a44474e425647593354514f4a51475d455a44474e425647593354514f4a51266973737565723d476974487562266469676974733d36", // lint: allow-literal
+ "6f7470617574683a2f2f5b5b5b5b5b5b8956945b5b5b5b5b5b5b5b5b5b5b5b5b737465616d2f537465616d3a6361726f6c3f7365637265743d47455a44474e425647593354514f4a514745", // lint: allow-literal
+ "6f7470617574683a2f2f737465616d2f537465616d3a6361726f6c3f7365637265743d475b5b5b5b5b5b5b5b5b966dcec25b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b455a44474e425647593354514f4a5147455a44474e425647593354514f4a51", // lint: allow-literal
+ "6f7470617574683a2f2f746f74702f4769744875623a616c6963652534306578616d706c652e636f6d3f7365637265743d47455a44474e4256471b3354514f4a5147455a44474e425647593354514f4a51266973737565723d476974487562266469676974733d366974487562266469676974733d36", // lint: allow-literal
+ "6f7470617574683a2f2f737465616d2f537465616d3a6361726f6c3a2f2f737465616d2f537465616d3a6361726f6c3f7365637265743d47455a44474e425647593354726f6c3f7365637265743d47455a44474e425647593354514f4a5147455a44474e42564759223354514f4a51", // lint: allow-literal
+ "22414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141226f", // lint: allow-literal
+ "6f7470617574683a2f2f746f74702f4769744875623a4769744875623a616c69636525343065785c616d706c652e636f6d3f7365637265743d47455a44474e425647593354514f4a5147455a44474e425647593354514f4a51266973737565723d476974487562266469676974733d36", // lint: allow-literal
+ "6f7470617574683a2f2f737465616d2f537465616d3a6361726f6c3f7365637265743d47455a44474e425647593354514f4a5147455a44474e4256477b593354514f4a51", // lint: allow-literal
+ "6f7470617574683a2f2f737465616d2f5374656154334c7d6d3a6361726f6c3f7365637265743d47455a44474e425647593354514f4a5147455a44474e425647593354514f4a51" // lint: allow-literal
+ ];
+}
diff --git a/test/golden/fenix7/about.png b/test/golden/fenix7/about.png
index d6a87a2..3f74946 100644
--- a/test/golden/fenix7/about.png
+++ b/test/golden/fenix7/about.png
Binary files differ
diff --git a/test/golden/fenix7/import_results.png b/test/golden/fenix7/import_results.png
new file mode 100644
index 0000000..34c1cf7
--- /dev/null
+++ b/test/golden/fenix7/import_results.png
Binary files differ
diff --git a/test/golden/fr255s/about.png b/test/golden/fr255s/about.png
index f8f23d2..b2f2548 100644
--- a/test/golden/fr255s/about.png
+++ b/test/golden/fr255s/about.png
Binary files differ
diff --git a/test/golden/fr255s/import_results.png b/test/golden/fr255s/import_results.png
new file mode 100644
index 0000000..d2cdc19
--- /dev/null
+++ b/test/golden/fr255s/import_results.png
Binary files differ
diff --git a/test/golden/fr70/about.png b/test/golden/fr70/about.png
index c46fa11..d7d18a0 100644
--- a/test/golden/fr70/about.png
+++ b/test/golden/fr70/about.png
Binary files differ
diff --git a/test/golden/fr70/import_results.png b/test/golden/fr70/import_results.png
new file mode 100644
index 0000000..2da3dcc
--- /dev/null
+++ b/test/golden/fr70/import_results.png
Binary files differ
diff --git a/test/golden/fr965/about.png b/test/golden/fr965/about.png
index 61b9745..65e3902 100644
--- a/test/golden/fr965/about.png
+++ b/test/golden/fr965/about.png
Binary files differ
diff --git a/test/golden/fr965/import_results.png b/test/golden/fr965/import_results.png
new file mode 100644
index 0000000..c827d20
--- /dev/null
+++ b/test/golden/fr965/import_results.png
Binary files differ
diff --git a/test/golden/instinct3amoled45mm/about.png b/test/golden/instinct3amoled45mm/about.png
index 1400601..75ad5de 100644
--- a/test/golden/instinct3amoled45mm/about.png
+++ b/test/golden/instinct3amoled45mm/about.png
Binary files differ
diff --git a/test/golden/instinct3amoled45mm/import_results.png b/test/golden/instinct3amoled45mm/import_results.png
new file mode 100644
index 0000000..0dbf559
--- /dev/null
+++ b/test/golden/instinct3amoled45mm/import_results.png
Binary files differ
diff --git a/test/golden/venusq2/import_results.png b/test/golden/venusq2/import_results.png
new file mode 100644
index 0000000..647ca21
--- /dev/null
+++ b/test/golden/venusq2/import_results.png
Binary files differ
diff --git a/test/golden/venux1/import_results.png b/test/golden/venux1/import_results.png
new file mode 100644
index 0000000..1d1251d
--- /dev/null
+++ b/test/golden/venux1/import_results.png
Binary files differ
diff --git a/test/import/AegisTest.mc b/test/import/AegisTest.mc
new file mode 100644
index 0000000..eaad039
--- /dev/null
+++ b/test/import/AegisTest.mc
@@ -0,0 +1,128 @@
+// Author: cirakg & claude ai
+// Summary: Tests for the Aegis reader: database versions 1-3, types, parameters, groups, icons, encryption and bad input.
+
+import Toybox.Lang;
+import Toybox.Test;
+
+// Database version 3: types, algorithms, digits, periods, groups listed
+// after the entries, favourites, icons and Unicode.
+(:test)
+function aegisReadsDatabaseVersion3(logger as Test.Logger) as Boolean {
+ var icon = "";
+ for (var i = 0; i < 200; i++) {
+ icon += "iVBORw0KGgoAAAANSUhEUgAAA";
+ }
+ var entries = [
+ AegisFixtures.entry("totp", "GitHub", "alice", AegisFixtures.SECRET_1, "SHA1", 6, 30,
+ "\"icon\": \"" + icon + "\", \"icon_mime\": \"image/png\", \"groups\": [\"g-work\", \"g-other\"], "),
+ AegisFixtures.entry("totp", "Bänk €", "bob", AegisFixtures.SECRET_2, "SHA256", 8, 60, "\"favorite\": true, "),
+ "{\"type\": \"hotp\", \"name\": \"carol\", \"issuer\": \"VPN\", \"info\": {\"secret\": \"" + AegisFixtures.SECRET_3
+ + "\", \"algo\": \"SHA512\", \"digits\": 7, \"period\": 30, \"counter\": 12}}",
+ AegisFixtures.entry("steam", "Steam", "dave", AegisFixtures.SECRET_1, "SHA1", 5, 30, ""),
+ AegisFixtures.entry("motp", "Old", "erin", AegisFixtures.SECRET_1, "MD5", 6, 10, ""),
+ AegisFixtures.entry("yandex", "Ya", "frank", AegisFixtures.SECRET_1, "SHA256", 8, 30, ""),
+ AegisFixtures.entry("totp", "Odd", "gina", AegisFixtures.SECRET_1, "SHA1", 6, 20, "\"groups\": [\"g-missing\"], "),
+ AegisFixtures.entry("totp", "Slow", "hal", AegisFixtures.SECRET_1, "SHA1", 6, 50, "")
+ ];
+ var groups = "[{\"uuid\": \"g-work\", \"name\": \"Work\"}, {\"uuid\": \"g-other\", \"name\": \"Other\"}]";
+ var reader = AegisFixtures.read(AegisFixtures.export(3, entries, groups));
+ if (reader.error != null || reader.items.size() != 8) {
+ logger.error("error " + reader.error + ", items " + reader.items.size());
+ return false;
+ }
+ var it = reader.items;
+ var github = it[0].entry;
+ var bank = it[1].entry;
+ var vpn = it[2].entry;
+ var steam = it[3].entry;
+ var odd = it[6].entry;
+ var slow = it[7].entry;
+ if (github == null || bank == null || vpn == null || steam == null || odd == null || slow == null) {
+ for (var i = 0; i < it.size(); i++) {
+ logger.error("item " + i + ": " + it[i].problem + " / " + it[i].skipped);
+ }
+ return false;
+ }
+ var checks = [
+ github.name.equals("GitHub") && github.account.equals("alice"),
+ github.group.equals("Work"),
+ bank.name.equals("Bänk €") && bank.algorithm == Hashes.SHA256 && bank.digits == 8 && bank.period == 60,
+ bank.favorite,
+ vpn.type == Entry.OTP_HOTP && vpn.counter == 12l && vpn.digits == 7 && vpn.algorithm == Hashes.SHA512,
+ steam.type == Entry.OTP_STEAM,
+ it[4].skipped != null && it[5].skipped != null,
+ odd.group.equals("") && odd.period == 20 && slow.period == 50,
+ Bytes.equal(github.secret as ByteArray, Bytes.fromUtf8("12345678901234567890"))
+ ];
+ for (var i = 0; i < checks.size(); i++) {
+ if (!(checks[i] as Boolean)) {
+ logger.error("check " + i + " failed");
+ return false;
+ }
+ }
+ return true;
+}
+
+// Database versions 1 and 2 name the group directly.
+(:test)
+function aegisReadsOlderDatabaseVersions(logger as Test.Logger) as Boolean {
+ for (var v = 1; v <= 2; v++) {
+ var reader = AegisFixtures.read(AegisFixtures.export(v,
+ [AegisFixtures.entry("totp", "Mail", "ann", AegisFixtures.SECRET_1, "SHA1", 6, 30, "\"group\": \"Private\", ")], ""));
+ var e = reader.items.size() == 1 ? reader.items[0].entry : null;
+ if (e == null || !e.group.equals("Private")) {
+ logger.error("database version " + v + " failed: " + reader.error);
+ return false;
+ }
+ }
+ return true;
+}
+
+(:test)
+function aegisRefusesEncryptedExports(logger as Test.Logger) as Boolean {
+ var slots = "{\"version\": 1, \"header\": {\"slots\": [{\"type\": 1}], \"params\": {}}, \"db\": \"c2VjcmV0\"}";
+ var dbString = "{\"version\": 1, \"header\": {\"slots\": null}, \"db\": \"c2VjcmV0\"}";
+ var a = AegisFixtures.read(slots);
+ var b = AegisFixtures.read(dbString);
+ return a.error != null && (a.error as String).find("encrypted") != null
+ && b.error != null && (b.error as String).find("encrypted") != null;
+}
+
+(:test)
+function aegisReportsBadEntriesAndDocuments(logger as Test.Logger) as Boolean {
+ var entries = [
+ AegisFixtures.entry("totp", "Nine", "x", AegisFixtures.SECRET_1, "SHA1", 9, 30, ""),
+ "{\"type\": \"totp\", \"name\": \"dup\", \"info\": {\"secret\": \"" + AegisFixtures.SECRET_1 + "\", \"secret\": \""
+ + AegisFixtures.SECRET_2 + "\", \"algo\": \"SHA1\", \"digits\": 6, \"period\": 30}}",
+ AegisFixtures.entry("totp", "Bad", "y", "NOT-BASE32!", "SHA1", 6, 30, ""),
+ "{\"type\": \"totp\", \"name\": \"z\", \"info\": {\"secret\": \"" + AegisFixtures.SECRET_1 + "\", \"digits\": 6.5}}"
+ ];
+ var reader = AegisFixtures.read(AegisFixtures.export(3, entries, ""));
+ var it = reader.items;
+ if (reader.error != null || it.size() != 4) {
+ logger.error("error " + reader.error);
+ return false;
+ }
+ var problems = [it[0].problem, it[1].problem, it[2].problem, it[3].problem];
+ var needles = ["9 digits", "twice", "Base32", "whole number"];
+ for (var i = 0; i < 4; i++) {
+ var p = problems[i];
+ if (p == null || (p as String).find(needles[i]) == null) {
+ logger.error("entry " + i + ": " + p);
+ return false;
+ }
+ }
+ var notJson = AegisFixtures.read("{\"version\": 1, \"db\": [1,]}");
+ var notAegis = AegisFixtures.read("{\"hello\": \"world\"}");
+ var newer = AegisFixtures.read("{\"version\": 2, \"db\": {\"version\": 3, \"entries\": []}}");
+ var deep = "{\"version\": 1, \"x\": " + "[[[[[[[[[[[[[[[[[[1]]]]]]]]]]]]]]]]]]" + "}";
+ return notJson.error != null && notAegis.error != null && newer.error != null
+ && (newer.error as String).find("version 2") != null && AegisFixtures.read(deep).error != null;
+}
+
+(:test)
+function aegisSaysWhenTheTextIsIncomplete(logger as Test.Logger) as Boolean {
+ var full = AegisFixtures.export(3, [AegisFixtures.totp("A", "a", AegisFixtures.SECRET_1), AegisFixtures.totp("B", "b", AegisFixtures.SECRET_2)], "");
+ return AegisFixtures.status(full.substring(0, full.length() / 2) as String) == AegisReader.INCOMPLETE
+ && AegisFixtures.status(full) == AegisReader.DONE;
+}
diff --git a/test/import/FuzzTest.mc b/test/import/FuzzTest.mc
new file mode 100644
index 0000000..7573ffa
--- /dev/null
+++ b/test/import/FuzzTest.mc
@@ -0,0 +1,107 @@
+// 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<String>;
+ all.addAll(FuzzCorpus.JSON as Array<String>);
+ all.addAll(FuzzCorpus.URIS as Array<String>);
+ 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;
+}
diff --git a/test/import/ImportSupport.mc b/test/import/ImportSupport.mc
new file mode 100644
index 0000000..ba891a7
--- /dev/null
+++ b/test/import/ImportSupport.mc
@@ -0,0 +1,76 @@
+// Author: cirakg & claude ai
+// Summary: Builders for fake Aegis exports and otpauth text used by the import tests (all secrets are RFC test keys).
+
+import Toybox.Lang;
+import Toybox.Test;
+
+(:test)
+module AegisFixtures {
+
+ const SECRET_1 = "GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ"; // lint: allow-literal (RFC 4226 key "12345678901234567890")
+ const SECRET_2 = "JBSWY3DPEHPK3PXP"; // lint: allow-literal (the classic "Hello!" demo key)
+ const SECRET_3 = "MFRGGZDFMZTWQ2LKNNWG23TPOBYXE43U"; // lint: allow-literal (Base32 of "abcdefghijklmnopqrst")
+
+ // One Aegis entry. extra is inserted verbatim (other fields, icon...).
+ function entry(type as String, issuer as String, name as String, secret as String, algo as String, digits as Number,
+ period as Number, extra as String) as String {
+ return "{\"type\": \"" + type + "\", \"uuid\": \"0000\", \"name\": \"" + name + "\", \"issuer\": \"" + issuer
+ + "\", \"note\": \"private note\", " + extra
+ + "\"info\": {\"secret\": \"" + secret + "\", \"algo\": \"" + algo + "\", \"digits\": " + digits
+ + ", \"period\": " + period + "}}";
+ }
+
+ function totp(issuer as String, name as String, secret as String) as String {
+ return entry("totp", issuer, name, secret, "SHA1", 6, 30, "");
+ }
+
+ // A whole unencrypted export (vault version 1) of the given database
+ // version, with entries joined by commas and groups after the entries.
+ function export(dbVersion as Number, entries as Array<String>, groups as String) as String {
+ var joined = "";
+ for (var i = 0; i < entries.size(); i++) {
+ joined += (i > 0 ? ",\n" : "") + entries[i];
+ }
+ return "{\"version\": 1, \"header\": {\"slots\": null, \"params\": null}, \"db\": {\"version\": " + dbVersion
+ + ", \"entries\": [\n" + joined + "\n]" + (groups.length() > 0 ? ", \"groups\": " + groups : "") + "}}";
+ }
+
+ // Runs the reader to the end (or a non-running status).
+ function read(text as String) as AegisReader {
+ var reader = new AegisReader(Bytes.fromUtf8(text));
+ for (var i = 0; i < 10000; i++) {
+ if (reader.step() != AegisReader.RUNNING) {
+ return reader;
+ }
+ }
+ return reader;
+ }
+
+ function status(text as String) as Number {
+ var reader = new AegisReader(Bytes.fromUtf8(text));
+ for (var i = 0; i < 10000; i++) {
+ var s = reader.step();
+ if (s != AegisReader.RUNNING) {
+ return s;
+ }
+ }
+ return -1;
+ }
+
+ // Runs an import to completion on vault and returns its report.
+ function runImport(vault as Vault, text as String, mode as Number, locked as Boolean, now as Long) as ImportReport {
+ var job = new Importer(vault, text, mode, locked, now);
+ Jobs.runToCompletion(job, 1);
+ return job.report;
+ }
+
+ // Some detail line contains needle.
+ function hasDetail(report as ImportReport, needle as String) as Boolean {
+ for (var i = 0; i < report.details.size(); i++) {
+ if (report.details[i].find(needle) != null) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/test/import/ImporterTest.mc b/test/import/ImporterTest.mc
new file mode 100644
index 0000000..411e528
--- /dev/null
+++ b/test/import/ImporterTest.mc
@@ -0,0 +1,208 @@
+// Author: cirakg & claude ai
+// Summary: Tests for the import job: reports, duplicates, replacements, locking, Replace mode, multi-part buffer, failures, a full store, 100 entries.
+
+import Toybox.Lang;
+import Toybox.System;
+import Toybox.Test;
+
+(:test)
+module ImporterFixtures {
+
+ const NOW = 1800000000l;
+
+ function uri(issuer as String, account as String, secret as String) as String {
+ return "otpauth://totp/" + issuer + ":" + account + "?secret=" + secret;
+ }
+
+ function vault() as Vault {
+ return new Vault(new MemoryBackend(), new FakeTime());
+ }
+}
+
+(:test)
+function importerStoresUrisAndReports(logger as Test.Logger) as Boolean {
+ var vault = ImporterFixtures.vault();
+ var text = ImporterFixtures.uri("GitHub", "alice", AegisFixtures.SECRET_1) + "\n"
+ + ImporterFixtures.uri("Mail", "bob", AegisFixtures.SECRET_2) + "\n"
+ + "otpauth://totp/Bad:carol?secret=" + AegisFixtures.SECRET_3 + "&digits=9\n"
+ + "otpauth://motp/Old:dan?secret=" + AegisFixtures.SECRET_3;
+ var report = AegisFixtures.runImport(vault, text, Importer.MODE_APPEND, false, ImporterFixtures.NOW);
+ var ids = vault.ids();
+ var first = vault.open(ids[0]);
+ var ok = first.name.equals("GitHub") && first.source == Entry.SOURCE_IMPORT && first.created == ImporterFixtures.NOW;
+ first.wipeSecrets();
+ var summary = report.summary();
+ if (!summary.equals("Imported 2 · 1 error · 1 skipped")) {
+ logger.error("summary: " + summary);
+ return false;
+ }
+ return ok && ids.size() == 2 && AegisFixtures.hasDetail(report, "URI 3 'Bad': 9 digits")
+ && AegisFixtures.hasDetail(report, "URI 4 'Old': skipped, mOTP");
+}
+
+// The same type and secret is a duplicate, also of a locked entry while the
+// watch is locked (fingerprints); same issuer and account with another
+// secret is imported and flagged.
+(:test)
+function importerFindsDuplicatesAndReplacements(logger as Test.Logger) as Boolean {
+ var vault = ImporterFixtures.vault();
+ StorageFixtures.setPin(vault, StorageFixtures.PIN);
+ var locked = StorageFixtures.totp("Vault");
+ locked.secret = Bytes.fromUtf8("12345678901234567890");
+ locked.algorithm = Hashes.SHA1;
+ locked.locked = true;
+ var lockedId = vault.add(locked);
+ vault.lock();
+ var first = AegisFixtures.runImport(vault, ImporterFixtures.uri("GitHub", "alice", AegisFixtures.SECRET_2),
+ Importer.MODE_APPEND, false, ImporterFixtures.NOW);
+ var text = ImporterFixtures.uri("GitHub", "alice", AegisFixtures.SECRET_2) + " " // duplicate of the first import
+ + ImporterFixtures.uri("Other", "x", AegisFixtures.SECRET_1) + " " // duplicate of the locked entry
+ + ImporterFixtures.uri("GitHub", "alice", AegisFixtures.SECRET_3) + " " // same names, new secret
+ + ImporterFixtures.uri("Twice", "y", AegisFixtures.SECRET_3); // duplicate within this import
+ var second = AegisFixtures.runImport(vault, text, Importer.MODE_APPEND, false, ImporterFixtures.NOW);
+ if (second.duplicates != 3 || second.imported != 1) {
+ logger.error(second.summary());
+ return false;
+ }
+ return first.imported == 1 && AegisFixtures.hasDetail(second, "duplicate of " + lockedId)
+ && AegisFixtures.hasDetail(second, "possible replacement for") && vault.ids().size() == 3;
+}
+
+// "Require PIN" locks the whole import; while locked they wait as pending.
+(:test)
+function importerLocksEntriesAsPending(logger as Test.Logger) as Boolean {
+ var vault = ImporterFixtures.vault();
+ StorageFixtures.setPin(vault, StorageFixtures.PIN);
+ vault.lock();
+ var report = AegisFixtures.runImport(vault, ImporterFixtures.uri("Bank", "eve", AegisFixtures.SECRET_1),
+ Importer.MODE_APPEND, true, ImporterFixtures.NOW);
+ return report.imported == 1 && report.pendingLocked == 1 && vault.pendingIds().size() == 1 && vault.ids().size() == 0
+ && report.summary().find("waiting for your PIN") != null;
+}
+
+// Replace mode deletes earlier imported entries, never manual ones.
+(:test)
+function importerReplaceModeKeepsManualEntries(logger as Test.Logger) as Boolean {
+ var vault = ImporterFixtures.vault();
+ var manualId = vault.add(StorageFixtures.totp("Manual"));
+ // (The manual fixture uses SECRET_1's bytes, so the imports use other secrets.)
+ AegisFixtures.runImport(vault, ImporterFixtures.uri("Old", "a", AegisFixtures.SECRET_2) + " "
+ + ImporterFixtures.uri("Older", "b", AegisFixtures.SECRET_3), Importer.MODE_APPEND, false, ImporterFixtures.NOW);
+ // The same secret as a replaced entry is not a duplicate any more.
+ var report = AegisFixtures.runImport(vault, ImporterFixtures.uri("New", "c", AegisFixtures.SECRET_2),
+ Importer.MODE_REPLACE, false, ImporterFixtures.NOW);
+ var ids = vault.ids();
+ var newest = vault.header(ids[1]);
+ return report.removed == 2 && report.imported == 1 && report.duplicates == 0 && ids.size() == 2
+ && ids[0].equals(manualId) && newest != null && newest.name.equals("New");
+}
+
+// JSON cut in two parts: the first waits (sealed in storage), the second
+// completes the import and clears the buffer.
+(:test)
+function importerJoinsMultiPartJson(logger as Test.Logger) as Boolean {
+ var vault = ImporterFixtures.vault();
+ var store = vault.storage() as MemoryBackend;
+ var full = AegisFixtures.export(3, [AegisFixtures.totp("A", "a", AegisFixtures.SECRET_1),
+ AegisFixtures.totp("B", "b", AegisFixtures.SECRET_2), AegisFixtures.totp("C", "c", AegisFixtures.SECRET_3)], "");
+ var cut = full.length() / 2;
+ var part1 = AegisFixtures.runImport(vault, full.substring(0, cut) as String, Importer.MODE_APPEND, false, ImporterFixtures.NOW);
+ var waiting = part1.waitingChars == cut && ImportBuffer.hasPending(store) && vault.ids().size() == 0
+ && part1.summary().find("Waiting for more") != null;
+ // The buffer is sealed: the secret's Base32 text isn't readable in storage.
+ var chunk = store.raw("imp.0") as ByteArray;
+ var sealed = Bytes.toHex(chunk).find(Bytes.toHex(Bytes.fromUtf8(AegisFixtures.SECRET_1))) == null;
+ var part2 = AegisFixtures.runImport(vault, full.substring(cut, full.length()) as String, Importer.MODE_APPEND, false,
+ ImporterFixtures.NOW + 60);
+ return waiting && sealed && part2.imported == 3 && !ImportBuffer.hasPending(store);
+}
+
+(:test)
+function importerDiscardsExpiredParts(logger as Test.Logger) as Boolean {
+ var vault = ImporterFixtures.vault();
+ var full = AegisFixtures.export(3, [AegisFixtures.totp("A", "a", AegisFixtures.SECRET_1)], "");
+ var cut = full.length() / 2;
+ AegisFixtures.runImport(vault, full.substring(0, cut) as String, Importer.MODE_APPEND, false, ImporterFixtures.NOW);
+ var late = AegisFixtures.runImport(vault, full.substring(cut, full.length()) as String, Importer.MODE_APPEND, false,
+ ImporterFixtures.NOW + ImportBuffer.MAX_AGE_S + 1);
+ return late.failure != null && vault.ids().size() == 0 && !ImportBuffer.hasPending(vault.storage());
+}
+
+(:test)
+function importerExplainsUnusableText(logger as Test.Logger) as Boolean {
+ var cases = [
+ ["", "empty"],
+ ["hello world", "not recognised"],
+ ["otpauth-migration://offline?data=CjEKCkhlbGxvId6tvu8", "Google Authenticator"],
+ ["{\"version\": 1, \"header\": {\"slots\": [1]}, \"db\": \"abc\"}", "encrypted"],
+ ["{\"version\": 1, \"db\": [1,]}", "not valid JSON"]
+ ];
+ for (var i = 0; i < cases.size(); i++) {
+ var c = cases[i] as Array<String>;
+ var report = AegisFixtures.runImport(ImporterFixtures.vault(), c[0], Importer.MODE_APPEND, false, ImporterFixtures.NOW);
+ var failure = report.failure;
+ if (failure == null || failure.find(c[1]) == null) {
+ logger.error("case " + i + ": " + failure);
+ return false;
+ }
+ }
+ return true;
+}
+
+// A store that fills up mid-import keeps the whole entries stored so far.
+(:test)
+function importerStopsCleanlyWhenTheStoreIsFull(logger as Test.Logger) as Boolean {
+ var vault = ImporterFixtures.vault();
+ var store = vault.storage() as MemoryBackend;
+ vault.add(StorageFixtures.totp("Existing"));
+ store.capacity = store.usedBytes() + 500;
+ var text = "";
+ for (var i = 0; i < 10; i++) {
+ text += ImporterFixtures.uri("E" + i, "a", Base32.encode(Bytes.fromUtf8("fake" + (200000 + i).toString()))) + " ";
+ }
+ var report = AegisFixtures.runImport(vault, text, Importer.MODE_APPEND, false, ImporterFixtures.NOW);
+ var reopened = new Vault(store, new FakeTime());
+ logger.debug("full store: " + report.summary() + " " + report.details.toString());
+ return report.imported >= 1 && report.imported < 10 && AegisFixtures.hasDetail(report, "Stopped")
+ && reopened.ids().size() == 1 + report.imported;
+}
+
+// Fingerprints: same type and secret give the same 8 bytes, anything else
+// differs; they are in the header and survive a round trip.
+(:test)
+function importerFingerprintsIdentifySecrets(logger as Test.Logger) as Boolean {
+ var vault = ImporterFixtures.vault();
+ var a = StorageFixtures.totp("A");
+ var b = StorageFixtures.totp("B");
+ var c = StorageFixtures.hotp("C", 0l);
+ var d = StorageFixtures.totp("D");
+ d.secret = Bytes.fromUtf8("another secret");
+ var fa = vault.keyManager.fingerprint(a);
+ var id = vault.add(a);
+ var header = vault.header(id);
+ return fa.size() == 8 && Bytes.equal(fa, vault.keyManager.fingerprint(b))
+ && !Bytes.equal(fa, vault.keyManager.fingerprint(c)) && !Bytes.equal(fa, vault.keyManager.fingerprint(d))
+ && header != null && Bytes.equal(header.fingerprint as ByteArray, fa);
+}
+
+// 100 entries from one Aegis export: all stored, timed for PERFORMANCE.md.
+(:test)
+function importerHandlesOneHundredEntries(logger as Test.Logger) as Boolean {
+ var entries = [] as Array<String>;
+ for (var i = 0; i < 100; i++) {
+ // Distinct fake secrets: Base32 of a counter-based 10-byte value.
+ var raw = Bytes.fromUtf8("fake" + (100000 + i).toString());
+ entries.add(AegisFixtures.totp("Issuer " + i, "user" + i + "@example.com", Base32.encode(raw)));
+ }
+ var text = AegisFixtures.export(3, entries, "");
+ var vault = ImporterFixtures.vault();
+ var start = System.getTimer();
+ var job = new Importer(vault, text, Importer.MODE_APPEND, false, ImporterFixtures.NOW);
+ var steps = 0;
+ while (!job.step(1)) {
+ steps++;
+ }
+ var elapsed = System.getTimer() - start;
+ logger.debug("BENCH import_100_aegis ms=" + elapsed + " steps=" + steps + " json_bytes=" + text.length());
+ return job.report.imported == 100 && vault.ids().size() == 100;
+}
diff --git a/test/import/JsonTest.mc b/test/import/JsonTest.mc
new file mode 100644
index 0000000..c98aaa4
--- /dev/null
+++ b/test/import/JsonTest.mc
@@ -0,0 +1,128 @@
+// Author: cirakg & claude ai
+// Summary: Tests for the streaming JSON tokenizer: tokens, escapes, numbers, errors, incomplete input, limits.
+
+import Toybox.Lang;
+import Toybox.Test;
+
+(:test)
+module JsonFixtures {
+
+ // All tokens of text, as "kind:value" strings (values for keys, strings
+ // and numbers), ending with the final token.
+ function tokens(text as String) as Array<String> {
+ var tok = new Json.Tokenizer(Bytes.fromUtf8(text));
+ var out = [] as Array<String>;
+ for (var i = 0; i < 200; i++) {
+ var t = tok.next();
+ if (t == Json.T_KEY) {
+ out.add("K:" + tok.text);
+ } else if (t == Json.T_STRING) {
+ out.add("S:" + tok.text);
+ } else if (t == Json.T_NUMBER) {
+ out.add(tok.numberIsInteger ? "N:" + tok.number : "N:float");
+ } else {
+ out.add("T" + t);
+ }
+ if (t == Json.T_END || t == Json.T_INCOMPLETE || t == Json.T_ERROR) {
+ return out;
+ }
+ }
+ return out;
+ }
+
+ function last(text as String) as Number {
+ var tok = new Json.Tokenizer(Bytes.fromUtf8(text));
+ for (var i = 0; i < 500; i++) {
+ var t = tok.next();
+ if (t == Json.T_END || t == Json.T_INCOMPLETE || t == Json.T_ERROR) {
+ return t;
+ }
+ }
+ return -1;
+ }
+}
+
+(:test)
+function jsonTokenizesAllValueKinds(logger as Test.Logger) as Boolean {
+ var got = JsonFixtures.tokens("{\"a\": [1, -20, true, false, null, \"x\\\"y\\n\\u00e9\\ud83d\\ude00\"], \"b\": {}}");
+ var expected = ["T1", "K:a", "T3", "N:1", "N:-20", "T8", "T9", "T10", "S:x\"y\né😀", "T4", "K:b", "T1", "T2", "T2", "T0"];
+ if (!got.toString().equals(expected.toString())) {
+ logger.error("got " + got.toString());
+ return false;
+ }
+ return true;
+}
+
+(:test)
+function jsonNumbers(logger as Test.Logger) as Boolean {
+ var got = JsonFixtures.tokens("[0, 1.5, 1e3, -0, 123456789012345678901, 999999999999999999]");
+ var expected = ["T3", "N:0", "N:float", "N:float", "N:0", "N:float", "N:999999999999999999", "T4", "T0"];
+ if (!got.toString().equals(expected.toString())) {
+ logger.error("got " + got.toString());
+ return false;
+ }
+ return true;
+}
+
+(:test)
+function jsonRejectsInvalidDocuments(logger as Test.Logger) as Boolean {
+ var bad = [
+ "[1,]", "{\"a\" 1}", "[01]", "[\"\\x\"]", "[\"\\ud800\"]", "[\"\\udc00\"]", "{a: 1}", "[1] [2]",
+ "[\"\\u0000\"]", "[1.]", "[-]", "[tru]", "{\"a\":1,}", "[\"a\tb\"]", "[1 2]", "]"
+ ];
+ for (var i = 0; i < bad.size(); i++) {
+ if (JsonFixtures.last(bad[i] as String) != Json.T_ERROR) {
+ logger.error("accepted invalid JSON " + bad[i]);
+ return false;
+ }
+ }
+ return true;
+}
+
+// Input that stops inside the document is "incomplete", not an error, so
+// a multi-part import can wait for the rest.
+(:test)
+function jsonReportsIncompleteInput(logger as Test.Logger) as Boolean {
+ var partial = ["{", "{\"a\"", "{\"a\":", "{\"a\": \"abc", "{\"a\": 12", "[tr", "[1, {\"b\": [", "{\"a\": \"x\\"];
+ for (var i = 0; i < partial.size(); i++) {
+ if (JsonFixtures.last(partial[i] as String) != Json.T_INCOMPLETE) {
+ logger.error("not incomplete: " + partial[i]);
+ return false;
+ }
+ }
+ return JsonFixtures.last("") == Json.T_INCOMPLETE && JsonFixtures.last(" 7 ") == Json.T_END;
+}
+
+(:test)
+function jsonEnforcesTheDepthLimit(logger as Test.Logger) as Boolean {
+ var ok = "";
+ var tooDeep = "";
+ for (var i = 0; i < Json.MAX_DEPTH; i++) {
+ ok += "[";
+ }
+ tooDeep = ok + "[";
+ var close = "";
+ for (var i = 0; i < Json.MAX_DEPTH; i++) {
+ close += "]";
+ }
+ return JsonFixtures.last(ok + close) == Json.T_END && JsonFixtures.last(tooDeep + close + "]") == Json.T_ERROR;
+}
+
+// Long strings (icons) are skipped without decoding, including escaped
+// quotes inside them; long keys are errors.
+(:test)
+function jsonSkipsLongStrings(logger as Test.Logger) as Boolean {
+ var big = "";
+ for (var i = 0; i < 300; i++) {
+ big += "ABCDEFGHIJ";
+ }
+ var withQuote = big + "\\\"" + big;
+ var got = JsonFixtures.tokens("{\"icon\": \"" + big + "\", \"q\": \"" + withQuote + "\", \"n\": \"ok\"}");
+ var expected = ["T1", "K:icon", "T11", "K:q", "T11", "K:n", "S:ok", "T2", "T0"];
+ if (!got.toString().equals(expected.toString())) {
+ logger.error("got " + got.toString());
+ return false;
+ }
+ return JsonFixtures.last("{\"" + big + "\": 1}") == Json.T_ERROR
+ && JsonFixtures.last("{\"icon\": \"" + big) == Json.T_INCOMPLETE;
+}
diff --git a/test/import/OtpAuthTest.mc b/test/import/OtpAuthTest.mc
new file mode 100644
index 0000000..67f43f5
--- /dev/null
+++ b/test/import/OtpAuthTest.mc
@@ -0,0 +1,99 @@
+// Author: cirakg & claude ai
+// Summary: Tests for otpauth:// parsing: splitting, labels, parameters, Steam, separators and every validation error.
+
+import Toybox.Lang;
+import Toybox.Test;
+
+(:test)
+module OtpAuthFixtures {
+
+ // RFC 4226 test key "12345678901234567890" in Base32.
+ const SECRET = "GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ"; // lint: allow-literal (RFC test key)
+
+ function parse(uri as String) as ImportItem {
+ return OtpAuth.parse(uri, 1);
+ }
+
+ function entry(logger as Test.Logger, uri as String) as Entry? {
+ var item = parse(uri);
+ if (item.entry == null) {
+ logger.error("no entry for " + uri + ": " + item.problem + " / " + item.skipped);
+ }
+ return item.entry;
+ }
+}
+
+(:test)
+function otpauthSplitsRunTogetherUris(logger as Test.Logger) as Boolean {
+ var s = OtpAuthFixtures.SECRET;
+ var uris = OtpAuth.splitUris("junk otpauth://totp/A?secret=" + s + " \n OTPAUTH://totp/B?secret=" + s
+ + "otpauth://hotp/C?secret=" + s + "&counter=1\n");
+ return uris.size() == 3 && (uris[0] as String).find("totp/A") != null && (uris[1] as String).find("totp/B") != null
+ && (uris[2] as String).find("counter=1") != null && (uris[2] as String).find("\n") == null;
+}
+
+(:test)
+function otpauthLabelsAndDefaults(logger as Test.Logger) as Boolean {
+ var e = OtpAuthFixtures.entry(logger, "otpauth://totp/ACME%20Co:john%40example.com?secret=" + OtpAuthFixtures.SECRET);
+ var bare = OtpAuthFixtures.entry(logger, "otpauth://totp/alice?secret=" + OtpAuthFixtures.SECRET.toLower());
+ if (e == null || bare == null) {
+ return false;
+ }
+ return e.name.equals("ACME Co") && e.account.equals("john@example.com") && e.algorithm == Hashes.SHA1
+ && e.digits == 6 && e.period == 30 && e.type == Entry.OTP_TOTP && e.source == Entry.SOURCE_IMPORT
+ && Bytes.equal(e.secret as ByteArray, Bytes.fromUtf8("12345678901234567890"))
+ && bare.name.equals("") && bare.account.equals("alice");
+}
+
+(:test)
+function otpauthParametersAndSeparators(logger as Test.Logger) as Boolean {
+ var s = OtpAuthFixtures.SECRET;
+ var e = OtpAuthFixtures.entry(logger,
+ "otpauth://totp/Label:bob?SECRET=" + s + ";Algorithm=sha512;digits=8&period=60&image=https%3A%2F%2Fx&issuer=Big+Bank");
+ var item = OtpAuthFixtures.parse("otpauth://totp/Old:bob?secret=" + s + "&issuer=New");
+ if (e == null) {
+ return false;
+ }
+ return e.name.equals("Big Bank") && e.algorithm == Hashes.SHA512 && e.digits == 8 && e.period == 60
+ && item.entry != null && (item.entry as Entry).name.equals("New") && item.note != null
+ && e.account.equals("bob");
+}
+
+(:test)
+function otpauthHotpAndSteam(logger as Test.Logger) as Boolean {
+ var s = OtpAuthFixtures.SECRET;
+ var h = OtpAuthFixtures.entry(logger, "otpauth://hotp/VPN:dave?secret=" + s + "&counter=42");
+ var noCounter = OtpAuthFixtures.parse("otpauth://hotp/VPN:dave?secret=" + s);
+ var steam = OtpAuthFixtures.entry(logger, "otpauth://steam/Steam:erin?secret=" + s);
+ var encoder = OtpAuthFixtures.entry(logger, "otpauth://totp/Steam:erin?secret=" + s + "&encoder=steam&digits=5");
+ return h != null && h.type == Entry.OTP_HOTP && h.counter == 42l
+ && noCounter.problem != null && (noCounter.problem as String).find("counter") != null
+ && steam != null && steam.type == Entry.OTP_STEAM && encoder != null && encoder.type == Entry.OTP_STEAM;
+}
+
+(:test)
+function otpauthReportsEveryProblem(logger as Test.Logger) as Boolean {
+ var s = OtpAuthFixtures.SECRET;
+ var cases = [
+ ["otpauth://totp/A:b", "secret missing"],
+ ["otpauth://totp/A:b?secret=" + s + "&digits=9", "9 digits"],
+ ["otpauth://totp/A:b?secret=" + s + "&digits=x", "not a whole number"],
+ ["otpauth://totp/A:b?secret=" + s + "&period=0", "period 0"],
+ ["otpauth://totp/A:b?secret=" + s + "&algorithm=MD5", "algorithm"],
+ ["otpauth://totp/A:b?secret=ABC1DEFG", "position 4"],
+ ["otpauth://totp/A%2:b?secret=" + s, "% escape"],
+ ["otpauth://totp/?secret=" + s, "issuer or an account"],
+ ["otpauth://totp", "no '/'"]
+ ];
+ for (var i = 0; i < cases.size(); i++) {
+ var c = cases[i] as Array<String>;
+ var item = OtpAuthFixtures.parse(c[0]);
+ var problem = item.problem;
+ if (item.entry != null || problem == null || problem.find(c[1]) == null) {
+ logger.error("case " + i + ": problem " + problem);
+ return false;
+ }
+ }
+ var motp = OtpAuthFixtures.parse("otpauth://motp/A:b?secret=" + s);
+ return motp.skipped != null && motp.entry == null;
+}
diff --git a/tools/gen_fuzz_corpus.py b/tools/gen_fuzz_corpus.py
new file mode 100644
index 0000000..87d11ae
--- /dev/null
+++ b/tools/gen_fuzz_corpus.py
@@ -0,0 +1,114 @@
+#!/usr/bin/env python3
+# Author: cirakg & claude ai
+# Summary: Generate the seeded fuzz corpus for the import parsers: mutated Aegis JSON and otpauth URIs.
+"""Seeded fuzz inputs for the Monkey C import tests (README 17.3).
+
+Starts from small valid documents (fake data only) and applies random
+mutations: truncation, deletion, insertion of random or structural bytes,
+byte flips, duplicated spans, deep nesting and long strings. The output is
+deterministic for a given seed, so the corpus in git is reproducible:
+
+ gen_fuzz_corpus.py [--seed 4242] [--out test/data/FuzzCorpus.mc]
+
+Inputs are written as hex, because many aren't valid UTF-8; the tests feed
+the bytes to the tokenizer and the Aegis reader, and the valid-UTF-8 ones
+to the whole importer as text.
+"""
+
+from __future__ import annotations
+
+import argparse
+import random
+from pathlib import Path
+
+SECRET = "GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ" # RFC 4226 test key in Base32, not a real secret
+AEGIS_SEED = (
+ '{"version": 1, "header": {"slots": null, "params": null}, "db": {"version": 3, "entries": ['
+ '{"type": "totp", "uuid": "u1", "name": "alice", "issuer": "GitHub", "note": "", "favorite": true, '
+ '"icon": null, "info": {"secret": "' + SECRET + '", "algo": "SHA1", "digits": 6, "period": 30}, '
+ '"groups": ["g1"]}, '
+ '{"type": "hotp", "name": "b\\u00e9b", "issuer": "VPN", "info": {"secret": "' + SECRET + '", '
+ '"algo": "SHA256", "digits": 8, "period": 30, "counter": 7}}], '
+ '"groups": [{"uuid": "g1", "name": "Work"}]}}'
+)
+URI_SEEDS = [
+ "otpauth://totp/GitHub:alice%40example.com?secret=" + SECRET + "&issuer=GitHub&digits=6",
+ "otpauth://hotp/VPN:bob?secret=" + SECRET + "&counter=5;algorithm=SHA256",
+ "otpauth://steam/Steam:carol?secret=" + SECRET,
+]
+STRUCTURAL = [
+ *(b"{", b"}", b"[", b"]", b'"', b":", b",", b"\\", b"\\u", b"\\ud83d"),
+ *(b"null", b"-", b"1e9", b"%", b"&", b"=", b"?"),
+]
+
+
+def mutate(data: bytes, rng: random.Random) -> bytes:
+ """One to three random mutations of data."""
+ out = bytearray(data)
+ for _ in range(rng.randint(1, 3)):
+ choice = rng.randrange(7)
+ at = rng.randrange(len(out) + 1) if out else 0
+ if choice == 0 and out: # truncate
+ del out[at:]
+ elif choice == 1 and out: # delete a span
+ del out[at : at + rng.randint(1, 8)]
+ elif choice == 2: # insert random bytes
+ out[at:at] = bytes(rng.randrange(256) for _ in range(rng.randint(1, 4)))
+ elif choice == 3: # insert a structural token
+ out[at:at] = rng.choice(STRUCTURAL)
+ elif choice == 4 and out: # flip a byte
+ pos = rng.randrange(len(out))
+ out[pos] ^= 1 << rng.randrange(8)
+ elif choice == 5 and out: # duplicate a span
+ span = out[at : at + rng.randint(1, 30)]
+ out[at:at] = span
+ else: # deep nesting or a long string
+ long_string = b'"' + b"A" * rng.randint(520, 1100) + b'"'
+ out[at:at] = b"[" * rng.randint(10, 30) if rng.random() < 0.5 else long_string
+ return bytes(out)
+
+
+def corpus(seed: int, count_json: int, count_uri: int) -> tuple[list[bytes], list[bytes]]:
+ rng = random.Random(seed)
+ base = AEGIS_SEED.encode()
+ jsons = [base] + [mutate(base, rng) for _ in range(count_json - 1)]
+ mutated = [mutate(rng.choice(URI_SEEDS).encode(), rng) for _ in range(count_uri - len(URI_SEEDS))]
+ uris = [u.encode() for u in URI_SEEDS] + mutated
+ return jsons, uris
+
+
+def render(jsons: list[bytes], uris: list[bytes], seed: int) -> str:
+ def table(name: str, items: list[bytes]) -> str:
+ last = len(items) - 1
+ rows = "\n".join(
+ f' "{item.hex()}"{"" if i == last else ","} // lint: allow-literal' for i, item in enumerate(items)
+ )
+ return f" const {name} = [\n{rows}\n ];\n"
+
+ return (
+ "// Author: cirakg & claude ai\n"
+ "// Summary: Seeded fuzz inputs for the import parsers (generated by tools/gen_fuzz_corpus.py; do not edit).\n"
+ f"//\n// Seed {seed}. Hex because many inputs are not valid UTF-8. Fake data only.\n\n"
+ "import Toybox.Lang;\n\n(:test)\nmodule FuzzCorpus {\n"
+ + table("JSON", jsons)
+ + "\n"
+ + table("URIS", uris)
+ + "}\n"
+ )
+
+
+def main(argv: list[str] | None = None) -> int:
+ parser = argparse.ArgumentParser(description=__doc__.splitlines()[0])
+ parser.add_argument("--seed", type=int, default=4242)
+ parser.add_argument("--out", type=Path, default=Path("test/data/FuzzCorpus.mc"))
+ parser.add_argument("--json", type=int, default=50)
+ parser.add_argument("--uris", type=int, default=50)
+ args = parser.parse_args(argv)
+ jsons, uris = corpus(args.seed, args.json, args.uris)
+ args.out.write_text(render(jsons, uris, args.seed), encoding="utf-8")
+ print(f"wrote {len(jsons)} JSON and {len(uris)} URI inputs to {args.out}")
+ return 0
+
+
+if __name__ == "__main__":
+ raise SystemExit(main())
diff --git a/tools/tests/test_gen_fuzz_corpus.py b/tools/tests/test_gen_fuzz_corpus.py
new file mode 100644
index 0000000..5ce8c99
--- /dev/null
+++ b/tools/tests/test_gen_fuzz_corpus.py
@@ -0,0 +1,41 @@
+# Author: cirakg & claude ai
+# Summary: Tests for the fuzz corpus generator: determinism, mutation, valid Monkey C output, and the committed corpus.
+from __future__ import annotations
+
+import random
+from pathlib import Path
+
+import gen_fuzz_corpus
+
+ROOT = Path(__file__).resolve().parents[2]
+
+
+def test_same_seed_gives_the_same_corpus() -> None:
+ assert gen_fuzz_corpus.corpus(7, 10, 10) == gen_fuzz_corpus.corpus(7, 10, 10)
+ assert gen_fuzz_corpus.corpus(7, 10, 10) != gen_fuzz_corpus.corpus(8, 10, 10)
+
+
+def test_corpus_starts_with_the_valid_seeds() -> None:
+ jsons, uris = gen_fuzz_corpus.corpus(1, 5, 5)
+ assert jsons[0] == gen_fuzz_corpus.AEGIS_SEED.encode()
+ assert uris[: len(gen_fuzz_corpus.URI_SEEDS)] == [u.encode() for u in gen_fuzz_corpus.URI_SEEDS]
+ assert len(jsons) == 5 and len(uris) == 5
+
+
+def test_mutate_changes_the_input() -> None:
+ rng = random.Random(3)
+ base = gen_fuzz_corpus.AEGIS_SEED.encode()
+ assert sum(gen_fuzz_corpus.mutate(base, rng) != base for _ in range(20)) >= 19
+
+
+def test_render_puts_commas_outside_the_comments() -> None:
+ text = gen_fuzz_corpus.render([b"{}", b"[]"], [b"x"], 1)
+ assert '"7b7d", // lint: allow-literal' in text
+ assert '"5b5d" // lint: allow-literal' in text
+ assert text.startswith("// Author: cirakg & claude ai\n// Summary:")
+
+
+def test_committed_corpus_matches_the_generator() -> None:
+ jsons, uris = gen_fuzz_corpus.corpus(4242, 50, 50)
+ committed = (ROOT / "test" / "data" / "FuzzCorpus.mc").read_text(encoding="utf-8")
+ assert committed == gen_fuzz_corpus.render(jsons, uris, 4242)
diff --git a/tools/ui_flow.py b/tools/ui_flow.py
index f43cf81..3e82231 100644
--- a/tools/ui_flow.py
+++ b/tools/ui_flow.py
@@ -19,7 +19,8 @@ drawing each screen, so every step waits for the screen it expects instead
of sleeping. Menus are native Menu2 views that print nothing; for those the
script waits a fixed time.
-The flow covers: the list, a TOTP entry, a HOTP entry and its next code, the
+The flow covers: the results of an Aegis import sent through the settings
+form (the fixture sets the form, as the phone would), the list, a TOTP entry, a HOTP entry and its next code, the
PIN screen, a wrong PIN, a correct PIN opening a locked entry, auto-lock
closing it, the menu, the About screen (watches with buttons; touch-only
menus have no focus to move) and the lockout after 5 wrong PINs.
@@ -46,6 +47,8 @@ import sim_screenshot as shots
EXIT_OK, EXIT_DIFFERENT, EXIT_FLOW = 0, 1, 2
SCREEN_TIMEOUT_S = 20.0
AUTO_LOCK_TIMEOUT_S = 25.0 # the UI-test build locks after 10 s without input
+IMPORT_TIMEOUT_S = 90.0 # the 100-entry fixture import runs on a timer after start-up
+IMPORT_MEMORY_BUDGET = 0.70 # README 18: peak memory at most 70 % of the watch-app memory
MENU_SETTLE_S = 1.5 # Menu2 has no "shown" line
SETTLE_S = 0.4 # after a "shown" line, before the screenshot
CLICK_PAUSE_S = 0.25
@@ -225,6 +228,12 @@ class Flow:
else:
self.sim.swipe(self._w() // 2, self._h() * 3 // 4, self._w() // 2, self._h() // 4)
+ def prev(self) -> None:
+ if self.sim.has("up"):
+ self.sim.press("up")
+ else:
+ self.sim.swipe(self._w() // 2, self._h() // 4, self._w() // 2, self._h() * 3 // 4)
+
def back(self) -> None:
self.sim.press("esc")
@@ -305,9 +314,26 @@ class Flow:
self.results[name] = shots.check_golden(screen, label, out, golden)
+def flow_import(f: Flow) -> None:
+ """The fixture's 100-entry Aegis import through the settings form opens its
+ results; its peak memory must stay within the budget."""
+ f.expect("results", IMPORT_TIMEOUT_S)
+ stats = f.log.latest(r"DIAG import entries=(\d+) wall_ms=(\d+) mem_peak=(\d+) mem_total=(\d+)")
+ if stats is None:
+ raise FlowError("the import didn't report its statistics")
+ entries, wall_ms, peak, total = (int(v) for v in stats.groups())
+ fraction = peak / total
+ print(f"[{f.sim.device_dir.name}] import of {entries} entries: {wall_ms} ms, peak memory {fraction:.1%}")
+ if fraction > IMPORT_MEMORY_BUDGET:
+ raise FlowError(f"import peak memory {fraction:.1%} is over {IMPORT_MEMORY_BUDGET:.0%}")
+ f.shot("import_results")
+ f.act()
+ f.back()
+ f.expect("list")
+
+
def flow_entries(f: Flow) -> None:
"""The list, a TOTP entry, a HOTP entry and its next code."""
- f.expect("list")
f.shot("list")
f.act()
f.open_focused_row() # GitHub, TOTP
@@ -333,8 +359,7 @@ def flow_entries(f: Flow) -> None:
def flow_locked_entry(f: Flow) -> None:
"""Bank is locked: PIN screen, a wrong PIN, the right PIN, then auto-lock."""
- for _ in range(6): # wrap around to Bank (index 2 of 7)
- f.next()
+ f.prev() # from VPN back to Bank
f.act()
f.open_focused_row()
f.expect("pin")
@@ -391,6 +416,7 @@ def flow_lockout(f: Flow) -> None:
def run_flow(f: Flow) -> None:
"""The scripted session; see the module docstring."""
+ flow_import(f)
flow_entries(f)
flow_locked_entry(f)
flow_menu(f)