@onekeyfe/hd-core 1.2.0-alpha.84 → 1.2.0-alpha.86
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 +69 -26
- package/__tests__/check-firmware-release-protocol-v2.test.ts +1 -2
- package/__tests__/firmware-memory-host.test.ts +108 -0
- package/__tests__/firmware-update/device-update-bootloader.test.ts +4 -1
- package/__tests__/firmware-update/firmware-host-binding.test.ts +9 -0
- package/__tests__/firmware-update/firmware-update-plan.test.ts +15 -70
- package/__tests__/firmware-update/firmware-update-prepared-plan.test.ts +53 -1
- package/__tests__/protocol-v2-resources.test.ts +59 -39
- package/__tests__/protocol-v2.test.ts +255 -306
- package/dist/api/CheckAllFirmwareRelease.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV4.d.ts +1 -2
- package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
- package/dist/api/firmware/FirmwareHostBinding.d.ts +1 -1
- package/dist/api/firmware/FirmwareHostBinding.d.ts.map +1 -1
- package/dist/api/firmware/FirmwareMemoryHost.d.ts +27 -0
- package/dist/api/firmware/FirmwareMemoryHost.d.ts.map +1 -0
- package/dist/api/firmware/FirmwareUpdatePlan.d.ts +3 -4
- package/dist/api/firmware/FirmwareUpdatePlan.d.ts.map +1 -1
- package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts.map +1 -1
- package/dist/api/firmware/getBinary.d.ts +0 -1
- package/dist/api/firmware/getBinary.d.ts.map +1 -1
- package/dist/data-manager/DataManager.d.ts +3 -1
- package/dist/data-manager/DataManager.d.ts.map +1 -1
- package/dist/index.d.ts +32 -70
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +450 -390
- package/dist/inject.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/resources.d.ts +0 -17
- package/dist/protocols/protocol-v2/resources.d.ts.map +1 -1
- package/dist/types/api/checkAllFirmwareRelease.d.ts +0 -1
- package/dist/types/api/checkAllFirmwareRelease.d.ts.map +1 -1
- package/dist/types/api/export.d.ts +0 -1
- package/dist/types/api/export.d.ts.map +1 -1
- package/dist/types/api/firmwareUpdate.d.ts +1 -10
- package/dist/types/api/firmwareUpdate.d.ts.map +1 -1
- package/dist/types/api/index.d.ts +0 -2
- package/dist/types/api/index.d.ts.map +1 -1
- package/dist/types/settings.d.ts +3 -15
- package/dist/types/settings.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/api/CheckAllFirmwareRelease.ts +4 -13
- package/src/api/FirmwareUpdateV4.ts +166 -170
- package/src/api/firmware/FirmwareHostBinding.ts +16 -3
- package/src/api/firmware/FirmwareMemoryHost.ts +174 -0
- package/src/api/firmware/FirmwareUpdatePlan.ts +28 -48
- package/src/api/firmware/FirmwareUpdatePreparedPlan.ts +8 -1
- package/src/data-manager/DataManager.ts +33 -13
- package/src/index.ts +6 -1
- package/src/inject.ts +0 -2
- package/src/protocols/protocol-v2/resources.ts +0 -47
- package/src/types/api/checkAllFirmwareRelease.ts +0 -1
- package/src/types/api/export.ts +0 -4
- package/src/types/api/firmwareUpdate.ts +2 -15
- package/src/types/api/index.ts +0 -2
- package/src/types/settings.ts +3 -25
- package/dist/types/api/protocolV2ResourceManifest.d.ts +0 -17
- package/dist/types/api/protocolV2ResourceManifest.d.ts.map +0 -1
- package/src/types/api/protocolV2ResourceManifest.ts +0 -19
|
@@ -62,14 +62,6 @@ const release: IFirmwareReleaseInfo = {
|
|
|
62
62
|
version: [2, 0, 0],
|
|
63
63
|
},
|
|
64
64
|
},
|
|
65
|
-
resourceBundles: [
|
|
66
|
-
{
|
|
67
|
-
name: 'images',
|
|
68
|
-
url: 'https://example.com/images.okpkg',
|
|
69
|
-
devicePath: 'vol0:/bundles/images/images.okpkg',
|
|
70
|
-
version: [1, 0, 0],
|
|
71
|
-
},
|
|
72
|
-
],
|
|
73
65
|
};
|
|
74
66
|
|
|
75
67
|
const resourceSource = {
|
|
@@ -152,7 +144,6 @@ describe('checkAllFirmwareRelease Protocol V2 support', () => {
|
|
|
152
144
|
test('detects same-version package hotfixes by P1 and P2 payload hash', () => {
|
|
153
145
|
const packageSet: IFirmwareReleaseInfo = {
|
|
154
146
|
...release,
|
|
155
|
-
resourceBundles: undefined,
|
|
156
147
|
installOrder: ['applicationP1', 'applicationP2'],
|
|
157
148
|
components: {
|
|
158
149
|
applicationP1: {
|
|
@@ -203,7 +194,6 @@ describe('checkAllFirmwareRelease Protocol V2 support', () => {
|
|
|
203
194
|
const packageSet: IFirmwareReleaseInfo = {
|
|
204
195
|
...release,
|
|
205
196
|
required: false,
|
|
206
|
-
resourceBundles: undefined,
|
|
207
197
|
installOrder: ['applicationP1'],
|
|
208
198
|
components: {
|
|
209
199
|
applicationP1: {
|
|
@@ -234,7 +224,6 @@ describe('checkAllFirmwareRelease Protocol V2 support', () => {
|
|
|
234
224
|
const packageSet: IFirmwareReleaseInfo = {
|
|
235
225
|
...release,
|
|
236
226
|
required: false,
|
|
237
|
-
resourceBundles: undefined,
|
|
238
227
|
installOrder: ['applicationP1'],
|
|
239
228
|
components: {
|
|
240
229
|
applicationP1: {
|
|
@@ -265,7 +254,6 @@ describe('checkAllFirmwareRelease Protocol V2 support', () => {
|
|
|
265
254
|
test('updates both application packages when normal mode only reports P1', () => {
|
|
266
255
|
const packageSet: IFirmwareReleaseInfo = {
|
|
267
256
|
...release,
|
|
268
|
-
resourceBundles: undefined,
|
|
269
257
|
installOrder: ['applicationP1', 'applicationP2'],
|
|
270
258
|
components: {
|
|
271
259
|
applicationP1: {
|
|
@@ -331,8 +319,7 @@ describe('checkAllFirmwareRelease Protocol V2 support', () => {
|
|
|
331
319
|
status: 'required',
|
|
332
320
|
resourceStatus: 'unknown',
|
|
333
321
|
resourceArchive: resourceSource,
|
|
334
|
-
|
|
335
|
-
targetsToUpdate: ['boot', 'app_v1', 'resource'],
|
|
322
|
+
targetsToUpdate: ['boot', 'app_v1'],
|
|
336
323
|
firmwareUpdatePlan: {
|
|
337
324
|
executor: 'v4',
|
|
338
325
|
platform: 'web',
|
|
@@ -381,7 +368,7 @@ describe('checkAllFirmwareRelease Protocol V2 support', () => {
|
|
|
381
368
|
jest.spyOn(DataManager, 'getProtocolV2ResourceSource').mockReturnValue(resourceSource);
|
|
382
369
|
|
|
383
370
|
await expect(method.run()).resolves.toMatchObject({
|
|
384
|
-
targetsToUpdate: ['boot', 'app_v1', 'se02', 'se03'
|
|
371
|
+
targetsToUpdate: ['boot', 'app_v1', 'se02', 'se03'],
|
|
385
372
|
firmwareUpdatePlan: {
|
|
386
373
|
executor: 'v4',
|
|
387
374
|
deviceIdentity: 'pro2-device-id',
|
|
@@ -442,7 +429,7 @@ describe('checkAllFirmwareRelease Protocol V2 support', () => {
|
|
|
442
429
|
jest.spyOn(DataManager, 'getProtocolV2ResourceSource').mockReturnValue(resourceSource);
|
|
443
430
|
|
|
444
431
|
await expect(method.run()).resolves.toMatchObject({
|
|
445
|
-
targetsToUpdate: ['app_v1', 'coprocessor'
|
|
432
|
+
targetsToUpdate: ['app_v1', 'coprocessor'],
|
|
446
433
|
firmwareUpdatePlan: {
|
|
447
434
|
executor: 'v4',
|
|
448
435
|
platform: 'desktop',
|
|
@@ -491,7 +478,7 @@ describe('checkAllFirmwareRelease Protocol V2 support', () => {
|
|
|
491
478
|
jest.spyOn(DataManager, 'getProtocolV2ResourceSource').mockReturnValue(resourceSource);
|
|
492
479
|
|
|
493
480
|
await expect(method.run()).resolves.toMatchObject({
|
|
494
|
-
targetsToUpdate: ['app_v1'
|
|
481
|
+
targetsToUpdate: ['app_v1'],
|
|
495
482
|
firmwareUpdatePlan: undefined,
|
|
496
483
|
});
|
|
497
484
|
});
|
|
@@ -571,7 +558,7 @@ describe('checkAllFirmwareRelease Protocol V2 support', () => {
|
|
|
571
558
|
jest.spyOn(DataManager, 'getProtocolV2ResourceSource').mockReturnValue(resourceSource);
|
|
572
559
|
|
|
573
560
|
await expect(method.run()).resolves.toMatchObject({
|
|
574
|
-
targetsToUpdate: ['se01', 'se02'
|
|
561
|
+
targetsToUpdate: ['se01', 'se02'],
|
|
575
562
|
firmwareUpdatePlan: {
|
|
576
563
|
executor: 'v4',
|
|
577
564
|
deviceModel: 'neo',
|
|
@@ -607,11 +594,68 @@ describe('checkAllFirmwareRelease Protocol V2 support', () => {
|
|
|
607
594
|
});
|
|
608
595
|
});
|
|
609
596
|
|
|
597
|
+
test('adds a Protocol V2 resource target only when explicitly forced', async () => {
|
|
598
|
+
const currentRelease: IFirmwareReleaseInfo = {
|
|
599
|
+
...release,
|
|
600
|
+
required: false,
|
|
601
|
+
installOrder: ['applicationP1'],
|
|
602
|
+
components: {
|
|
603
|
+
applicationP1: {
|
|
604
|
+
target: 'APPLICATION_P1',
|
|
605
|
+
url: 'https://example.com/application-p1.okpkg',
|
|
606
|
+
version: [1, 0, 0],
|
|
607
|
+
},
|
|
608
|
+
},
|
|
609
|
+
};
|
|
610
|
+
const method = new CheckAllFirmwareRelease({
|
|
611
|
+
id: 1,
|
|
612
|
+
payload: {
|
|
613
|
+
method: 'checkAllFirmwareRelease',
|
|
614
|
+
platform: 'desktop',
|
|
615
|
+
protocolV2ForceUpdateTargets: ['resource'],
|
|
616
|
+
},
|
|
617
|
+
});
|
|
618
|
+
method.init();
|
|
619
|
+
method.device = {
|
|
620
|
+
isProtocolV2: () => true,
|
|
621
|
+
features: {
|
|
622
|
+
deviceType: 'pro2',
|
|
623
|
+
serialNo: 'pro2-device-id',
|
|
624
|
+
firmwareVersion: '1.0.0',
|
|
625
|
+
},
|
|
626
|
+
getDeviceState: jest.fn().mockResolvedValue({
|
|
627
|
+
identity: { deviceType: 'pro2', firmwareType: EFirmwareType.Universal },
|
|
628
|
+
status: { mode: 'normal' },
|
|
629
|
+
versions: { ...currentVersions, applicationP1: '1.0.0' },
|
|
630
|
+
}),
|
|
631
|
+
} as unknown as CheckAllFirmwareRelease['device'];
|
|
632
|
+
jest.spyOn(DataManager, 'getFirmwareLatestRelease').mockReturnValue(currentRelease);
|
|
633
|
+
jest.spyOn(DataManager, 'getProtocolV2ResourceSource').mockReturnValue(resourceSource);
|
|
634
|
+
|
|
635
|
+
await expect(method.run()).resolves.toMatchObject({
|
|
636
|
+
status: 'valid',
|
|
637
|
+
hasUpgrade: true,
|
|
638
|
+
targetsToUpdate: ['resource'],
|
|
639
|
+
firmwareUpdatePlan: {
|
|
640
|
+
executor: 'v4',
|
|
641
|
+
platform: 'desktop',
|
|
642
|
+
artifacts: [
|
|
643
|
+
{
|
|
644
|
+
artifactId: 'resource:archive',
|
|
645
|
+
target: 'resource',
|
|
646
|
+
role: 'resourceBundle',
|
|
647
|
+
container: 'zip',
|
|
648
|
+
},
|
|
649
|
+
],
|
|
650
|
+
targetsToUpdate: ['resource'],
|
|
651
|
+
},
|
|
652
|
+
});
|
|
653
|
+
});
|
|
654
|
+
|
|
610
655
|
test('requests and compares firmware hashes only when explicitly enabled', async () => {
|
|
611
656
|
const packageSet: IFirmwareReleaseInfo = {
|
|
612
657
|
...release,
|
|
613
658
|
required: false,
|
|
614
|
-
resourceBundles: undefined,
|
|
615
659
|
installOrder: ['applicationP1'],
|
|
616
660
|
components: {
|
|
617
661
|
applicationP1: {
|
|
@@ -684,14 +728,13 @@ describe('checkAllFirmwareRelease Protocol V2 support', () => {
|
|
|
684
728
|
protocol: 'V2',
|
|
685
729
|
resourceStatus: 'unknown',
|
|
686
730
|
resourceArchive: resourceSource,
|
|
687
|
-
|
|
688
|
-
targetsToUpdate: ['boot', 'app_v1', 'resource'],
|
|
731
|
+
targetsToUpdate: ['boot', 'app_v1'],
|
|
689
732
|
});
|
|
690
733
|
expect(typedCall).not.toHaveBeenCalled();
|
|
691
734
|
}
|
|
692
735
|
);
|
|
693
736
|
|
|
694
|
-
test('
|
|
737
|
+
test('exposes resource archive availability without reporting an unconfirmed update', async () => {
|
|
695
738
|
const currentRelease: IFirmwareReleaseInfo = {
|
|
696
739
|
...release,
|
|
697
740
|
required: false,
|
|
@@ -725,11 +768,11 @@ describe('checkAllFirmwareRelease Protocol V2 support', () => {
|
|
|
725
768
|
jest.spyOn(DataManager, 'getProtocolV2ResourceSource').mockReturnValue(resourceSource);
|
|
726
769
|
|
|
727
770
|
await expect(method.run()).resolves.toMatchObject({
|
|
728
|
-
status: '
|
|
729
|
-
hasUpgrade:
|
|
771
|
+
status: 'valid',
|
|
772
|
+
hasUpgrade: false,
|
|
730
773
|
resourceStatus: 'unknown',
|
|
731
|
-
|
|
732
|
-
targetsToUpdate: [
|
|
774
|
+
resourceArchive: resourceSource,
|
|
775
|
+
targetsToUpdate: [],
|
|
733
776
|
firmwareUpdatePlan: undefined,
|
|
734
777
|
});
|
|
735
778
|
});
|
|
@@ -147,8 +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
|
-
|
|
151
|
-
targetsToUpdate: ['boot', 'app_v1', 'coprocessor', 'resource'],
|
|
150
|
+
targetsToUpdate: ['boot', 'app_v1', 'coprocessor'],
|
|
152
151
|
firmware: { status: 'outdated', shouldUpdate: true },
|
|
153
152
|
ble: {
|
|
154
153
|
status: 'outdated',
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { prepareFirmwareUpdateV4MemoryHost } from '../src/api/firmware/FirmwareMemoryHost';
|
|
2
|
+
|
|
3
|
+
import type { CoreApi } from '../src/types/api';
|
|
4
|
+
import type { FirmwareUpdatePlan } from '../src/types/api/firmwareUpdatePlan';
|
|
5
|
+
|
|
6
|
+
describe('prepareFirmwareUpdateV4MemoryHost', () => {
|
|
7
|
+
test('registers artifact-backed component and resource readers', async () => {
|
|
8
|
+
const componentBinary = new Uint8Array([1, 2, 3, 4]).buffer;
|
|
9
|
+
const archiveBinary = new Uint8Array([5, 6]).buffer;
|
|
10
|
+
const manifestBinary = new TextEncoder().encode('{"schema":1}').buffer;
|
|
11
|
+
let artifactReader: Parameters<
|
|
12
|
+
CoreApi['registerFirmwareUpdateHostBinding']
|
|
13
|
+
>[0]['artifactReader'];
|
|
14
|
+
const unregisterFirmwareUpdateHostBinding = jest.fn();
|
|
15
|
+
const sdk = {
|
|
16
|
+
prepareFirmwareUpdatePlan: jest.fn(({ plan, artifacts }) => ({
|
|
17
|
+
schemaVersion: 2,
|
|
18
|
+
preparedPlanDigest: 'b'.repeat(64),
|
|
19
|
+
planDigest: plan.planDigest,
|
|
20
|
+
networkPolicy: 'forbid',
|
|
21
|
+
executor: plan.executor,
|
|
22
|
+
deviceIdentity: plan.deviceIdentity,
|
|
23
|
+
deviceModel: plan.deviceModel,
|
|
24
|
+
firmwareType: plan.firmwareType,
|
|
25
|
+
platform: plan.platform,
|
|
26
|
+
leaseRef: 'test-lease',
|
|
27
|
+
targetsToUpdate: plan.targetsToUpdate,
|
|
28
|
+
artifacts: plan.artifacts.map(planArtifact => {
|
|
29
|
+
const input = artifacts.find(item => item.artifactId === planArtifact.artifactId);
|
|
30
|
+
return {
|
|
31
|
+
...planArtifact,
|
|
32
|
+
artifact: input?.artifact,
|
|
33
|
+
materializedEntries: input?.materializedEntries,
|
|
34
|
+
};
|
|
35
|
+
}),
|
|
36
|
+
})),
|
|
37
|
+
registerFirmwareUpdateHostBinding: jest.fn(binding => {
|
|
38
|
+
artifactReader = binding.artifactReader;
|
|
39
|
+
return 7;
|
|
40
|
+
}),
|
|
41
|
+
unregisterFirmwareUpdateHostBinding,
|
|
42
|
+
} as unknown as Pick<
|
|
43
|
+
CoreApi,
|
|
44
|
+
| 'prepareFirmwareUpdatePlan'
|
|
45
|
+
| 'registerFirmwareUpdateHostBinding'
|
|
46
|
+
| 'unregisterFirmwareUpdateHostBinding'
|
|
47
|
+
>;
|
|
48
|
+
const plan = {
|
|
49
|
+
schemaVersion: 2,
|
|
50
|
+
planDigest: 'a'.repeat(64),
|
|
51
|
+
executor: 'v4',
|
|
52
|
+
deviceIdentity: 'device-id',
|
|
53
|
+
deviceModel: 'pro2',
|
|
54
|
+
firmwareType: 0,
|
|
55
|
+
platform: 'web',
|
|
56
|
+
targetsToUpdate: ['boot', 'resource'],
|
|
57
|
+
artifacts: [
|
|
58
|
+
{
|
|
59
|
+
artifactId: 'component:boot',
|
|
60
|
+
role: 'component',
|
|
61
|
+
target: 'boot',
|
|
62
|
+
url: 'https://example.com/boot.okpkg',
|
|
63
|
+
container: 'raw',
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
artifactId: 'resource:archive',
|
|
67
|
+
role: 'resourceBundle',
|
|
68
|
+
target: 'resource',
|
|
69
|
+
url: 'https://example.com/resource.zip',
|
|
70
|
+
container: 'zip',
|
|
71
|
+
logicalName: 'resource-archive',
|
|
72
|
+
},
|
|
73
|
+
],
|
|
74
|
+
} as FirmwareUpdatePlan;
|
|
75
|
+
|
|
76
|
+
const host = prepareFirmwareUpdateV4MemoryHost({
|
|
77
|
+
sdk,
|
|
78
|
+
plan,
|
|
79
|
+
artifacts: [
|
|
80
|
+
{ artifactId: 'component:boot', binary: componentBinary },
|
|
81
|
+
{
|
|
82
|
+
artifactId: 'resource:archive',
|
|
83
|
+
binary: archiveBinary,
|
|
84
|
+
materializedEntries: [{ entryName: 'manifest.json', binary: manifestBinary }],
|
|
85
|
+
},
|
|
86
|
+
],
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
expect(host.hostBindingGeneration).toBe(7);
|
|
90
|
+
expect(sdk.registerFirmwareUpdateHostBinding).toHaveBeenCalledWith(
|
|
91
|
+
expect.objectContaining({ preparedPlanDigest: host.preparedPlan.preparedPlanDigest })
|
|
92
|
+
);
|
|
93
|
+
expect(host.componentArtifacts.boot?.size).toBe(componentBinary.byteLength);
|
|
94
|
+
const opened = await artifactReader!.open({
|
|
95
|
+
artifactRef: host.componentArtifacts.boot!.artifactRef,
|
|
96
|
+
});
|
|
97
|
+
const chunk = await artifactReader!.read({
|
|
98
|
+
readerId: opened.readerId,
|
|
99
|
+
offset: 1,
|
|
100
|
+
length: 2,
|
|
101
|
+
});
|
|
102
|
+
expect(Array.from(new Uint8Array(chunk.data))).toEqual([2, 3]);
|
|
103
|
+
await artifactReader!.close({ readerId: opened.readerId });
|
|
104
|
+
|
|
105
|
+
host.release();
|
|
106
|
+
expect(unregisterFirmwareUpdateHostBinding).toHaveBeenCalledWith(7);
|
|
107
|
+
});
|
|
108
|
+
});
|
|
@@ -36,7 +36,10 @@ const createMethod = () => {
|
|
|
36
36
|
read: jest.fn(),
|
|
37
37
|
close,
|
|
38
38
|
};
|
|
39
|
-
const hostBindingGeneration = registerFirmwareUpdateHostBinding({
|
|
39
|
+
const hostBindingGeneration = registerFirmwareUpdateHostBinding({
|
|
40
|
+
artifactReader,
|
|
41
|
+
preparedPlanDigest: 'a'.repeat(64),
|
|
42
|
+
});
|
|
40
43
|
const method = new DeviceUpdateBootloader({
|
|
41
44
|
id: 1,
|
|
42
45
|
payload: {
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
} from '../../src/api/firmware/FirmwareHostBinding';
|
|
7
7
|
|
|
8
8
|
const createBinding = () => ({
|
|
9
|
+
preparedPlanDigest: 'a'.repeat(64),
|
|
9
10
|
artifactReader: {
|
|
10
11
|
open: jest.fn(() => Promise.resolve({ readerId: 'reader-1', size: 4 })),
|
|
11
12
|
read: jest.fn(() =>
|
|
@@ -67,4 +68,12 @@ describe('firmware host binding generation', () => {
|
|
|
67
68
|
readerId: 'stale-reader',
|
|
68
69
|
});
|
|
69
70
|
});
|
|
71
|
+
|
|
72
|
+
test('rejects a prepared plan from another host generation before opening artifacts', () => {
|
|
73
|
+
const generation = registerFirmwareUpdateHostBinding(createBinding());
|
|
74
|
+
|
|
75
|
+
expect(() => resolveFirmwareUpdateHostBinding(generation, 'b'.repeat(64))).toThrow(
|
|
76
|
+
'does not match the prepared plan'
|
|
77
|
+
);
|
|
78
|
+
});
|
|
70
79
|
});
|
|
@@ -297,12 +297,6 @@ describe('buildFirmwareUpdatePlan', () => {
|
|
|
297
297
|
forceTarget: 'firmware' as const,
|
|
298
298
|
release: {
|
|
299
299
|
components: {},
|
|
300
|
-
resourceBundles: [
|
|
301
|
-
{
|
|
302
|
-
name: 'images',
|
|
303
|
-
url: 'https://firmware.onekey.so/pro2/images.okpkg',
|
|
304
|
-
},
|
|
305
|
-
],
|
|
306
300
|
},
|
|
307
301
|
},
|
|
308
302
|
{
|
|
@@ -325,41 +319,21 @@ describe('buildFirmwareUpdatePlan', () => {
|
|
|
325
319
|
}
|
|
326
320
|
);
|
|
327
321
|
|
|
328
|
-
test
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
forceTarget: 'resource' as const,
|
|
336
|
-
expectedArtifactIds: ['resourceBundle:images'],
|
|
337
|
-
expectedTargets: ['resource'],
|
|
338
|
-
},
|
|
339
|
-
])(
|
|
340
|
-
'limits a Pro2 $forceTarget force to the selected artifact role',
|
|
341
|
-
({ forceTarget, expectedArtifactIds, expectedTargets }) => {
|
|
342
|
-
const plan = buildFirmwareUpdatePlan(
|
|
343
|
-
createProtocolV2ForceInput([forceTarget], {
|
|
344
|
-
components: {
|
|
345
|
-
applicationP1: {
|
|
346
|
-
target: 'APPLICATION_P1',
|
|
347
|
-
url: 'https://firmware.onekey.so/pro2/application-p1.bin',
|
|
348
|
-
},
|
|
322
|
+
test('limits a Pro2 firmware force to component artifacts', () => {
|
|
323
|
+
const plan = buildFirmwareUpdatePlan(
|
|
324
|
+
createProtocolV2ForceInput(['firmware'], {
|
|
325
|
+
components: {
|
|
326
|
+
applicationP1: {
|
|
327
|
+
target: 'APPLICATION_P1',
|
|
328
|
+
url: 'https://firmware.onekey.so/pro2/application-p1.bin',
|
|
349
329
|
},
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
url: 'https://firmware.onekey.so/pro2/images.okpkg',
|
|
354
|
-
},
|
|
355
|
-
],
|
|
356
|
-
})
|
|
357
|
-
);
|
|
330
|
+
},
|
|
331
|
+
})
|
|
332
|
+
);
|
|
358
333
|
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
);
|
|
334
|
+
expect(plan.artifacts.map(artifact => artifact.artifactId)).toEqual(['component:app_v1']);
|
|
335
|
+
expect(plan.targetsToUpdate).toEqual(['app_v1']);
|
|
336
|
+
});
|
|
363
337
|
|
|
364
338
|
test('rejects a prepared native plan without a stable device identity', () => {
|
|
365
339
|
const features = createFeatures({
|
|
@@ -384,7 +358,7 @@ describe('buildFirmwareUpdatePlan', () => {
|
|
|
384
358
|
).toThrow('requires a stable device identity');
|
|
385
359
|
});
|
|
386
360
|
|
|
387
|
-
test('maps the Pro2 component order
|
|
361
|
+
test('maps the Pro2 component order without downloading artifacts', () => {
|
|
388
362
|
const plan = buildFirmwareUpdatePlan({
|
|
389
363
|
features: createFeatures({ deviceType: EDeviceType.Pro2 }),
|
|
390
364
|
firmwareType: EFirmwareType.Universal,
|
|
@@ -409,12 +383,6 @@ describe('buildFirmwareUpdatePlan', () => {
|
|
|
409
383
|
url: 'https://firmware.onekey.so/pro2/se01.bin',
|
|
410
384
|
},
|
|
411
385
|
},
|
|
412
|
-
resourceBundles: [
|
|
413
|
-
{
|
|
414
|
-
name: 'images',
|
|
415
|
-
url: 'https://firmware.onekey.so/pro2/images.okpkg',
|
|
416
|
-
},
|
|
417
|
-
],
|
|
418
386
|
},
|
|
419
387
|
},
|
|
420
388
|
ble: noUpdate,
|
|
@@ -426,9 +394,8 @@ describe('buildFirmwareUpdatePlan', () => {
|
|
|
426
394
|
'component:boot',
|
|
427
395
|
'component:app_v1',
|
|
428
396
|
'component:se01',
|
|
429
|
-
'resourceBundle:images',
|
|
430
397
|
]);
|
|
431
|
-
expect(plan.targetsToUpdate).toEqual(['boot', 'app_v1', 'se01'
|
|
398
|
+
expect(plan.targetsToUpdate).toEqual(['boot', 'app_v1', 'se01']);
|
|
432
399
|
expect(plan.artifacts[1]).toEqual(
|
|
433
400
|
expect.objectContaining({
|
|
434
401
|
expectedSize: 1024,
|
|
@@ -477,28 +444,6 @@ describe('buildFirmwareUpdatePlan', () => {
|
|
|
477
444
|
},
|
|
478
445
|
expectedError: 'duplicates target app_v1',
|
|
479
446
|
},
|
|
480
|
-
{
|
|
481
|
-
label: 'resource bundle name',
|
|
482
|
-
release: {
|
|
483
|
-
components: {
|
|
484
|
-
application: {
|
|
485
|
-
target: 'APPLICATION_P1',
|
|
486
|
-
url: 'https://firmware.onekey.so/pro2/application.bin',
|
|
487
|
-
},
|
|
488
|
-
},
|
|
489
|
-
resourceBundles: [
|
|
490
|
-
{
|
|
491
|
-
name: 'images',
|
|
492
|
-
url: 'https://firmware.onekey.so/pro2/images.okpkg',
|
|
493
|
-
},
|
|
494
|
-
{
|
|
495
|
-
name: 'images',
|
|
496
|
-
url: 'https://firmware.onekey.so/pro2/images-duplicate.okpkg',
|
|
497
|
-
},
|
|
498
|
-
],
|
|
499
|
-
},
|
|
500
|
-
expectedError: 'duplicates name images',
|
|
501
|
-
},
|
|
502
447
|
])('rejects a duplicate Pro2 $label', ({ release, expectedError }) => {
|
|
503
448
|
expect(() =>
|
|
504
449
|
buildFirmwareUpdatePlan({
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { EDeviceType, EFirmwareType } from '@onekeyfe/hd-shared';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
buildFirmwareUpdatePlan,
|
|
5
|
+
digestFirmwareUpdateContract,
|
|
6
|
+
} from '../../src/api/firmware/FirmwareUpdatePlan';
|
|
4
7
|
import {
|
|
5
8
|
assertFirmwareUpdatePreparedPlanBinding,
|
|
6
9
|
assertFirmwareUpdatePreparedPlanDeviceIdentity,
|
|
@@ -209,6 +212,55 @@ describe('FirmwareUpdatePreparedPlan', () => {
|
|
|
209
212
|
).toThrow('entry name is invalid');
|
|
210
213
|
});
|
|
211
214
|
|
|
215
|
+
test('rejects duplicate materialized entry names during standalone validation', () => {
|
|
216
|
+
const plan = buildPlan();
|
|
217
|
+
const prepared = prepareFirmwareUpdatePlan({
|
|
218
|
+
plan,
|
|
219
|
+
leaseRef: 'fwlease:00000000-0000-4000-8000-000000000001',
|
|
220
|
+
artifacts: [
|
|
221
|
+
{
|
|
222
|
+
artifactId: 'firmware',
|
|
223
|
+
artifact: artifact('a'.repeat(64), 4),
|
|
224
|
+
},
|
|
225
|
+
{
|
|
226
|
+
artifactId: 'resource',
|
|
227
|
+
artifact: artifact('b'.repeat(64), 8),
|
|
228
|
+
materializedEntries: [
|
|
229
|
+
{
|
|
230
|
+
entryName: 'resource/index.bin',
|
|
231
|
+
artifact: artifact('c'.repeat(64), 2),
|
|
232
|
+
},
|
|
233
|
+
],
|
|
234
|
+
},
|
|
235
|
+
],
|
|
236
|
+
});
|
|
237
|
+
const resourceArtifact = prepared.artifacts.find(item => item.target === 'resource')!;
|
|
238
|
+
const tamperedWithoutDigest = {
|
|
239
|
+
...prepared,
|
|
240
|
+
artifacts: prepared.artifacts.map(item =>
|
|
241
|
+
item === resourceArtifact
|
|
242
|
+
? {
|
|
243
|
+
...item,
|
|
244
|
+
materializedEntries: [
|
|
245
|
+
...resourceArtifact.materializedEntries!,
|
|
246
|
+
{
|
|
247
|
+
entryName: 'other/index.bin',
|
|
248
|
+
artifact: artifact('d'.repeat(64), 2),
|
|
249
|
+
},
|
|
250
|
+
],
|
|
251
|
+
}
|
|
252
|
+
: item
|
|
253
|
+
),
|
|
254
|
+
};
|
|
255
|
+
const { preparedPlanDigest: _preparedPlanDigest, ...digestInput } = tamperedWithoutDigest;
|
|
256
|
+
const tampered = {
|
|
257
|
+
...tamperedWithoutDigest,
|
|
258
|
+
preparedPlanDigest: digestFirmwareUpdateContract(digestInput),
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
expect(() => validateFirmwareUpdatePreparedPlan(tampered)).toThrow('duplicate entry names');
|
|
262
|
+
});
|
|
263
|
+
|
|
212
264
|
test('rejects unknown fields even when a caller presents a plan-shaped object', () => {
|
|
213
265
|
const plan = buildPlan();
|
|
214
266
|
expect(() =>
|
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
import axios from 'axios';
|
|
2
2
|
import { sha256 } from '@noble/hashes/sha256';
|
|
3
|
-
import { HardwareErrorCode } from '@onekeyfe/hd-shared';
|
|
3
|
+
import { EDeviceType, HardwareErrorCode } from '@onekeyfe/hd-shared';
|
|
4
4
|
|
|
5
5
|
import { DataManager } from '../src/data-manager';
|
|
6
6
|
import {
|
|
7
|
-
isProtocolV2ResourceFileValid,
|
|
8
7
|
parseProtocolV2ResourceManifest,
|
|
9
8
|
parseProtocolV2Resources,
|
|
10
|
-
prepareProtocolV2ResourceFiles,
|
|
11
9
|
} from '../src/protocols/protocol-v2/resources';
|
|
12
10
|
|
|
13
11
|
import type { ConnectSettings, RemoteConfigResponse } from '../src/types';
|
|
@@ -27,6 +25,12 @@ const resourceSource = {
|
|
|
27
25
|
archiveSize: 16_815_479,
|
|
28
26
|
};
|
|
29
27
|
|
|
28
|
+
const neoResourceSource = {
|
|
29
|
+
archiveUrl: 'https://example.com/resource/neo-resource.zip',
|
|
30
|
+
archiveSha256: 'b'.repeat(64),
|
|
31
|
+
archiveSize: 12_345_678,
|
|
32
|
+
};
|
|
33
|
+
|
|
30
34
|
const manifestFiles = [
|
|
31
35
|
['bundles/firmware_logo-build.okpkg', 'firmware_logo.okpkg', 'vol0:/bundles/firmware_logo.okpkg'],
|
|
32
36
|
['bundles/font/noto-build.okpkg', 'noto.okpkg', 'vol0:/bundles/font/noto.okpkg'],
|
|
@@ -105,6 +109,7 @@ const createRemoteConfig = (): RemoteConfigResponse =>
|
|
|
105
109
|
touch: { firmware: [], ble: [] },
|
|
106
110
|
pro: { firmware: [], ble: [] },
|
|
107
111
|
pro2: { firmware: [], ble: [], resources: { source: resourceSource } },
|
|
112
|
+
neo: { firmware: [], ble: [], resources: { source: neoResourceSource } },
|
|
108
113
|
bridge: {},
|
|
109
114
|
} as unknown as RemoteConfigResponse);
|
|
110
115
|
|
|
@@ -151,42 +156,6 @@ describe('Pro2 resource configuration', () => {
|
|
|
151
156
|
).toThrow('archive_path');
|
|
152
157
|
});
|
|
153
158
|
|
|
154
|
-
test('verifies selected manifest files and preserves manifest device paths', () => {
|
|
155
|
-
const prepared = prepareProtocolV2ResourceFiles({
|
|
156
|
-
manifest: resourceManifest,
|
|
157
|
-
files: manifestFiles.map(file => ({
|
|
158
|
-
archivePath: file.archive_path,
|
|
159
|
-
binary: file.binary,
|
|
160
|
-
})),
|
|
161
|
-
targetsToUpdate: ['resource'],
|
|
162
|
-
});
|
|
163
|
-
expect(prepared.map(file => file.devicePath)).toEqual(
|
|
164
|
-
manifestFiles.map(file => file.device_path)
|
|
165
|
-
);
|
|
166
|
-
expect(() =>
|
|
167
|
-
prepareProtocolV2ResourceFiles({
|
|
168
|
-
manifest: resourceManifest,
|
|
169
|
-
files: manifestFiles.map((file, index) => ({
|
|
170
|
-
archivePath: file.archive_path,
|
|
171
|
-
binary: index === 0 ? new Uint8Array([0]).buffer : file.binary,
|
|
172
|
-
})),
|
|
173
|
-
targetsToUpdate: ['resource'],
|
|
174
|
-
})
|
|
175
|
-
).toThrow('verification failed');
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
test('verifies both full file size and SHA-256 before transfer', () => {
|
|
179
|
-
const bytes = new Uint8Array([1, 2, 3]);
|
|
180
|
-
const binary = bytes.buffer;
|
|
181
|
-
const identity = { size: bytes.byteLength, fileHash: bytesToHex(sha256(bytes)) };
|
|
182
|
-
|
|
183
|
-
expect(isProtocolV2ResourceFileValid(binary, identity)).toBe(true);
|
|
184
|
-
expect(isProtocolV2ResourceFileValid(binary, { ...identity, size: 4 })).toBe(false);
|
|
185
|
-
expect(isProtocolV2ResourceFileValid(binary, { ...identity, fileHash: '0'.repeat(64) })).toBe(
|
|
186
|
-
false
|
|
187
|
-
);
|
|
188
|
-
});
|
|
189
|
-
|
|
190
159
|
test('applies a validated pre-release config and exposes its archive source', async () => {
|
|
191
160
|
const configFetcher = jest.fn().mockResolvedValue(createRemoteConfig());
|
|
192
161
|
|
|
@@ -217,6 +186,34 @@ describe('Pro2 resource configuration', () => {
|
|
|
217
186
|
await expect(DataManager.load(createSettings(configFetcher))).resolves.toBe(true);
|
|
218
187
|
|
|
219
188
|
expect(DataManager.getProtocolV2ResourceSource()).toBeUndefined();
|
|
189
|
+
expect(DataManager.protocolV2ResourcesConfigError).toBeInstanceOf(Error);
|
|
190
|
+
expect(DataManager.getProtocolV2ResourceSource(EDeviceType.Neo)).toEqual(neoResourceSource);
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
test('keeps Pro2 resources available when remote Neo resources are invalid', async () => {
|
|
194
|
+
const remoteConfig = createRemoteConfig();
|
|
195
|
+
(remoteConfig.neo as { resources?: unknown }).resources = { source: {} };
|
|
196
|
+
const configFetcher = jest.fn().mockResolvedValue(remoteConfig);
|
|
197
|
+
|
|
198
|
+
await expect(DataManager.load(createSettings(configFetcher))).resolves.toBe(true);
|
|
199
|
+
|
|
200
|
+
expect(DataManager.getProtocolV2ResourceSource(EDeviceType.Pro2)).toEqual(resourceSource);
|
|
201
|
+
expect(DataManager.getProtocolV2ResourceSource(EDeviceType.Neo)).toBeUndefined();
|
|
202
|
+
await expect(
|
|
203
|
+
DataManager.forceReloadData({
|
|
204
|
+
requireResources: true,
|
|
205
|
+
resourceDeviceType: EDeviceType.Pro2,
|
|
206
|
+
})
|
|
207
|
+
).resolves.toBeUndefined();
|
|
208
|
+
await expect(
|
|
209
|
+
DataManager.forceReloadData({
|
|
210
|
+
requireResources: true,
|
|
211
|
+
resourceDeviceType: EDeviceType.Neo,
|
|
212
|
+
})
|
|
213
|
+
).rejects.toMatchObject({
|
|
214
|
+
errorCode: HardwareErrorCode.FirmwareUpdateDownloadFailed,
|
|
215
|
+
message: expect.stringContaining('Invalid Neo resources config'),
|
|
216
|
+
});
|
|
220
217
|
});
|
|
221
218
|
|
|
222
219
|
test('only blocks resource mutation when the refreshed Pro2 resources are invalid', async () => {
|
|
@@ -233,6 +230,29 @@ describe('Pro2 resource configuration', () => {
|
|
|
233
230
|
expect(DataManager.lastCheckTimestamp).toBeGreaterThan(0);
|
|
234
231
|
});
|
|
235
232
|
|
|
233
|
+
test('checks resource configuration errors for the requested device only', async () => {
|
|
234
|
+
const remoteConfig = createRemoteConfig();
|
|
235
|
+
(remoteConfig.pro2 as { resources?: unknown }).resources = { source: {} };
|
|
236
|
+
const settings = createSettings(jest.fn().mockResolvedValue(remoteConfig));
|
|
237
|
+
DataManager.settings = settings;
|
|
238
|
+
|
|
239
|
+
await expect(
|
|
240
|
+
DataManager.forceReloadData({
|
|
241
|
+
requireResources: true,
|
|
242
|
+
resourceDeviceType: EDeviceType.Neo,
|
|
243
|
+
})
|
|
244
|
+
).resolves.toBeUndefined();
|
|
245
|
+
await expect(
|
|
246
|
+
DataManager.forceReloadData({
|
|
247
|
+
requireResources: true,
|
|
248
|
+
resourceDeviceType: EDeviceType.Pro2,
|
|
249
|
+
})
|
|
250
|
+
).rejects.toMatchObject({
|
|
251
|
+
errorCode: HardwareErrorCode.FirmwareUpdateDownloadFailed,
|
|
252
|
+
message: expect.stringContaining('Invalid Pro2 resources config'),
|
|
253
|
+
});
|
|
254
|
+
});
|
|
255
|
+
|
|
236
256
|
test('does not advance the cache timestamp when refresh fails', async () => {
|
|
237
257
|
jest.spyOn(axios, 'get').mockRejectedValue(new Error('offline'));
|
|
238
258
|
const settings = createSettings(jest.fn().mockResolvedValue(null));
|