@onekeyfe/hd-core 1.2.0-alpha.99 → 1.2.1-alpha.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 +979 -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 +2199 -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 +20 -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 +2150 -1469
- 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 +934 -739
- 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,
|
|
@@ -381,11 +374,22 @@ const isProtocolV2FirmwareStatusEndpointUnavailable = (error: unknown) => {
|
|
|
381
374
|
);
|
|
382
375
|
};
|
|
383
376
|
|
|
377
|
+
const isProtocolV2InstallResponseTimeout = (error: unknown) => {
|
|
378
|
+
if (!error || typeof error !== 'object') return false;
|
|
379
|
+
const { code } = error as { code?: number | string };
|
|
380
|
+
return (
|
|
381
|
+
code === 'response-timeout' ||
|
|
382
|
+
/\bresponse timeout\b/i.test(getProtocolV2UnknownErrorText(error))
|
|
383
|
+
);
|
|
384
|
+
};
|
|
385
|
+
|
|
384
386
|
const isProtocolV2TerminalInstallStatusError = (error: unknown) =>
|
|
385
|
-
error
|
|
386
|
-
(error
|
|
387
|
-
error.errorCode === HardwareErrorCode.
|
|
388
|
-
|
|
387
|
+
isBleStaleBondHardwareError(error) ||
|
|
388
|
+
(error instanceof HardwareError &&
|
|
389
|
+
(error.errorCode === HardwareErrorCode.ActionCancelled ||
|
|
390
|
+
error.errorCode === HardwareErrorCode.FirmwareError ||
|
|
391
|
+
error.errorCode === HardwareErrorCode.FirmwareVerificationFailed ||
|
|
392
|
+
error.params?.firmwareUpdateCode === PROTOCOL_V2_INSTALL_STATUS_CONFLICT_CODE));
|
|
389
393
|
|
|
390
394
|
const isProtocolV2TargetStatusFinished = (status: ProtocolV2FirmwareUpdateStatusTarget['status']) =>
|
|
391
395
|
normalizeProtocolV2TargetStatus(status) === PROTOCOL_V2_TARGET_STATUS_FINISHED;
|
|
@@ -422,6 +426,18 @@ const normalizeProtocolV2TargetStatus = (
|
|
|
422
426
|
return undefined;
|
|
423
427
|
};
|
|
424
428
|
|
|
429
|
+
const PROTOCOL_V2_INSTALL_PHASE_BY_DECODED_VALUE = new Map<
|
|
430
|
+
number | string,
|
|
431
|
+
'prepare' | 'install' | 'verify'
|
|
432
|
+
>([
|
|
433
|
+
[0, 'prepare'],
|
|
434
|
+
[1, 'install'],
|
|
435
|
+
[2, 'verify'],
|
|
436
|
+
['FW_MGMT_UPDATER_PHASE_PREPARE', 'prepare'],
|
|
437
|
+
['FW_MGMT_UPDATER_PHASE_INSTALL', 'install'],
|
|
438
|
+
['FW_MGMT_UPDATER_PHASE_VERIFY', 'verify'],
|
|
439
|
+
]);
|
|
440
|
+
|
|
425
441
|
const normalizeProtocolV2Hex = (value?: string) => value?.replace(/^0x/i, '').toLowerCase();
|
|
426
442
|
|
|
427
443
|
const versionArrayToNumber = (version?: IVersionArray) => {
|
|
@@ -479,40 +495,9 @@ const toProtocolV2FiniteNumber = (value: unknown) => {
|
|
|
479
495
|
return undefined;
|
|
480
496
|
};
|
|
481
497
|
|
|
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
|
-
};
|
|
498
|
+
const normalizeProtocolV2ProgressPercent = (value: unknown) => {
|
|
499
|
+
const progress = toProtocolV2FiniteNumber(value);
|
|
500
|
+
return progress === undefined ? undefined : Math.min(100, Math.max(0, progress));
|
|
516
501
|
};
|
|
517
502
|
|
|
518
503
|
export const isProtocolV2FirmwareFingerprintValid = (
|
|
@@ -557,7 +542,7 @@ export const assertProtocolV2ReconnectIdentity = (
|
|
|
557
542
|
*
|
|
558
543
|
* It intentionally does not fall back to FirmwareUpdateV3/V1 behavior:
|
|
559
544
|
* - upload uses FilesystemFileWrite
|
|
560
|
-
* - install uses DeviceFirmwareUpdateRequest
|
|
545
|
+
* - install uses DeviceFirmwareUpdateStage followed by an empty DeviceFirmwareUpdateRequest
|
|
561
546
|
* - completion waits for target status to finish, reboots to normal, then polls DeviceInfo
|
|
562
547
|
*/
|
|
563
548
|
export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareUpdateV4Params> {
|
|
@@ -579,19 +564,56 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
579
564
|
|
|
580
565
|
private protocolV2CompletedTargetVersions = new Map<number, number>();
|
|
581
566
|
|
|
567
|
+
private protocolV2CompletedTargetIds = new Set<number>();
|
|
568
|
+
|
|
582
569
|
private protocolV2LatestFinalFeatures?: Features;
|
|
583
570
|
|
|
584
|
-
private
|
|
571
|
+
private protocolV2LatestFinalDeviceInfo?: ProtocolV2DeviceInfo;
|
|
572
|
+
|
|
573
|
+
private protocolV2InstallBaselineVersions = new Map<number, string>();
|
|
574
|
+
|
|
575
|
+
private protocolV2InstallNeedsReconnect = false;
|
|
576
|
+
|
|
577
|
+
private protocolV2InstallDisconnectObserved = false;
|
|
578
|
+
|
|
579
|
+
private protocolV2InstallTerminalSuccessObserved = false;
|
|
580
|
+
|
|
581
|
+
private protocolV2LastRuntimeProbeFeatures?: Features;
|
|
582
|
+
|
|
583
|
+
private protocolV2LastTransferProgress?: number;
|
|
584
|
+
|
|
585
|
+
private protocolV2LastTransferProgressAt = 0;
|
|
585
586
|
|
|
586
587
|
init() {
|
|
587
588
|
this.allowDeviceMode = [UI_REQUEST.BOOTLOADER, UI_REQUEST.NOT_INITIALIZE];
|
|
588
589
|
this.requireDeviceMode = [];
|
|
589
590
|
this.unlockPolicy = 'unlock-before-run';
|
|
591
|
+
// Protocol V2 device-management actions are not wallet-scoped, so either
|
|
592
|
+
// the main PIN or an Attach PIN may authorize them.
|
|
593
|
+
this.protocolV2PreUnlockPinType = DeviceSessionPinType.Any;
|
|
590
594
|
this.useDevicePassphraseState = false;
|
|
591
595
|
this.skipForceUpdateCheck = true;
|
|
592
596
|
|
|
593
597
|
const { payload } = this;
|
|
594
598
|
|
|
599
|
+
const removedResourceInputs = ['resourceFiles', 'resourceBundleArtifacts'].filter(input =>
|
|
600
|
+
Object.prototype.hasOwnProperty.call(payload, input)
|
|
601
|
+
);
|
|
602
|
+
if (removedResourceInputs.length > 0) {
|
|
603
|
+
throw ERRORS.TypedError(
|
|
604
|
+
HardwareErrorCode.CallMethodInvalidParameter,
|
|
605
|
+
`Protocol V2 ${removedResourceInputs.join(
|
|
606
|
+
' and '
|
|
607
|
+
)} inputs are no longer supported; use resourceArchiveBinary for local updates or preparedPlan with hostBindingGeneration for remote updates`
|
|
608
|
+
);
|
|
609
|
+
}
|
|
610
|
+
if (payload.hostBindingGeneration !== undefined && !payload.preparedPlan) {
|
|
611
|
+
throw ERRORS.TypedError(
|
|
612
|
+
HardwareErrorCode.CallMethodInvalidParameter,
|
|
613
|
+
'Protocol V2 hostBindingGeneration requires preparedPlan for remote updates; use direct binaries or resourceArchiveBinary for local updates'
|
|
614
|
+
);
|
|
615
|
+
}
|
|
616
|
+
|
|
595
617
|
this.protocolV2HasExplicitTargetSelection = payload.targetsToUpdate !== undefined;
|
|
596
618
|
|
|
597
619
|
if (typeof payload.retryCount !== 'number') {
|
|
@@ -661,10 +683,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
661
683
|
payload.hostBindingGeneration,
|
|
662
684
|
preparedPlan.preparedPlanDigest
|
|
663
685
|
).artifactReader;
|
|
664
|
-
} else if (payload.hostBindingGeneration !== undefined) {
|
|
665
|
-
artifactReader = resolveFirmwareUpdateHostBinding(
|
|
666
|
-
payload.hostBindingGeneration
|
|
667
|
-
).artifactReader;
|
|
668
686
|
}
|
|
669
687
|
if (preparedPlan) {
|
|
670
688
|
assertFirmwareUpdatePreparedPlanBinding({
|
|
@@ -787,6 +805,9 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
787
805
|
private async runProtocolV2() {
|
|
788
806
|
await this.captureProtocolV2PhysicalIdentity();
|
|
789
807
|
const deviceFeatures = await this.getProtocolV2DeviceFeatures();
|
|
808
|
+
this.protocolV2InstallBaselineVersions =
|
|
809
|
+
this.getProtocolV2ObservableTargetVersions(deviceFeatures);
|
|
810
|
+
this.protocolV2LastRuntimeProbeFeatures = undefined;
|
|
790
811
|
const currentDeviceType = this.device.getCurrentDeviceType();
|
|
791
812
|
const capabilityDeviceType =
|
|
792
813
|
currentDeviceType === EDeviceType.Pro2 || currentDeviceType === EDeviceType.Neo
|
|
@@ -800,21 +821,14 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
800
821
|
const deviceFirmwareType = getFirmwareType(deviceFeatures);
|
|
801
822
|
const firmwareType = this.params.firmwareType ?? deviceFirmwareType;
|
|
802
823
|
this.validateExpectedTargetVersions();
|
|
824
|
+
const wantsResources = !!this.params.targetsToUpdate?.includes('resource');
|
|
803
825
|
|
|
804
826
|
if (
|
|
805
827
|
!this.params.preparedPlan &&
|
|
806
828
|
this.params.resourceArchiveBinary &&
|
|
807
829
|
this.params.targetsToUpdate?.includes('resource')
|
|
808
830
|
) {
|
|
809
|
-
|
|
810
|
-
features: deviceFeatures,
|
|
811
|
-
firmwareType,
|
|
812
|
-
});
|
|
813
|
-
try {
|
|
814
|
-
return await this.runProtocolV2PreparedArtifacts(deviceFeatures, firmwareType);
|
|
815
|
-
} finally {
|
|
816
|
-
localMemoryHost.release();
|
|
817
|
-
}
|
|
831
|
+
return this.runProtocolV2DirectArtifacts({});
|
|
818
832
|
}
|
|
819
833
|
|
|
820
834
|
const hasPreparedComponentArtifacts = Object.values(this.params.componentArtifacts ?? {}).some(
|
|
@@ -823,16 +837,16 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
823
837
|
if (this.params.preparedPlan || hasPreparedComponentArtifacts) {
|
|
824
838
|
return this.runProtocolV2PreparedArtifacts(deviceFeatures, firmwareType);
|
|
825
839
|
}
|
|
826
|
-
const wantsResources = !!this.params.targetsToUpdate?.includes('resource');
|
|
827
840
|
|
|
828
841
|
let fwBinaryMap: ProtocolV2TargetBinary[] = [];
|
|
829
842
|
let bootloaderBinary: ArrayBuffer | null = null;
|
|
830
843
|
let installItems: ProtocolV2InstallItem[] | undefined;
|
|
844
|
+
let explicitInstallItems: ProtocolV2InstallItem[] | undefined;
|
|
831
845
|
try {
|
|
832
846
|
this.postTipMessage(FirmwareUpdateTipMessage.StartDownloadFirmware);
|
|
833
847
|
fwBinaryMap = this.collectExplicitTargetBinaries();
|
|
834
848
|
bootloaderBinary = this.prepareBootloaderBinary();
|
|
835
|
-
|
|
849
|
+
explicitInstallItems = this.buildProtocolV2InstallItems({
|
|
836
850
|
bootloaderBinary,
|
|
837
851
|
fwBinaryMap,
|
|
838
852
|
});
|
|
@@ -840,17 +854,9 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
840
854
|
const needsRemoteFirmware = this.params.targetsToUpdate?.length
|
|
841
855
|
? missingFirmwareTargets.length > 0
|
|
842
856
|
: 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
|
-
}
|
|
857
|
+
const needsSdkManagedArtifacts = needsRemoteFirmware || wantsResources;
|
|
852
858
|
if (
|
|
853
|
-
|
|
859
|
+
needsSdkManagedArtifacts &&
|
|
854
860
|
(this.params.artifactReader ||
|
|
855
861
|
DataManager.getSettings('firmwareManifestMode') === 'external-only')
|
|
856
862
|
) {
|
|
@@ -862,9 +868,13 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
862
868
|
}
|
|
863
869
|
);
|
|
864
870
|
}
|
|
865
|
-
if (
|
|
871
|
+
if (needsSdkManagedArtifacts) {
|
|
866
872
|
// Remote updates must use a freshly fetched config before any reboot or file write.
|
|
867
|
-
await DataManager.forceReloadData(
|
|
873
|
+
await DataManager.forceReloadData({
|
|
874
|
+
requireResources: wantsResources,
|
|
875
|
+
resourceDeviceType:
|
|
876
|
+
capabilityDeviceType === EDeviceType.Neo ? EDeviceType.Neo : EDeviceType.Pro2,
|
|
877
|
+
});
|
|
868
878
|
}
|
|
869
879
|
if (needsRemoteFirmware) {
|
|
870
880
|
const remoteBinaries = await this.prepareRemoteProtocolV2Binaries(
|
|
@@ -887,6 +897,12 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
887
897
|
targetId: item.targetId,
|
|
888
898
|
}));
|
|
889
899
|
}
|
|
900
|
+
if (wantsResources) {
|
|
901
|
+
this.params.resourceArchiveBinary = await this.downloadRemoteProtocolV2ResourceArchive(
|
|
902
|
+
deviceFeatures,
|
|
903
|
+
firmwareType
|
|
904
|
+
);
|
|
905
|
+
}
|
|
890
906
|
this.postTipMessage(FirmwareUpdateTipMessage.FinishDownloadFirmware);
|
|
891
907
|
} catch (err) {
|
|
892
908
|
if (
|
|
@@ -903,6 +919,13 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
903
919
|
throw normalizeFirmwarePreparationError(err);
|
|
904
920
|
}
|
|
905
921
|
|
|
922
|
+
if (wantsResources && this.params.resourceArchiveBinary) {
|
|
923
|
+
return this.runProtocolV2DirectArtifacts({
|
|
924
|
+
availableInstallItems: installItems ?? explicitInstallItems,
|
|
925
|
+
announceDownload: false,
|
|
926
|
+
});
|
|
927
|
+
}
|
|
928
|
+
|
|
906
929
|
if (!bootloaderBinary && fwBinaryMap.length === 0 && !installItems?.length) {
|
|
907
930
|
throw ERRORS.TypedError(
|
|
908
931
|
HardwareErrorCode.FirmwareUpdateDownloadFailed,
|
|
@@ -1053,132 +1076,69 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1053
1076
|
return installSources;
|
|
1054
1077
|
}
|
|
1055
1078
|
|
|
1056
|
-
private async
|
|
1057
|
-
|
|
1058
|
-
|
|
1079
|
+
private async runProtocolV2DirectArtifacts({
|
|
1080
|
+
availableInstallItems,
|
|
1081
|
+
announceDownload = true,
|
|
1059
1082
|
}: {
|
|
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
|
-
}
|
|
1083
|
+
availableInstallItems?: ProtocolV2InstallItem[];
|
|
1084
|
+
announceDownload?: boolean;
|
|
1085
|
+
}) {
|
|
1086
|
+
try {
|
|
1087
|
+
if (announceDownload) {
|
|
1088
|
+
this.postTipMessage(FirmwareUpdateTipMessage.StartDownloadFirmware);
|
|
1089
|
+
}
|
|
1090
|
+
const installItems = this.filterProtocolV2LocalInstallItems(
|
|
1091
|
+
availableInstallItems ??
|
|
1092
|
+
this.buildProtocolV2InstallItems({
|
|
1093
|
+
bootloaderBinary: this.prepareBootloaderBinary(),
|
|
1094
|
+
fwBinaryMap: this.collectExplicitTargetBinaries(),
|
|
1095
|
+
})
|
|
1090
1096
|
);
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1097
|
+
const installSources = await Promise.all(
|
|
1098
|
+
installItems.map(async item => ({
|
|
1099
|
+
fileName: item.fileName,
|
|
1100
|
+
source: await this.openProtocolV2MemorySource(item.binary),
|
|
1101
|
+
targetId: item.targetId,
|
|
1102
|
+
kind: item.kind,
|
|
1103
|
+
}))
|
|
1104
|
+
);
|
|
1105
|
+
const resourceSources = this.params.resourceArchiveBinary
|
|
1106
|
+
? await this.createProtocolV2ResourceSourcesFromArchive(this.params.resourceArchiveBinary)
|
|
1107
|
+
: [];
|
|
1108
|
+
if (installSources.length === 0 && resourceSources.length === 0) {
|
|
1100
1109
|
throw ERRORS.TypedError(
|
|
1101
|
-
HardwareErrorCode.
|
|
1102
|
-
|
|
1103
|
-
{ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
|
|
1110
|
+
HardwareErrorCode.FirmwareUpdateDownloadFailed,
|
|
1111
|
+
'No firmware to update'
|
|
1104
1112
|
);
|
|
1105
1113
|
}
|
|
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
|
-
: {}),
|
|
1114
|
+
if (announceDownload) {
|
|
1115
|
+
this.postTipMessage(FirmwareUpdateTipMessage.FinishDownloadFirmware);
|
|
1116
|
+
}
|
|
1117
|
+
return await this.executeProtocolV2SourceUpdate({
|
|
1118
|
+
installSources,
|
|
1119
|
+
resourceSources,
|
|
1117
1120
|
});
|
|
1118
|
-
|
|
1121
|
+
} finally {
|
|
1122
|
+
await this.closeProtocolV2PreparedSources();
|
|
1119
1123
|
}
|
|
1124
|
+
}
|
|
1120
1125
|
|
|
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),
|
|
1126
|
+
private async createProtocolV2ResourceSourcesFromArchive(
|
|
1127
|
+
binary: ArrayBuffer
|
|
1128
|
+
): Promise<ProtocolV2ResourceBundleSource[]> {
|
|
1129
|
+
const archive = await this.prepareProtocolV2LocalResourceArchive(binary);
|
|
1130
|
+
const sources: ProtocolV2ResourceBundleSource[] = [];
|
|
1131
|
+
for (const resource of archive.resources) {
|
|
1132
|
+
sources.push({
|
|
1133
|
+
name: resource.entryName.split('/').pop() ?? resource.entryName,
|
|
1134
|
+
source: await this.openProtocolV2MemorySource(resource.binary),
|
|
1135
|
+
devicePath: resource.header.devicePath,
|
|
1136
|
+
version: resource.header.version,
|
|
1137
|
+
payloadHash: resource.header.payloadHash,
|
|
1138
|
+
headerHash: resource.header.headerHash,
|
|
1168
1139
|
});
|
|
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
1140
|
}
|
|
1141
|
+
return sources;
|
|
1182
1142
|
}
|
|
1183
1143
|
|
|
1184
1144
|
private async prepareProtocolV2LocalResourceArchive(
|
|
@@ -1202,130 +1162,67 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1202
1162
|
);
|
|
1203
1163
|
}
|
|
1204
1164
|
const zipEntries = Object.values(zip.files);
|
|
1165
|
+
const resourceEntries = zipEntries.filter(
|
|
1166
|
+
entry => !entry.dir && isProtocolV2ResourceArchiveEntryName(entry.name)
|
|
1167
|
+
);
|
|
1205
1168
|
if (
|
|
1206
|
-
|
|
1169
|
+
resourceEntries.length === 0 ||
|
|
1170
|
+
resourceEntries.length > PROTOCOL_V2_RESOURCE_FILE_MAX_COUNT
|
|
1207
1171
|
) {
|
|
1208
1172
|
throw ERRORS.TypedError(
|
|
1209
1173
|
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',
|
|
1174
|
+
'Protocol V2 resource ZIP has no valid resource package set',
|
|
1219
1175
|
{ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
|
|
1220
1176
|
);
|
|
1221
1177
|
}
|
|
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;
|
|
1178
|
+
|
|
1179
|
+
let totalSize = 0;
|
|
1180
|
+
const resources: ProtocolV2LocalResourceArchive['resources'] = [];
|
|
1181
|
+
const devicePaths = new Set<string>();
|
|
1182
|
+
for (const entry of resourceEntries) {
|
|
1183
|
+
const { compressedSize, uncompressedSize } = getProtocolV2ZipEntrySizes(entry);
|
|
1184
|
+
totalSize += uncompressedSize;
|
|
1232
1185
|
if (
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
PROTOCOL_V2_RESOURCE_TOTAL_MAX_BYTES + PROTOCOL_V2_RESOURCE_MANIFEST_MAX_BYTES
|
|
1186
|
+
compressedSize > binary.byteLength ||
|
|
1187
|
+
uncompressedSize > PROTOCOL_V2_RESOURCE_TOTAL_MAX_BYTES ||
|
|
1188
|
+
totalSize > PROTOCOL_V2_RESOURCE_TOTAL_MAX_BYTES
|
|
1237
1189
|
) {
|
|
1238
1190
|
throw ERRORS.TypedError(
|
|
1239
1191
|
HardwareErrorCode.RuntimeError,
|
|
1240
|
-
|
|
1192
|
+
`Protocol V2 resource package size is invalid: ${entry.name}`,
|
|
1241
1193
|
{ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
|
|
1242
1194
|
);
|
|
1243
1195
|
}
|
|
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) {
|
|
1196
|
+
const fileBinary = await entry.async('arraybuffer');
|
|
1197
|
+
if (fileBinary.byteLength !== uncompressedSize) {
|
|
1302
1198
|
throw ERRORS.TypedError(
|
|
1303
1199
|
HardwareErrorCode.RuntimeError,
|
|
1304
|
-
`Protocol V2
|
|
1305
|
-
{ firmwareUpdateCode: '
|
|
1200
|
+
`Protocol V2 resource package is incomplete: ${entry.name}`,
|
|
1201
|
+
{ firmwareUpdateCode: 'FirmwareArtifactReceiptMismatch' }
|
|
1306
1202
|
);
|
|
1307
1203
|
}
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1204
|
+
let header: ProtocolV2ResourcePackageHeader;
|
|
1205
|
+
try {
|
|
1206
|
+
header = parseProtocolV2ResourcePackage(fileBinary);
|
|
1207
|
+
} catch (error) {
|
|
1311
1208
|
throw ERRORS.TypedError(
|
|
1312
1209
|
HardwareErrorCode.RuntimeError,
|
|
1313
|
-
`Protocol V2
|
|
1210
|
+
`Protocol V2 resource package is invalid: ${entry.name}: ${String(error)}`,
|
|
1314
1211
|
{ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
|
|
1315
1212
|
);
|
|
1316
1213
|
}
|
|
1317
|
-
const
|
|
1318
|
-
|
|
1319
|
-
if (fileBinary.byteLength !== file.size || digest !== file.sha256.toLowerCase()) {
|
|
1214
|
+
const canonicalDevicePath = header.devicePath.toLowerCase();
|
|
1215
|
+
if (devicePaths.has(canonicalDevicePath)) {
|
|
1320
1216
|
throw ERRORS.TypedError(
|
|
1321
1217
|
HardwareErrorCode.RuntimeError,
|
|
1322
|
-
`Protocol V2
|
|
1323
|
-
{ firmwareUpdateCode: '
|
|
1218
|
+
`Protocol V2 resource ZIP contains duplicate device path: ${header.devicePath}`,
|
|
1219
|
+
{ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
|
|
1324
1220
|
);
|
|
1325
1221
|
}
|
|
1326
|
-
|
|
1222
|
+
devicePaths.add(canonicalDevicePath);
|
|
1223
|
+
resources.push({ entryName: entry.name, binary: fileBinary, header });
|
|
1327
1224
|
}
|
|
1328
|
-
return { binary,
|
|
1225
|
+
return { binary, resources };
|
|
1329
1226
|
}
|
|
1330
1227
|
|
|
1331
1228
|
private async prepareProtocolV2ResourceSources(): Promise<ProtocolV2ResourceBundleSource[]> {
|
|
@@ -1390,110 +1287,20 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1390
1287
|
);
|
|
1391
1288
|
}
|
|
1392
1289
|
|
|
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;
|
|
1290
|
+
// The verified ZIP bytes are authoritative. Non-okpkg entries are release
|
|
1291
|
+
// metadata only; each RESOURCE package carries its own device path.
|
|
1292
|
+
return this.createProtocolV2ResourceSourcesFromArchive(archiveBinary);
|
|
1492
1293
|
}
|
|
1493
1294
|
|
|
1494
|
-
private async runProtocolV2PreparedArtifacts(
|
|
1295
|
+
private async runProtocolV2PreparedArtifacts(
|
|
1296
|
+
features: Features,
|
|
1297
|
+
firmwareType: EFirmwareType,
|
|
1298
|
+
announceDownload = true
|
|
1299
|
+
) {
|
|
1495
1300
|
try {
|
|
1496
|
-
|
|
1301
|
+
if (announceDownload) {
|
|
1302
|
+
this.postTipMessage(FirmwareUpdateTipMessage.StartDownloadFirmware);
|
|
1303
|
+
}
|
|
1497
1304
|
const installSources = await this.prepareProtocolV2InstallSources(firmwareType, features);
|
|
1498
1305
|
const resourceSources = await this.prepareProtocolV2ResourceSources();
|
|
1499
1306
|
if (installSources.length === 0 && resourceSources.length === 0) {
|
|
@@ -1502,7 +1309,9 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1502
1309
|
'No firmware to update'
|
|
1503
1310
|
);
|
|
1504
1311
|
}
|
|
1505
|
-
|
|
1312
|
+
if (announceDownload) {
|
|
1313
|
+
this.postTipMessage(FirmwareUpdateTipMessage.FinishDownloadFirmware);
|
|
1314
|
+
}
|
|
1506
1315
|
|
|
1507
1316
|
return await this.executeProtocolV2SourceUpdate({
|
|
1508
1317
|
installSources,
|
|
@@ -1557,21 +1366,20 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1557
1366
|
const expected = this.params?.expectedTargetVersions;
|
|
1558
1367
|
if (!expected) return;
|
|
1559
1368
|
const features = this.protocolV2LatestFinalFeatures;
|
|
1560
|
-
const
|
|
1561
|
-
const applicationTargets = requestedTargets.filter(
|
|
1562
|
-
target => target === 'app_v1' || target === 'app_v2'
|
|
1563
|
-
);
|
|
1369
|
+
const deviceInfo = this.protocolV2LatestFinalDeviceInfo;
|
|
1564
1370
|
const visibleVersions: Partial<Record<FirmwareUpdateV4Target, string>> = {
|
|
1565
1371
|
boot: features ? getDeviceBootloaderVersion(features).join('.') : undefined,
|
|
1372
|
+
// Protocol V2 firmware exposes P1 as the device firmware version after reboot.
|
|
1373
|
+
app_v1: features
|
|
1374
|
+
? getDeviceFirmwareVersion(features).join('.')
|
|
1375
|
+
: deviceInfo?.main_mcu?.application?.version,
|
|
1376
|
+
app_v2: deviceInfo?.main_mcu?.application_data?.version,
|
|
1566
1377
|
coprocessor: features ? getDeviceBLEFirmwareVersion(features).join('.') : undefined,
|
|
1567
1378
|
se01: features?.se01Version ?? undefined,
|
|
1568
1379
|
se02: features?.se02Version ?? undefined,
|
|
1569
1380
|
se03: features?.se03Version ?? undefined,
|
|
1570
1381
|
se04: features?.se04Version ?? undefined,
|
|
1571
1382
|
};
|
|
1572
|
-
if (features && applicationTargets.length === 1) {
|
|
1573
|
-
visibleVersions[applicationTargets[0]] = getDeviceFirmwareVersion(features).join('.');
|
|
1574
|
-
}
|
|
1575
1383
|
const expectedEntries = (
|
|
1576
1384
|
Object.entries(expected) as Array<[FirmwareUpdateV4Target, string]>
|
|
1577
1385
|
).filter(([target]) => !targets || targets.includes(target));
|
|
@@ -1588,9 +1396,11 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1588
1396
|
Math.floor(statusVersion / 0x100) % 0x100
|
|
1589
1397
|
}.${statusVersion % 0x100}`;
|
|
1590
1398
|
if (!observedVersion) {
|
|
1591
|
-
|
|
1399
|
+
const hasCompleteTargetEvidence =
|
|
1400
|
+
targetId !== undefined && this.protocolV2CompletedTargetIds.has(targetId);
|
|
1401
|
+
if (hasCompleteTargetEvidence) {
|
|
1592
1402
|
Log.warn(
|
|
1593
|
-
`Protocol V2 target ${target} has no observable final version;
|
|
1403
|
+
`Protocol V2 target ${target} has no observable final version; install completion is authoritative`
|
|
1594
1404
|
);
|
|
1595
1405
|
} else {
|
|
1596
1406
|
throw ERRORS.TypedError(
|
|
@@ -1607,37 +1417,23 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1607
1417
|
}
|
|
1608
1418
|
}
|
|
1609
1419
|
|
|
1610
|
-
private
|
|
1611
|
-
|
|
1612
|
-
|
|
1420
|
+
private async getProtocolV2DeviceFeatures(): Promise<Features> {
|
|
1421
|
+
let { features } = this.device;
|
|
1422
|
+
if (typeof this.device.getFeatures === 'function') {
|
|
1423
|
+
features ??= await this.device.getFeatures();
|
|
1613
1424
|
}
|
|
1614
|
-
if (
|
|
1615
|
-
|
|
1425
|
+
if (!features) {
|
|
1426
|
+
throw ERRORS.TypedError(HardwareErrorCode.RuntimeError, 'Device features not available');
|
|
1616
1427
|
}
|
|
1617
|
-
const
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
);
|
|
1621
|
-
if (this.params.bootloaderBinary) targets.add('boot');
|
|
1622
|
-
if (this.params.applicationP1Binary) targets.add('app_v1');
|
|
1623
|
-
if (this.params.applicationP2Binary) targets.add('app_v2');
|
|
1624
|
-
if (this.params.coprocessorBinary) targets.add('coprocessor');
|
|
1625
|
-
if (this.params.se01Binary) targets.add('se01');
|
|
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;
|
|
1428
|
+
const deviceType = this.device.getCurrentDeviceType();
|
|
1429
|
+
if (deviceType !== EDeviceType.Pro2 && deviceType !== EDeviceType.Neo) {
|
|
1430
|
+
return features;
|
|
1639
1431
|
}
|
|
1640
|
-
|
|
1432
|
+
return {
|
|
1433
|
+
...features,
|
|
1434
|
+
deviceType,
|
|
1435
|
+
firmwareType: this.device.getCurrentFirmwareType(),
|
|
1436
|
+
};
|
|
1641
1437
|
}
|
|
1642
1438
|
|
|
1643
1439
|
private getProtocolV2SerialNumber(deviceInfo: ProtocolV2DeviceInfo) {
|
|
@@ -1840,22 +1636,54 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1840
1636
|
`Protocol V2 firmware fingerprint mismatch: ${key}/${component.target}`
|
|
1841
1637
|
);
|
|
1842
1638
|
}
|
|
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
1639
|
return {
|
|
1854
1640
|
...target,
|
|
1855
1641
|
binary,
|
|
1856
1642
|
};
|
|
1857
1643
|
}
|
|
1858
1644
|
|
|
1645
|
+
private async downloadRemoteProtocolV2ResourceArchive(
|
|
1646
|
+
features: Features,
|
|
1647
|
+
firmwareType: EFirmwareType
|
|
1648
|
+
): Promise<ArrayBuffer> {
|
|
1649
|
+
const deviceType = getDeviceType(features);
|
|
1650
|
+
if (deviceType !== EDeviceType.Pro2 && deviceType !== EDeviceType.Neo) {
|
|
1651
|
+
throw ERRORS.TypedError(
|
|
1652
|
+
HardwareErrorCode.RuntimeError,
|
|
1653
|
+
'Protocol V2 resource archive requires a Pro2 or Neo device'
|
|
1654
|
+
);
|
|
1655
|
+
}
|
|
1656
|
+
const release = DataManager.getFirmwareLatestRelease(features, firmwareType);
|
|
1657
|
+
const source = DataManager.getProtocolV2ResourceSource(release);
|
|
1658
|
+
const expectedSha256 = normalizeProtocolV2Hex(source?.archiveSha256);
|
|
1659
|
+
if (
|
|
1660
|
+
!source?.archiveUrl ||
|
|
1661
|
+
!Number.isSafeInteger(source.archiveSize) ||
|
|
1662
|
+
source.archiveSize <= 0 ||
|
|
1663
|
+
source.archiveSize > PROTOCOL_V2_RESOURCE_TOTAL_MAX_BYTES ||
|
|
1664
|
+
!expectedSha256 ||
|
|
1665
|
+
!/^[0-9a-f]{64}$/u.test(expectedSha256)
|
|
1666
|
+
) {
|
|
1667
|
+
throw ERRORS.TypedError(
|
|
1668
|
+
HardwareErrorCode.RuntimeError,
|
|
1669
|
+
'Protocol V2 resource archive integrity metadata is invalid',
|
|
1670
|
+
{ firmwareUpdateCode: 'FirmwarePlanInvalid' }
|
|
1671
|
+
);
|
|
1672
|
+
}
|
|
1673
|
+
const { binary } = await getSysResourceBinary(source.archiveUrl);
|
|
1674
|
+
if (
|
|
1675
|
+
binary.byteLength !== source.archiveSize ||
|
|
1676
|
+
bytesToHex(sha256(new Uint8Array(binary))) !== expectedSha256
|
|
1677
|
+
) {
|
|
1678
|
+
throw ERRORS.TypedError(
|
|
1679
|
+
HardwareErrorCode.RuntimeError,
|
|
1680
|
+
'Protocol V2 resource archive does not match the remote config',
|
|
1681
|
+
{ firmwareUpdateCode: 'FirmwareArtifactReceiptMismatch' }
|
|
1682
|
+
);
|
|
1683
|
+
}
|
|
1684
|
+
return binary;
|
|
1685
|
+
}
|
|
1686
|
+
|
|
1859
1687
|
private async prepareRemoteProtocolV2Binaries(
|
|
1860
1688
|
firmwareType: EFirmwareType,
|
|
1861
1689
|
features: Features,
|
|
@@ -1899,8 +1727,14 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1899
1727
|
: undefined;
|
|
1900
1728
|
if (updateTarget && targetsToUpdate.has(updateTarget)) {
|
|
1901
1729
|
const explicitInstallItem = explicitInstallItemByTargetId.get(target.targetId);
|
|
1902
|
-
|
|
1903
|
-
|
|
1730
|
+
let installItem = explicitInstallItem;
|
|
1731
|
+
if (!installItem) {
|
|
1732
|
+
installItem = await this.downloadRemoteProtocolV2Component(key, component);
|
|
1733
|
+
if (component.version) {
|
|
1734
|
+
this.params.expectedTargetVersions ??= {};
|
|
1735
|
+
this.params.expectedTargetVersions[updateTarget] = component.version.join('.');
|
|
1736
|
+
}
|
|
1737
|
+
}
|
|
1904
1738
|
if (installItem.kind === 'bootloader') {
|
|
1905
1739
|
bootloaderBinary = installItem.binary;
|
|
1906
1740
|
} else {
|
|
@@ -1939,6 +1773,12 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1939
1773
|
return `vol0:/${path}`;
|
|
1940
1774
|
}
|
|
1941
1775
|
|
|
1776
|
+
private getProtocolV2ResourceComparePath(devicePath: string) {
|
|
1777
|
+
return isProtocolV2BootResourcePackagePath(devicePath)
|
|
1778
|
+
? PROTOCOL_V2_BOOT_RESOURCE_PACKAGE_PATH
|
|
1779
|
+
: devicePath;
|
|
1780
|
+
}
|
|
1781
|
+
|
|
1942
1782
|
private async readProtocolV2DeviceFileHeader(path: string, expectedSize?: number) {
|
|
1943
1783
|
const typedCall = this.device.getCommands().typedCall.bind(this.device.getCommands());
|
|
1944
1784
|
const filePath = this.getProtocolV2ResourceFilePath(path);
|
|
@@ -1950,7 +1790,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1950
1790
|
!pathInfoRes.message?.exist ||
|
|
1951
1791
|
pathInfoRes.message?.directory ||
|
|
1952
1792
|
fileSize === undefined ||
|
|
1953
|
-
fileSize <
|
|
1793
|
+
fileSize < PROTOCOL_V2_RESOURCE_PACKAGE_HEADER_SIZE ||
|
|
1954
1794
|
(expectedSize !== undefined && fileSize !== expectedSize)
|
|
1955
1795
|
) {
|
|
1956
1796
|
return null;
|
|
@@ -1959,8 +1799,8 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1959
1799
|
const chunkSize = this.getProtocolV2FirmwareChunkSize('read');
|
|
1960
1800
|
const chunks: Uint8Array[] = [];
|
|
1961
1801
|
let offset = 0;
|
|
1962
|
-
while (offset <
|
|
1963
|
-
const readLen = Math.min(chunkSize,
|
|
1802
|
+
while (offset < PROTOCOL_V2_RESOURCE_PACKAGE_HEADER_SIZE) {
|
|
1803
|
+
const readLen = Math.min(chunkSize, PROTOCOL_V2_RESOURCE_PACKAGE_HEADER_SIZE - offset);
|
|
1964
1804
|
const res = await typedCall('FilesystemFileRead', 'FilesystemFile', {
|
|
1965
1805
|
file: {
|
|
1966
1806
|
path: filePath,
|
|
@@ -1982,7 +1822,11 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1982
1822
|
headerBytes.set(chunk, cursor);
|
|
1983
1823
|
cursor += chunk.byteLength;
|
|
1984
1824
|
});
|
|
1985
|
-
|
|
1825
|
+
try {
|
|
1826
|
+
return parseProtocolV2ResourcePackageHeader(headerBytes, fileSize);
|
|
1827
|
+
} catch {
|
|
1828
|
+
return null;
|
|
1829
|
+
}
|
|
1986
1830
|
}
|
|
1987
1831
|
|
|
1988
1832
|
/** Compare the downloaded and installed okpkg headers before transferring a resource. */
|
|
@@ -1997,7 +1841,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1997
1841
|
|
|
1998
1842
|
try {
|
|
1999
1843
|
const header = await this.readProtocolV2DeviceFileHeader(
|
|
2000
|
-
bundle.devicePath,
|
|
1844
|
+
this.getProtocolV2ResourceComparePath(bundle.devicePath),
|
|
2001
1845
|
bundle.source.size
|
|
2002
1846
|
);
|
|
2003
1847
|
if (!header) return false;
|
|
@@ -2012,6 +1856,9 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2012
1856
|
if (!expectedHeaderHash || header.headerHash !== expectedHeaderHash) return false;
|
|
2013
1857
|
return true;
|
|
2014
1858
|
} catch (error) {
|
|
1859
|
+
if (isBleStaleBondHardwareError(error)) {
|
|
1860
|
+
throw error;
|
|
1861
|
+
}
|
|
2015
1862
|
Log.log(`[FirmwareUpdateV4] RESC bundle ${bundle.name} header check failed: `, error);
|
|
2016
1863
|
return false;
|
|
2017
1864
|
}
|
|
@@ -2041,21 +1888,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2041
1888
|
return this.device.features?.mode === 'romloader';
|
|
2042
1889
|
}
|
|
2043
1890
|
|
|
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
|
-
|
|
1891
|
+
private async rebootProtocolV2ToBootloader() {
|
|
2059
1892
|
try {
|
|
2060
1893
|
this.postTipMessage(FirmwareUpdateTipMessage.AutoRebootToBootloader);
|
|
2061
1894
|
await this.protocolV2Reboot(DeviceRebootType.Bootloader);
|
|
@@ -2073,6 +1906,24 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2073
1906
|
}
|
|
2074
1907
|
}
|
|
2075
1908
|
|
|
1909
|
+
async enterProtocolV2BootloaderMode() {
|
|
1910
|
+
// romloader is the first update environment and forwards targets to bootloader.
|
|
1911
|
+
// It rejects DeviceRebootType.Bootloader, so reuse the current connection.
|
|
1912
|
+
if (this.isProtocolV2RomloaderMode()) {
|
|
1913
|
+
Log.debug('Protocol V2 device is in romloader mode; start firmware update directly');
|
|
1914
|
+
this.protocolV2ExecutionInLoader = true;
|
|
1915
|
+
return false;
|
|
1916
|
+
}
|
|
1917
|
+
if (this.isProtocolV2BootloaderMode()) {
|
|
1918
|
+
Log.debug('Protocol V2 device is already in bootloader mode, skip reboot');
|
|
1919
|
+
this.protocolV2ExecutionInLoader = true;
|
|
1920
|
+
this.postTipMessage(FirmwareUpdateTipMessage.GoToBootloaderSuccess);
|
|
1921
|
+
return false;
|
|
1922
|
+
}
|
|
1923
|
+
|
|
1924
|
+
return this.rebootProtocolV2ToBootloader();
|
|
1925
|
+
}
|
|
1926
|
+
|
|
2076
1927
|
private async waitForProtocolV2BootloaderMode(
|
|
2077
1928
|
timeout = PROTOCOL_V2_BOOTLOADER_RECONNECT_TIMEOUT,
|
|
2078
1929
|
retryInterval = 1000
|
|
@@ -2101,7 +1952,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2101
1952
|
}
|
|
2102
1953
|
lastError = new Error('Protocol V2 device is reachable but is not in bootloader mode');
|
|
2103
1954
|
} catch (error) {
|
|
2104
|
-
if (this.isProtocolV2ReconnectIdentityError(error)) {
|
|
1955
|
+
if (isBleStaleBondHardwareError(error) || this.isProtocolV2ReconnectIdentityError(error)) {
|
|
2105
1956
|
throw error;
|
|
2106
1957
|
}
|
|
2107
1958
|
shouldReconnect = true;
|
|
@@ -2157,68 +2008,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2157
2008
|
return entries;
|
|
2158
2009
|
}
|
|
2159
2010
|
|
|
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({
|
|
2011
|
+
private async executeProtocolV2SourceUpdate({
|
|
2222
2012
|
installSources,
|
|
2223
2013
|
resourceSources,
|
|
2224
2014
|
}: {
|
|
@@ -2226,38 +2016,22 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2226
2016
|
resourceSources: ProtocolV2ResourceBundleSource[];
|
|
2227
2017
|
}) {
|
|
2228
2018
|
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
|
-
}
|
|
2019
|
+
if (installSources.length > 0 || resourceSources.length > 0) {
|
|
2020
|
+
await this.enterProtocolV2BootloaderMode();
|
|
2021
|
+
// Clear stale boot-resource staging before writing any artifacts. The new
|
|
2022
|
+
// boot resource, resources and firmware then share one transfer session,
|
|
2023
|
+
// one global progress range and one multi-target install request.
|
|
2024
|
+
// Preserve the approved source order instead of synthesizing a boot-first
|
|
2025
|
+
// phase. Loader firmware owns the cross-stage handoff: bootloader runs its
|
|
2026
|
+
// component targets and leaves boot pending for romloader, while pending
|
|
2027
|
+
// runner-backed records always resume before the application boots.
|
|
2028
|
+
await this.ensureProtocolV2BootResourceStagingIsEmpty();
|
|
2029
|
+
await this.executeProtocolV2TransferPhase({
|
|
2030
|
+
installSources,
|
|
2031
|
+
resourceSources,
|
|
2032
|
+
});
|
|
2256
2033
|
}
|
|
2257
|
-
|
|
2258
|
-
HardwareErrorCode.RuntimeError,
|
|
2259
|
-
'Protocol V2 execution has no final verification phase'
|
|
2260
|
-
);
|
|
2034
|
+
return this.completeProtocolV2FinalVerification();
|
|
2261
2035
|
}
|
|
2262
2036
|
|
|
2263
2037
|
private async ensureProtocolV2BootResourceStagingIsEmpty() {
|
|
@@ -2289,19 +2063,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2289
2063
|
this.protocolV2BootResourceStagingSafe = true;
|
|
2290
2064
|
}
|
|
2291
2065
|
|
|
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
2066
|
private async executeProtocolV2Update({
|
|
2306
2067
|
fwBinaryMap,
|
|
2307
2068
|
bootloaderBinary,
|
|
@@ -2326,7 +2087,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2326
2087
|
kind: item.kind,
|
|
2327
2088
|
}))
|
|
2328
2089
|
);
|
|
2329
|
-
return await this.
|
|
2090
|
+
return await this.executeProtocolV2SourceUpdate({
|
|
2330
2091
|
installSources,
|
|
2331
2092
|
resourceSources: [],
|
|
2332
2093
|
});
|
|
@@ -2338,15 +2099,11 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2338
2099
|
private async executeProtocolV2TransferPhase({
|
|
2339
2100
|
installSources,
|
|
2340
2101
|
resourceSources,
|
|
2341
|
-
}:
|
|
2102
|
+
}: ProtocolV2TransferBatch) {
|
|
2342
2103
|
let totalSize = installSources.reduce((total, item) => total + item.source.size, 0);
|
|
2343
2104
|
const resourcesToSync: ProtocolV2ResourceBundleSource[] = [];
|
|
2344
2105
|
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))) {
|
|
2106
|
+
if (await this.isProtocolV2ResourceBundleUpToDate(resource)) {
|
|
2350
2107
|
Log.log(`[FirmwareUpdateV4] skip RESC bundle ${resource.name}; already up to date`);
|
|
2351
2108
|
} else {
|
|
2352
2109
|
resourcesToSync.push(resource);
|
|
@@ -2355,11 +2112,13 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2355
2112
|
}
|
|
2356
2113
|
|
|
2357
2114
|
this.postTipMessage(FirmwareUpdateTipMessage.StartTransferData);
|
|
2115
|
+
this.protocolV2LastTransferProgress = undefined;
|
|
2116
|
+
this.protocolV2LastTransferProgressAt = 0;
|
|
2358
2117
|
let processedSize = 0;
|
|
2359
2118
|
for (const resource of resourcesToSync) {
|
|
2360
2119
|
// The bootloader keeps its live resource package mounted. FatFs rejects
|
|
2361
2120
|
// replacing an open file, so early boot promotes this staging file before mounting it.
|
|
2362
|
-
const writePath =
|
|
2121
|
+
const writePath = resource.devicePath;
|
|
2363
2122
|
processedSize = await this.protocolV2SourceUpdateProcess({
|
|
2364
2123
|
source: resource.source,
|
|
2365
2124
|
filePath: writePath,
|
|
@@ -2400,8 +2159,13 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2400
2159
|
target_id: item.targetId,
|
|
2401
2160
|
path: item.path,
|
|
2402
2161
|
}));
|
|
2403
|
-
|
|
2404
|
-
|
|
2162
|
+
try {
|
|
2163
|
+
await this.protocolV2StartFirmwareUpdate({ targets });
|
|
2164
|
+
await wait(PROTOCOL_V2_INSTALL_STATUS_INITIAL_DELAY);
|
|
2165
|
+
await this.waitForProtocolV2FirmwareUpdateComplete(targets, true);
|
|
2166
|
+
} finally {
|
|
2167
|
+
this.device?.clearCancelableAction();
|
|
2168
|
+
}
|
|
2405
2169
|
}
|
|
2406
2170
|
|
|
2407
2171
|
private async protocolV2SourceUpdateProcess({
|
|
@@ -2446,23 +2210,33 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2446
2210
|
);
|
|
2447
2211
|
}
|
|
2448
2212
|
const transferredBytes = processedSize + chunkEnd;
|
|
2449
|
-
const
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2213
|
+
const now = Date.now();
|
|
2214
|
+
const elapsedMs = Math.max(now - transferStartedAt, 0);
|
|
2215
|
+
const progress = Math.min(Math.ceil((transferredBytes / totalSize) * 100), 99);
|
|
2216
|
+
const shouldPostProgress =
|
|
2217
|
+
progress !== this.protocolV2LastTransferProgress ||
|
|
2218
|
+
now - this.protocolV2LastTransferProgressAt >=
|
|
2219
|
+
PROTOCOL_V2_TRANSFER_PROGRESS_HEARTBEAT_MS ||
|
|
2220
|
+
chunkEnd === source.size;
|
|
2221
|
+
if (shouldPostProgress) {
|
|
2222
|
+
this.protocolV2LastTransferProgress = progress;
|
|
2223
|
+
this.protocolV2LastTransferProgressAt = now;
|
|
2224
|
+
this.postProgressMessage(progress, 'transferData', {
|
|
2454
2225
|
transferredBytes,
|
|
2455
2226
|
totalBytes: totalSize,
|
|
2456
2227
|
rateBytesPerSecond:
|
|
2457
2228
|
elapsedMs > 0 ? Math.round((chunkEnd / elapsedMs) * 1000) : undefined,
|
|
2458
2229
|
elapsedMs,
|
|
2459
|
-
}
|
|
2460
|
-
|
|
2230
|
+
});
|
|
2231
|
+
}
|
|
2461
2232
|
return length;
|
|
2462
2233
|
},
|
|
2463
2234
|
});
|
|
2464
2235
|
return processedSize + source.size;
|
|
2465
2236
|
} catch (error) {
|
|
2237
|
+
if (isBleStaleBondHardwareError(error)) {
|
|
2238
|
+
throw error;
|
|
2239
|
+
}
|
|
2466
2240
|
lastError = error;
|
|
2467
2241
|
if (attempt < PROTOCOL_V2_FILE_TRANSFER_RETRY_COUNT) {
|
|
2468
2242
|
await this.recoverProtocolV2FileTransfer();
|
|
@@ -2492,19 +2266,41 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2492
2266
|
}
|
|
2493
2267
|
}
|
|
2494
2268
|
|
|
2269
|
+
private collectProtocolV2LiveTargetIds(
|
|
2270
|
+
statusTargets: ProtocolV2FirmwareUpdateStatusTarget[],
|
|
2271
|
+
expectedTargetIds: Set<number>,
|
|
2272
|
+
liveTargetIds: Set<number>
|
|
2273
|
+
) {
|
|
2274
|
+
statusTargets.forEach(target => {
|
|
2275
|
+
const targetId = normalizeProtocolV2TargetId(target.target_id);
|
|
2276
|
+
if (
|
|
2277
|
+
targetId !== undefined &&
|
|
2278
|
+
expectedTargetIds.has(targetId) &&
|
|
2279
|
+
isProtocolV2TargetStatusInProgress(target.status)
|
|
2280
|
+
) {
|
|
2281
|
+
liveTargetIds.add(targetId);
|
|
2282
|
+
}
|
|
2283
|
+
});
|
|
2284
|
+
}
|
|
2285
|
+
|
|
2495
2286
|
private assertProtocolV2TargetStatus(
|
|
2496
2287
|
statusTargets: ProtocolV2FirmwareUpdateStatusTarget[],
|
|
2497
2288
|
expectedTargetIds: Set<number>,
|
|
2498
|
-
expectedPaths
|
|
2289
|
+
expectedPaths?: Map<number, string>,
|
|
2290
|
+
liveTargetIds?: Set<number>
|
|
2499
2291
|
) {
|
|
2292
|
+
const resolvedExpectedPaths = expectedPaths ?? new Map<number, string>();
|
|
2500
2293
|
Log.log(
|
|
2501
2294
|
`[FirmwareUpdateV4] DeviceFirmwareUpdateStatus records=${JSON.stringify(statusTargets)}`
|
|
2502
2295
|
);
|
|
2503
|
-
const failedTarget = statusTargets.find(
|
|
2504
|
-
target
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2296
|
+
const failedTarget = statusTargets.find(target => {
|
|
2297
|
+
const targetId = normalizeProtocolV2TargetId(target.target_id) ?? -1;
|
|
2298
|
+
return (
|
|
2299
|
+
expectedTargetIds.has(targetId) &&
|
|
2300
|
+
isProtocolV2TargetStatusFailed(target.status) &&
|
|
2301
|
+
(!liveTargetIds || liveTargetIds.has(targetId))
|
|
2302
|
+
);
|
|
2303
|
+
});
|
|
2508
2304
|
if (failedTarget) {
|
|
2509
2305
|
const failedTargetDetails = JSON.stringify({
|
|
2510
2306
|
targetId: failedTarget.target_id,
|
|
@@ -2515,13 +2311,33 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2515
2311
|
Log.error(`[FirmwareUpdateV4] firmware install failed target=${failedTargetDetails}`);
|
|
2516
2312
|
throw ERRORS.TypedError(
|
|
2517
2313
|
HardwareErrorCode.FirmwareError,
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
}`
|
|
2314
|
+
'Protocol V2 firmware install failed',
|
|
2315
|
+
{
|
|
2316
|
+
firmwareUpdateCode: PROTOCOL_V2_INSTALL_FAILED_CODE,
|
|
2317
|
+
}
|
|
2523
2318
|
);
|
|
2524
2319
|
}
|
|
2320
|
+
if (liveTargetIds) {
|
|
2321
|
+
const staleFailedTargets = statusTargets.filter(target => {
|
|
2322
|
+
const targetId = normalizeProtocolV2TargetId(target.target_id) ?? -1;
|
|
2323
|
+
return (
|
|
2324
|
+
expectedTargetIds.has(targetId) &&
|
|
2325
|
+
isProtocolV2TargetStatusFailed(target.status) &&
|
|
2326
|
+
!liveTargetIds.has(targetId)
|
|
2327
|
+
);
|
|
2328
|
+
});
|
|
2329
|
+
if (staleFailedTargets.length > 0) {
|
|
2330
|
+
Log.log(
|
|
2331
|
+
`[FirmwareUpdateV4] ignoring stale failed firmware status until those targets start: ${JSON.stringify(
|
|
2332
|
+
staleFailedTargets.map(target => ({
|
|
2333
|
+
targetId: target.target_id,
|
|
2334
|
+
status: target.status,
|
|
2335
|
+
path: target.path,
|
|
2336
|
+
}))
|
|
2337
|
+
)}`
|
|
2338
|
+
);
|
|
2339
|
+
}
|
|
2340
|
+
}
|
|
2525
2341
|
|
|
2526
2342
|
const matchingTargets = statusTargets.filter(target =>
|
|
2527
2343
|
expectedTargetIds.has(normalizeProtocolV2TargetId(target.target_id) ?? -1)
|
|
@@ -2529,14 +2345,21 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2529
2345
|
const seenTargetIds = new Set<number>();
|
|
2530
2346
|
for (const target of matchingTargets) {
|
|
2531
2347
|
const targetId = normalizeProtocolV2TargetId(target.target_id);
|
|
2348
|
+
const pathConflictsWithCurrentInstall =
|
|
2349
|
+
targetId !== undefined &&
|
|
2350
|
+
Boolean(target.path) &&
|
|
2351
|
+
Boolean(resolvedExpectedPaths.get(targetId)) &&
|
|
2352
|
+
target.path !== resolvedExpectedPaths.get(targetId) &&
|
|
2353
|
+
(!liveTargetIds || liveTargetIds.has(targetId));
|
|
2532
2354
|
if (
|
|
2533
2355
|
targetId === undefined ||
|
|
2534
2356
|
seenTargetIds.has(targetId) ||
|
|
2535
|
-
|
|
2357
|
+
pathConflictsWithCurrentInstall
|
|
2536
2358
|
) {
|
|
2359
|
+
Log.error(`[FirmwareUpdateV4] install status conflicts with target=${target.target_id}`);
|
|
2537
2360
|
throw ERRORS.TypedError(
|
|
2538
|
-
HardwareErrorCode.
|
|
2539
|
-
|
|
2361
|
+
HardwareErrorCode.FirmwareError,
|
|
2362
|
+
'Protocol V2 firmware install status conflict',
|
|
2540
2363
|
{
|
|
2541
2364
|
firmwareUpdateCode: PROTOCOL_V2_INSTALL_STATUS_CONFLICT_CODE,
|
|
2542
2365
|
}
|
|
@@ -2544,14 +2367,19 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2544
2367
|
}
|
|
2545
2368
|
seenTargetIds.add(targetId);
|
|
2546
2369
|
}
|
|
2547
|
-
const completedTargets = matchingTargets.filter(target =>
|
|
2548
|
-
|
|
2549
|
-
|
|
2370
|
+
const completedTargets = matchingTargets.filter(target => {
|
|
2371
|
+
const targetId = normalizeProtocolV2TargetId(target.target_id);
|
|
2372
|
+
return (
|
|
2373
|
+
isProtocolV2TargetStatusFinished(target.status) &&
|
|
2374
|
+
(!liveTargetIds || (targetId !== undefined && liveTargetIds.has(targetId)))
|
|
2375
|
+
);
|
|
2376
|
+
});
|
|
2550
2377
|
const completedTargetIds = new Set<number>();
|
|
2551
2378
|
completedTargets.forEach(target => {
|
|
2552
2379
|
const targetId = normalizeProtocolV2TargetId(target.target_id);
|
|
2553
2380
|
if (targetId !== undefined) {
|
|
2554
2381
|
completedTargetIds.add(targetId);
|
|
2382
|
+
this.protocolV2CompletedTargetIds.add(targetId);
|
|
2555
2383
|
}
|
|
2556
2384
|
});
|
|
2557
2385
|
if (completedTargetIds.size === expectedTargetIds.size && expectedTargetIds.size > 0) {
|
|
@@ -2582,16 +2410,54 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2582
2410
|
}
|
|
2583
2411
|
|
|
2584
2412
|
if (expectedTargetIds.size > 0 && matchingTargets.length > 0) {
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2413
|
+
let detailedProgress = completedTargetIds.size * 100;
|
|
2414
|
+
let installProgressMetadata:
|
|
2415
|
+
| {
|
|
2416
|
+
installTargetId: number;
|
|
2417
|
+
installPhase: 'prepare' | 'install' | 'verify';
|
|
2418
|
+
installPhaseProgress: number;
|
|
2419
|
+
}
|
|
2420
|
+
| undefined;
|
|
2421
|
+
let hasInProgressTarget = false;
|
|
2422
|
+
matchingTargets.forEach(target => {
|
|
2423
|
+
const targetId = normalizeProtocolV2TargetId(target.target_id);
|
|
2424
|
+
if (
|
|
2425
|
+
targetId === undefined ||
|
|
2426
|
+
completedTargetIds.has(targetId) ||
|
|
2427
|
+
!isProtocolV2TargetStatusInProgress(target.status) ||
|
|
2428
|
+
(liveTargetIds && !liveTargetIds.has(targetId))
|
|
2429
|
+
) {
|
|
2430
|
+
return;
|
|
2431
|
+
}
|
|
2432
|
+
hasInProgressTarget = true;
|
|
2433
|
+
detailedProgress += normalizeProtocolV2ProgressPercent(target.progress_percent) ?? 0;
|
|
2434
|
+
const installPhase = target.phase_info
|
|
2435
|
+
? PROTOCOL_V2_INSTALL_PHASE_BY_DECODED_VALUE.get(target.phase_info.phase)
|
|
2436
|
+
: undefined;
|
|
2437
|
+
const installPhaseProgress = normalizeProtocolV2ProgressPercent(
|
|
2438
|
+
target.phase_info?.progress_percent
|
|
2439
|
+
);
|
|
2440
|
+
if (installPhase && installPhaseProgress !== undefined && !installProgressMetadata) {
|
|
2441
|
+
installProgressMetadata = {
|
|
2442
|
+
installTargetId: targetId,
|
|
2443
|
+
installPhase,
|
|
2444
|
+
installPhaseProgress,
|
|
2445
|
+
};
|
|
2446
|
+
}
|
|
2447
|
+
});
|
|
2588
2448
|
const completedProgress = Math.floor(
|
|
2589
2449
|
(completedTargetIds.size / expectedTargetIds.size) * 100
|
|
2590
2450
|
);
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2451
|
+
const reportedProgress = Math.floor(detailedProgress / expectedTargetIds.size);
|
|
2452
|
+
const progress = Math.min(
|
|
2453
|
+
99,
|
|
2454
|
+
Math.max(completedProgress, reportedProgress, hasInProgressTarget ? 1 : 0)
|
|
2455
|
+
);
|
|
2456
|
+
if (installProgressMetadata) {
|
|
2457
|
+
this.postProgressMessage(progress, 'installingFirmware', installProgressMetadata);
|
|
2458
|
+
} else {
|
|
2459
|
+
this.postProgressMessage(progress, 'installingFirmware');
|
|
2460
|
+
}
|
|
2595
2461
|
}
|
|
2596
2462
|
|
|
2597
2463
|
return false;
|
|
@@ -2611,31 +2477,107 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2611
2477
|
return Array.from(expectedTargetIds).filter(targetId => !reportedTargetIds.has(targetId));
|
|
2612
2478
|
}
|
|
2613
2479
|
|
|
2480
|
+
private getProtocolV2ObservableTargetVersions(features: Features) {
|
|
2481
|
+
const versions = new Map<number, string>();
|
|
2482
|
+
versions.set(
|
|
2483
|
+
ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_BOOTLOADER,
|
|
2484
|
+
getDeviceBootloaderVersion(features).join('.')
|
|
2485
|
+
);
|
|
2486
|
+
const applicationVersion = getDeviceFirmwareVersion(features).join('.');
|
|
2487
|
+
versions.set(ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_APPLICATION_P1, applicationVersion);
|
|
2488
|
+
versions.set(ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_APPLICATION_P2, applicationVersion);
|
|
2489
|
+
versions.set(
|
|
2490
|
+
ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_COPROCESSOR,
|
|
2491
|
+
getDeviceBLEFirmwareVersion(features).join('.')
|
|
2492
|
+
);
|
|
2493
|
+
const secureElementVersions: Array<[number, string | null | undefined]> = [
|
|
2494
|
+
[ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_SE01, features.se01Version],
|
|
2495
|
+
[ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_SE02, features.se02Version],
|
|
2496
|
+
[ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_SE03, features.se03Version],
|
|
2497
|
+
[ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_SE04, features.se04Version],
|
|
2498
|
+
];
|
|
2499
|
+
secureElementVersions.forEach(([targetId, version]) => {
|
|
2500
|
+
if (version) versions.set(targetId, version);
|
|
2501
|
+
});
|
|
2502
|
+
return versions;
|
|
2503
|
+
}
|
|
2504
|
+
|
|
2505
|
+
private hasProtocolV2InstallVersionChanged(expectedTargetIds: Set<number>) {
|
|
2506
|
+
if (!this.protocolV2LastRuntimeProbeFeatures) return false;
|
|
2507
|
+
const currentVersions = this.getProtocolV2ObservableTargetVersions(
|
|
2508
|
+
this.protocolV2LastRuntimeProbeFeatures
|
|
2509
|
+
);
|
|
2510
|
+
return Array.from(expectedTargetIds).some(targetId => {
|
|
2511
|
+
const previousVersion = this.protocolV2InstallBaselineVersions.get(targetId);
|
|
2512
|
+
const currentVersion = currentVersions.get(targetId);
|
|
2513
|
+
return (
|
|
2514
|
+
previousVersion !== undefined &&
|
|
2515
|
+
currentVersion !== undefined &&
|
|
2516
|
+
previousVersion !== currentVersion
|
|
2517
|
+
);
|
|
2518
|
+
});
|
|
2519
|
+
}
|
|
2520
|
+
|
|
2521
|
+
private recordProtocolV2AuthoritativeInstallCompletion(expectedTargetIds: Set<number>) {
|
|
2522
|
+
expectedTargetIds.forEach(targetId => this.protocolV2CompletedTargetIds.add(targetId));
|
|
2523
|
+
}
|
|
2524
|
+
|
|
2614
2525
|
private async waitForProtocolV2FirmwareUpdateComplete(
|
|
2615
|
-
targets: Array<{ target_id: number; path: string }
|
|
2526
|
+
targets: Array<{ target_id: number; path: string }>,
|
|
2527
|
+
requireCurrentInstallStatus = false
|
|
2616
2528
|
) {
|
|
2617
|
-
this.protocolV2FinalStatusVerified = false;
|
|
2618
2529
|
const expectedTargetIds = new Set(targets.map(target => target.target_id));
|
|
2619
2530
|
const expectedPaths = new Map(targets.map(target => [target.target_id, target.path]));
|
|
2531
|
+
const isBleInstall = this.isBleReconnect();
|
|
2620
2532
|
const startTime = Date.now();
|
|
2621
2533
|
let lastError: unknown;
|
|
2622
|
-
|
|
2534
|
+
// The loader may release either USB or BLE as installation starts. Recovery reconnects
|
|
2535
|
+
// directly to status polling without generic Ping or DeviceInfo probes.
|
|
2536
|
+
let shouldReconnect = this.protocolV2InstallNeedsReconnect;
|
|
2537
|
+
this.protocolV2InstallNeedsReconnect = false;
|
|
2623
2538
|
let deviceInfo: ProtocolV2DeviceInfo | undefined;
|
|
2624
|
-
let
|
|
2625
|
-
let
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2539
|
+
let bleInstallLinkReady = false;
|
|
2540
|
+
let installStatusDisconnectObserved = isBleInstall && this.protocolV2InstallDisconnectObserved;
|
|
2541
|
+
this.protocolV2InstallDisconnectObserved = false;
|
|
2542
|
+
let installReconnectObserved = false;
|
|
2543
|
+
let finishedStatusSnapshotKey: string | undefined;
|
|
2544
|
+
let finishedStatusSnapshotPolls = 0;
|
|
2545
|
+
let fastFinishedInstallProgressReported = false;
|
|
2546
|
+
let installEvidenceObserved = this.protocolV2InstallTerminalSuccessObserved;
|
|
2547
|
+
let currentInstallStatusObserved = false;
|
|
2548
|
+
const liveTargetIds = new Set<number>();
|
|
2549
|
+
const recordBleInstallDisconnect = (error: unknown) => {
|
|
2550
|
+
if (
|
|
2551
|
+
!isBleInstall ||
|
|
2552
|
+
!isProtocolV2DeviceDisconnectedError(error) ||
|
|
2553
|
+
isProtocolV2InstallResponseTimeout(error)
|
|
2554
|
+
) {
|
|
2555
|
+
return;
|
|
2556
|
+
}
|
|
2557
|
+
installStatusDisconnectObserved = true;
|
|
2558
|
+
installReconnectObserved = false;
|
|
2559
|
+
finishedStatusSnapshotKey = undefined;
|
|
2560
|
+
finishedStatusSnapshotPolls = 0;
|
|
2629
2561
|
};
|
|
2630
2562
|
|
|
2631
2563
|
while (Date.now() - startTime < PROTOCOL_V2_INSTALL_TIMEOUT) {
|
|
2564
|
+
// A transport release caused by an explicit workflow cancellation must not
|
|
2565
|
+
// be mistaken for the expected device reboot during installation.
|
|
2566
|
+
this.throwIfAborted();
|
|
2632
2567
|
try {
|
|
2633
2568
|
if (shouldReconnect) {
|
|
2634
|
-
|
|
2635
|
-
|
|
2569
|
+
const isBleInstallReconnect = this.isBleReconnect();
|
|
2570
|
+
await this.reconnectProtocolV2Device({ skipProtocolProbe: true });
|
|
2571
|
+
bleInstallLinkReady = isBleInstallReconnect;
|
|
2572
|
+
deviceInfo = isBleInstallReconnect
|
|
2573
|
+
? undefined
|
|
2574
|
+
: await this.verifyProtocolV2ReconnectIdentity();
|
|
2575
|
+
if (isBleInstallReconnect && installStatusDisconnectObserved) {
|
|
2576
|
+
installReconnectObserved = true;
|
|
2577
|
+
}
|
|
2636
2578
|
shouldReconnect = false;
|
|
2637
2579
|
}
|
|
2638
|
-
|
|
2580
|
+
let currentDeviceInfo = deviceInfo;
|
|
2639
2581
|
try {
|
|
2640
2582
|
const statusResponse = await this.device.getCommands().typedCall(
|
|
2641
2583
|
'DeviceFirmwareUpdateStatusGet',
|
|
@@ -2643,91 +2585,235 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2643
2585
|
{
|
|
2644
2586
|
fields: {
|
|
2645
2587
|
status: true,
|
|
2588
|
+
progress_percent: true,
|
|
2589
|
+
phase_info: true,
|
|
2646
2590
|
payload_version: true,
|
|
2647
2591
|
path: true,
|
|
2648
2592
|
},
|
|
2649
2593
|
},
|
|
2650
2594
|
{ timeoutMs: PROTOCOL_V2_FIRMWARE_STATUS_RESPONSE_TIMEOUT }
|
|
2651
2595
|
);
|
|
2596
|
+
if (this.protocolV2InstallTerminalSuccessObserved) {
|
|
2597
|
+
installEvidenceObserved = true;
|
|
2598
|
+
}
|
|
2652
2599
|
const statusTargets = (statusResponse.message.records ??
|
|
2653
2600
|
[]) as ProtocolV2FirmwareUpdateStatusTarget[];
|
|
2654
|
-
|
|
2655
|
-
|
|
2601
|
+
this.collectProtocolV2LiveTargetIds(statusTargets, expectedTargetIds, liveTargetIds);
|
|
2602
|
+
if (liveTargetIds.size > 0) {
|
|
2603
|
+
currentInstallStatusObserved = true;
|
|
2604
|
+
}
|
|
2605
|
+
const hasMatchingTargetStatus = statusTargets.some(target => {
|
|
2606
|
+
const targetId = normalizeProtocolV2TargetId(target.target_id);
|
|
2607
|
+
return targetId !== undefined && expectedTargetIds.has(targetId);
|
|
2608
|
+
});
|
|
2609
|
+
if (
|
|
2610
|
+
hasMatchingTargetStatus &&
|
|
2611
|
+
(!requireCurrentInstallStatus || currentInstallStatusObserved)
|
|
2612
|
+
) {
|
|
2613
|
+
installEvidenceObserved = true;
|
|
2614
|
+
}
|
|
2615
|
+
const matchingStatusTargets = statusTargets.filter(target => {
|
|
2616
|
+
const targetId = normalizeProtocolV2TargetId(target.target_id);
|
|
2617
|
+
return targetId !== undefined && expectedTargetIds.has(targetId);
|
|
2618
|
+
});
|
|
2619
|
+
const matchingFinishedTargetIds = new Set<number>();
|
|
2620
|
+
const matchingFinishedStatus = matchingStatusTargets
|
|
2621
|
+
.map(target => {
|
|
2622
|
+
const targetId = normalizeProtocolV2TargetId(target.target_id);
|
|
2623
|
+
const expectedPath = targetId === undefined ? undefined : expectedPaths.get(targetId);
|
|
2624
|
+
const expectedVersion =
|
|
2625
|
+
targetId === undefined
|
|
2626
|
+
? undefined
|
|
2627
|
+
: this.getExpectedProtocolV2TargetVersion(targetId);
|
|
2628
|
+
const payloadVersion = toProtocolV2FiniteNumber(target.payload_version);
|
|
2629
|
+
if (
|
|
2630
|
+
targetId === undefined ||
|
|
2631
|
+
matchingFinishedTargetIds.has(targetId) ||
|
|
2632
|
+
!isProtocolV2TargetStatusFinished(target.status) ||
|
|
2633
|
+
(Boolean(target.path) && Boolean(expectedPath) && target.path !== expectedPath) ||
|
|
2634
|
+
(expectedVersion !== undefined &&
|
|
2635
|
+
payloadVersion !== undefined &&
|
|
2636
|
+
payloadVersion !== expectedVersion)
|
|
2637
|
+
) {
|
|
2638
|
+
return undefined;
|
|
2639
|
+
}
|
|
2640
|
+
matchingFinishedTargetIds.add(targetId);
|
|
2641
|
+
return [targetId, target.path ?? null, payloadVersion ?? null] as const;
|
|
2642
|
+
})
|
|
2643
|
+
.filter((target): target is readonly [number, string | null, number | null] => !!target)
|
|
2644
|
+
.sort(([leftTargetId], [rightTargetId]) => leftTargetId - rightTargetId);
|
|
2645
|
+
const hasCompleteMatchingFinishedStatus =
|
|
2646
|
+
matchingFinishedStatus.length === expectedTargetIds.size &&
|
|
2647
|
+
matchingFinishedTargetIds.size === expectedTargetIds.size &&
|
|
2648
|
+
expectedTargetIds.size > 0;
|
|
2649
|
+
if (
|
|
2650
|
+
requireCurrentInstallStatus &&
|
|
2651
|
+
installReconnectObserved &&
|
|
2652
|
+
hasCompleteMatchingFinishedStatus
|
|
2653
|
+
) {
|
|
2654
|
+
const nextSnapshotKey = JSON.stringify(matchingFinishedStatus);
|
|
2655
|
+
if (nextSnapshotKey === finishedStatusSnapshotKey) {
|
|
2656
|
+
finishedStatusSnapshotPolls += 1;
|
|
2657
|
+
} else {
|
|
2658
|
+
finishedStatusSnapshotKey = nextSnapshotKey;
|
|
2659
|
+
finishedStatusSnapshotPolls = 1;
|
|
2660
|
+
}
|
|
2661
|
+
} else {
|
|
2662
|
+
finishedStatusSnapshotKey = undefined;
|
|
2663
|
+
finishedStatusSnapshotPolls = 0;
|
|
2664
|
+
}
|
|
2665
|
+
if (
|
|
2666
|
+
requireCurrentInstallStatus &&
|
|
2667
|
+
installReconnectObserved &&
|
|
2668
|
+
hasCompleteMatchingFinishedStatus &&
|
|
2669
|
+
!currentInstallStatusObserved &&
|
|
2670
|
+
!this.protocolV2InstallTerminalSuccessObserved &&
|
|
2671
|
+
!fastFinishedInstallProgressReported
|
|
2672
|
+
) {
|
|
2673
|
+
// A fast BLE install may finish while disconnected, so IN_PROGRESS can be hidden.
|
|
2674
|
+
// A status response timeout alone is not evidence that the install was accepted.
|
|
2675
|
+
this.postProgressMessage(1, 'installingFirmware');
|
|
2676
|
+
fastFinishedInstallProgressReported = true;
|
|
2677
|
+
}
|
|
2678
|
+
const finishedAfterDisconnectObserved =
|
|
2679
|
+
finishedStatusSnapshotPolls >= PROTOCOL_V2_INSTALL_FINISHED_AFTER_DISCONNECT_POLLS;
|
|
2680
|
+
const hasCurrentInstallEvidence =
|
|
2681
|
+
currentInstallStatusObserved ||
|
|
2682
|
+
this.protocolV2InstallTerminalSuccessObserved ||
|
|
2683
|
+
finishedAfterDisconnectObserved;
|
|
2684
|
+
if (hasCurrentInstallEvidence) {
|
|
2685
|
+
installEvidenceObserved = true;
|
|
2686
|
+
}
|
|
2687
|
+
const shouldVerifyTargetCompletion =
|
|
2688
|
+
!requireCurrentInstallStatus || hasCurrentInstallEvidence;
|
|
2689
|
+
if (
|
|
2690
|
+
shouldVerifyTargetCompletion &&
|
|
2691
|
+
this.assertProtocolV2TargetStatus(
|
|
2692
|
+
statusTargets,
|
|
2693
|
+
expectedTargetIds,
|
|
2694
|
+
expectedPaths,
|
|
2695
|
+
requireCurrentInstallStatus &&
|
|
2696
|
+
!this.protocolV2InstallTerminalSuccessObserved &&
|
|
2697
|
+
!finishedAfterDisconnectObserved
|
|
2698
|
+
? liveTargetIds
|
|
2699
|
+
: undefined
|
|
2700
|
+
)
|
|
2701
|
+
) {
|
|
2656
2702
|
return;
|
|
2657
2703
|
}
|
|
2658
2704
|
|
|
2705
|
+
if (!hasCurrentInstallEvidence) {
|
|
2706
|
+
// A successful status poll clears DeviceCommands' cancel action. Keep cancellation
|
|
2707
|
+
// available while UpdateRequest is still waiting for confirmation on the device.
|
|
2708
|
+
this.device.setCancelableAction(() => this.device.getCommands().cancelDevice());
|
|
2709
|
+
}
|
|
2710
|
+
|
|
2659
2711
|
if (
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2712
|
+
requireCurrentInstallStatus &&
|
|
2713
|
+
!hasCurrentInstallEvidence &&
|
|
2714
|
+
matchingStatusTargets.length > 0
|
|
2663
2715
|
) {
|
|
2664
|
-
|
|
2665
|
-
'
|
|
2716
|
+
lastError = new Error(
|
|
2717
|
+
'Protocol V2 firmware status is stale; waiting for the current install to start'
|
|
2666
2718
|
);
|
|
2667
|
-
this.postProgressMessage(100, 'installingFirmware');
|
|
2668
|
-
return;
|
|
2669
2719
|
}
|
|
2670
2720
|
|
|
2671
|
-
|
|
2672
|
-
statusTargets
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2721
|
+
if (
|
|
2722
|
+
statusTargets.length === 0 &&
|
|
2723
|
+
!currentDeviceInfo &&
|
|
2724
|
+
bleInstallLinkReady &&
|
|
2725
|
+
installEvidenceObserved
|
|
2726
|
+
) {
|
|
2727
|
+
// BLE install reconnect skips generic probes because loaders may not answer Ping.
|
|
2728
|
+
// Restore the verified identity only after current-install evidence disappears.
|
|
2729
|
+
currentDeviceInfo = await this.verifyProtocolV2ReconnectIdentity();
|
|
2730
|
+
deviceInfo = currentDeviceInfo;
|
|
2731
|
+
}
|
|
2732
|
+
|
|
2733
|
+
if (statusTargets.length === 0 && currentDeviceInfo) {
|
|
2734
|
+
const isNormalMode = await this.probeProtocolV2NormalMode(currentDeviceInfo);
|
|
2735
|
+
if (
|
|
2736
|
+
isNormalMode &&
|
|
2737
|
+
(installEvidenceObserved ||
|
|
2738
|
+
this.hasProtocolV2InstallVersionChanged(expectedTargetIds))
|
|
2684
2739
|
) {
|
|
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
|
-
)}`
|
|
2740
|
+
Log.log(
|
|
2741
|
+
'[FirmwareUpdateV4] empty firmware status after confirmed App reboot; update complete'
|
|
2693
2742
|
);
|
|
2743
|
+
this.recordProtocolV2AuthoritativeInstallCompletion(expectedTargetIds);
|
|
2744
|
+
this.postProgressMessage(100, 'installingFirmware');
|
|
2745
|
+
return;
|
|
2694
2746
|
}
|
|
2747
|
+
}
|
|
2748
|
+
|
|
2749
|
+
if (statusTargets.length === 0) {
|
|
2695
2750
|
lastError = new Error(
|
|
2696
|
-
|
|
2751
|
+
'Protocol V2 firmware update is waiting for user confirmation or target status'
|
|
2697
2752
|
);
|
|
2698
2753
|
} else {
|
|
2699
|
-
|
|
2700
|
-
|
|
2754
|
+
const missingTargetIds = this.getProtocolV2MissingTargetIds(
|
|
2755
|
+
statusTargets,
|
|
2756
|
+
expectedTargetIds
|
|
2757
|
+
);
|
|
2758
|
+
if (missingTargetIds.length > 0) {
|
|
2759
|
+
const missingKey = missingTargetIds.join(',');
|
|
2760
|
+
lastError = new Error(
|
|
2761
|
+
`Protocol V2 firmware status is temporarily missing targetIds=${missingKey}`
|
|
2762
|
+
);
|
|
2763
|
+
} else {
|
|
2764
|
+
lastError = new Error('Protocol V2 firmware targets are still installing');
|
|
2765
|
+
}
|
|
2701
2766
|
}
|
|
2702
2767
|
} catch (error) {
|
|
2703
2768
|
if (isProtocolV2TerminalInstallStatusError(error)) {
|
|
2704
2769
|
throw error;
|
|
2705
2770
|
}
|
|
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
2771
|
// App firmware does not register DeviceFirmwareUpdateStatusGet. Treat the
|
|
2710
2772
|
// missing endpoint as completion only after the runtime probe confirms App mode.
|
|
2711
2773
|
if (isProtocolV2FirmwareStatusEndpointUnavailable(error)) {
|
|
2712
2774
|
if (!currentDeviceInfo) {
|
|
2775
|
+
if (!bleInstallLinkReady) {
|
|
2776
|
+
throw ERRORS.TypedError(
|
|
2777
|
+
HardwareErrorCode.RuntimeError,
|
|
2778
|
+
'Protocol V2 device identity is unavailable during install polling'
|
|
2779
|
+
);
|
|
2780
|
+
}
|
|
2781
|
+
deviceInfo = await this.verifyProtocolV2ReconnectIdentity();
|
|
2782
|
+
}
|
|
2783
|
+
const reconnectDeviceInfo = currentDeviceInfo ?? deviceInfo;
|
|
2784
|
+
if (!reconnectDeviceInfo) {
|
|
2713
2785
|
throw ERRORS.TypedError(
|
|
2714
2786
|
HardwareErrorCode.RuntimeError,
|
|
2715
2787
|
'Protocol V2 device identity is unavailable during install polling'
|
|
2716
2788
|
);
|
|
2717
2789
|
}
|
|
2718
|
-
|
|
2790
|
+
const isNormalMode = await this.probeProtocolV2NormalMode(reconnectDeviceInfo);
|
|
2791
|
+
if (
|
|
2792
|
+
isNormalMode &&
|
|
2793
|
+
(installEvidenceObserved ||
|
|
2794
|
+
this.hasProtocolV2InstallVersionChanged(expectedTargetIds))
|
|
2795
|
+
) {
|
|
2719
2796
|
Log.log(
|
|
2720
2797
|
'[FirmwareUpdateV4] firmware status endpoint unavailable after confirmed App reboot'
|
|
2721
2798
|
);
|
|
2799
|
+
this.recordProtocolV2AuthoritativeInstallCompletion(expectedTargetIds);
|
|
2722
2800
|
this.postProgressMessage(100, 'installingFirmware');
|
|
2723
2801
|
return;
|
|
2724
2802
|
}
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2803
|
+
if (isNormalMode) {
|
|
2804
|
+
lastError = new Error(
|
|
2805
|
+
'Protocol V2 device is in normal mode but installation is not yet confirmed'
|
|
2806
|
+
);
|
|
2807
|
+
} else {
|
|
2808
|
+
lastError = new Error(
|
|
2809
|
+
'Protocol V2 firmware status endpoint is unavailable while the device remains in loader mode'
|
|
2810
|
+
);
|
|
2811
|
+
}
|
|
2728
2812
|
} else {
|
|
2813
|
+
recordBleInstallDisconnect(error);
|
|
2729
2814
|
shouldReconnect = true;
|
|
2730
2815
|
deviceInfo = undefined;
|
|
2816
|
+
bleInstallLinkReady = false;
|
|
2731
2817
|
lastError = error;
|
|
2732
2818
|
Log.log(
|
|
2733
2819
|
'[FirmwareUpdateV4] DeviceFirmwareUpdateStatusGet unavailable during install: ',
|
|
@@ -2749,20 +2835,27 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2749
2835
|
) {
|
|
2750
2836
|
throw error;
|
|
2751
2837
|
}
|
|
2838
|
+
recordBleInstallDisconnect(error);
|
|
2752
2839
|
shouldReconnect = true;
|
|
2753
2840
|
deviceInfo = undefined;
|
|
2754
|
-
|
|
2841
|
+
bleInstallLinkReady = false;
|
|
2755
2842
|
Log.log('Protocol V2 firmware install device readiness probe failed: ', error);
|
|
2756
2843
|
}
|
|
2757
2844
|
await wait(1000);
|
|
2758
2845
|
}
|
|
2759
2846
|
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
`Protocol V2 firmware update status not complete within ${
|
|
2847
|
+
Log.error(
|
|
2848
|
+
`[FirmwareUpdateV4] install timed out after ${
|
|
2763
2849
|
PROTOCOL_V2_INSTALL_TIMEOUT / 1000
|
|
2764
2850
|
}s: ${this.normalizeErrorMessage(lastError)}`
|
|
2765
2851
|
);
|
|
2852
|
+
throw ERRORS.TypedError(
|
|
2853
|
+
HardwareErrorCode.FirmwareError,
|
|
2854
|
+
'Protocol V2 firmware install timed out',
|
|
2855
|
+
{
|
|
2856
|
+
firmwareUpdateCode: PROTOCOL_V2_INSTALL_TIMEOUT_CODE,
|
|
2857
|
+
}
|
|
2858
|
+
);
|
|
2766
2859
|
}
|
|
2767
2860
|
|
|
2768
2861
|
private async exitProtocolV2BootloaderToNormal() {
|
|
@@ -2774,6 +2867,9 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2774
2867
|
return;
|
|
2775
2868
|
}
|
|
2776
2869
|
} catch (error) {
|
|
2870
|
+
if (isBleStaleBondHardwareError(error)) {
|
|
2871
|
+
throw error;
|
|
2872
|
+
}
|
|
2777
2873
|
Log.log('[FirmwareUpdateV4] unable to confirm App mode before Normal reboot: ', error);
|
|
2778
2874
|
}
|
|
2779
2875
|
await this.protocolV2Reboot(DeviceRebootType.Normal);
|
|
@@ -2785,7 +2881,14 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2785
2881
|
deviceInfo,
|
|
2786
2882
|
PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT
|
|
2787
2883
|
);
|
|
2788
|
-
|
|
2884
|
+
this.protocolV2LastRuntimeProbeFeatures = features;
|
|
2885
|
+
return this.isProtocolV2ApplicationMode(features);
|
|
2886
|
+
}
|
|
2887
|
+
|
|
2888
|
+
private isProtocolV2ApplicationMode(features: Features) {
|
|
2889
|
+
return (
|
|
2890
|
+
(features.mode === 'normal' || features.mode === 'notInitialized') && !features.bootloaderMode
|
|
2891
|
+
);
|
|
2789
2892
|
}
|
|
2790
2893
|
|
|
2791
2894
|
private async waitForProtocolV2FinalFeatures() {
|
|
@@ -2829,6 +2932,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2829
2932
|
const startTime = Date.now();
|
|
2830
2933
|
let lastError: unknown;
|
|
2831
2934
|
let shouldReconnect = true;
|
|
2935
|
+
this.protocolV2LatestFinalDeviceInfo = undefined;
|
|
2832
2936
|
|
|
2833
2937
|
while (Date.now() - startTime < timeout) {
|
|
2834
2938
|
try {
|
|
@@ -2845,9 +2949,11 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2845
2949
|
this.assertProtocolV2DeviceInfoIdentity(deviceInfo);
|
|
2846
2950
|
const features = await this.device.probeProtocolV2RuntimeState(
|
|
2847
2951
|
deviceInfo,
|
|
2848
|
-
PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT
|
|
2952
|
+
PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT,
|
|
2953
|
+
{ forceRuntimeContextRefresh: true }
|
|
2849
2954
|
);
|
|
2850
|
-
if (
|
|
2955
|
+
if (this.isProtocolV2ApplicationMode(features)) {
|
|
2956
|
+
this.protocolV2LatestFinalDeviceInfo = deviceInfo;
|
|
2851
2957
|
return features;
|
|
2852
2958
|
}
|
|
2853
2959
|
lastError = ERRORS.TypedError(
|
|
@@ -2855,7 +2961,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2855
2961
|
'Protocol V2 device is still in bootloader mode'
|
|
2856
2962
|
);
|
|
2857
2963
|
} catch (error) {
|
|
2858
|
-
if (this.isProtocolV2ReconnectIdentityError(error)) {
|
|
2964
|
+
if (isBleStaleBondHardwareError(error) || this.isProtocolV2ReconnectIdentityError(error)) {
|
|
2859
2965
|
throw error;
|
|
2860
2966
|
}
|
|
2861
2967
|
shouldReconnect = true;
|
|
@@ -2873,19 +2979,53 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2873
2979
|
);
|
|
2874
2980
|
}
|
|
2875
2981
|
|
|
2876
|
-
private async reconnectProtocolV2Device() {
|
|
2982
|
+
private async reconnectProtocolV2Device(options?: { skipProtocolProbe?: boolean }) {
|
|
2877
2983
|
if (this.isBleReconnect()) {
|
|
2878
|
-
await this.acquireProtocolV2BleDevice();
|
|
2984
|
+
await this.acquireProtocolV2BleDevice(options?.skipProtocolProbe);
|
|
2879
2985
|
return;
|
|
2880
2986
|
}
|
|
2881
2987
|
|
|
2882
2988
|
const deviceDiff = await this.device.deviceConnector?.enumerate();
|
|
2883
2989
|
const devicesDescriptor = deviceDiff?.descriptors ?? [];
|
|
2990
|
+
const env = DataManager.getSettings('env');
|
|
2991
|
+
const isWebUsb = DataManager.isBrowserWebUsb(env) || DataManager.isDesktopWebUsb(env);
|
|
2884
2992
|
|
|
2885
|
-
if (
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
2993
|
+
if (isWebUsb && options?.skipProtocolProbe) {
|
|
2994
|
+
const expectedSerialNumber = this.protocolV2ExpectedSerialNumber?.trim();
|
|
2995
|
+
const expectedPath = this.protocolV2ExpectedPath?.trim();
|
|
2996
|
+
const reconnectDescriptor = devicesDescriptor.find(descriptor => {
|
|
2997
|
+
const descriptorPath = descriptor.path?.trim();
|
|
2998
|
+
return (
|
|
2999
|
+
(!!expectedSerialNumber && descriptorPath === expectedSerialNumber) ||
|
|
3000
|
+
(!!expectedPath && descriptorPath === expectedPath)
|
|
3001
|
+
);
|
|
3002
|
+
});
|
|
3003
|
+
if (!reconnectDescriptor?.path) {
|
|
3004
|
+
throw ERRORS.TypedError(HardwareErrorCode.DeviceNotFound);
|
|
3005
|
+
}
|
|
3006
|
+
this.device.updateDescriptor(
|
|
3007
|
+
{
|
|
3008
|
+
...reconnectDescriptor,
|
|
3009
|
+
protocolType: PROTOCOL_V2_CONNECT_PROTOCOL,
|
|
3010
|
+
},
|
|
3011
|
+
true
|
|
3012
|
+
);
|
|
3013
|
+
const mainId = await this.device.deviceConnector?.acquire(
|
|
3014
|
+
reconnectDescriptor.path,
|
|
3015
|
+
null,
|
|
3016
|
+
undefined,
|
|
3017
|
+
PROTOCOL_V2_CONNECT_PROTOCOL,
|
|
3018
|
+
undefined,
|
|
3019
|
+
undefined,
|
|
3020
|
+
true
|
|
3021
|
+
);
|
|
3022
|
+
this.device.mainId = typeof mainId === 'string' ? mainId : reconnectDescriptor.path;
|
|
3023
|
+
this.device.commands.disposed = false;
|
|
3024
|
+
this.device.getCommands().mainId = this.device.mainId;
|
|
3025
|
+
return;
|
|
3026
|
+
}
|
|
3027
|
+
|
|
3028
|
+
if (isWebUsb && devicesDescriptor.length === 1) {
|
|
2889
3029
|
const descriptor = devicesDescriptor[0];
|
|
2890
3030
|
if (!this.protocolV2ExpectedSerialNumber && descriptor.path !== this.protocolV2ExpectedPath) {
|
|
2891
3031
|
throw ERRORS.TypedError(HardwareErrorCode.DeviceNotFound);
|
|
@@ -3003,13 +3143,24 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
3003
3143
|
await wait(2000);
|
|
3004
3144
|
}
|
|
3005
3145
|
|
|
3006
|
-
private async acquireProtocolV2BleDevice() {
|
|
3007
|
-
|
|
3008
|
-
|
|
3146
|
+
private async acquireProtocolV2BleDevice(skipProtocolProbe = false) {
|
|
3147
|
+
const connector = this.device.deviceConnector;
|
|
3148
|
+
const expectedId = this.device.originalDescriptor.id;
|
|
3149
|
+
if (!skipProtocolProbe) {
|
|
3150
|
+
await connector?.acquire(expectedId, null, true, PROTOCOL_V2_CONNECT_PROTOCOL);
|
|
3151
|
+
return;
|
|
3152
|
+
}
|
|
3153
|
+
await connector?.acquire(
|
|
3154
|
+
expectedId,
|
|
3009
3155
|
null,
|
|
3010
3156
|
true,
|
|
3011
|
-
PROTOCOL_V2_CONNECT_PROTOCOL
|
|
3157
|
+
PROTOCOL_V2_CONNECT_PROTOCOL,
|
|
3158
|
+
undefined,
|
|
3159
|
+
undefined,
|
|
3160
|
+
skipProtocolProbe
|
|
3012
3161
|
);
|
|
3162
|
+
this.device.commands.disposed = false;
|
|
3163
|
+
this.device.getCommands().mainId = expectedId;
|
|
3013
3164
|
}
|
|
3014
3165
|
|
|
3015
3166
|
private async protocolV2StartFirmwareUpdate({
|
|
@@ -3017,30 +3168,74 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
3017
3168
|
}: {
|
|
3018
3169
|
targets: Array<{ target_id: number; path: string }>;
|
|
3019
3170
|
}) {
|
|
3171
|
+
this.protocolV2LastRuntimeProbeFeatures = undefined;
|
|
3172
|
+
this.protocolV2InstallNeedsReconnect = false;
|
|
3173
|
+
this.protocolV2InstallDisconnectObserved = false;
|
|
3174
|
+
this.protocolV2InstallTerminalSuccessObserved = false;
|
|
3020
3175
|
const commands = this.device.getCommands();
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
{
|
|
3176
|
+
await commands.typedCall('DeviceFirmwareUpdateStage', 'Success', { targets });
|
|
3177
|
+
this.device.setCancelableAction(() => this.device.getCommands().cancelDevice());
|
|
3178
|
+
const interaction = this.device.createProtocolV2UiPhaseMetadata('button', 'start');
|
|
3179
|
+
this.postMessage(
|
|
3180
|
+
createUiMessage(UI_REQUEST.REQUEST_BUTTON, {
|
|
3181
|
+
device: this.device.toMessageObject() as KnownDevice,
|
|
3182
|
+
source: 'method-lifecycle',
|
|
3183
|
+
reason: 'firmware-update',
|
|
3184
|
+
completion: 'operation-completed',
|
|
3185
|
+
deviceOnly: true,
|
|
3186
|
+
operation: this.name,
|
|
3187
|
+
...(interaction ? { interaction } : {}),
|
|
3188
|
+
})
|
|
3026
3189
|
);
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
|
|
3190
|
+
|
|
3191
|
+
try {
|
|
3192
|
+
await commands.call(
|
|
3193
|
+
'DeviceFirmwareUpdateRequest',
|
|
3194
|
+
{},
|
|
3195
|
+
{
|
|
3196
|
+
returnAfterWrite: true,
|
|
3197
|
+
expectedTypes: ['Success'],
|
|
3198
|
+
onResponseAfterWrite: response => {
|
|
3199
|
+
if (response.type !== 'Success') return;
|
|
3200
|
+
this.protocolV2InstallTerminalSuccessObserved = true;
|
|
3201
|
+
this.device.clearCancelableAction();
|
|
3202
|
+
Log.log('[FirmwareUpdateV4] firmware install completed by device response');
|
|
3203
|
+
},
|
|
3204
|
+
}
|
|
3205
|
+
);
|
|
3206
|
+
Log.log('[FirmwareUpdateV4] firmware install request written; continue with status polling');
|
|
3207
|
+
} catch (error) {
|
|
3208
|
+
this.throwIfAborted();
|
|
3209
|
+
if (!isProtocolV2DeviceDisconnectedError(error)) {
|
|
3210
|
+
throw error;
|
|
3211
|
+
}
|
|
3212
|
+
Log.log(
|
|
3213
|
+
'[FirmwareUpdateV4] transport released while writing install request; continue status polling'
|
|
3214
|
+
);
|
|
3215
|
+
this.protocolV2InstallNeedsReconnect = true;
|
|
3216
|
+
this.protocolV2InstallDisconnectObserved = !isProtocolV2InstallResponseTimeout(error);
|
|
3217
|
+
}
|
|
3032
3218
|
}
|
|
3033
3219
|
|
|
3034
3220
|
private async protocolV2Reboot(rebootType: DeviceRebootType) {
|
|
3035
3221
|
const typedCall = this.device.getCommands().typedCall.bind(this.device.getCommands());
|
|
3036
3222
|
try {
|
|
3037
|
-
const res = await typedCall(
|
|
3038
|
-
|
|
3039
|
-
|
|
3223
|
+
const res = await typedCall(
|
|
3224
|
+
'DeviceReboot',
|
|
3225
|
+
'Success',
|
|
3226
|
+
{
|
|
3227
|
+
reboot_type: rebootType,
|
|
3228
|
+
},
|
|
3229
|
+
{ timeoutMs: PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT }
|
|
3230
|
+
);
|
|
3040
3231
|
this.device.markProtocolV2Reboot(rebootType);
|
|
3041
3232
|
return res.message;
|
|
3042
3233
|
} catch (error) {
|
|
3043
|
-
if (
|
|
3234
|
+
if (
|
|
3235
|
+
isProtocolV2DeviceDisconnectedError(error) ||
|
|
3236
|
+
isProtocolV2ReconnectProbeError(error) ||
|
|
3237
|
+
isProtocolV2ResponseTimeout(error)
|
|
3238
|
+
) {
|
|
3044
3239
|
this.device.markProtocolV2Reboot(rebootType);
|
|
3045
3240
|
return { message: 'Device rebooted successfully' };
|
|
3046
3241
|
}
|