Commit Graph
115 Commits
Author SHA1 Message Date
catarrh e00a96b918 Release a Windows-friendly runtime zip (v2.7.12)
Release / release (push) Waiting to run
The releases carried the wheel and the sdist; the only zip was GitHub's source
archive of the whole repository (tests, CI, tools).  A Windows user just wants
to extract a folder and double-click start.bat.

- scripts/build_release_zip.py builds simtrace-analyser-<version>.zip with a
  single top-level directory: the two Python packages, the PWA runtime files
  (frontend/tests and package.json stay out), the four launchers, the udev
  rule, the READMEs and the screenshot.  The .bat entries are written with
  CRLF line endings (cmd.exe mishandles bare-LF labels/goto), the .sh files
  keep their executable bit; LICENSE is included automatically once the repo
  has one.
- tests/test_release_zip.py pins the archive shape, the exclusions, the sw.js
  PRECACHE drift (every precached asset must travel), the CRLF/LF line endings
  and start.sh's mode.
- the release workflow builds the zip, checks it carries start.bat and the
  PWA, uploads it with the run and attaches it to the release.
- READMEs (EN/RU) and the release-notes template offer the zip as the
  Windows-friendly option; version 2.7.12, PWA cache v94.

Verified: the built zip (34 entries, 658 KB compressed) extracted and started
on Linux serves the same index.html; 553 Python + 80 Node tests.
2026-10-21 00:17:06 +03:00
catarrh efcb6c6c3f Version 2.7.11: the Windows launcher and install-doc fixes
Ships the launcher/doc commit on top of v2.7.10 as a release:
- start.bat passes --db/DB to the server (it only stored the value before) and
  sends --gsmtap-port only for gsmtap capture, like start.sh;
- both .bat launchers quote their values and keep the quoting of pass-through
  options, so paths with spaces work (--db, --web-dir, --pcap, --output);
- the READMEs describe both install paths (release artifacts or a git clone)
  and document the launchers and the Windows python-on-PATH requirement.

Version strings in version.py, pyproject.toml, PWA_VERSION and the version
test move together; SW cache simtrace-analyser-v93.
2026-10-20 23:18:57 +03:00
catarrh 25aa87ba1e Packaging: the wheel carries the PWA, so a release install serves the UI (v2.7.10)
- pyproject declared build-backend = "setuptools.backends._legacy:_Backend",
  which does not exist: `python -m build`, `pip install .` and even
  `pip install -e .` died with "Backend ... is not available".  It is
  setuptools.build_meta now.
- the wheel and sdist carried only the Python packages, and _default_web_dir()
  resolved <site-packages>/../frontend, so an installed server started fine but
  answered every static request with 404.  setup.py's build_py now copies the
  checkout's frontend/ into simtrace_analyser/web/ inside the build (the PWA's
  tests excluded), and _default_web_dir() prefers that packaged copy, falling
  back to <repo>/frontend for source and editable runs.  The source directory
  is resolved from setup.py (so an sdist build works) and a missing frontend/
  fails the build loudly instead of shipping a package without its UI.
- MANIFEST.in grafts frontend/ (with the READMEs, the udev rule and the four
  launch scripts) into the sdist, so a wheel built from the sdist has the PWA.
- README (EN/RU): an install-from-a-release section (pipx/pip with the wheel,
  the direct release URL for pip) and the --web-dir default documented as the
  bundled PWA, or <repo>/frontend from source.
- tests/test_packaging.py pins the packaged-first resolution (including the
  empty-directory fallback), the source checkout default, the real build
  backend, the manifest graft and setup.py's bundling.
- verified: a fresh venv install from the wheel and from the sdist serve
  GET / (200, byte-identical index.html, PWA v2.7.10) out of
  site-packages/simtrace_analyser/web; an editable install still resolves
  <repo>/frontend.  547 Python + 80 Node tests.
2026-10-20 23:06:33 +03:00
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 1df52a34e5 Change details: the length cut never hides observed bytes (v2.7.5)
- a record whose FCP understates the layout (the reported case: record_len 2
  while the READ RECORD carried P3=0x2C and returned 44 bytes) had its rows
  cut to two cells by the v2.7.3 length rule, hiding 42 newly-observed bytes
  while the summary still counted them
- the filler now stops at the known length only while the data fits inside
  it; beyond that — and when the length is unknown or absent — the row ends
  at the data, so nothing observed is ever hidden and no bytes are invented
- verified on the live payload (session 55, 5f3a/4f09 change 0): before = 2
  cells, both changed; after = 16+16+12 cells with 2 changed and 42 newly
  observed, matching the summary line
- tests: the regression case (a 2-byte record_len with a 44-byte read) and
  the unknown-length case, whose row now ends at the data; 527 Python +
  76 Node tests
- PWA_VERSION/pyproject/version.py 2.7.5, SW cache simtrace-analyser-v85
2026-10-18 00:50:55 +03:00
catarrh 06c6e952a9 Change details: labelled APDU/time in the header, no zero counts (v2.7.4)
- the header's trailing meta is labelled instead of bare numbers:
  "ADF/EF_IMSI (6F07) — READ BINARY · APDU #490 · 0251.199 sec" (the record
  part already read "Record 2 / 5")
- the summary counts only what happened: "{c} bytes changed · {n} bytes newly
  observed" drops whichever part is zero ("4 bytes changed" alone when nothing
  was first observed) and an empty diff reads "No changes" instead of
  "0 bytes changed, 0 bytes newly observed"
- i18n: the combined key splits into '{c} bytes changed' / '{n} bytes newly
  observed' plus 'No changes', 'APDU' and 'sec' ('{c}','{n}' placeholders keep
  the existing replace-based substitution; the Russian keeps the established
  wording: «Изменено байт: {c}», «Впервые наблюдается байт: {n}», «с»)
- tests: the header and summary assertions follow the labels, a short-file
  case pins the omitted zero, and an empty-diff case pins 'No changes';
  527 Python + 76 Node tests
- PWA_VERSION/pyproject/version.py 2.7.4, SW cache simtrace-analyser-v84
2026-10-18 00:46:42 +03:00
catarrh 71a94f5f50 Change details: no filler past a short file, no offsets for a single row (v2.7.3)
- the hex views padded every line to 16 bytes with '--', so a 2-byte file
  looked like it was missing data; the row now ends at the known file/record
  length (from the FCP — hexImageRows gained the size parameter), while '--'
  still marks bytes *inside* the file that were never observed (an unknown
  length keeps the old row padding, nothing better is available)
- the offset column is shown only when a view spans more than one row — a
  four-hex-digit 0000 above two bytes was noise
- the legend lists only the marks actually rendered: 'never observed' is gone
  from a short/fully-observed view, and 'newly observed' when nothing was
  first observed (the summary line still counts both)
- tests: pure hexImageRows cases (the 2-byte file, a 9-byte record with an
  unobserved tail, the unknown-size fallback) plus two renderFileDiff cases
  pinning the missing filler/offset and the trimmed legend; 527 Python +
  75 Node tests
- README/README_RUS describe the new hex-view behaviour;
  PWA_VERSION/pyproject/version.py 2.7.3, SW cache simtrace-analyser-v83
2026-10-18 00:37:12 +03:00
catarrh bc6fcc02c2 Command filter: two presets, and a "Selected: N" summary (v2.7.2)
- the 'Proactive + events' preset is gone: the proactive one (FETCH,
  TERMINAL PROFILE and the SW 91xx initiators) plus the individual ENVELOPE
  entry cover it, and its Russian key is removed with it
- with several entries checked the filter button now reads "Selected: N"
  («Выбрано: N») instead of "N commands"; a single entry keeps showing its
  name and an empty selection stays "All commands"
- tests: the removed preset's case is replaced by an absence pin in the menu
  test, and the summary case follows the new wording; 527 Python + 72 Node
  tests
- README/README_RUS list two presets; PWA_VERSION/pyproject/version.py 2.7.2,
  SW cache simtrace-analyser-v82
2026-10-18 00:05:21 +03:00
catarrh 024527ada4 Command filter: FETCH and TERMINAL RESPONSE are separate entries (v2.7.1)
- the old "FETCH: ANY PROACTIVE" grouping label is gone: filter_label() now
  gives every command its own label, so the menu lists FETCH and TERMINAL
  RESPONSE individually — the multi-select filter (and the presets) cover the
  combinations that the grouping used to bundle
- the proactive presets keep their meaning: they name FETCH and TERMINAL
  PROFILE and reach the rest (a TERMINAL RESPONSE included) through the SW
  91xx rule; selecting FETCH now also brings its GET RESPONSE, which carries
  the fetched proactive command
- tests: the proactive-preset case follows the split (a TERMINAL RESPONSE is
  covered by 91xx, not by its name) plus a new case pinning the separate
  labels and the FETCH/GR pairing; 527 Python + 73 Node tests
- PWA_VERSION/pyproject/version.py 2.7.1, SW cache simtrace-analyser-v81
2026-10-17 23:58:23 +03:00
catarrh e0134c769a Command filter: multi-select with conversation presets (v2.7.0)
- the single-command <select> becomes a checkbox dropdown: the filter is a
  set of entries (an empty set shows everything), so any number of commands
  can be shown at once instead of one
- three fixed presets select whole conversations:
  * Proactive sessions — FETCH, TERMINAL PROFILE and every command the card
    answered with SW 91xx (the proactive-session initiators)
  * Proactive + events — the same plus ENVELOPE
  * File operations — SELECT, READ BINARY, READ RECORD, UPDATE BINARY,
    UPDATE RECORD (their GET RESPONSE, which carries the data/FCP, comes
    along through the existing response_for linkage)
  presets and single commands combine as a union, and the menu lists the
  session's commands with their counts
- the button summarises the selection ("All commands" / one name /
  "{n} commands"); Escape or a click outside closes the menu; focusing an
  APDU from the Files panel/map still clears the filter first
- tests: a new filter test (the matching rules per entry kind, the GR
  linkage, the presets, the union, the summary, the menu rows, the RU keys
  and the wiring pins), and the focusMessage tests follow the new state;
  527 Python + 72 Node tests
- README/README_RUS describe the filter; PWA_VERSION/pyproject/version.py
  2.7.0, SW cache simtrace-analyser-v80
2026-10-17 23:55:28 +03:00
catarrh 009fcf2253 Files view: the update row's change link is "Show diff" (v2.6.5)
- an update row whose image changed (or whose bytes were first observed)
  offered a generic "Details" link; it now says "Show diff" — Russian
  «Изменения» — while a card-side change keeps its "Changed on card!" link
  (both open the same change modal)
- the now-unused 'Details' RU key is gone; tests update the wiring pin and
  the Russian-translation list; 527 Python + 61 Node tests
- README/README_RUS mention the new label; PWA_VERSION/pyproject/version.py
  2.6.5, SW cache simtrace-analyser-v79
2026-10-17 23:41:28 +03:00
catarrh be75be9ed2 Files view: a card-side change reads as its read, with a "Changed on card!" link (v2.6.4)
- a card-side change is discovered by a read operation, so the timeline row
  now reads "Read EF_IMSI" — the row keeps its warn colour and layout — with
  "Changed on card!" as the trailing underlined link that opens the change
  modal; an update row keeps the generic "Details" link
- the 'Changed on card' verb string is gone; the new 'Changed on card!' key
  carries the Russian «Изменено на карте!»
- tests: the verb/label cases updated (card_change -> Read, "Read EF_LOCI")
  and the Files-view wiring test pins the kind-dependent link label;
  527 Python + 61 Node tests
- README/README_RUS describe the new row wording; PWA_VERSION/pyproject/
  version.py 2.6.4, SW cache simtrace-analyser-v78
2026-10-17 23:37:30 +03:00
catarrh 475c136749 Topbar: title trim, shorter search, the view selector pinned above the panel (v2.6.3)
- the header title drops its "UICC" prefix — "GSMTAP trace analyser" is enough
- the trace topbar's search input is half its previous width (flex basis
  33.333% -> 16.667%); the session name now ellipsizes instead of wrapping
  the tighter row
- the side-panel view selector moved out of the scrollable side panel into
  the trace topbar: right-aligned over the panel at the panel's width
  (--side-panel-w: 260px, the negative right margin cancelling the topbar's
  16px padding so the edges line up), so it stays visible at all times while
  the panel scrolls
- tests: the topbar/selector position pins (the Files-view wiring test and
  style.test.js), the shared width variable, the halved search basis and the
  title; 527 Python + 61 Node tests
- README/README_RUS: the selector's new place; PWA_VERSION/pyproject/
  version.py 2.6.3, SW cache simtrace-analyser-v77
2026-10-17 23:30:37 +03:00
catarrh 76c5647dd4 Files view: the jumped-to record stays selected (v2.6.2)
- focusMessage()/scrollToType() highlighted the target and removed the class
  after 1500 ms, so a Files-row (or map/category) jump looked selected for a
  moment and then lost it; on top of that any re-render (the 500 ms live
  poll, a filter/search change) rebuilt the list and dropped the highlight —
  even for a clicked entry
- the selection is now persistent app state (STATE.selectedIdx): the render
  marks the entry (gap rows included), focusMessage records the index before
  rendering, showDetail records it for plain clicks, scrollToType persists it
  too (clearing the previous highlight), and a new session/capture resets it
  so a stale index cannot mark an unrelated APDU
- tests: a stub-driven focusMessage case (state + detail updated, no render
  and no timer), the filter-clearing case, and source pins for
  renderMessages, focusMessage, scrollToType, showDetail, loadSession and
  startCapture; 527 Python + 58 Node tests
- README/README_RUS: the jump leaves the record selected; PWA_VERSION/
  pyproject/version.py 2.6.2, SW cache simtrace-analyser-v76
2026-10-17 23:16:33 +03:00
catarrh 08a6446bc8 Files view: the row jump no longer scrolls to the end of the trace (v2.6.1)
- jumpToMessage() rendered the APDU list through the unqualified
  renderMessages(), whose tail pins the view to the bottom (the live-capture
  stick), and only then let focusMessage() smooth-scroll back up to the
  clicked record — the visible jump-to-end-then-back.  It now renders with
  skipScroll, so the only scroll is the focus itself
- the content-follow became conditional: shouldStickToBottom() pins the view
  to the newest messages only while the reader is already at the bottom
  (40 px slack), so the 500 ms live poll no longer pulls away a view the
  reader scrolled to — a Files/map jump is no longer yanked down by the next
  batch.  A freshly opened session or a new capture still lands at the
  bottom (renderMessages(false, true))
- tests (node): the jump's paging/render(true)/focus sequence with stubs, the
  no-session no-op, the stick decision (at bottom / within slack / scrolled
  up / explicit jump / forced / first render) and source pins for
  renderMessages, jumpToMessage, loadSession and startCapture; 527 Python +
  53 Node tests
- README/README_RUS note the follow behaviour; PWA_VERSION/pyproject/
  version.py 2.6.1, SW cache simtrace-analyser-v73
2026-10-17 23:01:40 +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 5809092175 Files view: the change-details modal shows the full file path (v2.4.2)
- the modal header showed only the flat name (EF_ICCID, 4F09); it now shows the
  path built from data already available: the tracker's DF identity named via
  KNOWN_FIDS (MF, DF_GSM_ACCESS, DF_TELECOM, …), 'ADF' for the current
  application (7FFF), DF_CHILDREN for DF-qualified EF names (4F52 →
  EF_KcGPRS) and the EF.DIR labels for AID-selected applications —
  e.g. `MF/EF_ICCID (2FE2)`, `ADF/EF_IMSI (6F07)`,
  `DF_GSM_ACCESS/EF_KcGPRS (4F52)`, `AID A00000015141434C00/4F09`
- exposed as `path` in the change-details payload and in the files entries
  (the timeline rows keep the short name — they have no room)
- the colour legend (changed / newly observed / never observed) moved below
  the two hex views
- tests: `_file_path` cases (MF, ADF, DF-qualified EF, AID with and without an
  EF.DIR label, FID-as-name) plus the payload field, and the Node modal test
  asserts the path header and that the legend follows the views
- v2.4.2; SW cache simtrace-analyser-v68
2026-10-17 21:35:41 +03:00
catarrh 407821ab3d Fix session loading: a no-op capture stop aborted the load (v2.4.1)
- `loadSession` begins by stopping a running capture; when there is nothing
  to stop the server answers 400 'No active capture' (or 403 with capture
  disabled) — and since the v2.3.1 `_fetch` status fix those answers throw, so
  the whole load was skipped and the UI alerted "Failed to load session":
  no session could be opened at all
- new `stopCaptureIfRunning()` swallows exactly that no-op answer; both
  `loadSession` and the capture button use it, and the button now resets its
  state outside the try so a tolerated stop still clears the UI
- the `_fetch` status check stays — real errors keep surfacing (an unknown
  session still throws 'Session not found')
- tests: the helper resolves on a 400 and still calls once; source pins that
  `loadSession` uses the tolerant helper (the regression guard) and that
  `stopCapture` resets the state
- v2.4.1; SW cache simtrace-analyser-v66
2026-10-17 21:25:48 +03:00
catarrh cad3999307 Files view: change details with before/after images (v2.4.0)
- the timeline's Δ glyph became a **Details** button, and it now appears on
  Update rows too (not just card-side changes); EN "Changed (card)" ->
  "Changed on card", RU "Изменено (картой)" -> "Изменено картой"
- the details modal shows the affected unit — the whole file, or the single
  record that changed — **before and after** the change as hex views (16 bytes
  per row, ASCII column, never-observed bytes as `--`), with the bytes that
  changed and the bytes the operation *first observed* highlighted in two
  different colours, plus the changed/new byte counts and the APDU reference;
  the old before/after byte table is gone
- `GET /api/sessions/<id>/files/image?key=…&change=…` returns those two views
  (segments with offsets) plus the change's metadata; the images are
  reconstructed from the current image by undoing the later changes
  (`FileTracker.change_images`), so nothing is duplicated per event — every
  value change is an event, hence the values are exact (a byte only first
  observed later can appear as known in both views)
- tracker: change events carry `observed_ranges` (bytes that were unknown
  before the operation) next to `ranges`; an update that only observes new
  bytes now records an event (so it can be inspected); record-file ranges are
  in-record byte offsets (the record number stays in its own field), which the
  previous "record number as position" could not express for a hex highlight
- tests: change-images reconstruction (before/after per event, later changes
  undone, records isolated, observation-only events, unknown gaps), the image
  endpoint (200/404/400), and Node tests for the hex-image helper (highlight
  classes, unknown gaps, ASCII, row limit) and the modal rendering
- v2.4.0; SW cache simtrace-analyser-v65
2026-10-17 21:19:14 +03:00
catarrh 87b6811adf Files view: surface fetch errors instead of "no operations" (v2.3.1)
- `API._fetch` never checked the HTTP status: an error body (e.g. the 404
  `{"error": "Session not found"}`) was returned as data, so the Files view
  rendered it as "No file operations recorded" — and, worse, that bad payload
  was cached per session id, which is why only re-opening the session (which
  resets the cache) appeared to fix it.  It now throws with the server's
  message for any non-OK response (all 13 `await API.*` call sites are inside
  try/catch) and marks the server unavailable only for a real network error
- `renderFileView` accepts a payload only when it carries a `timeline` array:
  a genuine `timeline_total === 0` keeps the empty state, anything else shows
  "Failed to load files: <reason>"; failures are not cached, so the next visit
  retries; a response that arrives after the user switched sessions is ignored
- tests: HTTP-error vs network-error behaviour of `_fetch` (network error is
  the only disconnected state), the valid/empty/error render paths and the
  retry-after-error, the stale-response guard
- v2.3.1; SW cache simtrace-analyser-v64
2026-10-17 21:04:03 +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 28bc8f1f24 Import COMPRION trace exports (Protocol Layer + Application Layer time base)
- new `simtrace_analyser/comprion.py`: parses the tab-separated ISO Protocol
  Layer export (metadata block, ATR/PPS exchanges, APDUs with continuation
  rows, delays in etu) and re-encodes every exchange into the wire-shaped
  TPDUs the analyser stores — a case-4 exchange becomes the command, a
  fabricated `61 <len>` status word and a GET RESPONSE (command's logical
  channel preserved), so command/response pairing, FCP/file decoding and the
  GET RESPONSE filters work unchanged; Cold reset / Power off become RST/VCC
  line events, deliberately without the sigrok CLK extension (the export
  carries no clock measurement)
- optional matching **ISO Application Layer** export as the exact time base:
  its one `Time:` row per exchange (ms precision) replaces the delay estimate
  (0-based elapsed, `started = Session Name + first Time`), guarded by a
  session-identity check (`Session Name`, falling back to `Working
  directory`; the two layers' export timestamps differ and are not compared)
  and an exchange-count check — mismatched files are refused, never mixed
- protocol-only imports keep the Delay-based timeline: card/line activity
  time (host pauses absent), the unit assuming 3.8 MHz (`CLK_HZ`); the
  estimate is not fed into the CLK/RST line-event annotation
- `POST /api/sessions/import?format=comprion` (raw text body, or a JSON
  `{text, times_text}` bundle) with stable error codes (app_layer /
  not_comprion / unsupported_layout / no_data / times_unparsable /
  session_mismatch / time_base_unverified / time_base_mismatch); a COMPRION
  file dropped on the PCAP path answers with a pointer (comprion_export);
  session mode `comprion`, window from the Session Name
- PWA: import dialog with two separate pickers (Protocol Layer required,
  Application Layer optional) whose hint switches between "activity time"
  and "exact timestamps"; errors stay in the dialog with both selections
  until fixed or cancelled (Cancel/Esc, also disabled while importing);
  EN/RU
- tests: tests/test_comprion.py (parser, wire encoding, time base + guards,
  import endpoint) and frontend/tests/comprion.test.js (file checks, slot
  validation, dialog state machine with a DOM stub, RU coverage)
- v2.2.0; MIN_SERVER_VERSION 2.2.0; SW cache simtrace-analyser-v61
2026-10-17 00:45:15 +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 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 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 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