@onekeyfe/hd-core 1.2.0-alpha.105 → 1.2.0-alpha.106
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 +90 -26
- package/__tests__/check-all-firmware-release-protocol-v2.test.ts +0 -2
- package/__tests__/device-pool-state.test.ts +1 -5
- package/__tests__/deviceSettings.test.ts +0 -1
- package/__tests__/firmware-update/firmware-update-v4-install-poll.test.ts +145 -0
- package/__tests__/logBlockEvent.test.ts +13 -1
- package/__tests__/protocol-v2.test.ts +447 -122
- package/__tests__/refresh-device-state.test.ts +0 -1
- package/__tests__/search-devices.test.ts +0 -2
- package/dist/api/FirmwareUpdateV4.d.ts +7 -3
- package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
- package/dist/api/GetDeviceState.d.ts.map +1 -1
- package/dist/api/SearchDevices.d.ts.map +1 -1
- package/dist/api/firmware/protocolV2Release.d.ts.map +1 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/device/Device.d.ts +0 -3
- package/dist/device/Device.d.ts.map +1 -1
- package/dist/device/DeviceCommands.d.ts.map +1 -1
- package/dist/device/DevicePool.d.ts +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 +0 -6
- package/dist/index.js +251 -184
- package/dist/protocols/protocol-v2/features.d.ts +1 -1
- package/dist/protocols/protocol-v2/features.d.ts.map +1 -1
- package/dist/types/api/getDeviceState.d.ts +0 -1
- package/dist/types/api/getDeviceState.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/api/FirmwareUpdateV4.ts +262 -172
- package/src/api/GetDeviceState.ts +0 -1
- package/src/api/SearchDevices.ts +0 -2
- package/src/api/firmware/protocolV2Release.ts +0 -1
- package/src/core/index.ts +6 -6
- package/src/device/Device.ts +11 -48
- package/src/device/DeviceCommands.ts +4 -14
- package/src/device/DevicePool.ts +3 -11
- package/src/events/logBlockEvent.ts +14 -1
- package/src/protocols/protocol-v2/features.ts +9 -2
- package/src/types/api/getDeviceState.ts +0 -2
- package/src/utils/deviceSettings.ts +1 -1
- package/__tests__/protocol-v2-legacy-recovery.test.ts +0 -29
- package/dist/core/protocolV2LegacyRecovery.d.ts +0 -5
- package/dist/core/protocolV2LegacyRecovery.d.ts.map +0 -1
- package/src/core/protocolV2LegacyRecovery.ts +0 -12
|
@@ -244,15 +244,7 @@ type JSZipSizedEntry = JSZip.JSZipObject & {
|
|
|
244
244
|
};
|
|
245
245
|
};
|
|
246
246
|
|
|
247
|
-
type
|
|
248
|
-
| 'resource-sync'
|
|
249
|
-
| 'bootloader-install'
|
|
250
|
-
| 'bootloader-verify'
|
|
251
|
-
| 'component-install'
|
|
252
|
-
| 'final-verify';
|
|
253
|
-
|
|
254
|
-
type ProtocolV2ExecutionPhase = {
|
|
255
|
-
kind: ProtocolV2ExecutionPhaseKind;
|
|
247
|
+
type ProtocolV2TransferBatch = {
|
|
256
248
|
installSources: ProtocolV2InstallSource[];
|
|
257
249
|
resourceSources: ProtocolV2ResourceBundleSource[];
|
|
258
250
|
};
|
|
@@ -385,6 +377,19 @@ const isProtocolV2ReconnectProbeError = (error: unknown) => {
|
|
|
385
377
|
);
|
|
386
378
|
};
|
|
387
379
|
|
|
380
|
+
const isProtocolV2BleTransportReleasedError = (error: unknown) => {
|
|
381
|
+
const message = getProtocolV2UnknownErrorText(error).toLowerCase();
|
|
382
|
+
const compactMessage = message.replace(/\s+/gu, '');
|
|
383
|
+
return (
|
|
384
|
+
message.includes('react native ble transport released') ||
|
|
385
|
+
message.includes('bledevicedisconnected') ||
|
|
386
|
+
message.includes('bleconnectederror') ||
|
|
387
|
+
(compactMessage.includes('rxerrorerror6') &&
|
|
388
|
+
(compactMessage.includes('multiplatformbleadapter') ||
|
|
389
|
+
compactMessage.includes('multipalformebleadapter')))
|
|
390
|
+
);
|
|
391
|
+
};
|
|
392
|
+
|
|
388
393
|
const isProtocolV2FirmwareStatusEndpointUnavailable = (error: unknown) => {
|
|
389
394
|
const message = getProtocolV2UnknownErrorText(error).toLowerCase();
|
|
390
395
|
return (
|
|
@@ -605,6 +610,12 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
605
610
|
|
|
606
611
|
private protocolV2FinalStatusVerified = false;
|
|
607
612
|
|
|
613
|
+
private protocolV2InstallAckReceived = false;
|
|
614
|
+
|
|
615
|
+
private protocolV2InstallBaselineVersions = new Map<number, string>();
|
|
616
|
+
|
|
617
|
+
private protocolV2LastRuntimeProbeFeatures?: Features;
|
|
618
|
+
|
|
608
619
|
init() {
|
|
609
620
|
this.allowDeviceMode = [UI_REQUEST.BOOTLOADER, UI_REQUEST.NOT_INITIALIZE];
|
|
610
621
|
this.requireDeviceMode = [];
|
|
@@ -823,6 +834,9 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
823
834
|
private async runProtocolV2() {
|
|
824
835
|
await this.captureProtocolV2PhysicalIdentity();
|
|
825
836
|
const deviceFeatures = await this.getProtocolV2DeviceFeatures();
|
|
837
|
+
this.protocolV2InstallBaselineVersions =
|
|
838
|
+
this.getProtocolV2ObservableTargetVersions(deviceFeatures);
|
|
839
|
+
this.protocolV2LastRuntimeProbeFeatures = undefined;
|
|
826
840
|
const currentDeviceType = this.device.getCurrentDeviceType();
|
|
827
841
|
const capabilityDeviceType =
|
|
828
842
|
currentDeviceType === EDeviceType.Pro2 || currentDeviceType === EDeviceType.Neo
|
|
@@ -836,6 +850,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
836
850
|
const deviceFirmwareType = getFirmwareType(deviceFeatures);
|
|
837
851
|
const firmwareType = this.params.firmwareType ?? deviceFirmwareType;
|
|
838
852
|
this.validateExpectedTargetVersions();
|
|
853
|
+
const wantsResources = !!this.params.targetsToUpdate?.includes('resource');
|
|
839
854
|
|
|
840
855
|
if (
|
|
841
856
|
!this.params.preparedPlan &&
|
|
@@ -859,11 +874,11 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
859
874
|
if (this.params.preparedPlan || hasPreparedComponentArtifacts) {
|
|
860
875
|
return this.runProtocolV2PreparedArtifacts(deviceFeatures, firmwareType);
|
|
861
876
|
}
|
|
862
|
-
const wantsResources = !!this.params.targetsToUpdate?.includes('resource');
|
|
863
877
|
|
|
864
878
|
let fwBinaryMap: ProtocolV2TargetBinary[] = [];
|
|
865
879
|
let bootloaderBinary: ArrayBuffer | null = null;
|
|
866
880
|
let installItems: ProtocolV2InstallItem[] | undefined;
|
|
881
|
+
let resourceMemoryHost: FirmwareUpdateV4MemoryHost | undefined;
|
|
867
882
|
try {
|
|
868
883
|
this.postTipMessage(FirmwareUpdateTipMessage.StartDownloadFirmware);
|
|
869
884
|
fwBinaryMap = this.collectExplicitTargetBinaries();
|
|
@@ -876,17 +891,9 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
876
891
|
const needsRemoteFirmware = this.params.targetsToUpdate?.length
|
|
877
892
|
? missingFirmwareTargets.length > 0
|
|
878
893
|
: explicitInstallItems.length === 0;
|
|
879
|
-
|
|
880
|
-
throw ERRORS.TypedError(
|
|
881
|
-
HardwareErrorCode.RuntimeError,
|
|
882
|
-
'Protocol V2 resource archive must be provided through a local or external PreparedPlan',
|
|
883
|
-
{
|
|
884
|
-
firmwareUpdateCode: 'FirmwareArtifactsNotPrepared',
|
|
885
|
-
}
|
|
886
|
-
);
|
|
887
|
-
}
|
|
894
|
+
const needsSdkManagedArtifacts = needsRemoteFirmware || wantsResources;
|
|
888
895
|
if (
|
|
889
|
-
|
|
896
|
+
needsSdkManagedArtifacts &&
|
|
890
897
|
(this.params.artifactReader ||
|
|
891
898
|
DataManager.getSettings('firmwareManifestMode') === 'external-only')
|
|
892
899
|
) {
|
|
@@ -898,9 +905,13 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
898
905
|
}
|
|
899
906
|
);
|
|
900
907
|
}
|
|
901
|
-
if (
|
|
908
|
+
if (needsSdkManagedArtifacts) {
|
|
902
909
|
// Remote updates must use a freshly fetched config before any reboot or file write.
|
|
903
|
-
await DataManager.forceReloadData(
|
|
910
|
+
await DataManager.forceReloadData({
|
|
911
|
+
requireResources: wantsResources,
|
|
912
|
+
resourceDeviceType:
|
|
913
|
+
capabilityDeviceType === EDeviceType.Neo ? EDeviceType.Neo : EDeviceType.Pro2,
|
|
914
|
+
});
|
|
904
915
|
}
|
|
905
916
|
if (needsRemoteFirmware) {
|
|
906
917
|
const remoteBinaries = await this.prepareRemoteProtocolV2Binaries(
|
|
@@ -923,8 +934,19 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
923
934
|
targetId: item.targetId,
|
|
924
935
|
}));
|
|
925
936
|
}
|
|
937
|
+
if (wantsResources) {
|
|
938
|
+
this.params.resourceArchiveBinary = await this.downloadRemoteProtocolV2ResourceArchive(
|
|
939
|
+
deviceFeatures
|
|
940
|
+
);
|
|
941
|
+
resourceMemoryHost = await this.prepareProtocolV2LocalMemoryHost({
|
|
942
|
+
features: deviceFeatures,
|
|
943
|
+
firmwareType,
|
|
944
|
+
availableInstallItems: installItems ?? explicitInstallItems,
|
|
945
|
+
});
|
|
946
|
+
}
|
|
926
947
|
this.postTipMessage(FirmwareUpdateTipMessage.FinishDownloadFirmware);
|
|
927
948
|
} catch (err) {
|
|
949
|
+
resourceMemoryHost?.release();
|
|
928
950
|
if (
|
|
929
951
|
typeof err === 'object' &&
|
|
930
952
|
err !== null &&
|
|
@@ -939,6 +961,14 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
939
961
|
throw normalizeFirmwarePreparationError(err);
|
|
940
962
|
}
|
|
941
963
|
|
|
964
|
+
if (resourceMemoryHost) {
|
|
965
|
+
try {
|
|
966
|
+
return await this.runProtocolV2PreparedArtifacts(deviceFeatures, firmwareType, false);
|
|
967
|
+
} finally {
|
|
968
|
+
resourceMemoryHost.release();
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
|
|
942
972
|
if (!bootloaderBinary && fwBinaryMap.length === 0 && !installItems?.length) {
|
|
943
973
|
throw ERRORS.TypedError(
|
|
944
974
|
HardwareErrorCode.FirmwareUpdateDownloadFailed,
|
|
@@ -1092,14 +1122,15 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1092
1122
|
private async prepareProtocolV2LocalMemoryHost({
|
|
1093
1123
|
features,
|
|
1094
1124
|
firmwareType,
|
|
1125
|
+
availableInstallItems = this.buildProtocolV2InstallItems({
|
|
1126
|
+
bootloaderBinary: this.prepareBootloaderBinary(),
|
|
1127
|
+
fwBinaryMap: this.collectExplicitTargetBinaries(),
|
|
1128
|
+
}),
|
|
1095
1129
|
}: {
|
|
1096
1130
|
features: Features;
|
|
1097
1131
|
firmwareType: EFirmwareType;
|
|
1132
|
+
availableInstallItems?: ProtocolV2InstallItem[];
|
|
1098
1133
|
}): Promise<FirmwareUpdateV4MemoryHost> {
|
|
1099
|
-
const availableInstallItems = this.buildProtocolV2InstallItems({
|
|
1100
|
-
bootloaderBinary: this.prepareBootloaderBinary(),
|
|
1101
|
-
fwBinaryMap: this.collectExplicitTargetBinaries(),
|
|
1102
|
-
});
|
|
1103
1134
|
const requestedComponentTargets = new Set(
|
|
1104
1135
|
(this.params.targetsToUpdate ?? []).filter(
|
|
1105
1136
|
(target): target is Exclude<FirmwareUpdateV4Target, 'resource' | 'boot_resources'> =>
|
|
@@ -1527,9 +1558,15 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1527
1558
|
return sources;
|
|
1528
1559
|
}
|
|
1529
1560
|
|
|
1530
|
-
private async runProtocolV2PreparedArtifacts(
|
|
1561
|
+
private async runProtocolV2PreparedArtifacts(
|
|
1562
|
+
features: Features,
|
|
1563
|
+
firmwareType: EFirmwareType,
|
|
1564
|
+
announceDownload = true
|
|
1565
|
+
) {
|
|
1531
1566
|
try {
|
|
1532
|
-
|
|
1567
|
+
if (announceDownload) {
|
|
1568
|
+
this.postTipMessage(FirmwareUpdateTipMessage.StartDownloadFirmware);
|
|
1569
|
+
}
|
|
1533
1570
|
const installSources = await this.prepareProtocolV2InstallSources(firmwareType, features);
|
|
1534
1571
|
const resourceSources = await this.prepareProtocolV2ResourceSources();
|
|
1535
1572
|
if (installSources.length === 0 && resourceSources.length === 0) {
|
|
@@ -1538,7 +1575,9 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1538
1575
|
'No firmware to update'
|
|
1539
1576
|
);
|
|
1540
1577
|
}
|
|
1541
|
-
|
|
1578
|
+
if (announceDownload) {
|
|
1579
|
+
this.postTipMessage(FirmwareUpdateTipMessage.FinishDownloadFirmware);
|
|
1580
|
+
}
|
|
1542
1581
|
|
|
1543
1582
|
return await this.executeProtocolV2SourceUpdate({
|
|
1544
1583
|
installSources,
|
|
@@ -1892,6 +1931,44 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1892
1931
|
};
|
|
1893
1932
|
}
|
|
1894
1933
|
|
|
1934
|
+
private async downloadRemoteProtocolV2ResourceArchive(features: Features): Promise<ArrayBuffer> {
|
|
1935
|
+
const deviceType = getDeviceType(features);
|
|
1936
|
+
if (deviceType !== EDeviceType.Pro2 && deviceType !== EDeviceType.Neo) {
|
|
1937
|
+
throw ERRORS.TypedError(
|
|
1938
|
+
HardwareErrorCode.RuntimeError,
|
|
1939
|
+
'Protocol V2 resource archive requires a Pro2 or Neo device'
|
|
1940
|
+
);
|
|
1941
|
+
}
|
|
1942
|
+
const source = DataManager.getProtocolV2ResourceSource(deviceType);
|
|
1943
|
+
const expectedSha256 = normalizeProtocolV2Hex(source?.archiveSha256);
|
|
1944
|
+
if (
|
|
1945
|
+
!source?.archiveUrl ||
|
|
1946
|
+
!Number.isSafeInteger(source.archiveSize) ||
|
|
1947
|
+
source.archiveSize <= 0 ||
|
|
1948
|
+
source.archiveSize > PROTOCOL_V2_RESOURCE_TOTAL_MAX_BYTES ||
|
|
1949
|
+
!expectedSha256 ||
|
|
1950
|
+
!/^[0-9a-f]{64}$/u.test(expectedSha256)
|
|
1951
|
+
) {
|
|
1952
|
+
throw ERRORS.TypedError(
|
|
1953
|
+
HardwareErrorCode.RuntimeError,
|
|
1954
|
+
'Protocol V2 resource archive integrity metadata is invalid',
|
|
1955
|
+
{ firmwareUpdateCode: 'FirmwarePlanInvalid' }
|
|
1956
|
+
);
|
|
1957
|
+
}
|
|
1958
|
+
const { binary } = await getSysResourceBinary(source.archiveUrl);
|
|
1959
|
+
if (
|
|
1960
|
+
binary.byteLength !== source.archiveSize ||
|
|
1961
|
+
bytesToHex(sha256(new Uint8Array(binary))) !== expectedSha256
|
|
1962
|
+
) {
|
|
1963
|
+
throw ERRORS.TypedError(
|
|
1964
|
+
HardwareErrorCode.RuntimeError,
|
|
1965
|
+
'Protocol V2 resource archive does not match the remote config',
|
|
1966
|
+
{ firmwareUpdateCode: 'FirmwareArtifactReceiptMismatch' }
|
|
1967
|
+
);
|
|
1968
|
+
}
|
|
1969
|
+
return binary;
|
|
1970
|
+
}
|
|
1971
|
+
|
|
1895
1972
|
private async prepareRemoteProtocolV2Binaries(
|
|
1896
1973
|
firmwareType: EFirmwareType,
|
|
1897
1974
|
features: Features,
|
|
@@ -2142,8 +2219,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2142
2219
|
this.assertProtocolV2DeviceInfoIdentity(deviceInfo);
|
|
2143
2220
|
const features = await this.device.probeProtocolV2RuntimeState(
|
|
2144
2221
|
deviceInfo,
|
|
2145
|
-
PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT
|
|
2146
|
-
{ allowLegacyProtocolV2ProtocolInfo: true }
|
|
2222
|
+
PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT
|
|
2147
2223
|
);
|
|
2148
2224
|
if (features?.mode === 'bootloader') {
|
|
2149
2225
|
return features;
|
|
@@ -2206,68 +2282,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2206
2282
|
return entries;
|
|
2207
2283
|
}
|
|
2208
2284
|
|
|
2209
|
-
private
|
|
2210
|
-
installSources,
|
|
2211
|
-
resourceSources,
|
|
2212
|
-
}: {
|
|
2213
|
-
installSources: ProtocolV2InstallSource[];
|
|
2214
|
-
resourceSources: ProtocolV2ResourceBundleSource[];
|
|
2215
|
-
}): ProtocolV2ExecutionPhase[] {
|
|
2216
|
-
const phases: ProtocolV2ExecutionPhase[] = [];
|
|
2217
|
-
const bootResourceSources = resourceSources.filter(source =>
|
|
2218
|
-
isProtocolV2BootResourcePackagePath(source.devicePath)
|
|
2219
|
-
);
|
|
2220
|
-
const remainingResourceSources = resourceSources.filter(
|
|
2221
|
-
source => !isProtocolV2BootResourcePackagePath(source.devicePath)
|
|
2222
|
-
);
|
|
2223
|
-
// A previous interrupted run may have left a promotable staging file. Replace
|
|
2224
|
-
// it with the current approved package before any firmware-triggered reboot.
|
|
2225
|
-
if (bootResourceSources.length > 0) {
|
|
2226
|
-
phases.push({
|
|
2227
|
-
kind: 'resource-sync',
|
|
2228
|
-
installSources: [],
|
|
2229
|
-
resourceSources: bootResourceSources,
|
|
2230
|
-
});
|
|
2231
|
-
}
|
|
2232
|
-
const bootloaderSources = installSources.filter(source => source.kind === 'bootloader');
|
|
2233
|
-
if (bootloaderSources.length > 0) {
|
|
2234
|
-
phases.push(
|
|
2235
|
-
{
|
|
2236
|
-
kind: 'bootloader-install',
|
|
2237
|
-
installSources: bootloaderSources,
|
|
2238
|
-
resourceSources: [],
|
|
2239
|
-
},
|
|
2240
|
-
{
|
|
2241
|
-
kind: 'bootloader-verify',
|
|
2242
|
-
installSources: [],
|
|
2243
|
-
resourceSources: [],
|
|
2244
|
-
}
|
|
2245
|
-
);
|
|
2246
|
-
}
|
|
2247
|
-
if (remainingResourceSources.length > 0) {
|
|
2248
|
-
phases.push({
|
|
2249
|
-
kind: 'resource-sync',
|
|
2250
|
-
installSources: [],
|
|
2251
|
-
resourceSources: remainingResourceSources,
|
|
2252
|
-
});
|
|
2253
|
-
}
|
|
2254
|
-
const componentSources = installSources.filter(source => source.kind !== 'bootloader');
|
|
2255
|
-
if (componentSources.length > 0) {
|
|
2256
|
-
phases.push({
|
|
2257
|
-
kind: 'component-install',
|
|
2258
|
-
installSources: componentSources,
|
|
2259
|
-
resourceSources: [],
|
|
2260
|
-
});
|
|
2261
|
-
}
|
|
2262
|
-
phases.push({
|
|
2263
|
-
kind: 'final-verify',
|
|
2264
|
-
installSources: [],
|
|
2265
|
-
resourceSources: [],
|
|
2266
|
-
});
|
|
2267
|
-
return phases;
|
|
2268
|
-
}
|
|
2269
|
-
|
|
2270
|
-
private async executeProtocolV2Phases({
|
|
2285
|
+
private async executeProtocolV2SourceUpdate({
|
|
2271
2286
|
installSources,
|
|
2272
2287
|
resourceSources,
|
|
2273
2288
|
}: {
|
|
@@ -2275,38 +2290,22 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2275
2290
|
resourceSources: ProtocolV2ResourceBundleSource[];
|
|
2276
2291
|
}) {
|
|
2277
2292
|
this.protocolV2BootResourceStagingSafe = false;
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
) {
|
|
2293
|
-
await this.ensureProtocolV2BootResourceStagingIsEmpty();
|
|
2294
|
-
}
|
|
2295
|
-
await this.executeProtocolV2TransferPhase(phase);
|
|
2296
|
-
} else if (phase.kind === 'bootloader-install' || phase.kind === 'component-install') {
|
|
2297
|
-
await this.enterProtocolV2BootloaderMode();
|
|
2298
|
-
await this.ensureProtocolV2BootResourceStagingIsEmpty();
|
|
2299
|
-
await this.executeProtocolV2TransferPhase(phase);
|
|
2300
|
-
await this.exitProtocolV2BootloaderToNormal();
|
|
2301
|
-
} else if (phase.kind === 'bootloader-verify') {
|
|
2302
|
-
await this.waitForProtocolV2FinalFeatures();
|
|
2303
|
-
this.assertExpectedProtocolV2Versions(['boot']);
|
|
2304
|
-
}
|
|
2293
|
+
if (installSources.length > 0 || resourceSources.length > 0) {
|
|
2294
|
+
await this.enterProtocolV2BootloaderMode();
|
|
2295
|
+
// Clear stale boot-resource staging before writing any artifacts. The new
|
|
2296
|
+
// boot resource, resources and firmware then share one transfer session,
|
|
2297
|
+
// one global progress range and one multi-target install request.
|
|
2298
|
+
// Preserve the approved source order instead of synthesizing a boot-first
|
|
2299
|
+
// phase. Loader firmware owns the cross-stage handoff: bootloader runs its
|
|
2300
|
+
// component targets and leaves boot pending for romloader, while pending
|
|
2301
|
+
// runner-backed records always resume before the application boots.
|
|
2302
|
+
await this.ensureProtocolV2BootResourceStagingIsEmpty();
|
|
2303
|
+
await this.executeProtocolV2TransferPhase({
|
|
2304
|
+
installSources,
|
|
2305
|
+
resourceSources,
|
|
2306
|
+
});
|
|
2305
2307
|
}
|
|
2306
|
-
|
|
2307
|
-
HardwareErrorCode.RuntimeError,
|
|
2308
|
-
'Protocol V2 execution has no final verification phase'
|
|
2309
|
-
);
|
|
2308
|
+
return this.completeProtocolV2FinalVerification();
|
|
2310
2309
|
}
|
|
2311
2310
|
|
|
2312
2311
|
private async ensureProtocolV2BootResourceStagingIsEmpty() {
|
|
@@ -2338,19 +2337,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2338
2337
|
this.protocolV2BootResourceStagingSafe = true;
|
|
2339
2338
|
}
|
|
2340
2339
|
|
|
2341
|
-
private async executeProtocolV2SourceUpdate({
|
|
2342
|
-
installSources,
|
|
2343
|
-
resourceSources,
|
|
2344
|
-
}: {
|
|
2345
|
-
installSources: ProtocolV2InstallSource[];
|
|
2346
|
-
resourceSources: ProtocolV2ResourceBundleSource[];
|
|
2347
|
-
}) {
|
|
2348
|
-
return this.executeProtocolV2Phases({
|
|
2349
|
-
installSources,
|
|
2350
|
-
resourceSources,
|
|
2351
|
-
});
|
|
2352
|
-
}
|
|
2353
|
-
|
|
2354
2340
|
private async executeProtocolV2Update({
|
|
2355
2341
|
fwBinaryMap,
|
|
2356
2342
|
bootloaderBinary,
|
|
@@ -2375,7 +2361,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2375
2361
|
kind: item.kind,
|
|
2376
2362
|
}))
|
|
2377
2363
|
);
|
|
2378
|
-
return await this.
|
|
2364
|
+
return await this.executeProtocolV2SourceUpdate({
|
|
2379
2365
|
installSources,
|
|
2380
2366
|
resourceSources: [],
|
|
2381
2367
|
});
|
|
@@ -2387,7 +2373,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2387
2373
|
private async executeProtocolV2TransferPhase({
|
|
2388
2374
|
installSources,
|
|
2389
2375
|
resourceSources,
|
|
2390
|
-
}:
|
|
2376
|
+
}: ProtocolV2TransferBatch) {
|
|
2391
2377
|
let totalSize = installSources.reduce((total, item) => total + item.source.size, 0);
|
|
2392
2378
|
const resourcesToSync: ProtocolV2ResourceBundleSource[] = [];
|
|
2393
2379
|
for (const resource of resourceSources) {
|
|
@@ -2660,6 +2646,47 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2660
2646
|
return Array.from(expectedTargetIds).filter(targetId => !reportedTargetIds.has(targetId));
|
|
2661
2647
|
}
|
|
2662
2648
|
|
|
2649
|
+
private getProtocolV2ObservableTargetVersions(features: Features) {
|
|
2650
|
+
const versions = new Map<number, string>();
|
|
2651
|
+
versions.set(
|
|
2652
|
+
ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_BOOTLOADER,
|
|
2653
|
+
getDeviceBootloaderVersion(features).join('.')
|
|
2654
|
+
);
|
|
2655
|
+
const applicationVersion = getDeviceFirmwareVersion(features).join('.');
|
|
2656
|
+
versions.set(ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_APPLICATION_P1, applicationVersion);
|
|
2657
|
+
versions.set(ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_APPLICATION_P2, applicationVersion);
|
|
2658
|
+
versions.set(
|
|
2659
|
+
ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_COPROCESSOR,
|
|
2660
|
+
getDeviceBLEFirmwareVersion(features).join('.')
|
|
2661
|
+
);
|
|
2662
|
+
const secureElementVersions: Array<[number, string | null | undefined]> = [
|
|
2663
|
+
[ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_SE01, features.se01Version],
|
|
2664
|
+
[ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_SE02, features.se02Version],
|
|
2665
|
+
[ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_SE03, features.se03Version],
|
|
2666
|
+
[ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_SE04, features.se04Version],
|
|
2667
|
+
];
|
|
2668
|
+
secureElementVersions.forEach(([targetId, version]) => {
|
|
2669
|
+
if (version) versions.set(targetId, version);
|
|
2670
|
+
});
|
|
2671
|
+
return versions;
|
|
2672
|
+
}
|
|
2673
|
+
|
|
2674
|
+
private hasProtocolV2InstallVersionChanged(expectedTargetIds: Set<number>) {
|
|
2675
|
+
if (!this.protocolV2LastRuntimeProbeFeatures) return false;
|
|
2676
|
+
const currentVersions = this.getProtocolV2ObservableTargetVersions(
|
|
2677
|
+
this.protocolV2LastRuntimeProbeFeatures
|
|
2678
|
+
);
|
|
2679
|
+
return Array.from(expectedTargetIds).some(targetId => {
|
|
2680
|
+
const previousVersion = this.protocolV2InstallBaselineVersions.get(targetId);
|
|
2681
|
+
const currentVersion = currentVersions.get(targetId);
|
|
2682
|
+
return (
|
|
2683
|
+
previousVersion !== undefined &&
|
|
2684
|
+
currentVersion !== undefined &&
|
|
2685
|
+
previousVersion !== currentVersion
|
|
2686
|
+
);
|
|
2687
|
+
});
|
|
2688
|
+
}
|
|
2689
|
+
|
|
2663
2690
|
private async waitForProtocolV2FirmwareUpdateComplete(
|
|
2664
2691
|
targets: Array<{ target_id: number; path: string }>
|
|
2665
2692
|
) {
|
|
@@ -2672,12 +2699,17 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2672
2699
|
let deviceInfo: ProtocolV2DeviceInfo | undefined;
|
|
2673
2700
|
let missingTargetStatusSince: number | undefined;
|
|
2674
2701
|
let missingTargetStatusKey: string | undefined;
|
|
2702
|
+
let normalModeWithoutInstallEvidenceSince: number | undefined;
|
|
2703
|
+
let installEvidenceObserved = this.protocolV2InstallAckReceived;
|
|
2675
2704
|
const resetMissingTargetStatusGrace = () => {
|
|
2676
2705
|
missingTargetStatusSince = undefined;
|
|
2677
2706
|
missingTargetStatusKey = undefined;
|
|
2678
2707
|
};
|
|
2679
2708
|
|
|
2680
2709
|
while (Date.now() - startTime < PROTOCOL_V2_INSTALL_TIMEOUT) {
|
|
2710
|
+
// A transport release caused by an explicit workflow cancellation must not
|
|
2711
|
+
// be mistaken for the expected device reboot during installation.
|
|
2712
|
+
this.throwIfAborted();
|
|
2681
2713
|
try {
|
|
2682
2714
|
if (shouldReconnect) {
|
|
2683
2715
|
await this.reconnectProtocolV2Device();
|
|
@@ -2700,21 +2732,33 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2700
2732
|
);
|
|
2701
2733
|
const statusTargets = (statusResponse.message.records ??
|
|
2702
2734
|
[]) as ProtocolV2FirmwareUpdateStatusTarget[];
|
|
2735
|
+
if (
|
|
2736
|
+
statusTargets.some(target => {
|
|
2737
|
+
const targetId = normalizeProtocolV2TargetId(target.target_id);
|
|
2738
|
+
return targetId !== undefined && expectedTargetIds.has(targetId);
|
|
2739
|
+
})
|
|
2740
|
+
) {
|
|
2741
|
+
installEvidenceObserved = true;
|
|
2742
|
+
normalModeWithoutInstallEvidenceSince = undefined;
|
|
2743
|
+
}
|
|
2703
2744
|
if (this.assertProtocolV2TargetStatus(statusTargets, expectedTargetIds, expectedPaths)) {
|
|
2704
2745
|
this.protocolV2FinalStatusVerified = true;
|
|
2705
2746
|
return;
|
|
2706
2747
|
}
|
|
2707
2748
|
|
|
2708
|
-
if (
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2749
|
+
if (statusTargets.length === 0 && currentDeviceInfo) {
|
|
2750
|
+
const isNormalMode = await this.probeProtocolV2NormalMode(currentDeviceInfo);
|
|
2751
|
+
if (
|
|
2752
|
+
isNormalMode &&
|
|
2753
|
+
(installEvidenceObserved ||
|
|
2754
|
+
this.hasProtocolV2InstallVersionChanged(expectedTargetIds))
|
|
2755
|
+
) {
|
|
2756
|
+
Log.log(
|
|
2757
|
+
'[FirmwareUpdateV4] empty firmware status after confirmed App reboot; update complete'
|
|
2758
|
+
);
|
|
2759
|
+
this.postProgressMessage(100, 'installingFirmware');
|
|
2760
|
+
return;
|
|
2761
|
+
}
|
|
2718
2762
|
}
|
|
2719
2763
|
|
|
2720
2764
|
const missingTargetIds = this.getProtocolV2MissingTargetIds(
|
|
@@ -2764,16 +2808,39 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2764
2808
|
'Protocol V2 device identity is unavailable during install polling'
|
|
2765
2809
|
);
|
|
2766
2810
|
}
|
|
2767
|
-
|
|
2811
|
+
const isNormalMode = await this.probeProtocolV2NormalMode(currentDeviceInfo);
|
|
2812
|
+
if (
|
|
2813
|
+
isNormalMode &&
|
|
2814
|
+
(installEvidenceObserved ||
|
|
2815
|
+
this.hasProtocolV2InstallVersionChanged(expectedTargetIds))
|
|
2816
|
+
) {
|
|
2768
2817
|
Log.log(
|
|
2769
2818
|
'[FirmwareUpdateV4] firmware status endpoint unavailable after confirmed App reboot'
|
|
2770
2819
|
);
|
|
2771
2820
|
this.postProgressMessage(100, 'installingFirmware');
|
|
2772
2821
|
return;
|
|
2773
2822
|
}
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2823
|
+
if (isNormalMode) {
|
|
2824
|
+
const now = Date.now();
|
|
2825
|
+
normalModeWithoutInstallEvidenceSince ??= now;
|
|
2826
|
+
if (
|
|
2827
|
+
now - normalModeWithoutInstallEvidenceSince >=
|
|
2828
|
+
PROTOCOL_V2_MISSING_TARGET_STATUS_GRACE_TIMEOUT
|
|
2829
|
+
) {
|
|
2830
|
+
throw ERRORS.TypedError(
|
|
2831
|
+
HardwareErrorCode.FirmwareError,
|
|
2832
|
+
'Protocol V2 device returned to normal mode without install ACK, target status, or version change'
|
|
2833
|
+
);
|
|
2834
|
+
}
|
|
2835
|
+
lastError = new Error(
|
|
2836
|
+
'Protocol V2 device is in normal mode but installation is not yet confirmed'
|
|
2837
|
+
);
|
|
2838
|
+
} else {
|
|
2839
|
+
normalModeWithoutInstallEvidenceSince = undefined;
|
|
2840
|
+
lastError = new Error(
|
|
2841
|
+
'Protocol V2 firmware status endpoint is unavailable while the device remains in loader mode'
|
|
2842
|
+
);
|
|
2843
|
+
}
|
|
2777
2844
|
} else {
|
|
2778
2845
|
shouldReconnect = true;
|
|
2779
2846
|
deviceInfo = undefined;
|
|
@@ -2832,9 +2899,9 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2832
2899
|
private async probeProtocolV2NormalMode(deviceInfo: ProtocolV2DeviceInfo) {
|
|
2833
2900
|
const features = await this.device.probeProtocolV2RuntimeState(
|
|
2834
2901
|
deviceInfo,
|
|
2835
|
-
PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT
|
|
2836
|
-
{ allowLegacyProtocolV2ProtocolInfo: true }
|
|
2902
|
+
PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT
|
|
2837
2903
|
);
|
|
2904
|
+
this.protocolV2LastRuntimeProbeFeatures = features;
|
|
2838
2905
|
return features.mode === 'normal' && !features.bootloaderMode;
|
|
2839
2906
|
}
|
|
2840
2907
|
|
|
@@ -2895,8 +2962,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2895
2962
|
this.assertProtocolV2DeviceInfoIdentity(deviceInfo);
|
|
2896
2963
|
const features = await this.device.probeProtocolV2RuntimeState(
|
|
2897
2964
|
deviceInfo,
|
|
2898
|
-
PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT
|
|
2899
|
-
{ allowLegacyProtocolV2ProtocolInfo: true }
|
|
2965
|
+
PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT
|
|
2900
2966
|
);
|
|
2901
2967
|
if (features.mode === 'normal' && !features.bootloaderMode) {
|
|
2902
2968
|
return features;
|
|
@@ -3068,6 +3134,8 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
3068
3134
|
}: {
|
|
3069
3135
|
targets: Array<{ target_id: number; path: string }>;
|
|
3070
3136
|
}) {
|
|
3137
|
+
this.protocolV2InstallAckReceived = false;
|
|
3138
|
+
this.protocolV2LastRuntimeProbeFeatures = undefined;
|
|
3071
3139
|
const startUpdate = () =>
|
|
3072
3140
|
this.device
|
|
3073
3141
|
.getCommands()
|
|
@@ -3077,29 +3145,51 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
3077
3145
|
{ targets },
|
|
3078
3146
|
{ timeoutMs: PROTOCOL_V2_START_UPDATE_TIMEOUT }
|
|
3079
3147
|
);
|
|
3080
|
-
let response: ProtocolV2FirmwareUpdateStartResponse;
|
|
3148
|
+
let response: ProtocolV2FirmwareUpdateStartResponse | undefined;
|
|
3081
3149
|
try {
|
|
3082
3150
|
response = await startUpdate();
|
|
3151
|
+
this.protocolV2InstallAckReceived = true;
|
|
3083
3152
|
} catch (error) {
|
|
3084
|
-
if (
|
|
3085
|
-
|
|
3086
|
-
|
|
3153
|
+
if (this.isBleReconnect() && isProtocolV2BleTransportReleasedError(error)) {
|
|
3154
|
+
this.throwIfAborted();
|
|
3155
|
+
// Installation can reboot the device before the Success response reaches
|
|
3156
|
+
// the host. The request has side effects, so do not replay it; reconnect
|
|
3157
|
+
// and let status polling determine whether installation was accepted.
|
|
3158
|
+
Log.log(
|
|
3159
|
+
'[FirmwareUpdateV4] install request interrupted by device reboot; continue status polling: ',
|
|
3160
|
+
error
|
|
3161
|
+
);
|
|
3162
|
+
} else if (
|
|
3163
|
+
this.protocolV2LegacyDirectUpdate &&
|
|
3164
|
+
isProtocolV2FirmwareUpdateEndpointUnavailable(error)
|
|
3087
3165
|
) {
|
|
3166
|
+
// Both legacy loaders register this request. A missing handler therefore identifies
|
|
3167
|
+
// a legacy App before dispatch, so it is safe to reboot and retry the unhandled request.
|
|
3168
|
+
this.protocolV2LegacyDirectUpdate = false;
|
|
3169
|
+
Log.debug(
|
|
3170
|
+
'[FirmwareUpdateV4] legacy App does not expose DeviceFirmwareUpdateRequest; rebooting to bootloader'
|
|
3171
|
+
);
|
|
3172
|
+
await this.rebootProtocolV2ToBootloader();
|
|
3173
|
+
try {
|
|
3174
|
+
response = await startUpdate();
|
|
3175
|
+
this.protocolV2InstallAckReceived = true;
|
|
3176
|
+
} catch (retryError) {
|
|
3177
|
+
if (!(this.isBleReconnect() && isProtocolV2BleTransportReleasedError(retryError))) {
|
|
3178
|
+
throw retryError;
|
|
3179
|
+
}
|
|
3180
|
+
this.throwIfAborted();
|
|
3181
|
+
Log.log(
|
|
3182
|
+
'[FirmwareUpdateV4] install request interrupted after legacy reboot; continue status polling: ',
|
|
3183
|
+
retryError
|
|
3184
|
+
);
|
|
3185
|
+
}
|
|
3186
|
+
} else {
|
|
3088
3187
|
throw error;
|
|
3089
3188
|
}
|
|
3090
|
-
|
|
3091
|
-
// Both legacy loaders register this request. A missing handler therefore identifies
|
|
3092
|
-
// a legacy App before dispatch, so it is safe to reboot and retry the unhandled request.
|
|
3093
|
-
this.protocolV2LegacyDirectUpdate = false;
|
|
3094
|
-
Log.debug(
|
|
3095
|
-
'[FirmwareUpdateV4] legacy App does not expose DeviceFirmwareUpdateRequest; rebooting to bootloader'
|
|
3096
|
-
);
|
|
3097
|
-
await this.rebootProtocolV2ToBootloader();
|
|
3098
|
-
response = await startUpdate();
|
|
3099
3189
|
}
|
|
3100
3190
|
this.protocolV2LegacyDirectUpdate = false;
|
|
3101
|
-
// Success
|
|
3102
|
-
//
|
|
3191
|
+
// A Success ACK or an install-time disconnect both end confirmation. In the
|
|
3192
|
+
// latter case only status polling may establish the final outcome.
|
|
3103
3193
|
this.postTipMessage(FirmwareUpdateTipMessage.FirmwareUpdating);
|
|
3104
3194
|
this.postProgressMessage(0, 'installingFirmware');
|
|
3105
3195
|
return response;
|
|
@@ -43,7 +43,6 @@ export default class GetDeviceState extends BaseMethod<
|
|
|
43
43
|
async run() {
|
|
44
44
|
const state = await this.device.getDeviceState({
|
|
45
45
|
refreshSections: SCOPE_SECTIONS[this.params.scope],
|
|
46
|
-
...(this.params.scope === 'firmware' ? { allowLegacyProtocolV2ProtocolInfo: true } : {}),
|
|
47
46
|
});
|
|
48
47
|
if (
|
|
49
48
|
this.params.scope === 'settings' &&
|
package/src/api/SearchDevices.ts
CHANGED
|
@@ -63,8 +63,6 @@ export default class SearchDevices extends BaseMethod {
|
|
|
63
63
|
connectProtocol: undefined,
|
|
64
64
|
forceProtocolDetection: true,
|
|
65
65
|
refreshRuntimeState: true,
|
|
66
|
-
// Old loader firmware predates build_fingerprint. Keep this recovery-only.
|
|
67
|
-
allowLegacyProtocolV2ProtocolInfo: true,
|
|
68
66
|
});
|
|
69
67
|
deviceList.push(...result.deviceList);
|
|
70
68
|
} catch (error) {
|