diff --git a/frontend/index.html b/frontend/index.html index e387dbd..1bdd1bd 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -1711,7 +1711,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.6.22'; +const SIMPLE_VERSION = '3.6.23'; document.getElementById('app-version').textContent = 'v' + SIMPLE_VERSION; // ===== Tab switching ===== @@ -4593,7 +4593,7 @@ function packToSp(sourceId) { function spCntrAdjust(delta) { const el = document.getElementById('sp-cntr'); let val = parseInt(el.value.replace(/[^0-9a-fA-F]/g, ''), 16) || 0; - val = Math.max(0, val + delta); + val = Math.max(0, val + delta) % 0x10000000000; // wrap the 5-byte counter el.value = val.toString(16).padStart(10, '0').toUpperCase(); } function spInvalidate() { @@ -8015,8 +8015,10 @@ async function pysimApdu() { function spNextCntr(value) { // The OTA counter must strictly increase for every secured packet sent // (replay protection): increment the 5-byte counter, carries included. - const v = (parseInt((value || '').replace(/[^0-9a-fA-F]/g, ''), 16) || 0) >>> 0; - return ((v + 1) >>> 0).toString(16).toUpperCase().padStart(10, '0').slice(-10); + // 5 bytes = 40 bits - a 32-bit `>>> 0` dropped the top byte of high + // counters (`10000AAAC8` -> `00000AAAC9`, v3.6.23). + const v = (parseInt((value || '').replace(/[^0-9a-fA-F]/g, ''), 16) || 0) % 0x10000000000; + return ((v + 1) % 0x10000000000).toString(16).toUpperCase().padStart(10, '0'); } function spCntrSyncPreset() { diff --git a/frontend/sw.js b/frontend/sw.js index a6ddbaa..5450908 100644 --- a/frontend/sw.js +++ b/frontend/sw.js @@ -1,4 +1,4 @@ -const CACHE = 'simple-v295'; +const CACHE = 'simple-v296'; const URLS = [ 'index.html', 'help.html', diff --git a/frontend/tests/sp.test.js b/frontend/tests/sp.test.js index 16a0e2f..bf1b743 100644 --- a/frontend/tests/sp.test.js +++ b/frontend/tests/sp.test.js @@ -253,6 +253,14 @@ test('spNextCntr increments with carry', () => { assert.strictEqual(spNextCntr('0000ABCDEF'), '0000ABCDF0'); }); +test('spNextCntr keeps the full 5 bytes (no 32-bit truncation)', () => { + assert.strictEqual(spNextCntr('10000AAAC8'), '10000AAAC9'); // the reported case + assert.strictEqual(spNextCntr('1000 0AAAC8'), '10000AAAC9'); // separators stripped + assert.strictEqual(spNextCntr('FFFFFFFFF7'), 'FFFFFFFFF8'); + assert.strictEqual(spNextCntr('FFFFFFFFFF'), '0000000000'); // 40-bit wrap + assert.strictEqual(spNextCntr('10000000000'), '0000000001'); // out of range clamps +}); + test('spNextCntr tolerates lower case and separators', () => { assert.strictEqual(spNextCntr('00000000 0a'), '000000000B'); assert.strictEqual(spNextCntr(''), '0000000001'); diff --git a/pyproject.toml b/pyproject.toml index 803d337..df1e296 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "pysim-simple-server" -version = "3.6.22" +version = "3.6.23" 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 8dff77c..1dde36e 100644 --- a/pysim_simple_server/server.py +++ b/pysim_simple_server/server.py @@ -31,7 +31,7 @@ from osmocom.tlv import BER_TLV_IE from osmocom.utils import rpad -VERSION = '3.6.22' +VERSION = '3.6.23' MAX_ENVELOPE_SEGMENTS = 5 # max SMS segments for outgoing C-APDU in ENVELOPE