v3.20.0 (fix): the install-parameter composition - one System Specific Parameters EF
Two reported SCP80/RAM issues, both confirmed against the specs:
- docs/api.md called `stk_params` a "Hex CA TLV", but TS 102 226 8.2.1.3.2.1
puts the CA *inside* the System Specific Parameters EF ('EF'), and the PWA
builds `EF <len> CA <len> ...` (the SIM access domain leads the CA value as
`01 <ad>` / `00`). A client following the doc sent a CA at the wrong level.
- the RAM composition appended the STK part after a caller-composed
C9+EF(quotas) prefix, producing TWO EF objects. The card read the first
(the quotas) and ignored the CA: the install was accepted (por_ok) but the
toolkit parameters were never registered, so no STK menu appeared (live
2026-10-05). Per GPD_SPE_013 v1.1 Table 6-5 (pySim's
gen_install_parameters) the quotas (C7/C8) and the CA share ONE EF; an
EA-form STK part is a sibling of EF (the reference TCA loader's
`C9 00 EF 00 EA ...`).
- server: `_compose_install_params()` (used by `_cap_install_apdu` ->
`_cap_apdu_sequence`, hence /api/ram-install, /api/ram-install-app,
/api/scp81/gen-install and the PWA's install script template) enforces the
rule: a caller-composed EF plus an EF-form `stk_params`, a bare CA, a
malformed EF and quotas next to a non-empty `install_params` are refused
(400 / `{"ok": false}` with the fix named); with an empty `install_params`
the quotas and an EF-form CA are composed into one EF
(`C900 EF{C7?,C8?,CA}`), an EA part stays a sibling.
- the documented `nv_quota`/`volatile_quota` fields are now real (they were
named in the docs but implemented nowhere): integers composing C8/C7.
- PWA: `composeRamInstallParams()` composes the same single EF itself (a
CA-form STK field takes the quotas into its EF, the merged EF travels in
`stk_params`), the RAM install bodies send it, the placeholder shows it
with a note when the quotas moved into the STK EF, and a refused request
(no steps) reports the error instead of "FAILED at step: undefined".
- docs/api.md: the corrected `stk_params` description with both accepted
shapes, the composition rule paragraph with the refusal cases, the fixed
example (`C90000` -> `C900`), and the /api/ram-install-app and
/api/scp81/gen-install cross-references.
- tests: composition unit tests (merge/refusals/EA-sibling) and the
ram-install-app 400 paths (Python); `ramQuotaTlvs`/`composeRamInstallParams`/
`ramInstallParamsCompose` pins incl. the CA merge (Node).
Tests: 824 frontend + 740 Python; sw cache simple-v395.
This commit is contained in:
@@ -23,8 +23,9 @@ function extractFunc(src, name) {
|
||||
|
||||
let code = '';
|
||||
for (const fn of ['berLenStr', 'stkParamsBuild', 'buildRcToolkitParams',
|
||||
'updateStkParamsHex', 'adfAidFromFci', 'ramParamsPrefix', 'ramQuotaValue',
|
||||
'ramInstallParamsPrefix']) code += extractFunc(html, fn) + '\n';
|
||||
'updateStkParamsHex', 'updateInstallParamsHex', 'adfAidFromFci', 'ramQuotaValue', 'ramQuotaTlvs',
|
||||
'composeRamInstallParams', 'ramInstallParamsCompose',
|
||||
'parseTlvList', 'parseBerLen']) code += extractFunc(html, fn) + '\n';
|
||||
eval(code);
|
||||
|
||||
const base = { mode: 'ea', priority: '0', timers: '0', textLen: '0', menus: '0',
|
||||
@@ -128,24 +129,61 @@ test('adfAidFromFci extracts the DF name (tag 84) from an FCI', () => {
|
||||
assert.strictEqual(adfAidFromFci('ZZZZ'), '');
|
||||
});
|
||||
|
||||
test('ramParamsPrefix composes C9 + EF (C7/C8) like the vendor scripts', () => {
|
||||
test('composeRamInstallParams composes C9 + EF (C7/C8) like the vendor scripts', () => {
|
||||
// The vendor's working install (samples/uicc/applets/STK3):
|
||||
// C9 22 <config> EF 08 C7 02 0000 C8 02 0000 EA ...
|
||||
// C9 is mandatory (GP Table 11-49): an empty field is sent as C9 00 -
|
||||
// without it the card rejects the data field with 6A80 (live 2026-09-28).
|
||||
assert.strictEqual(ramParamsPrefix('', '', ''), 'C900');
|
||||
assert.strictEqual(ramParamsPrefix('082905112000012066', '', ''),
|
||||
assert.strictEqual(composeRamInstallParams('', '', '', '', '').install_params, 'C900');
|
||||
assert.strictEqual(
|
||||
composeRamInstallParams('082905112000012066', '', '', '', '').install_params,
|
||||
'C909082905112000012066');
|
||||
assert.strictEqual(ramParamsPrefix('', '0', '0'), 'C900EF08C7020000C8020000');
|
||||
assert.strictEqual(ramParamsPrefix('', '32768', ''), 'C900EF06C70400008000');
|
||||
assert.strictEqual(ramParamsPrefix('AA', '', '1234'), 'C901AAEF04C80204D2');
|
||||
assert.strictEqual(composeRamInstallParams('', '0', '0', '', '').install_params,
|
||||
'C900EF08C7020000C8020000');
|
||||
assert.strictEqual(composeRamInstallParams('', '32768', '', '', '').install_params,
|
||||
'C900EF06C70400008000');
|
||||
assert.strictEqual(composeRamInstallParams('AA', '', '1234', '', '').install_params,
|
||||
'C901AAEF04C80204D2');
|
||||
// extra raw TLVs follow the generated prefix
|
||||
assert.strictEqual(composeRamInstallParams('', '', '', 'CB02AABB', '').install_params,
|
||||
'C900CB02AABB');
|
||||
});
|
||||
|
||||
test('the RAM form composes the C9/quota prefix into the install parameters', () => {
|
||||
test('an EF-form STK part takes the quotas into its own EF (one EF, TS 102 226 8.2.1.3.2.1)', () => {
|
||||
// Two EFs made the card read the quotas and ignore the CA (live
|
||||
// 2026-10-05: por_ok but no STK menu): the CA and the quotas share one
|
||||
// System Specific Parameters EF (GPD_SPE_013 Table 6-5).
|
||||
const ca = stkParamsBuild(vals({ mode: 'ca' })); // EF0ACA08...
|
||||
assert.strictEqual(ca, 'EF0ACA080000000000000000');
|
||||
const c = composeRamInstallParams('', '100', '', '', ca);
|
||||
assert.strictEqual(c.install_params, 'C900');
|
||||
assert.strictEqual(c.stk_params, 'EF0EC7020064CA080000000000000000');
|
||||
assert.strictEqual(c.quota_in_stk, true);
|
||||
// without quotas the EF passes through unchanged
|
||||
const c2 = composeRamInstallParams('', '', '', '', ca);
|
||||
assert.strictEqual(c2.install_params, 'C900');
|
||||
assert.strictEqual(c2.stk_params, ca);
|
||||
assert.strictEqual(c2.quota_in_stk, false);
|
||||
// an EA-form STK part is a sibling of EF: the quotas keep their own EF
|
||||
const ea = stkParamsBuild(vals({ mode: 'ea' }));
|
||||
const c3 = composeRamInstallParams('', '100', '', '', ea);
|
||||
assert.strictEqual(c3.install_params, 'C900EF04C7020064');
|
||||
assert.strictEqual(c3.stk_params, ea);
|
||||
assert.strictEqual(c3.quota_in_stk, false);
|
||||
});
|
||||
|
||||
test('the RAM form composes the install parameters from its fields', () => {
|
||||
const els = fakeForm({ 'rc-c9': 'AA', 'rc-quota-c7': '0' });
|
||||
assert.strictEqual(ramInstallParamsPrefix(), 'C901AAEF04C7020000');
|
||||
assert.deepStrictEqual(ramInstallParamsCompose(),
|
||||
{ install_params: 'C901AAEF04C7020000', stk_params: '', quota_in_stk: false });
|
||||
els['rc-quota-c8'].value = '5';
|
||||
assert.strictEqual(ramInstallParamsPrefix(), 'C901AAEF08C7020000C8020005');
|
||||
assert.strictEqual(ramInstallParamsCompose().install_params, 'C901AAEF08C7020000C8020005');
|
||||
// a CA-form STK field takes the quotas into its EF
|
||||
els['ram-stk-params'].value = 'EF0ACA080000000000000000';
|
||||
const c = ramInstallParamsCompose();
|
||||
assert.strictEqual(c.install_params, 'C901AA');
|
||||
assert.strictEqual(c.stk_params, 'EF12C7020000C8020005CA080000000000000000');
|
||||
assert.strictEqual(c.quota_in_stk, true);
|
||||
});
|
||||
|
||||
test('updateStkParamsHex names the rejection reason', () => {
|
||||
@@ -166,7 +204,12 @@ test('updateStkParamsHex names the rejection reason', () => {
|
||||
|
||||
test('the install body composes the C9/quota prefix', () => {
|
||||
const fn = extractFunc(html, 'ramInstallCap');
|
||||
assert.ok(/install_params: \(ramInstallParamsPrefix\(\)/.test(fn), fn);
|
||||
assert.ok(/const composed = ramInstallParamsCompose\(\)/.test(fn), fn);
|
||||
assert.ok(/install_params: composed\.install_params/.test(fn), fn);
|
||||
assert.ok(/stk_params: composed\.stk_params/.test(fn), fn);
|
||||
const app = extractFunc(html, 'ramInstallApp');
|
||||
assert.ok(/const composed = ramInstallParamsCompose\(\)/.test(app), app);
|
||||
assert.ok(/install_params: composed\.install_params/.test(app), app);
|
||||
});
|
||||
|
||||
test('the ADF AID From-card button is wired', () => {
|
||||
|
||||
Reference in New Issue
Block a user