- 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.
- 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.
- 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)
- 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).
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)
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)
decode.py:
- decode_cat: TERMINAL RESPONSE (0x14) now extracts the proactive
command type from its echoed Command Details (tag 81) — no history
lookup needed since TR carries its own type
- decode_message gains prev param; GET RESPONSE (0xC0) resolves
'response_for' from the previous command when its SW1 was 0x61,
else None (orphaned)
- decode_sniff_msg threads prev through
database.py:
- _row_to_message accepts prev; new _decode_rows tracks previous-TPDU
context in sequence; get_messages/get_messages_after seed context
from the message before the window/cursor
tests: TR cat_command + GET RESPONSE context/orphaned cases (49 total)
Decode errors now log to stderr instead of crashing the entire
API response. This prevents a single malformed message from hiding
all subsequent messages in the same query.
decode_sniff_msg() dispatches to type-specific decoders:
- change: 4-byte flags bitmask → card insert/eject/reset/timeout
- fidi: 1-byte Fi/Di index → decoded values
- atr/pps: raw hex value
database.py now decodes all message types, not just tpdu.
Pure Python, no dependencies. Decodes raw TPDU bytes into
structured JSON: CLA bitfield, INS->name via APDU_SPEC table,
P1/P2 field breakdown, body extraction, SW name lookup.
~40 command types, 40+ SW codes, known FID notes.
database.py: _row_to_message() calls decode_message() for tpdu
messages, adding 'decoded' field to API responses.
Decoding happens at query time, not storage time — no schema
migration, all past sessions get decode retroactively.
Add simtrace2-pysniff-server CLI — HTTP API for simtrace-analyser PWA.
Two capture modes: GSMTAP UDP listener (compatible with simtrace2-sniff)
or direct SIMtrace2 hardware capture. SQLite storage with sessions +
messages tables. REST API with CORS.
server/ sub-package: database.py, capture.py, server.py, __main__.py
README updated with server usage section.
pyproject.toml: simtrace2-pysniff-server script entry added.