The UI only noticed a removed card when some user action ran a real card
command (e.g. Check status); /api/status is a cached-state read that kept
returning the old card, and _handle_card_disconnect() did not clear
app.card/rs.
- start_card_monitor() registers a pyscard CardObserver for our reader;
it only polls SCardGetStatusChange (no APDU, no connection, no extra
process), and on removal sets server.card_present=False and calls
_handle_card_disconnect() under _CARD_LOCK
- /api/status now exposes connected (session usable) and card_present
(physically inserted) and masks card/profile/atr/selection when not
connected; _CARD_CONNECTED is initialized from card presence instead of
being unconditionally True
- the 2s UI poll includes /api/status; on disconnect it switches to the
existing 'No card detected. Insert card and click Equip' state, or the
new 'Card inserted — press Equip' hint when the card is back; the old
_hadData heuristic is gone
Tests for the observer (filtering, removal, insertion) and the UI state
transitions. SW cache v90 -> v91.
New Compare snapshots button on the Card snapshots tab opens a dialog
with Master snapshot / Snapshot to check selects and the two mask
options (Match first 4 bytes for EF.IMSI / EF.ICCID, checked by
default). The comparison runs like a profile check where the master
snapshot takes the place of the profile: every file must match exactly
(exact FCI, contents), except the first 4 bytes of masked EF.IMSI/EF.ICCID.
profilerRulesFromSnapshot() synthesizes exact-match rules from the
master; profilerSnapshotSource() of the checked snapshot is the data
source; files present only in the checked snapshot are appended as
failed 'extra file' results via profilerExtraFileResults() and the new
optional extraResults param of profilerRunProfile(). Results reuse the
existing view, summary and Only mismatches filter; Back to list returns
to the snapshots tab. Tests for rule synthesis, masking, comparison and
extra files. SW cache v87 -> v88.
The results view header (live card and snapshot checks) gets an Only
mismatches checkbox that hides all passing files and keeps only failures
and errors; when everything passes under the filter, a 'No mismatches'
note is shown instead. The pass/fail/error summary always reflects all
results. Helps with large profiles on cards where most files match.
SW cache v86 -> v87.
Each profile row gets a Check card snapshot button next to Check card.
It opens a picker listing the stored snapshots (name, ICCID, date, file
count) and runs the profile rules offline against the selected snapshot,
showing the usual report titled 'profile — snapshot'.
profilerRunRule() accepts an optional data source: profilerLiveSource()
(default) wraps /api/select + /api/read, profilerSnapshotSource() serves
select/read from the snapshot files. Contents missing from the snapshot
are reported as unverifiable errors ('Content not captured in snapshot')
rather than mismatches. The shared check loop moved into
profilerRunProfile().
Tests for the snapshot source and snapshot-based checks; help docs
updated; SW cache v84 -> v85.
translatePage() only rewrites static [data-l10n] elements, so labels built
at render time (Check card/Edit/Export/Delete, Open, Remove, Send, ...)
stayed in the old language. toggleLang() now calls refreshDynamicI18n(),
which re-renders only the visible dynamic views:
- profiler sub-tab: current view (list, snapshot list, editor, results via
the new profilerRenderResultsView with profilerResults stored, snapshot
via profilerRenderSnapshotData which leaves the name input alone)
- open scan modal: profilerScanRefreshOptions re-translates the mask labels
without touching checkbox state
- cards table (cardsRender), proactive events/log/PLI (async, only when
the sub-tab is visible)
Also removed the duplicated translatePage() call in toggleLang.
New test for profilerScanRefreshOptions. SW cache v83 -> v84.
The list view now has a centered pill row (Profiles | Card snapshots) and
shows one list at a time; the last active tab persists while the app is
open. Snapshot flows (scan, import) switch to the snapshots tab, profile
import switches to profiles. Help docs reworded (tabs). New DOM test for
profilerListSwitch. SW cache v81 -> v82.
New read-only capture entity stored in localStorage 'otaman_snapshots':
- profiler list view is now two labelled columns: Profiles and Card
snapshots; each snapshot row shows name, decoded ICCID, date, file
count and Open / Export / Delete buttons
- New snapshot reuses the scan modal in a snapshot mode (name only, no
ignore/mask/FCP options) via profilerScanCard(..., 'snapshot') ->
profilerBuildSnapshotFile: metadata + raw FCI + exact contents for
every readable file (no ignore list, no masking). The scan returns to
the list afterwards.
- ICCID is decoded from EF.ICCID (2FE2) with decIccid() (nibble-swapped
E.118 digits, F pad per TS 102 221 13.2) and stored as an immutable
snapshot field, shown next to the name in the list and the view.
- Open shows all captured data read-only (attributes, raw FCI + decoded
FCI, contents or 'Not captured'); only the name is editable/saveable.
- Import/Export/Delete + profilerValidateSnapshot; quota-safe save.
Tests: decIccid, profilerSnapshotIccid, validation, snapshot builder
(exact contents/no mask/unreadable), snapshot-mode scan with ICCID.
Docs synced (help/help-ru). SW cache v79 -> v80.
Adds an 'All' header checkbox next to 'Ignore contents of files:' that
checks or unchecks every file; it reflects the list state (checked only
when all are checked, indeterminate on a mixed selection) and updates on
individual checkbox changes. RU label: 'Все'. Docs synced, tests for the
toggle/state helpers. SW cache v77 -> v78.
fcpDecode now parses with a partial-aware walker (fcpParseTlvs) that keeps
every complete TLV it encounters and reports why it stopped:
- 'TLV 62 declares N bytes, only M available' (truncation)
- 'Truncated length field at offset X' / 'incomplete TLV header'
- 'Invalid length form' / 'Trailing data after TLV 62'
- inner A5/C6 errors prefixed with their context ('In A5: ...')
The editor preview shows the decoded parameters plus a red
'Decode failed: <reason>' line (RU: 'Ошибка декодирования'), and the check
report's decoded FCI diff appends per-side error notes (expected/actual)
while still showing whatever decoded on either side.
SW cache v74 -> v75.
parseBerLen treated the first long-form length byte (0x81/0x82/...) as the
count of length bytes, so any TLV with a long-form length parsed as
garbage: the profiler FCI decoder returned ok:false (decoded preview
disappeared and never came back after editing an FCP with a '62 81 xx'
outer length or a long-form inner TLV), and the same bug hit parseTlvList
(C-APDU parser, INSTALL param walker), parseBerScript (expanded script
rows >= 128 bytes) and readLvField.
Per ISO 7816-4 5.2 / UICC_SPECS.md 1.7 the long form is 81-84 followed by
(b & 0x7F) length bytes. Fixed; short-form behavior unchanged.
New tests: parseBerLen short/81/82 forms, parseTlvList long-form outer and
inner TLVs, fcpDecode long-form regression (81/82, nested A5, >=128-byte
FCP) incl. the editor preview path. SW cache v73 -> v74.
The exact-FCI row is now a two-column flex layout: the editable raw FCI
textarea on the left, the decoded FCP list on the right (50/50, textarea
4 rows). Preview rendering split into profilerFciPreviewItems; a new
profilerFciInput hook updates the rule and re-decodes the panel on every
keystroke (no editor re-render, no focus loss), showing an empty body
while the hex is incomplete/invalid. Docs wording updated. SW cache
v72 -> v73.
Adds a spec-verified FCP/FCI decoder (ISO 7816-4 5.3.3 Tables 12-14,
TS 102 221 11.1.1.4; cross-checked against pySim ts_102_221.py — spec wins
on the data coding byte and termination mask). fcpDecode accepts the FCP
template '62', an FCI '6F' wrapper and bare FCP content, and decodes:
file size/total size, file descriptor (access/shareable, file type,
structure incl. BER-TLV/SIMPLE-TLV, data coding byte, record length/count),
FID, DF name, SFI, life cycle status, security attributes, the A5
proprietary sub-DOs (UICC characteristics, power, clock, memory, file
details, sizes, commands, environmental conditions, test config) and the C6
PIN status template DO. Unknown TLVs are preserved raw.
Usage:
- check report: an 'Exact FCI' mismatch now renders a decoded
expected/actual table under the raw fields, highlighting differing
parameters and showing missing ones as '—'
- rule editor: the FCI hex textarea shows a live decoded FCP preview
10 new tests (vectors incl. pySim linear-fixed, malformed inputs, diff
highlighting, report integration). Help docs updated. SW cache v71 -> v72.
The 'matching records: N-M' note was suppressed when every compared record
matched but the record count differed (e.g. 30 expected vs 10 read): the
renderer only looked for failed per-record checks, so the report showed
just the count lines. profilerRunRule now sets res.recordsMismatch whenever
the count or any record differs, and the renderer uses it (with the old
per-record check as fallback), so the matching-record ranges are listed in
that case too. Tests cover the count-only mismatch and the per-record case.
SW cache v65 -> v66.
FCI and content (incl. record) mismatches are now rendered as two read-only
monospace inputs — expected on top, actual directly beneath — sharing a
fixed-width right-aligned label column (w-24) so both fields start at
exactly the same horizontal position and stretch to fill the row
(flex-1/min-w-0, no overlap). A title attribute shows the full value on
hover. Non-raw checks (fileSize, recordLen, numRecords, content.records,
read failure) keep the inline line.
New profilerRawDataCheck helper; 2 new tests. SW cache v59 -> v60.
A record file with both a numRecords metadata check and record content
checks reported the same count mismatch twice (numRecords and
content.records). profilerRunRule now omits the content.records line when
the numRecords check already covers it, keeping it only when no numRecords
check ran (FCP/FCI 'type' mode or numRecords cleared) or when numRecords
passes but the read returns a different count.
3 new tests. SW cache v58 -> v59.
Profile-from-card scan form gains two checked-by-default options
'Match first 4 bytes for EF.IMSI / EF.ICCID', surfacing the previously
hardcoded mask behavior. Unchecking captures those files' contents
exactly. profilerBuildFileRule now takes a maskFids set (undefined keeps
the legacy mask default); scan start collects it and threads it through
profilerScanCard.
While a scan runs the options form (ignore list, mask options, FCP/FCI
selector) is hidden, Cancel and Scan are disabled (with visible
disabled styling), and the profile-name input becomes read-only — fixing
the latent cancel-mid-scan bug that left the scan running and popped the
editor open. profilerFromCard performs a full fresh-open reset.
4 new tests (mask/exact/legacy + scan threading). RU i18n key, help docs
synced. SW cache v57 -> v58.
The Check report now states exactly what was verified for each file:
- passing files append a plain summary to the path line
('filetype and size, contents' / 'filetype and records, contents' /
'Exact FCI, contents')
- mixed results mark each aspect inline
('filetype ✓, size ✗, contents ✓'), with the existing red detail lines
for the mismatches; 'Exact FCI' subsumes type/size/records
- record files with a contents mismatch add a 'matching records: 1-5,
7-10' note listing the records that did match
New pure helpers: profilerResultAspects (derives checked aspects from the
result checks), profilerAspectSummary (plain vs marked text),
profilerNumRanges (compresses record numbers into ranges). profilerRunRule
now also returns res.recordsMatched. 6 new tests incl. an end-to-end report
render. RU i18n keys added. Help docs synced. SW cache v56 -> v57.
Split profilerScanCard into two phases so the file total is known up
front: phase 1 walks the filesystem (tree calls only) collecting every
file entry, phase 2 builds a rule per file. The optional onProgress(done,
total, path) callback reports 'N / total files (%) — path'; the scan
dialog shows it under the FCP/FCI selector, starting with 'Discovering…'
during phase 1. No extra card I/O (tree calls are no longer interleaved
with select/read). Backward compatible: onProgress is optional.
2 new tests (progress sequence 0/total then 1..total with paths; scan
without callback). RU i18n keys (Discovering..., files). Help docs synced.
SW cache v53 -> v54.
Each rule now carries an fciMode ('type' | 'type_size' | 'exact') plus an
fciHex (raw SELECT response) so rules can verify different depths of file
control information:
- Filetype only (FCP): exists + file type
- Filetype + size (FCP): adds file size (or record length/count) - previous
behavior
- Exact FCI: adds byte-for-byte comparison of the raw SELECT response (the
FCP '62' template), catching FID/AID, life-cycle, security-attribute and
proprietary-parameter changes
/profile from card/ gains a matching FCP/FCI selector (default Filetype +
size); rules store fciMode and always capture fciHex so they can be
upgraded to Exact FCI in the editor without rescanning. The rule editor
adds the selector, hides size/record fields in 'type' mode and shows an
editable FCI hex textarea in 'exact' mode. Contents checks stay independent.
Server /api/select now returns fci_hex (raw FCP template hex, uppercased).
profilerValidateProfile accepts the new mode; profilerNormHexStrict added
for byte-exact comparison (no '?' wildcards). Legacy rules without fciMode
default to type_size.
10 new tests (build-rule fields, run-rule modes incl. byte compare +
missing-FCI, validation). Docs + RU i18n synced. SW cache v52 -> v53,
version 1.9.28.
Rules now persist the pySim symbolic name captured during 'Profile from
card' (profilerBuildFileRule stores c.name || sel.name), and the profile
edit view renders it next to the Path label using the same lookup order
as the check view: profilerCustomNameForPath(path) || rule.name.
Path edits call the new profilerUpdateRulePath, which drops the stale
scan-time name and refreshes the label from the custom-files dictionary
live, so the displayed name always corresponds to the current path.
Manually added rules get name: null (resolved via custom-file lookup).
Old profiles without rule.name still work.
4 new tests (rule.name from child/select/null, custom name lookup, path
edit clears name + refreshes label). SW cache v51 -> v52.
The 'Profile from card' ignore checkboxes matched only by FID, but the
list had EF.KcGPRS at 4F52 (TS 31.102 DF.GSM-ACCESS) while the live card
exposes it at 6F52 (TS 51.011 DF.GSM, verified in TS 51.011 v4.15.0
10.3.32 and the DF.GSM allocation table). The miss made profilerBuildFileRule
capture full contents (Exact) for a file the user had checked to ignore.
- Correct EF.KcGPRS FID to 6F52 (spec-verified)
- Match ignores by FID OR by pySim name: each checkbox now carries
data-ignore-name, profilerScanStart builds an ignoreNames set, and
profilerBuildFileRule checks both. Covers FID variants (6F52 vs 4F52)
and future constant typos; ignoreNames is optional for back-compat.
- 6 new tests: ignore-list FID/name sanity (incl. KcGPRS=6F52), duplicates,
ignored-by-FID, ignored-by-name-only regression case, non-ignored control,
back-compat when ignoreNames is omitted.
SW cache v50 -> v51.
- Seed one empty record row when record content is created or reset
(filetype change or Contents None->Exact/Mask) via profilerEmptyRecordContent
- Add per-record delete and '+ Record' add controls so the record view is
always editable; delete is hidden on the last remaining row
SW cache v47.
- Show Size only for transparent/ber_tlv; Record length/count only for
linear_fixed/cyclic; hide all size/record fields for df (unknown shows all)
- Hide the Contents section entirely for df (existence-only check)
- Changing file type clears now-inapplicable fields and resets content
(keeps mode) when its kind switches between record and transparent
- profilerSetContent defaults content kind from the selected file type
Add profilerFileFields + profilerContentKindForFileType helpers + tests.
SW cache v45.
When generating a ruleset from the card, capture EF.IMSI (6F07) and
EF.ICCID (2FE2) contents as a mask matching only the first 4 bytes
(e.g. 08290591??????????), the rest are '?' wildcards.
Add profilerMaskPrefix4 helper + PROFILER_MASK_PREFIX4_FIDS map + tests.
SW cache v44.
When a declared fileSize/recordLen/numRecords attribute mismatches, compare
file contents only over the overlapping (shorter) portion, so a length
difference alone doesn't fail the content check when the common bytes match.
When sizes match, contents are compared fully as before.
Add profilerMatchMin helper + tests. SW cache v43.
New 'Profiler' sub-tab in card-reader view:
- Named rulesets (profiles) persisted in localStorage; each has an ordered
list of filesystem rules (type 'file', extensible to OTA/TAR checks later)
- List page: New profile / Profile from card / Import profile + per-profile
Edit / Check / Export / Delete
- Editor page: inline rule fields (path, file type, size, record length,
record count, contents with exact/mask/'?'-wildcard modes)
- Results page: sequential rule checks with live progress and a pass/fail
report (existence + FCI attributes + content match)
- 'Profile from card' scans the equipped card; creates a rule only for files
that exist (FCI present). A scan-options dialog lets the user skip contents
of frequently-overwritten dynamic files (LOCI/PSLOCI/EPSLOCI/5GS3GPPLOCI/
Keys/KeysPS/SMS/Kc/KcGPRS/LOCIGPRS/CBMID/SMSS), checked by default
- ADF-rooted paths use the AID; paths resolve via a new server _select_path
Server:
- _select_path() resolves MF/ADF-AID-rooted paths (pySim can't select ADF by AID)
- /api/select and /api/read accept 'path'; /api/select returns file_size,
record_len, num_of_rec
Also fix: silent SELECT (P2=0x0C) no longer emits a trailing Le byte
(chain builder) - matches ETSI TS 102 221 silent-select behavior.
Tests: +profiler.test.js; frontend 117 pass, Python 61 pass.
SW cache v37.
Key fix: re-add chained C0000000 (GET RESPONSE Le=00) in paginate() so the
full GP APDU inside SCP80 is 80F2<p1>p2024F0000C0000000, matching the
working tool. The card's SCP80 layer executes both commands internally
(GET STATUS → 61XX → GET RESPONSE) and puts the final 9000 + data in
the PoR.
Also: server logging improvements (RAM RESPONSE-PACKET label, no truncation
of FETCH/PoR hex), docs for /api/ram-install endpoint, minor test fix.
OTA inner-APDU SWs from SIM-domain applications surfaced as 'Unknown
status word' (e.g. 9404 on a failed SELECT inside an authenticated
B00000 packet). SW_MAP.generic gains the complete TS 51.011 §9.4
families, so they decode in every resp-cmd context:
- 9200/9240 memory management (retry / memory problem)
- 9400/9402/9404/9408 referencing management (no EF selected,
out of range, file ID/pattern not found, file inconsistent)
- 9802/9804/9808/9810/9840/9850 security management (CHV/access-
condition/invalidation contradictions and blocked states; 9850
also added to generic alongside its gp/uicc copy)
- 9EXX/9FXX data-download error length / response length wildcards
Exact keys safely coexist with the 92XX proactive wildcard (exact
match wins). TS 102 221 sweep found no further stragglers: 63C0-C9
PIN retry counters are covered by the existing 63CX wildcard.
Secured packet page: enforce fresh packet per send
- any change to APDU/TAR/CNTR/keys/padding/SPI now clears the secured
packet textarea via spInvalidate(), so Send cannot reuse a stale
packet ('No secured packet to send' guards the path)
- on por_ok the CNTR field auto-increments (10-digit hex normalized,
wraps modulo 2^40) and the textarea clears; security-error verdicts
leave both untouched for retry after fixing the cause
SIM/USIM SELECT: P1/P2 per spec + live RFM idioms
- USIM FID selects requested no response data (P2=0C) - that coding is
reserved for the select-MF-by-empty-data special case; ordinary FID
selects now request the FCP template (P2=04) with Le='00' per
TS 102 221 Table 11.2 / pySim sel_ctrl convention; preset tables
fixed in both genSimUsim and updateP1P2Display
- path method gains base selector: from MF (P1=08) / from current DF
(P1=09) - reproduces the dominant live RFM idiom 09/0C
- 'silent' checkbox on fid/path/chain emits P2=0C without Le for hops
where the FCI is not needed
- chain syntax gains GET RESPONSE hops: 'C0' emits CLA C0 00 00 00 and
'C0:NN' sets explicit Le, enabling classic SELECT -> 9FXX ->
GET RESPONSE pairs in a single secured packet so the PoR carries the
actual FCI/response bytes instead of a bare length SW
- SW dictionary: 9FXX reworded to point at GET RESPONSE
- placeholders show the new chain syntax
- findings & backport decisions written to ~/WSL/RFM_notes.md
response_map.test.js: +8 assertions across the new families.
sim.test.js: USIM FID expectation updated; silent/base/chain cases.
apdu_parse.test.js: live capture lines 1-3 as regression fixtures.
Version 1.9.5 -> 1.9.6 everywhere; SW cache otaman-v16 -> otaman-v17
pySim encode_cmd transmits the 2-byte CPL only when ciphering is
applied; unciphered packets start at CHL. genSp always emitted it,
so every SPI1=0x00 packet diverged from the pySim reference at byte 0
(sp-verify MISMATCH) and carried a length octet pair real cards need
not expect.
- genSp output: bytesToHex(ciphering ? packet : packet.subarray(2));
MAC input unchanged (still covers the virtual-CPL frame, matching
pySim's sign-then-strip convention)
- Secured packet page: PoR verdict now rendered prominently in a
dedicated text-base semibold line ('PoR: por_ok' green / other
statuses red) above the small detail line; hidden when no PoR was
requested or the ENVELOPE failed
- sp.test.js: three unciphered expectations updated to CHL-first form;
ciphered vectors untouched (byte-identical)
- version 1.9.4 -> 1.9.5 everywhere; SW cache otaman-v15 -> otaman-v16
- PARSE_INS CB: rename 'RETRIEVE DATA (GET DATA)' -> 'RETRIEVE DATA'
(CA = GET DATA, CB = RETRIEVE DATA are separate commands per TS 102 221)
- Add trailing newlines to sim.test.js + response_map.test.js
SIM/USIM encoder (registry §2.3):
- New PIN commands: DISABLE '26' / ENABLE '28' (single FF-padded PIN,
Lc=08) and UNBLOCK '2C' (unblock+new, Lc=10); dynamic field labels
- ACTIVATE/DEACTIVATE FILE target selection: Current EF (case 1),
by FID (P1=00), path from MF (P1=08), path from current DF (P1=09)
with Lc+FID/path data form
- LANG_RU entries for new labels
Tests:
- sim.test.js: new stub-DOM genSimUsim harness; exact-hex for all PIN
ops, ACTIVATE/DEACTIVATE forms, SELECT Le rules, RECORD P1=00 rule
- response_map.test.js: lookupSw wildcards (91XX / 63CX), LIFECYCLE_MAP
per GPC v2.3, PRIVILEGE_NAMES vs Tables 11-7/11-8/11-9
GET STATUS E3 template decoding verified already present
(TLV_TAG_NAMES + decodeTlvValue cover 4F/9F70/C5/CF/C4/CC/CE/84).
Version 1.9.1 -> 1.9.2, SW cache otaman-v12 -> otaman-v13
Parser labels (§8.3, verified against GPC v2.3 / TS 102 221 PDFs):
- describeP1P2: SELECT P1/P2 (FID/DF-name/path-MF/path-DF; FCP/no-data),
READ/UPDATE RECORD modes (next/previous/absolute + SFI, P1-ignored note),
GET STATUS P1 (ISD/Apps/ELF/ELF+Modules) and P2 formats,
INSTALL P1 bit-aware roles ('for install + for make selectable'),
SET STATUS P1 (80 ISD / 40 App-or-SSD / 60 SD+associated) with
card states vs lock/unlock P2, VERIFY/CHANGE PIN ref
Parser structure (§8.4):
- describeInstallDataLv: exact-sum LV walker for all 5 INSTALL layouts
with privilege bit names and params tag nesting (C9, EF->CA, EA->80);
falls back to legacy TLV view when lengths do not sum exactly
- GET DATA: case-2 (P3=Le) vs case-4 (Lc + tag list + Le)
- SET STATUS data: 'ignored for ISD' / raw AID / legacy 4F-TLV labeled
- Trailing single byte consumed as Le at end of compact chain
- ACTIVATE/DEACTIVATE: case-1 (4 bytes), legacy empty-Lc, FID/path forms
- Expanded script C-APDU rows now decode into structured APDU nodes
- Compact matcher accepts CLA 84-87 (GP secure messaging)
Version 1.9.0 -> 1.9.1, SW cache otaman-v10 -> otaman-v11
New 'Parser' subtab in C-APDU section. Detects format (compact C-APDU
chain, expanded AA/AE80 script) and renders collapsible tree with decoded
parameters. Key features:
- BER-TLV parser with multi-byte tag support and CR-bit handling
- Proactive command decoder (types from BER_QUAL, devices from BER_DEVICES)
- SIM/UICC toolkit parameter decoder (CA/EA/80)
- GSM7 text decode (7-bit packed, escape sequences)
- Best-effort UCS2/GSM7 text detection
- Compact C-APDU chain with implied-CLA support (SELECT + op chaining)
- 18 test cases from exchange vectors (apdu_parse.test.js)