BIP was only reachable through the SCP81 listener; cards that use TCP for
other purposes (an applet's OPEN CHANNEL, a push trigger without HTTP OTA)
now get a generic control surface, independent of SCP81.
Server:
- /api/bip/control starts a plain BIP session in three modes: sink
(default; a local TcpDumpServer that accepts the card's channels and only
logs conn/sink-rx/conn-close, never answering; port 0 = ephemeral, the
bound port is reported), passthru (dial the OPEN CHANNEL destination, TCP
client only) and redirect (fixed host:port).
- /api/bip/status returns {owner, bip, listener}; /api/bip/log and
/api/bip/log-clear share the BIP event log with /api/scp81/log.
- The session state is shared with the SCP81 listener and only one session
runs at a time: _bip_session_stop() stops whichever control started it
and the control responses report it as `replaced` (both directions,
SCP81 <-> BIP). State renamed _SCP81_MODE/_TARGET/_LISTENER/_LINK_EVENTS
-> _BIP_* plus _BIP_OWNER.
- TcpDumpServer logs conn-close and counts accepted connections; stop()
joins the accept thread so the port is really free on restart.
- The new control endpoints are blocked during test-script runs.
PWA:
- New Simulator pill BIP (after TR Config): mode select with notes,
Host/Port rules, Start/Stop with a "replaced" notice, status line (mode,
bound address, owner, channels), a Channels box and the shared BIP log
(reuses the SCP81 log renderer, now showing `peer`).
- The SCP81 status line marks a session owned by the BIP pill.
Help EN/RU 8.10, docs/api.md, AGENTS; CAT_TP/UDP recorded as not
implemented.
Tests: tests/test_bip.py (9) and frontend/tests/bip.test.js (6).
600 frontend / 482 Python green; version 3.6.0; sw simple-v273.
- Top-level tab label "Phone simulator" -> "Simulator" (RU "Симулятор");
the DOM ids stay tab-phone / phone-sub-*.
- The eSIM pill inside the Simulator tab is now "eSIM LPA" (EN/RU),
matching the section's ES10/LPA scope; the panel id stays phone-sub-esim.
- Documentation updated: help EN/RU (section 8 Simulator, list of the four
pills including Test script, tab lists and cross-references),
README/README_RUS (tab lists, Simulator section, eSIM LPA), AGENTS
repo/root.
- Test titles updated; version 3.5.12; sw cache simple-v265.
579 frontend / 467 Python green.
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.
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.