/** * * Copyright (c) 2021 Project CHIP Authors * * 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. */ #include "general-diagnostics-server.h" #include #include #include #include "app/server/Server.h" #include #include #include #include #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::GeneralDiagnostics; using namespace chip::app::Clusters::GeneralDiagnostics::Attributes; using namespace chip::DeviceLayer; using chip::DeviceLayer::ConnectivityMgr; using chip::DeviceLayer::DiagnosticDataProvider; using chip::DeviceLayer::GetDiagnosticDataProvider; using chip::Protocols::InteractionModel::Status; namespace { constexpr uint8_t kCurrentClusterRevision = 2; bool IsTestEventTriggerEnabled() { auto * triggerDelegate = chip::Server::GetInstance().GetTestEventTriggerDelegate(); if (triggerDelegate == nullptr) { return false; } uint8_t zeroByteSpanData[TestEventTriggerDelegate::kEnableKeyLength] = { 0 }; if (triggerDelegate->DoesEnableKeyMatch(ByteSpan(zeroByteSpanData))) { return false; } return true; } bool IsByteSpanAllZeros(const ByteSpan & byteSpan) { for (unsigned char it : byteSpan) { if (it != 0) { return false; } } return true; } void ReportAttributeOnAllEndpoints(AttributeId attribute) { for (auto endpoint : EnabledEndpointsWithServerCluster(GeneralDiagnostics::Id)) { MatterReportingAttributeChangeCallback(endpoint, GeneralDiagnostics::Id, attribute); } } class GeneralDiagosticsAttrAccess : public AttributeAccessInterface { public: // Register for the GeneralDiagnostics cluster on all endpoints. GeneralDiagosticsAttrAccess() : AttributeAccessInterface(Optional::Missing(), GeneralDiagnostics::Id) {} CHIP_ERROR Read(const ConcreteReadAttributePath & aPath, AttributeValueEncoder & aEncoder) override; private: template CHIP_ERROR ReadIfSupported(CHIP_ERROR (DiagnosticDataProvider::*getter)(T &), AttributeValueEncoder & aEncoder); template CHIP_ERROR ReadListIfSupported(CHIP_ERROR (DiagnosticDataProvider::*getter)(T &), AttributeValueEncoder & aEncoder); CHIP_ERROR ReadNetworkInterfaces(AttributeValueEncoder & aEncoder); }; template CHIP_ERROR GeneralDiagosticsAttrAccess::ReadIfSupported(CHIP_ERROR (DiagnosticDataProvider::*getter)(T &), AttributeValueEncoder & aEncoder) { T data; CHIP_ERROR err = (GetDiagnosticDataProvider().*getter)(data); if (err == CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE) { data = {}; } else if (err != CHIP_NO_ERROR) { return err; } return aEncoder.Encode(data); } template CHIP_ERROR GeneralDiagosticsAttrAccess::ReadListIfSupported(CHIP_ERROR (DiagnosticDataProvider::*getter)(T &), AttributeValueEncoder & aEncoder) { CHIP_ERROR err = CHIP_NO_ERROR; T faultList; if ((GetDiagnosticDataProvider().*getter)(faultList) == CHIP_NO_ERROR) { err = aEncoder.EncodeList([&faultList](const auto & encoder) -> CHIP_ERROR { for (auto fault : faultList) { ReturnErrorOnFailure(encoder.Encode(fault)); } return CHIP_NO_ERROR; }); } else { err = aEncoder.EncodeEmptyList(); } return err; } CHIP_ERROR GeneralDiagosticsAttrAccess::ReadNetworkInterfaces(AttributeValueEncoder & aEncoder) { CHIP_ERROR err = CHIP_NO_ERROR; DeviceLayer::NetworkInterface * netifs; if (DeviceLayer::GetDiagnosticDataProvider().GetNetworkInterfaces(&netifs) == CHIP_NO_ERROR) { err = aEncoder.EncodeList([&netifs](const auto & encoder) -> CHIP_ERROR { for (DeviceLayer::NetworkInterface * ifp = netifs; ifp != nullptr; ifp = ifp->Next) { ReturnErrorOnFailure(encoder.Encode(*ifp)); } return CHIP_NO_ERROR; }); DeviceLayer::GetDiagnosticDataProvider().ReleaseNetworkInterfaces(netifs); } else { err = aEncoder.EncodeEmptyList(); } return err; } GeneralDiagosticsAttrAccess gAttrAccess; CHIP_ERROR GeneralDiagosticsAttrAccess::Read(const ConcreteReadAttributePath & aPath, AttributeValueEncoder & aEncoder) { if (aPath.mClusterId != GeneralDiagnostics::Id) { // We shouldn't have been called at all. return CHIP_ERROR_INVALID_ARGUMENT; } switch (aPath.mAttributeId) { case NetworkInterfaces::Id: { return ReadNetworkInterfaces(aEncoder); } case ActiveHardwareFaults::Id: { return ReadListIfSupported(&DiagnosticDataProvider::GetActiveHardwareFaults, aEncoder); } case ActiveRadioFaults::Id: { return ReadListIfSupported(&DiagnosticDataProvider::GetActiveRadioFaults, aEncoder); } case ActiveNetworkFaults::Id: { return ReadListIfSupported(&DiagnosticDataProvider::GetActiveNetworkFaults, aEncoder); } case RebootCount::Id: { return ReadIfSupported(&DiagnosticDataProvider::GetRebootCount, aEncoder); } case UpTime::Id: { System::Clock::Seconds64 system_time_seconds = std::chrono::duration_cast(Server::GetInstance().TimeSinceInit()); return aEncoder.Encode(static_cast(system_time_seconds.count())); } case TotalOperationalHours::Id: { return ReadIfSupported(&DiagnosticDataProvider::GetTotalOperationalHours, aEncoder); } case BootReason::Id: { return ReadIfSupported(&DiagnosticDataProvider::GetBootReason, aEncoder); } case TestEventTriggersEnabled::Id: { bool isTestEventTriggersEnabled = IsTestEventTriggerEnabled(); return aEncoder.Encode(isTestEventTriggersEnabled); } // Note: Attribute ID 0x0009 was removed (#30002). case FeatureMap::Id: { uint32_t features = 0; #if CHIP_CONFIG_MAX_PATHS_PER_INVOKE > 1 features |= to_underlying(Clusters::GeneralDiagnostics::Feature::kDataModelTest); #endif // CHIP_CONFIG_MAX_PATHS_PER_INVOKE > 1 return aEncoder.Encode(features); } case ClusterRevision::Id: { return aEncoder.Encode(kCurrentClusterRevision); } default: break; } return CHIP_NO_ERROR; } class GeneralDiagnosticsDelegate : public DeviceLayer::ConnectivityManagerDelegate { // Gets called when any network interface on the Node is updated. void OnNetworkInfoChanged() override { ChipLogDetail(Zcl, "GeneralDiagnosticsDelegate: OnNetworkInfoChanged"); ReportAttributeOnAllEndpoints(GeneralDiagnostics::Attributes::NetworkInterfaces::Id); } }; GeneralDiagnosticsDelegate gDiagnosticDelegate; } // anonymous namespace namespace chip { namespace app { namespace Clusters { GeneralDiagnosticsServer GeneralDiagnosticsServer::instance; /********************************************************** * GeneralDiagnosticsServer Implementation *********************************************************/ GeneralDiagnosticsServer & GeneralDiagnosticsServer::Instance() { return instance; } // Gets called when the device has been rebooted. void GeneralDiagnosticsServer::OnDeviceReboot(BootReasonEnum bootReason) { ChipLogDetail(Zcl, "GeneralDiagnostics: OnDeviceReboot"); ReportAttributeOnAllEndpoints(GeneralDiagnostics::Attributes::BootReason::Id); // GeneralDiagnostics cluster should exist only for endpoint 0. if (emberAfContainsServer(0, GeneralDiagnostics::Id)) { Events::BootReason::Type event{ bootReason }; EventNumber eventNumber; CHIP_ERROR err = LogEvent(event, 0, eventNumber); if (CHIP_NO_ERROR != err) { ChipLogError(Zcl, "GeneralDiagnostics: Failed to record BootReason event: %" CHIP_ERROR_FORMAT, err.Format()); } } } // Get called when the Node detects a hardware fault has been raised. void GeneralDiagnosticsServer::OnHardwareFaultsDetect(const GeneralFaults & previous, const GeneralFaults & current) { ChipLogDetail(Zcl, "GeneralDiagnostics: OnHardwareFaultsDetect"); for (auto endpointId : EnabledEndpointsWithServerCluster(GeneralDiagnostics::Id)) { // If General Diagnostics cluster is implemented on this endpoint MatterReportingAttributeChangeCallback(endpointId, GeneralDiagnostics::Id, GeneralDiagnostics::Attributes::ActiveHardwareFaults::Id); // Record HardwareFault event EventNumber eventNumber; DataModel::List currentList(reinterpret_cast(current.data()), current.size()); DataModel::List previousList(reinterpret_cast(previous.data()), previous.size()); Events::HardwareFaultChange::Type event{ currentList, previousList }; if (CHIP_NO_ERROR != LogEvent(event, endpointId, eventNumber)) { ChipLogError(Zcl, "GeneralDiagnostics: Failed to record HardwareFault event"); } } } // Get called when the Node detects a radio fault has been raised. void GeneralDiagnosticsServer::OnRadioFaultsDetect(const GeneralFaults & previous, const GeneralFaults & current) { ChipLogDetail(Zcl, "GeneralDiagnostics: OnRadioFaultsDetect"); for (auto endpointId : EnabledEndpointsWithServerCluster(GeneralDiagnostics::Id)) { // If General Diagnostics cluster is implemented on this endpoint MatterReportingAttributeChangeCallback(endpointId, GeneralDiagnostics::Id, GeneralDiagnostics::Attributes::ActiveRadioFaults::Id); // Record RadioFault event EventNumber eventNumber; DataModel::List currentList(reinterpret_cast(current.data()), current.size()); DataModel::List previousList(reinterpret_cast(previous.data()), previous.size()); Events::RadioFaultChange::Type event{ currentList, previousList }; if (CHIP_NO_ERROR != LogEvent(event, endpointId, eventNumber)) { ChipLogError(Zcl, "GeneralDiagnostics: Failed to record RadioFault event"); } } } // Get called when the Node detects a network fault has been raised. void GeneralDiagnosticsServer::OnNetworkFaultsDetect(const GeneralFaults & previous, const GeneralFaults & current) { ChipLogDetail(Zcl, "GeneralDiagnostics: OnNetworkFaultsDetect"); for (auto endpointId : EnabledEndpointsWithServerCluster(GeneralDiagnostics::Id)) { // If General Diagnostics cluster is implemented on this endpoint MatterReportingAttributeChangeCallback(endpointId, GeneralDiagnostics::Id, GeneralDiagnostics::Attributes::ActiveNetworkFaults::Id); // Record NetworkFault event EventNumber eventNumber; DataModel::List currentList(reinterpret_cast(current.data()), current.size()); DataModel::List previousList(reinterpret_cast(previous.data()), previous.size()); Events::NetworkFaultChange::Type event{ currentList, previousList }; if (CHIP_NO_ERROR != LogEvent(event, endpointId, eventNumber)) { ChipLogError(Zcl, "GeneralDiagnostics: Failed to record NetworkFault event"); } } } } // namespace Clusters } // namespace app } // namespace chip namespace { TestEventTriggerDelegate * GetTriggerDelegateOnMatchingKey(ByteSpan enableKey) { if (enableKey.size() != TestEventTriggerDelegate::kEnableKeyLength) { return nullptr; } if (IsByteSpanAllZeros(enableKey)) { return nullptr; } auto * triggerDelegate = chip::Server::GetInstance().GetTestEventTriggerDelegate(); if (triggerDelegate == nullptr || !triggerDelegate->DoesEnableKeyMatch(enableKey)) { return nullptr; } return triggerDelegate; } } // namespace bool emberAfGeneralDiagnosticsClusterTestEventTriggerCallback(CommandHandler * commandObj, const ConcreteCommandPath & commandPath, const Commands::TestEventTrigger::DecodableType & commandData) { auto * triggerDelegate = GetTriggerDelegateOnMatchingKey(commandData.enableKey); if (triggerDelegate == nullptr) { commandObj->AddStatus(commandPath, Status::ConstraintError); return true; } CHIP_ERROR handleEventTriggerResult = triggerDelegate->HandleEventTriggers(commandData.eventTrigger); // When HandleEventTrigger fails, we simply convert any error to INVALID_COMMAND commandObj->AddStatus(commandPath, (handleEventTriggerResult != CHIP_NO_ERROR) ? Status::InvalidCommand : Status::Success); return true; } bool emberAfGeneralDiagnosticsClusterTimeSnapshotCallback(CommandHandler * commandObj, ConcreteCommandPath const & commandPath, Commands::TimeSnapshot::DecodableType const & commandData) { ChipLogError(Zcl, "Received TimeSnapshot command!"); Commands::TimeSnapshotResponse::Type response; System::Clock::Microseconds64 posix_time_us{ 0 }; // Only consider real time if time sync cluster is actually enabled. Avoids // likelihood of frequently reporting unsynced time. #ifdef ZCL_USING_TIME_SYNCHRONIZATION_CLUSTER_SERVER CHIP_ERROR posix_time_err = System::SystemClock().GetClock_RealTime(posix_time_us); if (posix_time_err != CHIP_NO_ERROR) { ChipLogError(Zcl, "Failed to get POSIX real time: %" CHIP_ERROR_FORMAT, posix_time_err.Format()); posix_time_us = System::Clock::Microseconds64{ 0 }; } #endif // ZCL_USING_TIME_SYNCHRONIZATION_CLUSTER_SERVER System::Clock::Milliseconds64 system_time_ms = std::chrono::duration_cast(Server::GetInstance().TimeSinceInit()); response.systemTimeMs = static_cast(system_time_ms.count()); if (posix_time_us.count() != 0) { response.posixTimeMs.SetNonNull( static_cast(std::chrono::duration_cast(posix_time_us).count())); } commandObj->AddResponse(commandPath, response); return true; } bool emberAfGeneralDiagnosticsClusterPayloadTestRequestCallback(CommandHandler * commandObj, const ConcreteCommandPath & commandPath, const Commands::PayloadTestRequest::DecodableType & commandData) { // Max allowed is 2048. if (commandData.count > 2048) { commandObj->AddStatus(commandPath, Status::ConstraintError); return true; } // Ensure Test Event triggers are enabled and key matches. auto * triggerDelegate = GetTriggerDelegateOnMatchingKey(commandData.enableKey); if (triggerDelegate == nullptr) { commandObj->AddStatus(commandPath, Status::ConstraintError); return true; } Commands::PayloadTestResponse::Type response; Platform::ScopedMemoryBufferWithSize payload; if (!payload.Calloc(commandData.count)) { commandObj->AddStatus(commandPath, Status::ResourceExhausted); return true; } memset(payload.Get(), commandData.value, payload.AllocatedSize()); response.payload = ByteSpan{ payload.Get(), payload.AllocatedSize() }; if (commandObj->AddResponseData(commandPath, response) != CHIP_NO_ERROR) { commandObj->AddStatus(commandPath, Status::ResourceExhausted); } return true; } void MatterGeneralDiagnosticsPluginServerInitCallback() { BootReasonEnum bootReason; AttributeAccessInterfaceRegistry::Instance().Register(&gAttrAccess); ConnectivityMgr().SetDelegate(&gDiagnosticDelegate); if (GetDiagnosticDataProvider().GetBootReason(bootReason) == CHIP_NO_ERROR) { GeneralDiagnosticsServer::Instance().OnDeviceReboot(bootReason); } }