diff options
| author | cirakg <cirakg@cirakg.xyz> | 2026-10-21 03:55:01 +0200 |
|---|---|---|
| committer | cirakg <cirakg@cirakg.xyz> | 2026-10-21 03:55:01 +0200 |
| commit | a69e23fb5266dd942d125239e7dcbe8a7c8ab956 (patch) | |
| tree | 164c7ed21b550ac3d5fef567060ffad4e40e4583 /test | |
| parent | 6039571d70b1f6de1ae384e58e11879869be356c (diff) | |
perf(import): keep parsed items packed and the pasted text once
A parsed import item with its Entry took about 760 bytes as objects on the
Forerunner 255S; packed into one Codec ByteArray it takes about 110, and
the importer unpacks one item per store step (wiping the packed secret).
Aegis group names are resolved when unpacking, so no step repacks all
items. The JSON is parsed from the untrimmed text's bytes and the string
is dropped; an incomplete part is saved from those bytes. The form hash
is computed before the importer copies the text.
The peak of a 100-entry import on the 255S falls from 56 % to 45 %.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'test')
| -rw-r--r-- | test/import/AegisTest.mc | 6 | ||||
| -rw-r--r-- | test/import/FuzzTest.mc | 2 | ||||
| -rw-r--r-- | test/import/ImportSupport.mc | 9 | ||||
| -rw-r--r-- | test/import/ImporterTest.mc | 59 |
4 files changed, 71 insertions, 5 deletions
diff --git a/test/import/AegisTest.mc b/test/import/AegisTest.mc index eaad039..1644e5d 100644 --- a/test/import/AegisTest.mc +++ b/test/import/AegisTest.mc @@ -30,7 +30,7 @@ function aegisReadsDatabaseVersion3(logger as Test.Logger) as Boolean { logger.error("error " + reader.error + ", items " + reader.items.size()); return false; } - var it = reader.items; + var it = AegisFixtures.items(reader); var github = it[0].entry; var bank = it[1].entry; var vpn = it[2].entry; @@ -69,7 +69,7 @@ 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; + var e = reader.items.size() == 1 ? reader.item(0).entry : null; if (e == null || !e.group.equals("Private")) { logger.error("database version " + v + " failed: " + reader.error); return false; @@ -98,7 +98,7 @@ function aegisReportsBadEntriesAndDocuments(logger as Test.Logger) as Boolean { "{\"type\": \"totp\", \"name\": \"z\", \"info\": {\"secret\": \"" + AegisFixtures.SECRET_1 + "\", \"digits\": 6.5}}" ]; var reader = AegisFixtures.read(AegisFixtures.export(3, entries, "")); - var it = reader.items; + var it = AegisFixtures.items(reader); if (reader.error != null || it.size() != 4) { logger.error("error " + reader.error); return false; diff --git a/test/import/FuzzTest.mc b/test/import/FuzzTest.mc index 7573ffa..fcad345 100644 --- a/test/import/FuzzTest.mc +++ b/test/import/FuzzTest.mc @@ -50,7 +50,7 @@ function fuzzAegisReaderGivesDefiniteResults(logger as Test.Logger) as Boolean { if (status == AegisReader.DONE) { done++; for (var k = 0; k < reader.items.size(); k++) { - var item = reader.items[k]; + var item = reader.item(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"); diff --git a/test/import/ImportSupport.mc b/test/import/ImportSupport.mc index ba891a7..1c84ec3 100644 --- a/test/import/ImportSupport.mc +++ b/test/import/ImportSupport.mc @@ -36,6 +36,15 @@ module AegisFixtures { } // Runs the reader to the end (or a non-running status). + // Every parsed item, unpacked. + function items(reader as AegisReader) as Array<ImportItem> { + var out = [] as Array<ImportItem>; + for (var i = 0; i < reader.items.size(); i++) { + out.add(reader.item(i)); + } + return out; + } + function read(text as String) as AegisReader { var reader = new AegisReader(Bytes.fromUtf8(text)); for (var i = 0; i < 10000; i++) { diff --git a/test/import/ImporterTest.mc b/test/import/ImporterTest.mc index 411e528..3443f4e 100644 --- a/test/import/ImporterTest.mc +++ b/test/import/ImporterTest.mc @@ -1,5 +1,5 @@ // 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. +// Summary: Tests for the import job: reports, duplicates, replacements, locking, Replace mode, multi-part buffer, failures, a full store, 100 entries, packed items. import Toybox.Lang; import Toybox.System; @@ -206,3 +206,60 @@ function importerHandlesOneHundredEntries(logger as Test.Logger) as Boolean { logger.debug("BENCH import_100_aegis ms=" + elapsed + " steps=" + steps + " json_bytes=" + text.length()); return job.report.imported == 100 && vault.ids().size() == 100; } + +// Packed items (M8 memory) keep every field, resolve the group when +// unpacked, and leave no second copy of the secret. +(:test) +function importItemsPackAndUnpack(logger as Test.Logger) as Boolean { + var e = new Entry(); + e.type = Entry.OTP_HOTP; + e.name = "Bänk €"; + e.account = "bob"; + e.favorite = true; + e.algorithm = Hashes.SHA512; + e.digits = 8; + e.period = 60; + e.counter = 5000000000l; + var secret = [1, 2, 3, 4, 5]b; + e.secret = secret; + var item = new ImportItem(7); + item.label = e.name; + item.note = "issuer differs"; + item.groupRef = "uuid:g-1"; + item.entry = e; + var packed = item.pack(); + var wiped = secret[0] == 0 && secret[4] == 0 && e.secret == null; + var back = ImportItem.unpack(packed, {"g-1" => "Work"} as Dictionary<String, String>); + var b = back.entry; + if (!wiped || b == null) { + logger.error("wiped " + wiped + ", entry " + b); + return false; + } + var fieldsOk = back.index == 7 && back.label.equals("Bänk €") && "issuer differs".equals(back.note) && back.problem == null + && b.kind == Entry.KIND_OTP && b.source == Entry.SOURCE_IMPORT && b.type == Entry.OTP_HOTP && b.name.equals("Bänk €") + && b.account.equals("bob") && b.favorite && b.algorithm == Hashes.SHA512 && b.digits == 8 && b.period == 60 + && b.counter == 5000000000l && Bytes.equal(b.secret as ByteArray, [1, 2, 3, 4, 5]b) && b.group.equals("Work") + && b.validate() == null; + + var bad = new ImportItem(3); + bad.label = "x"; + bad.problem = "secret missing"; + bad.groupRef = "name:Private"; + var badBack = ImportItem.unpack(bad.pack(), {} as Dictionary<String, String>); + var named = new ImportItem(4); + named.groupRef = "name: Private "; + var namedEntry = new Entry(); + namedEntry.name = "Mail"; + namedEntry.secret = [9]b; + named.entry = namedEntry; + var namedBack = ImportItem.unpack(named.pack(), {} as Dictionary<String, String>).entry; + var missing = new ImportItem(5); + missing.groupRef = "uuid:nope"; + var missingEntry = new Entry(); + missingEntry.name = "Mail"; + missingEntry.secret = [9]b; + missing.entry = missingEntry; + var missingBack = ImportItem.unpack(missing.pack(), {} as Dictionary<String, String>).entry; + return fieldsOk && badBack.entry == null && "secret missing".equals(badBack.problem) && badBack.index == 3 + && namedBack != null && namedBack.group.equals("Private") && missingBack != null && missingBack.group.equals(""); +} |
