@onekeyfe/hd-transport 1.1.34-alpha.2 → 1.1.34-alpha.3

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 (93) hide show
  1. package/__tests__/build-receive.test.js +6 -8
  2. package/__tests__/decode-features.test.js +3 -2
  3. package/__tests__/messages.test.js +131 -0
  4. package/__tests__/protocol-v2-link-manager.test.js +351 -0
  5. package/__tests__/protocol-v2-usb-transport-base.test.js +296 -0
  6. package/__tests__/protocol-v2.test.js +899 -0
  7. package/dist/constants.d.ts +16 -5
  8. package/dist/constants.d.ts.map +1 -1
  9. package/dist/index.d.ts +1398 -45
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.js +1102 -85
  12. package/dist/protocols/index.d.ts +50 -0
  13. package/dist/protocols/index.d.ts.map +1 -0
  14. package/dist/protocols/v1/decode.d.ts +11 -0
  15. package/dist/protocols/v1/decode.d.ts.map +1 -0
  16. package/dist/protocols/v1/encode.d.ts +11 -0
  17. package/dist/protocols/v1/encode.d.ts.map +1 -0
  18. package/dist/protocols/v1/index.d.ts +5 -0
  19. package/dist/protocols/v1/index.d.ts.map +1 -0
  20. package/dist/protocols/v1/packets.d.ts +7 -0
  21. package/dist/protocols/v1/packets.d.ts.map +1 -0
  22. package/dist/{serialization → protocols/v1}/receive.d.ts +1 -1
  23. package/dist/protocols/v1/receive.d.ts.map +1 -0
  24. package/dist/protocols/v2/constants.d.ts +8 -0
  25. package/dist/protocols/v2/constants.d.ts.map +1 -0
  26. package/dist/protocols/v2/crc8.d.ts +3 -0
  27. package/dist/protocols/v2/crc8.d.ts.map +1 -0
  28. package/dist/protocols/v2/decode.d.ts +8 -0
  29. package/dist/protocols/v2/decode.d.ts.map +1 -0
  30. package/dist/protocols/v2/encode.d.ts +4 -0
  31. package/dist/protocols/v2/encode.d.ts.map +1 -0
  32. package/dist/protocols/v2/frame-assembler.d.ts +12 -0
  33. package/dist/protocols/v2/frame-assembler.d.ts.map +1 -0
  34. package/dist/protocols/v2/index.d.ts +6 -0
  35. package/dist/protocols/v2/index.d.ts.map +1 -0
  36. package/dist/protocols/v2/link-manager.d.ts +36 -0
  37. package/dist/protocols/v2/link-manager.d.ts.map +1 -0
  38. package/dist/protocols/v2/sequence-cursor.d.ts +5 -0
  39. package/dist/protocols/v2/sequence-cursor.d.ts.map +1 -0
  40. package/dist/protocols/v2/session.d.ts +63 -0
  41. package/dist/protocols/v2/session.d.ts.map +1 -0
  42. package/dist/protocols/v2/usb-transport-base.d.ts +31 -0
  43. package/dist/protocols/v2/usb-transport-base.d.ts.map +1 -0
  44. package/dist/serialization/index.d.ts +6 -3
  45. package/dist/serialization/index.d.ts.map +1 -1
  46. package/dist/serialization/protobuf/decode.d.ts.map +1 -1
  47. package/dist/serialization/protobuf/messages.d.ts +1 -1
  48. package/dist/serialization/protobuf/messages.d.ts.map +1 -1
  49. package/dist/types/messages.d.ts +707 -12
  50. package/dist/types/messages.d.ts.map +1 -1
  51. package/dist/types/transport.d.ts +19 -5
  52. package/dist/types/transport.d.ts.map +1 -1
  53. package/dist/utils/logBlockCommand.d.ts.map +1 -1
  54. package/messages-protocol-v2.json +13544 -0
  55. package/package.json +3 -3
  56. package/scripts/protobuf-patches/TxInputType.js +1 -0
  57. package/scripts/protobuf-patches/index.js +2 -0
  58. package/scripts/protobuf-types.js +233 -18
  59. package/src/constants.ts +48 -6
  60. package/src/index.ts +47 -11
  61. package/src/protocols/index.ts +124 -0
  62. package/src/{serialization/protocol → protocols/v1}/decode.ts +4 -4
  63. package/src/{serialization/protocol → protocols/v1}/encode.ts +18 -13
  64. package/src/protocols/v1/index.ts +4 -0
  65. package/src/protocols/v1/packets.ts +53 -0
  66. package/src/{serialization → protocols/v1}/receive.ts +5 -5
  67. package/src/protocols/v2/constants.ts +7 -0
  68. package/src/protocols/v2/crc8.ts +34 -0
  69. package/src/protocols/v2/decode.ts +89 -0
  70. package/src/protocols/v2/encode.ts +90 -0
  71. package/src/protocols/v2/frame-assembler.ts +98 -0
  72. package/src/protocols/v2/index.ts +5 -0
  73. package/src/protocols/v2/link-manager.ts +162 -0
  74. package/src/protocols/v2/sequence-cursor.ts +10 -0
  75. package/src/protocols/v2/session.ts +339 -0
  76. package/src/protocols/v2/usb-transport-base.ts +194 -0
  77. package/src/serialization/index.ts +6 -5
  78. package/src/serialization/protobuf/decode.ts +7 -0
  79. package/src/serialization/protobuf/messages.ts +14 -5
  80. package/src/types/messages.ts +903 -13
  81. package/src/types/transport.ts +29 -5
  82. package/src/utils/logBlockCommand.ts +9 -1
  83. package/dist/serialization/protocol/decode.d.ts +0 -11
  84. package/dist/serialization/protocol/decode.d.ts.map +0 -1
  85. package/dist/serialization/protocol/encode.d.ts +0 -11
  86. package/dist/serialization/protocol/encode.d.ts.map +0 -1
  87. package/dist/serialization/protocol/index.d.ts +0 -3
  88. package/dist/serialization/protocol/index.d.ts.map +0 -1
  89. package/dist/serialization/receive.d.ts.map +0 -1
  90. package/dist/serialization/send.d.ts +0 -7
  91. package/dist/serialization/send.d.ts.map +0 -1
  92. package/src/serialization/protocol/index.ts +0 -2
  93. package/src/serialization/send.ts +0 -58
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onekeyfe/hd-transport",
3
- "version": "1.1.34-alpha.2",
3
+ "version": "1.1.34-alpha.3",
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",
@@ -16,7 +16,7 @@
16
16
  "test": "jest",
17
17
  "lint": "eslint .",
18
18
  "lint:fix": "eslint . --fix",
19
- "update:protobuf": "./scripts/protobuf-build.sh"
19
+ "update-protobuf": "./scripts/protobuf-build.sh"
20
20
  },
21
21
  "devDependencies": {
22
22
  "@types/bytebuffer": "^5.0.42",
@@ -28,5 +28,5 @@
28
28
  "long": "^4.0.0",
29
29
  "protobufjs": "^6.11.2"
30
30
  },
31
- "gitHead": "dee167c35d191f3f4e02c4cb6228e1f926f929ec"
31
+ "gitHead": "3688bc8495eeb2ffd5616270d13fa29e8967623a"
32
32
  }
@@ -29,6 +29,7 @@ type CommonTxInputType = {|
29
29
  witness?: string, // used by EXTERNAL, depending on script_pubkey
30
30
  ownership_proof?: string, // used by EXTERNAL, depending on script_pubkey
31
31
  commitment_data?: string, // used by EXTERNAL, depending on ownership_proof
32
+ coinjoin_flags?: number, // Protocol V2 only (CoinJoin)
32
33
  |};
33
34
 
34
35
  export type TxInputType =
@@ -116,6 +116,7 @@ const TYPE_PATCH = {
116
116
  'Features.onekey_se03_state': 'string | null',
117
117
  'Features.onekey_se04_state': 'string | null',
118
118
  'HDNodePathType.node': 'HDNodeType | string',
119
+ 'FilesystemFile.data': 'Buffer | ArrayBuffer | Uint8Array | string',
119
120
  'FirmwareUpload.payload': 'Buffer | ArrayBuffer',
120
121
  'EthereumGetAddress.encoded_network': 'ArrayBuffer',
121
122
  'EthereumDefinitions.encoded_network': 'ArrayBuffer',
@@ -207,6 +208,7 @@ const TYPE_PATCH = {
207
208
  'TonSignMessage.fwd_fee': UINT_TYPE,
208
209
  'TonSignMessage.expire_at': UINT_TYPE,
209
210
  'TonSignMessage.ext_ton_amount': UINT_TYPE,
211
+ 'TonSignData.timestamp': UINT_TYPE,
210
212
  'TonSignProof.expire_at': UINT_TYPE,
211
213
  'TonSignData.timestamp': UINT_TYPE,
212
214
  };
@@ -6,9 +6,33 @@
6
6
  const fs = require('fs');
7
7
  const path = require('path');
8
8
 
9
- const json = require('../messages.json');
10
9
  const { RULE_PATCH, TYPE_PATCH, DEFINITION_PATCH, SKIP, UINT_TYPE } = require('./protobuf-patches');
11
10
 
11
+ const readJson = filePath => JSON.parse(fs.readFileSync(filePath, 'utf8'));
12
+
13
+ const localMessagesJsonPath = path.join(__dirname, '../messages.json');
14
+ const coreMessagesJsonPath = path.join(__dirname, '../../core/src/data/messages/messages.json');
15
+ const json = readJson(
16
+ fs.existsSync(localMessagesJsonPath) ? localMessagesJsonPath : coreMessagesJsonPath
17
+ );
18
+ const optionalJsonFiles = ['../messages-protocol-v2.json'];
19
+ const OPTIONAL_DUPLICATE_TYPE_ALIASES = {
20
+ DeviceInfo: 'ProtocolV2DeviceInfo',
21
+ };
22
+ const OPTIONAL_NESTED_TYPE_ALIASES = [
23
+ { parent: 'Failure', name: 'FailureType', alias: 'ProtocolV2FailureType' },
24
+ { parent: 'Features', name: 'Capability', alias: 'ProtocolV2Capability' },
25
+ ];
26
+ const messageTypeAliases = {};
27
+ const skipMessageTypeKeys = new Set(Object.values(OPTIONAL_DUPLICATE_TYPE_ALIASES));
28
+
29
+ // V1/V2 duplicate-type merge bookkeeping.
30
+ // When the same top-level type exists in both schemas with a different shape we emit
31
+ // the union of both sides instead of silently keeping the V1 definition only.
32
+ const mergedRuleOverrides = {}; // `${Type}.${field}` -> 'optional'
33
+ const mergedTypeOverrides = {}; // `${Type}.${field}` -> 'v1JsType | v2JsType'
34
+ const duplicateTypeMergeReport = []; // { name, diffs: string[] }
35
+
12
36
  const args = process.argv.slice(2);
13
37
 
14
38
  const isTypescript = args.includes('typescript');
@@ -26,6 +50,8 @@ const FIELD_TYPES = {
26
50
 
27
51
  const types = []; // { type: 'enum | message', name: string, value: string[], exact?: boolean };
28
52
 
53
+ const hasParsedType = name => types.some(t => t && t.name === name);
54
+
29
55
  // enums used as keys (string), used as values (number) by default
30
56
  const ENUM_KEYS = [
31
57
  'InputScriptType',
@@ -40,6 +66,7 @@ const ENUM_KEYS = [
40
66
  'ButtonRequestType',
41
67
  'PinMatrixRequestType',
42
68
  'WordRequestType',
69
+ 'ProtocolV2Capability',
43
70
  ];
44
71
 
45
72
  const parseEnumTypescript = (itemName, item) => {
@@ -114,8 +141,9 @@ const useDefinition = def => {
114
141
  return clean.replace(/\/\/ @typescript-variant(.*)/, '').replace(/\/\/ @flowtype-variant:/, '');
115
142
  };
116
143
 
117
- const parseMessage = (messageName, message, depth = 0) => {
144
+ const parseMessage = (messageName, message, depth = 0, skipExisting = false) => {
118
145
  if (messageName === 'google') return;
146
+ if (!depth && skipExisting && hasParsedType(messageName)) return;
119
147
  const value = [];
120
148
  // add comment line
121
149
  if (!depth) value.push(`// ${messageName}`);
@@ -123,7 +151,9 @@ const parseMessage = (messageName, message, depth = 0) => {
123
151
 
124
152
  // declare nested values
125
153
  if (message.nested) {
126
- Object.keys(message.nested).map(item => parseMessage(item, message.nested[item], depth + 1));
154
+ Object.keys(message.nested).map(item =>
155
+ parseMessage(item, message.nested[item], depth + 1, skipExisting)
156
+ );
127
157
  }
128
158
 
129
159
  if (message.values) {
@@ -145,11 +175,15 @@ const parseMessage = (messageName, message, depth = 0) => {
145
175
  Object.keys(message.fields).forEach(fieldName => {
146
176
  const field = message.fields[fieldName];
147
177
  const fieldKey = `${messageName}.${fieldName}`;
148
- // find patch for "rule"
149
- const fieldRule = RULE_PATCH[fieldKey] || field.rule;
178
+ // find patch for "rule" (hand-written patches win over V1/V2 merge overrides)
179
+ const fieldRule = RULE_PATCH[fieldKey] || mergedRuleOverrides[fieldKey] || field.rule;
150
180
  const rule = fieldRule === 'required' || fieldRule === 'repeated' ? ': ' : '?: ';
151
- // find patch for "type"
152
- let type = TYPE_PATCH[fieldKey] || FIELD_TYPES[field.type] || field.type;
181
+ // find patch for "type" (hand-written patches win over V1/V2 merge overrides)
182
+ let type =
183
+ TYPE_PATCH[fieldKey] ||
184
+ mergedTypeOverrides[fieldKey] ||
185
+ FIELD_TYPES[field.type] ||
186
+ field.type;
153
187
  // automatically convert all amount and fee fields to UINT_TYPE
154
188
  if (['amount', 'fee'].includes(fieldName)) {
155
189
  type = UINT_TYPE;
@@ -176,8 +210,163 @@ const parseMessage = (messageName, message, depth = 0) => {
176
210
  });
177
211
  };
178
212
 
213
+ const jsFieldType = protoType => FIELD_TYPES[protoType] || protoType;
214
+
215
+ // Merge a duplicate Protocol V2 definition into its V1 counterpart (in-memory only,
216
+ // messages.json / messages-protocol-v2.json on disk stay untouched).
217
+ // Strategy:
218
+ // - messages: emit the union of both sides' fields; a field present on one side only
219
+ // becomes optional
220
+ // - field type conflict: emit a union type and warn
221
+ // - field rule conflict (required/optional/repeated): keep the V1 rule and warn
222
+ // - enums: emit the union of members; member value conflicts keep the V1 value and warn
223
+ // - nested types are merged recursively
224
+ const mergeDuplicateDefinition = (typeName, base, extra, diffs) => {
225
+ if (base.values || extra.values) {
226
+ if (!base.values || !extra.values) {
227
+ diffs.push('! kind conflict (enum vs message), keeping V1 definition');
228
+ console.warn(
229
+ `[protobuf-types] duplicate type "${typeName}" is an enum on one side and a message on the other, keeping V1`
230
+ );
231
+ return;
232
+ }
233
+ Object.entries(extra.values).forEach(([memberName, memberValue]) => {
234
+ if (!(memberName in base.values)) {
235
+ base.values[memberName] = memberValue;
236
+ diffs.push(`+ enum member ${memberName} = ${memberValue} (V2 only)`);
237
+ } else if (base.values[memberName] !== memberValue) {
238
+ diffs.push(
239
+ `! enum member ${memberName} value conflict: V1=${base.values[memberName]} V2=${memberValue}, keeping V1`
240
+ );
241
+ console.warn(
242
+ `[protobuf-types] enum "${typeName}.${memberName}" value differs between V1 (${base.values[memberName]}) and V2 (${memberValue}), keeping V1`
243
+ );
244
+ }
245
+ });
246
+ return;
247
+ }
248
+
249
+ if (extra.nested) {
250
+ base.nested = base.nested || {};
251
+ Object.keys(extra.nested).forEach(nestedName => {
252
+ if (base.nested[nestedName]) {
253
+ mergeDuplicateDefinition(
254
+ nestedName,
255
+ base.nested[nestedName],
256
+ extra.nested[nestedName],
257
+ diffs
258
+ );
259
+ } else {
260
+ base.nested[nestedName] = extra.nested[nestedName];
261
+ diffs.push(`+ nested type ${nestedName} (V2 only)`);
262
+ }
263
+ });
264
+ }
265
+
266
+ const extraFields = extra.fields || {};
267
+ if (!Object.keys(extraFields).length && !base.fields) return;
268
+ base.fields = base.fields || {};
269
+ const baseFields = base.fields;
270
+
271
+ Object.entries(extraFields).forEach(([fieldName, field]) => {
272
+ const fieldKey = `${typeName}.${fieldName}`;
273
+ if (!(fieldName in baseFields)) {
274
+ baseFields[fieldName] = field;
275
+ // field exists in V2 only -> always optional in the merged interface
276
+ if (field.rule) mergedRuleOverrides[fieldKey] = 'optional';
277
+ diffs.push(
278
+ `+ field ${fieldName}?: ${jsFieldType(field.type)}${
279
+ field.rule === 'repeated' ? '[]' : ''
280
+ } (V2 only, marked optional)`
281
+ );
282
+ return;
283
+ }
284
+ const baseField = baseFields[fieldName];
285
+ if (baseField.type !== field.type) {
286
+ const baseJsType = jsFieldType(baseField.type);
287
+ const extraJsType = jsFieldType(field.type);
288
+ if (baseJsType !== extraJsType) {
289
+ mergedTypeOverrides[fieldKey] = `${baseJsType} | ${extraJsType}`;
290
+ diffs.push(
291
+ `! field ${fieldName} type conflict: V1=${baseField.type} V2=${field.type}, emitting union ${baseJsType} | ${extraJsType}`
292
+ );
293
+ console.warn(
294
+ `[protobuf-types] field "${fieldKey}" type differs between V1 (${baseField.type}) and V2 (${field.type}), emitting union type`
295
+ );
296
+ }
297
+ }
298
+ if ((baseField.rule || 'optional') !== (field.rule || 'optional')) {
299
+ diffs.push(
300
+ `! field ${fieldName} rule conflict: V1=${baseField.rule || 'optional'} V2=${
301
+ field.rule || 'optional'
302
+ }, keeping V1`
303
+ );
304
+ }
305
+ });
306
+
307
+ Object.entries(baseFields).forEach(([fieldName, field]) => {
308
+ if (fieldName in extraFields) return;
309
+ const fieldKey = `${typeName}.${fieldName}`;
310
+ // field exists in V1 only -> always optional in the merged interface
311
+ if (field.rule && !mergedRuleOverrides[fieldKey]) {
312
+ mergedRuleOverrides[fieldKey] = 'optional';
313
+ diffs.push(`~ field ${fieldName} (V1 only): ${field.rule} -> optional`);
314
+ } else {
315
+ diffs.push(`~ field ${fieldName} (V1 only, already optional)`);
316
+ }
317
+ });
318
+ };
319
+
320
+ // Pass 1 (before parsing): merge duplicate V2 definitions into the V1 schema so the
321
+ // generated interfaces contain the union of both protocols' fields. Previously the V2
322
+ // side of a duplicate type was silently dropped, hiding V2-only fields/enum members.
323
+ const optionalJsons = [];
324
+ optionalJsonFiles.forEach(jsonFile => {
325
+ const jsonPath = path.join(__dirname, jsonFile);
326
+ if (!fs.existsSync(jsonPath)) return;
327
+ const optionalJson = readJson(jsonPath);
328
+ OPTIONAL_NESTED_TYPE_ALIASES.forEach(({ parent, name, alias }) => {
329
+ const definition = optionalJson.nested[parent]?.nested?.[name];
330
+ if (definition) optionalJson.nested[alias] = definition;
331
+ });
332
+ optionalJsons.push(optionalJson);
333
+ Object.keys(optionalJson.nested).forEach(e => {
334
+ if (!json.nested[e]) return; // V2-only type, parsed after the V1 pass below
335
+ if (OPTIONAL_DUPLICATE_TYPE_ALIASES[e]) return; // kept as a separate aliased type
336
+ if (SKIP.includes(e)) return; // custom/hand-written definitions (MessageType, TxInput, ...)
337
+ const diffs = [];
338
+ mergeDuplicateDefinition(e, json.nested[e], optionalJson.nested[e], diffs);
339
+ if (diffs.length) duplicateTypeMergeReport.push({ name: e, diffs });
340
+ });
341
+ });
342
+
343
+ // Explicit drift report: every merged duplicate type and its V1/V2 differences.
344
+ if (duplicateTypeMergeReport.length) {
345
+ console.log(
346
+ `[protobuf-types] ${duplicateTypeMergeReport.length} duplicate V1/V2 type(s) differ and were merged (field union):`
347
+ );
348
+ duplicateTypeMergeReport.forEach(({ name, diffs }) => {
349
+ console.log(` - ${name}`);
350
+ diffs.forEach(diff => console.log(` ${diff}`));
351
+ });
352
+ }
353
+
179
354
  // top level messages and nested messages
180
- Object.keys(json.nested).map(e => parseMessage(e, json.nested[e]));
355
+ Object.keys(json.nested).forEach(e => parseMessage(e, json.nested[e]));
356
+
357
+ // Pass 2: aliased duplicates (e.g. DeviceInfo -> ProtocolV2DeviceInfo) and V2-only types
358
+ optionalJsons.forEach(optionalJson => {
359
+ Object.keys(optionalJson.nested).forEach(e => {
360
+ const alias = hasParsedType(e) ? OPTIONAL_DUPLICATE_TYPE_ALIASES[e] : undefined;
361
+ if (alias) {
362
+ parseMessage(alias, optionalJson.nested[e]);
363
+ messageTypeAliases[e] = [e, alias];
364
+ return;
365
+ }
366
+ if (json.nested[e]) return; // duplicate type, already merged into the V1 definition
367
+ parseMessage(e, optionalJson.nested[e], 0, true);
368
+ });
369
+ });
181
370
 
182
371
  // types needs reordering (used before defined)
183
372
  const ORDER = {
@@ -226,10 +415,14 @@ if (!isTypescript) {
226
415
  types
227
416
  .flatMap(t => (t && t.type === 'message' ? [t] : []))
228
417
  .forEach(t => {
418
+ if (skipMessageTypeKeys.has(t.name)) return;
419
+ const messageTypeValue = messageTypeAliases[t.name]
420
+ ? messageTypeAliases[t.name].join(' | ')
421
+ : t.name;
229
422
  if (t.exact) {
230
- lines.push(` ${t.name}: $Exact<${t.name}>;`);
423
+ lines.push(` ${t.name}: $Exact<${messageTypeValue}>;`);
231
424
  } else {
232
- lines.push(` ${t.name}: ${t.name};`);
425
+ lines.push(` ${t.name}: ${messageTypeValue};`);
233
426
  }
234
427
  // lines.push(' ' + t.name + ': $Exact<' + t.name + '>;');
235
428
  });
@@ -244,11 +437,18 @@ export type MessageResponse<T: MessageKey> = {
244
437
  message: $ElementType<MessageType, T>;
245
438
  };
246
439
 
247
- export type TypedCall = <T: MessageKey, R: MessageKey>(
440
+ export type TypedCall = {
441
+ <T: MessageKey, R: $ReadOnlyArray<MessageKey>>(
442
+ type: T,
443
+ resType: R,
444
+ message?: $ElementType<MessageType, T>
445
+ ): Promise<MessageResponse<$ElementType<R, number>>>;
446
+ <T: MessageKey, R: MessageKey>(
248
447
  type: T,
249
448
  resType: R,
250
449
  message?: $ElementType<MessageType, T>
251
- ) => Promise<MessageResponse<R>>;
450
+ ): Promise<MessageResponse<R>>;
451
+ };
252
452
  `);
253
453
  } else {
254
454
  lines.push('// custom connect definitions');
@@ -256,7 +456,11 @@ export type TypedCall = <T: MessageKey, R: MessageKey>(
256
456
  types
257
457
  .flatMap(t => (t && t.type === 'message' ? [t] : []))
258
458
  .forEach(t => {
259
- lines.push(` ${t.name}: ${t.name};`);
459
+ if (skipMessageTypeKeys.has(t.name)) return;
460
+ const messageTypeValue = messageTypeAliases[t.name]
461
+ ? messageTypeAliases[t.name].join(' | ')
462
+ : t.name;
463
+ lines.push(` ${t.name}: ${messageTypeValue};`);
260
464
  });
261
465
  lines.push('};');
262
466
 
@@ -269,11 +473,22 @@ export type MessageResponse<T extends MessageKey> = {
269
473
  message: MessageType[T];
270
474
  };
271
475
 
272
- export type TypedCall = <T extends MessageKey, R extends MessageKey>(
273
- type: T,
274
- resType: R,
275
- message?: MessageType[T],
276
- ) => Promise<MessageResponse<R>>;
476
+ export type MessageResponseMap = {
477
+ [K in MessageKey]: MessageResponse<K>;
478
+ };
479
+
480
+ export type TypedCall = {
481
+ <T extends MessageKey, R extends readonly MessageKey[]>(
482
+ type: T,
483
+ resType: R,
484
+ message?: MessageType[T],
485
+ ): Promise<MessageResponseMap[R[number]]>;
486
+ <T extends MessageKey, R extends MessageKey>(
487
+ type: T,
488
+ resType: R,
489
+ message?: MessageType[T],
490
+ ): Promise<MessageResponse<R>>;
491
+ };
277
492
  `);
278
493
  }
279
494
 
package/src/constants.ts CHANGED
@@ -1,8 +1,50 @@
1
- export const MESSAGE_TOP_CHAR = 0x003f;
2
- export const MESSAGE_HEADER_BYTE = 0x23;
3
- export const HEADER_SIZE = 1 + 1 + 4 + 2;
4
- export const BUFFER_SIZE = 63;
1
+ // ---- Protocol V1 (Pro1 / Touch / Mini / Classic) ----
2
+
3
+ /** Protocol V1 USB report marker, ASCII `?`. */
4
+ export const PROTOCOL_V1_REPORT_ID = 0x3f;
5
+
6
+ /** Protocol V1 envelope marker, ASCII `#`. */
7
+ export const PROTOCOL_V1_HEADER_BYTE = 0x23;
8
+
9
+ /** Protocol V1 payload bytes per chunk after the report marker. */
10
+ export const PROTOCOL_V1_CHUNK_PAYLOAD_SIZE = 63;
11
+
12
+ /** Protocol V1 USB packet length: report marker plus chunk payload. */
13
+ export const PROTOCOL_V1_USB_PACKET_SIZE = PROTOCOL_V1_CHUNK_PAYLOAD_SIZE + 1;
14
+
15
+ /** Protocol V1 message metadata: two-byte type plus four-byte payload length. */
16
+ export const PROTOCOL_V1_MESSAGE_HEADER_SIZE = 2 + 4;
17
+
18
+ /** Protocol V1 envelope metadata: `##`, message type, and payload length. */
19
+ export const PROTOCOL_V1_ENVELOPE_HEADER_SIZE = 1 + 1 + PROTOCOL_V1_MESSAGE_HEADER_SIZE;
20
+
21
+ // ---- Protocol V2 (Pro2 USB / BLE transports) ----
22
+
23
+ /** Maximum V2 frame length, including header, 4000-byte payload, protobuf overhead, and CRC. */
24
+ export const PROTOCOL_V2_FRAME_MAX_BYTES = 4608;
25
+
26
+ /** FilesystemFileWrite chunk size over WebUSB. */
27
+ export const PROTOCOL_V2_WEBUSB_FILE_CHUNK_SIZE = 4000;
28
+
29
+ /** FilesystemFileWrite chunk size over BLE. */
30
+ export const PROTOCOL_V2_BLE_FILE_CHUNK_SIZE = 1800;
31
+
32
+ /** BLE FilesystemFileRead chunk size, limited by the Pro2 1024-byte UART TX buffer. */
33
+ export const PROTOCOL_V2_BLE_FILE_READ_CHUNK_SIZE = 900;
34
+
35
+ /** Pro2 BLE/UART RX FIFO must hold a complete Proto Link frame. */
36
+ export const PROTOCOL_V2_BLE_FRAME_MAX_BYTES = 2048;
37
+
38
+ /** @deprecated Use the transport-specific WebUSB or BLE file chunk constant. */
39
+ export const PROTOCOL_V2_FILE_CHUNK_SIZE = PROTOCOL_V2_WEBUSB_FILE_CHUNK_SIZE;
40
+
5
41
  /**
6
- * exclude ?##
42
+ * Protocol V2 routing channel. USB reaches the main MCU directly, while BLE routes
43
+ * through the BLE coprocessor UART bridge.
7
44
  */
8
- export const COMMON_HEADER_SIZE = 6;
45
+ export const PROTOCOL_V2_CHANNEL_USB = 0;
46
+ export const PROTOCOL_V2_CHANNEL_BLE_UART = 1;
47
+ export const PROTOCOL_V2_CHANNEL_SOCKET = 2;
48
+
49
+ /** packet_src for protobuf commands; firmware routes zero to the protobuf dispatcher. */
50
+ export const PROTOCOL_V2_PACKET_SRC_COMMAND = 0;
package/src/index.ts CHANGED
@@ -1,14 +1,28 @@
1
1
  import * as protobuf from 'protobufjs/light';
2
- import * as Long from 'long';
2
+ import Long from 'long';
3
3
 
4
4
  import {
5
- buildBuffers,
6
- buildEncodeBuffers,
7
- buildOne,
8
- decodeProtocol,
5
+ createMessageFromName,
6
+ createMessageFromType,
7
+ decodeProtobuf,
8
+ encodeProtobuf,
9
9
  parseConfigure,
10
- receiveOne,
11
10
  } from './serialization';
11
+ import { PROTOCOL_V2_SYS_MESSAGE_THRESHOLD, ProtocolV1, ProtocolV2 } from './protocols';
12
+ import * as protocolV2Codec from './protocols/v2';
13
+ import { ProtocolV2LinkManager } from './protocols/v2/link-manager';
14
+ import { ProtocolV2UsbTransportBase } from './protocols/v2/usb-transport-base';
15
+ import {
16
+ ProtocolV2FrameAssembler,
17
+ ProtocolV2SequenceCursor,
18
+ ProtocolV2Session,
19
+ bytesToHex,
20
+ concatUint8Arrays,
21
+ getErrorMessage,
22
+ hexToBytes,
23
+ probeProtocolV2,
24
+ withProtocolTimeout,
25
+ } from './protocols/v2/session';
12
26
  import * as check from './utils/highlevel-checks';
13
27
 
14
28
  protobuf.util.Long = Long;
@@ -21,10 +35,12 @@ export type {
21
35
  OneKeyMobileDeviceInfo,
22
36
  OneKeyDeviceInfoWithSession,
23
37
  MessageFromOneKey,
38
+ TransportCallOptions,
24
39
  LowlevelTransportSharedPlugin,
25
40
  LowLevelDevice,
26
41
  OneKeyDeviceInfoBase,
27
42
  OneKeyDeviceCommType,
43
+ ProtocolType,
28
44
  } from './types';
29
45
 
30
46
  export { Messages } from './types';
@@ -32,13 +48,33 @@ export * from './types/messages';
32
48
  export * from './utils/logBlockCommand';
33
49
 
34
50
  export * from './constants';
51
+ export * from './protocols';
52
+ export * as protocolV1 from './protocols/v1';
53
+ export * as protocolV2 from './protocols/v2';
54
+ export * from './protocols/v2/session';
55
+ export * from './protocols/v2/link-manager';
56
+ export * from './protocols/v2/usb-transport-base';
35
57
 
36
58
  export default {
37
59
  check,
38
- buildOne,
39
- buildBuffers,
40
- buildEncodeBuffers,
41
- receiveOne,
42
60
  parseConfigure,
43
- decodeProtocol,
61
+ protocolV2: protocolV2Codec,
62
+ ProtocolV1,
63
+ ProtocolV2,
64
+ PROTOCOL_V2_SYS_MESSAGE_THRESHOLD,
65
+ ProtocolV2FrameAssembler,
66
+ ProtocolV2LinkManager,
67
+ ProtocolV2SequenceCursor,
68
+ ProtocolV2Session,
69
+ ProtocolV2UsbTransportBase,
70
+ bytesToHex,
71
+ concatUint8Arrays,
72
+ createMessageFromName,
73
+ createMessageFromType,
74
+ encodeProtobuf,
75
+ decodeProtobuf,
76
+ getErrorMessage,
77
+ hexToBytes,
78
+ probeProtocolV2,
79
+ withProtocolTimeout,
44
80
  };
@@ -0,0 +1,124 @@
1
+ import ByteBuffer from 'bytebuffer';
2
+
3
+ import { encodeEnvelopeMessage, encodeMessageChunks, encodeTransportPackets } from './v1/packets';
4
+ import { decodeFirstChunk } from './v1/decode';
5
+ import { decodeMessage as decodeV1Message } from './v1/receive';
6
+ import { createMessageFromName, createMessageFromType } from '../serialization/protobuf/messages';
7
+ import { encode as encodeProtobuf } from '../serialization/protobuf/encode';
8
+ import { decode as decodeProtobuf } from '../serialization/protobuf/decode';
9
+ import { decodeFrame as decodeV2Frame, encodeProtobufFrame, isAckFrame } from './v2';
10
+
11
+ import type { Root } from 'protobufjs/light';
12
+
13
+ export const PROTOCOL_V2_SYS_MESSAGE_THRESHOLD = 60000;
14
+
15
+ type ProtocolV2Schemas = {
16
+ protocolV1: Root;
17
+ protocolV2: Root;
18
+ };
19
+
20
+ type ProtocolV2FrameOptions = {
21
+ packetSrc?: number;
22
+ router?: number;
23
+ /** Sequence number (1-255). Managed per-session by ProtocolV2Session. */
24
+ seq?: number;
25
+ };
26
+
27
+ const resolveProtocolV2EncodeSchema = (name: string, schemas: ProtocolV2Schemas) => {
28
+ try {
29
+ schemas.protocolV2.lookupType(name);
30
+ return schemas.protocolV2;
31
+ } catch {
32
+ throw new Error(`Protocol V2 message "${name}" is not defined in Protocol V2 schema`);
33
+ }
34
+ };
35
+
36
+ const PROTOCOL_V2_LEGACY_DECODE_ALLOWLIST = new Set([
37
+ 'Failure',
38
+ 'ButtonRequest',
39
+ 'EntropyRequest',
40
+ 'PinMatrixRequest',
41
+ 'PassphraseRequest',
42
+ 'Deprecated_PassphraseStateRequest',
43
+ 'WordRequest',
44
+ ]);
45
+
46
+ const createProtocolV2MessageFromType = (messageTypeId: number, schemas: ProtocolV2Schemas) => {
47
+ try {
48
+ return createMessageFromType(schemas.protocolV2, messageTypeId);
49
+ } catch (protocolV2Error) {
50
+ let legacyMessage: ReturnType<typeof createMessageFromType>;
51
+ try {
52
+ legacyMessage = createMessageFromType(schemas.protocolV1, messageTypeId);
53
+ } catch {
54
+ throw protocolV2Error;
55
+ }
56
+ if (PROTOCOL_V2_LEGACY_DECODE_ALLOWLIST.has(legacyMessage.messageName)) {
57
+ return legacyMessage;
58
+ }
59
+ throw protocolV2Error;
60
+ }
61
+ };
62
+
63
+ export const ProtocolV1 = {
64
+ encodeEnvelope: encodeEnvelopeMessage,
65
+ encodeMessageChunks,
66
+ encodeTransportPackets,
67
+ decodeFirstChunk,
68
+
69
+ decodeMessage: decodeV1Message,
70
+ };
71
+
72
+ export const ProtocolV2 = {
73
+ isAckFrame,
74
+
75
+ inspectFrame(schemas: ProtocolV2Schemas, frame: Uint8Array) {
76
+ const { messageTypeId, pbPayload, seq } = decodeV2Frame(frame);
77
+ const { messageName } = createProtocolV2MessageFromType(messageTypeId, schemas);
78
+ return {
79
+ messageName,
80
+ messageTypeId,
81
+ pbPayload,
82
+ seq,
83
+ type: messageName,
84
+ };
85
+ },
86
+
87
+ encodeFrame(
88
+ schemas: ProtocolV2Schemas,
89
+ name: string,
90
+ data: Record<string, unknown>,
91
+ options: ProtocolV2FrameOptions = {}
92
+ ) {
93
+ const encodeMessages = resolveProtocolV2EncodeSchema(name, schemas);
94
+ const { Message, messageTypeId } = createMessageFromName(encodeMessages, name);
95
+ const pbBuffer = encodeProtobuf(Message, data);
96
+ pbBuffer.reset();
97
+ const rawPbBuffer = pbBuffer.toBuffer() as unknown as ArrayBuffer;
98
+ const pbBytes = new Uint8Array(rawPbBuffer);
99
+
100
+ return encodeProtobufFrame(
101
+ messageTypeId,
102
+ pbBytes,
103
+ options.packetSrc,
104
+ options.router,
105
+ options.seq
106
+ );
107
+ },
108
+
109
+ decodeFrame(schemas: ProtocolV2Schemas, frame: Uint8Array) {
110
+ const { messageTypeId, pbPayload, seq, messageName } = this.inspectFrame(schemas, frame);
111
+ const { Message } = createProtocolV2MessageFromType(messageTypeId, schemas);
112
+ const rxByteBuffer = ByteBuffer.wrap(Buffer.from(pbPayload) as unknown as ArrayBuffer);
113
+ const message = decodeProtobuf(Message, rxByteBuffer);
114
+
115
+ return {
116
+ message,
117
+ messageName,
118
+ messageTypeId,
119
+ pbPayload,
120
+ seq,
121
+ type: messageName,
122
+ };
123
+ },
124
+ };
@@ -1,6 +1,6 @@
1
1
  import ByteBuffer from 'bytebuffer';
2
2
 
3
- import { MESSAGE_HEADER_BYTE } from '../../constants';
3
+ import { PROTOCOL_V1_HEADER_BYTE } from '../../constants';
4
4
 
5
5
  /**
6
6
  * Reads meta information from buffer
@@ -24,7 +24,7 @@ const readHeaderChunked = (buffer: ByteBuffer) => {
24
24
  return { sharp1, sharp2, typeId, length };
25
25
  };
26
26
 
27
- export const decode = (byteBuffer: ByteBuffer) => {
27
+ export const decodeEnvelope = (byteBuffer: ByteBuffer) => {
28
28
  const { typeId } = readHeader(byteBuffer);
29
29
 
30
30
  return {
@@ -35,13 +35,13 @@ export const decode = (byteBuffer: ByteBuffer) => {
35
35
 
36
36
  // Parses first raw input that comes from Trezor and returns some information about the whole message.
37
37
  // [compatibility]: accept Buffer just like decode does. But this would require changes in lower levels
38
- export const decodeChunked = (bytes: ArrayBuffer) => {
38
+ export const decodeFirstChunk = (bytes: ArrayBuffer) => {
39
39
  // convert to ByteBuffer so it's easier to read
40
40
  const byteBuffer = ByteBuffer.wrap(bytes, undefined, undefined, true);
41
41
 
42
42
  const { sharp1, sharp2, typeId, length } = readHeaderChunked(byteBuffer);
43
43
 
44
- if (sharp1 !== MESSAGE_HEADER_BYTE || sharp2 !== MESSAGE_HEADER_BYTE) {
44
+ if (sharp1 !== PROTOCOL_V1_HEADER_BYTE || sharp2 !== PROTOCOL_V1_HEADER_BYTE) {
45
45
  throw new Error("Didn't receive expected header signature.");
46
46
  }
47
47