v3.23.5: the TERMINAL RESPONSE result list and its spec corrections

The step editor's result select (the expect's respond, the proactive-drain's
respond/first) offered five symbolic values with no hex.  It now offers the
full TS 102 223 v18.3.0 8.12.0 list (00-3F), labelled '<hex> - <alias>' (or
'<hex> - <spec name>'), plus a manual hex entry, and an optional
additional-information byte for the general result (83 02 <result> <info>,
default 00; the 8.12.2 terminal-problem values are suggested through a
datalist).

The script aliases were corrected to the spec: refused 03->22,
not_understood 04->32, modified 06->07, no_response 22->12 - a stored script
using one of them now answers with the corrected value; no_response stays
accepted for compatibility but is no longer offered by the editor.

The server's TR decode tables (_RESULT_NAMES_BASIC/_GENERAL, a GSM 11.14
mix displayed in the proactive log) are replaced by the spec table plus the
8.12.2 additional-information names; the dcs field parses as hex (0A used to
be refused and 10 read as decimal 10); the drain summary's 22 label is fixed.

The result labels are not translated (spec text); the unused RU keys were
removed.  Shared fixture frontend/tests/tr_results.json, drift-guarded by
both suites.  855 frontend + 838 Python; sw cache simple-v418.
This commit is contained in:
2026-10-07 16:41:53 +03:00
parent cc335def00
commit e0dfc1032e
14 changed files with 627 additions and 86 deletions
+26 -49
View File
@@ -36,7 +36,7 @@ from osmocom.tlv import BER_TLV_IE
from osmocom.utils import rpad
VERSION = '3.23.4'
VERSION = '3.23.5'
MAX_ENVELOPE_SEGMENTS = 5 # max SMS segments for outgoing C-APDU in ENVELOPE
@@ -4209,52 +4209,22 @@ def _build_tr(scc, cmd_num, cmd_type, dev_src, dev_dst, cmd_qual, dt=None):
return base
_RESULT_NAMES_BASIC = {
0x00: 'Command performed successfully',
0x01: 'Command performed with partial comprehension',
0x02: 'Command performed, with missing information',
0x03: 'REFUSED BY THE ME',
0x04: 'Command not understood by the ME',
0x05: 'Command not permitted by the user',
0x06: 'Command performed with modification',
0x20: 'Proactive SIM session terminated by the user',
0x21: 'Backward move in the proactive SIM session requested by the user',
0x22: 'No response from user',
0x23: 'Help information required by the user',
0x24: 'Action in contradiction with the current timer state',
0x25: 'Interaction with call control by NAA, temporary problem',
0x26: 'Launch browser generic error',
}
def _tr_result_name(b):
"""The general result's name (TS 102 223 v18.3.0 8.12.0). The result byte
has the same coding in the basic (03) and general (83) forms; only the
general form carries additional information, which is named separately
(8.12.2 for the terminal problem)."""
return testscript.TR_RESULTS.get('%02X' % (b & 0xFF))
_RESULT_NAMES_GENERAL = {
0x10: 'Command performed with additional information',
0x20: 'ME currently unable to process command',
0x21: 'Network currently unable to process command',
0x22: 'User did not accept the proactive command',
0x23: 'User cleared down call before connection or network release',
0x24: 'Action in contradiction with the current enforcement state',
0x25: 'Action in contradiction with the current timer state',
0x26: 'ME currently unable to process command',
0x27: 'User did not accept the proactive command',
0x28: 'User cleared down call before connection or network release',
0x29: 'Action in contradiction with the current enforcement state',
0x2A: 'Action in contradiction with the current timer state',
0x30: 'Command performed but partial understanding',
0x31: 'Command performed, with missing information',
0x32: 'REFUSED BY THE ME',
0x33: 'Command not understood by the ME',
0x34: 'Command not permitted by the user',
0x35: 'Command performed with modification',
}
def _tr_result_name(b, is_general):
table = _RESULT_NAMES_GENERAL if is_general else _RESULT_NAMES_BASIC
if b in table:
return table[b]
if 0x40 <= b <= 0x4F or 0x70 <= b <= 0x7F:
return 'Command performed with modification'
if 0x60 <= b <= 0x6F:
return 'Command performed with limited understanding'
def _tr_result_add_info_name(result, info):
"""The general result's additional information name: 8.12.2 defines it for
the terminal problem (result 20); for the network problem (21) the coding
is NAA-specific and only '00' is defined; elsewhere it is not defined."""
if result == 0x20:
return testscript.TR_RESULT_ADD_INFO.get('%02X' % (info & 0xFF))
if result == 0x21 and (info & 0xFF) == 0x00:
return testscript.TR_RESULT_ADD_INFO.get('00')
return None
@@ -4286,9 +4256,13 @@ def _record_tr(entry, tr_tlv, tr_sw=None):
if result is not None:
tag, val = result
entry['tr_result'] = val.hex()
name = _tr_result_name(val[0], tag == 0x83)
name = _tr_result_name(val[0])
if name:
entry['tr_result_name'] = name
if len(val) > 1:
info_name = _tr_result_add_info_name(val[0], val[1])
if info_name:
entry['tr_result_info_name'] = info_name
entry['tr_decoded'] = _decode_tr(entry.get('type_hex'), entry.get('qualifier'), entry['tr_hex'])
except Exception:
entry['tr_decoded'] = []
@@ -7095,9 +7069,12 @@ def _test_drain_pending(scc, fetch_len, result=0x00, max_commands=3):
return {'note': 'drain failed: %s' % e}
if not answered:
return {'note': 'nothing to drain'}
result_name = {0x00: 'ok', 0x10: 'cancel', 0x11: 'back',
0x12: 'timeout', 0x22: 'no response'}.get(result,
'0x%02X' % result)
# the drain's sent-TR summary: the short alias where one exists, else the
# spec name (TS 102 223 8.12.0) - 0x22 is "user did not accept" (the old
# label "no response" belongs to 0x12)
_DRAIN_ALIAS = {0x00: 'ok', 0x10: 'cancel', 0x11: 'back', 0x12: 'timeout',
0x22: 'refused'}
result_name = _DRAIN_ALIAS.get(result) or _tr_result_name(result) or ('0x%02X' % result)
return {'sent': '; '.join('TR %s %s' % (result_name, a['tr']) for a in answered),
'sw': answered[-1]['sw'], 'data': answered[0]['raw'],
'note': '; '.join('pending %s (0x%02X) answered with %s'
+91 -6
View File
@@ -27,6 +27,7 @@ from pysim_simple_server import events
__all__ = [
'ScriptError', 'ACTION_KINDS', 'FAIL_LEVELS', 'POR_CHECKS', 'RESULT_NAMES',
'TR_RESULTS', 'TR_RESULT_ADD_INFO',
'normalise_script', 'normalise_step', 'normalise_respond',
'match_value', 'match_text', 'match_item', 'combine_levels', 'build_tr',
'pack_gsm7',
@@ -37,11 +38,79 @@ ACTION_KINDS = ('envelope', 'event', 'menu-select', 'file-write', 'file-read',
FAIL_LEVELS = ('error', 'warning')
POR_CHECKS = ('none', 'ok', 'any')
# TERMINAL RESPONSE result values commonly used by scripts (TS 102 223 8.12).
# TERMINAL RESPONSE general results (TS 102 223 v18.3.0 8.12.0) and the
# terminal-problem additional information (8.12.2), pinned by
# frontend/tests/tr_results.json (both suites drift-guard against it).
TR_RESULTS = {
'00': 'Command performed successfully',
'01': 'Command performed with partial comprehension',
'02': 'Command performed, with missing information',
'03': 'REFRESH performed with additional EFs read',
'04': 'Command performed successfully, but requested icon could not be displayed',
'05': 'Command performed, but modified by call control by NAA',
'06': 'Command performed successfully, limited service',
'07': 'Command performed with modification',
'08': 'REFRESH performed but indicated NAA was not active',
'09': 'Command performed successfully, tone not played',
'10': 'Proactive UICC session terminated by the user',
'11': 'Backward move in the proactive UICC session requested by the user',
'12': 'No response from user',
'13': 'Help information required by the user',
'14': 'Reserved for GSM/3G',
'15': 'Reserved for 3GPP (for future usage)',
'16': 'Reserved for 3GPP (for future usage)',
'20': 'Terminal currently unable to process command',
'21': 'Network currently unable to process command',
'22': 'User did not accept the proactive command',
'23': 'User cleared down call before connection or network release',
'24': 'Action in contradiction with the current timer state',
'25': 'Interaction with call control by NAA, temporary problem',
'26': 'Launch browser generic error',
'27': 'MMS temporary problem',
'28': 'Reserved for 3GPP (BIP temporary error, T timer in the additional information)',
'29': 'Reserved for 3GPP (for future usage)',
'30': "Command beyond terminal's capabilities",
'31': 'Command type not understood by terminal',
'32': 'Command data not understood by terminal',
'33': 'Command number not known by terminal',
'34': 'Reserved for GSM/3G',
'35': 'Reserved for GSM/3G',
'36': 'Error, required values are missing',
'37': 'Reserved for GSM/3G',
'38': 'MultipleCard commands error',
'39': 'Interaction with call control by NAA, permanent problem',
'3A': 'Bearer Independent Protocol permanent error',
'3B': 'Access Technology unable to process command',
'3C': 'Frames error',
'3D': 'MMS Error',
'3E': 'Reserved for 3GPP (for future usage)',
'3F': 'Reserved for 3GPP (for future usage)',
}
TR_RESULT_ADD_INFO = {
'00': 'No specific cause can be given',
'01': 'Screen is busy',
'02': 'Terminal currently busy on call',
'03': 'Reserved for GSM/3G',
'04': 'No service',
'05': 'Access control class bar',
'06': 'Radio resource not granted',
'07': 'Not in speech call',
'08': 'Reserved for GSM/3G',
'09': 'Terminal currently busy on SEND DTMF command',
'0A': 'No NAA active',
}
# The script authoring aliases: a convenience for the common values (the
# script may also use the two-digit hex value directly). `no_response` is
# kept as an alias of 12 for backward compatibility but is not offered by the
# editor (12's canonical alias is `timeout`); the v3.23.5 review corrected
# `refused` (03 -> 22), `not_understood` (04 -> 32), `modified` (06 -> 07)
# and `no_response` (22 -> 12) to their TS 102 223 8.12.0 meanings.
RESULT_NAMES = {
'ok': 0x00, 'partial': 0x01, 'missing': 0x02, 'refused': 0x03,
'not_understood': 0x04, 'modified': 0x06,
'cancel': 0x10, 'back': 0x11, 'timeout': 0x12, 'no_response': 0x22,
'ok': 0x00, 'partial': 0x01, 'missing': 0x02, 'refused': 0x22,
'not_understood': 0x32, 'modified': 0x07,
'cancel': 0x10, 'back': 0x11, 'timeout': 0x12, 'no_response': 0x12,
}
_HEX_MASK_RE = re.compile(r'^[0-9A-F?]+$')
@@ -89,6 +158,16 @@ def _int(value, what, lo, hi):
return v
def _hex_byte(value, what):
"""A byte given as an int or as a hex string ('0A' / '0a' / '0x0A')."""
if isinstance(value, str):
text = value.strip().lower().replace('0x', '')
if not re.fullmatch(r'[0-9a-f]{1,2}', text):
raise ScriptError('%s must be a hex byte' % what)
return int(text, 16)
return _int(value, what, 0, 255)
def _data_hex(value, what, allow_empty=False):
if value in (None, ''):
if allow_empty:
@@ -562,12 +641,17 @@ def normalise_respond(respond, cmd_type=None):
else:
res = _int(res, 'respond result', 0, 255)
out = {'result': res}
if respond.get('additional_info') is not None:
out['additional_info'] = _hex_byte(respond['additional_info'],
'respond additional_info')
if respond.get('item_id') is not None:
out['item_id'] = _int(respond['item_id'], 'respond item_id', 1, 255)
if respond.get('text') is not None:
out['text'] = str(respond['text'])
dcs = respond.get('dcs')
out['dcs'] = _int(dcs, 'respond dcs', 0, 255) if dcs is not None else 0x00
# the field is a hex byte (the PWA's input strips to hex): parse it as
# hex, not decimal - '0A' used to be refused and '10' read as 0x0A
out['dcs'] = _hex_byte(dcs, 'respond dcs') if dcs is not None else 0x00
# The same coding the TERMINAL RESPONSE builder uses: a text the
# selected dcs cannot code (a non-GSM character with 00/04) must fail
# the script at load, not mid-run after the command was fetched
@@ -627,7 +711,8 @@ def build_tr(cmd_num, cmd_type, dev_dst, dev_src, respond, answer_tlvs=None):
out = bytearray([0x81, 0x03, cmd_num & 0xFF, cmd_type & 0xFF, 0x00])
out += bytes([0x82, 0x02, dev_dst & 0xFF, dev_src & 0xFF])
result = int(respond.get('result', 0))
out += bytes([0x83, 0x02, result & 0xFF, 0x00])
add_info = int(respond.get('additional_info', 0) or 0)
out += bytes([0x83, 0x02, result & 0xFF, add_info & 0xFF])
if answer_tlvs:
out += bytes.fromhex(answer_tlvs)
if respond.get('text') is not None: