27 Commits

Author SHA1 Message Date
catarrh dfb9551eb7 release: v2.1.1
Version bumped in server.py, pyproject.toml, the PWA header and the
docs/api.md example; SW cache v124 -> v125.
2026-09-12 22:25:07 +03:00
catarrh a5cccee17c release: v2.1.0
Version bumped in server.py, pyproject.toml, the PWA header and the
docs/api.md example; SW cache v122 -> v123.
2026-09-12 22:16:46 +03:00
catarrh ddc6cb8f9d fix: parent-scoped file selection; never mutate the pySim model
pySim's lchan.select() resolves names against global selectables (self +
parent chain + MF children + applications) and falls back to probe_file(),
which blindly SELECTs an unknown FID and permanently injects a dynamically
named DF.XXXX/EF.XXXX into the running filesystem model. Probing a whole tree
or scanning a snapshot with custom files therefore polluted the model, made
tree branches show children of the wrong object, and could persist phantom
files into snapshots.

- server: new _select_with_parent()/_select_path() walk the requested parent
  path (new parent_path field, parent_sel kept as legacy fallback) strictly
  through the model and call lchan.select_file() only; model-unknown 4-hex
  segments are probed only with allow_probe and the temporary child pySim
  adds is detached again via the cleanup callable that the four handlers
  (/api/tree|select|read|write) now run in a finally block
- frontend: getParentPath() builds the segment chain (MF, ADF names, FIDs)
  and all tree/select/read/write bodies plus the snapshot/profile walker send
  parent_path; allow_probe is set only for custom files; the blind retries
  in the file manager were dropped
- tests: tests/test_select_scope.py (duplicate-FID resolution, no APDU for
  unknown non-custom files, probe+detach, model unchanged); fs_load/fs_probe
  assertions for parent_path and allow_probe; docs/api.md and AGENTS.md
  document the contract; SW cache v117 -> v118.
2026-09-12 21:45:57 +03:00
catarrh f1ed6a2a48 release: v2.0.0 2026-09-12 15:08:04 +03:00
catarrh 6d30e7095e snapshots: measure SELECT / READ commands and show timing stats
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.
2026-09-12 14:56:59 +03:00
catarrh b50be83da4 server: guarantee a TERMINAL RESPONSE for paused proactive commands
Every FETCHed proactive command must be answered, otherwise the card is
left in an unfinished session and stops issuing commands (e.g. it will
not deliver a PoR for SEND SM). The menu handlers now share
_menu_send_response, and a server-side watchdog (_arm_menu_timeout /
_menu_timeout_fire, --menu-timeout, default 60s, 0 disables) sends the
timeout result (0x12) when the user never answers. The timer is armed
while a command is pending and cancelled on any response, equip, rescue
and card disconnect.

Also fixes docs/api.md, which had back (0x11) and timeout (0x12) codes
swapped. Tests for arm/cancel/clamping and the flat timeout TR. SW cache
v89 -> v90.
2026-09-12 12:31:22 +03:00
catarrh ef00b2c3f4 docs: keep a local AGENTS.md out of the repo; fix RAM-install note
Add AGENTS.md to .gitignore so the local agent guide (goal, implemented
features, pySim usage) stays untracked. Correct the /api/ram-install
description: the .cap archive is parsed server-side by _cap_parse, not
by pySim.javacard/global_platform.
2026-09-12 11:46:32 +03:00
catarrh 0fa9548c59 docs: cover C-APDU parser, HTTP OTA, profiler, snapshots
README(.md/_RUS.md) gain the sections that existed only in the in-app
help: C-APDU Parser, HTTP OTA (GP GPC v2.2 Amd B v1.1 4.7), and the
Profiler subtree (profile list, filesystem rules, scan options, check
report incl. Only mismatches, card snapshots, Check card snapshot and
Compare snapshots). Also correct the tab/sub-tab counts and add
Theme/Localization sections to the English README.

docs/api.md: remove the duplicate /api/ram-install section, document
fci_hex/file_size/record_len/num_of_rec in /api/select, the records
shape of /api/read, bump the version example, and add the missing
endpoint sections (events, event-send, proactive-log, status-poll,
rescue, poll-status, poll-toggle, pli-qualifiers, pli-dict).
2026-09-12 08:22:02 +03:00
catarrh 2c0889a8a3 docs: complete SCP80/RAM coverage — cards, presets, counter, PoR
- Fix duplicate /api/ram-install in api.md endpoints table
- Add Card Preset section to RAM tab (fields, counter auto-increment)
- Add SCP80 delivery mechanism explanation (ENVELOPE wrapping)
- Add Cards subtab documentation (add/edit/delete presets, CNTR management)
- Add PoR section (delivery PoR vs submit PoR, SPI2 bit 5)
2026-08-31 20:48:15 +03:00
catarrh b5f871641f docs: update RAM section, add explorer delete buttons, add /api/ram-install
- README/README_RUS: RAM tab now describes Explore Card + Install Package operations,
  explorer view with inline Delete/Delete All buttons for Applications and ELFs
- docs/api.md: add full /api/ram-install endpoint documentation
- index.html: remove Delete from dropdown, add ramDeleteFromExplorer() with confirm dialog,
  add Delete buttons in explorer for Apps and ELFs (ELFs get cascade option)
2026-08-31 09:09:20 +03:00
catarrh 54dfb2f6b6 v1.9.17: fix explore card — chain GET STATUS + GET RESPONSE inside SCP80
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.
2026-08-30 14:13:13 +03:00
catarrh 629c9101bc STATUS: use P2=0C (phone-like, no FCP echo); v1.9.12
_send_status sent F2 00 00 -> the card echoes the FCP of the currently
selected DF on every poll/one-shot STATUS. Phones use P2=0C (no
response expected) to avoid that overhead. Keep P1=00 and the SIM/UICC
P3 distinction (SIM=0x23, UICC=0x00):

  CLA F2 00 0C P3

Affects one-time /api/status-poll and the automatic polling timer.

Version 1.9.11 -> 1.9.12 everywhere; SW cache otaman-v22 -> otaman-v23
2026-08-27 01:11:55 +03:00
catarrh 4f8dae041d Cards table renders at startup and on pill entry; v1.9.11
cardsRender() was mutation-only (add/import/remove/counter-sync), so
after a reload the SCP80 -> Cards table showed 'No cards defined.'
even while localStorage held presets and the SP dropdown listed them
correctly. Now:

- cardsLoad() renders the table right after rebuilding the select
- entering the Cards pill re-renders (mirrors pysim-pill pattern)

Version 1.9.10 -> 1.9.11 everywhere; SW cache otaman-v21 -> otaman-v22
2026-08-25 20:04:55 +03:00
catarrh 7e1916628e SP form: compact layout; v1.9.10
- SPI1/SPI2 side by side in one row
- KIc/KID byte selectors side by side in one row
- KIc/KID key textareas moved directly under the KIc/KID selectors
- Card preset selector moved to the top of the form, full width
- Padding byte joins TAR + Counter in one three-column row
- Pure HTML reflow: no id/handler changes, all behavior preserved
  (invalidation, presets sync, por_ok counter bump)

Version 1.9.9 -> 1.9.10 everywhere; SW cache otaman-v20 -> otaman-v21
2026-08-25 18:39:17 +03:00
catarrh 07c3cdc923 UI: SCP80 top-level tab hosting Secured packet + Cards pills; v1.9.9
Secured Packet and Cards pages move into a new SCP80 top-level tab
(positioned next to C-APDU), switched by rounded-full pills - both
features are SCP80 concerns and more SCP80 operations are planned.

- nav: 'sp'/'cards' buttons replaced by single data-tab=scp80 button
- new #tab-scp80 with scp80-subtab pills and re-parented page wrappers
  (#scp80-sub-sp / #scp80-sub-cards); all element IDs preserved so
  genSp/verify/send-to-card/CNTR-sync/preset flows are untouched
- switchTab(): sp/cards branches replaced by scp80 -> help anchor
- new scp80SwitchSubtab(): pill styling, wrapper visibility, per-pill
  help anchor (secured-packet | cards)
- packToSp() hands off via switchTab('scp80') + Secured packet pill

Version 1.9.8 -> 1.9.9 everywhere; SW cache otaman-v19 -> otaman-v20
2026-08-25 17:06:36 +03:00
catarrh 269ad51f7e SP page: por_ok syncs the selected card preset's counter; v1.9.8
When a secured packet is accepted (por_ok) and a card is selected in
the SP-page card dropdown, the new normalized counter is written back
into that preset (cards[].cntr), persisted via cardsSave() and
reflected in the presets table via cardsRender(). Placeholder option
and unselected state are skipped. Form bump + preset sync happen in
the same okPor block, so form and stored preset always climb together.

Version 1.9.7 -> 1.9.8 everywhere; SW cache otaman-v18 -> otaman-v19
2026-08-25 00:11:56 +03:00
catarrh eb21715050 OTA status stays on the SP page, not in the header; v1.9.7
pysimSendOta wrote its progress/verdict into #pysim-status - the shared
header line owned by connect/equip/poll logic - clobbering the card
status shown under 'Equip card'. OTA outcomes now live exclusively on
the Secured Packet page:

- send start: small gray 'Sending OTA...' in #sp-send-result
- verdicts: big #sp-por-status + details line, as before
- header #pysim-status untouched by OTA flows

Version housekeeping: v1.9.6 -> 1.9.7 everywhere; SW cache
otaman-v17 -> otaman-v18. Also realigns pyproject.toml which the
multi-amend rounds had left stale at 1.9.5.
2026-08-24 23:28:35 +03:00
catarrh 7ece8a531c Response parser: add TS 51.011 SIM status word families; v1.9.6
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
2026-08-24 23:04:54 +03:00
catarrh 73f46e99c4 genSp: unciphered packets drop CPL prefix + prominent PoR status; v1.9.5
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
2026-08-24 20:55:00 +03:00
catarrh a36f26e8e1 OTA send UX + PoR transparency; v1.9.4
- pysimSendOta: Response Parser filled strictly from decoded PoR
  (por.decoded) for both SPI2 variants; envelope SW/failures no longer
  leak into it — they render in a new inline result line (#sp-send-result)
  next to Send to Card, always including SW on failure
- Default-level stderr tracing per send (no flags needed):
  'OTA SEND: SPI .. KIc .. KID .. TAR .. CNTR .. LEN ..B CHUNKS N',
  'OTA SEND FAILED: chunk N SW xxxx' and
  'OTA PoR[envelope|sms-submit]: status=.. TAR=.. CNTR=.. PCNTR=..
  RPL=.. RHL=..' (+ compact summary / undecodable raw / none fallbacks)
- _decode_por: surface every parsed PoR field verbatim (cntr, rpl, rhl,
  cc_rc, raw) instead of status/tar/pcntr only
- genSp: CNTR normalization padEnd -> padStart so short input like '1'
  becomes 0000000001, not 1000000000 (counter is big-endian 5 bytes)
- tests: TestDecodePor completeness + cntr_low field-report cases

Version 1.9.3 -> 1.9.4 everywhere; SW cache otaman-v14 -> otaman-v15
2026-08-24 20:09:37 +03:00
catarrh 7327a879ce Card presets + custom files: file & pasted JSON import, file export; v1.9.3
Both sections (card presets, custom files) gain two import paths and a
file download alongside the existing clipboard import:
- Import from file: hidden <input type=file accept=.json> + FileReader
  feeding the unchanged dedupe/merge logic (works on plain-HTTP LAN
  origins where navigator.clipboard is unavailable)
- Paste & import: reuses the IO textarea; guard requires '[' prefix so
  status messages are never mis-imported; empty state reveals + focuses
  the field with a hint placeholder
- Export to file: Blob download (otaman-cards.json /
  otaman-custom-files.json) via shared downloadJson() helper
- Import functions take optional text arg; clipboard stays the fallback
- data-l10n on all five buttons per section; LANG_RU entries for the
  three new labels

Version 1.9.2 -> 1.9.3 everywhere; SW cache otaman-v13 -> otaman-v14
2026-08-24 15:49:58 +03:00
catarrh 072e8fe08b C-APDU encoder features + GET STATUS E3 coverage; v1.9.2
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
2026-08-22 23:54:49 +03:00
catarrh 71d2e80242 C-APDU parser: P1/P2 descriptors, INSTALL LV decode, structural fixes; v1.9.1
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
2026-08-22 08:36:20 +03:00
catarrh 0fd91d7771 C-APDU parser + BER fixes: DCS prefix, qualifier tables, EFRMA refs, CLA labels; v1.9.0
Parser + BER constructor (TS 102 223 / TS 102 226):
- DCS byte: genBerPcValue prepends DCS to 8D (00=GSM7, 08=UCS2, 04=unpacked)
- decodeTextData: consume DCS first, legacy no-DCS fallback kept
- BER_QUAL.display['80']: 'Clear after delay' -> 'Wait for user to clear message'
- BER_QUAL.tone: 'Monotonous/Alternating' -> 'Vibrate alert' names
- describeProactiveCommand: map type byte -> correct BER_QUAL key
  (01->refresh, 21->display, 20->tone); proper command names
- parseActionRow: values 01-7F -> 'Reference to EFRMA record'
- parseOneApdu: CLA 84-87 -> 'GlobalPlatform (secure messaging)'
- PARSE_INS: added GET RESPONSE (INS C0)

Version: 1.8.1 -> 1.9.0, SW cache otaman-v9 -> otaman-v10
2026-08-21 22:53:16 +03:00
catarrh e6602c40bf RAM constructor fixes: EA/CA nesting, access domain order, menu pairs, BER lengths, services byte (TS 102 226)
- UICC Toolkit tag 80 nested inside EA (EA 12 80 10 ...)
- ram-tk-mode value ea (was 80), label 'UICC Toolkit (Tag EA)'
- SIM (CA) access domain FIRST in payload, blank = 00
- Menu entries: 2×m pairs (i=1 first, i=m last, else 0000)
- CA/EA lengths via berLenStr() (supports 81xx long form)
- UICC trailing 'Maximum number of services' byte (ram-tk-services)
- DELETE: P1=00 fixed, mode in P2 (80E4 00 <mode> ...)
- STORE DATA: ram-enc 00/40/80/C0/E0 with correct labels
- LOAD: P1 fixed to 0x80 (last block)
- USIM SELECT: selP1 0x09 -> 0x00 (select by file id)
- ram.test.js: 11 exact-hex tests from exchange vectors
- v1.8.1, SW cache otaman-v9
2026-08-20 21:23:37 +03:00
catarrh 80a860549a v1.8.0: proactive log overhaul, Expanded Script rename, proactive view sizing
Server:
- Track all TERMINAL RESPONSEs (chain, TP, pySim auto-handler, menu-respond
  link to paused entry) with shared _build_tr/_record_tr
- Log entry ids + cmd_num; server-side decode of fetched commands and TRs
  (compact PLI port); Result CTLV extracted as tr_result/tr_result_name
- _LoggingApduTracer captures TR SW for pySim auto-handler responses
- _DefaultProactiveHandler answers PLI with editor data (no re-request)
- VERSION 1.8.0

Frontend:
- Expandable proactive log rows (click to expand, state survives re-render)
- Response section: Result name + hex, decoded fields, width-restrained
  readonly hex inputs; no SW display
- Rename Expanded Script tab/pill/button/docs; proactive UICC view font
  sizing increase; card reader pills text-sm
2026-08-19 18:04:08 +03:00
catarrh d2c292e5b8 Merge pysim-otaman-server into otaman (monorepo, v1.7.0)
- Move PWA into frontend/; server package at pysim_otaman_server/
- Server now serves the PWA (same origin -> no CORS/PNA): static file handler
  + --web-dir flag + injects window.PYSIM_EMBEDDED marker into index.html
- Frontend defaults to relative API base when embedded (pysimBase = '')
- Version 1.7.0 aligned: server.py VERSION, pyproject.toml, PWA header
- SW cache bump otaman-v7 -> otaman-v8
- Docs: combined README + docs/api.md endpoint reference; update links
- Fix stale package.json repository URL
2026-08-14 15:48:38 +03:00