@onekeyfe/hd-transport 1.2.0-alpha.12 → 1.2.0-alpha.121
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/__tests__/messages.test.js +165 -13
- package/__tests__/protocol-v2-ble-frame-writer.test.js +119 -0
- package/__tests__/protocol-v2-link-manager.test.js +120 -9
- package/__tests__/protocol-v2-usb-transport-base.test.js +36 -2
- package/__tests__/protocol-v2.test.js +566 -69
- package/__tests__/transport-log.test.js +79 -0
- package/dist/constants.d.ts +3 -1
- package/dist/constants.d.ts.map +1 -1
- package/dist/index.d.ts +696 -221
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +662 -130
- package/dist/protocols/index.d.ts +24 -1
- 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/decode.d.ts +11 -0
- package/dist/protocols/v2/decode.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 -0
- package/dist/protocols/v2/index.d.ts.map +1 -1
- package/dist/protocols/v2/link-manager.d.ts +4 -0
- package/dist/protocols/v2/link-manager.d.ts.map +1 -1
- package/dist/protocols/v2/session.d.ts +11 -4
- package/dist/protocols/v2/session.d.ts.map +1 -1
- package/dist/protocols/v2/usb-transport-base.d.ts +1 -0
- package/dist/protocols/v2/usb-transport-base.d.ts.map +1 -1
- package/dist/serialization/protobuf/decode.d.ts.map +1 -1
- package/dist/serialization/protobuf/encode.d.ts.map +1 -1
- package/dist/types/messages.d.ts +345 -158
- package/dist/types/messages.d.ts.map +1 -1
- package/dist/types/transport.d.ts +21 -3
- package/dist/types/transport.d.ts.map +1 -1
- package/dist/utils/transportLog.d.ts +9 -0
- package/dist/utils/transportLog.d.ts.map +1 -0
- package/messages-protocol-v2.json +1057 -577
- package/package.json +3 -3
- package/scripts/protobuf-build.sh +85 -217
- package/scripts/protobuf-patches/index.js +1 -0
- package/scripts/protobuf-types.js +19 -1
- package/src/constants.ts +30 -16
- package/src/index.ts +5 -1
- package/src/protocols/index.ts +127 -4
- package/src/protocols/v2/ble-frame-writer.ts +77 -0
- package/src/protocols/v2/crc8.ts +1 -1
- package/src/protocols/v2/decode.ts +49 -12
- package/src/protocols/v2/encode.ts +1 -1
- package/src/protocols/v2/errors.ts +25 -0
- package/src/protocols/v2/frame-assembler.ts +6 -4
- package/src/protocols/v2/index.ts +2 -0
- package/src/protocols/v2/link-manager.ts +54 -5
- package/src/protocols/v2/session.ts +166 -59
- package/src/protocols/v2/usb-transport-base.ts +29 -10
- package/src/serialization/protobuf/decode.ts +4 -1
- package/src/serialization/protobuf/encode.ts +5 -1
- package/src/types/messages.ts +421 -201
- package/src/types/transport.ts +30 -3
- package/src/utils/transportLog.ts +102 -0
|
@@ -2,12 +2,26 @@ const { ProtocolV2 } = require('../src/protocols');
|
|
|
2
2
|
const { parseConfigure } = require('../src/serialization/protobuf/messages');
|
|
3
3
|
const {
|
|
4
4
|
ProtocolV2FrameAssembler,
|
|
5
|
+
ProtocolV2LinkError,
|
|
5
6
|
ProtocolV2SequenceCursor,
|
|
6
7
|
ProtocolV2Session,
|
|
7
8
|
hexToBytes,
|
|
9
|
+
isProtocolV2HighThroughputCall,
|
|
8
10
|
probeProtocolV2,
|
|
9
11
|
} = require('../src/protocols/v2/session');
|
|
10
12
|
const protocolV2 = require('../src/protocols/v2');
|
|
13
|
+
const {
|
|
14
|
+
PROTOCOL_V2_BLE_FIRMWARE_FILE_CHUNK_SIZE,
|
|
15
|
+
PROTOCOL_V2_BLE_FRAME_MAX_BYTES,
|
|
16
|
+
PROTOCOL_V2_DEFAULT_RESPONSE_TIMEOUT_MS,
|
|
17
|
+
PROTOCOL_V2_FRAME_MAX_BYTES,
|
|
18
|
+
} = require('../src/constants');
|
|
19
|
+
|
|
20
|
+
test('keeps sensitive acknowledgements separate from high-throughput calls', () => {
|
|
21
|
+
expect(isProtocolV2HighThroughputCall('PassphraseAck')).toBe(false);
|
|
22
|
+
expect(isProtocolV2HighThroughputCall('PinMatrixAck')).toBe(false);
|
|
23
|
+
expect(isProtocolV2HighThroughputCall('FilesystemFileWrite')).toBe(true);
|
|
24
|
+
});
|
|
11
25
|
|
|
12
26
|
const protocolV1Messages = parseConfigure({
|
|
13
27
|
nested: {
|
|
@@ -68,14 +82,14 @@ const protocolV2Messages = parseConfigure({
|
|
|
68
82
|
type: 'uint32',
|
|
69
83
|
id: 1,
|
|
70
84
|
},
|
|
71
|
-
|
|
72
|
-
type: '
|
|
85
|
+
build_fingerprint: {
|
|
86
|
+
type: 'string',
|
|
73
87
|
id: 2,
|
|
74
|
-
rule: 'repeated',
|
|
75
88
|
},
|
|
76
|
-
|
|
77
|
-
type: '
|
|
89
|
+
supported_messages: {
|
|
90
|
+
type: 'uint32',
|
|
78
91
|
id: 3,
|
|
92
|
+
rule: 'repeated',
|
|
79
93
|
},
|
|
80
94
|
},
|
|
81
95
|
},
|
|
@@ -95,6 +109,22 @@ const protocolV2Messages = parseConfigure({
|
|
|
95
109
|
},
|
|
96
110
|
},
|
|
97
111
|
},
|
|
112
|
+
Failure: {
|
|
113
|
+
fields: {
|
|
114
|
+
code: {
|
|
115
|
+
type: 'uint32',
|
|
116
|
+
id: 1,
|
|
117
|
+
},
|
|
118
|
+
subcode: {
|
|
119
|
+
type: 'uint32',
|
|
120
|
+
id: 2,
|
|
121
|
+
},
|
|
122
|
+
message: {
|
|
123
|
+
type: 'string',
|
|
124
|
+
id: 3,
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
},
|
|
98
128
|
DeviceFirmwareTarget: {
|
|
99
129
|
fields: {
|
|
100
130
|
target_id: {
|
|
@@ -107,7 +137,7 @@ const protocolV2Messages = parseConfigure({
|
|
|
107
137
|
},
|
|
108
138
|
},
|
|
109
139
|
},
|
|
110
|
-
|
|
140
|
+
DeviceFirmwareUpdateStage: {
|
|
111
141
|
fields: {
|
|
112
142
|
targets: {
|
|
113
143
|
type: 'DeviceFirmwareTarget',
|
|
@@ -116,6 +146,9 @@ const protocolV2Messages = parseConfigure({
|
|
|
116
146
|
},
|
|
117
147
|
},
|
|
118
148
|
},
|
|
149
|
+
DeviceFirmwareUpdateRequest: {
|
|
150
|
+
fields: {},
|
|
151
|
+
},
|
|
119
152
|
DeviceFirmwareUpdateRecord: {
|
|
120
153
|
fields: {
|
|
121
154
|
target_id: {
|
|
@@ -145,6 +178,18 @@ const protocolV2Messages = parseConfigure({
|
|
|
145
178
|
},
|
|
146
179
|
},
|
|
147
180
|
},
|
|
181
|
+
DeviceSession: {
|
|
182
|
+
fields: {
|
|
183
|
+
session_id: {
|
|
184
|
+
type: 'bytes',
|
|
185
|
+
id: 1,
|
|
186
|
+
},
|
|
187
|
+
btc_test_address: {
|
|
188
|
+
type: 'string',
|
|
189
|
+
id: 2,
|
|
190
|
+
},
|
|
191
|
+
},
|
|
192
|
+
},
|
|
148
193
|
FileWrite: {
|
|
149
194
|
fields: {},
|
|
150
195
|
},
|
|
@@ -174,9 +219,12 @@ const protocolV2Messages = parseConfigure({
|
|
|
174
219
|
MessageType_ProtocolInfo: 60201,
|
|
175
220
|
MessageType_Ping: 60206,
|
|
176
221
|
MessageType_Success: 60207,
|
|
222
|
+
MessageType_Failure: 60208,
|
|
177
223
|
MessageType_FileWrite: 60805,
|
|
178
|
-
|
|
224
|
+
MessageType_DeviceFirmwareUpdateStage: 61000,
|
|
225
|
+
MessageType_DeviceFirmwareUpdateRequest: 61001,
|
|
179
226
|
MessageType_DeviceFirmwareUpdateStatus: 61002,
|
|
227
|
+
MessageType_DeviceSession: 61201,
|
|
180
228
|
MessageType_PartialNested: 62000,
|
|
181
229
|
},
|
|
182
230
|
},
|
|
@@ -187,9 +235,37 @@ const schemas = {
|
|
|
187
235
|
protocolV1: protocolV1Messages,
|
|
188
236
|
protocolV2: protocolV2Messages,
|
|
189
237
|
};
|
|
238
|
+
const productionProtocolV2Messages = parseConfigure(require('../messages-protocol-v2.json'));
|
|
239
|
+
|
|
240
|
+
const legacyProtocolInfoMessage = parseConfigure({
|
|
241
|
+
nested: {
|
|
242
|
+
ProtocolInfo: {
|
|
243
|
+
fields: {
|
|
244
|
+
version: {
|
|
245
|
+
type: 'uint32',
|
|
246
|
+
id: 1,
|
|
247
|
+
rule: 'required',
|
|
248
|
+
},
|
|
249
|
+
supported_messages: {
|
|
250
|
+
type: 'uint32',
|
|
251
|
+
id: 2,
|
|
252
|
+
rule: 'repeated',
|
|
253
|
+
options: {
|
|
254
|
+
packed: false,
|
|
255
|
+
},
|
|
256
|
+
},
|
|
257
|
+
protobuf_definition: {
|
|
258
|
+
type: 'string',
|
|
259
|
+
id: 3,
|
|
260
|
+
},
|
|
261
|
+
},
|
|
262
|
+
},
|
|
263
|
+
},
|
|
264
|
+
}).lookupType('ProtocolInfo');
|
|
190
265
|
|
|
191
266
|
const rewriteSeq = (frame, seq) => {
|
|
192
267
|
const copy = new Uint8Array(frame);
|
|
268
|
+
copy[4] = 1;
|
|
193
269
|
copy[6] = seq;
|
|
194
270
|
copy[copy.length - 1] = protocolV2.crc8(copy, copy.length - 1);
|
|
195
271
|
return copy;
|
|
@@ -199,8 +275,8 @@ describe('Protocol V2 framing and session', () => {
|
|
|
199
275
|
test('encodes and decodes Protocol V2 protobuf frames', () => {
|
|
200
276
|
const frame = ProtocolV2.encodeFrame(schemas, 'ProtocolInfo', {
|
|
201
277
|
version: 1,
|
|
278
|
+
build_fingerprint: 'application__1.0.0__abc__DEV__DEBUG',
|
|
202
279
|
supported_messages: [60200, 60201],
|
|
203
|
-
protobuf_definition: 'proto',
|
|
204
280
|
});
|
|
205
281
|
|
|
206
282
|
const parsed = protocolV2.decodeFrame(frame);
|
|
@@ -215,12 +291,70 @@ describe('Protocol V2 framing and session', () => {
|
|
|
215
291
|
seq: parsed.seq,
|
|
216
292
|
message: {
|
|
217
293
|
version: 1,
|
|
294
|
+
build_fingerprint: 'application__1.0.0__abc__DEV__DEBUG',
|
|
218
295
|
supported_messages: [60200, 60201],
|
|
219
|
-
protobuf_definition: 'proto',
|
|
220
296
|
},
|
|
221
297
|
});
|
|
222
298
|
});
|
|
223
299
|
|
|
300
|
+
test('decodes a two-byte legacy ProtocolInfo at the generic frame boundary', () => {
|
|
301
|
+
const frame = protocolV2.encodeProtobufFrame(60201, new Uint8Array([0x08, 0x01]));
|
|
302
|
+
const productionSchemas = {
|
|
303
|
+
protocolV1: protocolV1Messages,
|
|
304
|
+
protocolV2: productionProtocolV2Messages,
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
expect(ProtocolV2.decodeFrame(productionSchemas, frame)).toMatchObject({
|
|
308
|
+
type: 'ProtocolInfo',
|
|
309
|
+
message: {
|
|
310
|
+
version: 1,
|
|
311
|
+
build_fingerprint: '',
|
|
312
|
+
supported_messages: [],
|
|
313
|
+
protobuf_definition: null,
|
|
314
|
+
},
|
|
315
|
+
});
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
test('does not reinterpret a malformed current ProtocolInfo as the legacy layout', () => {
|
|
319
|
+
const frame = protocolV2.encodeProtobufFrame(60201, new Uint8Array([0x08, 0x01, 0x18, 0x01]));
|
|
320
|
+
|
|
321
|
+
expect(() =>
|
|
322
|
+
ProtocolV2.decodeFrame(
|
|
323
|
+
{
|
|
324
|
+
protocolV1: protocolV1Messages,
|
|
325
|
+
protocolV2: productionProtocolV2Messages,
|
|
326
|
+
},
|
|
327
|
+
frame
|
|
328
|
+
)
|
|
329
|
+
).toThrow('Protocol V2 protobuf decode failed for "ProtocolInfo"');
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
test('preserves legacy ProtocolInfo capabilities when using the old field numbers', () => {
|
|
333
|
+
const payload = legacyProtocolInfoMessage
|
|
334
|
+
.encode({
|
|
335
|
+
version: 1,
|
|
336
|
+
supported_messages: [60602],
|
|
337
|
+
protobuf_definition: 'legacy',
|
|
338
|
+
})
|
|
339
|
+
.finish();
|
|
340
|
+
const frame = protocolV2.encodeProtobufFrame(60201, payload);
|
|
341
|
+
|
|
342
|
+
const decoded = ProtocolV2.decodeFrame(
|
|
343
|
+
{
|
|
344
|
+
protocolV1: protocolV1Messages,
|
|
345
|
+
protocolV2: productionProtocolV2Messages,
|
|
346
|
+
},
|
|
347
|
+
frame
|
|
348
|
+
);
|
|
349
|
+
|
|
350
|
+
expect(decoded.message).toEqual({
|
|
351
|
+
version: 1,
|
|
352
|
+
build_fingerprint: '',
|
|
353
|
+
supported_messages: [60602],
|
|
354
|
+
protobuf_definition: null,
|
|
355
|
+
});
|
|
356
|
+
});
|
|
357
|
+
|
|
224
358
|
test('does not encode V1-only messages into Protocol V2 frames', () => {
|
|
225
359
|
expect(() => ProtocolV2.encodeFrame(schemas, 'Ping', { message: 'ok' })).not.toThrow();
|
|
226
360
|
expect(() => ProtocolV2.encodeFrame(schemas, 'OnekeyGetFeatures', {})).toThrow(
|
|
@@ -241,6 +375,34 @@ describe('Protocol V2 framing and session', () => {
|
|
|
241
375
|
expect(decoded.message).toEqual({ message: 'ok' });
|
|
242
376
|
});
|
|
243
377
|
|
|
378
|
+
test('round-trips a DeviceSession response with a 32-byte session id', () => {
|
|
379
|
+
const sessionId = '01'.repeat(32);
|
|
380
|
+
const frame = ProtocolV2.encodeFrame(schemas, 'DeviceSession', {
|
|
381
|
+
session_id: sessionId,
|
|
382
|
+
btc_test_address: 'tb1qwallet',
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
const decoded = ProtocolV2.decodeFrame(schemas, frame);
|
|
386
|
+
|
|
387
|
+
expect(decoded.type).toBe('DeviceSession');
|
|
388
|
+
expect(decoded.message).toEqual({
|
|
389
|
+
session_id: sessionId,
|
|
390
|
+
btc_test_address: 'tb1qwallet',
|
|
391
|
+
});
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
test('reports malformed DeviceSession protobuf as a schema-compatible transport error', () => {
|
|
395
|
+
const malformedPayload = new Uint8Array(32);
|
|
396
|
+
malformedPayload[0] = 0x0a;
|
|
397
|
+
malformedPayload[1] = 101;
|
|
398
|
+
const frame = protocolV2.encodeProtobufFrame(61201, malformedPayload);
|
|
399
|
+
|
|
400
|
+
expect(() => ProtocolV2.decodeFrame(schemas, frame)).toThrow(
|
|
401
|
+
'Protocol V2 protobuf decode failed for "DeviceSession" (61201, 32-byte payload); ' +
|
|
402
|
+
'the payload is malformed or incompatible with the active SDK schema.'
|
|
403
|
+
);
|
|
404
|
+
});
|
|
405
|
+
|
|
244
406
|
test('decodes missing optional nested messages as null', () => {
|
|
245
407
|
const frame = ProtocolV2.encodeFrame(schemas, 'PartialNested', {
|
|
246
408
|
label: 'only label',
|
|
@@ -267,6 +429,60 @@ describe('Protocol V2 framing and session', () => {
|
|
|
267
429
|
expect(() => assembler.push(oversized)).toThrow('Protocol V2 frame too large');
|
|
268
430
|
});
|
|
269
431
|
|
|
432
|
+
test('enforces a transport-specific receive frame boundary', () => {
|
|
433
|
+
const assembler = new ProtocolV2FrameAssembler(2048);
|
|
434
|
+
const oversizedHeader = new Uint8Array([0x5a, 0x01, 0x08]);
|
|
435
|
+
|
|
436
|
+
expect(() => assembler.push(oversizedHeader)).toThrow('Protocol V2 frame too large: 2049');
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
test('enforces the firmware 4200-byte Protocol V2 frame boundary', () => {
|
|
440
|
+
const boundaryFrame = ProtocolV2.encodeFrame(schemas, 'Ping', {
|
|
441
|
+
message: 'x'.repeat(4187),
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
expect(PROTOCOL_V2_FRAME_MAX_BYTES).toBe(4200);
|
|
445
|
+
expect(boundaryFrame).toHaveLength(PROTOCOL_V2_FRAME_MAX_BYTES);
|
|
446
|
+
expect(() =>
|
|
447
|
+
ProtocolV2.encodeFrame(schemas, 'Ping', {
|
|
448
|
+
message: 'x'.repeat(4188),
|
|
449
|
+
})
|
|
450
|
+
).toThrow('Protocol V2 frame too large: 4201 > 4200');
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
test('keeps optimized BLE firmware chunks inside the transport frame boundary', () => {
|
|
454
|
+
const productionSchemas = {
|
|
455
|
+
protocolV1: protocolV1Messages,
|
|
456
|
+
protocolV2: productionProtocolV2Messages,
|
|
457
|
+
};
|
|
458
|
+
const stagingPaths = [
|
|
459
|
+
'vol0:/bootloader.bin',
|
|
460
|
+
'vol0:/application_p1.bin',
|
|
461
|
+
'vol0:/application_p2.bin',
|
|
462
|
+
'vol0:/coprocessor.bin',
|
|
463
|
+
'vol0:/se01.bin',
|
|
464
|
+
'vol0:/se02.bin',
|
|
465
|
+
'vol0:/se03.bin',
|
|
466
|
+
'vol0:/se04.bin',
|
|
467
|
+
];
|
|
468
|
+
|
|
469
|
+
for (const path of stagingPaths) {
|
|
470
|
+
const frame = ProtocolV2.encodeFrame(productionSchemas, 'FilesystemFileWrite', {
|
|
471
|
+
file: {
|
|
472
|
+
path,
|
|
473
|
+
offset: 0xffffffff,
|
|
474
|
+
total_size: 0xffffffff,
|
|
475
|
+
data: new Uint8Array(PROTOCOL_V2_BLE_FIRMWARE_FILE_CHUNK_SIZE),
|
|
476
|
+
},
|
|
477
|
+
overwrite: true,
|
|
478
|
+
append: false,
|
|
479
|
+
ui_percentage: 100,
|
|
480
|
+
});
|
|
481
|
+
|
|
482
|
+
expect(frame.length).toBeLessThanOrEqual(PROTOCOL_V2_BLE_FRAME_MAX_BYTES);
|
|
483
|
+
}
|
|
484
|
+
});
|
|
485
|
+
|
|
270
486
|
test('keeps bytes after the first complete frame for the next read', () => {
|
|
271
487
|
const first = ProtocolV2.encodeFrame(schemas, 'ProtocolInfo', {
|
|
272
488
|
version: 1,
|
|
@@ -312,12 +528,90 @@ describe('Protocol V2 framing and session', () => {
|
|
|
312
528
|
type: 'ProtocolInfo',
|
|
313
529
|
message: {
|
|
314
530
|
version: 2,
|
|
531
|
+
build_fingerprint: null,
|
|
315
532
|
supported_messages: [60206],
|
|
533
|
+
},
|
|
534
|
+
});
|
|
535
|
+
});
|
|
536
|
+
|
|
537
|
+
test('session decodes legacy ProtocolInfo through the generic frame boundary', async () => {
|
|
538
|
+
const productionSchemas = {
|
|
539
|
+
protocolV1: protocolV1Messages,
|
|
540
|
+
protocolV2: productionProtocolV2Messages,
|
|
541
|
+
};
|
|
542
|
+
const response = protocolV2.encodeProtobufFrame(60201, new Uint8Array([0x08, 0x01]));
|
|
543
|
+
const session = new ProtocolV2Session({
|
|
544
|
+
schemas: productionSchemas,
|
|
545
|
+
router: 1,
|
|
546
|
+
writeFrame: () => Promise.resolve(),
|
|
547
|
+
readFrame: () => Promise.resolve(rewriteSeq(response, 1)),
|
|
548
|
+
});
|
|
549
|
+
|
|
550
|
+
await expect(
|
|
551
|
+
session.call(
|
|
552
|
+
'ProtocolInfoRequest',
|
|
553
|
+
{ eventless_wallet_session: true },
|
|
554
|
+
{
|
|
555
|
+
expectedTypes: ['ProtocolInfo'],
|
|
556
|
+
}
|
|
557
|
+
)
|
|
558
|
+
).resolves.toEqual({
|
|
559
|
+
type: 'ProtocolInfo',
|
|
560
|
+
message: {
|
|
561
|
+
version: 1,
|
|
562
|
+
build_fingerprint: '',
|
|
563
|
+
supported_messages: [],
|
|
316
564
|
protobuf_definition: null,
|
|
317
565
|
},
|
|
318
566
|
});
|
|
319
567
|
});
|
|
320
568
|
|
|
569
|
+
test('session does not log Protocol V2 TX and RX frames', async () => {
|
|
570
|
+
const written = [];
|
|
571
|
+
const logger = { debug: jest.fn() };
|
|
572
|
+
const response = ProtocolV2.encodeFrame(schemas, 'ProtocolInfo', {
|
|
573
|
+
version: 2,
|
|
574
|
+
build_fingerprint: 'application__1.0.0__sensitive__DEV__DEBUG',
|
|
575
|
+
supported_messages: [60206],
|
|
576
|
+
});
|
|
577
|
+
const session = new ProtocolV2Session({
|
|
578
|
+
schemas,
|
|
579
|
+
router: 1,
|
|
580
|
+
logger,
|
|
581
|
+
logPrefix: 'ProtocolV2 Test',
|
|
582
|
+
writeFrame: frame => {
|
|
583
|
+
written.push(frame);
|
|
584
|
+
return Promise.resolve();
|
|
585
|
+
},
|
|
586
|
+
readFrame: () =>
|
|
587
|
+
Promise.resolve(rewriteSeq(response, protocolV2.decodeFrame(written[0]).seq)),
|
|
588
|
+
});
|
|
589
|
+
|
|
590
|
+
await session.call('ProtocolInfoRequest', {});
|
|
591
|
+
|
|
592
|
+
expect(logger.debug).not.toHaveBeenCalled();
|
|
593
|
+
});
|
|
594
|
+
|
|
595
|
+
test('session does not log RX frame metadata when protobuf payload decoding fails', async () => {
|
|
596
|
+
const logger = { debug: jest.fn() };
|
|
597
|
+
const response = protocolV2.encodeProtobufFrame(
|
|
598
|
+
60208,
|
|
599
|
+
hexToBytes('0801121648616e646c6572206e6f742072656769737465726564')
|
|
600
|
+
);
|
|
601
|
+
const session = new ProtocolV2Session({
|
|
602
|
+
schemas,
|
|
603
|
+
router: 1,
|
|
604
|
+
logger,
|
|
605
|
+
logPrefix: 'ProtocolV2 Test',
|
|
606
|
+
writeFrame: () => Promise.resolve(),
|
|
607
|
+
readFrame: () => Promise.resolve(rewriteSeq(response, 1)),
|
|
608
|
+
});
|
|
609
|
+
|
|
610
|
+
await expect(session.call('Ping', { message: 'hello' })).rejects.toThrow();
|
|
611
|
+
|
|
612
|
+
expect(logger.debug).not.toHaveBeenCalled();
|
|
613
|
+
});
|
|
614
|
+
|
|
321
615
|
test('session skips Proto Link ACK frames before decoding the protobuf response', async () => {
|
|
322
616
|
const ack = new Uint8Array(8);
|
|
323
617
|
ack[0] = 0x5a;
|
|
@@ -332,7 +626,10 @@ describe('Protocol V2 framing and session', () => {
|
|
|
332
626
|
const response = ProtocolV2.encodeFrame(schemas, 'Success', {
|
|
333
627
|
message: 'ok',
|
|
334
628
|
});
|
|
335
|
-
const readFrame = jest
|
|
629
|
+
const readFrame = jest
|
|
630
|
+
.fn()
|
|
631
|
+
.mockResolvedValueOnce(ack)
|
|
632
|
+
.mockResolvedValueOnce(rewriteSeq(response, 1));
|
|
336
633
|
const session = new ProtocolV2Session({
|
|
337
634
|
schemas,
|
|
338
635
|
router: 1,
|
|
@@ -349,6 +646,96 @@ describe('Protocol V2 framing and session', () => {
|
|
|
349
646
|
expect(readFrame).toHaveBeenCalledTimes(2);
|
|
350
647
|
});
|
|
351
648
|
|
|
649
|
+
test('session accepts a terminal response before an ACK', async () => {
|
|
650
|
+
const response = ProtocolV2.encodeFrame(schemas, 'Success', {
|
|
651
|
+
message: 'ok',
|
|
652
|
+
});
|
|
653
|
+
const session = new ProtocolV2Session({
|
|
654
|
+
schemas,
|
|
655
|
+
router: 1,
|
|
656
|
+
writeFrame: () => Promise.resolve(),
|
|
657
|
+
readFrame: () => Promise.resolve(rewriteSeq(response, 1)),
|
|
658
|
+
});
|
|
659
|
+
|
|
660
|
+
await expect(
|
|
661
|
+
session.call('Ping', { message: 'hello' }, { expectedTypes: ['Success'] })
|
|
662
|
+
).resolves.toEqual({
|
|
663
|
+
type: 'Success',
|
|
664
|
+
message: {
|
|
665
|
+
message: 'ok',
|
|
666
|
+
},
|
|
667
|
+
});
|
|
668
|
+
});
|
|
669
|
+
|
|
670
|
+
test('session uses the unified response timeout before the first ACK', async () => {
|
|
671
|
+
const response = ProtocolV2.encodeFrame(schemas, 'Success', {
|
|
672
|
+
message: 'ok',
|
|
673
|
+
});
|
|
674
|
+
const readFrame = jest.fn(() => Promise.resolve(rewriteSeq(response, 1)));
|
|
675
|
+
const session = new ProtocolV2Session({
|
|
676
|
+
schemas,
|
|
677
|
+
router: 1,
|
|
678
|
+
writeFrame: () => Promise.resolve(),
|
|
679
|
+
readFrame,
|
|
680
|
+
});
|
|
681
|
+
|
|
682
|
+
await expect(
|
|
683
|
+
session.call(
|
|
684
|
+
'Ping',
|
|
685
|
+
{ message: 'hello' },
|
|
686
|
+
{
|
|
687
|
+
timeoutMs: 12_345,
|
|
688
|
+
expectedTypes: ['Success'],
|
|
689
|
+
}
|
|
690
|
+
)
|
|
691
|
+
).resolves.toEqual({
|
|
692
|
+
type: 'Success',
|
|
693
|
+
message: {
|
|
694
|
+
message: 'ok',
|
|
695
|
+
},
|
|
696
|
+
});
|
|
697
|
+
expect(readFrame).toHaveBeenCalledWith(expect.objectContaining({ timeoutMs: 12_345 }));
|
|
698
|
+
});
|
|
699
|
+
|
|
700
|
+
test('session rejects an ACK that belongs to another request sequence', async () => {
|
|
701
|
+
const ack = new Uint8Array(8);
|
|
702
|
+
ack[0] = 0x5a;
|
|
703
|
+
ack[1] = 8;
|
|
704
|
+
ack[4] = 1;
|
|
705
|
+
ack[5] = 1;
|
|
706
|
+
ack[6] = 2;
|
|
707
|
+
ack[3] = protocolV2.crc8(ack, 3);
|
|
708
|
+
ack[7] = protocolV2.crc8(ack, 7);
|
|
709
|
+
const session = new ProtocolV2Session({
|
|
710
|
+
schemas,
|
|
711
|
+
router: 1,
|
|
712
|
+
writeFrame: () => Promise.resolve(),
|
|
713
|
+
readFrame: () => Promise.resolve(ack),
|
|
714
|
+
});
|
|
715
|
+
|
|
716
|
+
await expect(session.call('Ping', { message: 'hello' })).rejects.toEqual(
|
|
717
|
+
expect.objectContaining({
|
|
718
|
+
name: 'ProtocolV2LinkError',
|
|
719
|
+
code: 'ack-sequence',
|
|
720
|
+
message: 'Protocol V2 ACK sequence mismatch: expected 1, got 2',
|
|
721
|
+
})
|
|
722
|
+
);
|
|
723
|
+
});
|
|
724
|
+
|
|
725
|
+
test('session rejects a response from another routing channel', async () => {
|
|
726
|
+
const response = ProtocolV2.encodeFrame(schemas, 'Success', { message: 'wrong route' });
|
|
727
|
+
const session = new ProtocolV2Session({
|
|
728
|
+
schemas,
|
|
729
|
+
router: 1,
|
|
730
|
+
writeFrame: () => Promise.resolve(),
|
|
731
|
+
readFrame: () => Promise.resolve(response),
|
|
732
|
+
});
|
|
733
|
+
|
|
734
|
+
await expect(session.call('Ping', { message: 'hello' })).rejects.toBeInstanceOf(
|
|
735
|
+
ProtocolV2LinkError
|
|
736
|
+
);
|
|
737
|
+
});
|
|
738
|
+
|
|
352
739
|
test('session rejects BLE frames above its configured frame limit before writing', async () => {
|
|
353
740
|
const writeFrame = jest.fn().mockResolvedValue(undefined);
|
|
354
741
|
const response = ProtocolV2.encodeFrame(schemas, 'Success', {
|
|
@@ -359,7 +746,7 @@ describe('Protocol V2 framing and session', () => {
|
|
|
359
746
|
router: 1,
|
|
360
747
|
maxFrameBytes: 2048,
|
|
361
748
|
writeFrame,
|
|
362
|
-
readFrame: () => Promise.resolve(response),
|
|
749
|
+
readFrame: () => Promise.resolve(rewriteSeq(response, 1)),
|
|
363
750
|
});
|
|
364
751
|
|
|
365
752
|
await expect(session.call('Ping', { message: 'x'.repeat(2048) })).rejects.toThrow(
|
|
@@ -368,31 +755,43 @@ describe('Protocol V2 framing and session', () => {
|
|
|
368
755
|
expect(writeFrame).not.toHaveBeenCalled();
|
|
369
756
|
});
|
|
370
757
|
|
|
371
|
-
test('session
|
|
372
|
-
const
|
|
373
|
-
|
|
374
|
-
});
|
|
758
|
+
test('session watchdog covers a write that never settles', async () => {
|
|
759
|
+
const readFrame = jest.fn();
|
|
760
|
+
let writeSignal;
|
|
375
761
|
const session = new ProtocolV2Session({
|
|
376
762
|
schemas,
|
|
377
763
|
router: 1,
|
|
378
|
-
writeFrame: () =>
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
readFrame
|
|
764
|
+
writeFrame: (_frame, context) => {
|
|
765
|
+
writeSignal = context.signal;
|
|
766
|
+
return new Promise(() => {});
|
|
767
|
+
},
|
|
768
|
+
readFrame,
|
|
383
769
|
});
|
|
384
770
|
|
|
385
771
|
await expect(
|
|
386
772
|
session.call('Ping', { message: 'hello' }, { timeoutMs: 10, expectedTypes: ['Success'] })
|
|
387
|
-
).
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
773
|
+
).rejects.toThrow('Protocol V2 response timeout after 10ms for Ping');
|
|
774
|
+
expect(writeSignal.aborted).toBe(true);
|
|
775
|
+
expect(readFrame).not.toHaveBeenCalled();
|
|
776
|
+
});
|
|
777
|
+
|
|
778
|
+
test('session watchdog covers prepareCall before writing', async () => {
|
|
779
|
+
const writeFrame = jest.fn();
|
|
780
|
+
const session = new ProtocolV2Session({
|
|
781
|
+
schemas,
|
|
782
|
+
router: 1,
|
|
783
|
+
prepareCall: () => new Promise(() => {}),
|
|
784
|
+
writeFrame,
|
|
785
|
+
readFrame: jest.fn(),
|
|
392
786
|
});
|
|
787
|
+
|
|
788
|
+
await expect(session.call('Ping', { message: 'hello' }, { timeoutMs: 10 })).rejects.toThrow(
|
|
789
|
+
'Protocol V2 response timeout after 10ms for Ping'
|
|
790
|
+
);
|
|
791
|
+
expect(writeFrame).not.toHaveBeenCalled();
|
|
393
792
|
});
|
|
394
793
|
|
|
395
|
-
test('session
|
|
794
|
+
test('session logs a device-owned response seq without rejecting the frame', async () => {
|
|
396
795
|
const response = ProtocolV2.encodeFrame(schemas, 'ProtocolInfo', {
|
|
397
796
|
version: 2,
|
|
398
797
|
supported_messages: [],
|
|
@@ -412,13 +811,48 @@ describe('Protocol V2 framing and session', () => {
|
|
|
412
811
|
type: 'ProtocolInfo',
|
|
413
812
|
message: {
|
|
414
813
|
version: 2,
|
|
814
|
+
build_fingerprint: null,
|
|
415
815
|
supported_messages: [],
|
|
416
|
-
protobuf_definition: null,
|
|
417
816
|
},
|
|
418
817
|
});
|
|
419
818
|
expect(logger.debug).not.toHaveBeenCalled();
|
|
420
819
|
});
|
|
421
820
|
|
|
821
|
+
test('session accepts gaps in the firmware-global response sequence', async () => {
|
|
822
|
+
const response = ProtocolV2.encodeFrame(schemas, 'Success', { message: 'ok' });
|
|
823
|
+
const readFrame = jest
|
|
824
|
+
.fn()
|
|
825
|
+
.mockResolvedValueOnce(rewriteSeq(response, 1))
|
|
826
|
+
.mockResolvedValueOnce(rewriteSeq(response, 4));
|
|
827
|
+
const session = new ProtocolV2Session({
|
|
828
|
+
schemas,
|
|
829
|
+
router: 1,
|
|
830
|
+
writeFrame: () => Promise.resolve(),
|
|
831
|
+
readFrame,
|
|
832
|
+
});
|
|
833
|
+
|
|
834
|
+
await session.call('Ping', { message: 'first' });
|
|
835
|
+
await expect(session.call('Ping', { message: 'second' })).resolves.toMatchObject({
|
|
836
|
+
type: 'Success',
|
|
837
|
+
});
|
|
838
|
+
});
|
|
839
|
+
|
|
840
|
+
test('session rejects a duplicate device response sequence', async () => {
|
|
841
|
+
const response = ProtocolV2.encodeFrame(schemas, 'Success', { message: 'ok' });
|
|
842
|
+
const readFrame = jest.fn(() => Promise.resolve(rewriteSeq(response, 7)));
|
|
843
|
+
const session = new ProtocolV2Session({
|
|
844
|
+
schemas,
|
|
845
|
+
router: 1,
|
|
846
|
+
writeFrame: () => Promise.resolve(),
|
|
847
|
+
readFrame,
|
|
848
|
+
});
|
|
849
|
+
|
|
850
|
+
await session.call('Ping', { message: 'first' });
|
|
851
|
+
await expect(session.call('Ping', { message: 'second' })).rejects.toThrow(
|
|
852
|
+
'Protocol V2 duplicate response sequence: 7'
|
|
853
|
+
);
|
|
854
|
+
});
|
|
855
|
+
|
|
422
856
|
test('session does not log transmit or receive payload details', async () => {
|
|
423
857
|
const response = ProtocolV2.encodeFrame(schemas, 'Success', {
|
|
424
858
|
message: 'accepted',
|
|
@@ -430,7 +864,7 @@ describe('Protocol V2 framing and session', () => {
|
|
|
430
864
|
schemas,
|
|
431
865
|
router: 1,
|
|
432
866
|
writeFrame: () => Promise.resolve(),
|
|
433
|
-
readFrame: () => Promise.resolve(response),
|
|
867
|
+
readFrame: () => Promise.resolve(rewriteSeq(response, 1)),
|
|
434
868
|
logger,
|
|
435
869
|
logPrefix: 'ProtocolV2 Test',
|
|
436
870
|
});
|
|
@@ -456,7 +890,7 @@ describe('Protocol V2 framing and session', () => {
|
|
|
456
890
|
schemas,
|
|
457
891
|
router: 1,
|
|
458
892
|
writeFrame: () => Promise.resolve(),
|
|
459
|
-
readFrame: () => Promise.resolve(response),
|
|
893
|
+
readFrame: () => Promise.resolve(rewriteSeq(response, 1)),
|
|
460
894
|
logger,
|
|
461
895
|
});
|
|
462
896
|
|
|
@@ -470,6 +904,34 @@ describe('Protocol V2 framing and session', () => {
|
|
|
470
904
|
expect(logger.debug).not.toHaveBeenCalled();
|
|
471
905
|
});
|
|
472
906
|
|
|
907
|
+
test('session reports when the complete request frame has been written', async () => {
|
|
908
|
+
const response = ProtocolV2.encodeFrame(schemas, 'Success', {
|
|
909
|
+
message: 'ok',
|
|
910
|
+
});
|
|
911
|
+
const events = [];
|
|
912
|
+
const onWriteCompleted = jest.fn(metrics => events.push(['written', metrics]));
|
|
913
|
+
const session = new ProtocolV2Session({
|
|
914
|
+
schemas,
|
|
915
|
+
router: 1,
|
|
916
|
+
writeFrame: frame => {
|
|
917
|
+
events.push(['write', frame.byteLength]);
|
|
918
|
+
return Promise.resolve();
|
|
919
|
+
},
|
|
920
|
+
readFrame: () => {
|
|
921
|
+
events.push(['read']);
|
|
922
|
+
return Promise.resolve(rewriteSeq(response, 1));
|
|
923
|
+
},
|
|
924
|
+
});
|
|
925
|
+
|
|
926
|
+
await session.call('Ping', { message: 'metrics' }, { onWriteCompleted });
|
|
927
|
+
|
|
928
|
+
expect(events.map(event => event[0])).toEqual(['write', 'written', 'read']);
|
|
929
|
+
expect(onWriteCompleted).toHaveBeenCalledWith({
|
|
930
|
+
elapsedMs: expect.any(Number),
|
|
931
|
+
frameBytes: expect.any(Number),
|
|
932
|
+
});
|
|
933
|
+
});
|
|
934
|
+
|
|
473
935
|
test('session skips unrelated terminal frames when expected response types are provided', async () => {
|
|
474
936
|
const stale = ProtocolV2.encodeFrame(schemas, 'Success', {
|
|
475
937
|
message: 'stale response',
|
|
@@ -481,7 +943,10 @@ describe('Protocol V2 framing and session', () => {
|
|
|
481
943
|
const logger = {
|
|
482
944
|
debug: jest.fn(),
|
|
483
945
|
};
|
|
484
|
-
const readFrame = jest
|
|
946
|
+
const readFrame = jest
|
|
947
|
+
.fn()
|
|
948
|
+
.mockResolvedValueOnce(rewriteSeq(stale, 1))
|
|
949
|
+
.mockResolvedValueOnce(rewriteSeq(response, 2));
|
|
485
950
|
const session = new ProtocolV2Session({
|
|
486
951
|
schemas,
|
|
487
952
|
router: 1,
|
|
@@ -496,8 +961,8 @@ describe('Protocol V2 framing and session', () => {
|
|
|
496
961
|
type: 'ProtocolInfo',
|
|
497
962
|
message: {
|
|
498
963
|
version: 2,
|
|
964
|
+
build_fingerprint: null,
|
|
499
965
|
supported_messages: [],
|
|
500
|
-
protobuf_definition: null,
|
|
501
966
|
},
|
|
502
967
|
});
|
|
503
968
|
|
|
@@ -505,22 +970,10 @@ describe('Protocol V2 framing and session', () => {
|
|
|
505
970
|
expect(logger.debug).toHaveBeenCalledWith(expect.stringContaining('skip unexpected response'));
|
|
506
971
|
});
|
|
507
972
|
|
|
508
|
-
test('session
|
|
973
|
+
test('session can return after a request frame is written', async () => {
|
|
509
974
|
const written = [];
|
|
510
|
-
const
|
|
511
|
-
|
|
512
|
-
});
|
|
513
|
-
const success = ProtocolV2.encodeFrame(schemas, 'Success', {
|
|
514
|
-
message: 'ok',
|
|
515
|
-
});
|
|
516
|
-
const onIntermediateResponse = jest.fn();
|
|
517
|
-
const readFrame = jest.fn(() => {
|
|
518
|
-
const [writtenFrame] = written;
|
|
519
|
-
const { seq } = protocolV2.decodeFrame(writtenFrame);
|
|
520
|
-
return Promise.resolve(
|
|
521
|
-
readFrame.mock.calls.length === 1 ? rewriteSeq(progress, seq) : rewriteSeq(success, seq)
|
|
522
|
-
);
|
|
523
|
-
});
|
|
975
|
+
const readFrame = jest.fn();
|
|
976
|
+
const onWriteCompleted = jest.fn();
|
|
524
977
|
const session = new ProtocolV2Session({
|
|
525
978
|
schemas,
|
|
526
979
|
router: 1,
|
|
@@ -533,26 +986,20 @@ describe('Protocol V2 framing and session', () => {
|
|
|
533
986
|
|
|
534
987
|
const result = await session.call(
|
|
535
988
|
'DeviceFirmwareUpdateRequest',
|
|
536
|
-
{
|
|
989
|
+
{},
|
|
537
990
|
{
|
|
538
|
-
|
|
539
|
-
|
|
991
|
+
returnAfterWrite: true,
|
|
992
|
+
onWriteCompleted,
|
|
540
993
|
}
|
|
541
994
|
);
|
|
542
995
|
|
|
543
|
-
expect(
|
|
544
|
-
expect(
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
});
|
|
550
|
-
expect(result).toEqual({
|
|
551
|
-
type: 'Success',
|
|
552
|
-
message: {
|
|
553
|
-
message: 'ok',
|
|
554
|
-
},
|
|
555
|
-
});
|
|
996
|
+
expect(written).toHaveLength(1);
|
|
997
|
+
expect(ProtocolV2.decodeFrame(schemas, written[0]).messageName).toBe(
|
|
998
|
+
'DeviceFirmwareUpdateRequest'
|
|
999
|
+
);
|
|
1000
|
+
expect(readFrame).not.toHaveBeenCalled();
|
|
1001
|
+
expect(onWriteCompleted).toHaveBeenCalledTimes(1);
|
|
1002
|
+
expect(result).toEqual({ type: 'WriteCompleted', message: {} });
|
|
556
1003
|
});
|
|
557
1004
|
|
|
558
1005
|
test('probeProtocolV2 accepts Success as a normal V2 probe response', async () => {
|
|
@@ -695,7 +1142,7 @@ describe('Protocol V2 framing and session', () => {
|
|
|
695
1142
|
}
|
|
696
1143
|
return Promise.resolve();
|
|
697
1144
|
},
|
|
698
|
-
readFrame: () => Promise.resolve(response),
|
|
1145
|
+
readFrame: () => Promise.resolve(rewriteSeq(response, 1)),
|
|
699
1146
|
});
|
|
700
1147
|
|
|
701
1148
|
await expect(session.call('Ping', { message: '1' })).rejects.toThrow('transport write failed');
|
|
@@ -764,6 +1211,7 @@ describe('Protocol V2 framing and session', () => {
|
|
|
764
1211
|
const writeContexts = [];
|
|
765
1212
|
const readContexts = [];
|
|
766
1213
|
const response = ProtocolV2.encodeFrame(schemas, 'Success', { message: 'ok' });
|
|
1214
|
+
let responseSeq = 0;
|
|
767
1215
|
const session = new ProtocolV2Session({
|
|
768
1216
|
schemas,
|
|
769
1217
|
router: 1,
|
|
@@ -774,18 +1222,67 @@ describe('Protocol V2 framing and session', () => {
|
|
|
774
1222
|
},
|
|
775
1223
|
readFrame: context => {
|
|
776
1224
|
readContexts.push(context);
|
|
777
|
-
|
|
1225
|
+
responseSeq = protocolV2.nextProtoSeq(responseSeq);
|
|
1226
|
+
return Promise.resolve(rewriteSeq(response, responseSeq));
|
|
778
1227
|
},
|
|
779
1228
|
});
|
|
780
1229
|
|
|
781
1230
|
await session.call('Ping', { message: 'ping' }, { timeoutMs: 123 });
|
|
782
|
-
await session.call('FileWrite', {}, { timeoutMs: 456 });
|
|
1231
|
+
await session.call('FileWrite', {}, { timeoutMs: 456, writeWithResponse: true });
|
|
1232
|
+
await session.call('Ping', { message: 'default-timeout' });
|
|
783
1233
|
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
1234
|
+
const normalizeContext = ({ signal, ...context }) => ({
|
|
1235
|
+
...context,
|
|
1236
|
+
signalAborted: signal.aborted,
|
|
1237
|
+
});
|
|
1238
|
+
expect(writeContexts.map(normalizeContext)).toEqual([
|
|
1239
|
+
{
|
|
1240
|
+
messageName: 'Ping',
|
|
1241
|
+
timeoutMs: 123,
|
|
1242
|
+
highThroughput: false,
|
|
1243
|
+
generation: 7,
|
|
1244
|
+
signalAborted: false,
|
|
1245
|
+
},
|
|
1246
|
+
{
|
|
1247
|
+
messageName: 'FileWrite',
|
|
1248
|
+
timeoutMs: 456,
|
|
1249
|
+
highThroughput: true,
|
|
1250
|
+
writeWithResponse: true,
|
|
1251
|
+
generation: 7,
|
|
1252
|
+
signalAborted: false,
|
|
1253
|
+
},
|
|
1254
|
+
{
|
|
1255
|
+
messageName: 'Ping',
|
|
1256
|
+
timeoutMs: PROTOCOL_V2_DEFAULT_RESPONSE_TIMEOUT_MS,
|
|
1257
|
+
highThroughput: false,
|
|
1258
|
+
generation: 7,
|
|
1259
|
+
signalAborted: false,
|
|
1260
|
+
},
|
|
1261
|
+
]);
|
|
1262
|
+
expect(readContexts.map(normalizeContext)).toEqual([
|
|
1263
|
+
{
|
|
1264
|
+
messageName: 'Ping',
|
|
1265
|
+
timeoutMs: 123,
|
|
1266
|
+
highThroughput: false,
|
|
1267
|
+
generation: 7,
|
|
1268
|
+
signalAborted: false,
|
|
1269
|
+
},
|
|
1270
|
+
{
|
|
1271
|
+
messageName: 'FileWrite',
|
|
1272
|
+
timeoutMs: 456,
|
|
1273
|
+
highThroughput: true,
|
|
1274
|
+
writeWithResponse: true,
|
|
1275
|
+
generation: 7,
|
|
1276
|
+
signalAborted: false,
|
|
1277
|
+
},
|
|
1278
|
+
{
|
|
1279
|
+
messageName: 'Ping',
|
|
1280
|
+
timeoutMs: PROTOCOL_V2_DEFAULT_RESPONSE_TIMEOUT_MS,
|
|
1281
|
+
highThroughput: false,
|
|
1282
|
+
generation: 7,
|
|
1283
|
+
signalAborted: false,
|
|
1284
|
+
},
|
|
787
1285
|
]);
|
|
788
|
-
expect(readContexts).toEqual(writeContexts);
|
|
789
1286
|
});
|
|
790
1287
|
|
|
791
1288
|
test('assembler throws and resets on frames with an impossible length field', () => {
|