@@ -1313,172 +827,12 @@ function cApduSwitchSubtab(name) {
const el = document.getElementById('c-apdu-sub-' + s);
if (el) el.classList.toggle('hidden', s !== name);
});
- if (name === 'sim' || name === 'usim') {
- updateSimUsimFields(name);
- updateSimSelectMethod(name);
- }
setHelpAnchor({sim:'sim-rfm', usim:'usim-rfm', ber:'ber-tlv', ram:'ram-gp', parse:'c-apdu'}[name] || 'c-apdu');
}
document.querySelectorAll('.tab-btn').forEach(btn => {
btn.addEventListener('click', () => switchTab(btn.dataset.tab));
});
-// ===== SIM/USIM field visibility =====
-const RECORD_OPS = new Set(['update-record', 'read-record']);
-const BINARY_OPS = new Set(['update-binary', 'read-binary', 'erase-binary']);
-const READ_OPS = new Set(['read-record', 'read-binary']);
-const DATA_OPS = new Set(['update-record', 'update-binary']);
-const RECSIZE_OPS = new Set(['update-record']);
-const PIN_OPS = new Set(['verify', 'change', 'disable', 'enable', 'unblock']);
-
-function updateSimUsimFields(mode) {
- if (!mode) return;
- const cmd = document.getElementById(mode + '-cmd').value;
- document.getElementById(mode + '-record-row').style.display = RECORD_OPS.has(cmd) ? '' : 'none';
- document.getElementById(mode + '-offset-row').style.display = BINARY_OPS.has(cmd) ? '' : 'none';
- document.getElementById(mode + '-le-row').style.display = READ_OPS.has(cmd) ? '' : 'none';
- document.getElementById(mode + '-data-row').style.display = DATA_OPS.has(cmd) ? '' : 'none';
- document.getElementById(mode + '-recsize-row').style.display = RECSIZE_OPS.has(cmd) ? '' : 'none';
- document.getElementById(mode + '-pin-row').style.display = PIN_OPS.has(cmd) ? '' : 'none';
- const pinValCmds = cmd === 'verify' || cmd === 'disable' || cmd === 'enable';
- document.getElementById(mode + '-pin-val-row').style.display = pinValCmds ? '' : 'none';
- const oldNewCmds = cmd === 'change' || cmd === 'unblock';
- document.getElementById(mode + '-pin-old-row').style.display = oldNewCmds ? '' : 'none';
- document.getElementById(mode + '-pin-new-row').style.display = oldNewCmds ? '' : 'none';
- if (oldNewCmds) {
- document.querySelector('#' + mode + '-pin-old-row label').textContent = cmd === 'unblock' ? 'Unblock PIN' : 'Old PIN';
- }
- document.getElementById(mode + '-act-row').style.display = (cmd === 'activate-file' || cmd === 'deactivate-file') ? '' : 'none';
- if (cmd === 'select') {
- document.getElementById(mode + '-select-checkbox-row').style.display = 'none';
- } else {
- document.getElementById(mode + '-select-checkbox-row').style.display = '';
- }
- updateP1P2Display(mode);
-}
-
-function updateSimSelectMethod(mode) {
- if (!mode) return;
- const method = document.getElementById(mode + '-sel-method').value;
- document.getElementById(mode + '-sel-fid-row').style.display = method === 'fid' ? '' : 'none';
- document.getElementById(mode + '-sel-path-row').style.display = method === 'path' ? '' : 'none';
- document.getElementById(mode + '-sel-dfname-row').style.display = method === 'dfname' ? '' : 'none';
- document.getElementById(mode + '-sel-chain-row').style.display = method === 'chain' ? '' : 'none';
-}
-
-function updateActFileInput(mode) {
- const target = document.getElementById(mode + '-act-target').value;
- document.getElementById(mode + '-act-file').style.display = target === 'current' ? 'none' : '';
-}
-
-function toggleSimOverride(mode) {
- const checked = document.getElementById(mode + '-override').checked;
- document.getElementById(mode + '-p1').disabled = !checked;
- document.getElementById(mode + '-p2').disabled = !checked;
-}
-
-function updateSimSelectSection(mode) {
- const cmd = document.getElementById(mode + '-cmd').value;
- if (cmd === 'select') return;
- document.getElementById(mode + '-select-method-body').style.display =
- document.getElementById(mode + '-select').checked ? '' : 'none';
-}
-
-function updateP1P2Display(mode) {
- const preset = mode === 'sim' ? {selP1: 0x00, selP2: 0x00} : {selP1: 0x00, selP2: 0x04};
- const cmd = document.getElementById(mode + '-cmd').value;
- if (cmd === 'select') {
- const method = document.getElementById(mode + '-sel-method').value;
- const silent = mode === 'usim' && !!document.getElementById('usim-sel-silent').checked;
- const p2Base = silent ? 0x0C : preset.selP2;
- let p1, p2;
- if (method === 'fid' || method === 'chain') {
- p1 = preset.selP1; p2 = p2Base;
- } else if (method === 'path') {
- p1 = (mode === 'usim' && document.querySelector('input[name="usim-sel-base"]:checked')?.value === 'df') ? 0x09 : 0x08;
- p2 = mode === 'sim' ? 0x00 : p2Base;
- } else if (method === 'dfname') {
- p1 = 0x04; p2 = mode === 'sim' ? 0x00 : p2Base;
- } else {
- p1 = preset.selP1; p2 = p2Base;
- }
- document.getElementById(mode + '-p1').value = p1.toString(16).padStart(2, '0').toUpperCase();
- document.getElementById(mode + '-p2').value = p2.toString(16).padStart(2, '0').toUpperCase();
- return;
- }
- const record = parseInt(document.getElementById(mode + '-record').value) || 1;
- const recMode = parseInt(document.getElementById(mode + '-rec-mode').value, 16);
- const offsetHex = (document.getElementById(mode + '-offset').value || '0000').replace(/[^0-9a-fA-F]/g, '').padStart(4, '0').slice(0, 4);
- const offset = parseInt(offsetHex, 16);
- const pin = document.getElementById(mode + '-pin').value;
- let p1, p2;
- if (cmd === 'read-record' || cmd === 'update-record') {
- p1 = (recMode === 0x02 || recMode === 0x03) ? 0x00 : record; p2 = recMode;
- } else if (cmd === 'read-binary' || cmd === 'update-binary' || cmd === 'erase-binary') {
- p1 = (offset >> 8) & 0xFF; p2 = offset & 0xFF;
- } else if (cmd === 'activate-file' || cmd === 'deactivate-file') {
- const target = document.getElementById(mode + '-act-target').value;
- if (target === 'current') {
- p1 = 0x00; p2 = 0x00;
- } else {
- p1 = target === 'fid' ? 0x00 : (target === 'mfpath' ? 0x08 : 0x09);
- p2 = 0x00;
- }
- } else if (cmd === 'verify' || cmd === 'change' || cmd === 'disable' || cmd === 'enable' || cmd === 'unblock') {
- p1 = 0x00; p2 = parseInt(pin, 16);
- } else {
- p1 = 0x00; p2 = 0x00;
- }
- document.getElementById(mode + '-p1').value = p1.toString(16).padStart(2, '0').toUpperCase();
- document.getElementById(mode + '-p2').value = p2.toString(16).padStart(2, '0').toUpperCase();
-}
-
-// ===== RAM field visibility =====
-function updateRamFields() {
- const cmd = document.getElementById('ram-cmd').value;
- const showPriv = ['install-install', 'install-make-sel', 'install-reg-update'].includes(cmd);
- document.getElementById('ram-priv-row').style.display = showPriv ? '' : 'none';
- document.getElementById('ram-toolkit-row').style.display = cmd === 'install-install' ? '' : 'none';
- document.getElementById('ram-sdaid-row').style.display = ['install-load', 'install-reg-update', 'install-extradition'].includes(cmd) ? '' : 'none';
- document.getElementById('ram-elfaid-row').style.display = cmd === 'install-install' ? '' : 'none';
- document.getElementById('ram-modaid-row').style.display = cmd === 'install-install' ? '' : 'none';
- document.getElementById('ram-load-data-row').style.display = cmd === 'install-load' ? '' : 'none';
- document.getElementById('ram-data-row').style.display = ['load', 'store-data', 'ext-auth', 'int-auth'].includes(cmd) ? '' : 'none';
- document.getElementById('ram-block-row').style.display = ['load', 'store-data'].includes(cmd) ? '' : 'none';
- document.getElementById('ram-enc-row').style.display = cmd === 'store-data' ? '' : 'none';
- document.getElementById('ram-authp1-row').style.display = cmd === 'ext-auth' ? '' : 'none';
- document.getElementById('ram-del-mode-row').style.display = cmd === 'delete' ? '' : 'none';
- document.getElementById('ram-gs-mode-row').style.display = cmd === 'get-status' ? '' : 'none';
- document.getElementById('ram-gs-p2-row').style.display = cmd === 'get-status' ? '' : 'none';
- document.getElementById('ram-tag-row').style.display = cmd === 'get-data' ? '' : 'none';
- document.getElementById('ram-ss-mode-row').style.display = cmd === 'set-status' ? '' : 'none';
- document.getElementById('ram-ss-state-row').style.display = cmd === 'set-status' ? '' : 'none';
- updateRamToolkit();
-}
-
-function updateRamPriv() {
- let b1 = 0, b2 = 0, b3 = 0;
- document.querySelectorAll('.ram-priv-b1:checked').forEach(cb => b1 |= parseInt(cb.value, 16));
- document.querySelectorAll('.ram-priv-b2:checked').forEach(cb => b2 |= parseInt(cb.value, 16));
- document.querySelectorAll('.ram-priv-b3:checked').forEach(cb => b3 |= parseInt(cb.value, 16));
- let hex = b1.toString(16).padStart(2, '0').toUpperCase();
- hex += b2.toString(16).padStart(2, '0').toUpperCase();
- if (b3) hex += b3.toString(16).padStart(2, '0').toUpperCase();
- document.getElementById('ram-priv').value = hex;
-}
-
-function updateRamToolkit() {
- const checked = document.getElementById('ram-toolkit-enable').checked;
- document.getElementById('ram-toolkit-body').style.display = checked ? '' : 'none';
- updateRamTkMode();
-}
-function updateRamTkMode() {
- const isSim = document.getElementById('ram-tk-mode').value === 'ca';
- document.getElementById('ram-tk-ad-row').style.display = isSim ? '' : 'none';
- document.getElementById('ram-tk-services-row').style.display = isSim ? 'none' : '';
-}
-document.getElementById('ram-tk-mode').addEventListener('change', updateRamTkMode);
-
function updateRcPriv() {
let b1 = 0, b2 = 0, b3 = 0;
document.querySelectorAll('.rc-priv-b1:checked').forEach(cb => b1 |= parseInt(cb.value, 16));
@@ -1733,257 +1087,660 @@ function buildSelect(cla, p1, p2, fid) {
return buildApdu(cla, 0xA4, p1, p2, 0x02, data);
}
+function escHtml(s) { return String(s).replace(/&/g,'&').replace(//g,'>').replace(/"/g,'"'); }
-const OPS = {
- 'select': {ins: 0xA4},
- 'read-record': {ins: 0xB2},
- 'read-binary': {ins: 0xB0},
- 'update-record': {ins: 0xDC},
- 'update-binary': {ins: 0xD6},
- 'erase-binary': {ins: 0x0E},
- 'activate-file': {ins: 0x44},
- 'deactivate-file': {ins: 0x04},
- 'verify': {ins: 0x20},
- 'change': {ins: 0x24},
- 'disable': {ins: 0x26},
- 'enable': {ins: 0x28},
- 'unblock': {ins: 0x2C},
-};
+// ===== Chain Builder =====
+// Row-based APDU chain builder for SIM RFM, USIM RFM, and RAM/GP sub-tabs.
+// Each chain has a list of rows; each row = one APDU command with its own fields.
+// Chain preview = concatenation of all row hexes, with Le stripping when
+// a Case 4 command is followed by GET RESPONSE (ETSI TS 102 226 compact).
+const _chains = {};
-function getSelValue(mode) {
- const method = document.getElementById(mode + '-sel-method').value;
- if (method === 'fid') {
- return {type: 'fid', value: (document.getElementById(mode + '-sel-fid').value || '').replace(/[^0-9a-fA-F]/g, '')};
- } else if (method === 'path') {
- return {type: 'path', value: (document.getElementById(mode + '-sel-path').value || '').replace(/[^0-9a-fA-F]/g, '')};
- } else if (method === 'dfname') {
- return {type: 'dfname', value: (document.getElementById(mode + '-sel-dfname').value || '').replace(/[^0-9a-fA-F]/g, '')};
- } else if (method === 'chain') {
- return {type: 'chain', value: (document.getElementById(mode + '-sel-chain').value || '').trim()};
- }
- return null;
+const CHAIN_CMDS_SIM = [
+ {value:'select',label:'SELECT'},{value:'read-record',label:'READ RECORD'},
+ {value:'read-binary',label:'READ BINARY'},{value:'update-record',label:'UPDATE RECORD'},
+ {value:'update-binary',label:'UPDATE BINARY'},{value:'erase-binary',label:'ERASE BINARY'},
+ {value:'activate-file',label:'ACTIVATE FILE'},{value:'deactivate-file',label:'DEACTIVATE FILE'},
+ {value:'verify',label:'VERIFY PIN'},{value:'change',label:'CHANGE PIN'},
+ {value:'disable',label:'DISABLE PIN'},{value:'enable',label:'ENABLE PIN'},
+ {value:'unblock',label:'UNBLOCK PIN'},{value:'get-response',label:'GET RESPONSE'},
+];
+const CHAIN_CMDS_USIM = [
+ {value:'select',label:'SELECT'},{value:'read-record',label:'READ RECORD'},
+ {value:'read-binary',label:'READ BINARY'},{value:'update-record',label:'UPDATE RECORD'},
+ {value:'update-binary',label:'UPDATE BINARY'},
+ {value:'activate-file',label:'ACTIVATE FILE'},{value:'deactivate-file',label:'DEACTIVATE FILE'},
+ {value:'verify',label:'VERIFY PIN'},{value:'change',label:'CHANGE PIN'},
+ {value:'disable',label:'DISABLE PIN'},{value:'enable',label:'ENABLE PIN'},
+ {value:'unblock',label:'UNBLOCK PIN'},{value:'get-response',label:'GET RESPONSE'},
+];
+const CHAIN_CMDS_RAM = [
+ {value:'install-load',label:'INSTALL [for load]'},
+ {value:'install-install',label:'INSTALL [for install]'},
+ {value:'install-make-sel',label:'INSTALL [for make selectable]'},
+ {value:'install-reg-update',label:'INSTALL [for registry update]'},
+ {value:'install-extradition',label:'INSTALL [for extradition]'},
+ {value:'load',label:'LOAD'},{value:'delete',label:'DELETE'},
+ {value:'get-status',label:'GET STATUS'},{value:'get-data',label:'GET DATA'},
+ {value:'store-data',label:'STORE DATA'},{value:'set-status',label:'SET STATUS'},
+ {value:'ext-auth',label:'EXTERNAL AUTHENTICATE'},
+ {value:'int-auth',label:'INTERNAL AUTHENTICATE'},
+];
+
+function chainInit(id) { if (!_chains[id]) _chains[id] = {rows: []}; }
+function chainAddRow(id, cmd) { chainInit(id); _chains[id].rows.push({cmd: cmd, fields: {}}); chainRender(id); }
+function chainDeleteRow(id, idx) { chainInit(id); _chains[id].rows.splice(idx, 1); chainRender(id); }
+function chainUpdateCmd(id, idx, cmd) { chainInit(id); _chains[id].rows[idx].cmd = cmd; _chains[id].rows[idx].fields = {}; chainRender(id); }
+function chainUpdateField(id, idx, field, value) {
+ chainInit(id);
+ _chains[id].rows[idx].fields[field] = value;
+ const h = document.getElementById(id + '-row-' + idx + '-hex');
+ if (h) h.value = chainBuildRowHex(id, idx);
+ const p = document.getElementById(id + '-preview');
+ if (p) p.value = chainBuildHex(id);
+ const b = document.getElementById(id + '-pack-btn');
+ if (b) b.disabled = !chainBuildHex(id);
}
-function buildSelectApdu(mode, preset, selInfo) {
- const isSim = preset.cla === 0xA0;
- if (!selInfo || !selInfo.value) return '';
- // USIM response control: FCP request (P2=04 + Le) vs silent hop (P2=0C, no Le)
- const silent = !isSim && !!document.getElementById('usim-sel-silent').checked;
- const p2 = silent ? 0x0C : preset.selP2;
- const le = (!isSim && !silent) ? '00' : null;
- if (selInfo.type === 'fid') {
- if (selInfo.value.length !== 4) return '';
- let apdu = buildSelect(preset.cla, preset.selP1, p2, selInfo.value);
- if (le) apdu += le;
- return apdu;
- } else if (selInfo.type === 'path') {
- if (selInfo.value.length < 2 || selInfo.value.length % 2 !== 0) return '';
- const p3 = selInfo.value.length / 2;
- let p1 = isSim ? 0x08 : (document.querySelector('input[name="usim-sel-base"]:checked')?.value === 'df' ? 0x09 : 0x08);
- let apdu = buildApdu(preset.cla, 0xA4, p1, p2, p3, selInfo.value);
- if (le) apdu += le;
- return apdu;
- } else if (selInfo.type === 'dfname') {
- if (selInfo.value.length < 2) return '';
- const p3 = selInfo.value.length / 2;
- let apdu = buildApdu(preset.cla, 0xA4, 0x04, p2, p3, selInfo.value);
- if (le) apdu += le;
- return apdu;
- } else if (selInfo.type === 'chain') {
- const tokens = selInfo.value.split(',').map(t => t.trim()).filter(t => t.length > 0);
- let out = '';
- for (const tok of tokens) {
- // 'C0' or 'C0:NN' - GET RESPONSE hop (Le defaults to '00')
- const gr = /^c0(?::([0-9a-fA-F]{1,2}))?$/i.exec(tok);
- if (gr) {
- const leByte = parseInt(gr[1] || '00', 16);
- out += buildApdu(preset.cla, 0xC0, 0x00, 0x00, leByte, '');
- continue;
- }
- const fid = tok.replace(/[^0-9a-fA-F]/g, '');
- if (fid.length !== 4) continue;
- let apdu = buildSelect(preset.cla, preset.selP1, p2, fid);
- if (le) apdu += le;
- out += apdu;
+function chainRender(chainId) {
+ chainInit(chainId);
+ const container = document.getElementById(chainId + '-rows');
+ const preview = document.getElementById(chainId + '-preview');
+ const packBtn = document.getElementById(chainId + '-pack-btn');
+ if (!container) return;
+ const rows = _chains[chainId].rows;
+ container.innerHTML = '';
+ rows.forEach(function(row, idx) {
+ const div = document.createElement('div');
+ div.className = 'mb-3 p-3 border border-gray-200 dark:border-slate-600 rounded';
+ div.innerHTML = chainRenderRow(chainId, idx, row);
+ container.appendChild(div);
+ });
+ const hex = chainBuildHex(chainId);
+ if (preview) preview.value = hex;
+ if (packBtn) packBtn.disabled = !hex;
+}
+
+function chainRenderRow(chainId, idx, row) {
+ const isRam = chainId === 'chain-ram';
+ const cmds = isRam ? CHAIN_CMDS_RAM : (chainId === 'chain-usim' ? CHAIN_CMDS_USIM : CHAIN_CMDS_SIM);
+ const cmdOpts = cmds.map(function(c) {
+ return '
';
+ }).join('');
+ const fieldsHtml = chainRowFieldsHtml(chainId, idx, row);
+ const hex = chainBuildRowHex(chainId, idx);
+ return '
' +
+ '' +
+ '' +
+ '' +
+ '
' +
+ '
' + fieldsHtml + '
';
+}
+
+function chainRowFieldsHtml(chainId, idx, row) {
+ var f = row.fields;
+ function uf(field) { return "chainUpdateField('" + chainId + "'," + idx + ",'" + field + "',this.value)"; }
+ if (chainId === 'chain-sim' || chainId === 'chain-usim') return chainSimFieldsHtml(chainId, idx, f, uf);
+ return chainRamFieldsHtml(idx, row, f, uf);
+}
+
+// ===== SIM/USIM field rendering =====
+function chainSimFieldsHtml(chainId, idx, f, uf) {
+ var cmd = (_chains[chainId] && _chains[chainId].rows[idx]) ? _chains[chainId].rows[idx].cmd : '';
+ if (cmd === 'select') {
+ var method = f.method || 'fid';
+ var silent = chainId === 'chain-usim' && f.silent;
+ var html = '
' +
+ '' +
+ '
';
+ if (chainId === 'chain-usim') {
+ html += '
';
}
- return out;
+ html += '
';
+ if (method === 'fid') {
+ html += '
' +
+ '
';
+ }
+ if (method === 'path') {
+ html += '
';
+ }
+ if (method === 'dfname') {
+ html += '
' +
+ '
';
+ }
+ if (method === 'chain') {
+ html += '
' +
+ '
';
+ }
+ return html;
+ }
+ if (cmd === 'read-record') {
+ var rm = f.recMode || '04';
+ return '
' +
+ '
' +
+ '
' +
+ '
' +
+ '
' +
+ '
' +
+ '
';
+ }
+ if (cmd === 'read-binary') {
+ return '
';
+ }
+ if (cmd === 'update-record') {
+ var rm2 = f.recMode || '04';
+ return '
' +
+ '
' +
+ '
' +
+ '
' +
+ '
' +
+ '
' +
+ '
';
+ }
+ if (cmd === 'update-binary') {
+ return '
' +
+ '
' +
+ '
' +
+ '
';
+ }
+ if (cmd === 'erase-binary') {
+ return '
' +
+ '
';
+ }
+ if (cmd === 'activate-file' || cmd === 'deactivate-file') {
+ var target = f.target || 'current';
+ var html2 = '
' +
+ '' +
+ '
';
+ if (target !== 'current') {
+ html2 += '
' +
+ '
';
+ }
+ html2 += '
';
+ return html2;
+ }
+ if (cmd === 'verify' || cmd === 'disable' || cmd === 'enable') {
+ var pin = f.pin || '01';
+ return '
' +
+ '
' +
+ '
' +
+ '
' +
+ '
';
+ }
+ if (cmd === 'change' || cmd === 'unblock') {
+ var pin2 = f.pin || '01';
+ return '
' +
+ '
' +
+ '
' +
+ '
' +
+ '
' +
+ '
' +
+ '
';
+ }
+ if (cmd === 'get-response') {
+ return '
No parameters — CLA=C0 INS=C0 P1=00 P2=00
';
}
return '';
}
-function genSimUsim(mode) {
- const preset = mode === 'sim' ? {cla: 0xA0, selP1: 0x00, selP2: 0x00} : {cla: 0x00, selP1: 0x00, selP2: 0x04};
- const cmd = document.getElementById(mode + '-cmd').value;
- const doSelect = document.getElementById(mode + '-select').checked;
- const selInfo = getSelValue(mode);
- const record = parseInt(document.getElementById(mode + '-record').value) || 1;
- const recModeHex = document.getElementById(mode + '-rec-mode').value;
- const recMode = parseInt(recModeHex, 16);
- const offsetHex = (document.getElementById(mode + '-offset').value || '0000').replace(/[^0-9a-fA-F]/g, '').padStart(4, '0').slice(0, 4);
- const offset = parseInt(offsetHex, 16);
- const leHex = (document.getElementById(mode + '-le').value || '00').replace(/[^0-9a-fA-F]/g, '').padStart(2, '0').slice(0, 2);
- const le = parseInt(leHex, 16);
- let dataHex = (document.getElementById(mode + '-data').value || '').replace(/[^0-9a-fA-F]/g, '');
- const recSize = parseInt(document.getElementById(mode + '-recsize').value) || 0;
- const pin = document.getElementById(mode + '-pin').value;
- const override = document.getElementById(mode + '-override').checked;
- const overrideP1 = override ? parseInt((document.getElementById(mode + '-p1').value || '00').replace(/[^0-9a-fA-F]/g, '').padStart(2, '0').slice(0, 2), 16) : null;
- const overrideP2 = override ? parseInt((document.getElementById(mode + '-p2').value || '00').replace(/[^0-9a-fA-F]/g, '').padStart(2, '0').slice(0, 2), 16) : null;
-
- const resultEl = document.getElementById(mode + '-result');
-
- if (recSize > 0 && dataHex.length > 0) {
- const targetBytes = recSize * 2;
- if (dataHex.length > targetBytes) dataHex = dataHex.slice(0, targetBytes);
- else dataHex = dataHex.padEnd(targetBytes, 'F');
+// ===== RAM/GP field rendering =====
+function chainRamFieldsHtml(idx, row, f, uf) {
+ var cmd = row.cmd;
+ if (cmd === 'install-load') {
+ return '
' +
+ '
' +
+ '
';
}
-
- if (cmd === 'select') {
- const selectApdu = buildSelectApdu(mode, preset, selInfo);
- if (!selectApdu) { resultEl.value = 'Error: specify SELECT parameters'; return; }
- resultEl.value = selectApdu;
- document.getElementById(mode + '-pack-btn').disabled = false;
- return;
+ if (cmd === 'install-install') {
+ var html = '
';
+ // Privileges
+ html += chainRamPrivHtml(idx, f, uf);
+ // Toolkit
+ html += chainRamToolkitHtml(idx, f, uf);
+ return html;
}
-
- const op = OPS[cmd];
- if (!op) { resultEl.value = 'Error: unknown command'; return; }
-
- let p1, p2, p3, data = null;
- if (cmd === 'read-record') {
- p1 = (recMode === 0x02 || recMode === 0x03) ? 0x00 : record; p2 = recMode; p3 = le;
- } else if (cmd === 'read-binary') {
- p1 = (offset >> 8) & 0xFF; p2 = offset & 0xFF; p3 = le;
- } else if (cmd === 'update-record') {
- if (!dataHex) { resultEl.value = 'Error: specify data'; return; }
- p1 = (recMode === 0x02 || recMode === 0x03) ? 0x00 : record; p2 = recMode; p3 = dataHex.length / 2; data = dataHex;
- } else if (cmd === 'update-binary') {
- if (!dataHex) { resultEl.value = 'Error: specify data'; return; }
- p1 = (offset >> 8) & 0xFF; p2 = offset & 0xFF; p3 = dataHex.length / 2; data = dataHex;
- } else if (cmd === 'erase-binary') {
- p1 = (offset >> 8) & 0xFF; p2 = offset & 0xFF; p3 = 0x00;
- } else if (cmd === 'activate-file' || cmd === 'deactivate-file') {
- const target = document.getElementById(mode + '-act-target').value;
- if (target === 'current') {
- p1 = 0x00; p2 = 0x00; p3 = null;
- } else {
- const fileHex = (document.getElementById(mode + '-act-file').value || '').replace(/[^0-9a-fA-F]/g, '');
- if (!fileHex || fileHex.length % 2 !== 0) { resultEl.value = 'Error: specify FID or path (even hex length)'; return; }
- p1 = target === 'fid' ? 0x00 : (target === 'mfpath' ? 0x08 : 0x09);
- p2 = 0x00;
- p3 = fileHex.length / 2;
- data = fileHex;
+ if (cmd === 'install-make-sel') {
+ return '
' +
+ '
' +
+ chainRamPrivHtml(idx, f, uf);
+ }
+ if (cmd === 'install-reg-update') {
+ return '
' +
+ chainRamPrivHtml(idx, f, uf);
+ }
+ if (cmd === 'install-extradition') {
+ return '
';
+ }
+ if (cmd === 'load') {
+ return '
' +
+ '
' +
+ '
' +
+ '
';
+ }
+ if (cmd === 'delete') {
+ var dm = f.delMode || '00';
+ return '
' +
+ '
' +
+ '
' +
+ '
';
+ }
+ if (cmd === 'get-status') {
+ var gm = f.gsMode || '80';
+ var gp = f.gsP2 || '00';
+ return '
' +
+ '
' +
+ '
' +
+ '
' +
+ '
';
+ }
+ if (cmd === 'get-data') {
+ var tag = f.tag || '5F50';
+ return '
' +
+ '' +
+ '
';
+ }
+ if (cmd === 'store-data') {
+ var enc = f.enc || '00';
+ return '
' +
+ '
' +
+ '
' +
+ '
' +
+ '
' +
+ '
' +
+ '
';
+ }
+ if (cmd === 'set-status') {
+ var sm = f.ssMode || '80';
+ var ss = f.ssState || '00';
+ var html3 = '
' +
+ '
' +
+ '
' +
+ '
' +
+ '
';
+ if (sm !== '80') {
+ html3 += '
' +
+ '
';
}
- } else if (cmd === 'verify' || cmd === 'disable' || cmd === 'enable') {
- const pinVal = document.getElementById(mode + '-pin-val').value || '';
- if (!pinVal) { resultEl.value = 'Error: specify PIN value'; return; }
- const pinHex = Array.from(pinVal).map(c => c.charCodeAt(0).toString(16).padStart(2, '0')).join('').padEnd(16, 'F').slice(0, 16);
- p1 = 0x00; p2 = parseInt(pin, 16); p3 = 0x08; data = pinHex;
- } else if (cmd === 'change' || cmd === 'unblock') {
- const oldPin = document.getElementById(mode + '-pin-old').value || '';
- const newPin = document.getElementById(mode + '-pin-new').value || '';
- if (!oldPin || !newPin) { resultEl.value = 'Error: specify old and new PIN'; return; }
- const oldHex = Array.from(oldPin).map(c => c.charCodeAt(0).toString(16).padStart(2, '0')).join('').padEnd(16, 'F').slice(0, 16);
- const newHex = Array.from(newPin).map(c => c.charCodeAt(0).toString(16).padStart(2, '0')).join('').padEnd(16, 'F').slice(0, 16);
- p1 = 0x00; p2 = parseInt(pin, 16); p3 = 0x10; data = oldHex + newHex;
+ return html3;
}
-
- if (override) {
- if (overrideP1 !== null) p1 = overrideP1;
- if (overrideP2 !== null) p2 = overrideP2;
+ if (cmd === 'ext-auth') {
+ var ap = f.authP1 || '00';
+ return '
' +
+ '
' +
+ '
' +
+ '
';
}
-
- let apdu = '';
- if (doSelect) {
- const selectApdu = buildSelectApdu(mode, preset, selInfo);
- if (selectApdu) {
- // full CLA on every command - scripted secured-packet streams must be
- // self-delimiting; implied-CLA fragments desync the card's parser
- apdu = selectApdu + buildApdu(preset.cla, op.ins, p1, p2, p3, data);
- } else {
- apdu = buildApdu(preset.cla, op.ins, p1, p2, p3, data);
- }
- } else {
- apdu = buildApdu(preset.cla, op.ins, p1, p2, p3, data);
+ if (cmd === 'int-auth') {
+ return '
' +
+ '
';
}
-
- resultEl.value = apdu;
- document.getElementById(mode + '-pack-btn').disabled = false;
+ return '';
}
-function genRam() {
- const cmd = document.getElementById('ram-cmd').value;
- const aid = (document.getElementById('ram-aid').value || '').replace(/[^0-9a-fA-F]/g, '');
- const sdAid = (document.getElementById('ram-sdaid').value || '').replace(/[^0-9a-fA-F]/g, '');
- const elfAid = (document.getElementById('ram-elfaid').value || '').replace(/[^0-9a-fA-F]/g, '');
- const modAid = (document.getElementById('ram-modaid').value || '').replace(/[^0-9a-fA-F]/g, '');
- const loadData = (document.getElementById('ram-load-data').value || '').replace(/[^0-9a-fA-F]/g, '');
- const ramData = (document.getElementById('ram-data').value || '').replace(/[^0-9a-fA-F]/g, '');
- const blockNum = parseInt(document.getElementById('ram-block').value) || 0;
- const enc = document.getElementById('ram-enc').value;
- const delMode = document.getElementById('ram-del-mode').value;
- const gsMode = document.getElementById('ram-gs-mode').value;
- const tagHex = (document.getElementById('ram-tag').value || '5F50').replace(/[^0-9a-fA-F]/g, '').padStart(4, '0').slice(0, 4);
- const ssMode = parseInt(document.getElementById('ram-ss-mode').value, 16);
- const ssState = parseInt(document.getElementById('ram-ss-state').value, 16);
+// ===== RAM/GP privileges sub-form (per-row) =====
+function chainRamPrivHtml(idx, f, uf) {
+ var priv = f.priv || '00';
+ var b1 = parseInt(priv.substring(0, 2), 16) || 0;
+ var b2 = priv.length >= 4 ? parseInt(priv.substring(2, 4), 16) : 0;
+ var b3 = priv.length >= 6 ? parseInt(priv.substring(4, 6), 16) : 0;
+ var html = '
' +
+ '' +
+ '' +
+ '
';
+ var CHECKS = [
+ ['Security Domain','80','b1'],['Trusted Path','80','b2'],
+ ['DAP Verification','40','b1'],['Authorized Management','40','b2'],
+ ['Delegated Management','20','b1'],['Token Verification','20','b2'],
+ ['Card Lock','10','b1'],['Global Delete','10','b2'],
+ ['Card Terminate','08','b1'],['Global Lock','08','b2'],
+ ['Card Reset','04','b1'],['Global Registry','04','b2'],
+ ['CVM Management','02','b1'],['Final Application','02','b2'],
+ ['Mandated DAP Verification','01','b1'],['',0,''],
+ ['Receipt Generation','80','b3'],['','','']
+ ];
+ CHECKS.forEach(function(c) {
+ if (!c[0]) return '';
+ var byteIdx = c[2];
+ var val = parseInt(c[1], 16);
+ var checked = (byteIdx === 'b1' && (b1 & val)) || (byteIdx === 'b2' && (b2 & val)) || (byteIdx === 'b3' && (b3 & val));
+ html += '';
+ });
+ html += '
';
+ return html;
+}
- const resultEl = document.getElementById('ram-apdu-result');
+function chainRamUpdatePriv(idx) {
+ var b1 = 0, b2 = 0, b3 = 0;
+ document.querySelectorAll('.chain-priv-b1-' + idx + ':checked').forEach(function(cb) { b1 |= parseInt(cb.value, 16); });
+ document.querySelectorAll('.chain-priv-b2-' + idx + ':checked').forEach(function(cb) { b2 |= parseInt(cb.value, 16); });
+ document.querySelectorAll('.chain-priv-b3-' + idx + ':checked').forEach(function(cb) { b3 |= parseInt(cb.value, 16); });
+ var hex = b1.toString(16).padStart(2, '0').toUpperCase();
+ hex += b2.toString(16).padStart(2, '0').toUpperCase();
+ if (b3) hex += b3.toString(16).padStart(2, '0').toUpperCase();
+ chainUpdateField('chain-ram', idx, 'priv', hex);
+}
- const INSTALL_P1 = {
- 'install-load': 0x02,
- 'install-install': 0x0C,
- 'install-make-sel': 0x08,
- 'install-reg-update': 0x40,
- 'install-extradition': 0x10,
+// ===== RAM/GP toolkit sub-form (per-row) =====
+function chainRamToolkitHtml(idx, f, uf) {
+ var enabled = f.tkEnabled;
+ var html = '
';
+ if (!enabled) return html;
+ var tkMode = f.tkMode || 'ea';
+ var priority = f.tkPriority || '0';
+ var timers = f.tkTimers || '0';
+ var textLen = f.tkTextlen || '0';
+ var menus = f.tkMenus || '0';
+ var firstPos = f.tkFirstpos || '0';
+ var firstId = f.tkFirstid || '00';
+ var lastPos = f.tkLastpos || '0';
+ var lastId = f.tkLastid || '00';
+ var channels = f.tkChannels || '0';
+ var msl = f.tkMsl || '16';
+ var tar = f.tkTar || 'B00001';
+ var ad = f.tkAd || '';
+ var services = f.tkServices || '0';
+ html += '
' +
+ '
' +
+ '
' +
+ '
';
+ return html;
+}
+
+// ===== SIM/USIM hex builder =====
+function chainSimBuildRowHex(chainId, idx, row) {
+ var f = row.fields;
+ var isSim = chainId === 'chain-sim';
+ var cla = isSim ? 0xA0 : 0x00;
+
+ if (row.cmd === 'select') {
+ var method = f.method || 'fid';
+ var silent = !isSim && f.silent;
+ var p2 = silent ? 0x0C : (isSim ? 0x00 : 0x04);
+ if (method === 'fid') {
+ var fid = (f.fid || '').replace(/[^0-9a-fA-F]/g, '').toUpperCase();
+ if (fid.length !== 4) return '';
+ var apdu = buildApdu(cla, 0xA4, 0x00, p2, 0x02, fid);
+ if (!isSim) apdu += '00';
+ return apdu;
+ }
+ if (method === 'path') {
+ var path = (f.path || '').replace(/[^0-9a-fA-F]/g, '').toUpperCase();
+ if (path.length < 2 || path.length % 2 !== 0) return '';
+ var p1 = (!isSim && f.base === 'df') ? 0x09 : 0x08;
+ var apdu2 = buildApdu(cla, 0xA4, p1, p2, path.length / 2, path);
+ if (!isSim) apdu2 += '00';
+ return apdu2;
+ }
+ if (method === 'dfname') {
+ var aid = (f.dfname || '').replace(/[^0-9a-fA-F]/g, '').toUpperCase();
+ if (aid.length < 2) return '';
+ var apdu3 = buildApdu(cla, 0xA4, 0x04, p2, aid.length / 2, aid);
+ if (!isSim) apdu3 += '00';
+ return apdu3;
+ }
+ if (method === 'chain') {
+ var tokens = (f.chain || '').split(',').map(function(t) { return t.trim(); }).filter(function(t) { return t.length > 0; });
+ var out = '';
+ for (var ti = 0; ti < tokens.length; ti++) {
+ var tok = tokens[ti];
+ var gr = /^c0(?::([0-9a-fA-F]{1,2}))?$/i.exec(tok);
+ if (gr) {
+ var leByte = parseInt(gr[1] || '00', 16);
+ out += buildApdu(cla, 0xC0, 0x00, 0x00, leByte, '');
+ continue;
+ }
+ var fid2 = tok.replace(/[^0-9a-fA-F]/g, '').toUpperCase();
+ if (fid2.length !== 4) continue;
+ var s = buildSelect(cla, 0x00, p2, fid2);
+ if (!isSim) s += '00';
+ out += s;
+ }
+ return out;
+ }
+ return '';
+ }
+ if (row.cmd === 'get-response') {
+ return '00C0000000';
+ }
+ var OPS2 = {
+ 'read-record': {ins: 0xB2}, 'read-binary': {ins: 0xB0},
+ 'update-record': {ins: 0xDC}, 'update-binary': {ins: 0xD6},
+ 'erase-binary': {ins: 0x0E}, 'activate-file': {ins: 0x44},
+ 'deactivate-file': {ins: 0x04}, 'verify': {ins: 0x20},
+ 'change': {ins: 0x24}, 'disable': {ins: 0x26},
+ 'enable': {ins: 0x28}, 'unblock': {ins: 0x2C},
};
-
- function lv(data) {
- if (!data) return '00';
- return berLenStr(data.length / 2) + data;
+ var op = OPS2[row.cmd];
+ if (!op) return '';
+ var p1, p2v, p3, data = null;
+ if (row.cmd === 'read-record') {
+ var rm = parseInt(f.recMode || '04', 16);
+ var rec = parseInt(f.record) || 1;
+ var le = parseInt(f.le || '00', 16);
+ p1 = (rm === 0x02 || rm === 0x03) ? 0x00 : rec;
+ p2v = rm; p3 = le;
+ } else if (row.cmd === 'read-binary') {
+ var off = parseInt((f.offset || '0000').replace(/[^0-9a-fA-F]/g, ''), 16) || 0;
+ var le2 = parseInt(f.le || '00', 16);
+ p1 = (off >> 8) & 0xFF; p2v = off & 0xFF; p3 = le2;
+ } else if (row.cmd === 'update-record') {
+ var rm2 = parseInt(f.recMode || '04', 16);
+ var rec2 = parseInt(f.record) || 1;
+ data = (f.data || '').replace(/[^0-9a-fA-F]/g, '').toUpperCase();
+ if (!data) return '';
+ p1 = (rm2 === 0x02 || rm2 === 0x03) ? 0x00 : rec2;
+ p2v = rm2; p3 = data.length / 2;
+ } else if (row.cmd === 'update-binary') {
+ var off2 = parseInt((f.offset || '0000').replace(/[^0-9a-fA-F]/g, ''), 16) || 0;
+ data = (f.data || '').replace(/[^0-9a-fA-F]/g, '').toUpperCase();
+ if (!data) return '';
+ p1 = (off2 >> 8) & 0xFF; p2v = off2 & 0xFF; p3 = data.length / 2;
+ } else if (row.cmd === 'erase-binary') {
+ var off3 = parseInt((f.offset || '0000').replace(/[^0-9a-fA-F]/g, ''), 16) || 0;
+ p1 = (off3 >> 8) & 0xFF; p2v = off3 & 0xFF; p3 = 0x00;
+ } else if (row.cmd === 'activate-file' || row.cmd === 'deactivate-file') {
+ var target = f.target || 'current';
+ if (target === 'current') { p1 = 0x00; p2v = 0x00; p3 = null; }
+ else {
+ var fileHex = (f.file || '').replace(/[^0-9a-fA-F]/g, '').toUpperCase();
+ if (!fileHex || fileHex.length % 2 !== 0) return '';
+ p1 = target === 'fid' ? 0x00 : (target === 'mfpath' ? 0x08 : 0x09);
+ p2v = 0x00; p3 = fileHex.length / 2; data = fileHex;
+ }
+ } else if (row.cmd === 'verify' || row.cmd === 'disable' || row.cmd === 'enable') {
+ var pin = f.pin || '01';
+ var pinVal = f.pinVal || '';
+ if (!pinVal) return '';
+ var pinHex = Array.from(pinVal).map(function(c) { return c.charCodeAt(0).toString(16).padStart(2, '0'); }).join('').padEnd(16, 'F').slice(0, 16);
+ p1 = 0x00; p2v = parseInt(pin, 16); p3 = 0x08; data = pinHex;
+ } else if (row.cmd === 'change' || row.cmd === 'unblock') {
+ var pin2 = f.pin || '01';
+ var oldPin = f.pinOld || '';
+ var newPin = f.pinNew || '';
+ if (!oldPin || !newPin) return '';
+ var oldHex = Array.from(oldPin).map(function(c) { return c.charCodeAt(0).toString(16).padStart(2, '0'); }).join('').padEnd(16, 'F').slice(0, 16);
+ var newHex = Array.from(newPin).map(function(c) { return c.charCodeAt(0).toString(16).padStart(2, '0'); }).join('').padEnd(16, 'F').slice(0, 16);
+ p1 = 0x00; p2v = parseInt(pin2, 16); p3 = 0x10; data = oldHex + newHex;
+ } else {
+ return '';
}
+ return buildApdu(cla, op.ins, p1, p2v, p3, data);
+}
+// ===== RAM/GP hex builder =====
+function chainRamBuildRowHex(idx, row) {
+ var f = row.fields;
+ var cmd = row.cmd;
+ function lv(data) { if (!data) return '00'; return berLenStr(data.length / 2) + data; }
function computePriv() {
- const privHex = document.getElementById('ram-priv').value;
- const b1 = parseInt(privHex.substr(0, 2), 16) || 0;
- const b2 = privHex.length >= 4 ? parseInt(privHex.substr(2, 2), 16) : 0;
- const b3 = privHex.length >= 6 ? parseInt(privHex.substr(4, 2), 16) : 0;
+ var ph = f.priv || '00';
+ var b1 = parseInt(ph.substring(0, 2), 16) || 0;
+ var b2 = ph.length >= 4 ? parseInt(ph.substring(2, 4), 16) : 0;
+ var b3 = ph.length >= 6 ? parseInt(ph.substring(4, 6), 16) : 0;
if (b2 === 0 && b3 === 0) return b1.toString(16).padStart(2, '0').toUpperCase();
- return b1.toString(16).padStart(2, '0').toUpperCase() + b2.toString(16).padStart(2, '0').toUpperCase() + b3.toString(16).padStart(2, '0').toUpperCase();
+ var r = b1.toString(16).padStart(2, '0').toUpperCase() + b2.toString(16).padStart(2, '0').toUpperCase();
+ if (b3) r += b3.toString(16).padStart(2, '0').toUpperCase();
+ return r;
}
-
- function buildToolkitParams() {
- if (!document.getElementById('ram-toolkit-enable').checked) return '';
- const tkMode = document.getElementById('ram-tk-mode').value;
- const priority = parseInt(document.getElementById('ram-tk-priority').value) || 0;
- const timers = parseInt(document.getElementById('ram-tk-timers').value) || 0;
- const textLen = parseInt(document.getElementById('ram-tk-textlen').value) || 0;
- const menus = parseInt(document.getElementById('ram-tk-menus').value) || 0;
- const firstPos = parseInt(document.getElementById('ram-tk-firstpos').value) || 0;
- const firstId = (document.getElementById('ram-tk-firstid').value || '00').replace(/[^0-9a-fA-F]/g, '').padStart(2, '0').slice(0, 2) || '00';
- const lastPos = parseInt(document.getElementById('ram-tk-lastpos').value) || 0;
- const lastId = (document.getElementById('ram-tk-lastid').value || '00').replace(/[^0-9a-fA-F]/g, '').padStart(2, '0').slice(0, 2) || '00';
- const channels = parseInt(document.getElementById('ram-tk-channels').value) || 0;
- const msl = document.getElementById('ram-tk-msl').value;
- const tar = (document.getElementById('ram-tk-tar').value || '').replace(/[^0-9a-fA-F]/g, '');
- const adRaw = (document.getElementById('ram-tk-ad').value || '').replace(/[^0-9a-fA-F]/g, '');
- const ad = adRaw.padStart(2, '0').slice(0, 2);
- const services = parseInt(document.getElementById('ram-tk-services').value) || 0;
-
- if (tar && tar.length % 6 !== 0) { resultEl.value = 'Error: TAR must be 3-byte multiples'; return null; }
- if (parseInt(firstId, 16) > 0x7F || parseInt(lastId, 16) > 0x7F) { resultEl.value = 'Error: menu IDs must be <= 7F'; return null; }
-
- let menuPairs = '';
- for (let i = 1; i <= menus; i++) {
+ function buildTkParams() {
+ if (!f.tkEnabled) return '';
+ var tkMode = f.tkMode || 'ea';
+ var priority = parseInt(f.tkPriority) || 0;
+ var timers = parseInt(f.tkTimers) || 0;
+ var textLen = parseInt(f.tkTextlen) || 0;
+ var menus = parseInt(f.tkMenus) || 0;
+ var firstPos = parseInt(f.tkFirstpos) || 0;
+ var firstId = (f.tkFirstid || '00').replace(/[^0-9a-fA-F]/g, '').padStart(2, '0').slice(0, 2) || '00';
+ var lastPos = parseInt(f.tkLastpos) || 0;
+ var lastId = (f.tkLastid || '00').replace(/[^0-9a-fA-F]/g, '').padStart(2, '0').slice(0, 2) || '00';
+ var channels = parseInt(f.tkChannels) || 0;
+ var msl = f.tkMsl || '00';
+ var tar = (f.tkTar || '').replace(/[^0-9a-fA-F]/g, '');
+ var ad = (f.tkAd || '').replace(/[^0-9a-fA-F]/g, '').padStart(2, '0').slice(0, 2);
+ var services = parseInt(f.tkServices) || 0;
+ if (tar && tar.length % 6 !== 0) return null;
+ var menuPairs = '';
+ for (var i = 1; i <= menus; i++) {
if (i === 1) menuPairs += firstPos.toString(16).padStart(2, '0') + firstId;
else if (i === menus) menuPairs += lastPos.toString(16).padStart(2, '0') + lastId;
else menuPairs += '0000';
}
-
- const mslField = msl === '00' ? '00' : '0201' + msl;
-
- let tkPayload = '';
+ var mslField = msl === '00' ? '00' : '0201' + msl;
+ var tkPayload = '';
if (tkMode === 'ca') {
- tkPayload = (adRaw ? '01' + ad : '00') +
+ tkPayload = (f.tkAd ? '01' + ad : '00') +
priority.toString(16).padStart(2, '0') +
timers.toString(16).padStart(2, '0') +
textLen.toString(16).padStart(2, '0') +
@@ -1992,7 +1749,7 @@ function genRam() {
channels.toString(16).padStart(2, '0') +
mslField +
(tar ? (tar.length / 2).toString(16).padStart(2, '0') + tar : '00');
- const caTlv = 'CA' + berLenStr(tkPayload.length / 2) + tkPayload;
+ var caTlv = 'CA' + berLenStr(tkPayload.length / 2) + tkPayload;
return 'EF' + berLenStr(caTlv.length / 2) + caTlv;
} else {
tkPayload = priority.toString(16).padStart(2, '0') +
@@ -2004,87 +1761,150 @@ function genRam() {
mslField +
(tar ? (tar.length / 2).toString(16).padStart(2, '0') + tar : '00') +
services.toString(16).padStart(2, '0');
- const innerTlv = '80' + berLenStr(tkPayload.length / 2) + tkPayload;
+ var innerTlv = '80' + berLenStr(tkPayload.length / 2) + tkPayload;
return 'EA' + berLenStr(innerTlv.length / 2) + innerTlv;
}
}
-
- let apdu = '';
+ var INSTALL_P1 = {
+ 'install-load': 0x02, 'install-install': 0x0C,
+ 'install-make-sel': 0x08, 'install-reg-update': 0x40,
+ 'install-extradition': 0x10,
+ };
+ var aid = (f.aid || '').replace(/[^0-9a-fA-F]/g, '').toUpperCase();
+ var sdAid = (f.sdAid || '').replace(/[^0-9a-fA-F]/g, '').toUpperCase();
if (cmd === 'install-load') {
- if (!aid) { resultEl.value = 'Error: specify Load File AID'; return; }
- let dataField = lv(aid) + lv(sdAid) + '00' + (loadData ? lv(loadData) : '00') + '00';
- const dataLen = berLenStr(dataField.length / 2);
- apdu = buildApdu(0x80, 0xE6, INSTALL_P1[cmd], 0x00, parseInt(dataLen, 16), dataField) + '00';
- } else if (cmd === 'install-install') {
- if (!aid) { resultEl.value = 'Error: specify AID'; return; }
- const privBytes = computePriv();
- let installParams = 'C900';
- const tkParams = buildToolkitParams();
- if (tkParams === null) return;
- if (tkParams) installParams += tkParams;
- let dataField = lv(elfAid) + lv(modAid) + lv(aid) + lv(privBytes) + lv(installParams) + '00';
- const dataLen = berLenStr(dataField.length / 2);
- apdu = buildApdu(0x80, 0xE6, INSTALL_P1[cmd], 0x00, parseInt(dataLen, 16), dataField) + '00';
- } else if (cmd === 'install-make-sel') {
- if (!aid) { resultEl.value = 'Error: specify AID'; return; }
- const privBytes = computePriv();
- let dataField = '00' + '00' + lv(aid) + lv(privBytes) + '00' + '00';
- const dataLen = berLenStr(dataField.length / 2);
- apdu = buildApdu(0x80, 0xE6, INSTALL_P1[cmd], 0x00, parseInt(dataLen, 16), dataField) + '00';
- } else if (cmd === 'install-reg-update') {
- if (!aid) { resultEl.value = 'Error: specify AID'; return; }
- const privBytes = computePriv();
- let dataField = lv(sdAid) + '00' + lv(aid) + lv(privBytes) + '00' + '00';
- const dataLen = berLenStr(dataField.length / 2);
- apdu = buildApdu(0x80, 0xE6, INSTALL_P1[cmd], 0x00, parseInt(dataLen, 16), dataField) + '00';
- } else if (cmd === 'install-extradition') {
- if (!sdAid || sdAid.length < 10 || sdAid.length > 20) { resultEl.value = 'Error: specify SD AID (5-10 bytes)'; return; }
- if (!aid) { resultEl.value = 'Error: specify AID'; return; }
- let dataField = lv(sdAid) + '00' + lv(aid) + '00' + '00' + '00';
- const dataLen = berLenStr(dataField.length / 2);
- apdu = buildApdu(0x80, 0xE6, INSTALL_P1[cmd], 0x00, parseInt(dataLen, 16), dataField) + '00';
- } else if (cmd === 'load') {
- if (!ramData) { resultEl.value = 'Error: specify data'; return; }
- const dataLen = berLenStr(ramData.length / 2);
- apdu = buildApdu(0x80, 0xE8, 0x80, blockNum, parseInt(dataLen, 16), ramData) + '00';
- } else if (cmd === 'delete') {
- if (!aid) { resultEl.value = 'Error: specify AID'; return; }
- const dataField = '4F' + berLenStr(aid.length / 2) + aid;
- const dataLen = berLenStr(dataField.length / 2);
- apdu = buildApdu(0x80, 0xE4, 0x00, parseInt(delMode, 16), parseInt(dataLen, 16), dataField) + '00';
- } else if (cmd === 'get-status') {
- const gsP2 = document.getElementById('ram-gs-p2').value;
- apdu = buildApdu(0x80, 0xF2, parseInt(gsMode, 16), parseInt(gsP2, 16), 0x02, '4F00') + '00';
- } else if (cmd === 'get-data') {
- const tag = parseInt(tagHex, 16);
- if (tagHex === '2F00' || tagHex === '5031') {
- apdu = buildApdu(0x80, 0xCA, (tag >> 8) & 0xFF, tag & 0xFF, 0x02, '5C00') + '00';
- } else {
- apdu = buildApdu(0x80, 0xCA, (tag >> 8) & 0xFF, tag & 0xFF, 0x00, null) + '00';
- }
- } else if (cmd === 'store-data') {
- if (!ramData) { resultEl.value = 'Error: specify data'; return; }
- const dataLen = berLenStr(ramData.length / 2);
- apdu = buildApdu(0x80, 0xE2, parseInt(enc, 16), blockNum, parseInt(dataLen, 16), ramData);
- } else if (cmd === 'set-status') {
- if (ssMode === 0x80) {
- apdu = buildApdu(0x80, 0xF0, 0x80, ssState, 0x00, null);
- } else {
- if (!aid) { resultEl.value = 'Error: specify AID'; return; }
- apdu = buildApdu(0x80, 0xF0, ssMode, ssState, aid.length / 2, aid);
- }
- } else if (cmd === 'ext-auth') {
- if (!ramData || ramData.length !== 32) { resultEl.value = 'Error: specify 16-byte data (host cryptogram + MAC)'; return; }
- const authP1 = parseInt(document.getElementById('ram-authp1').value, 16);
- apdu = buildApdu(0x84, 0x82, authP1, 0x00, 0x10, ramData);
- } else if (cmd === 'int-auth') {
- if (!ramData) { resultEl.value = 'Error: specify data (challenge)'; return; }
- const dataLen = berLenStr(ramData.length / 2);
- apdu = buildApdu(0x00, 0x88, 0x00, 0x00, parseInt(dataLen, 16), ramData) + '00';
+ var loadFileAid = (f.loadFileAid || '').replace(/[^0-9a-fA-F]/g, '').toUpperCase();
+ var loadData = (f.loadData || '').replace(/[^0-9a-fA-F]/g, '').toUpperCase();
+ if (!loadFileAid) return '';
+ var dataField = lv(loadFileAid) + lv(sdAid) + '00' + (loadData ? lv(loadData) : '00') + '00';
+ var dataLen = berLenStr(dataField.length / 2);
+ return buildApdu(0x80, 0xE6, INSTALL_P1[cmd], 0x00, parseInt(dataLen, 16), dataField) + '00';
}
+ if (cmd === 'install-install') {
+ var elfAid = (f.elfAid || '').replace(/[^0-9a-fA-F]/g, '').toUpperCase();
+ var modAid = (f.modAid || '').replace(/[^0-9a-fA-F]/g, '').toUpperCase();
+ if (!aid) return '';
+ var privBytes = computePriv();
+ var installParams = 'C900';
+ var tkParams = buildTkParams();
+ if (tkParams === null) return '';
+ if (tkParams) installParams += tkParams;
+ var df2 = lv(elfAid) + lv(modAid) + lv(aid) + lv(privBytes) + lv(installParams) + '00';
+ var dl2 = berLenStr(df2.length / 2);
+ return buildApdu(0x80, 0xE6, INSTALL_P1[cmd], 0x00, parseInt(dl2, 16), df2) + '00';
+ }
+ if (cmd === 'install-make-sel') {
+ if (!aid) return '';
+ var privBytes2 = computePriv();
+ var df3 = '00' + '00' + lv(aid) + lv(privBytes2) + '00' + '00';
+ var dl3 = berLenStr(df3.length / 2);
+ return buildApdu(0x80, 0xE6, INSTALL_P1[cmd], 0x00, parseInt(dl3, 16), df3) + '00';
+ }
+ if (cmd === 'install-reg-update') {
+ if (!aid) return '';
+ var privBytes3 = computePriv();
+ var df4 = lv(sdAid) + '00' + lv(aid) + lv(privBytes3) + '00' + '00';
+ var dl4 = berLenStr(df4.length / 2);
+ return buildApdu(0x80, 0xE6, INSTALL_P1[cmd], 0x00, parseInt(dl4, 16), df4) + '00';
+ }
+ if (cmd === 'install-extradition') {
+ if (!sdAid || sdAid.length < 10 || sdAid.length > 20) return '';
+ if (!aid) return '';
+ var df5 = lv(sdAid) + '00' + lv(aid) + '00' + '00' + '00';
+ var dl5 = berLenStr(df5.length / 2);
+ return buildApdu(0x80, 0xE6, INSTALL_P1[cmd], 0x00, parseInt(dl5, 16), df5) + '00';
+ }
+ if (cmd === 'load') {
+ var ramData = (f.data || '').replace(/[^0-9a-fA-F]/g, '').toUpperCase();
+ var blockNum = parseInt(f.block) || 0;
+ if (!ramData) return '';
+ var dl6 = berLenStr(ramData.length / 2);
+ return buildApdu(0x80, 0xE8, 0x80, blockNum, parseInt(dl6, 16), ramData) + '00';
+ }
+ if (cmd === 'delete') {
+ if (!aid) return '';
+ var delMode = f.delMode || '00';
+ var df7 = '4F' + berLenStr(aid.length / 2) + aid;
+ var dl7 = berLenStr(df7.length / 2);
+ return buildApdu(0x80, 0xE4, 0x00, parseInt(delMode, 16), parseInt(dl7, 16), df7) + '00';
+ }
+ if (cmd === 'get-status') {
+ var gsMode = f.gsMode || '80';
+ var gsP2 = f.gsP2 || '00';
+ return buildApdu(0x80, 0xF2, parseInt(gsMode, 16), parseInt(gsP2, 16), 0x02, '4F00') + '00';
+ }
+ if (cmd === 'get-data') {
+ var tagHex = (f.tag || '5F50').replace(/[^0-9a-fA-F]/g, '').padStart(4, '0').slice(0, 4);
+ var tag = parseInt(tagHex, 16);
+ if (tagHex === '2F00' || tagHex === '5031') {
+ return buildApdu(0x80, 0xCA, (tag >> 8) & 0xFF, tag & 0xFF, 0x02, '5C00') + '00';
+ }
+ return buildApdu(0x80, 0xCA, (tag >> 8) & 0xFF, tag & 0xFF, 0x00, null) + '00';
+ }
+ if (cmd === 'store-data') {
+ var ramData2 = (f.data || '').replace(/[^0-9a-fA-F]/g, '').toUpperCase();
+ var enc = f.enc || '00';
+ var blockNum2 = parseInt(f.block) || 0;
+ if (!ramData2) return '';
+ var dl8 = berLenStr(ramData2.length / 2);
+ return buildApdu(0x80, 0xE2, parseInt(enc, 16), blockNum2, parseInt(dl8, 16), ramData2);
+ }
+ if (cmd === 'set-status') {
+ var ssMode = f.ssMode || '80';
+ var ssState = f.ssState || '00';
+ if (ssMode === '80') {
+ return buildApdu(0x80, 0xF0, 0x80, parseInt(ssState, 16), 0x00, null);
+ }
+ if (!aid) return '';
+ return buildApdu(0x80, 0xF0, parseInt(ssMode, 16), parseInt(ssState, 16), aid.length / 2, aid);
+ }
+ if (cmd === 'ext-auth') {
+ var ramData3 = (f.data || '').replace(/[^0-9a-fA-F]/g, '').toUpperCase();
+ var authP1 = f.authP1 || '00';
+ if (!ramData3 || ramData3.length !== 32) return '';
+ return buildApdu(0x84, 0x82, parseInt(authP1, 16), 0x00, 0x10, ramData3);
+ }
+ if (cmd === 'int-auth') {
+ var ramData4 = (f.data || '').replace(/[^0-9a-fA-F]/g, '').toUpperCase();
+ if (!ramData4) return '';
+ var dl9 = berLenStr(ramData4.length / 2);
+ return buildApdu(0x00, 0x88, 0x00, 0x00, parseInt(dl9, 16), ramData4) + '00';
+ }
+ return '';
+}
- resultEl.value = apdu || 'Error: unknown command';
- if (apdu) document.getElementById('ram-pack-btn').disabled = false;
+// ===== Chain hex builder (with Le stripping) =====
+function chainBuildRowHex(chainId, idx) {
+ chainInit(chainId);
+ var row = _chains[chainId].rows[idx];
+ if (!row) return '';
+ if (chainId === 'chain-sim' || chainId === 'chain-usim') {
+ return chainSimBuildRowHex(chainId, idx, row);
+ }
+ return chainRamBuildRowHex(idx, row);
+}
+
+function chainIsCase4(cmd, chainId) {
+ if (cmd === 'select') return chainId === 'chain-usim';
+ if (cmd === 'get-status' || cmd === 'get-data' || cmd === 'store-data' || cmd === 'int-auth') return true;
+ return false;
+}
+
+function chainBuildHex(chainId) {
+ chainInit(chainId);
+ var rows = _chains[chainId].rows;
+ if (!rows.length) return '';
+ var parts = [];
+ for (var i = 0; i < rows.length; i++) {
+ var rowHex = chainBuildRowHex(chainId, i);
+ if (i < rows.length - 1 && rows[i + 1].cmd === 'get-response') {
+ if (chainIsCase4(rows[i].cmd, chainId) && rowHex.length >= 2) {
+ rowHex = rowHex.slice(0, -2);
+ }
+ }
+ parts.push(rowHex);
+ }
+ return parts.join('');
}
function updateConvFields() {
@@ -7023,14 +6843,9 @@ updateConvFields();
updateSp();
updateSpKic();
updateSpKid();
-updateSimUsimFields('sim');
-updateSimSelectMethod('sim');
-updateSimSelectSection('sim');
-toggleSimOverride('sim');
-toggleSimOverride('usim');
-updateP1P2Display('sim');
-updateP1P2Display('usim');
-updateRamFields();
+chainInit('chain-sim');
+chainInit('chain-usim');
+chainInit('chain-ram');
translatePage();
// PWA
let deferredPrompt;
diff --git a/frontend/sw.js b/frontend/sw.js
index 6fe9c3b..e72269c 100644
--- a/frontend/sw.js
+++ b/frontend/sw.js
@@ -1,4 +1,4 @@
-const CACHE = 'otaman-v34';
+const CACHE = 'otaman-v35';
const URLS = [
'index.html',
'help.html',
diff --git a/frontend/tests/ram.test.js b/frontend/tests/ram.test.js
index ac204a3..e003cf9 100644
--- a/frontend/tests/ram.test.js
+++ b/frontend/tests/ram.test.js
@@ -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);
}
-});
\ No newline at end of file
+});
diff --git a/frontend/tests/sim.test.js b/frontend/tests/sim.test.js
index 772893e..adca5b1 100644
--- a/frontend/tests/sim.test.js
+++ b/frontend/tests/sim.test.js
@@ -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');
});
diff --git a/pyproject.toml b/pyproject.toml
index 38f9f33..c450d46 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -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.
diff --git a/pysim_otaman_server/server.py b/pysim_otaman_server/server.py
index 67d5271..1f6b3ee 100644
--- a/pysim_otaman_server/server.py
+++ b/pysim_otaman_server/server.py
@@ -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