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