SIM Bank — distributed SIM inventory
The SIM Bank shows every SIM in every smart-card reader across every OmniWeb agent. Each agent scans its local PC/SC readers, reads each SIM's identity, and reports the inventory to central exactly like it already reports modems/phones as devices. Central aggregates the inventory per agent and persists a database of SIMs keyed by ICCID, so the "what SIMs are where" view survives reader and agent disconnects.
This is the inventory half of a distributed remote-SIM testbed. The osmo-remsim card-emulation/banking layer that lets you connect any SIM to any cardem over the network is documented separately in Remote SIM (osmo-remsim); this page covers discovery and inventory. Saving, exporting and reusing a card's file system as a portable profile package is covered in SIM Images (SAIP Profile Packages).
The page is reader-centric: one row per physical reader slot, grouped by agent, so empty, unreadable and in-use slots all show — the bank layout is the truth, not just the populated slots.

How the agent reads a SIM
The agent scanner mirrors the modem scanner: enumeration is slow, so results are cached and rescanned on an interval.
For each PC/SC reader on the host:
- Enumerate the PC/SC readers attached to the host.
- Probe each reader with an exclusive connect.
- No card →
present=false. - Connect raises a sharing violation → the reader is held by another
process (e.g. an osmo-remsim
bankdor a card-emulation session) →present=true, in_use=true, and no read is attempted. A busy reader never crashes the scan. - Card present and free → read it.
- No card →
- Read the SIM identity with a self-contained set of raw APDUs:
- UICC-vs-classic SIM detection (select
3F00;6E00→ classic GSM, so useCLA=A0and classic selection mode), - EF.ICCID (
2FE2), EF.IMSI (6F07), EF.SPN (6F46), EF.AD(6FAD) for the MNC length (so MCC/MNC are split correctly),- a safe default-PIN (
0000) VERIFY on a locked card — it probes the retry counter first (a non-decrementing empty VERIFY) and refuses if only one retry remains, so it never burns the last retry or touches the PUK (a SIM bank must not brick cards), - the card ATR.
- UICC-vs-classic SIM detection (select
Each reader becomes a dict:
{
"reader_name": "Identiv SCR35xx USB Smart Card Reader …",
"reader_index": 0,
"present": true,
"in_use": false,
"iccid": "8961…", "imsi": "505…", "mcc": "505", "mnc": "01",
"country": "Australia", "spn": "Telstra", "atr": "3b9f96…"
}
The list is sent to central as a sims message (alongside the device list) on
register and whenever it changes; a simbank_rescan command forces an immediate
uncached scan.
Card-reading capability
A host reads SIMs through its PC/SC smart-card stack. Where deeper card decoding is needed, the agent uses an optional, separately-licensed smart-card library kept isolated from the rest of the agent (it is not merged into OmniWeb's own code). A host without the smart-card stack still runs the agent fine and simply reports no SIMs — the SIM bank is an optional per-host capability, like modems.
The reference reader is an SCM/Identiv SCR35xx. Multiple readers per host are supported.
Inventory → central → UI flow
agent host central (android-role process) browser
────────── ────────────────────────────── ───────
simbank.list_sims() ──sims──▶ android agent_socket
(PC/SC read) └─ registry.update_sims(agent_id, …) GET /api/simbank/sims
└─ simbank.record_sims(…) ─▶ DB GET /api/simbank/readers
POST /api/simbank/agents/<id>/rescan
◀─simbank_rescan── (forces an uncached scan)
- The agent dials out to central over the same WebSocket it uses for phones
(
/api/android/agent). SIM inventory rides that connection — there is no new transport. - Central holds the live per-agent reader snapshot in the in-memory
android.Registry(update_sims/all_sims), and mirrors each ICCID into the persistentsimbank_simstable viasimbank.record_sims. - Because this depends on the single-process agent Registry, the SIM-bank API is served only by the agent-role backend process (same constraint as Test Devices).
REST API (/api/simbank, JWT, RBAC element type simbank)
| Method | Route | Purpose |
|---|---|---|
| GET | /sims | Every SIM across all agents (incl. offline persisted-only), with live present/in-use overlaid. |
| GET | /readers | Every PC/SC reader on every connected agent (live), incl. empty and in-use readers. |
| GET | /sims/<iccid> | One SIM. |
| PATCH | /sims/<iccid> | Edit operator-assigned fields (notes today). Creates the row if the ICCID is unseen. |
| DELETE | /sims/<iccid> | Forget a SIM (drops the persisted record; reappears on next scan if still inserted). |
| POST | /agents/<id>/rescan | Force an immediate uncached PC/SC rescan on one agent and return its fresh inventory. |
RBAC mirrors ir38/android-control: admins always pass; otherwise a JWT
permission with element_type == "simbank" grants view, and POST in its
methods grants control.
Frontend
src/pages/simbank/SimBank.tsx — a searchable, filterable table of all SIMs:
status (present / in use / offline), agent/site, reader, ICCID, IMSI, MCC/MNC,
country, SPN, editable notes, last seen. Per-agent Rescan button. Routed at
/simbank and linked from the sidebar under Roaming Tests › SIM Bank.
Database schema
simbank_sims — one row per ICCID ever seen:
| Column | Notes |
|---|---|
iccid (PK) | SIM serial. |
imsi, mcc, mnc, country, spn, atr | Last-known identity (only overwritten by non-empty reads). |
notes | Operator-assigned, editable. |
last_agent_id, last_site, last_reader_name, last_reader_index | Where it was last (or is currently) seen. |
first_seen, last_seen | Timestamps (UTC). |
remsim_bank_id, remsim_slot_nr, remsim_mapped_cardem | Phase-2 reserved (nullable, unused). |
Empty and in-use readers carry no ICCID, so they are not persisted as SIMs —
they only appear in the live /readers snapshot.
Future: osmo-remsim
Phase 2 maps a physical reader/SIM into an osmo-remsim
bank so a SIM in one site can be presented to a modem/phone in another (remote
SIM). The hooks are already in place — search the code for # TODO remsim:
simbank_sims.remsim_bank_id/remsim_slot_nr/remsim_mapped_cardem(nullable columns, surfaced into_dictand the TSSimRowtype asnull),- the agent's per-reader
in_useflag already distinguishes a reader held by abankd/emulation session — that's the reader phase 2 will map, PATCH /api/simbank/sims/<iccid>is where editing the remsim mapping will be wired.
Nothing here drives remsim today; the shape is reserved so the mapping slots in without reworking the inventory hot path.