@onekeyfe/hd-transport 1.2.0-alpha.3 → 1.2.0-alpha.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/__tests__/messages.test.js +142 -0
- package/__tests__/protocol-v2-ble-frame-writer.test.js +119 -0
- package/__tests__/protocol-v2-link-manager.test.js +389 -0
- package/__tests__/protocol-v2-usb-transport-base.test.js +330 -0
- package/__tests__/protocol-v2.test.js +555 -119
- package/dist/constants.d.ts +6 -3
- package/dist/constants.d.ts.map +1 -1
- package/dist/index.d.ts +981 -278
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +759 -247
- package/dist/protocols/index.d.ts +14 -5
- package/dist/protocols/index.d.ts.map +1 -1
- package/dist/protocols/v2/ble-frame-writer.d.ts +15 -0
- package/dist/protocols/v2/ble-frame-writer.d.ts.map +1 -0
- package/dist/protocols/v2/constants.d.ts +1 -0
- package/dist/protocols/v2/constants.d.ts.map +1 -1
- package/dist/protocols/v2/decode.d.ts +13 -2
- package/dist/protocols/v2/decode.d.ts.map +1 -1
- package/dist/protocols/v2/encode.d.ts +2 -3
- package/dist/protocols/v2/encode.d.ts.map +1 -1
- package/dist/protocols/v2/errors.d.ts +8 -0
- package/dist/protocols/v2/errors.d.ts.map +1 -0
- package/dist/protocols/v2/frame-assembler.d.ts.map +1 -1
- package/dist/protocols/v2/index.d.ts +2 -1
- package/dist/protocols/v2/index.d.ts.map +1 -1
- package/dist/protocols/v2/link-manager.d.ts +38 -0
- package/dist/protocols/v2/link-manager.d.ts.map +1 -0
- package/dist/protocols/v2/sequence-cursor.d.ts +5 -0
- package/dist/protocols/v2/sequence-cursor.d.ts.map +1 -0
- package/dist/protocols/v2/session.d.ts +19 -5
- package/dist/protocols/v2/session.d.ts.map +1 -1
- package/dist/protocols/v2/usb-transport-base.d.ts +32 -0
- package/dist/protocols/v2/usb-transport-base.d.ts.map +1 -0
- package/dist/serialization/protobuf/decode.d.ts.map +1 -1
- package/dist/types/messages.d.ts +523 -158
- package/dist/types/messages.d.ts.map +1 -1
- package/dist/types/transport.d.ts +13 -3
- package/dist/types/transport.d.ts.map +1 -1
- package/messages-protocol-v2.json +796 -620
- package/package.json +3 -3
- package/scripts/protobuf-build.sh +96 -310
- package/scripts/protobuf-patches/index.js +1 -0
- package/scripts/protobuf-types.js +19 -1
- package/src/constants.ts +29 -15
- package/src/index.ts +11 -1
- package/src/protocols/index.ts +39 -40
- package/src/protocols/v2/ble-frame-writer.ts +77 -0
- package/src/protocols/v2/constants.ts +1 -0
- package/src/protocols/v2/crc8.ts +1 -1
- package/src/protocols/v2/decode.ts +71 -37
- package/src/protocols/v2/encode.ts +2 -28
- package/src/protocols/v2/errors.ts +25 -0
- package/src/protocols/v2/frame-assembler.ts +6 -4
- package/src/protocols/v2/index.ts +2 -1
- package/src/protocols/v2/link-manager.ts +186 -0
- package/src/protocols/v2/sequence-cursor.ts +10 -0
- package/src/protocols/v2/session.ts +157 -201
- package/src/protocols/v2/usb-transport-base.ts +213 -0
- package/src/serialization/protobuf/decode.ts +4 -1
- package/src/types/messages.ts +623 -181
- package/src/types/transport.ts +13 -3
- package/dist/protocols/v2/debug.d.ts +0 -13
- package/dist/protocols/v2/debug.d.ts.map +0 -1
- package/src/protocols/v2/debug.ts +0 -26
|
@@ -1,9 +1,18 @@
|
|
|
1
1
|
const { ProtocolV2 } = require('../src/protocols');
|
|
2
2
|
const { parseConfigure } = require('../src/serialization/protobuf/messages');
|
|
3
|
-
const
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
const {
|
|
4
|
+
ProtocolV2FrameAssembler,
|
|
5
|
+
ProtocolV2LinkError,
|
|
6
|
+
ProtocolV2SequenceCursor,
|
|
7
|
+
ProtocolV2Session,
|
|
8
|
+
hexToBytes,
|
|
9
|
+
probeProtocolV2,
|
|
10
|
+
} = require('../src/protocols/v2/session');
|
|
6
11
|
const protocolV2 = require('../src/protocols/v2');
|
|
12
|
+
const {
|
|
13
|
+
PROTOCOL_V2_DEFAULT_RESPONSE_TIMEOUT_MS,
|
|
14
|
+
PROTOCOL_V2_FRAME_MAX_BYTES,
|
|
15
|
+
} = require('../src/constants');
|
|
7
16
|
|
|
8
17
|
const protocolV1Messages = parseConfigure({
|
|
9
18
|
nested: {
|
|
@@ -55,21 +64,22 @@ const protocolV1Messages = parseConfigure({
|
|
|
55
64
|
|
|
56
65
|
const protocolV2Messages = parseConfigure({
|
|
57
66
|
nested: {
|
|
58
|
-
|
|
67
|
+
ProtocolInfoRequest: {
|
|
59
68
|
fields: {},
|
|
60
69
|
},
|
|
61
|
-
|
|
70
|
+
ProtocolInfo: {
|
|
62
71
|
fields: {
|
|
63
|
-
|
|
72
|
+
version: {
|
|
64
73
|
type: 'uint32',
|
|
65
74
|
id: 1,
|
|
66
75
|
},
|
|
67
|
-
|
|
76
|
+
supported_messages: {
|
|
68
77
|
type: 'uint32',
|
|
69
78
|
id: 2,
|
|
79
|
+
rule: 'repeated',
|
|
70
80
|
},
|
|
71
|
-
|
|
72
|
-
type: '
|
|
81
|
+
protobuf_definition: {
|
|
82
|
+
type: 'string',
|
|
73
83
|
id: 3,
|
|
74
84
|
},
|
|
75
85
|
},
|
|
@@ -90,17 +100,80 @@ const protocolV2Messages = parseConfigure({
|
|
|
90
100
|
},
|
|
91
101
|
},
|
|
92
102
|
},
|
|
93
|
-
|
|
94
|
-
fields: {
|
|
103
|
+
Failure: {
|
|
104
|
+
fields: {
|
|
105
|
+
code: {
|
|
106
|
+
type: 'uint32',
|
|
107
|
+
id: 1,
|
|
108
|
+
},
|
|
109
|
+
subcode: {
|
|
110
|
+
type: 'uint32',
|
|
111
|
+
id: 2,
|
|
112
|
+
},
|
|
113
|
+
message: {
|
|
114
|
+
type: 'string',
|
|
115
|
+
id: 3,
|
|
116
|
+
},
|
|
117
|
+
},
|
|
95
118
|
},
|
|
96
|
-
|
|
119
|
+
DeviceFirmwareTarget: {
|
|
97
120
|
fields: {
|
|
98
121
|
target_id: {
|
|
99
122
|
type: 'uint32',
|
|
100
123
|
id: 1,
|
|
101
124
|
},
|
|
102
|
-
|
|
125
|
+
path: {
|
|
126
|
+
type: 'string',
|
|
127
|
+
id: 2,
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
DeviceFirmwareUpdateRequest: {
|
|
132
|
+
fields: {
|
|
133
|
+
targets: {
|
|
134
|
+
type: 'DeviceFirmwareTarget',
|
|
135
|
+
id: 1,
|
|
136
|
+
rule: 'repeated',
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
DeviceFirmwareUpdateRecord: {
|
|
141
|
+
fields: {
|
|
142
|
+
target_id: {
|
|
103
143
|
type: 'uint32',
|
|
144
|
+
id: 1,
|
|
145
|
+
},
|
|
146
|
+
status: {
|
|
147
|
+
type: 'uint32',
|
|
148
|
+
id: 10,
|
|
149
|
+
},
|
|
150
|
+
payload_version: {
|
|
151
|
+
type: 'uint32',
|
|
152
|
+
id: 20,
|
|
153
|
+
},
|
|
154
|
+
path: {
|
|
155
|
+
type: 'string',
|
|
156
|
+
id: 30,
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
DeviceFirmwareUpdateStatus: {
|
|
161
|
+
fields: {
|
|
162
|
+
records: {
|
|
163
|
+
type: 'DeviceFirmwareUpdateRecord',
|
|
164
|
+
id: 1,
|
|
165
|
+
rule: 'repeated',
|
|
166
|
+
},
|
|
167
|
+
},
|
|
168
|
+
},
|
|
169
|
+
DeviceSession: {
|
|
170
|
+
fields: {
|
|
171
|
+
session_id: {
|
|
172
|
+
type: 'bytes',
|
|
173
|
+
id: 1,
|
|
174
|
+
},
|
|
175
|
+
btc_test_address: {
|
|
176
|
+
type: 'string',
|
|
104
177
|
id: 2,
|
|
105
178
|
},
|
|
106
179
|
},
|
|
@@ -130,13 +203,15 @@ const protocolV2Messages = parseConfigure({
|
|
|
130
203
|
},
|
|
131
204
|
MessageType: {
|
|
132
205
|
values: {
|
|
133
|
-
|
|
134
|
-
|
|
206
|
+
MessageType_ProtocolInfoRequest: 60200,
|
|
207
|
+
MessageType_ProtocolInfo: 60201,
|
|
135
208
|
MessageType_Ping: 60206,
|
|
136
209
|
MessageType_Success: 60207,
|
|
210
|
+
MessageType_Failure: 60208,
|
|
137
211
|
MessageType_FileWrite: 60805,
|
|
138
|
-
|
|
139
|
-
|
|
212
|
+
MessageType_DeviceFirmwareUpdateRequest: 61000,
|
|
213
|
+
MessageType_DeviceFirmwareUpdateStatus: 61002,
|
|
214
|
+
MessageType_DeviceSession: 60607,
|
|
140
215
|
MessageType_PartialNested: 62000,
|
|
141
216
|
},
|
|
142
217
|
},
|
|
@@ -150,6 +225,7 @@ const schemas = {
|
|
|
150
225
|
|
|
151
226
|
const rewriteSeq = (frame, seq) => {
|
|
152
227
|
const copy = new Uint8Array(frame);
|
|
228
|
+
copy[4] = 1;
|
|
153
229
|
copy[6] = seq;
|
|
154
230
|
copy[copy.length - 1] = protocolV2.crc8(copy, copy.length - 1);
|
|
155
231
|
return copy;
|
|
@@ -157,10 +233,10 @@ const rewriteSeq = (frame, seq) => {
|
|
|
157
233
|
|
|
158
234
|
describe('Protocol V2 framing and session', () => {
|
|
159
235
|
test('encodes and decodes Protocol V2 protobuf frames', () => {
|
|
160
|
-
const frame = ProtocolV2.encodeFrame(schemas, '
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
236
|
+
const frame = ProtocolV2.encodeFrame(schemas, 'ProtocolInfo', {
|
|
237
|
+
version: 1,
|
|
238
|
+
supported_messages: [60200, 60201],
|
|
239
|
+
protobuf_definition: 'proto',
|
|
164
240
|
});
|
|
165
241
|
|
|
166
242
|
const parsed = protocolV2.decodeFrame(frame);
|
|
@@ -168,15 +244,15 @@ describe('Protocol V2 framing and session', () => {
|
|
|
168
244
|
|
|
169
245
|
const decoded = ProtocolV2.decodeFrame(schemas, frame);
|
|
170
246
|
expect(decoded).toEqual({
|
|
171
|
-
type: '
|
|
172
|
-
messageName: '
|
|
247
|
+
type: 'ProtocolInfo',
|
|
248
|
+
messageName: 'ProtocolInfo',
|
|
173
249
|
messageTypeId: 60201,
|
|
174
250
|
pbPayload: parsed.pbPayload,
|
|
175
251
|
seq: parsed.seq,
|
|
176
252
|
message: {
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
253
|
+
version: 1,
|
|
254
|
+
supported_messages: [60200, 60201],
|
|
255
|
+
protobuf_definition: 'proto',
|
|
180
256
|
},
|
|
181
257
|
});
|
|
182
258
|
});
|
|
@@ -201,6 +277,34 @@ describe('Protocol V2 framing and session', () => {
|
|
|
201
277
|
expect(decoded.message).toEqual({ message: 'ok' });
|
|
202
278
|
});
|
|
203
279
|
|
|
280
|
+
test('round-trips a DeviceSession response with a 32-byte session id', () => {
|
|
281
|
+
const sessionId = '01'.repeat(32);
|
|
282
|
+
const frame = ProtocolV2.encodeFrame(schemas, 'DeviceSession', {
|
|
283
|
+
session_id: sessionId,
|
|
284
|
+
btc_test_address: 'tb1qwallet',
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
const decoded = ProtocolV2.decodeFrame(schemas, frame);
|
|
288
|
+
|
|
289
|
+
expect(decoded.type).toBe('DeviceSession');
|
|
290
|
+
expect(decoded.message).toEqual({
|
|
291
|
+
session_id: sessionId,
|
|
292
|
+
btc_test_address: 'tb1qwallet',
|
|
293
|
+
});
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
test('reports malformed DeviceSession protobuf as a schema-compatible transport error', () => {
|
|
297
|
+
const malformedPayload = new Uint8Array(32);
|
|
298
|
+
malformedPayload[0] = 0x0a;
|
|
299
|
+
malformedPayload[1] = 101;
|
|
300
|
+
const frame = protocolV2.encodeProtobufFrame(60607, malformedPayload);
|
|
301
|
+
|
|
302
|
+
expect(() => ProtocolV2.decodeFrame(schemas, frame)).toThrow(
|
|
303
|
+
'Protocol V2 protobuf decode failed for "DeviceSession" (60607, 32-byte payload); ' +
|
|
304
|
+
'the payload is malformed or incompatible with the active SDK schema.'
|
|
305
|
+
);
|
|
306
|
+
});
|
|
307
|
+
|
|
204
308
|
test('decodes missing optional nested messages as null', () => {
|
|
205
309
|
const frame = ProtocolV2.encodeFrame(schemas, 'PartialNested', {
|
|
206
310
|
label: 'only label',
|
|
@@ -214,10 +318,9 @@ describe('Protocol V2 framing and session', () => {
|
|
|
214
318
|
});
|
|
215
319
|
|
|
216
320
|
test('reassembles split Protocol V2 frames and rejects oversized frames', () => {
|
|
217
|
-
const frame = ProtocolV2.encodeFrame(schemas, '
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
patch_version: 0,
|
|
321
|
+
const frame = ProtocolV2.encodeFrame(schemas, 'ProtocolInfo', {
|
|
322
|
+
version: 1,
|
|
323
|
+
supported_messages: [],
|
|
221
324
|
});
|
|
222
325
|
const assembler = new ProtocolV2FrameAssembler();
|
|
223
326
|
|
|
@@ -228,16 +331,35 @@ describe('Protocol V2 framing and session', () => {
|
|
|
228
331
|
expect(() => assembler.push(oversized)).toThrow('Protocol V2 frame too large');
|
|
229
332
|
});
|
|
230
333
|
|
|
334
|
+
test('enforces a transport-specific receive frame boundary', () => {
|
|
335
|
+
const assembler = new ProtocolV2FrameAssembler(2048);
|
|
336
|
+
const oversizedHeader = new Uint8Array([0x5a, 0x01, 0x08]);
|
|
337
|
+
|
|
338
|
+
expect(() => assembler.push(oversizedHeader)).toThrow('Protocol V2 frame too large: 2049');
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
test('enforces the firmware 4200-byte Protocol V2 frame boundary', () => {
|
|
342
|
+
const boundaryFrame = ProtocolV2.encodeFrame(schemas, 'Ping', {
|
|
343
|
+
message: 'x'.repeat(4187),
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
expect(PROTOCOL_V2_FRAME_MAX_BYTES).toBe(4200);
|
|
347
|
+
expect(boundaryFrame).toHaveLength(PROTOCOL_V2_FRAME_MAX_BYTES);
|
|
348
|
+
expect(() =>
|
|
349
|
+
ProtocolV2.encodeFrame(schemas, 'Ping', {
|
|
350
|
+
message: 'x'.repeat(4188),
|
|
351
|
+
})
|
|
352
|
+
).toThrow('Protocol V2 frame too large: 4201 > 4200');
|
|
353
|
+
});
|
|
354
|
+
|
|
231
355
|
test('keeps bytes after the first complete frame for the next read', () => {
|
|
232
|
-
const first = ProtocolV2.encodeFrame(schemas, '
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
patch_version: 0,
|
|
356
|
+
const first = ProtocolV2.encodeFrame(schemas, 'ProtocolInfo', {
|
|
357
|
+
version: 1,
|
|
358
|
+
supported_messages: [],
|
|
236
359
|
});
|
|
237
|
-
const second = ProtocolV2.encodeFrame(schemas, '
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
patch_version: 0,
|
|
360
|
+
const second = ProtocolV2.encodeFrame(schemas, 'ProtocolInfo', {
|
|
361
|
+
version: 2,
|
|
362
|
+
supported_messages: [],
|
|
241
363
|
});
|
|
242
364
|
const assembler = new ProtocolV2FrameAssembler();
|
|
243
365
|
const combined = new Uint8Array(first.length + second.length);
|
|
@@ -250,10 +372,9 @@ describe('Protocol V2 framing and session', () => {
|
|
|
250
372
|
|
|
251
373
|
test('session writes one encoded frame and decodes the response frame', async () => {
|
|
252
374
|
const written = [];
|
|
253
|
-
const response = ProtocolV2.encodeFrame(schemas, '
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
patch_version: 1,
|
|
375
|
+
const response = ProtocolV2.encodeFrame(schemas, 'ProtocolInfo', {
|
|
376
|
+
version: 2,
|
|
377
|
+
supported_messages: [60206],
|
|
257
378
|
});
|
|
258
379
|
const session = new ProtocolV2Session({
|
|
259
380
|
schemas,
|
|
@@ -266,51 +387,251 @@ describe('Protocol V2 framing and session', () => {
|
|
|
266
387
|
Promise.resolve(rewriteSeq(response, protocolV2.decodeFrame(written[0]).seq)),
|
|
267
388
|
});
|
|
268
389
|
|
|
269
|
-
const result = await session.call('
|
|
390
|
+
const result = await session.call('ProtocolInfoRequest', {});
|
|
270
391
|
|
|
271
392
|
expect(written).toHaveLength(1);
|
|
272
393
|
expect(written[0][4]).toBe(1);
|
|
273
394
|
expect(written[0][5]).toBe(0);
|
|
274
395
|
expect(protocolV2.decodeFrame(written[0]).messageTypeId).toBe(60200);
|
|
275
396
|
expect(result).toEqual({
|
|
276
|
-
type: '
|
|
397
|
+
type: 'ProtocolInfo',
|
|
398
|
+
message: {
|
|
399
|
+
version: 2,
|
|
400
|
+
supported_messages: [60206],
|
|
401
|
+
protobuf_definition: null,
|
|
402
|
+
},
|
|
403
|
+
});
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
test('session does not log Protocol V2 TX and RX frames', async () => {
|
|
407
|
+
const written = [];
|
|
408
|
+
const logger = { debug: jest.fn() };
|
|
409
|
+
const response = ProtocolV2.encodeFrame(schemas, 'ProtocolInfo', {
|
|
410
|
+
version: 2,
|
|
411
|
+
supported_messages: [60206],
|
|
412
|
+
protobuf_definition: 'sensitive-schema',
|
|
413
|
+
});
|
|
414
|
+
const session = new ProtocolV2Session({
|
|
415
|
+
schemas,
|
|
416
|
+
router: 1,
|
|
417
|
+
logger,
|
|
418
|
+
logPrefix: 'ProtocolV2 Test',
|
|
419
|
+
writeFrame: frame => {
|
|
420
|
+
written.push(frame);
|
|
421
|
+
return Promise.resolve();
|
|
422
|
+
},
|
|
423
|
+
readFrame: () =>
|
|
424
|
+
Promise.resolve(rewriteSeq(response, protocolV2.decodeFrame(written[0]).seq)),
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
await session.call('ProtocolInfoRequest', {});
|
|
428
|
+
|
|
429
|
+
expect(logger.debug).not.toHaveBeenCalled();
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
test('session does not log RX frame metadata when protobuf payload decoding fails', async () => {
|
|
433
|
+
const logger = { debug: jest.fn() };
|
|
434
|
+
const response = protocolV2.encodeProtobufFrame(
|
|
435
|
+
60208,
|
|
436
|
+
hexToBytes('0801121648616e646c6572206e6f742072656769737465726564')
|
|
437
|
+
);
|
|
438
|
+
const session = new ProtocolV2Session({
|
|
439
|
+
schemas,
|
|
440
|
+
router: 1,
|
|
441
|
+
logger,
|
|
442
|
+
logPrefix: 'ProtocolV2 Test',
|
|
443
|
+
writeFrame: () => Promise.resolve(),
|
|
444
|
+
readFrame: () => Promise.resolve(rewriteSeq(response, 1)),
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
await expect(session.call('Ping', { message: 'hello' })).rejects.toThrow();
|
|
448
|
+
|
|
449
|
+
expect(logger.debug).not.toHaveBeenCalled();
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
test('session skips Proto Link ACK frames before decoding the protobuf response', async () => {
|
|
453
|
+
const ack = new Uint8Array(8);
|
|
454
|
+
ack[0] = 0x5a;
|
|
455
|
+
ack[1] = 8;
|
|
456
|
+
ack[2] = 0;
|
|
457
|
+
ack[4] = 1;
|
|
458
|
+
ack[5] = 1;
|
|
459
|
+
ack[6] = 1;
|
|
460
|
+
ack[3] = protocolV2.crc8(ack, 3);
|
|
461
|
+
ack[7] = protocolV2.crc8(ack, 7);
|
|
462
|
+
|
|
463
|
+
const response = ProtocolV2.encodeFrame(schemas, 'Success', {
|
|
464
|
+
message: 'ok',
|
|
465
|
+
});
|
|
466
|
+
const readFrame = jest
|
|
467
|
+
.fn()
|
|
468
|
+
.mockResolvedValueOnce(ack)
|
|
469
|
+
.mockResolvedValueOnce(rewriteSeq(response, 1));
|
|
470
|
+
const session = new ProtocolV2Session({
|
|
471
|
+
schemas,
|
|
472
|
+
router: 1,
|
|
473
|
+
writeFrame: () => Promise.resolve(),
|
|
474
|
+
readFrame,
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
await expect(session.call('Ping', { message: 'hello' })).resolves.toEqual({
|
|
478
|
+
type: 'Success',
|
|
277
479
|
message: {
|
|
278
|
-
|
|
279
|
-
minor_version: 0,
|
|
280
|
-
patch_version: 1,
|
|
480
|
+
message: 'ok',
|
|
281
481
|
},
|
|
282
482
|
});
|
|
483
|
+
expect(readFrame).toHaveBeenCalledTimes(2);
|
|
283
484
|
});
|
|
284
485
|
|
|
285
|
-
test('session
|
|
486
|
+
test('session accepts a terminal response before an ACK', async () => {
|
|
286
487
|
const response = ProtocolV2.encodeFrame(schemas, 'Success', {
|
|
287
488
|
message: 'ok',
|
|
288
489
|
});
|
|
289
490
|
const session = new ProtocolV2Session({
|
|
290
491
|
schemas,
|
|
291
492
|
router: 1,
|
|
292
|
-
writeFrame: () =>
|
|
293
|
-
|
|
294
|
-
setTimeout(resolve, 30);
|
|
295
|
-
}),
|
|
296
|
-
readFrame: () => Promise.resolve(response),
|
|
493
|
+
writeFrame: () => Promise.resolve(),
|
|
494
|
+
readFrame: () => Promise.resolve(rewriteSeq(response, 1)),
|
|
297
495
|
});
|
|
298
496
|
|
|
299
497
|
await expect(
|
|
300
|
-
session.call('Ping', { message: 'hello' }, {
|
|
498
|
+
session.call('Ping', { message: 'hello' }, { expectedTypes: ['Success'] })
|
|
499
|
+
).resolves.toEqual({
|
|
500
|
+
type: 'Success',
|
|
501
|
+
message: {
|
|
502
|
+
message: 'ok',
|
|
503
|
+
},
|
|
504
|
+
});
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
test('session uses the unified response timeout before the first ACK', async () => {
|
|
508
|
+
const response = ProtocolV2.encodeFrame(schemas, 'Success', {
|
|
509
|
+
message: 'ok',
|
|
510
|
+
});
|
|
511
|
+
const readFrame = jest.fn(() => Promise.resolve(rewriteSeq(response, 1)));
|
|
512
|
+
const session = new ProtocolV2Session({
|
|
513
|
+
schemas,
|
|
514
|
+
router: 1,
|
|
515
|
+
writeFrame: () => Promise.resolve(),
|
|
516
|
+
readFrame,
|
|
517
|
+
});
|
|
518
|
+
|
|
519
|
+
await expect(
|
|
520
|
+
session.call(
|
|
521
|
+
'Ping',
|
|
522
|
+
{ message: 'hello' },
|
|
523
|
+
{
|
|
524
|
+
timeoutMs: 12_345,
|
|
525
|
+
expectedTypes: ['Success'],
|
|
526
|
+
}
|
|
527
|
+
)
|
|
301
528
|
).resolves.toEqual({
|
|
302
529
|
type: 'Success',
|
|
303
530
|
message: {
|
|
304
531
|
message: 'ok',
|
|
305
532
|
},
|
|
306
533
|
});
|
|
534
|
+
expect(readFrame).toHaveBeenCalledWith(expect.objectContaining({ timeoutMs: 12_345 }));
|
|
535
|
+
});
|
|
536
|
+
|
|
537
|
+
test('session rejects an ACK that belongs to another request sequence', async () => {
|
|
538
|
+
const ack = new Uint8Array(8);
|
|
539
|
+
ack[0] = 0x5a;
|
|
540
|
+
ack[1] = 8;
|
|
541
|
+
ack[4] = 1;
|
|
542
|
+
ack[5] = 1;
|
|
543
|
+
ack[6] = 2;
|
|
544
|
+
ack[3] = protocolV2.crc8(ack, 3);
|
|
545
|
+
ack[7] = protocolV2.crc8(ack, 7);
|
|
546
|
+
const session = new ProtocolV2Session({
|
|
547
|
+
schemas,
|
|
548
|
+
router: 1,
|
|
549
|
+
writeFrame: () => Promise.resolve(),
|
|
550
|
+
readFrame: () => Promise.resolve(ack),
|
|
551
|
+
});
|
|
552
|
+
|
|
553
|
+
await expect(session.call('Ping', { message: 'hello' })).rejects.toEqual(
|
|
554
|
+
expect.objectContaining({
|
|
555
|
+
name: 'ProtocolV2LinkError',
|
|
556
|
+
code: 'ack-sequence',
|
|
557
|
+
message: 'Protocol V2 ACK sequence mismatch: expected 1, got 2',
|
|
558
|
+
})
|
|
559
|
+
);
|
|
560
|
+
});
|
|
561
|
+
|
|
562
|
+
test('session rejects a response from another routing channel', async () => {
|
|
563
|
+
const response = ProtocolV2.encodeFrame(schemas, 'Success', { message: 'wrong route' });
|
|
564
|
+
const session = new ProtocolV2Session({
|
|
565
|
+
schemas,
|
|
566
|
+
router: 1,
|
|
567
|
+
writeFrame: () => Promise.resolve(),
|
|
568
|
+
readFrame: () => Promise.resolve(response),
|
|
569
|
+
});
|
|
570
|
+
|
|
571
|
+
await expect(session.call('Ping', { message: 'hello' })).rejects.toBeInstanceOf(
|
|
572
|
+
ProtocolV2LinkError
|
|
573
|
+
);
|
|
307
574
|
});
|
|
308
575
|
|
|
309
|
-
test('session
|
|
310
|
-
const
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
576
|
+
test('session rejects BLE frames above its configured frame limit before writing', async () => {
|
|
577
|
+
const writeFrame = jest.fn().mockResolvedValue(undefined);
|
|
578
|
+
const response = ProtocolV2.encodeFrame(schemas, 'Success', {
|
|
579
|
+
message: 'ok',
|
|
580
|
+
});
|
|
581
|
+
const session = new ProtocolV2Session({
|
|
582
|
+
schemas,
|
|
583
|
+
router: 1,
|
|
584
|
+
maxFrameBytes: 2048,
|
|
585
|
+
writeFrame,
|
|
586
|
+
readFrame: () => Promise.resolve(rewriteSeq(response, 1)),
|
|
587
|
+
});
|
|
588
|
+
|
|
589
|
+
await expect(session.call('Ping', { message: 'x'.repeat(2048) })).rejects.toThrow(
|
|
590
|
+
'Protocol V2 frame too large for transport: 2061 > 2048'
|
|
591
|
+
);
|
|
592
|
+
expect(writeFrame).not.toHaveBeenCalled();
|
|
593
|
+
});
|
|
594
|
+
|
|
595
|
+
test('session watchdog covers a write that never settles', async () => {
|
|
596
|
+
const readFrame = jest.fn();
|
|
597
|
+
let writeSignal;
|
|
598
|
+
const session = new ProtocolV2Session({
|
|
599
|
+
schemas,
|
|
600
|
+
router: 1,
|
|
601
|
+
writeFrame: (_frame, context) => {
|
|
602
|
+
writeSignal = context.signal;
|
|
603
|
+
return new Promise(() => {});
|
|
604
|
+
},
|
|
605
|
+
readFrame,
|
|
606
|
+
});
|
|
607
|
+
|
|
608
|
+
await expect(
|
|
609
|
+
session.call('Ping', { message: 'hello' }, { timeoutMs: 10, expectedTypes: ['Success'] })
|
|
610
|
+
).rejects.toThrow('Protocol V2 response timeout after 10ms for Ping');
|
|
611
|
+
expect(writeSignal.aborted).toBe(true);
|
|
612
|
+
expect(readFrame).not.toHaveBeenCalled();
|
|
613
|
+
});
|
|
614
|
+
|
|
615
|
+
test('session watchdog covers prepareCall before writing', async () => {
|
|
616
|
+
const writeFrame = jest.fn();
|
|
617
|
+
const session = new ProtocolV2Session({
|
|
618
|
+
schemas,
|
|
619
|
+
router: 1,
|
|
620
|
+
prepareCall: () => new Promise(() => {}),
|
|
621
|
+
writeFrame,
|
|
622
|
+
readFrame: jest.fn(),
|
|
623
|
+
});
|
|
624
|
+
|
|
625
|
+
await expect(session.call('Ping', { message: 'hello' }, { timeoutMs: 10 })).rejects.toThrow(
|
|
626
|
+
'Protocol V2 response timeout after 10ms for Ping'
|
|
627
|
+
);
|
|
628
|
+
expect(writeFrame).not.toHaveBeenCalled();
|
|
629
|
+
});
|
|
630
|
+
|
|
631
|
+
test('session logs a device-owned response seq without rejecting the frame', async () => {
|
|
632
|
+
const response = ProtocolV2.encodeFrame(schemas, 'ProtocolInfo', {
|
|
633
|
+
version: 2,
|
|
634
|
+
supported_messages: [],
|
|
314
635
|
});
|
|
315
636
|
const logger = {
|
|
316
637
|
debug: jest.fn(),
|
|
@@ -323,18 +644,53 @@ describe('Protocol V2 framing and session', () => {
|
|
|
323
644
|
logger,
|
|
324
645
|
});
|
|
325
646
|
|
|
326
|
-
await expect(session.call('
|
|
327
|
-
type: '
|
|
647
|
+
await expect(session.call('ProtocolInfoRequest', {})).resolves.toEqual({
|
|
648
|
+
type: 'ProtocolInfo',
|
|
328
649
|
message: {
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
650
|
+
version: 2,
|
|
651
|
+
supported_messages: [],
|
|
652
|
+
protobuf_definition: null,
|
|
332
653
|
},
|
|
333
654
|
});
|
|
334
|
-
expect(logger.debug).
|
|
655
|
+
expect(logger.debug).not.toHaveBeenCalled();
|
|
656
|
+
});
|
|
657
|
+
|
|
658
|
+
test('session accepts gaps in the firmware-global response sequence', async () => {
|
|
659
|
+
const response = ProtocolV2.encodeFrame(schemas, 'Success', { message: 'ok' });
|
|
660
|
+
const readFrame = jest
|
|
661
|
+
.fn()
|
|
662
|
+
.mockResolvedValueOnce(rewriteSeq(response, 1))
|
|
663
|
+
.mockResolvedValueOnce(rewriteSeq(response, 4));
|
|
664
|
+
const session = new ProtocolV2Session({
|
|
665
|
+
schemas,
|
|
666
|
+
router: 1,
|
|
667
|
+
writeFrame: () => Promise.resolve(),
|
|
668
|
+
readFrame,
|
|
669
|
+
});
|
|
670
|
+
|
|
671
|
+
await session.call('Ping', { message: 'first' });
|
|
672
|
+
await expect(session.call('Ping', { message: 'second' })).resolves.toMatchObject({
|
|
673
|
+
type: 'Success',
|
|
674
|
+
});
|
|
675
|
+
});
|
|
676
|
+
|
|
677
|
+
test('session rejects a duplicate device response sequence', async () => {
|
|
678
|
+
const response = ProtocolV2.encodeFrame(schemas, 'Success', { message: 'ok' });
|
|
679
|
+
const readFrame = jest.fn(() => Promise.resolve(rewriteSeq(response, 7)));
|
|
680
|
+
const session = new ProtocolV2Session({
|
|
681
|
+
schemas,
|
|
682
|
+
router: 1,
|
|
683
|
+
writeFrame: () => Promise.resolve(),
|
|
684
|
+
readFrame,
|
|
685
|
+
});
|
|
686
|
+
|
|
687
|
+
await session.call('Ping', { message: 'first' });
|
|
688
|
+
await expect(session.call('Ping', { message: 'second' })).rejects.toThrow(
|
|
689
|
+
'Protocol V2 duplicate response sequence: 7'
|
|
690
|
+
);
|
|
335
691
|
});
|
|
336
692
|
|
|
337
|
-
test('session
|
|
693
|
+
test('session does not log transmit or receive payload details', async () => {
|
|
338
694
|
const response = ProtocolV2.encodeFrame(schemas, 'Success', {
|
|
339
695
|
message: 'accepted',
|
|
340
696
|
});
|
|
@@ -345,7 +701,7 @@ describe('Protocol V2 framing and session', () => {
|
|
|
345
701
|
schemas,
|
|
346
702
|
router: 1,
|
|
347
703
|
writeFrame: () => Promise.resolve(),
|
|
348
|
-
readFrame: () => Promise.resolve(response),
|
|
704
|
+
readFrame: () => Promise.resolve(rewriteSeq(response, 1)),
|
|
349
705
|
logger,
|
|
350
706
|
logPrefix: 'ProtocolV2 Test',
|
|
351
707
|
});
|
|
@@ -357,30 +713,7 @@ describe('Protocol V2 framing and session', () => {
|
|
|
357
713
|
},
|
|
358
714
|
});
|
|
359
715
|
|
|
360
|
-
expect(logger.debug).
|
|
361
|
-
message: 'hello',
|
|
362
|
-
});
|
|
363
|
-
expect(logger.debug).toHaveBeenCalledWith(
|
|
364
|
-
'[ProtocolV2 Test] encode raw frame',
|
|
365
|
-
expect.objectContaining({
|
|
366
|
-
context: 'tx:Ping',
|
|
367
|
-
messageTypeId: 60206,
|
|
368
|
-
router: 1,
|
|
369
|
-
})
|
|
370
|
-
);
|
|
371
|
-
expect(logger.debug).toHaveBeenCalledWith(
|
|
372
|
-
'[ProtocolV2 Test] decode raw frame',
|
|
373
|
-
expect.objectContaining({
|
|
374
|
-
context: 'rx:Ping',
|
|
375
|
-
messageTypeId: 60207,
|
|
376
|
-
})
|
|
377
|
-
);
|
|
378
|
-
expect(logger.debug).toHaveBeenCalledWith(
|
|
379
|
-
'[ProtocolV2 Test] RX payload type=Success messageTypeId=60207',
|
|
380
|
-
{
|
|
381
|
-
message: 'accepted',
|
|
382
|
-
}
|
|
383
|
-
);
|
|
716
|
+
expect(logger.debug).not.toHaveBeenCalled();
|
|
384
717
|
});
|
|
385
718
|
|
|
386
719
|
test('session suppresses debug logs for file transfer calls', async () => {
|
|
@@ -394,7 +727,7 @@ describe('Protocol V2 framing and session', () => {
|
|
|
394
727
|
schemas,
|
|
395
728
|
router: 1,
|
|
396
729
|
writeFrame: () => Promise.resolve(),
|
|
397
|
-
readFrame: () => Promise.resolve(response),
|
|
730
|
+
readFrame: () => Promise.resolve(rewriteSeq(response, 1)),
|
|
398
731
|
logger,
|
|
399
732
|
});
|
|
400
733
|
|
|
@@ -412,15 +745,17 @@ describe('Protocol V2 framing and session', () => {
|
|
|
412
745
|
const stale = ProtocolV2.encodeFrame(schemas, 'Success', {
|
|
413
746
|
message: 'stale response',
|
|
414
747
|
});
|
|
415
|
-
const response = ProtocolV2.encodeFrame(schemas, '
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
patch_version: 1,
|
|
748
|
+
const response = ProtocolV2.encodeFrame(schemas, 'ProtocolInfo', {
|
|
749
|
+
version: 2,
|
|
750
|
+
supported_messages: [],
|
|
419
751
|
});
|
|
420
752
|
const logger = {
|
|
421
753
|
debug: jest.fn(),
|
|
422
754
|
};
|
|
423
|
-
const readFrame = jest
|
|
755
|
+
const readFrame = jest
|
|
756
|
+
.fn()
|
|
757
|
+
.mockResolvedValueOnce(rewriteSeq(stale, 1))
|
|
758
|
+
.mockResolvedValueOnce(rewriteSeq(response, 2));
|
|
424
759
|
const session = new ProtocolV2Session({
|
|
425
760
|
schemas,
|
|
426
761
|
router: 1,
|
|
@@ -430,13 +765,13 @@ describe('Protocol V2 framing and session', () => {
|
|
|
430
765
|
});
|
|
431
766
|
|
|
432
767
|
await expect(
|
|
433
|
-
session.call('
|
|
768
|
+
session.call('ProtocolInfoRequest', {}, { expectedTypes: ['ProtocolInfo'] })
|
|
434
769
|
).resolves.toEqual({
|
|
435
|
-
type: '
|
|
770
|
+
type: 'ProtocolInfo',
|
|
436
771
|
message: {
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
772
|
+
version: 2,
|
|
773
|
+
supported_messages: [],
|
|
774
|
+
protobuf_definition: null,
|
|
440
775
|
},
|
|
441
776
|
});
|
|
442
777
|
|
|
@@ -446,9 +781,8 @@ describe('Protocol V2 framing and session', () => {
|
|
|
446
781
|
|
|
447
782
|
test('session consumes intermediate response frames before returning the final response', async () => {
|
|
448
783
|
const written = [];
|
|
449
|
-
const progress = ProtocolV2.encodeFrame(schemas, '
|
|
450
|
-
target_id:
|
|
451
|
-
progress: 42,
|
|
784
|
+
const progress = ProtocolV2.encodeFrame(schemas, 'DeviceFirmwareUpdateStatus', {
|
|
785
|
+
records: [{ target_id: 4, status: 1 }],
|
|
452
786
|
});
|
|
453
787
|
const success = ProtocolV2.encodeFrame(schemas, 'Success', {
|
|
454
788
|
message: 'ok',
|
|
@@ -458,7 +792,9 @@ describe('Protocol V2 framing and session', () => {
|
|
|
458
792
|
const [writtenFrame] = written;
|
|
459
793
|
const { seq } = protocolV2.decodeFrame(writtenFrame);
|
|
460
794
|
return Promise.resolve(
|
|
461
|
-
readFrame.mock.calls.length === 1
|
|
795
|
+
readFrame.mock.calls.length === 1
|
|
796
|
+
? rewriteSeq(progress, seq)
|
|
797
|
+
: rewriteSeq(success, protocolV2.nextProtoSeq(seq))
|
|
462
798
|
);
|
|
463
799
|
});
|
|
464
800
|
const session = new ProtocolV2Session({
|
|
@@ -472,20 +808,19 @@ describe('Protocol V2 framing and session', () => {
|
|
|
472
808
|
});
|
|
473
809
|
|
|
474
810
|
const result = await session.call(
|
|
475
|
-
'
|
|
476
|
-
{},
|
|
811
|
+
'DeviceFirmwareUpdateRequest',
|
|
812
|
+
{ targets: [{ target_id: 4, path: 'vol1:firmware.bin' }] },
|
|
477
813
|
{
|
|
478
|
-
intermediateTypes: ['
|
|
814
|
+
intermediateTypes: ['DeviceFirmwareUpdateStatus'],
|
|
479
815
|
onIntermediateResponse,
|
|
480
816
|
}
|
|
481
817
|
);
|
|
482
818
|
|
|
483
819
|
expect(readFrame).toHaveBeenCalledTimes(2);
|
|
484
820
|
expect(onIntermediateResponse).toHaveBeenCalledWith({
|
|
485
|
-
type: '
|
|
821
|
+
type: 'DeviceFirmwareUpdateStatus',
|
|
486
822
|
message: {
|
|
487
|
-
target_id:
|
|
488
|
-
progress: 42,
|
|
823
|
+
records: [{ target_id: 4, status: 1, payload_version: null, path: null }],
|
|
489
824
|
},
|
|
490
825
|
});
|
|
491
826
|
expect(result).toEqual({
|
|
@@ -575,7 +910,7 @@ describe('Protocol V2 framing and session', () => {
|
|
|
575
910
|
});
|
|
576
911
|
|
|
577
912
|
await expect(
|
|
578
|
-
session.call('
|
|
913
|
+
session.call('ProtocolInfoRequest', {}, { timeoutMs: 10, expectedTypes: ['ProtocolInfo'] })
|
|
579
914
|
).rejects.toThrow('Protocol V2 response timeout');
|
|
580
915
|
|
|
581
916
|
// Without cancellation the loop would skip this unexpected Success frame
|
|
@@ -636,7 +971,7 @@ describe('Protocol V2 framing and session', () => {
|
|
|
636
971
|
}
|
|
637
972
|
return Promise.resolve();
|
|
638
973
|
},
|
|
639
|
-
readFrame: () => Promise.resolve(response),
|
|
974
|
+
readFrame: () => Promise.resolve(rewriteSeq(response, 1)),
|
|
640
975
|
});
|
|
641
976
|
|
|
642
977
|
await expect(session.call('Ping', { message: '1' })).rejects.toThrow('transport write failed');
|
|
@@ -674,6 +1009,109 @@ describe('Protocol V2 framing and session', () => {
|
|
|
674
1009
|
expect(written.map(frame => frame[6])).toEqual([1, 2, 1]);
|
|
675
1010
|
});
|
|
676
1011
|
|
|
1012
|
+
test('session reuses an injected sequence cursor across recreated sessions', async () => {
|
|
1013
|
+
const written = [];
|
|
1014
|
+
const cursor = new ProtocolV2SequenceCursor();
|
|
1015
|
+
const makeSession = () =>
|
|
1016
|
+
new ProtocolV2Session({
|
|
1017
|
+
schemas,
|
|
1018
|
+
router: 1,
|
|
1019
|
+
sequenceCursor: cursor,
|
|
1020
|
+
writeFrame: frame => {
|
|
1021
|
+
written.push(frame);
|
|
1022
|
+
return Promise.resolve();
|
|
1023
|
+
},
|
|
1024
|
+
readFrame: () => {
|
|
1025
|
+
const [frame] = written.slice(-1);
|
|
1026
|
+
const { seq } = protocolV2.decodeFrame(frame);
|
|
1027
|
+
return Promise.resolve(
|
|
1028
|
+
rewriteSeq(ProtocolV2.encodeFrame(schemas, 'Success', { message: 'ok' }), seq)
|
|
1029
|
+
);
|
|
1030
|
+
},
|
|
1031
|
+
});
|
|
1032
|
+
|
|
1033
|
+
await makeSession().call('Ping', { message: '1' });
|
|
1034
|
+
await makeSession().call('Ping', { message: '2' });
|
|
1035
|
+
|
|
1036
|
+
expect(written.map(frame => frame[6])).toEqual([1, 2]);
|
|
1037
|
+
});
|
|
1038
|
+
|
|
1039
|
+
test('session passes per-call context to frame IO callbacks', async () => {
|
|
1040
|
+
const writeContexts = [];
|
|
1041
|
+
const readContexts = [];
|
|
1042
|
+
const response = ProtocolV2.encodeFrame(schemas, 'Success', { message: 'ok' });
|
|
1043
|
+
let responseSeq = 0;
|
|
1044
|
+
const session = new ProtocolV2Session({
|
|
1045
|
+
schemas,
|
|
1046
|
+
router: 1,
|
|
1047
|
+
generation: 7,
|
|
1048
|
+
writeFrame: (_frame, context) => {
|
|
1049
|
+
writeContexts.push(context);
|
|
1050
|
+
return Promise.resolve();
|
|
1051
|
+
},
|
|
1052
|
+
readFrame: context => {
|
|
1053
|
+
readContexts.push(context);
|
|
1054
|
+
responseSeq = protocolV2.nextProtoSeq(responseSeq);
|
|
1055
|
+
return Promise.resolve(rewriteSeq(response, responseSeq));
|
|
1056
|
+
},
|
|
1057
|
+
});
|
|
1058
|
+
|
|
1059
|
+
await session.call('Ping', { message: 'ping' }, { timeoutMs: 123 });
|
|
1060
|
+
await session.call('FileWrite', {}, { timeoutMs: 456 });
|
|
1061
|
+
await session.call('Ping', { message: 'default-timeout' });
|
|
1062
|
+
|
|
1063
|
+
const normalizeContext = ({ signal, ...context }) => ({
|
|
1064
|
+
...context,
|
|
1065
|
+
signalAborted: signal.aborted,
|
|
1066
|
+
});
|
|
1067
|
+
expect(writeContexts.map(normalizeContext)).toEqual([
|
|
1068
|
+
{
|
|
1069
|
+
messageName: 'Ping',
|
|
1070
|
+
timeoutMs: 123,
|
|
1071
|
+
highVolume: false,
|
|
1072
|
+
generation: 7,
|
|
1073
|
+
signalAborted: false,
|
|
1074
|
+
},
|
|
1075
|
+
{
|
|
1076
|
+
messageName: 'FileWrite',
|
|
1077
|
+
timeoutMs: 456,
|
|
1078
|
+
highVolume: true,
|
|
1079
|
+
generation: 7,
|
|
1080
|
+
signalAborted: false,
|
|
1081
|
+
},
|
|
1082
|
+
{
|
|
1083
|
+
messageName: 'Ping',
|
|
1084
|
+
timeoutMs: PROTOCOL_V2_DEFAULT_RESPONSE_TIMEOUT_MS,
|
|
1085
|
+
highVolume: false,
|
|
1086
|
+
generation: 7,
|
|
1087
|
+
signalAborted: false,
|
|
1088
|
+
},
|
|
1089
|
+
]);
|
|
1090
|
+
expect(readContexts.map(normalizeContext)).toEqual([
|
|
1091
|
+
{
|
|
1092
|
+
messageName: 'Ping',
|
|
1093
|
+
timeoutMs: 123,
|
|
1094
|
+
highVolume: false,
|
|
1095
|
+
generation: 7,
|
|
1096
|
+
signalAborted: false,
|
|
1097
|
+
},
|
|
1098
|
+
{
|
|
1099
|
+
messageName: 'FileWrite',
|
|
1100
|
+
timeoutMs: 456,
|
|
1101
|
+
highVolume: true,
|
|
1102
|
+
generation: 7,
|
|
1103
|
+
signalAborted: false,
|
|
1104
|
+
},
|
|
1105
|
+
{
|
|
1106
|
+
messageName: 'Ping',
|
|
1107
|
+
timeoutMs: PROTOCOL_V2_DEFAULT_RESPONSE_TIMEOUT_MS,
|
|
1108
|
+
highVolume: false,
|
|
1109
|
+
generation: 7,
|
|
1110
|
+
signalAborted: false,
|
|
1111
|
+
},
|
|
1112
|
+
]);
|
|
1113
|
+
});
|
|
1114
|
+
|
|
677
1115
|
test('assembler throws and resets on frames with an impossible length field', () => {
|
|
678
1116
|
const assembler = new ProtocolV2FrameAssembler();
|
|
679
1117
|
// expectedLen = 0 < 8-byte minimum: without the guard this poisons the
|
|
@@ -700,10 +1138,9 @@ describe('Protocol V2 framing and session', () => {
|
|
|
700
1138
|
});
|
|
701
1139
|
|
|
702
1140
|
test('assembler drain returns every buffered complete frame', () => {
|
|
703
|
-
const first = ProtocolV2.encodeFrame(schemas, '
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
patch_version: 0,
|
|
1141
|
+
const first = ProtocolV2.encodeFrame(schemas, 'ProtocolInfo', {
|
|
1142
|
+
version: 1,
|
|
1143
|
+
supported_messages: [],
|
|
707
1144
|
});
|
|
708
1145
|
const second = ProtocolV2.encodeFrame(schemas, 'Success', { message: 'ok' });
|
|
709
1146
|
const third = ProtocolV2.encodeFrame(schemas, 'Success', { message: 'last' });
|
|
@@ -754,7 +1191,6 @@ describe('Protocol V2 framing and session', () => {
|
|
|
754
1191
|
});
|
|
755
1192
|
|
|
756
1193
|
test('hexToBytes converts valid hex and rejects malformed input', () => {
|
|
757
|
-
const { hexToBytes } = sessionModule;
|
|
758
1194
|
expect(hexToBytes('5a0102')).toEqual(new Uint8Array([0x5a, 0x01, 0x02]));
|
|
759
1195
|
expect(hexToBytes('')).toEqual(new Uint8Array(0));
|
|
760
1196
|
expect(() => hexToBytes('abc')).toThrow('Invalid hex string: odd length');
|