| /* |
| * |
| * Copyright (c) 2021 Project CHIP Authors |
| * All rights reserved. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| /* This file contains the implementation of the OTARequestorInterface class. All the core |
| * OTA Requestor logic is contained in this class. |
| */ |
| |
| #include <app/clusters/basic-information/basic-information.h> |
| #include <app/clusters/ota-requestor/ota-requestor-server.h> |
| #include <lib/core/CHIPEncoding.h> |
| #include <platform/CHIPDeviceLayer.h> |
| #include <platform/DeviceInstanceInfoProvider.h> |
| #include <platform/OTAImageProcessor.h> |
| #include <protocols/bdx/BdxUri.h> |
| #include <zap-generated/CHIPClusters.h> |
| |
| #include "BDXDownloader.h" |
| #include "DefaultOTARequestor.h" |
| |
| namespace chip { |
| |
| using namespace app; |
| using namespace app::Clusters; |
| using namespace app::Clusters::OtaSoftwareUpdateProvider; |
| using namespace app::Clusters::OtaSoftwareUpdateProvider::Commands; |
| using namespace app::Clusters::OtaSoftwareUpdateRequestor; |
| using namespace app::Clusters::OtaSoftwareUpdateRequestor::Commands; |
| using namespace app::Clusters::OtaSoftwareUpdateRequestor::Structs; |
| using app::DataModel::Nullable; |
| using bdx::TransferSession; |
| using Protocols::InteractionModel::Status; |
| |
| // Global instance of the OTARequestorInterface. |
| OTARequestorInterface * globalOTARequestorInstance = nullptr; |
| |
| // Abort the QueryImage download request if there's been no progress for 5 minutes |
| static constexpr System::Clock::Timeout kDownloadTimeoutSec = chip::System::Clock::Seconds32(5 * 60); |
| |
| static void LogQueryImageResponse(const QueryImageResponse::DecodableType & response) |
| { |
| ChipLogDetail(SoftwareUpdate, "QueryImageResponse:"); |
| ChipLogDetail(SoftwareUpdate, " status: %u", to_underlying(response.status)); |
| if (response.delayedActionTime.HasValue()) |
| { |
| ChipLogDetail(SoftwareUpdate, " delayedActionTime: %" PRIu32 " seconds", response.delayedActionTime.Value()); |
| } |
| if (response.imageURI.HasValue()) |
| { |
| ChipLogDetail(SoftwareUpdate, " imageURI: %.*s", static_cast<int>(response.imageURI.Value().size()), |
| response.imageURI.Value().data()); |
| } |
| if (response.softwareVersion.HasValue()) |
| { |
| ChipLogDetail(SoftwareUpdate, " softwareVersion: %" PRIu32 "", response.softwareVersion.Value()); |
| } |
| if (response.softwareVersionString.HasValue()) |
| { |
| ChipLogDetail(SoftwareUpdate, " softwareVersionString: %.*s", |
| static_cast<int>(response.softwareVersionString.Value().size()), |
| response.softwareVersionString.Value().data()); |
| } |
| if (response.updateToken.HasValue()) |
| { |
| ChipLogDetail(SoftwareUpdate, " updateToken: %u", static_cast<unsigned int>(response.updateToken.Value().size())); |
| } |
| if (response.userConsentNeeded.HasValue()) |
| { |
| ChipLogDetail(SoftwareUpdate, " userConsentNeeded: %d", response.userConsentNeeded.Value()); |
| } |
| if (response.metadataForRequestor.HasValue()) |
| { |
| ChipLogDetail(SoftwareUpdate, " metadataForRequestor: %u", |
| static_cast<unsigned int>(response.metadataForRequestor.Value().size())); |
| } |
| } |
| |
| static void LogApplyUpdateResponse(const ApplyUpdateResponse::DecodableType & response) |
| { |
| ChipLogDetail(SoftwareUpdate, "ApplyUpdateResponse:"); |
| ChipLogDetail(SoftwareUpdate, " action: %u", to_underlying(response.action)); |
| ChipLogDetail(SoftwareUpdate, " delayedActionTime: %" PRIu32 " seconds", response.delayedActionTime); |
| } |
| |
| void SetRequestorInstance(OTARequestorInterface * instance) |
| { |
| globalOTARequestorInstance = instance; |
| } |
| |
| OTARequestorInterface * GetRequestorInstance() |
| { |
| return globalOTARequestorInstance; |
| } |
| |
| void DefaultOTARequestor::InitState(intptr_t context) |
| { |
| DefaultOTARequestor * requestorCore = reinterpret_cast<DefaultOTARequestor *>(context); |
| VerifyOrDie(requestorCore != nullptr); |
| |
| // This initialization may occur due to the following: |
| // 1) Regular boot up - the states should already be correct |
| // 2) Reboot from applying an image - once the image has been confirmed, the provider will be notified of the new version and |
| // all relevant states will reset for a new OTA update. If the image cannot be confirmed, the driver will be responsible for |
| // resetting the states appropriately, including the current update state. |
| OtaRequestorServerSetUpdateState(requestorCore->mCurrentUpdateState); |
| OtaRequestorServerSetUpdateStateProgress(app::DataModel::NullNullable); |
| } |
| |
| CHIP_ERROR DefaultOTARequestor::Init(Server & server, OTARequestorStorage & storage, OTARequestorDriver & driver, |
| BDXDownloader & downloader) |
| { |
| mServer = &server; |
| mCASESessionManager = server.GetCASESessionManager(); |
| mStorage = &storage; |
| mOtaRequestorDriver = &driver; |
| mBdxDownloader = &downloader; |
| |
| ReturnErrorOnFailure(DeviceLayer::ConfigurationMgr().GetSoftwareVersion(mCurrentVersion)); |
| |
| // Load data from KVS |
| LoadCurrentUpdateInfo(); |
| |
| // Schedule the initializations that needs to be performed in the CHIP context |
| DeviceLayer::PlatformMgr().ScheduleWork(InitState, reinterpret_cast<intptr_t>(this)); |
| |
| return chip::DeviceLayer::PlatformMgrImpl().AddEventHandler(OnCommissioningCompleteRequestor, reinterpret_cast<intptr_t>(this)); |
| } |
| |
| void DefaultOTARequestor::OnQueryImageResponse(void * context, const QueryImageResponse::DecodableType & response) |
| { |
| LogQueryImageResponse(response); |
| |
| DefaultOTARequestor * requestorCore = static_cast<DefaultOTARequestor *>(context); |
| VerifyOrDie(requestorCore != nullptr); |
| |
| switch (response.status) |
| { |
| case OTAQueryStatus::kUpdateAvailable: { |
| UpdateDescription update; |
| CHIP_ERROR err = requestorCore->ExtractUpdateDescription(response, update); |
| |
| if (err != CHIP_NO_ERROR) |
| { |
| ChipLogError(SoftwareUpdate, "QueryImageResponse contains invalid fields: %" CHIP_ERROR_FORMAT, err.Format()); |
| requestorCore->RecordErrorUpdateState(err); |
| return; |
| } |
| |
| // This should never happen since receiving a response implies that a CASE session had previously been established with a |
| // valid provider |
| if (!requestorCore->mProviderLocation.HasValue()) |
| { |
| ChipLogError(SoftwareUpdate, "No provider location set"); |
| requestorCore->RecordErrorUpdateState(CHIP_ERROR_INCORRECT_STATE); |
| return; |
| } |
| |
| // The Operational Node ID in the host field SHALL match the NodeID of the OTA Provider responding with the |
| // QueryImageResponse |
| if (update.nodeId != requestorCore->mProviderLocation.Value().providerNodeID) |
| { |
| ChipLogError(SoftwareUpdate, |
| "The ImageURI provider node 0x" ChipLogFormatX64 |
| " does not match the QueryImageResponse provider node 0x" ChipLogFormatX64, |
| ChipLogValueX64(update.nodeId), ChipLogValueX64(requestorCore->mProviderLocation.Value().providerNodeID)); |
| requestorCore->RecordErrorUpdateState(CHIP_ERROR_WRONG_NODE_ID); |
| return; |
| } |
| |
| if (update.softwareVersion > requestorCore->mCurrentVersion) |
| { |
| ChipLogDetail(SoftwareUpdate, "Update available from version %" PRIu32 " to %" PRIu32, requestorCore->mCurrentVersion, |
| update.softwareVersion); |
| MutableByteSpan updateToken(requestorCore->mUpdateTokenBuffer); |
| // This function copies the bytespan to mutablebytespan only if size of mutablebytespan buffer is greater or equal to |
| // bytespan otherwise we are copying data upto available size. |
| err = CopySpanToMutableSpan(update.updateToken, updateToken); |
| if (err == CHIP_ERROR_BUFFER_TOO_SMALL) |
| { |
| memset(updateToken.data(), 0, updateToken.size()); |
| requestorCore->GenerateUpdateToken(); |
| } |
| requestorCore->mTargetVersion = update.softwareVersion; |
| requestorCore->mUpdateToken = updateToken; |
| |
| // Store file designator needed for BDX transfers |
| MutableCharSpan fileDesignator(requestorCore->mFileDesignatorBuffer); |
| if (update.fileDesignator.size() > fileDesignator.size()) |
| { |
| ChipLogError(SoftwareUpdate, "File designator size %u is too large to store", |
| static_cast<unsigned int>(update.fileDesignator.size())); |
| requestorCore->RecordErrorUpdateState(CHIP_ERROR_BUFFER_TOO_SMALL); |
| return; |
| } |
| memcpy(fileDesignator.data(), update.fileDesignator.data(), update.fileDesignator.size()); |
| fileDesignator.reduce_size(update.fileDesignator.size()); |
| requestorCore->mFileDesignator = fileDesignator; |
| |
| requestorCore->mOtaRequestorDriver->UpdateAvailable(update, |
| System::Clock::Seconds32(response.delayedActionTime.ValueOr(0))); |
| } |
| else |
| { |
| ChipLogDetail(SoftwareUpdate, "Available update version %" PRIu32 " is <= current version %" PRIu32 ", update ignored", |
| update.softwareVersion, requestorCore->mCurrentVersion); |
| |
| requestorCore->mOtaRequestorDriver->UpdateNotFound(UpdateNotFoundReason::kUpToDate, |
| System::Clock::Seconds32(response.delayedActionTime.ValueOr(0))); |
| requestorCore->RecordNewUpdateState(OTAUpdateStateEnum::kIdle, OTAChangeReasonEnum::kSuccess); |
| } |
| |
| break; |
| } |
| case OTAQueryStatus::kBusy: { |
| CHIP_ERROR status = requestorCore->mOtaRequestorDriver->UpdateNotFound( |
| UpdateNotFoundReason::kBusy, System::Clock::Seconds32(response.delayedActionTime.ValueOr(0))); |
| if ((status == CHIP_ERROR_MAX_RETRY_EXCEEDED) || (status == CHIP_ERROR_PROVIDER_LIST_EXHAUSTED)) |
| { |
| requestorCore->RecordNewUpdateState(OTAUpdateStateEnum::kIdle, OTAChangeReasonEnum::kSuccess); |
| } |
| else |
| { |
| requestorCore->RecordNewUpdateState(OTAUpdateStateEnum::kDelayedOnQuery, OTAChangeReasonEnum::kDelayByProvider); |
| } |
| |
| break; |
| } |
| case OTAQueryStatus::kNotAvailable: { |
| requestorCore->mOtaRequestorDriver->UpdateNotFound(UpdateNotFoundReason::kNotAvailable, |
| System::Clock::Seconds32(response.delayedActionTime.ValueOr(0))); |
| requestorCore->RecordNewUpdateState(OTAUpdateStateEnum::kIdle, OTAChangeReasonEnum::kSuccess); |
| break; |
| } |
| default: |
| requestorCore->RecordErrorUpdateState(CHIP_ERROR_BAD_REQUEST); |
| break; |
| } |
| } |
| |
| void DefaultOTARequestor::OnQueryImageFailure(void * context, CHIP_ERROR error) |
| { |
| DefaultOTARequestor * requestorCore = static_cast<DefaultOTARequestor *>(context); |
| VerifyOrDie(requestorCore != nullptr); |
| |
| ChipLogError(SoftwareUpdate, "Received QueryImage failure response: %" CHIP_ERROR_FORMAT, error.Format()); |
| |
| // A previously valid CASE session may have become invalid |
| if (error == CHIP_ERROR_TIMEOUT) |
| { |
| ChipLogError(SoftwareUpdate, "CASE session may be invalid, tear down session"); |
| requestorCore->DisconnectFromProvider(); |
| error = CHIP_ERROR_CONNECTION_CLOSED_UNEXPECTEDLY; |
| } |
| |
| requestorCore->RecordErrorUpdateState(error); |
| } |
| |
| void DefaultOTARequestor::OnApplyUpdateResponse(void * context, const ApplyUpdateResponse::DecodableType & response) |
| { |
| LogApplyUpdateResponse(response); |
| |
| DefaultOTARequestor * requestorCore = static_cast<DefaultOTARequestor *>(context); |
| VerifyOrDie(requestorCore != nullptr); |
| |
| switch (response.action) |
| { |
| case OTAApplyUpdateAction::kProceed: |
| requestorCore->mOtaRequestorDriver->UpdateConfirmed(System::Clock::Seconds32(response.delayedActionTime)); |
| break; |
| case OTAApplyUpdateAction::kAwaitNextAction: |
| requestorCore->mOtaRequestorDriver->UpdateSuspended(System::Clock::Seconds32(response.delayedActionTime)); |
| requestorCore->RecordNewUpdateState(OTAUpdateStateEnum::kDelayedOnApply, OTAChangeReasonEnum::kDelayByProvider); |
| break; |
| case OTAApplyUpdateAction::kDiscontinue: |
| requestorCore->mOtaRequestorDriver->UpdateDiscontinued(); |
| requestorCore->RecordNewUpdateState(OTAUpdateStateEnum::kIdle, OTAChangeReasonEnum::kSuccess); |
| break; |
| case OTAApplyUpdateAction::kUnknownEnumValue: |
| OnApplyUpdateFailure(context, CHIP_ERROR_INVALID_ARGUMENT); |
| break; |
| } |
| } |
| |
| void DefaultOTARequestor::OnApplyUpdateFailure(void * context, CHIP_ERROR error) |
| { |
| DefaultOTARequestor * requestorCore = static_cast<DefaultOTARequestor *>(context); |
| VerifyOrDie(requestorCore != nullptr); |
| |
| ChipLogDetail(SoftwareUpdate, "ApplyUpdate failure response %" CHIP_ERROR_FORMAT, error.Format()); |
| requestorCore->RecordErrorUpdateState(error); |
| } |
| |
| void DefaultOTARequestor::OnNotifyUpdateAppliedResponse(void * context, const app::DataModel::NullObjectType & response) {} |
| |
| void DefaultOTARequestor::OnNotifyUpdateAppliedFailure(void * context, CHIP_ERROR error) |
| { |
| DefaultOTARequestor * requestorCore = static_cast<DefaultOTARequestor *>(context); |
| VerifyOrDie(requestorCore != nullptr); |
| |
| ChipLogDetail(SoftwareUpdate, "NotifyUpdateApplied failure response %" CHIP_ERROR_FORMAT, error.Format()); |
| requestorCore->RecordErrorUpdateState(error); |
| } |
| |
| void DefaultOTARequestor::Reset() |
| { |
| mProviderLocation.ClearValue(); |
| mUpdateToken.reduce_size(0); |
| RecordNewUpdateState(OTAUpdateStateEnum::kIdle, OTAChangeReasonEnum::kSuccess); |
| mTargetVersion = 0; |
| |
| // Persist in case of a reboot or crash |
| StoreCurrentUpdateInfo(); |
| } |
| |
| void DefaultOTARequestor::HandleAnnounceOTAProvider(app::CommandHandler * commandObj, const app::ConcreteCommandPath & commandPath, |
| const AnnounceOTAProvider::DecodableType & commandData) |
| { |
| VerifyOrReturn(commandObj != nullptr, ChipLogError(SoftwareUpdate, "Invalid commandObj, cannot handle AnnounceOTAProvider")); |
| |
| auto & announcementReason = commandData.announcementReason; |
| |
| ChipLogProgress(SoftwareUpdate, "OTA Requestor received AnnounceOTAProvider"); |
| |
| ProviderLocationType providerLocation = { .providerNodeID = commandData.providerNodeID, |
| .endpoint = commandData.endpoint, |
| .fabricIndex = commandObj->GetAccessingFabricIndex() }; |
| |
| ChipLogDetail(SoftwareUpdate, " FabricIndex: %u", providerLocation.fabricIndex); |
| ChipLogDetail(SoftwareUpdate, " ProviderNodeID: 0x" ChipLogFormatX64, ChipLogValueX64(providerLocation.providerNodeID)); |
| ChipLogDetail(SoftwareUpdate, " VendorID: 0x%x", commandData.vendorID); |
| ChipLogDetail(SoftwareUpdate, " AnnouncementReason: %u", to_underlying(announcementReason)); |
| if (commandData.metadataForNode.HasValue()) |
| { |
| ChipLogDetail(SoftwareUpdate, " MetadataForNode: %u", |
| static_cast<unsigned int>(commandData.metadataForNode.Value().size())); |
| } |
| ChipLogDetail(SoftwareUpdate, " Endpoint: %u", providerLocation.endpoint); |
| |
| mOtaRequestorDriver->ProcessAnnounceOTAProviders(providerLocation, announcementReason); |
| |
| commandObj->AddStatus(commandPath, Status::Success); |
| } |
| |
| void DefaultOTARequestor::ConnectToProvider(OnConnectedAction onConnectedAction) |
| { |
| VerifyOrDie(mServer != nullptr); |
| |
| if (!mProviderLocation.HasValue()) |
| { |
| ChipLogError(SoftwareUpdate, "Provider location not set"); |
| RecordErrorUpdateState(CHIP_ERROR_INCORRECT_STATE); |
| return; |
| } |
| |
| // Set the action to take once connection is successfully established |
| mOnConnectedAction = onConnectedAction; |
| |
| ChipLogDetail(SoftwareUpdate, "Establishing session to provider node ID 0x" ChipLogFormatX64 " on fabric index %d", |
| ChipLogValueX64(mProviderLocation.Value().providerNodeID), mProviderLocation.Value().fabricIndex); |
| |
| mCASESessionManager->FindOrEstablishSession(GetProviderScopedId(), &mOnConnectedCallback, &mOnConnectionFailureCallback); |
| } |
| |
| void DefaultOTARequestor::DisconnectFromProvider() |
| { |
| VerifyOrDie(mServer != nullptr); |
| |
| if (!mProviderLocation.HasValue()) |
| { |
| ChipLogError(SoftwareUpdate, "Provider location not set"); |
| RecordErrorUpdateState(CHIP_ERROR_INCORRECT_STATE); |
| return; |
| } |
| |
| auto optionalSessionHandle = mSessionHolder.Get(); |
| if (optionalSessionHandle.HasValue()) |
| { |
| if (optionalSessionHandle.Value()->IsActiveSession()) |
| { |
| optionalSessionHandle.Value()->AsSecureSession()->MarkAsDefunct(); |
| } |
| } |
| mSessionHolder.Release(); |
| } |
| |
| // Requestor is directed to cancel image update in progress. All the Requestor state is |
| // cleared, UpdateState is reset to Idle |
| void DefaultOTARequestor::CancelImageUpdate() |
| { |
| mBdxDownloader->EndDownload(CHIP_ERROR_CONNECTION_ABORTED); |
| |
| mOtaRequestorDriver->UpdateCancelled(); |
| |
| Reset(); |
| } |
| |
| CHIP_ERROR DefaultOTARequestor::GetUpdateStateProgressAttribute(EndpointId endpointId, app::DataModel::Nullable<uint8_t> & progress) |
| { |
| VerifyOrReturnError(OtaRequestorServerGetUpdateStateProgress(endpointId, progress) == EMBER_ZCL_STATUS_SUCCESS, |
| CHIP_ERROR_BAD_REQUEST); |
| return CHIP_NO_ERROR; |
| } |
| |
| CHIP_ERROR DefaultOTARequestor::GetUpdateStateAttribute(EndpointId endpointId, OTAUpdateStateEnum & state) |
| { |
| VerifyOrReturnError(OtaRequestorServerGetUpdateState(endpointId, state) == EMBER_ZCL_STATUS_SUCCESS, CHIP_ERROR_BAD_REQUEST); |
| return CHIP_NO_ERROR; |
| } |
| |
| // Called whenever FindOrEstablishSession is successful |
| void DefaultOTARequestor::OnConnected(void * context, Messaging::ExchangeManager & exchangeMgr, const SessionHandle & sessionHandle) |
| { |
| DefaultOTARequestor * requestorCore = static_cast<DefaultOTARequestor *>(context); |
| VerifyOrDie(requestorCore != nullptr); |
| requestorCore->mSessionHolder.Grab(sessionHandle); |
| |
| switch (requestorCore->mOnConnectedAction) |
| { |
| case kQueryImage: { |
| CHIP_ERROR err = requestorCore->SendQueryImageRequest(exchangeMgr, sessionHandle); |
| |
| if (err != CHIP_NO_ERROR) |
| { |
| ChipLogError(SoftwareUpdate, "Failed to send QueryImage command: %" CHIP_ERROR_FORMAT, err.Format()); |
| requestorCore->RecordErrorUpdateState(err); |
| return; |
| } |
| break; |
| } |
| case kDownload: { |
| CHIP_ERROR err = requestorCore->StartDownload(exchangeMgr, sessionHandle); |
| |
| if (err != CHIP_NO_ERROR) |
| { |
| ChipLogError(SoftwareUpdate, "Failed to start download: %" CHIP_ERROR_FORMAT, err.Format()); |
| requestorCore->RecordErrorUpdateState(err); |
| return; |
| } |
| break; |
| } |
| case kApplyUpdate: { |
| CHIP_ERROR err = requestorCore->SendApplyUpdateRequest(exchangeMgr, sessionHandle); |
| |
| if (err != CHIP_NO_ERROR) |
| { |
| ChipLogError(SoftwareUpdate, "Failed to send ApplyUpdate command: %" CHIP_ERROR_FORMAT, err.Format()); |
| requestorCore->RecordErrorUpdateState(err); |
| return; |
| } |
| break; |
| } |
| case kNotifyUpdateApplied: { |
| CHIP_ERROR err = requestorCore->SendNotifyUpdateAppliedRequest(exchangeMgr, sessionHandle); |
| |
| if (err != CHIP_NO_ERROR) |
| { |
| ChipLogError(SoftwareUpdate, "Failed to send NotifyUpdateApplied command: %" CHIP_ERROR_FORMAT, err.Format()); |
| requestorCore->RecordErrorUpdateState(err); |
| return; |
| } |
| break; |
| } |
| default: |
| break; |
| } |
| } |
| |
| // Called whenever FindOrEstablishSession fails |
| void DefaultOTARequestor::OnConnectionFailure(void * context, const ScopedNodeId & peerId, CHIP_ERROR error) |
| { |
| DefaultOTARequestor * requestorCore = static_cast<DefaultOTARequestor *>(context); |
| VerifyOrDie(requestorCore != nullptr); |
| requestorCore->mSessionHolder.Release(); |
| |
| ChipLogError(SoftwareUpdate, "Failed to connect to node 0x" ChipLogFormatX64 ": %" CHIP_ERROR_FORMAT, |
| ChipLogValueX64(peerId.GetNodeId()), error.Format()); |
| |
| switch (requestorCore->mOnConnectedAction) |
| { |
| case kQueryImage: |
| case kDownload: |
| case kApplyUpdate: |
| case kNotifyUpdateApplied: |
| requestorCore->RecordErrorUpdateState(error); |
| break; |
| default: |
| break; |
| } |
| } |
| |
| void DefaultOTARequestor::TriggerImmediateQueryInternal() |
| { |
| // We are now connecting to a provider for the purpose of sending a QueryImage, |
| // treat this as a move to the Querying state |
| RecordNewUpdateState(OTAUpdateStateEnum::kQuerying, OTAChangeReasonEnum::kSuccess); |
| |
| ConnectToProvider(kQueryImage); |
| } |
| |
| CHIP_ERROR DefaultOTARequestor::TriggerImmediateQuery(FabricIndex fabricIndex) |
| { |
| ProviderLocationType providerLocation; |
| bool providerFound = false; |
| |
| if (fabricIndex == kUndefinedFabricIndex) |
| { |
| bool listExhausted = false; |
| providerFound = mOtaRequestorDriver->GetNextProviderLocation(providerLocation, listExhausted); |
| } |
| else |
| { |
| for (auto providerIter = mDefaultOtaProviderList.Begin(); providerIter.Next();) |
| { |
| providerLocation = providerIter.GetValue(); |
| |
| if (providerLocation.GetFabricIndex() == fabricIndex) |
| { |
| providerFound = true; |
| break; |
| } |
| } |
| } |
| |
| if (!providerFound) |
| { |
| ChipLogError(SoftwareUpdate, "No OTA Providers available for immediate query"); |
| return CHIP_ERROR_NOT_FOUND; |
| } |
| |
| SetCurrentProviderLocation(providerLocation); |
| |
| // Go through the driver as it has additional logic to execute |
| mOtaRequestorDriver->SendQueryImage(); |
| |
| ChipLogProgress(SoftwareUpdate, "Triggered immediate OTA query for fabric: 0x%x", |
| static_cast<unsigned>(providerLocation.GetFabricIndex())); |
| |
| return CHIP_NO_ERROR; |
| } |
| |
| void DefaultOTARequestor::DownloadUpdate() |
| { |
| RecordNewUpdateState(OTAUpdateStateEnum::kDownloading, OTAChangeReasonEnum::kSuccess); |
| ConnectToProvider(kDownload); |
| } |
| |
| void DefaultOTARequestor::DownloadUpdateDelayedOnUserConsent() |
| { |
| RecordNewUpdateState(OTAUpdateStateEnum::kDelayedOnUserConsent, OTAChangeReasonEnum::kSuccess); |
| } |
| |
| void DefaultOTARequestor::ApplyUpdate() |
| { |
| RecordNewUpdateState(OTAUpdateStateEnum::kApplying, OTAChangeReasonEnum::kSuccess); |
| |
| // If image is successfully applied, the device will reboot so persist all relevant data |
| StoreCurrentUpdateInfo(); |
| |
| ConnectToProvider(kApplyUpdate); |
| } |
| |
| void DefaultOTARequestor::NotifyUpdateApplied() |
| { |
| // Log the VersionApplied event |
| uint16_t productId; |
| if (DeviceLayer::GetDeviceInstanceInfoProvider()->GetProductId(productId) != CHIP_NO_ERROR) |
| { |
| ChipLogError(SoftwareUpdate, "Cannot get Product ID"); |
| RecordErrorUpdateState(CHIP_ERROR_INCORRECT_STATE); |
| return; |
| } |
| |
| OtaRequestorServerOnVersionApplied(mCurrentVersion, productId); |
| |
| ConnectToProvider(kNotifyUpdateApplied); |
| } |
| |
| CHIP_ERROR DefaultOTARequestor::ClearDefaultOtaProviderList(FabricIndex fabricIndex) |
| { |
| CHIP_ERROR error = mDefaultOtaProviderList.Delete(fabricIndex); |
| |
| // Ignore the error if no entry for the associated fabric index has been found. |
| ReturnErrorCodeIf(error == CHIP_ERROR_NOT_FOUND, CHIP_NO_ERROR); |
| ReturnErrorOnFailure(error); |
| |
| return mStorage->StoreDefaultProviders(mDefaultOtaProviderList); |
| } |
| |
| CHIP_ERROR DefaultOTARequestor::AddDefaultOtaProvider(const ProviderLocationType & providerLocation) |
| { |
| // Look for an entry with the same fabric index indicated |
| auto iterator = mDefaultOtaProviderList.Begin(); |
| while (iterator.Next()) |
| { |
| ProviderLocationType pl = iterator.GetValue(); |
| if (pl.GetFabricIndex() == providerLocation.GetFabricIndex()) |
| { |
| ChipLogError(SoftwareUpdate, "Default OTA provider entry with fabric %d already exists", pl.GetFabricIndex()); |
| return CHIP_IM_GLOBAL_STATUS(ConstraintError); |
| } |
| } |
| |
| ReturnErrorOnFailure(mDefaultOtaProviderList.Add(providerLocation)); |
| |
| return mStorage->StoreDefaultProviders(mDefaultOtaProviderList); |
| } |
| |
| void DefaultOTARequestor::OnDownloadStateChanged(OTADownloader::State state, OTAChangeReasonEnum reason) |
| { |
| VerifyOrDie(mOtaRequestorDriver != nullptr); |
| |
| switch (state) |
| { |
| case OTADownloader::State::kComplete: |
| mOtaRequestorDriver->UpdateDownloaded(); |
| mBdxMessenger.Reset(); |
| break; |
| case OTADownloader::State::kIdle: |
| if (reason != OTAChangeReasonEnum::kSuccess) |
| { |
| RecordErrorUpdateState(CHIP_ERROR_CONNECTION_ABORTED, reason); |
| } |
| mBdxMessenger.Reset(); |
| break; |
| default: |
| break; |
| } |
| } |
| |
| void DefaultOTARequestor::OnUpdateProgressChanged(Nullable<uint8_t> percent) |
| { |
| OtaRequestorServerSetUpdateStateProgress(percent); |
| } |
| |
| IdleStateReason DefaultOTARequestor::MapErrorToIdleStateReason(CHIP_ERROR error) |
| { |
| if (error == CHIP_NO_ERROR) |
| { |
| return IdleStateReason::kIdle; |
| } |
| if (error == CHIP_ERROR_CONNECTION_CLOSED_UNEXPECTEDLY) |
| { |
| return IdleStateReason::kInvalidSession; |
| } |
| |
| return IdleStateReason::kUnknown; |
| } |
| |
| void DefaultOTARequestor::RecordNewUpdateState(OTAUpdateStateEnum newState, OTAChangeReasonEnum reason, CHIP_ERROR error) |
| { |
| // Set server UpdateState attribute |
| OtaRequestorServerSetUpdateState(newState); |
| |
| // The UpdateStateProgress attribute only applies to the downloading state |
| if (newState != OTAUpdateStateEnum::kDownloading) |
| { |
| app::DataModel::Nullable<uint8_t> percent; |
| percent.SetNull(); |
| OtaRequestorServerSetUpdateStateProgress(percent); |
| } |
| |
| // Log the StateTransition event |
| Nullable<uint32_t> targetSoftwareVersion; |
| if ((newState == OTAUpdateStateEnum::kDownloading) || (newState == OTAUpdateStateEnum::kApplying) || |
| (newState == OTAUpdateStateEnum::kRollingBack)) |
| { |
| targetSoftwareVersion.SetNonNull(mTargetVersion); |
| } |
| OtaRequestorServerOnStateTransition(mCurrentUpdateState, newState, reason, targetSoftwareVersion); |
| |
| OTAUpdateStateEnum prevState = mCurrentUpdateState; |
| // Update the new state before handling the state transition |
| mCurrentUpdateState = newState; |
| |
| if ((newState == OTAUpdateStateEnum::kIdle) && (prevState != OTAUpdateStateEnum::kIdle)) |
| { |
| IdleStateReason idleStateReason = MapErrorToIdleStateReason(error); |
| mOtaRequestorDriver->HandleIdleStateEnter(idleStateReason); |
| } |
| else if ((prevState == OTAUpdateStateEnum::kIdle) && (newState != OTAUpdateStateEnum::kIdle)) |
| { |
| mOtaRequestorDriver->HandleIdleStateExit(); |
| } |
| } |
| |
| void DefaultOTARequestor::RecordErrorUpdateState(CHIP_ERROR error, OTAChangeReasonEnum reason) |
| { |
| // Log the DownloadError event |
| OTAImageProcessorInterface * imageProcessor = mBdxDownloader->GetImageProcessorDelegate(); |
| VerifyOrDie(imageProcessor != nullptr); |
| Nullable<uint8_t> progressPercent = imageProcessor->GetPercentComplete(); |
| Nullable<int64_t> platformCode; |
| OtaRequestorServerOnDownloadError(mTargetVersion, imageProcessor->GetBytesDownloaded(), progressPercent, platformCode); |
| |
| // Whenever an error occurs, always reset to Idle state |
| RecordNewUpdateState(OTAUpdateStateEnum::kIdle, reason, error); |
| } |
| |
| CHIP_ERROR DefaultOTARequestor::GenerateUpdateToken() |
| { |
| if (mUpdateToken.empty()) |
| { |
| VerifyOrReturnError(mServer != nullptr, CHIP_ERROR_INCORRECT_STATE); |
| VerifyOrReturnError(mProviderLocation.HasValue(), CHIP_ERROR_INCORRECT_STATE); |
| |
| const FabricInfo * fabricInfo = mServer->GetFabricTable().FindFabricWithIndex(mProviderLocation.Value().fabricIndex); |
| VerifyOrReturnError(fabricInfo != nullptr, CHIP_ERROR_INCORRECT_STATE); |
| |
| static_assert(sizeof(NodeId) == sizeof(uint64_t), "Unexpected NodeId size"); |
| Encoding::BigEndian::Put64(mUpdateTokenBuffer, fabricInfo->GetPeerId().GetNodeId()); |
| mUpdateToken = ByteSpan(mUpdateTokenBuffer, sizeof(NodeId)); |
| } |
| |
| return CHIP_NO_ERROR; |
| } |
| |
| CHIP_ERROR DefaultOTARequestor::SendQueryImageRequest(Messaging::ExchangeManager & exchangeMgr, const SessionHandle & sessionHandle) |
| { |
| VerifyOrReturnError(mProviderLocation.HasValue(), CHIP_ERROR_INCORRECT_STATE); |
| |
| constexpr OTADownloadProtocol kProtocolsSupported[] = { OTADownloadProtocol::kBDXSynchronous }; |
| QueryImage::Type args; |
| |
| uint16_t vendorId; |
| ReturnErrorOnFailure(DeviceLayer::GetDeviceInstanceInfoProvider()->GetVendorId(vendorId)); |
| args.vendorID = static_cast<VendorId>(vendorId); |
| |
| ReturnErrorOnFailure(DeviceLayer::GetDeviceInstanceInfoProvider()->GetProductId(args.productID)); |
| |
| ReturnErrorOnFailure(DeviceLayer::ConfigurationMgr().GetSoftwareVersion(args.softwareVersion)); |
| |
| args.protocolsSupported = kProtocolsSupported; |
| args.requestorCanConsent.SetValue(!BasicInformation::IsLocalConfigDisabled() && mOtaRequestorDriver->CanConsent()); |
| |
| uint16_t hardwareVersion; |
| if (DeviceLayer::GetDeviceInstanceInfoProvider()->GetHardwareVersion(hardwareVersion) == CHIP_NO_ERROR) |
| { |
| args.hardwareVersion.SetValue(hardwareVersion); |
| } |
| |
| char location[DeviceLayer::ConfigurationManager::kMaxLocationLength]; |
| size_t codeLen = 0; |
| if ((DeviceLayer::ConfigurationMgr().GetCountryCode(location, sizeof(location), codeLen) == CHIP_NO_ERROR) && (codeLen == 2)) |
| { |
| args.location.SetValue(CharSpan(location, codeLen)); |
| } |
| else |
| { |
| // Country code unavailable or invalid, use default |
| args.location.SetValue(CharSpan("XX", strlen("XX"))); |
| } |
| |
| args.metadataForProvider = mMetadataForProvider; |
| Controller::OtaSoftwareUpdateProviderCluster cluster(exchangeMgr, sessionHandle, mProviderLocation.Value().endpoint); |
| |
| return cluster.InvokeCommand(args, this, OnQueryImageResponse, OnQueryImageFailure); |
| } |
| |
| CHIP_ERROR DefaultOTARequestor::ExtractUpdateDescription(const QueryImageResponseDecodableType & response, |
| UpdateDescription & update) const |
| { |
| NodeId nodeId; |
| CharSpan fileDesignator; |
| |
| VerifyOrReturnError(response.imageURI.HasValue(), CHIP_ERROR_INVALID_ARGUMENT); |
| ReturnErrorOnFailure(bdx::ParseURI(response.imageURI.Value(), nodeId, fileDesignator)); |
| VerifyOrReturnError(IsSpanUsable(fileDesignator), CHIP_ERROR_INVALID_ARGUMENT); |
| update.nodeId = nodeId; |
| update.fileDesignator = fileDesignator; |
| |
| VerifyOrReturnError(response.softwareVersion.HasValue(), CHIP_ERROR_INVALID_ARGUMENT); |
| VerifyOrReturnError(response.softwareVersionString.HasValue(), CHIP_ERROR_INVALID_ARGUMENT); |
| update.softwareVersion = response.softwareVersion.Value(); |
| update.softwareVersionStr = response.softwareVersionString.Value(); |
| |
| VerifyOrReturnError(response.updateToken.HasValue(), CHIP_ERROR_INVALID_ARGUMENT); |
| update.updateToken = response.updateToken.Value(); |
| |
| update.userConsentNeeded = response.userConsentNeeded.ValueOr(false); |
| update.metadataForRequestor = response.metadataForRequestor.ValueOr({}); |
| |
| return CHIP_NO_ERROR; |
| } |
| |
| CHIP_ERROR DefaultOTARequestor::StartDownload(Messaging::ExchangeManager & exchangeMgr, const SessionHandle & sessionHandle) |
| { |
| VerifyOrReturnError(mBdxDownloader != nullptr, CHIP_ERROR_INCORRECT_STATE); |
| |
| // TODO: allow caller to provide their own OTADownloader instance and set BDX parameters |
| |
| TransferSession::TransferInitData initOptions; |
| initOptions.TransferCtlFlags = bdx::TransferControlFlags::kReceiverDrive; |
| initOptions.MaxBlockSize = mOtaRequestorDriver->GetMaxDownloadBlockSize(); |
| initOptions.FileDesLength = static_cast<uint16_t>(mFileDesignator.size()); |
| initOptions.FileDesignator = reinterpret_cast<const uint8_t *>(mFileDesignator.data()); |
| |
| chip::Messaging::ExchangeContext * exchangeCtx = exchangeMgr.NewContext(sessionHandle, &mBdxMessenger); |
| VerifyOrReturnError(exchangeCtx != nullptr, CHIP_ERROR_NO_MEMORY); |
| |
| mBdxMessenger.Init(mBdxDownloader, exchangeCtx); |
| mBdxDownloader->SetMessageDelegate(&mBdxMessenger); |
| mBdxDownloader->SetStateDelegate(this); |
| |
| CHIP_ERROR err = mBdxDownloader->SetBDXParams(initOptions, kDownloadTimeoutSec); |
| if (err == CHIP_NO_ERROR) |
| { |
| err = mBdxDownloader->BeginPrepareDownload(); |
| } |
| |
| if (err != CHIP_NO_ERROR) |
| { |
| mBdxMessenger.Reset(); |
| } |
| |
| return err; |
| } |
| |
| CHIP_ERROR DefaultOTARequestor::SendApplyUpdateRequest(Messaging::ExchangeManager & exchangeMgr, |
| const SessionHandle & sessionHandle) |
| { |
| VerifyOrReturnError(mProviderLocation.HasValue(), CHIP_ERROR_INCORRECT_STATE); |
| ReturnErrorOnFailure(GenerateUpdateToken()); |
| |
| ApplyUpdateRequest::Type args; |
| args.updateToken = mUpdateToken; |
| args.newVersion = mTargetVersion; |
| |
| Controller::OtaSoftwareUpdateProviderCluster cluster(exchangeMgr, sessionHandle, mProviderLocation.Value().endpoint); |
| |
| return cluster.InvokeCommand(args, this, OnApplyUpdateResponse, OnApplyUpdateFailure); |
| } |
| |
| CHIP_ERROR DefaultOTARequestor::SendNotifyUpdateAppliedRequest(Messaging::ExchangeManager & exchangeMgr, |
| const SessionHandle & sessionHandle) |
| { |
| VerifyOrReturnError(mProviderLocation.HasValue(), CHIP_ERROR_INCORRECT_STATE); |
| ReturnErrorOnFailure(GenerateUpdateToken()); |
| |
| NotifyUpdateApplied::Type args; |
| args.updateToken = mUpdateToken; |
| args.softwareVersion = mCurrentVersion; |
| |
| Controller::OtaSoftwareUpdateProviderCluster cluster(exchangeMgr, sessionHandle, mProviderLocation.Value().endpoint); |
| |
| // There is no response for a notify so consider this OTA complete. Clear the provider location and reset any states to indicate |
| // so. |
| Reset(); |
| |
| return cluster.InvokeCommand(args, this, OnNotifyUpdateAppliedResponse, OnNotifyUpdateAppliedFailure); |
| } |
| |
| void DefaultOTARequestor::StoreCurrentUpdateInfo() |
| { |
| // TODO: change OTA requestor storage interface to store both values at once |
| CHIP_ERROR error = CHIP_NO_ERROR; |
| if (mProviderLocation.HasValue()) |
| { |
| error = mStorage->StoreCurrentProviderLocation(mProviderLocation.Value()); |
| } |
| else |
| { |
| error = mStorage->ClearCurrentProviderLocation(); |
| } |
| |
| if ((error == CHIP_NO_ERROR) || (error == CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND)) |
| { |
| if (mUpdateToken.size() > 0) |
| { |
| error = mStorage->StoreUpdateToken(mUpdateToken); |
| } |
| else |
| { |
| error = mStorage->ClearUpdateToken(); |
| } |
| } |
| |
| if ((error == CHIP_NO_ERROR) || (error == CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND)) |
| { |
| error = mStorage->StoreCurrentUpdateState(mCurrentUpdateState); |
| } |
| |
| if ((error == CHIP_NO_ERROR) || (error == CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND)) |
| { |
| if (mTargetVersion > 0) |
| { |
| error = mStorage->StoreTargetVersion(mTargetVersion); |
| } |
| else |
| { |
| error = mStorage->ClearTargetVersion(); |
| } |
| } |
| |
| if ((error != CHIP_NO_ERROR) && (error != CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND)) |
| { |
| ChipLogError(SoftwareUpdate, "Failed to store current update: %" CHIP_ERROR_FORMAT, error.Format()); |
| } |
| } |
| |
| void DefaultOTARequestor::LoadCurrentUpdateInfo() |
| { |
| mStorage->LoadDefaultProviders(mDefaultOtaProviderList); |
| |
| ProviderLocationType providerLocation; |
| if (mStorage->LoadCurrentProviderLocation(providerLocation) == CHIP_NO_ERROR) |
| { |
| mProviderLocation.SetValue(providerLocation); |
| } |
| |
| MutableByteSpan updateToken(mUpdateTokenBuffer); |
| if (mStorage->LoadUpdateToken(updateToken) == CHIP_NO_ERROR) |
| { |
| mUpdateToken = updateToken; |
| } |
| |
| if (mStorage->LoadCurrentUpdateState(mCurrentUpdateState) != CHIP_NO_ERROR) |
| { |
| mCurrentUpdateState = OTAUpdateStateEnum::kIdle; |
| } |
| |
| if (mStorage->LoadTargetVersion(mTargetVersion) != CHIP_NO_ERROR) |
| { |
| mTargetVersion = 0; |
| } |
| } |
| |
| // Invoked when the device becomes commissioned |
| void DefaultOTARequestor::OnCommissioningCompleteRequestor(const DeviceLayer::ChipDeviceEvent * event, intptr_t arg) |
| { |
| VerifyOrReturn(event->Type == DeviceLayer::DeviceEventType::kCommissioningComplete); |
| |
| ChipLogProgress(SoftwareUpdate, "Device commissioned, schedule a default provider query"); |
| |
| // TODO: Should we also send UpdateApplied here? |
| |
| // Schedule a query. At the end of this query/update process the Default Provider timer is started |
| OTARequestorDriver * driver = (reinterpret_cast<DefaultOTARequestor *>(arg))->mOtaRequestorDriver; |
| driver->OTACommissioningCallback(); |
| } |
| |
| } // namespace chip |