- the ADF AID field gains a "From card" button: it reads the ADF.USIM FCI
via /api/select and extracts the DF name (tag 84) with the new
adfAidFromFci() - the AID is card-specific (the live card's is
A0000000871002FF49FF0589, 12 bytes; the base A0000000871002 is only a
prefix, and a shorter AID makes the card reject the access entry with
6A80).
- the priority field defaults to 255 (0xFF) and is labelled "(0 = RFU)":
TS 102 226 8.2.1.3.2.6 makes '00' RFU, which can break the toolkit
registration; the builder defaults an unset priority to 0xFF and the
chain rows follow.
- stkParamsBuild's formatted payload fields are uppercase now (the rest of
the tool's hex is; toString(16) emits lowercase).
- tests: adfAidFromFci pinned to the live FCI, the From-card wiring, the
updated form expectations.
643 frontend / 496 Python green; version 3.6.16; sw simple-v289.
The Explore delete showed "Failed: por_ok - remote SW 6700" while the delete
executed: ramDeleteApdu appended the trailing Le, and this card's SCP80
layer counts a trailing byte as a phantom second command whose SW 6700
masked the real result (count=2, last SW 6700) - the same quirk as
INSTALL [for load] before v3.6.8.
- ramDeleteApdu and the chain builder's DELETE row emit the case-3 APDU
(4F AID TLV, no Le); the SCP81 Delete-AID template follows (it reuses
ramDeleteApdu).
- tests updated (delete APDU expectations in ram/ram_counter_flow/scripts).
641 frontend / 496 Python green; version 3.6.15; sw simple-v288.
TS 102 226 8.2.1.3.2.2 defines the EA inner tags as 80 (toolkit params),
C3 (toolkit DAP), 81 (UICC Access Application specific parameters) and 82
(UICC *Administrative* Access) - the plain-text spec extract had shifted the
table columns, so the access list was emitted under 82. With the list under
82 the card rejected the ADF.USIM entry with 6A80 and the applet never got
its uicc.access.FileView rights (live 2026-09-28).
- stkParamsBuild emits the access list under 81 (the structure is unchanged:
the admin field is coded the same way per 8.2.1.3.2.2.4).
- tests updated (81 04 / 81 0F / 81 17 expectations).
641 frontend / 496 Python green; version 3.6.14; sw simple-v287.
"Failed: 9000" although the delete executed: the RAM helpers sent
`sp-spi2` - the base SPI2 select, whose default is "00 - No PoR" - so the
card answered the envelope 9000 with no PoR and the verdict treated the
missing PoR as a failure. `cardsApplyFields` also set `sp-spi2-hex` from
the preset and then `updateSp()` recomputed it from the selects (which the
preset never updated), clobbering the preset byte for anything reading it.
- `getRamSpParams()` reads the computed `sp-spi2-hex`; `cardsApplyFields`
syncs the SPI2 selects from the preset byte (base = low 5 bits, bit 0x20
= PoR via SMS-SUBMIT, dynamic option for unlisted bases) and the base
select gains the missing cipher + RC/CC/DS combinations (15/19/1D).
- a missing PoR is no longer a failure (the SPI may request none, and the
card sometimes refuses one): `OK` with the SW, or `OK (no PoR)`.
- a successful delete drops the object from the Explore result locally (no
re-explore): an applet row goes away; a cascade package delete also drops
the applets whose AID starts with the package AID. The consumed counter
is still saved.
- tests: ramRemoveFromExplorer (app/cascade/prefix), the no-PoR flow, the
SPI2 source guard, and the updated delete-flow stubs.
641 frontend / 496 Python green; version 3.6.13; sw simple-v286.
Live findings (2026-09-28): a browser-restored Receipt Generation privilege
bit (third byte, ISD-only per GP Table 6-2) was silently sent and produced
6985, and the '82' access entries were missing the mandatory "Length of
Access Domain DAP" byte, so the card rejected the ADF.USIM entry with 6A80.
- ramResetGrants() clears the privilege / toolkit-enable / file-access
checkboxes and refreshes the aggregates on load; ramInstallCap re-derives
the privileges from the checkboxes at send time - no stale or
browser-restored grant can be sent.
- the privileges aggregate emits 1 or 3 bytes, never the invalid 2-byte
form (GP Table 11-43), in both updateRcPriv and the chain's computePriv.
- '82' entries carry the DAP-length byte: '00 01 00 00' (shared FS) and
'<len> <ADF AID> 01 00 00' (ADF); the ADF AID is editable
(rc-tk-adfaid, default A0000000871002 = ADF.USIM, 5..16 bytes enforced).
- tests: ram_grants.test.js (aggregate forms, the send-time derivation, the
load-time reset) and the updated access-parameter shapes in
stk_params.test.js.
638 frontend / 496 Python green; version 3.6.12; sw simple-v285.
SIM toolkit applets installed while UICC-library applets failed at INSTALL
[for install] with 6F00 - including with the reference tool's exact install
parameters. The asymmetry: the SIM (CA) path grants file access via the
Access Domain field (default 00 = full access), while the UICC (EA) path
sent no '82' (UICC Access Application specific parameters) at all, and the
reference's '82 00' is empty. An applet importing uicc.access (the failing
CAP has 2 refs) can then fail inside its install().
- EA mode gains two checkboxes (RAM install form + the chain rows' toolkit
block): "File system access (full)" appends '82 03 00 01 00' (shared file
system + Access Domain Parameter 00 = full access, TS 102 226
8.2.1.3.2.2.2/8.2.1.3.2.5); "ADF.USIM access (full)" adds
'07 A0000000871002 01 00' to it. Both default off.
- tests: the access TLV shapes (with/without the ADF entry), the form -> hex
path with the checkbox, and the toolkit-field wiring count (16 fields).
635 frontend / 496 Python green; version 3.6.11; sw simple-v284.
The Apps query (P1=40) was sent with SPI2=0x01 while only the ELF queries
(P1=20/10) used SPI2=0x21. The Apps listing can exceed the ENVELOPE
response, and the card then answers the envelope PoR with
`actual_response_sms_submit` - the actual response would follow as an
SMS-SUBMIT, which is never sent unless the PoR is requested in submit mode.
The paginate only accepted `por_ok`, so the Explore reported the raw status
as an error ("Partial - Apps: actual_response_sms_submit") and the Apps list
never rendered (live 2026-09-28).
- `ramListingSpi2(p1)`: 40/20/10 -> '21', else '01'; the paginate uses it.
- the paginate accepts `actual_response_sms_submit` via `spPorAccepted` and,
when no data arrived, notes "response via SMS not captured" instead of
reporting the card status as a failure.
- the Explore delete verdict follows the same acceptance rule (an accepted
SMS-submit delete no longer shows "Failed" while its counter advanced) and
no longer prints a dangling " -> " without a SW.
- tests: ramListingSpi2 + the delete-accepted-via-sms-submit flow.
633 frontend / 496 Python green; version 3.6.10; sw simple-v283.
The RAM install form sent the `STK parameters (hex)` field, which was only
regenerated when the toolkit checkbox or the mode select changed - editing
the TAR (or any other toolkit field) afterwards was silently dropped. The
live install therefore carried the form defaults (TAR B00001, textLen 0,
menus 0, MSL 16, channels 0) instead of the entered AF4D01 / MSL 12 /
channels 1, and the card rejected the install parameters with 6A80
(TS 102 226 8.2.1.3.2.7: a TAR already assigned on the card).
- every `rc-tk-*` field regenerates the hex on input/change; the install
recomputes it at send time unless the user hand-edited the hex
(`dataset.manual`), so the edit order can no longer drop values;
- one pure `stkParamsBuild` serves the RAM form (`buildRcToolkitParams`) and
the RAM/GP chain rows (`buildTkParams`) - no drift between them (the chain
row also gains the menu-ID <= 7F check);
- applet TAR field: empty by default, no placeholder - B0 00 01 is the
allocated ADF RFM TAR (TS 101 220 Annex D), not an applet TAR; an empty
field is coded as TAR length 00 (the card may take a TAR from the AID only
when the AID carries one, PIX hex digits 15-20);
- the chain builder's INSTALL/LOAD rows drop the trailing Le (the v3.6.8
server form; a trailing Le made the card execute a phantom command);
- tests: stk_params.test.js (the decrypted live parameters, empty-TAR
coding, CA wrapper, long-form lengths, rejection cases, form wiring, and
the form -> hex path with the real builders) and ram_counter_flow.test.js
(the Explore delete persists the consumed counter through the real
ramSaveCntr); STK fixtures no longer use B00001.
631 frontend / 496 Python green; version 3.6.9; sw simple-v282.
INSTALL [for load]/LOAD/INSTALL [for install] carried a trailing Le and an
explicit ISD AID in the Security Domain field; the card executed the extra
byte as a second (phantom) command, so the compact response reported count=2
with SW 6700 - which the v3.6.2 remote-SW check correctly treated as a
failure, aborting at step 1.
Decrypted from the live trace (KIc/KID 25/25, 3DES): ours was
80E6020014 07F0414C46416101 08A000000003000000 00 00 00 00
the reference tool sends
80E602000C 07<aid> 00 00 00 00 (empty SD, no Le)
and its response is count=1 / 9000 while ours was count=2 / 6700.
- `_cap_apdu_sequence`: the SD AID is only sent when a custom one was
supplied (empty -> '00', the card defaults to the ISD; GP 11.5.2.3.1
Table 11-42) and all three RAM install APDUs are case 3 (no Le).
- tests: the expected INSTALL bytes updated, a no-Le assertion for every
APDU in the sequence, and a custom-SD-AID case.
615 frontend / 496 Python green; version 3.6.8; sw simple-v281.
Delete All worked but reset the preset counter: the handler never re-read
the preset (unlike ramExecute), saved N+1, then re-ran the Explore with the
OLD sp (counter N). Every page answered cntr_low, nothing was accepted, and
ramExplore's final ramSaveCntr wrote N back over the preset.
- ramDeleteFromExplorer re-reads the preset first (spRefreshFromPreset
'ram-card-sel'), advances and saves the counter only for a packet the card
accepted (spPorAccepted) - a rejected packet leaves it untouched - and
continues the follow-up Explore from the consumed counter instead of
replaying it.
- the delete result also checks the remote command's SW via a new
`ramRemoteSwOk` (9000 / 61xx / 62xx / 63xx / CAFE, mirroring the server's
`_ram_remote_sw_ok`): a por_ok packet whose DELETE was refused (e.g. 6A88)
no longer shows "OK" (the counter still advances, the card consumed it).
- ramExplore only saves the counter when it advanced, so a stale caller can
never lower a preset counter again.
- tests: flow tests for accepted / rejected / refused-remote-SW deletes
(refresh order, saved counter, the counter passed to the re-explore) and
the ramRemoteSwOk set.
615 frontend / 495 Python green; version 3.6.7; sw simple-v280.
ramDeleteFromExplorer() called an undefined `_ber_len()` helper, so clicking
Delete / Delete All raised a ReferenceError right after the confirm and no
APDU was ever sent. The inline builder also omitted the mandatory Le byte.
- new pure `ramDeleteApdu(aid, withCascade)` = GP Card Spec v2.3.1 Table
11-20/23 form: `80 E4 00 <p2> <Lc> 4F <len> <AID> 00` (P2 00 = object,
80 = object and related objects), used by the Explore handler.
- `scp81DeleteApdus()` (the "Delete AID" script template) now shares the
same builder: it used to send the raw AID without the mandatory '4F' TLV.
- tests: exact bytes for 7/16-byte AIDs and both P2 modes, the SCP81 script
expectations updated to the TLV form, and a wiring check that the undefined
helper call does not come back (`_ber_len(`).
611 frontend / 495 Python green; version 3.6.6; sw simple-v279.
Explore still missed the F0414C46416101 package on a card whose responses
wrap the R-APDU in the TS 102 226 5.2.2 Response Scripting template
(`AB <len> 80 <count> 23 <len> <R-APDU>`): `_decode_por` parsed that as a
compact response, so the frontend got `last_status_word` 81d0/7680 and data
starting `80 01 01 23 ...` instead of the listing.
- server: `_parse_response_scripting()` (AB definite / AF 80 ... 00 00
indefinite) extracts the executed-command count and the last R-APDU's SW
and data; `_decode_por` exposes it as `response_type: scripting` in the
same `decoded` shape as compact, so the RAM explore paging and the RAM
install `por_sw` see the real 9000/6310.
- frontend: the compact listing walk is deterministic on the AID length
(`len AID life ver`; P1=10 adds `module_count (len module_AID)*`).
`_parseRawAppEntry` no longer guesses `rawLen-1` for >8-byte AIDs (16-byte
A113 applet AIDs were truncated), and `_parseRawElfEntry` no longer scans
for `0x10` (a length/AID byte equal to 0x10 derailed the walk: a live page
parsed to 1 entry with no F0414C46416101).
- tests: exact trace fixtures - the ELF page lists F0414C46416101 (11
entries), the P1=10 page attaches its F0414C4641610101 module, the app page
keeps the 16-byte A113 AIDs; Python covers the scripting template
(definite/indefinite) against the live `AB 12` ISD and listing vectors.
610 frontend / 495 Python green; version 3.6.5; sw simple-v278.
Explore returned partial data (no ELF/module entries, the installed package
missing) and then failed with cntr_low. Two causes, both in the
actual-response SMS-SUBMIT path the card uses for big listings:
- `_find_sms_tpdu` read TLV lengths as a single byte; the FETCH carries
`8B 81 97 ...` (BER long form) for the big pages, so it returned a
corrupted, truncated TPDU.
- `_calc_ud_offset` treated the SMS-SUBMIT relative validity period (VPF=10)
as 7 bytes instead of 1, shifting the UD offset: `_parse_sms_concat` then
read a bogus UDH and reported no concatenation, so the segments never
assembled and the PoR/data was dropped ("RAM RESPONSE-PACKET: empty").
The step was marked failed, the counter did not advance, the same counter
was retried and the card answered `cntr_low`, which also blocked P1=10
(modules) where the installed package appears.
- the captured segments now always store the assembled UD (`submit_ud_hex`);
`_sms_submit_por()` rebuilds the DELIVER-style packet (`02 71 00` + UD) for
`_decode_por`. Verified against the live capture: por_ok, remote SW 6310,
438 hex chars of listing data (the exact bytes of the P1=20 page).
- `spPorAccepted` counts `actual_response_sms_submit` (0x0B) as accepted, so
the counter advances when the data follows via SMS-SUBMIT.
Explore queries follow GP Card Spec v2.3.1 11.4.2.2: compact listings
(P2.b2=0) with the chained GET RESPONSE (`ramGetStatusApdu`, paging P2=00 ->
P2=01), the malformed P2=02 attempt is gone, and the ISD-only query (P1=80)
never pages with next-occurrence (the card shall reject it).
Tests: Python SmsSubmitCaptureTest with the live FETCH bytes (TPDU length,
UD offset, concat reassembly, 027100+UD decode); ram.test.js checks the APDU
builder and the missing P2=02 attempt; cards_counter covers 0x0B.
608 frontend / 492 Python green; version 3.6.4; sw simple-v277.
The SCP80/RAM forms hold a copy of the preset (counter, keys, TAR, SPI).
Editing the preset on the Cards tab saved correctly, but the form kept the
old copy: the operation sent the stale counter (the card answers cntr_low)
and the post-send sync wrote the stale value back over the preset - the
saved counter silently reverted. Reproduced in a real DOM:
after select in SCP80: preset=0000000001 sp=0000000001
after Cards edit+save: preset=00000000AA sp=0000000001
after a sync: preset=0000000001 (edit lost)
- `cardsApply()` split into `cardsApplyFields()` (field copy, no packet) and
`cardsApply()` = fields + genSp; new `spRefreshFromPreset(selId)` re-reads
the selected preset from `sp-card-sel` / `ram-card-sel` and re-applies it.
- `pysimSendOta()` and `ramExecute()` call it before starting, so every
SCP80/RAM operation uses the preset as it is now.
- A rejected send no longer advances the counter: new `spPorAccepted(por)`
gates the advance+write-back in `pysimSendOta`, the Explore pagination and
its GET DATA step, and the server's RAM install (`_ram_next_cntr`: advance
only for `por_ok`/`no_por` steps). A failed install still returns
`final_cntr` (the accepted prefix) and the PWA persists it, so a retry
never replays a counter the card already consumed.
- tests: cards_counter.test.js (the stale-form regression, RAM selector,
fields-without-genSp, spPorAccepted) and `_ram_next_cntr` cases; the
cards_form/ram harnesses updated for the split.
- docs/api.md counter semantics; AGENTS preset-source-of-truth rule.
607 frontend / 488 Python green; version 3.6.3; sw simple-v276.
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.
Small code-review leftover: efArrSc() keeps only the last '83' key reference
of a malformed multi-reference AT control reference template (compliant CRTs
carry exactly one, ISO 7816-4 6.3.1) - now said so in the code.
The scp81-findings log needs no new rows: no new live SCP81 tests were run
(it is current through 2026-09-16h; the "load/store over SCP81" item in its
Next-tests list is already verified).
Comment-only index.html change -> sw cache simple-v257 (version stays 3.5.4).
549 frontend / 421 Python green.
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.
- README/RUS: the Remote APDU tab has six sub-tabs, not seven; the GP
connection-parameter table listed the wrong TLV tags (02/80/01) - it now
matches the UI presets (82 Device Identities, 05 Alpha, 81 Command details,
35 Bearer, 03 = default bearer). The Push commands intro names the
mode-specific rows (SD params tag in Store, Command Scripting template in
Trigger) and the RU text uses the RU sub-pill labels; both READMEs gain the
PoR advice bullet (Stepping Stones R7 18.6.3: no PoR on success, on failure
SMS-SUBMIT + SPI2 0x20).
- help RU: the C-APDU parser section names the Parser pill sub-pills like the
EN one; the Push commands intro describes what the mode actually switches
and uses the RU sub-pill labels.
- sw cache -> simple-v252 (help pages are precached).
Docs only; no functional change, version stays 3.5.0.
The 01 (BIP channel opening) variant was raw hex while 03 (TCP) had a detailed
form. The asymmetry is real - 01 accepts any OPEN CHANNEL COMPREHENSION-TLVs
(TS 102 226 9.2.1) while 03's set is fixed by 9.2.3 - so 01 now offers a
Fields/Hex switch instead of a rigid form:
- Fields (default) composes the most-used OPEN CHANNEL parameters per
SIMalliance Stepping Stones R7 18.6.1: bearer description (default 35 01 03),
transport protocol (UDP 01 / TCP 02, UICC client, remote - the only values
TS 102 223 6.6.27.4 allows when the transport level is present) with the
destination port, destination address, NAA/APN, buffer size, alpha
(transparent checkbox -> 05 00 = no user confirmation) and an extra-TLVs
field for login/password and anything else. The local address is never
emitted (9.2.1 and the guide forbid it).
- Hex keeps the raw COMPREHENSION-TLVs field; pushOpenChannelTlvs() is a pure,
vector-tested composer feeding the same encoder as before.
Help 2.7 EN/RU and the READMEs explain the two modes; sw cache ->
simple-v251; version stays 3.5.0.
- The first two Push commands sub-pills carry the HTTP OTA prefix
("HTTP OTA Trigger (Push SMS)", "HTTP OTA Store (SD admin params)"), so the
form headings drop it and read "Administrative session trigger (Push SMS)"
/ "Administrative session store data (SD admin params)".
- BIP channel opening (01) gains the same-message CAT_TP follow-up: an "Also
request the CAT_TP link establishment (02) in the same message" checkbox
with the shared CAT_TP fields (destination port mandatory, optional max SDU
/ identification data / extra TLVs). The preview and Pack then carry the
pair 80EC0101...80EC0102... as one command script (concatenated SMS when
over 140 octets) and -> Expanded Script appends both 22 Command TLVs.
Rationale (SIMalliance Stepping Stones R7 18.6): 01 carries the channel
parameters, 02 only the CAT_TP port, and the guide recommends both in the
same SMS; standalone 02 stays for cards with provisioned defaults.
- Help 2.7 EN/RU, READMEs and AGENTS document the pairing, the fact that 03 is
self-contained (it carries the mandatory OPEN CHANNEL TCP set, so no
companion 01) and the Stepping Stones PoR advice (no PoR on success; on
failure use SMS-SUBMIT / SPI2 0x20).
- pushSectionApdus vectors cover the 01+02 pair and its mandatory port;
sw cache -> simple-v250.
The Push commands pill now has three sub-pills: Trigger (Push SMS), Store
(SD admin params) and Channel / link trigger, which builds all three TS 102 226
§9 establishment requests - 01 BIP channel opening (OPEN CHANNEL), 02 CAT_TP
link, 03 TCP connection - with one shared destination-port field and
per-request field visibility. The TCP parameters are the OPEN CHANNEL TCP
set (BIP, or a direct IP connection per TS 102 483 where supported), so 03
belongs with the other channel/link requests.
The identification packet (04) is not a trigger: it presupposes an already
open TCP channel and only makes sense as a follow-up in the same message, so
it is no longer a standalone request. The TCP request carries an "Also send
the identification packet (04) in the same message" checkbox (optional data,
ICCID when empty): the preview then shows both commands (a command string sent
in one secured packet) and → Expanded Script appends one 22 Command TLV per
command. pushSectionApdus() returns the APDU list; 04 stays available in the
RAM/GP chain's PUSH row for script sequences.
Help 2.7 EN/RU, READMEs and AGENTS describe the three sub-pills and the BIP vs
direct-IP nuance; sw cache -> simple-v249; version stays 3.5.0.
html.test.js also asserts that the document is complete (ends with </html>,
every <script> closed, the inline script parses and defines cApduSwitchSubtab):
a truncated index.html made the browser fail to parse the inline script, so
every onclick handler reported "function is not defined" while the Node tests
still passed.
Remote APDU now has six top-level pills (SIM RFM, USIM RFM, RAM/GP,
Expanded Script, Push commands, Parser); the C-APDU and R-APDU parsers are
nested panels behind the Parser pill, switched by parserSwitchSubtab().
- "Response parser" renamed to "R-APDU parser" (RU: «Разбор R-APDU»), the
umbrella is "Parser" / «Разбор»; the C-APDU panel keeps its id and label.
- cApduSwitchSubtab() routes nested names through the Parser pill first, so
pysimSendOta()'s post-send jump keeps working; the help deep-link anchor
follows the selected parser (c-apdu-parser / response-parser) and the Push
commands pill now links to the renamed #push-commands section.
- The small C-APDU panel moved next to the R-APDU panel so both live in the
#c-apdu-sub-parser container; html.test.js checks the nesting and sub-pills.
- Pill lists updated in help EN/RU (2.6 note, 2.8 heading), READMEs, AGENTS;
sw cache -> simple-v248.
The Push commands pill now switches between four sub-pills - Trigger (Push
SMS), Store (SD admin params), BIP / CAT_TP trigger, TCP trigger - and shows
only the selected form:
- Trigger/Store share one GP administration form (fields unchanged); the mode
only changes the payload wrapping and the form heading, which now carries
the HTTP OTA prefix: "HTTP OTA Administrative session trigger (Push SMS)"
/ "HTTP OTA Administrative session store data (SD admin params)".
- BIP/CAT_TP and TCP keep their own headings ("... (TS 102 226 §9)") and
their "not the administration session" note.
- hotaSetForm() owns the pill styling and form visibility and delegates to
hotaSetMode() (Trigger/Store) or pushUpdate() (SS9 forms); the view always
opens on Trigger, nothing is persisted.
sw cache -> simple-v247; help 2.7 EN/RU, READMEs and AGENTS updated.
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.
Swap the two pills in the Remote APDU view (SIM RFM, USIM RFM, RAM/GP,
Expanded Script, HTTP OTA, C-APDU Parser, Response parser) and keep the
subtab-toggling name list in the same order. Pill order updated in help
EN/RU, READMEs and AGENTS; sw cache -> simple-v245; version stays 3.4.0.
The file picker's implicit current DF now defaults to MF for SIM RFM and
USIM RFM alike (the real context follows the TAR the packet is sent to,
TS 102 226 7.2/7.3, so the default is neutral and the per-row "starts in"
selector covers ADF sessions - ADF.USIM files appear once it is switched).
Tests set the ADF session explicitly where they exercise ADF-relative fills
plus a new default assertion; help 2.1 EN/RU, READMEs and AGENTS updated.
sw cache -> simple-v244; version stays 3.4.0.
The picker option label read backwards, e.g. "EF.IMSI — 6F07 — 7F20".
It now shows the path within the optgroup's root with the FIDs in order,
matching the path-field convention: "EF.IMSI — 7F20/6F07",
"EF.ICCID — 2FE2", "EF.PSC — 5F3A/4F22" (ADF session). Switch markers and
the filter (which also matches the symbolic path) are unchanged.
sw cache -> simple-v243; version stays 3.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.
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.