@onekeyfe/hd-transport 1.2.0-alpha.20 → 1.2.0-alpha.22

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.
Files changed (35) hide show
  1. package/__tests__/messages.test.js +32 -7
  2. package/__tests__/protocol-v2-link-manager.test.js +27 -34
  3. package/__tests__/protocol-v2.test.js +242 -56
  4. package/dist/constants.d.ts +1 -0
  5. package/dist/constants.d.ts.map +1 -1
  6. package/dist/index.d.ts +101 -19
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +166 -106
  9. package/dist/protocols/index.d.ts +5 -2
  10. package/dist/protocols/index.d.ts.map +1 -1
  11. package/dist/protocols/v2/decode.d.ts +11 -1
  12. package/dist/protocols/v2/decode.d.ts.map +1 -1
  13. package/dist/protocols/v2/errors.d.ts +8 -0
  14. package/dist/protocols/v2/errors.d.ts.map +1 -0
  15. package/dist/protocols/v2/frame-assembler.d.ts.map +1 -1
  16. package/dist/protocols/v2/index.d.ts +1 -0
  17. package/dist/protocols/v2/index.d.ts.map +1 -1
  18. package/dist/protocols/v2/link-manager.d.ts +0 -1
  19. package/dist/protocols/v2/link-manager.d.ts.map +1 -1
  20. package/dist/protocols/v2/session.d.ts +4 -5
  21. package/dist/protocols/v2/session.d.ts.map +1 -1
  22. package/dist/types/messages.d.ts +34 -5
  23. package/dist/types/messages.d.ts.map +1 -1
  24. package/messages-protocol-v2.json +77 -4
  25. package/package.json +2 -2
  26. package/scripts/protobuf-types.js +7 -1
  27. package/src/constants.ts +8 -0
  28. package/src/protocols/index.ts +18 -4
  29. package/src/protocols/v2/decode.ts +49 -19
  30. package/src/protocols/v2/errors.ts +23 -0
  31. package/src/protocols/v2/frame-assembler.ts +6 -4
  32. package/src/protocols/v2/index.ts +1 -0
  33. package/src/protocols/v2/link-manager.ts +0 -2
  34. package/src/protocols/v2/session.ts +124 -122
  35. package/src/types/messages.ts +46 -6
@@ -482,9 +482,9 @@
482
482
  "MessageType_DeviceStatus": 60603,
483
483
  "MessageType_DevGetOnboardingStatus": 60604,
484
484
  "MessageType_DevOnboardingStatus": 60605,
485
- "MessageType_DeviceSessionGet": 60606,
486
485
  "MessageType_DeviceSession": 60607,
487
486
  "MessageType_DeviceSessionAskPin": 60608,
487
+ "MessageType_DeviceSessionOpen": 60609,
488
488
  "MessageType_FilesystemPermissionFix": 60800,
489
489
  "MessageType_FilesystemPathInfo": 60801,
490
490
  "MessageType_FilesystemPathInfoQuery": 60802,
@@ -506,7 +506,8 @@
506
506
  [90, 92],
507
507
  [114, 122],
508
508
  [300, 304],
509
- [309, 312]
509
+ [309, 312],
510
+ [60606, 60606]
510
511
  ]
511
512
  },
512
513
  "AlephiumGetAddress": {
@@ -11744,7 +11745,15 @@
11744
11745
  }
11745
11746
  },
11746
11747
  "ProtocolInfoRequest": {
11747
- "fields": {}
11748
+ "fields": {
11749
+ "eventless_wallet_session": {
11750
+ "type": "bool",
11751
+ "id": 1,
11752
+ "options": {
11753
+ "default": false
11754
+ }
11755
+ }
11756
+ }
11748
11757
  },
11749
11758
  "ProtocolInfo": {
11750
11759
  "fields": {
@@ -12427,14 +12436,78 @@
12427
12436
  }
12428
12437
  }
12429
12438
  },
12430
- "DeviceSessionGet": {
12439
+ "DeviceSessionErrorCode": {
12440
+ "values": {
12441
+ "DeviceSessionError_None": 0,
12442
+ "DeviceSessionError_UserCancelled": 1,
12443
+ "DeviceSessionError_InvalidSession": 2,
12444
+ "DeviceSessionError_AttachPinUnavailable": 3,
12445
+ "DeviceSessionError_PassphraseDisabled": 4,
12446
+ "DeviceSessionError_Busy": 5
12447
+ }
12448
+ },
12449
+ "DeviceSessionResume": {
12431
12450
  "fields": {
12432
12451
  "session_id": {
12452
+ "rule": "required",
12433
12453
  "type": "bytes",
12434
12454
  "id": 1
12435
12455
  }
12436
12456
  }
12437
12457
  },
12458
+ "DeviceSessionHostPassphrase": {
12459
+ "fields": {
12460
+ "passphrase": {
12461
+ "rule": "required",
12462
+ "type": "string",
12463
+ "id": 1
12464
+ }
12465
+ }
12466
+ },
12467
+ "DeviceSessionPassphraseOnDevice": {
12468
+ "fields": {}
12469
+ },
12470
+ "DeviceSessionAttachPinOnDevice": {
12471
+ "fields": {}
12472
+ },
12473
+ "DeviceSessionSelect": {
12474
+ "oneofs": {
12475
+ "access": {
12476
+ "oneof": ["host_passphrase", "passphrase_on_device", "attach_pin_on_device"]
12477
+ }
12478
+ },
12479
+ "fields": {
12480
+ "host_passphrase": {
12481
+ "type": "DeviceSessionHostPassphrase",
12482
+ "id": 1
12483
+ },
12484
+ "passphrase_on_device": {
12485
+ "type": "DeviceSessionPassphraseOnDevice",
12486
+ "id": 2
12487
+ },
12488
+ "attach_pin_on_device": {
12489
+ "type": "DeviceSessionAttachPinOnDevice",
12490
+ "id": 3
12491
+ }
12492
+ }
12493
+ },
12494
+ "DeviceSessionOpen": {
12495
+ "oneofs": {
12496
+ "mode": {
12497
+ "oneof": ["resume", "select"]
12498
+ }
12499
+ },
12500
+ "fields": {
12501
+ "resume": {
12502
+ "type": "DeviceSessionResume",
12503
+ "id": 1
12504
+ },
12505
+ "select": {
12506
+ "type": "DeviceSessionSelect",
12507
+ "id": 2
12508
+ }
12509
+ }
12510
+ },
12438
12511
  "DeviceSession": {
12439
12512
  "fields": {
12440
12513
  "session_id": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onekeyfe/hd-transport",
3
- "version": "1.2.0-alpha.20",
3
+ "version": "1.2.0-alpha.22",
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": "5fbc1ada90fd3cfda7e5ef08be368cb452018733"
31
+ "gitHead": "ce94a5ebbe141b0c15db7c68d08085ca1ddb0166"
32
32
  }
@@ -69,6 +69,9 @@ const ENUM_KEYS = [
69
69
  'ProtocolV2Capability',
70
70
  ];
71
71
 
72
+ // Safety checks are accepted as both protobuf enum names and numeric legacy values.
73
+ const ENUM_KEYS_WITH_NUMERIC_VALUES = ['SafetyCheckLevel'];
74
+
72
75
  const parseEnumTypescript = (itemName, item) => {
73
76
  const value = [];
74
77
  const IS_KEY = ENUM_KEYS.includes(itemName);
@@ -87,7 +90,10 @@ const parseEnumTypescript = (itemName, item) => {
87
90
  value.push('}');
88
91
 
89
92
  if (IS_KEY) {
90
- value.push(`export type ${itemName} = keyof typeof Enum_${itemName};`);
93
+ const numericValue = ENUM_KEYS_WITH_NUMERIC_VALUES.includes(itemName)
94
+ ? ` | Enum_${itemName}`
95
+ : '';
96
+ value.push(`export type ${itemName} = keyof typeof Enum_${itemName}${numericValue};`);
91
97
  }
92
98
  // empty line
93
99
  value.push('');
package/src/constants.ts CHANGED
@@ -48,3 +48,11 @@ export const PROTOCOL_V2_CHANNEL_SOCKET = 2;
48
48
 
49
49
  /** packet_src for protobuf commands; firmware routes zero to the protobuf dispatcher. */
50
50
  export const PROTOCOL_V2_PACKET_SRC_COMMAND = 0;
51
+
52
+ /**
53
+ * Shared upper bound for a Protocol V2 request without a method-specific timeout.
54
+ * Interactive and signing calls may wait on the device UI, so keep this longer than
55
+ * ordinary transport timeouts while still preventing a stalled link from blocking
56
+ * the per-device queue forever.
57
+ */
58
+ export const PROTOCOL_V2_DEFAULT_RESPONSE_TIMEOUT_MS = 5 * 60 * 1000;
@@ -9,7 +9,7 @@ import { decode as decodeProtobuf } from '../serialization/protobuf/decode';
9
9
  import {
10
10
  decodeFrame as decodeV2Frame,
11
11
  encodeProtobufFrame,
12
- getAckSequence,
12
+ inspectFrameHeader,
13
13
  isAckFrame,
14
14
  } from './v2';
15
15
 
@@ -76,16 +76,19 @@ export const ProtocolV1 = {
76
76
 
77
77
  export const ProtocolV2 = {
78
78
  isAckFrame,
79
- getAckSequence,
79
+ inspectFrameHeader,
80
80
 
81
81
  inspectFrame(schemas: ProtocolV2Schemas, frame: Uint8Array) {
82
- const { messageTypeId, pbPayload, seq } = decodeV2Frame(frame);
82
+ const { messageTypeId, pbPayload, seq, router, packetSrc, dataType } = decodeV2Frame(frame);
83
83
  const { messageName } = createProtocolV2MessageFromType(messageTypeId, schemas);
84
84
  return {
85
85
  messageName,
86
86
  messageTypeId,
87
87
  pbPayload,
88
88
  seq,
89
+ router,
90
+ packetSrc,
91
+ dataType,
89
92
  type: messageName,
90
93
  };
91
94
  },
@@ -116,7 +119,18 @@ export const ProtocolV2 = {
116
119
  const { messageTypeId, pbPayload, seq, messageName } = this.inspectFrame(schemas, frame);
117
120
  const { Message } = createProtocolV2MessageFromType(messageTypeId, schemas);
118
121
  const rxByteBuffer = ByteBuffer.wrap(Buffer.from(pbPayload) as unknown as ArrayBuffer);
119
- const message = decodeProtobuf(Message, rxByteBuffer);
122
+ let message: ReturnType<typeof decodeProtobuf>;
123
+ try {
124
+ message = decodeProtobuf(Message, rxByteBuffer);
125
+ } catch (cause) {
126
+ const error = new Error(
127
+ `Protocol V2 protobuf decode failed for "${messageName}" ` +
128
+ `(${messageTypeId}, ${pbPayload.length}-byte payload); ` +
129
+ 'the payload is malformed or incompatible with the active SDK schema.'
130
+ );
131
+ (error as Error & { cause?: unknown }).cause = cause;
132
+ throw error;
133
+ }
120
134
 
121
135
  return {
122
136
  message,
@@ -1,4 +1,9 @@
1
- import { PROTO_DATA_TYPE_ACK, PROTO_HEAD_CRC_SIZE, PROTO_HEAD_SOF } from './constants';
1
+ import {
2
+ PROTO_DATA_TYPE_ACK,
3
+ PROTO_DATA_TYPE_PACKET,
4
+ PROTO_HEAD_CRC_SIZE,
5
+ PROTO_HEAD_SOF,
6
+ } from './constants';
2
7
  import { crc8 } from './crc8';
3
8
 
4
9
  export interface ProtoV2Frame {
@@ -8,9 +13,23 @@ export interface ProtoV2Frame {
8
13
  pbPayload: Uint8Array;
9
14
  /** Sequence number from the frame header */
10
15
  seq: number;
16
+ /** Routing channel from the frame header */
17
+ router: number;
18
+ /** Packet source from the frame header */
19
+ packetSrc: number;
20
+ /** Packet or ACK discriminator from the frame header */
21
+ dataType: number;
11
22
  }
12
23
 
13
- function validateFrame(data: Uint8Array): number {
24
+ export type ProtoV2FrameHeader = {
25
+ frameLen: number;
26
+ router: number;
27
+ packetSrc: number;
28
+ dataType: number;
29
+ seq: number;
30
+ };
31
+
32
+ export function inspectFrameHeader(data: Uint8Array): ProtoV2FrameHeader {
14
33
  if (data.length < PROTO_HEAD_CRC_SIZE) {
15
34
  throw new Error(`Protocol V2 frame too short: ${data.length} bytes`);
16
35
  }
@@ -23,8 +42,10 @@ function validateFrame(data: Uint8Array): number {
23
42
 
24
43
  const frameLen = data[1] + data[2] * 256;
25
44
 
26
- if (data.length < frameLen) {
27
- throw new Error(`Frame truncated: expected ${frameLen} bytes, got ${data.length}`);
45
+ if (data.length !== frameLen) {
46
+ throw new Error(
47
+ `Protocol V2 frame length mismatch: expected ${frameLen} bytes, got ${data.length}`
48
+ );
28
49
  }
29
50
 
30
51
  const expectedHeaderCrc = crc8(data, 3);
@@ -45,7 +66,22 @@ function validateFrame(data: Uint8Array): number {
45
66
  );
46
67
  }
47
68
 
48
- return frameLen;
69
+ // eslint-disable-next-line no-bitwise
70
+ const dataType = data[5] & 0x03;
71
+ // eslint-disable-next-line no-bitwise
72
+ const packetSrc = (data[5] >> 2) & 0x0f;
73
+ const seq = data[6];
74
+ if (seq === 0) {
75
+ throw new Error('Invalid Protocol V2 sequence: 0 is reserved');
76
+ }
77
+
78
+ return {
79
+ frameLen,
80
+ router: data[4],
81
+ packetSrc,
82
+ dataType,
83
+ seq,
84
+ };
49
85
  }
50
86
 
51
87
  export function isAckFrame(data: Uint8Array): boolean {
@@ -54,20 +90,13 @@ export function isAckFrame(data: Uint8Array): boolean {
54
90
  return false;
55
91
  }
56
92
 
57
- const frameLen = validateFrame(data);
58
- if (frameLen !== PROTO_HEAD_CRC_SIZE) {
59
- throw new Error(`Invalid Protocol V2 ACK frame length: ${frameLen}`);
93
+ const header = inspectFrameHeader(data);
94
+ if (header.frameLen !== PROTO_HEAD_CRC_SIZE) {
95
+ throw new Error(`Invalid Protocol V2 ACK frame length: ${header.frameLen}`);
60
96
  }
61
97
  return true;
62
98
  }
63
99
 
64
- export function getAckSequence(data: Uint8Array): number | undefined {
65
- if (!isAckFrame(data)) {
66
- return undefined;
67
- }
68
- return data[6];
69
- }
70
-
71
100
  /**
72
101
  * Parse and validate a Protocol V2 response frame.
73
102
  *
@@ -79,9 +108,10 @@ export function getAckSequence(data: Uint8Array): number | undefined {
79
108
  * Returns the decoded messageTypeId, raw protobuf payload, and sequence number.
80
109
  */
81
110
  export function decodeFrame(data: Uint8Array): ProtoV2Frame {
82
- const frameLen = validateFrame(data);
83
-
84
- const seq = data[6];
111
+ const { frameLen, router, packetSrc, dataType, seq } = inspectFrameHeader(data);
112
+ if (dataType !== PROTO_DATA_TYPE_PACKET) {
113
+ throw new Error(`Invalid Protocol V2 data type: expected packet, got ${dataType}`);
114
+ }
85
115
  // Payload spans bytes 7 to frameLen-2 (inclusive), excluding final CRC byte
86
116
  const payloadData = data.slice(7, frameLen - 1);
87
117
 
@@ -92,5 +122,5 @@ export function decodeFrame(data: Uint8Array): ProtoV2Frame {
92
122
  const messageTypeId = payloadData[0] + payloadData[1] * 256;
93
123
  const pbPayload = payloadData.slice(2);
94
124
 
95
- return { messageTypeId, pbPayload, seq };
125
+ return { messageTypeId, pbPayload, seq, router, packetSrc, dataType };
96
126
  }
@@ -0,0 +1,23 @@
1
+ export type ProtocolV2LinkErrorCode =
2
+ | 'response-timeout'
3
+ | 'router'
4
+ | 'packet-source'
5
+ | 'ack-sequence'
6
+ | 'response-sequence'
7
+ | 'frame';
8
+
9
+ export class ProtocolV2LinkError extends Error {
10
+ readonly code: ProtocolV2LinkErrorCode;
11
+
12
+ readonly cause?: unknown;
13
+
14
+ constructor(code: ProtocolV2LinkErrorCode, message: string, cause?: unknown) {
15
+ super(message);
16
+ this.name = 'ProtocolV2LinkError';
17
+ this.code = code;
18
+ this.cause = cause;
19
+ }
20
+ }
21
+
22
+ export const isProtocolV2LinkError = (error: unknown): error is ProtocolV2LinkError =>
23
+ error instanceof ProtocolV2LinkError;
@@ -1,6 +1,7 @@
1
1
  import { PROTOCOL_V2_FRAME_MAX_BYTES } from '../../constants';
2
2
  import { PROTO_HEAD_CRC_SIZE, PROTO_HEAD_SOF, PROTO_PRE_HEAD_SIZE } from './constants';
3
3
  import { crc8 } from './crc8';
4
+ import { ProtocolV2LinkError } from './errors';
4
5
 
5
6
  export function concatUint8Arrays(arrays: Uint8Array[]): Uint8Array {
6
7
  const totalLength = arrays.reduce((sum, arr) => sum + arr.length, 0);
@@ -58,7 +59,7 @@ export class ProtocolV2FrameAssembler {
58
59
 
59
60
  if (this.buffer[0] !== PROTO_HEAD_SOF) {
60
61
  this.reset();
61
- throw new Error('Invalid Protocol V2 SOF');
62
+ throw new ProtocolV2LinkError('frame', 'Invalid Protocol V2 SOF');
62
63
  }
63
64
 
64
65
  const expectedLen = this.buffer[1] + this.buffer[2] * 256;
@@ -67,11 +68,11 @@ export class ProtocolV2FrameAssembler {
67
68
  // complete frame: without resetting, this poison prefix would stay in
68
69
  // the buffer forever and deadlock the caller's drain loop.
69
70
  this.reset();
70
- throw new Error(`Protocol V2 frame length too small: ${expectedLen}`);
71
+ throw new ProtocolV2LinkError('frame', `Protocol V2 frame length too small: ${expectedLen}`);
71
72
  }
72
73
  if (expectedLen > this.maxFrameBytes) {
73
74
  this.reset();
74
- throw new Error(`Protocol V2 frame too large: ${expectedLen}`);
75
+ throw new ProtocolV2LinkError('frame', `Protocol V2 frame too large: ${expectedLen}`);
75
76
  }
76
77
 
77
78
  if (this.buffer.length < PROTO_PRE_HEAD_SIZE) return undefined;
@@ -82,7 +83,8 @@ export class ProtocolV2FrameAssembler {
82
83
  const expectedHeaderCrc = crc8(this.buffer, 3);
83
84
  if (this.buffer[3] !== expectedHeaderCrc) {
84
85
  this.reset();
85
- throw new Error(
86
+ throw new ProtocolV2LinkError(
87
+ 'frame',
86
88
  `Protocol V2 header CRC mismatch: expected 0x${expectedHeaderCrc
87
89
  .toString(16)
88
90
  .padStart(2, '0')}`
@@ -2,4 +2,5 @@ export * from './constants';
2
2
  export * from './crc8';
3
3
  export * from './encode';
4
4
  export * from './decode';
5
+ export * from './errors';
5
6
  export * from './frame-assembler';
@@ -17,7 +17,6 @@ export interface ProtocolV2LinkAdapter {
17
17
  logger?: ProtocolV2SessionOptions['logger'];
18
18
  logPrefix?: string;
19
19
  createTimeoutError?: ProtocolV2SessionOptions['createTimeoutError'];
20
- writeTimeoutMs?: number;
21
20
  }
22
21
 
23
22
  export type ProtocolV2LinkManagerOptions<Key> = {
@@ -129,7 +128,6 @@ export class ProtocolV2LinkManager<Key> {
129
128
  logger: adapter.logger,
130
129
  logPrefix: adapter.logPrefix,
131
130
  createTimeoutError: adapter.createTimeoutError,
132
- writeTimeoutMs: adapter.writeTimeoutMs,
133
131
  });
134
132
  const link = { adapter, session, state };
135
133
  this.links.set(key, link);