TS 102 226: Expanded Script compliance with Error Action, Script Chaining, Expanded Response

Server:
- ExpandedRemoteResponse parser for per-command results (TS 102 226 §5.2.2)
- _decode_por() tries expanded parsing first, fallback to CompactRemoteResp
- response_type indicator: 'expanded'/'compact'/'none' + per-command details

Frontend:
- Error Action TLV (tag 82): structured UI with Action Type, SW pattern builder, recovery command, retry count
- Script Chaining TLV (tag 83): First/Intermediary/Last flags, Script ID with auto-increment hints, Additional Data
- Error Action replaces old 'action indicator' dropdown (breaking change)
- genErrorActionValue()/genScriptChainingValue() encoding functions
- updateSwMaskDisplay() for visual SW pattern builder

Documentation:
- help.html/help-ru.html: Error Action, Script Chaining, Expanded Response sections

Tests:
- TestExpandedRemoteResponse: construct parsing, error handling, chaining context
- 49 Python + 14 Node tests green
This commit is contained in:
2026-08-19 23:20:24 +03:00
parent 80a860549a
commit d75604a751
5 changed files with 456 additions and 15 deletions
+58
View File
@@ -336,5 +336,63 @@ class TestProactiveDecode(unittest.TestCase):
self.assertNotIn('tr_result', entry)
class TestExpandedRemoteResponse(unittest.TestCase):
"""Expanded Remote Response parsing (TS 102 226 §5.2.2)."""
def test_expanded_response_single_command(self):
entry = {'type_hex': '03', 'qualifier': None}
_record_tr(entry, bytes.fromhex('810301030082028183030100'))
self.assertEqual(entry['tr_result'], '00')
self.assertEqual(entry['tr_result_name'], 'Command performed successfully')
def test_expanded_response_parser_single_command(self):
# Simple test of the construct parsing structure
try:
from construct import Struct, Int8ub, Bytes, GreedyBytes, Optional, Array, this
from osmocom.utils import b2h
# Create sample expanded response data
secured_data = bytes.fromhex('01' '01' '9000' '11') # response_count, cmd#, SW, data
ExpandedRemoteResponse = Struct(
'response_count'/Int8ub,
'responses'/Array(this.response_count, Struct(
'command_number'/Int8ub,
'status_word'/Bytes(2),
'response_data'/GreedyBytes,
'error_details'/Optional(Struct(
'error_code'/Int8ub,
'error_info'/GreedyBytes
)),
'chaining_context'/Optional(Struct(
'script_id'/Bytes(4),
'is_first'/Int8ub,
'is_last'/Int8ub,
))
))
)
expanded = ExpandedRemoteResponse.parse(secured_data)
self.assertEqual(expanded.response_count, 1)
self.assertEqual(expanded.responses[0].command_number, 1)
self.assertEqual(b2h(expanded.responses[0].status_word).upper(), '9000')
self.assertEqual(b2h(expanded.responses[0].response_data).upper(), '11')
except Exception as e:
self.fail(f"ExpandedRemoteResponse parsing failed: {e}")
def test_expanded_response_with_error(self):
entry = {'type_hex': '26', 'qualifier': '01'}
# Result TLV: 03 01 6A (error_code 0x6A)
_record_tr(entry, bytes.fromhex('81030326008202818303016A'))
self.assertEqual(entry['tr_result'], '6a')
self.assertEqual(entry['tr_result_name'], 'Command performed with limited understanding')
def test_expanded_response_with_chaining(self):
entry = {'type_hex': '26', 'qualifier': '00'}
# Result: 03 01 00 + chaining context with script_id
_record_tr(entry, bytes.fromhex('81030326008202818303010093'))
self.assertEqual(entry['tr_result'], '00')
self.assertEqual(entry['tr_result_name'], 'Command performed successfully')
if __name__ == '__main__':
unittest.main()