@onekeyfe/hd-core 1.2.0-alpha.88 → 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 +1 -0
- package/__tests__/firmware-update/firmware-update-plan.test.ts +47 -0
- package/__tests__/protocol-v2.test.ts +147 -12
- package/dist/api/FirmwareUpdateV4.d.ts +1 -0
- package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
- 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 +17 -3
- package/dist/index.js +290 -123
- package/dist/types/api/firmwareUpdate.d.ts +12 -2
- package/dist/types/api/firmwareUpdate.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/api/FirmwareUpdateV4.ts +295 -55
- package/src/api/firmware/FirmwareMemoryHost.ts +9 -4
- package/src/api/firmware/FirmwareUpdatePlan.ts +49 -4
- package/src/types/api/firmwareUpdate.ts +19 -2
|
@@ -43,12 +43,19 @@ import {
|
|
|
43
43
|
readFirmwareByteSourceFully,
|
|
44
44
|
writeFirmwareByteSource,
|
|
45
45
|
} from './firmware/FirmwareArtifactSource';
|
|
46
|
-
import {
|
|
46
|
+
import {
|
|
47
|
+
registerFirmwareUpdateHostBinding,
|
|
48
|
+
resolveFirmwareUpdateHostBinding,
|
|
49
|
+
unregisterFirmwareUpdateHostBinding,
|
|
50
|
+
} from './firmware/FirmwareHostBinding';
|
|
47
51
|
import {
|
|
48
52
|
assertFirmwareUpdatePreparedPlanBinding,
|
|
49
53
|
assertFirmwareUpdatePreparedPlanDeviceIdentity,
|
|
54
|
+
prepareFirmwareUpdatePlan,
|
|
50
55
|
validateFirmwareUpdatePreparedPlan,
|
|
51
56
|
} from './firmware/FirmwareUpdatePreparedPlan';
|
|
57
|
+
import { prepareFirmwareUpdateV4MemoryHost } from './firmware/FirmwareMemoryHost';
|
|
58
|
+
import { buildProtocolV2LocalFirmwareUpdatePlan } from './firmware/FirmwareUpdatePlan';
|
|
52
59
|
|
|
53
60
|
import type {
|
|
54
61
|
FirmwareArtifactReference,
|
|
@@ -65,6 +72,11 @@ import type {
|
|
|
65
72
|
IVersionArray,
|
|
66
73
|
} from '../types';
|
|
67
74
|
import type { FirmwareByteSource } from './firmware/FirmwareArtifactSource';
|
|
75
|
+
import type {
|
|
76
|
+
FirmwareMemoryArtifact,
|
|
77
|
+
FirmwareMemoryArtifactEntry,
|
|
78
|
+
FirmwareUpdateV4MemoryHost,
|
|
79
|
+
} from './firmware/FirmwareMemoryHost';
|
|
68
80
|
|
|
69
81
|
const Log = getLogger(LoggerNames.Method);
|
|
70
82
|
|
|
@@ -102,6 +114,28 @@ const PROTOCOL_V2_RESOURCE_TOTAL_MAX_BYTES = 256 * 1024 * 1024;
|
|
|
102
114
|
|
|
103
115
|
const PROTOCOL_V2_NEO_UNSUPPORTED_TARGETS = new Set<FirmwareUpdateV4Target>(['se03', 'se04']);
|
|
104
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
|
+
|
|
105
139
|
export function assertProtocolV2FirmwareTargetsSupported(
|
|
106
140
|
deviceType: EDeviceType | string | undefined,
|
|
107
141
|
params: FirmwareUpdateV4Params
|
|
@@ -183,6 +217,18 @@ type ProtocolV2ResourceBundleBinary = Omit<ProtocolV2ResourceBundleSource, 'sour
|
|
|
183
217
|
binary: ArrayBuffer;
|
|
184
218
|
};
|
|
185
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
|
+
|
|
186
232
|
type ProtocolV2ExecutionPhaseKind =
|
|
187
233
|
| 'resource-sync'
|
|
188
234
|
| 'bootloader-install'
|
|
@@ -264,7 +310,10 @@ const resolveProtocolV2ResourceWritePath = (devicePath: string) => {
|
|
|
264
310
|
: devicePath;
|
|
265
311
|
};
|
|
266
312
|
|
|
267
|
-
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
|
+
>([
|
|
268
317
|
[ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_BOOTLOADER, 'boot'],
|
|
269
318
|
[ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_APPLICATION_P1, 'app_v1'],
|
|
270
319
|
[ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_APPLICATION_P2, 'app_v2'],
|
|
@@ -560,6 +609,8 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
560
609
|
{ name: 'se03Binary', type: 'buffer' },
|
|
561
610
|
{ name: 'se04Binary', type: 'buffer' },
|
|
562
611
|
{ name: 'resourceArchiveBinary', type: 'buffer' },
|
|
612
|
+
{ name: 'resourceFiles', type: 'array', allowEmpty: true },
|
|
613
|
+
{ name: 'resourceBundleArtifacts', type: 'array', allowEmpty: true },
|
|
563
614
|
{ name: 'firmwareType', type: 'string' },
|
|
564
615
|
{ name: 'targetsToUpdate', type: 'array', allowEmpty: true },
|
|
565
616
|
{ name: 'platform', type: 'string' },
|
|
@@ -574,6 +625,12 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
574
625
|
'Protocol V2 expected device identity is invalid'
|
|
575
626
|
);
|
|
576
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
|
+
}
|
|
577
634
|
const preparedPlan = payload.preparedPlan
|
|
578
635
|
? validateFirmwareUpdatePreparedPlan(payload.preparedPlan)
|
|
579
636
|
: undefined;
|
|
@@ -667,7 +724,9 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
667
724
|
firmwareType: preparedPlan?.firmwareType ?? payload.firmwareType,
|
|
668
725
|
targetsToUpdate: preparedPlan
|
|
669
726
|
? ([...preparedPlan.targetsToUpdate] as FirmwareUpdateV4Target[])
|
|
670
|
-
: payload.targetsToUpdate
|
|
727
|
+
: payload.targetsToUpdate?.map((target: FirmwareUpdateV4Target) =>
|
|
728
|
+
target === 'boot_resources' ? 'resource' : target
|
|
729
|
+
),
|
|
671
730
|
expectedTargetVersions: preparedPlan
|
|
672
731
|
? preparedExpectedTargetVersions
|
|
673
732
|
: payload.expectedTargetVersions,
|
|
@@ -720,6 +779,22 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
720
779
|
const firmwareType = this.params.firmwareType ?? deviceFirmwareType;
|
|
721
780
|
this.validateExpectedTargetVersions();
|
|
722
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
|
+
|
|
723
798
|
const hasPreparedComponentArtifacts = Object.values(this.params.componentArtifacts ?? {}).some(
|
|
724
799
|
Boolean
|
|
725
800
|
);
|
|
@@ -731,7 +806,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
731
806
|
let fwBinaryMap: ProtocolV2TargetBinary[] = [];
|
|
732
807
|
let bootloaderBinary: ArrayBuffer | null = null;
|
|
733
808
|
let installItems: ProtocolV2InstallItem[] | undefined;
|
|
734
|
-
let resourceBundles: ProtocolV2ResourceBundleBinary[] = [];
|
|
735
809
|
try {
|
|
736
810
|
this.postTipMessage(FirmwareUpdateTipMessage.StartDownloadFirmware);
|
|
737
811
|
fwBinaryMap = this.collectExplicitTargetBinaries();
|
|
@@ -745,17 +819,12 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
745
819
|
? missingFirmwareTargets.length > 0
|
|
746
820
|
: explicitInstallItems.length === 0;
|
|
747
821
|
if (wantsResources) {
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
}
|
|
755
|
-
);
|
|
756
|
-
}
|
|
757
|
-
resourceBundles = await this.prepareProtocolV2LocalResourceArchive(
|
|
758
|
-
this.params.resourceArchiveBinary
|
|
822
|
+
throw ERRORS.TypedError(
|
|
823
|
+
HardwareErrorCode.RuntimeError,
|
|
824
|
+
'Protocol V2 resource archive must be provided through a local or external PreparedPlan',
|
|
825
|
+
{
|
|
826
|
+
firmwareUpdateCode: 'FirmwareArtifactsNotPrepared',
|
|
827
|
+
}
|
|
759
828
|
);
|
|
760
829
|
}
|
|
761
830
|
if (
|
|
@@ -801,12 +870,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
801
870
|
throw normalizeFirmwarePreparationError(err);
|
|
802
871
|
}
|
|
803
872
|
|
|
804
|
-
if (
|
|
805
|
-
!bootloaderBinary &&
|
|
806
|
-
fwBinaryMap.length === 0 &&
|
|
807
|
-
!installItems?.length &&
|
|
808
|
-
resourceBundles.length === 0
|
|
809
|
-
) {
|
|
873
|
+
if (!bootloaderBinary && fwBinaryMap.length === 0 && !installItems?.length) {
|
|
810
874
|
throw ERRORS.TypedError(
|
|
811
875
|
HardwareErrorCode.FirmwareUpdateDownloadFailed,
|
|
812
876
|
'No firmware to update'
|
|
@@ -817,7 +881,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
817
881
|
fwBinaryMap,
|
|
818
882
|
bootloaderBinary,
|
|
819
883
|
...(installItems ? { installItems } : undefined),
|
|
820
|
-
...(resourceBundles.length > 0 ? { resourceBundles } : undefined),
|
|
821
884
|
});
|
|
822
885
|
}
|
|
823
886
|
|
|
@@ -957,11 +1020,154 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
957
1020
|
return installSources;
|
|
958
1021
|
}
|
|
959
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
|
+
|
|
960
1149
|
private async prepareProtocolV2LocalResourceArchive(
|
|
961
1150
|
binary: ArrayBuffer
|
|
962
|
-
): Promise<
|
|
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
|
+
}
|
|
963
1159
|
const zip = await JSZip.loadAsync(binary);
|
|
964
|
-
const
|
|
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);
|
|
965
1171
|
if (entries.length === 0 || entries.length > PROTOCOL_V2_RESOURCE_FILE_MAX_COUNT + 1) {
|
|
966
1172
|
throw ERRORS.TypedError(
|
|
967
1173
|
HardwareErrorCode.RuntimeError,
|
|
@@ -969,6 +1175,29 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
969
1175
|
{ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
|
|
970
1176
|
);
|
|
971
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
|
+
}
|
|
972
1201
|
const manifestEntry = zip.file('manifest.json');
|
|
973
1202
|
if (!manifestEntry) {
|
|
974
1203
|
throw ERRORS.TypedError(
|
|
@@ -1020,8 +1249,10 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1020
1249
|
}
|
|
1021
1250
|
|
|
1022
1251
|
let totalSize = 0;
|
|
1023
|
-
const
|
|
1024
|
-
|
|
1252
|
+
const materializedEntries: FirmwareMemoryArtifactEntry[] = [
|
|
1253
|
+
{ entryName: 'manifest.json', binary: manifestBinary },
|
|
1254
|
+
];
|
|
1255
|
+
for (const file of selectedFiles) {
|
|
1025
1256
|
const entry = zip.file(file.archive_path);
|
|
1026
1257
|
if (!entry) {
|
|
1027
1258
|
throw ERRORS.TypedError(
|
|
@@ -1030,6 +1261,15 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1030
1261
|
{ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
|
|
1031
1262
|
);
|
|
1032
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
|
+
}
|
|
1033
1273
|
const fileBinary = await entry.async('arraybuffer');
|
|
1034
1274
|
const digest = bytesToHex(sha256(new Uint8Array(fileBinary)));
|
|
1035
1275
|
if (fileBinary.byteLength !== file.size || digest !== file.sha256.toLowerCase()) {
|
|
@@ -1039,32 +1279,9 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1039
1279
|
{ firmwareUpdateCode: 'FirmwareArtifactReceiptMismatch' }
|
|
1040
1280
|
);
|
|
1041
1281
|
}
|
|
1042
|
-
|
|
1043
|
-
if (totalSize > PROTOCOL_V2_RESOURCE_TOTAL_MAX_BYTES) {
|
|
1044
|
-
throw ERRORS.TypedError(
|
|
1045
|
-
HardwareErrorCode.RuntimeError,
|
|
1046
|
-
'Protocol V2 local resource ZIP exceeds the total size limit',
|
|
1047
|
-
{ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
|
|
1048
|
-
);
|
|
1049
|
-
}
|
|
1050
|
-
const header =
|
|
1051
|
-
fileBinary.byteLength >= PROTOCOL_V2_OKPP_HEADER_SIZE
|
|
1052
|
-
? parseProtocolV2OkppHeader(new Uint8Array(fileBinary))
|
|
1053
|
-
: null;
|
|
1054
|
-
resourceBundles.push({
|
|
1055
|
-
name: file.original_name || `resource-${index}`,
|
|
1056
|
-
binary: fileBinary,
|
|
1057
|
-
devicePath: file.device_path,
|
|
1058
|
-
...(header
|
|
1059
|
-
? {
|
|
1060
|
-
version: header.version,
|
|
1061
|
-
payloadHash: header.payloadHash,
|
|
1062
|
-
headerHash: header.headerHash,
|
|
1063
|
-
}
|
|
1064
|
-
: {}),
|
|
1065
|
-
});
|
|
1282
|
+
materializedEntries.push({ entryName: file.archive_path, binary: fileBinary });
|
|
1066
1283
|
}
|
|
1067
|
-
return
|
|
1284
|
+
return { binary, materializedEntries };
|
|
1068
1285
|
}
|
|
1069
1286
|
|
|
1070
1287
|
private async prepareProtocolV2ResourceSources(): Promise<ProtocolV2ResourceBundleSource[]> {
|
|
@@ -1237,7 +1454,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1237
1454
|
for (const [target, version] of Object.entries(expected)) {
|
|
1238
1455
|
if (
|
|
1239
1456
|
!Array.from(PROTOCOL_V2_UPDATE_TARGET_BY_TARGET_ID.values()).includes(
|
|
1240
|
-
target as FirmwareUpdateV4Target
|
|
1457
|
+
target as Exclude<FirmwareUpdateV4Target, 'boot_resources'>
|
|
1241
1458
|
) ||
|
|
1242
1459
|
typeof version !== 'string' ||
|
|
1243
1460
|
!/^\d+\.\d+\.\d+(?:[-+][A-Za-z0-9.-]+)?$/u.test(version) ||
|
|
@@ -1414,9 +1631,12 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1414
1631
|
return target ? [target] : [];
|
|
1415
1632
|
})
|
|
1416
1633
|
);
|
|
1417
|
-
return (this.params.targetsToUpdate ?? [])
|
|
1418
|
-
|
|
1419
|
-
|
|
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));
|
|
1420
1640
|
}
|
|
1421
1641
|
|
|
1422
1642
|
private buildProtocolV2InstallItems({
|
|
@@ -1487,8 +1707,28 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1487
1707
|
`Missing Protocol V2 firmware component url: ${key}/${component.target}`
|
|
1488
1708
|
);
|
|
1489
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
|
+
}
|
|
1490
1723
|
|
|
1491
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
|
+
}
|
|
1492
1732
|
if (!isProtocolV2FirmwareFingerprintValid(binary, component.fingerprint)) {
|
|
1493
1733
|
throw ERRORS.TypedError(
|
|
1494
1734
|
HardwareErrorCode.RuntimeError,
|
|
@@ -61,14 +61,19 @@ export function prepareFirmwareUpdateV4MemoryHost({
|
|
|
61
61
|
const hostId = `${Date.now()}:${memoryHostSequence}`;
|
|
62
62
|
const binaries = new Map<string, Uint8Array>();
|
|
63
63
|
const inputs = artifacts.map((input, artifactIndex) => {
|
|
64
|
-
const
|
|
65
|
-
|
|
64
|
+
const artifactBinary = new Uint8Array(input.binary).slice();
|
|
65
|
+
const artifact = createReference(
|
|
66
|
+
artifactBinary.buffer as ArrayBuffer,
|
|
67
|
+
`${hostId}:artifact:${artifactIndex}`
|
|
68
|
+
);
|
|
69
|
+
binaries.set(artifact.artifactRef, artifactBinary);
|
|
66
70
|
const materializedEntries = input.materializedEntries?.map((entry, entryIndex) => {
|
|
71
|
+
const entryBinary = new Uint8Array(entry.binary).slice();
|
|
67
72
|
const entryArtifact = createReference(
|
|
68
|
-
|
|
73
|
+
entryBinary.buffer as ArrayBuffer,
|
|
69
74
|
`${hostId}:entry:${artifactIndex}:${entryIndex}`
|
|
70
75
|
);
|
|
71
|
-
binaries.set(entryArtifact.artifactRef,
|
|
76
|
+
binaries.set(entryArtifact.artifactRef, entryBinary);
|
|
72
77
|
return {
|
|
73
78
|
entryName: entry.entryName,
|
|
74
79
|
artifact: entryArtifact,
|
|
@@ -504,6 +504,13 @@ const buildProtocolV2Artifacts = (
|
|
|
504
504
|
);
|
|
505
505
|
}
|
|
506
506
|
componentTargetSet.add(target);
|
|
507
|
+
const integrity = asIntegrity({
|
|
508
|
+
size: component.expectedSize,
|
|
509
|
+
sha256: component.fingerprint,
|
|
510
|
+
});
|
|
511
|
+
if (integrity.expectedSize === undefined || integrity.expectedSha256 === undefined) {
|
|
512
|
+
planError(`Protocol V2 component ${key} integrity metadata is invalid`);
|
|
513
|
+
}
|
|
507
514
|
artifacts.push({
|
|
508
515
|
artifactId: `component:${target}`,
|
|
509
516
|
role: 'component',
|
|
@@ -511,10 +518,7 @@ const buildProtocolV2Artifacts = (
|
|
|
511
518
|
url: assertArtifactUrl(component.url, `Protocol V2 component ${key}`),
|
|
512
519
|
container: 'raw',
|
|
513
520
|
logicalName: key,
|
|
514
|
-
...
|
|
515
|
-
size: component.expectedSize,
|
|
516
|
-
sha256: component.fingerprint,
|
|
517
|
-
}),
|
|
521
|
+
...integrity,
|
|
518
522
|
...(asVersion(component.version) ? { targetVersion: asVersion(component.version) } : {}),
|
|
519
523
|
});
|
|
520
524
|
targets.push(target);
|
|
@@ -632,6 +636,47 @@ const finalizeFirmwareUpdatePlan = ({
|
|
|
632
636
|
});
|
|
633
637
|
};
|
|
634
638
|
|
|
639
|
+
export type ProtocolV2LocalFirmwareUpdatePlanArtifact = {
|
|
640
|
+
artifactId: string;
|
|
641
|
+
target: Exclude<FirmwareUpdateV4Target, 'boot_resources'>;
|
|
642
|
+
container: 'raw' | 'zip';
|
|
643
|
+
logicalName: string;
|
|
644
|
+
expectedSize: number;
|
|
645
|
+
expectedSha256: string;
|
|
646
|
+
targetVersion?: string;
|
|
647
|
+
};
|
|
648
|
+
|
|
649
|
+
export const buildProtocolV2LocalFirmwareUpdatePlan = ({
|
|
650
|
+
features,
|
|
651
|
+
firmwareType,
|
|
652
|
+
platform,
|
|
653
|
+
artifacts,
|
|
654
|
+
}: {
|
|
655
|
+
features: Features;
|
|
656
|
+
firmwareType: EFirmwareType;
|
|
657
|
+
platform: FirmwareUpdatePlatform;
|
|
658
|
+
artifacts: ProtocolV2LocalFirmwareUpdatePlanArtifact[];
|
|
659
|
+
}): FirmwareUpdatePlan => {
|
|
660
|
+
if (artifacts.length === 0) {
|
|
661
|
+
return planError('Protocol V2 local firmware plan has no artifacts');
|
|
662
|
+
}
|
|
663
|
+
const plan = finalizeFirmwareUpdatePlan({
|
|
664
|
+
features,
|
|
665
|
+
firmwareType,
|
|
666
|
+
platform,
|
|
667
|
+
artifacts: artifacts.map(artifact => ({
|
|
668
|
+
...artifact,
|
|
669
|
+
role: artifact.target === 'resource' ? 'resourceBundle' : 'component',
|
|
670
|
+
url: `https://local-firmware.invalid/${encodeURIComponent(artifact.artifactId)}`,
|
|
671
|
+
})),
|
|
672
|
+
targetsToUpdate: artifacts.map(artifact => artifact.target),
|
|
673
|
+
});
|
|
674
|
+
if (plan.executor !== 'v4') {
|
|
675
|
+
return planError('Protocol V2 local firmware plan requires executor v4');
|
|
676
|
+
}
|
|
677
|
+
return plan;
|
|
678
|
+
};
|
|
679
|
+
|
|
635
680
|
export const buildProtocolV2FirmwareUpdatePlan = ({
|
|
636
681
|
features,
|
|
637
682
|
firmwareType,
|
|
@@ -120,6 +120,8 @@ export interface FirmwareUpdateV3Params {
|
|
|
120
120
|
*/
|
|
121
121
|
export type FirmwareUpdateV4Target =
|
|
122
122
|
| 'boot'
|
|
123
|
+
/** @deprecated Use resource with resourceArchiveBinary. */
|
|
124
|
+
| 'boot_resources'
|
|
123
125
|
| 'app_v1'
|
|
124
126
|
| 'app_v2'
|
|
125
127
|
| 'coprocessor'
|
|
@@ -154,13 +156,28 @@ export interface FirmwareUpdateV4Params {
|
|
|
154
156
|
se02Binary?: ArrayBuffer;
|
|
155
157
|
se03Binary?: ArrayBuffer;
|
|
156
158
|
se04Binary?: ArrayBuffer;
|
|
157
|
-
/** Complete Protocol V2 resource ZIP for local development;
|
|
159
|
+
/** Complete Protocol V2 resource ZIP for local development; Core converts it to a local PreparedPlan. */
|
|
158
160
|
resourceArchiveBinary?: ArrayBuffer;
|
|
161
|
+
/** @deprecated Package the complete signed resource set as a ZIP and use resourceArchiveBinary. */
|
|
162
|
+
resourceFiles?: Array<{
|
|
163
|
+
binary: ArrayBuffer;
|
|
164
|
+
devicePath: string;
|
|
165
|
+
size?: number;
|
|
166
|
+
fileHash?: string;
|
|
167
|
+
}>;
|
|
159
168
|
forcedUpdateRes?: boolean;
|
|
160
169
|
artifactReader?: FirmwareArtifactReader;
|
|
161
170
|
componentArtifacts?: Partial<
|
|
162
|
-
Record<
|
|
171
|
+
Record<
|
|
172
|
+
Exclude<FirmwareUpdateV4Target, 'resource' | 'boot_resources'>,
|
|
173
|
+
FirmwareArtifactReference
|
|
174
|
+
>
|
|
163
175
|
>;
|
|
176
|
+
/** @deprecated Use a ZIP artifact in preparedPlan instead. */
|
|
177
|
+
resourceBundleArtifacts?: Array<{
|
|
178
|
+
name: string;
|
|
179
|
+
artifact: FirmwareArtifactReference;
|
|
180
|
+
}>;
|
|
164
181
|
}
|
|
165
182
|
|
|
166
183
|
export declare function registerFirmwareUpdateHostBinding(
|