@onekeyfe/hd-core 1.2.0-alpha.97 → 1.2.0-alpha.99

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 (103) hide show
  1. package/__tests__/check-all-firmware-release-protocol-v2.test.ts +411 -86
  2. package/__tests__/check-firmware-release-protocol-v2.test.ts +199 -0
  3. package/__tests__/firmware-memory-host.test.ts +126 -0
  4. package/__tests__/firmware-update/device-update-bootloader.test.ts +14 -2
  5. package/__tests__/firmware-update/firmware-host-binding.test.ts +9 -0
  6. package/__tests__/firmware-update/firmware-prepared-host-digest.test.ts +573 -0
  7. package/__tests__/firmware-update/firmware-prepared-resource-archive.test.ts +150 -0
  8. package/__tests__/firmware-update/firmware-update-bootloader-poll.test.ts +65 -2
  9. package/__tests__/firmware-update/firmware-update-plan-bootloader-identity.test.ts +80 -0
  10. package/__tests__/firmware-update/firmware-update-plan.test.ts +293 -71
  11. package/__tests__/firmware-update/firmware-update-prepared-plan.test.ts +53 -1
  12. package/__tests__/firmware-update/firmware-update-prepared-resource-executors.test.ts +326 -0
  13. package/__tests__/firmware-update/firmware-update-v2-download-before-boot.test.ts +2 -2
  14. package/__tests__/protocol-v2-firmware-targets.test.ts +16 -0
  15. package/__tests__/protocol-v2-resources.test.ts +148 -232
  16. package/__tests__/protocol-v2.test.ts +1326 -510
  17. package/dist/api/CheckAllFirmwareRelease.d.ts +2 -3
  18. package/dist/api/CheckAllFirmwareRelease.d.ts.map +1 -1
  19. package/dist/api/CheckBLEFirmwareRelease.d.ts +3 -2
  20. package/dist/api/CheckBLEFirmwareRelease.d.ts.map +1 -1
  21. package/dist/api/CheckBootloaderRelease.d.ts +2 -1
  22. package/dist/api/CheckBootloaderRelease.d.ts.map +1 -1
  23. package/dist/api/CheckFirmwareRelease.d.ts +3 -2
  24. package/dist/api/CheckFirmwareRelease.d.ts.map +1 -1
  25. package/dist/api/FirmwareUpdateV2.d.ts +1 -0
  26. package/dist/api/FirmwareUpdateV2.d.ts.map +1 -1
  27. package/dist/api/FirmwareUpdateV3.d.ts.map +1 -1
  28. package/dist/api/FirmwareUpdateV4.d.ts +11 -9
  29. package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
  30. package/dist/api/UploadPortfolio.d.ts.map +1 -1
  31. package/dist/api/device/DeviceUpdateBootloader.d.ts.map +1 -1
  32. package/dist/api/firmware/FirmwareHostBinding.d.ts +1 -1
  33. package/dist/api/firmware/FirmwareHostBinding.d.ts.map +1 -1
  34. package/dist/api/firmware/FirmwareMemoryHost.d.ts +22 -0
  35. package/dist/api/firmware/FirmwareMemoryHost.d.ts.map +1 -0
  36. package/dist/api/firmware/FirmwarePreparedResourceArchive.d.ts +11 -0
  37. package/dist/api/firmware/FirmwarePreparedResourceArchive.d.ts.map +1 -0
  38. package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts.map +1 -1
  39. package/dist/api/firmware/FirmwareUpdatePlan.d.ts +48 -1
  40. package/dist/api/firmware/FirmwareUpdatePlan.d.ts.map +1 -1
  41. package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts +7 -1
  42. package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts.map +1 -1
  43. package/dist/api/firmware/getBinary.d.ts +0 -1
  44. package/dist/api/firmware/getBinary.d.ts.map +1 -1
  45. package/dist/api/firmware/protocolV2Release.d.ts +33 -0
  46. package/dist/api/firmware/protocolV2Release.d.ts.map +1 -0
  47. package/dist/data-manager/DataManager.d.ts +4 -3
  48. package/dist/data-manager/DataManager.d.ts.map +1 -1
  49. package/dist/index.d.ts +108 -114
  50. package/dist/index.d.ts.map +1 -1
  51. package/dist/index.js +1825 -1006
  52. package/dist/protocols/protocol-v2/resources.d.ts +10 -28
  53. package/dist/protocols/protocol-v2/resources.d.ts.map +1 -1
  54. package/dist/types/api/checkAllFirmwareRelease.d.ts +20 -8
  55. package/dist/types/api/checkAllFirmwareRelease.d.ts.map +1 -1
  56. package/dist/types/api/checkBLEFirmwareRelease.d.ts +2 -13
  57. package/dist/types/api/checkBLEFirmwareRelease.d.ts.map +1 -1
  58. package/dist/types/api/checkBootloaderRelease.d.ts +2 -6
  59. package/dist/types/api/checkBootloaderRelease.d.ts.map +1 -1
  60. package/dist/types/api/checkFirmwareRelease.d.ts +2 -13
  61. package/dist/types/api/checkFirmwareRelease.d.ts.map +1 -1
  62. package/dist/types/api/checkFirmwareTypeAvailable.d.ts +2 -2
  63. package/dist/types/api/checkFirmwareTypeAvailable.d.ts.map +1 -1
  64. package/dist/types/api/deviceUpdateBootloader.d.ts.map +1 -1
  65. package/dist/types/api/export.d.ts +1 -1
  66. package/dist/types/api/export.d.ts.map +1 -1
  67. package/dist/types/api/firmwareUpdate.d.ts +4 -12
  68. package/dist/types/api/firmwareUpdate.d.ts.map +1 -1
  69. package/dist/types/api/firmwareUpdatePlan.d.ts +2 -2
  70. package/dist/types/api/firmwareUpdatePlan.d.ts.map +1 -1
  71. package/dist/types/settings.d.ts +35 -36
  72. package/dist/types/settings.d.ts.map +1 -1
  73. package/package.json +4 -4
  74. package/src/api/CheckAllFirmwareRelease.ts +113 -80
  75. package/src/api/CheckBLEFirmwareRelease.ts +37 -5
  76. package/src/api/CheckBootloaderRelease.ts +35 -3
  77. package/src/api/CheckFirmwareRelease.ts +35 -7
  78. package/src/api/FirmwareUpdateV2.ts +231 -138
  79. package/src/api/FirmwareUpdateV3.ts +101 -56
  80. package/src/api/FirmwareUpdateV4.ts +936 -408
  81. package/src/api/UploadPortfolio.ts +18 -0
  82. package/src/api/device/DeviceUpdateBootloader.ts +37 -11
  83. package/src/api/firmware/FirmwareHostBinding.ts +26 -3
  84. package/src/api/firmware/FirmwareMemoryHost.ts +143 -0
  85. package/src/api/firmware/FirmwarePreparedResourceArchive.ts +207 -0
  86. package/src/api/firmware/FirmwareUpdateBaseMethod.ts +122 -73
  87. package/src/api/firmware/FirmwareUpdatePlan.ts +295 -122
  88. package/src/api/firmware/FirmwareUpdatePreparedPlan.ts +49 -12
  89. package/src/api/firmware/protocolV2Release.ts +168 -0
  90. package/src/data-manager/DataManager.ts +35 -19
  91. package/src/index.ts +10 -0
  92. package/src/protocols/protocol-v2/resources.ts +157 -341
  93. package/src/types/api/checkAllFirmwareRelease.ts +27 -12
  94. package/src/types/api/checkBLEFirmwareRelease.ts +4 -13
  95. package/src/types/api/checkBootloaderRelease.ts +2 -6
  96. package/src/types/api/checkFirmwareRelease.ts +2 -13
  97. package/src/types/api/checkFirmwareTypeAvailable.ts +2 -2
  98. package/src/types/api/deviceUpdateBootloader.ts +1 -0
  99. package/src/types/api/export.ts +6 -1
  100. package/src/types/api/firmwareUpdate.ts +12 -17
  101. package/src/types/api/firmwareUpdatePlan.ts +2 -2
  102. package/src/types/settings.ts +35 -61
  103. package/src/utils/deviceFeaturesUtils.ts +2 -2
@@ -0,0 +1,150 @@
1
+ import { sha256 } from '@noble/hashes/sha256';
2
+ import { bytesToHex } from '@noble/hashes/utils';
3
+ import { EFirmwareType } from '@onekeyfe/hd-shared';
4
+ import JSZip from 'jszip';
5
+
6
+ import { readVerifiedPreparedResourceArchive } from '../../src/api/firmware/FirmwarePreparedResourceArchive';
7
+ import { prepareFirmwareUpdatePlan } from '../../src/api/firmware/FirmwareUpdatePreparedPlan';
8
+ import { digestFirmwareUpdateContract } from '../../src/api/firmware/FirmwareUpdatePlan';
9
+
10
+ import type {
11
+ FirmwareArtifactReader,
12
+ FirmwareArtifactReference,
13
+ } from '../../src/types/api/firmwareUpdate';
14
+ import type { FirmwareUpdatePlan } from '../../src/types/api/firmwareUpdatePlan';
15
+
16
+ jest.mock('../../src/data/config', () => ({
17
+ getSDKVersion: jest.fn(() => '1.0.0'),
18
+ DEFAULT_DOMAIN: 'https://jssdk.onekey.so/1.0.0/',
19
+ }));
20
+
21
+ const createReference = (artifactRef: string, binary: ArrayBuffer): FirmwareArtifactReference => ({
22
+ artifactRef,
23
+ size: binary.byteLength,
24
+ sha256: bytesToHex(sha256(new Uint8Array(binary))),
25
+ });
26
+
27
+ const createReader = (artifacts: Map<string, ArrayBuffer>): FirmwareArtifactReader => {
28
+ const opened = new Map<string, ArrayBuffer>();
29
+ let sequence = 0;
30
+ return {
31
+ open({ artifactRef }) {
32
+ const binary = artifacts.get(artifactRef);
33
+ if (!binary) throw new Error(`missing artifact: ${artifactRef}`);
34
+ sequence += 1;
35
+ const readerId = `reader-${sequence}`;
36
+ opened.set(readerId, binary);
37
+ return Promise.resolve({ readerId, size: binary.byteLength });
38
+ },
39
+ read({ readerId, offset, length }) {
40
+ const binary = opened.get(readerId);
41
+ if (!binary) throw new Error(`missing reader: ${readerId}`);
42
+ const data = binary.slice(offset, offset + length);
43
+ return Promise.resolve({
44
+ data,
45
+ bytesRead: data.byteLength,
46
+ eof: offset + length === binary.byteLength,
47
+ });
48
+ },
49
+ close({ readerId }) {
50
+ opened.delete(readerId);
51
+ return Promise.resolve();
52
+ },
53
+ };
54
+ };
55
+
56
+ const createPreparedResourcePlan = ({
57
+ executor,
58
+ archiveBinary,
59
+ entryBinary,
60
+ }: {
61
+ executor: 'v2' | 'v3';
62
+ archiveBinary: ArrayBuffer;
63
+ entryBinary: ArrayBuffer;
64
+ }) => {
65
+ const archiveArtifact = createReference('resource-archive', archiveBinary);
66
+ const entryArtifact = createReference('resource-entry', entryBinary);
67
+ const planWithoutDigest: Omit<FirmwareUpdatePlan, 'planDigest'> = {
68
+ schemaVersion: 2,
69
+ executor,
70
+ deviceIdentity: `${executor}-device`,
71
+ deviceModel: executor === 'v2' ? 'TOUCH' : 'PRO',
72
+ firmwareType: EFirmwareType.Universal,
73
+ platform: 'desktop',
74
+ artifacts: [
75
+ {
76
+ artifactId: 'resource',
77
+ role: 'resource',
78
+ target: 'resource',
79
+ url: 'https://firmware.onekey.so/resource.zip',
80
+ container: 'zip',
81
+ expectedSize: archiveArtifact.size,
82
+ expectedSha256: archiveArtifact.sha256,
83
+ },
84
+ ],
85
+ targetsToUpdate: ['resource'],
86
+ };
87
+ const plan: FirmwareUpdatePlan = {
88
+ ...planWithoutDigest,
89
+ planDigest: digestFirmwareUpdateContract(planWithoutDigest),
90
+ };
91
+ return {
92
+ archiveArtifact,
93
+ preparedPlan: prepareFirmwareUpdatePlan({
94
+ plan,
95
+ leaseRef: `${executor}-resource-lease`,
96
+ artifacts: [
97
+ {
98
+ artifactId: 'resource',
99
+ artifact: archiveArtifact,
100
+ materializedEntries: [{ entryName: 'images/logo.bin', artifact: entryArtifact }],
101
+ },
102
+ ],
103
+ }),
104
+ };
105
+ };
106
+
107
+ describe.each(['v2', 'v3'] as const)('%s prepared resource archive binding', executor => {
108
+ test('uses canonical bytes extracted from the approved ZIP', async () => {
109
+ const entryBinary = Uint8Array.from([1, 2, 3, 4]).buffer;
110
+ const zip = new JSZip();
111
+ zip.file('images/logo.bin', entryBinary);
112
+ const archiveBinary = await zip.generateAsync({ type: 'arraybuffer' });
113
+ const { archiveArtifact, preparedPlan } = createPreparedResourcePlan({
114
+ executor,
115
+ archiveBinary,
116
+ entryBinary,
117
+ });
118
+
119
+ const result = await readVerifiedPreparedResourceArchive({
120
+ preparedPlan,
121
+ reader: createReader(new Map([[archiveArtifact.artifactRef, archiveBinary]])),
122
+ });
123
+
124
+ expect(result).toHaveLength(1);
125
+ expect(result[0].entryName).toBe('logo.bin');
126
+ expect(new Uint8Array(result[0].binary)).toEqual(new Uint8Array(entryBinary));
127
+ });
128
+
129
+ test('rejects entries that are not derived from the approved ZIP', async () => {
130
+ const approvedEntry = Uint8Array.from([1, 2, 3, 4]).buffer;
131
+ const substitutedEntry = Uint8Array.from([9, 9, 9, 9]).buffer;
132
+ const zip = new JSZip();
133
+ zip.file('images/logo.bin', approvedEntry);
134
+ const archiveBinary = await zip.generateAsync({ type: 'arraybuffer' });
135
+ const { archiveArtifact, preparedPlan } = createPreparedResourcePlan({
136
+ executor,
137
+ archiveBinary,
138
+ entryBinary: substitutedEntry,
139
+ });
140
+
141
+ await expect(
142
+ readVerifiedPreparedResourceArchive({
143
+ preparedPlan,
144
+ reader: createReader(new Map([[archiveArtifact.artifactRef, archiveBinary]])),
145
+ })
146
+ ).rejects.toMatchObject({
147
+ params: { firmwareUpdateCode: 'FirmwareArtifactReceiptMismatch' },
148
+ });
149
+ });
150
+ });
@@ -231,7 +231,7 @@ describe.each([
231
231
  jest.restoreAllMocks();
232
232
  });
233
233
 
234
- test('pauses the reboot deadline while waiting for browser authorization', async () => {
234
+ test('resumes bounded polling after browser authorization returns an empty enumeration', async () => {
235
235
  jest.spyOn(DataManager, 'getSettings').mockReturnValue('webusb' as never);
236
236
  jest.spyOn(DataManager, 'isBleConnect').mockReturnValue(false);
237
237
  jest.spyOn(DataManager, 'isBrowserWebUsb').mockReturnValue(true);
@@ -300,13 +300,76 @@ describe.each([
300
300
  }
301
301
  expect(rejected).not.toHaveBeenCalled();
302
302
 
303
- permissionGranted = true;
304
303
  resolvePrompt('bootloader-device');
305
304
  await flush();
306
305
  await flush();
307
306
 
307
+ // The chooser may resolve before the newly authorized device appears in the
308
+ // next WebUSB enumeration. That transient empty result must not strand the
309
+ // deferred check after its original interval and timeout were cleared.
310
+ expect(resolved).not.toHaveBeenCalled();
311
+ expect(rejected).not.toHaveBeenCalled();
312
+
313
+ permissionGranted = true;
314
+ for (let elapsed = 0; elapsed < 5000; elapsed += 500) {
315
+ jest.advanceTimersByTime(500);
316
+ // eslint-disable-next-line no-await-in-loop
317
+ await flush();
318
+ if (resolved.mock.calls.length) break;
319
+ }
320
+
308
321
  expect(resolved).toHaveBeenCalledWith(true);
309
322
  expect(updateFromCache).toHaveBeenCalledWith(bootloaderDevice);
310
323
  expect(commands.disposed).toBe(false);
311
324
  });
325
+
326
+ test('rejects after the fresh deadline when the authorized device never appears', async () => {
327
+ jest.spyOn(DataManager, 'getSettings').mockReturnValue('webusb' as never);
328
+ jest.spyOn(DataManager, 'isBleConnect').mockReturnValue(false);
329
+ jest.spyOn(DataManager, 'isBrowserWebUsb').mockReturnValue(true);
330
+ jest.spyOn(DevicePool, 'getDevices').mockResolvedValue({ devices: {}, deviceList: [] });
331
+
332
+ const method = buildMethod();
333
+ method.device = {
334
+ originalDescriptor: {
335
+ id: WEBUSB_ID,
336
+ path: WEBUSB_ID,
337
+ protocolType: 'V1',
338
+ },
339
+ deviceConnector: {
340
+ enumerate: jest.fn().mockResolvedValue({ descriptors: [] }),
341
+ },
342
+ commands: { disposed: true },
343
+ updateFromCache: jest.fn(),
344
+ getCurrentDeviceType: jest.fn(() => EDeviceType.Pro),
345
+ } as unknown as Device;
346
+ method.postTipMessage = jest.fn();
347
+ const prompt = jest
348
+ .spyOn(method as any, '_promptDeviceInBootloaderForWebDevice')
349
+ .mockResolvedValue('bootloader-device');
350
+
351
+ method.checkDeviceToBootloader(WEBUSB_ID);
352
+ const resolved = jest.fn();
353
+ const rejected = jest.fn();
354
+ method.checkPromise?.promise.then(resolved, rejected);
355
+
356
+ for (let elapsed = 0; elapsed < 15000; elapsed += 500) {
357
+ jest.advanceTimersByTime(500);
358
+ // eslint-disable-next-line no-await-in-loop
359
+ await flush();
360
+ if (prompt.mock.calls.length) break;
361
+ }
362
+ expect(prompt).toHaveBeenCalledTimes(1);
363
+
364
+ for (let elapsed = 0; elapsed < 31000; elapsed += 500) {
365
+ jest.advanceTimersByTime(500);
366
+ // eslint-disable-next-line no-await-in-loop
367
+ await flush();
368
+ if (rejected.mock.calls.length) break;
369
+ }
370
+
371
+ expect(resolved).not.toHaveBeenCalled();
372
+ expect(rejected).toHaveBeenCalledTimes(1);
373
+ expect(prompt).toHaveBeenCalledTimes(1);
374
+ });
312
375
  });
@@ -1,6 +1,10 @@
1
1
  import { EDeviceType, EFirmwareType } from '@onekeyfe/hd-shared';
2
2
 
3
3
  import { buildFirmwareUpdatePlan } from '../../src/api/firmware/FirmwareUpdatePlan';
4
+ import {
5
+ registerFirmwareUpdateHostBinding,
6
+ unregisterFirmwareUpdateHostBinding,
7
+ } from '../../src/api/firmware/FirmwareHostBinding';
4
8
  import {
5
9
  assertFirmwareUpdatePreparedPlanDeviceIdentity,
6
10
  prepareFirmwareUpdatePlan,
@@ -179,6 +183,19 @@ describe('firmware update plan identity in bootloader mode', () => {
179
183
  ],
180
184
  });
181
185
  };
186
+ const createBinding = (preparedPlanDigest: string) => ({
187
+ preparedPlanDigest,
188
+ artifactReader: {
189
+ open: () => Promise.resolve({ readerId: 'unused', size: 0 }),
190
+ read: () =>
191
+ Promise.resolve({
192
+ data: new ArrayBuffer(0),
193
+ bytesRead: 0,
194
+ eof: true,
195
+ }),
196
+ close: () => Promise.resolve(),
197
+ },
198
+ });
182
199
 
183
200
  test('accepts the phase that runs after the device reboots and reports a serial', () => {
184
201
  const preparedPlan = prepareRecoveryPlan();
@@ -250,6 +267,69 @@ describe('firmware update plan identity in bootloader mode', () => {
250
267
  })
251
268
  ).not.toThrow();
252
269
  });
270
+
271
+ test('releases the identity pin when the digest-bound host workflow ends', () => {
272
+ const preparedPlan = prepareRecoveryPlan();
273
+ const firstGeneration = registerFirmwareUpdateHostBinding(
274
+ createBinding(preparedPlan.preparedPlanDigest)
275
+ );
276
+ try {
277
+ assertFirmwareUpdatePreparedPlanDeviceIdentity({
278
+ preparedPlan,
279
+ deviceIdentity: 'CLA45F0023',
280
+ bootloaderMode: false,
281
+ deviceModel: String(EDeviceType.Classic1s),
282
+ });
283
+ } finally {
284
+ expect(unregisterFirmwareUpdateHostBinding(firstGeneration)).toBe(true);
285
+ }
286
+
287
+ const secondGeneration = registerFirmwareUpdateHostBinding(
288
+ createBinding(preparedPlan.preparedPlanDigest)
289
+ );
290
+ try {
291
+ expect(() =>
292
+ assertFirmwareUpdatePreparedPlanDeviceIdentity({
293
+ preparedPlan,
294
+ deviceIdentity: 'CLA99Z9999',
295
+ bootloaderMode: false,
296
+ deviceModel: String(EDeviceType.Classic1s),
297
+ })
298
+ ).not.toThrow();
299
+ } finally {
300
+ expect(unregisterFirmwareUpdateHostBinding(secondGeneration)).toBe(true);
301
+ }
302
+ });
303
+
304
+ test('a stale unregister cannot release the active workflow identity pin', () => {
305
+ const stalePlan = prepareRecoveryPlan();
306
+ const staleGeneration = registerFirmwareUpdateHostBinding(
307
+ createBinding(stalePlan.preparedPlanDigest)
308
+ );
309
+ const activePlan = prepareRecoveryPlan();
310
+ const activeGeneration = registerFirmwareUpdateHostBinding(
311
+ createBinding(activePlan.preparedPlanDigest)
312
+ );
313
+ try {
314
+ assertFirmwareUpdatePreparedPlanDeviceIdentity({
315
+ preparedPlan: activePlan,
316
+ deviceIdentity: 'CLA45F0023',
317
+ bootloaderMode: false,
318
+ deviceModel: String(EDeviceType.Classic1s),
319
+ });
320
+ expect(unregisterFirmwareUpdateHostBinding(staleGeneration)).toBe(false);
321
+ expect(() =>
322
+ assertFirmwareUpdatePreparedPlanDeviceIdentity({
323
+ preparedPlan: activePlan,
324
+ deviceIdentity: 'CLA99Z9999',
325
+ bootloaderMode: false,
326
+ deviceModel: String(EDeviceType.Classic1s),
327
+ })
328
+ ).toThrow();
329
+ } finally {
330
+ expect(unregisterFirmwareUpdateHostBinding(activeGeneration)).toBe(true);
331
+ }
332
+ });
253
333
  });
254
334
 
255
335
  describe('prepared plan device identity assertion', () => {