-
OTAMan SIM OTA with a Human Face v2.1.16
+
OTAMan SIM OTA with a Human Face v2.1.17
●
diff --git a/frontend/sw.js b/frontend/sw.js
index fea8e44..71cdbc5 100644
--- a/frontend/sw.js
+++ b/frontend/sw.js
@@ -1,4 +1,4 @@
-const CACHE = 'otaman-v153';
+const CACHE = 'otaman-v154';
const URLS = [
'index.html',
'help.html',
diff --git a/pyproject.toml b/pyproject.toml
index 58ef5aa..e9abd68 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "pysim-otaman-server"
-version = "2.1.16"
+version = "2.1.17"
description = "HTTP REST server wrapping pysim for the OTAMan PWA"
requires-python = ">=3.8"
# pysim is a git-only dependency installed explicitly by setup.bat/setup.sh.
diff --git a/pysim_otaman_server/server.py b/pysim_otaman_server/server.py
index 2452b35..f4e80a3 100644
--- a/pysim_otaman_server/server.py
+++ b/pysim_otaman_server/server.py
@@ -21,7 +21,7 @@ from osmocom.construct import GsmOrUcs2Adapter
from osmocom.tlv import BER_TLV_IE
-VERSION = '2.1.16'
+VERSION = '2.1.17'
MAX_ENVELOPE_SEGMENTS = 5 # max SMS segments for outgoing C-APDU in ENVELOPE
@@ -1479,10 +1479,16 @@ def _scp81_parse_response(body):
content = body
count, out = 0, []
off = 0
- while off + 1 < len(content):
- tag, tlen = content[off], content[off + 1]
- val = content[off + 2:off + 2 + tlen]
- off += 2 + tlen
+ while off < len(content):
+ tag = content[off]
+ # BER lengths: an R-APDU TLV above 127 bytes is `23 81 FC ...`; a raw
+ # byte >0x7F used as a length silently truncated every listing page
+ # to 127 bytes (live 2026-09-16), hiding later registry entries.
+ tlen, voff = httpota.ber_len_read(content, off + 1)
+ val = content[voff:voff + tlen]
+ if len(val) < tlen:
+ break
+ off = voff + tlen
if tag == 0x80:
count = int.from_bytes(val, 'big') if val else 0
elif tag == 0x23 and len(val) >= 2:
diff --git a/tests/test_scp81.py b/tests/test_scp81.py
index c79b9c3..7f8ebcf 100644
--- a/tests/test_scp81.py
+++ b/tests/test_scp81.py
@@ -855,3 +855,22 @@ class VerbatimScriptTest(unittest.TestCase):
finally:
server._SCP81_SCRIPT = list(server._SCP81_SCRIPTS['explore'])
server._SCP81_SCRIPT_SENT = 0
+
+
+class ResponseTlvLengthTest(unittest.TestCase):
+ def test_long_form_r_apdu_length(self):
+ # A page bigger than 127 bytes: `AF 80 23 81 FC <252 bytes> 00 00`
+ page = bytes.fromhex('E3284F08D276000005AAFFCAFE00019F700101CC08A000000003000000' * 9)[:250]
+ rapdu = page + b'\x63\x10'
+ body = b'\xAF\x80\x23' + bytes([0x81, len(rapdu)]) + rapdu + b'\x00\x00'
+ count, rapdus = server._scp81_parse_response(body)
+ self.assertEqual(len(rapdus), 1)
+ data, sw = rapdus[0]
+ self.assertEqual(sw, '6310')
+ self.assertEqual(len(data), 250)
+
+ def test_short_form_still_works(self):
+ page = bytes.fromhex('E3114F08A0000000030000009F70010FC50100')
+ body = b'\xAF\x80\x23' + bytes([len(page) + 2]) + page + b'\xCA\xFE' + b'\x00\x00'
+ count, rapdus = server._scp81_parse_response(body)
+ self.assertEqual(rapdus, [(page, 'CAFE')])