fix: use the Network Name spare-bit count for PNN GSM-7 text (v2.7.21)
EF.PNN decoded 'Miranda@' for the live card's record 2: efPnnText ignored the number of spare bits in the coding octet (TS 24.008 10.5.3.5a bits 1-3) and unpacked floor(8n/7) septets, so the 7 zero pad bits became '@'. - gsm7Decode(octets, septets?) takes an exact character count and removes a final <CR> used as padding (TS 23.038 6.1.2.1.1). - efPnnText derives the count from the spare field ((8n - spare)/7 when 1-7; floor(8n/7) + CR drop for 000 = no information). - tests: the seven live EF.PNN records (Alfa, Miranda, Win, Mir Telekom, +7 Telekom, Fenix, MKS), a UCS2 record and the CR-padding drop. - docs/uicc/UICC_SPECS.md: CR-padding rule in 10.4 and a new 10.7 Network Name coding section (outside the repo).
This commit is contained in:
+21
-5
@@ -1414,7 +1414,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 = '2.7.20';
|
||||
const SIMPLE_VERSION = '2.7.21';
|
||||
document.getElementById('app-version').textContent = 'v' + SIMPLE_VERSION;
|
||||
|
||||
// ===== Tab switching =====
|
||||
@@ -3767,16 +3767,22 @@ function decodePrivileges(hex) {
|
||||
return parts.join(', ') || 'None';
|
||||
}
|
||||
|
||||
function gsm7Decode(octets) {
|
||||
// GSM 03.38 7-bit unpacking. `septets` (optional) is the exact character
|
||||
// count — the Network Name coding carries the number of spare bits, so the
|
||||
// zero padding must not decode as a trailing '@'. A final <CR> used as
|
||||
// padding (TS 23.038 6.1.2.1.1) is removed when the count is known.
|
||||
function gsm7Decode(octets, septets) {
|
||||
const bits = [];
|
||||
for (const b of octets) {
|
||||
for (let j = 0; j < 8; j++) bits.push((b >> j) & 1);
|
||||
}
|
||||
const max = Math.floor(bits.length / 7);
|
||||
const count = septets === undefined ? max : Math.min(septets, max);
|
||||
let out = '';
|
||||
let escape = false;
|
||||
for (let i = 0; i + 7 <= bits.length; i += 7) {
|
||||
for (let i = 0; i < count; i++) {
|
||||
let septet = 0;
|
||||
for (let j = 0; j < 7; j++) septet |= bits[i + j] << j;
|
||||
for (let j = 0; j < 7; j++) septet |= bits[i * 7 + j] << j;
|
||||
if (escape) {
|
||||
const ext = Object.entries(GSM7_EXT_MAP).find(([,v]) => v === septet);
|
||||
out += ext ? ext[0] : '?';
|
||||
@@ -3787,6 +3793,7 @@ function gsm7Decode(octets) {
|
||||
out += septet < GSM7_ALPHABET.length ? GSM7_ALPHABET[septet] : '?';
|
||||
}
|
||||
}
|
||||
if (septets !== undefined && out.endsWith('\r')) out = out.slice(0, -1);
|
||||
return out;
|
||||
}
|
||||
|
||||
@@ -12058,10 +12065,15 @@ function efPlmnAct(u16) {
|
||||
}
|
||||
|
||||
// TS 23.038 Network Name (DCS byte + text) from EF_PNN.
|
||||
// TS 24.008 10.5.3.5a Network Name (the EF.PNN 43/45 TLV value): the first
|
||||
// byte is the coding octet — bit 8 ext, bits 5-7 coding scheme (000 GSM
|
||||
// 7-bit, 001 UCS2), bit 4 Add CI, bits 1-3 the number of spare bits in the
|
||||
// last octet (the value is the count, 1-7; 000 = no information).
|
||||
function efPnnText(value) {
|
||||
if (value.length < 2) return '';
|
||||
const dcs = value[0];
|
||||
const coding = (dcs >> 4) & 0x07;
|
||||
const spare = dcs & 0x07;
|
||||
let data = efRstripFf(value.subarray(1));
|
||||
if (coding === 1) {
|
||||
for (let i = 0; i + 1 < data.length; i += 2) {
|
||||
@@ -12072,7 +12084,11 @@ function efPnnText(value) {
|
||||
if (coding === 0) {
|
||||
let allLow = data.length > 0;
|
||||
for (let i = 0; i < data.length; i++) if (data[i] >= 0x80) { allLow = false; break; }
|
||||
return allLow ? efGsm7Octets(data) : gsm7Decode(data);
|
||||
if (allLow) return efGsm7Octets(data);
|
||||
const nbits = data.length * 8;
|
||||
const septets = spare > 0 ? Math.floor((nbits - spare) / 7)
|
||||
: Math.floor(nbits / 7);
|
||||
return gsm7Decode(data, septets);
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
const CACHE = 'simple-v223';
|
||||
const CACHE = 'simple-v224';
|
||||
const URLS = [
|
||||
'index.html',
|
||||
'help.html',
|
||||
|
||||
@@ -73,6 +73,28 @@ test('efAnnexA decodes GSM7 octets, UCS2 and packed variants', () => {
|
||||
assert.strictEqual(efAnnexA(efBytes('FFFFFFFF')), '');
|
||||
});
|
||||
|
||||
test('efPnnText uses the spare-bit count (TS 24.008 10.5.3.5a)', () => {
|
||||
// Live card EF.PNN records: coding octet + packed GSM-7 text. The spare
|
||||
// bits in the coding octet give the exact character count, so the zero
|
||||
// padding must not decode as a trailing '@' (record 2 was 'Miranda@').
|
||||
const cases = [
|
||||
['8441b6390c', 'Alfa'],
|
||||
['87cdb43cec268701', 'Miranda'],
|
||||
['83d7b41b', 'Win'],
|
||||
['83cdb41c442db3cbeb771b', 'Mir Telekom'],
|
||||
['82ab1b885a6697d7ef36', '+7 Telekom'],
|
||||
['85c6b23b8d07', 'Fenix'],
|
||||
['83cde514', 'MKS'],
|
||||
];
|
||||
for (const [hex, text] of cases) {
|
||||
assert.strictEqual(efPnnText(efBytes(hex)), text, hex);
|
||||
}
|
||||
// UCS2 coding scheme (octet 3 bits 5-7 = 001)
|
||||
assert.strictEqual(efPnnText(efBytes('91004d006900720061006e00640061')), 'Miranda');
|
||||
// a final <CR> used as padding is removed (TS 23.038 6.1.2.1.1)
|
||||
assert.strictEqual(gsm7Decode(efBytes('cdb43cec26871b'), 8), 'Miranda');
|
||||
});
|
||||
|
||||
test('EF decoders match the pinned spec / pySim test vectors', () => {
|
||||
assert.deepStrictEqual(efDecIccid(efBytes('988812010000400310f0')), { iccid: '8988211000000430010' });
|
||||
assert.deepStrictEqual(efDecImsi(efBytes('082982608200002080')), { imsi: '228062800000208' });
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "pysim-simple-server"
|
||||
version = "2.7.20"
|
||||
version = "2.7.21"
|
||||
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.
|
||||
|
||||
@@ -26,7 +26,7 @@ from osmocom.tlv import BER_TLV_IE
|
||||
from osmocom.utils import rpad
|
||||
|
||||
|
||||
VERSION = '2.7.20'
|
||||
VERSION = '2.7.21'
|
||||
|
||||
MAX_ENVELOPE_SEGMENTS = 5 # max SMS segments for outgoing C-APDU in ENVELOPE
|
||||
|
||||
|
||||
Reference in New Issue
Block a user