SCP81:
- New listener mode "passthru": no local listener - the card's BIP
channels connect straight to a configured external platform
(host/port required), which terminates TLS and runs the dialog.
The status API reports mode/target (_SCP81_MODE/_SCP81_TARGET) and
clears them on stop. PWA mode select, hint, Start validation; TLS PSK
stays the default.
Proactive command decoding (Phone tab log):
- SEND SHORT MESSAGE (0x13) is now decoded: alpha, address (TON/NPI +
number), 3GPP-SMS TPDU (type, TP-MR, TP-DA, TP-PID 0x7F flagged as
SIM data download, TP-DCS, TP-VP, TP-UDL), UDH concatenation IEs, and
the TP-UD as text (GSM-7 with a septet unpacker, UCS2, 8-bit) or as a
TS 31.115 secured packet for PID 0x7F; malformed TPDUs fall back to
the raw hex line.
- PROVIDE LOCAL INFORMATION qualifier names completed per TS 102 223
V18.3.0: ESN (07), MEID (0B), Supported RATs (1A); 05 relabelled
"Reserved for GSM (Timing Advance)". Fixed in the server dict and
both frontend tables.
Header:
- Compact ADM badge next to the card indicator: "ADM ✓" green when
pySim's adm_verified is set, "ADM ✗" red otherwise, hidden without a
card session or when the server is down; updated ahead of the card
state-key early return so it never disturbs the connect/reset flow.
File manager:
- Sort pills (FID/Name), Probe all files button and progress line are
pinned above the tree instead of scrolling with it; the tree box cap
grows from 420px to 65vh.
SW cache otaman-v165; help EN/RU updated; tests 234 Python + 361 frontend.
Cards / SCP81:
- Cards is a top-level tab; presets gain PSK identity + key, HTTP-OTA
column, Edit/Update and a live PSK-map push into a running listener.
- SCP81 has Listener/Scripts pills; scripts are named local APDU lists
(Empty / Explore / Install from .cap / Delete templates), sent to the
server explicitly at start. The listener takes mode/host/port/script
only; PSK inputs and the .cap row are gone.
- Multi-PSK TLS listener: identity -> key lookup from the card presets
(POST /api/scp81/psk-map updates a running listener), unknown
identities log tls-psk-unknown and fail; handshake logs carry psk_match.
- Script engine: execution tracking (next/done/pending/results), a
resumed dialog sends only the unexecuted tail (unreported APDU is
resent), a fresh dialog restarts, listing continuation pages are
tracked separately (pending.pages/complete). Restart script button.
- POST /api/scp81/gen-install replaces the SCP81 ram-install queueing
(generation only; the .cap is never stored).
Profiler / snapshots:
- Snapshot comparison is always exact (mask checkboxes removed; the
first-4-bytes mask remains a profile-creation option).
- "matching records" line shows count + #record numbers.
- New Clone action: copy named "Copy of <profile>", opened in the editor.
- Matched-record count/numbers fix ("1 из 8 (#8)").
SCP80:
- Configurable / auto-fitted LOAD block size: each LOAD APDU encodes into
one SMS (pySim rejects secured packets above 140 octets, so a 240-byte
block could never be sent). Response reports the effective size and
clamps; encode failures are reported per step with the pySim message.
SW cache otaman-v161; docs/api.md, scp81-findings and help EN/RU updated.
Tests: 226 Python + 356 frontend.
cardsSave() calls cardsRebuildSelect(), and the innerHTML rebuild reset the
dropdown to its first option ("- Select card -"). Since the counter sync
saves after every send, the selection was lost each time and had to be
re-picked manually. The rebuild now restores the previous selection
(guarded by the preset still existing). Service worker v155.
The R-APDU TLV length was read as a raw byte, so a listing page above 127
bytes (AF 80 23 81 FC <252 bytes> 00 00) was cut to its first 127 bytes with
a bogus status word (the data's last two bytes: CAFE/0001/9F70 instead of
the real 63 10 "more data"). The bogus SW also stopped the SW CAFE/6310
pagination, which is why later registry entries - e.g. the installed package
AA1902BC225801 - never showed up. Uses httpota.ber_len_read now.
Tests: 250-byte long-form page and short-form regression (204 python);
findings updated; service worker v154
- continuation repeats the SAME GET STATUS command with P2.b1 set (the
pagination state lives in the card); changing the 4F criterion is a match
filter, not a position - P2=03 with the last AID is rejected with 6A80 and
P2=02 with it returns that single match (the earlier duplicate)
- handle the standard "more data available" warning SW 63 10 (Table 11-38)
in addition to the live card's proprietary CA FE
- explore script: P1=40 is applications+SDs, P1=20 the ELF registry, P1=10
ELF+modules (Table 11-33) - the ELF-only registry was never queried, which
hid the installed package; labels and the results decoder show C4 (ELF AID)
and CC (SD AID) too
- UICC_SPECS.md: GET STATUS P1/P2 tables made explicit with the pagination
rule, plus BER length coding notes for the scripting templates and the
TS 102 223 channel data TLV (the two >127-byte traps)
201 python + 346 frontend; service worker v153
P2=02 means "get first or all occurrence(s)" (Table 11-34), so every
continuation re-returned the first listing (the criterion's single match),
pagination stopped after one extra page and the installed package
AA1902BC225801 never showed up in the ELF registry. P2=03 = "get next
occurrence(s)" is the correct value for the SW CAFE continuation.
Tests updated with the new continuation bytes; findings documented;
service worker v152
- POST /api/scp81/queue takes an explicit APDU list (or single APDU) and
queues it as the SCP81 script; entries that already are Command Scripting
templates (AA.../AE80..., the expanded format) are sent verbatim instead of
being wrapped again
- Remote APDU -> RAM chain: "To expanded" builds each command in the
TS 102 226 expanded form (AA definite / AE80 indefinite selector) and
"Queue in SCP81" queues the built commands for the next card POST, so the
full-featured RAM/INSTALL [for install] form (AIDs, privileges, TK/STK
parameters) can drive the HTTP OTA install
- RU strings; api.md; service worker v151
The SCP81 install row now carries the same fields as the SCP80/RAM form:
install parameters (default C900), STK parameters (CA TLV) and make
selectable. An UICC applet install can require STK parameters; without them
the card answers INSTALL [for install] with SW 6A80 (incorrect parameters in
data field). RU strings added; service worker v150.
The block slicer used the block number as a character offset
(loadfile_tlv[i * 2:(i + 240) * 2]), so every LOAD block after the first was
a 1-byte-shifted copy of the previous one - the cap header repeated every
239 bytes on the wire. A live install accepted three blocks, failed block 4
with SW 6400, then 6985, and INSTALL [for install] answered 6A88. The same
slicing was inherited by the SCP81 helper from the SCP80 path, so
multi-block caps could not install there either; both are fixed.
Tests: blocks are consecutive and reassemble the C4 TLV byte-for-byte
(200 python); findings updated; service worker v149.
The C-APDU TLV length was written as a raw byte: a 245-byte LOAD command
produced 'AE 80 22 F5 ...', which BER reads as a long-form marker, so the
card mis-parsed every script command over 127 bytes. Small commands worked,
which made a RAM install look alive: the card answered the LOAD steps with a
degenerate 'AF 80' body (no R-APDU), and the final INSTALL [for install]
failed with 6A88 because the package never loaded.
Both the indefinite ('22' TLV) and definite ('AA' outer) lengths are now
BER-encoded (same rule as the BIP channel data TLV fix). Tests: 245-byte
LOAD body, short form, definite variant (200 python, 346 frontend);
findings doc updated; service worker v148.
- each result group shows the command label (GET DATA FF21, GET STATUS
P1=80/40/10, GET DATA 0085, INSTALL/LOAD)
- GET STATUS pages deduplicate by AID: the continuation page re-includes its
search criterion, which made the last entry of every listing appear twice
- the GET DATA 0085 answer is decoded (host/agent/uri, PSK identity +
KVN/KID from the unframed [14][id][02 KVN/KID] security TLV, retry counter
and timer, connection block with APN and destination address) instead of a
truncated hex dump; undecodable results show the full hex now
- tests: admin-params decode with the live sample, command labels
(346 frontend, 196 python); service worker v147
The counter only advanced when a PoR came back with status por_ok, so
PoR-less sends kept reusing the same counter (the card rejects a repeated
counter for replay protection). It now advances on every successful send
(and shows "CNTR -> ..." in the result line), and the new value is written
into the selected card preset via spCntrSyncPreset(). Manually edited
counters are persisted too (the field's onchange). New spNextCntr() helper
with carry tests; service worker v146.
- shared _cap_apdu_sequence helper (INSTALL [for load] -> 240-byte LOAD
blocks -> INSTALL [for install]); the SCP80 /api/ram-install path now uses
it too (one source of truth; byte-level tests pin the APDUs)
- POST /api/scp81/ram-install: parses the .cap server-side and queues the
APDU sequence as the SCP81 command script (one C-APDU per POST, runs on the
card's next push); refused while a script is mid-run unless force
- /api/scp81/script reports the script kind (explore/none/custom/ram-install)
- tab: RAM install row (CAP file + SD AID + Queue button); RU strings
- docs: api.md, findings, AGENTS; service worker v145
- results carry the originating APDU, so auto-continued SW CAFE pages group
under their logical command
- new tab panel: memory pages decode to applets / free NV / free volatile,
GET STATUS pages decode to AID + lifecycle + privileges (via the existing
decodePrivileges) + module AIDs, truncated page tails are skipped
- tests: decoders and grouping (frontend 342, python 192)
- service worker v144
Long GET STATUS listings answer SW CAFE with 127-byte pages. The script
responder now extracts the last complete AID from the page and inserts a
next-occurrence GET STATUS (80F2 <P1> 02 <Lc> 4F <len> <AID> 00) as the
next command, until the listing ends. Pages are logged (script-page), a
repeated page logs script-page-stalled and stops, inserted continuations are
dropped at session start.
Live-verified: ELF registry and applications collected completely in two
pages each (7/7 commands, all script-status ok).
- tests: page parsing (truncated tails, live FC-prefixed junk), continuation
bytes, auto-insert, stall guard, per-session purge (192 python + 337 frontend)
- UI: page number and script-status log rendering; service worker v143
- scp81.py: PSK TLS listener (stdlib ssl PSK callbacks) speaking the GP
HTTP administration dialog; configurable framing (chunked/Content-Length,
TLS record split, Apache-style/compact headers, Connection header,
keep-alive, Next-URI template with %d, TLS version/cipher, answer delay,
keylog for capture decryption)
- server.py: script responder + Response Scripting parsing (AF/AB, 80/23
TLVs), memory decoder, SCP81 start options, terminal-side timer
management, background-mode BIP events, permissive OPEN CHANNEL
- BIP fix: the RECEIVE DATA channel-data TLV length is BER long form
(36 81 <len>) above 127 bytes; a raw length byte is mis-parsed on the
card, so the large TLS records never reached its stack (a live card
fetched the script response and silently never processed it - endless
resume). The card now executes scripts and returns R-APDUs: memory
(13 applets, 50646 B NV free, 2402 B volatile), ISD, stored HTTP OTA
parameters, ELF and application registries
- frontend: SCP81 tab (listener, script selection, HTTP OTA log), phone
event forms, i18n; service worker v141
- docs: api.md, scp81-findings.md (attempt matrix + root cause analysis);
tools/scp81_decrypt.py decrypts listener captures via the keylog
- tests: 187 python + 337 frontend
Groundwork for HTTP OTA emulation (GP RAM over HTTP / SCP81): the server
now emulates the terminal side of Bearer Independent Protocol and redirects
the card's TCP channel to a locally configured target.
- new pysim_otaman_server/httpota.py: BER-TLV parser for proactive commands,
BipTerminal (per-channel TCP client with rx buffering, redirect target,
bounded event log) and TcpDumpServer (raw capture listener used to
calibrate the card's TLS ClientHello before the PSK platform exists)
- server.py: BIP commands 0x40-0x43 (+0x44) are handled in
_handle_proactive_chain with TR payloads matching the captured real
terminal traces (result first, Channel status with link-established bit,
Bearer description, Buffer size, Channel data/length); connection
failures return result 3A/00; names and decoders added for the proactive
log; /api/scp81/status, /log, /log-clear and /bip (start/stop dump mode)
- tests: TR byte vectors from the traces, TLV parsing, open/send/receive/
close flow over a local peer, dump listener; 123 Python tests pass
The proactive command list only re-rendered on tab/pill switches, so new
fetched commands (background STATUS polling, menu traffic) stayed
invisible while the Phone view was open.
- /api/status now exposes proactive_seq (_PROACTIVE_ENTRY_ID, monotonic
and not reset with the log), so the existing 2s status poll detects
changes with no extra request; pysimCardStateUpdate() re-renders the
log when the sequence changed and the Phone/Phone view is visible
- the 5s backend timer no longer fetches the log (it was discarded) and
now uses the already-fetched stk-status: when active/pending changes
while the Phone tab is visible, stkCheckMenu() refreshes the menu
button without a tab switch
- pysimProactiveLogRender() keeps its scroll position and only shows
Loading... on a first/empty paint
- tests: proactive seq + STK signature helpers and poll-driven render
behaviour in card_state.test.js; SW cache v123 -> v124.
pySim's lchan.select() resolves names against global selectables (self +
parent chain + MF children + applications) and falls back to probe_file(),
which blindly SELECTs an unknown FID and permanently injects a dynamically
named DF.XXXX/EF.XXXX into the running filesystem model. Probing a whole tree
or scanning a snapshot with custom files therefore polluted the model, made
tree branches show children of the wrong object, and could persist phantom
files into snapshots.
- server: new _select_with_parent()/_select_path() walk the requested parent
path (new parent_path field, parent_sel kept as legacy fallback) strictly
through the model and call lchan.select_file() only; model-unknown 4-hex
segments are probed only with allow_probe and the temporary child pySim
adds is detached again via the cleanup callable that the four handlers
(/api/tree|select|read|write) now run in a finally block
- frontend: getParentPath() builds the segment chain (MF, ADF names, FIDs)
and all tree/select/read/write bodies plus the snapshot/profile walker send
parent_path; allow_probe is set only for custom files; the blind retries
in the file manager were dropped
- tests: tests/test_select_scope.py (duplicate-FID resolution, no APDU for
unknown non-custom files, probe+detach, model unchanged); fs_load/fs_probe
assertions for parent_path and allow_probe; docs/api.md and AGENTS.md
document the contract; SW cache v117 -> v118.
- pysimFsLoadChildren now treats {success:false,error} (and any error
payload) as a failed listing: absent DFs/ADFs render as a red cross
without an expand arrow instead of silently opening empty; the
/api/tree 500 body also carries exists:false for shape consistency
with /api/select
- an expanded directory with zero children shows a gray (empty)
placeholder
- new Probe all files button (data-needs=card) in the tree header:
walks the whole model tree from MF, verifies every DF/ADF via
/api/tree and every file (incl. custom entries) via /api/select,
skips subtrees of absent dirs, shows N/total progress, toggles to
Stop, and reports present/absent counts with elapsed time; results
colour the current tree only
- tests: fs_load (error/retry/empty), fs_render (red cross, (empty)),
fs_probe (flags, absent-dir skip, custom files, stop, summary);
html structural check; help EN/RU, READMEs, AGENTS updated;
SW cache v115 -> v116.
The stuck-card pattern: after a Back TR the card re-issues the parent menu
as a new SELECT ITEM (91XX -> FETCH, paused, awaiting TR), but the UI's
back/timeout branch ignored that response and showed the cached top menu;
the next item click then sent ENVELOPE(Menu Selection) while the card was
waiting for the TR. The card answers such an ENVELOPE with 9000 (not the
usual 91XX), and menu-select cleared the pending command without a TR,
leaving an unfinished proactive session until reset/equip.
- server: _finish_pending_menu() answers a paused command with a cancel TR
(0x10) before /api/menu-select sends its ENVELOPE and drains a 91XX
follow-up, so a new selection can never shadow an unanswered FETCH
- frontend: stkMenuRespond('back'|'timeout') renders the follow-up command
from the server response (SELECT ITEM / DISPLAY TEXT) and shows the
cached top menu only when the TR answer carries no command (9000)
- tests: finish-pending cancel TR + chain drain (Python); stkMenuRespond
back/timeout/cancel/ok rendering (frontend); help/AGENTS updated;
SW cache v107 -> v108.
Server measures every classified APDU (A4 select, B0 read binary, B2 read
record) from command to response: _collect_apdu_times() enables the tracer
(reattaching it if pySim nulled it) and /api/select + /api/read return
'apdu_times': [{type, ms}]. Collection is safe: handlers hold _CARD_LOCK.
Snapshots store per-file {select_ms, read_ms}, a ms value per record and
snapshot-level stats {select, read_binary, read_record}: {min, max, avg,
count} plus total_ms (wall time of the scan). The snapshot view gets a
summary under the title (files/records counts, scan time, min/avg/max per
command type) and shows select/read per file and read time per record.
Timings are display-only: checks, snapshots comparison and imports ignore
them (old snapshots simply show 'No timing data').
Tests: Python classifier/collection (tests/test_apdu_timing.py) and
frontend stats/accumulator/format/build-file/summary. SW cache v99 ->
v100; help, README and docs/api.md updated.
- _apply_equipped_card() centralizes the post-equip refresh + TERMINAL
PROFILE (shared by the /api/command equip branch and auto-equip)
- server tracks card_session (bumped on equip and disconnect) and
equipping; /api/status exposes connected, card_present, card_session,
equipping, auto_equip and is exempt from _CARD_LOCK (pure cached state)
- auto-equip is on by default (--no-auto-equip; off with --no-card-init):
the presence observer spawns a one-shot worker after insertion, which
runs equip under _CARD_LOCK and applies the same refresh; the monitor
starts after the startup init so pyscard's initial 'already present'
event does not re-equip a fresh session
- UI: /api/status polls every 2s (other views stay at 5s); when
card_session changes it runs pysimResetCardData() (STK overlay, file
tree, events, proactive log, PLI, status) — the same reset as a manual
Equip; messages: initializing / press Equip / no card
Tests for the observer and auto-equip rules, session bumps,
_apply_equipped_card, and the UI state machine. SW cache v91 -> v92.
The UI only noticed a removed card when some user action ran a real card
command (e.g. Check status); /api/status is a cached-state read that kept
returning the old card, and _handle_card_disconnect() did not clear
app.card/rs.
- start_card_monitor() registers a pyscard CardObserver for our reader;
it only polls SCardGetStatusChange (no APDU, no connection, no extra
process), and on removal sets server.card_present=False and calls
_handle_card_disconnect() under _CARD_LOCK
- /api/status now exposes connected (session usable) and card_present
(physically inserted) and masks card/profile/atr/selection when not
connected; _CARD_CONNECTED is initialized from card presence instead of
being unconditionally True
- the 2s UI poll includes /api/status; on disconnect it switches to the
existing 'No card detected. Insert card and click Equip' state, or the
new 'Card inserted — press Equip' hint when the card is back; the old
_hadData heuristic is gone
Tests for the observer (filtering, removal, insertion) and the UI state
transitions. SW cache v90 -> v91.
Every FETCHed proactive command must be answered, otherwise the card is
left in an unfinished session and stops issuing commands (e.g. it will
not deliver a PoR for SEND SM). The menu handlers now share
_menu_send_response, and a server-side watchdog (_arm_menu_timeout /
_menu_timeout_fire, --menu-timeout, default 60s, 0 disables) sends the
timeout result (0x12) when the user never answers. The timer is armed
while a command is pending and cancelled on any response, equip, rescue
and card disconnect.
Also fixes docs/api.md, which had back (0x11) and timeout (0x12) codes
swapped. Tests for arm/cancel/clamping and the flat timeout TR. SW cache
v89 -> v90.
--poll-interval 0 now really disables background STATUS polling (it was
clamped to 1s, and the equip branch force-enabled it anyway). Request
handlers and the poll thread now share _CARD_LOCK so a poll can never
interleave a FETCH/TERMINAL RESPONSE pair — the baseline log showed
AUTO-STATUS chains and duplicate FETCHes inside the equip TP chain.
--timing adds elapsed timestamps, per-reset logging (RESET #n) and
phase durations for startup (init_reader, card_init, pysim_app,
terminal_profile_drain) and equip (onecmd, terminal profile).
AGENTS.md documents the proactive-TR invariant: never fetch without
answering, never fetch twice, one APDU conversation at a time.
Each rule now carries an fciMode ('type' | 'type_size' | 'exact') plus an
fciHex (raw SELECT response) so rules can verify different depths of file
control information:
- Filetype only (FCP): exists + file type
- Filetype + size (FCP): adds file size (or record length/count) - previous
behavior
- Exact FCI: adds byte-for-byte comparison of the raw SELECT response (the
FCP '62' template), catching FID/AID, life-cycle, security-attribute and
proprietary-parameter changes
/profile from card/ gains a matching FCP/FCI selector (default Filetype +
size); rules store fciMode and always capture fciHex so they can be
upgraded to Exact FCI in the editor without rescanning. The rule editor
adds the selector, hides size/record fields in 'type' mode and shows an
editable FCI hex textarea in 'exact' mode. Contents checks stay independent.
Server /api/select now returns fci_hex (raw FCP template hex, uppercased).
profilerValidateProfile accepts the new mode; profilerNormHexStrict added
for byte-exact comparison (no '?' wildcards). Legacy rules without fciMode
default to type_size.
10 new tests (build-rule fields, run-rule modes incl. byte compare +
missing-FCI, validation). Docs + RU i18n synced. SW cache v52 -> v53,
version 1.9.28.
New 'Profiler' sub-tab in card-reader view:
- Named rulesets (profiles) persisted in localStorage; each has an ordered
list of filesystem rules (type 'file', extensible to OTA/TAR checks later)
- List page: New profile / Profile from card / Import profile + per-profile
Edit / Check / Export / Delete
- Editor page: inline rule fields (path, file type, size, record length,
record count, contents with exact/mask/'?'-wildcard modes)
- Results page: sequential rule checks with live progress and a pass/fail
report (existence + FCI attributes + content match)
- 'Profile from card' scans the equipped card; creates a rule only for files
that exist (FCI present). A scan-options dialog lets the user skip contents
of frequently-overwritten dynamic files (LOCI/PSLOCI/EPSLOCI/5GS3GPPLOCI/
Keys/KeysPS/SMS/Kc/KcGPRS/LOCIGPRS/CBMID/SMSS), checked by default
- ADF-rooted paths use the AID; paths resolve via a new server _select_path
Server:
- _select_path() resolves MF/ADF-AID-rooted paths (pySim can't select ADF by AID)
- /api/select and /api/read accept 'path'; /api/select returns file_size,
record_len, num_of_rec
Also fix: silent SELECT (P2=0x0C) no longer emits a trailing Le byte
(chain builder) - matches ETSI TS 102 221 silent-select behavior.
Tests: +profiler.test.js; frontend 117 pass, Python 61 pass.
SW cache v37.
- Removed STK pill from pysim sub-tab bar
- Added 'STK menu' block atop 'STATUS and Polling' in Proactive UICC view
- Shows emerald button when card issued SET UP MENU, 'no menu' text otherwise
- stkCheckMenu toggles button vs no-menu text; refreshed on proactive tab open
- Dropped dead stk-menu-btn exemption in stkSetPillsDisabled
- i18n: Меню STK / Меню не задано картой
- SW cache v36
__main__.py:
- Probe smartcard.System.readers() before init_reader when no reader
was explicitly specified
- Retry 3 times with 2s delay to handle late pcscd startup and
USB enumeration delays
start.sh:
- Also match pcscd.bin process name (some distros)
- Wait 1s for pcscd if not running yet
- Moved Execute button to the right of the Operation dropdown (same row)
- Removed standalone Execute button that was on its own row
- Added LANG_RU translations: Card preset, Operation, Explore Card, Install Package
Docs:
- SCP80 tab is now a parent section (3) with subsections 3.1/3.2/3.3
- Renumbered sections: Response parser→4, Card reader→5, Server→6, Version compat→7
- Applied to help.html, help-ru.html, README.md, README_RUS.md
C-APDU/RAM form labels (GP + JavaCard terminology):
- AID → Application / Instance AID
- ELF AID → Load File AID / Package AID
- Module AID → Executable Module AID / Applet Class AID
SCP80/RAM explorer labels (GP + JavaCard terminology):
- ISD → ISD (Issuer Security Domain)
- Applications → Applications / Applet Instances
- Executable Load Files → Executable Load Files (ELFs) / Packages
- AID → Application/Instance AID or Load File AID/Package AID (context-dependent)
- Module AIDs → Executable Module AIDs / Applet Class AIDs
UX fixes:
- Delete All button now matches Delete button style (red)
- Explorer results cleared when switching away from Explore Card operation
- KIc/KID dropdowns (index + algorithm) now update when selecting saved card
- Added LANG_RU translations for new labels
- README/README_RUS: RAM tab now describes Explore Card + Install Package operations,
explorer view with inline Delete/Delete All buttons for Applications and ELFs
- docs/api.md: add full /api/ram-install endpoint documentation
- index.html: remove Delete from dropdown, add ramDeleteFromExplorer() with confirm dialog,
add Delete buttons in explorer for Apps and ELFs (ELFs get cascade option)
Incoming: _parse_sms_concat parses UDH for IEI 0x00 (8-bit ref) and
IEI 0x08 (16-bit ref). PoRSubmitHandler accumulates segments, sorts by
num, and reassembles complete payload when all parts arrive.
Outgoing: MAX_ENVELOPE_SEGMENTS=5 limits the secured packet to 5
ENVELOPE chunks (650B max at 130B/chunk).
10 new tests for concat parsing and reassembly.
Key fix: re-add chained C0000000 (GET RESPONSE Le=00) in paginate() so the
full GP APDU inside SCP80 is 80F2<p1>p2024F0000C0000000, matching the
working tool. The card's SCP80 layer executes both commands internally
(GET STATUS → 61XX → GET RESPONSE) and puts the final 9000 + data in
the PoR.
Also: server logging improvements (RAM RESPONSE-PACKET label, no truncation
of FETCH/PoR hex), docs for /api/ram-install endpoint, minor test fix.
_send_status sent F2 00 00 -> the card echoes the FCP of the currently
selected DF on every poll/one-shot STATUS. Phones use P2=0C (no
response expected) to avoid that overhead. Keep P1=00 and the SIM/UICC
P3 distinction (SIM=0x23, UICC=0x00):
CLA F2 00 0C P3
Affects one-time /api/status-poll and the automatic polling timer.
Version 1.9.11 -> 1.9.12 everywhere; SW cache otaman-v22 -> otaman-v23
cardsRender() was mutation-only (add/import/remove/counter-sync), so
after a reload the SCP80 -> Cards table showed 'No cards defined.'
even while localStorage held presets and the SP dropdown listed them
correctly. Now:
- cardsLoad() renders the table right after rebuilding the select
- entering the Cards pill re-renders (mirrors pysim-pill pattern)
Version 1.9.10 -> 1.9.11 everywhere; SW cache otaman-v21 -> otaman-v22
- SPI1/SPI2 side by side in one row
- KIc/KID byte selectors side by side in one row
- KIc/KID key textareas moved directly under the KIc/KID selectors
- Card preset selector moved to the top of the form, full width
- Padding byte joins TAR + Counter in one three-column row
- Pure HTML reflow: no id/handler changes, all behavior preserved
(invalidation, presets sync, por_ok counter bump)
Version 1.9.9 -> 1.9.10 everywhere; SW cache otaman-v20 -> otaman-v21
Secured Packet and Cards pages move into a new SCP80 top-level tab
(positioned next to C-APDU), switched by rounded-full pills - both
features are SCP80 concerns and more SCP80 operations are planned.
- nav: 'sp'/'cards' buttons replaced by single data-tab=scp80 button
- new #tab-scp80 with scp80-subtab pills and re-parented page wrappers
(#scp80-sub-sp / #scp80-sub-cards); all element IDs preserved so
genSp/verify/send-to-card/CNTR-sync/preset flows are untouched
- switchTab(): sp/cards branches replaced by scp80 -> help anchor
- new scp80SwitchSubtab(): pill styling, wrapper visibility, per-pill
help anchor (secured-packet | cards)
- packToSp() hands off via switchTab('scp80') + Secured packet pill
Version 1.9.8 -> 1.9.9 everywhere; SW cache otaman-v19 -> otaman-v20
When a secured packet is accepted (por_ok) and a card is selected in
the SP-page card dropdown, the new normalized counter is written back
into that preset (cards[].cntr), persisted via cardsSave() and
reflected in the presets table via cardsRender(). Placeholder option
and unselected state are skipped. Form bump + preset sync happen in
the same okPor block, so form and stored preset always climb together.
Version 1.9.7 -> 1.9.8 everywhere; SW cache otaman-v18 -> otaman-v19
pysimSendOta wrote its progress/verdict into #pysim-status - the shared
header line owned by connect/equip/poll logic - clobbering the card
status shown under 'Equip card'. OTA outcomes now live exclusively on
the Secured Packet page:
- send start: small gray 'Sending OTA...' in #sp-send-result
- verdicts: big #sp-por-status + details line, as before
- header #pysim-status untouched by OTA flows
Version housekeeping: v1.9.6 -> 1.9.7 everywhere; SW cache
otaman-v17 -> otaman-v18. Also realigns pyproject.toml which the
multi-amend rounds had left stale at 1.9.5.
OTA inner-APDU SWs from SIM-domain applications surfaced as 'Unknown
status word' (e.g. 9404 on a failed SELECT inside an authenticated
B00000 packet). SW_MAP.generic gains the complete TS 51.011 §9.4
families, so they decode in every resp-cmd context:
- 9200/9240 memory management (retry / memory problem)
- 9400/9402/9404/9408 referencing management (no EF selected,
out of range, file ID/pattern not found, file inconsistent)
- 9802/9804/9808/9810/9840/9850 security management (CHV/access-
condition/invalidation contradictions and blocked states; 9850
also added to generic alongside its gp/uicc copy)
- 9EXX/9FXX data-download error length / response length wildcards
Exact keys safely coexist with the 92XX proactive wildcard (exact
match wins). TS 102 221 sweep found no further stragglers: 63C0-C9
PIN retry counters are covered by the existing 63CX wildcard.
Secured packet page: enforce fresh packet per send
- any change to APDU/TAR/CNTR/keys/padding/SPI now clears the secured
packet textarea via spInvalidate(), so Send cannot reuse a stale
packet ('No secured packet to send' guards the path)
- on por_ok the CNTR field auto-increments (10-digit hex normalized,
wraps modulo 2^40) and the textarea clears; security-error verdicts
leave both untouched for retry after fixing the cause
SIM/USIM SELECT: P1/P2 per spec + live RFM idioms
- USIM FID selects requested no response data (P2=0C) - that coding is
reserved for the select-MF-by-empty-data special case; ordinary FID
selects now request the FCP template (P2=04) with Le='00' per
TS 102 221 Table 11.2 / pySim sel_ctrl convention; preset tables
fixed in both genSimUsim and updateP1P2Display
- path method gains base selector: from MF (P1=08) / from current DF
(P1=09) - reproduces the dominant live RFM idiom 09/0C
- 'silent' checkbox on fid/path/chain emits P2=0C without Le for hops
where the FCI is not needed
- chain syntax gains GET RESPONSE hops: 'C0' emits CLA C0 00 00 00 and
'C0:NN' sets explicit Le, enabling classic SELECT -> 9FXX ->
GET RESPONSE pairs in a single secured packet so the PoR carries the
actual FCI/response bytes instead of a bare length SW
- SW dictionary: 9FXX reworded to point at GET RESPONSE
- placeholders show the new chain syntax
- findings & backport decisions written to ~/WSL/RFM_notes.md
response_map.test.js: +8 assertions across the new families.
sim.test.js: USIM FID expectation updated; silent/base/chain cases.
apdu_parse.test.js: live capture lines 1-3 as regression fixtures.
Version 1.9.5 -> 1.9.6 everywhere; SW cache otaman-v16 -> otaman-v17
pySim encode_cmd transmits the 2-byte CPL only when ciphering is
applied; unciphered packets start at CHL. genSp always emitted it,
so every SPI1=0x00 packet diverged from the pySim reference at byte 0
(sp-verify MISMATCH) and carried a length octet pair real cards need
not expect.
- genSp output: bytesToHex(ciphering ? packet : packet.subarray(2));
MAC input unchanged (still covers the virtual-CPL frame, matching
pySim's sign-then-strip convention)
- Secured packet page: PoR verdict now rendered prominently in a
dedicated text-base semibold line ('PoR: por_ok' green / other
statuses red) above the small detail line; hidden when no PoR was
requested or the ENVELOPE failed
- sp.test.js: three unciphered expectations updated to CHL-first form;
ciphered vectors untouched (byte-identical)
- version 1.9.4 -> 1.9.5 everywhere; SW cache otaman-v15 -> otaman-v16