Compare commits
5 Commits
9d7fa11871
...
9879675df2
| Author | SHA1 | Date | |
|---|---|---|---|
| 9879675df2 | |||
| dd18a24062 | |||
| 5c279147c1 | |||
| 330a1f0070 | |||
| 1daced1e18 |
+40
-12
@@ -721,7 +721,7 @@
|
||||
<button onclick="profilerBackToList()" class="px-2.5 py-1 text-sm rounded border border-gray-300 dark:border-slate-600 hover:bg-gray-200 dark:hover:bg-slate-700 text-gray-700 dark:text-slate-300" data-l10n="Back to list">Back to list</button>
|
||||
<span id="profiler-results-title" class="text-sm font-semibold"></span>
|
||||
</div>
|
||||
<div id="profiler-progress" class="mb-2 text-xs font-mono text-gray-600 dark:text-slate-400"></div>
|
||||
<div id="profiler-progress" class="mb-2 text-base font-mono text-gray-600 dark:text-slate-400"></div>
|
||||
<div id="profiler-summary" class="mb-2 text-sm"></div>
|
||||
<div id="profiler-report"></div>
|
||||
</div>
|
||||
@@ -6672,6 +6672,13 @@ const PROFILER_IGNORE_FILES = [
|
||||
{ fid: '6F43', name: 'EF.SMSS' },
|
||||
];
|
||||
|
||||
// Files where contents are captured as a mask matching only the first 4 bytes
|
||||
// (the rest are '?' wildcards). Keyed by FID; display name is the pySim name.
|
||||
const PROFILER_MASK_PREFIX4_FIDS = {
|
||||
'6F07': 'EF.IMSI',
|
||||
'2FE2': 'EF.ICCID',
|
||||
};
|
||||
|
||||
function profilerLoad() {
|
||||
try {
|
||||
const d = localStorage.getItem('otaman_profiles');
|
||||
@@ -6709,6 +6716,21 @@ function profilerMatch(mode, expected, actual) {
|
||||
return e === a;
|
||||
}
|
||||
|
||||
// Like profilerMatch, but compares only the overlapping (shorter) portion.
|
||||
function profilerMatchMin(mode, expected, actual) {
|
||||
const e = profilerNormHex(expected);
|
||||
const a = profilerNormHex(actual);
|
||||
const n = Math.min(e.length, a.length);
|
||||
return profilerMatch(mode, e.slice(0, n), a.slice(0, n));
|
||||
}
|
||||
|
||||
// Keep the first 4 bytes (8 hex chars) exact and mask the rest with '?'.
|
||||
function profilerMaskPrefix4(hex) {
|
||||
const h = profilerNormHex(hex);
|
||||
if (h.length <= 8) return h;
|
||||
return h.slice(0, 8) + '?'.repeat(h.length - 8);
|
||||
}
|
||||
|
||||
function profilerValidateProfile(obj) {
|
||||
if (!obj || typeof obj !== 'object') return 'Not an object';
|
||||
if (!obj.name) return 'Missing name';
|
||||
@@ -6888,7 +6910,11 @@ async function profilerBuildFileRule(path, c, ignoreFids) {
|
||||
records: rd.records.map(r => ({ num: r.num, data: r.data })),
|
||||
};
|
||||
} else if (rd.data) {
|
||||
rule.content = { mode: 'exact', kind: 'transparent', expected: rd.data };
|
||||
if (PROFILER_MASK_PREFIX4_FIDS[fid]) {
|
||||
rule.content = { mode: 'mask', kind: 'transparent', expected: profilerMaskPrefix4(rd.data) };
|
||||
} else {
|
||||
rule.content = { mode: 'exact', kind: 'transparent', expected: rd.data };
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {}
|
||||
@@ -7096,6 +7122,7 @@ async function profilerCheck(i) {
|
||||
|
||||
async function profilerRunRule(r) {
|
||||
const res = { path: r.path, name: null, status: 'pass', checks: [] };
|
||||
let sizeMismatch = false;
|
||||
let sel;
|
||||
try {
|
||||
sel = await pysimFetch('/api/select', { path: r.path });
|
||||
@@ -7118,17 +7145,17 @@ async function profilerRunRule(r) {
|
||||
}
|
||||
if (r.fileSize !== null && r.fileSize !== undefined) {
|
||||
const ok = sel.file_size === r.fileSize;
|
||||
if (!ok) res.status = 'fail';
|
||||
if (!ok) { res.status = 'fail'; sizeMismatch = true; }
|
||||
res.checks.push({ label: 'fileSize', expected: r.fileSize, actual: sel.file_size, ok: ok });
|
||||
}
|
||||
if (r.recordLen !== null && r.recordLen !== undefined) {
|
||||
const ok = sel.record_len === r.recordLen;
|
||||
if (!ok) res.status = 'fail';
|
||||
if (!ok) { res.status = 'fail'; sizeMismatch = true; }
|
||||
res.checks.push({ label: 'recordLen', expected: r.recordLen, actual: sel.record_len, ok: ok });
|
||||
}
|
||||
if (r.numRecords !== null && r.numRecords !== undefined) {
|
||||
const ok = sel.num_of_rec === r.numRecords;
|
||||
if (!ok) res.status = 'fail';
|
||||
if (!ok) { res.status = 'fail'; sizeMismatch = true; }
|
||||
res.checks.push({ label: 'numRecords', expected: r.numRecords, actual: sel.num_of_rec, ok: ok });
|
||||
}
|
||||
if (r.content) {
|
||||
@@ -7146,18 +7173,19 @@ async function profilerRunRule(r) {
|
||||
} else if (r.content.kind === 'record') {
|
||||
const exp = r.content.records || [];
|
||||
const act = rd.records || [];
|
||||
const n = Math.min(exp.length, act.length);
|
||||
if (exp.length !== act.length) {
|
||||
res.status = 'fail';
|
||||
res.checks.push({ label: 'content.records', expected: exp.length + ' records', actual: act.length + ' records', ok: false });
|
||||
} else {
|
||||
for (let j = 0; j < exp.length; j++) {
|
||||
const ok = exp[j].num === act[j].num && profilerMatch(r.content.mode, exp[j].data, act[j].data);
|
||||
if (!ok) res.status = 'fail';
|
||||
res.checks.push({ label: 'content.rec' + exp[j].num, expected: exp[j].data, actual: act[j].data, ok: ok });
|
||||
}
|
||||
}
|
||||
for (let j = 0; j < n; j++) {
|
||||
const dataOk = sizeMismatch ? profilerMatchMin(r.content.mode, exp[j].data, act[j].data) : profilerMatch(r.content.mode, exp[j].data, act[j].data);
|
||||
const ok = exp[j].num === act[j].num && dataOk;
|
||||
if (!ok) res.status = 'fail';
|
||||
res.checks.push({ label: 'content.rec' + exp[j].num, expected: exp[j].data, actual: act[j].data, ok: ok });
|
||||
}
|
||||
} else {
|
||||
const ok = profilerMatch(r.content.mode, r.content.expected, rd.data);
|
||||
const ok = sizeMismatch ? profilerMatchMin(r.content.mode, r.content.expected, rd.data) : profilerMatch(r.content.mode, r.content.expected, rd.data);
|
||||
if (!ok) res.status = 'fail';
|
||||
res.checks.push({ label: 'content', expected: r.content.expected, actual: rd.data, ok: ok });
|
||||
}
|
||||
|
||||
+74
-2
@@ -1325,8 +1325,8 @@ video {
|
||||
}
|
||||
|
||||
.text-xs {
|
||||
font-size: 0.75rem;
|
||||
line-height: 1rem;
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.25rem;
|
||||
}
|
||||
|
||||
.font-bold {
|
||||
@@ -1685,4 +1685,76 @@ video {
|
||||
.dark\:hover\:text-slate-300:hover:is(.dark *) {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(203 213 225 / var(--tw-text-opacity, 1));
|
||||
}
|
||||
|
||||
/* ===== Theme contrast adjustments ===== */
|
||||
|
||||
/* Light theme: darken softer (muted) text one step for higher contrast */
|
||||
.text-gray-400 {
|
||||
color: rgb(107 114 128 / var(--tw-text-opacity, 1));
|
||||
}
|
||||
.text-gray-500 {
|
||||
color: rgb(75 85 99 / var(--tw-text-opacity, 1));
|
||||
}
|
||||
.text-gray-600 {
|
||||
color: rgb(55 65 81 / var(--tw-text-opacity, 1));
|
||||
}
|
||||
|
||||
/* Dark theme: lighten softer (muted) text one step for higher contrast */
|
||||
.dark\:text-slate-500:is(.dark *) {
|
||||
color: rgb(148 163 184 / var(--tw-text-opacity, 1));
|
||||
}
|
||||
.dark\:text-slate-400:is(.dark *) {
|
||||
color: rgb(203 213 225 / var(--tw-text-opacity, 1));
|
||||
}
|
||||
|
||||
/* Dark theme: red text is too dark on dark backgrounds — lighten it */
|
||||
.dark :where(.text-red-500) {
|
||||
color: rgb(248 113 113 / var(--tw-text-opacity, 1));
|
||||
}
|
||||
.dark :where(.text-red-600) {
|
||||
color: rgb(248 113 113 / var(--tw-text-opacity, 1));
|
||||
}
|
||||
.dark :where(.text-red-700) {
|
||||
color: rgb(239 68 68 / var(--tw-text-opacity, 1));
|
||||
}
|
||||
|
||||
/* Light theme: darken light gray shades (backgrounds & borders) one step */
|
||||
.bg-gray-50 {
|
||||
background-color: rgb(243 244 246 / var(--tw-bg-opacity, 1));
|
||||
}
|
||||
.bg-gray-100 {
|
||||
background-color: rgb(229 231 235 / var(--tw-bg-opacity, 1));
|
||||
}
|
||||
.bg-gray-200 {
|
||||
background-color: rgb(209 213 219 / var(--tw-bg-opacity, 1));
|
||||
}
|
||||
.border-gray-100 {
|
||||
border-color: rgb(229 231 235 / var(--tw-border-opacity, 1));
|
||||
}
|
||||
.border-gray-200 {
|
||||
border-color: rgb(209 213 219 / var(--tw-border-opacity, 1));
|
||||
}
|
||||
.border-gray-300 {
|
||||
border-color: rgb(156 163 175 / var(--tw-border-opacity, 1));
|
||||
}
|
||||
.text-gray-300 {
|
||||
color: rgb(156 163 175 / var(--tw-text-opacity, 1));
|
||||
}
|
||||
.hover\:bg-gray-100:hover {
|
||||
background-color: rgb(229 231 235 / var(--tw-bg-opacity, 1));
|
||||
}
|
||||
.hover\:bg-gray-200:hover {
|
||||
background-color: rgb(209 213 219 / var(--tw-bg-opacity, 1));
|
||||
}
|
||||
.hover\:bg-gray-300:hover {
|
||||
background-color: rgb(156 163 175 / var(--tw-bg-opacity, 1));
|
||||
}
|
||||
|
||||
/* Dark theme: lighten gray fonts */
|
||||
.dark\:text-gray-400:is(.dark *) {
|
||||
color: rgb(156 163 175 / var(--tw-text-opacity, 1));
|
||||
}
|
||||
.dark\:text-gray-600:is(.dark *) {
|
||||
color: rgb(156 163 175 / var(--tw-text-opacity, 1));
|
||||
}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
const CACHE = 'otaman-v39';
|
||||
const CACHE = 'otaman-v44';
|
||||
const URLS = [
|
||||
'index.html',
|
||||
'help.html',
|
||||
|
||||
@@ -21,7 +21,7 @@ function extractFunc(src, name) {
|
||||
return src.slice(m.index, i + 1);
|
||||
}
|
||||
|
||||
const FNS = ['profilerNormHex', 'profilerMatch', 'profilerValidateProfile'];
|
||||
const FNS = ['profilerNormHex', 'profilerMatch', 'profilerMatchMin', 'profilerMaskPrefix4', 'profilerValidateProfile'];
|
||||
let code = '';
|
||||
for (const f of FNS) code += extractFunc(html, f) + '\n';
|
||||
eval(code);
|
||||
@@ -54,6 +54,26 @@ test('mask without ? is a prefix match', () => {
|
||||
assert.ok(!profilerMatch('mask', '', '0891'));
|
||||
});
|
||||
|
||||
test('profilerMatchMin compares the shorter overlapping portion', () => {
|
||||
// shorter expected vs longer actual -> compare prefix
|
||||
assert.ok(profilerMatchMin('exact', '1122FFFF', '1122FFFFFF'));
|
||||
assert.ok(!profilerMatchMin('exact', '1122FFFF', '1122FFAA'));
|
||||
// shorter actual vs longer expected -> compare prefix
|
||||
assert.ok(profilerMatchMin('exact', '1122FFFFFF', '1122FFFF'));
|
||||
// equal lengths behave like profilerMatch
|
||||
assert.ok(profilerMatchMin('exact', '1122FFFF', '1122FFFF'));
|
||||
assert.ok(!profilerMatchMin('exact', '1122FFFF', '1122FFEE'));
|
||||
// mask with ? over a shorter actual
|
||||
assert.ok(profilerMatchMin('mask', '08?91?AA', '081910'));
|
||||
assert.ok(!profilerMatchMin('mask', '08?91?AA', '091110'));
|
||||
});
|
||||
|
||||
test('profilerMaskPrefix4 keeps first 4 bytes and masks the rest', () => {
|
||||
assert.strictEqual(profilerMaskPrefix4('082905911234567890'), '08290591??????????');
|
||||
assert.strictEqual(profilerMaskPrefix4('08290591'), '08290591');
|
||||
assert.strictEqual(profilerMaskPrefix4('1234'), '1234');
|
||||
});
|
||||
|
||||
test('profile validation', () => {
|
||||
assert.strictEqual(profilerValidateProfile(null), 'Not an object');
|
||||
assert.strictEqual(profilerValidateProfile({ name: 'x' }), 'Missing rules array');
|
||||
|
||||
Reference in New Issue
Block a user