@onekeyfe/hd-transport-lowlevel 1.2.0-alpha.18 → 1.2.0-alpha.181
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/__tests__/protocol-v2.test.js +176 -23
- package/dist/index.d.ts +5 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +88 -59
- package/dist/types.d.ts +1 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/index.ts +109 -74
- package/src/types.ts +1 -0
|
@@ -68,12 +68,25 @@ const protocolV2Schema = {
|
|
|
68
68
|
},
|
|
69
69
|
},
|
|
70
70
|
},
|
|
71
|
+
Failure: {
|
|
72
|
+
fields: {
|
|
73
|
+
code: {
|
|
74
|
+
type: 'uint32',
|
|
75
|
+
id: 1,
|
|
76
|
+
},
|
|
77
|
+
message: {
|
|
78
|
+
type: 'string',
|
|
79
|
+
id: 2,
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
},
|
|
71
83
|
MessageType: {
|
|
72
84
|
values: {
|
|
73
85
|
MessageType_ProtocolInfoRequest: 60200,
|
|
74
86
|
MessageType_ProtocolInfo: 60201,
|
|
75
87
|
MessageType_Ping: 60206,
|
|
76
88
|
MessageType_Success: 60207,
|
|
89
|
+
MessageType_Failure: 60208,
|
|
77
90
|
},
|
|
78
91
|
},
|
|
79
92
|
},
|
|
@@ -122,6 +135,49 @@ const splitFrame = (frame, index) => [
|
|
|
122
135
|
];
|
|
123
136
|
|
|
124
137
|
describe('LowlevelTransport protocol framing', () => {
|
|
138
|
+
test('falls back to Protocol V2 when a cached V1 hint is stale', async () => {
|
|
139
|
+
const plugin = createPlugin({ devices: [], responses: [] });
|
|
140
|
+
const lowlevel = configureTransport(plugin);
|
|
141
|
+
const events = [];
|
|
142
|
+
lowlevel.probeProtocolV1 = jest.fn().mockImplementation(() => {
|
|
143
|
+
events.push('probe-v1');
|
|
144
|
+
return Promise.resolve(false);
|
|
145
|
+
});
|
|
146
|
+
lowlevel.resetConnectionAfterProbe = jest.fn().mockImplementation(() => {
|
|
147
|
+
events.push('reset');
|
|
148
|
+
return Promise.resolve();
|
|
149
|
+
});
|
|
150
|
+
lowlevel.probeProtocolV2 = jest.fn().mockImplementation(() => {
|
|
151
|
+
events.push('probe-v2');
|
|
152
|
+
return Promise.resolve(true);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
await expect(lowlevel.detectProtocol('pro-lowlevel', undefined, 'V1')).resolves.toBe('V2');
|
|
156
|
+
|
|
157
|
+
expect(events).toEqual(['probe-v1', 'reset', 'probe-v2']);
|
|
158
|
+
expect(lowlevel.getProtocolType('pro-lowlevel')).toBe('V2');
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
test('keeps active links when the Protocol V2 schema is configured repeatedly', () => {
|
|
162
|
+
const lowlevel = new LowlevelTransport();
|
|
163
|
+
const invalidateAllLinks = jest.fn().mockResolvedValue(undefined);
|
|
164
|
+
lowlevel.protocolV2Links.invalidateAllLinks = invalidateAllLinks;
|
|
165
|
+
|
|
166
|
+
lowlevel.configureProtocolV2(protocolV2Schema);
|
|
167
|
+
lowlevel.configureProtocolV2(protocolV2Schema);
|
|
168
|
+
|
|
169
|
+
expect(invalidateAllLinks).not.toHaveBeenCalled();
|
|
170
|
+
|
|
171
|
+
lowlevel.configureProtocolV2({
|
|
172
|
+
nested: {
|
|
173
|
+
...protocolV2Schema.nested,
|
|
174
|
+
ExtraMessage: { fields: {} },
|
|
175
|
+
},
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
expect(invalidateAllLinks).toHaveBeenCalledWith('Protocol V2 schema reconfigured');
|
|
179
|
+
});
|
|
180
|
+
|
|
125
181
|
test('keeps Protocol V1 raw notification chunks compatible', async () => {
|
|
126
182
|
const responseChunks = ProtocolV1.encodeTransportPackets(schemas.protocolV1, 'Success', {
|
|
127
183
|
message: 'ok',
|
|
@@ -142,6 +198,23 @@ describe('LowlevelTransport protocol framing', () => {
|
|
|
142
198
|
});
|
|
143
199
|
});
|
|
144
200
|
|
|
201
|
+
test('uses the Protocol V2 BLE writer with the lowlevel compatibility packet size', async () => {
|
|
202
|
+
const plugin = createPlugin({ devices: [], responses: [] });
|
|
203
|
+
const lowlevel = configureTransport(plugin);
|
|
204
|
+
const context = {
|
|
205
|
+
messageName: 'Ping',
|
|
206
|
+
timeoutMs: 1000,
|
|
207
|
+
highThroughput: false,
|
|
208
|
+
generation: 1,
|
|
209
|
+
signal: new AbortController().signal,
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
await lowlevel.writeProtocolV2Frame('pro2-id', new Uint8Array(130), context, jest.fn());
|
|
213
|
+
|
|
214
|
+
expect(plugin.send).toHaveBeenCalledTimes(3);
|
|
215
|
+
expect(plugin.send.mock.calls.map(([, hex]) => hex.length / 2)).toEqual([64, 64, 2]);
|
|
216
|
+
});
|
|
217
|
+
|
|
145
218
|
test('rejects calls before protocol detection', async () => {
|
|
146
219
|
const responseChunks = ProtocolV1.encodeTransportPackets(schemas.protocolV1, 'Success', {
|
|
147
220
|
message: 'ok',
|
|
@@ -172,11 +245,15 @@ describe('LowlevelTransport protocol framing', () => {
|
|
|
172
245
|
supported_messages: [60200, 60201, 60206, 60207],
|
|
173
246
|
protobuf_definition: 'onekey-protocol-v2',
|
|
174
247
|
},
|
|
175
|
-
{ router: PROTOCOL_V2_CHANNEL_BLE_UART }
|
|
248
|
+
{ router: PROTOCOL_V2_CHANNEL_BLE_UART, seq: 2 }
|
|
176
249
|
);
|
|
177
250
|
const plugin = createPlugin({
|
|
178
251
|
devices: [{ id: 'pro2-id', name: 'OneKey Pro 2', commType: 'ble' }],
|
|
179
|
-
responses: [
|
|
252
|
+
responses: [
|
|
253
|
+
new Error('Protocol V1 probe timed out'),
|
|
254
|
+
...splitFrame(probeResponse, 4),
|
|
255
|
+
...splitFrame(callResponse, 5),
|
|
256
|
+
],
|
|
180
257
|
});
|
|
181
258
|
const lowlevel = configureTransport(plugin);
|
|
182
259
|
|
|
@@ -196,9 +273,9 @@ describe('LowlevelTransport protocol framing', () => {
|
|
|
196
273
|
},
|
|
197
274
|
});
|
|
198
275
|
expect(plugin.send).toHaveBeenCalled();
|
|
199
|
-
const sentSeqs = plugin.send.mock.calls
|
|
200
|
-
Number.parseInt(hex.slice(12, 14), 16)
|
|
201
|
-
|
|
276
|
+
const sentSeqs = plugin.send.mock.calls
|
|
277
|
+
.map(([, hex]) => Number.parseInt(hex.slice(12, 14), 16))
|
|
278
|
+
.filter(seq => seq > 0);
|
|
202
279
|
expect(sentSeqs).toEqual([1, 2]);
|
|
203
280
|
});
|
|
204
281
|
|
|
@@ -222,7 +299,7 @@ describe('LowlevelTransport protocol framing', () => {
|
|
|
222
299
|
expect(lowlevel.getProtocolType('unknown-pro2-id')).toBe('V2');
|
|
223
300
|
});
|
|
224
301
|
|
|
225
|
-
test('
|
|
302
|
+
test('detects Protocol V2 again after release without a name-derived hint', async () => {
|
|
226
303
|
const probeResponse = ProtocolV2.encodeFrame(
|
|
227
304
|
schemas,
|
|
228
305
|
'Success',
|
|
@@ -231,7 +308,12 @@ describe('LowlevelTransport protocol framing', () => {
|
|
|
231
308
|
);
|
|
232
309
|
const plugin = createPlugin({
|
|
233
310
|
devices: [{ id: 'reconnect-pro2-id', name: 'OneKey Pro 2', commType: 'ble' }],
|
|
234
|
-
responses: [
|
|
311
|
+
responses: [
|
|
312
|
+
new Error('Protocol V1 probe timed out'),
|
|
313
|
+
bytesToHex(probeResponse),
|
|
314
|
+
new Error('Protocol V1 probe timed out'),
|
|
315
|
+
bytesToHex(probeResponse),
|
|
316
|
+
],
|
|
235
317
|
});
|
|
236
318
|
const lowlevel = configureTransport(plugin);
|
|
237
319
|
|
|
@@ -246,22 +328,26 @@ describe('LowlevelTransport protocol framing', () => {
|
|
|
246
328
|
protocolType: 'V2',
|
|
247
329
|
});
|
|
248
330
|
|
|
249
|
-
const sentSeqs = plugin.send.mock.calls
|
|
250
|
-
Number.parseInt(hex.slice(12, 14), 16)
|
|
251
|
-
|
|
331
|
+
const sentSeqs = plugin.send.mock.calls
|
|
332
|
+
.map(([, hex]) => Number.parseInt(hex.slice(12, 14), 16))
|
|
333
|
+
.filter(seq => seq > 0);
|
|
252
334
|
expect(sentSeqs).toEqual([1, 2]);
|
|
253
335
|
});
|
|
254
336
|
|
|
255
337
|
test('reuses the active generation when Core acquires the same BLE connection again', async () => {
|
|
256
|
-
const
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
338
|
+
const responses = [1, 2, 3, 4].map(seq =>
|
|
339
|
+
bytesToHex(
|
|
340
|
+
ProtocolV2.encodeFrame(
|
|
341
|
+
schemas,
|
|
342
|
+
'Success',
|
|
343
|
+
{ message: 'ok' },
|
|
344
|
+
{ router: PROTOCOL_V2_CHANNEL_BLE_UART, seq }
|
|
345
|
+
)
|
|
346
|
+
)
|
|
261
347
|
);
|
|
262
348
|
const plugin = createPlugin({
|
|
263
349
|
devices: [{ id: 'repeated-acquire-id', name: 'OneKey Pro 2', commType: 'ble' }],
|
|
264
|
-
responses
|
|
350
|
+
responses,
|
|
265
351
|
});
|
|
266
352
|
const lowlevel = configureTransport(plugin);
|
|
267
353
|
|
|
@@ -283,13 +369,19 @@ describe('LowlevelTransport protocol framing', () => {
|
|
|
283
369
|
const sentSeqs = plugin.send.mock.calls.map(([, hex]) =>
|
|
284
370
|
Number.parseInt(hex.slice(12, 14), 16)
|
|
285
371
|
);
|
|
286
|
-
expect(sentSeqs).toEqual([1, 2]);
|
|
372
|
+
expect(sentSeqs).toEqual([1, 2, 3, 4]);
|
|
287
373
|
});
|
|
288
374
|
|
|
289
|
-
test('
|
|
375
|
+
test('actively probes explicit Protocol V2 during bootloader reconnect', async () => {
|
|
376
|
+
const probeResponse = ProtocolV2.encodeFrame(
|
|
377
|
+
schemas,
|
|
378
|
+
'Success',
|
|
379
|
+
{ message: 'ok' },
|
|
380
|
+
{ router: PROTOCOL_V2_CHANNEL_BLE_UART }
|
|
381
|
+
);
|
|
290
382
|
const plugin = createPlugin({
|
|
291
383
|
devices: [{ id: 'bootloader-v2-id', name: 'OneKey Pro 2', commType: 'ble' }],
|
|
292
|
-
responses: [],
|
|
384
|
+
responses: [bytesToHex(probeResponse)],
|
|
293
385
|
});
|
|
294
386
|
const lowlevel = configureTransport(plugin);
|
|
295
387
|
|
|
@@ -299,8 +391,35 @@ describe('LowlevelTransport protocol framing', () => {
|
|
|
299
391
|
uuid: 'bootloader-v2-id',
|
|
300
392
|
protocolType: 'V2',
|
|
301
393
|
});
|
|
302
|
-
expect(plugin.send).
|
|
303
|
-
expect(plugin.receive).
|
|
394
|
+
expect(plugin.send).toHaveBeenCalledTimes(1);
|
|
395
|
+
expect(plugin.receive).toHaveBeenCalledTimes(1);
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
test('disconnects and clears a lowlevel connection when Protocol V2 reports link disabled', async () => {
|
|
399
|
+
const failureResponse = ProtocolV2.encodeFrame(
|
|
400
|
+
schemas,
|
|
401
|
+
'Failure',
|
|
402
|
+
{ code: 5, message: 'link disabled' },
|
|
403
|
+
{ router: PROTOCOL_V2_CHANNEL_BLE_UART }
|
|
404
|
+
);
|
|
405
|
+
const plugin = createPlugin({
|
|
406
|
+
devices: [{ id: 'usb-owned-id', name: 'OneKey Pro 2', commType: 'ble' }],
|
|
407
|
+
responses: [bytesToHex(failureResponse)],
|
|
408
|
+
});
|
|
409
|
+
const lowlevel = configureTransport(plugin);
|
|
410
|
+
|
|
411
|
+
await expect(
|
|
412
|
+
lowlevel.acquire({ uuid: 'usb-owned-id', expectedProtocol: 'V2' })
|
|
413
|
+
).rejects.toMatchObject({
|
|
414
|
+
name: 'ProtocolV2LinkDisabledError',
|
|
415
|
+
failureCode: 5,
|
|
416
|
+
firmwareMessage: 'link disabled',
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
expect(plugin.disconnect).toHaveBeenCalledWith('usb-owned-id');
|
|
420
|
+
expect(lowlevel.connectedDevices.has('usb-owned-id')).toBe(false);
|
|
421
|
+
expect(lowlevel.getProtocolType('usb-owned-id')).toBeUndefined();
|
|
422
|
+
expect(lowlevel.protocolV2Assemblers.has('usb-owned-id')).toBe(false);
|
|
304
423
|
});
|
|
305
424
|
|
|
306
425
|
test('resets the lowlevel connection before probing Protocol V2 after a V1 timeout', async () => {
|
|
@@ -342,11 +461,23 @@ describe('LowlevelTransport protocol framing', () => {
|
|
|
342
461
|
});
|
|
343
462
|
|
|
344
463
|
test('disconnects a tainted Protocol V2 link after a response timeout', async () => {
|
|
464
|
+
const probeResponse = ProtocolV2.encodeFrame(
|
|
465
|
+
schemas,
|
|
466
|
+
'Success',
|
|
467
|
+
{ message: 'ok' },
|
|
468
|
+
{ router: PROTOCOL_V2_CHANNEL_BLE_UART }
|
|
469
|
+
);
|
|
345
470
|
const plugin = createPlugin({
|
|
346
471
|
devices: [{ id: 'timeout-v2-id', name: 'OneKey Pro 2', commType: 'ble' }],
|
|
347
|
-
responses: [],
|
|
472
|
+
responses: [bytesToHex(probeResponse)],
|
|
473
|
+
});
|
|
474
|
+
let receiveCount = 0;
|
|
475
|
+
plugin.receive.mockImplementation(() => {
|
|
476
|
+
receiveCount += 1;
|
|
477
|
+
return receiveCount === 1
|
|
478
|
+
? Promise.resolve(bytesToHex(probeResponse))
|
|
479
|
+
: new Promise(() => {});
|
|
348
480
|
});
|
|
349
|
-
plugin.receive.mockImplementation(() => new Promise(() => {}));
|
|
350
481
|
const lowlevel = configureTransport(plugin);
|
|
351
482
|
|
|
352
483
|
await lowlevel.acquire({ uuid: 'timeout-v2-id', expectedProtocol: 'V2' });
|
|
@@ -358,6 +489,28 @@ describe('LowlevelTransport protocol framing', () => {
|
|
|
358
489
|
expect(plugin.disconnect).toHaveBeenCalledWith('timeout-v2-id');
|
|
359
490
|
});
|
|
360
491
|
|
|
492
|
+
test('preserves an undefined business timeout outside explicit probes', async () => {
|
|
493
|
+
const plugin = createPlugin({
|
|
494
|
+
devices: [{ id: 'v2-id', name: 'OneKey Pro 2', commType: 'ble' }],
|
|
495
|
+
responses: [],
|
|
496
|
+
});
|
|
497
|
+
const lowlevel = configureTransport(plugin);
|
|
498
|
+
lowlevel.deviceProtocol.set('v2-id', 'V2');
|
|
499
|
+
const linkCall = jest
|
|
500
|
+
.spyOn(lowlevel.protocolV2Links, 'call')
|
|
501
|
+
.mockResolvedValue({ type: 'Success', message: {} });
|
|
502
|
+
|
|
503
|
+
await lowlevel.call('v2-id', 'Ping', { message: 'no-business-timeout' });
|
|
504
|
+
|
|
505
|
+
expect(linkCall).toHaveBeenCalledWith(
|
|
506
|
+
'v2-id',
|
|
507
|
+
expect.any(Function),
|
|
508
|
+
'Ping',
|
|
509
|
+
{ message: 'no-business-timeout' },
|
|
510
|
+
undefined
|
|
511
|
+
);
|
|
512
|
+
});
|
|
513
|
+
|
|
361
514
|
test('verifies expected Protocol V1 instead of trusting the requested protocol', async () => {
|
|
362
515
|
const plugin = createPlugin({
|
|
363
516
|
devices: [{ id: 'v2-id', name: 'Unknown BLE Device', commType: 'ble' }],
|
package/dist/index.d.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import * as transport from '@onekeyfe/hd-transport';
|
|
2
|
-
import transport__default, { ProtocolType, LowlevelTransportSharedPlugin,
|
|
2
|
+
import transport__default, { ProtocolType, LowlevelTransportSharedPlugin, TransportCallOptions } from '@onekeyfe/hd-transport';
|
|
3
3
|
import EventEmitter from 'events';
|
|
4
4
|
|
|
5
5
|
type LowLevelAcquireInput = {
|
|
6
6
|
uuid: string;
|
|
7
7
|
expectedProtocol?: ProtocolType;
|
|
8
|
+
protocolHint?: ProtocolType;
|
|
8
9
|
};
|
|
9
10
|
|
|
10
11
|
declare function shouldLogFirmwareUploadProgress({ percent, lastLoggedPercent, now, lastLoggedAt, }: {
|
|
@@ -29,18 +30,20 @@ declare class LowlevelTransport {
|
|
|
29
30
|
private protocolV2Generations;
|
|
30
31
|
private connectedDevices;
|
|
31
32
|
private protocolV2Links;
|
|
33
|
+
private protocolV2SchemaConfiguration;
|
|
32
34
|
getProtocolType(path: string): ProtocolType | undefined;
|
|
33
35
|
init(logger: any, emitter: EventEmitter, plugin: LowlevelTransportSharedPlugin): void;
|
|
34
36
|
configure(signedData: any): void;
|
|
35
37
|
configureProtocolV2(signedData: any): void;
|
|
36
38
|
listen(): void;
|
|
37
|
-
enumerate(): Promise<LowLevelDevice[]>;
|
|
39
|
+
enumerate(): Promise<transport.LowLevelDevice[]>;
|
|
38
40
|
acquire(input: LowLevelAcquireInput): Promise<{
|
|
39
41
|
uuid: string;
|
|
40
42
|
protocolType: ProtocolType;
|
|
41
43
|
}>;
|
|
42
44
|
release(uuid: string): Promise<boolean>;
|
|
43
45
|
call(uuid: string, name: string, data: Record<string, unknown>, options?: TransportCallOptions): Promise<transport.MessageFromOneKey>;
|
|
46
|
+
post(uuid: string, name: string, data: Record<string, unknown>): Promise<void>;
|
|
44
47
|
private callProtocolV1;
|
|
45
48
|
private createProtocolTimeoutError;
|
|
46
49
|
private createProtocolMismatchError;
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,SAYN,MAAM,wBAAwB,CAAC;AAEhC,OAAO,KAAK,YAAY,MAAM,QAAQ,CAAC;AACvC,OAAO,KAAK,EACV,6BAA6B,EAC7B,YAAY,EAEZ,oBAAoB,EACrB,MAAM,wBAAwB,CAAC;AAChC,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC;AAUpD,wBAAgB,+BAA+B,CAAC,EAC9C,OAAO,EACP,iBAAiB,EACjB,GAAG,EACH,YAAY,GACb,EAAE;IACD,OAAO,EAAE,MAAM,CAAC;IAChB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,EAAE,MAAM,CAAC;CACtB,WAMA;AAED,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM;;cAEpD;AAUD,MAAM,CAAC,OAAO,OAAO,iBAAiB;IACpC,SAAS,EAAE,UAAU,CAAC,OAAO,SAAS,CAAC,cAAc,CAAC,GAAG,SAAS,CAAC;IAEnE,WAAW,EAAE,UAAU,CAAC,OAAO,SAAS,CAAC,cAAc,CAAC,GAAG,SAAS,CAAC;IAErE,UAAU,UAAS;IAEnB,GAAG,CAAC,EAAE,GAAG,CAAC;IAEV,OAAO,CAAC,EAAE,YAAY,CAAC;IAEvB,MAAM,EAAE,6BAA6B,CAAuC;IAE5E,OAAO,CAAC,cAAc,CAAwC;IAE9D,OAAO,CAAC,mBAAmB,CAAwC;IAEnE,OAAO,CAAC,oBAAoB,CAAoD;IAEhF,OAAO,CAAC,qBAAqB,CAAkC;IAE/D,OAAO,CAAC,gBAAgB,CAA0B;IAElD,OAAO,CAAC,eAAe,CA6BpB;IAEH,OAAO,CAAC,6BAA6B,CAAqB;IAE1D,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS;IAIvD,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE,6BAA6B;IAO9E,SAAS,CAAC,UAAU,EAAE,GAAG;IAMzB,mBAAmB,CAAC,UAAU,EAAE,GAAG;IAiBnC,MAAM;IAIA,SAAS;IAKT,OAAO,CAAC,KAAK,EAAE,oBAAoB;;;;IAmDnC,OAAO,CAAC,IAAI,EAAE,MAAM;IAepB,IAAI,CACR,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,OAAO,CAAC,EAAE,oBAAoB;IAoB1B,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;YAatD,cAAc;IA6E5B,OAAO,CAAC,0BAA0B;IAOlC,OAAO,CAAC,2BAA2B;IAOnC,OAAO,CAAC,4BAA4B;IAOpC,OAAO,CAAC,kBAAkB;YAMZ,cAAc;YA2Cd,yBAAyB;YAiCzB,eAAe;YAgBf,eAAe;YA6Bf,UAAU;YAUV,qBAAqB;YAmBrB,mBAAmB;YAqBnB,oBAAoB;YAgBpB,cAAc;IAwB5B,OAAO,CAAC,uBAAuB;IAgC/B,OAAO,CAAC,2BAA2B;IAMnC,MAAM;CAGP"}
|
package/dist/index.js
CHANGED
|
@@ -42,7 +42,6 @@ typeof SuppressedError === "function" ? SuppressedError : function (error, suppr
|
|
|
42
42
|
const { check, ProtocolV1, parseConfigure } = transport__default["default"];
|
|
43
43
|
const PROTOCOL_PROBE_TIMEOUT_MS = 1000;
|
|
44
44
|
const PROTOCOL_V2_PROBE_TIMEOUT_MS = 5000;
|
|
45
|
-
const LOWLEVEL_PROTOCOL_TIMEOUT_MS = 30000;
|
|
46
45
|
const LOWLEVEL_PROTOCOL_V2_PACKET_LENGTH = 64;
|
|
47
46
|
const FIRMWARE_UPLOAD_LOG_PERCENT_STEP = 5;
|
|
48
47
|
const FIRMWARE_UPLOAD_LOG_INTERVAL_MS = 10000;
|
|
@@ -54,9 +53,6 @@ function shouldLogFirmwareUploadProgress({ percent, lastLoggedPercent, now, last
|
|
|
54
53
|
function getProtocolV1SendOptions(name) {
|
|
55
54
|
return name === 'FirmwareUpload' ? { withoutResponse: false } : undefined;
|
|
56
55
|
}
|
|
57
|
-
function inferProtocolHintFromDeviceName(name) {
|
|
58
|
-
return /\bpro\s*2\b/i.test(name !== null && name !== void 0 ? name : '') ? 'V2' : undefined;
|
|
59
|
-
}
|
|
60
56
|
function isProtocolV1TransportChunk(data) {
|
|
61
57
|
return data.length >= 9 && data[0] === 0x3f && data[1] === 0x23 && data[2] === 0x23;
|
|
62
58
|
}
|
|
@@ -118,27 +114,29 @@ class LowlevelTransport {
|
|
|
118
114
|
this._messages = messages;
|
|
119
115
|
}
|
|
120
116
|
configureProtocolV2(signedData) {
|
|
117
|
+
const configuration = typeof signedData === 'string' ? signedData : JSON.stringify(signedData);
|
|
118
|
+
if (this.protocolV2SchemaConfiguration === configuration) {
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
const isReconfiguration = this.protocolV2SchemaConfiguration !== undefined;
|
|
121
122
|
this._messagesV2 = parseConfigure(signedData);
|
|
122
|
-
this.
|
|
123
|
-
|
|
124
|
-
|
|
123
|
+
this.protocolV2SchemaConfiguration = configuration;
|
|
124
|
+
if (isReconfiguration) {
|
|
125
|
+
this.protocolV2Links
|
|
126
|
+
.invalidateAllLinks('Protocol V2 schema reconfigured')
|
|
127
|
+
.catch(error => { var _a; return (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('Protocol V2 schema link cleanup failed:', error); });
|
|
128
|
+
}
|
|
125
129
|
}
|
|
126
130
|
listen() {
|
|
127
131
|
}
|
|
128
132
|
enumerate() {
|
|
129
133
|
return __awaiter(this, void 0, void 0, function* () {
|
|
130
134
|
const devices = yield this.plugin.enumerate();
|
|
131
|
-
return devices
|
|
132
|
-
const protocolHint = inferProtocolHintFromDeviceName(device.name);
|
|
133
|
-
if (protocolHint) {
|
|
134
|
-
this.deviceProtocolHints.set(device.id, protocolHint);
|
|
135
|
-
}
|
|
136
|
-
return device;
|
|
137
|
-
});
|
|
135
|
+
return devices;
|
|
138
136
|
});
|
|
139
137
|
}
|
|
140
138
|
acquire(input) {
|
|
141
|
-
var _a;
|
|
139
|
+
var _a, _b, _c, _d;
|
|
142
140
|
return __awaiter(this, void 0, void 0, function* () {
|
|
143
141
|
const alreadyConnected = this.connectedDevices.has(input.uuid);
|
|
144
142
|
try {
|
|
@@ -153,12 +151,35 @@ class LowlevelTransport {
|
|
|
153
151
|
this.Log.debug('lowlelvel transport connect error: ', error);
|
|
154
152
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.LowlevelTrasnportConnectError, (_a = error.message) !== null && _a !== void 0 ? _a : error);
|
|
155
153
|
}
|
|
156
|
-
this.protocolV2Assemblers.set(input.uuid, new transport.ProtocolV2FrameAssembler());
|
|
154
|
+
this.protocolV2Assemblers.set(input.uuid, new transport.ProtocolV2FrameAssembler(transport.PROTOCOL_V2_BLE_FRAME_MAX_BYTES));
|
|
157
155
|
const protocolHint = input.expectedProtocol
|
|
158
156
|
? undefined
|
|
159
|
-
: this.deviceProtocolHints.get(input.uuid);
|
|
160
|
-
|
|
161
|
-
|
|
157
|
+
: (_b = input.protocolHint) !== null && _b !== void 0 ? _b : this.deviceProtocolHints.get(input.uuid);
|
|
158
|
+
try {
|
|
159
|
+
const protocolType = yield this.detectProtocol(input.uuid, input.expectedProtocol, protocolHint);
|
|
160
|
+
return { uuid: input.uuid, protocolType };
|
|
161
|
+
}
|
|
162
|
+
catch (error) {
|
|
163
|
+
try {
|
|
164
|
+
yield this.protocolV2Links.invalidateLink(input.uuid, 'Lowlevel transport acquire failed');
|
|
165
|
+
}
|
|
166
|
+
catch (cleanupError) {
|
|
167
|
+
(_c = this.Log) === null || _c === void 0 ? void 0 : _c.debug('[LowlevelTransport] acquire link cleanup failed:', cleanupError);
|
|
168
|
+
}
|
|
169
|
+
try {
|
|
170
|
+
yield this.plugin.disconnect(input.uuid);
|
|
171
|
+
}
|
|
172
|
+
catch (cleanupError) {
|
|
173
|
+
(_d = this.Log) === null || _d === void 0 ? void 0 : _d.debug('[LowlevelTransport] acquire disconnect failed:', cleanupError);
|
|
174
|
+
}
|
|
175
|
+
finally {
|
|
176
|
+
this.connectedDevices.delete(input.uuid);
|
|
177
|
+
this.deviceProtocol.delete(input.uuid);
|
|
178
|
+
this.protocolV2Assemblers.delete(input.uuid);
|
|
179
|
+
this.advanceProtocolV2Generation(input.uuid);
|
|
180
|
+
}
|
|
181
|
+
throw error;
|
|
182
|
+
}
|
|
162
183
|
});
|
|
163
184
|
}
|
|
164
185
|
release(uuid) {
|
|
@@ -186,13 +207,21 @@ class LowlevelTransport {
|
|
|
186
207
|
if (!protocol) {
|
|
187
208
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, `Device protocol has not been detected for ${uuid}`);
|
|
188
209
|
}
|
|
189
|
-
this.Log.debug('transport call', { name, protocol });
|
|
190
210
|
if (protocol === 'V2') {
|
|
191
211
|
return this.callProtocolV2(uuid, name, data, options);
|
|
192
212
|
}
|
|
193
213
|
return this.callProtocolV1(uuid, name, data, options);
|
|
194
214
|
});
|
|
195
215
|
}
|
|
216
|
+
post(uuid, name, data) {
|
|
217
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
218
|
+
if (this.getProtocolType(uuid) === 'V2') {
|
|
219
|
+
yield this.protocolV2Links.sendFlowControl(uuid, () => this.createProtocolV2Adapter(uuid), name, data);
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
yield this.callProtocolV1(uuid, name, data);
|
|
223
|
+
});
|
|
224
|
+
}
|
|
196
225
|
callProtocolV1(uuid, name, data, options) {
|
|
197
226
|
var _a;
|
|
198
227
|
return __awaiter(this, void 0, void 0, function* () {
|
|
@@ -242,6 +271,15 @@ class LowlevelTransport {
|
|
|
242
271
|
return check.call(jsonData);
|
|
243
272
|
}
|
|
244
273
|
catch (e) {
|
|
274
|
+
if ((e === null || e === void 0 ? void 0 : e.errorCode) === hdShared.HardwareErrorCode.BleTimeoutError &&
|
|
275
|
+
(options === null || options === void 0 ? void 0 : options.timeoutMs) !== PROTOCOL_PROBE_TIMEOUT_MS) {
|
|
276
|
+
try {
|
|
277
|
+
yield this.resetConnectionAfterProbe(uuid, 'V1');
|
|
278
|
+
}
|
|
279
|
+
catch (resetError) {
|
|
280
|
+
this.Log.debug('[LowlevelTransport] reset after Protocol V1 timeout failed:', resetError);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
245
283
|
if (name === 'Initialize' && (options === null || options === void 0 ? void 0 : options.timeoutMs) === PROTOCOL_PROBE_TIMEOUT_MS) {
|
|
246
284
|
this.Log.debug('[LowlevelTransport] Protocol V1 Initialize probe call failed:', e);
|
|
247
285
|
}
|
|
@@ -267,12 +305,15 @@ class LowlevelTransport {
|
|
|
267
305
|
}
|
|
268
306
|
}
|
|
269
307
|
detectProtocol(uuid, expectedProtocol, protocolHint) {
|
|
270
|
-
var _a, _b, _c
|
|
308
|
+
var _a, _b, _c;
|
|
271
309
|
return __awaiter(this, void 0, void 0, function* () {
|
|
272
310
|
if (expectedProtocol === 'V2') {
|
|
273
|
-
this.
|
|
274
|
-
|
|
275
|
-
|
|
311
|
+
if (yield this.probeProtocolV2(uuid)) {
|
|
312
|
+
this.deviceProtocol.set(uuid, 'V2');
|
|
313
|
+
(_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug(`[LowlevelTransport] detectProtocol: uuid=${uuid} -> V2 (expected)`);
|
|
314
|
+
return 'V2';
|
|
315
|
+
}
|
|
316
|
+
throw this.createProtocolMismatchError(expectedProtocol);
|
|
276
317
|
}
|
|
277
318
|
if (expectedProtocol === 'V1') {
|
|
278
319
|
if (yield this.probeProtocolV1(uuid)) {
|
|
@@ -282,28 +323,17 @@ class LowlevelTransport {
|
|
|
282
323
|
}
|
|
283
324
|
throw this.createProtocolMismatchError(expectedProtocol);
|
|
284
325
|
}
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
(
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
const protocolV1Detected = yield this.probeProtocolV1(uuid);
|
|
297
|
-
if (protocolV1Detected) {
|
|
298
|
-
this.deviceProtocol.set(uuid, 'V1');
|
|
299
|
-
(_e = this.Log) === null || _e === void 0 ? void 0 : _e.debug(`[LowlevelTransport] detectProtocol: uuid=${uuid} -> V1`);
|
|
300
|
-
return 'V1';
|
|
301
|
-
}
|
|
302
|
-
yield this.resetConnectionAfterProbe(uuid, 'V1');
|
|
303
|
-
if (yield this.probeProtocolV2(uuid)) {
|
|
304
|
-
this.deviceProtocol.set(uuid, 'V2');
|
|
305
|
-
(_f = this.Log) === null || _f === void 0 ? void 0 : _f.debug(`[LowlevelTransport] detectProtocol: uuid=${uuid} -> V2`);
|
|
306
|
-
return 'V2';
|
|
326
|
+
const probeOrder = protocolHint === 'V2' || this.deviceProtocol.get(uuid) === 'V2' ? ['V2', 'V1'] : ['V1', 'V2'];
|
|
327
|
+
for (const [index, protocol] of probeOrder.entries()) {
|
|
328
|
+
if (index > 0) {
|
|
329
|
+
yield this.resetConnectionAfterProbe(uuid, probeOrder[index - 1]);
|
|
330
|
+
}
|
|
331
|
+
const detected = protocol === 'V1' ? yield this.probeProtocolV1(uuid) : yield this.probeProtocolV2(uuid);
|
|
332
|
+
if (detected) {
|
|
333
|
+
this.deviceProtocol.set(uuid, protocol);
|
|
334
|
+
(_c = this.Log) === null || _c === void 0 ? void 0 : _c.debug(`[LowlevelTransport] detectProtocol: uuid=${uuid} -> ${protocol}`);
|
|
335
|
+
return protocol;
|
|
336
|
+
}
|
|
307
337
|
}
|
|
308
338
|
this.deviceProtocol.delete(uuid);
|
|
309
339
|
throw this.createProtocolDetectionError();
|
|
@@ -411,7 +441,7 @@ class LowlevelTransport {
|
|
|
411
441
|
return __awaiter(this, void 0, void 0, function* () {
|
|
412
442
|
let assembler = this.protocolV2Assemblers.get(uuid);
|
|
413
443
|
if (!assembler) {
|
|
414
|
-
assembler = new transport.ProtocolV2FrameAssembler();
|
|
444
|
+
assembler = new transport.ProtocolV2FrameAssembler(transport.PROTOCOL_V2_BLE_FRAME_MAX_BYTES);
|
|
415
445
|
this.protocolV2Assemblers.set(uuid, assembler);
|
|
416
446
|
}
|
|
417
447
|
const queuedFrame = assembler.push(new Uint8Array(0));
|
|
@@ -428,23 +458,25 @@ class LowlevelTransport {
|
|
|
428
458
|
return frame;
|
|
429
459
|
});
|
|
430
460
|
}
|
|
431
|
-
writeProtocolV2Frame(uuid, frame) {
|
|
461
|
+
writeProtocolV2Frame(uuid, frame, context, assertCurrentGeneration) {
|
|
432
462
|
return __awaiter(this, void 0, void 0, function* () {
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
463
|
+
yield transport.writeProtocolV2BleFrame({
|
|
464
|
+
frame,
|
|
465
|
+
packetCapacity: LOWLEVEL_PROTOCOL_V2_PACKET_LENGTH,
|
|
466
|
+
assertActive: assertCurrentGeneration,
|
|
467
|
+
signal: context.signal,
|
|
468
|
+
abortMessage: `Protocol V2 BLE write aborted for ${context.messageName}`,
|
|
469
|
+
writePacket: packet => this.plugin.send(uuid, transport.bytesToHex(packet)),
|
|
470
|
+
});
|
|
437
471
|
});
|
|
438
472
|
}
|
|
439
473
|
callProtocolV2(uuid, name, data, options) {
|
|
440
|
-
var _a;
|
|
441
474
|
return __awaiter(this, void 0, void 0, function* () {
|
|
442
475
|
if (!this._messages || !this._messagesV2) {
|
|
443
476
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured);
|
|
444
477
|
}
|
|
445
|
-
const timeoutMs = (_a = options === null || options === void 0 ? void 0 : options.timeoutMs) !== null && _a !== void 0 ? _a : LOWLEVEL_PROTOCOL_TIMEOUT_MS;
|
|
446
478
|
try {
|
|
447
|
-
return yield this.protocolV2Links.call(uuid, () => this.createProtocolV2Adapter(uuid), name, data,
|
|
479
|
+
return yield this.protocolV2Links.call(uuid, () => this.createProtocolV2Adapter(uuid), name, data, options);
|
|
448
480
|
}
|
|
449
481
|
catch (e) {
|
|
450
482
|
this.Log.error('lowlevel Protocol V2 call error: ', e);
|
|
@@ -469,10 +501,7 @@ class LowlevelTransport {
|
|
|
469
501
|
assertCurrentGeneration();
|
|
470
502
|
(_a = this.protocolV2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
|
|
471
503
|
},
|
|
472
|
-
writeFrame: (frame) =>
|
|
473
|
-
assertCurrentGeneration();
|
|
474
|
-
return this.writeProtocolV2Frame(uuid, frame);
|
|
475
|
-
},
|
|
504
|
+
writeFrame: (frame, context) => this.writeProtocolV2Frame(uuid, frame, context, assertCurrentGeneration),
|
|
476
505
|
readFrame: (context) => {
|
|
477
506
|
assertCurrentGeneration();
|
|
478
507
|
return this.readProtocolV2Frame(uuid, context.timeoutMs, context.messageName);
|
package/dist/types.d.ts
CHANGED
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAE3D,MAAM,MAAM,oBAAoB,GAAG;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,gBAAgB,CAAC,EAAE,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAE3D,MAAM,MAAM,oBAAoB,GAAG;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,gBAAgB,CAAC,EAAE,YAAY,CAAC;IAChC,YAAY,CAAC,EAAE,YAAY,CAAC;CAC7B,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@onekeyfe/hd-transport-lowlevel",
|
|
3
|
-
"version": "1.2.0-alpha.
|
|
3
|
+
"version": "1.2.0-alpha.181",
|
|
4
4
|
"homepage": "https://github.com/OneKeyHQ/hardware-js-sdk#readme",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -20,8 +20,8 @@
|
|
|
20
20
|
"lint:fix": "eslint . --fix"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@onekeyfe/hd-shared": "1.2.0-alpha.
|
|
24
|
-
"@onekeyfe/hd-transport": "1.2.0-alpha.
|
|
23
|
+
"@onekeyfe/hd-shared": "1.2.0-alpha.181",
|
|
24
|
+
"@onekeyfe/hd-transport": "1.2.0-alpha.181"
|
|
25
25
|
},
|
|
26
|
-
"gitHead": "
|
|
26
|
+
"gitHead": "cb79652ebe130ceb01490195aa8903715f267e36"
|
|
27
27
|
}
|
package/src/index.ts
CHANGED
|
@@ -10,13 +10,14 @@ import transport, {
|
|
|
10
10
|
hexToBytes,
|
|
11
11
|
probeProtocolV2 as probeProtocolV2Helper,
|
|
12
12
|
withProtocolTimeout,
|
|
13
|
+
writeProtocolV2BleFrame,
|
|
13
14
|
} from '@onekeyfe/hd-transport';
|
|
14
15
|
|
|
15
16
|
import type EventEmitter from 'events';
|
|
16
17
|
import type {
|
|
17
|
-
LowLevelDevice,
|
|
18
18
|
LowlevelTransportSharedPlugin,
|
|
19
19
|
ProtocolType,
|
|
20
|
+
ProtocolV2CallContext,
|
|
20
21
|
TransportCallOptions,
|
|
21
22
|
} from '@onekeyfe/hd-transport';
|
|
22
23
|
import type { LowLevelAcquireInput } from './types';
|
|
@@ -25,7 +26,6 @@ const { check, ProtocolV1, parseConfigure } = transport;
|
|
|
25
26
|
|
|
26
27
|
const PROTOCOL_PROBE_TIMEOUT_MS = 1000;
|
|
27
28
|
const PROTOCOL_V2_PROBE_TIMEOUT_MS = 5000;
|
|
28
|
-
const LOWLEVEL_PROTOCOL_TIMEOUT_MS = 30_000;
|
|
29
29
|
const LOWLEVEL_PROTOCOL_V2_PACKET_LENGTH = 64;
|
|
30
30
|
const FIRMWARE_UPLOAD_LOG_PERCENT_STEP = 5;
|
|
31
31
|
const FIRMWARE_UPLOAD_LOG_INTERVAL_MS = 10_000;
|
|
@@ -52,10 +52,6 @@ export function getProtocolV1SendOptions(name: string) {
|
|
|
52
52
|
return name === 'FirmwareUpload' ? { withoutResponse: false } : undefined;
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
function inferProtocolHintFromDeviceName(name?: string | null): ProtocolType | undefined {
|
|
56
|
-
return /\bpro\s*2\b/i.test(name ?? '') ? 'V2' : undefined;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
55
|
function isProtocolV1TransportChunk(data: Uint8Array) {
|
|
60
56
|
return data.length >= 9 && data[0] === 0x3f && data[1] === 0x23 && data[2] === 0x23;
|
|
61
57
|
}
|
|
@@ -118,6 +114,8 @@ export default class LowlevelTransport {
|
|
|
118
114
|
},
|
|
119
115
|
});
|
|
120
116
|
|
|
117
|
+
private protocolV2SchemaConfiguration: string | undefined;
|
|
118
|
+
|
|
121
119
|
getProtocolType(path: string): ProtocolType | undefined {
|
|
122
120
|
return this.deviceProtocol.get(path);
|
|
123
121
|
}
|
|
@@ -136,10 +134,20 @@ export default class LowlevelTransport {
|
|
|
136
134
|
}
|
|
137
135
|
|
|
138
136
|
configureProtocolV2(signedData: any) {
|
|
137
|
+
const configuration = typeof signedData === 'string' ? signedData : JSON.stringify(signedData);
|
|
138
|
+
if (this.protocolV2SchemaConfiguration === configuration) {
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const isReconfiguration = this.protocolV2SchemaConfiguration !== undefined;
|
|
139
143
|
this._messagesV2 = parseConfigure(signedData);
|
|
140
|
-
this.
|
|
141
|
-
|
|
142
|
-
|
|
144
|
+
this.protocolV2SchemaConfiguration = configuration;
|
|
145
|
+
|
|
146
|
+
if (isReconfiguration) {
|
|
147
|
+
this.protocolV2Links
|
|
148
|
+
.invalidateAllLinks('Protocol V2 schema reconfigured')
|
|
149
|
+
.catch(error => this.Log?.debug('Protocol V2 schema link cleanup failed:', error));
|
|
150
|
+
}
|
|
143
151
|
}
|
|
144
152
|
|
|
145
153
|
listen() {
|
|
@@ -148,13 +156,7 @@ export default class LowlevelTransport {
|
|
|
148
156
|
|
|
149
157
|
async enumerate() {
|
|
150
158
|
const devices = await this.plugin.enumerate();
|
|
151
|
-
return devices
|
|
152
|
-
const protocolHint = inferProtocolHintFromDeviceName(device.name);
|
|
153
|
-
if (protocolHint) {
|
|
154
|
-
this.deviceProtocolHints.set(device.id, protocolHint);
|
|
155
|
-
}
|
|
156
|
-
return device;
|
|
157
|
-
});
|
|
159
|
+
return devices;
|
|
158
160
|
}
|
|
159
161
|
|
|
160
162
|
async acquire(input: LowLevelAcquireInput) {
|
|
@@ -174,16 +176,38 @@ export default class LowlevelTransport {
|
|
|
174
176
|
);
|
|
175
177
|
}
|
|
176
178
|
|
|
177
|
-
this.protocolV2Assemblers.set(
|
|
178
|
-
const protocolHint = input.expectedProtocol
|
|
179
|
-
? undefined
|
|
180
|
-
: this.deviceProtocolHints.get(input.uuid);
|
|
181
|
-
const protocolType = await this.detectProtocol(
|
|
179
|
+
this.protocolV2Assemblers.set(
|
|
182
180
|
input.uuid,
|
|
183
|
-
|
|
184
|
-
protocolHint
|
|
181
|
+
new ProtocolV2FrameAssembler(PROTOCOL_V2_BLE_FRAME_MAX_BYTES)
|
|
185
182
|
);
|
|
186
|
-
|
|
183
|
+
const protocolHint = input.expectedProtocol
|
|
184
|
+
? undefined
|
|
185
|
+
: input.protocolHint ?? this.deviceProtocolHints.get(input.uuid);
|
|
186
|
+
try {
|
|
187
|
+
const protocolType = await this.detectProtocol(
|
|
188
|
+
input.uuid,
|
|
189
|
+
input.expectedProtocol,
|
|
190
|
+
protocolHint
|
|
191
|
+
);
|
|
192
|
+
return { uuid: input.uuid, protocolType };
|
|
193
|
+
} catch (error) {
|
|
194
|
+
try {
|
|
195
|
+
await this.protocolV2Links.invalidateLink(input.uuid, 'Lowlevel transport acquire failed');
|
|
196
|
+
} catch (cleanupError) {
|
|
197
|
+
this.Log?.debug('[LowlevelTransport] acquire link cleanup failed:', cleanupError);
|
|
198
|
+
}
|
|
199
|
+
try {
|
|
200
|
+
await this.plugin.disconnect(input.uuid);
|
|
201
|
+
} catch (cleanupError) {
|
|
202
|
+
this.Log?.debug('[LowlevelTransport] acquire disconnect failed:', cleanupError);
|
|
203
|
+
} finally {
|
|
204
|
+
this.connectedDevices.delete(input.uuid);
|
|
205
|
+
this.deviceProtocol.delete(input.uuid);
|
|
206
|
+
this.protocolV2Assemblers.delete(input.uuid);
|
|
207
|
+
this.advanceProtocolV2Generation(input.uuid);
|
|
208
|
+
}
|
|
209
|
+
throw error;
|
|
210
|
+
}
|
|
187
211
|
}
|
|
188
212
|
|
|
189
213
|
async release(uuid: string) {
|
|
@@ -192,8 +216,7 @@ export default class LowlevelTransport {
|
|
|
192
216
|
await this.plugin.disconnect(uuid);
|
|
193
217
|
this.connectedDevices.delete(uuid);
|
|
194
218
|
this.deviceProtocol.delete(uuid);
|
|
195
|
-
//
|
|
196
|
-
// 直接执行 Protocol V2 探测,避免先发送一次无意义的 V1 Initialize。
|
|
219
|
+
// Confirmed protocol stays on the device endpoint; BLE names are not a probe hint.
|
|
197
220
|
this.protocolV2Assemblers.delete(uuid);
|
|
198
221
|
return true;
|
|
199
222
|
} catch (error) {
|
|
@@ -219,8 +242,6 @@ export default class LowlevelTransport {
|
|
|
219
242
|
`Device protocol has not been detected for ${uuid}`
|
|
220
243
|
);
|
|
221
244
|
}
|
|
222
|
-
this.Log.debug('transport call', { name, protocol });
|
|
223
|
-
|
|
224
245
|
if (protocol === 'V2') {
|
|
225
246
|
return this.callProtocolV2(uuid, name, data, options);
|
|
226
247
|
}
|
|
@@ -228,6 +249,19 @@ export default class LowlevelTransport {
|
|
|
228
249
|
return this.callProtocolV1(uuid, name, data, options);
|
|
229
250
|
}
|
|
230
251
|
|
|
252
|
+
async post(uuid: string, name: string, data: Record<string, unknown>) {
|
|
253
|
+
if (this.getProtocolType(uuid) === 'V2') {
|
|
254
|
+
await this.protocolV2Links.sendFlowControl(
|
|
255
|
+
uuid,
|
|
256
|
+
() => this.createProtocolV2Adapter(uuid),
|
|
257
|
+
name,
|
|
258
|
+
data
|
|
259
|
+
);
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
await this.callProtocolV1(uuid, name, data);
|
|
263
|
+
}
|
|
264
|
+
|
|
231
265
|
private async callProtocolV1(
|
|
232
266
|
uuid: string,
|
|
233
267
|
name: string,
|
|
@@ -286,6 +320,16 @@ export default class LowlevelTransport {
|
|
|
286
320
|
const jsonData = ProtocolV1.decodeMessage(messages, response);
|
|
287
321
|
return check.call(jsonData);
|
|
288
322
|
} catch (e) {
|
|
323
|
+
if (
|
|
324
|
+
e?.errorCode === HardwareErrorCode.BleTimeoutError &&
|
|
325
|
+
options?.timeoutMs !== PROTOCOL_PROBE_TIMEOUT_MS
|
|
326
|
+
) {
|
|
327
|
+
try {
|
|
328
|
+
await this.resetConnectionAfterProbe(uuid, 'V1');
|
|
329
|
+
} catch (resetError) {
|
|
330
|
+
this.Log.debug('[LowlevelTransport] reset after Protocol V1 timeout failed:', resetError);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
289
333
|
if (name === 'Initialize' && options?.timeoutMs === PROTOCOL_PROBE_TIMEOUT_MS) {
|
|
290
334
|
this.Log.debug('[LowlevelTransport] Protocol V1 Initialize probe call failed:', e);
|
|
291
335
|
} else {
|
|
@@ -328,12 +372,12 @@ export default class LowlevelTransport {
|
|
|
328
372
|
protocolHint?: ProtocolType
|
|
329
373
|
): Promise<ProtocolType> {
|
|
330
374
|
if (expectedProtocol === 'V2') {
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
375
|
+
if (await this.probeProtocolV2(uuid)) {
|
|
376
|
+
this.deviceProtocol.set(uuid, 'V2');
|
|
377
|
+
this.Log?.debug(`[LowlevelTransport] detectProtocol: uuid=${uuid} -> V2 (expected)`);
|
|
378
|
+
return 'V2';
|
|
379
|
+
}
|
|
380
|
+
throw this.createProtocolMismatchError(expectedProtocol);
|
|
337
381
|
}
|
|
338
382
|
|
|
339
383
|
if (expectedProtocol === 'V1') {
|
|
@@ -345,31 +389,20 @@ export default class LowlevelTransport {
|
|
|
345
389
|
throw this.createProtocolMismatchError(expectedProtocol);
|
|
346
390
|
}
|
|
347
391
|
|
|
348
|
-
|
|
349
|
-
this.deviceProtocol.
|
|
350
|
-
this.Log?.debug(`[LowlevelTransport] detectProtocol: uuid=${uuid} -> V2 (hint)`);
|
|
351
|
-
return 'V2';
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
const cachedProtocol = this.deviceProtocol.get(uuid);
|
|
355
|
-
if (cachedProtocol === 'V2' && (await this.probeProtocolV2(uuid))) {
|
|
356
|
-
this.deviceProtocol.set(uuid, 'V2');
|
|
357
|
-
this.Log?.debug(`[LowlevelTransport] detectProtocol: uuid=${uuid} -> V2 (cached)`);
|
|
358
|
-
return 'V2';
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
const protocolV1Detected = await this.probeProtocolV1(uuid);
|
|
362
|
-
if (protocolV1Detected) {
|
|
363
|
-
this.deviceProtocol.set(uuid, 'V1');
|
|
364
|
-
this.Log?.debug(`[LowlevelTransport] detectProtocol: uuid=${uuid} -> V1`);
|
|
365
|
-
return 'V1';
|
|
366
|
-
}
|
|
392
|
+
const probeOrder: ProtocolType[] =
|
|
393
|
+
protocolHint === 'V2' || this.deviceProtocol.get(uuid) === 'V2' ? ['V2', 'V1'] : ['V1', 'V2'];
|
|
367
394
|
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
395
|
+
for (const [index, protocol] of probeOrder.entries()) {
|
|
396
|
+
if (index > 0) {
|
|
397
|
+
await this.resetConnectionAfterProbe(uuid, probeOrder[index - 1]);
|
|
398
|
+
}
|
|
399
|
+
const detected =
|
|
400
|
+
protocol === 'V1' ? await this.probeProtocolV1(uuid) : await this.probeProtocolV2(uuid);
|
|
401
|
+
if (detected) {
|
|
402
|
+
this.deviceProtocol.set(uuid, protocol);
|
|
403
|
+
this.Log?.debug(`[LowlevelTransport] detectProtocol: uuid=${uuid} -> ${protocol}`);
|
|
404
|
+
return protocol;
|
|
405
|
+
}
|
|
373
406
|
}
|
|
374
407
|
|
|
375
408
|
this.deviceProtocol.delete(uuid);
|
|
@@ -486,7 +519,7 @@ export default class LowlevelTransport {
|
|
|
486
519
|
private async readProtocolV2Frame(uuid: string, timeoutMs?: number, commandName = 'ProtocolV2') {
|
|
487
520
|
let assembler = this.protocolV2Assemblers.get(uuid);
|
|
488
521
|
if (!assembler) {
|
|
489
|
-
assembler = new ProtocolV2FrameAssembler();
|
|
522
|
+
assembler = new ProtocolV2FrameAssembler(PROTOCOL_V2_BLE_FRAME_MAX_BYTES);
|
|
490
523
|
this.protocolV2Assemblers.set(uuid, assembler);
|
|
491
524
|
}
|
|
492
525
|
|
|
@@ -504,11 +537,20 @@ export default class LowlevelTransport {
|
|
|
504
537
|
return frame;
|
|
505
538
|
}
|
|
506
539
|
|
|
507
|
-
private async writeProtocolV2Frame(
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
540
|
+
private async writeProtocolV2Frame(
|
|
541
|
+
uuid: string,
|
|
542
|
+
frame: Uint8Array,
|
|
543
|
+
context: ProtocolV2CallContext,
|
|
544
|
+
assertCurrentGeneration: () => void
|
|
545
|
+
) {
|
|
546
|
+
await writeProtocolV2BleFrame({
|
|
547
|
+
frame,
|
|
548
|
+
packetCapacity: LOWLEVEL_PROTOCOL_V2_PACKET_LENGTH,
|
|
549
|
+
assertActive: assertCurrentGeneration,
|
|
550
|
+
signal: context.signal,
|
|
551
|
+
abortMessage: `Protocol V2 BLE write aborted for ${context.messageName}`,
|
|
552
|
+
writePacket: packet => this.plugin.send(uuid, bytesToHex(packet)),
|
|
553
|
+
});
|
|
512
554
|
}
|
|
513
555
|
|
|
514
556
|
private async callProtocolV2(
|
|
@@ -521,18 +563,13 @@ export default class LowlevelTransport {
|
|
|
521
563
|
throw ERRORS.TypedError(HardwareErrorCode.TransportNotConfigured);
|
|
522
564
|
}
|
|
523
565
|
|
|
524
|
-
const timeoutMs = options?.timeoutMs ?? LOWLEVEL_PROTOCOL_TIMEOUT_MS;
|
|
525
|
-
|
|
526
566
|
try {
|
|
527
567
|
return await this.protocolV2Links.call(
|
|
528
568
|
uuid,
|
|
529
569
|
() => this.createProtocolV2Adapter(uuid),
|
|
530
570
|
name,
|
|
531
571
|
data,
|
|
532
|
-
|
|
533
|
-
...options,
|
|
534
|
-
timeoutMs,
|
|
535
|
-
}
|
|
572
|
+
options
|
|
536
573
|
);
|
|
537
574
|
} catch (e) {
|
|
538
575
|
this.Log.error('lowlevel Protocol V2 call error: ', e);
|
|
@@ -556,10 +593,8 @@ export default class LowlevelTransport {
|
|
|
556
593
|
assertCurrentGeneration();
|
|
557
594
|
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
558
595
|
},
|
|
559
|
-
writeFrame: (frame: Uint8Array) =>
|
|
560
|
-
assertCurrentGeneration
|
|
561
|
-
return this.writeProtocolV2Frame(uuid, frame);
|
|
562
|
-
},
|
|
596
|
+
writeFrame: (frame: Uint8Array, context: ProtocolV2CallContext) =>
|
|
597
|
+
this.writeProtocolV2Frame(uuid, frame, context, assertCurrentGeneration),
|
|
563
598
|
readFrame: (context: { messageName: string; timeoutMs?: number }) => {
|
|
564
599
|
assertCurrentGeneration();
|
|
565
600
|
return this.readProtocolV2Frame(uuid, context.timeoutMs, context.messageName);
|