feat: decode the Items Next Action Indicator in proactive commands (v3.5.3)
SET UP MENU / SELECT ITEM items carry an optional Items Next Action Indicator (TS 102 223 8.24, tag '18'): one byte per item, in list order, coded with the Table 9.4 values marked "Used for Next Action Indicator". The decoder ignored it, so neither the proactive log nor the STK menu showed what the card would do when an item is selected. - server: NAI_TYPES whitelist (the ToC-only values - e.g. '26', '27', '47' - are reserved there and are ignored, as are '00' and unlisted values), _nai_name(), _attach_nai() (a short NAI list leaves the tail without an indicator, extra bytes are ignored); _parse_select_item() and _parse_setup_menu_items() plus the TERMINAL PROFILE SET UP MENU walk attach nai/nai_name to the items; _decode_cmd() renders '1. Menu -> SET UP MENU'. - PWA: stkMenuNaiSuffix() adds a small gray '<name>' suffix to STK menu items that carry an NAI; the proactive log picks the decode up via cmd_decoded. - tests: SET UP MENU / SELECT ITEM vectors with NAIs, a reserved value that is ignored, a short NAI list, and the stkMenuNaiSuffix unit test. - docs: UICC_SPECS 6.5 gained the '18' Items next action indicator row (8.24/9.4); help EN/RU mention the menu suffix; AGENTS files updated. 548 frontend / 413 Python green; version 3.5.3; sw cache simple-v255.
This commit is contained in:
@@ -482,7 +482,7 @@
|
|||||||
<p class="text-sm mb-3">Работа с сессией Card Application Toolkit. Три подвкладки: <strong>«Телефон»</strong> (меню STK, STATUS и опрос, подписанные события, журнал проактивных команд), <strong>«Конфигурация TR»</strong> (данные ответов, подставляемые в TERMINAL RESPONSE для проактивных команд) и <strong>«eSIM»</strong> (локальные операции с eUICC).</p>
|
<p class="text-sm mb-3">Работа с сессией Card Application Toolkit. Три подвкладки: <strong>«Телефон»</strong> (меню STK, STATUS и опрос, подписанные события, журнал проактивных команд), <strong>«Конфигурация TR»</strong> (данные ответов, подставляемые в TERMINAL RESPONSE для проактивных команд) и <strong>«eSIM»</strong> (локальные операции с eUICC).</p>
|
||||||
|
|
||||||
<h3 id="stk-menu" class="text-lg font-medium mb-2">8.1 Меню STK</h3>
|
<h3 id="stk-menu" class="text-lg font-medium mb-2">8.1 Меню STK</h3>
|
||||||
<p class="text-sm mb-3">Если карта выдала команду SET UP MENU, вверху этого представления появляется блок «Меню STK» с изумрудной кнопкой <strong>STK: <название></strong>, открывающей оверлей меню (браузер STK-меню карты). Если карта не задала меню, вместо кнопки показывается «Меню не задано картой». Состояние меню обновляется при каждом открытии представления. Интерактивные проактивные команды всегда получают TERMINAL RESPONSE: оверлей ждёт вашего выбора, и если вы не ответили и не нажали <strong>Timeout</strong>, сервер сам отвечает результатом timeout через <code class="font-mono text-sm">--menu-timeout</code> секунд (по умолчанию 60, <code class="font-mono text-sm">0</code> отключает). <strong>Назад</strong> и <strong>Timeout</strong> продолжают диалог с картой: если карта в ответ выдаёт следующую проактивную команду (SELECT ITEM или DISPLAY TEXT), панель показывает её; кэшированное верхнее меню появляется только когда карте больше нечего выполнять.</p>
|
<p class="text-sm mb-3">Если карта выдала команду SET UP MENU, вверху этого представления появляется блок «Меню STK» с изумрудной кнопкой <strong>STK: <название></strong>, открывающей оверлей меню (браузер STK-меню карты). Если карта не задала меню, вместо кнопки показывается «Меню не задано картой». Состояние меню обновляется при каждом открытии представления. Пункты меню, для которых карта передала <strong>Next Action Indicator</strong> (TS 102 223 §8.24), показывают его серым суффиксом (например, <code class="font-mono text-sm">→ SET UP MENU</code>) — что карта выполнит при выборе пункта. Интерактивные проактивные команды всегда получают TERMINAL RESPONSE: оверлей ждёт вашего выбора, и если вы не ответили и не нажали <strong>Timeout</strong>, сервер сам отвечает результатом timeout через <code class="font-mono text-sm">--menu-timeout</code> секунд (по умолчанию 60, <code class="font-mono text-sm">0</code> отключает). <strong>Назад</strong> и <strong>Timeout</strong> продолжают диалог с картой: если карта в ответ выдаёт следующую проактивную команду (SELECT ITEM или DISPLAY TEXT), панель показывает её; кэшированное верхнее меню появляется только когда карте больше нечего выполнять.</p>
|
||||||
|
|
||||||
<h3 id="subscribed-events" class="text-lg font-medium mb-2">8.2 Подписанные события (SET UP EVENT LIST)</h3>
|
<h3 id="subscribed-events" class="text-lg font-medium mb-2">8.2 Подписанные события (SET UP EVENT LIST)</h3>
|
||||||
<p class="text-sm mb-2">События, которые отслеживает карта. У каждого события есть кнопка <strong>Отправить</strong>, открывающая форму, специфичную для типа события:</p>
|
<p class="text-sm mb-2">События, которые отслеживает карта. У каждого события есть кнопка <strong>Отправить</strong>, открывающая форму, специфичную для типа события:</p>
|
||||||
|
|||||||
+1
-1
@@ -481,7 +481,7 @@
|
|||||||
<p class="text-sm mb-3">Interacts with the Card Application Toolkit session. The view has three pills: <strong>Phone</strong> (STK menu, STATUS and polling, subscribed events, proactive command log), <strong>TR Config</strong> (response data injected into TERMINAL RESPONSEs for proactive commands) and <strong>eSIM</strong> (local eUICC operations).</p>
|
<p class="text-sm mb-3">Interacts with the Card Application Toolkit session. The view has three pills: <strong>Phone</strong> (STK menu, STATUS and polling, subscribed events, proactive command log), <strong>TR Config</strong> (response data injected into TERMINAL RESPONSEs for proactive commands) and <strong>eSIM</strong> (local eUICC operations).</p>
|
||||||
|
|
||||||
<h3 id="stk-menu" class="text-lg font-medium mb-2">8.1 STK menu</h3>
|
<h3 id="stk-menu" class="text-lg font-medium mb-2">8.1 STK menu</h3>
|
||||||
<p class="text-sm mb-3">When the card has issued a SET UP MENU command, a “STK menu” block appears at the top of this view with an emerald <strong>STK: <title></strong> button that opens the menu overlay (same as the card’s STK menu browser). If the card has not set up a menu, the block shows “No menu set by the card” instead. The menu state is refreshed each time the view is opened. User-interactive proactive commands always get a TERMINAL RESPONSE: the overlay pauses for your choice, and if you neither answer nor press <strong>Timeout</strong>, the server answers with a timeout result after the <code class="font-mono text-sm">--menu-timeout</code> seconds (default 60, <code class="font-mono text-sm">0</code> disables). <strong>Back</strong> and <strong>Timeout</strong> keep the dialogue with the card going: when the card replies with a further proactive command (SELECT ITEM or DISPLAY TEXT) the panel shows it; the cached top menu appears only when the card has nothing more to execute.</p>
|
<p class="text-sm mb-3">When the card has issued a SET UP MENU command, a “STK menu” block appears at the top of this view with an emerald <strong>STK: <title></strong> button that opens the menu overlay (same as the card’s STK menu browser). If the card has not set up a menu, the block shows “No menu set by the card” instead. The menu state is refreshed each time the view is opened. Menu items whose card-provided <strong>Next Action Indicator</strong> (TS 102 223 §8.24) is present show it as a small gray suffix (e.g. <code class="font-mono text-sm">→ SET UP MENU</code>) — what the card would do when the item is selected. User-interactive proactive commands always get a TERMINAL RESPONSE: the overlay pauses for your choice, and if you neither answer nor press <strong>Timeout</strong>, the server answers with a timeout result after the <code class="font-mono text-sm">--menu-timeout</code> seconds (default 60, <code class="font-mono text-sm">0</code> disables). <strong>Back</strong> and <strong>Timeout</strong> keep the dialogue with the card going: when the card replies with a further proactive command (SELECT ITEM or DISPLAY TEXT) the panel shows it; the cached top menu appears only when the card has nothing more to execute.</p>
|
||||||
|
|
||||||
<h3 id="subscribed-events" class="text-lg font-medium mb-2">8.2 Subscribed events (SET UP EVENT LIST)</h3>
|
<h3 id="subscribed-events" class="text-lg font-medium mb-2">8.2 Subscribed events (SET UP EVENT LIST)</h3>
|
||||||
<p class="text-sm mb-2">The events the card monitors. Each event has a <strong>Send</strong> button that opens a form specific to the event type:</p>
|
<p class="text-sm mb-2">The events the card monitors. Each event has a <strong>Send</strong> button that opens a form specific to the event type:</p>
|
||||||
|
|||||||
+10
-2
@@ -1559,7 +1559,7 @@
|
|||||||
// ===== Version =====
|
// ===== Version =====
|
||||||
// Single source of truth for the PWA version: shown in the header and used
|
// Single source of truth for the PWA version: shown in the header and used
|
||||||
// by the server version check in pysimConnect().
|
// by the server version check in pysimConnect().
|
||||||
const SIMPLE_VERSION = '3.5.2';
|
const SIMPLE_VERSION = '3.5.3';
|
||||||
document.getElementById('app-version').textContent = 'v' + SIMPLE_VERSION;
|
document.getElementById('app-version').textContent = 'v' + SIMPLE_VERSION;
|
||||||
|
|
||||||
// ===== Tab switching =====
|
// ===== Tab switching =====
|
||||||
@@ -7693,6 +7693,12 @@ async function stkMenuExit() {
|
|||||||
stkMenuClose();
|
stkMenuClose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TS 102 223 8.24: the item's next action (coded like the Type of Command);
|
||||||
|
// the server only attaches nai_name for values the table marks for the NAI.
|
||||||
|
function stkMenuNaiSuffix(item) {
|
||||||
|
return (item && item.nai_name) ? '\u25b8 ' + item.nai_name : '';
|
||||||
|
}
|
||||||
|
|
||||||
function stkMenuRenderItems() {
|
function stkMenuRenderItems() {
|
||||||
const content = document.getElementById('stk-menu-panel-content');
|
const content = document.getElementById('stk-menu-panel-content');
|
||||||
content.innerHTML = '<span class="text-gray-500 text-sm">Loading...</span>';
|
content.innerHTML = '<span class="text-gray-500 text-sm">Loading...</span>';
|
||||||
@@ -7700,7 +7706,9 @@ function stkMenuRenderItems() {
|
|||||||
if (!data || !data.items) { content.innerHTML = '<span class="text-red-500 text-sm">No menu data</span>'; return; }
|
if (!data || !data.items) { content.innerHTML = '<span class="text-red-500 text-sm">No menu data</span>'; return; }
|
||||||
let html = '<div class="space-y-1">';
|
let html = '<div class="space-y-1">';
|
||||||
data.items.forEach(item => {
|
data.items.forEach(item => {
|
||||||
html += '<div class="flex items-center gap-2 px-2 py-1.5 rounded hover:bg-gray-100 dark:hover:bg-slate-700 cursor-pointer" onclick="stkMenuItemClick(' + item.id + ')"><span class="w-5 text-xs text-gray-300 dark:text-gray-600 text-right">' + item.id + '</span><span class="text-sm">' + esc(item.text) + '</span></div>';
|
const nai = stkMenuNaiSuffix(item);
|
||||||
|
html += '<div class="flex items-center gap-2 px-2 py-1.5 rounded hover:bg-gray-100 dark:hover:bg-slate-700 cursor-pointer" onclick="stkMenuItemClick(' + item.id + ')"><span class="w-5 text-xs text-gray-300 dark:text-gray-600 text-right">' + item.id + '</span><span class="text-sm">' + esc(item.text) + '</span>' +
|
||||||
|
(nai ? '<span class="text-xs text-gray-400 dark:text-slate-500">' + esc(nai) + '</span>' : '') + '</div>';
|
||||||
});
|
});
|
||||||
html += '</div>';
|
html += '</div>';
|
||||||
content.innerHTML = html;
|
content.innerHTML = html;
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
const CACHE = 'simple-v254';
|
const CACHE = 'simple-v255';
|
||||||
const URLS = [
|
const URLS = [
|
||||||
'index.html',
|
'index.html',
|
||||||
'help.html',
|
'help.html',
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ function extractFunc(src, name, asyncFn) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let code = extractFunc(html, 'stkMenuRespond', true) + '\n';
|
let code = extractFunc(html, 'stkMenuRespond', true) + '\n';
|
||||||
|
code += extractFunc(html, 'stkMenuNaiSuffix') + '\n';
|
||||||
code += 'globalThis.esc = s => s;\n';
|
code += 'globalThis.esc = s => s;\n';
|
||||||
eval(code);
|
eval(code);
|
||||||
|
|
||||||
@@ -38,6 +39,14 @@ function setup(response) {
|
|||||||
return calls;
|
return calls;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test('the STK menu item suffix shows the item next action (8.24)', () => {
|
||||||
|
assert.strictEqual(stkMenuNaiSuffix({ id: 1, text: 'Menu', nai: 0x25, nai_name: 'SET UP MENU' }),
|
||||||
|
'\u25b8 SET UP MENU');
|
||||||
|
// no NAI (or a reserved one, which the server drops) -> no suffix
|
||||||
|
assert.strictEqual(stkMenuNaiSuffix({ id: 2, text: 'Info' }), '');
|
||||||
|
assert.strictEqual(stkMenuNaiSuffix(null), '');
|
||||||
|
});
|
||||||
|
|
||||||
test('back with a fetched SELECT ITEM continues the card dialogue', async () => {
|
test('back with a fetched SELECT ITEM continues the card dialogue', async () => {
|
||||||
const data = { type: 'select_item', items: [{ id: 1, text: 'Info' }] };
|
const data = { type: 'select_item', items: [{ id: 1, text: 'Info' }] };
|
||||||
const calls = setup(data);
|
const calls = setup(data);
|
||||||
|
|||||||
+1
-1
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "pysim-simple-server"
|
name = "pysim-simple-server"
|
||||||
version = "3.5.2"
|
version = "3.5.3"
|
||||||
description = "HTTP REST server wrapping pysim for the SIMple PWA"
|
description = "HTTP REST server wrapping pysim for the SIMple PWA"
|
||||||
requires-python = ">=3.8"
|
requires-python = ">=3.8"
|
||||||
# pysim is a git-only dependency installed explicitly by setup.bat/setup.sh.
|
# pysim is a git-only dependency installed explicitly by setup.bat/setup.sh.
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ from osmocom.tlv import BER_TLV_IE
|
|||||||
from osmocom.utils import rpad
|
from osmocom.utils import rpad
|
||||||
|
|
||||||
|
|
||||||
VERSION = '3.5.2'
|
VERSION = '3.5.3'
|
||||||
|
|
||||||
MAX_ENVELOPE_SEGMENTS = 5 # max SMS segments for outgoing C-APDU in ENVELOPE
|
MAX_ENVELOPE_SEGMENTS = 5 # max SMS segments for outgoing C-APDU in ENVELOPE
|
||||||
|
|
||||||
@@ -1691,12 +1691,12 @@ def _decode_cmd(cmd_type, raw, qualifier):
|
|||||||
if cmd_type == 0x24:
|
if cmd_type == 0x24:
|
||||||
items = _parse_select_item(raw)
|
items = _parse_select_item(raw)
|
||||||
if items:
|
if items:
|
||||||
return [{'label': 'Items', 'value': ', '.join('%s. %s' % (it['id'], it['text']) for it in items)}]
|
return [{'label': 'Items', 'value': ', '.join(_item_line(it) for it in items)}]
|
||||||
return []
|
return []
|
||||||
if cmd_type == 0x25:
|
if cmd_type == 0x25:
|
||||||
items = _parse_setup_menu_items(raw)
|
items = _parse_setup_menu_items(raw)
|
||||||
if items:
|
if items:
|
||||||
return [{'label': 'Items', 'value': ', '.join('%s. %s' % (it['id'], it['text']) for it in items)}]
|
return [{'label': 'Items', 'value': ', '.join(_item_line(it) for it in items)}]
|
||||||
return []
|
return []
|
||||||
if cmd_type in (0x40, 0x42, 0x43):
|
if cmd_type in (0x40, 0x42, 0x43):
|
||||||
return _decode_bip_cmd(cmd_type, raw)
|
return _decode_bip_cmd(cmd_type, raw)
|
||||||
@@ -3567,8 +3567,57 @@ def _parse_display_text(raw):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
# TS 102 223 8.24 + Table 9.4: the Items Next Action Indicator reuses the
|
||||||
|
# Type of Command coding, but only the values the table marks "Used for Next
|
||||||
|
# Action Indicator". The ToC-only values are reserved there and shall be
|
||||||
|
# ignored, as are '00' and any value not listed (8.24).
|
||||||
|
NAI_TYPES = frozenset([
|
||||||
|
0x10, 0x11, 0x12, 0x13, 0x15, # SET UP CALL / SEND SS / SEND USSD / SEND SM / LAUNCH BROWSER
|
||||||
|
0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x28, # PLAY TONE .. SET UP IDLE MODE TEXT
|
||||||
|
0x30, 0x31, 0x32, 0x33, # PERFORM CARD APDU .. GET READER STATUS
|
||||||
|
0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, # BIP commands
|
||||||
|
0x60, 0x61, 0x62, # MMS commands
|
||||||
|
0x81, # end of the proactive session
|
||||||
|
])
|
||||||
|
|
||||||
|
|
||||||
|
def _nai_name(value):
|
||||||
|
"""Items Next Action Indicator name (TS 102 223 Table 9.4); None = reserved."""
|
||||||
|
if value in NAI_TYPES:
|
||||||
|
return PROACTIVE_TYPE_NAMES.get(value)
|
||||||
|
if 0xF0 <= value <= 0xFE:
|
||||||
|
return 'Proprietary (0x%02X)' % value
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _attach_nai(items, nai):
|
||||||
|
"""Attach an Items Next Action Indicator list (TS 102 223 8.24) to items.
|
||||||
|
|
||||||
|
One byte per item in list order; a short list leaves the tail without an
|
||||||
|
indicator, extra bytes are ignored, reserved values are skipped (8.24)."""
|
||||||
|
if not items or not nai:
|
||||||
|
return items
|
||||||
|
for i, item in enumerate(items):
|
||||||
|
if i >= len(nai):
|
||||||
|
break
|
||||||
|
name = _nai_name(nai[i])
|
||||||
|
if name:
|
||||||
|
item['nai'] = nai[i]
|
||||||
|
item['nai_name'] = name
|
||||||
|
return items
|
||||||
|
|
||||||
|
|
||||||
|
def _item_line(item):
|
||||||
|
"""'1. Menu' plus ' -> SET UP MENU' when the item has a next action."""
|
||||||
|
line = '%s. %s' % (item['id'], item['text'])
|
||||||
|
if item.get('nai_name'):
|
||||||
|
line += ' \u2192 ' + item['nai_name']
|
||||||
|
return line
|
||||||
|
|
||||||
|
|
||||||
def _parse_select_item(raw):
|
def _parse_select_item(raw):
|
||||||
items = []
|
items = []
|
||||||
|
nai = None
|
||||||
if raw[0] == 0xD0:
|
if raw[0] == 0xD0:
|
||||||
off = _skip_ber_len(raw, 1)
|
off = _skip_ber_len(raw, 1)
|
||||||
while off < len(raw) - 1:
|
while off < len(raw) - 1:
|
||||||
@@ -3581,11 +3630,14 @@ def _parse_select_item(raw):
|
|||||||
pass
|
pass
|
||||||
elif tag in (0x8F, 0x0F) and tlen >= 2:
|
elif tag in (0x8F, 0x0F) and tlen >= 2:
|
||||||
items.append({'id': val[0], 'text': _decode_stk_text(val[1:])})
|
items.append({'id': val[0], 'text': _decode_stk_text(val[1:])})
|
||||||
return items
|
elif tag in (0x18, 0x98) and tlen >= 1:
|
||||||
|
nai = val
|
||||||
|
return _attach_nai(items, nai)
|
||||||
|
|
||||||
|
|
||||||
def _parse_setup_menu_items(raw):
|
def _parse_setup_menu_items(raw):
|
||||||
items = []
|
items = []
|
||||||
|
nai = None
|
||||||
if not raw or raw[0] != 0xD0:
|
if not raw or raw[0] != 0xD0:
|
||||||
return items
|
return items
|
||||||
off = _skip_ber_len(raw, 1)
|
off = _skip_ber_len(raw, 1)
|
||||||
@@ -3594,7 +3646,9 @@ def _parse_setup_menu_items(raw):
|
|||||||
val = raw[off + 2: off + 2 + tlen]; off += 2 + tlen
|
val = raw[off + 2: off + 2 + tlen]; off += 2 + tlen
|
||||||
if tag == 0x8F and tlen >= 2:
|
if tag == 0x8F and tlen >= 2:
|
||||||
items.append({'id': val[0], 'text': _decode_stk_text(val[1:])})
|
items.append({'id': val[0], 'text': _decode_stk_text(val[1:])})
|
||||||
return items
|
elif tag in (0x18, 0x98) and tlen >= 1:
|
||||||
|
nai = val
|
||||||
|
return _attach_nai(items, nai)
|
||||||
|
|
||||||
|
|
||||||
def _handle_proactive_chain(scc, sw91, on_fetch=None, status_poll=True):
|
def _handle_proactive_chain(scc, sw91, on_fetch=None, status_poll=True):
|
||||||
@@ -3676,6 +3730,7 @@ def _send_terminal_profile(scc, tp_hex):
|
|||||||
off = _skip_ber_len(raw, 1)
|
off = _skip_ber_len(raw, 1)
|
||||||
menu = None
|
menu = None
|
||||||
items = []
|
items = []
|
||||||
|
nai = None
|
||||||
while off < len(raw) - 1:
|
while off < len(raw) - 1:
|
||||||
tag, tlen = raw[off], raw[off + 1]
|
tag, tlen = raw[off], raw[off + 1]
|
||||||
val = raw[off + 2: off + 2 + tlen]
|
val = raw[off + 2: off + 2 + tlen]
|
||||||
@@ -3697,9 +3752,12 @@ def _send_terminal_profile(scc, tp_hex):
|
|||||||
except Exception:
|
except Exception:
|
||||||
txt = val[1:].hex()
|
txt = val[1:].hex()
|
||||||
items.append({'id': val[0], 'text': txt})
|
items.append({'id': val[0], 'text': txt})
|
||||||
|
elif tag in (0x18, 0x98) and tlen >= 1:
|
||||||
|
nai = val
|
||||||
elif tag in (0x99, 0x19) and tlen >= 1:
|
elif tag in (0x99, 0x19) and tlen >= 1:
|
||||||
event_list = [b for b in val]
|
event_list = [b for b in val]
|
||||||
if menu:
|
if menu:
|
||||||
|
_attach_nai(items, nai)
|
||||||
sim_menu = menu
|
sim_menu = menu
|
||||||
if fdata and cmd_type:
|
if fdata and cmd_type:
|
||||||
entry = _log_proactive(cmd_type, raw, cmd_qual, cmd_num)
|
entry = _log_proactive(cmd_type, raw, cmd_qual, cmd_num)
|
||||||
|
|||||||
@@ -28,6 +28,8 @@ from pysim_simple_server.server import (
|
|||||||
_decode_tr,
|
_decode_tr,
|
||||||
_log_proactive,
|
_log_proactive,
|
||||||
_ota_reference,
|
_ota_reference,
|
||||||
|
_parse_select_item,
|
||||||
|
_parse_setup_menu_items,
|
||||||
_record_tr,
|
_record_tr,
|
||||||
_send_secured_packet,
|
_send_secured_packet,
|
||||||
_spi_from_bytes,
|
_spi_from_bytes,
|
||||||
@@ -432,6 +434,43 @@ class TestProactiveDecode(unittest.TestCase):
|
|||||||
r = _decode_cmd(0x05, bytes.fromhex('d00c810301050082028381990101'), None)
|
r = _decode_cmd(0x05, bytes.fromhex('d00c810301050082028381990101'), None)
|
||||||
self.assertEqual(r, [{'label': 'Events', 'value': 'Call connected'}])
|
self.assertEqual(r, [{'label': 'Events', 'value': 'Call connected'}])
|
||||||
|
|
||||||
|
def test_decode_cmd_set_up_menu_with_next_action(self):
|
||||||
|
# TS 102 223 8.24: one NAI byte per item, in item order (UCS2 item text).
|
||||||
|
extras = bytes.fromhex(
|
||||||
|
'8F0A0180004D0065006E0075' # 1: 'Menu'
|
||||||
|
'8F0A028000430061006C006C' # 2: 'Call'
|
||||||
|
'18022510') # NAI: SET UP MENU, SET UP CALL
|
||||||
|
raw = self._cmd_raw(0x25, 0x00, extras)
|
||||||
|
items = _parse_setup_menu_items(raw)
|
||||||
|
self.assertEqual([it['nai'] for it in items], [0x25, 0x10])
|
||||||
|
self.assertEqual([it['nai_name'] for it in items], ['SET UP MENU', 'SET UP CALL'])
|
||||||
|
self.assertEqual(self._decoded(0x25, raw)['Items'],
|
||||||
|
'1. Menu \u2192 SET UP MENU, 2. Call \u2192 SET UP CALL')
|
||||||
|
|
||||||
|
def test_decode_cmd_next_action_reserved_is_ignored(self):
|
||||||
|
# '26' (PROVIDE LOCAL INFORMATION) is Type-of-Command only, so it is
|
||||||
|
# reserved for the NAI and shall be ignored (8.24).
|
||||||
|
extras = bytes.fromhex('8F0A0180004D0065006E0075' '180126')
|
||||||
|
raw = self._cmd_raw(0x25, 0x00, extras)
|
||||||
|
items = _parse_setup_menu_items(raw)
|
||||||
|
self.assertNotIn('nai', items[0])
|
||||||
|
self.assertNotIn('nai_name', items[0])
|
||||||
|
self.assertEqual(self._decoded(0x25, raw)['Items'], '1. Menu')
|
||||||
|
|
||||||
|
def test_decode_cmd_select_item_short_next_action_list(self):
|
||||||
|
# A short NAI list leaves the tail without an indicator; extra bytes
|
||||||
|
# beyond the item count are ignored (8.24).
|
||||||
|
extras = bytes.fromhex(
|
||||||
|
'8F0A0180004D0065006E0075'
|
||||||
|
'8F0A028000430061006C006C'
|
||||||
|
'180121')
|
||||||
|
raw = self._cmd_raw(0x24, 0x00, extras)
|
||||||
|
items = _parse_select_item(raw)
|
||||||
|
self.assertEqual(items[0]['nai_name'], 'DISPLAY TEXT')
|
||||||
|
self.assertNotIn('nai_name', items[1])
|
||||||
|
self.assertEqual(self._decoded(0x24, raw)['Items'],
|
||||||
|
'1. Menu \u2192 DISPLAY TEXT, 2. Call')
|
||||||
|
|
||||||
def test_decode_cmd_send_short_message(self):
|
def test_decode_cmd_send_short_message(self):
|
||||||
# SEND SHORT MESSAGE with an SMS-SUBMIT TPDU carrying GSM-7 text.
|
# SEND SHORT MESSAGE with an SMS-SUBMIT TPDU carrying GSM-7 text.
|
||||||
tpdu = bytes.fromhex('010006912143F5000005E8329BFD06')
|
tpdu = bytes.fromhex('010006912143F5000005E8329BFD06')
|
||||||
|
|||||||
Reference in New Issue
Block a user