Commit Graph
100 Commits
Author SHA1 Message Date
catarrh ed7e99d671 Rename server-start.* to start.*, add sniff-start.bat
- git mv server-start.sh -> start.sh, server-start.bat -> start.bat
- Add Windows sniff-start.bat mirroring sniff-start.sh options
- Update usage comments and PWA help text references
- Bump 1.22.17, SW cache v48->v49
2026-09-11 08:02:15 +03:00
catarrh c7b87cb587 Widen APDU list vertical scrollbar to 12px
- .apdu-list::-webkit-scrollbar override (global default is 6px)
- Bump 1.22.16, SW cache v47->v48
2026-09-10 00:01:41 +03:00
catarrh ede38c985e Fix session name label language switch + add frontend screenshot
- 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
2026-09-06 22:05:25 +03:00
catarrh af16eb1dd8 Add grip dots visual indicator to splitter bar
- ::after pseudo-element with radial-gradient dot pattern
- Dots darken on hover/active for contrast against accent background
- Bump 1.22.14, SW cache v45->v46
2026-09-06 21:52:13 +03:00
catarrh 347ff5387b Clear detail view on capture start and session load
- Reset apdu-detail to empty state in startCapture() and loadSession()
- Bump 1.22.13, SW cache v44->v45
2026-09-06 21:44:02 +03:00
catarrh 52243f496c Add draggable splitter between APDU list and detail view
- 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
2026-09-06 21:36:33 +03:00
catarrh 7d8dca1ffd Fix GSMTAP capture restart: recreate UDP socket on start after stop
GsmtapListener.stop() closed the UDP socket permanently, but start()
only set _running=True without recreating it. Second capture session
would silently spin on the dead socket (OSError → None → continue).

- Add _closed flag to GsmtapReceiver, set in close()
- Store bind_port in GsmtapListener for socket recreation
- start() recreates GsmtapReceiver only if previous one was closed
- Uses getattr for backward compat with test fakes
2026-09-06 21:09:04 +03:00
catarrh 78c255464e Fix list view row border visibility in light theme + vertical-align buttons
- Change row border from var(--bg-input) to var(--border)
- Add vertical-align: middle to td cells
- Bump 1.22.11, SW cache v42->v43
2026-09-06 14:53:21 +03:00
catarrh 3dc59539d4 Fix list view name input dark mode + widen Name column
- Add .sessions-list input[type=text] with themed background/color/border
- Set Name column min-width 200px, input width 100%
- Bump 1.22.10, SW cache v41->v42
2026-09-06 14:44:27 +03:00
catarrh 2f49ee5f0f Show file size from FCI tag 0x80 in SELECT response detail view
- Bump 1.22.9, SW cache v40->v41
2026-09-06 14:14:29 +03:00
catarrh 340017c7cc Unify session action button styles: same padding, delete in red
- Add .session-copy CSS (was unstyled — browser default)
- .session-download/.session-copy: neutral gray, same padding 4px 12px
- .session-delete: red (--danger) instead of accent
- Bump 1.22.8, SW cache v39->v40
2026-09-06 14:04:54 +03:00
catarrh f9e399183c Fix imported PCAP session duration for relative timestamps
SIGROK-derived PCAPs have relative timestamps (0, 1.37, 15.02) rather
than epoch time. The _REAL_TS_MIN check failed, so close_session() set
ended to wall-clock (same as started → duration 0s). Now close_session
accepts an optional duration_secs param; the import handler passes
last_ts - first_ts so the session gets the correct capture duration.
2026-09-06 13:01:46 +03:00
catarrh 05d7a26f2e Reduce vertical padding for session list view rows (8px->4px)
- Bump 1.22.7, SW cache v38->v39
2026-09-06 12:08:46 +03:00
catarrh 1502ff80e3 Add list/table view for sessions panel alongside tiles view
- 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
2026-09-06 11:57:23 +03:00
catarrh 7e537abc55 Warn on data length mismatch in APDU list; rename SIM-ME to UICC
- 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
2026-09-06 11:04:38 +03:00
catarrh 0ec7db8d34 Move capture button left of search; always visible; disabled when server-disabled
- 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
2026-09-06 10:45:11 +03:00
catarrh 0fac73833d Log session open (load saved session for viewing)
- _handle_get_session now logs: Session opened: session=1
  name=my capture messages=1200
2026-09-05 23:47:25 +03:00
catarrh 811a6a1ed7 Make Stop Capture button red and prominent
- Add --danger/--danger-hover CSS variables (dark + light themes)
- .stop button now uses red background + white text with brighter hover
  (was muted gray, easy to miss)
- Bump 1.22.3, SW cache v34->v35
2026-09-05 23:41:49 +03:00
catarrh 70d288fd0f Log sessions by default, HTTP requests opt-in via --log-requests
- HTTP request logging now off by default (log_message guarded by
  RequestHandler.log_requests); --log-requests enables it
- Add session lifecycle logging: POST /api/sessions/import and
  DELETE /api/sessions/<id> emit session+name+messages/bytes or
  session= lines via the shared _log() helper
- Capture start/stop and periodic status keep logging as before
2026-09-05 23:36:14 +03:00
catarrh 6bdbbea209 Show T=15 as PPS capability, not a transport protocol
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
2026-09-05 23:20:16 +03:00
catarrh 674a979645 Enrich per-event RST/VCC labels (ATR STARTS / power-up) + decode server-side
- decode_line_event adds human label: RST de-asserted->ATR STARTS,
  VCC applied->VCC ON (power-up); keep canonical event string
- Enable rst/vcc in DB decode dispatch so PWA receives decoded
  (fixes bare RESET/VCC fallback and a latent showDetail crash)
- PWA line_event_label/showDetail render decoded.label
- Bump 1.22.1, SW cache v32->v33; +1 test
2026-08-27 00:07:35 +03:00
catarrh e4ac80fb34 Decode EF_IMPU/IMPI/DOMAIN empty records as empty, not text
_decode_nai (decode.py) only treated an all-0xFF record as empty.
A record that is a lone identity tag, or a tag with a zero-length
value (e.g. 80 00 + FF erase filler — a standard unused EF_IMPU
record) slipped through to an ascii fallback and rendered as garbage
text (the "Text: �" artifact).

- Treat a lone tag (80 / 81) or tag+zero-length-value (80 00 / 81 00)
  as empty.
- Only decode the TLV *value* bytes (UTF-8); never feed the TLV header
  or trailing FF filler to the text decoder.  Drop the fragile
  raw.rstrip(...).decode('ascii','replace') fallback.

Tests: +4 (empty 80 00+FF record, all-FF, real SIP URI, lone tag).
Backend-only; stays on 1.22.0.
2026-08-26 23:23:56 +03:00
catarrh 6887e53144 Add --log-interval flag and timestamp capture logs
Follow-ups from the capture-reliability review:
- All server-side capture log lines are now prefixed with a local
  timestamp (YYYY-MM-DD HH:MM:SS) for easier correlation.
- New --log-interval SECONDS CLI flag (default 60) tunes the
  "Capture alive" heartbeat interval; passed through to CaptureManager.

Stays on 1.22.0 (backend-only). 369 tests pass.
2026-08-26 22:35:59 +03:00
catarrh 51e1b4ae08 Keep GSMTAP capture alive + server-side liveness logging
The GSMTAP capture thread died silently whenever a single non-SIM or
truncated GSMTAP datagram arrived (read_packet() raises ValueError,
uncaught in the loop, killing the daemon thread while UDP kept
flowing) — matching the "stops listening after a sniffer restart"
symptom.

Fixes:
- GsmtapListener.iter_messages() now catches ValueError/struct.error
  from read_packet(), drops the offending datagram (counted via a new
  `dropped` counter) and keeps listening.
- CaptureManager._capture_loop() guards the DB insert so a transient
  error can't kill the thread; a top-level guard logs
  "ERROR: capture thread stopped on error …" + traceback and clears
  the session so /api/status no longer reports a stuck "active".
- Server now logs capture liveness to stderr: "Capture started", a
  periodic "Capture alive: session=N messages=M bytes=B dropped=D"
  heartbeat (default every 60 s, configurable via log_interval), and
  "Capture stopped" (or "empty session discarded").

Tests: +5 (listener survives non-SIM/short packet, capture loop
survives insert error, logs stop-on-error, heartbeat+stop log).
Stays on 1.22.0 (backend-only).
2026-08-26 22:21:14 +03:00
catarrh 79acb3773f Address code-review nits for BAD_FCS support
- decode.py: use the GSMTAP_FLAG_BAD_FCS constant instead of a literal
  0x01 in gsmtap_flag_names() (import from ..gsmtap); no behavior change.
- tests: add a DB round-trip test (insert_message with BAD_FCS flag →
  get_messages → decoded.errors) covering the end-to-end path, not just
  the direct decode_sniff_msg call.
2026-08-26 22:11:29 +03:00
catarrh 66094a0114 Surface GSMTAP BAD_FCS desync flag end-to-end
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.
2026-08-26 18:08:25 +03:00
catarrh 7eaf7e9b53 Decode sigrok-iso7816-stream RST/VCC line events (GSMTAP 0x10/0x11)
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.
2026-08-26 17:29:36 +03:00
catarrh 3e21d093d9 Compact C-APDU detail rows; drop duplicated Data hex
- 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.
2026-08-24 23:05:58 +03:00
catarrh acbd2bda4a README: document C-APDU decoding of secured-packet payloads 2026-08-24 22:55:06 +03:00
catarrh 6d7e87b52a Add node:test suite for vendored C-APDU parser and SCP80 decryption
frontend/tests/ (otaman-style function extraction from index.html):
- apdu_parse.test.js: PARSE_INS registry, parseOneApdu (SELECT by FID,
  READ BINARY Le-form, UPDATE BINARY, GET DATA, lowercase input),
  parseCompactApdus (concatenated implied-CLA stream, graceful
  unrecognized-byte degradation).
- scp80_decrypt.test.js: decryptSecured known-answer tests with
  node-crypto generated ciphertexts — 3DES-CBC 2/3-key (real CNTR=200
  revealed), AES-CBC 128, single DES, wrong-key padding guard, key
  length validation, unsupported-algorithm rejection.
- helpers.js: brace-matched function/const/section extraction from
  the single-file frontend; package.json ("test": "node --test").

Fix AES-CBC decryption path found by the new tests: SubtleCrypto CBC
decrypt always validates PKCS#7, which SCP80 PCNTR-style padding
violates. Workaround: append a synthetic block C* = E(C_last XOR
padBlock) so the validator sees a full 0x10 padding block, decrypt,
then discard the synthetic plaintext. Validated: CNTR=200 case
decrypts correctly.

Version 1.20.1, SW cache simtrace-analyser-v29.
2026-08-24 22:34:23 +03:00
catarrh 6da5acca21 Decode C-APDU commands inside secured-packet payloads (frontend)
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.
2026-08-24 21:55:15 +03:00
catarrh 6f058392f8 README: document client-side secured-packet decryption 2026-08-24 21:23:05 +03:00
catarrh d21997764f Client-side SCP80 decryption: pure-JS 3DES/AES with user-supplied keys
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.
2026-08-24 21:12:24 +03:00
catarrh 840e233baf Flag ciphered SCP80 fields: on-wire CNTR/PCNTR/RC are ciphertext
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.
2026-08-24 19:51:40 +03:00
catarrh 14445ae38f Fix EF_SPDI decode crash: parse inner '80' TLVs, emit dict plmns
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.
2026-08-24 13:03:31 +03:00
catarrh 07deab9c26 Show absolute wall-clock time in the APDU detail heading
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.
2026-08-24 08:53:27 +03:00
catarrh 4541cc66c5 Attribute non-SFI READ/UPDATE to the selected file even without a content decoder
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.
2026-08-23 22:39:25 +03:00
catarrh ac7519499a Audit fixes: FDB structure coding, FCP tags, ECC; new file decoders
Full APDU + file-data decoder audit against UICC_SPECS.md /
UICC_FILES.md (deep-verified against ISO 7816-4 Table 14 and
TS 102 221 V18.4 Tables 11.4/11.5, §11.1.1.4.8):

- EF structure per TS 221 T11.5 (overrides ISO): 100 = cyclic,
  110 = BER-TLV (was: 110 mislabeled cyclic, BER-TLV missing);
  comment documents the ISO divergence.
- FCP tag 'AB' = security attributes expanded format (was wrongly
  "short_ef_id"); add tags '86'/'87'/'8B'/'8C' (security attrs,
  extending EF id).
- EF_ECC: BCD digits(2) + ESC code(1) per record (was Annex-A text).
- APDU_SPEC +11: WRITE BINARY D0/D1, SEARCH BINARY A0/A1, READ
  RECORD odd B3, ENVELOPE odd C3, EXCHANGE CAPABILITIES 7A, RESIZE
  FILE D4 (TS 102 222), ERASE BINARY odd 0F, PSO 2A, GAKP 46.
- New file decoders per UICC_FILES.md: EF_Kc/KcGPRS (6F20/6F52,
  Kc + KSI nibble), EF_OPL (6FC6, PLMN + LAC range + PNN rec),
  EXT1-4/BDN-EXT (6F4A/B/C/E/55, 13-byte records), EF_AD (6FAD,
  op mode + MNC length), EF_ACL (6F57, 'DD' APN TLVs after count
  byte), EF_SMSP (6F42, alpha + PI-gated TP-DA/SCA/PID/DCS/VP),
  EF_SPDI (6FCD, 'A3' PLMN container); EF_CBMIR now decodes all
  4-byte ranges (was first only).

Audit false-positive noted: original file-type bit extraction
(b>>3, 1-based b6-b4) was correct — retained with clarifying
comment. Tests: +18 audit fixtures — 338 total. Version 1.16.0,
SW cache simtrace-analyser-v22.
2026-08-23 19:39:20 +03:00
catarrh bf83074de8 Drop fabricated SET UP IDLE MODE TEXT qualifier decode; bump to 1.15.1
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.
2026-08-23 15:16:40 +03:00
catarrh f61ca9685c Style command + GET RESPONSE pairs in the APDU list; bump to 1.15.0
- 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.
2026-08-23 14:54:18 +03:00
catarrh 0e99e73fda Fix review findings: timer value nibble order, TIMER MGMT qualifier bits
Self-review of 174df8a/d0f8fb8 found two real bugs plus smaller items:

- _decode_timer_value read semi-octets high-nibble first; TS 23.040
  coding (referenced by §8.38) puts the first digit in the LOW nibble
  — "09:25:03" decoded as "90:25:03". Regression test added with an
  asymmetric vector; the original symmetric vectors masked the bug.
- TIMER MANAGEMENT qualifier used bits b2-b3 instead of the spec's
  bits 1-2 (q & 0x03): '01' deactivate was hidden, '02' printed
  "deactivate timer", RFU '04' printed "get current timer value".
  Decoder fixed and wrong expectations corrected.
- Stale text_note: a compliant 8D Text String arriving after the
  non-standard '10' quirk TLV now clears the note.
- Proprietary type format unified to uppercase ("Proprietary (0xF2)").
- GET INKEY: Yes/No bit now suppresses the b1/b2 character-set parts,
  matching §8.6 ("character sets ... are disabled").
- Frontend renders string timer_id values verbatim instead of NaN.

Tests: +4 regressions, expectations corrected — 320 total.
2026-08-23 09:38:05 +03:00
catarrh d0f8fb8f36 Decode command qualifiers for all CAT/SAT/USAT commands; bump to 1.14.0
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.
2026-08-22 23:39:41 +03:00
catarrh 174df8a8ad Fix CAT registries per TS 102 223 V18.3/TS 31.111 V18.12; decode all ENVELOPE types
Spec audit found three registries drifted from an old release, plus
missing command/envelope coverage:

- EVENT_TYPES: correct from '14' up — Access Technology Change
  (multiple), IMS Registration/Incoming data, Profile Container ('19),
  Secured Profile Container, Poll Interval Negotiation, Data Connection
  Status Change, CAG cell selection, Slices Status Change ('1F);
  '20-'22 reserved. Old table wrongly showed LTE D2D/ECall/SCEF names.
- TR_RESULTS: session/user results live at '10-'14 ('14 = USSD/SS
  transaction terminated), not '0A-'0E; single '20 = ME currently
  unable (per-cause details are additional-info bytes); add '22-'27,
  '3A BIP error; unshift '3B-'3D; '39 permanent-problem wording.
- ENVELOPE_TYPES: add D9 USSD Download, DA/DB MMS, DC Terminal
  Applications, DD Geo Location Reporting, DE Envelope Container,
  DF ProSe Report, E0 5G ProSe Report, E1-E3 reserved, E4 GSMA.
- CAT_COMMAND_TYPES: add '79 LSI COMMAND; F0-FE render as Proprietary.
- PLI_QUALIFIERS: add '07 ESN, '1A Supported RATs.

Phase 2:
- _decode_envelope: MENU SELECTION (item id + help request), TIMER
  EXPIRATION (id + semi-octet h/m:s value), CALL CONTROL (address +
  CCP), USSD DOWNLOAD (DCS text), GEO LOCATION REPORTING, ENVELOPE
  CONTAINER (recursive inner dispatch); unknown inner TLVs preserved
  in raw_tlv for every type.
- Device Identities now decode to names (Keypad/Display/UICC/
  Terminal/Network/Card reader n/Channel n/eCAT client) in proactive
  commands and envelopes; frontend renders "src → dst".

Tags verified against ETSI TS 101 220 registry (help request '15/'95,
timer id/value '24/'A4, USSD string '0A/'8A). Tests: +16 spec-derived
fixtures, one stale expectation corrected — 306 total. Version 1.13.0,
SW cache simtrace-analyser-v16.
2026-08-22 23:35:20 +03:00
catarrh fac6bd09af Decode SET UP IDLE MODE TEXT command data; bump to 1.11.0
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.
2026-08-22 22:17:27 +03:00
catarrh 0f4f5e4e95 Theme-aware SVG favicon; SW cache v13
- 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.
2026-08-22 12:13:16 +03:00
catarrh d9133ffa57 Document decoder features, server options, recovery flags in README
- Add Features section: APDU decoder coverage (CLA/SFI-aware P1/P2,
  UICC-specific status words, CAT proactive/TR/ENVELOPE, SMS TPDU,
  SCP80 OTA secured packets + PoR, ATR/FCP) and simtrace-analyser
  PWA bullets (map/list views, session deep links, PCAP import,
  EN/RU i18n).
- Document server CLI: --host/--port/--db/--capture/--gsmtap-port/
  --web-dir; add --capture disabled example (three capture modes,
  was two).
- Add missing sniffer recovery flags: --reconnect-delay-min/max,
  --backoff-factor.
2026-08-21 22:53:53 +03:00
catarrh ed25fcecf5 Fix APDU/SPI/SW decoder bugs per TS 102 221/TS 102 225; bump to 1.10.0
Spec-correctness release, verified against TS 102 221 V18.2.0 and
TS 102 225 §5.1.1:

- SPI1 ciphering bit: b3 (0x04), not b4 (0x08) — both the SPI dict
  and the KIc-unused check. Real MegaFon captures (SPI1=16) decoded
  as unciphered with "KIc unused" before this fix.
- SPI2 PoR-ciphering bit: b5 (0x10), not b6 (0x20); add por_transport
  field from SPI2 b6 (SMS-DELIVER-REPORT vs SMS-SUBMIT).
- SW table: add UICC-specific SWs 62F1/F2/F3, 63F1/F2, 6401, 9300
  (SAT busy), 9850, 9862/63/64; fix 6500 text ("NV memory changed");
  91XX now decodes as "Proactive command pending" via pattern.
- 63CX no longer hardcoded as PIN failure — exact 63F1/F2 entries take
  precedence; all other 63CX route through one verification-retry
  pattern (_SW_PATTERNS dict → ordered list).
- APDU_SPEC: +9 commands (D7/DD odd-INS update, D2 write record,
  82 external auth, 86/87 general auth, 22 MSE, 0E/0C erase, 7C LSI).
- decode_cla: proper ISO 7816-4 §5.1.1 coding — SM bits b4-b3,
  chain bit b5, further-interindustry channel offset (+4).

Tests: +27 (TestSwUiccSpecific, TestSpiBits, TestApduSpecCoverage,
TestClaDecode) — 279 total. SW cache bumped to simtrace-analyser-v12.
2026-08-21 22:41:16 +03:00
catarrh f75a4a4183 Bump version to 1.9.0 2026-08-20 23:13:21 +03:00
catarrh cee1b6f7c3 Add shareable session deep links (#session=N) 2026-08-20 23:13:03 +03:00
catarrh 66eb0b86e7 Mirror command colors to APDU list; show envelope type name in map view 2026-08-20 22:47:33 +03:00
catarrh ab1e95e40f Shrink search field, label filter selector, bold session name 2026-08-20 22:25:06 +03:00
catarrh 2cfab0e9e8 Distinctive map-view styling for proactive, TR, ENVELOPE, AUTH commands 2026-08-20 22:13:45 +03:00
catarrh 582c42c5cd Decode SCP80 Response Packet (PoR) in GET RESPONSE after SMS-PP DOWNLOAD 2026-08-20 21:48:28 +03:00
catarrh ef1ba00732 Attribute SFI ops in trace map; collapse long file lists 2026-08-20 21:23:25 +03:00
catarrh 8cb6c4d547 Bump version to 1.8.0 2026-08-19 18:01:12 +03:00
catarrh 5d91450b55 Track device connection and capture gaps; surface sniff error flags 2026-08-19 18:00:52 +03:00
catarrh 26043e5381 Decode EF_DIR EAP discretionary template (TS 102 310) 2026-08-19 09:02:39 +03:00
catarrh f11a6a62e6 Fix IMSI decode: drop TS 24.008 type+parity nibble 2026-08-19 02:00:25 +03:00
catarrh 342167fb52 Bump version to 1.7.0 2026-08-19 01:41:16 +03:00
catarrh ee5cd8e1fe Resolve SFI addressing for record commands; track selection per logical channel 2026-08-19 01:40:04 +03:00
catarrh 7b4f192a48 Bump version to 1.6.0 2026-08-18 23:31:36 +03:00
catarrh 86317d0105 Decode READ/UPDATE BINARY SFI vs offset; surface proactive command qualifiers; fix capture stop 2026-08-18 23:31:26 +03:00
catarrh 166cf4ed0f Decode KIc/KID algorithms unconditionally; expand packet-length labels 2026-08-17 21:40:12 +03:00
catarrh f6ce7dfe3e Decode STATUS FCP, REFRESH mode, and length mismatches 2026-08-17 20:06:28 +03:00
catarrh 0ae75bfdb2 Shorten 'current ADF' and dedupe files in trace map 2026-08-17 01:26:09 +03:00
catarrh 42580d8999 Bump version to 1.5.0 2026-08-17 01:09:17 +03:00
catarrh 017c65de27 Add trace map view; decode PNN/SPN and 7-bit SMS TP-UD 2026-08-17 01:08:49 +03:00
catarrh 1c2d6af9e4 Decode OTA secured command/response packets in SMS TP-UD; collapse RFU P1/P2 2026-08-16 23:08:54 +03:00
catarrh a056acb753 Bump version to 1.4.0 2026-08-16 22:18:24 +03:00
catarrh 58af838e72 Decode P1/P2 for all commands; fix PIN/SEARCH RECORD references
- Always emit raw P1/P2 in decode_message (with name/bits when the spec
  provides them), so FETCH/ENVELOPE/GET RESPONSE/etc. show P1/P2.
- Add PIN key-reference table (TS 102 221 Table 9.3) and move PIN
  reference from P1 to P2 for VERIFY/CHANGE/DISABLE/ENABLE/UNBLOCK PIN.
- Fix SEARCH RECORD P2 mode bits (forward/backward/enhanced/proprietary).
- Add MANAGE CHANNEL P2 (channel number), RETRIEVE/SET DATA block mode,
  GET/PUT DATA tag, STATUS/DELETE FILE P2.
- Frontend renders raw-only P1/P2 cleanly.
2026-08-16 22:15:20 +03:00
catarrh 0fdaed503e Deepen AUTHENTICATE decode: P2 auth context + AUTN/AUTS decomposition
- Add a P2 spec for AUTHENTICATE (auth context bits + specific-reference-data
  flag), so P2 is displayed instead of only leaking through cmd.context.
- Fix P1 name to 'No indication' (was the misleading 'Run GSM algorithm').
- Decompose AUTN into SQN(xor)AK / AMF / MAC and AUTS into SQN(xor)AK / MAC-S
  per TS 33.102; surface 'specific reference data' key flag.
2026-08-16 21:44:57 +03:00
catarrh 35067f8d9d Color-code SW in APDU list + fix empty ISIM record decode
- SW column now shows green (success 90/92-9F), blue (pending 61/91/6C),
  amber (warning 62/63), red (error 64-6F) per ISO 7816-4 / TS 102 221.
- _decode_nai: treat all-FF records as empty instead of emitting
  replacement characters; strip FF padding before the ASCII fallback.
2026-08-16 13:22:01 +03:00
catarrh 747974460e Harden selection tracking against dropped/failed SELECTs
- Only update the selected-file state on a successful SELECT (SW1 in
  61/62/63/90/91/92/9E/9F); a failed SELECT (e.g. 6A82) leaves the
  current file unchanged.
- Track the file structure from the FCP (GET RESPONSE after SELECT).
- Skip file decoding for record ops (READ/UPDATE/SEARCH RECORD) when the
  selected file is transparent/DF, marking the result 'stale' instead of
  decoding garbage — this catches silently-dropped SELECTs.
2026-08-16 00:20:32 +03:00
catarrh 91cb4e6e02 Add file-aware decoding for READ/UPDATE/SEARCH RECORD
- Track the currently selected file (SELECT by FID/path) across the
  session and pass it to the decoder; reset on ATR/card change; seed via
  a bounded backward scan on paged views.
- Decode READ BINARY/RECORD, UPDATE BINARY/RECORD and (via GET RESPONSE)
  SEARCH RECORD bodies using a FILE_DECODERS registry: IMSI, ICCID,
  LI/PL, ADN-format (MSISDN/SDN/FDN/...), SMS records (SMSC + TPDU),
  UST/SST/IST service tables, PLMN lists (+access tech), EF_DIR,
  CBMI/R, ARR, PNN, ECC, SPN, LOCI, ACC, Phase, ISIM IMPI/DOMAIN/IMPU.
- File formats verified against TS 51.011 / TS 102 221 / TS 31.102
  (PLMNwAcT is 5-byte entries; EF_SMS = status + SMSC + TPDU; ISIM
  identities are 0x80 TLV).
- Offset != 0 READ BINARY/UPDATE BINARY is skipped (fragment reads).
- SEARCH RECORD results (record numbers) decoded in GET RESPONSE.
- GET RESPONSE summary now shows FCP file type/structure/size.
- New 'File data' section in the detail view.
2026-08-15 23:11:31 +03:00
catarrh 143e1854e2 Add ATR/PPS + SMS TPDU decoding, command summaries, SW column
- Decode ATR (convention, interface bytes, historical, TCK) and PPS (Fi/Di, PCK)
- Decode ENVELOPE SMS-PP DOWNLOAD + SEND SHORT MESSAGE TPDU: SCTS, VP,
  DCS/PID, UDH IEIs, best-effort 8-bit text
- Fix Duration data object ([time unit][interval], not a 16-bit int)
- List view: command summary instead of raw hex, SW column, raw APDU
  first line in detail view
- Expand FID tables (full pySim USIM/ISIM/SIM set) + context-aware
  SELECT-path resolution (7FFF = current ADF)
- Fix SELECT P1 names (0x09 = path from current DF)
- Bump version to 1.3.0
2026-08-15 21:24:50 +03:00
catarrh ef62956922 Light theme: match APDU detail header bar to selected-row tint
Use the same blue-100 (#dbeafe) background on the detail header bar as
the selected row, for uniformity; dark theme unchanged.
2026-08-15 18:08:59 +03:00
catarrh 84454ed66e Light theme: tint selected APDU row with light blue
The selected row was white (--bg-input) in light theme, nearly
indistinguishable from the unselected/hover rows. Use a blue-100 tint
(#dbeafe) in light theme only; dark theme unchanged.
2026-08-15 18:05:49 +03:00
catarrh 97a7e6034a Show status in trace empty-state; discard empty captures; polish capture-mode
Frontend:
- trace empty-state now shows connection status + capture mode (boxed,
  colored) in the center, updated live via checkConnection
- capture-mode uses amber warning style (gsmtap/disabled), green for direct
- stopCapture clears currentSessionId

Server:
- gsmtap read_packet catches OSError on socket close (no more EBADF
  traceback on stop)
- stop_session joins the loop and discards a session with zero messages;
  /api/capture/stop reports discarded
- startup prints server version

Tests: 122 (gsmtap close, empty/non-empty capture lifecycle).
2026-08-15 08:48:07 +03:00
catarrh 0bb3bc82c7 Add --host bind flag + gitignore tools/ (Docker demo scaffolding)
- server --host (default 127.0.0.1) so it can bind 0.0.0.0 inside a
  container
- gitignore tools/ (untracked local Docker/export scripts)
2026-08-15 02:31:34 +03:00
catarrh c21b655fe1 Fix pcapng timestamp resolution + imported-session duration
- pcap.py: _parse_tsresol now pads options to a 32-bit boundary
  ((-length) % 4) and uses 2** for the MSB-set (power-of-2) case, so
  nanosecond if_tsresol is read correctly (was defaulting to µs and
  scaling timestamps 1000x)
- database/server: set imported session started/ended from the first/last
  packet timestamps (falling back to wall-clock for synthetic SPB)
- frontend: formatDuration renders hours
- tests: tsresol (ns after padded option, default µs, ns round-trip) and
  session-window/fallback (119 total)
2026-08-15 02:02:44 +03:00
catarrh 5ba55ac3a9 Decode EVENT DOWNLOAD location/caller and CELL BROADCAST DOWNLOAD CB page
- EVENT DOWNLOAD: decode location information (PLMN/LAC/Cell), caller
  number (MT call Address), transaction identifier, subaddress
- CELL BROADCAST DOWNLOAD: decode CB page header (serial, message ID,
  DCS, page) with raw content hex
- frontend: render Caller/Transaction/Subaddress and CB page fields

Tests: 114.
2026-08-15 00:55:07 +03:00
catarrh d32da2b375 sw.js: ignore non-http(s) and non-GET requests in fetch handler
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.
2026-08-15 00:40:26 +03:00
catarrh 9362bef869 Add server-source selector (same-origin default) and drop SIMTRACE_EMBEDDED
- Settings: 'Server' selector — 'Same origin' (default) vs 'Custom URL'
  (URL input revealed only in custom mode, placeholder 127.0.0.1:8081)
- serverUrl defaults to '' (same-origin); persisted per choice
- remove SIMTRACE_EMBEDDED frontend check and the server's _EMBEDDED_MARKER
  index.html injection (no longer needed)
- i18n: Server / Same origin / Custom URL (EN/RU)
2026-08-15 00:24:58 +03:00
catarrh 24d0decd51 Add --capture disabled mode + capture-mode status in PWA
Server:
- --capture accepts 'disabled'; no backend is created, capture_mode exposed
- /api/status returns capture_mode; capture start/stop return 403 when
  disabled; latest/status/delete_session are None-safe

PWA:
- captureMode state; conn-status shows server version
- new capture-mode line (Settings) explaining gsmtap/direct/disabled
- hide capture + New capture UI when disabled; trace empty-state shows
  'Capture unavailable'
- 7 new EN/RU i18n keys

Version 1.2.0 (server + PWA); tests: 111.
2026-08-14 23:49:10 +03:00
catarrh b900a8a3e1 Merge simtrace-analyser PWA into frontend/ (monorepo); serve it same-origin
- frontend/: simtrace-analyser PWA (index.html, sw.js, manifest.json,
  icons, tests/index.test.html) copied in fresh
- server.py: static file serving (_STATIC_MIME, _serve_static) with
  path-traversal guard; injects window.SIMTRACE_EMBEDDED='1' into
  index.html so the PWA defaults to a relative API base
- server __main__: --web-dir (default <repo>/frontend)
- frontend: embedded same-origin mode; sw.js relative precache paths +
  cache v4; GitHub link + version-warn point at simtrace2-pysniff
- version 1.1.0 everywhere (__version__, pyproject, PWA_VERSION,
  manifest); MIN_SERVER_VERSION stays 1.0.0
- README/scripts/.gitignore updated

Tests: 107.
2026-08-14 16:26:28 +03:00
catarrh 8b7b10b8fa Version 1.0.0; expose version in /api/status
First published version. __version__ and pyproject bumped to 1.0.0,
/api/status now returns 'version' so the PWA can check compatibility.

Tests: 107 (status includes version, package version).
2026-08-14 10:24:24 +03:00
catarrh 73f40ad570 server: send Access-Control-Allow-Private-Network on CORS responses
Lets a public-hosted PWA reach a local simtrace2-pysniff-server
(http://127.0.0.1:8081); without it the browser blocks the preflight
before any request is sent.
2026-08-14 00:45:52 +03:00
catarrh e5a6000bfe parse_tlv: support BER-TLV long-form lengths
Proactive commands with a body over 127 bytes (e.g. SET UP MENU with a
long item list) use a long-form length (0x81 NN / 0x82 NN NN) in their
D0 wrapper.  Previously parse_tlv bailed on those, so the FETCH decoded
as a bare 'FETCH' without its command name or items.

Tests: 105 (long-form 1/2-byte, real SET UP MENU sample).
2026-08-13 10:24:59 +03:00
catarrh dd34c25899 Add PCAP/PCAPNG import via POST /api/sessions/import
- pcap.py: parse_pcap (micro/nano, both byte orders) and parse_pcapng
  (SHB byte-order, IDB linktype + if_tsresol, EPB/SPB) readers that
  unwrap Ethernet/RAW/IPv4/NULL/SLL/SLL2 to UDP and extract GSMTAP-SIM
  packets (APDU->tpdu, ATR->atr, PPS->pps), with a heuristic fallback
- database: insert_messages bulk-insert helper
- server: import endpoint reads raw body, auto-detects format, skips
  non-GSMTAP packets; 400 'No GSMTAP data found in trace' (no session)
  when empty, else creates a 'pcap' session named after the file

Tests: 102 (pcap round-trip, pcapng, no-GSMTAP, endpoint 200/400).
2026-08-13 10:15:22 +03:00
catarrh 6b2bcefbac decode_cla: label standard ETSI class bytes precisely
CLA 0x80 is the ETSI/3GPP-defined UICC/USIM class (TS 102 221 Table 10.3),
not ISO 7816-4 'proprietary'. Relabel 0x80 -> ETSI-defined (UICC/USIM),
0xA0 -> ETSI-defined (SIM/GSM), 0x00 -> inter-industry (ISO 7816-4);
drop the now-redundant note field.
2026-08-13 09:14:41 +03:00
catarrh 4e67948dce Add CAT deep decode: TR local info (PLI) + proactive command bodies
TERMINAL RESPONSE additional information:
- PLI local-info decoders by tag: Location (PLMN/LAC/Cell), IMEI (BCD),
  date-time (TS 23.040 SCTS), language, access technology, timing
  advance, battery state, network search mode, NMR
- Duration (POLL INTERVAL) and Item Identifier (SELECT ITEM)

Proactive commands (FETCH, D0 TLV, tag&0x7F normalized):
- Annex A text (0x80/0x81/0x82 UCS2 variants, else GSM-7 default) and
  DCS text decoders
- SET UP MENU / SELECT ITEM title + items, SEND SHORT MESSAGE TPDU,
  SET UP EVENT LIST events, POLL INTERVAL duration, DISPLAY TEXT etc.

Tests: 96 (TR PLI, proactive, Annex A variants, DCS text).
2026-08-13 09:00:14 +03:00
catarrh 0785e4d1df Add deeper command-body decoding: AUTHENTICATE, ENVELOPE, SM TPDU
- _decode_auth_cmd: AUTHENTICATE command body → RAND/AUTN (GSM/3G,
  context from P2)
- EVENT_TYPES + event download decode (Event List → event name,
  Location status → Normal/Limited/No service)
- _decode_envelope: D1 SMS-PP (SMSC + SMS TPDU), D2 Cell Broadcast
  (structure), D5 MO SM control (SMSC + TP-DA + location), D3/D4/D7 basic
- _decode_sm_tpdu: SMS-DELIVER/SMS-SUBMIT → TP-MTI, BCD address, TP-PID,
  TP-DCS (encoding + class), text (GSM 7-bit / UCS2 only; 8-bit stays
  hex; PID 0x7F → secured packet hex)
- helpers: _decode_bcd_address, _decode_gsm7, GSM 7-bit alphabet

decode_message stores command-body decode in decoded.cmd for
AUTHENTICATE (0x88/0x89) and ENVELOPE (0xC2).

tests: 79 total (auth-cmd, event download, SM TPDU, BCD, GSM-7).
2026-08-12 22:23:39 +03:00
catarrh 0d9d6f7157 README: fix PCAP/GSMTAP output wording, add Related section
PCAP is now Ethernet/IP/UDP/GSMTAP (not raw LINKTYPE 155). Cross-reference
simtrace-analyser with a Try-it-now link.
2026-08-12 21:50:30 +03:00
catarrh 7556401618 CLI --pcap: only write ATR/TPDU messages (skip PPS/CHANGE/FIDI)
The PCAP branch fell back to GSMTAP_SIM_APDU for every non-ATR message,
so PPS/CHANGE/FIDI were written as 'APDU'. Align with the UDP branch and
the server export: only atr→ATR and tpdu→APDU are written.
2026-08-12 21:37:06 +03:00
catarrh cb4c07f3c2 Fix GSMTAP linktype + type/sub-type values so Wireshark decodes PCAP
pcap.py: LINKTYPE_GSMTAP (155) was wrong — 155 is DLT_USER8, so
Wireshark showed 'USER 8 / DLT=155'. Now write Ethernet-encapsulated
PCAP (LINKTYPE 1): each GSMTAP message wrapped in Ethernet + IPv4 +
UDP (port 4729), with correct checksums — matching a real tcpdump
capture of GSMTAP-over-UDP and how Wireshark's gsmtap dissector is keyed.

gsmtap.py: GSMTAP_TYPE_SIM was 0x07 (WiMAX burst) — corrected to 0x04;
GSMTAP_SIM_APDU was 0x02 (PPS req) — corrected to 0x00; added PPS_REQ/
PPS_RSP. These match libosmocore/Wireshark packet-gsmtap.h.

Verified with tshark: PCAP now decodes as GSM SIM (SELECT/STATUS/ATR)
instead of 'USER 8'.

tests: pcap tests assert Ethernet linktype + framing + GSMTAP payload.
2026-08-12 21:32:21 +03:00
catarrh e09aac4cb7 Add session export to PCAP
pcap.py: build_pcap(packets) — build PCAP bytes in memory (same format
as PcapWriter)
database.py: get_messages_raw() — raw elapsed/type/data without decode
server.py: GET /api/sessions/<id>/pcap generates a GSMTAP-encapsulated
PCAP (ATR/TPDU) with real timestamps and downloads it as
<session-name>.pcap (ASCII filename fallback + RFC 5987 UTF-8 for
spaces/unicode via _content_disposition)

tests: build_pcap + _content_disposition (67 total)
2026-08-12 21:16:11 +03:00
catarrh a1bbd0cf0f Add session stats and VACUUM on deletion
database.py: count_sessions(), db_size() (db+WAL+SHM), vacuum()
(VACUUM + wal_checkpoint(TRUNCATE) to actually reclaim space in WAL mode)

server.py: /api/sessions now returns total_sessions + db_size;
DELETE /api/sessions/<id> runs vacuum() after deletion
2026-08-12 21:05:54 +03:00
catarrh 31011301ea Add PLI qualifier description to PROVIDE LOCAL INFORMATION decode
- PLI_QUALIFIERS: TS 102 223 qualifier values (Location Info, IMEI,
  NMR, Date/time, Timing Advance, Access Technology, etc.)
- _command_details_name(): appends the qualifier description to
  PROVIDE LOCAL INFORMATION (type 0x26) in Command Details
- decode_cat (FETCH) and decode_tr_command (TR) now use it, so both
  'cat_command' and 'response_to' carry the qualifier (e.g.
  'PROVIDE LOCAL INFORMATION — IMEI')

tests: PLI qualifier cases (TR/FETCH, IMEI/location/date, non-PLI)
2026-08-12 20:58:15 +03:00
catarrh f30fe41d75 Add per-command response decoders (FCP, AUTH, TERMINAL RESPONSE)
decode.py:
- _decode_fcp: FCP/FCI/FMD template — full bit-level file descriptor
  (TS 102 221 Table 11.5: file type, structure, shareable, data coding,
  record length, num records), file ID (+ KNOWN_FIDS name), DF name,
  SFI, life cycle
- _decode_auth: GSM SRES+Kc, or 3G tag DB (RES/CK/IK/KC) / DC (AUTS)
- _decode_tr_result: TERMINAL RESPONSE Result TLV (code + name)
- _decode_response_for(ins, data): GET RESPONSE decodes its body using
  the previous command's INS (SELECT→FCP, AUTHENTICATE→auth)
- decode_message wires response decoding for TR and GET RESPONSE

database.py: prev context now carries 'ins' (INS byte) via
_context_from_decoded, used by GET RESPONSE response decoding

tests: FCP, AUTH (GSM/3G), TR result, GET RESPONSE wiring (57 total)
2026-08-12 20:45:06 +03:00
catarrh e68df163a9 TERMINAL RESPONSE: label as 'TERMINAL RESPONSE', keep command type as context
decode.py: split decode_tr_command() out of decode_cat(); TR (0x14)
now stores the echoed proactive command type in 'response_to' instead
of 'cat_command', so it no longer overrides the INS name.

FETCH/ENVELOPE still use cat_command as the primary label.

tests: TR now asserts response_to + no cat_command
2026-08-12 20:02:45 +03:00