profiler: apply 'ignore contents' to real card files (KcGPRS FID fix + name fallback)
The 'Profile from card' ignore checkboxes matched only by FID, but the list had EF.KcGPRS at 4F52 (TS 31.102 DF.GSM-ACCESS) while the live card exposes it at 6F52 (TS 51.011 DF.GSM, verified in TS 51.011 v4.15.0 10.3.32 and the DF.GSM allocation table). The miss made profilerBuildFileRule capture full contents (Exact) for a file the user had checked to ignore. - Correct EF.KcGPRS FID to 6F52 (spec-verified) - Match ignores by FID OR by pySim name: each checkbox now carries data-ignore-name, profilerScanStart builds an ignoreNames set, and profilerBuildFileRule checks both. Covers FID variants (6F52 vs 4F52) and future constant typos; ignoreNames is optional for back-compat. - 6 new tests: ignore-list FID/name sanity (incl. KcGPRS=6F52), duplicates, ignored-by-FID, ignored-by-name-only regression case, non-ignored control, back-compat when ignoreNames is omitted. SW cache v50 -> v51.
This commit is contained in:
+14
-9
@@ -6946,7 +6946,7 @@ const PROFILER_IGNORE_FILES = [
|
||||
{ fid: '6F09', name: 'EF.KeysPS' },
|
||||
{ fid: '6F3C', name: 'EF.SMS' },
|
||||
{ fid: '6F20', name: 'EF.Kc' },
|
||||
{ fid: '4F52', name: 'EF.KcGPRS' },
|
||||
{ fid: '6F52', name: 'EF.KcGPRS' },
|
||||
{ fid: '6F53', name: 'EF.LOCIGPRS' },
|
||||
{ fid: '6F48', name: 'EF.CBMID' },
|
||||
{ fid: '6F43', name: 'EF.SMSS' },
|
||||
@@ -7086,7 +7086,7 @@ function profilerFromCard() {
|
||||
let html = '';
|
||||
for (const f of PROFILER_IGNORE_FILES) {
|
||||
html += '<label class="flex items-center gap-2 text-sm">' +
|
||||
'<input type="checkbox" data-ignore-fid="' + f.fid + '" checked>' +
|
||||
'<input type="checkbox" data-ignore-fid="' + f.fid + '" data-ignore-name="' + f.name + '" checked>' +
|
||||
'<span>' + esc(t('Ignore contents of')) + ' ' + esc(f.name) + '</span></label>';
|
||||
}
|
||||
ignore.innerHTML = html;
|
||||
@@ -7107,14 +7107,18 @@ async function profilerScanStart() {
|
||||
return;
|
||||
}
|
||||
const ignoreFids = new Set();
|
||||
const ignoreNames = new Set();
|
||||
document.querySelectorAll('#profiler-scan-ignore input[data-ignore-fid]').forEach(cb => {
|
||||
if (cb.checked) ignoreFids.add(cb.getAttribute('data-ignore-fid').toUpperCase());
|
||||
if (cb.checked) {
|
||||
ignoreFids.add(cb.getAttribute('data-ignore-fid').toUpperCase());
|
||||
ignoreNames.add((cb.getAttribute('data-ignore-name') || '').toUpperCase());
|
||||
}
|
||||
});
|
||||
const btn = document.getElementById('profiler-scan-btn');
|
||||
btn.disabled = true;
|
||||
btn.textContent = t('Scanning...');
|
||||
try {
|
||||
const rules = await profilerScanCard(ignoreFids);
|
||||
const rules = await profilerScanCard(ignoreFids, ignoreNames);
|
||||
const profile = { id: profilerNewId(), name: name, created: new Date().toISOString(), rules: rules };
|
||||
profiles.push(profile);
|
||||
profilerSave();
|
||||
@@ -7130,7 +7134,7 @@ async function profilerScanStart() {
|
||||
}
|
||||
|
||||
// Recursively walk the card filesystem and build rules for files that exist.
|
||||
async function profilerScanCard(ignoreFids) {
|
||||
async function profilerScanCard(ignoreFids, ignoreNames) {
|
||||
const rules = [];
|
||||
const seen = new Set();
|
||||
|
||||
@@ -7157,7 +7161,7 @@ async function profilerScanCard(ignoreFids) {
|
||||
const childPath = dir.pathPrefix.concat(c.fid ? c.fid.toUpperCase() : c.name).join('/');
|
||||
if (seen.has(childPath)) continue;
|
||||
seen.add(childPath);
|
||||
const rule = await profilerBuildFileRule(childPath, c, ignoreFids);
|
||||
const rule = await profilerBuildFileRule(childPath, c, ignoreFids, ignoreNames);
|
||||
if (rule) rules.push(rule);
|
||||
}
|
||||
}
|
||||
@@ -7176,13 +7180,13 @@ async function profilerScanCard(ignoreFids) {
|
||||
const path = segs.join('/');
|
||||
if (seen.has(path)) continue;
|
||||
seen.add(path);
|
||||
const rule = await profilerBuildFileRule(path, { fid: cf.fid, name: cf.name }, ignoreFids);
|
||||
const rule = await profilerBuildFileRule(path, { fid: cf.fid, name: cf.name }, ignoreFids, ignoreNames);
|
||||
if (rule) rules.push(rule);
|
||||
}
|
||||
return rules;
|
||||
}
|
||||
|
||||
async function profilerBuildFileRule(path, c, ignoreFids) {
|
||||
async function profilerBuildFileRule(path, c, ignoreFids, ignoreNames) {
|
||||
let sel;
|
||||
try {
|
||||
sel = await pysimFetch('/api/select', { path: path });
|
||||
@@ -7199,7 +7203,8 @@ async function profilerBuildFileRule(path, c, ignoreFids) {
|
||||
content: null,
|
||||
};
|
||||
const fid = (c.fid || sel.fid || '').toUpperCase();
|
||||
if (ignoreFids.has(fid)) return rule;
|
||||
const name = (c.name || sel.name || '').toUpperCase();
|
||||
if (ignoreFids.has(fid) || (ignoreNames && ignoreNames.has(name))) return rule;
|
||||
try {
|
||||
const rd = await pysimFetch('/api/read', { path: path, mode: 'raw' });
|
||||
if (rd && rd.success) {
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
const CACHE = 'otaman-v50';
|
||||
const CACHE = 'otaman-v51';
|
||||
const URLS = [
|
||||
'index.html',
|
||||
'help.html',
|
||||
|
||||
@@ -5,7 +5,7 @@ const path = require('node:path');
|
||||
|
||||
const html = fs.readFileSync(path.join(__dirname, '..', 'index.html'), 'utf8');
|
||||
|
||||
function extractFunc(src, name) {
|
||||
function extractFunc(src, name, asyncFn) {
|
||||
const re = new RegExp('function\\s+' + name + '\\s*\\([^)]*\\)\\s*\\{');
|
||||
const m = re.exec(src);
|
||||
if (!m) throw new Error('function ' + name + ' not found');
|
||||
@@ -18,12 +18,14 @@ function extractFunc(src, name) {
|
||||
if (depth === 0) break;
|
||||
}
|
||||
}
|
||||
return src.slice(m.index, i + 1);
|
||||
return (asyncFn ? 'async ' : '') + src.slice(m.index, i + 1);
|
||||
}
|
||||
|
||||
const FNS = ['profilerNormHex', 'profilerMatch', 'profilerMatchMin', 'profilerMaskPrefix4', 'profilerFileFields', 'profilerContentKindForFileType', 'profilerEmptyRecordContent', 'profilerValidateProfile'];
|
||||
let code = '';
|
||||
for (const f of FNS) code += extractFunc(html, f) + '\n';
|
||||
code += extractFunc(html, 'profilerBuildFileRule', true) + '\n';
|
||||
code += html.match(/const PROFILER_MASK_PREFIX4_FIDS = \{[\s\S]*?\n\};/)[0] + '\n';
|
||||
eval(code);
|
||||
|
||||
test('profilerNormHex uppercases and strips non-hex', () => {
|
||||
@@ -107,3 +109,85 @@ test('profile validation', () => {
|
||||
assert.strictEqual(profilerValidateProfile({ name: 'x', rules: [{ type: 'file' }] }), 'Rule missing path');
|
||||
assert.strictEqual(profilerValidateProfile({ name: 'x', rules: [{ type: 'file', path: 'MF/7F10/6F3A' }] }), null);
|
||||
});
|
||||
|
||||
// --- "Profile from card" ignore list ---
|
||||
|
||||
function parseIgnoreFiles() {
|
||||
const raw = html.match(/const PROFILER_IGNORE_FILES = \[([\s\S]*?)\n\];/)[1];
|
||||
return [...raw.matchAll(/\{ fid: '([^']*)', name: '([^']*)' \}/g)].map(m => ({ fid: m[1], name: m[2] }));
|
||||
}
|
||||
|
||||
test('ignore list FIDs are well-formed and KcGPRS uses the TS 51.011 FID (6F52)', () => {
|
||||
const files = parseIgnoreFiles();
|
||||
assert.ok(files.length >= 12);
|
||||
for (const f of files) {
|
||||
assert.match(f.fid, /^[0-9A-F]{4}$/, f.name + ' has a malformed FID');
|
||||
assert.ok(f.name.startsWith('EF.'), f.fid + ' has a malformed name');
|
||||
}
|
||||
assert.strictEqual(files.find(f => f.name === 'EF.KcGPRS').fid, '6F52');
|
||||
});
|
||||
|
||||
test('ignore list has no duplicate FIDs or names', () => {
|
||||
const files = parseIgnoreFiles();
|
||||
assert.strictEqual(new Set(files.map(f => f.fid)).size, files.length);
|
||||
assert.strictEqual(new Set(files.map(f => f.name)).size, files.length);
|
||||
});
|
||||
|
||||
function mockFetch(handlers) {
|
||||
const calls = [];
|
||||
global.pysimFetch = async (path, body) => {
|
||||
calls.push(path);
|
||||
if (handlers[path]) return handlers[path](body);
|
||||
throw new Error('unexpected fetch: ' + path);
|
||||
};
|
||||
return calls;
|
||||
}
|
||||
|
||||
const KCGPRS_SELECT = {
|
||||
name: 'EF.KcGPRS', fid: '6F52', file_type: 'transparent',
|
||||
file_size: 9, record_len: null, num_of_rec: null, exists: true,
|
||||
};
|
||||
|
||||
test('profilerBuildFileRule skips contents for an ignored FID', async () => {
|
||||
const calls = mockFetch({ '/api/select': () => KCGPRS_SELECT });
|
||||
const rule = await profilerBuildFileRule('MF/7F20/6F52',
|
||||
{ fid: '6f52', name: 'EF.KcGPRS' }, new Set(['6F52']), new Set(['EF.KCGPRS']));
|
||||
assert.strictEqual(rule.content, null);
|
||||
assert.strictEqual(rule.path, 'MF/7F20/6F52');
|
||||
assert.strictEqual(rule.fileType, 'transparent');
|
||||
assert.strictEqual(rule.fileSize, 9);
|
||||
assert.ok(!calls.includes('/api/read'), 'contents must not be read for ignored files');
|
||||
});
|
||||
|
||||
test('profilerBuildFileRule skips contents when the name matches but the FID differs', async () => {
|
||||
// Regression: a KcGPRS copy at the TS 31.102 DF.GSM-ACCESS FID (4F52) must
|
||||
// still be ignored when the user checked EF.KcGPRS in the ignore list.
|
||||
const calls = mockFetch({
|
||||
'/api/select': () => ({ name: 'EF.KcGPRS', fid: '4F52', file_type: 'transparent', file_size: 9, record_len: null, num_of_rec: null, exists: true }),
|
||||
});
|
||||
const rule = await profilerBuildFileRule('MF/5F3B/4F52',
|
||||
{ fid: '4f52', name: 'EF.KcGPRS' }, new Set(['6F52']), new Set(['EF.KCGPRS']));
|
||||
assert.strictEqual(rule.content, null);
|
||||
assert.ok(!calls.includes('/api/read'), 'contents must not be read for name-matched files');
|
||||
});
|
||||
|
||||
test('profilerBuildFileRule captures contents for non-ignored files', async () => {
|
||||
const calls = mockFetch({
|
||||
'/api/select': () => ({ name: 'EF.ADN', fid: '6F3A', file_type: 'transparent', file_size: 4, record_len: null, num_of_rec: null, exists: true }),
|
||||
'/api/read': () => ({ success: true, data: 'AABBCCDD' }),
|
||||
});
|
||||
const rule = await profilerBuildFileRule('MF/7F20/6F3A',
|
||||
{ fid: '6f3a', name: 'EF.ADN' }, new Set(['6F52']), new Set(['EF.KCGPRS']));
|
||||
assert.ok(rule.content);
|
||||
assert.strictEqual(rule.content.mode, 'exact');
|
||||
assert.strictEqual(rule.content.expected, 'AABBCCDD');
|
||||
assert.ok(calls.includes('/api/read'));
|
||||
});
|
||||
|
||||
test('profilerBuildFileRule still ignores when ignoreNames is omitted (back-compat)', async () => {
|
||||
const calls = mockFetch({ '/api/select': () => KCGPRS_SELECT });
|
||||
const rule = await profilerBuildFileRule('MF/7F20/6F52',
|
||||
{ fid: '6F52', name: 'EF.KcGPRS' }, new Set(['6F52']));
|
||||
assert.strictEqual(rule.content, null);
|
||||
assert.ok(!calls.includes('/api/read'));
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user