frontend/tests/ (otaman-style function extraction from index.html):
- apdu_parse.test.js: PARSE_INS registry, parseOneApdu (SELECT by FID,
READ BINARY Le-form, UPDATE BINARY, GET DATA, lowercase input),
parseCompactApdus (concatenated implied-CLA stream, graceful
unrecognized-byte degradation).
- scp80_decrypt.test.js: decryptSecured known-answer tests with
node-crypto generated ciphertexts — 3DES-CBC 2/3-key (real CNTR=200
revealed), AES-CBC 128, single DES, wrong-key padding guard, key
length validation, unsupported-algorithm rejection.
- helpers.js: brace-matched function/const/section extraction from
the single-file frontend; package.json ("test": "node --test").
Fix AES-CBC decryption path found by the new tests: SubtleCrypto CBC
decrypt always validates PKCS#7, which SCP80 PCNTR-style padding
violates. Workaround: append a synthetic block C* = E(C_last XOR
padBlock) so the validator sees a full 0x10 padding block, decrypt,
then discard the synthetic plaintext. Validated: CNTR=200 case
decrypts correctly.
Version 1.20.1, SW cache simtrace-analyser-v29.
58 lines
1.8 KiB
JavaScript
58 lines
1.8 KiB
JavaScript
// Shared helpers to extract functions/constants from the single-file
|
|
// frontend (index.html) so they can be unit-tested with node:test.
|
|
const fs = require('node:fs');
|
|
const path = require('node:path');
|
|
|
|
const INDEX_HTML = path.join(__dirname, '..', 'index.html');
|
|
|
|
function loadSrc() {
|
|
return fs.readFileSync(INDEX_HTML, 'utf8');
|
|
}
|
|
|
|
// Extract a `function name(...) { ... }` by brace matching (includes a
|
|
// preceding `async` modifier when present).
|
|
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 start = m.index;
|
|
if (src.slice(Math.max(0, start - 6), start) === 'async ') start -= 6;
|
|
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(start, i + 1);
|
|
}
|
|
|
|
// Extract a `const NAME = { ... };` block by brace matching.
|
|
function extractConst(src, name) {
|
|
const re = new RegExp('const\\s+' + name + '\\s*=\\s*\\{');
|
|
const m = re.exec(src);
|
|
if (!m) throw new Error('const ' + 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) + ';';
|
|
}
|
|
|
|
// Extract a `// marker ... ` section up to the next marker comment.
|
|
function extractSection(src, startMarker, endMarker) {
|
|
const i = src.indexOf(startMarker);
|
|
if (i < 0) throw new Error('section not found: ' + startMarker);
|
|
const j = endMarker ? src.indexOf(endMarker, i) : -1;
|
|
return src.slice(i, j > i ? j : undefined);
|
|
}
|
|
|
|
module.exports = { loadSrc, extractFunc, extractConst, extractSection, INDEX_HTML };
|