The access-rules form is large for every file rule, so the block now
collapses to its header (a toggle carrying the rule count) and hides
entirely when None is selected - the header keeps the title and the
None/Check radios, so re-enabling is one click (Check also reopens the
block). The expanded state is transient editor UI state (a module map
keyed by the rule index), kept across the editor's re-renders; a rule
without accessRules starts hidden and Check creates the empty block to
build rules in.
Tests: collapsed by default (header + count, no rows), expanded rows,
None and no-accessRules hidden, the enabled-but-empty hint, and the
radio driving the visibility. 882 frontend + 858 Python; sw cache
simple-v424.
The access-rules block was read-only and its Check radio was disabled
without captured lines, so a hand-made rule could not define rules and
captured ones could not be adjusted. It now edits the decoded rule's
structure:
- fcpArrRuleParse/fcpArrRuleEncode parse a canonical decoded line into
the AM (bit mask, RESIZE FILE, INCREASE) + SC condition model and
re-encode it byte-identically (round-trip tested); the template forms
(OR/AND/NOT, SM CRTs, command descriptions) stay raw and removable.
- The editor renders each rule as operation checkboxes (or the command
form) and condition selects (always / never / a key reference with
its usage), with a preview line of the canonical string the check
compares; + rule adds READ always; + condition / x adjust the rows.
- The None/Check radio no longer disables; a new rule can be built by
hand and captured rules adjusted. The stored format is unchanged
(the canonical strings), so capture, the check, the set comparison
and validation are untouched.
Tests: parse/encode round-trips (incl. raw forms), the mutators, the
editor render, plus the existing check tests. 882 frontend + 858
Python; sw cache simple-v424.
The live card references 6F06 from MF-tree files (MF/7F20/6F07 ->
8B 03 6F06 14), but the resolver compared only the path's first
segment: the reference fell back to the ADF.USIM copy instead of the
file's own DF copy (ISO 7816-4 5.4.3.3 / TS 102 221 9.2.7 walk-up) -
masked on this card only because all four copies are byte-identical.
- The server reads the DF.TELECOM and DF.GSM copies too (MF/7F10/6F06,
MF/7F20/6F06 - TS 51.011), best effort.
- fcpArrRules resolves by the file's DF chain (own DF first, then
walking up to MF); a reference with no copy in the chain resolves
only when a single candidate exists, otherwise it stays silent.
- The implicit form prefers the file's DF copy (6F06), walking up to
the MF copy (2F06).
Tests: the DF copy wins over the ADF copy; two candidates outside the
chain stay unresolved; a single one resolves; the implicit walk-up;
the server reads the DF-tree paths. 879 frontend + 858 Python;
sw cache simple-v424.
A profile file rule may carry accessRules {expected, enabled?, ignoreFciRef?}:
the check resolves the file FCI's '8B' security-attribute reference against
the card's (or the snapshot's) EF.ARR copies and compares the decoded rules as
a set - record-number independent, so cards with a different EF.ARR layout
still verify. Missing/extra lines are listed in the report; a file whose FCI
lost the reference fails; a reference that cannot be resolved (no cached
EF.ARR / record out of range) is an unverifiable error.
Capture fills the lines when a profile is created from a card
(pysimArrEnsure) or a snapshot (profilerSnapshotArr), before the contents
ignore list - the rules are the meaningful per-file check, and EF.ARR's own
contents stay ignored by default as before. The editor shows the captured
lines read-only with a None/Check toggle; with exact FCI an optional "Ignore
the EF.ARR reference in the exact FCI" checkbox compares the FCI without the
'8B' value (every other DO stays byte-exact; fcpWithoutArrRef), so a different
record number no longer fails while the rules check owns its meaning.
The snapshot comparison is unchanged (byte-exact EF.ARR + FCI catch record
renumbering). New 'Access rules' aspect in the report/summary with a
matching/missing/extra line diff.
Tests: capture, the four check outcomes, the ignore-FCI path, the
aspect/report, validation. 877 frontend + 857 Python; sw cache simple-v424.
- pysimArrEnsure no longer caches a transient /api/arr fetch failure:
the next file click retries instead of silently showing no ARR rules
for the rest of the card session (a success is still cached per
session, and a session change refetches).
- The help (EN/RU) and both AGENTS state the cache scope: only the MF
(2F06) and ADF.USIM (6F06) ARR copies are read - a reference to
another DF's copy is left unresolved.
- Test: the transient failure retries, a success is cached, a session
change refetches. 869 frontend + 856 Python; sw cache simple-v423.
The file manager's decoded FCI shows the '8B' security attribute
reference (EF_ARR 2F06, record 23). The referenced record's access
rules are now rendered as indented muted lines under that row, decoded
with the existing EF.ARR record decoder (AM_DO/SC_DO conditions).
The server reads the card's ARR copies (MF/2F06 and ADF.USIM/6F06,
best effort) once per card session in _apply_equipped_card and keeps
them as server.arr; GET /api/arr serves the cache without the card
lock, a disconnect clears it, and a write to an ARR copy through
/api/write refreshes it. The PWA fetches it lazily per card session
(pysimArrEnsure, keyed by card_session) and resolves the reference with
the selected file's DF context: an ADF.USIM file uses the ADF's 6F06
copy, the implicit reference form too (ISO 7816-4 5.4.3.3 lookup
order). Unresolvable references stay silent.
The '8B' decode now covers all three ISO 7816-4 Table 25 forms:
implicit ARR byte / FID + record / FID + (SEID, ARR) pairs - the pairs
form was mis-decoded as a record before. A snapshot resolves the same
references offline from its own captured EF.ARR copy.
Tests: the three forms, the resolution (DF preference, out-of-range
silence), the snapshot path, the server read/cache/route/write-refresh.
868 frontend + 856 Python; sw cache simple-v423.
The browser Back button now walks the app's own view stack:
- a small state object {tab, sub, parser, view, listtab, preset, suite,
script, item} is recorded on every view switch (navRecord/navCurrent/
navStateKey) via history.pushState - session-only, the URL never changes
(pushState throwing on file:// degrades to no history);
- popstate replays through the same switch functions under a _navApplying
guard (they re-render and re-translate): the seven tabs, every pill set
(SCP80 / Remote APDU + parser / Simulator / SCP81), the Cards list/editor
(preset id), the test-suite views (suite/script ids) and the Profiler
views/list tabs (profile/snapshot ids); unresolvable ids fall back to the
area's default view;
- the in-app Cancel/Back buttons (cardsCancelEdit, testScriptBack,
testBackToSuites, profilerBackToList, profilerSaveProfile) call
navReplace() so their entry is replaced instead of stacked, and the
initial entry is seeded with replaceState so the first Back returns to
the initial view.
The reported untranslated suite Run button was the tip of a larger gap: the
sweep found 21 t() keys without a Russian entry (Run/Suite/Move/Copy/Warning/
Text, the respond-result names, the event-data errors, the CAP tooltip, ...)
- all added, and html.test.js now asserts every t('...') literal and every
data-l10n/data-l10n-title attribute has a LANG_RU entry.
Tests: nav.test.js (the recording/replace hooks, the state-key
canonicalisation, the router core), the i18n completeness test, the
extracted-function stubs in the cards/phone/profiler tests.
842 frontend + 819 Python; sw cache simple-v409.
Three fixes found while filling the ETSI/3GPP registry gaps against the
SIMalliance Stepping Stones R7:
- SCP80 builder/verification parity (TS 31.115 Table 1 NOTE / 4.2 / 4.3):
the CPL is now transmitted whenever the packet is ciphered or carries
RC/CC/DS - it is part of their input - and whenever the packet needs SMS
concatenation; a single unprotected SM keeps pySim's CHL-first form.
Before, the JS dropped the CPL for every unciphered packet while the
server reference re-added it, so "Verify vs pySim" reported a false
MISMATCH for every unciphered RC/CC packet (SPI1 01/02/0A/12/1A...).
All ten offered SPI1 values now match the server reference byte-for-byte.
- RC (SPI1 b2b1 = 01) was offered but not built: the JS now computes CRC-32
(TS 102 225 5.1.3.2, pySim zlib.crc32 parity) over the same CPL frame as
the CC; the packet no longer silently omits the 4-byte RC field.
- Server: _build_secured_packet/_ota_reference add the CPL to a concatenated
unprotected packet too (Table 1 NOTE / 4.3).
- fcpLifeCycle: unlisted values with b8 clear are RFU, b8 set is proprietary
(Table 11.7b); previously all unmatched values were labelled proprietary.
- EF.ARR decoder now decodes the expanded format (AM_DO/SC_DO per ISO 7816-4
5.4.3.2 + TS 102 221 9.2.7): operation bit masks, INCREASE/RESIZE AM_DO
0x84, OR/AND/NOT templates, PIN key references with usage qualifiers.
- FCP 'C6' PS template DO decoded (PS_DO bitmap + key references + usage
qualifiers, TS 102 221 11.1.1.4.10/9.5.2) with a shared key-reference map.
Tests: sp.test.js (CPL/RC vectors + crc32 known answer), ef_decode.test.js
(expanded-format ARR vectors), profiler.test.js (LCSI RFU/proprietary, C6),
test_ota_helpers.py (RC reference, unprotected single-SM vs concatenated).
Help EN/RU and READMEs: CPL size 2 (SMS), RC/CC/DS 4-8, RC bullet, CPL rule.
547 frontend / 397 Python green; version 3.5.1; sw cache simple-v253.
- pysimFsInfoHtml puts the FID/type/size line inside the fieldset, so the
selected file is one bordered block with its symbolic name in the border;
files without an FCI keep the plain borderless line.
- The content pane starts hidden and is synced after every mutation
(selection clears it, read success shows it, read failure keeps it hidden,
edit/cancel re-sync): no empty box before a read or edit.
- tests: FCI ordering assertions; new fs_read.test.js (visibility helper,
read success/failure/throw, markup + call sites); fs_edit extraction.
The file manager's decoded FCI rows now sit in a fieldset matching the
phone-simulator containers, with the symbolic file name as its legend
(pysimFsInfoHtml(sel, name)); the metadata line stays above the border.
Without a name the fieldset renders without a legend, and without
fci_hex no block is shown at all.
- tests: fieldset/legend markup, metadata line before the border, no
legend without a name, escaped legend text, no fieldset without FCI.
- help EN/RU mention the named FCI block.
- The short file identifier is merged into the File identifier row
(same row, ' · ' separator) in profilerFciPreviewItems, everywhere the
decoded FCI is rendered.
- New hideFileSize option: pysimFsInfoHtml drops the FCI File size row
when its own header already shows Size (kept otherwise, e.g. DFs).
- tests: merged SFI row, hideFileSize, header-size precedence.
TERMINAL PROFILE:
- GET /api/terminal-profile returns the profile in effect + the CLI default;
POST /api/terminal-profile validates ({profile}, hex, even, 1-255 bytes),
stores it in memory and re-sends it, resetting the STK session like
/api/rescue (the shared _resend_terminal_profile helper; rescue now
delegates to it). __main__ keeps server.cli_terminal_profile.
- Phone tab: a TERMINAL PROFILE block next to STATUS and Polling with the
current hex/byte count, Send (re-send) and Configure. The Configure
dialog has a device-model preset selector, a hex field and a per-bit
form generated from a 264-entry table for TS 102 223 V18.3.0 5.2 bytes
1-33 (pySim's table as scaffold, later bytes/3GPP bits added from the
spec; beyond the table generic RFU labels). Form <-> hex sync both
ways, hex authoritative, bits preserved. Apply posts the new value.
- Presets: Xiaomi Mi A1 (project default), Quectel GSM module example.
In-memory only, no persistence.
Custom files:
- Canonical paths rooted at MF / ADF.USIM / ADF.ISIM; entries are
{path, name, kind}. The editor now uses root + parent-DF selector +
4-hex FID + alias, requires the parent DF to be defined first, rejects
duplicates, rewrites descendants when a DF's path changes and cascades
deletes after a confirmation.
- Legacy forms are normalized on load/import (3F00/... -> MF/..., relative
a153/4954 resolved against the custom DFs); unresolvable entries are
dropped and reported in the list.
- Tree injection matches by exact parent path via the new pysimFsNodePath
(same-FID DFs under different parents no longer collide);
profilerCustomNameForPath uses the same normalization.
SW cache otaman-v167; help EN/RU + docs/api.md + AGENTS updated.
Tests: 236 Python + 371 frontend.
Cards / SCP81:
- Cards is a top-level tab; presets gain PSK identity + key, HTTP-OTA
column, Edit/Update and a live PSK-map push into a running listener.
- SCP81 has Listener/Scripts pills; scripts are named local APDU lists
(Empty / Explore / Install from .cap / Delete templates), sent to the
server explicitly at start. The listener takes mode/host/port/script
only; PSK inputs and the .cap row are gone.
- Multi-PSK TLS listener: identity -> key lookup from the card presets
(POST /api/scp81/psk-map updates a running listener), unknown
identities log tls-psk-unknown and fail; handshake logs carry psk_match.
- Script engine: execution tracking (next/done/pending/results), a
resumed dialog sends only the unexecuted tail (unreported APDU is
resent), a fresh dialog restarts, listing continuation pages are
tracked separately (pending.pages/complete). Restart script button.
- POST /api/scp81/gen-install replaces the SCP81 ram-install queueing
(generation only; the .cap is never stored).
Profiler / snapshots:
- Snapshot comparison is always exact (mask checkboxes removed; the
first-4-bytes mask remains a profile-creation option).
- "matching records" line shows count + #record numbers.
- New Clone action: copy named "Copy of <profile>", opened in the editor.
- Matched-record count/numbers fix ("1 из 8 (#8)").
SCP80:
- Configurable / auto-fitted LOAD block size: each LOAD APDU encodes into
one SMS (pySim rejects secured packets above 140 octets, so a 240-byte
block could never be sent). Response reports the effective size and
clamps; encode failures are reported per step with the pySim message.
SW cache otaman-v161; docs/api.md, scp81-findings and help EN/RU updated.
Tests: 226 Python + 356 frontend.
profilerScanCard's walkDir passed each child dir's own segment in
parent_path (dir.parentPath.concat(childSeg) as the child's parent path),
so /api/tree for every DF child walked the target as its own parent and
failed: DF.GSM, DF.TELECOM and DF.GSM-ACCESS subtrees were silently
missing from New snapshot, Profile from card and Profile from snapshot
(ADF children survived only because application names/AIDs resolve
globally).
- walkDir now stores each dir's full path and sends
parent_path = fullPath.slice(0, -1) (omitted for MF); the child's full
path is fullPath.concat(aid/fid/name)
- verified against the running server: the walker now enumerates 189 EFs
and captures 92, matching the file-manager probe exactly (was 111/48)
- test: fake MF -> DF.GSM tree asserts parent_path [undefined, ['MF']]
and that both subtrees' rules are produced; SW cache v132 -> v133.
- new 'Profile from snapshot' button in the Profiles toolbar: reuses the
snapshot picker (profilerSnapshotPickListHtml refactor) and then opens
the same scan form as Profile from card (profilerScanOpenForm), with
the profile name prefilled from the snapshot
- new profiler-build target 'profile-snapshot': the form's ignore list,
mask options and FCP/FCI mode apply to profilerScanSnapshot(), which
walks the snapshot's captured files instead of the card; uncaptured
contents produce rules without a content check, record files map to
exact record content, transparent files honor the masks
- profilerScanStart() pushes the generated profile and opens the editor
like the live scan; _scanSnapshot is cleared in the finally block
- RU entry 'Профиль из снимка'; tests for the snapshot rule builder
(exact/mask/ignore/uncaptured/record), the snapshot scan progress, the
picker wiring and the form title, plus a structural button check;
help EN/RU and READMEs updated; SW cache v130 -> v131.
- new profilerResultsHeaderText() builds the header from a structured
{kind, from, to}: profile checks read 'Profile verification results
for: <profile> -> <card ICCID | snapshot name>', snapshot comparison
reads 'Snapshot comparison results: <master> -> <checked>' (the arrow
matches the one already used in the compare header)
- profilerRunProfile() stores the header and profilerRenderResultsView()
writes it, so the header follows a language switch and the ICCID stays
current; RU keys added ('Результаты проверки профиля:',
'Результаты сравнения снимков:')
- tests: header builder variants + render wiring; flow tests now expect
header objects; help EN/RU and READMEs updated; SW cache v129 -> v130.
- new profilerLabelText() resolves expected/actual labels at render time;
profilerRenderReport and fcpDiffHtml (including the decode-failure
notes) use it, so a language switch keeps the labels localized
- Check card: reads EF.ICCID via /api/read (MF/2FE2) and labels the
report 'expected (<profile>)' / 'actual (<card ICCID>)'; the results
title becomes '<profile> — <ICCID>' (falls back to the plain title and
'actual' when EF.ICCID is unreadable)
- Check card snapshot: labels 'expected (<profile>)' / 'actual
(<snapshot name>)
- snapshot-vs-snapshot compare keeps verbatim master/check names;
the mismatch label column widens to w-48 for prefixed labels
- tests: profilerLabelText, prefixed report/diff labels, profilerCardIccid
decode/fallback, both check flows pass the labels; help EN/RU, READMEs
updated; SW cache v128 -> v129.
The decoded FCI items now follow the FID/type/size line directly; the
snapshot detail view keeps its caption. Test updated to assert the
caption is absent; SW cache v120 -> v121.
- selecting an EF now renders an info block between the File: title and
the contents: FID, file type, size, record length/count (nulls skipped)
followed by the decoded FCI via the existing profilerFciPreviewItems()
decoder (no raw hex), matching the snapshot detail view
- pysimFsInfoHtml() is pure and reused by pysimFsClickFile(); the [LF]
shorthand stays in the title; custom files get the same metadata from
the temporary select probe
- tests: pysimFsInfoHtml in profiler.test.js (full response, missing
fci_hex, skipped nulls) and a structural check for #pysim-fs-info;
help EN/RU and READMEs updated; SW cache v119 -> v120.
Availability model: server-down / server-up-no-card / card-equipped.
- header shows a symbolic indicator dot (red/yellow/green, tooltip)
- a 5s probe tracks /api/version+/api/status while not fully ready; the
2s status poll takes over once connected
- controls declare data-needs='server' or 'card'; disabled + tooltip when
the state does not satisfy them: equip (server), status/reset, file
manager read/save/edit, raw APDU, profile-from-card, new snapshot,
profiler list Check card, event Send, RAM install/explore delete,
STK menu, Send STATUS, Verify vs pySim (server), pySim execute (server),
PLI save/poll toggle (server)
- pysimConnect no longer conflates server and card: it records both and
Connect stays usable without a card
- ramSendOta/ramInstallCap now use pysimFetch instead of raw fetch
(relative /api URLs broke custom server URLs)
Custom files moved from the Card reader sub-tabs to the Profiler list
tabs (Profiles / Card snapshots / Custom files); help/README/AGENTS
updated. Tests: availability gating, indicator structure, moved tab.
SW cache v103 -> v104.
Snapshot comparison results now use the master/checked snapshot names
where profile checks keep expected/actual: the raw-data field rows, the
generic mismatch line and the decoded FCI comparison table headers
(profilerRenderReport/fcpDiffHtml take an optional {expected, actual}
labels object; the name column widens to w-40 and wraps). Labels are
stored with the results so the language switch and the Only mismatches
filter re-render correctly; Check card snapshot and live checks are
unchanged. Tests for both labeled and default rendering; help/README
updated; SW cache v101 -> v102.
Each snapshot row now renders '(N files, scanned in X.XX sec)' when the
snapshot has timing data (profilerSnapshotCountLabel); old/imported
snapshots without timing keep the plain '(N files)'. RU wording 'за X.XX
сек'. SW cache v100 -> v101.
Server measures every classified APDU (A4 select, B0 read binary, B2 read
record) from command to response: _collect_apdu_times() enables the tracer
(reattaching it if pySim nulled it) and /api/select + /api/read return
'apdu_times': [{type, ms}]. Collection is safe: handlers hold _CARD_LOCK.
Snapshots store per-file {select_ms, read_ms}, a ms value per record and
snapshot-level stats {select, read_binary, read_record}: {min, max, avg,
count} plus total_ms (wall time of the scan). The snapshot view gets a
summary under the title (files/records counts, scan time, min/avg/max per
command type) and shows select/read per file and read time per record.
Timings are display-only: checks, snapshots comparison and imports ignore
them (old snapshots simply show 'No timing data').
Tests: Python classifier/collection (tests/test_apdu_timing.py) and
frontend stats/accumulator/format/build-file/summary. SW cache v99 ->
v100; help, README and docs/api.md updated.
'Profile from card' and 'New snapshot' now focus the name field when the
dialog opens, and Enter in that field starts the scan
(profilerScanNameKeydown, ignored while the Scan button is disabled).
Tests for the key handler and the input wiring; SW cache v98 -> v99.
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.