ui: net-sim Home network button; compact HPLMNwAcT monitor row (v2.7.18)
- netstate.home_plmn(): the card's home network — EF.HPLMNwAcT first record (the HPLMN per TS 31.102 4.2.5), falling back to the IMSI with a 2-digit MNC. Exposed as state.network.home in /api/net-state. - The net-sim parameters get a Home network button next to Random roaming operator: it fills MCC/MNC and reports the source (EF.HPLMNwAcT/IMSI). - The monitor's HPLMNwAcT row now shows only the first network (code only) plus a '… +N' counter; the tooltip keeps the full decoded list with the access technologies. - tests: netstate home_plmn cases, netsim button handler, updated netstate expectations; help EN/RU.
This commit is contained in:
@@ -489,10 +489,10 @@
|
||||
<li><strong>Перенастройка CB</strong> — запись списков CBMI/CBMIR или их очистка (все FF).</li>
|
||||
<li><strong>AUTHENTICATE</strong> — команда AUTHENTICATE (3G/EPS/5G, <code class="font-mono text-sm">00 88 00 81 22</code>) с заданными или случайными RAND/AUTN и показ ответа (успех <code class="font-mono text-sm">DB</code> или ошибка синхронизации <code class="font-mono text-sm">DC</code> с AUTS).</li>
|
||||
</ul>
|
||||
<p class="text-sm mb-3"><strong>Параметры</strong> (свёрнуты) задают оператора (поиск по мировому списку MCC/MNC с сервера плюс выбор случайного роуминг-оператора), LAC/Cell ID/TAC/RAC, необязательные идентификаторы (пусто = случайно: TMSI, GUTI, KSI, KASME, Kc, счётчики NAS, алгоритм, RAND/AUTN), переключатели сценария (включая <strong>«Отказ: записать FPLMN»</strong>) и число циклов/задержку. Журнал шагов показывает каждую запись, ENVELOPE и AUTHENTICATE с их SW. Отправляются только UPDATE BINARY/RECORD, ENVELOPE и AUTHENTICATE — FPLMN записывается только сценарием постоянного отказа (TS 31.102 §4.2.16), а 5GS location-файлы не записываются; записи меняют карту и видны в последующих сравнениях снимков.</p>
|
||||
<p class="text-sm mb-3"><strong>Параметры</strong> (свёрнуты) задают оператора (поиск по мировому списку MCC/MNC с сервера плюс выбор случайного роуминг-оператора и кнопка <strong>«Домашняя сеть»</strong>, заполняющая HPLMN карты из первой записи EF.HPLMNwAcT с откатом на IMSI), LAC/Cell ID/TAC/RAC, необязательные идентификаторы (пусто = случайно: TMSI, GUTI, KSI, KASME, Kc, счётчики NAS, алгоритм, RAND/AUTN), переключатели сценария (включая <strong>«Отказ: записать FPLMN»</strong>) и число циклов/задержку. Журнал шагов показывает каждую запись, ENVELOPE и AUTHENTICATE с их SW. Отправляются только UPDATE BINARY/RECORD, ENVELOPE и AUTHENTICATE — FPLMN записывается только сценарием постоянного отказа (TS 31.102 §4.2.16), а 5GS location-файлы не записываются; записи меняют карту и видны в последующих сравнениях снимков.</p>
|
||||
|
||||
<h4 id="network-state" class="font-medium mb-1">Монитор сетевого состояния</h4>
|
||||
<p class="text-sm mb-3">Рядом с кнопками симуляции компактная панель <strong>«Сетевое состояние»</strong> показывает, что сейчас хранит карта и что было сэмулировано последним. В заголовке — <strong>сэмулированное состояние сервиса</strong>: <em>Не определено</em>, пока его не задаст сценарий или событие Location status, затем <em>Обычный сервис</em> (зелёный), <em>Ограниченный сервис</em> (жёлтый) или <em>Нет сервиса</em> (красный), с красной пометкой <em>PLMN не разрешён</em>, если location-файлы или EF.FPLMN указывают на отказ регистрации — плюс текущее <strong>местоположение</strong>: PLMN, страна и оператор (из необязательного мирового списка MCC/MNC, если он загружен), LAI/RAI/TAI и <strong>класс роуминга</strong> (<em>Домашняя сеть</em>, если PLMN совпадает с HPLMN; <em>Эквивалентная домашней</em>, если он есть в EF.EHPLMN; иначе <em>Гостевая (роуминг)</em>). Ниже — по одной компактной строке на контролируемый файл (IMSI, EHPLMN, SPDI, HPLMNwAcT, LOCI, PSLOCI, EPSLOCI, EPSNSC, CBMI, CBMIR, SMSstatus, FPLMN) с декодированной сводкой и признаком последнего обновления (<code class="font-mono text-sm">init</code>, <code class="font-mono text-sm">write</code>, <code class="font-mono text-sm">read</code>, <code class="font-mono text-sm">refresh</code>); при наведении — все декодированные поля; длинные списки PLMN (например, EF.HPLMNwAcT) сокращаются до первых сетей с пометкой <code class="font-mono text-sm">… +N</code>. Панель читает файлы один раз при подключении карты (только если ICCID читается), обновляет их на месте по записанным симулятором байтам, перечитывает EF.IMSI после каждого сценария и события Location status (мульти-IMSI апплеты) и никогда не опрашивает карту — кнопка <strong>«Обновить»</strong> перечитывает все файлы по требованию.</p>
|
||||
<p class="text-sm mb-3">Рядом с кнопками симуляции компактная панель <strong>«Сетевое состояние»</strong> показывает, что сейчас хранит карта и что было сэмулировано последним. В заголовке — <strong>сэмулированное состояние сервиса</strong>: <em>Не определено</em>, пока его не задаст сценарий или событие Location status, затем <em>Обычный сервис</em> (зелёный), <em>Ограниченный сервис</em> (жёлтый) или <em>Нет сервиса</em> (красный), с красной пометкой <em>PLMN не разрешён</em>, если location-файлы или EF.FPLMN указывают на отказ регистрации — плюс текущее <strong>местоположение</strong>: PLMN, страна и оператор (из необязательного мирового списка MCC/MNC, если он загружен), LAI/RAI/TAI и <strong>класс роуминга</strong> (<em>Домашняя сеть</em>, если PLMN совпадает с HPLMN; <em>Эквивалентная домашней</em>, если он есть в EF.EHPLMN; иначе <em>Гостевая (роуминг)</em>). Ниже — по одной компактной строке на контролируемый файл (IMSI, EHPLMN, SPDI, HPLMNwAcT, LOCI, PSLOCI, EPSLOCI, EPSNSC, CBMI, CBMIR, SMSstatus, FPLMN) с декодированной сводкой и признаком последнего обновления (<code class="font-mono text-sm">init</code>, <code class="font-mono text-sm">write</code>, <code class="font-mono text-sm">read</code>, <code class="font-mono text-sm">refresh</code>); при наведении — все декодированные поля; длинные списки PLMN сокращаются (EF.HPLMNwAcT показывает только первую сеть и пометку <code class="font-mono text-sm">… +N</code>, а технологии доступа — в подсказке). Панель читает файлы один раз при подключении карты (только если ICCID читается), обновляет их на месте по записанным симулятором байтам, перечитывает EF.IMSI после каждого сценария и события Location status (мульти-IMSI апплеты) и никогда не опрашивает карту — кнопка <strong>«Обновить»</strong> перечитывает все файлы по требованию.</p>
|
||||
|
||||
</section>
|
||||
|
||||
|
||||
+2
-2
@@ -489,10 +489,10 @@
|
||||
<li><strong>CB reconfig</strong> — write the CBMI/CBMIR message-ID lists or clear them (all FF).</li>
|
||||
<li><strong>AUTHENTICATE</strong> — send AUTHENTICATE (3G/EPS/5G, <code class="font-mono text-sm">00 88 00 81 22</code>) with the given or random RAND/AUTN and show the response (success <code class="font-mono text-sm">DB</code> or synchronisation failure <code class="font-mono text-sm">DC</code> with AUTS).</li>
|
||||
</ul>
|
||||
<p class="text-sm mb-3"><strong>Parameters</strong> (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 <strong>Rejection: write FPLMN</strong>) 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.</p>
|
||||
<p class="text-sm mb-3"><strong>Parameters</strong> (collapsed) provide the operator (searchable worldwide MCC/MNC list served from the server, plus a random roaming picker and a <strong>Home network</strong> button that fills the card’s HPLMN from EF.HPLMNwAcT’s first record, falling back to the IMSI), LAC/Cell ID/TAC/RAC, optional identity values (empty = random: TMSI, GUTI, KSI, KASME, Kc, NAS counts, algorithm, RAND/AUTN), the scenario toggles (including <strong>Rejection: write FPLMN</strong>) 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.</p>
|
||||
|
||||
<h4 id="network-state" class="font-medium mb-1">Network state monitor</h4>
|
||||
<p class="text-sm mb-3">Next to the simulation buttons a compact <strong>Network state</strong> panel shows what the card currently holds and what was last simulated. Its header carries the <strong>simulated service state</strong> — <em>Undefined</em> until a scenario or a Location status event sets it, then <em>Normal service</em> (green), <em>Limited service</em> (amber) or <em>No service</em> (red), with a red <em>PLMN not allowed</em> marker when the location files or EF.FPLMN show a rejection — plus the current <strong>location</strong>: PLMN, country and operator (from the optional worldwide MCC/MNC list when loaded), the LAI/RAI/TAI, and the <strong>roaming class</strong> (<em>Home</em> when the PLMN equals the HPLMN, <em>Home equivalent</em> when it is in EF.EHPLMN, otherwise <em>Guest</em>). 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 (<code class="font-mono text-sm">init</code>, <code class="font-mono text-sm">write</code>, <code class="font-mono text-sm">read</code>, <code class="font-mono text-sm">refresh</code>); hover for the full decoded fields — long PLMN lists (e.g. EF.HPLMNwAcT) are abbreviated to the first few networks plus a <code class="font-mono text-sm">… +N</code> 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 <strong>Refresh</strong> to re-read all files on demand.</p>
|
||||
<p class="text-sm mb-3">Next to the simulation buttons a compact <strong>Network state</strong> panel shows what the card currently holds and what was last simulated. Its header carries the <strong>simulated service state</strong> — <em>Undefined</em> until a scenario or a Location status event sets it, then <em>Normal service</em> (green), <em>Limited service</em> (amber) or <em>No service</em> (red), with a red <em>PLMN not allowed</em> marker when the location files or EF.FPLMN show a rejection — plus the current <strong>location</strong>: PLMN, country and operator (from the optional worldwide MCC/MNC list when loaded), the LAI/RAI/TAI, and the <strong>roaming class</strong> (<em>Home</em> when the PLMN equals the HPLMN, <em>Home equivalent</em> when it is in EF.EHPLMN, otherwise <em>Guest</em>). 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 (<code class="font-mono text-sm">init</code>, <code class="font-mono text-sm">write</code>, <code class="font-mono text-sm">read</code>, <code class="font-mono text-sm">refresh</code>); hover for the full decoded fields — long PLMN lists are abbreviated (EF.HPLMNwAcT shows only the first network plus a <code class="font-mono text-sm">… +N</code> counter, with the access technologies in the tooltip). 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 <strong>Refresh</strong> to re-read all files on demand.</p>
|
||||
|
||||
</section>
|
||||
|
||||
|
||||
+32
-2
@@ -972,6 +972,7 @@
|
||||
<input id="netsim-mnc" value="01" maxlength="3" class="w-16 font-mono border border-gray-300 dark:border-slate-600 text-sm rounded px-2 py-1 dark:bg-slate-800">
|
||||
</div>
|
||||
<button data-needs="server" onclick="netSimRandomOperator()" class="px-2 py-1 text-xs rounded bg-gray-200 dark:bg-slate-700 text-gray-700 dark:text-slate-300 hover:bg-gray-300 dark:hover:bg-slate-600 disabled:opacity-40 disabled:cursor-not-allowed" data-l10n="Random roaming operator">Random roaming operator</button>
|
||||
<button data-needs="card" onclick="netSimHomeOperator()" class="px-2 py-1 text-xs rounded bg-gray-200 dark:bg-slate-700 text-gray-700 dark:text-slate-300 hover:bg-gray-300 dark:hover:bg-slate-600 disabled:opacity-40 disabled:cursor-not-allowed" data-l10n="Home network">Home network</button>
|
||||
</div>
|
||||
<div class="text-xs text-gray-400 dark:text-slate-500" data-l10n="Sets the PLMN written to LOCI/PSLOCI/EPSLOCI. Used by EPS attach, 2G attach and SMS received (real LAI/RAI/TAI; SMS only when its location rewrite is enabled), and by Cold boot, Service lost, Limited service and Roaming denied (the dummy LOCI/PSLOCI keep this PLMN, the EPSLOCI dummy wipes the TAI PLMN). Churn, CB reconfig and AUTHENTICATE don't use it.">Sets the PLMN written to LOCI/PSLOCI/EPSLOCI. Used by EPS attach, 2G attach and SMS received (real LAI/RAI/TAI; SMS only when its location rewrite is enabled), and by Cold boot, Service lost, Limited service and Roaming denied (the dummy LOCI/PSLOCI keep this PLMN, the EPSLOCI dummy wipes the TAI PLMN). Churn, CB reconfig and AUTHENTICATE don't use it.</div>
|
||||
<div id="netsim-op-results" class="text-xs font-mono max-h-32 overflow-auto"></div>
|
||||
@@ -1413,7 +1414,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.17';
|
||||
const SIMPLE_VERSION = '2.7.18';
|
||||
document.getElementById('app-version').textContent = 'v' + SIMPLE_VERSION;
|
||||
|
||||
// ===== Tab switching =====
|
||||
@@ -12814,7 +12815,9 @@ function netStateSummary(key, entry) {
|
||||
} else if (key === 'ehplmn' || key === 'spdi' || key === 'fplmn') {
|
||||
text = netStatePlmnList(d, false, 3);
|
||||
} else if (key === 'hplmnwact') {
|
||||
text = netStatePlmnList(d, true, 3);
|
||||
// Compact row: the first network only, plus a count of the hidden
|
||||
// entries (the tooltip carries the full decoded list with tech).
|
||||
text = netStatePlmnList(d, false, 1);
|
||||
} else if (NET_STATE_AREA[key]) {
|
||||
const [area, statusKey] = NET_STATE_AREA[key];
|
||||
const plmn = (d.mcc && d.mnc) ? d.mcc + '-' + d.mnc : null;
|
||||
@@ -12984,6 +12987,29 @@ async function netSimRandomOperator() {
|
||||
}
|
||||
}
|
||||
|
||||
// The card's home network: EF.HPLMNwAcT first record, else the IMSI (the
|
||||
// server resolves it; see netstate.home_plmn).
|
||||
async function netSimHomeOperator() {
|
||||
const statusEl = document.getElementById('netsim-status');
|
||||
try {
|
||||
const res = await pysimFetch('/api/net-state');
|
||||
const home = res && res.available && res.state && res.state.network ? res.state.network.home : null;
|
||||
if (!home) {
|
||||
statusEl.textContent = t('Home PLMN not available (no HPLMNwAcT/IMSI)');
|
||||
statusEl.className = 'text-xs text-yellow-600 mb-1';
|
||||
return;
|
||||
}
|
||||
document.getElementById('netsim-mcc').value = home.mcc;
|
||||
document.getElementById('netsim-mnc').value = home.mnc;
|
||||
statusEl.textContent = t('Home network') + ': ' + home.mcc + '/' + home.mnc +
|
||||
' (' + t(home.source === 'imsi' ? 'from IMSI' : 'from EF.HPLMNwAcT') + ')';
|
||||
statusEl.className = 'text-xs text-gray-500 dark:text-slate-400 mb-1';
|
||||
} catch (e) {
|
||||
statusEl.textContent = t('Error') + ': ' + e.message;
|
||||
statusEl.className = 'text-xs text-red-500 mb-1';
|
||||
}
|
||||
}
|
||||
|
||||
// ===== i18n =====
|
||||
const LANG_RU = {
|
||||
'Generate APDU': 'Сгенерировать APDU',
|
||||
@@ -13281,6 +13307,10 @@ const LANG_RU = {
|
||||
'No steps': 'Нет шагов',
|
||||
'skip': 'пропуск',
|
||||
'Operator list not loaded (server --mcc-mnc-list)': 'Список операторов не загружен (--mcc-mnc-list на сервере)',
|
||||
'Home network': 'Домашняя сеть',
|
||||
'from EF.HPLMNwAcT': 'из EF.HPLMNwAcT',
|
||||
'from IMSI': 'из IMSI',
|
||||
'Home PLMN not available (no HPLMNwAcT/IMSI)': 'Домашняя сеть недоступна (нет HPLMNwAcT/IMSI)',
|
||||
'No commands logged yet.': 'Команды пока не регистрировались.',
|
||||
'Send event notification to the card?': 'Отправить уведомление о событии на карту?',
|
||||
'This event type is not yet supported.': 'Этот тип события пока не поддерживается.',
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
const CACHE = 'simple-v220';
|
||||
const CACHE = 'simple-v221';
|
||||
const URLS = [
|
||||
'index.html',
|
||||
'help.html',
|
||||
|
||||
@@ -6,7 +6,7 @@ const path = require('node:path');
|
||||
const html = fs.readFileSync(path.join(__dirname, '..', 'index.html'), 'utf8');
|
||||
|
||||
function extractFunc(src, name) {
|
||||
const re = new RegExp('function\\s+' + name + '\\s*\\([^)]*\\)\\s*\\{');
|
||||
const re = new RegExp('(?:async\\s+)?function\\s+' + name + '\\s*\\([^)]*\\)\\s*\\{');
|
||||
const m = re.exec(src);
|
||||
if (!m) throw new Error('function ' + name + ' not found');
|
||||
let i = m.index + m[0].length - 1;
|
||||
@@ -22,7 +22,7 @@ function extractFunc(src, name) {
|
||||
}
|
||||
|
||||
let code = '';
|
||||
for (const f of ['netSimRandomHex', 'netSimDice', 'netSimVal', 'netSimChk', 'netSimParams', 'netSimStepText']) {
|
||||
for (const f of ['netSimRandomHex', 'netSimDice', 'netSimVal', 'netSimChk', 'netSimParams', 'netSimStepText', 'netSimHomeOperator']) {
|
||||
code += extractFunc(html, f) + '\n';
|
||||
}
|
||||
code += 'function t(k){return k;}\n';
|
||||
@@ -84,3 +84,44 @@ test('netSimStepText renders the step log lines', () => {
|
||||
netSimStepText({ action: 'authenticate', parsed: { type: 'synchronisation_failure' }, sw: '9000', response: 'DC10AA' }),
|
||||
/^AUTHENTICATE synchronisation_failure → SW 9000 DC10AA$/);
|
||||
});
|
||||
|
||||
test('netSimHomeOperator fills the home PLMN and reports its source', async () => {
|
||||
_fields = {
|
||||
'netsim-mcc': { value: '' },
|
||||
'netsim-mnc': { value: '' },
|
||||
'netsim-status': { textContent: '', className: '' },
|
||||
};
|
||||
globalThis.pysimFetch = async () => ({
|
||||
available: true,
|
||||
state: { network: { home: { mcc: '250', mnc: '99', source: 'hplmnwact' } } },
|
||||
});
|
||||
await netSimHomeOperator();
|
||||
assert.strictEqual(_fields['netsim-mcc'].value, '250');
|
||||
assert.strictEqual(_fields['netsim-mnc'].value, '99');
|
||||
assert.ok(_fields['netsim-status'].textContent.includes('250/99'));
|
||||
assert.ok(_fields['netsim-status'].textContent.includes('from EF.HPLMNwAcT'));
|
||||
assert.ok(_fields['netsim-status'].className.includes('text-gray-500'));
|
||||
});
|
||||
|
||||
test('netSimHomeOperator reports the IMSI source and warns when unavailable', async () => {
|
||||
_fields = {
|
||||
'netsim-mcc': { value: '' },
|
||||
'netsim-mnc': { value: '' },
|
||||
'netsim-status': { textContent: '', className: '' },
|
||||
};
|
||||
globalThis.pysimFetch = async () => ({
|
||||
available: true,
|
||||
state: { network: { home: { mcc: '228', mnc: '06', source: 'imsi' } } },
|
||||
});
|
||||
await netSimHomeOperator();
|
||||
assert.ok(_fields['netsim-status'].textContent.includes('from IMSI'));
|
||||
// no home PLMN -> the fields stay and the status warns
|
||||
_fields['netsim-mcc'].value = '001';
|
||||
_fields['netsim-mnc'].value = '01';
|
||||
globalThis.pysimFetch = async () => ({ available: true, state: { network: { home: null } } });
|
||||
await netSimHomeOperator();
|
||||
assert.strictEqual(_fields['netsim-mcc'].value, '001');
|
||||
assert.strictEqual(_fields['netsim-mnc'].value, '01');
|
||||
assert.ok(_fields['netsim-status'].textContent.includes('not available'));
|
||||
assert.ok(_fields['netsim-status'].className.includes('text-yellow-600'));
|
||||
});
|
||||
|
||||
@@ -74,7 +74,7 @@ test('netStateSummary renders decoded file contents compactly', () => {
|
||||
const fplmn = netStateSummary('fplmn', { present: true, kind: 'transparent', data: 'AA' });
|
||||
assert.strictEqual(fplmn.text, '262-01, 246-81');
|
||||
const hplmn = netStateSummary('hplmnwact', { present: true, kind: 'transparent', data: 'AA' });
|
||||
assert.strictEqual(hplmn.text, '262-01 (LTE)');
|
||||
assert.strictEqual(hplmn.text, '262-01');
|
||||
const absent = netStateSummary('loci', { present: false });
|
||||
assert.strictEqual(absent.text, '—');
|
||||
assert.strictEqual(absent.title, 'not present');
|
||||
@@ -110,7 +110,8 @@ test('netStateSummary abbreviates a long HPLMNwAcT list', () => {
|
||||
] },
|
||||
});
|
||||
const s = netStateSummary('hplmnwact', { present: true, kind: 'transparent', data: 'AA' });
|
||||
assert.strictEqual(s.text, '250-99 (LTE), 250-32 (LTE), 250-54 (LTE) … +2');
|
||||
// first network only (no access tech in the row), plus the hidden count
|
||||
assert.strictEqual(s.text, '250-99 … +4');
|
||||
});
|
||||
|
||||
test('netStateRender paints the service badge, location and rows', () => {
|
||||
|
||||
Reference in New Issue
Block a user