- TP_BITS: fill the value-field rows that were all-RFU (byte 11 soft keys,
byte 13 b6-b8 channels, byte 14 height + ND/NK/sizing, byte 15 width +
variable fonts, byte 16 effects + width reduction, byte 19 TIA version,
byte 24 max frames), per TS 102 223 5.2.
- TP_VALUE_FIELDS + tpGetValue/tpSetValue (little-endian bit ranges) and
number inputs in the Configure dialog; the byte 11 'FF' reserved note
stays visible in the hint; flags remain checkboxes.
- Tests: new label spot checks, value round-trips/clamping, render guard.
- docs/TERMINAL_PROFILE.md: byte 22 b1 not universal (S25 Ultra/edge 60
clear, WISMO228 sets), b2/b3 counts fixed, byte 21 markup present in
Pixel 3/4 + OnePlus 12, ESN is a capability bit not a CDMA marker,
0xFE sentinel unexplained by the spec, channel count 5 added, new
Appendix A with the value-field ranges.
- Version 2.2.21, SW cache otaman-v188.
The spec tables (TS 102 223 5.2) list b1 - the least significant bit - first:
verified from the PDF connector geometry (label row 1 connects to the
rightmost cell) and from Wireshark's packet-gsm_sim.c masks ('Profile
download' = 0x01, 'E-UTRAN bearer' = 0x40, 'HSDPA bearer' = 0x80). The
Configure dialog had it mirrored: TP_BITS[n] was matched with 0x80 >> (n%8)
and displayed as b8..b1, so every label toggled the wrong physical bit
(our own BIP-capable default proved it: byte 12 = 0x1F read as DECLARE
SERVICE.. instead of OPEN/CLOSE/RECEIVE/SEND/GET CHANNEL STATUS).
- tpGetBit/tpSetBit now use 1 << (n%8); the form lists b1 first, matching
the spec; tpBitLabel names the RFU fallback bit accordingly.
- Tests pin the new mapping and the corrected spot-check indices, and the
project default is asserted to decode as BIP-capable (OPEN/SEND DATA,
E-UTRAN, HSDPA).
- docs/TERMINAL_PROFILE.md (in projects/docs): everything gathered about
inferring device characteristics from a TP - bit order, profile-length era
bands, headless/modem markers (Annex S ND/NK plus the byte 11/screen-size
heuristics), radio generation (byte 17 b7/b8 with the 'capability, not
attach' caveat), standards family (ESN/IMEISV/TIA/CCAT), BIP capability,
UI paradigm, and a suggested inference model. Corpus: samples/tp_db.csv
(185 profiles, CC-BY-SA, kept out of the repo).
- Help EN/RU note the bit order and point at the new document; AGENTS
records the mapping rule and the doc location.
- The card create/edit form now surrounds the SCP80 parameters (KIc/KID,
SPI1/SPI2, TAR, counter, keys) with a bordered <fieldset> labelled
'SCP80 (GSM 03.48, ETSI TS 102 225)' and the PSK pair (with the listener
explanation) with one labelled 'SCP81 (HTTP OTA)'; the name/ICCID row and
the list stay outside the groups.
- i18n: the SCP81 PSK note is translated now, plus the keys an audit found
missing from the RU dictionary (Start, Clear, Loading..., Working...,
APDU hex, CAP file (max 48 kB), Install from .cap, Card reader server is
not connected, KIc/KID required messages, Memory (GET DATA FF21), the two
no-secured-packet alerts, Select, event notification to the card?).
- Tests: html.test.js guards both fieldsets, their legends and that the PSK
note and fields live inside the SCP81 group (401 frontend tests).
- Docs: help EN+RU Cards intro. SW cache otaman-v186.
- The card create/edit form gets a From card button next to the ICCID
field: it fills the field with the equipped card's EF.ICCID. It uses the
availability model through a new 'card-iccid' requirement - enabled only
while /api/status reports a connected card with a readable iccid; with a
card but no readable ICCID the tooltip says so, otherwise the usual
'insert and equip' hint applies. The last status ICCID is kept in
_pysimCardIccid (cleared on disconnect) so the click needs no round-trip.
- Duplicate ICCID refusal now compares normalized values via
cardsNormIccid/cardsFindDuplicateIccid (digits, spaced digits and raw EF
hex all identify the same card), skips the row being edited, ignores an
empty field, and names the conflicting preset in the alert (translated).
- i18n RU: From card -> 'С карты', duplicate/unreadable alerts.
- Tests: cardsFindDuplicateIccid across formats and edit-skip,
cardsIccidFromCard fill + alert path, form wiring guards, _pysimCardIccid
tracking in the card-state harness, card-iccid availability gating in the
indicator harness. 261 Python + 400 frontend tests green.
- Docs: help EN+RU Cards field table, READMEs, AGENTS. SW otaman-v185.
- __main__ catches NoCardError from the startup init (fast and stock paths,
and the stock fallback) and reports it with one line - 'INIT: no card in
the reader - server ready; insert a card or press Equip' - without a
traceback and without the stock-pysim fallback. Any other init failure
still falls back with its traceback.
- The cardless PysimApp construction no longer prints pySim's 'Waiting for
card...' or pySim-shell's 'pySim-shell not equipped!': a new _LineFilter
(server.py) drops whole matching lines while proxying encoding/isatty/
fileno (the cmd2 Rich console probes them on its stdout).
- Removed the now-duplicate 'INIT: card not initialized' tail line and the
cat_cla assignment when no card was detected (scc exists but is cardless).
- Tests: tests/test_startup_cardless.py (line filter semantics incl. partial
lines, one-line cardless report, no fallback call, real failures keep the
fallback + traceback, muted PysimApp construction). Live-verified against
an empty SCR 3310: two init lines, no traceback, API serves 2.2.17.
- Docs: READMEs quick start, help EN+RU (Card reader), AGENTS (equip flow).
SW cache otaman-v184.
- Server: _decode_iccid (nibble-swapped E.118 digits, trailing-F pad) and
_read_iccid (best-effort MF/2FE2 read through the parent-scoped select
helper; the previous selection is restored, the read never raises). The
equip path records the digit string before the TERMINAL PROFILE, i.e.
before any CAT session is active; a startup with a card does the same.
The value is cleared on card removal and exposed as /api/status 'iccid'
(only while connected).
- PWA: when a connected status update reports a *new* ICCID, the matching
card preset is selected in both SCP80 views - Secured Packet (sp-card-sel
+ form fill) and RAM (ram-card-sel + _ramCardIdx). Matching normalizes
digits and accepts the raw EF hex form, leading zeros ignored; a manual
choice for the same card is kept until the next equip, and a card removal
re-arms the auto-selection. The status line shows the ICCID.
- Tests: tests/test_iccid.py (decode variants, model + probe read, equip
recording, disconnect clearing) and frontend/tests/cards_iccid.test.js
(normalize, find, select, no-override, card swap); the card_state test
harness stubs the new hook and covers the guard reset.
- Docs: api.md /api/status fields, help EN+RU (SCP80 intro + Cards tab),
READMEs, AGENTS. SW cache otaman-v183.
The dictionary hint, the RU help paragraph and README_RUS use the short
'выкл - команда без ответа' phrasing for the unchecked Next-URI case.
SW cache otaman-v182 (index.html/help-ru.html changed).
- Keep-alive is no longer a setting: the TLS connection stays open between
POSTs for the whole dialog (the card, as HTTP client, may still dial a new
one at any time - GP Am. B 4.3.1) and only the 204 ends it, with a clean
close_notify while the response is still buffered, then FIN. Dropped
keep_alive/on_before_close/_scp81_wait_drained and the Connection-header
'close' value (the API, status and UI no longer carry a keep-alive knob).
- The Listener Options block is a collapsed <details> with a 'custom' badge
when anything differs from the reference defaults; the Reset button moved
into the body so it cannot toggle the panel.
- X-Admin-Targeted-Application is opt-in (checkbox + //aid/... field, field
disabled while off); X-Admin-Next-URI has a checkbox + hint explaining the
one-shot rule of GP Am. B 4.4.2; both dependent fields grey out when
unchecked.
- Labels/i18n: 'Chunked body (Transfer-Encoding: chunked)' stays English,
'Show link events (...)', 'Теги comprehension-required'; the compact-header
wording now spells out that it omits the optional space after ':' (legal
per RFC 7230 3.2 OWS; saves one byte per header).
- Tests: a 200 keeps the socket for the next POST; the 204 closes with a
mutual close_notify exchange; options helper/badge unit tests; removed the
close-per-response and drain-wait tests. SW cache otaman-v181.
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.