@onekeyfe/hd-transport-lowlevel 1.2.0-alpha.8 → 1.2.0-alpha.9
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 +102 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +100 -31
- package/package.json +4 -4
- package/src/index.ts +121 -35
|
@@ -196,6 +196,10 @@ describe('LowlevelTransport protocol framing', () => {
|
|
|
196
196
|
},
|
|
197
197
|
});
|
|
198
198
|
expect(plugin.send).toHaveBeenCalled();
|
|
199
|
+
const sentSeqs = plugin.send.mock.calls.map(([, hex]) =>
|
|
200
|
+
Number.parseInt(hex.slice(12, 14), 16)
|
|
201
|
+
);
|
|
202
|
+
expect(sentSeqs).toEqual([1, 2]);
|
|
199
203
|
});
|
|
200
204
|
|
|
201
205
|
test('falls back to Protocol V2 probe for unnamed Protocol V2 devices', async () => {
|
|
@@ -218,6 +222,87 @@ describe('LowlevelTransport protocol framing', () => {
|
|
|
218
222
|
expect(lowlevel.getProtocolType('unknown-pro2-id')).toBe('V2');
|
|
219
223
|
});
|
|
220
224
|
|
|
225
|
+
test('retains the Protocol V2 hint and sequence cursor across release and reacquire', async () => {
|
|
226
|
+
const probeResponse = ProtocolV2.encodeFrame(
|
|
227
|
+
schemas,
|
|
228
|
+
'Success',
|
|
229
|
+
{ message: 'ok' },
|
|
230
|
+
{ router: PROTOCOL_V2_CHANNEL_BLE_UART }
|
|
231
|
+
);
|
|
232
|
+
const plugin = createPlugin({
|
|
233
|
+
devices: [{ id: 'reconnect-pro2-id', name: 'OneKey Pro 2', commType: 'ble' }],
|
|
234
|
+
responses: [bytesToHex(probeResponse), bytesToHex(probeResponse)],
|
|
235
|
+
});
|
|
236
|
+
const lowlevel = configureTransport(plugin);
|
|
237
|
+
|
|
238
|
+
await lowlevel.enumerate();
|
|
239
|
+
await expect(lowlevel.acquire({ uuid: 'reconnect-pro2-id' })).resolves.toEqual({
|
|
240
|
+
uuid: 'reconnect-pro2-id',
|
|
241
|
+
protocolType: 'V2',
|
|
242
|
+
});
|
|
243
|
+
await lowlevel.release('reconnect-pro2-id');
|
|
244
|
+
await expect(lowlevel.acquire({ uuid: 'reconnect-pro2-id' })).resolves.toEqual({
|
|
245
|
+
uuid: 'reconnect-pro2-id',
|
|
246
|
+
protocolType: 'V2',
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
const sentSeqs = plugin.send.mock.calls.map(([, hex]) =>
|
|
250
|
+
Number.parseInt(hex.slice(12, 14), 16)
|
|
251
|
+
);
|
|
252
|
+
expect(sentSeqs).toEqual([1, 2]);
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
test('reuses the active generation when Core acquires the same BLE connection again', async () => {
|
|
256
|
+
const probeResponse = ProtocolV2.encodeFrame(
|
|
257
|
+
schemas,
|
|
258
|
+
'Success',
|
|
259
|
+
{ message: 'ok' },
|
|
260
|
+
{ router: PROTOCOL_V2_CHANNEL_BLE_UART }
|
|
261
|
+
);
|
|
262
|
+
const plugin = createPlugin({
|
|
263
|
+
devices: [{ id: 'repeated-acquire-id', name: 'OneKey Pro 2', commType: 'ble' }],
|
|
264
|
+
responses: [bytesToHex(probeResponse), bytesToHex(probeResponse)],
|
|
265
|
+
});
|
|
266
|
+
const lowlevel = configureTransport(plugin);
|
|
267
|
+
|
|
268
|
+
await expect(
|
|
269
|
+
lowlevel.acquire({ uuid: 'repeated-acquire-id', expectedProtocol: 'V2' })
|
|
270
|
+
).resolves.toEqual({
|
|
271
|
+
uuid: 'repeated-acquire-id',
|
|
272
|
+
protocolType: 'V2',
|
|
273
|
+
});
|
|
274
|
+
await lowlevel.call('repeated-acquire-id', 'Ping', { message: 'first-acquire' });
|
|
275
|
+
await expect(
|
|
276
|
+
lowlevel.acquire({ uuid: 'repeated-acquire-id', expectedProtocol: 'V2' })
|
|
277
|
+
).resolves.toEqual({
|
|
278
|
+
uuid: 'repeated-acquire-id',
|
|
279
|
+
protocolType: 'V2',
|
|
280
|
+
});
|
|
281
|
+
await lowlevel.call('repeated-acquire-id', 'Ping', { message: 'second-acquire' });
|
|
282
|
+
|
|
283
|
+
const sentSeqs = plugin.send.mock.calls.map(([, hex]) =>
|
|
284
|
+
Number.parseInt(hex.slice(12, 14), 16)
|
|
285
|
+
);
|
|
286
|
+
expect(sentSeqs).toEqual([1, 2]);
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
test('trusts explicit Protocol V2 during bootloader reconnect without probing Ping', async () => {
|
|
290
|
+
const plugin = createPlugin({
|
|
291
|
+
devices: [{ id: 'bootloader-v2-id', name: 'OneKey Pro 2', commType: 'ble' }],
|
|
292
|
+
responses: [],
|
|
293
|
+
});
|
|
294
|
+
const lowlevel = configureTransport(plugin);
|
|
295
|
+
|
|
296
|
+
await expect(
|
|
297
|
+
lowlevel.acquire({ uuid: 'bootloader-v2-id', expectedProtocol: 'V2' })
|
|
298
|
+
).resolves.toEqual({
|
|
299
|
+
uuid: 'bootloader-v2-id',
|
|
300
|
+
protocolType: 'V2',
|
|
301
|
+
});
|
|
302
|
+
expect(plugin.send).not.toHaveBeenCalled();
|
|
303
|
+
expect(plugin.receive).not.toHaveBeenCalled();
|
|
304
|
+
});
|
|
305
|
+
|
|
221
306
|
test('resets the lowlevel connection before probing Protocol V2 after a V1 timeout', async () => {
|
|
222
307
|
const probeResponse = ProtocolV2.encodeFrame(
|
|
223
308
|
schemas,
|
|
@@ -256,6 +341,23 @@ describe('LowlevelTransport protocol framing', () => {
|
|
|
256
341
|
expect(plugin.connect).toHaveBeenCalledTimes(2);
|
|
257
342
|
});
|
|
258
343
|
|
|
344
|
+
test('disconnects a tainted Protocol V2 link after a response timeout', async () => {
|
|
345
|
+
const plugin = createPlugin({
|
|
346
|
+
devices: [{ id: 'timeout-v2-id', name: 'OneKey Pro 2', commType: 'ble' }],
|
|
347
|
+
responses: [],
|
|
348
|
+
});
|
|
349
|
+
plugin.receive.mockImplementation(() => new Promise(() => {}));
|
|
350
|
+
const lowlevel = configureTransport(plugin);
|
|
351
|
+
|
|
352
|
+
await lowlevel.acquire({ uuid: 'timeout-v2-id', expectedProtocol: 'V2' });
|
|
353
|
+
plugin.disconnect.mockClear();
|
|
354
|
+
await expect(
|
|
355
|
+
lowlevel.call('timeout-v2-id', 'Ping', { message: 'timeout' }, { timeoutMs: 10 })
|
|
356
|
+
).rejects.toThrow('Lowlevel response timeout after 10ms for Ping');
|
|
357
|
+
|
|
358
|
+
expect(plugin.disconnect).toHaveBeenCalledWith('timeout-v2-id');
|
|
359
|
+
});
|
|
360
|
+
|
|
259
361
|
test('verifies expected Protocol V1 instead of trusting the requested protocol', async () => {
|
|
260
362
|
const plugin = createPlugin({
|
|
261
363
|
devices: [{ id: 'v2-id', name: 'Unknown BLE Device', commType: 'ble' }],
|
package/dist/index.d.ts
CHANGED
|
@@ -17,6 +17,9 @@ declare class LowlevelTransport {
|
|
|
17
17
|
private deviceProtocol;
|
|
18
18
|
private deviceProtocolHints;
|
|
19
19
|
private protocolV2Assemblers;
|
|
20
|
+
private protocolV2Generations;
|
|
21
|
+
private connectedDevices;
|
|
22
|
+
private protocolV2Links;
|
|
20
23
|
getProtocolType(path: string): ProtocolType | undefined;
|
|
21
24
|
init(logger: any, emitter: EventEmitter, plugin: LowlevelTransportSharedPlugin): void;
|
|
22
25
|
configure(signedData: any): void;
|
|
@@ -43,6 +46,8 @@ declare class LowlevelTransport {
|
|
|
43
46
|
private readProtocolV2Frame;
|
|
44
47
|
private writeProtocolV2Frame;
|
|
45
48
|
private callProtocolV2;
|
|
49
|
+
private createProtocolV2Adapter;
|
|
50
|
+
private advanceProtocolV2Generation;
|
|
46
51
|
cancel(): void;
|
|
47
52
|
}
|
|
48
53
|
|
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,cAAc,EACd,6BAA6B,EAC7B,YAAY,EACZ,oBAAoB,EACrB,MAAM,wBAAwB,CAAC;AAChC,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC;AAqBpD,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,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;IAOnC,MAAM;IAIA,SAAS;IAWT,OAAO,CAAC,KAAK,EAAE,oBAAoB;;;;IA6BnC,OAAO,CAAC,IAAI,EAAE,MAAM;IAgBpB,IAAI,CACR,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,OAAO,CAAC,EAAE,oBAAoB;YAmClB,cAAc;IAuC5B,OAAO,CAAC,0BAA0B;IAOlC,OAAO,CAAC,2BAA2B;IAOnC,OAAO,CAAC,4BAA4B;IAOpC,OAAO,CAAC,kBAAkB;YAMZ,cAAc;YAsDd,yBAAyB;YAiCzB,eAAe;YAgBf,eAAe;YA6Bf,UAAU;YAUV,qBAAqB;YAmBrB,mBAAmB;YAqBnB,oBAAoB;YAOpB,cAAc;IA6B5B,OAAO,CAAC,uBAAuB;IAkC/B,OAAO,CAAC,2BAA2B;IAMnC,MAAM;CAGP"}
|
package/dist/index.js
CHANGED
|
@@ -58,6 +58,38 @@ class LowlevelTransport {
|
|
|
58
58
|
this.deviceProtocol = new Map();
|
|
59
59
|
this.deviceProtocolHints = new Map();
|
|
60
60
|
this.protocolV2Assemblers = new Map();
|
|
61
|
+
this.protocolV2Generations = new Map();
|
|
62
|
+
this.connectedDevices = new Set();
|
|
63
|
+
this.protocolV2Links = new transport.ProtocolV2LinkManager({
|
|
64
|
+
getSchemas: () => {
|
|
65
|
+
if (!this._messages || !this._messagesV2) {
|
|
66
|
+
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured);
|
|
67
|
+
}
|
|
68
|
+
return {
|
|
69
|
+
protocolV1: this._messages,
|
|
70
|
+
protocolV2: this._messagesV2,
|
|
71
|
+
};
|
|
72
|
+
},
|
|
73
|
+
classifyError: () => 'link-fatal',
|
|
74
|
+
onLinkInvalidated: (uuid, reason) => __awaiter(this, void 0, void 0, function* () {
|
|
75
|
+
var _a, _b, _c;
|
|
76
|
+
(_a = this.protocolV2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
|
|
77
|
+
(_b = this.Log) === null || _b === void 0 ? void 0 : _b.debug(`[LowlevelTransport] Protocol V2 link invalidated: ${uuid}`, reason);
|
|
78
|
+
if (reason.startsWith('Protocol V2 link-fatal error:')) {
|
|
79
|
+
this.deviceProtocol.delete(uuid);
|
|
80
|
+
this.advanceProtocolV2Generation(uuid);
|
|
81
|
+
try {
|
|
82
|
+
yield this.plugin.disconnect(uuid);
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
(_c = this.Log) === null || _c === void 0 ? void 0 : _c.debug(`[LowlevelTransport] disconnect tainted Protocol V2 link failed: ${uuid}`, error);
|
|
86
|
+
}
|
|
87
|
+
finally {
|
|
88
|
+
this.connectedDevices.delete(uuid);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}),
|
|
92
|
+
});
|
|
61
93
|
}
|
|
62
94
|
getProtocolType(path) {
|
|
63
95
|
return this.deviceProtocol.get(path);
|
|
@@ -75,6 +107,9 @@ class LowlevelTransport {
|
|
|
75
107
|
}
|
|
76
108
|
configureProtocolV2(signedData) {
|
|
77
109
|
this._messagesV2 = parseConfigure(signedData);
|
|
110
|
+
this.protocolV2Links
|
|
111
|
+
.invalidateAllLinks('Protocol V2 schema reconfigured')
|
|
112
|
+
.catch(error => { var _a; return (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('Protocol V2 schema link cleanup failed:', error); });
|
|
78
113
|
}
|
|
79
114
|
listen() {
|
|
80
115
|
}
|
|
@@ -93,10 +128,16 @@ class LowlevelTransport {
|
|
|
93
128
|
acquire(input) {
|
|
94
129
|
var _a;
|
|
95
130
|
return __awaiter(this, void 0, void 0, function* () {
|
|
131
|
+
const alreadyConnected = this.connectedDevices.has(input.uuid);
|
|
96
132
|
try {
|
|
97
133
|
yield this.plugin.connect(input.uuid);
|
|
134
|
+
if (!alreadyConnected) {
|
|
135
|
+
this.connectedDevices.add(input.uuid);
|
|
136
|
+
this.advanceProtocolV2Generation(input.uuid);
|
|
137
|
+
}
|
|
98
138
|
}
|
|
99
139
|
catch (error) {
|
|
140
|
+
this.connectedDevices.delete(input.uuid);
|
|
100
141
|
this.Log.debug('lowlelvel transport connect error: ', error);
|
|
101
142
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.LowlevelTrasnportConnectError, (_a = error.message) !== null && _a !== void 0 ? _a : error);
|
|
102
143
|
}
|
|
@@ -111,9 +152,10 @@ class LowlevelTransport {
|
|
|
111
152
|
release(uuid) {
|
|
112
153
|
return __awaiter(this, void 0, void 0, function* () {
|
|
113
154
|
try {
|
|
155
|
+
yield this.protocolV2Links.invalidateLink(uuid, 'Lowlevel transport released');
|
|
114
156
|
yield this.plugin.disconnect(uuid);
|
|
157
|
+
this.connectedDevices.delete(uuid);
|
|
115
158
|
this.deviceProtocol.delete(uuid);
|
|
116
|
-
this.deviceProtocolHints.delete(uuid);
|
|
117
159
|
this.protocolV2Assemblers.delete(uuid);
|
|
118
160
|
return true;
|
|
119
161
|
}
|
|
@@ -163,7 +205,7 @@ class LowlevelTransport {
|
|
|
163
205
|
}
|
|
164
206
|
}
|
|
165
207
|
try {
|
|
166
|
-
const response = yield this.readProtocolV1Message(options === null || options === void 0 ? void 0 : options.timeoutMs);
|
|
208
|
+
const response = yield this.readProtocolV1Message(uuid, options === null || options === void 0 ? void 0 : options.timeoutMs);
|
|
167
209
|
this.Log.debug('receive data: ', response);
|
|
168
210
|
const jsonData = ProtocolV1.decodeMessage(messages, response);
|
|
169
211
|
return check.call(jsonData);
|
|
@@ -197,12 +239,9 @@ class LowlevelTransport {
|
|
|
197
239
|
var _a, _b, _c, _d, _e, _f;
|
|
198
240
|
return __awaiter(this, void 0, void 0, function* () {
|
|
199
241
|
if (expectedProtocol === 'V2') {
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
return 'V2';
|
|
204
|
-
}
|
|
205
|
-
throw this.createProtocolMismatchError(expectedProtocol);
|
|
242
|
+
this.deviceProtocol.set(uuid, 'V2');
|
|
243
|
+
(_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug(`[LowlevelTransport] detectProtocol: uuid=${uuid} -> V2 (expected)`);
|
|
244
|
+
return 'V2';
|
|
206
245
|
}
|
|
207
246
|
if (expectedProtocol === 'V1') {
|
|
208
247
|
if (yield this.probeProtocolV1(uuid)) {
|
|
@@ -242,8 +281,10 @@ class LowlevelTransport {
|
|
|
242
281
|
resetConnectionAfterProbe(uuid, protocol) {
|
|
243
282
|
var _a, _b, _c, _d;
|
|
244
283
|
return __awaiter(this, void 0, void 0, function* () {
|
|
284
|
+
yield this.protocolV2Links.invalidateLink(uuid, `Reset connection after Protocol ${protocol} probe`);
|
|
245
285
|
(_a = this.protocolV2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
|
|
246
286
|
try {
|
|
287
|
+
this.connectedDevices.delete(uuid);
|
|
247
288
|
yield this.plugin.disconnect(uuid);
|
|
248
289
|
}
|
|
249
290
|
catch (error) {
|
|
@@ -251,6 +292,8 @@ class LowlevelTransport {
|
|
|
251
292
|
}
|
|
252
293
|
try {
|
|
253
294
|
yield this.plugin.connect(uuid);
|
|
295
|
+
this.connectedDevices.add(uuid);
|
|
296
|
+
this.advanceProtocolV2Generation(uuid);
|
|
254
297
|
}
|
|
255
298
|
catch (error) {
|
|
256
299
|
(_c = this.Log) === null || _c === void 0 ? void 0 : _c.debug(`[LowlevelTransport] reconnect after Protocol ${protocol} probe failed:`, error);
|
|
@@ -307,18 +350,18 @@ class LowlevelTransport {
|
|
|
307
350
|
}
|
|
308
351
|
});
|
|
309
352
|
}
|
|
310
|
-
receiveHex(timeoutMs, commandName) {
|
|
353
|
+
receiveHex(uuid, timeoutMs, commandName) {
|
|
311
354
|
return __awaiter(this, void 0, void 0, function* () {
|
|
312
|
-
const response = yield transport.withProtocolTimeout(this.plugin.receive(), timeoutMs, () => this.createProtocolTimeoutError(commandName, timeoutMs !== null && timeoutMs !== void 0 ? timeoutMs : 0));
|
|
355
|
+
const response = yield transport.withProtocolTimeout(this.plugin.receive(uuid), timeoutMs, () => this.createProtocolTimeoutError(commandName, timeoutMs !== null && timeoutMs !== void 0 ? timeoutMs : 0));
|
|
313
356
|
if (typeof response !== 'string') {
|
|
314
357
|
throw new Error('Returning data is not string');
|
|
315
358
|
}
|
|
316
359
|
return response;
|
|
317
360
|
});
|
|
318
361
|
}
|
|
319
|
-
readProtocolV1Message(timeoutMs) {
|
|
362
|
+
readProtocolV1Message(uuid, timeoutMs) {
|
|
320
363
|
return __awaiter(this, void 0, void 0, function* () {
|
|
321
|
-
const first = yield this.receiveHex(timeoutMs, 'ProtocolV1');
|
|
364
|
+
const first = yield this.receiveHex(uuid, timeoutMs, 'ProtocolV1');
|
|
322
365
|
const firstData = transport.hexToBytes(first);
|
|
323
366
|
if (!isProtocolV1TransportChunk(firstData)) {
|
|
324
367
|
return first;
|
|
@@ -327,13 +370,13 @@ class LowlevelTransport {
|
|
|
327
370
|
let buffer = firstData.slice(3);
|
|
328
371
|
const expectedLength = transport.PROTOCOL_V1_MESSAGE_HEADER_SIZE + payloadLength;
|
|
329
372
|
while (buffer.length < expectedLength) {
|
|
330
|
-
const next = yield this.receiveHex(timeoutMs, 'ProtocolV1');
|
|
373
|
+
const next = yield this.receiveHex(uuid, timeoutMs, 'ProtocolV1');
|
|
331
374
|
buffer = transport.concatUint8Arrays([buffer, transport.hexToBytes(next)]);
|
|
332
375
|
}
|
|
333
376
|
return transport.bytesToHex(buffer.slice(0, expectedLength));
|
|
334
377
|
});
|
|
335
378
|
}
|
|
336
|
-
readProtocolV2Frame(uuid, timeoutMs) {
|
|
379
|
+
readProtocolV2Frame(uuid, timeoutMs, commandName = 'ProtocolV2') {
|
|
337
380
|
return __awaiter(this, void 0, void 0, function* () {
|
|
338
381
|
let assembler = this.protocolV2Assemblers.get(uuid);
|
|
339
382
|
if (!assembler) {
|
|
@@ -345,7 +388,7 @@ class LowlevelTransport {
|
|
|
345
388
|
return queuedFrame;
|
|
346
389
|
let frame;
|
|
347
390
|
while (!frame) {
|
|
348
|
-
const response = yield this.receiveHex(timeoutMs,
|
|
391
|
+
const response = yield this.receiveHex(uuid, timeoutMs, commandName);
|
|
349
392
|
const chunk = transport.hexToBytes(response);
|
|
350
393
|
if (chunk.length > 0) {
|
|
351
394
|
frame = assembler.push(chunk);
|
|
@@ -363,35 +406,61 @@ class LowlevelTransport {
|
|
|
363
406
|
});
|
|
364
407
|
}
|
|
365
408
|
callProtocolV2(uuid, name, data, options) {
|
|
366
|
-
var _a
|
|
409
|
+
var _a;
|
|
367
410
|
return __awaiter(this, void 0, void 0, function* () {
|
|
368
411
|
if (!this._messages || !this._messagesV2) {
|
|
369
412
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured);
|
|
370
413
|
}
|
|
371
414
|
const timeoutMs = (_a = options === null || options === void 0 ? void 0 : options.timeoutMs) !== null && _a !== void 0 ? _a : LOWLEVEL_PROTOCOL_TIMEOUT_MS;
|
|
372
|
-
(_b = this.protocolV2Assemblers.get(uuid)) === null || _b === void 0 ? void 0 : _b.reset();
|
|
373
|
-
const session = new transport.ProtocolV2Session({
|
|
374
|
-
schemas: {
|
|
375
|
-
protocolV1: this._messages,
|
|
376
|
-
protocolV2: this._messagesV2,
|
|
377
|
-
},
|
|
378
|
-
router: transport.PROTOCOL_V2_CHANNEL_BLE_UART,
|
|
379
|
-
writeFrame: (frame) => this.writeProtocolV2Frame(uuid, frame),
|
|
380
|
-
readFrame: () => this.readProtocolV2Frame(uuid, timeoutMs),
|
|
381
|
-
logger: this.Log,
|
|
382
|
-
logPrefix: 'ProtocolV2 Lowlevel-BLE',
|
|
383
|
-
createTimeoutError: (_messageName, timeout) => this.createProtocolTimeoutError(name, timeout),
|
|
384
|
-
});
|
|
385
415
|
try {
|
|
386
|
-
return yield
|
|
416
|
+
return yield this.protocolV2Links.call(uuid, () => this.createProtocolV2Adapter(uuid), name, data, Object.assign(Object.assign({}, options), { timeoutMs }));
|
|
387
417
|
}
|
|
388
418
|
catch (e) {
|
|
389
|
-
(_c = this.protocolV2Assemblers.get(uuid)) === null || _c === void 0 ? void 0 : _c.reset();
|
|
390
419
|
this.Log.error('lowlevel Protocol V2 call error: ', e);
|
|
391
420
|
throw e;
|
|
392
421
|
}
|
|
393
422
|
});
|
|
394
423
|
}
|
|
424
|
+
createProtocolV2Adapter(uuid) {
|
|
425
|
+
var _a;
|
|
426
|
+
const generation = (_a = this.protocolV2Generations.get(uuid)) !== null && _a !== void 0 ? _a : 0;
|
|
427
|
+
const assertCurrentGeneration = () => {
|
|
428
|
+
if (this.protocolV2Generations.get(uuid) !== generation) {
|
|
429
|
+
throw new Error(`Protocol V2 connection generation changed for ${uuid}`);
|
|
430
|
+
}
|
|
431
|
+
};
|
|
432
|
+
return {
|
|
433
|
+
router: transport.PROTOCOL_V2_CHANNEL_BLE_UART,
|
|
434
|
+
maxFrameBytes: transport.PROTOCOL_V2_BLE_FRAME_MAX_BYTES,
|
|
435
|
+
generation,
|
|
436
|
+
prepareCall: () => {
|
|
437
|
+
var _a;
|
|
438
|
+
assertCurrentGeneration();
|
|
439
|
+
(_a = this.protocolV2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
|
|
440
|
+
},
|
|
441
|
+
writeFrame: (frame) => {
|
|
442
|
+
assertCurrentGeneration();
|
|
443
|
+
return this.writeProtocolV2Frame(uuid, frame);
|
|
444
|
+
},
|
|
445
|
+
readFrame: (context) => {
|
|
446
|
+
assertCurrentGeneration();
|
|
447
|
+
return this.readProtocolV2Frame(uuid, context.timeoutMs, context.messageName);
|
|
448
|
+
},
|
|
449
|
+
reset: () => {
|
|
450
|
+
var _a;
|
|
451
|
+
(_a = this.protocolV2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
|
|
452
|
+
},
|
|
453
|
+
logger: this.Log,
|
|
454
|
+
logPrefix: 'ProtocolV2 Lowlevel-BLE',
|
|
455
|
+
createTimeoutError: (messageName, timeout) => this.createProtocolTimeoutError(messageName, timeout),
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
advanceProtocolV2Generation(uuid) {
|
|
459
|
+
var _a;
|
|
460
|
+
const nextGeneration = ((_a = this.protocolV2Generations.get(uuid)) !== null && _a !== void 0 ? _a : 0) + 1;
|
|
461
|
+
this.protocolV2Generations.set(uuid, nextGeneration);
|
|
462
|
+
return nextGeneration;
|
|
463
|
+
}
|
|
395
464
|
cancel() {
|
|
396
465
|
this.Log.debug('lowlevel-transport', 'cancel');
|
|
397
466
|
}
|
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.9",
|
|
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.9",
|
|
24
|
+
"@onekeyfe/hd-transport": "1.2.0-alpha.9"
|
|
25
25
|
},
|
|
26
|
-
"gitHead": "
|
|
26
|
+
"gitHead": "f90ef9921b742f68cce9f4e7a0425e45e8ae7ec9"
|
|
27
27
|
}
|
package/src/index.ts
CHANGED
|
@@ -2,9 +2,10 @@ import { ERRORS, HardwareErrorCode } from '@onekeyfe/hd-shared';
|
|
|
2
2
|
import transport, {
|
|
3
3
|
LogBlockCommand,
|
|
4
4
|
PROTOCOL_V1_MESSAGE_HEADER_SIZE,
|
|
5
|
+
PROTOCOL_V2_BLE_FRAME_MAX_BYTES,
|
|
5
6
|
PROTOCOL_V2_CHANNEL_BLE_UART,
|
|
6
7
|
ProtocolV2FrameAssembler,
|
|
7
|
-
|
|
8
|
+
ProtocolV2LinkManager,
|
|
8
9
|
bytesToHex,
|
|
9
10
|
concatUint8Arrays,
|
|
10
11
|
hexToBytes,
|
|
@@ -59,6 +60,41 @@ export default class LowlevelTransport {
|
|
|
59
60
|
|
|
60
61
|
private protocolV2Assemblers: Map<string, ProtocolV2FrameAssembler> = new Map();
|
|
61
62
|
|
|
63
|
+
private protocolV2Generations: Map<string, number> = new Map();
|
|
64
|
+
|
|
65
|
+
private connectedDevices: Set<string> = new Set();
|
|
66
|
+
|
|
67
|
+
private protocolV2Links = new ProtocolV2LinkManager<string>({
|
|
68
|
+
getSchemas: () => {
|
|
69
|
+
if (!this._messages || !this._messagesV2) {
|
|
70
|
+
throw ERRORS.TypedError(HardwareErrorCode.TransportNotConfigured);
|
|
71
|
+
}
|
|
72
|
+
return {
|
|
73
|
+
protocolV1: this._messages,
|
|
74
|
+
protocolV2: this._messagesV2,
|
|
75
|
+
};
|
|
76
|
+
},
|
|
77
|
+
classifyError: () => 'link-fatal',
|
|
78
|
+
onLinkInvalidated: async (uuid, reason) => {
|
|
79
|
+
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
80
|
+
this.Log?.debug(`[LowlevelTransport] Protocol V2 link invalidated: ${uuid}`, reason);
|
|
81
|
+
if (reason.startsWith('Protocol V2 link-fatal error:')) {
|
|
82
|
+
this.deviceProtocol.delete(uuid);
|
|
83
|
+
this.advanceProtocolV2Generation(uuid);
|
|
84
|
+
try {
|
|
85
|
+
await this.plugin.disconnect(uuid);
|
|
86
|
+
} catch (error) {
|
|
87
|
+
this.Log?.debug(
|
|
88
|
+
`[LowlevelTransport] disconnect tainted Protocol V2 link failed: ${uuid}`,
|
|
89
|
+
error
|
|
90
|
+
);
|
|
91
|
+
} finally {
|
|
92
|
+
this.connectedDevices.delete(uuid);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
});
|
|
97
|
+
|
|
62
98
|
getProtocolType(path: string): ProtocolType | undefined {
|
|
63
99
|
return this.deviceProtocol.get(path);
|
|
64
100
|
}
|
|
@@ -78,6 +114,9 @@ export default class LowlevelTransport {
|
|
|
78
114
|
|
|
79
115
|
configureProtocolV2(signedData: any) {
|
|
80
116
|
this._messagesV2 = parseConfigure(signedData);
|
|
117
|
+
this.protocolV2Links
|
|
118
|
+
.invalidateAllLinks('Protocol V2 schema reconfigured')
|
|
119
|
+
.catch(error => this.Log?.debug('Protocol V2 schema link cleanup failed:', error));
|
|
81
120
|
}
|
|
82
121
|
|
|
83
122
|
listen() {
|
|
@@ -96,9 +135,15 @@ export default class LowlevelTransport {
|
|
|
96
135
|
}
|
|
97
136
|
|
|
98
137
|
async acquire(input: LowLevelAcquireInput) {
|
|
138
|
+
const alreadyConnected = this.connectedDevices.has(input.uuid);
|
|
99
139
|
try {
|
|
100
140
|
await this.plugin.connect(input.uuid);
|
|
141
|
+
if (!alreadyConnected) {
|
|
142
|
+
this.connectedDevices.add(input.uuid);
|
|
143
|
+
this.advanceProtocolV2Generation(input.uuid);
|
|
144
|
+
}
|
|
101
145
|
} catch (error) {
|
|
146
|
+
this.connectedDevices.delete(input.uuid);
|
|
102
147
|
this.Log.debug('lowlelvel transport connect error: ', error);
|
|
103
148
|
throw ERRORS.TypedError(
|
|
104
149
|
HardwareErrorCode.LowlevelTrasnportConnectError,
|
|
@@ -120,9 +165,12 @@ export default class LowlevelTransport {
|
|
|
120
165
|
|
|
121
166
|
async release(uuid: string) {
|
|
122
167
|
try {
|
|
168
|
+
await this.protocolV2Links.invalidateLink(uuid, 'Lowlevel transport released');
|
|
123
169
|
await this.plugin.disconnect(uuid);
|
|
170
|
+
this.connectedDevices.delete(uuid);
|
|
124
171
|
this.deviceProtocol.delete(uuid);
|
|
125
|
-
|
|
172
|
+
// 设备名称推断出的协议提示不依赖当前连接,保留它可以让快速重连
|
|
173
|
+
// 直接执行 Protocol V2 探测,避免先发送一次无意义的 V1 Initialize。
|
|
126
174
|
this.protocolV2Assemblers.delete(uuid);
|
|
127
175
|
return true;
|
|
128
176
|
} catch (error) {
|
|
@@ -195,7 +243,7 @@ export default class LowlevelTransport {
|
|
|
195
243
|
}
|
|
196
244
|
|
|
197
245
|
try {
|
|
198
|
-
const response = await this.readProtocolV1Message(options?.timeoutMs);
|
|
246
|
+
const response = await this.readProtocolV1Message(uuid, options?.timeoutMs);
|
|
199
247
|
this.Log.debug('receive data: ', response);
|
|
200
248
|
const jsonData = ProtocolV1.decodeMessage(messages, response);
|
|
201
249
|
return check.call(jsonData);
|
|
@@ -242,12 +290,12 @@ export default class LowlevelTransport {
|
|
|
242
290
|
protocolHint?: ProtocolType
|
|
243
291
|
): Promise<ProtocolType> {
|
|
244
292
|
if (expectedProtocol === 'V2') {
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
}
|
|
250
|
-
|
|
293
|
+
// 固件升级重启后的 bootloader 不保证响应 Ping。上层显式传入 V2
|
|
294
|
+
// 表示协议已经在重启前确认,这里与 RN/Electron BLE 保持一致,
|
|
295
|
+
// 直接建立 V2 Link,首个业务命令负责验证链路是否可用。
|
|
296
|
+
this.deviceProtocol.set(uuid, 'V2');
|
|
297
|
+
this.Log?.debug(`[LowlevelTransport] detectProtocol: uuid=${uuid} -> V2 (expected)`);
|
|
298
|
+
return 'V2';
|
|
251
299
|
}
|
|
252
300
|
|
|
253
301
|
if (expectedProtocol === 'V1') {
|
|
@@ -291,9 +339,14 @@ export default class LowlevelTransport {
|
|
|
291
339
|
}
|
|
292
340
|
|
|
293
341
|
private async resetConnectionAfterProbe(uuid: string, protocol: ProtocolType) {
|
|
342
|
+
await this.protocolV2Links.invalidateLink(
|
|
343
|
+
uuid,
|
|
344
|
+
`Reset connection after Protocol ${protocol} probe`
|
|
345
|
+
);
|
|
294
346
|
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
295
347
|
|
|
296
348
|
try {
|
|
349
|
+
this.connectedDevices.delete(uuid);
|
|
297
350
|
await this.plugin.disconnect(uuid);
|
|
298
351
|
} catch (error) {
|
|
299
352
|
this.Log?.debug(
|
|
@@ -304,6 +357,8 @@ export default class LowlevelTransport {
|
|
|
304
357
|
|
|
305
358
|
try {
|
|
306
359
|
await this.plugin.connect(uuid);
|
|
360
|
+
this.connectedDevices.add(uuid);
|
|
361
|
+
this.advanceProtocolV2Generation(uuid);
|
|
307
362
|
} catch (error) {
|
|
308
363
|
this.Log?.debug(
|
|
309
364
|
`[LowlevelTransport] reconnect after Protocol ${protocol} probe failed:`,
|
|
@@ -361,8 +416,8 @@ export default class LowlevelTransport {
|
|
|
361
416
|
}
|
|
362
417
|
}
|
|
363
418
|
|
|
364
|
-
private async receiveHex(timeoutMs: number | undefined, commandName: string) {
|
|
365
|
-
const response = await withProtocolTimeout(this.plugin.receive(), timeoutMs, () =>
|
|
419
|
+
private async receiveHex(uuid: string, timeoutMs: number | undefined, commandName: string) {
|
|
420
|
+
const response = await withProtocolTimeout(this.plugin.receive(uuid), timeoutMs, () =>
|
|
366
421
|
this.createProtocolTimeoutError(commandName, timeoutMs ?? 0)
|
|
367
422
|
);
|
|
368
423
|
if (typeof response !== 'string') {
|
|
@@ -371,8 +426,8 @@ export default class LowlevelTransport {
|
|
|
371
426
|
return response;
|
|
372
427
|
}
|
|
373
428
|
|
|
374
|
-
private async readProtocolV1Message(timeoutMs?: number) {
|
|
375
|
-
const first = await this.receiveHex(timeoutMs, 'ProtocolV1');
|
|
429
|
+
private async readProtocolV1Message(uuid: string, timeoutMs?: number) {
|
|
430
|
+
const first = await this.receiveHex(uuid, timeoutMs, 'ProtocolV1');
|
|
376
431
|
const firstData = hexToBytes(first);
|
|
377
432
|
if (!isProtocolV1TransportChunk(firstData)) {
|
|
378
433
|
return first;
|
|
@@ -383,14 +438,14 @@ export default class LowlevelTransport {
|
|
|
383
438
|
const expectedLength = PROTOCOL_V1_MESSAGE_HEADER_SIZE + payloadLength;
|
|
384
439
|
|
|
385
440
|
while (buffer.length < expectedLength) {
|
|
386
|
-
const next = await this.receiveHex(timeoutMs, 'ProtocolV1');
|
|
441
|
+
const next = await this.receiveHex(uuid, timeoutMs, 'ProtocolV1');
|
|
387
442
|
buffer = concatUint8Arrays([buffer, hexToBytes(next)]);
|
|
388
443
|
}
|
|
389
444
|
|
|
390
445
|
return bytesToHex(buffer.slice(0, expectedLength));
|
|
391
446
|
}
|
|
392
447
|
|
|
393
|
-
private async readProtocolV2Frame(uuid: string, timeoutMs?: number) {
|
|
448
|
+
private async readProtocolV2Frame(uuid: string, timeoutMs?: number, commandName = 'ProtocolV2') {
|
|
394
449
|
let assembler = this.protocolV2Assemblers.get(uuid);
|
|
395
450
|
if (!assembler) {
|
|
396
451
|
assembler = new ProtocolV2FrameAssembler();
|
|
@@ -402,7 +457,7 @@ export default class LowlevelTransport {
|
|
|
402
457
|
|
|
403
458
|
let frame: Uint8Array | undefined;
|
|
404
459
|
while (!frame) {
|
|
405
|
-
const response = await this.receiveHex(timeoutMs,
|
|
460
|
+
const response = await this.receiveHex(uuid, timeoutMs, commandName);
|
|
406
461
|
const chunk = hexToBytes(response);
|
|
407
462
|
if (chunk.length > 0) {
|
|
408
463
|
frame = assembler.push(chunk);
|
|
@@ -429,33 +484,64 @@ export default class LowlevelTransport {
|
|
|
429
484
|
}
|
|
430
485
|
|
|
431
486
|
const timeoutMs = options?.timeoutMs ?? LOWLEVEL_PROTOCOL_TIMEOUT_MS;
|
|
432
|
-
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
433
|
-
const session = new ProtocolV2Session({
|
|
434
|
-
schemas: {
|
|
435
|
-
protocolV1: this._messages,
|
|
436
|
-
protocolV2: this._messagesV2,
|
|
437
|
-
},
|
|
438
|
-
router: PROTOCOL_V2_CHANNEL_BLE_UART,
|
|
439
|
-
writeFrame: (frame: Uint8Array) => this.writeProtocolV2Frame(uuid, frame),
|
|
440
|
-
readFrame: () => this.readProtocolV2Frame(uuid, timeoutMs),
|
|
441
|
-
logger: this.Log,
|
|
442
|
-
logPrefix: 'ProtocolV2 Lowlevel-BLE',
|
|
443
|
-
createTimeoutError: (_messageName: string, timeout: number) =>
|
|
444
|
-
this.createProtocolTimeoutError(name, timeout),
|
|
445
|
-
});
|
|
446
487
|
|
|
447
488
|
try {
|
|
448
|
-
return await
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
489
|
+
return await this.protocolV2Links.call(
|
|
490
|
+
uuid,
|
|
491
|
+
() => this.createProtocolV2Adapter(uuid),
|
|
492
|
+
name,
|
|
493
|
+
data,
|
|
494
|
+
{
|
|
495
|
+
...options,
|
|
496
|
+
timeoutMs,
|
|
497
|
+
}
|
|
498
|
+
);
|
|
452
499
|
} catch (e) {
|
|
453
|
-
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
454
500
|
this.Log.error('lowlevel Protocol V2 call error: ', e);
|
|
455
501
|
throw e;
|
|
456
502
|
}
|
|
457
503
|
}
|
|
458
504
|
|
|
505
|
+
private createProtocolV2Adapter(uuid: string) {
|
|
506
|
+
const generation = this.protocolV2Generations.get(uuid) ?? 0;
|
|
507
|
+
const assertCurrentGeneration = () => {
|
|
508
|
+
if (this.protocolV2Generations.get(uuid) !== generation) {
|
|
509
|
+
throw new Error(`Protocol V2 connection generation changed for ${uuid}`);
|
|
510
|
+
}
|
|
511
|
+
};
|
|
512
|
+
|
|
513
|
+
return {
|
|
514
|
+
router: PROTOCOL_V2_CHANNEL_BLE_UART,
|
|
515
|
+
maxFrameBytes: PROTOCOL_V2_BLE_FRAME_MAX_BYTES,
|
|
516
|
+
generation,
|
|
517
|
+
prepareCall: () => {
|
|
518
|
+
assertCurrentGeneration();
|
|
519
|
+
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
520
|
+
},
|
|
521
|
+
writeFrame: (frame: Uint8Array) => {
|
|
522
|
+
assertCurrentGeneration();
|
|
523
|
+
return this.writeProtocolV2Frame(uuid, frame);
|
|
524
|
+
},
|
|
525
|
+
readFrame: (context: { messageName: string; timeoutMs?: number }) => {
|
|
526
|
+
assertCurrentGeneration();
|
|
527
|
+
return this.readProtocolV2Frame(uuid, context.timeoutMs, context.messageName);
|
|
528
|
+
},
|
|
529
|
+
reset: () => {
|
|
530
|
+
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
531
|
+
},
|
|
532
|
+
logger: this.Log,
|
|
533
|
+
logPrefix: 'ProtocolV2 Lowlevel-BLE',
|
|
534
|
+
createTimeoutError: (messageName: string, timeout: number) =>
|
|
535
|
+
this.createProtocolTimeoutError(messageName, timeout),
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
private advanceProtocolV2Generation(uuid: string) {
|
|
540
|
+
const nextGeneration = (this.protocolV2Generations.get(uuid) ?? 0) + 1;
|
|
541
|
+
this.protocolV2Generations.set(uuid, nextGeneration);
|
|
542
|
+
return nextGeneration;
|
|
543
|
+
}
|
|
544
|
+
|
|
459
545
|
cancel() {
|
|
460
546
|
this.Log.debug('lowlevel-transport', 'cancel');
|
|
461
547
|
}
|