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