feat: the remaining six event types get their parameter forms
MT call, Call connected, Call disconnected, Browser termination, (I-)WLAN access status and CSG cell selection no longer fall back to the generic "send a bare notification" prompt: - MT call (TS 31.111 7.5.1A.2): transaction identifier, calling party number + TON -> Address (EFADN BCD coding, encBcdDigits/eventAddressTlv), subaddress, IMS URI, media type; source network. - Call connected (7.5.2A.2) / Call disconnected (7.5.3A.2): transaction identifier, near/far-end source select, media type, and a cause - absent, the zero-length radio-link-timeout form or custom bytes (the hex field shows only for "custom"). - Browser termination (TS 102 223 7.5.9.2): user / error cause. - (I-)WLAN access status (TS 31.111 7.5.1.2): no coverage / coverage / connection on-going. - CSG cell selection (TS 31.111 7.5.3.2): status + selection mechanism, optional access technology, CSG ID, HNB name and PLMN ID; source network. The device-identities source (TS 102 223 8.7: 82 terminal / 83 network) is now selectable: EVENT_FORMS carries a fixed `src` or a `srcField`, and _send_event_download()/`/api/event-send` take an `event_src` override (an invalid value falls back to the terminal). Tests: the BCD/Address builders, each event's build vectors, the "no not_yet left" guard and the server source override. Help EN/RU, both AGENTS files; sw cache simple-v348.
This commit is contained in:
@@ -504,6 +504,8 @@
|
|||||||
<li><strong>Location Status</strong> — выпадающий список: Normal / Limited / No service (тег <code class="font-mono text-sm">9B</code>)</li>
|
<li><strong>Location Status</strong> — выпадающий список: Normal / Limited / No service (тег <code class="font-mono text-sm">9B</code>)</li>
|
||||||
<li><strong>Access Technology Change</strong> — 13 типов RAT (тег <code class="font-mono text-sm">BF</code>)</li>
|
<li><strong>Access Technology Change</strong> — 13 типов RAT (тег <code class="font-mono text-sm">BF</code>)</li>
|
||||||
<li><strong>Network Rejection</strong> — полная адаптивная форма: тип регистрации (LU / GPRS / EPS / 5GS), поля местоположения (MCC, MNC, LAC, RAC, TAC), технология доступа и единый выпадающий список из 53 кодов причин (EMM, GMM, 5GMM и LU)</li>
|
<li><strong>Network Rejection</strong> — полная адаптивная форма: тип регистрации (LU / GPRS / EPS / 5GS), поля местоположения (MCC, MNC, LAC, RAC, TAC), технология доступа и единый выпадающий список из 53 кодов причин (EMM, GMM, 5GMM и LU)</li>
|
||||||
|
<li><strong>События звонка</strong> — MT call (transaction identifier, номер вызывающего + TON → Address, subaddress, IMS URI, media type; источник <em>network</em>), Call connected и Call disconnected (transaction identifier, источник near/far, media type и причина — отсутствует, нулевая форма radio link timeout или свои байты)</li>
|
||||||
|
<li><strong>Browser termination</strong> (причина user / error), <strong>(I-)WLAN access status</strong> (нет покрытия / покрытие / соединение) и <strong>CSG cell selection</strong> (статус + механизм выбора, access technology, CSG ID, HNB name, PLMN ID; источник <em>network</em>)</li>
|
||||||
<li><strong>Data Connection Status Change</strong> (0x1D) — статус (successful / rejected / dropped-deactivated), тип соединения (PDP / PDN / PDU), необязательная причина (E/5G)SM, transaction identifier и location status, а также необязательные date-time, access technology, APN, тип PDP/PDN/PDU и location information (MCC/MNC + LAC + Cell ID) — TS 131 111 §7.5.25</li>
|
<li><strong>Data Connection Status Change</strong> (0x1D) — статус (successful / rejected / dropped-deactivated), тип соединения (PDP / PDN / PDU), необязательная причина (E/5G)SM, transaction identifier и location status, а также необязательные date-time, access technology, APN, тип PDP/PDN/PDU и location information (MCC/MNC + LAC + Cell ID) — TS 131 111 §7.5.25</li>
|
||||||
<li><strong>Poll Interval Negotiation</strong> (0x1C) — предлагает интервал опроса в виде Duration (единица + значение); после отправки показывается ответ UICC (принято / отклонено / изменено + длительность), а значение <em>изменено</em> становится интервалом фонового опроса (TS 102 223 §7.5.22). Форма появляется, когда карта подписана на это событие.</li>
|
<li><strong>Poll Interval Negotiation</strong> (0x1C) — предлагает интервал опроса в виде Duration (единица + значение); после отправки показывается ответ UICC (принято / отклонено / изменено + длительность), а значение <em>изменено</em> становится интервалом фонового опроса (TS 102 223 §7.5.22). Форма появляется, когда карта подписана на это событие.</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
@@ -503,6 +503,8 @@
|
|||||||
<li><strong>Location Status</strong> — dropdown: Normal / Limited / No service (tag <code class="font-mono text-sm">9B</code>)</li>
|
<li><strong>Location Status</strong> — dropdown: Normal / Limited / No service (tag <code class="font-mono text-sm">9B</code>)</li>
|
||||||
<li><strong>Access Technology Change</strong> — 13 RAT types (tag <code class="font-mono text-sm">BF</code>)</li>
|
<li><strong>Access Technology Change</strong> — 13 RAT types (tag <code class="font-mono text-sm">BF</code>)</li>
|
||||||
<li><strong>Network Rejection</strong> — full adaptive form: registration type (LU / GPRS / EPS / 5GS), location fields (MCC, MNC, LAC, RAC, TAC), access technology, and a 53-cause unified rejection cause dropdown covering EMM, GMM, 5GMM and LU causes</li>
|
<li><strong>Network Rejection</strong> — full adaptive form: registration type (LU / GPRS / EPS / 5GS), location fields (MCC, MNC, LAC, RAC, TAC), access technology, and a 53-cause unified rejection cause dropdown covering EMM, GMM, 5GMM and LU causes</li>
|
||||||
|
<li><strong>Call events</strong> — MT call (transaction identifier, calling party number + TON → Address, subaddress, IMS URI, media type; source <em>network</em>), Call connected and Call disconnected (transaction identifier, near/far-end source, media type, and a cause — absent, the zero-length radio-link-timeout form, or custom bytes)</li>
|
||||||
|
<li><strong>Browser termination</strong> (user / error cause), <strong>(I-)WLAN access status</strong> (no coverage / coverage / connection on-going) and <strong>CSG cell selection</strong> (status + selection mechanism, access technology, CSG ID, HNB name, PLMN ID; source <em>network</em>)</li>
|
||||||
<li><strong>Data Connection Status Change</strong> (0x1D) — status (successful / rejected / dropped-deactivated), connection type (PDP / PDN / PDU), optional (E/5G)SM cause, transaction identifier and location status, plus optional date-time, access technology, APN, PDP/PDN/PDU type and location information (MCC/MNC + LAC + Cell ID) — TS 131 111 §7.5.25</li>
|
<li><strong>Data Connection Status Change</strong> (0x1D) — status (successful / rejected / dropped-deactivated), connection type (PDP / PDN / PDU), optional (E/5G)SM cause, transaction identifier and location status, plus optional date-time, access technology, APN, PDP/PDN/PDU type and location information (MCC/MNC + LAC + Cell ID) — TS 131 111 §7.5.25</li>
|
||||||
<li><strong>Poll Interval Negotiation</strong> (0x1C) — proposes a poll interval as a Duration (unit + value); after sending, the UICC's answer is shown (accepted / rejected / modified + duration), and a <em>modified</em> duration becomes the background poll interval (TS 102 223 §7.5.22). The form appears when the card subscribed to this event.</li>
|
<li><strong>Poll Interval Negotiation</strong> (0x1C) — proposes a poll interval as a Duration (unit + value); after sending, the UICC's answer is shown (accepted / rejected / modified + duration), and a <em>modified</em> duration becomes the background poll interval (TS 102 223 §7.5.22). The form appears when the card subscribed to this event.</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
+153
-7
@@ -13049,6 +13049,27 @@ function eventDateTimeTlv(d) {
|
|||||||
hx(semi(d.getSeconds())) + hx(tz);
|
hx(semi(d.getSeconds())) + hx(tz);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// EFADN number coding (TS 31.102 / TS 24.008 10.5.4.7): digit pairs in
|
||||||
|
// swapped semi-octets, an odd number of digits padded with 'F'.
|
||||||
|
function encBcdDigits(digits) {
|
||||||
|
const d = String(digits || '').replace(/\D/g, '');
|
||||||
|
let out = '';
|
||||||
|
for (let i = 0; i < d.length; i += 2) {
|
||||||
|
out += ((i + 1 < d.length ? d[i + 1] : 'F') + d[i]).toUpperCase();
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Address object (TS 102 223 8.1): TON/NPI byte + the number in the EFADN
|
||||||
|
// coding. NPI = ISDN/telephony (1), TON per the form (0 = unknown).
|
||||||
|
function eventAddressTlv(number, ton) {
|
||||||
|
const bcd = encBcdDigits(number);
|
||||||
|
if (!bcd) return '';
|
||||||
|
const tonNpi = (0x80 | ((parseInt(ton, 10) & 0x07) << 4) | 0x01);
|
||||||
|
const value = tonNpi.toString(16).padStart(2, '0') + bcd;
|
||||||
|
return '86' + (value.length / 2).toString(16).padStart(2, '0') + value;
|
||||||
|
}
|
||||||
|
|
||||||
const ACCESSTECH = [
|
const ACCESSTECH = [
|
||||||
{v:0,l:'GSM'},{v:1,l:'TIA/EIA-553-A'},{v:2,l:'TIA/EIA-136-270'},{v:3,l:'UTRAN'},
|
{v:0,l:'GSM'},{v:1,l:'TIA/EIA-553-A'},{v:2,l:'TIA/EIA-136-270'},{v:3,l:'UTRAN'},
|
||||||
{v:4,l:'TETRA'},{v:5,l:'TIA/EIA-95-B'},{v:6,l:'CDMA2000 1x'},{v:7,l:'CDMA2000 HRPD'},
|
{v:4,l:'TETRA'},{v:5,l:'TIA/EIA-95-B'},{v:6,l:'CDMA2000 1x'},{v:7,l:'CDMA2000 HRPD'},
|
||||||
@@ -13056,9 +13077,70 @@ const ACCESSTECH = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
const EVENT_FORMS = {
|
const EVENT_FORMS = {
|
||||||
0x00: { title: 'MT call', note: 'not_yet' },
|
0x00: { title: 'MT call', src: '83',
|
||||||
0x01: { title: 'Call connected', note: 'not_yet' },
|
fields: [
|
||||||
0x02: { title: 'Call disconnected', note: 'not_yet' },
|
{ id: 'ti', label: 'Transaction identifier', type: 'text', pattern: '[0-9A-Fa-f]{2}', placeholder: 'hex byte, e.g. 00' },
|
||||||
|
{ id: 'number', label: 'Calling party number (optional)', type: 'text', pattern: '\\d{1,20}', placeholder: 'digits, e.g. 79001234567' },
|
||||||
|
{ id: 'ton', label: 'Type of number', type: 'select',
|
||||||
|
opts: [{v:0,l:'Unknown'},{v:1,l:'International'},{v:2,l:'National'},{v:3,l:'Network specific'}] },
|
||||||
|
{ id: 'sub', label: 'Subaddress (optional, hex)', type: 'text', pattern: '[0-9A-Fa-f]{2,40}', placeholder: 'e.g. 1234' },
|
||||||
|
{ id: 'ims_uri', label: 'IMS URI (optional)', type: 'text', placeholder: 'e.g. sip:user@example.com' },
|
||||||
|
{ id: 'media', label: 'Media Type (optional)', type: 'select',
|
||||||
|
opts: [{v:'',l:'\u2014 not present \u2014'},{v:0,l:'00 \u2014 voice'},{v:1,l:'01 \u2014 video'}] },
|
||||||
|
],
|
||||||
|
build(v) {
|
||||||
|
let d = '1C01' + (v.ti || '00').replace(/[^0-9a-fA-F]/g, '').padStart(2, '0').slice(0, 2);
|
||||||
|
d += eventAddressTlv(v.number, v.ton);
|
||||||
|
if (v.sub) {
|
||||||
|
const h = v.sub.replace(/[^0-9a-fA-F]/g, '');
|
||||||
|
d += '88' + (h.length / 2).toString(16).padStart(2, '0') + h;
|
||||||
|
}
|
||||||
|
if (v.ims_uri) {
|
||||||
|
const b = new TextEncoder().encode(v.ims_uri).slice(0, 100);
|
||||||
|
d += 'B1' + b.length.toString(16).padStart(2, '0') + bytesToHex(Array.from(b));
|
||||||
|
}
|
||||||
|
if (v.media !== '' && v.media !== undefined)
|
||||||
|
d += 'FE01' + ((parseInt(v.media) || 0) & 0xFF).toString(16).padStart(2, '0');
|
||||||
|
return d.toUpperCase();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
0x01: { title: 'Call connected', srcField: 'src',
|
||||||
|
fields: [
|
||||||
|
{ id: 'src', label: 'Source', type: 'select',
|
||||||
|
opts: [{v:'82',l:'terminal \u2014 near end (MT call)'},{v:'83',l:'network \u2014 far end (MO call)'}] },
|
||||||
|
{ id: 'ti', label: 'Transaction identifier', type: 'text', pattern: '[0-9A-Fa-f]{2}', placeholder: 'hex byte, e.g. 00' },
|
||||||
|
{ id: 'media', label: 'Media Type (optional)', type: 'select',
|
||||||
|
opts: [{v:'',l:'\u2014 not present \u2014'},{v:0,l:'00 \u2014 voice'},{v:1,l:'01 \u2014 video'}] },
|
||||||
|
],
|
||||||
|
build(v) {
|
||||||
|
let d = '1C01' + (v.ti || '00').replace(/[^0-9a-fA-F]/g, '').padStart(2, '0').slice(0, 2);
|
||||||
|
if (v.media !== '' && v.media !== undefined)
|
||||||
|
d += 'FE01' + ((parseInt(v.media) || 0) & 0xFF).toString(16).padStart(2, '0');
|
||||||
|
return d.toUpperCase();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
0x02: { title: 'Call disconnected', srcField: 'src',
|
||||||
|
fields: [
|
||||||
|
{ id: 'src', label: 'Source', type: 'select',
|
||||||
|
opts: [{v:'82',l:'terminal \u2014 near end (terminal-initiated)'},{v:'83',l:'network \u2014 far end (network-initiated)'}] },
|
||||||
|
{ id: 'ti', label: 'Transaction identifier', type: 'text', pattern: '[0-9A-Fa-f]{2}', placeholder: 'hex byte, e.g. 00' },
|
||||||
|
{ id: 'cause_mode', label: 'Cause', type: 'select', onchange: 'updateCallCauseFields()',
|
||||||
|
opts: [{v:'',l:'\u2014 not present \u2014'},{v:'rlt',l:'radio link timeout (zero length)'},{v:'custom',l:'custom (hex)'}] },
|
||||||
|
{ id: 'cause', label: 'Cause bytes (hex)', type: 'text', pattern: '[0-9A-Fa-f]{2,60}', placeholder: 'e.g. 10', g: 'custom' },
|
||||||
|
{ id: 'media', label: 'Media Type (optional)', type: 'select',
|
||||||
|
opts: [{v:'',l:'\u2014 not present \u2014'},{v:0,l:'00 \u2014 voice'},{v:1,l:'01 \u2014 video'}] },
|
||||||
|
],
|
||||||
|
build(v) {
|
||||||
|
let d = '1C01' + (v.ti || '00').replace(/[^0-9a-fA-F]/g, '').padStart(2, '0').slice(0, 2);
|
||||||
|
const cause = (v.cause || '').replace(/[^0-9a-fA-F]/g, '');
|
||||||
|
if (v.cause_mode === 'rlt') d += '9A00';
|
||||||
|
else if (v.cause_mode === 'custom' && cause)
|
||||||
|
d += '9A' + (cause.length / 2).toString(16).padStart(2, '0') + cause;
|
||||||
|
if (v.media !== '' && v.media !== undefined)
|
||||||
|
d += 'FE01' + ((parseInt(v.media) || 0) & 0xFF).toString(16).padStart(2, '0');
|
||||||
|
return d.toUpperCase();
|
||||||
|
}
|
||||||
|
},
|
||||||
0x03: { title: 'Location status',
|
0x03: { title: 'Location status',
|
||||||
fields: [
|
fields: [
|
||||||
{ id: 'status', label: 'Service Status', type: 'select',
|
{ id: 'status', label: 'Service Status', type: 'select',
|
||||||
@@ -13081,7 +13163,13 @@ const EVENT_FORMS = {
|
|||||||
],
|
],
|
||||||
build(v) { const b = new TextEncoder().encode(v.lang||'en'); return b.length.toString(16).padStart(2,'0') + bytesToHex(Array.from(b)); }
|
build(v) { const b = new TextEncoder().encode(v.lang||'en'); return b.length.toString(16).padStart(2,'0') + bytesToHex(Array.from(b)); }
|
||||||
},
|
},
|
||||||
0x08: { title: 'Browser termination', note: 'not_yet' },
|
0x08: { title: 'Browser termination',
|
||||||
|
fields: [
|
||||||
|
{ id: 'cause', label: 'Browser termination cause', type: 'select',
|
||||||
|
opts: [{v:0,l:'00 \u2014 user termination'},{v:1,l:'01 \u2014 error termination'}] },
|
||||||
|
],
|
||||||
|
build(v) { return 'B401' + ((parseInt(v.cause) || 0) & 0xFF).toString(16).padStart(2, '0'); }
|
||||||
|
},
|
||||||
0x09: null,
|
0x09: null,
|
||||||
0x0A: { title: 'Channel status',
|
0x0A: { title: 'Channel status',
|
||||||
fields: [
|
fields: [
|
||||||
@@ -13114,7 +13202,15 @@ const EVENT_FORMS = {
|
|||||||
0x0E: null,
|
0x0E: null,
|
||||||
0x0F: null,
|
0x0F: null,
|
||||||
0x10: null,
|
0x10: null,
|
||||||
0x11: { title: 'I-WLAN Access Status', note: 'not_yet' },
|
0x11: { title: 'I-WLAN Access Status',
|
||||||
|
fields: [
|
||||||
|
{ id: 'status', label: 'Access status', type: 'select',
|
||||||
|
opts: [{v:0,l:'00 \u2014 no current (I-)WLAN coverage'},
|
||||||
|
{v:1,l:'01 \u2014 coverage available, no current connection'},
|
||||||
|
{v:2,l:'02 \u2014 coverage available, connection on-going'}] },
|
||||||
|
],
|
||||||
|
build(v) { return 'CB01' + ((parseInt(v.status) || 0) & 0xFF).toString(16).padStart(2, '0'); }
|
||||||
|
},
|
||||||
0x12: { title: 'Network Rejection',
|
0x12: { title: 'Network Rejection',
|
||||||
fields: [
|
fields: [
|
||||||
{ id: 'reg_type', label: 'Registration type', type: 'select', onchange: 'updateNetRejectionFields()',
|
{ id: 'reg_type', label: 'Registration type', type: 'select', onchange: 'updateNetRejectionFields()',
|
||||||
@@ -13166,7 +13262,40 @@ const EVENT_FORMS = {
|
|||||||
opts: [{v:0,l:'GSM'},{v:3,l:'UTRAN'},{v:8,l:'E-UTRAN'},{v:10,l:'NG-RAN'}] }],
|
opts: [{v:0,l:'GSM'},{v:3,l:'UTRAN'},{v:8,l:'E-UTRAN'},{v:10,l:'NG-RAN'}] }],
|
||||||
build(v) { return 'BF01' + (v.tech|0).toString(16).padStart(2,'0'); }
|
build(v) { return 'BF01' + (v.tech|0).toString(16).padStart(2,'0'); }
|
||||||
},
|
},
|
||||||
0x15: { title: 'CSG cell selection', note: 'not_yet' },
|
0x15: { title: 'CSG cell selection', src: '83',
|
||||||
|
fields: [
|
||||||
|
{ id: 'status', label: 'CSG cell selection status', type: 'select',
|
||||||
|
opts: [{v:0,l:'00 \u2014 not camped on an Allowed / Operator CSG cell'},
|
||||||
|
{v:1,l:'01 \u2014 camped on an Allowed / Operator CSG cell'}] },
|
||||||
|
{ id: 'mech', label: 'Selection mechanism', type: 'select',
|
||||||
|
opts: [{v:'',l:'\u2014 none \u2014'},{v:'manual',l:'manual CSG selection'},
|
||||||
|
{v:'other',l:'another CSG selection type'}] },
|
||||||
|
{ id: 'tech', label: 'Access Technology (optional)', type: 'select',
|
||||||
|
opts: [{v:'',l:'\u2014 not present \u2014'}].concat(ACCESSTECH) },
|
||||||
|
{ id: 'csg', label: 'CSG ID (optional, hex)', type: 'text', pattern: '[0-9A-Fa-f]{2,16}', placeholder: 'e.g. 00000001' },
|
||||||
|
{ id: 'hnb', label: 'HNB name (optional)', type: 'text', placeholder: 'e.g. MyCSG' },
|
||||||
|
{ id: 'mcc', label: 'MCC (PLMN ID, optional)', type: 'text', pattern: '\\d{3}', placeholder: 'e.g. 250' },
|
||||||
|
{ id: 'mnc', label: 'MNC', type: 'text', pattern: '\\d{2,3}', placeholder: 'e.g. 01' },
|
||||||
|
],
|
||||||
|
build(v) {
|
||||||
|
let d = '';
|
||||||
|
if (v.tech !== '' && v.tech !== undefined)
|
||||||
|
d += 'BF01' + ((parseInt(v.tech) || 0) & 0xFF).toString(16).padStart(2, '0');
|
||||||
|
const mech = v.mech === 'manual' ? 0x40 : (v.mech === 'other' ? 0x80 : 0x00);
|
||||||
|
d += 'D502' + ((parseInt(v.status) || 0) & 0xFF).toString(16).padStart(2, '0') +
|
||||||
|
(0x01 | mech).toString(16).padStart(2, '0');
|
||||||
|
if (v.csg) {
|
||||||
|
const h = v.csg.replace(/[^0-9a-fA-F]/g, '');
|
||||||
|
d += 'D6' + (h.length / 2).toString(16).padStart(2, '0') + h;
|
||||||
|
}
|
||||||
|
if (v.hnb) {
|
||||||
|
const b = new TextEncoder().encode(v.hnb).slice(0, 60);
|
||||||
|
d += 'D7' + b.length.toString(16).padStart(2, '0') + bytesToHex(Array.from(b));
|
||||||
|
}
|
||||||
|
if (v.mcc) d += '8903' + encPlmn(v.mcc, v.mnc || '01');
|
||||||
|
return d.toUpperCase();
|
||||||
|
}
|
||||||
|
},
|
||||||
0x16: null,
|
0x16: null,
|
||||||
0x17: null,
|
0x17: null,
|
||||||
0x1C: { title: 'Poll interval negotiation',
|
0x1C: { title: 'Poll interval negotiation',
|
||||||
@@ -13690,10 +13819,23 @@ function pysimEventSendForm(eventType) {
|
|||||||
body.innerHTML = html;
|
body.innerHTML = html;
|
||||||
btn.classList.remove('hidden');
|
btn.classList.remove('hidden');
|
||||||
if (eventType === 0x12) updateNetRejectionFields();
|
if (eventType === 0x12) updateNetRejectionFields();
|
||||||
|
if (eventType === 0x02) updateCallCauseFields();
|
||||||
}
|
}
|
||||||
modal.classList.remove('hidden');
|
modal.classList.remove('hidden');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The call-disconnected form's Cause bytes are shown only for a custom cause
|
||||||
|
// (the radio-link-timeout form is the zero-length object).
|
||||||
|
function updateCallCauseFields() {
|
||||||
|
const sel = document.getElementById('ev-f-cause_mode');
|
||||||
|
if (!sel) return;
|
||||||
|
const g = sel.value;
|
||||||
|
document.querySelectorAll('#event-send-body .event-field').forEach(div => {
|
||||||
|
const groups = div.getAttribute('data-groups') || '';
|
||||||
|
div.style.display = (groups === '' || groups.indexOf(g) >= 0) ? '' : 'none';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function updateNetRejectionFields() {
|
function updateNetRejectionFields() {
|
||||||
const sel = document.getElementById('ev-f-reg_type');
|
const sel = document.getElementById('ev-f-reg_type');
|
||||||
if (!sel) return;
|
if (!sel) return;
|
||||||
@@ -13715,6 +13857,9 @@ async function pysimEventSend() {
|
|||||||
|
|
||||||
const cfg = EVENT_FORMS[_eventFormType];
|
const cfg = EVENT_FORMS[_eventFormType];
|
||||||
let eventData = null;
|
let eventData = null;
|
||||||
|
// The device-identities source (TS 102 223 8.7): a fixed per-event value
|
||||||
|
// or a form field (the call events differ near/far end).
|
||||||
|
let eventSrc = (cfg && cfg.src) || null;
|
||||||
|
|
||||||
if (cfg && cfg.build) {
|
if (cfg && cfg.build) {
|
||||||
const values = {};
|
const values = {};
|
||||||
@@ -13733,6 +13878,7 @@ async function pysimEventSend() {
|
|||||||
if (!valid) return;
|
if (!valid) return;
|
||||||
try {
|
try {
|
||||||
eventData = cfg.build(values);
|
eventData = cfg.build(values);
|
||||||
|
if (cfg.srcField && values[cfg.srcField]) eventSrc = values[cfg.srcField];
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
err.textContent = 'Error: ' + e.message;
|
err.textContent = 'Error: ' + e.message;
|
||||||
err.classList.remove('hidden');
|
err.classList.remove('hidden');
|
||||||
@@ -13743,7 +13889,7 @@ async function pysimEventSend() {
|
|||||||
btn.disabled = true;
|
btn.disabled = true;
|
||||||
btn.textContent = t('Sending...');
|
btn.textContent = t('Sending...');
|
||||||
try {
|
try {
|
||||||
const resp = await pysimFetch('/api/event-send', { event_type: _eventFormType, event_data: eventData });
|
const resp = await pysimFetch('/api/event-send', { event_type: _eventFormType, event_data: eventData, event_src: eventSrc });
|
||||||
if (resp.sw === '9000') {
|
if (resp.sw === '9000') {
|
||||||
result.textContent = (eventData ? t('Event data sent.') : t('Event sent.')) + ' SW: ' + resp.sw;
|
result.textContent = (eventData ? t('Event data sent.') : t('Event sent.')) + ' SW: ' + resp.sw;
|
||||||
if (resp.negotiation) {
|
if (resp.negotiation) {
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
const CACHE = 'simple-v347';
|
const CACHE = 'simple-v348';
|
||||||
const URLS = [
|
const URLS = [
|
||||||
'index.html',
|
'index.html',
|
||||||
'help.html',
|
'help.html',
|
||||||
|
|||||||
@@ -149,3 +149,40 @@ test('the data connection event can carry the current date-time', () => {
|
|||||||
assert.strictEqual(build({ status: '0', type: '0', ti: '00', loc_status: '0' }),
|
assert.strictEqual(build({ status: '0', type: '0', ti: '00', loc_status: '0' }),
|
||||||
'9D0100AA01001C01009B0100');
|
'9D0100AA01001C01009B0100');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('encBcdDigits and eventAddressTlv build the EFADN number coding', () => {
|
||||||
|
assert.strictEqual(encBcdDigits('12345'), '2143F5');
|
||||||
|
assert.strictEqual(encBcdDigits('79001234567'), '9700214365F7');
|
||||||
|
// international number -> TON/NPI 0x91, length = 1 + 6 bytes
|
||||||
|
assert.strictEqual(eventAddressTlv('79001234567', '1'), '8607919700214365F7');
|
||||||
|
assert.strictEqual(eventAddressTlv('', '1'), '');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('the call events build their 31.111 object sets', () => {
|
||||||
|
// MT call: source network, TI + Address + Subaddress + IMS URI + Media Type
|
||||||
|
assert.strictEqual(EVENT_FORMS[0x00].src, '83');
|
||||||
|
assert.strictEqual(EVENT_FORMS[0x00].build({ ti: '85', number: '79001234567', ton: '1',
|
||||||
|
sub: '12', ims_uri: 'sip:a@b', media: '1' }),
|
||||||
|
'1C01858607919700214365F7880112B1077369703A614062FE0101');
|
||||||
|
// call connected: source is the form's field, TI + optional Media Type
|
||||||
|
assert.strictEqual(EVENT_FORMS[0x01].srcField, 'src');
|
||||||
|
assert.strictEqual(EVENT_FORMS[0x01].build({ ti: '00', media: '0' }), '1C0100FE0100');
|
||||||
|
assert.strictEqual(EVENT_FORMS[0x01].build({ ti: '00', media: '' }), '1C0100');
|
||||||
|
// call disconnected: radio link timeout = zero-length cause, custom = bytes
|
||||||
|
assert.strictEqual(EVENT_FORMS[0x02].build({ ti: '00', cause_mode: 'rlt', media: '' }), '1C01009A00');
|
||||||
|
assert.strictEqual(EVENT_FORMS[0x02].build({ ti: '00', cause_mode: 'custom', cause: '10', media: '' }), '1C01009A0110');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('the browser, WLAN and CSG events build their object sets', () => {
|
||||||
|
assert.strictEqual(EVENT_FORMS[0x08].build({ cause: '1' }), 'B40101');
|
||||||
|
assert.strictEqual(EVENT_FORMS[0x11].build({ status: '2' }), 'CB0102');
|
||||||
|
// CSG: source network, access tech + status/mechanism + ID + name + PLMN
|
||||||
|
assert.strictEqual(EVENT_FORMS[0x15].src, '83');
|
||||||
|
assert.strictEqual(EVENT_FORMS[0x15].build({ status: '1', mech: 'manual', tech: '8',
|
||||||
|
csg: '00000001', hnb: 'MyCSG', mcc: '250', mnc: '01' }),
|
||||||
|
'BF0108D5020141D60400000001D7054D79435347890352F010');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('every event type has a parameter form now', () => {
|
||||||
|
assert.ok(!html.includes("note: 'not_yet'"), 'no event may stay unimplemented');
|
||||||
|
});
|
||||||
|
|||||||
@@ -4276,12 +4276,20 @@ def _verify_adm(scc, app, adm_hex):
|
|||||||
'error': 'Security status not satisfied' if sw == '6982' else 'Error'}
|
'error': 'Security status not satisfied' if sw == '6982' else 'Error'}
|
||||||
|
|
||||||
|
|
||||||
def _send_event_download(scc, event_type, event_data=None, drain=True):
|
def _send_event_download(scc, event_type, event_data=None, drain=True, src=None):
|
||||||
"""Send ENVELOPE(Event Download) for the given event type.
|
"""Send ENVELOPE(Event Download) for the given event type.
|
||||||
Builds: CLA C2 0000 Lc D6 [len] (99 01 [type] 82 02 82 81 [extra])"""
|
Builds: CLA C2 0000 Lc D6 [len] (99 01 [type] 82 02 <src> 81 [extra]).
|
||||||
|
`src` is the source device identity (TS 102 223 8.7: '82' terminal,
|
||||||
|
'83' network); the default is the terminal."""
|
||||||
|
src_byte = 0x82
|
||||||
|
if src:
|
||||||
|
try:
|
||||||
|
src_byte = int(str(src), 16) & 0xFF
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
src_byte = 0x82
|
||||||
inner = bytearray()
|
inner = bytearray()
|
||||||
inner.extend([0x99, 0x01, event_type])
|
inner.extend([0x99, 0x01, event_type])
|
||||||
inner.extend([0x82, 0x02, 0x82, 0x81])
|
inner.extend([0x82, 0x02, src_byte, 0x81])
|
||||||
if event_data:
|
if event_data:
|
||||||
inner.extend(event_data)
|
inner.extend(event_data)
|
||||||
if len(inner) < 0x80:
|
if len(inner) < 0x80:
|
||||||
@@ -6849,8 +6857,9 @@ class PysimHandler(BaseHTTPRequestHandler):
|
|||||||
self._send_json({'error': 'event_type is required'}, 400)
|
self._send_json({'error': 'event_type is required'}, 400)
|
||||||
return
|
return
|
||||||
event_data = bytes.fromhex(event_data_hex) if event_data_hex else None
|
event_data = bytes.fromhex(event_data_hex) if event_data_hex else None
|
||||||
|
event_src = body.get('event_src')
|
||||||
try:
|
try:
|
||||||
data, sw = _send_event_download(scc, event_type, event_data)
|
data, sw = _send_event_download(scc, event_type, event_data, src=event_src)
|
||||||
try:
|
try:
|
||||||
ev_type = int(event_type)
|
ev_type = int(event_type)
|
||||||
except (TypeError, ValueError):
|
except (TypeError, ValueError):
|
||||||
|
|||||||
@@ -789,7 +789,7 @@ class TestProactiveDecode(unittest.TestCase):
|
|||||||
class TestEventDownload(unittest.TestCase):
|
class TestEventDownload(unittest.TestCase):
|
||||||
"""ENVELOPE (EVENT DOWNLOAD) assembly, TS 102 223 7.5.11."""
|
"""ENVELOPE (EVENT DOWNLOAD) assembly, TS 102 223 7.5.11."""
|
||||||
|
|
||||||
def _send(self, event_type, event_data):
|
def _send(self, event_type, event_data, src=None):
|
||||||
import pysim_simple_server.server as srv
|
import pysim_simple_server.server as srv
|
||||||
calls = []
|
calls = []
|
||||||
|
|
||||||
@@ -802,7 +802,7 @@ class TestEventDownload(unittest.TestCase):
|
|||||||
cat_cla = '80'
|
cat_cla = '80'
|
||||||
_tp = Tp()
|
_tp = Tp()
|
||||||
|
|
||||||
data, sw = srv._send_event_download(Scc(), event_type, event_data)
|
data, sw = srv._send_event_download(Scc(), event_type, event_data, src=src)
|
||||||
return calls[0], sw
|
return calls[0], sw
|
||||||
|
|
||||||
def test_channel_status_event(self):
|
def test_channel_status_event(self):
|
||||||
@@ -817,6 +817,15 @@ class TestEventDownload(unittest.TestCase):
|
|||||||
self.assertEqual(sw, '9000')
|
self.assertEqual(sw, '9000')
|
||||||
self.assertEqual(apdu, '80c2000009d60799010582028281')
|
self.assertEqual(apdu, '80c2000009d60799010582028281')
|
||||||
|
|
||||||
|
def test_event_source_device_identity_can_be_overridden(self):
|
||||||
|
# MT call / CSG cell selection come from the network (TS 102 223 8.7)
|
||||||
|
apdu, sw = self._send(0x00, None, src='83')
|
||||||
|
self.assertEqual(sw, '9000')
|
||||||
|
self.assertEqual(apdu, '80c2000009d60799010082028381')
|
||||||
|
# an invalid source falls back to the terminal
|
||||||
|
apdu, sw = self._send(0x00, None, src='zz')
|
||||||
|
self.assertEqual(apdu, '80c2000009d60799010082028281')
|
||||||
|
|
||||||
def test_event_with_long_data_uses_the_ber_long_form(self):
|
def test_event_with_long_data_uses_the_ber_long_form(self):
|
||||||
# 128+ bytes of event data: the D6 length becomes '81' <len> (the
|
# 128+ bytes of event data: the D6 length becomes '81' <len> (the
|
||||||
# 7.5.x tables allow 1 or 2 length bytes; a raw byte above 0x7F would
|
# 7.5.x tables allow 1 or 2 length bytes; a raw byte above 0x7F would
|
||||||
|
|||||||
Reference in New Issue
Block a user