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.
- _apply_equipped_card() centralizes the post-equip refresh + TERMINAL
PROFILE (shared by the /api/command equip branch and auto-equip)
- server tracks card_session (bumped on equip and disconnect) and
equipping; /api/status exposes connected, card_present, card_session,
equipping, auto_equip and is exempt from _CARD_LOCK (pure cached state)
- auto-equip is on by default (--no-auto-equip; off with --no-card-init):
the presence observer spawns a one-shot worker after insertion, which
runs equip under _CARD_LOCK and applies the same refresh; the monitor
starts after the startup init so pyscard's initial 'already present'
event does not re-equip a fresh session
- UI: /api/status polls every 2s (other views stay at 5s); when
card_session changes it runs pysimResetCardData() (STK overlay, file
tree, events, proactive log, PLI, status) — the same reset as a manual
Equip; messages: initializing / press Equip / no card
Tests for the observer and auto-equip rules, session bumps,
_apply_equipped_card, and the UI state machine. SW cache v91 -> v92.
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.
A probe can leave a card in a context where CLA-00 file access returns
6d00, so the software MF select fails (stock pysim init survives only
because it resets physically after probing). Fast init now recovers the
same way, on demand:
- FastRuntimeState.reset() falls back to hard_reset() on
SwMatchError/ProtocolError (logged as FAST-RESET)
- init_card_fast() retries once after sl.reset_card() (FAST-INIT)
- __main__ falls back to stock pysim init once and skips
TERMINAL PROFILE/drain when no card was initialized (no more
6d00/6985 noise after a failed init)
- do_equip_fast() no longer pre-unregisters command sets; PysimApp.equip
does that after a successful init, so a failed equip keeps the
previous card/rs instead of leaving the app unequipped
Tests for the escalation, the retry and failed-equip state retention.
No card-model special cases.
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.
pysim's init_card() resets the card once per profile candidate in
CardProfile.pick(), once in RuntimeState.__init__ and again in
PysimApp.equip(); on typical readers each reconnect costs ~1.3s and a
normal init/equip does 7-8 of them (measured: equip 9.85s, startup
~14.8s).
fastinit.py mirrors pySim.app.init_card() with the resets removed:
pick_profile_no_reset() runs all profile probes back-to-back on one
connection, FastRuntimeState.reset() is a software reset (select MF,
clear selected_adf/scp, ATR from the transport) and the equip/reset
commands are routed through do_equip_fast (one reconnect via
wait_for_card) and do_reset_fast (always a physical reset). Enabled
with --fast-init; stock behavior remains the default.
Tests for the reset-free pick, the soft reset and the explicit reset
paths. Docs updated; SW cache v88 -> v89.
--poll-interval 0 now really disables background STATUS polling (it was
clamped to 1s, and the equip branch force-enabled it anyway). Request
handlers and the poll thread now share _CARD_LOCK so a poll can never
interleave a FETCH/TERMINAL RESPONSE pair — the baseline log showed
AUTO-STATUS chains and duplicate FETCHes inside the equip TP chain.
--timing adds elapsed timestamps, per-reset logging (RESET #n) and
phase durations for startup (init_reader, card_init, pysim_app,
terminal_profile_drain) and equip (onecmd, terminal profile).
AGENTS.md documents the proactive-TR invariant: never fetch without
answering, never fetch twice, one APDU conversation at a time.
Incoming: _parse_sms_concat parses UDH for IEI 0x00 (8-bit ref) and
IEI 0x08 (16-bit ref). PoRSubmitHandler accumulates segments, sorts by
num, and reassembles complete payload when all parts arrive.
Outgoing: MAX_ENVELOPE_SEGMENTS=5 limits the secured packet to 5
ENVELOPE chunks (650B max at 130B/chunk).
10 new tests for concat parsing and reassembly.
- 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
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