terminal profile: numeric value fields (soft keys, channels, screen, ND/NK, frames), doc corrections (v2.2.21)

- TP_BITS: fill the value-field rows that were all-RFU (byte 11 soft keys,
  byte 13 b6-b8 channels, byte 14 height + ND/NK/sizing, byte 15 width +
  variable fonts, byte 16 effects + width reduction, byte 19 TIA version,
  byte 24 max frames), per TS 102 223 5.2.
- TP_VALUE_FIELDS + tpGetValue/tpSetValue (little-endian bit ranges) and
  number inputs in the Configure dialog; the byte 11 'FF' reserved note
  stays visible in the hint; flags remain checkboxes.
- Tests: new label spot checks, value round-trips/clamping, render guard.
- docs/TERMINAL_PROFILE.md: byte 22 b1 not universal (S25 Ultra/edge 60
  clear, WISMO228 sets), b2/b3 counts fixed, byte 21 markup present in
  Pixel 3/4 + OnePlus 12, ESN is a capability bit not a CDMA marker,
  0xFE sentinel unexplained by the spec, channel count 5 added, new
  Appendix A with the value-field ranges.
- Version 2.2.21, SW cache otaman-v188.
This commit is contained in:
2026-09-19 07:45:12 +03:00
parent 0fe51491d8
commit 9c64da86a3
7 changed files with 189 additions and 19 deletions
+1 -1
View File
@@ -459,7 +459,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&nbsp;7-бит, UCS2, 8-бит) или как secured-пакет (TS&nbsp;31.115) при TP-PID&nbsp;=&nbsp;<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">8.4 TERMINAL PROFILE</h3>
<p class="text-sm mb-3">Блок <strong>TERMINAL PROFILE</strong> (рядом с «STATUS и опрос») содержит кнопки <strong>«Отправить»</strong> (повторная отправка, как «Спасение») и <strong>«Настроить»</strong>. В диалоге настройки: селектор пресетов (модели устройств, например профиль BIP-совместимого аппарата из этого проекта), поле hex и форма с флажком на каждый бит профиля по <strong>TS 102 223 §5.2</strong> / <strong>TS 31.111 §5.2</strong> (байты 1&ndash;39; для битов 3GPP используются имена из TS 31.111); каждый байт — вертикальный список битов в порядке спецификации (<strong>b1 — младший бит, первая строка таблицы</strong>); блоки размещены фиксированными группами (байты 1–12 в 2 колонки, 1316 в 4, 1718 в 2, 1921 в 3, 2225 в 2, 2628 в 3, 2930 в 2, далее по одному в строке); переключение бита обновляет hex, а правка hex перерисовывает форму &mdash; поле hex основное, неизвестные байты и биты сохраняются. Кнопка <strong>«Применить»</strong> отправляет новое значение на сервер (и далее на карту), сбрасывая STK-сессию как «Спасение»; изменение хранится только в памяти (значение <code class="font-mono text-sm">--terminal-profile</code> — стартовое по умолчанию). Правила вывода характеристик устройства из TP (поколение связи, бездисплейные модемы, семейство стандартов) собраны в <code class="font-mono text-sm">docs/TERMINAL_PROFILE.md</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> / <strong>TS 31.111 §5.2</strong> (байты 1&ndash;39; для битов 3GPP используются имена из TS 31.111); каждый байт — вертикальный список битов в порядке спецификации (<strong>b1 — младший бит, первая строка таблицы</strong>); биты, образующие числовое поле по спецификации (байт 11 — число soft keys, байт 13 — число BIP-каналов, байты 14/15 — высота/ширина экрана, байт 16 — уменьшение ширины в меню, байт 19 — версия TIA/EIA-136, байт 24 — максимум кадров), показываются одним числовым полем на весь диапазон битов, а в подсказке байта 11 отмечено зарезервированное значение <code class="font-mono text-sm">'FF'</code>; блоки размещены фиксированными группами (байты 1–12 в 2 колонки, 1316 в 4, 1718 в 2, 1921 в 3, 2225 в 2, 2628 в 3, 2930 в 2, далее по одному в строке); переключение бита обновляет hex, а правка hex перерисовывает форму &mdash; поле hex основное, неизвестные байты и биты сохраняются. Кнопка <strong>«Применить»</strong> отправляет новое значение на сервер (и далее на карту), сбрасывая STK-сессию как «Спасение»; изменение хранится только в памяти (значение <code class="font-mono text-sm">--terminal-profile</code> — стартовое по умолчанию). Правила вывода характеристик устройства из TP (поколение связи, бездисплейные модемы, семейство стандартов) собраны в <code class="font-mono text-sm">docs/TERMINAL_PROFILE.md</code>.</p>
<h3 id="status-polling" class="text-lg font-medium mb-2">8.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&ndash;255&nbsp;с, по умолчанию 30&nbsp;с, <code class="font-mono text-sm">0</code> отключает опрос) сервер отправляет STATUS и обрабатывает любую ожидающую проактивную команду. При извлечении карты опрос останавливается, а состояние карты сбрасывается.</p>
+1 -1
View File
@@ -459,7 +459,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 &mdash; 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&nbsp;7-bit, UCS2, 8-bit) or as a secured packet (TS&nbsp;31.115) when TP-PID&nbsp;=&nbsp;<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">8.4 TERMINAL PROFILE</h3>
<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 &sect;5.2</strong> / <strong>TS 31.111 &sect;5.2</strong> (bytes 1&ndash;39, 3GPP-defined bits use the TS 31.111 names), each byte is a vertical list of its bits in spec order (<strong>b1 is the least significant bit and the first table row</strong>), 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 &mdash; 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). The rules for inferring device characteristics from a TP (radio generation, headless modems, standards family) are collected in <code class="font-mono text-sm">docs/TERMINAL_PROFILE.md</code>.</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 &sect;5.2</strong> / <strong>TS 31.111 &sect;5.2</strong> (bytes 1&ndash;39, 3GPP-defined bits use the TS 31.111 names), each byte is a vertical list of its bits in spec order (<strong>b1 is the least significant bit and the first table row</strong>); bits that form a numeric value field per the spec (byte 11 soft keys, byte 13 BIP channels, byte 14/15 screen height/width, byte 16 menu width reduction, byte 19 TIA/EIA-136 version, byte 24 max frames) are shown as a single number input for the whole bit range, with the byte 11 <code class="font-mono text-sm">'FF'</code> reserved value noted in the hint; 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 &mdash; 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). The rules for inferring device characteristics from a TP (radio generation, headless modems, standards family) are collected in <code class="font-mono text-sm">docs/TERMINAL_PROFILE.md</code>.</p>
<h3 id="status-polling" class="text-lg font-medium mb-2">8.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&ndash;255&nbsp;s, default 30&nbsp;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>
+87 -13
View File
@@ -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.20</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.21</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>
@@ -7196,23 +7196,23 @@ const TP_BITS = [
'Proactive UICC: PROVIDE LOCAL INFORMATION (Timing Advance)', 'Proactive UICC: LANGUAGE NOTIFICATION', 'Proactive UICC: LAUNCH BROWSER', 'Proactive UICC: PROVIDE LOCAL INFORMATION (Access Technology)',
'Soft keys support for SELECT ITEM', 'Soft keys support for SET UP MENU ITEM', 'RFU', 'RFU',
'RFU', 'RFU', 'RFU', 'RFU',
'RFU', 'RFU', 'RFU', 'RFU',
'RFU', 'RFU', 'RFU', 'RFU',
'Maximum number of soft keys (value b1..b8; FF reserved)', 'Maximum number of soft keys (value b1..b8; FF reserved)', 'Maximum number of soft keys (value b1..b8; FF reserved)', 'Maximum number of soft keys (value b1..b8; FF reserved)',
'Maximum number of soft keys (value b1..b8; FF reserved)', 'Maximum number of soft keys (value b1..b8; FF reserved)', 'Maximum number of soft keys (value b1..b8; FF reserved)', 'Maximum number of soft keys (value b1..b8; FF reserved)',
'Proactive UICC: OPEN CHANNEL', 'Proactive UICC: CLOSE CHANNEL', 'Proactive UICC: RECEIVE DATA', 'Proactive UICC: SEND DATA',
'Proactive UICC: GET CHANNEL STATUS', 'Proactive UICC: SERVICE SEARCH', 'Proactive UICC: GET SERVICE INFORMATION', 'Proactive UICC: DECLARE SERVICE',
'BIP supported Bearer: CSD', 'BIP supported Bearer: GPRS', 'BIP supported Bearer: Bluetooth', 'BIP supported Bearer: IrDA',
'BIP supported Bearer: RS232', 'RFU', 'RFU', 'RFU',
'RFU', 'RFU', 'RFU', 'RFU',
'RFU', 'RFU', 'RFU', 'RFU',
'RFU', 'RFU', 'RFU', 'RFU',
'RFU', 'RFU', 'RFU', 'RFU',
'RFU', 'RFU', 'RFU', 'RFU',
'RFU', 'RFU', 'RFU', 'RFU',
'BIP supported Bearer: RS232', 'Number of BIP channels supported (value b6..b8)', 'Number of BIP channels supported (value b6..b8)', 'Number of BIP channels supported (value b6..b8)',
'Screen height in characters (value b1..b5)', 'Screen height in characters (value b1..b5)', 'Screen height in characters (value b1..b5)', 'Screen height in characters (value b1..b5)',
'Screen height in characters (value b1..b5)', 'No display capability (class ND)', 'No keypad available (class NK)', 'Screen Sizing Parameters supported',
'Screen width in characters (value b1..b7)', 'Screen width in characters (value b1..b7)', 'Screen width in characters (value b1..b7)', 'Screen width in characters (value b1..b7)',
'Screen width in characters (value b1..b7)', 'Screen width in characters (value b1..b7)', 'Screen width in characters (value b1..b7)', 'Variable size fonts',
'Display can be resized', 'Text Wrapping supported', 'Text Scrolling supported', 'Text Attributes supported',
'RFU', 'Width reduction when in a menu (value b6..b8)', 'Width reduction when in a menu (value b6..b8)', 'Width reduction when in a menu (value b6..b8)',
'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', '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)', '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',
'TIA/EIA-136-270 protocol version (value b1..b4)', 'TIA/EIA-136-270 protocol version (value b1..b4)', 'TIA/EIA-136-270 protocol version (value b1..b4)', 'TIA/EIA-136-270 protocol version (value b1..b4)',
'RFU', 'RFU', 'RFU', 'RFU',
'Reserved for CCAT', 'Reserved for CCAT', 'Reserved for CCAT', 'Reserved for CCAT',
'Reserved for CCAT', 'Reserved for CCAT', 'Reserved for CCAT', 'Reserved for CCAT',
@@ -7222,7 +7222,7 @@ const TP_BITS = [
'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',
'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',
'Maximum number of frames supported (value b1..b4)', 'Maximum number of frames supported (value b1..b4)', 'Maximum number of frames supported (value b1..b4)', 'Maximum number of frames supported (value b1..b4)',
'RFU', 'RFU', '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)',
@@ -7256,6 +7256,49 @@ const TP_BITS = [
'Reserved for future use', 'Reserved for future use', 'Reserved for future use', 'Reserved for future use',
];
// Value fields: spec table rows that carry a number instead of a flag.
// Bit ranges are 1-based within the byte, b_from = LSB of the value
// (TS 102 223 5.2: e.g. byte 13 b6..b8 = number of BIP channels,
// byte 14 b1..b5 = characters down). The Configure dialog renders these
// as number inputs instead of per-bit checkboxes. `hint` is shown in the UI.
const TP_VALUE_FIELDS = [
{ byte: 11, from: 1, to: 8, label: 'Maximum number of soft keys', hint: "'FF' is reserved for future use" },
{ byte: 13, from: 6, to: 8, label: 'Number of BIP channels supported' },
{ byte: 14, from: 1, to: 5, label: 'Screen height (characters down)' },
{ byte: 15, from: 1, to: 7, label: 'Screen width (characters across)' },
{ byte: 16, from: 6, to: 8, label: 'Width reduction when in a menu' },
{ byte: 19, from: 1, to: 4, label: 'TIA/EIA-136-270 protocol version' },
{ byte: 24, from: 1, to: 4, label: 'Maximum number of frames supported' },
];
function tpValueFieldFor(byteNr) {
return TP_VALUE_FIELDS.find(f => f.byte === byteNr) || null;
}
function tpValueWidth(f) { return f.to - f.from + 1; }
function tpGetValue(hex, f) {
const byteIdx = f.byte - 1;
if (byteIdx * 2 + 2 > hex.length) return null;
const b = parseInt(hex.substr(byteIdx * 2, 2), 16);
return (b >> (f.from - 1)) & ((1 << tpValueWidth(f)) - 1);
}
function tpSetValue(hex, f, value) {
const bytes = hex.match(/../g) || [];
const byteIdx = f.byte - 1;
while (bytes.length <= byteIdx) bytes.push('00');
let b = parseInt(bytes[byteIdx], 16);
const width = tpValueWidth(f);
const mask = ((1 << width) - 1) << (f.from - 1);
let v = parseInt(value, 10);
if (!isFinite(v)) v = 0;
v = Math.max(0, Math.min((1 << width) - 1, v));
b = (b & ~mask) | ((v << (f.from - 1)) & mask);
bytes[byteIdx] = b.toString(16).padStart(2, '0').toUpperCase();
return bytes.join('');
}
// TERMINAL PROFILE presets (device/model -> profile hex). Add new entries
// here; the Configure dialog lists them by name.
const TP_PRESETS = [
@@ -7340,7 +7383,28 @@ function tpRenderForm() {
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' + (k + 1); // b1 first, matching the spec tables
const bitNr = k + 1;
const vf = tpValueFieldFor(bi + 1);
if (vf && bitNr >= vf.from && bitNr <= vf.to) {
// One number input for the whole value field, placed at
// the field's first (least significant) bit.
if (bitNr === vf.from) {
const val = tpGetValue(hex, vf);
const max = (1 << tpValueWidth(vf)) - 1;
const range = 'b' + vf.from + (vf.to > vf.from ? '..b' + vf.to : '');
const title = range + ' ' + vf.label + (vf.hint ? ' — ' + vf.hint : '');
html += '<label class="flex items-start gap-1 mb-0.5">' +
'<span class="shrink-0" title="' + escHtml(title) + '">' + esc(range) + '</span>' +
'<input type="number" min="0" max="' + max + '" value="' + (val === null ? '' : val) + '" ' +
'class="w-16 font-mono text-xs border border-gray-300 dark:border-slate-600 rounded px-1 py-0.5 dark:bg-slate-800" ' +
'onchange="tpValueInput(' + vf.byte + ', ' + vf.from + ', ' + vf.to + ', this)"> ' +
'<span title="' + escHtml(vf.label) + '">' + esc(vf.label) +
(vf.hint ? ' <span class="text-gray-400 dark:text-slate-500">(' + esc(vf.hint) + ')</span>' : '') +
'</span></label>';
}
continue;
}
const mask = 'b' + bitNr; // b1 first, matching the spec tables
const label = tpBitLabel(n);
html += '<label class="flex items-start gap-1 mb-0.5">' +
'<input type="checkbox" class="mt-0.5 shrink-0" ' +
@@ -7362,6 +7426,16 @@ function tpToggle(n, on) {
tpSyncPreset();
}
function tpValueInput(byteNr, from, to, el) {
const hex = tpNorm(document.getElementById('tp-hex').value);
if (!tpValid(hex)) return;
const f = { byte: byteNr, from: from, to: to };
const next = tpSetValue(hex, f, el.value);
document.getElementById('tp-hex').value = next;
el.value = tpGetValue(next, f); // show the clamped value
tpSyncPreset();
}
function tpHexInput() {
const err = document.getElementById('tp-error');
const hex = tpNorm(document.getElementById('tp-hex').value);
+1 -1
View File
@@ -1,4 +1,4 @@
const CACHE = 'otaman-v187';
const CACHE = 'otaman-v188';
const URLS = [
'index.html',
'help.html',
+97 -1
View File
@@ -22,12 +22,14 @@ function extractFunc(src, name) {
}
let code = '';
for (const f of ['tpNorm', 'tpValid', 'tpGetBit', 'tpSetBit', 'tpBitLabel', 'tpLayoutGroups']) {
for (const f of ['tpNorm', 'tpValid', 'tpGetBit', 'tpSetBit', 'tpBitLabel', 'tpLayoutGroups',
'tpValueFieldFor', 'tpValueWidth', 'tpGetValue', 'tpSetValue']) {
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';
code += html.match(/const TP_VALUE_FIELDS = \[[\s\S]*?\n\];/)[0].replace('const ', 'var ') + '\n';
eval(code);
test('tpNorm / tpValid normalize and validate profile hex', () => {
@@ -78,6 +80,100 @@ test('TERMINAL PROFILE bit table matches the spec spot checks', () => {
assert.match(tpBitLabel(400), /^RFU \(byte 51 b1\)/); // beyond the table
});
test('value-field rows carry the spec labels (soft keys, channels, screen, ND/NK, frames)', () => {
const byteLabels = b => TP_BITS.slice((b - 1) * 8, b * 8);
// byte 11: soft keys value (0xFF reserved)
assert.match(byteLabels(11)[0], /Maximum number of soft keys/);
assert.match(byteLabels(11)[0], /FF reserved/);
assert.strictEqual(byteLabels(11)[7], byteLabels(11)[0]);
// byte 13 b6..b8: BIP channel count
assert.strictEqual(byteLabels(13)[5], 'Number of BIP channels supported (value b6..b8)');
assert.strictEqual(byteLabels(13)[7], byteLabels(13)[5]);
// byte 14: height b1..b5, ND b6, NK b7, sizing b8
assert.match(byteLabels(14)[0], /Screen height/);
assert.strictEqual(byteLabels(14)[5], 'No display capability (class ND)');
assert.strictEqual(byteLabels(14)[6], 'No keypad available (class NK)');
assert.strictEqual(byteLabels(14)[7], 'Screen Sizing Parameters supported');
// byte 15: width b1..b7, variable fonts b8
assert.match(byteLabels(15)[0], /Screen width/);
assert.strictEqual(byteLabels(15)[7], 'Variable size fonts');
// byte 16: four effect flags, RFU b5, width reduction b6..b8
assert.strictEqual(byteLabels(16)[0], 'Display can be resized');
assert.strictEqual(byteLabels(16)[1], 'Text Wrapping supported');
assert.strictEqual(byteLabels(16)[2], 'Text Scrolling supported');
assert.strictEqual(byteLabels(16)[3], 'Text Attributes supported');
assert.strictEqual(byteLabels(16)[4], 'RFU');
assert.match(byteLabels(16)[5], /Width reduction when in a menu/);
// byte 19 b1..b4: TIA/EIA-136-270 protocol version
assert.match(byteLabels(19)[0], /TIA\/EIA-136-270 protocol version/);
assert.strictEqual(byteLabels(19)[3], byteLabels(19)[0]);
// byte 24 b1..b4: max frames
assert.match(byteLabels(24)[0], /Maximum number of frames supported/);
assert.strictEqual(byteLabels(24)[3], byteLabels(24)[0]);
// every value field matches the TP_VALUE_FIELDS table (no overlap with flags)
for (const f of TP_VALUE_FIELDS) {
for (let b = f.from; b <= f.to; b++) {
assert.match(byteLabels(f.byte)[b - 1], /value|soft keys|Screen|Width|TIA|frames/i,
'byte ' + f.byte + ' b' + b);
}
}
});
test('TP_VALUE_FIELDS decode little-endian bit ranges and round-trip', () => {
const f = (byte, from, to) => ({ byte: byte, from: from, to: to });
// byte 13 b6..b8: 0xE0 = 7 channels (project default)
const mi = 'FFFFFFFF7F9F00DFFF03021FE2000000C3FB000704117800710100000038428003';
const ch = tpValueFieldFor(13);
assert.deepStrictEqual({ byte: ch.byte, from: ch.from, to: ch.to }, f(13, 6, 8));
assert.strictEqual(tpValueWidth(ch), 3);
assert.strictEqual(tpGetValue(mi, ch), 7); // byte 13 = 0xE2
assert.strictEqual(tpGetValue(mi, tpValueFieldFor(11)), 0x02); // soft keys = 2
assert.strictEqual(tpGetValue(mi, tpValueFieldFor(14)), 0x00); // height
assert.strictEqual(tpGetValue(mi, tpValueFieldFor(15)), 0x00); // width
assert.strictEqual(tpGetValue(mi, tpValueFieldFor(16)), 0x00); // width reduction
assert.strictEqual(tpGetValue(mi, tpValueFieldFor(19)), 0x00);
assert.strictEqual(tpGetValue(mi, tpValueFieldFor(24)), 0x00);
// soft keys 0xFE is the largest documented value (byte 11; 'FF' reserved)
const sk = tpValueFieldFor(11);
const blank11 = '00'.repeat(11);
assert.strictEqual(tpGetValue(blank11, sk), 0x00);
assert.strictEqual(tpGetValue(tpSetValue(blank11, sk, 0xFE), sk), 0xFE);
assert.strictEqual(tpGetValue(tpSetValue(blank11, sk, 0xFF), sk), 0xFF);
assert.strictEqual(tpGetValue('FE', sk), null); // byte 11 missing from a short profile
// set: byte 13 b6..b8 = 5 keeps the bearer bits, clamps to 3 bits
const base13 = '00'.repeat(12) + 'E2';
assert.strictEqual(tpSetValue(base13, ch, 5), '00'.repeat(12) + 'A2'); // 0xE2 & 0x1F | (5<<5)
assert.strictEqual(tpSetValue(base13, ch, 8), base13); // clamped to 7
assert.strictEqual(tpSetValue('00'.repeat(13), ch, 3), '00'.repeat(12) + '60');
// a profile shorter than the field's byte grows with zero bytes (like tpSetBit)
assert.strictEqual(tpSetValue('', ch, 3), '00'.repeat(12) + '60');
// round-trip every field through get/set
for (const vf of TP_VALUE_FIELDS) {
const max = (1 << tpValueWidth(vf)) - 1;
const lo = tpSetValue('0000000000000000000000000000000000000000000000000000000000000000', vf, 1);
assert.strictEqual(tpGetValue(lo, vf), 1, 'field byte ' + vf.byte);
const hi = tpSetValue(lo, vf, max);
assert.strictEqual(tpGetValue(hi, vf), max, 'field byte ' + vf.byte);
assert.strictEqual(tpSetValue(hi, vf, 0), '0000000000000000000000000000000000000000000000000000000000000000',
'field byte ' + vf.byte + ' clears');
}
// out-of-range values clamp, garbage reads as 0
assert.strictEqual(tpGetValue('', tpValueFieldFor(11)), null); // byte missing
assert.strictEqual(tpGetValue('FF', tpValueFieldFor(13)), null);
});
test('the Configure dialog renders number inputs for value fields', () => {
const fn = extractFunc(html, 'tpRenderForm');
assert.match(fn, /tpValueFieldFor\(bi \+ 1\)/);
assert.match(fn, /type="number"/);
assert.match(fn, /tpValueInput\(/);
// the soft-keys hint stays visible in the UI
assert.match(html, /FF' is reserved for future use/);
const handler = extractFunc(html, 'tpValueInput');
assert.match(handler, /tpSetValue/);
assert.match(handler, /tpSyncPreset/);
});
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)));
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "pysim-otaman-server"
version = "2.2.20"
version = "2.2.21"
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.
+1 -1
View File
@@ -21,7 +21,7 @@ from osmocom.construct import GsmOrUcs2Adapter
from osmocom.tlv import BER_TLV_IE
VERSION = '2.2.20'
VERSION = '2.2.21'
MAX_ENVELOPE_SEGMENTS = 5 # max SMS segments for outgoing C-APDU in ENVELOPE