@onekeyfe/hd-transport-usb 1.1.26 → 1.1.27-alpha.31

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/dist/index.d.ts CHANGED
@@ -1,11 +1,10 @@
1
1
  import * as transport from '@onekeyfe/hd-transport';
2
- import transport__default, { OneKeyDeviceInfo, AcquireInput } from '@onekeyfe/hd-transport';
2
+ import transport__default, { OneKeyDeviceInfo, AcquireInput, TransportCallOptions, ProtocolType } from '@onekeyfe/hd-transport';
3
3
  import EventEmitter from 'events';
4
4
 
5
- declare const PACKET_SIZE = 64;
6
-
7
5
  declare class NodeUsbTransport {
8
6
  messages: ReturnType<typeof transport__default.parseConfigure> | undefined;
7
+ messagesV2: ReturnType<typeof transport__default.parseConfigure> | undefined;
9
8
  name: string;
10
9
  version: string;
11
10
  configured: boolean;
@@ -14,10 +13,13 @@ declare class NodeUsbTransport {
14
13
  emitter?: EventEmitter;
15
14
  private serialToBusId;
16
15
  private openDevices;
16
+ private deviceProtocol;
17
+ private protocolV2Assemblers;
17
18
  private reconnectLocks;
18
19
  private cancelled;
19
20
  init(logger: any, emitter?: EventEmitter): Promise<string>;
20
21
  configure(signedData: any): Promise<void>;
22
+ configureProtocolV2(signedData: any): void;
21
23
  listen(): void;
22
24
  stop(): void;
23
25
  post(path: string, name: string, data: Record<string, unknown>): Promise<void>;
@@ -30,16 +32,29 @@ declare class NodeUsbTransport {
30
32
  enumerate(): Promise<OneKeyDeviceInfo[]>;
31
33
  acquire(input: AcquireInput): Promise<string>;
32
34
  release(path: string, _onclose?: boolean): Promise<void>;
33
- call(path: string, name: string, data: Record<string, unknown>): Promise<transport.MessageFromOneKey>;
35
+ private closeOpenDevice;
36
+ call(path: string, name: string, data: Record<string, unknown>, options?: TransportCallOptions): Promise<transport.MessageFromOneKey>;
37
+ private callProtocolV1;
34
38
  cancel(): void;
35
39
  private getOpenDevice;
36
40
  private getErrorMessage;
37
41
  private isRetryableError;
42
+ private getDeviceInterface;
38
43
  private reconnectForRetry;
39
44
  private sendAllChunksWithRetry;
40
45
  private transferInWithRetry;
41
46
  private openDevice;
47
+ private createProtocolMismatchError;
48
+ private detectProtocol;
49
+ private resetConnectionAfterProbe;
50
+ private withProtocolReadTimeout;
51
+ private probeProtocolV1;
52
+ private probeProtocolV2;
53
+ private writeProtocolV2Frame;
54
+ private receiveProtocolV2Frame;
55
+ private callProtocolV2;
42
56
  private receiveData;
57
+ getProtocolType(path: string): ProtocolType;
43
58
  }
44
59
 
45
- export { PACKET_SIZE, NodeUsbTransport as default };
60
+ export { NodeUsbTransport as default };
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,SAA8B,MAAM,wBAAwB,CAAC;AAKpE,OAAO,KAAK,YAAY,MAAM,QAAQ,CAAC;AACvC,OAAO,KAAK,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AA0J7E,MAAM,CAAC,OAAO,OAAO,gBAAgB;IACnC,QAAQ,EAAE,UAAU,CAAC,OAAO,SAAS,CAAC,cAAc,CAAC,GAAG,SAAS,CAAC;IAElE,IAAI,SAAsB;IAE1B,OAAO,SAAM;IAEb,UAAU,UAAS;IAEnB,UAAU,UAAS;IAEnB,GAAG,CAAC,EAAE,GAAG,CAAC;IAEV,OAAO,CAAC,EAAE,YAAY,CAAC;IAGvB,OAAO,CAAC,aAAa,CAA6B;IAGlD,OAAO,CAAC,WAAW,CAAiC;IAGpD,OAAO,CAAC,cAAc,CAA0C;IAGhE,OAAO,CAAC,SAAS,CAAS;IAM1B,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE,OAAO,CAAC,EAAE,YAAY;IAMxC,SAAS,CAAC,UAAU,EAAE,GAAG;IAOzB,MAAM;IAIN,IAAI;IAQE,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAY9E,IAAI,CAAC,IAAI,EAAE,MAAM;;;;;;IAiBjB,SAAS,IAAI,OAAO,CAAC,gBAAgB,EAAE,CAAC;IA8B9C,OAAO,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC;IAkBvC,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IA6BxD,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAkCpE,MAAM;IAWN,OAAO,CAAC,aAAa;IAQrB,OAAO,CAAC,eAAe;IAUvB,OAAO,CAAC,gBAAgB;IAoBxB,OAAO,CAAC,iBAAiB;YAmDX,sBAAsB;YAyCtB,mBAAmB;IAsCjC,OAAO,CAAC,UAAU;YAgEJ,WAAW;CAkC1B;AAED,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,SAWN,MAAM,wBAAwB,CAAC;AAGhC,OAAO,KAAK,YAAY,MAAM,QAAQ,CAAC;AACvC,OAAO,KAAK,EACV,YAAY,EACZ,gBAAgB,EAChB,YAAY,EACZ,oBAAoB,EACrB,MAAM,wBAAwB,CAAC;AAgKhC,MAAM,CAAC,OAAO,OAAO,gBAAgB;IACnC,QAAQ,EAAE,UAAU,CAAC,OAAO,SAAS,CAAC,cAAc,CAAC,GAAG,SAAS,CAAC;IAGlE,UAAU,EAAE,UAAU,CAAC,OAAO,SAAS,CAAC,cAAc,CAAC,GAAG,SAAS,CAAC;IAEpE,IAAI,SAAsB;IAE1B,OAAO,SAAM;IAEb,UAAU,UAAS;IAEnB,UAAU,UAAS;IAEnB,GAAG,CAAC,EAAE,GAAG,CAAC;IAEV,OAAO,CAAC,EAAE,YAAY,CAAC;IAGvB,OAAO,CAAC,aAAa,CAA6B;IAGlD,OAAO,CAAC,WAAW,CAAiC;IAGpD,OAAO,CAAC,cAAc,CAAwC;IAG9D,OAAO,CAAC,oBAAoB,CAAoD;IAGhF,OAAO,CAAC,cAAc,CAA0C;IAGhE,OAAO,CAAC,SAAS,CAAS;IAM1B,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE,OAAO,CAAC,EAAE,YAAY;IAMxC,SAAS,CAAC,UAAU,EAAE,GAAG;IAOzB,mBAAmB,CAAC,UAAU,EAAE,GAAG;IAKnC,MAAM;IAIN,IAAI;IAQE,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAY9E,IAAI,CAAC,IAAI,EAAE,MAAM;;;;;;IAiBjB,SAAS,IAAI,OAAO,CAAC,gBAAgB,EAAE,CAAC;IA8BxC,OAAO,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC;IAmB7C,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;YAMhD,eAAe;IA6BvB,IAAI,CACR,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,OAAO,CAAC,EAAE,oBAAoB;YAkClB,cAAc;IA0B5B,MAAM;IAWN,OAAO,CAAC,aAAa;IAQrB,OAAO,CAAC,eAAe;IAUvB,OAAO,CAAC,gBAAgB;IAgBxB,OAAO,CAAC,kBAAkB;IAmB1B,OAAO,CAAC,iBAAiB;YAmDX,sBAAsB;YAyCtB,mBAAmB;IAsCjC,OAAO,CAAC,UAAU;IA8DlB,OAAO,CAAC,2BAA2B;YAOrB,cAAc;YAqCd,yBAAyB;YAazB,uBAAuB;YA0CvB,eAAe;YAcf,eAAe;YAcf,oBAAoB;YA+BpB,sBAAsB;YAuCtB,cAAc;YAuCd,WAAW;IA6CzB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY;CAG5C"}
package/dist/index.js CHANGED
@@ -1,7 +1,5 @@
1
1
  'use strict';
2
2
 
3
- Object.defineProperty(exports, '__esModule', { value: true });
4
-
5
3
  var ByteBuffer = require('bytebuffer');
6
4
  var usb = require('usb');
7
5
  var transport = require('@onekeyfe/hd-transport');
@@ -61,12 +59,11 @@ typeof SuppressedError === "function" ? SuppressedError : function (error, suppr
61
59
  return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
62
60
  };
63
61
 
64
- const PACKET_SIZE = 64;
65
- const REPORT_ID = 0x3f;
66
- const PAYLOAD_SIZE = PACKET_SIZE - 1;
67
- const HEADER_LENGTH = 6;
68
-
69
- const { parseConfigure, buildEncodeBuffers, decodeProtocol, receiveOne, check } = transport__default["default"];
62
+ const { parseConfigure, ProtocolV1, check } = transport__default["default"];
63
+ const PACKET_SIZE = transport.PROTOCOL_V1_USB_PACKET_SIZE;
64
+ const REPORT_ID = transport.PROTOCOL_V1_REPORT_ID;
65
+ const PAYLOAD_SIZE = transport.PROTOCOL_V1_CHUNK_PAYLOAD_SIZE;
66
+ const HEADER_LENGTH = transport.PROTOCOL_V1_MESSAGE_HEADER_SIZE;
70
67
  const INTERFACE_NUMBER = 0;
71
68
  const ENDPOINT_IN = 0x81;
72
69
  const ENDPOINT_OUT = 0x01;
@@ -74,6 +71,7 @@ const TRANSFER_TIMEOUT_MS = 30000;
74
71
  const SERIAL_READ_TIMEOUT_MS = 5000;
75
72
  const PACKET_IO_MAX_RETRIES = 3;
76
73
  const PACKET_IO_RETRY_DELAY = 300;
74
+ const PROTOCOL_PROBE_TIMEOUT = 1000;
77
75
  function getBusId(dev) {
78
76
  return `usb:${dev.busNumber}:${dev.deviceAddress}`;
79
77
  }
@@ -171,6 +169,8 @@ class NodeUsbTransport {
171
169
  this.isOutdated = false;
172
170
  this.serialToBusId = new Map();
173
171
  this.openDevices = new Map();
172
+ this.deviceProtocol = new Map();
173
+ this.protocolV2Assemblers = new Map();
174
174
  this.reconnectLocks = new Map();
175
175
  this.cancelled = false;
176
176
  }
@@ -185,6 +185,11 @@ class NodeUsbTransport {
185
185
  this.messages = messages;
186
186
  return Promise.resolve();
187
187
  }
188
+ configureProtocolV2(signedData) {
189
+ var _a;
190
+ this.messagesV2 = parseConfigure(signedData);
191
+ (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('[NodeUsbTransport] Protocol V2 schema configured');
192
+ }
188
193
  listen() {
189
194
  }
190
195
  stop() {
@@ -194,7 +199,7 @@ class NodeUsbTransport {
194
199
  if (!this.messages) {
195
200
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured);
196
201
  }
197
- const encodeBuffers = buildEncodeBuffers(this.messages, name, data);
202
+ const encodeBuffers = ProtocolV1.encodeMessageChunks(this.messages, name, data);
198
203
  yield this.sendAllChunksWithRetry(path, encodeBuffers);
199
204
  });
200
205
  }
@@ -208,7 +213,7 @@ class NodeUsbTransport {
208
213
  if (!this.messages) {
209
214
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured);
210
215
  }
211
- return receiveOne(this.messages, resData);
216
+ return ProtocolV1.decodeMessage(this.messages, resData);
212
217
  });
213
218
  }
214
219
  enumerate() {
@@ -238,20 +243,30 @@ class NodeUsbTransport {
238
243
  }
239
244
  acquire(input) {
240
245
  var _a, _b, _c;
241
- const path = (_a = input.path) !== null && _a !== void 0 ? _a : '';
242
- if (!path) {
243
- throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.DeviceNotFound, 'No device path provided');
244
- }
245
- try {
246
- this.openDevice(path);
247
- return Promise.resolve(path);
248
- }
249
- catch (error) {
250
- (_b = this.Log) === null || _b === void 0 ? void 0 : _b.debug('NodeUsbTransport acquire error: ', error);
251
- throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.DeviceNotFound, (_c = error.message) !== null && _c !== void 0 ? _c : String(error));
252
- }
246
+ return __awaiter(this, void 0, void 0, function* () {
247
+ const path = (_a = input.path) !== null && _a !== void 0 ? _a : '';
248
+ if (!path) {
249
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.DeviceNotFound, 'No device path provided');
250
+ }
251
+ try {
252
+ this.openDevice(path);
253
+ yield this.detectProtocol(path, input.expectedProtocol);
254
+ return path;
255
+ }
256
+ catch (error) {
257
+ (_b = this.Log) === null || _b === void 0 ? void 0 : _b.debug('NodeUsbTransport acquire error: ', error);
258
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.DeviceNotFound, (_c = error.message) !== null && _c !== void 0 ? _c : String(error));
259
+ }
260
+ });
253
261
  }
254
262
  release(path, _onclose) {
263
+ return __awaiter(this, void 0, void 0, function* () {
264
+ yield this.closeOpenDevice(path);
265
+ this.deviceProtocol.delete(path);
266
+ this.protocolV2Assemblers.delete(path);
267
+ });
268
+ }
269
+ closeOpenDevice(path) {
255
270
  return __awaiter(this, void 0, void 0, function* () {
256
271
  const openDev = this.openDevices.get(path);
257
272
  if (!openDev)
@@ -278,8 +293,8 @@ class NodeUsbTransport {
278
293
  this.openDevices.delete(path);
279
294
  });
280
295
  }
281
- call(path, name, data) {
282
- var _a, _b;
296
+ call(path, name, data, options) {
297
+ var _a, _b, _c;
283
298
  return __awaiter(this, void 0, void 0, function* () {
284
299
  this.cancelled = false;
285
300
  if (!this.messages) {
@@ -288,20 +303,32 @@ class NodeUsbTransport {
288
303
  if (!this.openDevices.get(path)) {
289
304
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.DeviceNotFound, `Device not acquired: ${path}`);
290
305
  }
291
- const { messages } = this;
306
+ const protocol = (_a = this.deviceProtocol.get(path)) !== null && _a !== void 0 ? _a : 'V1';
292
307
  if (transport.LogBlockCommand.has(name)) {
293
- (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('NodeUsbTransport call-', ' name: ', name);
308
+ (_b = this.Log) === null || _b === void 0 ? void 0 : _b.debug('NodeUsbTransport call-', ' name: ', name, ' protocol: ', protocol);
294
309
  }
295
310
  else {
296
- (_b = this.Log) === null || _b === void 0 ? void 0 : _b.debug('NodeUsbTransport call-', ' name: ', name, ' data: ', data);
311
+ (_c = this.Log) === null || _c === void 0 ? void 0 : _c.debug('NodeUsbTransport call-', ' name: ', name, ' data: ', data, ' protocol: ', protocol);
312
+ }
313
+ if (protocol === 'V2') {
314
+ return this.callProtocolV2(path, name, data, options);
297
315
  }
298
- const encodeBuffers = buildEncodeBuffers(messages, name, data);
316
+ return this.callProtocolV1(path, name, data, options);
317
+ });
318
+ }
319
+ callProtocolV1(path, name, data, options) {
320
+ return __awaiter(this, void 0, void 0, function* () {
321
+ const { messages } = this;
322
+ if (!messages) {
323
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured);
324
+ }
325
+ const encodeBuffers = ProtocolV1.encodeMessageChunks(messages, name, data);
299
326
  yield this.sendAllChunksWithRetry(path, encodeBuffers);
300
- const resData = yield this.receiveData(path, this.getOpenDevice(path));
327
+ const resData = yield this.receiveData(path, this.getOpenDevice(path), options === null || options === void 0 ? void 0 : options.timeoutMs);
301
328
  if (typeof resData !== 'string') {
302
329
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.NetworkError, 'Returning data is not string.');
303
330
  }
304
- const jsonData = receiveOne(messages, resData);
331
+ const jsonData = ProtocolV1.decodeMessage(messages, resData);
305
332
  return check.call(jsonData);
306
333
  });
307
334
  }
@@ -341,6 +368,17 @@ class NodeUsbTransport {
341
368
  message.includes('timeout') ||
342
369
  message.includes('interrupt'));
343
370
  }
371
+ getDeviceInterface(dev) {
372
+ var _a;
373
+ const { interfaces } = dev;
374
+ if (!(interfaces === null || interfaces === void 0 ? void 0 : interfaces.length)) {
375
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.DeviceNotFound, 'USB interface not found');
376
+ }
377
+ const vendorInterface = interfaces.find(iface => iface.descriptor.bInterfaceClass === 0xff);
378
+ const defaultInterface = interfaces.find(iface => iface.descriptor.bInterfaceNumber === INTERFACE_NUMBER);
379
+ const iface = (_a = vendorInterface !== null && vendorInterface !== void 0 ? vendorInterface : defaultInterface) !== null && _a !== void 0 ? _a : interfaces[0];
380
+ return iface;
381
+ }
344
382
  reconnectForRetry(path, direction, attempt, error) {
345
383
  const existing = this.reconnectLocks.get(path);
346
384
  if (existing)
@@ -350,7 +388,7 @@ class NodeUsbTransport {
350
388
  (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug(`[NodeUsbTransport] transfer${direction} failed, retry ${attempt}/${PACKET_IO_MAX_RETRIES}: ${this.getErrorMessage(error)}`);
351
389
  yield hdShared.wait(attempt * PACKET_IO_RETRY_DELAY);
352
390
  try {
353
- yield this.release(path);
391
+ yield this.closeOpenDevice(path);
354
392
  }
355
393
  catch (releaseError) {
356
394
  (_b = this.Log) === null || _b === void 0 ? void 0 : _b.debug('[NodeUsbTransport] release before retry error:', releaseError);
@@ -437,7 +475,7 @@ class NodeUsbTransport {
437
475
  });
438
476
  }
439
477
  openDevice(path) {
440
- var _a;
478
+ var _a, _b, _c;
441
479
  const existing = this.openDevices.get(path);
442
480
  if (existing)
443
481
  return;
@@ -450,19 +488,19 @@ class NodeUsbTransport {
450
488
  dev.open();
451
489
  try {
452
490
  dev.timeout = TRANSFER_TIMEOUT_MS;
453
- const iface = dev.interface(INTERFACE_NUMBER);
491
+ const iface = this.getDeviceInterface(dev);
454
492
  if (process.platform === 'linux') {
455
493
  try {
456
494
  if (iface.isKernelDriverActive()) {
457
495
  iface.detachKernelDriver();
458
496
  }
459
497
  }
460
- catch (_b) {
498
+ catch (_d) {
461
499
  }
462
500
  }
463
501
  iface.claim();
464
- const epIn = iface.endpoints.find((e) => e.direction === 'in' && e.address === ENDPOINT_IN);
465
- const epOut = iface.endpoints.find((e) => e.direction === 'out' && e.address === ENDPOINT_OUT);
502
+ const epIn = (_b = iface.endpoints.find((e) => e.direction === 'in' && e.address === ENDPOINT_IN)) !== null && _b !== void 0 ? _b : iface.endpoints.find((e) => e.direction === 'in');
503
+ const epOut = (_c = iface.endpoints.find((e) => e.direction === 'out' && e.address === ENDPOINT_OUT)) !== null && _c !== void 0 ? _c : iface.endpoints.find((e) => e.direction === 'out');
466
504
  if (!epIn || !epOut) {
467
505
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.DeviceNotFound, 'USB endpoints not found (expected IN 0x81, OUT 0x01)');
468
506
  }
@@ -474,16 +512,224 @@ class NodeUsbTransport {
474
512
  try {
475
513
  dev.close();
476
514
  }
477
- catch (_c) {
515
+ catch (_e) {
478
516
  }
479
517
  throw err;
480
518
  }
481
519
  }
482
- receiveData(path, dev) {
520
+ createProtocolMismatchError(expected) {
521
+ return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, `Device protocol mismatch: expected ${expected}, but device did not respond to expected protocol`);
522
+ }
523
+ detectProtocol(path, expectedProtocol) {
524
+ var _a, _b, _c, _d;
525
+ return __awaiter(this, void 0, void 0, function* () {
526
+ if (expectedProtocol === 'V1') {
527
+ if (yield this.probeProtocolV1(path)) {
528
+ this.deviceProtocol.set(path, 'V1');
529
+ (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug(`[NodeUsbTransport] detectProtocol: path=${path} -> V1 (expected)`);
530
+ return 'V1';
531
+ }
532
+ throw this.createProtocolMismatchError(expectedProtocol);
533
+ }
534
+ if (expectedProtocol === 'V2') {
535
+ if (yield this.probeProtocolV2(path)) {
536
+ this.deviceProtocol.set(path, 'V2');
537
+ (_b = this.Log) === null || _b === void 0 ? void 0 : _b.debug(`[NodeUsbTransport] detectProtocol: path=${path} -> V2 (expected)`);
538
+ return 'V2';
539
+ }
540
+ throw this.createProtocolMismatchError(expectedProtocol);
541
+ }
542
+ if (this.deviceProtocol.get(path) === 'V2' && (yield this.probeProtocolV2(path))) {
543
+ this.deviceProtocol.set(path, 'V2');
544
+ (_c = this.Log) === null || _c === void 0 ? void 0 : _c.debug(`[NodeUsbTransport] detectProtocol: path=${path} -> V2 (cached)`);
545
+ return 'V2';
546
+ }
547
+ let protocol = 'V1';
548
+ if (!(yield this.probeProtocolV1(path)) && (yield this.probeProtocolV2(path))) {
549
+ protocol = 'V2';
550
+ }
551
+ this.deviceProtocol.set(path, protocol);
552
+ (_d = this.Log) === null || _d === void 0 ? void 0 : _d.debug(`[NodeUsbTransport] detectProtocol: path=${path} -> ${protocol}`);
553
+ return protocol;
554
+ });
555
+ }
556
+ resetConnectionAfterProbe(path) {
557
+ var _a, _b;
558
+ return __awaiter(this, void 0, void 0, function* () {
559
+ (_a = this.protocolV2Assemblers.get(path)) === null || _a === void 0 ? void 0 : _a.reset();
560
+ try {
561
+ yield this.closeOpenDevice(path);
562
+ }
563
+ catch (error) {
564
+ (_b = this.Log) === null || _b === void 0 ? void 0 : _b.debug('[NodeUsbTransport] close after protocol probe error:', error);
565
+ }
566
+ yield this.enumerate();
567
+ this.openDevice(path);
568
+ });
569
+ }
570
+ withProtocolReadTimeout(path, promise, timeoutMs, protocol) {
571
+ return __awaiter(this, void 0, void 0, function* () {
572
+ let timer;
573
+ let timedOut = false;
574
+ const waitForeverAfterTimeout = () => new Promise(() => { });
575
+ const guardedPromise = promise.then(value => (timedOut ? waitForeverAfterTimeout() : value), error => {
576
+ if (timedOut) {
577
+ return waitForeverAfterTimeout();
578
+ }
579
+ throw error;
580
+ });
581
+ try {
582
+ return yield Promise.race([
583
+ guardedPromise,
584
+ new Promise((_, reject) => {
585
+ timer = setTimeout(() => __awaiter(this, void 0, void 0, function* () {
586
+ var _a;
587
+ timedOut = true;
588
+ try {
589
+ yield this.resetConnectionAfterProbe(path);
590
+ }
591
+ catch (error) {
592
+ (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug(`[NodeUsbTransport] reset after Protocol ${protocol} timeout failed:`, error);
593
+ }
594
+ finally {
595
+ reject(new Error(`Protocol ${protocol} read timeout after ${timeoutMs}ms`));
596
+ }
597
+ }), timeoutMs);
598
+ }),
599
+ ]);
600
+ }
601
+ finally {
602
+ if (timer)
603
+ clearTimeout(timer);
604
+ }
605
+ });
606
+ }
607
+ probeProtocolV1(path) {
608
+ var _a;
609
+ return __awaiter(this, void 0, void 0, function* () {
610
+ if (!this.messages) {
611
+ return false;
612
+ }
613
+ try {
614
+ yield this.callProtocolV1(path, 'Initialize', {}, { timeoutMs: PROTOCOL_PROBE_TIMEOUT });
615
+ return true;
616
+ }
617
+ catch (error) {
618
+ (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('[NodeUsbTransport] Protocol V1 Initialize probe failed:', error);
619
+ return false;
620
+ }
621
+ });
622
+ }
623
+ probeProtocolV2(path) {
624
+ return __awaiter(this, void 0, void 0, function* () {
625
+ if (!this.messages || !this.messagesV2) {
626
+ return false;
627
+ }
628
+ return transport.probeProtocolV2({
629
+ call: (name, data, options) => this.callProtocolV2(path, name, data, options),
630
+ timeoutMs: PROTOCOL_PROBE_TIMEOUT,
631
+ logger: this.Log,
632
+ logPrefix: 'ProtocolV2 NodeUSB',
633
+ onProbeFailed: () => this.resetConnectionAfterProbe(path),
634
+ });
635
+ });
636
+ }
637
+ writeProtocolV2Frame(path, frame) {
638
+ var _a;
639
+ return __awaiter(this, void 0, void 0, function* () {
640
+ let lastError;
641
+ for (let attempt = 1; attempt <= PACKET_IO_MAX_RETRIES; attempt++) {
642
+ if (this.cancelled) {
643
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.DeviceInterruptedFromOutside, 'Cancelled');
644
+ }
645
+ try {
646
+ yield transferOutOnce(this.getOpenDevice(path).epOut, Buffer.from(frame));
647
+ return;
648
+ }
649
+ catch (error) {
650
+ lastError = error;
651
+ const shouldRetry = attempt < PACKET_IO_MAX_RETRIES && this.isRetryableError(error);
652
+ if (!shouldRetry) {
653
+ throw error;
654
+ }
655
+ try {
656
+ yield this.reconnectForRetry(path, 'out', attempt, error);
657
+ }
658
+ catch (reconnectError) {
659
+ lastError = reconnectError;
660
+ (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug(`[NodeUsbTransport] Protocol V2 write reconnect failed on retry ${attempt}/${PACKET_IO_MAX_RETRIES}: ${this.getErrorMessage(reconnectError)}`);
661
+ break;
662
+ }
663
+ }
664
+ }
665
+ throw lastError;
666
+ });
667
+ }
668
+ receiveProtocolV2Frame(path, timeoutMs) {
483
669
  return __awaiter(this, void 0, void 0, function* () {
484
- const firstPacket = yield this.transferInWithRetry(path, dev, PACKET_SIZE);
670
+ let assembler = this.protocolV2Assemblers.get(path);
671
+ if (!assembler) {
672
+ assembler = new transport.ProtocolV2FrameAssembler(transport.PROTOCOL_V2_FRAME_MAX_BYTES);
673
+ this.protocolV2Assemblers.set(path, assembler);
674
+ }
675
+ let frame = assembler.push(new Uint8Array(0));
676
+ const deadline = timeoutMs ? Date.now() + timeoutMs : undefined;
677
+ while (!frame) {
678
+ const transferIn = this.transferInWithRetry(path, this.getOpenDevice(path), transport.PROTOCOL_V2_FRAME_MAX_BYTES);
679
+ const packet = deadline
680
+ ? yield this.withProtocolReadTimeout(path, transferIn, Math.max(deadline - Date.now(), 1), 'V2')
681
+ : yield transferIn;
682
+ const bytes = new Uint8Array(packet.buffer.slice(packet.byteOffset, packet.byteOffset + packet.byteLength));
683
+ try {
684
+ frame = assembler.push(bytes);
685
+ }
686
+ catch (error) {
687
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.NetworkError, error instanceof Error ? error.message : String(error));
688
+ }
689
+ }
690
+ return frame;
691
+ });
692
+ }
693
+ callProtocolV2(path, name, data, options) {
694
+ var _a;
695
+ return __awaiter(this, void 0, void 0, function* () {
696
+ const protocolV1Messages = this.messages;
697
+ if (!this.messagesV2) {
698
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured, 'Protocol V2 schema not configured');
699
+ }
700
+ if (!protocolV1Messages) {
701
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured);
702
+ }
703
+ const session = new transport.ProtocolV2Session({
704
+ schemas: {
705
+ protocolV1: protocolV1Messages,
706
+ protocolV2: this.messagesV2,
707
+ },
708
+ router: transport.PROTOCOL_V2_CHANNEL_USB,
709
+ writeFrame: (frame) => this.writeProtocolV2Frame(path, frame),
710
+ readFrame: () => this.receiveProtocolV2Frame(path, options === null || options === void 0 ? void 0 : options.timeoutMs),
711
+ logger: this.Log,
712
+ logPrefix: 'ProtocolV2 NodeUSB',
713
+ createTimeoutError: (_messageName, timeoutMs) => new Error(`Protocol V2 response timeout after ${timeoutMs}ms for ${name}`),
714
+ });
715
+ (_a = this.protocolV2Assemblers.get(path)) === null || _a === void 0 ? void 0 : _a.reset();
716
+ return session.call(name, data, options);
717
+ });
718
+ }
719
+ receiveData(path, dev, timeoutMs) {
720
+ return __awaiter(this, void 0, void 0, function* () {
721
+ const deadline = timeoutMs ? Date.now() + timeoutMs : undefined;
722
+ const readPacket = () => __awaiter(this, void 0, void 0, function* () {
723
+ const transferIn = this.transferInWithRetry(path, this.getOpenDevice(path), PACKET_SIZE);
724
+ return deadline
725
+ ? this.withProtocolReadTimeout(path, transferIn, Math.max(deadline - Date.now(), 1), 'V1')
726
+ : transferIn;
727
+ });
728
+ const firstPacket = timeoutMs
729
+ ? yield readPacket()
730
+ : yield this.transferInWithRetry(path, dev, PACKET_SIZE);
485
731
  const firstData = skipReportByte(firstPacket);
486
- const { length, typeId, restBuffer } = decodeProtocol.decodeChunked(toArrayBuffer(firstData));
732
+ const { length, typeId, restBuffer } = ProtocolV1.decodeFirstChunk(toArrayBuffer(firstData));
487
733
  const lengthWithHeader = Number(length) + HEADER_LENGTH;
488
734
  const decoded = new ByteBuffer__default["default"](lengthWithHeader);
489
735
  decoded.writeUint16(typeId);
@@ -492,7 +738,7 @@ class NodeUsbTransport {
492
738
  decoded.append(restBuffer);
493
739
  }
494
740
  while (decoded.offset < lengthWithHeader) {
495
- const packet = yield this.transferInWithRetry(path, this.getOpenDevice(path), PACKET_SIZE);
741
+ const packet = yield readPacket();
496
742
  const pktData = skipReportByte(packet);
497
743
  const buf = toArrayBuffer(pktData);
498
744
  if (lengthWithHeader - decoded.offset >= PAYLOAD_SIZE) {
@@ -507,7 +753,10 @@ class NodeUsbTransport {
507
753
  return Buffer.from(result).toString('hex');
508
754
  });
509
755
  }
756
+ getProtocolType(path) {
757
+ var _a;
758
+ return (_a = this.deviceProtocol.get(path)) !== null && _a !== void 0 ? _a : 'V1';
759
+ }
510
760
  }
511
761
 
512
- exports.PACKET_SIZE = PACKET_SIZE;
513
- exports["default"] = NodeUsbTransport;
762
+ module.exports = NodeUsbTransport;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onekeyfe/hd-transport-usb",
3
- "version": "1.1.26",
3
+ "version": "1.1.27-alpha.31",
4
4
  "description": "OneKey hardware wallet direct USB transport plugin (libusb)",
5
5
  "homepage": "https://github.com/OneKeyHQ/hardware-js-sdk#readme",
6
6
  "license": "MIT",
@@ -20,10 +20,10 @@
20
20
  "lint:fix": "eslint . --fix"
21
21
  },
22
22
  "dependencies": {
23
- "@onekeyfe/hd-shared": "1.1.26",
24
- "@onekeyfe/hd-transport": "1.1.26",
23
+ "@onekeyfe/hd-shared": "1.1.27-alpha.31",
24
+ "@onekeyfe/hd-transport": "1.1.27-alpha.31",
25
25
  "bytebuffer": "^5.0.1",
26
26
  "usb": "^2.14.0"
27
27
  },
28
- "gitHead": "25f2737f5e9d357764fb82f558b024f763cdd30d"
28
+ "gitHead": "73a8ecbc0e5e47038966b2f9a5ab30dc2f947d8f"
29
29
  }
package/src/index.ts CHANGED
@@ -1,14 +1,33 @@
1
1
  import ByteBuffer from 'bytebuffer';
2
2
  import * as usb from 'usb';
3
- import transport, { LogBlockCommand } from '@onekeyfe/hd-transport';
3
+ import transport, {
4
+ LogBlockCommand,
5
+ PROTOCOL_V1_CHUNK_PAYLOAD_SIZE,
6
+ PROTOCOL_V1_MESSAGE_HEADER_SIZE,
7
+ PROTOCOL_V1_REPORT_ID,
8
+ PROTOCOL_V1_USB_PACKET_SIZE,
9
+ PROTOCOL_V2_CHANNEL_USB,
10
+ PROTOCOL_V2_FRAME_MAX_BYTES,
11
+ ProtocolV2FrameAssembler,
12
+ ProtocolV2Session,
13
+ probeProtocolV2 as probeProtocolV2Helper,
14
+ } from '@onekeyfe/hd-transport';
4
15
  import { ERRORS, HardwareErrorCode, ONEKEY_WEBUSB_FILTER, wait } from '@onekeyfe/hd-shared';
5
16
 
6
- import { HEADER_LENGTH, PACKET_SIZE, PAYLOAD_SIZE, REPORT_ID } from './constants';
7
-
8
17
  import type EventEmitter from 'events';
9
- import type { AcquireInput, OneKeyDeviceInfo } from '@onekeyfe/hd-transport';
18
+ import type {
19
+ AcquireInput,
20
+ OneKeyDeviceInfo,
21
+ ProtocolType,
22
+ TransportCallOptions,
23
+ } from '@onekeyfe/hd-transport';
24
+
25
+ const { parseConfigure, ProtocolV1, check } = transport;
10
26
 
11
- const { parseConfigure, buildEncodeBuffers, decodeProtocol, receiveOne, check } = transport;
27
+ const PACKET_SIZE = PROTOCOL_V1_USB_PACKET_SIZE;
28
+ const REPORT_ID = PROTOCOL_V1_REPORT_ID;
29
+ const PAYLOAD_SIZE = PROTOCOL_V1_CHUNK_PAYLOAD_SIZE;
30
+ const HEADER_LENGTH = PROTOCOL_V1_MESSAGE_HEADER_SIZE;
12
31
 
13
32
  /** USB interface number for vendor-specific communication */
14
33
  const INTERFACE_NUMBER = 0;
@@ -25,6 +44,7 @@ const SERIAL_READ_TIMEOUT_MS = 5000;
25
44
  /** Packet I/O retry configuration (matches WebUsbTransport) */
26
45
  const PACKET_IO_MAX_RETRIES = 3;
27
46
  const PACKET_IO_RETRY_DELAY = 300;
47
+ const PROTOCOL_PROBE_TIMEOUT = 1000;
28
48
 
29
49
  /**
30
50
  * Opened device state — holds the USB device, claimed interface, and endpoints.
@@ -163,6 +183,9 @@ function toArrayBuffer(buf: Buffer): ArrayBuffer {
163
183
  export default class NodeUsbTransport {
164
184
  messages: ReturnType<typeof transport.parseConfigure> | undefined;
165
185
 
186
+ /** Protobuf schema for Protocol V2 transports. */
187
+ messagesV2: ReturnType<typeof transport.parseConfigure> | undefined;
188
+
166
189
  name = 'NodeUsbTransport';
167
190
 
168
191
  version = '';
@@ -181,6 +204,12 @@ export default class NodeUsbTransport {
181
204
  /** path → opened device state */
182
205
  private openDevices = new Map<string, OpenDevice>();
183
206
 
207
+ /** Per-path protocol type detected by active wire-level probe. */
208
+ private deviceProtocol: Map<string, ProtocolType> = new Map();
209
+
210
+ /** Per-path Protocol V2 frame assembler, preserving buffered frames during reads. */
211
+ private protocolV2Assemblers: Map<string, ProtocolV2FrameAssembler> = new Map();
212
+
184
213
  /** per-path reconnect lock to prevent concurrent reconnects */
185
214
  private reconnectLocks = new Map<string, Promise<OpenDevice>>();
186
215
 
@@ -204,6 +233,11 @@ export default class NodeUsbTransport {
204
233
  return Promise.resolve();
205
234
  }
206
235
 
236
+ configureProtocolV2(signedData: any) {
237
+ this.messagesV2 = parseConfigure(signedData);
238
+ this.Log?.debug('[NodeUsbTransport] Protocol V2 schema configured');
239
+ }
240
+
207
241
  listen() {
208
242
  // empty — could add hotplug events via usb.on('attach'/'detach')
209
243
  }
@@ -220,7 +254,7 @@ export default class NodeUsbTransport {
220
254
  if (!this.messages) {
221
255
  throw ERRORS.TypedError(HardwareErrorCode.TransportNotConfigured);
222
256
  }
223
- const encodeBuffers = buildEncodeBuffers(this.messages, name, data);
257
+ const encodeBuffers = ProtocolV1.encodeMessageChunks(this.messages, name, data);
224
258
  await this.sendAllChunksWithRetry(path, encodeBuffers);
225
259
  }
226
260
 
@@ -237,7 +271,7 @@ export default class NodeUsbTransport {
237
271
  if (!this.messages) {
238
272
  throw ERRORS.TypedError(HardwareErrorCode.TransportNotConfigured);
239
273
  }
240
- return receiveOne(this.messages, resData);
274
+ return ProtocolV1.decodeMessage(this.messages, resData);
241
275
  }
242
276
 
243
277
  /**
@@ -275,7 +309,7 @@ export default class NodeUsbTransport {
275
309
  /**
276
310
  * Acquire device — open USB device, claim interface, return path (string).
277
311
  */
278
- acquire(input: AcquireInput): Promise<string> {
312
+ async acquire(input: AcquireInput): Promise<string> {
279
313
  const path = input.path ?? '';
280
314
  if (!path) {
281
315
  throw ERRORS.TypedError(HardwareErrorCode.DeviceNotFound, 'No device path provided');
@@ -283,7 +317,8 @@ export default class NodeUsbTransport {
283
317
 
284
318
  try {
285
319
  this.openDevice(path);
286
- return Promise.resolve(path);
320
+ await this.detectProtocol(path, input.expectedProtocol);
321
+ return path;
287
322
  } catch (error: any) {
288
323
  this.Log?.debug('NodeUsbTransport acquire error: ', error);
289
324
  throw ERRORS.TypedError(HardwareErrorCode.DeviceNotFound, error.message ?? String(error));
@@ -294,6 +329,12 @@ export default class NodeUsbTransport {
294
329
  * Release device — release interface and close.
295
330
  */
296
331
  async release(path: string, _onclose?: boolean): Promise<void> {
332
+ await this.closeOpenDevice(path);
333
+ this.deviceProtocol.delete(path);
334
+ this.protocolV2Assemblers.delete(path);
335
+ }
336
+
337
+ private async closeOpenDevice(path: string): Promise<void> {
297
338
  const openDev = this.openDevices.get(path);
298
339
  if (!openDev) return;
299
340
 
@@ -322,7 +363,12 @@ export default class NodeUsbTransport {
322
363
  * Call device method — encode protobuf, send packets, receive response.
323
364
  * This is the core method that replaces LowlevelTransport's call + UsbPlugin's send/receive.
324
365
  */
325
- async call(path: string, name: string, data: Record<string, unknown>) {
366
+ async call(
367
+ path: string,
368
+ name: string,
369
+ data: Record<string, unknown>,
370
+ options?: TransportCallOptions
371
+ ) {
326
372
  this.cancelled = false;
327
373
 
328
374
  if (!this.messages) {
@@ -333,26 +379,51 @@ export default class NodeUsbTransport {
333
379
  throw ERRORS.TypedError(HardwareErrorCode.DeviceNotFound, `Device not acquired: ${path}`);
334
380
  }
335
381
 
336
- const { messages } = this;
382
+ const protocol = this.deviceProtocol.get(path) ?? 'V1';
337
383
  if (LogBlockCommand.has(name)) {
338
- this.Log?.debug('NodeUsbTransport call-', ' name: ', name);
384
+ this.Log?.debug('NodeUsbTransport call-', ' name: ', name, ' protocol: ', protocol);
339
385
  } else {
340
- this.Log?.debug('NodeUsbTransport call-', ' name: ', name, ' data: ', data);
386
+ this.Log?.debug(
387
+ 'NodeUsbTransport call-',
388
+ ' name: ',
389
+ name,
390
+ ' data: ',
391
+ data,
392
+ ' protocol: ',
393
+ protocol
394
+ );
395
+ }
396
+
397
+ if (protocol === 'V2') {
398
+ return this.callProtocolV2(path, name, data, options);
341
399
  }
342
400
 
401
+ return this.callProtocolV1(path, name, data, options);
402
+ }
403
+
404
+ private async callProtocolV1(
405
+ path: string,
406
+ name: string,
407
+ data: Record<string, unknown>,
408
+ options?: TransportCallOptions
409
+ ) {
410
+ const { messages } = this;
411
+ if (!messages) {
412
+ throw ERRORS.TypedError(HardwareErrorCode.TransportNotConfigured);
413
+ }
343
414
  // Encode protobuf message into 63-byte chunks (same as WebUsbTransport)
344
- const encodeBuffers = buildEncodeBuffers(messages, name, data);
415
+ const encodeBuffers = ProtocolV1.encodeMessageChunks(messages, name, data);
345
416
 
346
417
  // Send all chunks with retry — if any chunk fails and reconnects,
347
418
  // restart the entire send sequence from chunk 0 (device resets state on reconnect)
348
419
  await this.sendAllChunksWithRetry(path, encodeBuffers);
349
420
 
350
421
  // Receive response — re-resolve in case reconnect happened during send
351
- const resData = await this.receiveData(path, this.getOpenDevice(path));
422
+ const resData = await this.receiveData(path, this.getOpenDevice(path), options?.timeoutMs);
352
423
  if (typeof resData !== 'string') {
353
424
  throw ERRORS.TypedError(HardwareErrorCode.NetworkError, 'Returning data is not string.');
354
425
  }
355
- const jsonData = receiveOne(messages, resData);
426
+ const jsonData = ProtocolV1.decodeMessage(messages, resData);
356
427
  return check.call(jsonData);
357
428
  }
358
429
 
@@ -401,6 +472,21 @@ export default class NodeUsbTransport {
401
472
  );
402
473
  }
403
474
 
475
+ private getDeviceInterface(dev: usb.Device): usb.Interface {
476
+ const { interfaces } = dev;
477
+ if (!interfaces?.length) {
478
+ throw ERRORS.TypedError(HardwareErrorCode.DeviceNotFound, 'USB interface not found');
479
+ }
480
+
481
+ const vendorInterface = interfaces.find(iface => iface.descriptor.bInterfaceClass === 0xff);
482
+ const defaultInterface = interfaces.find(
483
+ iface => iface.descriptor.bInterfaceNumber === INTERFACE_NUMBER
484
+ );
485
+ const iface = vendorInterface ?? defaultInterface ?? interfaces[0];
486
+
487
+ return iface;
488
+ }
489
+
404
490
  /**
405
491
  * Reconnect device before retrying a failed transfer (aligned with WebUsbTransport).
406
492
  * Uses per-path lock to prevent concurrent reconnects to the same device.
@@ -423,9 +509,9 @@ export default class NodeUsbTransport {
423
509
  );
424
510
  await wait(attempt * PACKET_IO_RETRY_DELAY);
425
511
 
426
- // Close the existing device
512
+ // Close the existing device without clearing the detected protocol cache.
427
513
  try {
428
- await this.release(path);
514
+ await this.closeOpenDevice(path);
429
515
  } catch (releaseError) {
430
516
  this.Log?.debug('[NodeUsbTransport] release before retry error:', releaseError);
431
517
  }
@@ -552,7 +638,7 @@ export default class NodeUsbTransport {
552
638
  try {
553
639
  dev.timeout = TRANSFER_TIMEOUT_MS;
554
640
 
555
- const iface = dev.interface(INTERFACE_NUMBER);
641
+ const iface = this.getDeviceInterface(dev);
556
642
 
557
643
  // On Linux, detach kernel driver if active
558
644
  if (process.platform === 'linux') {
@@ -567,12 +653,14 @@ export default class NodeUsbTransport {
567
653
 
568
654
  iface.claim();
569
655
 
570
- const epIn = iface.endpoints.find(
571
- (e): e is usb.InEndpoint => e.direction === 'in' && e.address === ENDPOINT_IN
572
- );
573
- const epOut = iface.endpoints.find(
574
- (e): e is usb.OutEndpoint => e.direction === 'out' && e.address === ENDPOINT_OUT
575
- );
656
+ const epIn =
657
+ iface.endpoints.find(
658
+ (e): e is usb.InEndpoint => e.direction === 'in' && e.address === ENDPOINT_IN
659
+ ) ?? iface.endpoints.find((e): e is usb.InEndpoint => e.direction === 'in');
660
+ const epOut =
661
+ iface.endpoints.find(
662
+ (e): e is usb.OutEndpoint => e.direction === 'out' && e.address === ENDPOINT_OUT
663
+ ) ?? iface.endpoints.find((e): e is usb.OutEndpoint => e.direction === 'out');
576
664
 
577
665
  if (!epIn || !epOut) {
578
666
  throw ERRORS.TypedError(
@@ -595,17 +683,259 @@ export default class NodeUsbTransport {
595
683
  }
596
684
  }
597
685
 
686
+ private createProtocolMismatchError(expected: ProtocolType) {
687
+ return ERRORS.TypedError(
688
+ HardwareErrorCode.RuntimeError,
689
+ `Device protocol mismatch: expected ${expected}, but device did not respond to expected protocol`
690
+ );
691
+ }
692
+
693
+ private async detectProtocol(
694
+ path: string,
695
+ expectedProtocol?: ProtocolType
696
+ ): Promise<ProtocolType> {
697
+ if (expectedProtocol === 'V1') {
698
+ if (await this.probeProtocolV1(path)) {
699
+ this.deviceProtocol.set(path, 'V1');
700
+ this.Log?.debug(`[NodeUsbTransport] detectProtocol: path=${path} -> V1 (expected)`);
701
+ return 'V1';
702
+ }
703
+ throw this.createProtocolMismatchError(expectedProtocol);
704
+ }
705
+
706
+ if (expectedProtocol === 'V2') {
707
+ if (await this.probeProtocolV2(path)) {
708
+ this.deviceProtocol.set(path, 'V2');
709
+ this.Log?.debug(`[NodeUsbTransport] detectProtocol: path=${path} -> V2 (expected)`);
710
+ return 'V2';
711
+ }
712
+ throw this.createProtocolMismatchError(expectedProtocol);
713
+ }
714
+
715
+ if (this.deviceProtocol.get(path) === 'V2' && (await this.probeProtocolV2(path))) {
716
+ this.deviceProtocol.set(path, 'V2');
717
+ this.Log?.debug(`[NodeUsbTransport] detectProtocol: path=${path} -> V2 (cached)`);
718
+ return 'V2';
719
+ }
720
+
721
+ let protocol: ProtocolType = 'V1';
722
+ if (!(await this.probeProtocolV1(path)) && (await this.probeProtocolV2(path))) {
723
+ protocol = 'V2';
724
+ }
725
+ this.deviceProtocol.set(path, protocol);
726
+ this.Log?.debug(`[NodeUsbTransport] detectProtocol: path=${path} -> ${protocol}`);
727
+ return protocol;
728
+ }
729
+
730
+ private async resetConnectionAfterProbe(path: string) {
731
+ this.protocolV2Assemblers.get(path)?.reset();
732
+
733
+ try {
734
+ await this.closeOpenDevice(path);
735
+ } catch (error) {
736
+ this.Log?.debug('[NodeUsbTransport] close after protocol probe error:', error);
737
+ }
738
+
739
+ await this.enumerate();
740
+ this.openDevice(path);
741
+ }
742
+
743
+ private async withProtocolReadTimeout<T>(
744
+ path: string,
745
+ promise: Promise<T>,
746
+ timeoutMs: number,
747
+ protocol: ProtocolType
748
+ ): Promise<T> {
749
+ let timer: ReturnType<typeof setTimeout> | undefined;
750
+ let timedOut = false;
751
+ const waitForeverAfterTimeout = () => new Promise<never>(() => {});
752
+ const guardedPromise = promise.then(
753
+ value => (timedOut ? waitForeverAfterTimeout() : value),
754
+ error => {
755
+ if (timedOut) {
756
+ return waitForeverAfterTimeout();
757
+ }
758
+ throw error;
759
+ }
760
+ );
761
+ try {
762
+ return await Promise.race([
763
+ guardedPromise,
764
+ new Promise<never>((_, reject) => {
765
+ timer = setTimeout(async () => {
766
+ timedOut = true;
767
+ try {
768
+ await this.resetConnectionAfterProbe(path);
769
+ } catch (error) {
770
+ this.Log?.debug(
771
+ `[NodeUsbTransport] reset after Protocol ${protocol} timeout failed:`,
772
+ error
773
+ );
774
+ } finally {
775
+ reject(new Error(`Protocol ${protocol} read timeout after ${timeoutMs}ms`));
776
+ }
777
+ }, timeoutMs);
778
+ }),
779
+ ]);
780
+ } finally {
781
+ if (timer) clearTimeout(timer);
782
+ }
783
+ }
784
+
785
+ private async probeProtocolV1(path: string) {
786
+ if (!this.messages) {
787
+ return false;
788
+ }
789
+
790
+ try {
791
+ await this.callProtocolV1(path, 'Initialize', {}, { timeoutMs: PROTOCOL_PROBE_TIMEOUT });
792
+ return true;
793
+ } catch (error) {
794
+ this.Log?.debug('[NodeUsbTransport] Protocol V1 Initialize probe failed:', error);
795
+ return false;
796
+ }
797
+ }
798
+
799
+ private async probeProtocolV2(path: string) {
800
+ if (!this.messages || !this.messagesV2) {
801
+ return false;
802
+ }
803
+
804
+ return probeProtocolV2Helper({
805
+ call: (name, data, options) => this.callProtocolV2(path, name, data, options),
806
+ timeoutMs: PROTOCOL_PROBE_TIMEOUT,
807
+ logger: this.Log,
808
+ logPrefix: 'ProtocolV2 NodeUSB',
809
+ onProbeFailed: () => this.resetConnectionAfterProbe(path),
810
+ });
811
+ }
812
+
813
+ private async writeProtocolV2Frame(path: string, frame: Uint8Array) {
814
+ let lastError: unknown;
815
+ for (let attempt = 1; attempt <= PACKET_IO_MAX_RETRIES; attempt++) {
816
+ if (this.cancelled) {
817
+ throw ERRORS.TypedError(HardwareErrorCode.DeviceInterruptedFromOutside, 'Cancelled');
818
+ }
819
+ try {
820
+ await transferOutOnce(this.getOpenDevice(path).epOut, Buffer.from(frame));
821
+ return;
822
+ } catch (error) {
823
+ lastError = error;
824
+ const shouldRetry = attempt < PACKET_IO_MAX_RETRIES && this.isRetryableError(error);
825
+ if (!shouldRetry) {
826
+ throw error;
827
+ }
828
+ try {
829
+ await this.reconnectForRetry(path, 'out', attempt, error);
830
+ } catch (reconnectError) {
831
+ lastError = reconnectError;
832
+ this.Log?.debug(
833
+ `[NodeUsbTransport] Protocol V2 write reconnect failed on retry ${attempt}/${PACKET_IO_MAX_RETRIES}: ${this.getErrorMessage(
834
+ reconnectError
835
+ )}`
836
+ );
837
+ break;
838
+ }
839
+ }
840
+ }
841
+ throw lastError;
842
+ }
843
+
844
+ private async receiveProtocolV2Frame(path: string, timeoutMs?: number): Promise<Uint8Array> {
845
+ let assembler = this.protocolV2Assemblers.get(path);
846
+ if (!assembler) {
847
+ assembler = new ProtocolV2FrameAssembler(PROTOCOL_V2_FRAME_MAX_BYTES);
848
+ this.protocolV2Assemblers.set(path, assembler);
849
+ }
850
+
851
+ let frame: Uint8Array | undefined = assembler.push(new Uint8Array(0));
852
+ const deadline = timeoutMs ? Date.now() + timeoutMs : undefined;
853
+
854
+ while (!frame) {
855
+ const transferIn = this.transferInWithRetry(
856
+ path,
857
+ this.getOpenDevice(path),
858
+ PROTOCOL_V2_FRAME_MAX_BYTES
859
+ );
860
+ const packet = deadline
861
+ ? await this.withProtocolReadTimeout(
862
+ path,
863
+ transferIn,
864
+ Math.max(deadline - Date.now(), 1),
865
+ 'V2'
866
+ )
867
+ : await transferIn;
868
+ const bytes = new Uint8Array(
869
+ packet.buffer.slice(packet.byteOffset, packet.byteOffset + packet.byteLength)
870
+ );
871
+ try {
872
+ frame = assembler.push(bytes);
873
+ } catch (error) {
874
+ throw ERRORS.TypedError(
875
+ HardwareErrorCode.NetworkError,
876
+ error instanceof Error ? error.message : String(error)
877
+ );
878
+ }
879
+ }
880
+ return frame;
881
+ }
882
+
883
+ private async callProtocolV2(
884
+ path: string,
885
+ name: string,
886
+ data: Record<string, unknown>,
887
+ options?: TransportCallOptions
888
+ ) {
889
+ const protocolV1Messages = this.messages;
890
+ if (!this.messagesV2) {
891
+ throw ERRORS.TypedError(
892
+ HardwareErrorCode.TransportNotConfigured,
893
+ 'Protocol V2 schema not configured'
894
+ );
895
+ }
896
+ if (!protocolV1Messages) {
897
+ throw ERRORS.TypedError(HardwareErrorCode.TransportNotConfigured);
898
+ }
899
+
900
+ const session = new ProtocolV2Session({
901
+ schemas: {
902
+ protocolV1: protocolV1Messages,
903
+ protocolV2: this.messagesV2,
904
+ },
905
+ router: PROTOCOL_V2_CHANNEL_USB,
906
+ writeFrame: (frame: Uint8Array) => this.writeProtocolV2Frame(path, frame),
907
+ readFrame: () => this.receiveProtocolV2Frame(path, options?.timeoutMs),
908
+ logger: this.Log,
909
+ logPrefix: 'ProtocolV2 NodeUSB',
910
+ createTimeoutError: (_messageName: string, timeoutMs: number) =>
911
+ new Error(`Protocol V2 response timeout after ${timeoutMs}ms for ${name}`),
912
+ });
913
+
914
+ this.protocolV2Assemblers.get(path)?.reset();
915
+ return session.call(name, data, options);
916
+ }
917
+
598
918
  /**
599
919
  * Receive a complete protobuf response from the device.
600
920
  * Reads 64-byte packets, strips 0x3F marker, reassembles into hex string.
601
921
  */
602
- private async receiveData(path: string, dev: OpenDevice): Promise<string> {
922
+ private async receiveData(path: string, dev: OpenDevice, timeoutMs?: number): Promise<string> {
923
+ const deadline = timeoutMs ? Date.now() + timeoutMs : undefined;
924
+ const readPacket = async () => {
925
+ const transferIn = this.transferInWithRetry(path, this.getOpenDevice(path), PACKET_SIZE);
926
+ return deadline
927
+ ? this.withProtocolReadTimeout(path, transferIn, Math.max(deadline - Date.now(), 1), 'V1')
928
+ : transferIn;
929
+ };
930
+
603
931
  // Read first packet, skip report byte
604
- const firstPacket = await this.transferInWithRetry(path, dev, PACKET_SIZE);
932
+ const firstPacket = timeoutMs
933
+ ? await readPacket()
934
+ : await this.transferInWithRetry(path, dev, PACKET_SIZE);
605
935
  const firstData = skipReportByte(firstPacket);
606
936
 
607
937
  // Decode header: ## marker → { typeId, length, restBuffer }
608
- const { length, typeId, restBuffer } = decodeProtocol.decodeChunked(toArrayBuffer(firstData));
938
+ const { length, typeId, restBuffer } = ProtocolV1.decodeFirstChunk(toArrayBuffer(firstData));
609
939
 
610
940
  // Allocate result: typeId(2) + length(4) + payload(length)
611
941
  const lengthWithHeader = Number(length) + HEADER_LENGTH;
@@ -619,7 +949,7 @@ export default class NodeUsbTransport {
619
949
  // Read subsequent packets until complete
620
950
  // Re-resolve device on each iteration so we use a fresh handle after any reconnect
621
951
  while (decoded.offset < lengthWithHeader) {
622
- const packet = await this.transferInWithRetry(path, this.getOpenDevice(path), PACKET_SIZE);
952
+ const packet = await readPacket();
623
953
  const pktData = skipReportByte(packet);
624
954
  const buf = toArrayBuffer(pktData);
625
955
  if (lengthWithHeader - decoded.offset >= PAYLOAD_SIZE) {
@@ -633,6 +963,8 @@ export default class NodeUsbTransport {
633
963
  const result = decoded.toBuffer();
634
964
  return Buffer.from(result as unknown as ArrayBuffer).toString('hex');
635
965
  }
636
- }
637
966
 
638
- export { PACKET_SIZE } from './constants';
967
+ getProtocolType(path: string): ProtocolType {
968
+ return this.deviceProtocol.get(path) ?? 'V1';
969
+ }
970
+ }
@@ -1,5 +0,0 @@
1
- export declare const PACKET_SIZE = 64;
2
- export declare const REPORT_ID = 63;
3
- export declare const PAYLOAD_SIZE: number;
4
- export declare const HEADER_LENGTH = 6;
5
- //# sourceMappingURL=constants.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AACA,eAAO,MAAM,WAAW,KAAK,CAAC;AAG9B,eAAO,MAAM,SAAS,KAAO,CAAC;AAG9B,eAAO,MAAM,YAAY,QAAkB,CAAC;AAG5C,eAAO,MAAM,aAAa,IAAI,CAAC"}
package/src/constants.ts DELETED
@@ -1,11 +0,0 @@
1
- /** USB packet size in bytes */
2
- export const PACKET_SIZE = 64;
3
-
4
- /** Protocol marker byte (0x3F = '?') — first byte of every packet */
5
- export const REPORT_ID = 0x3f;
6
-
7
- /** Usable payload per packet after stripping the 0x3F report byte */
8
- export const PAYLOAD_SIZE = PACKET_SIZE - 1;
9
-
10
- /** Protocol header length: typeId (2 bytes) + length (4 bytes) */
11
- export const HEADER_LENGTH = 6;