@onekeyfe/hd-core 1.2.0-alpha.98 → 1.2.0
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__/AllNetworkGetAddressBase.tracing.test.ts +20 -4
- package/__tests__/DeviceCommands.test.ts +203 -27
- package/__tests__/base64Data.test.ts +69 -0
- package/__tests__/check-all-firmware-release-protocol-v2.test.ts +89 -38
- package/__tests__/check-firmware-release-protocol-v2.test.ts +83 -14
- package/__tests__/device-connector-protocol.test.ts +21 -0
- package/__tests__/device-lifecycle-events.test.ts +427 -7
- package/__tests__/device-pool-state.test.ts +25 -0
- package/__tests__/device-settings.test.ts +237 -9
- package/__tests__/device-state-events.test.ts +2 -2
- package/__tests__/device-state-mapper.test.ts +61 -5
- package/__tests__/device-utils.test.ts +15 -1
- package/__tests__/device-wallet-session-store.test.ts +147 -21
- package/__tests__/deviceSettings.test.ts +7 -3
- package/__tests__/deviceUploadNft.test.ts +36 -4
- package/__tests__/firmware-update/device-update-bootloader.test.ts +14 -3
- package/__tests__/firmware-update/firmware-artifact-source.test.ts +61 -9
- package/__tests__/firmware-update/firmware-prepared-host-digest.test.ts +19 -9
- package/__tests__/firmware-update/firmware-prepared-resource-archive.test.ts +21 -0
- package/__tests__/firmware-update/firmware-update-bootloader-poll.test.ts +128 -2
- package/__tests__/firmware-update/firmware-update-plan-bootloader-identity.test.ts +80 -0
- package/__tests__/firmware-update/firmware-update-plan.test.ts +114 -50
- package/__tests__/firmware-update/firmware-update-prepared-plan.test.ts +19 -5
- package/__tests__/firmware-update/firmware-update-v3-reconnect.test.ts +116 -0
- package/__tests__/firmware-update/firmware-update-v4-install-poll.test.ts +678 -0
- package/__tests__/firmware-update/firmware-update-v4-parameters.test.ts +85 -0
- package/__tests__/get-device-state.test.ts +60 -21
- package/__tests__/logBlockEvent.test.ts +13 -1
- package/__tests__/open-wallet-session.test.ts +127 -1
- package/__tests__/protocol-v2-firmware-targets.test.ts +39 -0
- package/__tests__/protocol-v2-resources.test.ts +185 -88
- package/__tests__/protocol-v2-unlock-policy.test.ts +235 -2
- package/__tests__/protocol-v2.test.ts +2274 -466
- package/__tests__/refresh-device-state.test.ts +3 -1
- package/__tests__/resourceBase64Boundary.test.ts +48 -0
- package/__tests__/search-devices.test.ts +79 -32
- package/__tests__/ton-sign-message.test.ts +84 -0
- package/dist/api/BaseMethod.d.ts +2 -1
- package/dist/api/BaseMethod.d.ts.map +1 -1
- package/dist/api/CheckAllFirmwareRelease.d.ts.map +1 -1
- package/dist/api/CheckBootloaderRelease.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV2.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV3.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV4.d.ts +22 -7
- package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
- package/dist/api/OpenWalletSession.d.ts.map +1 -1
- package/dist/api/SearchDevices.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/allnetwork/AllNetworkGetAddressBase.d.ts.map +1 -1
- package/dist/api/device/DeviceChangePin.d.ts.map +1 -1
- package/dist/api/device/DeviceRebootToBoardloader.d.ts.map +1 -1
- package/dist/api/device/DeviceRebootToBootloader.d.ts.map +1 -1
- package/dist/api/device/DeviceSettings.d.ts.map +1 -1
- package/dist/api/device/DeviceVerify.d.ts +1 -0
- package/dist/api/device/DeviceVerify.d.ts.map +1 -1
- package/dist/api/device/DeviceWipe.d.ts.map +1 -1
- package/dist/api/firmware/FirmwareArtifactSource.d.ts +5 -1
- package/dist/api/firmware/FirmwareArtifactSource.d.ts.map +1 -1
- package/dist/api/firmware/FirmwareHostBinding.d.ts.map +1 -1
- package/dist/api/firmware/FirmwarePreparedResourceArchive.d.ts.map +1 -1
- package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts +2 -2
- package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts.map +1 -1
- package/dist/api/firmware/FirmwareUpdatePlan.d.ts +0 -15
- package/dist/api/firmware/FirmwareUpdatePlan.d.ts.map +1 -1
- package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts +1 -0
- package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts.map +1 -1
- package/dist/api/firmware/getBinary.d.ts +3 -2
- package/dist/api/firmware/getBinary.d.ts.map +1 -1
- package/dist/api/firmware/protocolV2Release.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/DeviceFirmwareUpdate.d.ts +3 -1
- package/dist/api/protocol-v2/DeviceFirmwareUpdate.d.ts.map +1 -1
- package/dist/api/protocol-v2/DeviceGetFirmwareUpdateStatus.d.ts.map +1 -1
- 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/DeviceReboot.d.ts +1 -1
- package/dist/api/protocol-v2/DeviceReboot.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/protocol-v2/helpers.d.ts.map +1 -1
- package/dist/api/ton/TonSignMessage.d.ts.map +1 -1
- package/dist/core/index.d.ts +6 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/data-manager/DataManager.d.ts +4 -3
- package/dist/data-manager/DataManager.d.ts.map +1 -1
- package/dist/data-manager/TransportManager.d.ts.map +1 -1
- package/dist/device/Device.d.ts +16 -3
- package/dist/device/Device.d.ts.map +1 -1
- package/dist/device/DeviceCommands.d.ts +4 -4
- package/dist/device/DeviceCommands.d.ts.map +1 -1
- package/dist/device/DeviceConnector.d.ts +1 -1
- package/dist/device/DeviceConnector.d.ts.map +1 -1
- package/dist/device/DevicePool.d.ts.map +1 -1
- package/dist/device/DeviceStateMapper.d.ts.map +1 -1
- package/dist/deviceProfile/buildDeviceFeatures.d.ts.map +1 -1
- package/dist/events/logBlockEvent.d.ts.map +1 -1
- package/dist/events/ui-request.d.ts +3 -0
- package/dist/events/ui-request.d.ts.map +1 -1
- package/dist/index.d.ts +54 -84
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2333 -1515
- 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 +12 -7
- package/dist/protocols/protocol-v2/resources.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/unlockPolicyRunner.d.ts +3 -1
- package/dist/protocols/protocol-v2/unlockPolicyRunner.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/walletSession.d.ts +3 -0
- package/dist/protocols/protocol-v2/walletSession.d.ts.map +1 -1
- package/dist/types/api/firmwareUpdatePlan.d.ts +2 -2
- package/dist/types/api/firmwareUpdatePlan.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/device.d.ts.map +1 -1
- package/dist/types/settings.d.ts +3 -31
- package/dist/types/settings.d.ts.map +1 -1
- package/dist/utils/deviceFeaturesCompat.d.ts.map +1 -1
- package/dist/utils/deviceFeaturesUtils.d.ts +3 -0
- package/dist/utils/deviceFeaturesUtils.d.ts.map +1 -1
- package/dist/utils/deviceInfoUtils.d.ts.map +1 -1
- package/dist/utils/deviceSettings.d.ts +1 -0
- package/dist/utils/deviceSettings.d.ts.map +1 -1
- package/dist/utils/index.d.ts +1 -1
- package/dist/utils/index.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/BaseMethod.ts +8 -1
- package/src/api/CheckAllFirmwareRelease.ts +8 -5
- package/src/api/CheckBLEFirmwareRelease.ts +2 -2
- package/src/api/CheckBootloaderRelease.ts +3 -2
- package/src/api/CheckFirmwareRelease.ts +2 -2
- package/src/api/FirmwareUpdateV2.ts +130 -69
- package/src/api/FirmwareUpdateV3.ts +29 -19
- package/src/api/FirmwareUpdateV4.ts +966 -743
- package/src/api/OpenWalletSession.ts +39 -9
- package/src/api/PromptWebDeviceAccess.ts +2 -2
- package/src/api/SearchDevices.ts +6 -3
- package/src/api/UploadPortfolio.ts +9 -2
- package/src/api/allnetwork/AllNetworkGetAddressBase.ts +2 -1
- package/src/api/device/DeviceChangePin.ts +4 -1
- package/src/api/device/DeviceRebootToBoardloader.ts +3 -1
- package/src/api/device/DeviceRebootToBootloader.ts +3 -1
- package/src/api/device/DeviceSettings.ts +34 -20
- package/src/api/device/DeviceVerify.ts +25 -0
- package/src/api/device/DeviceWipe.ts +4 -1
- package/src/api/firmware/FirmwareArtifactSource.ts +41 -14
- package/src/api/firmware/FirmwareHostBinding.ts +10 -0
- package/src/api/firmware/FirmwarePreparedResourceArchive.ts +16 -27
- package/src/api/firmware/FirmwareUpdateBaseMethod.ts +131 -76
- package/src/api/firmware/FirmwareUpdatePlan.ts +55 -82
- package/src/api/firmware/FirmwareUpdatePreparedPlan.ts +32 -15
- package/src/api/firmware/protocolV2Release.ts +7 -2
- package/src/api/helpers/base64Data.ts +85 -0
- package/src/api/protocol-v2/DeviceFactoryInfoSet.ts +1 -1
- package/src/api/protocol-v2/DeviceFirmwareUpdate.ts +6 -28
- package/src/api/protocol-v2/DeviceGetFirmwareUpdateStatus.ts +7 -1
- package/src/api/protocol-v2/DeviceInfoGet.ts +3 -3
- package/src/api/protocol-v2/DeviceReboot.ts +5 -0
- package/src/api/protocol-v2/DeviceUploadNft.ts +61 -9
- package/src/api/protocol-v2/DeviceUploadWallpaper.ts +53 -19
- package/src/api/protocol-v2/helpers.ts +8 -0
- package/src/api/ton/TonSignMessage.ts +5 -3
- package/src/core/index.ts +264 -55
- package/src/data/messages/messages-protocol-v2.json +140 -13
- package/src/data-manager/DataManager.ts +46 -39
- package/src/data-manager/TransportManager.ts +3 -1
- package/src/device/Device.ts +210 -44
- package/src/device/DeviceCommands.ts +27 -18
- package/src/device/DeviceConnector.ts +17 -3
- package/src/device/DevicePool.ts +9 -2
- package/src/device/DeviceStateMapper.ts +30 -24
- package/src/deviceProfile/buildDeviceFeatures.ts +10 -5
- package/src/events/logBlockEvent.ts +14 -1
- package/src/events/ui-request.ts +3 -0
- package/src/index.ts +0 -10
- 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 +110 -147
- package/src/protocols/protocol-v2/unlockPolicyRunner.ts +13 -1
- package/src/protocols/protocol-v2/walletSession.ts +65 -22
- package/src/types/api/firmwareUpdate.ts +1 -1
- package/src/types/api/firmwareUpdatePlan.ts +2 -2
- package/src/types/api/protocolV2.ts +1 -1
- package/src/types/device.ts +2 -1
- package/src/types/settings.ts +6 -34
- package/src/utils/deviceFeaturesCompat.ts +9 -4
- package/src/utils/deviceFeaturesUtils.ts +4 -0
- package/src/utils/deviceInfoUtils.ts +9 -2
- package/src/utils/deviceSettings.ts +11 -4
- package/src/utils/index.ts +1 -0
- package/src/utils/pro2Nft.ts +31 -8
- package/__tests__/firmware-memory-host.test.ts +0 -112
- package/dist/api/firmware/FirmwareMemoryHost.d.ts +0 -22
- package/dist/api/firmware/FirmwareMemoryHost.d.ts.map +0 -1
- package/src/api/firmware/FirmwareMemoryHost.ts +0 -142
|
@@ -1,7 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
EDeviceType,
|
|
3
|
+
ERRORS,
|
|
4
|
+
HardwareError,
|
|
5
|
+
HardwareErrorCode,
|
|
6
|
+
isBleStaleBondHardwareError,
|
|
7
|
+
wait,
|
|
8
|
+
} from '@onekeyfe/hd-shared';
|
|
2
9
|
import JSZip from 'jszip';
|
|
3
10
|
import {
|
|
4
11
|
DeviceRebootType,
|
|
12
|
+
DeviceSessionPinType,
|
|
5
13
|
PROTOCOL_V2_BLE_FILE_CHUNK_SIZE,
|
|
6
14
|
PROTOCOL_V2_BLE_FILE_READ_CHUNK_SIZE,
|
|
7
15
|
PROTOCOL_V2_BLE_FIRMWARE_FILE_CHUNK_SIZE,
|
|
@@ -9,7 +17,7 @@ import {
|
|
|
9
17
|
} from '@onekeyfe/hd-transport';
|
|
10
18
|
import { sha256 } from '@noble/hashes/sha256';
|
|
11
19
|
|
|
12
|
-
import { FirmwareUpdateTipMessage, UI_REQUEST } from '../events/ui-request';
|
|
20
|
+
import { FirmwareUpdateTipMessage, UI_REQUEST, createUiMessage } from '../events/ui-request';
|
|
13
21
|
import { validateParams } from './helpers/paramsValidator';
|
|
14
22
|
import {
|
|
15
23
|
LoggerNames,
|
|
@@ -31,31 +39,30 @@ import {
|
|
|
31
39
|
} from '../protocols/protocol-v2';
|
|
32
40
|
import { requestProtocolV2DeviceInfo } from '../protocols/protocol-v2/features';
|
|
33
41
|
import {
|
|
34
|
-
|
|
35
|
-
|
|
42
|
+
PROTOCOL_V2_BOOT_RESOURCE_PACKAGE_PATH,
|
|
43
|
+
PROTOCOL_V2_BOOT_RESOURCE_PACKAGE_STAGING_PATH,
|
|
44
|
+
PROTOCOL_V2_RESOURCE_PACKAGE_HEADER_SIZE,
|
|
45
|
+
type ProtocolV2ResourcePackageHeader,
|
|
46
|
+
isProtocolV2ResourceArchiveEntryName,
|
|
47
|
+
parseProtocolV2ResourcePackage,
|
|
48
|
+
parseProtocolV2ResourcePackageHeader,
|
|
36
49
|
} from '../protocols/protocol-v2/resources';
|
|
37
50
|
import {
|
|
38
51
|
getProtocolV2UnknownErrorText,
|
|
39
52
|
isProtocolV2DeviceDisconnectedError,
|
|
40
53
|
} from './protocol-v2/helpers';
|
|
54
|
+
import { isProtocolV2ResponseTimeout } from './helpers/protocolV2FileWrite';
|
|
41
55
|
import {
|
|
42
56
|
openFirmwareByteSource,
|
|
43
57
|
readFirmwareByteSourceFully,
|
|
44
58
|
writeFirmwareByteSource,
|
|
45
59
|
} from './firmware/FirmwareArtifactSource';
|
|
46
|
-
import {
|
|
47
|
-
registerFirmwareUpdateHostBinding,
|
|
48
|
-
resolveFirmwareUpdateHostBinding,
|
|
49
|
-
unregisterFirmwareUpdateHostBinding,
|
|
50
|
-
} from './firmware/FirmwareHostBinding';
|
|
60
|
+
import { resolveFirmwareUpdateHostBinding } from './firmware/FirmwareHostBinding';
|
|
51
61
|
import {
|
|
52
62
|
assertFirmwareUpdatePreparedPlanBinding,
|
|
53
63
|
assertFirmwareUpdatePreparedPlanDeviceIdentity,
|
|
54
|
-
prepareFirmwareUpdatePlan,
|
|
55
64
|
validateFirmwareUpdatePreparedPlan,
|
|
56
65
|
} from './firmware/FirmwareUpdatePreparedPlan';
|
|
57
|
-
import { prepareFirmwareUpdateV4MemoryHost } from './firmware/FirmwareMemoryHost';
|
|
58
|
-
import { buildProtocolV2LocalFirmwareUpdatePlan } from './firmware/FirmwareUpdatePlan';
|
|
59
66
|
|
|
60
67
|
import type {
|
|
61
68
|
FirmwareArtifactReference,
|
|
@@ -70,13 +77,9 @@ import type {
|
|
|
70
77
|
IFirmwareReleaseInfo,
|
|
71
78
|
IProtocolV2FirmwareComponent,
|
|
72
79
|
IVersionArray,
|
|
80
|
+
KnownDevice,
|
|
73
81
|
} from '../types';
|
|
74
82
|
import type { FirmwareByteSource } from './firmware/FirmwareArtifactSource';
|
|
75
|
-
import type {
|
|
76
|
-
FirmwareMemoryArtifact,
|
|
77
|
-
FirmwareMemoryArtifactEntry,
|
|
78
|
-
FirmwareUpdateV4MemoryHost,
|
|
79
|
-
} from './firmware/FirmwareMemoryHost';
|
|
80
83
|
|
|
81
84
|
const Log = getLogger(LoggerNames.Method);
|
|
82
85
|
|
|
@@ -88,9 +91,9 @@ const PROTOCOL_V2_BOOTLOADER_RECONNECT_TIMEOUT = 90 * 1000;
|
|
|
88
91
|
const PROTOCOL_V2_FINAL_RECONNECT_TIMEOUT = 3 * 60 * 1000;
|
|
89
92
|
const PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT = 5 * 1000;
|
|
90
93
|
const PROTOCOL_V2_FIRMWARE_STATUS_RESPONSE_TIMEOUT = 15 * 1000;
|
|
91
|
-
const
|
|
92
|
-
const
|
|
93
|
-
const
|
|
94
|
+
const PROTOCOL_V2_INSTALL_TIMEOUT = 5 * 60 * 1000;
|
|
95
|
+
const PROTOCOL_V2_INSTALL_STATUS_INITIAL_DELAY = 1000;
|
|
96
|
+
const PROTOCOL_V2_INSTALL_FINISHED_AFTER_DISCONNECT_POLLS = 4;
|
|
94
97
|
const PROTOCOL_V2_TARGET_STATUS_PENDING = 0;
|
|
95
98
|
const PROTOCOL_V2_TARGET_STATUS_IN_PROGRESS = 1;
|
|
96
99
|
const PROTOCOL_V2_TARGET_STATUS_FINISHED = 2;
|
|
@@ -103,12 +106,10 @@ const PROTOCOL_V2_CONNECT_POLL_INTERVAL = 500;
|
|
|
103
106
|
const PROTOCOL_V2_CONNECT_SINGLE_TIMEOUT = 75 * 1000;
|
|
104
107
|
const PROTOCOL_V2_DEVICE_INFO_READY_TIMEOUT = 30 * 1000;
|
|
105
108
|
const PROTOCOL_V2_FILE_TRANSFER_RETRY_COUNT = 3;
|
|
109
|
+
const PROTOCOL_V2_TRANSFER_PROGRESS_HEARTBEAT_MS = 1000;
|
|
106
110
|
const PROTOCOL_V2_INSTALL_STATUS_CONFLICT_CODE = 'FirmwareInstallStatusConflict';
|
|
107
|
-
const
|
|
108
|
-
const
|
|
109
|
-
const PROTOCOL_V2_OKPP_HEADER_HASH_OFFSET = 0x240;
|
|
110
|
-
const PROTOCOL_V2_OKPP_HASH_SIZE = 64;
|
|
111
|
-
const PROTOCOL_V2_RESOURCE_MANIFEST_MAX_BYTES = 1024 * 1024;
|
|
111
|
+
const PROTOCOL_V2_INSTALL_FAILED_CODE = 'FirmwareInstallFailed';
|
|
112
|
+
const PROTOCOL_V2_INSTALL_TIMEOUT_CODE = 'FirmwareInstallTimeout';
|
|
112
113
|
const PROTOCOL_V2_RESOURCE_FILE_MAX_COUNT = 512;
|
|
113
114
|
const PROTOCOL_V2_RESOURCE_TOTAL_MAX_BYTES = 256 * 1024 * 1024;
|
|
114
115
|
|
|
@@ -138,13 +139,31 @@ const getProtocolV2ZipEntrySizes = (entry: JSZip.JSZipObject) => {
|
|
|
138
139
|
|
|
139
140
|
export function assertProtocolV2FirmwareTargetsSupported(
|
|
140
141
|
deviceType: EDeviceType | string | undefined,
|
|
141
|
-
params: FirmwareUpdateV4Params
|
|
142
|
+
params: FirmwareUpdateV4Params,
|
|
143
|
+
hasExplicitTargetSelection = false
|
|
142
144
|
) {
|
|
145
|
+
const requestedTargets = new Set(params.targetsToUpdate ?? []);
|
|
143
146
|
const unsupportedTargets = new Set(
|
|
144
|
-
(
|
|
147
|
+
Array.from(requestedTargets).filter(target => PROTOCOL_V2_NEO_UNSUPPORTED_TARGETS.has(target))
|
|
145
148
|
);
|
|
146
|
-
if (params.se03Binary
|
|
147
|
-
|
|
149
|
+
if (params.se03Binary && (!hasExplicitTargetSelection || requestedTargets.has('se03'))) {
|
|
150
|
+
unsupportedTargets.add('se03');
|
|
151
|
+
}
|
|
152
|
+
if (params.se04Binary && (!hasExplicitTargetSelection || requestedTargets.has('se04'))) {
|
|
153
|
+
unsupportedTargets.add('se04');
|
|
154
|
+
}
|
|
155
|
+
if (
|
|
156
|
+
params.componentArtifacts?.se03 &&
|
|
157
|
+
(!hasExplicitTargetSelection || requestedTargets.has('se03'))
|
|
158
|
+
) {
|
|
159
|
+
unsupportedTargets.add('se03');
|
|
160
|
+
}
|
|
161
|
+
if (
|
|
162
|
+
params.componentArtifacts?.se04 &&
|
|
163
|
+
(!hasExplicitTargetSelection || requestedTargets.has('se04'))
|
|
164
|
+
) {
|
|
165
|
+
unsupportedTargets.add('se04');
|
|
166
|
+
}
|
|
148
167
|
|
|
149
168
|
if (!unsupportedTargets.size || deviceType === EDeviceType.Pro2) return;
|
|
150
169
|
|
|
@@ -162,26 +181,26 @@ const getProtocolV2DeviceTransferProgress = (
|
|
|
162
181
|
totalBytes: number
|
|
163
182
|
) => {
|
|
164
183
|
if (!Number.isFinite(totalBytes) || totalBytes <= 0) {
|
|
165
|
-
return
|
|
184
|
+
return 0;
|
|
166
185
|
}
|
|
167
186
|
if (bytesBeforeChunk <= 0 && bytesAfterChunk < totalBytes) {
|
|
168
187
|
return 0;
|
|
169
188
|
}
|
|
170
|
-
if (bytesAfterChunk >= totalBytes) {
|
|
171
|
-
return 100;
|
|
172
|
-
}
|
|
173
189
|
return Math.min(Math.max(Math.ceil((bytesAfterChunk / totalBytes) * 100), 1), 99);
|
|
174
190
|
};
|
|
175
191
|
|
|
176
192
|
type ProtocolV2FirmwareUpdateStatusTarget = {
|
|
177
193
|
target_id: number | string;
|
|
178
194
|
status?: number | string;
|
|
195
|
+
progress_percent?: number;
|
|
196
|
+
phase_info?: {
|
|
197
|
+
phase: number | string;
|
|
198
|
+
progress_percent: number;
|
|
199
|
+
};
|
|
179
200
|
payload_version?: number;
|
|
180
201
|
path?: string;
|
|
181
202
|
};
|
|
182
203
|
|
|
183
|
-
type ProtocolV2FirmwareUpdateStartResponse = TypedResponseMessage<'Success'>;
|
|
184
|
-
|
|
185
204
|
type ProtocolV2TargetBinary = { fileName: string; binary: ArrayBuffer; targetId: number };
|
|
186
205
|
type ProtocolV2InstallItem = ProtocolV2TargetBinary & {
|
|
187
206
|
kind: ProtocolV2RemoteComponentTarget['kind'];
|
|
@@ -215,7 +234,11 @@ type ProtocolV2ResourceBundleSource = {
|
|
|
215
234
|
|
|
216
235
|
type ProtocolV2LocalResourceArchive = {
|
|
217
236
|
binary: ArrayBuffer;
|
|
218
|
-
|
|
237
|
+
resources: Array<{
|
|
238
|
+
entryName: string;
|
|
239
|
+
binary: ArrayBuffer;
|
|
240
|
+
header: ProtocolV2ResourcePackageHeader;
|
|
241
|
+
}>;
|
|
219
242
|
};
|
|
220
243
|
|
|
221
244
|
type JSZipSizedEntry = JSZip.JSZipObject & {
|
|
@@ -225,26 +248,11 @@ type JSZipSizedEntry = JSZip.JSZipObject & {
|
|
|
225
248
|
};
|
|
226
249
|
};
|
|
227
250
|
|
|
228
|
-
type
|
|
229
|
-
| 'resource-sync'
|
|
230
|
-
| 'bootloader-install'
|
|
231
|
-
| 'bootloader-verify'
|
|
232
|
-
| 'component-install'
|
|
233
|
-
| 'final-verify';
|
|
234
|
-
|
|
235
|
-
type ProtocolV2ExecutionPhase = {
|
|
236
|
-
kind: ProtocolV2ExecutionPhaseKind;
|
|
251
|
+
type ProtocolV2TransferBatch = {
|
|
237
252
|
installSources: ProtocolV2InstallSource[];
|
|
238
253
|
resourceSources: ProtocolV2ResourceBundleSource[];
|
|
239
254
|
};
|
|
240
255
|
|
|
241
|
-
type ProtocolV2OkppHeader = {
|
|
242
|
-
type: string;
|
|
243
|
-
version: IVersionArray;
|
|
244
|
-
payloadHash: string;
|
|
245
|
-
headerHash: string;
|
|
246
|
-
};
|
|
247
|
-
|
|
248
256
|
const PROTOCOL_V2_REMOTE_COMPONENT_TARGETS: Readonly<
|
|
249
257
|
Record<string, ProtocolV2RemoteComponentTarget>
|
|
250
258
|
> = {
|
|
@@ -296,18 +304,9 @@ const PROTOCOL_V2_FIRMWARE_STAGING_PATHS = new Set(
|
|
|
296
304
|
)
|
|
297
305
|
);
|
|
298
306
|
|
|
299
|
-
const PROTOCOL_V2_BOOT_RESOURCE_PACKAGE_PATH = 'vol0:/loaders/bootloader/boot_resource.okpkg';
|
|
300
|
-
const PROTOCOL_V2_BOOT_RESOURCE_PACKAGE_STAGING_PATH = `${PROTOCOL_V2_BOOT_RESOURCE_PACKAGE_PATH}.staging`;
|
|
301
|
-
|
|
302
307
|
const isProtocolV2BootResourcePackagePath = (devicePath: string) =>
|
|
303
308
|
typeof devicePath === 'string' &&
|
|
304
|
-
devicePath.
|
|
305
|
-
PROTOCOL_V2_BOOT_RESOURCE_PACKAGE_PATH;
|
|
306
|
-
|
|
307
|
-
const resolveProtocolV2ResourceWritePath = (devicePath: string) =>
|
|
308
|
-
isProtocolV2BootResourcePackagePath(devicePath)
|
|
309
|
-
? PROTOCOL_V2_BOOT_RESOURCE_PACKAGE_STAGING_PATH
|
|
310
|
-
: devicePath;
|
|
309
|
+
devicePath.toLowerCase() === PROTOCOL_V2_BOOT_RESOURCE_PACKAGE_STAGING_PATH;
|
|
311
310
|
|
|
312
311
|
const PROTOCOL_V2_UPDATE_TARGET_BY_TARGET_ID = new Map<
|
|
313
312
|
number,
|
|
@@ -376,10 +375,12 @@ const isProtocolV2FirmwareStatusEndpointUnavailable = (error: unknown) => {
|
|
|
376
375
|
};
|
|
377
376
|
|
|
378
377
|
const isProtocolV2TerminalInstallStatusError = (error: unknown) =>
|
|
379
|
-
error
|
|
380
|
-
(error
|
|
381
|
-
error.errorCode === HardwareErrorCode.
|
|
382
|
-
|
|
378
|
+
isBleStaleBondHardwareError(error) ||
|
|
379
|
+
(error instanceof HardwareError &&
|
|
380
|
+
(error.errorCode === HardwareErrorCode.ActionCancelled ||
|
|
381
|
+
error.errorCode === HardwareErrorCode.FirmwareError ||
|
|
382
|
+
error.errorCode === HardwareErrorCode.FirmwareVerificationFailed ||
|
|
383
|
+
error.params?.firmwareUpdateCode === PROTOCOL_V2_INSTALL_STATUS_CONFLICT_CODE));
|
|
383
384
|
|
|
384
385
|
const isProtocolV2TargetStatusFinished = (status: ProtocolV2FirmwareUpdateStatusTarget['status']) =>
|
|
385
386
|
normalizeProtocolV2TargetStatus(status) === PROTOCOL_V2_TARGET_STATUS_FINISHED;
|
|
@@ -416,6 +417,18 @@ const normalizeProtocolV2TargetStatus = (
|
|
|
416
417
|
return undefined;
|
|
417
418
|
};
|
|
418
419
|
|
|
420
|
+
const PROTOCOL_V2_INSTALL_PHASE_BY_DECODED_VALUE = new Map<
|
|
421
|
+
number | string,
|
|
422
|
+
'prepare' | 'install' | 'verify'
|
|
423
|
+
>([
|
|
424
|
+
[0, 'prepare'],
|
|
425
|
+
[1, 'install'],
|
|
426
|
+
[2, 'verify'],
|
|
427
|
+
['FW_MGMT_UPDATER_PHASE_PREPARE', 'prepare'],
|
|
428
|
+
['FW_MGMT_UPDATER_PHASE_INSTALL', 'install'],
|
|
429
|
+
['FW_MGMT_UPDATER_PHASE_VERIFY', 'verify'],
|
|
430
|
+
]);
|
|
431
|
+
|
|
419
432
|
const normalizeProtocolV2Hex = (value?: string) => value?.replace(/^0x/i, '').toLowerCase();
|
|
420
433
|
|
|
421
434
|
const versionArrayToNumber = (version?: IVersionArray) => {
|
|
@@ -473,40 +486,9 @@ const toProtocolV2FiniteNumber = (value: unknown) => {
|
|
|
473
486
|
return undefined;
|
|
474
487
|
};
|
|
475
488
|
|
|
476
|
-
const
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
.join('');
|
|
480
|
-
|
|
481
|
-
const parseProtocolV2OkppHeader = (bytes: Uint8Array): ProtocolV2OkppHeader | null => {
|
|
482
|
-
if (bytes.byteLength < PROTOCOL_V2_OKPP_HEADER_SIZE) return null;
|
|
483
|
-
if (readProtocolV2Ascii(bytes, 0, 4) !== 'OKPP') return null;
|
|
484
|
-
|
|
485
|
-
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
486
|
-
const headerLen = view.getUint32(0x0c, true);
|
|
487
|
-
if (headerLen !== PROTOCOL_V2_OKPP_HEADER_SIZE) return null;
|
|
488
|
-
|
|
489
|
-
const packedVersion = view.getUint32(0x10, true);
|
|
490
|
-
return {
|
|
491
|
-
type: readProtocolV2Ascii(bytes, 0x08, 4),
|
|
492
|
-
version: [
|
|
493
|
-
Math.floor(packedVersion / 0x10000) % 0x100,
|
|
494
|
-
Math.floor(packedVersion / 0x100) % 0x100,
|
|
495
|
-
packedVersion % 0x100,
|
|
496
|
-
],
|
|
497
|
-
payloadHash: bytesToHex(
|
|
498
|
-
bytes.slice(
|
|
499
|
-
PROTOCOL_V2_OKPP_PAYLOAD_HASH_OFFSET,
|
|
500
|
-
PROTOCOL_V2_OKPP_PAYLOAD_HASH_OFFSET + PROTOCOL_V2_OKPP_HASH_SIZE
|
|
501
|
-
)
|
|
502
|
-
),
|
|
503
|
-
headerHash: bytesToHex(
|
|
504
|
-
bytes.slice(
|
|
505
|
-
PROTOCOL_V2_OKPP_HEADER_HASH_OFFSET,
|
|
506
|
-
PROTOCOL_V2_OKPP_HEADER_HASH_OFFSET + PROTOCOL_V2_OKPP_HASH_SIZE
|
|
507
|
-
)
|
|
508
|
-
),
|
|
509
|
-
};
|
|
489
|
+
const normalizeProtocolV2ProgressPercent = (value: unknown) => {
|
|
490
|
+
const progress = toProtocolV2FiniteNumber(value);
|
|
491
|
+
return progress === undefined ? undefined : Math.min(100, Math.max(0, progress));
|
|
510
492
|
};
|
|
511
493
|
|
|
512
494
|
export const isProtocolV2FirmwareFingerprintValid = (
|
|
@@ -551,7 +533,7 @@ export const assertProtocolV2ReconnectIdentity = (
|
|
|
551
533
|
*
|
|
552
534
|
* It intentionally does not fall back to FirmwareUpdateV3/V1 behavior:
|
|
553
535
|
* - upload uses FilesystemFileWrite
|
|
554
|
-
* - install uses DeviceFirmwareUpdateRequest
|
|
536
|
+
* - install uses DeviceFirmwareUpdateStage followed by an empty DeviceFirmwareUpdateRequest
|
|
555
537
|
* - completion waits for target status to finish, reboots to normal, then polls DeviceInfo
|
|
556
538
|
*/
|
|
557
539
|
export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareUpdateV4Params> {
|
|
@@ -559,6 +541,8 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
559
541
|
|
|
560
542
|
private protocolV2ExpectedPath?: string;
|
|
561
543
|
|
|
544
|
+
private protocolV2HasExplicitTargetSelection = false;
|
|
545
|
+
|
|
562
546
|
getSupportedProtocols() {
|
|
563
547
|
return ['V2'] as const;
|
|
564
548
|
}
|
|
@@ -571,19 +555,56 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
571
555
|
|
|
572
556
|
private protocolV2CompletedTargetVersions = new Map<number, number>();
|
|
573
557
|
|
|
558
|
+
private protocolV2CompletedTargetIds = new Set<number>();
|
|
559
|
+
|
|
574
560
|
private protocolV2LatestFinalFeatures?: Features;
|
|
575
561
|
|
|
576
|
-
private
|
|
562
|
+
private protocolV2LatestFinalDeviceInfo?: ProtocolV2DeviceInfo;
|
|
563
|
+
|
|
564
|
+
private protocolV2InstallBaselineVersions = new Map<number, string>();
|
|
565
|
+
|
|
566
|
+
private protocolV2InstallNeedsReconnect = false;
|
|
567
|
+
|
|
568
|
+
private protocolV2InstallTerminalSuccessObserved = false;
|
|
569
|
+
|
|
570
|
+
private protocolV2LastRuntimeProbeFeatures?: Features;
|
|
571
|
+
|
|
572
|
+
private protocolV2LastTransferProgress?: number;
|
|
573
|
+
|
|
574
|
+
private protocolV2LastTransferProgressAt = 0;
|
|
577
575
|
|
|
578
576
|
init() {
|
|
579
577
|
this.allowDeviceMode = [UI_REQUEST.BOOTLOADER, UI_REQUEST.NOT_INITIALIZE];
|
|
580
578
|
this.requireDeviceMode = [];
|
|
581
579
|
this.unlockPolicy = 'unlock-before-run';
|
|
580
|
+
// Protocol V2 device-management actions are not wallet-scoped, so either
|
|
581
|
+
// the main PIN or an Attach PIN may authorize them.
|
|
582
|
+
this.protocolV2PreUnlockPinType = DeviceSessionPinType.Any;
|
|
582
583
|
this.useDevicePassphraseState = false;
|
|
583
584
|
this.skipForceUpdateCheck = true;
|
|
584
585
|
|
|
585
586
|
const { payload } = this;
|
|
586
587
|
|
|
588
|
+
const removedResourceInputs = ['resourceFiles', 'resourceBundleArtifacts'].filter(input =>
|
|
589
|
+
Object.prototype.hasOwnProperty.call(payload, input)
|
|
590
|
+
);
|
|
591
|
+
if (removedResourceInputs.length > 0) {
|
|
592
|
+
throw ERRORS.TypedError(
|
|
593
|
+
HardwareErrorCode.CallMethodInvalidParameter,
|
|
594
|
+
`Protocol V2 ${removedResourceInputs.join(
|
|
595
|
+
' and '
|
|
596
|
+
)} inputs are no longer supported; use resourceArchiveBinary for local updates or preparedPlan with hostBindingGeneration for remote updates`
|
|
597
|
+
);
|
|
598
|
+
}
|
|
599
|
+
if (payload.hostBindingGeneration !== undefined && !payload.preparedPlan) {
|
|
600
|
+
throw ERRORS.TypedError(
|
|
601
|
+
HardwareErrorCode.CallMethodInvalidParameter,
|
|
602
|
+
'Protocol V2 hostBindingGeneration requires preparedPlan for remote updates; use direct binaries or resourceArchiveBinary for local updates'
|
|
603
|
+
);
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
this.protocolV2HasExplicitTargetSelection = payload.targetsToUpdate !== undefined;
|
|
607
|
+
|
|
587
608
|
if (typeof payload.retryCount !== 'number') {
|
|
588
609
|
payload.retryCount = PROTOCOL_V2_CONNECT_RETRY_COUNT;
|
|
589
610
|
}
|
|
@@ -651,10 +672,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
651
672
|
payload.hostBindingGeneration,
|
|
652
673
|
preparedPlan.preparedPlanDigest
|
|
653
674
|
).artifactReader;
|
|
654
|
-
} else if (payload.hostBindingGeneration !== undefined) {
|
|
655
|
-
artifactReader = resolveFirmwareUpdateHostBinding(
|
|
656
|
-
payload.hostBindingGeneration
|
|
657
|
-
).artifactReader;
|
|
658
675
|
}
|
|
659
676
|
if (preparedPlan) {
|
|
660
677
|
assertFirmwareUpdatePreparedPlanBinding({
|
|
@@ -777,30 +794,30 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
777
794
|
private async runProtocolV2() {
|
|
778
795
|
await this.captureProtocolV2PhysicalIdentity();
|
|
779
796
|
const deviceFeatures = await this.getProtocolV2DeviceFeatures();
|
|
797
|
+
this.protocolV2InstallBaselineVersions =
|
|
798
|
+
this.getProtocolV2ObservableTargetVersions(deviceFeatures);
|
|
799
|
+
this.protocolV2LastRuntimeProbeFeatures = undefined;
|
|
780
800
|
const currentDeviceType = this.device.getCurrentDeviceType();
|
|
781
801
|
const capabilityDeviceType =
|
|
782
802
|
currentDeviceType === EDeviceType.Pro2 || currentDeviceType === EDeviceType.Neo
|
|
783
803
|
? currentDeviceType
|
|
784
804
|
: getDeviceType(deviceFeatures);
|
|
785
|
-
assertProtocolV2FirmwareTargetsSupported(
|
|
805
|
+
assertProtocolV2FirmwareTargetsSupported(
|
|
806
|
+
capabilityDeviceType,
|
|
807
|
+
this.params,
|
|
808
|
+
this.protocolV2HasExplicitTargetSelection
|
|
809
|
+
);
|
|
786
810
|
const deviceFirmwareType = getFirmwareType(deviceFeatures);
|
|
787
811
|
const firmwareType = this.params.firmwareType ?? deviceFirmwareType;
|
|
788
812
|
this.validateExpectedTargetVersions();
|
|
813
|
+
const wantsResources = !!this.params.targetsToUpdate?.includes('resource');
|
|
789
814
|
|
|
790
815
|
if (
|
|
791
816
|
!this.params.preparedPlan &&
|
|
792
817
|
this.params.resourceArchiveBinary &&
|
|
793
818
|
this.params.targetsToUpdate?.includes('resource')
|
|
794
819
|
) {
|
|
795
|
-
|
|
796
|
-
features: deviceFeatures,
|
|
797
|
-
firmwareType,
|
|
798
|
-
});
|
|
799
|
-
try {
|
|
800
|
-
return await this.runProtocolV2PreparedArtifacts(deviceFeatures, firmwareType);
|
|
801
|
-
} finally {
|
|
802
|
-
localMemoryHost.release();
|
|
803
|
-
}
|
|
820
|
+
return this.runProtocolV2DirectArtifacts({});
|
|
804
821
|
}
|
|
805
822
|
|
|
806
823
|
const hasPreparedComponentArtifacts = Object.values(this.params.componentArtifacts ?? {}).some(
|
|
@@ -809,16 +826,16 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
809
826
|
if (this.params.preparedPlan || hasPreparedComponentArtifacts) {
|
|
810
827
|
return this.runProtocolV2PreparedArtifacts(deviceFeatures, firmwareType);
|
|
811
828
|
}
|
|
812
|
-
const wantsResources = !!this.params.targetsToUpdate?.includes('resource');
|
|
813
829
|
|
|
814
830
|
let fwBinaryMap: ProtocolV2TargetBinary[] = [];
|
|
815
831
|
let bootloaderBinary: ArrayBuffer | null = null;
|
|
816
832
|
let installItems: ProtocolV2InstallItem[] | undefined;
|
|
833
|
+
let explicitInstallItems: ProtocolV2InstallItem[] | undefined;
|
|
817
834
|
try {
|
|
818
835
|
this.postTipMessage(FirmwareUpdateTipMessage.StartDownloadFirmware);
|
|
819
836
|
fwBinaryMap = this.collectExplicitTargetBinaries();
|
|
820
837
|
bootloaderBinary = this.prepareBootloaderBinary();
|
|
821
|
-
|
|
838
|
+
explicitInstallItems = this.buildProtocolV2InstallItems({
|
|
822
839
|
bootloaderBinary,
|
|
823
840
|
fwBinaryMap,
|
|
824
841
|
});
|
|
@@ -826,17 +843,9 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
826
843
|
const needsRemoteFirmware = this.params.targetsToUpdate?.length
|
|
827
844
|
? missingFirmwareTargets.length > 0
|
|
828
845
|
: explicitInstallItems.length === 0;
|
|
829
|
-
|
|
830
|
-
throw ERRORS.TypedError(
|
|
831
|
-
HardwareErrorCode.RuntimeError,
|
|
832
|
-
'Protocol V2 resource archive must be provided through a local or external PreparedPlan',
|
|
833
|
-
{
|
|
834
|
-
firmwareUpdateCode: 'FirmwareArtifactsNotPrepared',
|
|
835
|
-
}
|
|
836
|
-
);
|
|
837
|
-
}
|
|
846
|
+
const needsSdkManagedArtifacts = needsRemoteFirmware || wantsResources;
|
|
838
847
|
if (
|
|
839
|
-
|
|
848
|
+
needsSdkManagedArtifacts &&
|
|
840
849
|
(this.params.artifactReader ||
|
|
841
850
|
DataManager.getSettings('firmwareManifestMode') === 'external-only')
|
|
842
851
|
) {
|
|
@@ -848,9 +857,13 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
848
857
|
}
|
|
849
858
|
);
|
|
850
859
|
}
|
|
851
|
-
if (
|
|
860
|
+
if (needsSdkManagedArtifacts) {
|
|
852
861
|
// Remote updates must use a freshly fetched config before any reboot or file write.
|
|
853
|
-
await DataManager.forceReloadData(
|
|
862
|
+
await DataManager.forceReloadData({
|
|
863
|
+
requireResources: wantsResources,
|
|
864
|
+
resourceDeviceType:
|
|
865
|
+
capabilityDeviceType === EDeviceType.Neo ? EDeviceType.Neo : EDeviceType.Pro2,
|
|
866
|
+
});
|
|
854
867
|
}
|
|
855
868
|
if (needsRemoteFirmware) {
|
|
856
869
|
const remoteBinaries = await this.prepareRemoteProtocolV2Binaries(
|
|
@@ -861,6 +874,23 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
861
874
|
bootloaderBinary = remoteBinaries.bootloaderBinary;
|
|
862
875
|
fwBinaryMap = remoteBinaries.fwBinaryMap;
|
|
863
876
|
installItems = remoteBinaries.installItems;
|
|
877
|
+
} else {
|
|
878
|
+
const selectedInstallItems = this.filterProtocolV2LocalInstallItems(explicitInstallItems);
|
|
879
|
+
bootloaderBinary =
|
|
880
|
+
selectedInstallItems.find(item => item.kind === 'bootloader')?.binary ?? null;
|
|
881
|
+
fwBinaryMap = selectedInstallItems
|
|
882
|
+
.filter(item => item.kind === 'firmware')
|
|
883
|
+
.map(item => ({
|
|
884
|
+
fileName: item.fileName,
|
|
885
|
+
binary: item.binary,
|
|
886
|
+
targetId: item.targetId,
|
|
887
|
+
}));
|
|
888
|
+
}
|
|
889
|
+
if (wantsResources) {
|
|
890
|
+
this.params.resourceArchiveBinary = await this.downloadRemoteProtocolV2ResourceArchive(
|
|
891
|
+
deviceFeatures,
|
|
892
|
+
firmwareType
|
|
893
|
+
);
|
|
864
894
|
}
|
|
865
895
|
this.postTipMessage(FirmwareUpdateTipMessage.FinishDownloadFirmware);
|
|
866
896
|
} catch (err) {
|
|
@@ -878,6 +908,13 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
878
908
|
throw normalizeFirmwarePreparationError(err);
|
|
879
909
|
}
|
|
880
910
|
|
|
911
|
+
if (wantsResources && this.params.resourceArchiveBinary) {
|
|
912
|
+
return this.runProtocolV2DirectArtifacts({
|
|
913
|
+
availableInstallItems: installItems ?? explicitInstallItems,
|
|
914
|
+
announceDownload: false,
|
|
915
|
+
});
|
|
916
|
+
}
|
|
917
|
+
|
|
881
918
|
if (!bootloaderBinary && fwBinaryMap.length === 0 && !installItems?.length) {
|
|
882
919
|
throw ERRORS.TypedError(
|
|
883
920
|
HardwareErrorCode.FirmwareUpdateDownloadFailed,
|
|
@@ -1028,131 +1065,69 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1028
1065
|
return installSources;
|
|
1029
1066
|
}
|
|
1030
1067
|
|
|
1031
|
-
private async
|
|
1032
|
-
|
|
1033
|
-
|
|
1068
|
+
private async runProtocolV2DirectArtifacts({
|
|
1069
|
+
availableInstallItems,
|
|
1070
|
+
announceDownload = true,
|
|
1034
1071
|
}: {
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
})
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
const localComponentTargets = new Set(
|
|
1049
|
-
installItems.flatMap(item => {
|
|
1050
|
-
const target = PROTOCOL_V2_UPDATE_TARGET_BY_TARGET_ID.get(item.targetId);
|
|
1051
|
-
return target ? [target] : [];
|
|
1052
|
-
})
|
|
1053
|
-
);
|
|
1054
|
-
const missingTarget = Array.from(requestedComponentTargets).find(
|
|
1055
|
-
target => !localComponentTargets.has(target)
|
|
1056
|
-
);
|
|
1057
|
-
if (missingTarget) {
|
|
1058
|
-
throw ERRORS.TypedError(
|
|
1059
|
-
HardwareErrorCode.RuntimeError,
|
|
1060
|
-
`Protocol V2 local update has no binary for requested target ${missingTarget}`,
|
|
1061
|
-
{
|
|
1062
|
-
firmwareUpdateCode: 'FirmwareArtifactsNotPrepared',
|
|
1063
|
-
artifactName: missingTarget,
|
|
1064
|
-
}
|
|
1072
|
+
availableInstallItems?: ProtocolV2InstallItem[];
|
|
1073
|
+
announceDownload?: boolean;
|
|
1074
|
+
}) {
|
|
1075
|
+
try {
|
|
1076
|
+
if (announceDownload) {
|
|
1077
|
+
this.postTipMessage(FirmwareUpdateTipMessage.StartDownloadFirmware);
|
|
1078
|
+
}
|
|
1079
|
+
const installItems = this.filterProtocolV2LocalInstallItems(
|
|
1080
|
+
availableInstallItems ??
|
|
1081
|
+
this.buildProtocolV2InstallItems({
|
|
1082
|
+
bootloaderBinary: this.prepareBootloaderBinary(),
|
|
1083
|
+
fwBinaryMap: this.collectExplicitTargetBinaries(),
|
|
1084
|
+
})
|
|
1065
1085
|
);
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1086
|
+
const installSources = await Promise.all(
|
|
1087
|
+
installItems.map(async item => ({
|
|
1088
|
+
fileName: item.fileName,
|
|
1089
|
+
source: await this.openProtocolV2MemorySource(item.binary),
|
|
1090
|
+
targetId: item.targetId,
|
|
1091
|
+
kind: item.kind,
|
|
1092
|
+
}))
|
|
1093
|
+
);
|
|
1094
|
+
const resourceSources = this.params.resourceArchiveBinary
|
|
1095
|
+
? await this.createProtocolV2ResourceSourcesFromArchive(this.params.resourceArchiveBinary)
|
|
1096
|
+
: [];
|
|
1097
|
+
if (installSources.length === 0 && resourceSources.length === 0) {
|
|
1074
1098
|
throw ERRORS.TypedError(
|
|
1075
|
-
HardwareErrorCode.
|
|
1076
|
-
|
|
1077
|
-
{ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
|
|
1099
|
+
HardwareErrorCode.FirmwareUpdateDownloadFailed,
|
|
1100
|
+
'No firmware to update'
|
|
1078
1101
|
);
|
|
1079
1102
|
}
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
expectedSize: item.binary.byteLength,
|
|
1087
|
-
expectedSha256: bytesToHex(sha256(new Uint8Array(item.binary))),
|
|
1088
|
-
...(this.params.expectedTargetVersions?.[target]
|
|
1089
|
-
? { targetVersion: this.params.expectedTargetVersions[target] }
|
|
1090
|
-
: {}),
|
|
1103
|
+
if (announceDownload) {
|
|
1104
|
+
this.postTipMessage(FirmwareUpdateTipMessage.FinishDownloadFirmware);
|
|
1105
|
+
}
|
|
1106
|
+
return await this.executeProtocolV2SourceUpdate({
|
|
1107
|
+
installSources,
|
|
1108
|
+
resourceSources,
|
|
1091
1109
|
});
|
|
1092
|
-
|
|
1110
|
+
} finally {
|
|
1111
|
+
await this.closeProtocolV2PreparedSources();
|
|
1093
1112
|
}
|
|
1113
|
+
}
|
|
1094
1114
|
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
const
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
artifactId: resourceArtifactId,
|
|
1109
|
-
binary: resourceArchive.binary,
|
|
1110
|
-
materializedEntries: resourceArchive.materializedEntries,
|
|
1111
|
-
});
|
|
1112
|
-
|
|
1113
|
-
const plan = buildProtocolV2LocalFirmwareUpdatePlan({
|
|
1114
|
-
features,
|
|
1115
|
-
firmwareType,
|
|
1116
|
-
platform: this.params.platform,
|
|
1117
|
-
artifacts: planArtifacts,
|
|
1118
|
-
});
|
|
1119
|
-
let memoryHost: FirmwareUpdateV4MemoryHost | undefined;
|
|
1120
|
-
try {
|
|
1121
|
-
memoryHost = prepareFirmwareUpdateV4MemoryHost({
|
|
1122
|
-
sdk: {
|
|
1123
|
-
prepareFirmwareUpdatePlan,
|
|
1124
|
-
registerFirmwareUpdateHostBinding,
|
|
1125
|
-
unregisterFirmwareUpdateHostBinding,
|
|
1126
|
-
},
|
|
1127
|
-
plan,
|
|
1128
|
-
artifacts: memoryArtifacts,
|
|
1129
|
-
});
|
|
1130
|
-
const preparedPlan = validateFirmwareUpdatePreparedPlan(memoryHost.preparedPlan);
|
|
1131
|
-
assertFirmwareUpdatePreparedPlanBinding({
|
|
1132
|
-
preparedPlan,
|
|
1133
|
-
executor: 'v4',
|
|
1134
|
-
platform: this.params.platform,
|
|
1135
|
-
scopeTargets: [],
|
|
1136
|
-
bindings: [],
|
|
1137
|
-
});
|
|
1138
|
-
assertFirmwareUpdatePreparedPlanDeviceIdentity({
|
|
1139
|
-
preparedPlan,
|
|
1140
|
-
deviceIdentity: this.protocolV2ExpectedSerialNumber,
|
|
1141
|
-
deviceModel: this.getProtocolV2PreparedPlanDeviceModel(features),
|
|
1115
|
+
private async createProtocolV2ResourceSourcesFromArchive(
|
|
1116
|
+
binary: ArrayBuffer
|
|
1117
|
+
): Promise<ProtocolV2ResourceBundleSource[]> {
|
|
1118
|
+
const archive = await this.prepareProtocolV2LocalResourceArchive(binary);
|
|
1119
|
+
const sources: ProtocolV2ResourceBundleSource[] = [];
|
|
1120
|
+
for (const resource of archive.resources) {
|
|
1121
|
+
sources.push({
|
|
1122
|
+
name: resource.entryName.split('/').pop() ?? resource.entryName,
|
|
1123
|
+
source: await this.openProtocolV2MemorySource(resource.binary),
|
|
1124
|
+
devicePath: resource.header.devicePath,
|
|
1125
|
+
version: resource.header.version,
|
|
1126
|
+
payloadHash: resource.header.payloadHash,
|
|
1127
|
+
headerHash: resource.header.headerHash,
|
|
1142
1128
|
});
|
|
1143
|
-
const hostBinding = resolveFirmwareUpdateHostBinding(
|
|
1144
|
-
memoryHost.hostBindingGeneration,
|
|
1145
|
-
preparedPlan.preparedPlanDigest
|
|
1146
|
-
);
|
|
1147
|
-
this.params.preparedPlan = preparedPlan;
|
|
1148
|
-
this.params.targetsToUpdate = [...preparedPlan.targetsToUpdate] as FirmwareUpdateV4Target[];
|
|
1149
|
-
this.params.artifactReader = hostBinding.artifactReader;
|
|
1150
|
-
this.params.componentArtifacts = undefined;
|
|
1151
|
-
return memoryHost;
|
|
1152
|
-
} catch (error) {
|
|
1153
|
-
memoryHost?.release();
|
|
1154
|
-
throw error;
|
|
1155
1129
|
}
|
|
1130
|
+
return sources;
|
|
1156
1131
|
}
|
|
1157
1132
|
|
|
1158
1133
|
private async prepareProtocolV2LocalResourceArchive(
|
|
@@ -1165,132 +1140,78 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1165
1140
|
{ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
|
|
1166
1141
|
);
|
|
1167
1142
|
}
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
) {
|
|
1143
|
+
let zip: JSZip;
|
|
1144
|
+
try {
|
|
1145
|
+
zip = await JSZip.loadAsync(binary);
|
|
1146
|
+
} catch {
|
|
1173
1147
|
throw ERRORS.TypedError(
|
|
1174
1148
|
HardwareErrorCode.RuntimeError,
|
|
1175
|
-
'Protocol V2
|
|
1149
|
+
'Protocol V2 resource ZIP cannot be parsed',
|
|
1176
1150
|
{ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
|
|
1177
1151
|
);
|
|
1178
1152
|
}
|
|
1179
|
-
const
|
|
1180
|
-
|
|
1153
|
+
const zipEntries = Object.values(zip.files);
|
|
1154
|
+
const resourceEntries = zipEntries.filter(
|
|
1155
|
+
entry => !entry.dir && isProtocolV2ResourceArchiveEntryName(entry.name)
|
|
1156
|
+
);
|
|
1157
|
+
if (
|
|
1158
|
+
resourceEntries.length === 0 ||
|
|
1159
|
+
resourceEntries.length > PROTOCOL_V2_RESOURCE_FILE_MAX_COUNT
|
|
1160
|
+
) {
|
|
1181
1161
|
throw ERRORS.TypedError(
|
|
1182
1162
|
HardwareErrorCode.RuntimeError,
|
|
1183
|
-
'Protocol V2
|
|
1163
|
+
'Protocol V2 resource ZIP has no valid resource package set',
|
|
1184
1164
|
{ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
|
|
1185
1165
|
);
|
|
1186
1166
|
}
|
|
1187
|
-
|
|
1188
|
-
let
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
entry.name === 'manifest.json'
|
|
1195
|
-
? PROTOCOL_V2_RESOURCE_MANIFEST_MAX_BYTES
|
|
1196
|
-
: PROTOCOL_V2_RESOURCE_TOTAL_MAX_BYTES;
|
|
1167
|
+
|
|
1168
|
+
let totalSize = 0;
|
|
1169
|
+
const resources: ProtocolV2LocalResourceArchive['resources'] = [];
|
|
1170
|
+
const devicePaths = new Set<string>();
|
|
1171
|
+
for (const entry of resourceEntries) {
|
|
1172
|
+
const { compressedSize, uncompressedSize } = getProtocolV2ZipEntrySizes(entry);
|
|
1173
|
+
totalSize += uncompressedSize;
|
|
1197
1174
|
if (
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
PROTOCOL_V2_RESOURCE_TOTAL_MAX_BYTES + PROTOCOL_V2_RESOURCE_MANIFEST_MAX_BYTES
|
|
1175
|
+
compressedSize > binary.byteLength ||
|
|
1176
|
+
uncompressedSize > PROTOCOL_V2_RESOURCE_TOTAL_MAX_BYTES ||
|
|
1177
|
+
totalSize > PROTOCOL_V2_RESOURCE_TOTAL_MAX_BYTES
|
|
1202
1178
|
) {
|
|
1203
1179
|
throw ERRORS.TypedError(
|
|
1204
1180
|
HardwareErrorCode.RuntimeError,
|
|
1205
|
-
|
|
1181
|
+
`Protocol V2 resource package size is invalid: ${entry.name}`,
|
|
1206
1182
|
{ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
|
|
1207
1183
|
);
|
|
1208
1184
|
}
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
if (!manifestEntry) {
|
|
1212
|
-
throw ERRORS.TypedError(
|
|
1213
|
-
HardwareErrorCode.RuntimeError,
|
|
1214
|
-
'Protocol V2 local resource ZIP has no manifest.json',
|
|
1215
|
-
{ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
|
|
1216
|
-
);
|
|
1217
|
-
}
|
|
1218
|
-
const manifestBinary = await manifestEntry.async('arraybuffer');
|
|
1219
|
-
if (
|
|
1220
|
-
manifestBinary.byteLength <= 0 ||
|
|
1221
|
-
manifestBinary.byteLength > PROTOCOL_V2_RESOURCE_MANIFEST_MAX_BYTES
|
|
1222
|
-
) {
|
|
1223
|
-
throw ERRORS.TypedError(
|
|
1224
|
-
HardwareErrorCode.RuntimeError,
|
|
1225
|
-
'Protocol V2 local resource manifest size is invalid',
|
|
1226
|
-
{ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
|
|
1227
|
-
);
|
|
1228
|
-
}
|
|
1229
|
-
|
|
1230
|
-
let manifestValue: unknown;
|
|
1231
|
-
try {
|
|
1232
|
-
manifestValue = JSON.parse(new TextDecoder().decode(manifestBinary));
|
|
1233
|
-
} catch (error) {
|
|
1234
|
-
throw ERRORS.TypedError(
|
|
1235
|
-
HardwareErrorCode.RuntimeError,
|
|
1236
|
-
`Protocol V2 local resource manifest is invalid: ${String(error)}`,
|
|
1237
|
-
{ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
|
|
1238
|
-
);
|
|
1239
|
-
}
|
|
1240
|
-
const manifest = parseProtocolV2ResourceManifest(manifestValue);
|
|
1241
|
-
const selectedFiles = selectProtocolV2ResourceManifestFiles({
|
|
1242
|
-
manifest,
|
|
1243
|
-
targetsToUpdate: this.params.targetsToUpdate ?? [],
|
|
1244
|
-
});
|
|
1245
|
-
const expectedEntryNames = new Set([
|
|
1246
|
-
'manifest.json',
|
|
1247
|
-
...selectedFiles.map(file => file.archive_path),
|
|
1248
|
-
]);
|
|
1249
|
-
if (
|
|
1250
|
-
entries.length !== expectedEntryNames.size ||
|
|
1251
|
-
entries.some(entry => !expectedEntryNames.has(entry.name))
|
|
1252
|
-
) {
|
|
1253
|
-
throw ERRORS.TypedError(
|
|
1254
|
-
HardwareErrorCode.RuntimeError,
|
|
1255
|
-
'Protocol V2 local resource ZIP contains an unexpected entry',
|
|
1256
|
-
{ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
|
|
1257
|
-
);
|
|
1258
|
-
}
|
|
1259
|
-
|
|
1260
|
-
let totalSize = 0;
|
|
1261
|
-
const materializedEntries: FirmwareMemoryArtifactEntry[] = [
|
|
1262
|
-
{ entryName: 'manifest.json', binary: manifestBinary },
|
|
1263
|
-
];
|
|
1264
|
-
for (const file of selectedFiles) {
|
|
1265
|
-
const entry = zip.file(file.archive_path);
|
|
1266
|
-
if (!entry) {
|
|
1185
|
+
const fileBinary = await entry.async('arraybuffer');
|
|
1186
|
+
if (fileBinary.byteLength !== uncompressedSize) {
|
|
1267
1187
|
throw ERRORS.TypedError(
|
|
1268
1188
|
HardwareErrorCode.RuntimeError,
|
|
1269
|
-
`Protocol V2
|
|
1270
|
-
{ firmwareUpdateCode: '
|
|
1189
|
+
`Protocol V2 resource package is incomplete: ${entry.name}`,
|
|
1190
|
+
{ firmwareUpdateCode: 'FirmwareArtifactReceiptMismatch' }
|
|
1271
1191
|
);
|
|
1272
1192
|
}
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1193
|
+
let header: ProtocolV2ResourcePackageHeader;
|
|
1194
|
+
try {
|
|
1195
|
+
header = parseProtocolV2ResourcePackage(fileBinary);
|
|
1196
|
+
} catch (error) {
|
|
1276
1197
|
throw ERRORS.TypedError(
|
|
1277
1198
|
HardwareErrorCode.RuntimeError,
|
|
1278
|
-
`Protocol V2
|
|
1199
|
+
`Protocol V2 resource package is invalid: ${entry.name}: ${String(error)}`,
|
|
1279
1200
|
{ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
|
|
1280
1201
|
);
|
|
1281
1202
|
}
|
|
1282
|
-
const
|
|
1283
|
-
|
|
1284
|
-
if (fileBinary.byteLength !== file.size || digest !== file.sha256.toLowerCase()) {
|
|
1203
|
+
const canonicalDevicePath = header.devicePath.toLowerCase();
|
|
1204
|
+
if (devicePaths.has(canonicalDevicePath)) {
|
|
1285
1205
|
throw ERRORS.TypedError(
|
|
1286
1206
|
HardwareErrorCode.RuntimeError,
|
|
1287
|
-
`Protocol V2
|
|
1288
|
-
{ firmwareUpdateCode: '
|
|
1207
|
+
`Protocol V2 resource ZIP contains duplicate device path: ${header.devicePath}`,
|
|
1208
|
+
{ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
|
|
1289
1209
|
);
|
|
1290
1210
|
}
|
|
1291
|
-
|
|
1211
|
+
devicePaths.add(canonicalDevicePath);
|
|
1212
|
+
resources.push({ entryName: entry.name, binary: fileBinary, header });
|
|
1292
1213
|
}
|
|
1293
|
-
return { binary,
|
|
1214
|
+
return { binary, resources };
|
|
1294
1215
|
}
|
|
1295
1216
|
|
|
1296
1217
|
private async prepareProtocolV2ResourceSources(): Promise<ProtocolV2ResourceBundleSource[]> {
|
|
@@ -1355,110 +1276,20 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1355
1276
|
);
|
|
1356
1277
|
}
|
|
1357
1278
|
|
|
1358
|
-
//
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
const entriesByName = new Map(entries.map(entry => [entry.entryName, entry] as const));
|
|
1362
|
-
if (
|
|
1363
|
-
entries.length !== verifiedArchive.materializedEntries.length ||
|
|
1364
|
-
verifiedArchive.materializedEntries.some(entry => {
|
|
1365
|
-
const preparedEntry = entriesByName.get(entry.entryName);
|
|
1366
|
-
const digest = bytesToHex(sha256(new Uint8Array(entry.binary)));
|
|
1367
|
-
return (
|
|
1368
|
-
!preparedEntry ||
|
|
1369
|
-
preparedEntry.artifact.size !== entry.binary.byteLength ||
|
|
1370
|
-
preparedEntry.artifact.sha256.toLowerCase() !== digest
|
|
1371
|
-
);
|
|
1372
|
-
})
|
|
1373
|
-
) {
|
|
1374
|
-
throw ERRORS.TypedError(
|
|
1375
|
-
HardwareErrorCode.RuntimeError,
|
|
1376
|
-
'Protocol V2 prepared resource entries do not match the approved archive',
|
|
1377
|
-
{ firmwareUpdateCode: 'FirmwareArtifactReceiptMismatch' }
|
|
1378
|
-
);
|
|
1379
|
-
}
|
|
1380
|
-
|
|
1381
|
-
const verifiedEntriesByName = new Map(
|
|
1382
|
-
verifiedArchive.materializedEntries.map(entry => [entry.entryName, entry.binary] as const)
|
|
1383
|
-
);
|
|
1384
|
-
const manifestBinary = verifiedEntriesByName.get('manifest.json');
|
|
1385
|
-
if (!manifestBinary) {
|
|
1386
|
-
throw ERRORS.TypedError(
|
|
1387
|
-
HardwareErrorCode.RuntimeError,
|
|
1388
|
-
'Protocol V2 prepared resource archive has no valid manifest.json',
|
|
1389
|
-
{ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
|
|
1390
|
-
);
|
|
1391
|
-
}
|
|
1392
|
-
|
|
1393
|
-
let manifestValue: unknown;
|
|
1394
|
-
try {
|
|
1395
|
-
manifestValue = JSON.parse(new TextDecoder().decode(manifestBinary));
|
|
1396
|
-
} catch (error) {
|
|
1397
|
-
throw ERRORS.TypedError(
|
|
1398
|
-
HardwareErrorCode.RuntimeError,
|
|
1399
|
-
`Protocol V2 prepared resource manifest is invalid: ${String(error)}`,
|
|
1400
|
-
{ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
|
|
1401
|
-
);
|
|
1402
|
-
}
|
|
1403
|
-
const manifest = parseProtocolV2ResourceManifest(manifestValue);
|
|
1404
|
-
const selectedFiles = selectProtocolV2ResourceManifestFiles({
|
|
1405
|
-
manifest,
|
|
1406
|
-
targetsToUpdate: this.params.targetsToUpdate ?? [],
|
|
1407
|
-
});
|
|
1408
|
-
if (selectedFiles.length > PROTOCOL_V2_RESOURCE_FILE_MAX_COUNT) {
|
|
1409
|
-
throw ERRORS.TypedError(
|
|
1410
|
-
HardwareErrorCode.RuntimeError,
|
|
1411
|
-
'Protocol V2 prepared resource archive contains too many files',
|
|
1412
|
-
{ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
|
|
1413
|
-
);
|
|
1414
|
-
}
|
|
1415
|
-
|
|
1416
|
-
let totalSize = 0;
|
|
1417
|
-
const sources: ProtocolV2ResourceBundleSource[] = [];
|
|
1418
|
-
for (const [index, file] of selectedFiles.entries()) {
|
|
1419
|
-
const binary = verifiedEntriesByName.get(file.archive_path);
|
|
1420
|
-
if (!binary || binary.byteLength !== file.size) {
|
|
1421
|
-
throw ERRORS.TypedError(
|
|
1422
|
-
HardwareErrorCode.RuntimeError,
|
|
1423
|
-
`Protocol V2 prepared resource file does not match manifest: ${file.archive_path}`,
|
|
1424
|
-
{ firmwareUpdateCode: 'FirmwareArtifactReceiptMismatch' }
|
|
1425
|
-
);
|
|
1426
|
-
}
|
|
1427
|
-
totalSize += binary.byteLength;
|
|
1428
|
-
if (totalSize > PROTOCOL_V2_RESOURCE_TOTAL_MAX_BYTES) {
|
|
1429
|
-
throw ERRORS.TypedError(
|
|
1430
|
-
HardwareErrorCode.RuntimeError,
|
|
1431
|
-
'Protocol V2 prepared resource archive exceeds the total size limit',
|
|
1432
|
-
{ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
|
|
1433
|
-
);
|
|
1434
|
-
}
|
|
1435
|
-
// 使用从获批 ZIP 中提取的规范字节,避免宿主在校验后替换 entry reader 内容。
|
|
1436
|
-
const source = await this.openProtocolV2MemorySource(binary);
|
|
1437
|
-
const header =
|
|
1438
|
-
source.size >= PROTOCOL_V2_OKPP_HEADER_SIZE
|
|
1439
|
-
? parseProtocolV2OkppHeader(
|
|
1440
|
-
new Uint8Array(await source.readAt(0, PROTOCOL_V2_OKPP_HEADER_SIZE))
|
|
1441
|
-
)
|
|
1442
|
-
: null;
|
|
1443
|
-
sources.push({
|
|
1444
|
-
name: file.original_name || `resource-${index}`,
|
|
1445
|
-
source,
|
|
1446
|
-
devicePath: file.device_path,
|
|
1447
|
-
...(header
|
|
1448
|
-
? {
|
|
1449
|
-
version: header.version,
|
|
1450
|
-
payloadHash: header.payloadHash,
|
|
1451
|
-
headerHash: header.headerHash,
|
|
1452
|
-
}
|
|
1453
|
-
: {}),
|
|
1454
|
-
});
|
|
1455
|
-
}
|
|
1456
|
-
return sources;
|
|
1279
|
+
// The verified ZIP bytes are authoritative. Non-okpkg entries are release
|
|
1280
|
+
// metadata only; each RESOURCE package carries its own device path.
|
|
1281
|
+
return this.createProtocolV2ResourceSourcesFromArchive(archiveBinary);
|
|
1457
1282
|
}
|
|
1458
1283
|
|
|
1459
|
-
private async runProtocolV2PreparedArtifacts(
|
|
1284
|
+
private async runProtocolV2PreparedArtifacts(
|
|
1285
|
+
features: Features,
|
|
1286
|
+
firmwareType: EFirmwareType,
|
|
1287
|
+
announceDownload = true
|
|
1288
|
+
) {
|
|
1460
1289
|
try {
|
|
1461
|
-
|
|
1290
|
+
if (announceDownload) {
|
|
1291
|
+
this.postTipMessage(FirmwareUpdateTipMessage.StartDownloadFirmware);
|
|
1292
|
+
}
|
|
1462
1293
|
const installSources = await this.prepareProtocolV2InstallSources(firmwareType, features);
|
|
1463
1294
|
const resourceSources = await this.prepareProtocolV2ResourceSources();
|
|
1464
1295
|
if (installSources.length === 0 && resourceSources.length === 0) {
|
|
@@ -1467,7 +1298,9 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1467
1298
|
'No firmware to update'
|
|
1468
1299
|
);
|
|
1469
1300
|
}
|
|
1470
|
-
|
|
1301
|
+
if (announceDownload) {
|
|
1302
|
+
this.postTipMessage(FirmwareUpdateTipMessage.FinishDownloadFirmware);
|
|
1303
|
+
}
|
|
1471
1304
|
|
|
1472
1305
|
return await this.executeProtocolV2SourceUpdate({
|
|
1473
1306
|
installSources,
|
|
@@ -1522,21 +1355,20 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1522
1355
|
const expected = this.params?.expectedTargetVersions;
|
|
1523
1356
|
if (!expected) return;
|
|
1524
1357
|
const features = this.protocolV2LatestFinalFeatures;
|
|
1525
|
-
const
|
|
1526
|
-
const applicationTargets = requestedTargets.filter(
|
|
1527
|
-
target => target === 'app_v1' || target === 'app_v2'
|
|
1528
|
-
);
|
|
1358
|
+
const deviceInfo = this.protocolV2LatestFinalDeviceInfo;
|
|
1529
1359
|
const visibleVersions: Partial<Record<FirmwareUpdateV4Target, string>> = {
|
|
1530
1360
|
boot: features ? getDeviceBootloaderVersion(features).join('.') : undefined,
|
|
1361
|
+
// Protocol V2 firmware exposes P1 as the device firmware version after reboot.
|
|
1362
|
+
app_v1: features
|
|
1363
|
+
? getDeviceFirmwareVersion(features).join('.')
|
|
1364
|
+
: deviceInfo?.main_mcu?.application?.version,
|
|
1365
|
+
app_v2: deviceInfo?.main_mcu?.application_data?.version,
|
|
1531
1366
|
coprocessor: features ? getDeviceBLEFirmwareVersion(features).join('.') : undefined,
|
|
1532
1367
|
se01: features?.se01Version ?? undefined,
|
|
1533
1368
|
se02: features?.se02Version ?? undefined,
|
|
1534
1369
|
se03: features?.se03Version ?? undefined,
|
|
1535
1370
|
se04: features?.se04Version ?? undefined,
|
|
1536
1371
|
};
|
|
1537
|
-
if (features && applicationTargets.length === 1) {
|
|
1538
|
-
visibleVersions[applicationTargets[0]] = getDeviceFirmwareVersion(features).join('.');
|
|
1539
|
-
}
|
|
1540
1372
|
const expectedEntries = (
|
|
1541
1373
|
Object.entries(expected) as Array<[FirmwareUpdateV4Target, string]>
|
|
1542
1374
|
).filter(([target]) => !targets || targets.includes(target));
|
|
@@ -1553,9 +1385,11 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1553
1385
|
Math.floor(statusVersion / 0x100) % 0x100
|
|
1554
1386
|
}.${statusVersion % 0x100}`;
|
|
1555
1387
|
if (!observedVersion) {
|
|
1556
|
-
|
|
1388
|
+
const hasCompleteTargetEvidence =
|
|
1389
|
+
targetId !== undefined && this.protocolV2CompletedTargetIds.has(targetId);
|
|
1390
|
+
if (hasCompleteTargetEvidence) {
|
|
1557
1391
|
Log.warn(
|
|
1558
|
-
`Protocol V2 target ${target} has no observable final version;
|
|
1392
|
+
`Protocol V2 target ${target} has no observable final version; install completion is authoritative`
|
|
1559
1393
|
);
|
|
1560
1394
|
} else {
|
|
1561
1395
|
throw ERRORS.TypedError(
|
|
@@ -1572,37 +1406,23 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1572
1406
|
}
|
|
1573
1407
|
}
|
|
1574
1408
|
|
|
1575
|
-
private
|
|
1576
|
-
|
|
1577
|
-
|
|
1409
|
+
private async getProtocolV2DeviceFeatures(): Promise<Features> {
|
|
1410
|
+
let { features } = this.device;
|
|
1411
|
+
if (typeof this.device.getFeatures === 'function') {
|
|
1412
|
+
features ??= await this.device.getFeatures();
|
|
1578
1413
|
}
|
|
1579
|
-
if (
|
|
1580
|
-
|
|
1414
|
+
if (!features) {
|
|
1415
|
+
throw ERRORS.TypedError(HardwareErrorCode.RuntimeError, 'Device features not available');
|
|
1581
1416
|
}
|
|
1582
|
-
const
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
);
|
|
1586
|
-
if (this.params.bootloaderBinary) targets.add('boot');
|
|
1587
|
-
if (this.params.applicationP1Binary) targets.add('app_v1');
|
|
1588
|
-
if (this.params.applicationP2Binary) targets.add('app_v2');
|
|
1589
|
-
if (this.params.coprocessorBinary) targets.add('coprocessor');
|
|
1590
|
-
if (this.params.se01Binary) targets.add('se01');
|
|
1591
|
-
if (this.params.se02Binary) targets.add('se02');
|
|
1592
|
-
if (this.params.se03Binary) targets.add('se03');
|
|
1593
|
-
if (this.params.se04Binary) targets.add('se04');
|
|
1594
|
-
return Array.from(targets);
|
|
1595
|
-
}
|
|
1596
|
-
|
|
1597
|
-
private async getProtocolV2DeviceFeatures(): Promise<Features> {
|
|
1598
|
-
if (this.device.features) {
|
|
1599
|
-
return this.device.features;
|
|
1600
|
-
}
|
|
1601
|
-
if (typeof this.device.getFeatures === 'function') {
|
|
1602
|
-
const features = await this.device.getFeatures();
|
|
1603
|
-
if (features) return features;
|
|
1417
|
+
const deviceType = this.device.getCurrentDeviceType();
|
|
1418
|
+
if (deviceType !== EDeviceType.Pro2 && deviceType !== EDeviceType.Neo) {
|
|
1419
|
+
return features;
|
|
1604
1420
|
}
|
|
1605
|
-
|
|
1421
|
+
return {
|
|
1422
|
+
...features,
|
|
1423
|
+
deviceType,
|
|
1424
|
+
firmwareType: this.device.getCurrentFirmwareType(),
|
|
1425
|
+
};
|
|
1606
1426
|
}
|
|
1607
1427
|
|
|
1608
1428
|
private getProtocolV2SerialNumber(deviceInfo: ProtocolV2DeviceInfo) {
|
|
@@ -1698,6 +1518,17 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1698
1518
|
.filter(target => !preparedTargets.has(target));
|
|
1699
1519
|
}
|
|
1700
1520
|
|
|
1521
|
+
private filterProtocolV2LocalInstallItems(installItems: ProtocolV2InstallItem[]) {
|
|
1522
|
+
if (!this.protocolV2HasExplicitTargetSelection) {
|
|
1523
|
+
return installItems;
|
|
1524
|
+
}
|
|
1525
|
+
const requestedTargets = new Set(this.params.targetsToUpdate ?? []);
|
|
1526
|
+
return installItems.filter(item => {
|
|
1527
|
+
const target = PROTOCOL_V2_UPDATE_TARGET_BY_TARGET_ID.get(item.targetId);
|
|
1528
|
+
return target !== undefined && requestedTargets.has(target);
|
|
1529
|
+
});
|
|
1530
|
+
}
|
|
1531
|
+
|
|
1701
1532
|
private buildProtocolV2InstallItems({
|
|
1702
1533
|
bootloaderBinary,
|
|
1703
1534
|
fwBinaryMap,
|
|
@@ -1794,22 +1625,54 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1794
1625
|
`Protocol V2 firmware fingerprint mismatch: ${key}/${component.target}`
|
|
1795
1626
|
);
|
|
1796
1627
|
}
|
|
1797
|
-
const expectedPayloadHash = normalizeProtocolV2Hex(component.payloadHash);
|
|
1798
|
-
if (expectedPayloadHash) {
|
|
1799
|
-
const header = parseProtocolV2OkppHeader(toProtocolV2Bytes(binary));
|
|
1800
|
-
if (!header || header.payloadHash !== expectedPayloadHash) {
|
|
1801
|
-
throw ERRORS.TypedError(
|
|
1802
|
-
HardwareErrorCode.RuntimeError,
|
|
1803
|
-
`Protocol V2 firmware payload hash mismatch: ${key}/${component.target}`
|
|
1804
|
-
);
|
|
1805
|
-
}
|
|
1806
|
-
}
|
|
1807
1628
|
return {
|
|
1808
1629
|
...target,
|
|
1809
1630
|
binary,
|
|
1810
1631
|
};
|
|
1811
1632
|
}
|
|
1812
1633
|
|
|
1634
|
+
private async downloadRemoteProtocolV2ResourceArchive(
|
|
1635
|
+
features: Features,
|
|
1636
|
+
firmwareType: EFirmwareType
|
|
1637
|
+
): Promise<ArrayBuffer> {
|
|
1638
|
+
const deviceType = getDeviceType(features);
|
|
1639
|
+
if (deviceType !== EDeviceType.Pro2 && deviceType !== EDeviceType.Neo) {
|
|
1640
|
+
throw ERRORS.TypedError(
|
|
1641
|
+
HardwareErrorCode.RuntimeError,
|
|
1642
|
+
'Protocol V2 resource archive requires a Pro2 or Neo device'
|
|
1643
|
+
);
|
|
1644
|
+
}
|
|
1645
|
+
const release = DataManager.getFirmwareLatestRelease(features, firmwareType);
|
|
1646
|
+
const source = DataManager.getProtocolV2ResourceSource(release);
|
|
1647
|
+
const expectedSha256 = normalizeProtocolV2Hex(source?.archiveSha256);
|
|
1648
|
+
if (
|
|
1649
|
+
!source?.archiveUrl ||
|
|
1650
|
+
!Number.isSafeInteger(source.archiveSize) ||
|
|
1651
|
+
source.archiveSize <= 0 ||
|
|
1652
|
+
source.archiveSize > PROTOCOL_V2_RESOURCE_TOTAL_MAX_BYTES ||
|
|
1653
|
+
!expectedSha256 ||
|
|
1654
|
+
!/^[0-9a-f]{64}$/u.test(expectedSha256)
|
|
1655
|
+
) {
|
|
1656
|
+
throw ERRORS.TypedError(
|
|
1657
|
+
HardwareErrorCode.RuntimeError,
|
|
1658
|
+
'Protocol V2 resource archive integrity metadata is invalid',
|
|
1659
|
+
{ firmwareUpdateCode: 'FirmwarePlanInvalid' }
|
|
1660
|
+
);
|
|
1661
|
+
}
|
|
1662
|
+
const { binary } = await getSysResourceBinary(source.archiveUrl);
|
|
1663
|
+
if (
|
|
1664
|
+
binary.byteLength !== source.archiveSize ||
|
|
1665
|
+
bytesToHex(sha256(new Uint8Array(binary))) !== expectedSha256
|
|
1666
|
+
) {
|
|
1667
|
+
throw ERRORS.TypedError(
|
|
1668
|
+
HardwareErrorCode.RuntimeError,
|
|
1669
|
+
'Protocol V2 resource archive does not match the remote config',
|
|
1670
|
+
{ firmwareUpdateCode: 'FirmwareArtifactReceiptMismatch' }
|
|
1671
|
+
);
|
|
1672
|
+
}
|
|
1673
|
+
return binary;
|
|
1674
|
+
}
|
|
1675
|
+
|
|
1813
1676
|
private async prepareRemoteProtocolV2Binaries(
|
|
1814
1677
|
firmwareType: EFirmwareType,
|
|
1815
1678
|
features: Features,
|
|
@@ -1853,8 +1716,14 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1853
1716
|
: undefined;
|
|
1854
1717
|
if (updateTarget && targetsToUpdate.has(updateTarget)) {
|
|
1855
1718
|
const explicitInstallItem = explicitInstallItemByTargetId.get(target.targetId);
|
|
1856
|
-
|
|
1857
|
-
|
|
1719
|
+
let installItem = explicitInstallItem;
|
|
1720
|
+
if (!installItem) {
|
|
1721
|
+
installItem = await this.downloadRemoteProtocolV2Component(key, component);
|
|
1722
|
+
if (component.version) {
|
|
1723
|
+
this.params.expectedTargetVersions ??= {};
|
|
1724
|
+
this.params.expectedTargetVersions[updateTarget] = component.version.join('.');
|
|
1725
|
+
}
|
|
1726
|
+
}
|
|
1858
1727
|
if (installItem.kind === 'bootloader') {
|
|
1859
1728
|
bootloaderBinary = installItem.binary;
|
|
1860
1729
|
} else {
|
|
@@ -1893,6 +1762,12 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1893
1762
|
return `vol0:/${path}`;
|
|
1894
1763
|
}
|
|
1895
1764
|
|
|
1765
|
+
private getProtocolV2ResourceComparePath(devicePath: string) {
|
|
1766
|
+
return isProtocolV2BootResourcePackagePath(devicePath)
|
|
1767
|
+
? PROTOCOL_V2_BOOT_RESOURCE_PACKAGE_PATH
|
|
1768
|
+
: devicePath;
|
|
1769
|
+
}
|
|
1770
|
+
|
|
1896
1771
|
private async readProtocolV2DeviceFileHeader(path: string, expectedSize?: number) {
|
|
1897
1772
|
const typedCall = this.device.getCommands().typedCall.bind(this.device.getCommands());
|
|
1898
1773
|
const filePath = this.getProtocolV2ResourceFilePath(path);
|
|
@@ -1904,7 +1779,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1904
1779
|
!pathInfoRes.message?.exist ||
|
|
1905
1780
|
pathInfoRes.message?.directory ||
|
|
1906
1781
|
fileSize === undefined ||
|
|
1907
|
-
fileSize <
|
|
1782
|
+
fileSize < PROTOCOL_V2_RESOURCE_PACKAGE_HEADER_SIZE ||
|
|
1908
1783
|
(expectedSize !== undefined && fileSize !== expectedSize)
|
|
1909
1784
|
) {
|
|
1910
1785
|
return null;
|
|
@@ -1913,8 +1788,8 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1913
1788
|
const chunkSize = this.getProtocolV2FirmwareChunkSize('read');
|
|
1914
1789
|
const chunks: Uint8Array[] = [];
|
|
1915
1790
|
let offset = 0;
|
|
1916
|
-
while (offset <
|
|
1917
|
-
const readLen = Math.min(chunkSize,
|
|
1791
|
+
while (offset < PROTOCOL_V2_RESOURCE_PACKAGE_HEADER_SIZE) {
|
|
1792
|
+
const readLen = Math.min(chunkSize, PROTOCOL_V2_RESOURCE_PACKAGE_HEADER_SIZE - offset);
|
|
1918
1793
|
const res = await typedCall('FilesystemFileRead', 'FilesystemFile', {
|
|
1919
1794
|
file: {
|
|
1920
1795
|
path: filePath,
|
|
@@ -1936,7 +1811,11 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1936
1811
|
headerBytes.set(chunk, cursor);
|
|
1937
1812
|
cursor += chunk.byteLength;
|
|
1938
1813
|
});
|
|
1939
|
-
|
|
1814
|
+
try {
|
|
1815
|
+
return parseProtocolV2ResourcePackageHeader(headerBytes, fileSize);
|
|
1816
|
+
} catch {
|
|
1817
|
+
return null;
|
|
1818
|
+
}
|
|
1940
1819
|
}
|
|
1941
1820
|
|
|
1942
1821
|
/** Compare the downloaded and installed okpkg headers before transferring a resource. */
|
|
@@ -1951,7 +1830,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1951
1830
|
|
|
1952
1831
|
try {
|
|
1953
1832
|
const header = await this.readProtocolV2DeviceFileHeader(
|
|
1954
|
-
bundle.devicePath,
|
|
1833
|
+
this.getProtocolV2ResourceComparePath(bundle.devicePath),
|
|
1955
1834
|
bundle.source.size
|
|
1956
1835
|
);
|
|
1957
1836
|
if (!header) return false;
|
|
@@ -1966,6 +1845,9 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1966
1845
|
if (!expectedHeaderHash || header.headerHash !== expectedHeaderHash) return false;
|
|
1967
1846
|
return true;
|
|
1968
1847
|
} catch (error) {
|
|
1848
|
+
if (isBleStaleBondHardwareError(error)) {
|
|
1849
|
+
throw error;
|
|
1850
|
+
}
|
|
1969
1851
|
Log.log(`[FirmwareUpdateV4] RESC bundle ${bundle.name} header check failed: `, error);
|
|
1970
1852
|
return false;
|
|
1971
1853
|
}
|
|
@@ -1995,21 +1877,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1995
1877
|
return this.device.features?.mode === 'romloader';
|
|
1996
1878
|
}
|
|
1997
1879
|
|
|
1998
|
-
async
|
|
1999
|
-
// romloader is the first update environment and forwards targets to bootloader.
|
|
2000
|
-
// It rejects DeviceRebootType.Bootloader, so reuse the current connection.
|
|
2001
|
-
if (this.isProtocolV2RomloaderMode()) {
|
|
2002
|
-
Log.debug('Protocol V2 device is in romloader mode; start firmware update directly');
|
|
2003
|
-
this.protocolV2ExecutionInLoader = true;
|
|
2004
|
-
return false;
|
|
2005
|
-
}
|
|
2006
|
-
if (this.isProtocolV2BootloaderMode()) {
|
|
2007
|
-
Log.debug('Protocol V2 device is already in bootloader mode, skip reboot');
|
|
2008
|
-
this.protocolV2ExecutionInLoader = true;
|
|
2009
|
-
this.postTipMessage(FirmwareUpdateTipMessage.GoToBootloaderSuccess);
|
|
2010
|
-
return false;
|
|
2011
|
-
}
|
|
2012
|
-
|
|
1880
|
+
private async rebootProtocolV2ToBootloader() {
|
|
2013
1881
|
try {
|
|
2014
1882
|
this.postTipMessage(FirmwareUpdateTipMessage.AutoRebootToBootloader);
|
|
2015
1883
|
await this.protocolV2Reboot(DeviceRebootType.Bootloader);
|
|
@@ -2027,6 +1895,24 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2027
1895
|
}
|
|
2028
1896
|
}
|
|
2029
1897
|
|
|
1898
|
+
async enterProtocolV2BootloaderMode() {
|
|
1899
|
+
// romloader is the first update environment and forwards targets to bootloader.
|
|
1900
|
+
// It rejects DeviceRebootType.Bootloader, so reuse the current connection.
|
|
1901
|
+
if (this.isProtocolV2RomloaderMode()) {
|
|
1902
|
+
Log.debug('Protocol V2 device is in romloader mode; start firmware update directly');
|
|
1903
|
+
this.protocolV2ExecutionInLoader = true;
|
|
1904
|
+
return false;
|
|
1905
|
+
}
|
|
1906
|
+
if (this.isProtocolV2BootloaderMode()) {
|
|
1907
|
+
Log.debug('Protocol V2 device is already in bootloader mode, skip reboot');
|
|
1908
|
+
this.protocolV2ExecutionInLoader = true;
|
|
1909
|
+
this.postTipMessage(FirmwareUpdateTipMessage.GoToBootloaderSuccess);
|
|
1910
|
+
return false;
|
|
1911
|
+
}
|
|
1912
|
+
|
|
1913
|
+
return this.rebootProtocolV2ToBootloader();
|
|
1914
|
+
}
|
|
1915
|
+
|
|
2030
1916
|
private async waitForProtocolV2BootloaderMode(
|
|
2031
1917
|
timeout = PROTOCOL_V2_BOOTLOADER_RECONNECT_TIMEOUT,
|
|
2032
1918
|
retryInterval = 1000
|
|
@@ -2055,7 +1941,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2055
1941
|
}
|
|
2056
1942
|
lastError = new Error('Protocol V2 device is reachable but is not in bootloader mode');
|
|
2057
1943
|
} catch (error) {
|
|
2058
|
-
if (this.isProtocolV2ReconnectIdentityError(error)) {
|
|
1944
|
+
if (isBleStaleBondHardwareError(error) || this.isProtocolV2ReconnectIdentityError(error)) {
|
|
2059
1945
|
throw error;
|
|
2060
1946
|
}
|
|
2061
1947
|
shouldReconnect = true;
|
|
@@ -2111,68 +1997,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2111
1997
|
return entries;
|
|
2112
1998
|
}
|
|
2113
1999
|
|
|
2114
|
-
private
|
|
2115
|
-
installSources,
|
|
2116
|
-
resourceSources,
|
|
2117
|
-
}: {
|
|
2118
|
-
installSources: ProtocolV2InstallSource[];
|
|
2119
|
-
resourceSources: ProtocolV2ResourceBundleSource[];
|
|
2120
|
-
}): ProtocolV2ExecutionPhase[] {
|
|
2121
|
-
const phases: ProtocolV2ExecutionPhase[] = [];
|
|
2122
|
-
const bootResourceSources = resourceSources.filter(source =>
|
|
2123
|
-
isProtocolV2BootResourcePackagePath(source.devicePath)
|
|
2124
|
-
);
|
|
2125
|
-
const remainingResourceSources = resourceSources.filter(
|
|
2126
|
-
source => !isProtocolV2BootResourcePackagePath(source.devicePath)
|
|
2127
|
-
);
|
|
2128
|
-
// A previous interrupted run may have left a promotable staging file. Replace
|
|
2129
|
-
// it with the current approved package before any firmware-triggered reboot.
|
|
2130
|
-
if (bootResourceSources.length > 0) {
|
|
2131
|
-
phases.push({
|
|
2132
|
-
kind: 'resource-sync',
|
|
2133
|
-
installSources: [],
|
|
2134
|
-
resourceSources: bootResourceSources,
|
|
2135
|
-
});
|
|
2136
|
-
}
|
|
2137
|
-
const bootloaderSources = installSources.filter(source => source.kind === 'bootloader');
|
|
2138
|
-
if (bootloaderSources.length > 0) {
|
|
2139
|
-
phases.push(
|
|
2140
|
-
{
|
|
2141
|
-
kind: 'bootloader-install',
|
|
2142
|
-
installSources: bootloaderSources,
|
|
2143
|
-
resourceSources: [],
|
|
2144
|
-
},
|
|
2145
|
-
{
|
|
2146
|
-
kind: 'bootloader-verify',
|
|
2147
|
-
installSources: [],
|
|
2148
|
-
resourceSources: [],
|
|
2149
|
-
}
|
|
2150
|
-
);
|
|
2151
|
-
}
|
|
2152
|
-
if (remainingResourceSources.length > 0) {
|
|
2153
|
-
phases.push({
|
|
2154
|
-
kind: 'resource-sync',
|
|
2155
|
-
installSources: [],
|
|
2156
|
-
resourceSources: remainingResourceSources,
|
|
2157
|
-
});
|
|
2158
|
-
}
|
|
2159
|
-
const componentSources = installSources.filter(source => source.kind !== 'bootloader');
|
|
2160
|
-
if (componentSources.length > 0) {
|
|
2161
|
-
phases.push({
|
|
2162
|
-
kind: 'component-install',
|
|
2163
|
-
installSources: componentSources,
|
|
2164
|
-
resourceSources: [],
|
|
2165
|
-
});
|
|
2166
|
-
}
|
|
2167
|
-
phases.push({
|
|
2168
|
-
kind: 'final-verify',
|
|
2169
|
-
installSources: [],
|
|
2170
|
-
resourceSources: [],
|
|
2171
|
-
});
|
|
2172
|
-
return phases;
|
|
2173
|
-
}
|
|
2174
|
-
|
|
2175
|
-
private async executeProtocolV2Phases({
|
|
2000
|
+
private async executeProtocolV2SourceUpdate({
|
|
2176
2001
|
installSources,
|
|
2177
2002
|
resourceSources,
|
|
2178
2003
|
}: {
|
|
@@ -2180,38 +2005,22 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2180
2005
|
resourceSources: ProtocolV2ResourceBundleSource[];
|
|
2181
2006
|
}) {
|
|
2182
2007
|
this.protocolV2BootResourceStagingSafe = false;
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
) {
|
|
2198
|
-
await this.ensureProtocolV2BootResourceStagingIsEmpty();
|
|
2199
|
-
}
|
|
2200
|
-
await this.executeProtocolV2TransferPhase(phase);
|
|
2201
|
-
} else if (phase.kind === 'bootloader-install' || phase.kind === 'component-install') {
|
|
2202
|
-
await this.enterProtocolV2BootloaderMode();
|
|
2203
|
-
await this.ensureProtocolV2BootResourceStagingIsEmpty();
|
|
2204
|
-
await this.executeProtocolV2TransferPhase(phase);
|
|
2205
|
-
await this.exitProtocolV2BootloaderToNormal();
|
|
2206
|
-
} else if (phase.kind === 'bootloader-verify') {
|
|
2207
|
-
await this.waitForProtocolV2FinalFeatures();
|
|
2208
|
-
this.assertExpectedProtocolV2Versions(['boot']);
|
|
2209
|
-
}
|
|
2008
|
+
if (installSources.length > 0 || resourceSources.length > 0) {
|
|
2009
|
+
await this.enterProtocolV2BootloaderMode();
|
|
2010
|
+
// Clear stale boot-resource staging before writing any artifacts. The new
|
|
2011
|
+
// boot resource, resources and firmware then share one transfer session,
|
|
2012
|
+
// one global progress range and one multi-target install request.
|
|
2013
|
+
// Preserve the approved source order instead of synthesizing a boot-first
|
|
2014
|
+
// phase. Loader firmware owns the cross-stage handoff: bootloader runs its
|
|
2015
|
+
// component targets and leaves boot pending for romloader, while pending
|
|
2016
|
+
// runner-backed records always resume before the application boots.
|
|
2017
|
+
await this.ensureProtocolV2BootResourceStagingIsEmpty();
|
|
2018
|
+
await this.executeProtocolV2TransferPhase({
|
|
2019
|
+
installSources,
|
|
2020
|
+
resourceSources,
|
|
2021
|
+
});
|
|
2210
2022
|
}
|
|
2211
|
-
|
|
2212
|
-
HardwareErrorCode.RuntimeError,
|
|
2213
|
-
'Protocol V2 execution has no final verification phase'
|
|
2214
|
-
);
|
|
2023
|
+
return this.completeProtocolV2FinalVerification();
|
|
2215
2024
|
}
|
|
2216
2025
|
|
|
2217
2026
|
private async ensureProtocolV2BootResourceStagingIsEmpty() {
|
|
@@ -2243,19 +2052,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2243
2052
|
this.protocolV2BootResourceStagingSafe = true;
|
|
2244
2053
|
}
|
|
2245
2054
|
|
|
2246
|
-
private async executeProtocolV2SourceUpdate({
|
|
2247
|
-
installSources,
|
|
2248
|
-
resourceSources,
|
|
2249
|
-
}: {
|
|
2250
|
-
installSources: ProtocolV2InstallSource[];
|
|
2251
|
-
resourceSources: ProtocolV2ResourceBundleSource[];
|
|
2252
|
-
}) {
|
|
2253
|
-
return this.executeProtocolV2Phases({
|
|
2254
|
-
installSources,
|
|
2255
|
-
resourceSources,
|
|
2256
|
-
});
|
|
2257
|
-
}
|
|
2258
|
-
|
|
2259
2055
|
private async executeProtocolV2Update({
|
|
2260
2056
|
fwBinaryMap,
|
|
2261
2057
|
bootloaderBinary,
|
|
@@ -2280,7 +2076,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2280
2076
|
kind: item.kind,
|
|
2281
2077
|
}))
|
|
2282
2078
|
);
|
|
2283
|
-
return await this.
|
|
2079
|
+
return await this.executeProtocolV2SourceUpdate({
|
|
2284
2080
|
installSources,
|
|
2285
2081
|
resourceSources: [],
|
|
2286
2082
|
});
|
|
@@ -2292,15 +2088,11 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2292
2088
|
private async executeProtocolV2TransferPhase({
|
|
2293
2089
|
installSources,
|
|
2294
2090
|
resourceSources,
|
|
2295
|
-
}:
|
|
2091
|
+
}: ProtocolV2TransferBatch) {
|
|
2296
2092
|
let totalSize = installSources.reduce((total, item) => total + item.source.size, 0);
|
|
2297
2093
|
const resourcesToSync: ProtocolV2ResourceBundleSource[] = [];
|
|
2298
2094
|
for (const resource of resourceSources) {
|
|
2299
|
-
|
|
2300
|
-
// stale staging bytes with the artifact approved by the current PreparedPlan,
|
|
2301
|
-
// even when the mounted final file itself is already current.
|
|
2302
|
-
const requiresFreshStaging = isProtocolV2BootResourcePackagePath(resource.devicePath);
|
|
2303
|
-
if (!requiresFreshStaging && (await this.isProtocolV2ResourceBundleUpToDate(resource))) {
|
|
2095
|
+
if (await this.isProtocolV2ResourceBundleUpToDate(resource)) {
|
|
2304
2096
|
Log.log(`[FirmwareUpdateV4] skip RESC bundle ${resource.name}; already up to date`);
|
|
2305
2097
|
} else {
|
|
2306
2098
|
resourcesToSync.push(resource);
|
|
@@ -2309,11 +2101,13 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2309
2101
|
}
|
|
2310
2102
|
|
|
2311
2103
|
this.postTipMessage(FirmwareUpdateTipMessage.StartTransferData);
|
|
2104
|
+
this.protocolV2LastTransferProgress = undefined;
|
|
2105
|
+
this.protocolV2LastTransferProgressAt = 0;
|
|
2312
2106
|
let processedSize = 0;
|
|
2313
2107
|
for (const resource of resourcesToSync) {
|
|
2314
2108
|
// The bootloader keeps its live resource package mounted. FatFs rejects
|
|
2315
2109
|
// replacing an open file, so early boot promotes this staging file before mounting it.
|
|
2316
|
-
const writePath =
|
|
2110
|
+
const writePath = resource.devicePath;
|
|
2317
2111
|
processedSize = await this.protocolV2SourceUpdateProcess({
|
|
2318
2112
|
source: resource.source,
|
|
2319
2113
|
filePath: writePath,
|
|
@@ -2354,8 +2148,13 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2354
2148
|
target_id: item.targetId,
|
|
2355
2149
|
path: item.path,
|
|
2356
2150
|
}));
|
|
2357
|
-
|
|
2358
|
-
|
|
2151
|
+
try {
|
|
2152
|
+
await this.protocolV2StartFirmwareUpdate({ targets });
|
|
2153
|
+
await wait(PROTOCOL_V2_INSTALL_STATUS_INITIAL_DELAY);
|
|
2154
|
+
await this.waitForProtocolV2FirmwareUpdateComplete(targets, true);
|
|
2155
|
+
} finally {
|
|
2156
|
+
this.device?.clearCancelableAction();
|
|
2157
|
+
}
|
|
2359
2158
|
}
|
|
2360
2159
|
|
|
2361
2160
|
private async protocolV2SourceUpdateProcess({
|
|
@@ -2400,23 +2199,33 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2400
2199
|
);
|
|
2401
2200
|
}
|
|
2402
2201
|
const transferredBytes = processedSize + chunkEnd;
|
|
2403
|
-
const
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2202
|
+
const now = Date.now();
|
|
2203
|
+
const elapsedMs = Math.max(now - transferStartedAt, 0);
|
|
2204
|
+
const progress = Math.min(Math.ceil((transferredBytes / totalSize) * 100), 99);
|
|
2205
|
+
const shouldPostProgress =
|
|
2206
|
+
progress !== this.protocolV2LastTransferProgress ||
|
|
2207
|
+
now - this.protocolV2LastTransferProgressAt >=
|
|
2208
|
+
PROTOCOL_V2_TRANSFER_PROGRESS_HEARTBEAT_MS ||
|
|
2209
|
+
chunkEnd === source.size;
|
|
2210
|
+
if (shouldPostProgress) {
|
|
2211
|
+
this.protocolV2LastTransferProgress = progress;
|
|
2212
|
+
this.protocolV2LastTransferProgressAt = now;
|
|
2213
|
+
this.postProgressMessage(progress, 'transferData', {
|
|
2408
2214
|
transferredBytes,
|
|
2409
2215
|
totalBytes: totalSize,
|
|
2410
2216
|
rateBytesPerSecond:
|
|
2411
2217
|
elapsedMs > 0 ? Math.round((chunkEnd / elapsedMs) * 1000) : undefined,
|
|
2412
2218
|
elapsedMs,
|
|
2413
|
-
}
|
|
2414
|
-
|
|
2219
|
+
});
|
|
2220
|
+
}
|
|
2415
2221
|
return length;
|
|
2416
2222
|
},
|
|
2417
2223
|
});
|
|
2418
2224
|
return processedSize + source.size;
|
|
2419
2225
|
} catch (error) {
|
|
2226
|
+
if (isBleStaleBondHardwareError(error)) {
|
|
2227
|
+
throw error;
|
|
2228
|
+
}
|
|
2420
2229
|
lastError = error;
|
|
2421
2230
|
if (attempt < PROTOCOL_V2_FILE_TRANSFER_RETRY_COUNT) {
|
|
2422
2231
|
await this.recoverProtocolV2FileTransfer();
|
|
@@ -2446,19 +2255,41 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2446
2255
|
}
|
|
2447
2256
|
}
|
|
2448
2257
|
|
|
2258
|
+
private collectProtocolV2LiveTargetIds(
|
|
2259
|
+
statusTargets: ProtocolV2FirmwareUpdateStatusTarget[],
|
|
2260
|
+
expectedTargetIds: Set<number>,
|
|
2261
|
+
liveTargetIds: Set<number>
|
|
2262
|
+
) {
|
|
2263
|
+
statusTargets.forEach(target => {
|
|
2264
|
+
const targetId = normalizeProtocolV2TargetId(target.target_id);
|
|
2265
|
+
if (
|
|
2266
|
+
targetId !== undefined &&
|
|
2267
|
+
expectedTargetIds.has(targetId) &&
|
|
2268
|
+
isProtocolV2TargetStatusInProgress(target.status)
|
|
2269
|
+
) {
|
|
2270
|
+
liveTargetIds.add(targetId);
|
|
2271
|
+
}
|
|
2272
|
+
});
|
|
2273
|
+
}
|
|
2274
|
+
|
|
2449
2275
|
private assertProtocolV2TargetStatus(
|
|
2450
2276
|
statusTargets: ProtocolV2FirmwareUpdateStatusTarget[],
|
|
2451
2277
|
expectedTargetIds: Set<number>,
|
|
2452
|
-
expectedPaths
|
|
2278
|
+
expectedPaths?: Map<number, string>,
|
|
2279
|
+
liveTargetIds?: Set<number>
|
|
2453
2280
|
) {
|
|
2281
|
+
const resolvedExpectedPaths = expectedPaths ?? new Map<number, string>();
|
|
2454
2282
|
Log.log(
|
|
2455
2283
|
`[FirmwareUpdateV4] DeviceFirmwareUpdateStatus records=${JSON.stringify(statusTargets)}`
|
|
2456
2284
|
);
|
|
2457
|
-
const failedTarget = statusTargets.find(
|
|
2458
|
-
target
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2285
|
+
const failedTarget = statusTargets.find(target => {
|
|
2286
|
+
const targetId = normalizeProtocolV2TargetId(target.target_id) ?? -1;
|
|
2287
|
+
return (
|
|
2288
|
+
expectedTargetIds.has(targetId) &&
|
|
2289
|
+
isProtocolV2TargetStatusFailed(target.status) &&
|
|
2290
|
+
(!liveTargetIds || liveTargetIds.has(targetId))
|
|
2291
|
+
);
|
|
2292
|
+
});
|
|
2462
2293
|
if (failedTarget) {
|
|
2463
2294
|
const failedTargetDetails = JSON.stringify({
|
|
2464
2295
|
targetId: failedTarget.target_id,
|
|
@@ -2469,13 +2300,33 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2469
2300
|
Log.error(`[FirmwareUpdateV4] firmware install failed target=${failedTargetDetails}`);
|
|
2470
2301
|
throw ERRORS.TypedError(
|
|
2471
2302
|
HardwareErrorCode.FirmwareError,
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
}`
|
|
2303
|
+
'Protocol V2 firmware install failed',
|
|
2304
|
+
{
|
|
2305
|
+
firmwareUpdateCode: PROTOCOL_V2_INSTALL_FAILED_CODE,
|
|
2306
|
+
}
|
|
2477
2307
|
);
|
|
2478
2308
|
}
|
|
2309
|
+
if (liveTargetIds) {
|
|
2310
|
+
const staleFailedTargets = statusTargets.filter(target => {
|
|
2311
|
+
const targetId = normalizeProtocolV2TargetId(target.target_id) ?? -1;
|
|
2312
|
+
return (
|
|
2313
|
+
expectedTargetIds.has(targetId) &&
|
|
2314
|
+
isProtocolV2TargetStatusFailed(target.status) &&
|
|
2315
|
+
!liveTargetIds.has(targetId)
|
|
2316
|
+
);
|
|
2317
|
+
});
|
|
2318
|
+
if (staleFailedTargets.length > 0) {
|
|
2319
|
+
Log.log(
|
|
2320
|
+
`[FirmwareUpdateV4] ignoring stale failed firmware status until those targets start: ${JSON.stringify(
|
|
2321
|
+
staleFailedTargets.map(target => ({
|
|
2322
|
+
targetId: target.target_id,
|
|
2323
|
+
status: target.status,
|
|
2324
|
+
path: target.path,
|
|
2325
|
+
}))
|
|
2326
|
+
)}`
|
|
2327
|
+
);
|
|
2328
|
+
}
|
|
2329
|
+
}
|
|
2479
2330
|
|
|
2480
2331
|
const matchingTargets = statusTargets.filter(target =>
|
|
2481
2332
|
expectedTargetIds.has(normalizeProtocolV2TargetId(target.target_id) ?? -1)
|
|
@@ -2483,14 +2334,21 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2483
2334
|
const seenTargetIds = new Set<number>();
|
|
2484
2335
|
for (const target of matchingTargets) {
|
|
2485
2336
|
const targetId = normalizeProtocolV2TargetId(target.target_id);
|
|
2337
|
+
const pathConflictsWithCurrentInstall =
|
|
2338
|
+
targetId !== undefined &&
|
|
2339
|
+
Boolean(target.path) &&
|
|
2340
|
+
Boolean(resolvedExpectedPaths.get(targetId)) &&
|
|
2341
|
+
target.path !== resolvedExpectedPaths.get(targetId) &&
|
|
2342
|
+
(!liveTargetIds || liveTargetIds.has(targetId));
|
|
2486
2343
|
if (
|
|
2487
2344
|
targetId === undefined ||
|
|
2488
2345
|
seenTargetIds.has(targetId) ||
|
|
2489
|
-
|
|
2346
|
+
pathConflictsWithCurrentInstall
|
|
2490
2347
|
) {
|
|
2348
|
+
Log.error(`[FirmwareUpdateV4] install status conflicts with target=${target.target_id}`);
|
|
2491
2349
|
throw ERRORS.TypedError(
|
|
2492
|
-
HardwareErrorCode.
|
|
2493
|
-
|
|
2350
|
+
HardwareErrorCode.FirmwareError,
|
|
2351
|
+
'Protocol V2 firmware install status conflict',
|
|
2494
2352
|
{
|
|
2495
2353
|
firmwareUpdateCode: PROTOCOL_V2_INSTALL_STATUS_CONFLICT_CODE,
|
|
2496
2354
|
}
|
|
@@ -2498,14 +2356,19 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2498
2356
|
}
|
|
2499
2357
|
seenTargetIds.add(targetId);
|
|
2500
2358
|
}
|
|
2501
|
-
const completedTargets = matchingTargets.filter(target =>
|
|
2502
|
-
|
|
2503
|
-
|
|
2359
|
+
const completedTargets = matchingTargets.filter(target => {
|
|
2360
|
+
const targetId = normalizeProtocolV2TargetId(target.target_id);
|
|
2361
|
+
return (
|
|
2362
|
+
isProtocolV2TargetStatusFinished(target.status) &&
|
|
2363
|
+
(!liveTargetIds || (targetId !== undefined && liveTargetIds.has(targetId)))
|
|
2364
|
+
);
|
|
2365
|
+
});
|
|
2504
2366
|
const completedTargetIds = new Set<number>();
|
|
2505
2367
|
completedTargets.forEach(target => {
|
|
2506
2368
|
const targetId = normalizeProtocolV2TargetId(target.target_id);
|
|
2507
2369
|
if (targetId !== undefined) {
|
|
2508
2370
|
completedTargetIds.add(targetId);
|
|
2371
|
+
this.protocolV2CompletedTargetIds.add(targetId);
|
|
2509
2372
|
}
|
|
2510
2373
|
});
|
|
2511
2374
|
if (completedTargetIds.size === expectedTargetIds.size && expectedTargetIds.size > 0) {
|
|
@@ -2536,16 +2399,54 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2536
2399
|
}
|
|
2537
2400
|
|
|
2538
2401
|
if (expectedTargetIds.size > 0 && matchingTargets.length > 0) {
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2402
|
+
let detailedProgress = completedTargetIds.size * 100;
|
|
2403
|
+
let installProgressMetadata:
|
|
2404
|
+
| {
|
|
2405
|
+
installTargetId: number;
|
|
2406
|
+
installPhase: 'prepare' | 'install' | 'verify';
|
|
2407
|
+
installPhaseProgress: number;
|
|
2408
|
+
}
|
|
2409
|
+
| undefined;
|
|
2410
|
+
let hasInProgressTarget = false;
|
|
2411
|
+
matchingTargets.forEach(target => {
|
|
2412
|
+
const targetId = normalizeProtocolV2TargetId(target.target_id);
|
|
2413
|
+
if (
|
|
2414
|
+
targetId === undefined ||
|
|
2415
|
+
completedTargetIds.has(targetId) ||
|
|
2416
|
+
!isProtocolV2TargetStatusInProgress(target.status) ||
|
|
2417
|
+
(liveTargetIds && !liveTargetIds.has(targetId))
|
|
2418
|
+
) {
|
|
2419
|
+
return;
|
|
2420
|
+
}
|
|
2421
|
+
hasInProgressTarget = true;
|
|
2422
|
+
detailedProgress += normalizeProtocolV2ProgressPercent(target.progress_percent) ?? 0;
|
|
2423
|
+
const installPhase = target.phase_info
|
|
2424
|
+
? PROTOCOL_V2_INSTALL_PHASE_BY_DECODED_VALUE.get(target.phase_info.phase)
|
|
2425
|
+
: undefined;
|
|
2426
|
+
const installPhaseProgress = normalizeProtocolV2ProgressPercent(
|
|
2427
|
+
target.phase_info?.progress_percent
|
|
2428
|
+
);
|
|
2429
|
+
if (installPhase && installPhaseProgress !== undefined && !installProgressMetadata) {
|
|
2430
|
+
installProgressMetadata = {
|
|
2431
|
+
installTargetId: targetId,
|
|
2432
|
+
installPhase,
|
|
2433
|
+
installPhaseProgress,
|
|
2434
|
+
};
|
|
2435
|
+
}
|
|
2436
|
+
});
|
|
2542
2437
|
const completedProgress = Math.floor(
|
|
2543
2438
|
(completedTargetIds.size / expectedTargetIds.size) * 100
|
|
2544
2439
|
);
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2440
|
+
const reportedProgress = Math.floor(detailedProgress / expectedTargetIds.size);
|
|
2441
|
+
const progress = Math.min(
|
|
2442
|
+
99,
|
|
2443
|
+
Math.max(completedProgress, reportedProgress, hasInProgressTarget ? 1 : 0)
|
|
2444
|
+
);
|
|
2445
|
+
if (installProgressMetadata) {
|
|
2446
|
+
this.postProgressMessage(progress, 'installingFirmware', installProgressMetadata);
|
|
2447
|
+
} else {
|
|
2448
|
+
this.postProgressMessage(progress, 'installingFirmware');
|
|
2449
|
+
}
|
|
2549
2450
|
}
|
|
2550
2451
|
|
|
2551
2452
|
return false;
|
|
@@ -2565,31 +2466,95 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2565
2466
|
return Array.from(expectedTargetIds).filter(targetId => !reportedTargetIds.has(targetId));
|
|
2566
2467
|
}
|
|
2567
2468
|
|
|
2469
|
+
private getProtocolV2ObservableTargetVersions(features: Features) {
|
|
2470
|
+
const versions = new Map<number, string>();
|
|
2471
|
+
versions.set(
|
|
2472
|
+
ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_BOOTLOADER,
|
|
2473
|
+
getDeviceBootloaderVersion(features).join('.')
|
|
2474
|
+
);
|
|
2475
|
+
const applicationVersion = getDeviceFirmwareVersion(features).join('.');
|
|
2476
|
+
versions.set(ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_APPLICATION_P1, applicationVersion);
|
|
2477
|
+
versions.set(ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_APPLICATION_P2, applicationVersion);
|
|
2478
|
+
versions.set(
|
|
2479
|
+
ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_COPROCESSOR,
|
|
2480
|
+
getDeviceBLEFirmwareVersion(features).join('.')
|
|
2481
|
+
);
|
|
2482
|
+
const secureElementVersions: Array<[number, string | null | undefined]> = [
|
|
2483
|
+
[ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_SE01, features.se01Version],
|
|
2484
|
+
[ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_SE02, features.se02Version],
|
|
2485
|
+
[ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_SE03, features.se03Version],
|
|
2486
|
+
[ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_SE04, features.se04Version],
|
|
2487
|
+
];
|
|
2488
|
+
secureElementVersions.forEach(([targetId, version]) => {
|
|
2489
|
+
if (version) versions.set(targetId, version);
|
|
2490
|
+
});
|
|
2491
|
+
return versions;
|
|
2492
|
+
}
|
|
2493
|
+
|
|
2494
|
+
private hasProtocolV2InstallVersionChanged(expectedTargetIds: Set<number>) {
|
|
2495
|
+
if (!this.protocolV2LastRuntimeProbeFeatures) return false;
|
|
2496
|
+
const currentVersions = this.getProtocolV2ObservableTargetVersions(
|
|
2497
|
+
this.protocolV2LastRuntimeProbeFeatures
|
|
2498
|
+
);
|
|
2499
|
+
return Array.from(expectedTargetIds).some(targetId => {
|
|
2500
|
+
const previousVersion = this.protocolV2InstallBaselineVersions.get(targetId);
|
|
2501
|
+
const currentVersion = currentVersions.get(targetId);
|
|
2502
|
+
return (
|
|
2503
|
+
previousVersion !== undefined &&
|
|
2504
|
+
currentVersion !== undefined &&
|
|
2505
|
+
previousVersion !== currentVersion
|
|
2506
|
+
);
|
|
2507
|
+
});
|
|
2508
|
+
}
|
|
2509
|
+
|
|
2510
|
+
private recordProtocolV2AuthoritativeInstallCompletion(expectedTargetIds: Set<number>) {
|
|
2511
|
+
expectedTargetIds.forEach(targetId => this.protocolV2CompletedTargetIds.add(targetId));
|
|
2512
|
+
}
|
|
2513
|
+
|
|
2568
2514
|
private async waitForProtocolV2FirmwareUpdateComplete(
|
|
2569
|
-
targets: Array<{ target_id: number; path: string }
|
|
2515
|
+
targets: Array<{ target_id: number; path: string }>,
|
|
2516
|
+
requireCurrentInstallStatus = false
|
|
2570
2517
|
) {
|
|
2571
|
-
this.protocolV2FinalStatusVerified = false;
|
|
2572
2518
|
const expectedTargetIds = new Set(targets.map(target => target.target_id));
|
|
2573
2519
|
const expectedPaths = new Map(targets.map(target => [target.target_id, target.path]));
|
|
2520
|
+
const isBleCoprocessorInstall =
|
|
2521
|
+
this.isBleReconnect() &&
|
|
2522
|
+
expectedTargetIds.has(ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_COPROCESSOR);
|
|
2574
2523
|
const startTime = Date.now();
|
|
2575
2524
|
let lastError: unknown;
|
|
2576
|
-
|
|
2525
|
+
// The loader may release either USB or BLE as installation starts. Recovery reconnects
|
|
2526
|
+
// directly to status polling without generic Ping or DeviceInfo probes.
|
|
2527
|
+
let shouldReconnect = this.protocolV2InstallNeedsReconnect;
|
|
2528
|
+
this.protocolV2InstallNeedsReconnect = false;
|
|
2577
2529
|
let deviceInfo: ProtocolV2DeviceInfo | undefined;
|
|
2578
|
-
let
|
|
2579
|
-
let
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2530
|
+
let bleInstallLinkReady = false;
|
|
2531
|
+
let installStatusDisconnectObserved = false;
|
|
2532
|
+
let installReconnectObserved = false;
|
|
2533
|
+
let finishedStatusSnapshotKey: string | undefined;
|
|
2534
|
+
let finishedStatusSnapshotPolls = 0;
|
|
2535
|
+
let fastFinishedInstallProgressReported = false;
|
|
2536
|
+
let installEvidenceObserved = this.protocolV2InstallTerminalSuccessObserved;
|
|
2537
|
+
let currentInstallStatusObserved = false;
|
|
2538
|
+
const liveTargetIds = new Set<number>();
|
|
2584
2539
|
|
|
2585
2540
|
while (Date.now() - startTime < PROTOCOL_V2_INSTALL_TIMEOUT) {
|
|
2541
|
+
// A transport release caused by an explicit workflow cancellation must not
|
|
2542
|
+
// be mistaken for the expected device reboot during installation.
|
|
2543
|
+
this.throwIfAborted();
|
|
2586
2544
|
try {
|
|
2587
2545
|
if (shouldReconnect) {
|
|
2588
|
-
|
|
2589
|
-
|
|
2546
|
+
const isBleInstallReconnect = this.isBleReconnect();
|
|
2547
|
+
await this.reconnectProtocolV2Device({ skipProtocolProbe: true });
|
|
2548
|
+
bleInstallLinkReady = isBleInstallReconnect;
|
|
2549
|
+
deviceInfo = isBleInstallReconnect
|
|
2550
|
+
? undefined
|
|
2551
|
+
: await this.verifyProtocolV2ReconnectIdentity();
|
|
2552
|
+
if (isBleInstallReconnect && installStatusDisconnectObserved) {
|
|
2553
|
+
installReconnectObserved = true;
|
|
2554
|
+
}
|
|
2590
2555
|
shouldReconnect = false;
|
|
2591
2556
|
}
|
|
2592
|
-
|
|
2557
|
+
let currentDeviceInfo = deviceInfo;
|
|
2593
2558
|
try {
|
|
2594
2559
|
const statusResponse = await this.device.getCommands().typedCall(
|
|
2595
2560
|
'DeviceFirmwareUpdateStatusGet',
|
|
@@ -2597,91 +2562,243 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2597
2562
|
{
|
|
2598
2563
|
fields: {
|
|
2599
2564
|
status: true,
|
|
2565
|
+
progress_percent: true,
|
|
2566
|
+
phase_info: true,
|
|
2600
2567
|
payload_version: true,
|
|
2601
2568
|
path: true,
|
|
2602
2569
|
},
|
|
2603
2570
|
},
|
|
2604
2571
|
{ timeoutMs: PROTOCOL_V2_FIRMWARE_STATUS_RESPONSE_TIMEOUT }
|
|
2605
2572
|
);
|
|
2573
|
+
if (this.protocolV2InstallTerminalSuccessObserved) {
|
|
2574
|
+
installEvidenceObserved = true;
|
|
2575
|
+
}
|
|
2606
2576
|
const statusTargets = (statusResponse.message.records ??
|
|
2607
2577
|
[]) as ProtocolV2FirmwareUpdateStatusTarget[];
|
|
2608
|
-
|
|
2609
|
-
|
|
2578
|
+
this.collectProtocolV2LiveTargetIds(statusTargets, expectedTargetIds, liveTargetIds);
|
|
2579
|
+
if (liveTargetIds.size > 0) {
|
|
2580
|
+
currentInstallStatusObserved = true;
|
|
2581
|
+
}
|
|
2582
|
+
const hasMatchingTargetStatus = statusTargets.some(target => {
|
|
2583
|
+
const targetId = normalizeProtocolV2TargetId(target.target_id);
|
|
2584
|
+
return targetId !== undefined && expectedTargetIds.has(targetId);
|
|
2585
|
+
});
|
|
2586
|
+
if (
|
|
2587
|
+
hasMatchingTargetStatus &&
|
|
2588
|
+
(!requireCurrentInstallStatus || currentInstallStatusObserved)
|
|
2589
|
+
) {
|
|
2590
|
+
installEvidenceObserved = true;
|
|
2591
|
+
}
|
|
2592
|
+
const matchingStatusTargets = statusTargets.filter(target => {
|
|
2593
|
+
const targetId = normalizeProtocolV2TargetId(target.target_id);
|
|
2594
|
+
return targetId !== undefined && expectedTargetIds.has(targetId);
|
|
2595
|
+
});
|
|
2596
|
+
const matchingFinishedTargetIds = new Set<number>();
|
|
2597
|
+
const matchingFinishedStatus = matchingStatusTargets
|
|
2598
|
+
.map(target => {
|
|
2599
|
+
const targetId = normalizeProtocolV2TargetId(target.target_id);
|
|
2600
|
+
const expectedPath = targetId === undefined ? undefined : expectedPaths.get(targetId);
|
|
2601
|
+
const expectedVersion =
|
|
2602
|
+
targetId === undefined
|
|
2603
|
+
? undefined
|
|
2604
|
+
: this.getExpectedProtocolV2TargetVersion(targetId);
|
|
2605
|
+
const payloadVersion = toProtocolV2FiniteNumber(target.payload_version);
|
|
2606
|
+
if (
|
|
2607
|
+
targetId === undefined ||
|
|
2608
|
+
matchingFinishedTargetIds.has(targetId) ||
|
|
2609
|
+
!isProtocolV2TargetStatusFinished(target.status) ||
|
|
2610
|
+
(Boolean(target.path) && Boolean(expectedPath) && target.path !== expectedPath) ||
|
|
2611
|
+
(expectedVersion !== undefined &&
|
|
2612
|
+
payloadVersion !== undefined &&
|
|
2613
|
+
payloadVersion !== expectedVersion)
|
|
2614
|
+
) {
|
|
2615
|
+
return undefined;
|
|
2616
|
+
}
|
|
2617
|
+
matchingFinishedTargetIds.add(targetId);
|
|
2618
|
+
return [targetId, target.path ?? null, payloadVersion ?? null] as const;
|
|
2619
|
+
})
|
|
2620
|
+
.filter((target): target is readonly [number, string | null, number | null] => !!target)
|
|
2621
|
+
.sort(([leftTargetId], [rightTargetId]) => leftTargetId - rightTargetId);
|
|
2622
|
+
const hasCompleteMatchingFinishedStatus =
|
|
2623
|
+
matchingFinishedStatus.length === expectedTargetIds.size &&
|
|
2624
|
+
matchingFinishedTargetIds.size === expectedTargetIds.size &&
|
|
2625
|
+
expectedTargetIds.size > 0;
|
|
2626
|
+
if (
|
|
2627
|
+
requireCurrentInstallStatus &&
|
|
2628
|
+
installReconnectObserved &&
|
|
2629
|
+
hasCompleteMatchingFinishedStatus
|
|
2630
|
+
) {
|
|
2631
|
+
const nextSnapshotKey = JSON.stringify(matchingFinishedStatus);
|
|
2632
|
+
if (nextSnapshotKey === finishedStatusSnapshotKey) {
|
|
2633
|
+
finishedStatusSnapshotPolls += 1;
|
|
2634
|
+
} else {
|
|
2635
|
+
finishedStatusSnapshotKey = nextSnapshotKey;
|
|
2636
|
+
finishedStatusSnapshotPolls = 1;
|
|
2637
|
+
}
|
|
2638
|
+
} else {
|
|
2639
|
+
finishedStatusSnapshotKey = undefined;
|
|
2640
|
+
finishedStatusSnapshotPolls = 0;
|
|
2641
|
+
}
|
|
2642
|
+
if (
|
|
2643
|
+
requireCurrentInstallStatus &&
|
|
2644
|
+
installReconnectObserved &&
|
|
2645
|
+
hasCompleteMatchingFinishedStatus &&
|
|
2646
|
+
!currentInstallStatusObserved &&
|
|
2647
|
+
!this.protocolV2InstallTerminalSuccessObserved &&
|
|
2648
|
+
!fastFinishedInstallProgressReported
|
|
2649
|
+
) {
|
|
2650
|
+
// A fast BLE install may finish while disconnected, so IN_PROGRESS can be hidden.
|
|
2651
|
+
// A status response timeout alone is not evidence that the install was accepted.
|
|
2652
|
+
this.postProgressMessage(1, 'installingFirmware');
|
|
2653
|
+
fastFinishedInstallProgressReported = true;
|
|
2654
|
+
}
|
|
2655
|
+
const finishedAfterDisconnectObserved =
|
|
2656
|
+
finishedStatusSnapshotPolls >= PROTOCOL_V2_INSTALL_FINISHED_AFTER_DISCONNECT_POLLS;
|
|
2657
|
+
const hasCurrentInstallEvidence =
|
|
2658
|
+
currentInstallStatusObserved ||
|
|
2659
|
+
this.protocolV2InstallTerminalSuccessObserved ||
|
|
2660
|
+
finishedAfterDisconnectObserved;
|
|
2661
|
+
if (hasCurrentInstallEvidence) {
|
|
2662
|
+
installEvidenceObserved = true;
|
|
2663
|
+
}
|
|
2664
|
+
const shouldVerifyTargetCompletion =
|
|
2665
|
+
!requireCurrentInstallStatus || hasCurrentInstallEvidence;
|
|
2666
|
+
if (
|
|
2667
|
+
shouldVerifyTargetCompletion &&
|
|
2668
|
+
this.assertProtocolV2TargetStatus(
|
|
2669
|
+
statusTargets,
|
|
2670
|
+
expectedTargetIds,
|
|
2671
|
+
expectedPaths,
|
|
2672
|
+
requireCurrentInstallStatus &&
|
|
2673
|
+
!this.protocolV2InstallTerminalSuccessObserved &&
|
|
2674
|
+
!finishedAfterDisconnectObserved
|
|
2675
|
+
? liveTargetIds
|
|
2676
|
+
: undefined
|
|
2677
|
+
)
|
|
2678
|
+
) {
|
|
2610
2679
|
return;
|
|
2611
2680
|
}
|
|
2612
2681
|
|
|
2682
|
+
if (!hasCurrentInstallEvidence) {
|
|
2683
|
+
// A successful status poll clears DeviceCommands' cancel action. Keep cancellation
|
|
2684
|
+
// available while UpdateRequest is still waiting for confirmation on the device.
|
|
2685
|
+
this.device.setCancelableAction(() => this.device.getCommands().cancelDevice());
|
|
2686
|
+
}
|
|
2687
|
+
|
|
2613
2688
|
if (
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2689
|
+
requireCurrentInstallStatus &&
|
|
2690
|
+
!hasCurrentInstallEvidence &&
|
|
2691
|
+
matchingStatusTargets.length > 0
|
|
2617
2692
|
) {
|
|
2618
|
-
|
|
2619
|
-
'
|
|
2693
|
+
lastError = new Error(
|
|
2694
|
+
'Protocol V2 firmware status is stale; waiting for the current install to start'
|
|
2620
2695
|
);
|
|
2621
|
-
this.postProgressMessage(100, 'installingFirmware');
|
|
2622
|
-
return;
|
|
2623
2696
|
}
|
|
2624
2697
|
|
|
2625
|
-
|
|
2626
|
-
statusTargets
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2698
|
+
if (
|
|
2699
|
+
statusTargets.length === 0 &&
|
|
2700
|
+
!currentDeviceInfo &&
|
|
2701
|
+
bleInstallLinkReady &&
|
|
2702
|
+
installEvidenceObserved
|
|
2703
|
+
) {
|
|
2704
|
+
// BLE install reconnect skips generic probes because loaders may not answer Ping.
|
|
2705
|
+
// Restore the verified identity only after current-install evidence disappears.
|
|
2706
|
+
currentDeviceInfo = await this.verifyProtocolV2ReconnectIdentity();
|
|
2707
|
+
deviceInfo = currentDeviceInfo;
|
|
2708
|
+
}
|
|
2709
|
+
|
|
2710
|
+
if (statusTargets.length === 0 && currentDeviceInfo) {
|
|
2711
|
+
const isNormalMode = await this.probeProtocolV2NormalMode(currentDeviceInfo);
|
|
2712
|
+
if (
|
|
2713
|
+
isNormalMode &&
|
|
2714
|
+
(installEvidenceObserved ||
|
|
2715
|
+
this.hasProtocolV2InstallVersionChanged(expectedTargetIds))
|
|
2638
2716
|
) {
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
.filter((targetId): targetId is number => targetId !== undefined);
|
|
2642
|
-
throw ERRORS.TypedError(
|
|
2643
|
-
HardwareErrorCode.FirmwareError,
|
|
2644
|
-
`Protocol V2 firmware status is missing requested records: targetIds=${missingKey} reportedTargetIds=${reportedTargetIds.join(
|
|
2645
|
-
','
|
|
2646
|
-
)}`
|
|
2717
|
+
Log.log(
|
|
2718
|
+
'[FirmwareUpdateV4] empty firmware status after confirmed App reboot; update complete'
|
|
2647
2719
|
);
|
|
2720
|
+
this.recordProtocolV2AuthoritativeInstallCompletion(expectedTargetIds);
|
|
2721
|
+
this.postProgressMessage(100, 'installingFirmware');
|
|
2722
|
+
return;
|
|
2648
2723
|
}
|
|
2724
|
+
}
|
|
2725
|
+
|
|
2726
|
+
if (statusTargets.length === 0) {
|
|
2649
2727
|
lastError = new Error(
|
|
2650
|
-
|
|
2728
|
+
'Protocol V2 firmware update is waiting for user confirmation or target status'
|
|
2651
2729
|
);
|
|
2652
2730
|
} else {
|
|
2653
|
-
|
|
2654
|
-
|
|
2731
|
+
const missingTargetIds = this.getProtocolV2MissingTargetIds(
|
|
2732
|
+
statusTargets,
|
|
2733
|
+
expectedTargetIds
|
|
2734
|
+
);
|
|
2735
|
+
if (missingTargetIds.length > 0) {
|
|
2736
|
+
const missingKey = missingTargetIds.join(',');
|
|
2737
|
+
lastError = new Error(
|
|
2738
|
+
`Protocol V2 firmware status is temporarily missing targetIds=${missingKey}`
|
|
2739
|
+
);
|
|
2740
|
+
} else {
|
|
2741
|
+
lastError = new Error('Protocol V2 firmware targets are still installing');
|
|
2742
|
+
}
|
|
2655
2743
|
}
|
|
2656
2744
|
} catch (error) {
|
|
2657
2745
|
if (isProtocolV2TerminalInstallStatusError(error)) {
|
|
2658
2746
|
throw error;
|
|
2659
2747
|
}
|
|
2660
|
-
// Only consecutive full status dumps can prove that a record is absent.
|
|
2661
|
-
// A reboot or transport interruption starts a fresh grace window.
|
|
2662
|
-
resetMissingTargetStatusGrace();
|
|
2663
2748
|
// App firmware does not register DeviceFirmwareUpdateStatusGet. Treat the
|
|
2664
2749
|
// missing endpoint as completion only after the runtime probe confirms App mode.
|
|
2665
2750
|
if (isProtocolV2FirmwareStatusEndpointUnavailable(error)) {
|
|
2666
2751
|
if (!currentDeviceInfo) {
|
|
2752
|
+
if (!bleInstallLinkReady) {
|
|
2753
|
+
throw ERRORS.TypedError(
|
|
2754
|
+
HardwareErrorCode.RuntimeError,
|
|
2755
|
+
'Protocol V2 device identity is unavailable during install polling'
|
|
2756
|
+
);
|
|
2757
|
+
}
|
|
2758
|
+
deviceInfo = await this.verifyProtocolV2ReconnectIdentity();
|
|
2759
|
+
}
|
|
2760
|
+
const reconnectDeviceInfo = currentDeviceInfo ?? deviceInfo;
|
|
2761
|
+
if (!reconnectDeviceInfo) {
|
|
2667
2762
|
throw ERRORS.TypedError(
|
|
2668
2763
|
HardwareErrorCode.RuntimeError,
|
|
2669
2764
|
'Protocol V2 device identity is unavailable during install polling'
|
|
2670
2765
|
);
|
|
2671
2766
|
}
|
|
2672
|
-
|
|
2767
|
+
const isNormalMode = await this.probeProtocolV2NormalMode(reconnectDeviceInfo);
|
|
2768
|
+
if (
|
|
2769
|
+
isNormalMode &&
|
|
2770
|
+
(installEvidenceObserved ||
|
|
2771
|
+
this.hasProtocolV2InstallVersionChanged(expectedTargetIds))
|
|
2772
|
+
) {
|
|
2673
2773
|
Log.log(
|
|
2674
2774
|
'[FirmwareUpdateV4] firmware status endpoint unavailable after confirmed App reboot'
|
|
2675
2775
|
);
|
|
2776
|
+
this.recordProtocolV2AuthoritativeInstallCompletion(expectedTargetIds);
|
|
2676
2777
|
this.postProgressMessage(100, 'installingFirmware');
|
|
2677
2778
|
return;
|
|
2678
2779
|
}
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2780
|
+
if (isNormalMode) {
|
|
2781
|
+
lastError = new Error(
|
|
2782
|
+
'Protocol V2 device is in normal mode but installation is not yet confirmed'
|
|
2783
|
+
);
|
|
2784
|
+
} else {
|
|
2785
|
+
lastError = new Error(
|
|
2786
|
+
'Protocol V2 firmware status endpoint is unavailable while the device remains in loader mode'
|
|
2787
|
+
);
|
|
2788
|
+
}
|
|
2682
2789
|
} else {
|
|
2790
|
+
if (
|
|
2791
|
+
isBleCoprocessorInstall &&
|
|
2792
|
+
isProtocolV2DeviceDisconnectedError(error) &&
|
|
2793
|
+
!isProtocolV2ResponseTimeout(error)
|
|
2794
|
+
) {
|
|
2795
|
+
installStatusDisconnectObserved = true;
|
|
2796
|
+
finishedStatusSnapshotKey = undefined;
|
|
2797
|
+
finishedStatusSnapshotPolls = 0;
|
|
2798
|
+
}
|
|
2683
2799
|
shouldReconnect = true;
|
|
2684
2800
|
deviceInfo = undefined;
|
|
2801
|
+
bleInstallLinkReady = false;
|
|
2685
2802
|
lastError = error;
|
|
2686
2803
|
Log.log(
|
|
2687
2804
|
'[FirmwareUpdateV4] DeviceFirmwareUpdateStatusGet unavailable during install: ',
|
|
@@ -2705,18 +2822,24 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2705
2822
|
}
|
|
2706
2823
|
shouldReconnect = true;
|
|
2707
2824
|
deviceInfo = undefined;
|
|
2708
|
-
|
|
2825
|
+
bleInstallLinkReady = false;
|
|
2709
2826
|
Log.log('Protocol V2 firmware install device readiness probe failed: ', error);
|
|
2710
2827
|
}
|
|
2711
2828
|
await wait(1000);
|
|
2712
2829
|
}
|
|
2713
2830
|
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
`Protocol V2 firmware update status not complete within ${
|
|
2831
|
+
Log.error(
|
|
2832
|
+
`[FirmwareUpdateV4] install timed out after ${
|
|
2717
2833
|
PROTOCOL_V2_INSTALL_TIMEOUT / 1000
|
|
2718
2834
|
}s: ${this.normalizeErrorMessage(lastError)}`
|
|
2719
2835
|
);
|
|
2836
|
+
throw ERRORS.TypedError(
|
|
2837
|
+
HardwareErrorCode.FirmwareError,
|
|
2838
|
+
'Protocol V2 firmware install timed out',
|
|
2839
|
+
{
|
|
2840
|
+
firmwareUpdateCode: PROTOCOL_V2_INSTALL_TIMEOUT_CODE,
|
|
2841
|
+
}
|
|
2842
|
+
);
|
|
2720
2843
|
}
|
|
2721
2844
|
|
|
2722
2845
|
private async exitProtocolV2BootloaderToNormal() {
|
|
@@ -2728,6 +2851,9 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2728
2851
|
return;
|
|
2729
2852
|
}
|
|
2730
2853
|
} catch (error) {
|
|
2854
|
+
if (isBleStaleBondHardwareError(error)) {
|
|
2855
|
+
throw error;
|
|
2856
|
+
}
|
|
2731
2857
|
Log.log('[FirmwareUpdateV4] unable to confirm App mode before Normal reboot: ', error);
|
|
2732
2858
|
}
|
|
2733
2859
|
await this.protocolV2Reboot(DeviceRebootType.Normal);
|
|
@@ -2739,7 +2865,14 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2739
2865
|
deviceInfo,
|
|
2740
2866
|
PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT
|
|
2741
2867
|
);
|
|
2742
|
-
|
|
2868
|
+
this.protocolV2LastRuntimeProbeFeatures = features;
|
|
2869
|
+
return this.isProtocolV2ApplicationMode(features);
|
|
2870
|
+
}
|
|
2871
|
+
|
|
2872
|
+
private isProtocolV2ApplicationMode(features: Features) {
|
|
2873
|
+
return (
|
|
2874
|
+
(features.mode === 'normal' || features.mode === 'notInitialized') && !features.bootloaderMode
|
|
2875
|
+
);
|
|
2743
2876
|
}
|
|
2744
2877
|
|
|
2745
2878
|
private async waitForProtocolV2FinalFeatures() {
|
|
@@ -2783,6 +2916,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2783
2916
|
const startTime = Date.now();
|
|
2784
2917
|
let lastError: unknown;
|
|
2785
2918
|
let shouldReconnect = true;
|
|
2919
|
+
this.protocolV2LatestFinalDeviceInfo = undefined;
|
|
2786
2920
|
|
|
2787
2921
|
while (Date.now() - startTime < timeout) {
|
|
2788
2922
|
try {
|
|
@@ -2799,9 +2933,11 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2799
2933
|
this.assertProtocolV2DeviceInfoIdentity(deviceInfo);
|
|
2800
2934
|
const features = await this.device.probeProtocolV2RuntimeState(
|
|
2801
2935
|
deviceInfo,
|
|
2802
|
-
PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT
|
|
2936
|
+
PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT,
|
|
2937
|
+
{ forceRuntimeContextRefresh: true }
|
|
2803
2938
|
);
|
|
2804
|
-
if (
|
|
2939
|
+
if (this.isProtocolV2ApplicationMode(features)) {
|
|
2940
|
+
this.protocolV2LatestFinalDeviceInfo = deviceInfo;
|
|
2805
2941
|
return features;
|
|
2806
2942
|
}
|
|
2807
2943
|
lastError = ERRORS.TypedError(
|
|
@@ -2809,7 +2945,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2809
2945
|
'Protocol V2 device is still in bootloader mode'
|
|
2810
2946
|
);
|
|
2811
2947
|
} catch (error) {
|
|
2812
|
-
if (this.isProtocolV2ReconnectIdentityError(error)) {
|
|
2948
|
+
if (isBleStaleBondHardwareError(error) || this.isProtocolV2ReconnectIdentityError(error)) {
|
|
2813
2949
|
throw error;
|
|
2814
2950
|
}
|
|
2815
2951
|
shouldReconnect = true;
|
|
@@ -2827,19 +2963,53 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2827
2963
|
);
|
|
2828
2964
|
}
|
|
2829
2965
|
|
|
2830
|
-
private async reconnectProtocolV2Device() {
|
|
2966
|
+
private async reconnectProtocolV2Device(options?: { skipProtocolProbe?: boolean }) {
|
|
2831
2967
|
if (this.isBleReconnect()) {
|
|
2832
|
-
await this.acquireProtocolV2BleDevice();
|
|
2968
|
+
await this.acquireProtocolV2BleDevice(options?.skipProtocolProbe);
|
|
2833
2969
|
return;
|
|
2834
2970
|
}
|
|
2835
2971
|
|
|
2836
2972
|
const deviceDiff = await this.device.deviceConnector?.enumerate();
|
|
2837
2973
|
const devicesDescriptor = deviceDiff?.descriptors ?? [];
|
|
2974
|
+
const env = DataManager.getSettings('env');
|
|
2975
|
+
const isWebUsb = DataManager.isBrowserWebUsb(env) || DataManager.isDesktopWebUsb(env);
|
|
2838
2976
|
|
|
2839
|
-
if (
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
2977
|
+
if (isWebUsb && options?.skipProtocolProbe) {
|
|
2978
|
+
const expectedSerialNumber = this.protocolV2ExpectedSerialNumber?.trim();
|
|
2979
|
+
const expectedPath = this.protocolV2ExpectedPath?.trim();
|
|
2980
|
+
const reconnectDescriptor = devicesDescriptor.find(descriptor => {
|
|
2981
|
+
const descriptorPath = descriptor.path?.trim();
|
|
2982
|
+
return (
|
|
2983
|
+
(!!expectedSerialNumber && descriptorPath === expectedSerialNumber) ||
|
|
2984
|
+
(!!expectedPath && descriptorPath === expectedPath)
|
|
2985
|
+
);
|
|
2986
|
+
});
|
|
2987
|
+
if (!reconnectDescriptor?.path) {
|
|
2988
|
+
throw ERRORS.TypedError(HardwareErrorCode.DeviceNotFound);
|
|
2989
|
+
}
|
|
2990
|
+
this.device.updateDescriptor(
|
|
2991
|
+
{
|
|
2992
|
+
...reconnectDescriptor,
|
|
2993
|
+
protocolType: PROTOCOL_V2_CONNECT_PROTOCOL,
|
|
2994
|
+
},
|
|
2995
|
+
true
|
|
2996
|
+
);
|
|
2997
|
+
const mainId = await this.device.deviceConnector?.acquire(
|
|
2998
|
+
reconnectDescriptor.path,
|
|
2999
|
+
null,
|
|
3000
|
+
undefined,
|
|
3001
|
+
PROTOCOL_V2_CONNECT_PROTOCOL,
|
|
3002
|
+
undefined,
|
|
3003
|
+
undefined,
|
|
3004
|
+
true
|
|
3005
|
+
);
|
|
3006
|
+
this.device.mainId = typeof mainId === 'string' ? mainId : reconnectDescriptor.path;
|
|
3007
|
+
this.device.commands.disposed = false;
|
|
3008
|
+
this.device.getCommands().mainId = this.device.mainId;
|
|
3009
|
+
return;
|
|
3010
|
+
}
|
|
3011
|
+
|
|
3012
|
+
if (isWebUsb && devicesDescriptor.length === 1) {
|
|
2843
3013
|
const descriptor = devicesDescriptor[0];
|
|
2844
3014
|
if (!this.protocolV2ExpectedSerialNumber && descriptor.path !== this.protocolV2ExpectedPath) {
|
|
2845
3015
|
throw ERRORS.TypedError(HardwareErrorCode.DeviceNotFound);
|
|
@@ -2957,13 +3127,24 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2957
3127
|
await wait(2000);
|
|
2958
3128
|
}
|
|
2959
3129
|
|
|
2960
|
-
private async acquireProtocolV2BleDevice() {
|
|
2961
|
-
|
|
2962
|
-
|
|
3130
|
+
private async acquireProtocolV2BleDevice(skipProtocolProbe = false) {
|
|
3131
|
+
const connector = this.device.deviceConnector;
|
|
3132
|
+
const expectedId = this.device.originalDescriptor.id;
|
|
3133
|
+
if (!skipProtocolProbe) {
|
|
3134
|
+
await connector?.acquire(expectedId, null, true, PROTOCOL_V2_CONNECT_PROTOCOL);
|
|
3135
|
+
return;
|
|
3136
|
+
}
|
|
3137
|
+
await connector?.acquire(
|
|
3138
|
+
expectedId,
|
|
2963
3139
|
null,
|
|
2964
3140
|
true,
|
|
2965
|
-
PROTOCOL_V2_CONNECT_PROTOCOL
|
|
3141
|
+
PROTOCOL_V2_CONNECT_PROTOCOL,
|
|
3142
|
+
undefined,
|
|
3143
|
+
undefined,
|
|
3144
|
+
skipProtocolProbe
|
|
2966
3145
|
);
|
|
3146
|
+
this.device.commands.disposed = false;
|
|
3147
|
+
this.device.getCommands().mainId = expectedId;
|
|
2967
3148
|
}
|
|
2968
3149
|
|
|
2969
3150
|
private async protocolV2StartFirmwareUpdate({
|
|
@@ -2971,30 +3152,72 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2971
3152
|
}: {
|
|
2972
3153
|
targets: Array<{ target_id: number; path: string }>;
|
|
2973
3154
|
}) {
|
|
3155
|
+
this.protocolV2LastRuntimeProbeFeatures = undefined;
|
|
3156
|
+
this.protocolV2InstallNeedsReconnect = false;
|
|
3157
|
+
this.protocolV2InstallTerminalSuccessObserved = false;
|
|
2974
3158
|
const commands = this.device.getCommands();
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
{
|
|
3159
|
+
await commands.typedCall('DeviceFirmwareUpdateStage', 'Success', { targets });
|
|
3160
|
+
this.device.setCancelableAction(() => this.device.getCommands().cancelDevice());
|
|
3161
|
+
const interaction = this.device.createProtocolV2UiPhaseMetadata('button', 'start');
|
|
3162
|
+
this.postMessage(
|
|
3163
|
+
createUiMessage(UI_REQUEST.REQUEST_BUTTON, {
|
|
3164
|
+
device: this.device.toMessageObject() as KnownDevice,
|
|
3165
|
+
source: 'method-lifecycle',
|
|
3166
|
+
reason: 'firmware-update',
|
|
3167
|
+
completion: 'operation-completed',
|
|
3168
|
+
deviceOnly: true,
|
|
3169
|
+
operation: this.name,
|
|
3170
|
+
...(interaction ? { interaction } : {}),
|
|
3171
|
+
})
|
|
2980
3172
|
);
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
3173
|
+
|
|
3174
|
+
try {
|
|
3175
|
+
await commands.call(
|
|
3176
|
+
'DeviceFirmwareUpdateRequest',
|
|
3177
|
+
{},
|
|
3178
|
+
{
|
|
3179
|
+
returnAfterWrite: true,
|
|
3180
|
+
expectedTypes: ['Success'],
|
|
3181
|
+
onResponseAfterWrite: response => {
|
|
3182
|
+
if (response.type !== 'Success') return;
|
|
3183
|
+
this.protocolV2InstallTerminalSuccessObserved = true;
|
|
3184
|
+
this.device.clearCancelableAction();
|
|
3185
|
+
Log.log('[FirmwareUpdateV4] firmware install completed by device response');
|
|
3186
|
+
},
|
|
3187
|
+
}
|
|
3188
|
+
);
|
|
3189
|
+
Log.log('[FirmwareUpdateV4] firmware install request written; continue with status polling');
|
|
3190
|
+
} catch (error) {
|
|
3191
|
+
this.throwIfAborted();
|
|
3192
|
+
if (!isProtocolV2DeviceDisconnectedError(error)) {
|
|
3193
|
+
throw error;
|
|
3194
|
+
}
|
|
3195
|
+
Log.log(
|
|
3196
|
+
'[FirmwareUpdateV4] transport released while writing install request; continue status polling'
|
|
3197
|
+
);
|
|
3198
|
+
this.protocolV2InstallNeedsReconnect = true;
|
|
3199
|
+
}
|
|
2986
3200
|
}
|
|
2987
3201
|
|
|
2988
3202
|
private async protocolV2Reboot(rebootType: DeviceRebootType) {
|
|
2989
3203
|
const typedCall = this.device.getCommands().typedCall.bind(this.device.getCommands());
|
|
2990
3204
|
try {
|
|
2991
|
-
const res = await typedCall(
|
|
2992
|
-
|
|
2993
|
-
|
|
3205
|
+
const res = await typedCall(
|
|
3206
|
+
'DeviceReboot',
|
|
3207
|
+
'Success',
|
|
3208
|
+
{
|
|
3209
|
+
reboot_type: rebootType,
|
|
3210
|
+
},
|
|
3211
|
+
{ timeoutMs: PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT }
|
|
3212
|
+
);
|
|
2994
3213
|
this.device.markProtocolV2Reboot(rebootType);
|
|
2995
3214
|
return res.message;
|
|
2996
3215
|
} catch (error) {
|
|
2997
|
-
if (
|
|
3216
|
+
if (
|
|
3217
|
+
isProtocolV2DeviceDisconnectedError(error) ||
|
|
3218
|
+
isProtocolV2ReconnectProbeError(error) ||
|
|
3219
|
+
isProtocolV2ResponseTimeout(error)
|
|
3220
|
+
) {
|
|
2998
3221
|
this.device.markProtocolV2Reboot(rebootType);
|
|
2999
3222
|
return { message: 'Device rebooted successfully' };
|
|
3000
3223
|
}
|