@onekeyfe/hd-transport 1.2.8-alpha.117 → 1.2.8-alpha.118
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/__tests__/protocol-v2-file-write-pipeline.test.js +109 -48
- package/dist/index.d.ts +10 -3
- package/dist/index.js +105 -105
- package/dist/protocols/v2/session.d.ts +3 -0
- package/dist/protocols/v2/session.d.ts.map +1 -1
- package/dist/types/transport.d.ts +1 -0
- package/dist/types/transport.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/protocols/v2/session.ts +139 -146
- package/src/types/transport.ts +6 -0
|
@@ -110,7 +110,7 @@ const createDevice = ({ reply } = {}) => {
|
|
|
110
110
|
const request = requests[index];
|
|
111
111
|
const { offset, total_size: totalSize, data } = request.message.file;
|
|
112
112
|
// Decoded protobuf bytes are hex strings.
|
|
113
|
-
const length =
|
|
113
|
+
const length = data.length / 2;
|
|
114
114
|
const confirmation = encode('FilesystemFile', {
|
|
115
115
|
path: PATH,
|
|
116
116
|
offset,
|
|
@@ -154,10 +154,9 @@ const createSession = (device, options = {}) =>
|
|
|
154
154
|
...options,
|
|
155
155
|
});
|
|
156
156
|
|
|
157
|
-
const runPipeline = (session, chunks, callOptions = {}) => {
|
|
157
|
+
const runPipeline = (session, chunks, callOptions = {}, onResponse = jest.fn()) => {
|
|
158
158
|
const [first, ...rest] = chunks;
|
|
159
159
|
const next = jest.fn(() => Promise.resolve(rest.shift()));
|
|
160
|
-
const onResponse = jest.fn();
|
|
161
160
|
return {
|
|
162
161
|
next,
|
|
163
162
|
onResponse,
|
|
@@ -168,6 +167,28 @@ const runPipeline = (session, chunks, callOptions = {}) => {
|
|
|
168
167
|
};
|
|
169
168
|
};
|
|
170
169
|
|
|
170
|
+
const runManagedPipeline = (device, chunks) => {
|
|
171
|
+
const adapter = {
|
|
172
|
+
router: 1,
|
|
173
|
+
generation: 1,
|
|
174
|
+
prepareCall: jest.fn(),
|
|
175
|
+
writeFrame: device.writeFrame,
|
|
176
|
+
readFrame: device.readFrame,
|
|
177
|
+
reset: jest.fn(),
|
|
178
|
+
};
|
|
179
|
+
const manager = new ProtocolV2LinkManager({
|
|
180
|
+
getSchemas: () => schemas,
|
|
181
|
+
classifyError: () => 'link-fatal',
|
|
182
|
+
});
|
|
183
|
+
const [first, ...rest] = chunks;
|
|
184
|
+
return {
|
|
185
|
+
adapter,
|
|
186
|
+
result: manager.call('device-a', () => adapter, 'FilesystemFileWrite', first, {
|
|
187
|
+
fileWritePipeline: { next: () => Promise.resolve(rest.shift()), onResponse: jest.fn() },
|
|
188
|
+
}),
|
|
189
|
+
};
|
|
190
|
+
};
|
|
191
|
+
|
|
171
192
|
describe('Protocol V2 file write pipeline', () => {
|
|
172
193
|
test('keeps two ordered chunks in flight and confirms each one', async () => {
|
|
173
194
|
const device = createDevice();
|
|
@@ -232,25 +253,9 @@ describe('Protocol V2 file write pipeline', () => {
|
|
|
232
253
|
const device = createDevice({
|
|
233
254
|
reply: ({ encode }) => [encode('Failure', { code: 9, message: 'Write offset mismatch' })],
|
|
234
255
|
});
|
|
235
|
-
const adapter =
|
|
236
|
-
router: 1,
|
|
237
|
-
generation: 1,
|
|
238
|
-
prepareCall: jest.fn(),
|
|
239
|
-
writeFrame: device.writeFrame,
|
|
240
|
-
readFrame: device.readFrame,
|
|
241
|
-
reset: jest.fn(),
|
|
242
|
-
};
|
|
243
|
-
const manager = new ProtocolV2LinkManager({
|
|
244
|
-
getSchemas: () => schemas,
|
|
245
|
-
classifyError: () => 'link-fatal',
|
|
246
|
-
});
|
|
247
|
-
const [first, ...rest] = chunksOf(6, 2);
|
|
256
|
+
const { adapter, result } = runManagedPipeline(device, chunksOf(6, 2));
|
|
248
257
|
|
|
249
|
-
await expect(
|
|
250
|
-
manager.call('device-a', () => adapter, 'FilesystemFileWrite', first, {
|
|
251
|
-
fileWritePipeline: { next: () => Promise.resolve(rest.shift()), onResponse: jest.fn() },
|
|
252
|
-
})
|
|
253
|
-
).resolves.toMatchObject({ type: 'Failure' });
|
|
258
|
+
await expect(result).resolves.toMatchObject({ type: 'Failure' });
|
|
254
259
|
expect(adapter.reset).not.toHaveBeenCalled();
|
|
255
260
|
expect(adapter.prepareCall).toHaveBeenCalledTimes(1);
|
|
256
261
|
});
|
|
@@ -262,25 +267,11 @@ describe('Protocol V2 file write pipeline', () => {
|
|
|
262
267
|
? [encode('FilesystemFile', { path: PATH, offset: 4, total_size: 6, processed_byte: 6 })]
|
|
263
268
|
: [confirmation],
|
|
264
269
|
});
|
|
265
|
-
const adapter =
|
|
266
|
-
router: 1,
|
|
267
|
-
generation: 1,
|
|
268
|
-
prepareCall: jest.fn(),
|
|
269
|
-
writeFrame: device.writeFrame,
|
|
270
|
-
readFrame: device.readFrame,
|
|
271
|
-
reset: jest.fn(() => Promise.resolve()),
|
|
272
|
-
};
|
|
273
|
-
const manager = new ProtocolV2LinkManager({
|
|
274
|
-
getSchemas: () => schemas,
|
|
275
|
-
classifyError: () => 'link-fatal',
|
|
276
|
-
});
|
|
277
|
-
const [first, ...rest] = chunksOf(6, 2);
|
|
270
|
+
const { adapter, result } = runManagedPipeline(device, chunksOf(6, 2));
|
|
278
271
|
|
|
279
|
-
await expect(
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
})
|
|
283
|
-
).rejects.toEqual(expect.objectContaining({ name: 'ProtocolV2LinkError', code: 'frame' }));
|
|
272
|
+
await expect(result).rejects.toEqual(
|
|
273
|
+
expect.objectContaining({ name: 'ProtocolV2LinkError', code: 'frame' })
|
|
274
|
+
);
|
|
284
275
|
expect(adapter.reset).toHaveBeenCalled();
|
|
285
276
|
});
|
|
286
277
|
|
|
@@ -302,9 +293,79 @@ describe('Protocol V2 file write pipeline', () => {
|
|
|
302
293
|
const { result } = runPipeline(session, [chunk(0, 2, 8), chunk(4, 2, 8)]);
|
|
303
294
|
|
|
304
295
|
await expect(result).rejects.toThrow('chunk out of order: expected offset 2, got 4');
|
|
296
|
+
await expect(result).rejects.toEqual(
|
|
297
|
+
expect.objectContaining({ name: 'ProtocolV2LinkError', code: 'frame' })
|
|
298
|
+
);
|
|
305
299
|
expect(device.requests).toHaveLength(1);
|
|
306
300
|
});
|
|
307
301
|
|
|
302
|
+
test.each([
|
|
303
|
+
['path', { path: 'vol0:/application_p2.bin' }],
|
|
304
|
+
['total size', { total_size: 9 }],
|
|
305
|
+
])('rejects a source chunk with another %s before writing it', async (_field, file) => {
|
|
306
|
+
const device = createDevice();
|
|
307
|
+
const session = createSession(device);
|
|
308
|
+
const second = chunk(2, 2, 8);
|
|
309
|
+
const { result } = runPipeline(session, [
|
|
310
|
+
chunk(0, 2, 8),
|
|
311
|
+
{ ...second, file: { ...second.file, ...file } },
|
|
312
|
+
]);
|
|
313
|
+
|
|
314
|
+
await expect(result).rejects.toThrow('chunk belongs to a different file');
|
|
315
|
+
await expect(result).rejects.toEqual(
|
|
316
|
+
expect.objectContaining({ name: 'ProtocolV2LinkError', code: 'frame' })
|
|
317
|
+
);
|
|
318
|
+
expect(device.requests).toHaveLength(1);
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
test.each([
|
|
322
|
+
['path', { path: 'vol0:/application_p2.bin' }],
|
|
323
|
+
['total size', { total_size: 8 }],
|
|
324
|
+
])('invalidates the link when a confirmation echoes another %s', async (_field, echo) => {
|
|
325
|
+
const device = createDevice({
|
|
326
|
+
reply: ({ index, confirmation, encode }) =>
|
|
327
|
+
index === 0
|
|
328
|
+
? [
|
|
329
|
+
encode('FilesystemFile', {
|
|
330
|
+
path: PATH,
|
|
331
|
+
offset: 0,
|
|
332
|
+
total_size: 6,
|
|
333
|
+
processed_byte: 2,
|
|
334
|
+
...echo,
|
|
335
|
+
}),
|
|
336
|
+
]
|
|
337
|
+
: [confirmation],
|
|
338
|
+
});
|
|
339
|
+
const { adapter, result } = runManagedPipeline(device, chunksOf(6, 2));
|
|
340
|
+
|
|
341
|
+
await expect(result).rejects.toEqual(
|
|
342
|
+
expect.objectContaining({ name: 'ProtocolV2LinkError', code: 'frame' })
|
|
343
|
+
);
|
|
344
|
+
expect(adapter.reset).toHaveBeenCalled();
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
test.each(['readFrame', 'writeFrame'])(
|
|
348
|
+
'passes a %s transport error through unchanged with chunks in flight',
|
|
349
|
+
async method => {
|
|
350
|
+
const device = createDevice();
|
|
351
|
+
const linkLost = new Error('BLE device disconnected');
|
|
352
|
+
const original = device[method].getMockImplementation();
|
|
353
|
+
let calls = 0;
|
|
354
|
+
device[method].mockImplementation((...args) => {
|
|
355
|
+
calls += 1;
|
|
356
|
+
// The second write, or the first read, happens with chunk 0 still in flight.
|
|
357
|
+
return calls === (method === 'writeFrame' ? 2 : 1)
|
|
358
|
+
? Promise.reject(linkLost)
|
|
359
|
+
: original(...args);
|
|
360
|
+
});
|
|
361
|
+
const session = createSession(device);
|
|
362
|
+
const { result } = runPipeline(session, chunksOf(6, 2));
|
|
363
|
+
|
|
364
|
+
await expect(result).rejects.toBe(linkLost);
|
|
365
|
+
expect(device.requests.length).toBeGreaterThan(0);
|
|
366
|
+
}
|
|
367
|
+
);
|
|
368
|
+
|
|
308
369
|
test('drains the in-flight chunk and returns the last confirmation when the source stops', async () => {
|
|
309
370
|
const device = createDevice();
|
|
310
371
|
const session = createSession(device);
|
|
@@ -317,7 +378,7 @@ describe('Protocol V2 file write pipeline', () => {
|
|
|
317
378
|
expect(onResponse).toHaveBeenCalledTimes(2);
|
|
318
379
|
});
|
|
319
380
|
|
|
320
|
-
test('detects a chunk
|
|
381
|
+
test('detects a lost chunk when the bootloader appends at its own cursor', async () => {
|
|
321
382
|
let cursor = 0;
|
|
322
383
|
const device = createDevice({
|
|
323
384
|
reply: ({ index, request, encode }) => {
|
|
@@ -366,6 +427,9 @@ describe('Protocol V2 file write pipeline', () => {
|
|
|
366
427
|
const { result } = runPipeline(session, [chunk(0, 128, 128)]);
|
|
367
428
|
|
|
368
429
|
await expect(result).rejects.toThrow('Protocol V2 frame too large for transport');
|
|
430
|
+
await expect(result).rejects.not.toEqual(
|
|
431
|
+
expect.objectContaining({ name: 'ProtocolV2LinkError' })
|
|
432
|
+
);
|
|
369
433
|
expect(device.requests).toHaveLength(0);
|
|
370
434
|
});
|
|
371
435
|
|
|
@@ -401,17 +465,14 @@ describe('Protocol V2 file write pipeline', () => {
|
|
|
401
465
|
test('stops when the caller rejects a confirmed chunk', async () => {
|
|
402
466
|
const device = createDevice();
|
|
403
467
|
const session = createSession(device);
|
|
404
|
-
const
|
|
405
|
-
|
|
406
|
-
fileWritePipeline: {
|
|
407
|
-
next: () => Promise.resolve(rest.shift()),
|
|
408
|
-
onResponse: () => {
|
|
409
|
-
throw new Error('cancelled');
|
|
410
|
-
},
|
|
411
|
-
},
|
|
468
|
+
const { result } = runPipeline(session, chunksOf(10, 2), {}, () => {
|
|
469
|
+
throw new Error('cancelled');
|
|
412
470
|
});
|
|
413
471
|
|
|
414
472
|
await expect(result).rejects.toThrow('cancelled');
|
|
473
|
+
await expect(result).rejects.toEqual(
|
|
474
|
+
expect.objectContaining({ name: 'ProtocolV2LinkError', code: 'frame' })
|
|
475
|
+
);
|
|
415
476
|
expect(device.requests).toHaveLength(2);
|
|
416
477
|
});
|
|
417
478
|
});
|
package/dist/index.d.ts
CHANGED
|
@@ -323,6 +323,12 @@ type OneKeyDeviceInfoWithSession = OneKeyUsbDeviceInfo & {
|
|
|
323
323
|
type OneKeyMobileDeviceInfo = {
|
|
324
324
|
id: string;
|
|
325
325
|
name: string | null;
|
|
326
|
+
/**
|
|
327
|
+
* Color letter of the serial number ('A'-'Z') from the Pro2 / Neo BLE advertisement,
|
|
328
|
+
* possibly seen in an earlier scan. Set by the React Native and Electron BLE
|
|
329
|
+
* transports; absent when unknown.
|
|
330
|
+
*/
|
|
331
|
+
bleColorCode?: string;
|
|
326
332
|
};
|
|
327
333
|
type OneKeyDeviceInfoBase = {
|
|
328
334
|
commType: OneKeyDeviceCommType;
|
|
@@ -6869,7 +6875,10 @@ declare class ProtocolV2Session {
|
|
|
6869
6875
|
call(name: string, data: Record<string, unknown>, callOptions?: ProtocolV2CallOptions): Promise<MessageFromOneKey>;
|
|
6870
6876
|
sendFlowControl(name: string, data: Record<string, unknown>): Promise<MessageFromOneKey>;
|
|
6871
6877
|
private serializeWrite;
|
|
6878
|
+
private reportWriteCompleted;
|
|
6879
|
+
private createCallTimeoutError;
|
|
6872
6880
|
private executeCall;
|
|
6881
|
+
private encodeRequestFrame;
|
|
6873
6882
|
/**
|
|
6874
6883
|
* Validates one received frame for this session. Returns undefined for an ACK of one of
|
|
6875
6884
|
* `ackSequences`, otherwise the decoded response.
|
|
@@ -6881,9 +6890,7 @@ declare class ProtocolV2Session {
|
|
|
6881
6890
|
* answers every chunk in order, so responses are matched to chunks by position. Upstream
|
|
6882
6891
|
* bootloaders append at their own cursor without checking the request offset, so the echoed
|
|
6883
6892
|
* offset is what exposes a lost chunk. A device Failure, or `next` returning undefined, stops
|
|
6884
|
-
* sending; the remaining in-flight responses are drained so the link stays usable.
|
|
6885
|
-
* then returns the Failure like a serial call, or the last confirmation, which the caller
|
|
6886
|
-
* checks for completeness.
|
|
6893
|
+
* sending; the remaining in-flight responses are drained so the link stays usable.
|
|
6887
6894
|
*/
|
|
6888
6895
|
private executeFileWritePipeline;
|
|
6889
6896
|
}
|
package/dist/index.js
CHANGED
|
@@ -1108,7 +1108,8 @@ function withProtocolTimeout(promise, timeoutMs, createTimeoutError, onTimeout,
|
|
|
1108
1108
|
}
|
|
1109
1109
|
});
|
|
1110
1110
|
}
|
|
1111
|
-
|
|
1111
|
+
const resolveProtocolV2CallTimeoutMs = (timeoutMs) => timeoutMs && timeoutMs > 0 ? timeoutMs : PROTOCOL_V2_DEFAULT_RESPONSE_TIMEOUT_MS;
|
|
1112
|
+
function parseFileWriteChunk(request) {
|
|
1112
1113
|
var _a;
|
|
1113
1114
|
const file = request.file;
|
|
1114
1115
|
const offset = Number(file === null || file === void 0 ? void 0 : file.offset);
|
|
@@ -1167,46 +1168,47 @@ class ProtocolV2Session {
|
|
|
1167
1168
|
this.pendingWrite = result.catch(() => undefined);
|
|
1168
1169
|
return result;
|
|
1169
1170
|
}
|
|
1171
|
+
reportWriteCompleted(callOptions, writeStartedAt, frameBytes) {
|
|
1172
|
+
var _a, _b;
|
|
1173
|
+
try {
|
|
1174
|
+
(_a = callOptions.onWriteCompleted) === null || _a === void 0 ? void 0 : _a.call(callOptions, {
|
|
1175
|
+
elapsedMs: Math.max(Date.now() - writeStartedAt, 0),
|
|
1176
|
+
frameBytes,
|
|
1177
|
+
});
|
|
1178
|
+
}
|
|
1179
|
+
catch (error) {
|
|
1180
|
+
const { logger, logPrefix = 'ProtocolV2' } = this.options;
|
|
1181
|
+
(_b = logger === null || logger === void 0 ? void 0 : logger.error) === null || _b === void 0 ? void 0 : _b.call(logger, `${logPrefix} write metrics callback failed: ${String(error)}`);
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
createCallTimeoutError(name, timeoutMs) {
|
|
1185
|
+
const { createTimeoutError } = this.options;
|
|
1186
|
+
return createTimeoutError
|
|
1187
|
+
? createTimeoutError(name, timeoutMs)
|
|
1188
|
+
: new Error(`Protocol V2 response timeout after ${timeoutMs}ms for ${name}`);
|
|
1189
|
+
}
|
|
1170
1190
|
executeCall(name, data, callOptions) {
|
|
1171
1191
|
if (callOptions.fileWritePipeline) {
|
|
1172
1192
|
return this.executeFileWritePipeline(name, data, callOptions, callOptions.fileWritePipeline);
|
|
1173
1193
|
}
|
|
1174
|
-
const {
|
|
1175
|
-
const timeoutMs = callOptions.timeoutMs
|
|
1176
|
-
? callOptions.timeoutMs
|
|
1177
|
-
: PROTOCOL_V2_DEFAULT_RESPONSE_TIMEOUT_MS;
|
|
1194
|
+
const { prepareCall, writeFrame, readFrame, logger, logPrefix = 'ProtocolV2', generation = 0, } = this.options;
|
|
1195
|
+
const timeoutMs = resolveProtocolV2CallTimeoutMs(callOptions.timeoutMs);
|
|
1178
1196
|
const abortController = new AbortController();
|
|
1179
1197
|
const shouldReduceDebug = shouldReduceProtocolV2Debug(name);
|
|
1180
1198
|
const baseCallContext = Object.assign(Object.assign({ messageName: name, timeoutMs, highThroughput: isProtocolV2HighThroughputCall(name) }, (callOptions.writeWithResponse === undefined
|
|
1181
1199
|
? {}
|
|
1182
1200
|
: { writeWithResponse: callOptions.writeWithResponse })), { generation, signal: abortController.signal });
|
|
1183
1201
|
const runCall = () => __awaiter(this, void 0, void 0, function* () {
|
|
1184
|
-
var _a, _b, _c, _d, _e, _f, _g
|
|
1202
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
1185
1203
|
if (!this.pendingResponseAfterWrite) {
|
|
1186
1204
|
yield (prepareCall === null || prepareCall === void 0 ? void 0 : prepareCall(baseCallContext));
|
|
1187
1205
|
}
|
|
1188
|
-
const protoSeq = this.
|
|
1189
|
-
const frame = ProtocolV2.encodeFrame(schemas, name, data, {
|
|
1190
|
-
packetSrc,
|
|
1191
|
-
router,
|
|
1192
|
-
seq: protoSeq,
|
|
1193
|
-
});
|
|
1194
|
-
if (maxFrameBytes !== undefined && frame.length > maxFrameBytes) {
|
|
1195
|
-
throw new Error(`Protocol V2 frame too large for transport: ${frame.length} > ${maxFrameBytes}`);
|
|
1196
|
-
}
|
|
1206
|
+
const { seq: protoSeq, frame } = this.encodeRequestFrame(name, data);
|
|
1197
1207
|
const writeStartedAt = Date.now();
|
|
1198
1208
|
yield this.serializeWrite(() => writeFrame(frame, baseCallContext));
|
|
1199
|
-
|
|
1200
|
-
(_a = callOptions.onWriteCompleted) === null || _a === void 0 ? void 0 : _a.call(callOptions, {
|
|
1201
|
-
elapsedMs: Math.max(Date.now() - writeStartedAt, 0),
|
|
1202
|
-
frameBytes: frame.byteLength,
|
|
1203
|
-
});
|
|
1204
|
-
}
|
|
1205
|
-
catch (error) {
|
|
1206
|
-
(_b = logger === null || logger === void 0 ? void 0 : logger.error) === null || _b === void 0 ? void 0 : _b.call(logger, `${logPrefix} write metrics callback failed: ${String(error)}`);
|
|
1207
|
-
}
|
|
1209
|
+
this.reportWriteCompleted(callOptions, writeStartedAt, frame.byteLength);
|
|
1208
1210
|
if (callOptions.returnAfterWrite) {
|
|
1209
|
-
if ((
|
|
1211
|
+
if ((_a = callOptions.expectedTypes) === null || _a === void 0 ? void 0 : _a.length) {
|
|
1210
1212
|
this.pendingResponseAfterWrite = {
|
|
1211
1213
|
expectedTypes: new Set(callOptions.expectedTypes),
|
|
1212
1214
|
onResponse: callOptions.onResponseAfterWrite,
|
|
@@ -1228,34 +1230,41 @@ class ProtocolV2Session {
|
|
|
1228
1230
|
this.pendingResponseAfterWrite = undefined;
|
|
1229
1231
|
if (belongsToWriteOnlyCall) {
|
|
1230
1232
|
try {
|
|
1231
|
-
(
|
|
1233
|
+
(_b = pendingResponseAfterWrite.onResponse) === null || _b === void 0 ? void 0 : _b.call(pendingResponseAfterWrite, response);
|
|
1232
1234
|
}
|
|
1233
1235
|
catch (error) {
|
|
1234
|
-
(
|
|
1236
|
+
(_c = logger === null || logger === void 0 ? void 0 : logger.error) === null || _c === void 0 ? void 0 : _c.call(logger, `${logPrefix} delayed response callback failed: ${String(error)}`);
|
|
1235
1237
|
}
|
|
1236
1238
|
}
|
|
1237
1239
|
}
|
|
1238
1240
|
}
|
|
1239
1241
|
if (belongsToWriteOnlyCall) ;
|
|
1240
|
-
else if ((
|
|
1241
|
-
(
|
|
1242
|
+
else if ((_d = callOptions.intermediateTypes) === null || _d === void 0 ? void 0 : _d.includes(response.type)) {
|
|
1243
|
+
(_e = callOptions.onIntermediateResponse) === null || _e === void 0 ? void 0 : _e.call(callOptions, response);
|
|
1242
1244
|
}
|
|
1243
1245
|
else if (isExpectedTerminalResponse(response, callOptions.expectedTypes)) {
|
|
1244
1246
|
return response;
|
|
1245
1247
|
}
|
|
1246
1248
|
else if (!shouldReduceDebug) {
|
|
1247
|
-
(
|
|
1249
|
+
(_f = logger === null || logger === void 0 ? void 0 : logger.debug) === null || _f === void 0 ? void 0 : _f.call(logger, `[${logPrefix}] skip unexpected response for ${name}: expected=${(_g = callOptions.expectedTypes) === null || _g === void 0 ? void 0 : _g.join('|')} got=${response.type}`);
|
|
1248
1250
|
}
|
|
1249
1251
|
}
|
|
1250
1252
|
}
|
|
1251
1253
|
throw new Error(`Protocol V2 read loop cancelled after timeout for ${name}`);
|
|
1252
1254
|
});
|
|
1253
|
-
return withProtocolTimeout(runCall(), timeoutMs, () =>
|
|
1254
|
-
? createTimeoutError(name, timeoutMs)
|
|
1255
|
-
: new Error(`Protocol V2 response timeout after ${timeoutMs}ms for ${name}`), () => {
|
|
1255
|
+
return withProtocolTimeout(runCall(), timeoutMs, () => this.createCallTimeoutError(name, timeoutMs), () => {
|
|
1256
1256
|
abortController.abort();
|
|
1257
1257
|
});
|
|
1258
1258
|
}
|
|
1259
|
+
encodeRequestFrame(name, data) {
|
|
1260
|
+
const { schemas, router, packetSrc = PROTOCOL_V2_PACKET_SRC_COMMAND, maxFrameBytes, } = this.options;
|
|
1261
|
+
const seq = this.sequenceCursor.next();
|
|
1262
|
+
const frame = ProtocolV2.encodeFrame(schemas, name, data, { packetSrc, router, seq });
|
|
1263
|
+
if (maxFrameBytes !== undefined && frame.length > maxFrameBytes) {
|
|
1264
|
+
throw new Error(`Protocol V2 frame too large for transport: ${frame.length} > ${maxFrameBytes}`);
|
|
1265
|
+
}
|
|
1266
|
+
return { seq, frame };
|
|
1267
|
+
}
|
|
1259
1268
|
parseResponseFrame(rxFrame, ackSequences) {
|
|
1260
1269
|
const { schemas, router, packetSrc = PROTOCOL_V2_PACKET_SRC_COMMAND } = this.options;
|
|
1261
1270
|
let header;
|
|
@@ -1298,7 +1307,6 @@ class ProtocolV2Session {
|
|
|
1298
1307
|
return call(decoded);
|
|
1299
1308
|
}
|
|
1300
1309
|
executeFileWritePipeline(name, data, callOptions, pipeline) {
|
|
1301
|
-
var _a;
|
|
1302
1310
|
return __awaiter(this, void 0, void 0, function* () {
|
|
1303
1311
|
if (name !== 'FilesystemFileWrite') {
|
|
1304
1312
|
throw new Error(`Protocol V2 file write pipeline does not support ${name}`);
|
|
@@ -1306,110 +1314,102 @@ class ProtocolV2Session {
|
|
|
1306
1314
|
if (this.pendingResponseAfterWrite) {
|
|
1307
1315
|
throw new Error('Protocol V2 file write pipeline cannot start while a response is pending');
|
|
1308
1316
|
}
|
|
1309
|
-
const {
|
|
1310
|
-
const timeoutMs = callOptions.timeoutMs
|
|
1311
|
-
? callOptions.timeoutMs
|
|
1312
|
-
: PROTOCOL_V2_DEFAULT_RESPONSE_TIMEOUT_MS;
|
|
1317
|
+
const { prepareCall, writeFrame, readFrame, generation = 0 } = this.options;
|
|
1318
|
+
const timeoutMs = resolveProtocolV2CallTimeoutMs(callOptions.timeoutMs);
|
|
1313
1319
|
const abortController = new AbortController();
|
|
1314
1320
|
const context = Object.assign(Object.assign({ messageName: name, timeoutMs, highThroughput: true }, (callOptions.writeWithResponse === undefined
|
|
1315
1321
|
? {}
|
|
1316
1322
|
: { writeWithResponse: callOptions.writeWithResponse })), { generation, signal: abortController.signal });
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
: new Error(`Protocol V2 response timeout after ${timeoutMs}ms for ${name}`), () => {
|
|
1323
|
+
let transportFailure;
|
|
1324
|
+
const withStepTimeout = (promise, stepTimeoutMs = timeoutMs) => withProtocolTimeout(promise, stepTimeoutMs, () => this.createCallTimeoutError(name, timeoutMs), () => {
|
|
1320
1325
|
abortController.abort();
|
|
1326
|
+
}).catch((error) => {
|
|
1327
|
+
transportFailure = error;
|
|
1328
|
+
throw error;
|
|
1321
1329
|
});
|
|
1322
1330
|
let progressDeadline = 0;
|
|
1323
1331
|
const markProgress = () => {
|
|
1324
1332
|
progressDeadline = Date.now() + timeoutMs;
|
|
1325
1333
|
};
|
|
1326
1334
|
const inFlight = [];
|
|
1327
|
-
let firstRequest = data;
|
|
1328
1335
|
let path;
|
|
1329
1336
|
let totalSize;
|
|
1330
1337
|
let nextOffset;
|
|
1331
1338
|
let sourceDone = false;
|
|
1332
1339
|
let failure;
|
|
1333
1340
|
let lastConfirmed;
|
|
1334
|
-
const
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
if (!request) {
|
|
1339
|
-
sourceDone = true;
|
|
1340
|
-
return;
|
|
1341
|
+
const sendChunk = (request) => __awaiter(this, void 0, void 0, function* () {
|
|
1342
|
+
const chunk = parseFileWriteChunk(request);
|
|
1343
|
+
if (path !== undefined && (chunk.path !== path || chunk.totalSize !== totalSize)) {
|
|
1344
|
+
throw new Error('Protocol V2 file write pipeline chunk belongs to a different file');
|
|
1341
1345
|
}
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
(totalSize !== undefined && chunk.totalSize !== totalSize) ||
|
|
1345
|
-
(nextOffset !== undefined && chunk.offset !== nextOffset)) {
|
|
1346
|
-
throw new Error(`Protocol V2 file write pipeline chunk out of order: expected offset ${String(nextOffset)}, got ${chunk.offset}`);
|
|
1346
|
+
if (nextOffset !== undefined && chunk.offset !== nextOffset) {
|
|
1347
|
+
throw new Error(`Protocol V2 file write pipeline chunk out of order: expected offset ${nextOffset}, got ${chunk.offset}`);
|
|
1347
1348
|
}
|
|
1348
1349
|
path = chunk.path;
|
|
1349
1350
|
totalSize = chunk.totalSize;
|
|
1350
1351
|
nextOffset = chunk.end;
|
|
1351
|
-
const seq = this.
|
|
1352
|
-
const frame = ProtocolV2.encodeFrame(schemas, name, request, {
|
|
1353
|
-
packetSrc,
|
|
1354
|
-
router,
|
|
1355
|
-
seq,
|
|
1356
|
-
});
|
|
1357
|
-
if (maxFrameBytes !== undefined && frame.length > maxFrameBytes) {
|
|
1358
|
-
throw new Error(`Protocol V2 frame too large for transport: ${frame.length} > ${maxFrameBytes}`);
|
|
1359
|
-
}
|
|
1352
|
+
const { seq, frame } = this.encodeRequestFrame(name, request);
|
|
1360
1353
|
const writeStartedAt = Date.now();
|
|
1361
1354
|
yield withStepTimeout(this.serializeWrite(() => writeFrame(frame, context)));
|
|
1362
|
-
inFlight.push({ seq,
|
|
1355
|
+
inFlight.push({ seq, offset: chunk.offset, end: chunk.end });
|
|
1363
1356
|
markProgress();
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
(_c = logger === null || logger === void 0 ? void 0 : logger.error) === null || _c === void 0 ? void 0 : _c.call(logger, `${logPrefix} write metrics callback failed: ${String(error)}`);
|
|
1357
|
+
this.reportWriteCompleted(callOptions, writeStartedAt, frame.byteLength);
|
|
1358
|
+
});
|
|
1359
|
+
const sendNextChunk = () => __awaiter(this, void 0, void 0, function* () {
|
|
1360
|
+
const request = yield pipeline.next();
|
|
1361
|
+
if (!request) {
|
|
1362
|
+
sourceDone = true;
|
|
1363
|
+
return;
|
|
1372
1364
|
}
|
|
1365
|
+
yield sendChunk(request);
|
|
1373
1366
|
});
|
|
1374
1367
|
yield withStepTimeout(Promise.resolve().then(() => prepareCall === null || prepareCall === void 0 ? void 0 : prepareCall(context)));
|
|
1375
|
-
|
|
1376
|
-
yield
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
const { message } = response;
|
|
1385
|
-
if (Number(message.processed_byte) !== expected.end ||
|
|
1386
|
-
message.path !== expected.path ||
|
|
1387
|
-
Number(message.offset) !== expected.offset ||
|
|
1388
|
-
Number(message.total_size) !== totalSize) {
|
|
1389
|
-
throw new ProtocolV2LinkError('frame', `Protocol V2 file write response does not match chunk ${expected.offset}-${expected.end}: got offset ${String(message.offset)} processed_byte ${String(message.processed_byte)}`);
|
|
1368
|
+
try {
|
|
1369
|
+
yield sendChunk(data);
|
|
1370
|
+
while (!sourceDone && inFlight.length < PROTOCOL_V2_FILE_WRITE_PIPELINE_WINDOW) {
|
|
1371
|
+
yield sendNextChunk();
|
|
1372
|
+
}
|
|
1373
|
+
while (inFlight.length > 0) {
|
|
1374
|
+
const rxFrame = yield withStepTimeout(readFrame(context), Math.max(progressDeadline - Date.now(), 1));
|
|
1375
|
+
const response = this.parseResponseFrame(rxFrame, inFlight.map(item => item.seq));
|
|
1376
|
+
if (!response) {
|
|
1390
1377
|
}
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1378
|
+
else if (response.type === 'FilesystemFile') {
|
|
1379
|
+
const [expected] = inFlight;
|
|
1380
|
+
const { message } = response;
|
|
1381
|
+
if (Number(message.processed_byte) !== expected.end ||
|
|
1382
|
+
message.path !== path ||
|
|
1383
|
+
Number(message.offset) !== expected.offset ||
|
|
1384
|
+
Number(message.total_size) !== totalSize) {
|
|
1385
|
+
throw new ProtocolV2LinkError('frame', `Protocol V2 file write response does not match chunk ${expected.offset}-${expected.end}: got offset ${String(message.offset)} processed_byte ${String(message.processed_byte)}`);
|
|
1386
|
+
}
|
|
1387
|
+
inFlight.shift();
|
|
1388
|
+
markProgress();
|
|
1389
|
+
if (!failure) {
|
|
1390
|
+
lastConfirmed = response;
|
|
1391
|
+
pipeline.onResponse(response);
|
|
1392
|
+
if (!sourceDone) {
|
|
1393
|
+
yield sendNextChunk();
|
|
1394
|
+
}
|
|
1398
1395
|
}
|
|
1399
1396
|
}
|
|
1397
|
+
else if (response.type === 'Failure') {
|
|
1398
|
+
inFlight.shift();
|
|
1399
|
+
markProgress();
|
|
1400
|
+
failure = failure !== null && failure !== void 0 ? failure : response;
|
|
1401
|
+
sourceDone = true;
|
|
1402
|
+
}
|
|
1403
|
+
else if (COMMON_TERMINAL_RESPONSE_TYPES.has(response.type)) {
|
|
1404
|
+
throw new ProtocolV2LinkError('frame', `Protocol V2 file write pipeline received unexpected ${response.type}`);
|
|
1405
|
+
}
|
|
1400
1406
|
}
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
sourceDone = true;
|
|
1406
|
-
}
|
|
1407
|
-
else if (COMMON_TERMINAL_RESPONSE_TYPES.has(response.type)) {
|
|
1408
|
-
throw new ProtocolV2LinkError('frame', `Protocol V2 file write pipeline received unexpected ${response.type}`);
|
|
1409
|
-
}
|
|
1410
|
-
else {
|
|
1411
|
-
(_a = logger === null || logger === void 0 ? void 0 : logger.debug) === null || _a === void 0 ? void 0 : _a.call(logger, `[${logPrefix}] skip unexpected response for ${name}: expected=FilesystemFile got=${response.type}`);
|
|
1407
|
+
}
|
|
1408
|
+
catch (error) {
|
|
1409
|
+
if (inFlight.length === 0 || error === transportFailure || isProtocolV2LinkError(error)) {
|
|
1410
|
+
throw error;
|
|
1412
1411
|
}
|
|
1412
|
+
throw new ProtocolV2LinkError('frame', `Protocol V2 file write pipeline aborted with chunks in flight: ${getErrorMessage(error)}`, error);
|
|
1413
1413
|
}
|
|
1414
1414
|
const terminal = failure !== null && failure !== void 0 ? failure : lastConfirmed;
|
|
1415
1415
|
if (!terminal) {
|
|
@@ -67,7 +67,10 @@ export declare class ProtocolV2Session {
|
|
|
67
67
|
call(name: string, data: Record<string, unknown>, callOptions?: ProtocolV2CallOptions): Promise<MessageFromOneKey>;
|
|
68
68
|
sendFlowControl(name: string, data: Record<string, unknown>): Promise<MessageFromOneKey>;
|
|
69
69
|
private serializeWrite;
|
|
70
|
+
private reportWriteCompleted;
|
|
71
|
+
private createCallTimeoutError;
|
|
70
72
|
private executeCall;
|
|
73
|
+
private encodeRequestFrame;
|
|
71
74
|
private parseResponseFrame;
|
|
72
75
|
private executeFileWritePipeline;
|
|
73
76
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../../src/protocols/v2/session.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,wBAAwB,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAChF,OAAO,EAAE,wBAAwB,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../../src/protocols/v2/session.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../../src/types/transport.ts"],"names":[],"mappings":";;;AAAA,OAAO,KAAK,YAAY,MAAM,QAAQ,CAAC;AAEvC,eAAO,MAAM,eAAe;;CAElB,CAAC;AAEX,MAAM,MAAM,8BAA8B,GAAG;IAC3C,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG,IAAI,GAAG,IAAI,CAAC;AAEvC,MAAM,MAAM,oBAAoB,GAC5B,KAAK,GACL,QAAQ,GACR,KAAK,GACL,QAAQ,GACR,cAAc,GACd,QAAQ,GACR,UAAU,CAAC;AAEf,MAAM,MAAM,mBAAmB,GAAG;IAChC,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,2BAA2B,GAAG,mBAAmB,GAAG;IAC9D,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,KAAK,EAAE,OAAO,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG;IACnC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../../src/types/transport.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@onekeyfe/hd-transport",
|
|
3
|
-
"version": "1.2.8-alpha.
|
|
3
|
+
"version": "1.2.8-alpha.118",
|
|
4
4
|
"description": "Transport layer abstractions and utilities for OneKey hardware SDK.",
|
|
5
5
|
"author": "OneKey",
|
|
6
6
|
"homepage": "https://github.com/OneKeyHQ/hardware-js-sdk#readme",
|
|
@@ -28,5 +28,5 @@
|
|
|
28
28
|
"long": "^4.0.0",
|
|
29
29
|
"protobufjs": "^6.11.2"
|
|
30
30
|
},
|
|
31
|
-
"gitHead": "
|
|
31
|
+
"gitHead": "7b5fe42a7c29ff0aa4c8340c1d5da93026a1b590"
|
|
32
32
|
}
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
createProtocolV2LinkDisabledError,
|
|
11
11
|
isProtocolV2LinkDisabledError,
|
|
12
12
|
isProtocolV2LinkDisabledFailure,
|
|
13
|
+
isProtocolV2LinkError,
|
|
13
14
|
} from './errors';
|
|
14
15
|
import { ProtocolV2 } from '..';
|
|
15
16
|
import * as check from '../../utils/highlevel-checks';
|
|
@@ -220,7 +221,10 @@ export async function withProtocolTimeout<T>(
|
|
|
220
221
|
}
|
|
221
222
|
}
|
|
222
223
|
|
|
223
|
-
|
|
224
|
+
const resolveProtocolV2CallTimeoutMs = (timeoutMs?: number) =>
|
|
225
|
+
timeoutMs && timeoutMs > 0 ? timeoutMs : PROTOCOL_V2_DEFAULT_RESPONSE_TIMEOUT_MS;
|
|
226
|
+
|
|
227
|
+
function parseFileWriteChunk(request: Record<string, unknown>) {
|
|
224
228
|
const file = request.file as
|
|
225
229
|
| { path?: unknown; offset?: unknown; total_size?: unknown; data?: { byteLength?: unknown } }
|
|
226
230
|
| undefined;
|
|
@@ -320,6 +324,29 @@ export class ProtocolV2Session {
|
|
|
320
324
|
return result;
|
|
321
325
|
}
|
|
322
326
|
|
|
327
|
+
private reportWriteCompleted(
|
|
328
|
+
callOptions: ProtocolV2CallOptions,
|
|
329
|
+
writeStartedAt: number,
|
|
330
|
+
frameBytes: number
|
|
331
|
+
) {
|
|
332
|
+
try {
|
|
333
|
+
callOptions.onWriteCompleted?.({
|
|
334
|
+
elapsedMs: Math.max(Date.now() - writeStartedAt, 0),
|
|
335
|
+
frameBytes,
|
|
336
|
+
});
|
|
337
|
+
} catch (error) {
|
|
338
|
+
const { logger, logPrefix = 'ProtocolV2' } = this.options;
|
|
339
|
+
logger?.error?.(`${logPrefix} write metrics callback failed: ${String(error)}`);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
private createCallTimeoutError(name: string, timeoutMs: number): Error {
|
|
344
|
+
const { createTimeoutError } = this.options;
|
|
345
|
+
return createTimeoutError
|
|
346
|
+
? createTimeoutError(name, timeoutMs)
|
|
347
|
+
: new Error(`Protocol V2 response timeout after ${timeoutMs}ms for ${name}`);
|
|
348
|
+
}
|
|
349
|
+
|
|
323
350
|
private executeCall(
|
|
324
351
|
name: string,
|
|
325
352
|
data: Record<string, unknown>,
|
|
@@ -329,22 +356,14 @@ export class ProtocolV2Session {
|
|
|
329
356
|
return this.executeFileWritePipeline(name, data, callOptions, callOptions.fileWritePipeline);
|
|
330
357
|
}
|
|
331
358
|
const {
|
|
332
|
-
schemas,
|
|
333
|
-
router,
|
|
334
|
-
packetSrc = PROTOCOL_V2_PACKET_SRC_COMMAND,
|
|
335
|
-
maxFrameBytes,
|
|
336
359
|
prepareCall,
|
|
337
360
|
writeFrame,
|
|
338
361
|
readFrame,
|
|
339
362
|
logger,
|
|
340
363
|
logPrefix = 'ProtocolV2',
|
|
341
|
-
createTimeoutError,
|
|
342
364
|
generation = 0,
|
|
343
365
|
} = this.options;
|
|
344
|
-
const timeoutMs =
|
|
345
|
-
callOptions.timeoutMs && callOptions.timeoutMs > 0
|
|
346
|
-
? callOptions.timeoutMs
|
|
347
|
-
: PROTOCOL_V2_DEFAULT_RESPONSE_TIMEOUT_MS;
|
|
366
|
+
const timeoutMs = resolveProtocolV2CallTimeoutMs(callOptions.timeoutMs);
|
|
348
367
|
const abortController = new AbortController();
|
|
349
368
|
|
|
350
369
|
const shouldReduceDebug = shouldReduceProtocolV2Debug(name);
|
|
@@ -365,29 +384,11 @@ export class ProtocolV2Session {
|
|
|
365
384
|
if (!this.pendingResponseAfterWrite) {
|
|
366
385
|
await prepareCall?.(baseCallContext);
|
|
367
386
|
}
|
|
368
|
-
const protoSeq = this.
|
|
369
|
-
const frame = ProtocolV2.encodeFrame(schemas, name, data, {
|
|
370
|
-
packetSrc,
|
|
371
|
-
router,
|
|
372
|
-
seq: protoSeq,
|
|
373
|
-
});
|
|
374
|
-
|
|
375
|
-
if (maxFrameBytes !== undefined && frame.length > maxFrameBytes) {
|
|
376
|
-
throw new Error(
|
|
377
|
-
`Protocol V2 frame too large for transport: ${frame.length} > ${maxFrameBytes}`
|
|
378
|
-
);
|
|
379
|
-
}
|
|
387
|
+
const { seq: protoSeq, frame } = this.encodeRequestFrame(name, data);
|
|
380
388
|
|
|
381
389
|
const writeStartedAt = Date.now();
|
|
382
390
|
await this.serializeWrite(() => writeFrame(frame, baseCallContext));
|
|
383
|
-
|
|
384
|
-
callOptions.onWriteCompleted?.({
|
|
385
|
-
elapsedMs: Math.max(Date.now() - writeStartedAt, 0),
|
|
386
|
-
frameBytes: frame.byteLength,
|
|
387
|
-
});
|
|
388
|
-
} catch (error) {
|
|
389
|
-
logger?.error?.(`${logPrefix} write metrics callback failed: ${String(error)}`);
|
|
390
|
-
}
|
|
391
|
+
this.reportWriteCompleted(callOptions, writeStartedAt, frame.byteLength);
|
|
391
392
|
|
|
392
393
|
if (callOptions.returnAfterWrite) {
|
|
393
394
|
if (callOptions.expectedTypes?.length) {
|
|
@@ -447,16 +448,30 @@ export class ProtocolV2Session {
|
|
|
447
448
|
return withProtocolTimeout(
|
|
448
449
|
runCall(),
|
|
449
450
|
timeoutMs,
|
|
450
|
-
() =>
|
|
451
|
-
createTimeoutError
|
|
452
|
-
? createTimeoutError(name, timeoutMs)
|
|
453
|
-
: new Error(`Protocol V2 response timeout after ${timeoutMs}ms for ${name}`),
|
|
451
|
+
() => this.createCallTimeoutError(name, timeoutMs),
|
|
454
452
|
() => {
|
|
455
453
|
abortController.abort();
|
|
456
454
|
}
|
|
457
455
|
);
|
|
458
456
|
}
|
|
459
457
|
|
|
458
|
+
private encodeRequestFrame(name: string, data: Record<string, unknown>) {
|
|
459
|
+
const {
|
|
460
|
+
schemas,
|
|
461
|
+
router,
|
|
462
|
+
packetSrc = PROTOCOL_V2_PACKET_SRC_COMMAND,
|
|
463
|
+
maxFrameBytes,
|
|
464
|
+
} = this.options;
|
|
465
|
+
const seq = this.sequenceCursor.next();
|
|
466
|
+
const frame = ProtocolV2.encodeFrame(schemas, name, data, { packetSrc, router, seq });
|
|
467
|
+
if (maxFrameBytes !== undefined && frame.length > maxFrameBytes) {
|
|
468
|
+
throw new Error(
|
|
469
|
+
`Protocol V2 frame too large for transport: ${frame.length} > ${maxFrameBytes}`
|
|
470
|
+
);
|
|
471
|
+
}
|
|
472
|
+
return { seq, frame };
|
|
473
|
+
}
|
|
474
|
+
|
|
460
475
|
/**
|
|
461
476
|
* Validates one received frame for this session. Returns undefined for an ACK of one of
|
|
462
477
|
* `ackSequences`, otherwise the decoded response.
|
|
@@ -537,9 +552,7 @@ export class ProtocolV2Session {
|
|
|
537
552
|
* answers every chunk in order, so responses are matched to chunks by position. Upstream
|
|
538
553
|
* bootloaders append at their own cursor without checking the request offset, so the echoed
|
|
539
554
|
* offset is what exposes a lost chunk. A device Failure, or `next` returning undefined, stops
|
|
540
|
-
* sending; the remaining in-flight responses are drained so the link stays usable.
|
|
541
|
-
* then returns the Failure like a serial call, or the last confirmation, which the caller
|
|
542
|
-
* checks for completeness.
|
|
555
|
+
* sending; the remaining in-flight responses are drained so the link stays usable.
|
|
543
556
|
*/
|
|
544
557
|
private async executeFileWritePipeline(
|
|
545
558
|
name: string,
|
|
@@ -553,23 +566,8 @@ export class ProtocolV2Session {
|
|
|
553
566
|
if (this.pendingResponseAfterWrite) {
|
|
554
567
|
throw new Error('Protocol V2 file write pipeline cannot start while a response is pending');
|
|
555
568
|
}
|
|
556
|
-
const {
|
|
557
|
-
|
|
558
|
-
router,
|
|
559
|
-
packetSrc = PROTOCOL_V2_PACKET_SRC_COMMAND,
|
|
560
|
-
maxFrameBytes,
|
|
561
|
-
prepareCall,
|
|
562
|
-
writeFrame,
|
|
563
|
-
readFrame,
|
|
564
|
-
logger,
|
|
565
|
-
logPrefix = 'ProtocolV2',
|
|
566
|
-
createTimeoutError,
|
|
567
|
-
generation = 0,
|
|
568
|
-
} = this.options;
|
|
569
|
-
const timeoutMs =
|
|
570
|
-
callOptions.timeoutMs && callOptions.timeoutMs > 0
|
|
571
|
-
? callOptions.timeoutMs
|
|
572
|
-
: PROTOCOL_V2_DEFAULT_RESPONSE_TIMEOUT_MS;
|
|
569
|
+
const { prepareCall, writeFrame, readFrame, generation = 0 } = this.options;
|
|
570
|
+
const timeoutMs = resolveProtocolV2CallTimeoutMs(callOptions.timeoutMs);
|
|
573
571
|
const abortController = new AbortController();
|
|
574
572
|
const context: ProtocolV2CallContext = {
|
|
575
573
|
messageName: name,
|
|
@@ -581,27 +579,28 @@ export class ProtocolV2Session {
|
|
|
581
579
|
generation,
|
|
582
580
|
signal: abortController.signal,
|
|
583
581
|
};
|
|
582
|
+
// Transport errors and timeouts propagate unchanged: each transport classifies its own.
|
|
583
|
+
let transportFailure: unknown;
|
|
584
584
|
// One file can take far longer than one chunk, so the watchdog bounds each step instead.
|
|
585
585
|
const withStepTimeout = <T>(promise: Promise<T>, stepTimeoutMs = timeoutMs) =>
|
|
586
586
|
withProtocolTimeout(
|
|
587
587
|
promise,
|
|
588
588
|
stepTimeoutMs,
|
|
589
|
-
() =>
|
|
590
|
-
createTimeoutError
|
|
591
|
-
? createTimeoutError(name, timeoutMs)
|
|
592
|
-
: new Error(`Protocol V2 response timeout after ${timeoutMs}ms for ${name}`),
|
|
589
|
+
() => this.createCallTimeoutError(name, timeoutMs),
|
|
593
590
|
() => {
|
|
594
591
|
abortController.abort();
|
|
595
592
|
}
|
|
596
|
-
)
|
|
593
|
+
).catch((error: unknown) => {
|
|
594
|
+
transportFailure = error;
|
|
595
|
+
throw error;
|
|
596
|
+
});
|
|
597
597
|
// ACKs and skipped frames must not extend the wait for an in-flight chunk's response.
|
|
598
598
|
let progressDeadline = 0;
|
|
599
599
|
const markProgress = () => {
|
|
600
600
|
progressDeadline = Date.now() + timeoutMs;
|
|
601
601
|
};
|
|
602
602
|
|
|
603
|
-
const inFlight: Array<{ seq: number;
|
|
604
|
-
let firstRequest: Record<string, unknown> | undefined = data;
|
|
603
|
+
const inFlight: Array<{ seq: number; offset: number; end: number }> = [];
|
|
605
604
|
let path: string | undefined;
|
|
606
605
|
let totalSize: number | undefined;
|
|
607
606
|
let nextOffset: number | undefined;
|
|
@@ -609,112 +608,106 @@ export class ProtocolV2Session {
|
|
|
609
608
|
let failure: MessageFromOneKey | undefined;
|
|
610
609
|
let lastConfirmed: MessageFromOneKey | undefined;
|
|
611
610
|
|
|
612
|
-
const
|
|
613
|
-
const
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
sourceDone = true;
|
|
617
|
-
return;
|
|
611
|
+
const sendChunk = async (request: Record<string, unknown>) => {
|
|
612
|
+
const chunk = parseFileWriteChunk(request);
|
|
613
|
+
if (path !== undefined && (chunk.path !== path || chunk.totalSize !== totalSize)) {
|
|
614
|
+
throw new Error('Protocol V2 file write pipeline chunk belongs to a different file');
|
|
618
615
|
}
|
|
619
|
-
|
|
620
|
-
if (
|
|
621
|
-
(path !== undefined && chunk.path !== path) ||
|
|
622
|
-
(totalSize !== undefined && chunk.totalSize !== totalSize) ||
|
|
623
|
-
(nextOffset !== undefined && chunk.offset !== nextOffset)
|
|
624
|
-
) {
|
|
616
|
+
if (nextOffset !== undefined && chunk.offset !== nextOffset) {
|
|
625
617
|
throw new Error(
|
|
626
|
-
`Protocol V2 file write pipeline chunk out of order: expected offset ${
|
|
627
|
-
nextOffset
|
|
628
|
-
)}, got ${chunk.offset}`
|
|
618
|
+
`Protocol V2 file write pipeline chunk out of order: expected offset ${nextOffset}, got ${chunk.offset}`
|
|
629
619
|
);
|
|
630
620
|
}
|
|
631
621
|
path = chunk.path;
|
|
632
622
|
totalSize = chunk.totalSize;
|
|
633
623
|
nextOffset = chunk.end;
|
|
634
624
|
|
|
635
|
-
const seq = this.
|
|
636
|
-
const frame = ProtocolV2.encodeFrame(schemas, name, request, {
|
|
637
|
-
packetSrc,
|
|
638
|
-
router,
|
|
639
|
-
seq,
|
|
640
|
-
});
|
|
641
|
-
if (maxFrameBytes !== undefined && frame.length > maxFrameBytes) {
|
|
642
|
-
throw new Error(
|
|
643
|
-
`Protocol V2 frame too large for transport: ${frame.length} > ${maxFrameBytes}`
|
|
644
|
-
);
|
|
645
|
-
}
|
|
625
|
+
const { seq, frame } = this.encodeRequestFrame(name, request);
|
|
646
626
|
const writeStartedAt = Date.now();
|
|
647
627
|
await withStepTimeout(this.serializeWrite(() => writeFrame(frame, context)));
|
|
648
|
-
inFlight.push({ seq,
|
|
628
|
+
inFlight.push({ seq, offset: chunk.offset, end: chunk.end });
|
|
649
629
|
markProgress();
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
630
|
+
this.reportWriteCompleted(callOptions, writeStartedAt, frame.byteLength);
|
|
631
|
+
};
|
|
632
|
+
const sendNextChunk = async () => {
|
|
633
|
+
const request = await pipeline.next();
|
|
634
|
+
if (!request) {
|
|
635
|
+
sourceDone = true;
|
|
636
|
+
return;
|
|
657
637
|
}
|
|
638
|
+
await sendChunk(request);
|
|
658
639
|
};
|
|
659
640
|
|
|
660
641
|
await withStepTimeout(Promise.resolve().then(() => prepareCall?.(context)));
|
|
661
|
-
|
|
662
|
-
await
|
|
663
|
-
|
|
642
|
+
try {
|
|
643
|
+
await sendChunk(data);
|
|
644
|
+
while (!sourceDone && inFlight.length < PROTOCOL_V2_FILE_WRITE_PIPELINE_WINDOW) {
|
|
645
|
+
await sendNextChunk();
|
|
646
|
+
}
|
|
664
647
|
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
648
|
+
while (inFlight.length > 0) {
|
|
649
|
+
// Floor at 1 ms: withProtocolTimeout treats 0 as "no timeout", so an expired deadline
|
|
650
|
+
// must still time out.
|
|
651
|
+
const rxFrame = await withStepTimeout(
|
|
652
|
+
readFrame(context),
|
|
653
|
+
Math.max(progressDeadline - Date.now(), 1)
|
|
654
|
+
);
|
|
655
|
+
const response = this.parseResponseFrame(
|
|
656
|
+
rxFrame,
|
|
657
|
+
inFlight.map(item => item.seq)
|
|
658
|
+
);
|
|
659
|
+
if (!response) {
|
|
660
|
+
// ACK of an in-flight chunk; its FilesystemFile response follows.
|
|
661
|
+
} else if (response.type === 'FilesystemFile') {
|
|
662
|
+
const [expected] = inFlight;
|
|
663
|
+
const { message } = response;
|
|
664
|
+
if (
|
|
665
|
+
Number(message.processed_byte) !== expected.end ||
|
|
666
|
+
message.path !== path ||
|
|
667
|
+
Number(message.offset) !== expected.offset ||
|
|
668
|
+
Number(message.total_size) !== totalSize
|
|
669
|
+
) {
|
|
670
|
+
throw new ProtocolV2LinkError(
|
|
671
|
+
'frame',
|
|
672
|
+
`Protocol V2 file write response does not match chunk ${expected.offset}-${
|
|
673
|
+
expected.end
|
|
674
|
+
}: got offset ${String(message.offset)} processed_byte ${String(
|
|
675
|
+
message.processed_byte
|
|
676
|
+
)}`
|
|
677
|
+
);
|
|
678
|
+
}
|
|
679
|
+
inFlight.shift();
|
|
680
|
+
markProgress();
|
|
681
|
+
if (!failure) {
|
|
682
|
+
lastConfirmed = response;
|
|
683
|
+
pipeline.onResponse(response);
|
|
684
|
+
if (!sourceDone) {
|
|
685
|
+
await sendNextChunk();
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
} else if (response.type === 'Failure') {
|
|
689
|
+
inFlight.shift();
|
|
690
|
+
markProgress();
|
|
691
|
+
failure = failure ?? response;
|
|
692
|
+
sourceDone = true;
|
|
693
|
+
} else if (COMMON_TERMINAL_RESPONSE_TYPES.has(response.type)) {
|
|
685
694
|
throw new ProtocolV2LinkError(
|
|
686
695
|
'frame',
|
|
687
|
-
`Protocol V2 file write
|
|
688
|
-
expected.end
|
|
689
|
-
}: got offset ${String(message.offset)} processed_byte ${String(
|
|
690
|
-
message.processed_byte
|
|
691
|
-
)}`
|
|
696
|
+
`Protocol V2 file write pipeline received unexpected ${response.type}`
|
|
692
697
|
);
|
|
693
698
|
}
|
|
694
|
-
inFlight.shift();
|
|
695
|
-
markProgress();
|
|
696
|
-
if (!failure) {
|
|
697
|
-
lastConfirmed = response;
|
|
698
|
-
pipeline.onResponse(response);
|
|
699
|
-
if (!sourceDone) {
|
|
700
|
-
await sendNextChunk();
|
|
701
|
-
}
|
|
702
|
-
}
|
|
703
|
-
} else if (response.type === 'Failure') {
|
|
704
|
-
inFlight.shift();
|
|
705
|
-
markProgress();
|
|
706
|
-
failure = failure ?? response;
|
|
707
|
-
sourceDone = true;
|
|
708
|
-
} else if (COMMON_TERMINAL_RESPONSE_TYPES.has(response.type)) {
|
|
709
|
-
throw new ProtocolV2LinkError(
|
|
710
|
-
'frame',
|
|
711
|
-
`Protocol V2 file write pipeline received unexpected ${response.type}`
|
|
712
|
-
);
|
|
713
|
-
} else {
|
|
714
|
-
logger?.debug?.(
|
|
715
|
-
`[${logPrefix}] skip unexpected response for ${name}: expected=FilesystemFile got=${response.type}`
|
|
716
|
-
);
|
|
717
699
|
}
|
|
700
|
+
} catch (error) {
|
|
701
|
+
// Unread responses of in-flight chunks would answer the next call on this link, so a
|
|
702
|
+
// session or caller error raised with chunks in flight must invalidate it.
|
|
703
|
+
if (inFlight.length === 0 || error === transportFailure || isProtocolV2LinkError(error)) {
|
|
704
|
+
throw error;
|
|
705
|
+
}
|
|
706
|
+
throw new ProtocolV2LinkError(
|
|
707
|
+
'frame',
|
|
708
|
+
`Protocol V2 file write pipeline aborted with chunks in flight: ${getErrorMessage(error)}`,
|
|
709
|
+
error
|
|
710
|
+
);
|
|
718
711
|
}
|
|
719
712
|
|
|
720
713
|
const terminal = failure ?? lastConfirmed;
|
package/src/types/transport.ts
CHANGED
|
@@ -34,6 +34,12 @@ export type OneKeyDeviceInfoWithSession = OneKeyUsbDeviceInfo & {
|
|
|
34
34
|
export type OneKeyMobileDeviceInfo = {
|
|
35
35
|
id: string;
|
|
36
36
|
name: string | null;
|
|
37
|
+
/**
|
|
38
|
+
* Color letter of the serial number ('A'-'Z') from the Pro2 / Neo BLE advertisement,
|
|
39
|
+
* possibly seen in an earlier scan. Set by the React Native and Electron BLE
|
|
40
|
+
* transports; absent when unknown.
|
|
41
|
+
*/
|
|
42
|
+
bleColorCode?: string;
|
|
37
43
|
};
|
|
38
44
|
|
|
39
45
|
export type OneKeyDeviceInfoBase = {
|