/* * * 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 glue code for passing the incoming OTA Requestor cluster commands * to the OTA Requestor object that handles them */ #include #include #include #include #include #include #include #include using namespace chip; using namespace chip::app; using namespace chip::app::Clusters; using namespace chip::app::Clusters::OtaSoftwareUpdateRequestor; using namespace chip::app::Clusters::OtaSoftwareUpdateRequestor::Attributes; using namespace chip::app::Clusters::OtaSoftwareUpdateRequestor::Structs; using Protocols::InteractionModel::Status; namespace { constexpr size_t kMaxMetadataLen = 512; // The maximum length of Metadata in any OTA Requestor command class OtaSoftwareUpdateRequestorAttrAccess : public AttributeAccessInterface { public: // Register for the OTA Requestor Cluster on all endpoints. OtaSoftwareUpdateRequestorAttrAccess() : AttributeAccessInterface(Optional::Missing(), OtaSoftwareUpdateRequestor::Id) {} // TODO: Implement Read/Write for OtaSoftwareUpdateRequestorAttrAccess CHIP_ERROR Read(const ConcreteReadAttributePath & aPath, AttributeValueEncoder & aEncoder) override; CHIP_ERROR Write(const ConcreteDataAttributePath & aPath, AttributeValueDecoder & aDecoder) override; private: CHIP_ERROR ReadDefaultOtaProviders(AttributeValueEncoder & aEncoder); CHIP_ERROR WriteDefaultOtaProviders(const ConcreteDataAttributePath & aPath, AttributeValueDecoder & aDecoder); }; OtaSoftwareUpdateRequestorAttrAccess gAttrAccess; CHIP_ERROR OtaSoftwareUpdateRequestorAttrAccess::Read(const ConcreteReadAttributePath & aPath, AttributeValueEncoder & aEncoder) { switch (aPath.mAttributeId) { case Attributes::DefaultOTAProviders::Id: return ReadDefaultOtaProviders(aEncoder); default: break; } return CHIP_NO_ERROR; } CHIP_ERROR OtaSoftwareUpdateRequestorAttrAccess::Write(const ConcreteDataAttributePath & aPath, AttributeValueDecoder & aDecoder) { switch (aPath.mAttributeId) { case Attributes::DefaultOTAProviders::Id: { return WriteDefaultOtaProviders(aPath, aDecoder); } default: break; } return CHIP_NO_ERROR; } CHIP_ERROR OtaSoftwareUpdateRequestorAttrAccess::ReadDefaultOtaProviders(AttributeValueEncoder & aEncoder) { chip::OTARequestorInterface * requestor = chip::GetRequestorInstance(); if (requestor == nullptr) { return aEncoder.EncodeEmptyList(); } return aEncoder.EncodeList([&](const auto & encoder) -> CHIP_ERROR { auto iterator = requestor->GetDefaultOTAProviderListIterator(); while (iterator.Next()) { ProviderLocation::Type pl = iterator.GetValue(); ReturnErrorOnFailure(encoder.Encode(pl)); } return CHIP_NO_ERROR; }); } CHIP_ERROR OtaSoftwareUpdateRequestorAttrAccess::WriteDefaultOtaProviders(const ConcreteDataAttributePath & aPath, AttributeValueDecoder & aDecoder) { chip::OTARequestorInterface * requestor = chip::GetRequestorInstance(); if (requestor == nullptr) { return CHIP_ERROR_NOT_FOUND; } if (!aPath.IsListOperation() || aPath.mListOp == ConcreteDataAttributePath::ListOperation::ReplaceAll) { DataModel::DecodableList list; ReturnErrorOnFailure(aDecoder.Decode(list)); ReturnErrorOnFailure(requestor->ClearDefaultOtaProviderList(aDecoder.AccessingFabricIndex())); auto iter = list.begin(); while (iter.Next()) { ReturnErrorOnFailure(requestor->AddDefaultOtaProvider(iter.GetValue())); } return iter.GetStatus(); } switch (aPath.mListOp) { case ConcreteDataAttributePath::ListOperation::ReplaceItem: return CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE; case ConcreteDataAttributePath::ListOperation::DeleteItem: return CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE; case ConcreteDataAttributePath::ListOperation::AppendItem: { OtaSoftwareUpdateRequestor::Structs::ProviderLocation::DecodableType item; ReturnErrorOnFailure(aDecoder.Decode(item)); return requestor->AddDefaultOtaProvider(item); } default: return CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE; } return CHIP_NO_ERROR; } } // namespace // ----------------------------------------------------------------------------- // Global functions Status OtaRequestorServerSetUpdateState(OTAUpdateStateEnum value) { Status status = Status::Success; // Find all endpoints that have OtaSoftwareUpdateRequestor implemented for (auto endpoint : EnabledEndpointsWithServerCluster(OtaSoftwareUpdateRequestor::Id)) { OTAUpdateStateEnum currentValue; status = Attributes::UpdateState::Get(endpoint, ¤tValue); VerifyOrDie(Status::Success == status); if (currentValue != value) { status = Attributes::UpdateState::Set(endpoint, value); VerifyOrDie(Status::Success == status); } } return status; } Status OtaRequestorServerGetUpdateState(chip::EndpointId endpointId, OTAUpdateStateEnum & value) { return Attributes::UpdateState::Get(endpointId, &value); } Status OtaRequestorServerSetUpdateStateProgress(app::DataModel::Nullable value) { Status status = Status::Success; // Find all endpoints that have OtaSoftwareUpdateRequestor implemented for (auto endpoint : EnabledEndpointsWithServerCluster(OtaSoftwareUpdateRequestor::Id)) { app::DataModel::Nullable currentValue; status = Attributes::UpdateStateProgress::Get(endpoint, currentValue); VerifyOrDie(Status::Success == status); if (currentValue != value) { status = Attributes::UpdateStateProgress::Set(endpoint, value); VerifyOrDie(Status::Success == status); } } return status; } Status OtaRequestorServerGetUpdateStateProgress(chip::EndpointId endpointId, DataModel::Nullable & value) { return Attributes::UpdateStateProgress::Get(endpointId, value); } void OtaRequestorServerOnStateTransition(OTAUpdateStateEnum previousState, OTAUpdateStateEnum newState, OTAChangeReasonEnum reason, DataModel::Nullable const & targetSoftwareVersion) { if (previousState == newState) { ChipLogError(Zcl, "Previous state and new state are the same (%d), no event to log", to_underlying(newState)); return; } // Find all endpoints that have OtaSoftwareUpdateRequestor implemented for (auto endpoint : EnabledEndpointsWithServerCluster(OtaSoftwareUpdateRequestor::Id)) { Events::StateTransition::Type event{ previousState, newState, reason, targetSoftwareVersion }; EventNumber eventNumber; CHIP_ERROR err = LogEvent(event, endpoint, eventNumber); if (CHIP_NO_ERROR != err) { ChipLogError(Zcl, "Failed to record StateTransition event: %" CHIP_ERROR_FORMAT, err.Format()); } } } void OtaRequestorServerOnVersionApplied(uint32_t softwareVersion, uint16_t productId) { // Find all endpoints that have OtaSoftwareUpdateRequestor implemented for (auto endpoint : EnabledEndpointsWithServerCluster(OtaSoftwareUpdateRequestor::Id)) { Events::VersionApplied::Type event{ softwareVersion, productId }; EventNumber eventNumber; CHIP_ERROR err = LogEvent(event, endpoint, eventNumber); if (CHIP_NO_ERROR != err) { ChipLogError(Zcl, "Failed to record VersionApplied event: %" CHIP_ERROR_FORMAT, err.Format()); } } } void OtaRequestorServerOnDownloadError(uint32_t softwareVersion, uint64_t bytesDownloaded, DataModel::Nullable progressPercent, DataModel::Nullable platformCode) { // Find all endpoints that have OtaSoftwareUpdateRequestor implemented for (auto endpoint : EnabledEndpointsWithServerCluster(OtaSoftwareUpdateRequestor::Id)) { Events::DownloadError::Type event{ softwareVersion, bytesDownloaded, progressPercent, platformCode }; EventNumber eventNumber; CHIP_ERROR err = LogEvent(event, endpoint, eventNumber); if (CHIP_NO_ERROR != err) { ChipLogError(Zcl, "Failed to record DownloadError event: %" CHIP_ERROR_FORMAT, err.Format()); } } } // ----------------------------------------------------------------------------- // Callbacks implementation bool emberAfOtaSoftwareUpdateRequestorClusterAnnounceOTAProviderCallback( chip::app::CommandHandler * commandObj, const chip::app::ConcreteCommandPath & commandPath, const chip::app::Clusters::OtaSoftwareUpdateRequestor::Commands::AnnounceOTAProvider::DecodableType & commandData) { auto & metadataForNode = commandData.metadataForNode; chip::OTARequestorInterface * requestor = chip::GetRequestorInstance(); if (requestor == nullptr) { commandObj->AddStatus(commandPath, Status::UnsupportedCommand); return true; } if (metadataForNode.HasValue() && metadataForNode.Value().size() > kMaxMetadataLen) { ChipLogError(Zcl, "Metadata size %u exceeds max %u", static_cast(metadataForNode.Value().size()), static_cast(kMaxMetadataLen)); commandObj->AddStatus(commandPath, Status::InvalidCommand); return true; } requestor->HandleAnnounceOTAProvider(commandObj, commandPath, commandData); return true; } // ----------------------------------------------------------------------------- // Plugin initialization void MatterOtaSoftwareUpdateRequestorPluginServerInitCallback() { AttributeAccessInterfaceRegistry::Instance().Register(&gAttrAccess); }