Listener Options block (applied at Start, persisted in localStorage, Reset to
defaults): HTTP framing - chunked body, chunk size (0 = one TLS record),
keep-alive, Connection header, compact headers, Next-URI (unchecked = omit);
script framing - indefinite/definite Command Scripting template, CR tag,
targeted app; link events (now parsed on the common start path, so every mode
honors them). scp81OptionsFromForm() is unit-tested.
TLS is automatic: 'auto' (min 1.0, max 1.2 + :@SECLEVEL=0) is the new
default, all six PSK suites are offered and OpenSSL negotiates the highest;
the negotiated version/cipher is logged (tls-handshake) and reported as
version_seen/cipher_seen in /api/scp81/status, and a handshake failing for a
TLS/cipher reason logs tls-handshake-failed (post-handshake record errors
stay tls-error). tls_version/cipher/keylog/answer_delay stay as API-only pins.
Dropped the Apache-style header mimicry completely: no Date/Server/
X-Powered-By, no Content-Length-before-Content-Type ordering, no Content-Type
on 204 - the minimal response set is X-Admin-Protocol (+ X-Admin-Next-URI /
Targeted-Application), Content-Type on 200s, and Transfer-Encoding or
Content-Length per the chunked flag. Docs, help (EN/RU), READMEs and the
AGENTS notes updated; SW cache otaman-v180.
The former 'passthru' mode is now 'redirect': it pins the configured target
and every BIP channel is connected there (the card's requested address is
only logged; host/port required). The name 'passthru' now means the new mode:
no listener and no target - each channel dials the destination the card
requests in OPEN CHANNEL (Other address + Transport level port, TCP client,
remote, 0x02 only). The specs define no default port (TS 102 223 8.59), so an
incomplete or non-TCP request fails the channel with result 3A and an
open-fail log reason.
BipTerminal gains a mode (enable(host, port, mode=...), open(..., proto=...)),
reports it in status(), and the control API/status expose redirect (target)
and passthru (per-channel targets). The PWA mode selector shows four modes
with per-mode notes and disables Host/Port in passthru; the status line shows
each channel's actual target. Docs, help (EN/RU), READMEs and the AGENTS notes
updated; SW cache otaman-v179.
Breaking API change: mode:'passthru' has the new semantics - use
mode:'redirect' for the previous behavior (no alias).
The help pages had no SCP81 section and documented Cards as a 3.2 subsection
of the SCP80 tab. They now mirror the UI: 3 SCP80 (3.1 Secured Packet, 3.2
RAM), 4 Cards tab, 5 SCP81 tab (5.1 Listener, 5.2 Scripts), with the
following sections renumbered (6 Card reader, 7 Profiler, 8 Phone simulator,
9 Server, 10 Compatibility) and the TERMINAL PROFILE range corrected to bytes
1-39 (TS 102 223 5.2 + TS 31.111 5.2). The SCP81 tab and its sub-pills set
their own help anchors; a new test asserts every help anchor used by the UI
exists in help.html. READMEs: seven top-level tabs, SCP80 two pills, Cards
fields/buttons/SCP81 column, SCP81 modes (PSK TLS / pass-through / capture),
PSK pairs from presets, Scripts page and resume semantics. SW cache
otaman-v178.
Audit against GP v2.2 Am.B 4.7 / TS 102 226 / GP Card Spec 11.11 found the
retry waiting delay encoded as plain hex instead of the TP-SCTS semi-octet
order required by TS 102 223 8.38 -> TS 23.040 9.1.2.3 (1 min must be 10,
20 s must be 02); the builder now encodes semi-octets, clamps 0-59 / 0-99 and
pads the 2-byte counter. Odd-length hex is padded instead of producing
fractional BER lengths; empty 83/84/89 sub-TLVs are omitted (lengths are 1-n
per Tables 4-3/4-5/4-8..10) and an empty trigger becomes 81 00; a store
payload above a short APDU is chained as P1.b8=0 STORE DATA blocks (P2 =
block number). Connection presets fixed: device identities 82, alpha 05,
command details 81, bearer 35/03; the A5 store tag and the 'B0,00=unlimited'
counter hint are marked unverified (not in the pinned spec). Docs:
UICC_SPECS.md 9.6.5 example annotated 2 s, findings 10-minute timer corrected
to 1 minute; help updated. SW cache otaman-v177.
The parent field was a select over custom DFs only, so a standard DF (e.g.
MF/7F20) could not be a parent and canonical entries under one were dropped
on import. It is now a free-text path with suggestions from the root, custom
DFs and DFs of the loaded tree (any depth); a bare FID chain is completed
from the root selector. Validation accepts standard or not-yet-seen parents
(flagged in the list), rejects a parent known to be an EF, and import keeps
such entries. Custom-file edits re-inject the loaded tree: new nodes appear
at once, renamed model nodes are restored and injected nodes removed when
their entry goes away. Help updated (the form lives in Profiler, not Card
reader). SW cache otaman-v176.
The actions sat below the note line and the (long, 45vh) bits grid, so they
needed scrolling to reach. They now sit to the right of the preset selector
and hex field, aligned with the preset row; the bit grid is unchanged.
The tree box was capped at a fixed 65vh, which ignored the chrome above it
(header, tabs, status, sub-tabs, sort/probe row), so the page itself grew a
scrollbar once the tree hit its cap. pysimFsFitTree() now caps it to
innerHeight - top - 12px at runtime (the inline 65vh stays as the no-JS
fallback), re-run on resize, on render, on sub-tab/tab entry and when the
probe status line appears.
TERMINAL PROFILE form (Phone simulator):
- The compact block no longer shows the hex value (no room); Send +
Configure + status remain. Preset selector sits above the hex field.
- Bits are never spread horizontally: each byte is one vertical list.
Byte blocks are placed by the fixed TP_LAYOUT groups - bytes 1-12 in
2 columns, 13-16 in 4, 17-18 in 2, 19-21 in 3, 22-25 in 2, 26-28 in 3,
29-30 in 2, 31+ one per row - in strict byte order.
Bit descriptions audited against the pinned specs:
- Table regenerated from ETSI TS 102 223 V18.3.0 5.2 (bytes 1-39) with
the 3GPP-defined bits named per ETSI TS 131 111 V18.12.0 5.2; the
"reserved by 3GPP" placeholders are gone (tests guard this).
- Fixes found in the audit: byte 7 b6 PERFORM CARD APDU (pySim had
RESET), MO short message control by USIM, USSD string DO support in
Call Control by USIM, the earlier "Data available" typo.
- Presets: project default + Quectel example + Samsung S21+ 5G, Samsung
A55 5G, Xiaomi Redmi Note 10 LTE, Sony Xperia Z5c LTE, Huawei
E5573c/M150 (LTE), Huawei E173 3G modem, Nokia 7210 2G (9 total).
Also fixes a page-load TDZ regression: the custom-files init (which
calls t()) now runs after the language init (guard test added).
SW cache otaman-v174; help EN/RU updated; tests 236 Python + 374 frontend.
TERMINAL PROFILE:
- GET /api/terminal-profile returns the profile in effect + the CLI default;
POST /api/terminal-profile validates ({profile}, hex, even, 1-255 bytes),
stores it in memory and re-sends it, resetting the STK session like
/api/rescue (the shared _resend_terminal_profile helper; rescue now
delegates to it). __main__ keeps server.cli_terminal_profile.
- Phone tab: a TERMINAL PROFILE block next to STATUS and Polling with the
current hex/byte count, Send (re-send) and Configure. The Configure
dialog has a device-model preset selector, a hex field and a per-bit
form generated from a 264-entry table for TS 102 223 V18.3.0 5.2 bytes
1-33 (pySim's table as scaffold, later bytes/3GPP bits added from the
spec; beyond the table generic RFU labels). Form <-> hex sync both
ways, hex authoritative, bits preserved. Apply posts the new value.
- Presets: Xiaomi Mi A1 (project default), Quectel GSM module example.
In-memory only, no persistence.
Custom files:
- Canonical paths rooted at MF / ADF.USIM / ADF.ISIM; entries are
{path, name, kind}. The editor now uses root + parent-DF selector +
4-hex FID + alias, requires the parent DF to be defined first, rejects
duplicates, rewrites descendants when a DF's path changes and cascades
deletes after a confirmation.
- Legacy forms are normalized on load/import (3F00/... -> MF/..., relative
a153/4954 resolved against the custom DFs); unresolvable entries are
dropped and reported in the list.
- Tree injection matches by exact parent path via the new pysimFsNodePath
(same-FID DFs under different parents no longer collide);
profilerCustomNameForPath uses the same normalization.
SW cache otaman-v167; help EN/RU + docs/api.md + AGENTS updated.
Tests: 236 Python + 371 frontend.
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.
explore with the fixed response parser ran 18 commands / 10 continuation
pages (real 63 10/9000 statuses) and shows the installed applet:
AA1902BC22580101 (life=07 selectable, elf=AA1902BC225801) in the
applications listing, AA1902BC225801 (life=01) in the ELF registry and with
its module in the ELF+modules listing.
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
INSTALL [for load] -> LOAD x6 -> INSTALL [for install] (SW 9000) against the
live card. Records the parameter-less 6A80 finding and the working
INSTALL [for install] command built from the Remote APDU -> RAM form and
queued via "Queue in SCP81".
- 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
profilerScanCard's walkDir passed each child dir's own segment in
parent_path (dir.parentPath.concat(childSeg) as the child's parent path),
so /api/tree for every DF child walked the target as its own parent and
failed: DF.GSM, DF.TELECOM and DF.GSM-ACCESS subtrees were silently
missing from New snapshot, Profile from card and Profile from snapshot
(ADF children survived only because application names/AIDs resolve
globally).
- walkDir now stores each dir's full path and sends
parent_path = fullPath.slice(0, -1) (omitted for MF); the child's full
path is fullPath.concat(aid/fid/name)
- verified against the running server: the walker now enumerates 189 EFs
and captures 92, matching the file-manager probe exactly (was 111/48)
- test: fake MF -> DF.GSM tree asserts parent_path [undefined, ['MF']]
and that both subtrees' rules are produced; SW cache v132 -> v133.
- new 'Profile from snapshot' button in the Profiles toolbar: reuses the
snapshot picker (profilerSnapshotPickListHtml refactor) and then opens
the same scan form as Profile from card (profilerScanOpenForm), with
the profile name prefilled from the snapshot
- new profiler-build target 'profile-snapshot': the form's ignore list,
mask options and FCP/FCI mode apply to profilerScanSnapshot(), which
walks the snapshot's captured files instead of the card; uncaptured
contents produce rules without a content check, record files map to
exact record content, transparent files honor the masks
- profilerScanStart() pushes the generated profile and opens the editor
like the live scan; _scanSnapshot is cleared in the finally block
- RU entry 'Профиль из снимка'; tests for the snapshot rule builder
(exact/mask/ignore/uncaptured/record), the snapshot scan progress, the
picker wiring and the form title, plus a structural button check;
help EN/RU and READMEs updated; SW cache v130 -> v131.
- new profilerResultsHeaderText() builds the header from a structured
{kind, from, to}: profile checks read 'Profile verification results
for: <profile> -> <card ICCID | snapshot name>', snapshot comparison
reads 'Snapshot comparison results: <master> -> <checked>' (the arrow
matches the one already used in the compare header)
- profilerRunProfile() stores the header and profilerRenderResultsView()
writes it, so the header follows a language switch and the ICCID stays
current; RU keys added ('Результаты проверки профиля:',
'Результаты сравнения снимков:')
- tests: header builder variants + render wiring; flow tests now expect
header objects; help EN/RU and READMEs updated; SW cache v129 -> v130.
- new profilerLabelText() resolves expected/actual labels at render time;
profilerRenderReport and fcpDiffHtml (including the decode-failure
notes) use it, so a language switch keeps the labels localized
- Check card: reads EF.ICCID via /api/read (MF/2FE2) and labels the
report 'expected (<profile>)' / 'actual (<card ICCID>)'; the results
title becomes '<profile> — <ICCID>' (falls back to the plain title and
'actual' when EF.ICCID is unreadable)
- Check card snapshot: labels 'expected (<profile>)' / 'actual
(<snapshot name>)
- snapshot-vs-snapshot compare keeps verbatim master/check names;
the mismatch label column widens to w-48 for prefixed labels
- tests: profilerLabelText, prefixed report/diff labels, profilerCardIccid
decode/fallback, both check flows pass the labels; help EN/RU, READMEs
updated; SW cache v128 -> v129.
The data-l10n key for 'RAM operations perform atomic GlobalPlatform
commands over SCP80. Card keys are taken from the saved preset.' was
missing from LANG_RU, so the hint stayed English. SW cache v127 -> v128.
- ramOpChanged() now clears the previous execution status (result line,
steps, explorer, progress) only when the Operation value actually
changes, so switching Explore -> Install Package no longer shows the
old 'Partial - ELF Modules: no data' until Execute; re-entering the
RAM subtab still preserves the last result
- ramClearResults() also hides the progress caption
- the Card preset dropdown no longer resets to the placeholder after
every executed operation (ramSaveCntr -> ramRender rebuilt it) or on
subtab re-entry: ramRender keeps the current/remembered index,
ramApplyCard and ramExecute remember the selection for the session,
and cardsRemove keeps _ramCardIdx aligned via ramCardIdxAfterRemove
- tests: op-change clearing, selection preservation across rebuilds,
pick/execute remembering, index bookkeeping; SW cache v126 -> v127.
The RAM explorer HTML is generated after load, so its data-l10n sections
and Delete/Delete All buttons stayed English until a language switch, and
the memory/AID/lifecycle labels were hardcoded with no key at all.
- ramRenderExploreHtml() now builds every label and button with t()
(reusing the existing Delete/Delete All/heading/AID keys and wiring the
three previously-unused Application/Instance, Load File and Module AID
keys); new RU keys cover Applications:, Free NV:, Free Volatile:, AID:,
Lifecycle:, Privileges:, SD AID:, Implicit sel:, Version: and (none)
- the explorer dataset is cached in _ramExplorerData and re-rendered by
refreshDynamicI18n() on language switch (cleared on op change/results
reset)
- RAM flow strings localized too: Partial/OK summaries, error labels
(Memory/ISD/Apps/ELFs/ELF Modules/no data), GET DATA progress, delete
confirm/labels, install alerts/progress/steps and Error:
- tests: ram.test.js asserts every explorer label/button goes through t()
and the (none) placeholder; SW cache v125 -> v126.
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.
The File: EF.DIR [LF] line doubled as the selection store: pysimFsClickFile
wrote a formatted display string there and pysimFsRead/Save parsed it back
(stripping '✗ ' and ' [TYPE]'). The pane now keeps the name in
pysimFsSelected, the redundant title is gone (FID/type/size/FCI remain in
the info block), and the dead pysimFsSelect() plus the unused 'File:' RU
entry are removed. Read/Save still re-select on the server first: pySim's
current selection is a shared cursor that tree expansion, scans and
commands move, so it cannot identify the file shown in the pane.
SW cache v121 -> v122; structural test updated.
The decoded FCI items now follow the FID/type/size line directly; the
snapshot detail view keeps its caption. Test updated to assert the
caption is absent; SW cache v120 -> v121.
- selecting an EF now renders an info block between the File: title and
the contents: FID, file type, size, record length/count (nulls skipped)
followed by the decoded FCI via the existing profilerFciPreviewItems()
decoder (no raw hex), matching the snapshot detail view
- pysimFsInfoHtml() is pure and reused by pysimFsClickFile(); the [LF]
shorthand stays in the title; custom files get the same metadata from
the temporary select probe
- tests: pysimFsInfoHtml in profiler.test.js (full response, missing
fci_hex, skipped nulls) and a structural check for #pysim-fs-info;
help EN/RU and READMEs updated; SW cache v119 -> v120.
- EN: Custom files are added in file manager tree and included in card scan.
- RU: Пользовательские файлы добавляются в менеджер файлов и учитываются
при чтении карт.
- SW cache v118 -> v119.
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 failure now only marks exists=false and re-renders;
loaded children stay in the node and are hidden by the renderer (red
cross, no toggle, no (empty)), so a failed DF is re-attempted on the
next probe/refresh instead of being short-circuited
- dropped the parent_sel-less /api/tree retry: error payloads now carry
exists:false, so it doubled requests for every absent DF and re-selected
the same FID without its parent (pySim probe_file fallback)
- probe walks strictly on exists===true and never collects files under a
non-existing DF; no longer clears children on a failed EF select
- tests updated: single request per failed load, children untouched,
200 exists:false marks absent without retry, render hides stale
children, probe never fetches/selects them; SW cache v116 -> v117.
- 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.
- each row now shows Edit and Delete buttons (same styling as the
profiler list buttons) instead of the red X glyph
- Edit reloads the entry into the top form: the Add button becomes
Save (data-l10n updated too) and a Cancel button appears; submit
updates the entry in place, recomputing fid/parentFid and excluding
the edited row from the duplicate-path check
- Delete removes without confirmation; deleting the row being edited
cancels the edit and an earlier deletion shifts the edit index
- pysimCustomAdd renamed to pysimCustomSubmit; tests for the whole
flow; SW cache v114 -> v115.
- top-level tab order is now Remote APDU, SCP80, Profiler, Card reader,
Phone simulator; the profiler view moved with its button so the DOM
order matches the tab strip
- docs (help EN/RU, READMEs) and the tab-order test updated; SW cache
v113 -> v114.
- the SVG artwork occupies only ~46% x 63% of its 183x183 canvas, so
the 18px render showed ~8x11px of ink; 30px roughly doubles the
visible glyph while staying under the 32px h1 line-height, so the
header row height is unchanged
- guard test asserts the inline size stays within the 24-32px budget;
SW cache v112 -> v113.
- the wrapper is select-none with an inline cursor:default (the compiled
CSS has no cursor-default utility), so hovering the dot/icons no
longer shows the I-beam or allows text selection
- SW cache v111 -> v112.
- the dot carries its own title (Connecting... / No server connection),
matching the wrapper tooltip; it is cleared when the sim icons replace
the dot
- tests assert the dot title in both server states and its removal once
the server is up; SW cache v110 -> v111.
- #state-indicator is now a wrapper with a dot plus an 18px dark:invert
image; no new compiled Tailwind classes (h-4/w-5 are not in the
prebuilt CSS), so sizing uses an inline style and the header height
is unchanged
- gray dot while probing, red dot when the server is unreachable;
server up -> nosim.svg, animated sim_anim.svg while equipping, and
sim.svg when the card is equipped (`_pysimEquipping` tracked from
status.equipping, refreshed on every 2s poll)
- i18n reuses existing strings; tests for all five states, dot color
transitions and the markup; SW cache v109 -> v110.
- pysimFsSortChildren() groups DFs above EFs, then sorts each group by
FID (default) or case-insensitive symbolic name; missing names fall
back to the FID as the sort key; deterministic tie-break; the input
array is not mutated
- render uses the sorted copy, so lazily loaded directories and custom
file injections are sorted too
- FID / Name pills above the tree (pysimFsSetSort) persist the choice in
localStorage (otaman_fs_sort) and re-render without refetching
- tests for the comparator (DF priority, both keys, fallbacks, custom
entries, no mutation) and the pill wiring; help/README updated;
SW cache v108 -> v109.
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.
The message branch treated the static auto_equip config flag as if
initialization were in progress, so with auto-equip on (default) a
cardless server showed 'Card inserted — initializing...' instead of the
no-card message. The initializing state now requires equipping or an
actually present card with auto-equip enabled; regression test added.
SW cache v106 -> v107.
SCP80, Card reader and Profiler list pill rows now use the same centered
layout as Remote APDU and Phone simulator (flex-wrap justify-center).
SW cache v105 -> v106.
Profiles / Card snapshots / Custom files switch from top-bar style tabs
(border-b, rounded-t) to the rounded-full pill style used by the other
second-level switchers. SW cache v104 -> v105.
Availability model: server-down / server-up-no-card / card-equipped.
- header shows a symbolic indicator dot (red/yellow/green, tooltip)
- a 5s probe tracks /api/version+/api/status while not fully ready; the
2s status poll takes over once connected
- controls declare data-needs='server' or 'card'; disabled + tooltip when
the state does not satisfy them: equip (server), status/reset, file
manager read/save/edit, raw APDU, profile-from-card, new snapshot,
profiler list Check card, event Send, RAM install/explore delete,
STK menu, Send STATUS, Verify vs pySim (server), pySim execute (server),
PLI save/poll toggle (server)
- pysimConnect no longer conflates server and card: it records both and
Connect stays usable without a card
- ramSendOta/ramInstallCap now use pysimFetch instead of raw fetch
(relative /api URLs broke custom server URLs)
Custom files moved from the Card reader sub-tabs to the Profiler list
tabs (Profiles / Card snapshots / Custom files); help/README/AGENTS
updated. Tests: availability gating, indicator structure, moved tab.
SW cache v103 -> v104.
Snapshot comparison results now use the master/checked snapshot names
where profile checks keep expected/actual: the raw-data field rows, the
generic mismatch line and the decoded FCI comparison table headers
(profilerRenderReport/fcpDiffHtml take an optional {expected, actual}
labels object; the name column widens to w-40 and wraps). Labels are
stored with the results so the language switch and the Only mismatches
filter re-render correctly; Check card snapshot and live checks are
unchanged. Tests for both labeled and default rendering; help/README
updated; SW cache v101 -> v102.
Each snapshot row now renders '(N files, scanned in X.XX sec)' when the
snapshot has timing data (profilerSnapshotCountLabel); old/imported
snapshots without timing keep the plain '(N files)'. RU wording 'за X.XX
сек'. SW cache v100 -> v101.
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.
'Profile from card' and 'New snapshot' now focus the name field when the
dialog opens, and Enter in that field starts the scan
(profilerScanNameKeydown, ignored while the Scan button is disabled).
Tests for the key handler and the input wiring; SW cache v98 -> v99.
When a navigation fetch failed and the cache had no entry for the exact
URL (e.g. '/' while only 'index.html' is precached, common while the
local server restarts), the fetch handler resolved respondWith() to
undefined, producing 'TypeError: Failed to convert value to Response'.
The navigate fallback now chains caches.match(request) -> cached
index.html -> an explicit 503 offline Response. sw.js requests are
network-only with the same offline Response (previously they fell into
the navigate branch), and a new unit test loads sw.js in a VM sandbox
covering navigate/cache/asset/api paths. SW cache v97 -> v98.
After the gray text was raised to #cbd5e1 in dark mode, elements using
dark:text-slate-300 (inactive tabs, labels, list text) matched the muted
level. Map dark:text-slate-300 to #e2e8f0 (slate-200, the body's dark
text color) so normal text stays one step brighter than muted gray.
SW cache v96 -> v97.
Elements using bare text-gray-300/400/500/600 (proactive log ids and
timestamps, expand markers, event codes, ...) kept the light-theme gray
values in dark mode, which made them hard to read on slate-800/900.
- dark .text-gray-*:not([class*='dark:text-']) rules map them to the same
level as the other dark muted text (slate-300 #cbd5e1, slate-200/100 for
gray-700/800)
- dark:text-gray-400/600 and dark:text-slate-500 normalized to #cbd5e1
- dark:border-slate-600/700/800 (+ /50) brightened one step
- light theme unchanged
- the whole contrast block moved to src/contrast.css, appended by the
npm build scripts after Tailwind so it stays reproducible; SW cache
v95 -> v96
Wrap each block of the phone row in a bordered card (border, rounded,
p-3) with items-start and gap-4 so they read as distinct panels and wrap
cleanly. SW cache v94 -> v95.
- Phone pill: STK menu, STATUS and polling and the subscribed-events list
in one row, proactive command log full-width below (room for more
elements)
- TR Config pill: response data injected into TERMINAL RESPONSEs;
currently the PROVIDE LOCAL INFORMATION dictionary, structured as
heading + body blocks for future proactive-command responses
- phoneSwitchSubtab() mirrors the other sub-tab switchers and sets help
anchors stk-menu / pli-dict; switchTab('phone') always opens Phone
- refreshDynamicI18n renders only the visible phone panel
- help EN/RU section 6 regrouped (6.4 STATUS polling under Phone, 6.5
TR Config response data), READMEs and AGENTS.md updated
- structural and behavioral tests (html.test.js, phone_tabs.test.js);
SW cache v93 -> v94
- C-APDU tab renamed Remote APDU (label untranslated EN/RU); Response
parser moved from a top-level tab to a pill under it
- Profiler moved from the Card reader sub-tabs to a top-level tab
- Proactive UICC moved to a top-level tab and renamed Phone simulator
- Card reader keeps File manager, Custom files, pySim command line,
Raw APDU
- modals (event send, profiler scan/snapshot, STK menu) moved outside
the tab containers so they can open from any tab
- STK overlay disables the top-level tabs while waiting for user input
- OTA PoR jump now goes to Remote APDU > Response parser
- help EN/RU restructured (2.8 Response parser, 5 Profiler, 6 Phone
simulator, subsequent sections renumbered, anchors kept); READMEs and
AGENTS.md updated
- html.test.js asserts the new tab/pill structure; SW cache v92 -> v93
- _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.
A probe can leave a card in a context where CLA-00 file access returns
6d00, so the software MF select fails (stock pysim init survives only
because it resets physically after probing). Fast init now recovers the
same way, on demand:
- FastRuntimeState.reset() falls back to hard_reset() on
SwMatchError/ProtocolError (logged as FAST-RESET)
- init_card_fast() retries once after sl.reset_card() (FAST-INIT)
- __main__ falls back to stock pysim init once and skips
TERMINAL PROFILE/drain when no card was initialized (no more
6d00/6985 noise after a failed init)
- do_equip_fast() no longer pre-unregisters command sets; PysimApp.equip
does that after a successful init, so a failed equip keeps the
previous card/rs instead of leaving the app unequipped
Tests for the escalation, the retry and failed-equip state retention.
No card-model special cases.
Fast init (fastinit.py) is now the default for startup and equip: all
profile probes run on one connection, RuntimeState uses a software reset
and only explicit equip/reset reconnect the card. Measured on the
reference reader: equip 1729ms vs 9850ms and zero RESET events vs 7-8.
--full-pysim-init restores pysim's stock init_card/equip for
compatibility/debugging; the former --fast-init flag is kept as a hidden
no-op alias so existing command lines keep parsing. Docs updated.
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.
pysim's init_card() resets the card once per profile candidate in
CardProfile.pick(), once in RuntimeState.__init__ and again in
PysimApp.equip(); on typical readers each reconnect costs ~1.3s and a
normal init/equip does 7-8 of them (measured: equip 9.85s, startup
~14.8s).
fastinit.py mirrors pySim.app.init_card() with the resets removed:
pick_profile_no_reset() runs all profile probes back-to-back on one
connection, FastRuntimeState.reset() is a software reset (select MF,
clear selected_adf/scp, ATR from the transport) and the equip/reset
commands are routed through do_equip_fast (one reconnect via
wait_for_card) and do_reset_fast (always a physical reset). Enabled
with --fast-init; stock behavior remains the default.
Tests for the reset-free pick, the soft reset and the explicit reset
paths. Docs updated; SW cache v88 -> v89.
--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.
Add AGENTS.md to .gitignore so the local agent guide (goal, implemented
features, pySim usage) stays untracked. Correct the /api/ram-install
description: the .cap archive is parsed server-side by _cap_parse, not
by pySim.javacard/global_platform.
README(.md/_RUS.md) gain the sections that existed only in the in-app
help: C-APDU Parser, HTTP OTA (GP GPC v2.2 Amd B v1.1 4.7), and the
Profiler subtree (profile list, filesystem rules, scan options, check
report incl. Only mismatches, card snapshots, Check card snapshot and
Compare snapshots). Also correct the tab/sub-tab counts and add
Theme/Localization sections to the English README.
docs/api.md: remove the duplicate /api/ram-install section, document
fci_hex/file_size/record_len/num_of_rec in /api/select, the records
shape of /api/read, bump the version example, and add the missing
endpoint sections (events, event-send, proactive-log, status-poll,
rescue, poll-status, poll-toggle, pli-qualifiers, pli-dict).
New Compare snapshots button on the Card snapshots tab opens a dialog
with Master snapshot / Snapshot to check selects and the two mask
options (Match first 4 bytes for EF.IMSI / EF.ICCID, checked by
default). The comparison runs like a profile check where the master
snapshot takes the place of the profile: every file must match exactly
(exact FCI, contents), except the first 4 bytes of masked EF.IMSI/EF.ICCID.
profilerRulesFromSnapshot() synthesizes exact-match rules from the
master; profilerSnapshotSource() of the checked snapshot is the data
source; files present only in the checked snapshot are appended as
failed 'extra file' results via profilerExtraFileResults() and the new
optional extraResults param of profilerRunProfile(). Results reuse the
existing view, summary and Only mismatches filter; Back to list returns
to the snapshots tab. Tests for rule synthesis, masking, comparison and
extra files. SW cache v87 -> v88.
The results view header (live card and snapshot checks) gets an Only
mismatches checkbox that hides all passing files and keeps only failures
and errors; when everything passes under the filter, a 'No mismatches'
note is shown instead. The pass/fail/error summary always reflects all
results. Helps with large profiles on cards where most files match.
SW cache v86 -> v87.
Each profile row gets a Check card snapshot button next to Check card.
It opens a picker listing the stored snapshots (name, ICCID, date, file
count) and runs the profile rules offline against the selected snapshot,
showing the usual report titled 'profile — snapshot'.
profilerRunRule() accepts an optional data source: profilerLiveSource()
(default) wraps /api/select + /api/read, profilerSnapshotSource() serves
select/read from the snapshot files. Contents missing from the snapshot
are reported as unverifiable errors ('Content not captured in snapshot')
rather than mismatches. The shared check loop moved into
profilerRunProfile().
Tests for the snapshot source and snapshot-based checks; help docs
updated; SW cache v84 -> v85.
translatePage() only rewrites static [data-l10n] elements, so labels built
at render time (Check card/Edit/Export/Delete, Open, Remove, Send, ...)
stayed in the old language. toggleLang() now calls refreshDynamicI18n(),
which re-renders only the visible dynamic views:
- profiler sub-tab: current view (list, snapshot list, editor, results via
the new profilerRenderResultsView with profilerResults stored, snapshot
via profilerRenderSnapshotData which leaves the name input alone)
- open scan modal: profilerScanRefreshOptions re-translates the mask labels
without touching checkbox state
- cards table (cardsRender), proactive events/log/PLI (async, only when
the sub-tab is visible)
Also removed the duplicated translatePage() call in toggleLang.
New test for profilerScanRefreshOptions. SW cache v83 -> v84.
Rectangular top-rounded tabs with a bottom border, matching the app's
top-level tab bar (incl. dark:bg-blue-500/dark:text-white active state,
toggled by profilerListSwitch). SW cache v82 -> v83.
The list view now has a centered pill row (Profiles | Card snapshots) and
shows one list at a time; the last active tab persists while the app is
open. Snapshot flows (scan, import) switch to the snapshots tab, profile
import switches to profiles. Help docs reworded (tabs). New DOM test for
profilerListSwitch. SW cache v81 -> v82.
New read-only capture entity stored in localStorage 'otaman_snapshots':
- profiler list view is now two labelled columns: Profiles and Card
snapshots; each snapshot row shows name, decoded ICCID, date, file
count and Open / Export / Delete buttons
- New snapshot reuses the scan modal in a snapshot mode (name only, no
ignore/mask/FCP options) via profilerScanCard(..., 'snapshot') ->
profilerBuildSnapshotFile: metadata + raw FCI + exact contents for
every readable file (no ignore list, no masking). The scan returns to
the list afterwards.
- ICCID is decoded from EF.ICCID (2FE2) with decIccid() (nibble-swapped
E.118 digits, F pad per TS 102 221 13.2) and stored as an immutable
snapshot field, shown next to the name in the list and the view.
- Open shows all captured data read-only (attributes, raw FCI + decoded
FCI, contents or 'Not captured'); only the name is editable/saveable.
- Import/Export/Delete + profilerValidateSnapshot; quota-safe save.
Tests: decIccid, profilerSnapshotIccid, validation, snapshot builder
(exact contents/no mask/unreadable), snapshot-mode scan with ICCID.
Docs synced (help/help-ru). SW cache v79 -> v80.
Adds an 'All' header checkbox next to 'Ignore contents of files:' that
checks or unchecks every file; it reflects the list state (checked only
when all are checked, indeterminate on a mixed selection) and updates on
individual checkbox changes. RU label: 'Все'. Docs synced, tests for the
toggle/state helpers. SW cache v77 -> v78.
fcpDecode now parses with a partial-aware walker (fcpParseTlvs) that keeps
every complete TLV it encounters and reports why it stopped:
- 'TLV 62 declares N bytes, only M available' (truncation)
- 'Truncated length field at offset X' / 'incomplete TLV header'
- 'Invalid length form' / 'Trailing data after TLV 62'
- inner A5/C6 errors prefixed with their context ('In A5: ...')
The editor preview shows the decoded parameters plus a red
'Decode failed: <reason>' line (RU: 'Ошибка декодирования'), and the check
report's decoded FCI diff appends per-side error notes (expected/actual)
while still showing whatever decoded on either side.
SW cache v74 -> v75.
parseBerLen treated the first long-form length byte (0x81/0x82/...) as the
count of length bytes, so any TLV with a long-form length parsed as
garbage: the profiler FCI decoder returned ok:false (decoded preview
disappeared and never came back after editing an FCP with a '62 81 xx'
outer length or a long-form inner TLV), and the same bug hit parseTlvList
(C-APDU parser, INSTALL param walker), parseBerScript (expanded script
rows >= 128 bytes) and readLvField.
Per ISO 7816-4 5.2 / UICC_SPECS.md 1.7 the long form is 81-84 followed by
(b & 0x7F) length bytes. Fixed; short-form behavior unchanged.
New tests: parseBerLen short/81/82 forms, parseTlvList long-form outer and
inner TLVs, fcpDecode long-form regression (81/82, nested A5, >=128-byte
FCP) incl. the editor preview path. SW cache v73 -> v74.
The exact-FCI row is now a two-column flex layout: the editable raw FCI
textarea on the left, the decoded FCP list on the right (50/50, textarea
4 rows). Preview rendering split into profilerFciPreviewItems; a new
profilerFciInput hook updates the rule and re-decodes the panel on every
keystroke (no editor re-render, no focus loss), showing an empty body
while the hex is incomplete/invalid. Docs wording updated. SW cache
v72 -> v73.
Adds a spec-verified FCP/FCI decoder (ISO 7816-4 5.3.3 Tables 12-14,
TS 102 221 11.1.1.4; cross-checked against pySim ts_102_221.py — spec wins
on the data coding byte and termination mask). fcpDecode accepts the FCP
template '62', an FCI '6F' wrapper and bare FCP content, and decodes:
file size/total size, file descriptor (access/shareable, file type,
structure incl. BER-TLV/SIMPLE-TLV, data coding byte, record length/count),
FID, DF name, SFI, life cycle status, security attributes, the A5
proprietary sub-DOs (UICC characteristics, power, clock, memory, file
details, sizes, commands, environmental conditions, test config) and the C6
PIN status template DO. Unknown TLVs are preserved raw.
Usage:
- check report: an 'Exact FCI' mismatch now renders a decoded
expected/actual table under the raw fields, highlighting differing
parameters and showing missing ones as '—'
- rule editor: the FCI hex textarea shows a live decoded FCP preview
10 new tests (vectors incl. pySim linear-fixed, malformed inputs, diff
highlighting, report integration). Help docs updated. SW cache v71 -> v72.
Path, FCP/FCI check, file type and size (or record length + count) now
share one flex-wrap row; path column narrowed from w-72 to w-48. The FCI
hex textarea (Exact FCI mode) keeps its own full-width row and the
contents check stays on the following row. SW cache v70 -> v71.