@onekeyfe/hd-core 1.2.0-alpha.84 → 1.2.0-alpha.86

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.
Files changed (58) hide show
  1. package/__tests__/check-all-firmware-release-protocol-v2.test.ts +69 -26
  2. package/__tests__/check-firmware-release-protocol-v2.test.ts +1 -2
  3. package/__tests__/firmware-memory-host.test.ts +108 -0
  4. package/__tests__/firmware-update/device-update-bootloader.test.ts +4 -1
  5. package/__tests__/firmware-update/firmware-host-binding.test.ts +9 -0
  6. package/__tests__/firmware-update/firmware-update-plan.test.ts +15 -70
  7. package/__tests__/firmware-update/firmware-update-prepared-plan.test.ts +53 -1
  8. package/__tests__/protocol-v2-resources.test.ts +59 -39
  9. package/__tests__/protocol-v2.test.ts +255 -306
  10. package/dist/api/CheckAllFirmwareRelease.d.ts.map +1 -1
  11. package/dist/api/FirmwareUpdateV4.d.ts +1 -2
  12. package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
  13. package/dist/api/firmware/FirmwareHostBinding.d.ts +1 -1
  14. package/dist/api/firmware/FirmwareHostBinding.d.ts.map +1 -1
  15. package/dist/api/firmware/FirmwareMemoryHost.d.ts +27 -0
  16. package/dist/api/firmware/FirmwareMemoryHost.d.ts.map +1 -0
  17. package/dist/api/firmware/FirmwareUpdatePlan.d.ts +3 -4
  18. package/dist/api/firmware/FirmwareUpdatePlan.d.ts.map +1 -1
  19. package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts.map +1 -1
  20. package/dist/api/firmware/getBinary.d.ts +0 -1
  21. package/dist/api/firmware/getBinary.d.ts.map +1 -1
  22. package/dist/data-manager/DataManager.d.ts +3 -1
  23. package/dist/data-manager/DataManager.d.ts.map +1 -1
  24. package/dist/index.d.ts +32 -70
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js +450 -390
  27. package/dist/inject.d.ts.map +1 -1
  28. package/dist/protocols/protocol-v2/resources.d.ts +0 -17
  29. package/dist/protocols/protocol-v2/resources.d.ts.map +1 -1
  30. package/dist/types/api/checkAllFirmwareRelease.d.ts +0 -1
  31. package/dist/types/api/checkAllFirmwareRelease.d.ts.map +1 -1
  32. package/dist/types/api/export.d.ts +0 -1
  33. package/dist/types/api/export.d.ts.map +1 -1
  34. package/dist/types/api/firmwareUpdate.d.ts +1 -10
  35. package/dist/types/api/firmwareUpdate.d.ts.map +1 -1
  36. package/dist/types/api/index.d.ts +0 -2
  37. package/dist/types/api/index.d.ts.map +1 -1
  38. package/dist/types/settings.d.ts +3 -15
  39. package/dist/types/settings.d.ts.map +1 -1
  40. package/package.json +4 -4
  41. package/src/api/CheckAllFirmwareRelease.ts +4 -13
  42. package/src/api/FirmwareUpdateV4.ts +166 -170
  43. package/src/api/firmware/FirmwareHostBinding.ts +16 -3
  44. package/src/api/firmware/FirmwareMemoryHost.ts +174 -0
  45. package/src/api/firmware/FirmwareUpdatePlan.ts +28 -48
  46. package/src/api/firmware/FirmwareUpdatePreparedPlan.ts +8 -1
  47. package/src/data-manager/DataManager.ts +33 -13
  48. package/src/index.ts +6 -1
  49. package/src/inject.ts +0 -2
  50. package/src/protocols/protocol-v2/resources.ts +0 -47
  51. package/src/types/api/checkAllFirmwareRelease.ts +0 -1
  52. package/src/types/api/export.ts +0 -4
  53. package/src/types/api/firmwareUpdate.ts +2 -15
  54. package/src/types/api/index.ts +0 -2
  55. package/src/types/settings.ts +3 -25
  56. package/dist/types/api/protocolV2ResourceManifest.d.ts +0 -17
  57. package/dist/types/api/protocolV2ResourceManifest.d.ts.map +0 -1
  58. package/src/types/api/protocolV2ResourceManifest.ts +0 -19
@@ -9,7 +9,6 @@ import {
9
9
  import { sha256 } from '@noble/hashes/sha256';
10
10
 
11
11
  import { FirmwareUpdateTipMessage, UI_REQUEST } from '../events/ui-request';
12
- import { validateProtocolV2FilesystemPath } from './helpers/filesystemValidation';
13
12
  import { validateParams } from './helpers/paramsValidator';
14
13
  import {
15
14
  LoggerNames,
@@ -30,16 +29,24 @@ import {
30
29
  ProtocolV2FirmwareTargetType,
31
30
  } from '../protocols/protocol-v2';
32
31
  import { requestProtocolV2DeviceInfo } from '../protocols/protocol-v2/features';
33
- import { isProtocolV2ResourceFileValid } from '../protocols/protocol-v2/resources';
32
+ import {
33
+ parseProtocolV2ResourceManifest,
34
+ selectProtocolV2ResourceManifestFiles,
35
+ } from '../protocols/protocol-v2/resources';
34
36
  import {
35
37
  getProtocolV2UnknownErrorText,
36
38
  isProtocolV2DeviceDisconnectedError,
37
39
  } from './protocol-v2/helpers';
38
- import { openFirmwareByteSource, writeFirmwareByteSource } from './firmware/FirmwareArtifactSource';
40
+ import {
41
+ openFirmwareByteSource,
42
+ readFirmwareByteSourceFully,
43
+ writeFirmwareByteSource,
44
+ } from './firmware/FirmwareArtifactSource';
39
45
  import { resolveFirmwareUpdateHostBinding } from './firmware/FirmwareHostBinding';
40
46
  import {
41
47
  assertFirmwareUpdatePreparedPlanBinding,
42
48
  assertFirmwareUpdatePreparedPlanDeviceIdentity,
49
+ validateFirmwareUpdatePreparedPlan,
43
50
  } from './firmware/FirmwareUpdatePreparedPlan';
44
51
 
45
52
  import type {
@@ -88,6 +95,9 @@ const PROTOCOL_V2_OKPP_HEADER_SIZE = 0x52a0;
88
95
  const PROTOCOL_V2_OKPP_PAYLOAD_HASH_OFFSET = 0x200;
89
96
  const PROTOCOL_V2_OKPP_HEADER_HASH_OFFSET = 0x240;
90
97
  const PROTOCOL_V2_OKPP_HASH_SIZE = 64;
98
+ const PROTOCOL_V2_RESOURCE_MANIFEST_MAX_BYTES = 1024 * 1024;
99
+ const PROTOCOL_V2_RESOURCE_FILE_MAX_COUNT = 512;
100
+ const PROTOCOL_V2_RESOURCE_TOTAL_MAX_BYTES = 256 * 1024 * 1024;
91
101
 
92
102
  const PROTOCOL_V2_NEO_UNSUPPORTED_TARGETS = new Set<FirmwareUpdateV4Target>(['se03', 'se04']);
93
103
 
@@ -141,15 +151,6 @@ type ProtocolV2TargetBinary = { fileName: string; binary: ArrayBuffer; targetId:
141
151
  type ProtocolV2InstallItem = ProtocolV2TargetBinary & {
142
152
  kind: ProtocolV2RemoteComponentTarget['kind'];
143
153
  };
144
- /** Protocol V2 resource file written independently to devicePath through FileWrite. */
145
- type ProtocolV2ResourceBundleBinary = {
146
- name: string;
147
- binary: ArrayBuffer;
148
- devicePath: string;
149
- version?: IVersionArray;
150
- payloadHash?: string;
151
- headerHash?: string;
152
- };
153
154
 
154
155
  type ProtocolV2RemoteComponentBinary = ProtocolV2RemoteComponentTarget & {
155
156
  binary: ArrayBuffer;
@@ -544,7 +545,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
544
545
  { name: 'targetsToUpdate', type: 'array', allowEmpty: true },
545
546
  { name: 'platform', type: 'string' },
546
547
  { name: 'expectedDeviceId', type: 'string' },
547
- { name: 'resourceFiles', type: 'array', allowEmpty: true },
548
548
  ]);
549
549
  if (
550
550
  payload.expectedDeviceId !== undefined &&
@@ -555,28 +555,27 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
555
555
  'Protocol V2 expected device identity is invalid'
556
556
  );
557
557
  }
558
+ const preparedPlan = payload.preparedPlan
559
+ ? validateFirmwareUpdatePreparedPlan(payload.preparedPlan)
560
+ : undefined;
558
561
  const hostBinding =
559
- payload.preparedPlan || payload.componentArtifacts || payload.resourceBundleArtifacts
560
- ? resolveFirmwareUpdateHostBinding(payload.hostBindingGeneration)
562
+ preparedPlan || payload.componentArtifacts
563
+ ? resolveFirmwareUpdateHostBinding(
564
+ payload.hostBindingGeneration,
565
+ preparedPlan?.preparedPlanDigest
566
+ )
561
567
  : undefined;
562
568
  if (hostBinding) {
569
+ const componentArtifacts = (payload.componentArtifacts ?? {}) as NonNullable<
570
+ FirmwareUpdateV4Params['componentArtifacts']
571
+ >;
563
572
  assertFirmwareUpdatePreparedPlanBinding({
564
- preparedPlan: payload.preparedPlan,
573
+ preparedPlan: preparedPlan ?? payload.preparedPlan,
565
574
  executor: 'v4',
566
575
  platform: payload.platform,
567
- scopeTargets: [
568
- 'boot',
569
- 'app_v1',
570
- 'app_v2',
571
- 'coprocessor',
572
- 'resource',
573
- 'se01',
574
- 'se02',
575
- 'se03',
576
- 'se04',
577
- ],
576
+ scopeTargets: ['boot', 'app_v1', 'app_v2', 'coprocessor', 'se01', 'se02', 'se03', 'se04'],
578
577
  bindings: [
579
- ...Object.entries(payload.componentArtifacts ?? {}).flatMap(([target, artifact]) =>
578
+ ...Object.entries(componentArtifacts).flatMap(([target, artifact]) =>
580
579
  artifact
581
580
  ? [
582
581
  {
@@ -586,19 +585,12 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
586
585
  ]
587
586
  : []
588
587
  ),
589
- ...(payload.resourceBundleArtifacts ?? []).map(
590
- (entry: { name: string; artifact: FirmwareArtifactReference }) => ({
591
- target: 'resource' as const,
592
- logicalName: entry.name,
593
- artifact: entry.artifact,
594
- })
595
- ),
596
588
  ],
597
589
  });
598
590
  }
599
591
 
600
592
  this.params = {
601
- preparedPlan: payload.preparedPlan,
593
+ preparedPlan,
602
594
  chunkSize: payload.chunkSize,
603
595
  forcedUpdateRes: payload.forcedUpdateRes,
604
596
  bootloaderBinary: payload.bootloaderBinary,
@@ -610,7 +602,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
610
602
  se02Binary: payload.se02Binary,
611
603
  se03Binary: payload.se03Binary,
612
604
  se04Binary: payload.se04Binary,
613
- resourceFiles: payload.resourceFiles,
614
605
  firmwareType: payload.firmwareType,
615
606
  targetsToUpdate: payload.targetsToUpdate,
616
607
  expectedTargetVersions: payload.expectedTargetVersions,
@@ -618,7 +609,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
618
609
  expectedDeviceId: payload.expectedDeviceId,
619
610
  artifactReader: hostBinding?.artifactReader ?? payload.artifactReader,
620
611
  componentArtifacts: payload.componentArtifacts,
621
- resourceBundleArtifacts: payload.resourceBundleArtifacts,
622
612
  };
623
613
  }
624
614
 
@@ -664,23 +654,16 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
664
654
  const firmwareType = this.params.firmwareType ?? deviceFirmwareType;
665
655
  this.validateExpectedTargetVersions();
666
656
 
667
- if (
668
- (this.params.componentArtifacts && Object.keys(this.params.componentArtifacts).length > 0) ||
669
- this.params.resourceBundleArtifacts?.length
670
- ) {
657
+ if (this.params.preparedPlan) {
671
658
  return this.runProtocolV2PreparedArtifacts(deviceFeatures, firmwareType);
672
659
  }
673
- const hasExplicitResourceFiles = !!this.params.resourceFiles?.length;
674
660
  const wantsResources = !!this.params.targetsToUpdate?.includes('resource');
675
- const needsPreparedResources = !hasExplicitResourceFiles && wantsResources;
676
661
 
677
662
  let fwBinaryMap: ProtocolV2TargetBinary[] = [];
678
663
  let bootloaderBinary: ArrayBuffer | null = null;
679
664
  let installItems: ProtocolV2InstallItem[] | undefined;
680
- let resourceBundles: ProtocolV2ResourceBundleBinary[] | undefined;
681
665
  try {
682
666
  this.postTipMessage(FirmwareUpdateTipMessage.StartDownloadFirmware);
683
- resourceBundles = this.prepareExplicitProtocolV2ResourceFiles();
684
667
  fwBinaryMap = this.collectExplicitTargetBinaries();
685
668
  bootloaderBinary = this.prepareBootloaderBinary();
686
669
  const explicitInstallItems = this.buildProtocolV2InstallItems({
@@ -690,8 +673,8 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
690
673
  const missingFirmwareTargets = this.getMissingProtocolV2FirmwareTargets(explicitInstallItems);
691
674
  const needsRemoteFirmware = this.params.targetsToUpdate?.length
692
675
  ? missingFirmwareTargets.length > 0
693
- : !this.hasExplicitProtocolV2Payload(explicitInstallItems);
694
- if (needsPreparedResources) {
676
+ : explicitInstallItems.length === 0;
677
+ if (wantsResources) {
695
678
  throw ERRORS.TypedError(
696
679
  HardwareErrorCode.RuntimeError,
697
680
  'Protocol V2 resource manifest must be prepared by the external firmware host',
@@ -743,12 +726,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
743
726
  throw normalizeFirmwarePreparationError(err);
744
727
  }
745
728
 
746
- if (
747
- !bootloaderBinary &&
748
- fwBinaryMap.length === 0 &&
749
- !installItems?.length &&
750
- !resourceBundles?.length
751
- ) {
729
+ if (!bootloaderBinary && fwBinaryMap.length === 0 && !installItems?.length) {
752
730
  throw ERRORS.TypedError(
753
731
  HardwareErrorCode.FirmwareUpdateDownloadFailed,
754
732
  'No firmware to update'
@@ -759,7 +737,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
759
737
  fwBinaryMap,
760
738
  bootloaderBinary,
761
739
  ...(installItems ? { installItems } : undefined),
762
- ...(resourceBundles?.length ? { resourceBundles } : undefined),
763
740
  });
764
741
  }
765
742
 
@@ -851,53 +828,138 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
851
828
  return installSources;
852
829
  }
853
830
 
854
- private async prepareProtocolV2ResourceSources(
855
- firmwareType: EFirmwareType,
856
- features: Features
857
- ): Promise<ProtocolV2ResourceBundleSource[]> {
858
- const preparedArtifacts = this.params.resourceBundleArtifacts ?? [];
859
- const resourceRequested =
860
- this.params.targetsToUpdate?.includes('resource') || preparedArtifacts.length > 0;
831
+ private async prepareProtocolV2ResourceSources(): Promise<ProtocolV2ResourceBundleSource[]> {
832
+ const resourceRequested = this.params.targetsToUpdate?.includes('resource') ?? false;
861
833
  if (!resourceRequested) {
862
834
  return [];
863
835
  }
864
- const release = DataManager.getFirmwareLatestRelease(features, firmwareType);
865
- const descriptors = release?.resourceBundles ?? [];
866
- const artifactByName = new Map(
867
- preparedArtifacts.map(item => [item.name, item.artifact] as const)
836
+ const archiveSources = await this.prepareProtocolV2ResourceArchiveSources();
837
+ if (archiveSources) {
838
+ return archiveSources;
839
+ }
840
+ throw ERRORS.TypedError(
841
+ HardwareErrorCode.RuntimeError,
842
+ 'Protocol V2 resource archive is not prepared',
843
+ { firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
868
844
  );
845
+ }
846
+
847
+ private async prepareProtocolV2ResourceArchiveSources(): Promise<
848
+ ProtocolV2ResourceBundleSource[] | undefined
849
+ > {
850
+ const archiveArtifacts =
851
+ this.params.preparedPlan?.artifacts.filter(
852
+ artifact =>
853
+ artifact.role === 'resourceBundle' &&
854
+ artifact.target === 'resource' &&
855
+ artifact.container === 'zip'
856
+ ) ?? [];
857
+ if (archiveArtifacts.length === 0) {
858
+ return undefined;
859
+ }
860
+ if (archiveArtifacts.length !== 1) {
861
+ throw ERRORS.TypedError(
862
+ HardwareErrorCode.RuntimeError,
863
+ 'Protocol V2 prepared plan must contain exactly one resource archive',
864
+ { firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
865
+ );
866
+ }
867
+
868
+ const entries = archiveArtifacts[0].materializedEntries ?? [];
869
+ const manifestEntry = entries.find(entry => entry.entryName === 'manifest.json');
870
+ if (
871
+ !manifestEntry ||
872
+ manifestEntry.artifact.size <= 0 ||
873
+ manifestEntry.artifact.size > PROTOCOL_V2_RESOURCE_MANIFEST_MAX_BYTES
874
+ ) {
875
+ throw ERRORS.TypedError(
876
+ HardwareErrorCode.RuntimeError,
877
+ 'Protocol V2 prepared resource archive has no valid manifest.json',
878
+ { firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
879
+ );
880
+ }
881
+
882
+ const manifestSource = await this.openProtocolV2PreparedSource(manifestEntry.artifact);
883
+ let manifestValue: unknown;
884
+ try {
885
+ manifestValue = JSON.parse(
886
+ new TextDecoder().decode(await readFirmwareByteSourceFully(manifestSource))
887
+ );
888
+ } catch (error) {
889
+ throw ERRORS.TypedError(
890
+ HardwareErrorCode.RuntimeError,
891
+ `Protocol V2 prepared resource manifest is invalid: ${String(error)}`,
892
+ { firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
893
+ );
894
+ }
895
+ const manifest = parseProtocolV2ResourceManifest(manifestValue);
896
+ const selectedFiles = selectProtocolV2ResourceManifestFiles({
897
+ manifest,
898
+ targetsToUpdate: this.params.targetsToUpdate ?? [],
899
+ });
900
+ if (selectedFiles.length > PROTOCOL_V2_RESOURCE_FILE_MAX_COUNT) {
901
+ throw ERRORS.TypedError(
902
+ HardwareErrorCode.RuntimeError,
903
+ 'Protocol V2 prepared resource archive contains too many files',
904
+ { firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
905
+ );
906
+ }
907
+
908
+ const entriesByName = new Map(entries.map(entry => [entry.entryName, entry] as const));
909
+ const expectedEntryNames = new Set([
910
+ 'manifest.json',
911
+ ...selectedFiles.map(file => file.archive_path),
912
+ ]);
913
+ if (entries.some(entry => !expectedEntryNames.has(entry.entryName))) {
914
+ throw ERRORS.TypedError(
915
+ HardwareErrorCode.RuntimeError,
916
+ 'Protocol V2 prepared resource archive contains an unexpected entry',
917
+ { firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
918
+ );
919
+ }
920
+
921
+ let totalSize = 0;
869
922
  const sources: ProtocolV2ResourceBundleSource[] = [];
870
- for (const descriptor of descriptors) {
871
- const artifact = artifactByName.get(descriptor.name);
872
- if (!artifact) {
923
+ for (const [index, file] of selectedFiles.entries()) {
924
+ const entry = entriesByName.get(file.archive_path);
925
+ if (
926
+ !entry ||
927
+ entry.artifact.size !== file.size ||
928
+ entry.artifact.sha256.toLowerCase() !== file.sha256.toLowerCase()
929
+ ) {
873
930
  throw ERRORS.TypedError(
874
931
  HardwareErrorCode.RuntimeError,
875
- `Protocol V2 resource bundle ${descriptor.name} is not prepared`,
876
- {
877
- firmwareUpdateCode: 'FirmwareArtifactsNotPrepared',
878
- artifactName: descriptor.name,
879
- }
932
+ `Protocol V2 prepared resource file does not match manifest: ${file.archive_path}`,
933
+ { firmwareUpdateCode: 'FirmwareArtifactReceiptMismatch' }
880
934
  );
881
935
  }
882
- const devicePath = validateProtocolV2FilesystemPath(
883
- descriptor.devicePath,
884
- 'resourceBundles[].devicePath'
885
- );
936
+ totalSize += entry.artifact.size;
937
+ if (totalSize > PROTOCOL_V2_RESOURCE_TOTAL_MAX_BYTES) {
938
+ throw ERRORS.TypedError(
939
+ HardwareErrorCode.RuntimeError,
940
+ 'Protocol V2 prepared resource archive exceeds the total size limit',
941
+ { firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
942
+ );
943
+ }
944
+ const source = await this.openProtocolV2PreparedSource(entry.artifact);
945
+ const header =
946
+ source.size >= PROTOCOL_V2_OKPP_HEADER_SIZE
947
+ ? parseProtocolV2OkppHeader(
948
+ new Uint8Array(await source.readAt(0, PROTOCOL_V2_OKPP_HEADER_SIZE))
949
+ )
950
+ : null;
886
951
  sources.push({
887
- name: descriptor.name,
888
- source: await this.openProtocolV2PreparedSource(artifact),
889
- devicePath,
890
- version: descriptor.version,
891
- payloadHash: descriptor.payloadHash,
892
- headerHash: descriptor.headerHash,
952
+ name: file.original_name || `resource-${index}`,
953
+ source,
954
+ devicePath: file.device_path,
955
+ ...(header
956
+ ? {
957
+ version: header.version,
958
+ payloadHash: header.payloadHash,
959
+ headerHash: header.headerHash,
960
+ }
961
+ : {}),
893
962
  });
894
- artifactByName.delete(descriptor.name);
895
- }
896
- if (artifactByName.size > 0) {
897
- throw ERRORS.TypedError(
898
- HardwareErrorCode.RuntimeError,
899
- `Protocol V2 release does not contain resource bundle ${artifactByName.keys().next().value}`
900
- );
901
963
  }
902
964
  return sources;
903
965
  }
@@ -906,19 +968,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
906
968
  try {
907
969
  this.postTipMessage(FirmwareUpdateTipMessage.StartDownloadFirmware);
908
970
  const installSources = await this.prepareProtocolV2InstallSources(firmwareType, features);
909
- const explicitResourceBundles = this.prepareExplicitProtocolV2ResourceFiles();
910
- const resourceSources = explicitResourceBundles?.length
911
- ? await Promise.all(
912
- explicitResourceBundles.map(async bundle => ({
913
- name: bundle.name,
914
- source: await this.openProtocolV2MemorySource(bundle.binary),
915
- devicePath: bundle.devicePath,
916
- version: bundle.version,
917
- payloadHash: bundle.payloadHash,
918
- headerHash: bundle.headerHash,
919
- }))
920
- )
921
- : await this.prepareProtocolV2ResourceSources(firmwareType, features);
971
+ const resourceSources = await this.prepareProtocolV2ResourceSources();
922
972
  if (installSources.length === 0 && resourceSources.length === 0) {
923
973
  throw ERRORS.TypedError(
924
974
  HardwareErrorCode.FirmwareUpdateDownloadFailed,
@@ -1034,13 +1084,13 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1034
1084
  if (this.params.targetsToUpdate?.length) {
1035
1085
  return [...new Set(this.params.targetsToUpdate)];
1036
1086
  }
1087
+ if (this.params.preparedPlan?.targetsToUpdate.length) {
1088
+ return [...new Set(this.params.preparedPlan.targetsToUpdate)] as FirmwareUpdateV4Target[];
1089
+ }
1037
1090
  const targets = new Set<FirmwareUpdateV4Target>();
1038
1091
  Object.keys(this.params.componentArtifacts ?? {}).forEach(target =>
1039
1092
  targets.add(target as FirmwareUpdateV4Target)
1040
1093
  );
1041
- if (this.params.resourceBundleArtifacts?.length || this.params.resourceFiles?.length) {
1042
- targets.add('resource');
1043
- }
1044
1094
  if (this.params.bootloaderBinary) targets.add('boot');
1045
1095
  if (this.params.applicationP1Binary) targets.add('app_v1');
1046
1096
  if (this.params.applicationP2Binary) targets.add('app_v2');
@@ -1118,10 +1168,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1118
1168
  return this.params.bootloaderBinary ?? null;
1119
1169
  }
1120
1170
 
1121
- private hasExplicitProtocolV2Payload(installItems: ProtocolV2InstallItem[]) {
1122
- return !!this.params.resourceFiles?.length || installItems.length > 0;
1123
- }
1124
-
1125
1171
  private getMissingProtocolV2FirmwareTargets(installItems: ProtocolV2InstallItem[]) {
1126
1172
  const preparedTargets = new Set(
1127
1173
  installItems.flatMap(item => {
@@ -1303,55 +1349,13 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1303
1349
  };
1304
1350
  }
1305
1351
 
1306
- private prepareExplicitProtocolV2ResourceFiles(): ProtocolV2ResourceBundleBinary[] | undefined {
1307
- const files = this.params.resourceFiles ?? [];
1308
- if (!files?.length) return undefined;
1309
-
1310
- const prepared = files.map((file, index) => {
1311
- const devicePath = validateProtocolV2FilesystemPath(
1312
- file.devicePath,
1313
- `resourceFiles[${index}].devicePath`
1314
- );
1315
- const descriptor = file as typeof file & Partial<{ size: number; fileHash: string }>;
1316
- if (descriptor.size !== undefined && descriptor.size !== file.binary.byteLength) {
1317
- throw new Error(`resourceFiles[${index}] size mismatch`);
1318
- }
1319
- if (
1320
- descriptor.fileHash &&
1321
- !isProtocolV2ResourceFileValid(file.binary, {
1322
- size: file.binary.byteLength,
1323
- fileHash: descriptor.fileHash,
1324
- })
1325
- ) {
1326
- throw new Error(`resourceFiles[${index}] SHA-256 mismatch`);
1327
- }
1328
- const header = parseProtocolV2OkppHeader(toProtocolV2Bytes(file.binary));
1329
- return {
1330
- name: devicePath.split('/').pop() ?? devicePath,
1331
- binary: file.binary,
1332
- devicePath,
1333
- ...(header
1334
- ? {
1335
- version: header.version,
1336
- payloadHash: header.payloadHash,
1337
- headerHash: header.headerHash,
1338
- }
1339
- : {}),
1340
- };
1341
- });
1342
- if (new Set(prepared.map(file => file.devicePath)).size !== prepared.length) {
1343
- throw new Error('resourceFiles contain duplicate devicePath values');
1344
- }
1345
- return prepared;
1346
- }
1347
-
1348
1352
  private getProtocolV2ResourceFilePath(path: string) {
1349
1353
  if (path.startsWith('vol')) return path;
1350
1354
  if (path.startsWith('/')) return `vol0:${path}`;
1351
1355
  return `vol0:/${path}`;
1352
1356
  }
1353
1357
 
1354
- private async readProtocolV2DeviceFileHeader(path: string) {
1358
+ private async readProtocolV2DeviceFileHeader(path: string, expectedSize?: number) {
1355
1359
  const typedCall = this.device.getCommands().typedCall.bind(this.device.getCommands());
1356
1360
  const filePath = this.getProtocolV2ResourceFilePath(path);
1357
1361
  const pathInfoRes = await typedCall('FilesystemPathInfoQuery', 'FilesystemPathInfo', {
@@ -1362,7 +1366,8 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1362
1366
  !pathInfoRes.message?.exist ||
1363
1367
  pathInfoRes.message?.directory ||
1364
1368
  fileSize === undefined ||
1365
- fileSize < PROTOCOL_V2_OKPP_HEADER_SIZE
1369
+ fileSize < PROTOCOL_V2_OKPP_HEADER_SIZE ||
1370
+ (expectedSize !== undefined && fileSize !== expectedSize)
1366
1371
  ) {
1367
1372
  return null;
1368
1373
  }
@@ -1400,14 +1405,17 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1400
1405
  private async isProtocolV2ResourceBundleUpToDate(
1401
1406
  bundle: Pick<
1402
1407
  ProtocolV2ResourceBundleSource,
1403
- 'name' | 'devicePath' | 'version' | 'payloadHash' | 'headerHash'
1408
+ 'name' | 'source' | 'devicePath' | 'version' | 'payloadHash' | 'headerHash'
1404
1409
  >
1405
1410
  ): Promise<boolean> {
1406
1411
  if (this.params?.forcedUpdateRes) return false;
1407
1412
  if (!bundle.payloadHash || !bundle.headerHash) return false;
1408
1413
 
1409
1414
  try {
1410
- const header = await this.readProtocolV2DeviceFileHeader(bundle.devicePath);
1415
+ const header = await this.readProtocolV2DeviceFileHeader(
1416
+ bundle.devicePath,
1417
+ bundle.source.size
1418
+ );
1411
1419
  if (!header) return false;
1412
1420
 
1413
1421
  if (bundle.version) {
@@ -1661,12 +1669,10 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1661
1669
  fwBinaryMap,
1662
1670
  bootloaderBinary,
1663
1671
  installItems,
1664
- resourceBundles,
1665
1672
  }: {
1666
1673
  fwBinaryMap?: ProtocolV2TargetBinary[];
1667
1674
  bootloaderBinary?: ArrayBuffer | null;
1668
1675
  installItems?: ProtocolV2InstallItem[];
1669
- resourceBundles?: ProtocolV2ResourceBundleBinary[];
1670
1676
  }) {
1671
1677
  const memoryInstallItems =
1672
1678
  installItems ??
@@ -1683,19 +1689,9 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1683
1689
  kind: item.kind,
1684
1690
  }))
1685
1691
  );
1686
- const resourceSources = await Promise.all(
1687
- (resourceBundles ?? []).map(async bundle => ({
1688
- name: bundle.name,
1689
- source: await this.openProtocolV2MemorySource(bundle.binary),
1690
- devicePath: bundle.devicePath,
1691
- version: bundle.version,
1692
- payloadHash: bundle.payloadHash,
1693
- headerHash: bundle.headerHash,
1694
- }))
1695
- );
1696
1692
  return await this.executeProtocolV2Phases({
1697
1693
  installSources,
1698
- resourceSources,
1694
+ resourceSources: [],
1699
1695
  });
1700
1696
  } finally {
1701
1697
  await this.closeProtocolV2PreparedSources();
@@ -16,6 +16,8 @@ class FirmwareHostBindingRegistry {
16
16
  register(binding: FirmwareUpdateHostBinding): number {
17
17
  if (
18
18
  !binding ||
19
+ typeof binding.preparedPlanDigest !== 'string' ||
20
+ !/^[0-9a-f]{64}$/i.test(binding.preparedPlanDigest) ||
19
21
  typeof binding.artifactReader?.open !== 'function' ||
20
22
  typeof binding.artifactReader.read !== 'function' ||
21
23
  typeof binding.artifactReader.close !== 'function'
@@ -40,7 +42,7 @@ class FirmwareHostBindingRegistry {
40
42
  return this.generation;
41
43
  }
42
44
 
43
- resolve(generation: number): FirmwareUpdateHostBinding {
45
+ resolve(generation: number, preparedPlanDigest?: string): FirmwareUpdateHostBinding {
44
46
  if (
45
47
  !Number.isSafeInteger(generation) ||
46
48
  generation <= 0 ||
@@ -50,12 +52,21 @@ class FirmwareHostBindingRegistry {
50
52
  return bindingError(`Firmware host binding generation ${generation} is stale`);
51
53
  }
52
54
  const { binding } = this;
55
+ if (
56
+ preparedPlanDigest !== undefined &&
57
+ binding.preparedPlanDigest.toLowerCase() !== preparedPlanDigest.toLowerCase()
58
+ ) {
59
+ return bindingError(
60
+ `Firmware host binding generation ${generation} does not match the prepared plan`
61
+ );
62
+ }
53
63
  const assertCurrent = () => {
54
64
  if (generation !== this.generation || binding !== this.binding) {
55
65
  bindingError(`Firmware host binding generation ${generation} is stale`);
56
66
  }
57
67
  };
58
68
  return {
69
+ preparedPlanDigest: binding.preparedPlanDigest,
59
70
  artifactReader: {
60
71
  open: async input => {
61
72
  assertCurrent();
@@ -96,5 +107,7 @@ export const getFirmwareUpdateHostBindingGeneration = (): number =>
96
107
  firmwareHostBindingRegistry.getGeneration();
97
108
 
98
109
  export const resolveFirmwareUpdateHostBinding = (
99
- generation: number | undefined
100
- ): FirmwareUpdateHostBinding => firmwareHostBindingRegistry.resolve(generation ?? Number.NaN);
110
+ generation: number | undefined,
111
+ preparedPlanDigest?: string
112
+ ): FirmwareUpdateHostBinding =>
113
+ firmwareHostBindingRegistry.resolve(generation ?? Number.NaN, preparedPlanDigest);