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.
#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.
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.
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.
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.
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.
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.
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.
- 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.
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.
- 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.
- 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.
- 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.
- 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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
'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.
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.
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.
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.
- 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.
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.
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).
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.
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).
/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.
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.
- 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.
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).
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.
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.
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.
- 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.
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.
- 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.
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.
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.
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
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