@onekeyfe/hd-core 1.2.0-alpha.97 → 1.2.0-alpha.98
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 +404 -84
- package/__tests__/check-firmware-release-protocol-v2.test.ts +199 -0
- package/__tests__/firmware-memory-host.test.ts +112 -0
- package/__tests__/firmware-update/device-update-bootloader.test.ts +13 -2
- package/__tests__/firmware-update/firmware-host-binding.test.ts +9 -0
- package/__tests__/firmware-update/firmware-prepared-host-digest.test.ts +573 -0
- package/__tests__/firmware-update/firmware-prepared-resource-archive.test.ts +150 -0
- package/__tests__/firmware-update/firmware-update-plan.test.ts +232 -70
- package/__tests__/firmware-update/firmware-update-prepared-plan.test.ts +53 -1
- package/__tests__/firmware-update/firmware-update-prepared-resource-executors.test.ts +326 -0
- package/__tests__/firmware-update/firmware-update-v2-download-before-boot.test.ts +2 -2
- package/__tests__/protocol-v2-resources.test.ts +148 -232
- package/__tests__/protocol-v2.test.ts +1236 -516
- package/dist/api/CheckAllFirmwareRelease.d.ts +2 -3
- package/dist/api/CheckAllFirmwareRelease.d.ts.map +1 -1
- package/dist/api/CheckBLEFirmwareRelease.d.ts +3 -2
- package/dist/api/CheckBLEFirmwareRelease.d.ts.map +1 -1
- package/dist/api/CheckBootloaderRelease.d.ts +2 -1
- package/dist/api/CheckBootloaderRelease.d.ts.map +1 -1
- package/dist/api/CheckFirmwareRelease.d.ts +3 -2
- package/dist/api/CheckFirmwareRelease.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV2.d.ts +1 -0
- package/dist/api/FirmwareUpdateV2.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV3.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV4.d.ts +8 -8
- package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
- package/dist/api/UploadPortfolio.d.ts.map +1 -1
- package/dist/api/device/DeviceUpdateBootloader.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 +22 -0
- package/dist/api/firmware/FirmwareMemoryHost.d.ts.map +1 -0
- package/dist/api/firmware/FirmwarePreparedResourceArchive.d.ts +11 -0
- package/dist/api/firmware/FirmwarePreparedResourceArchive.d.ts.map +1 -0
- package/dist/api/firmware/FirmwareUpdatePlan.d.ts +48 -1
- package/dist/api/firmware/FirmwareUpdatePlan.d.ts.map +1 -1
- package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts +6 -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/api/firmware/protocolV2Release.d.ts +33 -0
- package/dist/api/firmware/protocolV2Release.d.ts.map +1 -0
- package/dist/data-manager/DataManager.d.ts +4 -3
- package/dist/data-manager/DataManager.d.ts.map +1 -1
- package/dist/index.d.ts +108 -114
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1633 -967
- package/dist/protocols/protocol-v2/resources.d.ts +10 -28
- package/dist/protocols/protocol-v2/resources.d.ts.map +1 -1
- package/dist/types/api/checkAllFirmwareRelease.d.ts +20 -8
- package/dist/types/api/checkAllFirmwareRelease.d.ts.map +1 -1
- package/dist/types/api/checkBLEFirmwareRelease.d.ts +2 -13
- package/dist/types/api/checkBLEFirmwareRelease.d.ts.map +1 -1
- package/dist/types/api/checkBootloaderRelease.d.ts +2 -6
- package/dist/types/api/checkBootloaderRelease.d.ts.map +1 -1
- package/dist/types/api/checkFirmwareRelease.d.ts +2 -13
- package/dist/types/api/checkFirmwareRelease.d.ts.map +1 -1
- package/dist/types/api/checkFirmwareTypeAvailable.d.ts +2 -2
- package/dist/types/api/checkFirmwareTypeAvailable.d.ts.map +1 -1
- package/dist/types/api/deviceUpdateBootloader.d.ts.map +1 -1
- package/dist/types/api/export.d.ts +1 -1
- package/dist/types/api/export.d.ts.map +1 -1
- package/dist/types/api/firmwareUpdate.d.ts +4 -12
- package/dist/types/api/firmwareUpdate.d.ts.map +1 -1
- package/dist/types/api/firmwareUpdatePlan.d.ts +2 -2
- package/dist/types/api/firmwareUpdatePlan.d.ts.map +1 -1
- package/dist/types/settings.d.ts +35 -36
- package/dist/types/settings.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/api/CheckAllFirmwareRelease.ts +108 -80
- package/src/api/CheckBLEFirmwareRelease.ts +37 -5
- package/src/api/CheckBootloaderRelease.ts +35 -3
- package/src/api/CheckFirmwareRelease.ts +35 -7
- package/src/api/FirmwareUpdateV2.ts +108 -73
- package/src/api/FirmwareUpdateV3.ts +101 -56
- package/src/api/FirmwareUpdateV4.ts +885 -403
- package/src/api/UploadPortfolio.ts +18 -0
- package/src/api/device/DeviceUpdateBootloader.ts +37 -11
- package/src/api/firmware/FirmwareHostBinding.ts +16 -3
- package/src/api/firmware/FirmwareMemoryHost.ts +142 -0
- package/src/api/firmware/FirmwarePreparedResourceArchive.ts +207 -0
- package/src/api/firmware/FirmwareUpdatePlan.ts +282 -122
- package/src/api/firmware/FirmwareUpdatePreparedPlan.ts +27 -3
- package/src/api/firmware/protocolV2Release.ts +168 -0
- package/src/data-manager/DataManager.ts +35 -19
- package/src/index.ts +10 -0
- package/src/protocols/protocol-v2/resources.ts +157 -341
- package/src/types/api/checkAllFirmwareRelease.ts +27 -12
- package/src/types/api/checkBLEFirmwareRelease.ts +4 -13
- package/src/types/api/checkBootloaderRelease.ts +2 -6
- package/src/types/api/checkFirmwareRelease.ts +2 -13
- package/src/types/api/checkFirmwareTypeAvailable.ts +2 -2
- package/src/types/api/deviceUpdateBootloader.ts +1 -0
- package/src/types/api/export.ts +6 -1
- package/src/types/api/firmwareUpdate.ts +12 -17
- package/src/types/api/firmwareUpdatePlan.ts +2 -2
- package/src/types/settings.ts +35 -61
- 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
|
+
});
|
|
@@ -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
|
+
assertFirmwareUpdatePreparedPlanDeviceIdentity,
|
|
6
|
+
prepareFirmwareUpdatePlan,
|
|
7
|
+
} from '../../src/api/firmware/FirmwareUpdatePreparedPlan';
|
|
4
8
|
|
|
5
9
|
import type { Features } from '../../src/types';
|
|
6
10
|
|
|
@@ -50,6 +54,8 @@ const createLegacyForceInput = (
|
|
|
50
54
|
release: Record<string, unknown> = {
|
|
51
55
|
url: 'https://firmware.onekey.so/pro/firmware.bin',
|
|
52
56
|
version: [4, 21, 0],
|
|
57
|
+
expectedSize: 1024,
|
|
58
|
+
fingerprint: '1'.repeat(64),
|
|
53
59
|
}
|
|
54
60
|
): BuildPlanInput => ({
|
|
55
61
|
features: createFeatures({
|
|
@@ -168,6 +174,8 @@ describe('buildFirmwareUpdatePlan', () => {
|
|
|
168
174
|
release: {
|
|
169
175
|
url: 'https://firmware.onekey.so/pro/firmware.bin',
|
|
170
176
|
version: [4, 0, 0],
|
|
177
|
+
expectedSize: 1024,
|
|
178
|
+
fingerprint: '1'.repeat(64),
|
|
171
179
|
},
|
|
172
180
|
},
|
|
173
181
|
ble: noUpdate,
|
|
@@ -193,7 +201,11 @@ describe('buildFirmwareUpdatePlan', () => {
|
|
|
193
201
|
release: {
|
|
194
202
|
url: 'https://firmware.onekey.so/pro/firmware.bin',
|
|
195
203
|
version: [4, 21, 0],
|
|
204
|
+
expectedSize: 1024,
|
|
205
|
+
fingerprint: '1'.repeat(64),
|
|
196
206
|
resource: 'https://firmware.onekey.so/pro/resource.zip',
|
|
207
|
+
resourceExpectedSize: 4096,
|
|
208
|
+
resourceFingerprint: '1'.repeat(64),
|
|
197
209
|
},
|
|
198
210
|
},
|
|
199
211
|
ble: {
|
|
@@ -201,6 +213,8 @@ describe('buildFirmwareUpdatePlan', () => {
|
|
|
201
213
|
release: {
|
|
202
214
|
webUpdate: 'https://firmware.onekey.so/pro/ble.bin',
|
|
203
215
|
version: [2, 3, 7],
|
|
216
|
+
expectedSize: 512,
|
|
217
|
+
fingerprintWeb: '2'.repeat(64),
|
|
204
218
|
},
|
|
205
219
|
},
|
|
206
220
|
bootloader: {
|
|
@@ -208,6 +222,8 @@ describe('buildFirmwareUpdatePlan', () => {
|
|
|
208
222
|
release: {
|
|
209
223
|
bootloaderResource: 'https://firmware.onekey.so/pro/bootloader.bin',
|
|
210
224
|
bootloaderVersion: [2, 8, 4],
|
|
225
|
+
bootloaderExpectedSize: 768,
|
|
226
|
+
bootloaderFingerprint: '3'.repeat(64),
|
|
211
227
|
},
|
|
212
228
|
},
|
|
213
229
|
forceUpdateTargets: ['firmware', 'resource', 'ble', 'bootloader'],
|
|
@@ -237,6 +253,8 @@ describe('buildFirmwareUpdatePlan', () => {
|
|
|
237
253
|
release: {
|
|
238
254
|
url: 'https://firmware.onekey.so/pro/firmware.bin',
|
|
239
255
|
version: [4, 21, 0],
|
|
256
|
+
expectedSize: 1024,
|
|
257
|
+
fingerprint: '1'.repeat(64),
|
|
240
258
|
resource: 'https://firmware.onekey.so/pro/resource.zip',
|
|
241
259
|
},
|
|
242
260
|
},
|
|
@@ -249,6 +267,32 @@ describe('buildFirmwareUpdatePlan', () => {
|
|
|
249
267
|
expect(plan.targetsToUpdate).toEqual(['firmware']);
|
|
250
268
|
});
|
|
251
269
|
|
|
270
|
+
test('does not approve an unexecutable V2 resource during bootloader recovery', () => {
|
|
271
|
+
const features = createFeatures({ deviceType: EDeviceType.Classic1s });
|
|
272
|
+
features.bootloaderMode = true;
|
|
273
|
+
const plan = buildFirmwareUpdatePlan({
|
|
274
|
+
features,
|
|
275
|
+
firmwareType: EFirmwareType.Universal,
|
|
276
|
+
platform: 'desktop',
|
|
277
|
+
firmware: {
|
|
278
|
+
status: 'none',
|
|
279
|
+
release: {
|
|
280
|
+
url: 'https://firmware.onekey.so/classic/firmware.bin',
|
|
281
|
+
expectedSize: 1024,
|
|
282
|
+
fingerprint: '1'.repeat(64),
|
|
283
|
+
resource: 'https://firmware.onekey.so/classic/resource.zip',
|
|
284
|
+
resourceExpectedSize: 4096,
|
|
285
|
+
resourceFingerprint: '2'.repeat(64),
|
|
286
|
+
},
|
|
287
|
+
},
|
|
288
|
+
ble: noUpdate,
|
|
289
|
+
bootloader: noUpdate,
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
expect(plan.artifacts.map(artifact => artifact.artifactId)).toEqual(['firmware']);
|
|
293
|
+
expect(plan.targetsToUpdate).toEqual(['firmware']);
|
|
294
|
+
});
|
|
295
|
+
|
|
252
296
|
test.each([
|
|
253
297
|
{
|
|
254
298
|
label: 'a non-array value',
|
|
@@ -272,6 +316,65 @@ describe('buildFirmwareUpdatePlan', () => {
|
|
|
272
316
|
expectFirmwarePlanInvalid(() => buildFirmwareUpdatePlan(createLegacyForceInput(['resource'])));
|
|
273
317
|
});
|
|
274
318
|
|
|
319
|
+
test('rejects a legacy resource archive without complete integrity metadata', () => {
|
|
320
|
+
expectFirmwarePlanInvalid(
|
|
321
|
+
() =>
|
|
322
|
+
buildFirmwareUpdatePlan(
|
|
323
|
+
createLegacyForceInput(['resource'], {
|
|
324
|
+
url: 'https://firmware.onekey.so/pro/firmware.bin',
|
|
325
|
+
resource: 'https://firmware.onekey.so/pro/resource.zip',
|
|
326
|
+
resourceExpectedSize: 4096,
|
|
327
|
+
})
|
|
328
|
+
),
|
|
329
|
+
'integrity metadata is invalid'
|
|
330
|
+
);
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
test.each(['firmware', 'ble', 'bootloader'] as const)(
|
|
334
|
+
'rejects a remote legacy %s artifact without complete integrity metadata',
|
|
335
|
+
target => {
|
|
336
|
+
expectFirmwarePlanInvalid(
|
|
337
|
+
() =>
|
|
338
|
+
buildFirmwareUpdatePlan({
|
|
339
|
+
features: createFeatures({ deviceType: EDeviceType.Classic1s }),
|
|
340
|
+
firmwareType: EFirmwareType.Universal,
|
|
341
|
+
platform: 'desktop',
|
|
342
|
+
firmware:
|
|
343
|
+
target === 'firmware'
|
|
344
|
+
? {
|
|
345
|
+
status: 'outdated',
|
|
346
|
+
release: {
|
|
347
|
+
url: 'https://firmware.onekey.so/classic/firmware.bin',
|
|
348
|
+
expectedSize: 1024,
|
|
349
|
+
},
|
|
350
|
+
}
|
|
351
|
+
: noUpdate,
|
|
352
|
+
ble:
|
|
353
|
+
target === 'ble'
|
|
354
|
+
? {
|
|
355
|
+
status: 'outdated',
|
|
356
|
+
release: {
|
|
357
|
+
webUpdate: 'https://firmware.onekey.so/classic/ble.bin',
|
|
358
|
+
expectedSize: 512,
|
|
359
|
+
},
|
|
360
|
+
}
|
|
361
|
+
: noUpdate,
|
|
362
|
+
bootloader:
|
|
363
|
+
target === 'bootloader'
|
|
364
|
+
? {
|
|
365
|
+
status: 'outdated',
|
|
366
|
+
release: {
|
|
367
|
+
bootloaderResource: 'https://firmware.onekey.so/classic/bootloader.bin',
|
|
368
|
+
bootloaderExpectedSize: 768,
|
|
369
|
+
},
|
|
370
|
+
}
|
|
371
|
+
: noUpdate,
|
|
372
|
+
}),
|
|
373
|
+
'integrity metadata is invalid'
|
|
374
|
+
);
|
|
375
|
+
}
|
|
376
|
+
);
|
|
377
|
+
|
|
275
378
|
test.each(['ble', 'bootloader'] as const)(
|
|
276
379
|
'rejects the unsupported Pro2 %s force target',
|
|
277
380
|
forceTarget => {
|
|
@@ -297,12 +400,6 @@ describe('buildFirmwareUpdatePlan', () => {
|
|
|
297
400
|
forceTarget: 'firmware' as const,
|
|
298
401
|
release: {
|
|
299
402
|
components: {},
|
|
300
|
-
resourceBundles: [
|
|
301
|
-
{
|
|
302
|
-
name: 'images',
|
|
303
|
-
url: 'https://firmware.onekey.so/pro2/images.okpkg',
|
|
304
|
-
},
|
|
305
|
-
],
|
|
306
403
|
},
|
|
307
404
|
},
|
|
308
405
|
{
|
|
@@ -312,6 +409,8 @@ describe('buildFirmwareUpdatePlan', () => {
|
|
|
312
409
|
applicationP1: {
|
|
313
410
|
target: 'APPLICATION_P1',
|
|
314
411
|
url: 'https://firmware.onekey.so/pro2/application-p1.bin',
|
|
412
|
+
expectedSize: 1024,
|
|
413
|
+
fingerprint: '1111111111111111111111111111111111111111111111111111111111111111',
|
|
315
414
|
},
|
|
316
415
|
},
|
|
317
416
|
},
|
|
@@ -325,41 +424,23 @@ describe('buildFirmwareUpdatePlan', () => {
|
|
|
325
424
|
}
|
|
326
425
|
);
|
|
327
426
|
|
|
328
|
-
test
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
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
|
-
},
|
|
427
|
+
test('limits a Pro2 firmware force to component artifacts', () => {
|
|
428
|
+
const plan = buildFirmwareUpdatePlan(
|
|
429
|
+
createProtocolV2ForceInput(['firmware'], {
|
|
430
|
+
components: {
|
|
431
|
+
applicationP1: {
|
|
432
|
+
target: 'APPLICATION_P1',
|
|
433
|
+
url: 'https://firmware.onekey.so/pro2/application-p1.bin',
|
|
434
|
+
expectedSize: 1024,
|
|
435
|
+
fingerprint: '1111111111111111111111111111111111111111111111111111111111111111',
|
|
349
436
|
},
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
url: 'https://firmware.onekey.so/pro2/images.okpkg',
|
|
354
|
-
},
|
|
355
|
-
],
|
|
356
|
-
})
|
|
357
|
-
);
|
|
437
|
+
},
|
|
438
|
+
})
|
|
439
|
+
);
|
|
358
440
|
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
);
|
|
441
|
+
expect(plan.artifacts.map(artifact => artifact.artifactId)).toEqual(['component:app_v1']);
|
|
442
|
+
expect(plan.targetsToUpdate).toEqual(['app_v1']);
|
|
443
|
+
});
|
|
363
444
|
|
|
364
445
|
test('rejects a prepared native plan without a stable device identity', () => {
|
|
365
446
|
const features = createFeatures({
|
|
@@ -376,6 +457,8 @@ describe('buildFirmwareUpdatePlan', () => {
|
|
|
376
457
|
status: 'outdated',
|
|
377
458
|
release: {
|
|
378
459
|
url: 'https://firmware.onekey.so/classic/firmware.bin',
|
|
460
|
+
expectedSize: 1024,
|
|
461
|
+
fingerprint: '1'.repeat(64),
|
|
379
462
|
},
|
|
380
463
|
},
|
|
381
464
|
ble: noUpdate,
|
|
@@ -384,7 +467,72 @@ describe('buildFirmwareUpdatePlan', () => {
|
|
|
384
467
|
).toThrow('requires a stable device identity');
|
|
385
468
|
});
|
|
386
469
|
|
|
387
|
-
test('
|
|
470
|
+
test('allows an identity-less Protocol V2 device while retaining the model binding', () => {
|
|
471
|
+
const features = createFeatures({
|
|
472
|
+
deviceType: EDeviceType.Pro2,
|
|
473
|
+
});
|
|
474
|
+
delete (features as Partial<Features>).serialNo;
|
|
475
|
+
|
|
476
|
+
const plan = buildFirmwareUpdatePlan({
|
|
477
|
+
features,
|
|
478
|
+
firmwareType: EFirmwareType.Universal,
|
|
479
|
+
platform: 'desktop',
|
|
480
|
+
firmware: {
|
|
481
|
+
status: 'outdated',
|
|
482
|
+
release: {
|
|
483
|
+
components: {
|
|
484
|
+
applicationP1: {
|
|
485
|
+
target: 'APPLICATION_P1',
|
|
486
|
+
url: 'https://firmware.onekey.so/pro2/application-p1.bin',
|
|
487
|
+
expectedSize: 1024,
|
|
488
|
+
fingerprint: '1111111111111111111111111111111111111111111111111111111111111111',
|
|
489
|
+
},
|
|
490
|
+
},
|
|
491
|
+
},
|
|
492
|
+
},
|
|
493
|
+
ble: noUpdate,
|
|
494
|
+
bootloader: noUpdate,
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
expect(plan).toMatchObject({
|
|
498
|
+
executor: 'v4',
|
|
499
|
+
deviceIdentity: 'unavailable',
|
|
500
|
+
deviceModel: String(EDeviceType.Pro2),
|
|
501
|
+
});
|
|
502
|
+
|
|
503
|
+
const preparedPlan = prepareFirmwareUpdatePlan({
|
|
504
|
+
plan,
|
|
505
|
+
leaseRef: 'fwlease:00000000-0000-4000-8000-000000000004',
|
|
506
|
+
artifacts: [
|
|
507
|
+
{
|
|
508
|
+
artifactId: 'component:app_v1',
|
|
509
|
+
artifact: {
|
|
510
|
+
artifactRef: `fw:${'1'.repeat(64)}`,
|
|
511
|
+
size: 1024,
|
|
512
|
+
sha256: '1'.repeat(64),
|
|
513
|
+
},
|
|
514
|
+
},
|
|
515
|
+
],
|
|
516
|
+
});
|
|
517
|
+
expect(() =>
|
|
518
|
+
assertFirmwareUpdatePreparedPlanDeviceIdentity({
|
|
519
|
+
preparedPlan,
|
|
520
|
+
deviceIdentity: undefined,
|
|
521
|
+
bootloaderMode: false,
|
|
522
|
+
deviceModel: String(EDeviceType.Pro2),
|
|
523
|
+
})
|
|
524
|
+
).not.toThrow();
|
|
525
|
+
expect(() =>
|
|
526
|
+
assertFirmwareUpdatePreparedPlanDeviceIdentity({
|
|
527
|
+
preparedPlan,
|
|
528
|
+
deviceIdentity: undefined,
|
|
529
|
+
bootloaderMode: false,
|
|
530
|
+
deviceModel: String(EDeviceType.Neo),
|
|
531
|
+
})
|
|
532
|
+
).toThrow();
|
|
533
|
+
});
|
|
534
|
+
|
|
535
|
+
test('maps the Pro2 component order without downloading artifacts', () => {
|
|
388
536
|
const plan = buildFirmwareUpdatePlan({
|
|
389
537
|
features: createFeatures({ deviceType: EDeviceType.Pro2 }),
|
|
390
538
|
firmwareType: EFirmwareType.Universal,
|
|
@@ -403,18 +551,16 @@ describe('buildFirmwareUpdatePlan', () => {
|
|
|
403
551
|
bootloader: {
|
|
404
552
|
target: 'BOOTLOADER',
|
|
405
553
|
url: 'https://firmware.onekey.so/pro2/bootloader.bin',
|
|
554
|
+
expectedSize: 2048,
|
|
555
|
+
fingerprint: '2222222222222222222222222222222222222222222222222222222222222222',
|
|
406
556
|
},
|
|
407
557
|
se01: {
|
|
408
558
|
target: 'SE01',
|
|
409
559
|
url: 'https://firmware.onekey.so/pro2/se01.bin',
|
|
560
|
+
expectedSize: 512,
|
|
561
|
+
fingerprint: '3333333333333333333333333333333333333333333333333333333333333333',
|
|
410
562
|
},
|
|
411
563
|
},
|
|
412
|
-
resourceBundles: [
|
|
413
|
-
{
|
|
414
|
-
name: 'images',
|
|
415
|
-
url: 'https://firmware.onekey.so/pro2/images.okpkg',
|
|
416
|
-
},
|
|
417
|
-
],
|
|
418
564
|
},
|
|
419
565
|
},
|
|
420
566
|
ble: noUpdate,
|
|
@@ -426,9 +572,8 @@ describe('buildFirmwareUpdatePlan', () => {
|
|
|
426
572
|
'component:boot',
|
|
427
573
|
'component:app_v1',
|
|
428
574
|
'component:se01',
|
|
429
|
-
'resourceBundle:images',
|
|
430
575
|
]);
|
|
431
|
-
expect(plan.targetsToUpdate).toEqual(['boot', 'app_v1', 'se01'
|
|
576
|
+
expect(plan.targetsToUpdate).toEqual(['boot', 'app_v1', 'se01']);
|
|
432
577
|
expect(plan.artifacts[1]).toEqual(
|
|
433
578
|
expect.objectContaining({
|
|
434
579
|
expectedSize: 1024,
|
|
@@ -437,6 +582,41 @@ describe('buildFirmwareUpdatePlan', () => {
|
|
|
437
582
|
);
|
|
438
583
|
});
|
|
439
584
|
|
|
585
|
+
test.each([
|
|
586
|
+
{
|
|
587
|
+
expectedSize: undefined,
|
|
588
|
+
fingerprint: '1'.repeat(64),
|
|
589
|
+
},
|
|
590
|
+
{
|
|
591
|
+
expectedSize: 1024,
|
|
592
|
+
fingerprint: undefined,
|
|
593
|
+
},
|
|
594
|
+
])('rejects a remote Pro2 component without complete integrity metadata', integrity => {
|
|
595
|
+
expectFirmwarePlanInvalid(
|
|
596
|
+
() =>
|
|
597
|
+
buildFirmwareUpdatePlan({
|
|
598
|
+
features: createFeatures({ deviceType: EDeviceType.Pro2 }),
|
|
599
|
+
firmwareType: EFirmwareType.Universal,
|
|
600
|
+
platform: 'desktop',
|
|
601
|
+
firmware: {
|
|
602
|
+
status: 'required',
|
|
603
|
+
release: {
|
|
604
|
+
components: {
|
|
605
|
+
applicationP1: {
|
|
606
|
+
target: 'APPLICATION_P1',
|
|
607
|
+
url: 'https://firmware.onekey.so/pro2/application-p1.bin',
|
|
608
|
+
...integrity,
|
|
609
|
+
},
|
|
610
|
+
},
|
|
611
|
+
},
|
|
612
|
+
},
|
|
613
|
+
ble: noUpdate,
|
|
614
|
+
bootloader: noUpdate,
|
|
615
|
+
}),
|
|
616
|
+
'integrity metadata is invalid'
|
|
617
|
+
);
|
|
618
|
+
});
|
|
619
|
+
|
|
440
620
|
test('rejects Pro2 ROMLOADER because it requires a separate loader flow', () => {
|
|
441
621
|
expect(() =>
|
|
442
622
|
buildFirmwareUpdatePlan({
|
|
@@ -468,37 +648,19 @@ describe('buildFirmwareUpdatePlan', () => {
|
|
|
468
648
|
application: {
|
|
469
649
|
target: 'APPLICATION_P1',
|
|
470
650
|
url: 'https://firmware.onekey.so/pro2/application.bin',
|
|
651
|
+
expectedSize: 1024,
|
|
652
|
+
fingerprint: '1111111111111111111111111111111111111111111111111111111111111111',
|
|
471
653
|
},
|
|
472
654
|
applicationDuplicate: {
|
|
473
655
|
target: 'APPLICATION_P1',
|
|
474
656
|
url: 'https://firmware.onekey.so/pro2/application-duplicate.bin',
|
|
657
|
+
expectedSize: 1024,
|
|
658
|
+
fingerprint: '2222222222222222222222222222222222222222222222222222222222222222',
|
|
475
659
|
},
|
|
476
660
|
},
|
|
477
661
|
},
|
|
478
662
|
expectedError: 'duplicates target app_v1',
|
|
479
663
|
},
|
|
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
664
|
])('rejects a duplicate Pro2 $label', ({ release, expectedError }) => {
|
|
503
665
|
expect(() =>
|
|
504
666
|
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(() =>
|