The file picker's implicit current DF now defaults to MF for SIM RFM and
USIM RFM alike (the real context follows the TAR the packet is sent to,
TS 102 226 7.2/7.3, so the default is neutral and the per-row "starts in"
selector covers ADF sessions - ADF.USIM files appear once it is switched).
Tests set the ADF session explicitly where they exercise ADF-relative fills
plus a new default assertion; help 2.1 EN/RU, READMEs and AGENTS updated.
sw cache -> simple-v244; version stays 3.4.0.
The picker option label read backwards, e.g. "EF.IMSI — 6F07 — 7F20".
It now shows the path within the optgroup's root with the FIDs in order,
matching the path-field convention: "EF.IMSI — 7F20/6F07",
"EF.ICCID — 2FE2", "EF.PSC — 5F3A/4F22" (ADF session). Switch markers and
the filter (which also matches the symbolic path) are unchanged.
sw cache -> simple-v243; version stays 3.4.0.
The SELECT rows (compact RFM chains and the Expanded Script C-APDU picker,
which shares the row editor) now have a file picker instead of typing FIDs,
paths or chains by hand.
Data:
- `pysim_simple_server/uicc_files.py` builds the standard file list cardless
from pySim's profiles and application classes (CardProfileUICC + CardProfileSIM
for the MF tree, every concrete CardApplication subclass for the ADFs) and
writes `frontend/uicc_files.json` (490 entries: canonical FID path, symbolic
name, fid, kind, root, ADF AID). `tests/test_uicc_files.py` regenerates it
and fails when pySim adds or renames files. The asset is precached by the
service worker, so the builders keep working offline.
Picker:
- Sources merged per canonical path: the loaded file-manager tree (card names
and probed presence; probed-absent files hidden), custom files, and the
shipped standard list (specs-default until "Probe all files" has run).
- Default list shows what the current method can express; "all files" reveals
the rest, and picking one auto-switches the method - preferring path (one
SELECT) over chain - with a note line explaining the switch. A "starts in"
selector sets the implicit current DF (UICC shared-FS RFM app starts in MF,
an ADF RFM app in its ADF, TS 102 226 7.2/7.3 - the TAR decides; default per
builder, overridable per row).
- Fill rules: by FID only for direct children of the current DF; path = FID
sequence from MF without the MF identifier (ISO 7816-4) or the relative tail
(USIM P1=09); chain stays relative and follows the TS 102 221 11.1.1.2 FID
search order (children, parent, siblings), so no hop to the common ancestor.
ADF roots are not pickable (selection is by AID; TS 102 226 7.1 forbids
P1=04 for RFM) - the By AID method stays manual.
Tests/docs: frontend/tests/uicc_files.test.js (asset shape, merge/priority/
exclusion, fill matrix, relative chains, session-context tracking, option
grouping); help 2.1/2.2 EN+RU, READMEs, AGENTS. sw cache -> simple-v242.
Phase 1 — the Expanded Script C-APDU rows are no longer hex-only:
- chain containers gained a kind (chainKind/chainIsEmbedded/chainCommands),
so the SIM RFM / USIM RFM / RAM-GP row editors and hex builders can be
embedded as one-row "virtual" chains (ber-<kind>-<uid>) with a listener
hook (chainListen/chainNotify) refreshing the owning row.
- the C-APDU row offers Hex / SIM RFM / USIM RFM / RAM-GP; the built APDU is
echoed next to the picker and wrapped in the 22 Command TLV. GET RESPONSE
is not offered in the embedded pickers (TS 102 226 5.2.1.1).
Phase 2 — "→ Expanded Script" in the SIM RFM / USIM RFM / RAM-GP views
imports the built chain as C-APDU rows (chainApduList drops GET RESPONSE
and splits multi-APDU FID-chain selects; the RAM "To expanded" preview
stays).
Phase 3 — the TS 102 226 table 7.1/8.1/9.1 gaps:
- RFM: SEARCH RECORD (TS 102 221 11.1.7 / SEEK per TS 151 011 9.2.7),
INCREASE (11.1.8 / TS 151 011 9.2.8), CREATE FILE / DELETE FILE /
RESIZE FILE (TS 102 222 6.3/6.4/6.10, with an FCP skeleton builder and
CLA 80 for RESIZE), SET DATA / RETRIEVE DATA (11.3, block + SFI coding);
- RAM: PUT KEY (GP Card Spec 11.8) and the TS 102 226 §9 PUSH command
(80 EC 01 P2) with BIP opening (optional OPEN CHANNEL TLVs), CAT_TP
(3C/39/36), TCP (35/3C/3E/47) and identification-packet variants.
Tests: new frontend/tests/ts102226.test.js (byte-exact spec vectors for
every new command, the FCP skeletons, chainApduList) plus C-APDU row tests
in ber.test.js; 530 frontend / 390 Python green. Help EN/RU, README/RUS
and AGENTS document the Command TLV taxonomy (Tables 5.5/5.9 limits), the
picker and the new commands.
Packets longer than one SMS now go out as concatenated SMS-PP downloads
per TS 31.115 4.3 and the UI shows how many SMS a packet needs.
Server:
- `_split_secured_packet` cuts the command packet at the exact SMS
user-data capacities (first SM 132 octets: concat IE 5 + CPI IE 2;
following ones 134; a single-SM packet may be 137 with the CPI IE) and
`_build_sms_tpdu` tags every segment with the fixed concatenation
reference 01; `_build_sms_tpdu` also enforces the 140-octet budget.
- `_send_secured_packet` (shared by /api/send-ota and /api/ram-install)
sends one ENVELOPE per segment in order, refuses more than
MAX_ENVELOPE_SEGMENTS (5, the card's concatenation buffer) and reports
`bytes`/`segments` in the response (RAM install per step as well).
- `_build_secured_packet`/`_encode_cmd_unlimited`: our own TS 102 225
5.1.1 encoder on pySim's keyset/header constructors, byte-identical to
pySim for packets <= 140 octets (tests) and not limited to one SMS
(pySim refuses the longer ones, which is why they never went out).
- RAM LOAD blocks are no longer clamped to one SMS: 1-240 bytes of
payload with the default 240 (the GP maximum); `load_block_size_auto`
replaces `load_block_size_clamped`.
PWA:
- `scp80SegmentInfo` / `spSizeInfoText` show "N bytes . M SMS
(concatenated)" under the packet field, turn red past 5 SMS and report
size/SMS in the send result and the RAM step log; LOAD block hints and
placeholders updated; EN/RU.
Tests/docs: Python +2 cases incl. segment order/capacities and byte
identity with pySim; Node drift guard against the server constants and
UI text tests; README/README_RUS, help EN/RU, docs/api.md, AGENTS.
A profile check comparing an empty PNN record against a populated one
showed a cryptic 'Empty: yes' row: the decoders return {empty: true} for
an all-FF record and efFlatten() rendered that marker as a pseudo-field
('empty' prettified to 'Empty', the boolean as 'yes').
- efFlatten() skips the marker (the decoded views already show their own
translated 'empty' note next to the record).
- efDiffData() fills the cells of a side that decodes to an all-FF record
with 'Empty (all FF)' (RU: 'Пусто (только FF)') instead of leaving them
blank; a field merely missing from a non-empty record stays blank.
- tests: efFlatten marker, one-sided and both-sided empty diffs, and the
PNN case from the report; help EN/RU note the new text.
- sw.js simple-v239.
'Explore' and 'Delete' were ambiguous next to the other SCP81 actions.
- template selector: Explore -> Explore ISD, Delete -> Delete AID (EN/RU);
- a new Explore script is named 'Explore ISD'; the Delete template keeps
its count-based name (e.g. 'Delete 2 AIDs');
- new scriptKindLabel() maps the stored kinds (unchanged:
explore/delete/install/empty, so existing localStorage scripts keep
working) to those labels and is now used for the scripts table kind
column, which used to print the raw lowercase kind untranslated;
- docs: help EN/RU, README/RUS, docs/api.md examples, AGENTS;
- sw.js simple-v238.
After pcscd restarted (or the reader re-enumerated) the server stayed
permanently cardless:
- pyscard's presence-monitor thread stops itself on SCARD_E_NO_SERVICE
and pyscard never starts it again, so card insertions were no longer
noticed and auto-equip was dead;
- PCSCCardConnection.connect() reuses the context handle captured when
the reader was opened, so the old connection could not be revived and
every APDU kept failing with 'Service not available'.
- _start_card_watchdog(): a daemon that every 5 s checks whether
CardMonitor().rmthread.instance is alive and recreates the stopped
thread (CardMonitoringThread.instance = None + a fresh rmthread); the
new thread reports already-present cards as inserted, which triggers
auto-equip. start_card_monitor() starts the watchdog.
- _is_pcsc_error(): pyscard exceptions carry an hresult, card-level
errors do not; the disconnect paths (AUTO-STATUS, status poll, timer
expiration, net-sim, tree, event send, OTA send) pass the verdict to
_handle_card_disconnect(stale=...), which marks the transport stale.
- _ensure_transport(): builds a fresh transport via
server.transport_factory (installed by __main__ as
mod.init_reader(opts, **tracer_kwargs)) and installs it into
app.sl/server.sl; called by the auto-equip worker, the /api/command
equip branch and _esim_reinit.
- tests: watchdog tick, transport recreation (fresh/stale/failure/no
factory), PC/SC classification, stale disconnect flag.
- docs: AGENTS card-behavior section, README troubleshooting (EN/RU);
version 3.1.2, sw.js simple-v237.
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.
The card title showed only the nickname or profile name ('0002'); with
the provider in front it reads 'Alfa 0002' and still prefers a user
nickname ('Alfa Work') when one is set.
- esimProfileTitle(): provider + (nickname || name), falling back to the
ICCID and finally '?'.
- tests for the helper; help EN/RU and sw.js simple-v234.
The short EUICCInfo2 fields (versions, card resources, category, PPRs, …)
were one per row in the wide right-hand box, wasting half of it.
- esimChipValueWide(): a row needs the full width when its value is a
list or a long token (>24 chars or ', '), so short scalars pair up.
- esimChipBox() gained a twoColumns mode: rows in a responsive
grid-cols-1 sm:grid-cols-2 grid with long values spanning both columns;
the other boxes keep their single-column rows.
- esimRenderChip() renders the EUICCInfo2 box with two columns.
- style.css rebuilt (sm:grid-cols-2, sm:col-span-2).
- tests: the wide/short rule, the two-column markup and the render call;
help EN/RU and AGENTS note the layout; sw.js simple-v233.
The chip view was one long flat list spread over the full height.
- esimRenderChip split into esimChipSectionRows (object or RAT rule list
-> rows) and esimChipBox (titled box, skipped when empty); the boxes
are laid out on a grid: EID, EUICCInfo1, Addresses and the rules
authorisation table in the left column, the larger EUICCInfo2 in the
wider right-hand box (single column on narrow screens). Rows use a
narrower label column (w-36).
- Values wrap with break-words + min-w-0 instead of break-all: text
breaks at spaces first and long hex/AID tokens only when they cannot
fit (same for the profile metadata rows).
- style.css rebuilt (grid-cols-1, lg:grid-cols-3, lg:col-span-1/2/3,
break-words).
- tests: the new helpers, the grid/box layout and the wrapping check;
help EN/RU note the grouping; sw.js simple-v232.
pySim already requests the Icon tag (0x94) together with the other
ProfileInfo tags; the mapping dropped it, so the PWA only had the icon
type.
- esim.profiles(): each profile now carries `icon` (the image bytes as
hex) and `icon_size` (byte count), null when the card sent no image.
- PWA: esimIconDataUrl() builds a data: URL (png/jpg -> image/png|jpeg)
and the profile card shows the image unscaled to the left of the
metadata rows; the Icon row keeps the type and adds the data size
(e.g. 'png · 1234 B').
- tests: profiles icon/icon_size mapping (+ the absent case) and the
frontend data URL / row / render checks; docs and sw simple-v231.
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.
The chip endpoint returned pySim's flattened EuiccInfo dict, whose classes
are incomplete: the capability fields are raw GreedyBytes, extCardResource
is raw bytes and several SGP.22 TLVs are missing from the class, so cards
showed 'unknown_ber_tlv_ie_99' and raw hex instead of decoded values.
- request the EUICCInfo1/2 and configured-address TLVs raw and decode them
in esim.py per SGP.22 v2.6 5.7.8, cross-checked against lpac's
es10c_ex.c: extended card resource, UICC/RSP capability bit lists (first
octet = unused bits, MSB-first), CI PKI lists, category (both the
implicit 0x8B and explicit 0xAB tag encodings), forbidden profile policy
rules (0x99), ppVersion (0x04), sasAcreditationNumber (0x0C) and the
optional certification data object / TRE fields; undecoded TLVs stay in
raw_tlvs instead of being dropped.
- add the ES10b GetRat rules authorisation table (PPR ids, allowed
operators, consent flag) to the chip response.
- PWA: label every new field, map nested labels per path component (the
old code only matched whole keys), group the view into EUICCInfo1 /
EUICCInfo2 / Addresses / RAT sections, render arrays of objects with
index labels and translate the labels (RU).
- tests: decoders against a real card's values (077F3E1F80, 0490, 0640,
81010082040006B32C83022646, the RAT fixture) and the frontend label
mapping; sw.js simple-v229.
- 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 three TAR inputs move to the right of the Counter field and get
visible labels: ISD TAR, UICC RFM TAR, ADF RFM TAR (placeholders removed)
- names verified against the pinned specs: TS 101 220 Annex D ('00 00 00'
Issuer Security Domain, 'B0 00 00' UICC Shared File System, 'B0 00 01'
ADF) and TS 102 226 §7.2/§7.3 (MF implicitly selected, no ADF access vs
ADF implicitly selected, TAR linked to the ADF AID); help EN/RU updated
with the references
- card list TAR header/tooltip use the new names
- html test guards the labels and the Counter -> TAR field order
- SW cache simple-v200 (version stays 2.6.0, relabel/layout only)
- 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
- TERMINAL PROFILE: the status line moves out of the button row (its own
line under Send/Configure; tpStatusMsg keeps the top margin on every
update) and the Send button turns emerald
- STATUS and Polling: Send STATUS turns emerald as well
- html guard test for the green buttons and the status placement;
SW cache simple-v195