@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.
@@ -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 { resolveFirmwareUpdateHostBinding } from './firmware/FirmwareHostBinding';
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<number, FirmwareUpdateV4Target>([
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: ['boot', 'app_v1', 'app_v2', 'coprocessor', 'se01', 'se02', 'se03', 'se04'],
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
- firmwareType: payload.firmwareType,
606
- targetsToUpdate: payload.targetsToUpdate,
607
- expectedTargetVersions: payload.expectedTargetVersions,
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 manifest must be prepared by the external firmware host',
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 ?? []).filter(
1182
- target => target !== 'resource' && !preparedTargets.has(target)
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();