@onekeyfe/hd-transport 1.2.0-alpha.2 → 1.2.0-alpha.21

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.
Files changed (58) hide show
  1. package/README.md +1 -1
  2. package/__tests__/messages.test.js +122 -0
  3. package/__tests__/protocol-v2-link-manager.test.js +326 -0
  4. package/__tests__/protocol-v2-usb-transport-base.test.js +296 -0
  5. package/__tests__/protocol-v2.test.js +371 -107
  6. package/dist/constants.d.ts +5 -3
  7. package/dist/constants.d.ts.map +1 -1
  8. package/dist/index.d.ts +912 -371
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.js +551 -254
  11. package/dist/protocols/index.d.ts +10 -5
  12. package/dist/protocols/index.d.ts.map +1 -1
  13. package/dist/protocols/v2/constants.d.ts +1 -0
  14. package/dist/protocols/v2/constants.d.ts.map +1 -1
  15. package/dist/protocols/v2/decode.d.ts +2 -2
  16. package/dist/protocols/v2/decode.d.ts.map +1 -1
  17. package/dist/protocols/v2/encode.d.ts +2 -3
  18. package/dist/protocols/v2/encode.d.ts.map +1 -1
  19. package/dist/protocols/v2/index.d.ts +0 -1
  20. package/dist/protocols/v2/index.d.ts.map +1 -1
  21. package/dist/protocols/v2/link-manager.d.ts +35 -0
  22. package/dist/protocols/v2/link-manager.d.ts.map +1 -0
  23. package/dist/protocols/v2/sequence-cursor.d.ts +5 -0
  24. package/dist/protocols/v2/sequence-cursor.d.ts.map +1 -0
  25. package/dist/protocols/v2/session.d.ts +15 -4
  26. package/dist/protocols/v2/session.d.ts.map +1 -1
  27. package/dist/protocols/v2/usb-transport-base.d.ts +31 -0
  28. package/dist/protocols/v2/usb-transport-base.d.ts.map +1 -0
  29. package/dist/serialization/protobuf/decode.d.ts.map +1 -1
  30. package/dist/serialization/protobuf/messages.d.ts.map +1 -1
  31. package/dist/types/messages.d.ts +535 -226
  32. package/dist/types/messages.d.ts.map +1 -1
  33. package/dist/types/transport.d.ts +5 -3
  34. package/dist/types/transport.d.ts.map +1 -1
  35. package/messages-protocol-v2.json +825 -709
  36. package/package.json +3 -3
  37. package/scripts/protobuf-build.sh +96 -310
  38. package/scripts/protobuf-patches/index.js +1 -0
  39. package/scripts/protobuf-types.js +12 -0
  40. package/src/constants.ts +21 -15
  41. package/src/index.ts +8 -0
  42. package/src/protocols/index.ts +31 -40
  43. package/src/protocols/v2/constants.ts +1 -0
  44. package/src/protocols/v2/crc8.ts +1 -1
  45. package/src/protocols/v2/decode.ts +31 -34
  46. package/src/protocols/v2/encode.ts +2 -28
  47. package/src/protocols/v2/index.ts +0 -1
  48. package/src/protocols/v2/link-manager.ts +160 -0
  49. package/src/protocols/v2/sequence-cursor.ts +10 -0
  50. package/src/protocols/v2/session.ts +79 -180
  51. package/src/protocols/v2/usb-transport-base.ts +194 -0
  52. package/src/serialization/protobuf/decode.ts +4 -1
  53. package/src/serialization/protobuf/messages.ts +6 -1
  54. package/src/types/messages.ts +637 -266
  55. package/src/types/transport.ts +3 -3
  56. package/dist/protocols/v2/debug.d.ts +0 -13
  57. package/dist/protocols/v2/debug.d.ts.map +0 -1
  58. package/src/protocols/v2/debug.ts +0 -26
@@ -1,9 +1,14 @@
1
1
  const { ProtocolV2 } = require('../src/protocols');
2
2
  const { parseConfigure } = require('../src/serialization/protobuf/messages');
3
- const sessionModule = require('../src/protocols/v2/session');
4
-
5
- const { ProtocolV2FrameAssembler, ProtocolV2Session, probeProtocolV2 } = sessionModule;
3
+ const {
4
+ ProtocolV2FrameAssembler,
5
+ ProtocolV2SequenceCursor,
6
+ ProtocolV2Session,
7
+ hexToBytes,
8
+ probeProtocolV2,
9
+ } = require('../src/protocols/v2/session');
6
10
  const protocolV2 = require('../src/protocols/v2');
11
+ const { PROTOCOL_V2_FRAME_MAX_BYTES } = require('../src/constants');
7
12
 
8
13
  const protocolV1Messages = parseConfigure({
9
14
  nested: {
@@ -15,6 +20,18 @@ const protocolV1Messages = parseConfigure({
15
20
  },
16
21
  },
17
22
  },
23
+ Failure: {
24
+ fields: {
25
+ code: {
26
+ type: 'uint32',
27
+ id: 1,
28
+ },
29
+ message: {
30
+ type: 'string',
31
+ id: 2,
32
+ },
33
+ },
34
+ },
18
35
  ButtonRequest: {
19
36
  fields: {
20
37
  code: {
@@ -32,6 +49,7 @@ const protocolV1Messages = parseConfigure({
32
49
  MessageType: {
33
50
  values: {
34
51
  MessageType_Success: 2,
52
+ MessageType_Failure: 3,
35
53
  MessageType_ButtonRequest: 26,
36
54
  MessageType_OnekeyGetFeatures: 10025,
37
55
  MessageType_OnekeyFeatures: 10026,
@@ -42,21 +60,22 @@ const protocolV1Messages = parseConfigure({
42
60
 
43
61
  const protocolV2Messages = parseConfigure({
44
62
  nested: {
45
- GetProtoVersion: {
63
+ ProtocolInfoRequest: {
46
64
  fields: {},
47
65
  },
48
- ProtoVersion: {
66
+ ProtocolInfo: {
49
67
  fields: {
50
- major_version: {
68
+ version: {
51
69
  type: 'uint32',
52
70
  id: 1,
53
71
  },
54
- minor_version: {
72
+ supported_messages: {
55
73
  type: 'uint32',
56
74
  id: 2,
75
+ rule: 'repeated',
57
76
  },
58
- patch_version: {
59
- type: 'uint32',
77
+ protobuf_definition: {
78
+ type: 'string',
60
79
  id: 3,
61
80
  },
62
81
  },
@@ -77,17 +96,80 @@ const protocolV2Messages = parseConfigure({
77
96
  },
78
97
  },
79
98
  },
80
- DevFirmwareUpdate: {
81
- fields: {},
99
+ Failure: {
100
+ fields: {
101
+ code: {
102
+ type: 'uint32',
103
+ id: 1,
104
+ },
105
+ subcode: {
106
+ type: 'uint32',
107
+ id: 2,
108
+ },
109
+ message: {
110
+ type: 'string',
111
+ id: 3,
112
+ },
113
+ },
114
+ },
115
+ DeviceFirmwareTarget: {
116
+ fields: {
117
+ target_id: {
118
+ type: 'uint32',
119
+ id: 1,
120
+ },
121
+ path: {
122
+ type: 'string',
123
+ id: 2,
124
+ },
125
+ },
126
+ },
127
+ DeviceFirmwareUpdateRequest: {
128
+ fields: {
129
+ targets: {
130
+ type: 'DeviceFirmwareTarget',
131
+ id: 1,
132
+ rule: 'repeated',
133
+ },
134
+ },
82
135
  },
83
- DevFirmwareInstallProgress: {
136
+ DeviceFirmwareUpdateRecord: {
84
137
  fields: {
85
138
  target_id: {
86
139
  type: 'uint32',
87
140
  id: 1,
88
141
  },
89
- progress: {
142
+ status: {
90
143
  type: 'uint32',
144
+ id: 10,
145
+ },
146
+ payload_version: {
147
+ type: 'uint32',
148
+ id: 20,
149
+ },
150
+ path: {
151
+ type: 'string',
152
+ id: 30,
153
+ },
154
+ },
155
+ },
156
+ DeviceFirmwareUpdateStatus: {
157
+ fields: {
158
+ records: {
159
+ type: 'DeviceFirmwareUpdateRecord',
160
+ id: 1,
161
+ rule: 'repeated',
162
+ },
163
+ },
164
+ },
165
+ DeviceSession: {
166
+ fields: {
167
+ session_id: {
168
+ type: 'bytes',
169
+ id: 1,
170
+ },
171
+ btc_test_address: {
172
+ type: 'string',
91
173
  id: 2,
92
174
  },
93
175
  },
@@ -117,13 +199,15 @@ const protocolV2Messages = parseConfigure({
117
199
  },
118
200
  MessageType: {
119
201
  values: {
120
- MessageType_GetProtoVersion: 60200,
121
- MessageType_ProtoVersion: 60201,
202
+ MessageType_ProtocolInfoRequest: 60200,
203
+ MessageType_ProtocolInfo: 60201,
122
204
  MessageType_Ping: 60206,
123
205
  MessageType_Success: 60207,
206
+ MessageType_Failure: 60208,
124
207
  MessageType_FileWrite: 60805,
125
- MessageType_DevFirmwareUpdate: 61000,
126
- MessageType_DevFirmwareInstallProgress: 61001,
208
+ MessageType_DeviceFirmwareUpdateRequest: 61000,
209
+ MessageType_DeviceFirmwareUpdateStatus: 61002,
210
+ MessageType_DeviceSession: 60607,
127
211
  MessageType_PartialNested: 62000,
128
212
  },
129
213
  },
@@ -144,10 +228,10 @@ const rewriteSeq = (frame, seq) => {
144
228
 
145
229
  describe('Protocol V2 framing and session', () => {
146
230
  test('encodes and decodes Protocol V2 protobuf frames', () => {
147
- const frame = ProtocolV2.encodeFrame(schemas, 'ProtoVersion', {
148
- major_version: 1,
149
- minor_version: 2,
150
- patch_version: 3,
231
+ const frame = ProtocolV2.encodeFrame(schemas, 'ProtocolInfo', {
232
+ version: 1,
233
+ supported_messages: [60200, 60201],
234
+ protobuf_definition: 'proto',
151
235
  });
152
236
 
153
237
  const parsed = protocolV2.decodeFrame(frame);
@@ -155,15 +239,15 @@ describe('Protocol V2 framing and session', () => {
155
239
 
156
240
  const decoded = ProtocolV2.decodeFrame(schemas, frame);
157
241
  expect(decoded).toEqual({
158
- type: 'ProtoVersion',
159
- messageName: 'ProtoVersion',
242
+ type: 'ProtocolInfo',
243
+ messageName: 'ProtocolInfo',
160
244
  messageTypeId: 60201,
161
245
  pbPayload: parsed.pbPayload,
162
246
  seq: parsed.seq,
163
247
  message: {
164
- major_version: 1,
165
- minor_version: 2,
166
- patch_version: 3,
248
+ version: 1,
249
+ supported_messages: [60200, 60201],
250
+ protobuf_definition: 'proto',
167
251
  },
168
252
  });
169
253
  });
@@ -188,6 +272,34 @@ describe('Protocol V2 framing and session', () => {
188
272
  expect(decoded.message).toEqual({ message: 'ok' });
189
273
  });
190
274
 
275
+ test('round-trips a DeviceSession response with a 32-byte session id', () => {
276
+ const sessionId = '01'.repeat(32);
277
+ const frame = ProtocolV2.encodeFrame(schemas, 'DeviceSession', {
278
+ session_id: sessionId,
279
+ btc_test_address: 'tb1qwallet',
280
+ });
281
+
282
+ const decoded = ProtocolV2.decodeFrame(schemas, frame);
283
+
284
+ expect(decoded.type).toBe('DeviceSession');
285
+ expect(decoded.message).toEqual({
286
+ session_id: sessionId,
287
+ btc_test_address: 'tb1qwallet',
288
+ });
289
+ });
290
+
291
+ test('reports malformed DeviceSession protobuf as a schema-compatible transport error', () => {
292
+ const malformedPayload = new Uint8Array(32);
293
+ malformedPayload[0] = 0x0a;
294
+ malformedPayload[1] = 101;
295
+ const frame = protocolV2.encodeProtobufFrame(60607, malformedPayload);
296
+
297
+ expect(() => ProtocolV2.decodeFrame(schemas, frame)).toThrow(
298
+ 'Protocol V2 protobuf decode failed for "DeviceSession" (60607, 32-byte payload); ' +
299
+ 'the payload is malformed or incompatible with the active SDK schema.'
300
+ );
301
+ });
302
+
191
303
  test('decodes missing optional nested messages as null', () => {
192
304
  const frame = ProtocolV2.encodeFrame(schemas, 'PartialNested', {
193
305
  label: 'only label',
@@ -201,10 +313,9 @@ describe('Protocol V2 framing and session', () => {
201
313
  });
202
314
 
203
315
  test('reassembles split Protocol V2 frames and rejects oversized frames', () => {
204
- const frame = ProtocolV2.encodeFrame(schemas, 'ProtoVersion', {
205
- major_version: 1,
206
- minor_version: 0,
207
- patch_version: 0,
316
+ const frame = ProtocolV2.encodeFrame(schemas, 'ProtocolInfo', {
317
+ version: 1,
318
+ supported_messages: [],
208
319
  });
209
320
  const assembler = new ProtocolV2FrameAssembler();
210
321
 
@@ -215,16 +326,28 @@ describe('Protocol V2 framing and session', () => {
215
326
  expect(() => assembler.push(oversized)).toThrow('Protocol V2 frame too large');
216
327
  });
217
328
 
329
+ test('enforces the firmware 4200-byte Protocol V2 frame boundary', () => {
330
+ const boundaryFrame = ProtocolV2.encodeFrame(schemas, 'Ping', {
331
+ message: 'x'.repeat(4187),
332
+ });
333
+
334
+ expect(PROTOCOL_V2_FRAME_MAX_BYTES).toBe(4200);
335
+ expect(boundaryFrame).toHaveLength(PROTOCOL_V2_FRAME_MAX_BYTES);
336
+ expect(() =>
337
+ ProtocolV2.encodeFrame(schemas, 'Ping', {
338
+ message: 'x'.repeat(4188),
339
+ })
340
+ ).toThrow('Protocol V2 frame too large: 4201 > 4200');
341
+ });
342
+
218
343
  test('keeps bytes after the first complete frame for the next read', () => {
219
- const first = ProtocolV2.encodeFrame(schemas, 'ProtoVersion', {
220
- major_version: 1,
221
- minor_version: 0,
222
- patch_version: 0,
344
+ const first = ProtocolV2.encodeFrame(schemas, 'ProtocolInfo', {
345
+ version: 1,
346
+ supported_messages: [],
223
347
  });
224
- const second = ProtocolV2.encodeFrame(schemas, 'ProtoVersion', {
225
- major_version: 2,
226
- minor_version: 0,
227
- patch_version: 0,
348
+ const second = ProtocolV2.encodeFrame(schemas, 'ProtocolInfo', {
349
+ version: 2,
350
+ supported_messages: [],
228
351
  });
229
352
  const assembler = new ProtocolV2FrameAssembler();
230
353
  const combined = new Uint8Array(first.length + second.length);
@@ -237,10 +360,9 @@ describe('Protocol V2 framing and session', () => {
237
360
 
238
361
  test('session writes one encoded frame and decodes the response frame', async () => {
239
362
  const written = [];
240
- const response = ProtocolV2.encodeFrame(schemas, 'ProtoVersion', {
241
- major_version: 2,
242
- minor_version: 0,
243
- patch_version: 1,
363
+ const response = ProtocolV2.encodeFrame(schemas, 'ProtocolInfo', {
364
+ version: 2,
365
+ supported_messages: [60206],
244
366
  });
245
367
  const session = new ProtocolV2Session({
246
368
  schemas,
@@ -253,20 +375,116 @@ describe('Protocol V2 framing and session', () => {
253
375
  Promise.resolve(rewriteSeq(response, protocolV2.decodeFrame(written[0]).seq)),
254
376
  });
255
377
 
256
- const result = await session.call('GetProtoVersion', {});
378
+ const result = await session.call('ProtocolInfoRequest', {});
257
379
 
258
380
  expect(written).toHaveLength(1);
259
381
  expect(written[0][4]).toBe(1);
260
382
  expect(written[0][5]).toBe(0);
261
383
  expect(protocolV2.decodeFrame(written[0]).messageTypeId).toBe(60200);
262
384
  expect(result).toEqual({
263
- type: 'ProtoVersion',
385
+ type: 'ProtocolInfo',
264
386
  message: {
265
- major_version: 2,
266
- minor_version: 0,
267
- patch_version: 1,
387
+ version: 2,
388
+ supported_messages: [60206],
389
+ protobuf_definition: null,
390
+ },
391
+ });
392
+ });
393
+
394
+ test('session does not log Protocol V2 TX and RX frames', async () => {
395
+ const written = [];
396
+ const logger = { debug: jest.fn() };
397
+ const response = ProtocolV2.encodeFrame(schemas, 'ProtocolInfo', {
398
+ version: 2,
399
+ supported_messages: [60206],
400
+ protobuf_definition: 'sensitive-schema',
401
+ });
402
+ const session = new ProtocolV2Session({
403
+ schemas,
404
+ router: 1,
405
+ logger,
406
+ logPrefix: 'ProtocolV2 Test',
407
+ writeFrame: frame => {
408
+ written.push(frame);
409
+ return Promise.resolve();
268
410
  },
411
+ readFrame: () =>
412
+ Promise.resolve(rewriteSeq(response, protocolV2.decodeFrame(written[0]).seq)),
269
413
  });
414
+
415
+ await session.call('ProtocolInfoRequest', {});
416
+
417
+ expect(logger.debug).not.toHaveBeenCalled();
418
+ });
419
+
420
+ test('session does not log RX frame metadata when protobuf payload decoding fails', async () => {
421
+ const logger = { debug: jest.fn() };
422
+ const response = protocolV2.encodeProtobufFrame(
423
+ 60208,
424
+ hexToBytes('0801121648616e646c6572206e6f742072656769737465726564')
425
+ );
426
+ const session = new ProtocolV2Session({
427
+ schemas,
428
+ router: 1,
429
+ logger,
430
+ logPrefix: 'ProtocolV2 Test',
431
+ writeFrame: () => Promise.resolve(),
432
+ readFrame: () => Promise.resolve(response),
433
+ });
434
+
435
+ await expect(session.call('Ping', { message: 'hello' })).rejects.toThrow();
436
+
437
+ expect(logger.debug).not.toHaveBeenCalled();
438
+ });
439
+
440
+ test('session skips Proto Link ACK frames before decoding the protobuf response', async () => {
441
+ const ack = new Uint8Array(8);
442
+ ack[0] = 0x5a;
443
+ ack[1] = 8;
444
+ ack[2] = 0;
445
+ ack[4] = 1;
446
+ ack[5] = 1;
447
+ ack[6] = 1;
448
+ ack[3] = protocolV2.crc8(ack, 3);
449
+ ack[7] = protocolV2.crc8(ack, 7);
450
+
451
+ const response = ProtocolV2.encodeFrame(schemas, 'Success', {
452
+ message: 'ok',
453
+ });
454
+ const readFrame = jest.fn().mockResolvedValueOnce(ack).mockResolvedValueOnce(response);
455
+ const session = new ProtocolV2Session({
456
+ schemas,
457
+ router: 1,
458
+ writeFrame: () => Promise.resolve(),
459
+ readFrame,
460
+ });
461
+
462
+ await expect(session.call('Ping', { message: 'hello' })).resolves.toEqual({
463
+ type: 'Success',
464
+ message: {
465
+ message: 'ok',
466
+ },
467
+ });
468
+ expect(readFrame).toHaveBeenCalledTimes(2);
469
+ });
470
+
471
+ test('session rejects BLE frames above its configured frame limit before writing', async () => {
472
+ const writeFrame = jest.fn().mockResolvedValue(undefined);
473
+ const response = ProtocolV2.encodeFrame(schemas, 'Success', {
474
+ message: 'ok',
475
+ });
476
+ const session = new ProtocolV2Session({
477
+ schemas,
478
+ router: 1,
479
+ maxFrameBytes: 2048,
480
+ writeFrame,
481
+ readFrame: () => Promise.resolve(response),
482
+ });
483
+
484
+ await expect(session.call('Ping', { message: 'x'.repeat(2048) })).rejects.toThrow(
485
+ 'Protocol V2 frame too large for transport: 2061 > 2048'
486
+ );
487
+ expect(writeFrame).not.toHaveBeenCalled();
270
488
  });
271
489
 
272
490
  test('session starts response timeout after the frame is written', async () => {
@@ -293,11 +511,10 @@ describe('Protocol V2 framing and session', () => {
293
511
  });
294
512
  });
295
513
 
296
- test('session accepts response frames with a device-owned seq', async () => {
297
- const response = ProtocolV2.encodeFrame(schemas, 'ProtoVersion', {
298
- major_version: 2,
299
- minor_version: 0,
300
- patch_version: 1,
514
+ test('session logs a device-owned response seq without rejecting the frame', async () => {
515
+ const response = ProtocolV2.encodeFrame(schemas, 'ProtocolInfo', {
516
+ version: 2,
517
+ supported_messages: [],
301
518
  });
302
519
  const logger = {
303
520
  debug: jest.fn(),
@@ -310,18 +527,18 @@ describe('Protocol V2 framing and session', () => {
310
527
  logger,
311
528
  });
312
529
 
313
- await expect(session.call('GetProtoVersion', {})).resolves.toEqual({
314
- type: 'ProtoVersion',
530
+ await expect(session.call('ProtocolInfoRequest', {})).resolves.toEqual({
531
+ type: 'ProtocolInfo',
315
532
  message: {
316
- major_version: 2,
317
- minor_version: 0,
318
- patch_version: 1,
533
+ version: 2,
534
+ supported_messages: [],
535
+ protobuf_definition: null,
319
536
  },
320
537
  });
321
- expect(logger.debug).toHaveBeenCalledWith(expect.stringContaining('seq differs'));
538
+ expect(logger.debug).not.toHaveBeenCalled();
322
539
  });
323
540
 
324
- test('session logs decoded transmit and receive payloads', async () => {
541
+ test('session does not log transmit or receive payload details', async () => {
325
542
  const response = ProtocolV2.encodeFrame(schemas, 'Success', {
326
543
  message: 'accepted',
327
544
  });
@@ -344,30 +561,7 @@ describe('Protocol V2 framing and session', () => {
344
561
  },
345
562
  });
346
563
 
347
- expect(logger.debug).toHaveBeenCalledWith('[ProtocolV2 Test] TX payload name=Ping', {
348
- message: 'hello',
349
- });
350
- expect(logger.debug).toHaveBeenCalledWith(
351
- '[ProtocolV2 Test] encode raw frame',
352
- expect.objectContaining({
353
- context: 'tx:Ping',
354
- messageTypeId: 60206,
355
- router: 1,
356
- })
357
- );
358
- expect(logger.debug).toHaveBeenCalledWith(
359
- '[ProtocolV2 Test] decode raw frame',
360
- expect.objectContaining({
361
- context: 'rx:Ping',
362
- messageTypeId: 60207,
363
- })
364
- );
365
- expect(logger.debug).toHaveBeenCalledWith(
366
- '[ProtocolV2 Test] RX payload type=Success messageTypeId=60207',
367
- {
368
- message: 'accepted',
369
- }
370
- );
564
+ expect(logger.debug).not.toHaveBeenCalled();
371
565
  });
372
566
 
373
567
  test('session suppresses debug logs for file transfer calls', async () => {
@@ -399,10 +593,9 @@ describe('Protocol V2 framing and session', () => {
399
593
  const stale = ProtocolV2.encodeFrame(schemas, 'Success', {
400
594
  message: 'stale response',
401
595
  });
402
- const response = ProtocolV2.encodeFrame(schemas, 'ProtoVersion', {
403
- major_version: 2,
404
- minor_version: 0,
405
- patch_version: 1,
596
+ const response = ProtocolV2.encodeFrame(schemas, 'ProtocolInfo', {
597
+ version: 2,
598
+ supported_messages: [],
406
599
  });
407
600
  const logger = {
408
601
  debug: jest.fn(),
@@ -417,13 +610,13 @@ describe('Protocol V2 framing and session', () => {
417
610
  });
418
611
 
419
612
  await expect(
420
- session.call('GetProtoVersion', {}, { expectedTypes: ['ProtoVersion'] })
613
+ session.call('ProtocolInfoRequest', {}, { expectedTypes: ['ProtocolInfo'] })
421
614
  ).resolves.toEqual({
422
- type: 'ProtoVersion',
615
+ type: 'ProtocolInfo',
423
616
  message: {
424
- major_version: 2,
425
- minor_version: 0,
426
- patch_version: 1,
617
+ version: 2,
618
+ supported_messages: [],
619
+ protobuf_definition: null,
427
620
  },
428
621
  });
429
622
 
@@ -433,9 +626,8 @@ describe('Protocol V2 framing and session', () => {
433
626
 
434
627
  test('session consumes intermediate response frames before returning the final response', async () => {
435
628
  const written = [];
436
- const progress = ProtocolV2.encodeFrame(schemas, 'DevFirmwareInstallProgress', {
437
- target_id: 0,
438
- progress: 42,
629
+ const progress = ProtocolV2.encodeFrame(schemas, 'DeviceFirmwareUpdateStatus', {
630
+ records: [{ target_id: 4, status: 1 }],
439
631
  });
440
632
  const success = ProtocolV2.encodeFrame(schemas, 'Success', {
441
633
  message: 'ok',
@@ -459,20 +651,19 @@ describe('Protocol V2 framing and session', () => {
459
651
  });
460
652
 
461
653
  const result = await session.call(
462
- 'DevFirmwareUpdate',
463
- {},
654
+ 'DeviceFirmwareUpdateRequest',
655
+ { targets: [{ target_id: 4, path: 'vol1:firmware.bin' }] },
464
656
  {
465
- intermediateTypes: ['DevFirmwareInstallProgress'],
657
+ intermediateTypes: ['DeviceFirmwareUpdateStatus'],
466
658
  onIntermediateResponse,
467
659
  }
468
660
  );
469
661
 
470
662
  expect(readFrame).toHaveBeenCalledTimes(2);
471
663
  expect(onIntermediateResponse).toHaveBeenCalledWith({
472
- type: 'DevFirmwareInstallProgress',
664
+ type: 'DeviceFirmwareUpdateStatus',
473
665
  message: {
474
- target_id: 0,
475
- progress: 42,
666
+ records: [{ target_id: 4, status: 1, payload_version: null, path: null }],
476
667
  },
477
668
  });
478
669
  expect(result).toEqual({
@@ -562,7 +753,7 @@ describe('Protocol V2 framing and session', () => {
562
753
  });
563
754
 
564
755
  await expect(
565
- session.call('GetProtoVersion', {}, { timeoutMs: 10, expectedTypes: ['ProtoVersion'] })
756
+ session.call('ProtocolInfoRequest', {}, { timeoutMs: 10, expectedTypes: ['ProtocolInfo'] })
566
757
  ).rejects.toThrow('Protocol V2 response timeout');
567
758
 
568
759
  // Without cancellation the loop would skip this unexpected Success frame
@@ -661,6 +852,61 @@ describe('Protocol V2 framing and session', () => {
661
852
  expect(written.map(frame => frame[6])).toEqual([1, 2, 1]);
662
853
  });
663
854
 
855
+ test('session reuses an injected sequence cursor across recreated sessions', async () => {
856
+ const written = [];
857
+ const cursor = new ProtocolV2SequenceCursor();
858
+ const makeSession = () =>
859
+ new ProtocolV2Session({
860
+ schemas,
861
+ router: 1,
862
+ sequenceCursor: cursor,
863
+ writeFrame: frame => {
864
+ written.push(frame);
865
+ return Promise.resolve();
866
+ },
867
+ readFrame: () => {
868
+ const [frame] = written.slice(-1);
869
+ const { seq } = protocolV2.decodeFrame(frame);
870
+ return Promise.resolve(
871
+ rewriteSeq(ProtocolV2.encodeFrame(schemas, 'Success', { message: 'ok' }), seq)
872
+ );
873
+ },
874
+ });
875
+
876
+ await makeSession().call('Ping', { message: '1' });
877
+ await makeSession().call('Ping', { message: '2' });
878
+
879
+ expect(written.map(frame => frame[6])).toEqual([1, 2]);
880
+ });
881
+
882
+ test('session passes per-call context to frame IO callbacks', async () => {
883
+ const writeContexts = [];
884
+ const readContexts = [];
885
+ const response = ProtocolV2.encodeFrame(schemas, 'Success', { message: 'ok' });
886
+ const session = new ProtocolV2Session({
887
+ schemas,
888
+ router: 1,
889
+ generation: 7,
890
+ writeFrame: (_frame, context) => {
891
+ writeContexts.push(context);
892
+ return Promise.resolve();
893
+ },
894
+ readFrame: context => {
895
+ readContexts.push(context);
896
+ return Promise.resolve(response);
897
+ },
898
+ });
899
+
900
+ await session.call('Ping', { message: 'ping' }, { timeoutMs: 123 });
901
+ await session.call('FileWrite', {}, { timeoutMs: 456 });
902
+
903
+ expect(writeContexts).toEqual([
904
+ { messageName: 'Ping', timeoutMs: 123, highVolume: false, generation: 7 },
905
+ { messageName: 'FileWrite', timeoutMs: 456, highVolume: true, generation: 7 },
906
+ ]);
907
+ expect(readContexts).toEqual(writeContexts);
908
+ });
909
+
664
910
  test('assembler throws and resets on frames with an impossible length field', () => {
665
911
  const assembler = new ProtocolV2FrameAssembler();
666
912
  // expectedLen = 0 < 8-byte minimum: without the guard this poisons the
@@ -687,10 +933,9 @@ describe('Protocol V2 framing and session', () => {
687
933
  });
688
934
 
689
935
  test('assembler drain returns every buffered complete frame', () => {
690
- const first = ProtocolV2.encodeFrame(schemas, 'ProtoVersion', {
691
- major_version: 1,
692
- minor_version: 0,
693
- patch_version: 0,
936
+ const first = ProtocolV2.encodeFrame(schemas, 'ProtocolInfo', {
937
+ version: 1,
938
+ supported_messages: [],
694
939
  });
695
940
  const second = ProtocolV2.encodeFrame(schemas, 'Success', { message: 'ok' });
696
941
  const third = ProtocolV2.encodeFrame(schemas, 'Success', { message: 'last' });
@@ -714,6 +959,26 @@ describe('Protocol V2 framing and session', () => {
714
959
  expect(decoded.type).toBe('ButtonRequest');
715
960
  });
716
961
 
962
+ test('decodes legacy V1 Failure as a Protocol V2 fallback', () => {
963
+ // Some device-side rejection paths still return legacy Failure(type=3)
964
+ // inside a Protocol V2 frame. It must surface as a device Failure, not as
965
+ // a protobuf catalog TypeError.
966
+ const frame = ProtocolV2.encodeFrame(
967
+ { ...schemas, protocolV2: schemas.protocolV1 },
968
+ 'Failure',
969
+ {
970
+ code: 1,
971
+ message: 'Action cancelled',
972
+ }
973
+ );
974
+ const decoded = ProtocolV2.decodeFrame(schemas, frame);
975
+ expect(decoded.type).toBe('Failure');
976
+ expect(decoded.message).toEqual({
977
+ code: 1,
978
+ message: 'Action cancelled',
979
+ });
980
+ });
981
+
717
982
  test('does not fall back to legacy V1 messages outside the allowlist', () => {
718
983
  // OnekeyFeatures exists only in the V1 schema and is not allowlisted.
719
984
  const frame = protocolV2.encodeProtobufFrame(10026, new Uint8Array(0));
@@ -721,7 +986,6 @@ describe('Protocol V2 framing and session', () => {
721
986
  });
722
987
 
723
988
  test('hexToBytes converts valid hex and rejects malformed input', () => {
724
- const { hexToBytes } = sessionModule;
725
989
  expect(hexToBytes('5a0102')).toEqual(new Uint8Array([0x5a, 0x01, 0x02]));
726
990
  expect(hexToBytes('')).toEqual(new Uint8Array(0));
727
991
  expect(() => hexToBytes('abc')).toThrow('Invalid hex string: odd length');