b2cb42caf51e8495780e5892b56a64a725bb4d37
179 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
40c1e0f148 |
v3.22.0: the event action uses the Simulator's forms (all modelled events)
The test-script event action edited its fields as a JSON textarea and only accepted the four events events.py builds; every other event needed the raw envelope action with hand-copied hex. It now renders the same EVENT_FORMS form the Phone tab's send dialog uses, for every modelled event (29; no release gating - a script may target another card). - PWA: the field renderer/collector is shared (eventFormFieldsHtml / eventFormValues / eventFormPatternError / eventFormSrc / eventFieldGroupsUpdate); the Phone tab keeps its ids, its release gating and its onchange updaters, the step editor uses test-f-ev- ids, the generic group updater and a live Event data (hex) preview with the one-ENVELOPE check. The event select lists all EVENT_FORMS events (the numeric keys are decimal strings - parseInt(k, 10)); the action stores the collected fields, the built data and the event's source (fixed cfg.src or the cfg.srcField input - the action never carried src before, so MT call / IMS registration / slices could not be network-sourced). The JSON textarea is gone. - engine: the `event` action keeps `fields` validation for the four server-built events (a stray data hex is ignored) and accepts the form-built `data` for every other event - hex-validated and checked against events.EVENT_DATA_MAX (244, the one-ENVELOPE budget; server.py's _EVENT_DATA_MAX now reads it from events.py). The runner builds the four at run time (fresh host clock) and sends the stored data for the rest. - tests: the engine's data path (accepted / missing / malformed / oversized, the four still fields-built), the runner sending the data (fake scc), the editor's collect/prefill/patterns/source (Node), the shared helpers. Docs: test-scripts.md, api.md, help EN/RU. Tests: 838 frontend + 797 Python; sw cache simple-v401. |
||
|
|
5976ef89b4 |
v3.22.0: the test suites (scripts grouped, ordered, with setup/teardown)
A suite is the root object: every script belongs to exactly one suite, the
suite holds the ordered member list by reference - the optional setup first,
the members, the optional teardown last - and runs them in one card session,
without resets and without resume.
- test_suites.py: the suite store (test_suites.json, --test-suites), validated
and atomic like the script store: roles normalised (setup first, teardown
last, at most one of each, a script appears once per suite), per-member
on_fail stop/continue and the suite-level require_adm.
- test_scripts.py schema v2: suite_id/require_adm; list_by_suite()/orphans();
the import reports the id_map a bundle import remaps with. v3.21.0 scripts
without a suite are attached once to an auto-created "Imported scripts"
suite (migrate_scripts_to_suites, called from __main__).
- API: /api/test/suites (+ update|delete|import - delete refused while
non-empty; the bundle import imports the scripts first, remaps the suite
references and skips unresolvable ones); the scripts endpoints gain
suite_id/require_adm plus move|copy (delete detaches from the suite first;
a suite update refuses a membership change).
- runner: /api/test/run {suite_id} resolves the members and the preset, runs
the ADM gate, then setup -> members (per-member on_fail) -> teardown. The
teardown always runs on a stop or a failure (skipped only when the card is
gone; it clears the stop flag for its duration); a card reset stops the
suite, skips the remaining members and is flagged. Members' live steps land
in the shared snapshot, their records (status/steps/note/log excerpt/
counters) in _TEST_RUN['suite']; the summary carries the counts, wall time,
preset, the SCP80 counters before/after per keyset and the session flags.
_test_log buffers the semantic lines (bounded) for the report's per-member
excerpts.
- ADM gate (_ensure_adm): a suite/script require_adm verifies the matched
preset's key once per card session (the /api/verify-adm badge sets the same
latch); a failure refuses the start, stamps the session (no automatic
retry) and needs a manual verify.
- PWA: the Test script pill becomes four views - the suite list (default),
the suite editor (the script list: role badges, on_fail, up/down, per-script
Run/Edit/Move/Copy/Delete, Add script with the templates), the script editor
(the step list; a single run shows its result under the steps and is not
preserved) and the suite report (per-member blocks with the step table,
notes and run log; summary; export JSON/Markdown). Suite bundles export/
import; the legacy script export imports into "Imported scripts". EN/RU.
- tests: the suite store/migration, the runner orchestration (order, on_fail,
setup failure, stop, card reset, teardown, per-member logs) and the ADM gate
(Python); the suite HTTP paths; the PWA report/Markdown/wiring (Node).
Tests: 833 frontend + 794 Python; sw cache simple-v397.
|
||
|
|
e6ced47323 |
v3.21.0: the test-runner tolerance batch + the proactive cleanup action
Five reported issues, plus the action that makes an explicit drain possible: - on_fail is honoured by every step failure: an expectation with no pending command (or a FETCH that returns nothing) fails as a Command/Fetch check at the step's level instead of raising past it; the default still stops the run, on_fail "warning" continues (the tolerant consume-if-pending form). - the run cleanup answers ok (0x00) and keeps draining while the TR answers 91XX (bounded); a cancel is only sent when a script asks for it - it tells the card the user aborted the session and pushes assertive applets onto an error path. - new `proactive` action: STATUS rounds consume every announced command (respond/first TERMINAL RESPONSE results, attempts/interval_ms bound) until the card answers 9000; an optional require asserts at least one drained command (an empty drain fails it), the final SW is the step's SW check and a still-pending command is handed to the next step. PWA editor + summary + validation, EN/RU help, docs, action-kind drift test. - the PoR response form follows the command's TAR: _tar_is_rm (preset role entries + the TS 101 220 Annex D ISD/RFM allocations) decides; a non-RM TAR's reply is always raw (TS 102 226 4) - no fabricated status word even when the bytes happen to parse (01 00 0B 91 ... used to read as SW 000B). - a secured data too short for the compact structure (a 2-byte applet reply) is recovered by _por_header_parse (the decode minus the compact parse; a malformed packet or a failed RC/CC/DS check still yields no por) and reported raw, so short applet replies are assertable via por.data. Docs: test-scripts.md, api.md, help EN/RU, repo AGENTS. sw cache simple-v396. Tests: 825 frontend + 755 Python green. |
||
|
|
85cc4b9535 |
v3.20.0: the server-owned SCP80 counter (an untouched field = the preset's next value)
The counter an operation consumed was re-entered by the PWA on every send (the form field was filled from the preset and posted back), so a stale tab could send a value the card rejects (cntr_low), and typing in the field silently wrote the preset. Now the store is the single source of truth and the form field is a per-send override (Option B). - _preset_counter_seed: an explicit `cntr` in the request wins (a hand send, the counter probe or a pre-built packet may deliberately pick one); otherwise the preset store's value for the packet's keyset. Wired into /api/send-ota (the APDU path; the pre-built `sp` path keeps the caller's bookkeeping value - the packet carries its own counter), /api/ram-install, /api/ram-install-app, /api/cap-compat, /api/tar-probe and /api/sp-verify (the reference must use the counter the JS packet was built with). - the send-ota response echoes the effective `cntr` (a caller that omitted one learns what was used) and the OTA SEND log line prints it. - persistence is unchanged and monotonic (presets.set_counter refuses backwards writes), pinned by tests: an explicit lower counter is used for the packet but never lowers the store, a higher accepted one is saved. - PWA: the Secured packet counter field starts empty with the preset's next value as its placeholder (auto: <cntr>); typing affects that send only and is never written to the preset (spCntrSyncPreset is gone), spParams / getRamSpParams omit an empty counter, the RAM/send flows sync only the local preset copy (forward-only) instead of filling the field, the +/- buttons start from the effective value, and the preview/verify packet is built from the effective counter (genSp). Placeholder translated EN/RU. - the runner and /api/counter-probe keep their existing seeding (the probe already starts at the failed value and walks above it). - docs: api.md's optional-`cntr` contract + seeding notes on every endpoint, help EN/RU 3.1. Tests: 822 frontend + 734 Python; sw cache simple-v393. |
||
|
|
0d63e5592f |
docs: a refused envelope may carry no response packet (guide + help)
The alfa-dsa control-commands run showed the error case: an applet-level refusal (envelope 6200 for an unknown command) carries no PoR at all, so a por expectation on such a step can never match. The authoring guide's status-words section now states that a 62XX/63XX envelope does not guarantee a response packet - other warning paths still deliver one (the low-counter case arrives via SEND SHORT MESSAGE) - and that a step expected to be refused should leave por at its "any" default or assert "none"; help EN/RU notes the same. docs/test-scripts.md only, no code change; sw cache simple-v392 (help files are precached). |
||
|
|
78bc91d297 |
v3.19.2: the three-status-words disambiguation (envelope SW vs por.sw)
The script author read por.sw as the envelope SW, so a por.sw expectation on an applet's own TAR failed with (none). - docs/test-scripts.md: a 'The three status words (envelope SW / PoR status / R-APDU SW)' table in the authoring section - where each one comes from, the script field and the report row - plus a cross-reference from the por check paragraph; help EN/RU 8.9 points at it. - a failed por.sw check on a response_type: 'raw' decode carries the check detail 'the response is application data - an applet's own TAR has no R-APDU SW; assert it with data' (the PWA renders check details). - the runner's log prints APP DATA=<hex> (no R-APDU SW) for application responses instead of the misleading R-APDU SW=(none) data=...; the compact/scripting lines are unchanged. - the step editor's custom-PoR block gets a one-line hint about the same. Tests: 821 frontend + 730 Python; sw cache simple-v391. |
||
|
|
4a353720d0 |
v3.19.0 (review fixes): expose the menu-select Match case choice; doc/comment fixes
Review of
|
||
|
|
7250dbd63a |
v3.19.0 (cont.): live fixtures; menu text match is case-insensitive by default
No test scripts have been authored yet, so the format is still free to
change - the menu-select text match is now case-insensitive by default
(menu labels are UI text; case_sensitive: true still forces an exact-case
match, and a miss on it names the case-insensitive candidates).
Verification against the live card's own bytes (captured 2026-10-04):
- the Annex A Variant 2 Cyrillic menu (ids 128..130) resolves by text and
the case behaviour above is pinned ('кофе' -> item 130);
- the applet's SEND SHORT MESSAGE (alpha 'Отправка...', SMS-SUBMIT to
+79332505884) pins the new alpha and sms checks and the run-log line.
docs/help/api updated; sw cache simple-v388 (help pages changed).
Tests: 819 frontend + 726 Python.
|
||
|
|
92506e72b9 |
v3.19.0: the applet-agent script batch - por objects, alpha/sms checks, scp80 format
The test scripts get the pieces an applet test needed:
- scp80 check.por accepts an object {status?, sw?, data?} asserting the
decoded PoR of this very exchange (inline or via SEND SHORT MESSAGE);
the evaluation is shared with the expect-step por check
(_por_check_results). The editor's PoR select gains 'custom' with the
three fields (an empty object refuses on save).
- a new alpha content check reads the command's Alpha identifier (05/85,
Annex A coded) - the text check stays bound to the 8D Text string, so a
command carrying both keeps them distinct.
- a new sms content check asserts a SEND SHORT MESSAGE's submit TPDU:
DA / PID / DCS / UDL / UD (the parsed fields are logged and reported;
an applet's own SMS, not the OTA PoR the por check decodes).
- scp80.params.format wraps the C-APDU in the TS 102 226 5.2.1 Command
Scripting template (expanded = AA..., expanded-ae = AE 80...00 00); the
log shows WRAPPED=, the report C-APDU=<effective> format=<fmt>.
- docs: Worksheet 1 addresses an RFM TAR (B00000/B00001/B00010 compact,
B00120/B00140/B00130 expanded) with the own-TAR and MSL/spi1 notes, and
file-write is documented as a direct APDU that does not fire
EVENT_REMOTE_FILE_UPDATE.
Tests: 819 frontend + 724 Python; sw cache simple-v387.
|
||
|
|
c444468d2a |
v3.18.3: the test-run card-exchange log
Verifying a script from the log was impossible: the runner wrote no card
lines, SCP80's plaintext C-APDU / secured packet / PoR landed only in the
report, and a menu selection logged nothing (the interactive path showed just
"STATUS poll (menu-select 9000)" / "STATUS -> 9000").
Every step now writes a `TEST-RUN step N:` line family to stderr, request
before response:
- generic actions: the APDU/raw event data sent and the SW/RESP received
(`APDU TX=...`, `ENVELOPE(Event Download) ... data=...`, `READ/UPDATE ... -> SW=...`);
- SCP80: `C-APDU=` (or `SP=`), `SECURED=<hex> (N B) TAR=... SPI1=... SPI2=... CNTR=...`,
the response SW, and the PoR - `PoR[inline] status=... TAR=... CNTR=... raw=...`
plus `R-APDU SW=... data=...` (with the bad-format note);
- expectations: `FETCH=<apdu> -> SW=... <raw>`, `CMD 0x.. <NAME> qual=..`,
`TR=<apdu> -> SW=...`, and `PoR[sms-submit] ...` + `R-APDU` for the SEND
SHORT MESSAGE transport;
- status: `STATUS 1/N -> <sw>` per attempt;
- menu selection: `MENU-SELECT ENVELOPE=<apdu> item=<id> ('<text>') -> SW=...`
(both the runner action and /api/menu-select; the old STATUS lines stay as
context).
The shared secured-packet sender logs each segment's answer
(`OTA SEND: ENVELOPE i/N -> <SW>`), the scp80 step's report `sent` now carries
the C-APDU/SP hex, and `--apdu-trace` remains the raw transport view.
help EN/RU and docs/test-scripts.md gained the "reading the run log" note.
Tests: 816 frontend + 718 Python; sw cache simple-v385.
|
||
|
|
9109b68924 |
docs: help/README catch up with the UI (pill count, event preview, store saving)
The audit of the help and READMEs against the current UI found four stale pill enumerations and five undocumented behaviours: - The Simulator tab has had five pills since the BIP pill landed; the help (EN/RU) and both READMEs said "four" and omitted BIP. All four now name it (the help links §8.10). - Test-script help: the envelope action's source override (82/83), the event action's fields being a JSON object in the step editor, edits being written to the store as you change them (no Save button; an incomplete draft stays local), and the templates wording adjusted. - Subscribed-events help: the form's "event data (hex)" preview and how to copy it into a raw envelope test step. - SCP80 help: the counter probe's amber outcome (a security verdict after a counter rejection saves the value and warns). - Cards help: the key table's "Counter" row aligns with the editor's "Counter (hex)" heading. sw cache simple-v384 (help files are precached). |
||
|
|
fb4e4bd7b2 |
v3.18.0: the semantic event action for test scripts
The other-applet batch: a test script can send the common event downloads semantically instead of raw hex. - events.py: server-side mirrors of the PWA's event forms for Location status (0x03), Access technology change (0x0B), Network rejection (0x12 - the LA/RA/TA object selected by the registration group, the extended EMM cause and the extended information object) and Data connection status change (0x1D - status/type/TI plus the optional cause, host-clock 8.39 date-time, location information, access technology, APN and PDP/PDN/PDU type). Fields are validated before the run (a bad value is a script error, not a step failure); `src` overrides the device-identities source (default terminal, matching the Phone forms). - The engine gains the `event` action kind; the runner builds and sends it. The PWA's action-kind list stays in sync (drift-guarded - the guard itself caught its own regex bug on the first run). - Byte parity: the vectors are generated from the PWA's own builders and pinned in tests/test_events.py; the frontend suites assert the same values. PLMN and SCTS helpers are mirrored too. - The Phone tab's event form shows the built "Event data (hex)" (updated as the fields change), so every event without a builder can be used through the raw `envelope` action. - The test-step editor offers the event select + a JSON fields textarea; malformed JSON refuses the save with a clear message. - docs/test-scripts.md (the semantic-event table, an event example, the format stamp), docs/api.md and help EN/RU updated. Tests: 812 frontend + 717 Python; sw cache simple-v381. |
||
|
|
0b9e9e4c5e |
v3.17.0: test scripts for the applet handoff - menu by text, spec-order TRs, codings
The applet-testing batch agreed after v3.15/3.16:
- menu-select by text: the step names the item by `text` (+ `mode`
exact/contains, `case_sensitive`) instead of the install-dependent id
(both or neither is refused); the runner resolves it against the card's
cached menu and raises a step error naming the available items when nothing
(or several items) matches. The cache is refreshed on every SET UP MENU
fetch - the proactive chain and the expect step - so an applet install (new
items/ids after its REFRESH) no longer leaves a stale menu behind. The
step editor offers the id or the text + mode and the client-side pre-check
bounds them.
- TERMINAL RESPONSE object order: both builders emitted the Item identifier
before the Result (and the scripted builder its Text string before it too);
TS 102 223 6.8.0 mandates Command details, Device identities, Result,
Duration, Text string, Item identifier. `testscript.build_tr` and
`_menu_send_response` now follow it; `respond.raw` is documented as
appended after the standard objects, and the exact-byte tests pin the order.
- Text codings: the scripted response codes the GSM default alphabet via the
GSM 03.38 codec ('00' packed, '04' 8-bit) instead of latin-1, keeps '08'
UCS2 and sends the spec's null text string for an empty answer; the packing
helper is shared with the interactive path (`testscript.pack_gsm7`). A
UCS2 GET INPUT answer is documented and tested.
- docs/test-scripts.md gained the handoff brief for the applet agent: the
value-collection table, fill-in worksheets for the two applet scenarios, a
menu-by-text example, the validation checklist and a format stamp; help
EN/RU and docs/api.md updated.
Tests: 809 frontend + 705 Python; sw cache simple-v380.
|
||
|
|
8430dd1694 |
v3.16.0: test scripts move to a server-side store
The scripts lived in the browser's localStorage although only the local
server can run them. The new test_scripts.py store
(~/.pysim-simple-server/test_scripts.json, --test-scripts PATH) sits next to
the card presets: stable uuids and timestamps, atomic writes, and every
mutation is validated with the runner's own engine (the submitted shape is
kept - the normaliser's output is not re-normalisable: it fills defaults
like an 'any' PoR check that the check normaliser then rejects), so a stored
script cannot fail at run start for a validation reason. Import accepts the
old localStorage export as-is (export/import remains the migration path; the
PWA import also takes an exported array).
- /api/test/scripts (GET card-free: path/count + scripts), POST create,
/update, /delete and /import serve the store; the endpoints answer 503
without a store and 500 on a write failure, like the preset endpoints.
- /api/test/run takes a stored script_id (resolved when no inline script is
sent); the PWA sends the edited copy inline plus the id for the report,
and the run snapshot carries script_id.
- The PWA's Test script pill loads from the store, writes through on edits
(debounced name, immediate step/new/copy/delete), reports load/save/delete
failures inline, and exports the portable {name, steps} shape.
Tests: 808 frontend + 701 Python; sw cache simple-v379.
|
||
|
|
abc9ea2751 |
v3.15.0: test-script fixes for applet testing - preset id, kvn, PoR and file-list checks
Three gaps blocked SCP80 applet testing from the PWA: - testRunPreset() still marshalled the pre-v3.8 flat preset fields (kic/kid/keys/counter/tar/spi1/spi2, all empty on a stored preset), so every SCP80 run failed with "SCP80 preset is incomplete: no keyset is defined". The PWA sends the matched preset's id now and the server resolves it from the store (_test_run_preset_from_body; the inline `preset` body stays for API callers), so a run always uses the stored keysets, TARs and counters. Regression tests on both sides. - testscript._normalise_params dropped the scp80 step's `kvn` (the editor exposed it and the runner and validation consumed it, so scripts silently used the preset's first keyset). kvn is validated (1-15) and passed through; the client-side problem check bounds it too. - The PoR delivered as a SEND SHORT MESSAGE (PoR-in-submit, SPI2 bit 0x20) is decoded for the expect step that fetches it: the new `por` content check asserts the response status / R-APDU SW / response data, decoded with the preceding scp80 step's context (concatenated SMS accumulate across expectations). The inline transport stays on the action (check.por / check.data). - The new `files` content check asserts the File List of a REFRESH (TS 102 223 8.18, tag 12/92) - e.g. qualifier 00 = NAA initialization with full file change notification - as an order-insensitive set of FID paths. The step editor exposes both checks; help EN/RU, docs/api.md and the new authoring reference docs/test-scripts.md document the format. Tests: 803 frontend + 680 Python; sw cache simple-v378. |
||
|
|
c18b6cd0d4 |
v3.14.0: event form object-set fixes + the slices single-envelope mandate
An audit of every event form against the pinned specs (31.111 7.5.x/9.3, 102 223 8.87, TS 101 220's master tag table) found three missing objects, three malformed/wrong ones, and settled the slice-data scope. - 0x00 MT call: the IMS URI object is cut at 100 bytes and now carries the URI truncated object (8.135, F3 00) when the cut happened. - 0x1F Slices: the form gains the Partial NSSAI object (8.154, F9, rel 18); a single envelope carrying it is marked as the Last Envelope (8.79, F0 00) automatically (7.5.27.2). - 0x06 Card reader status and 0x07 Language: the builders now emit their object tags (A0 01 ..., AD ...) - they emitted bare values before. - 0x18 Incoming IMS data: the IARI tag is F6 (76/F6), not F7 (which is the IMPU list tag - the two-column 9.3 table had been misread). - Single-envelope mandate: the event data is capped at 244 bytes (inner 252; the D6 long-form length and the APDU Lc stay in one byte). The server refuses more with a 400 and the PWA shows the size error before sending. Chained (multi-envelope) delivery, the More Data object and envelope sequences are not implemented - documented in the help and the repo AGENTS. - Tests: event form vectors (URI truncation, the restored tags, partial / Last Envelope, the size helper) and the server guard; the release reference and its PWA/code mirrors gain the three objects and the corrected IARI key. - sw cache simple-v375. |
||
|
|
cc480c30b7 |
v3.13.3: ELF version retry for both P1 values + query-based per-AID budget
elfTagListRetry walks the untried tag-list forms for P1=10 and P1=20 (skipping combinations the main pass already refused via the expandedTried map; only the tag-list forms are retried - the no-tag-list form never carries CE - and the walk stops as soon as a version appears). The form memory can skip the tag-list forms for P1=10 when the Apps listing served only the no-tag-list form (live 2026-10-02: EP BAP - its P1=10 tag lists were never queried while the P1=20 retry refused them). elfPerAidVersions filters to packages that actually have an applet candidate before the six-query cap: the budget counts queries, not list positions (EP BAP's first six packages are Oracle/JC-API entries with no app instances - they consumed the cap and the A113 packages were never queried). Docs: help EN/RU, repo AGENTS (v3.13.3 note). sw cache simple-v373. Tests: 667 Python + 796 frontend green. |
||
|
|
2c49c54d2d |
v3.13.2: ELF versions derived through the package's module AIDs
ramDeriveElfVersions matches an applet instance to its package through a shared ramElfAppMatch: the app's C4 (Load File AID) when the card reports the real one, then the package's module AIDs (the applet instance AIDs), then the shared-prefix fallback (the same tiers ramElfAppletCandidate uses for the per-AID queries). Live 2026-10-02 (test-eSIM): the card returns C4 = the applet's own AID (c4 10 ...3908 for package ...3900), so the old C4-only derivation filled nothing; the module lists map ...3900 -> ...3908 (9.3), ...0200 -> ...0208/...494D4508 (1.1) and F0414C46416001 -> F0414C4641600101 (0.1). The ELF version hint names the remaining case: a package without an applet instance has no version to derive either (the Oracle A00000006203010101 stays bare - the card refuses every ELF tag-list form, so its CE is unreachable). Docs: help EN/RU, repo AGENTS (v3.13.2 note). sw cache simple-v372. Tests: 667 Python + 796 frontend green. |
||
|
|
5b8324ed02 |
v3.13.1: expanded-registry pages try every command format
The explorer's next-occurrence page (P2=03) is retried in the other command formats (ramPageFormats: the expanded codings first - the serving one first when it is expanded - and the compact command string last, because a compact page attempt can consume the page without delivering it) and the format that serves a page is kept for the following ones. Live 2026-10-02 (test-eSIM card): a compact page 2 answered 61B6 and the chained GET RESPONSE 6A86, after which the AA and AE 80 retries both saw the occurrence already gone; the AA-wrapped page returns 182 B with the remaining app instances' CE/CC/EA, while AE 80 is refused. A page that no format serves ends the listing. Only an explicit 6310 promise counts for the "(first page only)" flag (ramExpandedMore): a probe after a 61xx page that every format refuses is the card's final answer, so that card's ELF listing is no longer flagged (its expanded registry has three entries; the fourth package exists only in the compact listing - the ELF tag-list forms and per-AID queries are refused in every format). In Auto the explorer also queries entries the bulk listing left without expanded fields individually by AID (ramAppsMissingDetails, <= 6 packets, the compact command string carries the GET RESPONSE for 61xx). Docs: help EN/RU, repo AGENTS (v3.13.1 note). sw cache simple-v371. Tests: 667 Python + 794 frontend green. |
||
|
|
4f0b2a743c |
v3.13.0: the Simulator's event forms honour the preset's CAT release
The consumption half of the release guard: the event-download forms hide the parameters introduced after the effective release and say so. - catEffectiveReleaseInfo(cards, iccid): the equipped card's preset (ICCID match, the same rule the SCP80 views use) resolved through presetRelease(), else the default Rel-12. - eventFieldsForRelease(cfg, rel): a field whose introduction release (`rel`) is above the effective one is hidden; a field without a known release is never hidden. The EVENT_FORMS annotations verified against the pinned specs/histories: the MT call IMS URI (Rel-12, CR 12.4.0 "URI support in MT Call event"), the CSG cell selection PLMN ID (Rel-12, 12.1.0), the IMS registration IMPU list + status code (Rel-11), the slices S-NSSAI-mapping and rejected objects (Rel-18). - The form notes what was hidden: "hidden parameters: N · introduced after Rel-12 · preset: X" (or "default release"), with a tooltip naming the source; the send path already skips absent fields, so no builder changes. - help EN/RU; sw cache simple-v369. |
||
|
|
3660a7e173 |
v3.12.0: the card preset carries the card's CAT (USAT) release
The release selector lives in the preset rather than a global chip: presets already travel with the card (ICCID auto-selection, export/import), and the operations that will consume the release are preset-aware. - presets.py: the optional `release` field (4-18; empty = unspecified) joins PRESET_FIELDS; a value outside 4-18 is refused on save/import; the store schema version is 5. - catrelease.py: DEFAULT_RELEASE = 12 and preset_release() - the effective release is the preset's own value, else Rel-12 (the safe level for cards whose release is not known; an unset release never filters anything away). The PWA mirrors both (CAT_RELEASE_DEFAULT + presetRelease()), drift-guarded. - Cards tab: the editor's header row gains a compact CAT release select (unspecified / pre-Rel-6 stored as 5 / Rel-6 ... Rel-18); the empty option names the Rel-12 default and the tooltip explains the guard; the field is collected, cleared and restored with the other form fields. - Nothing filters yet: the Simulator's event parameters (stage 3) will use the effective release. - docs/api.md + help EN/RU updated; sw cache simple-v368. |
||
|
|
5a3c4c3529 |
v3.11.0 review fixes: ADM length, TAR re-render hygiene
- the ADM input capped the key at 8 chars, but the server's _verify_adm takes 4-16 hex digits (padded to 8 CHV bytes) and the old input had no limit; maxlength=16 now, with a static guard in html.test.js. - cardsTarAdd/cardsTarRemove re-rendered the table from the save-path reader, so an emptied role field was silently refilled with its default. The renderer no longer invents defaults; a raw reader (cardsTarsFromTable) feeds Add/Remove and the language-switch refresh, while the save path (cardsTarsFromForm) keeps the default-filling for the mandatory roles. - the editor's dynamic TAR/keyset rows now rebuild on a language switch (refreshDynamicI18n) from what is typed - their placeholders and titles carry t() strings. (The title/Save label were already refreshed there.) - dropped the now-unused cardsRoleMsl helper and its test references. - docs: the ADM is documented as 4-16 hex digits (help EN/RU, api.md, AGENTS). sw cache simple-v362. Tests: 665 Python + 784 frontend green; the whole inline PWA script syntax-checked. |
||
|
|
65c565e058 |
v3.11.0: Cards tab redesign (list + editor), preset PIN/PUK codes
The Cards tab now shows the preset list by default; "Add preset" / "Edit" open a separate editor view (Save stores and returns to the list, Cancel discards; ramGoToPreset opens it too). The editor keeps the name/ICCID header (with From card) and groups its fields into bordered blocks: - PIN/ADM: the new optional card codes PIN1/PUK1/PIN2/PUK2 (plain text, 4-8 decimal digits) plus ADM, moved out of the top row; nothing consumes the PIN/PUK codes yet. - TAR: one uniform table (TAR, MSL, description); the first three rows are the fixed role entries with the immutable English descriptions "ISD compact format" / "UICC shared FS RFM compact format" / "ADF FS RFM compact format"; further rows are deletable with an optional description. - SCP80 (keyset editor) and SCP81 (PSK pair) as before. Store: PRESET_FIELDS gains pin1/puk1/pin2/puk2 (optional; whitespace stripped; 4-8 decimal digits validated on save and import), SCHEMA_VERSION 4, docs/help/AGENTS updated. TAR rows use unified ids (cards-tr-N-*), the editor switches via cardsShowList/cardsShowEditor. Tests: 665 Python + 783 frontend green; the whole inline PWA script syntax-checked. sw cache simple-v361. |
||
|
|
6360dc5bae |
v3.10.0: preset TAR table with per-TAR MSL; derive the SPI1 from the TAR
Presets drop the card-wide SPI1/SPI2 and the fixed tar/uiccTar/usimTar fields: a preset now carries `tars` - the three mandatory role entries (isd/uiccRfm/usimRfm, spec defaults pre-settled) plus free TARs with an optional description, each with its MSL (Minimum SPI1, TS 102 226 8.2.1.3.2.4). v3.9.1 presets convert on load/import (the spi1 seeds all three MSLs); duplicate TARs and empty TAR/MSL values are refused; the store schema version is 3. Operations derive a packet's SPI1 from its TAR's MSL (_spi1_for_tar): the RAM ops, the test runner (per-step overrides still win), the counter probe and each TAR-probe row; the plain send keeps an explicit SPI1 with a `warning` when it is below the MSL, and a TAR outside the preset without an explicit SPI1 is refused with a hint. The TAR probe resolves per checked TAR before sending and reports the effective spi1 per row. The secured packet form's SPI2 default is 00 (the RAM/probe operations pin their own). PWA: the Cards form edits the TAR table (role rows with MSL inputs plus Add/Remove free rows with a description), the list shows a TARs (MSL) column, the SCP80 TAR field suggests the preset's entries (datalist) and a preset apply seeds the SPI1 from the operation TAR's MSL with an amber warning below it; the preset-wide SPI1/SPI2 fields and the header marker's SPI requirement are gone (completeness = a keyset + the three role entries with their MSLs). Help EN/RU, docs/api.md and AGENTS updated; sw cache simple-v358. Live: TAR probe 000000 at its MSL 16 (registered, por_ok, counter advanced); a send without spi1 derived 16 (por_ok, counter advanced); a send with spi1 0A returned the warning and the card refused it (6200, no counter consumed); an unknown TAR without spi1 was refused with the hint. |
||
|
|
00181ba3ad |
Infer the card's ETSI release from its installed uicc.toolkit version
The explorer's card-resources block now names the card's ETSI release, using the same mapping as the CAP analysis (capmem.ETSI_TOOLKIT_RELEASE: uicc.toolkit version -> REL-N, basis in brackets; the live card's 1.12 -> REL-15/16/17). The table is mirrored in the PWA (drift-guarded by tests/test_cap_memory.py, like JC_FRAMEWORK_SDK); the version comes from the expanded registry's CE tag and degrades to the version hint when the listing has none; SIM-only cards (no uicc.toolkit) get no label, the same rule as SIM-only CAPs. - cardEtsiRelease(elfs) (pure) + UICC_TOOLKIT_AID / ETSI_TOOLKIT_RELEASE in the PWA; rendered after the free-memory lines with a tooltip naming the basis. - Tests: helper vectors (1.12/1.11/1.5, unknown version, versionless, SIM-only), the rendering guard, and the table/AID drift guard. - Help EN/RU; sw cache simple-v354. |
||
|
|
6e4eb5932c |
Expanded listings follow the command format; registry gets a format fallback
The TCA loader's Explore traces settle the format question: it sends the same P2=00 listing queries wrapped and unchained (`AA0A2208 80F24000024F0000`, pages `...01024F0000`) - no GET RESPONSE anywhere - while the live card needs the chain and answers 6700 to the wrapped unchained form; and the live ISD refuses the compact P2=02 registry query (6700) while serving it wrapped (`AA11220F 80F2400209 4F005C054F9F70C5CC00` -> 6310 + entries with CE versions). Both the listing and the registry queries now follow the operation's command format and fall back across formats. - ramFormatIsExpanded(fmt) + ramFmtLadder(runFmt, working) (pure): the run's format first, then expanded, expanded-ae and compact as the last resort; a format that served data is tried first and never repeated. - ramGetStatusApdu(p1, p2, fmt): the chain exists in compact only (TS 102 226 5.2.1.1) - an expanded run sends the bare query. - paginate: the P2=00 listing follows the run's format and pins it per send, so a GET RESPONSE can never ride inside an expanded packet. - expandedPass: each registry form goes through the format ladder; the chained 61xx retry is compact-only (it was guarded for 'expanded' but not 'expanded-ae'); the serving format is remembered for the remaining listings and named in the report ([AA]/[AE 80]); ramExpandedGroups carries it. - Tests: the helper + ladder vectors, the unchained expanded listing builder (both codings), the fallback/chain source guards, the report marker. - Help EN/RU, the repo AGENTS.md; sw cache simple-v353. |
||
|
|
391d4a322d |
Low-counter reporting, counter sync probe, Bad-format decode, expanded forms
Live findings that drove this: the "card counter 0000000000" the low-counter notice showed is not the card's counter at all (the PoR's CNTR is a copy of the command's counter, TS 102 225 5.2 table 3, and a rejection leaves it zeroed); and an earlier "no card accepts the expanded format" observation was a hand-made template whose declared length did not match its body - the card validates the lengths and answers with a Bad format TLV. - _cntr_low_fields(status) reports the verdict as a flag; card_cntr/ suggested_cntr and the UI's "(card counter ...)" text are gone - the value is not observable. The notice offers a bounded Sync counter probe instead. - POST /api/counter-probe: a doubling counter ladder (start+1, +2, +4, ...) with a read-only command until the card accepts one, then accepted+1 is persisted (monotonic, source counter-probe). Rejected attempts change nothing on the card; the search stops at a ceiling (default FFFFFFFF, far below the 40-bit maximum where the counter blocks) and after max_attempts (default 40, clamped 1-64); a non-cntr_low failure stops it at once. - _parse_response_scripting decodes the Bad format TLV (90, TS 101 220 table 7.20; error types per TS 102 226 table 5.12) and returns a dict; the PoR carries bad_format/bad_format_name and _ram_step_result fails the step with "bad format 02 (wrong length)" instead of falling through to the compact decoder (which read the AB tag as 171 commands). - _wrap_expanded_apdu(apdu, form) emits the definite (AA) or the indefinite (AE 80 ... 00 00) scripting template; the RAM command formats are compact/ expanded/expanded-ae and _ram_detect_format probes compact, then both expanded codings (the expanded probes use Le='00' and no chained GET RESPONSE, TS 102 226 5.2.1.1). The RAM form gains a Format selector. - Live-verified: the verdict carries no value; the probe walked 0x1000001/0x1000003 (low) then 0x1000007 (accepted, stored 0x1000008); the ISD executes both expanded codings (R-APDU 08A0000000030000000F80, SW 9000) and a length-mismatched template answers bad_format 02 - decoded now. - Tests: the ladder, the probe walk + persistence, params validation, both template forms, the Bad-format TLV (definite and plain tag styles), the live ISD response through _decode_por, the step-result failure, and the PWA guards; help EN/RU, docs/api.md, the repo AGENTS.md; sw cache simple-v352. |
||
|
|
69abbe2fa5 |
feat: the conditional event objects are complete
The six forms that still missed conditional/optional objects now carry them: - Location status (0x03): Location Information (MCC/MNC + LAC + Cell ID) - included only for Normal service, as TS 102 223 8.19 requires. - Channel status (0x0A): Bearer Description (B5, type + parameters) and Other address (BE, IPv4 dotted / IPv6 hex / the empty "dynamic address" form); new eventIpBytes() helper. - Call disconnected (0x02): IMS call disconnection cause (D5, SIP/Q.850 + 2-byte cause). - Network rejection (0x12): Extended information (F2, CAG ID / NID / RedCap). - CAG cell selection (0x1E): CAG Human-readable network name list (D7, 80-tagged names). - Slices status change (0x1F): Allowed slices with S-NSSAI mapping (F7), Allowed slices (F8), Rejected slices with mapping (D7) and Rejected slices (B1) beside the served list. Every implemented event form now carries all the objects of its spec table. Tests: the new build vectors. Help EN/RU and both AGENTS files; sw simple-v350. |
||
|
|
7061f42781 |
feat: the remaining spec-defined event forms (data/display/search/frames, IMS/CAG/slices)
Group 1 (TS 102 223): Data available (Channel status B8 + channel data length B7), Display parameters changed (rows/columns + sizing/fonts/effects, C0), Network search mode change (manual/automatic, E5), Browsing status (browser error code, E4) and Frames information change (default frame + per-frame info, E7). Group 2 (TS 31.111): IMS registration (IMPU list as 80-tagged URIs + IMS status code), Incoming IMS data (IARI), CAG cell selection (status + mechanism, access technology, CAG information list) and Slices status change (allowed / rejected / served / partially, access technology, served S-NSSAIs) - all with source network. Still unimplemented by design: 0x0D Local connection, 0x16 Contactless state request (3GPP: not required) and the eCAT containers 0x19/0x1B; 0x04/0x05/0x13/ 0x1A are data-free, where the one-click send is spec-correct. Tests: the new build vectors. Help EN/RU, both AGENTS files; sw simple-v349. |
||
|
|
a26a2b6648 |
feat: the remaining six event types get their parameter forms
MT call, Call connected, Call disconnected, Browser termination, (I-)WLAN access status and CSG cell selection no longer fall back to the generic "send a bare notification" prompt: - MT call (TS 31.111 7.5.1A.2): transaction identifier, calling party number + TON -> Address (EFADN BCD coding, encBcdDigits/eventAddressTlv), subaddress, IMS URI, media type; source network. - Call connected (7.5.2A.2) / Call disconnected (7.5.3A.2): transaction identifier, near/far-end source select, media type, and a cause - absent, the zero-length radio-link-timeout form or custom bytes (the hex field shows only for "custom"). - Browser termination (TS 102 223 7.5.9.2): user / error cause. - (I-)WLAN access status (TS 31.111 7.5.1.2): no coverage / coverage / connection on-going. - CSG cell selection (TS 31.111 7.5.3.2): status + selection mechanism, optional access technology, CSG ID, HNB name and PLMN ID; source network. The device-identities source (TS 102 223 8.7: 82 terminal / 83 network) is now selectable: EVENT_FORMS carries a fixed `src` or a `srcField`, and _send_event_download()/`/api/event-send` take an `event_src` override (an invalid value falls back to the terminal). Tests: the BCD/Address builders, each event's build vectors, the "no not_yet left" guard and the server source override. Help EN/RU, both AGENTS files; sw cache simple-v348. |
||
|
|
994ee6d2a3 |
ui: the Data Connection event gains the date-time object; the "more" flag is precise
- The 0x1D event form now offers the Date-Time and Time zone object (TS 102 223 8.39): an optional select that includes the host clock's date-time, built by eventDateTimeTlv() as seven semi-octet BCD bytes in the TS 23.040 9.2.3.11 order (units high, tens low) with the time zone in quarters of an hour and its sign in bit 3 of the first semi-octet. - The Cards list's empty-state colspan is 8 now (the table has had 8 columns since the keyset refactor, not 10). - The expanded-registry "(first page only)" note marks only an unfulfilled 6310: ramExpandedMore() keeps the promise set when the follow-up page was refused/failed or the page cap stopped the paging, and clears it when an accepted page ended the listing (a completed multi-page listing was annotated wrongly before). Tests: eventDateTimeTlv (fixed dates, both timezone signs), the 0x1D build with and without the date-time, and ramExpandedMore's cases. Help EN/RU and both AGENTS files; sw cache simple-v347. |
||
|
|
0cccd62230 |
release: v3.9.0 - the operation start buttons wait for a preset and a keyset
Gating: the SCP80 "Send to Card", the RAM Execute and the TAR-probe Execute buttons carry `data-needs-keyset`; pysimApplyAvailability() disables them (with the hint "Select a card preset and its keyset" when the card itself is fine) unless the view's preset is selected and the form's keyset number is one of its keysets - `spKeysetStartReady`, the same rule the click-time guards enforce, which stay as the last line of defence. spKeysetSync() refreshes the gate on every preset/keyset change, and the KIc/KID index selects now call spKeysetSync() so the keyset selector follows a manual index change too. Version 3.9.0: server VERSION, pyproject.toml and the PWA header; sw cache simple-v346; the workspace AGENTS gains its v3.9.0 history entry. Tests: indicator.test.js drives the gate (no preset -> disabled with the hint, an undefined keyset -> disabled, a matching keyset -> enabled) and guards the three buttons' attribute; the cards/keysets/ram sandboxes stub pysimApplyAvailability. Help EN/RU notes the disabled start buttons. |
||
|
|
39723406b4 |
ui: the preset list highlights the equipped card's preset
The Cards list now marks the row whose ICCID matches the equipped card (cardsFindByIccid handles digits / spaced / raw EF hex alike): an emerald row tint plus a small dot with a "The equipped card's preset" tooltip in the Name cell. There is no highlight while the ICCID is unknown (no card, unreadable EF.ICCID) or when no preset matches - the amber banner already covers that. pysimCardStateUpdate() re-renders the list when the ICCID actually changes, so a visible Cards tab follows equip/removal live (opening the tab re-renders anyway through switchTab). Tests: cards_iccid.test.js drives cardsRender with a fake tbody - the matching preset is highlighted (both ICCID forms), the others stay plain and no ICCID means no highlight; card_state.test.js stubs cardsRender. Help EN/RU and the repo AGENTS updated; sw cache simple-v344. |
||
|
|
12da7ed593 |
ui: the keyset selector is disabled and cleared while no preset is selected
A keyset only exists inside a card preset, but the SCP80/RAM/TAR keyset
selectors stayed enabled with no preset selected - showing either
"— no keyset —" or a stale "Keyset N — not defined in the preset" entry left
over from an earlier preset. spKeysetSync() now disables the selector and
reduces it to a "— select a card preset —" placeholder whenever its view has
no preset selected, and re-enables/populates it as soon as one is.
Clearing the preset dropdown ("— Select card —") also refreshed nothing:
cardsApply() returned before the sync, so the stale keyset list stayed; it now
syncs the selectors (and the preset warning) on the empty selection too.
The three selects get disabled:opacity-40 disabled:cursor-not-allowed (the
rules are already in the compiled style.css). Tests: keysets.test.js covers
the disabled/cleared state and the re-enable plus a cardsApply source guard;
cards_form.test.js and cards_counter.test.js gain tarPresetIdx in their
sandboxes. Help EN/RU and both AGENTS files; sw cache simple-v343.
|
||
|
|
cafec549fa |
feat: the Data Connection Status Change event gets its parameter form (0x1D)
EVENT_FORMS[0x1D] was missing, so its Send button posted event_data: null - the ENVELOPE carried only the event list and device identities, none of the event's data objects. The form now builds the TS 31.111 7.5.25.2 object set (tags per TS 101 220 / TS 31.111 9.3): data connection status (9D), connection type (AA), optional (E/5G)SM cause (AE), transaction identifier (1C), optional location information (13, CR flag 0 as the spec requires for this command), optional access technology (BF), location status (9B), optional APN (C7) and PDP/PDN/PDU type (0B). Adjacent spec fixes found while verifying: - the Access Technology Change options (0x0B) did not match the pinned TS 102 223 V18.3.0 8.61 coding (GSM Compact / GSM w-EGPRS / UTRAN w-HSDPA ...); they now use the shared ACCESSTECH list, whose two labels were also corrected (TIA/EIA-553-A, TIA/EIA-136-270); - _send_event_download() wrote the D6 length as a single byte - a long APN would overflow it; 128..255 bytes now use the BER long form (81 <len>). Tests: the 0x1D build (defaults, full form, fallbacks), the 8.61 option list, the long-form D6. Help EN/RU and both AGENTS files; sw cache simple-v342. |
||
|
|
0114c4748b |
ui: the STK menu button shows the title without the "STK: " prefix
The Simulator view's "STK menu" block already carries the legend, so the emerald button's hardcoded "STK: " prefix was redundant. It now shows just the card's menu title (e.g. "Alfa Mobile"), or "Menu"/"Меню" for a title-less SET UP MENU (`stkMenuLabel`, unchanged). The help pages (EN/RU) describe the button as the menu title, both AGENTS files drop the old `STK: Menu` wording, and stk_menu.test.js gains a source guard against the prefix returning. sw cache simple-v341. Tests: 743 Node / 619 Python. |
||
|
|
9f7876b5fa |
docs: help/README catch up with v3.8.0; keyset-number wording; RU "пресет"
Documentation:
- help (§1/§3): the SCP80 tab is switched by three pills - the tab list and the
section intro now name the TAR probe (RU "TAR-сканер"); §3.1/§3.2 describe the
Keyset selector next to the preset selector (and that a successful PoR
advances the counter only when SPI1 carries a counter check); §4 describes
the keyset editor rows, the derived number and the per-keyset counters; the
probe section keeps its counter rule.
- README/README_RUS: the SCP80 tab says three pills and gains a "TAR probe" /
"TAR-сканер" section (standard list, verdicts, counter persistence); the
Cards section is rewritten for the server-side store - keysets, server-owned
per-keyset counters with the audit trail, JSON export/import as the migration
path, the keyset editor, the equipped-card mismatch banner - and the field
table, "Add a card" and "Edit / remove" follow.
Terminology:
- "key version" is no longer used for SCP80 keysets in the UI, the server error
strings and the docs: the selector is "Keyset", its options "Keyset 1 - ...",
the messages say "keyset N ...". The TS 102 225 A.1/A.2 quotes and the GP
PUT KEY P1 field keep the spec's own wording.
- RU keeps the word "keyset" untranslated ('Добавить keyset', 'Номер keyset',
"не является keyset пресета", ...) and uses "пресет" instead of
"предустановка" throughout the dictionary, the help and README_RUS.
sw cache simple-v340. Tests: 742 Node / 619 Python.
|
||
|
|
18e705a8c3 |
feat: TAR probe becomes its own SCP80 pill with a TAR checklist (v3.8.0)
The probe is not a RAM operation, so it moves out of the RAM view into a third SCP80 pill (Secured packet | RAM | TAR probe, RU "TAR-сканер"), with its own preset and key-version selectors, its own equipped-card mismatch banner and its own result line; the help gains a "3.3 TAR probe" section and the anchor map follows the pill. The TAR field is a checklist now: pre-populated with the standard allocations of TS 101 220 V18.3.0 Annex D, Table D.1 (each row carrying the document it comes from, all checked by default, Select all / Select none), plus custom TARs added through a small form (three hex bytes + an optional description, removed with a x). Custom entries live in localStorage['simple_tar_probe'] on a best-effort basis - losing them is harmless, the standard list is always there. Only the checked TARs are sent; nothing checked is refused with a message. A Python test guards the PWA's list against the server's TAR_PROBE_TARS (the endpoint keeps its own default for API callers). RAM leftovers removed (the tar-probe operation entry, the #ram-tar-rows block, the ramExecute dispatch, ramTarProbe); getRamSpParams() takes the view's selector id; tarRender/tarApplyCard/tarKeysetChanged/tarKeysetGuard mirror the RAM view; spKeysetSync and spPresetWarningRender cover the new selectors. sw cache simple-v338. Tests: 740 Node (the checklist rows, the custom round-trip, the checked-list read, the renderer, the pill wiring) and 619 Python (the list drift guard). |
||
|
|
6eeb8dacb3 |
feat: TAR probe + the SPI1 counter-tracking rule; keyless packets build again (v3.8.0)
Three things learned from probing a live card, plus the capability they enabled:
1. Counter tracking follows SPI1.b5b4 (TS 102 225 5.1.1): with 00 the counter
is "present, ignored, never updated", so a packet without a counter check
must leave every counter untouched. _counter_tracked() now guards
/api/send-ota (no final_cntr, no persistence, a log line), the RAM step
sender and the test runner; _preset_counter_persist() requires a non-zero
key version (the "single keyset" fallback could advance a keyset spuriously
for a keyless send). The PWA advances the form counter only when the server
reports final_cntr and says "counter not tracked" otherwise.
2. The spec's no-security form builds again: _ota_keyset() refused the valid
KIc/KID '00' (TS 102 225 A.2: '00' is legal with SPI1.b3=0 for KIc and
SPI1.b2b1=00 for KID) because pySim wants an algorithm. An unknown nibble
is now refused only when the algorithm is actually used (ciphering / a
ciphered PoR for KIc, RC/CC/DS / a MAC'd PoR for KID) and 'implicit' codes
the nibble as 0 otherwise - POST /api/send-ota {apdu, spi1:"00", kic:"00",
kid:"00"} now builds 0D00010000B0000000000000010000A40000, the exact packet
the card accepted (a fake 15/15 was refused with 6200). A no-security PoR
is decodable again too, and a requested-but-missing PoR sets por_missing
on the plain path as well.
3. TAR probe (POST /api/tar-probe, RAM op "Probe TARs"): one secured packet per
TAR - the standard allocations of TS 101 220 V18.3.0 Annex D, Table D.1
(TAR_PROBE_TARS), or a custom list - carrying a harmless C-APDU (SELECT MF)
with the preset's keyset and SPI 16/01. Each TAR is classified
(registered / no_answer / no_por / refused) from the ENVELOPE verdict and
the application's PoR, counters advance and persist normally, and the run is
refused when SPI1 has no counter check. Live on a card: 000000, B00000 and
B00001 registered (compact-format applications, answering 6D00 / 6B00),
the expanded-format TARs, USAT, multiplexing and CASD refused with 6200 -
with the keyless form the same card accepted every TAR and answered no PoR.
Docs (api.md, help EN/RU, AGENTS), sw cache simple-v337. Tests: 618 Python
(no-security build, the algorithm-only-when-used rule, the tracking rule, the
persist guard, the probe classification) and 736 Node (the probe renderer and
wiring, spCounterTracked).
|
||
|
|
386958b261 |
feat: warn when the equipped card and the selected preset disagree (v3.8.0)
Autoselection normally keeps the SCP80/RAM preset in step with the card, so
the mismatch is easy to miss. Both views now carry an amber banner above the
preset selector (the existing warning style - no new Tailwind classes):
- the equipped card matches no preset: "The equipped card <ICCID> does not
match any card preset - the selected preset may belong to another card.
Add it in the Cards tab (From card)"; with no presets at all the banner says
so instead;
- a preset is selected but it is not the equipped card's: a softer line naming
the matching preset ("check that its keys and counters belong to this card").
`spPresetWarning(iccid, presets, selectedIdx)` is pure (unit-tested with the
ICCID forms the store accepts: digits, spaced, raw EF hex), `spPresetWarningRender`
fills both banners and runs on every card-state poll, preset load, preset
selection and page init. No card session or an unreadable ICCID shows nothing
(the card-iccid gating already explains that state); the banner warns only -
it never blocks a send (preparing a packet for another card stays possible).
Tests: frontend/tests/preset_warning.test.js (5), the card-state/preset
harnesses stub the DOM-bound renderer. sw cache simple-v336.
|
||
|
|
cc2edb3d8a |
feat: one keyset per key version - indexed keys and counters (v3.8.0)
A card usually holds several OTA key sets, and the SCP80 counter is tracked
per key version on the card, so a preset now carries one keyset per
GlobalPlatform key version - the b8..b5 nibble of KIc/KID (TS 102 225
5.1.2/A.2), the version is implicit in the bytes. The spec is explicit:
"a dedicated counter shall be associated to each key version" (Annex A.1) and
"the key version number indicated in the KIc and KID (bits b8..b5) fields
shall be identical when different from 0" - a mismatch is rejected by the card
with "Unidentified security error" (A.2).
Server (presets.py, schema 2):
- a preset carries `keysets: [{kic, kid, kicKey, kidKey, cntr}]`; validation
refuses a KIc/KID version mismatch (A.2), a version outside 01-0F ('00'
means "no security" and is a packet-level choice, not a keyset), duplicate
versions and missing keys/counters; a v3.8.0 flat preset converts into one
keyset on load and on import;
- `set_counter(..., kvn)` writes the counter of that keyset (monotonic per
keyset), the audit line carries the kvn, and `find_keyset()` serves the
lookups;
- the operations derive the key version from the request's KIc byte and
persist into that keyset: /api/send-ota, the RAM install/install-app/compat
steps (_ram_send_gp_apdu) and the test runner's SCP80 steps (which may name
a `kvn` per step and keep one working counter per keyset);
- a non-zero key version the named preset does not define, or a KIc/KID
mismatch, is refused before anything is built (_preset_keyset_check /
_keyset_guard: 400, or a step error in the test runner). Without a
preset_id only the mismatch rule applies.
PWA:
- the Cards form's KIc/KID/counter/keys inputs become a keyset editor (one row
per version: KIc, KID, both keys, that version's counter, Add/Remove; the
version is shown derived from the bytes and a mismatch is flagged);
- the SCP80 and RAM views get a Key-version selector beside the preset
selector, driving the KIc/KID bytes and loading that keyset's keys and
counter; the packet builder refuses a version the preset does not define
(spKeysetCheck/spKeysetGuard, pure and unit-tested);
- the counter syncs are per keyset (spCntrLocalSync/ramSaveCntr), the SCP80
form's counter edit posts the keysets, and the test-run write-back syncs
every counter the server reports (scp80_counters).
Docs/help/AGENTS updated; sw cache simple-v335. Tests: 610 Python (store
keysets, endpoint refusals, the counter landing in the right keyset) and 726
Node (keysets.test.js plus the updated harnesses).
|
||
|
|
795a1fea49 |
feat: card presets move to a server-side store; the server owns the SCP80 counter (v3.8.0)
The presets lived in the browser's localStorage and the PWA wrote the SCP80 counter back after every operation, so an increment could be lost when the response never reached the tab, when two browser contexts (or the demo origin plus a local one) held separate copies, or when the tab was closed mid-run. The counter is card state and the server performs every card operation, so the store now lives next to the server: - pysim_simple_server/presets.py: one JSON file (~/.pysim-simple-server/card_presets.json, --card-presets overrides; Path.home() resolves the same way on Windows), atomic writes (temp + fsync + os.replace), an RLock, stable uuid ids, the ICCID normalisation the PWA uses (digits / spaced / raw EF hex), a duplicate-ICCID refusal and a monotonic set_counter; every counter change is appended to card_presets.json.audit.jsonl. - API: GET /api/presets (card-free), POST /api/presets and /api/presets/update|delete|import; the import also takes the old localStorage export shape - that is the migration path, there is no automatic migration. - Operations name a preset_id and the server persists the counter they consumed: /api/send-ota (the plain SCP80 path reports final_cntr now too), the RAM install/install-app/compat steps (_ram_send_gp_apdu) and the test runner's SCP80 steps (_preset_counter_persist). - PWA: the Cards tab is a thin client of the store (load on connect, CRUD via the API, export/import), the counter write-backs became local display syncs (the SCP80 form's counter input stays a deliberate human edit) and the SCP80/RAM bodies carry preset_id; a new pysimFetchOk() surfaces the server's error bodies instead of silently using them. Policy: card keys in logs and local files are not a risk for this workbench tool (AGENTS.md) - the store is not encrypted and no redaction is required. Tests: tests/test_presets.py (store: ICCID forms, counter monotonicity and wrap, atomic writes, corrupt-file recovery, import), the preset endpoints in tests/test_fetch_routes.py (incl. the counter persistence via /api/send-ota) and the updated PWA tests. sw cache simple-v334. |
||
|
|
162930eb96 |
feat: low-counter guard for every SCP80/RAM operation (v3.6.58)
A cntr_low PoR verdict is not recoverable by retrying - every packet whose
counter is not above the card's is rejected again - so it now stops the
operation and is stated as plain data instead of a generic PoR error.
Server:
- _cntr_low_fields(status, cntr) -> {cntr_low, card_cntr, suggested_cntr}
(fixed-width wrap like the counter field); carried by /api/send-ota (plus
an 'OTA CNTR-LOW: card counter ...' log line) and by the RAM install /
install-app / cap-compat failure responses (from the failed step);
- _send_envelope's late-PoR poll now also runs after a warning ENVELOPE
answer (62xx/63xx - the live card answers 6200 for a low counter) and
_send_secured_packet passes the captured PoR on, so the verdict is named
instead of a bare 'ENVELOPE failed' (that is why it only surfaced on the
later SPI2=0x21 query in the live log).
PWA:
- spCntrLow(res) guards every flow; the Explore routes the whole run through
a checked sendOta(), so the paginate/expanded loops stop, no further
packet is sent and no counter is written back (delete and Send to Card
check it too);
- the result states the card's counter and offers 'Go to preset'
(ramGoToPreset: opens the failed operation's preset in the Cards tab for
editing and focuses the counter field) - nothing is written
automatically; the operation modal carries the same notice;
- help (EN/RU) documents the guard.
sw cache simple-v331; tests: 557 Python / 714 Node.
|
||
|
|
aa4bf8e008 |
feat: expanded registry on by default, form ladder + fallback report (v3.6.54)
The 'Fetch expanded registry details' checkbox is now on by default: the
expanded pass carries the package versions, the SD/load-file associations
and the applet menu entries; an explicit stored opt-out is still honoured.
Live 2026-09-30: a card with a non-default ISD AID answered 6A88 to the
reference expanded query (P2=02 + the tag list), so the ELF rows had no
versions. Cards differ in what they accept, so the query is now tried in
three forms (RAM_EXPANDED_FORMS):
tags - the reference tag list (incl. the TS 102 226 EA SCP Registry Data)
noea - the same list without EA (a card that errors on it)
notags - no tag list at all (GPPro's 4F00-only data field); the card
builds the default E3
The first form that answers is remembered for the run and tried first for
the later listings; the ISD gets a single attempt - its 6A88/6A86 is normal
('no data to report', GPPro) and must not poison the detection for the
apps/ELF queries.
Every listing's outcome is reported in the explorer (ramExpandedReport):
'Expanded registry: tag list + EA (Apps, ELFs) - ISD: no expanded data
(6A88) - compact listing used', or 'Expanded registry refused by the card -
compact listing only (package versions unavailable)'; the ELF section hints
when no version came back. Tooltip/help (EN/RU) updated.
sw cache simple-v327; tests: 550 Python / 707 Node.
|
||
|
|
ee747003f2 |
feat: CAP compatibility test in the popup + per-AID failure analysis (v3.6.52)
The compatibility test now runs in the same operation modal as the installs
(live progress from the server's ram_progress, Close disabled until done) and
its result gains a per-AID detail block instead of the comma list:
- the probe summary (probe: 2 imports -> 0.0 . +1 appended);
- the import list as tested (ramCompatImportRows): the CAP's imports with the
version the card was asked for and the CAP's own version when the probe
lowered it, appended synthetic entries and unmatched lines - each with the
resolved library name (jcAidName), so a raw AID list becomes readable rows;
- on a rejection, the failure analysis (ramCompatFailureHtml): which CAP
section the failing block covers (load-file byte range and component names,
'completes Import'), what the card checks at that point (JC VM spec 4.5.2)
or that the import list already passed, the remote status word with its
name, and what the probe state implies (every import at 0.0 -> a missing
package or another load-file check is the cause; otherwise lower the
versions and bisect).
ramCompatVerdict names the remote SW; the inline result keeps the verdict +
short probe note and the modal carries the escaped step lines + the detail
HTML (ramOpModalFinish gained an asHtml mode); the server reports
probe_unmatched as [{'aid', 'version'}] (_probe_unmatched), documented in
docs/api.md. The same failure block for the real install is the next step.
sw cache simple-v325; tests: 550 Python / 703 Node.
|
||
|
|
599d84fd66 |
feat: Explore exposes the rest of the registry data + install NV footprint (v3.6.51)
The compact (legacy) GET STATUS entry is <aid_len><AID><life cycle> <privileges> + (P1=10) <module count> (<len><module AID>)*, cross-checked against GlobalPlatformPro's populate_legacy (the 4th byte is privileges; 0x00 is required for load files). The parser read it as a 'version' one byte late, so ELF rows showed the module count / the next entry's length as Version: 00/10 - the compact listing has no version at all; it now comes from the expanded CE tag (ramFmtVersion: 2 bytes -> major.minor, GP Table 11-37 Note 1) and merges (the bogus compact value no longer blocked it). New data exposed: - EA SCP Registry Data (TS 102 226 8.2.1.6) -> 80 Menu Parameters -> the applet's STK menu entries: STK menu entry: 0x80 (position 1, Enabled) - unknown registry objects are preserved (rawTlvs) and shown as 'Other registry data: 8F=752B . 90=012D ...' instead of being dropped - 84 parses the GP coding (one Executable Module AID per TLV, repeated; the concatenated 4F<len><aid> form stays as fallback) and the ELF expanded queries add 84 to the tag list (with a tag list the card returns only the listed tags - the fallback path would otherwise lose the module lists and their Install buttons) - life cycle names decode per object kind (GP Tables 11-3..11-6: ISD/card, app, SD - detected via the Security Domain privilege - and ELF), with multi-byte 9F70 (02 0700 on live cards) decoding the first byte and surfacing non-zero extras; the R-APDU parser's LIFECYCLE_MAP labels were aligned with the same tables /api/ram-install measures the NV footprint: a silent GET DATA FF21 read before and after the chain (also on failure) returns nv_before/nv_after/ nv_delta; the reads never fail the operation (no FF21 -> no fields) and the PWA shows 'NV used: ~N B (free A -> B)' in the install result. sw cache simple-v324; tests: 548 Python / 698 Node. |
||
|
|
0dc79a3ba4 |
feat: CAP compatibility test as its own operation + append-only imports (v3.6.48)
The import probe no longer lives in 'Install Package (.cap file)': a
diagnostic that can leave a LOADED-only package does not belong in the
operation meant to produce a working applet. /api/ram-install no longer
takes probe_imports; the install form's probe fieldset is gone.
New RAM operation CAP compatibility test (POST /api/cap-compat): the
server runs the format check, INSTALL [for load] and the LOAD blocks only
up to the block that completes the Import component (_cap_import_end +
_cap_compat_blocks, computed from the load file and the C4 TLV framing),
then stops - no last-block flag, no INSTALL [for install], so nothing is
committed and nothing needs deleting. The verdict (imports_ok,
boundary_block/total_blocks) states it covers the LOAD/import gate only;
the link gate stays the real install's business. Verified on the real
RemMobileID CAP: the boundary is LOAD 1/64, 2/128, 3/240 for 240/120/64 B
blocks - exactly the blocks where the live cards rejected the load.
_probe_import_versions(load_file, overrides, additions) now supports
append-only synthetic imports: +AID=version appends a new entry, the
Import component's size field and the Directory's Import size are patched,
and existing entries keep their positions (constant-pool import indices
stay valid); an AID that is already imported becomes a version override.
This is the card-capability query ('does this card export package X at
>= Y?') without building a probe applet.
PWA: new op section (CAP file + analysis + the version list + block size),
ramCompatTest with inline results and ramCompatVerdict, ramProbeParse
understands +AID=version; help EN/RU documents the operation, the two-step
bisect and the gate scope.
sw cache simple-v321; tests: 538 Python / 686 Node.
|
||
|
|
cf9c9b7bab |
feat: clean up the Explore UX (v3.6.45)
- Explore runs inline: no blocking modal (that stays for the install operations, which can leave a half-loaded package; Explore leaves the card consistent). A test guards that ramExplore never calls the modal. - Minimal success line: just OK - the details live in the explorer below; error runs keep the detailed 'Partial - ...' line. - Counts moved into the section headings (Applications / Applet Instances (n), Executable Load Files (ELFs) / Packages (n)); the redundant '1 ISD' count is gone (P1=80 returns the ISD as a single occurrence, GP 11.4.2.1). - The RAM command format line gained a tooltip: compact = TS 102 226 5.2.1 command string, expanded = the AA/22 scripting template (5.2.1.0), detected per operation with a read-only GET STATUS probe. - The expanded-registry checkbox is relabelled 'Fetch expanded registry details (load-file / SD associations)' with a tooltip (extra secured packets, card support varies, the fallback is automatic) and is now a sticky per-browser preference (simple_ram_expanded). sw cache simple-v318; tests: 531 Python / 681 Node. |
||
|
|
3d4e4e81f8 |
feat: infer the ETSI release from the CAP's uicc.toolkit import (v3.6.44)
The CAP details' Platform row named only the Java Card level; it now also shows the ETSI/3GPP release the UICC API imports come from, with its basis in brackets, e.g. 'Java Card >= 2.2.2 - ETSI REL-7 (uicc.toolkit 1.4) - CAP format 2.1'. capmem.ETSI_TOOLKIT_RELEASE maps the uicc.toolkit export version to the release, sourced from the workspace corpus: sim-tools/ETSI-REL.md for the prepared trees (api21_rel7_export_files carries uicc.toolkit 1.4 = the REL-7 jar, api21_export_files 1.11 = the REL-12 jar) and sim-tools/etsi/102241/README.md's package-version table for the rest (1.0 -> REL-6/7, 1.3 -> REL-8, 1.5 -> REL-9, 1.12-1.14 -> REL-15..18; the 1.11 spec range is 13.01-14.02 but the tree the builds use is REL-12). Exposed as requires_etsi_release/requires_etsi_basis in /api/cap-info and the CLI; SIM-only CAPs (no uicc.toolkit) get no label. Verified: RemMobileID -> REL-7 (uicc.toolkit 1.4), AlfaDSA -> REL-12 (uicc.toolkit 1.11). sw cache simple-v317; tests: 531 Python / 677 Node. |
||
|
|
f21d47538f |
feat: clearer import-probe wording, tooltips, translatable titles (v3.6.43)
The probe's checkbox described the mechanism ('Rewrite the CAP's import
versions before the load'); it now reads 'Enable import probe' (purpose
first) with the mechanism in a tooltip, and the field carries two short
hint lines: what the card checks while loading, and the workflow (Set all
0.0 first; if the LOAD passes the list was the cause, then restore one
line at a time to find the package/version the card lacks).
Static tooltips are translatable now via data-l10n-title (handled by
translatePage, checked against LANG_RU by html.test.js together with
data-l10n); the help's RAM-install section documents the two-step
workflow (EN/RU).
sw cache simple-v316; tests: 530 Python / 676 Node.
|