@onekeyfe/hd-transport 1.2.8-alpha.113 → 1.2.8-alpha.114

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.
@@ -1,6 +1,5 @@
1
1
  import {
2
2
  PROTOCOL_V2_DEFAULT_RESPONSE_TIMEOUT_MS,
3
- PROTOCOL_V2_FILE_WRITE_PIPELINE_WINDOW,
4
3
  PROTOCOL_V2_PACKET_SRC_COMMAND,
5
4
  } from '../../constants';
6
5
  import { ProtocolV2FrameAssembler, concatUint8Arrays } from './frame-assembler';
@@ -16,11 +15,7 @@ import * as check from '../../utils/highlevel-checks';
16
15
  import { LogBlockCommand } from '../../utils/logBlockCommand';
17
16
 
18
17
  import type { Root } from 'protobufjs/light';
19
- import type {
20
- MessageFromOneKey,
21
- ProtocolV2FileWritePipeline,
22
- TransportWriteMetrics,
23
- } from '../../types';
18
+ import type { MessageFromOneKey, TransportWriteMetrics } from '../../types';
24
19
 
25
20
  export * from './errors';
26
21
 
@@ -67,7 +62,6 @@ export type ProtocolV2CallOptions = {
67
62
  returnAfterWrite?: boolean;
68
63
  onResponseAfterWrite?: (response: MessageFromOneKey) => void;
69
64
  writeWithResponse?: boolean;
70
- fileWritePipeline?: ProtocolV2FileWritePipeline;
71
65
  };
72
66
 
73
67
  export { concatUint8Arrays, ProtocolV2FrameAssembler };
@@ -220,27 +214,6 @@ export async function withProtocolTimeout<T>(
220
214
  }
221
215
  }
222
216
 
223
- function readFileWriteChunk(request: Record<string, unknown>) {
224
- const file = request.file as
225
- | { path?: unknown; offset?: unknown; total_size?: unknown; data?: { byteLength?: unknown } }
226
- | undefined;
227
- const offset = Number(file?.offset);
228
- const totalSize = Number(file?.total_size);
229
- const length = Number(file?.data?.byteLength);
230
- if (
231
- typeof file?.path !== 'string' ||
232
- !Number.isSafeInteger(offset) ||
233
- offset < 0 ||
234
- !Number.isSafeInteger(totalSize) ||
235
- !Number.isSafeInteger(length) ||
236
- length <= 0 ||
237
- offset + length > totalSize
238
- ) {
239
- throw new Error('Protocol V2 file write pipeline chunk is invalid');
240
- }
241
- return { path: file.path, offset, end: offset + length, totalSize };
242
- }
243
-
244
217
  export class ProtocolV2Session {
245
218
  private readonly options: ProtocolV2SessionOptions;
246
219
 
@@ -325,9 +298,6 @@ export class ProtocolV2Session {
325
298
  data: Record<string, unknown>,
326
299
  callOptions: ProtocolV2CallOptions
327
300
  ): Promise<MessageFromOneKey> {
328
- if (callOptions.fileWritePipeline) {
329
- return this.executeFileWritePipeline(name, data, callOptions, callOptions.fileWritePipeline);
330
- }
331
301
  const {
332
302
  schemas,
333
303
  router,
@@ -406,8 +376,68 @@ export class ProtocolV2Session {
406
376
  const rxFrame = await readFrame(baseCallContext);
407
377
  if (abortController.signal.aborted) break;
408
378
 
409
- const response = this.parseResponseFrame(rxFrame, [protoSeq]);
410
- if (response) {
379
+ let header: ReturnType<typeof ProtocolV2.inspectFrameHeader>;
380
+ try {
381
+ header = ProtocolV2.inspectFrameHeader(rxFrame);
382
+ } catch (cause) {
383
+ throw new ProtocolV2LinkError(
384
+ 'frame',
385
+ `Protocol V2 frame validation failed: ${getErrorMessage(cause)}`,
386
+ cause
387
+ );
388
+ }
389
+ if (header.router !== router) {
390
+ throw new ProtocolV2LinkError(
391
+ 'router',
392
+ `Protocol V2 router mismatch: expected ${router}, got ${header.router}`
393
+ );
394
+ }
395
+ if (header.packetSrc !== packetSrc) {
396
+ throw new ProtocolV2LinkError(
397
+ 'packet-source',
398
+ `Protocol V2 packet source mismatch: expected ${packetSrc}, got ${header.packetSrc}`
399
+ );
400
+ }
401
+
402
+ let isAck: boolean;
403
+ try {
404
+ isAck = ProtocolV2.isAckFrame(rxFrame);
405
+ } catch (cause) {
406
+ throw new ProtocolV2LinkError(
407
+ 'frame',
408
+ `Protocol V2 ACK validation failed: ${getErrorMessage(cause)}`,
409
+ cause
410
+ );
411
+ }
412
+ if (isAck) {
413
+ if (header.seq !== protoSeq) {
414
+ throw new ProtocolV2LinkError(
415
+ 'ack-sequence',
416
+ `Protocol V2 ACK sequence mismatch: expected ${protoSeq}, got ${header.seq}`
417
+ );
418
+ }
419
+ } else {
420
+ let decoded: ReturnType<typeof ProtocolV2.decodeFrame>;
421
+ try {
422
+ decoded = ProtocolV2.decodeFrame(schemas, rxFrame);
423
+ } catch (cause) {
424
+ throw new ProtocolV2LinkError(
425
+ 'frame',
426
+ `Protocol V2 frame decode failed: ${getErrorMessage(cause)}`,
427
+ cause
428
+ );
429
+ }
430
+ // Firmware owns one TX sequence across all channels and packet sources,
431
+ // so frames routed elsewhere create valid gaps in this session.
432
+ if (this.lastResponseSequence === decoded.seq) {
433
+ throw new ProtocolV2LinkError(
434
+ 'response-sequence',
435
+ `Protocol V2 duplicate response sequence: ${decoded.seq}`
436
+ );
437
+ }
438
+ this.lastResponseSequence = decoded.seq;
439
+
440
+ const response = check.call(decoded);
411
441
  const { pendingResponseAfterWrite } = this;
412
442
  let belongsToWriteOnlyCall = false;
413
443
  if (pendingResponseAfterWrite) {
@@ -456,273 +486,6 @@ export class ProtocolV2Session {
456
486
  }
457
487
  );
458
488
  }
459
-
460
- /**
461
- * Validates one received frame for this session. Returns undefined for an ACK of one of
462
- * `ackSequences`, otherwise the decoded response.
463
- */
464
- private parseResponseFrame(
465
- rxFrame: Uint8Array,
466
- ackSequences: readonly number[]
467
- ): MessageFromOneKey | undefined {
468
- const { schemas, router, packetSrc = PROTOCOL_V2_PACKET_SRC_COMMAND } = this.options;
469
- let header: ReturnType<typeof ProtocolV2.inspectFrameHeader>;
470
- try {
471
- header = ProtocolV2.inspectFrameHeader(rxFrame);
472
- } catch (cause) {
473
- throw new ProtocolV2LinkError(
474
- 'frame',
475
- `Protocol V2 frame validation failed: ${getErrorMessage(cause)}`,
476
- cause
477
- );
478
- }
479
- if (header.router !== router) {
480
- throw new ProtocolV2LinkError(
481
- 'router',
482
- `Protocol V2 router mismatch: expected ${router}, got ${header.router}`
483
- );
484
- }
485
- if (header.packetSrc !== packetSrc) {
486
- throw new ProtocolV2LinkError(
487
- 'packet-source',
488
- `Protocol V2 packet source mismatch: expected ${packetSrc}, got ${header.packetSrc}`
489
- );
490
- }
491
-
492
- let isAck: boolean;
493
- try {
494
- isAck = ProtocolV2.isAckFrame(rxFrame);
495
- } catch (cause) {
496
- throw new ProtocolV2LinkError(
497
- 'frame',
498
- `Protocol V2 ACK validation failed: ${getErrorMessage(cause)}`,
499
- cause
500
- );
501
- }
502
- if (isAck) {
503
- if (!ackSequences.includes(header.seq)) {
504
- throw new ProtocolV2LinkError(
505
- 'ack-sequence',
506
- `Protocol V2 ACK sequence mismatch: expected ${ackSequences.join('|')}, got ${header.seq}`
507
- );
508
- }
509
- return undefined;
510
- }
511
-
512
- let decoded: ReturnType<typeof ProtocolV2.decodeFrame>;
513
- try {
514
- decoded = ProtocolV2.decodeFrame(schemas, rxFrame);
515
- } catch (cause) {
516
- throw new ProtocolV2LinkError(
517
- 'frame',
518
- `Protocol V2 frame decode failed: ${getErrorMessage(cause)}`,
519
- cause
520
- );
521
- }
522
- // Firmware owns one TX sequence across all channels and packet sources,
523
- // so frames routed elsewhere create valid gaps in this session.
524
- if (this.lastResponseSequence === decoded.seq) {
525
- throw new ProtocolV2LinkError(
526
- 'response-sequence',
527
- `Protocol V2 duplicate response sequence: ${decoded.seq}`
528
- );
529
- }
530
- this.lastResponseSequence = decoded.seq;
531
- return check.call(decoded);
532
- }
533
-
534
- /**
535
- * Writes one file as ordered FilesystemFileWrite chunks while keeping up to
536
- * PROTOCOL_V2_FILE_WRITE_PIPELINE_WINDOW chunks in flight. Firmware still processes and
537
- * answers every chunk in order, so responses are matched to chunks by position. Upstream
538
- * bootloaders append at their own cursor without checking the request offset, so the echoed
539
- * 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. The call
541
- * then returns the Failure like a serial call, or the last confirmation, which the caller
542
- * checks for completeness.
543
- */
544
- private async executeFileWritePipeline(
545
- name: string,
546
- data: Record<string, unknown>,
547
- callOptions: ProtocolV2CallOptions,
548
- pipeline: ProtocolV2FileWritePipeline
549
- ): Promise<MessageFromOneKey> {
550
- if (name !== 'FilesystemFileWrite') {
551
- throw new Error(`Protocol V2 file write pipeline does not support ${name}`);
552
- }
553
- if (this.pendingResponseAfterWrite) {
554
- throw new Error('Protocol V2 file write pipeline cannot start while a response is pending');
555
- }
556
- const {
557
- schemas,
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;
573
- const abortController = new AbortController();
574
- const context: ProtocolV2CallContext = {
575
- messageName: name,
576
- timeoutMs,
577
- highThroughput: true,
578
- ...(callOptions.writeWithResponse === undefined
579
- ? {}
580
- : { writeWithResponse: callOptions.writeWithResponse }),
581
- generation,
582
- signal: abortController.signal,
583
- };
584
- // One file can take far longer than one chunk, so the watchdog bounds each step instead.
585
- const withStepTimeout = <T>(promise: Promise<T>, stepTimeoutMs = timeoutMs) =>
586
- withProtocolTimeout(
587
- promise,
588
- stepTimeoutMs,
589
- () =>
590
- createTimeoutError
591
- ? createTimeoutError(name, timeoutMs)
592
- : new Error(`Protocol V2 response timeout after ${timeoutMs}ms for ${name}`),
593
- () => {
594
- abortController.abort();
595
- }
596
- );
597
- // ACKs and skipped frames must not extend the wait for an in-flight chunk's response.
598
- let progressDeadline = 0;
599
- const markProgress = () => {
600
- progressDeadline = Date.now() + timeoutMs;
601
- };
602
-
603
- const inFlight: Array<{ seq: number; path: string; offset: number; end: number }> = [];
604
- let firstRequest: Record<string, unknown> | undefined = data;
605
- let path: string | undefined;
606
- let totalSize: number | undefined;
607
- let nextOffset: number | undefined;
608
- let sourceDone = false;
609
- let failure: MessageFromOneKey | undefined;
610
- let lastConfirmed: MessageFromOneKey | undefined;
611
-
612
- const sendNextChunk = async () => {
613
- const request = firstRequest ?? (await pipeline.next());
614
- firstRequest = undefined;
615
- if (!request) {
616
- sourceDone = true;
617
- return;
618
- }
619
- const chunk = readFileWriteChunk(request);
620
- if (
621
- (path !== undefined && chunk.path !== path) ||
622
- (totalSize !== undefined && chunk.totalSize !== totalSize) ||
623
- (nextOffset !== undefined && chunk.offset !== nextOffset)
624
- ) {
625
- throw new Error(
626
- `Protocol V2 file write pipeline chunk out of order: expected offset ${String(
627
- nextOffset
628
- )}, got ${chunk.offset}`
629
- );
630
- }
631
- path = chunk.path;
632
- totalSize = chunk.totalSize;
633
- nextOffset = chunk.end;
634
-
635
- const seq = this.sequenceCursor.next();
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
- }
646
- const writeStartedAt = Date.now();
647
- await withStepTimeout(this.serializeWrite(() => writeFrame(frame, context)));
648
- inFlight.push({ seq, path: chunk.path, offset: chunk.offset, end: chunk.end });
649
- markProgress();
650
- try {
651
- callOptions.onWriteCompleted?.({
652
- elapsedMs: Math.max(Date.now() - writeStartedAt, 0),
653
- frameBytes: frame.byteLength,
654
- });
655
- } catch (error) {
656
- logger?.error?.(`${logPrefix} write metrics callback failed: ${String(error)}`);
657
- }
658
- };
659
-
660
- await withStepTimeout(Promise.resolve().then(() => prepareCall?.(context)));
661
- while (!sourceDone && inFlight.length < PROTOCOL_V2_FILE_WRITE_PIPELINE_WINDOW) {
662
- await sendNextChunk();
663
- }
664
-
665
- while (inFlight.length > 0) {
666
- const rxFrame = await withStepTimeout(
667
- readFrame(context),
668
- Math.max(progressDeadline - Date.now(), 1)
669
- );
670
- const response = this.parseResponseFrame(
671
- rxFrame,
672
- inFlight.map(item => item.seq)
673
- );
674
- if (!response) {
675
- // ACK of an in-flight chunk; its FilesystemFile response follows.
676
- } else if (response.type === 'FilesystemFile') {
677
- const [expected] = inFlight;
678
- const { message } = response;
679
- if (
680
- Number(message.processed_byte) !== expected.end ||
681
- message.path !== expected.path ||
682
- Number(message.offset) !== expected.offset ||
683
- Number(message.total_size) !== totalSize
684
- ) {
685
- throw new ProtocolV2LinkError(
686
- 'frame',
687
- `Protocol V2 file write response does not match chunk ${expected.offset}-${
688
- expected.end
689
- }: got offset ${String(message.offset)} processed_byte ${String(
690
- message.processed_byte
691
- )}`
692
- );
693
- }
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
- }
718
- }
719
-
720
- const terminal = failure ?? lastConfirmed;
721
- if (!terminal) {
722
- throw new ProtocolV2LinkError('frame', 'Protocol V2 file write pipeline ended unconfirmed');
723
- }
724
- return terminal;
725
- }
726
489
  }
727
490
 
728
491
  export async function probeProtocolV2({
@@ -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 = {
@@ -70,19 +76,6 @@ export type TransportWriteMetrics = {
70
76
  frameBytes: number;
71
77
  };
72
78
 
73
- /**
74
- * Supplies the remaining chunks of one file for an ordered FilesystemFileWrite pipeline.
75
- * The call's request data is the first chunk; `next` returns the following chunk in offset
76
- * order, or undefined to stop. The call then resolves with the last confirmation (or the
77
- * device Failure), so the caller must check `processed_byte` for completeness. Callbacks
78
- * should not throw: an error escapes with chunks still in flight and invalidates the link.
79
- */
80
- export type ProtocolV2FileWritePipeline = {
81
- next: () => Promise<Record<string, unknown> | undefined>;
82
- /** Called for each chunk after the device confirms it, in offset order. */
83
- onResponse: (response: MessageFromOneKey) => void;
84
- };
85
-
86
79
  export type TransportCallOptions = {
87
80
  timeoutMs?: number;
88
81
  expectedTypes?: string[];
@@ -96,12 +89,6 @@ export type TransportCallOptions = {
96
89
  onResponseAfterWrite?: (response: MessageFromOneKey) => void;
97
90
  /** Prefer acknowledged BLE characteristic writes for this call when supported. */
98
91
  writeWithResponse?: boolean;
99
- /**
100
- * Protocol V2 only: keep up to PROTOCOL_V2_FILE_WRITE_PIPELINE_WINDOW ordered
101
- * FilesystemFileWrite frames of one file in flight within this call. `timeoutMs`
102
- * bounds each frame write and each response instead of the whole file.
103
- */
104
- fileWritePipeline?: ProtocolV2FileWritePipeline;
105
92
  };
106
93
 
107
94
  type ITransportInitFn = (