@onekeyfe/hd-transport-lowlevel 1.1.27-alpha.41 → 1.1.27-alpha.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +5 -27
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +14 -283
- package/dist/types.d.ts +0 -2
- package/dist/types.d.ts.map +1 -1
- package/package.json +4 -5
- package/src/index.ts +17 -372
- package/src/types.ts +0 -3
- package/.eslintignore +0 -4
- package/__tests__/protocol-v2.test.js +0 -288
- package/jest.config.js +0 -6
package/dist/index.d.ts
CHANGED
|
@@ -1,48 +1,26 @@
|
|
|
1
|
-
import * as
|
|
2
|
-
import
|
|
1
|
+
import * as _onekeyfe_hd_transport from '@onekeyfe/hd-transport';
|
|
2
|
+
import _onekeyfe_hd_transport__default, { LowlevelTransportSharedPlugin } from '@onekeyfe/hd-transport';
|
|
3
3
|
import EventEmitter from 'events';
|
|
4
4
|
|
|
5
5
|
type LowLevelAcquireInput = {
|
|
6
6
|
uuid: string;
|
|
7
|
-
expectedProtocol?: ProtocolType;
|
|
8
7
|
};
|
|
9
8
|
|
|
10
9
|
declare class LowlevelTransport {
|
|
11
|
-
_messages: ReturnType<typeof
|
|
12
|
-
_messagesV2: ReturnType<typeof transport__default.parseConfigure> | undefined;
|
|
10
|
+
_messages: ReturnType<typeof _onekeyfe_hd_transport__default.parseConfigure> | undefined;
|
|
13
11
|
configured: boolean;
|
|
14
12
|
Log?: any;
|
|
15
13
|
emitter?: EventEmitter;
|
|
16
14
|
plugin: LowlevelTransportSharedPlugin;
|
|
17
|
-
private deviceProtocol;
|
|
18
|
-
private deviceProtocolHints;
|
|
19
|
-
private protocolV2Assemblers;
|
|
20
|
-
getProtocolType(path: string): ProtocolType | undefined;
|
|
21
15
|
init(logger: any, emitter: EventEmitter, plugin: LowlevelTransportSharedPlugin): void;
|
|
22
16
|
configure(signedData: any): void;
|
|
23
|
-
configureProtocolV2(signedData: any): void;
|
|
24
17
|
listen(): void;
|
|
25
|
-
enumerate(): Promise<LowLevelDevice[]>;
|
|
18
|
+
enumerate(): Promise<_onekeyfe_hd_transport.LowLevelDevice[]>;
|
|
26
19
|
acquire(input: LowLevelAcquireInput): Promise<{
|
|
27
20
|
uuid: string;
|
|
28
|
-
protocolType: ProtocolType;
|
|
29
21
|
}>;
|
|
30
22
|
release(uuid: string): Promise<boolean>;
|
|
31
|
-
call(uuid: string, name: string, data: Record<string, unknown
|
|
32
|
-
private callProtocolV1;
|
|
33
|
-
private createProtocolTimeoutError;
|
|
34
|
-
private createProtocolMismatchError;
|
|
35
|
-
private createProtocolDetectionError;
|
|
36
|
-
private clearProbeProtocol;
|
|
37
|
-
private detectProtocol;
|
|
38
|
-
private resetConnectionAfterProbe;
|
|
39
|
-
private probeProtocolV1;
|
|
40
|
-
private probeProtocolV2;
|
|
41
|
-
private receiveHex;
|
|
42
|
-
private readProtocolV1Message;
|
|
43
|
-
private readProtocolV2Frame;
|
|
44
|
-
private writeProtocolV2Frame;
|
|
45
|
-
private callProtocolV2;
|
|
23
|
+
call(uuid: string, name: string, data: Record<string, unknown>): Promise<_onekeyfe_hd_transport.MessageFromOneKey>;
|
|
46
24
|
cancel(): void;
|
|
47
25
|
}
|
|
48
26
|
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,SAA8B,MAAM,wBAAwB,CAAC;AAEpE,OAAO,KAAK,YAAY,MAAM,QAAQ,CAAC;AACvC,OAAO,KAAK,EAAE,6BAA6B,EAAE,MAAM,wBAAwB,CAAC;AAC5E,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC;AAIpD,MAAM,CAAC,OAAO,OAAO,iBAAiB;IACpC,SAAS,EAAE,UAAU,CAAC,OAAO,SAAS,CAAC,cAAc,CAAC,GAAG,SAAS,CAAC;IAEnE,UAAU,UAAS;IAEnB,GAAG,CAAC,EAAE,GAAG,CAAC;IAEV,OAAO,CAAC,EAAE,YAAY,CAAC;IAEvB,MAAM,EAAE,6BAA6B,CAAuC;IAE5E,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE,6BAA6B;IAO9E,SAAS,CAAC,UAAU,EAAE,GAAG;IAMzB,MAAM;IAIN,SAAS;IAIH,OAAO,CAAC,KAAK,EAAE,oBAAoB;;;IAanC,OAAO,CAAC,IAAI,EAAE,MAAM;IAUpB,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAuCpE,MAAM;CAGP"}
|
package/dist/index.js
CHANGED
|
@@ -37,30 +37,11 @@ typeof SuppressedError === "function" ? SuppressedError : function (error, suppr
|
|
|
37
37
|
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
38
38
|
};
|
|
39
39
|
|
|
40
|
-
const { check,
|
|
41
|
-
const PROTOCOL_PROBE_TIMEOUT_MS = 1000;
|
|
42
|
-
const PROTOCOL_V2_PROBE_TIMEOUT_MS = 5000;
|
|
43
|
-
const LOWLEVEL_PROTOCOL_TIMEOUT_MS = 30000;
|
|
44
|
-
const LOWLEVEL_PROTOCOL_V2_PACKET_LENGTH = 64;
|
|
45
|
-
function inferProtocolHintFromDeviceName(name) {
|
|
46
|
-
return /\bpro\s*2\b/i.test(name !== null && name !== void 0 ? name : '') ? 'V2' : undefined;
|
|
47
|
-
}
|
|
48
|
-
function isProtocolV1TransportChunk(data) {
|
|
49
|
-
return data.length >= 9 && data[0] === 0x3f && data[1] === 0x23 && data[2] === 0x23;
|
|
50
|
-
}
|
|
51
|
-
function readProtocolV1PayloadLength(data) {
|
|
52
|
-
return data[5] * 0x1000000 + data[6] * 0x10000 + data[7] * 0x100 + data[8];
|
|
53
|
-
}
|
|
40
|
+
const { check, buildBuffers, receiveOne, parseConfigure } = transport__default["default"];
|
|
54
41
|
class LowlevelTransport {
|
|
55
42
|
constructor() {
|
|
56
43
|
this.configured = false;
|
|
57
44
|
this.plugin = {};
|
|
58
|
-
this.deviceProtocol = new Map();
|
|
59
|
-
this.deviceProtocolHints = new Map();
|
|
60
|
-
this.protocolV2Assemblers = new Map();
|
|
61
|
-
}
|
|
62
|
-
getProtocolType(path) {
|
|
63
|
-
return this.deviceProtocol.get(path);
|
|
64
45
|
}
|
|
65
46
|
init(logger, emitter, plugin) {
|
|
66
47
|
this.Log = logger;
|
|
@@ -73,48 +54,28 @@ class LowlevelTransport {
|
|
|
73
54
|
this.configured = true;
|
|
74
55
|
this._messages = messages;
|
|
75
56
|
}
|
|
76
|
-
configureProtocolV2(signedData) {
|
|
77
|
-
this._messagesV2 = parseConfigure(signedData);
|
|
78
|
-
}
|
|
79
57
|
listen() {
|
|
80
58
|
}
|
|
81
59
|
enumerate() {
|
|
82
|
-
return
|
|
83
|
-
const devices = yield this.plugin.enumerate();
|
|
84
|
-
return devices.map((device) => {
|
|
85
|
-
const protocolHint = inferProtocolHintFromDeviceName(device.name);
|
|
86
|
-
if (protocolHint) {
|
|
87
|
-
this.deviceProtocolHints.set(device.id, protocolHint);
|
|
88
|
-
}
|
|
89
|
-
return device;
|
|
90
|
-
});
|
|
91
|
-
});
|
|
60
|
+
return this.plugin.enumerate();
|
|
92
61
|
}
|
|
93
62
|
acquire(input) {
|
|
94
63
|
var _a;
|
|
95
64
|
return __awaiter(this, void 0, void 0, function* () {
|
|
96
65
|
try {
|
|
97
66
|
yield this.plugin.connect(input.uuid);
|
|
67
|
+
return { uuid: input.uuid };
|
|
98
68
|
}
|
|
99
69
|
catch (error) {
|
|
100
70
|
this.Log.debug('lowlelvel transport connect error: ', error);
|
|
101
71
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.LowlevelTrasnportConnectError, (_a = error.message) !== null && _a !== void 0 ? _a : error);
|
|
102
72
|
}
|
|
103
|
-
this.protocolV2Assemblers.set(input.uuid, new transport.ProtocolV2FrameAssembler());
|
|
104
|
-
const protocolHint = input.expectedProtocol
|
|
105
|
-
? undefined
|
|
106
|
-
: this.deviceProtocolHints.get(input.uuid);
|
|
107
|
-
const protocolType = yield this.detectProtocol(input.uuid, input.expectedProtocol, protocolHint);
|
|
108
|
-
return { uuid: input.uuid, protocolType };
|
|
109
73
|
});
|
|
110
74
|
}
|
|
111
75
|
release(uuid) {
|
|
112
76
|
return __awaiter(this, void 0, void 0, function* () {
|
|
113
77
|
try {
|
|
114
78
|
yield this.plugin.disconnect(uuid);
|
|
115
|
-
this.deviceProtocol.delete(uuid);
|
|
116
|
-
this.deviceProtocolHints.delete(uuid);
|
|
117
|
-
this.protocolV2Assemblers.delete(uuid);
|
|
118
79
|
return true;
|
|
119
80
|
}
|
|
120
81
|
catch (error) {
|
|
@@ -123,34 +84,19 @@ class LowlevelTransport {
|
|
|
123
84
|
}
|
|
124
85
|
});
|
|
125
86
|
}
|
|
126
|
-
call(uuid, name, data
|
|
87
|
+
call(uuid, name, data) {
|
|
127
88
|
return __awaiter(this, void 0, void 0, function* () {
|
|
128
89
|
if (this._messages === null || !this._messages) {
|
|
129
90
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured);
|
|
130
91
|
}
|
|
131
|
-
const
|
|
132
|
-
if (!protocol) {
|
|
133
|
-
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, `Device protocol has not been detected for ${uuid}`);
|
|
134
|
-
}
|
|
92
|
+
const messages = this._messages;
|
|
135
93
|
if (transport.LogBlockCommand.has(name)) {
|
|
136
|
-
this.Log.debug('lowlevel-transport', 'call-', ' name: ', name
|
|
94
|
+
this.Log.debug('lowlevel-transport', 'call-', ' name: ', name);
|
|
137
95
|
}
|
|
138
96
|
else {
|
|
139
|
-
this.Log.debug('lowlevel-transport', 'call-', ' name: ', name, ' data: ', data
|
|
97
|
+
this.Log.debug('lowlevel-transport', 'call-', ' name: ', name, ' data: ', data);
|
|
140
98
|
}
|
|
141
|
-
|
|
142
|
-
return this.callProtocolV2(uuid, name, data, options);
|
|
143
|
-
}
|
|
144
|
-
return this.callProtocolV1(uuid, name, data, options);
|
|
145
|
-
});
|
|
146
|
-
}
|
|
147
|
-
callProtocolV1(uuid, name, data, options) {
|
|
148
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
149
|
-
if (!this._messages) {
|
|
150
|
-
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured);
|
|
151
|
-
}
|
|
152
|
-
const messages = this._messages;
|
|
153
|
-
const buffers = ProtocolV1.encodeTransportPackets(messages, name, data);
|
|
99
|
+
const buffers = buildBuffers(messages, name, data);
|
|
154
100
|
for (const o of buffers) {
|
|
155
101
|
const outData = o.toString('hex');
|
|
156
102
|
this.Log.debug('send hex strting: ', outData);
|
|
@@ -163,231 +109,16 @@ class LowlevelTransport {
|
|
|
163
109
|
}
|
|
164
110
|
}
|
|
165
111
|
try {
|
|
166
|
-
const response = yield this.
|
|
112
|
+
const response = yield this.plugin.receive();
|
|
113
|
+
if (typeof response !== 'string') {
|
|
114
|
+
throw new Error('Returning data is not string');
|
|
115
|
+
}
|
|
167
116
|
this.Log.debug('receive data: ', response);
|
|
168
|
-
const jsonData =
|
|
117
|
+
const jsonData = receiveOne(messages, response);
|
|
169
118
|
return check.call(jsonData);
|
|
170
119
|
}
|
|
171
120
|
catch (e) {
|
|
172
|
-
|
|
173
|
-
this.Log.debug('[LowlevelTransport] Protocol V1 Initialize probe call failed:', e);
|
|
174
|
-
}
|
|
175
|
-
else {
|
|
176
|
-
this.Log.error('lowlevel call error: ', e);
|
|
177
|
-
}
|
|
178
|
-
throw e;
|
|
179
|
-
}
|
|
180
|
-
});
|
|
181
|
-
}
|
|
182
|
-
createProtocolTimeoutError(name, timeout) {
|
|
183
|
-
return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleTimeoutError, `Lowlevel response timeout after ${timeout}ms for ${name}`);
|
|
184
|
-
}
|
|
185
|
-
createProtocolMismatchError(expected) {
|
|
186
|
-
return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, `Device protocol mismatch: expected ${expected}, but device did not respond to expected protocol`);
|
|
187
|
-
}
|
|
188
|
-
createProtocolDetectionError() {
|
|
189
|
-
return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleTimeoutError, 'Unable to detect BLE protocol: device did not respond to Protocol V1 Initialize or Protocol V2 Ping');
|
|
190
|
-
}
|
|
191
|
-
clearProbeProtocol(uuid, protocol) {
|
|
192
|
-
if (this.deviceProtocol.get(uuid) === protocol) {
|
|
193
|
-
this.deviceProtocol.delete(uuid);
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
detectProtocol(uuid, expectedProtocol, protocolHint) {
|
|
197
|
-
var _a, _b, _c, _d, _e, _f;
|
|
198
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
199
|
-
if (expectedProtocol === 'V2') {
|
|
200
|
-
if (yield this.probeProtocolV2(uuid)) {
|
|
201
|
-
this.deviceProtocol.set(uuid, 'V2');
|
|
202
|
-
(_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug(`[LowlevelTransport] detectProtocol: uuid=${uuid} -> V2 (expected)`);
|
|
203
|
-
return 'V2';
|
|
204
|
-
}
|
|
205
|
-
throw this.createProtocolMismatchError(expectedProtocol);
|
|
206
|
-
}
|
|
207
|
-
if (expectedProtocol === 'V1') {
|
|
208
|
-
if (yield this.probeProtocolV1(uuid)) {
|
|
209
|
-
this.deviceProtocol.set(uuid, 'V1');
|
|
210
|
-
(_b = this.Log) === null || _b === void 0 ? void 0 : _b.debug(`[LowlevelTransport] detectProtocol: uuid=${uuid} -> V1 (expected)`);
|
|
211
|
-
return 'V1';
|
|
212
|
-
}
|
|
213
|
-
throw this.createProtocolMismatchError(expectedProtocol);
|
|
214
|
-
}
|
|
215
|
-
if (protocolHint === 'V2' && (yield this.probeProtocolV2(uuid))) {
|
|
216
|
-
this.deviceProtocol.set(uuid, 'V2');
|
|
217
|
-
(_c = this.Log) === null || _c === void 0 ? void 0 : _c.debug(`[LowlevelTransport] detectProtocol: uuid=${uuid} -> V2 (hint)`);
|
|
218
|
-
return 'V2';
|
|
219
|
-
}
|
|
220
|
-
const cachedProtocol = this.deviceProtocol.get(uuid);
|
|
221
|
-
if (cachedProtocol === 'V2' && (yield this.probeProtocolV2(uuid))) {
|
|
222
|
-
this.deviceProtocol.set(uuid, 'V2');
|
|
223
|
-
(_d = this.Log) === null || _d === void 0 ? void 0 : _d.debug(`[LowlevelTransport] detectProtocol: uuid=${uuid} -> V2 (cached)`);
|
|
224
|
-
return 'V2';
|
|
225
|
-
}
|
|
226
|
-
const protocolV1Detected = yield this.probeProtocolV1(uuid);
|
|
227
|
-
if (protocolV1Detected) {
|
|
228
|
-
this.deviceProtocol.set(uuid, 'V1');
|
|
229
|
-
(_e = this.Log) === null || _e === void 0 ? void 0 : _e.debug(`[LowlevelTransport] detectProtocol: uuid=${uuid} -> V1`);
|
|
230
|
-
return 'V1';
|
|
231
|
-
}
|
|
232
|
-
yield this.resetConnectionAfterProbe(uuid, 'V1');
|
|
233
|
-
if (yield this.probeProtocolV2(uuid)) {
|
|
234
|
-
this.deviceProtocol.set(uuid, 'V2');
|
|
235
|
-
(_f = this.Log) === null || _f === void 0 ? void 0 : _f.debug(`[LowlevelTransport] detectProtocol: uuid=${uuid} -> V2`);
|
|
236
|
-
return 'V2';
|
|
237
|
-
}
|
|
238
|
-
this.deviceProtocol.delete(uuid);
|
|
239
|
-
throw this.createProtocolDetectionError();
|
|
240
|
-
});
|
|
241
|
-
}
|
|
242
|
-
resetConnectionAfterProbe(uuid, protocol) {
|
|
243
|
-
var _a, _b, _c, _d;
|
|
244
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
245
|
-
(_a = this.protocolV2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
|
|
246
|
-
try {
|
|
247
|
-
yield this.plugin.disconnect(uuid);
|
|
248
|
-
}
|
|
249
|
-
catch (error) {
|
|
250
|
-
(_b = this.Log) === null || _b === void 0 ? void 0 : _b.debug(`[LowlevelTransport] disconnect after Protocol ${protocol} probe failed:`, error);
|
|
251
|
-
}
|
|
252
|
-
try {
|
|
253
|
-
yield this.plugin.connect(uuid);
|
|
254
|
-
}
|
|
255
|
-
catch (error) {
|
|
256
|
-
(_c = this.Log) === null || _c === void 0 ? void 0 : _c.debug(`[LowlevelTransport] reconnect after Protocol ${protocol} probe failed:`, error);
|
|
257
|
-
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.LowlevelTrasnportConnectError, (_d = error.message) !== null && _d !== void 0 ? _d : error);
|
|
258
|
-
}
|
|
259
|
-
});
|
|
260
|
-
}
|
|
261
|
-
probeProtocolV1(uuid) {
|
|
262
|
-
var _a;
|
|
263
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
264
|
-
if (!this._messages) {
|
|
265
|
-
return false;
|
|
266
|
-
}
|
|
267
|
-
try {
|
|
268
|
-
this.deviceProtocol.set(uuid, 'V1');
|
|
269
|
-
yield this.callProtocolV1(uuid, 'Initialize', {}, { timeoutMs: PROTOCOL_PROBE_TIMEOUT_MS });
|
|
270
|
-
return true;
|
|
271
|
-
}
|
|
272
|
-
catch (error) {
|
|
273
|
-
this.clearProbeProtocol(uuid, 'V1');
|
|
274
|
-
(_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('[LowlevelTransport] Protocol V1 Initialize probe failed:', error);
|
|
275
|
-
return false;
|
|
276
|
-
}
|
|
277
|
-
});
|
|
278
|
-
}
|
|
279
|
-
probeProtocolV2(uuid) {
|
|
280
|
-
var _a;
|
|
281
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
282
|
-
if (!this._messages || !this._messagesV2) {
|
|
283
|
-
return false;
|
|
284
|
-
}
|
|
285
|
-
this.deviceProtocol.set(uuid, 'V2');
|
|
286
|
-
(_a = this.protocolV2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
|
|
287
|
-
try {
|
|
288
|
-
const detected = yield transport.probeProtocolV2({
|
|
289
|
-
call: (name, data, options) => this.callProtocolV2(uuid, name, data, options),
|
|
290
|
-
timeoutMs: PROTOCOL_V2_PROBE_TIMEOUT_MS,
|
|
291
|
-
logger: this.Log,
|
|
292
|
-
logPrefix: 'ProtocolV2 Lowlevel-BLE',
|
|
293
|
-
onProbeFailed: () => __awaiter(this, void 0, void 0, function* () {
|
|
294
|
-
var _b;
|
|
295
|
-
(_b = this.protocolV2Assemblers.get(uuid)) === null || _b === void 0 ? void 0 : _b.reset();
|
|
296
|
-
yield this.resetConnectionAfterProbe(uuid, 'V2');
|
|
297
|
-
}),
|
|
298
|
-
});
|
|
299
|
-
if (!detected) {
|
|
300
|
-
this.clearProbeProtocol(uuid, 'V2');
|
|
301
|
-
}
|
|
302
|
-
return detected;
|
|
303
|
-
}
|
|
304
|
-
catch (error) {
|
|
305
|
-
this.clearProbeProtocol(uuid, 'V2');
|
|
306
|
-
throw error;
|
|
307
|
-
}
|
|
308
|
-
});
|
|
309
|
-
}
|
|
310
|
-
receiveHex(timeoutMs, commandName) {
|
|
311
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
312
|
-
const response = yield transport.withProtocolTimeout(this.plugin.receive(), timeoutMs, () => this.createProtocolTimeoutError(commandName, timeoutMs !== null && timeoutMs !== void 0 ? timeoutMs : 0));
|
|
313
|
-
if (typeof response !== 'string') {
|
|
314
|
-
throw new Error('Returning data is not string');
|
|
315
|
-
}
|
|
316
|
-
return response;
|
|
317
|
-
});
|
|
318
|
-
}
|
|
319
|
-
readProtocolV1Message(timeoutMs) {
|
|
320
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
321
|
-
const first = yield this.receiveHex(timeoutMs, 'ProtocolV1');
|
|
322
|
-
const firstData = transport.hexToBytes(first);
|
|
323
|
-
if (!isProtocolV1TransportChunk(firstData)) {
|
|
324
|
-
return first;
|
|
325
|
-
}
|
|
326
|
-
const payloadLength = readProtocolV1PayloadLength(firstData);
|
|
327
|
-
let buffer = firstData.slice(3);
|
|
328
|
-
const expectedLength = transport.PROTOCOL_V1_MESSAGE_HEADER_SIZE + payloadLength;
|
|
329
|
-
while (buffer.length < expectedLength) {
|
|
330
|
-
const next = yield this.receiveHex(timeoutMs, 'ProtocolV1');
|
|
331
|
-
buffer = transport.concatUint8Arrays([buffer, transport.hexToBytes(next)]);
|
|
332
|
-
}
|
|
333
|
-
return transport.bytesToHex(buffer.slice(0, expectedLength));
|
|
334
|
-
});
|
|
335
|
-
}
|
|
336
|
-
readProtocolV2Frame(uuid, timeoutMs) {
|
|
337
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
338
|
-
let assembler = this.protocolV2Assemblers.get(uuid);
|
|
339
|
-
if (!assembler) {
|
|
340
|
-
assembler = new transport.ProtocolV2FrameAssembler();
|
|
341
|
-
this.protocolV2Assemblers.set(uuid, assembler);
|
|
342
|
-
}
|
|
343
|
-
const queuedFrame = assembler.push(new Uint8Array(0));
|
|
344
|
-
if (queuedFrame)
|
|
345
|
-
return queuedFrame;
|
|
346
|
-
let frame;
|
|
347
|
-
while (!frame) {
|
|
348
|
-
const response = yield this.receiveHex(timeoutMs, 'ProtocolV2');
|
|
349
|
-
const chunk = transport.hexToBytes(response);
|
|
350
|
-
if (chunk.length > 0) {
|
|
351
|
-
frame = assembler.push(chunk);
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
return frame;
|
|
355
|
-
});
|
|
356
|
-
}
|
|
357
|
-
writeProtocolV2Frame(uuid, frame) {
|
|
358
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
359
|
-
for (let offset = 0; offset < frame.length; offset += LOWLEVEL_PROTOCOL_V2_PACKET_LENGTH) {
|
|
360
|
-
const chunk = frame.slice(offset, offset + LOWLEVEL_PROTOCOL_V2_PACKET_LENGTH);
|
|
361
|
-
yield this.plugin.send(uuid, transport.bytesToHex(chunk));
|
|
362
|
-
}
|
|
363
|
-
});
|
|
364
|
-
}
|
|
365
|
-
callProtocolV2(uuid, name, data, options) {
|
|
366
|
-
var _a, _b, _c;
|
|
367
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
368
|
-
if (!this._messages || !this._messagesV2) {
|
|
369
|
-
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured);
|
|
370
|
-
}
|
|
371
|
-
const timeoutMs = (_a = options === null || options === void 0 ? void 0 : options.timeoutMs) !== null && _a !== void 0 ? _a : LOWLEVEL_PROTOCOL_TIMEOUT_MS;
|
|
372
|
-
(_b = this.protocolV2Assemblers.get(uuid)) === null || _b === void 0 ? void 0 : _b.reset();
|
|
373
|
-
const session = new transport.ProtocolV2Session({
|
|
374
|
-
schemas: {
|
|
375
|
-
protocolV1: this._messages,
|
|
376
|
-
protocolV2: this._messagesV2,
|
|
377
|
-
},
|
|
378
|
-
router: transport.PROTOCOL_V2_CHANNEL_BLE_UART,
|
|
379
|
-
writeFrame: (frame) => this.writeProtocolV2Frame(uuid, frame),
|
|
380
|
-
readFrame: () => this.readProtocolV2Frame(uuid, timeoutMs),
|
|
381
|
-
logger: this.Log,
|
|
382
|
-
logPrefix: 'ProtocolV2 Lowlevel-BLE',
|
|
383
|
-
createTimeoutError: (_messageName, timeout) => this.createProtocolTimeoutError(name, timeout),
|
|
384
|
-
});
|
|
385
|
-
try {
|
|
386
|
-
return yield session.call(name, data, Object.assign(Object.assign({}, options), { timeoutMs }));
|
|
387
|
-
}
|
|
388
|
-
catch (e) {
|
|
389
|
-
(_c = this.protocolV2Assemblers.get(uuid)) === null || _c === void 0 ? void 0 : _c.reset();
|
|
390
|
-
this.Log.error('lowlevel Protocol V2 call error: ', e);
|
|
121
|
+
this.Log.error('lowlevel call error: ', e);
|
|
391
122
|
throw e;
|
|
392
123
|
}
|
|
393
124
|
});
|
package/dist/types.d.ts
CHANGED
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,oBAAoB,GAAG;IACjC,IAAI,EAAE,MAAM,CAAC;CACd,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@onekeyfe/hd-transport-lowlevel",
|
|
3
|
-
"version": "1.1.27-alpha.
|
|
3
|
+
"version": "1.1.27-alpha.6",
|
|
4
4
|
"homepage": "https://github.com/OneKeyHQ/hardware-js-sdk#readme",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -15,13 +15,12 @@
|
|
|
15
15
|
"scripts": {
|
|
16
16
|
"dev": "rimraf dist && rollup -c ../../build/rollup.config.js -w",
|
|
17
17
|
"build": "rimraf dist && rollup -c ../../build/rollup.config.js",
|
|
18
|
-
"test": "jest",
|
|
19
18
|
"lint": "eslint .",
|
|
20
19
|
"lint:fix": "eslint . --fix"
|
|
21
20
|
},
|
|
22
21
|
"dependencies": {
|
|
23
|
-
"@onekeyfe/hd-shared": "1.1.27-alpha.
|
|
24
|
-
"@onekeyfe/hd-transport": "1.1.27-alpha.
|
|
22
|
+
"@onekeyfe/hd-shared": "1.1.27-alpha.6",
|
|
23
|
+
"@onekeyfe/hd-transport": "1.1.27-alpha.6"
|
|
25
24
|
},
|
|
26
|
-
"gitHead": "
|
|
25
|
+
"gitHead": "a1fb7786313ccee4b1e570c53ae2c05a96f785df"
|
|
27
26
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,50 +1,15 @@
|
|
|
1
1
|
import { ERRORS, HardwareErrorCode } from '@onekeyfe/hd-shared';
|
|
2
|
-
import transport, {
|
|
3
|
-
LogBlockCommand,
|
|
4
|
-
PROTOCOL_V1_MESSAGE_HEADER_SIZE,
|
|
5
|
-
PROTOCOL_V2_CHANNEL_BLE_UART,
|
|
6
|
-
ProtocolV2FrameAssembler,
|
|
7
|
-
ProtocolV2Session,
|
|
8
|
-
bytesToHex,
|
|
9
|
-
concatUint8Arrays,
|
|
10
|
-
hexToBytes,
|
|
11
|
-
probeProtocolV2 as probeProtocolV2Helper,
|
|
12
|
-
withProtocolTimeout,
|
|
13
|
-
} from '@onekeyfe/hd-transport';
|
|
2
|
+
import transport, { LogBlockCommand } from '@onekeyfe/hd-transport';
|
|
14
3
|
|
|
15
4
|
import type EventEmitter from 'events';
|
|
16
|
-
import type {
|
|
17
|
-
LowLevelDevice,
|
|
18
|
-
LowlevelTransportSharedPlugin,
|
|
19
|
-
ProtocolType,
|
|
20
|
-
TransportCallOptions,
|
|
21
|
-
} from '@onekeyfe/hd-transport';
|
|
5
|
+
import type { LowlevelTransportSharedPlugin } from '@onekeyfe/hd-transport';
|
|
22
6
|
import type { LowLevelAcquireInput } from './types';
|
|
23
7
|
|
|
24
|
-
const { check,
|
|
25
|
-
|
|
26
|
-
const PROTOCOL_PROBE_TIMEOUT_MS = 1000;
|
|
27
|
-
const PROTOCOL_V2_PROBE_TIMEOUT_MS = 5000;
|
|
28
|
-
const LOWLEVEL_PROTOCOL_TIMEOUT_MS = 30_000;
|
|
29
|
-
const LOWLEVEL_PROTOCOL_V2_PACKET_LENGTH = 64;
|
|
30
|
-
|
|
31
|
-
function inferProtocolHintFromDeviceName(name?: string | null): ProtocolType | undefined {
|
|
32
|
-
return /\bpro\s*2\b/i.test(name ?? '') ? 'V2' : undefined;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function isProtocolV1TransportChunk(data: Uint8Array) {
|
|
36
|
-
return data.length >= 9 && data[0] === 0x3f && data[1] === 0x23 && data[2] === 0x23;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function readProtocolV1PayloadLength(data: Uint8Array) {
|
|
40
|
-
return data[5] * 0x1000000 + data[6] * 0x10000 + data[7] * 0x100 + data[8];
|
|
41
|
-
}
|
|
8
|
+
const { check, buildBuffers, receiveOne, parseConfigure } = transport;
|
|
42
9
|
|
|
43
10
|
export default class LowlevelTransport {
|
|
44
11
|
_messages: ReturnType<typeof transport.parseConfigure> | undefined;
|
|
45
12
|
|
|
46
|
-
_messagesV2: ReturnType<typeof transport.parseConfigure> | undefined;
|
|
47
|
-
|
|
48
13
|
configured = false;
|
|
49
14
|
|
|
50
15
|
Log?: any;
|
|
@@ -53,16 +18,6 @@ export default class LowlevelTransport {
|
|
|
53
18
|
|
|
54
19
|
plugin: LowlevelTransportSharedPlugin = {} as LowlevelTransportSharedPlugin;
|
|
55
20
|
|
|
56
|
-
private deviceProtocol: Map<string, ProtocolType> = new Map();
|
|
57
|
-
|
|
58
|
-
private deviceProtocolHints: Map<string, ProtocolType> = new Map();
|
|
59
|
-
|
|
60
|
-
private protocolV2Assemblers: Map<string, ProtocolV2FrameAssembler> = new Map();
|
|
61
|
-
|
|
62
|
-
getProtocolType(path: string): ProtocolType | undefined {
|
|
63
|
-
return this.deviceProtocol.get(path);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
21
|
init(logger: any, emitter: EventEmitter, plugin: LowlevelTransportSharedPlugin) {
|
|
67
22
|
this.Log = logger;
|
|
68
23
|
this.emitter = emitter;
|
|
@@ -76,28 +31,18 @@ export default class LowlevelTransport {
|
|
|
76
31
|
this._messages = messages;
|
|
77
32
|
}
|
|
78
33
|
|
|
79
|
-
configureProtocolV2(signedData: any) {
|
|
80
|
-
this._messagesV2 = parseConfigure(signedData);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
34
|
listen() {
|
|
84
35
|
// empty
|
|
85
36
|
}
|
|
86
37
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
return devices.map((device: LowLevelDevice) => {
|
|
90
|
-
const protocolHint = inferProtocolHintFromDeviceName(device.name);
|
|
91
|
-
if (protocolHint) {
|
|
92
|
-
this.deviceProtocolHints.set(device.id, protocolHint);
|
|
93
|
-
}
|
|
94
|
-
return device;
|
|
95
|
-
});
|
|
38
|
+
enumerate() {
|
|
39
|
+
return this.plugin.enumerate();
|
|
96
40
|
}
|
|
97
41
|
|
|
98
42
|
async acquire(input: LowLevelAcquireInput) {
|
|
99
43
|
try {
|
|
100
44
|
await this.plugin.connect(input.uuid);
|
|
45
|
+
return { uuid: input.uuid };
|
|
101
46
|
} catch (error) {
|
|
102
47
|
this.Log.debug('lowlelvel transport connect error: ', error);
|
|
103
48
|
throw ERRORS.TypedError(
|
|
@@ -105,25 +50,11 @@ export default class LowlevelTransport {
|
|
|
105
50
|
error.message ?? error
|
|
106
51
|
);
|
|
107
52
|
}
|
|
108
|
-
|
|
109
|
-
this.protocolV2Assemblers.set(input.uuid, new ProtocolV2FrameAssembler());
|
|
110
|
-
const protocolHint = input.expectedProtocol
|
|
111
|
-
? undefined
|
|
112
|
-
: this.deviceProtocolHints.get(input.uuid);
|
|
113
|
-
const protocolType = await this.detectProtocol(
|
|
114
|
-
input.uuid,
|
|
115
|
-
input.expectedProtocol,
|
|
116
|
-
protocolHint
|
|
117
|
-
);
|
|
118
|
-
return { uuid: input.uuid, protocolType };
|
|
119
53
|
}
|
|
120
54
|
|
|
121
55
|
async release(uuid: string) {
|
|
122
56
|
try {
|
|
123
57
|
await this.plugin.disconnect(uuid);
|
|
124
|
-
this.deviceProtocol.delete(uuid);
|
|
125
|
-
this.deviceProtocolHints.delete(uuid);
|
|
126
|
-
this.protocolV2Assemblers.delete(uuid);
|
|
127
58
|
return true;
|
|
128
59
|
} catch (error) {
|
|
129
60
|
this.Log.debug('lowlelvel transport disconnect error: ', error);
|
|
@@ -131,57 +62,19 @@ export default class LowlevelTransport {
|
|
|
131
62
|
}
|
|
132
63
|
}
|
|
133
64
|
|
|
134
|
-
async call(
|
|
135
|
-
uuid: string,
|
|
136
|
-
name: string,
|
|
137
|
-
data: Record<string, unknown>,
|
|
138
|
-
options?: TransportCallOptions
|
|
139
|
-
) {
|
|
65
|
+
async call(uuid: string, name: string, data: Record<string, unknown>) {
|
|
140
66
|
if (this._messages === null || !this._messages) {
|
|
141
67
|
throw ERRORS.TypedError(HardwareErrorCode.TransportNotConfigured);
|
|
142
68
|
}
|
|
143
69
|
|
|
144
|
-
const
|
|
145
|
-
if (!protocol) {
|
|
146
|
-
throw ERRORS.TypedError(
|
|
147
|
-
HardwareErrorCode.RuntimeError,
|
|
148
|
-
`Device protocol has not been detected for ${uuid}`
|
|
149
|
-
);
|
|
150
|
-
}
|
|
70
|
+
const messages = this._messages;
|
|
151
71
|
if (LogBlockCommand.has(name)) {
|
|
152
|
-
this.Log.debug('lowlevel-transport', 'call-', ' name: ', name
|
|
72
|
+
this.Log.debug('lowlevel-transport', 'call-', ' name: ', name);
|
|
153
73
|
} else {
|
|
154
|
-
this.Log.debug(
|
|
155
|
-
'lowlevel-transport',
|
|
156
|
-
'call-',
|
|
157
|
-
' name: ',
|
|
158
|
-
name,
|
|
159
|
-
' data: ',
|
|
160
|
-
data,
|
|
161
|
-
' protocol: ',
|
|
162
|
-
protocol
|
|
163
|
-
);
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
if (protocol === 'V2') {
|
|
167
|
-
return this.callProtocolV2(uuid, name, data, options);
|
|
74
|
+
this.Log.debug('lowlevel-transport', 'call-', ' name: ', name, ' data: ', data);
|
|
168
75
|
}
|
|
169
76
|
|
|
170
|
-
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
private async callProtocolV1(
|
|
174
|
-
uuid: string,
|
|
175
|
-
name: string,
|
|
176
|
-
data: Record<string, unknown>,
|
|
177
|
-
options?: TransportCallOptions
|
|
178
|
-
) {
|
|
179
|
-
if (!this._messages) {
|
|
180
|
-
throw ERRORS.TypedError(HardwareErrorCode.TransportNotConfigured);
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
const messages = this._messages;
|
|
184
|
-
const buffers = ProtocolV1.encodeTransportPackets(messages, name, data);
|
|
77
|
+
const buffers = buildBuffers(messages, name, data);
|
|
185
78
|
for (const o of buffers) {
|
|
186
79
|
const outData = o.toString('hex');
|
|
187
80
|
// Upload resources on low-end phones may OOM
|
|
@@ -195,263 +88,15 @@ export default class LowlevelTransport {
|
|
|
195
88
|
}
|
|
196
89
|
|
|
197
90
|
try {
|
|
198
|
-
const response = await this.
|
|
91
|
+
const response = await this.plugin.receive();
|
|
92
|
+
if (typeof response !== 'string') {
|
|
93
|
+
throw new Error('Returning data is not string');
|
|
94
|
+
}
|
|
199
95
|
this.Log.debug('receive data: ', response);
|
|
200
|
-
const jsonData =
|
|
96
|
+
const jsonData = receiveOne(messages, response);
|
|
201
97
|
return check.call(jsonData);
|
|
202
98
|
} catch (e) {
|
|
203
|
-
|
|
204
|
-
this.Log.debug('[LowlevelTransport] Protocol V1 Initialize probe call failed:', e);
|
|
205
|
-
} else {
|
|
206
|
-
this.Log.error('lowlevel call error: ', e);
|
|
207
|
-
}
|
|
208
|
-
throw e;
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
private createProtocolTimeoutError(name: string, timeout: number) {
|
|
213
|
-
return ERRORS.TypedError(
|
|
214
|
-
HardwareErrorCode.BleTimeoutError,
|
|
215
|
-
`Lowlevel response timeout after ${timeout}ms for ${name}`
|
|
216
|
-
);
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
private createProtocolMismatchError(expected: ProtocolType) {
|
|
220
|
-
return ERRORS.TypedError(
|
|
221
|
-
HardwareErrorCode.RuntimeError,
|
|
222
|
-
`Device protocol mismatch: expected ${expected}, but device did not respond to expected protocol`
|
|
223
|
-
);
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
private createProtocolDetectionError() {
|
|
227
|
-
return ERRORS.TypedError(
|
|
228
|
-
HardwareErrorCode.BleTimeoutError,
|
|
229
|
-
'Unable to detect BLE protocol: device did not respond to Protocol V1 Initialize or Protocol V2 Ping'
|
|
230
|
-
);
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
private clearProbeProtocol(uuid: string, protocol: ProtocolType) {
|
|
234
|
-
if (this.deviceProtocol.get(uuid) === protocol) {
|
|
235
|
-
this.deviceProtocol.delete(uuid);
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
private async detectProtocol(
|
|
240
|
-
uuid: string,
|
|
241
|
-
expectedProtocol?: ProtocolType,
|
|
242
|
-
protocolHint?: ProtocolType
|
|
243
|
-
): Promise<ProtocolType> {
|
|
244
|
-
if (expectedProtocol === 'V2') {
|
|
245
|
-
if (await this.probeProtocolV2(uuid)) {
|
|
246
|
-
this.deviceProtocol.set(uuid, 'V2');
|
|
247
|
-
this.Log?.debug(`[LowlevelTransport] detectProtocol: uuid=${uuid} -> V2 (expected)`);
|
|
248
|
-
return 'V2';
|
|
249
|
-
}
|
|
250
|
-
throw this.createProtocolMismatchError(expectedProtocol);
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
if (expectedProtocol === 'V1') {
|
|
254
|
-
if (await this.probeProtocolV1(uuid)) {
|
|
255
|
-
this.deviceProtocol.set(uuid, 'V1');
|
|
256
|
-
this.Log?.debug(`[LowlevelTransport] detectProtocol: uuid=${uuid} -> V1 (expected)`);
|
|
257
|
-
return 'V1';
|
|
258
|
-
}
|
|
259
|
-
throw this.createProtocolMismatchError(expectedProtocol);
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
if (protocolHint === 'V2' && (await this.probeProtocolV2(uuid))) {
|
|
263
|
-
this.deviceProtocol.set(uuid, 'V2');
|
|
264
|
-
this.Log?.debug(`[LowlevelTransport] detectProtocol: uuid=${uuid} -> V2 (hint)`);
|
|
265
|
-
return 'V2';
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
const cachedProtocol = this.deviceProtocol.get(uuid);
|
|
269
|
-
if (cachedProtocol === 'V2' && (await this.probeProtocolV2(uuid))) {
|
|
270
|
-
this.deviceProtocol.set(uuid, 'V2');
|
|
271
|
-
this.Log?.debug(`[LowlevelTransport] detectProtocol: uuid=${uuid} -> V2 (cached)`);
|
|
272
|
-
return 'V2';
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
const protocolV1Detected = await this.probeProtocolV1(uuid);
|
|
276
|
-
if (protocolV1Detected) {
|
|
277
|
-
this.deviceProtocol.set(uuid, 'V1');
|
|
278
|
-
this.Log?.debug(`[LowlevelTransport] detectProtocol: uuid=${uuid} -> V1`);
|
|
279
|
-
return 'V1';
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
await this.resetConnectionAfterProbe(uuid, 'V1');
|
|
283
|
-
if (await this.probeProtocolV2(uuid)) {
|
|
284
|
-
this.deviceProtocol.set(uuid, 'V2');
|
|
285
|
-
this.Log?.debug(`[LowlevelTransport] detectProtocol: uuid=${uuid} -> V2`);
|
|
286
|
-
return 'V2';
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
this.deviceProtocol.delete(uuid);
|
|
290
|
-
throw this.createProtocolDetectionError();
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
private async resetConnectionAfterProbe(uuid: string, protocol: ProtocolType) {
|
|
294
|
-
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
295
|
-
|
|
296
|
-
try {
|
|
297
|
-
await this.plugin.disconnect(uuid);
|
|
298
|
-
} catch (error) {
|
|
299
|
-
this.Log?.debug(
|
|
300
|
-
`[LowlevelTransport] disconnect after Protocol ${protocol} probe failed:`,
|
|
301
|
-
error
|
|
302
|
-
);
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
try {
|
|
306
|
-
await this.plugin.connect(uuid);
|
|
307
|
-
} catch (error) {
|
|
308
|
-
this.Log?.debug(
|
|
309
|
-
`[LowlevelTransport] reconnect after Protocol ${protocol} probe failed:`,
|
|
310
|
-
error
|
|
311
|
-
);
|
|
312
|
-
throw ERRORS.TypedError(
|
|
313
|
-
HardwareErrorCode.LowlevelTrasnportConnectError,
|
|
314
|
-
error.message ?? error
|
|
315
|
-
);
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
private async probeProtocolV1(uuid: string) {
|
|
320
|
-
if (!this._messages) {
|
|
321
|
-
return false;
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
try {
|
|
325
|
-
this.deviceProtocol.set(uuid, 'V1');
|
|
326
|
-
await this.callProtocolV1(uuid, 'Initialize', {}, { timeoutMs: PROTOCOL_PROBE_TIMEOUT_MS });
|
|
327
|
-
return true;
|
|
328
|
-
} catch (error) {
|
|
329
|
-
this.clearProbeProtocol(uuid, 'V1');
|
|
330
|
-
this.Log?.debug('[LowlevelTransport] Protocol V1 Initialize probe failed:', error);
|
|
331
|
-
return false;
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
private async probeProtocolV2(uuid: string) {
|
|
336
|
-
if (!this._messages || !this._messagesV2) {
|
|
337
|
-
return false;
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
this.deviceProtocol.set(uuid, 'V2');
|
|
341
|
-
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
342
|
-
try {
|
|
343
|
-
const detected = await probeProtocolV2Helper({
|
|
344
|
-
call: (name: string, data: Record<string, unknown>, options?: TransportCallOptions) =>
|
|
345
|
-
this.callProtocolV2(uuid, name, data, options),
|
|
346
|
-
timeoutMs: PROTOCOL_V2_PROBE_TIMEOUT_MS,
|
|
347
|
-
logger: this.Log,
|
|
348
|
-
logPrefix: 'ProtocolV2 Lowlevel-BLE',
|
|
349
|
-
onProbeFailed: async () => {
|
|
350
|
-
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
351
|
-
await this.resetConnectionAfterProbe(uuid, 'V2');
|
|
352
|
-
},
|
|
353
|
-
});
|
|
354
|
-
if (!detected) {
|
|
355
|
-
this.clearProbeProtocol(uuid, 'V2');
|
|
356
|
-
}
|
|
357
|
-
return detected;
|
|
358
|
-
} catch (error) {
|
|
359
|
-
this.clearProbeProtocol(uuid, 'V2');
|
|
360
|
-
throw error;
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
private async receiveHex(timeoutMs: number | undefined, commandName: string) {
|
|
365
|
-
const response = await withProtocolTimeout(this.plugin.receive(), timeoutMs, () =>
|
|
366
|
-
this.createProtocolTimeoutError(commandName, timeoutMs ?? 0)
|
|
367
|
-
);
|
|
368
|
-
if (typeof response !== 'string') {
|
|
369
|
-
throw new Error('Returning data is not string');
|
|
370
|
-
}
|
|
371
|
-
return response;
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
private async readProtocolV1Message(timeoutMs?: number) {
|
|
375
|
-
const first = await this.receiveHex(timeoutMs, 'ProtocolV1');
|
|
376
|
-
const firstData = hexToBytes(first);
|
|
377
|
-
if (!isProtocolV1TransportChunk(firstData)) {
|
|
378
|
-
return first;
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
const payloadLength = readProtocolV1PayloadLength(firstData);
|
|
382
|
-
let buffer = firstData.slice(3);
|
|
383
|
-
const expectedLength = PROTOCOL_V1_MESSAGE_HEADER_SIZE + payloadLength;
|
|
384
|
-
|
|
385
|
-
while (buffer.length < expectedLength) {
|
|
386
|
-
const next = await this.receiveHex(timeoutMs, 'ProtocolV1');
|
|
387
|
-
buffer = concatUint8Arrays([buffer, hexToBytes(next)]);
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
return bytesToHex(buffer.slice(0, expectedLength));
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
private async readProtocolV2Frame(uuid: string, timeoutMs?: number) {
|
|
394
|
-
let assembler = this.protocolV2Assemblers.get(uuid);
|
|
395
|
-
if (!assembler) {
|
|
396
|
-
assembler = new ProtocolV2FrameAssembler();
|
|
397
|
-
this.protocolV2Assemblers.set(uuid, assembler);
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
const queuedFrame = assembler.push(new Uint8Array(0));
|
|
401
|
-
if (queuedFrame) return queuedFrame;
|
|
402
|
-
|
|
403
|
-
let frame: Uint8Array | undefined;
|
|
404
|
-
while (!frame) {
|
|
405
|
-
const response = await this.receiveHex(timeoutMs, 'ProtocolV2');
|
|
406
|
-
const chunk = hexToBytes(response);
|
|
407
|
-
if (chunk.length > 0) {
|
|
408
|
-
frame = assembler.push(chunk);
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
return frame;
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
private async writeProtocolV2Frame(uuid: string, frame: Uint8Array) {
|
|
415
|
-
for (let offset = 0; offset < frame.length; offset += LOWLEVEL_PROTOCOL_V2_PACKET_LENGTH) {
|
|
416
|
-
const chunk = frame.slice(offset, offset + LOWLEVEL_PROTOCOL_V2_PACKET_LENGTH);
|
|
417
|
-
await this.plugin.send(uuid, bytesToHex(chunk));
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
private async callProtocolV2(
|
|
422
|
-
uuid: string,
|
|
423
|
-
name: string,
|
|
424
|
-
data: Record<string, unknown>,
|
|
425
|
-
options?: TransportCallOptions
|
|
426
|
-
) {
|
|
427
|
-
if (!this._messages || !this._messagesV2) {
|
|
428
|
-
throw ERRORS.TypedError(HardwareErrorCode.TransportNotConfigured);
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
const timeoutMs = options?.timeoutMs ?? LOWLEVEL_PROTOCOL_TIMEOUT_MS;
|
|
432
|
-
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
433
|
-
const session = new ProtocolV2Session({
|
|
434
|
-
schemas: {
|
|
435
|
-
protocolV1: this._messages,
|
|
436
|
-
protocolV2: this._messagesV2,
|
|
437
|
-
},
|
|
438
|
-
router: PROTOCOL_V2_CHANNEL_BLE_UART,
|
|
439
|
-
writeFrame: (frame: Uint8Array) => this.writeProtocolV2Frame(uuid, frame),
|
|
440
|
-
readFrame: () => this.readProtocolV2Frame(uuid, timeoutMs),
|
|
441
|
-
logger: this.Log,
|
|
442
|
-
logPrefix: 'ProtocolV2 Lowlevel-BLE',
|
|
443
|
-
createTimeoutError: (_messageName: string, timeout: number) =>
|
|
444
|
-
this.createProtocolTimeoutError(name, timeout),
|
|
445
|
-
});
|
|
446
|
-
|
|
447
|
-
try {
|
|
448
|
-
return await session.call(name, data, {
|
|
449
|
-
...options,
|
|
450
|
-
timeoutMs,
|
|
451
|
-
});
|
|
452
|
-
} catch (e) {
|
|
453
|
-
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
454
|
-
this.Log.error('lowlevel Protocol V2 call error: ', e);
|
|
99
|
+
this.Log.error('lowlevel call error: ', e);
|
|
455
100
|
throw e;
|
|
456
101
|
}
|
|
457
102
|
}
|
package/src/types.ts
CHANGED
package/.eslintignore
DELETED
|
@@ -1,288 +0,0 @@
|
|
|
1
|
-
/* eslint-disable @typescript-eslint/no-var-requires */
|
|
2
|
-
const LowlevelTransport = require('../src').default;
|
|
3
|
-
const { parseConfigure } = require('../../hd-transport/src/serialization/protobuf/messages');
|
|
4
|
-
const { ProtocolV1, ProtocolV2 } = require('../../hd-transport/src/protocols');
|
|
5
|
-
const { bytesToHex } = require('../../hd-transport/src/protocols/v2/session');
|
|
6
|
-
const { PROTOCOL_V2_CHANNEL_BLE_UART } = require('../../hd-transport/src/constants');
|
|
7
|
-
|
|
8
|
-
const protocolV1Schema = {
|
|
9
|
-
nested: {
|
|
10
|
-
Initialize: {
|
|
11
|
-
fields: {},
|
|
12
|
-
},
|
|
13
|
-
Success: {
|
|
14
|
-
fields: {
|
|
15
|
-
message: {
|
|
16
|
-
type: 'string',
|
|
17
|
-
id: 1,
|
|
18
|
-
},
|
|
19
|
-
},
|
|
20
|
-
},
|
|
21
|
-
MessageType: {
|
|
22
|
-
values: {
|
|
23
|
-
MessageType_Initialize: 1,
|
|
24
|
-
MessageType_Success: 2,
|
|
25
|
-
},
|
|
26
|
-
},
|
|
27
|
-
},
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
const protocolV2Schema = {
|
|
31
|
-
nested: {
|
|
32
|
-
GetProtoVersion: {
|
|
33
|
-
fields: {},
|
|
34
|
-
},
|
|
35
|
-
ProtoVersion: {
|
|
36
|
-
fields: {
|
|
37
|
-
major_version: {
|
|
38
|
-
type: 'uint32',
|
|
39
|
-
id: 1,
|
|
40
|
-
},
|
|
41
|
-
minor_version: {
|
|
42
|
-
type: 'uint32',
|
|
43
|
-
id: 2,
|
|
44
|
-
},
|
|
45
|
-
patch_version: {
|
|
46
|
-
type: 'uint32',
|
|
47
|
-
id: 3,
|
|
48
|
-
},
|
|
49
|
-
},
|
|
50
|
-
},
|
|
51
|
-
Ping: {
|
|
52
|
-
fields: {
|
|
53
|
-
message: {
|
|
54
|
-
type: 'string',
|
|
55
|
-
id: 1,
|
|
56
|
-
},
|
|
57
|
-
},
|
|
58
|
-
},
|
|
59
|
-
Success: {
|
|
60
|
-
fields: {
|
|
61
|
-
message: {
|
|
62
|
-
type: 'string',
|
|
63
|
-
id: 1,
|
|
64
|
-
},
|
|
65
|
-
},
|
|
66
|
-
},
|
|
67
|
-
MessageType: {
|
|
68
|
-
values: {
|
|
69
|
-
MessageType_GetProtoVersion: 60200,
|
|
70
|
-
MessageType_ProtoVersion: 60201,
|
|
71
|
-
MessageType_Ping: 60206,
|
|
72
|
-
MessageType_Success: 60207,
|
|
73
|
-
},
|
|
74
|
-
},
|
|
75
|
-
},
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
const schemas = {
|
|
79
|
-
protocolV1: parseConfigure(protocolV1Schema),
|
|
80
|
-
protocolV2: parseConfigure(protocolV2Schema),
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
const createLogger = () => ({
|
|
84
|
-
debug: jest.fn(),
|
|
85
|
-
error: jest.fn(),
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
const createPlugin = ({ devices, responses }) => ({
|
|
89
|
-
enumerate: jest.fn(() => Promise.resolve(devices)),
|
|
90
|
-
connect: jest.fn(() => Promise.resolve()),
|
|
91
|
-
disconnect: jest.fn(() => Promise.resolve()),
|
|
92
|
-
init: jest.fn(() => Promise.resolve()),
|
|
93
|
-
send: jest.fn(() => Promise.resolve()),
|
|
94
|
-
receive: jest.fn(() => {
|
|
95
|
-
const next = responses.shift();
|
|
96
|
-
if (next instanceof Error) {
|
|
97
|
-
return Promise.reject(next);
|
|
98
|
-
}
|
|
99
|
-
if (!next) {
|
|
100
|
-
return Promise.reject(new Error('No queued response'));
|
|
101
|
-
}
|
|
102
|
-
return Promise.resolve(next);
|
|
103
|
-
}),
|
|
104
|
-
version: 'test-plugin',
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
const configureTransport = plugin => {
|
|
108
|
-
const lowlevel = new LowlevelTransport();
|
|
109
|
-
lowlevel.init(createLogger(), undefined, plugin);
|
|
110
|
-
lowlevel.configure(protocolV1Schema);
|
|
111
|
-
lowlevel.configureProtocolV2(protocolV2Schema);
|
|
112
|
-
return lowlevel;
|
|
113
|
-
};
|
|
114
|
-
|
|
115
|
-
const splitFrame = (frame, index) => [
|
|
116
|
-
bytesToHex(frame.slice(0, index)),
|
|
117
|
-
bytesToHex(frame.slice(index)),
|
|
118
|
-
];
|
|
119
|
-
|
|
120
|
-
describe('LowlevelTransport protocol framing', () => {
|
|
121
|
-
test('keeps Protocol V1 raw notification chunks compatible', async () => {
|
|
122
|
-
const responseChunks = ProtocolV1.encodeTransportPackets(schemas.protocolV1, 'Success', {
|
|
123
|
-
message: 'ok',
|
|
124
|
-
}).map(chunk => chunk.toString('hex'));
|
|
125
|
-
const plugin = createPlugin({
|
|
126
|
-
devices: [{ id: 'classic-id', name: 'OneKey Classic', commType: 'ble' }],
|
|
127
|
-
responses: [...responseChunks, ...responseChunks],
|
|
128
|
-
});
|
|
129
|
-
const lowlevel = configureTransport(plugin);
|
|
130
|
-
|
|
131
|
-
await expect(lowlevel.acquire({ uuid: 'classic-id' })).resolves.toEqual({
|
|
132
|
-
uuid: 'classic-id',
|
|
133
|
-
protocolType: 'V1',
|
|
134
|
-
});
|
|
135
|
-
await expect(lowlevel.call('classic-id', 'Initialize', {})).resolves.toEqual({
|
|
136
|
-
type: 'Success',
|
|
137
|
-
message: { message: 'ok' },
|
|
138
|
-
});
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
test('rejects calls before protocol detection', async () => {
|
|
142
|
-
const responseChunks = ProtocolV1.encodeTransportPackets(schemas.protocolV1, 'Success', {
|
|
143
|
-
message: 'ok',
|
|
144
|
-
}).map(chunk => chunk.toString('hex'));
|
|
145
|
-
const plugin = createPlugin({
|
|
146
|
-
devices: [{ id: 'classic-id', name: 'OneKey Classic', commType: 'ble' }],
|
|
147
|
-
responses: responseChunks,
|
|
148
|
-
});
|
|
149
|
-
const lowlevel = configureTransport(plugin);
|
|
150
|
-
|
|
151
|
-
await expect(lowlevel.call('classic-id', 'Initialize', {})).rejects.toThrow(
|
|
152
|
-
'Device protocol has not been detected'
|
|
153
|
-
);
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
test('detects Protocol V2 devices and reassembles split Protocol V2 notifications', async () => {
|
|
157
|
-
const probeResponse = ProtocolV2.encodeFrame(
|
|
158
|
-
schemas,
|
|
159
|
-
'Success',
|
|
160
|
-
{
|
|
161
|
-
message: 'probe',
|
|
162
|
-
},
|
|
163
|
-
{ router: PROTOCOL_V2_CHANNEL_BLE_UART }
|
|
164
|
-
);
|
|
165
|
-
const callResponse = ProtocolV2.encodeFrame(
|
|
166
|
-
schemas,
|
|
167
|
-
'ProtoVersion',
|
|
168
|
-
{
|
|
169
|
-
major_version: 2,
|
|
170
|
-
minor_version: 1,
|
|
171
|
-
patch_version: 3,
|
|
172
|
-
},
|
|
173
|
-
{ router: PROTOCOL_V2_CHANNEL_BLE_UART }
|
|
174
|
-
);
|
|
175
|
-
const plugin = createPlugin({
|
|
176
|
-
devices: [{ id: 'pro2-id', name: 'OneKey Pro 2', commType: 'ble' }],
|
|
177
|
-
responses: [...splitFrame(probeResponse, 4), ...splitFrame(callResponse, 5)],
|
|
178
|
-
});
|
|
179
|
-
const lowlevel = configureTransport(plugin);
|
|
180
|
-
|
|
181
|
-
await expect(lowlevel.enumerate()).resolves.toEqual([
|
|
182
|
-
{ id: 'pro2-id', name: 'OneKey Pro 2', commType: 'ble' },
|
|
183
|
-
]);
|
|
184
|
-
await expect(lowlevel.acquire({ uuid: 'pro2-id' })).resolves.toEqual({
|
|
185
|
-
uuid: 'pro2-id',
|
|
186
|
-
protocolType: 'V2',
|
|
187
|
-
});
|
|
188
|
-
await expect(lowlevel.call('pro2-id', 'GetProtoVersion', {})).resolves.toEqual({
|
|
189
|
-
type: 'ProtoVersion',
|
|
190
|
-
message: {
|
|
191
|
-
major_version: 2,
|
|
192
|
-
minor_version: 1,
|
|
193
|
-
patch_version: 3,
|
|
194
|
-
},
|
|
195
|
-
});
|
|
196
|
-
expect(plugin.send).toHaveBeenCalled();
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
test('falls back to Protocol V2 probe for unnamed Protocol V2 devices', async () => {
|
|
200
|
-
const probeResponse = ProtocolV2.encodeFrame(
|
|
201
|
-
schemas,
|
|
202
|
-
'Success',
|
|
203
|
-
{
|
|
204
|
-
message: 'probe',
|
|
205
|
-
},
|
|
206
|
-
{ router: PROTOCOL_V2_CHANNEL_BLE_UART }
|
|
207
|
-
);
|
|
208
|
-
const plugin = createPlugin({
|
|
209
|
-
devices: [{ id: 'unknown-pro2-id', name: 'Unknown BLE Device', commType: 'ble' }],
|
|
210
|
-
responses: [new Error('Protocol V1 probe timed out'), bytesToHex(probeResponse)],
|
|
211
|
-
});
|
|
212
|
-
const lowlevel = configureTransport(plugin);
|
|
213
|
-
|
|
214
|
-
await expect(lowlevel.acquire({ uuid: 'unknown-pro2-id' })).resolves.toEqual({
|
|
215
|
-
uuid: 'unknown-pro2-id',
|
|
216
|
-
protocolType: 'V2',
|
|
217
|
-
});
|
|
218
|
-
expect(lowlevel.getProtocolType('unknown-pro2-id')).toBe('V2');
|
|
219
|
-
});
|
|
220
|
-
|
|
221
|
-
test('resets the lowlevel connection before probing Protocol V2 after a V1 timeout', async () => {
|
|
222
|
-
const probeResponse = ProtocolV2.encodeFrame(
|
|
223
|
-
schemas,
|
|
224
|
-
'Success',
|
|
225
|
-
{
|
|
226
|
-
message: 'probe',
|
|
227
|
-
},
|
|
228
|
-
{ router: PROTOCOL_V2_CHANNEL_BLE_UART }
|
|
229
|
-
);
|
|
230
|
-
let staleReceivePending = false;
|
|
231
|
-
let resetAfterTimeout = false;
|
|
232
|
-
const plugin = createPlugin({
|
|
233
|
-
devices: [{ id: 'slow-v2-id', name: 'Unknown BLE Device', commType: 'ble' }],
|
|
234
|
-
responses: [],
|
|
235
|
-
});
|
|
236
|
-
plugin.disconnect.mockImplementation(() => {
|
|
237
|
-
staleReceivePending = false;
|
|
238
|
-
resetAfterTimeout = true;
|
|
239
|
-
return Promise.resolve();
|
|
240
|
-
});
|
|
241
|
-
plugin.receive.mockImplementation(() => {
|
|
242
|
-
if (!staleReceivePending && !resetAfterTimeout) {
|
|
243
|
-
staleReceivePending = true;
|
|
244
|
-
return new Promise(() => {});
|
|
245
|
-
}
|
|
246
|
-
if (staleReceivePending) {
|
|
247
|
-
return Promise.reject(new Error('stale receive still pending'));
|
|
248
|
-
}
|
|
249
|
-
return Promise.resolve(bytesToHex(probeResponse));
|
|
250
|
-
});
|
|
251
|
-
const lowlevel = configureTransport(plugin);
|
|
252
|
-
|
|
253
|
-
await expect(lowlevel.acquire({ uuid: 'slow-v2-id' })).resolves.toEqual({
|
|
254
|
-
uuid: 'slow-v2-id',
|
|
255
|
-
protocolType: 'V2',
|
|
256
|
-
});
|
|
257
|
-
expect(plugin.disconnect).toHaveBeenCalledWith('slow-v2-id');
|
|
258
|
-
expect(plugin.connect).toHaveBeenCalledTimes(2);
|
|
259
|
-
});
|
|
260
|
-
|
|
261
|
-
test('verifies expected Protocol V1 instead of trusting the requested protocol', async () => {
|
|
262
|
-
const plugin = createPlugin({
|
|
263
|
-
devices: [{ id: 'v2-id', name: 'Unknown BLE Device', commType: 'ble' }],
|
|
264
|
-
responses: [new Error('Protocol V1 probe timed out')],
|
|
265
|
-
});
|
|
266
|
-
const lowlevel = configureTransport(plugin);
|
|
267
|
-
|
|
268
|
-
await expect(lowlevel.acquire({ uuid: 'v2-id', expectedProtocol: 'V1' })).rejects.toThrow(
|
|
269
|
-
'Device protocol mismatch: expected V1'
|
|
270
|
-
);
|
|
271
|
-
});
|
|
272
|
-
|
|
273
|
-
test('rejects automatic detection instead of caching V1 when both protocol probes fail', async () => {
|
|
274
|
-
const plugin = createPlugin({
|
|
275
|
-
devices: [{ id: 'flaky-pro2-id', name: 'Unknown BLE Device', commType: 'ble' }],
|
|
276
|
-
responses: [
|
|
277
|
-
new Error('Protocol V1 probe timed out'),
|
|
278
|
-
new Error('Protocol V2 probe timed out'),
|
|
279
|
-
],
|
|
280
|
-
});
|
|
281
|
-
const lowlevel = configureTransport(plugin);
|
|
282
|
-
|
|
283
|
-
await expect(lowlevel.acquire({ uuid: 'flaky-pro2-id' })).rejects.toThrow(
|
|
284
|
-
'Unable to detect BLE protocol'
|
|
285
|
-
);
|
|
286
|
-
expect(lowlevel.getProtocolType('flaky-pro2-id')).toBeUndefined();
|
|
287
|
-
});
|
|
288
|
-
});
|