Commit Graph

120 Commits

Author SHA1 Message Date
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
catarrh 4c3fa64c95 feat: polling on by default and an effective-state toggle (v3.5.10)
- Background STATUS polling is enabled by default, per the spec's idle
  polling rule (TS 102 221 14.6.2): `_POLL_ENABLED = True`, __main__ runs
  `_poll_enable()` regardless of card presence (--poll-interval 0 still
  disables) and `_do_status_poll` keeps ticking while enabled even without
  a session, so a cardless start resumes as soon as a card appears.
- A new card session clears a POLLING OFF from the previous card in
  `_apply_equipped_card` before re-enabling polling.
- The Phone-tab toggle now shows the *effective* state: OFF in amber
  ("card disabled polling (POLLING OFF)") while the card suspended
  proactive polling, back to ON on the next POLL INTERVAL.  The 5 s
  background poll now passes `card_disabled` through (it was dropped, so
  an autonomously received POLLING OFF never reached the UI).
- Tests: poll_ui updated (button OFF while suspended, warning path),
  test_poll +2 (cardless ticking, module default via a subprocess check).
- Help EN/RU, docs/api.md, AGENTS; version 3.5.10; sw cache simple-v263.

573 frontend / 441 Python green.
2026-09-26 13:09:44 +03:00
catarrh 25f001a778 feat: card-driven STATUS polling and the Poll Interval Negotiation event (v3.5.9)
- EVENT_NAMES (server + PWA) corrected against TS 102 223 v18.3.0 8.25:
  0x14 is "Access technology change (multiple)" (not Change of UICC
  Access), 0x19 Profile container, 0x1A Void, 0x1B Secured profile
  container, 0x1C Poll interval negotiation, 0x20-0x22 reserved.  Values
  the CAT spec leaves "Reserved for 3GPP" now carry the concrete TS 31.111
  event name + clause (0x11 (I-)WLAN access status, 0x12 Network
  rejection, 0x15 CSG cell selection, 0x17 IMS registration, 0x18 Incoming
  IMS data, 0x1D Data connection status change, 0x1E CAG cell selection,
  0x1F Slices status change).
- Poll Interval Negotiation (0x1C) in the Phone tab: the form proposes a
  Duration (unit + interval) and the UICC's answer (TS 102 223 8.97
  accepted / rejected / modified + optional Duration) is decoded and
  shown; a "modified" duration becomes the background poll interval.  The
  form appears when the card subscribed to the event, like every other.
- Polling emulation is card-driven: a POLL INTERVAL adopts its Duration
  (minutes/seconds/tenths -> 1..255 s, logged) for the background poll,
  is echoed in the TERMINAL RESPONSE (6.8.4) and clears a POLLING OFF
  suspension; POLLING OFF (6.4.14) suspends proactive polling until a new
  POLL INTERVAL - the manual Send STATUS button and presence detection are
  unaffected.  /api/poll-status and /api/poll-toggle report card_disabled
  (+ a warning when enabling while suspended); the PWA shows it in amber.
- Tests: tests/test_poll.py +7, test_proactive_names.py +3, frontend
  event_forms +2 and poll_ui (4).  Help EN/RU, docs/api.md, AGENTS.

573 frontend / 439 Python green; version 3.5.9; sw cache simple-v262.
2026-09-26 12:56:18 +03:00
catarrh d9e6c6c9dd feat: CAP memory estimation with a confirm step before install (v3.5.8)
Selecting a .cap in the RAM installer or the SCP81 "Install from .cap"
template now runs a read-only analysis (POST /api/cap-info) before any
APDU is built: the archive is validated structurally (a corrupt or
wrong-format file fails here) and the bundled capmem analyzer estimates
the code size and the persistent (NVRAM) / volatile (RAM) requirements,
with the tool's suggested C6/C7/C8 quotas shown as information.  The
form's action button (Execute / Generate) stays disabled until the
analysis succeeds - pressing it is the user's confirmation to continue.

- pysim_simple_server/capmem.py: bundled analyzer (component parsers +
  JCVM opcode table + method-bytecode allocation scan), adapted to take
  the CAP archive as bytes and return report/memory dicts; output
  verified byte-identical to the workspace tool on 21 real CAPs.
- _cap_info_body + POST /api/cap-info (read-only; the install endpoints
  stay unchanged and self-sufficient).
- PWA: shared capAnalyzeFile/capMemHtml/capGateOk helpers, estimate box
  under both CAP inputs (reusing the idle #ram-cap-info div, new
  #scripts-cap-info), data-cap-gate gating in pysimApplyAvailability,
  stale-response guard, Retry, EN/RU strings.
- Tests: tests/test_cap_memory.py (synthetic CAPs: new/newarray/
  makeTransientByteArray/static fields/unknown-opcode warnings/corrupt
  input), frontend capmem.test.js (renderer, gate, analyze flow).
- help EN/RU, docs/api.md, AGENTS; version 3.5.8; sw cache simple-v261.

567 frontend / 429 Python green.
2026-09-26 08:50:46 +03:00
catarrh b88f04fc69 feat: name standard package AIDs in the Explore / SCP81 / R-APDU views (v3.5.7)
The RAM Explore view, the SCP81 GET STATUS script results and the R-APDU
parser tree showed package AIDs as bare hex.  A shared resolver now
annotates the known standard JavaCard / ETSI / 3GPP / GlobalPlatform
package AIDs with their library name (workspace export-file study
`docs/JAVACARD.md`, 2026-09-26, plus the GP default ISD AID from GP Card
Spec v2.3.1 H.1.3); a RID table gives a weak owner hint for otherwise
unknown AIDs, and vendor/applet AIDs stay bare.

- JC_AID_NAMES / JC_AID_RIDS + jcAidName / jcAidSuffix / jcAidHtml.
- Wired into ramRenderExploreHtml (ISD, applications, ELFs, module and SD
  AIDs), scp81ResultLines GET STATUS listings and decodeTlvValue
  (4F/84/C4/CC - the R-APDU parser tree).
- aid_names.test.js (families, normalisation, RID hints, malformed input,
  table sanity) plus render assertions in ram.test.js and scp81.test.js.
- Help EN/RU note the annotation; table is hand-maintained from the note.

561 frontend / 421 Python green; version 3.5.7; sw cache simple-v260.
2026-09-26 00:33:38 +03:00
catarrh 8064625d56 feat: next action suffix in the pending SELECT ITEM panel (v3.5.6)
The STK menu overlay's cached top menu showed the card-provided Items Next
Action Indicator (TS 102 223 8.24) as a gray suffix, but the pending
SELECT ITEM list the card returns mid-dialogue did not: the server already
attaches nai_name (via _parse_select_item), the panel just had its own row
markup without the suffix.

Both lists now render through stkMenuItemsHtml(items, handler) - identical
rows (id, text, optional gray suffix) with the per-list click callback
(stkMenuItemClick / stkSubItemClick).

stk_menu.test.js: the shared renderer (suffix only with nai_name, handler
in the row, null list) plus a call-site guard for both lists.

554 frontend / 421 Python green; version 3.5.6; sw cache simple-v259.
2026-09-25 23:05:22 +03:00
catarrh 31ece16ada fix: animated card icon while the card is being initialized (v3.5.5)
The Card reader view showed the static nosim.svg while the status line said
"Card inserted - initializing..." (the header indicator was correct): the
2 s /api/status poll called pysimSetConnected(false) unconditionally in its
not-connected branch, ignoring the equipping / auto-equip-pending state.

- pysimCardStateUpdate(): one `initializing` flag (equipping || card_present
  && auto_equip) now drives both the status text and the icon
  (pysimSetConnected('spin') -> sim_anim.svg), matching the header indicator.
- pysimRefresh() ("Check status"): a reachable server is not an equipped card;
  the icon now follows connected / initializing / none instead of always
  showing the equipped icon.
- card_state.test.js: initializing states -> 'spin', cardless -> false, and
  the four Check-status combinations.

552 frontend / 421 Python green; version 3.5.5; sw cache simple-v258.
2026-09-24 21:41:40 +03:00
catarrh bed66c8ff8 fix: robust equip-state detection and review follow-ups (v3.5.4)
Code-review follow-ups for v3.5.2/v3.5.1, plus a test flake found while
re-running the suites:

- F1: a half-initialized equip is no longer reported as success.  cmd2
  swallows exceptions raised inside the equip command (and prints no
  traceback by default), while PysimApp.equip() assigns card/rs before it
  registers the command sets - so app.card alone once let the "CommandSet ...
  is already installed" abort pass as done while /api/tree stayed broken.
  The auto-equip attempt now captures the output with cmd2 debug on (a
  swallowed error prints a traceback), requires the new profile's command-set
  instances to be installed (_app_equip_complete), and the manual
  /api/command equip branch applies the post-equip refresh only when that
  check passes (and reports it in the output when it does not).
- F3: SCARD_E_SHARING_VIOLATION is recoverable (a rebuild cannot free another
  process's claim) instead of transport-fatal.
- F4: SPI1 b2b1 = 11 (Digital Signature) is refused instead of building an
  unsigned packet; help notes RC is CRC-32 only (KID CRC-16 not offered).
- F5: _clear_app_card_state removes the muted stdout again when the app object
  had no stdout attribute.
- F7: the watchdog re-arm keeps its rate-limit window when the trigger is
  busy/disabled instead of consuming it.
- F2: stale docstring in _auto_equip_attempt.
- tests: equip-state units, half-equip and captured-traceback failures,
  unequip-on-failure, busy trigger, sharing violation, DS refusal.
- bonus: the MCC/MNC random-pick test could fail because a dict keyed by
  (mcc, mnc) keeps one of two entries (the bundled list carries both a real
  and an MVNO entry for 234/18 and 234/28); the picker was correct - the
  assertion now checks the pair against the non-MVNO pairs.

549 frontend / 421 Python green; version 3.5.4; sw cache simple-v256.
2026-09-24 20:52:49 +03:00
catarrh 337e5df770 feat: decode the Items Next Action Indicator in proactive commands (v3.5.3)
SET UP MENU / SELECT ITEM items carry an optional Items Next Action Indicator
(TS 102 223 8.24, tag '18'): one byte per item, in list order, coded with the
Table 9.4 values marked "Used for Next Action Indicator".  The decoder ignored
it, so neither the proactive log nor the STK menu showed what the card would
do when an item is selected.

- server: NAI_TYPES whitelist (the ToC-only values - e.g. '26', '27', '47' -
  are reserved there and are ignored, as are '00' and unlisted values),
  _nai_name(), _attach_nai() (a short NAI list leaves the tail without an
  indicator, extra bytes are ignored); _parse_select_item() and
  _parse_setup_menu_items() plus the TERMINAL PROFILE SET UP MENU walk attach
  nai/nai_name to the items; _decode_cmd() renders '1. Menu -> SET UP MENU'.
- PWA: stkMenuNaiSuffix() adds a small gray '<name>' suffix to STK menu items
  that carry an NAI; the proactive log picks the decode up via cmd_decoded.
- tests: SET UP MENU / SELECT ITEM vectors with NAIs, a reserved value that is
  ignored, a short NAI list, and the stkMenuNaiSuffix unit test.
- docs: UICC_SPECS 6.5 gained the '18' Items next action indicator row
  (8.24/9.4); help EN/RU mention the menu suffix; AGENTS files updated.

548 frontend / 413 Python green; version 3.5.3; sw cache simple-v255.
2026-09-24 20:37:53 +03:00
catarrh 0f8c6dea50 fix: recover from a card swap without a server restart (v3.5.2)
Equip was broken after a card swap: auto-equip (and manual Equip) failed with
"Failed to transmit with protocol T0. Card was removed. (0x80100069)" until
the server was restarted.

Chain (v3.1.2 regression):
- _handle_card_disconnect() cleared server.card/scc but not pySim's
  app.card/app.rs/app.lchan, so the removed card - and with it the old PC/SC
  link and its exclusive card handle - stayed referenced; handlers using
  app.rs (e.g. the PWA's /api/tree poll) kept transmitting over the dead card.
- those errors carry hresult=0x80100069 (SCARD_W_REMOVED_CARD), but
  _is_pcsc_error() treated any PC/SC error as a dead service and set
  _TRANSPORT_STALE; the next auto-equip then called _ensure_transport(),
  which built a second PcscSimLink while the old one was still connected -
  the new link inherited the removed card's handle and failed on its first
  APDU.  No retry existed, so every later equip repeated the failure.

Fixes:
- _is_transport_fatal(): only service/context hresults rebuild the transport
  (E_NO_SERVICE, E_SERVICE_STOPPED, E_NO_READERS_AVAILABLE, E_INVALID_HANDLE,
  ...); card-level states (W_REMOVED_CARD, E_NO_SMARTCARD, W_RESET_CARD,
  W_UNRESPONSIVE_CARD, W_UNPOWERED_CARD) reconnect on the existing link.
  All 8 disconnect call sites pass the new verdict.
- _clear_app_card_state(): unequip through pySim's own equip(None, None)
  before clearing app.card/app.rs/app.lchan.  Nulling them alone would make
  the next equip abort with "CommandSet ... is already installed"
  (PysimApp.equip() unregisters the previous profile's command sets from
  self.rs), which left /api/tree broken after a swap.  A failed auto-equip
  attempt unequips the half-initialized shell as well.  Handlers now answer
  "no card" instead of transmitting over the dead card, and the old link
  becomes collectable.
- _ensure_transport(): disconnects the old link before building the new one
  (restoring it if the factory fails) - the rebuild path is now safe for the
  real pcscd-restart case.
- auto-equip: _auto_equip_attempt()/_auto_equip_attempts() retry up to 3
  times, 1 s apart (fatal failures mark the transport so the retry rebuilds);
  the watchdog re-arms it (_auto_equip_rearm) every 5 s while a card is
  present and the session is down, with exponential backoff to 60 s for a
  card that cannot be initialized at all.
- fastinit.init_card_fast(): also retries once after a PC/SC link error
  (CardConnectionException/NoCardException), not only after SW mismatches.

Tests: transport-fatal classification, app-state clearing, shell unequip,
old-link release, auto-equip retry/backoff/re-arm, fastinit link retry.
547 frontend / 410 Python green; version 3.5.2; sw cache simple-v254.
2026-09-24 09:01:46 +03:00
catarrh 73ff6a60bd fix: SCP80 RC/CPL parity, file-life-cycle labels, EF.ARR and PS template decoders (v3.5.1)
Three fixes found while filling the ETSI/3GPP registry gaps against the
SIMalliance Stepping Stones R7:

- SCP80 builder/verification parity (TS 31.115 Table 1 NOTE / 4.2 / 4.3):
  the CPL is now transmitted whenever the packet is ciphered or carries
  RC/CC/DS - it is part of their input - and whenever the packet needs SMS
  concatenation; a single unprotected SM keeps pySim's CHL-first form.
  Before, the JS dropped the CPL for every unciphered packet while the
  server reference re-added it, so "Verify vs pySim" reported a false
  MISMATCH for every unciphered RC/CC packet (SPI1 01/02/0A/12/1A...).
  All ten offered SPI1 values now match the server reference byte-for-byte.
- RC (SPI1 b2b1 = 01) was offered but not built: the JS now computes CRC-32
  (TS 102 225 5.1.3.2, pySim zlib.crc32 parity) over the same CPL frame as
  the CC; the packet no longer silently omits the 4-byte RC field.
- Server: _build_secured_packet/_ota_reference add the CPL to a concatenated
  unprotected packet too (Table 1 NOTE / 4.3).

- fcpLifeCycle: unlisted values with b8 clear are RFU, b8 set is proprietary
  (Table 11.7b); previously all unmatched values were labelled proprietary.
- EF.ARR decoder now decodes the expanded format (AM_DO/SC_DO per ISO 7816-4
  5.4.3.2 + TS 102 221 9.2.7): operation bit masks, INCREASE/RESIZE AM_DO
  0x84, OR/AND/NOT templates, PIN key references with usage qualifiers.
- FCP 'C6' PS template DO decoded (PS_DO bitmap + key references + usage
  qualifiers, TS 102 221 11.1.1.4.10/9.5.2) with a shared key-reference map.

Tests: sp.test.js (CPL/RC vectors + crc32 known answer), ef_decode.test.js
(expanded-format ARR vectors), profiler.test.js (LCSI RFU/proprietary, C6),
test_ota_helpers.py (RC reference, unprotected single-SM vs concatenated).
Help EN/RU and READMEs: CPL size 2 (SMS), RC/CC/DS 4-8, RC bullet, CPL rule.
547 frontend / 397 Python green; version 3.5.1; sw cache simple-v253.
2026-09-24 08:05:07 +03:00
catarrh f17e7cb8da feat: Push commands pill — GP administration trigger + TS 102 226 §9 pushes (v3.5.0)
The former HTTP OTA pill becomes "Push commands" (RU: Пуш/триггер) and groups
everything that makes the card dial out:

- Administrative session (GP Amd B §4.7): the existing Trigger/Store UI,
  unchanged, as the first section (most used).
- BIP / CAT_TP trigger (§9): request 01 BIP channel opening (optional OPEN
  CHANNEL COMPREHENSION-TLVs) or 02 CAT_TP link (destination port, optional
  max SDU / identification data).
- TCP trigger (§9): request 03 TCP connection (bearer, transport level with
  protocol type 02, destination address 21/57/F0, NAA/APN, extra TLVs) or
  04 identification packet (optional data, ICCID when absent — sent over an
  already open channel).

The guided sections reuse chainPushData (the RAM/GP chain PUSH encoder) and
preview the C-APDU; mandatory parameters (02 port, 03 port + address) are
enforced there while the chain row stays lenient.  Actions: Pack into Secured
packet (no auto-TAR — a §9 PUSH goes to the target application) and
→ Expanded Script (22 Command TLV import; berAppendApdu extracted from
loadExpandedScript).  Each §9 request has a note separating it from the
administration session.  Chain-builder PUSH command and the expanded-script
C-APDU picker are unchanged.

Tests: pushSectionApdu vectors in ts102226.test.js; help 2.7 EN/RU
restructured, READMEs, AGENTS; sw cache -> simple-v246.
2026-09-23 08:57:59 +03:00
catarrh b8734189cf feat: offline file list + SELECT picker for the SIM/USIM RFM builders (v3.4.0)
The SELECT rows (compact RFM chains and the Expanded Script C-APDU picker,
which shares the row editor) now have a file picker instead of typing FIDs,
paths or chains by hand.

Data:
- `pysim_simple_server/uicc_files.py` builds the standard file list cardless
  from pySim's profiles and application classes (CardProfileUICC + CardProfileSIM
  for the MF tree, every concrete CardApplication subclass for the ADFs) and
  writes `frontend/uicc_files.json` (490 entries: canonical FID path, symbolic
  name, fid, kind, root, ADF AID).  `tests/test_uicc_files.py` regenerates it
  and fails when pySim adds or renames files.  The asset is precached by the
  service worker, so the builders keep working offline.

Picker:
- Sources merged per canonical path: the loaded file-manager tree (card names
  and probed presence; probed-absent files hidden), custom files, and the
  shipped standard list (specs-default until "Probe all files" has run).
- Default list shows what the current method can express; "all files" reveals
  the rest, and picking one auto-switches the method - preferring path (one
  SELECT) over chain - with a note line explaining the switch.  A "starts in"
  selector sets the implicit current DF (UICC shared-FS RFM app starts in MF,
  an ADF RFM app in its ADF, TS 102 226 7.2/7.3 - the TAR decides; default per
  builder, overridable per row).
- Fill rules: by FID only for direct children of the current DF; path = FID
  sequence from MF without the MF identifier (ISO 7816-4) or the relative tail
  (USIM P1=09); chain stays relative and follows the TS 102 221 11.1.1.2 FID
  search order (children, parent, siblings), so no hop to the common ancestor.
  ADF roots are not pickable (selection is by AID; TS 102 226 7.1 forbids
  P1=04 for RFM) - the By AID method stays manual.

Tests/docs: frontend/tests/uicc_files.test.js (asset shape, merge/priority/
exclusion, fill matrix, relative chains, session-context tracking, option
grouping); help 2.1/2.2 EN+RU, READMEs, AGENTS.  sw cache -> simple-v242.
2026-09-23 08:20:10 +03:00
catarrh 7aee1818b8 feat: remote-script command palette — RFM/RAM commands in Expanded Script, TS 102 226 §9 PUSH (v3.3.0)
Phase 1 — the Expanded Script C-APDU rows are no longer hex-only:
- chain containers gained a kind (chainKind/chainIsEmbedded/chainCommands),
  so the SIM RFM / USIM RFM / RAM-GP row editors and hex builders can be
  embedded as one-row "virtual" chains (ber-<kind>-<uid>) with a listener
  hook (chainListen/chainNotify) refreshing the owning row.
- the C-APDU row offers Hex / SIM RFM / USIM RFM / RAM-GP; the built APDU is
  echoed next to the picker and wrapped in the 22 Command TLV. GET RESPONSE
  is not offered in the embedded pickers (TS 102 226 5.2.1.1).

Phase 2 — "→ Expanded Script" in the SIM RFM / USIM RFM / RAM-GP views
imports the built chain as C-APDU rows (chainApduList drops GET RESPONSE
and splits multi-APDU FID-chain selects; the RAM "To expanded" preview
stays).

Phase 3 — the TS 102 226 table 7.1/8.1/9.1 gaps:
- RFM: SEARCH RECORD (TS 102 221 11.1.7 / SEEK per TS 151 011 9.2.7),
  INCREASE (11.1.8 / TS 151 011 9.2.8), CREATE FILE / DELETE FILE /
  RESIZE FILE (TS 102 222 6.3/6.4/6.10, with an FCP skeleton builder and
  CLA 80 for RESIZE), SET DATA / RETRIEVE DATA (11.3, block + SFI coding);
- RAM: PUT KEY (GP Card Spec 11.8) and the TS 102 226 §9 PUSH command
  (80 EC 01 P2) with BIP opening (optional OPEN CHANNEL TLVs), CAT_TP
  (3C/39/36), TCP (35/3C/3E/47) and identification-packet variants.

Tests: new frontend/tests/ts102226.test.js (byte-exact spec vectors for
every new command, the FCP skeletons, chainApduList) plus C-APDU row tests
in ber.test.js; 530 frontend / 390 Python green. Help EN/RU, README/RUS
and AGENTS document the Command TLV taxonomy (Tables 5.5/5.9 limits), the
picker and the new commands.
2026-09-23 07:34:21 +03:00
catarrh 43f50d3b72 feat: SCP80 SMS concatenation + packet size / SMS count display (v3.2.0)
Packets longer than one SMS now go out as concatenated SMS-PP downloads
per TS 31.115 4.3 and the UI shows how many SMS a packet needs.

Server:
- `_split_secured_packet` cuts the command packet at the exact SMS
  user-data capacities (first SM 132 octets: concat IE 5 + CPI IE 2;
  following ones 134; a single-SM packet may be 137 with the CPI IE) and
  `_build_sms_tpdu` tags every segment with the fixed concatenation
  reference 01; `_build_sms_tpdu` also enforces the 140-octet budget.
- `_send_secured_packet` (shared by /api/send-ota and /api/ram-install)
  sends one ENVELOPE per segment in order, refuses more than
  MAX_ENVELOPE_SEGMENTS (5, the card's concatenation buffer) and reports
  `bytes`/`segments` in the response (RAM install per step as well).
- `_build_secured_packet`/`_encode_cmd_unlimited`: our own TS 102 225
  5.1.1 encoder on pySim's keyset/header constructors, byte-identical to
  pySim for packets <= 140 octets (tests) and not limited to one SMS
  (pySim refuses the longer ones, which is why they never went out).
- RAM LOAD blocks are no longer clamped to one SMS: 1-240 bytes of
  payload with the default 240 (the GP maximum); `load_block_size_auto`
  replaces `load_block_size_clamped`.

PWA:
- `scp80SegmentInfo` / `spSizeInfoText` show "N bytes . M SMS
  (concatenated)" under the packet field, turn red past 5 SMS and report
  size/SMS in the send result and the RAM step log; LOAD block hints and
  placeholders updated; EN/RU.

Tests/docs: Python +2 cases incl. segment order/capacities and byte
identity with pySim; Node drift guard against the server constants and
UI text tests; README/README_RUS, help EN/RU, docs/api.md, AGENTS.
2026-09-22 23:02:13 +03:00
catarrh f72054f61f ui: stop showing the all-FF record marker as a field; label it in the diff (v3.1.4)
A profile check comparing an empty PNN record against a populated one
showed a cryptic 'Empty: yes' row: the decoders return {empty: true} for
an all-FF record and efFlatten() rendered that marker as a pseudo-field
('empty' prettified to 'Empty', the boolean as 'yes').

- efFlatten() skips the marker (the decoded views already show their own
  translated 'empty' note next to the record).
- efDiffData() fills the cells of a side that decodes to an all-FF record
  with 'Empty (all FF)' (RU: 'Пусто (только FF)') instead of leaving them
  blank; a field merely missing from a non-empty record stays blank.
- tests: efFlatten marker, one-sided and both-sided empty diffs, and the
  PNN case from the report; help EN/RU note the new text.
- sw.js simple-v239.
2026-09-22 22:26:20 +03:00
catarrh fa600cb45d ui: rename the SCP81 script templates to Explore ISD / Delete AID (v3.1.3)
'Explore' and 'Delete' were ambiguous next to the other SCP81 actions.

- template selector: Explore -> Explore ISD, Delete -> Delete AID (EN/RU);
- a new Explore script is named 'Explore ISD'; the Delete template keeps
  its count-based name (e.g. 'Delete 2 AIDs');
- new scriptKindLabel() maps the stored kinds (unchanged:
  explore/delete/install/empty, so existing localStorage scripts keep
  working) to those labels and is now used for the scripts table kind
  column, which used to print the raw lowercase kind untranslated;
- docs: help EN/RU, README/RUS, docs/api.md examples, AGENTS;
- sw.js simple-v238.
2026-09-22 22:07:57 +03:00
catarrh b87b3905ee fix: recover from a PC/SC service failure without a server restart (v3.1.2)
After pcscd restarted (or the reader re-enumerated) the server stayed
permanently cardless:
- pyscard's presence-monitor thread stops itself on SCARD_E_NO_SERVICE
  and pyscard never starts it again, so card insertions were no longer
  noticed and auto-equip was dead;
- PCSCCardConnection.connect() reuses the context handle captured when
  the reader was opened, so the old connection could not be revived and
  every APDU kept failing with 'Service not available'.

- _start_card_watchdog(): a daemon that every 5 s checks whether
  CardMonitor().rmthread.instance is alive and recreates the stopped
  thread (CardMonitoringThread.instance = None + a fresh rmthread); the
  new thread reports already-present cards as inserted, which triggers
  auto-equip.  start_card_monitor() starts the watchdog.
- _is_pcsc_error(): pyscard exceptions carry an hresult, card-level
  errors do not; the disconnect paths (AUTO-STATUS, status poll, timer
  expiration, net-sim, tree, event send, OTA send) pass the verdict to
  _handle_card_disconnect(stale=...), which marks the transport stale.
- _ensure_transport(): builds a fresh transport via
  server.transport_factory (installed by __main__ as
  mod.init_reader(opts, **tracer_kwargs)) and installs it into
  app.sl/server.sl; called by the auto-equip worker, the /api/command
  equip branch and _esim_reinit.
- tests: watchdog tick, transport recreation (fresh/stale/failure/no
  factory), PC/SC classification, stale disconnect flag.
- docs: AGENTS card-behavior section, README troubleshooting (EN/RU);
  version 3.1.2, sw.js simple-v237.
2026-09-22 22:01:28 +03:00
catarrh 7fd3a3c973 fix: emit wire-shaped TPDUs in the GSMTAP stream (v3.1.1)
The GSMTAP stream sent logical APDUs, so a receiver (SIMtrace Analyser,
Wireshark) flagged almost every packet: case-4 commands carried their Le
byte ('length_mismatch: excessive') and responses were headerless packets
decoded as bogus commands ('truncated').  The ATR also arrived in the
middle of the stream.

- gsmtap.py: the tracer now emits wire-shaped TPDUs, matching a hardware
  sniffer capture: case 4 -> the command without Le plus the 61XX 'bytes
  available' SW (derived from the real response length), then the data as
  a GET RESPONSE TPDU (00C00000<len> + data + SW, chunked at 255); case
  1/2/3 -> one packet cmd + data + SW; unparseable APDUs fall back to raw
  command/response packets.
- No ATR/VCC/RST/PPS events at all (no line-level access over PC/SC);
  the ATR sending helper and the server-side plumbing were removed.
- Verified by replaying the bad capture's exchanges through the new
  tracer: 116 packets, zero decoder warnings (previously nearly all).
- tests: the per-case wire forms, chunking, fallbacks; README/AGENTS
  document the wire shape and its limits; version 3.1.1, sw simple-v236.
2026-09-22 07:56:21 +03:00
catarrh a387cdc1b6 feat: GSMTAP-SIM APDU streaming for Wireshark / SIMtrace Analyser (v3.1.0)
Streams every APDU the server sends or receives as GSMTAP-SIM UDP packets,
so a live capture can be followed in Wireshark or the SIMtrace Analyser
without a hardware sniffer.  CLI-only: --gsmtap [HOST[:PORT]], default
target 127.0.0.1:4729; no UI or API.

- pysim_simple_server/gsmtap.py: 16-byte big-endian GSMTAP-SIM header
  (type 0x04, sub_type 0x00 = APDU / 0x01 = ATR) + raw APDU bytes, a
  fire-and-forget non-blocking sender that never raises into card I/O, an
  ApduTracer (a response is sent as data + SW1SW2, the wire form) and a
  fan-out tracer.  The packet layout is byte-identical to
  sigrok-iso7816-stream / simtrace2-sniff (verified against the sigrok
  module) and is what the analyser's GSMTAP receiver expects.
- __main__.py: --gsmtap option, tracer installed on the shared transport
  before the first APDU, combined with --apdu-trace via the fan-out and
  re-attached across equips (pySim nulls the tracer on every equip); one
  ATR packet per equip from _apply_equipped_card/_send_gsmtap_atr.
- start.sh/start.bat: forward their extra arguments to the server, so
  ./start.sh --gsmtap works.
- tests: packet layout, loopback UDP delivery, tracer mapping, target
  parsing, fan-out; docs (READMEs, help EN/RU, AGENTS); version 3.1.0,
  sw.js simple-v235.
2026-09-22 01:41:14 +03:00
catarrh d087632573 docs: align the READMEs, help and API reference with the current UI (v3.0.1)
- docs/api.md: document GET /api/net-state and POST /api/net-state-refresh
  (state shape, monitored keys, refresh filter, 503); add eid/euicc to
  /api/status; correct the /api/net-sim FPLMN wording (roaming_denied
  appends, attach clears; 5GS files untouched) and its response
  (net_state); move the stray /api/event-send example back into its
  section; refresh the version examples to 3.x.
- README/RUS: Cards — ADM field, the three TARs, the SCP80/SCP81
  fieldsets and the header markers; File Browser — Read raw/Read decoded
  pills, Edit raw, named FCI block, Verify ADM; Profiler — Clone, three
  list tabs; Custom files — root/parent/FID/alias form, canonical paths,
  cascade delete, legacy-path resolution; Phone simulator — three pills,
  eSIM, Network state monitor, Home network button, corrected FPLMN
  wording, proactive-log row content; RU CLI table gaps (--sms-oa/
  --sms-sm-sc, --terminal-profile, --mcc-mnc-list) and the missing
  event-form bullets.
- help EN/RU: three profiler list tabs, 3.x compatibility example.
- Version 3.0.1 (docs release), sw.js simple-v228.
2026-09-21 23:38:59 +03:00
catarrh 563b211e15 fix: serve the eSIM GET endpoints from _do_GET; release v3.0.0
The three eSIM GET routes were inserted after the POST-only
/api/verify-adm branch, i.e. into the _do_POST chain, so GET
/api/esim/chip|profiles|notifications fell through to the 404 handler
(only POST /api/esim/profile was reachable).

- Moved /api/esim/chip|profiles|notifications into _do_GET (after
  /api/status); /api/esim/profile stays in _do_POST.
- Regression test: inspect the handler sources and assert each route is
  in the right chain.
- Version 3.0.0 (eSIM support is a major update): server/pyproject/PWA
  header, sw.js simple-v227.
2026-09-21 23:27:16 +03:00
catarrh 69b4caf5d8 fix: restore MF after fast-init probing so eUICC cards init cleanly (v2.8.1)
With an eUICC in the reader the fast init raised SwMatchError twice and
fell back to the stock pysim init: pick_profile_no_reset() disables the
physical resets that CardProfile.match_with_card() normally performs, so
the successful SGP.22 probe leaves the ISD-R ADF selected.  RuntimeState
then selects MF by FID (00 A4 00 04 02 3F00) from within the ADF, which
this card answers with 6A82.

- _restore_mf_after_probe() re-selects MF after the profile pick: the
  cheap select keeps the reset-free path for normal cards, a physical
  reset covers cards that refuse the MF select from an ADF.
- The EID read restores with rs.reset() (soft reset, escalating to a
  physical reset) instead of rs.soft_reset(), which had the same trap.
- tests: FakeScc mf_select_error mode + two _restore_mf_after_probe
  cases (no reset / exactly one physical reset).
2026-09-21 23:20:50 +03:00
catarrh a4d5415034 esim: local eUICC operations in the Phone simulator tab (v2.8.0)
New eSIM pill (Phone simulator) for SGP.22/32 cards, built on pySim's
ES10 static API — no lpac, no new dependencies, no pysim patches, no
SM-DP+ interaction:

- Chip: EID, EUICCInfo1/2, configured addresses (ES10a/b).
- Profiles: GetProfilesInfo with metadata (state, nickname, provider,
  ICCID, ISD-P AID, class, owner, icon).
- Notifications: read-only ListNotification viewer.
- Switch: Enable/DisableProfile with RefreshFlag=1; the card's REFRESH
  (fetched/answered/logged by the transport's proactive handler) or an ok
  result triggers _esim_reinit() — reset + equip + _apply_equipped_card —
  so the ICCID, network state and cached views are re-read.
- Server: pysim_simple_server/esim.py, GET /api/esim/chip|profiles|
  notifications, POST /api/esim/profile (all under _CARD_LOCK; 400
  not_an_euicc), euicc/eid in /api/status.
- Tests: tests/test_esim.py (fake scc, monkeypatched store_data_tlv),
  frontend/tests/esim.test.js; help EN/RU, docs/api.md, AGENTS.
2026-09-21 23:10:52 +03:00
catarrh e3502c059e fix: use the Network Name spare-bit count for PNN GSM-7 text (v2.7.21)
EF.PNN decoded 'Miranda@' for the live card's record 2: efPnnText ignored
the number of spare bits in the coding octet (TS 24.008 10.5.3.5a bits
1-3) and unpacked floor(8n/7) septets, so the 7 zero pad bits became '@'.

- gsm7Decode(octets, septets?) takes an exact character count and removes
  a final <CR> used as padding (TS 23.038 6.1.2.1.1).
- efPnnText derives the count from the spare field ((8n - spare)/7 when
  1-7; floor(8n/7) + CR drop for 000 = no information).
- tests: the seven live EF.PNN records (Alfa, Miranda, Win, Mir Telekom,
  +7 Telekom, Fenix, MKS), a UCS2 record and the CR-padding drop.
- docs/uicc/UICC_SPECS.md: CR-padding rule in 10.4 and a new 10.7
  Network Name coding section (outside the repo).
2026-09-21 22:37:26 +03:00
catarrh f8ba5dea2c fix: use the card's own SimCardCommands for ADM and AUTHENTICATE (v2.7.20)
/api/verify-adm sent VERIFY with CLA A0 on a UICC (SW 6E00) while
pySim-shell's verify_adm worked: fast init builds the card on its own
SimCardCommands instance, but __main__ kept server.scc at the startup
placeholder left at the SIM defaults; only an equip repointed it.

- __main__ adopts card._scc after init (cat_cla still set on it), so
  server.scc carries the card's cla_byte/sel_ctrl from startup on.
- _verify_adm prefers app.rs.lchan[0].scc / app.card._scc, exactly like
  pySim-shell's verify_adm, independent of server.scc.
- netsim AUTHENTICATE follows the card class: a UICC gets 00 88 00 81 22
  (RAND+AUTN, DB/DC response), a SIM gets A0 88 00 00 10 (RAND only,
  SRES+Kc); the 61xx GET RESPONSE uses the same CLA.
- tests: ADM with a stale placeholder scc; 2G builder/parser; SIM-CLA
  runner case; help EN/RU and AGENTS updated.
2026-09-21 22:13:52 +03:00
catarrh 0446d2a93c ui: FPLMN manual-selection clear and duplicate guard (v2.7.19)
Attaching to a PLMN listed in EF.FPLMN used to write successful locations
anyway, i.e. it attached to a forbidden network.  Per TS 23.122 a
successful manual selection removes the entry, so the attach scenarios
clear it first:

- netsim.remove_fplmn() clears every occurrence of the PLMN (entries are
  not compacted; FFFFFF gaps stay); insert_fplmn() returns None when the
  PLMN is already listed, so roaming_denied no longer stores duplicates
  (the live card had '250-99, 250-99').
- clear_fplmn() step, called from write_real_locations() -> covers
  attach_eps, attach_2g and the sms_received location rewrite; the write
  is logged as a normal fplmn update_binary.
- tests: remove_fplmn duplicates/absent/gaps; roaming_denied duplicate
  skip; attach clears both occurrences before the location writes;
  make_runner now copies FakeFileInfo so seeded data does not leak
  between tests.
- help EN/RU and AGENTS updated.
2026-09-21 01:14:45 +03:00
catarrh dfb7b694f9 ui: net-sim Home network button; compact HPLMNwAcT monitor row (v2.7.18)
- netstate.home_plmn(): the card's home network — EF.HPLMNwAcT first
  record (the HPLMN per TS 31.102 4.2.5), falling back to the IMSI with a
  2-digit MNC.  Exposed as state.network.home in /api/net-state.
- The net-sim parameters get a Home network button next to Random roaming
  operator: it fills MCC/MNC and reports the source (EF.HPLMNwAcT/IMSI).
- The monitor's HPLMNwAcT row now shows only the first network (code
  only) plus a '… +N' counter; the tooltip keeps the full decoded list
  with the access technologies.
- tests: netstate home_plmn cases, netsim button handler, updated
  netstate expectations; help EN/RU.
2026-09-21 00:44:20 +03:00
catarrh 87052212b7 ui: name REFRESH and the rest of the TS 102 223 command types (v2.7.17)
The proactive command log showed 'Cmd 0x01' for REFRESH: 0x01 was missing
from both CMD_NAMES (frontend) and PROACTIVE_TYPE_NAMES (server), so the
log fell back to the generic placeholder.  Both tables now carry the full
TS 102 223 9.4 command type list (REFRESH, MORE TIME, POLLING OFF, SET UP
CALL, SEND SS/USSD/DTMF, GEOGRAPHICAL LOCATION REQUEST, SET UP IDLE MODE
TEXT, PERFORM CARD APDU, POWER ON/OFF CARD, GET READER STATUS, RUN AT
COMMAND, LANGUAGE NOTIFICATION, SERVICE SEARCH/INFORMATION, DECLARE
SERVICE, frames, multimedia, COMMAND CONTAINER, ...).

- tests: event_forms CMD_NAMES cases; new tests/test_proactive_names.py
  (REFRESH + spec spot checks).
2026-09-21 00:24:46 +03:00
catarrh ef3726441b ui: decode and abbreviate long PLMN lists in the network monitor (v2.7.16)
EF.HPLMNwAcT fell back to raw hex: efFindDecoder uppercases the requested
name but compared it against the mixed-case registry names
('EF.HPLMNwAcT'), and the monitor passes no FID, so the lookup failed.
Name matching is now case-insensitive, which also fixes EF.PLMNwAcT,
EF.OPLMNwAcT, EF.PLMNsel, EF.KcGPRS and EF.LOCIGPRS for name-only callers.

The decoded HPLMNwAcT list can still be long (one entry per PLMN/AcT
combination), so netStatePlmnList keeps the first three networks and
appends a '… +N' counter; the row tooltip keeps the full decoded list.

- tests: registry case-insensitivity, netStatePlmnList limit, HPLMNwAcT
  summary abbreviation; help EN/RU.
2026-09-21 00:18:36 +03:00
catarrh 4730e81ec2 ui: show raw and decoded comparisons together, with labelled columns (v2.7.15)
Content mismatches in profile checks and snapshot comparisons used to
replace the raw expected/actual pair with the decoded per-field table
whenever a decoder could see a difference.

- The raw comparison is always shown first (heading + expected/actual
  rows); the decoded per-field table follows when the decoder finds
  differing fields.
- The decoded table gets a column-header row carrying the same
  expected/actual labels (profile/snapshot names in a comparison) that
  the raw rows and the FCI diff table use.
- Matching decoded values keep the 'Decoded values match; raw bytes
  differ' note and show no decoded table.
- tests: ef_decode report test extended (raw + decoded + column headers,
  custom labels, matching-decoded note); help EN/RU.
2026-09-21 00:09:39 +03:00
catarrh bfb455b3c8 ui: content-sized label column in decoded field tables (v2.7.14)
The decoded view used a fixed 10rem right-aligned label column, so short
labels pushed the values ~168px to the right — for transparent files the
block looked like indented record data.  efRenderFieldsHtml now uses a
grid with grid-template-columns: max-content 1fr: labels stay right
aligned and values stay aligned, but the column is only as wide as the
widest label of the block.  Applies to the file manager decoded view and
the snapshot Decoded block (transparent and per-record fields alike).

- tests: efRenderFieldsHtml markup (grid, no w-40) and decoded view.
2026-09-20 23:56:40 +03:00
catarrh 93ed870f75 ui: named FCI block holds the metadata; hide the empty content pane (v2.7.13)
- pysimFsInfoHtml puts the FID/type/size line inside the fieldset, so the
  selected file is one bordered block with its symbolic name in the border;
  files without an FCI keep the plain borderless line.
- The content pane starts hidden and is synced after every mutation
  (selection clears it, read success shows it, read failure keeps it hidden,
  edit/cancel re-sync): no empty box before a read or edit.
- tests: FCI ordering assertions; new fs_read.test.js (visibility helper,
  read success/failure/throw, markup + call sites); fs_edit extraction.
2026-09-20 23:45:12 +03:00
catarrh f966276217 ui: put the decoded FCI in a named bordered block (v2.7.12)
The file manager's decoded FCI rows now sit in a fieldset matching the
phone-simulator containers, with the symbolic file name as its legend
(pysimFsInfoHtml(sel, name)); the metadata line stays above the border.
Without a name the fieldset renders without a legend, and without
fci_hex no block is shown at all.

- tests: fieldset/legend markup, metadata line before the border, no
  legend without a name, escaped legend text, no fieldset without FCI.
- help EN/RU mention the named FCI block.
2026-09-20 23:37:37 +03:00
catarrh 3cab9867be ui: compact FCI rows in the file manager (v2.7.11)
- The short file identifier is merged into the File identifier row
  (same row, ' · ' separator) in profilerFciPreviewItems, everywhere the
  decoded FCI is rendered.
- New hideFileSize option: pysimFsInfoHtml drops the FCI File size row
  when its own header already shows Size (kept otherwise, e.g. DFs).
- tests: merged SFI row, hideFileSize, header-size precedence.
2026-09-20 23:29:51 +03:00
catarrh e9f0a50ea5 ui: gate Read decoded on client-side decoder availability (v2.7.10)
Files without a client-side decoder used to fall back to a raw view when
the Decoded pill was pressed.  The decoder is resolved by name/FID
(efFindDecoder) before any read, so the pill can be disabled instead.

- pysimFsHasDecoder() mirrors the decoded view's resolution (tree node
  name, FID fallback).
- pysimApplyAvailability() honours a new data-needs-decoder marker:
  disabled with the existing 'No decoder for this file' tooltip; the
  card hint still wins when no card is equipped.
- pysimFsClickFile() re-runs the availability pass after selecting a
  file; pysimFsSetMode('dec') ignores the call without a decoder.
- tests: indicator (enable/disable/tooltip precedence), fs_edit
  (decoded guard, name/FID resolution); help EN/RU.
2026-09-20 23:21:06 +03:00
catarrh cad14e2b5a ui: file manager read pills and Edit raw (v2.7.9)
The decoded view is a read-only field table, so pressing Edit there did
nothing (no readonly inputs to strip) and Save found nothing to write.

- Remove the redundant Read button: the Raw/Decoded pills already read
  the file, so they become explicit read actions — Read raw / Read
  decoded (Читать сырые данные / Декодировать).  They are buttons now
  so the server/card availability model disables them like the old Read
  button did.
- Edit raw (Редактировать сырые данные) always edits the hex view: from
  decoded (or with no content loaded yet) it switches back to raw and
  reads the file first; the decoded view stays read-only.
- Save/Cancel moved next to Edit raw (right-aligned group); the pills
  are inert and dimmed while editing (mode switching would discard
  unsaved changes).
- Tests: frontend/tests/fs_edit.test.js (skipRead, edit guard, decoded
  auto-switch ordering, read failure abort, cancel/reset); help EN/RU.
2026-09-20 23:12:07 +03:00
catarrh 2c793720f6 ui: verify the ADM PIN from the matched card preset (v2.7.8)
The preset ADM key was stored but never used: the header badge showed
whether a key exists and whether the card was verified, yet the only way
to verify was the pySim command line.

- POST /api/verify-adm builds the TS 102 221 VERIFY itself (CHV number
  from the card model, short keys padded to 8 bytes with 'f') so the raw
  SW is reported: 63Cx -> attempts_left, 6983/9804 -> blocked, 6982 ->
  security error.  The key is never stored and is redacted from request
  logs.
- PWA: the header ADM badge is clickable when the matched preset has a
  key; a failed file-manager read/write (6982/9804) shows a Verify ADM
  button next to the error.  Every retry after a failure asks for
  confirmation and shows the remaining attempts (stronger text on the
  last attempt); a blocked ADM disables both entry points until the card
  session changes.  No automatic retries.
- tests: tests/test_adm_verify.py (fake scc, APDU/SW mapping, redaction)
  and frontend/tests/adm_verify.test.js (retry prompt, SW classifier,
  wiring) + card_state indicator expectations
- docs/api.md, help EN/RU, AGENTS; version trio 2.7.8; sw cache v211
2026-09-20 22:09:39 +03:00
catarrh 3c6bc7ac02 net-sim: bundle the MCC/MNC list and hide MVNOs from the picker (v2.7.7)
The operator picker depended on a workspace file outside the repo
(<workspace>/samples/mcc-mnc-list.json), so a fresh clone showed
'Operator list not loaded'.

- bundle the list in the package (pysim_simple_server/data/
  mcc-mnc-list.json, byte-identical to pbakondy/mcc-mnc-list master
  commit 97bc1652, MIT) with license + provenance in
  mcc-mnc-list.LICENSE; declared as package data so wheels carry it
- _default_mcc_mnc_list() now points at the bundled file;
  --mcc-mnc-list still overrides it
- hide MVNO entries from the simulator picker (search + random): they
  do not operate their own network.  Detected via the `bands` field
  ('MVNO', 'Satellite MVNO', ...); the full list stays loaded so the
  Network state panel still resolves operator names
- tests/test_mcc_mnc.py: bundled file ships/parses, marker variants,
  search/random filters on fixtures and on the real list
- README/AGENTS updated; version trio 2.7.7; sw cache simple-v210
2026-09-20 21:20:25 +03:00
catarrh a688c63919 ui: fix net-sim i18n — decode-entity key match + label coverage (v2.7.6)
The network-simulation description never switched to Russian: its
data-l10n attribute carries &quot; (decoded to " by the browser), while the
LANG_RU key is raw text inside <script> and keeps the literal &quot;, so
translatePage()'s lookup missed and t() returned the English key.

- LANG_RU: the description key now carries real quotes, matching the
  decoded attribute value (RU value unchanged)
- add the missing RU entries for the net-sim labels AUTHENTICATE, MCC,
  MNC, LAC, Cell ID, TAC, RAC (acronyms map to themselves; Cell ID ->
  «Идентификатор соты»)
- html.test.js: strict cross-check that every data-l10n attribute
  (entity-decoded, as the browser does) is a LANG_RU key
- sw.js cache simple-v209; version trio 2.7.6
2026-09-20 14:05:27 +03:00
catarrh 6131d34d28 ui: drop the network-monitor area line, label the refresh time (v2.7.5)
The second line under the location (e.g. "RAI FFFE") showed only the LAC
part of the current area identity: after every dummy-writing scenario it
was the constant FFFE, and otherwise it was redundant with the
EF.LOCI/PSLOCI/EPSLOCI rows below.  netStateRender no longer renders it.

The timestamp line now carries a translated "Last refresh" label
('Last refresh': 'Последнее обновление').

- frontend/index.html: remove #netstate-area (markup + render), label the
  read time, LANG_RU entry
- frontend/tests: html test pins the label and the absence of the area
  line; netstate test asserts the rendered timestamp
- sw.js cache simple-v208; version trio 2.7.5
2026-09-20 13:50:42 +03:00