From 9ad22f2e26aad2b54009b5d7fda1bc601e38ea88 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: Wed, 7 Oct 2026 10:09:14 +0300 Subject: [PATCH] v3.23.2: the start-reset ordering fix (review follow-up) A suite with start_reset and an incomplete SCP80 preset reset the card and only then refused the start: the reset block ran before _test_preset_error. The reset now runs after the preset checks - a run that cannot start must not touch the card - and stays before the ADM gate, which must see the post-reset card session (the reset bumps card_session, invalidating the ADM latch). The ICCID re-match stays with the reset block. Test: an incomplete preset refuses with 400 and _card_reset_reinit is not called. 846 frontend + 830 Python; sw cache simple-v415. --- frontend/index.html | 2 +- frontend/sw.js | 2 +- pyproject.toml | 2 +- pysim_simple_server/server.py | 23 ++++++++++++----------- tests/test_fetch_routes.py | 15 +++++++++++++++ 5 files changed, 30 insertions(+), 14 deletions(-) diff --git a/frontend/index.html b/frontend/index.html index f83a653..1e9f268 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -1904,7 +1904,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.23.1'; +const SIMPLE_VERSION = '3.23.2'; document.getElementById('app-version').textContent = 'v' + SIMPLE_VERSION; // ===== view history (the browser Back/Forward buttons) ===== diff --git a/frontend/sw.js b/frontend/sw.js index 84e7e97..aa91ce0 100644 --- a/frontend/sw.js +++ b/frontend/sw.js @@ -1,4 +1,4 @@ -const CACHE = 'simple-v414'; +const CACHE = 'simple-v415'; const URLS = [ 'index.html', 'help.html', diff --git a/pyproject.toml b/pyproject.toml index deab407..2492189 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "pysim-simple-server" -version = "3.23.1" +version = "3.23.2" 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 3415e58..f89d30e 100644 --- a/pysim_simple_server/server.py +++ b/pysim_simple_server/server.py @@ -36,7 +36,7 @@ from osmocom.tlv import BER_TLV_IE from osmocom.utils import rpad -VERSION = '3.23.1' +VERSION = '3.23.2' MAX_ENVELOPE_SEGMENTS = 5 # max SMS segments for outgoing C-APDU in ENVELOPE @@ -7738,12 +7738,21 @@ class PysimHandler(BaseHTTPRequestHandler): self._send_json(resp, 400) self._log_resp(resp) return + steps = [step for _entry, script in members for step in script['steps']] + if any(s['type'] == 'action' and s['kind'] == 'scp80' for s in steps): + err = _test_preset_error({'steps': steps}, preset) + if err: + resp = {'error': err} + self._send_json(resp, 400) + self._log_resp(resp) + return reset_info = None if suite.get('start_reset'): # A suite may start with a card reset: the physical reset + the # full equip re-initialization, so its scripts run against a - # freshly initialized card. This runs before the scp80 checks and - # the ADM gate: the card session changes, so the ADM latch is + # freshly initialized card. This runs after the preset checks - a + # run that cannot start must not reset the card - and before the + # ADM gate: the card session changes, so the ADM latch is # re-verified against the post-reset card, and the ICCID must # still match the run's preset (a reset can switch the active # profile on an eUICC). @@ -7777,14 +7786,6 @@ class PysimHandler(BaseHTTPRequestHandler): self._send_json(resp, 400) self._log_resp(resp) return - steps = [step for _entry, script in members for step in script['steps']] - if any(s['type'] == 'action' and s['kind'] == 'scp80' for s in steps): - err = _test_preset_error({'steps': steps}, preset) - if err: - resp = {'error': err} - self._send_json(resp, 400) - self._log_resp(resp) - return needed_adm = bool(suite.get('require_adm')) or any( bool(script.get('require_adm')) for _entry, script in members) ok, adm_info, adm_err = _ensure_adm(self.server, preset, needed_adm) diff --git a/tests/test_fetch_routes.py b/tests/test_fetch_routes.py index 118ee52..863f5b0 100644 --- a/tests/test_fetch_routes.py +++ b/tests/test_fetch_routes.py @@ -1702,6 +1702,21 @@ class TestScriptStoreHttpTests(unittest.TestCase): time.sleep(0.05) self.assertFalse(snap['running'], snap) + def test_a_bad_scp80_preset_refuses_before_the_reset(self): + # v3.23.2: the preset checks come first - a suite that cannot start + # must not reset the card + self.server.app = object() + suite = self._suite(name='reset me', start_reset=True) + self._post('/api/test/scripts', self._script( + suite_id=suite['id'], + steps=[{'type': 'action', 'kind': 'scp80', + 'params': {'apdu': '80E2900000'}}])) + with mock.patch.object(self.srv, '_card_reset_reinit') as r: + status, resp = self._post('/api/test/run', {'suite_id': suite['id']}) + self.assertEqual(status, 400, resp) + self.assertIn('incomplete', resp['error']) + r.assert_not_called() + def test_suite_start_reset_refuses_a_mismatched_iccid(self): self.server.app = object() suite = self._suite(name='reset me', start_reset=True)