The GSMTAP stream sent logical APDUs, so a receiver (SIMtrace Analyser,
Wireshark) flagged almost every packet: case-4 commands carried their Le
byte ('length_mismatch: excessive') and responses were headerless packets
decoded as bogus commands ('truncated'). The ATR also arrived in the
middle of the stream.
- gsmtap.py: the tracer now emits wire-shaped TPDUs, matching a hardware
sniffer capture: case 4 -> the command without Le plus the 61XX 'bytes
available' SW (derived from the real response length), then the data as
a GET RESPONSE TPDU (00C00000<len> + data + SW, chunked at 255); case
1/2/3 -> one packet cmd + data + SW; unparseable APDUs fall back to raw
command/response packets.
- No ATR/VCC/RST/PPS events at all (no line-level access over PC/SC);
the ATR sending helper and the server-side plumbing were removed.
- Verified by replaying the bad capture's exchanges through the new
tracer: 116 packets, zero decoder warnings (previously nearly all).
- tests: the per-case wire forms, chunking, fallbacks; README/AGENTS
document the wire shape and its limits; version 3.1.1, sw simple-v236.
Streams every APDU the server sends or receives as GSMTAP-SIM UDP packets,
so a live capture can be followed in Wireshark or the SIMtrace Analyser
without a hardware sniffer. CLI-only: --gsmtap [HOST[:PORT]], default
target 127.0.0.1:4729; no UI or API.
- pysim_simple_server/gsmtap.py: 16-byte big-endian GSMTAP-SIM header
(type 0x04, sub_type 0x00 = APDU / 0x01 = ATR) + raw APDU bytes, a
fire-and-forget non-blocking sender that never raises into card I/O, an
ApduTracer (a response is sent as data + SW1SW2, the wire form) and a
fan-out tracer. The packet layout is byte-identical to
sigrok-iso7816-stream / simtrace2-sniff (verified against the sigrok
module) and is what the analyser's GSMTAP receiver expects.
- __main__.py: --gsmtap option, tracer installed on the shared transport
before the first APDU, combined with --apdu-trace via the fan-out and
re-attached across equips (pySim nulls the tracer on every equip); one
ATR packet per equip from _apply_equipped_card/_send_gsmtap_atr.
- start.sh/start.bat: forward their extra arguments to the server, so
./start.sh --gsmtap works.
- tests: packet layout, loopback UDP delivery, tracer mapping, target
parsing, fan-out; docs (READMEs, help EN/RU, AGENTS); version 3.1.0,
sw.js simple-v235.
After a profile switch the shell was unusable: every file select failed
with 'Attribute already exists: do_decode_hex (ShellCommands)' and even
equipping did not help - only a server restart recovered it.
pySim's equip() unregisters only the command sets of the file that is
selected at that moment, then re-registers everything while selecting
MF/EF.ICCID and MF with the app as cmd_app. esim._select_isdr() selected
the ISD-R ADF without cmd_app, so MF's sets (ShellCommands, ...) stayed
registered while the selection moved to the ADF; the equip after the
profile switch then died re-registering them (cmd2 raises
CommandSetRegistrationError), leaving the app broken.
- _select_isdr() passes the shell app as cmd_app, so the old file's sets
are unregistered and the ADF's (none) registered.
- _restore() passes it to soft_reset() too, so the MF re-selection updates
the bookkeeping on both the success and the failure path.
- _esim_reinit() probes select('MF', app) after the equip and reports
reinitialized: false on a registration error (a card-level select
failure, e.g. no active profile, is tolerated).
- tests assert the cmd_app plumbing on the ISD-R select and the restore
(profiles, chip and both switch paths).
The switch refactor dropped the ISD-R selection that the old
_transceive/_run wrapper performed, so the STORE DATA went to whatever
application was selected (MF after a preceding ES10 call) and the card
answered 6D00 ('instruction not supported') without ever reaching the
switch flow.
- esim.switch_profile() now takes the app, selects the ISD-R before
sending the raw STORE DATA and restores the selection in a finally,
like the other ES10 functions.
- tests assert the selection and the restore for the 9000 and 91XX
paths (the FakeLchan/rs already track both).
Disabling a profile failed with 6985 and left the card stuck until an
equip. pySim's send_apdu_checksw auto-handler keeps flushing proactive
commands after the REFRESH TERMINAL RESPONSE; the card is then mid-switch
and answers 6985 to the next FETCH, which propagated as a 500 and skipped
the re-initialization. Per SGP.22 v2.6 5.7.16/5.7.17 a 91XX answer is
the ISD-R's 'result OK before REFRESH' (step 6) and the switch completes
on the TERMINAL RESPONSE or the following RESET (step 8) - lpac treats
91XX the same way and never retries.
- esim.py: build_switch_apdu/parse_switch_response/switch_profile split
out of set_profile_state; the switch is one raw STORE DATA via
scc._tp.send_apdu, a 91XX runs our own FETCH/TR chain (status_poll=False)
and is reported as ok, the STORE DATA is never retried and a chain
failure still counts the accepted switch.
- server.py: /api/esim/profile answers the REFRESH with our chain, then
re-initializes the card and re-reads the profile list, returning
verified/state_after; _handle_proactive_chain grew status_poll.
- /api/status and /api/select: FCP metadata via _fcp_value - an ADF or a
failed select (card with the active profile disabled) has no
file_descriptor and used to crash the request handler; _get_file_type
no longer raises either.
- esim._restore logs a failed selection restore instead of swallowing it.
- PWA: esimSwitchStatus shows the verified state / not-confirmed warning.
- tests: the switch flow (9000 / 91XX / error SW / chain failure), the
FCP guards and the status helper; docs and sw simple-v230.
- docs/api.md: document GET /api/net-state and POST /api/net-state-refresh
(state shape, monitored keys, refresh filter, 503); add eid/euicc to
/api/status; correct the /api/net-sim FPLMN wording (roaming_denied
appends, attach clears; 5GS files untouched) and its response
(net_state); move the stray /api/event-send example back into its
section; refresh the version examples to 3.x.
- README/RUS: Cards — ADM field, the three TARs, the SCP80/SCP81
fieldsets and the header markers; File Browser — Read raw/Read decoded
pills, Edit raw, named FCI block, Verify ADM; Profiler — Clone, three
list tabs; Custom files — root/parent/FID/alias form, canonical paths,
cascade delete, legacy-path resolution; Phone simulator — three pills,
eSIM, Network state monitor, Home network button, corrected FPLMN
wording, proactive-log row content; RU CLI table gaps (--sms-oa/
--sms-sm-sc, --terminal-profile, --mcc-mnc-list) and the missing
event-form bullets.
- help EN/RU: three profiler list tabs, 3.x compatibility example.
- Version 3.0.1 (docs release), sw.js simple-v228.
The three eSIM GET routes were inserted after the POST-only
/api/verify-adm branch, i.e. into the _do_POST chain, so GET
/api/esim/chip|profiles|notifications fell through to the 404 handler
(only POST /api/esim/profile was reachable).
- Moved /api/esim/chip|profiles|notifications into _do_GET (after
/api/status); /api/esim/profile stays in _do_POST.
- Regression test: inspect the handler sources and assert each route is
in the right chain.
- Version 3.0.0 (eSIM support is a major update): server/pyproject/PWA
header, sw.js simple-v227.
With an eUICC in the reader the fast init raised SwMatchError twice and
fell back to the stock pysim init: pick_profile_no_reset() disables the
physical resets that CardProfile.match_with_card() normally performs, so
the successful SGP.22 probe leaves the ISD-R ADF selected. RuntimeState
then selects MF by FID (00 A4 00 04 02 3F00) from within the ADF, which
this card answers with 6A82.
- _restore_mf_after_probe() re-selects MF after the profile pick: the
cheap select keeps the reset-free path for normal cards, a physical
reset covers cards that refuse the MF select from an ADF.
- The EID read restores with rs.reset() (soft reset, escalating to a
physical reset) instead of rs.soft_reset(), which had the same trap.
- tests: FakeScc mf_select_error mode + two _restore_mf_after_probe
cases (no reset / exactly one physical reset).
New eSIM pill (Phone simulator) for SGP.22/32 cards, built on pySim's
ES10 static API — no lpac, no new dependencies, no pysim patches, no
SM-DP+ interaction:
- Chip: EID, EUICCInfo1/2, configured addresses (ES10a/b).
- Profiles: GetProfilesInfo with metadata (state, nickname, provider,
ICCID, ISD-P AID, class, owner, icon).
- Notifications: read-only ListNotification viewer.
- Switch: Enable/DisableProfile with RefreshFlag=1; the card's REFRESH
(fetched/answered/logged by the transport's proactive handler) or an ok
result triggers _esim_reinit() — reset + equip + _apply_equipped_card —
so the ICCID, network state and cached views are re-read.
- Server: pysim_simple_server/esim.py, GET /api/esim/chip|profiles|
notifications, POST /api/esim/profile (all under _CARD_LOCK; 400
not_an_euicc), euicc/eid in /api/status.
- Tests: tests/test_esim.py (fake scc, monkeypatched store_data_tlv),
frontend/tests/esim.test.js; help EN/RU, docs/api.md, AGENTS.
EF.PNN decoded 'Miranda@' for the live card's record 2: efPnnText ignored
the number of spare bits in the coding octet (TS 24.008 10.5.3.5a bits
1-3) and unpacked floor(8n/7) septets, so the 7 zero pad bits became '@'.
- gsm7Decode(octets, septets?) takes an exact character count and removes
a final <CR> used as padding (TS 23.038 6.1.2.1.1).
- efPnnText derives the count from the spare field ((8n - spare)/7 when
1-7; floor(8n/7) + CR drop for 000 = no information).
- tests: the seven live EF.PNN records (Alfa, Miranda, Win, Mir Telekom,
+7 Telekom, Fenix, MKS), a UCS2 record and the CR-padding drop.
- docs/uicc/UICC_SPECS.md: CR-padding rule in 10.4 and a new 10.7
Network Name coding section (outside the repo).
/api/verify-adm sent VERIFY with CLA A0 on a UICC (SW 6E00) while
pySim-shell's verify_adm worked: fast init builds the card on its own
SimCardCommands instance, but __main__ kept server.scc at the startup
placeholder left at the SIM defaults; only an equip repointed it.
- __main__ adopts card._scc after init (cat_cla still set on it), so
server.scc carries the card's cla_byte/sel_ctrl from startup on.
- _verify_adm prefers app.rs.lchan[0].scc / app.card._scc, exactly like
pySim-shell's verify_adm, independent of server.scc.
- netsim AUTHENTICATE follows the card class: a UICC gets 00 88 00 81 22
(RAND+AUTN, DB/DC response), a SIM gets A0 88 00 00 10 (RAND only,
SRES+Kc); the 61xx GET RESPONSE uses the same CLA.
- tests: ADM with a stale placeholder scc; 2G builder/parser; SIM-CLA
runner case; help EN/RU and AGENTS updated.
Attaching to a PLMN listed in EF.FPLMN used to write successful locations
anyway, i.e. it attached to a forbidden network. Per TS 23.122 a
successful manual selection removes the entry, so the attach scenarios
clear it first:
- netsim.remove_fplmn() clears every occurrence of the PLMN (entries are
not compacted; FFFFFF gaps stay); insert_fplmn() returns None when the
PLMN is already listed, so roaming_denied no longer stores duplicates
(the live card had '250-99, 250-99').
- clear_fplmn() step, called from write_real_locations() -> covers
attach_eps, attach_2g and the sms_received location rewrite; the write
is logged as a normal fplmn update_binary.
- tests: remove_fplmn duplicates/absent/gaps; roaming_denied duplicate
skip; attach clears both occurrences before the location writes;
make_runner now copies FakeFileInfo so seeded data does not leak
between tests.
- help EN/RU and AGENTS updated.
- netstate.home_plmn(): the card's home network — EF.HPLMNwAcT first
record (the HPLMN per TS 31.102 4.2.5), falling back to the IMSI with a
2-digit MNC. Exposed as state.network.home in /api/net-state.
- The net-sim parameters get a Home network button next to Random roaming
operator: it fills MCC/MNC and reports the source (EF.HPLMNwAcT/IMSI).
- The monitor's HPLMNwAcT row now shows only the first network (code
only) plus a '… +N' counter; the tooltip keeps the full decoded list
with the access technologies.
- tests: netstate home_plmn cases, netsim button handler, updated
netstate expectations; help EN/RU.
The proactive command log showed 'Cmd 0x01' for REFRESH: 0x01 was missing
from both CMD_NAMES (frontend) and PROACTIVE_TYPE_NAMES (server), so the
log fell back to the generic placeholder. Both tables now carry the full
TS 102 223 9.4 command type list (REFRESH, MORE TIME, POLLING OFF, SET UP
CALL, SEND SS/USSD/DTMF, GEOGRAPHICAL LOCATION REQUEST, SET UP IDLE MODE
TEXT, PERFORM CARD APDU, POWER ON/OFF CARD, GET READER STATUS, RUN AT
COMMAND, LANGUAGE NOTIFICATION, SERVICE SEARCH/INFORMATION, DECLARE
SERVICE, frames, multimedia, COMMAND CONTAINER, ...).
- tests: event_forms CMD_NAMES cases; new tests/test_proactive_names.py
(REFRESH + spec spot checks).
EF.HPLMNwAcT fell back to raw hex: efFindDecoder uppercases the requested
name but compared it against the mixed-case registry names
('EF.HPLMNwAcT'), and the monitor passes no FID, so the lookup failed.
Name matching is now case-insensitive, which also fixes EF.PLMNwAcT,
EF.OPLMNwAcT, EF.PLMNsel, EF.KcGPRS and EF.LOCIGPRS for name-only callers.
The decoded HPLMNwAcT list can still be long (one entry per PLMN/AcT
combination), so netStatePlmnList keeps the first three networks and
appends a '… +N' counter; the row tooltip keeps the full decoded list.
- tests: registry case-insensitivity, netStatePlmnList limit, HPLMNwAcT
summary abbreviation; help EN/RU.
Content mismatches in profile checks and snapshot comparisons used to
replace the raw expected/actual pair with the decoded per-field table
whenever a decoder could see a difference.
- The raw comparison is always shown first (heading + expected/actual
rows); the decoded per-field table follows when the decoder finds
differing fields.
- The decoded table gets a column-header row carrying the same
expected/actual labels (profile/snapshot names in a comparison) that
the raw rows and the FCI diff table use.
- Matching decoded values keep the 'Decoded values match; raw bytes
differ' note and show no decoded table.
- tests: ef_decode report test extended (raw + decoded + column headers,
custom labels, matching-decoded note); help EN/RU.
The decoded view used a fixed 10rem right-aligned label column, so short
labels pushed the values ~168px to the right — for transparent files the
block looked like indented record data. efRenderFieldsHtml now uses a
grid with grid-template-columns: max-content 1fr: labels stay right
aligned and values stay aligned, but the column is only as wide as the
widest label of the block. Applies to the file manager decoded view and
the snapshot Decoded block (transparent and per-record fields alike).
- tests: efRenderFieldsHtml markup (grid, no w-40) and decoded view.
- 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.
Files without a client-side decoder used to fall back to a raw view when
the Decoded pill was pressed. The decoder is resolved by name/FID
(efFindDecoder) before any read, so the pill can be disabled instead.
- pysimFsHasDecoder() mirrors the decoded view's resolution (tree node
name, FID fallback).
- pysimApplyAvailability() honours a new data-needs-decoder marker:
disabled with the existing 'No decoder for this file' tooltip; the
card hint still wins when no card is equipped.
- pysimFsClickFile() re-runs the availability pass after selecting a
file; pysimFsSetMode('dec') ignores the call without a decoder.
- tests: indicator (enable/disable/tooltip precedence), fs_edit
(decoded guard, name/FID resolution); help EN/RU.
The decoded view is a read-only field table, so pressing Edit there did
nothing (no readonly inputs to strip) and Save found nothing to write.
- Remove the redundant Read button: the Raw/Decoded pills already read
the file, so they become explicit read actions — Read raw / Read
decoded (Читать сырые данные / Декодировать). They are buttons now
so the server/card availability model disables them like the old Read
button did.
- Edit raw (Редактировать сырые данные) always edits the hex view: from
decoded (or with no content loaded yet) it switches back to raw and
reads the file first; the decoded view stays read-only.
- Save/Cancel moved next to Edit raw (right-aligned group); the pills
are inert and dimmed while editing (mode switching would discard
unsaved changes).
- Tests: frontend/tests/fs_edit.test.js (skipRead, edit guard, decoded
auto-switch ordering, read failure abort, cancel/reset); help EN/RU.
The preset ADM key was stored but never used: the header badge showed
whether a key exists and whether the card was verified, yet the only way
to verify was the pySim command line.
- POST /api/verify-adm builds the TS 102 221 VERIFY itself (CHV number
from the card model, short keys padded to 8 bytes with 'f') so the raw
SW is reported: 63Cx -> attempts_left, 6983/9804 -> blocked, 6982 ->
security error. The key is never stored and is redacted from request
logs.
- PWA: the header ADM badge is clickable when the matched preset has a
key; a failed file-manager read/write (6982/9804) shows a Verify ADM
button next to the error. Every retry after a failure asks for
confirmation and shows the remaining attempts (stronger text on the
last attempt); a blocked ADM disables both entry points until the card
session changes. No automatic retries.
- tests: tests/test_adm_verify.py (fake scc, APDU/SW mapping, redaction)
and frontend/tests/adm_verify.test.js (retry prompt, SW classifier,
wiring) + card_state indicator expectations
- docs/api.md, help EN/RU, AGENTS; version trio 2.7.8; sw cache v211
The operator picker depended on a workspace file outside the repo
(<workspace>/samples/mcc-mnc-list.json), so a fresh clone showed
'Operator list not loaded'.
- bundle the list in the package (pysim_simple_server/data/
mcc-mnc-list.json, byte-identical to pbakondy/mcc-mnc-list master
commit 97bc1652, MIT) with license + provenance in
mcc-mnc-list.LICENSE; declared as package data so wheels carry it
- _default_mcc_mnc_list() now points at the bundled file;
--mcc-mnc-list still overrides it
- hide MVNO entries from the simulator picker (search + random): they
do not operate their own network. Detected via the `bands` field
('MVNO', 'Satellite MVNO', ...); the full list stays loaded so the
Network state panel still resolves operator names
- tests/test_mcc_mnc.py: bundled file ships/parses, marker variants,
search/random filters on fixtures and on the real list
- README/AGENTS updated; version trio 2.7.7; sw cache simple-v210
The network-simulation description never switched to Russian: its
data-l10n attribute carries " (decoded to " by the browser), while the
LANG_RU key is raw text inside <script> and keeps the literal ", so
translatePage()'s lookup missed and t() returned the English key.
- LANG_RU: the description key now carries real quotes, matching the
decoded attribute value (RU value unchanged)
- add the missing RU entries for the net-sim labels AUTHENTICATE, MCC,
MNC, LAC, Cell ID, TAC, RAC (acronyms map to themselves; Cell ID ->
«Идентификатор соты»)
- html.test.js: strict cross-check that every data-l10n attribute
(entity-decoded, as the browser does) is a LANG_RU key
- sw.js cache simple-v209; version trio 2.7.6
The second line under the location (e.g. "RAI FFFE") showed only the LAC
part of the current area identity: after every dummy-writing scenario it
was the constant FFFE, and otherwise it was redundant with the
EF.LOCI/PSLOCI/EPSLOCI rows below. netStateRender no longer renders it.
The timestamp line now carries a translated "Last refresh" label
('Last refresh': 'Последнее обновление').
- frontend/index.html: remove #netstate-area (markup + render), label the
read time, LANG_RU entry
- frontend/tests: html test pins the label and the absence of the area
line; netstate test asserts the rendered timestamp
- sw.js cache simple-v208; version trio 2.7.5
- byte 30 b5-b8 were marked RFU; now Proactive UICC: ACTIVATE,
GEOGRAPHICAL LOCATION REQUEST, PROVIDE LOCAL INFORMATION (Broadcast
Network Information) and "Steering of Roaming for I-WLAN" REFRESH
support (TS 102 223 cl. 5.2; TS 31.111 cl. 5.2 names)
- byte 34 b8 "Reserved for future use" -> "Deprecated, bit = 0"
(TS 102 223 cl. 5.2)
- mirrors the UICC_SPECS.md audit fixes (byte 30/34 tables, ESN/MEID
bit positions b5/b6, Data Connection Status Change in extra events)
- SW cache simple-v207; 454 frontend / 298 Python tests green
The location line in the Phone-tab network monitor grew too long once the
status was appended (e.g. "262-01 · Germany · Telekom · Guest (roaming)
⛔ PLMN not allowed"). netStateRender now keeps the identity
(mcc-mnc · country · operator) on the first line and renders the status
elements (roaming tag + forbidden badge) in a block span on a second line;
a location without a status still renders on one line.
Also add the missing Russian translation for the monitor's Refresh button
(t() fell back to English): 'Refresh': 'Обновить'.
- frontend/index.html: two-line location render, LANG_RU Refresh, 2.7.3
- frontend/tests/netstate.test.js: pins the block-span layout and the
one-line no-status case
- sw.js cache simple-v206; version trio 2.7.3
The UICC_NAA.md C3/C3a recipes were re-checked against the corpus: the
EPSLOCI dummy tail is FF*13 + `FF FE` + status - the TAC is always FFFE
(never FF) and the status byte is 01 (not updated), not FF; the old
`<FF*16>` reading was a misparse of that tail. The rejection form
(status 02) is the C3a spec model: the only observed rejection trace never
writes 6FE3 at all.
- build_epsloci_dummy(status=ST_NOT_UPDATED, keep_plmn=None): corrected
tail; keep_plmn selects the NMR style that preserves the last visited
TAI PLMN (the guest style wipes GUTI and TAI PLMN) - both are observed
- write_dummy_locations(): drop the all-FF special case (service loss now
ends `FF FE 01`) and thread keep_plmn through
- tests: exact guest/NMR/rejection vectors, 18-byte length, runner
assertions for service_lost and roaming_denied; netstate fixture updated
- UICC_NAA.md C3a tail `<FF*15> 02` corrected to `<FF*13> FF FE 02`;
the PWA help/param text no longer claims the EPSLOCI dummy keeps the
PLMN; SW cache simple-v205
The startup init (server booted with the card already in the reader) equips
the card and reads the ICCID itself, but never created server.net_state -
only /api/command equip and the auto-equip worker did. With a card present
at boot the monitor stayed empty, net-sim responses carried net_state: null
and the panel only started updating after pressing Refresh (which lazily
created the state). Fix:
- factor _netstate_read(app) / _netstate_install() / _netstate_init() in
server.py; _apply_equipped_card uses _netstate_init()
- __main__ startup: read the monitored EFs in the same CAT-free window as
the ICCID and install the state once the server object exists
- _netstate_ensure() lazily initializes the state in the net-sim/event hooks
so any equip path predating the monitor cannot leave it dead
- netSimRun falls back to netStateFetch() when a response has no net_state
- tests: tests/test_netstate_server.py (candidate order, record vs
transparent, absent files, install, ensure); SW cache simple-v204
server:
- netsim: LOCI/PSLOCI dummy builders take a status; the EPSLOCI dummy is
wiped to 0B F6 + FF per UICC_NAA.md C3; roaming_denied now emulates the
permanent 'PLMN not allowed' rejection (C3a): status 010, EPSNSC dropped,
EF.FPLMN append with TS 31.102 4.2.16 shift semantics and a home-PLMN
guard (HPLMNwAcT/EHPLMN, IMSI fallback), optional 'Rejection: write FPLMN'
toggle; write steps carry the logical key; SCENARIO_SERVICE map;
insert_fplmn/fplmn_entries/parse_imsi helpers
- netstate.py: cached per-session monitor state for the 12 network EFs,
step-based patch, simulated service state, derived location with country/
operator (optional MCC/MNC list) and roaming class
- server: monitor read at equip right after a readable ICCID (skipped
otherwise), cleared on card removal; GET /api/net-state and POST
/api/net-state-refresh; net-sim patches the cache from the written bytes
and re-reads EF.IMSI; Location-status events set the service state and
re-read EF.IMSI (multi-IMSI applets)
frontend:
- Phone tab: 'Network state' panel next to Network simulation with the
simulated service badge (Undefined until simulated; normal/limited/no
service + rejection marker), location/roaming line, compact per-file
summaries with full-decode tooltips and a Refresh button; no card polling
- EF decoders: EF.FPLMN (FFFFFF gaps are not terminators) and EF.EHPLMN
- i18n EN/RU, help updated; SW cache simple-v203
tests: 295 Python / 453 frontend
- ADM/SCP80/SCP81 header indicators become bordered chips (neutral gray
border, rounded, px-1.5 py-0.5); SCP80/SCP81 get an even ml-1 gap, so no
separator glyph is needed
- profiler/snapshot card actions carry the equipped card's ICCID:
'Profile from card <ICCID>', 'Check card <ICCID>' (row button, arrow
dropped), 'New snapshot: <ICCID>'; pysimUpdateCardActionLabels() fills
the spans on every status poll and after a language switch
- these buttons switch to data-needs='card-iccid': disabled without a
readable ICCID ('Card equipped but its ICCID is not readable' hint)
- profiler rows wrap so the longer button cannot break the layout;
help EN/RU updated; SW cache simple-v202
- the ADM badge gains a trailing key glyph (ADM ✓ ⚿ / ADM ✗ ⚿) when the
preset matching the equipped card's ICCID carries an ADM key; the four
tooltip states cover key/no-key x verified/not-verified
- new gray SCP80/SCP81 markers after ADM, shown only when that preset has
all SCP80 fields (KIc/KID, SPI1/SPI2, counter, KIc/KID keys) or the SCP81
PSK pair filled in; hidden without a card session, a matching preset or
the fields, never color-coded
- cardsScp80Complete/cardsScp81Complete/cardsAdmPresent/cardsMatchedPreset
helpers; cardsRender reuses the SCP81 predicate
- tests: ADM four-state badge, marker show/hide, predicate whitespace
handling, header markup order; help EN/RU updated; SW cache simple-v201
- the SCP80 fieldset gains ISD TAR (000000), UICC TAR (B00000) and USIM TAR
(B00001), prefilled with the spec defaults; empty stored values mean the
default for that target (cardsTarValue)
- ADM field between "From card" and "Add", stored in the preset for later
use by the file manager (placeholder ADM (optional), spaces stripped)
- packing a chain selects the preset TAR for its target: RAM/GP -> ISD,
SIM RFM -> UICC, USIM RFM -> USIM (spec default without a preset);
_spTarKey keeps the target when the card preset is picked afterwards,
RAM card selection forces the ISD TAR
- USIM RFM 'from current DF' labelled (ADF.USIM); the list TAR column shows
all three values; help EN/RU updated
- SW cache simple-v199; new cards_form.test.js + html markup guards
(440 frontend / 281 Python tests green)
- new #state-indicator-iccid span between the card image and the ADM badge;
shows the EF.ICCID digits of the equipped card (hidden without a card
session or when the ICCID is unreadable, cleared when the server is lost)
- pysimUpdateIccidIndicator() mirrors the ADM badge pattern (no rewrite when
the value is unchanged); wired into pysimCardStateUpdate() and
pysimSetServerAvailable()
- indicator/card_state tests for show/hide and markup order;
SW cache simple-v197
- Cards and Custom files now expose only Export to file (JSON download) and
Import from file; the Export-as-JSON / Paste & import / Import-JSON-from-
clipboard buttons and their paste textareas are gone
- import feedback moves to a transient status line (new ioStatus helper);
cardsImport/pysimCustomImport no longer read the clipboard
- Network simulation parameters: note under the operator form listing the
scenarios that use the MCC/MNC (EPS/2G attach and SMS received write real
LAI/RAI/TAI; cold boot, service lost, limited service and roaming denied
write dummy files keeping the PLMN; churn/CB/AUTHENTICATE do not use it)
- i18n: removed-button entries dropped, note added EN/RU; help EN/RU updated
- guard tests for the button set and the note; SW cache simple-v196
Live-testing against a UICC without the EPS files (no USIM service 85)
and without the Kc files showed the runner aborting when EPSLOCI/Kc were
absent. write_binary/write_record now take an 'optional' flag: a missing
candidate file is logged as a skip step instead of failing the scenario
(store_epsnsc, real/dummy locations, churn and Kc writes). Also allow
POST /api/write to select by 'path' like /api/select and /api/read (the
netsim live test needed it to restore the captured file values).
Server (pysim_simple_server/netsim.py + POST /api/net-sim):
- pure builders for the observed phone write vocabulary (UICC_NAA.md §13):
EPSNSC (A0 TLV with KSI/KASME/counts/algo, padded to the card's FCP record
size; invalidate-wipe and invalidate-keep-key), LOCI/PSLOCI/EPSLOCI real
and dummy forms (PLMN kept, LAC FFFE, status 01/02), Kc/KcGPRS (9-byte
USIM and 33-byte GSM forms, 07 invalidates), EF.SMSstatus counter bump
(read-modify-write), CBMI/CBMIR, the Location status event (9B 01 <st>,
with the optional 13 07 location info for normal service) and the
AUTHENTICATE APDU (TS 31.102 7.1.2.1) with DB/DC response parsing
- scenario runner executes the recipes under _CARD_LOCK, picks the first
existing candidate path (ADF.USIM vs DF.GSM/DF.TELECOM), reads FCP/current
records where the format is card-specific, skips the event unless the card
subscribed to Location status, honours the scenario toggles and returns a
per-step log; only D6/DC, ENVELOPE and AUTHENTICATE are ever sent
- GET /api/mcc-mnc serves the optional worldwide operator list
(--mcc-mnc-list, default <workspace>/samples/mcc-mnc-list.json): ?q=
search and ?random=1&exclude= for roaming; the list stays out of the repo
PWA:
- Phone pill gains a 'Network simulation' fieldset: one button per scenario
(cold boot, EPS/2G attach, service lost, limited service, roaming denied,
churn, SMS received, CB reconfig, AUTHENTICATE), a collapsed Parameters
block (operator search + random roaming, LAC/Cell ID/TAC/RAC, optional
identities, toggles, churn count/delay) and a step log with SWs
- i18n EN/RU, help/README/api.md/AGENTS.md updated; version 2.5.0,
SW cache simple-v193
Tests: tests/test_netsim.py (19), frontend/tests/netsim.test.js (3) and
html guards - 423 frontend / 280 Python, all passing
Client-side, offline decoder layer (ported from simtrace-analyser, verified
against the pinned specs and pySim test vectors):
- file manager Decoded pill: decoded field table instead of the raw pySim
JSON dump (per-record fields for record files); the server's
read_*_decoded JSON stays behind a 'pySim JSON (server)' disclosure;
switching Raw/Decoded re-reads the open file
- snapshot view: decoded field block for transparent files and a per-record
decoded summary for record files
- profile check / snapshot comparison report: content mismatches of files
with a decoder render as a per-field decoded diff (only the differing
fields, expected | actual); the raw expected/actual pair is kept when the
decoded values match
- coverage: ICCID, IMSI/IST, UST/SST, LI/PL, SPN, PLMNsel/PLMNwAcT/
OPLMNwAcT/HPLMNwAcT, OPL, SPDI, PNN, LOCI/PSLOCI/EPSLOCI/EPSNSC/LOCIGPRS,
Kc/KcGPRS, ACC, PHASE, CBMI/CBMID/CBMIR, ECC, AD, ACL, SMSP, SMS, ARR, DIR,
IMPI/DOMAIN/IMPU, ADN/FDN/SDN/MSISDN/LND/BDN, EXT1-5, SUME (TS 51.011
§10.3.34); unknown files fall back to raw
- new frontend/tests/ef_decode.test.js (14 tests); help EN/RU + READMEs
document the decoded views; version 2.4.0, SW cache simple-v191
- also fix the README/RUS snapshot-comparison text: comparison is always
exact (no content masking)
- version check in pysimConnect() now compares the server major against
SIMPLE_VERSION (2.3.1) instead of the hardcoded 1.x assumption; the header
version is rendered from the same constant
- removed the 'SIM OTA with a Human Face' slogan (span, i18n key, translate
hook) to free header space
- broaden the PWA description in manifest.json/package.json and the help
overview (EN/RU): APDU workbench, OTA lab, CAT/STK simulator, card profiler
- version compatibility tables in README/RUS, help EN/RU and docs/api.md now
describe the major-version rule
- README/RUS: Cards promoted to a top-level section; EN duplicate
Theme/Localisation sections removed
- docs: /api/cardinfo documented; SCP81 'expose framing options' next-work
item dropped (done); local AGENTS.md not-implemented note corrected
- SW cache simple-v190