@onekeyfe/hd-core 1.2.0-alpha.64 → 1.2.0-alpha.66
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 +313 -0
- package/__tests__/DeviceCommands.test.ts +59 -13
- package/__tests__/DeviceEventRegistration.test.ts +6 -0
- package/__tests__/check-all-firmware-release-protocol-v2.test.ts +198 -3
- package/__tests__/connectSettings.test.ts +5 -47
- package/__tests__/core-initialization.test.ts +23 -0
- package/__tests__/device-connector-protocol.test.ts +81 -0
- package/__tests__/device-lifecycle-events.test.ts +105 -3
- package/__tests__/device-pool-state.test.ts +22 -0
- package/__tests__/device-settings.test.ts +39 -18
- package/__tests__/device-state-contract.test.ts +1 -0
- package/__tests__/device-state-mapper.test.ts +13 -1
- package/__tests__/device-utils.test.ts +48 -1
- package/__tests__/deviceSettings.test.ts +11 -1
- package/__tests__/deviceUploadNft.test.ts +316 -0
- package/__tests__/firmware-update/firmware-update-v2-download-before-boot.test.ts +31 -59
- package/__tests__/get-device-state.test.ts +132 -12
- package/__tests__/homescreen.test.ts +41 -1
- package/__tests__/kaspa-use-tweak-pro2.test.ts +2 -2
- package/__tests__/kaspaSignTransaction.test.ts +3 -26
- package/__tests__/logBlockEvent.test.ts +1 -1
- package/__tests__/method-protocol-support.test.ts +19 -0
- package/__tests__/open-wallet-session.test.ts +568 -37
- package/__tests__/pro2Nft.test.ts +108 -0
- package/__tests__/protocol-binding.test.ts +89 -0
- package/__tests__/protocol-v2-resources.test.ts +340 -0
- package/__tests__/protocol-v2-ui-lifecycle.test.ts +91 -0
- package/__tests__/protocol-v2-unlock-policy.test.ts +300 -37
- package/__tests__/protocol-v2.test.ts +1506 -1097
- package/__tests__/protocolV2FileWrite.test.ts +152 -0
- package/__tests__/protocolV2UiInteraction.test.ts +63 -0
- package/__tests__/public-pro2-api-boundary.test.ts +1 -0
- package/__tests__/search-devices.test.ts +12 -3
- package/__tests__/tron-sign-message-init.test.ts +2 -2
- package/__tests__/uiProgressThrottle.test.ts +74 -0
- package/__tests__/uiPromiseRegistry.test.ts +115 -0
- package/dist/api/BaseMethod.d.ts +3 -1
- package/dist/api/BaseMethod.d.ts.map +1 -1
- package/dist/api/CheckAllFirmwareRelease.d.ts +2 -1
- package/dist/api/CheckAllFirmwareRelease.d.ts.map +1 -1
- package/dist/api/DetectDeviceConnectProtocol.d.ts +7 -0
- package/dist/api/DetectDeviceConnectProtocol.d.ts.map +1 -0
- package/dist/api/FirmwareUpdate.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV2.d.ts +0 -11
- package/dist/api/FirmwareUpdateV2.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV3.d.ts +3 -7
- package/dist/api/FirmwareUpdateV3.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV4.d.ts +13 -27
- package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
- package/dist/api/GetPassphraseState.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/allnetwork/AllNetworkGetAddressBase.d.ts.map +1 -1
- package/dist/api/device/DeviceSettings.d.ts.map +1 -1
- package/dist/api/device/DeviceUnlock.d.ts +2 -2
- package/dist/api/device/DeviceUnlock.d.ts.map +1 -1
- package/dist/api/device/DeviceUpdateBootloader.d.ts +1 -5
- package/dist/api/device/DeviceUpdateBootloader.d.ts.map +1 -1
- package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts +4 -11
- package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts.map +1 -1
- package/dist/api/firmware/getBinary.d.ts +0 -7
- package/dist/api/firmware/getBinary.d.ts.map +1 -1
- package/dist/api/firmware/updateBootloader.d.ts +0 -2
- package/dist/api/firmware/updateBootloader.d.ts.map +1 -1
- package/dist/api/firmware/uploadFirmware.d.ts +1 -9
- package/dist/api/firmware/uploadFirmware.d.ts.map +1 -1
- package/dist/api/helpers/protocolV2FileWrite.d.ts +9 -0
- package/dist/api/helpers/protocolV2FileWrite.d.ts.map +1 -1
- package/dist/api/index.d.ts +2 -0
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/kaspa/KaspaGetAddress.d.ts +1 -1
- package/dist/api/kaspa/KaspaSignTransaction.d.ts +1 -1
- package/dist/api/kaspa/KaspaSignTransaction.d.ts.map +1 -1
- package/dist/api/polkadot/PolkadotGetAddress.d.ts +1 -1
- package/dist/api/polkadot/networks.d.ts +1 -1
- package/dist/api/protocol-v2/DeviceUploadNft.d.ts +31 -0
- package/dist/api/protocol-v2/DeviceUploadNft.d.ts.map +1 -0
- package/dist/api/protocol-v2/ProtocolInfoRequest.d.ts.map +1 -1
- package/dist/api/protocol-v2/helpers.d.ts.map +1 -1
- package/dist/api/solana/SolSignMessage.d.ts +1 -1
- package/dist/api/solana/SolSignOffchainMessage.d.ts +1 -1
- package/dist/api/solana/SolSignTransaction.d.ts +2 -2
- package/dist/api/stellar/StellarSignTransaction.d.ts +1 -1
- package/dist/api/sui/SuiGetAddress.d.ts +1 -1
- package/dist/api/sui/SuiGetPublicKey.d.ts +1 -1
- package/dist/api/sui/SuiSignMessage.d.ts +1 -1
- package/dist/api/sui/SuiSignTransaction.d.ts +1 -1
- package/dist/api/ton/TonGetAddress.d.ts +1 -1
- package/dist/api/ton/TonSignMessage.d.ts +1 -1
- package/dist/api/ton/TonSignProof.d.ts +1 -1
- package/dist/api/tron/TronSignMessage.d.ts +2 -2
- package/dist/api/tron/TronSignTransaction.d.ts +1 -1
- package/dist/core/deviceEventRegistration.d.ts +2 -0
- package/dist/core/deviceEventRegistration.d.ts.map +1 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/uiPromiseRegistry.d.ts +6 -0
- package/dist/core/uiPromiseRegistry.d.ts.map +1 -0
- package/dist/data-manager/DataManager.d.ts +9 -4
- package/dist/data-manager/DataManager.d.ts.map +1 -1
- package/dist/data-manager/TransportManager.d.ts.map +1 -1
- package/dist/data-manager/connectSettings.d.ts.map +1 -1
- package/dist/device/Device.d.ts +29 -6
- 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 +2 -1
- package/dist/device/DeviceConnector.d.ts.map +1 -1
- package/dist/device/DevicePool.d.ts.map +1 -1
- package/dist/deviceProfile/protocolV2DeviceIdentity.d.ts.map +1 -1
- package/dist/events/device.d.ts +4 -0
- package/dist/events/device.d.ts.map +1 -1
- package/dist/events/logBlockEvent.d.ts.map +1 -1
- package/dist/events/ui-promise.d.ts +4 -2
- package/dist/events/ui-promise.d.ts.map +1 -1
- package/dist/events/ui-request.d.ts +31 -2
- package/dist/events/ui-request.d.ts.map +1 -1
- package/dist/events/ui-response.d.ts +10 -2
- package/dist/events/ui-response.d.ts.map +1 -1
- package/dist/index.d.ts +243 -248
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3051 -3774
- package/dist/inject.d.ts +6 -1
- package/dist/inject.d.ts.map +1 -1
- package/dist/lowLevelInject.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/features.d.ts +0 -4
- package/dist/protocols/protocol-v2/features.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/firmware.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/resources.d.ts +30 -0
- package/dist/protocols/protocol-v2/resources.d.ts.map +1 -0
- package/dist/protocols/protocol-v2/uiInteraction.d.ts +7 -4
- package/dist/protocols/protocol-v2/uiInteraction.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/unlockPolicy.d.ts +1 -3
- package/dist/protocols/protocol-v2/unlockPolicy.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/unlockPolicyRunner.d.ts +22 -0
- package/dist/protocols/protocol-v2/unlockPolicyRunner.d.ts.map +1 -0
- package/dist/protocols/protocol-v2/walletSession.d.ts +6 -1
- package/dist/protocols/protocol-v2/walletSession.d.ts.map +1 -1
- package/dist/topLevelInject.d.ts.map +1 -1
- package/dist/types/api/checkAllFirmwareRelease.d.ts +3 -5
- package/dist/types/api/checkAllFirmwareRelease.d.ts.map +1 -1
- package/dist/types/api/detectDeviceConnectProtocol.d.ts +4 -0
- package/dist/types/api/detectDeviceConnectProtocol.d.ts.map +1 -0
- package/dist/types/api/deviceUnlock.d.ts +5 -1
- package/dist/types/api/deviceUnlock.d.ts.map +1 -1
- package/dist/types/api/deviceUpdateBootloader.d.ts +0 -6
- package/dist/types/api/deviceUpdateBootloader.d.ts.map +1 -1
- package/dist/types/api/export.d.ts +2 -4
- package/dist/types/api/export.d.ts.map +1 -1
- package/dist/types/api/firmwareUpdate.d.ts +4 -71
- package/dist/types/api/firmwareUpdate.d.ts.map +1 -1
- package/dist/types/api/index.d.ts +7 -10
- package/dist/types/api/index.d.ts.map +1 -1
- package/dist/types/api/protocolV2.d.ts +3 -0
- package/dist/types/api/protocolV2.d.ts.map +1 -1
- package/dist/types/device.d.ts +2 -2
- package/dist/types/device.d.ts.map +1 -1
- package/dist/types/params.d.ts +1 -0
- package/dist/types/params.d.ts.map +1 -1
- package/dist/types/settings.d.ts +38 -21
- package/dist/types/settings.d.ts.map +1 -1
- package/dist/utils/deviceFeaturesCompat.d.ts +0 -1
- package/dist/utils/deviceFeaturesCompat.d.ts.map +1 -1
- package/dist/utils/deviceFeaturesUtils.d.ts +4 -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.map +1 -1
- package/dist/utils/homescreen.d.ts +4 -0
- package/dist/utils/homescreen.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 +31 -0
- package/dist/utils/pro2Nft.d.ts.map +1 -0
- package/dist/utils/pro2Wallpaper.d.ts +10 -0
- package/dist/utils/pro2Wallpaper.d.ts.map +1 -1
- package/dist/utils/uiProgressThrottle.d.ts +3 -0
- package/dist/utils/uiProgressThrottle.d.ts.map +1 -0
- package/package.json +4 -4
- package/src/api/BaseMethod.ts +8 -10
- package/src/api/CheckAllFirmwareRelease.ts +59 -60
- package/src/api/DetectDeviceConnectProtocol.ts +18 -0
- package/src/api/FirmwareUpdate.ts +7 -15
- package/src/api/FirmwareUpdateV2.ts +126 -316
- package/src/api/FirmwareUpdateV3.ts +63 -265
- package/src/api/FirmwareUpdateV4.ts +597 -1005
- package/src/api/GetPassphraseState.ts +7 -1
- package/src/api/OpenWalletSession.ts +71 -17
- package/src/api/SearchDevices.ts +3 -1
- package/src/api/allnetwork/AllNetworkGetAddress.ts +21 -21
- package/src/api/allnetwork/AllNetworkGetAddressBase.ts +36 -11
- package/src/api/device/DeviceSettings.ts +5 -4
- package/src/api/device/DeviceUnlock.ts +8 -3
- package/src/api/device/DeviceUpdateBootloader.ts +6 -125
- package/src/api/firmware/FirmwareUpdateBaseMethod.ts +27 -83
- package/src/api/firmware/updateBootloader.ts +4 -19
- package/src/api/firmware/uploadFirmware.ts +39 -144
- package/src/api/helpers/protocolV2FileWrite.ts +202 -63
- package/src/api/index.ts +2 -0
- package/src/api/kaspa/KaspaGetAddress.ts +1 -1
- package/src/api/kaspa/KaspaSignTransaction.ts +2 -13
- package/src/api/polkadot/networks.ts +3 -3
- package/src/api/protocol-v2/DeviceUploadNft.ts +197 -0
- package/src/api/protocol-v2/ProtocolInfoRequest.ts +3 -1
- package/src/api/protocol-v2/helpers.ts +1 -1
- package/src/api/solana/SolSignMessage.ts +1 -1
- package/src/api/solana/SolSignOffchainMessage.ts +1 -1
- package/src/api/solana/SolSignTransaction.ts +2 -2
- package/src/api/sui/SuiGetAddress.ts +1 -1
- package/src/api/sui/SuiGetPublicKey.ts +1 -1
- package/src/api/sui/SuiSignMessage.ts +1 -1
- package/src/api/sui/SuiSignTransaction.ts +1 -1
- package/src/api/ton/TonGetAddress.ts +1 -1
- package/src/api/ton/TonSignMessage.ts +1 -1
- package/src/api/ton/TonSignProof.ts +1 -1
- package/src/api/tron/TronSignMessage.ts +2 -2
- package/src/api/tron/TronSignTransaction.ts +1 -1
- package/src/core/deviceEventRegistration.ts +4 -0
- package/src/core/index.ts +213 -111
- package/src/core/uiPromiseRegistry.ts +63 -0
- package/src/data/messages/messages-protocol-v2.json +25 -0
- package/src/data-manager/DataManager.ts +113 -41
- package/src/data-manager/TransportManager.ts +6 -0
- package/src/data-manager/connectSettings.ts +0 -11
- package/src/device/Device.ts +274 -49
- package/src/device/DeviceCommands.ts +31 -4
- package/src/device/DeviceConnector.ts +33 -13
- package/src/device/DevicePool.ts +21 -7
- package/src/deviceProfile/buildDeviceFeatures.ts +2 -2
- package/src/deviceProfile/protocolV2DeviceIdentity.ts +3 -0
- package/src/events/device.ts +4 -0
- package/src/events/logBlockEvent.ts +1 -0
- package/src/events/ui-promise.ts +4 -2
- package/src/events/ui-request.ts +36 -2
- package/src/events/ui-response.ts +12 -2
- package/src/index.ts +0 -5
- package/src/inject.ts +60 -24
- package/src/lowLevelInject.ts +7 -8
- package/src/protocols/protocol-v2/features.ts +10 -7
- package/src/protocols/protocol-v2/firmware.ts +2 -0
- package/src/protocols/protocol-v2/resources.ts +390 -0
- package/src/protocols/protocol-v2/uiInteraction.ts +46 -6
- package/src/protocols/protocol-v2/unlockPolicy.ts +1 -105
- package/src/protocols/protocol-v2/unlockPolicyRunner.ts +131 -0
- package/src/protocols/protocol-v2/walletSession.ts +214 -36
- package/src/topLevelInject.ts +7 -8
- package/src/types/api/checkAllFirmwareRelease.ts +3 -5
- package/src/types/api/detectDeviceConnectProtocol.ts +6 -0
- package/src/types/api/deviceUnlock.ts +12 -1
- package/src/types/api/deviceUpdateBootloader.ts +0 -6
- package/src/types/api/export.ts +1 -18
- package/src/types/api/firmwareUpdate.ts +6 -79
- package/src/types/api/index.ts +13 -14
- package/src/types/api/protocolV2.ts +13 -0
- package/src/types/device.ts +15 -3
- package/src/types/params.ts +7 -1
- package/src/types/settings.ts +60 -22
- package/src/utils/deviceFeaturesCompat.ts +5 -6
- package/src/utils/deviceFeaturesUtils.ts +14 -3
- package/src/utils/deviceInfoUtils.ts +2 -0
- package/src/utils/deviceSettings.ts +3 -0
- package/src/utils/homescreen.ts +32 -6
- package/src/utils/index.ts +6 -1
- package/src/utils/pro2Nft.ts +142 -0
- package/src/utils/pro2Wallpaper.ts +35 -8
- package/src/utils/uiProgressThrottle.ts +63 -0
- package/__tests__/device-initialize-timeout.test.ts +0 -56
- package/__tests__/firmware-update/check-all-firmware-release.test.ts +0 -175
- package/__tests__/firmware-update/firmware-artifact-source.test.ts +0 -119
- package/__tests__/firmware-update/firmware-host-binding.test.ts +0 -70
- package/__tests__/firmware-update/firmware-preparation-error.test.ts +0 -27
- package/__tests__/firmware-update/firmware-update-bootloader-poll.test.ts +0 -200
- package/__tests__/firmware-update/firmware-update-capabilities.test.ts +0 -13
- package/__tests__/firmware-update/firmware-update-plan-bootloader-identity.test.ts +0 -394
- package/__tests__/firmware-update/firmware-update-plan.test.ts +0 -593
- package/__tests__/firmware-update/firmware-update-prepared-plan.test.ts +0 -213
- package/__tests__/firmware-update/firmware-update-v3-reconnect.test.ts +0 -58
- package/__tests__/firmware-update/firmware-upload-source.test.ts +0 -70
- package/dist/api/firmware/FirmwareArtifactSource.d.ts +0 -25
- package/dist/api/firmware/FirmwareArtifactSource.d.ts.map +0 -1
- package/dist/api/firmware/FirmwareHostBinding.d.ts +0 -6
- package/dist/api/firmware/FirmwareHostBinding.d.ts.map +0 -1
- package/dist/api/firmware/FirmwarePreparationError.d.ts +0 -3
- package/dist/api/firmware/FirmwarePreparationError.d.ts.map +0 -1
- package/dist/api/firmware/FirmwareUpdateCapabilities.d.ts +0 -3
- package/dist/api/firmware/FirmwareUpdateCapabilities.d.ts.map +0 -1
- package/dist/api/firmware/FirmwareUpdatePlan.d.ts +0 -25
- package/dist/api/firmware/FirmwareUpdatePlan.d.ts.map +0 -1
- package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts +0 -32
- package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts.map +0 -1
- package/dist/api/firmware/progressThrottle.d.ts +0 -3
- package/dist/api/firmware/progressThrottle.d.ts.map +0 -1
- package/dist/protocols/protocol-v2/lockedError.d.ts +0 -2
- package/dist/protocols/protocol-v2/lockedError.d.ts.map +0 -1
- package/dist/protocols/protocol-v2/unlockRetry.d.ts +0 -10
- package/dist/protocols/protocol-v2/unlockRetry.d.ts.map +0 -1
- package/dist/types/api/firmwareUpdateCapabilities.d.ts +0 -9
- package/dist/types/api/firmwareUpdateCapabilities.d.ts.map +0 -1
- package/dist/types/api/firmwareUpdatePlan.d.ts +0 -28
- package/dist/types/api/firmwareUpdatePlan.d.ts.map +0 -1
- package/dist/types/api/firmwareUpdatePreparedPlan.d.ts +0 -42
- package/dist/types/api/firmwareUpdatePreparedPlan.d.ts.map +0 -1
- package/src/api/firmware/FirmwareArtifactSource.ts +0 -278
- package/src/api/firmware/FirmwareHostBinding.ts +0 -100
- package/src/api/firmware/FirmwarePreparationError.ts +0 -12
- package/src/api/firmware/FirmwareUpdateCapabilities.ts +0 -9
- package/src/api/firmware/FirmwareUpdatePlan.ts +0 -772
- package/src/api/firmware/FirmwareUpdatePreparedPlan.ts +0 -449
- package/src/api/firmware/progressThrottle.ts +0 -44
- package/src/protocols/protocol-v2/lockedError.ts +0 -10
- package/src/protocols/protocol-v2/unlockRetry.ts +0 -109
- package/src/types/api/firmwareUpdateCapabilities.ts +0 -9
- package/src/types/api/firmwareUpdatePlan.ts +0 -38
- package/src/types/api/firmwareUpdatePreparedPlan.ts +0 -53
|
@@ -2,13 +2,13 @@ import { EDeviceType, ERRORS, HardwareError, HardwareErrorCode, wait } from '@on
|
|
|
2
2
|
import {
|
|
3
3
|
DeviceRebootType,
|
|
4
4
|
PROTOCOL_V2_BLE_FILE_CHUNK_SIZE,
|
|
5
|
-
PROTOCOL_V2_BLE_FILE_READ_CHUNK_SIZE,
|
|
6
5
|
PROTOCOL_V2_WEBUSB_FILE_CHUNK_SIZE,
|
|
7
6
|
} from '@onekeyfe/hd-transport';
|
|
8
7
|
import { sha256 } from '@noble/hashes/sha256';
|
|
9
8
|
|
|
10
9
|
import { FirmwareUpdateTipMessage, UI_REQUEST } from '../events/ui-request';
|
|
11
10
|
import { validateProtocolV2FilesystemPath } from './helpers/filesystemValidation';
|
|
11
|
+
import { writeProtocolV2File } from './helpers/protocolV2FileWrite';
|
|
12
12
|
import { validateParams } from './helpers/paramsValidator';
|
|
13
13
|
import {
|
|
14
14
|
LoggerNames,
|
|
@@ -19,7 +19,6 @@ import {
|
|
|
19
19
|
getLogger,
|
|
20
20
|
} from '../utils';
|
|
21
21
|
import { getSysResourceBinary } from './firmware/getBinary';
|
|
22
|
-
import { normalizeFirmwarePreparationError } from './firmware/FirmwarePreparationError';
|
|
23
22
|
import { DataManager } from '../data-manager';
|
|
24
23
|
import { FirmwareUpdateBaseMethod } from './firmware/FirmwareUpdateBaseMethod';
|
|
25
24
|
import { DevicePool } from '../device/DevicePool';
|
|
@@ -28,41 +27,40 @@ import {
|
|
|
28
27
|
ProtocolV2FirmwareTargetType,
|
|
29
28
|
} from '../protocols/protocol-v2';
|
|
30
29
|
import { requestProtocolV2DeviceInfo } from '../protocols/protocol-v2/features';
|
|
30
|
+
import {
|
|
31
|
+
PROTOCOL_V2_RESOURCE_DEVICE_PATHS,
|
|
32
|
+
buildProtocolV2ResourceUpdatePlan,
|
|
33
|
+
isProtocolV2ResourceFileValid,
|
|
34
|
+
readProtocolV2ResourceInventory,
|
|
35
|
+
} from '../protocols/protocol-v2/resources';
|
|
31
36
|
import {
|
|
32
37
|
getProtocolV2UnknownErrorText,
|
|
33
38
|
isProtocolV2DeviceDisconnectedError,
|
|
34
39
|
} from './protocol-v2/helpers';
|
|
35
|
-
import { openFirmwareByteSource, writeFirmwareByteSource } from './firmware/FirmwareArtifactSource';
|
|
36
|
-
import { resolveFirmwareUpdateHostBinding } from './firmware/FirmwareHostBinding';
|
|
37
|
-
import {
|
|
38
|
-
assertFirmwareUpdatePreparedPlanBinding,
|
|
39
|
-
assertFirmwareUpdatePreparedPlanDeviceIdentity,
|
|
40
|
-
} from './firmware/FirmwareUpdatePreparedPlan';
|
|
41
40
|
|
|
42
|
-
import type {
|
|
43
|
-
FirmwareArtifactReference,
|
|
44
|
-
FirmwareUpdateV4Params,
|
|
45
|
-
FirmwareUpdateV4Target,
|
|
46
|
-
} from '../types/api/firmwareUpdate';
|
|
41
|
+
import type { FirmwareUpdateV4Params, FirmwareUpdateV4Target } from '../types/api/firmwareUpdate';
|
|
47
42
|
import type { EFirmwareType } from '@onekeyfe/hd-shared';
|
|
48
43
|
import type { ProtocolV2DeviceInfo } from '@onekeyfe/hd-transport';
|
|
44
|
+
import type { PROTO } from '../constants';
|
|
49
45
|
import type { TypedResponseMessage } from '../device/DeviceCommands';
|
|
50
46
|
import type {
|
|
51
47
|
Features,
|
|
52
48
|
IFirmwareReleaseInfo,
|
|
53
49
|
IProtocolV2FirmwareComponent,
|
|
50
|
+
IProtocolV2Resource,
|
|
51
|
+
IProtocolV2ResourceFile,
|
|
54
52
|
IVersionArray,
|
|
55
53
|
} from '../types';
|
|
56
|
-
import type { FirmwareByteSource } from './firmware/FirmwareArtifactSource';
|
|
57
54
|
|
|
58
55
|
const Log = getLogger(LoggerNames.Method);
|
|
59
56
|
|
|
60
|
-
const
|
|
61
|
-
const
|
|
62
|
-
const PROTOCOL_V2_FINAL_RECONNECT_TIMEOUT = 2 * 60 * 1000;
|
|
57
|
+
const PROTOCOL_V2_BOOTLOADER_RECONNECT_TIMEOUT = 90 * 1000;
|
|
58
|
+
const PROTOCOL_V2_FINAL_RECONNECT_TIMEOUT = 3 * 60 * 1000;
|
|
63
59
|
const PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT = 5 * 1000;
|
|
60
|
+
const PROTOCOL_V2_FIRMWARE_STATUS_RESPONSE_TIMEOUT = 15 * 1000;
|
|
64
61
|
const PROTOCOL_V2_START_UPDATE_TIMEOUT = 3 * 60 * 1000;
|
|
65
|
-
const PROTOCOL_V2_INSTALL_TIMEOUT =
|
|
62
|
+
const PROTOCOL_V2_INSTALL_TIMEOUT = 8 * 60 * 1000;
|
|
63
|
+
const PROTOCOL_V2_MISSING_TARGET_STATUS_GRACE_TIMEOUT = 30 * 1000;
|
|
66
64
|
const PROTOCOL_V2_TARGET_STATUS_PENDING = 0;
|
|
67
65
|
const PROTOCOL_V2_TARGET_STATUS_IN_PROGRESS = 1;
|
|
68
66
|
const PROTOCOL_V2_TARGET_STATUS_FINISHED = 2;
|
|
@@ -73,7 +71,7 @@ const PROTOCOL_V2_MIN_FILE_CHUNK_SIZE = 64;
|
|
|
73
71
|
const PROTOCOL_V2_CONNECT_RETRY_COUNT = 10;
|
|
74
72
|
const PROTOCOL_V2_CONNECT_POLL_INTERVAL = 500;
|
|
75
73
|
const PROTOCOL_V2_CONNECT_SINGLE_TIMEOUT = 75 * 1000;
|
|
76
|
-
const PROTOCOL_V2_DEVICE_INFO_READY_TIMEOUT =
|
|
74
|
+
const PROTOCOL_V2_DEVICE_INFO_READY_TIMEOUT = 30 * 1000;
|
|
77
75
|
const PROTOCOL_V2_FILE_TRANSFER_RETRY_COUNT = 3;
|
|
78
76
|
const PROTOCOL_V2_OKPP_HEADER_SIZE = 0x52a0;
|
|
79
77
|
const PROTOCOL_V2_OKPP_PAYLOAD_HASH_OFFSET = 0x200;
|
|
@@ -97,6 +95,11 @@ const getProtocolV2DeviceTransferProgress = (
|
|
|
97
95
|
return Math.min(Math.max(Math.ceil((bytesAfterChunk / totalBytes) * 100), 1), 99);
|
|
98
96
|
};
|
|
99
97
|
|
|
98
|
+
const formatProtocolV2TransferSpeed = (bytes: number, elapsedMs: number) => {
|
|
99
|
+
const safeElapsedMs = Math.max(elapsedMs, 1);
|
|
100
|
+
return (bytes / 1024 / (safeElapsedMs / 1000)).toFixed(2);
|
|
101
|
+
};
|
|
102
|
+
|
|
100
103
|
type ProtocolV2FirmwareUpdateStatusTarget = {
|
|
101
104
|
target_id: number | string;
|
|
102
105
|
status?: number | string;
|
|
@@ -110,16 +113,22 @@ type ProtocolV2TargetBinary = { fileName: string; binary: ArrayBuffer; targetId:
|
|
|
110
113
|
type ProtocolV2InstallItem = ProtocolV2TargetBinary & {
|
|
111
114
|
kind: ProtocolV2RemoteComponentTarget['kind'];
|
|
112
115
|
};
|
|
113
|
-
|
|
116
|
+
type ProtocolV2InstallTarget = ProtocolV2InstallItem & {
|
|
117
|
+
path: string;
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
type ProtocolV2FileTransferParams = PROTO.FirmwareUpload & {
|
|
121
|
+
filePath: string;
|
|
122
|
+
processedSize?: number;
|
|
123
|
+
totalSize?: number;
|
|
124
|
+
onTransferredBytes?: (transferredBytes: number) => void;
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
/** Protocol V2 resource file written independently to devicePath through FileWrite. */
|
|
114
128
|
type ProtocolV2ResourceBundleBinary = {
|
|
115
129
|
name: string;
|
|
116
130
|
binary: ArrayBuffer;
|
|
117
131
|
devicePath: string;
|
|
118
|
-
/** Download URL for remote-config mode; omitted in manual mode. */
|
|
119
|
-
url?: string;
|
|
120
|
-
version?: IVersionArray;
|
|
121
|
-
payloadHash?: string;
|
|
122
|
-
headerHash?: string;
|
|
123
132
|
};
|
|
124
133
|
|
|
125
134
|
type ProtocolV2RemoteComponentBinary = ProtocolV2RemoteComponentTarget & {
|
|
@@ -132,35 +141,6 @@ type ProtocolV2RemoteComponentTarget = {
|
|
|
132
141
|
kind: 'bootloader' | 'firmware';
|
|
133
142
|
};
|
|
134
143
|
|
|
135
|
-
type ProtocolV2InstallSource = {
|
|
136
|
-
fileName: string;
|
|
137
|
-
source: FirmwareByteSource;
|
|
138
|
-
targetId: number;
|
|
139
|
-
kind: ProtocolV2RemoteComponentTarget['kind'];
|
|
140
|
-
};
|
|
141
|
-
|
|
142
|
-
type ProtocolV2ResourceBundleSource = {
|
|
143
|
-
name: string;
|
|
144
|
-
source: FirmwareByteSource;
|
|
145
|
-
devicePath: string;
|
|
146
|
-
version?: IVersionArray;
|
|
147
|
-
payloadHash?: string;
|
|
148
|
-
headerHash?: string;
|
|
149
|
-
};
|
|
150
|
-
|
|
151
|
-
type ProtocolV2ExecutionPhaseKind =
|
|
152
|
-
| 'resource-sync'
|
|
153
|
-
| 'bootloader-install'
|
|
154
|
-
| 'bootloader-verify'
|
|
155
|
-
| 'component-install'
|
|
156
|
-
| 'final-verify';
|
|
157
|
-
|
|
158
|
-
type ProtocolV2ExecutionPhase = {
|
|
159
|
-
kind: ProtocolV2ExecutionPhaseKind;
|
|
160
|
-
installSources: ProtocolV2InstallSource[];
|
|
161
|
-
resourceSources: ProtocolV2ResourceBundleSource[];
|
|
162
|
-
};
|
|
163
|
-
|
|
164
144
|
type ProtocolV2OkppHeader = {
|
|
165
145
|
type: string;
|
|
166
146
|
version: IVersionArray;
|
|
@@ -300,18 +280,6 @@ const normalizeProtocolV2TargetStatus = (
|
|
|
300
280
|
|
|
301
281
|
const normalizeProtocolV2Hex = (value?: string) => value?.replace(/^0x/i, '').toLowerCase();
|
|
302
282
|
|
|
303
|
-
const versionArrayToNumber = (version?: IVersionArray) => {
|
|
304
|
-
if (!version) return undefined;
|
|
305
|
-
return version[0] * 0x10000 + version[1] * 0x100 + version[2];
|
|
306
|
-
};
|
|
307
|
-
|
|
308
|
-
const compareProtocolV2Versions = (current?: IVersionArray, target?: IVersionArray) => {
|
|
309
|
-
const currentNumber = versionArrayToNumber(current);
|
|
310
|
-
const targetNumber = versionArrayToNumber(target);
|
|
311
|
-
if (currentNumber === undefined || targetNumber === undefined) return undefined;
|
|
312
|
-
return currentNumber - targetNumber;
|
|
313
|
-
};
|
|
314
|
-
|
|
315
283
|
const bytesToHex = (bytes: Uint8Array) =>
|
|
316
284
|
Array.from(bytes)
|
|
317
285
|
.map(byte => byte.toString(16).padStart(2, '0'))
|
|
@@ -402,20 +370,30 @@ export const isProtocolV2FirmwareFingerprintValid = (
|
|
|
402
370
|
|
|
403
371
|
export const assertProtocolV2ReconnectIdentity = (
|
|
404
372
|
expectedSerialNumber?: string,
|
|
405
|
-
actualSerialNumber?: string
|
|
373
|
+
actualSerialNumber?: string,
|
|
374
|
+
expectedPath?: string,
|
|
375
|
+
actualPath?: string
|
|
406
376
|
) => {
|
|
407
|
-
if (
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
377
|
+
if (expectedSerialNumber && actualSerialNumber) {
|
|
378
|
+
if (actualSerialNumber !== expectedSerialNumber) {
|
|
379
|
+
throw ERRORS.TypedError(
|
|
380
|
+
HardwareErrorCode.DeviceNotFound,
|
|
381
|
+
`Protocol V2 reconnect physical identity mismatch: expected ${expectedSerialNumber}, received ${actualSerialNumber}`
|
|
382
|
+
);
|
|
383
|
+
}
|
|
384
|
+
return;
|
|
412
385
|
}
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
`Protocol V2 reconnect physical identity mismatch: expected ${expectedSerialNumber}, received ${actualSerialNumber}`
|
|
417
|
-
);
|
|
386
|
+
|
|
387
|
+
if (expectedPath && actualPath && expectedPath === actualPath) {
|
|
388
|
+
return;
|
|
418
389
|
}
|
|
390
|
+
|
|
391
|
+
throw ERRORS.TypedError(
|
|
392
|
+
HardwareErrorCode.DeviceNotFound,
|
|
393
|
+
`Protocol V2 reconnect physical identity unavailable: expected path ${
|
|
394
|
+
expectedPath ?? 'unknown'
|
|
395
|
+
}, received ${actualPath ?? 'unknown'}`
|
|
396
|
+
);
|
|
419
397
|
};
|
|
420
398
|
|
|
421
399
|
/**
|
|
@@ -429,20 +407,12 @@ export const assertProtocolV2ReconnectIdentity = (
|
|
|
429
407
|
export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareUpdateV4Params> {
|
|
430
408
|
private protocolV2ExpectedSerialNumber?: string;
|
|
431
409
|
|
|
410
|
+
private protocolV2ExpectedPath?: string;
|
|
411
|
+
|
|
432
412
|
getSupportedProtocols() {
|
|
433
413
|
return ['V2'] as const;
|
|
434
414
|
}
|
|
435
415
|
|
|
436
|
-
private protocolV2PreparedSources: FirmwareByteSource[] = [];
|
|
437
|
-
|
|
438
|
-
private protocolV2ExecutionInLoader = false;
|
|
439
|
-
|
|
440
|
-
private protocolV2CompletedTargetVersions = new Map<number, number>();
|
|
441
|
-
|
|
442
|
-
private protocolV2LatestFinalFeatures?: Features;
|
|
443
|
-
|
|
444
|
-
private protocolV2FinalStatusVerified = false;
|
|
445
|
-
|
|
446
416
|
init() {
|
|
447
417
|
this.allowDeviceMode = [UI_REQUEST.BOOTLOADER, UI_REQUEST.NOT_INITIALIZE];
|
|
448
418
|
this.requireDeviceMode = [];
|
|
@@ -480,62 +450,10 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
480
450
|
{ name: 'firmwareType', type: 'string' },
|
|
481
451
|
{ name: 'targetsToUpdate', type: 'array', allowEmpty: true },
|
|
482
452
|
{ name: 'platform', type: 'string' },
|
|
483
|
-
{ name: '
|
|
484
|
-
{ name: 'resourceBundleFiles', type: 'array', allowEmpty: true },
|
|
453
|
+
{ name: 'resourceFiles', type: 'array', allowEmpty: true },
|
|
485
454
|
]);
|
|
486
|
-
if (
|
|
487
|
-
payload.expectedDeviceId !== undefined &&
|
|
488
|
-
(payload.expectedDeviceId.length === 0 || payload.expectedDeviceId.length > 160)
|
|
489
|
-
) {
|
|
490
|
-
throw ERRORS.TypedError(
|
|
491
|
-
HardwareErrorCode.CallMethodInvalidParameter,
|
|
492
|
-
'Protocol V2 expected device identity is invalid'
|
|
493
|
-
);
|
|
494
|
-
}
|
|
495
|
-
const hostBinding =
|
|
496
|
-
payload.preparedPlan || payload.componentArtifacts || payload.resourceBundleArtifacts
|
|
497
|
-
? resolveFirmwareUpdateHostBinding(payload.hostBindingGeneration)
|
|
498
|
-
: undefined;
|
|
499
|
-
if (hostBinding) {
|
|
500
|
-
assertFirmwareUpdatePreparedPlanBinding({
|
|
501
|
-
preparedPlan: payload.preparedPlan,
|
|
502
|
-
executor: 'v4',
|
|
503
|
-
platform: payload.platform,
|
|
504
|
-
scopeTargets: [
|
|
505
|
-
'boot',
|
|
506
|
-
'app_v1',
|
|
507
|
-
'app_v2',
|
|
508
|
-
'coprocessor',
|
|
509
|
-
'resource',
|
|
510
|
-
'se01',
|
|
511
|
-
'se02',
|
|
512
|
-
'se03',
|
|
513
|
-
'se04',
|
|
514
|
-
],
|
|
515
|
-
bindings: [
|
|
516
|
-
...Object.entries(payload.componentArtifacts ?? {}).flatMap(([target, artifact]) =>
|
|
517
|
-
artifact
|
|
518
|
-
? [
|
|
519
|
-
{
|
|
520
|
-
target: target as Exclude<FirmwareUpdateV4Target, 'resource'>,
|
|
521
|
-
artifact,
|
|
522
|
-
},
|
|
523
|
-
]
|
|
524
|
-
: []
|
|
525
|
-
),
|
|
526
|
-
...(payload.resourceBundleArtifacts ?? []).map(
|
|
527
|
-
(entry: { name: string; artifact: FirmwareArtifactReference }) => ({
|
|
528
|
-
target: 'resource' as const,
|
|
529
|
-
logicalName: entry.name,
|
|
530
|
-
artifact: entry.artifact,
|
|
531
|
-
})
|
|
532
|
-
),
|
|
533
|
-
],
|
|
534
|
-
});
|
|
535
|
-
}
|
|
536
455
|
|
|
537
456
|
this.params = {
|
|
538
|
-
preparedPlan: payload.preparedPlan,
|
|
539
457
|
chunkSize: payload.chunkSize,
|
|
540
458
|
forcedUpdateRes: payload.forcedUpdateRes,
|
|
541
459
|
bootloaderBinary: payload.bootloaderBinary,
|
|
@@ -547,28 +465,20 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
547
465
|
se02Binary: payload.se02Binary,
|
|
548
466
|
se03Binary: payload.se03Binary,
|
|
549
467
|
se04Binary: payload.se04Binary,
|
|
550
|
-
|
|
468
|
+
resourceFiles: payload.resourceFiles,
|
|
551
469
|
firmwareType: payload.firmwareType,
|
|
552
470
|
targetsToUpdate: payload.targetsToUpdate,
|
|
553
|
-
expectedTargetVersions: payload.expectedTargetVersions,
|
|
554
471
|
platform: payload.platform,
|
|
555
|
-
expectedDeviceId: payload.expectedDeviceId,
|
|
556
|
-
artifactReader: hostBinding?.artifactReader ?? payload.artifactReader,
|
|
557
|
-
componentArtifacts: payload.componentArtifacts,
|
|
558
|
-
resourceBundleArtifacts: payload.resourceBundleArtifacts,
|
|
559
472
|
};
|
|
560
473
|
}
|
|
561
474
|
|
|
562
|
-
private getProtocolV2FirmwareChunkSize(
|
|
475
|
+
private getProtocolV2FirmwareChunkSize() {
|
|
563
476
|
const payloadChunkSize = Number(this.params?.chunkSize);
|
|
564
477
|
const env = DataManager.getSettings('env');
|
|
565
478
|
const isBle = this.params?.platform === 'native' || (env && DataManager.isBleConnect(env));
|
|
566
479
|
let maxChunkSize = PROTOCOL_V2_WEBUSB_FILE_CHUNK_SIZE;
|
|
567
480
|
if (isBle) {
|
|
568
|
-
maxChunkSize =
|
|
569
|
-
direction === 'read'
|
|
570
|
-
? PROTOCOL_V2_BLE_FILE_READ_CHUNK_SIZE
|
|
571
|
-
: PROTOCOL_V2_BLE_FILE_CHUNK_SIZE;
|
|
481
|
+
maxChunkSize = PROTOCOL_V2_BLE_FILE_CHUNK_SIZE;
|
|
572
482
|
}
|
|
573
483
|
if (!Number.isFinite(payloadChunkSize) || payloadChunkSize <= 0) {
|
|
574
484
|
return maxChunkSize;
|
|
@@ -589,14 +499,8 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
589
499
|
const deviceFeatures = await this.getProtocolV2DeviceFeatures();
|
|
590
500
|
const deviceFirmwareType = getFirmwareType(deviceFeatures);
|
|
591
501
|
const firmwareType = this.params.firmwareType ?? deviceFirmwareType;
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
if (
|
|
595
|
-
(this.params.componentArtifacts && Object.keys(this.params.componentArtifacts).length > 0) ||
|
|
596
|
-
this.params.resourceBundleArtifacts?.length
|
|
597
|
-
) {
|
|
598
|
-
return this.runProtocolV2PreparedArtifacts(deviceFeatures, firmwareType);
|
|
599
|
-
}
|
|
502
|
+
const needsRemoteResources =
|
|
503
|
+
!this.params.resourceFiles?.length && !!this.params.targetsToUpdate?.includes('resource');
|
|
600
504
|
|
|
601
505
|
let fwBinaryMap: ProtocolV2TargetBinary[] = [];
|
|
602
506
|
let bootloaderBinary: ArrayBuffer | null = null;
|
|
@@ -604,21 +508,18 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
604
508
|
let resourceBundles: ProtocolV2ResourceBundleBinary[] | undefined;
|
|
605
509
|
try {
|
|
606
510
|
this.postTipMessage(FirmwareUpdateTipMessage.StartDownloadFirmware);
|
|
511
|
+
resourceBundles = this.prepareExplicitProtocolV2ResourceFiles();
|
|
607
512
|
fwBinaryMap = this.collectExplicitTargetBinaries();
|
|
608
513
|
bootloaderBinary = this.prepareBootloaderBinary();
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
firmwareUpdateCode: 'FirmwareArtifactsNotPrepared',
|
|
619
|
-
}
|
|
620
|
-
);
|
|
621
|
-
}
|
|
514
|
+
const needsRemoteFirmware = !this.hasExplicitProtocolV2Payload(fwBinaryMap);
|
|
515
|
+
const needsRemoteBootResources = !!this.params.targetsToUpdate?.includes('boot_resources');
|
|
516
|
+
if (needsRemoteFirmware || needsRemoteResources || needsRemoteBootResources) {
|
|
517
|
+
// Remote updates must use a freshly fetched config before any reboot or file write.
|
|
518
|
+
await DataManager.forceReloadData({
|
|
519
|
+
requireResources: needsRemoteResources || needsRemoteBootResources,
|
|
520
|
+
});
|
|
521
|
+
}
|
|
522
|
+
if (needsRemoteFirmware) {
|
|
622
523
|
const remoteBinaries = await this.prepareRemoteProtocolV2Binaries(
|
|
623
524
|
firmwareType,
|
|
624
525
|
deviceFeatures
|
|
@@ -627,308 +528,70 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
627
528
|
fwBinaryMap = remoteBinaries.fwBinaryMap;
|
|
628
529
|
installItems = remoteBinaries.installItems;
|
|
629
530
|
}
|
|
630
|
-
const
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
this.postTipMessage(FirmwareUpdateTipMessage.FinishDownloadFirmware);
|
|
531
|
+
const bootResourceFiles = await this.prepareProtocolV2BootResources();
|
|
532
|
+
if (bootResourceFiles?.length) {
|
|
533
|
+
resourceBundles = [...(resourceBundles ?? []), ...bootResourceFiles];
|
|
534
|
+
}
|
|
535
|
+
if (!needsRemoteResources) {
|
|
536
|
+
this.postTipMessage(FirmwareUpdateTipMessage.FinishDownloadFirmware);
|
|
537
|
+
}
|
|
638
538
|
} catch (err) {
|
|
639
|
-
|
|
539
|
+
if (err instanceof HardwareError && err.errorCode === HardwareErrorCode.NetworkError) {
|
|
540
|
+
throw err;
|
|
541
|
+
}
|
|
542
|
+
throw ERRORS.TypedError(HardwareErrorCode.FirmwareUpdateDownloadFailed, err.message ?? err);
|
|
640
543
|
}
|
|
641
544
|
|
|
642
|
-
if (
|
|
545
|
+
if (
|
|
546
|
+
!bootloaderBinary &&
|
|
547
|
+
fwBinaryMap.length === 0 &&
|
|
548
|
+
!installItems?.length &&
|
|
549
|
+
!resourceBundles?.length &&
|
|
550
|
+
!needsRemoteResources
|
|
551
|
+
) {
|
|
643
552
|
throw ERRORS.TypedError(
|
|
644
553
|
HardwareErrorCode.FirmwareUpdateDownloadFailed,
|
|
645
554
|
'No firmware to update'
|
|
646
555
|
);
|
|
647
556
|
}
|
|
648
557
|
|
|
649
|
-
|
|
650
|
-
fwBinaryMap,
|
|
651
|
-
bootloaderBinary,
|
|
652
|
-
...(installItems ? { installItems } : undefined),
|
|
653
|
-
...(resourceBundles?.length ? { resourceBundles } : undefined),
|
|
654
|
-
});
|
|
655
|
-
}
|
|
656
|
-
|
|
657
|
-
private async openProtocolV2PreparedSource(artifact: FirmwareArtifactReference) {
|
|
658
|
-
const source = await openFirmwareByteSource({
|
|
659
|
-
artifact,
|
|
660
|
-
reader: this.params.artifactReader,
|
|
661
|
-
});
|
|
662
|
-
if (!source) {
|
|
663
|
-
throw ERRORS.TypedError(
|
|
664
|
-
HardwareErrorCode.RuntimeError,
|
|
665
|
-
`Firmware artifact ${artifact.artifactRef} is not prepared`,
|
|
666
|
-
{
|
|
667
|
-
firmwareUpdateCode: 'FirmwareArtifactsNotPrepared',
|
|
668
|
-
}
|
|
669
|
-
);
|
|
670
|
-
}
|
|
671
|
-
this.protocolV2PreparedSources.push(source);
|
|
672
|
-
return source;
|
|
673
|
-
}
|
|
674
|
-
|
|
675
|
-
private async openProtocolV2MemorySource(binary: ArrayBuffer) {
|
|
676
|
-
const source = await openFirmwareByteSource({ binary });
|
|
677
|
-
if (!source) {
|
|
678
|
-
throw ERRORS.TypedError(
|
|
679
|
-
HardwareErrorCode.RuntimeError,
|
|
680
|
-
'Protocol V2 firmware binary is empty'
|
|
681
|
-
);
|
|
682
|
-
}
|
|
683
|
-
this.protocolV2PreparedSources.push(source);
|
|
684
|
-
return source;
|
|
685
|
-
}
|
|
686
|
-
|
|
687
|
-
private async closeProtocolV2PreparedSources() {
|
|
688
|
-
const sources = this.protocolV2PreparedSources.splice(0);
|
|
689
|
-
await Promise.all(sources.map(source => source.close().catch(() => undefined)));
|
|
690
|
-
}
|
|
558
|
+
const enteredBootloader = await this.enterProtocolV2BootloaderMode();
|
|
691
559
|
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
);
|
|
707
|
-
const installSources: ProtocolV2InstallSource[] = [];
|
|
708
|
-
const preparedTargets = new Set<FirmwareUpdateV4Target>();
|
|
709
|
-
|
|
710
|
-
for (const [key, component] of this.getRemoteComponentEntries(release)) {
|
|
711
|
-
const target = this.getRemoteComponentTarget(key, component);
|
|
712
|
-
const updateTarget = PROTOCOL_V2_UPDATE_TARGET_BY_TARGET_ID.get(target.targetId);
|
|
713
|
-
if (updateTarget && updateTarget !== 'resource' && requestedTargets.has(updateTarget)) {
|
|
714
|
-
const artifact = componentArtifacts[updateTarget];
|
|
715
|
-
if (!artifact) {
|
|
716
|
-
throw ERRORS.TypedError(
|
|
717
|
-
HardwareErrorCode.RuntimeError,
|
|
718
|
-
`Protocol V2 ${updateTarget} artifact is not prepared`,
|
|
719
|
-
{
|
|
720
|
-
firmwareUpdateCode: 'FirmwareArtifactsNotPrepared',
|
|
721
|
-
artifactName: updateTarget,
|
|
722
|
-
}
|
|
723
|
-
);
|
|
724
|
-
}
|
|
725
|
-
installSources.push({
|
|
726
|
-
...target,
|
|
727
|
-
source: await this.openProtocolV2PreparedSource(artifact),
|
|
728
|
-
});
|
|
729
|
-
preparedTargets.add(updateTarget);
|
|
730
|
-
}
|
|
731
|
-
}
|
|
732
|
-
|
|
733
|
-
const unknownTarget = Array.from(requestedTargets).find(
|
|
734
|
-
target => target !== 'resource' && !preparedTargets.has(target)
|
|
735
|
-
);
|
|
736
|
-
if (unknownTarget) {
|
|
737
|
-
throw ERRORS.TypedError(
|
|
738
|
-
HardwareErrorCode.RuntimeError,
|
|
739
|
-
`Protocol V2 release does not contain requested target ${unknownTarget}`
|
|
740
|
-
);
|
|
741
|
-
}
|
|
742
|
-
return installSources;
|
|
743
|
-
}
|
|
744
|
-
|
|
745
|
-
private async prepareProtocolV2ResourceSources(
|
|
746
|
-
firmwareType: EFirmwareType,
|
|
747
|
-
features: Features
|
|
748
|
-
): Promise<ProtocolV2ResourceBundleSource[]> {
|
|
749
|
-
const preparedArtifacts = this.params.resourceBundleArtifacts ?? [];
|
|
750
|
-
const resourceRequested =
|
|
751
|
-
this.params.targetsToUpdate?.includes('resource') || preparedArtifacts.length > 0;
|
|
752
|
-
if (!resourceRequested) {
|
|
753
|
-
return [];
|
|
754
|
-
}
|
|
755
|
-
const release = DataManager.getFirmwareLatestRelease(features, firmwareType);
|
|
756
|
-
const descriptors = release?.resourceBundles ?? [];
|
|
757
|
-
const artifactByName = new Map(
|
|
758
|
-
preparedArtifacts.map(item => [item.name, item.artifact] as const)
|
|
759
|
-
);
|
|
760
|
-
const sources: ProtocolV2ResourceBundleSource[] = [];
|
|
761
|
-
for (const descriptor of descriptors) {
|
|
762
|
-
const artifact = artifactByName.get(descriptor.name);
|
|
763
|
-
if (!artifact) {
|
|
764
|
-
throw ERRORS.TypedError(
|
|
765
|
-
HardwareErrorCode.RuntimeError,
|
|
766
|
-
`Protocol V2 resource bundle ${descriptor.name} is not prepared`,
|
|
767
|
-
{
|
|
768
|
-
firmwareUpdateCode: 'FirmwareArtifactsNotPrepared',
|
|
769
|
-
artifactName: descriptor.name,
|
|
560
|
+
if (needsRemoteResources) {
|
|
561
|
+
try {
|
|
562
|
+
const stableResources = await this.prepareProtocolV2ResourceBundles();
|
|
563
|
+
resourceBundles = [...(resourceBundles ?? []), ...(stableResources ?? [])];
|
|
564
|
+
this.postTipMessage(FirmwareUpdateTipMessage.FinishDownloadFirmware);
|
|
565
|
+
} catch (err) {
|
|
566
|
+
if (enteredBootloader) {
|
|
567
|
+
try {
|
|
568
|
+
await this.exitProtocolV2BootloaderToNormal();
|
|
569
|
+
} catch (restoreError) {
|
|
570
|
+
Log.warn(
|
|
571
|
+
'[FirmwareUpdateV4] failed to restore App mode after resource preparation error:',
|
|
572
|
+
restoreError
|
|
573
|
+
);
|
|
770
574
|
}
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
const devicePath = validateProtocolV2FilesystemPath(
|
|
774
|
-
descriptor.devicePath,
|
|
775
|
-
'resourceBundles[].devicePath'
|
|
776
|
-
);
|
|
777
|
-
sources.push({
|
|
778
|
-
name: descriptor.name,
|
|
779
|
-
source: await this.openProtocolV2PreparedSource(artifact),
|
|
780
|
-
devicePath,
|
|
781
|
-
version: descriptor.version,
|
|
782
|
-
payloadHash: descriptor.payloadHash,
|
|
783
|
-
headerHash: descriptor.headerHash,
|
|
784
|
-
});
|
|
785
|
-
artifactByName.delete(descriptor.name);
|
|
786
|
-
}
|
|
787
|
-
if (artifactByName.size > 0) {
|
|
788
|
-
throw ERRORS.TypedError(
|
|
789
|
-
HardwareErrorCode.RuntimeError,
|
|
790
|
-
`Protocol V2 release does not contain resource bundle ${artifactByName.keys().next().value}`
|
|
791
|
-
);
|
|
792
|
-
}
|
|
793
|
-
return sources;
|
|
794
|
-
}
|
|
795
|
-
|
|
796
|
-
private async runProtocolV2PreparedArtifacts(features: Features, firmwareType: EFirmwareType) {
|
|
797
|
-
try {
|
|
798
|
-
this.postTipMessage(FirmwareUpdateTipMessage.StartDownloadFirmware);
|
|
799
|
-
const installSources = await this.prepareProtocolV2InstallSources(firmwareType, features);
|
|
800
|
-
const resourceSources = await this.prepareProtocolV2ResourceSources(firmwareType, features);
|
|
801
|
-
if (installSources.length === 0 && resourceSources.length === 0) {
|
|
802
|
-
throw ERRORS.TypedError(
|
|
803
|
-
HardwareErrorCode.FirmwareUpdateDownloadFailed,
|
|
804
|
-
'No firmware to update'
|
|
805
|
-
);
|
|
575
|
+
}
|
|
576
|
+
throw ERRORS.TypedError(HardwareErrorCode.FirmwareUpdateDownloadFailed, err.message ?? err);
|
|
806
577
|
}
|
|
807
|
-
this.postTipMessage(FirmwareUpdateTipMessage.FinishDownloadFirmware);
|
|
808
|
-
|
|
809
|
-
return await this.executeProtocolV2SourceUpdate({
|
|
810
|
-
installSources,
|
|
811
|
-
resourceSources,
|
|
812
|
-
});
|
|
813
|
-
} finally {
|
|
814
|
-
await this.closeProtocolV2PreparedSources();
|
|
815
578
|
}
|
|
816
|
-
}
|
|
817
579
|
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
'Protocol V2 expected target versions are invalid'
|
|
825
|
-
);
|
|
826
|
-
}
|
|
827
|
-
for (const [target, version] of Object.entries(expected)) {
|
|
828
|
-
if (
|
|
829
|
-
!Array.from(PROTOCOL_V2_UPDATE_TARGET_BY_TARGET_ID.values()).includes(
|
|
830
|
-
target as FirmwareUpdateV4Target
|
|
831
|
-
) ||
|
|
832
|
-
typeof version !== 'string' ||
|
|
833
|
-
!/^\d+\.\d+\.\d+(?:[-+][A-Za-z0-9.-]+)?$/u.test(version) ||
|
|
834
|
-
version.length > 64
|
|
835
|
-
) {
|
|
836
|
-
throw ERRORS.TypedError(
|
|
837
|
-
HardwareErrorCode.RuntimeError,
|
|
838
|
-
'Protocol V2 expected target version is invalid'
|
|
839
|
-
);
|
|
840
|
-
}
|
|
841
|
-
}
|
|
842
|
-
}
|
|
580
|
+
await this.executeProtocolV2Update({
|
|
581
|
+
fwBinaryMap,
|
|
582
|
+
bootloaderBinary,
|
|
583
|
+
...(installItems ? { installItems } : undefined),
|
|
584
|
+
...(resourceBundles?.length ? { resourceBundles } : undefined),
|
|
585
|
+
});
|
|
843
586
|
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
const version = target ? this.params?.expectedTargetVersions?.[target] : undefined;
|
|
847
|
-
if (!version) return undefined;
|
|
848
|
-
const parts = version.split(/[+-]/u, 1)[0].split('.').map(Number);
|
|
849
|
-
if (
|
|
850
|
-
parts.length !== 3 ||
|
|
851
|
-
parts.some(part => !Number.isSafeInteger(part) || part < 0 || part > 0xff)
|
|
852
|
-
) {
|
|
853
|
-
return undefined;
|
|
854
|
-
}
|
|
855
|
-
return parts[0] * 0x10000 + parts[1] * 0x100 + parts[2];
|
|
856
|
-
}
|
|
587
|
+
this.postTipMessage(FirmwareUpdateTipMessage.SwitchFirmwareReconnectDevice);
|
|
588
|
+
await this.exitProtocolV2BootloaderToNormal();
|
|
857
589
|
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
const features = this.protocolV2LatestFinalFeatures;
|
|
862
|
-
const requestedTargets = this.getProtocolV2RequestedTargets();
|
|
863
|
-
const applicationTargets = requestedTargets.filter(
|
|
864
|
-
target => target === 'app_v1' || target === 'app_v2'
|
|
865
|
-
);
|
|
866
|
-
const visibleVersions: Partial<Record<FirmwareUpdateV4Target, string>> = {
|
|
867
|
-
boot: features ? getDeviceBootloaderVersion(features).join('.') : undefined,
|
|
868
|
-
coprocessor: features ? getDeviceBLEFirmwareVersion(features).join('.') : undefined,
|
|
869
|
-
se01: features?.se01Version ?? undefined,
|
|
870
|
-
se02: features?.se02Version ?? undefined,
|
|
871
|
-
se03: features?.se03Version ?? undefined,
|
|
872
|
-
se04: features?.se04Version ?? undefined,
|
|
873
|
-
};
|
|
874
|
-
if (features && applicationTargets.length === 1) {
|
|
875
|
-
visibleVersions[applicationTargets[0]] = getDeviceFirmwareVersion(features).join('.');
|
|
876
|
-
}
|
|
877
|
-
const expectedEntries = (
|
|
878
|
-
Object.entries(expected) as Array<[FirmwareUpdateV4Target, string]>
|
|
879
|
-
).filter(([target]) => !targets || targets.includes(target));
|
|
880
|
-
for (const [target, expectedVersion] of expectedEntries) {
|
|
881
|
-
const targetId = Array.from(PROTOCOL_V2_UPDATE_TARGET_BY_TARGET_ID.entries()).find(
|
|
882
|
-
([, mappedTarget]) => mappedTarget === target
|
|
883
|
-
)?.[0];
|
|
884
|
-
const statusVersion =
|
|
885
|
-
targetId === undefined ? undefined : this.protocolV2CompletedTargetVersions.get(targetId);
|
|
886
|
-
const observedVersion =
|
|
887
|
-
statusVersion === undefined
|
|
888
|
-
? visibleVersions[target]
|
|
889
|
-
: `${Math.floor(statusVersion / 0x10000) % 0x100}.${
|
|
890
|
-
Math.floor(statusVersion / 0x100) % 0x100
|
|
891
|
-
}.${statusVersion % 0x100}`;
|
|
892
|
-
if (!observedVersion) {
|
|
893
|
-
if (this.protocolV2FinalStatusVerified) {
|
|
894
|
-
Log.warn(
|
|
895
|
-
`Protocol V2 target ${target} has no observable final version; completed target status is authoritative`
|
|
896
|
-
);
|
|
897
|
-
} else {
|
|
898
|
-
throw ERRORS.TypedError(
|
|
899
|
-
HardwareErrorCode.FirmwareVerificationFailed,
|
|
900
|
-
`Protocol V2 target ${target} has no observable final version after status fallback`
|
|
901
|
-
);
|
|
902
|
-
}
|
|
903
|
-
} else if (observedVersion !== expectedVersion) {
|
|
904
|
-
throw ERRORS.TypedError(
|
|
905
|
-
HardwareErrorCode.FirmwareVerificationFailed,
|
|
906
|
-
`Protocol V2 target ${target} reached ${observedVersion}, expected ${expectedVersion}`
|
|
907
|
-
);
|
|
908
|
-
}
|
|
909
|
-
}
|
|
910
|
-
}
|
|
590
|
+
const versions = await this.waitForProtocolV2FinalFeatures();
|
|
591
|
+
this.postTipMessage(FirmwareUpdateTipMessage.FirmwareUpdateCompleted);
|
|
592
|
+
DevicePool.resetState();
|
|
911
593
|
|
|
912
|
-
|
|
913
|
-
if (this.params.targetsToUpdate?.length) {
|
|
914
|
-
return [...new Set(this.params.targetsToUpdate)];
|
|
915
|
-
}
|
|
916
|
-
const targets = new Set<FirmwareUpdateV4Target>();
|
|
917
|
-
Object.keys(this.params.componentArtifacts ?? {}).forEach(target =>
|
|
918
|
-
targets.add(target as FirmwareUpdateV4Target)
|
|
919
|
-
);
|
|
920
|
-
if (this.params.resourceBundleArtifacts?.length || this.params.resourceBundleFiles?.length) {
|
|
921
|
-
targets.add('resource');
|
|
922
|
-
}
|
|
923
|
-
if (this.params.bootloaderBinary) targets.add('boot');
|
|
924
|
-
if (this.params.applicationP1Binary) targets.add('app_v1');
|
|
925
|
-
if (this.params.applicationP2Binary) targets.add('app_v2');
|
|
926
|
-
if (this.params.coprocessorBinary) targets.add('coprocessor');
|
|
927
|
-
if (this.params.se01Binary) targets.add('se01');
|
|
928
|
-
if (this.params.se02Binary) targets.add('se02');
|
|
929
|
-
if (this.params.se03Binary) targets.add('se03');
|
|
930
|
-
if (this.params.se04Binary) targets.add('se04');
|
|
931
|
-
return Array.from(targets);
|
|
594
|
+
return versions;
|
|
932
595
|
}
|
|
933
596
|
|
|
934
597
|
private async getProtocolV2DeviceFeatures(): Promise<Features> {
|
|
@@ -950,31 +613,32 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
950
613
|
private assertProtocolV2DeviceInfoIdentity(deviceInfo: ProtocolV2DeviceInfo) {
|
|
951
614
|
assertProtocolV2ReconnectIdentity(
|
|
952
615
|
this.protocolV2ExpectedSerialNumber,
|
|
953
|
-
this.getProtocolV2SerialNumber(deviceInfo)
|
|
616
|
+
this.getProtocolV2SerialNumber(deviceInfo),
|
|
617
|
+
this.protocolV2ExpectedPath,
|
|
618
|
+
this.device.originalDescriptor.path
|
|
954
619
|
);
|
|
955
620
|
}
|
|
956
621
|
|
|
622
|
+
private getProtocolV2DeviceInfoTimeout() {
|
|
623
|
+
return this.isBleReconnect()
|
|
624
|
+
? this.payload.protocolV2DeviceInfoTimeoutMs ?? PROTOCOL_V2_DEVICE_INFO_READY_TIMEOUT
|
|
625
|
+
: PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT;
|
|
626
|
+
}
|
|
627
|
+
|
|
957
628
|
private async requestProtocolV2PhysicalIdentity() {
|
|
958
629
|
return requestProtocolV2DeviceInfo({
|
|
959
630
|
commands: this.device.getCommands(),
|
|
960
|
-
timeoutMs:
|
|
631
|
+
timeoutMs: this.getProtocolV2DeviceInfoTimeout(),
|
|
961
632
|
});
|
|
962
633
|
}
|
|
963
634
|
|
|
964
635
|
private async captureProtocolV2PhysicalIdentity() {
|
|
965
636
|
const deviceInfo = await this.requestProtocolV2PhysicalIdentity();
|
|
966
637
|
const serialNumber = this.getProtocolV2SerialNumber(deviceInfo);
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
preparedPlan: this.params.preparedPlan,
|
|
970
|
-
deviceIdentity: serialNumber,
|
|
971
|
-
});
|
|
972
|
-
} else if (this.params?.expectedDeviceId) {
|
|
973
|
-
assertProtocolV2ReconnectIdentity(this.params?.expectedDeviceId, serialNumber);
|
|
974
|
-
} else {
|
|
975
|
-
assertProtocolV2ReconnectIdentity(serialNumber, serialNumber);
|
|
976
|
-
}
|
|
638
|
+
const path = this.device.originalDescriptor.path?.trim() || undefined;
|
|
639
|
+
assertProtocolV2ReconnectIdentity(serialNumber, serialNumber, path, path);
|
|
977
640
|
this.protocolV2ExpectedSerialNumber = serialNumber;
|
|
641
|
+
this.protocolV2ExpectedPath = path;
|
|
978
642
|
}
|
|
979
643
|
|
|
980
644
|
private async verifyProtocolV2ReconnectIdentity() {
|
|
@@ -989,7 +653,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
989
653
|
|
|
990
654
|
private hasExplicitProtocolV2Payload(fwBinaryMap: ProtocolV2TargetBinary[]) {
|
|
991
655
|
return (
|
|
992
|
-
!!this.params.
|
|
656
|
+
!!this.params.resourceFiles?.length ||
|
|
993
657
|
!!this.params.bootloaderBinary ||
|
|
994
658
|
fwBinaryMap.length > 0
|
|
995
659
|
);
|
|
@@ -1052,57 +716,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1052
716
|
return target;
|
|
1053
717
|
}
|
|
1054
718
|
|
|
1055
|
-
private getProtocolV2ResourceFilePath(path: string) {
|
|
1056
|
-
if (path.startsWith('vol')) return path;
|
|
1057
|
-
if (path.startsWith('/')) return `vol0:${path}`;
|
|
1058
|
-
return `vol0:/${path}`;
|
|
1059
|
-
}
|
|
1060
|
-
|
|
1061
|
-
private async readProtocolV2DeviceFileHeader(path: string) {
|
|
1062
|
-
const typedCall = this.device.getCommands().typedCall.bind(this.device.getCommands());
|
|
1063
|
-
const filePath = this.getProtocolV2ResourceFilePath(path);
|
|
1064
|
-
const pathInfoRes = await typedCall('FilesystemPathInfoQuery', 'FilesystemPathInfo', {
|
|
1065
|
-
path: filePath,
|
|
1066
|
-
});
|
|
1067
|
-
const fileSize = toProtocolV2FiniteNumber(pathInfoRes.message?.size);
|
|
1068
|
-
if (
|
|
1069
|
-
!pathInfoRes.message?.exist ||
|
|
1070
|
-
pathInfoRes.message?.directory ||
|
|
1071
|
-
fileSize === undefined ||
|
|
1072
|
-
fileSize < PROTOCOL_V2_OKPP_HEADER_SIZE
|
|
1073
|
-
) {
|
|
1074
|
-
return null;
|
|
1075
|
-
}
|
|
1076
|
-
|
|
1077
|
-
const chunkSize = this.getProtocolV2FirmwareChunkSize('read');
|
|
1078
|
-
const chunks: Uint8Array[] = [];
|
|
1079
|
-
let offset = 0;
|
|
1080
|
-
while (offset < PROTOCOL_V2_OKPP_HEADER_SIZE) {
|
|
1081
|
-
const readLen = Math.min(chunkSize, PROTOCOL_V2_OKPP_HEADER_SIZE - offset);
|
|
1082
|
-
const res = await typedCall('FilesystemFileRead', 'FilesystemFile', {
|
|
1083
|
-
file: {
|
|
1084
|
-
path: filePath,
|
|
1085
|
-
offset,
|
|
1086
|
-
total_size: 0,
|
|
1087
|
-
},
|
|
1088
|
-
chunk_len: readLen,
|
|
1089
|
-
ui_percentage: undefined,
|
|
1090
|
-
});
|
|
1091
|
-
const data = toProtocolV2Bytes(res.message?.data);
|
|
1092
|
-
if (data.byteLength === 0) return null;
|
|
1093
|
-
chunks.push(data);
|
|
1094
|
-
offset += data.byteLength;
|
|
1095
|
-
}
|
|
1096
|
-
|
|
1097
|
-
const headerBytes = new Uint8Array(offset);
|
|
1098
|
-
let cursor = 0;
|
|
1099
|
-
chunks.forEach(chunk => {
|
|
1100
|
-
headerBytes.set(chunk, cursor);
|
|
1101
|
-
cursor += chunk.byteLength;
|
|
1102
|
-
});
|
|
1103
|
-
return parseProtocolV2OkppHeader(headerBytes);
|
|
1104
|
-
}
|
|
1105
|
-
|
|
1106
719
|
private async downloadRemoteProtocolV2Component(
|
|
1107
720
|
key: string,
|
|
1108
721
|
component: IProtocolV2FirmwareComponent
|
|
@@ -1191,155 +804,159 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1191
804
|
};
|
|
1192
805
|
}
|
|
1193
806
|
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
807
|
+
private getProtocolV2DeviceType(): EDeviceType.Pro2 | EDeviceType.Neo {
|
|
808
|
+
const deviceType = this.device.getCurrentDeviceType();
|
|
809
|
+
if (deviceType === EDeviceType.Pro2 || deviceType === EDeviceType.Neo) return deviceType;
|
|
810
|
+
throw new Error(`Unsupported Protocol V2 device type: ${deviceType}`);
|
|
811
|
+
}
|
|
1197
812
|
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
);
|
|
1217
|
-
return {
|
|
1218
|
-
name: devicePath.split('/').pop() ?? devicePath,
|
|
1219
|
-
binary: file.binary,
|
|
1220
|
-
devicePath,
|
|
1221
|
-
};
|
|
813
|
+
private async prepareProtocolV2BootResources(): Promise<
|
|
814
|
+
ProtocolV2ResourceBundleBinary[] | undefined
|
|
815
|
+
> {
|
|
816
|
+
if (!this.params.targetsToUpdate?.includes('boot_resources')) return undefined;
|
|
817
|
+
const resource = DataManager.getProtocolV2BootResources(this.getProtocolV2DeviceType());
|
|
818
|
+
if (!resource) throw new Error('Missing Protocol V2 boot resources configuration');
|
|
819
|
+
|
|
820
|
+
const files: ProtocolV2ResourceBundleBinary[] = [];
|
|
821
|
+
for (const file of resource.files) {
|
|
822
|
+
Log.log(`[FirmwareUpdateV4] downloading boot resource ${file.devicePath}`);
|
|
823
|
+
const { binary } = await getSysResourceBinary(file.url);
|
|
824
|
+
if (!isProtocolV2ResourceFileValid(binary, file)) {
|
|
825
|
+
throw new Error(`Boot resource file verification failed: ${file.devicePath}`);
|
|
826
|
+
}
|
|
827
|
+
files.push({
|
|
828
|
+
name: file.name ?? file.devicePath.split('/').pop() ?? file.devicePath,
|
|
829
|
+
binary,
|
|
830
|
+
devicePath: file.devicePath,
|
|
1222
831
|
});
|
|
1223
832
|
}
|
|
833
|
+
return files;
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
private prepareExplicitProtocolV2ResourceFiles(): ProtocolV2ResourceBundleBinary[] | undefined {
|
|
837
|
+
const files = this.params.resourceFiles ?? [];
|
|
838
|
+
if (!files?.length) return undefined;
|
|
1224
839
|
|
|
840
|
+
const prepared = files.map((file, index) => {
|
|
841
|
+
const devicePath = validateProtocolV2FilesystemPath(
|
|
842
|
+
file.devicePath,
|
|
843
|
+
`resourceFiles[${index}].devicePath`
|
|
844
|
+
);
|
|
845
|
+
const descriptor = file as typeof file &
|
|
846
|
+
Pick<Partial<IProtocolV2ResourceFile>, 'size' | 'fileHash'>;
|
|
847
|
+
if (descriptor.size !== undefined && descriptor.size !== file.binary.byteLength) {
|
|
848
|
+
throw new Error(`resourceFiles[${index}] size mismatch`);
|
|
849
|
+
}
|
|
850
|
+
if (
|
|
851
|
+
descriptor.fileHash &&
|
|
852
|
+
!isProtocolV2ResourceFileValid(file.binary, {
|
|
853
|
+
size: file.binary.byteLength,
|
|
854
|
+
fileHash: descriptor.fileHash,
|
|
855
|
+
})
|
|
856
|
+
) {
|
|
857
|
+
throw new Error(`resourceFiles[${index}] SHA-256 mismatch`);
|
|
858
|
+
}
|
|
859
|
+
return {
|
|
860
|
+
name: devicePath.split('/').pop() ?? devicePath,
|
|
861
|
+
binary: file.binary,
|
|
862
|
+
devicePath,
|
|
863
|
+
};
|
|
864
|
+
});
|
|
865
|
+
if (new Set(prepared.map(file => file.devicePath)).size !== prepared.length) {
|
|
866
|
+
throw new Error('resourceFiles contain duplicate devicePath values');
|
|
867
|
+
}
|
|
868
|
+
return prepared;
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
private async prepareProtocolV2ResourceBundles(): Promise<
|
|
872
|
+
ProtocolV2ResourceBundleBinary[] | undefined
|
|
873
|
+
> {
|
|
1225
874
|
if (!this.params.targetsToUpdate?.includes('resource')) {
|
|
1226
875
|
return undefined;
|
|
1227
876
|
}
|
|
1228
877
|
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
throw ERRORS.TypedError(
|
|
1256
|
-
HardwareErrorCode.FirmwareUpdateDownloadFailed,
|
|
1257
|
-
`Missing Protocol V2 RESC bundle binary: ${bundle.name}`
|
|
1258
|
-
);
|
|
1259
|
-
}
|
|
1260
|
-
if (
|
|
1261
|
-
this.params.artifactReader ||
|
|
1262
|
-
DataManager.getSettings('firmwareManifestMode') === 'external-only'
|
|
1263
|
-
) {
|
|
1264
|
-
throw ERRORS.TypedError(
|
|
1265
|
-
HardwareErrorCode.RuntimeError,
|
|
1266
|
-
`Firmware resource bundle ${bundle.name} is not prepared`,
|
|
1267
|
-
{
|
|
1268
|
-
firmwareUpdateCode: 'FirmwareArtifactsNotPrepared',
|
|
1269
|
-
artifactName: bundle.name,
|
|
1270
|
-
}
|
|
1271
|
-
);
|
|
1272
|
-
}
|
|
1273
|
-
Log.log(`[FirmwareUpdateV4] downloading remote RESC bundle ${bundle.name}`);
|
|
1274
|
-
({ binary } = await getSysResourceBinary(bundle.url));
|
|
1275
|
-
bundle.binary = binary;
|
|
1276
|
-
downloadedFromRemote = true;
|
|
1277
|
-
}
|
|
1278
|
-
if (bundle.binary.byteLength === 0) {
|
|
1279
|
-
throw ERRORS.TypedError(
|
|
1280
|
-
HardwareErrorCode.FirmwareUpdateDownloadFailed,
|
|
1281
|
-
`Protocol V2 RESC bundle is empty: ${bundle.name}`
|
|
1282
|
-
);
|
|
1283
|
-
}
|
|
1284
|
-
if (downloadedFromRemote) {
|
|
1285
|
-
const header = parseProtocolV2OkppHeader(toProtocolV2Bytes(binary));
|
|
1286
|
-
if (!header || header.type !== 'RESC') {
|
|
1287
|
-
throw new Error(`Invalid Protocol V2 RESC bundle header: ${bundle.name}`);
|
|
1288
|
-
}
|
|
1289
|
-
if (bundle.version && compareProtocolV2Versions(header.version, bundle.version) !== 0) {
|
|
1290
|
-
throw new Error(`Protocol V2 RESC bundle version mismatch: ${bundle.name}`);
|
|
1291
|
-
}
|
|
1292
|
-
const expectedPayloadHash = normalizeProtocolV2Hex(bundle.payloadHash);
|
|
1293
|
-
if (expectedPayloadHash && header.payloadHash !== expectedPayloadHash) {
|
|
1294
|
-
throw new Error(`Protocol V2 RESC bundle payload hash mismatch: ${bundle.name}`);
|
|
1295
|
-
}
|
|
1296
|
-
const expectedHeaderHash = normalizeProtocolV2Hex(bundle.headerHash);
|
|
1297
|
-
if (expectedHeaderHash && header.headerHash !== expectedHeaderHash) {
|
|
1298
|
-
throw new Error(`Protocol V2 RESC bundle header hash mismatch: ${bundle.name}`);
|
|
1299
|
-
}
|
|
1300
|
-
}
|
|
878
|
+
const resources = DataManager.getProtocolV2Resources(this.getProtocolV2DeviceType());
|
|
879
|
+
if (!resources?.length) {
|
|
880
|
+
throw new Error('Missing Pro2 stable resource configuration');
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
const inventory = this.params.forcedUpdateRes
|
|
884
|
+
? undefined
|
|
885
|
+
: await readProtocolV2ResourceInventory({
|
|
886
|
+
commands: this.device.getCommands(),
|
|
887
|
+
resources,
|
|
888
|
+
chunkSize: this.getProtocolV2FirmwareChunkSize(),
|
|
889
|
+
timeoutMs: PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT,
|
|
890
|
+
});
|
|
891
|
+
const plan = buildProtocolV2ResourceUpdatePlan({
|
|
892
|
+
resources,
|
|
893
|
+
inventory,
|
|
894
|
+
mode: 'bootloader-recovery',
|
|
895
|
+
forced: this.params.forcedUpdateRes,
|
|
896
|
+
});
|
|
897
|
+
Log.log(
|
|
898
|
+
`[FirmwareUpdateV4] Protocol V2 resource plan mode=bootloader-recovery status=${plan.status} count=${plan.resources.length}`
|
|
899
|
+
);
|
|
900
|
+
|
|
901
|
+
const bundles: ProtocolV2ResourceBundleBinary[] = [];
|
|
902
|
+
for (const resource of plan.resources) {
|
|
903
|
+
bundles.push(await this.downloadProtocolV2Resource(resource));
|
|
1301
904
|
}
|
|
1302
905
|
return bundles;
|
|
1303
906
|
}
|
|
1304
907
|
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
908
|
+
private async downloadProtocolV2Resource(
|
|
909
|
+
resource: IProtocolV2Resource
|
|
910
|
+
): Promise<ProtocolV2ResourceBundleBinary> {
|
|
911
|
+
Log.log(`[FirmwareUpdateV4] downloading Pro2 resource ${resource.type}`);
|
|
912
|
+
const { binary } = await getSysResourceBinary(resource.url);
|
|
913
|
+
if (!isProtocolV2ResourceFileValid(binary, resource)) {
|
|
914
|
+
throw new Error(`Pro2 resource file verification failed: ${resource.type}`);
|
|
915
|
+
}
|
|
916
|
+
return {
|
|
917
|
+
name: `${resource.type}.okpkg`,
|
|
918
|
+
binary,
|
|
919
|
+
devicePath: PROTOCOL_V2_RESOURCE_DEVICE_PATHS[resource.type],
|
|
920
|
+
};
|
|
921
|
+
}
|
|
1316
922
|
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
923
|
+
private async syncProtocolV2ResourceBundles(
|
|
924
|
+
bundles: ProtocolV2ResourceBundleBinary[],
|
|
925
|
+
firmwareSize: number
|
|
926
|
+
): Promise<{ processedSize: number; totalSize: number }> {
|
|
927
|
+
const transferStartTime = Date.now();
|
|
928
|
+
const transferTransport = this.getProtocolV2FirmwareTransferTransport();
|
|
1320
929
|
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
930
|
+
// Write directly to the device through FileWrite.
|
|
931
|
+
let totalSize = 0;
|
|
932
|
+
for (const b of bundles) totalSize += b.binary.byteLength;
|
|
933
|
+
|
|
934
|
+
const transferTotalSize = totalSize + firmwareSize;
|
|
935
|
+
let processedSize = 0;
|
|
936
|
+
for (const bundle of bundles) {
|
|
937
|
+
Log.log(
|
|
938
|
+
`[FirmwareUpdateV4] syncing resource ${bundle.name} -> ${bundle.devicePath} bytes=${bundle.binary.byteLength}`
|
|
939
|
+
);
|
|
940
|
+
processedSize = await this.protocolV2CommonUpdateProcess({
|
|
941
|
+
payload: bundle.binary,
|
|
942
|
+
filePath: bundle.devicePath,
|
|
943
|
+
processedSize,
|
|
944
|
+
totalSize: transferTotalSize,
|
|
945
|
+
});
|
|
1337
946
|
}
|
|
947
|
+
|
|
948
|
+
const elapsedMs = Date.now() - transferStartTime;
|
|
949
|
+
Log.log(
|
|
950
|
+
`[FirmwareUpdateV4] resource sync finished transport=${transferTransport} bytes=${totalSize} elapsed=${(
|
|
951
|
+
elapsedMs / 1000
|
|
952
|
+
).toFixed(2)}s speed=${formatProtocolV2TransferSpeed(totalSize, elapsedMs)} KB/s`
|
|
953
|
+
);
|
|
954
|
+
return { processedSize, totalSize: transferTotalSize };
|
|
1338
955
|
}
|
|
1339
956
|
|
|
1340
957
|
private isProtocolV2BootloaderMode() {
|
|
1341
958
|
if (typeof this.device.isBootloader === 'function') {
|
|
1342
|
-
return this.device.isBootloader();
|
|
959
|
+
return !!this.device.isBootloader();
|
|
1343
960
|
}
|
|
1344
961
|
return (
|
|
1345
962
|
this.device.features?.mode === 'bootloader' ||
|
|
@@ -1348,7 +965,11 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1348
965
|
}
|
|
1349
966
|
|
|
1350
967
|
private isProtocolV2RomloaderMode() {
|
|
1351
|
-
|
|
968
|
+
const deviceType = this.device.getCurrentDeviceType();
|
|
969
|
+
if (
|
|
970
|
+
!this.device.isProtocolV2() ||
|
|
971
|
+
(deviceType !== EDeviceType.Pro2 && deviceType !== EDeviceType.Neo)
|
|
972
|
+
) {
|
|
1352
973
|
return false;
|
|
1353
974
|
}
|
|
1354
975
|
if (typeof this.device.isRomloader === 'function') {
|
|
@@ -1362,22 +983,20 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1362
983
|
// It rejects DeviceRebootType.Bootloader, so reuse the current connection.
|
|
1363
984
|
if (this.isProtocolV2RomloaderMode()) {
|
|
1364
985
|
Log.debug('Protocol V2 device is in romloader mode; start firmware update directly');
|
|
1365
|
-
this.protocolV2ExecutionInLoader = true;
|
|
1366
986
|
return false;
|
|
1367
987
|
}
|
|
1368
988
|
if (this.isProtocolV2BootloaderMode()) {
|
|
1369
989
|
Log.debug('Protocol V2 device is already in bootloader mode, skip reboot');
|
|
1370
|
-
this.
|
|
990
|
+
this.postTipMessage(FirmwareUpdateTipMessage.GoToBootloaderSuccess);
|
|
1371
991
|
return false;
|
|
1372
992
|
}
|
|
1373
993
|
|
|
1374
994
|
try {
|
|
1375
995
|
this.postTipMessage(FirmwareUpdateTipMessage.AutoRebootToBootloader);
|
|
1376
996
|
await this.protocolV2Reboot(DeviceRebootType.Bootloader);
|
|
1377
|
-
this.postTipMessage(FirmwareUpdateTipMessage.GoToBootloaderSuccess);
|
|
1378
997
|
await wait(1000);
|
|
1379
998
|
await this.waitForProtocolV2BootloaderMode();
|
|
1380
|
-
this.
|
|
999
|
+
this.postTipMessage(FirmwareUpdateTipMessage.GoToBootloaderSuccess);
|
|
1381
1000
|
return true;
|
|
1382
1001
|
} catch (error) {
|
|
1383
1002
|
if (error instanceof HardwareError) {
|
|
@@ -1394,13 +1013,17 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1394
1013
|
) {
|
|
1395
1014
|
const startTime = Date.now();
|
|
1396
1015
|
let lastError: unknown;
|
|
1016
|
+
let shouldReconnect = true;
|
|
1397
1017
|
|
|
1398
1018
|
while (Date.now() - startTime < timeout) {
|
|
1399
1019
|
try {
|
|
1400
|
-
|
|
1020
|
+
if (shouldReconnect) {
|
|
1021
|
+
await this.reconnectProtocolV2Device();
|
|
1022
|
+
shouldReconnect = false;
|
|
1023
|
+
}
|
|
1401
1024
|
const deviceInfo = await requestProtocolV2DeviceInfo({
|
|
1402
1025
|
commands: this.device.getCommands(),
|
|
1403
|
-
timeoutMs:
|
|
1026
|
+
timeoutMs: this.getProtocolV2DeviceInfoTimeout(),
|
|
1404
1027
|
});
|
|
1405
1028
|
this.assertProtocolV2DeviceInfoIdentity(deviceInfo);
|
|
1406
1029
|
const features = await this.device.probeProtocolV2RuntimeState(
|
|
@@ -1415,6 +1038,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1415
1038
|
if (this.isProtocolV2ReconnectIdentityError(error)) {
|
|
1416
1039
|
throw error;
|
|
1417
1040
|
}
|
|
1041
|
+
shouldReconnect = true;
|
|
1418
1042
|
lastError = error;
|
|
1419
1043
|
Log.log('Protocol V2 bootloader mode not ready, polling reconnect: ', error);
|
|
1420
1044
|
}
|
|
@@ -1467,98 +1091,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1467
1091
|
return entries;
|
|
1468
1092
|
}
|
|
1469
1093
|
|
|
1470
|
-
private buildProtocolV2ExecutionPhases({
|
|
1471
|
-
installSources,
|
|
1472
|
-
resourceSources,
|
|
1473
|
-
}: {
|
|
1474
|
-
installSources: ProtocolV2InstallSource[];
|
|
1475
|
-
resourceSources: ProtocolV2ResourceBundleSource[];
|
|
1476
|
-
}): ProtocolV2ExecutionPhase[] {
|
|
1477
|
-
const phases: ProtocolV2ExecutionPhase[] = [];
|
|
1478
|
-
if (resourceSources.length > 0) {
|
|
1479
|
-
phases.push({
|
|
1480
|
-
kind: 'resource-sync',
|
|
1481
|
-
installSources: [],
|
|
1482
|
-
resourceSources,
|
|
1483
|
-
});
|
|
1484
|
-
}
|
|
1485
|
-
const bootloaderSources = installSources.filter(source => source.kind === 'bootloader');
|
|
1486
|
-
if (bootloaderSources.length > 0) {
|
|
1487
|
-
phases.push(
|
|
1488
|
-
{
|
|
1489
|
-
kind: 'bootloader-install',
|
|
1490
|
-
installSources: bootloaderSources,
|
|
1491
|
-
resourceSources: [],
|
|
1492
|
-
},
|
|
1493
|
-
{
|
|
1494
|
-
kind: 'bootloader-verify',
|
|
1495
|
-
installSources: [],
|
|
1496
|
-
resourceSources: [],
|
|
1497
|
-
}
|
|
1498
|
-
);
|
|
1499
|
-
}
|
|
1500
|
-
const componentSources = installSources.filter(source => source.kind !== 'bootloader');
|
|
1501
|
-
if (componentSources.length > 0) {
|
|
1502
|
-
phases.push({
|
|
1503
|
-
kind: 'component-install',
|
|
1504
|
-
installSources: componentSources,
|
|
1505
|
-
resourceSources: [],
|
|
1506
|
-
});
|
|
1507
|
-
}
|
|
1508
|
-
phases.push({
|
|
1509
|
-
kind: 'final-verify',
|
|
1510
|
-
installSources: [],
|
|
1511
|
-
resourceSources: [],
|
|
1512
|
-
});
|
|
1513
|
-
return phases;
|
|
1514
|
-
}
|
|
1515
|
-
|
|
1516
|
-
private async executeProtocolV2Phases({
|
|
1517
|
-
installSources,
|
|
1518
|
-
resourceSources,
|
|
1519
|
-
}: {
|
|
1520
|
-
installSources: ProtocolV2InstallSource[];
|
|
1521
|
-
resourceSources: ProtocolV2ResourceBundleSource[];
|
|
1522
|
-
}) {
|
|
1523
|
-
const phases = this.buildProtocolV2ExecutionPhases({
|
|
1524
|
-
installSources,
|
|
1525
|
-
resourceSources,
|
|
1526
|
-
});
|
|
1527
|
-
for (const phase of phases) {
|
|
1528
|
-
if (phase.kind === 'final-verify') {
|
|
1529
|
-
return this.completeProtocolV2FinalVerification();
|
|
1530
|
-
}
|
|
1531
|
-
if (phase.kind === 'resource-sync') {
|
|
1532
|
-
await this.enterProtocolV2BootloaderMode();
|
|
1533
|
-
await this.executeProtocolV2TransferPhase(phase);
|
|
1534
|
-
} else if (phase.kind === 'bootloader-install' || phase.kind === 'component-install') {
|
|
1535
|
-
await this.enterProtocolV2BootloaderMode();
|
|
1536
|
-
await this.executeProtocolV2TransferPhase(phase);
|
|
1537
|
-
await this.exitProtocolV2BootloaderToNormal();
|
|
1538
|
-
} else if (phase.kind === 'bootloader-verify') {
|
|
1539
|
-
await this.waitForProtocolV2FinalFeatures();
|
|
1540
|
-
this.assertExpectedProtocolV2Versions(['boot']);
|
|
1541
|
-
}
|
|
1542
|
-
}
|
|
1543
|
-
throw ERRORS.TypedError(
|
|
1544
|
-
HardwareErrorCode.RuntimeError,
|
|
1545
|
-
'Protocol V2 execution has no final verification phase'
|
|
1546
|
-
);
|
|
1547
|
-
}
|
|
1548
|
-
|
|
1549
|
-
private async executeProtocolV2SourceUpdate({
|
|
1550
|
-
installSources,
|
|
1551
|
-
resourceSources,
|
|
1552
|
-
}: {
|
|
1553
|
-
installSources: ProtocolV2InstallSource[];
|
|
1554
|
-
resourceSources: ProtocolV2ResourceBundleSource[];
|
|
1555
|
-
}) {
|
|
1556
|
-
return this.executeProtocolV2Phases({
|
|
1557
|
-
installSources,
|
|
1558
|
-
resourceSources,
|
|
1559
|
-
});
|
|
1560
|
-
}
|
|
1561
|
-
|
|
1562
1094
|
private async executeProtocolV2Update({
|
|
1563
1095
|
fwBinaryMap,
|
|
1564
1096
|
bootloaderBinary,
|
|
@@ -1570,158 +1102,104 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1570
1102
|
installItems?: ProtocolV2InstallItem[];
|
|
1571
1103
|
resourceBundles?: ProtocolV2ResourceBundleBinary[];
|
|
1572
1104
|
}) {
|
|
1573
|
-
const
|
|
1105
|
+
const orderedInstallItems =
|
|
1574
1106
|
installItems ??
|
|
1575
1107
|
this.buildProtocolV2InstallItems({
|
|
1576
|
-
fwBinaryMap: fwBinaryMap ?? [],
|
|
1577
1108
|
bootloaderBinary: bootloaderBinary ?? null,
|
|
1109
|
+
fwBinaryMap: fwBinaryMap ?? [],
|
|
1578
1110
|
});
|
|
1579
|
-
try {
|
|
1580
|
-
const installSources = await Promise.all(
|
|
1581
|
-
memoryInstallItems.map(async item => ({
|
|
1582
|
-
fileName: item.fileName,
|
|
1583
|
-
source: await this.openProtocolV2MemorySource(item.binary),
|
|
1584
|
-
targetId: item.targetId,
|
|
1585
|
-
kind: item.kind,
|
|
1586
|
-
}))
|
|
1587
|
-
);
|
|
1588
|
-
const resourceSources = await Promise.all(
|
|
1589
|
-
(resourceBundles ?? []).map(async bundle => ({
|
|
1590
|
-
name: bundle.name,
|
|
1591
|
-
source: await this.openProtocolV2MemorySource(bundle.binary),
|
|
1592
|
-
devicePath: bundle.devicePath,
|
|
1593
|
-
version: bundle.version,
|
|
1594
|
-
payloadHash: bundle.payloadHash,
|
|
1595
|
-
headerHash: bundle.headerHash,
|
|
1596
|
-
}))
|
|
1597
|
-
);
|
|
1598
|
-
return await this.executeProtocolV2Phases({
|
|
1599
|
-
installSources,
|
|
1600
|
-
resourceSources,
|
|
1601
|
-
});
|
|
1602
|
-
} finally {
|
|
1603
|
-
await this.closeProtocolV2PreparedSources();
|
|
1604
|
-
}
|
|
1605
|
-
}
|
|
1606
1111
|
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
resourceSources,
|
|
1610
|
-
}: ProtocolV2ExecutionPhase) {
|
|
1611
|
-
let totalSize = installSources.reduce((total, item) => total + item.source.size, 0);
|
|
1612
|
-
const resourcesToSync: ProtocolV2ResourceBundleSource[] = [];
|
|
1613
|
-
for (const resource of resourceSources) {
|
|
1614
|
-
if (await this.isProtocolV2ResourceBundleUpToDate(resource)) {
|
|
1615
|
-
Log.log(`[FirmwareUpdateV4] skip RESC bundle ${resource.name}; already up to date`);
|
|
1616
|
-
} else {
|
|
1617
|
-
resourcesToSync.push(resource);
|
|
1618
|
-
totalSize += resource.source.size;
|
|
1619
|
-
}
|
|
1620
|
-
}
|
|
1112
|
+
let firmwareSize = 0;
|
|
1113
|
+
for (const item of orderedInstallItems) firmwareSize += item.binary.byteLength;
|
|
1621
1114
|
|
|
1622
1115
|
this.postTipMessage(FirmwareUpdateTipMessage.StartTransferData);
|
|
1116
|
+
|
|
1623
1117
|
let processedSize = 0;
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1118
|
+
let totalSize = firmwareSize;
|
|
1119
|
+
if (resourceBundles?.length) {
|
|
1120
|
+
const resourceTransfer = await this.syncProtocolV2ResourceBundles(
|
|
1121
|
+
resourceBundles,
|
|
1122
|
+
firmwareSize
|
|
1123
|
+
);
|
|
1124
|
+
processedSize = resourceTransfer.processedSize;
|
|
1125
|
+
totalSize = resourceTransfer.totalSize;
|
|
1632
1126
|
}
|
|
1633
1127
|
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
totalSize,
|
|
1642
|
-
});
|
|
1643
|
-
await this.verifyProtocolV2StagedFile(filePath, item.source.size);
|
|
1644
|
-
stagedInstallTargets.push({
|
|
1645
|
-
targetId: item.targetId,
|
|
1646
|
-
path: filePath,
|
|
1647
|
-
});
|
|
1128
|
+
// Skip staging and installation when the update contains resource files only.
|
|
1129
|
+
if (orderedInstallItems.length === 0) {
|
|
1130
|
+
Log.log('[FirmwareUpdateV4] no firmware targets to install (resource files only)');
|
|
1131
|
+
if (totalSize > 0) {
|
|
1132
|
+
this.postProgressMessage(100, 'transferData');
|
|
1133
|
+
}
|
|
1134
|
+
return;
|
|
1648
1135
|
}
|
|
1136
|
+
let transferredSize = processedSize;
|
|
1137
|
+
const transferStartTime = Date.now();
|
|
1138
|
+
const transferTransport = this.getProtocolV2FirmwareTransferTransport();
|
|
1139
|
+
const chunkSize = this.getProtocolV2FirmwareChunkSize();
|
|
1140
|
+
const onTransferredBytes = (bytes: number) => {
|
|
1141
|
+
transferredSize = bytes;
|
|
1142
|
+
};
|
|
1143
|
+
Log.log(
|
|
1144
|
+
`[FirmwareUpdateV4] transfer started transport=${transferTransport} total=${totalSize} bytes chunk=${chunkSize} bytes`
|
|
1145
|
+
);
|
|
1649
1146
|
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1147
|
+
const stagedInstallTargets: ProtocolV2InstallTarget[] = [];
|
|
1148
|
+
|
|
1149
|
+
try {
|
|
1150
|
+
for (const item of orderedInstallItems) {
|
|
1151
|
+
const filePath = this.getProtocolV2InstallItemStagingPath(item);
|
|
1152
|
+
Log.log(
|
|
1153
|
+
`[FirmwareUpdateV4] staging ${item.kind} via FilesystemFileWrite target=${item.targetId} path=${filePath} source=${item.fileName} bytes=${item.binary.byteLength}`
|
|
1154
|
+
);
|
|
1155
|
+
processedSize = await this.protocolV2CommonUpdateProcess({
|
|
1156
|
+
payload: item.binary,
|
|
1157
|
+
filePath,
|
|
1158
|
+
processedSize,
|
|
1159
|
+
totalSize,
|
|
1160
|
+
onTransferredBytes,
|
|
1161
|
+
});
|
|
1162
|
+
transferredSize = processedSize;
|
|
1163
|
+
await this.verifyProtocolV2StagedFile(filePath, item.binary.byteLength);
|
|
1164
|
+
|
|
1165
|
+
stagedInstallTargets.push({
|
|
1166
|
+
...item,
|
|
1167
|
+
path: filePath,
|
|
1168
|
+
});
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
if (totalSize > 0) {
|
|
1172
|
+
this.postProgressMessage(100, 'transferData');
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
const elapsedMs = Date.now() - transferStartTime;
|
|
1176
|
+
Log.log(
|
|
1177
|
+
`[FirmwareUpdateV4] transfer finished transport=${transferTransport} bytes=${totalSize} elapsed=${(
|
|
1178
|
+
elapsedMs / 1000
|
|
1179
|
+
).toFixed(2)}s speed=${formatProtocolV2TransferSpeed(totalSize, elapsedMs)} KB/s`
|
|
1180
|
+
);
|
|
1181
|
+
} catch (error) {
|
|
1182
|
+
const elapsedMs = Date.now() - transferStartTime;
|
|
1183
|
+
Log.warn(
|
|
1184
|
+
`[FirmwareUpdateV4] transfer failed transport=${transferTransport} bytes=${transferredSize}/${totalSize} elapsed=${(
|
|
1185
|
+
elapsedMs / 1000
|
|
1186
|
+
).toFixed(2)}s speed=${formatProtocolV2TransferSpeed(transferredSize, elapsedMs)} KB/s`
|
|
1187
|
+
);
|
|
1188
|
+
throw error;
|
|
1655
1189
|
}
|
|
1656
1190
|
|
|
1657
1191
|
this.postTipMessage(FirmwareUpdateTipMessage.ConfirmOnDevice);
|
|
1658
|
-
|
|
1192
|
+
|
|
1193
|
+
const allTargets = stagedInstallTargets.map(item => ({
|
|
1659
1194
|
target_id: item.targetId,
|
|
1660
1195
|
path: item.path,
|
|
1661
1196
|
}));
|
|
1662
|
-
|
|
1663
|
-
await this.
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
private async protocolV2SourceUpdateProcess({
|
|
1667
|
-
source,
|
|
1668
|
-
filePath,
|
|
1669
|
-
processedSize,
|
|
1670
|
-
totalSize,
|
|
1671
|
-
}: {
|
|
1672
|
-
source: FirmwareByteSource;
|
|
1673
|
-
filePath: string;
|
|
1674
|
-
processedSize: number;
|
|
1675
|
-
totalSize: number;
|
|
1676
|
-
}) {
|
|
1677
|
-
let lastError: unknown;
|
|
1678
|
-
for (let attempt = 1; attempt <= PROTOCOL_V2_FILE_TRANSFER_RETRY_COUNT; attempt += 1) {
|
|
1679
|
-
try {
|
|
1680
|
-
await writeFirmwareByteSource({
|
|
1681
|
-
source,
|
|
1682
|
-
chunkSize: this.getProtocolV2FirmwareChunkSize(),
|
|
1683
|
-
write: async ({ data, sourceOffset, length, first }) => {
|
|
1684
|
-
const chunkEnd = sourceOffset + length;
|
|
1685
|
-
const progress = getProtocolV2DeviceTransferProgress(
|
|
1686
|
-
processedSize + sourceOffset,
|
|
1687
|
-
processedSize + chunkEnd,
|
|
1688
|
-
totalSize
|
|
1689
|
-
);
|
|
1690
|
-
const response = await this.fileWriteChunk(
|
|
1691
|
-
filePath,
|
|
1692
|
-
source.size,
|
|
1693
|
-
sourceOffset,
|
|
1694
|
-
data,
|
|
1695
|
-
first,
|
|
1696
|
-
progress
|
|
1697
|
-
);
|
|
1698
|
-
const rawProcessedByte = response.message.processed_byte;
|
|
1699
|
-
const nextOffset = rawProcessedByte === undefined ? chunkEnd : Number(rawProcessedByte);
|
|
1700
|
-
if (!Number.isFinite(nextOffset) || nextOffset !== chunkEnd) {
|
|
1701
|
-
throw ERRORS.TypedError(
|
|
1702
|
-
HardwareErrorCode.EmmcFileWriteFirmwareError,
|
|
1703
|
-
`invalid processed_byte ${rawProcessedByte} for offset ${sourceOffset}`
|
|
1704
|
-
);
|
|
1705
|
-
}
|
|
1706
|
-
this.postProgressMessage(progress, 'transferData');
|
|
1707
|
-
return length;
|
|
1708
|
-
},
|
|
1709
|
-
});
|
|
1710
|
-
return processedSize + source.size;
|
|
1711
|
-
} catch (error) {
|
|
1712
|
-
lastError = error;
|
|
1713
|
-
if (attempt < PROTOCOL_V2_FILE_TRANSFER_RETRY_COUNT) {
|
|
1714
|
-
await this.recoverProtocolV2FileTransfer();
|
|
1715
|
-
}
|
|
1716
|
-
}
|
|
1717
|
-
}
|
|
1718
|
-
throw ERRORS.TypedError(
|
|
1719
|
-
HardwareErrorCode.EmmcFileWriteFirmwareError,
|
|
1720
|
-
`transfer data error: ${getProtocolV2UnknownErrorText(lastError)}`
|
|
1721
|
-
);
|
|
1197
|
+
Log.log(`[FirmwareUpdateV4] DeviceFirmwareUpdateRequest targets=${JSON.stringify(allTargets)}`);
|
|
1198
|
+
await this.protocolV2StartFirmwareUpdate({ targets: allTargets });
|
|
1199
|
+
await this.waitForProtocolV2FirmwareUpdateComplete(allTargets);
|
|
1722
1200
|
}
|
|
1723
1201
|
|
|
1724
|
-
private getProtocolV2InstallItemStagingPath(item:
|
|
1202
|
+
private getProtocolV2InstallItemStagingPath(item: ProtocolV2InstallItem) {
|
|
1725
1203
|
return `${PROTOCOL_V2_FIRMWARE_STAGING_VOLUME}${item.fileName}`;
|
|
1726
1204
|
}
|
|
1727
1205
|
|
|
@@ -1740,8 +1218,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1740
1218
|
|
|
1741
1219
|
private assertProtocolV2TargetStatus(
|
|
1742
1220
|
statusTargets: ProtocolV2FirmwareUpdateStatusTarget[],
|
|
1743
|
-
expectedTargetIds: Set<number
|
|
1744
|
-
expectedPaths = new Map<number, string>()
|
|
1221
|
+
expectedTargetIds: Set<number>
|
|
1745
1222
|
) {
|
|
1746
1223
|
Log.log(
|
|
1747
1224
|
`[FirmwareUpdateV4] DeviceFirmwareUpdateStatus records=${JSON.stringify(statusTargets)}`
|
|
@@ -1772,54 +1249,17 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1772
1249
|
const matchingTargets = statusTargets.filter(target =>
|
|
1773
1250
|
expectedTargetIds.has(normalizeProtocolV2TargetId(target.target_id) ?? -1)
|
|
1774
1251
|
);
|
|
1775
|
-
const seenTargetIds = new Set<number>();
|
|
1776
|
-
for (const target of matchingTargets) {
|
|
1777
|
-
const targetId = normalizeProtocolV2TargetId(target.target_id);
|
|
1778
|
-
if (
|
|
1779
|
-
targetId === undefined ||
|
|
1780
|
-
seenTargetIds.has(targetId) ||
|
|
1781
|
-
(target.path && expectedPaths.get(targetId) && target.path !== expectedPaths.get(targetId))
|
|
1782
|
-
) {
|
|
1783
|
-
throw ERRORS.TypedError(
|
|
1784
|
-
HardwareErrorCode.RuntimeError,
|
|
1785
|
-
`Protocol V2 install status conflicts with target ${target.target_id}`
|
|
1786
|
-
);
|
|
1787
|
-
}
|
|
1788
|
-
seenTargetIds.add(targetId);
|
|
1789
|
-
}
|
|
1790
|
-
const completedTargets = matchingTargets.filter(target =>
|
|
1791
|
-
isProtocolV2TargetStatusFinished(target.status)
|
|
1792
|
-
);
|
|
1793
1252
|
const completedTargetIds = new Set<number>();
|
|
1794
|
-
|
|
1253
|
+
matchingTargets.forEach(target => {
|
|
1795
1254
|
const targetId = normalizeProtocolV2TargetId(target.target_id);
|
|
1796
|
-
if (targetId !== undefined) {
|
|
1255
|
+
if (targetId !== undefined && isProtocolV2TargetStatusFinished(target.status)) {
|
|
1797
1256
|
completedTargetIds.add(targetId);
|
|
1798
1257
|
}
|
|
1799
1258
|
});
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
if (targetId === undefined) {
|
|
1805
|
-
throw ERRORS.TypedError(
|
|
1806
|
-
HardwareErrorCode.RuntimeError,
|
|
1807
|
-
'Protocol V2 completed target identity is invalid'
|
|
1808
|
-
);
|
|
1809
|
-
}
|
|
1810
|
-
const expectedVersion = this.getExpectedProtocolV2TargetVersion(targetId);
|
|
1811
|
-
if (expectedVersion !== undefined && payloadVersion !== undefined) {
|
|
1812
|
-
if (payloadVersion !== expectedVersion) {
|
|
1813
|
-
throw ERRORS.TypedError(
|
|
1814
|
-
HardwareErrorCode.FirmwareVerificationFailed,
|
|
1815
|
-
`Protocol V2 target ${targetId} reached payload version ${payloadVersion}, expected ${expectedVersion}`
|
|
1816
|
-
);
|
|
1817
|
-
}
|
|
1818
|
-
}
|
|
1819
|
-
if (payloadVersion !== undefined) {
|
|
1820
|
-
this.protocolV2CompletedTargetVersions.set(targetId, payloadVersion);
|
|
1821
|
-
}
|
|
1822
|
-
}
|
|
1259
|
+
const allExpectedTargetsCompleted =
|
|
1260
|
+
expectedTargetIds.size > 0 &&
|
|
1261
|
+
Array.from(expectedTargetIds).every(targetId => completedTargetIds.has(targetId));
|
|
1262
|
+
if (allExpectedTargetsCompleted) {
|
|
1823
1263
|
this.postProgressMessage(100, 'installingFirmware');
|
|
1824
1264
|
return true;
|
|
1825
1265
|
}
|
|
@@ -1840,19 +1280,43 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1840
1280
|
return false;
|
|
1841
1281
|
}
|
|
1842
1282
|
|
|
1283
|
+
private getProtocolV2MissingTargetIds(
|
|
1284
|
+
statusTargets: ProtocolV2FirmwareUpdateStatusTarget[],
|
|
1285
|
+
expectedTargetIds: Set<number>
|
|
1286
|
+
) {
|
|
1287
|
+
const reportedTargetIds = new Set<number>();
|
|
1288
|
+
statusTargets.forEach(target => {
|
|
1289
|
+
const targetId = normalizeProtocolV2TargetId(target.target_id);
|
|
1290
|
+
if (targetId !== undefined) {
|
|
1291
|
+
reportedTargetIds.add(targetId);
|
|
1292
|
+
}
|
|
1293
|
+
});
|
|
1294
|
+
return Array.from(expectedTargetIds).filter(targetId => !reportedTargetIds.has(targetId));
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1843
1297
|
private async waitForProtocolV2FirmwareUpdateComplete(
|
|
1844
1298
|
targets: Array<{ target_id: number; path: string }>
|
|
1845
1299
|
) {
|
|
1846
|
-
this.protocolV2FinalStatusVerified = false;
|
|
1847
1300
|
const expectedTargetIds = new Set(targets.map(target => target.target_id));
|
|
1848
|
-
const expectedPaths = new Map(targets.map(target => [target.target_id, target.path]));
|
|
1849
1301
|
const startTime = Date.now();
|
|
1850
1302
|
let lastError: unknown;
|
|
1303
|
+
let shouldReconnect = true;
|
|
1304
|
+
let deviceInfo: ProtocolV2DeviceInfo | undefined;
|
|
1305
|
+
let missingTargetStatusSince: number | undefined;
|
|
1306
|
+
let missingTargetStatusKey: string | undefined;
|
|
1307
|
+
const resetMissingTargetStatusGrace = () => {
|
|
1308
|
+
missingTargetStatusSince = undefined;
|
|
1309
|
+
missingTargetStatusKey = undefined;
|
|
1310
|
+
};
|
|
1851
1311
|
|
|
1852
1312
|
while (Date.now() - startTime < PROTOCOL_V2_INSTALL_TIMEOUT) {
|
|
1853
1313
|
try {
|
|
1854
|
-
|
|
1855
|
-
|
|
1314
|
+
if (shouldReconnect) {
|
|
1315
|
+
await this.reconnectProtocolV2Device();
|
|
1316
|
+
deviceInfo = await this.verifyProtocolV2ReconnectIdentity();
|
|
1317
|
+
shouldReconnect = false;
|
|
1318
|
+
}
|
|
1319
|
+
const currentDeviceInfo = deviceInfo;
|
|
1856
1320
|
try {
|
|
1857
1321
|
const statusResponse = await this.device.getCommands().typedCall(
|
|
1858
1322
|
'DeviceFirmwareUpdateStatusGet',
|
|
@@ -1864,19 +1328,57 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1864
1328
|
path: true,
|
|
1865
1329
|
},
|
|
1866
1330
|
},
|
|
1867
|
-
{ timeoutMs:
|
|
1331
|
+
{ timeoutMs: PROTOCOL_V2_FIRMWARE_STATUS_RESPONSE_TIMEOUT }
|
|
1868
1332
|
);
|
|
1333
|
+
const statusTargets = (statusResponse.message.records ??
|
|
1334
|
+
[]) as ProtocolV2FirmwareUpdateStatusTarget[];
|
|
1335
|
+
if (this.assertProtocolV2TargetStatus(statusTargets, expectedTargetIds)) {
|
|
1336
|
+
return;
|
|
1337
|
+
}
|
|
1338
|
+
|
|
1869
1339
|
if (
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
expectedPaths
|
|
1874
|
-
)
|
|
1340
|
+
statusTargets.length === 0 &&
|
|
1341
|
+
currentDeviceInfo &&
|
|
1342
|
+
(await this.probeProtocolV2NormalMode(currentDeviceInfo))
|
|
1875
1343
|
) {
|
|
1876
|
-
|
|
1344
|
+
Log.log(
|
|
1345
|
+
'[FirmwareUpdateV4] empty firmware status after confirmed App reboot; update complete'
|
|
1346
|
+
);
|
|
1347
|
+
this.postProgressMessage(100, 'installingFirmware');
|
|
1877
1348
|
return;
|
|
1878
1349
|
}
|
|
1879
|
-
|
|
1350
|
+
|
|
1351
|
+
const missingTargetIds = this.getProtocolV2MissingTargetIds(
|
|
1352
|
+
statusTargets,
|
|
1353
|
+
expectedTargetIds
|
|
1354
|
+
);
|
|
1355
|
+
if (missingTargetIds.length > 0) {
|
|
1356
|
+
const now = Date.now();
|
|
1357
|
+
const missingKey = missingTargetIds.join(',');
|
|
1358
|
+
if (missingTargetStatusSince === undefined || missingTargetStatusKey !== missingKey) {
|
|
1359
|
+
missingTargetStatusSince = now;
|
|
1360
|
+
missingTargetStatusKey = missingKey;
|
|
1361
|
+
} else if (
|
|
1362
|
+
now - missingTargetStatusSince >=
|
|
1363
|
+
PROTOCOL_V2_MISSING_TARGET_STATUS_GRACE_TIMEOUT
|
|
1364
|
+
) {
|
|
1365
|
+
const reportedTargetIds = statusTargets
|
|
1366
|
+
.map(target => normalizeProtocolV2TargetId(target.target_id))
|
|
1367
|
+
.filter((targetId): targetId is number => targetId !== undefined);
|
|
1368
|
+
throw ERRORS.TypedError(
|
|
1369
|
+
HardwareErrorCode.FirmwareError,
|
|
1370
|
+
`Protocol V2 firmware status is missing requested records: targetIds=${missingKey} reportedTargetIds=${reportedTargetIds.join(
|
|
1371
|
+
','
|
|
1372
|
+
)}`
|
|
1373
|
+
);
|
|
1374
|
+
}
|
|
1375
|
+
lastError = new Error(
|
|
1376
|
+
`Protocol V2 firmware status is temporarily missing targetIds=${missingKey}`
|
|
1377
|
+
);
|
|
1378
|
+
} else {
|
|
1379
|
+
resetMissingTargetStatusGrace();
|
|
1380
|
+
lastError = new Error('Protocol V2 firmware targets are still installing');
|
|
1381
|
+
}
|
|
1880
1382
|
} catch (error) {
|
|
1881
1383
|
if (
|
|
1882
1384
|
error instanceof HardwareError &&
|
|
@@ -1884,19 +1386,31 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1884
1386
|
) {
|
|
1885
1387
|
throw error;
|
|
1886
1388
|
}
|
|
1389
|
+
// Only consecutive full status dumps can prove that a record is absent.
|
|
1390
|
+
// A reboot or transport interruption starts a fresh grace window.
|
|
1391
|
+
resetMissingTargetStatusGrace();
|
|
1887
1392
|
// App firmware does not register DeviceFirmwareUpdateStatusGet. Treat the
|
|
1888
1393
|
// missing endpoint as completion only after the runtime probe confirms App mode.
|
|
1889
1394
|
if (isProtocolV2FirmwareStatusEndpointUnavailable(error)) {
|
|
1890
|
-
if (
|
|
1395
|
+
if (!currentDeviceInfo) {
|
|
1396
|
+
throw ERRORS.TypedError(
|
|
1397
|
+
HardwareErrorCode.RuntimeError,
|
|
1398
|
+
'Protocol V2 device identity is unavailable during install polling'
|
|
1399
|
+
);
|
|
1400
|
+
}
|
|
1401
|
+
if (await this.probeProtocolV2NormalMode(currentDeviceInfo)) {
|
|
1891
1402
|
Log.log(
|
|
1892
1403
|
'[FirmwareUpdateV4] firmware status endpoint unavailable after confirmed App reboot'
|
|
1893
1404
|
);
|
|
1405
|
+
this.postProgressMessage(100, 'installingFirmware');
|
|
1894
1406
|
return;
|
|
1895
1407
|
}
|
|
1896
1408
|
lastError = new Error(
|
|
1897
1409
|
'Protocol V2 firmware status endpoint is unavailable while the device remains in loader mode'
|
|
1898
1410
|
);
|
|
1899
1411
|
} else {
|
|
1412
|
+
shouldReconnect = true;
|
|
1413
|
+
deviceInfo = undefined;
|
|
1900
1414
|
lastError = error;
|
|
1901
1415
|
Log.log(
|
|
1902
1416
|
'[FirmwareUpdateV4] DeviceFirmwareUpdateStatusGet unavailable during install: ',
|
|
@@ -1912,12 +1426,9 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1912
1426
|
if (error instanceof HardwareError && error.errorCode === HardwareErrorCode.FirmwareError) {
|
|
1913
1427
|
throw error;
|
|
1914
1428
|
}
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
) {
|
|
1919
|
-
throw error;
|
|
1920
|
-
}
|
|
1429
|
+
shouldReconnect = true;
|
|
1430
|
+
deviceInfo = undefined;
|
|
1431
|
+
resetMissingTargetStatusGrace();
|
|
1921
1432
|
Log.log('Protocol V2 firmware install device readiness probe failed: ', error);
|
|
1922
1433
|
}
|
|
1923
1434
|
await wait(1000);
|
|
@@ -1925,17 +1436,24 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1925
1436
|
|
|
1926
1437
|
throw ERRORS.TypedError(
|
|
1927
1438
|
HardwareErrorCode.RuntimeError,
|
|
1928
|
-
`Protocol V2 firmware update status
|
|
1439
|
+
`Protocol V2 firmware update status not complete within ${
|
|
1440
|
+
PROTOCOL_V2_INSTALL_TIMEOUT / 1000
|
|
1441
|
+
}s: ${this.normalizeErrorMessage(lastError)}`
|
|
1929
1442
|
);
|
|
1930
1443
|
}
|
|
1931
1444
|
|
|
1932
1445
|
private async exitProtocolV2BootloaderToNormal() {
|
|
1933
1446
|
await this.reconnectProtocolV2Device();
|
|
1934
|
-
await this.verifyProtocolV2ReconnectIdentity();
|
|
1935
|
-
|
|
1936
|
-
|
|
1447
|
+
const deviceInfo = await this.verifyProtocolV2ReconnectIdentity();
|
|
1448
|
+
try {
|
|
1449
|
+
if (await this.probeProtocolV2NormalMode(deviceInfo)) {
|
|
1450
|
+
Log.log('[FirmwareUpdateV4] device already returned to App mode; skip Normal reboot');
|
|
1451
|
+
return;
|
|
1452
|
+
}
|
|
1453
|
+
} catch (error) {
|
|
1454
|
+
Log.log('[FirmwareUpdateV4] unable to confirm App mode before Normal reboot: ', error);
|
|
1455
|
+
}
|
|
1937
1456
|
await this.protocolV2Reboot(DeviceRebootType.Normal);
|
|
1938
|
-
this.protocolV2ExecutionInLoader = false;
|
|
1939
1457
|
}
|
|
1940
1458
|
|
|
1941
1459
|
private async probeProtocolV2NormalMode(deviceInfo: ProtocolV2DeviceInfo) {
|
|
@@ -1951,10 +1469,13 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1951
1469
|
PROTOCOL_V2_FINAL_RECONNECT_TIMEOUT
|
|
1952
1470
|
);
|
|
1953
1471
|
|
|
1472
|
+
return this.getProtocolV2VersionResult(features);
|
|
1473
|
+
}
|
|
1474
|
+
|
|
1475
|
+
private getProtocolV2VersionResult(features: Features) {
|
|
1954
1476
|
const bootloaderVersion = getDeviceBootloaderVersion(features).join('.');
|
|
1955
1477
|
const bleVersion = getDeviceBLEFirmwareVersion(features).join('.');
|
|
1956
1478
|
const firmwareVersion = getDeviceFirmwareVersion(features).join('.');
|
|
1957
|
-
this.protocolV2LatestFinalFeatures = features;
|
|
1958
1479
|
if (firmwareVersion === '0.0.0') {
|
|
1959
1480
|
Log.warn(
|
|
1960
1481
|
'Protocol V2 firmware update finished but app firmware version is still 0.0.0. This is allowed for Pro2 debug BLE-only update flows.'
|
|
@@ -1968,27 +1489,20 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1968
1489
|
};
|
|
1969
1490
|
}
|
|
1970
1491
|
|
|
1971
|
-
private async completeProtocolV2FinalVerification() {
|
|
1972
|
-
if (this.protocolV2ExecutionInLoader || this.isProtocolV2BootloaderMode()) {
|
|
1973
|
-
await this.exitProtocolV2BootloaderToNormal();
|
|
1974
|
-
}
|
|
1975
|
-
const versions = await this.waitForProtocolV2FinalFeatures();
|
|
1976
|
-
this.assertExpectedProtocolV2Versions();
|
|
1977
|
-
this.postTipMessage(FirmwareUpdateTipMessage.FirmwareUpdateCompleted);
|
|
1978
|
-
DevicePool.resetState();
|
|
1979
|
-
return versions;
|
|
1980
|
-
}
|
|
1981
|
-
|
|
1982
1492
|
private async waitForProtocolV2ReconnectAndFeatures(timeout: number) {
|
|
1983
1493
|
const startTime = Date.now();
|
|
1984
1494
|
let lastError: unknown;
|
|
1495
|
+
let shouldReconnect = true;
|
|
1985
1496
|
|
|
1986
1497
|
while (Date.now() - startTime < timeout) {
|
|
1987
1498
|
try {
|
|
1988
|
-
|
|
1499
|
+
if (shouldReconnect) {
|
|
1500
|
+
await this.reconnectProtocolV2Device();
|
|
1501
|
+
shouldReconnect = false;
|
|
1502
|
+
}
|
|
1989
1503
|
const deviceInfo = await requestProtocolV2DeviceInfo({
|
|
1990
1504
|
commands: this.device.getCommands(),
|
|
1991
|
-
timeoutMs:
|
|
1505
|
+
timeoutMs: this.getProtocolV2DeviceInfoTimeout(),
|
|
1992
1506
|
// Completion needs target versions only; keep scope aligned with the request.
|
|
1993
1507
|
request: PROTOCOL_V2_VERSIONS_DEVICE_INFO_REQUEST,
|
|
1994
1508
|
});
|
|
@@ -1997,21 +1511,22 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1997
1511
|
deviceInfo,
|
|
1998
1512
|
PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT
|
|
1999
1513
|
);
|
|
2000
|
-
if (features.mode
|
|
2001
|
-
|
|
2002
|
-
HardwareErrorCode.DeviceNotFound,
|
|
2003
|
-
'Protocol V2 device is still in bootloader mode'
|
|
2004
|
-
);
|
|
1514
|
+
if (features.mode === 'normal' && !features.bootloaderMode) {
|
|
1515
|
+
return features;
|
|
2005
1516
|
}
|
|
2006
|
-
|
|
1517
|
+
lastError = ERRORS.TypedError(
|
|
1518
|
+
HardwareErrorCode.DeviceNotFound,
|
|
1519
|
+
'Protocol V2 device is still in bootloader mode'
|
|
1520
|
+
);
|
|
2007
1521
|
} catch (error) {
|
|
2008
1522
|
if (this.isProtocolV2ReconnectIdentityError(error)) {
|
|
2009
1523
|
throw error;
|
|
2010
1524
|
}
|
|
1525
|
+
shouldReconnect = true;
|
|
2011
1526
|
lastError = error;
|
|
2012
1527
|
Log.log('Protocol V2 normal mode not ready, polling Ping: ', error);
|
|
2013
|
-
await wait(1000);
|
|
2014
1528
|
}
|
|
1529
|
+
await wait(1000);
|
|
2015
1530
|
}
|
|
2016
1531
|
|
|
2017
1532
|
throw ERRORS.TypedError(
|
|
@@ -2035,9 +1550,13 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2035
1550
|
DataManager.isBrowserWebUsb(DataManager.getSettings('env')) &&
|
|
2036
1551
|
devicesDescriptor.length === 1
|
|
2037
1552
|
) {
|
|
1553
|
+
const descriptor = devicesDescriptor[0];
|
|
1554
|
+
if (!this.protocolV2ExpectedSerialNumber && descriptor.path !== this.protocolV2ExpectedPath) {
|
|
1555
|
+
throw ERRORS.TypedError(HardwareErrorCode.DeviceNotFound);
|
|
1556
|
+
}
|
|
2038
1557
|
this.device.updateDescriptor(
|
|
2039
1558
|
{
|
|
2040
|
-
...
|
|
1559
|
+
...descriptor,
|
|
2041
1560
|
protocolType: PROTOCOL_V2_CONNECT_PROTOCOL,
|
|
2042
1561
|
},
|
|
2043
1562
|
true
|
|
@@ -2048,20 +1567,29 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2048
1567
|
return;
|
|
2049
1568
|
}
|
|
2050
1569
|
|
|
2051
|
-
//
|
|
2052
|
-
//
|
|
1570
|
+
// Reinitialize every USB descriptor so DeviceInfo can provide the physical serial.
|
|
1571
|
+
// Enumeration order is not stable and must never decide the reconnect target.
|
|
2053
1572
|
const { deviceList } = await DevicePool.getDevices(devicesDescriptor, undefined, {
|
|
2054
1573
|
connectProtocol: PROTOCOL_V2_CONNECT_PROTOCOL,
|
|
2055
1574
|
});
|
|
2056
|
-
|
|
1575
|
+
const expectedSerialNumber = this.protocolV2ExpectedSerialNumber?.trim();
|
|
1576
|
+
const expectedPath = this.protocolV2ExpectedPath?.trim();
|
|
1577
|
+
const reconnectDevice = deviceList.find(candidate => {
|
|
1578
|
+
const candidateSerialNumber = candidate.getCurrentSerialNo?.().trim();
|
|
1579
|
+
if (expectedSerialNumber && candidateSerialNumber) {
|
|
1580
|
+
return candidateSerialNumber === expectedSerialNumber;
|
|
1581
|
+
}
|
|
1582
|
+
return !!expectedPath && candidate.getConnectId() === expectedPath;
|
|
1583
|
+
});
|
|
1584
|
+
if (!reconnectDevice) {
|
|
2057
1585
|
throw ERRORS.TypedError(HardwareErrorCode.DeviceNotFound);
|
|
2058
1586
|
}
|
|
2059
1587
|
|
|
2060
1588
|
Log.debug(
|
|
2061
|
-
'Protocol V2 firmware reconnect using
|
|
2062
|
-
|
|
1589
|
+
'Protocol V2 firmware reconnect using matched device:',
|
|
1590
|
+
reconnectDevice.getConnectId()
|
|
2063
1591
|
);
|
|
2064
|
-
this.device.updateFromCache(
|
|
1592
|
+
this.device.updateFromCache(reconnectDevice);
|
|
2065
1593
|
await this.ensureProtocolV2DeviceAcquired();
|
|
2066
1594
|
this.device.commands.disposed = false;
|
|
2067
1595
|
this.device.getCommands().mainId = this.device.mainId ?? '';
|
|
@@ -2085,35 +1613,99 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2085
1613
|
return this.normalizeErrorMessage(error).includes('Protocol V2 reconnect physical identity');
|
|
2086
1614
|
}
|
|
2087
1615
|
|
|
2088
|
-
private async
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
total_size: totalFileSize,
|
|
2102
|
-
data: chunk,
|
|
2103
|
-
},
|
|
2104
|
-
overwrite,
|
|
2105
|
-
append: false,
|
|
2106
|
-
ui_percentage: progress ?? undefined,
|
|
2107
|
-
});
|
|
2108
|
-
if (writeRes.type !== 'FilesystemFile') {
|
|
2109
|
-
if ((writeRes as any).type === 'CallMethodError') {
|
|
2110
|
-
if (((writeRes as any).message.error ?? '').indexOf(SESSION_ERROR) > -1) {
|
|
2111
|
-
throw ERRORS.TypedError(HardwareErrorCode.RuntimeError, SESSION_ERROR);
|
|
1616
|
+
private async protocolV2CommonUpdateProcess(params: ProtocolV2FileTransferParams) {
|
|
1617
|
+
let lastError: unknown;
|
|
1618
|
+
for (let attempt = 1; attempt <= PROTOCOL_V2_FILE_TRANSFER_RETRY_COUNT; attempt += 1) {
|
|
1619
|
+
try {
|
|
1620
|
+
return await this.protocolV2WriteWholeFile(params);
|
|
1621
|
+
} catch (error) {
|
|
1622
|
+
lastError = error;
|
|
1623
|
+
Log.error(
|
|
1624
|
+
`Protocol V2 file transfer failed path=${params.filePath} attempt=${attempt}/${PROTOCOL_V2_FILE_TRANSFER_RETRY_COUNT}; restarting from offset 0`,
|
|
1625
|
+
error
|
|
1626
|
+
);
|
|
1627
|
+
if (attempt === PROTOCOL_V2_FILE_TRANSFER_RETRY_COUNT) {
|
|
1628
|
+
break;
|
|
2112
1629
|
}
|
|
1630
|
+
await this.recoverProtocolV2FileTransfer();
|
|
2113
1631
|
}
|
|
2114
|
-
throw ERRORS.TypedError(HardwareErrorCode.EmmcFileWriteFirmwareError, 'transfer data error');
|
|
2115
1632
|
}
|
|
2116
|
-
|
|
1633
|
+
|
|
1634
|
+
throw ERRORS.TypedError(
|
|
1635
|
+
HardwareErrorCode.EmmcFileWriteFirmwareError,
|
|
1636
|
+
`transfer data error: ${getProtocolV2UnknownErrorText(lastError)}`
|
|
1637
|
+
);
|
|
1638
|
+
}
|
|
1639
|
+
|
|
1640
|
+
private async protocolV2WriteWholeFile({
|
|
1641
|
+
payload,
|
|
1642
|
+
filePath,
|
|
1643
|
+
processedSize,
|
|
1644
|
+
totalSize,
|
|
1645
|
+
onTransferredBytes,
|
|
1646
|
+
}: ProtocolV2FileTransferParams) {
|
|
1647
|
+
const chunkSize = this.getProtocolV2FirmwareChunkSize();
|
|
1648
|
+
const getUploadProgress = (fileOffset: number) => {
|
|
1649
|
+
if (totalSize !== undefined && processedSize !== undefined) {
|
|
1650
|
+
return Math.min(Math.ceil(((processedSize + fileOffset) / totalSize) * 100), 99);
|
|
1651
|
+
}
|
|
1652
|
+
return Math.min(Math.ceil((fileOffset / payload.byteLength) * 100), 99);
|
|
1653
|
+
};
|
|
1654
|
+
|
|
1655
|
+
try {
|
|
1656
|
+
await writeProtocolV2File({
|
|
1657
|
+
commands: this.device.getCommands(),
|
|
1658
|
+
path: filePath,
|
|
1659
|
+
data: payload,
|
|
1660
|
+
totalSize: payload.byteLength,
|
|
1661
|
+
chunkSize,
|
|
1662
|
+
overwrite: true,
|
|
1663
|
+
append: false,
|
|
1664
|
+
writeWithResponse: true,
|
|
1665
|
+
maxChunkRetries: 0,
|
|
1666
|
+
getUiPercentage: ({ offset, chunkLength }) =>
|
|
1667
|
+
getProtocolV2DeviceTransferProgress(
|
|
1668
|
+
(processedSize ?? 0) + offset,
|
|
1669
|
+
(processedSize ?? 0) + offset + chunkLength,
|
|
1670
|
+
totalSize ?? payload.byteLength
|
|
1671
|
+
),
|
|
1672
|
+
onProgress: progress => {
|
|
1673
|
+
const transferredBytes = (processedSize ?? 0) + progress.transferredBytes;
|
|
1674
|
+
onTransferredBytes?.(transferredBytes);
|
|
1675
|
+
this.postProgressMessage(getUploadProgress(progress.transferredBytes), 'transferData', {
|
|
1676
|
+
transferredBytes,
|
|
1677
|
+
totalBytes: totalSize ?? payload.byteLength,
|
|
1678
|
+
rateBytesPerSecond: progress.rateBytesPerSecond,
|
|
1679
|
+
elapsedMs: progress.elapsedMs,
|
|
1680
|
+
});
|
|
1681
|
+
},
|
|
1682
|
+
});
|
|
1683
|
+
} catch (error) {
|
|
1684
|
+
if (
|
|
1685
|
+
error instanceof HardwareError &&
|
|
1686
|
+
error.errorCode === HardwareErrorCode.RuntimeError &&
|
|
1687
|
+
error.message.includes('FilesystemFileWrite')
|
|
1688
|
+
) {
|
|
1689
|
+
throw ERRORS.TypedError(HardwareErrorCode.EmmcFileWriteFirmwareError, error.message);
|
|
1690
|
+
}
|
|
1691
|
+
throw error;
|
|
1692
|
+
}
|
|
1693
|
+
|
|
1694
|
+
return totalSize !== undefined ? (processedSize ?? 0) + payload.byteLength : 0;
|
|
1695
|
+
}
|
|
1696
|
+
|
|
1697
|
+
private getProtocolV2FirmwareTransferTransport() {
|
|
1698
|
+
const env = DataManager.getSettings('env');
|
|
1699
|
+
if (env && DataManager.isBleConnect(env)) {
|
|
1700
|
+
return 'BLE';
|
|
1701
|
+
}
|
|
1702
|
+
if (
|
|
1703
|
+
env &&
|
|
1704
|
+
(DataManager.isBrowserWebUsb(env) || DataManager.isDesktopWebUsb(env) || env === 'web')
|
|
1705
|
+
) {
|
|
1706
|
+
return 'WebUSB';
|
|
1707
|
+
}
|
|
1708
|
+
return env ?? 'unknown';
|
|
2117
1709
|
}
|
|
2118
1710
|
|
|
2119
1711
|
private async recoverProtocolV2FileTransfer() {
|