diff --git a/frontend/index.html b/frontend/index.html index c16dd2c..b1ecce1 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -1910,7 +1910,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.28.5'; +const SIMPLE_VERSION = '3.28.6'; 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 861d8a9..36d56ce 100644 --- a/frontend/sw.js +++ b/frontend/sw.js @@ -1,4 +1,4 @@ -const CACHE = 'simple-v443'; +const CACHE = 'simple-v444'; const URLS = [ 'index.html', 'help.html', diff --git a/pyproject.toml b/pyproject.toml index ade2d7e..5b399e3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "pysim-simple-server" -version = "3.28.5" +version = "3.28.6" 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 989a4ce..2780d1f 100644 --- a/pysim_simple_server/server.py +++ b/pysim_simple_server/server.py @@ -37,7 +37,7 @@ from osmocom.tlv import BER_TLV_IE from osmocom.utils import rpad -VERSION = '3.28.5' +VERSION = '3.28.6' MAX_ENVELOPE_SEGMENTS = 5 # max SMS segments for outgoing C-APDU in ENVELOPE @@ -1350,8 +1350,16 @@ def _send_envelope(tpdu_hex, scc, sm_sc='12345678912', submit_handler=None, # retrieved from this exchange (TS 31.115 4.2/4.4, v3.25.7). data, sw = _envelope_get_response(scc, data, sw) if handle_proactive and sw.startswith('91'): + # 91xx is the CAT success state (a proactive command is pending), not + # an error: run the chain, then report the exchange as successful. + # The GET RESPONSE above may have delivered the response packet (the + # PoR) in the very same answer - keep those bytes. The old + # `data, sw = '', '9000'` dropped a delivered PoR whenever the card + # signalled a proactive command at the same time (live 2026-10-14: a + # DELETE answered 6113, the GET RESPONSE delivered the PoR with 9130 + # and the operation reported 'no PoR' although the card returned one). _handle_proactive_chain(scc, sw, _capture_sms_tpdu) - data, sw = '', '9000' + sw = '9000' # Late-PoR fallback: only after the last segment of a packet (a # concatenated download cannot have produced its PoR before the card got # every part, and polling mid-packet made spurious STATUS exchanges). diff --git a/tests/test_ota_helpers.py b/tests/test_ota_helpers.py index 8d93f70..1f1a16b 100644 --- a/tests/test_ota_helpers.py +++ b/tests/test_ota_helpers.py @@ -512,6 +512,23 @@ class TestEnvelopeGetResponse(unittest.TestCase): self.assertEqual(scc._tp.sent[1], '00c0000000') self.assertEqual((data, sw), ('AABB', '9000')) + def test_a_por_delivered_with_a_91xx_answer_survives_the_chain(self): + # v3.28.6: 91xx is the CAT success state (a proactive command is + # pending), not an error - and the GET RESPONSE may deliver the + # response packet (the PoR) in the very same answer. The chain runs; + # the delivered bytes must reach the caller (the old branch cleared + # them: live 2026-10-14, a DELETE answered 6113, the GET RESPONSE + # delivered the PoR with 9130 and the operation reported no PoR). + import pysim_simple_server.server as srv + por = '027100000e0a00000000000000140000016101' + scc = self._scc([('', '6113'), (por, '9130')]) + with mock.patch.object(srv, '_handle_proactive_chain') as chain: + data, sw = srv._send_envelope('00', scc, handle_proactive=True) + self.assertEqual(scc._tp.sent[1], '00c0000013') + self.assertEqual((data, sw), (por, '9000')) + self.assertTrue(chain.called) + self.assertEqual(chain.call_args[0][1], '9130') + class TestDecodePor(unittest.TestCase): def test_plaintext_no_cc_synthetic(self):