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
This commit is contained in:
1 parent
b8d9c4cc63
commit
c494bf4111
14 files changed
+1302
-36
No files matched your search
@@ -124,7 +124,7 @@ test('the COMPRION import is a two-slot dialog', () => {
|
||||
assert.match(src, /time_base === 'application-layer'/);
|
||||
assert.match(src, /\$on\('import-comprion-btn', 'click', openComprionDialog\)/);
|
||||
assert.match(src, /\$on\('comprion-cancel', 'click', closeComprionDialog\)/);
|
||||
assert.match(src, /e\.key === 'Escape'/);
|
||||
assert.match(src, /e\.key !== 'Escape'/);
|
||||
});
|
||||
|
||||
test('comprionSlotCheck: slot/kind outcomes', () => {
|
||||
@@ -275,3 +275,65 @@ test('COMPRION dialog: success closes, server errors stay', async () => {
|
||||
|
||||
closeComprionDialog();
|
||||
});
|
||||
|
||||
// ── Files view (per-file operation timeline) ───────────────────────────
|
||||
eval(
|
||||
'function t(x) { return x; }\n' +
|
||||
extractFunc(src, 'fileOpClass') + '\n' +
|
||||
extractFunc(src, 'fileOpVerb') + '\n' +
|
||||
extractFunc(src, 'fileOpLabel') + '\n' +
|
||||
extractFunc(src, 'fileDiffRows')
|
||||
);
|
||||
|
||||
test('Files view: operation kinds map to classes and verbs', () => {
|
||||
assert.equal(fileOpClass('read'), 'file-op-read');
|
||||
assert.equal(fileOpClass('update'), 'file-op-update');
|
||||
assert.equal(fileOpClass('card_change'), 'file-op-card');
|
||||
assert.equal(fileOpVerb('read'), 'Read');
|
||||
assert.equal(fileOpVerb('update'), 'Update');
|
||||
assert.equal(fileOpVerb('card_change'), 'Changed (card)');
|
||||
assert.equal(fileOpLabel({kind: 'read', file: {name: 'EF_ICCID'}}), 'Read EF_ICCID');
|
||||
assert.equal(fileOpLabel({kind: 'card_change', file: {name: 'EF_LOCI'}}),
|
||||
'Changed (card) EF_LOCI');
|
||||
assert.equal(fileOpLabel({kind: 'read', file: null}), 'Read ?');
|
||||
});
|
||||
|
||||
test('Files view: the diff rows slice the concatenated hex', () => {
|
||||
const change = {
|
||||
ranges: [[8, 2], [16, 1]],
|
||||
before_hex: '95f9aa',
|
||||
after_hex: '000001',
|
||||
};
|
||||
assert.deepEqual(fileDiffRows(change), [
|
||||
{pos: 8, len: 2, before: '95f9', after: '0000'},
|
||||
{pos: 16, len: 1, before: 'aa', after: '01'},
|
||||
]);
|
||||
assert.deepEqual(fileDiffRows({ranges: [], before_hex: '', after_hex: ''}), []);
|
||||
});
|
||||
|
||||
test('Files view: side-panel dropdown and the timeline are wired', () => {
|
||||
assert.match(src, /<select class="side-view-select" id="side-view-select"[^>]*>/);
|
||||
assert.match(src, /<option value="files" data-l10n="Files">/);
|
||||
assert.doesNotMatch(src, /class="side-tab/);
|
||||
assert.match(src, /id="file-view"/);
|
||||
assert.match(src, /id="file-diff-modal"/);
|
||||
assert.match(src, /id="file-diff-body"/);
|
||||
assert.match(src, /\$on\('side-view-select', 'change'/);
|
||||
assert.match(src, /\$on\('file-view', 'click'/);
|
||||
assert.match(src, /API\.getSessionFiles\(STATE\.sessionViewId\)/);
|
||||
assert.match(src, /if \(row\) jumpToMessage\(Number\(row\.dataset\.idx\)\);/);
|
||||
assert.match(src, /getSessionFiles\(id\)\s*\{ return this\._fetch\('GET', `\/api\/sessions\/\$\{id\}\/files`\); \}/);
|
||||
});
|
||||
|
||||
test('Files view: strings carry Russian translations', () => {
|
||||
const lang = extractConst(src, 'LANG_RU');
|
||||
const keys = new Set();
|
||||
for (const m of lang.matchAll(/'((?:[^'\\]|\\.)*)'\s*:/g)) {
|
||||
keys.add(m[1].replace(/\\(['\\])/g, '$1'));
|
||||
}
|
||||
for (const s of ['Files', 'Read', 'Update', 'Changed (card)', 'Card-side change',
|
||||
'Close', 'Offset', 'Record', 'Before', 'After', 'Show the change',
|
||||
'No file operations recorded', 'Failed to load files', 'Loading…']) {
|
||||
assert.ok(keys.has(s), 'no LANG_RU entry for: ' + s);
|
||||
}
|
||||
});
|
||||
Reference in new issue
Block a user