@onekeyfe/hd-core 1.2.0-alpha.11 → 1.2.0-alpha.110
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 +390 -0
- package/__tests__/DeviceCommands.test.ts +652 -0
- package/__tests__/DeviceEventRegistration.test.ts +49 -0
- package/__tests__/base64Data.test.ts +70 -0
- package/__tests__/chain-params-onekey-compatibility.test.ts +229 -0
- package/__tests__/check-all-firmware-release-protocol-v2.test.ts +830 -0
- package/__tests__/check-firmware-release-protocol-v2.test.ts +270 -0
- package/__tests__/connectSettings.test.ts +45 -5
- package/__tests__/core-dispose.test.ts +34 -0
- package/__tests__/core-error-output.test.ts +39 -0
- package/__tests__/core-initialization.test.ts +23 -0
- package/__tests__/device-connector-protocol.test.ts +81 -0
- package/__tests__/device-features-state.test.ts +78 -0
- package/__tests__/device-identity.test.ts +52 -0
- package/__tests__/device-initialize-timeout.test.ts +56 -0
- package/__tests__/device-lifecycle-events.test.ts +462 -0
- package/__tests__/device-pool-state.test.ts +191 -0
- package/__tests__/device-settings.test.ts +437 -0
- package/__tests__/device-state-contract.test.ts +28 -0
- package/__tests__/device-state-events.test.ts +286 -0
- package/__tests__/device-state-mapper.test.ts +288 -0
- package/__tests__/device-state-projector.test.ts +114 -0
- package/__tests__/device-state-store.test.ts +109 -0
- package/__tests__/device-utils.test.ts +154 -0
- package/__tests__/device-wallet-session-store.test.ts +348 -1
- package/__tests__/deviceFeaturesUtils.test.ts +39 -0
- package/__tests__/devicePoolBootloaderPath.test.ts +64 -0
- package/__tests__/deviceSettings.test.ts +112 -0
- package/__tests__/deviceUploadNft.test.ts +345 -0
- package/__tests__/evmLedgerLegacySafety.test.ts +12 -6
- package/__tests__/evmSignTransaction.test.ts +69 -0
- package/__tests__/evmSignTypedData.test.ts +80 -0
- package/__tests__/filesystemValidation.test.ts +53 -0
- package/__tests__/firmware-memory-host.test.ts +126 -0
- package/__tests__/firmware-update/check-all-firmware-release.test.ts +175 -0
- package/__tests__/firmware-update/device-update-bootloader.test.ts +109 -0
- package/__tests__/firmware-update/firmware-artifact-source.test.ts +171 -0
- package/__tests__/firmware-update/firmware-host-binding.test.ts +79 -0
- package/__tests__/firmware-update/firmware-preparation-error.test.ts +27 -0
- package/__tests__/firmware-update/firmware-prepared-host-digest.test.ts +583 -0
- package/__tests__/firmware-update/firmware-prepared-resource-archive.test.ts +150 -0
- package/__tests__/firmware-update/firmware-update-bootloader-poll.test.ts +375 -0
- package/__tests__/firmware-update/firmware-update-capabilities.test.ts +13 -0
- package/__tests__/firmware-update/firmware-update-plan-bootloader-identity.test.ts +474 -0
- package/__tests__/firmware-update/firmware-update-plan.test.ts +819 -0
- package/__tests__/firmware-update/firmware-update-prepared-plan.test.ts +283 -0
- package/__tests__/firmware-update/firmware-update-prepared-resource-executors.test.ts +326 -0
- package/__tests__/firmware-update/firmware-update-v2-download-before-boot.test.ts +457 -0
- package/__tests__/firmware-update/firmware-update-v3-reconnect.test.ts +175 -0
- package/__tests__/firmware-update/firmware-update-v4-install-poll.test.ts +178 -0
- package/__tests__/firmware-update/firmware-update-v4-parameters.test.ts +85 -0
- package/__tests__/firmware-update/firmware-upload-source.test.ts +70 -0
- package/__tests__/get-device-state.test.ts +617 -0
- package/__tests__/homescreen.test.ts +81 -0
- package/__tests__/kaspa-public-types.type-test.ts +16 -0
- package/__tests__/kaspa-use-tweak-pro2.test.ts +20 -0
- package/__tests__/kaspaSignTransaction.test.ts +578 -0
- package/__tests__/known-device-public-types.type-test.ts +3 -0
- package/__tests__/logBlockEvent.test.ts +140 -1
- package/__tests__/method-protocol-support.test.ts +144 -0
- package/__tests__/networkUtils.test.ts +406 -0
- package/__tests__/open-wallet-session-error-response.test.ts +43 -0
- package/__tests__/open-wallet-session.test.ts +1899 -0
- package/__tests__/pro2Nft.test.ts +108 -0
- package/__tests__/pro2Wallpaper.test.ts +8 -19
- package/__tests__/protocol-binding.test.ts +89 -0
- package/__tests__/protocol-v2-bootloader-mode.test.ts +37 -0
- package/__tests__/protocol-v2-firmware-targets.test.ts +105 -0
- package/__tests__/protocol-v2-ping.test.ts +28 -0
- package/__tests__/protocol-v2-resources.test.ts +270 -0
- package/__tests__/protocol-v2-ui-lifecycle.test.ts +91 -0
- package/__tests__/protocol-v2-unlock-policy.test.ts +308 -0
- package/__tests__/protocol-v2.test.ts +7490 -2020
- package/__tests__/protocolV2FileWrite.test.ts +285 -0
- package/__tests__/protocolV2UiInteraction.test.ts +329 -0
- package/__tests__/public-device-state-api.test.ts +275 -0
- package/__tests__/public-pro2-api-boundary.test.ts +107 -0
- package/__tests__/refresh-device-state.test.ts +103 -0
- package/__tests__/request-context-logging.test.ts +16 -0
- package/__tests__/resourceBase64Boundary.test.ts +48 -0
- package/__tests__/search-devices.test.ts +151 -0
- package/__tests__/ton-sign-message.test.ts +84 -0
- package/__tests__/tron-sign-message-init.test.ts +74 -0
- package/__tests__/uiProgressThrottle.test.ts +74 -0
- package/__tests__/uiPromiseRegistry.test.ts +115 -0
- package/dist/api/BaseMethod.d.ts +13 -2
- package/dist/api/BaseMethod.d.ts.map +1 -1
- package/dist/api/CheckAllFirmwareRelease.d.ts +25 -1
- package/dist/api/CheckAllFirmwareRelease.d.ts.map +1 -1
- package/dist/api/CheckBLEFirmwareRelease.d.ts +3 -2
- package/dist/api/CheckBLEFirmwareRelease.d.ts.map +1 -1
- package/dist/api/CheckBootloaderRelease.d.ts +2 -1
- package/dist/api/CheckBootloaderRelease.d.ts.map +1 -1
- package/dist/api/CheckFirmwareRelease.d.ts +3 -2
- package/dist/api/CheckFirmwareRelease.d.ts.map +1 -1
- package/dist/api/CipherKeyValue.d.ts +1 -0
- package/dist/api/CipherKeyValue.d.ts.map +1 -1
- package/dist/api/ClearSessionCache.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/DirList.d.ts +1 -0
- package/dist/api/DirList.d.ts.map +1 -1
- package/dist/api/DirMake.d.ts +1 -0
- package/dist/api/DirMake.d.ts.map +1 -1
- package/dist/api/DirRemove.d.ts +1 -0
- package/dist/api/DirRemove.d.ts.map +1 -1
- package/dist/api/FileDelete.d.ts +1 -0
- package/dist/api/FileDelete.d.ts.map +1 -1
- package/dist/api/FileRead.d.ts +1 -0
- package/dist/api/FileRead.d.ts.map +1 -1
- package/dist/api/FileWrite.d.ts +2 -0
- package/dist/api/FileWrite.d.ts.map +1 -1
- package/dist/api/FirmwareUpdate.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV2.d.ts +13 -2
- package/dist/api/FirmwareUpdateV2.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV3.d.ts +7 -3
- package/dist/api/FirmwareUpdateV3.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV4.d.ts +59 -12
- package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
- package/dist/api/GetDeviceState.d.ts +8 -0
- package/dist/api/GetDeviceState.d.ts.map +1 -0
- package/dist/api/GetFeatures.d.ts +1 -1
- package/dist/api/GetFeatures.d.ts.map +1 -1
- package/dist/api/GetOnekeyFeatures.d.ts.map +1 -1
- package/dist/api/GetPassphraseState.d.ts +1 -0
- package/dist/api/GetPassphraseState.d.ts.map +1 -1
- package/dist/api/OpenWalletSession.d.ts +8 -0
- package/dist/api/OpenWalletSession.d.ts.map +1 -0
- package/dist/api/PathInfo.d.ts +1 -0
- package/dist/api/PathInfo.d.ts.map +1 -1
- package/dist/api/PromptWebDeviceAccess.d.ts.map +1 -1
- package/dist/api/SearchDevices.d.ts +3 -0
- package/dist/api/SearchDevices.d.ts.map +1 -1
- package/dist/api/UploadPortfolio.d.ts +1 -2
- package/dist/api/UploadPortfolio.d.ts.map +1 -1
- package/dist/api/alephium/AlephiumGetAddress.d.ts.map +1 -1
- package/dist/api/alephium/AlephiumSignMessage.d.ts.map +1 -1
- package/dist/api/alephium/AlephiumSignTransaction.d.ts.map +1 -1
- package/dist/api/algo/AlgoGetAddress.d.ts +1 -0
- package/dist/api/algo/AlgoGetAddress.d.ts.map +1 -1
- package/dist/api/algo/AlgoSignTransaction.d.ts +1 -0
- package/dist/api/algo/AlgoSignTransaction.d.ts.map +1 -1
- package/dist/api/allnetwork/AllNetworkGetAddressBase.d.ts +1 -0
- package/dist/api/allnetwork/AllNetworkGetAddressBase.d.ts.map +1 -1
- package/dist/api/aptos/AptosGetAddress.d.ts +1 -0
- package/dist/api/aptos/AptosGetAddress.d.ts.map +1 -1
- package/dist/api/aptos/AptosGetPublicKey.d.ts +1 -0
- package/dist/api/aptos/AptosGetPublicKey.d.ts.map +1 -1
- package/dist/api/aptos/AptosSignInMessage.d.ts +1 -0
- package/dist/api/aptos/AptosSignInMessage.d.ts.map +1 -1
- package/dist/api/aptos/AptosSignMessage.d.ts +1 -0
- package/dist/api/aptos/AptosSignMessage.d.ts.map +1 -1
- package/dist/api/aptos/AptosSignTransaction.d.ts +1 -0
- package/dist/api/aptos/AptosSignTransaction.d.ts.map +1 -1
- package/dist/api/benfen/BenfenGetAddress.d.ts.map +1 -1
- package/dist/api/benfen/BenfenGetPublicKey.d.ts.map +1 -1
- package/dist/api/benfen/BenfenSignMessage.d.ts.map +1 -1
- package/dist/api/benfen/BenfenSignTransaction.d.ts.map +1 -1
- package/dist/api/btc/BTCGetAddress.d.ts +1 -0
- package/dist/api/btc/BTCGetAddress.d.ts.map +1 -1
- package/dist/api/btc/BTCGetPublicKey.d.ts +1 -0
- package/dist/api/btc/BTCGetPublicKey.d.ts.map +1 -1
- package/dist/api/btc/BTCSignMessage.d.ts +1 -0
- package/dist/api/btc/BTCSignMessage.d.ts.map +1 -1
- package/dist/api/btc/BTCSignPsbt.d.ts +1 -0
- package/dist/api/btc/BTCSignPsbt.d.ts.map +1 -1
- package/dist/api/btc/BTCSignTransaction.d.ts +1 -0
- package/dist/api/btc/BTCSignTransaction.d.ts.map +1 -1
- package/dist/api/btc/BTCVerifyMessage.d.ts +1 -0
- package/dist/api/btc/BTCVerifyMessage.d.ts.map +1 -1
- package/dist/api/btc/helpers/versionLimit.d.ts +2 -0
- package/dist/api/btc/helpers/versionLimit.d.ts.map +1 -1
- package/dist/api/cardano/CardanoGetAddress.d.ts +1 -0
- package/dist/api/cardano/CardanoGetAddress.d.ts.map +1 -1
- package/dist/api/cardano/CardanoGetPublicKey.d.ts +1 -0
- package/dist/api/cardano/CardanoGetPublicKey.d.ts.map +1 -1
- package/dist/api/cardano/CardanoSignMessage.d.ts +1 -0
- package/dist/api/cardano/CardanoSignMessage.d.ts.map +1 -1
- package/dist/api/cardano/CardanoSignTransaction.d.ts +1 -0
- package/dist/api/cardano/CardanoSignTransaction.d.ts.map +1 -1
- package/dist/api/conflux/ConfluxGetAddress.d.ts +2 -0
- package/dist/api/conflux/ConfluxGetAddress.d.ts.map +1 -1
- package/dist/api/conflux/ConfluxSignMessage.d.ts +2 -0
- package/dist/api/conflux/ConfluxSignMessage.d.ts.map +1 -1
- package/dist/api/conflux/ConfluxSignMessageCIP23.d.ts +2 -0
- package/dist/api/conflux/ConfluxSignMessageCIP23.d.ts.map +1 -1
- package/dist/api/conflux/ConfluxSignTransaction.d.ts +2 -0
- package/dist/api/conflux/ConfluxSignTransaction.d.ts.map +1 -1
- package/dist/api/cosmos/CosmosGetAddress.d.ts +1 -0
- package/dist/api/cosmos/CosmosGetAddress.d.ts.map +1 -1
- package/dist/api/cosmos/CosmosGetPublicKey.d.ts +1 -0
- package/dist/api/cosmos/CosmosGetPublicKey.d.ts.map +1 -1
- package/dist/api/cosmos/CosmosSignTransaction.d.ts +1 -0
- package/dist/api/cosmos/CosmosSignTransaction.d.ts.map +1 -1
- package/dist/api/device/DeviceCancel.d.ts +1 -0
- package/dist/api/device/DeviceCancel.d.ts.map +1 -1
- package/dist/api/device/DeviceChangePin.d.ts +1 -0
- package/dist/api/device/DeviceChangePin.d.ts.map +1 -1
- package/dist/api/device/DeviceFullyUploadResource.d.ts +1 -2
- package/dist/api/device/DeviceFullyUploadResource.d.ts.map +1 -1
- package/dist/api/device/DeviceLock.d.ts +1 -0
- package/dist/api/device/DeviceLock.d.ts.map +1 -1
- package/dist/api/device/DeviceRebootToBoardloader.d.ts +1 -0
- package/dist/api/device/DeviceRebootToBoardloader.d.ts.map +1 -1
- package/dist/api/device/DeviceRebootToBootloader.d.ts +1 -0
- package/dist/api/device/DeviceRebootToBootloader.d.ts.map +1 -1
- package/dist/api/device/DeviceSettings.d.ts +1 -0
- package/dist/api/device/DeviceSettings.d.ts.map +1 -1
- package/dist/api/device/DeviceSupportFeatures.d.ts +1 -0
- package/dist/api/device/DeviceSupportFeatures.d.ts.map +1 -1
- package/dist/api/device/DeviceUnlock.d.ts +3 -2
- package/dist/api/device/DeviceUnlock.d.ts.map +1 -1
- package/dist/api/device/DeviceUpdateBootloader.d.ts +5 -1
- package/dist/api/device/DeviceUpdateBootloader.d.ts.map +1 -1
- package/dist/api/device/DeviceWipe.d.ts +1 -0
- package/dist/api/device/DeviceWipe.d.ts.map +1 -1
- package/dist/api/device/PreInitialize.d.ts +1 -0
- package/dist/api/device/PreInitialize.d.ts.map +1 -1
- package/dist/api/dynex/DnxGetAddress.d.ts.map +1 -1
- package/dist/api/dynex/DnxSignTransaction.d.ts.map +1 -1
- package/dist/api/evm/EVMGetAddress.d.ts +1 -0
- package/dist/api/evm/EVMGetAddress.d.ts.map +1 -1
- package/dist/api/evm/EVMGetPublicKey.d.ts +1 -0
- package/dist/api/evm/EVMGetPublicKey.d.ts.map +1 -1
- package/dist/api/evm/EVMSignMessage.d.ts +1 -0
- package/dist/api/evm/EVMSignMessage.d.ts.map +1 -1
- package/dist/api/evm/EVMSignMessageEIP712.d.ts.map +1 -1
- package/dist/api/evm/EVMSignTransaction.d.ts +1 -0
- package/dist/api/evm/EVMSignTransaction.d.ts.map +1 -1
- package/dist/api/evm/EVMSignTypedData.d.ts +1 -0
- package/dist/api/evm/EVMSignTypedData.d.ts.map +1 -1
- package/dist/api/evm/EVMVerifyMessage.d.ts +1 -0
- package/dist/api/evm/EVMVerifyMessage.d.ts.map +1 -1
- package/dist/api/evm/protocol.d.ts +3 -0
- package/dist/api/evm/protocol.d.ts.map +1 -0
- package/dist/api/filecoin/FilecoinGetAddress.d.ts +1 -0
- package/dist/api/filecoin/FilecoinGetAddress.d.ts.map +1 -1
- package/dist/api/filecoin/FilecoinSignTransaction.d.ts +1 -0
- package/dist/api/filecoin/FilecoinSignTransaction.d.ts.map +1 -1
- package/dist/api/firmware/FirmwareArtifactSource.d.ts +29 -0
- package/dist/api/firmware/FirmwareArtifactSource.d.ts.map +1 -0
- package/dist/api/firmware/FirmwareHostBinding.d.ts +6 -0
- package/dist/api/firmware/FirmwareHostBinding.d.ts.map +1 -0
- package/dist/api/firmware/FirmwareMemoryHost.d.ts +22 -0
- package/dist/api/firmware/FirmwareMemoryHost.d.ts.map +1 -0
- package/dist/api/firmware/FirmwarePreparationError.d.ts +3 -0
- package/dist/api/firmware/FirmwarePreparationError.d.ts.map +1 -0
- package/dist/api/firmware/FirmwarePreparedResourceArchive.d.ts +11 -0
- package/dist/api/firmware/FirmwarePreparedResourceArchive.d.ts.map +1 -0
- package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts +12 -3
- package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts.map +1 -1
- package/dist/api/firmware/FirmwareUpdateCapabilities.d.ts +3 -0
- package/dist/api/firmware/FirmwareUpdateCapabilities.d.ts.map +1 -0
- package/dist/api/firmware/FirmwareUpdatePlan.d.ts +72 -0
- package/dist/api/firmware/FirmwareUpdatePlan.d.ts.map +1 -0
- package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts +38 -0
- package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts.map +1 -0
- package/dist/api/firmware/getBinary.d.ts +14 -4
- package/dist/api/firmware/getBinary.d.ts.map +1 -1
- package/dist/api/firmware/protocolV2Release.d.ts +33 -0
- package/dist/api/firmware/protocolV2Release.d.ts.map +1 -0
- package/dist/api/firmware/updateBootloader.d.ts +2 -0
- package/dist/api/firmware/updateBootloader.d.ts.map +1 -1
- package/dist/api/firmware/uploadFirmware.d.ts +9 -1
- package/dist/api/firmware/uploadFirmware.d.ts.map +1 -1
- package/dist/api/helpers/base64Data.d.ts +16 -0
- package/dist/api/helpers/base64Data.d.ts.map +1 -0
- package/dist/api/helpers/filesystemValidation.d.ts +3 -0
- package/dist/api/helpers/filesystemValidation.d.ts.map +1 -1
- package/dist/api/helpers/paramsValidator.d.ts +1 -0
- package/dist/api/helpers/paramsValidator.d.ts.map +1 -1
- package/dist/api/helpers/protocolV2FileWrite.d.ts +43 -0
- package/dist/api/helpers/protocolV2FileWrite.d.ts.map +1 -0
- package/dist/api/index.d.ts +6 -30
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/kaspa/KaspaGetAddress.d.ts +4 -0
- package/dist/api/kaspa/KaspaGetAddress.d.ts.map +1 -1
- package/dist/api/kaspa/KaspaSignTransaction.d.ts +9 -1
- package/dist/api/kaspa/KaspaSignTransaction.d.ts.map +1 -1
- package/dist/api/kaspa/helpers/TransferSerialize.d.ts.map +1 -1
- package/dist/api/lightning/LnurlAuth.d.ts +0 -4
- package/dist/api/lightning/LnurlAuth.d.ts.map +1 -1
- package/dist/api/near/NearGetAddress.d.ts +1 -0
- package/dist/api/near/NearGetAddress.d.ts.map +1 -1
- package/dist/api/near/NearSignTransaction.d.ts +1 -0
- package/dist/api/near/NearSignTransaction.d.ts.map +1 -1
- package/dist/api/nem/NEMGetAddress.d.ts +1 -0
- package/dist/api/nem/NEMGetAddress.d.ts.map +1 -1
- package/dist/api/nem/NEMSignTransaction.d.ts +1 -0
- package/dist/api/nem/NEMSignTransaction.d.ts.map +1 -1
- package/dist/api/neo/NeoGetAddress.d.ts.map +1 -1
- package/dist/api/neo/NeoSignTransaction.d.ts.map +1 -1
- package/dist/api/nervos/NervosGetAddress.d.ts.map +1 -1
- package/dist/api/nervos/NervosSignTransaction.d.ts.map +1 -1
- package/dist/api/nexa/NexaGetAddress.d.ts.map +1 -1
- package/dist/api/nexa/NexaSignTransaction.d.ts +1 -0
- package/dist/api/nexa/NexaSignTransaction.d.ts.map +1 -1
- package/dist/api/nostr/NostrDecryptMessage.d.ts +1 -0
- package/dist/api/nostr/NostrDecryptMessage.d.ts.map +1 -1
- package/dist/api/nostr/NostrEncryptMessage.d.ts +1 -0
- package/dist/api/nostr/NostrEncryptMessage.d.ts.map +1 -1
- package/dist/api/nostr/NostrGetPublicKey.d.ts +1 -0
- package/dist/api/nostr/NostrGetPublicKey.d.ts.map +1 -1
- package/dist/api/nostr/NostrSignEvent.d.ts +1 -0
- package/dist/api/nostr/NostrSignEvent.d.ts.map +1 -1
- package/dist/api/nostr/NostrSignSchnorr.d.ts +1 -0
- package/dist/api/nostr/NostrSignSchnorr.d.ts.map +1 -1
- package/dist/api/polkadot/PolkadotGetAddress.d.ts +2 -1
- package/dist/api/polkadot/PolkadotGetAddress.d.ts.map +1 -1
- package/dist/api/polkadot/PolkadotSignTransaction.d.ts +1 -0
- package/dist/api/polkadot/PolkadotSignTransaction.d.ts.map +1 -1
- package/dist/api/polkadot/networks.d.ts +1 -1
- package/dist/api/protocol-v2/DeviceFactoryInfoGet.d.ts +1 -0
- package/dist/api/protocol-v2/DeviceFactoryInfoGet.d.ts.map +1 -1
- package/dist/api/protocol-v2/DeviceFactoryInfoSet.d.ts +1 -0
- package/dist/api/protocol-v2/DeviceFactoryInfoSet.d.ts.map +1 -1
- package/dist/api/protocol-v2/DeviceFirmwareUpdate.d.ts +1 -0
- package/dist/api/protocol-v2/DeviceFirmwareUpdate.d.ts.map +1 -1
- package/dist/api/protocol-v2/DeviceGetFirmwareUpdateStatus.d.ts +1 -0
- package/dist/api/protocol-v2/DeviceGetFirmwareUpdateStatus.d.ts.map +1 -1
- package/dist/api/protocol-v2/DeviceGetOnboardingStatus.d.ts +7 -0
- package/dist/api/protocol-v2/DeviceGetOnboardingStatus.d.ts.map +1 -0
- package/dist/api/protocol-v2/DeviceInfoGet.d.ts +1 -1
- package/dist/api/protocol-v2/DeviceInfoGet.d.ts.map +1 -1
- package/dist/api/protocol-v2/DeviceReboot.d.ts +1 -0
- package/dist/api/protocol-v2/DeviceReboot.d.ts.map +1 -1
- package/dist/api/protocol-v2/DeviceStatusGet.d.ts +1 -0
- package/dist/api/protocol-v2/DeviceStatusGet.d.ts.map +1 -1
- package/dist/api/protocol-v2/DeviceUploadNft.d.ts +30 -0
- package/dist/api/protocol-v2/DeviceUploadNft.d.ts.map +1 -0
- package/dist/api/protocol-v2/DeviceUploadWallpaper.d.ts +3 -3
- package/dist/api/protocol-v2/DeviceUploadWallpaper.d.ts.map +1 -1
- package/dist/api/protocol-v2/FilesystemFormat.d.ts +1 -0
- package/dist/api/protocol-v2/FilesystemFormat.d.ts.map +1 -1
- package/dist/api/protocol-v2/FilesystemPermissionFix.d.ts +1 -0
- package/dist/api/protocol-v2/FilesystemPermissionFix.d.ts.map +1 -1
- package/dist/api/protocol-v2/Ping.d.ts +3 -0
- package/dist/api/protocol-v2/Ping.d.ts.map +1 -1
- package/dist/api/protocol-v2/ProtocolInfoRequest.d.ts +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/scdo/ScdoGetAddress.d.ts.map +1 -1
- package/dist/api/scdo/ScdoSignMessage.d.ts.map +1 -1
- package/dist/api/scdo/ScdoSignTransaction.d.ts.map +1 -1
- package/dist/api/solana/SolGetAddress.d.ts +1 -0
- package/dist/api/solana/SolGetAddress.d.ts.map +1 -1
- package/dist/api/solana/SolSignMessage.d.ts +2 -1
- package/dist/api/solana/SolSignMessage.d.ts.map +1 -1
- package/dist/api/solana/SolSignOffchainMessage.d.ts +2 -1
- package/dist/api/solana/SolSignOffchainMessage.d.ts.map +1 -1
- package/dist/api/solana/SolSignTransaction.d.ts +3 -2
- package/dist/api/solana/SolSignTransaction.d.ts.map +1 -1
- package/dist/api/starcoin/StarcoinGetAddress.d.ts +1 -0
- package/dist/api/starcoin/StarcoinGetAddress.d.ts.map +1 -1
- package/dist/api/starcoin/StarcoinGetPublicKey.d.ts +1 -0
- package/dist/api/starcoin/StarcoinGetPublicKey.d.ts.map +1 -1
- package/dist/api/starcoin/StarcoinSignMessage.d.ts +1 -0
- package/dist/api/starcoin/StarcoinSignMessage.d.ts.map +1 -1
- package/dist/api/starcoin/StarcoinSignTransaction.d.ts +1 -0
- package/dist/api/starcoin/StarcoinSignTransaction.d.ts.map +1 -1
- package/dist/api/starcoin/StarcoinVerifyMessage.d.ts +1 -0
- package/dist/api/starcoin/StarcoinVerifyMessage.d.ts.map +1 -1
- package/dist/api/stellar/StellarGetAddress.d.ts +1 -2
- package/dist/api/stellar/StellarGetAddress.d.ts.map +1 -1
- package/dist/api/stellar/StellarSignTransaction.d.ts +2 -3
- package/dist/api/stellar/StellarSignTransaction.d.ts.map +1 -1
- package/dist/api/sui/SuiGetAddress.d.ts +2 -1
- package/dist/api/sui/SuiGetAddress.d.ts.map +1 -1
- package/dist/api/sui/SuiGetPublicKey.d.ts +2 -1
- package/dist/api/sui/SuiGetPublicKey.d.ts.map +1 -1
- package/dist/api/sui/SuiSignMessage.d.ts +2 -1
- package/dist/api/sui/SuiSignMessage.d.ts.map +1 -1
- package/dist/api/sui/SuiSignTransaction.d.ts +2 -1
- package/dist/api/sui/SuiSignTransaction.d.ts.map +1 -1
- package/dist/api/ton/TonGetAddress.d.ts +2 -1
- package/dist/api/ton/TonGetAddress.d.ts.map +1 -1
- package/dist/api/ton/TonSignData.d.ts +1 -0
- package/dist/api/ton/TonSignData.d.ts.map +1 -1
- package/dist/api/ton/TonSignMessage.d.ts +2 -1
- package/dist/api/ton/TonSignMessage.d.ts.map +1 -1
- package/dist/api/ton/TonSignProof.d.ts +2 -1
- package/dist/api/ton/TonSignProof.d.ts.map +1 -1
- package/dist/api/tron/TronGetAddress.d.ts +1 -0
- package/dist/api/tron/TronGetAddress.d.ts.map +1 -1
- package/dist/api/tron/TronSignMessage.d.ts +6 -1
- package/dist/api/tron/TronSignMessage.d.ts.map +1 -1
- package/dist/api/tron/TronSignTransaction.d.ts +2 -1
- package/dist/api/tron/TronSignTransaction.d.ts.map +1 -1
- package/dist/api/utils.d.ts +4 -1
- package/dist/api/utils.d.ts.map +1 -1
- package/dist/api/xrp/XrpGetAddress.d.ts +1 -0
- package/dist/api/xrp/XrpGetAddress.d.ts.map +1 -1
- package/dist/api/xrp/XrpSignTransaction.d.ts +1 -0
- package/dist/api/xrp/XrpSignTransaction.d.ts.map +1 -1
- package/dist/constants/index.d.ts +1 -1
- package/dist/constants/index.d.ts.map +1 -1
- package/dist/core/RequestQueue.d.ts +0 -2
- package/dist/core/RequestQueue.d.ts.map +1 -1
- package/dist/core/deviceEventRegistration.d.ts +13 -0
- package/dist/core/deviceEventRegistration.d.ts.map +1 -0
- package/dist/core/index.d.ts +3 -2
- 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 +11 -3
- 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 +76 -16
- package/dist/device/Device.d.ts.map +1 -1
- package/dist/device/DeviceCommands.d.ts +8 -6
- package/dist/device/DeviceCommands.d.ts.map +1 -1
- package/dist/device/DeviceConnector.d.ts +4 -3
- package/dist/device/DeviceConnector.d.ts.map +1 -1
- package/dist/device/DeviceFeaturesState.d.ts +3 -0
- package/dist/device/DeviceFeaturesState.d.ts.map +1 -0
- package/dist/device/DevicePool.d.ts +6 -2
- package/dist/device/DevicePool.d.ts.map +1 -1
- package/dist/device/DeviceStateMapper.d.ts +13 -0
- package/dist/device/DeviceStateMapper.d.ts.map +1 -0
- package/dist/device/DeviceStateProjector.d.ts +7 -0
- package/dist/device/DeviceStateProjector.d.ts.map +1 -0
- package/dist/device/DeviceStateStore.d.ts +21 -0
- package/dist/device/DeviceStateStore.d.ts.map +1 -0
- package/dist/device/DeviceWalletSessionStore.d.ts +13 -1
- package/dist/device/DeviceWalletSessionStore.d.ts.map +1 -1
- package/dist/device/cloneDeviceState.d.ts +2 -0
- package/dist/device/cloneDeviceState.d.ts.map +1 -0
- package/dist/device/deviceIdentity.d.ts +5 -0
- package/dist/device/deviceIdentity.d.ts.map +1 -0
- package/dist/deviceProfile/buildDeviceFeatures.d.ts +9 -3
- package/dist/deviceProfile/buildDeviceFeatures.d.ts.map +1 -1
- package/dist/deviceProfile/index.d.ts +0 -1
- package/dist/deviceProfile/index.d.ts.map +1 -1
- package/dist/deviceProfile/protocolV2DeviceIdentity.d.ts +7 -0
- package/dist/deviceProfile/protocolV2DeviceIdentity.d.ts.map +1 -0
- package/dist/events/call.d.ts +0 -7
- package/dist/events/call.d.ts.map +1 -1
- package/dist/events/core.d.ts +2 -2
- package/dist/events/core.d.ts.map +1 -1
- package/dist/events/device.d.ts +16 -2
- package/dist/events/device.d.ts.map +1 -1
- package/dist/events/iframe.d.ts +0 -1
- package/dist/events/iframe.d.ts.map +1 -1
- package/dist/events/logBlockEvent.d.ts +2 -0
- 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 +50 -4
- 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 +1779 -835
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12334 -5037
- package/dist/inject.d.ts +7 -3
- package/dist/inject.d.ts.map +1 -1
- package/dist/lowLevelInject.d.ts +1 -2
- package/dist/lowLevelInject.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/deviceSession.d.ts +7 -0
- package/dist/protocols/protocol-v2/deviceSession.d.ts.map +1 -0
- package/dist/protocols/protocol-v2/features.d.ts +29 -17
- 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/index.d.ts +2 -2
- package/dist/protocols/protocol-v2/index.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/resources.d.ts +12 -0
- package/dist/protocols/protocol-v2/resources.d.ts.map +1 -0
- package/dist/protocols/protocol-v2/settingsUnlockPolicy.d.ts +17 -0
- package/dist/protocols/protocol-v2/settingsUnlockPolicy.d.ts.map +1 -0
- package/dist/protocols/protocol-v2/uiInteraction.d.ts +42 -0
- package/dist/protocols/protocol-v2/uiInteraction.d.ts.map +1 -0
- package/dist/protocols/protocol-v2/unlockPolicy.d.ts +2 -0
- package/dist/protocols/protocol-v2/unlockPolicy.d.ts.map +1 -0
- 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 +18 -4
- 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 +48 -9
- package/dist/types/api/checkAllFirmwareRelease.d.ts.map +1 -1
- package/dist/types/api/checkBLEFirmwareRelease.d.ts +2 -13
- package/dist/types/api/checkBLEFirmwareRelease.d.ts.map +1 -1
- package/dist/types/api/checkBootloaderRelease.d.ts +2 -6
- package/dist/types/api/checkBootloaderRelease.d.ts.map +1 -1
- package/dist/types/api/checkFirmwareRelease.d.ts +2 -13
- package/dist/types/api/checkFirmwareRelease.d.ts.map +1 -1
- package/dist/types/api/checkFirmwareTypeAvailable.d.ts +2 -2
- package/dist/types/api/checkFirmwareTypeAvailable.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/deviceSettings.d.ts +11 -2
- package/dist/types/api/deviceSettings.d.ts.map +1 -1
- package/dist/types/api/deviceUnlock.d.ts +6 -2
- package/dist/types/api/deviceUnlock.d.ts.map +1 -1
- package/dist/types/api/deviceUpdateBootloader.d.ts +6 -0
- package/dist/types/api/deviceUpdateBootloader.d.ts.map +1 -1
- package/dist/types/api/export.d.ts +8 -3
- package/dist/types/api/export.d.ts.map +1 -1
- package/dist/types/api/firmwareUpdate.d.ts +67 -5
- package/dist/types/api/firmwareUpdate.d.ts.map +1 -1
- package/dist/types/api/firmwareUpdateCapabilities.d.ts +9 -0
- package/dist/types/api/firmwareUpdateCapabilities.d.ts.map +1 -0
- package/dist/types/api/firmwareUpdatePlan.d.ts +28 -0
- package/dist/types/api/firmwareUpdatePlan.d.ts.map +1 -0
- package/dist/types/api/firmwareUpdatePreparedPlan.d.ts +42 -0
- package/dist/types/api/firmwareUpdatePreparedPlan.d.ts.map +1 -0
- package/dist/types/api/getDeviceState.d.ts +12 -0
- package/dist/types/api/getDeviceState.d.ts.map +1 -0
- package/dist/types/api/getFeatures.d.ts.map +1 -1
- package/dist/types/api/getOnekeyFeatures.d.ts.map +1 -1
- package/dist/types/api/getPassphraseState.d.ts.map +1 -1
- package/dist/types/api/index.d.ts +26 -36
- package/dist/types/api/index.d.ts.map +1 -1
- package/dist/types/api/kaspaSignTransaction.d.ts +42 -4
- package/dist/types/api/kaspaSignTransaction.d.ts.map +1 -1
- package/dist/types/api/openWalletSession.d.ts +35 -0
- package/dist/types/api/openWalletSession.d.ts.map +1 -0
- package/dist/types/api/protocolV2.d.ts +11 -96
- package/dist/types/api/protocolV2.d.ts.map +1 -1
- package/dist/types/api/sessionCache.d.ts +4 -1
- package/dist/types/api/sessionCache.d.ts.map +1 -1
- package/dist/types/device.d.ts +142 -10
- 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 +59 -16
- package/dist/types/settings.d.ts.map +1 -1
- package/dist/utils/assets.d.ts +2 -1
- package/dist/utils/assets.d.ts.map +1 -1
- package/dist/utils/deviceFeaturesCompat.d.ts +14 -0
- package/dist/utils/deviceFeaturesCompat.d.ts.map +1 -0
- package/dist/utils/deviceFeaturesUtils.d.ts +10 -1
- package/dist/utils/deviceFeaturesUtils.d.ts.map +1 -1
- package/dist/utils/deviceInfoUtils.d.ts +9 -9
- package/dist/utils/deviceInfoUtils.d.ts.map +1 -1
- package/dist/utils/deviceSettings.d.ts +38 -7
- package/dist/utils/deviceSettings.d.ts.map +1 -1
- package/dist/utils/deviceVersionUtils.d.ts +7 -5
- package/dist/utils/deviceVersionUtils.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 +5 -4
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/networkUtils.d.ts +10 -1
- package/dist/utils/networkUtils.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 +38 -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/tracing.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 +6 -4
- package/src/api/BaseMethod.ts +38 -23
- package/src/api/CheckAllFirmwareRelease.ts +442 -4
- package/src/api/CheckBLEFirmwareRelease.ts +37 -5
- package/src/api/CheckBootloaderRelease.ts +37 -5
- package/src/api/CheckFirmwareRelease.ts +36 -9
- package/src/api/CipherKeyValue.ts +4 -0
- package/src/api/ClearSessionCache.ts +4 -0
- package/src/api/DetectDeviceConnectProtocol.ts +18 -0
- package/src/api/DirList.ts +9 -4
- package/src/api/DirMake.ts +7 -4
- package/src/api/DirRemove.ts +7 -4
- package/src/api/FileDelete.ts +7 -4
- package/src/api/FileRead.ts +13 -7
- package/src/api/FileWrite.ts +26 -189
- package/src/api/FirmwareUpdate.ts +30 -15
- package/src/api/FirmwareUpdateV2.ts +532 -169
- package/src/api/FirmwareUpdateV3.ts +343 -88
- package/src/api/FirmwareUpdateV4.ts +2331 -657
- package/src/api/GetDeviceState.ts +59 -0
- package/src/api/GetFeatures.ts +6 -4
- package/src/api/GetOnekeyFeatures.ts +1 -75
- package/src/api/GetPassphraseState.ts +36 -12
- package/src/api/OpenWalletSession.ts +268 -0
- package/src/api/PathInfo.ts +9 -4
- package/src/api/PromptWebDeviceAccess.ts +2 -7
- package/src/api/SearchDevices.ts +31 -4
- package/src/api/UploadPortfolio.ts +32 -4
- package/src/api/alephium/AlephiumGetAddress.ts +0 -4
- package/src/api/alephium/AlephiumSignMessage.ts +0 -4
- package/src/api/alephium/AlephiumSignTransaction.ts +2 -6
- package/src/api/algo/AlgoGetAddress.ts +4 -0
- package/src/api/algo/AlgoSignTransaction.ts +4 -0
- package/src/api/allnetwork/AllNetworkGetAddress.ts +21 -21
- package/src/api/allnetwork/AllNetworkGetAddressBase.ts +45 -32
- package/src/api/aptos/AptosGetAddress.ts +4 -0
- package/src/api/aptos/AptosGetPublicKey.ts +4 -0
- package/src/api/aptos/AptosSignInMessage.ts +4 -0
- package/src/api/aptos/AptosSignMessage.ts +4 -0
- package/src/api/aptos/AptosSignTransaction.ts +4 -0
- package/src/api/benfen/BenfenGetAddress.ts +11 -7
- package/src/api/benfen/BenfenGetPublicKey.ts +0 -4
- package/src/api/benfen/BenfenSignMessage.ts +0 -4
- package/src/api/benfen/BenfenSignTransaction.ts +0 -4
- package/src/api/btc/BTCGetAddress.ts +8 -1
- package/src/api/btc/BTCGetPublicKey.ts +9 -2
- package/src/api/btc/BTCSignMessage.ts +8 -1
- package/src/api/btc/BTCSignPsbt.ts +4 -0
- package/src/api/btc/BTCSignTransaction.ts +8 -1
- package/src/api/btc/BTCVerifyMessage.ts +8 -1
- package/src/api/btc/helpers/versionLimit.ts +7 -4
- package/src/api/cardano/CardanoGetAddress.ts +4 -0
- package/src/api/cardano/CardanoGetPublicKey.ts +4 -0
- package/src/api/cardano/CardanoSignMessage.ts +5 -1
- package/src/api/cardano/CardanoSignTransaction.ts +5 -1
- package/src/api/conflux/ConfluxGetAddress.ts +6 -0
- package/src/api/conflux/ConfluxSignMessage.ts +6 -0
- package/src/api/conflux/ConfluxSignMessageCIP23.ts +6 -0
- package/src/api/conflux/ConfluxSignTransaction.ts +8 -5
- package/src/api/cosmos/CosmosGetAddress.ts +4 -0
- package/src/api/cosmos/CosmosGetPublicKey.ts +4 -0
- package/src/api/cosmos/CosmosSignTransaction.ts +4 -0
- package/src/api/device/DeviceCancel.ts +5 -0
- package/src/api/device/DeviceChangePin.ts +28 -0
- package/src/api/device/DeviceFullyUploadResource.ts +4 -5
- package/src/api/device/DeviceLock.ts +5 -0
- package/src/api/device/DeviceRebootToBoardloader.ts +5 -0
- package/src/api/device/DeviceRebootToBootloader.ts +5 -0
- package/src/api/device/DeviceSettings.ts +195 -1
- package/src/api/device/DeviceSupportFeatures.ts +4 -0
- package/src/api/device/DeviceUnlock.ts +21 -3
- package/src/api/device/DeviceUpdateBootloader.ts +152 -8
- package/src/api/device/DeviceWipe.ts +25 -0
- package/src/api/device/PreInitialize.ts +4 -0
- package/src/api/dynex/DnxGetAddress.ts +0 -6
- package/src/api/dynex/DnxSignTransaction.ts +0 -6
- package/src/api/evm/EVMGetAddress.ts +6 -2
- package/src/api/evm/EVMGetPublicKey.ts +6 -2
- package/src/api/evm/EVMSignMessage.ts +6 -2
- package/src/api/evm/EVMSignMessageEIP712.ts +1 -11
- package/src/api/evm/EVMSignTransaction.ts +6 -2
- package/src/api/evm/EVMSignTypedData.ts +42 -50
- package/src/api/evm/EVMVerifyMessage.ts +6 -2
- package/src/api/evm/legacyV1/getAddress.ts +2 -2
- package/src/api/evm/legacyV1/getPublicKey.ts +2 -2
- package/src/api/evm/legacyV1/signMessage.ts +2 -2
- package/src/api/evm/legacyV1/signTypedData.ts +2 -2
- package/src/api/evm/legacyV1/signTypedHash.ts +2 -2
- package/src/api/evm/legacyV1/verifyMessage.ts +2 -2
- package/src/api/evm/protocol.ts +6 -0
- package/src/api/filecoin/FilecoinGetAddress.ts +4 -0
- package/src/api/filecoin/FilecoinSignTransaction.ts +4 -0
- package/src/api/firmware/FirmwareArtifactSource.ts +305 -0
- package/src/api/firmware/FirmwareHostBinding.ts +123 -0
- package/src/api/firmware/FirmwareMemoryHost.ts +143 -0
- package/src/api/firmware/FirmwarePreparationError.ts +12 -0
- package/src/api/firmware/FirmwarePreparedResourceArchive.ts +187 -0
- package/src/api/firmware/FirmwareUpdateBaseMethod.ts +214 -77
- package/src/api/firmware/FirmwareUpdateCapabilities.ts +9 -0
- package/src/api/firmware/FirmwareUpdatePlan.ts +946 -0
- package/src/api/firmware/FirmwareUpdatePreparedPlan.ts +486 -0
- package/src/api/firmware/getBinary.ts +8 -3
- package/src/api/firmware/protocolV2Release.ts +168 -0
- package/src/api/firmware/updateBootloader.ts +19 -4
- package/src/api/firmware/uploadFirmware.ts +157 -26
- package/src/api/helpers/base64Data.ts +85 -0
- package/src/api/helpers/batchGetPublickeys.ts +2 -2
- package/src/api/helpers/filesystemValidation.ts +61 -0
- package/src/api/helpers/paramsValidator.ts +1 -1
- package/src/api/helpers/protocolV2FileWrite.ts +392 -0
- package/src/api/index.ts +7 -31
- package/src/api/kaspa/KaspaGetAddress.ts +7 -0
- package/src/api/kaspa/KaspaSignTransaction.ts +369 -29
- package/src/api/kaspa/helpers/TransferSerialize.ts +17 -7
- package/src/api/lightning/LnurlAuth.ts +0 -4
- package/src/api/near/NearGetAddress.ts +4 -0
- package/src/api/near/NearSignTransaction.ts +4 -0
- package/src/api/nem/NEMGetAddress.ts +4 -0
- package/src/api/nem/NEMSignTransaction.ts +4 -0
- package/src/api/neo/NeoGetAddress.ts +0 -4
- package/src/api/neo/NeoSignTransaction.ts +0 -4
- package/src/api/nervos/NervosGetAddress.ts +0 -4
- package/src/api/nervos/NervosSignTransaction.ts +2 -6
- package/src/api/nexa/NexaGetAddress.ts +0 -4
- package/src/api/nexa/NexaSignTransaction.ts +3 -5
- package/src/api/nostr/NostrDecryptMessage.ts +4 -0
- package/src/api/nostr/NostrEncryptMessage.ts +4 -0
- package/src/api/nostr/NostrGetPublicKey.ts +4 -0
- package/src/api/nostr/NostrSignEvent.ts +4 -0
- package/src/api/nostr/NostrSignSchnorr.ts +4 -0
- package/src/api/polkadot/PolkadotGetAddress.ts +4 -0
- package/src/api/polkadot/PolkadotSignTransaction.ts +4 -0
- package/src/api/polkadot/networks.ts +3 -3
- package/src/api/protocol-v2/DeviceFactoryInfoGet.ts +5 -2
- package/src/api/protocol-v2/DeviceFactoryInfoSet.ts +5 -2
- package/src/api/protocol-v2/DeviceFirmwareUpdate.ts +8 -5
- package/src/api/protocol-v2/DeviceGetFirmwareUpdateStatus.ts +10 -6
- package/src/api/protocol-v2/DeviceGetOnboardingStatus.ts +21 -0
- package/src/api/protocol-v2/DeviceInfoGet.ts +8 -15
- package/src/api/protocol-v2/DeviceReboot.ts +7 -3
- package/src/api/protocol-v2/DeviceStatusGet.ts +6 -1
- package/src/api/protocol-v2/DeviceUploadNft.ts +245 -0
- package/src/api/protocol-v2/DeviceUploadWallpaper.ts +66 -73
- package/src/api/protocol-v2/FilesystemFormat.ts +5 -2
- package/src/api/protocol-v2/FilesystemPermissionFix.ts +5 -2
- package/src/api/protocol-v2/Ping.ts +37 -3
- package/src/api/protocol-v2/ProtocolInfoRequest.ts +8 -3
- package/src/api/protocol-v2/helpers.ts +1 -4
- package/src/api/scdo/ScdoGetAddress.ts +0 -4
- package/src/api/scdo/ScdoSignMessage.ts +0 -4
- package/src/api/scdo/ScdoSignTransaction.ts +0 -4
- package/src/api/solana/SolGetAddress.ts +4 -0
- package/src/api/solana/SolSignMessage.ts +5 -1
- package/src/api/solana/SolSignOffchainMessage.ts +5 -1
- package/src/api/solana/SolSignTransaction.ts +6 -2
- package/src/api/starcoin/StarcoinGetAddress.ts +4 -0
- package/src/api/starcoin/StarcoinGetPublicKey.ts +4 -0
- package/src/api/starcoin/StarcoinSignMessage.ts +4 -0
- package/src/api/starcoin/StarcoinSignTransaction.ts +4 -0
- package/src/api/starcoin/StarcoinVerifyMessage.ts +4 -0
- package/src/api/stellar/StellarGetAddress.ts +1 -10
- package/src/api/stellar/StellarSignTransaction.ts +1 -14
- package/src/api/sui/SuiGetAddress.ts +5 -1
- package/src/api/sui/SuiGetPublicKey.ts +5 -1
- package/src/api/sui/SuiSignMessage.ts +5 -1
- package/src/api/sui/SuiSignTransaction.ts +5 -1
- package/src/api/ton/TonGetAddress.ts +5 -1
- package/src/api/ton/TonSignData.ts +5 -1
- package/src/api/ton/TonSignMessage.ts +10 -4
- package/src/api/ton/TonSignProof.ts +5 -1
- package/src/api/tron/TronGetAddress.ts +4 -0
- package/src/api/tron/TronSignMessage.ts +19 -8
- package/src/api/tron/TronSignTransaction.ts +5 -1
- package/src/api/utils.ts +32 -3
- package/src/api/xrp/XrpGetAddress.ts +4 -0
- package/src/api/xrp/XrpSignTransaction.ts +5 -1
- package/src/constants/index.ts +1 -4
- package/src/core/RequestQueue.ts +0 -18
- package/src/core/deviceEventRegistration.ts +27 -0
- package/src/core/index.ts +387 -163
- package/src/core/uiPromiseRegistry.ts +63 -0
- package/src/data/messages/messages-protocol-v2.json +1030 -567
- package/src/data/messages/messages.json +286 -9
- package/src/data-manager/DataManager.ts +154 -43
- package/src/data-manager/TransportManager.ts +7 -1
- package/src/data-manager/connectSettings.ts +11 -0
- package/src/device/Device.ts +880 -278
- package/src/device/DeviceCommands.ts +177 -158
- package/src/device/DeviceConnector.ts +44 -21
- package/src/device/DeviceFeaturesState.ts +25 -0
- package/src/device/DevicePool.ts +105 -23
- package/src/device/DeviceStateMapper.ts +416 -0
- package/src/device/DeviceStateProjector.ts +152 -0
- package/src/device/DeviceStateStore.ts +232 -0
- package/src/device/DeviceWalletSessionStore.ts +72 -6
- package/src/device/cloneDeviceState.ts +39 -0
- package/src/device/deviceIdentity.ts +18 -0
- package/src/deviceProfile/buildDeviceFeatures.ts +223 -189
- package/src/deviceProfile/index.ts +0 -1
- package/src/deviceProfile/protocolV2DeviceIdentity.ts +35 -0
- package/src/events/call.ts +0 -6
- package/src/events/core.ts +0 -2
- package/src/events/device.ts +22 -1
- package/src/events/iframe.ts +0 -1
- package/src/events/logBlockEvent.ts +93 -2
- package/src/events/ui-promise.ts +4 -2
- package/src/events/ui-request.ts +61 -4
- package/src/events/ui-response.ts +12 -2
- package/src/index.ts +17 -4
- package/src/inject.ts +91 -57
- package/src/lowLevelInject.ts +11 -7
- package/src/protocols/protocol-v2/deviceSession.ts +24 -0
- package/src/protocols/protocol-v2/features.ts +117 -33
- package/src/protocols/protocol-v2/firmware.ts +3 -1
- package/src/protocols/protocol-v2/index.ts +9 -1
- package/src/protocols/protocol-v2/resources.ts +210 -0
- package/src/protocols/protocol-v2/settingsUnlockPolicy.ts +65 -0
- package/src/protocols/protocol-v2/uiInteraction.ts +250 -0
- package/src/protocols/protocol-v2/unlockPolicy.ts +1 -0
- package/src/protocols/protocol-v2/unlockPolicyRunner.ts +131 -0
- package/src/protocols/protocol-v2/walletSession.ts +420 -49
- package/src/topLevelInject.ts +11 -6
- package/src/types/api/checkAllFirmwareRelease.ts +72 -9
- package/src/types/api/checkBLEFirmwareRelease.ts +4 -13
- package/src/types/api/checkBootloaderRelease.ts +2 -6
- package/src/types/api/checkFirmwareRelease.ts +2 -13
- package/src/types/api/checkFirmwareTypeAvailable.ts +2 -2
- package/src/types/api/detectDeviceConnectProtocol.ts +6 -0
- package/src/types/api/deviceSettings.ts +26 -2
- package/src/types/api/deviceUnlock.ts +13 -2
- package/src/types/api/deviceUpdateBootloader.ts +7 -0
- package/src/types/api/export.ts +33 -1
- package/src/types/api/firmwareUpdate.ts +85 -11
- package/src/types/api/firmwareUpdateCapabilities.ts +9 -0
- package/src/types/api/firmwareUpdatePlan.ts +38 -0
- package/src/types/api/firmwareUpdatePreparedPlan.ts +53 -0
- package/src/types/api/getDeviceState.ts +19 -0
- package/src/types/api/getFeatures.ts +3 -0
- package/src/types/api/getOnekeyFeatures.ts +3 -0
- package/src/types/api/getPassphraseState.ts +7 -0
- package/src/types/api/index.ts +45 -75
- package/src/types/api/kaspaSignTransaction.ts +60 -4
- package/src/types/api/openWalletSession.ts +54 -0
- package/src/types/api/protocolV2.ts +24 -189
- package/src/types/api/sessionCache.ts +9 -4
- package/src/types/device.ts +217 -10
- package/src/types/params.ts +9 -2
- package/src/types/settings.ts +67 -25
- package/src/utils/assets.ts +4 -1
- package/src/utils/deviceFeaturesCompat.ts +191 -0
- package/src/utils/deviceFeaturesUtils.ts +42 -9
- package/src/utils/deviceInfoUtils.ts +29 -69
- package/src/utils/deviceSettings.ts +174 -39
- package/src/utils/deviceVersionUtils.ts +25 -44
- package/src/utils/findDefectiveBatchDevice.ts +3 -3
- package/src/utils/homescreen.ts +40 -0
- package/src/utils/index.ts +25 -7
- package/src/utils/networkUtils.ts +274 -16
- package/src/utils/pro2Nft.ts +165 -0
- package/src/utils/pro2Wallpaper.ts +44 -25
- package/src/utils/tracing.ts +3 -1
- package/src/utils/uiProgressThrottle.ts +63 -0
- package/tsconfig.type-tests.json +12 -0
- package/__tests__/RequestQueue.test.ts +0 -34
- package/dist/api/GetDeviceInfo.d.ts +0 -9
- package/dist/api/GetDeviceInfo.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/api/protocol-v2/DeviceSessionGet.d.ts +0 -6
- package/dist/api/protocol-v2/DeviceSessionGet.d.ts.map +0 -1
- package/dist/api/protocol-v2/DeviceSettingsGet.d.ts +0 -6
- package/dist/api/protocol-v2/DeviceSettingsGet.d.ts.map +0 -1
- package/dist/api/protocol-v2/DeviceSettingsPageShow.d.ts +0 -16
- package/dist/api/protocol-v2/DeviceSettingsPageShow.d.ts.map +0 -1
- package/dist/api/protocol-v2/DeviceSettingsSet.d.ts +0 -12
- package/dist/api/protocol-v2/DeviceSettingsSet.d.ts.map +0 -1
- package/dist/api/protocol-v2/FilesystemDiskControl.d.ts +0 -13
- package/dist/api/protocol-v2/FilesystemDiskControl.d.ts.map +0 -1
- package/dist/deviceProfile/buildDeviceProfile.d.ts +0 -21
- package/dist/deviceProfile/buildDeviceProfile.d.ts.map +0 -1
- package/dist/protocols/protocol-v2/unlockRetry.d.ts +0 -9
- package/dist/protocols/protocol-v2/unlockRetry.d.ts.map +0 -1
- package/dist/types/api/getDeviceInfo.d.ts +0 -87
- package/dist/types/api/getDeviceInfo.d.ts.map +0 -1
- package/src/api/GetDeviceInfo.ts +0 -145
- package/src/api/firmware/progressThrottle.ts +0 -44
- package/src/api/protocol-v2/DeviceSessionGet.ts +0 -15
- package/src/api/protocol-v2/DeviceSettingsGet.ts +0 -16
- package/src/api/protocol-v2/DeviceSettingsPageShow.ts +0 -59
- package/src/api/protocol-v2/DeviceSettingsSet.ts +0 -39
- package/src/api/protocol-v2/FilesystemDiskControl.ts +0 -50
- package/src/deviceProfile/buildDeviceProfile.ts +0 -352
- package/src/protocols/protocol-v2/unlockRetry.ts +0 -46
- package/src/types/api/getDeviceInfo.ts +0 -101
|
@@ -1,9 +1,13 @@
|
|
|
1
|
-
import { ERRORS, HardwareError, HardwareErrorCode, wait } from '@onekeyfe/hd-shared';
|
|
1
|
+
import { EDeviceType, ERRORS, HardwareError, HardwareErrorCode, wait } from '@onekeyfe/hd-shared';
|
|
2
|
+
import JSZip from 'jszip';
|
|
2
3
|
import {
|
|
3
4
|
DeviceRebootType,
|
|
4
5
|
PROTOCOL_V2_BLE_FILE_CHUNK_SIZE,
|
|
6
|
+
PROTOCOL_V2_BLE_FILE_READ_CHUNK_SIZE,
|
|
7
|
+
PROTOCOL_V2_BLE_FIRMWARE_FILE_CHUNK_SIZE,
|
|
5
8
|
PROTOCOL_V2_WEBUSB_FILE_CHUNK_SIZE,
|
|
6
9
|
} from '@onekeyfe/hd-transport';
|
|
10
|
+
import { sha256 } from '@noble/hashes/sha256';
|
|
7
11
|
|
|
8
12
|
import { FirmwareUpdateTipMessage, UI_REQUEST } from '../events/ui-request';
|
|
9
13
|
import { validateParams } from './helpers/paramsValidator';
|
|
@@ -12,27 +16,55 @@ import {
|
|
|
12
16
|
getDeviceBLEFirmwareVersion,
|
|
13
17
|
getDeviceBootloaderVersion,
|
|
14
18
|
getDeviceFirmwareVersion,
|
|
19
|
+
getDeviceType,
|
|
15
20
|
getFirmwareType,
|
|
16
21
|
getLogger,
|
|
17
22
|
} from '../utils';
|
|
18
23
|
import { getSysResourceBinary } from './firmware/getBinary';
|
|
24
|
+
import { normalizeFirmwarePreparationError } from './firmware/FirmwarePreparationError';
|
|
19
25
|
import { DataManager } from '../data-manager';
|
|
20
26
|
import { FirmwareUpdateBaseMethod } from './firmware/FirmwareUpdateBaseMethod';
|
|
21
27
|
import { DevicePool } from '../device/DevicePool';
|
|
22
28
|
import {
|
|
23
29
|
PROTOCOL_V2_VERSIONS_DEVICE_INFO_REQUEST,
|
|
24
30
|
ProtocolV2FirmwareTargetType,
|
|
31
|
+
isLegacyProtocolV2ProtocolInfo,
|
|
25
32
|
} from '../protocols/protocol-v2';
|
|
26
33
|
import { requestProtocolV2DeviceInfo } from '../protocols/protocol-v2/features';
|
|
27
34
|
import {
|
|
28
|
-
|
|
35
|
+
parseProtocolV2ResourceManifest,
|
|
36
|
+
selectProtocolV2ResourceManifestFiles,
|
|
37
|
+
} from '../protocols/protocol-v2/resources';
|
|
38
|
+
import {
|
|
29
39
|
getProtocolV2UnknownErrorText,
|
|
30
40
|
isProtocolV2DeviceDisconnectedError,
|
|
31
41
|
} from './protocol-v2/helpers';
|
|
42
|
+
import {
|
|
43
|
+
openFirmwareByteSource,
|
|
44
|
+
readFirmwareByteSourceFully,
|
|
45
|
+
writeFirmwareByteSource,
|
|
46
|
+
} from './firmware/FirmwareArtifactSource';
|
|
47
|
+
import {
|
|
48
|
+
registerFirmwareUpdateHostBinding,
|
|
49
|
+
resolveFirmwareUpdateHostBinding,
|
|
50
|
+
unregisterFirmwareUpdateHostBinding,
|
|
51
|
+
} from './firmware/FirmwareHostBinding';
|
|
52
|
+
import {
|
|
53
|
+
assertFirmwareUpdatePreparedPlanBinding,
|
|
54
|
+
assertFirmwareUpdatePreparedPlanDeviceIdentity,
|
|
55
|
+
prepareFirmwareUpdatePlan,
|
|
56
|
+
validateFirmwareUpdatePreparedPlan,
|
|
57
|
+
} from './firmware/FirmwareUpdatePreparedPlan';
|
|
58
|
+
import { prepareFirmwareUpdateV4MemoryHost } from './firmware/FirmwareMemoryHost';
|
|
59
|
+
import { buildProtocolV2LocalFirmwareUpdatePlan } from './firmware/FirmwareUpdatePlan';
|
|
32
60
|
|
|
33
|
-
import type {
|
|
61
|
+
import type {
|
|
62
|
+
FirmwareArtifactReference,
|
|
63
|
+
FirmwareUpdateV4Params,
|
|
64
|
+
FirmwareUpdateV4Target,
|
|
65
|
+
} from '../types/api/firmwareUpdate';
|
|
34
66
|
import type { EFirmwareType } from '@onekeyfe/hd-shared';
|
|
35
|
-
import type {
|
|
67
|
+
import type { ProtocolV2DeviceInfo } from '@onekeyfe/hd-transport';
|
|
36
68
|
import type { TypedResponseMessage } from '../device/DeviceCommands';
|
|
37
69
|
import type {
|
|
38
70
|
Features,
|
|
@@ -40,14 +72,26 @@ import type {
|
|
|
40
72
|
IProtocolV2FirmwareComponent,
|
|
41
73
|
IVersionArray,
|
|
42
74
|
} from '../types';
|
|
75
|
+
import type { FirmwareByteSource } from './firmware/FirmwareArtifactSource';
|
|
76
|
+
import type {
|
|
77
|
+
FirmwareMemoryArtifact,
|
|
78
|
+
FirmwareMemoryArtifactEntry,
|
|
79
|
+
FirmwareUpdateV4MemoryHost,
|
|
80
|
+
} from './firmware/FirmwareMemoryHost';
|
|
43
81
|
|
|
44
82
|
const Log = getLogger(LoggerNames.Method);
|
|
45
83
|
|
|
84
|
+
// Restored after a rebase dropped the declaration while keeping its use in
|
|
85
|
+
// fileWriteChunk; without it that error branch throws ReferenceError instead of
|
|
86
|
+
// the intended typed session error.
|
|
46
87
|
const SESSION_ERROR = 'session not found';
|
|
47
|
-
const PROTOCOL_V2_BOOTLOADER_RECONNECT_TIMEOUT =
|
|
88
|
+
const PROTOCOL_V2_BOOTLOADER_RECONNECT_TIMEOUT = 90 * 1000;
|
|
89
|
+
const PROTOCOL_V2_FINAL_RECONNECT_TIMEOUT = 3 * 60 * 1000;
|
|
48
90
|
const PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT = 5 * 1000;
|
|
91
|
+
const PROTOCOL_V2_FIRMWARE_STATUS_RESPONSE_TIMEOUT = 15 * 1000;
|
|
49
92
|
const PROTOCOL_V2_START_UPDATE_TIMEOUT = 3 * 60 * 1000;
|
|
50
|
-
const PROTOCOL_V2_INSTALL_TIMEOUT =
|
|
93
|
+
const PROTOCOL_V2_INSTALL_TIMEOUT = 8 * 60 * 1000;
|
|
94
|
+
const PROTOCOL_V2_MISSING_TARGET_STATUS_GRACE_TIMEOUT = 30 * 1000;
|
|
51
95
|
const PROTOCOL_V2_TARGET_STATUS_PENDING = 0;
|
|
52
96
|
const PROTOCOL_V2_TARGET_STATUS_IN_PROGRESS = 1;
|
|
53
97
|
const PROTOCOL_V2_TARGET_STATUS_FINISHED = 2;
|
|
@@ -58,12 +102,78 @@ const PROTOCOL_V2_MIN_FILE_CHUNK_SIZE = 64;
|
|
|
58
102
|
const PROTOCOL_V2_CONNECT_RETRY_COUNT = 10;
|
|
59
103
|
const PROTOCOL_V2_CONNECT_POLL_INTERVAL = 500;
|
|
60
104
|
const PROTOCOL_V2_CONNECT_SINGLE_TIMEOUT = 75 * 1000;
|
|
61
|
-
const PROTOCOL_V2_DEVICE_INFO_READY_TIMEOUT =
|
|
105
|
+
const PROTOCOL_V2_DEVICE_INFO_READY_TIMEOUT = 30 * 1000;
|
|
62
106
|
const PROTOCOL_V2_FILE_TRANSFER_RETRY_COUNT = 3;
|
|
107
|
+
const PROTOCOL_V2_INSTALL_STATUS_CONFLICT_CODE = 'FirmwareInstallStatusConflict';
|
|
63
108
|
const PROTOCOL_V2_OKPP_HEADER_SIZE = 0x52a0;
|
|
64
109
|
const PROTOCOL_V2_OKPP_PAYLOAD_HASH_OFFSET = 0x200;
|
|
65
110
|
const PROTOCOL_V2_OKPP_HEADER_HASH_OFFSET = 0x240;
|
|
66
111
|
const PROTOCOL_V2_OKPP_HASH_SIZE = 64;
|
|
112
|
+
const PROTOCOL_V2_RESOURCE_MANIFEST_MAX_BYTES = 1024 * 1024;
|
|
113
|
+
const PROTOCOL_V2_RESOURCE_FILE_MAX_COUNT = 512;
|
|
114
|
+
const PROTOCOL_V2_RESOURCE_TOTAL_MAX_BYTES = 256 * 1024 * 1024;
|
|
115
|
+
|
|
116
|
+
const PROTOCOL_V2_NEO_UNSUPPORTED_TARGETS = new Set<FirmwareUpdateV4Target>(['se03', 'se04']);
|
|
117
|
+
|
|
118
|
+
const getProtocolV2ZipEntrySizes = (entry: JSZip.JSZipObject) => {
|
|
119
|
+
// loadAsync records central-directory sizes on JSZip's documented private data object.
|
|
120
|
+
// Validate those bounds before calling async(), which allocates the decompressed entry.
|
|
121
|
+
const { compressedSize, uncompressedSize } = (entry as JSZipSizedEntry)._data ?? {};
|
|
122
|
+
if (
|
|
123
|
+
!Number.isSafeInteger(compressedSize) ||
|
|
124
|
+
Number(compressedSize) < 0 ||
|
|
125
|
+
!Number.isSafeInteger(uncompressedSize) ||
|
|
126
|
+
Number(uncompressedSize) <= 0
|
|
127
|
+
) {
|
|
128
|
+
throw ERRORS.TypedError(
|
|
129
|
+
HardwareErrorCode.RuntimeError,
|
|
130
|
+
`Protocol V2 local resource ZIP entry size is invalid: ${entry.name}`,
|
|
131
|
+
{ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
return {
|
|
135
|
+
compressedSize: Number(compressedSize),
|
|
136
|
+
uncompressedSize: Number(uncompressedSize),
|
|
137
|
+
};
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
export function assertProtocolV2FirmwareTargetsSupported(
|
|
141
|
+
deviceType: EDeviceType | string | undefined,
|
|
142
|
+
params: FirmwareUpdateV4Params,
|
|
143
|
+
hasExplicitTargetSelection = false
|
|
144
|
+
) {
|
|
145
|
+
const requestedTargets = new Set(params.targetsToUpdate ?? []);
|
|
146
|
+
const unsupportedTargets = new Set(
|
|
147
|
+
Array.from(requestedTargets).filter(target => PROTOCOL_V2_NEO_UNSUPPORTED_TARGETS.has(target))
|
|
148
|
+
);
|
|
149
|
+
if (params.se03Binary && (!hasExplicitTargetSelection || requestedTargets.has('se03'))) {
|
|
150
|
+
unsupportedTargets.add('se03');
|
|
151
|
+
}
|
|
152
|
+
if (params.se04Binary && (!hasExplicitTargetSelection || requestedTargets.has('se04'))) {
|
|
153
|
+
unsupportedTargets.add('se04');
|
|
154
|
+
}
|
|
155
|
+
if (
|
|
156
|
+
params.componentArtifacts?.se03 &&
|
|
157
|
+
(!hasExplicitTargetSelection || requestedTargets.has('se03'))
|
|
158
|
+
) {
|
|
159
|
+
unsupportedTargets.add('se03');
|
|
160
|
+
}
|
|
161
|
+
if (
|
|
162
|
+
params.componentArtifacts?.se04 &&
|
|
163
|
+
(!hasExplicitTargetSelection || requestedTargets.has('se04'))
|
|
164
|
+
) {
|
|
165
|
+
unsupportedTargets.add('se04');
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (!unsupportedTargets.size || deviceType === EDeviceType.Pro2) return;
|
|
169
|
+
|
|
170
|
+
const targetList = Array.from(unsupportedTargets).join(', ');
|
|
171
|
+
const message =
|
|
172
|
+
deviceType === EDeviceType.Neo
|
|
173
|
+
? `Neo only supports SE01 and SE02; unsupported firmware targets: ${targetList}`
|
|
174
|
+
: `Cannot safely update ${targetList} without a confirmed Pro2 device type`;
|
|
175
|
+
throw ERRORS.TypedError(HardwareErrorCode.DeviceNotSupportMethod, message);
|
|
176
|
+
}
|
|
67
177
|
|
|
68
178
|
const getProtocolV2DeviceTransferProgress = (
|
|
69
179
|
bytesBeforeChunk: number,
|
|
@@ -82,11 +192,6 @@ const getProtocolV2DeviceTransferProgress = (
|
|
|
82
192
|
return Math.min(Math.max(Math.ceil((bytesAfterChunk / totalBytes) * 100), 1), 99);
|
|
83
193
|
};
|
|
84
194
|
|
|
85
|
-
const formatProtocolV2TransferSpeed = (bytes: number, elapsedMs: number) => {
|
|
86
|
-
const safeElapsedMs = Math.max(elapsedMs, 1);
|
|
87
|
-
return (bytes / 1024 / (safeElapsedMs / 1000)).toFixed(2);
|
|
88
|
-
};
|
|
89
|
-
|
|
90
195
|
type ProtocolV2FirmwareUpdateStatusTarget = {
|
|
91
196
|
target_id: number | string;
|
|
92
197
|
status?: number | string;
|
|
@@ -94,46 +199,54 @@ type ProtocolV2FirmwareUpdateStatusTarget = {
|
|
|
94
199
|
path?: string;
|
|
95
200
|
};
|
|
96
201
|
|
|
97
|
-
type ProtocolV2FirmwareUpdateStartResponse =
|
|
98
|
-
| TypedResponseMessage<'Success'>
|
|
99
|
-
| TypedResponseMessage<'DeviceFirmwareUpdateStatus'>
|
|
100
|
-
| undefined;
|
|
202
|
+
type ProtocolV2FirmwareUpdateStartResponse = TypedResponseMessage<'Success'>;
|
|
101
203
|
|
|
102
204
|
type ProtocolV2TargetBinary = { fileName: string; binary: ArrayBuffer; targetId: number };
|
|
103
205
|
type ProtocolV2InstallItem = ProtocolV2TargetBinary & {
|
|
104
206
|
kind: ProtocolV2RemoteComponentTarget['kind'];
|
|
105
207
|
};
|
|
106
|
-
|
|
107
|
-
|
|
208
|
+
|
|
209
|
+
type ProtocolV2RemoteComponentBinary = ProtocolV2RemoteComponentTarget & {
|
|
210
|
+
binary: ArrayBuffer;
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
type ProtocolV2RemoteComponentTarget = {
|
|
214
|
+
fileName: string;
|
|
215
|
+
targetId: number;
|
|
216
|
+
kind: 'bootloader' | 'firmware';
|
|
108
217
|
};
|
|
109
218
|
|
|
110
|
-
type
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
219
|
+
type ProtocolV2InstallSource = {
|
|
220
|
+
fileName: string;
|
|
221
|
+
source: FirmwareByteSource;
|
|
222
|
+
targetId: number;
|
|
223
|
+
kind: ProtocolV2RemoteComponentTarget['kind'];
|
|
115
224
|
};
|
|
116
225
|
|
|
117
|
-
|
|
118
|
-
type ProtocolV2ResourceBundleBinary = {
|
|
226
|
+
type ProtocolV2ResourceBundleSource = {
|
|
119
227
|
name: string;
|
|
120
|
-
|
|
228
|
+
source: FirmwareByteSource;
|
|
121
229
|
devicePath: string;
|
|
122
|
-
/** 远端配置模式下的下载 URL(手动模式不填) */
|
|
123
|
-
url?: string;
|
|
124
230
|
version?: IVersionArray;
|
|
125
231
|
payloadHash?: string;
|
|
126
232
|
headerHash?: string;
|
|
127
233
|
};
|
|
128
234
|
|
|
129
|
-
type
|
|
235
|
+
type ProtocolV2LocalResourceArchive = {
|
|
130
236
|
binary: ArrayBuffer;
|
|
237
|
+
materializedEntries: FirmwareMemoryArtifactEntry[];
|
|
131
238
|
};
|
|
132
239
|
|
|
133
|
-
type
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
240
|
+
type JSZipSizedEntry = JSZip.JSZipObject & {
|
|
241
|
+
_data?: {
|
|
242
|
+
compressedSize?: unknown;
|
|
243
|
+
uncompressedSize?: unknown;
|
|
244
|
+
};
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
type ProtocolV2TransferBatch = {
|
|
248
|
+
installSources: ProtocolV2InstallSource[];
|
|
249
|
+
resourceSources: ProtocolV2ResourceBundleSource[];
|
|
137
250
|
};
|
|
138
251
|
|
|
139
252
|
type ProtocolV2OkppHeader = {
|
|
@@ -188,7 +301,29 @@ const PROTOCOL_V2_REMOTE_COMPONENT_TARGETS: Readonly<
|
|
|
188
301
|
},
|
|
189
302
|
};
|
|
190
303
|
|
|
191
|
-
const
|
|
304
|
+
const PROTOCOL_V2_FIRMWARE_STAGING_PATHS = new Set(
|
|
305
|
+
Object.values(PROTOCOL_V2_REMOTE_COMPONENT_TARGETS).map(
|
|
306
|
+
target => `${PROTOCOL_V2_FIRMWARE_STAGING_VOLUME}${target.fileName}`
|
|
307
|
+
)
|
|
308
|
+
);
|
|
309
|
+
|
|
310
|
+
const PROTOCOL_V2_BOOT_RESOURCE_PACKAGE_PATH = 'vol0:/loaders/bootloader/boot_resource.okpkg';
|
|
311
|
+
const PROTOCOL_V2_BOOT_RESOURCE_PACKAGE_STAGING_PATH = `${PROTOCOL_V2_BOOT_RESOURCE_PACKAGE_PATH}.staging`;
|
|
312
|
+
|
|
313
|
+
const isProtocolV2BootResourcePackagePath = (devicePath: string) =>
|
|
314
|
+
typeof devicePath === 'string' &&
|
|
315
|
+
devicePath.replace(/^vol0:(?!\/)/i, 'vol0:/').toLowerCase() ===
|
|
316
|
+
PROTOCOL_V2_BOOT_RESOURCE_PACKAGE_PATH;
|
|
317
|
+
|
|
318
|
+
const resolveProtocolV2ResourceWritePath = (devicePath: string) =>
|
|
319
|
+
isProtocolV2BootResourcePackagePath(devicePath)
|
|
320
|
+
? PROTOCOL_V2_BOOT_RESOURCE_PACKAGE_STAGING_PATH
|
|
321
|
+
: devicePath;
|
|
322
|
+
|
|
323
|
+
const PROTOCOL_V2_UPDATE_TARGET_BY_TARGET_ID = new Map<
|
|
324
|
+
number,
|
|
325
|
+
Exclude<FirmwareUpdateV4Target, 'boot_resources'>
|
|
326
|
+
>([
|
|
192
327
|
[ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_BOOTLOADER, 'boot'],
|
|
193
328
|
[ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_APPLICATION_P1, 'app_v1'],
|
|
194
329
|
[ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_APPLICATION_P2, 'app_v2'],
|
|
@@ -199,11 +334,24 @@ const PROTOCOL_V2_UPDATE_TARGET_BY_TARGET_ID = new Map<number, FirmwareUpdateV4T
|
|
|
199
334
|
[ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_SE04, 'se04'],
|
|
200
335
|
]);
|
|
201
336
|
|
|
337
|
+
const PROTOCOL_V2_INSTALL_TARGET_BY_UPDATE_TARGET = new Map<
|
|
338
|
+
Exclude<FirmwareUpdateV4Target, 'resource'>,
|
|
339
|
+
ProtocolV2RemoteComponentTarget
|
|
340
|
+
>(
|
|
341
|
+
Object.values(PROTOCOL_V2_REMOTE_COMPONENT_TARGETS).map(target => [
|
|
342
|
+
PROTOCOL_V2_UPDATE_TARGET_BY_TARGET_ID.get(target.targetId) as Exclude<
|
|
343
|
+
FirmwareUpdateV4Target,
|
|
344
|
+
'resource'
|
|
345
|
+
>,
|
|
346
|
+
target,
|
|
347
|
+
])
|
|
348
|
+
);
|
|
349
|
+
|
|
202
350
|
const PROTOCOL_V2_ROMLOADER_UNSUPPORTED_MESSAGE =
|
|
203
351
|
'FW_MGMT_TARGET_ROMLOADER is not accepted by the current Pro2 bootloader update request. Flash romloader with the loader-specific flow instead of firmwareUpdateV4.';
|
|
204
352
|
|
|
205
|
-
// hd-transport
|
|
206
|
-
//
|
|
353
|
+
// hd-transport historically decodes scalar enums as enum-name strings.
|
|
354
|
+
// Map them back to firmware protocol values before internal comparisons.
|
|
207
355
|
const PROTOCOL_V2_TARGET_ID_BY_DECODED_NAME = new Map<string, number>(
|
|
208
356
|
Object.entries(ProtocolV2FirmwareTargetType).map(([key, value]) => [key, value])
|
|
209
357
|
);
|
|
@@ -229,28 +377,55 @@ const isProtocolV2ReconnectProbeError = (error: unknown) => {
|
|
|
229
377
|
);
|
|
230
378
|
};
|
|
231
379
|
|
|
232
|
-
const
|
|
380
|
+
const PROTOCOL_V2_BLE_INSTALL_INTERRUPTION_ERROR_CODES = new Set<number>([
|
|
381
|
+
HardwareErrorCode.BleConnectedError,
|
|
382
|
+
HardwareErrorCode.BleCharacteristicNotifyError,
|
|
383
|
+
HardwareErrorCode.BleForceCleanRunPromise,
|
|
384
|
+
HardwareErrorCode.BleDeviceDisconnected,
|
|
385
|
+
]);
|
|
386
|
+
|
|
387
|
+
const isProtocolV2BleInstallInterruptionError = (error: unknown) => {
|
|
388
|
+
if (
|
|
389
|
+
error instanceof HardwareError &&
|
|
390
|
+
PROTOCOL_V2_BLE_INSTALL_INTERRUPTION_ERROR_CODES.has(error.errorCode)
|
|
391
|
+
) {
|
|
392
|
+
return true;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
const message = getProtocolV2UnknownErrorText(error).toLowerCase();
|
|
396
|
+
const compactMessage = message.replace(/\s+/gu, '');
|
|
397
|
+
return (
|
|
398
|
+
/react native ble transport (?:released|disconnected)/u.test(message) ||
|
|
399
|
+
(compactMessage.includes('rxerrorerror6') &&
|
|
400
|
+
(compactMessage.includes('multiplatformbleadapter') ||
|
|
401
|
+
compactMessage.includes('multipalformebleadapter')))
|
|
402
|
+
);
|
|
403
|
+
};
|
|
404
|
+
|
|
405
|
+
const isProtocolV2FirmwareStatusEndpointUnavailable = (error: unknown) => {
|
|
233
406
|
const message = getProtocolV2UnknownErrorText(error).toLowerCase();
|
|
234
407
|
return (
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
message.includes('
|
|
238
|
-
(message.includes('response timeout') && message.includes('devicefirmwareupdatestatusget')) ||
|
|
239
|
-
message.includes('device not found') ||
|
|
240
|
-
message.includes('transportnotfound')
|
|
408
|
+
message.includes('handler not registered') ||
|
|
409
|
+
message.includes('message handler not found') ||
|
|
410
|
+
message.includes('unsupported message')
|
|
241
411
|
);
|
|
242
412
|
};
|
|
243
413
|
|
|
244
|
-
const
|
|
414
|
+
const isProtocolV2FirmwareUpdateEndpointUnavailable = (error: unknown) => {
|
|
245
415
|
const message = getProtocolV2UnknownErrorText(error).toLowerCase();
|
|
246
416
|
return (
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
message.includes('
|
|
250
|
-
(message.includes('response timeout') && message.includes('devicefirmwareupdaterequest'))
|
|
417
|
+
message.includes('handler not registered') ||
|
|
418
|
+
message.includes('message handler not found') ||
|
|
419
|
+
message.includes('unsupported message')
|
|
251
420
|
);
|
|
252
421
|
};
|
|
253
422
|
|
|
423
|
+
const isProtocolV2TerminalInstallStatusError = (error: unknown) =>
|
|
424
|
+
error instanceof HardwareError &&
|
|
425
|
+
(error.errorCode === HardwareErrorCode.FirmwareError ||
|
|
426
|
+
error.errorCode === HardwareErrorCode.FirmwareVerificationFailed ||
|
|
427
|
+
error.params?.firmwareUpdateCode === PROTOCOL_V2_INSTALL_STATUS_CONFLICT_CODE);
|
|
428
|
+
|
|
254
429
|
const isProtocolV2TargetStatusFinished = (status: ProtocolV2FirmwareUpdateStatusTarget['status']) =>
|
|
255
430
|
normalizeProtocolV2TargetStatus(status) === PROTOCOL_V2_TARGET_STATUS_FINISHED;
|
|
256
431
|
|
|
@@ -379,6 +554,43 @@ const parseProtocolV2OkppHeader = (bytes: Uint8Array): ProtocolV2OkppHeader | nu
|
|
|
379
554
|
};
|
|
380
555
|
};
|
|
381
556
|
|
|
557
|
+
export const isProtocolV2FirmwareFingerprintValid = (
|
|
558
|
+
binary: ArrayBuffer | Uint8Array,
|
|
559
|
+
fingerprint: string | undefined
|
|
560
|
+
) => {
|
|
561
|
+
const expectedFingerprint = normalizeProtocolV2Hex(fingerprint);
|
|
562
|
+
if (!expectedFingerprint) return true;
|
|
563
|
+
return bytesToHex(sha256(toProtocolV2Bytes(binary))) === expectedFingerprint;
|
|
564
|
+
};
|
|
565
|
+
|
|
566
|
+
export const assertProtocolV2ReconnectIdentity = (
|
|
567
|
+
expectedSerialNumber?: string,
|
|
568
|
+
actualSerialNumber?: string,
|
|
569
|
+
expectedPath?: string,
|
|
570
|
+
actualPath?: string
|
|
571
|
+
) => {
|
|
572
|
+
if (expectedSerialNumber && actualSerialNumber) {
|
|
573
|
+
if (actualSerialNumber !== expectedSerialNumber) {
|
|
574
|
+
throw ERRORS.TypedError(
|
|
575
|
+
HardwareErrorCode.DeviceNotFound,
|
|
576
|
+
`Protocol V2 reconnect physical identity mismatch: expected ${expectedSerialNumber}, received ${actualSerialNumber}`
|
|
577
|
+
);
|
|
578
|
+
}
|
|
579
|
+
return;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
if (expectedPath && actualPath && expectedPath === actualPath) {
|
|
583
|
+
return;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
throw ERRORS.TypedError(
|
|
587
|
+
HardwareErrorCode.DeviceNotFound,
|
|
588
|
+
`Protocol V2 reconnect physical identity unavailable: expected path ${
|
|
589
|
+
expectedPath ?? 'unknown'
|
|
590
|
+
}, received ${actualPath ?? 'unknown'}`
|
|
591
|
+
);
|
|
592
|
+
};
|
|
593
|
+
|
|
382
594
|
/**
|
|
383
595
|
* FirmwareUpdateV4 is the complete Protocol V2 firmware update flow.
|
|
384
596
|
*
|
|
@@ -388,14 +600,65 @@ const parseProtocolV2OkppHeader = (bytes: Uint8Array): ProtocolV2OkppHeader | nu
|
|
|
388
600
|
* - completion waits for target status to finish, reboots to normal, then polls DeviceInfo
|
|
389
601
|
*/
|
|
390
602
|
export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareUpdateV4Params> {
|
|
603
|
+
private protocolV2ExpectedSerialNumber?: string;
|
|
604
|
+
|
|
605
|
+
private protocolV2ExpectedPath?: string;
|
|
606
|
+
|
|
607
|
+
private protocolV2HasExplicitTargetSelection = false;
|
|
608
|
+
|
|
609
|
+
getSupportedProtocols() {
|
|
610
|
+
return ['V2'] as const;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
private protocolV2PreparedSources: FirmwareByteSource[] = [];
|
|
614
|
+
|
|
615
|
+
private protocolV2ExecutionInLoader = false;
|
|
616
|
+
|
|
617
|
+
private protocolV2LegacyDirectUpdate = false;
|
|
618
|
+
|
|
619
|
+
private protocolV2BootResourceStagingSafe = false;
|
|
620
|
+
|
|
621
|
+
private protocolV2CompletedTargetVersions = new Map<number, number>();
|
|
622
|
+
|
|
623
|
+
private protocolV2LatestFinalFeatures?: Features;
|
|
624
|
+
|
|
625
|
+
private protocolV2FinalStatusVerified = false;
|
|
626
|
+
|
|
627
|
+
private protocolV2InstallAckReceived = false;
|
|
628
|
+
|
|
629
|
+
private protocolV2InstallBaselineVersions = new Map<number, string>();
|
|
630
|
+
|
|
631
|
+
private protocolV2LastRuntimeProbeFeatures?: Features;
|
|
632
|
+
|
|
391
633
|
init() {
|
|
392
634
|
this.allowDeviceMode = [UI_REQUEST.BOOTLOADER, UI_REQUEST.NOT_INITIALIZE];
|
|
393
635
|
this.requireDeviceMode = [];
|
|
636
|
+
this.unlockPolicy = 'unlock-before-run';
|
|
394
637
|
this.useDevicePassphraseState = false;
|
|
395
638
|
this.skipForceUpdateCheck = true;
|
|
396
639
|
|
|
397
640
|
const { payload } = this;
|
|
398
641
|
|
|
642
|
+
const removedResourceInputs = ['resourceFiles', 'resourceBundleArtifacts'].filter(input =>
|
|
643
|
+
Object.prototype.hasOwnProperty.call(payload, input)
|
|
644
|
+
);
|
|
645
|
+
if (removedResourceInputs.length > 0) {
|
|
646
|
+
throw ERRORS.TypedError(
|
|
647
|
+
HardwareErrorCode.CallMethodInvalidParameter,
|
|
648
|
+
`Protocol V2 ${removedResourceInputs.join(
|
|
649
|
+
' and '
|
|
650
|
+
)} inputs are no longer supported; use resourceArchiveBinary for local updates or preparedPlan with hostBindingGeneration for remote updates`
|
|
651
|
+
);
|
|
652
|
+
}
|
|
653
|
+
if (payload.hostBindingGeneration !== undefined && !payload.preparedPlan) {
|
|
654
|
+
throw ERRORS.TypedError(
|
|
655
|
+
HardwareErrorCode.CallMethodInvalidParameter,
|
|
656
|
+
'Protocol V2 hostBindingGeneration requires preparedPlan for remote updates; use direct binaries or resourceArchiveBinary for local updates'
|
|
657
|
+
);
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
this.protocolV2HasExplicitTargetSelection = payload.targetsToUpdate !== undefined;
|
|
661
|
+
|
|
399
662
|
if (typeof payload.retryCount !== 'number') {
|
|
400
663
|
payload.retryCount = PROTOCOL_V2_CONNECT_RETRY_COUNT;
|
|
401
664
|
}
|
|
@@ -421,13 +684,112 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
421
684
|
{ name: 'se02Binary', type: 'buffer' },
|
|
422
685
|
{ name: 'se03Binary', type: 'buffer' },
|
|
423
686
|
{ name: 'se04Binary', type: 'buffer' },
|
|
687
|
+
{ name: 'resourceArchiveBinary', type: 'buffer' },
|
|
424
688
|
{ name: 'firmwareType', type: 'string' },
|
|
425
689
|
{ name: 'targetsToUpdate', type: 'array', allowEmpty: true },
|
|
426
690
|
{ name: 'platform', type: 'string' },
|
|
427
|
-
{ name: '
|
|
691
|
+
{ name: 'expectedDeviceId', type: 'string' },
|
|
428
692
|
]);
|
|
693
|
+
if (
|
|
694
|
+
payload.expectedDeviceId !== undefined &&
|
|
695
|
+
(payload.expectedDeviceId.length === 0 || payload.expectedDeviceId.length > 160)
|
|
696
|
+
) {
|
|
697
|
+
throw ERRORS.TypedError(
|
|
698
|
+
HardwareErrorCode.CallMethodInvalidParameter,
|
|
699
|
+
'Protocol V2 expected device identity is invalid'
|
|
700
|
+
);
|
|
701
|
+
}
|
|
702
|
+
const preparedPlan = payload.preparedPlan
|
|
703
|
+
? validateFirmwareUpdatePreparedPlan(payload.preparedPlan)
|
|
704
|
+
: undefined;
|
|
705
|
+
const hasLocalArtifacts = [
|
|
706
|
+
payload.bootloaderBinary,
|
|
707
|
+
payload.romloaderBinary,
|
|
708
|
+
payload.applicationP1Binary,
|
|
709
|
+
payload.applicationP2Binary,
|
|
710
|
+
payload.coprocessorBinary,
|
|
711
|
+
payload.se01Binary,
|
|
712
|
+
payload.se02Binary,
|
|
713
|
+
payload.se03Binary,
|
|
714
|
+
payload.se04Binary,
|
|
715
|
+
payload.resourceArchiveBinary,
|
|
716
|
+
].some(Boolean);
|
|
717
|
+
if (preparedPlan && hasLocalArtifacts) {
|
|
718
|
+
throw ERRORS.TypedError(
|
|
719
|
+
HardwareErrorCode.CallMethodInvalidParameter,
|
|
720
|
+
'Prepared firmware plans cannot be combined with legacy or local firmware inputs'
|
|
721
|
+
);
|
|
722
|
+
}
|
|
723
|
+
let { artifactReader } = payload;
|
|
724
|
+
if (preparedPlan) {
|
|
725
|
+
artifactReader = resolveFirmwareUpdateHostBinding(
|
|
726
|
+
payload.hostBindingGeneration,
|
|
727
|
+
preparedPlan.preparedPlanDigest
|
|
728
|
+
).artifactReader;
|
|
729
|
+
}
|
|
730
|
+
if (preparedPlan) {
|
|
731
|
+
assertFirmwareUpdatePreparedPlanBinding({
|
|
732
|
+
preparedPlan,
|
|
733
|
+
executor: 'v4',
|
|
734
|
+
platform: payload.platform,
|
|
735
|
+
scopeTargets: [],
|
|
736
|
+
bindings: [],
|
|
737
|
+
});
|
|
738
|
+
if (payload.componentArtifacts) {
|
|
739
|
+
const componentBindings: Array<{
|
|
740
|
+
target: Exclude<FirmwareUpdateV4Target, 'resource'>;
|
|
741
|
+
artifact: FirmwareArtifactReference;
|
|
742
|
+
}> = Object.entries(payload.componentArtifacts).flatMap(([target, artifact]) =>
|
|
743
|
+
artifact
|
|
744
|
+
? [
|
|
745
|
+
{
|
|
746
|
+
target: target as Exclude<FirmwareUpdateV4Target, 'resource'>,
|
|
747
|
+
artifact: artifact as FirmwareArtifactReference,
|
|
748
|
+
},
|
|
749
|
+
]
|
|
750
|
+
: []
|
|
751
|
+
);
|
|
752
|
+
assertFirmwareUpdatePreparedPlanBinding({
|
|
753
|
+
preparedPlan,
|
|
754
|
+
executor: 'v4',
|
|
755
|
+
platform: payload.platform,
|
|
756
|
+
scopeTargets: componentBindings.map(binding => binding.target),
|
|
757
|
+
bindings: componentBindings,
|
|
758
|
+
});
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
const preparedExpectedTargetVersions = preparedPlan?.artifacts.reduce<
|
|
763
|
+
NonNullable<FirmwareUpdateV4Params['expectedTargetVersions']>
|
|
764
|
+
>((result, artifact) => {
|
|
765
|
+
if (
|
|
766
|
+
artifact.role === 'component' &&
|
|
767
|
+
artifact.target !== 'resource' &&
|
|
768
|
+
artifact.targetVersion &&
|
|
769
|
+
PROTOCOL_V2_INSTALL_TARGET_BY_UPDATE_TARGET.has(
|
|
770
|
+
artifact.target as Exclude<FirmwareUpdateV4Target, 'resource'>
|
|
771
|
+
)
|
|
772
|
+
) {
|
|
773
|
+
result[artifact.target as FirmwareUpdateV4Target] = artifact.targetVersion;
|
|
774
|
+
}
|
|
775
|
+
return result;
|
|
776
|
+
}, {});
|
|
777
|
+
const localTargetsToUpdate = payload.targetsToUpdate?.map((target: FirmwareUpdateV4Target) =>
|
|
778
|
+
target === 'boot_resources' ? 'resource' : target
|
|
779
|
+
);
|
|
780
|
+
if (
|
|
781
|
+
payload.resourceArchiveBinary &&
|
|
782
|
+
localTargetsToUpdate &&
|
|
783
|
+
!localTargetsToUpdate.includes('resource')
|
|
784
|
+
) {
|
|
785
|
+
localTargetsToUpdate.push('resource');
|
|
786
|
+
}
|
|
787
|
+
// 本地 ZIP 本身就是明确的资源升级请求,不要求调用方重复声明 target。
|
|
788
|
+
const resolvedLocalTargetsToUpdate =
|
|
789
|
+
localTargetsToUpdate ?? (payload.resourceArchiveBinary ? ['resource'] : undefined);
|
|
429
790
|
|
|
430
791
|
this.params = {
|
|
792
|
+
preparedPlan,
|
|
431
793
|
chunkSize: payload.chunkSize,
|
|
432
794
|
forcedUpdateRes: payload.forcedUpdateRes,
|
|
433
795
|
bootloaderBinary: payload.bootloaderBinary,
|
|
@@ -439,20 +801,36 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
439
801
|
se02Binary: payload.se02Binary,
|
|
440
802
|
se03Binary: payload.se03Binary,
|
|
441
803
|
se04Binary: payload.se04Binary,
|
|
442
|
-
|
|
443
|
-
firmwareType: payload.firmwareType,
|
|
444
|
-
targetsToUpdate:
|
|
804
|
+
resourceArchiveBinary: payload.resourceArchiveBinary,
|
|
805
|
+
firmwareType: preparedPlan?.firmwareType ?? payload.firmwareType,
|
|
806
|
+
targetsToUpdate: preparedPlan
|
|
807
|
+
? ([...preparedPlan.targetsToUpdate] as FirmwareUpdateV4Target[])
|
|
808
|
+
: resolvedLocalTargetsToUpdate,
|
|
809
|
+
expectedTargetVersions: preparedPlan
|
|
810
|
+
? preparedExpectedTargetVersions
|
|
811
|
+
: payload.expectedTargetVersions,
|
|
445
812
|
platform: payload.platform,
|
|
813
|
+
expectedDeviceId: payload.expectedDeviceId,
|
|
814
|
+
artifactReader,
|
|
815
|
+
componentArtifacts: preparedPlan ? undefined : payload.componentArtifacts,
|
|
446
816
|
};
|
|
447
817
|
}
|
|
448
818
|
|
|
449
|
-
private getProtocolV2FirmwareChunkSize() {
|
|
819
|
+
private getProtocolV2FirmwareChunkSize(direction: 'read' | 'write', filePath?: string) {
|
|
450
820
|
const payloadChunkSize = Number(this.params?.chunkSize);
|
|
451
821
|
const env = DataManager.getSettings('env');
|
|
452
|
-
const
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
822
|
+
const isBle = this.params?.platform === 'native' || (env && DataManager.isBleConnect(env));
|
|
823
|
+
let maxChunkSize = PROTOCOL_V2_WEBUSB_FILE_CHUNK_SIZE;
|
|
824
|
+
if (isBle) {
|
|
825
|
+
if (direction === 'read') {
|
|
826
|
+
maxChunkSize = PROTOCOL_V2_BLE_FILE_READ_CHUNK_SIZE;
|
|
827
|
+
} else {
|
|
828
|
+
// Firmware staging writes tolerate a larger BLE chunk than generic filesystem writes.
|
|
829
|
+
maxChunkSize = PROTOCOL_V2_FIRMWARE_STAGING_PATHS.has(filePath ?? '')
|
|
830
|
+
? PROTOCOL_V2_BLE_FIRMWARE_FILE_CHUNK_SIZE
|
|
831
|
+
: PROTOCOL_V2_BLE_FILE_CHUNK_SIZE;
|
|
832
|
+
}
|
|
833
|
+
}
|
|
456
834
|
if (!Number.isFinite(payloadChunkSize) || payloadChunkSize <= 0) {
|
|
457
835
|
return maxChunkSize;
|
|
458
836
|
}
|
|
@@ -463,199 +841,1053 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
463
841
|
}
|
|
464
842
|
|
|
465
843
|
async run() {
|
|
466
|
-
if (!this.device.isProtocolV2()) {
|
|
467
|
-
throw ERRORS.TypedError(
|
|
468
|
-
HardwareErrorCode.RuntimeError,
|
|
469
|
-
'firmwareUpdateV4 requires a Protocol V2 device'
|
|
470
|
-
);
|
|
471
|
-
}
|
|
472
|
-
|
|
473
844
|
Log.debug('FirmwareUpdateV4 strategy: Protocol V2');
|
|
474
845
|
return this.runProtocolV2();
|
|
475
846
|
}
|
|
476
847
|
|
|
477
848
|
private async runProtocolV2() {
|
|
849
|
+
await this.captureProtocolV2PhysicalIdentity();
|
|
478
850
|
const deviceFeatures = await this.getProtocolV2DeviceFeatures();
|
|
851
|
+
this.protocolV2InstallBaselineVersions =
|
|
852
|
+
this.getProtocolV2ObservableTargetVersions(deviceFeatures);
|
|
853
|
+
this.protocolV2LastRuntimeProbeFeatures = undefined;
|
|
854
|
+
const currentDeviceType = this.device.getCurrentDeviceType();
|
|
855
|
+
const capabilityDeviceType =
|
|
856
|
+
currentDeviceType === EDeviceType.Pro2 || currentDeviceType === EDeviceType.Neo
|
|
857
|
+
? currentDeviceType
|
|
858
|
+
: getDeviceType(deviceFeatures);
|
|
859
|
+
assertProtocolV2FirmwareTargetsSupported(
|
|
860
|
+
capabilityDeviceType,
|
|
861
|
+
this.params,
|
|
862
|
+
this.protocolV2HasExplicitTargetSelection
|
|
863
|
+
);
|
|
479
864
|
const deviceFirmwareType = getFirmwareType(deviceFeatures);
|
|
480
865
|
const firmwareType = this.params.firmwareType ?? deviceFirmwareType;
|
|
866
|
+
this.validateExpectedTargetVersions();
|
|
867
|
+
const wantsResources = !!this.params.targetsToUpdate?.includes('resource');
|
|
868
|
+
|
|
869
|
+
if (
|
|
870
|
+
!this.params.preparedPlan &&
|
|
871
|
+
this.params.resourceArchiveBinary &&
|
|
872
|
+
this.params.targetsToUpdate?.includes('resource')
|
|
873
|
+
) {
|
|
874
|
+
const localMemoryHost = await this.prepareProtocolV2LocalMemoryHost({
|
|
875
|
+
features: deviceFeatures,
|
|
876
|
+
firmwareType,
|
|
877
|
+
});
|
|
878
|
+
try {
|
|
879
|
+
return await this.runProtocolV2PreparedArtifacts(deviceFeatures, firmwareType);
|
|
880
|
+
} finally {
|
|
881
|
+
localMemoryHost.release();
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
const hasPreparedComponentArtifacts = Object.values(this.params.componentArtifacts ?? {}).some(
|
|
886
|
+
Boolean
|
|
887
|
+
);
|
|
888
|
+
if (this.params.preparedPlan || hasPreparedComponentArtifacts) {
|
|
889
|
+
return this.runProtocolV2PreparedArtifacts(deviceFeatures, firmwareType);
|
|
890
|
+
}
|
|
481
891
|
|
|
482
892
|
let fwBinaryMap: ProtocolV2TargetBinary[] = [];
|
|
483
893
|
let bootloaderBinary: ArrayBuffer | null = null;
|
|
484
894
|
let installItems: ProtocolV2InstallItem[] | undefined;
|
|
895
|
+
let resourceMemoryHost: FirmwareUpdateV4MemoryHost | undefined;
|
|
485
896
|
try {
|
|
486
897
|
this.postTipMessage(FirmwareUpdateTipMessage.StartDownloadFirmware);
|
|
487
898
|
fwBinaryMap = this.collectExplicitTargetBinaries();
|
|
488
899
|
bootloaderBinary = this.prepareBootloaderBinary();
|
|
489
|
-
|
|
900
|
+
const explicitInstallItems = this.buildProtocolV2InstallItems({
|
|
901
|
+
bootloaderBinary,
|
|
902
|
+
fwBinaryMap,
|
|
903
|
+
});
|
|
904
|
+
const missingFirmwareTargets = this.getMissingProtocolV2FirmwareTargets(explicitInstallItems);
|
|
905
|
+
const needsRemoteFirmware = this.params.targetsToUpdate?.length
|
|
906
|
+
? missingFirmwareTargets.length > 0
|
|
907
|
+
: explicitInstallItems.length === 0;
|
|
908
|
+
const needsSdkManagedArtifacts = needsRemoteFirmware || wantsResources;
|
|
909
|
+
if (
|
|
910
|
+
needsSdkManagedArtifacts &&
|
|
911
|
+
(this.params.artifactReader ||
|
|
912
|
+
DataManager.getSettings('firmwareManifestMode') === 'external-only')
|
|
913
|
+
) {
|
|
914
|
+
throw ERRORS.TypedError(
|
|
915
|
+
HardwareErrorCode.RuntimeError,
|
|
916
|
+
'Protocol V2 firmware artifacts must be prepared by the external firmware host',
|
|
917
|
+
{
|
|
918
|
+
firmwareUpdateCode: 'FirmwareArtifactsNotPrepared',
|
|
919
|
+
}
|
|
920
|
+
);
|
|
921
|
+
}
|
|
922
|
+
if (needsSdkManagedArtifacts) {
|
|
923
|
+
// Remote updates must use a freshly fetched config before any reboot or file write.
|
|
924
|
+
await DataManager.forceReloadData({
|
|
925
|
+
requireResources: wantsResources,
|
|
926
|
+
resourceDeviceType:
|
|
927
|
+
capabilityDeviceType === EDeviceType.Neo ? EDeviceType.Neo : EDeviceType.Pro2,
|
|
928
|
+
});
|
|
929
|
+
}
|
|
930
|
+
if (needsRemoteFirmware) {
|
|
490
931
|
const remoteBinaries = await this.prepareRemoteProtocolV2Binaries(
|
|
491
932
|
firmwareType,
|
|
492
|
-
deviceFeatures
|
|
933
|
+
deviceFeatures,
|
|
934
|
+
explicitInstallItems
|
|
493
935
|
);
|
|
494
936
|
bootloaderBinary = remoteBinaries.bootloaderBinary;
|
|
495
937
|
fwBinaryMap = remoteBinaries.fwBinaryMap;
|
|
496
938
|
installItems = remoteBinaries.installItems;
|
|
939
|
+
} else {
|
|
940
|
+
const selectedInstallItems = this.filterProtocolV2LocalInstallItems(explicitInstallItems);
|
|
941
|
+
bootloaderBinary =
|
|
942
|
+
selectedInstallItems.find(item => item.kind === 'bootloader')?.binary ?? null;
|
|
943
|
+
fwBinaryMap = selectedInstallItems
|
|
944
|
+
.filter(item => item.kind === 'firmware')
|
|
945
|
+
.map(item => ({
|
|
946
|
+
fileName: item.fileName,
|
|
947
|
+
binary: item.binary,
|
|
948
|
+
targetId: item.targetId,
|
|
949
|
+
}));
|
|
950
|
+
}
|
|
951
|
+
if (wantsResources) {
|
|
952
|
+
this.params.resourceArchiveBinary = await this.downloadRemoteProtocolV2ResourceArchive(
|
|
953
|
+
deviceFeatures
|
|
954
|
+
);
|
|
955
|
+
resourceMemoryHost = await this.prepareProtocolV2LocalMemoryHost({
|
|
956
|
+
features: deviceFeatures,
|
|
957
|
+
firmwareType,
|
|
958
|
+
availableInstallItems: installItems ?? explicitInstallItems,
|
|
959
|
+
});
|
|
497
960
|
}
|
|
498
961
|
this.postTipMessage(FirmwareUpdateTipMessage.FinishDownloadFirmware);
|
|
499
962
|
} catch (err) {
|
|
500
|
-
|
|
963
|
+
resourceMemoryHost?.release();
|
|
964
|
+
if (
|
|
965
|
+
typeof err === 'object' &&
|
|
966
|
+
err !== null &&
|
|
967
|
+
'params' in err &&
|
|
968
|
+
(err as HardwareError).params?.firmwareUpdateCode === 'FirmwareArtifactsNotPrepared'
|
|
969
|
+
) {
|
|
970
|
+
throw err;
|
|
971
|
+
}
|
|
972
|
+
if (err instanceof HardwareError && err.errorCode === HardwareErrorCode.NetworkError) {
|
|
973
|
+
throw err;
|
|
974
|
+
}
|
|
975
|
+
throw normalizeFirmwarePreparationError(err);
|
|
501
976
|
}
|
|
502
977
|
|
|
503
|
-
|
|
978
|
+
if (resourceMemoryHost) {
|
|
979
|
+
try {
|
|
980
|
+
return await this.runProtocolV2PreparedArtifacts(deviceFeatures, firmwareType, false);
|
|
981
|
+
} finally {
|
|
982
|
+
resourceMemoryHost.release();
|
|
983
|
+
}
|
|
984
|
+
}
|
|
504
985
|
|
|
505
|
-
if (!bootloaderBinary && fwBinaryMap.length === 0 && !
|
|
986
|
+
if (!bootloaderBinary && fwBinaryMap.length === 0 && !installItems?.length) {
|
|
506
987
|
throw ERRORS.TypedError(
|
|
507
988
|
HardwareErrorCode.FirmwareUpdateDownloadFailed,
|
|
508
989
|
'No firmware to update'
|
|
509
990
|
);
|
|
510
991
|
}
|
|
511
992
|
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
await this.executeProtocolV2Update({
|
|
993
|
+
return this.executeProtocolV2Update({
|
|
515
994
|
fwBinaryMap,
|
|
516
995
|
bootloaderBinary,
|
|
517
996
|
...(installItems ? { installItems } : undefined),
|
|
518
|
-
...(resourceBundles?.length ? { resourceBundles } : undefined),
|
|
519
997
|
});
|
|
520
|
-
|
|
521
|
-
await this.exitProtocolV2BootloaderToNormal();
|
|
522
|
-
|
|
523
|
-
const versions = await this.waitForProtocolV2FinalFeatures();
|
|
524
|
-
this.postTipMessage(FirmwareUpdateTipMessage.FirmwareUpdateCompleted);
|
|
525
|
-
DevicePool.resetState();
|
|
526
|
-
|
|
527
|
-
return versions;
|
|
528
998
|
}
|
|
529
999
|
|
|
530
|
-
private async
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
1000
|
+
private async openProtocolV2PreparedSource(artifact: FirmwareArtifactReference) {
|
|
1001
|
+
const source = await openFirmwareByteSource({
|
|
1002
|
+
artifact,
|
|
1003
|
+
reader: this.params.artifactReader,
|
|
1004
|
+
});
|
|
1005
|
+
if (!source) {
|
|
1006
|
+
throw ERRORS.TypedError(
|
|
1007
|
+
HardwareErrorCode.RuntimeError,
|
|
1008
|
+
`Firmware artifact ${artifact.artifactRef} is not prepared`,
|
|
1009
|
+
{
|
|
1010
|
+
firmwareUpdateCode: 'FirmwareArtifactsNotPrepared',
|
|
1011
|
+
}
|
|
1012
|
+
);
|
|
537
1013
|
}
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
private prepareBootloaderBinary(): ArrayBuffer | null {
|
|
542
|
-
return this.params.bootloaderBinary ?? null;
|
|
543
|
-
}
|
|
544
|
-
|
|
545
|
-
private hasExplicitProtocolV2Payload(fwBinaryMap: ProtocolV2TargetBinary[]) {
|
|
546
|
-
return (
|
|
547
|
-
!!this.params.resourceBundleFiles?.length ||
|
|
548
|
-
!!this.params.bootloaderBinary ||
|
|
549
|
-
fwBinaryMap.length > 0
|
|
550
|
-
);
|
|
1014
|
+
this.protocolV2PreparedSources.push(source);
|
|
1015
|
+
return source;
|
|
551
1016
|
}
|
|
552
1017
|
|
|
553
|
-
private
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
const installItems: ProtocolV2InstallItem[] = [];
|
|
561
|
-
|
|
562
|
-
if (bootloaderBinary) {
|
|
563
|
-
installItems.push({
|
|
564
|
-
fileName: 'bootloader.bin',
|
|
565
|
-
binary: bootloaderBinary,
|
|
566
|
-
targetId: ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_BOOTLOADER,
|
|
567
|
-
kind: 'bootloader',
|
|
568
|
-
});
|
|
1018
|
+
private async openProtocolV2MemorySource(binary: ArrayBuffer) {
|
|
1019
|
+
const source = await openFirmwareByteSource({ binary });
|
|
1020
|
+
if (!source) {
|
|
1021
|
+
throw ERRORS.TypedError(
|
|
1022
|
+
HardwareErrorCode.RuntimeError,
|
|
1023
|
+
'Protocol V2 firmware binary is empty'
|
|
1024
|
+
);
|
|
569
1025
|
}
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
return installItems;
|
|
1026
|
+
this.protocolV2PreparedSources.push(source);
|
|
1027
|
+
return source;
|
|
573
1028
|
}
|
|
574
1029
|
|
|
575
|
-
private
|
|
576
|
-
const
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
const orderedKeys = [
|
|
580
|
-
...(release.installOrder ?? []),
|
|
581
|
-
...Object.keys(components).filter(key => !release.installOrder?.includes(key)),
|
|
582
|
-
];
|
|
583
|
-
|
|
584
|
-
return orderedKeys
|
|
585
|
-
.map(key => {
|
|
586
|
-
const component = components[key];
|
|
587
|
-
return component ? ([key, component] as const) : undefined;
|
|
588
|
-
})
|
|
589
|
-
.filter((entry): entry is readonly [string, IProtocolV2FirmwareComponent] => !!entry);
|
|
1030
|
+
private async closeProtocolV2PreparedSources() {
|
|
1031
|
+
const sources = this.protocolV2PreparedSources.splice(0);
|
|
1032
|
+
await Promise.all(sources.map(source => source.close().catch(() => undefined)));
|
|
590
1033
|
}
|
|
591
1034
|
|
|
592
|
-
private
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
1035
|
+
private async prepareProtocolV2InstallSources(
|
|
1036
|
+
firmwareType: EFirmwareType,
|
|
1037
|
+
features: Features
|
|
1038
|
+
): Promise<ProtocolV2InstallSource[]> {
|
|
1039
|
+
if (this.params.preparedPlan) {
|
|
1040
|
+
const requestedTargets = new Set(
|
|
1041
|
+
this.params.preparedPlan.targetsToUpdate.filter(
|
|
1042
|
+
(target): target is Exclude<FirmwareUpdateV4Target, 'resource'> => target !== 'resource'
|
|
1043
|
+
)
|
|
1044
|
+
);
|
|
1045
|
+
const installSources: ProtocolV2InstallSource[] = [];
|
|
1046
|
+
const preparedTargets = new Set<Exclude<FirmwareUpdateV4Target, 'resource'>>();
|
|
1047
|
+
for (const artifact of this.params.preparedPlan.artifacts) {
|
|
1048
|
+
if (artifact.target !== 'resource') {
|
|
1049
|
+
const target = artifact.target as Exclude<FirmwareUpdateV4Target, 'resource'>;
|
|
1050
|
+
const installTarget = PROTOCOL_V2_INSTALL_TARGET_BY_UPDATE_TARGET.get(target);
|
|
1051
|
+
if (
|
|
1052
|
+
!requestedTargets.has(target) ||
|
|
1053
|
+
artifact.role !== 'component' ||
|
|
1054
|
+
artifact.container !== 'raw' ||
|
|
1055
|
+
!installTarget ||
|
|
1056
|
+
preparedTargets.has(target)
|
|
1057
|
+
) {
|
|
1058
|
+
throw ERRORS.TypedError(
|
|
1059
|
+
HardwareErrorCode.RuntimeError,
|
|
1060
|
+
`Protocol V2 prepared component artifact is invalid: ${artifact.artifactId}`,
|
|
1061
|
+
{ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
|
|
1062
|
+
);
|
|
1063
|
+
}
|
|
1064
|
+
installSources.push({
|
|
1065
|
+
...installTarget,
|
|
1066
|
+
source: await this.openProtocolV2PreparedSource(artifact.artifact),
|
|
1067
|
+
});
|
|
1068
|
+
preparedTargets.add(target);
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
const missingTarget = Array.from(requestedTargets).find(
|
|
1072
|
+
target => !preparedTargets.has(target)
|
|
598
1073
|
);
|
|
1074
|
+
if (missingTarget) {
|
|
1075
|
+
throw ERRORS.TypedError(
|
|
1076
|
+
HardwareErrorCode.RuntimeError,
|
|
1077
|
+
`Protocol V2 ${missingTarget} artifact is not prepared`,
|
|
1078
|
+
{
|
|
1079
|
+
firmwareUpdateCode: 'FirmwareArtifactsNotPrepared',
|
|
1080
|
+
artifactName: missingTarget,
|
|
1081
|
+
}
|
|
1082
|
+
);
|
|
1083
|
+
}
|
|
1084
|
+
return installSources;
|
|
599
1085
|
}
|
|
600
|
-
|
|
601
|
-
|
|
1086
|
+
|
|
1087
|
+
const release = DataManager.getFirmwareLatestRelease(features, firmwareType);
|
|
1088
|
+
if (!release) {
|
|
602
1089
|
throw ERRORS.TypedError(
|
|
603
1090
|
HardwareErrorCode.RuntimeError,
|
|
604
|
-
|
|
1091
|
+
'Protocol V2 firmware release is unavailable'
|
|
605
1092
|
);
|
|
606
1093
|
}
|
|
607
|
-
|
|
1094
|
+
const componentArtifacts = this.params.componentArtifacts ?? {};
|
|
1095
|
+
const requestedTargets = new Set(
|
|
1096
|
+
this.params.targetsToUpdate ?? (Object.keys(componentArtifacts) as FirmwareUpdateV4Target[])
|
|
1097
|
+
);
|
|
1098
|
+
const installSources: ProtocolV2InstallSource[] = [];
|
|
1099
|
+
const preparedTargets = new Set<FirmwareUpdateV4Target>();
|
|
1100
|
+
|
|
1101
|
+
for (const [key, component] of this.getRemoteComponentEntries(release)) {
|
|
1102
|
+
const target = this.getRemoteComponentTarget(key, component);
|
|
1103
|
+
const updateTarget = PROTOCOL_V2_UPDATE_TARGET_BY_TARGET_ID.get(target.targetId);
|
|
1104
|
+
if (updateTarget && updateTarget !== 'resource' && requestedTargets.has(updateTarget)) {
|
|
1105
|
+
const artifact = componentArtifacts[updateTarget];
|
|
1106
|
+
if (!artifact) {
|
|
1107
|
+
throw ERRORS.TypedError(
|
|
1108
|
+
HardwareErrorCode.RuntimeError,
|
|
1109
|
+
`Protocol V2 ${updateTarget} artifact is not prepared`,
|
|
1110
|
+
{
|
|
1111
|
+
firmwareUpdateCode: 'FirmwareArtifactsNotPrepared',
|
|
1112
|
+
artifactName: updateTarget,
|
|
1113
|
+
}
|
|
1114
|
+
);
|
|
1115
|
+
}
|
|
1116
|
+
installSources.push({
|
|
1117
|
+
...target,
|
|
1118
|
+
source: await this.openProtocolV2PreparedSource(artifact),
|
|
1119
|
+
});
|
|
1120
|
+
preparedTargets.add(updateTarget);
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
const unknownTarget = Array.from(requestedTargets).find(
|
|
1125
|
+
target => target !== 'resource' && !preparedTargets.has(target)
|
|
1126
|
+
);
|
|
1127
|
+
if (unknownTarget) {
|
|
1128
|
+
throw ERRORS.TypedError(
|
|
1129
|
+
HardwareErrorCode.RuntimeError,
|
|
1130
|
+
`Protocol V2 release does not contain requested target ${unknownTarget}`
|
|
1131
|
+
);
|
|
1132
|
+
}
|
|
1133
|
+
return installSources;
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
private async prepareProtocolV2LocalMemoryHost({
|
|
1137
|
+
features,
|
|
1138
|
+
firmwareType,
|
|
1139
|
+
availableInstallItems = this.buildProtocolV2InstallItems({
|
|
1140
|
+
bootloaderBinary: this.prepareBootloaderBinary(),
|
|
1141
|
+
fwBinaryMap: this.collectExplicitTargetBinaries(),
|
|
1142
|
+
}),
|
|
1143
|
+
}: {
|
|
1144
|
+
features: Features;
|
|
1145
|
+
firmwareType: EFirmwareType;
|
|
1146
|
+
availableInstallItems?: ProtocolV2InstallItem[];
|
|
1147
|
+
}): Promise<FirmwareUpdateV4MemoryHost> {
|
|
1148
|
+
const requestedComponentTargets = new Set(
|
|
1149
|
+
(this.params.targetsToUpdate ?? []).filter(
|
|
1150
|
+
(target): target is Exclude<FirmwareUpdateV4Target, 'resource' | 'boot_resources'> =>
|
|
1151
|
+
target !== 'resource' && target !== 'boot_resources'
|
|
1152
|
+
)
|
|
1153
|
+
);
|
|
1154
|
+
const localComponentTargets = new Set(
|
|
1155
|
+
availableInstallItems.flatMap(item => {
|
|
1156
|
+
const target = PROTOCOL_V2_UPDATE_TARGET_BY_TARGET_ID.get(item.targetId);
|
|
1157
|
+
return target ? [target] : [];
|
|
1158
|
+
})
|
|
1159
|
+
);
|
|
1160
|
+
const missingTarget = Array.from(requestedComponentTargets).find(
|
|
1161
|
+
target => !localComponentTargets.has(target)
|
|
1162
|
+
);
|
|
1163
|
+
if (missingTarget) {
|
|
1164
|
+
throw ERRORS.TypedError(
|
|
1165
|
+
HardwareErrorCode.RuntimeError,
|
|
1166
|
+
`Protocol V2 local update has no binary for requested target ${missingTarget}`,
|
|
1167
|
+
{
|
|
1168
|
+
firmwareUpdateCode: 'FirmwareArtifactsNotPrepared',
|
|
1169
|
+
artifactName: missingTarget,
|
|
1170
|
+
}
|
|
1171
|
+
);
|
|
1172
|
+
}
|
|
1173
|
+
const installItems = this.filterProtocolV2LocalInstallItems(availableInstallItems);
|
|
1174
|
+
|
|
1175
|
+
const planArtifacts: Parameters<typeof buildProtocolV2LocalFirmwareUpdatePlan>[0]['artifacts'] =
|
|
1176
|
+
[];
|
|
1177
|
+
const memoryArtifacts: FirmwareMemoryArtifact[] = [];
|
|
1178
|
+
for (const item of installItems) {
|
|
1179
|
+
const target = PROTOCOL_V2_UPDATE_TARGET_BY_TARGET_ID.get(item.targetId);
|
|
1180
|
+
if (!target || item.binary.byteLength <= 0) {
|
|
1181
|
+
throw ERRORS.TypedError(
|
|
1182
|
+
HardwareErrorCode.RuntimeError,
|
|
1183
|
+
`Protocol V2 local firmware artifact is invalid: ${item.fileName}`,
|
|
1184
|
+
{ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
|
|
1185
|
+
);
|
|
1186
|
+
}
|
|
1187
|
+
const artifactId = `component:${target}`;
|
|
1188
|
+
planArtifacts.push({
|
|
1189
|
+
artifactId,
|
|
1190
|
+
target,
|
|
1191
|
+
container: 'raw',
|
|
1192
|
+
logicalName: item.fileName,
|
|
1193
|
+
expectedSize: item.binary.byteLength,
|
|
1194
|
+
expectedSha256: bytesToHex(sha256(new Uint8Array(item.binary))),
|
|
1195
|
+
...(this.params.expectedTargetVersions?.[target]
|
|
1196
|
+
? { targetVersion: this.params.expectedTargetVersions[target] }
|
|
1197
|
+
: {}),
|
|
1198
|
+
});
|
|
1199
|
+
memoryArtifacts.push({ artifactId, binary: item.binary });
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
const resourceArchive = await this.prepareProtocolV2LocalResourceArchive(
|
|
1203
|
+
this.params.resourceArchiveBinary as ArrayBuffer
|
|
1204
|
+
);
|
|
1205
|
+
const resourceArtifactId = 'resource:archive';
|
|
1206
|
+
planArtifacts.push({
|
|
1207
|
+
artifactId: resourceArtifactId,
|
|
1208
|
+
target: 'resource',
|
|
1209
|
+
container: 'zip',
|
|
1210
|
+
logicalName: 'protocol-v2-local-resource-archive',
|
|
1211
|
+
expectedSize: resourceArchive.binary.byteLength,
|
|
1212
|
+
expectedSha256: bytesToHex(sha256(new Uint8Array(resourceArchive.binary))),
|
|
1213
|
+
});
|
|
1214
|
+
memoryArtifacts.push({
|
|
1215
|
+
artifactId: resourceArtifactId,
|
|
1216
|
+
binary: resourceArchive.binary,
|
|
1217
|
+
materializedEntries: resourceArchive.materializedEntries,
|
|
1218
|
+
});
|
|
1219
|
+
|
|
1220
|
+
const plan = buildProtocolV2LocalFirmwareUpdatePlan({
|
|
1221
|
+
features,
|
|
1222
|
+
firmwareType,
|
|
1223
|
+
platform: this.params.platform,
|
|
1224
|
+
artifacts: planArtifacts,
|
|
1225
|
+
});
|
|
1226
|
+
let memoryHost: FirmwareUpdateV4MemoryHost | undefined;
|
|
1227
|
+
try {
|
|
1228
|
+
memoryHost = prepareFirmwareUpdateV4MemoryHost({
|
|
1229
|
+
sdk: {
|
|
1230
|
+
prepareFirmwareUpdatePlan,
|
|
1231
|
+
registerFirmwareUpdateHostBinding,
|
|
1232
|
+
unregisterFirmwareUpdateHostBinding,
|
|
1233
|
+
},
|
|
1234
|
+
plan,
|
|
1235
|
+
artifacts: memoryArtifacts,
|
|
1236
|
+
});
|
|
1237
|
+
const preparedPlan = validateFirmwareUpdatePreparedPlan(memoryHost.preparedPlan);
|
|
1238
|
+
assertFirmwareUpdatePreparedPlanBinding({
|
|
1239
|
+
preparedPlan,
|
|
1240
|
+
executor: 'v4',
|
|
1241
|
+
platform: this.params.platform,
|
|
1242
|
+
scopeTargets: [],
|
|
1243
|
+
bindings: [],
|
|
1244
|
+
});
|
|
1245
|
+
assertFirmwareUpdatePreparedPlanDeviceIdentity({
|
|
1246
|
+
preparedPlan,
|
|
1247
|
+
deviceIdentity: this.protocolV2ExpectedSerialNumber,
|
|
1248
|
+
deviceModel: this.getProtocolV2PreparedPlanDeviceModel(features),
|
|
1249
|
+
});
|
|
1250
|
+
const hostBinding = resolveFirmwareUpdateHostBinding(
|
|
1251
|
+
memoryHost.hostBindingGeneration,
|
|
1252
|
+
preparedPlan.preparedPlanDigest
|
|
1253
|
+
);
|
|
1254
|
+
this.params.preparedPlan = preparedPlan;
|
|
1255
|
+
this.params.targetsToUpdate = [...preparedPlan.targetsToUpdate] as FirmwareUpdateV4Target[];
|
|
1256
|
+
this.params.artifactReader = hostBinding.artifactReader;
|
|
1257
|
+
this.params.componentArtifacts = undefined;
|
|
1258
|
+
return memoryHost;
|
|
1259
|
+
} catch (error) {
|
|
1260
|
+
memoryHost?.release();
|
|
1261
|
+
throw error;
|
|
1262
|
+
}
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
private async prepareProtocolV2LocalResourceArchive(
|
|
1266
|
+
binary: ArrayBuffer
|
|
1267
|
+
): Promise<ProtocolV2LocalResourceArchive> {
|
|
1268
|
+
if (binary.byteLength <= 0 || binary.byteLength > PROTOCOL_V2_RESOURCE_TOTAL_MAX_BYTES) {
|
|
1269
|
+
throw ERRORS.TypedError(
|
|
1270
|
+
HardwareErrorCode.RuntimeError,
|
|
1271
|
+
'Protocol V2 local resource ZIP archive size is invalid',
|
|
1272
|
+
{ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
|
|
1273
|
+
);
|
|
1274
|
+
}
|
|
1275
|
+
let zip: JSZip;
|
|
1276
|
+
try {
|
|
1277
|
+
zip = await JSZip.loadAsync(binary);
|
|
1278
|
+
} catch {
|
|
1279
|
+
throw ERRORS.TypedError(
|
|
1280
|
+
HardwareErrorCode.RuntimeError,
|
|
1281
|
+
'Protocol V2 resource ZIP cannot be parsed',
|
|
1282
|
+
{ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
|
|
1283
|
+
);
|
|
1284
|
+
}
|
|
1285
|
+
const zipEntries = Object.values(zip.files);
|
|
1286
|
+
if (
|
|
1287
|
+
zipEntries.some(entry => entry.unsafeOriginalName && entry.unsafeOriginalName !== entry.name)
|
|
1288
|
+
) {
|
|
1289
|
+
throw ERRORS.TypedError(
|
|
1290
|
+
HardwareErrorCode.RuntimeError,
|
|
1291
|
+
'Protocol V2 local resource ZIP contains an unsafe entry path',
|
|
1292
|
+
{ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
|
|
1293
|
+
);
|
|
1294
|
+
}
|
|
1295
|
+
const entries = zipEntries.filter(entry => !entry.dir);
|
|
1296
|
+
if (entries.length === 0 || entries.length > PROTOCOL_V2_RESOURCE_FILE_MAX_COUNT + 1) {
|
|
1297
|
+
throw ERRORS.TypedError(
|
|
1298
|
+
HardwareErrorCode.RuntimeError,
|
|
1299
|
+
'Protocol V2 local resource ZIP entry set is invalid',
|
|
1300
|
+
{ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
|
|
1301
|
+
);
|
|
1302
|
+
}
|
|
1303
|
+
let declaredUncompressedSize = 0;
|
|
1304
|
+
let declaredCompressedSize = 0;
|
|
1305
|
+
for (const entry of entries) {
|
|
1306
|
+
const sizes = getProtocolV2ZipEntrySizes(entry);
|
|
1307
|
+
declaredCompressedSize += sizes.compressedSize;
|
|
1308
|
+
declaredUncompressedSize += sizes.uncompressedSize;
|
|
1309
|
+
const entryLimit =
|
|
1310
|
+
entry.name === 'manifest.json'
|
|
1311
|
+
? PROTOCOL_V2_RESOURCE_MANIFEST_MAX_BYTES
|
|
1312
|
+
: PROTOCOL_V2_RESOURCE_TOTAL_MAX_BYTES;
|
|
1313
|
+
if (
|
|
1314
|
+
sizes.uncompressedSize > entryLimit ||
|
|
1315
|
+
declaredCompressedSize > binary.byteLength ||
|
|
1316
|
+
declaredUncompressedSize >
|
|
1317
|
+
PROTOCOL_V2_RESOURCE_TOTAL_MAX_BYTES + PROTOCOL_V2_RESOURCE_MANIFEST_MAX_BYTES
|
|
1318
|
+
) {
|
|
1319
|
+
throw ERRORS.TypedError(
|
|
1320
|
+
HardwareErrorCode.RuntimeError,
|
|
1321
|
+
'Protocol V2 local resource ZIP declared size exceeds the allowed limit',
|
|
1322
|
+
{ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
|
|
1323
|
+
);
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
1326
|
+
const manifestEntry = zip.file('manifest.json');
|
|
1327
|
+
if (!manifestEntry) {
|
|
1328
|
+
throw ERRORS.TypedError(
|
|
1329
|
+
HardwareErrorCode.RuntimeError,
|
|
1330
|
+
'Protocol V2 local resource ZIP has no manifest.json',
|
|
1331
|
+
{ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
|
|
1332
|
+
);
|
|
1333
|
+
}
|
|
1334
|
+
const manifestBinary = await manifestEntry.async('arraybuffer');
|
|
1335
|
+
if (
|
|
1336
|
+
manifestBinary.byteLength <= 0 ||
|
|
1337
|
+
manifestBinary.byteLength > PROTOCOL_V2_RESOURCE_MANIFEST_MAX_BYTES
|
|
1338
|
+
) {
|
|
1339
|
+
throw ERRORS.TypedError(
|
|
1340
|
+
HardwareErrorCode.RuntimeError,
|
|
1341
|
+
'Protocol V2 local resource manifest size is invalid',
|
|
1342
|
+
{ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
|
|
1343
|
+
);
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1346
|
+
let manifestValue: unknown;
|
|
1347
|
+
try {
|
|
1348
|
+
manifestValue = JSON.parse(new TextDecoder().decode(manifestBinary));
|
|
1349
|
+
} catch (error) {
|
|
1350
|
+
throw ERRORS.TypedError(
|
|
1351
|
+
HardwareErrorCode.RuntimeError,
|
|
1352
|
+
`Protocol V2 local resource manifest is invalid: ${String(error)}`,
|
|
1353
|
+
{ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
|
|
1354
|
+
);
|
|
1355
|
+
}
|
|
1356
|
+
const manifest = parseProtocolV2ResourceManifest(manifestValue);
|
|
1357
|
+
const selectedFiles = selectProtocolV2ResourceManifestFiles({
|
|
1358
|
+
manifest,
|
|
1359
|
+
targetsToUpdate: this.params.targetsToUpdate ?? [],
|
|
1360
|
+
});
|
|
1361
|
+
const expectedEntryNames = new Set([
|
|
1362
|
+
'manifest.json',
|
|
1363
|
+
...selectedFiles.map(file => file.archive_path),
|
|
1364
|
+
]);
|
|
1365
|
+
if (
|
|
1366
|
+
entries.length !== expectedEntryNames.size ||
|
|
1367
|
+
entries.some(entry => !expectedEntryNames.has(entry.name))
|
|
1368
|
+
) {
|
|
1369
|
+
throw ERRORS.TypedError(
|
|
1370
|
+
HardwareErrorCode.RuntimeError,
|
|
1371
|
+
'Protocol V2 local resource ZIP contains an unexpected entry',
|
|
1372
|
+
{ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
|
|
1373
|
+
);
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1376
|
+
let totalSize = 0;
|
|
1377
|
+
const materializedEntries: FirmwareMemoryArtifactEntry[] = [
|
|
1378
|
+
{ entryName: 'manifest.json', binary: manifestBinary },
|
|
1379
|
+
];
|
|
1380
|
+
for (const file of selectedFiles) {
|
|
1381
|
+
const entry = zip.file(file.archive_path);
|
|
1382
|
+
if (!entry) {
|
|
1383
|
+
throw ERRORS.TypedError(
|
|
1384
|
+
HardwareErrorCode.RuntimeError,
|
|
1385
|
+
`Protocol V2 local resource ZIP is missing ${file.archive_path}`,
|
|
1386
|
+
{ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
|
|
1387
|
+
);
|
|
1388
|
+
}
|
|
1389
|
+
const { uncompressedSize } = getProtocolV2ZipEntrySizes(entry);
|
|
1390
|
+
totalSize += uncompressedSize;
|
|
1391
|
+
if (uncompressedSize !== file.size || totalSize > PROTOCOL_V2_RESOURCE_TOTAL_MAX_BYTES) {
|
|
1392
|
+
throw ERRORS.TypedError(
|
|
1393
|
+
HardwareErrorCode.RuntimeError,
|
|
1394
|
+
`Protocol V2 local resource file declared size is invalid: ${file.archive_path}`,
|
|
1395
|
+
{ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
|
|
1396
|
+
);
|
|
1397
|
+
}
|
|
1398
|
+
const fileBinary = await entry.async('arraybuffer');
|
|
1399
|
+
const digest = bytesToHex(sha256(new Uint8Array(fileBinary)));
|
|
1400
|
+
if (fileBinary.byteLength !== file.size || digest !== file.sha256.toLowerCase()) {
|
|
1401
|
+
throw ERRORS.TypedError(
|
|
1402
|
+
HardwareErrorCode.RuntimeError,
|
|
1403
|
+
`Protocol V2 local resource file does not match manifest: ${file.archive_path}`,
|
|
1404
|
+
{ firmwareUpdateCode: 'FirmwareArtifactReceiptMismatch' }
|
|
1405
|
+
);
|
|
1406
|
+
}
|
|
1407
|
+
materializedEntries.push({ entryName: file.archive_path, binary: fileBinary });
|
|
1408
|
+
}
|
|
1409
|
+
return { binary, materializedEntries };
|
|
1410
|
+
}
|
|
1411
|
+
|
|
1412
|
+
private async prepareProtocolV2ResourceSources(): Promise<ProtocolV2ResourceBundleSource[]> {
|
|
1413
|
+
const resourceRequested = this.params.targetsToUpdate?.includes('resource') ?? false;
|
|
1414
|
+
if (!resourceRequested) {
|
|
1415
|
+
return [];
|
|
1416
|
+
}
|
|
1417
|
+
const archiveSources = await this.prepareProtocolV2ResourceArchiveSources();
|
|
1418
|
+
if (archiveSources) {
|
|
1419
|
+
return archiveSources;
|
|
1420
|
+
}
|
|
1421
|
+
throw ERRORS.TypedError(
|
|
1422
|
+
HardwareErrorCode.RuntimeError,
|
|
1423
|
+
'Protocol V2 resource archive is not prepared',
|
|
1424
|
+
{ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
|
|
1425
|
+
);
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1428
|
+
private async prepareProtocolV2ResourceArchiveSources(): Promise<
|
|
1429
|
+
ProtocolV2ResourceBundleSource[] | undefined
|
|
1430
|
+
> {
|
|
1431
|
+
const archiveArtifacts =
|
|
1432
|
+
this.params.preparedPlan?.artifacts.filter(
|
|
1433
|
+
artifact =>
|
|
1434
|
+
artifact.role === 'resourceBundle' &&
|
|
1435
|
+
artifact.target === 'resource' &&
|
|
1436
|
+
artifact.container === 'zip'
|
|
1437
|
+
) ?? [];
|
|
1438
|
+
if (archiveArtifacts.length === 0) {
|
|
1439
|
+
return undefined;
|
|
1440
|
+
}
|
|
1441
|
+
if (archiveArtifacts.length !== 1) {
|
|
1442
|
+
throw ERRORS.TypedError(
|
|
1443
|
+
HardwareErrorCode.RuntimeError,
|
|
1444
|
+
'Protocol V2 prepared plan must contain exactly one resource archive',
|
|
1445
|
+
{ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
|
|
1446
|
+
);
|
|
1447
|
+
}
|
|
1448
|
+
|
|
1449
|
+
const archiveArtifact = archiveArtifacts[0];
|
|
1450
|
+
const archiveSource = await this.openProtocolV2PreparedSource(archiveArtifact.artifact);
|
|
1451
|
+
if (archiveSource.size > PROTOCOL_V2_RESOURCE_TOTAL_MAX_BYTES) {
|
|
1452
|
+
throw ERRORS.TypedError(
|
|
1453
|
+
HardwareErrorCode.RuntimeError,
|
|
1454
|
+
'Protocol V2 prepared resource archive exceeds the total size limit',
|
|
1455
|
+
{ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
|
|
1456
|
+
);
|
|
1457
|
+
}
|
|
1458
|
+
|
|
1459
|
+
let archiveBinary: ArrayBuffer;
|
|
1460
|
+
try {
|
|
1461
|
+
archiveBinary = await readFirmwareByteSourceFully(archiveSource);
|
|
1462
|
+
} finally {
|
|
1463
|
+
await archiveSource.close();
|
|
1464
|
+
}
|
|
1465
|
+
const archiveDigest = bytesToHex(sha256(new Uint8Array(archiveBinary)));
|
|
1466
|
+
if (archiveDigest !== archiveArtifact.artifact.sha256.toLowerCase()) {
|
|
1467
|
+
throw ERRORS.TypedError(
|
|
1468
|
+
HardwareErrorCode.RuntimeError,
|
|
1469
|
+
'Protocol V2 prepared resource archive does not match its approved receipt',
|
|
1470
|
+
{ firmwareUpdateCode: 'FirmwareArtifactReceiptMismatch' }
|
|
1471
|
+
);
|
|
1472
|
+
}
|
|
1473
|
+
|
|
1474
|
+
// materializedEntries 由宿主生成,只有与获批 ZIP 的规范字节完全一致后才能使用。
|
|
1475
|
+
const verifiedArchive = await this.prepareProtocolV2LocalResourceArchive(archiveBinary);
|
|
1476
|
+
const entries = archiveArtifact.materializedEntries ?? [];
|
|
1477
|
+
const entriesByName = new Map(entries.map(entry => [entry.entryName, entry] as const));
|
|
1478
|
+
if (
|
|
1479
|
+
entries.length !== verifiedArchive.materializedEntries.length ||
|
|
1480
|
+
verifiedArchive.materializedEntries.some(entry => {
|
|
1481
|
+
const preparedEntry = entriesByName.get(entry.entryName);
|
|
1482
|
+
const digest = bytesToHex(sha256(new Uint8Array(entry.binary)));
|
|
1483
|
+
return (
|
|
1484
|
+
!preparedEntry ||
|
|
1485
|
+
preparedEntry.artifact.size !== entry.binary.byteLength ||
|
|
1486
|
+
preparedEntry.artifact.sha256.toLowerCase() !== digest
|
|
1487
|
+
);
|
|
1488
|
+
})
|
|
1489
|
+
) {
|
|
1490
|
+
throw ERRORS.TypedError(
|
|
1491
|
+
HardwareErrorCode.RuntimeError,
|
|
1492
|
+
'Protocol V2 prepared resource entries do not match the approved archive',
|
|
1493
|
+
{ firmwareUpdateCode: 'FirmwareArtifactReceiptMismatch' }
|
|
1494
|
+
);
|
|
1495
|
+
}
|
|
1496
|
+
|
|
1497
|
+
const verifiedEntriesByName = new Map(
|
|
1498
|
+
verifiedArchive.materializedEntries.map(entry => [entry.entryName, entry.binary] as const)
|
|
1499
|
+
);
|
|
1500
|
+
const manifestBinary = verifiedEntriesByName.get('manifest.json');
|
|
1501
|
+
if (!manifestBinary) {
|
|
1502
|
+
throw ERRORS.TypedError(
|
|
1503
|
+
HardwareErrorCode.RuntimeError,
|
|
1504
|
+
'Protocol V2 prepared resource archive has no valid manifest.json',
|
|
1505
|
+
{ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
|
|
1506
|
+
);
|
|
1507
|
+
}
|
|
1508
|
+
|
|
1509
|
+
let manifestValue: unknown;
|
|
1510
|
+
try {
|
|
1511
|
+
manifestValue = JSON.parse(new TextDecoder().decode(manifestBinary));
|
|
1512
|
+
} catch (error) {
|
|
1513
|
+
throw ERRORS.TypedError(
|
|
1514
|
+
HardwareErrorCode.RuntimeError,
|
|
1515
|
+
`Protocol V2 prepared resource manifest is invalid: ${String(error)}`,
|
|
1516
|
+
{ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
|
|
1517
|
+
);
|
|
1518
|
+
}
|
|
1519
|
+
const manifest = parseProtocolV2ResourceManifest(manifestValue);
|
|
1520
|
+
const selectedFiles = selectProtocolV2ResourceManifestFiles({
|
|
1521
|
+
manifest,
|
|
1522
|
+
targetsToUpdate: this.params.targetsToUpdate ?? [],
|
|
1523
|
+
});
|
|
1524
|
+
if (selectedFiles.length > PROTOCOL_V2_RESOURCE_FILE_MAX_COUNT) {
|
|
1525
|
+
throw ERRORS.TypedError(
|
|
1526
|
+
HardwareErrorCode.RuntimeError,
|
|
1527
|
+
'Protocol V2 prepared resource archive contains too many files',
|
|
1528
|
+
{ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
|
|
1529
|
+
);
|
|
1530
|
+
}
|
|
1531
|
+
|
|
1532
|
+
let totalSize = 0;
|
|
1533
|
+
const sources: ProtocolV2ResourceBundleSource[] = [];
|
|
1534
|
+
for (const [index, file] of selectedFiles.entries()) {
|
|
1535
|
+
const binary = verifiedEntriesByName.get(file.archive_path);
|
|
1536
|
+
if (!binary || binary.byteLength !== file.size) {
|
|
1537
|
+
throw ERRORS.TypedError(
|
|
1538
|
+
HardwareErrorCode.RuntimeError,
|
|
1539
|
+
`Protocol V2 prepared resource file does not match manifest: ${file.archive_path}`,
|
|
1540
|
+
{ firmwareUpdateCode: 'FirmwareArtifactReceiptMismatch' }
|
|
1541
|
+
);
|
|
1542
|
+
}
|
|
1543
|
+
totalSize += binary.byteLength;
|
|
1544
|
+
if (totalSize > PROTOCOL_V2_RESOURCE_TOTAL_MAX_BYTES) {
|
|
1545
|
+
throw ERRORS.TypedError(
|
|
1546
|
+
HardwareErrorCode.RuntimeError,
|
|
1547
|
+
'Protocol V2 prepared resource archive exceeds the total size limit',
|
|
1548
|
+
{ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared' }
|
|
1549
|
+
);
|
|
1550
|
+
}
|
|
1551
|
+
// 使用从获批 ZIP 中提取的规范字节,避免宿主在校验后替换 entry reader 内容。
|
|
1552
|
+
const source = await this.openProtocolV2MemorySource(binary);
|
|
1553
|
+
const header =
|
|
1554
|
+
source.size >= PROTOCOL_V2_OKPP_HEADER_SIZE
|
|
1555
|
+
? parseProtocolV2OkppHeader(
|
|
1556
|
+
new Uint8Array(await source.readAt(0, PROTOCOL_V2_OKPP_HEADER_SIZE))
|
|
1557
|
+
)
|
|
1558
|
+
: null;
|
|
1559
|
+
sources.push({
|
|
1560
|
+
name: file.original_name || `resource-${index}`,
|
|
1561
|
+
source,
|
|
1562
|
+
devicePath: file.device_path,
|
|
1563
|
+
...(header
|
|
1564
|
+
? {
|
|
1565
|
+
version: header.version,
|
|
1566
|
+
payloadHash: header.payloadHash,
|
|
1567
|
+
headerHash: header.headerHash,
|
|
1568
|
+
}
|
|
1569
|
+
: {}),
|
|
1570
|
+
});
|
|
1571
|
+
}
|
|
1572
|
+
return sources;
|
|
1573
|
+
}
|
|
1574
|
+
|
|
1575
|
+
private async runProtocolV2PreparedArtifacts(
|
|
1576
|
+
features: Features,
|
|
1577
|
+
firmwareType: EFirmwareType,
|
|
1578
|
+
announceDownload = true
|
|
1579
|
+
) {
|
|
1580
|
+
try {
|
|
1581
|
+
if (announceDownload) {
|
|
1582
|
+
this.postTipMessage(FirmwareUpdateTipMessage.StartDownloadFirmware);
|
|
1583
|
+
}
|
|
1584
|
+
const installSources = await this.prepareProtocolV2InstallSources(firmwareType, features);
|
|
1585
|
+
const resourceSources = await this.prepareProtocolV2ResourceSources();
|
|
1586
|
+
if (installSources.length === 0 && resourceSources.length === 0) {
|
|
1587
|
+
throw ERRORS.TypedError(
|
|
1588
|
+
HardwareErrorCode.FirmwareUpdateDownloadFailed,
|
|
1589
|
+
'No firmware to update'
|
|
1590
|
+
);
|
|
1591
|
+
}
|
|
1592
|
+
if (announceDownload) {
|
|
1593
|
+
this.postTipMessage(FirmwareUpdateTipMessage.FinishDownloadFirmware);
|
|
1594
|
+
}
|
|
1595
|
+
|
|
1596
|
+
return await this.executeProtocolV2SourceUpdate({
|
|
1597
|
+
installSources,
|
|
1598
|
+
resourceSources,
|
|
1599
|
+
});
|
|
1600
|
+
} finally {
|
|
1601
|
+
await this.closeProtocolV2PreparedSources();
|
|
1602
|
+
}
|
|
1603
|
+
}
|
|
1604
|
+
|
|
1605
|
+
private validateExpectedTargetVersions() {
|
|
1606
|
+
const expected = this.params.expectedTargetVersions;
|
|
1607
|
+
if (expected === undefined) return;
|
|
1608
|
+
if (typeof expected !== 'object' || expected === null || Array.isArray(expected)) {
|
|
1609
|
+
throw ERRORS.TypedError(
|
|
1610
|
+
HardwareErrorCode.RuntimeError,
|
|
1611
|
+
'Protocol V2 expected target versions are invalid'
|
|
1612
|
+
);
|
|
1613
|
+
}
|
|
1614
|
+
for (const [target, version] of Object.entries(expected)) {
|
|
1615
|
+
if (
|
|
1616
|
+
!Array.from(PROTOCOL_V2_UPDATE_TARGET_BY_TARGET_ID.values()).includes(
|
|
1617
|
+
target as Exclude<FirmwareUpdateV4Target, 'boot_resources'>
|
|
1618
|
+
) ||
|
|
1619
|
+
typeof version !== 'string' ||
|
|
1620
|
+
!/^\d+\.\d+\.\d+(?:[-+][A-Za-z0-9.-]+)?$/u.test(version) ||
|
|
1621
|
+
version.length > 64
|
|
1622
|
+
) {
|
|
1623
|
+
throw ERRORS.TypedError(
|
|
1624
|
+
HardwareErrorCode.RuntimeError,
|
|
1625
|
+
'Protocol V2 expected target version is invalid'
|
|
1626
|
+
);
|
|
1627
|
+
}
|
|
1628
|
+
}
|
|
1629
|
+
}
|
|
1630
|
+
|
|
1631
|
+
private getExpectedProtocolV2TargetVersion(targetId: number) {
|
|
1632
|
+
const target = PROTOCOL_V2_UPDATE_TARGET_BY_TARGET_ID.get(targetId);
|
|
1633
|
+
const version = target ? this.params?.expectedTargetVersions?.[target] : undefined;
|
|
1634
|
+
if (!version) return undefined;
|
|
1635
|
+
const parts = version.split(/[+-]/u, 1)[0].split('.').map(Number);
|
|
1636
|
+
if (
|
|
1637
|
+
parts.length !== 3 ||
|
|
1638
|
+
parts.some(part => !Number.isSafeInteger(part) || part < 0 || part > 0xff)
|
|
1639
|
+
) {
|
|
1640
|
+
return undefined;
|
|
1641
|
+
}
|
|
1642
|
+
return parts[0] * 0x10000 + parts[1] * 0x100 + parts[2];
|
|
1643
|
+
}
|
|
1644
|
+
|
|
1645
|
+
private assertExpectedProtocolV2Versions(targets?: readonly FirmwareUpdateV4Target[]) {
|
|
1646
|
+
const expected = this.params?.expectedTargetVersions;
|
|
1647
|
+
if (!expected) return;
|
|
1648
|
+
const features = this.protocolV2LatestFinalFeatures;
|
|
1649
|
+
const requestedTargets = this.getProtocolV2RequestedTargets();
|
|
1650
|
+
const applicationTargets = requestedTargets.filter(
|
|
1651
|
+
target => target === 'app_v1' || target === 'app_v2'
|
|
1652
|
+
);
|
|
1653
|
+
const visibleVersions: Partial<Record<FirmwareUpdateV4Target, string>> = {
|
|
1654
|
+
boot: features ? getDeviceBootloaderVersion(features).join('.') : undefined,
|
|
1655
|
+
coprocessor: features ? getDeviceBLEFirmwareVersion(features).join('.') : undefined,
|
|
1656
|
+
se01: features?.se01Version ?? undefined,
|
|
1657
|
+
se02: features?.se02Version ?? undefined,
|
|
1658
|
+
se03: features?.se03Version ?? undefined,
|
|
1659
|
+
se04: features?.se04Version ?? undefined,
|
|
1660
|
+
};
|
|
1661
|
+
if (features && applicationTargets.length === 1) {
|
|
1662
|
+
visibleVersions[applicationTargets[0]] = getDeviceFirmwareVersion(features).join('.');
|
|
1663
|
+
}
|
|
1664
|
+
const expectedEntries = (
|
|
1665
|
+
Object.entries(expected) as Array<[FirmwareUpdateV4Target, string]>
|
|
1666
|
+
).filter(([target]) => !targets || targets.includes(target));
|
|
1667
|
+
for (const [target, expectedVersion] of expectedEntries) {
|
|
1668
|
+
const targetId = Array.from(PROTOCOL_V2_UPDATE_TARGET_BY_TARGET_ID.entries()).find(
|
|
1669
|
+
([, mappedTarget]) => mappedTarget === target
|
|
1670
|
+
)?.[0];
|
|
1671
|
+
const statusVersion =
|
|
1672
|
+
targetId === undefined ? undefined : this.protocolV2CompletedTargetVersions.get(targetId);
|
|
1673
|
+
const observedVersion =
|
|
1674
|
+
statusVersion === undefined
|
|
1675
|
+
? visibleVersions[target]
|
|
1676
|
+
: `${Math.floor(statusVersion / 0x10000) % 0x100}.${
|
|
1677
|
+
Math.floor(statusVersion / 0x100) % 0x100
|
|
1678
|
+
}.${statusVersion % 0x100}`;
|
|
1679
|
+
if (!observedVersion) {
|
|
1680
|
+
if (this.protocolV2FinalStatusVerified) {
|
|
1681
|
+
Log.warn(
|
|
1682
|
+
`Protocol V2 target ${target} has no observable final version; completed target status is authoritative`
|
|
1683
|
+
);
|
|
1684
|
+
} else {
|
|
1685
|
+
throw ERRORS.TypedError(
|
|
1686
|
+
HardwareErrorCode.FirmwareVerificationFailed,
|
|
1687
|
+
`Protocol V2 target ${target} has no observable final version after status fallback`
|
|
1688
|
+
);
|
|
1689
|
+
}
|
|
1690
|
+
} else if (observedVersion !== expectedVersion) {
|
|
1691
|
+
throw ERRORS.TypedError(
|
|
1692
|
+
HardwareErrorCode.FirmwareVerificationFailed,
|
|
1693
|
+
`Protocol V2 target ${target} reached ${observedVersion}, expected ${expectedVersion}`
|
|
1694
|
+
);
|
|
1695
|
+
}
|
|
1696
|
+
}
|
|
1697
|
+
}
|
|
1698
|
+
|
|
1699
|
+
private getProtocolV2RequestedTargets(): FirmwareUpdateV4Target[] {
|
|
1700
|
+
if (this.params.targetsToUpdate?.length) {
|
|
1701
|
+
return [...new Set(this.params.targetsToUpdate)];
|
|
1702
|
+
}
|
|
1703
|
+
if (this.params.preparedPlan?.targetsToUpdate.length) {
|
|
1704
|
+
return [...new Set(this.params.preparedPlan.targetsToUpdate)] as FirmwareUpdateV4Target[];
|
|
1705
|
+
}
|
|
1706
|
+
const targets = new Set<FirmwareUpdateV4Target>();
|
|
1707
|
+
Object.keys(this.params.componentArtifacts ?? {}).forEach(target =>
|
|
1708
|
+
targets.add(target as FirmwareUpdateV4Target)
|
|
1709
|
+
);
|
|
1710
|
+
if (this.params.bootloaderBinary) targets.add('boot');
|
|
1711
|
+
if (this.params.applicationP1Binary) targets.add('app_v1');
|
|
1712
|
+
if (this.params.applicationP2Binary) targets.add('app_v2');
|
|
1713
|
+
if (this.params.coprocessorBinary) targets.add('coprocessor');
|
|
1714
|
+
if (this.params.se01Binary) targets.add('se01');
|
|
1715
|
+
if (this.params.se02Binary) targets.add('se02');
|
|
1716
|
+
if (this.params.se03Binary) targets.add('se03');
|
|
1717
|
+
if (this.params.se04Binary) targets.add('se04');
|
|
1718
|
+
return Array.from(targets);
|
|
1719
|
+
}
|
|
1720
|
+
|
|
1721
|
+
private async getProtocolV2DeviceFeatures(): Promise<Features> {
|
|
1722
|
+
if (this.device.features) {
|
|
1723
|
+
return this.device.features;
|
|
1724
|
+
}
|
|
1725
|
+
if (typeof this.device.getFeatures === 'function') {
|
|
1726
|
+
const features = await this.device.getFeatures();
|
|
1727
|
+
if (features) return features;
|
|
1728
|
+
}
|
|
1729
|
+
throw ERRORS.TypedError(HardwareErrorCode.RuntimeError, 'Device features not available');
|
|
1730
|
+
}
|
|
1731
|
+
|
|
1732
|
+
private getProtocolV2SerialNumber(deviceInfo: ProtocolV2DeviceInfo) {
|
|
1733
|
+
const serialNumber = deviceInfo.hw?.serial_no?.trim();
|
|
1734
|
+
return serialNumber || undefined;
|
|
1735
|
+
}
|
|
1736
|
+
|
|
1737
|
+
private getProtocolV2PreparedPlanDeviceModel(features?: Features) {
|
|
1738
|
+
const currentDeviceType = this.device.getCurrentDeviceType();
|
|
1739
|
+
const featureDeviceType = features ? getDeviceType(features) : undefined;
|
|
1740
|
+
const deviceType =
|
|
1741
|
+
currentDeviceType === EDeviceType.Pro2 || currentDeviceType === EDeviceType.Neo
|
|
1742
|
+
? currentDeviceType
|
|
1743
|
+
: featureDeviceType;
|
|
1744
|
+
return deviceType === EDeviceType.Pro2 || deviceType === EDeviceType.Neo
|
|
1745
|
+
? String(deviceType)
|
|
1746
|
+
: undefined;
|
|
1747
|
+
}
|
|
1748
|
+
|
|
1749
|
+
private getProtocolV2ConnectionRoute() {
|
|
1750
|
+
const descriptor = this.device.originalDescriptor;
|
|
1751
|
+
return (
|
|
1752
|
+
descriptor?.path?.trim() ||
|
|
1753
|
+
this.device.getConnectId?.()?.trim() ||
|
|
1754
|
+
descriptor?.id?.trim() ||
|
|
1755
|
+
undefined
|
|
1756
|
+
);
|
|
1757
|
+
}
|
|
1758
|
+
|
|
1759
|
+
private assertProtocolV2DeviceInfoIdentity(deviceInfo: ProtocolV2DeviceInfo) {
|
|
1760
|
+
assertProtocolV2ReconnectIdentity(
|
|
1761
|
+
this.protocolV2ExpectedSerialNumber,
|
|
1762
|
+
this.getProtocolV2SerialNumber(deviceInfo),
|
|
1763
|
+
this.protocolV2ExpectedPath,
|
|
1764
|
+
this.getProtocolV2ConnectionRoute()
|
|
1765
|
+
);
|
|
1766
|
+
}
|
|
1767
|
+
|
|
1768
|
+
private getProtocolV2DeviceInfoTimeout() {
|
|
1769
|
+
return this.isBleReconnect()
|
|
1770
|
+
? this.payload.protocolV2DeviceInfoTimeoutMs ?? PROTOCOL_V2_DEVICE_INFO_READY_TIMEOUT
|
|
1771
|
+
: PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT;
|
|
1772
|
+
}
|
|
1773
|
+
|
|
1774
|
+
private async requestProtocolV2PhysicalIdentity() {
|
|
1775
|
+
return requestProtocolV2DeviceInfo({
|
|
1776
|
+
commands: this.device.getCommands(),
|
|
1777
|
+
timeoutMs: this.getProtocolV2DeviceInfoTimeout(),
|
|
1778
|
+
});
|
|
1779
|
+
}
|
|
1780
|
+
|
|
1781
|
+
private async captureProtocolV2PhysicalIdentity() {
|
|
1782
|
+
const deviceInfo = await this.requestProtocolV2PhysicalIdentity();
|
|
1783
|
+
const serialNumber = this.getProtocolV2SerialNumber(deviceInfo);
|
|
1784
|
+
const path = this.getProtocolV2ConnectionRoute();
|
|
1785
|
+
if (this.params?.preparedPlan) {
|
|
1786
|
+
assertFirmwareUpdatePreparedPlanDeviceIdentity({
|
|
1787
|
+
preparedPlan: this.params.preparedPlan,
|
|
1788
|
+
deviceIdentity: serialNumber,
|
|
1789
|
+
deviceModel: this.getProtocolV2PreparedPlanDeviceModel(this.device.features),
|
|
1790
|
+
});
|
|
1791
|
+
} else if (this.params?.expectedDeviceId) {
|
|
1792
|
+
assertProtocolV2ReconnectIdentity(this.params?.expectedDeviceId, serialNumber);
|
|
1793
|
+
} else {
|
|
1794
|
+
assertProtocolV2ReconnectIdentity(serialNumber, serialNumber, path, path);
|
|
1795
|
+
}
|
|
1796
|
+
this.protocolV2ExpectedSerialNumber = serialNumber;
|
|
1797
|
+
this.protocolV2ExpectedPath = path;
|
|
1798
|
+
}
|
|
1799
|
+
|
|
1800
|
+
private async verifyProtocolV2ReconnectIdentity() {
|
|
1801
|
+
const deviceInfo = await this.requestProtocolV2PhysicalIdentity();
|
|
1802
|
+
this.assertProtocolV2DeviceInfoIdentity(deviceInfo);
|
|
1803
|
+
return deviceInfo;
|
|
1804
|
+
}
|
|
1805
|
+
|
|
1806
|
+
private prepareBootloaderBinary(): ArrayBuffer | null {
|
|
1807
|
+
return this.params.bootloaderBinary ?? null;
|
|
1808
|
+
}
|
|
1809
|
+
|
|
1810
|
+
private getMissingProtocolV2FirmwareTargets(installItems: ProtocolV2InstallItem[]) {
|
|
1811
|
+
const preparedTargets = new Set(
|
|
1812
|
+
installItems.flatMap(item => {
|
|
1813
|
+
const target = PROTOCOL_V2_UPDATE_TARGET_BY_TARGET_ID.get(item.targetId);
|
|
1814
|
+
return target ? [target] : [];
|
|
1815
|
+
})
|
|
1816
|
+
);
|
|
1817
|
+
return (this.params.targetsToUpdate ?? [])
|
|
1818
|
+
.filter(
|
|
1819
|
+
(target): target is Exclude<FirmwareUpdateV4Target, 'resource' | 'boot_resources'> =>
|
|
1820
|
+
target !== 'resource' && target !== 'boot_resources'
|
|
1821
|
+
)
|
|
1822
|
+
.filter(target => !preparedTargets.has(target));
|
|
1823
|
+
}
|
|
1824
|
+
|
|
1825
|
+
private filterProtocolV2LocalInstallItems(installItems: ProtocolV2InstallItem[]) {
|
|
1826
|
+
if (!this.protocolV2HasExplicitTargetSelection) {
|
|
1827
|
+
return installItems;
|
|
1828
|
+
}
|
|
1829
|
+
const requestedTargets = new Set(this.params.targetsToUpdate ?? []);
|
|
1830
|
+
return installItems.filter(item => {
|
|
1831
|
+
const target = PROTOCOL_V2_UPDATE_TARGET_BY_TARGET_ID.get(item.targetId);
|
|
1832
|
+
return target !== undefined && requestedTargets.has(target);
|
|
1833
|
+
});
|
|
1834
|
+
}
|
|
1835
|
+
|
|
1836
|
+
private buildProtocolV2InstallItems({
|
|
1837
|
+
bootloaderBinary,
|
|
1838
|
+
fwBinaryMap,
|
|
1839
|
+
}: {
|
|
1840
|
+
bootloaderBinary: ArrayBuffer | null;
|
|
1841
|
+
fwBinaryMap: ProtocolV2TargetBinary[];
|
|
1842
|
+
}): ProtocolV2InstallItem[] {
|
|
1843
|
+
const installItems: ProtocolV2InstallItem[] = [];
|
|
1844
|
+
|
|
1845
|
+
if (bootloaderBinary) {
|
|
1846
|
+
installItems.push({
|
|
1847
|
+
fileName: 'bootloader.bin',
|
|
1848
|
+
binary: bootloaderBinary,
|
|
1849
|
+
targetId: ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_BOOTLOADER,
|
|
1850
|
+
kind: 'bootloader',
|
|
1851
|
+
});
|
|
1852
|
+
}
|
|
1853
|
+
|
|
1854
|
+
installItems.push(...fwBinaryMap.map(item => ({ ...item, kind: 'firmware' as const })));
|
|
1855
|
+
return installItems;
|
|
608
1856
|
}
|
|
609
1857
|
|
|
610
|
-
private
|
|
611
|
-
|
|
612
|
-
if (
|
|
613
|
-
|
|
1858
|
+
private getRemoteComponentEntries(release: IFirmwareReleaseInfo) {
|
|
1859
|
+
const { components } = release;
|
|
1860
|
+
if (!components) return [];
|
|
1861
|
+
|
|
1862
|
+
const orderedKeys = [
|
|
1863
|
+
...(release.installOrder ?? []),
|
|
1864
|
+
...Object.keys(components).filter(key => !release.installOrder?.includes(key)),
|
|
1865
|
+
];
|
|
1866
|
+
|
|
1867
|
+
return orderedKeys
|
|
1868
|
+
.map(key => {
|
|
1869
|
+
const component = components[key];
|
|
1870
|
+
return component ? ([key, component] as const) : undefined;
|
|
1871
|
+
})
|
|
1872
|
+
.filter((entry): entry is readonly [string, IProtocolV2FirmwareComponent] => !!entry);
|
|
614
1873
|
}
|
|
615
1874
|
|
|
616
|
-
private
|
|
617
|
-
const
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
if (
|
|
624
|
-
!pathInfoRes.message?.exist ||
|
|
625
|
-
pathInfoRes.message?.directory ||
|
|
626
|
-
fileSize === undefined ||
|
|
627
|
-
fileSize < PROTOCOL_V2_OKPP_HEADER_SIZE
|
|
628
|
-
) {
|
|
629
|
-
return null;
|
|
1875
|
+
private getRemoteComponentTarget(key: string, component: IProtocolV2FirmwareComponent) {
|
|
1876
|
+
const targetName = component.target?.toUpperCase();
|
|
1877
|
+
if (targetName === 'ROMLOADER') {
|
|
1878
|
+
throw ERRORS.TypedError(
|
|
1879
|
+
HardwareErrorCode.RuntimeError,
|
|
1880
|
+
PROTOCOL_V2_ROMLOADER_UNSUPPORTED_MESSAGE
|
|
1881
|
+
);
|
|
630
1882
|
}
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
const res = await typedCall('FilesystemFileRead', 'FilesystemFile', {
|
|
638
|
-
file: {
|
|
639
|
-
path: filePath,
|
|
640
|
-
offset,
|
|
641
|
-
total_size: 0,
|
|
642
|
-
},
|
|
643
|
-
chunk_len: readLen,
|
|
644
|
-
ui_percentage: undefined,
|
|
645
|
-
});
|
|
646
|
-
const data = toProtocolV2Bytes(res.message?.data);
|
|
647
|
-
if (data.byteLength === 0) return null;
|
|
648
|
-
chunks.push(data);
|
|
649
|
-
offset += data.byteLength;
|
|
1883
|
+
const target = PROTOCOL_V2_REMOTE_COMPONENT_TARGETS[targetName];
|
|
1884
|
+
if (!target) {
|
|
1885
|
+
throw ERRORS.TypedError(
|
|
1886
|
+
HardwareErrorCode.RuntimeError,
|
|
1887
|
+
`Unsupported Protocol V2 firmware component target: ${key}/${component.target}`
|
|
1888
|
+
);
|
|
650
1889
|
}
|
|
651
|
-
|
|
652
|
-
const headerBytes = new Uint8Array(offset);
|
|
653
|
-
let cursor = 0;
|
|
654
|
-
chunks.forEach(chunk => {
|
|
655
|
-
headerBytes.set(chunk, cursor);
|
|
656
|
-
cursor += chunk.byteLength;
|
|
657
|
-
});
|
|
658
|
-
return parseProtocolV2OkppHeader(headerBytes);
|
|
1890
|
+
return target;
|
|
659
1891
|
}
|
|
660
1892
|
|
|
661
1893
|
private async downloadRemoteProtocolV2Component(
|
|
@@ -669,15 +1901,93 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
669
1901
|
`Missing Protocol V2 firmware component url: ${key}/${component.target}`
|
|
670
1902
|
);
|
|
671
1903
|
}
|
|
1904
|
+
const expectedFingerprint = normalizeProtocolV2Hex(component.fingerprint);
|
|
1905
|
+
if (
|
|
1906
|
+
!Number.isSafeInteger(component.expectedSize) ||
|
|
1907
|
+
Number(component.expectedSize) <= 0 ||
|
|
1908
|
+
!expectedFingerprint ||
|
|
1909
|
+
!/^[0-9a-f]{64}$/u.test(expectedFingerprint)
|
|
1910
|
+
) {
|
|
1911
|
+
throw ERRORS.TypedError(
|
|
1912
|
+
HardwareErrorCode.RuntimeError,
|
|
1913
|
+
`Protocol V2 firmware component integrity metadata is invalid: ${key}/${component.target}`,
|
|
1914
|
+
{ firmwareUpdateCode: 'FirmwarePlanInvalid' }
|
|
1915
|
+
);
|
|
1916
|
+
}
|
|
672
1917
|
|
|
673
1918
|
const { binary } = await getSysResourceBinary(component.url);
|
|
1919
|
+
if (binary.byteLength !== component.expectedSize) {
|
|
1920
|
+
throw ERRORS.TypedError(
|
|
1921
|
+
HardwareErrorCode.RuntimeError,
|
|
1922
|
+
`Protocol V2 firmware size mismatch: ${key}/${component.target}`,
|
|
1923
|
+
{ firmwareUpdateCode: 'FirmwareArtifactReceiptMismatch' }
|
|
1924
|
+
);
|
|
1925
|
+
}
|
|
1926
|
+
if (!isProtocolV2FirmwareFingerprintValid(binary, component.fingerprint)) {
|
|
1927
|
+
throw ERRORS.TypedError(
|
|
1928
|
+
HardwareErrorCode.RuntimeError,
|
|
1929
|
+
`Protocol V2 firmware fingerprint mismatch: ${key}/${component.target}`
|
|
1930
|
+
);
|
|
1931
|
+
}
|
|
1932
|
+
const expectedPayloadHash = normalizeProtocolV2Hex(component.payloadHash);
|
|
1933
|
+
if (expectedPayloadHash) {
|
|
1934
|
+
const header = parseProtocolV2OkppHeader(toProtocolV2Bytes(binary));
|
|
1935
|
+
if (!header || header.payloadHash !== expectedPayloadHash) {
|
|
1936
|
+
throw ERRORS.TypedError(
|
|
1937
|
+
HardwareErrorCode.RuntimeError,
|
|
1938
|
+
`Protocol V2 firmware payload hash mismatch: ${key}/${component.target}`
|
|
1939
|
+
);
|
|
1940
|
+
}
|
|
1941
|
+
}
|
|
674
1942
|
return {
|
|
675
1943
|
...target,
|
|
676
1944
|
binary,
|
|
677
1945
|
};
|
|
678
1946
|
}
|
|
679
1947
|
|
|
680
|
-
private async
|
|
1948
|
+
private async downloadRemoteProtocolV2ResourceArchive(features: Features): Promise<ArrayBuffer> {
|
|
1949
|
+
const deviceType = getDeviceType(features);
|
|
1950
|
+
if (deviceType !== EDeviceType.Pro2 && deviceType !== EDeviceType.Neo) {
|
|
1951
|
+
throw ERRORS.TypedError(
|
|
1952
|
+
HardwareErrorCode.RuntimeError,
|
|
1953
|
+
'Protocol V2 resource archive requires a Pro2 or Neo device'
|
|
1954
|
+
);
|
|
1955
|
+
}
|
|
1956
|
+
const source = DataManager.getProtocolV2ResourceSource(deviceType);
|
|
1957
|
+
const expectedSha256 = normalizeProtocolV2Hex(source?.archiveSha256);
|
|
1958
|
+
if (
|
|
1959
|
+
!source?.archiveUrl ||
|
|
1960
|
+
!Number.isSafeInteger(source.archiveSize) ||
|
|
1961
|
+
source.archiveSize <= 0 ||
|
|
1962
|
+
source.archiveSize > PROTOCOL_V2_RESOURCE_TOTAL_MAX_BYTES ||
|
|
1963
|
+
!expectedSha256 ||
|
|
1964
|
+
!/^[0-9a-f]{64}$/u.test(expectedSha256)
|
|
1965
|
+
) {
|
|
1966
|
+
throw ERRORS.TypedError(
|
|
1967
|
+
HardwareErrorCode.RuntimeError,
|
|
1968
|
+
'Protocol V2 resource archive integrity metadata is invalid',
|
|
1969
|
+
{ firmwareUpdateCode: 'FirmwarePlanInvalid' }
|
|
1970
|
+
);
|
|
1971
|
+
}
|
|
1972
|
+
const { binary } = await getSysResourceBinary(source.archiveUrl);
|
|
1973
|
+
if (
|
|
1974
|
+
binary.byteLength !== source.archiveSize ||
|
|
1975
|
+
bytesToHex(sha256(new Uint8Array(binary))) !== expectedSha256
|
|
1976
|
+
) {
|
|
1977
|
+
throw ERRORS.TypedError(
|
|
1978
|
+
HardwareErrorCode.RuntimeError,
|
|
1979
|
+
'Protocol V2 resource archive does not match the remote config',
|
|
1980
|
+
{ firmwareUpdateCode: 'FirmwareArtifactReceiptMismatch' }
|
|
1981
|
+
);
|
|
1982
|
+
}
|
|
1983
|
+
return binary;
|
|
1984
|
+
}
|
|
1985
|
+
|
|
1986
|
+
private async prepareRemoteProtocolV2Binaries(
|
|
1987
|
+
firmwareType: EFirmwareType,
|
|
1988
|
+
features: Features,
|
|
1989
|
+
explicitInstallItems: ProtocolV2InstallItem[] = []
|
|
1990
|
+
) {
|
|
681
1991
|
const release = DataManager.getFirmwareLatestRelease(features, firmwareType);
|
|
682
1992
|
|
|
683
1993
|
let bootloaderBinary: ArrayBuffer | null = null;
|
|
@@ -685,6 +1995,15 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
685
1995
|
const installItems: ProtocolV2InstallItem[] = [];
|
|
686
1996
|
|
|
687
1997
|
if (!release) {
|
|
1998
|
+
const missingFirmwareTargets = this.getMissingProtocolV2FirmwareTargets(explicitInstallItems);
|
|
1999
|
+
if (missingFirmwareTargets.length > 0) {
|
|
2000
|
+
throw ERRORS.TypedError(
|
|
2001
|
+
HardwareErrorCode.RuntimeError,
|
|
2002
|
+
`Protocol V2 firmware release is unavailable for requested targets: ${missingFirmwareTargets.join(
|
|
2003
|
+
', '
|
|
2004
|
+
)}`
|
|
2005
|
+
);
|
|
2006
|
+
}
|
|
688
2007
|
return {
|
|
689
2008
|
bootloaderBinary,
|
|
690
2009
|
fwBinaryMap,
|
|
@@ -694,33 +2013,46 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
694
2013
|
|
|
695
2014
|
const entries = this.getRemoteComponentEntries(release);
|
|
696
2015
|
const targetsToUpdate = new Set(this.params.targetsToUpdate ?? []);
|
|
2016
|
+
const explicitInstallItemByTargetId = new Map(
|
|
2017
|
+
explicitInstallItems.map(item => [item.targetId, item] as const)
|
|
2018
|
+
);
|
|
2019
|
+
const preparedTargets = new Set<FirmwareUpdateV4Target>();
|
|
697
2020
|
|
|
698
2021
|
for (const [key, component] of entries) {
|
|
699
|
-
const
|
|
700
|
-
const
|
|
701
|
-
const
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
kind: remoteBinary.kind,
|
|
711
|
-
});
|
|
2022
|
+
const targetName = component.target?.toUpperCase();
|
|
2023
|
+
const target = PROTOCOL_V2_REMOTE_COMPONENT_TARGETS[targetName];
|
|
2024
|
+
const updateTarget = target
|
|
2025
|
+
? PROTOCOL_V2_UPDATE_TARGET_BY_TARGET_ID.get(target.targetId)
|
|
2026
|
+
: undefined;
|
|
2027
|
+
if (updateTarget && targetsToUpdate.has(updateTarget)) {
|
|
2028
|
+
const explicitInstallItem = explicitInstallItemByTargetId.get(target.targetId);
|
|
2029
|
+
const installItem =
|
|
2030
|
+
explicitInstallItem ?? (await this.downloadRemoteProtocolV2Component(key, component));
|
|
2031
|
+
if (installItem.kind === 'bootloader') {
|
|
2032
|
+
bootloaderBinary = installItem.binary;
|
|
712
2033
|
} else {
|
|
713
2034
|
const binaryEntry = {
|
|
714
|
-
fileName:
|
|
715
|
-
binary:
|
|
716
|
-
targetId:
|
|
2035
|
+
fileName: installItem.fileName,
|
|
2036
|
+
binary: installItem.binary,
|
|
2037
|
+
targetId: installItem.targetId,
|
|
717
2038
|
};
|
|
718
2039
|
fwBinaryMap.push(binaryEntry);
|
|
719
|
-
installItems.push({ ...binaryEntry, kind: remoteBinary.kind });
|
|
720
2040
|
}
|
|
2041
|
+
installItems.push({ ...installItem });
|
|
2042
|
+
preparedTargets.add(updateTarget);
|
|
721
2043
|
}
|
|
722
2044
|
}
|
|
723
2045
|
|
|
2046
|
+
const missingTarget = Array.from(targetsToUpdate).find(
|
|
2047
|
+
target => target !== 'resource' && !preparedTargets.has(target)
|
|
2048
|
+
);
|
|
2049
|
+
if (missingTarget) {
|
|
2050
|
+
throw ERRORS.TypedError(
|
|
2051
|
+
HardwareErrorCode.RuntimeError,
|
|
2052
|
+
`Protocol V2 release does not contain requested target ${missingTarget}`
|
|
2053
|
+
);
|
|
2054
|
+
}
|
|
2055
|
+
|
|
724
2056
|
return {
|
|
725
2057
|
bootloaderBinary,
|
|
726
2058
|
fwBinaryMap,
|
|
@@ -728,144 +2060,83 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
728
2060
|
};
|
|
729
2061
|
}
|
|
730
2062
|
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
/**
|
|
736
|
-
* 准备 RESC bundle 列表。
|
|
737
|
-
*
|
|
738
|
-
* 两种模式(同 FirmwareUpdateV3 的 binary vs version 模式):
|
|
739
|
-
* - 用户传了 resourceBundleFiles(binary 数组):直接用,不做版本比对。
|
|
740
|
-
* - 用户没传:从远端 config.json 的 release.resourceBundles 拉取,
|
|
741
|
-
* 此时 syncProtocolV2ResourceBundles 会按需下载 + 比对设备已有 header 跳过。
|
|
742
|
-
*/
|
|
743
|
-
private prepareProtocolV2ResourceBundles(
|
|
744
|
-
firmwareType: EFirmwareType,
|
|
745
|
-
features: Features
|
|
746
|
-
): ProtocolV2ResourceBundleBinary[] | undefined {
|
|
747
|
-
// 模式1:用户手动传入 binary,直接安装,不比对
|
|
748
|
-
if (this.params.resourceBundleFiles?.length) {
|
|
749
|
-
return this.params.resourceBundleFiles.map(file => ({
|
|
750
|
-
name: file.devicePath.split('/').pop() ?? file.devicePath,
|
|
751
|
-
binary: file.binary,
|
|
752
|
-
devicePath: file.devicePath,
|
|
753
|
-
}));
|
|
754
|
-
}
|
|
755
|
-
|
|
756
|
-
if (!this.params.targetsToUpdate?.includes('resource')) {
|
|
757
|
-
return undefined;
|
|
758
|
-
}
|
|
759
|
-
|
|
760
|
-
// 模式2:远端配置模式,后续按需下载 + 比对
|
|
761
|
-
const release = DataManager.getFirmwareLatestRelease(features, firmwareType);
|
|
762
|
-
if (!release?.resourceBundles?.length) return undefined;
|
|
763
|
-
|
|
764
|
-
return release.resourceBundles.map(bundle => ({
|
|
765
|
-
name: bundle.name,
|
|
766
|
-
binary: new ArrayBuffer(0),
|
|
767
|
-
devicePath: bundle.devicePath,
|
|
768
|
-
url: bundle.url,
|
|
769
|
-
version: bundle.version,
|
|
770
|
-
payloadHash: bundle.payloadHash,
|
|
771
|
-
headerHash: bundle.headerHash,
|
|
772
|
-
})) as ProtocolV2ResourceBundleBinary[];
|
|
2063
|
+
private getProtocolV2ResourceFilePath(path: string) {
|
|
2064
|
+
if (path.startsWith('vol')) return path;
|
|
2065
|
+
if (path.startsWith('/')) return `vol0:${path}`;
|
|
2066
|
+
return `vol0:/${path}`;
|
|
773
2067
|
}
|
|
774
2068
|
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
// 远端配置模式:按需下载 + 比对跳过
|
|
792
|
-
if (!isManualMode) {
|
|
793
|
-
const filtered: ProtocolV2ResourceBundleBinary[] = [];
|
|
794
|
-
for (const bundle of bundles) {
|
|
795
|
-
// 下载 binary
|
|
796
|
-
if (bundle.binary.byteLength === 0 && bundle.url) {
|
|
797
|
-
Log.log(`[FirmwareUpdateV4] downloading RESC bundle ${bundle.name} from ${bundle.url}`);
|
|
798
|
-
const { binary } = await getSysResourceBinary(bundle.url);
|
|
799
|
-
bundle.binary = binary;
|
|
800
|
-
}
|
|
801
|
-
// 比对设备上已有 header
|
|
802
|
-
const upToDate = await this.isProtocolV2ResourceBundleUpToDate(bundle);
|
|
803
|
-
if (upToDate) {
|
|
804
|
-
Log.log(`[FirmwareUpdateV4] skip RESC bundle ${bundle.name}; already up to date`);
|
|
805
|
-
} else {
|
|
806
|
-
filtered.push(bundle);
|
|
807
|
-
}
|
|
808
|
-
}
|
|
809
|
-
bundlesToSync = filtered;
|
|
810
|
-
}
|
|
811
|
-
|
|
812
|
-
if (bundlesToSync.length === 0) {
|
|
813
|
-
Log.log('[FirmwareUpdateV4] all RESC bundles up to date, nothing to sync');
|
|
814
|
-
return { processedSize: 0, totalSize: firmwareSize };
|
|
2069
|
+
private async readProtocolV2DeviceFileHeader(path: string, expectedSize?: number) {
|
|
2070
|
+
const typedCall = this.device.getCommands().typedCall.bind(this.device.getCommands());
|
|
2071
|
+
const filePath = this.getProtocolV2ResourceFilePath(path);
|
|
2072
|
+
const pathInfoRes = await typedCall('FilesystemPathInfoQuery', 'FilesystemPathInfo', {
|
|
2073
|
+
path: filePath,
|
|
2074
|
+
});
|
|
2075
|
+
const fileSize = toProtocolV2FiniteNumber(pathInfoRes.message?.size);
|
|
2076
|
+
if (
|
|
2077
|
+
!pathInfoRes.message?.exist ||
|
|
2078
|
+
pathInfoRes.message?.directory ||
|
|
2079
|
+
fileSize === undefined ||
|
|
2080
|
+
fileSize < PROTOCOL_V2_OKPP_HEADER_SIZE ||
|
|
2081
|
+
(expectedSize !== undefined && fileSize !== expectedSize)
|
|
2082
|
+
) {
|
|
2083
|
+
return null;
|
|
815
2084
|
}
|
|
816
2085
|
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
processedSize,
|
|
831
|
-
totalSize: transferTotalSize,
|
|
2086
|
+
const chunkSize = this.getProtocolV2FirmwareChunkSize('read');
|
|
2087
|
+
const chunks: Uint8Array[] = [];
|
|
2088
|
+
let offset = 0;
|
|
2089
|
+
while (offset < PROTOCOL_V2_OKPP_HEADER_SIZE) {
|
|
2090
|
+
const readLen = Math.min(chunkSize, PROTOCOL_V2_OKPP_HEADER_SIZE - offset);
|
|
2091
|
+
const res = await typedCall('FilesystemFileRead', 'FilesystemFile', {
|
|
2092
|
+
file: {
|
|
2093
|
+
path: filePath,
|
|
2094
|
+
offset,
|
|
2095
|
+
total_size: 0,
|
|
2096
|
+
},
|
|
2097
|
+
chunk_len: readLen,
|
|
2098
|
+
ui_percentage: undefined,
|
|
832
2099
|
});
|
|
2100
|
+
const data = toProtocolV2Bytes(res.message?.data);
|
|
2101
|
+
if (data.byteLength === 0) return null;
|
|
2102
|
+
chunks.push(data);
|
|
2103
|
+
offset += data.byteLength;
|
|
833
2104
|
}
|
|
834
2105
|
|
|
835
|
-
const
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
);
|
|
841
|
-
return
|
|
2106
|
+
const headerBytes = new Uint8Array(offset);
|
|
2107
|
+
let cursor = 0;
|
|
2108
|
+
chunks.forEach(chunk => {
|
|
2109
|
+
headerBytes.set(chunk, cursor);
|
|
2110
|
+
cursor += chunk.byteLength;
|
|
2111
|
+
});
|
|
2112
|
+
return parseProtocolV2OkppHeader(headerBytes);
|
|
842
2113
|
}
|
|
843
2114
|
|
|
844
|
-
/**
|
|
845
|
-
* 比对设备上已有 okpkg 的 OKPP header(仅远端配置模式使用)。
|
|
846
|
-
*/
|
|
2115
|
+
/** Compare the downloaded and installed okpkg headers before transferring a resource. */
|
|
847
2116
|
private async isProtocolV2ResourceBundleUpToDate(
|
|
848
|
-
bundle:
|
|
2117
|
+
bundle: Pick<
|
|
2118
|
+
ProtocolV2ResourceBundleSource,
|
|
2119
|
+
'name' | 'source' | 'devicePath' | 'version' | 'payloadHash' | 'headerHash'
|
|
2120
|
+
>
|
|
849
2121
|
): Promise<boolean> {
|
|
850
|
-
if (this.params
|
|
851
|
-
if (!bundle.
|
|
2122
|
+
if (this.params?.forcedUpdateRes) return false;
|
|
2123
|
+
if (!bundle.payloadHash || !bundle.headerHash) return false;
|
|
852
2124
|
|
|
853
2125
|
try {
|
|
854
|
-
const header = await this.readProtocolV2DeviceFileHeader(
|
|
2126
|
+
const header = await this.readProtocolV2DeviceFileHeader(
|
|
2127
|
+
bundle.devicePath,
|
|
2128
|
+
bundle.source.size
|
|
2129
|
+
);
|
|
855
2130
|
if (!header) return false;
|
|
856
2131
|
|
|
857
2132
|
if (bundle.version) {
|
|
858
2133
|
const cmp = compareProtocolV2Versions(header.version, bundle.version);
|
|
859
2134
|
if (cmp === undefined || cmp !== 0) return false;
|
|
860
2135
|
}
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
if (bundle.headerHash) {
|
|
866
|
-
const expected = normalizeProtocolV2Hex(bundle.headerHash);
|
|
867
|
-
if (expected && header.headerHash !== expected) return false;
|
|
868
|
-
}
|
|
2136
|
+
const expectedPayloadHash = normalizeProtocolV2Hex(bundle.payloadHash);
|
|
2137
|
+
const expectedHeaderHash = normalizeProtocolV2Hex(bundle.headerHash);
|
|
2138
|
+
if (!expectedPayloadHash || header.payloadHash !== expectedPayloadHash) return false;
|
|
2139
|
+
if (!expectedHeaderHash || header.headerHash !== expectedHeaderHash) return false;
|
|
869
2140
|
return true;
|
|
870
2141
|
} catch (error) {
|
|
871
2142
|
Log.log(`[FirmwareUpdateV4] RESC bundle ${bundle.name} header check failed: `, error);
|
|
@@ -875,23 +2146,41 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
875
2146
|
|
|
876
2147
|
private isProtocolV2BootloaderMode() {
|
|
877
2148
|
if (typeof this.device.isBootloader === 'function') {
|
|
878
|
-
return this.device.isBootloader();
|
|
2149
|
+
return !!this.device.isBootloader();
|
|
879
2150
|
}
|
|
880
|
-
return
|
|
2151
|
+
return (
|
|
2152
|
+
this.device.features?.mode === 'bootloader' ||
|
|
2153
|
+
(this.device.features?.mode == null && !!this.device.features?.bootloaderMode)
|
|
2154
|
+
);
|
|
881
2155
|
}
|
|
882
2156
|
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
2157
|
+
private isProtocolV2RomloaderMode() {
|
|
2158
|
+
const deviceType = this.device.getCurrentDeviceType();
|
|
2159
|
+
if (
|
|
2160
|
+
!this.device.isProtocolV2() ||
|
|
2161
|
+
(deviceType !== EDeviceType.Pro2 && deviceType !== EDeviceType.Neo)
|
|
2162
|
+
) {
|
|
886
2163
|
return false;
|
|
887
2164
|
}
|
|
2165
|
+
if (typeof this.device.isRomloader === 'function') {
|
|
2166
|
+
return this.device.isRomloader();
|
|
2167
|
+
}
|
|
2168
|
+
return this.device.features?.mode === 'romloader';
|
|
2169
|
+
}
|
|
2170
|
+
|
|
2171
|
+
private isLegacyProtocolV2Runtime() {
|
|
2172
|
+
const protocolInfo = this.device.state?.raw?.protocolV2ProtocolInfo;
|
|
2173
|
+
return protocolInfo ? isLegacyProtocolV2ProtocolInfo(protocolInfo) : false;
|
|
2174
|
+
}
|
|
888
2175
|
|
|
2176
|
+
private async rebootProtocolV2ToBootloader() {
|
|
889
2177
|
try {
|
|
890
2178
|
this.postTipMessage(FirmwareUpdateTipMessage.AutoRebootToBootloader);
|
|
891
2179
|
await this.protocolV2Reboot(DeviceRebootType.Bootloader);
|
|
892
|
-
this.postTipMessage(FirmwareUpdateTipMessage.GoToBootloaderSuccess);
|
|
893
2180
|
await wait(1000);
|
|
894
2181
|
await this.waitForProtocolV2BootloaderMode();
|
|
2182
|
+
this.protocolV2ExecutionInLoader = true;
|
|
2183
|
+
this.postTipMessage(FirmwareUpdateTipMessage.GoToBootloaderSuccess);
|
|
895
2184
|
return true;
|
|
896
2185
|
} catch (error) {
|
|
897
2186
|
if (error instanceof HardwareError) {
|
|
@@ -902,26 +2191,59 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
902
2191
|
}
|
|
903
2192
|
}
|
|
904
2193
|
|
|
2194
|
+
async enterProtocolV2BootloaderMode() {
|
|
2195
|
+
this.protocolV2LegacyDirectUpdate = false;
|
|
2196
|
+
// romloader is the first update environment and forwards targets to bootloader.
|
|
2197
|
+
// It rejects DeviceRebootType.Bootloader, so reuse the current connection.
|
|
2198
|
+
if (this.isProtocolV2RomloaderMode()) {
|
|
2199
|
+
Log.debug('Protocol V2 device is in romloader mode; start firmware update directly');
|
|
2200
|
+
this.protocolV2LegacyDirectUpdate = this.isLegacyProtocolV2Runtime();
|
|
2201
|
+
this.protocolV2ExecutionInLoader = true;
|
|
2202
|
+
return false;
|
|
2203
|
+
}
|
|
2204
|
+
if (this.isProtocolV2BootloaderMode()) {
|
|
2205
|
+
Log.debug('Protocol V2 device is already in bootloader mode, skip reboot');
|
|
2206
|
+
this.protocolV2LegacyDirectUpdate = this.isLegacyProtocolV2Runtime();
|
|
2207
|
+
this.protocolV2ExecutionInLoader = true;
|
|
2208
|
+
this.postTipMessage(FirmwareUpdateTipMessage.GoToBootloaderSuccess);
|
|
2209
|
+
return false;
|
|
2210
|
+
}
|
|
2211
|
+
|
|
2212
|
+
return this.rebootProtocolV2ToBootloader();
|
|
2213
|
+
}
|
|
2214
|
+
|
|
905
2215
|
private async waitForProtocolV2BootloaderMode(
|
|
906
2216
|
timeout = PROTOCOL_V2_BOOTLOADER_RECONNECT_TIMEOUT,
|
|
907
2217
|
retryInterval = 1000
|
|
908
2218
|
) {
|
|
909
2219
|
const startTime = Date.now();
|
|
910
2220
|
let lastError: unknown;
|
|
2221
|
+
let shouldReconnect = true;
|
|
911
2222
|
|
|
912
2223
|
while (Date.now() - startTime < timeout) {
|
|
913
2224
|
try {
|
|
914
|
-
|
|
2225
|
+
if (shouldReconnect) {
|
|
2226
|
+
await this.reconnectProtocolV2Device();
|
|
2227
|
+
shouldReconnect = false;
|
|
2228
|
+
}
|
|
915
2229
|
const deviceInfo = await requestProtocolV2DeviceInfo({
|
|
916
2230
|
commands: this.device.getCommands(),
|
|
917
|
-
timeoutMs:
|
|
2231
|
+
timeoutMs: this.getProtocolV2DeviceInfoTimeout(),
|
|
918
2232
|
});
|
|
919
|
-
|
|
920
|
-
|
|
2233
|
+
this.assertProtocolV2DeviceInfoIdentity(deviceInfo);
|
|
2234
|
+
const features = await this.device.probeProtocolV2RuntimeState(
|
|
2235
|
+
deviceInfo,
|
|
2236
|
+
PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT
|
|
2237
|
+
);
|
|
2238
|
+
if (features?.mode === 'bootloader') {
|
|
921
2239
|
return features;
|
|
922
2240
|
}
|
|
923
2241
|
lastError = new Error('Protocol V2 device is reachable but is not in bootloader mode');
|
|
924
2242
|
} catch (error) {
|
|
2243
|
+
if (this.isProtocolV2ReconnectIdentityError(error)) {
|
|
2244
|
+
throw error;
|
|
2245
|
+
}
|
|
2246
|
+
shouldReconnect = true;
|
|
925
2247
|
lastError = error;
|
|
926
2248
|
Log.log('Protocol V2 bootloader mode not ready, polling reconnect: ', error);
|
|
927
2249
|
}
|
|
@@ -937,8 +2259,8 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
937
2259
|
}
|
|
938
2260
|
|
|
939
2261
|
/**
|
|
940
|
-
*
|
|
941
|
-
*
|
|
2262
|
+
* Collect explicit target binaries grouped by DeviceFirmwareTargetType.
|
|
2263
|
+
* Filenames are display-only staging paths because target_id is explicit.
|
|
942
2264
|
*/
|
|
943
2265
|
private collectExplicitTargetBinaries() {
|
|
944
2266
|
const entries: ProtocolV2TargetBinary[] = [];
|
|
@@ -974,115 +2296,235 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
974
2296
|
return entries;
|
|
975
2297
|
}
|
|
976
2298
|
|
|
2299
|
+
private async executeProtocolV2SourceUpdate({
|
|
2300
|
+
installSources,
|
|
2301
|
+
resourceSources,
|
|
2302
|
+
}: {
|
|
2303
|
+
installSources: ProtocolV2InstallSource[];
|
|
2304
|
+
resourceSources: ProtocolV2ResourceBundleSource[];
|
|
2305
|
+
}) {
|
|
2306
|
+
this.protocolV2BootResourceStagingSafe = false;
|
|
2307
|
+
if (installSources.length > 0 || resourceSources.length > 0) {
|
|
2308
|
+
await this.enterProtocolV2BootloaderMode();
|
|
2309
|
+
// Clear stale boot-resource staging before writing any artifacts. The new
|
|
2310
|
+
// boot resource, resources and firmware then share one transfer session,
|
|
2311
|
+
// one global progress range and one multi-target install request.
|
|
2312
|
+
// Preserve the approved source order instead of synthesizing a boot-first
|
|
2313
|
+
// phase. Loader firmware owns the cross-stage handoff: bootloader runs its
|
|
2314
|
+
// component targets and leaves boot pending for romloader, while pending
|
|
2315
|
+
// runner-backed records always resume before the application boots.
|
|
2316
|
+
await this.ensureProtocolV2BootResourceStagingIsEmpty();
|
|
2317
|
+
await this.executeProtocolV2TransferPhase({
|
|
2318
|
+
installSources,
|
|
2319
|
+
resourceSources,
|
|
2320
|
+
});
|
|
2321
|
+
}
|
|
2322
|
+
return this.completeProtocolV2FinalVerification();
|
|
2323
|
+
}
|
|
2324
|
+
|
|
2325
|
+
private async ensureProtocolV2BootResourceStagingIsEmpty() {
|
|
2326
|
+
if (this.protocolV2BootResourceStagingSafe) return;
|
|
2327
|
+
const typedCall = this.device.getCommands().typedCall.bind(this.device.getCommands());
|
|
2328
|
+
const query = () =>
|
|
2329
|
+
typedCall('FilesystemPathInfoQuery', 'FilesystemPathInfo', {
|
|
2330
|
+
path: PROTOCOL_V2_BOOT_RESOURCE_PACKAGE_STAGING_PATH,
|
|
2331
|
+
});
|
|
2332
|
+
const current = await query();
|
|
2333
|
+
if (current.message?.exist) {
|
|
2334
|
+
if (current.message.directory) {
|
|
2335
|
+
throw ERRORS.TypedError(
|
|
2336
|
+
HardwareErrorCode.EmmcFileWriteFirmwareError,
|
|
2337
|
+
'Protocol V2 boot resource staging path is not a file'
|
|
2338
|
+
);
|
|
2339
|
+
}
|
|
2340
|
+
await typedCall('FilesystemFileDelete', 'Success', {
|
|
2341
|
+
path: PROTOCOL_V2_BOOT_RESOURCE_PACKAGE_STAGING_PATH,
|
|
2342
|
+
});
|
|
2343
|
+
const remaining = await query();
|
|
2344
|
+
if (remaining.message?.exist) {
|
|
2345
|
+
throw ERRORS.TypedError(
|
|
2346
|
+
HardwareErrorCode.EmmcFileWriteFirmwareError,
|
|
2347
|
+
'Protocol V2 stale boot resource staging file could not be removed'
|
|
2348
|
+
);
|
|
2349
|
+
}
|
|
2350
|
+
}
|
|
2351
|
+
this.protocolV2BootResourceStagingSafe = true;
|
|
2352
|
+
}
|
|
2353
|
+
|
|
977
2354
|
private async executeProtocolV2Update({
|
|
978
2355
|
fwBinaryMap,
|
|
979
2356
|
bootloaderBinary,
|
|
980
2357
|
installItems,
|
|
981
|
-
resourceBundles,
|
|
982
2358
|
}: {
|
|
983
2359
|
fwBinaryMap?: ProtocolV2TargetBinary[];
|
|
984
2360
|
bootloaderBinary?: ArrayBuffer | null;
|
|
985
2361
|
installItems?: ProtocolV2InstallItem[];
|
|
986
|
-
resourceBundles?: ProtocolV2ResourceBundleBinary[];
|
|
987
2362
|
}) {
|
|
988
|
-
const
|
|
2363
|
+
const memoryInstallItems =
|
|
989
2364
|
installItems ??
|
|
990
2365
|
this.buildProtocolV2InstallItems({
|
|
991
|
-
bootloaderBinary: bootloaderBinary ?? null,
|
|
992
2366
|
fwBinaryMap: fwBinaryMap ?? [],
|
|
2367
|
+
bootloaderBinary: bootloaderBinary ?? null,
|
|
993
2368
|
});
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
if (resourceBundles?.length) {
|
|
1003
|
-
const resourceTransfer = await this.syncProtocolV2ResourceBundles(
|
|
1004
|
-
resourceBundles,
|
|
1005
|
-
firmwareSize
|
|
2369
|
+
try {
|
|
2370
|
+
const installSources = await Promise.all(
|
|
2371
|
+
memoryInstallItems.map(async item => ({
|
|
2372
|
+
fileName: item.fileName,
|
|
2373
|
+
source: await this.openProtocolV2MemorySource(item.binary),
|
|
2374
|
+
targetId: item.targetId,
|
|
2375
|
+
kind: item.kind,
|
|
2376
|
+
}))
|
|
1006
2377
|
);
|
|
1007
|
-
|
|
1008
|
-
|
|
2378
|
+
return await this.executeProtocolV2SourceUpdate({
|
|
2379
|
+
installSources,
|
|
2380
|
+
resourceSources: [],
|
|
2381
|
+
});
|
|
2382
|
+
} finally {
|
|
2383
|
+
await this.closeProtocolV2PreparedSources();
|
|
1009
2384
|
}
|
|
2385
|
+
}
|
|
1010
2386
|
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
2387
|
+
private async executeProtocolV2TransferPhase({
|
|
2388
|
+
installSources,
|
|
2389
|
+
resourceSources,
|
|
2390
|
+
}: ProtocolV2TransferBatch) {
|
|
2391
|
+
let totalSize = installSources.reduce((total, item) => total + item.source.size, 0);
|
|
2392
|
+
const resourcesToSync: ProtocolV2ResourceBundleSource[] = [];
|
|
2393
|
+
for (const resource of resourceSources) {
|
|
2394
|
+
// Early boot promotes this mounted package's staging file. Always replace any
|
|
2395
|
+
// stale staging bytes with the artifact approved by the current PreparedPlan,
|
|
2396
|
+
// even when the mounted final file itself is already current.
|
|
2397
|
+
const requiresFreshStaging = isProtocolV2BootResourcePackagePath(resource.devicePath);
|
|
2398
|
+
if (!requiresFreshStaging && (await this.isProtocolV2ResourceBundleUpToDate(resource))) {
|
|
2399
|
+
Log.log(`[FirmwareUpdateV4] skip RESC bundle ${resource.name}; already up to date`);
|
|
2400
|
+
} else {
|
|
2401
|
+
resourcesToSync.push(resource);
|
|
2402
|
+
totalSize += resource.source.size;
|
|
1016
2403
|
}
|
|
1017
|
-
return;
|
|
1018
2404
|
}
|
|
1019
|
-
let transferredSize = processedSize;
|
|
1020
|
-
const transferStartTime = Date.now();
|
|
1021
|
-
const transferTransport = this.getProtocolV2FirmwareTransferTransport();
|
|
1022
|
-
const chunkSize = this.getProtocolV2FirmwareChunkSize();
|
|
1023
|
-
const onTransferredBytes = (bytes: number) => {
|
|
1024
|
-
transferredSize = bytes;
|
|
1025
|
-
};
|
|
1026
|
-
Log.log(
|
|
1027
|
-
`[FirmwareUpdateV4] transfer started transport=${transferTransport} total=${totalSize} bytes chunk=${chunkSize} bytes`
|
|
1028
|
-
);
|
|
1029
|
-
|
|
1030
|
-
const stagedInstallTargets: ProtocolV2InstallTarget[] = [];
|
|
1031
2405
|
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
stagedInstallTargets.push({
|
|
1049
|
-
...item,
|
|
1050
|
-
path: filePath,
|
|
1051
|
-
});
|
|
2406
|
+
this.postTipMessage(FirmwareUpdateTipMessage.StartTransferData);
|
|
2407
|
+
let processedSize = 0;
|
|
2408
|
+
for (const resource of resourcesToSync) {
|
|
2409
|
+
// The bootloader keeps its live resource package mounted. FatFs rejects
|
|
2410
|
+
// replacing an open file, so early boot promotes this staging file before mounting it.
|
|
2411
|
+
const writePath = resolveProtocolV2ResourceWritePath(resource.devicePath);
|
|
2412
|
+
processedSize = await this.protocolV2SourceUpdateProcess({
|
|
2413
|
+
source: resource.source,
|
|
2414
|
+
filePath: writePath,
|
|
2415
|
+
processedSize,
|
|
2416
|
+
totalSize,
|
|
2417
|
+
});
|
|
2418
|
+
await this.verifyProtocolV2StagedFile(writePath, resource.source.size);
|
|
2419
|
+
if (isProtocolV2BootResourcePackagePath(resource.devicePath)) {
|
|
2420
|
+
this.protocolV2BootResourceStagingSafe = true;
|
|
1052
2421
|
}
|
|
2422
|
+
}
|
|
1053
2423
|
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
2424
|
+
const stagedInstallTargets: Array<{ targetId: number; path: string }> = [];
|
|
2425
|
+
for (const item of installSources) {
|
|
2426
|
+
const filePath = this.getProtocolV2InstallItemStagingPath(item);
|
|
2427
|
+
processedSize = await this.protocolV2SourceUpdateProcess({
|
|
2428
|
+
source: item.source,
|
|
2429
|
+
filePath,
|
|
2430
|
+
processedSize,
|
|
2431
|
+
totalSize,
|
|
2432
|
+
});
|
|
2433
|
+
await this.verifyProtocolV2StagedFile(filePath, item.source.size);
|
|
2434
|
+
stagedInstallTargets.push({
|
|
2435
|
+
targetId: item.targetId,
|
|
2436
|
+
path: filePath,
|
|
2437
|
+
});
|
|
2438
|
+
}
|
|
1057
2439
|
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
);
|
|
1064
|
-
} catch (error) {
|
|
1065
|
-
const elapsedMs = Date.now() - transferStartTime;
|
|
1066
|
-
Log.warn(
|
|
1067
|
-
`[FirmwareUpdateV4] transfer failed transport=${transferTransport} bytes=${transferredSize}/${totalSize} elapsed=${(
|
|
1068
|
-
elapsedMs / 1000
|
|
1069
|
-
).toFixed(2)}s speed=${formatProtocolV2TransferSpeed(transferredSize, elapsedMs)} KB/s`
|
|
1070
|
-
);
|
|
1071
|
-
throw error;
|
|
2440
|
+
if (totalSize > 0) {
|
|
2441
|
+
this.postProgressMessage(100, 'transferData');
|
|
2442
|
+
}
|
|
2443
|
+
if (stagedInstallTargets.length === 0) {
|
|
2444
|
+
return;
|
|
1072
2445
|
}
|
|
1073
2446
|
|
|
1074
2447
|
this.postTipMessage(FirmwareUpdateTipMessage.ConfirmOnDevice);
|
|
1075
|
-
|
|
1076
|
-
const allTargets = stagedInstallTargets.map(item => ({
|
|
2448
|
+
const targets = stagedInstallTargets.map(item => ({
|
|
1077
2449
|
target_id: item.targetId,
|
|
1078
2450
|
path: item.path,
|
|
1079
2451
|
}));
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
2452
|
+
await this.protocolV2StartFirmwareUpdate({ targets });
|
|
2453
|
+
await this.waitForProtocolV2FirmwareUpdateComplete(targets);
|
|
2454
|
+
}
|
|
2455
|
+
|
|
2456
|
+
private async protocolV2SourceUpdateProcess({
|
|
2457
|
+
source,
|
|
2458
|
+
filePath,
|
|
2459
|
+
processedSize,
|
|
2460
|
+
totalSize,
|
|
2461
|
+
}: {
|
|
2462
|
+
source: FirmwareByteSource;
|
|
2463
|
+
filePath: string;
|
|
2464
|
+
processedSize: number;
|
|
2465
|
+
totalSize: number;
|
|
2466
|
+
}) {
|
|
2467
|
+
let lastError: unknown;
|
|
2468
|
+
for (let attempt = 1; attempt <= PROTOCOL_V2_FILE_TRANSFER_RETRY_COUNT; attempt += 1) {
|
|
2469
|
+
try {
|
|
2470
|
+
const transferStartedAt = Date.now();
|
|
2471
|
+
await writeFirmwareByteSource({
|
|
2472
|
+
source,
|
|
2473
|
+
chunkSize: this.getProtocolV2FirmwareChunkSize('write', filePath),
|
|
2474
|
+
write: async ({ data, sourceOffset, length, first }) => {
|
|
2475
|
+
const chunkEnd = sourceOffset + length;
|
|
2476
|
+
const deviceProgress = getProtocolV2DeviceTransferProgress(
|
|
2477
|
+
processedSize + sourceOffset,
|
|
2478
|
+
processedSize + chunkEnd,
|
|
2479
|
+
totalSize
|
|
2480
|
+
);
|
|
2481
|
+
const response = await this.fileWriteChunk(
|
|
2482
|
+
filePath,
|
|
2483
|
+
source.size,
|
|
2484
|
+
sourceOffset,
|
|
2485
|
+
data,
|
|
2486
|
+
first,
|
|
2487
|
+
deviceProgress
|
|
2488
|
+
);
|
|
2489
|
+
const rawProcessedByte = response.message.processed_byte;
|
|
2490
|
+
const nextOffset = rawProcessedByte === undefined ? chunkEnd : Number(rawProcessedByte);
|
|
2491
|
+
if (!Number.isFinite(nextOffset) || nextOffset !== chunkEnd) {
|
|
2492
|
+
throw ERRORS.TypedError(
|
|
2493
|
+
HardwareErrorCode.EmmcFileWriteFirmwareError,
|
|
2494
|
+
`invalid processed_byte ${rawProcessedByte} for offset ${sourceOffset}`
|
|
2495
|
+
);
|
|
2496
|
+
}
|
|
2497
|
+
const transferredBytes = processedSize + chunkEnd;
|
|
2498
|
+
const elapsedMs = Math.max(Date.now() - transferStartedAt, 0);
|
|
2499
|
+
this.postProgressMessage(
|
|
2500
|
+
Math.min(Math.ceil((transferredBytes / totalSize) * 100), 99),
|
|
2501
|
+
'transferData',
|
|
2502
|
+
{
|
|
2503
|
+
transferredBytes,
|
|
2504
|
+
totalBytes: totalSize,
|
|
2505
|
+
rateBytesPerSecond:
|
|
2506
|
+
elapsedMs > 0 ? Math.round((chunkEnd / elapsedMs) * 1000) : undefined,
|
|
2507
|
+
elapsedMs,
|
|
2508
|
+
}
|
|
2509
|
+
);
|
|
2510
|
+
return length;
|
|
2511
|
+
},
|
|
2512
|
+
});
|
|
2513
|
+
return processedSize + source.size;
|
|
2514
|
+
} catch (error) {
|
|
2515
|
+
lastError = error;
|
|
2516
|
+
if (attempt < PROTOCOL_V2_FILE_TRANSFER_RETRY_COUNT) {
|
|
2517
|
+
await this.recoverProtocolV2FileTransfer();
|
|
2518
|
+
}
|
|
2519
|
+
}
|
|
2520
|
+
}
|
|
2521
|
+
throw ERRORS.TypedError(
|
|
2522
|
+
HardwareErrorCode.EmmcFileWriteFirmwareError,
|
|
2523
|
+
`transfer data error: ${getProtocolV2UnknownErrorText(lastError)}`
|
|
2524
|
+
);
|
|
1083
2525
|
}
|
|
1084
2526
|
|
|
1085
|
-
private getProtocolV2InstallItemStagingPath(item: ProtocolV2InstallItem) {
|
|
2527
|
+
private getProtocolV2InstallItemStagingPath(item: Pick<ProtocolV2InstallItem, 'fileName'>) {
|
|
1086
2528
|
return `${PROTOCOL_V2_FIRMWARE_STAGING_VOLUME}${item.fileName}`;
|
|
1087
2529
|
}
|
|
1088
2530
|
|
|
@@ -1099,168 +2541,397 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1099
2541
|
}
|
|
1100
2542
|
}
|
|
1101
2543
|
|
|
1102
|
-
private async queryProtocolV2FirmwareUpdateStatus() {
|
|
1103
|
-
const typedCall = this.device.getCommands().typedCall.bind(this.device.getCommands());
|
|
1104
|
-
return typedCall(
|
|
1105
|
-
'DeviceFirmwareUpdateStatusGet',
|
|
1106
|
-
'DeviceFirmwareUpdateStatus',
|
|
1107
|
-
{},
|
|
1108
|
-
{
|
|
1109
|
-
timeoutMs: PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT,
|
|
1110
|
-
}
|
|
1111
|
-
);
|
|
1112
|
-
}
|
|
1113
|
-
|
|
1114
|
-
private async pingProtocolV2Device() {
|
|
1115
|
-
const typedCall = this.device.getCommands().typedCall.bind(this.device.getCommands());
|
|
1116
|
-
await typedCall(
|
|
1117
|
-
'Ping',
|
|
1118
|
-
'Success',
|
|
1119
|
-
{ message: 'firmware-update' },
|
|
1120
|
-
{
|
|
1121
|
-
timeoutMs: PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT,
|
|
1122
|
-
}
|
|
1123
|
-
);
|
|
1124
|
-
}
|
|
1125
|
-
|
|
1126
|
-
private isProtocolV2NormalModeFeatures(features?: Features | null) {
|
|
1127
|
-
return !!features && !features.bootloaderMode && features.mode !== 'bootloader';
|
|
1128
|
-
}
|
|
1129
|
-
|
|
1130
|
-
private async probeProtocolV2NormalMode() {
|
|
1131
|
-
const deviceInfo = await requestProtocolV2DeviceInfo({
|
|
1132
|
-
commands: this.device.getCommands(),
|
|
1133
|
-
timeoutMs: PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT,
|
|
1134
|
-
request: PROTOCOL_V2_VERSIONS_DEVICE_INFO_REQUEST,
|
|
1135
|
-
});
|
|
1136
|
-
const features = this.device.updateProtocolV2Features(deviceInfo);
|
|
1137
|
-
if (this.isProtocolV2NormalModeFeatures(features)) {
|
|
1138
|
-
Log.log('Protocol V2 firmware install finished; device is back in normal mode');
|
|
1139
|
-
return true;
|
|
1140
|
-
}
|
|
1141
|
-
return false;
|
|
1142
|
-
}
|
|
1143
|
-
|
|
1144
2544
|
private assertProtocolV2TargetStatus(
|
|
1145
2545
|
statusTargets: ProtocolV2FirmwareUpdateStatusTarget[],
|
|
1146
|
-
expectedTargetIds: Set<number
|
|
2546
|
+
expectedTargetIds: Set<number>,
|
|
2547
|
+
expectedPaths = new Map<number, string>()
|
|
1147
2548
|
) {
|
|
2549
|
+
Log.log(
|
|
2550
|
+
`[FirmwareUpdateV4] DeviceFirmwareUpdateStatus records=${JSON.stringify(statusTargets)}`
|
|
2551
|
+
);
|
|
1148
2552
|
const failedTarget = statusTargets.find(
|
|
1149
2553
|
target =>
|
|
1150
2554
|
expectedTargetIds.has(normalizeProtocolV2TargetId(target.target_id) ?? -1) &&
|
|
1151
2555
|
isProtocolV2TargetStatusFailed(target.status)
|
|
1152
2556
|
);
|
|
1153
2557
|
if (failedTarget) {
|
|
2558
|
+
const failedTargetDetails = JSON.stringify({
|
|
2559
|
+
targetId: failedTarget.target_id,
|
|
2560
|
+
status: failedTarget.status,
|
|
2561
|
+
payloadVersion: failedTarget.payload_version,
|
|
2562
|
+
path: failedTarget.path,
|
|
2563
|
+
});
|
|
2564
|
+
Log.error(`[FirmwareUpdateV4] firmware install failed target=${failedTargetDetails}`);
|
|
1154
2565
|
throw ERRORS.TypedError(
|
|
1155
2566
|
HardwareErrorCode.FirmwareError,
|
|
1156
|
-
`Protocol V2 firmware target
|
|
2567
|
+
`Protocol V2 firmware target failed: target=${failedTarget.target_id} status=${
|
|
2568
|
+
failedTarget.status ?? 'unknown'
|
|
2569
|
+
} payloadVersion=${failedTarget.payload_version ?? 'unknown'} path=${
|
|
2570
|
+
failedTarget.path ?? 'unknown'
|
|
2571
|
+
}`
|
|
1157
2572
|
);
|
|
1158
2573
|
}
|
|
1159
2574
|
|
|
1160
|
-
const
|
|
1161
|
-
target
|
|
1162
|
-
|
|
1163
|
-
|
|
2575
|
+
const matchingTargets = statusTargets.filter(target =>
|
|
2576
|
+
expectedTargetIds.has(normalizeProtocolV2TargetId(target.target_id) ?? -1)
|
|
2577
|
+
);
|
|
2578
|
+
const seenTargetIds = new Set<number>();
|
|
2579
|
+
for (const target of matchingTargets) {
|
|
2580
|
+
const targetId = normalizeProtocolV2TargetId(target.target_id);
|
|
2581
|
+
if (
|
|
2582
|
+
targetId === undefined ||
|
|
2583
|
+
seenTargetIds.has(targetId) ||
|
|
2584
|
+
(target.path && expectedPaths.get(targetId) && target.path !== expectedPaths.get(targetId))
|
|
2585
|
+
) {
|
|
2586
|
+
throw ERRORS.TypedError(
|
|
2587
|
+
HardwareErrorCode.RuntimeError,
|
|
2588
|
+
`Protocol V2 install status conflicts with target ${target.target_id}`,
|
|
2589
|
+
{
|
|
2590
|
+
firmwareUpdateCode: PROTOCOL_V2_INSTALL_STATUS_CONFLICT_CODE,
|
|
2591
|
+
}
|
|
2592
|
+
);
|
|
2593
|
+
}
|
|
2594
|
+
seenTargetIds.add(targetId);
|
|
2595
|
+
}
|
|
2596
|
+
const completedTargets = matchingTargets.filter(target =>
|
|
2597
|
+
isProtocolV2TargetStatusFinished(target.status)
|
|
1164
2598
|
);
|
|
1165
|
-
|
|
2599
|
+
const completedTargetIds = new Set<number>();
|
|
2600
|
+
completedTargets.forEach(target => {
|
|
2601
|
+
const targetId = normalizeProtocolV2TargetId(target.target_id);
|
|
2602
|
+
if (targetId !== undefined) {
|
|
2603
|
+
completedTargetIds.add(targetId);
|
|
2604
|
+
}
|
|
2605
|
+
});
|
|
2606
|
+
if (completedTargetIds.size === expectedTargetIds.size && expectedTargetIds.size > 0) {
|
|
2607
|
+
for (const target of completedTargets) {
|
|
2608
|
+
const targetId = normalizeProtocolV2TargetId(target.target_id);
|
|
2609
|
+
const payloadVersion = toProtocolV2FiniteNumber(target.payload_version);
|
|
2610
|
+
if (targetId === undefined) {
|
|
2611
|
+
throw ERRORS.TypedError(
|
|
2612
|
+
HardwareErrorCode.RuntimeError,
|
|
2613
|
+
'Protocol V2 completed target identity is invalid'
|
|
2614
|
+
);
|
|
2615
|
+
}
|
|
2616
|
+
const expectedVersion = this.getExpectedProtocolV2TargetVersion(targetId);
|
|
2617
|
+
if (expectedVersion !== undefined && payloadVersion !== undefined) {
|
|
2618
|
+
if (payloadVersion !== expectedVersion) {
|
|
2619
|
+
throw ERRORS.TypedError(
|
|
2620
|
+
HardwareErrorCode.FirmwareVerificationFailed,
|
|
2621
|
+
`Protocol V2 target ${targetId} reached payload version ${payloadVersion}, expected ${expectedVersion}`
|
|
2622
|
+
);
|
|
2623
|
+
}
|
|
2624
|
+
}
|
|
2625
|
+
if (payloadVersion !== undefined) {
|
|
2626
|
+
this.protocolV2CompletedTargetVersions.set(targetId, payloadVersion);
|
|
2627
|
+
}
|
|
2628
|
+
}
|
|
2629
|
+
this.postProgressMessage(100, 'installingFirmware');
|
|
1166
2630
|
return true;
|
|
1167
2631
|
}
|
|
1168
2632
|
|
|
1169
|
-
|
|
1170
|
-
target =>
|
|
1171
|
-
expectedTargetIds.has(normalizeProtocolV2TargetId(target.target_id) ?? -1) &&
|
|
2633
|
+
if (expectedTargetIds.size > 0 && matchingTargets.length > 0) {
|
|
2634
|
+
const hasInProgressTarget = matchingTargets.some(target =>
|
|
1172
2635
|
isProtocolV2TargetStatusInProgress(target.status)
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
2636
|
+
);
|
|
2637
|
+
const completedProgress = Math.floor(
|
|
2638
|
+
(completedTargetIds.size / expectedTargetIds.size) * 100
|
|
2639
|
+
);
|
|
2640
|
+
// The protocol exposes no per-target percentage, so report coarse progress by
|
|
2641
|
+
// completed targets and use 1% once work starts to keep the UI responsive.
|
|
2642
|
+
const progress = Math.min(99, Math.max(completedProgress, hasInProgressTarget ? 1 : 0));
|
|
2643
|
+
this.postProgressMessage(progress, 'installingFirmware');
|
|
1176
2644
|
}
|
|
1177
2645
|
|
|
1178
2646
|
return false;
|
|
1179
2647
|
}
|
|
1180
2648
|
|
|
1181
|
-
private
|
|
1182
|
-
|
|
1183
|
-
|
|
2649
|
+
private getProtocolV2MissingTargetIds(
|
|
2650
|
+
statusTargets: ProtocolV2FirmwareUpdateStatusTarget[],
|
|
2651
|
+
expectedTargetIds: Set<number>
|
|
1184
2652
|
) {
|
|
1185
|
-
const
|
|
1186
|
-
|
|
1187
|
-
const
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
return;
|
|
2653
|
+
const reportedTargetIds = new Set<number>();
|
|
2654
|
+
statusTargets.forEach(target => {
|
|
2655
|
+
const targetId = normalizeProtocolV2TargetId(target.target_id);
|
|
2656
|
+
if (targetId !== undefined) {
|
|
2657
|
+
reportedTargetIds.add(targetId);
|
|
1191
2658
|
}
|
|
1192
|
-
}
|
|
2659
|
+
});
|
|
2660
|
+
return Array.from(expectedTargetIds).filter(targetId => !reportedTargetIds.has(targetId));
|
|
2661
|
+
}
|
|
2662
|
+
|
|
2663
|
+
private getProtocolV2ObservableTargetVersions(features: Features) {
|
|
2664
|
+
const versions = new Map<number, string>();
|
|
2665
|
+
versions.set(
|
|
2666
|
+
ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_BOOTLOADER,
|
|
2667
|
+
getDeviceBootloaderVersion(features).join('.')
|
|
2668
|
+
);
|
|
2669
|
+
const applicationVersion = getDeviceFirmwareVersion(features).join('.');
|
|
2670
|
+
versions.set(ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_APPLICATION_P1, applicationVersion);
|
|
2671
|
+
versions.set(ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_APPLICATION_P2, applicationVersion);
|
|
2672
|
+
versions.set(
|
|
2673
|
+
ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_COPROCESSOR,
|
|
2674
|
+
getDeviceBLEFirmwareVersion(features).join('.')
|
|
2675
|
+
);
|
|
2676
|
+
const secureElementVersions: Array<[number, string | null | undefined]> = [
|
|
2677
|
+
[ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_SE01, features.se01Version],
|
|
2678
|
+
[ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_SE02, features.se02Version],
|
|
2679
|
+
[ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_SE03, features.se03Version],
|
|
2680
|
+
[ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_SE04, features.se04Version],
|
|
2681
|
+
];
|
|
2682
|
+
secureElementVersions.forEach(([targetId, version]) => {
|
|
2683
|
+
if (version) versions.set(targetId, version);
|
|
2684
|
+
});
|
|
2685
|
+
return versions;
|
|
2686
|
+
}
|
|
2687
|
+
|
|
2688
|
+
private hasProtocolV2InstallVersionChanged(expectedTargetIds: Set<number>) {
|
|
2689
|
+
if (!this.protocolV2LastRuntimeProbeFeatures) return false;
|
|
2690
|
+
const currentVersions = this.getProtocolV2ObservableTargetVersions(
|
|
2691
|
+
this.protocolV2LastRuntimeProbeFeatures
|
|
2692
|
+
);
|
|
2693
|
+
return Array.from(expectedTargetIds).some(targetId => {
|
|
2694
|
+
const previousVersion = this.protocolV2InstallBaselineVersions.get(targetId);
|
|
2695
|
+
const currentVersion = currentVersions.get(targetId);
|
|
2696
|
+
return (
|
|
2697
|
+
previousVersion !== undefined &&
|
|
2698
|
+
currentVersion !== undefined &&
|
|
2699
|
+
previousVersion !== currentVersion
|
|
2700
|
+
);
|
|
2701
|
+
});
|
|
2702
|
+
}
|
|
1193
2703
|
|
|
2704
|
+
private async waitForProtocolV2FirmwareUpdateComplete(
|
|
2705
|
+
targets: Array<{ target_id: number; path: string }>
|
|
2706
|
+
) {
|
|
2707
|
+
this.protocolV2FinalStatusVerified = false;
|
|
2708
|
+
const expectedTargetIds = new Set(targets.map(target => target.target_id));
|
|
2709
|
+
const expectedPaths = new Map(targets.map(target => [target.target_id, target.path]));
|
|
1194
2710
|
const startTime = Date.now();
|
|
1195
2711
|
let lastError: unknown;
|
|
2712
|
+
let shouldReconnect = true;
|
|
2713
|
+
let deviceInfo: ProtocolV2DeviceInfo | undefined;
|
|
2714
|
+
let missingTargetStatusSince: number | undefined;
|
|
2715
|
+
let missingTargetStatusKey: string | undefined;
|
|
2716
|
+
let normalModeWithoutInstallEvidenceSince: number | undefined;
|
|
2717
|
+
let installEvidenceObserved = this.protocolV2InstallAckReceived;
|
|
2718
|
+
const resetMissingTargetStatusGrace = () => {
|
|
2719
|
+
missingTargetStatusSince = undefined;
|
|
2720
|
+
missingTargetStatusKey = undefined;
|
|
2721
|
+
};
|
|
1196
2722
|
|
|
1197
2723
|
while (Date.now() - startTime < PROTOCOL_V2_INSTALL_TIMEOUT) {
|
|
2724
|
+
// A transport release caused by an explicit workflow cancellation must not
|
|
2725
|
+
// be mistaken for the expected device reboot during installation.
|
|
2726
|
+
this.throwIfAborted();
|
|
1198
2727
|
try {
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
return;
|
|
1204
|
-
}
|
|
1205
|
-
} catch (error) {
|
|
1206
|
-
lastError = error;
|
|
1207
|
-
if (error instanceof HardwareError && error.errorCode === HardwareErrorCode.FirmwareError) {
|
|
1208
|
-
throw error;
|
|
2728
|
+
if (shouldReconnect) {
|
|
2729
|
+
await this.reconnectProtocolV2Device();
|
|
2730
|
+
deviceInfo = await this.verifyProtocolV2ReconnectIdentity();
|
|
2731
|
+
shouldReconnect = false;
|
|
1209
2732
|
}
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
2733
|
+
const currentDeviceInfo = deviceInfo;
|
|
2734
|
+
try {
|
|
2735
|
+
const statusResponse = await this.device.getCommands().typedCall(
|
|
2736
|
+
'DeviceFirmwareUpdateStatusGet',
|
|
2737
|
+
'DeviceFirmwareUpdateStatus',
|
|
2738
|
+
{
|
|
2739
|
+
fields: {
|
|
2740
|
+
status: true,
|
|
2741
|
+
payload_version: true,
|
|
2742
|
+
path: true,
|
|
2743
|
+
},
|
|
2744
|
+
},
|
|
2745
|
+
{ timeoutMs: PROTOCOL_V2_FIRMWARE_STATUS_RESPONSE_TIMEOUT }
|
|
2746
|
+
);
|
|
2747
|
+
const statusTargets = (statusResponse.message.records ??
|
|
2748
|
+
[]) as ProtocolV2FirmwareUpdateStatusTarget[];
|
|
2749
|
+
if (
|
|
2750
|
+
statusTargets.some(target => {
|
|
2751
|
+
const targetId = normalizeProtocolV2TargetId(target.target_id);
|
|
2752
|
+
return targetId !== undefined && expectedTargetIds.has(targetId);
|
|
2753
|
+
})
|
|
2754
|
+
) {
|
|
2755
|
+
installEvidenceObserved = true;
|
|
2756
|
+
normalModeWithoutInstallEvidenceSince = undefined;
|
|
2757
|
+
}
|
|
2758
|
+
if (this.assertProtocolV2TargetStatus(statusTargets, expectedTargetIds, expectedPaths)) {
|
|
2759
|
+
this.protocolV2FinalStatusVerified = true;
|
|
2760
|
+
return;
|
|
2761
|
+
}
|
|
2762
|
+
|
|
2763
|
+
if (statusTargets.length === 0 && currentDeviceInfo) {
|
|
2764
|
+
const isNormalMode = await this.probeProtocolV2NormalMode(currentDeviceInfo);
|
|
2765
|
+
if (
|
|
2766
|
+
isNormalMode &&
|
|
2767
|
+
(installEvidenceObserved ||
|
|
2768
|
+
this.hasProtocolV2InstallVersionChanged(expectedTargetIds))
|
|
2769
|
+
) {
|
|
2770
|
+
Log.log(
|
|
2771
|
+
'[FirmwareUpdateV4] empty firmware status after confirmed App reboot; update complete'
|
|
2772
|
+
);
|
|
2773
|
+
this.postProgressMessage(100, 'installingFirmware');
|
|
1215
2774
|
return;
|
|
1216
2775
|
}
|
|
1217
|
-
}
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
2776
|
+
}
|
|
2777
|
+
|
|
2778
|
+
const missingTargetIds = this.getProtocolV2MissingTargetIds(
|
|
2779
|
+
statusTargets,
|
|
2780
|
+
expectedTargetIds
|
|
2781
|
+
);
|
|
2782
|
+
if (missingTargetIds.length > 0) {
|
|
2783
|
+
const now = Date.now();
|
|
2784
|
+
const missingKey = missingTargetIds.join(',');
|
|
2785
|
+
if (missingTargetStatusSince === undefined || missingTargetStatusKey !== missingKey) {
|
|
2786
|
+
missingTargetStatusSince = now;
|
|
2787
|
+
missingTargetStatusKey = missingKey;
|
|
2788
|
+
} else if (
|
|
2789
|
+
now - missingTargetStatusSince >=
|
|
2790
|
+
PROTOCOL_V2_MISSING_TARGET_STATUS_GRACE_TIMEOUT
|
|
2791
|
+
) {
|
|
2792
|
+
const reportedTargetIds = statusTargets
|
|
2793
|
+
.map(target => normalizeProtocolV2TargetId(target.target_id))
|
|
2794
|
+
.filter((targetId): targetId is number => targetId !== undefined);
|
|
2795
|
+
throw ERRORS.TypedError(
|
|
2796
|
+
HardwareErrorCode.FirmwareError,
|
|
2797
|
+
`Protocol V2 firmware status is missing requested records: targetIds=${missingKey} reportedTargetIds=${reportedTargetIds.join(
|
|
2798
|
+
','
|
|
2799
|
+
)}`
|
|
2800
|
+
);
|
|
2801
|
+
}
|
|
2802
|
+
lastError = new Error(
|
|
2803
|
+
`Protocol V2 firmware status is temporarily missing targetIds=${missingKey}`
|
|
1222
2804
|
);
|
|
2805
|
+
} else {
|
|
2806
|
+
resetMissingTargetStatusGrace();
|
|
2807
|
+
lastError = new Error('Protocol V2 firmware targets are still installing');
|
|
2808
|
+
}
|
|
2809
|
+
} catch (error) {
|
|
2810
|
+
if (isProtocolV2TerminalInstallStatusError(error)) {
|
|
2811
|
+
throw error;
|
|
1223
2812
|
}
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
2813
|
+
// Only consecutive full status dumps can prove that a record is absent.
|
|
2814
|
+
// A reboot or transport interruption starts a fresh grace window.
|
|
2815
|
+
resetMissingTargetStatusGrace();
|
|
2816
|
+
// App firmware does not register DeviceFirmwareUpdateStatusGet. Treat the
|
|
2817
|
+
// missing endpoint as completion only after the runtime probe confirms App mode.
|
|
2818
|
+
if (isProtocolV2FirmwareStatusEndpointUnavailable(error)) {
|
|
2819
|
+
if (!currentDeviceInfo) {
|
|
2820
|
+
throw ERRORS.TypedError(
|
|
2821
|
+
HardwareErrorCode.RuntimeError,
|
|
2822
|
+
'Protocol V2 device identity is unavailable during install polling'
|
|
2823
|
+
);
|
|
2824
|
+
}
|
|
2825
|
+
const isNormalMode = await this.probeProtocolV2NormalMode(currentDeviceInfo);
|
|
2826
|
+
if (
|
|
2827
|
+
isNormalMode &&
|
|
2828
|
+
(installEvidenceObserved ||
|
|
2829
|
+
this.hasProtocolV2InstallVersionChanged(expectedTargetIds))
|
|
2830
|
+
) {
|
|
2831
|
+
Log.log(
|
|
2832
|
+
'[FirmwareUpdateV4] firmware status endpoint unavailable after confirmed App reboot'
|
|
2833
|
+
);
|
|
2834
|
+
this.postProgressMessage(100, 'installingFirmware');
|
|
2835
|
+
return;
|
|
2836
|
+
}
|
|
2837
|
+
if (isNormalMode) {
|
|
2838
|
+
const now = Date.now();
|
|
2839
|
+
normalModeWithoutInstallEvidenceSince ??= now;
|
|
2840
|
+
if (
|
|
2841
|
+
now - normalModeWithoutInstallEvidenceSince >=
|
|
2842
|
+
PROTOCOL_V2_MISSING_TARGET_STATUS_GRACE_TIMEOUT
|
|
2843
|
+
) {
|
|
2844
|
+
throw ERRORS.TypedError(
|
|
2845
|
+
HardwareErrorCode.FirmwareError,
|
|
2846
|
+
'Protocol V2 device returned to normal mode without install ACK, target status, or version change'
|
|
2847
|
+
);
|
|
2848
|
+
}
|
|
2849
|
+
lastError = new Error(
|
|
2850
|
+
'Protocol V2 device is in normal mode but installation is not yet confirmed'
|
|
2851
|
+
);
|
|
2852
|
+
} else {
|
|
2853
|
+
normalModeWithoutInstallEvidenceSince = undefined;
|
|
2854
|
+
lastError = new Error(
|
|
2855
|
+
'Protocol V2 firmware status endpoint is unavailable while the device remains in loader mode'
|
|
2856
|
+
);
|
|
2857
|
+
}
|
|
2858
|
+
} else {
|
|
2859
|
+
shouldReconnect = true;
|
|
2860
|
+
deviceInfo = undefined;
|
|
2861
|
+
lastError = error;
|
|
2862
|
+
Log.log(
|
|
2863
|
+
'[FirmwareUpdateV4] DeviceFirmwareUpdateStatusGet unavailable during install: ',
|
|
2864
|
+
error
|
|
2865
|
+
);
|
|
1230
2866
|
}
|
|
1231
2867
|
}
|
|
2868
|
+
} catch (error) {
|
|
2869
|
+
lastError = error;
|
|
2870
|
+
if (this.isProtocolV2ReconnectIdentityError(error)) {
|
|
2871
|
+
throw error;
|
|
2872
|
+
}
|
|
2873
|
+
if (isProtocolV2TerminalInstallStatusError(error)) {
|
|
2874
|
+
throw error;
|
|
2875
|
+
}
|
|
2876
|
+
if (
|
|
2877
|
+
error instanceof HardwareError &&
|
|
2878
|
+
error.params?.firmwareUpdateCode === 'FirmwareInstallStatusUnavailable'
|
|
2879
|
+
) {
|
|
2880
|
+
throw error;
|
|
2881
|
+
}
|
|
2882
|
+
shouldReconnect = true;
|
|
2883
|
+
deviceInfo = undefined;
|
|
2884
|
+
resetMissingTargetStatusGrace();
|
|
2885
|
+
Log.log('Protocol V2 firmware install device readiness probe failed: ', error);
|
|
1232
2886
|
}
|
|
1233
2887
|
await wait(1000);
|
|
1234
2888
|
}
|
|
1235
2889
|
|
|
1236
2890
|
throw ERRORS.TypedError(
|
|
1237
2891
|
HardwareErrorCode.RuntimeError,
|
|
1238
|
-
`Protocol V2 firmware update status
|
|
2892
|
+
`Protocol V2 firmware update status not complete within ${
|
|
2893
|
+
PROTOCOL_V2_INSTALL_TIMEOUT / 1000
|
|
2894
|
+
}s: ${this.normalizeErrorMessage(lastError)}`
|
|
1239
2895
|
);
|
|
1240
2896
|
}
|
|
1241
2897
|
|
|
1242
2898
|
private async exitProtocolV2BootloaderToNormal() {
|
|
1243
|
-
this.
|
|
2899
|
+
await this.reconnectProtocolV2Device();
|
|
2900
|
+
const deviceInfo = await this.verifyProtocolV2ReconnectIdentity();
|
|
1244
2901
|
try {
|
|
1245
|
-
await this.
|
|
1246
|
-
|
|
1247
|
-
Log.log('Protocol V2 device is already in normal mode, skip normal reboot');
|
|
2902
|
+
if (await this.probeProtocolV2NormalMode(deviceInfo)) {
|
|
2903
|
+
Log.log('[FirmwareUpdateV4] device already returned to App mode; skip Normal reboot');
|
|
1248
2904
|
return;
|
|
1249
2905
|
}
|
|
1250
2906
|
} catch (error) {
|
|
1251
|
-
Log.log('
|
|
2907
|
+
Log.log('[FirmwareUpdateV4] unable to confirm App mode before Normal reboot: ', error);
|
|
1252
2908
|
}
|
|
1253
2909
|
await this.protocolV2Reboot(DeviceRebootType.Normal);
|
|
2910
|
+
this.protocolV2ExecutionInLoader = false;
|
|
2911
|
+
}
|
|
2912
|
+
|
|
2913
|
+
private async probeProtocolV2NormalMode(deviceInfo: ProtocolV2DeviceInfo) {
|
|
2914
|
+
const features = await this.device.probeProtocolV2RuntimeState(
|
|
2915
|
+
deviceInfo,
|
|
2916
|
+
PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT
|
|
2917
|
+
);
|
|
2918
|
+
this.protocolV2LastRuntimeProbeFeatures = features;
|
|
2919
|
+
return features.mode === 'normal' && !features.bootloaderMode;
|
|
1254
2920
|
}
|
|
1255
2921
|
|
|
1256
2922
|
private async waitForProtocolV2FinalFeatures() {
|
|
1257
2923
|
const features = await this.waitForProtocolV2ReconnectAndFeatures(
|
|
1258
|
-
|
|
2924
|
+
PROTOCOL_V2_FINAL_RECONNECT_TIMEOUT
|
|
1259
2925
|
);
|
|
1260
2926
|
|
|
2927
|
+
return this.getProtocolV2VersionResult(features);
|
|
2928
|
+
}
|
|
2929
|
+
|
|
2930
|
+
private getProtocolV2VersionResult(features: Features) {
|
|
1261
2931
|
const bootloaderVersion = getDeviceBootloaderVersion(features).join('.');
|
|
1262
2932
|
const bleVersion = getDeviceBLEFirmwareVersion(features).join('.');
|
|
1263
2933
|
const firmwareVersion = getDeviceFirmwareVersion(features).join('.');
|
|
2934
|
+
this.protocolV2LatestFinalFeatures = features;
|
|
1264
2935
|
if (firmwareVersion === '0.0.0') {
|
|
1265
2936
|
Log.warn(
|
|
1266
2937
|
'Protocol V2 firmware update finished but app firmware version is still 0.0.0. This is allowed for Pro2 debug BLE-only update flows.'
|
|
@@ -1274,32 +2945,55 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1274
2945
|
};
|
|
1275
2946
|
}
|
|
1276
2947
|
|
|
2948
|
+
private async completeProtocolV2FinalVerification() {
|
|
2949
|
+
if (this.protocolV2ExecutionInLoader || this.isProtocolV2BootloaderMode()) {
|
|
2950
|
+
await this.exitProtocolV2BootloaderToNormal();
|
|
2951
|
+
}
|
|
2952
|
+
const versions = await this.waitForProtocolV2FinalFeatures();
|
|
2953
|
+
this.assertExpectedProtocolV2Versions();
|
|
2954
|
+
this.postTipMessage(FirmwareUpdateTipMessage.FirmwareUpdateCompleted);
|
|
2955
|
+
DevicePool.resetState();
|
|
2956
|
+
return versions;
|
|
2957
|
+
}
|
|
2958
|
+
|
|
1277
2959
|
private async waitForProtocolV2ReconnectAndFeatures(timeout: number) {
|
|
1278
2960
|
const startTime = Date.now();
|
|
1279
2961
|
let lastError: unknown;
|
|
2962
|
+
let shouldReconnect = true;
|
|
1280
2963
|
|
|
1281
2964
|
while (Date.now() - startTime < timeout) {
|
|
1282
2965
|
try {
|
|
1283
|
-
|
|
2966
|
+
if (shouldReconnect) {
|
|
2967
|
+
await this.reconnectProtocolV2Device();
|
|
2968
|
+
shouldReconnect = false;
|
|
2969
|
+
}
|
|
1284
2970
|
const deviceInfo = await requestProtocolV2DeviceInfo({
|
|
1285
2971
|
commands: this.device.getCommands(),
|
|
1286
|
-
timeoutMs:
|
|
1287
|
-
//
|
|
2972
|
+
timeoutMs: this.getProtocolV2DeviceInfoTimeout(),
|
|
2973
|
+
// Completion needs target versions only; keep scope aligned with the request.
|
|
1288
2974
|
request: PROTOCOL_V2_VERSIONS_DEVICE_INFO_REQUEST,
|
|
1289
2975
|
});
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
2976
|
+
this.assertProtocolV2DeviceInfoIdentity(deviceInfo);
|
|
2977
|
+
const features = await this.device.probeProtocolV2RuntimeState(
|
|
2978
|
+
deviceInfo,
|
|
2979
|
+
PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT
|
|
2980
|
+
);
|
|
2981
|
+
if (features.mode === 'normal' && !features.bootloaderMode) {
|
|
2982
|
+
return features;
|
|
1296
2983
|
}
|
|
1297
|
-
|
|
2984
|
+
lastError = ERRORS.TypedError(
|
|
2985
|
+
HardwareErrorCode.DeviceNotFound,
|
|
2986
|
+
'Protocol V2 device is still in bootloader mode'
|
|
2987
|
+
);
|
|
1298
2988
|
} catch (error) {
|
|
2989
|
+
if (this.isProtocolV2ReconnectIdentityError(error)) {
|
|
2990
|
+
throw error;
|
|
2991
|
+
}
|
|
2992
|
+
shouldReconnect = true;
|
|
1299
2993
|
lastError = error;
|
|
1300
2994
|
Log.log('Protocol V2 normal mode not ready, polling Ping: ', error);
|
|
1301
|
-
await wait(1000);
|
|
1302
2995
|
}
|
|
2996
|
+
await wait(1000);
|
|
1303
2997
|
}
|
|
1304
2998
|
|
|
1305
2999
|
throw ERRORS.TypedError(
|
|
@@ -1323,130 +3017,71 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1323
3017
|
DataManager.isBrowserWebUsb(DataManager.getSettings('env')) &&
|
|
1324
3018
|
devicesDescriptor.length === 1
|
|
1325
3019
|
) {
|
|
3020
|
+
const descriptor = devicesDescriptor[0];
|
|
3021
|
+
if (!this.protocolV2ExpectedSerialNumber && descriptor.path !== this.protocolV2ExpectedPath) {
|
|
3022
|
+
throw ERRORS.TypedError(HardwareErrorCode.DeviceNotFound);
|
|
3023
|
+
}
|
|
1326
3024
|
this.device.updateDescriptor(
|
|
1327
3025
|
{
|
|
1328
|
-
...
|
|
3026
|
+
...descriptor,
|
|
1329
3027
|
protocolType: PROTOCOL_V2_CONNECT_PROTOCOL,
|
|
1330
3028
|
},
|
|
1331
3029
|
true
|
|
1332
3030
|
);
|
|
1333
|
-
await this.
|
|
3031
|
+
await this.ensureProtocolV2DeviceAcquired();
|
|
1334
3032
|
this.device.commands.disposed = false;
|
|
1335
3033
|
this.device.getCommands().mainId = this.device.mainId ?? '';
|
|
1336
|
-
await this.device.initialize();
|
|
1337
3034
|
return;
|
|
1338
3035
|
}
|
|
1339
3036
|
|
|
1340
|
-
//
|
|
1341
|
-
//
|
|
3037
|
+
// Reinitialize every USB descriptor so DeviceInfo can provide the physical serial.
|
|
3038
|
+
// Enumeration order is not stable and must never decide the reconnect target.
|
|
1342
3039
|
const { deviceList } = await DevicePool.getDevices(devicesDescriptor, undefined, {
|
|
1343
3040
|
connectProtocol: PROTOCOL_V2_CONNECT_PROTOCOL,
|
|
1344
3041
|
});
|
|
1345
|
-
|
|
3042
|
+
const expectedSerialNumber = this.protocolV2ExpectedSerialNumber?.trim();
|
|
3043
|
+
const expectedPath = this.protocolV2ExpectedPath?.trim();
|
|
3044
|
+
const reconnectDevice = deviceList.find(candidate => {
|
|
3045
|
+
const candidateSerialNumber = candidate.getCurrentSerialNo?.().trim();
|
|
3046
|
+
if (expectedSerialNumber && candidateSerialNumber) {
|
|
3047
|
+
return candidateSerialNumber === expectedSerialNumber;
|
|
3048
|
+
}
|
|
3049
|
+
return !!expectedPath && candidate.getConnectId() === expectedPath;
|
|
3050
|
+
});
|
|
3051
|
+
if (!reconnectDevice) {
|
|
1346
3052
|
throw ERRORS.TypedError(HardwareErrorCode.DeviceNotFound);
|
|
1347
3053
|
}
|
|
1348
3054
|
|
|
1349
3055
|
Log.debug(
|
|
1350
|
-
'Protocol V2 firmware reconnect using
|
|
1351
|
-
|
|
3056
|
+
'Protocol V2 firmware reconnect using matched device:',
|
|
3057
|
+
reconnectDevice.getConnectId()
|
|
1352
3058
|
);
|
|
1353
|
-
this.device.updateFromCache(
|
|
1354
|
-
await this.
|
|
3059
|
+
this.device.updateFromCache(reconnectDevice);
|
|
3060
|
+
await this.ensureProtocolV2DeviceAcquired();
|
|
1355
3061
|
this.device.commands.disposed = false;
|
|
1356
3062
|
this.device.getCommands().mainId = this.device.mainId ?? '';
|
|
1357
|
-
await this.device.initialize();
|
|
1358
|
-
}
|
|
1359
|
-
|
|
1360
|
-
private async protocolV2CommonUpdateProcess(params: ProtocolV2FileTransferParams) {
|
|
1361
|
-
let lastError: unknown;
|
|
1362
|
-
for (let attempt = 1; attempt <= PROTOCOL_V2_FILE_TRANSFER_RETRY_COUNT; attempt += 1) {
|
|
1363
|
-
try {
|
|
1364
|
-
return await this.protocolV2WriteWholeFile(params);
|
|
1365
|
-
} catch (error) {
|
|
1366
|
-
lastError = error;
|
|
1367
|
-
Log.error(
|
|
1368
|
-
`Protocol V2 file transfer failed path=${params.filePath} attempt=${attempt}/${PROTOCOL_V2_FILE_TRANSFER_RETRY_COUNT}; restarting from offset 0`,
|
|
1369
|
-
error
|
|
1370
|
-
);
|
|
1371
|
-
if (attempt === PROTOCOL_V2_FILE_TRANSFER_RETRY_COUNT) {
|
|
1372
|
-
break;
|
|
1373
|
-
}
|
|
1374
|
-
await this.recoverProtocolV2FileTransfer();
|
|
1375
|
-
}
|
|
1376
|
-
}
|
|
1377
|
-
|
|
1378
|
-
throw ERRORS.TypedError(
|
|
1379
|
-
HardwareErrorCode.EmmcFileWriteFirmwareError,
|
|
1380
|
-
`transfer data error: ${getProtocolV2UnknownErrorText(lastError)}`
|
|
1381
|
-
);
|
|
1382
3063
|
}
|
|
1383
3064
|
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
let offset = 0;
|
|
1393
|
-
const getUploadProgress = (fileOffset: number) => {
|
|
1394
|
-
if (totalSize !== undefined && processedSize !== undefined) {
|
|
1395
|
-
return Math.min(Math.ceil(((processedSize + fileOffset) / totalSize) * 100), 99);
|
|
1396
|
-
}
|
|
1397
|
-
return Math.min(Math.ceil((fileOffset / payload.byteLength) * 100), 99);
|
|
1398
|
-
};
|
|
1399
|
-
|
|
1400
|
-
while (offset < payload.byteLength) {
|
|
1401
|
-
const chunkEnd = Math.min(offset + chunkSize, payload.byteLength);
|
|
1402
|
-
const chunkLength = chunkEnd - offset;
|
|
1403
|
-
const chunk = payload.slice(offset, chunkEnd);
|
|
1404
|
-
const overwrite = offset === 0;
|
|
1405
|
-
const progress = getProtocolV2DeviceTransferProgress(
|
|
1406
|
-
(processedSize ?? 0) + offset,
|
|
1407
|
-
(processedSize ?? 0) + chunkEnd,
|
|
1408
|
-
totalSize ?? payload.byteLength
|
|
1409
|
-
);
|
|
1410
|
-
|
|
1411
|
-
const writeRes = await this.fileWriteChunk(
|
|
1412
|
-
filePath,
|
|
1413
|
-
payload.byteLength,
|
|
1414
|
-
offset,
|
|
1415
|
-
chunk,
|
|
1416
|
-
overwrite,
|
|
1417
|
-
progress
|
|
1418
|
-
);
|
|
1419
|
-
const processedByte = Number(writeRes.message.processed_byte);
|
|
1420
|
-
const nextOffset =
|
|
1421
|
-
Number.isFinite(processedByte) && processedByte > offset
|
|
1422
|
-
? processedByte
|
|
1423
|
-
: offset + chunkLength;
|
|
1424
|
-
if (nextOffset <= offset || nextOffset > payload.byteLength) {
|
|
1425
|
-
throw ERRORS.TypedError(
|
|
1426
|
-
HardwareErrorCode.EmmcFileWriteFirmwareError,
|
|
1427
|
-
`invalid processed_byte ${writeRes.message.processed_byte} for offset ${offset}`
|
|
1428
|
-
);
|
|
1429
|
-
}
|
|
1430
|
-
offset = nextOffset;
|
|
1431
|
-
onTransferredBytes?.((processedSize ?? 0) + offset);
|
|
1432
|
-
this.postProgressMessage(getUploadProgress(offset), 'transferData');
|
|
3065
|
+
/**
|
|
3066
|
+
* After acquiring a USB session, polling reuses its command channel. Reacquire only
|
|
3067
|
+
* after reboot or enumeration changes cause hasDeviceAcquire() to become false.
|
|
3068
|
+
*/
|
|
3069
|
+
private async ensureProtocolV2DeviceAcquired() {
|
|
3070
|
+
const commands = this.device.getCommands();
|
|
3071
|
+
if (this.device.hasDeviceAcquire() && !commands.disposed) {
|
|
3072
|
+
return;
|
|
1433
3073
|
}
|
|
1434
|
-
|
|
1435
|
-
|
|
3074
|
+
await this.device.acquire(PROTOCOL_V2_CONNECT_PROTOCOL, {
|
|
3075
|
+
throwOnRunPromiseError: true,
|
|
3076
|
+
});
|
|
1436
3077
|
}
|
|
1437
3078
|
|
|
1438
|
-
private
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
env &&
|
|
1445
|
-
(DataManager.isBrowserWebUsb(env) || DataManager.isDesktopWebUsb(env) || env === 'web')
|
|
1446
|
-
) {
|
|
1447
|
-
return 'WebUSB';
|
|
1448
|
-
}
|
|
1449
|
-
return env ?? 'unknown';
|
|
3079
|
+
private isProtocolV2ReconnectIdentityError(error: unknown) {
|
|
3080
|
+
// A serial can be temporarily unavailable while Loader starts, and BLE has no stable path.
|
|
3081
|
+
// Keep polling unless the device explicitly reports a different serial.
|
|
3082
|
+
return this.normalizeErrorMessage(error).includes(
|
|
3083
|
+
'Protocol V2 reconnect physical identity mismatch'
|
|
3084
|
+
);
|
|
1450
3085
|
}
|
|
1451
3086
|
|
|
1452
3087
|
private async fileWriteChunk(
|
|
@@ -1458,17 +3093,25 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1458
3093
|
progress: number | null
|
|
1459
3094
|
): Promise<TypedResponseMessage<'FilesystemFile'>> {
|
|
1460
3095
|
const typedCall = this.device.getCommands().typedCall.bind(this.device.getCommands());
|
|
1461
|
-
const writeRes = await typedCall(
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
3096
|
+
const writeRes = await typedCall(
|
|
3097
|
+
'FilesystemFileWrite',
|
|
3098
|
+
'FilesystemFile',
|
|
3099
|
+
{
|
|
3100
|
+
file: {
|
|
3101
|
+
path: filePath,
|
|
3102
|
+
offset,
|
|
3103
|
+
total_size: totalFileSize,
|
|
3104
|
+
data: chunk,
|
|
3105
|
+
},
|
|
3106
|
+
overwrite,
|
|
3107
|
+
append: false,
|
|
3108
|
+
ui_percentage: progress ?? undefined,
|
|
1467
3109
|
},
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
3110
|
+
{
|
|
3111
|
+
writeWithResponse: false,
|
|
3112
|
+
onWriteCompleted: () => undefined,
|
|
3113
|
+
}
|
|
3114
|
+
);
|
|
1472
3115
|
if (writeRes.type !== 'FilesystemFile') {
|
|
1473
3116
|
if ((writeRes as any).type === 'CallMethodError') {
|
|
1474
3117
|
if (((writeRes as any).message.error ?? '').indexOf(SESSION_ERROR) > -1) {
|
|
@@ -1484,9 +3127,10 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1484
3127
|
const env = DataManager.getSettings('env');
|
|
1485
3128
|
if (DataManager.isBleConnect(env)) {
|
|
1486
3129
|
await wait(3000);
|
|
1487
|
-
await this.acquireProtocolV2BleDevice();
|
|
1488
|
-
await this.device.initialize();
|
|
1489
3130
|
}
|
|
3131
|
+
await this.reconnectProtocolV2Device();
|
|
3132
|
+
await this.verifyProtocolV2ReconnectIdentity();
|
|
3133
|
+
await this.device.initialize();
|
|
1490
3134
|
await wait(2000);
|
|
1491
3135
|
}
|
|
1492
3136
|
|
|
@@ -1504,36 +3148,64 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1504
3148
|
}: {
|
|
1505
3149
|
targets: Array<{ target_id: number; path: string }>;
|
|
1506
3150
|
}) {
|
|
1507
|
-
|
|
1508
|
-
|
|
3151
|
+
this.protocolV2InstallAckReceived = false;
|
|
3152
|
+
this.protocolV2LastRuntimeProbeFeatures = undefined;
|
|
3153
|
+
const startUpdate = () =>
|
|
3154
|
+
this.device
|
|
3155
|
+
.getCommands()
|
|
3156
|
+
.typedCall(
|
|
3157
|
+
'DeviceFirmwareUpdateRequest',
|
|
3158
|
+
'Success',
|
|
3159
|
+
{ targets },
|
|
3160
|
+
{ timeoutMs: PROTOCOL_V2_START_UPDATE_TIMEOUT }
|
|
3161
|
+
);
|
|
3162
|
+
let response: ProtocolV2FirmwareUpdateStartResponse | undefined;
|
|
1509
3163
|
try {
|
|
1510
|
-
response = await
|
|
1511
|
-
|
|
1512
|
-
PROTOCOL_V2_FIRMWARE_UPDATE_RESPONSE_TYPES,
|
|
1513
|
-
{
|
|
1514
|
-
targets,
|
|
1515
|
-
},
|
|
1516
|
-
{
|
|
1517
|
-
intermediateTypes: ['DeviceFirmwareUpdateStatus'],
|
|
1518
|
-
timeoutMs: PROTOCOL_V2_START_UPDATE_TIMEOUT,
|
|
1519
|
-
onIntermediateResponse: (response: { type?: string }) => {
|
|
1520
|
-
if (response.type === 'DeviceFirmwareUpdateStatus') {
|
|
1521
|
-
this.postProgressMessage(99, 'installingFirmware');
|
|
1522
|
-
}
|
|
1523
|
-
},
|
|
1524
|
-
}
|
|
1525
|
-
);
|
|
3164
|
+
response = await startUpdate();
|
|
3165
|
+
this.protocolV2InstallAckReceived = true;
|
|
1526
3166
|
} catch (error) {
|
|
1527
|
-
if (
|
|
3167
|
+
if (this.isBleReconnect() && isProtocolV2BleInstallInterruptionError(error)) {
|
|
3168
|
+
this.throwIfAborted();
|
|
3169
|
+
// Installation can reboot the device before the Success response reaches
|
|
3170
|
+
// the host. The request has side effects, so do not replay it; reconnect
|
|
3171
|
+
// and let status polling determine whether installation was accepted.
|
|
1528
3172
|
Log.log(
|
|
1529
|
-
'
|
|
3173
|
+
'[FirmwareUpdateV4] install request interrupted by device reboot; continue status polling: ',
|
|
1530
3174
|
error
|
|
1531
3175
|
);
|
|
3176
|
+
} else if (
|
|
3177
|
+
this.protocolV2LegacyDirectUpdate &&
|
|
3178
|
+
isProtocolV2FirmwareUpdateEndpointUnavailable(error)
|
|
3179
|
+
) {
|
|
3180
|
+
// Both legacy loaders register this request. A missing handler therefore identifies
|
|
3181
|
+
// a legacy App before dispatch, so it is safe to reboot and retry the unhandled request.
|
|
3182
|
+
this.protocolV2LegacyDirectUpdate = false;
|
|
3183
|
+
Log.debug(
|
|
3184
|
+
'[FirmwareUpdateV4] legacy App does not expose DeviceFirmwareUpdateRequest; rebooting to bootloader'
|
|
3185
|
+
);
|
|
3186
|
+
await this.rebootProtocolV2ToBootloader();
|
|
3187
|
+
try {
|
|
3188
|
+
response = await startUpdate();
|
|
3189
|
+
this.protocolV2InstallAckReceived = true;
|
|
3190
|
+
} catch (retryError) {
|
|
3191
|
+
if (!(this.isBleReconnect() && isProtocolV2BleInstallInterruptionError(retryError))) {
|
|
3192
|
+
throw retryError;
|
|
3193
|
+
}
|
|
3194
|
+
this.throwIfAborted();
|
|
3195
|
+
Log.log(
|
|
3196
|
+
'[FirmwareUpdateV4] install request interrupted after legacy reboot; continue status polling: ',
|
|
3197
|
+
retryError
|
|
3198
|
+
);
|
|
3199
|
+
}
|
|
1532
3200
|
} else {
|
|
1533
3201
|
throw error;
|
|
1534
3202
|
}
|
|
1535
3203
|
}
|
|
3204
|
+
this.protocolV2LegacyDirectUpdate = false;
|
|
3205
|
+
// A Success ACK or an install-time disconnect both end confirmation. In the
|
|
3206
|
+
// latter case only status polling may establish the final outcome.
|
|
1536
3207
|
this.postTipMessage(FirmwareUpdateTipMessage.FirmwareUpdating);
|
|
3208
|
+
this.postProgressMessage(0, 'installingFirmware');
|
|
1537
3209
|
return response;
|
|
1538
3210
|
}
|
|
1539
3211
|
|
|
@@ -1543,9 +3215,11 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1543
3215
|
const res = await typedCall('DeviceReboot', 'Success', {
|
|
1544
3216
|
reboot_type: rebootType,
|
|
1545
3217
|
});
|
|
3218
|
+
this.device.markProtocolV2Reboot(rebootType);
|
|
1546
3219
|
return res.message;
|
|
1547
3220
|
} catch (error) {
|
|
1548
3221
|
if (isProtocolV2DeviceDisconnectedError(error) || isProtocolV2ReconnectProbeError(error)) {
|
|
3222
|
+
this.device.markProtocolV2Reboot(rebootType);
|
|
1549
3223
|
return { message: 'Device rebooted successfully' };
|
|
1550
3224
|
}
|
|
1551
3225
|
throw error;
|