diff --git a/frontend/index.html b/frontend/index.html index 19ad60c..6b06952 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -3127,8 +3127,8 @@ const PARSE_INS = { function parseBerLen(hex, i) { const b = parseInt(hex.substr(i, 2), 16); if (b < 128) return {len: b, consumed: 2}; - const n = parseInt(hex.substr(i + 2, b * 2), 16); - return {len: n, consumed: 2 + b * 2}; + const n = b & 0x7F; + return {len: parseInt(hex.substr(i + 2, n * 2), 16), consumed: 2 + n * 2}; } function parseTlvList(hex) { diff --git a/frontend/sw.js b/frontend/sw.js index a490a1c..52a02ec 100644 --- a/frontend/sw.js +++ b/frontend/sw.js @@ -1,4 +1,4 @@ -const CACHE = 'otaman-v73'; +const CACHE = 'otaman-v74'; const URLS = [ 'index.html', 'help.html', diff --git a/frontend/tests/apdu_parse.test.js b/frontend/tests/apdu_parse.test.js index adcb44b..c4cc60b 100644 --- a/frontend/tests/apdu_parse.test.js +++ b/frontend/tests/apdu_parse.test.js @@ -165,6 +165,39 @@ test('parseTlvList handles BER-TLVs', () => { assert.strictEqual(tlvs[1].tag, '82'); }); +test('parseBerLen handles short and long form lengths (ISO 7816-4 5.2)', () => { + assert.deepStrictEqual(parseBerLen('1200', 0), { len: 18, consumed: 2 }); + assert.deepStrictEqual(parseBerLen('8112', 0), { len: 18, consumed: 4 }); + assert.deepStrictEqual(parseBerLen('820100', 0), { len: 256, consumed: 6 }); + assert.deepStrictEqual(parseBerLen('820182' + '0102030405060708', 0), { len: 386, consumed: 6 }); +}); + +test('parseTlvList parses long-form lengths (81/82)', () => { + const short = parseTlvList('6212' + '8202412183026F078A010580020009880110'); + assert.strictEqual(short.length, 1); + assert.strictEqual(short[0].tag, '62'); + assert.strictEqual(short[0].length, 18); + + // same content with a long-form outer length + const long81 = parseTlvList('628112' + '8202412183026F078A010580020009880110'); + assert.strictEqual(long81.length, 1); + assert.strictEqual(long81[0].length, 18); + assert.strictEqual(long81[0].value, short[0].value); + + // 2-byte length form + const long82 = parseTlvList('62820004' + '80020009'); + assert.strictEqual(long82.length, 1); + assert.strictEqual(long82[0].length, 4); + assert.strictEqual(long82[0].value, '80020009'); + + // inner TLV with a long-form length (A5 81 05 85 03 00 00 00) + const inner = parseTlvList('A581058503000000'); + assert.strictEqual(inner.length, 1); + assert.strictEqual(inner[0].tag, 'A5'); + assert.strictEqual(inner[0].length, 5); + assert.strictEqual(inner[0].value, '8503000000'); +}); + test('gsm7Decode unpacks "HI" from C824', () => { const bytes = new Uint8Array([0xC8, 0x24]); assert.strictEqual(gsm7Decode(bytes), 'HI'); diff --git a/frontend/tests/profiler.test.js b/frontend/tests/profiler.test.js index 8239701..b053ff3 100644 --- a/frontend/tests/profiler.test.js +++ b/frontend/tests/profiler.test.js @@ -641,6 +641,35 @@ test('fcpDecode unwraps an FCI 6F template and rejects malformed input', () => { assert.strictEqual(fcpDecode('6213' + FCP_TRANSPARENT.slice(4)).ok, false); // wrong outer length }); +test('fcpDecode accepts long-form BER lengths (81/82) — editor preview regression', () => { + // 62 12 <18B> vs 62 81 12 + const long81 = '628112' + FCP_TRANSPARENT.slice(4); + const d1 = fcpDecode(long81); + assert.strictEqual(d1.ok, true); + assert.strictEqual(d1.items.find(it => it.key === '80').decoded, '9 bytes'); + + // 2-byte length form + const d2 = fcpDecode('62820004' + '80020009'); + assert.strictEqual(d2.ok, true); + assert.strictEqual(d2.items.find(it => it.key === '80').decoded, '9 bytes'); + + // nested A5 with a long-form length (A5 81 05 …) + const d3 = fcpDecode('62138202412183026F078A0105A581058503000000'); + assert.strictEqual(d3.ok, true); + assert.strictEqual(d3.items.find(it => it.key === 'A5/85').decoded, '0 bytes'); + + // FCP content >= 128 bytes uses a long-form outer length + let big = ''; + for (let i = 0; i < 13; i++) big += '8808' + '0102030405060708'; // 13 x 10B = 130B + const d4 = fcpDecode('628182' + big); + assert.strictEqual(d4.ok, true); + assert.strictEqual(d4.items.filter(it => it.key === '88').length, 13); + + global.t = s => s; + assert.ok(profilerFciPreviewItems(long81).includes('File size: ')); + delete global.t; +}); + test('fcpDiffHtml highlights differing FCP parameters', () => { global.t = s => s; const same = fcpDiffHtml(FCP_TRANSPARENT, FCP_TRANSPARENT);