@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.
- package/README.md +1 -1
- package/__tests__/messages.test.js +122 -0
- package/__tests__/protocol-v2-link-manager.test.js +326 -0
- package/__tests__/protocol-v2-usb-transport-base.test.js +296 -0
- package/__tests__/protocol-v2.test.js +371 -107
- package/dist/constants.d.ts +5 -3
- package/dist/constants.d.ts.map +1 -1
- package/dist/index.d.ts +912 -371
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +551 -254
- package/dist/protocols/index.d.ts +10 -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 +2 -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 +35 -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 +15 -4
- 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 +535 -226
- 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 +31 -40
- package/src/protocols/v2/constants.ts +1 -0
- package/src/protocols/v2/crc8.ts +1 -1
- package/src/protocols/v2/decode.ts +31 -34
- package/src/protocols/v2/encode.ts +2 -28
- package/src/protocols/v2/index.ts +0 -1
- package/src/protocols/v2/link-manager.ts +160 -0
- package/src/protocols/v2/sequence-cursor.ts +10 -0
- package/src/protocols/v2/session.ts +79 -180
- 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 +637 -266
- 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,17 +96,80 @@ 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
|
+
},
|
|
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
|
-
|
|
136
|
+
DeviceFirmwareUpdateRecord: {
|
|
84
137
|
fields: {
|
|
85
138
|
target_id: {
|
|
86
139
|
type: 'uint32',
|
|
87
140
|
id: 1,
|
|
88
141
|
},
|
|
89
|
-
|
|
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
|
-
|
|
121
|
-
|
|
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
|
-
|
|
126
|
-
|
|
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, '
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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: '
|
|
159
|
-
messageName: '
|
|
242
|
+
type: 'ProtocolInfo',
|
|
243
|
+
messageName: 'ProtocolInfo',
|
|
160
244
|
messageTypeId: 60201,
|
|
161
245
|
pbPayload: parsed.pbPayload,
|
|
162
246
|
seq: parsed.seq,
|
|
163
247
|
message: {
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
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, '
|
|
205
|
-
|
|
206
|
-
|
|
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, '
|
|
220
|
-
|
|
221
|
-
|
|
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, '
|
|
225
|
-
|
|
226
|
-
|
|
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, '
|
|
241
|
-
|
|
242
|
-
|
|
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('
|
|
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: '
|
|
385
|
+
type: 'ProtocolInfo',
|
|
264
386
|
message: {
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
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
|
|
297
|
-
const response = ProtocolV2.encodeFrame(schemas, '
|
|
298
|
-
|
|
299
|
-
|
|
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('
|
|
314
|
-
type: '
|
|
530
|
+
await expect(session.call('ProtocolInfoRequest', {})).resolves.toEqual({
|
|
531
|
+
type: 'ProtocolInfo',
|
|
315
532
|
message: {
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
533
|
+
version: 2,
|
|
534
|
+
supported_messages: [],
|
|
535
|
+
protobuf_definition: null,
|
|
319
536
|
},
|
|
320
537
|
});
|
|
321
|
-
expect(logger.debug).
|
|
538
|
+
expect(logger.debug).not.toHaveBeenCalled();
|
|
322
539
|
});
|
|
323
540
|
|
|
324
|
-
test('session
|
|
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).
|
|
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, '
|
|
403
|
-
|
|
404
|
-
|
|
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('
|
|
613
|
+
session.call('ProtocolInfoRequest', {}, { expectedTypes: ['ProtocolInfo'] })
|
|
421
614
|
).resolves.toEqual({
|
|
422
|
-
type: '
|
|
615
|
+
type: 'ProtocolInfo',
|
|
423
616
|
message: {
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
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, '
|
|
437
|
-
target_id:
|
|
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
|
-
'
|
|
463
|
-
{},
|
|
654
|
+
'DeviceFirmwareUpdateRequest',
|
|
655
|
+
{ targets: [{ target_id: 4, path: 'vol1:firmware.bin' }] },
|
|
464
656
|
{
|
|
465
|
-
intermediateTypes: ['
|
|
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: '
|
|
664
|
+
type: 'DeviceFirmwareUpdateStatus',
|
|
473
665
|
message: {
|
|
474
|
-
target_id:
|
|
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('
|
|
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, '
|
|
691
|
-
|
|
692
|
-
|
|
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');
|