scp81: continuation pages use GET STATUS P2=03 (next occurrence) (v2.1.15)
P2=02 means "get first or all occurrence(s)" (Table 11-34), so every continuation re-returned the first listing (the criterion's single match), pagination stopped after one extra page and the installed package AA1902BC225801 never showed up in the ELF registry. P2=03 = "get next occurrence(s)" is the correct value for the SW CAFE continuation. Tests updated with the new continuation bytes; findings documented; service worker v152
This commit is contained in:
@@ -245,6 +245,20 @@ was SW 6A80 (incorrect parameters in data field).
|
|||||||
Working INSTALL [for install] example (compact):
|
Working INSTALL [for install] example (compact):
|
||||||
`80E60C002E07AA1902BC22580108AA1902BC2258010108AA1902BC22580101010010C900EA0C800A00000F010000000000000000`
|
`80E60C002E07AA1902BC22580108AA1902BC2258010108AA1902BC22580101010010C900EA0C800A00000F010000000000000000`
|
||||||
|
|
||||||
|
## RESOLVED 2026-09-16f: SW CAFE pagination used the wrong P2 (02 instead of 03)
|
||||||
|
|
||||||
|
**Root cause:** the continuation GET STATUS used `P2=02`, which Table 11-34
|
||||||
|
(GP Card Spec 2.3.1) defines as "**Get first or all occurrence(s)**" - the
|
||||||
|
card returned the first listing again (with the search criterion's single
|
||||||
|
match), so every listing appeared to end after one extra page and newly
|
||||||
|
installed/registered entries were invisible (the installed package
|
||||||
|
`AA1902BC225801` was missing from the ELF registry). The correct value is
|
||||||
|
`P2=03` = "**Get next occurrence(s)**".
|
||||||
|
|
||||||
|
**Fix:** `_scp81_continuation` emits `80F2 <P1> 03 <Lc> 4F <len> <lastAID> 00`
|
||||||
|
and the earlier duplicate entry per page is gone (the criterion entry is no
|
||||||
|
longer re-returned).
|
||||||
|
|
||||||
## Next tests / work
|
## Next tests / work
|
||||||
|
|
||||||
1. **UI:** group the per-page R-APDUs under their logical command in the
|
1. **UI:** group the per-page R-APDUs under their logical command in the
|
||||||
|
|||||||
+1
-1
@@ -18,7 +18,7 @@
|
|||||||
<div class="max-w-7xl mx-auto px-6 py-2">
|
<div class="max-w-7xl mx-auto px-6 py-2">
|
||||||
|
|
||||||
<div class="flex items-center justify-between mb-3">
|
<div class="flex items-center justify-between mb-3">
|
||||||
<h1 class="text-2xl font-bold text-heading">OTAMan <span id="slogan" class="text-sm font-normal text-gray-500 dark:text-slate-400 ml-2" data-l10n="SIM OTA with a Human Face">SIM OTA with a Human Face</span> <span class="text-xs text-gray-400 dark:text-slate-500 ml-1">v2.1.14</span></h1>
|
<h1 class="text-2xl font-bold text-heading">OTAMan <span id="slogan" class="text-sm font-normal text-gray-500 dark:text-slate-400 ml-2" data-l10n="SIM OTA with a Human Face">SIM OTA with a Human Face</span> <span class="text-xs text-gray-400 dark:text-slate-500 ml-1">v2.1.15</span></h1>
|
||||||
<div class="flex items-center gap-4">
|
<div class="flex items-center gap-4">
|
||||||
<span id="state-indicator" class="flex items-center select-none" style="cursor:default" title="Connecting...">
|
<span id="state-indicator" class="flex items-center select-none" style="cursor:default" title="Connecting...">
|
||||||
<span id="state-indicator-dot" class="text-xs text-gray-400" title="Connecting...">●</span>
|
<span id="state-indicator-dot" class="text-xs text-gray-400" title="Connecting...">●</span>
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
const CACHE = 'otaman-v151';
|
const CACHE = 'otaman-v152';
|
||||||
const URLS = [
|
const URLS = [
|
||||||
'index.html',
|
'index.html',
|
||||||
'help.html',
|
'help.html',
|
||||||
|
|||||||
+1
-1
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "pysim-otaman-server"
|
name = "pysim-otaman-server"
|
||||||
version = "2.1.14"
|
version = "2.1.15"
|
||||||
description = "HTTP REST server wrapping pysim for the OTAMan PWA"
|
description = "HTTP REST server wrapping pysim for the OTAMan PWA"
|
||||||
requires-python = ">=3.8"
|
requires-python = ">=3.8"
|
||||||
# pysim is a git-only dependency installed explicitly by setup.bat/setup.sh.
|
# pysim is a git-only dependency installed explicitly by setup.bat/setup.sh.
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ from osmocom.construct import GsmOrUcs2Adapter
|
|||||||
from osmocom.tlv import BER_TLV_IE
|
from osmocom.tlv import BER_TLV_IE
|
||||||
|
|
||||||
|
|
||||||
VERSION = '2.1.14'
|
VERSION = '2.1.15'
|
||||||
|
|
||||||
MAX_ENVELOPE_SEGMENTS = 5 # max SMS segments for outgoing C-APDU in ENVELOPE
|
MAX_ENVELOPE_SEGMENTS = 5 # max SMS segments for outgoing C-APDU in ENVELOPE
|
||||||
|
|
||||||
@@ -1548,7 +1548,11 @@ def _scp81_continuation(apdu, rapdu):
|
|||||||
if not aid:
|
if not aid:
|
||||||
return None
|
return None
|
||||||
lc = 2 + len(aid)
|
lc = 2 + len(aid)
|
||||||
return '80F2%s02%02X4F%02X%s00' % (u[4:6], lc, len(aid), aid.hex().upper())
|
# P2=03 = "get next occurrence(s)" (Table 11-34); P2=02 ("first or all")
|
||||||
|
# made the card return the first listing again, so every continuation
|
||||||
|
# page repeated its search criterion and the scan stopped early - the
|
||||||
|
# newly installed package never appeared in the registry.
|
||||||
|
return '80F2%s03%02X4F%02X%s00' % (u[4:6], lc, len(aid), aid.hex().upper())
|
||||||
|
|
||||||
|
|
||||||
def _scp81_script_responder(method, target, headers, body):
|
def _scp81_script_responder(method, target, headers, body):
|
||||||
|
|||||||
+4
-4
@@ -679,7 +679,7 @@ class TargetedAppTest(unittest.TestCase):
|
|||||||
def test_continuation_builds_next_occurrence_apdu(self):
|
def test_continuation_builds_next_occurrence_apdu(self):
|
||||||
page = bytes.fromhex('E3114F08A0000000030000009F70010FC50100')
|
page = bytes.fromhex('E3114F08A0000000030000009F70010FC50100')
|
||||||
self.assertEqual(server._scp81_continuation('80F24002024F0000', page),
|
self.assertEqual(server._scp81_continuation('80F24002024F0000', page),
|
||||||
'80F240020A4F08A00000000300000000')
|
'80F240030A4F08A00000000300000000')
|
||||||
self.assertIsNone(server._scp81_continuation('80CAFF2100', page))
|
self.assertIsNone(server._scp81_continuation('80CAFF2100', page))
|
||||||
|
|
||||||
def test_cafe_page_auto_continuation(self):
|
def test_cafe_page_auto_continuation(self):
|
||||||
@@ -698,9 +698,9 @@ class TargetedAppTest(unittest.TestCase):
|
|||||||
'POST', '/api/scp81?req=1', {'x-admin-script-status': 'ok'}, body)
|
'POST', '/api/scp81?req=1', {'x-admin-script-status': 'ok'}, body)
|
||||||
# The continuation was appended and sent as the next command.
|
# The continuation was appended and sent as the next command.
|
||||||
self.assertEqual(server._SCP81_SCRIPT[1],
|
self.assertEqual(server._SCP81_SCRIPT[1],
|
||||||
'80F24002094F07A000000151535000')
|
'80F24003094F07A000000151535000')
|
||||||
self.assertEqual(status, 200)
|
self.assertEqual(status, 200)
|
||||||
self.assertIn(bytes.fromhex('80F24002094F07A000000151535000'), out)
|
self.assertIn(bytes.fromhex('80F24003094F07A000000151535000'), out)
|
||||||
finally:
|
finally:
|
||||||
server._SCP81_SCRIPT = list(server._SCP81_SCRIPTS['explore'])
|
server._SCP81_SCRIPT = list(server._SCP81_SCRIPTS['explore'])
|
||||||
server._SCP81_SCRIPT_SENT = 0
|
server._SCP81_SCRIPT_SENT = 0
|
||||||
@@ -712,7 +712,7 @@ class TargetedAppTest(unittest.TestCase):
|
|||||||
server._SCP81_SCRIPT = ['80F24002024F0000']
|
server._SCP81_SCRIPT = ['80F24002024F0000']
|
||||||
server._SCP81_SCRIPT_SENT = 1
|
server._SCP81_SCRIPT_SENT = 1
|
||||||
server._SCP81_SCRIPT_RESULTS = []
|
server._SCP81_SCRIPT_RESULTS = []
|
||||||
server._SCP81_SCRIPT_INSERTED = ['80F240020A4F08A00000000300000000']
|
server._SCP81_SCRIPT_INSERTED = ['80F240030A4F08A00000000300000000']
|
||||||
server._SCP81_PAGES = 1
|
server._SCP81_PAGES = 1
|
||||||
try:
|
try:
|
||||||
page = bytes.fromhex('E3114F08A0000000030000009F70010FC50100')
|
page = bytes.fromhex('E3114F08A0000000030000009F70010FC50100')
|
||||||
|
|||||||
Reference in New Issue
Block a user