blob: 0bd3fe0af9332f88a72490325c67b94ef3c4d82c [file] [log] [blame]
/**
*
* 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 <stdint.h>
#include <string.h>
#include <app/util/config.h>
#include "app/server/Server.h"
#include <app-common/zap-generated/attributes/Accessors.h>
#include <app-common/zap-generated/cluster-objects.h>
#include <app-common/zap-generated/ids/Attributes.h>
#include <app-common/zap-generated/ids/Clusters.h>
#include <app/AttributeAccessInterface.h>
#include <app/EventLogging.h>
#include <app/reporting/reporting.h>
#include <app/util/attribute-storage.h>
#include <lib/support/ScopedBuffer.h>
#include <platform/ConnectivityManager.h>
#include <platform/DiagnosticDataProvider.h>
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<EndpointId>::Missing(), GeneralDiagnostics::Id) {}
CHIP_ERROR Read(const ConcreteReadAttributePath & aPath, AttributeValueEncoder & aEncoder) override;
private:
template <typename T>
CHIP_ERROR ReadIfSupported(CHIP_ERROR (DiagnosticDataProvider::*getter)(T &), AttributeValueEncoder & aEncoder);
template <typename T>
CHIP_ERROR ReadListIfSupported(CHIP_ERROR (DiagnosticDataProvider::*getter)(T &), AttributeValueEncoder & aEncoder);
CHIP_ERROR ReadNetworkInterfaces(AttributeValueEncoder & aEncoder);
};
template <typename T>
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 <typename T>
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<System::Clock::Seconds64>(Server::GetInstance().TimeSinceInit());
return aEncoder.Encode(static_cast<uint64_t>(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<kMaxHardwareFaults> & previous,
const GeneralFaults<kMaxHardwareFaults> & 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<const HardwareFaultEnum> currentList(reinterpret_cast<const HardwareFaultEnum *>(current.data()),
current.size());
DataModel::List<const HardwareFaultEnum> previousList(reinterpret_cast<const HardwareFaultEnum *>(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<kMaxRadioFaults> & previous,
const GeneralFaults<kMaxRadioFaults> & 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<const RadioFaultEnum> currentList(reinterpret_cast<const RadioFaultEnum *>(current.data()), current.size());
DataModel::List<const RadioFaultEnum> previousList(reinterpret_cast<const RadioFaultEnum *>(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<kMaxNetworkFaults> & previous,
const GeneralFaults<kMaxNetworkFaults> & 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<const NetworkFaultEnum> currentList(reinterpret_cast<const NetworkFaultEnum *>(current.data()),
current.size());
DataModel::List<const NetworkFaultEnum> previousList(reinterpret_cast<const NetworkFaultEnum *>(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<System::Clock::Milliseconds64>(Server::GetInstance().TimeSinceInit());
response.systemTimeMs = static_cast<uint64_t>(system_time_ms.count());
if (posix_time_us.count() != 0)
{
response.posixTimeMs.SetNonNull(
static_cast<uint64_t>(std::chrono::duration_cast<System::Clock::Milliseconds64>(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<uint8_t> 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;
registerAttributeAccessOverride(&gAttrAccess);
ConnectivityMgr().SetDelegate(&gDiagnosticDelegate);
if (GetDiagnosticDataProvider().GetBootReason(bootReason) == CHIP_NO_ERROR)
{
GeneralDiagnosticsServer::Instance().OnDeviceReboot(bootReason);
}
}