v1.9.24: chain builder for SIM/USIM/RAM C-APDU sub-tabs
Replaced single-command builders with row-based chain builder: - chainInit/chainAddRow/chainDeleteRow/chainRender - chainSimBuildRowHex/chainRamBuildRowHex per-sub-tab hex builders - Auto-updating chain preview (no Generate button) - GET RESPONSE as fixed row in SIM chain - Le stripping for Case 4 + GET RESPONSE (ETSI TS 102 226) - Each row: command dropdown + context fields + delete x + hex preview - + SIM: SELECT (FID/path/chain/GET RESPONSE), PIN ops, file ops - + USIM: SELECT with FCP requests, silent mode, record ops - + RAM/GP: INSTALL[for install/delete/move], LOAD, STORE DATA, GET STATUS - UICC toolkit nested inside EA (C900 EF00 EA...), SIM CA (EF CA) - Updated tests: 21 SIM + 11 RAM tests for new chain builder API - Removed old genSimUsim/genRam/OPS/updateSimUsimFields and friends - SW cache v35, version 1.9.24
This commit is contained in:
+820
-1005
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -1,4 +1,4 @@
|
||||
const CACHE = 'otaman-v34';
|
||||
const CACHE = 'otaman-v35';
|
||||
const URLS = [
|
||||
'index.html',
|
||||
'help.html',
|
||||
|
||||
+81
-211
@@ -21,68 +21,50 @@ function extractFunc(src, name) {
|
||||
return src.slice(m.index, i + 1);
|
||||
}
|
||||
|
||||
class StubEl {
|
||||
constructor(value = '') {
|
||||
this.value = value;
|
||||
this.checked = false;
|
||||
this.style = {};
|
||||
}
|
||||
}
|
||||
|
||||
const els = {};
|
||||
function el(id, value = '') {
|
||||
if (!els[id]) els[id] = new StubEl(value);
|
||||
return els[id];
|
||||
}
|
||||
|
||||
function reset() {
|
||||
for (const id of Object.keys(els)) delete els[id];
|
||||
}
|
||||
|
||||
const FNS = ['berLenStr', 'buildApdu', 'genRam'];
|
||||
// Extract chain builder functions and dependencies
|
||||
const FNS = ['berLenStr', 'buildApdu', 'escHtml', 'chainInit', 'chainRamBuildRowHex'];
|
||||
let code = '';
|
||||
for (const f of FNS) code += extractFunc(html, f) + '\n';
|
||||
for (const f of FNS) {
|
||||
code += extractFunc(html, f) + '\n';
|
||||
}
|
||||
const m = html.match(/const _chains = \{\};/);
|
||||
if (m) code += m[0].replace(/^const /, 'var ') + '\n';
|
||||
eval(code);
|
||||
|
||||
// document.getElementById stub: global getElementById in the extracted functions
|
||||
const doc = {
|
||||
getElementById: (id) => {
|
||||
if (!els[id]) els[id] = new StubEl();
|
||||
return els[id];
|
||||
},
|
||||
};
|
||||
// genRam also references document.getElementById via global document
|
||||
const els = {};
|
||||
const doc = { getElementById: (id) => { if (!els[id]) els[id] = {value:''}; return els[id]; } };
|
||||
global.document = doc;
|
||||
|
||||
function set(id, v) { el(id).value = v; }
|
||||
function setChecked(id, v) { el(id).checked = v; }
|
||||
function reset() { for (const id of Object.keys(els)) delete els[id]; }
|
||||
|
||||
function genRamResult() {
|
||||
genRam();
|
||||
return els['ram-apdu-result'].value;
|
||||
function genRamResult(fields) {
|
||||
const row = { cmd: fields.cmd, fields: fields };
|
||||
return chainRamBuildRowHex(0, row);
|
||||
}
|
||||
|
||||
function genRamApdu() {
|
||||
reset();
|
||||
set('ram-cmd', 'install-install');
|
||||
set('ram-aid', 'A000000151000000');
|
||||
set('ram-priv', '00');
|
||||
setChecked('ram-toolkit-enable', true);
|
||||
set('ram-tk-mode', 'ea');
|
||||
set('ram-tk-priority', '0');
|
||||
set('ram-tk-timers', '0');
|
||||
set('ram-tk-textlen', '0');
|
||||
set('ram-tk-menus', '2');
|
||||
set('ram-tk-firstpos', '1');
|
||||
set('ram-tk-firstid', '01');
|
||||
set('ram-tk-lastpos', '2');
|
||||
set('ram-tk-lastid', '02');
|
||||
set('ram-tk-channels', '0');
|
||||
set('ram-tk-msl', '16');
|
||||
set('ram-tk-tar', 'B00001');
|
||||
set('ram-tk-ad', '');
|
||||
set('ram-tk-services', '0');
|
||||
return genRamResult();
|
||||
return genRamResult({
|
||||
cmd: 'install-install',
|
||||
aid: 'A000000151000000',
|
||||
elfAid: '',
|
||||
modAid: '',
|
||||
priv: '00',
|
||||
tkEnabled: true,
|
||||
tkMode: 'ea',
|
||||
tkPriority: '0',
|
||||
tkTimers: '0',
|
||||
tkTextlen: '0',
|
||||
tkMenus: '2',
|
||||
tkFirstpos: '1',
|
||||
tkFirstid: '01',
|
||||
tkLastpos: '2',
|
||||
tkLastid: '02',
|
||||
tkChannels: '0',
|
||||
tkMsl: '16',
|
||||
tkTar: 'B00001',
|
||||
tkAd: '',
|
||||
tkServices: '0',
|
||||
});
|
||||
}
|
||||
|
||||
test('UICC toolkit nested inside EA (m=2, services 0)', () => {
|
||||
@@ -91,203 +73,91 @@ test('UICC toolkit nested inside EA (m=2, services 0)', () => {
|
||||
});
|
||||
|
||||
test('UICC m=1 emits single pair', () => {
|
||||
reset();
|
||||
set('ram-cmd', 'install-install');
|
||||
set('ram-aid', 'A000000151000000');
|
||||
set('ram-priv', '00');
|
||||
setChecked('ram-toolkit-enable', true);
|
||||
set('ram-tk-mode', 'ea');
|
||||
set('ram-tk-priority', '0');
|
||||
set('ram-tk-timers', '0');
|
||||
set('ram-tk-textlen', '0');
|
||||
set('ram-tk-menus', '1');
|
||||
set('ram-tk-firstpos', '1');
|
||||
set('ram-tk-firstid', '01');
|
||||
set('ram-tk-lastpos', '0');
|
||||
set('ram-tk-lastid', '00');
|
||||
set('ram-tk-channels', '0');
|
||||
set('ram-tk-msl', '16');
|
||||
set('ram-tk-tar', 'B00001');
|
||||
set('ram-tk-ad', '');
|
||||
set('ram-tk-services', '0');
|
||||
const apdu = genRamResult();
|
||||
const apdu = genRamResult({
|
||||
cmd: 'install-install', aid: 'A000000151000000', priv: '00',
|
||||
tkEnabled: true, tkMode: 'ea', tkPriority: '0', tkTimers: '0', tkTextlen: '0',
|
||||
tkMenus: '1', tkFirstpos: '1', tkFirstid: '01', tkLastpos: '0', tkLastid: '00',
|
||||
tkChannels: '0', tkMsl: '16', tkTar: 'B00001', tkAd: '', tkServices: '0',
|
||||
});
|
||||
assert.ok(apdu.includes('EA11800F0000000101010002011603B0000100'), apdu);
|
||||
});
|
||||
|
||||
test('UICC m=3 fills middle pair with 0000', () => {
|
||||
reset();
|
||||
set('ram-cmd', 'install-install');
|
||||
set('ram-aid', 'A000000151000000');
|
||||
set('ram-priv', '00');
|
||||
setChecked('ram-toolkit-enable', true);
|
||||
set('ram-tk-mode', 'ea');
|
||||
set('ram-tk-priority', '0');
|
||||
set('ram-tk-timers', '0');
|
||||
set('ram-tk-textlen', '0');
|
||||
set('ram-tk-menus', '3');
|
||||
set('ram-tk-firstpos', '1');
|
||||
set('ram-tk-firstid', '01');
|
||||
set('ram-tk-lastpos', '3');
|
||||
set('ram-tk-lastid', '03');
|
||||
set('ram-tk-channels', '0');
|
||||
set('ram-tk-msl', '16');
|
||||
set('ram-tk-tar', 'B00001');
|
||||
set('ram-tk-ad', '');
|
||||
set('ram-tk-services', '0');
|
||||
const apdu = genRamResult();
|
||||
const apdu = genRamResult({
|
||||
cmd: 'install-install', aid: 'A000000151000000', priv: '00',
|
||||
tkEnabled: true, tkMode: 'ea', tkPriority: '0', tkTimers: '0', tkTextlen: '0',
|
||||
tkMenus: '3', tkFirstpos: '1', tkFirstid: '01', tkLastpos: '3', tkLastid: '03',
|
||||
tkChannels: '0', tkMsl: '16', tkTar: 'B00001', tkAd: '', tkServices: '0',
|
||||
});
|
||||
assert.ok(apdu.includes('EA158013000000030101000003030002011603B0000100'), apdu);
|
||||
});
|
||||
|
||||
test('UICC services 7 appended as final byte', () => {
|
||||
reset();
|
||||
set('ram-cmd', 'install-install');
|
||||
set('ram-aid', 'A000000151000000');
|
||||
set('ram-priv', '00');
|
||||
setChecked('ram-toolkit-enable', true);
|
||||
set('ram-tk-mode', 'ea');
|
||||
set('ram-tk-priority', '0');
|
||||
set('ram-tk-timers', '0');
|
||||
set('ram-tk-textlen', '0');
|
||||
set('ram-tk-menus', '2');
|
||||
set('ram-tk-firstpos', '1');
|
||||
set('ram-tk-firstid', '01');
|
||||
set('ram-tk-lastpos', '2');
|
||||
set('ram-tk-lastid', '02');
|
||||
set('ram-tk-channels', '0');
|
||||
set('ram-tk-msl', '16');
|
||||
set('ram-tk-tar', 'B00001');
|
||||
set('ram-tk-ad', '');
|
||||
set('ram-tk-services', '7');
|
||||
const apdu = genRamResult();
|
||||
const apdu = genRamResult({
|
||||
cmd: 'install-install', aid: 'A000000151000000', priv: '00',
|
||||
tkEnabled: true, tkMode: 'ea', tkPriority: '0', tkTimers: '0', tkTextlen: '0',
|
||||
tkMenus: '2', tkFirstpos: '1', tkFirstid: '01', tkLastpos: '2', tkLastid: '02',
|
||||
tkChannels: '0', tkMsl: '16', tkTar: 'B00001', tkAd: '', tkServices: '7',
|
||||
});
|
||||
assert.ok(apdu.includes('EA13801100000002010102020002011603B0000107'), apdu);
|
||||
});
|
||||
|
||||
test('SIM (CA) access domain FIRST, no services byte', () => {
|
||||
reset();
|
||||
set('ram-cmd', 'install-install');
|
||||
set('ram-aid', 'A000000151000000');
|
||||
set('ram-priv', '00');
|
||||
setChecked('ram-toolkit-enable', true);
|
||||
set('ram-tk-mode', 'ca');
|
||||
set('ram-tk-priority', '0');
|
||||
set('ram-tk-timers', '0');
|
||||
set('ram-tk-textlen', '0');
|
||||
set('ram-tk-menus', '2');
|
||||
set('ram-tk-firstpos', '1');
|
||||
set('ram-tk-firstid', '01');
|
||||
set('ram-tk-lastpos', '2');
|
||||
set('ram-tk-lastid', '02');
|
||||
set('ram-tk-channels', '0');
|
||||
set('ram-tk-msl', '16');
|
||||
set('ram-tk-tar', 'B00001');
|
||||
set('ram-tk-ad', '5A');
|
||||
set('ram-tk-services', '0');
|
||||
const apdu = genRamResult();
|
||||
const apdu = genRamResult({
|
||||
cmd: 'install-install', aid: 'A000000151000000', priv: '00',
|
||||
tkEnabled: true, tkMode: 'ca', tkPriority: '0', tkTimers: '0', tkTextlen: '0',
|
||||
tkMenus: '2', tkFirstpos: '1', tkFirstid: '01', tkLastpos: '2', tkLastid: '02',
|
||||
tkChannels: '0', tkMsl: '16', tkTar: 'B00001', tkAd: '5A', tkServices: '0',
|
||||
});
|
||||
assert.ok(apdu.includes('EF14CA12015A00000002010102020002011603B00001'), apdu);
|
||||
});
|
||||
|
||||
test('SIM (CA) blank access domain emits length byte 00', () => {
|
||||
reset();
|
||||
set('ram-cmd', 'install-install');
|
||||
set('ram-aid', 'A000000151000000');
|
||||
set('ram-priv', '00');
|
||||
setChecked('ram-toolkit-enable', true);
|
||||
set('ram-tk-mode', 'ca');
|
||||
set('ram-tk-priority', '0');
|
||||
set('ram-tk-timers', '0');
|
||||
set('ram-tk-textlen', '0');
|
||||
set('ram-tk-menus', '2');
|
||||
set('ram-tk-firstpos', '1');
|
||||
set('ram-tk-firstid', '01');
|
||||
set('ram-tk-lastpos', '2');
|
||||
set('ram-tk-lastid', '02');
|
||||
set('ram-tk-channels', '0');
|
||||
set('ram-tk-msl', '16');
|
||||
set('ram-tk-tar', 'B00001');
|
||||
set('ram-tk-ad', '');
|
||||
set('ram-tk-services', '0');
|
||||
const apdu = genRamResult();
|
||||
const apdu = genRamResult({
|
||||
cmd: 'install-install', aid: 'A000000151000000', priv: '00',
|
||||
tkEnabled: true, tkMode: 'ca', tkPriority: '0', tkTimers: '0', tkTextlen: '0',
|
||||
tkMenus: '2', tkFirstpos: '1', tkFirstid: '01', tkLastpos: '2', tkLastid: '02',
|
||||
tkChannels: '0', tkMsl: '16', tkTar: 'B00001', tkAd: '', tkServices: '0',
|
||||
});
|
||||
assert.ok(apdu.includes('EF13CA110000000002010102020002011603B00001'), apdu);
|
||||
});
|
||||
|
||||
test('SIM (CA) m=3, no TAR, blank access domain', () => {
|
||||
reset();
|
||||
set('ram-cmd', 'install-install');
|
||||
set('ram-aid', 'A000000151000000');
|
||||
set('ram-priv', '00');
|
||||
setChecked('ram-toolkit-enable', true);
|
||||
set('ram-tk-mode', 'ca');
|
||||
set('ram-tk-priority', '0');
|
||||
set('ram-tk-timers', '0');
|
||||
set('ram-tk-textlen', '0');
|
||||
set('ram-tk-menus', '3');
|
||||
set('ram-tk-firstpos', '1');
|
||||
set('ram-tk-firstid', '01');
|
||||
set('ram-tk-lastpos', '3');
|
||||
set('ram-tk-lastid', '03');
|
||||
set('ram-tk-channels', '0');
|
||||
set('ram-tk-msl', '16');
|
||||
set('ram-tk-tar', '');
|
||||
set('ram-tk-ad', '');
|
||||
set('ram-tk-services', '0');
|
||||
const apdu = genRamResult();
|
||||
const apdu = genRamResult({
|
||||
cmd: 'install-install', aid: 'A000000151000000', priv: '00',
|
||||
tkEnabled: true, tkMode: 'ca', tkPriority: '0', tkTimers: '0', tkTextlen: '0',
|
||||
tkMenus: '3', tkFirstpos: '1', tkFirstid: '01', tkLastpos: '3', tkLastid: '03',
|
||||
tkChannels: '0', tkMsl: '16', tkTar: '', tkAd: '', tkServices: '0',
|
||||
});
|
||||
assert.ok(apdu.includes('EF12CA1000000000030101000003030002011600'), apdu);
|
||||
});
|
||||
|
||||
test('UICC m=60 uses long-form BER lengths (EA 81 87 / inner 81 84)', () => {
|
||||
reset();
|
||||
set('ram-cmd', 'install-install');
|
||||
set('ram-aid', 'A000000151000000');
|
||||
set('ram-priv', '00');
|
||||
setChecked('ram-toolkit-enable', true);
|
||||
set('ram-tk-mode', 'ea');
|
||||
set('ram-tk-priority', '0');
|
||||
set('ram-tk-timers', '0');
|
||||
set('ram-tk-textlen', '0');
|
||||
set('ram-tk-menus', '60');
|
||||
set('ram-tk-firstpos', '1');
|
||||
set('ram-tk-firstid', '01');
|
||||
set('ram-tk-lastpos', '60');
|
||||
set('ram-tk-lastid', '3C');
|
||||
set('ram-tk-channels', '0');
|
||||
set('ram-tk-msl', '16');
|
||||
set('ram-tk-tar', 'B00001');
|
||||
set('ram-tk-ad', '');
|
||||
set('ram-tk-services', '0');
|
||||
const apdu = genRamResult();
|
||||
const apdu = genRamResult({
|
||||
cmd: 'install-install', aid: 'A000000151000000', priv: '00',
|
||||
tkEnabled: true, tkMode: 'ea', tkPriority: '0', tkTimers: '0', tkTextlen: '0',
|
||||
tkMenus: '60', tkFirstpos: '1', tkFirstid: '01', tkLastpos: '60', tkLastid: '3C',
|
||||
tkChannels: '0', tkMsl: '16', tkTar: 'B00001', tkAd: '', tkServices: '0',
|
||||
});
|
||||
assert.ok(apdu.includes('EA8188808185'), apdu);
|
||||
});
|
||||
|
||||
test('LOAD P1 fixed to 80 (last block)', () => {
|
||||
reset();
|
||||
set('ram-cmd', 'load');
|
||||
set('ram-data', 'AABBCC');
|
||||
set('ram-block', '0');
|
||||
const apdu = genRamResult();
|
||||
const apdu = genRamResult({ cmd: 'load', data: 'AABBCC', block: '0' });
|
||||
assert.ok(apdu.startsWith('80E88000'), apdu);
|
||||
});
|
||||
|
||||
test('DELETE: P1=00, mode in P2', () => {
|
||||
reset();
|
||||
set('ram-cmd', 'delete');
|
||||
set('ram-aid', 'AA1902BC225501');
|
||||
set('ram-del-mode', '00');
|
||||
let apdu = genRamResult();
|
||||
let apdu = genRamResult({ cmd: 'delete', aid: 'AA1902BC225501', delMode: '00' });
|
||||
assert.ok(apdu.startsWith('80E40000'), apdu);
|
||||
|
||||
set('ram-del-mode', '80');
|
||||
apdu = genRamResult();
|
||||
apdu = genRamResult({ cmd: 'delete', aid: 'AA1902BC225501', delMode: '80' });
|
||||
assert.ok(apdu.startsWith('80E40080'), apdu);
|
||||
});
|
||||
|
||||
test('STORE DATA ram-enc P1 values 00/40/80/C0/E0', () => {
|
||||
for (const [enc, p1] of [['00','00'],['40','40'],['80','80'],['C0','C0'],['E0','E0']]) {
|
||||
reset();
|
||||
set('ram-cmd', 'store-data');
|
||||
set('ram-data', 'AABB');
|
||||
set('ram-enc', enc);
|
||||
set('ram-block', '0');
|
||||
const apdu = genRamResult();
|
||||
const apdu = genRamResult({ cmd: 'store-data', data: 'AABB', enc: enc, block: '0' });
|
||||
assert.ok(apdu.startsWith('80E2' + p1 + '00'), enc + ' -> P1 ' + p1);
|
||||
}
|
||||
});
|
||||
+95
-69
@@ -21,73 +21,89 @@ function extractFunc(src, name) {
|
||||
return src.slice(m.index, i + 1);
|
||||
}
|
||||
|
||||
class StubEl {
|
||||
constructor(value = '') {
|
||||
this.value = value;
|
||||
this.checked = false;
|
||||
this.style = {};
|
||||
this.disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
const els = {};
|
||||
let usimBase = 'mf';
|
||||
const doc = {
|
||||
getElementById: (id) => {
|
||||
if (!els[id]) els[id] = new StubEl();
|
||||
return els[id];
|
||||
},
|
||||
querySelector: (sel) => (sel.includes('usim-sel-base') ? { value: usimBase } : null),
|
||||
};
|
||||
global.document = doc;
|
||||
|
||||
const FNS = ['buildApdu', 'buildSelect', 'getSelValue', 'buildSelectApdu',
|
||||
'updateSimUsimFields', 'updateP1P2Display', 'genSimUsim', 'OPS'];
|
||||
// Extract chain builder functions and dependencies
|
||||
const FNS = ['buildApdu', 'buildSelect', 'escHtml', 'chainInit', 'chainSimBuildRowHex'];
|
||||
let code = '';
|
||||
for (const f of FNS) {
|
||||
if (f === 'OPS') {
|
||||
const m = html.match(/const OPS = \{[\s\S]*?\n\};/);
|
||||
code += m[0].replace(/^const /, 'var ') + '\n';
|
||||
} else {
|
||||
code += extractFunc(html, f) + '\n';
|
||||
}
|
||||
code += extractFunc(html, f) + '\n';
|
||||
}
|
||||
code += '\nvar SIM_PRESETS = {};';
|
||||
// Also extract _chains
|
||||
const m = html.match(/const _chains = \{\};/);
|
||||
if (m) code += m[0].replace(/^const /, 'var ') + '\n';
|
||||
eval(code);
|
||||
|
||||
|
||||
function set(id, v) { els[id] = Object.assign(new StubEl(), { value: v }); }
|
||||
function setChecked(id, v) { els[id] = Object.assign(new StubEl(), { checked: v }); }
|
||||
|
||||
function setup(mode, opts) {
|
||||
for (const k of Object.keys(els)) delete els[k];
|
||||
usimBase = opts.base || 'mf';
|
||||
set(mode + '-cmd', opts.cmd);
|
||||
setChecked(mode + '-select', !!opts.doSelect);
|
||||
set(mode + '-sel-method', opts.selMethod || 'fid');
|
||||
set(mode + '-sel-fid', opts.fid || '');
|
||||
set(mode + '-sel-path', opts.path || '');
|
||||
set(mode + '-sel-dfname', opts.dfname || '');
|
||||
set(mode + '-sel-chain', opts.chain || '');
|
||||
set(mode + '-record', opts.record || '1');
|
||||
set(mode + '-rec-mode', opts.recMode || '04');
|
||||
set(mode + '-offset', opts.offset || '0000');
|
||||
set(mode + '-le', opts.le || '00');
|
||||
set(mode + '-data', opts.data || '');
|
||||
set(mode + '-recsize', opts.recsize || '0');
|
||||
set(mode + '-pin', opts.pin || '01');
|
||||
set(mode + '-pin-val', opts.pinVal || '');
|
||||
set(mode + '-pin-old', opts.pinOld || '');
|
||||
set(mode + '-pin-new', opts.pinNew || '');
|
||||
set(mode + '-act-target', opts.actTarget || 'current');
|
||||
set(mode + '-act-file', opts.actFile || '');
|
||||
els['usim-sel-silent'] = Object.assign(new StubEl(), { checked: !!opts.silent });
|
||||
els[mode + '-select'] = Object.assign(new StubEl(), { checked: !!opts.doSelect });
|
||||
set(mode + '-override', '');
|
||||
els[mode + '-result'] = new StubEl();
|
||||
els[mode + '-pack-btn'] = new StubEl();
|
||||
genSimUsim(mode);
|
||||
return els[mode + '-result'].value;
|
||||
const chainId = mode === 'sim' ? 'chain-sim' : 'chain-usim';
|
||||
_chains[chainId] = { rows: [] };
|
||||
|
||||
const cmd = opts.cmd;
|
||||
const fields = {};
|
||||
|
||||
if (cmd === 'select') {
|
||||
fields.method = opts.selMethod || 'fid';
|
||||
if (opts.fid) fields.fid = opts.fid;
|
||||
if (opts.path) fields.path = opts.path;
|
||||
if (opts.dfname) fields.dfname = opts.dfname;
|
||||
if (opts.chain) fields.chain = opts.chain;
|
||||
if (opts.silent) fields.silent = opts.silent;
|
||||
if (opts.base) fields.base = opts.base;
|
||||
} else if (cmd === 'read-record') {
|
||||
fields.record = opts.record || '1';
|
||||
fields.recMode = opts.recMode || '04';
|
||||
fields.le = opts.le || '00';
|
||||
} else if (cmd === 'read-binary') {
|
||||
fields.offset = opts.offset || '0000';
|
||||
fields.le = opts.le || '00';
|
||||
} else if (cmd === 'update-record') {
|
||||
fields.record = opts.record || '1';
|
||||
fields.recMode = opts.recMode || '04';
|
||||
fields.data = opts.data || '';
|
||||
} else if (cmd === 'update-binary') {
|
||||
fields.offset = opts.offset || '0000';
|
||||
fields.data = opts.data || '';
|
||||
} else if (cmd === 'erase-binary') {
|
||||
fields.offset = opts.offset || '0000';
|
||||
} else if (cmd === 'activate-file' || cmd === 'deactivate-file') {
|
||||
fields.target = opts.actTarget || 'current';
|
||||
fields.file = opts.actFile || '';
|
||||
} else if (cmd === 'verify' || cmd === 'disable' || cmd === 'enable') {
|
||||
fields.pin = opts.pin || '01';
|
||||
fields.pinVal = opts.pinVal || '';
|
||||
} else if (cmd === 'change' || cmd === 'unblock') {
|
||||
fields.pin = opts.pin || '01';
|
||||
fields.pinOld = opts.pinOld || '';
|
||||
fields.pinNew = opts.pinNew || '';
|
||||
}
|
||||
|
||||
_chains[chainId].rows.push({ cmd: cmd, fields: fields });
|
||||
|
||||
if (opts.doSelect) {
|
||||
const selFields = {};
|
||||
selFields.method = opts.selMethod || 'fid';
|
||||
if (opts.fid) selFields.fid = opts.fid;
|
||||
if (opts.path) selFields.path = opts.path;
|
||||
if (opts.dfname) selFields.dfname = opts.dfname;
|
||||
if (opts.chain) selFields.chain = opts.chain;
|
||||
if (opts.silent) selFields.silent = opts.silent;
|
||||
if (opts.base) selFields.base = opts.base;
|
||||
_chains[chainId].rows.unshift({ cmd: 'select', fields: selFields });
|
||||
}
|
||||
|
||||
const rowIdx = opts.doSelect ? 1 : 0;
|
||||
return chainSimBuildRowHex(chainId, rowIdx, _chains[chainId].rows[rowIdx]);
|
||||
}
|
||||
|
||||
function setupMulti(mode, rows) {
|
||||
const chainId = mode === 'sim' ? 'chain-sim' : 'chain-usim';
|
||||
_chains[chainId] = { rows: [] };
|
||||
for (const r of rows) {
|
||||
_chains[chainId].rows.push({ cmd: r.cmd, fields: r.fields || {} });
|
||||
}
|
||||
let hex = '';
|
||||
for (let i = 0; i < _chains[chainId].rows.length; i++) {
|
||||
hex += chainSimBuildRowHex(chainId, i, _chains[chainId].rows[i]);
|
||||
}
|
||||
return hex;
|
||||
}
|
||||
|
||||
test('VERIFY PIN FF-pads to 8 bytes (Lc=08)', () => {
|
||||
@@ -166,18 +182,28 @@ test('GET RESPONSE hop with explicit Le (C0:NN)', () => {
|
||||
});
|
||||
|
||||
test('silent path select + READ RECORD keeps full CLA (live PNN read)', () => {
|
||||
const apdu = setup('usim', {
|
||||
cmd: 'read-record', selMethod: 'path', base: 'df', silent: true, doSelect: true,
|
||||
path: '6FC5', record: '1', recMode: '04', le: '14',
|
||||
});
|
||||
const chainId = 'chain-usim';
|
||||
_chains[chainId] = { rows: [
|
||||
{ cmd: 'select', fields: { method: 'path', path: '6FC5', base: 'df', silent: true } },
|
||||
{ cmd: 'read-record', fields: { record: '1', recMode: '04', le: '14' } },
|
||||
]};
|
||||
let apdu = '';
|
||||
for (let i = 0; i < _chains[chainId].rows.length; i++) {
|
||||
apdu += chainSimBuildRowHex(chainId, i, _chains[chainId].rows[i]);
|
||||
}
|
||||
assert.strictEqual(apdu, '00A4090C026FC500B2010414');
|
||||
});
|
||||
|
||||
test('select + READ BINARY emits explicit CLA on second command', () => {
|
||||
const apdu = setup('usim', {
|
||||
cmd: 'read-binary', selMethod: 'path', base: 'df', silent: true, doSelect: true,
|
||||
path: '6FC5', offset: '0000', le: '0A',
|
||||
});
|
||||
const chainId = 'chain-usim';
|
||||
_chains[chainId] = { rows: [
|
||||
{ cmd: 'select', fields: { method: 'path', path: '6FC5', base: 'df', silent: true } },
|
||||
{ cmd: 'read-binary', fields: { offset: '0000', le: '0A' } },
|
||||
]};
|
||||
let apdu = '';
|
||||
for (let i = 0; i < _chains[chainId].rows.length; i++) {
|
||||
apdu += chainSimBuildRowHex(chainId, i, _chains[chainId].rows[i]);
|
||||
}
|
||||
assert.strictEqual(apdu, '00A4090C026FC500B000000A');
|
||||
});
|
||||
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "pysim-otaman-server"
|
||||
version = "1.9.18"
|
||||
version = "1.9.24"
|
||||
description = "HTTP REST server wrapping pysim for the OTAMan PWA"
|
||||
requires-python = ">=3.8"
|
||||
# pysim is a git-only dependency installed explicitly by setup.bat/setup.sh.
|
||||
|
||||
@@ -18,7 +18,7 @@ from osmocom.construct import GsmOrUcs2Adapter
|
||||
from osmocom.tlv import BER_TLV_IE
|
||||
|
||||
|
||||
VERSION = '1.9.23'
|
||||
VERSION = '1.9.24'
|
||||
|
||||
MAX_ENVELOPE_SEGMENTS = 5 # max SMS segments for outgoing C-APDU in ENVELOPE
|
||||
|
||||
|
||||
Reference in New Issue
Block a user