net-sim: network-condition simulation in the Phone tab (v2.5.0)

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
This commit is contained in:
2026-09-19 22:24:57 +03:00
parent 158f1c676f
commit 0d92ea8f24
15 changed files with 1571 additions and 4 deletions
+15
View File
@@ -202,6 +202,21 @@ test('every help anchor used by the UI exists in help.html', () => {
assert.ok(html.includes("? 'scp81-scripts' : 'scp81-listener'"));
});
test('phone simulator has the network-simulation fieldset', () => {
assert.ok(html.includes('data-l10n="Network simulation"'));
for (const s of ['cold_boot', 'attach_eps', 'attach_2g', 'service_lost',
'limited_service', 'roaming_denied', 'churn', 'sms_received',
'cb_reconfig', 'authenticate']) {
assert.ok(html.includes("netSimRun('" + s + "')"), s);
}
assert.ok(html.includes('id="netsim-op-search"'));
assert.ok(html.includes('id="netsim-log"'));
assert.ok(html.includes('id="netsim-status"'));
assert.match(html, /id="netsim-mcc" value="001"/);
// scenario buttons need the card (the server writes real EFs)
assert.match(html, /data-needs="card" onclick="netSimRun\('service_lost'\)"/);
});
test('SCP81 listener exposes its four modes with the matching notes', () => {
for (const v of ['tls', 'redirect', 'passthru', 'dump']) {
assert.ok(html.includes('value="' + v + '"'), v);