-
OTAMan SIM OTA with a Human Face v2.1.11
+
OTAMan SIM OTA with a Human Face v2.1.12
●
diff --git a/frontend/sw.js b/frontend/sw.js
index 587101b..2ca4573 100644
--- a/frontend/sw.js
+++ b/frontend/sw.js
@@ -1,4 +1,4 @@
-const CACHE = 'otaman-v148';
+const CACHE = 'otaman-v149';
const URLS = [
'index.html',
'help.html',
diff --git a/pyproject.toml b/pyproject.toml
index 5433bef..3520a70 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "pysim-otaman-server"
-version = "2.1.11"
+version = "2.1.12"
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 2baae70..bcf25b5 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.11'
+VERSION = '2.1.12'
MAX_ENVELOPE_SEGMENTS = 5 # max SMS segments for outgoing C-APDU in ENVELOPE
@@ -414,9 +414,12 @@ def _cap_apdu_sequence(loadfile_aid, module_aid, loadfile_data, sd_aid='',
ifl_data = _lv(loadfile_aid) + _lv(sd) + '00' + '00' + '00'
apdus = ['80E60200%02X%s00' % (len(ifl_data) // 2, ifl_data)]
loadfile_tlv = 'C4' + _ber_len(len(loadfile_data) // 2) + loadfile_data
- total_bytes = len(loadfile_tlv) // 2
- blocks = [loadfile_tlv[i * 2:(i + block_size) * 2]
- for i in range(0, (total_bytes + block_size - 1) // block_size)]
+ # Split the TLV into consecutive 240-byte blocks (char offsets, 2 per
+ # byte). The earlier form indexed with the block number ('i * 2'), which
+ # produced overlapping 1-byte-shifted copies - the card failed mid-load
+ # with SW 6400 (live 2026-09-16).
+ blocks = [loadfile_tlv[off:off + block_size * 2]
+ for off in range(0, len(loadfile_tlv), block_size * 2)]
for i, block in enumerate(blocks):
p1 = 0x80 if i == len(blocks) - 1 else 0x00
apdus.append('80E8%02X%02X%02X%s00' % (p1, i % 256, len(block) // 2, block))
diff --git a/tests/test_ota_helpers.py b/tests/test_ota_helpers.py
index 2304477..d653d34 100644
--- a/tests/test_ota_helpers.py
+++ b/tests/test_ota_helpers.py
@@ -879,10 +879,20 @@ class CapApduSequenceTest(unittest.TestCase):
self.assertIn('06A00000010001' + '05A000000100' + '05A000000100' + '0100', seq[2])
def test_load_blocks_split_and_counter(self):
- from pysim_otaman_server.server import _cap_apdu_sequence
- data = 'AB' * 700 # 700 bytes -> C4 TLV 703 -> 3 x 240-byte blocks
+ from pysim_otaman_server.server import _cap_apdu_sequence, _ber_len as _ber_len_lower
+ data = ''.join('%02X' % (i % 256) for i in range(700))
seq = _cap_apdu_sequence('A00000010001', 'A000000100', data)
self.assertEqual(len(seq), 5) # INSTALL + 3 LOAD + INSTALL
self.assertEqual(seq[1][:8], '80E80000')
self.assertEqual(seq[2][:8], '80E80001')
self.assertEqual(seq[3][:8], '80E88002') # last block: P1=0x80
+ # The blocks are consecutive chunks and reassemble the load file TLV
+ # byte-for-byte (a shifted/overlapping split fails the card mid-load).
+ def payload(apdu):
+ lc = int(apdu[8:10], 16)
+ return apdu[10:10 + lc * 2]
+ joined = payload(seq[1]) + payload(seq[2]) + payload(seq[3])
+ self.assertTrue(joined.startswith('C482'))
+ expected = 'C4' + _ber_len_lower(700) + data # 700 = 0x2BC
+ self.assertEqual(joined.upper(), expected.upper())
+ self.assertEqual(int(seq[3][8:10], 16), len(expected) // 2 - 480)