@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.
@@ -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
 
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,SAWN,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,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;IAInC,MAAM;IAIA,SAAS;IAWT,OAAO,CAAC,KAAK,EAAE,oBAAoB;;;;IAuBnC,OAAO,CAAC,IAAI,EAAE,MAAM;IAapB,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;YA0BzB,eAAe;YAgBf,eAAe;YA6Bf,UAAU;YAUV,qBAAqB;YAmBrB,mBAAmB;YAqBnB,oBAAoB;YAOpB,cAAc;IAsC5B,MAAM;CAGP"}
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
- if (yield this.probeProtocolV2(uuid)) {
201
- this.deviceProtocol.set(uuid, 'V2');
202
- (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug(`[LowlevelTransport] detectProtocol: uuid=${uuid} -> V2 (expected)`);
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, 'ProtocolV2');
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, _b, _c;
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 session.call(name, data, Object.assign(Object.assign({}, options), { timeoutMs }));
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.8",
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.8",
24
- "@onekeyfe/hd-transport": "1.2.0-alpha.8"
23
+ "@onekeyfe/hd-shared": "1.2.0-alpha.9",
24
+ "@onekeyfe/hd-transport": "1.2.0-alpha.9"
25
25
  },
26
- "gitHead": "d2b1323e5ad9c373e72d4d092f35c9f274c1fd74"
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
- ProtocolV2Session,
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
- this.deviceProtocolHints.delete(uuid);
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
- if (await this.probeProtocolV2(uuid)) {
246
- this.deviceProtocol.set(uuid, 'V2');
247
- this.Log?.debug(`[LowlevelTransport] detectProtocol: uuid=${uuid} -> V2 (expected)`);
248
- return 'V2';
249
- }
250
- throw this.createProtocolMismatchError(expectedProtocol);
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, 'ProtocolV2');
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 session.call(name, data, {
449
- ...options,
450
- timeoutMs,
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
  }