@onekeyfe/hd-transport 1.2.0-alpha.99 → 1.2.0

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.
@@ -4,7 +4,12 @@ import {
4
4
  } from '../../constants';
5
5
  import { ProtocolV2FrameAssembler, concatUint8Arrays } from './frame-assembler';
6
6
  import { ProtocolV2SequenceCursor } from './sequence-cursor';
7
- import { ProtocolV2LinkError } from './errors';
7
+ import {
8
+ ProtocolV2LinkError,
9
+ createProtocolV2LinkDisabledError,
10
+ isProtocolV2LinkDisabledError,
11
+ isProtocolV2LinkDisabledFailure,
12
+ } from './errors';
8
13
  import { ProtocolV2 } from '..';
9
14
  import * as check from '../../utils/highlevel-checks';
10
15
  import { LogBlockCommand } from '../../utils/logBlockCommand';
@@ -54,6 +59,8 @@ export type ProtocolV2CallOptions = {
54
59
  intermediateTypes?: string[];
55
60
  onIntermediateResponse?: (response: MessageFromOneKey) => void;
56
61
  onWriteCompleted?: (metrics: TransportWriteMetrics) => void;
62
+ returnAfterWrite?: boolean;
63
+ onResponseAfterWrite?: (response: MessageFromOneKey) => void;
57
64
  writeWithResponse?: boolean;
58
65
  };
59
66
 
@@ -75,6 +82,33 @@ export function hexToBytes(hex: string): Uint8Array {
75
82
  return bytes;
76
83
  }
77
84
 
85
+ export function detectProtocolV2LinkDisabledError({
86
+ schemas,
87
+ assembler,
88
+ bytes,
89
+ }: {
90
+ schemas: ProtocolV2Schemas;
91
+ assembler: ProtocolV2FrameAssembler;
92
+ bytes: Uint8Array;
93
+ }) {
94
+ try {
95
+ for (const frame of assembler.drain(bytes)) {
96
+ const response = check.call(ProtocolV2.decodeFrame(schemas, frame));
97
+ if (response.type === 'Failure') {
98
+ const failureCode = response.message?.code;
99
+ const firmwareMessage = response.message?.message;
100
+ if (isProtocolV2LinkDisabledFailure(failureCode, firmwareMessage)) {
101
+ return createProtocolV2LinkDisabledError(failureCode, firmwareMessage);
102
+ }
103
+ }
104
+ }
105
+ } catch {
106
+ // Cross-protocol detection may receive a normal Protocol V1 notification.
107
+ assembler.reset();
108
+ }
109
+ return undefined;
110
+ }
111
+
78
112
  export function bytesToHex(bytes: Uint8Array): string {
79
113
  return Array.from(bytes)
80
114
  .map(b => b.toString(16).padStart(2, '0'))
@@ -189,6 +223,15 @@ export class ProtocolV2Session {
189
223
  // in-flight calls on the same session would steal each other's responses.
190
224
  private pendingCall: Promise<unknown> = Promise.resolve();
191
225
 
226
+ // Flow-control messages may interrupt a call that is waiting for a device UI
227
+ // response, but their frames must never interleave with the active request write.
228
+ private pendingWrite: Promise<unknown> = Promise.resolve();
229
+
230
+ private pendingResponseAfterWrite?: {
231
+ expectedTypes: Set<string>;
232
+ onResponse?: (response: MessageFromOneKey) => void;
233
+ };
234
+
192
235
  private lastResponseSequence?: number;
193
236
 
194
237
  constructor(options: ProtocolV2SessionOptions) {
@@ -209,6 +252,47 @@ export class ProtocolV2Session {
209
252
  return result;
210
253
  }
211
254
 
255
+ sendFlowControl(name: string, data: Record<string, unknown>): Promise<MessageFromOneKey> {
256
+ const {
257
+ schemas,
258
+ router,
259
+ packetSrc = PROTOCOL_V2_PACKET_SRC_COMMAND,
260
+ maxFrameBytes,
261
+ writeFrame,
262
+ generation = 0,
263
+ } = this.options;
264
+ const abortController = new AbortController();
265
+ const context: ProtocolV2CallContext = {
266
+ messageName: name,
267
+ highThroughput: false,
268
+ generation,
269
+ signal: abortController.signal,
270
+ };
271
+ const protoSeq = this.sequenceCursor.next();
272
+ const frame = ProtocolV2.encodeFrame(schemas, name, data, {
273
+ packetSrc,
274
+ router,
275
+ seq: protoSeq,
276
+ });
277
+
278
+ if (maxFrameBytes !== undefined && frame.length > maxFrameBytes) {
279
+ return Promise.reject(
280
+ new Error(`Protocol V2 frame too large for transport: ${frame.length} > ${maxFrameBytes}`)
281
+ );
282
+ }
283
+
284
+ return this.serializeWrite(() => writeFrame(frame, context)).then(() => ({
285
+ type: 'WriteCompleted',
286
+ message: {},
287
+ }));
288
+ }
289
+
290
+ private serializeWrite<T>(write: () => Promise<T>): Promise<T> {
291
+ const result = this.pendingWrite.then(write, write);
292
+ this.pendingWrite = result.catch(() => undefined);
293
+ return result;
294
+ }
295
+
212
296
  private executeCall(
213
297
  name: string,
214
298
  data: Record<string, unknown>,
@@ -246,7 +330,11 @@ export class ProtocolV2Session {
246
330
  };
247
331
 
248
332
  const runCall = async (): Promise<MessageFromOneKey> => {
249
- await prepareCall?.(baseCallContext);
333
+ // A write-only call may still have a terminal response in flight. Preserve
334
+ // the continuous receive queue until the next call consumes that response.
335
+ if (!this.pendingResponseAfterWrite) {
336
+ await prepareCall?.(baseCallContext);
337
+ }
250
338
  const protoSeq = this.sequenceCursor.next();
251
339
  const frame = ProtocolV2.encodeFrame(schemas, name, data, {
252
340
  packetSrc,
@@ -261,7 +349,7 @@ export class ProtocolV2Session {
261
349
  }
262
350
 
263
351
  const writeStartedAt = Date.now();
264
- await writeFrame(frame, baseCallContext);
352
+ await this.serializeWrite(() => writeFrame(frame, baseCallContext));
265
353
  try {
266
354
  callOptions.onWriteCompleted?.({
267
355
  elapsedMs: Math.max(Date.now() - writeStartedAt, 0),
@@ -271,6 +359,16 @@ export class ProtocolV2Session {
271
359
  logger?.error?.(`${logPrefix} write metrics callback failed: ${String(error)}`);
272
360
  }
273
361
 
362
+ if (callOptions.returnAfterWrite) {
363
+ if (callOptions.expectedTypes?.length) {
364
+ this.pendingResponseAfterWrite = {
365
+ expectedTypes: new Set(callOptions.expectedTypes),
366
+ onResponse: callOptions.onResponseAfterWrite,
367
+ };
368
+ }
369
+ return { type: 'WriteCompleted', message: {} };
370
+ }
371
+
274
372
  // Some Protocol V2 operations emit progress notifications before the
275
373
  // terminal response. Consume those frames here so callers still see a
276
374
  // request/terminal-response shaped API.
@@ -340,7 +438,26 @@ export class ProtocolV2Session {
340
438
  this.lastResponseSequence = decoded.seq;
341
439
 
342
440
  const response = check.call(decoded);
343
- if (callOptions.intermediateTypes?.includes(response.type)) {
441
+ const { pendingResponseAfterWrite } = this;
442
+ let belongsToWriteOnlyCall = false;
443
+ if (pendingResponseAfterWrite) {
444
+ belongsToWriteOnlyCall = pendingResponseAfterWrite.expectedTypes.has(response.type);
445
+ if (belongsToWriteOnlyCall || COMMON_TERMINAL_RESPONSE_TYPES.has(response.type)) {
446
+ this.pendingResponseAfterWrite = undefined;
447
+ if (belongsToWriteOnlyCall) {
448
+ try {
449
+ pendingResponseAfterWrite.onResponse?.(response);
450
+ } catch (error) {
451
+ logger?.error?.(
452
+ `${logPrefix} delayed response callback failed: ${String(error)}`
453
+ );
454
+ }
455
+ }
456
+ }
457
+ }
458
+ if (belongsToWriteOnlyCall) {
459
+ // The delayed response belongs to the preceding write-only call.
460
+ } else if (callOptions.intermediateTypes?.includes(response.type)) {
344
461
  callOptions.onIntermediateResponse?.(response);
345
462
  } else if (isExpectedTerminalResponse(response, callOptions.expectedTypes)) {
346
463
  return response;
@@ -378,6 +495,7 @@ export async function probeProtocolV2({
378
495
  logPrefix = 'ProtocolV2',
379
496
  onBeforeProbe,
380
497
  onProbeFailed,
498
+ shouldRethrow,
381
499
  }: {
382
500
  call: (
383
501
  name: string,
@@ -389,6 +507,7 @@ export async function probeProtocolV2({
389
507
  logPrefix?: string;
390
508
  onBeforeProbe?: () => Promise<void> | void;
391
509
  onProbeFailed?: (error: unknown) => Promise<void> | void;
510
+ shouldRethrow?: (error: unknown) => boolean;
392
511
  }) {
393
512
  let probeError: unknown;
394
513
  try {
@@ -404,8 +523,18 @@ export async function probeProtocolV2({
404
523
  if (response.type === 'Success') {
405
524
  return true;
406
525
  }
526
+ if (response.type === 'Failure') {
527
+ const failureCode = response.message?.code;
528
+ const firmwareMessage = response.message?.message;
529
+ if (isProtocolV2LinkDisabledFailure(failureCode, firmwareMessage)) {
530
+ throw createProtocolV2LinkDisabledError(failureCode, firmwareMessage);
531
+ }
532
+ }
407
533
  probeError = new Error(`unexpected response type ${response.type}`);
408
534
  } catch (error) {
535
+ if (isProtocolV2LinkDisabledError(error) || shouldRethrow?.(error)) {
536
+ throw error;
537
+ }
409
538
  probeError = error;
410
539
  }
411
540
 
@@ -93,6 +93,19 @@ export abstract class ProtocolV2UsbTransportBase<Key> {
93
93
  );
94
94
  }
95
95
 
96
+ protected sendProtocolV2UsbFlowControl(
97
+ key: Key,
98
+ name: string,
99
+ data: Record<string, unknown>
100
+ ): Promise<MessageFromOneKey> {
101
+ return this.protocolV2UsbLinks.sendFlowControl(
102
+ key,
103
+ () => this.createProtocolV2UsbAdapter(key),
104
+ name,
105
+ data
106
+ );
107
+ }
108
+
96
109
  protected invalidateProtocolV2UsbLink(key: Key, reason: string): Promise<void> {
97
110
  return this.protocolV2UsbLinks.invalidateLink(key, reason);
98
111
  }
@@ -1950,6 +1950,8 @@ export type EthereumSignTxOneKey = {
1950
1950
  data_length?: number;
1951
1951
  chain_id: number;
1952
1952
  tx_type?: number;
1953
+ expected_address?: string;
1954
+ source_fingerprint?: number;
1953
1955
  };
1954
1956
 
1955
1957
  // EthereumAccessListOneKey
@@ -1971,6 +1973,8 @@ export type EthereumSignTxEIP1559OneKey = {
1971
1973
  data_length: number;
1972
1974
  chain_id: number;
1973
1975
  access_list: EthereumAccessListOneKey[];
1976
+ expected_address?: string;
1977
+ source_fingerprint?: number;
1974
1978
  };
1975
1979
 
1976
1980
  // EthereumAuthorizationSignature
@@ -2023,6 +2027,7 @@ export type EthereumSignMessageOneKey = {
2023
2027
  address_n: number[];
2024
2028
  message: string;
2025
2029
  chain_id?: number;
2030
+ source_fingerprint?: number;
2026
2031
  };
2027
2032
 
2028
2033
  // EthereumMessageSignatureOneKey
@@ -3815,6 +3820,7 @@ export type SolanaSignTx = {
3815
3820
  address_n: number[];
3816
3821
  raw_tx: string;
3817
3822
  extra_info?: SolanaTxExtraInfo;
3823
+ source_fingerprint?: number;
3818
3824
  };
3819
3825
 
3820
3826
  // SolanaSignedTx
@@ -3838,12 +3844,14 @@ export type SolanaSignOffChainMessage = {
3838
3844
  message_version?: SolanaOffChainMessageVersion;
3839
3845
  message_format?: SolanaOffChainMessageFormat;
3840
3846
  application_domain?: string;
3847
+ source_fingerprint?: number;
3841
3848
  };
3842
3849
 
3843
3850
  // SolanaSignUnsafeMessage
3844
3851
  export type SolanaSignUnsafeMessage = {
3845
3852
  address_n: number[];
3846
3853
  message: string;
3854
+ source_fingerprint?: number;
3847
3855
  };
3848
3856
 
3849
3857
  // SolanaMessageSignature
@@ -4594,6 +4602,7 @@ export type EthereumSignTypedDataQR = {
4594
4602
  chain_id?: number;
4595
4603
  metamask_v4_compat?: boolean;
4596
4604
  request_id?: string;
4605
+ source_fingerprint?: number;
4597
4606
  };
4598
4607
 
4599
4608
  // SetBusy
@@ -4766,6 +4775,19 @@ export type DeviceMiscUsbMscControl = {
4766
4775
  enable: boolean;
4767
4776
  };
4768
4777
 
4778
+ // DeviceFindMyTokenUpdate
4779
+ export type DeviceFindMyTokenUpdate = {
4780
+ token: string;
4781
+ };
4782
+
4783
+ // DeviceFindMyTokenStateGet
4784
+ export type DeviceFindMyTokenStateGet = {};
4785
+
4786
+ // DeviceFindMyTokenState
4787
+ export type DeviceFindMyTokenState = {
4788
+ burned: boolean;
4789
+ };
4790
+
4769
4791
  export enum DeviceFactoryAck {
4770
4792
  FACTORY_ACK_SUCCESS = 0,
4771
4793
  FACTORY_ACK_FAIL = 1,
@@ -4785,8 +4807,8 @@ export type DeviceFactoryInfoManufactureTime = {
4785
4807
  export type DeviceFactoryInfo = {
4786
4808
  version?: number;
4787
4809
  serial_number?: string;
4788
- burn_in_completed?: boolean;
4789
4810
  factory_test_completed?: boolean;
4811
+ factory_burn_in_completed?: boolean;
4790
4812
  manufacture_time?: DeviceFactoryInfoManufactureTime;
4791
4813
  };
4792
4814
 
@@ -4837,21 +4859,40 @@ export enum DeviceFirmwareUpdateTaskStatus {
4837
4859
  FW_MGMT_UPDATER_TASK_STATUS_FAILED_ENTRY_OUT_OF_BOUNDS = 10,
4838
4860
  }
4839
4861
 
4862
+ export enum DeviceFirmwareUpdatePhase {
4863
+ FW_MGMT_UPDATER_PHASE_PREPARE = 0,
4864
+ FW_MGMT_UPDATER_PHASE_INSTALL = 1,
4865
+ FW_MGMT_UPDATER_PHASE_VERIFY = 2,
4866
+ }
4867
+
4840
4868
  // DeviceFirmwareTarget
4841
4869
  export type DeviceFirmwareTarget = {
4842
4870
  target_id: DeviceFirmwareTargetType;
4843
4871
  path: string;
4844
4872
  };
4845
4873
 
4874
+ // DeviceFirmwareUpdateStage
4875
+ export type DeviceFirmwareUpdateStage = {
4876
+ targets: DeviceFirmwareTarget[];
4877
+ };
4878
+
4846
4879
  // DeviceFirmwareUpdateRequest
4847
4880
  export type DeviceFirmwareUpdateRequest = {
4848
- targets: DeviceFirmwareTarget[];
4881
+ reboot_after_update?: boolean;
4882
+ };
4883
+
4884
+ // DeviceFirmwareUpdatePhaseInfo
4885
+ export type DeviceFirmwareUpdatePhaseInfo = {
4886
+ phase: DeviceFirmwareUpdatePhase;
4887
+ progress_percent: number;
4849
4888
  };
4850
4889
 
4851
4890
  // DeviceFirmwareUpdateRecord
4852
4891
  export type DeviceFirmwareUpdateRecord = {
4853
4892
  target_id: DeviceFirmwareTargetType;
4854
4893
  status?: DeviceFirmwareUpdateTaskStatus;
4894
+ progress_percent?: number;
4895
+ phase_info?: DeviceFirmwareUpdatePhaseInfo;
4855
4896
  payload_version?: number;
4856
4897
  path?: string;
4857
4898
  };
@@ -4859,6 +4900,8 @@ export type DeviceFirmwareUpdateRecord = {
4859
4900
  // DeviceFirmwareUpdateRecordFields
4860
4901
  export type DeviceFirmwareUpdateRecordFields = {
4861
4902
  status?: boolean;
4903
+ progress_percent?: boolean;
4904
+ phase_info?: boolean;
4862
4905
  payload_version?: boolean;
4863
4906
  path?: boolean;
4864
4907
  };
@@ -4937,7 +4980,7 @@ export type DeviceSEInfo = {
4937
4980
  // DeviceInfoTargets
4938
4981
  export type DeviceInfoTargets = {
4939
4982
  hw?: boolean;
4940
- fw?: boolean;
4983
+ main_mcu?: boolean;
4941
4984
  coprocessor?: boolean;
4942
4985
  se1?: boolean;
4943
4986
  se2?: boolean;
@@ -4963,7 +5006,7 @@ export type DeviceInfoGet = {
4963
5006
  export type ProtocolV2DeviceInfo = {
4964
5007
  protocol_version: number;
4965
5008
  hw?: DeviceHardwareInfo;
4966
- fw?: DeviceMainMcuInfo;
5009
+ main_mcu?: DeviceMainMcuInfo;
4967
5010
  coprocessor?: DeviceCoprocessorInfo;
4968
5011
  se1?: DeviceSEInfo;
4969
5012
  se2?: DeviceSEInfo;
@@ -5215,7 +5258,9 @@ export enum ViewTipType {
5215
5258
  // ViewTip
5216
5259
  export type ViewTip = {
5217
5260
  type: ViewTipType;
5218
- text: string;
5261
+ text?: string;
5262
+ text_id?: number;
5263
+ text_arg?: string;
5219
5264
  };
5220
5265
 
5221
5266
  // ViewRawData
@@ -5235,23 +5280,27 @@ export enum ViewSignLayout {
5235
5280
 
5236
5281
  // ViewSignPage
5237
5282
  export type ViewSignPage = {
5238
- title: string;
5283
+ title?: string;
5239
5284
  amount?: UintType;
5240
5285
  general: ViewDetail[];
5241
5286
  tip?: ViewTip;
5242
5287
  raw_data?: ViewRawData;
5243
5288
  slide_to_confirm?: boolean;
5244
5289
  layout?: ViewSignLayout;
5290
+ title_id?: number;
5291
+ title_arg?: string;
5245
5292
  };
5246
5293
 
5247
5294
  // ViewVerifyPage
5248
5295
  export type ViewVerifyPage = {
5249
- title: string;
5296
+ title?: string;
5250
5297
  address: string;
5251
5298
  path: string;
5252
5299
  network?: string;
5253
5300
  derive_type?: string;
5254
5301
  value_key?: number;
5302
+ title_id?: number;
5303
+ chain_id?: number;
5255
5304
  };
5256
5305
 
5257
5306
  export enum ProtocolV2FailureType {
@@ -5881,6 +5930,9 @@ export type MessageType = {
5881
5930
  DeviceCertificateSignature: DeviceCertificateSignature;
5882
5931
  DeviceCertificateSign: DeviceCertificateSign;
5883
5932
  DeviceMiscUsbMscControl: DeviceMiscUsbMscControl;
5933
+ DeviceFindMyTokenUpdate: DeviceFindMyTokenUpdate;
5934
+ DeviceFindMyTokenStateGet: DeviceFindMyTokenStateGet;
5935
+ DeviceFindMyTokenState: DeviceFindMyTokenState;
5884
5936
  DeviceFactoryInfoManufactureTime: DeviceFactoryInfoManufactureTime;
5885
5937
  DeviceFactoryInfo: DeviceFactoryInfo;
5886
5938
  DeviceFactoryInfoSet: DeviceFactoryInfoSet;
@@ -5888,7 +5940,9 @@ export type MessageType = {
5888
5940
  DeviceFactoryPermanentLock: DeviceFactoryPermanentLock;
5889
5941
  DeviceFactoryTest: DeviceFactoryTest;
5890
5942
  DeviceFirmwareTarget: DeviceFirmwareTarget;
5943
+ DeviceFirmwareUpdateStage: DeviceFirmwareUpdateStage;
5891
5944
  DeviceFirmwareUpdateRequest: DeviceFirmwareUpdateRequest;
5945
+ DeviceFirmwareUpdatePhaseInfo: DeviceFirmwareUpdatePhaseInfo;
5892
5946
  DeviceFirmwareUpdateRecord: DeviceFirmwareUpdateRecord;
5893
5947
  DeviceFirmwareUpdateRecordFields: DeviceFirmwareUpdateRecordFields;
5894
5948
  DeviceFirmwareUpdateStatusGet: DeviceFirmwareUpdateStatusGet;
@@ -54,6 +54,13 @@ export type AcquireInput = {
54
54
  forceCleanRunPromise?: boolean;
55
55
  expectedProtocol?: ProtocolType;
56
56
  protocolHint?: ProtocolType;
57
+ /**
58
+ * Explicit recovery/discovery (e.g. detectDeviceConnectProtocol): the
59
+ * transport must probe the protocol on the wire, bypassing any cached result.
60
+ */
61
+ forceProtocolDetection?: boolean;
62
+ /** Reuse expectedProtocol only when this transport previously confirmed it for the same endpoint. */
63
+ skipProtocolProbe?: boolean;
57
64
  };
58
65
 
59
66
  export type MessageFromOneKey = { type: string; message: Record<string, any> };
@@ -70,6 +77,10 @@ export type TransportCallOptions = {
70
77
  onIntermediateResponse?: (response: MessageFromOneKey) => void;
71
78
  /** Called after the complete request frame has been submitted to the transport. */
72
79
  onWriteCompleted?: (metrics: TransportWriteMetrics) => void;
80
+ /** Resolve after the complete request frame is written without waiting for a response. */
81
+ returnAfterWrite?: boolean;
82
+ /** Observe the delayed terminal response of a write-only call while a later call is active. */
83
+ onResponseAfterWrite?: (response: MessageFromOneKey) => void;
73
84
  /** Prefer acknowledged BLE characteristic writes for this call when supported. */
74
85
  writeWithResponse?: boolean;
75
86
  };