@onekeyfe/hd-transport-usb 1.1.27-alpha.40 → 1.1.27-alpha.5
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 -21
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +42 -302
- package/package.json +4 -4
- package/src/constants.ts +11 -0
- package/src/index.ts +31 -385
|
@@ -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,30 +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 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;
|
|
57
42
|
private receiveData;
|
|
58
|
-
getProtocolType(path: string): ProtocolType | undefined;
|
|
59
43
|
}
|
|
60
44
|
|
|
61
|
-
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,7 +278,7 @@ class NodeUsbTransport {
|
|
|
294
278
|
this.openDevices.delete(path);
|
|
295
279
|
});
|
|
296
280
|
}
|
|
297
|
-
call(path, name, data
|
|
281
|
+
call(path, name, data) {
|
|
298
282
|
var _a, _b;
|
|
299
283
|
return __awaiter(this, void 0, void 0, function* () {
|
|
300
284
|
this.cancelled = false;
|
|
@@ -304,35 +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
|
|
308
|
-
if (!protocol) {
|
|
309
|
-
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, `Device protocol has not been detected for ${path}`);
|
|
310
|
-
}
|
|
291
|
+
const { messages } = this;
|
|
311
292
|
if (transport.LogBlockCommand.has(name)) {
|
|
312
|
-
(_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('NodeUsbTransport call-', ' name: ', name
|
|
293
|
+
(_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('NodeUsbTransport call-', ' name: ', name);
|
|
313
294
|
}
|
|
314
295
|
else {
|
|
315
|
-
(_b = this.Log) === null || _b === void 0 ? void 0 : _b.debug('NodeUsbTransport call-', ' name: ', name, ' data: ', data
|
|
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);
|
|
296
|
+
(_b = this.Log) === null || _b === void 0 ? void 0 : _b.debug('NodeUsbTransport call-', ' name: ', name, ' data: ', data);
|
|
328
297
|
}
|
|
329
|
-
const encodeBuffers =
|
|
298
|
+
const encodeBuffers = buildEncodeBuffers(messages, name, data);
|
|
330
299
|
yield this.sendAllChunksWithRetry(path, encodeBuffers);
|
|
331
|
-
const resData = yield this.receiveData(path, this.getOpenDevice(path)
|
|
300
|
+
const resData = yield this.receiveData(path, this.getOpenDevice(path));
|
|
332
301
|
if (typeof resData !== 'string') {
|
|
333
302
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.NetworkError, 'Returning data is not string.');
|
|
334
303
|
}
|
|
335
|
-
const jsonData =
|
|
304
|
+
const jsonData = receiveOne(messages, resData);
|
|
336
305
|
return check.call(jsonData);
|
|
337
306
|
});
|
|
338
307
|
}
|
|
@@ -372,17 +341,6 @@ class NodeUsbTransport {
|
|
|
372
341
|
message.includes('timeout') ||
|
|
373
342
|
message.includes('interrupt'));
|
|
374
343
|
}
|
|
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
|
-
}
|
|
386
344
|
reconnectForRetry(path, direction, attempt, error) {
|
|
387
345
|
const existing = this.reconnectLocks.get(path);
|
|
388
346
|
if (existing)
|
|
@@ -392,7 +350,7 @@ class NodeUsbTransport {
|
|
|
392
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)}`);
|
|
393
351
|
yield hdShared.wait(attempt * PACKET_IO_RETRY_DELAY);
|
|
394
352
|
try {
|
|
395
|
-
yield this.
|
|
353
|
+
yield this.release(path);
|
|
396
354
|
}
|
|
397
355
|
catch (releaseError) {
|
|
398
356
|
(_b = this.Log) === null || _b === void 0 ? void 0 : _b.debug('[NodeUsbTransport] release before retry error:', releaseError);
|
|
@@ -479,7 +437,7 @@ class NodeUsbTransport {
|
|
|
479
437
|
});
|
|
480
438
|
}
|
|
481
439
|
openDevice(path) {
|
|
482
|
-
var _a
|
|
440
|
+
var _a;
|
|
483
441
|
const existing = this.openDevices.get(path);
|
|
484
442
|
if (existing)
|
|
485
443
|
return;
|
|
@@ -492,19 +450,19 @@ class NodeUsbTransport {
|
|
|
492
450
|
dev.open();
|
|
493
451
|
try {
|
|
494
452
|
dev.timeout = TRANSFER_TIMEOUT_MS;
|
|
495
|
-
const iface =
|
|
453
|
+
const iface = dev.interface(INTERFACE_NUMBER);
|
|
496
454
|
if (process.platform === 'linux') {
|
|
497
455
|
try {
|
|
498
456
|
if (iface.isKernelDriverActive()) {
|
|
499
457
|
iface.detachKernelDriver();
|
|
500
458
|
}
|
|
501
459
|
}
|
|
502
|
-
catch (
|
|
460
|
+
catch (_b) {
|
|
503
461
|
}
|
|
504
462
|
}
|
|
505
463
|
iface.claim();
|
|
506
|
-
const epIn =
|
|
507
|
-
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);
|
|
508
466
|
if (!epIn || !epOut) {
|
|
509
467
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.DeviceNotFound, 'USB endpoints not found (expected IN 0x81, OUT 0x01)');
|
|
510
468
|
}
|
|
@@ -516,232 +474,16 @@ class NodeUsbTransport {
|
|
|
516
474
|
try {
|
|
517
475
|
dev.close();
|
|
518
476
|
}
|
|
519
|
-
catch (
|
|
477
|
+
catch (_c) {
|
|
520
478
|
}
|
|
521
479
|
throw err;
|
|
522
480
|
}
|
|
523
481
|
}
|
|
524
|
-
|
|
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;
|
|
482
|
+
receiveData(path, dev) {
|
|
570
483
|
return __awaiter(this, void 0, void 0, function* () {
|
|
571
|
-
|
|
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);
|
|
484
|
+
const firstPacket = yield this.transferInWithRetry(path, dev, PACKET_SIZE);
|
|
743
485
|
const firstData = skipReportByte(firstPacket);
|
|
744
|
-
const { length, typeId, restBuffer } =
|
|
486
|
+
const { length, typeId, restBuffer } = decodeProtocol.decodeChunked(toArrayBuffer(firstData));
|
|
745
487
|
const lengthWithHeader = Number(length) + HEADER_LENGTH;
|
|
746
488
|
const decoded = new ByteBuffer__default["default"](lengthWithHeader);
|
|
747
489
|
decoded.writeUint16(typeId);
|
|
@@ -750,7 +492,7 @@ class NodeUsbTransport {
|
|
|
750
492
|
decoded.append(restBuffer);
|
|
751
493
|
}
|
|
752
494
|
while (decoded.offset < lengthWithHeader) {
|
|
753
|
-
const packet = yield
|
|
495
|
+
const packet = yield this.transferInWithRetry(path, this.getOpenDevice(path), PACKET_SIZE);
|
|
754
496
|
const pktData = skipReportByte(packet);
|
|
755
497
|
const buf = toArrayBuffer(pktData);
|
|
756
498
|
if (lengthWithHeader - decoded.offset >= PAYLOAD_SIZE) {
|
|
@@ -765,9 +507,7 @@ class NodeUsbTransport {
|
|
|
765
507
|
return Buffer.from(result).toString('hex');
|
|
766
508
|
});
|
|
767
509
|
}
|
|
768
|
-
getProtocolType(path) {
|
|
769
|
-
return this.deviceProtocol.get(path);
|
|
770
|
-
}
|
|
771
510
|
}
|
|
772
511
|
|
|
773
|
-
|
|
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.5",
|
|
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.5",
|
|
24
|
+
"@onekeyfe/hd-transport": "1.1.27-alpha.5",
|
|
25
25
|
"bytebuffer": "^5.0.1",
|
|
26
26
|
"usb": "^2.14.0"
|
|
27
27
|
},
|
|
28
|
-
"gitHead": "
|
|
28
|
+
"gitHead": "0c7353e097121596ad4076a531a473408471674c"
|
|
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,57 +333,26 @@ export default class NodeUsbTransport {
|
|
|
381
333
|
throw ERRORS.TypedError(HardwareErrorCode.DeviceNotFound, `Device not acquired: ${path}`);
|
|
382
334
|
}
|
|
383
335
|
|
|
384
|
-
const
|
|
385
|
-
if (!protocol) {
|
|
386
|
-
throw ERRORS.TypedError(
|
|
387
|
-
HardwareErrorCode.RuntimeError,
|
|
388
|
-
`Device protocol has not been detected for ${path}`
|
|
389
|
-
);
|
|
390
|
-
}
|
|
336
|
+
const { messages } = this;
|
|
391
337
|
if (LogBlockCommand.has(name)) {
|
|
392
|
-
this.Log?.debug('NodeUsbTransport call-', ' name: ', name
|
|
338
|
+
this.Log?.debug('NodeUsbTransport call-', ' name: ', name);
|
|
393
339
|
} else {
|
|
394
|
-
this.Log?.debug(
|
|
395
|
-
'NodeUsbTransport call-',
|
|
396
|
-
' name: ',
|
|
397
|
-
name,
|
|
398
|
-
' data: ',
|
|
399
|
-
data,
|
|
400
|
-
' protocol: ',
|
|
401
|
-
protocol
|
|
402
|
-
);
|
|
340
|
+
this.Log?.debug('NodeUsbTransport call-', ' name: ', name, ' data: ', data);
|
|
403
341
|
}
|
|
404
342
|
|
|
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
|
-
}
|
|
422
343
|
// Encode protobuf message into 63-byte chunks (same as WebUsbTransport)
|
|
423
|
-
const encodeBuffers =
|
|
344
|
+
const encodeBuffers = buildEncodeBuffers(messages, name, data);
|
|
424
345
|
|
|
425
346
|
// Send all chunks with retry — if any chunk fails and reconnects,
|
|
426
347
|
// restart the entire send sequence from chunk 0 (device resets state on reconnect)
|
|
427
348
|
await this.sendAllChunksWithRetry(path, encodeBuffers);
|
|
428
349
|
|
|
429
350
|
// Receive response — re-resolve in case reconnect happened during send
|
|
430
|
-
const resData = await this.receiveData(path, this.getOpenDevice(path)
|
|
351
|
+
const resData = await this.receiveData(path, this.getOpenDevice(path));
|
|
431
352
|
if (typeof resData !== 'string') {
|
|
432
353
|
throw ERRORS.TypedError(HardwareErrorCode.NetworkError, 'Returning data is not string.');
|
|
433
354
|
}
|
|
434
|
-
const jsonData =
|
|
355
|
+
const jsonData = receiveOne(messages, resData);
|
|
435
356
|
return check.call(jsonData);
|
|
436
357
|
}
|
|
437
358
|
|
|
@@ -480,21 +401,6 @@ export default class NodeUsbTransport {
|
|
|
480
401
|
);
|
|
481
402
|
}
|
|
482
403
|
|
|
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
|
-
|
|
498
404
|
/**
|
|
499
405
|
* Reconnect device before retrying a failed transfer (aligned with WebUsbTransport).
|
|
500
406
|
* Uses per-path lock to prevent concurrent reconnects to the same device.
|
|
@@ -517,9 +423,9 @@ export default class NodeUsbTransport {
|
|
|
517
423
|
);
|
|
518
424
|
await wait(attempt * PACKET_IO_RETRY_DELAY);
|
|
519
425
|
|
|
520
|
-
// Close the existing device
|
|
426
|
+
// Close the existing device
|
|
521
427
|
try {
|
|
522
|
-
await this.
|
|
428
|
+
await this.release(path);
|
|
523
429
|
} catch (releaseError) {
|
|
524
430
|
this.Log?.debug('[NodeUsbTransport] release before retry error:', releaseError);
|
|
525
431
|
}
|
|
@@ -646,7 +552,7 @@ export default class NodeUsbTransport {
|
|
|
646
552
|
try {
|
|
647
553
|
dev.timeout = TRANSFER_TIMEOUT_MS;
|
|
648
554
|
|
|
649
|
-
const iface =
|
|
555
|
+
const iface = dev.interface(INTERFACE_NUMBER);
|
|
650
556
|
|
|
651
557
|
// On Linux, detach kernel driver if active
|
|
652
558
|
if (process.platform === 'linux') {
|
|
@@ -661,14 +567,12 @@ export default class NodeUsbTransport {
|
|
|
661
567
|
|
|
662
568
|
iface.claim();
|
|
663
569
|
|
|
664
|
-
const epIn =
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
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');
|
|
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
|
+
);
|
|
672
576
|
|
|
673
577
|
if (!epIn || !epOut) {
|
|
674
578
|
throw ERRORS.TypedError(
|
|
@@ -691,273 +595,17 @@ export default class NodeUsbTransport {
|
|
|
691
595
|
}
|
|
692
596
|
}
|
|
693
597
|
|
|
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
|
-
|
|
940
598
|
/**
|
|
941
599
|
* Receive a complete protobuf response from the device.
|
|
942
600
|
* Reads 64-byte packets, strips 0x3F marker, reassembles into hex string.
|
|
943
601
|
*/
|
|
944
|
-
private async receiveData(path: string, dev: OpenDevice
|
|
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
|
-
|
|
602
|
+
private async receiveData(path: string, dev: OpenDevice): Promise<string> {
|
|
953
603
|
// Read first packet, skip report byte
|
|
954
|
-
const firstPacket =
|
|
955
|
-
? await readPacket()
|
|
956
|
-
: await this.transferInWithRetry(path, dev, PACKET_SIZE);
|
|
604
|
+
const firstPacket = await this.transferInWithRetry(path, dev, PACKET_SIZE);
|
|
957
605
|
const firstData = skipReportByte(firstPacket);
|
|
958
606
|
|
|
959
607
|
// Decode header: ## marker → { typeId, length, restBuffer }
|
|
960
|
-
const { length, typeId, restBuffer } =
|
|
608
|
+
const { length, typeId, restBuffer } = decodeProtocol.decodeChunked(toArrayBuffer(firstData));
|
|
961
609
|
|
|
962
610
|
// Allocate result: typeId(2) + length(4) + payload(length)
|
|
963
611
|
const lengthWithHeader = Number(length) + HEADER_LENGTH;
|
|
@@ -971,7 +619,7 @@ export default class NodeUsbTransport {
|
|
|
971
619
|
// Read subsequent packets until complete
|
|
972
620
|
// Re-resolve device on each iteration so we use a fresh handle after any reconnect
|
|
973
621
|
while (decoded.offset < lengthWithHeader) {
|
|
974
|
-
const packet = await
|
|
622
|
+
const packet = await this.transferInWithRetry(path, this.getOpenDevice(path), PACKET_SIZE);
|
|
975
623
|
const pktData = skipReportByte(packet);
|
|
976
624
|
const buf = toArrayBuffer(pktData);
|
|
977
625
|
if (lengthWithHeader - decoded.offset >= PAYLOAD_SIZE) {
|
|
@@ -985,8 +633,6 @@ export default class NodeUsbTransport {
|
|
|
985
633
|
const result = decoded.toBuffer();
|
|
986
634
|
return Buffer.from(result as unknown as ArrayBuffer).toString('hex');
|
|
987
635
|
}
|
|
988
|
-
|
|
989
|
-
getProtocolType(path: string): ProtocolType | undefined {
|
|
990
|
-
return this.deviceProtocol.get(path);
|
|
991
|
-
}
|
|
992
636
|
}
|
|
637
|
+
|
|
638
|
+
export { PACKET_SIZE } from './constants';
|