From ef3726441b213542ace2a01b66ff57ab32aadd27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BD=D1=82=D0=BE=D0=BD=20=D0=A2=D1=80=D0=BE=D1=88?= =?UTF-8?q?=D0=B8=D0=BD?= Date: Mon, 21 Sep 2026 00:18:36 +0300 Subject: [PATCH] ui: decode and abbreviate long PLMN lists in the network monitor (v2.7.16) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- frontend/help-ru.html | 2 +- frontend/help.html | 2 +- frontend/index.html | 18 +++++++++++------- frontend/sw.js | 2 +- frontend/tests/ef_decode.test.js | 3 +++ frontend/tests/netstate.test.js | 24 ++++++++++++++++++++++++ pyproject.toml | 2 +- pysim_simple_server/server.py | 2 +- 8 files changed, 43 insertions(+), 12 deletions(-) diff --git a/frontend/help-ru.html b/frontend/help-ru.html index 23b14b4..6a3d551 100644 --- a/frontend/help-ru.html +++ b/frontend/help-ru.html @@ -492,7 +492,7 @@

Параметры (свёрнуты) задают оператора (поиск по мировому списку MCC/MNC с сервера плюс выбор случайного роуминг-оператора), LAC/Cell ID/TAC/RAC, необязательные идентификаторы (пусто = случайно: TMSI, GUTI, KSI, KASME, Kc, счётчики NAS, алгоритм, RAND/AUTN), переключатели сценария (включая «Отказ: записать FPLMN») и число циклов/задержку. Журнал шагов показывает каждую запись, ENVELOPE и AUTHENTICATE с их SW. Отправляются только UPDATE BINARY/RECORD, ENVELOPE и AUTHENTICATE — FPLMN записывается только сценарием постоянного отказа (TS 31.102 §4.2.16), а 5GS location-файлы не записываются; записи меняют карту и видны в последующих сравнениях снимков.

Монитор сетевого состояния

-

Рядом с кнопками симуляции компактная панель «Сетевое состояние» показывает, что сейчас хранит карта и что было сэмулировано последним. В заголовке — сэмулированное состояние сервиса: Не определено, пока его не задаст сценарий или событие Location status, затем Обычный сервис (зелёный), Ограниченный сервис (жёлтый) или Нет сервиса (красный), с красной пометкой PLMN не разрешён, если location-файлы или EF.FPLMN указывают на отказ регистрации — плюс текущее местоположение: PLMN, страна и оператор (из необязательного мирового списка MCC/MNC, если он загружен), LAI/RAI/TAI и класс роуминга (Домашняя сеть, если PLMN совпадает с HPLMN; Эквивалентная домашней, если он есть в EF.EHPLMN; иначе Гостевая (роуминг)). Ниже — по одной компактной строке на контролируемый файл (IMSI, EHPLMN, SPDI, HPLMNwAcT, LOCI, PSLOCI, EPSLOCI, EPSNSC, CBMI, CBMIR, SMSstatus, FPLMN) с декодированной сводкой и признаком последнего обновления (init, write, read, refresh); при наведении — все декодированные поля. Панель читает файлы один раз при подключении карты (только если ICCID читается), обновляет их на месте по записанным симулятором байтам, перечитывает EF.IMSI после каждого сценария и события Location status (мульти-IMSI апплеты) и никогда не опрашивает карту — кнопка «Обновить» перечитывает все файлы по требованию.

+

Рядом с кнопками симуляции компактная панель «Сетевое состояние» показывает, что сейчас хранит карта и что было сэмулировано последним. В заголовке — сэмулированное состояние сервиса: Не определено, пока его не задаст сценарий или событие Location status, затем Обычный сервис (зелёный), Ограниченный сервис (жёлтый) или Нет сервиса (красный), с красной пометкой PLMN не разрешён, если location-файлы или EF.FPLMN указывают на отказ регистрации — плюс текущее местоположение: PLMN, страна и оператор (из необязательного мирового списка MCC/MNC, если он загружен), LAI/RAI/TAI и класс роуминга (Домашняя сеть, если PLMN совпадает с HPLMN; Эквивалентная домашней, если он есть в EF.EHPLMN; иначе Гостевая (роуминг)). Ниже — по одной компактной строке на контролируемый файл (IMSI, EHPLMN, SPDI, HPLMNwAcT, LOCI, PSLOCI, EPSLOCI, EPSNSC, CBMI, CBMIR, SMSstatus, FPLMN) с декодированной сводкой и признаком последнего обновления (init, write, read, refresh); при наведении — все декодированные поля; длинные списки PLMN (например, EF.HPLMNwAcT) сокращаются до первых сетей с пометкой … +N. Панель читает файлы один раз при подключении карты (только если ICCID читается), обновляет их на месте по записанным симулятором байтам, перечитывает EF.IMSI после каждого сценария и события Location status (мульти-IMSI апплеты) и никогда не опрашивает карту — кнопка «Обновить» перечитывает все файлы по требованию.

diff --git a/frontend/help.html b/frontend/help.html index 7f8e9c1..85a61fe 100644 --- a/frontend/help.html +++ b/frontend/help.html @@ -492,7 +492,7 @@

Parameters (collapsed) provide the operator (searchable worldwide MCC/MNC list served from the server, plus a random roaming picker), LAC/Cell ID/TAC/RAC, optional identity values (empty = random: TMSI, GUTI, KSI, KASME, Kc, NAS counts, algorithm, RAND/AUTN), the scenario toggles (including Rejection: write FPLMN) and the churn count/delay. The step log lists every write, ENVELOPE and AUTHENTICATE with its SW. Only UPDATE BINARY/RECORD, ENVELOPE and AUTHENTICATE are sent — FPLMN is written only by the permanent rejection scenario (TS 31.102 §4.2.16) and the 5GS location files are never written; the writes change the card and are visible to later snapshot comparisons.

Network state monitor

-

Next to the simulation buttons a compact Network state panel shows what the card currently holds and what was last simulated. Its header carries the simulated service stateUndefined until a scenario or a Location status event sets it, then Normal service (green), Limited service (amber) or No service (red), with a red PLMN not allowed marker when the location files or EF.FPLMN show a rejection — plus the current location: PLMN, country and operator (from the optional worldwide MCC/MNC list when loaded), the LAI/RAI/TAI, and the roaming class (Home when the PLMN equals the HPLMN, Home equivalent when it is in EF.EHPLMN, otherwise Guest). Below it, one compact line per monitored file (IMSI, EHPLMN, SPDI, HPLMNwAcT, LOCI, PSLOCI, EPSLOCI, EPSNSC, CBMI, CBMIR, SMSstatus, FPLMN) with its decoded summary and how it was last updated (init, write, read, refresh); hover for the full decoded fields. The panel reads the files once at equip (only when the ICCID was readable), updates them in place from the bytes the simulator wrote, re-reads EF.IMSI after every scenario and Location-status event (multi-IMSI applets) and never polls the card — use Refresh to re-read all files on demand.

+

Next to the simulation buttons a compact Network state panel shows what the card currently holds and what was last simulated. Its header carries the simulated service stateUndefined until a scenario or a Location status event sets it, then Normal service (green), Limited service (amber) or No service (red), with a red PLMN not allowed marker when the location files or EF.FPLMN show a rejection — plus the current location: PLMN, country and operator (from the optional worldwide MCC/MNC list when loaded), the LAI/RAI/TAI, and the roaming class (Home when the PLMN equals the HPLMN, Home equivalent when it is in EF.EHPLMN, otherwise Guest). Below it, one compact line per monitored file (IMSI, EHPLMN, SPDI, HPLMNwAcT, LOCI, PSLOCI, EPSLOCI, EPSNSC, CBMI, CBMIR, SMSstatus, FPLMN) with its decoded summary and how it was last updated (init, write, read, refresh); hover for the full decoded fields — long PLMN lists (e.g. EF.HPLMNwAcT) are abbreviated to the first few networks plus a … +N counter. The panel reads the files once at equip (only when the ICCID was readable), updates them in place from the bytes the simulator wrote, re-reads EF.IMSI after every scenario and Location-status event (multi-IMSI applets) and never polls the card — use Refresh to re-read all files on demand.

diff --git a/frontend/index.html b/frontend/index.html index 092d8c3..f087bf5 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -1413,7 +1413,7 @@ // ===== Version ===== // Single source of truth for the PWA version: shown in the header and used // by the server version check in pysimConnect(). -const SIMPLE_VERSION = '2.7.15'; +const SIMPLE_VERSION = '2.7.16'; document.getElementById('app-version').textContent = 'v' + SIMPLE_VERSION; // ===== Tab switching ===== @@ -12522,7 +12522,7 @@ const EF_DECODERS = [ function efFindDecoder(name, fid) { let n = (name || '').toUpperCase(); if (n.includes('/')) n = n.split('/').pop(); - for (const d of EF_DECODERS) if (d.name === n) return d; + for (const d of EF_DECODERS) if (d.name.toUpperCase() === n) return d; const f = (fid || '').toLowerCase(); if (f) for (const d of EF_DECODERS) if (d.fid === f) return d; return null; @@ -12772,9 +12772,13 @@ function netStateFlattenTitle(decoded) { return efFlatten(data).map(r => (r[0] ? r[0] + ': ' : '') + r[1]).join(' · '); } -function netStatePlmnList(d, withTech) { - return (d.plmns || []).map(p => p.mcc + '-' + p.mnc + - (withTech && p.access_tech ? ' (' + p.access_tech + ')' : '')).join(', '); +// Compact PLMN list for the monitor rows: `max` keeps the row short (the +// full decoded list stays in the row's title tooltip). +function netStatePlmnList(d, withTech, max) { + const list = (d.plmns || []).map(p => p.mcc + '-' + p.mnc + + (withTech && p.access_tech ? ' (' + p.access_tech + ')' : '')); + if (max && list.length > max) return list.slice(0, max).join(', ') + ' … +' + (list.length - max); + return list.join(', '); } function netStateSummary(key, entry) { @@ -12797,9 +12801,9 @@ function netStateSummary(key, entry) { if (key === 'imsi') { text = d.imsi || ''; } else if (key === 'ehplmn' || key === 'spdi' || key === 'fplmn') { - text = netStatePlmnList(d); + text = netStatePlmnList(d, false, 3); } else if (key === 'hplmnwact') { - text = netStatePlmnList(d, true); + text = netStatePlmnList(d, true, 3); } else if (NET_STATE_AREA[key]) { const [area, statusKey] = NET_STATE_AREA[key]; const plmn = (d.mcc && d.mnc) ? d.mcc + '-' + d.mnc : null; diff --git a/frontend/sw.js b/frontend/sw.js index b6df5ac..4c0a54d 100644 --- a/frontend/sw.js +++ b/frontend/sw.js @@ -1,4 +1,4 @@ -const CACHE = 'simple-v218'; +const CACHE = 'simple-v219'; const URLS = [ 'index.html', 'help.html', diff --git a/frontend/tests/ef_decode.test.js b/frontend/tests/ef_decode.test.js index aed0be7..affd86d 100644 --- a/frontend/tests/ef_decode.test.js +++ b/frontend/tests/ef_decode.test.js @@ -159,6 +159,9 @@ test('decoder registry resolves by name first, then FID; unknown returns null', assert.strictEqual(efFindDecoder('ADF.USIM/EF.LOCI', null).name, 'EF.LOCI'); assert.strictEqual(efFindDecoder('EF.SUME', null).fid, '6f54'); assert.strictEqual(efFindDecoder('EF.NOPE', 'ABCD'), null); + // mixed-case registry names resolve case-insensitively without a FID + assert.strictEqual(efFindDecoder('EF.HPLMNwAcT', null).fid, '6f62'); + assert.strictEqual(efFindDecoder('ef.plmnwact', null).name, 'EF.PLMNwAcT'); assert.strictEqual(efFidFromPath('ADF.USIM/6F07'), '6F07'); assert.strictEqual(efFidFromPath('MF/7F10/6F3A'), '6F3A'); assert.strictEqual(efFidFromPath('ADF.USIM'), null); diff --git a/frontend/tests/netstate.test.js b/frontend/tests/netstate.test.js index 0292d4c..0d963c8 100644 --- a/frontend/tests/netstate.test.js +++ b/frontend/tests/netstate.test.js @@ -89,6 +89,30 @@ test('netStateSummary marks rejection statuses', () => { assert.strictEqual(loci.text, 'LAI 246-81/FFFE · PLMN not allowed'); }); +test('netStatePlmnList limits long lists with a count', () => { + const d = { plmns: [ + { mcc: '262', mnc: '01' }, { mcc: '246', mnc: '81' }, + { mcc: '204', mnc: '04' }, { mcc: '234', mnc: '15' }, + ] }; + assert.strictEqual(netStatePlmnList(d, false, 3), '262-01, 246-81, 204-04 … +1'); + assert.strictEqual(netStatePlmnList(d, false), '262-01, 246-81, 204-04, 234-15'); +}); + +test('netStateSummary abbreviates a long HPLMNwAcT list', () => { + setup(); + decoder({ + 'EF.HPLMNwAcT': { plmns: [ + { mcc: '250', mnc: '99', access_tech: 'LTE' }, + { mcc: '250', mnc: '32', access_tech: 'LTE' }, + { mcc: '250', mnc: '54', access_tech: 'LTE' }, + { mcc: '262', mnc: '01', access_tech: 'GSM' }, + { mcc: '246', mnc: '81', access_tech: 'GSM' }, + ] }, + }); + const s = netStateSummary('hplmnwact', { present: true, kind: 'transparent', data: 'AA' }); + assert.strictEqual(s.text, '250-99 (LTE), 250-32 (LTE), 250-54 (LTE) … +2'); +}); + test('netStateRender paints the service badge, location and rows', () => { const els = setup(); decoder({ 'EF.IMSI': { imsi: '262011234567890' } }); diff --git a/pyproject.toml b/pyproject.toml index adcc571..1b77bbd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "pysim-simple-server" -version = "2.7.15" +version = "2.7.16" description = "HTTP REST server wrapping pysim for the SIMple PWA" requires-python = ">=3.8" # pysim is a git-only dependency installed explicitly by setup.bat/setup.sh. diff --git a/pysim_simple_server/server.py b/pysim_simple_server/server.py index b72ee3f..b6d07fb 100644 --- a/pysim_simple_server/server.py +++ b/pysim_simple_server/server.py @@ -26,7 +26,7 @@ from osmocom.tlv import BER_TLV_IE from osmocom.utils import rpad -VERSION = '2.7.15' +VERSION = '2.7.16' MAX_ENVELOPE_SEGMENTS = 5 # max SMS segments for outgoing C-APDU in ENVELOPE