@onekeyfe/hd-transport 1.2.0-alpha.13 → 1.2.0-alpha.131
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 +144 -4
- package/__tests__/protocol-v2-ble-frame-writer.test.js +119 -0
- package/__tests__/protocol-v2-link-manager.test.js +176 -9
- package/__tests__/protocol-v2-usb-transport-base.test.js +36 -2
- package/__tests__/protocol-v2.test.js +540 -108
- 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 +645 -217
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +679 -153
- package/dist/protocols/index.d.ts +17 -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 +16 -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 +5 -0
- package/dist/protocols/v2/link-manager.d.ts.map +1 -1
- package/dist/protocols/v2/session.d.ts +14 -4
- package/dist/protocols/v2/session.d.ts.map +1 -1
- package/dist/protocols/v2/usb-transport-base.d.ts +2 -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 +301 -156
- 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 +523 -449
- package/package.json +2 -2
- package/scripts/protobuf-build.sh +112 -32
- package/scripts/protobuf-patches/index.js +1 -0
- package/scripts/protobuf-types.js +10 -1
- package/src/constants.ts +30 -16
- package/src/index.ts +5 -1
- package/src/protocols/index.ts +114 -3
- 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 +54 -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 +65 -5
- package/src/protocols/v2/session.ts +224 -80
- package/src/protocols/v2/usb-transport-base.ts +42 -10
- package/src/serialization/protobuf/decode.ts +4 -1
- package/src/serialization/protobuf/encode.ts +5 -1
- package/src/types/messages.ts +375 -198
- package/src/types/transport.ts +30 -3
- package/src/utils/transportLog.ts +102 -0
|
@@ -2,12 +2,27 @@ 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,
|
|
11
|
+
isProtocolV2LinkDisabledFailure,
|
|
9
12
|
} = require('../src/protocols/v2/session');
|
|
10
13
|
const protocolV2 = require('../src/protocols/v2');
|
|
14
|
+
const {
|
|
15
|
+
PROTOCOL_V2_BLE_FIRMWARE_FILE_CHUNK_SIZE,
|
|
16
|
+
PROTOCOL_V2_BLE_FRAME_MAX_BYTES,
|
|
17
|
+
PROTOCOL_V2_DEFAULT_RESPONSE_TIMEOUT_MS,
|
|
18
|
+
PROTOCOL_V2_FRAME_MAX_BYTES,
|
|
19
|
+
} = require('../src/constants');
|
|
20
|
+
|
|
21
|
+
test('keeps sensitive acknowledgements separate from high-throughput calls', () => {
|
|
22
|
+
expect(isProtocolV2HighThroughputCall('PassphraseAck')).toBe(false);
|
|
23
|
+
expect(isProtocolV2HighThroughputCall('PinMatrixAck')).toBe(false);
|
|
24
|
+
expect(isProtocolV2HighThroughputCall('FilesystemFileWrite')).toBe(true);
|
|
25
|
+
});
|
|
11
26
|
|
|
12
27
|
const protocolV1Messages = parseConfigure({
|
|
13
28
|
nested: {
|
|
@@ -68,14 +83,14 @@ const protocolV2Messages = parseConfigure({
|
|
|
68
83
|
type: 'uint32',
|
|
69
84
|
id: 1,
|
|
70
85
|
},
|
|
71
|
-
|
|
72
|
-
type: '
|
|
86
|
+
build_fingerprint: {
|
|
87
|
+
type: 'string',
|
|
73
88
|
id: 2,
|
|
74
|
-
rule: 'repeated',
|
|
75
89
|
},
|
|
76
|
-
|
|
77
|
-
type: '
|
|
90
|
+
supported_messages: {
|
|
91
|
+
type: 'uint32',
|
|
78
92
|
id: 3,
|
|
93
|
+
rule: 'repeated',
|
|
79
94
|
},
|
|
80
95
|
},
|
|
81
96
|
},
|
|
@@ -123,7 +138,7 @@ const protocolV2Messages = parseConfigure({
|
|
|
123
138
|
},
|
|
124
139
|
},
|
|
125
140
|
},
|
|
126
|
-
|
|
141
|
+
DeviceFirmwareUpdateStage: {
|
|
127
142
|
fields: {
|
|
128
143
|
targets: {
|
|
129
144
|
type: 'DeviceFirmwareTarget',
|
|
@@ -132,6 +147,9 @@ const protocolV2Messages = parseConfigure({
|
|
|
132
147
|
},
|
|
133
148
|
},
|
|
134
149
|
},
|
|
150
|
+
DeviceFirmwareUpdateRequest: {
|
|
151
|
+
fields: {},
|
|
152
|
+
},
|
|
135
153
|
DeviceFirmwareUpdateRecord: {
|
|
136
154
|
fields: {
|
|
137
155
|
target_id: {
|
|
@@ -161,6 +179,18 @@ const protocolV2Messages = parseConfigure({
|
|
|
161
179
|
},
|
|
162
180
|
},
|
|
163
181
|
},
|
|
182
|
+
DeviceSession: {
|
|
183
|
+
fields: {
|
|
184
|
+
session_id: {
|
|
185
|
+
type: 'bytes',
|
|
186
|
+
id: 1,
|
|
187
|
+
},
|
|
188
|
+
btc_test_address: {
|
|
189
|
+
type: 'string',
|
|
190
|
+
id: 2,
|
|
191
|
+
},
|
|
192
|
+
},
|
|
193
|
+
},
|
|
164
194
|
FileWrite: {
|
|
165
195
|
fields: {},
|
|
166
196
|
},
|
|
@@ -192,8 +222,10 @@ const protocolV2Messages = parseConfigure({
|
|
|
192
222
|
MessageType_Success: 60207,
|
|
193
223
|
MessageType_Failure: 60208,
|
|
194
224
|
MessageType_FileWrite: 60805,
|
|
195
|
-
|
|
225
|
+
MessageType_DeviceFirmwareUpdateStage: 61000,
|
|
226
|
+
MessageType_DeviceFirmwareUpdateRequest: 61001,
|
|
196
227
|
MessageType_DeviceFirmwareUpdateStatus: 61002,
|
|
228
|
+
MessageType_DeviceSession: 61201,
|
|
197
229
|
MessageType_PartialNested: 62000,
|
|
198
230
|
},
|
|
199
231
|
},
|
|
@@ -204,9 +236,37 @@ const schemas = {
|
|
|
204
236
|
protocolV1: protocolV1Messages,
|
|
205
237
|
protocolV2: protocolV2Messages,
|
|
206
238
|
};
|
|
239
|
+
const productionProtocolV2Messages = parseConfigure(require('../messages-protocol-v2.json'));
|
|
240
|
+
|
|
241
|
+
const legacyProtocolInfoMessage = parseConfigure({
|
|
242
|
+
nested: {
|
|
243
|
+
ProtocolInfo: {
|
|
244
|
+
fields: {
|
|
245
|
+
version: {
|
|
246
|
+
type: 'uint32',
|
|
247
|
+
id: 1,
|
|
248
|
+
rule: 'required',
|
|
249
|
+
},
|
|
250
|
+
supported_messages: {
|
|
251
|
+
type: 'uint32',
|
|
252
|
+
id: 2,
|
|
253
|
+
rule: 'repeated',
|
|
254
|
+
options: {
|
|
255
|
+
packed: false,
|
|
256
|
+
},
|
|
257
|
+
},
|
|
258
|
+
protobuf_definition: {
|
|
259
|
+
type: 'string',
|
|
260
|
+
id: 3,
|
|
261
|
+
},
|
|
262
|
+
},
|
|
263
|
+
},
|
|
264
|
+
},
|
|
265
|
+
}).lookupType('ProtocolInfo');
|
|
207
266
|
|
|
208
267
|
const rewriteSeq = (frame, seq) => {
|
|
209
268
|
const copy = new Uint8Array(frame);
|
|
269
|
+
copy[4] = 1;
|
|
210
270
|
copy[6] = seq;
|
|
211
271
|
copy[copy.length - 1] = protocolV2.crc8(copy, copy.length - 1);
|
|
212
272
|
return copy;
|
|
@@ -216,8 +276,8 @@ describe('Protocol V2 framing and session', () => {
|
|
|
216
276
|
test('encodes and decodes Protocol V2 protobuf frames', () => {
|
|
217
277
|
const frame = ProtocolV2.encodeFrame(schemas, 'ProtocolInfo', {
|
|
218
278
|
version: 1,
|
|
279
|
+
build_fingerprint: 'application__1.0.0__abc__DEV__DEBUG',
|
|
219
280
|
supported_messages: [60200, 60201],
|
|
220
|
-
protobuf_definition: 'proto',
|
|
221
281
|
});
|
|
222
282
|
|
|
223
283
|
const parsed = protocolV2.decodeFrame(frame);
|
|
@@ -232,12 +292,70 @@ describe('Protocol V2 framing and session', () => {
|
|
|
232
292
|
seq: parsed.seq,
|
|
233
293
|
message: {
|
|
234
294
|
version: 1,
|
|
295
|
+
build_fingerprint: 'application__1.0.0__abc__DEV__DEBUG',
|
|
235
296
|
supported_messages: [60200, 60201],
|
|
236
|
-
protobuf_definition: 'proto',
|
|
237
297
|
},
|
|
238
298
|
});
|
|
239
299
|
});
|
|
240
300
|
|
|
301
|
+
test('decodes a two-byte legacy ProtocolInfo at the generic frame boundary', () => {
|
|
302
|
+
const frame = protocolV2.encodeProtobufFrame(60201, new Uint8Array([0x08, 0x01]));
|
|
303
|
+
const productionSchemas = {
|
|
304
|
+
protocolV1: protocolV1Messages,
|
|
305
|
+
protocolV2: productionProtocolV2Messages,
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
expect(ProtocolV2.decodeFrame(productionSchemas, frame)).toMatchObject({
|
|
309
|
+
type: 'ProtocolInfo',
|
|
310
|
+
message: {
|
|
311
|
+
version: 1,
|
|
312
|
+
build_fingerprint: '',
|
|
313
|
+
supported_messages: [],
|
|
314
|
+
protobuf_definition: null,
|
|
315
|
+
},
|
|
316
|
+
});
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
test('does not reinterpret a malformed current ProtocolInfo as the legacy layout', () => {
|
|
320
|
+
const frame = protocolV2.encodeProtobufFrame(60201, new Uint8Array([0x08, 0x01, 0x18, 0x01]));
|
|
321
|
+
|
|
322
|
+
expect(() =>
|
|
323
|
+
ProtocolV2.decodeFrame(
|
|
324
|
+
{
|
|
325
|
+
protocolV1: protocolV1Messages,
|
|
326
|
+
protocolV2: productionProtocolV2Messages,
|
|
327
|
+
},
|
|
328
|
+
frame
|
|
329
|
+
)
|
|
330
|
+
).toThrow('Protocol V2 protobuf decode failed for "ProtocolInfo"');
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
test('preserves legacy ProtocolInfo capabilities when using the old field numbers', () => {
|
|
334
|
+
const payload = legacyProtocolInfoMessage
|
|
335
|
+
.encode({
|
|
336
|
+
version: 1,
|
|
337
|
+
supported_messages: [60602],
|
|
338
|
+
protobuf_definition: 'legacy',
|
|
339
|
+
})
|
|
340
|
+
.finish();
|
|
341
|
+
const frame = protocolV2.encodeProtobufFrame(60201, payload);
|
|
342
|
+
|
|
343
|
+
const decoded = ProtocolV2.decodeFrame(
|
|
344
|
+
{
|
|
345
|
+
protocolV1: protocolV1Messages,
|
|
346
|
+
protocolV2: productionProtocolV2Messages,
|
|
347
|
+
},
|
|
348
|
+
frame
|
|
349
|
+
);
|
|
350
|
+
|
|
351
|
+
expect(decoded.message).toEqual({
|
|
352
|
+
version: 1,
|
|
353
|
+
build_fingerprint: '',
|
|
354
|
+
supported_messages: [60602],
|
|
355
|
+
protobuf_definition: null,
|
|
356
|
+
});
|
|
357
|
+
});
|
|
358
|
+
|
|
241
359
|
test('does not encode V1-only messages into Protocol V2 frames', () => {
|
|
242
360
|
expect(() => ProtocolV2.encodeFrame(schemas, 'Ping', { message: 'ok' })).not.toThrow();
|
|
243
361
|
expect(() => ProtocolV2.encodeFrame(schemas, 'OnekeyGetFeatures', {})).toThrow(
|
|
@@ -258,6 +376,34 @@ describe('Protocol V2 framing and session', () => {
|
|
|
258
376
|
expect(decoded.message).toEqual({ message: 'ok' });
|
|
259
377
|
});
|
|
260
378
|
|
|
379
|
+
test('round-trips a DeviceSession response with a 32-byte session id', () => {
|
|
380
|
+
const sessionId = '01'.repeat(32);
|
|
381
|
+
const frame = ProtocolV2.encodeFrame(schemas, 'DeviceSession', {
|
|
382
|
+
session_id: sessionId,
|
|
383
|
+
btc_test_address: 'tb1qwallet',
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
const decoded = ProtocolV2.decodeFrame(schemas, frame);
|
|
387
|
+
|
|
388
|
+
expect(decoded.type).toBe('DeviceSession');
|
|
389
|
+
expect(decoded.message).toEqual({
|
|
390
|
+
session_id: sessionId,
|
|
391
|
+
btc_test_address: 'tb1qwallet',
|
|
392
|
+
});
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
test('reports malformed DeviceSession protobuf as a schema-compatible transport error', () => {
|
|
396
|
+
const malformedPayload = new Uint8Array(32);
|
|
397
|
+
malformedPayload[0] = 0x0a;
|
|
398
|
+
malformedPayload[1] = 101;
|
|
399
|
+
const frame = protocolV2.encodeProtobufFrame(61201, malformedPayload);
|
|
400
|
+
|
|
401
|
+
expect(() => ProtocolV2.decodeFrame(schemas, frame)).toThrow(
|
|
402
|
+
'Protocol V2 protobuf decode failed for "DeviceSession" (61201, 32-byte payload); ' +
|
|
403
|
+
'the payload is malformed or incompatible with the active SDK schema.'
|
|
404
|
+
);
|
|
405
|
+
});
|
|
406
|
+
|
|
261
407
|
test('decodes missing optional nested messages as null', () => {
|
|
262
408
|
const frame = ProtocolV2.encodeFrame(schemas, 'PartialNested', {
|
|
263
409
|
label: 'only label',
|
|
@@ -284,6 +430,60 @@ describe('Protocol V2 framing and session', () => {
|
|
|
284
430
|
expect(() => assembler.push(oversized)).toThrow('Protocol V2 frame too large');
|
|
285
431
|
});
|
|
286
432
|
|
|
433
|
+
test('enforces a transport-specific receive frame boundary', () => {
|
|
434
|
+
const assembler = new ProtocolV2FrameAssembler(2048);
|
|
435
|
+
const oversizedHeader = new Uint8Array([0x5a, 0x01, 0x08]);
|
|
436
|
+
|
|
437
|
+
expect(() => assembler.push(oversizedHeader)).toThrow('Protocol V2 frame too large: 2049');
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
test('enforces the firmware 4200-byte Protocol V2 frame boundary', () => {
|
|
441
|
+
const boundaryFrame = ProtocolV2.encodeFrame(schemas, 'Ping', {
|
|
442
|
+
message: 'x'.repeat(4187),
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
expect(PROTOCOL_V2_FRAME_MAX_BYTES).toBe(4200);
|
|
446
|
+
expect(boundaryFrame).toHaveLength(PROTOCOL_V2_FRAME_MAX_BYTES);
|
|
447
|
+
expect(() =>
|
|
448
|
+
ProtocolV2.encodeFrame(schemas, 'Ping', {
|
|
449
|
+
message: 'x'.repeat(4188),
|
|
450
|
+
})
|
|
451
|
+
).toThrow('Protocol V2 frame too large: 4201 > 4200');
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
test('keeps optimized BLE firmware chunks inside the transport frame boundary', () => {
|
|
455
|
+
const productionSchemas = {
|
|
456
|
+
protocolV1: protocolV1Messages,
|
|
457
|
+
protocolV2: productionProtocolV2Messages,
|
|
458
|
+
};
|
|
459
|
+
const stagingPaths = [
|
|
460
|
+
'vol0:/bootloader.bin',
|
|
461
|
+
'vol0:/application_p1.bin',
|
|
462
|
+
'vol0:/application_p2.bin',
|
|
463
|
+
'vol0:/coprocessor.bin',
|
|
464
|
+
'vol0:/se01.bin',
|
|
465
|
+
'vol0:/se02.bin',
|
|
466
|
+
'vol0:/se03.bin',
|
|
467
|
+
'vol0:/se04.bin',
|
|
468
|
+
];
|
|
469
|
+
|
|
470
|
+
for (const path of stagingPaths) {
|
|
471
|
+
const frame = ProtocolV2.encodeFrame(productionSchemas, 'FilesystemFileWrite', {
|
|
472
|
+
file: {
|
|
473
|
+
path,
|
|
474
|
+
offset: 0xffffffff,
|
|
475
|
+
total_size: 0xffffffff,
|
|
476
|
+
data: new Uint8Array(PROTOCOL_V2_BLE_FIRMWARE_FILE_CHUNK_SIZE),
|
|
477
|
+
},
|
|
478
|
+
overwrite: true,
|
|
479
|
+
append: false,
|
|
480
|
+
ui_percentage: 100,
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
expect(frame.length).toBeLessThanOrEqual(PROTOCOL_V2_BLE_FRAME_MAX_BYTES);
|
|
484
|
+
}
|
|
485
|
+
});
|
|
486
|
+
|
|
287
487
|
test('keeps bytes after the first complete frame for the next read', () => {
|
|
288
488
|
const first = ProtocolV2.encodeFrame(schemas, 'ProtocolInfo', {
|
|
289
489
|
version: 1,
|
|
@@ -329,19 +529,51 @@ describe('Protocol V2 framing and session', () => {
|
|
|
329
529
|
type: 'ProtocolInfo',
|
|
330
530
|
message: {
|
|
331
531
|
version: 2,
|
|
532
|
+
build_fingerprint: null,
|
|
332
533
|
supported_messages: [60206],
|
|
534
|
+
},
|
|
535
|
+
});
|
|
536
|
+
});
|
|
537
|
+
|
|
538
|
+
test('session decodes legacy ProtocolInfo through the generic frame boundary', async () => {
|
|
539
|
+
const productionSchemas = {
|
|
540
|
+
protocolV1: protocolV1Messages,
|
|
541
|
+
protocolV2: productionProtocolV2Messages,
|
|
542
|
+
};
|
|
543
|
+
const response = protocolV2.encodeProtobufFrame(60201, new Uint8Array([0x08, 0x01]));
|
|
544
|
+
const session = new ProtocolV2Session({
|
|
545
|
+
schemas: productionSchemas,
|
|
546
|
+
router: 1,
|
|
547
|
+
writeFrame: () => Promise.resolve(),
|
|
548
|
+
readFrame: () => Promise.resolve(rewriteSeq(response, 1)),
|
|
549
|
+
});
|
|
550
|
+
|
|
551
|
+
await expect(
|
|
552
|
+
session.call(
|
|
553
|
+
'ProtocolInfoRequest',
|
|
554
|
+
{ eventless_wallet_session: true },
|
|
555
|
+
{
|
|
556
|
+
expectedTypes: ['ProtocolInfo'],
|
|
557
|
+
}
|
|
558
|
+
)
|
|
559
|
+
).resolves.toEqual({
|
|
560
|
+
type: 'ProtocolInfo',
|
|
561
|
+
message: {
|
|
562
|
+
version: 1,
|
|
563
|
+
build_fingerprint: '',
|
|
564
|
+
supported_messages: [],
|
|
333
565
|
protobuf_definition: null,
|
|
334
566
|
},
|
|
335
567
|
});
|
|
336
568
|
});
|
|
337
569
|
|
|
338
|
-
test('session
|
|
570
|
+
test('session does not log Protocol V2 TX and RX frames', async () => {
|
|
339
571
|
const written = [];
|
|
340
572
|
const logger = { debug: jest.fn() };
|
|
341
573
|
const response = ProtocolV2.encodeFrame(schemas, 'ProtocolInfo', {
|
|
342
574
|
version: 2,
|
|
575
|
+
build_fingerprint: 'application__1.0.0__sensitive__DEV__DEBUG',
|
|
343
576
|
supported_messages: [60206],
|
|
344
|
-
protobuf_definition: 'sensitive-schema',
|
|
345
577
|
});
|
|
346
578
|
const session = new ProtocolV2Session({
|
|
347
579
|
schemas,
|
|
@@ -358,27 +590,10 @@ describe('Protocol V2 framing and session', () => {
|
|
|
358
590
|
|
|
359
591
|
await session.call('ProtocolInfoRequest', {});
|
|
360
592
|
|
|
361
|
-
|
|
362
|
-
const rx = protocolV2.decodeFrame(rewriteSeq(response, tx.seq));
|
|
363
|
-
expect(logger.debug).toHaveBeenCalledWith('[ProtocolV2 Test] TX', {
|
|
364
|
-
method: 'ProtocolInfoRequest',
|
|
365
|
-
type: 'ProtocolInfoRequest',
|
|
366
|
-
typeId: tx.messageTypeId,
|
|
367
|
-
seq: tx.seq,
|
|
368
|
-
bytes: written[0].length,
|
|
369
|
-
});
|
|
370
|
-
expect(logger.debug).toHaveBeenCalledWith('[ProtocolV2 Test] RX', {
|
|
371
|
-
method: 'ProtocolInfoRequest',
|
|
372
|
-
type: 'ProtocolInfo',
|
|
373
|
-
typeId: rx.messageTypeId,
|
|
374
|
-
seq: rx.seq,
|
|
375
|
-
bytes: response.length,
|
|
376
|
-
});
|
|
377
|
-
expect(JSON.stringify(logger.debug.mock.calls)).not.toContain('sensitive-schema');
|
|
378
|
-
expect(JSON.stringify(logger.debug.mock.calls)).not.toContain('supported_messages');
|
|
593
|
+
expect(logger.debug).not.toHaveBeenCalled();
|
|
379
594
|
});
|
|
380
595
|
|
|
381
|
-
test('session
|
|
596
|
+
test('session does not log RX frame metadata when protobuf payload decoding fails', async () => {
|
|
382
597
|
const logger = { debug: jest.fn() };
|
|
383
598
|
const response = protocolV2.encodeProtobufFrame(
|
|
384
599
|
60208,
|
|
@@ -390,19 +605,12 @@ describe('Protocol V2 framing and session', () => {
|
|
|
390
605
|
logger,
|
|
391
606
|
logPrefix: 'ProtocolV2 Test',
|
|
392
607
|
writeFrame: () => Promise.resolve(),
|
|
393
|
-
readFrame: () => Promise.resolve(response),
|
|
608
|
+
readFrame: () => Promise.resolve(rewriteSeq(response, 1)),
|
|
394
609
|
});
|
|
395
610
|
|
|
396
611
|
await expect(session.call('Ping', { message: 'hello' })).rejects.toThrow();
|
|
397
612
|
|
|
398
|
-
|
|
399
|
-
expect(logger.debug).toHaveBeenCalledWith('[ProtocolV2 Test] RX', {
|
|
400
|
-
method: 'Ping',
|
|
401
|
-
type: 'Failure',
|
|
402
|
-
typeId: rx.messageTypeId,
|
|
403
|
-
seq: rx.seq,
|
|
404
|
-
bytes: response.length,
|
|
405
|
-
});
|
|
613
|
+
expect(logger.debug).not.toHaveBeenCalled();
|
|
406
614
|
});
|
|
407
615
|
|
|
408
616
|
test('session skips Proto Link ACK frames before decoding the protobuf response', async () => {
|
|
@@ -419,7 +627,10 @@ describe('Protocol V2 framing and session', () => {
|
|
|
419
627
|
const response = ProtocolV2.encodeFrame(schemas, 'Success', {
|
|
420
628
|
message: 'ok',
|
|
421
629
|
});
|
|
422
|
-
const readFrame = jest
|
|
630
|
+
const readFrame = jest
|
|
631
|
+
.fn()
|
|
632
|
+
.mockResolvedValueOnce(ack)
|
|
633
|
+
.mockResolvedValueOnce(rewriteSeq(response, 1));
|
|
423
634
|
const session = new ProtocolV2Session({
|
|
424
635
|
schemas,
|
|
425
636
|
router: 1,
|
|
@@ -436,6 +647,96 @@ describe('Protocol V2 framing and session', () => {
|
|
|
436
647
|
expect(readFrame).toHaveBeenCalledTimes(2);
|
|
437
648
|
});
|
|
438
649
|
|
|
650
|
+
test('session accepts a terminal response before an ACK', async () => {
|
|
651
|
+
const response = ProtocolV2.encodeFrame(schemas, 'Success', {
|
|
652
|
+
message: 'ok',
|
|
653
|
+
});
|
|
654
|
+
const session = new ProtocolV2Session({
|
|
655
|
+
schemas,
|
|
656
|
+
router: 1,
|
|
657
|
+
writeFrame: () => Promise.resolve(),
|
|
658
|
+
readFrame: () => Promise.resolve(rewriteSeq(response, 1)),
|
|
659
|
+
});
|
|
660
|
+
|
|
661
|
+
await expect(
|
|
662
|
+
session.call('Ping', { message: 'hello' }, { expectedTypes: ['Success'] })
|
|
663
|
+
).resolves.toEqual({
|
|
664
|
+
type: 'Success',
|
|
665
|
+
message: {
|
|
666
|
+
message: 'ok',
|
|
667
|
+
},
|
|
668
|
+
});
|
|
669
|
+
});
|
|
670
|
+
|
|
671
|
+
test('session uses the unified response timeout before the first ACK', async () => {
|
|
672
|
+
const response = ProtocolV2.encodeFrame(schemas, 'Success', {
|
|
673
|
+
message: 'ok',
|
|
674
|
+
});
|
|
675
|
+
const readFrame = jest.fn(() => Promise.resolve(rewriteSeq(response, 1)));
|
|
676
|
+
const session = new ProtocolV2Session({
|
|
677
|
+
schemas,
|
|
678
|
+
router: 1,
|
|
679
|
+
writeFrame: () => Promise.resolve(),
|
|
680
|
+
readFrame,
|
|
681
|
+
});
|
|
682
|
+
|
|
683
|
+
await expect(
|
|
684
|
+
session.call(
|
|
685
|
+
'Ping',
|
|
686
|
+
{ message: 'hello' },
|
|
687
|
+
{
|
|
688
|
+
timeoutMs: 12_345,
|
|
689
|
+
expectedTypes: ['Success'],
|
|
690
|
+
}
|
|
691
|
+
)
|
|
692
|
+
).resolves.toEqual({
|
|
693
|
+
type: 'Success',
|
|
694
|
+
message: {
|
|
695
|
+
message: 'ok',
|
|
696
|
+
},
|
|
697
|
+
});
|
|
698
|
+
expect(readFrame).toHaveBeenCalledWith(expect.objectContaining({ timeoutMs: 12_345 }));
|
|
699
|
+
});
|
|
700
|
+
|
|
701
|
+
test('session rejects an ACK that belongs to another request sequence', async () => {
|
|
702
|
+
const ack = new Uint8Array(8);
|
|
703
|
+
ack[0] = 0x5a;
|
|
704
|
+
ack[1] = 8;
|
|
705
|
+
ack[4] = 1;
|
|
706
|
+
ack[5] = 1;
|
|
707
|
+
ack[6] = 2;
|
|
708
|
+
ack[3] = protocolV2.crc8(ack, 3);
|
|
709
|
+
ack[7] = protocolV2.crc8(ack, 7);
|
|
710
|
+
const session = new ProtocolV2Session({
|
|
711
|
+
schemas,
|
|
712
|
+
router: 1,
|
|
713
|
+
writeFrame: () => Promise.resolve(),
|
|
714
|
+
readFrame: () => Promise.resolve(ack),
|
|
715
|
+
});
|
|
716
|
+
|
|
717
|
+
await expect(session.call('Ping', { message: 'hello' })).rejects.toEqual(
|
|
718
|
+
expect.objectContaining({
|
|
719
|
+
name: 'ProtocolV2LinkError',
|
|
720
|
+
code: 'ack-sequence',
|
|
721
|
+
message: 'Protocol V2 ACK sequence mismatch: expected 1, got 2',
|
|
722
|
+
})
|
|
723
|
+
);
|
|
724
|
+
});
|
|
725
|
+
|
|
726
|
+
test('session rejects a response from another routing channel', async () => {
|
|
727
|
+
const response = ProtocolV2.encodeFrame(schemas, 'Success', { message: 'wrong route' });
|
|
728
|
+
const session = new ProtocolV2Session({
|
|
729
|
+
schemas,
|
|
730
|
+
router: 1,
|
|
731
|
+
writeFrame: () => Promise.resolve(),
|
|
732
|
+
readFrame: () => Promise.resolve(response),
|
|
733
|
+
});
|
|
734
|
+
|
|
735
|
+
await expect(session.call('Ping', { message: 'hello' })).rejects.toBeInstanceOf(
|
|
736
|
+
ProtocolV2LinkError
|
|
737
|
+
);
|
|
738
|
+
});
|
|
739
|
+
|
|
439
740
|
test('session rejects BLE frames above its configured frame limit before writing', async () => {
|
|
440
741
|
const writeFrame = jest.fn().mockResolvedValue(undefined);
|
|
441
742
|
const response = ProtocolV2.encodeFrame(schemas, 'Success', {
|
|
@@ -446,7 +747,7 @@ describe('Protocol V2 framing and session', () => {
|
|
|
446
747
|
router: 1,
|
|
447
748
|
maxFrameBytes: 2048,
|
|
448
749
|
writeFrame,
|
|
449
|
-
readFrame: () => Promise.resolve(response),
|
|
750
|
+
readFrame: () => Promise.resolve(rewriteSeq(response, 1)),
|
|
450
751
|
});
|
|
451
752
|
|
|
452
753
|
await expect(session.call('Ping', { message: 'x'.repeat(2048) })).rejects.toThrow(
|
|
@@ -455,28 +756,40 @@ describe('Protocol V2 framing and session', () => {
|
|
|
455
756
|
expect(writeFrame).not.toHaveBeenCalled();
|
|
456
757
|
});
|
|
457
758
|
|
|
458
|
-
test('session
|
|
459
|
-
const
|
|
460
|
-
|
|
461
|
-
});
|
|
759
|
+
test('session watchdog covers a write that never settles', async () => {
|
|
760
|
+
const readFrame = jest.fn();
|
|
761
|
+
let writeSignal;
|
|
462
762
|
const session = new ProtocolV2Session({
|
|
463
763
|
schemas,
|
|
464
764
|
router: 1,
|
|
465
|
-
writeFrame: () =>
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
readFrame
|
|
765
|
+
writeFrame: (_frame, context) => {
|
|
766
|
+
writeSignal = context.signal;
|
|
767
|
+
return new Promise(() => {});
|
|
768
|
+
},
|
|
769
|
+
readFrame,
|
|
470
770
|
});
|
|
471
771
|
|
|
472
772
|
await expect(
|
|
473
773
|
session.call('Ping', { message: 'hello' }, { timeoutMs: 10, expectedTypes: ['Success'] })
|
|
474
|
-
).
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
774
|
+
).rejects.toThrow('Protocol V2 response timeout after 10ms for Ping');
|
|
775
|
+
expect(writeSignal.aborted).toBe(true);
|
|
776
|
+
expect(readFrame).not.toHaveBeenCalled();
|
|
777
|
+
});
|
|
778
|
+
|
|
779
|
+
test('session watchdog covers prepareCall before writing', async () => {
|
|
780
|
+
const writeFrame = jest.fn();
|
|
781
|
+
const session = new ProtocolV2Session({
|
|
782
|
+
schemas,
|
|
783
|
+
router: 1,
|
|
784
|
+
prepareCall: () => new Promise(() => {}),
|
|
785
|
+
writeFrame,
|
|
786
|
+
readFrame: jest.fn(),
|
|
479
787
|
});
|
|
788
|
+
|
|
789
|
+
await expect(session.call('Ping', { message: 'hello' }, { timeoutMs: 10 })).rejects.toThrow(
|
|
790
|
+
'Protocol V2 response timeout after 10ms for Ping'
|
|
791
|
+
);
|
|
792
|
+
expect(writeFrame).not.toHaveBeenCalled();
|
|
480
793
|
});
|
|
481
794
|
|
|
482
795
|
test('session logs a device-owned response seq without rejecting the frame', async () => {
|
|
@@ -499,17 +812,46 @@ describe('Protocol V2 framing and session', () => {
|
|
|
499
812
|
type: 'ProtocolInfo',
|
|
500
813
|
message: {
|
|
501
814
|
version: 2,
|
|
815
|
+
build_fingerprint: null,
|
|
502
816
|
supported_messages: [],
|
|
503
|
-
protobuf_definition: null,
|
|
504
817
|
},
|
|
505
818
|
});
|
|
506
|
-
expect(logger.debug).
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
819
|
+
expect(logger.debug).not.toHaveBeenCalled();
|
|
820
|
+
});
|
|
821
|
+
|
|
822
|
+
test('session accepts gaps in the firmware-global response sequence', async () => {
|
|
823
|
+
const response = ProtocolV2.encodeFrame(schemas, 'Success', { message: 'ok' });
|
|
824
|
+
const readFrame = jest
|
|
825
|
+
.fn()
|
|
826
|
+
.mockResolvedValueOnce(rewriteSeq(response, 1))
|
|
827
|
+
.mockResolvedValueOnce(rewriteSeq(response, 4));
|
|
828
|
+
const session = new ProtocolV2Session({
|
|
829
|
+
schemas,
|
|
830
|
+
router: 1,
|
|
831
|
+
writeFrame: () => Promise.resolve(),
|
|
832
|
+
readFrame,
|
|
512
833
|
});
|
|
834
|
+
|
|
835
|
+
await session.call('Ping', { message: 'first' });
|
|
836
|
+
await expect(session.call('Ping', { message: 'second' })).resolves.toMatchObject({
|
|
837
|
+
type: 'Success',
|
|
838
|
+
});
|
|
839
|
+
});
|
|
840
|
+
|
|
841
|
+
test('session rejects a duplicate device response sequence', async () => {
|
|
842
|
+
const response = ProtocolV2.encodeFrame(schemas, 'Success', { message: 'ok' });
|
|
843
|
+
const readFrame = jest.fn(() => Promise.resolve(rewriteSeq(response, 7)));
|
|
844
|
+
const session = new ProtocolV2Session({
|
|
845
|
+
schemas,
|
|
846
|
+
router: 1,
|
|
847
|
+
writeFrame: () => Promise.resolve(),
|
|
848
|
+
readFrame,
|
|
849
|
+
});
|
|
850
|
+
|
|
851
|
+
await session.call('Ping', { message: 'first' });
|
|
852
|
+
await expect(session.call('Ping', { message: 'second' })).rejects.toThrow(
|
|
853
|
+
'Protocol V2 duplicate response sequence: 7'
|
|
854
|
+
);
|
|
513
855
|
});
|
|
514
856
|
|
|
515
857
|
test('session does not log transmit or receive payload details', async () => {
|
|
@@ -523,7 +865,7 @@ describe('Protocol V2 framing and session', () => {
|
|
|
523
865
|
schemas,
|
|
524
866
|
router: 1,
|
|
525
867
|
writeFrame: () => Promise.resolve(),
|
|
526
|
-
readFrame: () => Promise.resolve(response),
|
|
868
|
+
readFrame: () => Promise.resolve(rewriteSeq(response, 1)),
|
|
527
869
|
logger,
|
|
528
870
|
logPrefix: 'ProtocolV2 Test',
|
|
529
871
|
});
|
|
@@ -535,10 +877,7 @@ describe('Protocol V2 framing and session', () => {
|
|
|
535
877
|
},
|
|
536
878
|
});
|
|
537
879
|
|
|
538
|
-
expect(logger.debug).
|
|
539
|
-
const logs = JSON.stringify(logger.debug.mock.calls);
|
|
540
|
-
expect(logs).not.toContain('hello');
|
|
541
|
-
expect(logs).not.toContain('accepted');
|
|
880
|
+
expect(logger.debug).not.toHaveBeenCalled();
|
|
542
881
|
});
|
|
543
882
|
|
|
544
883
|
test('session suppresses debug logs for file transfer calls', async () => {
|
|
@@ -552,7 +891,7 @@ describe('Protocol V2 framing and session', () => {
|
|
|
552
891
|
schemas,
|
|
553
892
|
router: 1,
|
|
554
893
|
writeFrame: () => Promise.resolve(),
|
|
555
|
-
readFrame: () => Promise.resolve(response),
|
|
894
|
+
readFrame: () => Promise.resolve(rewriteSeq(response, 1)),
|
|
556
895
|
logger,
|
|
557
896
|
});
|
|
558
897
|
|
|
@@ -566,6 +905,34 @@ describe('Protocol V2 framing and session', () => {
|
|
|
566
905
|
expect(logger.debug).not.toHaveBeenCalled();
|
|
567
906
|
});
|
|
568
907
|
|
|
908
|
+
test('session reports when the complete request frame has been written', async () => {
|
|
909
|
+
const response = ProtocolV2.encodeFrame(schemas, 'Success', {
|
|
910
|
+
message: 'ok',
|
|
911
|
+
});
|
|
912
|
+
const events = [];
|
|
913
|
+
const onWriteCompleted = jest.fn(metrics => events.push(['written', metrics]));
|
|
914
|
+
const session = new ProtocolV2Session({
|
|
915
|
+
schemas,
|
|
916
|
+
router: 1,
|
|
917
|
+
writeFrame: frame => {
|
|
918
|
+
events.push(['write', frame.byteLength]);
|
|
919
|
+
return Promise.resolve();
|
|
920
|
+
},
|
|
921
|
+
readFrame: () => {
|
|
922
|
+
events.push(['read']);
|
|
923
|
+
return Promise.resolve(rewriteSeq(response, 1));
|
|
924
|
+
},
|
|
925
|
+
});
|
|
926
|
+
|
|
927
|
+
await session.call('Ping', { message: 'metrics' }, { onWriteCompleted });
|
|
928
|
+
|
|
929
|
+
expect(events.map(event => event[0])).toEqual(['write', 'written', 'read']);
|
|
930
|
+
expect(onWriteCompleted).toHaveBeenCalledWith({
|
|
931
|
+
elapsedMs: expect.any(Number),
|
|
932
|
+
frameBytes: expect.any(Number),
|
|
933
|
+
});
|
|
934
|
+
});
|
|
935
|
+
|
|
569
936
|
test('session skips unrelated terminal frames when expected response types are provided', async () => {
|
|
570
937
|
const stale = ProtocolV2.encodeFrame(schemas, 'Success', {
|
|
571
938
|
message: 'stale response',
|
|
@@ -577,7 +944,10 @@ describe('Protocol V2 framing and session', () => {
|
|
|
577
944
|
const logger = {
|
|
578
945
|
debug: jest.fn(),
|
|
579
946
|
};
|
|
580
|
-
const readFrame = jest
|
|
947
|
+
const readFrame = jest
|
|
948
|
+
.fn()
|
|
949
|
+
.mockResolvedValueOnce(rewriteSeq(stale, 1))
|
|
950
|
+
.mockResolvedValueOnce(rewriteSeq(response, 2));
|
|
581
951
|
const session = new ProtocolV2Session({
|
|
582
952
|
schemas,
|
|
583
953
|
router: 1,
|
|
@@ -592,8 +962,8 @@ describe('Protocol V2 framing and session', () => {
|
|
|
592
962
|
type: 'ProtocolInfo',
|
|
593
963
|
message: {
|
|
594
964
|
version: 2,
|
|
965
|
+
build_fingerprint: null,
|
|
595
966
|
supported_messages: [],
|
|
596
|
-
protobuf_definition: null,
|
|
597
967
|
},
|
|
598
968
|
});
|
|
599
969
|
|
|
@@ -601,22 +971,10 @@ describe('Protocol V2 framing and session', () => {
|
|
|
601
971
|
expect(logger.debug).toHaveBeenCalledWith(expect.stringContaining('skip unexpected response'));
|
|
602
972
|
});
|
|
603
973
|
|
|
604
|
-
test('session
|
|
974
|
+
test('session can return after a request frame is written', async () => {
|
|
605
975
|
const written = [];
|
|
606
|
-
const
|
|
607
|
-
|
|
608
|
-
});
|
|
609
|
-
const success = ProtocolV2.encodeFrame(schemas, 'Success', {
|
|
610
|
-
message: 'ok',
|
|
611
|
-
});
|
|
612
|
-
const onIntermediateResponse = jest.fn();
|
|
613
|
-
const readFrame = jest.fn(() => {
|
|
614
|
-
const [writtenFrame] = written;
|
|
615
|
-
const { seq } = protocolV2.decodeFrame(writtenFrame);
|
|
616
|
-
return Promise.resolve(
|
|
617
|
-
readFrame.mock.calls.length === 1 ? rewriteSeq(progress, seq) : rewriteSeq(success, seq)
|
|
618
|
-
);
|
|
619
|
-
});
|
|
976
|
+
const readFrame = jest.fn();
|
|
977
|
+
const onWriteCompleted = jest.fn();
|
|
620
978
|
const session = new ProtocolV2Session({
|
|
621
979
|
schemas,
|
|
622
980
|
router: 1,
|
|
@@ -629,26 +987,20 @@ describe('Protocol V2 framing and session', () => {
|
|
|
629
987
|
|
|
630
988
|
const result = await session.call(
|
|
631
989
|
'DeviceFirmwareUpdateRequest',
|
|
632
|
-
{
|
|
990
|
+
{},
|
|
633
991
|
{
|
|
634
|
-
|
|
635
|
-
|
|
992
|
+
returnAfterWrite: true,
|
|
993
|
+
onWriteCompleted,
|
|
636
994
|
}
|
|
637
995
|
);
|
|
638
996
|
|
|
639
|
-
expect(
|
|
640
|
-
expect(
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
});
|
|
646
|
-
expect(result).toEqual({
|
|
647
|
-
type: 'Success',
|
|
648
|
-
message: {
|
|
649
|
-
message: 'ok',
|
|
650
|
-
},
|
|
651
|
-
});
|
|
997
|
+
expect(written).toHaveLength(1);
|
|
998
|
+
expect(ProtocolV2.decodeFrame(schemas, written[0]).messageName).toBe(
|
|
999
|
+
'DeviceFirmwareUpdateRequest'
|
|
1000
|
+
);
|
|
1001
|
+
expect(readFrame).not.toHaveBeenCalled();
|
|
1002
|
+
expect(onWriteCompleted).toHaveBeenCalledTimes(1);
|
|
1003
|
+
expect(result).toEqual({ type: 'WriteCompleted', message: {} });
|
|
652
1004
|
});
|
|
653
1005
|
|
|
654
1006
|
test('probeProtocolV2 accepts Success as a normal V2 probe response', async () => {
|
|
@@ -667,6 +1019,36 @@ describe('Protocol V2 framing and session', () => {
|
|
|
667
1019
|
).resolves.toBe(false);
|
|
668
1020
|
});
|
|
669
1021
|
|
|
1022
|
+
test('recognizes USB-priority firmware failures independently of surrounding whitespace', () => {
|
|
1023
|
+
expect(isProtocolV2LinkDisabledFailure('Failure_ProcessError', ' link disabled ')).toBe(true);
|
|
1024
|
+
expect(isProtocolV2LinkDisabledFailure(5, 'Link Disabled')).toBe(true);
|
|
1025
|
+
expect(isProtocolV2LinkDisabledFailure('Failure_ProcessError', 'busy')).toBe(false);
|
|
1026
|
+
});
|
|
1027
|
+
|
|
1028
|
+
test.each(['Failure_ProcessError', 5])(
|
|
1029
|
+
'probeProtocolV2 surfaces link disabled without resetting the link for code %s',
|
|
1030
|
+
async code => {
|
|
1031
|
+
const onProbeFailed = jest.fn();
|
|
1032
|
+
|
|
1033
|
+
await expect(
|
|
1034
|
+
probeProtocolV2({
|
|
1035
|
+
call: () =>
|
|
1036
|
+
Promise.resolve({
|
|
1037
|
+
type: 'Failure',
|
|
1038
|
+
message: { code, message: ' link disabled ' },
|
|
1039
|
+
}),
|
|
1040
|
+
timeoutMs: 1,
|
|
1041
|
+
onProbeFailed,
|
|
1042
|
+
})
|
|
1043
|
+
).rejects.toMatchObject({
|
|
1044
|
+
name: 'ProtocolV2LinkDisabledError',
|
|
1045
|
+
failureCode: code,
|
|
1046
|
+
firmwareMessage: ' link disabled ',
|
|
1047
|
+
});
|
|
1048
|
+
expect(onProbeFailed).not.toHaveBeenCalled();
|
|
1049
|
+
}
|
|
1050
|
+
);
|
|
1051
|
+
|
|
670
1052
|
test('decodeFrame rejects frames that are too short', () => {
|
|
671
1053
|
expect(() => protocolV2.decodeFrame(new Uint8Array([0x5a, 0x08, 0x00]))).toThrow(
|
|
672
1054
|
'Protocol V2 frame too short'
|
|
@@ -791,7 +1173,7 @@ describe('Protocol V2 framing and session', () => {
|
|
|
791
1173
|
}
|
|
792
1174
|
return Promise.resolve();
|
|
793
1175
|
},
|
|
794
|
-
readFrame: () => Promise.resolve(response),
|
|
1176
|
+
readFrame: () => Promise.resolve(rewriteSeq(response, 1)),
|
|
795
1177
|
});
|
|
796
1178
|
|
|
797
1179
|
await expect(session.call('Ping', { message: '1' })).rejects.toThrow('transport write failed');
|
|
@@ -860,6 +1242,7 @@ describe('Protocol V2 framing and session', () => {
|
|
|
860
1242
|
const writeContexts = [];
|
|
861
1243
|
const readContexts = [];
|
|
862
1244
|
const response = ProtocolV2.encodeFrame(schemas, 'Success', { message: 'ok' });
|
|
1245
|
+
let responseSeq = 0;
|
|
863
1246
|
const session = new ProtocolV2Session({
|
|
864
1247
|
schemas,
|
|
865
1248
|
router: 1,
|
|
@@ -870,18 +1253,67 @@ describe('Protocol V2 framing and session', () => {
|
|
|
870
1253
|
},
|
|
871
1254
|
readFrame: context => {
|
|
872
1255
|
readContexts.push(context);
|
|
873
|
-
|
|
1256
|
+
responseSeq = protocolV2.nextProtoSeq(responseSeq);
|
|
1257
|
+
return Promise.resolve(rewriteSeq(response, responseSeq));
|
|
874
1258
|
},
|
|
875
1259
|
});
|
|
876
1260
|
|
|
877
1261
|
await session.call('Ping', { message: 'ping' }, { timeoutMs: 123 });
|
|
878
|
-
await session.call('FileWrite', {}, { timeoutMs: 456 });
|
|
1262
|
+
await session.call('FileWrite', {}, { timeoutMs: 456, writeWithResponse: true });
|
|
1263
|
+
await session.call('Ping', { message: 'default-timeout' });
|
|
879
1264
|
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
1265
|
+
const normalizeContext = ({ signal, ...context }) => ({
|
|
1266
|
+
...context,
|
|
1267
|
+
signalAborted: signal.aborted,
|
|
1268
|
+
});
|
|
1269
|
+
expect(writeContexts.map(normalizeContext)).toEqual([
|
|
1270
|
+
{
|
|
1271
|
+
messageName: 'Ping',
|
|
1272
|
+
timeoutMs: 123,
|
|
1273
|
+
highThroughput: false,
|
|
1274
|
+
generation: 7,
|
|
1275
|
+
signalAborted: false,
|
|
1276
|
+
},
|
|
1277
|
+
{
|
|
1278
|
+
messageName: 'FileWrite',
|
|
1279
|
+
timeoutMs: 456,
|
|
1280
|
+
highThroughput: true,
|
|
1281
|
+
writeWithResponse: true,
|
|
1282
|
+
generation: 7,
|
|
1283
|
+
signalAborted: false,
|
|
1284
|
+
},
|
|
1285
|
+
{
|
|
1286
|
+
messageName: 'Ping',
|
|
1287
|
+
timeoutMs: PROTOCOL_V2_DEFAULT_RESPONSE_TIMEOUT_MS,
|
|
1288
|
+
highThroughput: false,
|
|
1289
|
+
generation: 7,
|
|
1290
|
+
signalAborted: false,
|
|
1291
|
+
},
|
|
1292
|
+
]);
|
|
1293
|
+
expect(readContexts.map(normalizeContext)).toEqual([
|
|
1294
|
+
{
|
|
1295
|
+
messageName: 'Ping',
|
|
1296
|
+
timeoutMs: 123,
|
|
1297
|
+
highThroughput: false,
|
|
1298
|
+
generation: 7,
|
|
1299
|
+
signalAborted: false,
|
|
1300
|
+
},
|
|
1301
|
+
{
|
|
1302
|
+
messageName: 'FileWrite',
|
|
1303
|
+
timeoutMs: 456,
|
|
1304
|
+
highThroughput: true,
|
|
1305
|
+
writeWithResponse: true,
|
|
1306
|
+
generation: 7,
|
|
1307
|
+
signalAborted: false,
|
|
1308
|
+
},
|
|
1309
|
+
{
|
|
1310
|
+
messageName: 'Ping',
|
|
1311
|
+
timeoutMs: PROTOCOL_V2_DEFAULT_RESPONSE_TIMEOUT_MS,
|
|
1312
|
+
highThroughput: false,
|
|
1313
|
+
generation: 7,
|
|
1314
|
+
signalAborted: false,
|
|
1315
|
+
},
|
|
883
1316
|
]);
|
|
884
|
-
expect(readContexts).toEqual(writeContexts);
|
|
885
1317
|
});
|
|
886
1318
|
|
|
887
1319
|
test('assembler throws and resets on frames with an impossible length field', () => {
|