@onekeyfe/hd-transport 1.1.27 → 1.2.0-alpha.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.
Files changed (87) hide show
  1. package/README.md +2 -4
  2. package/__tests__/build-receive.test.js +6 -8
  3. package/__tests__/decode-features.test.js +3 -2
  4. package/__tests__/messages.test.js +8 -0
  5. package/__tests__/protocol-v2.test.js +754 -0
  6. package/dist/constants.d.ts +14 -5
  7. package/dist/constants.d.ts.map +1 -1
  8. package/dist/index.d.ts +934 -41
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.js +827 -84
  11. package/dist/protocols/index.d.ts +45 -0
  12. package/dist/protocols/index.d.ts.map +1 -0
  13. package/dist/protocols/v1/decode.d.ts +11 -0
  14. package/dist/protocols/v1/decode.d.ts.map +1 -0
  15. package/dist/protocols/v1/encode.d.ts +11 -0
  16. package/dist/protocols/v1/encode.d.ts.map +1 -0
  17. package/dist/protocols/v1/index.d.ts +5 -0
  18. package/dist/protocols/v1/index.d.ts.map +1 -0
  19. package/dist/protocols/v1/packets.d.ts +7 -0
  20. package/dist/protocols/v1/packets.d.ts.map +1 -0
  21. package/dist/{serialization → protocols/v1}/receive.d.ts +1 -1
  22. package/dist/protocols/v1/receive.d.ts.map +1 -0
  23. package/dist/protocols/v2/constants.d.ts +7 -0
  24. package/dist/protocols/v2/constants.d.ts.map +1 -0
  25. package/dist/protocols/v2/crc8.d.ts +3 -0
  26. package/dist/protocols/v2/crc8.d.ts.map +1 -0
  27. package/dist/protocols/v2/debug.d.ts +13 -0
  28. package/dist/protocols/v2/debug.d.ts.map +1 -0
  29. package/dist/protocols/v2/decode.d.ts +8 -0
  30. package/dist/protocols/v2/decode.d.ts.map +1 -0
  31. package/dist/protocols/v2/encode.d.ts +5 -0
  32. package/dist/protocols/v2/encode.d.ts.map +1 -0
  33. package/dist/protocols/v2/frame-assembler.d.ts +12 -0
  34. package/dist/protocols/v2/frame-assembler.d.ts.map +1 -0
  35. package/dist/protocols/v2/index.d.ts +7 -0
  36. package/dist/protocols/v2/index.d.ts.map +1 -0
  37. package/dist/protocols/v2/session.d.ts +50 -0
  38. package/dist/protocols/v2/session.d.ts.map +1 -0
  39. package/dist/serialization/index.d.ts +6 -3
  40. package/dist/serialization/index.d.ts.map +1 -1
  41. package/dist/serialization/protobuf/decode.d.ts.map +1 -1
  42. package/dist/serialization/protobuf/messages.d.ts +1 -1
  43. package/dist/serialization/protobuf/messages.d.ts.map +1 -1
  44. package/dist/types/messages.d.ts +461 -11
  45. package/dist/types/messages.d.ts.map +1 -1
  46. package/dist/types/transport.d.ts +14 -2
  47. package/dist/types/transport.d.ts.map +1 -1
  48. package/dist/utils/logBlockCommand.d.ts.map +1 -1
  49. package/messages-protocol-v2.json +13369 -0
  50. package/package.json +3 -3
  51. package/scripts/protobuf-build.sh +314 -20
  52. package/scripts/protobuf-patches/TxInputType.js +1 -0
  53. package/scripts/protobuf-patches/index.js +2 -0
  54. package/scripts/protobuf-types.js +224 -18
  55. package/src/constants.ts +42 -6
  56. package/src/index.ts +39 -11
  57. package/src/protocols/index.ts +144 -0
  58. package/src/{serialization/protocol → protocols/v1}/decode.ts +4 -4
  59. package/src/{serialization/protocol → protocols/v1}/encode.ts +18 -13
  60. package/src/protocols/v1/index.ts +4 -0
  61. package/src/protocols/v1/packets.ts +53 -0
  62. package/src/{serialization → protocols/v1}/receive.ts +5 -5
  63. package/src/protocols/v2/constants.ts +6 -0
  64. package/src/protocols/v2/crc8.ts +34 -0
  65. package/src/protocols/v2/debug.ts +26 -0
  66. package/src/protocols/v2/decode.ts +92 -0
  67. package/src/protocols/v2/encode.ts +116 -0
  68. package/src/protocols/v2/frame-assembler.ts +98 -0
  69. package/src/protocols/v2/index.ts +6 -0
  70. package/src/protocols/v2/session.ts +429 -0
  71. package/src/serialization/index.ts +6 -5
  72. package/src/serialization/protobuf/decode.ts +7 -0
  73. package/src/serialization/protobuf/messages.ts +8 -4
  74. package/src/types/messages.ts +606 -13
  75. package/src/types/transport.ts +26 -2
  76. package/src/utils/logBlockCommand.ts +9 -1
  77. package/dist/serialization/protocol/decode.d.ts +0 -11
  78. package/dist/serialization/protocol/decode.d.ts.map +0 -1
  79. package/dist/serialization/protocol/encode.d.ts +0 -11
  80. package/dist/serialization/protocol/encode.d.ts.map +0 -1
  81. package/dist/serialization/protocol/index.d.ts +0 -3
  82. package/dist/serialization/protocol/index.d.ts.map +0 -1
  83. package/dist/serialization/receive.d.ts.map +0 -1
  84. package/dist/serialization/send.d.ts +0 -7
  85. package/dist/serialization/send.d.ts.map +0 -1
  86. package/src/serialization/protocol/index.ts +0 -2
  87. package/src/serialization/send.ts +0 -58
@@ -6,9 +6,29 @@
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 messageTypeAliases = {};
23
+ const skipMessageTypeKeys = new Set(Object.values(OPTIONAL_DUPLICATE_TYPE_ALIASES));
24
+
25
+ // V1/V2 duplicate-type merge bookkeeping.
26
+ // When the same top-level type exists in both schemas with a different shape we emit
27
+ // the union of both sides instead of silently keeping the V1 definition only.
28
+ const mergedRuleOverrides = {}; // `${Type}.${field}` -> 'optional'
29
+ const mergedTypeOverrides = {}; // `${Type}.${field}` -> 'v1JsType | v2JsType'
30
+ const duplicateTypeMergeReport = []; // { name, diffs: string[] }
31
+
12
32
  const args = process.argv.slice(2);
13
33
 
14
34
  const isTypescript = args.includes('typescript');
@@ -26,6 +46,8 @@ const FIELD_TYPES = {
26
46
 
27
47
  const types = []; // { type: 'enum | message', name: string, value: string[], exact?: boolean };
28
48
 
49
+ const hasParsedType = name => types.some(t => t && t.name === name);
50
+
29
51
  // enums used as keys (string), used as values (number) by default
30
52
  const ENUM_KEYS = [
31
53
  'InputScriptType',
@@ -111,8 +133,9 @@ const useDefinition = def => {
111
133
  return clean.replace(/\/\/ @typescript-variant(.*)/, '').replace(/\/\/ @flowtype-variant:/, '');
112
134
  };
113
135
 
114
- const parseMessage = (messageName, message, depth = 0) => {
136
+ const parseMessage = (messageName, message, depth = 0, skipExisting = false) => {
115
137
  if (messageName === 'google') return;
138
+ if (!depth && skipExisting && hasParsedType(messageName)) return;
116
139
  const value = [];
117
140
  // add comment line
118
141
  if (!depth) value.push(`// ${messageName}`);
@@ -120,7 +143,9 @@ const parseMessage = (messageName, message, depth = 0) => {
120
143
 
121
144
  // declare nested values
122
145
  if (message.nested) {
123
- Object.keys(message.nested).map(item => parseMessage(item, message.nested[item], depth + 1));
146
+ Object.keys(message.nested).map(item =>
147
+ parseMessage(item, message.nested[item], depth + 1, skipExisting)
148
+ );
124
149
  }
125
150
 
126
151
  if (message.values) {
@@ -142,11 +167,15 @@ const parseMessage = (messageName, message, depth = 0) => {
142
167
  Object.keys(message.fields).forEach(fieldName => {
143
168
  const field = message.fields[fieldName];
144
169
  const fieldKey = `${messageName}.${fieldName}`;
145
- // find patch for "rule"
146
- const fieldRule = RULE_PATCH[fieldKey] || field.rule;
170
+ // find patch for "rule" (hand-written patches win over V1/V2 merge overrides)
171
+ const fieldRule = RULE_PATCH[fieldKey] || mergedRuleOverrides[fieldKey] || field.rule;
147
172
  const rule = fieldRule === 'required' || fieldRule === 'repeated' ? ': ' : '?: ';
148
- // find patch for "type"
149
- let type = TYPE_PATCH[fieldKey] || FIELD_TYPES[field.type] || field.type;
173
+ // find patch for "type" (hand-written patches win over V1/V2 merge overrides)
174
+ let type =
175
+ TYPE_PATCH[fieldKey] ||
176
+ mergedTypeOverrides[fieldKey] ||
177
+ FIELD_TYPES[field.type] ||
178
+ field.type;
150
179
  // automatically convert all amount and fee fields to UINT_TYPE
151
180
  if (['amount', 'fee'].includes(fieldName)) {
152
181
  type = UINT_TYPE;
@@ -173,8 +202,159 @@ const parseMessage = (messageName, message, depth = 0) => {
173
202
  });
174
203
  };
175
204
 
205
+ const jsFieldType = protoType => FIELD_TYPES[protoType] || protoType;
206
+
207
+ // Merge a duplicate Protocol V2 definition into its V1 counterpart (in-memory only,
208
+ // messages.json / messages-protocol-v2.json on disk stay untouched).
209
+ // Strategy:
210
+ // - messages: emit the union of both sides' fields; a field present on one side only
211
+ // becomes optional
212
+ // - field type conflict: emit a union type and warn
213
+ // - field rule conflict (required/optional/repeated): keep the V1 rule and warn
214
+ // - enums: emit the union of members; member value conflicts keep the V1 value and warn
215
+ // - nested types are merged recursively
216
+ const mergeDuplicateDefinition = (typeName, base, extra, diffs) => {
217
+ if (base.values || extra.values) {
218
+ if (!base.values || !extra.values) {
219
+ diffs.push('! kind conflict (enum vs message), keeping V1 definition');
220
+ console.warn(
221
+ `[protobuf-types] duplicate type "${typeName}" is an enum on one side and a message on the other, keeping V1`
222
+ );
223
+ return;
224
+ }
225
+ Object.entries(extra.values).forEach(([memberName, memberValue]) => {
226
+ if (!(memberName in base.values)) {
227
+ base.values[memberName] = memberValue;
228
+ diffs.push(`+ enum member ${memberName} = ${memberValue} (V2 only)`);
229
+ } else if (base.values[memberName] !== memberValue) {
230
+ diffs.push(
231
+ `! enum member ${memberName} value conflict: V1=${base.values[memberName]} V2=${memberValue}, keeping V1`
232
+ );
233
+ console.warn(
234
+ `[protobuf-types] enum "${typeName}.${memberName}" value differs between V1 (${base.values[memberName]}) and V2 (${memberValue}), keeping V1`
235
+ );
236
+ }
237
+ });
238
+ return;
239
+ }
240
+
241
+ if (extra.nested) {
242
+ base.nested = base.nested || {};
243
+ Object.keys(extra.nested).forEach(nestedName => {
244
+ if (base.nested[nestedName]) {
245
+ mergeDuplicateDefinition(
246
+ nestedName,
247
+ base.nested[nestedName],
248
+ extra.nested[nestedName],
249
+ diffs
250
+ );
251
+ } else {
252
+ base.nested[nestedName] = extra.nested[nestedName];
253
+ diffs.push(`+ nested type ${nestedName} (V2 only)`);
254
+ }
255
+ });
256
+ }
257
+
258
+ const extraFields = extra.fields || {};
259
+ if (!Object.keys(extraFields).length && !base.fields) return;
260
+ base.fields = base.fields || {};
261
+ const baseFields = base.fields;
262
+
263
+ Object.entries(extraFields).forEach(([fieldName, field]) => {
264
+ const fieldKey = `${typeName}.${fieldName}`;
265
+ if (!(fieldName in baseFields)) {
266
+ baseFields[fieldName] = field;
267
+ // field exists in V2 only -> always optional in the merged interface
268
+ if (field.rule) mergedRuleOverrides[fieldKey] = 'optional';
269
+ diffs.push(
270
+ `+ field ${fieldName}?: ${jsFieldType(field.type)}${
271
+ field.rule === 'repeated' ? '[]' : ''
272
+ } (V2 only, marked optional)`
273
+ );
274
+ return;
275
+ }
276
+ const baseField = baseFields[fieldName];
277
+ if (baseField.type !== field.type) {
278
+ const baseJsType = jsFieldType(baseField.type);
279
+ const extraJsType = jsFieldType(field.type);
280
+ if (baseJsType !== extraJsType) {
281
+ mergedTypeOverrides[fieldKey] = `${baseJsType} | ${extraJsType}`;
282
+ diffs.push(
283
+ `! field ${fieldName} type conflict: V1=${baseField.type} V2=${field.type}, emitting union ${baseJsType} | ${extraJsType}`
284
+ );
285
+ console.warn(
286
+ `[protobuf-types] field "${fieldKey}" type differs between V1 (${baseField.type}) and V2 (${field.type}), emitting union type`
287
+ );
288
+ }
289
+ }
290
+ if ((baseField.rule || 'optional') !== (field.rule || 'optional')) {
291
+ diffs.push(
292
+ `! field ${fieldName} rule conflict: V1=${baseField.rule || 'optional'} V2=${
293
+ field.rule || 'optional'
294
+ }, keeping V1`
295
+ );
296
+ }
297
+ });
298
+
299
+ Object.entries(baseFields).forEach(([fieldName, field]) => {
300
+ if (fieldName in extraFields) return;
301
+ const fieldKey = `${typeName}.${fieldName}`;
302
+ // field exists in V1 only -> always optional in the merged interface
303
+ if (field.rule && !mergedRuleOverrides[fieldKey]) {
304
+ mergedRuleOverrides[fieldKey] = 'optional';
305
+ diffs.push(`~ field ${fieldName} (V1 only): ${field.rule} -> optional`);
306
+ } else {
307
+ diffs.push(`~ field ${fieldName} (V1 only, already optional)`);
308
+ }
309
+ });
310
+ };
311
+
312
+ // Pass 1 (before parsing): merge duplicate V2 definitions into the V1 schema so the
313
+ // generated interfaces contain the union of both protocols' fields. Previously the V2
314
+ // side of a duplicate type was silently dropped, hiding V2-only fields/enum members.
315
+ const optionalJsons = [];
316
+ optionalJsonFiles.forEach(jsonFile => {
317
+ const jsonPath = path.join(__dirname, jsonFile);
318
+ if (!fs.existsSync(jsonPath)) return;
319
+ const optionalJson = readJson(jsonPath);
320
+ optionalJsons.push(optionalJson);
321
+ Object.keys(optionalJson.nested).forEach(e => {
322
+ if (!json.nested[e]) return; // V2-only type, parsed after the V1 pass below
323
+ if (OPTIONAL_DUPLICATE_TYPE_ALIASES[e]) return; // kept as a separate aliased type
324
+ if (SKIP.includes(e)) return; // custom/hand-written definitions (MessageType, TxInput, ...)
325
+ const diffs = [];
326
+ mergeDuplicateDefinition(e, json.nested[e], optionalJson.nested[e], diffs);
327
+ if (diffs.length) duplicateTypeMergeReport.push({ name: e, diffs });
328
+ });
329
+ });
330
+
331
+ // Explicit drift report: every merged duplicate type and its V1/V2 differences.
332
+ if (duplicateTypeMergeReport.length) {
333
+ console.log(
334
+ `[protobuf-types] ${duplicateTypeMergeReport.length} duplicate V1/V2 type(s) differ and were merged (field union):`
335
+ );
336
+ duplicateTypeMergeReport.forEach(({ name, diffs }) => {
337
+ console.log(` - ${name}`);
338
+ diffs.forEach(diff => console.log(` ${diff}`));
339
+ });
340
+ }
341
+
176
342
  // top level messages and nested messages
177
- Object.keys(json.nested).map(e => parseMessage(e, json.nested[e]));
343
+ Object.keys(json.nested).forEach(e => parseMessage(e, json.nested[e]));
344
+
345
+ // Pass 2: aliased duplicates (e.g. DeviceInfo -> ProtocolV2DeviceInfo) and V2-only types
346
+ optionalJsons.forEach(optionalJson => {
347
+ Object.keys(optionalJson.nested).forEach(e => {
348
+ const alias = hasParsedType(e) ? OPTIONAL_DUPLICATE_TYPE_ALIASES[e] : undefined;
349
+ if (alias) {
350
+ parseMessage(alias, optionalJson.nested[e]);
351
+ messageTypeAliases[e] = [e, alias];
352
+ return;
353
+ }
354
+ if (json.nested[e]) return; // duplicate type, already merged into the V1 definition
355
+ parseMessage(e, optionalJson.nested[e], 0, true);
356
+ });
357
+ });
178
358
 
179
359
  // types needs reordering (used before defined)
180
360
  const ORDER = {
@@ -223,10 +403,14 @@ if (!isTypescript) {
223
403
  types
224
404
  .flatMap(t => (t && t.type === 'message' ? [t] : []))
225
405
  .forEach(t => {
406
+ if (skipMessageTypeKeys.has(t.name)) return;
407
+ const messageTypeValue = messageTypeAliases[t.name]
408
+ ? messageTypeAliases[t.name].join(' | ')
409
+ : t.name;
226
410
  if (t.exact) {
227
- lines.push(` ${t.name}: $Exact<${t.name}>;`);
411
+ lines.push(` ${t.name}: $Exact<${messageTypeValue}>;`);
228
412
  } else {
229
- lines.push(` ${t.name}: ${t.name};`);
413
+ lines.push(` ${t.name}: ${messageTypeValue};`);
230
414
  }
231
415
  // lines.push(' ' + t.name + ': $Exact<' + t.name + '>;');
232
416
  });
@@ -241,11 +425,18 @@ export type MessageResponse<T: MessageKey> = {
241
425
  message: $ElementType<MessageType, T>;
242
426
  };
243
427
 
244
- export type TypedCall = <T: MessageKey, R: MessageKey>(
428
+ export type TypedCall = {
429
+ <T: MessageKey, R: $ReadOnlyArray<MessageKey>>(
430
+ type: T,
431
+ resType: R,
432
+ message?: $ElementType<MessageType, T>
433
+ ): Promise<MessageResponse<$ElementType<R, number>>>;
434
+ <T: MessageKey, R: MessageKey>(
245
435
  type: T,
246
436
  resType: R,
247
437
  message?: $ElementType<MessageType, T>
248
- ) => Promise<MessageResponse<R>>;
438
+ ): Promise<MessageResponse<R>>;
439
+ };
249
440
  `);
250
441
  } else {
251
442
  lines.push('// custom connect definitions');
@@ -253,7 +444,11 @@ export type TypedCall = <T: MessageKey, R: MessageKey>(
253
444
  types
254
445
  .flatMap(t => (t && t.type === 'message' ? [t] : []))
255
446
  .forEach(t => {
256
- lines.push(` ${t.name}: ${t.name};`);
447
+ if (skipMessageTypeKeys.has(t.name)) return;
448
+ const messageTypeValue = messageTypeAliases[t.name]
449
+ ? messageTypeAliases[t.name].join(' | ')
450
+ : t.name;
451
+ lines.push(` ${t.name}: ${messageTypeValue};`);
257
452
  });
258
453
  lines.push('};');
259
454
 
@@ -266,11 +461,22 @@ export type MessageResponse<T extends MessageKey> = {
266
461
  message: MessageType[T];
267
462
  };
268
463
 
269
- export type TypedCall = <T extends MessageKey, R extends MessageKey>(
270
- type: T,
271
- resType: R,
272
- message?: MessageType[T],
273
- ) => Promise<MessageResponse<R>>;
464
+ export type MessageResponseMap = {
465
+ [K in MessageKey]: MessageResponse<K>;
466
+ };
467
+
468
+ export type TypedCall = {
469
+ <T extends MessageKey, R extends readonly MessageKey[]>(
470
+ type: T,
471
+ resType: R,
472
+ message?: MessageType[T],
473
+ ): Promise<MessageResponseMap[R[number]]>;
474
+ <T extends MessageKey, R extends MessageKey>(
475
+ type: T,
476
+ resType: R,
477
+ message?: MessageType[T],
478
+ ): Promise<MessageResponse<R>>;
479
+ };
274
480
  `);
275
481
  }
276
482
 
package/src/constants.ts CHANGED
@@ -1,8 +1,44 @@
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 标记,ASCII '?'。 */
4
+ export const PROTOCOL_V1_REPORT_ID = 0x3f;
5
+
6
+ /** Protocol V1 envelope 头部标记,ASCII '#'。 */
7
+ export const PROTOCOL_V1_HEADER_BYTE = 0x23;
8
+
9
+ /** Protocol V1 单个 chunk 去掉 report 标记后的可用 payload 长度。 */
10
+ export const PROTOCOL_V1_CHUNK_PAYLOAD_SIZE = 63;
11
+
12
+ /** Protocol V1 USB packet 长度:report 标记 + chunk payload。 */
13
+ export const PROTOCOL_V1_USB_PACKET_SIZE = PROTOCOL_V1_CHUNK_PAYLOAD_SIZE + 1;
14
+
15
+ /** Protocol V1 message metadata:message type 2 字节 + payload length 4 字节。 */
16
+ export const PROTOCOL_V1_MESSAGE_HEADER_SIZE = 2 + 4;
17
+
18
+ /** Protocol V1 envelope metadata:## + message type + 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
+ /** Protocol V2 单帧最大长度,包含 header、payload 和 CRC;需容纳 4096 数据块及 protobuf 开销。 */
24
+ export const PROTOCOL_V2_FRAME_MAX_BYTES = 4608;
25
+
26
+ /** WebUSB 下 FilesystemFileWrite 的文件数据分块大小。 */
27
+ export const PROTOCOL_V2_WEBUSB_FILE_CHUNK_SIZE = 4096;
28
+
29
+ /** BLE 下 FilesystemFileWrite 的文件数据分块大小。 */
30
+ export const PROTOCOL_V2_BLE_FILE_CHUNK_SIZE = 1800;
31
+
32
+ /** @deprecated 使用按传输区分的 PROTOCOL_V2_WEBUSB_FILE_CHUNK_SIZE / PROTOCOL_V2_BLE_FILE_CHUNK_SIZE。 */
33
+ export const PROTOCOL_V2_FILE_CHUNK_SIZE = PROTOCOL_V2_WEBUSB_FILE_CHUNK_SIZE;
34
+
5
35
  /**
6
- * exclude ?##
36
+ * Protocol V2 路由 channel。
37
+ * USB 端点直接到主 MCU,不需要 proto-link 路由;BLE 需要经过 BLE 协处理器 UART bridge。
7
38
  */
8
- export const COMMON_HEADER_SIZE = 6;
39
+ export const PROTOCOL_V2_CHANNEL_USB = 0;
40
+ export const PROTOCOL_V2_CHANNEL_BLE_UART = 1;
41
+ export const PROTOCOL_V2_CHANNEL_SOCKET = 2;
42
+
43
+ /** protobuf 命令流的 packet_src,固件侧会把 0 路由到 protobuf dispatcher。 */
44
+ export const PROTOCOL_V2_PACKET_SRC_COMMAND = 0;
package/src/index.ts CHANGED
@@ -1,14 +1,25 @@
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 {
14
+ ProtocolV2FrameAssembler,
15
+ ProtocolV2Session,
16
+ bytesToHex,
17
+ concatUint8Arrays,
18
+ getErrorMessage,
19
+ hexToBytes,
20
+ probeProtocolV2,
21
+ withProtocolTimeout,
22
+ } from './protocols/v2/session';
12
23
  import * as check from './utils/highlevel-checks';
13
24
 
14
25
  protobuf.util.Long = Long;
@@ -21,10 +32,12 @@ export type {
21
32
  OneKeyMobileDeviceInfo,
22
33
  OneKeyDeviceInfoWithSession,
23
34
  MessageFromOneKey,
35
+ TransportCallOptions,
24
36
  LowlevelTransportSharedPlugin,
25
37
  LowLevelDevice,
26
38
  OneKeyDeviceInfoBase,
27
39
  OneKeyDeviceCommType,
40
+ ProtocolType,
28
41
  } from './types';
29
42
 
30
43
  export { Messages } from './types';
@@ -32,13 +45,28 @@ export * from './types/messages';
32
45
  export * from './utils/logBlockCommand';
33
46
 
34
47
  export * from './constants';
48
+ export * from './protocols';
49
+ export * as protocolV1 from './protocols/v1';
50
+ export * as protocolV2 from './protocols/v2';
51
+ export * from './protocols/v2/session';
35
52
 
36
53
  export default {
37
54
  check,
38
- buildOne,
39
- buildBuffers,
40
- buildEncodeBuffers,
41
- receiveOne,
42
55
  parseConfigure,
43
- decodeProtocol,
56
+ protocolV2: protocolV2Codec,
57
+ ProtocolV1,
58
+ ProtocolV2,
59
+ PROTOCOL_V2_SYS_MESSAGE_THRESHOLD,
60
+ ProtocolV2FrameAssembler,
61
+ ProtocolV2Session,
62
+ bytesToHex,
63
+ concatUint8Arrays,
64
+ createMessageFromName,
65
+ createMessageFromType,
66
+ encodeProtobuf,
67
+ decodeProtobuf,
68
+ getErrorMessage,
69
+ hexToBytes,
70
+ probeProtocolV2,
71
+ withProtocolTimeout,
44
72
  };
@@ -0,0 +1,144 @@
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 } from './v2';
10
+
11
+ import type { Root } from 'protobufjs/light';
12
+ import type { ProtocolV2DebugLogger } from './v2';
13
+
14
+ export const PROTOCOL_V2_SYS_MESSAGE_THRESHOLD = 60000;
15
+
16
+ type ProtocolV2Schemas = {
17
+ protocolV1: Root;
18
+ protocolV2: Root;
19
+ };
20
+
21
+ type ProtocolV2FrameOptions = {
22
+ packetSrc?: number;
23
+ router?: number;
24
+ /** Sequence number (1-255). Managed per-session by ProtocolV2Session. */
25
+ seq?: number;
26
+ logger?: ProtocolV2DebugLogger;
27
+ logPrefix?: string;
28
+ context?: string;
29
+ };
30
+
31
+ const resolveProtocolV2EncodeSchema = (name: string, schemas: ProtocolV2Schemas) => {
32
+ try {
33
+ schemas.protocolV2.lookupType(name);
34
+ return schemas.protocolV2;
35
+ } catch {
36
+ throw new Error(`Protocol V2 message "${name}" is not defined in Protocol V2 schema`);
37
+ }
38
+ };
39
+
40
+ const PROTOCOL_V2_LEGACY_DECODE_ALLOWLIST = new Set([
41
+ 'ButtonRequest',
42
+ 'EntropyRequest',
43
+ 'PinMatrixRequest',
44
+ 'PassphraseRequest',
45
+ 'Deprecated_PassphraseStateRequest',
46
+ 'WordRequest',
47
+ ]);
48
+
49
+ const createProtocolV2MessageFromType = (messageTypeId: number, schemas: ProtocolV2Schemas) => {
50
+ try {
51
+ return createMessageFromType(schemas.protocolV2, messageTypeId);
52
+ } catch (protocolV2Error) {
53
+ let legacyMessage: ReturnType<typeof createMessageFromType>;
54
+ try {
55
+ legacyMessage = createMessageFromType(schemas.protocolV1, messageTypeId);
56
+ } catch {
57
+ throw protocolV2Error;
58
+ }
59
+ if (PROTOCOL_V2_LEGACY_DECODE_ALLOWLIST.has(legacyMessage.messageName)) {
60
+ return legacyMessage;
61
+ }
62
+ throw protocolV2Error;
63
+ }
64
+ };
65
+
66
+ export const ProtocolV1 = {
67
+ encodeEnvelope: encodeEnvelopeMessage,
68
+ encodeMessageChunks,
69
+ encodeTransportPackets,
70
+ decodeFirstChunk,
71
+
72
+ decodeMessage: decodeV1Message,
73
+ };
74
+
75
+ export const ProtocolV2 = {
76
+ encodeFrame(
77
+ schemas: ProtocolV2Schemas,
78
+ name: string,
79
+ data: Record<string, unknown>,
80
+ options: ProtocolV2FrameOptions = {}
81
+ ) {
82
+ const encodeMessages = resolveProtocolV2EncodeSchema(name, schemas);
83
+ const { Message, messageTypeId } = createMessageFromName(encodeMessages, name);
84
+ const pbBuffer = encodeProtobuf(Message, data);
85
+ pbBuffer.reset();
86
+ const rawPbBuffer = pbBuffer.toBuffer() as unknown as ArrayBuffer;
87
+ const pbBytes = new Uint8Array(rawPbBuffer);
88
+
89
+ options.logger?.debug?.(`[${options.logPrefix ?? 'ProtocolV2'}] encode protobuf`, {
90
+ context: options.context ?? `encode:${name}`,
91
+ messageName: name,
92
+ messageTypeId,
93
+ pbPayloadLength: pbBytes.length,
94
+ packetSrc: options.packetSrc ?? 0,
95
+ router: options.router ?? 0,
96
+ });
97
+
98
+ return encodeProtobufFrame(
99
+ messageTypeId,
100
+ pbBytes,
101
+ options.packetSrc,
102
+ options.router,
103
+ {
104
+ logger: options.logger,
105
+ logPrefix: options.logPrefix,
106
+ context: options.context ?? `encode:${name}`,
107
+ messageName: name,
108
+ },
109
+ options.seq
110
+ );
111
+ },
112
+
113
+ decodeFrame(
114
+ schemas: ProtocolV2Schemas,
115
+ frame: Uint8Array,
116
+ options: Pick<ProtocolV2FrameOptions, 'logger' | 'logPrefix' | 'context'> = {}
117
+ ) {
118
+ const { messageTypeId, pbPayload, seq } = decodeV2Frame(frame, {
119
+ logger: options.logger,
120
+ logPrefix: options.logPrefix,
121
+ context: options.context ?? 'decode',
122
+ });
123
+ const { Message, messageName } = createProtocolV2MessageFromType(messageTypeId, schemas);
124
+ const rxByteBuffer = ByteBuffer.wrap(Buffer.from(pbPayload) as unknown as ArrayBuffer);
125
+ const message = decodeProtobuf(Message, rxByteBuffer);
126
+
127
+ options.logger?.debug?.(`[${options.logPrefix ?? 'ProtocolV2'}] decode protobuf`, {
128
+ context: options.context ?? `decode:${messageName}`,
129
+ messageName,
130
+ messageTypeId,
131
+ pbPayloadLength: pbPayload.length,
132
+ seq,
133
+ });
134
+
135
+ return {
136
+ message,
137
+ messageName,
138
+ messageTypeId,
139
+ pbPayload,
140
+ seq,
141
+ type: messageName,
142
+ };
143
+ },
144
+ };
@@ -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
 
@@ -1,30 +1,35 @@
1
1
  import ByteBuffer from 'bytebuffer';
2
2
 
3
- import { BUFFER_SIZE, HEADER_SIZE, MESSAGE_HEADER_BYTE } from '../../constants';
3
+ import {
4
+ PROTOCOL_V1_CHUNK_PAYLOAD_SIZE,
5
+ PROTOCOL_V1_ENVELOPE_HEADER_SIZE,
6
+ PROTOCOL_V1_HEADER_BYTE,
7
+ } from '../../constants';
4
8
 
5
9
  type Options<Chunked> = {
6
10
  chunked: Chunked;
7
11
  addTrezorHeaders: boolean;
8
- messageType: number;
12
+ messageTypeId: number;
9
13
  };
10
14
 
11
- function encode(data: ByteBuffer, options: Options<true>): Buffer[];
12
- function encode(data: ByteBuffer, options: Options<false>): Buffer;
13
- function encode(data: any, options: any): any {
14
- const { addTrezorHeaders, chunked, messageType } = options;
15
- // eslint-disable-next-line @typescript-eslint/restrict-plus-operands
16
- const fullSize = (addTrezorHeaders ? HEADER_SIZE : HEADER_SIZE - 2) + data.limit;
15
+ function encodeEnvelope(data: ByteBuffer, options: Options<true>): Buffer[];
16
+ function encodeEnvelope(data: ByteBuffer, options: Options<false>): Buffer;
17
+ function encodeEnvelope(data: any, options: any): any {
18
+ const { addTrezorHeaders, chunked, messageTypeId } = options;
19
+ const fullSize =
20
+ (addTrezorHeaders ? PROTOCOL_V1_ENVELOPE_HEADER_SIZE : PROTOCOL_V1_ENVELOPE_HEADER_SIZE - 2) +
21
+ Number(data.limit);
17
22
 
18
23
  const encodedByteBuffer = new ByteBuffer(fullSize);
19
24
 
20
25
  if (addTrezorHeaders) {
21
26
  // 2*1 byte
22
- encodedByteBuffer.writeByte(MESSAGE_HEADER_BYTE);
23
- encodedByteBuffer.writeByte(MESSAGE_HEADER_BYTE);
27
+ encodedByteBuffer.writeByte(PROTOCOL_V1_HEADER_BYTE);
28
+ encodedByteBuffer.writeByte(PROTOCOL_V1_HEADER_BYTE);
24
29
  }
25
30
 
26
31
  // 2 bytes
27
- encodedByteBuffer.writeUint16(messageType);
32
+ encodedByteBuffer.writeUint16(messageTypeId);
28
33
 
29
34
  // 4 bytes (so 8 in total)
30
35
  encodedByteBuffer.writeUint32(data.limit);
@@ -39,7 +44,7 @@ function encode(data: any, options: any): any {
39
44
  }
40
45
 
41
46
  const result: Buffer[] = [];
42
- const size = BUFFER_SIZE;
47
+ const size = PROTOCOL_V1_CHUNK_PAYLOAD_SIZE;
43
48
 
44
49
  // How many pieces will there actually be
45
50
  const count = Math.floor((encodedByteBuffer.limit - 1) / size) + 1 || 1;
@@ -56,4 +61,4 @@ function encode(data: any, options: any): any {
56
61
  return result;
57
62
  }
58
63
 
59
- export { encode };
64
+ export { encodeEnvelope };