@onekeyfe/hd-transport 1.2.2-alpha.100 → 1.2.2-alpha.2
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 +61 -21
- package/__tests__/protocol-v2-link-manager.test.js +70 -0
- package/__tests__/protocol-v2-usb-transport-base.test.js +6 -4
- package/__tests__/protocol-v2.test.js +151 -0
- package/dist/index.d.ts +121 -18
- package/dist/index.js +98 -13
- package/dist/protocols/v2/link-manager.d.ts +1 -0
- package/dist/protocols/v2/link-manager.d.ts.map +1 -1
- package/dist/protocols/v2/session.d.ts +9 -1
- package/dist/protocols/v2/session.d.ts.map +1 -1
- package/dist/types/messages.d.ts +79 -10
- package/dist/types/messages.d.ts.map +1 -1
- package/dist/types/transport.d.ts +2 -0
- package/dist/types/transport.d.ts.map +1 -1
- package/messages-protocol-v2.json +1541 -1332
- package/package.json +2 -2
- package/src/protocols/v2/link-manager.ts +46 -5
- package/src/protocols/v2/session.ts +67 -3
- package/src/types/messages.ts +103 -15
- package/src/types/transport.ts +4 -0
package/README.md
CHANGED
|
@@ -20,7 +20,7 @@ In order to be able to use new features of onekey-firmware you need to update pr
|
|
|
20
20
|
1. `yarn update-submodules` to update firmware submodule
|
|
21
21
|
1. `yarn update-protobuf` to generate new `./messages.json`, `./messages-protocol-v2.json` and `./src/types/messages.ts`
|
|
22
22
|
|
|
23
|
-
The same task can be run from the repository root with `yarn update-protobuf`. The Protocol V2 schema requires the `firmware-pro2` submodule checked out on branch `
|
|
23
|
+
The same task can be run from the repository root with `yarn update-protobuf`. The Protocol V2 schema requires the `firmware-pro2` submodule checked out on branch `main`.
|
|
24
24
|
|
|
25
25
|
## Docs
|
|
26
26
|
|
|
@@ -88,6 +88,31 @@ describe('messages', () => {
|
|
|
88
88
|
});
|
|
89
89
|
});
|
|
90
90
|
|
|
91
|
+
test('Protocol V2 firmware update progress matches firmware-pro2 main', () => {
|
|
92
|
+
expect(v2Messages.nested.MessageType.values).toMatchObject({
|
|
93
|
+
MessageType_DeviceFindMyTokenState: 60450,
|
|
94
|
+
MessageType_DeviceFindMyTokenUpdate: 60451,
|
|
95
|
+
MessageType_DeviceFindMyTokenStateGet: 60452,
|
|
96
|
+
});
|
|
97
|
+
expect(v2Messages.nested.DeviceFirmwareUpdatePhase.values).toEqual({
|
|
98
|
+
FW_MGMT_UPDATER_PHASE_PREPARE: 0,
|
|
99
|
+
FW_MGMT_UPDATER_PHASE_INSTALL: 1,
|
|
100
|
+
FW_MGMT_UPDATER_PHASE_VERIFY: 2,
|
|
101
|
+
});
|
|
102
|
+
expect(v2Messages.nested.DeviceFirmwareUpdateRequest.fields.reboot_after_update).toMatchObject({
|
|
103
|
+
id: 1,
|
|
104
|
+
type: 'bool',
|
|
105
|
+
});
|
|
106
|
+
expect(v2Messages.nested.DeviceFirmwareUpdateRecord.fields).toMatchObject({
|
|
107
|
+
progress_percent: { id: 11, type: 'uint32' },
|
|
108
|
+
phase_info: { id: 12, type: 'DeviceFirmwareUpdatePhaseInfo' },
|
|
109
|
+
});
|
|
110
|
+
expect(v2Messages.nested.DeviceFirmwareUpdateRecordFields.fields).toMatchObject({
|
|
111
|
+
progress_percent: { id: 11, type: 'bool' },
|
|
112
|
+
phase_info: { id: 12, type: 'bool' },
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
|
|
91
116
|
test('Protocol V2 conflicting enums keep their own wire values', () => {
|
|
92
117
|
expect(generatedTypes.ProtocolV2FailureType).toMatchObject({
|
|
93
118
|
Failure_DataError: 4,
|
|
@@ -125,11 +150,6 @@ describe('messages', () => {
|
|
|
125
150
|
expect(v2Messages.nested.DeviceSessionGet.fields).toEqual({
|
|
126
151
|
session_id: { id: 1, type: 'bytes' },
|
|
127
152
|
btc_test_address: { id: 2, type: 'string' },
|
|
128
|
-
seed_domains: {
|
|
129
|
-
id: 3,
|
|
130
|
-
rule: 'repeated',
|
|
131
|
-
type: 'DeviceSessionSeedDomain',
|
|
132
|
-
},
|
|
133
153
|
});
|
|
134
154
|
expect(v2Messages.nested.DeviceSessionSeedDomain.values).toEqual({
|
|
135
155
|
SeedDomain_Standard: 1,
|
|
@@ -170,6 +190,11 @@ describe('messages', () => {
|
|
|
170
190
|
type: 'bool',
|
|
171
191
|
id: 2,
|
|
172
192
|
},
|
|
193
|
+
seed_domains: {
|
|
194
|
+
rule: 'repeated',
|
|
195
|
+
type: 'DeviceSessionSeedDomain',
|
|
196
|
+
id: 3,
|
|
197
|
+
},
|
|
173
198
|
},
|
|
174
199
|
});
|
|
175
200
|
expect(v2Messages.nested.DeviceSessionAskPin_FailureSubCodes.values).toEqual({
|
|
@@ -187,42 +212,57 @@ describe('messages', () => {
|
|
|
187
212
|
const messages = parseConfigure(v2Messages);
|
|
188
213
|
const { Message } = createMessageFromName(messages, 'DeviceSessionAskPassphrase');
|
|
189
214
|
|
|
190
|
-
const standardWallet = encode(Message, {
|
|
215
|
+
const standardWallet = encode(Message, {
|
|
216
|
+
passphrase: '',
|
|
217
|
+
on_device: false,
|
|
218
|
+
seed_domains: [],
|
|
219
|
+
});
|
|
191
220
|
const onHost = Message.encode(
|
|
192
|
-
Message.create({
|
|
221
|
+
Message.create({
|
|
222
|
+
passphrase: 'host hidden wallet',
|
|
223
|
+
on_device: false,
|
|
224
|
+
seed_domains: [
|
|
225
|
+
generatedTypes.DeviceSessionSeedDomain.SeedDomain_Standard,
|
|
226
|
+
generatedTypes.DeviceSessionSeedDomain.SeedDomain_Cardano,
|
|
227
|
+
],
|
|
228
|
+
})
|
|
229
|
+
).finish();
|
|
230
|
+
const onDevice = Message.encode(
|
|
231
|
+
Message.create({
|
|
232
|
+
on_device: true,
|
|
233
|
+
seed_domains: [generatedTypes.DeviceSessionSeedDomain.SeedDomain_Standard],
|
|
234
|
+
})
|
|
193
235
|
).finish();
|
|
194
|
-
const onDevice = Message.encode(Message.create({ on_device: true })).finish();
|
|
195
236
|
|
|
196
237
|
expect(standardWallet.toString('hex')).toBe('0a001000');
|
|
197
238
|
expect(Buffer.from(onHost).toString('hex')).toBe(
|
|
198
|
-
'
|
|
239
|
+
'0a12686f73742068696464656e2077616c6c657410001a020102'
|
|
199
240
|
);
|
|
200
|
-
expect(Buffer.from(onDevice).toString('hex')).toBe('
|
|
241
|
+
expect(Buffer.from(onDevice).toString('hex')).toBe('10011a0101');
|
|
201
242
|
expect(Message.decode(onHost)).toMatchObject({
|
|
202
243
|
passphrase: 'host hidden wallet',
|
|
203
244
|
on_device: false,
|
|
245
|
+
seed_domains: [
|
|
246
|
+
generatedTypes.DeviceSessionSeedDomain.SeedDomain_Standard,
|
|
247
|
+
generatedTypes.DeviceSessionSeedDomain.SeedDomain_Cardano,
|
|
248
|
+
],
|
|
249
|
+
});
|
|
250
|
+
expect(Message.decode(onDevice)).toMatchObject({
|
|
251
|
+
on_device: true,
|
|
252
|
+
seed_domains: [generatedTypes.DeviceSessionSeedDomain.SeedDomain_Standard],
|
|
204
253
|
});
|
|
205
|
-
expect(Message.decode(onDevice)).toMatchObject({ on_device: true });
|
|
206
254
|
});
|
|
207
255
|
|
|
208
|
-
test('Protocol V2 wallet recovery carries the expected wallet
|
|
256
|
+
test('Protocol V2 wallet recovery carries the expected wallet on wire', () => {
|
|
209
257
|
const messages = parseConfigure(v2Messages);
|
|
210
258
|
const { Message } = createMessageFromName(messages, 'DeviceSessionGet');
|
|
211
259
|
const payload = encode(Message, {
|
|
212
260
|
btc_test_address: 'tb1qwallet',
|
|
213
|
-
seed_domains: [
|
|
214
|
-
generatedTypes.DeviceSessionSeedDomain.SeedDomain_Standard,
|
|
215
|
-
generatedTypes.DeviceSessionSeedDomain.SeedDomain_Cardano,
|
|
216
|
-
],
|
|
217
261
|
});
|
|
218
262
|
|
|
219
|
-
expect(payload.toString('hex')).toBe('
|
|
263
|
+
expect(payload.toString('hex')).toBe('120a7462317177616c6c6574');
|
|
220
264
|
expect(Message.decode(payload.toBuffer())).toMatchObject({
|
|
221
265
|
btc_test_address: 'tb1qwallet',
|
|
222
|
-
seed_domains: [
|
|
223
|
-
generatedTypes.DeviceSessionSeedDomain.SeedDomain_Standard,
|
|
224
|
-
generatedTypes.DeviceSessionSeedDomain.SeedDomain_Cardano,
|
|
225
|
-
],
|
|
226
266
|
});
|
|
227
267
|
});
|
|
228
268
|
|
|
@@ -317,6 +317,76 @@ describe('ProtocolV2LinkManager', () => {
|
|
|
317
317
|
]);
|
|
318
318
|
});
|
|
319
319
|
|
|
320
|
+
test('times out a queued call without sending it after the active call settles', async () => {
|
|
321
|
+
let releaseActiveRead;
|
|
322
|
+
let markActiveReadStarted;
|
|
323
|
+
const activeReadStarted = new Promise(resolve => {
|
|
324
|
+
markActiveReadStarted = resolve;
|
|
325
|
+
});
|
|
326
|
+
const activeReadBlocked = new Promise(resolve => {
|
|
327
|
+
releaseActiveRead = resolve;
|
|
328
|
+
});
|
|
329
|
+
const sentSeqs = [];
|
|
330
|
+
const success = ProtocolV2.encodeFrame(
|
|
331
|
+
schemas,
|
|
332
|
+
'Success',
|
|
333
|
+
{ message: 'ok' },
|
|
334
|
+
{ router: 1, packetSrc: 0, seq: 1 }
|
|
335
|
+
);
|
|
336
|
+
let requestSeq = 0;
|
|
337
|
+
let readCount = 0;
|
|
338
|
+
const adapter = {
|
|
339
|
+
router: 1,
|
|
340
|
+
generation: 1,
|
|
341
|
+
prepareCall: jest.fn(),
|
|
342
|
+
writeFrame: jest.fn(frame => {
|
|
343
|
+
[, , , , , , requestSeq] = frame;
|
|
344
|
+
sentSeqs.push(requestSeq);
|
|
345
|
+
return Promise.resolve();
|
|
346
|
+
}),
|
|
347
|
+
readFrame: jest.fn(async () => {
|
|
348
|
+
readCount += 1;
|
|
349
|
+
if (readCount === 1) {
|
|
350
|
+
markActiveReadStarted();
|
|
351
|
+
await activeReadBlocked;
|
|
352
|
+
}
|
|
353
|
+
return rewriteSeq(success, requestSeq);
|
|
354
|
+
}),
|
|
355
|
+
reset: jest.fn(),
|
|
356
|
+
createTimeoutError: (name, timeoutMs) =>
|
|
357
|
+
new Error(`response timeout after ${timeoutMs}ms for ${name}`),
|
|
358
|
+
};
|
|
359
|
+
const manager = new ProtocolV2LinkManager({
|
|
360
|
+
getSchemas: () => schemas,
|
|
361
|
+
classifyError: () => 'recoverable',
|
|
362
|
+
});
|
|
363
|
+
const createAdapter = jest.fn(() => adapter);
|
|
364
|
+
|
|
365
|
+
const activeCall = manager.call('device-a', createAdapter, 'Ping', { message: 'active' });
|
|
366
|
+
await activeReadStarted;
|
|
367
|
+
const queuedCall = manager.call(
|
|
368
|
+
'device-a',
|
|
369
|
+
createAdapter,
|
|
370
|
+
'Ping',
|
|
371
|
+
{ message: 'queued' },
|
|
372
|
+
{ timeoutMs: 20 }
|
|
373
|
+
);
|
|
374
|
+
|
|
375
|
+
await expect(queuedCall).rejects.toThrow('response timeout after 20ms for Ping');
|
|
376
|
+
expect(sentSeqs).toEqual([1]);
|
|
377
|
+
|
|
378
|
+
releaseActiveRead();
|
|
379
|
+
await activeCall;
|
|
380
|
+
await expect(
|
|
381
|
+
manager.call('device-a', createAdapter, 'Ping', { message: 'after-timeout' })
|
|
382
|
+
).resolves.toEqual({
|
|
383
|
+
type: 'Success',
|
|
384
|
+
message: { message: 'ok' },
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
expect(sentSeqs).toEqual([1, 2]);
|
|
388
|
+
});
|
|
389
|
+
|
|
320
390
|
test('writes flow control while the active call is waiting for its response', async () => {
|
|
321
391
|
const sentSeqs = [];
|
|
322
392
|
let releaseRead;
|
|
@@ -217,7 +217,7 @@ describe('ProtocolV2UsbTransportBase', () => {
|
|
|
217
217
|
expect(transport.nativeResets).toEqual([['device-a', 'USB reconnected']]);
|
|
218
218
|
});
|
|
219
219
|
|
|
220
|
-
test('passes each queued call its
|
|
220
|
+
test('passes each queued call its remaining timeout context', async () => {
|
|
221
221
|
const transport = new FakeUsbTransport();
|
|
222
222
|
await transport.rotate('device-a');
|
|
223
223
|
|
|
@@ -226,12 +226,14 @@ describe('ProtocolV2UsbTransportBase', () => {
|
|
|
226
226
|
transport.callDevice('device-a', 'second', 222),
|
|
227
227
|
]);
|
|
228
228
|
|
|
229
|
-
expect(transport.readContexts).toEqual([
|
|
229
|
+
expect(transport.readContexts.slice(0, 2)).toEqual([
|
|
230
230
|
['device-a', 111],
|
|
231
231
|
['device-a', 111],
|
|
232
|
-
['device-a', 222],
|
|
233
|
-
['device-a', 222],
|
|
234
232
|
]);
|
|
233
|
+
const secondCallTimeouts = transport.readContexts.slice(2).map(([, timeoutMs]) => timeoutMs);
|
|
234
|
+
expect(secondCallTimeouts[0]).toBeGreaterThan(0);
|
|
235
|
+
expect(secondCallTimeouts[0]).toBeLessThanOrEqual(222);
|
|
236
|
+
expect(secondCallTimeouts[1]).toBe(secondCallTimeouts[0]);
|
|
235
237
|
});
|
|
236
238
|
|
|
237
239
|
test('keeps a coalesced response buffered for the next call', async () => {
|
|
@@ -5,6 +5,7 @@ const {
|
|
|
5
5
|
ProtocolV2LinkError,
|
|
6
6
|
ProtocolV2SequenceCursor,
|
|
7
7
|
ProtocolV2Session,
|
|
8
|
+
detectProtocolV2LinkDisabledError,
|
|
8
9
|
hexToBytes,
|
|
9
10
|
isProtocolV2HighThroughputCall,
|
|
10
11
|
probeProtocolV2,
|
|
@@ -1003,6 +1004,126 @@ describe('Protocol V2 framing and session', () => {
|
|
|
1003
1004
|
expect(result).toEqual({ type: 'WriteCompleted', message: {} });
|
|
1004
1005
|
});
|
|
1005
1006
|
|
|
1007
|
+
test('session consumes a delayed write-only response without completing the next call', async () => {
|
|
1008
|
+
const requestSuccess = ProtocolV2.encodeFrame(schemas, 'Success', {
|
|
1009
|
+
message: 'install accepted',
|
|
1010
|
+
});
|
|
1011
|
+
const statusResponse = ProtocolV2.encodeFrame(schemas, 'DeviceFirmwareUpdateStatus', {
|
|
1012
|
+
records: [{ target_id: 6, status: 1, path: 'vol0:/coprocessor.bin' }],
|
|
1013
|
+
});
|
|
1014
|
+
const prepareCall = jest.fn();
|
|
1015
|
+
const onResponseAfterWrite = jest.fn();
|
|
1016
|
+
const readFrame = jest
|
|
1017
|
+
.fn()
|
|
1018
|
+
.mockResolvedValueOnce(rewriteSeq(requestSuccess, 1))
|
|
1019
|
+
.mockResolvedValueOnce(rewriteSeq(statusResponse, 2));
|
|
1020
|
+
const session = new ProtocolV2Session({
|
|
1021
|
+
schemas,
|
|
1022
|
+
router: 1,
|
|
1023
|
+
prepareCall,
|
|
1024
|
+
writeFrame: () => Promise.resolve(),
|
|
1025
|
+
readFrame,
|
|
1026
|
+
});
|
|
1027
|
+
|
|
1028
|
+
await expect(
|
|
1029
|
+
session.call(
|
|
1030
|
+
'DeviceFirmwareUpdateRequest',
|
|
1031
|
+
{},
|
|
1032
|
+
{
|
|
1033
|
+
returnAfterWrite: true,
|
|
1034
|
+
expectedTypes: ['Success'],
|
|
1035
|
+
onResponseAfterWrite,
|
|
1036
|
+
}
|
|
1037
|
+
)
|
|
1038
|
+
).resolves.toEqual({ type: 'WriteCompleted', message: {} });
|
|
1039
|
+
|
|
1040
|
+
await expect(
|
|
1041
|
+
session.call(
|
|
1042
|
+
'Ping',
|
|
1043
|
+
{ message: 'status-poll' },
|
|
1044
|
+
{
|
|
1045
|
+
expectedTypes: ['DeviceFirmwareUpdateStatus'],
|
|
1046
|
+
}
|
|
1047
|
+
)
|
|
1048
|
+
).resolves.toMatchObject({
|
|
1049
|
+
type: 'DeviceFirmwareUpdateStatus',
|
|
1050
|
+
message: {
|
|
1051
|
+
records: [{ target_id: 6, status: 1, path: 'vol0:/coprocessor.bin' }],
|
|
1052
|
+
},
|
|
1053
|
+
});
|
|
1054
|
+
|
|
1055
|
+
expect(prepareCall).toHaveBeenCalledTimes(1);
|
|
1056
|
+
expect(onResponseAfterWrite).toHaveBeenCalledWith({
|
|
1057
|
+
type: 'Success',
|
|
1058
|
+
message: { message: 'install accepted' },
|
|
1059
|
+
});
|
|
1060
|
+
expect(readFrame).toHaveBeenCalledTimes(2);
|
|
1061
|
+
});
|
|
1062
|
+
|
|
1063
|
+
test('session preserves a delayed write-only Failure for the next caller', async () => {
|
|
1064
|
+
const requestFailure = ProtocolV2.encodeFrame(schemas, 'Failure', {
|
|
1065
|
+
code: 4,
|
|
1066
|
+
message: 'install cancelled',
|
|
1067
|
+
});
|
|
1068
|
+
const prepareCall = jest.fn();
|
|
1069
|
+
const onResponseAfterWrite = jest.fn();
|
|
1070
|
+
const readFrame = jest.fn().mockResolvedValueOnce(rewriteSeq(requestFailure, 1));
|
|
1071
|
+
const session = new ProtocolV2Session({
|
|
1072
|
+
schemas,
|
|
1073
|
+
router: 1,
|
|
1074
|
+
prepareCall,
|
|
1075
|
+
writeFrame: () => Promise.resolve(),
|
|
1076
|
+
readFrame,
|
|
1077
|
+
});
|
|
1078
|
+
|
|
1079
|
+
await expect(
|
|
1080
|
+
session.call(
|
|
1081
|
+
'DeviceFirmwareUpdateRequest',
|
|
1082
|
+
{},
|
|
1083
|
+
{
|
|
1084
|
+
returnAfterWrite: true,
|
|
1085
|
+
expectedTypes: ['Success'],
|
|
1086
|
+
onResponseAfterWrite,
|
|
1087
|
+
}
|
|
1088
|
+
)
|
|
1089
|
+
).resolves.toEqual({ type: 'WriteCompleted', message: {} });
|
|
1090
|
+
|
|
1091
|
+
await expect(
|
|
1092
|
+
session.call(
|
|
1093
|
+
'Ping',
|
|
1094
|
+
{ message: 'status-poll' },
|
|
1095
|
+
{
|
|
1096
|
+
expectedTypes: ['DeviceFirmwareUpdateStatus'],
|
|
1097
|
+
}
|
|
1098
|
+
)
|
|
1099
|
+
).resolves.toMatchObject({
|
|
1100
|
+
type: 'Failure',
|
|
1101
|
+
message: {
|
|
1102
|
+
code: 4,
|
|
1103
|
+
message: 'install cancelled',
|
|
1104
|
+
},
|
|
1105
|
+
});
|
|
1106
|
+
|
|
1107
|
+
expect(prepareCall).toHaveBeenCalledTimes(1);
|
|
1108
|
+
expect(onResponseAfterWrite).not.toHaveBeenCalled();
|
|
1109
|
+
expect(readFrame).toHaveBeenCalledTimes(1);
|
|
1110
|
+
});
|
|
1111
|
+
|
|
1112
|
+
test('probeProtocolV2 rethrows caller-selected fatal errors without treating them as a miss', async () => {
|
|
1113
|
+
const onProbeFailed = jest.fn();
|
|
1114
|
+
const staleBond = Object.assign(new Error('Bluetooth pairing failed'), { errorCode: 715 });
|
|
1115
|
+
|
|
1116
|
+
await expect(
|
|
1117
|
+
probeProtocolV2({
|
|
1118
|
+
call: () => Promise.reject(staleBond),
|
|
1119
|
+
timeoutMs: 1,
|
|
1120
|
+
onProbeFailed,
|
|
1121
|
+
shouldRethrow: error => error?.errorCode === 715,
|
|
1122
|
+
})
|
|
1123
|
+
).rejects.toBe(staleBond);
|
|
1124
|
+
expect(onProbeFailed).not.toHaveBeenCalled();
|
|
1125
|
+
});
|
|
1126
|
+
|
|
1006
1127
|
test('probeProtocolV2 accepts Success as a normal V2 probe response', async () => {
|
|
1007
1128
|
await expect(
|
|
1008
1129
|
probeProtocolV2({
|
|
@@ -1025,6 +1146,36 @@ describe('Protocol V2 framing and session', () => {
|
|
|
1025
1146
|
expect(isProtocolV2LinkDisabledFailure('Failure_ProcessError', 'busy')).toBe(false);
|
|
1026
1147
|
});
|
|
1027
1148
|
|
|
1149
|
+
test('detects a split Protocol V2 link-disabled frame in the shared transport layer', () => {
|
|
1150
|
+
const assembler = new ProtocolV2FrameAssembler();
|
|
1151
|
+
const frame = ProtocolV2.encodeFrame(
|
|
1152
|
+
schemas,
|
|
1153
|
+
'Failure',
|
|
1154
|
+
{ code: 5, message: 'link disabled' },
|
|
1155
|
+
{ router: 2 }
|
|
1156
|
+
);
|
|
1157
|
+
const splitAt = 4;
|
|
1158
|
+
|
|
1159
|
+
expect(
|
|
1160
|
+
detectProtocolV2LinkDisabledError({
|
|
1161
|
+
schemas,
|
|
1162
|
+
assembler,
|
|
1163
|
+
bytes: frame.subarray(0, splitAt),
|
|
1164
|
+
})
|
|
1165
|
+
).toBeUndefined();
|
|
1166
|
+
expect(
|
|
1167
|
+
detectProtocolV2LinkDisabledError({
|
|
1168
|
+
schemas,
|
|
1169
|
+
assembler,
|
|
1170
|
+
bytes: frame.subarray(splitAt),
|
|
1171
|
+
})
|
|
1172
|
+
).toMatchObject({
|
|
1173
|
+
name: 'ProtocolV2LinkDisabledError',
|
|
1174
|
+
failureCode: 5,
|
|
1175
|
+
firmwareMessage: 'link disabled',
|
|
1176
|
+
});
|
|
1177
|
+
});
|
|
1178
|
+
|
|
1028
1179
|
test.each(['Failure_ProcessError', 5])(
|
|
1029
1180
|
'probeProtocolV2 surfaces link disabled without resetting the link for code %s',
|
|
1030
1181
|
async code => {
|