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