- the header title drops its "UICC" prefix — "GSMTAP trace analyser" is enough
- the trace topbar's search input is half its previous width (flex basis
33.333% -> 16.667%); the session name now ellipsizes instead of wrapping
the tighter row
- the side-panel view selector moved out of the scrollable side panel into
the trace topbar: right-aligned over the panel at the panel's width
(--side-panel-w: 260px, the negative right margin cancelling the topbar's
16px padding so the edges line up), so it stays visible at all times while
the panel scrolls
- tests: the topbar/selector position pins (the Files-view wiring test and
style.test.js), the shared width variable, the halved search basis and the
title; 527 Python + 61 Node tests
- README/README_RUS: the selector's new place; PWA_VERSION/pyproject/
version.py 2.6.3, SW cache simtrace-analyser-v77
- focusMessage()/scrollToType() highlighted the target and removed the class
after 1500 ms, so a Files-row (or map/category) jump looked selected for a
moment and then lost it; on top of that any re-render (the 500 ms live
poll, a filter/search change) rebuilt the list and dropped the highlight —
even for a clicked entry
- the selection is now persistent app state (STATE.selectedIdx): the render
marks the entry (gap rows included), focusMessage records the index before
rendering, showDetail records it for plain clicks, scrollToType persists it
too (clearing the previous highlight), and a new session/capture resets it
so a stale index cannot mark an unrelated APDU
- tests: a stub-driven focusMessage case (state + detail updated, no render
and no timer), the filter-clearing case, and source pins for
renderMessages, focusMessage, scrollToType, showDetail, loadSession and
startCapture; 527 Python + 58 Node tests
- README/README_RUS: the jump leaves the record selected; PWA_VERSION/
pyproject/version.py 2.6.2, SW cache simtrace-analyser-v76
- jumpToMessage() rendered the APDU list through the unqualified
renderMessages(), whose tail pins the view to the bottom (the live-capture
stick), and only then let focusMessage() smooth-scroll back up to the
clicked record — the visible jump-to-end-then-back. It now renders with
skipScroll, so the only scroll is the focus itself
- the content-follow became conditional: shouldStickToBottom() pins the view
to the newest messages only while the reader is already at the bottom
(40 px slack), so the 500 ms live poll no longer pulls away a view the
reader scrolled to — a Files/map jump is no longer yanked down by the next
batch. A freshly opened session or a new capture still lands at the
bottom (renderMessages(false, true))
- tests (node): the jump's paging/render(true)/focus sequence with stubs, the
no-session no-op, the stick decision (at bottom / within slack / scrolled
up / explicit jump / forced / first render) and source pins for
renderMessages, jumpToMessage, loadSession and startCapture; 527 Python +
53 Node tests
- README/README_RUS note the follow behaviour; PWA_VERSION/pyproject/
version.py 2.6.1, SW cache simtrace-analyser-v73
- CLA decode (TS 102 221 §10.1.1/§10.3): standard channels 0-3 from b2-b1
and the extended channels 4-19 from b4-b1. The old further-format rule
read only b2-b1 (channel 8 read as 6, 19 as 7) and the ETSI '11x0 xxxx'
coding (0xC0-0xEF) was not handled at all; the class ranges now follow the
specs (0x80-0x8F UICC, 0xA0-0xAF SIM, 0xC0-0xEF ETSI extended, 0x20-0x3F
reserved, 0xFF invalid) and SM/chain are decoded per family — the SM
extraction and the chaining bit (b5, not b6) were off by one bit
- per-channel state: database._decode_rows/_replay_context use the shared
cla_channel() helper — before, CLA & 0x03 with no +4, so a channel-5
SELECT clobbered channel 1's selection and a channel-8 one channel 0's
(the local traces already open channels: 29 MANAGE CHANNEL opens, 1-2)
- COMPRION T=0 import: the synthesized GET RESPONSE keeps the channel —
a plain CLA for 0-3 (as real terminals do) and 01x0 for 4-19
- ATR historical bytes: the UICC's COMPACT-TLV objects (TS 102 221 §6.3.1)
with card service data, the card capabilities (ISO 7816-4 §8.1.1.2.7
Table 88: assignment methods and max logical channels — the workspace
cards announce 4 and "8 or more") and the status indicator ('00' trailing
bytes / the '8Y' TLV); a truncated ATR no longer raises
- MANAGE CHANNEL success replies decode the assigned channel byte
- capture/import: a 'tpdu' packet that is not APDU-shaped (shorter than the
header, CLA FF, ISO-reserved class) is skipped and counted, so a
character-level or mis-sampled stream no longer pollutes the decode
(the local DB's outlier sessions are exactly that)
- tests: CLA table vectors, per-channel state isolation on channels 8/12,
COMPRION GR channels, filetrack extended channels, ATR compact-TLV and
card-capability cases, the capture skip counter, the PWA rows; 527 Python
+ 47 Node tests
- README/README_RUS document the extended channels, card capabilities and
the skip counter; PWA_VERSION/pyproject/version.py 2.6.0, SW v72
- the file/DF/SFI dictionaries move to simtrace_analyser/ef_tables.py and are
extracted from the pinned specs: TS 102 221 (MF, Annex H.1), TS 31.102
(ADF USIM 4.2 and the child DFs 4.3/4.4/4.6, the H.1-H.12 SFI annexes, the
Annex L.6 Relay Node/certificate files; Annex A/E as the ADF/DF file
completeness cross-check), TS 31.103 and TS 51.011 - 20 DF tables (~370
entries) and 14 SFI tables, up from ~180 flat names and 3 SFI tables
- names are DF-scoped: a FID reused across DFs resolves to the file its DF
context names - 4F09 is EF_SUPI_NAI under DF_5GS but EF_PROSE_GC under
DF_ProSe, 6F07 is EF_IMSI in the USIM/SIM but EF_IST in the ISIM, 6F4E is
EF_EXT4 in DF_TELECOM but EF_EXT5 in the USIM ADF; the flat table keeps
only names that are identical everywhere and is the fallback when no DF is
known (the two names the old table had wrong there are fixed: 6F0A is
EF_AC_GBAUAPI, 6F53 EF_LOCIGPRS)
- the SELECT-path decoder resolves the 7FFF children against the USIM ADF
table, and the Files view names files DF-first - including files first seen
only via their FCP (a select-only file used to keep the bare FID, e.g.
ADF/6F3C is now ADF/EF_SMS (6F3C))
- tests/data/spec_files.json pins the extracted tables; tests/test_decode.py
asserts the DF/ADF tables cover them, the SFI tables match them exactly and
every flat name is a spec name for that FID; tests/extract_spec_tables.py
regenerates the fixture from the PDFs and --check re-verifies the tables
- README/README_RUS document the dictionary and its sources; PWA_VERSION and
pyproject/version.py 2.5.0, SW cache simtrace-analyser-v71
- the modal header showed only the flat name (EF_ICCID, 4F09); it now shows the
path built from data already available: the tracker's DF identity named via
KNOWN_FIDS (MF, DF_GSM_ACCESS, DF_TELECOM, …), 'ADF' for the current
application (7FFF), DF_CHILDREN for DF-qualified EF names (4F52 →
EF_KcGPRS) and the EF.DIR labels for AID-selected applications —
e.g. `MF/EF_ICCID (2FE2)`, `ADF/EF_IMSI (6F07)`,
`DF_GSM_ACCESS/EF_KcGPRS (4F52)`, `AID A00000015141434C00/4F09`
- exposed as `path` in the change-details payload and in the files entries
(the timeline rows keep the short name — they have no room)
- the colour legend (changed / newly observed / never observed) moved below
the two hex views
- tests: `_file_path` cases (MF, ADF, DF-qualified EF, AID with and without an
EF.DIR label, FID-as-name) plus the payload field, and the Node modal test
asserts the path header and that the legend follows the views
- v2.4.2; SW cache simtrace-analyser-v68
- `loadSession` begins by stopping a running capture; when there is nothing
to stop the server answers 400 'No active capture' (or 403 with capture
disabled) — and since the v2.3.1 `_fetch` status fix those answers throw, so
the whole load was skipped and the UI alerted "Failed to load session":
no session could be opened at all
- new `stopCaptureIfRunning()` swallows exactly that no-op answer; both
`loadSession` and the capture button use it, and the button now resets its
state outside the try so a tolerated stop still clears the UI
- the `_fetch` status check stays — real errors keep surfacing (an unknown
session still throws 'Session not found')
- tests: the helper resolves on a 400 and still calls once; source pins that
`loadSession` uses the tolerant helper (the regression guard) and that
`stopCapture` resets the state
- v2.4.1; SW cache simtrace-analyser-v66
- the timeline's Δ glyph became a **Details** button, and it now appears on
Update rows too (not just card-side changes); EN "Changed (card)" ->
"Changed on card", RU "Изменено (картой)" -> "Изменено картой"
- the details modal shows the affected unit — the whole file, or the single
record that changed — **before and after** the change as hex views (16 bytes
per row, ASCII column, never-observed bytes as `--`), with the bytes that
changed and the bytes the operation *first observed* highlighted in two
different colours, plus the changed/new byte counts and the APDU reference;
the old before/after byte table is gone
- `GET /api/sessions/<id>/files/image?key=…&change=…` returns those two views
(segments with offsets) plus the change's metadata; the images are
reconstructed from the current image by undoing the later changes
(`FileTracker.change_images`), so nothing is duplicated per event — every
value change is an event, hence the values are exact (a byte only first
observed later can appear as known in both views)
- tracker: change events carry `observed_ranges` (bytes that were unknown
before the operation) next to `ranges`; an update that only observes new
bytes now records an event (so it can be inspected); record-file ranges are
in-record byte offsets (the record number stays in its own field), which the
previous "record number as position" could not express for a hex highlight
- tests: change-images reconstruction (before/after per event, later changes
undone, records isolated, observation-only events, unknown gaps), the image
endpoint (200/404/400), and Node tests for the hex-image helper (highlight
classes, unknown gaps, ASCII, row limit) and the modal rendering
- v2.4.0; SW cache simtrace-analyser-v65
- `API._fetch` never checked the HTTP status: an error body (e.g. the 404
`{"error": "Session not found"}`) was returned as data, so the Files view
rendered it as "No file operations recorded" — and, worse, that bad payload
was cached per session id, which is why only re-opening the session (which
resets the cache) appeared to fix it. It now throws with the server's
message for any non-OK response (all 13 `await API.*` call sites are inside
try/catch) and marks the server unavailable only for a real network error
- `renderFileView` accepts a payload only when it carries a `timeline` array:
a genuine `timeline_total === 0` keeps the empty state, anything else shows
"Failed to load files: <reason>"; failures are not cached, so the next visit
retries; a response that arrives after the user switched sessions is ignored
- tests: HTTP-error vs network-error behaviour of `_fetch` (network error is
the only disconnected state), the valid/empty/error render paths and the
retry-after-error, the stale-response guard
- v2.3.1; SW cache simtrace-analyser-v64
- new `simtrace_analyser/filetrack.py`: a single-pass tracker over a
session's decoded messages, keyed by (DF, FID) and trace-wide (resets only
wipe the per-channel selection): counters (`select`/`read`/`update`,
successful operations only), a sparse image of the observed bytes
(transparent files by offset, record files per record number) and the
change log
- changes: **device** = an UPDATE seen in the trace (applied to the image,
`no_change` flags a provable no-op); **card** = a READ whose bytes differ
from the image on bytes already known (logged at the surfacing read with
the changed ranges and the before/after hex; never-seen bytes just fill
the image, they are an observation, not a change)
- SFI-addressed accesses join the same entry as path/FID ones: the SFI ↔ FID
relations come from the normative tables and from every surfaced FCP `88`
DO (including the spec's "SFI = 5 LSBs of the FID" rule when the DO is
absent); an SFI used before its relation is learned stays provisional and
merges into the resolved entry
- the DF identity follows TS 102 221 §8.4/Table 8.2: `selected_df_fid()` now
resolves an EF path selection to its parent DF and returns `aid:<hex>` for
applications selected by an unknown AID (named from EF.DIR when the trace
surfaced it); `Database._decode_rows` gained an `on_message` hook and
`track_files()`
- `GET /api/sessions/<id>/files`: the operation timeline, the per-file
counters/coverage/change log, the SFI relations and the AID labels
- PWA: the side panel's Map/Stats tabs became a view selector (Map / Stats /
Files); the Files view is a timeline of Read / Update / Changed (card) rows
(colour-coded, the same file names as the Map view), a row click jumps to
the APDU (loading the pages it needs) and a card-change row's Δ opens the
changed ranges with the previous and the new bytes; EN/RU
- bounds: the image freezes past 64 KB of observed bytes per file, the change
log keeps the last 1000 events per file (counters keep counting)
- tests: tests/test_filetrack.py (counters, resets, channels, image/diff
semantics, record files, SFI resolution and the provisional merge, EF.DIR
labels, caps, the endpoint) and Node tests for the Files-view helpers and
wiring
- v2.3.0; MIN_SERVER_VERSION 2.3.0; SW cache simtrace-analyser-v63
- new `simtrace_analyser/comprion.py`: parses the tab-separated ISO Protocol
Layer export (metadata block, ATR/PPS exchanges, APDUs with continuation
rows, delays in etu) and re-encodes every exchange into the wire-shaped
TPDUs the analyser stores — a case-4 exchange becomes the command, a
fabricated `61 <len>` status word and a GET RESPONSE (command's logical
channel preserved), so command/response pairing, FCP/file decoding and the
GET RESPONSE filters work unchanged; Cold reset / Power off become RST/VCC
line events, deliberately without the sigrok CLK extension (the export
carries no clock measurement)
- optional matching **ISO Application Layer** export as the exact time base:
its one `Time:` row per exchange (ms precision) replaces the delay estimate
(0-based elapsed, `started = Session Name + first Time`), guarded by a
session-identity check (`Session Name`, falling back to `Working
directory`; the two layers' export timestamps differ and are not compared)
and an exchange-count check — mismatched files are refused, never mixed
- protocol-only imports keep the Delay-based timeline: card/line activity
time (host pauses absent), the unit assuming 3.8 MHz (`CLK_HZ`); the
estimate is not fed into the CLK/RST line-event annotation
- `POST /api/sessions/import?format=comprion` (raw text body, or a JSON
`{text, times_text}` bundle) with stable error codes (app_layer /
not_comprion / unsupported_layout / no_data / times_unparsable /
session_mismatch / time_base_unverified / time_base_mismatch); a COMPRION
file dropped on the PCAP path answers with a pointer (comprion_export);
session mode `comprion`, window from the Session Name
- PWA: import dialog with two separate pickers (Protocol Layer required,
Application Layer optional) whose hint switches between "activity time"
and "exact timestamps"; errors stay in the dialog with both selections
until fixed or cancelled (Cancel/Esc, also disabled while importing);
EN/RU
- tests: tests/test_comprion.py (parser, wire encoding, time base + guards,
import endpoint) and frontend/tests/comprion.test.js (file checks, slot
validation, dialog state machine with a DOM stub, RU coverage)
- v2.2.0; MIN_SERVER_VERSION 2.2.0; SW cache simtrace-analyser-v61
- tag '18' (TS 101 220: "18 only"): one byte per item, §9.4 coding;
'00' and reserved values mean "no next action" (decoded to None)
- items[] now carry a per-item next_action; a list whose length differs
from the item count is preserved raw (spec requires X = item count)
- detail view renders "Item 0xNN: text → COMMAND"; the APDU-list summary
is unchanged — next actions are detail-only, as requested
- tests: 3 real SET UP MENU samples (local sessions 47/53/57) + a
length-mismatch tolerance case
- v2.1.2; SW cache simtrace-analyser-v57
- REFRESH '07'/'08': use the TS 31.111 §8.6 names ("Steering of Roaming" /
"... for I-WLAN", per TS 23.122 / TS 24.234) instead of "Reserved (...)"
- envelope tags 'E1'-'E3': TS 31.111 §9.1 leaves them reserved for future
usage — drop the placeholder label and decode them as unknown
- TR result '28' = Bearer Independent Protocol temporary error (TS 102 223
§8.12); '29' stays reserved
- PLI qualifier '0B' = MEID of the terminal added; '05' documented as
TS 31.111's Timing Advance (ETSI text says "Reserved for GSM")
- ISO 7816-4:2005 Table 6: add '62 86' (sensor) and '63 81' (file filled)
- drop the two fabricated '66xx' names (no such rows in the pinned specs)
- tests: add the new value tests plus a guard that no decoder table emits
a "Reserved ..." label
- v2.1.1; SW cache simtrace-analyser-v56
- terminal power supply (tag 80): voltage class per Table 6.1, maximum
current in mA, clock in 0.1 MHz steps ('FF' = not indicated)
- extended logical channels (tag 81) — zero length per §11.1.19.2.2, a
nonzero length is preserved as raw
- additional interfaces (tag 82 b1 = UICC-CLF)
- eUICC device capabilities: tag 83 per GSMA SGP.22 v2.6 §3.4.2 Table 7
(LUId/LPDd/LDSd/LUIe-SCWS); tag 84 preserved raw (no pinned SGP.32 spec)
- unknown/private TLVs preserved via raw_tlv
- concise APDU-list summary; decoded rows in the PWA detail view
- v2.1.0; SW cache simtrace-analyser-v55
- tests: 6 real samples from the local trace sessions (49/55/58/61/206/208)
- move the analysis server out of simtrace2_pysniff into simtrace_analyser;
simtrace2_pysniff stays as the simtrace2-sniff replacement capture utility
- new server CLI: simtrace-analyser-server; python -m simtrace_analyser
(simtrace2-pysniff-server and python -m simtrace2_pysniff.server dropped)
- simtrace2_pysniff lazy-imports hardware symbols, so the server no longer
pulls PyUSB at startup; the GSMTAP/PCAP codecs work without libusb
- version 2.0.0 everywhere (was 1.22.21); PWA SW cache simtrace-analyser-v54
- frontend: server name, clone URL and i18n strings updated; vendored-parser
reference otaman -> SIMple
- README/README_RUS reworked product-first (analyser first, utility second)
- DB dir ~/.simtrace-analyser, /api status payload and hosted PWA unchanged
- decode.py: _build_summary emits 'Data: N bytes — FCP of <file> (...)' for
STATUS responses with data (FCP already decoded into decoded.response);
_fcp_brief builds the 'FCP of MF (DF or ADF)' / 'FCP of EF_xxx (...)'
fragment
- index.html: cmd_description suppresses the raw-hex fallback for
body-less STATUS rows (nothing to describe beyond type + SW)
- tests: no-data STATUS summary None; DF/EF FCP summaries; pluralization
- Bump 1.22.19, SW cache v50->v51
- capture.py: map GSMTAP PPS request/response (0x02/0x03) to 'pps',
matching the pcap-import mapping (was decoded as TPDU live)
- decode.py: parse the optional RST CLK extension (sigrok v1.9.0+,
payload flag bit 0); ATR gains clk_hz/data_rate/etu_us derived from
TA1 Fi/Di (data_rate = clk_hz × D / F)
- database.py: CLK state is consumed by the next ATR (no stale rate
for an ATR without preceding RST); a late-arriving RST measurement
is backfilled onto the ATR it belongs to; replay context carries
the CLK across pagination/live polling
- PWA: ATR detail shows CLK/Data rate/ETU; RST event detail shows CLK
- tests: PPS mapping, CLK parse with/without flag, rate math,
DB consume + backfill round-trips
- README/RUS updated
- Bump 1.22.18, SW cache v49->v50
- Store raw session name in STATE.currentSessionRawName
- Re-render session-name-display in toggleLang() with current translation
- Add frontend.png screenshot to README
- Bump 1.22.15, SW cache v46->v47
- Insert .splitter div between .apdu-list and .apdu-detail
- Replace flex:1 / max-height:40% with --detail-height CSS var
- Drag handler with clamp 10%-80%, persisted in localStorage
- Bump 1.22.12, SW cache v43->v44
- Toggle buttons (Tiles/List) in sessions header, preference
persisted in localStorage
- List view: table with Name, Date, Duration, Messages, Mode, Actions
columns; same fields and buttons as tiles, just tabular layout
- Refactored loadSessions() into buildGridHTML/buildListHTML helpers
with shared attachSessionListeners() for both views
- i18n: Tiles, List, Name, Date, Duration, Messages, Mode, Actions
keys added to LANG_RU
- Bump 1.22.6, SW cache v37->v38
- error_names(m) now includes 'Data length mismatch' when
decoded.length_mismatch is set, showing a ⚠ badge + tooltip
in the APDU list row (alongside existing errors like bad FCS)
- Header title: SIM-ME -> UICC
- Bump 1.22.5, SW cache v36->v37
- capture-btn moved to first child of trace-topbar (before search input)
- Button always visible: shows Start/Stop/Connect-to-server as before
- When server capture is disabled: grayed-out Start Capture with
tooltip via existing i18n key 'Capture is disabled ...'
- No more hiding button when viewing saved session or when disabled
- Bump 1.22.4, SW cache v35->v36
T=15 in the ATR is a Protocol-and-Parameter-Selection capability,
not a data protocol like T=0/T=1. Previously 'Protocols: T=0, T=15'
was misleading.
- _decode_atr: exclude T=15 from protocols, set pps=True instead
- TCK presence: only-t0 rule now also requires no PPS (T=15 implies TCK)
- PWA ATR detail: 'Protocol: T=0' + 'PPS: supported' rows
- +1 test with the real UICC ATR (3B 9F 95 80 1F C7 ...)
- Bump 1.22.2, SW cache v33->v34
sigrok-iso7816-stream sets GSMTAP_FLAG_BAD_FCS (0x01) in the GSMTAP
header `res` byte on desynced TPDUs (mis-framed exchanges kept for
forensics). Propagate that flag through the whole pipeline so such
packets are explicitly surfaced instead of decoding as garbage:
- gsmtap.py: GSMTAP_FLAG_BAD_FCS const; GsmtapReceiver.read_packet()
now returns (sub_type, data, flags) from the res byte.
- server/capture.py: GsmtapListener forwards the flags to the DB.
- pcap.py: _parse_gsmtap_hdr/_scan_gsmtap/extract_gsmtap and
parse_pcap/parse_pcapng now yield a (ts, type, payload, flags)
tuple, so the flag survives a PCAP/PCAPNG round-trip.
- server/server.py: PCAP import stores the flags.
- server/decode.py: gsmtap_flag_names() maps bit 0x01 to
'bad FCS (desynced)'; merged into decoded errors for tpdu/atr/pps
and rst/vcc, so the frontend ⚠ badge + Errors row flag the entry.
User-visible: desynced TPDUs show a ⚠ "bad FCS (desynced)" marker
in the trace and detail views. Tests: +4 (decode BAD_FCS markers,
PCAP flag round-trip); existing tests updated for the new tuple
arities. Version 1.22.0, SW cache simtrace-analyser-v32.
Support the sigrok-iso7816-stream decoder (FX2 logic-analyzer passive
SIM sniffer, github.com/anttro/sigrok_iso7816_stream) which streams
GSMTAP-SIM on UDP 4729 and adds two custom sub-types for line events.
- gsmtap.py: GSMTAP_SIM_RST_EVENT (0x10) / GSMTAP_SIM_VCC_EVENT (0x11).
- capture.py: gsmtap_msg_type() maps 0x10→'rst', 0x11→'vcc' (shared by
the listener and tests); pcap.py _SUB_TYPE_NAMES gains the same
entries so PCAP re-imports of these captures preserve the events.
- decode.py: decode_line_event() decodes [direction, level, reserved]
into reset asserted/de-asserted or power applied/removed + level;
routed through decode_sniff_msg.
- database.py: reset-assert and power-removal invalidate the per-channel
file selection (card-level transition); de-assert / power-on are
markers only.
- frontend: RST/VCC labels (RESET ASSERTED / VCC OFF, …), a detail-view
branch (Event + Level + raw), and list styling (.apdu-type.rst/.vcc).
README notes the custom sub-type compatibility. Tests: +8 (line-event
vectors, sniff routing, subtype + PCAP round-trip mappings) — 359
total. Version 1.21.0, SW cache simtrace-analyser-v31.
- Pair CLA+INS and P1+P2 into single rows (halves the header rows
per C-APDU): "· CLA, INS 00 — UICC, A4 — SELECT".
- Skip the " — desc" suffix when the parser's desc merely repeats
the hex (generic Data fields showed "6F46 — 6F46").
- Restore the implied-CLA annotation on the C-APDU title row (was
dead code behind an operator-precedence bug).
Version 1.20.2, SW cache simtrace-analyser-v30.
The secured data of an SCP80 packet is a stream of C-APDUs (e.g.
SELECT / UPDATE BINARY sequences for OTA file management). Vendor the
working C-APDU parser from the otaman project (frontend-only, ~10 KB):
PARSE_INS table, describeP1P2, describeInstallDataLv, parseOneApdu and
parseCompactApdus (concatenated streams with implied-CLA handling).
Detail view now shows an "Application message" section for secured
packets:
- unciphered packets (SPI1.b3=0): the on-wire secured data decodes
immediately;
- ciphered packets: decodes from the client-side decrypted payload
once the user supplies the key (before that, ciphertext rows only);
- each C-APDU renders as a numbered row with indented CLA/INS/P1/P2/
Lc/Data/Le + descriptions (nested LV for INSTALL), with implied-CLA
annotation; raw-hex fallback when nothing parses;
- vendored parser uppercases input (otaman's expects uppercase hex).
Tests deferred to the follow-up step (node:test infra, planned).
Version 1.20.0, SW cache simtrace-analyser-v28.
Adds optional decryption of ciphered secured packets, entirely in the
browser — the key never leaves the frontend.
- decode.py: _decode_secured_packet exposes 'cipher_block' (the
contiguous on-wire ciphered octets CNTR..end, ready for CBC) and
raw kic/kid bytes for algorithm selection. Additive only.
- index.html: pure-JS DES/3DES core (FIPS 46-3 bit-array tables,
parity ignored) + CBC; AES-CBC via native SubtleCrypto; both with
zero IV per TS 102 225. decryptSecured() picks the algorithm from
the KIc nibble (02 AES-CBC, 05 3DES-2K, 09 3DES-3K, 01 DES) with
key-length validation.
- UI: ciphered packets show a key-hex input + Decrypt strip below the
detail table; keys are memoized per key-set for the session in an
in-memory OTA_KEYS map — never sent to the server, never persisted.
- Decrypted results cached by cipher_block (survive live-polling
re-renders); plaintext rows rendered as CNTR/PCNTR/RC/Secured data
(decrypted) alongside the ciphertext; wrong-key guard via the
decrypted padding counter (PCNTR > remaining → "invalid padding").
- 3DES implementation validated byte-identical against openssl
des-ede3-cbc (3-key, 2-key ×3 random vectors, single-key).
No CC verification (out of scope). Tests: +2 (cipher_block
contiguity, AES registry guard) — 350 total. Version 1.19.0,
SW cache simtrace-analyser-v27.
User-reported: the decoded CNTR (0x1830A44E41 ≈ 104 billion) cannot
be the real counter — the OTA platform shows ~200 for that card.
Root cause: per TS 102 225 Table 2 note 1, when SPI indicates
ciphering the whole block from CNTR through the secured data
(CNTR, PCNTR, RC/CC/DS, secured data) is ENCRYPTED. Only CPI, CPL,
CHI/CHL, SPI, KIc, KID and TAR travel in the clear. Our decoder
parsed the ciphertext bytes as if they were the real CNTR/PCNTR/RC
and presented them as such — misleading (the true values are only
recoverable after 3DES decryption with the card's keys).
Fix:
- _decode_secured_packet: emit 'ciphered': True when SPI1.b3 is set
(wire hex values kept for reference); docstring documents the
ciphered block.
- _decode_response_packet: accepts ciphered (command SPI2.b5, PoR
ciphering) and flags the response likewise; database context now
carries por_ciphered from the ENVELOPE so the GET RESPONSE PoR
inherits it. Standalone SMS-SUBMIT PoRs carry no SPI — state
unknowable, defaults to False (documented).
- Frontend: CNTR/PCNTR/RC-CC-DS labels get "(encrypted)" when the
respective packet is ciphered (command, response, and ENVELOPE
response renderings).
Tests: +4 TestSecuredCiphering (ciphered flagged, unciphered plain
CNTR e.g. 200, response flag, PoR-via-context). 348 total. Version
1.18.0, SW cache simtrace-analyser-v26.
stk_test1 msg 14036 regression: READ BINARY of EF_SPDI returned
"No decoded data available" — _decode_spdi (added v1.16.0) parsed
the A3 container value as bare 3-byte records (missing the inner
'80' value TLVs) and returned a list of strings, while
_build_summary/_file_summary expects the EF_PLMNwAcT shape
(list of {mcc, mnc} dicts). The resulting TypeError aborted the
whole message decode — this was the source of the 28 "Decode
error ... string indices" messages seen across all sessions.
_decode_spdi now unwraps inner '80' TLVs (bare-records fallback
kept) and always returns {'plmns': [{mcc, mnc}...]}. Replay of all
33 sessions: decode exceptions 28 → 0 (2 remaining undecoded tpdu
are 1-byte truncated capture records, not decodable by nature).
Tests: +3 (standard inner-80 form, bare-records fallback, exact
stk_test1 regression via decode_message). 344 total. Version
1.17.1, SW cache simtrace-analyser-v25.
Next to the relative elapsed time, the detail-view header now shows
the absolute local time (YYYY-MM-DD HH:MM:SS.mmm) computed as
session start + message elapsed offset.
- STATE.sessionStarted populated from /api/capture/start (live
capture), data.session.started (session view), and PCAP imports
(sessions.started already derives from the first packet timestamp
when real).
- Graceful fallback to relative-only when the session start is
unknown; applies to all detail header variants (gap, no-decoded,
state change, Fi/Di, ATR/PPS, APDU).
Version 1.17.0, SW cache simtrace-analyser-v24.
mobileid_activate_setpin_resetpin_20201222 regression: UPDATE BINARY
(plain offset addressing) after an SMS-PP DOWNLOAD / proactive group
showed "Writes: —" in the map view, although selection tracking kept
EF_SMSS selected the whole time.
Root cause: the sel-based branch only emitted result['file'] when
_decode_file_data() produced content — files without a registered
content decoder (EF_SMSS 6F43) silently lost attribution. The first
write only displayed via the frontend's SELECT-note fallback, which
masked the bug until a proactive group split the RW run.
Fix: decouple attribution from content decoding in both the command
branch and the GET RESPONSE branch — when the selected file has no
content decoder (or the offset is non-zero, where content decode is
intentionally skipped), emit {fid, ef} so the map, list, and detail
views always name the file. Content decoders still take priority.
Session 63 now decodes all three UPDATE BINARY ops as ADF/EF_SMSS.
Tests: +4 TestFileAttribution, one expectation updated to the new
contract — 342 total. Version 1.16.1, SW cache v23.
Spec verification against UICC_SPECS.md / TS 102 223 V18.3.0 §8.6
showed the Command Qualifier for SET UP IDLE MODE TEXT is RFU
("this byte is RFU") — no icon self-explanatory bit exists in any
generation (GSM 11.14 predates the command; TS 31.111 does not
override it). The v1.11.0 _idle_text_qualifier decoded bit 1 as
"icon self-explanatory" from an unverified source.
Remove the decoder; the command now uses generic RFU handling
(hidden at '00', raw hex otherwise). Tests updated accordingly.
Version 1.15.1, SW cache v21.
- Pair members (a command ending '61 XX' and its GET RESPONSE, via the
server-side response_for link) get a subtle slate row tint
(--pair-row, themed: rgba(148,163,184,.12) dark / rgba(100,116,139,.15)
light); hover/selected still take precedence.
- Paired GET RESPONSE rows are prefixed with '↳' marking them as a
continuation of the previous command; orphaned GRs stay plain.
- GET RESPONSE label uses cat-gr class: muted color, weight 600 like
other command names.
- Map view untouched (it skips GRs); detail view already shows
"Response for".
Version 1.15.0, SW cache simtrace-analyser-v20.
Phase 3 of the spec audit: TS 102 223 §8.6 qualifier coverage for every
command that defines a coding (verified against V18.3.0 text):
- Value tables: SET UP CALL ('00-'05), LAUNCH BROWSER, GET READER
STATUS, SET FRAMES, ENCAPSULATED SESSION CONTROL ('00-'04), LSI
COMMAND ('00/'01).
- Bitmask decoders (deviations from default shown; all-default hidden;
pure-RFU falls back to raw hex): SEND SHORT MESSAGE packing bit,
PLAY TONE vibrate, DISPLAY TEXT / DISPLAY MULTIMEDIA MESSAGE
priority + clear-delay, GET INKEY (alphabet/UCS2/Yes-No/immediate-
digit/help), GET INPUT (alphabet/UCS2/hidden/packed/help),
SELECT ITEM (presentation type/soft key/help), SET UP MENU,
TIMER MANAGEMENT (start/deactivate/get value), LANGUAGE NOTIFICATION,
OPEN CHANNEL (link establishment/reconnection/background/DNS with
bearer-dependent note), CLOSE CHANNEL (NAA reuse note), SEND DATA,
DECLARE SERVICE.
- RFU-byte commands (MORE TIME, POLL INTERVAL, SEND SS/USSD, ACTIVATE…)
keep raw-hex fallback.
Tests: +11 TestQualifiers cases — 317 total. Version 1.14.0, SW cache
simtrace-analyser-v17.