From 31162efc8c673b64d119ed4bbe9a52a55af097a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BD=D1=82=D0=BE=D0=BD=20=D0=A2=D1=80=D0=BE=D1=88?= =?UTF-8?q?=D0=B8=D0=BD?= Date: Mon, 28 Sep 2026 08:23:28 +0300 Subject: [PATCH] feat: log the plaintext RAM APDUs of the install steps (v3.6.19) `_ram_send_gp_apdu` (the shared step sender of /api/ram-install and /api/ram-install-app) now logs every step's plaintext APDU and packed packet, like /api/send-ota's unconditional lines: RAM C-APDU (INSTALL [for load]): 80E602000C07F0414C4641610100000000 RAM SECURED-PACKET (INSTALL [for load]): 00281516212525000000... No flag - the step name keeps the sequence readable, and --apdu-trace stays the card-level (ENVELOPE) trace. Tests: the logging pinned with stubbed packet builders. 498 Python / 650 frontend green; version 3.6.19; sw simple-v292. --- frontend/index.html | 2 +- frontend/sw.js | 2 +- pyproject.toml | 2 +- pysim_simple_server/server.py | 6 +++++- tests/test_ota_helpers.py | 35 +++++++++++++++++++++++++++++++++++ 5 files changed, 43 insertions(+), 4 deletions(-) diff --git a/frontend/index.html b/frontend/index.html index b2cc724..ed02af4 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -1711,7 +1711,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.6.18'; +const SIMPLE_VERSION = '3.6.19'; document.getElementById('app-version').textContent = 'v' + SIMPLE_VERSION; // ===== Tab switching ===== diff --git a/frontend/sw.js b/frontend/sw.js index 6145420..23e6c45 100644 --- a/frontend/sw.js +++ b/frontend/sw.js @@ -1,4 +1,4 @@ -const CACHE = 'simple-v291'; +const CACHE = 'simple-v292'; const URLS = [ 'index.html', 'help.html', diff --git a/pyproject.toml b/pyproject.toml index 8c60a51..d4b0614 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "pysim-simple-server" -version = "3.6.18" +version = "3.6.19" 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. diff --git a/pysim_simple_server/server.py b/pysim_simple_server/server.py index 35f21e5..c5aaf32 100644 --- a/pysim_simple_server/server.py +++ b/pysim_simple_server/server.py @@ -31,7 +31,7 @@ from osmocom.tlv import BER_TLV_IE from osmocom.utils import rpad -VERSION = '3.6.18' +VERSION = '3.6.19' MAX_ENVELOPE_SEGMENTS = 5 # max SMS segments for outgoing C-APDU in ENVELOPE @@ -1208,6 +1208,10 @@ def _ram_send_gp_apdu(server, scc, sp, state, step_name, apdu_hex): sp_hex, _ = _build_secured_packet(spi1, spi2, sp['kic'], sp['kid'], sp['tar'], state['cntr'], apdu_hex, sp['kic_key'], sp['kid_key']) + # Log the plaintext APDU and the packed packet like /api/send-ota does + # (the step name keeps the sequence readable). + sys.stderr.write('RAM C-APDU (%s): %s\n' % (step_name, apdu_hex)) + sys.stderr.write('RAM SECURED-PACKET (%s): %s\n' % (step_name, sp_hex)) except ValueError as e: state['encode_error'] = str(e) state['steps'].append({'name': step_name, 'por_status': 'encode_error', diff --git a/tests/test_ota_helpers.py b/tests/test_ota_helpers.py index b1e8d0c..079b2d0 100644 --- a/tests/test_ota_helpers.py +++ b/tests/test_ota_helpers.py @@ -1475,3 +1475,38 @@ class ResponseScriptingTest(unittest.TestCase): self.assertIsNone(_parse_response_scripting(bytes.fromhex('027100000263100BD2'))) self.assertIsNone(_parse_response_scripting(b'')) + + +class RamSendGpApduLoggingTest(unittest.TestCase): + """_ram_send_gp_apdu (the shared step sender of /api/ram-install and + /api/ram-install-app) logs the plaintext RAM APDU and the packed SCP80 + packet for every step, like /api/send-ota does.""" + + def test_logs_plaintext_apdu_and_packed_packet(self): + import contextlib + import io + from pysim_simple_server import server as srv + + class FakeServer: + sms_oa = '12345' + sms_sc = '12345678912' + + sp = {'spi1': '16', 'spi2': '01', 'kic': '25', 'kid': '25', 'tar': '000000', + 'kic_key': 'AA', 'kid_key': 'BB', 'include_cpi': True} + state = {'steps': [], 'encode_error': None, 'failure': {}, 'cntr': '0000000001'} + orig_build = srv._build_secured_packet + orig_send = srv._send_secured_packet + try: + srv._build_secured_packet = lambda *a, **k: ('AABB', {}) + srv._send_secured_packet = lambda *a, **k: {'success': False, 'error': 'stub'} + buf = io.StringIO() + with contextlib.redirect_stderr(buf): + ok = srv._ram_send_gp_apdu(FakeServer(), object(), sp, state, + 'LOAD (1/2)', '80E8800001AA') + finally: + srv._build_secured_packet = orig_build + srv._send_secured_packet = orig_send + self.assertFalse(ok) + out = buf.getvalue() + self.assertIn('RAM C-APDU (LOAD (1/2)): 80E8800001AA', out) + self.assertIn('RAM SECURED-PACKET (LOAD (1/2)): AABB', out)