@onekeyfe/hd-core 1.2.0-alpha.107 → 1.2.0-alpha.109
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__/DeviceCommands.test.ts +140 -26
- package/__tests__/base64Data.test.ts +70 -0
- package/__tests__/device-lifecycle-events.test.ts +113 -2
- package/__tests__/device-pool-state.test.ts +25 -0
- package/__tests__/device-state-mapper.test.ts +4 -4
- package/__tests__/device-utils.test.ts +1 -1
- package/__tests__/deviceSettings.test.ts +0 -1
- package/__tests__/deviceUploadNft.test.ts +33 -4
- package/__tests__/firmware-update/firmware-update-v4-install-poll.test.ts +125 -0
- package/__tests__/get-device-state.test.ts +60 -21
- package/__tests__/logBlockEvent.test.ts +13 -1
- package/__tests__/protocol-v2-resources.test.ts +8 -0
- package/__tests__/protocol-v2.test.ts +821 -227
- package/__tests__/refresh-device-state.test.ts +3 -1
- package/__tests__/resourceBase64Boundary.test.ts +48 -0
- package/__tests__/ton-sign-message.test.ts +84 -0
- package/dist/api/FirmwareUpdateV4.d.ts +7 -3
- package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
- package/dist/api/UploadPortfolio.d.ts +1 -1
- package/dist/api/UploadPortfolio.d.ts.map +1 -1
- package/dist/api/helpers/base64Data.d.ts +16 -0
- package/dist/api/helpers/base64Data.d.ts.map +1 -0
- package/dist/api/protocol-v2/DeviceInfoGet.d.ts +1 -1
- package/dist/api/protocol-v2/DeviceInfoGet.d.ts.map +1 -1
- package/dist/api/protocol-v2/DeviceUploadNft.d.ts +2 -3
- package/dist/api/protocol-v2/DeviceUploadNft.d.ts.map +1 -1
- package/dist/api/protocol-v2/DeviceUploadWallpaper.d.ts +2 -3
- package/dist/api/protocol-v2/DeviceUploadWallpaper.d.ts.map +1 -1
- package/dist/api/ton/TonSignMessage.d.ts.map +1 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/device/Device.d.ts +7 -2
- package/dist/device/Device.d.ts.map +1 -1
- package/dist/device/DeviceCommands.d.ts.map +1 -1
- package/dist/device/DevicePool.d.ts.map +1 -1
- package/dist/events/logBlockEvent.d.ts.map +1 -1
- package/dist/index.d.ts +16 -34
- package/dist/index.js +506 -329
- package/dist/protocols/protocol-v2/features.d.ts +13 -5
- package/dist/protocols/protocol-v2/features.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/index.d.ts +2 -2
- package/dist/protocols/protocol-v2/index.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/resources.d.ts.map +1 -1
- package/dist/types/api/protocolV2.d.ts +1 -1
- package/dist/types/api/protocolV2.d.ts.map +1 -1
- package/dist/types/settings.d.ts +4 -19
- package/dist/types/settings.d.ts.map +1 -1
- package/dist/utils/patch.d.ts +1 -1
- package/dist/utils/patch.d.ts.map +1 -1
- package/dist/utils/pro2Nft.d.ts +7 -0
- package/dist/utils/pro2Nft.d.ts.map +1 -1
- package/package.json +6 -4
- package/src/api/FirmwareUpdateV4.ts +307 -245
- package/src/api/UploadPortfolio.ts +9 -2
- package/src/api/helpers/base64Data.ts +85 -0
- package/src/api/protocol-v2/DeviceFactoryInfoSet.ts +1 -1
- package/src/api/protocol-v2/DeviceInfoGet.ts +3 -3
- package/src/api/protocol-v2/DeviceUploadNft.ts +56 -8
- package/src/api/protocol-v2/DeviceUploadWallpaper.ts +37 -18
- package/src/api/ton/TonSignMessage.ts +5 -3
- package/src/core/index.ts +6 -2
- package/src/data/messages/messages-protocol-v2.json +31 -14
- package/src/device/Device.ts +53 -31
- package/src/device/DeviceCommands.ts +4 -14
- package/src/device/DevicePool.ts +6 -2
- package/src/device/DeviceStateMapper.ts +15 -15
- package/src/deviceProfile/buildDeviceFeatures.ts +3 -3
- package/src/events/logBlockEvent.ts +14 -1
- package/src/protocols/protocol-v2/features.ts +22 -5
- package/src/protocols/protocol-v2/index.ts +2 -0
- package/src/protocols/protocol-v2/resources.ts +9 -46
- package/src/types/api/protocolV2.ts +1 -1
- package/src/types/settings.ts +4 -19
- package/src/utils/deviceSettings.ts +1 -1
- package/src/utils/pro2Nft.ts +31 -8
|
@@ -69,6 +69,7 @@ import type {
|
|
|
69
69
|
Features,
|
|
70
70
|
IFirmwareReleaseInfo,
|
|
71
71
|
IProtocolV2FirmwareComponent,
|
|
72
|
+
IProtocolV2ResourceManifestFile,
|
|
72
73
|
IVersionArray,
|
|
73
74
|
} from '../types';
|
|
74
75
|
import type { FirmwareByteSource } from './firmware/FirmwareArtifactSource';
|
|
@@ -88,7 +89,6 @@ const PROTOCOL_V2_BOOTLOADER_RECONNECT_TIMEOUT = 90 * 1000;
|
|
|
88
89
|
const PROTOCOL_V2_FINAL_RECONNECT_TIMEOUT = 3 * 60 * 1000;
|
|
89
90
|
const PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT = 5 * 1000;
|
|
90
91
|
const PROTOCOL_V2_FIRMWARE_STATUS_RESPONSE_TIMEOUT = 15 * 1000;
|
|
91
|
-
const PROTOCOL_V2_START_UPDATE_TIMEOUT = 3 * 60 * 1000;
|
|
92
92
|
const PROTOCOL_V2_INSTALL_TIMEOUT = 8 * 60 * 1000;
|
|
93
93
|
const PROTOCOL_V2_MISSING_TARGET_STATUS_GRACE_TIMEOUT = 30 * 1000;
|
|
94
94
|
const PROTOCOL_V2_TARGET_STATUS_PENDING = 0;
|
|
@@ -112,6 +112,13 @@ const PROTOCOL_V2_RESOURCE_MANIFEST_MAX_BYTES = 1024 * 1024;
|
|
|
112
112
|
const PROTOCOL_V2_RESOURCE_FILE_MAX_COUNT = 512;
|
|
113
113
|
const PROTOCOL_V2_RESOURCE_TOTAL_MAX_BYTES = 256 * 1024 * 1024;
|
|
114
114
|
|
|
115
|
+
const getProtocolV2LocalResourceArchivePath = (entryName: string) => {
|
|
116
|
+
const match = entryName.match(
|
|
117
|
+
/(?:^|\/)((?:bundles\/|loaders\/(?:bootloader|rom)\/).+\.okpkg)$/iu
|
|
118
|
+
);
|
|
119
|
+
return match?.[1];
|
|
120
|
+
};
|
|
121
|
+
|
|
115
122
|
const PROTOCOL_V2_NEO_UNSUPPORTED_TARGETS = new Set<FirmwareUpdateV4Target>(['se03', 'se04']);
|
|
116
123
|
|
|
117
124
|
const getProtocolV2ZipEntrySizes = (entry: JSZip.JSZipObject) => {
|
|
@@ -198,8 +205,6 @@ type ProtocolV2FirmwareUpdateStatusTarget = {
|
|
|
198
205
|
path?: string;
|
|
199
206
|
};
|
|
200
207
|
|
|
201
|
-
type ProtocolV2FirmwareUpdateStartResponse = TypedResponseMessage<'Success'>;
|
|
202
|
-
|
|
203
208
|
type ProtocolV2TargetBinary = { fileName: string; binary: ArrayBuffer; targetId: number };
|
|
204
209
|
type ProtocolV2InstallItem = ProtocolV2TargetBinary & {
|
|
205
210
|
kind: ProtocolV2RemoteComponentTarget['kind'];
|
|
@@ -243,15 +248,7 @@ type JSZipSizedEntry = JSZip.JSZipObject & {
|
|
|
243
248
|
};
|
|
244
249
|
};
|
|
245
250
|
|
|
246
|
-
type
|
|
247
|
-
| 'resource-sync'
|
|
248
|
-
| 'bootloader-install'
|
|
249
|
-
| 'bootloader-verify'
|
|
250
|
-
| 'component-install'
|
|
251
|
-
| 'final-verify';
|
|
252
|
-
|
|
253
|
-
type ProtocolV2ExecutionPhase = {
|
|
254
|
-
kind: ProtocolV2ExecutionPhaseKind;
|
|
251
|
+
type ProtocolV2TransferBatch = {
|
|
255
252
|
installSources: ProtocolV2InstallSource[];
|
|
256
253
|
resourceSources: ProtocolV2ResourceBundleSource[];
|
|
257
254
|
};
|
|
@@ -569,7 +566,7 @@ export const assertProtocolV2ReconnectIdentity = (
|
|
|
569
566
|
*
|
|
570
567
|
* It intentionally does not fall back to FirmwareUpdateV3/V1 behavior:
|
|
571
568
|
* - upload uses FilesystemFileWrite
|
|
572
|
-
* - install uses DeviceFirmwareUpdateRequest
|
|
569
|
+
* - install uses DeviceFirmwareUpdateStage followed by an empty DeviceFirmwareUpdateRequest
|
|
573
570
|
* - completion waits for target status to finish, reboots to normal, then polls DeviceInfo
|
|
574
571
|
*/
|
|
575
572
|
export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareUpdateV4Params> {
|
|
@@ -595,6 +592,10 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
595
592
|
|
|
596
593
|
private protocolV2FinalStatusVerified = false;
|
|
597
594
|
|
|
595
|
+
private protocolV2InstallBaselineVersions = new Map<number, string>();
|
|
596
|
+
|
|
597
|
+
private protocolV2LastRuntimeProbeFeatures?: Features;
|
|
598
|
+
|
|
598
599
|
init() {
|
|
599
600
|
this.allowDeviceMode = [UI_REQUEST.BOOTLOADER, UI_REQUEST.NOT_INITIALIZE];
|
|
600
601
|
this.requireDeviceMode = [];
|
|
@@ -813,6 +814,9 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
813
814
|
private async runProtocolV2() {
|
|
814
815
|
await this.captureProtocolV2PhysicalIdentity();
|
|
815
816
|
const deviceFeatures = await this.getProtocolV2DeviceFeatures();
|
|
817
|
+
this.protocolV2InstallBaselineVersions =
|
|
818
|
+
this.getProtocolV2ObservableTargetVersions(deviceFeatures);
|
|
819
|
+
this.protocolV2LastRuntimeProbeFeatures = undefined;
|
|
816
820
|
const currentDeviceType = this.device.getCurrentDeviceType();
|
|
817
821
|
const capabilityDeviceType =
|
|
818
822
|
currentDeviceType === EDeviceType.Pro2 || currentDeviceType === EDeviceType.Neo
|
|
@@ -826,6 +830,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
826
830
|
const deviceFirmwareType = getFirmwareType(deviceFeatures);
|
|
827
831
|
const firmwareType = this.params.firmwareType ?? deviceFirmwareType;
|
|
828
832
|
this.validateExpectedTargetVersions();
|
|
833
|
+
const wantsResources = !!this.params.targetsToUpdate?.includes('resource');
|
|
829
834
|
|
|
830
835
|
if (
|
|
831
836
|
!this.params.preparedPlan &&
|
|
@@ -849,11 +854,11 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
849
854
|
if (this.params.preparedPlan || hasPreparedComponentArtifacts) {
|
|
850
855
|
return this.runProtocolV2PreparedArtifacts(deviceFeatures, firmwareType);
|
|
851
856
|
}
|
|
852
|
-
const wantsResources = !!this.params.targetsToUpdate?.includes('resource');
|
|
853
857
|
|
|
854
858
|
let fwBinaryMap: ProtocolV2TargetBinary[] = [];
|
|
855
859
|
let bootloaderBinary: ArrayBuffer | null = null;
|
|
856
860
|
let installItems: ProtocolV2InstallItem[] | undefined;
|
|
861
|
+
let resourceMemoryHost: FirmwareUpdateV4MemoryHost | undefined;
|
|
857
862
|
try {
|
|
858
863
|
this.postTipMessage(FirmwareUpdateTipMessage.StartDownloadFirmware);
|
|
859
864
|
fwBinaryMap = this.collectExplicitTargetBinaries();
|
|
@@ -866,17 +871,9 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
866
871
|
const needsRemoteFirmware = this.params.targetsToUpdate?.length
|
|
867
872
|
? missingFirmwareTargets.length > 0
|
|
868
873
|
: explicitInstallItems.length === 0;
|
|
869
|
-
|
|
870
|
-
throw ERRORS.TypedError(
|
|
871
|
-
HardwareErrorCode.RuntimeError,
|
|
872
|
-
'Protocol V2 resource archive must be provided through a local or external PreparedPlan',
|
|
873
|
-
{
|
|
874
|
-
firmwareUpdateCode: 'FirmwareArtifactsNotPrepared',
|
|
875
|
-
}
|
|
876
|
-
);
|
|
877
|
-
}
|
|
874
|
+
const needsSdkManagedArtifacts = needsRemoteFirmware || wantsResources;
|
|
878
875
|
if (
|
|
879
|
-
|
|
876
|
+
needsSdkManagedArtifacts &&
|
|
880
877
|
(this.params.artifactReader ||
|
|
881
878
|
DataManager.getSettings('firmwareManifestMode') === 'external-only')
|
|
882
879
|
) {
|
|
@@ -888,9 +885,13 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
888
885
|
}
|
|
889
886
|
);
|
|
890
887
|
}
|
|
891
|
-
if (
|
|
888
|
+
if (needsSdkManagedArtifacts) {
|
|
892
889
|
// Remote updates must use a freshly fetched config before any reboot or file write.
|
|
893
|
-
await DataManager.forceReloadData(
|
|
890
|
+
await DataManager.forceReloadData({
|
|
891
|
+
requireResources: wantsResources,
|
|
892
|
+
resourceDeviceType:
|
|
893
|
+
capabilityDeviceType === EDeviceType.Neo ? EDeviceType.Neo : EDeviceType.Pro2,
|
|
894
|
+
});
|
|
894
895
|
}
|
|
895
896
|
if (needsRemoteFirmware) {
|
|
896
897
|
const remoteBinaries = await this.prepareRemoteProtocolV2Binaries(
|
|
@@ -913,8 +914,19 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
913
914
|
targetId: item.targetId,
|
|
914
915
|
}));
|
|
915
916
|
}
|
|
917
|
+
if (wantsResources) {
|
|
918
|
+
this.params.resourceArchiveBinary = await this.downloadRemoteProtocolV2ResourceArchive(
|
|
919
|
+
deviceFeatures
|
|
920
|
+
);
|
|
921
|
+
resourceMemoryHost = await this.prepareProtocolV2LocalMemoryHost({
|
|
922
|
+
features: deviceFeatures,
|
|
923
|
+
firmwareType,
|
|
924
|
+
availableInstallItems: installItems ?? explicitInstallItems,
|
|
925
|
+
});
|
|
926
|
+
}
|
|
916
927
|
this.postTipMessage(FirmwareUpdateTipMessage.FinishDownloadFirmware);
|
|
917
928
|
} catch (err) {
|
|
929
|
+
resourceMemoryHost?.release();
|
|
918
930
|
if (
|
|
919
931
|
typeof err === 'object' &&
|
|
920
932
|
err !== null &&
|
|
@@ -929,6 +941,14 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
929
941
|
throw normalizeFirmwarePreparationError(err);
|
|
930
942
|
}
|
|
931
943
|
|
|
944
|
+
if (resourceMemoryHost) {
|
|
945
|
+
try {
|
|
946
|
+
return await this.runProtocolV2PreparedArtifacts(deviceFeatures, firmwareType, false);
|
|
947
|
+
} finally {
|
|
948
|
+
resourceMemoryHost.release();
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
|
|
932
952
|
if (!bootloaderBinary && fwBinaryMap.length === 0 && !installItems?.length) {
|
|
933
953
|
throw ERRORS.TypedError(
|
|
934
954
|
HardwareErrorCode.FirmwareUpdateDownloadFailed,
|
|
@@ -1082,14 +1102,15 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1082
1102
|
private async prepareProtocolV2LocalMemoryHost({
|
|
1083
1103
|
features,
|
|
1084
1104
|
firmwareType,
|
|
1105
|
+
availableInstallItems = this.buildProtocolV2InstallItems({
|
|
1106
|
+
bootloaderBinary: this.prepareBootloaderBinary(),
|
|
1107
|
+
fwBinaryMap: this.collectExplicitTargetBinaries(),
|
|
1108
|
+
}),
|
|
1085
1109
|
}: {
|
|
1086
1110
|
features: Features;
|
|
1087
1111
|
firmwareType: EFirmwareType;
|
|
1112
|
+
availableInstallItems?: ProtocolV2InstallItem[];
|
|
1088
1113
|
}): Promise<FirmwareUpdateV4MemoryHost> {
|
|
1089
|
-
const availableInstallItems = this.buildProtocolV2InstallItems({
|
|
1090
|
-
bootloaderBinary: this.prepareBootloaderBinary(),
|
|
1091
|
-
fwBinaryMap: this.collectExplicitTargetBinaries(),
|
|
1092
|
-
});
|
|
1093
1114
|
const requestedComponentTargets = new Set(
|
|
1094
1115
|
(this.params.targetsToUpdate ?? []).filter(
|
|
1095
1116
|
(target): target is Exclude<FirmwareUpdateV4Target, 'resource' | 'boot_resources'> =>
|
|
@@ -1238,92 +1259,86 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1238
1259
|
);
|
|
1239
1260
|
}
|
|
1240
1261
|
const entries = zipEntries.filter(entry => !entry.dir);
|
|
1241
|
-
if (entries.length === 0
|
|
1262
|
+
if (entries.length === 0) {
|
|
1242
1263
|
throw ERRORS.TypedError(
|
|
1243
1264
|
HardwareErrorCode.RuntimeError,
|
|
1244
1265
|
'Protocol V2 local resource ZIP entry set is invalid',
|
|
1245
1266
|
{ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
|
|
1246
1267
|
);
|
|
1247
1268
|
}
|
|
1248
|
-
|
|
1249
|
-
let
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
declaredUncompressedSize += sizes.uncompressedSize;
|
|
1254
|
-
const entryLimit =
|
|
1255
|
-
entry.name === 'manifest.json'
|
|
1256
|
-
? PROTOCOL_V2_RESOURCE_MANIFEST_MAX_BYTES
|
|
1257
|
-
: PROTOCOL_V2_RESOURCE_TOTAL_MAX_BYTES;
|
|
1269
|
+
const manifestEntry = entries.find(entry => entry.name.split('/').pop() === 'manifest.json');
|
|
1270
|
+
let manifestBinary: ArrayBuffer | undefined;
|
|
1271
|
+
let manifestDirectory = '';
|
|
1272
|
+
let selectedFiles: IProtocolV2ResourceManifestFile[];
|
|
1273
|
+
if (manifestEntry) {
|
|
1258
1274
|
if (
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
declaredUncompressedSize >
|
|
1262
|
-
PROTOCOL_V2_RESOURCE_TOTAL_MAX_BYTES + PROTOCOL_V2_RESOURCE_MANIFEST_MAX_BYTES
|
|
1275
|
+
getProtocolV2ZipEntrySizes(manifestEntry).uncompressedSize >
|
|
1276
|
+
PROTOCOL_V2_RESOURCE_MANIFEST_MAX_BYTES
|
|
1263
1277
|
) {
|
|
1264
1278
|
throw ERRORS.TypedError(
|
|
1265
1279
|
HardwareErrorCode.RuntimeError,
|
|
1266
|
-
'Protocol V2 local resource
|
|
1280
|
+
'Protocol V2 local resource manifest size is invalid',
|
|
1267
1281
|
{ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
|
|
1268
1282
|
);
|
|
1269
1283
|
}
|
|
1284
|
+
manifestBinary = await manifestEntry.async('arraybuffer');
|
|
1285
|
+
if (
|
|
1286
|
+
manifestBinary.byteLength <= 0 ||
|
|
1287
|
+
manifestBinary.byteLength > PROTOCOL_V2_RESOURCE_MANIFEST_MAX_BYTES
|
|
1288
|
+
) {
|
|
1289
|
+
throw ERRORS.TypedError(
|
|
1290
|
+
HardwareErrorCode.RuntimeError,
|
|
1291
|
+
'Protocol V2 local resource manifest size is invalid',
|
|
1292
|
+
{ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
|
|
1293
|
+
);
|
|
1294
|
+
}
|
|
1295
|
+
let manifestValue: unknown;
|
|
1296
|
+
try {
|
|
1297
|
+
manifestValue = JSON.parse(new TextDecoder().decode(manifestBinary));
|
|
1298
|
+
} catch (error) {
|
|
1299
|
+
throw ERRORS.TypedError(
|
|
1300
|
+
HardwareErrorCode.RuntimeError,
|
|
1301
|
+
`Protocol V2 local resource manifest is invalid: ${String(error)}`,
|
|
1302
|
+
{ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
|
|
1303
|
+
);
|
|
1304
|
+
}
|
|
1305
|
+
selectedFiles = selectProtocolV2ResourceManifestFiles({
|
|
1306
|
+
manifest: parseProtocolV2ResourceManifest(manifestValue),
|
|
1307
|
+
targetsToUpdate: this.params.targetsToUpdate ?? [],
|
|
1308
|
+
});
|
|
1309
|
+
manifestDirectory = manifestEntry.name.slice(0, -'manifest.json'.length);
|
|
1310
|
+
} else {
|
|
1311
|
+
selectedFiles = entries.flatMap(entry => {
|
|
1312
|
+
const archivePath = getProtocolV2LocalResourceArchivePath(entry.name);
|
|
1313
|
+
if (!archivePath) return [];
|
|
1314
|
+
return [
|
|
1315
|
+
{
|
|
1316
|
+
archive_path: archivePath,
|
|
1317
|
+
original_name: archivePath.split('/').pop() ?? archivePath,
|
|
1318
|
+
device_path: `vol0:/${archivePath}`,
|
|
1319
|
+
size: getProtocolV2ZipEntrySizes(entry).uncompressedSize,
|
|
1320
|
+
sha256: '',
|
|
1321
|
+
},
|
|
1322
|
+
];
|
|
1323
|
+
});
|
|
1270
1324
|
}
|
|
1271
|
-
|
|
1272
|
-
if (!manifestEntry) {
|
|
1273
|
-
throw ERRORS.TypedError(
|
|
1274
|
-
HardwareErrorCode.RuntimeError,
|
|
1275
|
-
'Protocol V2 local resource ZIP has no manifest.json',
|
|
1276
|
-
{ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
|
|
1277
|
-
);
|
|
1278
|
-
}
|
|
1279
|
-
const manifestBinary = await manifestEntry.async('arraybuffer');
|
|
1280
|
-
if (
|
|
1281
|
-
manifestBinary.byteLength <= 0 ||
|
|
1282
|
-
manifestBinary.byteLength > PROTOCOL_V2_RESOURCE_MANIFEST_MAX_BYTES
|
|
1283
|
-
) {
|
|
1284
|
-
throw ERRORS.TypedError(
|
|
1285
|
-
HardwareErrorCode.RuntimeError,
|
|
1286
|
-
'Protocol V2 local resource manifest size is invalid',
|
|
1287
|
-
{ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
|
|
1288
|
-
);
|
|
1289
|
-
}
|
|
1290
|
-
|
|
1291
|
-
let manifestValue: unknown;
|
|
1292
|
-
try {
|
|
1293
|
-
manifestValue = JSON.parse(new TextDecoder().decode(manifestBinary));
|
|
1294
|
-
} catch (error) {
|
|
1295
|
-
throw ERRORS.TypedError(
|
|
1296
|
-
HardwareErrorCode.RuntimeError,
|
|
1297
|
-
`Protocol V2 local resource manifest is invalid: ${String(error)}`,
|
|
1298
|
-
{ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
|
|
1299
|
-
);
|
|
1300
|
-
}
|
|
1301
|
-
const manifest = parseProtocolV2ResourceManifest(manifestValue);
|
|
1302
|
-
const selectedFiles = selectProtocolV2ResourceManifestFiles({
|
|
1303
|
-
manifest,
|
|
1304
|
-
targetsToUpdate: this.params.targetsToUpdate ?? [],
|
|
1305
|
-
});
|
|
1306
|
-
const expectedEntryNames = new Set([
|
|
1307
|
-
'manifest.json',
|
|
1308
|
-
...selectedFiles.map(file => file.archive_path),
|
|
1309
|
-
]);
|
|
1310
|
-
if (
|
|
1311
|
-
entries.length !== expectedEntryNames.size ||
|
|
1312
|
-
entries.some(entry => !expectedEntryNames.has(entry.name))
|
|
1313
|
-
) {
|
|
1325
|
+
if (selectedFiles.length === 0 || selectedFiles.length > PROTOCOL_V2_RESOURCE_FILE_MAX_COUNT) {
|
|
1314
1326
|
throw ERRORS.TypedError(
|
|
1315
1327
|
HardwareErrorCode.RuntimeError,
|
|
1316
|
-
'Protocol V2 local resource ZIP
|
|
1328
|
+
'Protocol V2 local resource ZIP has no resource packages',
|
|
1317
1329
|
{ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
|
|
1318
1330
|
);
|
|
1319
1331
|
}
|
|
1320
1332
|
|
|
1321
1333
|
let totalSize = 0;
|
|
1322
|
-
const materializedEntries: FirmwareMemoryArtifactEntry[] = [
|
|
1323
|
-
|
|
1324
|
-
];
|
|
1334
|
+
const materializedEntries: FirmwareMemoryArtifactEntry[] = [];
|
|
1335
|
+
const normalizedFiles: IProtocolV2ResourceManifestFile[] = [];
|
|
1325
1336
|
for (const file of selectedFiles) {
|
|
1326
|
-
const entry =
|
|
1337
|
+
const entry = manifestEntry
|
|
1338
|
+
? zip.file(`${manifestDirectory}${file.archive_path}`)
|
|
1339
|
+
: entries.find(
|
|
1340
|
+
candidate => getProtocolV2LocalResourceArchivePath(candidate.name) === file.archive_path
|
|
1341
|
+
);
|
|
1327
1342
|
if (!entry) {
|
|
1328
1343
|
throw ERRORS.TypedError(
|
|
1329
1344
|
HardwareErrorCode.RuntimeError,
|
|
@@ -1342,15 +1357,21 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1342
1357
|
}
|
|
1343
1358
|
const fileBinary = await entry.async('arraybuffer');
|
|
1344
1359
|
const digest = bytesToHex(sha256(new Uint8Array(fileBinary)));
|
|
1345
|
-
if (
|
|
1360
|
+
if (
|
|
1361
|
+
fileBinary.byteLength !== file.size ||
|
|
1362
|
+
(file.sha256 && digest !== file.sha256.toLowerCase())
|
|
1363
|
+
) {
|
|
1346
1364
|
throw ERRORS.TypedError(
|
|
1347
1365
|
HardwareErrorCode.RuntimeError,
|
|
1348
1366
|
`Protocol V2 local resource file does not match manifest: ${file.archive_path}`,
|
|
1349
1367
|
{ firmwareUpdateCode: 'FirmwareArtifactReceiptMismatch' }
|
|
1350
1368
|
);
|
|
1351
1369
|
}
|
|
1370
|
+
normalizedFiles.push({ ...file, sha256: digest });
|
|
1352
1371
|
materializedEntries.push({ entryName: file.archive_path, binary: fileBinary });
|
|
1353
1372
|
}
|
|
1373
|
+
manifestBinary ??= new TextEncoder().encode(JSON.stringify({ files: normalizedFiles })).buffer;
|
|
1374
|
+
materializedEntries.unshift({ entryName: 'manifest.json', binary: manifestBinary });
|
|
1354
1375
|
return { binary, materializedEntries };
|
|
1355
1376
|
}
|
|
1356
1377
|
|
|
@@ -1517,9 +1538,15 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1517
1538
|
return sources;
|
|
1518
1539
|
}
|
|
1519
1540
|
|
|
1520
|
-
private async runProtocolV2PreparedArtifacts(
|
|
1541
|
+
private async runProtocolV2PreparedArtifacts(
|
|
1542
|
+
features: Features,
|
|
1543
|
+
firmwareType: EFirmwareType,
|
|
1544
|
+
announceDownload = true
|
|
1545
|
+
) {
|
|
1521
1546
|
try {
|
|
1522
|
-
|
|
1547
|
+
if (announceDownload) {
|
|
1548
|
+
this.postTipMessage(FirmwareUpdateTipMessage.StartDownloadFirmware);
|
|
1549
|
+
}
|
|
1523
1550
|
const installSources = await this.prepareProtocolV2InstallSources(firmwareType, features);
|
|
1524
1551
|
const resourceSources = await this.prepareProtocolV2ResourceSources();
|
|
1525
1552
|
if (installSources.length === 0 && resourceSources.length === 0) {
|
|
@@ -1528,7 +1555,9 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1528
1555
|
'No firmware to update'
|
|
1529
1556
|
);
|
|
1530
1557
|
}
|
|
1531
|
-
|
|
1558
|
+
if (announceDownload) {
|
|
1559
|
+
this.postTipMessage(FirmwareUpdateTipMessage.FinishDownloadFirmware);
|
|
1560
|
+
}
|
|
1532
1561
|
|
|
1533
1562
|
return await this.executeProtocolV2SourceUpdate({
|
|
1534
1563
|
installSources,
|
|
@@ -1882,6 +1911,44 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1882
1911
|
};
|
|
1883
1912
|
}
|
|
1884
1913
|
|
|
1914
|
+
private async downloadRemoteProtocolV2ResourceArchive(features: Features): Promise<ArrayBuffer> {
|
|
1915
|
+
const deviceType = getDeviceType(features);
|
|
1916
|
+
if (deviceType !== EDeviceType.Pro2 && deviceType !== EDeviceType.Neo) {
|
|
1917
|
+
throw ERRORS.TypedError(
|
|
1918
|
+
HardwareErrorCode.RuntimeError,
|
|
1919
|
+
'Protocol V2 resource archive requires a Pro2 or Neo device'
|
|
1920
|
+
);
|
|
1921
|
+
}
|
|
1922
|
+
const source = DataManager.getProtocolV2ResourceSource(deviceType);
|
|
1923
|
+
const expectedSha256 = normalizeProtocolV2Hex(source?.archiveSha256);
|
|
1924
|
+
if (
|
|
1925
|
+
!source?.archiveUrl ||
|
|
1926
|
+
!Number.isSafeInteger(source.archiveSize) ||
|
|
1927
|
+
source.archiveSize <= 0 ||
|
|
1928
|
+
source.archiveSize > PROTOCOL_V2_RESOURCE_TOTAL_MAX_BYTES ||
|
|
1929
|
+
!expectedSha256 ||
|
|
1930
|
+
!/^[0-9a-f]{64}$/u.test(expectedSha256)
|
|
1931
|
+
) {
|
|
1932
|
+
throw ERRORS.TypedError(
|
|
1933
|
+
HardwareErrorCode.RuntimeError,
|
|
1934
|
+
'Protocol V2 resource archive integrity metadata is invalid',
|
|
1935
|
+
{ firmwareUpdateCode: 'FirmwarePlanInvalid' }
|
|
1936
|
+
);
|
|
1937
|
+
}
|
|
1938
|
+
const { binary } = await getSysResourceBinary(source.archiveUrl);
|
|
1939
|
+
if (
|
|
1940
|
+
binary.byteLength !== source.archiveSize ||
|
|
1941
|
+
bytesToHex(sha256(new Uint8Array(binary))) !== expectedSha256
|
|
1942
|
+
) {
|
|
1943
|
+
throw ERRORS.TypedError(
|
|
1944
|
+
HardwareErrorCode.RuntimeError,
|
|
1945
|
+
'Protocol V2 resource archive does not match the remote config',
|
|
1946
|
+
{ firmwareUpdateCode: 'FirmwareArtifactReceiptMismatch' }
|
|
1947
|
+
);
|
|
1948
|
+
}
|
|
1949
|
+
return binary;
|
|
1950
|
+
}
|
|
1951
|
+
|
|
1885
1952
|
private async prepareRemoteProtocolV2Binaries(
|
|
1886
1953
|
firmwareType: EFirmwareType,
|
|
1887
1954
|
features: Features,
|
|
@@ -2067,21 +2134,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2067
2134
|
return this.device.features?.mode === 'romloader';
|
|
2068
2135
|
}
|
|
2069
2136
|
|
|
2070
|
-
async
|
|
2071
|
-
// romloader is the first update environment and forwards targets to bootloader.
|
|
2072
|
-
// It rejects DeviceRebootType.Bootloader, so reuse the current connection.
|
|
2073
|
-
if (this.isProtocolV2RomloaderMode()) {
|
|
2074
|
-
Log.debug('Protocol V2 device is in romloader mode; start firmware update directly');
|
|
2075
|
-
this.protocolV2ExecutionInLoader = true;
|
|
2076
|
-
return false;
|
|
2077
|
-
}
|
|
2078
|
-
if (this.isProtocolV2BootloaderMode()) {
|
|
2079
|
-
Log.debug('Protocol V2 device is already in bootloader mode, skip reboot');
|
|
2080
|
-
this.protocolV2ExecutionInLoader = true;
|
|
2081
|
-
this.postTipMessage(FirmwareUpdateTipMessage.GoToBootloaderSuccess);
|
|
2082
|
-
return false;
|
|
2083
|
-
}
|
|
2084
|
-
|
|
2137
|
+
private async rebootProtocolV2ToBootloader() {
|
|
2085
2138
|
try {
|
|
2086
2139
|
this.postTipMessage(FirmwareUpdateTipMessage.AutoRebootToBootloader);
|
|
2087
2140
|
await this.protocolV2Reboot(DeviceRebootType.Bootloader);
|
|
@@ -2099,6 +2152,24 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2099
2152
|
}
|
|
2100
2153
|
}
|
|
2101
2154
|
|
|
2155
|
+
async enterProtocolV2BootloaderMode() {
|
|
2156
|
+
// romloader is the first update environment and forwards targets to bootloader.
|
|
2157
|
+
// It rejects DeviceRebootType.Bootloader, so reuse the current connection.
|
|
2158
|
+
if (this.isProtocolV2RomloaderMode()) {
|
|
2159
|
+
Log.debug('Protocol V2 device is in romloader mode; start firmware update directly');
|
|
2160
|
+
this.protocolV2ExecutionInLoader = true;
|
|
2161
|
+
return false;
|
|
2162
|
+
}
|
|
2163
|
+
if (this.isProtocolV2BootloaderMode()) {
|
|
2164
|
+
Log.debug('Protocol V2 device is already in bootloader mode, skip reboot');
|
|
2165
|
+
this.protocolV2ExecutionInLoader = true;
|
|
2166
|
+
this.postTipMessage(FirmwareUpdateTipMessage.GoToBootloaderSuccess);
|
|
2167
|
+
return false;
|
|
2168
|
+
}
|
|
2169
|
+
|
|
2170
|
+
return this.rebootProtocolV2ToBootloader();
|
|
2171
|
+
}
|
|
2172
|
+
|
|
2102
2173
|
private async waitForProtocolV2BootloaderMode(
|
|
2103
2174
|
timeout = PROTOCOL_V2_BOOTLOADER_RECONNECT_TIMEOUT,
|
|
2104
2175
|
retryInterval = 1000
|
|
@@ -2183,68 +2254,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2183
2254
|
return entries;
|
|
2184
2255
|
}
|
|
2185
2256
|
|
|
2186
|
-
private
|
|
2187
|
-
installSources,
|
|
2188
|
-
resourceSources,
|
|
2189
|
-
}: {
|
|
2190
|
-
installSources: ProtocolV2InstallSource[];
|
|
2191
|
-
resourceSources: ProtocolV2ResourceBundleSource[];
|
|
2192
|
-
}): ProtocolV2ExecutionPhase[] {
|
|
2193
|
-
const phases: ProtocolV2ExecutionPhase[] = [];
|
|
2194
|
-
const bootResourceSources = resourceSources.filter(source =>
|
|
2195
|
-
isProtocolV2BootResourcePackagePath(source.devicePath)
|
|
2196
|
-
);
|
|
2197
|
-
const remainingResourceSources = resourceSources.filter(
|
|
2198
|
-
source => !isProtocolV2BootResourcePackagePath(source.devicePath)
|
|
2199
|
-
);
|
|
2200
|
-
// A previous interrupted run may have left a promotable staging file. Replace
|
|
2201
|
-
// it with the current approved package before any firmware-triggered reboot.
|
|
2202
|
-
if (bootResourceSources.length > 0) {
|
|
2203
|
-
phases.push({
|
|
2204
|
-
kind: 'resource-sync',
|
|
2205
|
-
installSources: [],
|
|
2206
|
-
resourceSources: bootResourceSources,
|
|
2207
|
-
});
|
|
2208
|
-
}
|
|
2209
|
-
const bootloaderSources = installSources.filter(source => source.kind === 'bootloader');
|
|
2210
|
-
if (bootloaderSources.length > 0) {
|
|
2211
|
-
phases.push(
|
|
2212
|
-
{
|
|
2213
|
-
kind: 'bootloader-install',
|
|
2214
|
-
installSources: bootloaderSources,
|
|
2215
|
-
resourceSources: [],
|
|
2216
|
-
},
|
|
2217
|
-
{
|
|
2218
|
-
kind: 'bootloader-verify',
|
|
2219
|
-
installSources: [],
|
|
2220
|
-
resourceSources: [],
|
|
2221
|
-
}
|
|
2222
|
-
);
|
|
2223
|
-
}
|
|
2224
|
-
if (remainingResourceSources.length > 0) {
|
|
2225
|
-
phases.push({
|
|
2226
|
-
kind: 'resource-sync',
|
|
2227
|
-
installSources: [],
|
|
2228
|
-
resourceSources: remainingResourceSources,
|
|
2229
|
-
});
|
|
2230
|
-
}
|
|
2231
|
-
const componentSources = installSources.filter(source => source.kind !== 'bootloader');
|
|
2232
|
-
if (componentSources.length > 0) {
|
|
2233
|
-
phases.push({
|
|
2234
|
-
kind: 'component-install',
|
|
2235
|
-
installSources: componentSources,
|
|
2236
|
-
resourceSources: [],
|
|
2237
|
-
});
|
|
2238
|
-
}
|
|
2239
|
-
phases.push({
|
|
2240
|
-
kind: 'final-verify',
|
|
2241
|
-
installSources: [],
|
|
2242
|
-
resourceSources: [],
|
|
2243
|
-
});
|
|
2244
|
-
return phases;
|
|
2245
|
-
}
|
|
2246
|
-
|
|
2247
|
-
private async executeProtocolV2Phases({
|
|
2257
|
+
private async executeProtocolV2SourceUpdate({
|
|
2248
2258
|
installSources,
|
|
2249
2259
|
resourceSources,
|
|
2250
2260
|
}: {
|
|
@@ -2252,38 +2262,22 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2252
2262
|
resourceSources: ProtocolV2ResourceBundleSource[];
|
|
2253
2263
|
}) {
|
|
2254
2264
|
this.protocolV2BootResourceStagingSafe = false;
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
) {
|
|
2270
|
-
await this.ensureProtocolV2BootResourceStagingIsEmpty();
|
|
2271
|
-
}
|
|
2272
|
-
await this.executeProtocolV2TransferPhase(phase);
|
|
2273
|
-
} else if (phase.kind === 'bootloader-install' || phase.kind === 'component-install') {
|
|
2274
|
-
await this.enterProtocolV2BootloaderMode();
|
|
2275
|
-
await this.ensureProtocolV2BootResourceStagingIsEmpty();
|
|
2276
|
-
await this.executeProtocolV2TransferPhase(phase);
|
|
2277
|
-
await this.exitProtocolV2BootloaderToNormal();
|
|
2278
|
-
} else if (phase.kind === 'bootloader-verify') {
|
|
2279
|
-
await this.waitForProtocolV2FinalFeatures();
|
|
2280
|
-
this.assertExpectedProtocolV2Versions(['boot']);
|
|
2281
|
-
}
|
|
2265
|
+
if (installSources.length > 0 || resourceSources.length > 0) {
|
|
2266
|
+
await this.enterProtocolV2BootloaderMode();
|
|
2267
|
+
// Clear stale boot-resource staging before writing any artifacts. The new
|
|
2268
|
+
// boot resource, resources and firmware then share one transfer session,
|
|
2269
|
+
// one global progress range and one multi-target install request.
|
|
2270
|
+
// Preserve the approved source order instead of synthesizing a boot-first
|
|
2271
|
+
// phase. Loader firmware owns the cross-stage handoff: bootloader runs its
|
|
2272
|
+
// component targets and leaves boot pending for romloader, while pending
|
|
2273
|
+
// runner-backed records always resume before the application boots.
|
|
2274
|
+
await this.ensureProtocolV2BootResourceStagingIsEmpty();
|
|
2275
|
+
await this.executeProtocolV2TransferPhase({
|
|
2276
|
+
installSources,
|
|
2277
|
+
resourceSources,
|
|
2278
|
+
});
|
|
2282
2279
|
}
|
|
2283
|
-
|
|
2284
|
-
HardwareErrorCode.RuntimeError,
|
|
2285
|
-
'Protocol V2 execution has no final verification phase'
|
|
2286
|
-
);
|
|
2280
|
+
return this.completeProtocolV2FinalVerification();
|
|
2287
2281
|
}
|
|
2288
2282
|
|
|
2289
2283
|
private async ensureProtocolV2BootResourceStagingIsEmpty() {
|
|
@@ -2315,19 +2309,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2315
2309
|
this.protocolV2BootResourceStagingSafe = true;
|
|
2316
2310
|
}
|
|
2317
2311
|
|
|
2318
|
-
private async executeProtocolV2SourceUpdate({
|
|
2319
|
-
installSources,
|
|
2320
|
-
resourceSources,
|
|
2321
|
-
}: {
|
|
2322
|
-
installSources: ProtocolV2InstallSource[];
|
|
2323
|
-
resourceSources: ProtocolV2ResourceBundleSource[];
|
|
2324
|
-
}) {
|
|
2325
|
-
return this.executeProtocolV2Phases({
|
|
2326
|
-
installSources,
|
|
2327
|
-
resourceSources,
|
|
2328
|
-
});
|
|
2329
|
-
}
|
|
2330
|
-
|
|
2331
2312
|
private async executeProtocolV2Update({
|
|
2332
2313
|
fwBinaryMap,
|
|
2333
2314
|
bootloaderBinary,
|
|
@@ -2352,7 +2333,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2352
2333
|
kind: item.kind,
|
|
2353
2334
|
}))
|
|
2354
2335
|
);
|
|
2355
|
-
return await this.
|
|
2336
|
+
return await this.executeProtocolV2SourceUpdate({
|
|
2356
2337
|
installSources,
|
|
2357
2338
|
resourceSources: [],
|
|
2358
2339
|
});
|
|
@@ -2364,7 +2345,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2364
2345
|
private async executeProtocolV2TransferPhase({
|
|
2365
2346
|
installSources,
|
|
2366
2347
|
resourceSources,
|
|
2367
|
-
}:
|
|
2348
|
+
}: ProtocolV2TransferBatch) {
|
|
2368
2349
|
let totalSize = installSources.reduce((total, item) => total + item.source.size, 0);
|
|
2369
2350
|
const resourcesToSync: ProtocolV2ResourceBundleSource[] = [];
|
|
2370
2351
|
for (const resource of resourceSources) {
|
|
@@ -2637,6 +2618,47 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2637
2618
|
return Array.from(expectedTargetIds).filter(targetId => !reportedTargetIds.has(targetId));
|
|
2638
2619
|
}
|
|
2639
2620
|
|
|
2621
|
+
private getProtocolV2ObservableTargetVersions(features: Features) {
|
|
2622
|
+
const versions = new Map<number, string>();
|
|
2623
|
+
versions.set(
|
|
2624
|
+
ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_BOOTLOADER,
|
|
2625
|
+
getDeviceBootloaderVersion(features).join('.')
|
|
2626
|
+
);
|
|
2627
|
+
const applicationVersion = getDeviceFirmwareVersion(features).join('.');
|
|
2628
|
+
versions.set(ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_APPLICATION_P1, applicationVersion);
|
|
2629
|
+
versions.set(ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_APPLICATION_P2, applicationVersion);
|
|
2630
|
+
versions.set(
|
|
2631
|
+
ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_COPROCESSOR,
|
|
2632
|
+
getDeviceBLEFirmwareVersion(features).join('.')
|
|
2633
|
+
);
|
|
2634
|
+
const secureElementVersions: Array<[number, string | null | undefined]> = [
|
|
2635
|
+
[ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_SE01, features.se01Version],
|
|
2636
|
+
[ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_SE02, features.se02Version],
|
|
2637
|
+
[ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_SE03, features.se03Version],
|
|
2638
|
+
[ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_SE04, features.se04Version],
|
|
2639
|
+
];
|
|
2640
|
+
secureElementVersions.forEach(([targetId, version]) => {
|
|
2641
|
+
if (version) versions.set(targetId, version);
|
|
2642
|
+
});
|
|
2643
|
+
return versions;
|
|
2644
|
+
}
|
|
2645
|
+
|
|
2646
|
+
private hasProtocolV2InstallVersionChanged(expectedTargetIds: Set<number>) {
|
|
2647
|
+
if (!this.protocolV2LastRuntimeProbeFeatures) return false;
|
|
2648
|
+
const currentVersions = this.getProtocolV2ObservableTargetVersions(
|
|
2649
|
+
this.protocolV2LastRuntimeProbeFeatures
|
|
2650
|
+
);
|
|
2651
|
+
return Array.from(expectedTargetIds).some(targetId => {
|
|
2652
|
+
const previousVersion = this.protocolV2InstallBaselineVersions.get(targetId);
|
|
2653
|
+
const currentVersion = currentVersions.get(targetId);
|
|
2654
|
+
return (
|
|
2655
|
+
previousVersion !== undefined &&
|
|
2656
|
+
currentVersion !== undefined &&
|
|
2657
|
+
previousVersion !== currentVersion
|
|
2658
|
+
);
|
|
2659
|
+
});
|
|
2660
|
+
}
|
|
2661
|
+
|
|
2640
2662
|
private async waitForProtocolV2FirmwareUpdateComplete(
|
|
2641
2663
|
targets: Array<{ target_id: number; path: string }>
|
|
2642
2664
|
) {
|
|
@@ -2649,12 +2671,17 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2649
2671
|
let deviceInfo: ProtocolV2DeviceInfo | undefined;
|
|
2650
2672
|
let missingTargetStatusSince: number | undefined;
|
|
2651
2673
|
let missingTargetStatusKey: string | undefined;
|
|
2674
|
+
let normalModeWithoutInstallEvidenceSince: number | undefined;
|
|
2675
|
+
let installEvidenceObserved = false;
|
|
2652
2676
|
const resetMissingTargetStatusGrace = () => {
|
|
2653
2677
|
missingTargetStatusSince = undefined;
|
|
2654
2678
|
missingTargetStatusKey = undefined;
|
|
2655
2679
|
};
|
|
2656
2680
|
|
|
2657
2681
|
while (Date.now() - startTime < PROTOCOL_V2_INSTALL_TIMEOUT) {
|
|
2682
|
+
// A transport release caused by an explicit workflow cancellation must not
|
|
2683
|
+
// be mistaken for the expected device reboot during installation.
|
|
2684
|
+
this.throwIfAborted();
|
|
2658
2685
|
try {
|
|
2659
2686
|
if (shouldReconnect) {
|
|
2660
2687
|
await this.reconnectProtocolV2Device();
|
|
@@ -2665,7 +2692,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2665
2692
|
try {
|
|
2666
2693
|
const statusResponse = await this.device.getCommands().typedCall(
|
|
2667
2694
|
'DeviceFirmwareUpdateStatusGet',
|
|
2668
|
-
'DeviceFirmwareUpdateStatus',
|
|
2695
|
+
['DeviceFirmwareUpdateStatus', 'Success'],
|
|
2669
2696
|
{
|
|
2670
2697
|
fields: {
|
|
2671
2698
|
status: true,
|
|
@@ -2675,23 +2702,40 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2675
2702
|
},
|
|
2676
2703
|
{ timeoutMs: PROTOCOL_V2_FIRMWARE_STATUS_RESPONSE_TIMEOUT }
|
|
2677
2704
|
);
|
|
2705
|
+
if (statusResponse.type === 'Success') {
|
|
2706
|
+
this.protocolV2FinalStatusVerified = true;
|
|
2707
|
+
this.postProgressMessage(100, 'installingFirmware');
|
|
2708
|
+
return;
|
|
2709
|
+
}
|
|
2678
2710
|
const statusTargets = (statusResponse.message.records ??
|
|
2679
2711
|
[]) as ProtocolV2FirmwareUpdateStatusTarget[];
|
|
2712
|
+
if (
|
|
2713
|
+
statusTargets.some(target => {
|
|
2714
|
+
const targetId = normalizeProtocolV2TargetId(target.target_id);
|
|
2715
|
+
return targetId !== undefined && expectedTargetIds.has(targetId);
|
|
2716
|
+
})
|
|
2717
|
+
) {
|
|
2718
|
+
installEvidenceObserved = true;
|
|
2719
|
+
normalModeWithoutInstallEvidenceSince = undefined;
|
|
2720
|
+
}
|
|
2680
2721
|
if (this.assertProtocolV2TargetStatus(statusTargets, expectedTargetIds, expectedPaths)) {
|
|
2681
2722
|
this.protocolV2FinalStatusVerified = true;
|
|
2682
2723
|
return;
|
|
2683
2724
|
}
|
|
2684
2725
|
|
|
2685
|
-
if (
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2726
|
+
if (statusTargets.length === 0 && currentDeviceInfo) {
|
|
2727
|
+
const isNormalMode = await this.probeProtocolV2NormalMode(currentDeviceInfo);
|
|
2728
|
+
if (
|
|
2729
|
+
isNormalMode &&
|
|
2730
|
+
(installEvidenceObserved ||
|
|
2731
|
+
this.hasProtocolV2InstallVersionChanged(expectedTargetIds))
|
|
2732
|
+
) {
|
|
2733
|
+
Log.log(
|
|
2734
|
+
'[FirmwareUpdateV4] empty firmware status after confirmed App reboot; update complete'
|
|
2735
|
+
);
|
|
2736
|
+
this.postProgressMessage(100, 'installingFirmware');
|
|
2737
|
+
return;
|
|
2738
|
+
}
|
|
2695
2739
|
}
|
|
2696
2740
|
|
|
2697
2741
|
const missingTargetIds = this.getProtocolV2MissingTargetIds(
|
|
@@ -2741,16 +2785,39 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2741
2785
|
'Protocol V2 device identity is unavailable during install polling'
|
|
2742
2786
|
);
|
|
2743
2787
|
}
|
|
2744
|
-
|
|
2788
|
+
const isNormalMode = await this.probeProtocolV2NormalMode(currentDeviceInfo);
|
|
2789
|
+
if (
|
|
2790
|
+
isNormalMode &&
|
|
2791
|
+
(installEvidenceObserved ||
|
|
2792
|
+
this.hasProtocolV2InstallVersionChanged(expectedTargetIds))
|
|
2793
|
+
) {
|
|
2745
2794
|
Log.log(
|
|
2746
2795
|
'[FirmwareUpdateV4] firmware status endpoint unavailable after confirmed App reboot'
|
|
2747
2796
|
);
|
|
2748
2797
|
this.postProgressMessage(100, 'installingFirmware');
|
|
2749
2798
|
return;
|
|
2750
2799
|
}
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2800
|
+
if (isNormalMode) {
|
|
2801
|
+
const now = Date.now();
|
|
2802
|
+
normalModeWithoutInstallEvidenceSince ??= now;
|
|
2803
|
+
if (
|
|
2804
|
+
now - normalModeWithoutInstallEvidenceSince >=
|
|
2805
|
+
PROTOCOL_V2_MISSING_TARGET_STATUS_GRACE_TIMEOUT
|
|
2806
|
+
) {
|
|
2807
|
+
throw ERRORS.TypedError(
|
|
2808
|
+
HardwareErrorCode.FirmwareError,
|
|
2809
|
+
'Protocol V2 device returned to normal mode without install ACK, target status, or version change'
|
|
2810
|
+
);
|
|
2811
|
+
}
|
|
2812
|
+
lastError = new Error(
|
|
2813
|
+
'Protocol V2 device is in normal mode but installation is not yet confirmed'
|
|
2814
|
+
);
|
|
2815
|
+
} else {
|
|
2816
|
+
normalModeWithoutInstallEvidenceSince = undefined;
|
|
2817
|
+
lastError = new Error(
|
|
2818
|
+
'Protocol V2 firmware status endpoint is unavailable while the device remains in loader mode'
|
|
2819
|
+
);
|
|
2820
|
+
}
|
|
2754
2821
|
} else {
|
|
2755
2822
|
shouldReconnect = true;
|
|
2756
2823
|
deviceInfo = undefined;
|
|
@@ -2811,6 +2878,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2811
2878
|
deviceInfo,
|
|
2812
2879
|
PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT
|
|
2813
2880
|
);
|
|
2881
|
+
this.protocolV2LastRuntimeProbeFeatures = features;
|
|
2814
2882
|
return features.mode === 'normal' && !features.bootloaderMode;
|
|
2815
2883
|
}
|
|
2816
2884
|
|
|
@@ -3043,18 +3111,12 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
3043
3111
|
}: {
|
|
3044
3112
|
targets: Array<{ target_id: number; path: string }>;
|
|
3045
3113
|
}) {
|
|
3114
|
+
this.protocolV2LastRuntimeProbeFeatures = undefined;
|
|
3046
3115
|
const commands = this.device.getCommands();
|
|
3047
|
-
|
|
3048
|
-
|
|
3049
|
-
'Success',
|
|
3050
|
-
{ targets },
|
|
3051
|
-
{ timeoutMs: PROTOCOL_V2_START_UPDATE_TIMEOUT }
|
|
3052
|
-
);
|
|
3053
|
-
// Success acknowledges that the device accepted installation. End confirmation
|
|
3054
|
-
// and begin status polling only after receiving this ACK.
|
|
3116
|
+
await commands.typedCall('DeviceFirmwareUpdateStage', 'Success', { targets });
|
|
3117
|
+
await commands.call('DeviceFirmwareUpdateRequest', {}, { returnAfterWrite: true });
|
|
3055
3118
|
this.postTipMessage(FirmwareUpdateTipMessage.FirmwareUpdating);
|
|
3056
3119
|
this.postProgressMessage(0, 'installingFirmware');
|
|
3057
|
-
return response;
|
|
3058
3120
|
}
|
|
3059
3121
|
|
|
3060
3122
|
private async protocolV2Reboot(rebootType: DeviceRebootType) {
|