Commit Graph

109 Commits

Author SHA1 Message Date
catarrh 61a52a3f05 chore: drop the unused HTTPServer import (server.py) 2026-09-29 01:40:52 +03:00
catarrh f942056d21 fix: threaded HTTP server so the RAM modal progress is live (v3.6.39)
__main__ built a plain HTTPServer (single-threaded): during a RAM install
POST every other request waited in the accept queue, including the 2 s
/api/status poll that feeds the operation modal - its bar and step
headline never moved and the whole result appeared only at the end (live
2026-09-29).

_build_http_server() now returns a ThreadingHTTPServer (daemon threads).
Card access is unchanged: it is serialized by _CARD_LOCK, not by the
server - every POST is blanket-wrapped in do_POST, every /api/ GET except
/api/status and /api/test/status is wrapped in do_GET, and the background
threads (STATUS poll, _bip_data_available, test runner) take the lock;
the audit found no card-access holes.  do_GET also lets non-/api (static
PWA) paths bypass the lock, so page assets stay answerable during long
operations.

Regression test tests/test_http_concurrency.py: a fast request must
complete while a slow one is in flight (fails on HTTPServer with exactly
the reported symptom: 1.01 s vs 0.20 s); test_startup_cardless patches
the new factory.

sw cache simple-v312; tests: 519 Python / 674 Node.
2026-09-29 01:40:28 +03:00
catarrh 0027c405c1 feat: answer GET INKEY / GET INPUT interactively (v3.6.38)
The menu chain paused only for DISPLAY TEXT / SELECT ITEM / SET UP MENU,
so a GET INPUT was auto-answered with a bare success TR and no Text
string - the live RemMobileID PIN prompt got an empty answer.

Server: _parse_get_inkey/_parse_get_input read the prompt, qualifier
flags (digits-only, UCS2, Yes/No, immediate, hidden, packed, help),
Response length 91 and Default Text 9D; the fetch handler pauses with
them; _menu_send_response(..., text=) validates the answer (digits set,
min/max, one char for GET INKEY) and builds the Text string TLV 8D in the
requested coding (SMS unpacked 04 / UCS2 08 / packed 00 via _pack_gsm7),
Yes/No as 01/00, empty input as the null text string, help as result 13
and the variable-timeout Duration echoed (6.8.4).  /api/menu-respond
takes text; /api/stk-status carries pending_data so a card-initiated
prompt opens the panel; _decode_cmd decodes both commands.

PWA: stkInputHtml/stkInputHint/stkInputValidate render the constrained
panel (password field for hidden entry, numeric keyboard, default text,
Yes/No buttons, help, immediate-digit auto-submit); OK submits through
stkMenuOk; the poll opens the panel for card-initiated prompts.

sw cache simple-v311; tests: 517 Python / 674 Node.
2026-09-29 01:30:40 +03:00
catarrh ddf5cbb8e7 fix: late-PoR handler on every path, one poll per packet, LOAD-step components (v3.6.37)
_send_envelope defined its SEND SHORT MESSAGE capture handler inside the
91xx branch, but the 'STATUS poll (PoR not captured)' path used it: a
packet whose ENVELOPE answered 9000 and whose late PoR arrived on the
STATUS poll raised UnboundLocalError, returned 500 and aborted the RAM
operation mid-packet with the card's command left pending (live
2026-09-29, 120-byte LOAD blocks).  The handler is now defined for every
path, and the late-PoR poll runs only after a packet's last segment
(poll_status from _send_secured_packet) - a concatenated download makes
no spurious mid-packet STATUS exchanges.

The PWA annotates every LOAD step with the CAP components its block
covers (ramStepComponents: 'completes Import' / 'inside Method' /
'Header...Import') and ramInstallFailHint names the component the failing
block completed - the live import-verification rejection lands in the
block completing Import (6438/6985 at both 240 B and 64 B blocks).

sw cache simple-v310; tests: 509 Python / 672 Node.
2026-09-29 01:14:37 +03:00
catarrh f007d5c9be feat: CAP platform requirement + LOAD rejection hint (v3.6.36)
capmem derives the minimum platform from the CAP's Import component
(javacard.framework -> Java Card level via the SDK-kit corpus, int flag)
and /api/cap-info exposes it; the CAP details show it as a Platform row
('Java Card >= 2.2.2 - CAP format 2.1 - int support').

A failed RAM install at a LOAD / INSTALL [for load] step now appends a
hint naming the import requirement and the smaller-block fallback
(ramInstallFailHint, pure): JC VM spec 4.5.2 makes every import a
'>= version' demand on the card's own packages checked while the load
file is verified - live 2026-09-29, a CAP requiring Java Card 2.2.2 was
rejected exactly at the block containing the end of Import (6438 at
240 B blocks, 6985 at 64 B), not at a step count.

sw cache simple-v309; tests: 507 Python / 670 Node.
2026-09-29 00:59:17 +03:00
catarrh bd5dc69c4e fix: drop stale STK dialogs, finish pending on TP re-send, mute client-gone (v3.6.35)
The live log showed the "stuck" state: after the menu watchdog (60 s)
answered a pending command, the PWA kept the panel; the next click got
{'error': 'no pending command'}, 400 and that reply was fed to the dialog
handler as if it were a card answer.  A page reload then cancelled in-flight
polls, flooding the log with BrokenPipeError tracebacks.

- stkMenuRespond detects the already-answered reply (stkNoPendingError) and
  closes the panel (stkMenuPanelReset) instead of treating it as an answer;
  the 5 s status poll drops the panel whenever the server reports
  pending: false, even when the Phone tab is hidden.
- /api/terminal-profile (POST) and /api/rescue finish a pending command
  with a cancel TR first, so re-sending the TP can never shadow an
  unanswered FETCH.
- _send_json and _serve_static swallow BrokenPipe/ConnectionReset/
  ConnectionAborted with one "CLIENT GONE" line (a reload cancels polls);
  the handlers' state changes still stand.

Tests: stkNoPendingError + the already-answered respond path; version
3.6.35; sw simple-v308; stylesheet rebuilt.
2026-09-29 00:37:56 +03:00
catarrh 0b7b6b8157 feat: RAM operations run behind a blocking modal (v3.6.34)
A CAP install is a long chain of LOAD commands whose progress and results
were only visible at the bottom of a large form - and an interrupted
install can leave a half-loaded package.

- Server: _RAM_PROGRESS (+ _ram_progress_begin/step/end) records the live
  step of the running RAM operation; /api/ram-install and
  /api/ram-install-app wrap their chains in it (total = steps + 1 for the
  format check) and /api/status carries it as ram_progress - lock-free, so
  the 2 s poll works while the request holds _CARD_LOCK.
- PWA: #ram-op-modal (the STK panel pattern) blocks everything while a RAM
  operation runs: title, spinner, status line, progress bar, per-step
  result lines, Close disabled until it finishes; a beforeunload guard
  protects the window during the run.  install-cap, install-app /
  make-selectable and Explore all use the shared helpers
  (ramOpModalBegin/Status/Finish); the install results stay in the form
  area as before once the modal is closed.  The status poll feeds the modal
  via ramOpProgress (headline from the pure ramOpProgressText).
- Tests: ramOpProgressText (JS) + the progress lifecycle (Python); the
  card-state and capmem suites stub the DOM-bound hooks.
- Stylesheet rebuilt; version 3.6.34; sw simple-v307.
2026-09-29 00:09:35 +03:00
catarrh a935361ca6 fix: clarify the suggested quotas, drop the CAP-details notes (v3.6.33)
The suggested quota row mixed a decimal C6 with hex C7/C8, so C6=27246
could be misread as hex (about 6x off).  It now renders as one consistent
line via the pure capQuotaText(sug): decimal bytes with the hex in
parentheses (C6=27246 B (0x6A6E) . C7=270 B (0x010E) . C8=5143 B (0x1417)),
with a tooltip naming the tags (C6 = non-volatile code space / load file,
C7 = volatile RAM, C8 = non-volatile data).

The static "Notes" disclosure (NVRAM definition + "estimate only") is
removed from the CAP details box - the help (EN/RU, the Install Package
row) carries it now; only the dynamic scan warnings (e.g. unknown opcode)
stay visible, since they mean the estimate is partial.

Tests: the new formatting pinned, the removed notes asserted absent;
stylesheet rebuilt; 664 frontend / 504 Python green; version 3.6.33;
sw simple-v306.
2026-09-28 23:57:59 +03:00
catarrh e820a7ed41 feat: toolkit block emphasised + auto CA/EA from the CAP (v3.6.32)
The RAM install form's toolkit parameters are the make-or-break field for
toolkit applets, and CA vs EA is easy to get wrong (the earlier live 6A80s).

- "SIM/UICC Toolkit parameters" is now an accented bordered fieldset (blue
  border/legend, always visible in the cap / install / make-selectable
  modes) with the checkbox in the legend and the fields inside.
- ramCapToolkitMode(imports) maps the CAP's linked libraries to the install
  parameter family: sim.* (AID tree A0000000090003FFFFFFFF89...) -> SIM
  Toolkit (Tag CA), uicc.* / uicc.usim.* (A0000000090005FFFFFFFF89...,
  A0000000871005FFFFFFFF89...) -> UICC Toolkit (Tag EA) - family-based, so
  access-only applets get the right field too.  Applied when the block is
  enabled and when the CAP is swapped while it is on, with a "detected from
  the CAP" hint under the select; no CAP keeps the manual choice.
- Rebuilt style.css for the new accent classes; tests pin the fieldset and
  the family mapping.

663 frontend / 504 Python green; version 3.6.32; sw simple-v305.
2026-09-28 23:49:00 +03:00
catarrh 30918e4f4a fix: no stale stylesheet from caches (v3.6.31)
Even with the rebuilt style.css, three caching paths could still serve the
old file:

- the server sent Cache-Control: no-cache only for index.html/sw.js, so
  browsers could heuristically cache style.css;
- the SW install precached with addAll(), which re-uses HTTP-cached copies,
  so a new SW version could precache the stale file;
- the fetch handler is cache-first (fine once the cache is fresh).

Now every static shell file is served with Cache-Control: no-cache plus
Last-Modified and cheap 304 revalidation, and the SW install fetches each
precache URL with cache: 'reload' (CACHE simple-v304).  A test pins the
install behaviour.

661 frontend / 504 Python green; version 3.6.31.

Note: the public demo simple.atroshin.ru is still v3.6.20 with the old
stylesheet - it needs a deploy of the current frontend/.
2026-09-28 23:40:24 +03:00
catarrh c3117e90fa fix: rebuilt stylesheet + visible explorer rows (v3.6.30)
The compiled Tailwind stylesheet (frontend/style.css) had been stale since
2026-09-22: classes introduced later were missing from it, so the Install /
Make selectable chips had no background or text colour in light mode (dark
mode only looked green because an older dark:text-emerald-400 rule matched)
and the new w-28 action column did not apply.

- Rebuilt with `npm run build` (Tailwind + appended src/contrast.css);
  verified the previously missing classes (bg-emerald-100, text-emerald-700,
  hover:bg-emerald-200, bg-red-100, bg-blue-100, w-28, ...).
- Explorer rows are visible chips now: rounded bg-gray-50 dark:bg-slate-800/60
  with a hover:bg-gray-100 highlight and the colored left accent kept (module
  rows a subtler variant), so a right-column button is unambiguously tied to
  its row; ISD/app/ELF/module rows all use the same treatment.
- AGENTS working conventions: an index.html change that introduces new
  utility classes also requires `npm run build` in frontend/.

660 frontend / 504 Python green; version 3.6.30; sw simple-v303.
2026-09-28 23:38:06 +03:00
catarrh 3b5a1495f5 feat: table-align the explorer action buttons, Install green (v3.6.29)
The explorer buttons sat inline after each AID/detail line, so their columns
moved with the text length.  Now every entry (app / ELF) and every module
line is a row with a fixed-width action column (w-28):

- left cell: the text (module rows are siblings of their ELF entry, indented
  pl-6, so their buttons share the same column as the entry buttons);
- right cell: the buttons, full width of the column, so Delete / Delete All /
  Make selectable / Install line up vertically across rows.

All buttons go through ramActionBtn(label, action, tone): Danger (red) for
Delete / Delete All, Ok (emerald) for Make selectable and the **Install**
buttons, which were blue.  Test pins the column width and the Install tone.

660 frontend / 504 Python green; version 3.6.29; sw simple-v302.
2026-09-28 23:29:55 +03:00
catarrh 90fe409dec fix: correct the GP privilege decode and remove the duplicate (v3.6.28)
The RAM explorer's ramFmtPrivileges used a private table with wrong bits and
invented names ("Cum Deletion Ctr", "GSM Card Binding", "Global PIN",
"RFM"/"CFM") and no case for 0x80: a live ISD 9E printed four wrong names
and dropped Security Domain.  Per GP Card Spec v2.3.1 11.1.2 Tables
11-7/8/9, 9E = Security Domain, Card Lock, Card Terminate, Card Reset, CVM
Management.

- ramFmtPrivileges now delegates to decodePrivileges (one spec table).
- decodePrivileges decodes byte 1's DAP pair per Table 11-7: b8+b7 = DAP
  Verification, b8+b7+b1 = Mandated DAP Verification; the flag bits alone
  are no privilege.
- The file had two decodePrivileges copies (the runtime used the later one,
  the C-APDU parser test the earlier); the duplicate and its private table
  are gone - the single implementation plus PRIVILEGE_NAMES live in the
  parser section, which the test evaluates (converted to var like PARSE_INS).
- Tests: 9E/9A live values, the DAP pair, byte 2/3 spot checks; the
  PRIVILEGE_NAMES spec pin moved to apdu_parse.

659 frontend / 504 Python green; version 3.6.28; sw simple-v301.
2026-09-28 23:29:01 +03:00
catarrh 85ba2a709d fix: explain the FF21 resource block and move the format line last (v3.6.27)
The explorer's FF21 block was titled "Memory (GET DATA FF21)" although the
tag is the Extended Card Resources Information (GP tag FF21, TS 102 226
8.2.1.7.2: installed applications, free non-volatile and free volatile
memory), and the detected RAM command format line sat between the Free NV
and Free Volatile lines.

- Heading is now "Card resources (GET DATA FF21)" with a tooltip naming the
  tag and its three fields (EN/RU).
- The "RAM command format:" line moved after the resource lines.
- Help EN/RU spells out what FF21 returns; test asserts the format line
  follows the resource lines.

657 frontend / 504 Python green; version 3.6.27; sw simple-v300.
2026-09-28 23:18:00 +03:00
catarrh f65a512440 fix: query the ELF listing with P1=10 first, fall back to P1=20 (v3.6.26)
GP 11.4.2.1 makes '10' the superset of '20' (Executable Load Files with
their modules), yet the Explore queried '20' plus a separate '10' modules
pass and merged the two.  A live card answered 6A86 (incorrect P1-P2) to
'20' while '10' worked (2026-09-28) - all four P1 values "shall be
supported", so this is the guarded-fallback case, not a vendor branch.

- Explore now queries '10' (with module parsing) and falls back to '20'
  (no module lists) only when it yields nothing; a successful fallback
  drops the primary failure from the result, and the primary error surfaces
  only when both fail.
- The separate P1=10 "modules" pass and ramMergeElfData are gone.

656 frontend / 504 Python green; version 3.6.26; sw simple-v299.
2026-09-28 22:56:26 +03:00
catarrh 7c81f02308 feat: Explore expanded (E3) listing fallback + details pass + format display (v3.6.25)
The E3 parsers existed since the vendor-trace work; what was missing was
issuing the query.  Per GP 11.4.2.2 the compact listing format (P2.b2=0) is
deprecated and a card may reject it, while the expanded form (P2=02 + the
tag list 4F 9F70 C5 C4 CC CE EA) returns the richer registry data.

- ramExpandedQueryApdu(p1): the reference trace bytes
  (80F2 <p1> 02 0C 4F005C084F9F70C5C4CCCEEA 00), pinned in a test.
- Explore: when the compact pass for a P1 yields nothing, the query is
  retried in the expanded format and the result becomes the listing (the
  compact error only surfaces if the retry also fails); the new "Query
  expanded registry details (E3)" checkbox runs the expanded pass on top of
  a working compact listing and merges the expanded-only fields
  (privileges, ELF AID C4, SD AID CC, module lists) into the entries.
- The detected RAM command format (v3.6.24 ram_format) is now shown in the
  Explore result + explorer view, the delete verdict and the plain-send
  message.
- Tests parse the real trace payloads (NC ISD, ET application, NP ELF E3
  templates); help EN/RU updated.

656 frontend / 504 Python green; version 3.6.25; sw simple-v298.
2026-09-28 22:51:20 +03:00
catarrh cf931da2f9 fix: capture the submit PoR on every card + per-operation RAM format check (v3.6.24)
Comparing the new TCA vendor traces (Explore_EP/ET/NC/NP/SE) with our flow
showed why only one card works:

- The PoR capture was gated on SPI2 bit 0x20.  The traced cards deliver the
  PoR as a proactive SEND SHORT MESSAGE; with our ISD/memory queries
  (SPI2=0x01) the fetched TPDU was thrown away - the Explore died with
  "no data" and the RAM install steps silently degraded to an unverified
  "no_por".  The capture is now unconditional (inline ENVELOPE responses and
  proactive submits both work), and the STATUS-poll fallback is enabled for
  every card.
- `_ram_next_cntr` still wrapped at 32 bits (`% (2 ** 32)`): a 10-digit
  counter like 10000AAAC8 came back as 00000AAAC9 after a RAM/Explore step -
  the server-side half of the v3.6.23 counter report.  It now advances the
  full 40 bits.
- RAM command format (TS 102 226 5.2.1: compact C-APDU vs the expanded
  `AA` > `22` form) is detected per operation and never stored:
  /api/ram-install and /api/ram-install-app always probe, /api/send-ota
  probes when the caller passes `ram_format`.  The probe is a read-only
  GET STATUS [ISD] as the first step (compact, then wrapped) and every
  command of the operation uses the detected format.  `_wrap_expanded_apdu`
  is pinned to the reference bytes `AA0A220880F24000024F0000`; responses
  carry `ram_format` and `final_cntr` (accepted probes consume counters, so
  the views use the server's next counter).  Applet-directed payloads stay
  byte-exact; only RAM views (and RAM/GP chain packs) opt in.

504 Python / 653 frontend green; version 3.6.24; sw simple-v297.
2026-09-28 22:38:17 +03:00
catarrh 991be7353b fix: keep the full 5-byte OTA counter on increments (v3.6.23)
spNextCntr coerced the 40-bit replay counter with `>>> 0`, so any counter
above FFFFFFFF lost its top byte after an accepted send: the live
10000AAAC8 came back as 00000AAAC9, and spCntrSyncPreset then stored the
broken value in the card preset - the RAM/Explore flows (ramIncrementCntr,
which is correct) inherited it.

- spNextCntr: 40-bit arithmetic (`% 0x10000000000`, wrap at FFFFFFFFFF
  kept) instead of the 32-bit >>> 0 coercion plus slice(-10).
- spCntrAdjust (+/-): the same 40-bit wrap (an 11-digit value could be
  produced at the maximum).
- tests: 10000AAAC8 -> 10000AAAC9, separator handling, FFFFFFFFF7 ->
  FFFFFFFFF8, FFFFFFFFFFF -> 0000000000 wrap, out-of-range input clamps.

The server side (_increment_counter_hex, _ram_next_cntr, final_cntr) was
already exact.  653 frontend / 500 Python green; version 3.6.23;
sw simple-v296.
2026-09-28 22:01:54 +03:00
catarrh 3c104f5685 fix: drop the "Reserved for 3GPP" event-name cross-reference (v3.6.22)
The event list tables carried labels like
"12  Reserved for 3GPP - Network rejection (TS 31.111 7.5.2)".  The phrase is
an ETSI-to-3GPP document pointer, not a usage restriction, and it is not
what a user needs - the event name is.

- EVENT_NAMES (PWA + server mirror): the 3GPP-assigned codings carry their
  plain TS 31.111 7.5 name ((I-)WLAN access status, Network rejection, CSG
  cell selection, IMS registration, Incoming IMS data, Data connection
  status change, CAG cell selection, Slices status change).
- 0x20-0x22: "Reserved (future usage)" (no attribution).
- 0x0B/0x14: the spec's full "(single access technology)" / "(multiple
  access technologies)" wording instead of the cryptic "(single)/(multiple)".
- tests pin the exact names and that no name contains the cross-reference.

652 frontend / 500 Python green; version 3.6.22; sw simple-v295.
2026-09-28 21:54:28 +03:00
catarrh 5372a770eb fix: keep a hand-edited packet TAR in the SCP80 send (v3.6.21)
The plain "Send to Card" path refreshed the whole preset right before
sending (spRefreshFromPreset -> cardsApply -> cardsApplyFields) and
re-applied the preset's slot TAR - so a TAR typed for a push/link trigger,
an applet OTA message or the HTTP OTA admin params was silently replaced by
the preset's ISD TAR (the pack buttons for those sources deliberately leave
the TAR manual; the help even said so).

- spRefreshFromPreset(selId, keepTar): the plain send passes keepTar, so
  the form's TAR survives; the counter, keys and SPI are still re-read.
- selecting a card preset, ramApplyCard and the chain packs still set the
  TAR; the RAM operations keep the preset's ISD TAR by design.
- spParams()/getRamSpParams() normalise the TAR (strip/upper/pad to 6);
  only the preview padded before, a short TAR went to the server raw.
- help EN/RU clarified, cards_counter/ram tests updated, sw simple-v294.

652 frontend / 499 Python green; version 3.6.21.
2026-09-28 21:43:57 +03:00
catarrh 2d268f024b fix: always emit the mandatory C9 in the install parameters (v3.6.20)
The v3.6.17 prefix built `C9` only when its field was filled, so filling the
C7/C8 quotas with an empty C9 produced `EF…`-only Install Parameters - and
GP Table 11-49 makes C9 mandatory, so the card rejected the data field with
6A80 (decrypted live packet: params started `EF08C7020000C8020000EA25…`).

- `ramParamsPrefix` always emits the C9 TLV (empty -> `C900`, like the
  reference tools); the vendor form becomes `C900 EF08 C7020000 C8020000
  EA…`.
- the raw "Install parameters (hex)" field is relabelled "Extra install
  parameters (hex)" - it appends extra TLVs after the generated prefix.
- tests updated (empty prefix -> `C900`, quotas-only ->
  `C900EF08C7020000C8020000`).

650 frontend / 499 Python green; version 3.6.20; sw simple-v293.
2026-09-28 08:29:29 +03:00
catarrh 31162efc8c feat: log the plaintext RAM APDUs of the install steps (v3.6.19)
`_ram_send_gp_apdu` (the shared step sender of /api/ram-install and
/api/ram-install-app) now logs every step's plaintext APDU and packed
packet, like /api/send-ota's unconditional lines:

  RAM C-APDU (INSTALL [for load]): 80E602000C07F0414C4641610100000000
  RAM SECURED-PACKET (INSTALL [for load]): 00281516212525000000...

No flag - the step name keeps the sequence readable, and --apdu-trace stays
the card-level (ENVELOPE) trace.  Tests: the logging pinned with stubbed
packet builders.

498 Python / 650 frontend green; version 3.6.19; sw simple-v292.
2026-09-28 08:23:28 +03:00
catarrh face2268ca feat: single-APDU INSTALL [for install] / [for make selectable] ops (v3.6.18)
Iterating the final install step no longer needs a delete + re-load of the
.cap:

- `POST /api/ram-install-app` runs INSTALL [for install] (`mode: "install"`,
  P1 0x0C/0x04 by make_selectable) or INSTALL [for make selectable]
  (`mode: "make_selectable"`, GP 11.5.2.3.3 Table 11-44) for an already
  loaded package.  The APDU builders are shared with `_cap_apdu_sequence`
  (`_cap_install_apdu`, `_cap_make_selectable_apdu` - one builder, no drift)
  and the per-step send/PoR/counter logic is the extracted shared
  `_ram_send_gp_apdu` (used by /api/ram-install too).
- PWA: the RAM op selector gains "INSTALL [for install] (loaded package)"
  and "INSTALL [for make selectable]" with Load File / Module / Instance AID
  inputs (instance defaults to the module AID); the result renders through
  the shared `ramShowInstallResult` and `final_cntr` keeps the preset in
  sync.
- Explore: an **Install** button on a package's module AIDs that have no
  instance yet, and a **Make selectable** button on INSTALLED (0x03)
  instances - both switch the op and pre-fill the AIDs.
- tests: the install APDU pinned to the live no-STK packet and to the
  sequence tail, the make-selectable APDU, ramHasInstance, the Explore
  buttons and the op wiring; docs/api.md updated.

650 frontend / 498 Python green; version 3.6.18; sw simple-v291.
2026-09-28 08:10:40 +03:00
catarrh 6403fb9f29 feat: C9 + memory-quota inputs, STK rejection hint (v3.6.17)
The vendor's working install script (samples/uicc/applets/STK3) sends
`C9 22 <config> EF 08 C7 02 0000 C8 02 0000 EA 26 80 0A ... 81 18
<access>` - an applet config in C9 and memory quotas in EF.  The RAM install
form now composes that prefix:

- "Applet-specific parameters (C9, hex)" -> `C9 <len> <value>`;
- "C7 / C8 memory quotas (bytes)" -> `EF <len> C7/C8 <2-byte int>` (4-byte
  above 32767, GP 9.7);
- the prefix is prepended to the raw "Install parameters (hex)" field and
  the server appends the STK part, giving `C9 EF EA`;
- `ramParamsPrefix`/`ramQuotaValue` are pure and tested (incl. the vendor's
  `EF 08 C7 02 0000 C8 02 0000` form).

Also: `updateStkParamsHex` shows a hint naming the rejection reason (TAR not
a multiple of 3 bytes / menu ID > 7F / ADF AID not 5-16 bytes) instead of
silently emptying the field, and the UICC_SPECS.md install row notes that
`CA` sits inside `EF` and `CA`+`EA` together return 6A80.

647 frontend / 496 Python green; version 3.6.17; sw simple-v290.
2026-09-28 07:56:01 +03:00
catarrh 9a3174a324 feat: card-specific ADF AID helper + priority default (v3.6.16)
- the ADF AID field gains a "From card" button: it reads the ADF.USIM FCI
  via /api/select and extracts the DF name (tag 84) with the new
  adfAidFromFci() - the AID is card-specific (the live card's is
  A0000000871002FF49FF0589, 12 bytes; the base A0000000871002 is only a
  prefix, and a shorter AID makes the card reject the access entry with
  6A80).
- the priority field defaults to 255 (0xFF) and is labelled "(0 = RFU)":
  TS 102 226 8.2.1.3.2.6 makes '00' RFU, which can break the toolkit
  registration; the builder defaults an unset priority to 0xFF and the
  chain rows follow.
- stkParamsBuild's formatted payload fields are uppercase now (the rest of
  the tool's hex is; toString(16) emits lowercase).
- tests: adfAidFromFci pinned to the live FCI, the From-card wiring, the
  updated form expectations.

643 frontend / 496 Python green; version 3.6.16; sw simple-v289.
2026-09-28 03:32:13 +03:00
catarrh bab787fac1 fix: DELETE APDU is case 3 - no phantom command (v3.6.15)
The Explore delete showed "Failed: por_ok - remote SW 6700" while the delete
executed: ramDeleteApdu appended the trailing Le, and this card's SCP80
layer counts a trailing byte as a phantom second command whose SW 6700
masked the real result (count=2, last SW 6700) - the same quirk as
INSTALL [for load] before v3.6.8.

- ramDeleteApdu and the chain builder's DELETE row emit the case-3 APDU
  (4F AID TLV, no Le); the SCP81 Delete-AID template follows (it reuses
  ramDeleteApdu).
- tests updated (delete APDU expectations in ram/ram_counter_flow/scripts).

641 frontend / 496 Python green; version 3.6.15; sw simple-v288.
2026-09-28 03:16:10 +03:00
catarrh 7c683fcb0a fix: UICC file-access parameters use tag 81, not 82 (v3.6.14)
TS 102 226 8.2.1.3.2.2 defines the EA inner tags as 80 (toolkit params),
C3 (toolkit DAP), 81 (UICC Access Application specific parameters) and 82
(UICC *Administrative* Access) - the plain-text spec extract had shifted the
table columns, so the access list was emitted under 82.  With the list under
82 the card rejected the ADF.USIM entry with 6A80 and the applet never got
its uicc.access.FileView rights (live 2026-09-28).

- stkParamsBuild emits the access list under 81 (the structure is unchanged:
  the admin field is coded the same way per 8.2.1.3.2.2.4).
- tests updated (81 04 / 81 0F / 81 17 expectations).

641 frontend / 496 Python green; version 3.6.14; sw simple-v287.
2026-09-28 03:07:40 +03:00
catarrh 138760f75c fix: Explore delete - real SPI2, no-PoR verdict, local removal (v3.6.13)
"Failed: 9000" although the delete executed: the RAM helpers sent
`sp-spi2` - the base SPI2 select, whose default is "00 - No PoR" - so the
card answered the envelope 9000 with no PoR and the verdict treated the
missing PoR as a failure.  `cardsApplyFields` also set `sp-spi2-hex` from
the preset and then `updateSp()` recomputed it from the selects (which the
preset never updated), clobbering the preset byte for anything reading it.

- `getRamSpParams()` reads the computed `sp-spi2-hex`; `cardsApplyFields`
  syncs the SPI2 selects from the preset byte (base = low 5 bits, bit 0x20
  = PoR via SMS-SUBMIT, dynamic option for unlisted bases) and the base
  select gains the missing cipher + RC/CC/DS combinations (15/19/1D).
- a missing PoR is no longer a failure (the SPI may request none, and the
  card sometimes refuses one): `OK` with the SW, or `OK (no PoR)`.
- a successful delete drops the object from the Explore result locally (no
  re-explore): an applet row goes away; a cascade package delete also drops
  the applets whose AID starts with the package AID.  The consumed counter
  is still saved.
- tests: ramRemoveFromExplorer (app/cascade/prefix), the no-PoR flow, the
  SPI2 source guard, and the updated delete-flow stubs.

641 frontend / 496 Python green; version 3.6.13; sw simple-v286.
2026-09-28 03:03:53 +03:00
catarrh daaddf21cb fix: install-form grant hygiene + correct 82 access coding (v3.6.12)
Live findings (2026-09-28): a browser-restored Receipt Generation privilege
bit (third byte, ISD-only per GP Table 6-2) was silently sent and produced
6985, and the '82' access entries were missing the mandatory "Length of
Access Domain DAP" byte, so the card rejected the ADF.USIM entry with 6A80.

- ramResetGrants() clears the privilege / toolkit-enable / file-access
  checkboxes and refreshes the aggregates on load; ramInstallCap re-derives
  the privileges from the checkboxes at send time - no stale or
  browser-restored grant can be sent.
- the privileges aggregate emits 1 or 3 bytes, never the invalid 2-byte
  form (GP Table 11-43), in both updateRcPriv and the chain's computePriv.
- '82' entries carry the DAP-length byte: '00 01 00 00' (shared FS) and
  '<len> <ADF AID> 01 00 00' (ADF); the ADF AID is editable
  (rc-tk-adfaid, default A0000000871002 = ADF.USIM, 5..16 bytes enforced).
- tests: ram_grants.test.js (aggregate forms, the send-time derivation, the
  load-time reset) and the updated access-parameter shapes in
  stk_params.test.js.

638 frontend / 496 Python green; version 3.6.12; sw simple-v285.
2026-09-28 02:56:11 +03:00
catarrh 05c14b42dd feat: UICC file-access parameters in the install form (v3.6.11)
SIM toolkit applets installed while UICC-library applets failed at INSTALL
[for install] with 6F00 - including with the reference tool's exact install
parameters.  The asymmetry: the SIM (CA) path grants file access via the
Access Domain field (default 00 = full access), while the UICC (EA) path
sent no '82' (UICC Access Application specific parameters) at all, and the
reference's '82 00' is empty.  An applet importing uicc.access (the failing
CAP has 2 refs) can then fail inside its install().

- EA mode gains two checkboxes (RAM install form + the chain rows' toolkit
  block): "File system access (full)" appends '82 03 00 01 00' (shared file
  system + Access Domain Parameter 00 = full access, TS 102 226
  8.2.1.3.2.2.2/8.2.1.3.2.5); "ADF.USIM access (full)" adds
  '07 A0000000871002 01 00' to it.  Both default off.
- tests: the access TLV shapes (with/without the ADF entry), the form -> hex
  path with the checkbox, and the toolkit-field wiring count (16 fields).

635 frontend / 496 Python green; version 3.6.11; sw simple-v284.
2026-09-28 02:24:12 +03:00
catarrh a0704a8fd0 fix: Explore Apps listing uses the SMS-submit PoR transport (v3.6.10)
The Apps query (P1=40) was sent with SPI2=0x01 while only the ELF queries
(P1=20/10) used SPI2=0x21.  The Apps listing can exceed the ENVELOPE
response, and the card then answers the envelope PoR with
`actual_response_sms_submit` - the actual response would follow as an
SMS-SUBMIT, which is never sent unless the PoR is requested in submit mode.
The paginate only accepted `por_ok`, so the Explore reported the raw status
as an error ("Partial - Apps: actual_response_sms_submit") and the Apps list
never rendered (live 2026-09-28).

- `ramListingSpi2(p1)`: 40/20/10 -> '21', else '01'; the paginate uses it.
- the paginate accepts `actual_response_sms_submit` via `spPorAccepted` and,
  when no data arrived, notes "response via SMS not captured" instead of
  reporting the card status as a failure.
- the Explore delete verdict follows the same acceptance rule (an accepted
  SMS-submit delete no longer shows "Failed" while its counter advanced) and
  no longer prints a dangling " -> " without a SW.
- tests: ramListingSpi2 + the delete-accepted-via-sms-submit flow.

633 frontend / 496 Python green; version 3.6.10; sw simple-v283.
2026-09-28 02:13:13 +03:00
catarrh 9fb2ad5d2b fix: send the STK install parameters as entered (v3.6.9)
The RAM install form sent the `STK parameters (hex)` field, which was only
regenerated when the toolkit checkbox or the mode select changed - editing
the TAR (or any other toolkit field) afterwards was silently dropped.  The
live install therefore carried the form defaults (TAR B00001, textLen 0,
menus 0, MSL 16, channels 0) instead of the entered AF4D01 / MSL 12 /
channels 1, and the card rejected the install parameters with 6A80
(TS 102 226 8.2.1.3.2.7: a TAR already assigned on the card).

- every `rc-tk-*` field regenerates the hex on input/change; the install
  recomputes it at send time unless the user hand-edited the hex
  (`dataset.manual`), so the edit order can no longer drop values;
- one pure `stkParamsBuild` serves the RAM form (`buildRcToolkitParams`) and
  the RAM/GP chain rows (`buildTkParams`) - no drift between them (the chain
  row also gains the menu-ID <= 7F check);
- applet TAR field: empty by default, no placeholder - B0 00 01 is the
  allocated ADF RFM TAR (TS 101 220 Annex D), not an applet TAR; an empty
  field is coded as TAR length 00 (the card may take a TAR from the AID only
  when the AID carries one, PIX hex digits 15-20);
- the chain builder's INSTALL/LOAD rows drop the trailing Le (the v3.6.8
  server form; a trailing Le made the card execute a phantom command);
- tests: stk_params.test.js (the decrypted live parameters, empty-TAR
  coding, CA wrapper, long-form lengths, rejection cases, form wiring, and
  the form -> hex path with the real builders) and ram_counter_flow.test.js
  (the Explore delete persists the consumed counter through the real
  ramSaveCntr); STK fixtures no longer use B00001.

631 frontend / 496 Python green; version 3.6.9; sw simple-v282.
2026-09-28 01:49:35 +03:00
catarrh 29864617eb fix: CAP install APDUs match the reference terminal form (v3.6.8)
INSTALL [for load]/LOAD/INSTALL [for install] carried a trailing Le and an
explicit ISD AID in the Security Domain field; the card executed the extra
byte as a second (phantom) command, so the compact response reported count=2
with SW 6700 - which the v3.6.2 remote-SW check correctly treated as a
failure, aborting at step 1.

Decrypted from the live trace (KIc/KID 25/25, 3DES): ours was
  80E6020014 07F0414C46416101 08A000000003000000 00 00 00 00
the reference tool sends
  80E602000C 07<aid> 00 00 00 00          (empty SD, no Le)
and its response is count=1 / 9000 while ours was count=2 / 6700.

- `_cap_apdu_sequence`: the SD AID is only sent when a custom one was
  supplied (empty -> '00', the card defaults to the ISD; GP 11.5.2.3.1
  Table 11-42) and all three RAM install APDUs are case 3 (no Le).
- tests: the expected INSTALL bytes updated, a no-Le assertion for every
  APDU in the sequence, and a custom-SD-AID case.

615 frontend / 496 Python green; version 3.6.8; sw simple-v281.
2026-09-28 00:58:46 +03:00
catarrh 50fa8744fd fix: keep the preset counter intact through the Explore delete flow (v3.6.7)
Delete All worked but reset the preset counter: the handler never re-read
the preset (unlike ramExecute), saved N+1, then re-ran the Explore with the
OLD sp (counter N).  Every page answered cntr_low, nothing was accepted, and
ramExplore's final ramSaveCntr wrote N back over the preset.

- ramDeleteFromExplorer re-reads the preset first (spRefreshFromPreset
  'ram-card-sel'), advances and saves the counter only for a packet the card
  accepted (spPorAccepted) - a rejected packet leaves it untouched - and
  continues the follow-up Explore from the consumed counter instead of
  replaying it.
- the delete result also checks the remote command's SW via a new
  `ramRemoteSwOk` (9000 / 61xx / 62xx / 63xx / CAFE, mirroring the server's
  `_ram_remote_sw_ok`): a por_ok packet whose DELETE was refused (e.g. 6A88)
  no longer shows "OK" (the counter still advances, the card consumed it).
- ramExplore only saves the counter when it advanced, so a stale caller can
  never lower a preset counter again.
- tests: flow tests for accepted / rejected / refused-remote-SW deletes
  (refresh order, saved counter, the counter passed to the re-explore) and
  the ramRemoteSwOk set.

615 frontend / 495 Python green; version 3.6.7; sw simple-v280.
2026-09-28 00:50:07 +03:00
catarrh c39250f1b4 fix: make the Explore Delete buttons send the GP DELETE APDU (v3.6.6)
ramDeleteFromExplorer() called an undefined `_ber_len()` helper, so clicking
Delete / Delete All raised a ReferenceError right after the confirm and no
APDU was ever sent.  The inline builder also omitted the mandatory Le byte.

- new pure `ramDeleteApdu(aid, withCascade)` = GP Card Spec v2.3.1 Table
  11-20/23 form: `80 E4 00 <p2> <Lc> 4F <len> <AID> 00` (P2 00 = object,
  80 = object and related objects), used by the Explore handler.
- `scp81DeleteApdus()` (the "Delete AID" script template) now shares the
  same builder: it used to send the raw AID without the mandatory '4F' TLV.
- tests: exact bytes for 7/16-byte AIDs and both P2 modes, the SCP81 script
  expectations updated to the TLV form, and a wiring check that the undefined
  helper call does not come back (`_ber_len(`).

611 frontend / 495 Python green; version 3.6.6; sw simple-v279.
2026-09-28 00:42:37 +03:00
catarrh 40f20d539e fix: decode the Response Scripting template and the compact listings (v3.6.5)
Explore still missed the F0414C46416101 package on a card whose responses
wrap the R-APDU in the TS 102 226 5.2.2 Response Scripting template
(`AB <len> 80 <count> 23 <len> <R-APDU>`): `_decode_por` parsed that as a
compact response, so the frontend got `last_status_word` 81d0/7680 and data
starting `80 01 01 23 ...` instead of the listing.

- server: `_parse_response_scripting()` (AB definite / AF 80 ... 00 00
  indefinite) extracts the executed-command count and the last R-APDU's SW
  and data; `_decode_por` exposes it as `response_type: scripting` in the
  same `decoded` shape as compact, so the RAM explore paging and the RAM
  install `por_sw` see the real 9000/6310.
- frontend: the compact listing walk is deterministic on the AID length
  (`len AID life ver`; P1=10 adds `module_count (len module_AID)*`).
  `_parseRawAppEntry` no longer guesses `rawLen-1` for >8-byte AIDs (16-byte
  A113 applet AIDs were truncated), and `_parseRawElfEntry` no longer scans
  for `0x10` (a length/AID byte equal to 0x10 derailed the walk: a live page
  parsed to 1 entry with no F0414C46416101).
- tests: exact trace fixtures - the ELF page lists F0414C46416101 (11
  entries), the P1=10 page attaches its F0414C4641610101 module, the app page
  keeps the 16-byte A113 AIDs; Python covers the scripting template
  (definite/indefinite) against the live `AB 12` ISD and listing vectors.

610 frontend / 495 Python green; version 3.6.5; sw simple-v278.
2026-09-28 00:31:01 +03:00
catarrh 9e85f522f6 fix: recover the SMS-SUBMIT listings in RAM Explore (v3.6.4)
Explore returned partial data (no ELF/module entries, the installed package
missing) and then failed with cntr_low.  Two causes, both in the
actual-response SMS-SUBMIT path the card uses for big listings:

- `_find_sms_tpdu` read TLV lengths as a single byte; the FETCH carries
  `8B 81 97 ...` (BER long form) for the big pages, so it returned a
  corrupted, truncated TPDU.
- `_calc_ud_offset` treated the SMS-SUBMIT relative validity period (VPF=10)
  as 7 bytes instead of 1, shifting the UD offset: `_parse_sms_concat` then
  read a bogus UDH and reported no concatenation, so the segments never
  assembled and the PoR/data was dropped ("RAM RESPONSE-PACKET: empty").
  The step was marked failed, the counter did not advance, the same counter
  was retried and the card answered `cntr_low`, which also blocked P1=10
  (modules) where the installed package appears.

- the captured segments now always store the assembled UD (`submit_ud_hex`);
  `_sms_submit_por()` rebuilds the DELIVER-style packet (`02 71 00` + UD) for
  `_decode_por`.  Verified against the live capture: por_ok, remote SW 6310,
  438 hex chars of listing data (the exact bytes of the P1=20 page).
- `spPorAccepted` counts `actual_response_sms_submit` (0x0B) as accepted, so
  the counter advances when the data follows via SMS-SUBMIT.

Explore queries follow GP Card Spec v2.3.1 11.4.2.2: compact listings
(P2.b2=0) with the chained GET RESPONSE (`ramGetStatusApdu`, paging P2=00 ->
P2=01), the malformed P2=02 attempt is gone, and the ISD-only query (P1=80)
never pages with next-occurrence (the card shall reject it).

Tests: Python SmsSubmitCaptureTest with the live FETCH bytes (TPDU length,
UD offset, concat reassembly, 027100+UD decode); ram.test.js checks the APDU
builder and the missing P2=02 attempt; cards_counter covers 0x0B.

608 frontend / 492 Python green; version 3.6.4; sw simple-v277.
2026-09-28 00:09:54 +03:00
catarrh 39c82f26f3 fix: SCP80/RAM re-read the preset before every operation; no counter bump on a rejected send (v3.6.3)
The SCP80/RAM forms hold a copy of the preset (counter, keys, TAR, SPI).
Editing the preset on the Cards tab saved correctly, but the form kept the
old copy: the operation sent the stale counter (the card answers cntr_low)
and the post-send sync wrote the stale value back over the preset - the
saved counter silently reverted.  Reproduced in a real DOM:

  after select in SCP80:  preset=0000000001  sp=0000000001
  after Cards edit+save:  preset=00000000AA  sp=0000000001
  after a sync:           preset=0000000001  (edit lost)

- `cardsApply()` split into `cardsApplyFields()` (field copy, no packet) and
  `cardsApply()` = fields + genSp; new `spRefreshFromPreset(selId)` re-reads
  the selected preset from `sp-card-sel` / `ram-card-sel` and re-applies it.
- `pysimSendOta()` and `ramExecute()` call it before starting, so every
  SCP80/RAM operation uses the preset as it is now.
- A rejected send no longer advances the counter: new `spPorAccepted(por)`
  gates the advance+write-back in `pysimSendOta`, the Explore pagination and
  its GET DATA step, and the server's RAM install (`_ram_next_cntr`: advance
  only for `por_ok`/`no_por` steps).  A failed install still returns
  `final_cntr` (the accepted prefix) and the PWA persists it, so a retry
  never replays a counter the card already consumed.
- tests: cards_counter.test.js (the stale-form regression, RAM selector,
  fields-without-genSp, spPorAccepted) and `_ram_next_cntr` cases; the
  cards_form/ram harnesses updated for the split.
- docs/api.md counter semantics; AGENTS preset-source-of-truth rule.

607 frontend / 488 Python green; version 3.6.3; sw simple-v276.
2026-09-27 23:43:28 +03:00
catarrh 36d2f71bc7 fix: report the real PoR/remote-SW result of every RAM install step (v3.6.2)
The RAM installer read `por['decoded']['response_status']`, but the compact
response decoder's `decoded` only carries {number_of_commands,
last_status_word, last_response_data} - so the suffix was always empty and
every step showed the useless `por_error_`, even when the PoR was por_ok.
The PoR verdict is the top-level `response_status`.

With that fixed, the decoded details also show that the remote command's own
status word was the real verdict all along: a captured live install returned
por_ok with `last_status_word` 6700/6F00 (the card rejected every RAM APDU)
while the installer reported success.

- new `_ram_step_result()`/`_por_remote_sw()`/`_ram_remote_sw_ok()`: a step
  fails on a non-por_ok PoR, on a remote SW outside the success set (9000,
  61xx more data, 62xx/63xx warnings, CAFE GP "more data"), or on an
  undecodable PoR (a 9000 transport SW with no PoR at all is `no_por`, not a
  failure).  A decoded 61xx is explicitly success, per GP/ISO.
- step records gain por_sw/por_type/por_cntr/por_data/por_raw and
  `por_error`; the response carries `failed_step` and a detailed error such
  as `LOAD (1/9): remote SW 6700`; the log line now prints
  `status=por_ok remote_sw=6700`.
- PWA: new pure `ramStepLine()` renders e.g.
  `❌ Шаг 2: LOAD (1/9) — PoR ok · remote SW 6700 (Wrong length in Lc) · 274 B / 3 SMS`
  (SW meaning via the existing lookupSw decoder) and the transport SW only
  when it is not 9000.
- tests: tests/test_ota_helpers.py RamPorStepTest (success set incl. 61xx,
  the captured 6700 failure, no-PoR/undecodable, expanded responses);
  ram.test.js ramStepLine cases.
- docs/api.md RAM install step fields + failure semantics; AGENTS updated.

603 frontend / 487 Python green; version 3.6.2; sw simple-v275.
2026-09-27 23:21:56 +03:00
catarrh b92a47cd44 fix: close the eSIM panel so the Test script pill shows its panel (v3.6.1)
#phone-sub-esim was missing its closing </div> before #phone-sub-test, so
the browser parsed the test panel (and everything after) as a child of the
eSIM panel.  Clicking Test script unhid the panel itself, but its hidden
ancestor kept it invisible - the pill has never shown a panel in a real
browser since it was added (v3.5.11).  The eSIM LPA pill conversely showed
the whole test editor below its own fields.

The old whole-file div-count test stayed green because the inline script's
template strings happen to contain one extra </div>; the real markup was
409 opens vs 408 closes.

- add the missing </div>.
- html.test.js: count divs on the markup only (scripts excluded) and add a
  structural walk (stray end tags, cross-nesting, unclosed elements, HTML
  optional end tags honoured) over index.html, help.html and help-ru.html,
  plus a Simulator-panel sibling-depth check.  Run this for every HTML edit.
- the walk immediately found the same class of bug in both help pages: six
  unclosed <section> chapters each; closed them.
- AGENTS: the HTML structure test is now part of the working conventions.

602 frontend / 482 Python green; version 3.6.1; sw simple-v274.
2026-09-27 19:25:47 +03:00
catarrh 18a36a8f27 feat: generic BIP terminal control (Simulator BIP pill) (v3.6.0)
BIP was only reachable through the SCP81 listener; cards that use TCP for
other purposes (an applet's OPEN CHANNEL, a push trigger without HTTP OTA)
now get a generic control surface, independent of SCP81.

Server:
- /api/bip/control starts a plain BIP session in three modes: sink
  (default; a local TcpDumpServer that accepts the card's channels and only
  logs conn/sink-rx/conn-close, never answering; port 0 = ephemeral, the
  bound port is reported), passthru (dial the OPEN CHANNEL destination, TCP
  client only) and redirect (fixed host:port).
- /api/bip/status returns {owner, bip, listener}; /api/bip/log and
  /api/bip/log-clear share the BIP event log with /api/scp81/log.
- The session state is shared with the SCP81 listener and only one session
  runs at a time: _bip_session_stop() stops whichever control started it
  and the control responses report it as `replaced` (both directions,
  SCP81 <-> BIP).  State renamed _SCP81_MODE/_TARGET/_LISTENER/_LINK_EVENTS
  -> _BIP_* plus _BIP_OWNER.
- TcpDumpServer logs conn-close and counts accepted connections; stop()
  joins the accept thread so the port is really free on restart.
- The new control endpoints are blocked during test-script runs.

PWA:
- New Simulator pill BIP (after TR Config): mode select with notes,
  Host/Port rules, Start/Stop with a "replaced" notice, status line (mode,
  bound address, owner, channels), a Channels box and the shared BIP log
  (reuses the SCP81 log renderer, now showing `peer`).
- The SCP81 status line marks a session owned by the BIP pill.

Help EN/RU 8.10, docs/api.md, AGENTS; CAT_TP/UDP recorded as not
implemented.

Tests: tests/test_bip.py (9) and frontend/tests/bip.test.js (6).
600 frontend / 482 Python green; version 3.6.0; sw simple-v273.
2026-09-27 15:28:23 +03:00
catarrh 73ef3a67ca ui: put the CAP report in a bordered "CAP details" container (v3.5.19)
The CAP analysis now lives in the simulator-style fieldset with the title
embedded in the border:

  <fieldset id="ram-cap-info" class="hidden mt-2 border ... p-3 text-xs ...">
    <legend ... data-l10n="CAP details">CAP details</legend>
    <div id="ram-cap-body"></div>
  </fieldset>

- Both CAP boxes (RAM installer, SCP81 Scripts) use the same bordered
  fieldset + legend pattern as the simulator/eSIM/card fieldsets.
- capRenderAnalysis toggles the fieldset and writes into the body div, so
  the border and title stay visible for analyzing / failed (Retry) / done
  states alike.
- capMemHtml is pure content now; the inner "CAP requirements (estimate)"
  heading is gone - the border legend titles the box.
- i18n: 'CAP details' -> "Детали CAP" (static legend, data-l10n).
- refreshDynamicI18n() rebuilds both CAP bodies on a language switch (the
  dynamic report previously kept the old language).

Tests: the wiring test requires both legends and body divs, a new
capRenderAnalysis test covers idle/analyzing/error/ok, and the renderer
tests no longer expect the inner heading.

593 frontend / 473 Python green; version 3.5.19; sw simple-v272.
2026-09-27 15:02:21 +03:00
catarrh b43e3118cf ui: give the Requires table its own AID column (v3.5.18)
The imported-library AIDs are now shown in a dedicated column between the
library name and the minimum version (Library | AID | Version | Family |
Refs), monospace and break-all so the 32-char UICC/3GPP AIDs wrap cleanly
on narrow panels.  When a library name cannot be resolved (vendor AID)
the name cell shows a dash instead of repeating the AID now carried by
the column.

Tests updated: the Requires slice asserts the AID header and every row's
AID, and the unresolved-name case expects the dash plus the AID column.
Help EN/RU reworded; the AID key is a no-op in RU.

592 frontend / 473 Python green; version 3.5.18; sw simple-v271.
2026-09-27 13:11:28 +03:00
catarrh d37a29b389 ui: regroup the CAP report into Package / Requires / Memory tables (v3.5.17)
The CAP analysis box was a flat list mixing memory, package and library
data, with run-on "table-like" lines of differing lengths.  Rebuild it as
a labelled report (always visible except the bulky parts, which stay in
nested disclosures):

- Package: AID (plus the optional JC 2.2 package_name), version, header
  flags (applet/exports/int), applet AIDs and the compiled-against hint
  (javacard.framework -> Java Card SDK release + CAP format).
- Requires (n): one row per imported library with the minimum version
  (">=", same major and minor >= the recorded export version per JC VM
  4.5.2), the API family and the distinct constant-pool reference count.
  The AID is not repeated when the name resolves (redundant); unresolved
  AIDs are shown as the name.
- Memory: the NVRAM requirement stated once, with indented children (code
  image + persistent data parts), RAM (volatile) and the suggested
  C6/C7/C8 quotas - the old duplicated totals are gone.
- Components: collapsed table in load-file order with size and share,
  ending in the load-file total row; Notes holds the GP caveat and the
  estimate disclaimer.
- Numbers line up right-aligned in monospace columns; section labels use
  uppercase letter-spacing inline, so no Tailwind rebuild is needed.
- i18n: 27 new EN/RU keys; help EN/RU and AGENTS updated.

Frontend tests reworked for the new markup (group labels, aligned values,
load-file order, total row, and the no-imports / unknown-AID /
older-response / missing-components cases).

592 frontend / 473 Python green; version 3.5.17; sw simple-v270.
2026-09-27 13:03:57 +03:00
catarrh 0255a956e2 feat: show the CAP's required libraries, package identity and components (v3.5.16)
The Import component (JC VM spec 6.6) is now exposed by /api/cap-info and
rendered in the CAP analysis box:

- imports: the libraries the CAP is linked against with the export-file
  versions and the number of distinct constant-pool references (6.7),
  displayed as "name >= version" (a card resolves an import only with the
  same major and a minor >= the recorded one, 4.5.2).  Standard names come
  from the AID table; each gets a family label (Oracle JavaCard / ETSI SIM
  2G / ETSI UICC / 3GPP USIM-ISIM / GlobalPlatform) and, for
  javacard.framework, a Java Card SDK release hint derived from the local
  Oracle SDK kit corpus (jc211..jc305u4 exports; unknown versions stay
  unhinted).  Vendor/applet AIDs stay bare.
- Header package flags (Table 6-4: int / exports / applet package) and the
  optional JC 2.2 package_name (absent in all CAP 2.1 files).
- components: every archive entry in load-file order with its size and
  share of the load file (including the Directory/Export entries capmem
  does not parse); the sizes sum to load_file_bytes.
- The PWA box leads with "Requires: ..." when imports exist, keeps the
  compiled-against line (Java Card hint + CAP format), the package/applet
  identity, the import details (family, refs, AID) and the component
  breakdown in Details; a memory-only response still renders.

Tests: Python +2 (imports/flags/name/components; header flags + package
name) with the synthetic CAP builder extended; frontend +2 renderer cases
(unknown AIDs, no-import responses) plus jcAidNorm/jcAidFamily tests; the
jcAidNorm extraction added to the ram/scp81 harnesses.
Help EN/RU, docs/api.md, AGENTS.

591 frontend / 473 Python green; version 3.5.16; sw simple-v269.
2026-09-27 02:23:03 +03:00
catarrh 18e0db75a9 feat: approximate NVRAM requirement from the CAP analysis (v3.5.15)
GlobalPlatform models this explicitly (GP Card Spec v2.3.1 Table 11-48,
load parameters): C6 = non-volatile code, C7 = volatile data, C8 =
non-volatile data, and 11.5.2.3.7 - when the card makes no code/data
distinction the required minimum is C6 + C8.  The analysis now reports it:

- capmem.memory_json() accepts the load file size (all CAP components -
  the package image the card stores) and returns code.load_file,
  nvram.requirement = load_file + persistent data, and sets the suggested
  C6 to the load file (the bytecode-only Method.cap figure stays in
  code.method_component); _cap_info_body passes the size it already
  computed.
- The CAP estimate box (both the RAM installer and the SCP81
  Install-from-.cap form) leads with "NVRAM requirement ≈ code image +
  data" plus the RAM estimate, keeps the bytecode/other-component split and
  the full data breakdown in Details, and carries the GP citation with the
  caveats (card memory management, allocation rounding and the registry
  entry are not included; the static field image appears in both parts).
- An older server response without code.load_file still renders (the
  bytecode size is used as the fallback).

Tests: Python +1 (load-file semantics) with extended cap-info assertions,
frontend +1 (renderer + fallback).  Help EN/RU, docs/api.md, AGENTS.

587 frontend / 472 Python green; version 3.5.15; sw simple-v268.
2026-09-27 02:14:49 +03:00
catarrh 2d2a40a73c fix: follow-up test-script review findings (v3.5.14)
- The SCP80 counter write-back resolves the preset by NAME first (the run
  snapshot prefers the name) and only falls back to an ICCID-looking
  value: presets with digits in their names are found after a page reload
  (the previous fallback ran cardsNormIccid on the name and gave up).
- testScriptProblem validates the *selected* SCP80 source (like the form
  and the server), so source=apdu with only sp filled is caught locally
  instead of failing with a server 400.
- _test_run_start cleans up (_TEST_RUNNING=False, run state error) when
  the worker thread cannot be created/started, and the endpoint answers
  500 with a clear error instead of leaving the card blocked behind a run
  that never started.
- The 5 s poll writes the counter back whenever a run is finished
  (idempotent), so a reloaded page saves it without visiting the pill.
- Mask wildcards ('?') are stripped from the hex fields that cannot carry
  a mask (APDU, secured packet, event/file data, TAR/SPI overrides, DCS,
  extra TLVs); check values keep them.

Tests: frontend +3 (write-back by name / by ICCID, mask-free fields,
source-aware script check), Python +1 (failed thread start unblocks).
586 frontend / 471 Python green; version 3.5.14; sw simple-v267.
2026-09-27 01:52:03 +03:00
catarrh d1eb88d4d7 fix: test-script review findings (v3.5.13)
PWA:
- The SCP80 "Source" switch sticks: the choice is stored in
  `params.source`, switching keeps both values (the server honors the
  explicit source when both are present, and the form validates only the
  selected one).
- The SW check field is empty by default (placeholder "default: 9000
  (91?? when polling)"), so the server defaults apply - previously the
  pre-filled 9000 defeated the polling STATUS default and a card that
  announced a command made the step fail with "expected 9000".
- Item text checks offer contains/exact only (the mask mode was rejected
  by the server validation).
- The SCP80 counter is written back to the preset even when the run is
  observed after a page reload or was started elsewhere (resolved by
  ICCID or preset name from the run snapshot).

Server:
- Step-entry mutations happen under `_TEST_LOCK`
  (`_test_entry_update`/`_test_finish_entry`): the status endpoint
  serializes the state with json.dumps, so entries must not change while
  it iterates them.
- The pending-command drains (unexpected command, error, stop) hold
  `_CARD_LOCK` like every other card conversation.
- `_int` accepts plain decimals with leading zeros and 0x hex.
- The scripted TERMINAL RESPONSE text string uses the CR-set tag `8D`
  (consistent with the other TR TLVs; both are legal).
- A script-driven menu selection mirrors `server.menu_active`.

Tests: frontend +4 (source switch/round-trip, status SW default, render
checks, item modes), Python +3 (integer parsing, menu_active, source
selection).  Help/docs unaffected beyond api.md's `source` note.

583 frontend / 470 Python green.
2026-09-27 01:48:12 +03:00
catarrh d1c6a9c27a ui: rename the Phone simulator tab to Simulator and the eSIM pill to eSIM LPA (v3.5.12)
- Top-level tab label "Phone simulator" -> "Simulator" (RU "Симулятор");
  the DOM ids stay tab-phone / phone-sub-*.
- The eSIM pill inside the Simulator tab is now "eSIM LPA" (EN/RU),
  matching the section's ES10/LPA scope; the panel id stays phone-sub-esim.
- Documentation updated: help EN/RU (section 8 Simulator, list of the four
  pills including Test script, tab lists and cross-references),
  README/README_RUS (tab lists, Simulator section, eSIM LPA), AGENTS
  repo/root.
- Test titles updated; version 3.5.12; sw cache simple-v265.

579 frontend / 467 Python green.
2026-09-27 01:33:22 +03:00
catarrh 722e237ea4 feat: Test script pill in the Phone simulator (v3.5.11)
- Phone simulator tab: a fourth pill **Test script** - script list
  (localStorage `simple_tests`) with New/Clone/Delete/Export/Import, a
  form-based step editor (modal) and a run panel.
- Actions: ENVELOPE (Event Download), Menu Selection, UPDATE/READ file by
  path, raw APDU, SCP80 (C-APDU or a pre-built packet, per-step TAR/SPI1/
  SPI2 overrides; the preset is matched by the equipped card's ICCID and
  the final counter is written back) and STATUS with an optional poll.
  Every action has SW/data checks (exact or ?-mask) and a PoR check for
  SCP80, with an error (stops) / warning (continues) fail level.
- Expectations: FETCH the command announced by the previous step, verify
  type/qualifier/text/item/raw and answer with the scripted TERMINAL
  RESPONSE (result, item id, text, extra TLVs); templates for STK menu
  browsing and an applet via SCP80.
- Run panel: progress banner (plus a marker on the pill), per-step
  OK/Warning/Error badges with expected vs actual values, sent/received
  hex, decoded commands and the SCP80 counter; 500 ms polling while a run
  is active, and a running script disables other card controls
  (data-needs gate; the server answers 409 to other card endpoints).
- Help EN/RU (8.9 Test script), AGENTS; version 3.5.11; sw simple-v264.

579 frontend / 467 Python green.
2026-09-27 01:19:32 +03:00