From cad3999307ab80ec4db68479727f77501b512c1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BD=D1=82=D0=BE=D0=BD=20=D0=A2=D1=80=D0=BE=D1=88?= =?UTF-8?q?=D0=B8=D0=BD?= Date: Sat, 17 Oct 2026 21:19:14 +0300 Subject: [PATCH] Files view: change details with before/after images (v2.4.0) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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//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 --- README.md | 17 ++- README_RUS.md | 18 ++- frontend/index.html | 177 ++++++++++++++++++------ frontend/sw.js | 2 +- frontend/tests/comprion.test.js | 99 +++++++++++--- pyproject.toml | 2 +- simtrace_analyser/database.py | 11 +- simtrace_analyser/filetrack.py | 149 +++++++++++++++++--- simtrace_analyser/server.py | 26 ++++ simtrace_analyser/version.py | 2 +- tests/test_filetrack.py | 232 +++++++++++++++++++++++++++----- tests/test_pcap_import.py | 2 +- 12 files changed, 617 insertions(+), 120 deletions(-) diff --git a/README.md b/README.md index ba43b57..2a75795 100644 --- a/README.md +++ b/README.md @@ -191,9 +191,20 @@ in the stored wire shape and are skipped with a note in the import report. The Trace tab's side panel has a view selector (*Map* / *Stats* / **Files**; a drop-down, since three tab captions no longer fit). The Files view is a trace-wide timeline of file operations — every **Read**, **Update** and -**Changed (card)** row shows the same file name as the Map view, and clicking -a row jumps to the APDU in the trace. A *Changed (card)* row carries a Δ -button that opens the changed ranges with the previous and the new bytes. +**Changed on card** row shows the same file name as the Map view, and clicking +a row jumps to the APDU in the trace. Rows that carry a recorded change (a +card-side change, or an update that wrote or first observed bytes) also offer +**Details**: a modal showing the affected unit — the whole file, or the single +record that changed — **before and after** the change, as hex views (16 bytes +per row with an ASCII column, never-observed bytes drawn as `--`) with the +bytes that changed highlighted in one colour and the bytes the operation first +observed in another, plus the changed/new byte counts and the APDU reference. + +The images come from `GET /api/sessions//files/image?key=…&change=…` and +are reconstructed from the current image by undoing the later changes +(`filetrack.FileTracker.change_images`) — nothing is duplicated per event. +Every value change is recorded, so the values are exact; a byte that was only +*first observed* after the selected change can appear as known in both views. The data comes from `simtrace_analyser/filetrack.py` (`GET /api/sessions//files`), a single-pass tracker over the decoded diff --git a/README_RUS.md b/README_RUS.md index 6a42a34..b95b16e 100644 --- a/README_RUS.md +++ b/README_RUS.md @@ -196,9 +196,21 @@ Application Layer* — два отдельных поля выбора, имен В боковой панели вкладки «Трассировка» появился выбор вида (*Map* / *Stats* / **Files** — выпадающий список, три подписи-вкладки уже не помещались). Вид Files — это таймлайн файловых операций по всей трассе: строки **Read**, -**Update** и **Changed (card)** показывают те же имена файлов, что и вид Map, -а щелчок по строке переходит к APDU в трассе. У строки *Changed (card)* есть -кнопка Δ, открывающая изменённые диапазоны с прежними и новыми байтами. +**Update** и **Changed on card** показывают те же имена файлов, что и вид Map, +а щелчок по строке переходит к APDU в трассе. У строк с записанным изменением +(изменение со стороны карты либо запись, изменившая или впервые показавшая +байты) есть кнопка **Details**: окно показывает затронутую единицу — файл +целиком или изменившуюся запись — **до и после** изменения в виде hex-вида +(16 байт в строке со столбцом ASCII, никогда не наблюдавшиеся байты — `--`), +причём изменённые байты подсвечены одним цветом, а впервые наблюдаемые — +другим; ниже — счётчики изменённых/новых байт и ссылка на APDU. + +Изображения отдаёт `GET /api/sessions//files/image?key=…&change=…`; они +восстанавливаются из текущего образа отменой более поздних изменений +(`filetrack.FileTracker.change_images`) — поэтому ничего не дублируется по +событиям. Каждое изменение значения записывается, так что значения точны; +байт, который был лишь *впервые показан* после выбранного изменения, может +отображаться известным в обоих видах. Данные даёт `simtrace_analyser/filetrack.py` (`GET /api/sessions//files`) — однопроходный анализатор сессии, который ведёт по ключу (DF, FID) и **через diff --git a/frontend/index.html b/frontend/index.html index 46b3edd..25392c0 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -188,13 +188,25 @@ body { font: 14px/1.5 system-ui, sans-serif; background: var(--bg); color: var(- .file-op-read { color: var(--text-muted); } .file-op-update { color: var(--accent); } .file-op-card { color: var(--warn); } -.file-op .file-op-diff { margin-left: auto; background: none; border: none; color: var(--warn); cursor: pointer; font-size: 12px; padding: 0 2px; flex: 0 0 auto; } -.file-op .file-op-diff:hover { color: var(--accent); } -.file-diff-modal { max-width: 640px; } -.file-diff-body { max-height: 50vh; overflow-y: auto; margin-bottom: 16px; } -.file-diff-body table { font-size: 12px; } -.file-diff-body td { padding: 2px 8px 2px 0; vertical-align: top; } -.file-diff-body .file-diff-hex { font-family: ui-monospace, monospace; word-break: break-all; } +.file-op .file-op-diff { margin-left: auto; background: none; border: none; color: var(--accent); cursor: pointer; font-size: 12px; padding: 0 2px; flex: 0 0 auto; text-decoration: underline; } +.file-op .file-op-diff:hover { color: var(--warn); } +.file-diff-modal { max-width: 760px; } +.file-diff-body { max-height: 60vh; overflow-y: auto; margin-bottom: 16px; } +.hex-legend { display: flex; align-items: center; gap: 6px; font-size: 12px; color: var(--text-muted); margin-bottom: 6px; flex-wrap: wrap; } +.hex-summary { font-size: 12px; color: var(--text-muted); margin-bottom: 10px; } +.hex-pair { display: flex; gap: 16px; flex-wrap: wrap; } +.hex-pair > div { flex: 1 1 320px; min-width: 0; } +.hex-pair h4 { font-size: 12px; text-transform: uppercase; letter-spacing: 1px; color: var(--accent); margin-bottom: 4px; } +.hex-view { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12px; line-height: 1.45; white-space: pre; overflow-x: auto; } +.hex-row { display: flex; align-items: baseline; } +.hex-addr { color: var(--text-muted); margin-right: 8px; } +.hex-cell { padding: 0 1px; border-radius: 2px; } +.hex-cell.hex-changed { background: var(--warn); color: #1a1a1a; font-weight: 600; } +.hex-cell.hex-new { background: var(--accent); color: #fff; font-weight: 600; } +.hex-cell.hex-unknown { color: var(--text-muted); } +.hex-ascii { color: var(--text-muted); margin-left: 8px; } +.hex-note { font-size: 12px; color: var(--text-muted); margin-top: 6px; } +.hex-error { font-size: 12px; color: var(--error); } .map-item { padding: 3px 8px; cursor: pointer; border-radius: 4px; font-size: 13px; color: var(--text); word-break: break-word; } .map-item:hover { background: var(--bg-input); } .map-files-toggle { color: var(--accent); cursor: pointer; } @@ -438,7 +450,7 @@ body { font: 14px/1.5 system-ui, sans-serif; background: var(--bg); color: var(-