31715ba58a
Two selector bugs in the new TAR-probe view and one stale-list bug in the RAM view, all from the lazy population of the view-specific preset selects: - cardsAutoSelectByIccid() set sp-card-sel and ram-card-sel only, so the TAR probe view never got the equipped card's preset (and tarExecute() then ran with preset_id: '', i.e. without persisting the counters it consumed); - tarRender()'s keep-fallback was -1 instead of the remembered _ramCardIdx, so the select stayed on "— Select card —" even when the auto-selection had remembered the preset; the TAR pill switch now also calls tarRender(); - the keyset selectors showed a stale "N — not defined in the preset": spKeysetSync() runs during the auto-selection, when ram/tar-card-sel have no options yet, so those lists were built with no preset; ramRender() now rebuilds them (spKeysetSync) once its preset is known - tarRender() already did. Tests: the auto-select covers tar-card-sel; a DOM-driven regression test (ramRender with a remembered preset and a two-keyset preset must show value="2" selected and no "not defined" entry); source guards for ramRender's sync, tarRender's keep and the TAR pill switch. sw cache simple-v339.
178 lines
7.6 KiB
JavaScript
178 lines
7.6 KiB
JavaScript
const { test } = require('node:test');
|
|
const assert = require('node:assert');
|
|
const fs = require('node:fs');
|
|
const path = require('node:path');
|
|
|
|
const html = fs.readFileSync(path.join(__dirname, '..', 'index.html'), 'utf8');
|
|
|
|
function extractFunc(src, name) {
|
|
const re = new RegExp('function\\s+' + name + '\\s*\\([^)]*\\)\\s*\\{');
|
|
const m = re.exec(src);
|
|
if (!m) throw new Error('function ' + name + ' not found');
|
|
let i = m.index + m[0].length - 1;
|
|
let depth = 0;
|
|
for (; i < src.length; i++) {
|
|
if (src[i] === '{') depth++;
|
|
else if (src[i] === '}') {
|
|
depth--;
|
|
if (depth === 0) break;
|
|
}
|
|
}
|
|
return src.slice(m.index, i + 1);
|
|
}
|
|
|
|
let code = '';
|
|
for (const fn of ['spKeysetKvnOf', 'spKeysetList', 'spKeysetFor', 'spKeysetCheck',
|
|
'spKeysetOptionsHtml', 'spCounterTracked', 'spPresetIdx', 'ramPresetIdx',
|
|
'tarPresetIdx', 'spKeysetSync', 'ramRender', 'esc', 'escHtml']) {
|
|
code += extractFunc(html, fn) + '\n';
|
|
}
|
|
code += 'function t(s){return s;}\n';
|
|
eval(code);
|
|
|
|
const PRESET = {
|
|
name: 'Card A',
|
|
keysets: [
|
|
{ kic: '15', kid: '15', kicKey: 'AA', kidKey: 'BB', cntr: '0000000001' },
|
|
{ kic: '29', kid: '29', kicKey: 'CC', kidKey: 'DD', cntr: '0000000005' },
|
|
],
|
|
};
|
|
|
|
test('spKeysetKvnOf reads the key version from the high nibble', () => {
|
|
assert.strictEqual(spKeysetKvnOf('15'), 1);
|
|
assert.strictEqual(spKeysetKvnOf('29'), 2);
|
|
assert.strictEqual(spKeysetKvnOf('3A'), 3);
|
|
assert.strictEqual(spKeysetKvnOf('05'), 0);
|
|
assert.strictEqual(spKeysetKvnOf('ff'), 15);
|
|
// not a byte: no key version
|
|
assert.strictEqual(spKeysetKvnOf(''), null);
|
|
assert.strictEqual(spKeysetKvnOf('5'), null);
|
|
assert.strictEqual(spKeysetKvnOf('ZZ'), null);
|
|
assert.strictEqual(spKeysetKvnOf(null), null);
|
|
});
|
|
|
|
test('spKeysetList converts a v3.8.0 flat preset into one keyset', () => {
|
|
assert.deepStrictEqual(spKeysetList({ kic: '15', kid: '15', kicKey: 'A', kidKey: 'B',
|
|
cntr: '0000000001' }), [{ kic: '15', kid: '15', kicKey: 'A', kidKey: 'B',
|
|
cntr: '0000000001' }]);
|
|
assert.deepStrictEqual(spKeysetList(PRESET).length, 2);
|
|
assert.deepStrictEqual(spKeysetList(null), []);
|
|
assert.deepStrictEqual(spKeysetList({}), []);
|
|
});
|
|
|
|
test('spKeysetFor finds a keyset by key version', () => {
|
|
assert.strictEqual(spKeysetFor(PRESET, 1).cntr, '0000000001');
|
|
assert.strictEqual(spKeysetFor(PRESET, 2).cntr, '0000000005');
|
|
assert.strictEqual(spKeysetFor(PRESET, 3), null);
|
|
assert.strictEqual(spKeysetFor(null, 1), null);
|
|
});
|
|
|
|
test('spKeysetCheck enforces the key-version rules', () => {
|
|
// a defined key version passes
|
|
assert.strictEqual(spKeysetCheck(PRESET, '15', '15'), '');
|
|
assert.strictEqual(spKeysetCheck(PRESET, '29', '29'), '');
|
|
// '00'/'00' means no security: no keyset needed (TS 102 225 A.2)
|
|
assert.strictEqual(spKeysetCheck(PRESET, '00', '00'), '');
|
|
// ... and one zero byte with a defined version passes too
|
|
assert.strictEqual(spKeysetCheck(PRESET, '15', '00'), '');
|
|
// KIc/KID versions must agree when both are non-zero (A.2)
|
|
assert.match(spKeysetCheck(PRESET, '15', '25'), /same key version/);
|
|
// the key version must be defined in the preset
|
|
assert.match(spKeysetCheck(PRESET, '35', '35'), /3 .*not defined/);
|
|
// one zero byte: the other byte's version applies (keyset 2 is defined)
|
|
assert.strictEqual(spKeysetCheck(PRESET, '00', '25'), '');
|
|
assert.match(spKeysetCheck(PRESET, '00', '35'), /3 .*not defined/);
|
|
// without a preset only the byte rules apply (hand-entered keys)
|
|
assert.strictEqual(spKeysetCheck(null, '35', '35'), '');
|
|
assert.match(spKeysetCheck(null, '15', '25'), /15 \/ 25/);
|
|
// malformed bytes are named
|
|
assert.match(spKeysetCheck(PRESET, '5', '15'), /KIc/);
|
|
assert.match(spKeysetCheck(PRESET, '15', 'ZZ'), /KID/);
|
|
});
|
|
|
|
test('spKeysetOptionsHtml lists the keysets and marks an undefined version', () => {
|
|
const html1 = spKeysetOptionsHtml(PRESET, '15', '15');
|
|
assert.match(html1, /value="1" selected/);
|
|
assert.match(html1, /value="2"/);
|
|
assert.ok(html1.indexOf('0000000005') > 0, 'the counters are shown');
|
|
// a version the preset does not define is offered but marked
|
|
const html2 = spKeysetOptionsHtml(PRESET, '35', '35');
|
|
assert.match(html2, /value="3" selected/);
|
|
assert.match(html2, /not defined in the preset/);
|
|
// a preset without keysets: the form's version shows as undefined
|
|
const html3 = spKeysetOptionsHtml({ keysets: [] }, '15', '15');
|
|
assert.match(html3, /not defined in the preset/);
|
|
// no key version in the form and no keysets at all
|
|
assert.match(spKeysetOptionsHtml({}, '', ''), /no keyset/);
|
|
});
|
|
|
|
test('the Cards form wires the keyset editor', () => {
|
|
assert.match(html, /id="cards-keysets"/);
|
|
assert.match(html, /onclick="cardsKeysetAdd\(\)"/);
|
|
assert.match(html, /oninput="cardsKeysetKvnUpdate\(/);
|
|
assert.match(html, /data-l10n="Add keyset"/);
|
|
// the keyset rows are built with the KIc/KID/key/counter inputs
|
|
assert.match(html, /cards-ks-' \+ i \+ '-kic-key/);
|
|
assert.match(html, /cards-ks-' \+ i \+ '-cntr/);
|
|
// the selectors exist in both SCP80 views
|
|
assert.match(html, /id="sp-keyset-sel" onchange="spKeysetChanged\(\)"/);
|
|
assert.match(html, /id="ram-keyset-sel" onchange="ramKeysetChanged\(\)"/);
|
|
// the packet actions run the guard
|
|
assert.ok(html.includes('const ksErr = spKeysetGuard();'));
|
|
assert.ok(html.includes('const ksErr = ramKeysetGuard();'));
|
|
});
|
|
|
|
test('spCounterTracked follows SPI1 b5b4 (TS 102 225 5.1.1)', () => {
|
|
// b5b4 = 00: "present, ignored, never updated" - no counter to track
|
|
for (const spi1 of ['00', '01', '06', '']) {
|
|
assert.strictEqual(spCounterTracked(spi1), false, spi1);
|
|
}
|
|
for (const spi1 of ['08', '10', '16', '18', '1E']) {
|
|
assert.strictEqual(spCounterTracked(spi1), true, spi1);
|
|
}
|
|
assert.strictEqual(spCounterTracked('zz'), false);
|
|
});
|
|
|
|
test('ramRender rebuilds the keyset selector from the remembered preset', () => {
|
|
// regression: the auto-selection runs before the RAM select has options, so
|
|
// its keyset list was built with no preset and stayed on "not defined"
|
|
const els = {
|
|
'ram-card-sel': { value: '', innerHTML: '', appendChild() {} },
|
|
'ram-keyset-sel': { value: '', innerHTML: '' },
|
|
'sp-keyset-sel': { value: '', innerHTML: '' },
|
|
'tar-keyset-sel': { value: '', innerHTML: '' },
|
|
'sp-kic-hex': { value: '25' },
|
|
'sp-kid-hex': { value: '25' },
|
|
};
|
|
globalThis.document = {
|
|
getElementById: id => els[id] || null,
|
|
createElement: () => ({ value: '', textContent: '' }),
|
|
};
|
|
globalThis.cards = [{ name: 'EP', keysets: [
|
|
{ kic: '15', kid: '15', kicKey: 'AA', kidKey: 'BB', cntr: '0000000001' },
|
|
{ kic: '25', kid: '25', kicKey: 'CC', kidKey: 'DD', cntr: '0000000184' }] }];
|
|
globalThis._ramCardIdx = 0;
|
|
globalThis.ramOpChanged = () => {};
|
|
globalThis.ramExpandedDetailsInit = () => {};
|
|
ramRender();
|
|
assert.strictEqual(els['ram-card-sel'].value, '0',
|
|
'the preset select keeps the remembered card');
|
|
assert.match(els['ram-keyset-sel'].innerHTML, /value="2" selected/);
|
|
assert.ok(!els['ram-keyset-sel'].innerHTML.includes('not defined'),
|
|
'the keyset selector must not show a stale "not defined" entry');
|
|
});
|
|
|
|
test('the RAM and TAR views rebuild their keyset lists when their preset is known', () => {
|
|
// ramRender() syncs the keyset selectors once the preset is set ...
|
|
const ram = html.slice(html.indexOf('function ramRender()'),
|
|
html.indexOf('function ramRender()') + 1400);
|
|
assert.ok(ram.includes('spKeysetSync();'), 'ramRender must rebuild the keyset lists');
|
|
// ... tarRender() already does, and keeps the remembered selection
|
|
const tar = html.slice(html.indexOf('function tarRender()'),
|
|
html.indexOf('function tarRender()') + 1200);
|
|
assert.ok(tar.includes('spKeysetSync();'));
|
|
assert.ok(tar.includes(': _ramCardIdx;'), 'tarRender keeps the remembered preset');
|
|
// opening the TAR pill populates both its selectors
|
|
assert.ok(html.includes("if (name === 'tar') { tarRender(); tarProbeRender(); }"));
|
|
});
|