Commit Graph
60 Commits
Author SHA1 Message Date
catarrh b743c6621d Detail view: P3 says Lc or Le, and SEARCH RECORD names its target (v2.7.9)
- the P3 row read "0x02 — 2 bytes" for both kinds of P3; the decode now marks
  it (p3_kind): a command that carries data has Lc (the command-data length),
  a case-2 command (APDU_SPEC 'le', e.g. READ BINARY/RECORD, GET RESPONSE,
  SEARCH…) has Le (the expected response length).  The detail row appends
  "· Lc (command data length)" / "· Le (expected response length)"
  («Lc (длина данных команды)» / «Le (ожидаемая длина ответа)»)
- a SEARCH RECORD without an SFI searched the current EF (TS 102 221
  §11.1.7.1) but showed no file at all; it now resolves like READ/UPDATE:
  the SFI's declared file when SFI-addressed (an unresolved SFI stays marked
  "SFI n"), otherwise the current selection — with the stale-selection note
  when that EF is not a record file.  Verified on session 193: the 5.371 s
  search (P2=0x04, no SFI) now shows 4FA1, the 5.434 s one (SFI 2) shows 4F3A
- tests: p3_kind cases (Le/Lc/none) and the SEARCH target cases (current EF,
  SFI-resolved, unknown SFI, transparent selection); a Node pin for the P3
  row and the two new Russian strings; 540 Python + 77 Node tests
- PWA_VERSION/pyproject/version.py 2.7.9, SW cache simtrace-analyser-v89
2026-10-18 01:34:01 +03:00
catarrh 824405f936 Files view: the PBR's SFI relations reach the APDU decode — and survive DF hops (v2.7.8)
- the PBR SFIs were learned only by the tracker; the message decode's
  per-channel SFI map still did not know them, so the SEARCH RECORD addressed
  by SFI 2 after the PBR read showed "SFI 2, unknown file" (the reported
  case: session 193, 5.434 s — the PBR read at 4.451 declared ADN 4F3A/SFI 2)
- database._advance_state now parses an EF_PBR record it sees (a READ
  RECORD's data or an UPDATE RECORD's body) and merges its FID+SFI pairs
  into the channel's SFI map, so SFI-addressed SEARCH/READ resolve without
  the file's own FCP
- the learned relations are cached per DF for the session: the live trace
  leaves DF_PHONEBOOK for DF_TELECOM (SELECT 7F10/6F06) and comes back, and
  the DF change used to rebuild the map from the static table only, dropping
  the PBR's pairs — the re-entry now merges the session cache
- verified on session 193: the SEARCH at 5.434 s (SFI 2) now resolves to
  4f3a (EF_ADN), as do the later ones (31.562/253.475/279.874 s)
- tests: the DB-level case — a PBR read, an excursion to DF_TELECOM and back,
  then an SFI-addressed SEARCH asserting the declared file; 538 Python +
  76 Node tests
- PWA_VERSION/pyproject/version.py 2.7.8, SW cache simtrace-analyser-v88
2026-10-18 01:26:47 +03:00
catarrh ac424e67e3 Files view: name the phonebook files from the card's EF_PBR (v2.7.7)
- EF_PBR (TS 31.102 §4.4.2.1) is the card's declaration of its phonebook:
  the constructed tags A8/A9/AA announce the type-1/2/3 groups and each nested
  primitive tag C0…CC names the file kind plus its FID — and, in a three-byte
  object, its SFI (length 2 = FID only).  pySim has no parser for it (its
  EF_PBR class still carries a "# TODO: a80ac0034f3a…" stub), so the decoder
  follows the spec: new simtrace_analyser/pbr.py
- the tracker decodes every EF_PBR record it observes (a READ RECORD's data
  or an UPDATE RECORD's body) and uses it for the DF it belongs to: the
  phonebook files get their symbolic names (4FA1 = EF_PBC, 4F3A = EF_ADN,
  4F01 = EF_IAP, 4F4A = EF_EXT1, 4F3D = EF_CCP1, 4F61 = EF_EMAIL, …), an
  earlier placeholder name is upgraded when the PBR arrives later, and the
  declared SFIs feed the DF's SFI→FID map (so SFI-addressed reads resolve
  without waiting for an FCP)
- verified on the live sessions 47/193:
  DF_TELECOM/DF_PHONEBOOK/EF_PBC (4FA1) and the SFI map
  {2→4F3A, 4→4FA1, 6→4F4A, 7→4F4F}
- also fixed: the "SFI = FID's five low bits" fallback (TS 102 221
  §11.1.1.4.8) no longer applies to a DF/ADF's FCP, which has no SFI
- tests: tests/test_pbr.py (the two real records, SFI-less objects, padding,
  truncation, merging, the shape check) and tracker cases (names, path,
  SFI map, the later-PBR upgrade); 537 Python + 76 Node tests
- README/README_RUS document the PBR naming; PWA_VERSION/pyproject/
  version.py 2.7.7, SW cache simtrace-analyser-v87
2026-10-18 01:18:26 +03:00
catarrh 5dea6bceb8 Files view: the full DF chain in the file path (v2.7.6)
- the tracker kept only the immediate parent DF, so a phonebook file read as
  "DF_PHONEBOOK/4FA1" — the trace's own selection path (00a4080406
  7F105F3A4FA1) spells out the whole chain; the channel state now carries it
  (selected_df_state() returns (df, path), e.g. ('5f3a', ['7f10', '5f3a'])),
  the tracker stores it per file and _file_path() renders it:
  "DF_TELECOM/DF_PHONEBOOK/4FA1"
- chain rules: a child DF selected by FID appends to the current chain, MF and
  the ADF restart it, an AID is its own root, a path selection uses the DFs
  in the path (an explicit '7fff' collapsing to the ADF), and an EF selects
  keeps it.  A leading MF is implicit in a longer path; MF-only files keep
  "MF/…", ADF files "ADF/…" (the previous labels unchanged)
- the chain is learned from the FCP sighting too and extends a shorter path
  recorded earlier, so entries created from a GET RESPONSE still get the full
  path
- verified on the live traces (sessions 47/193): 5f3a/4fa1 ->
  DF_TELECOM/DF_PHONEBOOK/4FA1, 5f3a/4f30 -> DF_TELECOM/DF_PHONEBOOK/EF_PBR
  (4F30), while 3f00/2fe2 -> MF/EF_ICCID (2FE2) and 7fff/6f07 ->
  ADF/EF_IMSI (6F07) stay as before
- tests: selected_df_state (path/FID/MF/ADF/AID/EF cases) and a tracker flow
  with a path selection asserting the full path; 529 Python + 76 Node tests
- README/README_RUS show the chain; PWA_VERSION/pyproject/version.py 2.7.6,
  SW cache simtrace-analyser-v86
2026-10-18 01:05:55 +03:00
catarrh 64433f9739 Extended logical channels: CLA 0-19, ATR card capabilities, MANAGE CHANNEL (v2.6.0)
- 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
2026-10-17 22:42:11 +03:00
catarrh cbeeed3608 File names: the DF-scoped spec dictionary + refreshed EF/SFI tables (v2.5.0)
- 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
2026-10-17 22:11:47 +03:00
catarrh c494bf4111 Files view: per-file tracking (counters, image, device/card changes)
- 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
2026-10-17 20:51:33 +03:00
catarrh a50a93a238 Decode the Items Next Action Indicator (TS 102 223 §8.24)
- 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
2026-09-24 20:16:31 +03:00
catarrh d4a378695c Decode cross-spec "reserved" values via the sibling specs
- 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
2026-09-20 14:06:40 +03:00
catarrh 561801a983 TERMINAL CAPABILITY: decode the A9 template (TS 102 221 §11.1.19)
- 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)
2026-09-20 00:11:41 +03:00
catarrh 38650b4797 Rename project to SIMtrace Analyser (v2.0.0)
- 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
2026-09-19 12:34:16 +03:00
catarrh 8441b56e99 TERMINAL PROFILE: device characteristics in list + detail views
- decode.py: _decode_terminal_profile (TS 102 223 §5.2, b1 = LSB per the
  spec tables): class (declared ND/NK at byte 14 b6/b7, strict likely/
  handset rules), generation (positive-only 3G/HSDPA, 4G/E-UTRAN from
  byte 17 — never a 2G claim, real devices under-declare), BIP (byte 12
  b1, byte 13 bearers/channels, byte 17 transports), standards family
  (byte 18 IMEISV/ESN), UI facts (soft keys/screen/markup), era band
- list summary replaces raw hex: e.g. '3G/4G · BIP', 'modem · BIP',
  'no BIP', fallback '<n>-byte profile'
- detail view: tpRows() panel with class/reason, radio bits, BIP,
  standards, UI, profile length
- body row label for TERMINAL PROFILE: 'TLV data' -> 'Profile'
- tests: TERMINAL_PROFILE.md §10 regression anchors + synthetic
  declared-modem/handset vectors
- Bump 1.22.21, SW cache v52->v53
2026-09-19 01:39:34 +03:00
catarrh 2c373f50b7 Show Location Status value in EVENT DOWNLOAD list summaries
- _build_summary: fold cmd.location_status (Normal/Limited/No service)
  into the 'Location status' event entry, e.g. 'Location status: Normal
  service' (the detail view already showed it)
- tests: summary assertions for the Normal/No-service vectors
- Backend-only, no version bump
2026-09-19 00:43:58 +03:00
catarrh 04443878a7 Show destination address in MO SHORT MESSAGE CONTROL summaries
- _build_summary: append 'DA <tp_da>' after the SMSC part for the
  MO SHORT MESSAGE CONTROL envelope (the envelope decoder already
  stores the second address TLV as cmd.tp_da; the detail view already
  showed it)
- test: constructed D5 envelope with SMSC + destination address
- Backend-only, no version bump
2026-09-19 00:29:33 +03:00
catarrh 500e764a1b List view: decoded summaries for ATR/PPS/RST/VCC instead of raw hex
- decode.py: decode_sniff_msg builds list summaries — ATR: protocol(s),
  TA1 F/D + ratio, T=15 classes, data rate (when a CLK rate was known);
  PPS: protocol + F/D + ratio; RST: 'CLK x.xxx MHz' when the event
  carried a rate
- index.html: cmd_description returns summary || '' for these types and
  STATUS, never falling back to opaque bytes like 010100
- tests: ATR (plain/TA1/classes+rate/unparsed), PPS, RST CLK/no-CLK, VCC
- Bump 1.22.20, SW cache v51->v52
2026-09-19 00:17:47 +03:00
catarrh ccbb3f231b STATUS list entries: suppress raw hex when empty, describe FCP data
- 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
2026-09-19 00:10:09 +03:00
catarrh 39ee173a7d Rename RST de-asserted label to 'RST de-asserted — ATR follows'
- More precise than 'ATR STARTS' (the ATR starts after the de-assert
  delay, not at the edge) while staying human-friendly; reads as a pair
  with 'RESET ASSERTED'
- Update tests and both READMEs (backend-only, no version bump)
2026-09-18 23:59:45 +03:00
catarrh a46667d93f Decode standard PPS sub-types + RST CLK extension → ATR data rate
- 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
2026-09-18 00:30:26 +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 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 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 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 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 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 582c42c5cd Decode SCP80 Response Packet (PoR) in GET RESPONSE after SMS-PP DOWNLOAD 2026-08-20 21:48:28 +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 ee5cd8e1fe Resolve SFI addressing for record commands; track selection per logical channel 2026-08-19 01:40:04 +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 f6ce7dfe3e Decode STATUS FCP, REFRESH mode, and length mismatches 2026-08-17 20:06:28 +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 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