@onekeyfe/hd-core 1.2.0-alpha.99 → 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 +48 -2
- package/__tests__/check-firmware-release-protocol-v2.test.ts +82 -13
- 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 +13 -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 +63 -0
- package/__tests__/firmware-update/firmware-update-plan.test.ts +59 -55
- 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 +23 -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 +2198 -486
- 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 +19 -6
- 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/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.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 +2135 -1470
- 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 +2 -4
- 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 +12 -9
- package/src/api/FirmwareUpdateV3.ts +29 -19
- package/src/api/FirmwareUpdateV4.ts +918 -741
- 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/FirmwarePreparedResourceArchive.ts +16 -27
- package/src/api/firmware/FirmwareUpdateBaseMethod.ts +9 -3
- package/src/api/firmware/FirmwareUpdatePlan.ts +37 -77
- package/src/api/firmware/FirmwareUpdatePreparedPlan.ts +10 -6
- 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 -126
- 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 -143
|
@@ -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
|
|
|
@@ -151,6 +152,18 @@ export function assertProtocolV2FirmwareTargetsSupported(
|
|
|
151
152
|
if (params.se04Binary && (!hasExplicitTargetSelection || requestedTargets.has('se04'))) {
|
|
152
153
|
unsupportedTargets.add('se04');
|
|
153
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
|
+
}
|
|
154
167
|
|
|
155
168
|
if (!unsupportedTargets.size || deviceType === EDeviceType.Pro2) return;
|
|
156
169
|
|
|
@@ -168,26 +181,26 @@ const getProtocolV2DeviceTransferProgress = (
|
|
|
168
181
|
totalBytes: number
|
|
169
182
|
) => {
|
|
170
183
|
if (!Number.isFinite(totalBytes) || totalBytes <= 0) {
|
|
171
|
-
return
|
|
184
|
+
return 0;
|
|
172
185
|
}
|
|
173
186
|
if (bytesBeforeChunk <= 0 && bytesAfterChunk < totalBytes) {
|
|
174
187
|
return 0;
|
|
175
188
|
}
|
|
176
|
-
if (bytesAfterChunk >= totalBytes) {
|
|
177
|
-
return 100;
|
|
178
|
-
}
|
|
179
189
|
return Math.min(Math.max(Math.ceil((bytesAfterChunk / totalBytes) * 100), 1), 99);
|
|
180
190
|
};
|
|
181
191
|
|
|
182
192
|
type ProtocolV2FirmwareUpdateStatusTarget = {
|
|
183
193
|
target_id: number | string;
|
|
184
194
|
status?: number | string;
|
|
195
|
+
progress_percent?: number;
|
|
196
|
+
phase_info?: {
|
|
197
|
+
phase: number | string;
|
|
198
|
+
progress_percent: number;
|
|
199
|
+
};
|
|
185
200
|
payload_version?: number;
|
|
186
201
|
path?: string;
|
|
187
202
|
};
|
|
188
203
|
|
|
189
|
-
type ProtocolV2FirmwareUpdateStartResponse = TypedResponseMessage<'Success'>;
|
|
190
|
-
|
|
191
204
|
type ProtocolV2TargetBinary = { fileName: string; binary: ArrayBuffer; targetId: number };
|
|
192
205
|
type ProtocolV2InstallItem = ProtocolV2TargetBinary & {
|
|
193
206
|
kind: ProtocolV2RemoteComponentTarget['kind'];
|
|
@@ -221,7 +234,11 @@ type ProtocolV2ResourceBundleSource = {
|
|
|
221
234
|
|
|
222
235
|
type ProtocolV2LocalResourceArchive = {
|
|
223
236
|
binary: ArrayBuffer;
|
|
224
|
-
|
|
237
|
+
resources: Array<{
|
|
238
|
+
entryName: string;
|
|
239
|
+
binary: ArrayBuffer;
|
|
240
|
+
header: ProtocolV2ResourcePackageHeader;
|
|
241
|
+
}>;
|
|
225
242
|
};
|
|
226
243
|
|
|
227
244
|
type JSZipSizedEntry = JSZip.JSZipObject & {
|
|
@@ -231,26 +248,11 @@ type JSZipSizedEntry = JSZip.JSZipObject & {
|
|
|
231
248
|
};
|
|
232
249
|
};
|
|
233
250
|
|
|
234
|
-
type
|
|
235
|
-
| 'resource-sync'
|
|
236
|
-
| 'bootloader-install'
|
|
237
|
-
| 'bootloader-verify'
|
|
238
|
-
| 'component-install'
|
|
239
|
-
| 'final-verify';
|
|
240
|
-
|
|
241
|
-
type ProtocolV2ExecutionPhase = {
|
|
242
|
-
kind: ProtocolV2ExecutionPhaseKind;
|
|
251
|
+
type ProtocolV2TransferBatch = {
|
|
243
252
|
installSources: ProtocolV2InstallSource[];
|
|
244
253
|
resourceSources: ProtocolV2ResourceBundleSource[];
|
|
245
254
|
};
|
|
246
255
|
|
|
247
|
-
type ProtocolV2OkppHeader = {
|
|
248
|
-
type: string;
|
|
249
|
-
version: IVersionArray;
|
|
250
|
-
payloadHash: string;
|
|
251
|
-
headerHash: string;
|
|
252
|
-
};
|
|
253
|
-
|
|
254
256
|
const PROTOCOL_V2_REMOTE_COMPONENT_TARGETS: Readonly<
|
|
255
257
|
Record<string, ProtocolV2RemoteComponentTarget>
|
|
256
258
|
> = {
|
|
@@ -302,18 +304,9 @@ const PROTOCOL_V2_FIRMWARE_STAGING_PATHS = new Set(
|
|
|
302
304
|
)
|
|
303
305
|
);
|
|
304
306
|
|
|
305
|
-
const PROTOCOL_V2_BOOT_RESOURCE_PACKAGE_PATH = 'vol0:/loaders/bootloader/boot_resource.okpkg';
|
|
306
|
-
const PROTOCOL_V2_BOOT_RESOURCE_PACKAGE_STAGING_PATH = `${PROTOCOL_V2_BOOT_RESOURCE_PACKAGE_PATH}.staging`;
|
|
307
|
-
|
|
308
307
|
const isProtocolV2BootResourcePackagePath = (devicePath: string) =>
|
|
309
308
|
typeof devicePath === 'string' &&
|
|
310
|
-
devicePath.
|
|
311
|
-
PROTOCOL_V2_BOOT_RESOURCE_PACKAGE_PATH;
|
|
312
|
-
|
|
313
|
-
const resolveProtocolV2ResourceWritePath = (devicePath: string) =>
|
|
314
|
-
isProtocolV2BootResourcePackagePath(devicePath)
|
|
315
|
-
? PROTOCOL_V2_BOOT_RESOURCE_PACKAGE_STAGING_PATH
|
|
316
|
-
: devicePath;
|
|
309
|
+
devicePath.toLowerCase() === PROTOCOL_V2_BOOT_RESOURCE_PACKAGE_STAGING_PATH;
|
|
317
310
|
|
|
318
311
|
const PROTOCOL_V2_UPDATE_TARGET_BY_TARGET_ID = new Map<
|
|
319
312
|
number,
|
|
@@ -382,10 +375,12 @@ const isProtocolV2FirmwareStatusEndpointUnavailable = (error: unknown) => {
|
|
|
382
375
|
};
|
|
383
376
|
|
|
384
377
|
const isProtocolV2TerminalInstallStatusError = (error: unknown) =>
|
|
385
|
-
error
|
|
386
|
-
(error
|
|
387
|
-
error.errorCode === HardwareErrorCode.
|
|
388
|
-
|
|
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));
|
|
389
384
|
|
|
390
385
|
const isProtocolV2TargetStatusFinished = (status: ProtocolV2FirmwareUpdateStatusTarget['status']) =>
|
|
391
386
|
normalizeProtocolV2TargetStatus(status) === PROTOCOL_V2_TARGET_STATUS_FINISHED;
|
|
@@ -422,6 +417,18 @@ const normalizeProtocolV2TargetStatus = (
|
|
|
422
417
|
return undefined;
|
|
423
418
|
};
|
|
424
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
|
+
|
|
425
432
|
const normalizeProtocolV2Hex = (value?: string) => value?.replace(/^0x/i, '').toLowerCase();
|
|
426
433
|
|
|
427
434
|
const versionArrayToNumber = (version?: IVersionArray) => {
|
|
@@ -479,40 +486,9 @@ const toProtocolV2FiniteNumber = (value: unknown) => {
|
|
|
479
486
|
return undefined;
|
|
480
487
|
};
|
|
481
488
|
|
|
482
|
-
const
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
.join('');
|
|
486
|
-
|
|
487
|
-
const parseProtocolV2OkppHeader = (bytes: Uint8Array): ProtocolV2OkppHeader | null => {
|
|
488
|
-
if (bytes.byteLength < PROTOCOL_V2_OKPP_HEADER_SIZE) return null;
|
|
489
|
-
if (readProtocolV2Ascii(bytes, 0, 4) !== 'OKPP') return null;
|
|
490
|
-
|
|
491
|
-
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
492
|
-
const headerLen = view.getUint32(0x0c, true);
|
|
493
|
-
if (headerLen !== PROTOCOL_V2_OKPP_HEADER_SIZE) return null;
|
|
494
|
-
|
|
495
|
-
const packedVersion = view.getUint32(0x10, true);
|
|
496
|
-
return {
|
|
497
|
-
type: readProtocolV2Ascii(bytes, 0x08, 4),
|
|
498
|
-
version: [
|
|
499
|
-
Math.floor(packedVersion / 0x10000) % 0x100,
|
|
500
|
-
Math.floor(packedVersion / 0x100) % 0x100,
|
|
501
|
-
packedVersion % 0x100,
|
|
502
|
-
],
|
|
503
|
-
payloadHash: bytesToHex(
|
|
504
|
-
bytes.slice(
|
|
505
|
-
PROTOCOL_V2_OKPP_PAYLOAD_HASH_OFFSET,
|
|
506
|
-
PROTOCOL_V2_OKPP_PAYLOAD_HASH_OFFSET + PROTOCOL_V2_OKPP_HASH_SIZE
|
|
507
|
-
)
|
|
508
|
-
),
|
|
509
|
-
headerHash: bytesToHex(
|
|
510
|
-
bytes.slice(
|
|
511
|
-
PROTOCOL_V2_OKPP_HEADER_HASH_OFFSET,
|
|
512
|
-
PROTOCOL_V2_OKPP_HEADER_HASH_OFFSET + PROTOCOL_V2_OKPP_HASH_SIZE
|
|
513
|
-
)
|
|
514
|
-
),
|
|
515
|
-
};
|
|
489
|
+
const normalizeProtocolV2ProgressPercent = (value: unknown) => {
|
|
490
|
+
const progress = toProtocolV2FiniteNumber(value);
|
|
491
|
+
return progress === undefined ? undefined : Math.min(100, Math.max(0, progress));
|
|
516
492
|
};
|
|
517
493
|
|
|
518
494
|
export const isProtocolV2FirmwareFingerprintValid = (
|
|
@@ -557,7 +533,7 @@ export const assertProtocolV2ReconnectIdentity = (
|
|
|
557
533
|
*
|
|
558
534
|
* It intentionally does not fall back to FirmwareUpdateV3/V1 behavior:
|
|
559
535
|
* - upload uses FilesystemFileWrite
|
|
560
|
-
* - install uses DeviceFirmwareUpdateRequest
|
|
536
|
+
* - install uses DeviceFirmwareUpdateStage followed by an empty DeviceFirmwareUpdateRequest
|
|
561
537
|
* - completion waits for target status to finish, reboots to normal, then polls DeviceInfo
|
|
562
538
|
*/
|
|
563
539
|
export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareUpdateV4Params> {
|
|
@@ -579,19 +555,54 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
579
555
|
|
|
580
556
|
private protocolV2CompletedTargetVersions = new Map<number, number>();
|
|
581
557
|
|
|
558
|
+
private protocolV2CompletedTargetIds = new Set<number>();
|
|
559
|
+
|
|
582
560
|
private protocolV2LatestFinalFeatures?: Features;
|
|
583
561
|
|
|
584
|
-
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;
|
|
585
575
|
|
|
586
576
|
init() {
|
|
587
577
|
this.allowDeviceMode = [UI_REQUEST.BOOTLOADER, UI_REQUEST.NOT_INITIALIZE];
|
|
588
578
|
this.requireDeviceMode = [];
|
|
589
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;
|
|
590
583
|
this.useDevicePassphraseState = false;
|
|
591
584
|
this.skipForceUpdateCheck = true;
|
|
592
585
|
|
|
593
586
|
const { payload } = this;
|
|
594
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
|
+
|
|
595
606
|
this.protocolV2HasExplicitTargetSelection = payload.targetsToUpdate !== undefined;
|
|
596
607
|
|
|
597
608
|
if (typeof payload.retryCount !== 'number') {
|
|
@@ -661,10 +672,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
661
672
|
payload.hostBindingGeneration,
|
|
662
673
|
preparedPlan.preparedPlanDigest
|
|
663
674
|
).artifactReader;
|
|
664
|
-
} else if (payload.hostBindingGeneration !== undefined) {
|
|
665
|
-
artifactReader = resolveFirmwareUpdateHostBinding(
|
|
666
|
-
payload.hostBindingGeneration
|
|
667
|
-
).artifactReader;
|
|
668
675
|
}
|
|
669
676
|
if (preparedPlan) {
|
|
670
677
|
assertFirmwareUpdatePreparedPlanBinding({
|
|
@@ -787,6 +794,9 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
787
794
|
private async runProtocolV2() {
|
|
788
795
|
await this.captureProtocolV2PhysicalIdentity();
|
|
789
796
|
const deviceFeatures = await this.getProtocolV2DeviceFeatures();
|
|
797
|
+
this.protocolV2InstallBaselineVersions =
|
|
798
|
+
this.getProtocolV2ObservableTargetVersions(deviceFeatures);
|
|
799
|
+
this.protocolV2LastRuntimeProbeFeatures = undefined;
|
|
790
800
|
const currentDeviceType = this.device.getCurrentDeviceType();
|
|
791
801
|
const capabilityDeviceType =
|
|
792
802
|
currentDeviceType === EDeviceType.Pro2 || currentDeviceType === EDeviceType.Neo
|
|
@@ -800,21 +810,14 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
800
810
|
const deviceFirmwareType = getFirmwareType(deviceFeatures);
|
|
801
811
|
const firmwareType = this.params.firmwareType ?? deviceFirmwareType;
|
|
802
812
|
this.validateExpectedTargetVersions();
|
|
813
|
+
const wantsResources = !!this.params.targetsToUpdate?.includes('resource');
|
|
803
814
|
|
|
804
815
|
if (
|
|
805
816
|
!this.params.preparedPlan &&
|
|
806
817
|
this.params.resourceArchiveBinary &&
|
|
807
818
|
this.params.targetsToUpdate?.includes('resource')
|
|
808
819
|
) {
|
|
809
|
-
|
|
810
|
-
features: deviceFeatures,
|
|
811
|
-
firmwareType,
|
|
812
|
-
});
|
|
813
|
-
try {
|
|
814
|
-
return await this.runProtocolV2PreparedArtifacts(deviceFeatures, firmwareType);
|
|
815
|
-
} finally {
|
|
816
|
-
localMemoryHost.release();
|
|
817
|
-
}
|
|
820
|
+
return this.runProtocolV2DirectArtifacts({});
|
|
818
821
|
}
|
|
819
822
|
|
|
820
823
|
const hasPreparedComponentArtifacts = Object.values(this.params.componentArtifacts ?? {}).some(
|
|
@@ -823,16 +826,16 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
823
826
|
if (this.params.preparedPlan || hasPreparedComponentArtifacts) {
|
|
824
827
|
return this.runProtocolV2PreparedArtifacts(deviceFeatures, firmwareType);
|
|
825
828
|
}
|
|
826
|
-
const wantsResources = !!this.params.targetsToUpdate?.includes('resource');
|
|
827
829
|
|
|
828
830
|
let fwBinaryMap: ProtocolV2TargetBinary[] = [];
|
|
829
831
|
let bootloaderBinary: ArrayBuffer | null = null;
|
|
830
832
|
let installItems: ProtocolV2InstallItem[] | undefined;
|
|
833
|
+
let explicitInstallItems: ProtocolV2InstallItem[] | undefined;
|
|
831
834
|
try {
|
|
832
835
|
this.postTipMessage(FirmwareUpdateTipMessage.StartDownloadFirmware);
|
|
833
836
|
fwBinaryMap = this.collectExplicitTargetBinaries();
|
|
834
837
|
bootloaderBinary = this.prepareBootloaderBinary();
|
|
835
|
-
|
|
838
|
+
explicitInstallItems = this.buildProtocolV2InstallItems({
|
|
836
839
|
bootloaderBinary,
|
|
837
840
|
fwBinaryMap,
|
|
838
841
|
});
|
|
@@ -840,17 +843,9 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
840
843
|
const needsRemoteFirmware = this.params.targetsToUpdate?.length
|
|
841
844
|
? missingFirmwareTargets.length > 0
|
|
842
845
|
: explicitInstallItems.length === 0;
|
|
843
|
-
|
|
844
|
-
throw ERRORS.TypedError(
|
|
845
|
-
HardwareErrorCode.RuntimeError,
|
|
846
|
-
'Protocol V2 resource archive must be provided through a local or external PreparedPlan',
|
|
847
|
-
{
|
|
848
|
-
firmwareUpdateCode: 'FirmwareArtifactsNotPrepared',
|
|
849
|
-
}
|
|
850
|
-
);
|
|
851
|
-
}
|
|
846
|
+
const needsSdkManagedArtifacts = needsRemoteFirmware || wantsResources;
|
|
852
847
|
if (
|
|
853
|
-
|
|
848
|
+
needsSdkManagedArtifacts &&
|
|
854
849
|
(this.params.artifactReader ||
|
|
855
850
|
DataManager.getSettings('firmwareManifestMode') === 'external-only')
|
|
856
851
|
) {
|
|
@@ -862,9 +857,13 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
862
857
|
}
|
|
863
858
|
);
|
|
864
859
|
}
|
|
865
|
-
if (
|
|
860
|
+
if (needsSdkManagedArtifacts) {
|
|
866
861
|
// Remote updates must use a freshly fetched config before any reboot or file write.
|
|
867
|
-
await DataManager.forceReloadData(
|
|
862
|
+
await DataManager.forceReloadData({
|
|
863
|
+
requireResources: wantsResources,
|
|
864
|
+
resourceDeviceType:
|
|
865
|
+
capabilityDeviceType === EDeviceType.Neo ? EDeviceType.Neo : EDeviceType.Pro2,
|
|
866
|
+
});
|
|
868
867
|
}
|
|
869
868
|
if (needsRemoteFirmware) {
|
|
870
869
|
const remoteBinaries = await this.prepareRemoteProtocolV2Binaries(
|
|
@@ -887,6 +886,12 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
887
886
|
targetId: item.targetId,
|
|
888
887
|
}));
|
|
889
888
|
}
|
|
889
|
+
if (wantsResources) {
|
|
890
|
+
this.params.resourceArchiveBinary = await this.downloadRemoteProtocolV2ResourceArchive(
|
|
891
|
+
deviceFeatures,
|
|
892
|
+
firmwareType
|
|
893
|
+
);
|
|
894
|
+
}
|
|
890
895
|
this.postTipMessage(FirmwareUpdateTipMessage.FinishDownloadFirmware);
|
|
891
896
|
} catch (err) {
|
|
892
897
|
if (
|
|
@@ -903,6 +908,13 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
903
908
|
throw normalizeFirmwarePreparationError(err);
|
|
904
909
|
}
|
|
905
910
|
|
|
911
|
+
if (wantsResources && this.params.resourceArchiveBinary) {
|
|
912
|
+
return this.runProtocolV2DirectArtifacts({
|
|
913
|
+
availableInstallItems: installItems ?? explicitInstallItems,
|
|
914
|
+
announceDownload: false,
|
|
915
|
+
});
|
|
916
|
+
}
|
|
917
|
+
|
|
906
918
|
if (!bootloaderBinary && fwBinaryMap.length === 0 && !installItems?.length) {
|
|
907
919
|
throw ERRORS.TypedError(
|
|
908
920
|
HardwareErrorCode.FirmwareUpdateDownloadFailed,
|
|
@@ -1053,132 +1065,69 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1053
1065
|
return installSources;
|
|
1054
1066
|
}
|
|
1055
1067
|
|
|
1056
|
-
private async
|
|
1057
|
-
|
|
1058
|
-
|
|
1068
|
+
private async runProtocolV2DirectArtifacts({
|
|
1069
|
+
availableInstallItems,
|
|
1070
|
+
announceDownload = true,
|
|
1059
1071
|
}: {
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
})
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
const localComponentTargets = new Set(
|
|
1074
|
-
availableInstallItems.flatMap(item => {
|
|
1075
|
-
const target = PROTOCOL_V2_UPDATE_TARGET_BY_TARGET_ID.get(item.targetId);
|
|
1076
|
-
return target ? [target] : [];
|
|
1077
|
-
})
|
|
1078
|
-
);
|
|
1079
|
-
const missingTarget = Array.from(requestedComponentTargets).find(
|
|
1080
|
-
target => !localComponentTargets.has(target)
|
|
1081
|
-
);
|
|
1082
|
-
if (missingTarget) {
|
|
1083
|
-
throw ERRORS.TypedError(
|
|
1084
|
-
HardwareErrorCode.RuntimeError,
|
|
1085
|
-
`Protocol V2 local update has no binary for requested target ${missingTarget}`,
|
|
1086
|
-
{
|
|
1087
|
-
firmwareUpdateCode: 'FirmwareArtifactsNotPrepared',
|
|
1088
|
-
artifactName: missingTarget,
|
|
1089
|
-
}
|
|
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
|
+
})
|
|
1090
1085
|
);
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
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) {
|
|
1100
1098
|
throw ERRORS.TypedError(
|
|
1101
|
-
HardwareErrorCode.
|
|
1102
|
-
|
|
1103
|
-
{ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
|
|
1099
|
+
HardwareErrorCode.FirmwareUpdateDownloadFailed,
|
|
1100
|
+
'No firmware to update'
|
|
1104
1101
|
);
|
|
1105
1102
|
}
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
expectedSize: item.binary.byteLength,
|
|
1113
|
-
expectedSha256: bytesToHex(sha256(new Uint8Array(item.binary))),
|
|
1114
|
-
...(this.params.expectedTargetVersions?.[target]
|
|
1115
|
-
? { targetVersion: this.params.expectedTargetVersions[target] }
|
|
1116
|
-
: {}),
|
|
1103
|
+
if (announceDownload) {
|
|
1104
|
+
this.postTipMessage(FirmwareUpdateTipMessage.FinishDownloadFirmware);
|
|
1105
|
+
}
|
|
1106
|
+
return await this.executeProtocolV2SourceUpdate({
|
|
1107
|
+
installSources,
|
|
1108
|
+
resourceSources,
|
|
1117
1109
|
});
|
|
1118
|
-
|
|
1110
|
+
} finally {
|
|
1111
|
+
await this.closeProtocolV2PreparedSources();
|
|
1119
1112
|
}
|
|
1113
|
+
}
|
|
1120
1114
|
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
const
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
artifactId: resourceArtifactId,
|
|
1135
|
-
binary: resourceArchive.binary,
|
|
1136
|
-
materializedEntries: resourceArchive.materializedEntries,
|
|
1137
|
-
});
|
|
1138
|
-
|
|
1139
|
-
const plan = buildProtocolV2LocalFirmwareUpdatePlan({
|
|
1140
|
-
features,
|
|
1141
|
-
firmwareType,
|
|
1142
|
-
platform: this.params.platform,
|
|
1143
|
-
artifacts: planArtifacts,
|
|
1144
|
-
});
|
|
1145
|
-
let memoryHost: FirmwareUpdateV4MemoryHost | undefined;
|
|
1146
|
-
try {
|
|
1147
|
-
memoryHost = prepareFirmwareUpdateV4MemoryHost({
|
|
1148
|
-
sdk: {
|
|
1149
|
-
prepareFirmwareUpdatePlan,
|
|
1150
|
-
registerFirmwareUpdateHostBinding,
|
|
1151
|
-
unregisterFirmwareUpdateHostBinding,
|
|
1152
|
-
},
|
|
1153
|
-
plan,
|
|
1154
|
-
artifacts: memoryArtifacts,
|
|
1155
|
-
});
|
|
1156
|
-
const preparedPlan = validateFirmwareUpdatePreparedPlan(memoryHost.preparedPlan);
|
|
1157
|
-
assertFirmwareUpdatePreparedPlanBinding({
|
|
1158
|
-
preparedPlan,
|
|
1159
|
-
executor: 'v4',
|
|
1160
|
-
platform: this.params.platform,
|
|
1161
|
-
scopeTargets: [],
|
|
1162
|
-
bindings: [],
|
|
1163
|
-
});
|
|
1164
|
-
assertFirmwareUpdatePreparedPlanDeviceIdentity({
|
|
1165
|
-
preparedPlan,
|
|
1166
|
-
deviceIdentity: this.protocolV2ExpectedSerialNumber,
|
|
1167
|
-
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,
|
|
1168
1128
|
});
|
|
1169
|
-
const hostBinding = resolveFirmwareUpdateHostBinding(
|
|
1170
|
-
memoryHost.hostBindingGeneration,
|
|
1171
|
-
preparedPlan.preparedPlanDigest
|
|
1172
|
-
);
|
|
1173
|
-
this.params.preparedPlan = preparedPlan;
|
|
1174
|
-
this.params.targetsToUpdate = [...preparedPlan.targetsToUpdate] as FirmwareUpdateV4Target[];
|
|
1175
|
-
this.params.artifactReader = hostBinding.artifactReader;
|
|
1176
|
-
this.params.componentArtifacts = undefined;
|
|
1177
|
-
return memoryHost;
|
|
1178
|
-
} catch (error) {
|
|
1179
|
-
memoryHost?.release();
|
|
1180
|
-
throw error;
|
|
1181
1129
|
}
|
|
1130
|
+
return sources;
|
|
1182
1131
|
}
|
|
1183
1132
|
|
|
1184
1133
|
private async prepareProtocolV2LocalResourceArchive(
|
|
@@ -1202,130 +1151,67 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1202
1151
|
);
|
|
1203
1152
|
}
|
|
1204
1153
|
const zipEntries = Object.values(zip.files);
|
|
1154
|
+
const resourceEntries = zipEntries.filter(
|
|
1155
|
+
entry => !entry.dir && isProtocolV2ResourceArchiveEntryName(entry.name)
|
|
1156
|
+
);
|
|
1205
1157
|
if (
|
|
1206
|
-
|
|
1158
|
+
resourceEntries.length === 0 ||
|
|
1159
|
+
resourceEntries.length > PROTOCOL_V2_RESOURCE_FILE_MAX_COUNT
|
|
1207
1160
|
) {
|
|
1208
1161
|
throw ERRORS.TypedError(
|
|
1209
1162
|
HardwareErrorCode.RuntimeError,
|
|
1210
|
-
'Protocol V2
|
|
1211
|
-
{ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
|
|
1212
|
-
);
|
|
1213
|
-
}
|
|
1214
|
-
const entries = zipEntries.filter(entry => !entry.dir);
|
|
1215
|
-
if (entries.length === 0 || entries.length > PROTOCOL_V2_RESOURCE_FILE_MAX_COUNT + 1) {
|
|
1216
|
-
throw ERRORS.TypedError(
|
|
1217
|
-
HardwareErrorCode.RuntimeError,
|
|
1218
|
-
'Protocol V2 local resource ZIP entry set is invalid',
|
|
1163
|
+
'Protocol V2 resource ZIP has no valid resource package set',
|
|
1219
1164
|
{ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
|
|
1220
1165
|
);
|
|
1221
1166
|
}
|
|
1222
|
-
|
|
1223
|
-
let
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
entry.name === 'manifest.json'
|
|
1230
|
-
? PROTOCOL_V2_RESOURCE_MANIFEST_MAX_BYTES
|
|
1231
|
-
: 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;
|
|
1232
1174
|
if (
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
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
|
|
1237
1178
|
) {
|
|
1238
1179
|
throw ERRORS.TypedError(
|
|
1239
1180
|
HardwareErrorCode.RuntimeError,
|
|
1240
|
-
|
|
1181
|
+
`Protocol V2 resource package size is invalid: ${entry.name}`,
|
|
1241
1182
|
{ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
|
|
1242
1183
|
);
|
|
1243
1184
|
}
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
if (!manifestEntry) {
|
|
1247
|
-
throw ERRORS.TypedError(
|
|
1248
|
-
HardwareErrorCode.RuntimeError,
|
|
1249
|
-
'Protocol V2 local resource ZIP has no manifest.json',
|
|
1250
|
-
{ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
|
|
1251
|
-
);
|
|
1252
|
-
}
|
|
1253
|
-
const manifestBinary = await manifestEntry.async('arraybuffer');
|
|
1254
|
-
if (
|
|
1255
|
-
manifestBinary.byteLength <= 0 ||
|
|
1256
|
-
manifestBinary.byteLength > PROTOCOL_V2_RESOURCE_MANIFEST_MAX_BYTES
|
|
1257
|
-
) {
|
|
1258
|
-
throw ERRORS.TypedError(
|
|
1259
|
-
HardwareErrorCode.RuntimeError,
|
|
1260
|
-
'Protocol V2 local resource manifest size is invalid',
|
|
1261
|
-
{ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
|
|
1262
|
-
);
|
|
1263
|
-
}
|
|
1264
|
-
|
|
1265
|
-
let manifestValue: unknown;
|
|
1266
|
-
try {
|
|
1267
|
-
manifestValue = JSON.parse(new TextDecoder().decode(manifestBinary));
|
|
1268
|
-
} catch (error) {
|
|
1269
|
-
throw ERRORS.TypedError(
|
|
1270
|
-
HardwareErrorCode.RuntimeError,
|
|
1271
|
-
`Protocol V2 local resource manifest is invalid: ${String(error)}`,
|
|
1272
|
-
{ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
|
|
1273
|
-
);
|
|
1274
|
-
}
|
|
1275
|
-
const manifest = parseProtocolV2ResourceManifest(manifestValue);
|
|
1276
|
-
const selectedFiles = selectProtocolV2ResourceManifestFiles({
|
|
1277
|
-
manifest,
|
|
1278
|
-
targetsToUpdate: this.params.targetsToUpdate ?? [],
|
|
1279
|
-
});
|
|
1280
|
-
const expectedEntryNames = new Set([
|
|
1281
|
-
'manifest.json',
|
|
1282
|
-
...selectedFiles.map(file => file.archive_path),
|
|
1283
|
-
]);
|
|
1284
|
-
if (
|
|
1285
|
-
entries.length !== expectedEntryNames.size ||
|
|
1286
|
-
entries.some(entry => !expectedEntryNames.has(entry.name))
|
|
1287
|
-
) {
|
|
1288
|
-
throw ERRORS.TypedError(
|
|
1289
|
-
HardwareErrorCode.RuntimeError,
|
|
1290
|
-
'Protocol V2 local resource ZIP contains an unexpected entry',
|
|
1291
|
-
{ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
|
|
1292
|
-
);
|
|
1293
|
-
}
|
|
1294
|
-
|
|
1295
|
-
let totalSize = 0;
|
|
1296
|
-
const materializedEntries: FirmwareMemoryArtifactEntry[] = [
|
|
1297
|
-
{ entryName: 'manifest.json', binary: manifestBinary },
|
|
1298
|
-
];
|
|
1299
|
-
for (const file of selectedFiles) {
|
|
1300
|
-
const entry = zip.file(file.archive_path);
|
|
1301
|
-
if (!entry) {
|
|
1185
|
+
const fileBinary = await entry.async('arraybuffer');
|
|
1186
|
+
if (fileBinary.byteLength !== uncompressedSize) {
|
|
1302
1187
|
throw ERRORS.TypedError(
|
|
1303
1188
|
HardwareErrorCode.RuntimeError,
|
|
1304
|
-
`Protocol V2
|
|
1305
|
-
{ firmwareUpdateCode: '
|
|
1189
|
+
`Protocol V2 resource package is incomplete: ${entry.name}`,
|
|
1190
|
+
{ firmwareUpdateCode: 'FirmwareArtifactReceiptMismatch' }
|
|
1306
1191
|
);
|
|
1307
1192
|
}
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1193
|
+
let header: ProtocolV2ResourcePackageHeader;
|
|
1194
|
+
try {
|
|
1195
|
+
header = parseProtocolV2ResourcePackage(fileBinary);
|
|
1196
|
+
} catch (error) {
|
|
1311
1197
|
throw ERRORS.TypedError(
|
|
1312
1198
|
HardwareErrorCode.RuntimeError,
|
|
1313
|
-
`Protocol V2
|
|
1199
|
+
`Protocol V2 resource package is invalid: ${entry.name}: ${String(error)}`,
|
|
1314
1200
|
{ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
|
|
1315
1201
|
);
|
|
1316
1202
|
}
|
|
1317
|
-
const
|
|
1318
|
-
|
|
1319
|
-
if (fileBinary.byteLength !== file.size || digest !== file.sha256.toLowerCase()) {
|
|
1203
|
+
const canonicalDevicePath = header.devicePath.toLowerCase();
|
|
1204
|
+
if (devicePaths.has(canonicalDevicePath)) {
|
|
1320
1205
|
throw ERRORS.TypedError(
|
|
1321
1206
|
HardwareErrorCode.RuntimeError,
|
|
1322
|
-
`Protocol V2
|
|
1323
|
-
{ firmwareUpdateCode: '
|
|
1207
|
+
`Protocol V2 resource ZIP contains duplicate device path: ${header.devicePath}`,
|
|
1208
|
+
{ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
|
|
1324
1209
|
);
|
|
1325
1210
|
}
|
|
1326
|
-
|
|
1211
|
+
devicePaths.add(canonicalDevicePath);
|
|
1212
|
+
resources.push({ entryName: entry.name, binary: fileBinary, header });
|
|
1327
1213
|
}
|
|
1328
|
-
return { binary,
|
|
1214
|
+
return { binary, resources };
|
|
1329
1215
|
}
|
|
1330
1216
|
|
|
1331
1217
|
private async prepareProtocolV2ResourceSources(): Promise<ProtocolV2ResourceBundleSource[]> {
|
|
@@ -1390,110 +1276,20 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1390
1276
|
);
|
|
1391
1277
|
}
|
|
1392
1278
|
|
|
1393
|
-
//
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
const entriesByName = new Map(entries.map(entry => [entry.entryName, entry] as const));
|
|
1397
|
-
if (
|
|
1398
|
-
entries.length !== verifiedArchive.materializedEntries.length ||
|
|
1399
|
-
verifiedArchive.materializedEntries.some(entry => {
|
|
1400
|
-
const preparedEntry = entriesByName.get(entry.entryName);
|
|
1401
|
-
const digest = bytesToHex(sha256(new Uint8Array(entry.binary)));
|
|
1402
|
-
return (
|
|
1403
|
-
!preparedEntry ||
|
|
1404
|
-
preparedEntry.artifact.size !== entry.binary.byteLength ||
|
|
1405
|
-
preparedEntry.artifact.sha256.toLowerCase() !== digest
|
|
1406
|
-
);
|
|
1407
|
-
})
|
|
1408
|
-
) {
|
|
1409
|
-
throw ERRORS.TypedError(
|
|
1410
|
-
HardwareErrorCode.RuntimeError,
|
|
1411
|
-
'Protocol V2 prepared resource entries do not match the approved archive',
|
|
1412
|
-
{ firmwareUpdateCode: 'FirmwareArtifactReceiptMismatch' }
|
|
1413
|
-
);
|
|
1414
|
-
}
|
|
1415
|
-
|
|
1416
|
-
const verifiedEntriesByName = new Map(
|
|
1417
|
-
verifiedArchive.materializedEntries.map(entry => [entry.entryName, entry.binary] as const)
|
|
1418
|
-
);
|
|
1419
|
-
const manifestBinary = verifiedEntriesByName.get('manifest.json');
|
|
1420
|
-
if (!manifestBinary) {
|
|
1421
|
-
throw ERRORS.TypedError(
|
|
1422
|
-
HardwareErrorCode.RuntimeError,
|
|
1423
|
-
'Protocol V2 prepared resource archive has no valid manifest.json',
|
|
1424
|
-
{ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
|
|
1425
|
-
);
|
|
1426
|
-
}
|
|
1427
|
-
|
|
1428
|
-
let manifestValue: unknown;
|
|
1429
|
-
try {
|
|
1430
|
-
manifestValue = JSON.parse(new TextDecoder().decode(manifestBinary));
|
|
1431
|
-
} catch (error) {
|
|
1432
|
-
throw ERRORS.TypedError(
|
|
1433
|
-
HardwareErrorCode.RuntimeError,
|
|
1434
|
-
`Protocol V2 prepared resource manifest is invalid: ${String(error)}`,
|
|
1435
|
-
{ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
|
|
1436
|
-
);
|
|
1437
|
-
}
|
|
1438
|
-
const manifest = parseProtocolV2ResourceManifest(manifestValue);
|
|
1439
|
-
const selectedFiles = selectProtocolV2ResourceManifestFiles({
|
|
1440
|
-
manifest,
|
|
1441
|
-
targetsToUpdate: this.params.targetsToUpdate ?? [],
|
|
1442
|
-
});
|
|
1443
|
-
if (selectedFiles.length > PROTOCOL_V2_RESOURCE_FILE_MAX_COUNT) {
|
|
1444
|
-
throw ERRORS.TypedError(
|
|
1445
|
-
HardwareErrorCode.RuntimeError,
|
|
1446
|
-
'Protocol V2 prepared resource archive contains too many files',
|
|
1447
|
-
{ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
|
|
1448
|
-
);
|
|
1449
|
-
}
|
|
1450
|
-
|
|
1451
|
-
let totalSize = 0;
|
|
1452
|
-
const sources: ProtocolV2ResourceBundleSource[] = [];
|
|
1453
|
-
for (const [index, file] of selectedFiles.entries()) {
|
|
1454
|
-
const binary = verifiedEntriesByName.get(file.archive_path);
|
|
1455
|
-
if (!binary || binary.byteLength !== file.size) {
|
|
1456
|
-
throw ERRORS.TypedError(
|
|
1457
|
-
HardwareErrorCode.RuntimeError,
|
|
1458
|
-
`Protocol V2 prepared resource file does not match manifest: ${file.archive_path}`,
|
|
1459
|
-
{ firmwareUpdateCode: 'FirmwareArtifactReceiptMismatch' }
|
|
1460
|
-
);
|
|
1461
|
-
}
|
|
1462
|
-
totalSize += binary.byteLength;
|
|
1463
|
-
if (totalSize > PROTOCOL_V2_RESOURCE_TOTAL_MAX_BYTES) {
|
|
1464
|
-
throw ERRORS.TypedError(
|
|
1465
|
-
HardwareErrorCode.RuntimeError,
|
|
1466
|
-
'Protocol V2 prepared resource archive exceeds the total size limit',
|
|
1467
|
-
{ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
|
|
1468
|
-
);
|
|
1469
|
-
}
|
|
1470
|
-
// 使用从获批 ZIP 中提取的规范字节,避免宿主在校验后替换 entry reader 内容。
|
|
1471
|
-
const source = await this.openProtocolV2MemorySource(binary);
|
|
1472
|
-
const header =
|
|
1473
|
-
source.size >= PROTOCOL_V2_OKPP_HEADER_SIZE
|
|
1474
|
-
? parseProtocolV2OkppHeader(
|
|
1475
|
-
new Uint8Array(await source.readAt(0, PROTOCOL_V2_OKPP_HEADER_SIZE))
|
|
1476
|
-
)
|
|
1477
|
-
: null;
|
|
1478
|
-
sources.push({
|
|
1479
|
-
name: file.original_name || `resource-${index}`,
|
|
1480
|
-
source,
|
|
1481
|
-
devicePath: file.device_path,
|
|
1482
|
-
...(header
|
|
1483
|
-
? {
|
|
1484
|
-
version: header.version,
|
|
1485
|
-
payloadHash: header.payloadHash,
|
|
1486
|
-
headerHash: header.headerHash,
|
|
1487
|
-
}
|
|
1488
|
-
: {}),
|
|
1489
|
-
});
|
|
1490
|
-
}
|
|
1491
|
-
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);
|
|
1492
1282
|
}
|
|
1493
1283
|
|
|
1494
|
-
private async runProtocolV2PreparedArtifacts(
|
|
1284
|
+
private async runProtocolV2PreparedArtifacts(
|
|
1285
|
+
features: Features,
|
|
1286
|
+
firmwareType: EFirmwareType,
|
|
1287
|
+
announceDownload = true
|
|
1288
|
+
) {
|
|
1495
1289
|
try {
|
|
1496
|
-
|
|
1290
|
+
if (announceDownload) {
|
|
1291
|
+
this.postTipMessage(FirmwareUpdateTipMessage.StartDownloadFirmware);
|
|
1292
|
+
}
|
|
1497
1293
|
const installSources = await this.prepareProtocolV2InstallSources(firmwareType, features);
|
|
1498
1294
|
const resourceSources = await this.prepareProtocolV2ResourceSources();
|
|
1499
1295
|
if (installSources.length === 0 && resourceSources.length === 0) {
|
|
@@ -1502,7 +1298,9 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1502
1298
|
'No firmware to update'
|
|
1503
1299
|
);
|
|
1504
1300
|
}
|
|
1505
|
-
|
|
1301
|
+
if (announceDownload) {
|
|
1302
|
+
this.postTipMessage(FirmwareUpdateTipMessage.FinishDownloadFirmware);
|
|
1303
|
+
}
|
|
1506
1304
|
|
|
1507
1305
|
return await this.executeProtocolV2SourceUpdate({
|
|
1508
1306
|
installSources,
|
|
@@ -1557,21 +1355,20 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1557
1355
|
const expected = this.params?.expectedTargetVersions;
|
|
1558
1356
|
if (!expected) return;
|
|
1559
1357
|
const features = this.protocolV2LatestFinalFeatures;
|
|
1560
|
-
const
|
|
1561
|
-
const applicationTargets = requestedTargets.filter(
|
|
1562
|
-
target => target === 'app_v1' || target === 'app_v2'
|
|
1563
|
-
);
|
|
1358
|
+
const deviceInfo = this.protocolV2LatestFinalDeviceInfo;
|
|
1564
1359
|
const visibleVersions: Partial<Record<FirmwareUpdateV4Target, string>> = {
|
|
1565
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,
|
|
1566
1366
|
coprocessor: features ? getDeviceBLEFirmwareVersion(features).join('.') : undefined,
|
|
1567
1367
|
se01: features?.se01Version ?? undefined,
|
|
1568
1368
|
se02: features?.se02Version ?? undefined,
|
|
1569
1369
|
se03: features?.se03Version ?? undefined,
|
|
1570
1370
|
se04: features?.se04Version ?? undefined,
|
|
1571
1371
|
};
|
|
1572
|
-
if (features && applicationTargets.length === 1) {
|
|
1573
|
-
visibleVersions[applicationTargets[0]] = getDeviceFirmwareVersion(features).join('.');
|
|
1574
|
-
}
|
|
1575
1372
|
const expectedEntries = (
|
|
1576
1373
|
Object.entries(expected) as Array<[FirmwareUpdateV4Target, string]>
|
|
1577
1374
|
).filter(([target]) => !targets || targets.includes(target));
|
|
@@ -1588,9 +1385,11 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1588
1385
|
Math.floor(statusVersion / 0x100) % 0x100
|
|
1589
1386
|
}.${statusVersion % 0x100}`;
|
|
1590
1387
|
if (!observedVersion) {
|
|
1591
|
-
|
|
1388
|
+
const hasCompleteTargetEvidence =
|
|
1389
|
+
targetId !== undefined && this.protocolV2CompletedTargetIds.has(targetId);
|
|
1390
|
+
if (hasCompleteTargetEvidence) {
|
|
1592
1391
|
Log.warn(
|
|
1593
|
-
`Protocol V2 target ${target} has no observable final version;
|
|
1392
|
+
`Protocol V2 target ${target} has no observable final version; install completion is authoritative`
|
|
1594
1393
|
);
|
|
1595
1394
|
} else {
|
|
1596
1395
|
throw ERRORS.TypedError(
|
|
@@ -1607,37 +1406,23 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1607
1406
|
}
|
|
1608
1407
|
}
|
|
1609
1408
|
|
|
1610
|
-
private
|
|
1611
|
-
|
|
1612
|
-
|
|
1409
|
+
private async getProtocolV2DeviceFeatures(): Promise<Features> {
|
|
1410
|
+
let { features } = this.device;
|
|
1411
|
+
if (typeof this.device.getFeatures === 'function') {
|
|
1412
|
+
features ??= await this.device.getFeatures();
|
|
1613
1413
|
}
|
|
1614
|
-
if (
|
|
1615
|
-
|
|
1414
|
+
if (!features) {
|
|
1415
|
+
throw ERRORS.TypedError(HardwareErrorCode.RuntimeError, 'Device features not available');
|
|
1616
1416
|
}
|
|
1617
|
-
const
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
if (this.params.se02Binary) targets.add('se02');
|
|
1627
|
-
if (this.params.se03Binary) targets.add('se03');
|
|
1628
|
-
if (this.params.se04Binary) targets.add('se04');
|
|
1629
|
-
return Array.from(targets);
|
|
1630
|
-
}
|
|
1631
|
-
|
|
1632
|
-
private async getProtocolV2DeviceFeatures(): Promise<Features> {
|
|
1633
|
-
if (this.device.features) {
|
|
1634
|
-
return this.device.features;
|
|
1635
|
-
}
|
|
1636
|
-
if (typeof this.device.getFeatures === 'function') {
|
|
1637
|
-
const features = await this.device.getFeatures();
|
|
1638
|
-
if (features) return features;
|
|
1639
|
-
}
|
|
1640
|
-
throw ERRORS.TypedError(HardwareErrorCode.RuntimeError, 'Device features not available');
|
|
1417
|
+
const deviceType = this.device.getCurrentDeviceType();
|
|
1418
|
+
if (deviceType !== EDeviceType.Pro2 && deviceType !== EDeviceType.Neo) {
|
|
1419
|
+
return features;
|
|
1420
|
+
}
|
|
1421
|
+
return {
|
|
1422
|
+
...features,
|
|
1423
|
+
deviceType,
|
|
1424
|
+
firmwareType: this.device.getCurrentFirmwareType(),
|
|
1425
|
+
};
|
|
1641
1426
|
}
|
|
1642
1427
|
|
|
1643
1428
|
private getProtocolV2SerialNumber(deviceInfo: ProtocolV2DeviceInfo) {
|
|
@@ -1840,22 +1625,54 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1840
1625
|
`Protocol V2 firmware fingerprint mismatch: ${key}/${component.target}`
|
|
1841
1626
|
);
|
|
1842
1627
|
}
|
|
1843
|
-
const expectedPayloadHash = normalizeProtocolV2Hex(component.payloadHash);
|
|
1844
|
-
if (expectedPayloadHash) {
|
|
1845
|
-
const header = parseProtocolV2OkppHeader(toProtocolV2Bytes(binary));
|
|
1846
|
-
if (!header || header.payloadHash !== expectedPayloadHash) {
|
|
1847
|
-
throw ERRORS.TypedError(
|
|
1848
|
-
HardwareErrorCode.RuntimeError,
|
|
1849
|
-
`Protocol V2 firmware payload hash mismatch: ${key}/${component.target}`
|
|
1850
|
-
);
|
|
1851
|
-
}
|
|
1852
|
-
}
|
|
1853
1628
|
return {
|
|
1854
1629
|
...target,
|
|
1855
1630
|
binary,
|
|
1856
1631
|
};
|
|
1857
1632
|
}
|
|
1858
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
|
+
|
|
1859
1676
|
private async prepareRemoteProtocolV2Binaries(
|
|
1860
1677
|
firmwareType: EFirmwareType,
|
|
1861
1678
|
features: Features,
|
|
@@ -1899,8 +1716,14 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1899
1716
|
: undefined;
|
|
1900
1717
|
if (updateTarget && targetsToUpdate.has(updateTarget)) {
|
|
1901
1718
|
const explicitInstallItem = explicitInstallItemByTargetId.get(target.targetId);
|
|
1902
|
-
|
|
1903
|
-
|
|
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
|
+
}
|
|
1904
1727
|
if (installItem.kind === 'bootloader') {
|
|
1905
1728
|
bootloaderBinary = installItem.binary;
|
|
1906
1729
|
} else {
|
|
@@ -1939,6 +1762,12 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1939
1762
|
return `vol0:/${path}`;
|
|
1940
1763
|
}
|
|
1941
1764
|
|
|
1765
|
+
private getProtocolV2ResourceComparePath(devicePath: string) {
|
|
1766
|
+
return isProtocolV2BootResourcePackagePath(devicePath)
|
|
1767
|
+
? PROTOCOL_V2_BOOT_RESOURCE_PACKAGE_PATH
|
|
1768
|
+
: devicePath;
|
|
1769
|
+
}
|
|
1770
|
+
|
|
1942
1771
|
private async readProtocolV2DeviceFileHeader(path: string, expectedSize?: number) {
|
|
1943
1772
|
const typedCall = this.device.getCommands().typedCall.bind(this.device.getCommands());
|
|
1944
1773
|
const filePath = this.getProtocolV2ResourceFilePath(path);
|
|
@@ -1950,7 +1779,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1950
1779
|
!pathInfoRes.message?.exist ||
|
|
1951
1780
|
pathInfoRes.message?.directory ||
|
|
1952
1781
|
fileSize === undefined ||
|
|
1953
|
-
fileSize <
|
|
1782
|
+
fileSize < PROTOCOL_V2_RESOURCE_PACKAGE_HEADER_SIZE ||
|
|
1954
1783
|
(expectedSize !== undefined && fileSize !== expectedSize)
|
|
1955
1784
|
) {
|
|
1956
1785
|
return null;
|
|
@@ -1959,8 +1788,8 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1959
1788
|
const chunkSize = this.getProtocolV2FirmwareChunkSize('read');
|
|
1960
1789
|
const chunks: Uint8Array[] = [];
|
|
1961
1790
|
let offset = 0;
|
|
1962
|
-
while (offset <
|
|
1963
|
-
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);
|
|
1964
1793
|
const res = await typedCall('FilesystemFileRead', 'FilesystemFile', {
|
|
1965
1794
|
file: {
|
|
1966
1795
|
path: filePath,
|
|
@@ -1982,7 +1811,11 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1982
1811
|
headerBytes.set(chunk, cursor);
|
|
1983
1812
|
cursor += chunk.byteLength;
|
|
1984
1813
|
});
|
|
1985
|
-
|
|
1814
|
+
try {
|
|
1815
|
+
return parseProtocolV2ResourcePackageHeader(headerBytes, fileSize);
|
|
1816
|
+
} catch {
|
|
1817
|
+
return null;
|
|
1818
|
+
}
|
|
1986
1819
|
}
|
|
1987
1820
|
|
|
1988
1821
|
/** Compare the downloaded and installed okpkg headers before transferring a resource. */
|
|
@@ -1997,7 +1830,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1997
1830
|
|
|
1998
1831
|
try {
|
|
1999
1832
|
const header = await this.readProtocolV2DeviceFileHeader(
|
|
2000
|
-
bundle.devicePath,
|
|
1833
|
+
this.getProtocolV2ResourceComparePath(bundle.devicePath),
|
|
2001
1834
|
bundle.source.size
|
|
2002
1835
|
);
|
|
2003
1836
|
if (!header) return false;
|
|
@@ -2012,6 +1845,9 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2012
1845
|
if (!expectedHeaderHash || header.headerHash !== expectedHeaderHash) return false;
|
|
2013
1846
|
return true;
|
|
2014
1847
|
} catch (error) {
|
|
1848
|
+
if (isBleStaleBondHardwareError(error)) {
|
|
1849
|
+
throw error;
|
|
1850
|
+
}
|
|
2015
1851
|
Log.log(`[FirmwareUpdateV4] RESC bundle ${bundle.name} header check failed: `, error);
|
|
2016
1852
|
return false;
|
|
2017
1853
|
}
|
|
@@ -2041,21 +1877,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2041
1877
|
return this.device.features?.mode === 'romloader';
|
|
2042
1878
|
}
|
|
2043
1879
|
|
|
2044
|
-
async
|
|
2045
|
-
// romloader is the first update environment and forwards targets to bootloader.
|
|
2046
|
-
// It rejects DeviceRebootType.Bootloader, so reuse the current connection.
|
|
2047
|
-
if (this.isProtocolV2RomloaderMode()) {
|
|
2048
|
-
Log.debug('Protocol V2 device is in romloader mode; start firmware update directly');
|
|
2049
|
-
this.protocolV2ExecutionInLoader = true;
|
|
2050
|
-
return false;
|
|
2051
|
-
}
|
|
2052
|
-
if (this.isProtocolV2BootloaderMode()) {
|
|
2053
|
-
Log.debug('Protocol V2 device is already in bootloader mode, skip reboot');
|
|
2054
|
-
this.protocolV2ExecutionInLoader = true;
|
|
2055
|
-
this.postTipMessage(FirmwareUpdateTipMessage.GoToBootloaderSuccess);
|
|
2056
|
-
return false;
|
|
2057
|
-
}
|
|
2058
|
-
|
|
1880
|
+
private async rebootProtocolV2ToBootloader() {
|
|
2059
1881
|
try {
|
|
2060
1882
|
this.postTipMessage(FirmwareUpdateTipMessage.AutoRebootToBootloader);
|
|
2061
1883
|
await this.protocolV2Reboot(DeviceRebootType.Bootloader);
|
|
@@ -2073,6 +1895,24 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2073
1895
|
}
|
|
2074
1896
|
}
|
|
2075
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
|
+
|
|
2076
1916
|
private async waitForProtocolV2BootloaderMode(
|
|
2077
1917
|
timeout = PROTOCOL_V2_BOOTLOADER_RECONNECT_TIMEOUT,
|
|
2078
1918
|
retryInterval = 1000
|
|
@@ -2101,7 +1941,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2101
1941
|
}
|
|
2102
1942
|
lastError = new Error('Protocol V2 device is reachable but is not in bootloader mode');
|
|
2103
1943
|
} catch (error) {
|
|
2104
|
-
if (this.isProtocolV2ReconnectIdentityError(error)) {
|
|
1944
|
+
if (isBleStaleBondHardwareError(error) || this.isProtocolV2ReconnectIdentityError(error)) {
|
|
2105
1945
|
throw error;
|
|
2106
1946
|
}
|
|
2107
1947
|
shouldReconnect = true;
|
|
@@ -2157,68 +1997,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2157
1997
|
return entries;
|
|
2158
1998
|
}
|
|
2159
1999
|
|
|
2160
|
-
private
|
|
2161
|
-
installSources,
|
|
2162
|
-
resourceSources,
|
|
2163
|
-
}: {
|
|
2164
|
-
installSources: ProtocolV2InstallSource[];
|
|
2165
|
-
resourceSources: ProtocolV2ResourceBundleSource[];
|
|
2166
|
-
}): ProtocolV2ExecutionPhase[] {
|
|
2167
|
-
const phases: ProtocolV2ExecutionPhase[] = [];
|
|
2168
|
-
const bootResourceSources = resourceSources.filter(source =>
|
|
2169
|
-
isProtocolV2BootResourcePackagePath(source.devicePath)
|
|
2170
|
-
);
|
|
2171
|
-
const remainingResourceSources = resourceSources.filter(
|
|
2172
|
-
source => !isProtocolV2BootResourcePackagePath(source.devicePath)
|
|
2173
|
-
);
|
|
2174
|
-
// A previous interrupted run may have left a promotable staging file. Replace
|
|
2175
|
-
// it with the current approved package before any firmware-triggered reboot.
|
|
2176
|
-
if (bootResourceSources.length > 0) {
|
|
2177
|
-
phases.push({
|
|
2178
|
-
kind: 'resource-sync',
|
|
2179
|
-
installSources: [],
|
|
2180
|
-
resourceSources: bootResourceSources,
|
|
2181
|
-
});
|
|
2182
|
-
}
|
|
2183
|
-
const bootloaderSources = installSources.filter(source => source.kind === 'bootloader');
|
|
2184
|
-
if (bootloaderSources.length > 0) {
|
|
2185
|
-
phases.push(
|
|
2186
|
-
{
|
|
2187
|
-
kind: 'bootloader-install',
|
|
2188
|
-
installSources: bootloaderSources,
|
|
2189
|
-
resourceSources: [],
|
|
2190
|
-
},
|
|
2191
|
-
{
|
|
2192
|
-
kind: 'bootloader-verify',
|
|
2193
|
-
installSources: [],
|
|
2194
|
-
resourceSources: [],
|
|
2195
|
-
}
|
|
2196
|
-
);
|
|
2197
|
-
}
|
|
2198
|
-
if (remainingResourceSources.length > 0) {
|
|
2199
|
-
phases.push({
|
|
2200
|
-
kind: 'resource-sync',
|
|
2201
|
-
installSources: [],
|
|
2202
|
-
resourceSources: remainingResourceSources,
|
|
2203
|
-
});
|
|
2204
|
-
}
|
|
2205
|
-
const componentSources = installSources.filter(source => source.kind !== 'bootloader');
|
|
2206
|
-
if (componentSources.length > 0) {
|
|
2207
|
-
phases.push({
|
|
2208
|
-
kind: 'component-install',
|
|
2209
|
-
installSources: componentSources,
|
|
2210
|
-
resourceSources: [],
|
|
2211
|
-
});
|
|
2212
|
-
}
|
|
2213
|
-
phases.push({
|
|
2214
|
-
kind: 'final-verify',
|
|
2215
|
-
installSources: [],
|
|
2216
|
-
resourceSources: [],
|
|
2217
|
-
});
|
|
2218
|
-
return phases;
|
|
2219
|
-
}
|
|
2220
|
-
|
|
2221
|
-
private async executeProtocolV2Phases({
|
|
2000
|
+
private async executeProtocolV2SourceUpdate({
|
|
2222
2001
|
installSources,
|
|
2223
2002
|
resourceSources,
|
|
2224
2003
|
}: {
|
|
@@ -2226,38 +2005,22 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2226
2005
|
resourceSources: ProtocolV2ResourceBundleSource[];
|
|
2227
2006
|
}) {
|
|
2228
2007
|
this.protocolV2BootResourceStagingSafe = false;
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
) {
|
|
2244
|
-
await this.ensureProtocolV2BootResourceStagingIsEmpty();
|
|
2245
|
-
}
|
|
2246
|
-
await this.executeProtocolV2TransferPhase(phase);
|
|
2247
|
-
} else if (phase.kind === 'bootloader-install' || phase.kind === 'component-install') {
|
|
2248
|
-
await this.enterProtocolV2BootloaderMode();
|
|
2249
|
-
await this.ensureProtocolV2BootResourceStagingIsEmpty();
|
|
2250
|
-
await this.executeProtocolV2TransferPhase(phase);
|
|
2251
|
-
await this.exitProtocolV2BootloaderToNormal();
|
|
2252
|
-
} else if (phase.kind === 'bootloader-verify') {
|
|
2253
|
-
await this.waitForProtocolV2FinalFeatures();
|
|
2254
|
-
this.assertExpectedProtocolV2Versions(['boot']);
|
|
2255
|
-
}
|
|
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
|
+
});
|
|
2256
2022
|
}
|
|
2257
|
-
|
|
2258
|
-
HardwareErrorCode.RuntimeError,
|
|
2259
|
-
'Protocol V2 execution has no final verification phase'
|
|
2260
|
-
);
|
|
2023
|
+
return this.completeProtocolV2FinalVerification();
|
|
2261
2024
|
}
|
|
2262
2025
|
|
|
2263
2026
|
private async ensureProtocolV2BootResourceStagingIsEmpty() {
|
|
@@ -2289,19 +2052,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2289
2052
|
this.protocolV2BootResourceStagingSafe = true;
|
|
2290
2053
|
}
|
|
2291
2054
|
|
|
2292
|
-
private async executeProtocolV2SourceUpdate({
|
|
2293
|
-
installSources,
|
|
2294
|
-
resourceSources,
|
|
2295
|
-
}: {
|
|
2296
|
-
installSources: ProtocolV2InstallSource[];
|
|
2297
|
-
resourceSources: ProtocolV2ResourceBundleSource[];
|
|
2298
|
-
}) {
|
|
2299
|
-
return this.executeProtocolV2Phases({
|
|
2300
|
-
installSources,
|
|
2301
|
-
resourceSources,
|
|
2302
|
-
});
|
|
2303
|
-
}
|
|
2304
|
-
|
|
2305
2055
|
private async executeProtocolV2Update({
|
|
2306
2056
|
fwBinaryMap,
|
|
2307
2057
|
bootloaderBinary,
|
|
@@ -2326,7 +2076,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2326
2076
|
kind: item.kind,
|
|
2327
2077
|
}))
|
|
2328
2078
|
);
|
|
2329
|
-
return await this.
|
|
2079
|
+
return await this.executeProtocolV2SourceUpdate({
|
|
2330
2080
|
installSources,
|
|
2331
2081
|
resourceSources: [],
|
|
2332
2082
|
});
|
|
@@ -2338,15 +2088,11 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2338
2088
|
private async executeProtocolV2TransferPhase({
|
|
2339
2089
|
installSources,
|
|
2340
2090
|
resourceSources,
|
|
2341
|
-
}:
|
|
2091
|
+
}: ProtocolV2TransferBatch) {
|
|
2342
2092
|
let totalSize = installSources.reduce((total, item) => total + item.source.size, 0);
|
|
2343
2093
|
const resourcesToSync: ProtocolV2ResourceBundleSource[] = [];
|
|
2344
2094
|
for (const resource of resourceSources) {
|
|
2345
|
-
|
|
2346
|
-
// stale staging bytes with the artifact approved by the current PreparedPlan,
|
|
2347
|
-
// even when the mounted final file itself is already current.
|
|
2348
|
-
const requiresFreshStaging = isProtocolV2BootResourcePackagePath(resource.devicePath);
|
|
2349
|
-
if (!requiresFreshStaging && (await this.isProtocolV2ResourceBundleUpToDate(resource))) {
|
|
2095
|
+
if (await this.isProtocolV2ResourceBundleUpToDate(resource)) {
|
|
2350
2096
|
Log.log(`[FirmwareUpdateV4] skip RESC bundle ${resource.name}; already up to date`);
|
|
2351
2097
|
} else {
|
|
2352
2098
|
resourcesToSync.push(resource);
|
|
@@ -2355,11 +2101,13 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2355
2101
|
}
|
|
2356
2102
|
|
|
2357
2103
|
this.postTipMessage(FirmwareUpdateTipMessage.StartTransferData);
|
|
2104
|
+
this.protocolV2LastTransferProgress = undefined;
|
|
2105
|
+
this.protocolV2LastTransferProgressAt = 0;
|
|
2358
2106
|
let processedSize = 0;
|
|
2359
2107
|
for (const resource of resourcesToSync) {
|
|
2360
2108
|
// The bootloader keeps its live resource package mounted. FatFs rejects
|
|
2361
2109
|
// replacing an open file, so early boot promotes this staging file before mounting it.
|
|
2362
|
-
const writePath =
|
|
2110
|
+
const writePath = resource.devicePath;
|
|
2363
2111
|
processedSize = await this.protocolV2SourceUpdateProcess({
|
|
2364
2112
|
source: resource.source,
|
|
2365
2113
|
filePath: writePath,
|
|
@@ -2400,8 +2148,13 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2400
2148
|
target_id: item.targetId,
|
|
2401
2149
|
path: item.path,
|
|
2402
2150
|
}));
|
|
2403
|
-
|
|
2404
|
-
|
|
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
|
+
}
|
|
2405
2158
|
}
|
|
2406
2159
|
|
|
2407
2160
|
private async protocolV2SourceUpdateProcess({
|
|
@@ -2446,23 +2199,33 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2446
2199
|
);
|
|
2447
2200
|
}
|
|
2448
2201
|
const transferredBytes = processedSize + chunkEnd;
|
|
2449
|
-
const
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
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', {
|
|
2454
2214
|
transferredBytes,
|
|
2455
2215
|
totalBytes: totalSize,
|
|
2456
2216
|
rateBytesPerSecond:
|
|
2457
2217
|
elapsedMs > 0 ? Math.round((chunkEnd / elapsedMs) * 1000) : undefined,
|
|
2458
2218
|
elapsedMs,
|
|
2459
|
-
}
|
|
2460
|
-
|
|
2219
|
+
});
|
|
2220
|
+
}
|
|
2461
2221
|
return length;
|
|
2462
2222
|
},
|
|
2463
2223
|
});
|
|
2464
2224
|
return processedSize + source.size;
|
|
2465
2225
|
} catch (error) {
|
|
2226
|
+
if (isBleStaleBondHardwareError(error)) {
|
|
2227
|
+
throw error;
|
|
2228
|
+
}
|
|
2466
2229
|
lastError = error;
|
|
2467
2230
|
if (attempt < PROTOCOL_V2_FILE_TRANSFER_RETRY_COUNT) {
|
|
2468
2231
|
await this.recoverProtocolV2FileTransfer();
|
|
@@ -2492,19 +2255,41 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2492
2255
|
}
|
|
2493
2256
|
}
|
|
2494
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
|
+
|
|
2495
2275
|
private assertProtocolV2TargetStatus(
|
|
2496
2276
|
statusTargets: ProtocolV2FirmwareUpdateStatusTarget[],
|
|
2497
2277
|
expectedTargetIds: Set<number>,
|
|
2498
|
-
expectedPaths
|
|
2278
|
+
expectedPaths?: Map<number, string>,
|
|
2279
|
+
liveTargetIds?: Set<number>
|
|
2499
2280
|
) {
|
|
2281
|
+
const resolvedExpectedPaths = expectedPaths ?? new Map<number, string>();
|
|
2500
2282
|
Log.log(
|
|
2501
2283
|
`[FirmwareUpdateV4] DeviceFirmwareUpdateStatus records=${JSON.stringify(statusTargets)}`
|
|
2502
2284
|
);
|
|
2503
|
-
const failedTarget = statusTargets.find(
|
|
2504
|
-
target
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
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
|
+
});
|
|
2508
2293
|
if (failedTarget) {
|
|
2509
2294
|
const failedTargetDetails = JSON.stringify({
|
|
2510
2295
|
targetId: failedTarget.target_id,
|
|
@@ -2515,13 +2300,33 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2515
2300
|
Log.error(`[FirmwareUpdateV4] firmware install failed target=${failedTargetDetails}`);
|
|
2516
2301
|
throw ERRORS.TypedError(
|
|
2517
2302
|
HardwareErrorCode.FirmwareError,
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
}`
|
|
2303
|
+
'Protocol V2 firmware install failed',
|
|
2304
|
+
{
|
|
2305
|
+
firmwareUpdateCode: PROTOCOL_V2_INSTALL_FAILED_CODE,
|
|
2306
|
+
}
|
|
2523
2307
|
);
|
|
2524
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
|
+
}
|
|
2525
2330
|
|
|
2526
2331
|
const matchingTargets = statusTargets.filter(target =>
|
|
2527
2332
|
expectedTargetIds.has(normalizeProtocolV2TargetId(target.target_id) ?? -1)
|
|
@@ -2529,14 +2334,21 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2529
2334
|
const seenTargetIds = new Set<number>();
|
|
2530
2335
|
for (const target of matchingTargets) {
|
|
2531
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));
|
|
2532
2343
|
if (
|
|
2533
2344
|
targetId === undefined ||
|
|
2534
2345
|
seenTargetIds.has(targetId) ||
|
|
2535
|
-
|
|
2346
|
+
pathConflictsWithCurrentInstall
|
|
2536
2347
|
) {
|
|
2348
|
+
Log.error(`[FirmwareUpdateV4] install status conflicts with target=${target.target_id}`);
|
|
2537
2349
|
throw ERRORS.TypedError(
|
|
2538
|
-
HardwareErrorCode.
|
|
2539
|
-
|
|
2350
|
+
HardwareErrorCode.FirmwareError,
|
|
2351
|
+
'Protocol V2 firmware install status conflict',
|
|
2540
2352
|
{
|
|
2541
2353
|
firmwareUpdateCode: PROTOCOL_V2_INSTALL_STATUS_CONFLICT_CODE,
|
|
2542
2354
|
}
|
|
@@ -2544,14 +2356,19 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2544
2356
|
}
|
|
2545
2357
|
seenTargetIds.add(targetId);
|
|
2546
2358
|
}
|
|
2547
|
-
const completedTargets = matchingTargets.filter(target =>
|
|
2548
|
-
|
|
2549
|
-
|
|
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
|
+
});
|
|
2550
2366
|
const completedTargetIds = new Set<number>();
|
|
2551
2367
|
completedTargets.forEach(target => {
|
|
2552
2368
|
const targetId = normalizeProtocolV2TargetId(target.target_id);
|
|
2553
2369
|
if (targetId !== undefined) {
|
|
2554
2370
|
completedTargetIds.add(targetId);
|
|
2371
|
+
this.protocolV2CompletedTargetIds.add(targetId);
|
|
2555
2372
|
}
|
|
2556
2373
|
});
|
|
2557
2374
|
if (completedTargetIds.size === expectedTargetIds.size && expectedTargetIds.size > 0) {
|
|
@@ -2582,16 +2399,54 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2582
2399
|
}
|
|
2583
2400
|
|
|
2584
2401
|
if (expectedTargetIds.size > 0 && matchingTargets.length > 0) {
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
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
|
+
});
|
|
2588
2437
|
const completedProgress = Math.floor(
|
|
2589
2438
|
(completedTargetIds.size / expectedTargetIds.size) * 100
|
|
2590
2439
|
);
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
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
|
+
}
|
|
2595
2450
|
}
|
|
2596
2451
|
|
|
2597
2452
|
return false;
|
|
@@ -2611,31 +2466,95 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2611
2466
|
return Array.from(expectedTargetIds).filter(targetId => !reportedTargetIds.has(targetId));
|
|
2612
2467
|
}
|
|
2613
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
|
+
|
|
2614
2514
|
private async waitForProtocolV2FirmwareUpdateComplete(
|
|
2615
|
-
targets: Array<{ target_id: number; path: string }
|
|
2515
|
+
targets: Array<{ target_id: number; path: string }>,
|
|
2516
|
+
requireCurrentInstallStatus = false
|
|
2616
2517
|
) {
|
|
2617
|
-
this.protocolV2FinalStatusVerified = false;
|
|
2618
2518
|
const expectedTargetIds = new Set(targets.map(target => target.target_id));
|
|
2619
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);
|
|
2620
2523
|
const startTime = Date.now();
|
|
2621
2524
|
let lastError: unknown;
|
|
2622
|
-
|
|
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;
|
|
2623
2529
|
let deviceInfo: ProtocolV2DeviceInfo | undefined;
|
|
2624
|
-
let
|
|
2625
|
-
let
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
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>();
|
|
2630
2539
|
|
|
2631
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();
|
|
2632
2544
|
try {
|
|
2633
2545
|
if (shouldReconnect) {
|
|
2634
|
-
|
|
2635
|
-
|
|
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
|
+
}
|
|
2636
2555
|
shouldReconnect = false;
|
|
2637
2556
|
}
|
|
2638
|
-
|
|
2557
|
+
let currentDeviceInfo = deviceInfo;
|
|
2639
2558
|
try {
|
|
2640
2559
|
const statusResponse = await this.device.getCommands().typedCall(
|
|
2641
2560
|
'DeviceFirmwareUpdateStatusGet',
|
|
@@ -2643,91 +2562,243 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2643
2562
|
{
|
|
2644
2563
|
fields: {
|
|
2645
2564
|
status: true,
|
|
2565
|
+
progress_percent: true,
|
|
2566
|
+
phase_info: true,
|
|
2646
2567
|
payload_version: true,
|
|
2647
2568
|
path: true,
|
|
2648
2569
|
},
|
|
2649
2570
|
},
|
|
2650
2571
|
{ timeoutMs: PROTOCOL_V2_FIRMWARE_STATUS_RESPONSE_TIMEOUT }
|
|
2651
2572
|
);
|
|
2573
|
+
if (this.protocolV2InstallTerminalSuccessObserved) {
|
|
2574
|
+
installEvidenceObserved = true;
|
|
2575
|
+
}
|
|
2652
2576
|
const statusTargets = (statusResponse.message.records ??
|
|
2653
2577
|
[]) as ProtocolV2FirmwareUpdateStatusTarget[];
|
|
2654
|
-
|
|
2655
|
-
|
|
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
|
+
) {
|
|
2656
2679
|
return;
|
|
2657
2680
|
}
|
|
2658
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
|
+
|
|
2659
2688
|
if (
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2689
|
+
requireCurrentInstallStatus &&
|
|
2690
|
+
!hasCurrentInstallEvidence &&
|
|
2691
|
+
matchingStatusTargets.length > 0
|
|
2663
2692
|
) {
|
|
2664
|
-
|
|
2665
|
-
'
|
|
2693
|
+
lastError = new Error(
|
|
2694
|
+
'Protocol V2 firmware status is stale; waiting for the current install to start'
|
|
2666
2695
|
);
|
|
2667
|
-
this.postProgressMessage(100, 'installingFirmware');
|
|
2668
|
-
return;
|
|
2669
2696
|
}
|
|
2670
2697
|
|
|
2671
|
-
|
|
2672
|
-
statusTargets
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
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))
|
|
2684
2716
|
) {
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
.filter((targetId): targetId is number => targetId !== undefined);
|
|
2688
|
-
throw ERRORS.TypedError(
|
|
2689
|
-
HardwareErrorCode.FirmwareError,
|
|
2690
|
-
`Protocol V2 firmware status is missing requested records: targetIds=${missingKey} reportedTargetIds=${reportedTargetIds.join(
|
|
2691
|
-
','
|
|
2692
|
-
)}`
|
|
2717
|
+
Log.log(
|
|
2718
|
+
'[FirmwareUpdateV4] empty firmware status after confirmed App reboot; update complete'
|
|
2693
2719
|
);
|
|
2720
|
+
this.recordProtocolV2AuthoritativeInstallCompletion(expectedTargetIds);
|
|
2721
|
+
this.postProgressMessage(100, 'installingFirmware');
|
|
2722
|
+
return;
|
|
2694
2723
|
}
|
|
2724
|
+
}
|
|
2725
|
+
|
|
2726
|
+
if (statusTargets.length === 0) {
|
|
2695
2727
|
lastError = new Error(
|
|
2696
|
-
|
|
2728
|
+
'Protocol V2 firmware update is waiting for user confirmation or target status'
|
|
2697
2729
|
);
|
|
2698
2730
|
} else {
|
|
2699
|
-
|
|
2700
|
-
|
|
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
|
+
}
|
|
2701
2743
|
}
|
|
2702
2744
|
} catch (error) {
|
|
2703
2745
|
if (isProtocolV2TerminalInstallStatusError(error)) {
|
|
2704
2746
|
throw error;
|
|
2705
2747
|
}
|
|
2706
|
-
// Only consecutive full status dumps can prove that a record is absent.
|
|
2707
|
-
// A reboot or transport interruption starts a fresh grace window.
|
|
2708
|
-
resetMissingTargetStatusGrace();
|
|
2709
2748
|
// App firmware does not register DeviceFirmwareUpdateStatusGet. Treat the
|
|
2710
2749
|
// missing endpoint as completion only after the runtime probe confirms App mode.
|
|
2711
2750
|
if (isProtocolV2FirmwareStatusEndpointUnavailable(error)) {
|
|
2712
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) {
|
|
2713
2762
|
throw ERRORS.TypedError(
|
|
2714
2763
|
HardwareErrorCode.RuntimeError,
|
|
2715
2764
|
'Protocol V2 device identity is unavailable during install polling'
|
|
2716
2765
|
);
|
|
2717
2766
|
}
|
|
2718
|
-
|
|
2767
|
+
const isNormalMode = await this.probeProtocolV2NormalMode(reconnectDeviceInfo);
|
|
2768
|
+
if (
|
|
2769
|
+
isNormalMode &&
|
|
2770
|
+
(installEvidenceObserved ||
|
|
2771
|
+
this.hasProtocolV2InstallVersionChanged(expectedTargetIds))
|
|
2772
|
+
) {
|
|
2719
2773
|
Log.log(
|
|
2720
2774
|
'[FirmwareUpdateV4] firmware status endpoint unavailable after confirmed App reboot'
|
|
2721
2775
|
);
|
|
2776
|
+
this.recordProtocolV2AuthoritativeInstallCompletion(expectedTargetIds);
|
|
2722
2777
|
this.postProgressMessage(100, 'installingFirmware');
|
|
2723
2778
|
return;
|
|
2724
2779
|
}
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
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
|
+
}
|
|
2728
2789
|
} else {
|
|
2790
|
+
if (
|
|
2791
|
+
isBleCoprocessorInstall &&
|
|
2792
|
+
isProtocolV2DeviceDisconnectedError(error) &&
|
|
2793
|
+
!isProtocolV2ResponseTimeout(error)
|
|
2794
|
+
) {
|
|
2795
|
+
installStatusDisconnectObserved = true;
|
|
2796
|
+
finishedStatusSnapshotKey = undefined;
|
|
2797
|
+
finishedStatusSnapshotPolls = 0;
|
|
2798
|
+
}
|
|
2729
2799
|
shouldReconnect = true;
|
|
2730
2800
|
deviceInfo = undefined;
|
|
2801
|
+
bleInstallLinkReady = false;
|
|
2731
2802
|
lastError = error;
|
|
2732
2803
|
Log.log(
|
|
2733
2804
|
'[FirmwareUpdateV4] DeviceFirmwareUpdateStatusGet unavailable during install: ',
|
|
@@ -2751,18 +2822,24 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2751
2822
|
}
|
|
2752
2823
|
shouldReconnect = true;
|
|
2753
2824
|
deviceInfo = undefined;
|
|
2754
|
-
|
|
2825
|
+
bleInstallLinkReady = false;
|
|
2755
2826
|
Log.log('Protocol V2 firmware install device readiness probe failed: ', error);
|
|
2756
2827
|
}
|
|
2757
2828
|
await wait(1000);
|
|
2758
2829
|
}
|
|
2759
2830
|
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
`Protocol V2 firmware update status not complete within ${
|
|
2831
|
+
Log.error(
|
|
2832
|
+
`[FirmwareUpdateV4] install timed out after ${
|
|
2763
2833
|
PROTOCOL_V2_INSTALL_TIMEOUT / 1000
|
|
2764
2834
|
}s: ${this.normalizeErrorMessage(lastError)}`
|
|
2765
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
|
+
);
|
|
2766
2843
|
}
|
|
2767
2844
|
|
|
2768
2845
|
private async exitProtocolV2BootloaderToNormal() {
|
|
@@ -2774,6 +2851,9 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2774
2851
|
return;
|
|
2775
2852
|
}
|
|
2776
2853
|
} catch (error) {
|
|
2854
|
+
if (isBleStaleBondHardwareError(error)) {
|
|
2855
|
+
throw error;
|
|
2856
|
+
}
|
|
2777
2857
|
Log.log('[FirmwareUpdateV4] unable to confirm App mode before Normal reboot: ', error);
|
|
2778
2858
|
}
|
|
2779
2859
|
await this.protocolV2Reboot(DeviceRebootType.Normal);
|
|
@@ -2785,7 +2865,14 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2785
2865
|
deviceInfo,
|
|
2786
2866
|
PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT
|
|
2787
2867
|
);
|
|
2788
|
-
|
|
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
|
+
);
|
|
2789
2876
|
}
|
|
2790
2877
|
|
|
2791
2878
|
private async waitForProtocolV2FinalFeatures() {
|
|
@@ -2829,6 +2916,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2829
2916
|
const startTime = Date.now();
|
|
2830
2917
|
let lastError: unknown;
|
|
2831
2918
|
let shouldReconnect = true;
|
|
2919
|
+
this.protocolV2LatestFinalDeviceInfo = undefined;
|
|
2832
2920
|
|
|
2833
2921
|
while (Date.now() - startTime < timeout) {
|
|
2834
2922
|
try {
|
|
@@ -2845,9 +2933,11 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2845
2933
|
this.assertProtocolV2DeviceInfoIdentity(deviceInfo);
|
|
2846
2934
|
const features = await this.device.probeProtocolV2RuntimeState(
|
|
2847
2935
|
deviceInfo,
|
|
2848
|
-
PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT
|
|
2936
|
+
PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT,
|
|
2937
|
+
{ forceRuntimeContextRefresh: true }
|
|
2849
2938
|
);
|
|
2850
|
-
if (
|
|
2939
|
+
if (this.isProtocolV2ApplicationMode(features)) {
|
|
2940
|
+
this.protocolV2LatestFinalDeviceInfo = deviceInfo;
|
|
2851
2941
|
return features;
|
|
2852
2942
|
}
|
|
2853
2943
|
lastError = ERRORS.TypedError(
|
|
@@ -2855,7 +2945,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2855
2945
|
'Protocol V2 device is still in bootloader mode'
|
|
2856
2946
|
);
|
|
2857
2947
|
} catch (error) {
|
|
2858
|
-
if (this.isProtocolV2ReconnectIdentityError(error)) {
|
|
2948
|
+
if (isBleStaleBondHardwareError(error) || this.isProtocolV2ReconnectIdentityError(error)) {
|
|
2859
2949
|
throw error;
|
|
2860
2950
|
}
|
|
2861
2951
|
shouldReconnect = true;
|
|
@@ -2873,19 +2963,53 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2873
2963
|
);
|
|
2874
2964
|
}
|
|
2875
2965
|
|
|
2876
|
-
private async reconnectProtocolV2Device() {
|
|
2966
|
+
private async reconnectProtocolV2Device(options?: { skipProtocolProbe?: boolean }) {
|
|
2877
2967
|
if (this.isBleReconnect()) {
|
|
2878
|
-
await this.acquireProtocolV2BleDevice();
|
|
2968
|
+
await this.acquireProtocolV2BleDevice(options?.skipProtocolProbe);
|
|
2879
2969
|
return;
|
|
2880
2970
|
}
|
|
2881
2971
|
|
|
2882
2972
|
const deviceDiff = await this.device.deviceConnector?.enumerate();
|
|
2883
2973
|
const devicesDescriptor = deviceDiff?.descriptors ?? [];
|
|
2974
|
+
const env = DataManager.getSettings('env');
|
|
2975
|
+
const isWebUsb = DataManager.isBrowserWebUsb(env) || DataManager.isDesktopWebUsb(env);
|
|
2884
2976
|
|
|
2885
|
-
if (
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
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) {
|
|
2889
3013
|
const descriptor = devicesDescriptor[0];
|
|
2890
3014
|
if (!this.protocolV2ExpectedSerialNumber && descriptor.path !== this.protocolV2ExpectedPath) {
|
|
2891
3015
|
throw ERRORS.TypedError(HardwareErrorCode.DeviceNotFound);
|
|
@@ -3003,13 +3127,24 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
3003
3127
|
await wait(2000);
|
|
3004
3128
|
}
|
|
3005
3129
|
|
|
3006
|
-
private async acquireProtocolV2BleDevice() {
|
|
3007
|
-
|
|
3008
|
-
|
|
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,
|
|
3009
3139
|
null,
|
|
3010
3140
|
true,
|
|
3011
|
-
PROTOCOL_V2_CONNECT_PROTOCOL
|
|
3141
|
+
PROTOCOL_V2_CONNECT_PROTOCOL,
|
|
3142
|
+
undefined,
|
|
3143
|
+
undefined,
|
|
3144
|
+
skipProtocolProbe
|
|
3012
3145
|
);
|
|
3146
|
+
this.device.commands.disposed = false;
|
|
3147
|
+
this.device.getCommands().mainId = expectedId;
|
|
3013
3148
|
}
|
|
3014
3149
|
|
|
3015
3150
|
private async protocolV2StartFirmwareUpdate({
|
|
@@ -3017,30 +3152,72 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
3017
3152
|
}: {
|
|
3018
3153
|
targets: Array<{ target_id: number; path: string }>;
|
|
3019
3154
|
}) {
|
|
3155
|
+
this.protocolV2LastRuntimeProbeFeatures = undefined;
|
|
3156
|
+
this.protocolV2InstallNeedsReconnect = false;
|
|
3157
|
+
this.protocolV2InstallTerminalSuccessObserved = false;
|
|
3020
3158
|
const commands = this.device.getCommands();
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
{
|
|
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
|
+
})
|
|
3026
3172
|
);
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
|
|
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
|
+
}
|
|
3032
3200
|
}
|
|
3033
3201
|
|
|
3034
3202
|
private async protocolV2Reboot(rebootType: DeviceRebootType) {
|
|
3035
3203
|
const typedCall = this.device.getCommands().typedCall.bind(this.device.getCommands());
|
|
3036
3204
|
try {
|
|
3037
|
-
const res = await typedCall(
|
|
3038
|
-
|
|
3039
|
-
|
|
3205
|
+
const res = await typedCall(
|
|
3206
|
+
'DeviceReboot',
|
|
3207
|
+
'Success',
|
|
3208
|
+
{
|
|
3209
|
+
reboot_type: rebootType,
|
|
3210
|
+
},
|
|
3211
|
+
{ timeoutMs: PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT }
|
|
3212
|
+
);
|
|
3040
3213
|
this.device.markProtocolV2Reboot(rebootType);
|
|
3041
3214
|
return res.message;
|
|
3042
3215
|
} catch (error) {
|
|
3043
|
-
if (
|
|
3216
|
+
if (
|
|
3217
|
+
isProtocolV2DeviceDisconnectedError(error) ||
|
|
3218
|
+
isProtocolV2ReconnectProbeError(error) ||
|
|
3219
|
+
isProtocolV2ResponseTimeout(error)
|
|
3220
|
+
) {
|
|
3044
3221
|
this.device.markProtocolV2Reboot(rebootType);
|
|
3045
3222
|
return { message: 'Device rebooted successfully' };
|
|
3046
3223
|
}
|