@onekeyfe/hd-core 1.2.0-alpha.98 → 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.
- package/__tests__/check-all-firmware-release-protocol-v2.test.ts +66 -61
- package/__tests__/check-firmware-release-protocol-v2.test.ts +1 -1
- package/__tests__/firmware-memory-host.test.ts +18 -4
- package/__tests__/firmware-update/device-update-bootloader.test.ts +1 -0
- package/__tests__/firmware-update/firmware-update-bootloader-poll.test.ts +65 -2
- package/__tests__/firmware-update/firmware-update-plan-bootloader-identity.test.ts +80 -0
- package/__tests__/firmware-update/firmware-update-plan.test.ts +61 -1
- package/__tests__/protocol-v2-firmware-targets.test.ts +16 -0
- package/__tests__/protocol-v2.test.ts +96 -0
- package/dist/api/CheckAllFirmwareRelease.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV2.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV4.d.ts +3 -1
- package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
- package/dist/api/firmware/FirmwareHostBinding.d.ts.map +1 -1
- package/dist/api/firmware/FirmwareMemoryHost.d.ts.map +1 -1
- package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts.map +1 -1
- package/dist/api/firmware/FirmwareUpdatePlan.d.ts.map +1 -1
- package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts +1 -0
- package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts.map +1 -1
- package/dist/index.js +207 -54
- package/package.json +4 -4
- package/src/api/CheckAllFirmwareRelease.ts +6 -1
- package/src/api/FirmwareUpdateV2.ts +123 -65
- package/src/api/FirmwareUpdateV4.ts +54 -8
- package/src/api/firmware/FirmwareHostBinding.ts +10 -0
- package/src/api/firmware/FirmwareMemoryHost.ts +4 -3
- package/src/api/firmware/FirmwareUpdateBaseMethod.ts +122 -73
- package/src/api/firmware/FirmwareUpdatePlan.ts +18 -5
- package/src/api/firmware/FirmwareUpdatePreparedPlan.ts +22 -9
|
@@ -328,15 +328,16 @@ describe('checkAllFirmwareRelease Protocol V2 support', () => {
|
|
|
328
328
|
status: 'required',
|
|
329
329
|
resourceStatus: 'unknown',
|
|
330
330
|
resourceArchive: resourceSource,
|
|
331
|
-
targetsToUpdate: ['boot', 'app_v1'],
|
|
331
|
+
targetsToUpdate: ['boot', 'app_v1', 'resource'],
|
|
332
332
|
firmwareUpdatePlan: {
|
|
333
333
|
executor: 'v4',
|
|
334
334
|
platform: 'web',
|
|
335
335
|
artifacts: [
|
|
336
336
|
{ artifactId: 'component:boot', target: 'boot' },
|
|
337
337
|
{ artifactId: 'component:app_v1', target: 'app_v1' },
|
|
338
|
+
{ artifactId: 'resource:archive', target: 'resource' },
|
|
338
339
|
],
|
|
339
|
-
targetsToUpdate: ['boot', 'app_v1'],
|
|
340
|
+
targetsToUpdate: ['boot', 'app_v1', 'resource'],
|
|
340
341
|
},
|
|
341
342
|
firmware: {
|
|
342
343
|
status: 'required',
|
|
@@ -377,7 +378,7 @@ describe('checkAllFirmwareRelease Protocol V2 support', () => {
|
|
|
377
378
|
jest.spyOn(DataManager, 'getProtocolV2ResourceSource').mockReturnValue(resourceSource);
|
|
378
379
|
|
|
379
380
|
await expect(method.run()).resolves.toMatchObject({
|
|
380
|
-
targetsToUpdate: ['boot', 'app_v1', 'se02', 'se03'],
|
|
381
|
+
targetsToUpdate: ['boot', 'app_v1', 'se02', 'se03', 'resource'],
|
|
381
382
|
firmwareUpdatePlan: {
|
|
382
383
|
executor: 'v4',
|
|
383
384
|
deviceIdentity: 'pro2-device-id',
|
|
@@ -387,8 +388,9 @@ describe('checkAllFirmwareRelease Protocol V2 support', () => {
|
|
|
387
388
|
{ artifactId: 'component:app_v1', target: 'app_v1' },
|
|
388
389
|
{ artifactId: 'component:se02', target: 'se02' },
|
|
389
390
|
{ artifactId: 'component:se03', target: 'se03' },
|
|
391
|
+
{ artifactId: 'resource:archive', target: 'resource' },
|
|
390
392
|
],
|
|
391
|
-
targetsToUpdate: ['boot', 'app_v1', 'se02', 'se03'],
|
|
393
|
+
targetsToUpdate: ['boot', 'app_v1', 'se02', 'se03', 'resource'],
|
|
392
394
|
},
|
|
393
395
|
});
|
|
394
396
|
});
|
|
@@ -582,12 +584,12 @@ describe('checkAllFirmwareRelease Protocol V2 support', () => {
|
|
|
582
584
|
jest.spyOn(DataManager, 'getProtocolV2ResourceSource').mockReturnValue(resourceSource);
|
|
583
585
|
|
|
584
586
|
await expect(method.run()).resolves.toMatchObject({
|
|
585
|
-
targetsToUpdate: ['se01', 'se02'],
|
|
587
|
+
targetsToUpdate: ['se01', 'se02', 'resource'],
|
|
586
588
|
firmwareUpdatePlan: {
|
|
587
589
|
executor: 'v4',
|
|
588
590
|
deviceModel: 'neo',
|
|
589
591
|
platform: 'native',
|
|
590
|
-
targetsToUpdate: ['se01', 'se02'],
|
|
592
|
+
targetsToUpdate: ['se01', 'se02', 'resource'],
|
|
591
593
|
},
|
|
592
594
|
});
|
|
593
595
|
});
|
|
@@ -618,63 +620,66 @@ describe('checkAllFirmwareRelease Protocol V2 support', () => {
|
|
|
618
620
|
});
|
|
619
621
|
});
|
|
620
622
|
|
|
621
|
-
test('
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
623
|
+
test.each(['resource', 'boot_resources'] as const)(
|
|
624
|
+
'allows an explicitly forced Protocol V2 resource-only target through %s',
|
|
625
|
+
async forceTarget => {
|
|
626
|
+
const currentRelease: IFirmwareReleaseInfo = {
|
|
627
|
+
...release,
|
|
628
|
+
required: false,
|
|
629
|
+
installOrder: ['applicationP1'],
|
|
630
|
+
components: {
|
|
631
|
+
applicationP1: {
|
|
632
|
+
target: 'APPLICATION_P1',
|
|
633
|
+
url: 'https://example.com/application-p1.okpkg',
|
|
634
|
+
version: [1, 0, 0],
|
|
635
|
+
},
|
|
631
636
|
},
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
}
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
}
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
jest.spyOn(DataManager, 'getProtocolV2ResourceSource').mockReturnValue(resourceSource);
|
|
637
|
+
};
|
|
638
|
+
const method = new CheckAllFirmwareRelease({
|
|
639
|
+
id: 1,
|
|
640
|
+
payload: {
|
|
641
|
+
method: 'checkAllFirmwareRelease',
|
|
642
|
+
platform: 'desktop',
|
|
643
|
+
protocolV2ForceUpdateTargets: [forceTarget],
|
|
644
|
+
},
|
|
645
|
+
});
|
|
646
|
+
method.init();
|
|
647
|
+
method.device = {
|
|
648
|
+
isProtocolV2: () => true,
|
|
649
|
+
features: {
|
|
650
|
+
deviceType: 'pro2',
|
|
651
|
+
serialNo: 'pro2-device-id',
|
|
652
|
+
firmwareVersion: '1.0.0',
|
|
653
|
+
},
|
|
654
|
+
getDeviceState: jest.fn().mockResolvedValue({
|
|
655
|
+
identity: { deviceType: 'pro2', firmwareType: EFirmwareType.Universal },
|
|
656
|
+
status: { mode: 'normal' },
|
|
657
|
+
versions: { ...currentVersions, applicationP1: '1.0.0' },
|
|
658
|
+
}),
|
|
659
|
+
} as unknown as CheckAllFirmwareRelease['device'];
|
|
660
|
+
jest.spyOn(DataManager, 'getFirmwareLatestRelease').mockReturnValue(currentRelease);
|
|
661
|
+
jest.spyOn(DataManager, 'getProtocolV2ResourceSource').mockReturnValue(resourceSource);
|
|
658
662
|
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
targetsToUpdate: ['resource'],
|
|
663
|
-
firmwareUpdatePlan: {
|
|
664
|
-
executor: 'v4',
|
|
665
|
-
platform: 'desktop',
|
|
666
|
-
artifacts: [
|
|
667
|
-
{
|
|
668
|
-
artifactId: 'resource:archive',
|
|
669
|
-
target: 'resource',
|
|
670
|
-
role: 'resourceBundle',
|
|
671
|
-
container: 'zip',
|
|
672
|
-
},
|
|
673
|
-
],
|
|
663
|
+
await expect(method.run()).resolves.toMatchObject({
|
|
664
|
+
status: 'valid',
|
|
665
|
+
hasUpgrade: true,
|
|
674
666
|
targetsToUpdate: ['resource'],
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
667
|
+
firmwareUpdatePlan: {
|
|
668
|
+
executor: 'v4',
|
|
669
|
+
platform: 'desktop',
|
|
670
|
+
artifacts: [
|
|
671
|
+
{
|
|
672
|
+
artifactId: 'resource:archive',
|
|
673
|
+
target: 'resource',
|
|
674
|
+
role: 'resourceBundle',
|
|
675
|
+
container: 'zip',
|
|
676
|
+
},
|
|
677
|
+
],
|
|
678
|
+
targetsToUpdate: ['resource'],
|
|
679
|
+
},
|
|
680
|
+
});
|
|
681
|
+
}
|
|
682
|
+
);
|
|
678
683
|
|
|
679
684
|
test('requests and compares firmware hashes only when explicitly enabled', async () => {
|
|
680
685
|
const packageSet: IFirmwareReleaseInfo = {
|
|
@@ -752,7 +757,7 @@ describe('checkAllFirmwareRelease Protocol V2 support', () => {
|
|
|
752
757
|
protocol: 'V2',
|
|
753
758
|
resourceStatus: 'unknown',
|
|
754
759
|
resourceArchive: resourceSource,
|
|
755
|
-
targetsToUpdate: ['boot', 'app_v1'],
|
|
760
|
+
targetsToUpdate: ['boot', 'app_v1', 'resource'],
|
|
756
761
|
});
|
|
757
762
|
expect(typedCall).not.toHaveBeenCalled();
|
|
758
763
|
}
|
|
@@ -147,7 +147,7 @@ describe.each(['pro2', 'neo'] as const)('Protocol V2 release checks for %s', dev
|
|
|
147
147
|
deviceType,
|
|
148
148
|
status: 'outdated',
|
|
149
149
|
hasUpgrade: true,
|
|
150
|
-
targetsToUpdate: ['boot', 'app_v1', 'coprocessor'],
|
|
150
|
+
targetsToUpdate: ['boot', 'app_v1', 'coprocessor', 'resource'],
|
|
151
151
|
firmware: { status: 'outdated', shouldUpdate: true },
|
|
152
152
|
ble: {
|
|
153
153
|
status: 'outdated',
|
|
@@ -93,18 +93,32 @@ describe('prepareFirmwareUpdateV4MemoryHost', () => {
|
|
|
93
93
|
const componentArtifact = host.preparedPlan.artifacts.find(
|
|
94
94
|
artifact => artifact.target === 'boot'
|
|
95
95
|
)?.artifact;
|
|
96
|
+
const resourceEntryArtifact = host.preparedPlan.artifacts.find(
|
|
97
|
+
artifact => artifact.target === 'resource'
|
|
98
|
+
)?.materializedEntries?.[0]?.artifact;
|
|
96
99
|
expect(componentArtifact?.size).toBe(componentBinary.byteLength);
|
|
100
|
+
expect(resourceEntryArtifact?.size).toBe(manifestBinary.byteLength);
|
|
101
|
+
if (!artifactReader || !componentArtifact || !resourceEntryArtifact) {
|
|
102
|
+
throw new Error('Firmware memory host test setup is incomplete');
|
|
103
|
+
}
|
|
97
104
|
new Uint8Array(componentBinary).fill(9);
|
|
98
|
-
const opened = await artifactReader
|
|
99
|
-
artifactRef: componentArtifact
|
|
105
|
+
const opened = await artifactReader.open({
|
|
106
|
+
artifactRef: componentArtifact.artifactRef,
|
|
100
107
|
});
|
|
101
|
-
const chunk = await artifactReader
|
|
108
|
+
const chunk = await artifactReader.read({
|
|
102
109
|
readerId: opened.readerId,
|
|
103
110
|
offset: 1,
|
|
104
111
|
length: 2,
|
|
105
112
|
});
|
|
106
113
|
expect(Array.from(new Uint8Array(chunk.data))).toEqual([2, 3]);
|
|
107
|
-
await artifactReader
|
|
114
|
+
await artifactReader.close({ readerId: opened.readerId });
|
|
115
|
+
|
|
116
|
+
// Materialized ZIP entries are receipts only. Execution opens the approved
|
|
117
|
+
// archive and re-derives these bytes, so the memory host must not retain a
|
|
118
|
+
// second readable copy of every expanded resource file.
|
|
119
|
+
expect(() => artifactReader.open({ artifactRef: resourceEntryArtifact.artifactRef })).toThrow(
|
|
120
|
+
'Firmware memory artifact is unavailable'
|
|
121
|
+
);
|
|
108
122
|
|
|
109
123
|
host.release();
|
|
110
124
|
expect(unregisterFirmwareUpdateHostBinding).toHaveBeenCalledWith(7);
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
jest.mock('../../src/api/firmware/FirmwareUpdatePreparedPlan', () => ({
|
|
10
10
|
assertFirmwareUpdatePreparedPlanBinding: jest.fn(),
|
|
11
11
|
assertFirmwareUpdatePreparedPlanDeviceIdentity: jest.fn(),
|
|
12
|
+
clearFirmwareUpdatePreparedPlanDeviceIdentityPin: jest.fn(),
|
|
12
13
|
getFirmwareUpdatePreparedRawArtifact: jest.fn(
|
|
13
14
|
({ preparedPlan }: { preparedPlan: { artifacts: unknown[] } }) => preparedPlan.artifacts[0]
|
|
14
15
|
),
|
|
@@ -231,7 +231,7 @@ describe.each([
|
|
|
231
231
|
jest.restoreAllMocks();
|
|
232
232
|
});
|
|
233
233
|
|
|
234
|
-
test('
|
|
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', () => {
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { EDeviceType, EFirmwareType } from '@onekeyfe/hd-shared';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
buildFirmwareUpdatePlan,
|
|
5
|
+
validateProtocolV2FirmwareUpdateTargets,
|
|
6
|
+
} from '../../src/api/firmware/FirmwareUpdatePlan';
|
|
4
7
|
import {
|
|
5
8
|
assertFirmwareUpdatePreparedPlanDeviceIdentity,
|
|
6
9
|
prepareFirmwareUpdatePlan,
|
|
@@ -88,6 +91,18 @@ const createProtocolV2ForceInput = (
|
|
|
88
91
|
forceUpdateTargets: forceUpdateTargets as BuildPlanInput['forceUpdateTargets'],
|
|
89
92
|
});
|
|
90
93
|
|
|
94
|
+
describe('validateProtocolV2FirmwareUpdateTargets', () => {
|
|
95
|
+
test('normalizes the deprecated boot_resources alias to resource', () => {
|
|
96
|
+
expect(validateProtocolV2FirmwareUpdateTargets(['boot_resources'])).toEqual(['resource']);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test('rejects duplicate semantic targets after alias normalization', () => {
|
|
100
|
+
expectFirmwarePlanInvalid(() =>
|
|
101
|
+
validateProtocolV2FirmwareUpdateTargets(['boot_resources', 'resource'])
|
|
102
|
+
);
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
|
|
91
106
|
describe('buildFirmwareUpdatePlan', () => {
|
|
92
107
|
test('selects all legacy artifacts before execution and chooses the desktop full resource', () => {
|
|
93
108
|
const plan = buildFirmwareUpdatePlan({
|
|
@@ -316,6 +331,51 @@ describe('buildFirmwareUpdatePlan', () => {
|
|
|
316
331
|
expectFirmwarePlanInvalid(() => buildFirmwareUpdatePlan(createLegacyForceInput(['resource'])));
|
|
317
332
|
});
|
|
318
333
|
|
|
334
|
+
test.each([
|
|
335
|
+
{
|
|
336
|
+
executor: 'V2',
|
|
337
|
+
deviceType: EDeviceType.Classic1s,
|
|
338
|
+
bootloaderVersion: '2.1.2',
|
|
339
|
+
},
|
|
340
|
+
{
|
|
341
|
+
executor: 'V3',
|
|
342
|
+
deviceType: EDeviceType.Pro,
|
|
343
|
+
bootloaderVersion: '2.8.4',
|
|
344
|
+
},
|
|
345
|
+
])(
|
|
346
|
+
'rejects a resource-only prepared plan that the $executor executor cannot run',
|
|
347
|
+
({ deviceType, bootloaderVersion }) => {
|
|
348
|
+
expectFirmwarePlanInvalid(
|
|
349
|
+
() =>
|
|
350
|
+
buildFirmwareUpdatePlan({
|
|
351
|
+
features: createFeatures({
|
|
352
|
+
deviceType,
|
|
353
|
+
firmwareVersion: '4.21.0',
|
|
354
|
+
bootloaderVersion,
|
|
355
|
+
}),
|
|
356
|
+
firmwareType: EFirmwareType.Universal,
|
|
357
|
+
platform: 'desktop',
|
|
358
|
+
firmware: {
|
|
359
|
+
status: 'valid',
|
|
360
|
+
release: {
|
|
361
|
+
url: 'https://firmware.onekey.so/legacy/firmware.bin',
|
|
362
|
+
version: [4, 21, 0],
|
|
363
|
+
expectedSize: 1024,
|
|
364
|
+
fingerprint: '1'.repeat(64),
|
|
365
|
+
resource: 'https://firmware.onekey.so/legacy/resource.zip',
|
|
366
|
+
resourceExpectedSize: 4096,
|
|
367
|
+
resourceFingerprint: '2'.repeat(64),
|
|
368
|
+
},
|
|
369
|
+
},
|
|
370
|
+
ble: noUpdate,
|
|
371
|
+
bootloader: noUpdate,
|
|
372
|
+
forceUpdateTargets: ['resource'],
|
|
373
|
+
}),
|
|
374
|
+
'Legacy resource updates require a firmware target'
|
|
375
|
+
);
|
|
376
|
+
}
|
|
377
|
+
);
|
|
378
|
+
|
|
319
379
|
test('rejects a legacy resource archive without complete integrity metadata', () => {
|
|
320
380
|
expectFirmwarePlanInvalid(
|
|
321
381
|
() =>
|
|
@@ -57,6 +57,22 @@ describe('Protocol V2 firmware target contract', () => {
|
|
|
57
57
|
).toThrow('Neo only supports SE01 and SE02; unsupported firmware targets: se03, se04');
|
|
58
58
|
});
|
|
59
59
|
|
|
60
|
+
test('ignores unselected SE binaries when the caller supplied a target subset', () => {
|
|
61
|
+
const params = {
|
|
62
|
+
platform: 'web' as const,
|
|
63
|
+
targetsToUpdate: ['app_v1'] as Array<'app_v1'>,
|
|
64
|
+
se03Binary: new ArrayBuffer(1),
|
|
65
|
+
se04Binary: new ArrayBuffer(1),
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
expect(() =>
|
|
69
|
+
assertProtocolV2FirmwareTargetsSupported(EDeviceType.Neo, params, true)
|
|
70
|
+
).not.toThrow();
|
|
71
|
+
expect(() => assertProtocolV2FirmwareTargetsSupported(EDeviceType.Neo, params)).toThrow(
|
|
72
|
+
'Neo only supports SE01 and SE02; unsupported firmware targets: se03, se04'
|
|
73
|
+
);
|
|
74
|
+
});
|
|
75
|
+
|
|
60
76
|
test('keeps all four SE firmware targets available on Pro2', () => {
|
|
61
77
|
expect(() =>
|
|
62
78
|
assertProtocolV2FirmwareTargetsSupported(EDeviceType.Pro2, {
|
|
@@ -4033,6 +4033,71 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4033
4033
|
});
|
|
4034
4034
|
});
|
|
4035
4035
|
|
|
4036
|
+
test('filters unrequested binaries from a direct local Protocol V2 update', async () => {
|
|
4037
|
+
const applicationP1Binary = new Uint8Array([1, 2, 3]).buffer;
|
|
4038
|
+
const method = new FirmwareUpdateV4({
|
|
4039
|
+
id: 1,
|
|
4040
|
+
payload: {
|
|
4041
|
+
method: 'firmwareUpdateV4',
|
|
4042
|
+
platform: 'web',
|
|
4043
|
+
targetsToUpdate: ['app_v1'],
|
|
4044
|
+
applicationP1Binary,
|
|
4045
|
+
bootloaderBinary: new Uint8Array([4, 5, 6]).buffer,
|
|
4046
|
+
},
|
|
4047
|
+
});
|
|
4048
|
+
method.init();
|
|
4049
|
+
(method as any).captureProtocolV2PhysicalIdentity = jest.fn().mockResolvedValue(undefined);
|
|
4050
|
+
(method as any).device = stubDevice({
|
|
4051
|
+
originalDescriptor: { protocolType: 'V2' },
|
|
4052
|
+
features: {
|
|
4053
|
+
deviceType: 'pro2',
|
|
4054
|
+
firmwareVersion: '1.0.0',
|
|
4055
|
+
capabilities: [],
|
|
4056
|
+
},
|
|
4057
|
+
getCurrentDeviceType: () => 'pro2',
|
|
4058
|
+
});
|
|
4059
|
+
(method as any).executeProtocolV2Update = jest.fn().mockResolvedValue('local-result');
|
|
4060
|
+
method.postTipMessage = jest.fn();
|
|
4061
|
+
|
|
4062
|
+
await expect(method.run()).resolves.toBe('local-result');
|
|
4063
|
+
expect((method as any).executeProtocolV2Update).toHaveBeenCalledWith({
|
|
4064
|
+
bootloaderBinary: null,
|
|
4065
|
+
fwBinaryMap: [
|
|
4066
|
+
{
|
|
4067
|
+
fileName: 'application_p1.bin',
|
|
4068
|
+
binary: applicationP1Binary,
|
|
4069
|
+
targetId: 4,
|
|
4070
|
+
},
|
|
4071
|
+
],
|
|
4072
|
+
});
|
|
4073
|
+
});
|
|
4074
|
+
|
|
4075
|
+
test('keeps all local binaries when no target list was supplied', () => {
|
|
4076
|
+
const method = new FirmwareUpdateV4({
|
|
4077
|
+
id: 1,
|
|
4078
|
+
payload: {
|
|
4079
|
+
method: 'firmwareUpdateV4',
|
|
4080
|
+
platform: 'web',
|
|
4081
|
+
applicationP1Binary: new Uint8Array([1]).buffer,
|
|
4082
|
+
bootloaderBinary: new Uint8Array([2]).buffer,
|
|
4083
|
+
resourceArchiveBinary: new Uint8Array([3]).buffer,
|
|
4084
|
+
},
|
|
4085
|
+
});
|
|
4086
|
+
method.init();
|
|
4087
|
+
|
|
4088
|
+
const availableInstallItems = (method as any).buildProtocolV2InstallItems({
|
|
4089
|
+
bootloaderBinary: (method as any).prepareBootloaderBinary(),
|
|
4090
|
+
fwBinaryMap: (method as any).collectExplicitTargetBinaries(),
|
|
4091
|
+
});
|
|
4092
|
+
|
|
4093
|
+
expect((method as any).params.targetsToUpdate).toEqual(['resource']);
|
|
4094
|
+
expect(
|
|
4095
|
+
(method as any)
|
|
4096
|
+
.filterProtocolV2LocalInstallItems(availableInstallItems)
|
|
4097
|
+
.map((item: { targetId: number }) => item.targetId)
|
|
4098
|
+
).toEqual([3, 4]);
|
|
4099
|
+
});
|
|
4100
|
+
|
|
4036
4101
|
test('requires the external host to prepare manifest resources before bootloader entry', async () => {
|
|
4037
4102
|
const method = new FirmwareUpdateV4({
|
|
4038
4103
|
id: 1,
|
|
@@ -6702,6 +6767,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
6702
6767
|
zip.file('loaders/bootloader/boot_resource.okpkg', bootResourceBinary);
|
|
6703
6768
|
const resourceArchiveBinary = await zip.generateAsync({ type: 'arraybuffer' });
|
|
6704
6769
|
const applicationP1Binary = new Uint8Array([7, 8, 9]).buffer;
|
|
6770
|
+
const bootloaderBinary = new Uint8Array([10, 11, 12]).buffer;
|
|
6705
6771
|
const method = new FirmwareUpdateV4({
|
|
6706
6772
|
id: 1,
|
|
6707
6773
|
payload: {
|
|
@@ -6709,6 +6775,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
6709
6775
|
platform: 'web',
|
|
6710
6776
|
targetsToUpdate: ['app_v1'],
|
|
6711
6777
|
applicationP1Binary,
|
|
6778
|
+
bootloaderBinary,
|
|
6712
6779
|
resourceArchiveBinary,
|
|
6713
6780
|
},
|
|
6714
6781
|
});
|
|
@@ -6742,6 +6809,11 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
6742
6809
|
executor: 'v4',
|
|
6743
6810
|
targetsToUpdate: ['app_v1', 'resource'],
|
|
6744
6811
|
});
|
|
6812
|
+
expect(
|
|
6813
|
+
(method as any).params.preparedPlan.artifacts.some(
|
|
6814
|
+
(artifact: { target: string }) => artifact.target === 'boot'
|
|
6815
|
+
)
|
|
6816
|
+
).toBe(false);
|
|
6745
6817
|
expect((method as any).executeProtocolV2SourceUpdate).toHaveBeenCalledWith(
|
|
6746
6818
|
expect.objectContaining({
|
|
6747
6819
|
installSources: [
|
|
@@ -6832,6 +6904,30 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
6832
6904
|
);
|
|
6833
6905
|
});
|
|
6834
6906
|
|
|
6907
|
+
test('maps malformed resource ZIPs to a typed firmware preparation error', async () => {
|
|
6908
|
+
const loadSpy = jest.spyOn(JSZip, 'loadAsync').mockRejectedValue(new Error('corrupt ZIP'));
|
|
6909
|
+
const method = new FirmwareUpdateV4({
|
|
6910
|
+
id: 1,
|
|
6911
|
+
payload: {
|
|
6912
|
+
method: 'firmwareUpdateV4',
|
|
6913
|
+
platform: 'web',
|
|
6914
|
+
targetsToUpdate: ['resource'],
|
|
6915
|
+
},
|
|
6916
|
+
});
|
|
6917
|
+
method.init();
|
|
6918
|
+
|
|
6919
|
+
try {
|
|
6920
|
+
await expect(
|
|
6921
|
+
(method as any).prepareProtocolV2LocalResourceArchive(new Uint8Array([1]).buffer)
|
|
6922
|
+
).rejects.toMatchObject({
|
|
6923
|
+
errorCode: HardwareErrorCode.RuntimeError,
|
|
6924
|
+
params: { firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' },
|
|
6925
|
+
});
|
|
6926
|
+
} finally {
|
|
6927
|
+
loadSpy.mockRestore();
|
|
6928
|
+
}
|
|
6929
|
+
});
|
|
6930
|
+
|
|
6835
6931
|
test('rejects an oversized ZIP entry before allocating its decompressed bytes', async () => {
|
|
6836
6932
|
const extractEntry = jest.fn();
|
|
6837
6933
|
const loadSpy = jest.spyOn(JSZip, 'loadAsync').mockResolvedValue({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CheckAllFirmwareRelease.d.ts","sourceRoot":"","sources":["../../src/api/CheckAllFirmwareRelease.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAgC1C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EACV,kBAAkB,EAElB,kCAAkC,EAClC,+BAA+B,EAChC,MAAM,sCAAsC,CAAC;AAC9C,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AAC1E,OAAO,KAAK,EACV,oBAAoB,EACpB,mBAAmB,EACnB,oBAAoB,EAGrB,MAAM,UAAU,CAAC;AAgIlB,MAAM,MAAM,6BAA6B,GAAG;IAC1C,YAAY,EAAE,aAAa,CAAC;IAC5B,MAAM,EAAE,+BAA+B,CAAC;IACxC,UAAU,EAAE,OAAO,CAAC;IACpB,QAAQ,EAAE,OAAO,CAAC;IAClB,eAAe,EAAE,mBAAmB,CAAC;IACrC,UAAU,EAAE,kCAAkC,EAAE,CAAC;IACjD,eAAe,EAAE,sBAAsB,EAAE,CAAC;IAC1C,OAAO,EAAE,oBAAoB,GAAG,SAAS,CAAC;CAC3C,CAAC;AAEF,wBAAgB,8BAA8B,CAAC,EAC7C,eAAe,EACf,mBAAwB,EACxB,iBAAyB,EACzB,mBAAwB,EACxB,YAAY,EACZ,OAAO,EACP,UAAU,GACX,EAAE;IACD,eAAe,EAAE,mBAAmB,CAAC;IACrC,mBAAmB,CAAC,EAAE,OAAO,CAAC,oBAAoB,CAAC,CAAC;IACpD,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,mBAAmB,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,CAAC,CAAC;IAC9D,YAAY,EAAE,aAAa,CAAC;IAC5B,OAAO,EAAE,oBAAoB,GAAG,SAAS,CAAC;IAC1C,UAAU,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC;CAC7B,GAAG,6BAA6B,CAiFhC;AAID,MAAM,CAAC,OAAO,OAAO,uBAAwB,SAAQ,UAAU;IAC7D,qBAAqB;IAIrB,IAAI;IAME,GAAG;YAuFK,aAAa;
|
|
1
|
+
{"version":3,"file":"CheckAllFirmwareRelease.d.ts","sourceRoot":"","sources":["../../src/api/CheckAllFirmwareRelease.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAgC1C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EACV,kBAAkB,EAElB,kCAAkC,EAClC,+BAA+B,EAChC,MAAM,sCAAsC,CAAC;AAC9C,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AAC1E,OAAO,KAAK,EACV,oBAAoB,EACpB,mBAAmB,EACnB,oBAAoB,EAGrB,MAAM,UAAU,CAAC;AAgIlB,MAAM,MAAM,6BAA6B,GAAG;IAC1C,YAAY,EAAE,aAAa,CAAC;IAC5B,MAAM,EAAE,+BAA+B,CAAC;IACxC,UAAU,EAAE,OAAO,CAAC;IACpB,QAAQ,EAAE,OAAO,CAAC;IAClB,eAAe,EAAE,mBAAmB,CAAC;IACrC,UAAU,EAAE,kCAAkC,EAAE,CAAC;IACjD,eAAe,EAAE,sBAAsB,EAAE,CAAC;IAC1C,OAAO,EAAE,oBAAoB,GAAG,SAAS,CAAC;CAC3C,CAAC;AAEF,wBAAgB,8BAA8B,CAAC,EAC7C,eAAe,EACf,mBAAwB,EACxB,iBAAyB,EACzB,mBAAwB,EACxB,YAAY,EACZ,OAAO,EACP,UAAU,GACX,EAAE;IACD,eAAe,EAAE,mBAAmB,CAAC;IACrC,mBAAmB,CAAC,EAAE,OAAO,CAAC,oBAAoB,CAAC,CAAC;IACpD,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,mBAAmB,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,CAAC,CAAC;IAC9D,YAAY,EAAE,aAAa,CAAC;IAC5B,OAAO,EAAE,oBAAoB,GAAG,SAAS,CAAC;IAC1C,UAAU,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC;CAC7B,GAAG,6BAA6B,CAiFhC;AAID,MAAM,CAAC,OAAO,OAAO,uBAAwB,SAAQ,UAAU;IAC7D,qBAAqB;IAIrB,IAAI;IAME,GAAG;YAuFK,aAAa;CA2H5B"}
|