@onekeyfe/hd-transport 1.2.0-alpha.1 → 1.2.0-alpha.100

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 (73) hide show
  1. package/README.md +1 -1
  2. package/__tests__/messages.test.js +228 -0
  3. package/__tests__/protocol-v2-ble-frame-writer.test.js +119 -0
  4. package/__tests__/protocol-v2-link-manager.test.js +437 -0
  5. package/__tests__/protocol-v2-usb-transport-base.test.js +330 -0
  6. package/__tests__/protocol-v2.test.js +661 -119
  7. package/__tests__/transport-log.test.js +79 -0
  8. package/dist/constants.d.ts +7 -3
  9. package/dist/constants.d.ts.map +1 -1
  10. package/dist/index.d.ts +1068 -348
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +909 -260
  13. package/dist/protocols/index.d.ts +14 -5
  14. package/dist/protocols/index.d.ts.map +1 -1
  15. package/dist/protocols/v2/ble-frame-writer.d.ts +15 -0
  16. package/dist/protocols/v2/ble-frame-writer.d.ts.map +1 -0
  17. package/dist/protocols/v2/constants.d.ts +1 -0
  18. package/dist/protocols/v2/constants.d.ts.map +1 -1
  19. package/dist/protocols/v2/decode.d.ts +13 -2
  20. package/dist/protocols/v2/decode.d.ts.map +1 -1
  21. package/dist/protocols/v2/encode.d.ts +2 -3
  22. package/dist/protocols/v2/encode.d.ts.map +1 -1
  23. package/dist/protocols/v2/errors.d.ts +8 -0
  24. package/dist/protocols/v2/errors.d.ts.map +1 -0
  25. package/dist/protocols/v2/frame-assembler.d.ts.map +1 -1
  26. package/dist/protocols/v2/index.d.ts +2 -1
  27. package/dist/protocols/v2/index.d.ts.map +1 -1
  28. package/dist/protocols/v2/link-manager.d.ts +39 -0
  29. package/dist/protocols/v2/link-manager.d.ts.map +1 -0
  30. package/dist/protocols/v2/sequence-cursor.d.ts +5 -0
  31. package/dist/protocols/v2/sequence-cursor.d.ts.map +1 -0
  32. package/dist/protocols/v2/session.d.ts +24 -6
  33. package/dist/protocols/v2/session.d.ts.map +1 -1
  34. package/dist/protocols/v2/usb-transport-base.d.ts +32 -0
  35. package/dist/protocols/v2/usb-transport-base.d.ts.map +1 -0
  36. package/dist/serialization/protobuf/decode.d.ts.map +1 -1
  37. package/dist/serialization/protobuf/encode.d.ts.map +1 -1
  38. package/dist/serialization/protobuf/messages.d.ts.map +1 -1
  39. package/dist/types/messages.d.ts +562 -208
  40. package/dist/types/messages.d.ts.map +1 -1
  41. package/dist/types/transport.d.ts +21 -4
  42. package/dist/types/transport.d.ts.map +1 -1
  43. package/dist/utils/transportLog.d.ts +9 -0
  44. package/dist/utils/transportLog.d.ts.map +1 -0
  45. package/messages-protocol-v2.json +904 -791
  46. package/package.json +3 -3
  47. package/scripts/protobuf-build.sh +96 -310
  48. package/scripts/protobuf-patches/index.js +1 -0
  49. package/scripts/protobuf-types.js +19 -1
  50. package/src/constants.ts +35 -15
  51. package/src/index.ts +13 -1
  52. package/src/protocols/index.ts +40 -40
  53. package/src/protocols/v2/ble-frame-writer.ts +77 -0
  54. package/src/protocols/v2/constants.ts +1 -0
  55. package/src/protocols/v2/crc8.ts +1 -1
  56. package/src/protocols/v2/decode.ts +71 -37
  57. package/src/protocols/v2/encode.ts +2 -28
  58. package/src/protocols/v2/errors.ts +25 -0
  59. package/src/protocols/v2/frame-assembler.ts +6 -4
  60. package/src/protocols/v2/index.ts +2 -1
  61. package/src/protocols/v2/link-manager.ts +209 -0
  62. package/src/protocols/v2/sequence-cursor.ts +10 -0
  63. package/src/protocols/v2/session.ts +187 -201
  64. package/src/protocols/v2/usb-transport-base.ts +213 -0
  65. package/src/serialization/protobuf/decode.ts +4 -1
  66. package/src/serialization/protobuf/encode.ts +5 -1
  67. package/src/serialization/protobuf/messages.ts +6 -1
  68. package/src/types/messages.ts +672 -245
  69. package/src/types/transport.ts +29 -4
  70. package/src/utils/transportLog.ts +102 -0
  71. package/dist/protocols/v2/debug.d.ts +0 -13
  72. package/dist/protocols/v2/debug.d.ts.map +0 -1
  73. package/src/protocols/v2/debug.ts +0 -26
package/README.md CHANGED
@@ -20,7 +20,7 @@ In order to be able to use new features of onekey-firmware you need to update pr
20
20
  1. `yarn update-submodules` to update firmware submodule
21
21
  1. `yarn update-protobuf` to generate new `./messages.json`, `./messages-protocol-v2.json` and `./src/types/messages.ts`
22
22
 
23
- The same task can be run from the repository root with `yarn update-protobuf`. The Protocol V2 schema requires the `firmware-pro2` submodule checked out on branch `dev_romloader_split`.
23
+ The same task can be run from the repository root with `yarn update-protobuf`. The Protocol V2 schema requires the `firmware-pro2` submodule checked out on branch `dev`.
24
24
 
25
25
  ## Docs
26
26
 
@@ -3,6 +3,12 @@ const {
3
3
  createMessageFromType,
4
4
  parseConfigure,
5
5
  } = require('../src/serialization/protobuf/messages');
6
+ const { encode } = require('../src/serialization/protobuf/encode');
7
+ const v1Messages = require('../../core/src/data/messages/messages.json');
8
+ const v2Messages = require('../messages-protocol-v2.json');
9
+ const coreV2Messages = require('../../core/src/data/messages/messages-protocol-v2.json');
10
+ const generatedTypes = require('../src/types/messages');
11
+
6
12
  // const json = require('./data/messages.json');
7
13
 
8
14
  const json = {
@@ -58,6 +64,220 @@ const json = {
58
64
  };
59
65
 
60
66
  describe('messages', () => {
67
+ test('V1 GetPassphraseState matches the current firmware schema', () => {
68
+ const { fields } = v1Messages.nested.GetPassphraseState;
69
+
70
+ expect(fields.passphrase_state).toMatchObject({ id: 1, type: 'string' });
71
+ expect(fields).not.toHaveProperty('_only_main_pin');
72
+ expect(fields).not.toHaveProperty('allow_create_attach_pin');
73
+ });
74
+
75
+ test('Protocol V2 firmware targets match the current firmware-pro2 enum', () => {
76
+ expect(v2Messages.nested.DeviceFirmwareTargetType.values).toEqual({
77
+ FW_MGMT_TARGET_INVALID: 0,
78
+ FW_MGMT_TARGET_CRATE: 1,
79
+ FW_MGMT_TARGET_ROMLOADER: 2,
80
+ FW_MGMT_TARGET_BOOTLOADER: 3,
81
+ FW_MGMT_TARGET_APPLICATION_P1: 4,
82
+ FW_MGMT_TARGET_APPLICATION_P2: 5,
83
+ FW_MGMT_TARGET_COPROCESSOR: 6,
84
+ FW_MGMT_TARGET_SE01: 7,
85
+ FW_MGMT_TARGET_SE02: 8,
86
+ FW_MGMT_TARGET_SE03: 9,
87
+ FW_MGMT_TARGET_SE04: 10,
88
+ });
89
+ });
90
+
91
+ test('Protocol V2 conflicting enums keep their own wire values', () => {
92
+ expect(generatedTypes.ProtocolV2FailureType).toMatchObject({
93
+ Failure_DataError: 4,
94
+ Failure_ProcessError: 5,
95
+ });
96
+ expect(generatedTypes.Enum_ProtocolV2Capability).toMatchObject({
97
+ Capability_AttachToPin: 18,
98
+ });
99
+ });
100
+
101
+ test('Protocol V2 device status and session messages match firmware-pro2', () => {
102
+ expect(v2Messages.nested.MessageType.values).toMatchObject({
103
+ MessageType_DeviceStatusGet: 60602,
104
+ MessageType_DeviceStatus: 60603,
105
+ MessageType_DeviceSessionGet: 61200,
106
+ MessageType_DeviceSession: 61201,
107
+ MessageType_DeviceSessionAskPin: 61202,
108
+ MessageType_DeviceSessionAskPassphrase: 61203,
109
+ });
110
+ expect(v2Messages.nested.DeviceStatusGet).toEqual({ fields: {} });
111
+ expect(v2Messages.nested.ProtocolInfoRequest.fields.eventless_wallet_session).toMatchObject({
112
+ id: 1,
113
+ type: 'bool',
114
+ options: { default: false },
115
+ });
116
+ expect(v2Messages.nested.ProtocolInfo.fields).toMatchObject({
117
+ version: { id: 1, type: 'uint32', rule: 'required' },
118
+ build_fingerprint: { id: 2, type: 'string', rule: 'required' },
119
+ supported_messages: { id: 3, type: 'uint32', rule: 'repeated' },
120
+ });
121
+ expect(v2Messages.nested.DeviceSessionGet.fields.session_id).toMatchObject({
122
+ id: 1,
123
+ type: 'bytes',
124
+ });
125
+ expect(v2Messages.nested.DeviceSessionGet.fields).toEqual({
126
+ session_id: { id: 1, type: 'bytes' },
127
+ btc_test_address: { id: 2, type: 'string' },
128
+ seed_domains: {
129
+ id: 3,
130
+ rule: 'repeated',
131
+ type: 'DeviceSessionSeedDomain',
132
+ },
133
+ });
134
+ expect(v2Messages.nested.DeviceSessionSeedDomain.values).toEqual({
135
+ SeedDomain_Standard: 1,
136
+ SeedDomain_Cardano: 2,
137
+ });
138
+ expect(v2Messages.nested.DeviceSessionPinType.values).toEqual({
139
+ Any: 1,
140
+ Main: 2,
141
+ AttachToPin: 3,
142
+ });
143
+ expect(v2Messages.nested.DeviceSessionErrorCode.values).toEqual({
144
+ DeviceSessionError_None: 0,
145
+ DeviceSessionError_UserCancelled: 1,
146
+ DeviceSessionError_InvalidSession: 2,
147
+ DeviceSessionError_AttachPinUnavailable: 3,
148
+ DeviceSessionError_PassphraseDisabled: 4,
149
+ DeviceSessionError_Busy: 5,
150
+ });
151
+ expect(v2Messages.nested).not.toHaveProperty('DeviceWalletSelect');
152
+ expect(v2Messages.nested).not.toHaveProperty('DeviceWalletType');
153
+ expect(v2Messages.nested).not.toHaveProperty('DeviceHiddenWalletSelect');
154
+ expect(v2Messages.nested.DeviceSession.fields).toMatchObject({
155
+ session_id: { id: 1, type: 'bytes' },
156
+ btc_test_address: { id: 2, type: 'string' },
157
+ });
158
+ expect(v2Messages.nested.DeviceSessionAskPin.fields.type).toMatchObject({
159
+ id: 1,
160
+ type: 'DeviceSessionPinType',
161
+ });
162
+ expect(v2Messages.nested.DeviceSessionAskPassphrase).toEqual({
163
+ fields: {
164
+ passphrase: {
165
+ type: 'string',
166
+ id: 1,
167
+ },
168
+ on_device: {
169
+ rule: 'required',
170
+ type: 'bool',
171
+ id: 2,
172
+ },
173
+ },
174
+ });
175
+ expect(v2Messages.nested.DeviceSessionAskPin_FailureSubCodes.values).toEqual({
176
+ UserCancel: 1,
177
+ });
178
+ expect(v2Messages.nested.MessageType.values).not.toHaveProperty(
179
+ 'MessageType_DeviceSessionPinResult'
180
+ );
181
+ expect(v2Messages.nested.MessageType.values).not.toHaveProperty(
182
+ 'MessageType_DeviceSessionOpen'
183
+ );
184
+ });
185
+
186
+ test('Protocol V2 passphrase selection explicitly selects host or device input on wire', () => {
187
+ const messages = parseConfigure(v2Messages);
188
+ const { Message } = createMessageFromName(messages, 'DeviceSessionAskPassphrase');
189
+
190
+ const standardWallet = encode(Message, { passphrase: '', on_device: false });
191
+ const onHost = Message.encode(
192
+ Message.create({ passphrase: 'host hidden wallet', on_device: false })
193
+ ).finish();
194
+ const onDevice = Message.encode(Message.create({ on_device: true })).finish();
195
+
196
+ expect(standardWallet.toString('hex')).toBe('0a001000');
197
+ expect(Buffer.from(onHost).toString('hex')).toBe(
198
+ '0a12686f73742068696464656e2077616c6c65741000'
199
+ );
200
+ expect(Buffer.from(onDevice).toString('hex')).toBe('1001');
201
+ expect(Message.decode(onHost)).toMatchObject({
202
+ passphrase: 'host hidden wallet',
203
+ on_device: false,
204
+ });
205
+ expect(Message.decode(onDevice)).toMatchObject({ on_device: true });
206
+ });
207
+
208
+ test('Protocol V2 wallet recovery carries the expected wallet and seed domains on wire', () => {
209
+ const messages = parseConfigure(v2Messages);
210
+ const { Message } = createMessageFromName(messages, 'DeviceSessionGet');
211
+ const payload = encode(Message, {
212
+ btc_test_address: 'tb1qwallet',
213
+ seed_domains: [
214
+ generatedTypes.DeviceSessionSeedDomain.SeedDomain_Standard,
215
+ generatedTypes.DeviceSessionSeedDomain.SeedDomain_Cardano,
216
+ ],
217
+ });
218
+
219
+ expect(payload.toString('hex')).toBe('120a7462317177616c6c65741a020102');
220
+ expect(Message.decode(payload.toBuffer())).toMatchObject({
221
+ btc_test_address: 'tb1qwallet',
222
+ seed_domains: [
223
+ generatedTypes.DeviceSessionSeedDomain.SeedDomain_Standard,
224
+ generatedTypes.DeviceSessionSeedDomain.SeedDomain_Cardano,
225
+ ],
226
+ });
227
+ });
228
+
229
+ test('Protocol V2 onboarding status matches the current firmware-pro2 schema', () => {
230
+ expect(v2Messages.nested.MessageType.values).toMatchObject({
231
+ MessageType_OnboardingStatusGet: 61600,
232
+ MessageType_OnboardingStatus: 61601,
233
+ });
234
+ expect(v2Messages.nested.OnboardingStep.values).toMatchObject({
235
+ ONBOARDING_STEP_UNKNOWN: 0,
236
+ ONBOARDING_STEP_CHECKING: 1,
237
+ ONBOARDING_STEP_PERSONALIZATION: 2,
238
+ ONBOARDING_STEP_PIN: 3,
239
+ ONBOARDING_STEP_SETUP: 4,
240
+ ONBOARDING_STEP_DONE: 5,
241
+ });
242
+ expect(v2Messages.nested.OnboardingPhase.values).toBeDefined();
243
+ expect(v2Messages.nested.OnboardingSetupKind.values).toBeDefined();
244
+ expect(v2Messages.nested.OnboardingSetupMethod.values).toBeDefined();
245
+ expect(v2Messages.nested.OnboardingSetupStatus.fields).toMatchObject({
246
+ kind: { id: 1, type: 'OnboardingSetupKind' },
247
+ method: { id: 2, type: 'OnboardingSetupMethod' },
248
+ });
249
+ expect(v2Messages.nested.OnboardingStatus.fields).toMatchObject({
250
+ step: { id: 1, type: 'OnboardingStep' },
251
+ phase: { id: 2, type: 'OnboardingPhase' },
252
+ setup: { id: 3, type: 'OnboardingSetupStatus' },
253
+ pin_set: { id: 4, type: 'bool' },
254
+ wallet_initialized: { id: 5, type: 'bool' },
255
+ });
256
+ expect(v2Messages.nested).not.toHaveProperty('DevOnboardingStatus');
257
+ });
258
+
259
+ test('Protocol V2 NFT update matches the current firmware-pro2 schema', () => {
260
+ expect(v2Messages.nested.MessageType.values.MessageType_NftUpdate).toBe(61500);
261
+ expect(v2Messages.nested.NftUpdate.fields).toEqual({
262
+ file_name_no_ext: { id: 1, type: 'string', rule: 'required' },
263
+ });
264
+ });
265
+
266
+ test('Protocol V2 does not restore retired unlock or passphrase ids', () => {
267
+ expect(v2Messages.nested.MessageType.values).not.toHaveProperty('MessageType_UnLockDevice');
268
+ expect(v2Messages.nested.MessageType.values).not.toHaveProperty(
269
+ 'MessageType_UnLockDeviceResponse'
270
+ );
271
+ expect(v2Messages.nested.MessageType.values).not.toHaveProperty(
272
+ 'MessageType_GetPassphraseState'
273
+ );
274
+ expect(v2Messages.nested.MessageType.values).not.toHaveProperty('MessageType_PassphraseState');
275
+ });
276
+
277
+ test('Protocol V2 transport and core schemas stay identical', () => {
278
+ expect(coreV2Messages).toEqual(v2Messages);
279
+ });
280
+
61
281
  test('createMessageFromName (common case)', () => {
62
282
  const messages = parseConfigure(json);
63
283
  const name = 'Initialize';
@@ -70,6 +290,14 @@ describe('messages', () => {
70
290
  expect(() => createMessageFromType(messages, 0)).not.toThrow();
71
291
  });
72
292
 
293
+ test('createMessageFromType throws a readable error for unknown ids', () => {
294
+ const messages = parseConfigure(json);
295
+
296
+ expect(() => createMessageFromType(messages, 99999)).toThrow(
297
+ 'MessageType id "99999" is not defined in protobuf schema'
298
+ );
299
+ });
300
+
73
301
  test('createMessageFromName (wire_type case)', () => {
74
302
  const messages = parseConfigure(json);
75
303
  const name = 'TxAckInput';
@@ -0,0 +1,119 @@
1
+ /* eslint-disable @typescript-eslint/no-var-requires */
2
+ const { writeProtocolV2BleFrame } = require('../src');
3
+
4
+ describe('Protocol V2 BLE frame writer', () => {
5
+ test('splits one complete Protocol V2 frame without changing packet order', async () => {
6
+ const packets = [];
7
+
8
+ await writeProtocolV2BleFrame({
9
+ frame: new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]),
10
+ packetCapacity: 4,
11
+ writePacket: packet => {
12
+ packets.push(Array.from(packet));
13
+ return Promise.resolve();
14
+ },
15
+ });
16
+
17
+ expect(packets).toEqual([
18
+ [0, 1, 2, 3],
19
+ [4, 5, 6, 7],
20
+ [8, 9],
21
+ ]);
22
+ });
23
+
24
+ test('stops before the next packet when the connection generation changes', async () => {
25
+ const packets = [];
26
+ let active = true;
27
+
28
+ await expect(
29
+ writeProtocolV2BleFrame({
30
+ frame: new Uint8Array(9),
31
+ packetCapacity: 4,
32
+ assertActive: () => {
33
+ if (!active) throw new Error('stale BLE generation');
34
+ },
35
+ writePacket: packet => {
36
+ packets.push(packet);
37
+ active = false;
38
+ return Promise.resolve();
39
+ },
40
+ })
41
+ ).rejects.toThrow('stale BLE generation');
42
+
43
+ expect(packets).toHaveLength(1);
44
+ });
45
+
46
+ test('does not write when the call is already aborted', async () => {
47
+ const controller = new AbortController();
48
+ const writePacket = jest.fn();
49
+ controller.abort();
50
+
51
+ await expect(
52
+ writeProtocolV2BleFrame({
53
+ frame: new Uint8Array(4),
54
+ packetCapacity: 4,
55
+ signal: controller.signal,
56
+ abortMessage: 'Protocol V2 BLE write aborted for Ping',
57
+ writePacket,
58
+ })
59
+ ).rejects.toThrow('Protocol V2 BLE write aborted for Ping');
60
+
61
+ expect(writePacket).not.toHaveBeenCalled();
62
+ });
63
+
64
+ test('surfaces an abort that occurs while the final packet write is completing', async () => {
65
+ const controller = new AbortController();
66
+ const writePacket = jest.fn().mockImplementation(() => {
67
+ controller.abort();
68
+ return Promise.resolve();
69
+ });
70
+
71
+ await expect(
72
+ writeProtocolV2BleFrame({
73
+ frame: new Uint8Array(4),
74
+ packetCapacity: 4,
75
+ signal: controller.signal,
76
+ abortMessage: 'Protocol V2 BLE write aborted for Ping',
77
+ writePacket,
78
+ })
79
+ ).rejects.toThrow('Protocol V2 BLE write aborted for Ping');
80
+
81
+ expect(writePacket).toHaveBeenCalledTimes(1);
82
+ });
83
+
84
+ test('applies bounded burst pauses and one final flush delay', async () => {
85
+ const waits = [];
86
+ const writePacket = jest.fn().mockResolvedValue(undefined);
87
+
88
+ await writeProtocolV2BleFrame({
89
+ frame: new Uint8Array(10),
90
+ packetCapacity: 3,
91
+ initialDelayMs: 3,
92
+ burstSize: 2,
93
+ burstPauseMs: 7,
94
+ flushDelayMs: 11,
95
+ wait: ms => {
96
+ waits.push(ms);
97
+ return Promise.resolve();
98
+ },
99
+ writePacket,
100
+ });
101
+
102
+ expect(writePacket).toHaveBeenCalledTimes(4);
103
+ expect(waits).toEqual([3, 7, 11]);
104
+ });
105
+
106
+ test('rejects an invalid packet capacity before writing', async () => {
107
+ const writePacket = jest.fn();
108
+
109
+ await expect(
110
+ writeProtocolV2BleFrame({
111
+ frame: new Uint8Array(4),
112
+ packetCapacity: 0,
113
+ writePacket,
114
+ })
115
+ ).rejects.toThrow('Protocol V2 BLE packet capacity must be a positive integer');
116
+
117
+ expect(writePacket).not.toHaveBeenCalled();
118
+ });
119
+ });