v3.18.4: run-log review follow-ups (one ENVELOPE line per event step)
The review of v3.18.3 found the event steps logging twice: the shared sender writes its generic `ENVELOPE(Event Download): type=... data=...` / `ENVELOPE SW: ...` pair, and the runner's step-annotated line repeated the same data. `_send_event_download` gained `log=True`; the runner's envelope and event branches pass `log=False`, so an event step logs exactly one line - the annotated one, which already carries the step number, the event name, the effective source and the response SW. All other callers keep their output unchanged (netsim, the interactive event form, the BIP/timer paths). The review's test-pinning gaps are closed: - the shared sender's per-segment answer line (`OTA SEND: ENVELOPE i/N -> <SW>`) is asserted in the multi-segment test; - the scp80 step's report `sent` C-APDU/SP is asserted in the run-log test; - the single-ENVELOPE-line behaviour (and the source override) is asserted in the event-action test. Tests: 816 frontend + 718 Python; sw cache simple-v386.
This commit is contained in:
+1
-1
@@ -1877,7 +1877,7 @@
|
||||
// ===== Version =====
|
||||
// Single source of truth for the PWA version: shown in the header and used
|
||||
// by the server version check in pysimConnect().
|
||||
const SIMPLE_VERSION = '3.18.3';
|
||||
const SIMPLE_VERSION = '3.18.4';
|
||||
document.getElementById('app-version').textContent = 'v' + SIMPLE_VERSION;
|
||||
|
||||
// ===== Tab switching =====
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
const CACHE = 'simple-v385';
|
||||
const CACHE = 'simple-v386';
|
||||
const URLS = [
|
||||
'index.html',
|
||||
'help.html',
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "pysim-simple-server"
|
||||
version = "3.18.3"
|
||||
version = "3.18.4"
|
||||
description = "HTTP REST server wrapping pysim for the SIMple PWA"
|
||||
requires-python = ">=3.8"
|
||||
# pysim is a git-only dependency installed explicitly by setup.bat/setup.sh.
|
||||
|
||||
@@ -34,7 +34,7 @@ from osmocom.tlv import BER_TLV_IE
|
||||
from osmocom.utils import rpad
|
||||
|
||||
|
||||
VERSION = '3.18.3'
|
||||
VERSION = '3.18.4'
|
||||
|
||||
MAX_ENVELOPE_SEGMENTS = 5 # max SMS segments for outgoing C-APDU in ENVELOPE
|
||||
|
||||
@@ -4652,11 +4652,14 @@ _EVENT_INNER_MAX = 252
|
||||
_EVENT_DATA_MAX = _EVENT_INNER_MAX - 8
|
||||
|
||||
|
||||
def _send_event_download(scc, event_type, event_data=None, drain=True, src=None):
|
||||
def _send_event_download(scc, event_type, event_data=None, drain=True, src=None,
|
||||
log=True):
|
||||
"""Send ENVELOPE(Event Download) for the given event type.
|
||||
Builds: CLA C2 0000 Lc D6 [len] (99 01 [type] 82 02 <src> 81 [extra]).
|
||||
`src` is the source device identity (TS 102 223 8.7: '82' terminal,
|
||||
'83' network); the default is the terminal."""
|
||||
'83' network); the default is the terminal. `log=False` suppresses the
|
||||
generic ENVELOPE lines - the test runner logs its own step-annotated line
|
||||
with the event name, the source and the response SW."""
|
||||
if event_data and len(event_data) > _EVENT_DATA_MAX:
|
||||
raise ValueError('event data does not fit one ENVELOPE (max %d bytes; '
|
||||
'chained delivery is not implemented)' % _EVENT_DATA_MAX)
|
||||
@@ -4679,12 +4682,14 @@ def _send_event_download(scc, event_type, event_data=None, drain=True, src=None)
|
||||
# read as a long-form indicator)
|
||||
d6_tlv = bytes([0xD6, 0x81, len(inner)]) + bytes(inner)
|
||||
env_hex = '%sc20000%02x%s' % (scc.cat_cla, len(d6_tlv), d6_tlv.hex())
|
||||
sys.stderr.write('ENVELOPE(Event Download): type=0x%02x data=%s\n' % (event_type, event_data.hex() if event_data else '(none)'))
|
||||
if log:
|
||||
sys.stderr.write('ENVELOPE(Event Download): type=0x%02x data=%s\n' % (event_type, event_data.hex() if event_data else '(none)'))
|
||||
data, sw = scc._tp.send_apdu(env_hex)
|
||||
if sw.startswith('61'):
|
||||
get_len = int(sw[2:], 16) if len(sw) == 4 else 0x100
|
||||
data, sw = scc._tp.send_apdu('00c00000%02x' % get_len)
|
||||
sys.stderr.write('ENVELOPE SW: %s\n' % sw)
|
||||
if log:
|
||||
sys.stderr.write('ENVELOPE SW: %s\n' % sw)
|
||||
if drain and sw.startswith('91'):
|
||||
_handle_proactive_chain(scc, sw)
|
||||
sw = '9000'
|
||||
@@ -5989,7 +5994,7 @@ def _test_run_action(server, step, ctx):
|
||||
if kind == 'envelope':
|
||||
data, sw = _send_event_download(scc, p['event'],
|
||||
bytes.fromhex(p['data']) if p['data'] else None,
|
||||
drain=False, src=p.get('src'))
|
||||
drain=False, src=p.get('src'), log=False)
|
||||
sent = 'ENVELOPE(Event Download) type=0x%02X' % p['event']
|
||||
_test_log('%s src=%s data=%s -> SW=%s%s'
|
||||
% (sent, p.get('src') or '82', p['data'] or '(none)', sw or '(none)',
|
||||
@@ -5998,7 +6003,7 @@ def _test_run_action(server, step, ctx):
|
||||
# The semantic event download: the builder owns the byte layout.
|
||||
data_hex = events.build(p['event'], p.get('fields') or {})
|
||||
data, sw = _send_event_download(scc, p['event'], bytes.fromhex(data_hex),
|
||||
drain=False, src=p.get('src'))
|
||||
drain=False, src=p.get('src'), log=False)
|
||||
sent = 'ENVELOPE(Event Download) type=0x%02X (%s) data=%s' % (
|
||||
p['event'], events.event_name(p['event']), data_hex)
|
||||
_test_log('%s src=%s -> SW=%s%s'
|
||||
|
||||
@@ -297,6 +297,9 @@ class TestSmsConcatenation(unittest.TestCase):
|
||||
_ota_reference('16', '01', '15', '15', 'b00000', '0000000001', apdu, K, K)
|
||||
|
||||
def test_send_secured_packet_sends_the_segments_in_order(self):
|
||||
import contextlib
|
||||
import io
|
||||
|
||||
import pysim_simple_server.server as srv
|
||||
apdu = '80E80000F0' + '00' * 240 + '00'
|
||||
sp_hex, _ = _build_secured_packet('16', '01', '15', '15', 'b00000',
|
||||
@@ -307,13 +310,19 @@ class TestSmsConcatenation(unittest.TestCase):
|
||||
sent.append(tpdu_hex.upper())
|
||||
return '', '9000'
|
||||
|
||||
buf = io.StringIO()
|
||||
with mock.patch.object(srv, '_send_envelope', side_effect=fake_envelope):
|
||||
result = _send_secured_packet(object(), sp_hex, oa_number='12345')
|
||||
with contextlib.redirect_stderr(buf):
|
||||
result = _send_secured_packet(object(), sp_hex, oa_number='12345')
|
||||
self.assertTrue(result['success'], result)
|
||||
self.assertEqual(result['bytes'], len(sp_hex) // 2)
|
||||
self.assertEqual(result['segments'], len(sent))
|
||||
self.assertTrue(2 <= result['segments'] <= SCP80_MAX_SEGMENTS)
|
||||
total = result['segments']
|
||||
# every segment's answer is logged (the send-ota and test-run logs)
|
||||
log = buf.getvalue()
|
||||
for num in range(1, total + 1):
|
||||
self.assertIn('OTA SEND: ENVELOPE %d/%d -> 9000' % (num, total), log)
|
||||
for num, tpdu in enumerate(sent, start=1):
|
||||
concat = '000301%02X%02X' % (total, num) # IEI 00, IEDL 3, ref 01
|
||||
udhl = '07' if num == 1 else '05'
|
||||
|
||||
+27
-16
@@ -1,6 +1,8 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Tests for the test-script engine (pure parts) and the server-side runner."""
|
||||
|
||||
import contextlib
|
||||
import io
|
||||
import sys
|
||||
import time
|
||||
import unittest
|
||||
@@ -522,26 +524,39 @@ class TestRunnerActions(RunnerTestCase):
|
||||
def test_event_action_sends_the_built_object(self):
|
||||
scc = FakeScc()
|
||||
scc.push('80C2', '', '9000')
|
||||
run = self.run_script(FakeServer(scc), [
|
||||
{'type': 'action', 'kind': 'event',
|
||||
'params': {'event': 'location status',
|
||||
'fields': {'status': '0', 'mcc': '250', 'mnc': '01',
|
||||
'lac': '00FF', 'cell': '0001'}}},
|
||||
])
|
||||
buf = io.StringIO()
|
||||
with contextlib.redirect_stderr(buf):
|
||||
run = self.run_script(FakeServer(scc), [
|
||||
{'type': 'action', 'kind': 'event',
|
||||
'params': {'event': 'location status',
|
||||
'fields': {'status': '0', 'mcc': '250', 'mnc': '01',
|
||||
'lac': '00FF', 'cell': '0001'}}},
|
||||
])
|
||||
self.assertEqual(run['status'], 'ok', run['steps'])
|
||||
apdu = scc.sent[0].upper()
|
||||
# event list + device identities (terminal default) + the built object
|
||||
self.assertIn('99010382028281' + '9B0100930752F01000FF0001', apdu)
|
||||
# the runner logs one step-annotated ENVELOPE line, not the generic
|
||||
# sender pair as well (its line carries the name, src and SW)
|
||||
log = buf.getvalue()
|
||||
self.assertIn('TEST-RUN step 1: ENVELOPE(Event Download) type=0x03 '
|
||||
'(location_status) data=9B0100930752F01000FF0001 src=82 -> SW=9000',
|
||||
log)
|
||||
self.assertNotIn('ENVELOPE(Event Download): type=', log)
|
||||
self.assertNotIn('ENVELOPE SW:', log)
|
||||
# the src override names the network as the source
|
||||
scc2 = FakeScc()
|
||||
scc2.push('80C2', '', '9000')
|
||||
run = self.run_script(FakeServer(scc2), [
|
||||
{'type': 'action', 'kind': 'event',
|
||||
'params': {'event': '0x0B', 'fields': {'tech': '8'}, 'src': '83'}},
|
||||
])
|
||||
buf2 = io.StringIO()
|
||||
with contextlib.redirect_stderr(buf2):
|
||||
run = self.run_script(FakeServer(scc2), [
|
||||
{'type': 'action', 'kind': 'event',
|
||||
'params': {'event': '0x0B', 'fields': {'tech': '8'}, 'src': '83'}},
|
||||
])
|
||||
self.assertEqual(run['status'], 'ok', run['steps'])
|
||||
apdu = scc2.sent[0].upper()
|
||||
self.assertIn('99010B82028381' + 'BF0108', apdu)
|
||||
self.assertIn('src=83 -> SW=9000', buf2.getvalue())
|
||||
# the raw envelope action takes the same source override
|
||||
scc3 = FakeScc()
|
||||
scc3.push('80C2', '', '9000')
|
||||
@@ -733,8 +748,6 @@ class TestRunnerActions(RunnerTestCase):
|
||||
# the semantic lines a verification needs: the plaintext C-APDU, the
|
||||
# built secured packet, the FETCH/CMD/TR pair of an expectation and
|
||||
# the menu selection's ENVELOPE with the resolved item
|
||||
import contextlib
|
||||
import io
|
||||
scc = FakeScc()
|
||||
scc.push('80F2', '', '9102')
|
||||
scc.push('8012', self.SETUP_MENU_CMD, '9000')
|
||||
@@ -770,11 +783,11 @@ class TestRunnerActions(RunnerTestCase):
|
||||
self.assertIn('TEST-RUN step 3: TR=', log)
|
||||
self.assertIn('TEST-RUN step 4: MENU-SELECT ENVELOPE=', log)
|
||||
self.assertIn("item=1 ('One')", log)
|
||||
# the report's label carries the plaintext APDU too (log/report parity)
|
||||
self.assertIn('C-APDU=80E2900000', run['steps'][0]['sent'])
|
||||
|
||||
def test_the_run_log_carries_the_inline_por(self):
|
||||
# the decoded PoR and its R-APDU are logged with the raw packet
|
||||
import contextlib
|
||||
import io
|
||||
scc = FakeScc()
|
||||
preset = {'kic': '15', 'kid': '15', 'kicKey': 'AA' * 16, 'kidKey': 'BB' * 16,
|
||||
'counter': '0000000A', 'tars': preset_tars()}
|
||||
@@ -799,8 +812,6 @@ class TestRunnerActions(RunnerTestCase):
|
||||
self.assertIn('TEST-RUN step 1: R-APDU SW=9000 data=AABB', log)
|
||||
|
||||
def test_expect_decodes_the_submit_por(self):
|
||||
import contextlib
|
||||
import io
|
||||
scc = FakeScc()
|
||||
preset = {'kic': '15', 'kid': '15', 'kicKey': 'AA' * 16, 'kidKey': 'BB' * 16,
|
||||
'counter': '0000000A', 'tars': preset_tars()}
|
||||
|
||||
Reference in New Issue
Block a user