Add ATR/PPS + SMS TPDU decoding, command summaries, SW column
- Decode ATR (convention, interface bytes, historical, TCK) and PPS (Fi/Di, PCK) - Decode ENVELOPE SMS-PP DOWNLOAD + SEND SHORT MESSAGE TPDU: SCTS, VP, DCS/PID, UDH IEIs, best-effort 8-bit text - Fix Duration data object ([time unit][interval], not a 16-bit int) - List view: command summary instead of raw hex, SW column, raw APDU first line in detail view - Expand FID tables (full pySim USIM/ISIM/SIM set) + context-aware SELECT-path resolution (7FFF = current ADF) - Fix SELECT P1 names (0x09 = path from current DF) - Bump version to 1.3.0
This commit is contained in:
1 parent
ef62956922
commit
143e1854e2
7 files changed
+1242
-41
No files matched your search
+115
-14
@@ -110,6 +110,7 @@ body { font: 14px/1.5 system-ui, sans-serif; background: var(--bg); color: var(-
|
||||
.apdu-type.atr { color: var(--green); }
|
||||
.apdu-type.pps { color: var(--yellow); }
|
||||
.apdu-data { font-family: monospace; color: var(--text-dim); word-break: break-all; flex: 1; }
|
||||
.apdu-sw { font-family: monospace; color: var(--text-muted); white-space: nowrap; min-width: 52px; }
|
||||
|
||||
/* APDU detail panel */
|
||||
.apdu-detail { display: flex; flex-direction: column; max-height: 40%; overflow-y: auto; border-top: 2px solid var(--accent); background: var(--bg-panel); flex-shrink: 0; }
|
||||
@@ -441,6 +442,17 @@ function data_text(m) {
|
||||
return fmt_data(m.data);
|
||||
}
|
||||
|
||||
function cmd_description(m) {
|
||||
if (m.decoded && m.decoded.summary) return m.decoded.summary;
|
||||
return data_text(m);
|
||||
}
|
||||
|
||||
function sw_text(m) {
|
||||
const d = m.decoded;
|
||||
if (!d || !d.sw) return '';
|
||||
return d.sw.sw1 + ' ' + d.sw.sw2;
|
||||
}
|
||||
|
||||
// ──────────────────── API ────────────────────
|
||||
const API = {
|
||||
async _fetch(method, path, body) {
|
||||
@@ -515,8 +527,8 @@ function responseRows(resp) {
|
||||
if (resp.code !== undefined) {
|
||||
rows.push(['Result', resp.code + (resp.name ? ' — ' + resp.name : '')]);
|
||||
}
|
||||
if (resp.duration !== undefined) {
|
||||
rows.push(['Duration', resp.duration + ' s']);
|
||||
if (resp.duration) {
|
||||
rows.push(['Duration', resp.duration.value + ' ' + resp.duration.unit]);
|
||||
}
|
||||
if (resp.item_identifier !== undefined) {
|
||||
rows.push(['Item', resp.item_identifier]);
|
||||
@@ -570,7 +582,7 @@ function cmdRows(cmd) {
|
||||
rows.push(['Item ' + (it.id !== undefined ? '0x' + hx(it.id, 2) : '?'), it.text]);
|
||||
}
|
||||
}
|
||||
if (cmd.duration !== undefined) rows.push(['Duration', cmd.duration + ' s']);
|
||||
if (cmd.duration) rows.push(['Duration', cmd.duration.value + ' ' + cmd.duration.unit]);
|
||||
if (cmd.address) rows.push(['Address', cmd.address]);
|
||||
if (cmd.response_length) rows.push(['Response length', cmd.response_length.min + '…' + cmd.response_length.max]);
|
||||
|
||||
@@ -578,14 +590,39 @@ function cmdRows(cmd) {
|
||||
const t = cmd.tpdu;
|
||||
if (t) {
|
||||
rows.push(['SMS TPDU', t.mti]);
|
||||
if (t.oa) rows.push(['Originating addr', t.oa]);
|
||||
if (t.da) rows.push(['Destination addr', t.da]);
|
||||
if (t.mr !== undefined) rows.push(['Message ref', '0x' + hx(t.mr, 2)]);
|
||||
if (t.pid !== undefined) rows.push(['PID', '0x' + hx(t.pid, 2) + (t.pid_name ? ' — ' + t.pid_name : '')]);
|
||||
if (t.dcs !== undefined) rows.push(['DCS', '0x' + hx(t.dcs, 2) + ' — ' + t.encoding + (t.msg_class ? ' (class ' + t.msg_class + ')' : '')]);
|
||||
if (t.text !== undefined) rows.push(['Text', t.text]);
|
||||
if (t.payload) rows.push(['Payload', t.payload]);
|
||||
if (t.udh) rows.push(['UDH', t.udh]);
|
||||
if (t.oa) rows.push(['TP-OA', t.oa]);
|
||||
if (t.da) rows.push(['TP-DA', t.da]);
|
||||
if (t.mr !== undefined) rows.push(['TP-MR', '0x' + hx(t.mr, 2)]);
|
||||
if (t.scts) rows.push(['TP-SCTS', t.scts]);
|
||||
if (t.pid !== undefined) rows.push(['TP-PID', '0x' + hx(t.pid, 2) + (t.pid_name ? ' — ' + t.pid_name : '')]);
|
||||
if (t.dcs !== undefined) {
|
||||
const di = t.dcs_info || {};
|
||||
let dcsTxt = '0x' + hx(t.dcs, 2) + ' — ' + t.encoding;
|
||||
if (di.group) dcsTxt += ' · ' + di.group;
|
||||
if (di.compressed) dcsTxt += ' · compressed';
|
||||
if (di.indication) dcsTxt += ' · ' + di.indication + ' (' + di.sense + ')';
|
||||
else if (t.msg_class !== undefined && di.has_class) dcsTxt += ' · class ' + t.msg_class;
|
||||
rows.push(['TP-DCS', dcsTxt]);
|
||||
}
|
||||
if (t.vp) rows.push(['TP-VP', t.vp]);
|
||||
if (Array.isArray(t.udh)) {
|
||||
for (const ie of t.udh) {
|
||||
let v = ie.name || ('IEI ' + ie.iei);
|
||||
const d = ie.data;
|
||||
if (d) {
|
||||
if (d.seq !== undefined && d.max !== undefined)
|
||||
v += ` — ref ${d.reference}, part ${d.seq}/${d.max}`;
|
||||
else if (d.dest_port !== undefined)
|
||||
v += ` — dst ${d.dest_port}, src ${d.orig_port}`;
|
||||
else if (d.count !== undefined)
|
||||
v += ` — ${d.indication || 'messages'}: ${d.count}` + (d.store ? ' (store)' : '');
|
||||
}
|
||||
if (ie.hex) v += ' · ' + ie.hex;
|
||||
rows.push(['UDH', v]);
|
||||
}
|
||||
}
|
||||
if (t.text !== undefined) rows.push(['TP-UD', t.text]);
|
||||
if (t.payload) rows.push(['TP-UD (payload)', t.payload]);
|
||||
}
|
||||
|
||||
if (!rows.length) return '';
|
||||
@@ -593,6 +630,67 @@ function cmdRows(cmd) {
|
||||
rows.map(([l, v]) => `<tr><td class="label">${l}</td><td class="value">${v}</td></tr>`).join('');
|
||||
}
|
||||
|
||||
function ifaceValue(ifc) {
|
||||
const v = [];
|
||||
if (ifc.f !== undefined) v.push('F=' + ifc.f);
|
||||
if (ifc.d !== undefined) v.push('D=' + ifc.d);
|
||||
if (ifc.f_div_d !== undefined) v.push('F/D=' + ifc.f_div_d);
|
||||
if (ifc.f_max !== undefined) v.push('f(max)=' + ifc.f_max + ' MHz');
|
||||
if (ifc.programming !== undefined) v.push(ifc.programming);
|
||||
if (ifc.extra_guard_time !== undefined) v.push('N=' + ifc.extra_guard_time + ' etu');
|
||||
if (ifc.work_waiting_time !== undefined) v.push('WI=' + ifc.work_waiting_time);
|
||||
if (ifc.specific !== undefined) v.push(ifc.specific ? 'specific mode' : 'negotiable mode');
|
||||
if (ifc.protocol !== undefined) v.push(ifc.protocol);
|
||||
if (ifc.ifsc !== undefined) v.push('IFSC=' + ifc.ifsc);
|
||||
if (ifc.bwi !== undefined) v.push('BWI=' + ifc.bwi + ', CWI=' + ifc.cwi);
|
||||
if (ifc.clock_stop) v.push(ifc.clock_stop);
|
||||
if (ifc.classes && ifc.classes.length) v.push('classes ' + ifc.classes.join(', '));
|
||||
if (ifc.spu !== undefined) v.push('SPU ' + ifc.spu);
|
||||
return (v.length ? v.join(' · ') : '') + (ifc.hex ? ' (0x' + ifc.hex + ')' : '');
|
||||
}
|
||||
|
||||
function atrPpsRows(decoded) {
|
||||
const rows = [];
|
||||
if (decoded.type === 'atr') {
|
||||
rows.push(['Convention', decoded.convention || 'unknown']);
|
||||
if (decoded.ts) rows.push(['TS', '0x' + decoded.ts]);
|
||||
if (decoded.t0) rows.push(['T0', '0x' + decoded.t0 + ' — ' + decoded.historical_len + ' historical bytes']);
|
||||
if (decoded.protocols) rows.push(['Protocols', decoded.protocols.join(', ')]);
|
||||
if (decoded.interface) {
|
||||
for (const ifc of decoded.interface) {
|
||||
rows.push([ifc.name, ifaceValue(ifc)]);
|
||||
}
|
||||
}
|
||||
if (decoded.historical) {
|
||||
const h = decoded.historical;
|
||||
let v = h.category || '';
|
||||
if (h.life_cycle) v += ' — ' + h.life_cycle;
|
||||
if (h.sw) v += ' (SW ' + h.sw + ')';
|
||||
rows.push(['Historical', v + ' · ' + h.raw]);
|
||||
}
|
||||
if (decoded.tck) {
|
||||
rows.push(['TCK', '0x' + decoded.tck + (decoded.tck_valid ? ' (valid)' : ' (mismatch)')]);
|
||||
}
|
||||
} else {
|
||||
if (decoded.ppss) rows.push(['PPSS', '0x' + decoded.ppss]);
|
||||
if (decoded.pps0) rows.push(['PPS0', '0x' + decoded.pps0]);
|
||||
if (decoded.protocol) rows.push(['Protocol', decoded.protocol]);
|
||||
if (decoded.fi_di) {
|
||||
const fd = decoded.fi_di;
|
||||
const v = [];
|
||||
if (fd.f !== undefined) v.push('F=' + fd.f);
|
||||
if (fd.d !== undefined) v.push('D=' + fd.d);
|
||||
if (fd.f_div_d !== undefined) v.push('F/D=' + fd.f_div_d);
|
||||
rows.push(['PPS1', v.join(' · ') + ' (0x' + fd.pps1 + ')']);
|
||||
}
|
||||
if (decoded.pps2 !== undefined) rows.push(['PPS2', '0x' + decoded.pps2 + (decoded.spu ? ' — ' + decoded.spu : '')]);
|
||||
if (decoded.pck) rows.push(['PCK', '0x' + decoded.pck + (decoded.pck_valid ? ' (valid)' : ' (mismatch)')]);
|
||||
if (decoded.note) rows.push(['Note', decoded.note]);
|
||||
}
|
||||
rows.push(['Raw', (decoded.raw || '')]);
|
||||
return rows;
|
||||
}
|
||||
|
||||
function showDetail(msg, entry) {
|
||||
// Highlight clicked entry
|
||||
document.querySelectorAll('.apdu-entry.selected').forEach(e => e.classList.remove('selected'));
|
||||
@@ -630,10 +728,11 @@ function showDetail(msg, entry) {
|
||||
}
|
||||
|
||||
if (decoded.type === 'atr' || decoded.type === 'pps') {
|
||||
const rows = atrPpsRows(decoded);
|
||||
d.innerHTML = `<div class="detail-header"><strong>${decoded.type.toUpperCase()}</strong><span style="color:var(--text-muted)">${fmt_elapsed(msg.elapsed)}</span></div>
|
||||
<div class="detail-body">
|
||||
<table>
|
||||
<tr><td class="label">Data</td><td class="hex">${(decoded.hex || '').toUpperCase()}</td></tr>
|
||||
${rows.map(([l, v]) => `<tr><td class="label">${l}</td><td class="hex">${v}</td></tr>`).join('')}
|
||||
</table>
|
||||
</div>`;
|
||||
return;
|
||||
@@ -687,6 +786,7 @@ function showDetail(msg, entry) {
|
||||
</div>
|
||||
<div class="detail-body">
|
||||
<table>
|
||||
<tr><td class="label">Raw APDU</td><td class="hex">${fmt_data(msg.data, 9999)}</td></tr>
|
||||
<tr><td class="label">CLA</td><td class="value">0x${(cla.hex || '').toUpperCase()} — ${claInfo}</td></tr>
|
||||
<tr><td class="label">INS</td><td class="name">0x${(decoded.ins_hex || '').toUpperCase()} — ${decoded.ins_name}</td></tr>
|
||||
${catRow}${respRow}${trRow}
|
||||
@@ -821,7 +921,8 @@ function renderMessages() {
|
||||
return `<div class="apdu-entry" data-idx="${idx}">
|
||||
<span class="apdu-time">${fmt_elapsed(m.elapsed)}</span>
|
||||
<span class="apdu-type ${typeCls}">${label}</span>
|
||||
<span class="apdu-data">${data_text(m)}</span>
|
||||
<span class="apdu-sw">${sw_text(m)}</span>
|
||||
<span class="apdu-data">${cmd_description(m)}</span>
|
||||
</div>`;
|
||||
}).join('');
|
||||
|
||||
@@ -1278,7 +1379,7 @@ function onFilterChange() {
|
||||
}
|
||||
|
||||
// ──────────────────── Versioning ────────────────────
|
||||
const PWA_VERSION = '1.2.0';
|
||||
const PWA_VERSION = '1.3.0';
|
||||
const MIN_SERVER_VERSION = '1.0.0';
|
||||
|
||||
function parseVersion(v) {
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
const CACHE = 'simtrace-analyser-v4';
|
||||
const CACHE = 'simtrace-analyser-v5';
|
||||
|
||||
const PRECACHE = [
|
||||
'index.html',
|
||||
|
||||
Reference in new issue
Block a user