@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,5 +1,4 @@
|
|
|
1
1
|
import { EDeviceType, ERRORS, HardwareError, HardwareErrorCode, wait } from '@onekeyfe/hd-shared';
|
|
2
|
-
import JSZip from 'jszip';
|
|
3
2
|
import {
|
|
4
3
|
DeviceRebootType,
|
|
5
4
|
PROTOCOL_V2_BLE_FILE_CHUNK_SIZE,
|
|
@@ -10,6 +9,7 @@ import {
|
|
|
10
9
|
import { sha256 } from '@noble/hashes/sha256';
|
|
11
10
|
|
|
12
11
|
import { FirmwareUpdateTipMessage, UI_REQUEST } from '../events/ui-request';
|
|
12
|
+
import { validateProtocolV2FilesystemPath } from './helpers/filesystemValidation';
|
|
13
13
|
import { validateParams } from './helpers/paramsValidator';
|
|
14
14
|
import {
|
|
15
15
|
LoggerNames,
|
|
@@ -31,31 +31,21 @@ import {
|
|
|
31
31
|
} from '../protocols/protocol-v2';
|
|
32
32
|
import { requestProtocolV2DeviceInfo } from '../protocols/protocol-v2/features';
|
|
33
33
|
import {
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
PROTOCOL_V2_RESOURCE_DEVICE_PATHS,
|
|
35
|
+
buildProtocolV2ResourceUpdatePlan,
|
|
36
|
+
isProtocolV2ResourceFileValid,
|
|
37
|
+
readProtocolV2ResourceInventory,
|
|
36
38
|
} from '../protocols/protocol-v2/resources';
|
|
37
39
|
import {
|
|
38
40
|
getProtocolV2UnknownErrorText,
|
|
39
41
|
isProtocolV2DeviceDisconnectedError,
|
|
40
42
|
} from './protocol-v2/helpers';
|
|
41
|
-
import {
|
|
42
|
-
|
|
43
|
-
readFirmwareByteSourceFully,
|
|
44
|
-
writeFirmwareByteSource,
|
|
45
|
-
} from './firmware/FirmwareArtifactSource';
|
|
46
|
-
import {
|
|
47
|
-
registerFirmwareUpdateHostBinding,
|
|
48
|
-
resolveFirmwareUpdateHostBinding,
|
|
49
|
-
unregisterFirmwareUpdateHostBinding,
|
|
50
|
-
} from './firmware/FirmwareHostBinding';
|
|
43
|
+
import { openFirmwareByteSource, writeFirmwareByteSource } from './firmware/FirmwareArtifactSource';
|
|
44
|
+
import { resolveFirmwareUpdateHostBinding } from './firmware/FirmwareHostBinding';
|
|
51
45
|
import {
|
|
52
46
|
assertFirmwareUpdatePreparedPlanBinding,
|
|
53
47
|
assertFirmwareUpdatePreparedPlanDeviceIdentity,
|
|
54
|
-
prepareFirmwareUpdatePlan,
|
|
55
|
-
validateFirmwareUpdatePreparedPlan,
|
|
56
48
|
} from './firmware/FirmwareUpdatePreparedPlan';
|
|
57
|
-
import { prepareFirmwareUpdateV4MemoryHost } from './firmware/FirmwareMemoryHost';
|
|
58
|
-
import { buildProtocolV2LocalFirmwareUpdatePlan } from './firmware/FirmwareUpdatePlan';
|
|
59
49
|
|
|
60
50
|
import type {
|
|
61
51
|
FirmwareArtifactReference,
|
|
@@ -69,14 +59,11 @@ import type {
|
|
|
69
59
|
Features,
|
|
70
60
|
IFirmwareReleaseInfo,
|
|
71
61
|
IProtocolV2FirmwareComponent,
|
|
62
|
+
IProtocolV2Resource,
|
|
63
|
+
IProtocolV2ResourceFile,
|
|
72
64
|
IVersionArray,
|
|
73
65
|
} from '../types';
|
|
74
66
|
import type { FirmwareByteSource } from './firmware/FirmwareArtifactSource';
|
|
75
|
-
import type {
|
|
76
|
-
FirmwareMemoryArtifact,
|
|
77
|
-
FirmwareMemoryArtifactEntry,
|
|
78
|
-
FirmwareUpdateV4MemoryHost,
|
|
79
|
-
} from './firmware/FirmwareMemoryHost';
|
|
80
67
|
|
|
81
68
|
const Log = getLogger(LoggerNames.Method);
|
|
82
69
|
|
|
@@ -108,34 +95,9 @@ const PROTOCOL_V2_OKPP_HEADER_SIZE = 0x52a0;
|
|
|
108
95
|
const PROTOCOL_V2_OKPP_PAYLOAD_HASH_OFFSET = 0x200;
|
|
109
96
|
const PROTOCOL_V2_OKPP_HEADER_HASH_OFFSET = 0x240;
|
|
110
97
|
const PROTOCOL_V2_OKPP_HASH_SIZE = 64;
|
|
111
|
-
const PROTOCOL_V2_RESOURCE_MANIFEST_MAX_BYTES = 1024 * 1024;
|
|
112
|
-
const PROTOCOL_V2_RESOURCE_FILE_MAX_COUNT = 512;
|
|
113
|
-
const PROTOCOL_V2_RESOURCE_TOTAL_MAX_BYTES = 256 * 1024 * 1024;
|
|
114
98
|
|
|
115
99
|
const PROTOCOL_V2_NEO_UNSUPPORTED_TARGETS = new Set<FirmwareUpdateV4Target>(['se03', 'se04']);
|
|
116
100
|
|
|
117
|
-
const getProtocolV2ZipEntrySizes = (entry: JSZip.JSZipObject) => {
|
|
118
|
-
// loadAsync records central-directory sizes on JSZip's documented private data object.
|
|
119
|
-
// Validate those bounds before calling async(), which allocates the decompressed entry.
|
|
120
|
-
const { compressedSize, uncompressedSize } = (entry as JSZipSizedEntry)._data ?? {};
|
|
121
|
-
if (
|
|
122
|
-
!Number.isSafeInteger(compressedSize) ||
|
|
123
|
-
Number(compressedSize) < 0 ||
|
|
124
|
-
!Number.isSafeInteger(uncompressedSize) ||
|
|
125
|
-
Number(uncompressedSize) <= 0
|
|
126
|
-
) {
|
|
127
|
-
throw ERRORS.TypedError(
|
|
128
|
-
HardwareErrorCode.RuntimeError,
|
|
129
|
-
`Protocol V2 local resource ZIP entry size is invalid: ${entry.name}`,
|
|
130
|
-
{ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
|
|
131
|
-
);
|
|
132
|
-
}
|
|
133
|
-
return {
|
|
134
|
-
compressedSize: Number(compressedSize),
|
|
135
|
-
uncompressedSize: Number(uncompressedSize),
|
|
136
|
-
};
|
|
137
|
-
};
|
|
138
|
-
|
|
139
101
|
export function assertProtocolV2FirmwareTargetsSupported(
|
|
140
102
|
deviceType: EDeviceType | string | undefined,
|
|
141
103
|
params: FirmwareUpdateV4Params
|
|
@@ -186,6 +148,12 @@ type ProtocolV2TargetBinary = { fileName: string; binary: ArrayBuffer; targetId:
|
|
|
186
148
|
type ProtocolV2InstallItem = ProtocolV2TargetBinary & {
|
|
187
149
|
kind: ProtocolV2RemoteComponentTarget['kind'];
|
|
188
150
|
};
|
|
151
|
+
/** Protocol V2 resource file written independently to devicePath through FileWrite. */
|
|
152
|
+
type ProtocolV2ResourceBundleBinary = {
|
|
153
|
+
name: string;
|
|
154
|
+
binary: ArrayBuffer;
|
|
155
|
+
devicePath: string;
|
|
156
|
+
};
|
|
189
157
|
|
|
190
158
|
type ProtocolV2RemoteComponentBinary = ProtocolV2RemoteComponentTarget & {
|
|
191
159
|
binary: ArrayBuffer;
|
|
@@ -213,18 +181,6 @@ type ProtocolV2ResourceBundleSource = {
|
|
|
213
181
|
headerHash?: string;
|
|
214
182
|
};
|
|
215
183
|
|
|
216
|
-
type ProtocolV2LocalResourceArchive = {
|
|
217
|
-
binary: ArrayBuffer;
|
|
218
|
-
materializedEntries: FirmwareMemoryArtifactEntry[];
|
|
219
|
-
};
|
|
220
|
-
|
|
221
|
-
type JSZipSizedEntry = JSZip.JSZipObject & {
|
|
222
|
-
_data?: {
|
|
223
|
-
compressedSize?: unknown;
|
|
224
|
-
uncompressedSize?: unknown;
|
|
225
|
-
};
|
|
226
|
-
};
|
|
227
|
-
|
|
228
184
|
type ProtocolV2ExecutionPhaseKind =
|
|
229
185
|
| 'resource-sync'
|
|
230
186
|
| 'bootloader-install'
|
|
@@ -296,20 +252,7 @@ const PROTOCOL_V2_FIRMWARE_STAGING_PATHS = new Set(
|
|
|
296
252
|
)
|
|
297
253
|
);
|
|
298
254
|
|
|
299
|
-
const
|
|
300
|
-
const PROTOCOL_V2_BOOT_RESOURCE_PACKAGE_STAGING_PATH = `${PROTOCOL_V2_BOOT_RESOURCE_PACKAGE_PATH}.staging`;
|
|
301
|
-
|
|
302
|
-
const resolveProtocolV2ResourceWritePath = (devicePath: string) => {
|
|
303
|
-
const normalizedPath = devicePath.replace(/^vol0:(?!\/)/i, 'vol0:/').toLowerCase();
|
|
304
|
-
return normalizedPath === PROTOCOL_V2_BOOT_RESOURCE_PACKAGE_PATH
|
|
305
|
-
? PROTOCOL_V2_BOOT_RESOURCE_PACKAGE_STAGING_PATH
|
|
306
|
-
: devicePath;
|
|
307
|
-
};
|
|
308
|
-
|
|
309
|
-
const PROTOCOL_V2_UPDATE_TARGET_BY_TARGET_ID = new Map<
|
|
310
|
-
number,
|
|
311
|
-
Exclude<FirmwareUpdateV4Target, 'boot_resources'>
|
|
312
|
-
>([
|
|
255
|
+
const PROTOCOL_V2_UPDATE_TARGET_BY_TARGET_ID = new Map<number, FirmwareUpdateV4Target>([
|
|
313
256
|
[ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_BOOTLOADER, 'boot'],
|
|
314
257
|
[ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_APPLICATION_P1, 'app_v1'],
|
|
315
258
|
[ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_APPLICATION_P2, 'app_v2'],
|
|
@@ -320,19 +263,6 @@ const PROTOCOL_V2_UPDATE_TARGET_BY_TARGET_ID = new Map<
|
|
|
320
263
|
[ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_SE04, 'se04'],
|
|
321
264
|
]);
|
|
322
265
|
|
|
323
|
-
const PROTOCOL_V2_INSTALL_TARGET_BY_UPDATE_TARGET = new Map<
|
|
324
|
-
Exclude<FirmwareUpdateV4Target, 'resource'>,
|
|
325
|
-
ProtocolV2RemoteComponentTarget
|
|
326
|
-
>(
|
|
327
|
-
Object.values(PROTOCOL_V2_REMOTE_COMPONENT_TARGETS).map(target => [
|
|
328
|
-
PROTOCOL_V2_UPDATE_TARGET_BY_TARGET_ID.get(target.targetId) as Exclude<
|
|
329
|
-
FirmwareUpdateV4Target,
|
|
330
|
-
'resource'
|
|
331
|
-
>,
|
|
332
|
-
target,
|
|
333
|
-
])
|
|
334
|
-
);
|
|
335
|
-
|
|
336
266
|
const PROTOCOL_V2_ROMLOADER_UNSUPPORTED_MESSAGE =
|
|
337
267
|
'FW_MGMT_TARGET_ROMLOADER is not accepted by the current Pro2 bootloader update request. Flash romloader with the loader-specific flow instead of firmwareUpdateV4.';
|
|
338
268
|
|
|
@@ -604,11 +534,11 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
604
534
|
{ name: 'se02Binary', type: 'buffer' },
|
|
605
535
|
{ name: 'se03Binary', type: 'buffer' },
|
|
606
536
|
{ name: 'se04Binary', type: 'buffer' },
|
|
607
|
-
{ name: 'resourceArchiveBinary', type: 'buffer' },
|
|
608
537
|
{ name: 'firmwareType', type: 'string' },
|
|
609
538
|
{ name: 'targetsToUpdate', type: 'array', allowEmpty: true },
|
|
610
539
|
{ name: 'platform', type: 'string' },
|
|
611
540
|
{ name: 'expectedDeviceId', type: 'string' },
|
|
541
|
+
{ name: 'resourceFiles', type: 'array', allowEmpty: true },
|
|
612
542
|
]);
|
|
613
543
|
if (
|
|
614
544
|
payload.expectedDeviceId !== undefined &&
|
|
@@ -619,101 +549,50 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
619
549
|
'Protocol V2 expected device identity is invalid'
|
|
620
550
|
);
|
|
621
551
|
}
|
|
622
|
-
const
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
payload.romloaderBinary,
|
|
628
|
-
payload.applicationP1Binary,
|
|
629
|
-
payload.applicationP2Binary,
|
|
630
|
-
payload.coprocessorBinary,
|
|
631
|
-
payload.se01Binary,
|
|
632
|
-
payload.se02Binary,
|
|
633
|
-
payload.se03Binary,
|
|
634
|
-
payload.se04Binary,
|
|
635
|
-
payload.resourceArchiveBinary,
|
|
636
|
-
].some(Boolean);
|
|
637
|
-
if (preparedPlan && hasLocalArtifacts) {
|
|
638
|
-
throw ERRORS.TypedError(
|
|
639
|
-
HardwareErrorCode.CallMethodInvalidParameter,
|
|
640
|
-
'Prepared firmware plans cannot be combined with legacy or local firmware inputs'
|
|
641
|
-
);
|
|
642
|
-
}
|
|
643
|
-
let { artifactReader } = payload;
|
|
644
|
-
if (preparedPlan) {
|
|
645
|
-
artifactReader = resolveFirmwareUpdateHostBinding(
|
|
646
|
-
payload.hostBindingGeneration,
|
|
647
|
-
preparedPlan.preparedPlanDigest
|
|
648
|
-
).artifactReader;
|
|
649
|
-
} else if (payload.hostBindingGeneration !== undefined) {
|
|
650
|
-
artifactReader = resolveFirmwareUpdateHostBinding(
|
|
651
|
-
payload.hostBindingGeneration
|
|
652
|
-
).artifactReader;
|
|
653
|
-
}
|
|
654
|
-
if (preparedPlan) {
|
|
552
|
+
const hostBinding =
|
|
553
|
+
payload.preparedPlan || payload.componentArtifacts || payload.resourceBundleArtifacts
|
|
554
|
+
? resolveFirmwareUpdateHostBinding(payload.hostBindingGeneration)
|
|
555
|
+
: undefined;
|
|
556
|
+
if (hostBinding) {
|
|
655
557
|
assertFirmwareUpdatePreparedPlanBinding({
|
|
656
|
-
preparedPlan,
|
|
558
|
+
preparedPlan: payload.preparedPlan,
|
|
657
559
|
executor: 'v4',
|
|
658
560
|
platform: payload.platform,
|
|
659
|
-
scopeTargets: [
|
|
660
|
-
|
|
561
|
+
scopeTargets: [
|
|
562
|
+
'boot',
|
|
563
|
+
'app_v1',
|
|
564
|
+
'app_v2',
|
|
565
|
+
'coprocessor',
|
|
566
|
+
'resource',
|
|
567
|
+
'se01',
|
|
568
|
+
'se02',
|
|
569
|
+
'se03',
|
|
570
|
+
'se04',
|
|
571
|
+
],
|
|
572
|
+
bindings: [
|
|
573
|
+
...Object.entries(payload.componentArtifacts ?? {}).flatMap(([target, artifact]) =>
|
|
574
|
+
artifact
|
|
575
|
+
? [
|
|
576
|
+
{
|
|
577
|
+
target: target as Exclude<FirmwareUpdateV4Target, 'resource'>,
|
|
578
|
+
artifact,
|
|
579
|
+
},
|
|
580
|
+
]
|
|
581
|
+
: []
|
|
582
|
+
),
|
|
583
|
+
...(payload.resourceBundleArtifacts ?? []).map(
|
|
584
|
+
(entry: { name: string; artifact: FirmwareArtifactReference }) => ({
|
|
585
|
+
target: 'resource' as const,
|
|
586
|
+
logicalName: entry.name,
|
|
587
|
+
artifact: entry.artifact,
|
|
588
|
+
})
|
|
589
|
+
),
|
|
590
|
+
],
|
|
661
591
|
});
|
|
662
|
-
if (payload.componentArtifacts) {
|
|
663
|
-
const componentBindings: Array<{
|
|
664
|
-
target: Exclude<FirmwareUpdateV4Target, 'resource'>;
|
|
665
|
-
artifact: FirmwareArtifactReference;
|
|
666
|
-
}> = Object.entries(payload.componentArtifacts).flatMap(([target, artifact]) =>
|
|
667
|
-
artifact
|
|
668
|
-
? [
|
|
669
|
-
{
|
|
670
|
-
target: target as Exclude<FirmwareUpdateV4Target, 'resource'>,
|
|
671
|
-
artifact: artifact as FirmwareArtifactReference,
|
|
672
|
-
},
|
|
673
|
-
]
|
|
674
|
-
: []
|
|
675
|
-
);
|
|
676
|
-
assertFirmwareUpdatePreparedPlanBinding({
|
|
677
|
-
preparedPlan,
|
|
678
|
-
executor: 'v4',
|
|
679
|
-
platform: payload.platform,
|
|
680
|
-
scopeTargets: componentBindings.map(binding => binding.target),
|
|
681
|
-
bindings: componentBindings,
|
|
682
|
-
});
|
|
683
|
-
}
|
|
684
592
|
}
|
|
685
593
|
|
|
686
|
-
const preparedExpectedTargetVersions = preparedPlan?.artifacts.reduce<
|
|
687
|
-
NonNullable<FirmwareUpdateV4Params['expectedTargetVersions']>
|
|
688
|
-
>((result, artifact) => {
|
|
689
|
-
if (
|
|
690
|
-
artifact.role === 'component' &&
|
|
691
|
-
artifact.target !== 'resource' &&
|
|
692
|
-
artifact.targetVersion &&
|
|
693
|
-
PROTOCOL_V2_INSTALL_TARGET_BY_UPDATE_TARGET.has(
|
|
694
|
-
artifact.target as Exclude<FirmwareUpdateV4Target, 'resource'>
|
|
695
|
-
)
|
|
696
|
-
) {
|
|
697
|
-
result[artifact.target as FirmwareUpdateV4Target] = artifact.targetVersion;
|
|
698
|
-
}
|
|
699
|
-
return result;
|
|
700
|
-
}, {});
|
|
701
|
-
const localTargetsToUpdate = payload.targetsToUpdate?.map((target: FirmwareUpdateV4Target) =>
|
|
702
|
-
target === 'boot_resources' ? 'resource' : target
|
|
703
|
-
);
|
|
704
|
-
if (
|
|
705
|
-
payload.resourceArchiveBinary &&
|
|
706
|
-
localTargetsToUpdate &&
|
|
707
|
-
!localTargetsToUpdate.includes('resource')
|
|
708
|
-
) {
|
|
709
|
-
localTargetsToUpdate.push('resource');
|
|
710
|
-
}
|
|
711
|
-
// 本地 ZIP 本身就是明确的资源升级请求,不要求调用方重复声明 target。
|
|
712
|
-
const resolvedLocalTargetsToUpdate =
|
|
713
|
-
localTargetsToUpdate ?? (payload.resourceArchiveBinary ? ['resource'] : undefined);
|
|
714
|
-
|
|
715
594
|
this.params = {
|
|
716
|
-
preparedPlan,
|
|
595
|
+
preparedPlan: payload.preparedPlan,
|
|
717
596
|
chunkSize: payload.chunkSize,
|
|
718
597
|
forcedUpdateRes: payload.forcedUpdateRes,
|
|
719
598
|
bootloaderBinary: payload.bootloaderBinary,
|
|
@@ -725,18 +604,15 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
725
604
|
se02Binary: payload.se02Binary,
|
|
726
605
|
se03Binary: payload.se03Binary,
|
|
727
606
|
se04Binary: payload.se04Binary,
|
|
728
|
-
|
|
729
|
-
firmwareType:
|
|
730
|
-
targetsToUpdate:
|
|
731
|
-
|
|
732
|
-
: resolvedLocalTargetsToUpdate,
|
|
733
|
-
expectedTargetVersions: preparedPlan
|
|
734
|
-
? preparedExpectedTargetVersions
|
|
735
|
-
: payload.expectedTargetVersions,
|
|
607
|
+
resourceFiles: payload.resourceFiles,
|
|
608
|
+
firmwareType: payload.firmwareType,
|
|
609
|
+
targetsToUpdate: payload.targetsToUpdate,
|
|
610
|
+
expectedTargetVersions: payload.expectedTargetVersions,
|
|
736
611
|
platform: payload.platform,
|
|
737
612
|
expectedDeviceId: payload.expectedDeviceId,
|
|
738
|
-
artifactReader,
|
|
739
|
-
componentArtifacts:
|
|
613
|
+
artifactReader: hostBinding?.artifactReader ?? payload.artifactReader,
|
|
614
|
+
componentArtifacts: payload.componentArtifacts,
|
|
615
|
+
resourceBundleArtifacts: payload.resourceBundleArtifacts,
|
|
740
616
|
};
|
|
741
617
|
}
|
|
742
618
|
|
|
@@ -783,55 +659,30 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
783
659
|
this.validateExpectedTargetVersions();
|
|
784
660
|
|
|
785
661
|
if (
|
|
786
|
-
|
|
787
|
-
this.params.
|
|
788
|
-
this.params.targetsToUpdate?.includes('resource')
|
|
662
|
+
(this.params.componentArtifacts && Object.keys(this.params.componentArtifacts).length > 0) ||
|
|
663
|
+
this.params.resourceBundleArtifacts?.length
|
|
789
664
|
) {
|
|
790
|
-
const localMemoryHost = await this.prepareProtocolV2LocalMemoryHost({
|
|
791
|
-
features: deviceFeatures,
|
|
792
|
-
firmwareType,
|
|
793
|
-
});
|
|
794
|
-
try {
|
|
795
|
-
return await this.runProtocolV2PreparedArtifacts(deviceFeatures, firmwareType);
|
|
796
|
-
} finally {
|
|
797
|
-
localMemoryHost.release();
|
|
798
|
-
}
|
|
799
|
-
}
|
|
800
|
-
|
|
801
|
-
const hasPreparedComponentArtifacts = Object.values(this.params.componentArtifacts ?? {}).some(
|
|
802
|
-
Boolean
|
|
803
|
-
);
|
|
804
|
-
if (this.params.preparedPlan || hasPreparedComponentArtifacts) {
|
|
805
665
|
return this.runProtocolV2PreparedArtifacts(deviceFeatures, firmwareType);
|
|
806
666
|
}
|
|
807
|
-
const
|
|
667
|
+
const hasExplicitResourceFiles = !!this.params.resourceFiles?.length;
|
|
668
|
+
const wantsStableResources = !!this.params.targetsToUpdate?.includes('resource');
|
|
669
|
+
const wantsBootResources = !!this.params.targetsToUpdate?.includes('boot_resources');
|
|
670
|
+
const needsRemoteResources = !hasExplicitResourceFiles && wantsStableResources;
|
|
671
|
+
const needsRemoteBootResources =
|
|
672
|
+
!hasExplicitResourceFiles && (wantsBootResources || wantsStableResources);
|
|
808
673
|
|
|
809
674
|
let fwBinaryMap: ProtocolV2TargetBinary[] = [];
|
|
810
675
|
let bootloaderBinary: ArrayBuffer | null = null;
|
|
811
676
|
let installItems: ProtocolV2InstallItem[] | undefined;
|
|
677
|
+
let resourceBundles: ProtocolV2ResourceBundleBinary[] | undefined;
|
|
812
678
|
try {
|
|
813
679
|
this.postTipMessage(FirmwareUpdateTipMessage.StartDownloadFirmware);
|
|
680
|
+
resourceBundles = this.prepareExplicitProtocolV2ResourceFiles();
|
|
814
681
|
fwBinaryMap = this.collectExplicitTargetBinaries();
|
|
815
682
|
bootloaderBinary = this.prepareBootloaderBinary();
|
|
816
|
-
const
|
|
817
|
-
bootloaderBinary,
|
|
818
|
-
fwBinaryMap,
|
|
819
|
-
});
|
|
820
|
-
const missingFirmwareTargets = this.getMissingProtocolV2FirmwareTargets(explicitInstallItems);
|
|
821
|
-
const needsRemoteFirmware = this.params.targetsToUpdate?.length
|
|
822
|
-
? missingFirmwareTargets.length > 0
|
|
823
|
-
: explicitInstallItems.length === 0;
|
|
824
|
-
if (wantsResources) {
|
|
825
|
-
throw ERRORS.TypedError(
|
|
826
|
-
HardwareErrorCode.RuntimeError,
|
|
827
|
-
'Protocol V2 resource archive must be provided through a local or external PreparedPlan',
|
|
828
|
-
{
|
|
829
|
-
firmwareUpdateCode: 'FirmwareArtifactsNotPrepared',
|
|
830
|
-
}
|
|
831
|
-
);
|
|
832
|
-
}
|
|
683
|
+
const needsRemoteFirmware = !this.hasExplicitProtocolV2Payload(fwBinaryMap);
|
|
833
684
|
if (
|
|
834
|
-
needsRemoteFirmware &&
|
|
685
|
+
(needsRemoteFirmware || needsRemoteResources || needsRemoteBootResources) &&
|
|
835
686
|
(this.params.artifactReader ||
|
|
836
687
|
DataManager.getSettings('firmwareManifestMode') === 'external-only')
|
|
837
688
|
) {
|
|
@@ -843,47 +694,74 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
843
694
|
}
|
|
844
695
|
);
|
|
845
696
|
}
|
|
846
|
-
if (needsRemoteFirmware) {
|
|
697
|
+
if (needsRemoteFirmware || needsRemoteResources || needsRemoteBootResources) {
|
|
847
698
|
// Remote updates must use a freshly fetched config before any reboot or file write.
|
|
848
|
-
await DataManager.forceReloadData(
|
|
699
|
+
await DataManager.forceReloadData({
|
|
700
|
+
requireResources: needsRemoteResources || needsRemoteBootResources,
|
|
701
|
+
});
|
|
849
702
|
}
|
|
850
703
|
if (needsRemoteFirmware) {
|
|
851
704
|
const remoteBinaries = await this.prepareRemoteProtocolV2Binaries(
|
|
852
705
|
firmwareType,
|
|
853
|
-
deviceFeatures
|
|
854
|
-
explicitInstallItems
|
|
706
|
+
deviceFeatures
|
|
855
707
|
);
|
|
856
708
|
bootloaderBinary = remoteBinaries.bootloaderBinary;
|
|
857
709
|
fwBinaryMap = remoteBinaries.fwBinaryMap;
|
|
858
710
|
installItems = remoteBinaries.installItems;
|
|
859
711
|
}
|
|
860
|
-
this.
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
(err as HardwareError).params?.firmwareUpdateCode === 'FirmwareArtifactsNotPrepared'
|
|
867
|
-
) {
|
|
868
|
-
throw err;
|
|
712
|
+
const bootResourceFiles = await this.prepareProtocolV2BootResources();
|
|
713
|
+
if (bootResourceFiles?.length) {
|
|
714
|
+
resourceBundles = this.mergeProtocolV2ResourceBundles(resourceBundles, bootResourceFiles);
|
|
715
|
+
}
|
|
716
|
+
if (!needsRemoteResources) {
|
|
717
|
+
this.postTipMessage(FirmwareUpdateTipMessage.FinishDownloadFirmware);
|
|
869
718
|
}
|
|
719
|
+
} catch (err) {
|
|
870
720
|
if (err instanceof HardwareError && err.errorCode === HardwareErrorCode.NetworkError) {
|
|
871
721
|
throw err;
|
|
872
722
|
}
|
|
873
723
|
throw normalizeFirmwarePreparationError(err);
|
|
874
724
|
}
|
|
875
725
|
|
|
876
|
-
if (
|
|
726
|
+
if (
|
|
727
|
+
!bootloaderBinary &&
|
|
728
|
+
fwBinaryMap.length === 0 &&
|
|
729
|
+
!installItems?.length &&
|
|
730
|
+
!resourceBundles?.length &&
|
|
731
|
+
!needsRemoteResources
|
|
732
|
+
) {
|
|
877
733
|
throw ERRORS.TypedError(
|
|
878
734
|
HardwareErrorCode.FirmwareUpdateDownloadFailed,
|
|
879
735
|
'No firmware to update'
|
|
880
736
|
);
|
|
881
737
|
}
|
|
882
738
|
|
|
739
|
+
if (needsRemoteResources) {
|
|
740
|
+
const enteredBootloader = await this.enterProtocolV2BootloaderMode();
|
|
741
|
+
try {
|
|
742
|
+
const stableResources = await this.prepareProtocolV2ResourceBundles();
|
|
743
|
+
resourceBundles = this.mergeProtocolV2ResourceBundles(resourceBundles, stableResources);
|
|
744
|
+
this.postTipMessage(FirmwareUpdateTipMessage.FinishDownloadFirmware);
|
|
745
|
+
} catch (err) {
|
|
746
|
+
if (enteredBootloader) {
|
|
747
|
+
try {
|
|
748
|
+
await this.exitProtocolV2BootloaderToNormal();
|
|
749
|
+
} catch (restoreError) {
|
|
750
|
+
Log.warn(
|
|
751
|
+
'[FirmwareUpdateV4] failed to restore App mode after resource preparation error:',
|
|
752
|
+
restoreError
|
|
753
|
+
);
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
throw normalizeFirmwarePreparationError(err);
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
|
|
883
760
|
return this.executeProtocolV2Update({
|
|
884
761
|
fwBinaryMap,
|
|
885
762
|
bootloaderBinary,
|
|
886
763
|
...(installItems ? { installItems } : undefined),
|
|
764
|
+
...(resourceBundles?.length ? { resourceBundles } : undefined),
|
|
887
765
|
});
|
|
888
766
|
}
|
|
889
767
|
|
|
@@ -926,54 +804,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
926
804
|
firmwareType: EFirmwareType,
|
|
927
805
|
features: Features
|
|
928
806
|
): Promise<ProtocolV2InstallSource[]> {
|
|
929
|
-
if (this.params.preparedPlan) {
|
|
930
|
-
const requestedTargets = new Set(
|
|
931
|
-
this.params.preparedPlan.targetsToUpdate.filter(
|
|
932
|
-
(target): target is Exclude<FirmwareUpdateV4Target, 'resource'> => target !== 'resource'
|
|
933
|
-
)
|
|
934
|
-
);
|
|
935
|
-
const installSources: ProtocolV2InstallSource[] = [];
|
|
936
|
-
const preparedTargets = new Set<Exclude<FirmwareUpdateV4Target, 'resource'>>();
|
|
937
|
-
for (const artifact of this.params.preparedPlan.artifacts) {
|
|
938
|
-
if (artifact.target !== 'resource') {
|
|
939
|
-
const target = artifact.target as Exclude<FirmwareUpdateV4Target, 'resource'>;
|
|
940
|
-
const installTarget = PROTOCOL_V2_INSTALL_TARGET_BY_UPDATE_TARGET.get(target);
|
|
941
|
-
if (
|
|
942
|
-
!requestedTargets.has(target) ||
|
|
943
|
-
artifact.role !== 'component' ||
|
|
944
|
-
artifact.container !== 'raw' ||
|
|
945
|
-
!installTarget ||
|
|
946
|
-
preparedTargets.has(target)
|
|
947
|
-
) {
|
|
948
|
-
throw ERRORS.TypedError(
|
|
949
|
-
HardwareErrorCode.RuntimeError,
|
|
950
|
-
`Protocol V2 prepared component artifact is invalid: ${artifact.artifactId}`,
|
|
951
|
-
{ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
|
|
952
|
-
);
|
|
953
|
-
}
|
|
954
|
-
installSources.push({
|
|
955
|
-
...installTarget,
|
|
956
|
-
source: await this.openProtocolV2PreparedSource(artifact.artifact),
|
|
957
|
-
});
|
|
958
|
-
preparedTargets.add(target);
|
|
959
|
-
}
|
|
960
|
-
}
|
|
961
|
-
const missingTarget = Array.from(requestedTargets).find(
|
|
962
|
-
target => !preparedTargets.has(target)
|
|
963
|
-
);
|
|
964
|
-
if (missingTarget) {
|
|
965
|
-
throw ERRORS.TypedError(
|
|
966
|
-
HardwareErrorCode.RuntimeError,
|
|
967
|
-
`Protocol V2 ${missingTarget} artifact is not prepared`,
|
|
968
|
-
{
|
|
969
|
-
firmwareUpdateCode: 'FirmwareArtifactsNotPrepared',
|
|
970
|
-
artifactName: missingTarget,
|
|
971
|
-
}
|
|
972
|
-
);
|
|
973
|
-
}
|
|
974
|
-
return installSources;
|
|
975
|
-
}
|
|
976
|
-
|
|
977
807
|
const release = DataManager.getFirmwareLatestRelease(features, firmwareType);
|
|
978
808
|
if (!release) {
|
|
979
809
|
throw ERRORS.TypedError(
|
|
@@ -1023,430 +853,53 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1023
853
|
return installSources;
|
|
1024
854
|
}
|
|
1025
855
|
|
|
1026
|
-
private async
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
const installItems = this.buildProtocolV2InstallItems({
|
|
1034
|
-
bootloaderBinary: this.prepareBootloaderBinary(),
|
|
1035
|
-
fwBinaryMap: this.collectExplicitTargetBinaries(),
|
|
1036
|
-
});
|
|
1037
|
-
const requestedComponentTargets = new Set(
|
|
1038
|
-
(this.params.targetsToUpdate ?? []).filter(
|
|
1039
|
-
(target): target is Exclude<FirmwareUpdateV4Target, 'resource' | 'boot_resources'> =>
|
|
1040
|
-
target !== 'resource' && target !== 'boot_resources'
|
|
1041
|
-
)
|
|
1042
|
-
);
|
|
1043
|
-
const localComponentTargets = new Set(
|
|
1044
|
-
installItems.flatMap(item => {
|
|
1045
|
-
const target = PROTOCOL_V2_UPDATE_TARGET_BY_TARGET_ID.get(item.targetId);
|
|
1046
|
-
return target ? [target] : [];
|
|
1047
|
-
})
|
|
1048
|
-
);
|
|
1049
|
-
const missingTarget = Array.from(requestedComponentTargets).find(
|
|
1050
|
-
target => !localComponentTargets.has(target)
|
|
1051
|
-
);
|
|
1052
|
-
if (missingTarget) {
|
|
1053
|
-
throw ERRORS.TypedError(
|
|
1054
|
-
HardwareErrorCode.RuntimeError,
|
|
1055
|
-
`Protocol V2 local update has no binary for requested target ${missingTarget}`,
|
|
1056
|
-
{
|
|
1057
|
-
firmwareUpdateCode: 'FirmwareArtifactsNotPrepared',
|
|
1058
|
-
artifactName: missingTarget,
|
|
1059
|
-
}
|
|
1060
|
-
);
|
|
1061
|
-
}
|
|
1062
|
-
|
|
1063
|
-
const planArtifacts: Parameters<typeof buildProtocolV2LocalFirmwareUpdatePlan>[0]['artifacts'] =
|
|
1064
|
-
[];
|
|
1065
|
-
const memoryArtifacts: FirmwareMemoryArtifact[] = [];
|
|
1066
|
-
for (const item of installItems) {
|
|
1067
|
-
const target = PROTOCOL_V2_UPDATE_TARGET_BY_TARGET_ID.get(item.targetId);
|
|
1068
|
-
if (!target || item.binary.byteLength <= 0) {
|
|
1069
|
-
throw ERRORS.TypedError(
|
|
1070
|
-
HardwareErrorCode.RuntimeError,
|
|
1071
|
-
`Protocol V2 local firmware artifact is invalid: ${item.fileName}`,
|
|
1072
|
-
{ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
|
|
1073
|
-
);
|
|
1074
|
-
}
|
|
1075
|
-
const artifactId = `component:${target}`;
|
|
1076
|
-
planArtifacts.push({
|
|
1077
|
-
artifactId,
|
|
1078
|
-
target,
|
|
1079
|
-
container: 'raw',
|
|
1080
|
-
logicalName: item.fileName,
|
|
1081
|
-
expectedSize: item.binary.byteLength,
|
|
1082
|
-
expectedSha256: bytesToHex(sha256(new Uint8Array(item.binary))),
|
|
1083
|
-
...(this.params.expectedTargetVersions?.[target]
|
|
1084
|
-
? { targetVersion: this.params.expectedTargetVersions[target] }
|
|
1085
|
-
: {}),
|
|
1086
|
-
});
|
|
1087
|
-
memoryArtifacts.push({ artifactId, binary: item.binary });
|
|
1088
|
-
}
|
|
1089
|
-
|
|
1090
|
-
const resourceArchive = await this.prepareProtocolV2LocalResourceArchive(
|
|
1091
|
-
this.params.resourceArchiveBinary as ArrayBuffer
|
|
1092
|
-
);
|
|
1093
|
-
const resourceArtifactId = 'resource:archive';
|
|
1094
|
-
planArtifacts.push({
|
|
1095
|
-
artifactId: resourceArtifactId,
|
|
1096
|
-
target: 'resource',
|
|
1097
|
-
container: 'zip',
|
|
1098
|
-
logicalName: 'protocol-v2-local-resource-archive',
|
|
1099
|
-
expectedSize: resourceArchive.binary.byteLength,
|
|
1100
|
-
expectedSha256: bytesToHex(sha256(new Uint8Array(resourceArchive.binary))),
|
|
1101
|
-
});
|
|
1102
|
-
memoryArtifacts.push({
|
|
1103
|
-
artifactId: resourceArtifactId,
|
|
1104
|
-
binary: resourceArchive.binary,
|
|
1105
|
-
materializedEntries: resourceArchive.materializedEntries,
|
|
1106
|
-
});
|
|
1107
|
-
|
|
1108
|
-
const plan = buildProtocolV2LocalFirmwareUpdatePlan({
|
|
1109
|
-
features,
|
|
1110
|
-
firmwareType,
|
|
1111
|
-
platform: this.params.platform,
|
|
1112
|
-
artifacts: planArtifacts,
|
|
1113
|
-
});
|
|
1114
|
-
let memoryHost: FirmwareUpdateV4MemoryHost | undefined;
|
|
1115
|
-
try {
|
|
1116
|
-
memoryHost = prepareFirmwareUpdateV4MemoryHost({
|
|
1117
|
-
sdk: {
|
|
1118
|
-
prepareFirmwareUpdatePlan,
|
|
1119
|
-
registerFirmwareUpdateHostBinding,
|
|
1120
|
-
unregisterFirmwareUpdateHostBinding,
|
|
1121
|
-
},
|
|
1122
|
-
plan,
|
|
1123
|
-
artifacts: memoryArtifacts,
|
|
1124
|
-
});
|
|
1125
|
-
const preparedPlan = validateFirmwareUpdatePreparedPlan(memoryHost.preparedPlan);
|
|
1126
|
-
assertFirmwareUpdatePreparedPlanBinding({
|
|
1127
|
-
preparedPlan,
|
|
1128
|
-
executor: 'v4',
|
|
1129
|
-
platform: this.params.platform,
|
|
1130
|
-
scopeTargets: [],
|
|
1131
|
-
bindings: [],
|
|
1132
|
-
});
|
|
1133
|
-
assertFirmwareUpdatePreparedPlanDeviceIdentity({
|
|
1134
|
-
preparedPlan,
|
|
1135
|
-
deviceIdentity: this.protocolV2ExpectedSerialNumber,
|
|
1136
|
-
deviceModel: this.getProtocolV2PreparedPlanDeviceModel(features),
|
|
1137
|
-
});
|
|
1138
|
-
const hostBinding = resolveFirmwareUpdateHostBinding(
|
|
1139
|
-
memoryHost.hostBindingGeneration,
|
|
1140
|
-
preparedPlan.preparedPlanDigest
|
|
1141
|
-
);
|
|
1142
|
-
this.params.preparedPlan = preparedPlan;
|
|
1143
|
-
this.params.targetsToUpdate = [...preparedPlan.targetsToUpdate] as FirmwareUpdateV4Target[];
|
|
1144
|
-
this.params.artifactReader = hostBinding.artifactReader;
|
|
1145
|
-
this.params.componentArtifacts = undefined;
|
|
1146
|
-
return memoryHost;
|
|
1147
|
-
} catch (error) {
|
|
1148
|
-
memoryHost?.release();
|
|
1149
|
-
throw error;
|
|
1150
|
-
}
|
|
1151
|
-
}
|
|
1152
|
-
|
|
1153
|
-
private async prepareProtocolV2LocalResourceArchive(
|
|
1154
|
-
binary: ArrayBuffer
|
|
1155
|
-
): Promise<ProtocolV2LocalResourceArchive> {
|
|
1156
|
-
if (binary.byteLength <= 0 || binary.byteLength > PROTOCOL_V2_RESOURCE_TOTAL_MAX_BYTES) {
|
|
1157
|
-
throw ERRORS.TypedError(
|
|
1158
|
-
HardwareErrorCode.RuntimeError,
|
|
1159
|
-
'Protocol V2 local resource ZIP archive size is invalid',
|
|
1160
|
-
{ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
|
|
1161
|
-
);
|
|
1162
|
-
}
|
|
1163
|
-
const zip = await JSZip.loadAsync(binary);
|
|
1164
|
-
const zipEntries = Object.values(zip.files);
|
|
1165
|
-
if (
|
|
1166
|
-
zipEntries.some(entry => entry.unsafeOriginalName && entry.unsafeOriginalName !== entry.name)
|
|
1167
|
-
) {
|
|
1168
|
-
throw ERRORS.TypedError(
|
|
1169
|
-
HardwareErrorCode.RuntimeError,
|
|
1170
|
-
'Protocol V2 local resource ZIP contains an unsafe entry path',
|
|
1171
|
-
{ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
|
|
1172
|
-
);
|
|
1173
|
-
}
|
|
1174
|
-
const entries = zipEntries.filter(entry => !entry.dir);
|
|
1175
|
-
if (entries.length === 0 || entries.length > PROTOCOL_V2_RESOURCE_FILE_MAX_COUNT + 1) {
|
|
1176
|
-
throw ERRORS.TypedError(
|
|
1177
|
-
HardwareErrorCode.RuntimeError,
|
|
1178
|
-
'Protocol V2 local resource ZIP entry set is invalid',
|
|
1179
|
-
{ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
|
|
1180
|
-
);
|
|
1181
|
-
}
|
|
1182
|
-
let declaredUncompressedSize = 0;
|
|
1183
|
-
let declaredCompressedSize = 0;
|
|
1184
|
-
for (const entry of entries) {
|
|
1185
|
-
const sizes = getProtocolV2ZipEntrySizes(entry);
|
|
1186
|
-
declaredCompressedSize += sizes.compressedSize;
|
|
1187
|
-
declaredUncompressedSize += sizes.uncompressedSize;
|
|
1188
|
-
const entryLimit =
|
|
1189
|
-
entry.name === 'manifest.json'
|
|
1190
|
-
? PROTOCOL_V2_RESOURCE_MANIFEST_MAX_BYTES
|
|
1191
|
-
: PROTOCOL_V2_RESOURCE_TOTAL_MAX_BYTES;
|
|
1192
|
-
if (
|
|
1193
|
-
sizes.uncompressedSize > entryLimit ||
|
|
1194
|
-
declaredCompressedSize > binary.byteLength ||
|
|
1195
|
-
declaredUncompressedSize >
|
|
1196
|
-
PROTOCOL_V2_RESOURCE_TOTAL_MAX_BYTES + PROTOCOL_V2_RESOURCE_MANIFEST_MAX_BYTES
|
|
1197
|
-
) {
|
|
1198
|
-
throw ERRORS.TypedError(
|
|
1199
|
-
HardwareErrorCode.RuntimeError,
|
|
1200
|
-
'Protocol V2 local resource ZIP declared size exceeds the allowed limit',
|
|
1201
|
-
{ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
|
|
1202
|
-
);
|
|
1203
|
-
}
|
|
1204
|
-
}
|
|
1205
|
-
const manifestEntry = zip.file('manifest.json');
|
|
1206
|
-
if (!manifestEntry) {
|
|
1207
|
-
throw ERRORS.TypedError(
|
|
1208
|
-
HardwareErrorCode.RuntimeError,
|
|
1209
|
-
'Protocol V2 local resource ZIP has no manifest.json',
|
|
1210
|
-
{ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
|
|
1211
|
-
);
|
|
1212
|
-
}
|
|
1213
|
-
const manifestBinary = await manifestEntry.async('arraybuffer');
|
|
1214
|
-
if (
|
|
1215
|
-
manifestBinary.byteLength <= 0 ||
|
|
1216
|
-
manifestBinary.byteLength > PROTOCOL_V2_RESOURCE_MANIFEST_MAX_BYTES
|
|
1217
|
-
) {
|
|
1218
|
-
throw ERRORS.TypedError(
|
|
1219
|
-
HardwareErrorCode.RuntimeError,
|
|
1220
|
-
'Protocol V2 local resource manifest size is invalid',
|
|
1221
|
-
{ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
|
|
1222
|
-
);
|
|
1223
|
-
}
|
|
1224
|
-
|
|
1225
|
-
let manifestValue: unknown;
|
|
1226
|
-
try {
|
|
1227
|
-
manifestValue = JSON.parse(new TextDecoder().decode(manifestBinary));
|
|
1228
|
-
} catch (error) {
|
|
1229
|
-
throw ERRORS.TypedError(
|
|
1230
|
-
HardwareErrorCode.RuntimeError,
|
|
1231
|
-
`Protocol V2 local resource manifest is invalid: ${String(error)}`,
|
|
1232
|
-
{ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
|
|
1233
|
-
);
|
|
1234
|
-
}
|
|
1235
|
-
const manifest = parseProtocolV2ResourceManifest(manifestValue);
|
|
1236
|
-
const selectedFiles = selectProtocolV2ResourceManifestFiles({
|
|
1237
|
-
manifest,
|
|
1238
|
-
targetsToUpdate: this.params.targetsToUpdate ?? [],
|
|
1239
|
-
});
|
|
1240
|
-
const expectedEntryNames = new Set([
|
|
1241
|
-
'manifest.json',
|
|
1242
|
-
...selectedFiles.map(file => file.archive_path),
|
|
1243
|
-
]);
|
|
1244
|
-
if (
|
|
1245
|
-
entries.length !== expectedEntryNames.size ||
|
|
1246
|
-
entries.some(entry => !expectedEntryNames.has(entry.name))
|
|
1247
|
-
) {
|
|
1248
|
-
throw ERRORS.TypedError(
|
|
1249
|
-
HardwareErrorCode.RuntimeError,
|
|
1250
|
-
'Protocol V2 local resource ZIP contains an unexpected entry',
|
|
1251
|
-
{ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
|
|
1252
|
-
);
|
|
1253
|
-
}
|
|
1254
|
-
|
|
1255
|
-
let totalSize = 0;
|
|
1256
|
-
const materializedEntries: FirmwareMemoryArtifactEntry[] = [
|
|
1257
|
-
{ entryName: 'manifest.json', binary: manifestBinary },
|
|
1258
|
-
];
|
|
1259
|
-
for (const file of selectedFiles) {
|
|
1260
|
-
const entry = zip.file(file.archive_path);
|
|
1261
|
-
if (!entry) {
|
|
1262
|
-
throw ERRORS.TypedError(
|
|
1263
|
-
HardwareErrorCode.RuntimeError,
|
|
1264
|
-
`Protocol V2 local resource ZIP is missing ${file.archive_path}`,
|
|
1265
|
-
{ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
|
|
1266
|
-
);
|
|
1267
|
-
}
|
|
1268
|
-
const { uncompressedSize } = getProtocolV2ZipEntrySizes(entry);
|
|
1269
|
-
totalSize += uncompressedSize;
|
|
1270
|
-
if (uncompressedSize !== file.size || totalSize > PROTOCOL_V2_RESOURCE_TOTAL_MAX_BYTES) {
|
|
1271
|
-
throw ERRORS.TypedError(
|
|
1272
|
-
HardwareErrorCode.RuntimeError,
|
|
1273
|
-
`Protocol V2 local resource file declared size is invalid: ${file.archive_path}`,
|
|
1274
|
-
{ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
|
|
1275
|
-
);
|
|
1276
|
-
}
|
|
1277
|
-
const fileBinary = await entry.async('arraybuffer');
|
|
1278
|
-
const digest = bytesToHex(sha256(new Uint8Array(fileBinary)));
|
|
1279
|
-
if (fileBinary.byteLength !== file.size || digest !== file.sha256.toLowerCase()) {
|
|
1280
|
-
throw ERRORS.TypedError(
|
|
1281
|
-
HardwareErrorCode.RuntimeError,
|
|
1282
|
-
`Protocol V2 local resource file does not match manifest: ${file.archive_path}`,
|
|
1283
|
-
{ firmwareUpdateCode: 'FirmwareArtifactReceiptMismatch' }
|
|
1284
|
-
);
|
|
1285
|
-
}
|
|
1286
|
-
materializedEntries.push({ entryName: file.archive_path, binary: fileBinary });
|
|
1287
|
-
}
|
|
1288
|
-
return { binary, materializedEntries };
|
|
1289
|
-
}
|
|
1290
|
-
|
|
1291
|
-
private async prepareProtocolV2ResourceSources(): Promise<ProtocolV2ResourceBundleSource[]> {
|
|
1292
|
-
const resourceRequested = this.params.targetsToUpdate?.includes('resource') ?? false;
|
|
856
|
+
private async prepareProtocolV2ResourceSources(
|
|
857
|
+
firmwareType: EFirmwareType,
|
|
858
|
+
features: Features
|
|
859
|
+
): Promise<ProtocolV2ResourceBundleSource[]> {
|
|
860
|
+
const preparedArtifacts = this.params.resourceBundleArtifacts ?? [];
|
|
861
|
+
const resourceRequested =
|
|
862
|
+
this.params.targetsToUpdate?.includes('resource') || preparedArtifacts.length > 0;
|
|
1293
863
|
if (!resourceRequested) {
|
|
1294
864
|
return [];
|
|
1295
865
|
}
|
|
1296
|
-
const
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
throw ERRORS.TypedError(
|
|
1301
|
-
HardwareErrorCode.RuntimeError,
|
|
1302
|
-
'Protocol V2 resource archive is not prepared',
|
|
1303
|
-
{ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
|
|
1304
|
-
);
|
|
1305
|
-
}
|
|
1306
|
-
|
|
1307
|
-
private async prepareProtocolV2ResourceArchiveSources(): Promise<
|
|
1308
|
-
ProtocolV2ResourceBundleSource[] | undefined
|
|
1309
|
-
> {
|
|
1310
|
-
const archiveArtifacts =
|
|
1311
|
-
this.params.preparedPlan?.artifacts.filter(
|
|
1312
|
-
artifact =>
|
|
1313
|
-
artifact.role === 'resourceBundle' &&
|
|
1314
|
-
artifact.target === 'resource' &&
|
|
1315
|
-
artifact.container === 'zip'
|
|
1316
|
-
) ?? [];
|
|
1317
|
-
if (archiveArtifacts.length === 0) {
|
|
1318
|
-
return undefined;
|
|
1319
|
-
}
|
|
1320
|
-
if (archiveArtifacts.length !== 1) {
|
|
1321
|
-
throw ERRORS.TypedError(
|
|
1322
|
-
HardwareErrorCode.RuntimeError,
|
|
1323
|
-
'Protocol V2 prepared plan must contain exactly one resource archive',
|
|
1324
|
-
{ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
|
|
1325
|
-
);
|
|
1326
|
-
}
|
|
1327
|
-
|
|
1328
|
-
const archiveArtifact = archiveArtifacts[0];
|
|
1329
|
-
const archiveSource = await this.openProtocolV2PreparedSource(archiveArtifact.artifact);
|
|
1330
|
-
if (archiveSource.size > PROTOCOL_V2_RESOURCE_TOTAL_MAX_BYTES) {
|
|
1331
|
-
throw ERRORS.TypedError(
|
|
1332
|
-
HardwareErrorCode.RuntimeError,
|
|
1333
|
-
'Protocol V2 prepared resource archive exceeds the total size limit',
|
|
1334
|
-
{ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
|
|
1335
|
-
);
|
|
1336
|
-
}
|
|
1337
|
-
|
|
1338
|
-
let archiveBinary: ArrayBuffer;
|
|
1339
|
-
try {
|
|
1340
|
-
archiveBinary = await readFirmwareByteSourceFully(archiveSource);
|
|
1341
|
-
} finally {
|
|
1342
|
-
await archiveSource.close();
|
|
1343
|
-
}
|
|
1344
|
-
const archiveDigest = bytesToHex(sha256(new Uint8Array(archiveBinary)));
|
|
1345
|
-
if (archiveDigest !== archiveArtifact.artifact.sha256.toLowerCase()) {
|
|
1346
|
-
throw ERRORS.TypedError(
|
|
1347
|
-
HardwareErrorCode.RuntimeError,
|
|
1348
|
-
'Protocol V2 prepared resource archive does not match its approved receipt',
|
|
1349
|
-
{ firmwareUpdateCode: 'FirmwareArtifactReceiptMismatch' }
|
|
1350
|
-
);
|
|
1351
|
-
}
|
|
1352
|
-
|
|
1353
|
-
// materializedEntries 由宿主生成,只有与获批 ZIP 的规范字节完全一致后才能使用。
|
|
1354
|
-
const verifiedArchive = await this.prepareProtocolV2LocalResourceArchive(archiveBinary);
|
|
1355
|
-
const entries = archiveArtifact.materializedEntries ?? [];
|
|
1356
|
-
const entriesByName = new Map(entries.map(entry => [entry.entryName, entry] as const));
|
|
1357
|
-
if (
|
|
1358
|
-
entries.length !== verifiedArchive.materializedEntries.length ||
|
|
1359
|
-
verifiedArchive.materializedEntries.some(entry => {
|
|
1360
|
-
const preparedEntry = entriesByName.get(entry.entryName);
|
|
1361
|
-
const digest = bytesToHex(sha256(new Uint8Array(entry.binary)));
|
|
1362
|
-
return (
|
|
1363
|
-
!preparedEntry ||
|
|
1364
|
-
preparedEntry.artifact.size !== entry.binary.byteLength ||
|
|
1365
|
-
preparedEntry.artifact.sha256.toLowerCase() !== digest
|
|
1366
|
-
);
|
|
1367
|
-
})
|
|
1368
|
-
) {
|
|
1369
|
-
throw ERRORS.TypedError(
|
|
1370
|
-
HardwareErrorCode.RuntimeError,
|
|
1371
|
-
'Protocol V2 prepared resource entries do not match the approved archive',
|
|
1372
|
-
{ firmwareUpdateCode: 'FirmwareArtifactReceiptMismatch' }
|
|
1373
|
-
);
|
|
1374
|
-
}
|
|
1375
|
-
|
|
1376
|
-
const verifiedEntriesByName = new Map(
|
|
1377
|
-
verifiedArchive.materializedEntries.map(entry => [entry.entryName, entry.binary] as const)
|
|
866
|
+
const release = DataManager.getFirmwareLatestRelease(features, firmwareType);
|
|
867
|
+
const descriptors = release?.resourceBundles ?? [];
|
|
868
|
+
const artifactByName = new Map(
|
|
869
|
+
preparedArtifacts.map(item => [item.name, item.artifact] as const)
|
|
1378
870
|
);
|
|
1379
|
-
const manifestBinary = verifiedEntriesByName.get('manifest.json');
|
|
1380
|
-
if (!manifestBinary) {
|
|
1381
|
-
throw ERRORS.TypedError(
|
|
1382
|
-
HardwareErrorCode.RuntimeError,
|
|
1383
|
-
'Protocol V2 prepared resource archive has no valid manifest.json',
|
|
1384
|
-
{ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
|
|
1385
|
-
);
|
|
1386
|
-
}
|
|
1387
|
-
|
|
1388
|
-
let manifestValue: unknown;
|
|
1389
|
-
try {
|
|
1390
|
-
manifestValue = JSON.parse(new TextDecoder().decode(manifestBinary));
|
|
1391
|
-
} catch (error) {
|
|
1392
|
-
throw ERRORS.TypedError(
|
|
1393
|
-
HardwareErrorCode.RuntimeError,
|
|
1394
|
-
`Protocol V2 prepared resource manifest is invalid: ${String(error)}`,
|
|
1395
|
-
{ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
|
|
1396
|
-
);
|
|
1397
|
-
}
|
|
1398
|
-
const manifest = parseProtocolV2ResourceManifest(manifestValue);
|
|
1399
|
-
const selectedFiles = selectProtocolV2ResourceManifestFiles({
|
|
1400
|
-
manifest,
|
|
1401
|
-
targetsToUpdate: this.params.targetsToUpdate ?? [],
|
|
1402
|
-
});
|
|
1403
|
-
if (selectedFiles.length > PROTOCOL_V2_RESOURCE_FILE_MAX_COUNT) {
|
|
1404
|
-
throw ERRORS.TypedError(
|
|
1405
|
-
HardwareErrorCode.RuntimeError,
|
|
1406
|
-
'Protocol V2 prepared resource archive contains too many files',
|
|
1407
|
-
{ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
|
|
1408
|
-
);
|
|
1409
|
-
}
|
|
1410
|
-
|
|
1411
|
-
let totalSize = 0;
|
|
1412
871
|
const sources: ProtocolV2ResourceBundleSource[] = [];
|
|
1413
|
-
for (const
|
|
1414
|
-
const
|
|
1415
|
-
if (!
|
|
1416
|
-
throw ERRORS.TypedError(
|
|
1417
|
-
HardwareErrorCode.RuntimeError,
|
|
1418
|
-
`Protocol V2 prepared resource file does not match manifest: ${file.archive_path}`,
|
|
1419
|
-
{ firmwareUpdateCode: 'FirmwareArtifactReceiptMismatch' }
|
|
1420
|
-
);
|
|
1421
|
-
}
|
|
1422
|
-
totalSize += binary.byteLength;
|
|
1423
|
-
if (totalSize > PROTOCOL_V2_RESOURCE_TOTAL_MAX_BYTES) {
|
|
872
|
+
for (const descriptor of descriptors) {
|
|
873
|
+
const artifact = artifactByName.get(descriptor.name);
|
|
874
|
+
if (!artifact) {
|
|
1424
875
|
throw ERRORS.TypedError(
|
|
1425
876
|
HardwareErrorCode.RuntimeError,
|
|
1426
|
-
|
|
1427
|
-
{
|
|
877
|
+
`Protocol V2 resource bundle ${descriptor.name} is not prepared`,
|
|
878
|
+
{
|
|
879
|
+
firmwareUpdateCode: 'FirmwareArtifactsNotPrepared',
|
|
880
|
+
artifactName: descriptor.name,
|
|
881
|
+
}
|
|
1428
882
|
);
|
|
1429
883
|
}
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
? parseProtocolV2OkppHeader(
|
|
1435
|
-
new Uint8Array(await source.readAt(0, PROTOCOL_V2_OKPP_HEADER_SIZE))
|
|
1436
|
-
)
|
|
1437
|
-
: null;
|
|
884
|
+
const devicePath = validateProtocolV2FilesystemPath(
|
|
885
|
+
descriptor.devicePath,
|
|
886
|
+
'resourceBundles[].devicePath'
|
|
887
|
+
);
|
|
1438
888
|
sources.push({
|
|
1439
|
-
name:
|
|
1440
|
-
source,
|
|
1441
|
-
devicePath
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
payloadHash: header.payloadHash,
|
|
1446
|
-
headerHash: header.headerHash,
|
|
1447
|
-
}
|
|
1448
|
-
: {}),
|
|
889
|
+
name: descriptor.name,
|
|
890
|
+
source: await this.openProtocolV2PreparedSource(artifact),
|
|
891
|
+
devicePath,
|
|
892
|
+
version: descriptor.version,
|
|
893
|
+
payloadHash: descriptor.payloadHash,
|
|
894
|
+
headerHash: descriptor.headerHash,
|
|
1449
895
|
});
|
|
896
|
+
artifactByName.delete(descriptor.name);
|
|
897
|
+
}
|
|
898
|
+
if (artifactByName.size > 0) {
|
|
899
|
+
throw ERRORS.TypedError(
|
|
900
|
+
HardwareErrorCode.RuntimeError,
|
|
901
|
+
`Protocol V2 release does not contain resource bundle ${artifactByName.keys().next().value}`
|
|
902
|
+
);
|
|
1450
903
|
}
|
|
1451
904
|
return sources;
|
|
1452
905
|
}
|
|
@@ -1455,7 +908,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1455
908
|
try {
|
|
1456
909
|
this.postTipMessage(FirmwareUpdateTipMessage.StartDownloadFirmware);
|
|
1457
910
|
const installSources = await this.prepareProtocolV2InstallSources(firmwareType, features);
|
|
1458
|
-
const resourceSources = await this.prepareProtocolV2ResourceSources();
|
|
911
|
+
const resourceSources = await this.prepareProtocolV2ResourceSources(firmwareType, features);
|
|
1459
912
|
if (installSources.length === 0 && resourceSources.length === 0) {
|
|
1460
913
|
throw ERRORS.TypedError(
|
|
1461
914
|
HardwareErrorCode.FirmwareUpdateDownloadFailed,
|
|
@@ -1485,7 +938,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1485
938
|
for (const [target, version] of Object.entries(expected)) {
|
|
1486
939
|
if (
|
|
1487
940
|
!Array.from(PROTOCOL_V2_UPDATE_TARGET_BY_TARGET_ID.values()).includes(
|
|
1488
|
-
target as
|
|
941
|
+
target as FirmwareUpdateV4Target
|
|
1489
942
|
) ||
|
|
1490
943
|
typeof version !== 'string' ||
|
|
1491
944
|
!/^\d+\.\d+\.\d+(?:[-+][A-Za-z0-9.-]+)?$/u.test(version) ||
|
|
@@ -1571,13 +1024,13 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1571
1024
|
if (this.params.targetsToUpdate?.length) {
|
|
1572
1025
|
return [...new Set(this.params.targetsToUpdate)];
|
|
1573
1026
|
}
|
|
1574
|
-
if (this.params.preparedPlan?.targetsToUpdate.length) {
|
|
1575
|
-
return [...new Set(this.params.preparedPlan.targetsToUpdate)] as FirmwareUpdateV4Target[];
|
|
1576
|
-
}
|
|
1577
1027
|
const targets = new Set<FirmwareUpdateV4Target>();
|
|
1578
1028
|
Object.keys(this.params.componentArtifacts ?? {}).forEach(target =>
|
|
1579
1029
|
targets.add(target as FirmwareUpdateV4Target)
|
|
1580
1030
|
);
|
|
1031
|
+
if (this.params.resourceBundleArtifacts?.length || this.params.resourceFiles?.length) {
|
|
1032
|
+
targets.add('resource');
|
|
1033
|
+
}
|
|
1581
1034
|
if (this.params.bootloaderBinary) targets.add('boot');
|
|
1582
1035
|
if (this.params.applicationP1Binary) targets.add('app_v1');
|
|
1583
1036
|
if (this.params.applicationP2Binary) targets.add('app_v2');
|
|
@@ -1605,34 +1058,12 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1605
1058
|
return serialNumber || undefined;
|
|
1606
1059
|
}
|
|
1607
1060
|
|
|
1608
|
-
private getProtocolV2PreparedPlanDeviceModel(features?: Features) {
|
|
1609
|
-
const currentDeviceType = this.device.getCurrentDeviceType();
|
|
1610
|
-
const featureDeviceType = features ? getDeviceType(features) : undefined;
|
|
1611
|
-
const deviceType =
|
|
1612
|
-
currentDeviceType === EDeviceType.Pro2 || currentDeviceType === EDeviceType.Neo
|
|
1613
|
-
? currentDeviceType
|
|
1614
|
-
: featureDeviceType;
|
|
1615
|
-
return deviceType === EDeviceType.Pro2 || deviceType === EDeviceType.Neo
|
|
1616
|
-
? String(deviceType)
|
|
1617
|
-
: undefined;
|
|
1618
|
-
}
|
|
1619
|
-
|
|
1620
|
-
private getProtocolV2ConnectionRoute() {
|
|
1621
|
-
const descriptor = this.device.originalDescriptor;
|
|
1622
|
-
return (
|
|
1623
|
-
descriptor?.path?.trim() ||
|
|
1624
|
-
this.device.getConnectId?.()?.trim() ||
|
|
1625
|
-
descriptor?.id?.trim() ||
|
|
1626
|
-
undefined
|
|
1627
|
-
);
|
|
1628
|
-
}
|
|
1629
|
-
|
|
1630
1061
|
private assertProtocolV2DeviceInfoIdentity(deviceInfo: ProtocolV2DeviceInfo) {
|
|
1631
1062
|
assertProtocolV2ReconnectIdentity(
|
|
1632
1063
|
this.protocolV2ExpectedSerialNumber,
|
|
1633
1064
|
this.getProtocolV2SerialNumber(deviceInfo),
|
|
1634
1065
|
this.protocolV2ExpectedPath,
|
|
1635
|
-
this.
|
|
1066
|
+
this.device.originalDescriptor.path
|
|
1636
1067
|
);
|
|
1637
1068
|
}
|
|
1638
1069
|
|
|
@@ -1652,12 +1083,11 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1652
1083
|
private async captureProtocolV2PhysicalIdentity() {
|
|
1653
1084
|
const deviceInfo = await this.requestProtocolV2PhysicalIdentity();
|
|
1654
1085
|
const serialNumber = this.getProtocolV2SerialNumber(deviceInfo);
|
|
1655
|
-
const path = this.
|
|
1086
|
+
const path = this.device.originalDescriptor?.path?.trim() || undefined;
|
|
1656
1087
|
if (this.params?.preparedPlan) {
|
|
1657
1088
|
assertFirmwareUpdatePreparedPlanDeviceIdentity({
|
|
1658
1089
|
preparedPlan: this.params.preparedPlan,
|
|
1659
1090
|
deviceIdentity: serialNumber,
|
|
1660
|
-
deviceModel: this.getProtocolV2PreparedPlanDeviceModel(this.device.features),
|
|
1661
1091
|
});
|
|
1662
1092
|
} else if (this.params?.expectedDeviceId) {
|
|
1663
1093
|
assertProtocolV2ReconnectIdentity(this.params?.expectedDeviceId, serialNumber);
|
|
@@ -1678,19 +1108,12 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1678
1108
|
return this.params.bootloaderBinary ?? null;
|
|
1679
1109
|
}
|
|
1680
1110
|
|
|
1681
|
-
private
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
})
|
|
1111
|
+
private hasExplicitProtocolV2Payload(fwBinaryMap: ProtocolV2TargetBinary[]) {
|
|
1112
|
+
return (
|
|
1113
|
+
!!this.params.resourceFiles?.length ||
|
|
1114
|
+
!!this.params.bootloaderBinary ||
|
|
1115
|
+
fwBinaryMap.length > 0
|
|
1687
1116
|
);
|
|
1688
|
-
return (this.params.targetsToUpdate ?? [])
|
|
1689
|
-
.filter(
|
|
1690
|
-
(target): target is Exclude<FirmwareUpdateV4Target, 'resource' | 'boot_resources'> =>
|
|
1691
|
-
target !== 'resource' && target !== 'boot_resources'
|
|
1692
|
-
)
|
|
1693
|
-
.filter(target => !preparedTargets.has(target));
|
|
1694
1117
|
}
|
|
1695
1118
|
|
|
1696
1119
|
private buildProtocolV2InstallItems({
|
|
@@ -1761,28 +1184,8 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1761
1184
|
`Missing Protocol V2 firmware component url: ${key}/${component.target}`
|
|
1762
1185
|
);
|
|
1763
1186
|
}
|
|
1764
|
-
const expectedFingerprint = normalizeProtocolV2Hex(component.fingerprint);
|
|
1765
|
-
if (
|
|
1766
|
-
!Number.isSafeInteger(component.expectedSize) ||
|
|
1767
|
-
Number(component.expectedSize) <= 0 ||
|
|
1768
|
-
!expectedFingerprint ||
|
|
1769
|
-
!/^[0-9a-f]{64}$/u.test(expectedFingerprint)
|
|
1770
|
-
) {
|
|
1771
|
-
throw ERRORS.TypedError(
|
|
1772
|
-
HardwareErrorCode.RuntimeError,
|
|
1773
|
-
`Protocol V2 firmware component integrity metadata is invalid: ${key}/${component.target}`,
|
|
1774
|
-
{ firmwareUpdateCode: 'FirmwarePlanInvalid' }
|
|
1775
|
-
);
|
|
1776
|
-
}
|
|
1777
1187
|
|
|
1778
1188
|
const { binary } = await getSysResourceBinary(component.url);
|
|
1779
|
-
if (binary.byteLength !== component.expectedSize) {
|
|
1780
|
-
throw ERRORS.TypedError(
|
|
1781
|
-
HardwareErrorCode.RuntimeError,
|
|
1782
|
-
`Protocol V2 firmware size mismatch: ${key}/${component.target}`,
|
|
1783
|
-
{ firmwareUpdateCode: 'FirmwareArtifactReceiptMismatch' }
|
|
1784
|
-
);
|
|
1785
|
-
}
|
|
1786
1189
|
if (!isProtocolV2FirmwareFingerprintValid(binary, component.fingerprint)) {
|
|
1787
1190
|
throw ERRORS.TypedError(
|
|
1788
1191
|
HardwareErrorCode.RuntimeError,
|
|
@@ -1805,11 +1208,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1805
1208
|
};
|
|
1806
1209
|
}
|
|
1807
1210
|
|
|
1808
|
-
private async prepareRemoteProtocolV2Binaries(
|
|
1809
|
-
firmwareType: EFirmwareType,
|
|
1810
|
-
features: Features,
|
|
1811
|
-
explicitInstallItems: ProtocolV2InstallItem[] = []
|
|
1812
|
-
) {
|
|
1211
|
+
private async prepareRemoteProtocolV2Binaries(firmwareType: EFirmwareType, features: Features) {
|
|
1813
1212
|
const release = DataManager.getFirmwareLatestRelease(features, firmwareType);
|
|
1814
1213
|
|
|
1815
1214
|
let bootloaderBinary: ArrayBuffer | null = null;
|
|
@@ -1817,15 +1216,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1817
1216
|
const installItems: ProtocolV2InstallItem[] = [];
|
|
1818
1217
|
|
|
1819
1218
|
if (!release) {
|
|
1820
|
-
const missingFirmwareTargets = this.getMissingProtocolV2FirmwareTargets(explicitInstallItems);
|
|
1821
|
-
if (missingFirmwareTargets.length > 0) {
|
|
1822
|
-
throw ERRORS.TypedError(
|
|
1823
|
-
HardwareErrorCode.RuntimeError,
|
|
1824
|
-
`Protocol V2 firmware release is unavailable for requested targets: ${missingFirmwareTargets.join(
|
|
1825
|
-
', '
|
|
1826
|
-
)}`
|
|
1827
|
-
);
|
|
1828
|
-
}
|
|
1829
1219
|
return {
|
|
1830
1220
|
bootloaderBinary,
|
|
1831
1221
|
fwBinaryMap,
|
|
@@ -1835,10 +1225,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1835
1225
|
|
|
1836
1226
|
const entries = this.getRemoteComponentEntries(release);
|
|
1837
1227
|
const targetsToUpdate = new Set(this.params.targetsToUpdate ?? []);
|
|
1838
|
-
const explicitInstallItemByTargetId = new Map(
|
|
1839
|
-
explicitInstallItems.map(item => [item.targetId, item] as const)
|
|
1840
|
-
);
|
|
1841
|
-
const preparedTargets = new Set<FirmwareUpdateV4Target>();
|
|
1842
1228
|
|
|
1843
1229
|
for (const [key, component] of entries) {
|
|
1844
1230
|
const targetName = component.target?.toUpperCase();
|
|
@@ -1847,34 +1233,27 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1847
1233
|
? PROTOCOL_V2_UPDATE_TARGET_BY_TARGET_ID.get(target.targetId)
|
|
1848
1234
|
: undefined;
|
|
1849
1235
|
if (updateTarget && targetsToUpdate.has(updateTarget)) {
|
|
1850
|
-
const
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1236
|
+
const remoteBinary = await this.downloadRemoteProtocolV2Component(key, component);
|
|
1237
|
+
if (remoteBinary.kind === 'bootloader') {
|
|
1238
|
+
bootloaderBinary = remoteBinary.binary;
|
|
1239
|
+
installItems.push({
|
|
1240
|
+
fileName: remoteBinary.fileName,
|
|
1241
|
+
binary: remoteBinary.binary,
|
|
1242
|
+
targetId: remoteBinary.targetId,
|
|
1243
|
+
kind: remoteBinary.kind,
|
|
1244
|
+
});
|
|
1855
1245
|
} else {
|
|
1856
1246
|
const binaryEntry = {
|
|
1857
|
-
fileName:
|
|
1858
|
-
binary:
|
|
1859
|
-
targetId:
|
|
1247
|
+
fileName: remoteBinary.fileName,
|
|
1248
|
+
binary: remoteBinary.binary,
|
|
1249
|
+
targetId: remoteBinary.targetId,
|
|
1860
1250
|
};
|
|
1861
1251
|
fwBinaryMap.push(binaryEntry);
|
|
1252
|
+
installItems.push({ ...binaryEntry, kind: remoteBinary.kind });
|
|
1862
1253
|
}
|
|
1863
|
-
installItems.push({ ...installItem });
|
|
1864
|
-
preparedTargets.add(updateTarget);
|
|
1865
1254
|
}
|
|
1866
1255
|
}
|
|
1867
1256
|
|
|
1868
|
-
const missingTarget = Array.from(targetsToUpdate).find(
|
|
1869
|
-
target => target !== 'resource' && !preparedTargets.has(target)
|
|
1870
|
-
);
|
|
1871
|
-
if (missingTarget) {
|
|
1872
|
-
throw ERRORS.TypedError(
|
|
1873
|
-
HardwareErrorCode.RuntimeError,
|
|
1874
|
-
`Protocol V2 release does not contain requested target ${missingTarget}`
|
|
1875
|
-
);
|
|
1876
|
-
}
|
|
1877
|
-
|
|
1878
1257
|
return {
|
|
1879
1258
|
bootloaderBinary,
|
|
1880
1259
|
fwBinaryMap,
|
|
@@ -1882,13 +1261,211 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1882
1261
|
};
|
|
1883
1262
|
}
|
|
1884
1263
|
|
|
1264
|
+
private getProtocolV2DeviceType(): EDeviceType.Pro2 | EDeviceType.Neo {
|
|
1265
|
+
const deviceType = this.device.getCurrentDeviceType();
|
|
1266
|
+
if (deviceType === EDeviceType.Pro2 || deviceType === EDeviceType.Neo) return deviceType;
|
|
1267
|
+
throw new Error(`Unsupported Protocol V2 device type: ${deviceType}`);
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1270
|
+
private async prepareProtocolV2BootResources(): Promise<
|
|
1271
|
+
ProtocolV2ResourceBundleBinary[] | undefined
|
|
1272
|
+
> {
|
|
1273
|
+
const wantsStableResources = !!this.params.targetsToUpdate?.includes('resource');
|
|
1274
|
+
const wantsBootResources = !!this.params.targetsToUpdate?.includes('boot_resources');
|
|
1275
|
+
if (!wantsStableResources && !wantsBootResources) {
|
|
1276
|
+
return undefined;
|
|
1277
|
+
}
|
|
1278
|
+
if (this.params.resourceFiles?.length) {
|
|
1279
|
+
return undefined;
|
|
1280
|
+
}
|
|
1281
|
+
const resource = DataManager.getProtocolV2BootResources(this.getProtocolV2DeviceType());
|
|
1282
|
+
if (!resource) {
|
|
1283
|
+
if (wantsBootResources) {
|
|
1284
|
+
throw new Error('Missing Protocol V2 boot resources configuration');
|
|
1285
|
+
}
|
|
1286
|
+
Log.debug('[FirmwareUpdateV4] no boot resources configured; continue with stable resources');
|
|
1287
|
+
return undefined;
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1290
|
+
const files: ProtocolV2ResourceBundleBinary[] = [];
|
|
1291
|
+
for (const file of resource.files) {
|
|
1292
|
+
const isCurrent =
|
|
1293
|
+
!this.params.forcedUpdateRes && (await this.isProtocolV2BootResourceCurrent(file));
|
|
1294
|
+
if (isCurrent) {
|
|
1295
|
+
Log.log(`[FirmwareUpdateV4] boot resource unchanged, skipping ${file.devicePath}`);
|
|
1296
|
+
} else {
|
|
1297
|
+
Log.log(`[FirmwareUpdateV4] downloading boot resource ${file.devicePath}`);
|
|
1298
|
+
const { binary } = await getSysResourceBinary(file.url);
|
|
1299
|
+
if (!isProtocolV2ResourceFileValid(binary, file)) {
|
|
1300
|
+
throw new Error(`Boot resource file verification failed: ${file.devicePath}`);
|
|
1301
|
+
}
|
|
1302
|
+
files.push({
|
|
1303
|
+
name: file.name ?? file.devicePath.split('/').pop() ?? file.devicePath,
|
|
1304
|
+
binary,
|
|
1305
|
+
devicePath: file.devicePath,
|
|
1306
|
+
});
|
|
1307
|
+
}
|
|
1308
|
+
}
|
|
1309
|
+
return files;
|
|
1310
|
+
}
|
|
1311
|
+
|
|
1312
|
+
private async isProtocolV2BootResourceCurrent(file: IProtocolV2ResourceFile) {
|
|
1313
|
+
try {
|
|
1314
|
+
const commands = this.device.getCommands();
|
|
1315
|
+
const pathInfo = await commands.typedCall(
|
|
1316
|
+
'FilesystemPathInfoQuery',
|
|
1317
|
+
'FilesystemPathInfo',
|
|
1318
|
+
{ path: file.devicePath },
|
|
1319
|
+
{ timeoutMs: PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT }
|
|
1320
|
+
);
|
|
1321
|
+
const size = toProtocolV2FiniteNumber(pathInfo.message?.size);
|
|
1322
|
+
if (
|
|
1323
|
+
!pathInfo.message?.exist ||
|
|
1324
|
+
pathInfo.message?.directory ||
|
|
1325
|
+
!Number.isSafeInteger(size) ||
|
|
1326
|
+
size !== file.size
|
|
1327
|
+
) {
|
|
1328
|
+
return false;
|
|
1329
|
+
}
|
|
1330
|
+
|
|
1331
|
+
const digest = sha256.create();
|
|
1332
|
+
const chunkSize = this.getProtocolV2FirmwareChunkSize('write');
|
|
1333
|
+
let offset = 0;
|
|
1334
|
+
while (offset < file.size) {
|
|
1335
|
+
const response = await commands.typedCall(
|
|
1336
|
+
'FilesystemFileRead',
|
|
1337
|
+
'FilesystemFile',
|
|
1338
|
+
{
|
|
1339
|
+
file: { path: file.devicePath, offset, total_size: 0 },
|
|
1340
|
+
chunk_len: Math.min(chunkSize, file.size - offset),
|
|
1341
|
+
},
|
|
1342
|
+
{ timeoutMs: PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT }
|
|
1343
|
+
);
|
|
1344
|
+
const data = toProtocolV2Bytes(response.message?.data);
|
|
1345
|
+
if (data.byteLength === 0) return false;
|
|
1346
|
+
const consumed = data.subarray(0, Math.min(data.byteLength, file.size - offset));
|
|
1347
|
+
digest.update(consumed);
|
|
1348
|
+
offset += consumed.byteLength;
|
|
1349
|
+
}
|
|
1350
|
+
return bytesToHex(digest.digest()) === normalizeProtocolV2Hex(file.fileHash);
|
|
1351
|
+
} catch (error) {
|
|
1352
|
+
Log.debug(
|
|
1353
|
+
`[FirmwareUpdateV4] unable to compare boot resource ${file.devicePath}; scheduling rewrite`,
|
|
1354
|
+
error
|
|
1355
|
+
);
|
|
1356
|
+
return false;
|
|
1357
|
+
}
|
|
1358
|
+
}
|
|
1359
|
+
|
|
1360
|
+
private mergeProtocolV2ResourceBundles(
|
|
1361
|
+
...groups: Array<ProtocolV2ResourceBundleBinary[] | undefined>
|
|
1362
|
+
): ProtocolV2ResourceBundleBinary[] | undefined {
|
|
1363
|
+
const merged = groups.flatMap(group => group ?? []);
|
|
1364
|
+
if (!merged.length) return undefined;
|
|
1365
|
+
const seenPaths = new Set<string>();
|
|
1366
|
+
for (const bundle of merged) {
|
|
1367
|
+
if (seenPaths.has(bundle.devicePath)) {
|
|
1368
|
+
throw new Error(`Duplicate Protocol V2 resource devicePath: ${bundle.devicePath}`);
|
|
1369
|
+
}
|
|
1370
|
+
seenPaths.add(bundle.devicePath);
|
|
1371
|
+
}
|
|
1372
|
+
return merged;
|
|
1373
|
+
}
|
|
1374
|
+
|
|
1375
|
+
private prepareExplicitProtocolV2ResourceFiles(): ProtocolV2ResourceBundleBinary[] | undefined {
|
|
1376
|
+
const files = this.params.resourceFiles ?? [];
|
|
1377
|
+
if (!files?.length) return undefined;
|
|
1378
|
+
|
|
1379
|
+
const prepared = files.map((file, index) => {
|
|
1380
|
+
const devicePath = validateProtocolV2FilesystemPath(
|
|
1381
|
+
file.devicePath,
|
|
1382
|
+
`resourceFiles[${index}].devicePath`
|
|
1383
|
+
);
|
|
1384
|
+
const descriptor = file as typeof file &
|
|
1385
|
+
Pick<Partial<IProtocolV2ResourceFile>, 'size' | 'fileHash'>;
|
|
1386
|
+
if (descriptor.size !== undefined && descriptor.size !== file.binary.byteLength) {
|
|
1387
|
+
throw new Error(`resourceFiles[${index}] size mismatch`);
|
|
1388
|
+
}
|
|
1389
|
+
if (
|
|
1390
|
+
descriptor.fileHash &&
|
|
1391
|
+
!isProtocolV2ResourceFileValid(file.binary, {
|
|
1392
|
+
size: file.binary.byteLength,
|
|
1393
|
+
fileHash: descriptor.fileHash,
|
|
1394
|
+
})
|
|
1395
|
+
) {
|
|
1396
|
+
throw new Error(`resourceFiles[${index}] SHA-256 mismatch`);
|
|
1397
|
+
}
|
|
1398
|
+
return {
|
|
1399
|
+
name: devicePath.split('/').pop() ?? devicePath,
|
|
1400
|
+
binary: file.binary,
|
|
1401
|
+
devicePath,
|
|
1402
|
+
};
|
|
1403
|
+
});
|
|
1404
|
+
if (new Set(prepared.map(file => file.devicePath)).size !== prepared.length) {
|
|
1405
|
+
throw new Error('resourceFiles contain duplicate devicePath values');
|
|
1406
|
+
}
|
|
1407
|
+
return prepared;
|
|
1408
|
+
}
|
|
1409
|
+
|
|
1410
|
+
private async prepareProtocolV2ResourceBundles(): Promise<
|
|
1411
|
+
ProtocolV2ResourceBundleBinary[] | undefined
|
|
1412
|
+
> {
|
|
1413
|
+
if (!this.params.targetsToUpdate?.includes('resource')) {
|
|
1414
|
+
return undefined;
|
|
1415
|
+
}
|
|
1416
|
+
|
|
1417
|
+
const resources = DataManager.getProtocolV2Resources(this.getProtocolV2DeviceType());
|
|
1418
|
+
if (!resources?.length) {
|
|
1419
|
+
throw new Error('Missing Pro2 stable resource configuration');
|
|
1420
|
+
}
|
|
1421
|
+
|
|
1422
|
+
const inventory = this.params.forcedUpdateRes
|
|
1423
|
+
? undefined
|
|
1424
|
+
: await readProtocolV2ResourceInventory({
|
|
1425
|
+
commands: this.device.getCommands(),
|
|
1426
|
+
resources,
|
|
1427
|
+
chunkSize: this.getProtocolV2FirmwareChunkSize('write'),
|
|
1428
|
+
timeoutMs: PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT,
|
|
1429
|
+
});
|
|
1430
|
+
const plan = buildProtocolV2ResourceUpdatePlan({
|
|
1431
|
+
resources,
|
|
1432
|
+
inventory,
|
|
1433
|
+
mode: 'bootloader-recovery',
|
|
1434
|
+
forced: this.params.forcedUpdateRes,
|
|
1435
|
+
});
|
|
1436
|
+
Log.log(
|
|
1437
|
+
`[FirmwareUpdateV4] Protocol V2 resource plan mode=bootloader-recovery status=${plan.status} count=${plan.resources.length}`
|
|
1438
|
+
);
|
|
1439
|
+
|
|
1440
|
+
const bundles: ProtocolV2ResourceBundleBinary[] = [];
|
|
1441
|
+
for (const resource of plan.resources) {
|
|
1442
|
+
bundles.push(await this.downloadProtocolV2Resource(resource));
|
|
1443
|
+
}
|
|
1444
|
+
return bundles;
|
|
1445
|
+
}
|
|
1446
|
+
|
|
1447
|
+
private async downloadProtocolV2Resource(
|
|
1448
|
+
resource: IProtocolV2Resource
|
|
1449
|
+
): Promise<ProtocolV2ResourceBundleBinary> {
|
|
1450
|
+
Log.log(`[FirmwareUpdateV4] downloading Pro2 resource ${resource.type}`);
|
|
1451
|
+
const { binary } = await getSysResourceBinary(resource.url);
|
|
1452
|
+
if (!isProtocolV2ResourceFileValid(binary, resource)) {
|
|
1453
|
+
throw new Error(`Pro2 resource file verification failed: ${resource.type}`);
|
|
1454
|
+
}
|
|
1455
|
+
return {
|
|
1456
|
+
name: `${resource.type}.okpkg`,
|
|
1457
|
+
binary,
|
|
1458
|
+
devicePath: PROTOCOL_V2_RESOURCE_DEVICE_PATHS[resource.type],
|
|
1459
|
+
};
|
|
1460
|
+
}
|
|
1461
|
+
|
|
1885
1462
|
private getProtocolV2ResourceFilePath(path: string) {
|
|
1886
1463
|
if (path.startsWith('vol')) return path;
|
|
1887
1464
|
if (path.startsWith('/')) return `vol0:${path}`;
|
|
1888
1465
|
return `vol0:/${path}`;
|
|
1889
1466
|
}
|
|
1890
1467
|
|
|
1891
|
-
private async readProtocolV2DeviceFileHeader(path: string
|
|
1468
|
+
private async readProtocolV2DeviceFileHeader(path: string) {
|
|
1892
1469
|
const typedCall = this.device.getCommands().typedCall.bind(this.device.getCommands());
|
|
1893
1470
|
const filePath = this.getProtocolV2ResourceFilePath(path);
|
|
1894
1471
|
const pathInfoRes = await typedCall('FilesystemPathInfoQuery', 'FilesystemPathInfo', {
|
|
@@ -1899,8 +1476,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1899
1476
|
!pathInfoRes.message?.exist ||
|
|
1900
1477
|
pathInfoRes.message?.directory ||
|
|
1901
1478
|
fileSize === undefined ||
|
|
1902
|
-
fileSize < PROTOCOL_V2_OKPP_HEADER_SIZE
|
|
1903
|
-
(expectedSize !== undefined && fileSize !== expectedSize)
|
|
1479
|
+
fileSize < PROTOCOL_V2_OKPP_HEADER_SIZE
|
|
1904
1480
|
) {
|
|
1905
1481
|
return null;
|
|
1906
1482
|
}
|
|
@@ -1934,31 +1510,32 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1934
1510
|
return parseProtocolV2OkppHeader(headerBytes);
|
|
1935
1511
|
}
|
|
1936
1512
|
|
|
1937
|
-
/** Compare
|
|
1513
|
+
/** Compare a prepared okpkg header when its manifest supplies version or hash metadata. */
|
|
1938
1514
|
private async isProtocolV2ResourceBundleUpToDate(
|
|
1939
1515
|
bundle: Pick<
|
|
1940
1516
|
ProtocolV2ResourceBundleSource,
|
|
1941
|
-
'name' | '
|
|
1517
|
+
'name' | 'devicePath' | 'version' | 'payloadHash' | 'headerHash'
|
|
1942
1518
|
>
|
|
1943
1519
|
): Promise<boolean> {
|
|
1944
1520
|
if (this.params?.forcedUpdateRes) return false;
|
|
1945
|
-
if (!bundle.
|
|
1521
|
+
if (!bundle.version && !bundle.payloadHash) return false;
|
|
1946
1522
|
|
|
1947
1523
|
try {
|
|
1948
|
-
const header = await this.readProtocolV2DeviceFileHeader(
|
|
1949
|
-
bundle.devicePath,
|
|
1950
|
-
bundle.source.size
|
|
1951
|
-
);
|
|
1524
|
+
const header = await this.readProtocolV2DeviceFileHeader(bundle.devicePath);
|
|
1952
1525
|
if (!header) return false;
|
|
1953
1526
|
|
|
1954
1527
|
if (bundle.version) {
|
|
1955
1528
|
const cmp = compareProtocolV2Versions(header.version, bundle.version);
|
|
1956
1529
|
if (cmp === undefined || cmp !== 0) return false;
|
|
1957
1530
|
}
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1531
|
+
if (bundle.payloadHash) {
|
|
1532
|
+
const expected = normalizeProtocolV2Hex(bundle.payloadHash);
|
|
1533
|
+
if (expected && header.payloadHash !== expected) return false;
|
|
1534
|
+
}
|
|
1535
|
+
if (bundle.headerHash) {
|
|
1536
|
+
const expected = normalizeProtocolV2Hex(bundle.headerHash);
|
|
1537
|
+
if (expected && header.headerHash !== expected) return false;
|
|
1538
|
+
}
|
|
1962
1539
|
return true;
|
|
1963
1540
|
} catch (error) {
|
|
1964
1541
|
Log.log(`[FirmwareUpdateV4] RESC bundle ${bundle.name} header check failed: `, error);
|
|
@@ -2114,6 +1691,13 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2114
1691
|
resourceSources: ProtocolV2ResourceBundleSource[];
|
|
2115
1692
|
}): ProtocolV2ExecutionPhase[] {
|
|
2116
1693
|
const phases: ProtocolV2ExecutionPhase[] = [];
|
|
1694
|
+
if (resourceSources.length > 0) {
|
|
1695
|
+
phases.push({
|
|
1696
|
+
kind: 'resource-sync',
|
|
1697
|
+
installSources: [],
|
|
1698
|
+
resourceSources,
|
|
1699
|
+
});
|
|
1700
|
+
}
|
|
2117
1701
|
const bootloaderSources = installSources.filter(source => source.kind === 'bootloader');
|
|
2118
1702
|
if (bootloaderSources.length > 0) {
|
|
2119
1703
|
phases.push(
|
|
@@ -2129,13 +1713,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2129
1713
|
}
|
|
2130
1714
|
);
|
|
2131
1715
|
}
|
|
2132
|
-
if (resourceSources.length > 0) {
|
|
2133
|
-
phases.push({
|
|
2134
|
-
kind: 'resource-sync',
|
|
2135
|
-
installSources: [],
|
|
2136
|
-
resourceSources,
|
|
2137
|
-
});
|
|
2138
|
-
}
|
|
2139
1716
|
const componentSources = installSources.filter(source => source.kind !== 'bootloader');
|
|
2140
1717
|
if (componentSources.length > 0) {
|
|
2141
1718
|
phases.push({
|
|
@@ -2202,10 +1779,12 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2202
1779
|
fwBinaryMap,
|
|
2203
1780
|
bootloaderBinary,
|
|
2204
1781
|
installItems,
|
|
1782
|
+
resourceBundles,
|
|
2205
1783
|
}: {
|
|
2206
1784
|
fwBinaryMap?: ProtocolV2TargetBinary[];
|
|
2207
1785
|
bootloaderBinary?: ArrayBuffer | null;
|
|
2208
1786
|
installItems?: ProtocolV2InstallItem[];
|
|
1787
|
+
resourceBundles?: ProtocolV2ResourceBundleBinary[];
|
|
2209
1788
|
}) {
|
|
2210
1789
|
const memoryInstallItems =
|
|
2211
1790
|
installItems ??
|
|
@@ -2222,9 +1801,16 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2222
1801
|
kind: item.kind,
|
|
2223
1802
|
}))
|
|
2224
1803
|
);
|
|
1804
|
+
const resourceSources = await Promise.all(
|
|
1805
|
+
(resourceBundles ?? []).map(async bundle => ({
|
|
1806
|
+
name: bundle.name,
|
|
1807
|
+
source: await this.openProtocolV2MemorySource(bundle.binary),
|
|
1808
|
+
devicePath: bundle.devicePath,
|
|
1809
|
+
}))
|
|
1810
|
+
);
|
|
2225
1811
|
return await this.executeProtocolV2Phases({
|
|
2226
1812
|
installSources,
|
|
2227
|
-
resourceSources
|
|
1813
|
+
resourceSources,
|
|
2228
1814
|
});
|
|
2229
1815
|
} finally {
|
|
2230
1816
|
await this.closeProtocolV2PreparedSources();
|
|
@@ -2249,16 +1835,13 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2249
1835
|
this.postTipMessage(FirmwareUpdateTipMessage.StartTransferData);
|
|
2250
1836
|
let processedSize = 0;
|
|
2251
1837
|
for (const resource of resourcesToSync) {
|
|
2252
|
-
// The bootloader keeps its live resource package mounted. FatFs rejects
|
|
2253
|
-
// replacing an open file, so early boot promotes this staging file before mounting it.
|
|
2254
|
-
const writePath = resolveProtocolV2ResourceWritePath(resource.devicePath);
|
|
2255
1838
|
processedSize = await this.protocolV2SourceUpdateProcess({
|
|
2256
1839
|
source: resource.source,
|
|
2257
|
-
filePath:
|
|
1840
|
+
filePath: resource.devicePath,
|
|
2258
1841
|
processedSize,
|
|
2259
1842
|
totalSize,
|
|
2260
1843
|
});
|
|
2261
|
-
await this.verifyProtocolV2StagedFile(
|
|
1844
|
+
await this.verifyProtocolV2StagedFile(resource.devicePath, resource.source.size);
|
|
2262
1845
|
}
|
|
2263
1846
|
|
|
2264
1847
|
const stagedInstallTargets: Array<{ targetId: number; path: string }> = [];
|