@onekeyfe/hd-transport-usb 1.1.27-alpha.4 → 1.1.27-alpha.41

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,30 @@ 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 createProtocolDetectionError;
49
+ private detectProtocol;
50
+ private resetConnectionAfterProbe;
51
+ private withProtocolReadTimeout;
52
+ private probeProtocolV1;
53
+ private probeProtocolV2;
54
+ private writeProtocolV2Frame;
55
+ private receiveProtocolV2Frame;
56
+ private callProtocolV2;
42
57
  private receiveData;
58
+ getProtocolType(path: string): ProtocolType | undefined;
43
59
  }
44
60
 
45
- export { PACKET_SIZE, NodeUsbTransport as default };
61
+ 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;IAqB7C,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;YAwClB,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;IAOnC,OAAO,CAAC,4BAA4B;YAOtB,cAAc;YA4Cd,yBAAyB;YAazB,uBAAuB;YA0CvB,eAAe;YAcf,eAAe;YAcf,oBAAoB;YA+BpB,sBAAsB;YAuCtB,cAAc;YAuCd,WAAW;IA6CzB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS;CAGxD"}
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,31 @@ 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
+ this.cancelled = false;
248
+ const path = (_a = input.path) !== null && _a !== void 0 ? _a : '';
249
+ if (!path) {
250
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.DeviceNotFound, 'No device path provided');
251
+ }
252
+ try {
253
+ this.openDevice(path);
254
+ yield this.detectProtocol(path, input.expectedProtocol);
255
+ return path;
256
+ }
257
+ catch (error) {
258
+ (_b = this.Log) === null || _b === void 0 ? void 0 : _b.debug('NodeUsbTransport acquire error: ', error);
259
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.DeviceNotFound, (_c = error.message) !== null && _c !== void 0 ? _c : String(error));
260
+ }
261
+ });
253
262
  }
254
263
  release(path, _onclose) {
264
+ return __awaiter(this, void 0, void 0, function* () {
265
+ yield this.closeOpenDevice(path);
266
+ this.deviceProtocol.delete(path);
267
+ this.protocolV2Assemblers.delete(path);
268
+ });
269
+ }
270
+ closeOpenDevice(path) {
255
271
  return __awaiter(this, void 0, void 0, function* () {
256
272
  const openDev = this.openDevices.get(path);
257
273
  if (!openDev)
@@ -278,7 +294,7 @@ class NodeUsbTransport {
278
294
  this.openDevices.delete(path);
279
295
  });
280
296
  }
281
- call(path, name, data) {
297
+ call(path, name, data, options) {
282
298
  var _a, _b;
283
299
  return __awaiter(this, void 0, void 0, function* () {
284
300
  this.cancelled = false;
@@ -288,20 +304,35 @@ class NodeUsbTransport {
288
304
  if (!this.openDevices.get(path)) {
289
305
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.DeviceNotFound, `Device not acquired: ${path}`);
290
306
  }
291
- const { messages } = this;
307
+ const protocol = this.deviceProtocol.get(path);
308
+ if (!protocol) {
309
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, `Device protocol has not been detected for ${path}`);
310
+ }
292
311
  if (transport.LogBlockCommand.has(name)) {
293
- (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('NodeUsbTransport call-', ' name: ', name);
312
+ (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('NodeUsbTransport call-', ' name: ', name, ' protocol: ', protocol);
294
313
  }
295
314
  else {
296
- (_b = this.Log) === null || _b === void 0 ? void 0 : _b.debug('NodeUsbTransport call-', ' name: ', name, ' data: ', data);
315
+ (_b = this.Log) === null || _b === void 0 ? void 0 : _b.debug('NodeUsbTransport call-', ' name: ', name, ' data: ', data, ' protocol: ', protocol);
316
+ }
317
+ if (protocol === 'V2') {
318
+ return this.callProtocolV2(path, name, data, options);
319
+ }
320
+ return this.callProtocolV1(path, name, data, options);
321
+ });
322
+ }
323
+ callProtocolV1(path, name, data, options) {
324
+ return __awaiter(this, void 0, void 0, function* () {
325
+ const { messages } = this;
326
+ if (!messages) {
327
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured);
297
328
  }
298
- const encodeBuffers = buildEncodeBuffers(messages, name, data);
329
+ const encodeBuffers = ProtocolV1.encodeMessageChunks(messages, name, data);
299
330
  yield this.sendAllChunksWithRetry(path, encodeBuffers);
300
- const resData = yield this.receiveData(path, this.getOpenDevice(path));
331
+ const resData = yield this.receiveData(path, this.getOpenDevice(path), options === null || options === void 0 ? void 0 : options.timeoutMs);
301
332
  if (typeof resData !== 'string') {
302
333
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.NetworkError, 'Returning data is not string.');
303
334
  }
304
- const jsonData = receiveOne(messages, resData);
335
+ const jsonData = ProtocolV1.decodeMessage(messages, resData);
305
336
  return check.call(jsonData);
306
337
  });
307
338
  }
@@ -341,6 +372,17 @@ class NodeUsbTransport {
341
372
  message.includes('timeout') ||
342
373
  message.includes('interrupt'));
343
374
  }
375
+ getDeviceInterface(dev) {
376
+ var _a;
377
+ const { interfaces } = dev;
378
+ if (!(interfaces === null || interfaces === void 0 ? void 0 : interfaces.length)) {
379
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.DeviceNotFound, 'USB interface not found');
380
+ }
381
+ const vendorInterface = interfaces.find(iface => iface.descriptor.bInterfaceClass === 0xff);
382
+ const defaultInterface = interfaces.find(iface => iface.descriptor.bInterfaceNumber === INTERFACE_NUMBER);
383
+ const iface = (_a = vendorInterface !== null && vendorInterface !== void 0 ? vendorInterface : defaultInterface) !== null && _a !== void 0 ? _a : interfaces[0];
384
+ return iface;
385
+ }
344
386
  reconnectForRetry(path, direction, attempt, error) {
345
387
  const existing = this.reconnectLocks.get(path);
346
388
  if (existing)
@@ -350,7 +392,7 @@ class NodeUsbTransport {
350
392
  (_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
393
  yield hdShared.wait(attempt * PACKET_IO_RETRY_DELAY);
352
394
  try {
353
- yield this.release(path);
395
+ yield this.closeOpenDevice(path);
354
396
  }
355
397
  catch (releaseError) {
356
398
  (_b = this.Log) === null || _b === void 0 ? void 0 : _b.debug('[NodeUsbTransport] release before retry error:', releaseError);
@@ -437,7 +479,7 @@ class NodeUsbTransport {
437
479
  });
438
480
  }
439
481
  openDevice(path) {
440
- var _a;
482
+ var _a, _b, _c;
441
483
  const existing = this.openDevices.get(path);
442
484
  if (existing)
443
485
  return;
@@ -450,19 +492,19 @@ class NodeUsbTransport {
450
492
  dev.open();
451
493
  try {
452
494
  dev.timeout = TRANSFER_TIMEOUT_MS;
453
- const iface = dev.interface(INTERFACE_NUMBER);
495
+ const iface = this.getDeviceInterface(dev);
454
496
  if (process.platform === 'linux') {
455
497
  try {
456
498
  if (iface.isKernelDriverActive()) {
457
499
  iface.detachKernelDriver();
458
500
  }
459
501
  }
460
- catch (_b) {
502
+ catch (_d) {
461
503
  }
462
504
  }
463
505
  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);
506
+ 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');
507
+ 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
508
  if (!epIn || !epOut) {
467
509
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.DeviceNotFound, 'USB endpoints not found (expected IN 0x81, OUT 0x01)');
468
510
  }
@@ -474,16 +516,232 @@ class NodeUsbTransport {
474
516
  try {
475
517
  dev.close();
476
518
  }
477
- catch (_c) {
519
+ catch (_e) {
478
520
  }
479
521
  throw err;
480
522
  }
481
523
  }
482
- receiveData(path, dev) {
524
+ createProtocolMismatchError(expected) {
525
+ return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, `Device protocol mismatch: expected ${expected}, but device did not respond to expected protocol`);
526
+ }
527
+ createProtocolDetectionError() {
528
+ return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, 'Unable to detect USB protocol: device did not respond to Protocol V1 Initialize or Protocol V2 Ping');
529
+ }
530
+ detectProtocol(path, expectedProtocol) {
531
+ var _a, _b, _c, _d, _e;
532
+ return __awaiter(this, void 0, void 0, function* () {
533
+ if (expectedProtocol === 'V1') {
534
+ if (yield this.probeProtocolV1(path)) {
535
+ this.deviceProtocol.set(path, 'V1');
536
+ (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug(`[NodeUsbTransport] detectProtocol: path=${path} -> V1 (expected)`);
537
+ return 'V1';
538
+ }
539
+ throw this.createProtocolMismatchError(expectedProtocol);
540
+ }
541
+ if (expectedProtocol === 'V2') {
542
+ if (yield this.probeProtocolV2(path)) {
543
+ this.deviceProtocol.set(path, 'V2');
544
+ (_b = this.Log) === null || _b === void 0 ? void 0 : _b.debug(`[NodeUsbTransport] detectProtocol: path=${path} -> V2 (expected)`);
545
+ return 'V2';
546
+ }
547
+ throw this.createProtocolMismatchError(expectedProtocol);
548
+ }
549
+ if (this.deviceProtocol.get(path) === 'V2' && (yield this.probeProtocolV2(path))) {
550
+ this.deviceProtocol.set(path, 'V2');
551
+ (_c = this.Log) === null || _c === void 0 ? void 0 : _c.debug(`[NodeUsbTransport] detectProtocol: path=${path} -> V2 (cached)`);
552
+ return 'V2';
553
+ }
554
+ if (yield this.probeProtocolV1(path)) {
555
+ this.deviceProtocol.set(path, 'V1');
556
+ (_d = this.Log) === null || _d === void 0 ? void 0 : _d.debug(`[NodeUsbTransport] detectProtocol: path=${path} -> V1`);
557
+ return 'V1';
558
+ }
559
+ if (yield this.probeProtocolV2(path)) {
560
+ this.deviceProtocol.set(path, 'V2');
561
+ (_e = this.Log) === null || _e === void 0 ? void 0 : _e.debug(`[NodeUsbTransport] detectProtocol: path=${path} -> V2`);
562
+ return 'V2';
563
+ }
564
+ this.deviceProtocol.delete(path);
565
+ throw this.createProtocolDetectionError();
566
+ });
567
+ }
568
+ resetConnectionAfterProbe(path) {
569
+ var _a, _b;
483
570
  return __awaiter(this, void 0, void 0, function* () {
484
- const firstPacket = yield this.transferInWithRetry(path, dev, PACKET_SIZE);
571
+ (_a = this.protocolV2Assemblers.get(path)) === null || _a === void 0 ? void 0 : _a.reset();
572
+ try {
573
+ yield this.closeOpenDevice(path);
574
+ }
575
+ catch (error) {
576
+ (_b = this.Log) === null || _b === void 0 ? void 0 : _b.debug('[NodeUsbTransport] close after protocol probe error:', error);
577
+ }
578
+ yield this.enumerate();
579
+ this.openDevice(path);
580
+ });
581
+ }
582
+ withProtocolReadTimeout(path, promise, timeoutMs, protocol) {
583
+ return __awaiter(this, void 0, void 0, function* () {
584
+ let timer;
585
+ let timedOut = false;
586
+ const waitForeverAfterTimeout = () => new Promise(() => { });
587
+ const guardedPromise = promise.then(value => (timedOut ? waitForeverAfterTimeout() : value), error => {
588
+ if (timedOut) {
589
+ return waitForeverAfterTimeout();
590
+ }
591
+ throw error;
592
+ });
593
+ try {
594
+ return yield Promise.race([
595
+ guardedPromise,
596
+ new Promise((_, reject) => {
597
+ timer = setTimeout(() => __awaiter(this, void 0, void 0, function* () {
598
+ var _a;
599
+ timedOut = true;
600
+ try {
601
+ yield this.resetConnectionAfterProbe(path);
602
+ }
603
+ catch (error) {
604
+ (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug(`[NodeUsbTransport] reset after Protocol ${protocol} timeout failed:`, error);
605
+ }
606
+ finally {
607
+ reject(new Error(`Protocol ${protocol} read timeout after ${timeoutMs}ms`));
608
+ }
609
+ }), timeoutMs);
610
+ }),
611
+ ]);
612
+ }
613
+ finally {
614
+ if (timer)
615
+ clearTimeout(timer);
616
+ }
617
+ });
618
+ }
619
+ probeProtocolV1(path) {
620
+ var _a;
621
+ return __awaiter(this, void 0, void 0, function* () {
622
+ if (!this.messages) {
623
+ return false;
624
+ }
625
+ try {
626
+ yield this.callProtocolV1(path, 'Initialize', {}, { timeoutMs: PROTOCOL_PROBE_TIMEOUT });
627
+ return true;
628
+ }
629
+ catch (error) {
630
+ (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('[NodeUsbTransport] Protocol V1 Initialize probe failed:', error);
631
+ return false;
632
+ }
633
+ });
634
+ }
635
+ probeProtocolV2(path) {
636
+ return __awaiter(this, void 0, void 0, function* () {
637
+ if (!this.messages || !this.messagesV2) {
638
+ return false;
639
+ }
640
+ return transport.probeProtocolV2({
641
+ call: (name, data, options) => this.callProtocolV2(path, name, data, options),
642
+ timeoutMs: PROTOCOL_PROBE_TIMEOUT,
643
+ logger: this.Log,
644
+ logPrefix: 'ProtocolV2 NodeUSB',
645
+ onProbeFailed: () => this.resetConnectionAfterProbe(path),
646
+ });
647
+ });
648
+ }
649
+ writeProtocolV2Frame(path, frame) {
650
+ var _a;
651
+ return __awaiter(this, void 0, void 0, function* () {
652
+ let lastError;
653
+ for (let attempt = 1; attempt <= PACKET_IO_MAX_RETRIES; attempt++) {
654
+ if (this.cancelled) {
655
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.DeviceInterruptedFromOutside, 'Cancelled');
656
+ }
657
+ try {
658
+ yield transferOutOnce(this.getOpenDevice(path).epOut, Buffer.from(frame));
659
+ return;
660
+ }
661
+ catch (error) {
662
+ lastError = error;
663
+ const shouldRetry = attempt < PACKET_IO_MAX_RETRIES && this.isRetryableError(error);
664
+ if (!shouldRetry) {
665
+ throw error;
666
+ }
667
+ try {
668
+ yield this.reconnectForRetry(path, 'out', attempt, error);
669
+ }
670
+ catch (reconnectError) {
671
+ lastError = reconnectError;
672
+ (_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)}`);
673
+ break;
674
+ }
675
+ }
676
+ }
677
+ throw lastError;
678
+ });
679
+ }
680
+ receiveProtocolV2Frame(path, timeoutMs) {
681
+ return __awaiter(this, void 0, void 0, function* () {
682
+ let assembler = this.protocolV2Assemblers.get(path);
683
+ if (!assembler) {
684
+ assembler = new transport.ProtocolV2FrameAssembler(transport.PROTOCOL_V2_FRAME_MAX_BYTES);
685
+ this.protocolV2Assemblers.set(path, assembler);
686
+ }
687
+ let frame = assembler.push(new Uint8Array(0));
688
+ const deadline = timeoutMs ? Date.now() + timeoutMs : undefined;
689
+ while (!frame) {
690
+ const transferIn = this.transferInWithRetry(path, this.getOpenDevice(path), transport.PROTOCOL_V2_FRAME_MAX_BYTES);
691
+ const packet = deadline
692
+ ? yield this.withProtocolReadTimeout(path, transferIn, Math.max(deadline - Date.now(), 1), 'V2')
693
+ : yield transferIn;
694
+ const bytes = new Uint8Array(packet.buffer.slice(packet.byteOffset, packet.byteOffset + packet.byteLength));
695
+ try {
696
+ frame = assembler.push(bytes);
697
+ }
698
+ catch (error) {
699
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.NetworkError, error instanceof Error ? error.message : String(error));
700
+ }
701
+ }
702
+ return frame;
703
+ });
704
+ }
705
+ callProtocolV2(path, name, data, options) {
706
+ var _a;
707
+ return __awaiter(this, void 0, void 0, function* () {
708
+ const protocolV1Messages = this.messages;
709
+ if (!this.messagesV2) {
710
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured, 'Protocol V2 schema not configured');
711
+ }
712
+ if (!protocolV1Messages) {
713
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured);
714
+ }
715
+ const session = new transport.ProtocolV2Session({
716
+ schemas: {
717
+ protocolV1: protocolV1Messages,
718
+ protocolV2: this.messagesV2,
719
+ },
720
+ router: transport.PROTOCOL_V2_CHANNEL_USB,
721
+ writeFrame: (frame) => this.writeProtocolV2Frame(path, frame),
722
+ readFrame: () => this.receiveProtocolV2Frame(path, options === null || options === void 0 ? void 0 : options.timeoutMs),
723
+ logger: this.Log,
724
+ logPrefix: 'ProtocolV2 NodeUSB',
725
+ createTimeoutError: (_messageName, timeoutMs) => new Error(`Protocol V2 response timeout after ${timeoutMs}ms for ${name}`),
726
+ });
727
+ (_a = this.protocolV2Assemblers.get(path)) === null || _a === void 0 ? void 0 : _a.reset();
728
+ return session.call(name, data, options);
729
+ });
730
+ }
731
+ receiveData(path, dev, timeoutMs) {
732
+ return __awaiter(this, void 0, void 0, function* () {
733
+ const deadline = timeoutMs ? Date.now() + timeoutMs : undefined;
734
+ const readPacket = () => __awaiter(this, void 0, void 0, function* () {
735
+ const transferIn = this.transferInWithRetry(path, this.getOpenDevice(path), PACKET_SIZE);
736
+ return deadline
737
+ ? this.withProtocolReadTimeout(path, transferIn, Math.max(deadline - Date.now(), 1), 'V1')
738
+ : transferIn;
739
+ });
740
+ const firstPacket = timeoutMs
741
+ ? yield readPacket()
742
+ : yield this.transferInWithRetry(path, dev, PACKET_SIZE);
485
743
  const firstData = skipReportByte(firstPacket);
486
- const { length, typeId, restBuffer } = decodeProtocol.decodeChunked(toArrayBuffer(firstData));
744
+ const { length, typeId, restBuffer } = ProtocolV1.decodeFirstChunk(toArrayBuffer(firstData));
487
745
  const lengthWithHeader = Number(length) + HEADER_LENGTH;
488
746
  const decoded = new ByteBuffer__default["default"](lengthWithHeader);
489
747
  decoded.writeUint16(typeId);
@@ -492,7 +750,7 @@ class NodeUsbTransport {
492
750
  decoded.append(restBuffer);
493
751
  }
494
752
  while (decoded.offset < lengthWithHeader) {
495
- const packet = yield this.transferInWithRetry(path, this.getOpenDevice(path), PACKET_SIZE);
753
+ const packet = yield readPacket();
496
754
  const pktData = skipReportByte(packet);
497
755
  const buf = toArrayBuffer(pktData);
498
756
  if (lengthWithHeader - decoded.offset >= PAYLOAD_SIZE) {
@@ -507,7 +765,9 @@ class NodeUsbTransport {
507
765
  return Buffer.from(result).toString('hex');
508
766
  });
509
767
  }
768
+ getProtocolType(path) {
769
+ return this.deviceProtocol.get(path);
770
+ }
510
771
  }
511
772
 
512
- exports.PACKET_SIZE = PACKET_SIZE;
513
- exports["default"] = NodeUsbTransport;
773
+ module.exports = NodeUsbTransport;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onekeyfe/hd-transport-usb",
3
- "version": "1.1.27-alpha.4",
3
+ "version": "1.1.27-alpha.41",
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.27-alpha.4",
24
- "@onekeyfe/hd-transport": "1.1.27-alpha.4",
23
+ "@onekeyfe/hd-shared": "1.1.27-alpha.41",
24
+ "@onekeyfe/hd-transport": "1.1.27-alpha.41",
25
25
  "bytebuffer": "^5.0.1",
26
26
  "usb": "^2.14.0"
27
27
  },
28
- "gitHead": "93d0e7734f8c3a6380be10f74de203f0ba785bb8"
28
+ "gitHead": "09cece9f0c9d85f55ae2f7f0458ed7d8833483d2"
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,9 @@ 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> {
313
+ this.cancelled = false;
314
+
279
315
  const path = input.path ?? '';
280
316
  if (!path) {
281
317
  throw ERRORS.TypedError(HardwareErrorCode.DeviceNotFound, 'No device path provided');
@@ -283,7 +319,8 @@ export default class NodeUsbTransport {
283
319
 
284
320
  try {
285
321
  this.openDevice(path);
286
- return Promise.resolve(path);
322
+ await this.detectProtocol(path, input.expectedProtocol);
323
+ return path;
287
324
  } catch (error: any) {
288
325
  this.Log?.debug('NodeUsbTransport acquire error: ', error);
289
326
  throw ERRORS.TypedError(HardwareErrorCode.DeviceNotFound, error.message ?? String(error));
@@ -294,6 +331,12 @@ export default class NodeUsbTransport {
294
331
  * Release device — release interface and close.
295
332
  */
296
333
  async release(path: string, _onclose?: boolean): Promise<void> {
334
+ await this.closeOpenDevice(path);
335
+ this.deviceProtocol.delete(path);
336
+ this.protocolV2Assemblers.delete(path);
337
+ }
338
+
339
+ private async closeOpenDevice(path: string): Promise<void> {
297
340
  const openDev = this.openDevices.get(path);
298
341
  if (!openDev) return;
299
342
 
@@ -322,7 +365,12 @@ export default class NodeUsbTransport {
322
365
  * Call device method — encode protobuf, send packets, receive response.
323
366
  * This is the core method that replaces LowlevelTransport's call + UsbPlugin's send/receive.
324
367
  */
325
- async call(path: string, name: string, data: Record<string, unknown>) {
368
+ async call(
369
+ path: string,
370
+ name: string,
371
+ data: Record<string, unknown>,
372
+ options?: TransportCallOptions
373
+ ) {
326
374
  this.cancelled = false;
327
375
 
328
376
  if (!this.messages) {
@@ -333,26 +381,57 @@ export default class NodeUsbTransport {
333
381
  throw ERRORS.TypedError(HardwareErrorCode.DeviceNotFound, `Device not acquired: ${path}`);
334
382
  }
335
383
 
336
- const { messages } = this;
384
+ const protocol = this.deviceProtocol.get(path);
385
+ if (!protocol) {
386
+ throw ERRORS.TypedError(
387
+ HardwareErrorCode.RuntimeError,
388
+ `Device protocol has not been detected for ${path}`
389
+ );
390
+ }
337
391
  if (LogBlockCommand.has(name)) {
338
- this.Log?.debug('NodeUsbTransport call-', ' name: ', name);
392
+ this.Log?.debug('NodeUsbTransport call-', ' name: ', name, ' protocol: ', protocol);
339
393
  } else {
340
- this.Log?.debug('NodeUsbTransport call-', ' name: ', name, ' data: ', data);
394
+ this.Log?.debug(
395
+ 'NodeUsbTransport call-',
396
+ ' name: ',
397
+ name,
398
+ ' data: ',
399
+ data,
400
+ ' protocol: ',
401
+ protocol
402
+ );
341
403
  }
342
404
 
405
+ if (protocol === 'V2') {
406
+ return this.callProtocolV2(path, name, data, options);
407
+ }
408
+
409
+ return this.callProtocolV1(path, name, data, options);
410
+ }
411
+
412
+ private async callProtocolV1(
413
+ path: string,
414
+ name: string,
415
+ data: Record<string, unknown>,
416
+ options?: TransportCallOptions
417
+ ) {
418
+ const { messages } = this;
419
+ if (!messages) {
420
+ throw ERRORS.TypedError(HardwareErrorCode.TransportNotConfigured);
421
+ }
343
422
  // Encode protobuf message into 63-byte chunks (same as WebUsbTransport)
344
- const encodeBuffers = buildEncodeBuffers(messages, name, data);
423
+ const encodeBuffers = ProtocolV1.encodeMessageChunks(messages, name, data);
345
424
 
346
425
  // Send all chunks with retry — if any chunk fails and reconnects,
347
426
  // restart the entire send sequence from chunk 0 (device resets state on reconnect)
348
427
  await this.sendAllChunksWithRetry(path, encodeBuffers);
349
428
 
350
429
  // Receive response — re-resolve in case reconnect happened during send
351
- const resData = await this.receiveData(path, this.getOpenDevice(path));
430
+ const resData = await this.receiveData(path, this.getOpenDevice(path), options?.timeoutMs);
352
431
  if (typeof resData !== 'string') {
353
432
  throw ERRORS.TypedError(HardwareErrorCode.NetworkError, 'Returning data is not string.');
354
433
  }
355
- const jsonData = receiveOne(messages, resData);
434
+ const jsonData = ProtocolV1.decodeMessage(messages, resData);
356
435
  return check.call(jsonData);
357
436
  }
358
437
 
@@ -401,6 +480,21 @@ export default class NodeUsbTransport {
401
480
  );
402
481
  }
403
482
 
483
+ private getDeviceInterface(dev: usb.Device): usb.Interface {
484
+ const { interfaces } = dev;
485
+ if (!interfaces?.length) {
486
+ throw ERRORS.TypedError(HardwareErrorCode.DeviceNotFound, 'USB interface not found');
487
+ }
488
+
489
+ const vendorInterface = interfaces.find(iface => iface.descriptor.bInterfaceClass === 0xff);
490
+ const defaultInterface = interfaces.find(
491
+ iface => iface.descriptor.bInterfaceNumber === INTERFACE_NUMBER
492
+ );
493
+ const iface = vendorInterface ?? defaultInterface ?? interfaces[0];
494
+
495
+ return iface;
496
+ }
497
+
404
498
  /**
405
499
  * Reconnect device before retrying a failed transfer (aligned with WebUsbTransport).
406
500
  * Uses per-path lock to prevent concurrent reconnects to the same device.
@@ -423,9 +517,9 @@ export default class NodeUsbTransport {
423
517
  );
424
518
  await wait(attempt * PACKET_IO_RETRY_DELAY);
425
519
 
426
- // Close the existing device
520
+ // Close the existing device without clearing the detected protocol cache.
427
521
  try {
428
- await this.release(path);
522
+ await this.closeOpenDevice(path);
429
523
  } catch (releaseError) {
430
524
  this.Log?.debug('[NodeUsbTransport] release before retry error:', releaseError);
431
525
  }
@@ -552,7 +646,7 @@ export default class NodeUsbTransport {
552
646
  try {
553
647
  dev.timeout = TRANSFER_TIMEOUT_MS;
554
648
 
555
- const iface = dev.interface(INTERFACE_NUMBER);
649
+ const iface = this.getDeviceInterface(dev);
556
650
 
557
651
  // On Linux, detach kernel driver if active
558
652
  if (process.platform === 'linux') {
@@ -567,12 +661,14 @@ export default class NodeUsbTransport {
567
661
 
568
662
  iface.claim();
569
663
 
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
- );
664
+ const epIn =
665
+ iface.endpoints.find(
666
+ (e): e is usb.InEndpoint => e.direction === 'in' && e.address === ENDPOINT_IN
667
+ ) ?? iface.endpoints.find((e): e is usb.InEndpoint => e.direction === 'in');
668
+ const epOut =
669
+ iface.endpoints.find(
670
+ (e): e is usb.OutEndpoint => e.direction === 'out' && e.address === ENDPOINT_OUT
671
+ ) ?? iface.endpoints.find((e): e is usb.OutEndpoint => e.direction === 'out');
576
672
 
577
673
  if (!epIn || !epOut) {
578
674
  throw ERRORS.TypedError(
@@ -595,17 +691,273 @@ export default class NodeUsbTransport {
595
691
  }
596
692
  }
597
693
 
694
+ private createProtocolMismatchError(expected: ProtocolType) {
695
+ return ERRORS.TypedError(
696
+ HardwareErrorCode.RuntimeError,
697
+ `Device protocol mismatch: expected ${expected}, but device did not respond to expected protocol`
698
+ );
699
+ }
700
+
701
+ private createProtocolDetectionError() {
702
+ return ERRORS.TypedError(
703
+ HardwareErrorCode.RuntimeError,
704
+ 'Unable to detect USB protocol: device did not respond to Protocol V1 Initialize or Protocol V2 Ping'
705
+ );
706
+ }
707
+
708
+ private async detectProtocol(
709
+ path: string,
710
+ expectedProtocol?: ProtocolType
711
+ ): Promise<ProtocolType> {
712
+ if (expectedProtocol === 'V1') {
713
+ if (await this.probeProtocolV1(path)) {
714
+ this.deviceProtocol.set(path, 'V1');
715
+ this.Log?.debug(`[NodeUsbTransport] detectProtocol: path=${path} -> V1 (expected)`);
716
+ return 'V1';
717
+ }
718
+ throw this.createProtocolMismatchError(expectedProtocol);
719
+ }
720
+
721
+ if (expectedProtocol === 'V2') {
722
+ if (await this.probeProtocolV2(path)) {
723
+ this.deviceProtocol.set(path, 'V2');
724
+ this.Log?.debug(`[NodeUsbTransport] detectProtocol: path=${path} -> V2 (expected)`);
725
+ return 'V2';
726
+ }
727
+ throw this.createProtocolMismatchError(expectedProtocol);
728
+ }
729
+
730
+ if (this.deviceProtocol.get(path) === 'V2' && (await this.probeProtocolV2(path))) {
731
+ this.deviceProtocol.set(path, 'V2');
732
+ this.Log?.debug(`[NodeUsbTransport] detectProtocol: path=${path} -> V2 (cached)`);
733
+ return 'V2';
734
+ }
735
+
736
+ if (await this.probeProtocolV1(path)) {
737
+ this.deviceProtocol.set(path, 'V1');
738
+ this.Log?.debug(`[NodeUsbTransport] detectProtocol: path=${path} -> V1`);
739
+ return 'V1';
740
+ }
741
+
742
+ if (await this.probeProtocolV2(path)) {
743
+ this.deviceProtocol.set(path, 'V2');
744
+ this.Log?.debug(`[NodeUsbTransport] detectProtocol: path=${path} -> V2`);
745
+ return 'V2';
746
+ }
747
+
748
+ this.deviceProtocol.delete(path);
749
+ throw this.createProtocolDetectionError();
750
+ }
751
+
752
+ private async resetConnectionAfterProbe(path: string) {
753
+ this.protocolV2Assemblers.get(path)?.reset();
754
+
755
+ try {
756
+ await this.closeOpenDevice(path);
757
+ } catch (error) {
758
+ this.Log?.debug('[NodeUsbTransport] close after protocol probe error:', error);
759
+ }
760
+
761
+ await this.enumerate();
762
+ this.openDevice(path);
763
+ }
764
+
765
+ private async withProtocolReadTimeout<T>(
766
+ path: string,
767
+ promise: Promise<T>,
768
+ timeoutMs: number,
769
+ protocol: ProtocolType
770
+ ): Promise<T> {
771
+ let timer: ReturnType<typeof setTimeout> | undefined;
772
+ let timedOut = false;
773
+ const waitForeverAfterTimeout = () => new Promise<never>(() => {});
774
+ const guardedPromise = promise.then(
775
+ value => (timedOut ? waitForeverAfterTimeout() : value),
776
+ error => {
777
+ if (timedOut) {
778
+ return waitForeverAfterTimeout();
779
+ }
780
+ throw error;
781
+ }
782
+ );
783
+ try {
784
+ return await Promise.race([
785
+ guardedPromise,
786
+ new Promise<never>((_, reject) => {
787
+ timer = setTimeout(async () => {
788
+ timedOut = true;
789
+ try {
790
+ await this.resetConnectionAfterProbe(path);
791
+ } catch (error) {
792
+ this.Log?.debug(
793
+ `[NodeUsbTransport] reset after Protocol ${protocol} timeout failed:`,
794
+ error
795
+ );
796
+ } finally {
797
+ reject(new Error(`Protocol ${protocol} read timeout after ${timeoutMs}ms`));
798
+ }
799
+ }, timeoutMs);
800
+ }),
801
+ ]);
802
+ } finally {
803
+ if (timer) clearTimeout(timer);
804
+ }
805
+ }
806
+
807
+ private async probeProtocolV1(path: string) {
808
+ if (!this.messages) {
809
+ return false;
810
+ }
811
+
812
+ try {
813
+ await this.callProtocolV1(path, 'Initialize', {}, { timeoutMs: PROTOCOL_PROBE_TIMEOUT });
814
+ return true;
815
+ } catch (error) {
816
+ this.Log?.debug('[NodeUsbTransport] Protocol V1 Initialize probe failed:', error);
817
+ return false;
818
+ }
819
+ }
820
+
821
+ private async probeProtocolV2(path: string) {
822
+ if (!this.messages || !this.messagesV2) {
823
+ return false;
824
+ }
825
+
826
+ return probeProtocolV2Helper({
827
+ call: (name, data, options) => this.callProtocolV2(path, name, data, options),
828
+ timeoutMs: PROTOCOL_PROBE_TIMEOUT,
829
+ logger: this.Log,
830
+ logPrefix: 'ProtocolV2 NodeUSB',
831
+ onProbeFailed: () => this.resetConnectionAfterProbe(path),
832
+ });
833
+ }
834
+
835
+ private async writeProtocolV2Frame(path: string, frame: Uint8Array) {
836
+ let lastError: unknown;
837
+ for (let attempt = 1; attempt <= PACKET_IO_MAX_RETRIES; attempt++) {
838
+ if (this.cancelled) {
839
+ throw ERRORS.TypedError(HardwareErrorCode.DeviceInterruptedFromOutside, 'Cancelled');
840
+ }
841
+ try {
842
+ await transferOutOnce(this.getOpenDevice(path).epOut, Buffer.from(frame));
843
+ return;
844
+ } catch (error) {
845
+ lastError = error;
846
+ const shouldRetry = attempt < PACKET_IO_MAX_RETRIES && this.isRetryableError(error);
847
+ if (!shouldRetry) {
848
+ throw error;
849
+ }
850
+ try {
851
+ await this.reconnectForRetry(path, 'out', attempt, error);
852
+ } catch (reconnectError) {
853
+ lastError = reconnectError;
854
+ this.Log?.debug(
855
+ `[NodeUsbTransport] Protocol V2 write reconnect failed on retry ${attempt}/${PACKET_IO_MAX_RETRIES}: ${this.getErrorMessage(
856
+ reconnectError
857
+ )}`
858
+ );
859
+ break;
860
+ }
861
+ }
862
+ }
863
+ throw lastError;
864
+ }
865
+
866
+ private async receiveProtocolV2Frame(path: string, timeoutMs?: number): Promise<Uint8Array> {
867
+ let assembler = this.protocolV2Assemblers.get(path);
868
+ if (!assembler) {
869
+ assembler = new ProtocolV2FrameAssembler(PROTOCOL_V2_FRAME_MAX_BYTES);
870
+ this.protocolV2Assemblers.set(path, assembler);
871
+ }
872
+
873
+ let frame: Uint8Array | undefined = assembler.push(new Uint8Array(0));
874
+ const deadline = timeoutMs ? Date.now() + timeoutMs : undefined;
875
+
876
+ while (!frame) {
877
+ const transferIn = this.transferInWithRetry(
878
+ path,
879
+ this.getOpenDevice(path),
880
+ PROTOCOL_V2_FRAME_MAX_BYTES
881
+ );
882
+ const packet = deadline
883
+ ? await this.withProtocolReadTimeout(
884
+ path,
885
+ transferIn,
886
+ Math.max(deadline - Date.now(), 1),
887
+ 'V2'
888
+ )
889
+ : await transferIn;
890
+ const bytes = new Uint8Array(
891
+ packet.buffer.slice(packet.byteOffset, packet.byteOffset + packet.byteLength)
892
+ );
893
+ try {
894
+ frame = assembler.push(bytes);
895
+ } catch (error) {
896
+ throw ERRORS.TypedError(
897
+ HardwareErrorCode.NetworkError,
898
+ error instanceof Error ? error.message : String(error)
899
+ );
900
+ }
901
+ }
902
+ return frame;
903
+ }
904
+
905
+ private async callProtocolV2(
906
+ path: string,
907
+ name: string,
908
+ data: Record<string, unknown>,
909
+ options?: TransportCallOptions
910
+ ) {
911
+ const protocolV1Messages = this.messages;
912
+ if (!this.messagesV2) {
913
+ throw ERRORS.TypedError(
914
+ HardwareErrorCode.TransportNotConfigured,
915
+ 'Protocol V2 schema not configured'
916
+ );
917
+ }
918
+ if (!protocolV1Messages) {
919
+ throw ERRORS.TypedError(HardwareErrorCode.TransportNotConfigured);
920
+ }
921
+
922
+ const session = new ProtocolV2Session({
923
+ schemas: {
924
+ protocolV1: protocolV1Messages,
925
+ protocolV2: this.messagesV2,
926
+ },
927
+ router: PROTOCOL_V2_CHANNEL_USB,
928
+ writeFrame: (frame: Uint8Array) => this.writeProtocolV2Frame(path, frame),
929
+ readFrame: () => this.receiveProtocolV2Frame(path, options?.timeoutMs),
930
+ logger: this.Log,
931
+ logPrefix: 'ProtocolV2 NodeUSB',
932
+ createTimeoutError: (_messageName: string, timeoutMs: number) =>
933
+ new Error(`Protocol V2 response timeout after ${timeoutMs}ms for ${name}`),
934
+ });
935
+
936
+ this.protocolV2Assemblers.get(path)?.reset();
937
+ return session.call(name, data, options);
938
+ }
939
+
598
940
  /**
599
941
  * Receive a complete protobuf response from the device.
600
942
  * Reads 64-byte packets, strips 0x3F marker, reassembles into hex string.
601
943
  */
602
- private async receiveData(path: string, dev: OpenDevice): Promise<string> {
944
+ private async receiveData(path: string, dev: OpenDevice, timeoutMs?: number): Promise<string> {
945
+ const deadline = timeoutMs ? Date.now() + timeoutMs : undefined;
946
+ const readPacket = async () => {
947
+ const transferIn = this.transferInWithRetry(path, this.getOpenDevice(path), PACKET_SIZE);
948
+ return deadline
949
+ ? this.withProtocolReadTimeout(path, transferIn, Math.max(deadline - Date.now(), 1), 'V1')
950
+ : transferIn;
951
+ };
952
+
603
953
  // Read first packet, skip report byte
604
- const firstPacket = await this.transferInWithRetry(path, dev, PACKET_SIZE);
954
+ const firstPacket = timeoutMs
955
+ ? await readPacket()
956
+ : await this.transferInWithRetry(path, dev, PACKET_SIZE);
605
957
  const firstData = skipReportByte(firstPacket);
606
958
 
607
959
  // Decode header: ## marker → { typeId, length, restBuffer }
608
- const { length, typeId, restBuffer } = decodeProtocol.decodeChunked(toArrayBuffer(firstData));
960
+ const { length, typeId, restBuffer } = ProtocolV1.decodeFirstChunk(toArrayBuffer(firstData));
609
961
 
610
962
  // Allocate result: typeId(2) + length(4) + payload(length)
611
963
  const lengthWithHeader = Number(length) + HEADER_LENGTH;
@@ -619,7 +971,7 @@ export default class NodeUsbTransport {
619
971
  // Read subsequent packets until complete
620
972
  // Re-resolve device on each iteration so we use a fresh handle after any reconnect
621
973
  while (decoded.offset < lengthWithHeader) {
622
- const packet = await this.transferInWithRetry(path, this.getOpenDevice(path), PACKET_SIZE);
974
+ const packet = await readPacket();
623
975
  const pktData = skipReportByte(packet);
624
976
  const buf = toArrayBuffer(pktData);
625
977
  if (lengthWithHeader - decoded.offset >= PAYLOAD_SIZE) {
@@ -633,6 +985,8 @@ export default class NodeUsbTransport {
633
985
  const result = decoded.toBuffer();
634
986
  return Buffer.from(result as unknown as ArrayBuffer).toString('hex');
635
987
  }
636
- }
637
988
 
638
- export { PACKET_SIZE } from './constants';
989
+ getProtocolType(path: string): ProtocolType | undefined {
990
+ return this.deviceProtocol.get(path);
991
+ }
992
+ }
@@ -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;