@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.
- package/__tests__/check-all-firmware-release-protocol-v2.test.ts +69 -26
- package/__tests__/check-firmware-release-protocol-v2.test.ts +1 -2
- package/__tests__/firmware-memory-host.test.ts +108 -0
- package/__tests__/firmware-update/device-update-bootloader.test.ts +4 -1
- package/__tests__/firmware-update/firmware-host-binding.test.ts +9 -0
- package/__tests__/firmware-update/firmware-update-plan.test.ts +15 -70
- package/__tests__/firmware-update/firmware-update-prepared-plan.test.ts +53 -1
- package/__tests__/protocol-v2-resources.test.ts +59 -39
- package/__tests__/protocol-v2.test.ts +255 -306
- package/dist/api/CheckAllFirmwareRelease.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV4.d.ts +1 -2
- package/dist/api/FirmwareUpdateV4.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/FirmwareMemoryHost.d.ts +27 -0
- package/dist/api/firmware/FirmwareMemoryHost.d.ts.map +1 -0
- package/dist/api/firmware/FirmwareUpdatePlan.d.ts +3 -4
- 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 +0 -1
- package/dist/api/firmware/getBinary.d.ts.map +1 -1
- package/dist/data-manager/DataManager.d.ts +3 -1
- package/dist/data-manager/DataManager.d.ts.map +1 -1
- package/dist/index.d.ts +32 -70
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +450 -390
- package/dist/inject.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/resources.d.ts +0 -17
- package/dist/protocols/protocol-v2/resources.d.ts.map +1 -1
- package/dist/types/api/checkAllFirmwareRelease.d.ts +0 -1
- package/dist/types/api/checkAllFirmwareRelease.d.ts.map +1 -1
- package/dist/types/api/export.d.ts +0 -1
- package/dist/types/api/export.d.ts.map +1 -1
- package/dist/types/api/firmwareUpdate.d.ts +1 -10
- package/dist/types/api/firmwareUpdate.d.ts.map +1 -1
- package/dist/types/api/index.d.ts +0 -2
- package/dist/types/api/index.d.ts.map +1 -1
- package/dist/types/settings.d.ts +3 -15
- package/dist/types/settings.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/api/CheckAllFirmwareRelease.ts +4 -13
- package/src/api/FirmwareUpdateV4.ts +166 -170
- package/src/api/firmware/FirmwareHostBinding.ts +16 -3
- package/src/api/firmware/FirmwareMemoryHost.ts +174 -0
- package/src/api/firmware/FirmwareUpdatePlan.ts +28 -48
- package/src/api/firmware/FirmwareUpdatePreparedPlan.ts +8 -1
- package/src/data-manager/DataManager.ts +33 -13
- package/src/index.ts +6 -1
- package/src/inject.ts +0 -2
- package/src/protocols/protocol-v2/resources.ts +0 -47
- package/src/types/api/checkAllFirmwareRelease.ts +0 -1
- package/src/types/api/export.ts +0 -4
- package/src/types/api/firmwareUpdate.ts +2 -15
- package/src/types/api/index.ts +0 -2
- package/src/types/settings.ts +3 -25
- package/dist/types/api/protocolV2ResourceManifest.d.ts +0 -17
- package/dist/types/api/protocolV2ResourceManifest.d.ts.map +0 -1
- 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 {
|
|
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 {
|
|
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
|
-
|
|
560
|
-
? resolveFirmwareUpdateHostBinding(
|
|
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(
|
|
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
|
|
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
|
-
:
|
|
694
|
-
if (
|
|
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
|
-
|
|
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
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
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
|
|
871
|
-
const
|
|
872
|
-
if (
|
|
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
|
|
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
|
-
|
|
883
|
-
|
|
884
|
-
|
|
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:
|
|
888
|
-
source
|
|
889
|
-
devicePath,
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
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
|
|
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(
|
|
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
|
-
|
|
110
|
+
generation: number | undefined,
|
|
111
|
+
preparedPlanDigest?: string
|
|
112
|
+
): FirmwareUpdateHostBinding =>
|
|
113
|
+
firmwareHostBindingRegistry.resolve(generation ?? Number.NaN, preparedPlanDigest);
|