/** * * 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 #include #include #include #include #include "BDXDiagnosticLogsProvider.h" #ifdef MATTER_DM_DIAGNOSTIC_LOGS_CLUSTER_SERVER_ENDPOINT_COUNT static constexpr size_t kDiagnosticLogsDiagnosticLogsProviderDelegateTableSize = MATTER_DM_DIAGNOSTIC_LOGS_CLUSTER_SERVER_ENDPOINT_COUNT + CHIP_DEVICE_CONFIG_DYNAMIC_ENDPOINT_COUNT; static_assert(kDiagnosticLogsDiagnosticLogsProviderDelegateTableSize < kEmberInvalidEndpointIndex, "DiagnosticLogs: log provider delegate table size error"); using namespace chip::app::Clusters::DiagnosticLogs; using chip::Protocols::InteractionModel::Status; using chip::bdx::DiagnosticLogs::kMaxFileDesignatorLen; using chip::bdx::DiagnosticLogs::kMaxLogContentSize; namespace chip { namespace app { namespace Clusters { namespace DiagnosticLogs { namespace { DiagnosticLogsProviderDelegate * gDiagnosticLogsProviderDelegateTable[kDiagnosticLogsDiagnosticLogsProviderDelegateTableSize] = { nullptr }; DiagnosticLogsProviderDelegate * GetDiagnosticLogsProviderDelegate(EndpointId endpoint) { uint16_t ep = emberAfGetClusterServerEndpointIndex(endpoint, Id, MATTER_DM_DIAGNOSTIC_LOGS_CLUSTER_SERVER_ENDPOINT_COUNT); auto delegate = (ep >= ArraySize(gDiagnosticLogsProviderDelegateTable) ? nullptr : gDiagnosticLogsProviderDelegateTable[ep]); if (delegate == nullptr) { ChipLogProgress(Zcl, "Diagnosticlogs: no log provider delegate set for endpoint:%u", endpoint); } return delegate; } void AddResponse(CommandHandler * commandObj, const ConcreteCommandPath & path, StatusEnum status) { Commands::RetrieveLogsResponse::Type response; response.status = status; commandObj->AddResponse(path, response); } void AddResponse(CommandHandler * commandObj, const ConcreteCommandPath & path, StatusEnum status, MutableByteSpan & logContent, const Optional & timeStamp, const Optional & timeSinceBoot) { Commands::RetrieveLogsResponse::Type response; response.status = status; response.logContent = ByteSpan(logContent); response.UTCTimeStamp = timeStamp; response.timeSinceBoot = timeSinceBoot; commandObj->AddResponse(path, response); } #if CHIP_CONFIG_ENABLE_BDX_LOG_TRANSFER BDXDiagnosticLogsProvider gBDXDiagnosticLogsProvider; #endif // CHIP_CONFIG_ENABLE_BDX_LOG_TRANSFER } // anonymous namespace DiagnosticLogsServer DiagnosticLogsServer::sInstance; void DiagnosticLogsServer::SetDiagnosticLogsProviderDelegate(EndpointId endpoint, DiagnosticLogsProviderDelegate * delegate) { uint16_t ep = emberAfGetClusterServerEndpointIndex(endpoint, Id, MATTER_DM_DIAGNOSTIC_LOGS_CLUSTER_SERVER_ENDPOINT_COUNT); if (ep < kDiagnosticLogsDiagnosticLogsProviderDelegateTableSize) { gDiagnosticLogsProviderDelegateTable[ep] = delegate; } } DiagnosticLogsServer & DiagnosticLogsServer::Instance() { return sInstance; } void DiagnosticLogsServer::HandleLogRequestForResponsePayload(CommandHandler * commandObj, const ConcreteCommandPath & path, IntentEnum intent, StatusEnum status) { // If there is no delegate, there is no mechanism to read the logs. Assume those are empty and return NoLogs auto * delegate = GetDiagnosticLogsProviderDelegate(path.mEndpointId); VerifyOrReturn(nullptr != delegate, AddResponse(commandObj, path, StatusEnum::kNoLogs)); Platform::ScopedMemoryBuffer buffer; VerifyOrReturn(buffer.Alloc(kMaxLogContentSize), AddResponse(commandObj, path, StatusEnum::kDenied)); auto logContent = MutableByteSpan(buffer.Get(), kMaxLogContentSize); Optional timeStamp; Optional timeSinceBoot; auto size = delegate->GetSizeForIntent(intent); VerifyOrReturn(size != 0, AddResponse(commandObj, path, StatusEnum::kNoLogs)); auto err = delegate->GetLogForIntent(intent, logContent, timeStamp, timeSinceBoot); VerifyOrReturn(CHIP_ERROR_NOT_FOUND != err, AddResponse(commandObj, path, StatusEnum::kNoLogs)); VerifyOrReturn(CHIP_NO_ERROR == err, AddResponse(commandObj, path, StatusEnum::kDenied)); AddResponse(commandObj, path, status, logContent, timeStamp, timeSinceBoot); } void DiagnosticLogsServer::HandleLogRequestForBdx(CommandHandler * commandObj, const ConcreteCommandPath & path, IntentEnum intent, Optional transferFileDesignator) { // If the RequestedProtocol is set to BDX and there is no TransferFileDesignator the command SHALL fail with a Status Code of // INVALID_COMMAND. VerifyOrReturn(transferFileDesignator.HasValue(), commandObj->AddStatus(path, Status::InvalidCommand)); VerifyOrReturn(transferFileDesignator.Value().size() <= kMaxFileDesignatorLen, commandObj->AddStatus(path, Status::ConstraintError)); // If there is no delegate, there is no mechanism to read the logs. Assume those are empty and return NoLogs auto * delegate = GetDiagnosticLogsProviderDelegate(path.mEndpointId); VerifyOrReturn(nullptr != delegate, AddResponse(commandObj, path, StatusEnum::kNoLogs)); auto size = delegate->GetSizeForIntent(intent); // In the case where the size is 0 sets the Status field of the RetrieveLogsResponse to NoLogs and do not start a BDX session. VerifyOrReturn(size != 0, HandleLogRequestForResponsePayload(commandObj, path, intent, StatusEnum::kNoLogs)); // In the case where the Node is able to fit the entirety of the requested logs within the LogContent field, the Status field of // the RetrieveLogsResponse SHALL be set to Exhausted and a BDX session SHALL NOT be initiated. VerifyOrReturn(size > kMaxLogContentSize, HandleLogRequestForResponsePayload(commandObj, path, intent, StatusEnum::kExhausted)); // If the RequestedProtocol is set to BDX and either the Node does not support BDX or it is not possible for the Node // to establish a BDX session, then the Node SHALL utilize the LogContent field of the RetrieveLogsResponse command // to transfer as much of the current logs as it can fit within the response, and the Status field of the // RetrieveLogsResponse SHALL be set to Exhausted. #if CHIP_CONFIG_ENABLE_BDX_LOG_TRANSFER VerifyOrReturn(!gBDXDiagnosticLogsProvider.IsBusy(), AddResponse(commandObj, path, StatusEnum::kBusy)); auto err = gBDXDiagnosticLogsProvider.InitializeTransfer(commandObj, path, delegate, intent, transferFileDesignator.Value()); VerifyOrReturn(CHIP_NO_ERROR == err, AddResponse(commandObj, path, StatusEnum::kDenied)); #else HandleLogRequestForResponsePayload(commandObj, path, intent, StatusEnum::kExhausted); #endif // CHIP_CONFIG_ENABLE_BDX_LOG_TRANSFER } } // namespace DiagnosticLogs } // namespace Clusters } // namespace app } // namespace chip bool emberAfDiagnosticLogsClusterRetrieveLogsRequestCallback(chip::app::CommandHandler * commandObj, const chip::app::ConcreteCommandPath & commandPath, const Commands::RetrieveLogsRequest::DecodableType & commandData) { // If the Intent and/or the RequestedProtocol arguments contain invalid (out of range) values the command SHALL fail with a // Status Code of INVALID_COMMAND. auto intent = commandData.intent; auto protocol = commandData.requestedProtocol; if (intent == IntentEnum::kUnknownEnumValue || protocol == TransferProtocolEnum::kUnknownEnumValue) { commandObj->AddStatus(commandPath, Status::InvalidCommand); return true; } auto instance = DiagnosticLogsServer::Instance(); if (protocol == TransferProtocolEnum::kResponsePayload) { instance.HandleLogRequestForResponsePayload(commandObj, commandPath, intent); } else { instance.HandleLogRequestForBdx(commandObj, commandPath, intent, commandData.transferFileDesignator); } return true; } void MatterDiagnosticLogsPluginServerInitCallback() {} #endif // #ifdef MATTER_DM_DIAGNOSTIC_LOGS_CLUSTER_SERVER_ENDPOINT_COUNT