@onekeyfe/hd-transport 1.2.0-alpha.2 → 1.2.0-alpha.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/__tests__/messages.test.js +122 -0
- package/__tests__/protocol-v2-link-manager.test.js +351 -0
- package/__tests__/protocol-v2-usb-transport-base.test.js +296 -0
- package/__tests__/protocol-v2.test.js +384 -108
- package/dist/constants.d.ts +5 -3
- package/dist/constants.d.ts.map +1 -1
- package/dist/index.d.ts +920 -369
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +587 -251
- package/dist/protocols/index.d.ts +11 -5
- package/dist/protocols/index.d.ts.map +1 -1
- package/dist/protocols/v2/constants.d.ts +1 -0
- package/dist/protocols/v2/constants.d.ts.map +1 -1
- package/dist/protocols/v2/decode.d.ts +3 -2
- package/dist/protocols/v2/decode.d.ts.map +1 -1
- package/dist/protocols/v2/encode.d.ts +2 -3
- package/dist/protocols/v2/encode.d.ts.map +1 -1
- package/dist/protocols/v2/index.d.ts +0 -1
- package/dist/protocols/v2/index.d.ts.map +1 -1
- package/dist/protocols/v2/link-manager.d.ts +36 -0
- package/dist/protocols/v2/link-manager.d.ts.map +1 -0
- package/dist/protocols/v2/sequence-cursor.d.ts +5 -0
- package/dist/protocols/v2/sequence-cursor.d.ts.map +1 -0
- package/dist/protocols/v2/session.d.ts +18 -3
- package/dist/protocols/v2/session.d.ts.map +1 -1
- package/dist/protocols/v2/usb-transport-base.d.ts +31 -0
- package/dist/protocols/v2/usb-transport-base.d.ts.map +1 -0
- package/dist/serialization/protobuf/decode.d.ts.map +1 -1
- package/dist/serialization/protobuf/messages.d.ts.map +1 -1
- package/dist/types/messages.d.ts +534 -225
- package/dist/types/messages.d.ts.map +1 -1
- package/dist/types/transport.d.ts +5 -3
- package/dist/types/transport.d.ts.map +1 -1
- package/messages-protocol-v2.json +825 -709
- package/package.json +3 -3
- package/scripts/protobuf-build.sh +96 -310
- package/scripts/protobuf-patches/index.js +1 -0
- package/scripts/protobuf-types.js +12 -0
- package/src/constants.ts +21 -15
- package/src/index.ts +8 -0
- package/src/protocols/index.ts +25 -39
- package/src/protocols/v2/constants.ts +1 -0
- package/src/protocols/v2/crc8.ts +1 -1
- package/src/protocols/v2/decode.ts +38 -34
- package/src/protocols/v2/encode.ts +2 -28
- package/src/protocols/v2/index.ts +0 -1
- package/src/protocols/v2/link-manager.ts +162 -0
- package/src/protocols/v2/sequence-cursor.ts +10 -0
- package/src/protocols/v2/session.ts +132 -178
- package/src/protocols/v2/usb-transport-base.ts +194 -0
- package/src/serialization/protobuf/decode.ts +4 -1
- package/src/serialization/protobuf/messages.ts +6 -1
- package/src/types/messages.ts +636 -265
- package/src/types/transport.ts +3 -3
- package/dist/protocols/v2/debug.d.ts +0 -13
- package/dist/protocols/v2/debug.d.ts.map +0 -1
- package/src/protocols/v2/debug.ts +0 -26
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
const { ProtocolV2 } = require('../src/protocols');
|
|
2
2
|
const { parseConfigure } = require('../src/serialization/protobuf/messages');
|
|
3
|
-
const
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
const {
|
|
4
|
+
ProtocolV2FrameAssembler,
|
|
5
|
+
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
|
-
|
|
63
|
+
ProtocolInfoRequest: {
|
|
46
64
|
fields: {},
|
|
47
65
|
},
|
|
48
|
-
|
|
66
|
+
ProtocolInfo: {
|
|
49
67
|
fields: {
|
|
50
|
-
|
|
68
|
+
version: {
|
|
51
69
|
type: 'uint32',
|
|
52
70
|
id: 1,
|
|
53
71
|
},
|
|
54
|
-
|
|
72
|
+
supported_messages: {
|
|
55
73
|
type: 'uint32',
|
|
56
74
|
id: 2,
|
|
75
|
+
rule: 'repeated',
|
|
57
76
|
},
|
|
58
|
-
|
|
59
|
-
type: '
|
|
77
|
+
protobuf_definition: {
|
|
78
|
+
type: 'string',
|
|
60
79
|
id: 3,
|
|
61
80
|
},
|
|
62
81
|
},
|
|
@@ -77,21 +96,72 @@ const protocolV2Messages = parseConfigure({
|
|
|
77
96
|
},
|
|
78
97
|
},
|
|
79
98
|
},
|
|
80
|
-
|
|
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
|
+
},
|
|
82
114
|
},
|
|
83
|
-
|
|
115
|
+
DeviceFirmwareTarget: {
|
|
84
116
|
fields: {
|
|
85
117
|
target_id: {
|
|
86
118
|
type: 'uint32',
|
|
87
119
|
id: 1,
|
|
88
120
|
},
|
|
89
|
-
|
|
90
|
-
type: '
|
|
121
|
+
path: {
|
|
122
|
+
type: 'string',
|
|
91
123
|
id: 2,
|
|
92
124
|
},
|
|
93
125
|
},
|
|
94
126
|
},
|
|
127
|
+
DeviceFirmwareUpdateRequest: {
|
|
128
|
+
fields: {
|
|
129
|
+
targets: {
|
|
130
|
+
type: 'DeviceFirmwareTarget',
|
|
131
|
+
id: 1,
|
|
132
|
+
rule: 'repeated',
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
DeviceFirmwareUpdateRecord: {
|
|
137
|
+
fields: {
|
|
138
|
+
target_id: {
|
|
139
|
+
type: 'uint32',
|
|
140
|
+
id: 1,
|
|
141
|
+
},
|
|
142
|
+
status: {
|
|
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
|
+
},
|
|
95
165
|
FileWrite: {
|
|
96
166
|
fields: {},
|
|
97
167
|
},
|
|
@@ -117,13 +187,14 @@ const protocolV2Messages = parseConfigure({
|
|
|
117
187
|
},
|
|
118
188
|
MessageType: {
|
|
119
189
|
values: {
|
|
120
|
-
|
|
121
|
-
|
|
190
|
+
MessageType_ProtocolInfoRequest: 60200,
|
|
191
|
+
MessageType_ProtocolInfo: 60201,
|
|
122
192
|
MessageType_Ping: 60206,
|
|
123
193
|
MessageType_Success: 60207,
|
|
194
|
+
MessageType_Failure: 60208,
|
|
124
195
|
MessageType_FileWrite: 60805,
|
|
125
|
-
|
|
126
|
-
|
|
196
|
+
MessageType_DeviceFirmwareUpdateRequest: 61000,
|
|
197
|
+
MessageType_DeviceFirmwareUpdateStatus: 61002,
|
|
127
198
|
MessageType_PartialNested: 62000,
|
|
128
199
|
},
|
|
129
200
|
},
|
|
@@ -144,10 +215,10 @@ const rewriteSeq = (frame, seq) => {
|
|
|
144
215
|
|
|
145
216
|
describe('Protocol V2 framing and session', () => {
|
|
146
217
|
test('encodes and decodes Protocol V2 protobuf frames', () => {
|
|
147
|
-
const frame = ProtocolV2.encodeFrame(schemas, '
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
218
|
+
const frame = ProtocolV2.encodeFrame(schemas, 'ProtocolInfo', {
|
|
219
|
+
version: 1,
|
|
220
|
+
supported_messages: [60200, 60201],
|
|
221
|
+
protobuf_definition: 'proto',
|
|
151
222
|
});
|
|
152
223
|
|
|
153
224
|
const parsed = protocolV2.decodeFrame(frame);
|
|
@@ -155,15 +226,15 @@ describe('Protocol V2 framing and session', () => {
|
|
|
155
226
|
|
|
156
227
|
const decoded = ProtocolV2.decodeFrame(schemas, frame);
|
|
157
228
|
expect(decoded).toEqual({
|
|
158
|
-
type: '
|
|
159
|
-
messageName: '
|
|
229
|
+
type: 'ProtocolInfo',
|
|
230
|
+
messageName: 'ProtocolInfo',
|
|
160
231
|
messageTypeId: 60201,
|
|
161
232
|
pbPayload: parsed.pbPayload,
|
|
162
233
|
seq: parsed.seq,
|
|
163
234
|
message: {
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
235
|
+
version: 1,
|
|
236
|
+
supported_messages: [60200, 60201],
|
|
237
|
+
protobuf_definition: 'proto',
|
|
167
238
|
},
|
|
168
239
|
});
|
|
169
240
|
});
|
|
@@ -201,10 +272,9 @@ describe('Protocol V2 framing and session', () => {
|
|
|
201
272
|
});
|
|
202
273
|
|
|
203
274
|
test('reassembles split Protocol V2 frames and rejects oversized frames', () => {
|
|
204
|
-
const frame = ProtocolV2.encodeFrame(schemas, '
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
patch_version: 0,
|
|
275
|
+
const frame = ProtocolV2.encodeFrame(schemas, 'ProtocolInfo', {
|
|
276
|
+
version: 1,
|
|
277
|
+
supported_messages: [],
|
|
208
278
|
});
|
|
209
279
|
const assembler = new ProtocolV2FrameAssembler();
|
|
210
280
|
|
|
@@ -215,16 +285,28 @@ describe('Protocol V2 framing and session', () => {
|
|
|
215
285
|
expect(() => assembler.push(oversized)).toThrow('Protocol V2 frame too large');
|
|
216
286
|
});
|
|
217
287
|
|
|
288
|
+
test('enforces the firmware 4200-byte Protocol V2 frame boundary', () => {
|
|
289
|
+
const boundaryFrame = ProtocolV2.encodeFrame(schemas, 'Ping', {
|
|
290
|
+
message: 'x'.repeat(4187),
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
expect(PROTOCOL_V2_FRAME_MAX_BYTES).toBe(4200);
|
|
294
|
+
expect(boundaryFrame).toHaveLength(PROTOCOL_V2_FRAME_MAX_BYTES);
|
|
295
|
+
expect(() =>
|
|
296
|
+
ProtocolV2.encodeFrame(schemas, 'Ping', {
|
|
297
|
+
message: 'x'.repeat(4188),
|
|
298
|
+
})
|
|
299
|
+
).toThrow('Protocol V2 frame too large: 4201 > 4200');
|
|
300
|
+
});
|
|
301
|
+
|
|
218
302
|
test('keeps bytes after the first complete frame for the next read', () => {
|
|
219
|
-
const first = ProtocolV2.encodeFrame(schemas, '
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
patch_version: 0,
|
|
303
|
+
const first = ProtocolV2.encodeFrame(schemas, 'ProtocolInfo', {
|
|
304
|
+
version: 1,
|
|
305
|
+
supported_messages: [],
|
|
223
306
|
});
|
|
224
|
-
const second = ProtocolV2.encodeFrame(schemas, '
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
patch_version: 0,
|
|
307
|
+
const second = ProtocolV2.encodeFrame(schemas, 'ProtocolInfo', {
|
|
308
|
+
version: 2,
|
|
309
|
+
supported_messages: [],
|
|
228
310
|
});
|
|
229
311
|
const assembler = new ProtocolV2FrameAssembler();
|
|
230
312
|
const combined = new Uint8Array(first.length + second.length);
|
|
@@ -237,10 +319,9 @@ describe('Protocol V2 framing and session', () => {
|
|
|
237
319
|
|
|
238
320
|
test('session writes one encoded frame and decodes the response frame', async () => {
|
|
239
321
|
const written = [];
|
|
240
|
-
const response = ProtocolV2.encodeFrame(schemas, '
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
patch_version: 1,
|
|
322
|
+
const response = ProtocolV2.encodeFrame(schemas, 'ProtocolInfo', {
|
|
323
|
+
version: 2,
|
|
324
|
+
supported_messages: [60206],
|
|
244
325
|
});
|
|
245
326
|
const session = new ProtocolV2Session({
|
|
246
327
|
schemas,
|
|
@@ -253,20 +334,169 @@ describe('Protocol V2 framing and session', () => {
|
|
|
253
334
|
Promise.resolve(rewriteSeq(response, protocolV2.decodeFrame(written[0]).seq)),
|
|
254
335
|
});
|
|
255
336
|
|
|
256
|
-
const result = await session.call('
|
|
337
|
+
const result = await session.call('ProtocolInfoRequest', {});
|
|
257
338
|
|
|
258
339
|
expect(written).toHaveLength(1);
|
|
259
340
|
expect(written[0][4]).toBe(1);
|
|
260
341
|
expect(written[0][5]).toBe(0);
|
|
261
342
|
expect(protocolV2.decodeFrame(written[0]).messageTypeId).toBe(60200);
|
|
262
343
|
expect(result).toEqual({
|
|
263
|
-
type: '
|
|
344
|
+
type: 'ProtocolInfo',
|
|
345
|
+
message: {
|
|
346
|
+
version: 2,
|
|
347
|
+
supported_messages: [60206],
|
|
348
|
+
protobuf_definition: null,
|
|
349
|
+
},
|
|
350
|
+
});
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
test('session does not log Protocol V2 TX and RX frames', async () => {
|
|
354
|
+
const written = [];
|
|
355
|
+
const logger = { debug: jest.fn() };
|
|
356
|
+
const response = ProtocolV2.encodeFrame(schemas, 'ProtocolInfo', {
|
|
357
|
+
version: 2,
|
|
358
|
+
supported_messages: [60206],
|
|
359
|
+
protobuf_definition: 'sensitive-schema',
|
|
360
|
+
});
|
|
361
|
+
const session = new ProtocolV2Session({
|
|
362
|
+
schemas,
|
|
363
|
+
router: 1,
|
|
364
|
+
logger,
|
|
365
|
+
logPrefix: 'ProtocolV2 Test',
|
|
366
|
+
writeFrame: frame => {
|
|
367
|
+
written.push(frame);
|
|
368
|
+
return Promise.resolve();
|
|
369
|
+
},
|
|
370
|
+
readFrame: () =>
|
|
371
|
+
Promise.resolve(rewriteSeq(response, protocolV2.decodeFrame(written[0]).seq)),
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
await session.call('ProtocolInfoRequest', {});
|
|
375
|
+
|
|
376
|
+
expect(logger.debug).not.toHaveBeenCalled();
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
test('session does not log RX frame metadata when protobuf payload decoding fails', async () => {
|
|
380
|
+
const logger = { debug: jest.fn() };
|
|
381
|
+
const response = protocolV2.encodeProtobufFrame(
|
|
382
|
+
60208,
|
|
383
|
+
hexToBytes('0801121648616e646c6572206e6f742072656769737465726564')
|
|
384
|
+
);
|
|
385
|
+
const session = new ProtocolV2Session({
|
|
386
|
+
schemas,
|
|
387
|
+
router: 1,
|
|
388
|
+
logger,
|
|
389
|
+
logPrefix: 'ProtocolV2 Test',
|
|
390
|
+
writeFrame: () => Promise.resolve(),
|
|
391
|
+
readFrame: () => Promise.resolve(response),
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
await expect(session.call('Ping', { message: 'hello' })).rejects.toThrow();
|
|
395
|
+
|
|
396
|
+
expect(logger.debug).not.toHaveBeenCalled();
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
test('session skips Proto Link ACK frames before decoding the protobuf response', async () => {
|
|
400
|
+
const ack = new Uint8Array(8);
|
|
401
|
+
ack[0] = 0x5a;
|
|
402
|
+
ack[1] = 8;
|
|
403
|
+
ack[2] = 0;
|
|
404
|
+
ack[4] = 1;
|
|
405
|
+
ack[5] = 1;
|
|
406
|
+
ack[6] = 1;
|
|
407
|
+
ack[3] = protocolV2.crc8(ack, 3);
|
|
408
|
+
ack[7] = protocolV2.crc8(ack, 7);
|
|
409
|
+
|
|
410
|
+
const response = ProtocolV2.encodeFrame(schemas, 'Success', {
|
|
411
|
+
message: 'ok',
|
|
412
|
+
});
|
|
413
|
+
const readFrame = jest.fn().mockResolvedValueOnce(ack).mockResolvedValueOnce(response);
|
|
414
|
+
const session = new ProtocolV2Session({
|
|
415
|
+
schemas,
|
|
416
|
+
router: 1,
|
|
417
|
+
writeFrame: () => Promise.resolve(),
|
|
418
|
+
readFrame,
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
await expect(session.call('Ping', { message: 'hello' })).resolves.toEqual({
|
|
422
|
+
type: 'Success',
|
|
264
423
|
message: {
|
|
265
|
-
|
|
266
|
-
minor_version: 0,
|
|
267
|
-
patch_version: 1,
|
|
424
|
+
message: 'ok',
|
|
268
425
|
},
|
|
269
426
|
});
|
|
427
|
+
expect(readFrame).toHaveBeenCalledTimes(2);
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
test('session ignores an ACK whose sequence does not match the request', async () => {
|
|
431
|
+
const mismatchedAck = new Uint8Array(8);
|
|
432
|
+
mismatchedAck[0] = 0x5a;
|
|
433
|
+
mismatchedAck[1] = 8;
|
|
434
|
+
mismatchedAck[4] = 1;
|
|
435
|
+
mismatchedAck[5] = 1;
|
|
436
|
+
mismatchedAck[6] = 2;
|
|
437
|
+
mismatchedAck[3] = protocolV2.crc8(mismatchedAck, 3);
|
|
438
|
+
mismatchedAck[7] = protocolV2.crc8(mismatchedAck, 7);
|
|
439
|
+
const readFrame = jest
|
|
440
|
+
.fn()
|
|
441
|
+
.mockResolvedValueOnce(mismatchedAck)
|
|
442
|
+
.mockImplementation(() => new Promise(() => {}));
|
|
443
|
+
const session = new ProtocolV2Session({
|
|
444
|
+
schemas,
|
|
445
|
+
router: 1,
|
|
446
|
+
deliveryTimeoutMs: 10,
|
|
447
|
+
writeFrame: () => Promise.resolve(),
|
|
448
|
+
readFrame,
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
await expect(session.call('Ping', { message: 'hello' })).rejects.toThrow(
|
|
452
|
+
'Protocol V2 delivery timeout after 10ms for Ping'
|
|
453
|
+
);
|
|
454
|
+
expect(readFrame).toHaveBeenCalledTimes(2);
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
test('session keeps waiting for a response after a matching delivery ACK', async () => {
|
|
458
|
+
const ack = new Uint8Array(8);
|
|
459
|
+
ack[0] = 0x5a;
|
|
460
|
+
ack[1] = 8;
|
|
461
|
+
ack[4] = 1;
|
|
462
|
+
ack[5] = 1;
|
|
463
|
+
ack[6] = 1;
|
|
464
|
+
ack[3] = protocolV2.crc8(ack, 3);
|
|
465
|
+
ack[7] = protocolV2.crc8(ack, 7);
|
|
466
|
+
const readFrame = jest
|
|
467
|
+
.fn()
|
|
468
|
+
.mockResolvedValueOnce(ack)
|
|
469
|
+
.mockImplementation(() => new Promise(() => {}));
|
|
470
|
+
const session = new ProtocolV2Session({
|
|
471
|
+
schemas,
|
|
472
|
+
router: 1,
|
|
473
|
+
deliveryTimeoutMs: 10,
|
|
474
|
+
writeFrame: () => Promise.resolve(),
|
|
475
|
+
readFrame,
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
await expect(session.call('Ping', { message: 'hello' }, { timeoutMs: 25 })).rejects.toThrow(
|
|
479
|
+
'Protocol V2 response timeout after 25ms for Ping'
|
|
480
|
+
);
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
test('session rejects BLE frames above its configured frame limit before writing', async () => {
|
|
484
|
+
const writeFrame = jest.fn().mockResolvedValue(undefined);
|
|
485
|
+
const response = ProtocolV2.encodeFrame(schemas, 'Success', {
|
|
486
|
+
message: 'ok',
|
|
487
|
+
});
|
|
488
|
+
const session = new ProtocolV2Session({
|
|
489
|
+
schemas,
|
|
490
|
+
router: 1,
|
|
491
|
+
maxFrameBytes: 2048,
|
|
492
|
+
writeFrame,
|
|
493
|
+
readFrame: () => Promise.resolve(response),
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
await expect(session.call('Ping', { message: 'x'.repeat(2048) })).rejects.toThrow(
|
|
497
|
+
'Protocol V2 frame too large for transport: 2061 > 2048'
|
|
498
|
+
);
|
|
499
|
+
expect(writeFrame).not.toHaveBeenCalled();
|
|
270
500
|
});
|
|
271
501
|
|
|
272
502
|
test('session starts response timeout after the frame is written', async () => {
|
|
@@ -293,11 +523,10 @@ describe('Protocol V2 framing and session', () => {
|
|
|
293
523
|
});
|
|
294
524
|
});
|
|
295
525
|
|
|
296
|
-
test('session
|
|
297
|
-
const response = ProtocolV2.encodeFrame(schemas, '
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
patch_version: 1,
|
|
526
|
+
test('session logs a device-owned response seq without rejecting the frame', async () => {
|
|
527
|
+
const response = ProtocolV2.encodeFrame(schemas, 'ProtocolInfo', {
|
|
528
|
+
version: 2,
|
|
529
|
+
supported_messages: [],
|
|
301
530
|
});
|
|
302
531
|
const logger = {
|
|
303
532
|
debug: jest.fn(),
|
|
@@ -310,18 +539,18 @@ describe('Protocol V2 framing and session', () => {
|
|
|
310
539
|
logger,
|
|
311
540
|
});
|
|
312
541
|
|
|
313
|
-
await expect(session.call('
|
|
314
|
-
type: '
|
|
542
|
+
await expect(session.call('ProtocolInfoRequest', {})).resolves.toEqual({
|
|
543
|
+
type: 'ProtocolInfo',
|
|
315
544
|
message: {
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
545
|
+
version: 2,
|
|
546
|
+
supported_messages: [],
|
|
547
|
+
protobuf_definition: null,
|
|
319
548
|
},
|
|
320
549
|
});
|
|
321
|
-
expect(logger.debug).
|
|
550
|
+
expect(logger.debug).not.toHaveBeenCalled();
|
|
322
551
|
});
|
|
323
552
|
|
|
324
|
-
test('session
|
|
553
|
+
test('session does not log transmit or receive payload details', async () => {
|
|
325
554
|
const response = ProtocolV2.encodeFrame(schemas, 'Success', {
|
|
326
555
|
message: 'accepted',
|
|
327
556
|
});
|
|
@@ -344,30 +573,7 @@ describe('Protocol V2 framing and session', () => {
|
|
|
344
573
|
},
|
|
345
574
|
});
|
|
346
575
|
|
|
347
|
-
expect(logger.debug).
|
|
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
|
-
);
|
|
576
|
+
expect(logger.debug).not.toHaveBeenCalled();
|
|
371
577
|
});
|
|
372
578
|
|
|
373
579
|
test('session suppresses debug logs for file transfer calls', async () => {
|
|
@@ -399,10 +605,9 @@ describe('Protocol V2 framing and session', () => {
|
|
|
399
605
|
const stale = ProtocolV2.encodeFrame(schemas, 'Success', {
|
|
400
606
|
message: 'stale response',
|
|
401
607
|
});
|
|
402
|
-
const response = ProtocolV2.encodeFrame(schemas, '
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
patch_version: 1,
|
|
608
|
+
const response = ProtocolV2.encodeFrame(schemas, 'ProtocolInfo', {
|
|
609
|
+
version: 2,
|
|
610
|
+
supported_messages: [],
|
|
406
611
|
});
|
|
407
612
|
const logger = {
|
|
408
613
|
debug: jest.fn(),
|
|
@@ -417,13 +622,13 @@ describe('Protocol V2 framing and session', () => {
|
|
|
417
622
|
});
|
|
418
623
|
|
|
419
624
|
await expect(
|
|
420
|
-
session.call('
|
|
625
|
+
session.call('ProtocolInfoRequest', {}, { expectedTypes: ['ProtocolInfo'] })
|
|
421
626
|
).resolves.toEqual({
|
|
422
|
-
type: '
|
|
627
|
+
type: 'ProtocolInfo',
|
|
423
628
|
message: {
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
629
|
+
version: 2,
|
|
630
|
+
supported_messages: [],
|
|
631
|
+
protobuf_definition: null,
|
|
427
632
|
},
|
|
428
633
|
});
|
|
429
634
|
|
|
@@ -433,9 +638,8 @@ describe('Protocol V2 framing and session', () => {
|
|
|
433
638
|
|
|
434
639
|
test('session consumes intermediate response frames before returning the final response', async () => {
|
|
435
640
|
const written = [];
|
|
436
|
-
const progress = ProtocolV2.encodeFrame(schemas, '
|
|
437
|
-
target_id:
|
|
438
|
-
progress: 42,
|
|
641
|
+
const progress = ProtocolV2.encodeFrame(schemas, 'DeviceFirmwareUpdateStatus', {
|
|
642
|
+
records: [{ target_id: 4, status: 1 }],
|
|
439
643
|
});
|
|
440
644
|
const success = ProtocolV2.encodeFrame(schemas, 'Success', {
|
|
441
645
|
message: 'ok',
|
|
@@ -459,20 +663,19 @@ describe('Protocol V2 framing and session', () => {
|
|
|
459
663
|
});
|
|
460
664
|
|
|
461
665
|
const result = await session.call(
|
|
462
|
-
'
|
|
463
|
-
{},
|
|
666
|
+
'DeviceFirmwareUpdateRequest',
|
|
667
|
+
{ targets: [{ target_id: 4, path: 'vol1:firmware.bin' }] },
|
|
464
668
|
{
|
|
465
|
-
intermediateTypes: ['
|
|
669
|
+
intermediateTypes: ['DeviceFirmwareUpdateStatus'],
|
|
466
670
|
onIntermediateResponse,
|
|
467
671
|
}
|
|
468
672
|
);
|
|
469
673
|
|
|
470
674
|
expect(readFrame).toHaveBeenCalledTimes(2);
|
|
471
675
|
expect(onIntermediateResponse).toHaveBeenCalledWith({
|
|
472
|
-
type: '
|
|
676
|
+
type: 'DeviceFirmwareUpdateStatus',
|
|
473
677
|
message: {
|
|
474
|
-
target_id:
|
|
475
|
-
progress: 42,
|
|
678
|
+
records: [{ target_id: 4, status: 1, payload_version: null, path: null }],
|
|
476
679
|
},
|
|
477
680
|
});
|
|
478
681
|
expect(result).toEqual({
|
|
@@ -562,7 +765,7 @@ describe('Protocol V2 framing and session', () => {
|
|
|
562
765
|
});
|
|
563
766
|
|
|
564
767
|
await expect(
|
|
565
|
-
session.call('
|
|
768
|
+
session.call('ProtocolInfoRequest', {}, { timeoutMs: 10, expectedTypes: ['ProtocolInfo'] })
|
|
566
769
|
).rejects.toThrow('Protocol V2 response timeout');
|
|
567
770
|
|
|
568
771
|
// Without cancellation the loop would skip this unexpected Success frame
|
|
@@ -661,6 +864,61 @@ describe('Protocol V2 framing and session', () => {
|
|
|
661
864
|
expect(written.map(frame => frame[6])).toEqual([1, 2, 1]);
|
|
662
865
|
});
|
|
663
866
|
|
|
867
|
+
test('session reuses an injected sequence cursor across recreated sessions', async () => {
|
|
868
|
+
const written = [];
|
|
869
|
+
const cursor = new ProtocolV2SequenceCursor();
|
|
870
|
+
const makeSession = () =>
|
|
871
|
+
new ProtocolV2Session({
|
|
872
|
+
schemas,
|
|
873
|
+
router: 1,
|
|
874
|
+
sequenceCursor: cursor,
|
|
875
|
+
writeFrame: frame => {
|
|
876
|
+
written.push(frame);
|
|
877
|
+
return Promise.resolve();
|
|
878
|
+
},
|
|
879
|
+
readFrame: () => {
|
|
880
|
+
const [frame] = written.slice(-1);
|
|
881
|
+
const { seq } = protocolV2.decodeFrame(frame);
|
|
882
|
+
return Promise.resolve(
|
|
883
|
+
rewriteSeq(ProtocolV2.encodeFrame(schemas, 'Success', { message: 'ok' }), seq)
|
|
884
|
+
);
|
|
885
|
+
},
|
|
886
|
+
});
|
|
887
|
+
|
|
888
|
+
await makeSession().call('Ping', { message: '1' });
|
|
889
|
+
await makeSession().call('Ping', { message: '2' });
|
|
890
|
+
|
|
891
|
+
expect(written.map(frame => frame[6])).toEqual([1, 2]);
|
|
892
|
+
});
|
|
893
|
+
|
|
894
|
+
test('session passes per-call context to frame IO callbacks', async () => {
|
|
895
|
+
const writeContexts = [];
|
|
896
|
+
const readContexts = [];
|
|
897
|
+
const response = ProtocolV2.encodeFrame(schemas, 'Success', { message: 'ok' });
|
|
898
|
+
const session = new ProtocolV2Session({
|
|
899
|
+
schemas,
|
|
900
|
+
router: 1,
|
|
901
|
+
generation: 7,
|
|
902
|
+
writeFrame: (_frame, context) => {
|
|
903
|
+
writeContexts.push(context);
|
|
904
|
+
return Promise.resolve();
|
|
905
|
+
},
|
|
906
|
+
readFrame: context => {
|
|
907
|
+
readContexts.push(context);
|
|
908
|
+
return Promise.resolve(response);
|
|
909
|
+
},
|
|
910
|
+
});
|
|
911
|
+
|
|
912
|
+
await session.call('Ping', { message: 'ping' }, { timeoutMs: 123 });
|
|
913
|
+
await session.call('FileWrite', {}, { timeoutMs: 456 });
|
|
914
|
+
|
|
915
|
+
expect(writeContexts).toEqual([
|
|
916
|
+
{ messageName: 'Ping', timeoutMs: 123, highVolume: false, generation: 7 },
|
|
917
|
+
{ messageName: 'FileWrite', timeoutMs: 456, highVolume: true, generation: 7 },
|
|
918
|
+
]);
|
|
919
|
+
expect(readContexts).toEqual(writeContexts);
|
|
920
|
+
});
|
|
921
|
+
|
|
664
922
|
test('assembler throws and resets on frames with an impossible length field', () => {
|
|
665
923
|
const assembler = new ProtocolV2FrameAssembler();
|
|
666
924
|
// expectedLen = 0 < 8-byte minimum: without the guard this poisons the
|
|
@@ -687,10 +945,9 @@ describe('Protocol V2 framing and session', () => {
|
|
|
687
945
|
});
|
|
688
946
|
|
|
689
947
|
test('assembler drain returns every buffered complete frame', () => {
|
|
690
|
-
const first = ProtocolV2.encodeFrame(schemas, '
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
patch_version: 0,
|
|
948
|
+
const first = ProtocolV2.encodeFrame(schemas, 'ProtocolInfo', {
|
|
949
|
+
version: 1,
|
|
950
|
+
supported_messages: [],
|
|
694
951
|
});
|
|
695
952
|
const second = ProtocolV2.encodeFrame(schemas, 'Success', { message: 'ok' });
|
|
696
953
|
const third = ProtocolV2.encodeFrame(schemas, 'Success', { message: 'last' });
|
|
@@ -714,6 +971,26 @@ describe('Protocol V2 framing and session', () => {
|
|
|
714
971
|
expect(decoded.type).toBe('ButtonRequest');
|
|
715
972
|
});
|
|
716
973
|
|
|
974
|
+
test('decodes legacy V1 Failure as a Protocol V2 fallback', () => {
|
|
975
|
+
// Some device-side rejection paths still return legacy Failure(type=3)
|
|
976
|
+
// inside a Protocol V2 frame. It must surface as a device Failure, not as
|
|
977
|
+
// a protobuf catalog TypeError.
|
|
978
|
+
const frame = ProtocolV2.encodeFrame(
|
|
979
|
+
{ ...schemas, protocolV2: schemas.protocolV1 },
|
|
980
|
+
'Failure',
|
|
981
|
+
{
|
|
982
|
+
code: 1,
|
|
983
|
+
message: 'Action cancelled',
|
|
984
|
+
}
|
|
985
|
+
);
|
|
986
|
+
const decoded = ProtocolV2.decodeFrame(schemas, frame);
|
|
987
|
+
expect(decoded.type).toBe('Failure');
|
|
988
|
+
expect(decoded.message).toEqual({
|
|
989
|
+
code: 1,
|
|
990
|
+
message: 'Action cancelled',
|
|
991
|
+
});
|
|
992
|
+
});
|
|
993
|
+
|
|
717
994
|
test('does not fall back to legacy V1 messages outside the allowlist', () => {
|
|
718
995
|
// OnekeyFeatures exists only in the V1 schema and is not allowlisted.
|
|
719
996
|
const frame = protocolV2.encodeProtobufFrame(10026, new Uint8Array(0));
|
|
@@ -721,7 +998,6 @@ describe('Protocol V2 framing and session', () => {
|
|
|
721
998
|
});
|
|
722
999
|
|
|
723
1000
|
test('hexToBytes converts valid hex and rejects malformed input', () => {
|
|
724
|
-
const { hexToBytes } = sessionModule;
|
|
725
1001
|
expect(hexToBytes('5a0102')).toEqual(new Uint8Array([0x5a, 0x01, 0x02]));
|
|
726
1002
|
expect(hexToBytes('')).toEqual(new Uint8Array(0));
|
|
727
1003
|
expect(() => hexToBytes('abc')).toThrow('Invalid hex string: odd length');
|