RAM constructor fixes: EA/CA nesting, access domain order, menu pairs, BER lengths, services byte (TS 102 226)

- UICC Toolkit tag 80 nested inside EA (EA 12 80 10 ...)
- ram-tk-mode value ea (was 80), label 'UICC Toolkit (Tag EA)'
- SIM (CA) access domain FIRST in payload, blank = 00
- Menu entries: 2×m pairs (i=1 first, i=m last, else 0000)
- CA/EA lengths via berLenStr() (supports 81xx long form)
- UICC trailing 'Maximum number of services' byte (ram-tk-services)
- DELETE: P1=00 fixed, mode in P2 (80E4 00 <mode> ...)
- STORE DATA: ram-enc 00/40/80/C0/E0 with correct labels
- LOAD: P1 fixed to 0x80 (last block)
- USIM SELECT: selP1 0x09 -> 0x00 (select by file id)
- ram.test.js: 11 exact-hex tests from exchange vectors
- v1.8.1, SW cache otaman-v9
This commit is contained in:
2026-08-20 21:23:37 +03:00
parent 4a6a142c3d
commit e6602c40bf
6 changed files with 339 additions and 33 deletions
+1 -1
View File
@@ -54,7 +54,7 @@ Returns server version for compatibility checking.
**Example response:** **Example response:**
```json ```json
{"version": "1.8.0"} {"version": "1.8.1"}
``` ```
### `GET /api/status` ### `GET /api/status`
+42 -29
View File
@@ -17,7 +17,7 @@
<div class="max-w-7xl mx-auto px-6 py-2"> <div class="max-w-7xl mx-auto px-6 py-2">
<div class="flex items-center justify-between mb-3"> <div class="flex items-center justify-between mb-3">
<h1 class="text-2xl font-bold text-heading">OTAMan <span id="slogan" class="text-sm font-normal text-gray-500 dark:text-slate-400 ml-2" data-l10n="SIM OTA with a Human Face">SIM OTA with a Human Face</span> <span class="text-xs text-gray-400 dark:text-slate-500 ml-1">v1.8.0</span></h1> <h1 class="text-2xl font-bold text-heading">OTAMan <span id="slogan" class="text-sm font-normal text-gray-500 dark:text-slate-400 ml-2" data-l10n="SIM OTA with a Human Face">SIM OTA with a Human Face</span> <span class="text-xs text-gray-400 dark:text-slate-500 ml-1">v1.8.1</span></h1>
<div class="flex items-center gap-4"> <div class="flex items-center gap-4">
<button id="install-btn" class="px-2 py-1 text-xs rounded border border-gray-300 dark:border-slate-600 hover:bg-gray-200 dark:hover:bg-slate-700" style="display:none">INSTALL PWA [for offline use]</button> <button id="install-btn" class="px-2 py-1 text-xs rounded border border-gray-300 dark:border-slate-600 hover:bg-gray-200 dark:hover:bg-slate-700" style="display:none">INSTALL PWA [for offline use]</button>
<a href="https://github.com/anttro/otaman" target="_blank" class="text-xs text-gray-400 hover:text-gray-600 dark:text-slate-500 dark:hover:text-slate-300">github</a> <a href="https://github.com/anttro/otaman" target="_blank" class="text-xs text-gray-400 hover:text-gray-600 dark:text-slate-500 dark:hover:text-slate-300">github</a>
@@ -379,7 +379,7 @@
<label class="block mb-1 text-sm font-medium text-gray-700 dark:text-slate-300" data-l10n="Toolkit mode">Toolkit mode</label> <label class="block mb-1 text-sm font-medium text-gray-700 dark:text-slate-300" data-l10n="Toolkit mode">Toolkit mode</label>
<select id="ram-tk-mode" class="w-full border border-gray-300 dark:border-slate-600 text-sm rounded px-3 py-1.5 dark:bg-slate-800"> <select id="ram-tk-mode" class="w-full border border-gray-300 dark:border-slate-600 text-sm rounded px-3 py-1.5 dark:bg-slate-800">
<option value="ca">SIM Toolkit (Tag CA)</option> <option value="ca">SIM Toolkit (Tag CA)</option>
<option value="80">UICC Toolkit (Tag 80)</option> <option value="ea">UICC Toolkit (Tag EA)</option>
</select> </select>
</div> </div>
<div class="grid grid-cols-2 gap-x-3 gap-y-2 text-sm"> <div class="grid grid-cols-2 gap-x-3 gap-y-2 text-sm">
@@ -438,6 +438,10 @@
<label class="block mb-1 text-xs font-medium text-gray-700 dark:text-slate-300" data-l10n="Access domain (hex)">Access domain (hex)</label> <label class="block mb-1 text-xs font-medium text-gray-700 dark:text-slate-300" data-l10n="Access domain (hex)">Access domain (hex)</label>
<input id="ram-tk-ad" class="font-mono w-full border border-gray-300 dark:border-slate-600 text-xs rounded px-2 py-1.5 dark:bg-slate-800" placeholder="00" maxlength="2" value="00"> <input id="ram-tk-ad" class="font-mono w-full border border-gray-300 dark:border-slate-600 text-xs rounded px-2 py-1.5 dark:bg-slate-800" placeholder="00" maxlength="2" value="00">
</div> </div>
<div id="ram-tk-services-row">
<label class="block mb-1 text-xs font-medium text-gray-700 dark:text-slate-300" data-l10n="Max services">Max services</label>
<input id="ram-tk-services" type="number" min="0" max="255" value="0" class="font-mono w-full border border-gray-300 dark:border-slate-600 text-xs rounded px-2 py-1.5 dark:bg-slate-800">
</div>
</div> </div>
</div> </div>
</div> </div>
@@ -456,10 +460,11 @@
<div id="ram-enc-row" class="mb-3"> <div id="ram-enc-row" class="mb-3">
<label class="block mb-1 text-sm font-medium text-gray-700 dark:text-slate-300" data-l10n="Encryption">Encryption</label> <label class="block mb-1 text-sm font-medium text-gray-700 dark:text-slate-300" data-l10n="Encryption">Encryption</label>
<select id="ram-enc" class="w-full border border-gray-300 dark:border-slate-600 text-sm rounded px-3 py-1.5 dark:bg-slate-800"> <select id="ram-enc" class="w-full border border-gray-300 dark:border-slate-600 text-sm rounded px-3 py-1.5 dark:bg-slate-800">
<option value="00">No encryption, last block</option> <option value="00">No encryption, more blocks</option>
<option value="40">No encryption, more blocks</option> <option value="40">App-dependent encryption, more blocks</option>
<option value="80">With encryption, last block</option> <option value="80">No encryption, last block</option>
<option value="C0">With encryption, more blocks</option> <option value="C0">App-dependent encryption, last block</option>
<option value="E0">With encryption, last block</option>
</select> </select>
</div> </div>
<div id="ram-del-mode-row" class="mb-3"> <div id="ram-del-mode-row" class="mb-3">
@@ -1082,7 +1087,7 @@ function updateSimSelectSection(mode) {
} }
function updateP1P2Display(mode) { function updateP1P2Display(mode) {
const preset = mode === 'sim' ? {selP1: 0x00, selP2: 0x00} : {selP1: 0x09, selP2: 0x0C}; const preset = mode === 'sim' ? {selP1: 0x00, selP2: 0x00} : {selP1: 0x00, selP2: 0x0C};
const cmd = document.getElementById(mode + '-cmd').value; const cmd = document.getElementById(mode + '-cmd').value;
if (cmd === 'select') { if (cmd === 'select') {
document.getElementById(mode + '-p1').value = preset.selP1.toString(16).padStart(2, '0').toUpperCase(); document.getElementById(mode + '-p1').value = preset.selP1.toString(16).padStart(2, '0').toUpperCase();
@@ -1143,13 +1148,14 @@ function updateRamPriv() {
function updateRamToolkit() { function updateRamToolkit() {
const checked = document.getElementById('ram-toolkit-enable').checked; const checked = document.getElementById('ram-toolkit-enable').checked;
document.getElementById('ram-toolkit-body').style.display = checked ? '' : 'none'; document.getElementById('ram-toolkit-body').style.display = checked ? '' : 'none';
document.getElementById('ram-tk-ad-row').style.display = updateRamTkMode();
document.getElementById('ram-tk-mode').value === 'ca' ? '' : 'none';
} }
document.getElementById('ram-tk-mode').addEventListener('change', () => { function updateRamTkMode() {
document.getElementById('ram-tk-ad-row').style.display = const isSim = document.getElementById('ram-tk-mode').value === 'ca';
document.getElementById('ram-tk-mode').value === 'ca' ? '' : 'none'; 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);
// ===== GSM 7-bit alphabet ===== // ===== GSM 7-bit alphabet =====
const GSM7_ALPHABET = [ const GSM7_ALPHABET = [
@@ -1372,7 +1378,7 @@ function buildSelectApdu(mode, preset, selInfo) {
} }
function genSimUsim(mode) { function genSimUsim(mode) {
const preset = mode === 'sim' ? {cla: 0xA0, selP1: 0x00, selP2: 0x00} : {cla: 0x00, selP1: 0x09, selP2: 0x0C}; const preset = mode === 'sim' ? {cla: 0xA0, selP1: 0x00, selP2: 0x00} : {cla: 0x00, selP1: 0x00, selP2: 0x0C};
const cmd = document.getElementById(mode + '-cmd').value; const cmd = document.getElementById(mode + '-cmd').value;
const doSelect = document.getElementById(mode + '-select').checked; const doSelect = document.getElementById(mode + '-select').checked;
const selInfo = getSelValue(mode); const selInfo = getSelValue(mode);
@@ -1506,33 +1512,39 @@ function genRam() {
const channels = parseInt(document.getElementById('ram-tk-channels').value) || 0; const channels = parseInt(document.getElementById('ram-tk-channels').value) || 0;
const msl = document.getElementById('ram-tk-msl').value; const msl = document.getElementById('ram-tk-msl').value;
const tar = (document.getElementById('ram-tk-tar').value || '').replace(/[^0-9a-fA-F]/g, ''); const tar = (document.getElementById('ram-tk-tar').value || '').replace(/[^0-9a-fA-F]/g, '');
const ad = (document.getElementById('ram-tk-ad').value || '').replace(/[^0-9a-fA-F]/g, '').padStart(2, '0').slice(0, 2); 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;
let menuPairs = '';
for (let 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';
}
let tkPayload = ''; let tkPayload = '';
if (tkMode === 'ca') { if (tkMode === 'ca') {
tkPayload = priority.toString(16).padStart(2, '0') + tkPayload = (adRaw ? '01' + ad : '00') +
priority.toString(16).padStart(2, '0') +
timers.toString(16).padStart(2, '0') + timers.toString(16).padStart(2, '0') +
textLen.toString(16).padStart(2, '0') + textLen.toString(16).padStart(2, '0') +
menus.toString(16).padStart(2, '0') + menus.toString(16).padStart(2, '0') +
firstPos.toString(16).padStart(2, '0') + firstId + menuPairs +
lastPos.toString(16).padStart(2, '0') + lastId +
channels.toString(16).padStart(2, '0') + channels.toString(16).padStart(2, '0') +
'01' + msl + '01' + msl +
(tar ? (tar.length / 2).toString(16).padStart(2, '0') + tar : '00') + (tar ? (tar.length / 2).toString(16).padStart(2, '0') + tar : '00');
'01' + ad; dataField += 'CA' + berLenStr(tkPayload.length / 2) + tkPayload;
const tkLen = (tkPayload.length / 2).toString(16);
dataField += 'CA' + tkLen + tkPayload;
} else { } else {
tkPayload = priority.toString(16).padStart(2, '0') + tkPayload = priority.toString(16).padStart(2, '0') +
timers.toString(16).padStart(2, '0') + timers.toString(16).padStart(2, '0') +
textLen.toString(16).padStart(2, '0') + textLen.toString(16).padStart(2, '0') +
menus.toString(16).padStart(2, '0') + menus.toString(16).padStart(2, '0') +
firstPos.toString(16).padStart(2, '0') + firstId + menuPairs +
lastPos.toString(16).padStart(2, '0') + lastId +
channels.toString(16).padStart(2, '0') + channels.toString(16).padStart(2, '0') +
'01' + msl + '01' + msl +
(tar ? (tar.length / 2).toString(16).padStart(2, '0') + tar : '00'); (tar ? (tar.length / 2).toString(16).padStart(2, '0') + tar : '00') +
const tkLen = (tkPayload.length / 2).toString(16); services.toString(16).padStart(2, '0');
dataField += '80' + tkLen + tkPayload; const innerTlv = '80' + berLenStr(tkPayload.length / 2) + tkPayload;
dataField += 'EA' + berLenStr(innerTlv.length / 2) + innerTlv;
} }
} }
const dataLen = (dataField.length / 2).toString(16).padStart(2, '0'); const dataLen = (dataField.length / 2).toString(16).padStart(2, '0');
@@ -1552,13 +1564,13 @@ function genRam() {
} else if (cmd === 'load') { } else if (cmd === 'load') {
if (!ramData) { resultEl.value = 'Error: specify data'; return; } if (!ramData) { resultEl.value = 'Error: specify data'; return; }
const dataLen = (ramData.length / 2).toString(16).padStart(2, '0'); const dataLen = (ramData.length / 2).toString(16).padStart(2, '0');
apdu = buildApdu(0x80, 0xE8, 0x00, blockNum, parseInt(dataLen, 16), ramData); apdu = buildApdu(0x80, 0xE8, 0x80, blockNum, parseInt(dataLen, 16), ramData);
} else if (cmd === 'delete') { } else if (cmd === 'delete') {
if (!aid) { resultEl.value = 'Error: specify AID'; return; } if (!aid) { resultEl.value = 'Error: specify AID'; return; }
const aidLen = (aid.length / 2).toString(16).padStart(2, '0'); const aidLen = (aid.length / 2).toString(16).padStart(2, '0');
const dataField = '4F' + aidLen + aid; const dataField = '4F' + aidLen + aid;
const dataLen = (dataField.length / 2).toString(16).padStart(2, '0'); const dataLen = (dataField.length / 2).toString(16).padStart(2, '0');
apdu = buildApdu(0x80, 0xE4, parseInt(delMode, 16), 0x00, parseInt(dataLen, 16), dataField); apdu = buildApdu(0x80, 0xE4, 0x00, parseInt(delMode, 16), parseInt(dataLen, 16), dataField);
} else if (cmd === 'get-status') { } else if (cmd === 'get-status') {
const gsP2 = document.getElementById('ram-gs-p2').value; const gsP2 = document.getElementById('ram-gs-p2').value;
apdu = buildApdu(0x80, 0xF2, parseInt(gsMode, 16), parseInt(gsP2, 16), 0x02, '4F00'); apdu = buildApdu(0x80, 0xF2, parseInt(gsMode, 16), parseInt(gsP2, 16), 0x02, '4F00');
@@ -4882,6 +4894,7 @@ const LANG_RU = {
'Byte 2': 'Байт 2', 'Byte 2': 'Байт 2',
'SIM/UICC Toolkit parameters': 'Параметры SIM/UICC Toolkit', 'SIM/UICC Toolkit parameters': 'Параметры SIM/UICC Toolkit',
'Toolkit mode': 'Режим Toolkit', 'Toolkit mode': 'Режим Toolkit',
'Max services': 'Максимум сервисов',
'Priority': 'Приоритет', 'Priority': 'Приоритет',
'Timers (max)': 'Таймеры (макс)', 'Timers (max)': 'Таймеры (макс)',
'Menu text length': 'Длина текста меню', 'Menu text length': 'Длина текста меню',
+1 -1
View File
@@ -1,4 +1,4 @@
const CACHE = 'otaman-v8'; const CACHE = 'otaman-v9';
const URLS = [ const URLS = [
'index.html', 'index.html',
'help.html', 'help.html',
+293
View File
@@ -0,0 +1,293 @@
const { test } = require('node:test');
const assert = require('node:assert');
const fs = require('node:fs');
const path = require('node:path');
const html = fs.readFileSync(path.join(__dirname, '..', 'index.html'), 'utf8');
function extractFunc(src, name) {
const re = new RegExp('function\\s+' + name + '\\s*\\([^)]*\\)\\s*\\{');
const m = re.exec(src);
if (!m) throw new Error('function ' + name + ' not found');
let i = m.index + m[0].length - 1;
let depth = 0;
for (; i < src.length; i++) {
if (src[i] === '{') depth++;
else if (src[i] === '}') {
depth--;
if (depth === 0) break;
}
}
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'];
let code = '';
for (const f of FNS) code += extractFunc(html, f) + '\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
global.document = doc;
function set(id, v) { el(id).value = v; }
function setChecked(id, v) { el(id).checked = v; }
function genRamResult() {
genRam();
return els['ram-result'].value;
}
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();
}
test('UICC toolkit nested inside EA (m=2, services 0)', () => {
const apdu = genRamApdu();
assert.ok(apdu.includes('EA128010000000020101020200011603B0000100'), apdu);
});
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();
assert.ok(apdu.includes('EA10800E00000001010100011603B0000100'), 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();
assert.ok(apdu.includes('EA1480120000000301010000030300011603B0000100'), 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();
assert.ok(apdu.includes('EA128010000000020101020200011603B0000107'), 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();
assert.ok(apdu.includes('CA11015A000000020101020200011603B00001'), 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();
assert.ok(apdu.includes('CA1000000000020101020200011603B00001'), 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();
assert.ok(apdu.includes('CA0F000000000301010000030300011600'), 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();
assert.ok(apdu.includes('EA8187808184'), 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();
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();
assert.ok(apdu.startsWith('80E40000'), apdu);
set('ram-del-mode', '80');
apdu = genRamResult();
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();
assert.ok(apdu.startsWith('80E2' + p1 + '00'), enc + ' -> P1 ' + p1);
}
});
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project] [project]
name = "pysim-otaman-server" name = "pysim-otaman-server"
version = "1.8.0" version = "1.8.1"
description = "HTTP REST server wrapping pysim for the OTAMan PWA" description = "HTTP REST server wrapping pysim for the OTAMan PWA"
requires-python = ">=3.8" requires-python = ">=3.8"
# pysim is a git-only dependency installed explicitly by setup.bat/setup.sh. # pysim is a git-only dependency installed explicitly by setup.bat/setup.sh.
+1 -1
View File
@@ -18,7 +18,7 @@ from osmocom.construct import GsmOrUcs2Adapter
from osmocom.tlv import BER_TLV_IE from osmocom.tlv import BER_TLV_IE
VERSION = '1.8.0' VERSION = '1.8.1'
# Static file serving (the PWA lives in <repo>/frontend, served by this server # Static file serving (the PWA lives in <repo>/frontend, served by this server