feat: toolkit block emphasised + auto CA/EA from the CAP (v3.6.32)
The RAM install form's toolkit parameters are the make-or-break field for toolkit applets, and CA vs EA is easy to get wrong (the earlier live 6A80s). - "SIM/UICC Toolkit parameters" is now an accented bordered fieldset (blue border/legend, always visible in the cap / install / make-selectable modes) with the checkbox in the legend and the fields inside. - ramCapToolkitMode(imports) maps the CAP's linked libraries to the install parameter family: sim.* (AID tree A0000000090003FFFFFFFF89...) -> SIM Toolkit (Tag CA), uicc.* / uicc.usim.* (A0000000090005FFFFFFFF89..., A0000000871005FFFFFFFF89...) -> UICC Toolkit (Tag EA) - family-based, so access-only applets get the right field too. Applied when the block is enabled and when the CAP is swapped while it is on, with a "detected from the CAP" hint under the select; no CAP keeps the manual choice. - Rebuilt style.css for the new accent classes; tests pin the fieldset and the family mapping. 663 frontend / 504 Python green; version 3.6.32; sw simple-v305.
This commit is contained in:
+53
-7
@@ -751,13 +751,13 @@
|
|||||||
<label class="flex items-center gap-1"><input type="checkbox" class="rc-priv-b3" value="80" onchange="updateRcPriv()"> Receipt Generation</label>
|
<label class="flex items-center gap-1"><input type="checkbox" class="rc-priv-b3" value="80" onchange="updateRcPriv()"> Receipt Generation</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="rc-toolkit-row" class="mb-3">
|
<fieldset id="rc-toolkit-row" class="mb-3 mt-2 rounded border border-blue-300 dark:border-blue-800 bg-blue-50/50 dark:bg-blue-900/10 p-3">
|
||||||
<div class="mb-2">
|
<legend class="px-1 text-sm font-medium text-blue-800 dark:text-blue-300">
|
||||||
<label class="flex items-center gap-2 text-sm font-medium text-gray-700 dark:text-slate-300">
|
<label class="flex items-center gap-2">
|
||||||
<input id="rc-toolkit-enable" type="checkbox" class="rounded border-gray-300 dark:border-slate-600 dark:bg-slate-800" onchange="updateRcToolkit()">
|
<input id="rc-toolkit-enable" type="checkbox" class="rounded border-gray-300 dark:border-slate-600 dark:bg-slate-800" onchange="updateRcToolkit()">
|
||||||
SIM/UICC Toolkit parameters
|
<span data-l10n="SIM/UICC Toolkit parameters">SIM/UICC Toolkit parameters</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</legend>
|
||||||
<div id="rc-toolkit-body" class="hidden">
|
<div id="rc-toolkit-body" class="hidden">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="block mb-1 text-sm font-medium text-gray-700 dark:text-slate-300" data-l10n="Toolkit mode">Toolkit mode</label>
|
<label class="block mb-1 text-sm font-medium text-gray-700 dark:text-slate-300" data-l10n="Toolkit mode">Toolkit mode</label>
|
||||||
@@ -765,6 +765,7 @@
|
|||||||
<option value="ca">SIM Toolkit (Tag CA)</option>
|
<option value="ca">SIM Toolkit (Tag CA)</option>
|
||||||
<option value="ea">UICC Toolkit (Tag EA)</option>
|
<option value="ea">UICC Toolkit (Tag EA)</option>
|
||||||
</select>
|
</select>
|
||||||
|
<div id="rc-tk-mode-hint" class="hidden mt-1 text-xs text-blue-700 dark:text-blue-300"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-2 gap-x-3 gap-y-2 text-sm">
|
<div class="grid grid-cols-2 gap-x-3 gap-y-2 text-sm">
|
||||||
<div>
|
<div>
|
||||||
@@ -845,7 +846,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</fieldset>
|
||||||
<div class="grid grid-cols-2 gap-x-3 gap-y-2 text-sm mt-2">
|
<div class="grid grid-cols-2 gap-x-3 gap-y-2 text-sm mt-2">
|
||||||
<div>
|
<div>
|
||||||
<label class="block mb-1 text-xs font-medium text-gray-500 dark:text-slate-400" data-l10n="Applet-specific parameters (C9, hex, optional)">Applet-specific parameters (C9, hex, optional)</label>
|
<label class="block mb-1 text-xs font-medium text-gray-500 dark:text-slate-400" data-l10n="Applet-specific parameters (C9, hex, optional)">Applet-specific parameters (C9, hex, optional)</label>
|
||||||
@@ -1713,7 +1714,7 @@
|
|||||||
// ===== Version =====
|
// ===== Version =====
|
||||||
// Single source of truth for the PWA version: shown in the header and used
|
// Single source of truth for the PWA version: shown in the header and used
|
||||||
// by the server version check in pysimConnect().
|
// by the server version check in pysimConnect().
|
||||||
const SIMPLE_VERSION = '3.6.31';
|
const SIMPLE_VERSION = '3.6.32';
|
||||||
document.getElementById('app-version').textContent = 'v' + SIMPLE_VERSION;
|
document.getElementById('app-version').textContent = 'v' + SIMPLE_VERSION;
|
||||||
|
|
||||||
// ===== Tab switching =====
|
// ===== Tab switching =====
|
||||||
@@ -2545,9 +2546,52 @@ function updateRcPriv() {
|
|||||||
document.getElementById('rc-priv').value = hex;
|
document.getElementById('rc-priv').value = hex;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The install-parameter family a CAP belongs to, from its Import component:
|
||||||
|
// sim.* (sim.toolkit / sim.access, AID tree A0000000090003FFFFFFFF89...) uses
|
||||||
|
// the CA field (SIM Toolkit); uicc.* / uicc.usim.* (uicc.toolkit /
|
||||||
|
// uicc.access / ..., AID trees A0000000090005FFFFFFFF89... and
|
||||||
|
// A0000000871005FFFFFFFF89...) uses the EA field (UICC Toolkit). Returns
|
||||||
|
// 'ca', 'ea' or null when the CAP links against neither.
|
||||||
|
function ramCapToolkitMode(imports) {
|
||||||
|
const aids = (imports || []).map(im => (im.aid || '').toUpperCase());
|
||||||
|
if (aids.some(a => a.startsWith('A0000000090005FFFFFFFF89') ||
|
||||||
|
a.startsWith('A0000000871005FFFFFFFF89'))) return 'ea';
|
||||||
|
if (aids.some(a => a.startsWith('A0000000090003FFFFFFFF89'))) return 'ca';
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pick the toolkit mode of the analysed RAM CAP (when its libraries decide it)
|
||||||
|
// and, when the block is in use, apply it: never make the user guess CA vs EA.
|
||||||
|
function ramApplyCapToolkitMode() {
|
||||||
|
const st = _capAnalysis.ram;
|
||||||
|
const mode = ramCapToolkitMode((st.memory && st.memory.imports) || []);
|
||||||
|
st.tk_mode = mode;
|
||||||
|
const sel = document.getElementById('rc-tk-mode');
|
||||||
|
const hint = document.getElementById('rc-tk-mode-hint');
|
||||||
|
if (hint) {
|
||||||
|
if (mode) {
|
||||||
|
hint.textContent = t('detected from the CAP') + ': ' + sel.options[sel.selectedIndex].text;
|
||||||
|
hint.classList.remove('hidden');
|
||||||
|
} else {
|
||||||
|
hint.classList.add('hidden');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const enable = document.getElementById('rc-toolkit-enable');
|
||||||
|
if (mode && enable && enable.checked) {
|
||||||
|
sel.value = mode;
|
||||||
|
if (hint) hint.textContent = t('detected from the CAP') + ': ' +
|
||||||
|
(mode === 'ea' ? 'UICC Toolkit (Tag EA)' : 'SIM Toolkit (Tag CA)');
|
||||||
|
updateRcTkMode();
|
||||||
|
updateStkParamsHex();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function updateRcToolkit() {
|
function updateRcToolkit() {
|
||||||
const checked = document.getElementById('rc-toolkit-enable').checked;
|
const checked = document.getElementById('rc-toolkit-enable').checked;
|
||||||
document.getElementById('rc-toolkit-body').classList.toggle('hidden', !checked);
|
document.getElementById('rc-toolkit-body').classList.toggle('hidden', !checked);
|
||||||
|
// A CAP linked against sim.* / uicc.* decides the toolkit mode
|
||||||
|
const st = _capAnalysis.ram;
|
||||||
|
if (checked && st && st.tk_mode) document.getElementById('rc-tk-mode').value = st.tk_mode;
|
||||||
updateRcTkMode();
|
updateRcTkMode();
|
||||||
updateStkParamsHex();
|
updateStkParamsHex();
|
||||||
}
|
}
|
||||||
@@ -8454,6 +8498,7 @@ async function capAnalyzeFile(file, kind) {
|
|||||||
if (resp && resp.ok) {
|
if (resp && resp.ok) {
|
||||||
st.status = 'ok';
|
st.status = 'ok';
|
||||||
st.memory = resp.memory || {};
|
st.memory = resp.memory || {};
|
||||||
|
if (kind === 'ram') ramApplyCapToolkitMode();
|
||||||
} else {
|
} else {
|
||||||
st.status = 'error';
|
st.status = 'error';
|
||||||
st.error = (resp && resp.error) || t('Analysis failed');
|
st.error = (resp && resp.error) || t('Analysis failed');
|
||||||
@@ -16782,6 +16827,7 @@ const LANG_RU = {
|
|||||||
'Byte 1': 'Байт 1',
|
'Byte 1': 'Байт 1',
|
||||||
'Byte 2': 'Байт 2',
|
'Byte 2': 'Байт 2',
|
||||||
'SIM/UICC Toolkit parameters': 'Параметры SIM/UICC Toolkit',
|
'SIM/UICC Toolkit parameters': 'Параметры SIM/UICC Toolkit',
|
||||||
|
'detected from the CAP': 'определено по CAP',
|
||||||
'Toolkit mode': 'Режим Toolkit',
|
'Toolkit mode': 'Режим Toolkit',
|
||||||
'Max services': 'Максимум сервисов',
|
'Max services': 'Максимум сервисов',
|
||||||
'Priority': 'Приоритет',
|
'Priority': 'Приоритет',
|
||||||
|
|||||||
@@ -1296,6 +1296,11 @@ video {
|
|||||||
border-color: rgb(251 191 36 / var(--tw-border-opacity, 1));
|
border-color: rgb(251 191 36 / var(--tw-border-opacity, 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.border-blue-300 {
|
||||||
|
--tw-border-opacity: 1;
|
||||||
|
border-color: rgb(147 197 253 / var(--tw-border-opacity, 1));
|
||||||
|
}
|
||||||
|
|
||||||
.border-blue-400 {
|
.border-blue-400 {
|
||||||
--tw-border-opacity: 1;
|
--tw-border-opacity: 1;
|
||||||
border-color: rgb(96 165 250 / var(--tw-border-opacity, 1));
|
border-color: rgb(96 165 250 / var(--tw-border-opacity, 1));
|
||||||
@@ -1359,6 +1364,10 @@ video {
|
|||||||
background-color: rgb(219 234 254 / var(--tw-bg-opacity, 1));
|
background-color: rgb(219 234 254 / var(--tw-bg-opacity, 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.bg-blue-50\/50 {
|
||||||
|
background-color: rgb(239 246 255 / 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
.bg-blue-600 {
|
.bg-blue-600 {
|
||||||
--tw-bg-opacity: 1;
|
--tw-bg-opacity: 1;
|
||||||
background-color: rgb(37 99 235 / var(--tw-bg-opacity, 1));
|
background-color: rgb(37 99 235 / var(--tw-bg-opacity, 1));
|
||||||
@@ -1693,6 +1702,11 @@ video {
|
|||||||
color: rgb(29 78 216 / var(--tw-text-opacity, 1));
|
color: rgb(29 78 216 / var(--tw-text-opacity, 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.text-blue-800 {
|
||||||
|
--tw-text-opacity: 1;
|
||||||
|
color: rgb(30 64 175 / var(--tw-text-opacity, 1));
|
||||||
|
}
|
||||||
|
|
||||||
.text-emerald-600 {
|
.text-emerald-600 {
|
||||||
--tw-text-opacity: 1;
|
--tw-text-opacity: 1;
|
||||||
color: rgb(5 150 105 / var(--tw-text-opacity, 1));
|
color: rgb(5 150 105 / var(--tw-text-opacity, 1));
|
||||||
@@ -1954,6 +1968,11 @@ video {
|
|||||||
border-color: rgb(217 119 6 / var(--tw-border-opacity, 1));
|
border-color: rgb(217 119 6 / var(--tw-border-opacity, 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dark\:border-blue-800:is(.dark *) {
|
||||||
|
--tw-border-opacity: 1;
|
||||||
|
border-color: rgb(30 64 175 / var(--tw-border-opacity, 1));
|
||||||
|
}
|
||||||
|
|
||||||
.dark\:border-red-800:is(.dark *) {
|
.dark\:border-red-800:is(.dark *) {
|
||||||
--tw-border-opacity: 1;
|
--tw-border-opacity: 1;
|
||||||
border-color: rgb(153 27 27 / var(--tw-border-opacity, 1));
|
border-color: rgb(153 27 27 / var(--tw-border-opacity, 1));
|
||||||
@@ -1987,6 +2006,10 @@ video {
|
|||||||
background-color: rgb(59 130 246 / var(--tw-bg-opacity, 1));
|
background-color: rgb(59 130 246 / var(--tw-bg-opacity, 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dark\:bg-blue-900\/10:is(.dark *) {
|
||||||
|
background-color: rgb(30 58 138 / 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
.dark\:bg-blue-900\/40:is(.dark *) {
|
.dark\:bg-blue-900\/40:is(.dark *) {
|
||||||
background-color: rgb(30 58 138 / 0.4);
|
background-color: rgb(30 58 138 / 0.4);
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
const CACHE = 'simple-v304';
|
const CACHE = 'simple-v305';
|
||||||
const URLS = [
|
const URLS = [
|
||||||
'index.html',
|
'index.html',
|
||||||
'help.html',
|
'help.html',
|
||||||
|
|||||||
@@ -41,6 +41,9 @@ eval(extractFunc(html, 'jcAidFamily'));
|
|||||||
eval(extractFunc(html, 'capMemBytes'));
|
eval(extractFunc(html, 'capMemBytes'));
|
||||||
eval(extractFunc(html, 'capMemHtml'));
|
eval(extractFunc(html, 'capMemHtml'));
|
||||||
eval(extractFunc(html, 'capAnalyzeFile', true));
|
eval(extractFunc(html, 'capAnalyzeFile', true));
|
||||||
|
// The RAM CAP analysis also applies the detected toolkit mode (DOM-bound; its
|
||||||
|
// own logic is covered in ram.test.js) - stub it for these state tests.
|
||||||
|
globalThis.ramApplyCapToolkitMode = () => {};
|
||||||
|
|
||||||
globalThis.esc = s => s;
|
globalThis.esc = s => s;
|
||||||
globalThis.t = s => s;
|
globalThis.t = s => s;
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ function extractFunc(src, name) {
|
|||||||
const FNS = ['berLenStr', 'buildApdu', 'escHtml', 'esc', 'chainInit', 'chainRamBuildRowHex', 'ramFmtLifecycle', 'ramFmtPrivileges', 'ramRenderExploreHtml', 'ramStepLine', 'ramGetStatusApdu', 'ramDeleteApdu',
|
const FNS = ['berLenStr', 'buildApdu', 'escHtml', 'esc', 'chainInit', 'chainRamBuildRowHex', 'ramFmtLifecycle', 'ramFmtPrivileges', 'ramRenderExploreHtml', 'ramStepLine', 'ramGetStatusApdu', 'ramDeleteApdu',
|
||||||
'stkParamsBuild', 'ramRemoteSwOk', 'spPorAccepted', 'ramIncrementCntr', 'ramDeleteFromExplorer', 'ramListingSpi2', 'ramRemoveFromExplorer', 'ramHasInstance', 'ramExpandedQueryApdu',
|
'stkParamsBuild', 'ramRemoteSwOk', 'spPorAccepted', 'ramIncrementCntr', 'ramDeleteFromExplorer', 'ramListingSpi2', 'ramRemoveFromExplorer', 'ramHasInstance', 'ramExpandedQueryApdu',
|
||||||
'_parseRawElfEntry', '_parseRawAppEntry', 'ramParseElfStatus', 'ramParseAppStatus', 'parseTLV', '_parseE3Entry',
|
'_parseRawElfEntry', '_parseRawAppEntry', 'ramParseElfStatus', 'ramParseAppStatus', 'parseTLV', '_parseE3Entry',
|
||||||
'ramCardIdxAfterRemove', 'ramClearResults', 'ramHideProgress', 'ramOpChanged', 'ramRender', 'ramApplyCard', 'ramExecute', 'decodePrivileges', 'ramActionBtn',
|
'ramCardIdxAfterRemove', 'ramClearResults', 'ramHideProgress', 'ramOpChanged', 'ramRender', 'ramApplyCard', 'ramExecute', 'decodePrivileges', 'ramActionBtn', 'ramCapToolkitMode',
|
||||||
'jcAidNorm', 'jcAidName', 'jcAidSuffix', 'jcAidHtml'];
|
'jcAidNorm', 'jcAidName', 'jcAidSuffix', 'jcAidHtml'];
|
||||||
let code = '';
|
let code = '';
|
||||||
for (const f of FNS) {
|
for (const f of FNS) {
|
||||||
@@ -274,6 +274,33 @@ test('ramFmtPrivileges decodes the GP privilege bytes (live ISD values)', () =>
|
|||||||
delete global.t;
|
delete global.t;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('ramCapToolkitMode picks CA/EA from the CAP linked libraries', () => {
|
||||||
|
// sim.toolkit / sim.access (A0000000090003...) -> SIM Toolkit (CA field)
|
||||||
|
assert.strictEqual(ramCapToolkitMode([{ aid: 'A0000000090003FFFFFFFF8910710002' }]), 'ca');
|
||||||
|
assert.strictEqual(ramCapToolkitMode([{ aid: 'A0000000090003FFFFFFFF8910710001' }]), 'ca');
|
||||||
|
// uicc.toolkit / uicc.access (A0000000090005...) -> UICC Toolkit (EA field)
|
||||||
|
assert.strictEqual(ramCapToolkitMode([{ aid: 'A0000000090005FFFFFFFF8912000000' }]), 'ea');
|
||||||
|
assert.strictEqual(ramCapToolkitMode([{ aid: 'A0000000090005FFFFFFFF8911000000' }]), 'ea');
|
||||||
|
// uicc.usim.* (A0000000871005...) -> UA family wins over a SIM library
|
||||||
|
assert.strictEqual(ramCapToolkitMode([{ aid: 'A0000000871005FFFFFFFF8913200000' }]), 'ea');
|
||||||
|
assert.strictEqual(ramCapToolkitMode([
|
||||||
|
{ aid: 'A0000000090003FFFFFFFF8910710002' },
|
||||||
|
{ aid: 'A0000000871005FFFFFFFF8913200000' }]), 'ea');
|
||||||
|
// no toolkit/access library: leave the user's choice alone
|
||||||
|
assert.strictEqual(ramCapToolkitMode([{ aid: 'A0000000620101' }, { aid: 'A0000000620001' }]), null);
|
||||||
|
assert.strictEqual(ramCapToolkitMode([]), null);
|
||||||
|
assert.strictEqual(ramCapToolkitMode(undefined), null);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('the toolkit block is an accented bordered fieldset', () => {
|
||||||
|
const m = /id="rc-toolkit-row"[^>]*class="([^"]*)"/.exec(html);
|
||||||
|
assert.ok(m, 'rc-toolkit-row not found');
|
||||||
|
assert.ok(m[1].includes('border'), m[1]);
|
||||||
|
assert.ok(m[1].includes('p-3'), m[1]);
|
||||||
|
assert.ok(/<legend[^>]*>[\s\S]*?rc-toolkit-enable[\s\S]*?<\/legend>/.test(html),
|
||||||
|
'the enable checkbox lives in the fieldset legend');
|
||||||
|
});
|
||||||
|
|
||||||
test('ramFmtPrivileges uses the translated (none) placeholder', () => {
|
test('ramFmtPrivileges uses the translated (none) placeholder', () => {
|
||||||
global.t = s => 'XX' + s;
|
global.t = s => 'XX' + s;
|
||||||
assert.strictEqual(ramFmtPrivileges(''), 'XX(none)');
|
assert.strictEqual(ramFmtPrivileges(''), 'XX(none)');
|
||||||
|
|||||||
+1
-1
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "pysim-simple-server"
|
name = "pysim-simple-server"
|
||||||
version = "3.6.31"
|
version = "3.6.32"
|
||||||
description = "HTTP REST server wrapping pysim for the SIMple PWA"
|
description = "HTTP REST server wrapping pysim for the SIMple PWA"
|
||||||
requires-python = ">=3.8"
|
requires-python = ">=3.8"
|
||||||
# pysim is a git-only dependency installed explicitly by setup.bat/setup.sh.
|
# pysim is a git-only dependency installed explicitly by setup.bat/setup.sh.
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ from osmocom.tlv import BER_TLV_IE
|
|||||||
from osmocom.utils import rpad
|
from osmocom.utils import rpad
|
||||||
|
|
||||||
|
|
||||||
VERSION = '3.6.31'
|
VERSION = '3.6.32'
|
||||||
|
|
||||||
MAX_ENVELOPE_SEGMENTS = 5 # max SMS segments for outgoing C-APDU in ENVELOPE
|
MAX_ENVELOPE_SEGMENTS = 5 # max SMS segments for outgoing C-APDU in ENVELOPE
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user