OTA inner-APDU SWs from SIM-domain applications surfaced as 'Unknown
status word' (e.g. 9404 on a failed SELECT inside an authenticated
B00000 packet). SW_MAP.generic gains the complete TS 51.011 §9.4
families, so they decode in every resp-cmd context:
- 9200/9240 memory management (retry / memory problem)
- 9400/9402/9404/9408 referencing management (no EF selected,
out of range, file ID/pattern not found, file inconsistent)
- 9802/9804/9808/9810/9840/9850 security management (CHV/access-
condition/invalidation contradictions and blocked states; 9850
also added to generic alongside its gp/uicc copy)
- 9EXX/9FXX data-download error length / response length wildcards
Exact keys safely coexist with the 92XX proactive wildcard (exact
match wins). TS 102 221 sweep found no further stragglers: 63C0-C9
PIN retry counters are covered by the existing 63CX wildcard.
Secured packet page: enforce fresh packet per send
- any change to APDU/TAR/CNTR/keys/padding/SPI now clears the secured
packet textarea via spInvalidate(), so Send cannot reuse a stale
packet ('No secured packet to send' guards the path)
- on por_ok the CNTR field auto-increments (10-digit hex normalized,
wraps modulo 2^40) and the textarea clears; security-error verdicts
leave both untouched for retry after fixing the cause
SIM/USIM SELECT: P1/P2 per spec + live RFM idioms
- USIM FID selects requested no response data (P2=0C) - that coding is
reserved for the select-MF-by-empty-data special case; ordinary FID
selects now request the FCP template (P2=04) with Le='00' per
TS 102 221 Table 11.2 / pySim sel_ctrl convention; preset tables
fixed in both genSimUsim and updateP1P2Display
- path method gains base selector: from MF (P1=08) / from current DF
(P1=09) - reproduces the dominant live RFM idiom 09/0C
- 'silent' checkbox on fid/path/chain emits P2=0C without Le for hops
where the FCI is not needed
- chain syntax gains GET RESPONSE hops: 'C0' emits CLA C0 00 00 00 and
'C0:NN' sets explicit Le, enabling classic SELECT -> 9FXX ->
GET RESPONSE pairs in a single secured packet so the PoR carries the
actual FCI/response bytes instead of a bare length SW
- SW dictionary: 9FXX reworded to point at GET RESPONSE
- placeholders show the new chain syntax
- findings & backport decisions written to ~/WSL/RFM_notes.md
response_map.test.js: +8 assertions across the new families.
sim.test.js: USIM FID expectation updated; silent/base/chain cases.
apdu_parse.test.js: live capture lines 1-3 as regression fixtures.
Version 1.9.5 -> 1.9.6 everywhere; SW cache otaman-v16 -> otaman-v17
7.0 KiB
pysim-otaman-server — HTTP API reference
The server exposes a JSON HTTP API under /api/*. All responses carry
Access-Control-Allow-Origin: * (plus Access-Control-Allow-Private-Network: true
on the preflight), so the API is reachable from a separately-hosted PWA.
Version compatibility
| Server | PWA (OTAMan) | Status |
|---|---|---|
| 1.x.x | 1.x.x | ✅ Compatible |
| 0.x.x | 1.x.x | ❌ Outdated — update server |
| 2.x.x+ | 1.x.x | ⚠️ Server newer — update PWA |
The server reports its version via GET /api/version. The PWA checks this on
connect and warns if versions are incompatible.
Endpoints
| Endpoint | Method | Purpose |
|---|---|---|
/api/version |
GET | Server version string |
/api/status |
GET | Card reader + card info + current selection |
/api/command |
POST | pySim command (equip, status, tree, etc.) |
/api/commands |
GET | List available pySim commands |
/api/tree |
POST | File tree browser for given FID/name |
/api/select |
POST | Select a file by name or FID |
/api/read |
POST | Read file content |
/api/write |
POST | Write raw hex data to a file |
/api/apdu |
POST | Raw APDU send |
/api/help |
POST | pySim help for a given command |
/api/send-ota |
POST | SCP80 OTA secured packet delivery |
/api/sp-verify |
POST | Verify secured packet against pySim reference |
/api/menu |
GET | Current STK menu (title + items + active) |
/api/menu-select |
POST | ENVELOPE(Menu Selection) with item_id |
/api/menu-respond |
POST | TERMINAL RESPONSE for paused STK command |
/api/stk-status |
GET | STK session state (active/pending/type) |
/api/events |
GET | Event list from SET UP EVENT LIST |
/api/event-send |
POST | Send ENVELOPE(Event Download) |
/api/proactive-log |
GET | Last 50 proactive commands |
/api/status-poll |
POST | Manual STATUS poll + FETCH if 91XX |
/api/rescue |
POST | Re-send TERMINAL PROFILE to recover CAT session |
/api/poll-status |
GET | Background STATUS polling state |
/api/poll-toggle |
POST | Enable/disable background polling |
/api/pli-qualifiers |
GET | List of qualifier codes with descriptions |
/api/pli-dict |
GET | Current dictionary (hex values per qualifier) |
/api/pli-dict |
POST | Update dictionary entries |
Endpoint details
GET /api/version
Returns server version for compatibility checking.
Example response:
{"version": "1.9.6"}
GET /api/status
Card reader, card type, current selection, and card state.
GET /api/commands
List all available shell commands for the current card profile.
POST /api/command
Execute any pysim-shell command.
{"cmd": "select MF"}
Returns:
{"output": "..."}
POST /api/apdu
Send a raw APDU to the card.
{"apdu": "00A4040000..."}
Returns:
{"response": "...", "sw": "9000"}
POST /api/help
Get structured help for a shell command.
{"cmd": "apdu"}
Returns:
{"usage": "apdu [-h] [--expect-sw EXPECT_SW] [--raw] APDU", "description": "...", "args": [{"name": "APDU", "type": "positional", "help": "..."}]}
POST /api/send-ota
Send an OTA command (SCP80) to the card via SMS-PP-DOWNLOAD ENVELOPE. The secured packet is delivered in an SMS-DELIVER TPDU wrapped in an ENVELOPE command.
Request body:
{
"sp": "00201516011515b00000...",
"spi1": "16",
"spi2": "01",
"kic": "15",
"kid": "15",
"tar": "b00000",
"cntr": "0000000001",
"kicKey": "D6FCC023...",
"kidKey": "1B07E7E0..."
}
Response (delivery PoR):
{"success": true, "sw": "9000", "response_data": "027100000e0a...",
"por": {"response_status": "por_ok", "tar": "B00000", "pcntr": 0,
"decoded": {"number_of_commands": 1, "last_status_word": "6e00",
"last_response_data": ""}}}
Response (submit PoR): PoR is extracted from the SMS-SUBMIT TPDU
fetched via a proactive command (FETCH). The response contains the
same por structure if decoding succeeds.
The SPI2 por_in_submit bit (0x20) selects submit-mode PoR.
POST /api/sp-verify
Cross-check a secured packet against pySim's OtaDialectSms.encode_cmd
reference. Returns the JS-generated packet, pySim reference, a match flag,
and the decoded SPI fields.
{"spi1": "16", "spi2": "01", "kic": "15", "kid": "15", "tar": "b00000",
"cntr": "0000000001", "apdu": "00a40000023f00",
"kicKey": "D6FCC023...", "kidKey": "1B07E7E0..."}
Response:
{"js_sp": "...", "py_sp": "...", "match": true,
"diffs": [], "spi": {"counter": "counter_must_be_higher", ...}}
GET /api/menu
Returns the SIM Toolkit SETUP MENU captured from the card's TERMINAL PROFILE
response at startup. Empty {"items": []} if the card didn't send a menu.
Response:
{"command_number": 1, "items": [{"id": 128, "text": "Настройки/Settings"}],
"title": "Alfa Mobile", "active": false}
POST /api/menu-select
Sends an ENVELOPE(MENU SELECTION) with the selected item ID, then handles
the card's proactive response (DISPLAY TEXT or SELECT ITEM).
{"item_id": 128}
Response:
{"type": "display_text", "text": "Hello", "sw": "9122"}
or
{"type": "select_item", "items": [{"id": 1, "text": "Sub-menu"}], "sw": "9122"}
POST /api/menu-respond
Sends TERMINAL RESPONSE to the current proactive command with the given result
code. Continues the proactive chain if the card responds with 91XX.
{"result": "ok", "item_id": 1}
result |
TERMINAL RESPONSE code | Meaning |
|---|---|---|
ok |
0x00 |
Command performed successfully |
back |
0x12 |
Backward move requested |
cancel |
0x10 |
Proactive session terminated |
timeout |
0x11 |
No response from user |
GET /api/stk-status
Returns the current STK session state.
{"active": true, "pending": true, "pending_type": "select_item"}
POST /api/read
Read file content. Auto-detects transparent vs record files.
{"name": "EF.ICCID", "fid": "2FE2", "parent_sel": "3F00", "mode": "raw"}
Returns:
{"success": true, "sw": "9000", "file_type": "transparent", "data": "..."}
POST /api/write
Write raw hex data to a file.
{"name": "EF.ICCID", "fid": "2FE2", "data": "A0A1A2...", "parent_sel": "3F00"}
For record files:
{"name": "EF.ADN", "fid": "6F3A", "data": "A0A1...", "record_nr": 1, "parent_sel": "7F10"}
Returns:
{"success": true, "sw": "9000"}
POST /api/select
Select a file by name or FID, with optional parent selection.
{"name": "EF.ICCID", "fid": "2FE2", "parent_sel": "3F00"}
Returns:
{"name": "EF.ICCID", "fid": "2FE2", "file_type": "transparent", "exists": true}
POST /api/tree
Get directory listing with typed children.
{"name": "MF", "fid": "3F00"}
Returns:
{"exists": true, "name": "MF", "fid": "3F00", "file_type": "df", "children": [{"name": "EF.ICCID", "fid": "2fe2", "isDir": false}]}