- 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.
TS 102 223 §6.6.13 support for the SET UP IDLE MODE TEXT (0x28)
proactive command:
- Command qualifier: COMMAND_QUALIFIERS entries may now be callables
for bitmask-style qualifiers; _idle_text_qualifier decodes bit 1
as "icon self-explanatory", hides 0x00, falls back to raw hex on
RFU bits.
- Icon Identifier TLV (0x1E) parsed into icon_id (image record
number in EF_IMG, 0 = none); rendered as an "Icon" detail row in
the PWA.
- Zero-length Text String TLV now yields text='' ("remove idle mode
text") instead of being silently skipped.
Tests: +6 TestIdleModeText cases (8-bit/UCS-2 text, icon TLV,
qualifier variants, empty text) — 285 total. SW cache bumped to
simtrace-analyser-v14.
- Add sim-analyser-light.svg (#2563eb, light-theme accent); dark
keeps the existing #3b82f6 variant.
- applyTheme() now swaps the favicon href alongside data-theme and
theme-color, so the icon follows toggle click, saved theme on
load, and OS-preference fallback (all route through applyTheme).
- Precache the new asset in the service worker.
The fetch handler tried to cache every intercepted request, so a
chrome-extension:// request made cache.put() throw 'Request scheme
chrome-extension is unsupported'. Skip non-http(s) and non-GET requests,
and swallow cache-write failures.