@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.
- package/README.md +2 -4
- package/__tests__/build-receive.test.js +6 -8
- package/__tests__/decode-features.test.js +3 -2
- package/__tests__/messages.test.js +8 -0
- package/__tests__/protocol-v2.test.js +754 -0
- package/dist/constants.d.ts +14 -5
- package/dist/constants.d.ts.map +1 -1
- package/dist/index.d.ts +934 -41
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +827 -84
- package/dist/protocols/index.d.ts +45 -0
- package/dist/protocols/index.d.ts.map +1 -0
- package/dist/protocols/v1/decode.d.ts +11 -0
- package/dist/protocols/v1/decode.d.ts.map +1 -0
- package/dist/protocols/v1/encode.d.ts +11 -0
- package/dist/protocols/v1/encode.d.ts.map +1 -0
- package/dist/protocols/v1/index.d.ts +5 -0
- package/dist/protocols/v1/index.d.ts.map +1 -0
- package/dist/protocols/v1/packets.d.ts +7 -0
- package/dist/protocols/v1/packets.d.ts.map +1 -0
- package/dist/{serialization → protocols/v1}/receive.d.ts +1 -1
- package/dist/protocols/v1/receive.d.ts.map +1 -0
- package/dist/protocols/v2/constants.d.ts +7 -0
- package/dist/protocols/v2/constants.d.ts.map +1 -0
- package/dist/protocols/v2/crc8.d.ts +3 -0
- package/dist/protocols/v2/crc8.d.ts.map +1 -0
- package/dist/protocols/v2/debug.d.ts +13 -0
- package/dist/protocols/v2/debug.d.ts.map +1 -0
- package/dist/protocols/v2/decode.d.ts +8 -0
- package/dist/protocols/v2/decode.d.ts.map +1 -0
- package/dist/protocols/v2/encode.d.ts +5 -0
- package/dist/protocols/v2/encode.d.ts.map +1 -0
- package/dist/protocols/v2/frame-assembler.d.ts +12 -0
- package/dist/protocols/v2/frame-assembler.d.ts.map +1 -0
- package/dist/protocols/v2/index.d.ts +7 -0
- package/dist/protocols/v2/index.d.ts.map +1 -0
- package/dist/protocols/v2/session.d.ts +50 -0
- package/dist/protocols/v2/session.d.ts.map +1 -0
- package/dist/serialization/index.d.ts +6 -3
- package/dist/serialization/index.d.ts.map +1 -1
- package/dist/serialization/protobuf/decode.d.ts.map +1 -1
- package/dist/serialization/protobuf/messages.d.ts +1 -1
- package/dist/serialization/protobuf/messages.d.ts.map +1 -1
- package/dist/types/messages.d.ts +461 -11
- package/dist/types/messages.d.ts.map +1 -1
- package/dist/types/transport.d.ts +14 -2
- package/dist/types/transport.d.ts.map +1 -1
- package/dist/utils/logBlockCommand.d.ts.map +1 -1
- package/messages-protocol-v2.json +13369 -0
- package/package.json +3 -3
- package/scripts/protobuf-build.sh +314 -20
- package/scripts/protobuf-patches/TxInputType.js +1 -0
- package/scripts/protobuf-patches/index.js +2 -0
- package/scripts/protobuf-types.js +224 -18
- package/src/constants.ts +42 -6
- package/src/index.ts +39 -11
- package/src/protocols/index.ts +144 -0
- package/src/{serialization/protocol → protocols/v1}/decode.ts +4 -4
- package/src/{serialization/protocol → protocols/v1}/encode.ts +18 -13
- package/src/protocols/v1/index.ts +4 -0
- package/src/protocols/v1/packets.ts +53 -0
- package/src/{serialization → protocols/v1}/receive.ts +5 -5
- package/src/protocols/v2/constants.ts +6 -0
- package/src/protocols/v2/crc8.ts +34 -0
- package/src/protocols/v2/debug.ts +26 -0
- package/src/protocols/v2/decode.ts +92 -0
- package/src/protocols/v2/encode.ts +116 -0
- package/src/protocols/v2/frame-assembler.ts +98 -0
- package/src/protocols/v2/index.ts +6 -0
- package/src/protocols/v2/session.ts +429 -0
- package/src/serialization/index.ts +6 -5
- package/src/serialization/protobuf/decode.ts +7 -0
- package/src/serialization/protobuf/messages.ts +8 -4
- package/src/types/messages.ts +606 -13
- package/src/types/transport.ts +26 -2
- package/src/utils/logBlockCommand.ts +9 -1
- package/dist/serialization/protocol/decode.d.ts +0 -11
- package/dist/serialization/protocol/decode.d.ts.map +0 -1
- package/dist/serialization/protocol/encode.d.ts +0 -11
- package/dist/serialization/protocol/encode.d.ts.map +0 -1
- package/dist/serialization/protocol/index.d.ts +0 -3
- package/dist/serialization/protocol/index.d.ts.map +0 -1
- package/dist/serialization/receive.d.ts.map +0 -1
- package/dist/serialization/send.d.ts +0 -7
- package/dist/serialization/send.d.ts.map +0 -1
- package/src/serialization/protocol/index.ts +0 -2
- 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 =>
|
|
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 =
|
|
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).
|
|
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<${
|
|
411
|
+
lines.push(` ${t.name}: $Exact<${messageTypeValue}>;`);
|
|
228
412
|
} else {
|
|
229
|
-
lines.push(` ${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 =
|
|
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
|
-
)
|
|
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
|
-
|
|
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
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
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
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
export const
|
|
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
|
-
*
|
|
36
|
+
* Protocol V2 路由 channel。
|
|
37
|
+
* USB 端点直接到主 MCU,不需要 proto-link 路由;BLE 需要经过 BLE 协处理器 UART bridge。
|
|
7
38
|
*/
|
|
8
|
-
export const
|
|
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
|
|
2
|
+
import Long from 'long';
|
|
3
3
|
|
|
4
4
|
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
|
-
|
|
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 {
|
|
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
|
|
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
|
|
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 !==
|
|
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 {
|
|
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
|
-
|
|
12
|
+
messageTypeId: number;
|
|
9
13
|
};
|
|
10
14
|
|
|
11
|
-
function
|
|
12
|
-
function
|
|
13
|
-
function
|
|
14
|
-
const { addTrezorHeaders, chunked,
|
|
15
|
-
|
|
16
|
-
|
|
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(
|
|
23
|
-
encodedByteBuffer.writeByte(
|
|
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(
|
|
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 =
|
|
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 {
|
|
64
|
+
export { encodeEnvelope };
|