diff --git a/frontend/help-ru.html b/frontend/help-ru.html index 5f1e7ac..0a406bf 100644 --- a/frontend/help-ru.html +++ b/frontend/help-ru.html @@ -506,6 +506,8 @@
  • Network Rejection — полная адаптивная форма: тип регистрации (LU / GPRS / EPS / 5GS), поля местоположения (MCC, MNC, LAC, RAC, TAC), технология доступа и единый выпадающий список из 53 кодов причин (EMM, GMM, 5GMM и LU)
  • События звонка — MT call (transaction identifier, номер вызывающего + TON → Address, subaddress, IMS URI, media type; источник network), Call connected и Call disconnected (transaction identifier, источник near/far, media type и причина — отсутствует, нулевая форма radio link timeout или свои байты)
  • Browser termination (причина user / error), (I-)WLAN access status (нет покрытия / покрытие / соединение) и CSG cell selection (статус + механизм выбора, access technology, CSG ID, HNB name, PLMN ID; источник network)
  • +
  • Data available (канал + состояние звена + инфо, и длина данных канала, теги B8/B7), Display parameters changed (строки/столбцы + флаги sizing/fonts/effects, тег C0), Network search mode change (manual/automatic, тег E5), Browsing status (код ошибки браузера, тег E4) и Frames information change (фрейм по умолчанию + инфо по фреймам, тег E7)
  • +
  • IMS registration (IMPU URI + IMS status code) и Incoming IMS data (IARI) — источник network; CAG cell selection (статус + механизм, access technology, CAG information list) и Slices status change (allowed / rejected / served / partially, access technology, served S-NSSAIs) — источник network
  • Data Connection Status Change (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
  • Poll Interval Negotiation (0x1C) — предлагает интервал опроса в виде Duration (единица + значение); после отправки показывается ответ UICC (принято / отклонено / изменено + длительность), а значение изменено становится интервалом фонового опроса (TS 102 223 §7.5.22). Форма появляется, когда карта подписана на это событие.
  • diff --git a/frontend/help.html b/frontend/help.html index f7d6ed0..fc9b519 100644 --- a/frontend/help.html +++ b/frontend/help.html @@ -505,6 +505,8 @@
  • Network Rejection — 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
  • Call events — MT call (transaction identifier, calling party number + TON → Address, subaddress, IMS URI, media type; source network), 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)
  • Browser termination (user / error cause), (I-)WLAN access status (no coverage / coverage / connection on-going) and CSG cell selection (status + selection mechanism, access technology, CSG ID, HNB name, PLMN ID; source network)
  • +
  • Data available (channel + link state + channel info, and the channel data length, tags B8/B7), Display parameters changed (rows/columns + sizing/fonts/effects flags, tag C0), Network search mode change (manual/automatic, tag E5), Browsing status (browser error code, tag E4) and Frames information change (default frame + per-frame info, tag E7)
  • +
  • IMS registration (IMPU URIs + IMS status code) and Incoming IMS data (IARI) — source network; CAG cell selection (status + mechanism, access technology, CAG information list) and Slices status change (allowed / rejected / served / partially status, access technology, served S-NSSAIs) — source network
  • Data Connection Status Change (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
  • Poll Interval Negotiation (0x1C) — proposes a poll interval as a Duration (unit + value); after sending, the UICC's answer is shown (accepted / rejected / modified + duration), and a modified duration becomes the background poll interval (TS 102 223 §7.5.22). The form appears when the card subscribed to this event.
  • diff --git a/frontend/index.html b/frontend/index.html index 24359e6..9990188 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -13170,7 +13170,28 @@ const EVENT_FORMS = { ], build(v) { return 'B401' + ((parseInt(v.cause) || 0) & 0xFF).toString(16).padStart(2, '0'); } }, - 0x09: null, + 0x09: { title: 'Data available', + fields: [ + { id: 'channel', label: 'Channel', type: 'select', + opts: [{v:0,l:'0 \u2014 no channel available'}, + {v:1,l:'Channel 1'},{v:2,l:'Channel 2'},{v:3,l:'Channel 3'}, + {v:4,l:'Channel 4'},{v:5,l:'Channel 5'},{v:6,l:'Channel 6'},{v:7,l:'Channel 7'}] }, + { id: 'state', label: 'Link state', type: 'select', + opts: [{v:0x00,l:'Link not established / TCP closed'}, + {v:0x40,l:'TCP in LISTEN state (UICC server mode)'}, + {v:0x80,l:'Link established / TCP established'}] }, + { id: 'info', label: 'Channel info', type: 'select', + opts: [{v:0x00,l:'No further info can be given'}, + {v:0x05,l:'Link dropped (network failure or user cancellation)'}] }, + { id: 'len', label: 'Channel data length', type: 'text', pattern: '[0-9A-Fa-f]{2}', placeholder: 'hex byte, FF = more than 255' }, + ], + build(v) { + const st = (parseInt(v.state) || 0) | (parseInt(v.channel) || 0); + let d = 'B802' + st.toString(16).padStart(2, '0') + ((parseInt(v.info) || 0) & 0xFF).toString(16).padStart(2, '0'); + d += 'B701' + (v.len || 'FF').replace(/[^0-9a-fA-F]/g, '').padStart(2, '0').slice(0, 2); + return d.toUpperCase(); + } + }, 0x0A: { title: 'Channel status', fields: [ { id: 'channel', label: 'Channel', type: 'select', @@ -13197,11 +13218,53 @@ const EVENT_FORMS = { ], build(v) { return 'BF01' + (v.tech|0).toString(16).padStart(2,'0'); } }, - 0x0C: null, + 0x0C: { title: 'Display parameters changed', + fields: [ + { id: 'rows', label: 'Characters down (screen height)', type: 'number', min: '0', max: '31', placeholder: '0\u201331' }, + { id: 'cols', label: 'Characters across (screen width)', type: 'number', min: '0', max: '31', placeholder: '0\u201331' }, + { id: 'sizing', label: 'Screen sizing parameters supported', type: 'select', + opts: [{v:0,l:'No'},{v:1,l:'Yes'}] }, + { id: 'fonts', label: 'Variable size fonts supported', type: 'select', + opts: [{v:0,l:'No'},{v:1,l:'Yes'}] }, + { id: 'effects', label: 'Screen effects (hex byte)', type: 'text', pattern: '[0-9A-Fa-f]{2}', placeholder: 'b1 resizable, b2 wrapping, b3 scrolling, b6 menu width' }, + ], + build(v) { + const b1 = ((parseInt(v.rows) || 0) & 0x1F) | (parseInt(v.sizing) ? 0x80 : 0); + const b2 = ((parseInt(v.cols) || 0) & 0x1F) | (parseInt(v.fonts) ? 0x40 : 0); + const b3 = parseInt((v.effects || '00').replace(/[^0-9a-fA-F]/g, '').padStart(2, '0').slice(0, 2), 16) || 0; + return ('C003' + b1.toString(16).padStart(2, '0') + b2.toString(16).padStart(2, '0') + + b3.toString(16).padStart(2, '0')).toUpperCase(); + } + }, 0x0D: null, - 0x0E: null, - 0x0F: null, - 0x10: null, + 0x0E: { title: 'Network search mode change', + fields: [ + { id: 'mode', label: 'Network search mode', type: 'select', + opts: [{v:0,l:'00 \u2014 manual'},{v:1,l:'01 \u2014 automatic'}] }, + ], + build(v) { return 'E501' + ((parseInt(v.mode) || 0) & 0xFF).toString(16).padStart(2, '0'); } + }, + 0x0F: { title: 'Browsing status', + fields: [ + { id: 'status', label: 'Browsing status (hex bytes)', type: 'text', pattern: '[0-9A-Fa-f]{2,40}', placeholder: 'browser error code, e.g. 00' }, + ], + build(v) { + const h = (v.status || '00').replace(/[^0-9a-fA-F]/g, ''); + return ('E4' + (h.length / 2).toString(16).padStart(2, '0') + h).toUpperCase(); + } + }, + 0x10: { title: 'Frames information change', + fields: [ + { id: 'frame', label: 'Default frame ID', type: 'text', pattern: '[0-9A-Fa-f]{2}', placeholder: '01\u20130F, 00 = no frames' }, + { id: 'list', label: 'Frame information list (hex, optional)', type: 'text', pattern: '[0-9A-Fa-f]{2,80}', placeholder: '2 bytes per frame: down / across' }, + ], + build(v) { + const id = (v.frame || '00').replace(/[^0-9a-fA-F]/g, '').padStart(2, '0').slice(0, 2); + const h = (v.list || '').replace(/[^0-9a-fA-F]/g, ''); + const value = id + h; + return ('E7' + (value.length / 2).toString(16).padStart(2, '0') + value).toUpperCase(); + } + }, 0x11: { title: 'I-WLAN Access Status', fields: [ { id: 'status', label: 'Access status', type: 'select', @@ -13297,7 +13360,39 @@ const EVENT_FORMS = { } }, 0x16: null, - 0x17: null, + 0x17: { title: 'IMS registration', src: '83', + fields: [ + { id: 'impu', label: 'IMPU URI(s) (optional)', type: 'text', placeholder: 'one or more, comma-separated' }, + { id: 'code', label: 'IMS status code (optional)', type: 'text', pattern: '\\d{3}', placeholder: 'e.g. 200' }, + ], + build(v) { + let d = ''; + const uris = (v.impu || '').split(/[\s,;]+/).filter(Boolean); + if (uris.length) { + let inner = ''; + uris.forEach(u => { + const b = new TextEncoder().encode(u).slice(0, 100); + inner += '80' + b.length.toString(16).padStart(2, '0') + bytesToHex(Array.from(b)); + }); + d += 'F7' + (inner.length / 2).toString(16).padStart(2, '0') + inner; + } + if (v.code) { + const b = new TextEncoder().encode(v.code); + d += 'F8' + b.length.toString(16).padStart(2, '0') + bytesToHex(Array.from(b)); + } + return d.toUpperCase(); + } + }, + 0x18: { title: 'Incoming IMS data', src: '83', + fields: [ + { id: 'iari', label: 'IARI (optional)', type: 'text', placeholder: 'e.g. urn:urn-7:3gpp-service.ims.icsi.mmtel' }, + ], + build(v) { + if (!v.iari) return ''; + const b = new TextEncoder().encode(v.iari).slice(0, 100); + return ('F7' + b.length.toString(16).padStart(2, '0') + bytesToHex(Array.from(b))).toUpperCase(); + } + }, 0x1C: { title: 'Poll interval negotiation', fields: [ { id: 'unit', label: 'Time unit', type: 'select', @@ -13364,6 +13459,56 @@ const EVENT_FORMS = { return d.toUpperCase(); } }, + 0x1E: { title: 'CAG cell selection', src: '83', + fields: [ + { id: 'status', label: 'CAG cell selection status', type: 'select', + opts: [{v:0,l:'00 \u2014 not camped on a CAG cell'}, + {v:1,l:'01 \u2014 camped on a CAG cell'}] }, + { id: 'mech', label: 'Selection mechanism', type: 'select', + opts: [{v:'',l:'\u2014 none \u2014'},{v:'manual',l:'manual CAG selection'}, + {v:'other',l:'another CAG selection type'}] }, + { id: 'tech', label: 'Access Technology (optional)', type: 'select', + opts: [{v:'',l:'\u2014 not present \u2014'}].concat(ACCESSTECH) }, + { id: 'list', label: 'CAG information list (optional, hex)', type: 'text', pattern: '[0-9A-Fa-f]{2,80}', placeholder: 'CAG identity list' }, + ], + 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.list) { + const h = v.list.replace(/[^0-9a-fA-F]/g, ''); + d += 'D6' + (h.length / 2).toString(16).padStart(2, '0') + h; + } + return d.toUpperCase(); + } + }, + 0x1F: { title: 'Slices status change', src: '83', + fields: [ + { id: 'status', label: 'Slices status', type: 'select', + opts: [{v:0x01,l:'01 \u2014 allowed'},{v:0x02,l:'02 \u2014 rejected'}, + {v:0x04,l:'04 \u2014 served'},{v:0x81,l:'81 \u2014 partially allowed'}, + {v:0x82,l:'82 \u2014 partially rejected'}] }, + { id: 'tech', label: 'Access Technology (optional)', type: 'select', + opts: [{v:'',l:'\u2014 not present \u2014'}].concat(ACCESSTECH) }, + { id: 'served', label: 'Served S-NSSAIs (optional, hex)', type: 'text', pattern: '[0-9A-Fa-f]{8,80}', placeholder: '4 bytes per S-NSSAI (SST + SD)' }, + ], + build(v) { + let d = ''; + if (v.tech !== '' && v.tech !== undefined) + d += 'BF01' + ((parseInt(v.tech) || 0) & 0xFF).toString(16).padStart(2, '0'); + d += 'D501' + ((parseInt(v.status) || 1) & 0xFF).toString(16).padStart(2, '0'); + const h = (v.served || '').replace(/[^0-9a-fA-F]/g, ''); + if (h) { + const n = Math.floor(h.length / 8); + d += 'D6' + (1 + n * 4).toString(16).padStart(2, '0') + + n.toString(16).padStart(2, '0') + h.slice(0, n * 8); + } + return d.toUpperCase(); + } + }, }; const PLI_QUALIFIERS = [ diff --git a/frontend/sw.js b/frontend/sw.js index 0c4de4e..5fc904d 100644 --- a/frontend/sw.js +++ b/frontend/sw.js @@ -1,4 +1,4 @@ -const CACHE = 'simple-v348'; +const CACHE = 'simple-v349'; const URLS = [ 'index.html', 'help.html', diff --git a/frontend/tests/event_forms.test.js b/frontend/tests/event_forms.test.js index aa68d95..f322a92 100644 --- a/frontend/tests/event_forms.test.js +++ b/frontend/tests/event_forms.test.js @@ -186,3 +186,33 @@ test('the browser, WLAN and CSG events build their object sets', () => { test('every event type has a parameter form now', () => { assert.ok(!html.includes("note: 'not_yet'"), 'no event may stay unimplemented'); }); + +test('the data/display/search/frames events build their object sets', () => { + // Data available: Channel status (B8) + Channel data length (B7) + assert.strictEqual(EVENT_FORMS[0x09].build({ channel: '2', state: '128', info: '0', len: 'FF' }), + 'B8028200B701FF'); + // Display parameters: rows/cols + sizing/fonts/effects bytes + assert.strictEqual(EVENT_FORMS[0x0C].build({ rows: '4', cols: '16', sizing: '1', fonts: '0', effects: '00' }), + 'C003841000'); + assert.strictEqual(EVENT_FORMS[0x0E].build({ mode: '1' }), 'E50101'); + assert.strictEqual(EVENT_FORMS[0x0F].build({ status: '12' }), 'E40112'); + assert.strictEqual(EVENT_FORMS[0x0F].build({ status: '' }), 'E40100'); + assert.strictEqual(EVENT_FORMS[0x10].build({ frame: '01', list: '0410' }), 'E703010410'); +}); + +test('the IMS, CAG and slices events build their object sets', () => { + // IMS registration: source network, IMPU list (80-tagged URIs) + status code + assert.strictEqual(EVENT_FORMS[0x17].src, '83'); + assert.strictEqual(EVENT_FORMS[0x17].build({ impu: 'sip:a@b', code: '200' }), + 'F70980077369703A614062F803323030'); + assert.strictEqual(EVENT_FORMS[0x18].src, '83'); + assert.strictEqual(EVENT_FORMS[0x18].build({ iari: 'a' }), 'F70161'); + // CAG cell selection: tech + status/mechanism + information list + assert.strictEqual(EVENT_FORMS[0x1E].src, '83'); + assert.strictEqual(EVENT_FORMS[0x1E].build({ status: '1', mech: 'manual', tech: '8', list: '00000001' }), + 'BF0108D5020141D60400000001'); + // Slices status change: tech + status + served S-NSSAIs (count + 4 bytes each) + assert.strictEqual(EVENT_FORMS[0x1F].src, '83'); + assert.strictEqual(EVENT_FORMS[0x1F].build({ status: '1', tech: '8', served: '01020304' }), + 'BF0108D50101D6050101020304'); +});