v3.14.0: event form object-set fixes + the slices single-envelope mandate
An audit of every event form against the pinned specs (31.111 7.5.x/9.3, 102 223 8.87, TS 101 220's master tag table) found three missing objects, three malformed/wrong ones, and settled the slice-data scope. - 0x00 MT call: the IMS URI object is cut at 100 bytes and now carries the URI truncated object (8.135, F3 00) when the cut happened. - 0x1F Slices: the form gains the Partial NSSAI object (8.154, F9, rel 18); a single envelope carrying it is marked as the Last Envelope (8.79, F0 00) automatically (7.5.27.2). - 0x06 Card reader status and 0x07 Language: the builders now emit their object tags (A0 01 ..., AD ...) - they emitted bare values before. - 0x18 Incoming IMS data: the IARI tag is F6 (76/F6), not F7 (which is the IMPU list tag - the two-column 9.3 table had been misread). - Single-envelope mandate: the event data is capped at 244 bytes (inner 252; the D6 long-form length and the APDU Lc stay in one byte). The server refuses more with a 400 and the PWA shows the size error before sending. Chained (multi-envelope) delivery, the More Data object and envelope sequences are not implemented - documented in the help and the repo AGENTS. - Tests: event form vectors (URI truncation, the restored tags, partial / Last Envelope, the size helper) and the server guard; the release reference and its PWA/code mirrors gain the three objects and the corrected IARI key. - sw cache simple-v375.
This commit is contained in:
@@ -34,6 +34,7 @@ eval(extractFunc(html, 'cmdQualifierShort'));
|
||||
eval(extractBlock('const EVENT_NAMES = {', 'const REJECTION_CAUSES = [').replace(/^const /gm, 'var '));
|
||||
eval(extractBlock('const REJECTION_CAUSES = [', 'const EVENT_FORMS = {').replace(/^const /gm, 'var '));
|
||||
eval(extractBlock('const EVENT_FORMS = {', 'const PLI_QUALIFIERS = [').replace(/^const /gm, 'var '));
|
||||
eval(extractBlock('const EVENT_INNER_MAX = ', 'function pysimEventSendForm').replace(/^const /gm, 'var '));
|
||||
eval(extractFunc(html, 'eventFieldsForRelease'));
|
||||
eval(extractFunc(html, 'encPlmn'));
|
||||
eval(extractFunc(html, 'bytesToHex'));
|
||||
@@ -172,6 +173,11 @@ test('the call events build their 31.111 object sets', () => {
|
||||
// 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');
|
||||
// a URI longer than the 100-byte limit is cut and tagged (8.135)
|
||||
const long = EVENT_FORMS[0x00].build({ ti: '00', ims_uri: 'x'.repeat(120) });
|
||||
assert.ok(long.includes('B164' + '78'.repeat(100)), 'the URI is cut to 100 bytes');
|
||||
assert.ok(long.endsWith('F300'), long.slice(-8));
|
||||
assert.ok(!EVENT_FORMS[0x00].build({ ti: '00', ims_uri: 'sip:a@b' }).includes('F300'));
|
||||
});
|
||||
|
||||
test('the browser, WLAN and CSG events build their object sets', () => {
|
||||
@@ -199,6 +205,9 @@ test('the data/display/search/frames events build their object sets', () => {
|
||||
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');
|
||||
// Card reader status (8.33) and Language (8.45) carry their object tags
|
||||
assert.strictEqual(EVENT_FORMS[0x06].build({ status: '1' }), 'A00101');
|
||||
assert.strictEqual(EVENT_FORMS[0x07].build({ lang: 'en' }), 'AD02656E');
|
||||
});
|
||||
|
||||
test('the IMS, CAG and slices events build their object sets', () => {
|
||||
@@ -207,7 +216,7 @@ test('the IMS, CAG and slices events build their object sets', () => {
|
||||
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');
|
||||
assert.strictEqual(EVENT_FORMS[0x18].build({ iari: 'a' }), 'F60161');
|
||||
// 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' }),
|
||||
@@ -216,6 +225,9 @@ test('the IMS, CAG and slices events build their object sets', () => {
|
||||
assert.strictEqual(EVENT_FORMS[0x1F].src, '83');
|
||||
assert.strictEqual(EVENT_FORMS[0x1F].build({ status: '1', tech: '8', served: '01020304' }),
|
||||
'BF0108D50101D6050101020304');
|
||||
// partial NSSAI: the F9 object + the automatic Last Envelope marker
|
||||
assert.strictEqual(EVENT_FORMS[0x1F].build({ status: '1', partial: '0102' }), 'D50101F9020102F000');
|
||||
assert.ok(!EVENT_FORMS[0x1F].build({ status: '1', served: '01020304' }).includes('F000'));
|
||||
});
|
||||
|
||||
test('the location-status event can carry the location information', () => {
|
||||
@@ -265,10 +277,22 @@ test('event fields introduced after the effective release are hidden', () => {
|
||||
assert.deepStrictEqual(r.hidden.map(f => f.id).sort(), ['code', 'impu']);
|
||||
// slices: the mapping/rejected objects came in Rel-18
|
||||
r = eventFieldsForRelease(EVENT_FORMS[0x1F], 16);
|
||||
assert.deepStrictEqual(r.hidden.map(f => f.id).sort(), ['allowed_map', 'rejected', 'rejected_map']);
|
||||
assert.deepStrictEqual(r.hidden.map(f => f.id).sort(), ['allowed_map', 'partial', 'rejected', 'rejected_map']);
|
||||
assert.strictEqual(eventFieldsForRelease(EVENT_FORMS[0x1F], 18).hidden.length, 0);
|
||||
// a field without a known release is never hidden
|
||||
r = eventFieldsForRelease(EVENT_FORMS[0x12], 4);
|
||||
assert.strictEqual(r.hidden.length, 0);
|
||||
assert.strictEqual(r.fields.length, EVENT_FORMS[0x12].fields.length);
|
||||
});
|
||||
|
||||
test('the single-envelope budget is enforced by the size helper', () => {
|
||||
// inner = 8 header bytes + the event data; D6 81 <len> + inner must stay
|
||||
// within the 1-byte APDU Lc, so the inner data is capped at 252 bytes
|
||||
assert.strictEqual(EVENT_INNER_MAX, 252);
|
||||
assert.strictEqual(eventEnvelopeSize(''), 8);
|
||||
assert.strictEqual(eventEnvelopeSize('AABB'), 10);
|
||||
assert.strictEqual(eventEnvelopeSize('AB'.repeat(244)), 252);
|
||||
assert.ok(eventFitsOneEnvelope('AB'.repeat(244)));
|
||||
assert.ok(!eventFitsOneEnvelope('AB'.repeat(245)));
|
||||
assert.ok(eventFitsOneEnvelope(null));
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user