@onekeyfe/hd-core 1.2.0-alpha.87 → 1.2.0-alpha.89
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 +24 -2
- package/__tests__/firmware-memory-host.test.ts +6 -2
- package/__tests__/firmware-update/firmware-update-plan.test.ts +47 -0
- package/__tests__/protocol-v2.test.ts +492 -4
- package/dist/api/FirmwareUpdateV4.d.ts +2 -0
- package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
- package/dist/api/firmware/FirmwareMemoryHost.d.ts +0 -5
- package/dist/api/firmware/FirmwareMemoryHost.d.ts.map +1 -1
- package/dist/api/firmware/FirmwareUpdatePlan.d.ts +15 -0
- package/dist/api/firmware/FirmwareUpdatePlan.d.ts.map +1 -1
- package/dist/index.d.ts +18 -6
- package/dist/index.js +422 -134
- package/dist/types/api/firmwareUpdate.d.ts +13 -2
- package/dist/types/api/firmwareUpdate.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/api/FirmwareUpdateV4.ts +516 -28
- package/src/api/firmware/FirmwareMemoryHost.ts +9 -41
- package/src/api/firmware/FirmwareUpdatePlan.ts +49 -4
- package/src/types/api/firmwareUpdate.ts +20 -1
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { EDeviceType, ERRORS, HardwareError, HardwareErrorCode, wait } from '@onekeyfe/hd-shared';
|
|
2
|
+
import JSZip from 'jszip';
|
|
2
3
|
import {
|
|
3
4
|
DeviceRebootType,
|
|
4
5
|
PROTOCOL_V2_BLE_FILE_CHUNK_SIZE,
|
|
@@ -42,12 +43,19 @@ import {
|
|
|
42
43
|
readFirmwareByteSourceFully,
|
|
43
44
|
writeFirmwareByteSource,
|
|
44
45
|
} from './firmware/FirmwareArtifactSource';
|
|
45
|
-
import {
|
|
46
|
+
import {
|
|
47
|
+
registerFirmwareUpdateHostBinding,
|
|
48
|
+
resolveFirmwareUpdateHostBinding,
|
|
49
|
+
unregisterFirmwareUpdateHostBinding,
|
|
50
|
+
} from './firmware/FirmwareHostBinding';
|
|
46
51
|
import {
|
|
47
52
|
assertFirmwareUpdatePreparedPlanBinding,
|
|
48
53
|
assertFirmwareUpdatePreparedPlanDeviceIdentity,
|
|
54
|
+
prepareFirmwareUpdatePlan,
|
|
49
55
|
validateFirmwareUpdatePreparedPlan,
|
|
50
56
|
} from './firmware/FirmwareUpdatePreparedPlan';
|
|
57
|
+
import { prepareFirmwareUpdateV4MemoryHost } from './firmware/FirmwareMemoryHost';
|
|
58
|
+
import { buildProtocolV2LocalFirmwareUpdatePlan } from './firmware/FirmwareUpdatePlan';
|
|
51
59
|
|
|
52
60
|
import type {
|
|
53
61
|
FirmwareArtifactReference,
|
|
@@ -64,6 +72,11 @@ import type {
|
|
|
64
72
|
IVersionArray,
|
|
65
73
|
} from '../types';
|
|
66
74
|
import type { FirmwareByteSource } from './firmware/FirmwareArtifactSource';
|
|
75
|
+
import type {
|
|
76
|
+
FirmwareMemoryArtifact,
|
|
77
|
+
FirmwareMemoryArtifactEntry,
|
|
78
|
+
FirmwareUpdateV4MemoryHost,
|
|
79
|
+
} from './firmware/FirmwareMemoryHost';
|
|
67
80
|
|
|
68
81
|
const Log = getLogger(LoggerNames.Method);
|
|
69
82
|
|
|
@@ -101,6 +114,28 @@ const PROTOCOL_V2_RESOURCE_TOTAL_MAX_BYTES = 256 * 1024 * 1024;
|
|
|
101
114
|
|
|
102
115
|
const PROTOCOL_V2_NEO_UNSUPPORTED_TARGETS = new Set<FirmwareUpdateV4Target>(['se03', 'se04']);
|
|
103
116
|
|
|
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
|
+
|
|
104
139
|
export function assertProtocolV2FirmwareTargetsSupported(
|
|
105
140
|
deviceType: EDeviceType | string | undefined,
|
|
106
141
|
params: FirmwareUpdateV4Params
|
|
@@ -178,6 +213,22 @@ type ProtocolV2ResourceBundleSource = {
|
|
|
178
213
|
headerHash?: string;
|
|
179
214
|
};
|
|
180
215
|
|
|
216
|
+
type ProtocolV2ResourceBundleBinary = Omit<ProtocolV2ResourceBundleSource, 'source'> & {
|
|
217
|
+
binary: ArrayBuffer;
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
type ProtocolV2LocalResourceArchive = {
|
|
221
|
+
binary: ArrayBuffer;
|
|
222
|
+
materializedEntries: FirmwareMemoryArtifactEntry[];
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
type JSZipSizedEntry = JSZip.JSZipObject & {
|
|
226
|
+
_data?: {
|
|
227
|
+
compressedSize?: unknown;
|
|
228
|
+
uncompressedSize?: unknown;
|
|
229
|
+
};
|
|
230
|
+
};
|
|
231
|
+
|
|
181
232
|
type ProtocolV2ExecutionPhaseKind =
|
|
182
233
|
| 'resource-sync'
|
|
183
234
|
| 'bootloader-install'
|
|
@@ -259,7 +310,10 @@ const resolveProtocolV2ResourceWritePath = (devicePath: string) => {
|
|
|
259
310
|
: devicePath;
|
|
260
311
|
};
|
|
261
312
|
|
|
262
|
-
const PROTOCOL_V2_UPDATE_TARGET_BY_TARGET_ID = new Map<
|
|
313
|
+
const PROTOCOL_V2_UPDATE_TARGET_BY_TARGET_ID = new Map<
|
|
314
|
+
number,
|
|
315
|
+
Exclude<FirmwareUpdateV4Target, 'boot_resources'>
|
|
316
|
+
>([
|
|
263
317
|
[ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_BOOTLOADER, 'boot'],
|
|
264
318
|
[ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_APPLICATION_P1, 'app_v1'],
|
|
265
319
|
[ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_APPLICATION_P2, 'app_v2'],
|
|
@@ -270,6 +324,19 @@ const PROTOCOL_V2_UPDATE_TARGET_BY_TARGET_ID = new Map<number, FirmwareUpdateV4T
|
|
|
270
324
|
[ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_SE04, 'se04'],
|
|
271
325
|
]);
|
|
272
326
|
|
|
327
|
+
const PROTOCOL_V2_INSTALL_TARGET_BY_UPDATE_TARGET = new Map<
|
|
328
|
+
Exclude<FirmwareUpdateV4Target, 'resource'>,
|
|
329
|
+
ProtocolV2RemoteComponentTarget
|
|
330
|
+
>(
|
|
331
|
+
Object.values(PROTOCOL_V2_REMOTE_COMPONENT_TARGETS).map(target => [
|
|
332
|
+
PROTOCOL_V2_UPDATE_TARGET_BY_TARGET_ID.get(target.targetId) as Exclude<
|
|
333
|
+
FirmwareUpdateV4Target,
|
|
334
|
+
'resource'
|
|
335
|
+
>,
|
|
336
|
+
target,
|
|
337
|
+
])
|
|
338
|
+
);
|
|
339
|
+
|
|
273
340
|
const PROTOCOL_V2_ROMLOADER_UNSUPPORTED_MESSAGE =
|
|
274
341
|
'FW_MGMT_TARGET_ROMLOADER is not accepted by the current Pro2 bootloader update request. Flash romloader with the loader-specific flow instead of firmwareUpdateV4.';
|
|
275
342
|
|
|
@@ -541,6 +608,9 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
541
608
|
{ name: 'se02Binary', type: 'buffer' },
|
|
542
609
|
{ name: 'se03Binary', type: 'buffer' },
|
|
543
610
|
{ name: 'se04Binary', type: 'buffer' },
|
|
611
|
+
{ name: 'resourceArchiveBinary', type: 'buffer' },
|
|
612
|
+
{ name: 'resourceFiles', type: 'array', allowEmpty: true },
|
|
613
|
+
{ name: 'resourceBundleArtifacts', type: 'array', allowEmpty: true },
|
|
544
614
|
{ name: 'firmwareType', type: 'string' },
|
|
545
615
|
{ name: 'targetsToUpdate', type: 'array', allowEmpty: true },
|
|
546
616
|
{ name: 'platform', type: 'string' },
|
|
@@ -555,9 +625,33 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
555
625
|
'Protocol V2 expected device identity is invalid'
|
|
556
626
|
);
|
|
557
627
|
}
|
|
628
|
+
if (payload.resourceFiles?.length || payload.resourceBundleArtifacts?.length) {
|
|
629
|
+
throw ERRORS.TypedError(
|
|
630
|
+
HardwareErrorCode.CallMethodInvalidParameter,
|
|
631
|
+
'Protocol V2 resourceFiles and resourceBundleArtifacts are deprecated; provide a complete signed resource ZIP through resourceArchiveBinary or preparedPlan'
|
|
632
|
+
);
|
|
633
|
+
}
|
|
558
634
|
const preparedPlan = payload.preparedPlan
|
|
559
635
|
? validateFirmwareUpdatePreparedPlan(payload.preparedPlan)
|
|
560
636
|
: undefined;
|
|
637
|
+
const hasLocalArtifacts = [
|
|
638
|
+
payload.bootloaderBinary,
|
|
639
|
+
payload.romloaderBinary,
|
|
640
|
+
payload.applicationP1Binary,
|
|
641
|
+
payload.applicationP2Binary,
|
|
642
|
+
payload.coprocessorBinary,
|
|
643
|
+
payload.se01Binary,
|
|
644
|
+
payload.se02Binary,
|
|
645
|
+
payload.se03Binary,
|
|
646
|
+
payload.se04Binary,
|
|
647
|
+
payload.resourceArchiveBinary,
|
|
648
|
+
].some(Boolean);
|
|
649
|
+
if (preparedPlan && hasLocalArtifacts) {
|
|
650
|
+
throw ERRORS.TypedError(
|
|
651
|
+
HardwareErrorCode.CallMethodInvalidParameter,
|
|
652
|
+
'Prepared firmware plans cannot be combined with local firmware or resource binaries'
|
|
653
|
+
);
|
|
654
|
+
}
|
|
561
655
|
const hostBinding =
|
|
562
656
|
payload.hostBindingGeneration !== undefined
|
|
563
657
|
? resolveFirmwareUpdateHostBinding(
|
|
@@ -566,29 +660,53 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
566
660
|
)
|
|
567
661
|
: undefined;
|
|
568
662
|
if (preparedPlan) {
|
|
569
|
-
const componentArtifacts = (payload.componentArtifacts ?? {}) as NonNullable<
|
|
570
|
-
FirmwareUpdateV4Params['componentArtifacts']
|
|
571
|
-
>;
|
|
572
663
|
assertFirmwareUpdatePreparedPlanBinding({
|
|
573
664
|
preparedPlan,
|
|
574
665
|
executor: 'v4',
|
|
575
666
|
platform: payload.platform,
|
|
576
|
-
scopeTargets: [
|
|
577
|
-
bindings: [
|
|
578
|
-
...Object.entries(componentArtifacts).flatMap(([target, artifact]) =>
|
|
579
|
-
artifact
|
|
580
|
-
? [
|
|
581
|
-
{
|
|
582
|
-
target: target as Exclude<FirmwareUpdateV4Target, 'resource'>,
|
|
583
|
-
artifact,
|
|
584
|
-
},
|
|
585
|
-
]
|
|
586
|
-
: []
|
|
587
|
-
),
|
|
588
|
-
],
|
|
667
|
+
scopeTargets: [],
|
|
668
|
+
bindings: [],
|
|
589
669
|
});
|
|
670
|
+
if (payload.componentArtifacts) {
|
|
671
|
+
const componentBindings: Array<{
|
|
672
|
+
target: Exclude<FirmwareUpdateV4Target, 'resource'>;
|
|
673
|
+
artifact: FirmwareArtifactReference;
|
|
674
|
+
}> = Object.entries(payload.componentArtifacts).flatMap(([target, artifact]) =>
|
|
675
|
+
artifact
|
|
676
|
+
? [
|
|
677
|
+
{
|
|
678
|
+
target: target as Exclude<FirmwareUpdateV4Target, 'resource'>,
|
|
679
|
+
artifact: artifact as FirmwareArtifactReference,
|
|
680
|
+
},
|
|
681
|
+
]
|
|
682
|
+
: []
|
|
683
|
+
);
|
|
684
|
+
assertFirmwareUpdatePreparedPlanBinding({
|
|
685
|
+
preparedPlan,
|
|
686
|
+
executor: 'v4',
|
|
687
|
+
platform: payload.platform,
|
|
688
|
+
scopeTargets: componentBindings.map(binding => binding.target),
|
|
689
|
+
bindings: componentBindings,
|
|
690
|
+
});
|
|
691
|
+
}
|
|
590
692
|
}
|
|
591
693
|
|
|
694
|
+
const preparedExpectedTargetVersions = preparedPlan?.artifacts.reduce<
|
|
695
|
+
NonNullable<FirmwareUpdateV4Params['expectedTargetVersions']>
|
|
696
|
+
>((result, artifact) => {
|
|
697
|
+
if (
|
|
698
|
+
artifact.role === 'component' &&
|
|
699
|
+
artifact.target !== 'resource' &&
|
|
700
|
+
artifact.targetVersion &&
|
|
701
|
+
PROTOCOL_V2_INSTALL_TARGET_BY_UPDATE_TARGET.has(
|
|
702
|
+
artifact.target as Exclude<FirmwareUpdateV4Target, 'resource'>
|
|
703
|
+
)
|
|
704
|
+
) {
|
|
705
|
+
result[artifact.target as FirmwareUpdateV4Target] = artifact.targetVersion;
|
|
706
|
+
}
|
|
707
|
+
return result;
|
|
708
|
+
}, {});
|
|
709
|
+
|
|
592
710
|
this.params = {
|
|
593
711
|
preparedPlan,
|
|
594
712
|
chunkSize: payload.chunkSize,
|
|
@@ -602,13 +720,20 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
602
720
|
se02Binary: payload.se02Binary,
|
|
603
721
|
se03Binary: payload.se03Binary,
|
|
604
722
|
se04Binary: payload.se04Binary,
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
723
|
+
resourceArchiveBinary: payload.resourceArchiveBinary,
|
|
724
|
+
firmwareType: preparedPlan?.firmwareType ?? payload.firmwareType,
|
|
725
|
+
targetsToUpdate: preparedPlan
|
|
726
|
+
? ([...preparedPlan.targetsToUpdate] as FirmwareUpdateV4Target[])
|
|
727
|
+
: payload.targetsToUpdate?.map((target: FirmwareUpdateV4Target) =>
|
|
728
|
+
target === 'boot_resources' ? 'resource' : target
|
|
729
|
+
),
|
|
730
|
+
expectedTargetVersions: preparedPlan
|
|
731
|
+
? preparedExpectedTargetVersions
|
|
732
|
+
: payload.expectedTargetVersions,
|
|
608
733
|
platform: payload.platform,
|
|
609
734
|
expectedDeviceId: payload.expectedDeviceId,
|
|
610
735
|
artifactReader: hostBinding?.artifactReader ?? payload.artifactReader,
|
|
611
|
-
componentArtifacts: payload.componentArtifacts,
|
|
736
|
+
componentArtifacts: preparedPlan ? undefined : payload.componentArtifacts,
|
|
612
737
|
};
|
|
613
738
|
}
|
|
614
739
|
|
|
@@ -654,6 +779,22 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
654
779
|
const firmwareType = this.params.firmwareType ?? deviceFirmwareType;
|
|
655
780
|
this.validateExpectedTargetVersions();
|
|
656
781
|
|
|
782
|
+
if (
|
|
783
|
+
!this.params.preparedPlan &&
|
|
784
|
+
this.params.resourceArchiveBinary &&
|
|
785
|
+
this.params.targetsToUpdate?.includes('resource')
|
|
786
|
+
) {
|
|
787
|
+
const localMemoryHost = await this.prepareProtocolV2LocalMemoryHost({
|
|
788
|
+
features: deviceFeatures,
|
|
789
|
+
firmwareType,
|
|
790
|
+
});
|
|
791
|
+
try {
|
|
792
|
+
return await this.runProtocolV2PreparedArtifacts(deviceFeatures, firmwareType);
|
|
793
|
+
} finally {
|
|
794
|
+
localMemoryHost.release();
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
|
|
657
798
|
const hasPreparedComponentArtifacts = Object.values(this.params.componentArtifacts ?? {}).some(
|
|
658
799
|
Boolean
|
|
659
800
|
);
|
|
@@ -680,7 +821,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
680
821
|
if (wantsResources) {
|
|
681
822
|
throw ERRORS.TypedError(
|
|
682
823
|
HardwareErrorCode.RuntimeError,
|
|
683
|
-
'Protocol V2 resource
|
|
824
|
+
'Protocol V2 resource archive must be provided through a local or external PreparedPlan',
|
|
684
825
|
{
|
|
685
826
|
firmwareUpdateCode: 'FirmwareArtifactsNotPrepared',
|
|
686
827
|
}
|
|
@@ -782,6 +923,54 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
782
923
|
firmwareType: EFirmwareType,
|
|
783
924
|
features: Features
|
|
784
925
|
): Promise<ProtocolV2InstallSource[]> {
|
|
926
|
+
if (this.params.preparedPlan) {
|
|
927
|
+
const requestedTargets = new Set(
|
|
928
|
+
this.params.preparedPlan.targetsToUpdate.filter(
|
|
929
|
+
(target): target is Exclude<FirmwareUpdateV4Target, 'resource'> => target !== 'resource'
|
|
930
|
+
)
|
|
931
|
+
);
|
|
932
|
+
const installSources: ProtocolV2InstallSource[] = [];
|
|
933
|
+
const preparedTargets = new Set<Exclude<FirmwareUpdateV4Target, 'resource'>>();
|
|
934
|
+
for (const artifact of this.params.preparedPlan.artifacts) {
|
|
935
|
+
if (artifact.target !== 'resource') {
|
|
936
|
+
const target = artifact.target as Exclude<FirmwareUpdateV4Target, 'resource'>;
|
|
937
|
+
const installTarget = PROTOCOL_V2_INSTALL_TARGET_BY_UPDATE_TARGET.get(target);
|
|
938
|
+
if (
|
|
939
|
+
!requestedTargets.has(target) ||
|
|
940
|
+
artifact.role !== 'component' ||
|
|
941
|
+
artifact.container !== 'raw' ||
|
|
942
|
+
!installTarget ||
|
|
943
|
+
preparedTargets.has(target)
|
|
944
|
+
) {
|
|
945
|
+
throw ERRORS.TypedError(
|
|
946
|
+
HardwareErrorCode.RuntimeError,
|
|
947
|
+
`Protocol V2 prepared component artifact is invalid: ${artifact.artifactId}`,
|
|
948
|
+
{ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
|
|
949
|
+
);
|
|
950
|
+
}
|
|
951
|
+
installSources.push({
|
|
952
|
+
...installTarget,
|
|
953
|
+
source: await this.openProtocolV2PreparedSource(artifact.artifact),
|
|
954
|
+
});
|
|
955
|
+
preparedTargets.add(target);
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
const missingTarget = Array.from(requestedTargets).find(
|
|
959
|
+
target => !preparedTargets.has(target)
|
|
960
|
+
);
|
|
961
|
+
if (missingTarget) {
|
|
962
|
+
throw ERRORS.TypedError(
|
|
963
|
+
HardwareErrorCode.RuntimeError,
|
|
964
|
+
`Protocol V2 ${missingTarget} artifact is not prepared`,
|
|
965
|
+
{
|
|
966
|
+
firmwareUpdateCode: 'FirmwareArtifactsNotPrepared',
|
|
967
|
+
artifactName: missingTarget,
|
|
968
|
+
}
|
|
969
|
+
);
|
|
970
|
+
}
|
|
971
|
+
return installSources;
|
|
972
|
+
}
|
|
973
|
+
|
|
785
974
|
const release = DataManager.getFirmwareLatestRelease(features, firmwareType);
|
|
786
975
|
if (!release) {
|
|
787
976
|
throw ERRORS.TypedError(
|
|
@@ -831,6 +1020,270 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
831
1020
|
return installSources;
|
|
832
1021
|
}
|
|
833
1022
|
|
|
1023
|
+
private async prepareProtocolV2LocalMemoryHost({
|
|
1024
|
+
features,
|
|
1025
|
+
firmwareType,
|
|
1026
|
+
}: {
|
|
1027
|
+
features: Features;
|
|
1028
|
+
firmwareType: EFirmwareType;
|
|
1029
|
+
}): Promise<FirmwareUpdateV4MemoryHost> {
|
|
1030
|
+
const installItems = this.buildProtocolV2InstallItems({
|
|
1031
|
+
bootloaderBinary: this.prepareBootloaderBinary(),
|
|
1032
|
+
fwBinaryMap: this.collectExplicitTargetBinaries(),
|
|
1033
|
+
});
|
|
1034
|
+
const requestedComponentTargets = new Set(
|
|
1035
|
+
(this.params.targetsToUpdate ?? []).filter(
|
|
1036
|
+
(target): target is Exclude<FirmwareUpdateV4Target, 'resource' | 'boot_resources'> =>
|
|
1037
|
+
target !== 'resource' && target !== 'boot_resources'
|
|
1038
|
+
)
|
|
1039
|
+
);
|
|
1040
|
+
const localComponentTargets = new Set(
|
|
1041
|
+
installItems.flatMap(item => {
|
|
1042
|
+
const target = PROTOCOL_V2_UPDATE_TARGET_BY_TARGET_ID.get(item.targetId);
|
|
1043
|
+
return target ? [target] : [];
|
|
1044
|
+
})
|
|
1045
|
+
);
|
|
1046
|
+
const missingTarget = Array.from(requestedComponentTargets).find(
|
|
1047
|
+
target => !localComponentTargets.has(target)
|
|
1048
|
+
);
|
|
1049
|
+
if (missingTarget) {
|
|
1050
|
+
throw ERRORS.TypedError(
|
|
1051
|
+
HardwareErrorCode.RuntimeError,
|
|
1052
|
+
`Protocol V2 local update has no binary for requested target ${missingTarget}`,
|
|
1053
|
+
{
|
|
1054
|
+
firmwareUpdateCode: 'FirmwareArtifactsNotPrepared',
|
|
1055
|
+
artifactName: missingTarget,
|
|
1056
|
+
}
|
|
1057
|
+
);
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
const planArtifacts: Parameters<typeof buildProtocolV2LocalFirmwareUpdatePlan>[0]['artifacts'] =
|
|
1061
|
+
[];
|
|
1062
|
+
const memoryArtifacts: FirmwareMemoryArtifact[] = [];
|
|
1063
|
+
for (const item of installItems) {
|
|
1064
|
+
const target = PROTOCOL_V2_UPDATE_TARGET_BY_TARGET_ID.get(item.targetId);
|
|
1065
|
+
if (!target || item.binary.byteLength <= 0) {
|
|
1066
|
+
throw ERRORS.TypedError(
|
|
1067
|
+
HardwareErrorCode.RuntimeError,
|
|
1068
|
+
`Protocol V2 local firmware artifact is invalid: ${item.fileName}`,
|
|
1069
|
+
{ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
|
|
1070
|
+
);
|
|
1071
|
+
}
|
|
1072
|
+
const artifactId = `component:${target}`;
|
|
1073
|
+
planArtifacts.push({
|
|
1074
|
+
artifactId,
|
|
1075
|
+
target,
|
|
1076
|
+
container: 'raw',
|
|
1077
|
+
logicalName: item.fileName,
|
|
1078
|
+
expectedSize: item.binary.byteLength,
|
|
1079
|
+
expectedSha256: bytesToHex(sha256(new Uint8Array(item.binary))),
|
|
1080
|
+
...(this.params.expectedTargetVersions?.[target]
|
|
1081
|
+
? { targetVersion: this.params.expectedTargetVersions[target] }
|
|
1082
|
+
: {}),
|
|
1083
|
+
});
|
|
1084
|
+
memoryArtifacts.push({ artifactId, binary: item.binary });
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
const resourceArchive = await this.prepareProtocolV2LocalResourceArchive(
|
|
1088
|
+
this.params.resourceArchiveBinary as ArrayBuffer
|
|
1089
|
+
);
|
|
1090
|
+
const resourceArtifactId = 'resource:archive';
|
|
1091
|
+
planArtifacts.push({
|
|
1092
|
+
artifactId: resourceArtifactId,
|
|
1093
|
+
target: 'resource',
|
|
1094
|
+
container: 'zip',
|
|
1095
|
+
logicalName: 'protocol-v2-local-resource-archive',
|
|
1096
|
+
expectedSize: resourceArchive.binary.byteLength,
|
|
1097
|
+
expectedSha256: bytesToHex(sha256(new Uint8Array(resourceArchive.binary))),
|
|
1098
|
+
});
|
|
1099
|
+
memoryArtifacts.push({
|
|
1100
|
+
artifactId: resourceArtifactId,
|
|
1101
|
+
binary: resourceArchive.binary,
|
|
1102
|
+
materializedEntries: resourceArchive.materializedEntries,
|
|
1103
|
+
});
|
|
1104
|
+
|
|
1105
|
+
const plan = buildProtocolV2LocalFirmwareUpdatePlan({
|
|
1106
|
+
features,
|
|
1107
|
+
firmwareType,
|
|
1108
|
+
platform: this.params.platform,
|
|
1109
|
+
artifacts: planArtifacts,
|
|
1110
|
+
});
|
|
1111
|
+
let memoryHost: FirmwareUpdateV4MemoryHost | undefined;
|
|
1112
|
+
try {
|
|
1113
|
+
memoryHost = prepareFirmwareUpdateV4MemoryHost({
|
|
1114
|
+
sdk: {
|
|
1115
|
+
prepareFirmwareUpdatePlan,
|
|
1116
|
+
registerFirmwareUpdateHostBinding,
|
|
1117
|
+
unregisterFirmwareUpdateHostBinding,
|
|
1118
|
+
},
|
|
1119
|
+
plan,
|
|
1120
|
+
artifacts: memoryArtifacts,
|
|
1121
|
+
});
|
|
1122
|
+
const preparedPlan = validateFirmwareUpdatePreparedPlan(memoryHost.preparedPlan);
|
|
1123
|
+
assertFirmwareUpdatePreparedPlanBinding({
|
|
1124
|
+
preparedPlan,
|
|
1125
|
+
executor: 'v4',
|
|
1126
|
+
platform: this.params.platform,
|
|
1127
|
+
scopeTargets: [],
|
|
1128
|
+
bindings: [],
|
|
1129
|
+
});
|
|
1130
|
+
assertFirmwareUpdatePreparedPlanDeviceIdentity({
|
|
1131
|
+
preparedPlan,
|
|
1132
|
+
deviceIdentity: this.protocolV2ExpectedSerialNumber,
|
|
1133
|
+
});
|
|
1134
|
+
const hostBinding = resolveFirmwareUpdateHostBinding(
|
|
1135
|
+
memoryHost.hostBindingGeneration,
|
|
1136
|
+
preparedPlan.preparedPlanDigest
|
|
1137
|
+
);
|
|
1138
|
+
this.params.preparedPlan = preparedPlan;
|
|
1139
|
+
this.params.targetsToUpdate = [...preparedPlan.targetsToUpdate] as FirmwareUpdateV4Target[];
|
|
1140
|
+
this.params.artifactReader = hostBinding.artifactReader;
|
|
1141
|
+
this.params.componentArtifacts = undefined;
|
|
1142
|
+
return memoryHost;
|
|
1143
|
+
} catch (error) {
|
|
1144
|
+
memoryHost?.release();
|
|
1145
|
+
throw error;
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
private async prepareProtocolV2LocalResourceArchive(
|
|
1150
|
+
binary: ArrayBuffer
|
|
1151
|
+
): Promise<ProtocolV2LocalResourceArchive> {
|
|
1152
|
+
if (binary.byteLength <= 0 || binary.byteLength > PROTOCOL_V2_RESOURCE_TOTAL_MAX_BYTES) {
|
|
1153
|
+
throw ERRORS.TypedError(
|
|
1154
|
+
HardwareErrorCode.RuntimeError,
|
|
1155
|
+
'Protocol V2 local resource ZIP archive size is invalid',
|
|
1156
|
+
{ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
|
|
1157
|
+
);
|
|
1158
|
+
}
|
|
1159
|
+
const zip = await JSZip.loadAsync(binary);
|
|
1160
|
+
const zipEntries = Object.values(zip.files);
|
|
1161
|
+
if (
|
|
1162
|
+
zipEntries.some(entry => entry.unsafeOriginalName && entry.unsafeOriginalName !== entry.name)
|
|
1163
|
+
) {
|
|
1164
|
+
throw ERRORS.TypedError(
|
|
1165
|
+
HardwareErrorCode.RuntimeError,
|
|
1166
|
+
'Protocol V2 local resource ZIP contains an unsafe entry path',
|
|
1167
|
+
{ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
|
|
1168
|
+
);
|
|
1169
|
+
}
|
|
1170
|
+
const entries = zipEntries.filter(entry => !entry.dir);
|
|
1171
|
+
if (entries.length === 0 || entries.length > PROTOCOL_V2_RESOURCE_FILE_MAX_COUNT + 1) {
|
|
1172
|
+
throw ERRORS.TypedError(
|
|
1173
|
+
HardwareErrorCode.RuntimeError,
|
|
1174
|
+
'Protocol V2 local resource ZIP entry set is invalid',
|
|
1175
|
+
{ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
|
|
1176
|
+
);
|
|
1177
|
+
}
|
|
1178
|
+
let declaredUncompressedSize = 0;
|
|
1179
|
+
let declaredCompressedSize = 0;
|
|
1180
|
+
for (const entry of entries) {
|
|
1181
|
+
const sizes = getProtocolV2ZipEntrySizes(entry);
|
|
1182
|
+
declaredCompressedSize += sizes.compressedSize;
|
|
1183
|
+
declaredUncompressedSize += sizes.uncompressedSize;
|
|
1184
|
+
const entryLimit =
|
|
1185
|
+
entry.name === 'manifest.json'
|
|
1186
|
+
? PROTOCOL_V2_RESOURCE_MANIFEST_MAX_BYTES
|
|
1187
|
+
: PROTOCOL_V2_RESOURCE_TOTAL_MAX_BYTES;
|
|
1188
|
+
if (
|
|
1189
|
+
sizes.uncompressedSize > entryLimit ||
|
|
1190
|
+
declaredCompressedSize > binary.byteLength ||
|
|
1191
|
+
declaredUncompressedSize >
|
|
1192
|
+
PROTOCOL_V2_RESOURCE_TOTAL_MAX_BYTES + PROTOCOL_V2_RESOURCE_MANIFEST_MAX_BYTES
|
|
1193
|
+
) {
|
|
1194
|
+
throw ERRORS.TypedError(
|
|
1195
|
+
HardwareErrorCode.RuntimeError,
|
|
1196
|
+
'Protocol V2 local resource ZIP declared size exceeds the allowed limit',
|
|
1197
|
+
{ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
|
|
1198
|
+
);
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
const manifestEntry = zip.file('manifest.json');
|
|
1202
|
+
if (!manifestEntry) {
|
|
1203
|
+
throw ERRORS.TypedError(
|
|
1204
|
+
HardwareErrorCode.RuntimeError,
|
|
1205
|
+
'Protocol V2 local resource ZIP has no manifest.json',
|
|
1206
|
+
{ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
|
|
1207
|
+
);
|
|
1208
|
+
}
|
|
1209
|
+
const manifestBinary = await manifestEntry.async('arraybuffer');
|
|
1210
|
+
if (
|
|
1211
|
+
manifestBinary.byteLength <= 0 ||
|
|
1212
|
+
manifestBinary.byteLength > PROTOCOL_V2_RESOURCE_MANIFEST_MAX_BYTES
|
|
1213
|
+
) {
|
|
1214
|
+
throw ERRORS.TypedError(
|
|
1215
|
+
HardwareErrorCode.RuntimeError,
|
|
1216
|
+
'Protocol V2 local resource manifest size is invalid',
|
|
1217
|
+
{ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
|
|
1218
|
+
);
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1221
|
+
let manifestValue: unknown;
|
|
1222
|
+
try {
|
|
1223
|
+
manifestValue = JSON.parse(new TextDecoder().decode(manifestBinary));
|
|
1224
|
+
} catch (error) {
|
|
1225
|
+
throw ERRORS.TypedError(
|
|
1226
|
+
HardwareErrorCode.RuntimeError,
|
|
1227
|
+
`Protocol V2 local resource manifest is invalid: ${String(error)}`,
|
|
1228
|
+
{ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
|
|
1229
|
+
);
|
|
1230
|
+
}
|
|
1231
|
+
const manifest = parseProtocolV2ResourceManifest(manifestValue);
|
|
1232
|
+
const selectedFiles = selectProtocolV2ResourceManifestFiles({
|
|
1233
|
+
manifest,
|
|
1234
|
+
targetsToUpdate: this.params.targetsToUpdate ?? [],
|
|
1235
|
+
});
|
|
1236
|
+
const expectedEntryNames = new Set([
|
|
1237
|
+
'manifest.json',
|
|
1238
|
+
...selectedFiles.map(file => file.archive_path),
|
|
1239
|
+
]);
|
|
1240
|
+
if (
|
|
1241
|
+
entries.length !== expectedEntryNames.size ||
|
|
1242
|
+
entries.some(entry => !expectedEntryNames.has(entry.name))
|
|
1243
|
+
) {
|
|
1244
|
+
throw ERRORS.TypedError(
|
|
1245
|
+
HardwareErrorCode.RuntimeError,
|
|
1246
|
+
'Protocol V2 local resource ZIP contains an unexpected entry',
|
|
1247
|
+
{ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
|
|
1248
|
+
);
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1251
|
+
let totalSize = 0;
|
|
1252
|
+
const materializedEntries: FirmwareMemoryArtifactEntry[] = [
|
|
1253
|
+
{ entryName: 'manifest.json', binary: manifestBinary },
|
|
1254
|
+
];
|
|
1255
|
+
for (const file of selectedFiles) {
|
|
1256
|
+
const entry = zip.file(file.archive_path);
|
|
1257
|
+
if (!entry) {
|
|
1258
|
+
throw ERRORS.TypedError(
|
|
1259
|
+
HardwareErrorCode.RuntimeError,
|
|
1260
|
+
`Protocol V2 local resource ZIP is missing ${file.archive_path}`,
|
|
1261
|
+
{ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
|
|
1262
|
+
);
|
|
1263
|
+
}
|
|
1264
|
+
const { uncompressedSize } = getProtocolV2ZipEntrySizes(entry);
|
|
1265
|
+
totalSize += uncompressedSize;
|
|
1266
|
+
if (uncompressedSize !== file.size || totalSize > PROTOCOL_V2_RESOURCE_TOTAL_MAX_BYTES) {
|
|
1267
|
+
throw ERRORS.TypedError(
|
|
1268
|
+
HardwareErrorCode.RuntimeError,
|
|
1269
|
+
`Protocol V2 local resource file declared size is invalid: ${file.archive_path}`,
|
|
1270
|
+
{ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
|
|
1271
|
+
);
|
|
1272
|
+
}
|
|
1273
|
+
const fileBinary = await entry.async('arraybuffer');
|
|
1274
|
+
const digest = bytesToHex(sha256(new Uint8Array(fileBinary)));
|
|
1275
|
+
if (fileBinary.byteLength !== file.size || digest !== file.sha256.toLowerCase()) {
|
|
1276
|
+
throw ERRORS.TypedError(
|
|
1277
|
+
HardwareErrorCode.RuntimeError,
|
|
1278
|
+
`Protocol V2 local resource file does not match manifest: ${file.archive_path}`,
|
|
1279
|
+
{ firmwareUpdateCode: 'FirmwareArtifactReceiptMismatch' }
|
|
1280
|
+
);
|
|
1281
|
+
}
|
|
1282
|
+
materializedEntries.push({ entryName: file.archive_path, binary: fileBinary });
|
|
1283
|
+
}
|
|
1284
|
+
return { binary, materializedEntries };
|
|
1285
|
+
}
|
|
1286
|
+
|
|
834
1287
|
private async prepareProtocolV2ResourceSources(): Promise<ProtocolV2ResourceBundleSource[]> {
|
|
835
1288
|
const resourceRequested = this.params.targetsToUpdate?.includes('resource') ?? false;
|
|
836
1289
|
if (!resourceRequested) {
|
|
@@ -1001,7 +1454,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1001
1454
|
for (const [target, version] of Object.entries(expected)) {
|
|
1002
1455
|
if (
|
|
1003
1456
|
!Array.from(PROTOCOL_V2_UPDATE_TARGET_BY_TARGET_ID.values()).includes(
|
|
1004
|
-
target as FirmwareUpdateV4Target
|
|
1457
|
+
target as Exclude<FirmwareUpdateV4Target, 'boot_resources'>
|
|
1005
1458
|
) ||
|
|
1006
1459
|
typeof version !== 'string' ||
|
|
1007
1460
|
!/^\d+\.\d+\.\d+(?:[-+][A-Za-z0-9.-]+)?$/u.test(version) ||
|
|
@@ -1178,9 +1631,12 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1178
1631
|
return target ? [target] : [];
|
|
1179
1632
|
})
|
|
1180
1633
|
);
|
|
1181
|
-
return (this.params.targetsToUpdate ?? [])
|
|
1182
|
-
|
|
1183
|
-
|
|
1634
|
+
return (this.params.targetsToUpdate ?? [])
|
|
1635
|
+
.filter(
|
|
1636
|
+
(target): target is Exclude<FirmwareUpdateV4Target, 'resource' | 'boot_resources'> =>
|
|
1637
|
+
target !== 'resource' && target !== 'boot_resources'
|
|
1638
|
+
)
|
|
1639
|
+
.filter(target => !preparedTargets.has(target));
|
|
1184
1640
|
}
|
|
1185
1641
|
|
|
1186
1642
|
private buildProtocolV2InstallItems({
|
|
@@ -1251,8 +1707,28 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1251
1707
|
`Missing Protocol V2 firmware component url: ${key}/${component.target}`
|
|
1252
1708
|
);
|
|
1253
1709
|
}
|
|
1710
|
+
const expectedFingerprint = normalizeProtocolV2Hex(component.fingerprint);
|
|
1711
|
+
if (
|
|
1712
|
+
!Number.isSafeInteger(component.expectedSize) ||
|
|
1713
|
+
Number(component.expectedSize) <= 0 ||
|
|
1714
|
+
!expectedFingerprint ||
|
|
1715
|
+
!/^[0-9a-f]{64}$/u.test(expectedFingerprint)
|
|
1716
|
+
) {
|
|
1717
|
+
throw ERRORS.TypedError(
|
|
1718
|
+
HardwareErrorCode.RuntimeError,
|
|
1719
|
+
`Protocol V2 firmware component integrity metadata is invalid: ${key}/${component.target}`,
|
|
1720
|
+
{ firmwareUpdateCode: 'FirmwarePlanInvalid' }
|
|
1721
|
+
);
|
|
1722
|
+
}
|
|
1254
1723
|
|
|
1255
1724
|
const { binary } = await getSysResourceBinary(component.url);
|
|
1725
|
+
if (binary.byteLength !== component.expectedSize) {
|
|
1726
|
+
throw ERRORS.TypedError(
|
|
1727
|
+
HardwareErrorCode.RuntimeError,
|
|
1728
|
+
`Protocol V2 firmware size mismatch: ${key}/${component.target}`,
|
|
1729
|
+
{ firmwareUpdateCode: 'FirmwareArtifactReceiptMismatch' }
|
|
1730
|
+
);
|
|
1731
|
+
}
|
|
1256
1732
|
if (!isProtocolV2FirmwareFingerprintValid(binary, component.fingerprint)) {
|
|
1257
1733
|
throw ERRORS.TypedError(
|
|
1258
1734
|
HardwareErrorCode.RuntimeError,
|
|
@@ -1672,10 +2148,12 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1672
2148
|
fwBinaryMap,
|
|
1673
2149
|
bootloaderBinary,
|
|
1674
2150
|
installItems,
|
|
2151
|
+
resourceBundles = [],
|
|
1675
2152
|
}: {
|
|
1676
2153
|
fwBinaryMap?: ProtocolV2TargetBinary[];
|
|
1677
2154
|
bootloaderBinary?: ArrayBuffer | null;
|
|
1678
2155
|
installItems?: ProtocolV2InstallItem[];
|
|
2156
|
+
resourceBundles?: ProtocolV2ResourceBundleBinary[];
|
|
1679
2157
|
}) {
|
|
1680
2158
|
const memoryInstallItems =
|
|
1681
2159
|
installItems ??
|
|
@@ -1692,9 +2170,19 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1692
2170
|
kind: item.kind,
|
|
1693
2171
|
}))
|
|
1694
2172
|
);
|
|
2173
|
+
const resourceSources = await Promise.all(
|
|
2174
|
+
resourceBundles.map(async resource => ({
|
|
2175
|
+
name: resource.name,
|
|
2176
|
+
source: await this.openProtocolV2MemorySource(resource.binary),
|
|
2177
|
+
devicePath: resource.devicePath,
|
|
2178
|
+
...(resource.version ? { version: resource.version } : {}),
|
|
2179
|
+
...(resource.payloadHash ? { payloadHash: resource.payloadHash } : {}),
|
|
2180
|
+
...(resource.headerHash ? { headerHash: resource.headerHash } : {}),
|
|
2181
|
+
}))
|
|
2182
|
+
);
|
|
1695
2183
|
return await this.executeProtocolV2Phases({
|
|
1696
2184
|
installSources,
|
|
1697
|
-
resourceSources
|
|
2185
|
+
resourceSources,
|
|
1698
2186
|
});
|
|
1699
2187
|
} finally {
|
|
1700
2188
|
await this.closeProtocolV2PreparedSources();
|