v1.9.26: profiler (card file-structure & content verification)
New 'Profiler' sub-tab in card-reader view: - Named rulesets (profiles) persisted in localStorage; each has an ordered list of filesystem rules (type 'file', extensible to OTA/TAR checks later) - List page: New profile / Profile from card / Import profile + per-profile Edit / Check / Export / Delete - Editor page: inline rule fields (path, file type, size, record length, record count, contents with exact/mask/'?'-wildcard modes) - Results page: sequential rule checks with live progress and a pass/fail report (existence + FCI attributes + content match) - 'Profile from card' scans the equipped card; creates a rule only for files that exist (FCI present). A scan-options dialog lets the user skip contents of frequently-overwritten dynamic files (LOCI/PSLOCI/EPSLOCI/5GS3GPPLOCI/ Keys/KeysPS/SMS/Kc/KcGPRS/LOCIGPRS/CBMID/SMSS), checked by default - ADF-rooted paths use the AID; paths resolve via a new server _select_path Server: - _select_path() resolves MF/ADF-AID-rooted paths (pySim can't select ADF by AID) - /api/select and /api/read accept 'path'; /api/select returns file_size, record_len, num_of_rec Also fix: silent SELECT (P2=0x0C) no longer emits a trailing Le byte (chain builder) - matches ETSI TS 102 221 silent-select behavior. Tests: +profiler.test.js; frontend 117 pass, Python 61 pass. SW cache v37.
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
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);
|
||||
}
|
||||
|
||||
const FNS = ['profilerNormHex', 'profilerMatch', 'profilerValidateProfile'];
|
||||
let code = '';
|
||||
for (const f of FNS) code += extractFunc(html, f) + '\n';
|
||||
eval(code);
|
||||
|
||||
test('profilerNormHex uppercases and strips non-hex', () => {
|
||||
assert.strictEqual(profilerNormHex('aabbcc'), 'AABBCC');
|
||||
assert.strictEqual(profilerNormHex(' aa bb cc '), 'AABBCC');
|
||||
assert.strictEqual(profilerNormHex('08?91?'), '08?91?');
|
||||
assert.strictEqual(profilerNormHex(''), '');
|
||||
});
|
||||
|
||||
test('exact match requires full equality', () => {
|
||||
assert.ok(profilerMatch('exact', 'AABBCC', 'aabbcc'));
|
||||
assert.ok(!profilerMatch('exact', 'AABB', 'AABBCC'));
|
||||
assert.ok(!profilerMatch('exact', 'AABBCC', 'AABB'));
|
||||
assert.ok(!profilerMatch('exact', '', 'AABB'));
|
||||
});
|
||||
|
||||
test('mask with ? wildcard matches per nibble', () => {
|
||||
assert.ok(profilerMatch('mask', '08?91?', '081910'));
|
||||
assert.ok(profilerMatch('mask', '08?91?', '08A91B'));
|
||||
assert.ok(!profilerMatch('mask', '08?91?', '091910'));
|
||||
assert.ok(!profilerMatch('mask', '08?91?', '0891')); // length mismatch
|
||||
});
|
||||
|
||||
test('mask without ? is a prefix match', () => {
|
||||
assert.ok(profilerMatch('mask', '0891', '08911012345678'));
|
||||
assert.ok(profilerMatch('mask', '0891', '0891'));
|
||||
assert.ok(!profilerMatch('mask', '0891', '081910'));
|
||||
assert.ok(!profilerMatch('mask', '', '0891'));
|
||||
});
|
||||
|
||||
test('profile validation', () => {
|
||||
assert.strictEqual(profilerValidateProfile(null), 'Not an object');
|
||||
assert.strictEqual(profilerValidateProfile({ name: 'x' }), 'Missing rules array');
|
||||
assert.strictEqual(profilerValidateProfile({ name: 'x', rules: [{ type: 'ota' }] }), 'Unsupported rule type: ota');
|
||||
assert.strictEqual(profilerValidateProfile({ name: 'x', rules: [{ type: 'file' }] }), 'Rule missing path');
|
||||
assert.strictEqual(profilerValidateProfile({ name: 'x', rules: [{ type: 'file', path: 'MF/7F10/6F3A' }] }), null);
|
||||
});
|
||||
Reference in New Issue
Block a user