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