@onekeyfe/hd-core 1.2.0-alpha.95 → 1.2.0-alpha.97
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 +84 -404
- package/__tests__/firmware-update/device-update-bootloader.test.ts +2 -6
- package/__tests__/firmware-update/firmware-host-binding.test.ts +0 -9
- package/__tests__/firmware-update/firmware-update-bootloader-poll.test.ts +112 -0
- package/__tests__/firmware-update/firmware-update-plan.test.ts +70 -131
- package/__tests__/firmware-update/firmware-update-prepared-plan.test.ts +1 -53
- package/__tests__/protocol-v2-resources.test.ts +232 -148
- package/__tests__/protocol-v2.test.ts +517 -1106
- package/dist/api/CheckAllFirmwareRelease.d.ts +3 -2
- package/dist/api/CheckAllFirmwareRelease.d.ts.map +1 -1
- package/dist/api/CheckBLEFirmwareRelease.d.ts +2 -3
- package/dist/api/CheckBLEFirmwareRelease.d.ts.map +1 -1
- package/dist/api/CheckBootloaderRelease.d.ts +1 -2
- package/dist/api/CheckBootloaderRelease.d.ts.map +1 -1
- package/dist/api/CheckFirmwareRelease.d.ts +2 -3
- package/dist/api/CheckFirmwareRelease.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV2.d.ts +0 -1
- 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 -6
- 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/FirmwareUpdateBaseMethod.d.ts.map +1 -1
- package/dist/api/firmware/FirmwareUpdatePlan.d.ts +1 -48
- 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 +1 -0
- package/dist/api/firmware/getBinary.d.ts.map +1 -1
- package/dist/data-manager/DataManager.d.ts +3 -4
- package/dist/data-manager/DataManager.d.ts.map +1 -1
- package/dist/index.d.ts +111 -103
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +849 -1301
- package/dist/protocols/protocol-v2/resources.d.ts +28 -10
- package/dist/protocols/protocol-v2/resources.d.ts.map +1 -1
- package/dist/types/api/checkAllFirmwareRelease.d.ts +8 -20
- package/dist/types/api/checkAllFirmwareRelease.d.ts.map +1 -1
- package/dist/types/api/checkBLEFirmwareRelease.d.ts +13 -2
- package/dist/types/api/checkBLEFirmwareRelease.d.ts.map +1 -1
- package/dist/types/api/checkBootloaderRelease.d.ts +6 -2
- package/dist/types/api/checkBootloaderRelease.d.ts.map +1 -1
- package/dist/types/api/checkFirmwareRelease.d.ts +13 -2
- 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/export.d.ts +1 -1
- package/dist/types/api/export.d.ts.map +1 -1
- package/dist/types/api/firmwareUpdate.d.ts +11 -3
- package/dist/types/api/firmwareUpdate.d.ts.map +1 -1
- package/dist/types/settings.d.ts +36 -35
- package/dist/types/settings.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/api/CheckAllFirmwareRelease.ts +80 -108
- package/src/api/CheckBLEFirmwareRelease.ts +5 -37
- package/src/api/CheckBootloaderRelease.ts +3 -35
- package/src/api/CheckFirmwareRelease.ts +7 -35
- package/src/api/FirmwareUpdateV2.ts +3 -15
- package/src/api/FirmwareUpdateV3.ts +6 -13
- package/src/api/FirmwareUpdateV4.ts +407 -824
- package/src/api/UploadPortfolio.ts +0 -18
- package/src/api/device/DeviceUpdateBootloader.ts +5 -26
- package/src/api/firmware/FirmwareHostBinding.ts +3 -16
- package/src/api/firmware/FirmwareUpdateBaseMethod.ts +2 -0
- package/src/api/firmware/FirmwareUpdatePlan.ts +88 -226
- package/src/api/firmware/FirmwareUpdatePreparedPlan.ts +3 -10
- package/src/data-manager/DataManager.ts +19 -35
- package/src/index.ts +0 -10
- package/src/protocols/protocol-v2/resources.ts +341 -157
- package/src/types/api/checkAllFirmwareRelease.ts +12 -27
- package/src/types/api/checkBLEFirmwareRelease.ts +13 -4
- package/src/types/api/checkBootloaderRelease.ts +6 -2
- package/src/types/api/checkFirmwareRelease.ts +13 -2
- package/src/types/api/checkFirmwareTypeAvailable.ts +2 -2
- package/src/types/api/export.ts +1 -6
- package/src/types/api/firmwareUpdate.ts +16 -10
- package/src/types/settings.ts +61 -35
- package/src/utils/deviceFeaturesUtils.ts +2 -2
- package/__tests__/check-firmware-release-protocol-v2.test.ts +0 -199
- package/__tests__/firmware-memory-host.test.ts +0 -112
- package/__tests__/firmware-update/firmware-prepared-host-digest.test.ts +0 -448
- package/dist/api/firmware/FirmwareMemoryHost.d.ts +0 -22
- package/dist/api/firmware/FirmwareMemoryHost.d.ts.map +0 -1
- package/dist/api/firmware/protocolV2Release.d.ts +0 -33
- package/dist/api/firmware/protocolV2Release.d.ts.map +0 -1
- package/src/api/firmware/FirmwareMemoryHost.ts +0 -142
- package/src/api/firmware/protocolV2Release.ts +0 -168
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
import { createDeviceNotSupportMethodError } from '@onekeyfe/hd-shared';
|
|
2
|
-
|
|
3
|
-
import { supportsProtocolV2Message } from '../protocols/protocol-v2/features';
|
|
4
1
|
import FileWrite from './FileWrite';
|
|
5
2
|
|
|
6
3
|
export type UploadPortfolioParams = {
|
|
@@ -10,8 +7,6 @@ export type UploadPortfolioParams = {
|
|
|
10
7
|
|
|
11
8
|
const PORTFOLIO_PENDING_PATH = 'vol1:/portfolio/portfolio.okpkg.pending';
|
|
12
9
|
const PORTFOLIO_CHUNK_SIZE = 2048;
|
|
13
|
-
const FILESYSTEM_FILE_WRITE_MESSAGE_TYPE = 60805;
|
|
14
|
-
const PORTFOLIO_UPDATE_MESSAGE_TYPE = 61400;
|
|
15
10
|
|
|
16
11
|
export default class UploadPortfolio extends FileWrite {
|
|
17
12
|
init() {
|
|
@@ -34,19 +29,6 @@ export default class UploadPortfolio extends FileWrite {
|
|
|
34
29
|
}
|
|
35
30
|
|
|
36
31
|
async run() {
|
|
37
|
-
const protocolInfo = await this.device.ensureProtocolV2RuntimeContext();
|
|
38
|
-
const hasFileWrite = supportsProtocolV2Message(
|
|
39
|
-
protocolInfo,
|
|
40
|
-
FILESYSTEM_FILE_WRITE_MESSAGE_TYPE
|
|
41
|
-
);
|
|
42
|
-
const hasPortfolioUpdate = supportsProtocolV2Message(
|
|
43
|
-
protocolInfo,
|
|
44
|
-
PORTFOLIO_UPDATE_MESSAGE_TYPE
|
|
45
|
-
);
|
|
46
|
-
if (!hasFileWrite || !hasPortfolioUpdate) {
|
|
47
|
-
throw createDeviceNotSupportMethodError(this.name, this.device.getCurrentFirmwareType());
|
|
48
|
-
}
|
|
49
|
-
|
|
50
32
|
const stagedFile = await super.run();
|
|
51
33
|
this.throwIfAborted();
|
|
52
34
|
await this.device.commands.typedCall('PortfolioUpdate', 'Success', {});
|
|
@@ -14,7 +14,6 @@ import { resolveFirmwareUpdateHostBinding } from '../firmware/FirmwareHostBindin
|
|
|
14
14
|
import {
|
|
15
15
|
assertFirmwareUpdatePreparedPlanBinding,
|
|
16
16
|
assertFirmwareUpdatePreparedPlanDeviceIdentity,
|
|
17
|
-
validateFirmwareUpdatePreparedPlan,
|
|
18
17
|
} from '../firmware/FirmwareUpdatePreparedPlan';
|
|
19
18
|
import { getDeviceType, getDeviceUUID } from '../../utils';
|
|
20
19
|
import { resolveDeviceBootloaderMode } from '../../utils/deviceFeaturesCompat';
|
|
@@ -158,42 +157,22 @@ export default class DeviceUpdateBootloader extends FirmwareUpdateBaseMethod<any
|
|
|
158
157
|
const { features } = device;
|
|
159
158
|
|
|
160
159
|
const payload = this.payload as DeviceUpdateBootloaderParams;
|
|
161
|
-
const
|
|
162
|
-
|
|
163
|
-
if (hasPreparedPlan !== hasPreparedArtifact) {
|
|
164
|
-
throw ERRORS.TypedError(
|
|
165
|
-
HardwareErrorCode.CallMethodInvalidParameter,
|
|
166
|
-
'Prepared bootloader plans require exactly one prepared artifact'
|
|
167
|
-
);
|
|
168
|
-
}
|
|
169
|
-
if (payload.binary !== undefined && hasPreparedArtifact) {
|
|
170
|
-
throw ERRORS.TypedError(
|
|
171
|
-
HardwareErrorCode.CallMethodInvalidParameter,
|
|
172
|
-
'Bootloader binary and prepared artifact are mutually exclusive'
|
|
173
|
-
);
|
|
174
|
-
}
|
|
175
|
-
const preparedPlan = hasPreparedArtifact
|
|
176
|
-
? validateFirmwareUpdatePreparedPlan(payload.preparedPlan)
|
|
160
|
+
const hostBinding = payload.artifact
|
|
161
|
+
? resolveFirmwareUpdateHostBinding(payload.hostBindingGeneration)
|
|
177
162
|
: undefined;
|
|
178
|
-
const artifactReader = preparedPlan
|
|
179
|
-
? resolveFirmwareUpdateHostBinding(
|
|
180
|
-
payload.hostBindingGeneration,
|
|
181
|
-
preparedPlan.preparedPlanDigest
|
|
182
|
-
).artifactReader
|
|
183
|
-
: payload.artifactReader;
|
|
184
163
|
const executionParams = {
|
|
185
164
|
...payload,
|
|
186
|
-
artifactReader,
|
|
165
|
+
artifactReader: hostBinding?.artifactReader ?? payload.artifactReader,
|
|
187
166
|
};
|
|
188
167
|
if (payload.artifact) {
|
|
189
168
|
assertFirmwareUpdatePreparedPlanDeviceIdentity({
|
|
190
|
-
preparedPlan,
|
|
169
|
+
preparedPlan: payload.preparedPlan,
|
|
191
170
|
deviceIdentity: getDeviceUUID(features) || undefined,
|
|
192
171
|
bootloaderMode: resolveDeviceBootloaderMode(features),
|
|
193
172
|
deviceModel: String(getDeviceType(features)),
|
|
194
173
|
});
|
|
195
174
|
assertFirmwareUpdatePreparedPlanBinding({
|
|
196
|
-
preparedPlan,
|
|
175
|
+
preparedPlan: payload.preparedPlan,
|
|
197
176
|
executor: 'v2',
|
|
198
177
|
scopeTargets: ['bootloader'],
|
|
199
178
|
bindings: [
|
|
@@ -16,8 +16,6 @@ class FirmwareHostBindingRegistry {
|
|
|
16
16
|
register(binding: FirmwareUpdateHostBinding): number {
|
|
17
17
|
if (
|
|
18
18
|
!binding ||
|
|
19
|
-
typeof binding.preparedPlanDigest !== 'string' ||
|
|
20
|
-
!/^[0-9a-f]{64}$/i.test(binding.preparedPlanDigest) ||
|
|
21
19
|
typeof binding.artifactReader?.open !== 'function' ||
|
|
22
20
|
typeof binding.artifactReader.read !== 'function' ||
|
|
23
21
|
typeof binding.artifactReader.close !== 'function'
|
|
@@ -42,7 +40,7 @@ class FirmwareHostBindingRegistry {
|
|
|
42
40
|
return this.generation;
|
|
43
41
|
}
|
|
44
42
|
|
|
45
|
-
resolve(generation: number
|
|
43
|
+
resolve(generation: number): FirmwareUpdateHostBinding {
|
|
46
44
|
if (
|
|
47
45
|
!Number.isSafeInteger(generation) ||
|
|
48
46
|
generation <= 0 ||
|
|
@@ -52,21 +50,12 @@ class FirmwareHostBindingRegistry {
|
|
|
52
50
|
return bindingError(`Firmware host binding generation ${generation} is stale`);
|
|
53
51
|
}
|
|
54
52
|
const { binding } = this;
|
|
55
|
-
if (
|
|
56
|
-
preparedPlanDigest !== undefined &&
|
|
57
|
-
binding.preparedPlanDigest.toLowerCase() !== preparedPlanDigest.toLowerCase()
|
|
58
|
-
) {
|
|
59
|
-
return bindingError(
|
|
60
|
-
`Firmware host binding generation ${generation} does not match the prepared plan`
|
|
61
|
-
);
|
|
62
|
-
}
|
|
63
53
|
const assertCurrent = () => {
|
|
64
54
|
if (generation !== this.generation || binding !== this.binding) {
|
|
65
55
|
bindingError(`Firmware host binding generation ${generation} is stale`);
|
|
66
56
|
}
|
|
67
57
|
};
|
|
68
58
|
return {
|
|
69
|
-
preparedPlanDigest: binding.preparedPlanDigest,
|
|
70
59
|
artifactReader: {
|
|
71
60
|
open: async input => {
|
|
72
61
|
assertCurrent();
|
|
@@ -107,7 +96,5 @@ export const getFirmwareUpdateHostBindingGeneration = (): number =>
|
|
|
107
96
|
firmwareHostBindingRegistry.getGeneration();
|
|
108
97
|
|
|
109
98
|
export const resolveFirmwareUpdateHostBinding = (
|
|
110
|
-
generation: number | undefined
|
|
111
|
-
|
|
112
|
-
): FirmwareUpdateHostBinding =>
|
|
113
|
-
firmwareHostBindingRegistry.resolve(generation ?? Number.NaN, preparedPlanDigest);
|
|
99
|
+
generation: number | undefined
|
|
100
|
+
): FirmwareUpdateHostBinding => firmwareHostBindingRegistry.resolve(generation ?? Number.NaN);
|
|
@@ -191,6 +191,8 @@ export class FirmwareUpdateBaseMethod<Params> extends BaseMethod<Params> {
|
|
|
191
191
|
!hasPromptedWebDevice &&
|
|
192
192
|
!isPromptingWebDevice
|
|
193
193
|
) {
|
|
194
|
+
clearInterval(intervalTimer);
|
|
195
|
+
clearTimeout(timeoutTimer);
|
|
194
196
|
isPromptingWebDevice = true;
|
|
195
197
|
try {
|
|
196
198
|
this.postTipMessage(FirmwareUpdateTipMessage.SelectDeviceInBootloaderForWebDevice);
|
|
@@ -10,14 +10,13 @@ import {
|
|
|
10
10
|
getDeviceFirmwareVersion,
|
|
11
11
|
} from '../../utils/deviceVersionUtils';
|
|
12
12
|
|
|
13
|
-
import type { FirmwareUpdateV4Target } from '../../types/api/firmwareUpdate';
|
|
14
13
|
import type {
|
|
15
14
|
FirmwareUpdatePlan,
|
|
16
15
|
FirmwareUpdatePlanArtifact,
|
|
17
16
|
FirmwareUpdatePlanForceTarget,
|
|
18
17
|
FirmwareUpdatePlanTarget,
|
|
19
18
|
} from '../../types/api/firmwareUpdatePlan';
|
|
20
|
-
import type { Features
|
|
19
|
+
import type { Features } from '../../types';
|
|
21
20
|
|
|
22
21
|
type FirmwareUpdatePlatform = 'native' | 'desktop' | 'ext' | 'web' | 'web-embed';
|
|
23
22
|
|
|
@@ -31,6 +30,7 @@ type ReleaseRecord = {
|
|
|
31
30
|
version?: unknown;
|
|
32
31
|
components?: unknown;
|
|
33
32
|
installOrder?: unknown;
|
|
33
|
+
resourceBundles?: unknown;
|
|
34
34
|
fingerprint?: unknown;
|
|
35
35
|
fingerprintWeb?: unknown;
|
|
36
36
|
expectedSize?: unknown;
|
|
@@ -62,11 +62,6 @@ const PROTOCOL_V2_TARGETS: Readonly<
|
|
|
62
62
|
SE04: 'se04',
|
|
63
63
|
};
|
|
64
64
|
|
|
65
|
-
const PROTOCOL_V2_FIRMWARE_UPDATE_TARGETS = new Set<FirmwareUpdateV4Target>([
|
|
66
|
-
...Object.values(PROTOCOL_V2_TARGETS),
|
|
67
|
-
'resource',
|
|
68
|
-
]);
|
|
69
|
-
|
|
70
65
|
const asRecord = (value: unknown): Record<string, unknown> | undefined =>
|
|
71
66
|
value && typeof value === 'object' && !Array.isArray(value)
|
|
72
67
|
? (value as Record<string, unknown>)
|
|
@@ -196,23 +191,6 @@ export const validateFirmwareUpdatePlanForceTargets = (
|
|
|
196
191
|
return [...value] as FirmwareUpdatePlanForceTarget[];
|
|
197
192
|
};
|
|
198
193
|
|
|
199
|
-
export const validateProtocolV2FirmwareUpdateTargets = (
|
|
200
|
-
value: unknown
|
|
201
|
-
): FirmwareUpdateV4Target[] => {
|
|
202
|
-
if (value === undefined) {
|
|
203
|
-
return [];
|
|
204
|
-
}
|
|
205
|
-
if (
|
|
206
|
-
!Array.isArray(value) ||
|
|
207
|
-
value.length > PROTOCOL_V2_FIRMWARE_UPDATE_TARGETS.size ||
|
|
208
|
-
value.some(target => !PROTOCOL_V2_FIRMWARE_UPDATE_TARGETS.has(target)) ||
|
|
209
|
-
new Set(value).size !== value.length
|
|
210
|
-
) {
|
|
211
|
-
return planError('Protocol V2 firmware update targets are invalid');
|
|
212
|
-
}
|
|
213
|
-
return [...value] as FirmwareUpdateV4Target[];
|
|
214
|
-
};
|
|
215
|
-
|
|
216
194
|
const assertExactKeys = (
|
|
217
195
|
value: Record<string, unknown>,
|
|
218
196
|
required: readonly string[],
|
|
@@ -373,11 +351,13 @@ export const assertFirmwareUpdatePlan = (value: unknown): FirmwareUpdatePlan =>
|
|
|
373
351
|
plan.targetsToUpdate.some(target =>
|
|
374
352
|
legacyOnlyTargets.has(target as FirmwareUpdatePlanTarget)
|
|
375
353
|
)) ||
|
|
376
|
-
//
|
|
377
|
-
//
|
|
354
|
+
// Only executor v2 services identity-less bootloader recovery (classic family);
|
|
355
|
+
// Pro (v3) and Pro2 (v4) report a serial even in bootloader mode, so their plans
|
|
356
|
+
// must always carry a real identity. Degraded v2 plans are bound to the live
|
|
357
|
+
// device at install time instead.
|
|
378
358
|
(plan.artifacts.length > 0 &&
|
|
379
359
|
(plan.platform === 'native' || plan.platform === 'desktop') &&
|
|
380
|
-
plan.executor
|
|
360
|
+
plan.executor !== 'v2' &&
|
|
381
361
|
plan.deviceIdentity === 'unavailable')
|
|
382
362
|
) {
|
|
383
363
|
return planError('Firmware update plan executor contract is invalid');
|
|
@@ -425,7 +405,7 @@ const selectResourceUrl = ({
|
|
|
425
405
|
|
|
426
406
|
const getExecutor = (features: Features): FirmwareUpdatePlan['executor'] => {
|
|
427
407
|
const deviceType = getDeviceType(features);
|
|
428
|
-
if (deviceType === EDeviceType.Pro2
|
|
408
|
+
if (deviceType === EDeviceType.Pro2) {
|
|
429
409
|
return 'v4';
|
|
430
410
|
}
|
|
431
411
|
if (
|
|
@@ -441,10 +421,10 @@ const buildProtocolV2Artifacts = (
|
|
|
441
421
|
release: ReleaseRecord,
|
|
442
422
|
{
|
|
443
423
|
includeComponents = true,
|
|
444
|
-
|
|
424
|
+
includeResources = true,
|
|
445
425
|
}: {
|
|
446
426
|
includeComponents?: boolean;
|
|
447
|
-
|
|
427
|
+
includeResources?: boolean;
|
|
448
428
|
} = {}
|
|
449
429
|
): {
|
|
450
430
|
artifacts: FirmwareUpdatePlanArtifact[];
|
|
@@ -465,21 +445,12 @@ const buildProtocolV2Artifacts = (
|
|
|
465
445
|
includeComponents && components
|
|
466
446
|
? [...installOrder, ...Object.keys(components).filter(key => !installOrder.includes(key))]
|
|
467
447
|
: [];
|
|
468
|
-
const selectedComponentKeys = selectedComponentTargets
|
|
469
|
-
? componentKeys.filter(key => {
|
|
470
|
-
const component = asRecord(components?.[key]);
|
|
471
|
-
const targetName = asString(component?.target)?.toUpperCase();
|
|
472
|
-
const target = targetName ? PROTOCOL_V2_TARGETS[targetName] : undefined;
|
|
473
|
-
return !!target && selectedComponentTargets.has(target);
|
|
474
|
-
})
|
|
475
|
-
: componentKeys;
|
|
476
448
|
const artifacts: FirmwareUpdatePlanArtifact[] = [];
|
|
477
449
|
const targets: FirmwareUpdatePlanTarget[] = [];
|
|
478
|
-
const
|
|
479
|
-
for (const key of
|
|
450
|
+
const componentTargets = new Set<FirmwareUpdatePlanTarget>();
|
|
451
|
+
for (const key of componentKeys) {
|
|
480
452
|
const component = asRecord(components?.[key]);
|
|
481
453
|
const targetName = asString(component?.target)?.toUpperCase();
|
|
482
|
-
const target = targetName ? PROTOCOL_V2_TARGETS[targetName] : undefined;
|
|
483
454
|
if (targetName === 'ROMLOADER') {
|
|
484
455
|
throw ERRORS.TypedError(
|
|
485
456
|
HardwareErrorCode.RuntimeError,
|
|
@@ -487,6 +458,7 @@ const buildProtocolV2Artifacts = (
|
|
|
487
458
|
{ firmwareUpdateCode: 'FirmwarePlanInvalid' }
|
|
488
459
|
);
|
|
489
460
|
}
|
|
461
|
+
const target = targetName ? PROTOCOL_V2_TARGETS[targetName] : undefined;
|
|
490
462
|
if (!component || !target) {
|
|
491
463
|
throw ERRORS.TypedError(
|
|
492
464
|
HardwareErrorCode.RuntimeError,
|
|
@@ -494,21 +466,14 @@ const buildProtocolV2Artifacts = (
|
|
|
494
466
|
{ firmwareUpdateCode: 'FirmwarePlanInvalid' }
|
|
495
467
|
);
|
|
496
468
|
}
|
|
497
|
-
if (
|
|
469
|
+
if (componentTargets.has(target)) {
|
|
498
470
|
throw ERRORS.TypedError(
|
|
499
471
|
HardwareErrorCode.RuntimeError,
|
|
500
472
|
`Protocol V2 component ${key} duplicates target ${target}`,
|
|
501
473
|
{ firmwareUpdateCode: 'FirmwarePlanInvalid' }
|
|
502
474
|
);
|
|
503
475
|
}
|
|
504
|
-
|
|
505
|
-
const integrity = asIntegrity({
|
|
506
|
-
size: component.expectedSize,
|
|
507
|
-
sha256: component.fingerprint,
|
|
508
|
-
});
|
|
509
|
-
if (integrity.expectedSize === undefined || integrity.expectedSha256 === undefined) {
|
|
510
|
-
planError(`Protocol V2 component ${key} integrity metadata is invalid`);
|
|
511
|
-
}
|
|
476
|
+
componentTargets.add(target);
|
|
512
477
|
artifacts.push({
|
|
513
478
|
artifactId: `component:${target}`,
|
|
514
479
|
role: 'component',
|
|
@@ -516,12 +481,52 @@ const buildProtocolV2Artifacts = (
|
|
|
516
481
|
url: assertArtifactUrl(component.url, `Protocol V2 component ${key}`),
|
|
517
482
|
container: 'raw',
|
|
518
483
|
logicalName: key,
|
|
519
|
-
...
|
|
484
|
+
...asIntegrity({
|
|
485
|
+
size: component.expectedSize,
|
|
486
|
+
sha256: component.fingerprint,
|
|
487
|
+
}),
|
|
520
488
|
...(asVersion(component.version) ? { targetVersion: asVersion(component.version) } : {}),
|
|
521
489
|
});
|
|
522
490
|
targets.push(target);
|
|
523
491
|
}
|
|
524
492
|
|
|
493
|
+
const bundles =
|
|
494
|
+
includeResources && Array.isArray(release.resourceBundles) ? release.resourceBundles : [];
|
|
495
|
+
const resourceBundleNames = new Set<string>();
|
|
496
|
+
for (const value of bundles) {
|
|
497
|
+
const bundle = asRecord(value);
|
|
498
|
+
const name = asString(bundle?.name);
|
|
499
|
+
if (!bundle || !name) {
|
|
500
|
+
throw ERRORS.TypedError(
|
|
501
|
+
HardwareErrorCode.RuntimeError,
|
|
502
|
+
'Protocol V2 resource bundle is invalid',
|
|
503
|
+
{ firmwareUpdateCode: 'FirmwarePlanInvalid' }
|
|
504
|
+
);
|
|
505
|
+
}
|
|
506
|
+
if (resourceBundleNames.has(name)) {
|
|
507
|
+
throw ERRORS.TypedError(
|
|
508
|
+
HardwareErrorCode.RuntimeError,
|
|
509
|
+
`Protocol V2 resource bundle duplicates name ${name}`,
|
|
510
|
+
{ firmwareUpdateCode: 'FirmwarePlanInvalid' }
|
|
511
|
+
);
|
|
512
|
+
}
|
|
513
|
+
resourceBundleNames.add(name);
|
|
514
|
+
artifacts.push({
|
|
515
|
+
artifactId: `resourceBundle:${name}`,
|
|
516
|
+
role: 'resourceBundle',
|
|
517
|
+
target: 'resource',
|
|
518
|
+
url: assertArtifactUrl(bundle.url, `Protocol V2 resource bundle ${name}`),
|
|
519
|
+
container: 'raw',
|
|
520
|
+
logicalName: name,
|
|
521
|
+
...asIntegrity({
|
|
522
|
+
size: bundle.expectedSize,
|
|
523
|
+
sha256: bundle.fingerprint,
|
|
524
|
+
}),
|
|
525
|
+
});
|
|
526
|
+
}
|
|
527
|
+
if (bundles.length > 0) {
|
|
528
|
+
targets.push('resource');
|
|
529
|
+
}
|
|
525
530
|
return { artifacts, targets: [...new Set(targets)] };
|
|
526
531
|
};
|
|
527
532
|
|
|
@@ -582,179 +587,6 @@ const assertForcedTargetsRepresented = ({
|
|
|
582
587
|
}
|
|
583
588
|
};
|
|
584
589
|
|
|
585
|
-
const finalizeFirmwareUpdatePlan = ({
|
|
586
|
-
features,
|
|
587
|
-
firmwareType,
|
|
588
|
-
platform,
|
|
589
|
-
artifacts,
|
|
590
|
-
targetsToUpdate,
|
|
591
|
-
}: {
|
|
592
|
-
features: Features;
|
|
593
|
-
firmwareType: EFirmwareType;
|
|
594
|
-
platform: FirmwareUpdatePlatform;
|
|
595
|
-
artifacts: FirmwareUpdatePlanArtifact[];
|
|
596
|
-
targetsToUpdate: FirmwareUpdatePlanTarget[];
|
|
597
|
-
}): FirmwareUpdatePlan => {
|
|
598
|
-
const executor = getExecutor(features);
|
|
599
|
-
const bootloaderMode = resolveDeviceBootloaderMode(features);
|
|
600
|
-
// 'unavailable' is the reserved degraded-identity sentinel; a device-reported
|
|
601
|
-
// serial must never be able to collide with it.
|
|
602
|
-
const reportedIdentity = getDeviceUUID(features);
|
|
603
|
-
const deviceIdentity = reportedIdentity === 'unavailable' ? '' : reportedIdentity;
|
|
604
|
-
// Classic 系列仅在 bootloader 恢复时允许缺少身份;部分尚未完成产线写号的
|
|
605
|
-
// Pro2/Neo(V4)在正常模式下也可能没有序列号,因此允许使用降级身份。
|
|
606
|
-
const allowsUnavailableIdentity = (bootloaderMode && executor === 'v2') || executor === 'v4';
|
|
607
|
-
if (
|
|
608
|
-
artifacts.length > 0 &&
|
|
609
|
-
(platform === 'native' || platform === 'desktop') &&
|
|
610
|
-
!deviceIdentity &&
|
|
611
|
-
!allowsUnavailableIdentity
|
|
612
|
-
) {
|
|
613
|
-
throw ERRORS.TypedError(
|
|
614
|
-
HardwareErrorCode.RuntimeError,
|
|
615
|
-
'Prepared firmware update requires a stable device identity',
|
|
616
|
-
{ firmwareUpdateCode: 'FirmwarePlanInvalid' }
|
|
617
|
-
);
|
|
618
|
-
}
|
|
619
|
-
const planWithoutDigest: Omit<FirmwareUpdatePlan, 'planDigest'> = {
|
|
620
|
-
schemaVersion: 2,
|
|
621
|
-
executor,
|
|
622
|
-
deviceIdentity: deviceIdentity || 'unavailable',
|
|
623
|
-
deviceModel: String(getDeviceType(features)),
|
|
624
|
-
firmwareType,
|
|
625
|
-
platform,
|
|
626
|
-
artifacts,
|
|
627
|
-
targetsToUpdate,
|
|
628
|
-
};
|
|
629
|
-
return assertFirmwareUpdatePlan({
|
|
630
|
-
...planWithoutDigest,
|
|
631
|
-
planDigest: digestFirmwareUpdatePlan(planWithoutDigest),
|
|
632
|
-
});
|
|
633
|
-
};
|
|
634
|
-
|
|
635
|
-
export type ProtocolV2LocalFirmwareUpdatePlanArtifact = {
|
|
636
|
-
artifactId: string;
|
|
637
|
-
target: Exclude<FirmwareUpdateV4Target, 'boot_resources'>;
|
|
638
|
-
container: 'raw' | 'zip';
|
|
639
|
-
logicalName: string;
|
|
640
|
-
expectedSize: number;
|
|
641
|
-
expectedSha256: string;
|
|
642
|
-
targetVersion?: string;
|
|
643
|
-
};
|
|
644
|
-
|
|
645
|
-
export const buildProtocolV2LocalFirmwareUpdatePlan = ({
|
|
646
|
-
features,
|
|
647
|
-
firmwareType,
|
|
648
|
-
platform,
|
|
649
|
-
artifacts,
|
|
650
|
-
}: {
|
|
651
|
-
features: Features;
|
|
652
|
-
firmwareType: EFirmwareType;
|
|
653
|
-
platform: FirmwareUpdatePlatform;
|
|
654
|
-
artifacts: ProtocolV2LocalFirmwareUpdatePlanArtifact[];
|
|
655
|
-
}): FirmwareUpdatePlan => {
|
|
656
|
-
if (artifacts.length === 0) {
|
|
657
|
-
return planError('Protocol V2 local firmware plan has no artifacts');
|
|
658
|
-
}
|
|
659
|
-
const plan = finalizeFirmwareUpdatePlan({
|
|
660
|
-
features,
|
|
661
|
-
firmwareType,
|
|
662
|
-
platform,
|
|
663
|
-
artifacts: artifacts.map(artifact => ({
|
|
664
|
-
...artifact,
|
|
665
|
-
role: artifact.target === 'resource' ? 'resourceBundle' : 'component',
|
|
666
|
-
url: `https://local-firmware.invalid/${encodeURIComponent(artifact.artifactId)}`,
|
|
667
|
-
})),
|
|
668
|
-
targetsToUpdate: artifacts.map(artifact => artifact.target),
|
|
669
|
-
});
|
|
670
|
-
if (plan.executor !== 'v4') {
|
|
671
|
-
return planError('Protocol V2 local firmware plan requires executor v4');
|
|
672
|
-
}
|
|
673
|
-
return plan;
|
|
674
|
-
};
|
|
675
|
-
|
|
676
|
-
export const buildProtocolV2FirmwareUpdatePlan = ({
|
|
677
|
-
features,
|
|
678
|
-
firmwareType,
|
|
679
|
-
platform,
|
|
680
|
-
release,
|
|
681
|
-
targetsToUpdate,
|
|
682
|
-
forceUpdateTargets,
|
|
683
|
-
resourceArchive,
|
|
684
|
-
}: {
|
|
685
|
-
features: Features;
|
|
686
|
-
firmwareType: EFirmwareType;
|
|
687
|
-
platform: FirmwareUpdatePlatform;
|
|
688
|
-
release: ReleaseRecord | undefined;
|
|
689
|
-
targetsToUpdate: readonly FirmwareUpdateV4Target[];
|
|
690
|
-
forceUpdateTargets?: FirmwareUpdatePlanForceTarget[];
|
|
691
|
-
resourceArchive: IProtocolV2ResourceSource | undefined;
|
|
692
|
-
}): FirmwareUpdatePlan => {
|
|
693
|
-
const validatedForceTargets = validateFirmwareUpdatePlanForceTargets(forceUpdateTargets);
|
|
694
|
-
if (validatedForceTargets.some(target => target === 'ble' || target === 'bootloader')) {
|
|
695
|
-
planError('Protocol V2 does not support forced BLE or legacy bootloader targets');
|
|
696
|
-
}
|
|
697
|
-
|
|
698
|
-
const requestedComponentTargets = new Set<FirmwareUpdatePlanTarget>(
|
|
699
|
-
targetsToUpdate.filter(target => target !== 'resource')
|
|
700
|
-
);
|
|
701
|
-
if (
|
|
702
|
-
getDeviceType(features) === EDeviceType.Neo &&
|
|
703
|
-
(requestedComponentTargets.has('se03') || requestedComponentTargets.has('se04'))
|
|
704
|
-
) {
|
|
705
|
-
planError('Neo does not support Protocol V2 targets se03 or se04');
|
|
706
|
-
}
|
|
707
|
-
const protocolV2 = buildProtocolV2Artifacts(release ?? {}, {
|
|
708
|
-
includeComponents: requestedComponentTargets.size > 0,
|
|
709
|
-
componentTargets: requestedComponentTargets,
|
|
710
|
-
});
|
|
711
|
-
const resourceRequested = targetsToUpdate.includes('resource');
|
|
712
|
-
if (resourceRequested) {
|
|
713
|
-
const archive = resourceArchive;
|
|
714
|
-
if (!archive) {
|
|
715
|
-
return planError('Protocol V2 resource target has no resource archive');
|
|
716
|
-
}
|
|
717
|
-
const integrity = asIntegrity({
|
|
718
|
-
size: archive.archiveSize,
|
|
719
|
-
sha256: archive.archiveSha256,
|
|
720
|
-
});
|
|
721
|
-
if (integrity.expectedSize === undefined || integrity.expectedSha256 === undefined) {
|
|
722
|
-
planError('Protocol V2 resource archive integrity metadata is invalid');
|
|
723
|
-
}
|
|
724
|
-
protocolV2.artifacts.push({
|
|
725
|
-
artifactId: 'resource:archive',
|
|
726
|
-
role: 'resourceBundle',
|
|
727
|
-
target: 'resource',
|
|
728
|
-
url: assertArtifactUrl(archive.archiveUrl, 'Protocol V2 resource archive'),
|
|
729
|
-
container: 'zip',
|
|
730
|
-
logicalName: 'protocol-v2-resource-archive',
|
|
731
|
-
...integrity,
|
|
732
|
-
});
|
|
733
|
-
protocolV2.targets.push('resource');
|
|
734
|
-
}
|
|
735
|
-
const representedTargets = new Set(protocolV2.targets);
|
|
736
|
-
const missingTarget = Array.from(requestedComponentTargets).find(
|
|
737
|
-
target => !representedTargets.has(target)
|
|
738
|
-
);
|
|
739
|
-
if (missingTarget) {
|
|
740
|
-
planError(`Protocol V2 release does not contain requested target ${missingTarget}`);
|
|
741
|
-
}
|
|
742
|
-
if (validatedForceTargets.includes('firmware') && protocolV2.targets.length === 0) {
|
|
743
|
-
planError('Forced firmware update target firmware is not represented by the plan');
|
|
744
|
-
}
|
|
745
|
-
if (validatedForceTargets.includes('resource') && !resourceArchive) {
|
|
746
|
-
planError('Forced firmware update target resource has no Protocol V2 resource archive');
|
|
747
|
-
}
|
|
748
|
-
|
|
749
|
-
return finalizeFirmwareUpdatePlan({
|
|
750
|
-
features,
|
|
751
|
-
firmwareType,
|
|
752
|
-
platform,
|
|
753
|
-
artifacts: protocolV2.artifacts,
|
|
754
|
-
targetsToUpdate: protocolV2.targets,
|
|
755
|
-
});
|
|
756
|
-
};
|
|
757
|
-
|
|
758
590
|
export const buildFirmwareUpdatePlan = ({
|
|
759
591
|
features,
|
|
760
592
|
firmwareType,
|
|
@@ -803,6 +635,7 @@ export const buildFirmwareUpdatePlan = ({
|
|
|
803
635
|
if (executor === 'v4' && (shouldUpdateFirmware || shouldUpdateResource)) {
|
|
804
636
|
const protocolV2 = buildProtocolV2Artifacts(firmwareRelease ?? {}, {
|
|
805
637
|
includeComponents: shouldUpdateFirmware,
|
|
638
|
+
includeResources: shouldUpdateResource,
|
|
806
639
|
});
|
|
807
640
|
artifacts = protocolV2.artifacts;
|
|
808
641
|
targetsToUpdate = protocolV2.targets;
|
|
@@ -900,11 +733,40 @@ export const buildFirmwareUpdatePlan = ({
|
|
|
900
733
|
artifacts,
|
|
901
734
|
targetsToUpdate,
|
|
902
735
|
});
|
|
903
|
-
|
|
904
|
-
|
|
736
|
+
|
|
737
|
+
// 'unavailable' is the reserved degraded-identity sentinel; a device-reported
|
|
738
|
+
// serial must never be able to collide with it.
|
|
739
|
+
const reportedIdentity = getDeviceUUID(features);
|
|
740
|
+
const deviceIdentity = reportedIdentity === 'unavailable' ? '' : reportedIdentity;
|
|
741
|
+
// Bootloader-mode classic-family devices (executor v2, e.g. classic1s) cannot report
|
|
742
|
+
// a serial number, so their recovery plans fall back to the degraded 'unavailable'
|
|
743
|
+
// identity. Binding is then enforced against the live device at install time instead
|
|
744
|
+
// (assertFirmwareUpdatePreparedPlanDeviceIdentity), and plan integrity by the digest.
|
|
745
|
+
// Pro (v3) and Pro2 (v4) report a serial even in bootloader mode and stay strict.
|
|
746
|
+
if (
|
|
747
|
+
artifacts.length > 0 &&
|
|
748
|
+
(platform === 'native' || platform === 'desktop') &&
|
|
749
|
+
!deviceIdentity &&
|
|
750
|
+
!(bootloaderMode && executor === 'v2')
|
|
751
|
+
) {
|
|
752
|
+
throw ERRORS.TypedError(
|
|
753
|
+
HardwareErrorCode.RuntimeError,
|
|
754
|
+
'Prepared firmware update requires a stable device identity',
|
|
755
|
+
{ firmwareUpdateCode: 'FirmwarePlanInvalid' }
|
|
756
|
+
);
|
|
757
|
+
}
|
|
758
|
+
const planWithoutDigest: Omit<FirmwareUpdatePlan, 'planDigest'> = {
|
|
759
|
+
schemaVersion: 2,
|
|
760
|
+
executor,
|
|
761
|
+
deviceIdentity: deviceIdentity || 'unavailable',
|
|
762
|
+
deviceModel: String(getDeviceType(features)),
|
|
905
763
|
firmwareType,
|
|
906
764
|
platform,
|
|
907
765
|
artifacts,
|
|
908
766
|
targetsToUpdate,
|
|
767
|
+
};
|
|
768
|
+
return assertFirmwareUpdatePlan({
|
|
769
|
+
...planWithoutDigest,
|
|
770
|
+
planDigest: digestFirmwareUpdatePlan(planWithoutDigest),
|
|
909
771
|
});
|
|
910
772
|
};
|
|
@@ -278,14 +278,7 @@ export const validateFirmwareUpdatePreparedPlan = (value: unknown): FirmwareUpda
|
|
|
278
278
|
artifactIds.add(artifact.artifactId);
|
|
279
279
|
artifactTargets.add(artifact.target);
|
|
280
280
|
assertFirmwareArtifactReference(artifact.artifact);
|
|
281
|
-
|
|
282
|
-
artifact.materializedEntries?.map(entry => {
|
|
283
|
-
assertPreparedEntry(entry);
|
|
284
|
-
return getFirmwareUpdateResourceName(entry.entryName).toLowerCase();
|
|
285
|
-
}) ?? [];
|
|
286
|
-
if (new Set(materializedEntryNames).size !== materializedEntryNames.length) {
|
|
287
|
-
return preparedPlanError('Firmware prepared plan contains duplicate entry names');
|
|
288
|
-
}
|
|
281
|
+
artifact.materializedEntries?.forEach(assertPreparedEntry);
|
|
289
282
|
}
|
|
290
283
|
if (
|
|
291
284
|
preparedPlan.targetsToUpdate.some(target => !FIRMWARE_UPDATE_PLAN_TARGETS.has(target)) ||
|
|
@@ -349,8 +342,8 @@ export const assertFirmwareUpdatePreparedPlanDeviceIdentity = ({
|
|
|
349
342
|
}
|
|
350
343
|
const pinnedIdentity = degradedPlanIdentityPins.get(preparedPlan.leaseRef);
|
|
351
344
|
if (!deviceIdentity) {
|
|
352
|
-
//
|
|
353
|
-
if (
|
|
345
|
+
// Still identity-less: only legitimate while the device sits in bootloader.
|
|
346
|
+
if (bootloaderMode !== true) {
|
|
354
347
|
throw ERRORS.TypedError(HardwareErrorCode.DeviceCheckDeviceIdError);
|
|
355
348
|
}
|
|
356
349
|
return preparedPlan;
|