terminal profile: spec-audited bits, fixed layout, more presets (v2.2.10)
TERMINAL PROFILE form (Phone simulator): - The compact block no longer shows the hex value (no room); Send + Configure + status remain. Preset selector sits above the hex field. - Bits are never spread horizontally: each byte is one vertical list. Byte blocks are placed by the fixed TP_LAYOUT groups - bytes 1-12 in 2 columns, 13-16 in 4, 17-18 in 2, 19-21 in 3, 22-25 in 2, 26-28 in 3, 29-30 in 2, 31+ one per row - in strict byte order. Bit descriptions audited against the pinned specs: - Table regenerated from ETSI TS 102 223 V18.3.0 5.2 (bytes 1-39) with the 3GPP-defined bits named per ETSI TS 131 111 V18.12.0 5.2; the "reserved by 3GPP" placeholders are gone (tests guard this). - Fixes found in the audit: byte 7 b6 PERFORM CARD APDU (pySim had RESET), MO short message control by USIM, USSD string DO support in Call Control by USIM, the earlier "Data available" typo. - Presets: project default + Quectel example + Samsung S21+ 5G, Samsung A55 5G, Xiaomi Redmi Note 10 LTE, Sony Xperia Z5c LTE, Huawei E5573c/M150 (LTE), Huawei E173 3G modem, Nokia 7210 2G (9 total). Also fixes a page-load TDZ regression: the custom-files init (which calls t()) now runs after the language init (guard test added). SW cache otaman-v174; help EN/RU updated; tests 236 Python + 374 frontend.
This commit is contained in:
@@ -432,7 +432,7 @@
|
||||
<p class="text-sm mb-2">Хронологический список извлечённых проактивных команд. Каждая строка показывает время, код типа, имя и декодированный квалификатор (для команд, у которых он есть). Раскрытие строки показывает декодированную команду: текст и пункты DISPLAY TEXT / SELECT ITEM / SET UP MENU, параметры BIP-каналов, действия TIMER MANAGEMENT, названия квалификаторов PROVIDE LOCAL INFORMATION (все стандартные, включая ESN 07, MEID 0B и Supported RATs 1A) и SEND SHORT MESSAGE — разбирается SMS TPDU (тип, TP-MR, TP-DA, TP-PID, TP-DCS, срок действия, TP-UDL), а пользовательские данные показываются как текст для текстовых кодировок (GSM 7-бит, UCS2, 8-бит) или как secured-пакет (TS 31.115) при TP-PID = <code class="font-mono text-sm">7F</code> (SIM data download); заголовки конкатенации UDH декодируются. Для команд с данными ответа показывается строка <code class="font-mono text-sm">Ответ:</code> с байтами TERMINAL RESPONSE (без служебных TLV); ответы PROVIDE LOCAL INFORMATION декодируются через словарь данных PLI.</p>
|
||||
|
||||
<h3 id="terminal-profile" class="text-lg font-medium mb-2">6.4 TERMINAL PROFILE</h3>
|
||||
<p class="text-sm mb-3">Блок <strong>TERMINAL PROFILE</strong> (рядом с «STATUS и опрос») показывает действующий профиль (hex + число байт) и кнопки <strong>«Отправить»</strong> (повторная отправка, как «Спасение») и <strong>«Настроить»</strong>. В диалоге настройки: селектор пресетов (модели устройств, например профиль BIP-совместимого аппарата из этого проекта), поле hex и форма с флажком на каждый бит профиля по <strong>TS 102 223 §5.2</strong> (байты 1–33; далее — обобщённые подписи); переключение бита обновляет hex, а правка hex перерисовывает форму — поле hex основное, неизвестные байты и биты сохраняются. Кнопка <strong>«Применить»</strong> отправляет новое значение на сервер (и далее на карту), сбрасывая STK-сессию как «Спасение»; изменение хранится только в памяти (значение <code class="font-mono text-sm">--terminal-profile</code> — стартовое по умолчанию).</p>
|
||||
<p class="text-sm mb-3">Блок <strong>TERMINAL PROFILE</strong> (рядом с «STATUS и опрос») содержит кнопки <strong>«Отправить»</strong> (повторная отправка, как «Спасение») и <strong>«Настроить»</strong>. В диалоге настройки: селектор пресетов (модели устройств, например профиль BIP-совместимого аппарата из этого проекта), поле hex и форма с флажком на каждый бит профиля по <strong>TS 102 223 §5.2</strong> (байты 1–33; далее — обобщённые подписи); каждый байт — вертикальный список битов; блоки размещены фиксированными группами (байты 1–12 в 2 колонки, 13–16 в 4, 17–18 в 2, 19–21 в 3, 22–25 в 2, 26–28 в 3, 29–30 в 2, далее по одному в строке); переключение бита обновляет hex, а правка hex перерисовывает форму — поле hex основное, неизвестные байты и биты сохраняются. Кнопка <strong>«Применить»</strong> отправляет новое значение на сервер (и далее на карту), сбрасывая STK-сессию как «Спасение»; изменение хранится только в памяти (значение <code class="font-mono text-sm">--terminal-profile</code> — стартовое по умолчанию).</p>
|
||||
|
||||
<h3 id="status-polling" class="text-lg font-medium mb-2">6.5 Опрос STATUS</h3>
|
||||
<p class="text-sm mb-3">Кнопка <strong>Отправить STATUS</strong> отправляет STATUS (F2) вручную. Переключатель <strong>Опрос</strong> включает фоновый опрос: после настраиваемого интервала бездействия (аргумент сервера <code class="font-mono text-sm">--poll-interval</code>, 1–255 с, по умолчанию 30 с, <code class="font-mono text-sm">0</code> отключает опрос) сервер отправляет STATUS и обрабатывает любую ожидающую проактивную команду. При извлечении карты опрос останавливается, а состояние карты сбрасывается.</p>
|
||||
|
||||
+1
-1
@@ -432,7 +432,7 @@
|
||||
<p class="text-sm mb-2">Chronological list of fetched proactive commands. Each row shows the elapsed time, type code, name, and a decoded qualifier (for commands that have one). Expanding a row shows the decoded command: DISPLAY TEXT / SELECT ITEM / SET UP MENU text and items, BIP channel parameters, TIMER MANAGEMENT actions, PROVIDE LOCAL INFORMATION qualifier names (all standard qualifiers, including ESN 07, MEID 0B and Supported RATs 1A), and SEND SHORT MESSAGE — the SMS TPDU is parsed (type, TP-MR, TP-DA, TP-PID, TP-DCS, validity period, TP-UDL) with the user data shown as text for text codings (GSM 7-bit, UCS2, 8-bit) or as a secured packet (TS 31.115) when TP-PID = <code class="font-mono text-sm">7F</code> (SIM data download); UDH concatenation headers are decoded. Commands with response data show a <code class="font-mono text-sm">Response:</code> line with the TERMINAL RESPONSE bytes (boilerplate TLVs stripped); PROVIDE LOCAL INFORMATION responses are decoded using the PLI data dictionary decoders.</p>
|
||||
|
||||
<h3 id="terminal-profile" class="text-lg font-medium mb-2">6.4 TERMINAL PROFILE</h3>
|
||||
<p class="text-sm mb-3">The <strong>TERMINAL PROFILE</strong> block (next to STATUS and Polling) shows the profile currently in effect (hex + byte count) and offers <strong>Send</strong> (re-sends it, like Rescue) and <strong>Configure</strong>. The Configure dialog has a preset selector (device models, e.g. this project's BIP-capable handset profile), a hex field and a form with one checkbox per profile bit decoded per <strong>TS 102 223 §5.2</strong> (bytes 1–33; later bytes get generic labels); toggling a bit updates the hex and editing the hex re-renders the form — the hex field is authoritative and unknown bytes/bits are preserved. <strong>Apply</strong> sends the new value to the server (and on to the card), resetting the STK session like Rescue; the change is in-memory only (the <code class="font-mono text-sm">--terminal-profile</code> CLI value is the startup default).</p>
|
||||
<p class="text-sm mb-3">The <strong>TERMINAL PROFILE</strong> block (next to STATUS and Polling) offers <strong>Send</strong> (re-sends it, like Rescue) and <strong>Configure</strong>. The Configure dialog has a preset selector (device models, e.g. this project's BIP-capable handset profile), a hex field and a form with one checkbox per profile bit decoded per <strong>TS 102 223 §5.2</strong> (bytes 1–33; later bytes get generic labels), each byte is a vertical list of its bits, placed in fixed column groups (bytes 1-12 in 2 columns, 13-16 in 4, 17-18 in 2, 19-21 in 3, 22-25 in 2, 26-28 in 3, 29-30 in 2, later bytes one per row); toggling a bit updates the hex and editing the hex re-renders the form — the hex field is authoritative and unknown bytes/bits are preserved. <strong>Apply</strong> sends the new value to the server (and on to the card), resetting the STK session like Rescue; the change is in-memory only (the <code class="font-mono text-sm">--terminal-profile</code> CLI value is the startup default).</p>
|
||||
|
||||
<h3 id="status-polling" class="text-lg font-medium mb-2">6.5 STATUS polling</h3>
|
||||
<p class="text-sm mb-3">A <strong>Send STATUS</strong> button issues a manual STATUS (F2). A <strong>Polling</strong> toggle enables background polling: after a configurable idle interval (server CLI <code class="font-mono text-sm">--poll-interval</code>, 1–255 s, default 30 s, <code class="font-mono text-sm">0</code> disables polling) the server sends STATUS and handles any pending proactive command. Polling stops and card state resets if the card is removed.</p>
|
||||
|
||||
+91
-46
@@ -18,7 +18,7 @@
|
||||
<div class="max-w-7xl mx-auto px-6 py-2">
|
||||
|
||||
<div class="flex items-center justify-between mb-3">
|
||||
<h1 class="text-2xl font-bold text-heading">OTAMan <span id="slogan" class="text-sm font-normal text-gray-500 dark:text-slate-400 ml-2" data-l10n="SIM OTA with a Human Face">SIM OTA with a Human Face</span> <span class="text-xs text-gray-400 dark:text-slate-500 ml-1">v2.2.3</span></h1>
|
||||
<h1 class="text-2xl font-bold text-heading">OTAMan <span id="slogan" class="text-sm font-normal text-gray-500 dark:text-slate-400 ml-2" data-l10n="SIM OTA with a Human Face">SIM OTA with a Human Face</span> <span class="text-xs text-gray-400 dark:text-slate-500 ml-1">v2.2.10</span></h1>
|
||||
<div class="flex items-center gap-4">
|
||||
<span id="state-indicator" class="flex items-center select-none" style="cursor:default" title="Connecting...">
|
||||
<span id="state-indicator-dot" class="text-xs text-gray-400" title="Connecting...">●</span>
|
||||
@@ -926,7 +926,6 @@
|
||||
</div>
|
||||
<div class="border border-gray-200 dark:border-slate-700 rounded p-3">
|
||||
<div class="mb-1 text-sm text-gray-500 dark:text-slate-400" data-l10n="TERMINAL PROFILE">TERMINAL PROFILE</div>
|
||||
<div id="tp-current" class="text-xs font-mono text-gray-600 dark:text-slate-400 break-all max-w-xs mb-2" data-l10n="Loading...">Loading...</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<button id="tp-send-btn" data-needs="card" onclick="tpSend()" class="px-2.5 py-1 text-sm rounded bg-gray-600 text-white hover:bg-gray-700 disabled:opacity-40 disabled:cursor-not-allowed" data-l10n="Send">Send</button>
|
||||
<button id="tp-configure-btn" data-needs="server" onclick="tpOpen()" class="px-2.5 py-1 text-sm rounded bg-blue-600 text-white hover:bg-blue-700 disabled:opacity-40 disabled:cursor-not-allowed" data-l10n="Configure">Configure</button>
|
||||
@@ -1155,16 +1154,14 @@
|
||||
<div id="tp-modal" class="fixed inset-0 bg-black/50 z-50 flex items-center justify-center hidden">
|
||||
<div class="bg-white dark:bg-slate-800 rounded-lg p-6 max-w-3xl w-full mx-4 max-h-[85vh] overflow-auto">
|
||||
<h3 class="text-lg font-semibold mb-4 text-gray-800 dark:text-slate-200" data-l10n="TERMINAL PROFILE">TERMINAL PROFILE</h3>
|
||||
<div class="flex flex-wrap items-end gap-3 mb-3">
|
||||
<div>
|
||||
<div class="mb-3">
|
||||
<label class="block mb-1 text-xs font-medium text-gray-500 dark:text-slate-400" data-l10n="Preset (device model)">Preset (device model)</label>
|
||||
<select id="tp-preset" onchange="tpPresetChanged()" class="w-72 border border-gray-300 dark:border-slate-600 text-sm rounded px-2 py-1.5 dark:bg-slate-800"></select>
|
||||
<select id="tp-preset" onchange="tpPresetChanged()" class="w-full border border-gray-300 dark:border-slate-600 text-sm rounded px-2 py-1.5 dark:bg-slate-800"></select>
|
||||
</div>
|
||||
<div class="flex-1 min-w-[280px]">
|
||||
<div class="mb-3">
|
||||
<label class="block mb-1 text-xs font-medium text-gray-500 dark:text-slate-400" data-l10n="Profile (hex)">Profile (hex)</label>
|
||||
<input id="tp-hex" oninput="tpHexInput()" spellcheck="false" class="w-full font-mono border border-gray-300 dark:border-slate-600 text-sm rounded px-3 py-1.5 dark:bg-slate-800">
|
||||
</div>
|
||||
</div>
|
||||
<div id="tp-error" class="hidden text-xs text-red-500 mb-2"></div>
|
||||
<div class="text-xs text-gray-500 dark:text-slate-400 mb-2" data-l10n="Bits are decoded per TS 102 223 5.2; the hex field is authoritative and bytes/bits outside the table are preserved as-is.">Bits are decoded per TS 102 223 5.2; the hex field is authoritative and bytes/bits outside the table are preserved as-is.</div>
|
||||
<div id="tp-form" class="max-h-[45vh] overflow-auto border border-gray-200 dark:border-slate-700 rounded p-2 text-xs font-mono text-gray-600 dark:text-slate-400"></div>
|
||||
@@ -6967,10 +6964,15 @@ const PLI_QUALIFIERS = [
|
||||
// annex bits; bytes 1..33). Index 0 = bit 1 = first byte b8; bit n is
|
||||
// byte floor(n/8)+1, mask 0x80 >> (n%8). Bytes beyond 33 fall back to
|
||||
// generic labels in the UI.
|
||||
// TERMINAL PROFILE bit allocation: bytes 1..39 per ETSI TS 102 223
|
||||
// V18.3.0 5.2, 3GPP-defined bits named per ETSI TS 131 111 V18.12.0 5.2
|
||||
// (no "reserved by 3GPP" placeholders). Index 0 = bit 1 = first byte b8;
|
||||
// bit n is byte floor(n/8)+1, mask 0x80 >> (n%8). Bytes beyond the
|
||||
// table fall back to generic labels in the UI.
|
||||
const TP_BITS = [
|
||||
'Profile download', 'SMS-PP data download', 'Cell Broadcast data download', 'Menu selection',
|
||||
'SMS-PP data download', 'Timer expiration', 'USSD string DO support in CC by USIM', 'Call Control by NAA',
|
||||
'Command result', 'Call Control by NAA', 'Call Control by NAA', 'MO short message control support',
|
||||
'SMS-PP data download', 'Timer expiration', 'USSD string data object support in Call Control by USIM', 'Call Control by NAA',
|
||||
'Command result', 'Call Control by NAA', 'Call Control by NAA', 'MO short message control by USIM',
|
||||
'Call Control by NAA', 'UCS2 Entry supported', 'UCS2 Display supported', 'Display Text',
|
||||
'Proactive UICC: DISPLAY TEXT', 'Proactive UICC: GET INKEY', 'Proactive UICC: GET INPUT', 'Proactive UICC: MORE TIME',
|
||||
'Proactive UICC: PLAY TONE', 'Proactive UICC: POLL INTERVAL', 'Proactive UICC: POLLING OFF', 'Proactive UICC: REFRESH',
|
||||
@@ -6980,7 +6982,7 @@ const TP_BITS = [
|
||||
'Event: Location status', 'Event: User activity', 'Event: Idle screen available', 'Event: Card reader status',
|
||||
'Event: Language selection', 'Event: Browser Termination', 'Event: Data available', 'Event: Channel status',
|
||||
'Event: Access Technology Change', 'Event: Display parameters changed', 'Event: Local Connection', 'Event: Network Search Mode Change',
|
||||
'Proactive UICC: POWER ON CARD', 'Proactive UICC: POWER OFF CARD', 'Proactive UICC: PERFORM CARD RESET', 'Proactive UICC: GET READER STATUS (Card reader status)',
|
||||
'Proactive UICC: POWER ON CARD', 'Proactive UICC: POWER OFF CARD', 'Proactive UICC: PERFORM CARD APDU', 'Proactive UICC: GET READER STATUS (Card reader status)',
|
||||
'Proactive UICC: GET READER STATUS (Card reader identifier)', 'RFU', 'RFU', 'RFU',
|
||||
'Proactive UICC: TIMER MANAGEMENT (start, stop)', 'Proactive UICC: TIMER MANAGEMENT (get current value)', 'Proactive UICC: PROVIDE LOCAL INFORMATION (date, time and time zone)', 'GET INKEY',
|
||||
'SET UP IDLE MODE TEXT', 'RUN AT COMMAND', 'SETUP CALL', 'Call Control by NAA',
|
||||
@@ -7001,24 +7003,24 @@ const TP_BITS = [
|
||||
'RFU', 'RFU', 'RFU', 'RFU',
|
||||
'RFU', 'RFU', 'RFU', 'RFU',
|
||||
'BIP: TCP, UICC in client mode, remote connection', 'BIP: UDP, UICC in client mode, remote connection', 'BIP: TCP, UICC in server mode', 'BIP: TCP, UICC in client mode, local connection',
|
||||
'BIP: UDP, UICC in client mode, local connection', 'BIP: direct communication channel', 'reserved by 3GPP (E-UTRAN)', 'reserved by 3GPP (HSDPA)',
|
||||
'BIP: UDP, UICC in client mode, local connection', 'BIP: direct communication channel', 'E-UTRAN', 'HSDPA',
|
||||
'Proactive UICC: DISPLAY TEXT (variable timeout)', 'Proactive UICC: GET INKEY (help while waiting for immediate response or variable timeout)', 'USB (BIP supported bearer)', 'Proactive UICC: GET INKEY (variable timeout)',
|
||||
'Proactive UICC: PROVIDE LOCAL INFORMATION (ESN)', 'reserved by 3GPP (Call control on GPRS)', 'Proactive UICC: PROVIDE LOCAL INFORMATION (IMEISV)', 'Proactive UICC: PROVIDE LOCAL INFORMATION (Search mode change)',
|
||||
'reserved by TIA/EIA-136-270 (protocol version support)', 'RFU', 'RFU', 'RFU',
|
||||
'Proactive UICC: PROVIDE LOCAL INFORMATION (ESN)', 'CALL CONTROL on GPRS', 'Proactive UICC: PROVIDE LOCAL INFORMATION (IMEISV)', 'Proactive UICC: PROVIDE LOCAL INFORMATION (Search mode change)',
|
||||
'Protocol version support (TIA/EIA-136-270)', 'RFU', 'RFU', 'RFU',
|
||||
'RFU', 'RFU', 'RFU', 'RFU',
|
||||
'reserved by CCAT', 'reserved by CCAT', 'reserved by CCAT', 'reserved by CCAT',
|
||||
'reserved by CCAT', 'reserved by CCAT', 'reserved by CCAT', 'reserved by CCAT',
|
||||
'Reserved for CCAT', 'Reserved for CCAT', 'Reserved for CCAT', 'Reserved for CCAT',
|
||||
'Reserved for CCAT', 'Reserved for CCAT', 'Reserved for CCAT', 'Reserved for CCAT',
|
||||
'WML', 'XHTML', 'HTML', 'CHTML',
|
||||
'RFU', 'RFU', 'RFU', 'RFU',
|
||||
'reserved by 3GPP (UTRAN PS with extended parameters)', 'Proactive UICC: PROVIDE LOCAL INFORMATION (battery state)', 'Proactive UICC: PLAY TONE (melody/themed tones)', 'Multi-media calls in SET UP CALL',
|
||||
'reserved by 3GPP (Toolkit-initiated GBA)', 'Proactive UICC: RETRIEVE MULTIMEDIA MESSAGE', 'Proactive UICC: SUBMIT MULTIMEDIA MESSAGE', 'Proactive UICC: DISPLAY MULTIMEDIA MESSAGE',
|
||||
'Support of UTRAN PS with extended parameters', 'Proactive UICC: PROVIDE LOCAL INFORMATION (battery state)', 'Proactive UICC: PLAY TONE (melody/themed tones)', 'Multi-media calls in SET UP CALL',
|
||||
'Toolkit-initiated GBA', 'Proactive UICC: RETRIEVE MULTIMEDIA MESSAGE', 'Proactive UICC: SUBMIT MULTIMEDIA MESSAGE', 'Proactive UICC: DISPLAY MULTIMEDIA MESSAGE',
|
||||
'Proactive UICC: SET FRAMES', 'Proactive UICC: GET FRAMES STATUS', 'MMS notification download', 'Alpha identifier in REFRESH',
|
||||
'reserved by 3GPP (Geographical location reporting)', 'Proactive UICC: PROVIDE LOCAL INFORMATION (MEID)', 'reserved by 3GPP (PLI NMR for multiple access technologies)', 'reserved by 3GPP/3GPP2 (USSD data download and application mode)',
|
||||
'Geographical Location Reporting', 'Proactive UICC: PROVIDE LOCAL INFORMATION (MEID)', 'Proactive UICC: PROVIDE LOCAL INFORMATION (NMR (UTRAN/E-UTRAN/Satellite E-UTRAN/NG-RAN/Satellite NG-RAN))', 'USSD Data download and application mode',
|
||||
'Maximum number of frames supported (value field)', 'RFU', 'RFU', 'RFU',
|
||||
'RFU', 'RFU', 'RFU', 'RFU',
|
||||
'Event: Browsing status', 'Event: MMS transfer status', 'Event: Frame information changed', 'reserved by 3GPP (Event: I-WLAN access status)',
|
||||
'reserved by 3GPP (Event: Network rejection)', 'Event: HCI connectivity', 'reserved by 3GPP (E-UTRAN in Event Network Rejection)', 'Multiple access technologies supported (Event Access Technology Change / PLI)',
|
||||
'reserved by 3GPP (Event: CSG cell selection)', 'Event: Contactless state request', 'RFU', 'RFU',
|
||||
'Event: Browsing status', 'Event: MMS transfer status', 'Event: Frame information changed', 'Event: I-WLAN Access status',
|
||||
'Event: Network Rejection for GERAN/UTRAN', 'Event: HCI connectivity', 'Event: Network Rejection for E-UTRAN/Satellite E-UTRAN', 'Multiple access technologies supported (Event Access Technology Change / PLI)',
|
||||
'Event: CSG Cell Selection', 'Event: Contactless state request', 'RFU', 'RFU',
|
||||
'RFU', 'RFU', 'RFU', 'RFU',
|
||||
'RFU', 'RFU', 'RFU', 'RFU',
|
||||
'RFU', 'RFU', 'RFU', 'RFU',
|
||||
@@ -7026,14 +7028,26 @@ const TP_BITS = [
|
||||
'Font size large', 'Font size small', 'RFU', 'RFU',
|
||||
'Style normal', 'Style bold', 'Style italic', 'Style underlined',
|
||||
'Style strikethrough', 'Style text foreground colour', 'Style text background colour', 'RFU',
|
||||
'reserved by 3GPP (I-WLAN bearer)', 'reserved by 3GPP (PLI WSID of the current I-WLAN connection)', 'TERMINAL APPLICATIONS', 'reserved by 3GPP (Steering of Roaming REFRESH)',
|
||||
'I-WLAN bearer support', 'Proactive UICC: PROVIDE LOCAL INFORMATION (WSID of the current I-WLAN connection)', 'TERMINAL APPLICATIONS', '"Steering of Roaming" REFRESH support',
|
||||
'RFU', 'RFU', 'RFU', 'RFU',
|
||||
'Proactive UICC: Contactless State Changed', 'reserved by 3GPP (CSG cell discovery)', 'Confirmation parameters for OPEN CHANNEL in Terminal Server mode', 'reserved by 3GPP (Communication control for IMS)',
|
||||
'CAT over the modem interface', 'reserved by 3GPP (Incoming IMS data event)', 'reserved by 3GPP (IMS registration event)', 'Proactive UICC: Profile/Envelope/Command Container, ENCAPSULATED SESSION CONTROL',
|
||||
'reserved by 3GPP (IMS as BIP bearer)', 'reserved by 3GPP (PLI H(e)NB IP address)', 'reserved by 3GPP (PLI H(e)NB surrounding macrocells)', 'Launch parameters for OPEN CHANNEL in Terminal Server mode',
|
||||
'Proactive UICC: Contactless State Changed', 'Support of CSG cell discovery', 'Confirmation parameters for OPEN CHANNEL in Terminal Server mode', 'Communication Control for IMS',
|
||||
'CAT over the modem interface', 'Support for Incoming IMS Data event', 'Support for IMS Registration event', 'Proactive UICC: Profile/Envelope/Command Container, ENCAPSULATED SESSION CONTROL',
|
||||
'IMS support', 'Support of PROVIDE LOCAL INFORMATION, H(e)NB IP address', 'Support of PROVIDE LOCAL INFORMATION, H(e)NB surrounding macrocells', 'Launch parameters for OPEN CHANNEL in Terminal Server mode',
|
||||
'Direct communication channel for OPEN CHANNEL in Terminal Server mode', 'Security for Profile/Envelope/Command Container and ENCAPSULATED SESSION CONTROL', 'CAT service list for eCAT client', 'Refresh enforcement policy',
|
||||
'DNS server address request for OPEN CHANNEL (packet data bearer)', 'Network Access Name reuse indication for CLOSE CHANNEL', 'Event: Poll Interval Negotiation', 'reserved by 3GPP (ProSe usage information reporting)',
|
||||
'Proactive UICC: PROVIDE LOCAL INFORMATION (Supported Radio Access Technologies)', 'reserved by 3GPP (Event: WLAN access status)', 'reserved by 3GPP (WLAN bearer)', 'reserved by 3GPP (PLI WLAN identifier)',
|
||||
'DNS server address request for OPEN CHANNEL (packet data bearer)', 'Network Access Name reuse indication for CLOSE CHANNEL', 'Event: Poll Interval Negotiation', 'ProSe usage information reporting',
|
||||
'Proactive UICC: PROVIDE LOCAL INFORMATION (Supported Radio Access Technologies)', 'Event: WLAN Access status', 'WLAN bearer support', 'Proactive UICC: PROVIDE LOCAL INFORMATION (WLAN identifier of the current WLAN connection)',
|
||||
'URI support for SEND SHORT MESSAGE', 'IMS URI supported for SET UP CALL', 'Media Type "Voice" supported for SET UP CALL and Call Control by USIM', 'Media Type "Video" supported for SET UP CALL and Call Control by USIM',
|
||||
'Proactive UICC: PROVIDE LOCAL INFORMATION (E-UTRAN Timing Advance Information)', 'REFRESH with "eUICC Profile State Change" mode', 'Extended Rejection Cause Code in Event: Network Rejection for E-UTRAN', 'Reserved for future use',
|
||||
'Proactive UICC: GET INPUT (Variable Time out)', 'Data Connection Status Change Event support – PDP Connection', 'Data Connection Status Change Event support – PDN Connection', 'REFRESH with "Application Update" mode',
|
||||
'Proactive UICC: LSI COMMAND with "Proactive session request"', 'Proactive UICC: LSI COMMAND with "UICC Platform Reset"', 'RFU', 'RFU',
|
||||
'Data Connection Status Change Event support – PDU Connection', 'Event: Network Rejection for NG-RAN', 'Non-IP Data Delivery support', 'Support of PROVIDE LOCAL INFORMATION, Slice(s) information',
|
||||
'REFRESH "Steering of Roaming" SOR-CMCI parameter support', 'Event: Network Rejection for Satellite NG-RAN', 'Support of CAG feature', 'Event: Slices Status Change',
|
||||
'Support of PROVIDE LOCAL INFORMATION, Rejected Slice(s) Information', 'Support of Extended information for PLI (Location Information), Event: Location Status, Event: Network Rejection', 'Support of chaining of PLI/Envelope commands', '5G ProSe usage information reporting',
|
||||
'Reserved for future use', 'Reserved for future use', 'Reserved for future use', 'Reserved for future use',
|
||||
'Reserved for future use', 'Reserved for future use', 'Reserved for future use', 'Reserved for future use',
|
||||
'Reserved for future use', 'Reserved for future use', 'Reserved for future use', 'Reserved for future use',
|
||||
'Proactive UICC: PROVIDE LOCAL INFORMATION (NG-RAN/Satellite NG-RAN Timing Advance Information)', 'Reserved for future use', 'Reserved for future use', 'Reserved for future use',
|
||||
'Reserved for future use', 'Reserved for future use', 'Reserved for future use', 'Reserved for future use',
|
||||
];
|
||||
|
||||
// TERMINAL PROFILE presets (device/model -> profile hex). Add new entries
|
||||
@@ -7041,6 +7055,13 @@ const TP_BITS = [
|
||||
const TP_PRESETS = [
|
||||
{ name: 'Xiaomi Mi A1 (BIP-capable handset, project default)', profile: 'FFFFFFFF7F9F00DFFF03021FE2000000C3FB000704117800710100000038428003' },
|
||||
{ name: 'Quectel GSM module (AT+STKPD manual example)', profile: 'FFFFFFFF7F1F007FFF00001F230811060700' },
|
||||
{ name: 'Samsung S21+ 5G', profile: 'FFFFFFFF7F9D00DFFF00001FE2000000C3EB00000000400050000000000800000000' },
|
||||
{ name: 'Samsung A55 5G', profile: 'FFFFFFFF7F9D00DFFF00001FE2000000C3EB000000004000500000000008000003303002000001' },
|
||||
{ name: 'Xiaomi Redmi Note 10 LTE', profile: 'FFFFFFFF7F9F00FFFF03021FE2000000C3EB00000411580071000000003808800700' },
|
||||
{ name: 'Sony Xperia Z5c LTE', profile: 'FFFFFFFF7F9F00DFFF03071FE2080C00C3EB00000001480051000000000800' },
|
||||
{ name: 'Huawei E5573c / M150 (LTE)', profile: 'FFFFFFEF1F9C009B8500001F22000000C3CB000000010000900000000008' },
|
||||
{ name: 'Huawei E173 3G modem', profile: '2965D7E11F90001FD50000000000000000E00000' },
|
||||
{ name: 'Nokia 7210 2G', profile: 'FFFFFFFF7F3F00DFFF00001FE28A0D0203290000020040' },
|
||||
];
|
||||
|
||||
function tpNorm(hex) { return (hex || '').toUpperCase().replace(/[^0-9A-F]/g, ''); }
|
||||
@@ -7070,6 +7091,30 @@ function tpBitLabel(n) {
|
||||
return 'RFU (byte ' + ((n >> 3) + 1) + ' b' + (8 - (n & 7)) + ')';
|
||||
}
|
||||
|
||||
// Fixed byte-block layout: [firstByte, lastByte, columns] with 1-based
|
||||
// inclusive ranges (0 = "to the end of the profile"). Each byte block is a
|
||||
// vertical bit list; the groups place the blocks in 1..4 columns.
|
||||
const TP_LAYOUT = [
|
||||
[1, 12, 2],
|
||||
[13, 16, 4],
|
||||
[17, 18, 2],
|
||||
[19, 21, 3],
|
||||
[22, 25, 2],
|
||||
[26, 28, 3],
|
||||
[29, 30, 2],
|
||||
[31, 0, 1],
|
||||
];
|
||||
|
||||
function tpLayoutGroups(totalBytes) {
|
||||
const groups = [];
|
||||
for (const [from, to, cols] of TP_LAYOUT) {
|
||||
const end = to === 0 ? totalBytes : Math.min(to, totalBytes);
|
||||
if (from > end) continue;
|
||||
groups.push({from: from, to: end, cols: cols});
|
||||
}
|
||||
return groups;
|
||||
}
|
||||
|
||||
function tpRenderForm() {
|
||||
const el = document.getElementById('tp-form');
|
||||
if (!el) return;
|
||||
@@ -7077,18 +7122,25 @@ function tpRenderForm() {
|
||||
if (!tpValid(hex)) { el.innerHTML = ''; return; }
|
||||
const bytes = hex.length / 2;
|
||||
let html = '';
|
||||
for (let bi = 0; bi < bytes; bi++) {
|
||||
html += '<div class="mb-1.5"><div class="text-gray-500 dark:text-slate-400">' +
|
||||
esc(t('Byte')) + ' ' + (bi + 1) + '</div><div class="grid grid-cols-1 sm:grid-cols-2 gap-x-4">';
|
||||
for (const g of tpLayoutGroups(bytes)) {
|
||||
html += '<div style="display:grid;grid-template-columns:repeat(' + g.cols +
|
||||
',minmax(0,1fr));gap:0.5rem;align-items:start;margin-bottom:0.5rem">';
|
||||
for (let bi = g.from - 1; bi < g.to; bi++) {
|
||||
html += '<div class="border border-gray-200 dark:border-slate-700 rounded p-1.5">';
|
||||
html += '<div class="text-gray-500 dark:text-slate-400 mb-0.5">' + esc(t('Byte')) + ' ' + (bi + 1) + '</div>';
|
||||
for (let k = 0; k < 8; k++) {
|
||||
const n = bi * 8 + k;
|
||||
const mask = 'b' + (8 - k);
|
||||
const label = tpBitLabel(n);
|
||||
html += '<label class="flex items-center gap-1 min-w-0"><input type="checkbox" ' +
|
||||
html += '<label class="flex items-start gap-1 mb-0.5">' +
|
||||
'<input type="checkbox" class="mt-0.5 shrink-0" ' +
|
||||
(tpGetBit(hex, n) ? 'checked ' : '') +
|
||||
'onchange="tpToggle(' + n + ', this.checked)"> ' +
|
||||
'<span class="truncate" title="' + escHtml('b' + (8 - k) + ' ' + label) + '">b' + (8 - k) + ' ' + esc(label) + '</span></label>';
|
||||
'<span title="' + escHtml(mask + ' ' + label) + '">' + esc(mask + ' ' + label) + '</span></label>';
|
||||
}
|
||||
html += '</div></div>';
|
||||
html += '</div>';
|
||||
}
|
||||
html += '</div>';
|
||||
}
|
||||
el.innerHTML = html;
|
||||
}
|
||||
@@ -7140,15 +7192,8 @@ function tpStatusMsg(text, cls) {
|
||||
|
||||
async function tpRefresh() {
|
||||
try {
|
||||
const d = await pysimFetch('/api/terminal-profile');
|
||||
const el = document.getElementById('tp-current');
|
||||
if (el) el.textContent = d.profile
|
||||
? d.profile + ' (' + d.bytes + ' ' + t('bytes') + ')'
|
||||
: t('not configured');
|
||||
return d;
|
||||
return await pysimFetch('/api/terminal-profile');
|
||||
} catch (e) {
|
||||
const el = document.getElementById('tp-current');
|
||||
if (el && el.textContent === t('Loading...')) el.textContent = '—';
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -8931,11 +8976,6 @@ function pysimCustomImportField() {
|
||||
el.focus();
|
||||
}
|
||||
|
||||
// Init custom files
|
||||
pysimCustomLoad();
|
||||
pysimCustomRenderRoots();
|
||||
pysimCustomRenderParents();
|
||||
|
||||
// ===== Profiler =====
|
||||
let profiles = [];
|
||||
let profilerView = 'list';
|
||||
@@ -11333,6 +11373,11 @@ currentLang = localStorage.getItem('lang') || (userLang === 'ru' ? 'ru' : 'en');
|
||||
document.getElementById('lang-btn').textContent = currentLang.toUpperCase();
|
||||
updateHelpLink();
|
||||
|
||||
// Init custom files (after the language init: validation/rendering uses t())
|
||||
pysimCustomLoad();
|
||||
pysimCustomRenderRoots();
|
||||
pysimCustomRenderParents();
|
||||
|
||||
cardsLoad();
|
||||
pysimStartAvailabilityProbe();
|
||||
pysimFsSetSort(pysimFsSort);
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
const CACHE = 'otaman-v167';
|
||||
const CACHE = 'otaman-v174';
|
||||
const URLS = [
|
||||
'index.html',
|
||||
'help.html',
|
||||
|
||||
@@ -97,6 +97,13 @@ test('file manager keeps sort/probe controls above the scrolling tree', () => {
|
||||
assert.match(html, /style="max-height:65vh"[^>]*>\s*<div id="pysim-fs-tree">/);
|
||||
});
|
||||
|
||||
test('custom-files init runs after the language init', () => {
|
||||
// pysimCustomLoad/RenderParents call t(): running them before
|
||||
// currentLang is initialized throws a TDZ error and aborts the rest
|
||||
// of the script (all later handlers fail with 'before initialization').
|
||||
assert.ok(html.indexOf('// Init custom files') > html.indexOf("let currentLang = 'en';"));
|
||||
});
|
||||
|
||||
test('custom files form has add/save and cancel controls', () => {
|
||||
assert.match(html, /id="pysim-cf-add-btn"[^>]*data-l10n="Add"/);
|
||||
assert.match(html, /id="pysim-cf-cancel-btn"[^>]*class="hidden[^"]*"[^>]*data-l10n="Cancel"/);
|
||||
|
||||
@@ -22,10 +22,11 @@ function extractFunc(src, name) {
|
||||
}
|
||||
|
||||
let code = '';
|
||||
for (const f of ['tpNorm', 'tpValid', 'tpGetBit', 'tpSetBit', 'tpBitLabel']) {
|
||||
for (const f of ['tpNorm', 'tpValid', 'tpGetBit', 'tpSetBit', 'tpBitLabel', 'tpLayoutGroups']) {
|
||||
code += extractFunc(html, f) + '\n';
|
||||
}
|
||||
code += html.match(/const TP_BITS = \[[\s\S]*?\n\];/)[0].replace('const ', 'var ') + '\n';
|
||||
code += html.match(/const TP_LAYOUT = \[[\s\S]*?\n\];/)[0].replace('const ', 'var ') + '\n';
|
||||
code += html.match(/const TP_PRESETS = \[[\s\S]*?\n\];/)[0].replace('const ', 'var ') + '\n';
|
||||
eval(code);
|
||||
|
||||
@@ -53,30 +54,60 @@ test('tpGetBit / tpSetBit address bits MSB-first per byte', () => {
|
||||
assert.strictEqual(tpSetBit('FF', 24, true), 'FF000080');
|
||||
});
|
||||
|
||||
test('TERMINAL PROFILE bit table matches TS 102 223 5.2 spot checks', () => {
|
||||
assert.ok(TP_BITS.length >= 264, 'expected the full byte 1..33 table');
|
||||
test('TERMINAL PROFILE bit table matches the spec spot checks', () => {
|
||||
assert.ok(TP_BITS.length >= 312, 'expected the full byte 1..39 table');
|
||||
assert.strictEqual(TP_BITS.length % 8, 0);
|
||||
assert.strictEqual(TP_BITS[0], 'Profile download'); // byte 1 b8
|
||||
assert.strictEqual(TP_BITS[16], 'Proactive UICC: DISPLAY TEXT'); // byte 3 b8
|
||||
assert.strictEqual(TP_BITS[32], 'Proactive UICC: SET UP EVENT LIST'); // byte 5 b8
|
||||
assert.strictEqual(TP_BITS[42], 'Event: Data available'); // byte 6 b3
|
||||
assert.strictEqual(TP_BITS[50], 'Proactive UICC: PERFORM CARD APDU'); // byte 7 b6 (pySim said RESET)
|
||||
assert.strictEqual(TP_BITS[88], 'Proactive UICC: OPEN CHANNEL'); // byte 12 b8
|
||||
assert.strictEqual(TP_BITS[140], 'Proactive UICC: PROVIDE LOCAL INFORMATION (ESN)'); // byte 18 b4
|
||||
assert.strictEqual(TP_BITS[181], 'Proactive UICC: PROVIDE LOCAL INFORMATION (MEID)'); // byte 23 b3
|
||||
assert.strictEqual(TP_BITS[260], 'Proactive UICC: PROVIDE LOCAL INFORMATION (Supported Radio Access Technologies)');
|
||||
assert.strictEqual(TP_BITS[280], 'Data Connection Status Change Event support – PDU Connection'); // byte 36 b8
|
||||
assert.strictEqual(tpBitLabel(0), 'Profile download');
|
||||
assert.match(tpBitLabel(400), /^RFU \(byte 51 b/); // beyond the table
|
||||
});
|
||||
|
||||
test('3GPP-defined bits use the TS 31.111 names, not placeholders', () => {
|
||||
assert.ok(!TP_BITS.some(l => /reserved by 3gpp/i.test(l)), 'no "reserved by 3GPP" labels');
|
||||
assert.ok(!TP_BITS.some(l => /reserved by etsi/i.test(l)));
|
||||
// a few audited 3GPP bits (TS 31.111 5.2)
|
||||
const byteLabels = b => TP_BITS.slice((b - 1) * 8, b * 8);
|
||||
assert.strictEqual(byteLabels(17)[6], 'E-UTRAN'); // byte 17 b2
|
||||
assert.strictEqual(byteLabels(17)[7], 'HSDPA'); // byte 17 b1
|
||||
assert.strictEqual(byteLabels(18)[5], 'CALL CONTROL on GPRS'); // byte 18 b3
|
||||
assert.strictEqual(byteLabels(25)[4], 'Event: Network Rejection for GERAN/UTRAN');
|
||||
assert.strictEqual(byteLabels(32)[0], 'IMS support'); // byte 32 b8
|
||||
assert.strictEqual(byteLabels(34)[0], 'URI support for SEND SHORT MESSAGE');
|
||||
assert.match(byteLabels(39)[0], /NG-RAN\/Satellite NG-RAN Timing Advance/);
|
||||
});
|
||||
|
||||
test('TERMINAL PROFILE presets are valid even-length hex', () => {
|
||||
assert.ok(TP_PRESETS.length >= 2);
|
||||
assert.match(TP_PRESETS[0].name, /Xiaomi/i);
|
||||
assert.ok(TP_PRESETS.length >= 9);
|
||||
// the project default stays first (it matches the CLI default profile)
|
||||
assert.match(TP_PRESETS[0].name, /Xiaomi Mi A1/);
|
||||
const names = TP_PRESETS.map(p => p.name);
|
||||
for (const model of ['Quectel GSM module', 'Samsung S21+ 5G', 'Samsung A55 5G',
|
||||
'Xiaomi Redmi Note 10 LTE', 'Sony Xperia Z5c LTE', 'Huawei E5573c / M150 (LTE)',
|
||||
'Huawei E173 3G modem', 'Nokia 7210 2G']) {
|
||||
assert.ok(names.some(n => n.includes(model)), model);
|
||||
}
|
||||
const seen = new Set();
|
||||
for (const p of TP_PRESETS) {
|
||||
assert.ok(p.name && p.profile, p.name);
|
||||
assert.ok(/^[0-9A-F]+$/.test(p.profile) && p.profile.length % 2 === 0, p.name);
|
||||
assert.ok(p.profile.length <= 510, p.name);
|
||||
assert.ok(p.profile.length >= 8 && p.profile.length <= 510, p.name);
|
||||
assert.ok(!seen.has(p.profile), 'duplicate profile: ' + p.name);
|
||||
seen.add(p.profile);
|
||||
}
|
||||
});
|
||||
|
||||
test('Phone tab exposes the TERMINAL PROFILE block and Configure dialog', () => {
|
||||
assert.ok(html.includes('id="tp-current"'));
|
||||
// the compact block has no room for the hex value: Send + Configure only
|
||||
assert.ok(!html.includes('id="tp-current"'));
|
||||
assert.match(html, /id="tp-send-btn"[^>]*data-needs="card"/);
|
||||
assert.match(html, /id="tp-configure-btn"[^>]*data-needs="server"/);
|
||||
assert.ok(html.includes('id="tp-modal"'));
|
||||
@@ -84,4 +115,28 @@ test('Phone tab exposes the TERMINAL PROFILE block and Configure dialog', () =>
|
||||
assert.ok(html.includes('id="tp-hex"'));
|
||||
assert.ok(html.includes('id="tp-form"'));
|
||||
assert.ok(html.includes('id="tp-apply-btn"'));
|
||||
// the preset select sits above the hex field, not next to it
|
||||
assert.ok(html.indexOf('id="tp-preset"') < html.indexOf('id="tp-hex"'));
|
||||
});
|
||||
|
||||
test('tpLayoutGroups lays the byte blocks out in the configured columns', () => {
|
||||
assert.deepStrictEqual(tpLayoutGroups(33), [
|
||||
{ from: 1, to: 12, cols: 2 },
|
||||
{ from: 13, to: 16, cols: 4 },
|
||||
{ from: 17, to: 18, cols: 2 },
|
||||
{ from: 19, to: 21, cols: 3 },
|
||||
{ from: 22, to: 25, cols: 2 },
|
||||
{ from: 26, to: 28, cols: 3 },
|
||||
{ from: 29, to: 30, cols: 2 },
|
||||
{ from: 31, to: 33, cols: 1 },
|
||||
]);
|
||||
// short profiles clamp: only existing bytes get a group
|
||||
assert.deepStrictEqual(tpLayoutGroups(8), [{ from: 1, to: 8, cols: 2 }]);
|
||||
assert.deepStrictEqual(tpLayoutGroups(18), [
|
||||
{ from: 1, to: 12, cols: 2 },
|
||||
{ from: 13, to: 16, cols: 4 },
|
||||
{ from: 17, to: 18, cols: 2 },
|
||||
]);
|
||||
// long profiles: everything past byte 30 is one per row
|
||||
assert.deepStrictEqual(tpLayoutGroups(40).slice(-1), [{ from: 31, to: 40, cols: 1 }]);
|
||||
});
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "pysim-otaman-server"
|
||||
version = "2.2.3"
|
||||
version = "2.2.10"
|
||||
description = "HTTP REST server wrapping pysim for the OTAMan PWA"
|
||||
requires-python = ">=3.8"
|
||||
# pysim is a git-only dependency installed explicitly by setup.bat/setup.sh.
|
||||
|
||||
@@ -21,7 +21,7 @@ from osmocom.construct import GsmOrUcs2Adapter
|
||||
from osmocom.tlv import BER_TLV_IE
|
||||
|
||||
|
||||
VERSION = '2.2.3'
|
||||
VERSION = '2.2.10'
|
||||
|
||||
MAX_ENVELOPE_SEGMENTS = 5 # max SMS segments for outgoing C-APDU in ENVELOPE
|
||||
|
||||
|
||||
Reference in New Issue
Block a user