From 1078039a27a9bcf6cecb93cceb0345f623632c56 Mon Sep 17 00:00:00 2001 From: Nivedita Sarkar Date: Thu, 16 Nov 2023 23:52:59 -0800 Subject: [PATCH] Address review comments --- .../all-clusters-app/linux/AppOptions.cpp | 42 +-- examples/all-clusters-app/linux/AppOptions.h | 10 +- examples/all-clusters-app/linux/BUILD.gn | 2 +- ...diagnostic-logs-provider-delegate-impl.cpp | 86 +++--- .../diagnostic-logs-provider-delegate-impl.h | 20 +- .../all-clusters-app/linux/main-common.cpp | 13 +- .../bdx/DiagnosticLogsBDXTransferHandler.cpp | 154 +++++----- .../bdx/DiagnosticLogsBDXTransferHandler.h | 64 +++-- .../diagnostic-logs-provider-delegate.h | 10 +- .../diagnostic-logs-server.cpp | 117 ++++---- .../diagnostic-logs-server.h | 59 +++- src/darwin/Framework/CHIP/MTRDevice.mm | 202 +------------- .../Framework/CHIP/MTRDevice_Internal.h | 2 + .../CHIP/MTRDiagnosticLogsTransferHandler.h | 85 ------ .../CHIP/MTRDiagnosticsLogDownloadTask.h | 41 +++ .../CHIP/MTRDiagnosticsLogDownloadTask.mm | 262 ++++++++++++++++++ .../CHIP/MTRDiagnosticsLogTransferHandler.h | 131 +++++++++ ...mm => MTRDiagnosticsLogTransferHandler.mm} | 72 +++-- .../Framework/CHIPTests/MTRDeviceTests.m | 157 ++++------- .../Matter.xcodeproj/project.pbxproj | 24 +- src/lib/core/CHIPConfig.h | 1 + src/protocols/bdx/TransferFacilitator.h | 5 +- 22 files changed, 886 insertions(+), 673 deletions(-) delete mode 100644 src/darwin/Framework/CHIP/MTRDiagnosticLogsTransferHandler.h create mode 100644 src/darwin/Framework/CHIP/MTRDiagnosticsLogDownloadTask.h create mode 100644 src/darwin/Framework/CHIP/MTRDiagnosticsLogDownloadTask.mm create mode 100644 src/darwin/Framework/CHIP/MTRDiagnosticsLogTransferHandler.h rename src/darwin/Framework/CHIP/{MTRDiagnosticLogsTransferHandler.mm => MTRDiagnosticsLogTransferHandler.mm} (80%) diff --git a/examples/all-clusters-app/linux/AppOptions.cpp b/examples/all-clusters-app/linux/AppOptions.cpp index 0345945ba1a0ab..4fc4e4ddb397ff 100644 --- a/examples/all-clusters-app/linux/AppOptions.cpp +++ b/examples/all-clusters-app/linux/AppOptions.cpp @@ -22,6 +22,7 @@ #include #include +using namespace chip; using namespace chip::ArgParser; using namespace chip::app::Clusters::DiagnosticLogs; @@ -37,9 +38,14 @@ constexpr uint16_t kOptionCrashFilePath = 0xFF05; static chip::Credentials::Examples::TestHarnessDACProvider mDacProvider; -static char mEndUserSupportLogFileDesignator[kLogFileDesignatorMaxLen]; -static char mNetworkDiagnosticsLogFileDesignator[kLogFileDesignatorMaxLen]; -static char mCrashLogFileDesignator[kLogFileDesignatorMaxLen]; +chip::Optional mEndUserSupportLogFilePath; +chip::Optional mNetworkDiagnosticsLogFilePath; +chip::Optional mCrashLogFilePath; + +bool AppOptions::IsNullOrEmpty(const char * value) +{ + return (value == nullptr || strlen(value) == 0 || strncmp(value, "", strlen(value)) == 0); +} bool AppOptions::HandleOptions(const char * program, OptionSet * options, int identifier, const char * name, const char * value) { @@ -55,30 +61,24 @@ bool AppOptions::HandleOptions(const char * program, OptionSet * options, int id break; } case kOptionEndUserSupportFilePath: { - if (strlen(value) > kLogFileDesignatorMaxLen) + if (!IsNullOrEmpty(value)) { - PrintArgError("%s: Invalid file path length. Must be less that %d: %d\n", program, kLogFileDesignatorMaxLen, - strlen(value)); + mEndUserSupportLogFilePath.SetValue(std::string{ value }); } - strncpy(mEndUserSupportLogFileDesignator, value, strlen(value)); break; } case kOptionNetworkDiagnosticsFilePath: { - if (strlen(value) > kLogFileDesignatorMaxLen) + if (!IsNullOrEmpty(value)) { - PrintArgError("%s: Invalid file path length. Must be less that %d: %d\n", program, kLogFileDesignatorMaxLen, - strlen(value)); + mNetworkDiagnosticsLogFilePath.SetValue(std::string{ value }); } - strncpy(mNetworkDiagnosticsLogFileDesignator, value, strlen(value)); break; } case kOptionCrashFilePath: { - if (strlen(value) > kLogFileDesignatorMaxLen) + if (!IsNullOrEmpty(value)) { - PrintArgError("%s: Invalid file path length. Must be less that %d: %d\n", program, kLogFileDesignatorMaxLen, - strlen(value)); + mCrashLogFilePath.SetValue(std::string{ value }); } - strncpy(mCrashLogFileDesignator, value, strlen(value)); break; } default: @@ -123,17 +123,17 @@ chip::Credentials::DeviceAttestationCredentialsProvider * AppOptions::GetDACProv return &mDacProvider; } -char * AppOptions::GetEndUserSupportLogFileDesignator() +Optional AppOptions::GetEndUserSupportLogFilePath() { - return mEndUserSupportLogFileDesignator; + return mEndUserSupportLogFilePath; } -char * AppOptions::GetNetworkDiagnosticsLogFileDesignator() +Optional AppOptions::GetNetworkDiagnosticsLogFilePath() { - return mNetworkDiagnosticsLogFileDesignator; + return mNetworkDiagnosticsLogFilePath; } -char * AppOptions::GetCrashLogFileDesignator() +Optional AppOptions::GetCrashLogFilePath() { - return mCrashLogFileDesignator; + return mCrashLogFilePath; } diff --git a/examples/all-clusters-app/linux/AppOptions.h b/examples/all-clusters-app/linux/AppOptions.h index 8a0fb4d3fba965..760b3bcfd702f2 100644 --- a/examples/all-clusters-app/linux/AppOptions.h +++ b/examples/all-clusters-app/linux/AppOptions.h @@ -1,6 +1,6 @@ /* * - * Copyright (c) 2022 Project CHIP Authors + * Copyright (c) 2022-2023 Project CHIP Authors * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -27,11 +27,13 @@ class AppOptions public: static chip::ArgParser::OptionSet * GetOptions(); static chip::Credentials::DeviceAttestationCredentialsProvider * GetDACProvider(); - static char * GetEndUserSupportLogFileDesignator(); - static char * GetNetworkDiagnosticsLogFileDesignator(); - static char * GetCrashLogFileDesignator(); + static chip::Optional GetEndUserSupportLogFilePath(); + static chip::Optional GetNetworkDiagnosticsLogFilePath(); + static chip::Optional GetCrashLogFilePath(); private: static bool HandleOptions(const char * program, chip::ArgParser::OptionSet * options, int identifier, const char * name, const char * value); + + static bool IsNullOrEmpty(const char * value); }; diff --git a/examples/all-clusters-app/linux/BUILD.gn b/examples/all-clusters-app/linux/BUILD.gn index 1c0f1af6ab790b..e62b038a4840b1 100644 --- a/examples/all-clusters-app/linux/BUILD.gn +++ b/examples/all-clusters-app/linux/BUILD.gn @@ -25,7 +25,6 @@ source_set("chip-all-clusters-common") { "${chip_root}/examples/all-clusters-app/all-clusters-common/src/binding-handler.cpp", "${chip_root}/examples/all-clusters-app/all-clusters-common/src/bridged-actions-stub.cpp", "${chip_root}/examples/all-clusters-app/all-clusters-common/src/concentration-measurement-instances.cpp", - "${chip_root}/examples/all-clusters-app/linux/diagnostic-logs-provider-delegate-impl.cpp", "${chip_root}/examples/all-clusters-app/all-clusters-common/src/dishwasher-alarm-stub.cpp", "${chip_root}/examples/all-clusters-app/all-clusters-common/src/dishwasher-mode.cpp", "${chip_root}/examples/all-clusters-app/all-clusters-common/src/fan-stub.cpp", @@ -38,6 +37,7 @@ source_set("chip-all-clusters-common") { "${chip_root}/examples/all-clusters-app/all-clusters-common/src/static-supported-modes-manager.cpp", "${chip_root}/examples/all-clusters-app/all-clusters-common/src/static-supported-temperature-levels.cpp", "${chip_root}/examples/all-clusters-app/all-clusters-common/src/tcc-mode.cpp", + "${chip_root}/examples/all-clusters-app/linux/diagnostic-logs-provider-delegate-impl.cpp", "AllClustersCommandDelegate.cpp", "AppOptions.cpp", "WindowCoveringManager.cpp", diff --git a/examples/all-clusters-app/linux/diagnostic-logs-provider-delegate-impl.cpp b/examples/all-clusters-app/linux/diagnostic-logs-provider-delegate-impl.cpp index 89c498f430809a..722d14c40873e6 100644 --- a/examples/all-clusters-app/linux/diagnostic-logs-provider-delegate-impl.cpp +++ b/examples/all-clusters-app/linux/diagnostic-logs-provider-delegate-impl.cpp @@ -22,28 +22,42 @@ using namespace chip; using namespace chip::app::Clusters::DiagnosticLogs; -constexpr uint16_t kChunkSizeZero = 0; - LogProvider LogProvider::sInstance; LogSessionHandle LogProvider::sLogSessionHandle; +// TODO: Fix #30477 - Add support for multiple log collection sessions + LogSessionHandle LogProvider::StartLogCollection(IntentEnum logType) { + // We can handle only one log collection session. Return kInvalidLogSessionHandle + // if we are in the middle of another log collection session. + if (mIsInALogCollectionSession) + { + return kInvalidLogSessionHandle; + } + + mIsInALogCollectionSession = true; mTotalNumberOfBytesConsumed = 0; - // Open the file of type - const char * fileName = GetLogFilePath(logType); - if (fileName != nullptr) + // Open the log file for reading. + Optional filePath = GetLogFilePath(logType); + if (filePath.HasValue()) { - mFileStream.open(fileName, std::ios_base::binary | std::ios_base::in); + mFileStream.open(filePath.Value().c_str(), std::ios_base::binary | std::ios_base::in); if (!mFileStream.good()) { ChipLogError(BDX, "Failed to open the log file"); return kInvalidLogSessionHandle; } sLogSessionHandle++; + + // If the session handle rolls over to UINT16_MAX which is invalid, reset to 0. + if (sLogSessionHandle == kInvalidLogSessionHandle) + { + sLogSessionHandle = 0; + } mLogSessionHandle = sLogSessionHandle; } else @@ -53,79 +67,89 @@ LogSessionHandle LogProvider::StartLogCollection(IntentEnum logType) return mLogSessionHandle; } -uint64_t LogProvider::GetNextChunk(LogSessionHandle logSessionHandle, chip::MutableByteSpan & outBuffer, bool & outIsEOF) +CHIP_ERROR LogProvider::GetNextChunk(LogSessionHandle logSessionHandle, MutableByteSpan & outBuffer, bool & outIsEOF) { - if (logSessionHandle != mLogSessionHandle && outBuffer.size() == 0) + if (!mIsInALogCollectionSession) { - return kChunkSizeZero; + return CHIP_ERROR_INCORRECT_STATE; } if (!mFileStream.is_open()) { ChipLogError(BDX, "File is not open"); - return kChunkSizeZero; + return CHIP_ERROR_INCORRECT_STATE; + } + + mFileStream.seekg(mFileStream.end); + long long fileSize = mFileStream.tellg(); + + long long remainingBytesToBeRead = fileSize - static_cast(mTotalNumberOfBytesConsumed); + + if ((remainingBytesToBeRead >= kMaxLogContentSize && outBuffer.size() < kMaxLogContentSize) || + (remainingBytesToBeRead < kMaxLogContentSize && static_cast(outBuffer.size()) < remainingBytesToBeRead)) + { + ChipLogError(BDX, "Buffer is too small to read the next chunk from file"); + return CHIP_ERROR_INVALID_ARGUMENT; } mFileStream.seekg(static_cast(mTotalNumberOfBytesConsumed)); - mFileStream.read(reinterpret_cast(outBuffer.data()), kLogContentMaxSize); + mFileStream.read(reinterpret_cast(outBuffer.data()), kMaxLogContentSize); if (!(mFileStream.good() || mFileStream.eof())) { ChipLogError(BDX, "Failed to read the log file"); mFileStream.close(); - return kChunkSizeZero; + return CHIP_ERROR_INCORRECT_STATE; } uint64_t bytesRead = static_cast(mFileStream.gcount()); - outIsEOF = (mFileStream.peek() == EOF); + outBuffer.reduce_size(bytesRead); + outIsEOF = (mFileStream.peek() == EOF); mTotalNumberOfBytesConsumed += bytesRead; - return bytesRead; + return CHIP_NO_ERROR; } void LogProvider::EndLogCollection(LogSessionHandle logSessionHandle) { - if (logSessionHandle == mLogSessionHandle && mFileStream.is_open()) + if (mFileStream.is_open()) { mFileStream.close(); } + mIsInALogCollectionSession = false; } uint64_t LogProvider::GetTotalNumberOfBytesConsumed(LogSessionHandle logSessionHandle) { - if (logSessionHandle == mLogSessionHandle) - { - return mTotalNumberOfBytesConsumed; - } - return kChunkSizeZero; + return mTotalNumberOfBytesConsumed; } -const char * LogProvider::GetLogFilePath(IntentEnum logType) +Optional LogProvider::GetLogFilePath(IntentEnum logType) { switch (logType) { case IntentEnum::kEndUserSupport: - return mEndUserSupportLogFileDesignator; + return mEndUserSupportLogFilePath; case IntentEnum::kNetworkDiag: - return mNetworkDiagnosticsLogFileDesignator; + return mNetworkDiagnosticsLogFilePath; case IntentEnum::kCrashLogs: - return mCrashLogFileDesignator; + return mCrashLogFilePath; default: - return nullptr; + return NullOptional; } } -void LogProvider::SetEndUserSupportLogFileDesignator(const char * logFileName) +void LogProvider::SetEndUserSupportLogFilePath(Optional logFilePath) { - strncpy(mEndUserSupportLogFileDesignator, logFileName, strlen(logFileName)); + mEndUserSupportLogFilePath = logFilePath; } -void LogProvider::SetNetworkDiagnosticsLogFileDesignator(const char * logFileName) +void LogProvider::SetNetworkDiagnosticsLogFilePath(Optional logFilePath) { - strncpy(mNetworkDiagnosticsLogFileDesignator, logFileName, strlen(logFileName)); + mNetworkDiagnosticsLogFilePath = logFilePath; } -void LogProvider::SetCrashLogFileDesignator(const char * logFileName) +void LogProvider::SetCrashLogFilePath(Optional logFilePath) { - strncpy(mCrashLogFileDesignator, logFileName, strlen(logFileName)); + mCrashLogFilePath = logFilePath; } diff --git a/examples/all-clusters-app/linux/include/diagnostic-logs-provider-delegate-impl.h b/examples/all-clusters-app/linux/include/diagnostic-logs-provider-delegate-impl.h index 32b001ce522825..1af25363c3f862 100644 --- a/examples/all-clusters-app/linux/include/diagnostic-logs-provider-delegate-impl.h +++ b/examples/all-clusters-app/linux/include/diagnostic-logs-provider-delegate-impl.h @@ -40,35 +40,37 @@ class LogProvider : public LogProviderDelegate public: LogSessionHandle StartLogCollection(IntentEnum logType); - uint64_t GetNextChunk(LogSessionHandle logSessionHandle, chip::MutableByteSpan & outBuffer, bool & outIsEOF); + CHIP_ERROR GetNextChunk(LogSessionHandle logSessionHandle, MutableByteSpan & outBuffer, bool & outIsEOF); void EndLogCollection(LogSessionHandle logSessionHandle); uint64_t GetTotalNumberOfBytesConsumed(LogSessionHandle logSessionHandle); - void SetEndUserSupportLogFileDesignator(const char * logFileName); + void SetEndUserSupportLogFilePath(Optional logFilePath); - void SetNetworkDiagnosticsLogFileDesignator(const char * logFileName); + void SetNetworkDiagnosticsLogFilePath(Optional logFilePath); - void SetCrashLogFileDesignator(const char * logFileName); + void SetCrashLogFilePath(Optional logFilePath); LogProvider(){}; ~LogProvider(){}; - static inline LogProvider & GetInstance() { return sInstance; } + static inline LogProvider & getLogProvider() { return sInstance; } private: - const char * GetLogFilePath(IntentEnum logType); + Optional GetLogFilePath(IntentEnum logType); - char mEndUserSupportLogFileDesignator[kLogFileDesignatorMaxLen]; - char mNetworkDiagnosticsLogFileDesignator[kLogFileDesignatorMaxLen]; - char mCrashLogFileDesignator[kLogFileDesignatorMaxLen]; + Optional mEndUserSupportLogFilePath; + Optional mNetworkDiagnosticsLogFilePath; + Optional mCrashLogFilePath; std::ifstream mFileStream; LogSessionHandle mLogSessionHandle; + bool mIsInALogCollectionSession; + uint64_t mTotalNumberOfBytesConsumed; }; diff --git a/examples/all-clusters-app/linux/main-common.cpp b/examples/all-clusters-app/linux/main-common.cpp index 262c6867593a84..ce83714851f1b9 100644 --- a/examples/all-clusters-app/linux/main-common.cpp +++ b/examples/all-clusters-app/linux/main-common.cpp @@ -20,8 +20,8 @@ #include "AppOptions.h" #include "WindowCoveringManager.h" #include "air-quality-instance.h" -#include "include/diagnostic-logs-provider-delegate-impl.h" #include "dishwasher-mode.h" +#include "include/diagnostic-logs-provider-delegate-impl.h" #include "include/tv-callbacks.h" #include "laundry-washer-controls-delegate-impl.h" #include "laundry-washer-mode.h" @@ -265,9 +265,10 @@ void emberAfWindowCoveringClusterInitCallback(chip::EndpointId endpoint) using namespace chip::app::Clusters::DiagnosticLogs; void emberAfDiagnosticLogsClusterInitCallback(chip::EndpointId endpoint) { - ChipLogProgress(NotSpecified, "SetDefaultLogProviderDelegate"); - DiagnosticLogsServer::Instance().SetDefaultLogProviderDelegate(endpoint, &LogProvider::getLogProvider()); - LogProvider::getLogProvider().SetEndUserSupportLogFileDesignator(AppOptions::GetEndUserSupportLogFileDesignator()); - LogProvider::getLogProvider().SetNetworkDiagnosticsLogFileDesignator(AppOptions::GetNetworkDiagnosticsLogFileDesignator()); - LogProvider::getLogProvider().SetCrashLogFileDesignator(AppOptions::GetCrashLogFileDesignator()); + ChipLogProgress(NotSpecified, "SetLogProviderDelegate"); + DiagnosticLogsServer::Instance().SetLogProviderDelegate(endpoint, &LogProvider::getLogProvider()); + + LogProvider::getLogProvider().SetEndUserSupportLogFilePath(AppOptions::GetEndUserSupportLogFilePath()); + LogProvider::getLogProvider().SetNetworkDiagnosticsLogFilePath(AppOptions::GetNetworkDiagnosticsLogFilePath()); + LogProvider::getLogProvider().SetCrashLogFilePath(AppOptions::GetCrashLogFilePath()); } diff --git a/src/app/bdx/DiagnosticLogsBDXTransferHandler.cpp b/src/app/bdx/DiagnosticLogsBDXTransferHandler.cpp index e2e365b55c7460..b816a68da7ab07 100644 --- a/src/app/bdx/DiagnosticLogsBDXTransferHandler.cpp +++ b/src/app/bdx/DiagnosticLogsBDXTransferHandler.cpp @@ -1,6 +1,6 @@ /* * - * Copyright (c) 2021 Project CHIP Authors + * Copyright (c) 2023 Project CHIP Authors * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -24,60 +24,68 @@ #include #include -using chip::BitFlags; -using chip::ByteSpan; -using chip::CharSpan; -using chip::FabricIndex; -using chip::FabricInfo; -using chip::MutableCharSpan; -using chip::NodeId; -using chip::Span; -using chip::bdx::TransferControlFlags; -using chip::Protocols::InteractionModel::Status; - using namespace chip; -using namespace chip::app; -using namespace chip::bdx; -using namespace chip::app::Clusters::DiagnosticLogs; +using namespace app; +using namespace bdx; +using namespace app::Clusters::DiagnosticLogs; -// BDX Transfer Params +// BDX transfer session polling interval. constexpr System::Clock::Timeout kBdxPollIntervalMs = System::Clock::Milliseconds32(50); -// Timeout for the BDX transfer session. The OTA Spec mandates this should be >= 5 minutes. +// Timeout for the BDX transfer session constexpr System::Clock::Timeout kBdxTimeout = System::Clock::Seconds16(5 * 60); constexpr uint16_t kBdxMaxBlockSize = 1024; -CHIP_ERROR DiagnosticLogsBDXTransferHandler::InitializeTransfer(chip::Messaging::ExchangeContext * exchangeCtx, - FabricIndex fabricIndex, NodeId nodeId, - LogProviderDelegate * delegate, IntentEnum intent, - CharSpan fileDesignator) +StatusCode GetBdxStatusCodeFromChipError(CHIP_ERROR err) +{ + if (err == CHIP_ERROR_INCORRECT_STATE) + { + return StatusCode::kUnexpectedMessage; + } + if (err == CHIP_ERROR_INVALID_ARGUMENT) + { + return StatusCode::kBadMessageContents; + } + return StatusCode::kUnknown; +} + +CHIP_ERROR DiagnosticLogsBDXTransferHandler::InitializeTransfer(Messaging::ExchangeManager * exchangeMgr, + const SessionHandle sessionHandle, FabricIndex fabricIndex, + NodeId peerNodeId, LogProviderDelegate * delegate, + IntentEnum intent, CharSpan fileDesignator) { if (mInitialized) { + // Return busy if we are in a transfer session already with another node. + VerifyOrReturnError(mFabricIndex.Value() == fabricIndex && mPeerNodeId.Value() == peerNodeId, CHIP_ERROR_BUSY); + // Reset stale connection from the same Node if exists. Reset(); } - VerifyOrReturnError(exchangeCtx != nullptr, CHIP_ERROR_INCORRECT_STATE); - VerifyOrReturnError(delegate != nullptr, CHIP_ERROR_INCORRECT_STATE); + VerifyOrReturnError(exchangeMgr != nullptr, CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrReturnError(delegate != nullptr, CHIP_ERROR_INVALID_ARGUMENT); - mExchangeCtx = exchangeCtx->GetExchangeMgr()->NewContext(exchangeCtx->GetSessionHandle(), this); - VerifyOrReturnError(mExchangeCtx != nullptr, CHIP_ERROR_NO_MEMORY); + // Create a new exchange context to use for the BDX transfer session + // and send the SendInit message over the exchange. + mBDXTransferExchangeCtx = exchangeMgr->NewContext(sessionHandle, this); + VerifyOrReturnError(mBDXTransferExchangeCtx != nullptr, CHIP_ERROR_NO_MEMORY); mIntent = intent; mDelegate = delegate; mFabricIndex.SetValue(fabricIndex); - mNodeId.SetValue(nodeId); - mNumBytesSent = 0; + mPeerNodeId.SetValue(peerNodeId); + mNumBytesSent = 0; + mIsAcceptReceived = false; TransferSession::TransferInitData initOptions; - initOptions.TransferCtlFlags = bdx::TransferControlFlags::kSenderDrive; + initOptions.TransferCtlFlags = TransferControlFlags::kSenderDrive; initOptions.MaxBlockSize = kBdxMaxBlockSize; initOptions.FileDesLength = static_cast(fileDesignator.size()); - initOptions.FileDesignator = reinterpret_cast(fileDesignator.data()); + initOptions.FileDesignator = Uint8::from_const_char(fileDesignator.data()); - CHIP_ERROR err = Initiator::InitiateTransfer(&DeviceLayer::SystemLayer(), bdx::TransferRole::kSender, initOptions, kBdxTimeout, + CHIP_ERROR err = Initiator::InitiateTransfer(&DeviceLayer::SystemLayer(), TransferRole::kSender, initOptions, kBdxTimeout, kBdxPollIntervalMs); if (err != CHIP_NO_ERROR) { @@ -89,6 +97,23 @@ CHIP_ERROR DiagnosticLogsBDXTransferHandler::InitializeTransfer(chip::Messaging: return CHIP_NO_ERROR; } +void DiagnosticLogsBDXTransferHandler::HandleBDXError(CHIP_ERROR error) +{ + VerifyOrReturn(error != CHIP_NO_ERROR); + LogErrorOnFailure(error); + + // If Send Accept was not received, send a response with status Denied to the RetreiveLogRequest + if (!mIsAcceptReceived) + { + DiagnosticLogsServer::Instance().SendCommandResponse(StatusEnum::kDenied); + } + Reset(); + DeviceLayer::SystemLayer().ScheduleLambda([this] { + delete this; + DiagnosticLogsServer::Instance().HandleBDXTransferDone(); + }); +} + void DiagnosticLogsBDXTransferHandler::HandleTransferSessionOutput(TransferSession::OutputEvent & event) { CHIP_ERROR err = CHIP_NO_ERROR; @@ -100,63 +125,59 @@ void DiagnosticLogsBDXTransferHandler::HandleTransferSessionOutput(TransferSessi switch (event.EventType) { case TransferSession::OutputEventType::kAckEOFReceived: - mStopPolling = true; Reset(); + DeviceLayer::SystemLayer().ScheduleLambda([this] { + delete this; + DiagnosticLogsServer::Instance().HandleBDXTransferDone(); + }); break; case TransferSession::OutputEventType::kStatusReceived: ChipLogError(BDX, "Got StatusReport %x", to_underlying(event.statusData.statusCode)); - DiagnosticLogsServer::Instance().HandleBDXResponse(CHIP_ERROR_INTERNAL); - Reset(); - break; case TransferSession::OutputEventType::kInternalError: - DiagnosticLogsServer::Instance().HandleBDXResponse(CHIP_ERROR_INTERNAL); - Reset(); - break; case TransferSession::OutputEventType::kTransferTimeout: - DiagnosticLogsServer::Instance().HandleBDXResponse(CHIP_ERROR_TIMEOUT); - Reset(); + HandleBDXError(CHIP_ERROR_INTERNAL); break; case TransferSession::OutputEventType::kMsgToSend: { Messaging::SendFlags sendFlags; - if (!event.msgTypeData.HasMessageType(chip::Protocols::SecureChannel::MsgType::StatusReport)) + if (!event.msgTypeData.HasMessageType(Protocols::SecureChannel::MsgType::StatusReport)) { // All messages sent from the Sender expect a response, except for a StatusReport which would indicate an error and the // end of the transfer. - sendFlags.Set(chip::Messaging::SendMessageFlags::kExpectResponse); + sendFlags.Set(Messaging::SendMessageFlags::kExpectResponse); } - VerifyOrReturn(mExchangeCtx != nullptr); - err = mExchangeCtx->SendMessage(event.msgTypeData.ProtocolId, event.msgTypeData.MessageType, std::move(event.MsgData), - sendFlags); + VerifyOrReturn(mBDXTransferExchangeCtx != nullptr); + err = mBDXTransferExchangeCtx->SendMessage(event.msgTypeData.ProtocolId, event.msgTypeData.MessageType, + std::move(event.MsgData), sendFlags); if (err == CHIP_NO_ERROR) { - if (!sendFlags.Has(chip::Messaging::SendMessageFlags::kExpectResponse)) + if (!sendFlags.Has(Messaging::SendMessageFlags::kExpectResponse)) { - // After sending the StatusReport, exchange context gets closed so, set mExchangeCtx to null - mExchangeCtx = nullptr; + // After sending the StatusReport, exchange context gets closed so, set mBDXTransferExchangeCtx to null + mBDXTransferExchangeCtx = nullptr; } } else { ChipLogError(BDX, "SendMessage failed: %" CHIP_ERROR_FORMAT, err.Format()); - DiagnosticLogsServer::Instance().HandleBDXResponse(err); - Reset(); + mTransfer.AbortTransfer(GetBdxStatusCodeFromChipError(err)); } break; } case TransferSession::OutputEventType::kAcceptReceived: { + mIsAcceptReceived = true; mLogSessionHandle = mDelegate->StartLogCollection(mIntent); - if (mLogSessionHandle == kInvalidLogSessionHandle) { ChipLogError(BDX, "No log available for intent enum %d", to_underlying(mIntent)); - DiagnosticLogsServer::Instance().HandleBDXResponse(CHIP_ERROR_INCORRECT_STATE); - mTransfer.AbortTransfer(StatusCode::kUnknown); + DiagnosticLogsServer::Instance().SendCommandResponse(StatusEnum::kDenied); + mTransfer.AbortTransfer(GetBdxStatusCodeFromChipError(CHIP_ERROR_INCORRECT_STATE)); return; } - // Send a response to the RetreiveLogRequest, since we got a SendAccept message. - DiagnosticLogsServer::Instance().HandleBDXResponse(CHIP_NO_ERROR); + // Send a response with status Success to the RetreiveLogRequest, since we got a SendAccept message. + DiagnosticLogsServer::Instance().SendCommandResponse(StatusEnum::kSuccess); + [[fallthrough]]; } case TransferSession::OutputEventType::kAckReceived: { @@ -169,10 +190,10 @@ void DiagnosticLogsBDXTransferHandler::HandleTransferSessionOutput(TransferSessi bytesToRead = static_cast(mTransfer.GetTransferLength() - mNumBytesSent); } - chip::System::PacketBufferHandle blockBuf = chip::System::PacketBufferHandle::New(bytesToRead); + System::PacketBufferHandle blockBuf = System::PacketBufferHandle::New(bytesToRead); if (blockBuf.IsNull()) { - mTransfer.AbortTransfer(StatusCode::kUnknown); + mTransfer.AbortTransfer(GetBdxStatusCodeFromChipError(CHIP_ERROR_NO_MEMORY)); return; } @@ -183,23 +204,25 @@ void DiagnosticLogsBDXTransferHandler::HandleTransferSessionOutput(TransferSessi bool isEOF = false; // Get the log next chunk and see if it fits i.e. if EOF is reported - uint64_t bytesRead = mDelegate->GetNextChunk(mLogSessionHandle, buffer, isEOF); + err = mDelegate->GetNextChunk(mLogSessionHandle, buffer, isEOF); - if (bytesRead == 0) + if (err != CHIP_NO_ERROR) { - mTransfer.AbortTransfer(StatusCode::kUnknown); + mTransfer.AbortTransfer(GetBdxStatusCodeFromChipError(err)); return; } + // If EOF is received, end the log collection session. if (isEOF) { mDelegate->EndLogCollection(mLogSessionHandle); mLogSessionHandle = kInvalidLogSessionHandle; } + // Prepare the BDX block to send to the requestor TransferSession::BlockData blockData; blockData.Data = blockBuf->Start(); - blockData.Length = static_cast(bytesRead); + blockData.Length = static_cast(buffer.size()); blockData.IsEof = isEOF; mNumBytesSent += static_cast(blockData.Length); @@ -207,7 +230,7 @@ void DiagnosticLogsBDXTransferHandler::HandleTransferSessionOutput(TransferSessi if (err != CHIP_NO_ERROR) { ChipLogError(BDX, "PrepareBlock failed: %" CHIP_ERROR_FORMAT, err.Format()); - mTransfer.AbortTransfer(StatusCode::kUnknown); + mTransfer.AbortTransfer(GetBdxStatusCodeFromChipError(err)); } break; } @@ -227,14 +250,14 @@ void DiagnosticLogsBDXTransferHandler::Reset() assertChipStackLockedByCurrentThread(); mFabricIndex.ClearValue(); - mNodeId.ClearValue(); + mPeerNodeId.ClearValue(); Initiator::ResetTransfer(); - if (mExchangeCtx != nullptr) + if (mBDXTransferExchangeCtx != nullptr) { - mExchangeCtx->Close(); - mExchangeCtx = nullptr; + mBDXTransferExchangeCtx->Close(); + mBDXTransferExchangeCtx = nullptr; } if (mDelegate != nullptr) @@ -245,6 +268,7 @@ void DiagnosticLogsBDXTransferHandler::Reset() mLogSessionHandle = kInvalidLogSessionHandle; mInitialized = false; mNumBytesSent = 0; + mIsAcceptReceived = false; } #endif diff --git a/src/app/bdx/DiagnosticLogsBDXTransferHandler.h b/src/app/bdx/DiagnosticLogsBDXTransferHandler.h index 55a6ed77741df3..c5aee688625818 100644 --- a/src/app/bdx/DiagnosticLogsBDXTransferHandler.h +++ b/src/app/bdx/DiagnosticLogsBDXTransferHandler.h @@ -25,41 +25,71 @@ #include #include +#include namespace chip { namespace app { namespace Clusters { namespace DiagnosticLogs { + /** - * The BDX transfer handler than initiates a BDX transfer session as a Sender using the synchronous Sender Drive - * transfer mode. It gets the chunks of the log and sends the block accross to the receiver until + * The BDX transfer handler that initiates a BDX transfer session as a Sender using the synchronous Sender Drive + * transfer mode. It gets the chunks of the log and sends the block across to the receiver until * all the blocks have been transferred and the delegate reports that end of file is reached. */ -class DiagnosticLogsBDXTransferHandler : public chip::bdx::Initiator +class DiagnosticLogsBDXTransferHandler : public bdx::Initiator { public: DiagnosticLogsBDXTransferHandler(){}; ~DiagnosticLogsBDXTransferHandler(){}; - CHIP_ERROR Init(); - - CHIP_ERROR InitializeTransfer(chip::Messaging::ExchangeContext * exchangeCtx, chip::FabricIndex fabricIndex, - chip::NodeId nodeId, LogProviderDelegate * delegate, IntentEnum intent, - chip::CharSpan fileDesignator); - - void HandleTransferSessionOutput(chip::bdx::TransferSession::OutputEvent & event) override; + /** + * Intializes the BDX transfer session by creating a new exchange context for the transfer session. + * It starts the BDX transfer session by calling InitiateTransfer of the which sends the SendInit BDX message + * to the log requestor. + * + * @param[in] exchangeMgr The exchange manager from the command handler object for the RetreiveLogRequest command + * @param[in] sessionHandle The session handle from the command handler object for the RetreiveLogRequest command + * @param[in] fabricIndex The fabric index of the requestor + * @param[in] peerNodeId The node id of the requestor + * @param[in] delegate The log provider delegate that will provide the log chunks + * @param[in] intent The log type requested + * @param[in] fileDesignator The file designator to use for the BDX transfer + */ + CHIP_ERROR InitializeTransfer(Messaging::ExchangeManager * exchangeMgr, const SessionHandle sessionHandle, + FabricIndex fabricIndex, NodeId peerNodeId, LogProviderDelegate * delegate, IntentEnum intent, + CharSpan fileDesignator); + + /** + * This method handles BDX messages and other TransferSession events. + * + * @param[in] event An OutputEvent that contains output from the TransferSession object. + */ + void HandleTransferSessionOutput(bdx::TransferSession::OutputEvent & event) override; +private: + /** + * This method is called to reset state. It resets the transfer, cleans up the + * exchange and ends log collection. + */ void Reset(); -private: - void SendNextBlock(MutableByteSpan & buffer); + /** + * This method handles any error that might occur during the BDX session. + * If the error occurs before SendAccept is received, it sends a command response + * with appropriate status to the requestor. After that, it resets state and + * deletes itself. + * + * @param[in] error The error that occured during the BDX session + */ + void HandleBDXError(CHIP_ERROR error); - chip::Optional mFabricIndex; - chip::Optional mNodeId; + Optional mFabricIndex; + Optional mPeerNodeId; - chip::Messaging::ExchangeContext * mExchangeCtx; + Messaging::ExchangeContext * mBDXTransferExchangeCtx; - bool mInitialized; + bool mInitialized = false; uint64_t mNumBytesSent; @@ -68,6 +98,8 @@ class DiagnosticLogsBDXTransferHandler : public chip::bdx::Initiator LogProviderDelegate * mDelegate; IntentEnum mIntent; + + bool mIsAcceptReceived = false; }; } // namespace DiagnosticLogs diff --git a/src/app/clusters/diagnostic-logs-server/diagnostic-logs-provider-delegate.h b/src/app/clusters/diagnostic-logs-server/diagnostic-logs-provider-delegate.h index 99905874a379a8..3bf325c4d6cfed 100644 --- a/src/app/clusters/diagnostic-logs-server/diagnostic-logs-provider-delegate.h +++ b/src/app/clusters/diagnostic-logs-server/diagnostic-logs-provider-delegate.h @@ -29,8 +29,8 @@ namespace DiagnosticLogs { typedef uint16_t LogSessionHandle; -// The value 0xFF will be used as an invalid log session handle and must not be used as a valid value for LogSessionHandle -constexpr uint8_t kInvalidLogSessionHandle = 0xFF; +// The value UINT16_MAX will be used as an invalid log session handle and must not be used as a valid value for LogSessionHandle +constexpr LogSessionHandle kInvalidLogSessionHandle = UINT16_MAX; /** @brief * Defines methods for implementing application-specific logic for getting the log data from the diagnostic logs provider @@ -49,19 +49,21 @@ class LogProviderDelegate * @param[in] logType The type of log for which the start of log collection is requested. * * @return LogSessionHandle The unique log session handle that identifies the log collection session that has been started. + * Returns kInvalidLogSessionHandle if there are no logs of the logType. */ virtual LogSessionHandle StartLogCollection(IntentEnum logType) = 0; /** * Called to get the next chunk for the log session identified by logSessionHandle. - * Should return the number of bytes read and indicate if EOF has been reached. + * Should return CHIP_NO_ERROR if we were able to read successfully from the file into the buffer, otherwise + * an appropriate error code is returned. * * @param[in] logSessionHandle The unique handle for this log session returned from a call to StartLogCollection. * @param[out] outBuffer The buffer thats passed in by the caller to write to. * @param[in] bufferLen The size of the buffer passed in. * @param[out] outIsEOF Set to true if EOF is reached otherwise set to false. */ - virtual uint64_t GetNextChunk(LogSessionHandle logSessionHandle, chip::MutableByteSpan & outBuffer, bool & outIsEOF) = 0; + virtual CHIP_ERROR GetNextChunk(LogSessionHandle logSessionHandle, chip::MutableByteSpan & outBuffer, bool & outIsEOF) = 0; /** * Called to end log collection for the log session identified by logSessionHandle diff --git a/src/app/clusters/diagnostic-logs-server/diagnostic-logs-server.cpp b/src/app/clusters/diagnostic-logs-server/diagnostic-logs-server.cpp index ea12c2ec71a140..c8c812d68c0cab 100644 --- a/src/app/clusters/diagnostic-logs-server/diagnostic-logs-server.cpp +++ b/src/app/clusters/diagnostic-logs-server/diagnostic-logs-server.cpp @@ -27,6 +27,7 @@ #include #include #include +#include using namespace chip; using namespace chip::app; @@ -38,17 +39,13 @@ static constexpr size_t kDiagnosticLogsLogProviderDelegateTableSize = static_assert(kDiagnosticLogsLogProviderDelegateTableSize < kEmberInvalidEndpointIndex, "DiagnosticLogs: log provider delegate table size error"); -namespace chip { -namespace app { -namespace Clusters { -namespace DiagnosticLogs { +namespace { LogProviderDelegate * gLogProviderDelegateTable[kDiagnosticLogsLogProviderDelegateTableSize] = { nullptr }; LogProviderDelegate * GetLogProviderDelegate(EndpointId endpoint) { - uint16_t ep = - emberAfGetClusterServerEndpointIndex(endpoint, DiagnosticLogs::Id, EMBER_AF_DIAGNOSTIC_LOGS_CLUSTER_SERVER_ENDPOINT_COUNT); + uint16_t ep = emberAfGetClusterServerEndpointIndex(endpoint, Id, EMBER_AF_DIAGNOSTIC_LOGS_CLUSTER_SERVER_ENDPOINT_COUNT); return (ep >= kDiagnosticLogsLogProviderDelegateTableSize ? nullptr : gLogProviderDelegateTable[ep]); } @@ -62,14 +59,11 @@ bool IsLogProviderDelegateNull(LogProviderDelegate * logProviderDelegate, Endpoi return false; } -} // namespace DiagnosticLogs -} // namespace Clusters -} // namespace app -} // namespace chip +} // namespace DiagnosticLogsServer DiagnosticLogsServer::sInstance; -void DiagnosticLogsServer::SetDefaultLogProviderDelegate(EndpointId endpoint, LogProviderDelegate * logProviderDelegate) +void DiagnosticLogsServer::SetLogProviderDelegate(EndpointId endpoint, LogProviderDelegate * logProviderDelegate) { uint16_t ep = emberAfGetClusterServerEndpointIndex(endpoint, DiagnosticLogs::Id, EMBER_AF_DIAGNOSTIC_LOGS_CLUSTER_SERVER_ENDPOINT_COUNT); @@ -93,7 +87,7 @@ bool DiagnosticLogsServer::IsBDXProtocolRequested(TransferProtocolEnum requested bool DiagnosticLogsServer::HasValidFileDesignator(CharSpan transferFileDesignator) { - return (transferFileDesignator.size() > 0 && transferFileDesignator.size() <= kLogFileDesignatorMaxLen); + return (transferFileDesignator.size() <= kMaxFileDesignatorLen); } CHIP_ERROR DiagnosticLogsServer::HandleLogRequestForBDXProtocol(Messaging::ExchangeContext * exchangeCtx, EndpointId endpointId, @@ -102,43 +96,52 @@ CHIP_ERROR DiagnosticLogsServer::HandleLogRequestForBDXProtocol(Messaging::Excha VerifyOrReturnError(exchangeCtx != nullptr, CHIP_ERROR_INCORRECT_STATE); - mIntent = intent; + mIntent = intent; + auto scopedPeerNodeId = ScopedNodeId(); + if (exchangeCtx->HasSessionHandle()) + { + auto sessionHandle = exchangeCtx->GetSessionHandle(); + + if (sessionHandle->IsSecureSession()) + { + scopedPeerNodeId = sessionHandle->AsSecureSession()->GetPeer(); + } + } - auto scopedPeerNodeId = exchangeCtx->GetSessionHandle()->AsSecureSession()->GetPeer(); + LogProviderDelegate * logProviderDelegate = GetLogProviderDelegate(endpointId); - LogProviderDelegate * logProviderDelegate = DiagnosticLogs::GetLogProviderDelegate(endpointId); + VerifyOrReturnError(!(IsLogProviderDelegateNull(logProviderDelegate, endpointId)), CHIP_ERROR_INCORRECT_STATE); - VerifyOrReturnError(!(DiagnosticLogs::isLogProviderDelegateNull(logProviderDelegate, endpointId)), CHIP_ERROR_INCORRECT_STATE); + // If there is already an existing mDiagnosticLogsBDXTransferHandler, we will return a busy error. + if (mDiagnosticLogsBDXTransferHandler != nullptr) + { + return CHIP_ERROR_BUSY; + } + // TODO: Need to resolve #30539. The spec says we should check the log size to see if it fits in the response payload. + // If it fits, we send the content in the response and not initiate BDX. mDiagnosticLogsBDXTransferHandler = new DiagnosticLogsBDXTransferHandler(); CHIP_ERROR error = mDiagnosticLogsBDXTransferHandler->InitializeTransfer( - exchangeCtx, scopedPeerNodeId.GetFabricIndex(), scopedPeerNodeId.GetNodeId(), logProviderDelegate, intent, fileDesignator); + exchangeCtx->GetExchangeMgr(), exchangeCtx->GetSessionHandle(), scopedPeerNodeId.GetFabricIndex(), + scopedPeerNodeId.GetNodeId(), logProviderDelegate, intent, fileDesignator); + // TODO: Fix #30540 - If we fail to initialize a BDX session, we should call HandleLogRequestForResponsePayload. return error; } -void DiagnosticLogsServer::HandleBDXResponse(CHIP_ERROR error) +void DiagnosticLogsServer::SendCommandResponse(StatusEnum status) { - LogErrorOnFailure(error); - auto commandHandleRef = std::move(mAsyncCommandHandle); auto commandHandle = commandHandleRef.Get(); if (commandHandle == nullptr) { - ChipLogError(Zcl, "HandleBDXResponse - commandHandler is null"); + ChipLogError(Zcl, "SendCommandResponse - commandHandler is null"); return; } Commands::RetrieveLogsResponse::Type response; - if (error == CHIP_NO_ERROR) - { - response.status = StatusEnum::kSuccess; - commandHandle->AddResponse(mRequestPath, response); - } - else - { - SendErrorResponse(commandHandle, mRequestPath, StatusEnum::kNoLogs); - } + response.status = status; + commandHandle->AddResponse(mRequestPath, response); } void DiagnosticLogsServer::SetAsyncCommandHandleAndPath(CommandHandler * commandObj, const ConcreteCommandPath & commandPath) @@ -147,17 +150,6 @@ void DiagnosticLogsServer::SetAsyncCommandHandleAndPath(CommandHandler * command mRequestPath = commandPath; } -void DiagnosticLogsServer::SendErrorResponse(chip::app::CommandHandler * commandHandler, chip::app::ConcreteCommandPath path, - StatusEnum status) -{ - Commands::RetrieveLogsResponse::Type response; - if (commandHandler != nullptr) - { - response.status = status; - commandHandler->AddResponse(path, response); - } -} - #endif void DiagnosticLogsServer::HandleLogRequestForResponsePayload(CommandHandler * commandHandler, ConcreteCommandPath path, @@ -169,7 +161,7 @@ void DiagnosticLogsServer::HandleLogRequestForResponsePayload(CommandHandler * c EndpointId endpoint = path.mEndpointId; LogProviderDelegate * logProviderDelegate = GetLogProviderDelegate(endpoint); - if (isLogProviderDelegateNull(logProviderDelegate, endpoint)) + if (IsLogProviderDelegateNull(logProviderDelegate, endpoint)) { response.status = StatusEnum::kNoLogs; commandHandler->AddResponse(path, response); @@ -178,7 +170,7 @@ void DiagnosticLogsServer::HandleLogRequestForResponsePayload(CommandHandler * c Platform::ScopedMemoryBuffer buffer; - if (!buffer.Alloc(kLogContentMaxSize)) + if (!buffer.Alloc(kMaxLogContentSize)) { ChipLogError(Zcl, "buffer not allocated"); response.status = StatusEnum::kNoLogs; @@ -197,40 +189,25 @@ void DiagnosticLogsServer::HandleLogRequestForResponsePayload(CommandHandler * c MutableByteSpan mutableBuffer; - mutableBuffer = MutableByteSpan(buffer.Get(), kLogContentMaxSize); + mutableBuffer = MutableByteSpan(buffer.Get(), kMaxLogContentSize); bool isEOF = false; - // Get the log next chunk - uint64_t bytesRead = logProviderDelegate->GetNextChunk(mLogSessionHandle, mutableBuffer, isEOF); + // Get the log chunk of size kMaxLogContentSize to send in the response payload. + CHIP_ERROR err = logProviderDelegate->GetNextChunk(mLogSessionHandle, mutableBuffer, isEOF); - if (bytesRead == 0) + if (err != CHIP_NO_ERROR) { response.status = StatusEnum::kNoLogs; commandHandler->AddResponse(path, response); return; } - // log fits. Return in response commandData and call end of log collection - if (bytesRead > 0) - { - if (isEOF) - { - response.status = StatusEnum::kSuccess; - } - else - { - response.status = StatusEnum::kExhausted; - } - response.logContent = ByteSpan(mutableBuffer.data(), kLogContentMaxSize); - } - else - { - response.status = StatusEnum::kNoLogs; - } - logProviderDelegate->EndLogCollection(mLogSessionHandle); - + response.logContent = ByteSpan(mutableBuffer.data(), mutableBuffer.size()); + response.status = StatusEnum::kSuccess; commandHandler->AddResponse(path, response); + + logProviderDelegate->EndLogCollection(mLogSessionHandle); mLogSessionHandle = kInvalidLogSessionHandle; } @@ -243,6 +220,7 @@ static void HandleRetrieveLogRequest(CommandHandler * commandObj, const Concrete DiagnosticLogsServer::Instance().HandleLogRequestForResponsePayload(commandObj, commandPath, intent); } #if CHIP_CONFIG_ENABLE_BDX_LOG_TRANSFER + // TODO: Fix #30540 - If BDX is not supported, we should send whatever fits in the logContent of the ResponsePayload. else { Commands::RetrieveLogsResponse::Type response; @@ -250,8 +228,7 @@ static void HandleRetrieveLogRequest(CommandHandler * commandObj, const Concrete !DiagnosticLogsServer::Instance().HasValidFileDesignator(transferFileDesignator.Value())) { ChipLogError(Zcl, "HandleRetrieveLogRequest - fileDesignator not valid for BDX protocol"); - response.status = StatusEnum::kNoLogs; - commandObj->AddResponse(commandPath, response); + commandObj->AddStatus(commandPath, Protocols::InteractionModel::Status::InvalidCommand); return; } @@ -262,8 +239,8 @@ static void HandleRetrieveLogRequest(CommandHandler * commandObj, const Concrete if (err != CHIP_NO_ERROR) { LogErrorOnFailure(err); - response.status = StatusEnum::kNoLogs; - commandObj->AddResponse(commandPath, response); + // TODO: Fix #30540 - If a BDX session can't be started, we should send whatever fits in the logContent of the + // ResponsePayload. return; } DiagnosticLogsServer::Instance().SetAsyncCommandHandleAndPath(std::move(commandObj), std::move(commandPath)); diff --git a/src/app/clusters/diagnostic-logs-server/diagnostic-logs-server.h b/src/app/clusters/diagnostic-logs-server/diagnostic-logs-server.h index b122c83739f5dc..4dd2ef5c05e4a7 100644 --- a/src/app/clusters/diagnostic-logs-server/diagnostic-logs-server.h +++ b/src/app/clusters/diagnostic-logs-server/diagnostic-logs-server.h @@ -33,12 +33,10 @@ namespace Clusters { namespace DiagnosticLogs { // Spec mandated max file designator length -static constexpr uint8_t kLogFileDesignatorMaxLen = 32; - -static constexpr const uint16_t kDiagnosticLogsEndpoint = 0; +static constexpr uint8_t kMaxFileDesignatorLen = 32; // Spec mandated max size of the log content field in the Response paylod -static constexpr uint16_t kLogContentMaxSize = 1024; +static constexpr uint16_t kMaxLogContentSize = 1024; /// A reference implementation for DiagnosticLogs source. class DiagnosticLogsServer @@ -55,24 +53,57 @@ class DiagnosticLogsServer */ void SetLogProviderDelegate(EndpointId endpoint, LogProviderDelegate * delegate); - void HandleLogRequestForResponsePayload(chip::app::CommandHandler * commandHandler, chip::app::ConcreteCommandPath path, - IntentEnum intent); + /** + * Handles the request to download diagnostic logs of type specified in the intent argument for protocol type ResponsePayload + * This should return whatever fits in the logContent field of the RetrieveLogsResponse command + * + * @param commandHandler The command handler object from the RetrieveLogsRequest command + * + * @param path The command path from the RetrieveLogsRequest command + * + * @param intent The log type requested in the RetrieveLogsRequest command + * + */ + void HandleLogRequestForResponsePayload(CommandHandler * commandHandler, ConcreteCommandPath path, IntentEnum intent); #if CHIP_CONFIG_ENABLE_BDX_LOG_TRANSFER - void HandleBDXResponse(CHIP_ERROR error); + /** + * Send the command response to the requestor with the status value passed in. + * This should be called if there are any errors before we receive a SendAccept + * from the requestor. + * + * @param status The status to send in the command response payload. + * + */ + void SendCommandResponse(StatusEnum status); - CHIP_ERROR HandleLogRequestForBDXProtocol(chip::Messaging::ExchangeContext * exchangeCtx, chip::EndpointId endpointId, - IntentEnum intent, chip::CharSpan fileDesignator); + /** + * Handles the request to download diagnostic logs of type specified in the intent argument for protocol type BDX + * This should return whatever fits in the logContent field of the RetrieveLogsResponse command + * + * @param commandHandler The command handler object from the RetrieveLogsRequest command + * + * @param path The command path from the RetrieveLogsRequest command + * + * @param intent The log type requested in the RetrieveLogsRequest command + * + */ + CHIP_ERROR HandleLogRequestForBDXProtocol(Messaging::ExchangeContext * exchangeCtx, EndpointId endpointId, IntentEnum intent, + CharSpan fileDesignator); void SetAsyncCommandHandleAndPath(CommandHandler * commandObj, const ConcreteCommandPath & commandPath); - bool HasValidFileDesignator(chip::CharSpan transferFileDesignator); + bool HasValidFileDesignator(CharSpan transferFileDesignator); bool IsBDXProtocolRequested(TransferProtocolEnum requestedProtocol); - void SendErrorResponse(chip::app::CommandHandler * commandHandler, chip::app::ConcreteCommandPath path, - StatusEnum status); + /** + * Called to notify the DiagnosticsLogsServer that BDX has completed and the mDiagnosticLogsBDXTransferHandler + * object has been destroyed. We should set the mDiagnosticLogsBDXTransferHandler to null here. + * + */ + void HandleBDXTransferDone() { mDiagnosticLogsBDXTransferHandler = nullptr; } #endif @@ -85,8 +116,8 @@ class DiagnosticLogsServer LogSessionHandle mLogSessionHandle; - chip::app::CommandHandler::Handle mAsyncCommandHandle; - chip::app::ConcreteCommandPath mRequestPath = chip::app::ConcreteCommandPath(0, 0, 0); + CommandHandler::Handle mAsyncCommandHandle; + ConcreteCommandPath mRequestPath = ConcreteCommandPath(0, 0, 0); IntentEnum mIntent; static DiagnosticLogsServer sInstance; diff --git a/src/darwin/Framework/CHIP/MTRDevice.mm b/src/darwin/Framework/CHIP/MTRDevice.mm index e8947cb7c87238..cb3d7ac3668565 100644 --- a/src/darwin/Framework/CHIP/MTRDevice.mm +++ b/src/darwin/Framework/CHIP/MTRDevice.mm @@ -15,7 +15,6 @@ * limitations under the License. */ -#import #import #import @@ -29,24 +28,20 @@ #import "MTRDefines_Internal.h" #import "MTRDeviceController_Internal.h" #import "MTRDevice_Internal.h" +#import "MTRDiagnosticsLogDownloadTask.h" #import "MTRError_Internal.h" #import "MTREventTLVValueDecoder_Internal.h" #import "MTRLogging_Internal.h" -#import "NSDataSpanConversion.h" -#import "NSStringSpanConversion.h" #import "zap-generated/MTRCommandPayloads_Internal.h" #include "lib/core/CHIPError.h" #include "lib/core/DataModelTypes.h" #include -#include "MTRDiagnosticLogsTransferHandler.h" #include #include #include #include -#include -#include #include typedef void (^MTRDeviceAttributeReportHandler)(NSArray * _Nonnull); @@ -150,8 +145,7 @@ @interface MTRDevice () @property (nonatomic) chip::FabricIndex fabricIndex; @property (nonatomic) MTRWeakReference> * weakDelegate; @property (nonatomic) dispatch_queue_t delegateQueue; -@property (nonatomic) dispatch_source_t timerSource; -@property (nonatomic) MTRDiagnosticLogsTransferHandler * diagnosticLogsTransferHandler; +@property (nonatomic) MTRDiagnosticsLogDownloadTask * diagnosticsLogDownloadTask; @property (nonatomic) NSArray *> * unreportedEvents; /** @@ -1321,196 +1315,18 @@ - (void)openCommissioningWindowWithDiscriminator:(NSNumber *)discriminator [baseDevice openCommissioningWindowWithDiscriminator:discriminator duration:duration queue:queue completion:completion]; } -- (NSString *)_toLogTypeString:(MTRDiagnosticLogType)type -{ - switch (type) { - case MTRDiagnosticLogTypeEndUserSupport: - return @"EndUserSupport"; - case MTRDiagnosticLogTypeNetworkDiagnostics: - return @"NetworkDiag"; - case MTRDiagnosticLogTypeCrash: - return @"Crash"; - default: - return @""; - } -} - -- (NSString *)_getFileDesignatorForLogType:(MTRDiagnosticLogType)type -{ - - NSString * fileDesignator = [NSString stringWithFormat:@"%@%@/%@", @"bdx:/", self.nodeID, [self _toLogTypeString:type]]; - return fileDesignator; -} - -- (void)_startTimerForDownload:(NSTimeInterval)timeout -{ - _timerSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, DISPATCH_TIMER_STRICT, self.queue); - VerifyOrDie(_timerSource != nullptr); - - dispatch_source_set_timer( - _timerSource, dispatch_walltime(nullptr, static_cast(timeout * NSEC_PER_MSEC)), DISPATCH_TIME_FOREVER, 2 * NSEC_PER_MSEC); - - dispatch_source_set_event_handler(_timerSource, ^{ - dispatch_async(self.queue, ^{ - if (self->_diagnosticLogsTransferHandler != nil) { - self->_diagnosticLogsTransferHandler->AbortTransfer(chip::bdx::StatusCode::kUnknown); - } - }); - dispatch_source_cancel(self->_timerSource); - }); - dispatch_resume(_timerSource); -} - -- (NSURL * _Nullable)_temporaryFileURLForDownload:(MTRDiagnosticLogType)type - queue:(dispatch_queue_t)queue - completion:(void (^)(NSURL * _Nullable logResult, NSError * error))completion -{ - NSDateFormatter * dateFormatter = [[NSDateFormatter alloc] init]; - dateFormatter.dateFormat = @"yyyy-MM-dd_HH:mm:ss.SSSZZZ"; - - NSString * timeString = [dateFormatter stringFromDate:NSDate.now]; - - NSString * fileName = [NSString stringWithFormat:@"%@_%@_%@", timeString, self.nodeID, [self _toLogTypeString:type]]; - - NSURL * filePath = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:fileName] isDirectory:YES]; - NSError * error = nil; - - NSURL * downloadFileURL = [[NSFileManager defaultManager] URLForDirectory:NSItemReplacementDirectory - inDomain:NSUserDomainMask - appropriateForURL:filePath - create:YES - error:&error]; - if (downloadFileURL == nil || error != nil) { - return nil; - } - - if ([[NSFileManager defaultManager] createFileAtPath:[filePath path] contents:nil attributes:nil]) { - return filePath; - } - return nil; -} - -- (bool)_isErrorResponse:(MTRDiagnosticLogsClusterRetrieveLogsResponseParams * _Nullable)response -{ - chip::app::Clusters::DiagnosticLogs::StatusEnum statusValue = static_cast(response.status.intValue); - return (response == nil || (response.status != nil && statusValue != chip::app::Clusters::DiagnosticLogs::StatusEnum::kNoLogs && statusValue != chip::app::Clusters::DiagnosticLogs::StatusEnum::kExhausted) || response.logContent.length == 0); -} - -- (void)_invokeCompletion:(void (^)(NSURL * _Nullable logResult, NSError * error))completion - filepath:(NSURL * _Nullable)filepath - queue:(dispatch_queue_t)queue - error:(NSError * _Nullable)error -{ - dispatch_async(queue, ^{ - completion(filepath, error); - }); - - dispatch_async(self.queue, ^{ - if (self->_diagnosticLogsTransferHandler != nil) { - delete (self->_diagnosticLogsTransferHandler); - self->_diagnosticLogsTransferHandler = nil; - } - }); -} - -- (void)_invokeCompletionWithError:(void (^)(NSURL * _Nullable logResult, NSError * error))completion - queue:(dispatch_queue_t)queue - error:(NSError * _Nullable)error -{ - [self _invokeCompletion:completion filepath:nil queue:queue error:error]; -} - -- (void)_downloadLogOfType:(MTRDiagnosticLogType)type - timeout:(NSTimeInterval)timeout - queue:(dispatch_queue_t)queue - completion:(void (^)(NSURL * _Nullable logResult, NSError * error))completion -{ - if (type != MTRDiagnosticLogTypeEndUserSupport && type != MTRDiagnosticLogTypeNetworkDiagnostics && type != MTRDiagnosticLogTypeCrash) { - [self _invokeCompletionWithError:completion queue:queue error:[NSError errorWithDomain:MTRErrorDomain code:MTRErrorCodeInvalidArgument userInfo:nil]]; - return; - } - - MTRClusterDiagnosticLogs * cluster = [[MTRClusterDiagnosticLogs alloc] initWithDevice:self endpointID:@(0) queue:queue]; - if (cluster == nil) { - [self _invokeCompletionWithError:completion queue:queue error:[NSError errorWithDomain:MTRErrorDomain code:MTRErrorCodeInvalidState userInfo:nil]]; - return; - } - - NSURL * filePath = [self _temporaryFileURLForDownload:type queue:queue completion:completion]; - if (filePath == nil) { - [self _invokeCompletionWithError:completion queue:queue error:[NSError errorWithDomain:MTRErrorDomain code:MTRErrorCodeInvalidState userInfo:nil]]; - } - - self->_diagnosticLogsTransferHandler = new MTRDiagnosticLogsTransferHandler(filePath, ^(bool result) { - if (result == YES) { - [self _invokeCompletion:completion filepath:filePath queue:queue error:nil]; - } else { - [self _invokeCompletionWithError:completion queue:queue error:[NSError errorWithDomain:MTRErrorDomain code:MTRErrorCodeInvalidState userInfo:nil]]; - } - }); - - // Get the device commissionee and get the exchange manager to register for unsolicited message handler for BDX messages - [self.deviceController asyncGetCommissionerOnMatterQueue:^(Controller::DeviceCommissioner * commissioner) { - if (commissioner == nil) { - [self _invokeCompletionWithError:completion queue:queue error:[NSError errorWithDomain:MTRErrorDomain code:MTRErrorCodeInvalidState userInfo:nil]]; - return; - } - - commissioner->ExchangeMgr()->RegisterUnsolicitedMessageHandlerForProtocol(Protocols::BDX::Id, self->_diagnosticLogsTransferHandler); - - dispatch_async(self.queue, ^{ - // Start a timer if a timeout is provided - if (timeout > 0) { - [self _startTimerForDownload:timeout]; - } - - MTRDiagnosticLogsClusterRetrieveLogsRequestParams * requestParams = [[MTRDiagnosticLogsClusterRetrieveLogsRequestParams alloc] init]; - requestParams.intent = @(type); - requestParams.requestedProtocol = @(chip::to_underlying(chip::app::Clusters::DiagnosticLogs::TransferProtocolEnum::kBdx)); - requestParams.transferFileDesignator = [self _getFileDesignatorForLogType:type]; - [cluster retrieveLogsRequestWithParams:requestParams expectedValues:nil expectedValueInterval:nil - completion:^(MTRDiagnosticLogsClusterRetrieveLogsResponseParams * _Nullable response, NSError * _Nullable error) { - // If we are in a BDX session and there is no error, do nothing. Completion will be called when BDX succeeds or fails. - if (self->_diagnosticLogsTransferHandler != nil && error == nil) { - return; - } - - if ([self _isErrorResponse:response]) { - if (self->_timerSource) { - dispatch_source_cancel(self->_timerSource); - } - - [self _invokeCompletionWithError:completion queue:queue error:error]; - return; - } - - // If the response has a log content, copy it into the temporary location and send the URL. - if (response != nil && response.logContent != nil && response.logContent.length > 0) { - if ([response.logContent writeToURL:filePath atomically:YES]) { - if (self->_timerSource) { - dispatch_source_cancel(self->_timerSource); - } - [self _invokeCompletion:completion filepath:filePath queue:queue error:nil]; - return; - } - } - }]; - }); - } - errorHandler:^(NSError * error) { - if (self->_timerSource) { - dispatch_source_cancel(self->_timerSource); - } - [self _invokeCompletionWithError:completion queue:queue error:error]; - }]; -} - - (void)downloadLogOfType:(MTRDiagnosticLogType)type timeout:(NSTimeInterval)timeout queue:(dispatch_queue_t)queue completion:(void (^)(NSURL * _Nullable logResult, NSError * error))completion { - [self _downloadLogOfType:type timeout:timeout queue:queue completion:completion]; + self->_diagnosticsLogDownloadTask = [[MTRDiagnosticsLogDownloadTask alloc] initWithDevice:self]; + [self->_diagnosticsLogDownloadTask downloadLogOfType:type timeout:timeout queue:queue completion:completion]; +} + +- (void)removeDiagnosticsLogDownloadTask +{ + self->_diagnosticsLogDownloadTask = nil; } #pragma mark - Cache management diff --git a/src/darwin/Framework/CHIP/MTRDevice_Internal.h b/src/darwin/Framework/CHIP/MTRDevice_Internal.h index cad9af9e79cf81..000802544df030 100644 --- a/src/darwin/Framework/CHIP/MTRDevice_Internal.h +++ b/src/darwin/Framework/CHIP/MTRDevice_Internal.h @@ -64,6 +64,8 @@ typedef void (^MTRDevicePerformAsyncBlock)(MTRBaseDevice * baseDevice); queue:(dispatch_queue_t)queue completion:(void (^)(id _Nullable response, NSError * _Nullable error))completion; +- (void)removeDiagnosticsLogDownloadTask; + // Queue used for various internal bookkeeping work. @property (nonatomic) dispatch_queue_t queue; @property (nonatomic, readonly) MTRAsyncWorkQueue * asyncWorkQueue; diff --git a/src/darwin/Framework/CHIP/MTRDiagnosticLogsTransferHandler.h b/src/darwin/Framework/CHIP/MTRDiagnosticLogsTransferHandler.h deleted file mode 100644 index df75e925d741fb..00000000000000 --- a/src/darwin/Framework/CHIP/MTRDiagnosticLogsTransferHandler.h +++ /dev/null @@ -1,85 +0,0 @@ -/** - * - * Copyright (c) 2023 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 - -/** - * This class inherits from the AsyncResponder class and handles the BDX messages for a BDX transfer session. - * It overrides the HandleTransferSessionOutput virtual method and provides an implementation for it to handle - * the OutputEvents that are generated by the BDX transfer session state machine. - * - * An MTRDiagnosticLogsTransferHandler will be associated with a specific BDX transfer session. - * - * The lifecycle of this class is managed by the AsyncFacilitator class which calls the virtual CleanUp method - * that is implemented by this class to clean up and destroy itself and the AsyncFacilitator instances. - * Note: An object of this class can't be used after CleanUp has been called. - */ -class MTRDiagnosticLogsTransferHandler : public chip::bdx::Responder { -public: - MTRDiagnosticLogsTransferHandler() - : mFileURL(nil) - { - } - - MTRDiagnosticLogsTransferHandler(NSURL * _Nonnull url, void (^_Nonnull callback)(bool)) - { - mFileURL = url; - mCallback = callback; - } - - ~MTRDiagnosticLogsTransferHandler() {}; - - void HandleTransferSessionOutput(chip::bdx::TransferSession::OutputEvent & event) override; - - void AbortTransfer(chip::bdx::StatusCode reason); - -protected: - CHIP_ERROR OnMessageReceived(chip::Messaging::ExchangeContext * _Nonnull ec, const chip::PayloadHeader & payloadHeader, - chip::System::PacketBufferHandle && payload) override; - -private: - CHIP_ERROR PrepareForTransfer(chip::System::Layer * _Nonnull layer, chip::FabricIndex fabricIndex, chip::NodeId nodeId); - - CHIP_ERROR ConfigureState(chip::FabricIndex fabricIndex, chip::NodeId nodeId); - - CHIP_ERROR OnMessageToSend(chip::bdx::TransferSession::OutputEvent & event); - - CHIP_ERROR OnTransferSessionBegin(chip::bdx::TransferSession::OutputEvent & event); - - CHIP_ERROR OnTransferSessionEnd(chip::bdx::TransferSession::OutputEvent & event); - - CHIP_ERROR OnBlockReceived(chip::bdx::TransferSession::OutputEvent & event); - - void Reset(); - - // The fabric index of the node with which the BDX session is established. - chip::Optional mFabricIndex; - - // The node id of the node with which the BDX session is established. - chip::Optional mNodeId; - - chip::Messaging::ExchangeContext * _Nullable mExchangeCtx; - - NSURL * _Nullable mFileURL; - - NSFileHandle * _Nullable mFileHandle; - std::function mCallback; -}; diff --git a/src/darwin/Framework/CHIP/MTRDiagnosticsLogDownloadTask.h b/src/darwin/Framework/CHIP/MTRDiagnosticsLogDownloadTask.h new file mode 100644 index 00000000000000..b8c547973825d8 --- /dev/null +++ b/src/darwin/Framework/CHIP/MTRDiagnosticsLogDownloadTask.h @@ -0,0 +1,41 @@ +/** + * + * Copyright (c) 2023 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. + */ + +#import "MTRDevice.h" +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + * This class handles the task downloading a log file requested by the MTRDevice. It creates a new + * MTRDiagnosticsLogTransferHandler to prepare for the BDX transfer session and handle the BDX + * messages and transfer events and downloads the file. + * + */ +@interface MTRDiagnosticsLogDownloadTask : NSObject +- (instancetype)init NS_UNAVAILABLE; ++ (instancetype)new NS_UNAVAILABLE; + +- (instancetype)initWithDevice:(MTRDevice *)device; + +- (void)downloadLogOfType:(MTRDiagnosticLogType)type + timeout:(NSTimeInterval)timeout + queue:(dispatch_queue_t)queue + completion:(void (^)(NSURL * _Nullable logResult, NSError * error))completion; +@end + +NS_ASSUME_NONNULL_END diff --git a/src/darwin/Framework/CHIP/MTRDiagnosticsLogDownloadTask.mm b/src/darwin/Framework/CHIP/MTRDiagnosticsLogDownloadTask.mm new file mode 100644 index 00000000000000..96ddb713c33fd6 --- /dev/null +++ b/src/darwin/Framework/CHIP/MTRDiagnosticsLogDownloadTask.mm @@ -0,0 +1,262 @@ +// +/** + * Copyright (c) 2023 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. + */ + +#import "MTRDiagnosticsLogDownloadTask.h" +#import "MTRCluster.h" +#import "MTRClusterConstants.h" +#import "MTRDefines_Internal.h" +#import "MTRDeviceController.h" +#import "MTRDeviceController_Internal.h" +#import "MTRDevice_Internal.h" +#import "MTRDiagnosticsLogTransferHandler.h" +#import "MTRError.h" +#import +#import + +#import "zap-generated/MTRCommandPayloads_Internal.h" + +#import + +#include +#include + +using namespace chip; + +@interface MTRDiagnosticsLogDownloadTask () +@property (nonatomic, readonly) os_unfair_lock lock; +@property (nonatomic) dispatch_source_t timerSource; +@property (nonatomic) MTRDiagnosticsLogTransferHandler * diagnosticLogsTransferHandler; +@property (nonatomic, readonly, weak) MTRDevice * device; +@property (strong, nonatomic) dispatch_queue_t queue; + +@end + +@implementation MTRDiagnosticsLogDownloadTask +- (instancetype)initWithDevice:(MTRDevice *)device +{ + if (self = [super init]) { + _lock = OS_UNFAIR_LOCK_INIT; + _device = device; + _queue = dispatch_queue_create("org.csa-iot.matter.framework.device.workqueue", DISPATCH_QUEUE_SERIAL_WITH_AUTORELEASE_POOL); + } + return self; +} + +- (NSString *)_toLogTypeString:(MTRDiagnosticLogType)type +{ + switch (type) { + case MTRDiagnosticLogTypeEndUserSupport: + return @"EndUserSupport"; + case MTRDiagnosticLogTypeNetworkDiagnostics: + return @"NetworkDiag"; + case MTRDiagnosticLogTypeCrash: + return @"Crash"; + default: + return @""; + } +} + +- (NSString *)_getFileDesignatorForLogType:(MTRDiagnosticLogType)type +{ + NSString * fileDesignator = [NSString stringWithFormat:@"bdx:/%0llx/%s", _device.nodeID.unsignedLongLongValue, [self _toLogTypeString:type].UTF8String]; + return fileDesignator; +} + +- (void)_startTimerForDownload:(NSTimeInterval)timeout +{ + // TODO: Fix #30538 Fix the timeout code when downloadLogOfType API is called with a timeout value. + self->_timerSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, DISPATCH_TIMER_STRICT, self.queue); + VerifyOrDie(self->_timerSource != nullptr); + + dispatch_source_set_timer( + self->_timerSource, dispatch_walltime(nullptr, static_cast(timeout * NSEC_PER_MSEC)), DISPATCH_TIME_FOREVER, 2 * NSEC_PER_MSEC); + + dispatch_source_set_event_handler(self->_timerSource, ^{ + dispatch_async(self.queue, ^{ + os_unfair_lock_lock(&self->_lock); + if (self->_diagnosticLogsTransferHandler != nil) { + self->_diagnosticLogsTransferHandler->AbortTransfer(chip::bdx::StatusCode::kUnknown); + } + os_unfair_lock_unlock(&self->_lock); + }); + dispatch_source_cancel(self->_timerSource); + }); + dispatch_resume(self->_timerSource); +} + +- (NSURL * _Nullable)_temporaryFileURLForDownload:(MTRDiagnosticLogType)type + queue:(dispatch_queue_t)queue + completion:(void (^)(NSURL * _Nullable logResult, NSError * error))completion +{ + NSDateFormatter * dateFormatter = [[NSDateFormatter alloc] init]; + dateFormatter.dateFormat = @"yyyy-MM-dd_HH:mm:ss.SSSZZZ"; + + NSString * timeString = [dateFormatter stringFromDate:NSDate.now]; + + NSString * fileName = [NSString stringWithFormat:@"%s_%0llx_%s", timeString.UTF8String, _device.nodeID.unsignedLongLongValue, [self _toLogTypeString:type].UTF8String]; + + NSURL * filePath = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:fileName] isDirectory:YES]; + NSError * error = nil; + + NSURL * downloadFileURL = [[NSFileManager defaultManager] URLForDirectory:NSItemReplacementDirectory + inDomain:NSUserDomainMask + appropriateForURL:filePath + create:YES + error:&error]; + if (downloadFileURL == nil || error != nil) { + return nil; + } + + if ([[NSFileManager defaultManager] createFileAtPath:[filePath path] contents:nil attributes:nil]) { + return filePath; + } + return nil; +} + +using namespace chip::app::Clusters::DiagnosticLogs; +- (bool)_isErrorResponse:(MTRDiagnosticLogsClusterRetrieveLogsResponseParams * _Nullable)response +{ + if (response == nil || response.status == nil) { + return true; + } + StatusEnum statusValue = static_cast(response.status.intValue); + return ((statusValue != StatusEnum::kNoLogs && statusValue != StatusEnum::kExhausted) || response.logContent.length == 0); +} + +- (void)_invokeCompletion:(void (^)(NSURL * _Nullable logResult, NSError * error))completion + filepath:(NSURL * _Nullable)filepath + queue:(dispatch_queue_t)queue + error:(NSError * _Nullable)error +{ + dispatch_async(queue, ^{ + completion(filepath, error); + }); + + dispatch_async(self.queue, ^{ + if (self->_diagnosticLogsTransferHandler != nil) { + delete (self->_diagnosticLogsTransferHandler); + self->_diagnosticLogsTransferHandler = nil; + } + }); +} + +- (void)_invokeCompletionWithError:(void (^)(NSURL * _Nullable logResult, NSError * error))completion + queue:(dispatch_queue_t)queue + error:(NSError * _Nullable)error +{ + [self _invokeCompletion:completion filepath:nil queue:queue error:error]; +} + +- (void)_handleResponse:(NSError *)error + filepath:(NSURL * _Nullable)filepath + queue:(dispatch_queue_t)queue + completion:(void (^)(NSURL * _Nullable logResult, NSError * error))completion +{ + if (self->_timerSource) { + dispatch_source_cancel(self->_timerSource); + } + + if (error == nil && filepath != nil) { + [self _invokeCompletion:completion filepath:filepath queue:queue error:nil]; + } else { + [self _invokeCompletionWithError:completion queue:queue error:error]; + } + [self->_device removeDiagnosticsLogDownloadTask]; +} + +- (void)_downloadLogOfType:(MTRDiagnosticLogType)type + timeout:(NSTimeInterval)timeout + queue:(dispatch_queue_t)queue + completion:(void (^)(NSURL * _Nullable logResult, NSError * error))completion +{ + if (type != MTRDiagnosticLogTypeEndUserSupport && type != MTRDiagnosticLogTypeNetworkDiagnostics && type != MTRDiagnosticLogTypeCrash) { + [self _invokeCompletionWithError:completion queue:queue error:[NSError errorWithDomain:MTRErrorDomain code:MTRErrorCodeInvalidArgument userInfo:nil]]; + return; + } + + NSURL * filePath = [self _temporaryFileURLForDownload:type queue:queue completion:completion]; + if (filePath == nil) { + [self _invokeCompletionWithError:completion queue:queue error:[NSError errorWithDomain:MTRErrorDomain code:MTRErrorCodeInvalidState userInfo:nil]]; + return; + } + + os_unfair_lock_lock(&self->_lock); + self->_diagnosticLogsTransferHandler = new MTRDiagnosticsLogTransferHandler(filePath, ^(bool result) { + if (result == YES) { + [self _handleResponse:nil filepath:filePath queue:queue completion:completion]; + } else { + [self _handleResponse:[NSError errorWithDomain:MTRErrorDomain code:MTRErrorCodeInvalidState userInfo:nil] filepath:nil queue:queue completion:completion]; + } + }); + os_unfair_lock_unlock(&self->_lock); + + // Get the device commissionee and get the exchange manager to register for unsolicited message handler for BDX messages + [_device.deviceController asyncGetCommissionerOnMatterQueue:^(Controller::DeviceCommissioner * commissioner) { + os_unfair_lock_lock(&self->_lock); + commissioner->ExchangeMgr()->RegisterUnsolicitedMessageHandlerForProtocol(Protocols::BDX::Id, self->_diagnosticLogsTransferHandler); + os_unfair_lock_unlock(&self->_lock); + + dispatch_async(self.queue, ^{ + // Start a timer if a timeout is provided + if (timeout > 0) { + [self _startTimerForDownload:timeout]; + } + + MTRDiagnosticLogsClusterRetrieveLogsRequestParams * requestParams = [[MTRDiagnosticLogsClusterRetrieveLogsRequestParams alloc] init]; + requestParams.intent = @(type); + requestParams.requestedProtocol = @(chip::to_underlying(chip::app::Clusters::DiagnosticLogs::TransferProtocolEnum::kBdx)); + requestParams.transferFileDesignator = [self _getFileDesignatorForLogType:type]; + + MTRClusterDiagnosticLogs * cluster = [[MTRClusterDiagnosticLogs alloc] initWithDevice:self->_device endpointID:@(0) queue:self.queue]; + [cluster retrieveLogsRequestWithParams:requestParams expectedValues:nil expectedValueInterval:nil + completion:^(MTRDiagnosticLogsClusterRetrieveLogsResponseParams * _Nullable response, NSError * _Nullable error) { + os_unfair_lock_lock(&self->_lock); + // If we are in a BDX session and there is no error, do nothing. Completion will be called when BDX succeeds or fails. + if (self->_diagnosticLogsTransferHandler != nil && error == nil) { + return; + } + os_unfair_lock_unlock(&self->_lock); + + if ([self _isErrorResponse:response]) { + [self _handleResponse:error filepath:nil queue:queue completion:completion]; + return; + } + + // If the response has a log content, copy it into the temporary location and send the URL. + if (response != nil && response.logContent != nil && response.logContent.length > 0) { + if ([response.logContent writeToURL:filePath atomically:YES]) { + [self _handleResponse:nil filepath:filePath queue:queue completion:completion]; + return; + } + } + }]; + }); + } + errorHandler:^(NSError * error) { + [self _handleResponse:error filepath:nil queue:queue completion:completion]; + }]; +} + +- (void)downloadLogOfType:(MTRDiagnosticLogType)type + timeout:(NSTimeInterval)timeout + queue:(dispatch_queue_t)queue + completion:(void (^)(NSURL * _Nullable logResult, NSError * error))completion +{ + [self _downloadLogOfType:type timeout:timeout queue:queue completion:completion]; +} + +@end diff --git a/src/darwin/Framework/CHIP/MTRDiagnosticsLogTransferHandler.h b/src/darwin/Framework/CHIP/MTRDiagnosticsLogTransferHandler.h new file mode 100644 index 00000000000000..e9fb336bb67ea6 --- /dev/null +++ b/src/darwin/Framework/CHIP/MTRDiagnosticsLogTransferHandler.h @@ -0,0 +1,131 @@ +/** + * + * Copyright (c) 2023 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 + +/** + * This class is a subclass of the TransferFacilitator (Responder) class and handles the BDX messages for a BDX transfer session. + * It overrides the HandleTransferSessionOutput virtual method and provides an implementation for it to handle + * the OutputEvents that are generated by the BDX transfer session state machine. + * + * An MTRDiagnosticsLogTransferHandler object will be associated with a specific BDX transfer session. + * + */ +class MTRDiagnosticsLogTransferHandler : public chip::bdx::Responder { +public: + MTRDiagnosticsLogTransferHandler() + : mFileURL(nil) + { + } + + MTRDiagnosticsLogTransferHandler(NSURL * _Nonnull url, void (^_Nonnull callback)(bool)) + { + mFileURL = url; + mCallback = callback; + } + + ~MTRDiagnosticsLogTransferHandler() {}; + + /** + * This method handles BDX messages and other TransferSession events. + * + * @param[in] event An OutputEvent that contains output from the TransferSession object. + */ + void HandleTransferSessionOutput(chip::bdx::TransferSession::OutputEvent & event) override; + + /** + * This is called to abort an ongoing BDX transfer session + * + * @param[in] reason The reason to abort the transfer + */ + void AbortTransfer(chip::bdx::StatusCode reason); + +protected: + /** + * Called when a BDX message is received over the exchange context + * + * @param[in] ec The exchange context + * + * @param[in] payloadHeader The payload header of the message + * + * @param[in] payload The payload of the message + */ + CHIP_ERROR OnMessageReceived(chip::Messaging::ExchangeContext * _Nonnull ec, const chip::PayloadHeader & payloadHeader, + chip::System::PacketBufferHandle && payload) override; + +private: + /** + * Initializes the TransferSession state machine to be ready for an incoming transfer request, and start the polling timer. + * + * @param[in] layer A System::Layer pointer to use to start the polling timer + * + * @param[in] fabricIndex The fabric index of the peer node + * + * @param[in] peerNodeId The node id of the peer node + */ + CHIP_ERROR PrepareForTransfer(chip::System::Layer * _Nonnull layer, chip::FabricIndex fabricIndex, chip::NodeId peerNodeId); + + /** + * Called to send a BDX MsgToSend message over the exchange + * + * + * @param[in] event The output event to be send + */ + CHIP_ERROR OnMessageToSend(chip::bdx::TransferSession::OutputEvent & event); + + /** + * Called to begin the transfer session when an init message has been received + * + * @param[in] event The output event received + */ + CHIP_ERROR OnTransferSessionBegin(chip::bdx::TransferSession::OutputEvent & event); + + /** + * Called to end the transfer session when a BlockAckEOF message has been sent over the exchange + * or an error has occurred during the BDX session + * + * @param[in] event The output event received + */ + CHIP_ERROR OnTransferSessionEnd(chip::bdx::TransferSession::OutputEvent & event); + + /** + * Called when a block has been received from the Sender. The block is processed + * and written to a file and a block ack is sent back to the sender. + * + * @param[in] event The output event received + */ + CHIP_ERROR OnBlockReceived(chip::bdx::TransferSession::OutputEvent & event); + + /** + * This method is called to reset state. It resets the transfer and cleans up the + * exchange and the fabric index and peer node id. + */ + void Reset(); + + // The fabric index of the node with which the BDX session is established. + chip::Optional mFabricIndex; + + // The node id of the node with which the BDX session is established. + chip::Optional mPeerNodeId; + + chip::Messaging::ExchangeContext * _Nullable mExchangeCtx; + + NSURL * _Nullable mFileURL; + + NSFileHandle * _Nullable mFileHandle; + std::function mCallback; +}; diff --git a/src/darwin/Framework/CHIP/MTRDiagnosticLogsTransferHandler.mm b/src/darwin/Framework/CHIP/MTRDiagnosticsLogTransferHandler.mm similarity index 80% rename from src/darwin/Framework/CHIP/MTRDiagnosticLogsTransferHandler.mm rename to src/darwin/Framework/CHIP/MTRDiagnosticsLogTransferHandler.mm index b70921964c8b60..008451132d560d 100644 --- a/src/darwin/Framework/CHIP/MTRDiagnosticLogsTransferHandler.mm +++ b/src/darwin/Framework/CHIP/MTRDiagnosticsLogTransferHandler.mm @@ -15,51 +15,53 @@ * limitations under the License. */ +#include "MTRDiagnosticsLogTransferHandler.h" #import "MTRDeviceControllerFactory_Internal.h" #import "MTRDeviceController_Internal.h" -#import "NSDataSpanConversion.h" - -#include "MTRDiagnosticLogsTransferHandler.h" #include +#import "NSDataSpanConversion.h" + using namespace chip; using namespace chip::bdx; using namespace chip::app; +// Max block size for the BDX transfer. constexpr uint32_t kMaxBdxBlockSize = 1024; -// Timeout for the BDX transfer session. The OTA Spec mandates this should be >= 5 minutes. +// Timeout for the BDX transfer session.. constexpr System::Clock::Timeout kBdxTimeout = System::Clock::Seconds16(5 * 60); -constexpr bdx::TransferRole kBdxRole = bdx::TransferRole::kReceiver; +constexpr TransferRole kBdxRole = TransferRole::kReceiver; -CHIP_ERROR MTRDiagnosticLogsTransferHandler::PrepareForTransfer(System::Layer * _Nonnull layer, FabricIndex fabricIndex, NodeId nodeId) +CHIP_ERROR MTRDiagnosticsLogTransferHandler::PrepareForTransfer(System::Layer * _Nonnull layer, FabricIndex fabricIndex, NodeId peerNodeId) { assertChipStackLockedByCurrentThread(); - ReturnErrorOnFailure(ConfigureState(fabricIndex, nodeId)); + mFabricIndex.SetValue(fabricIndex); + mPeerNodeId.SetValue(peerNodeId); - BitFlags flags(bdx::TransferControlFlags::kSenderDrive); + BitFlags flags(TransferControlFlags::kSenderDrive); return Responder::PrepareForTransfer(layer, kBdxRole, flags, kMaxBdxBlockSize, kBdxTimeout); } -bdx::StatusCode GetBdxStatusCodeFromChipError(CHIP_ERROR err) +StatusCode GetBdxStatusCodeFromChipError(CHIP_ERROR err) { if (err == CHIP_ERROR_INCORRECT_STATE) { - return bdx::StatusCode::kUnexpectedMessage; + return StatusCode::kUnexpectedMessage; } if (err == CHIP_ERROR_INVALID_ARGUMENT) { - return bdx::StatusCode::kBadMessageContents; + return StatusCode::kBadMessageContents; } - return bdx::StatusCode::kUnknown; + return StatusCode::kUnknown; } -CHIP_ERROR MTRDiagnosticLogsTransferHandler::OnTransferSessionBegin(TransferSession::OutputEvent & event) +CHIP_ERROR MTRDiagnosticsLogTransferHandler::OnTransferSessionBegin(TransferSession::OutputEvent & event) { assertChipStackLockedByCurrentThread(); VerifyOrReturnError(mFabricIndex.HasValue(), CHIP_ERROR_INCORRECT_STATE); - VerifyOrReturnError(mNodeId.HasValue(), CHIP_ERROR_INCORRECT_STATE); + VerifyOrReturnError(mPeerNodeId.HasValue(), CHIP_ERROR_INCORRECT_STATE); NSError * error = nil; mFileHandle = [NSFileHandle fileHandleForWritingToURL:mFileURL error:&error]; @@ -71,7 +73,7 @@ } TransferSession::TransferAcceptData acceptData; - acceptData.ControlMode = bdx::TransferControlFlags::kSenderDrive; + acceptData.ControlMode = TransferControlFlags::kSenderDrive; acceptData.MaxBlockSize = mTransfer.GetTransferBlockSize(); acceptData.StartOffset = mTransfer.GetStartOffset(); acceptData.Length = mTransfer.GetTransferLength(); @@ -80,11 +82,11 @@ return CHIP_NO_ERROR; } -CHIP_ERROR MTRDiagnosticLogsTransferHandler::OnTransferSessionEnd(TransferSession::OutputEvent & event) +CHIP_ERROR MTRDiagnosticsLogTransferHandler::OnTransferSessionEnd(TransferSession::OutputEvent & event) { assertChipStackLockedByCurrentThread(); VerifyOrReturnError(mFabricIndex.HasValue(), CHIP_ERROR_INCORRECT_STATE); - VerifyOrReturnError(mNodeId.HasValue(), CHIP_ERROR_INCORRECT_STATE); + VerifyOrReturnError(mPeerNodeId.HasValue(), CHIP_ERROR_INCORRECT_STATE); CHIP_ERROR error = CHIP_NO_ERROR; if (event.EventType == TransferSession::OutputEventType::kTransferTimeout) { @@ -101,14 +103,14 @@ return error; } -CHIP_ERROR MTRDiagnosticLogsTransferHandler::OnBlockReceived(TransferSession::OutputEvent & event) +CHIP_ERROR MTRDiagnosticsLogTransferHandler::OnBlockReceived(TransferSession::OutputEvent & event) { assertChipStackLockedByCurrentThread(); VerifyOrReturnError(mFabricIndex.HasValue(), CHIP_ERROR_INCORRECT_STATE); - VerifyOrReturnError(mNodeId.HasValue(), CHIP_ERROR_INCORRECT_STATE); + VerifyOrReturnError(mPeerNodeId.HasValue(), CHIP_ERROR_INCORRECT_STATE); - chip::ByteSpan blockData(event.blockdata.Data, event.blockdata.Length); + ByteSpan blockData(event.blockdata.Data, event.blockdata.Length); if (mFileHandle != nil) { [mFileHandle seekToEndOfFile]; @@ -135,7 +137,7 @@ return CHIP_NO_ERROR; } -CHIP_ERROR MTRDiagnosticLogsTransferHandler::OnMessageToSend(TransferSession::OutputEvent & event) +CHIP_ERROR MTRDiagnosticsLogTransferHandler::OnMessageToSend(TransferSession::OutputEvent & event) { assertChipStackLockedByCurrentThread(); @@ -165,7 +167,7 @@ return err; } -void MTRDiagnosticLogsTransferHandler::HandleTransferSessionOutput(TransferSession::OutputEvent & event) +void MTRDiagnosticsLogTransferHandler::HandleTransferSessionOutput(TransferSession::OutputEvent & event) { assertChipStackLockedByCurrentThread(); ChipLogError(BDX, "Got an event %s", event.ToString(event.EventType)); @@ -215,13 +217,13 @@ } } -void MTRDiagnosticLogsTransferHandler::AbortTransfer(chip::bdx::StatusCode reason) +void MTRDiagnosticsLogTransferHandler::AbortTransfer(StatusCode reason) { assertChipStackLockedByCurrentThread(); mTransfer.AbortTransfer(reason); } -void MTRDiagnosticLogsTransferHandler::Reset() +void MTRDiagnosticsLogTransferHandler::Reset() { assertChipStackLockedByCurrentThread(); mFileURL = nil; @@ -233,20 +235,10 @@ mExchangeCtx = nil; } mFabricIndex.ClearValue(); - mNodeId.ClearValue(); + mPeerNodeId.ClearValue(); } -CHIP_ERROR MTRDiagnosticLogsTransferHandler::ConfigureState(chip::FabricIndex fabricIndex, chip::NodeId nodeId) -{ - assertChipStackLockedByCurrentThread(); - - mFabricIndex.SetValue(fabricIndex); - mNodeId.SetValue(nodeId); - - return CHIP_NO_ERROR; -} - -CHIP_ERROR MTRDiagnosticLogsTransferHandler::OnMessageReceived( +CHIP_ERROR MTRDiagnosticsLogTransferHandler::OnMessageReceived( Messaging::ExchangeContext * _Nonnull ec, const PayloadHeader & payloadHeader, System::PacketBufferHandle && payload) { VerifyOrReturnError(ec != nil, CHIP_ERROR_INCORRECT_STATE); @@ -255,19 +247,17 @@ // If we receive a ReceiveInit message, then we prepare for transfer if (payloadHeader.HasMessageType(MessageType::SendInit)) { - NodeId nodeId = ec->GetSessionHandle()->GetPeer().GetNodeId(); + NodeId peerNodeId = ec->GetSessionHandle()->GetPeer().GetNodeId(); FabricIndex fabricIndex = ec->GetSessionHandle()->GetFabricIndex(); - if (nodeId != kUndefinedNodeId && fabricIndex != kUndefinedFabricIndex) { - err = PrepareForTransfer(&(DeviceLayer::SystemLayer()), fabricIndex, nodeId); + if (peerNodeId != kUndefinedNodeId && fabricIndex != kUndefinedFabricIndex) { + err = PrepareForTransfer(&(DeviceLayer::SystemLayer()), fabricIndex, peerNodeId); if (err != CHIP_NO_ERROR) { ChipLogError(BDX, "Failed to prepare for transfer for BDX"); return err; } } } - TransferFacilitator::OnMessageReceived(ec, payloadHeader, std::move(payload)); - return err; } diff --git a/src/darwin/Framework/CHIPTests/MTRDeviceTests.m b/src/darwin/Framework/CHIPTests/MTRDeviceTests.m index 9a4cb543ffc799..b7e56ea791b1f4 100644 --- a/src/darwin/Framework/CHIPTests/MTRDeviceTests.m +++ b/src/darwin/Framework/CHIPTests/MTRDeviceTests.m @@ -39,6 +39,7 @@ static const uint16_t kPairingTimeoutInSeconds = 10; static const uint16_t kDownloadLogTimeoutInSeconds = 30; +static const uint16_t kDownloadLogDelayTimeoutInSeconds = 5; static const uint16_t kTimeoutInSeconds = 3; static const uint64_t kDeviceId = 0x12344321; static NSString * kOnboardingPayload = @"MT:-24J0AFN00KA0648G00"; @@ -2606,30 +2607,6 @@ - (void)test028_TimeZoneAndDST #endif // MTR_ENABLE_PROVISIONAL } -/** - * Given a path relative to the Matter root, create an absolute path to the file. - */ -- (NSString *)absolutePathFor:(NSString *)matterRootRelativePath -{ - // Find the right absolute path to our file. PWD should - // point to our src/darwin/Framework. - NSString * pwd = [[NSProcessInfo processInfo] environment][@"PWD"]; - NSMutableArray * pathComponents = [[NSMutableArray alloc] init]; - [pathComponents addObject:[pwd substringToIndex:(pwd.length - @"src/darwin/Framework".length)]]; - [pathComponents addObjectsFromArray:[matterRootRelativePath pathComponents]]; - return [NSString pathWithComponents:pathComponents]; -} - -/** - * Create a task given a path relative to the Matter root. - */ -- (NSTask *)createTaskForPath:(NSString *)path -{ - NSTask * task = [[NSTask alloc] init]; - [task setLaunchPath:[self absolutePathFor:path]]; - return task; -} - - (NSError *)generateLogFile:(NSString *)outFile size:(unsigned long long)size { @@ -2641,19 +2618,24 @@ - (NSError *)generateLogFile:(NSString *)outFile NSString * content = @"The quick brown fox jumps over the lazy dog"; NSFileHandle * handle = [NSFileHandle fileHandleForWritingAtPath:outFile]; - if (handle == nil) - { + if (handle == nil) { NSLog(@"Failed to generate the log file %@", outFile); + return [NSError errorWithDomain:MTRErrorDomain code:MTRErrorCodeInvalidState userInfo:nil]; } NSError * error = nil; - unsigned long long offset = 0; - - while (offset < size - content.length && error == nil) - { - if ([handle getOffset:&offset error:nil]) - { + unsigned long long count = 0; + + while (count < size && error == nil) { + unsigned long long remainingSize = size - count; + [handle seekToEndOfFile]; + if (remainingSize < content.length) { + NSString * substr = [content substringToIndex:remainingSize]; + [handle writeData:[substr dataUsingEncoding:NSUTF8StringEncoding] error:&error]; + count += remainingSize; + } else { [handle writeData:[content dataUsingEncoding:NSUTF8StringEncoding] error:&error]; + count += content.length; } } @@ -2661,11 +2643,14 @@ - (NSError *)generateLogFile:(NSString *)outFile return error; } -- (void)test029_DownloadEndUserSupportLog_NoTimeout +- (void)testDownloadLogOfType:(MTRDiagnosticLogType)logType + timeout:(NSTimeInterval)timeout + logFilePath:(NSString *)logFilePath + logFileSize:(unsigned long long)logFileSize + expectation:(XCTestExpectation *)expectation { MTRDeviceController * controller = sController; XCTAssertNotNil(controller); - XCTestExpectation * expectation = [self expectationWithDescription:@"End User Support Log Transfer Complete"]; dispatch_queue_t queue = dispatch_get_main_queue(); @@ -2673,97 +2658,65 @@ - (void)test029_DownloadEndUserSupportLog_NoTimeout MTRDevice * device = [MTRDevice deviceWithNodeID:@(kDeviceId) controller:controller]; XCTAssertNotNil(device); - NSString * outFile = [NSString stringWithFormat:@"/tmp/endusersupportlog.txt"]; - NSError * error = [self generateLogFile:outFile size:(10 * 1024)]; + NSError * error = [self generateLogFile:logFilePath size:logFileSize]; if (error != nil) { NSLog(@"Failed to generate log file"); return; } - [device downloadLogOfType:MTRDiagnosticLogTypeEndUserSupport timeout:0 queue:queue completion:^(NSURL * _Nullable logResult, NSError * error) { - XCTAssertNil(error); - XCTAssertNotNil(logResult); - - NSError * attributesError = nil; - NSDictionary * fileAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:[logResult path] error:&attributesError]; - - size_t fileSize = [fileAttributes fileSize]; - XCTAssertTrue(fileSize > 0); + [device downloadLogOfType:logType timeout:timeout queue:queue completion:^(NSURL * _Nullable logResult, NSError * error) { + if (timeout > 0) { + XCTAssertNotNil(error); + XCTAssertNil(logResult); + } else { + XCTAssertNil(error); + XCTAssertNotNil(logResult); + XCTAssertTrue([[NSFileManager defaultManager] contentsEqualAtPath:logFilePath andPath:[logResult path]]); + } [expectation fulfill]; }]; }); - [self waitForExpectations:[NSArray arrayWithObject:expectation] timeout:kDownloadLogTimeoutInSeconds]; } -- (void)test030_DownloadNetworkDiagnosticsLog_NoTimeout +- (void)test029_DownloadEndUserSupportLog_NoTimeout { - MTRDeviceController * controller = sController; - XCTAssertNotNil(controller); - XCTestExpectation * expectation = [self expectationWithDescription:@"Network Diagnostics Log Transfer Complete"]; - - dispatch_queue_t queue = dispatch_get_main_queue(); - - dispatch_async(queue, ^{ - MTRDevice * device = [MTRDevice deviceWithNodeID:@(kDeviceId) controller:controller]; - XCTAssertNotNil(device); - - NSString * outFile = [NSString stringWithFormat:@"/tmp/networkdiagnosticslog.txt"]; - NSError * error = [self generateLogFile:outFile size:(4 * 1024)]; - - if (error != nil) { - NSLog(@"Failed to generate log file"); - return; - } + XCTestExpectation * expectation = [self expectationWithDescription:@"End User Support Log Transfer Complete"]; + NSString * outFile = [NSString stringWithFormat:@"/tmp/endusersupportlog.txt"]; - [device downloadLogOfType:MTRDiagnosticLogTypeNetworkDiagnostics timeout:0 queue:queue completion:^(NSURL * _Nullable logResult, NSError * error) { - XCTAssertNil(error); - XCTAssertNotNil(logResult); + // Delay the request to allow the DiagnosticLogsServer to clean up and delete the DiagnosticLogsBDXTransferHandler object. + // TODO: Fix #30537 to keep retrying for a number of retry counts until we don't get busy anymore. + // Since these tests can be run in any order, we need this on all tests that download the diagnostic logs + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, kDownloadLogDelayTimeoutInSeconds * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ + [self testDownloadLogOfType:MTRDiagnosticLogTypeEndUserSupport timeout:0 logFilePath:outFile logFileSize:(10 * 1024) expectation:expectation]; + }); + [self waitForExpectations:[NSArray arrayWithObject:expectation] timeout:(kDownloadLogTimeoutInSeconds + kDownloadLogDelayTimeoutInSeconds)]; +} - NSError * attributesError = nil; - NSDictionary * fileAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:[logResult path] error:&attributesError]; +- (void)test030_DownloadNetworkDiagnosticsLog_NoTimeout +{ + XCTestExpectation * expectation = [self expectationWithDescription:@"Network Diagnostics Log Transfer Complete"]; + NSString * outFile = [NSString stringWithFormat:@"/tmp/networkdiagnosticslog.txt"]; - size_t fileSize = [fileAttributes fileSize]; - XCTAssertTrue(fileSize > 0); - [expectation fulfill]; - }]; + // Delay the request to allow the DiagnosticLogsServer to clean up and delete the DiagnosticLogsBDXTransferHandler object. + // TODO: Fix #30537 to keep retrying for a number of retry counts until we don't get busy anymore. + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, kDownloadLogDelayTimeoutInSeconds * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ + [self testDownloadLogOfType:MTRDiagnosticLogTypeNetworkDiagnostics timeout:0 logFilePath:outFile logFileSize:(4 * 1024) expectation:expectation]; }); - [self waitForExpectations:[NSArray arrayWithObject:expectation] timeout:kDownloadLogTimeoutInSeconds]; + [self waitForExpectations:[NSArray arrayWithObject:expectation] timeout:(kDownloadLogTimeoutInSeconds + kDownloadLogDelayTimeoutInSeconds)]; } - (void)test031_DownloadCrashLog_NoTimeout { - MTRDeviceController * controller = sController; - XCTAssertNotNil(controller); XCTestExpectation * expectation = [self expectationWithDescription:@"Crash Log Transfer Complete"]; + NSString * outFile = [NSString stringWithFormat:@"/tmp/crashlog.txt"]; - dispatch_queue_t queue = dispatch_get_main_queue(); - - dispatch_async(queue, ^{ - MTRDevice * device = [MTRDevice deviceWithNodeID:@(kDeviceId) controller:controller]; - XCTAssertNotNil(device); - - NSString * outFile = [NSString stringWithFormat:@"/tmp/crashlog.txt"]; - NSError * error = [self generateLogFile:outFile size:(3 * 1024)]; - - if (error != nil) { - NSLog(@"Failed to generate log file"); - return; - } - - [device downloadLogOfType:MTRDiagnosticLogTypeCrash timeout:0 queue:queue completion:^(NSURL * _Nullable logResult, NSError * error) { - XCTAssertNil(error); - XCTAssertNotNil(logResult); - - NSError * attributesError = nil; - NSDictionary * fileAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:[logResult path] error:&attributesError]; - - size_t fileSize = [fileAttributes fileSize]; - XCTAssertTrue(fileSize > 0); - [expectation fulfill]; - }]; + // Delay the request to allow the DiagnosticLogsServer to clean up and delete the DiagnosticLogsBDXTransferHandler object. + // TODO: Fix #30537 to keep retrying for a number of retry counts until we don't get busy anymore. + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, kDownloadLogDelayTimeoutInSeconds * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ + [self testDownloadLogOfType:MTRDiagnosticLogTypeCrash timeout:0 logFilePath:outFile logFileSize:(3 * 1024) expectation:expectation]; }); - [self waitForExpectations:[NSArray arrayWithObject:expectation] timeout:kDownloadLogTimeoutInSeconds]; + [self waitForExpectations:[NSArray arrayWithObject:expectation] timeout:(kDownloadLogTimeoutInSeconds + kDownloadLogDelayTimeoutInSeconds)]; } - (void)test900_SubscribeAllAttributes diff --git a/src/darwin/Framework/Matter.xcodeproj/project.pbxproj b/src/darwin/Framework/Matter.xcodeproj/project.pbxproj index 2afce4e1868212..a09c10f3f70fa7 100644 --- a/src/darwin/Framework/Matter.xcodeproj/project.pbxproj +++ b/src/darwin/Framework/Matter.xcodeproj/project.pbxproj @@ -304,8 +304,10 @@ B4E2621B2AA0D02000DBA5BC /* SleepCommand.mm in Sources */ = {isa = PBXBuildFile; fileRef = B4E262192AA0D01D00DBA5BC /* SleepCommand.mm */; }; B4E2621E2AA0D02D00DBA5BC /* WaitForCommissioneeCommand.mm in Sources */ = {isa = PBXBuildFile; fileRef = B4E2621C2AA0D02A00DBA5BC /* WaitForCommissioneeCommand.mm */; }; BA09EB43247477BA00605257 /* libCHIP.a in Frameworks */ = {isa = PBXBuildFile; fileRef = BA09EB3F2474762900605257 /* libCHIP.a */; }; - CFA0EDAD2AD9D75600C7FA40 /* MTRDiagnosticLogsTransferHandler.mm in Sources */ = {isa = PBXBuildFile; fileRef = CFA0EDAC2AD9D75600C7FA40 /* MTRDiagnosticLogsTransferHandler.mm */; }; - CFA0EDAF2AD9D76000C7FA40 /* MTRDiagnosticLogsTransferHandler.h in Headers */ = {isa = PBXBuildFile; fileRef = CFA0EDAE2AD9D76000C7FA40 /* MTRDiagnosticLogsTransferHandler.h */; }; + CF4BAE472B06EB8300022ED3 /* MTRDiagnosticsLogDownloadTask.mm in Sources */ = {isa = PBXBuildFile; fileRef = CF4BAE462B06EB8300022ED3 /* MTRDiagnosticsLogDownloadTask.mm */; }; + CF4BAE492B06EBC400022ED3 /* MTRDiagnosticsLogDownloadTask.h in Headers */ = {isa = PBXBuildFile; fileRef = CF4BAE482B06EBC400022ED3 /* MTRDiagnosticsLogDownloadTask.h */; }; + CFA0EDAD2AD9D75600C7FA40 /* MTRDiagnosticsLogTransferHandler.mm in Sources */ = {isa = PBXBuildFile; fileRef = CFA0EDAC2AD9D75600C7FA40 /* MTRDiagnosticsLogTransferHandler.mm */; }; + CFA0EDAF2AD9D76000C7FA40 /* MTRDiagnosticsLogTransferHandler.h in Headers */ = {isa = PBXBuildFile; fileRef = CFA0EDAE2AD9D76000C7FA40 /* MTRDiagnosticsLogTransferHandler.h */; }; D4772A46285AE98400383630 /* MTRClusterConstants.h in Headers */ = {isa = PBXBuildFile; fileRef = D4772A45285AE98300383630 /* MTRClusterConstants.h */; settings = {ATTRIBUTES = (Public, ); }; }; /* End PBXBuildFile section */ @@ -677,8 +679,10 @@ B4E2621C2AA0D02A00DBA5BC /* WaitForCommissioneeCommand.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = WaitForCommissioneeCommand.mm; sourceTree = ""; }; BA09EB3F2474762900605257 /* libCHIP.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libCHIP.a; path = lib/libCHIP.a; sourceTree = BUILT_PRODUCTS_DIR; }; BA107AEE2470CFBB004287EB /* chip_xcode_build_connector.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = chip_xcode_build_connector.sh; sourceTree = ""; }; - CFA0EDAC2AD9D75600C7FA40 /* MTRDiagnosticLogsTransferHandler.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MTRDiagnosticLogsTransferHandler.mm; sourceTree = ""; }; - CFA0EDAE2AD9D76000C7FA40 /* MTRDiagnosticLogsTransferHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MTRDiagnosticLogsTransferHandler.h; sourceTree = ""; }; + CF4BAE462B06EB8300022ED3 /* MTRDiagnosticsLogDownloadTask.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = MTRDiagnosticsLogDownloadTask.mm; sourceTree = ""; }; + CF4BAE482B06EBC400022ED3 /* MTRDiagnosticsLogDownloadTask.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MTRDiagnosticsLogDownloadTask.h; sourceTree = ""; }; + CFA0EDAC2AD9D75600C7FA40 /* MTRDiagnosticsLogTransferHandler.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MTRDiagnosticsLogTransferHandler.mm; sourceTree = ""; }; + CFA0EDAE2AD9D76000C7FA40 /* MTRDiagnosticsLogTransferHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MTRDiagnosticsLogTransferHandler.h; sourceTree = ""; }; D437613E285BDC0D0051FEA2 /* MTRErrorTestUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MTRErrorTestUtils.h; sourceTree = ""; }; D437613F285BDC0D0051FEA2 /* MTRTestKeys.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MTRTestKeys.h; sourceTree = ""; }; D4376140285BDC0D0051FEA2 /* MTRTestStorage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MTRTestStorage.h; sourceTree = ""; }; @@ -1109,8 +1113,10 @@ B202528F2459E34F00F97062 /* CHIP */ = { isa = PBXGroup; children = ( - CFA0EDAE2AD9D76000C7FA40 /* MTRDiagnosticLogsTransferHandler.h */, - CFA0EDAC2AD9D75600C7FA40 /* MTRDiagnosticLogsTransferHandler.mm */, + CFA0EDAE2AD9D76000C7FA40 /* MTRDiagnosticsLogTransferHandler.h */, + CF4BAE462B06EB8300022ED3 /* MTRDiagnosticsLogDownloadTask.mm */, + CF4BAE482B06EBC400022ED3 /* MTRDiagnosticsLogDownloadTask.h */, + CFA0EDAC2AD9D75600C7FA40 /* MTRDiagnosticsLogTransferHandler.mm */, 1E4D655129C30A8700BC3478 /* MTRCommissionableBrowser.mm */, 1E4D654C29C208DD00BC3478 /* MTRCommissionableBrowser.h */, 1E4D654D29C208DD00BC3478 /* MTRCommissionableBrowserDelegate.h */, @@ -1447,12 +1453,13 @@ 5178E6822AE098520069DF72 /* MTRCommissionableBrowserResult_Internal.h in Headers */, 3CF134AB289D8DF70017A19E /* MTRDeviceAttestationInfo.h in Headers */, B2E0D7B2245B0B5C003C5B48 /* MTRManualSetupPayloadParser.h in Headers */, - CFA0EDAF2AD9D76000C7FA40 /* MTRDiagnosticLogsTransferHandler.h in Headers */, + CFA0EDAF2AD9D76000C7FA40 /* MTRDiagnosticsLogTransferHandler.h in Headers */, 3CF134A7289D8ADA0017A19E /* MTRCSRInfo.h in Headers */, B2E0D7B1245B0B5C003C5B48 /* Matter.h in Headers */, 7596A84428762729004DAE0E /* MTRDevice.h in Headers */, B2E0D7B8245B0B5C003C5B48 /* MTRSetupPayload.h in Headers */, 1E4D654F29C208DD00BC3478 /* MTRCommissionableBrowser.h in Headers */, + CF4BAE492B06EBC400022ED3 /* MTRDiagnosticsLogDownloadTask.h in Headers */, 3D843756294AD25A0070D20A /* MTRCertificateInfo.h in Headers */, 7596A83E28751220004DAE0E /* MTRBaseClusters_Internal.h in Headers */, 997DED182695344800975E97 /* MTRThreadOperationalDataset.h in Headers */, @@ -1762,6 +1769,7 @@ 5A6FEC9627B5983000F25F42 /* MTRDeviceControllerXPCConnection.mm in Sources */, 511913FB28C100EF009235E9 /* MTRBaseSubscriptionCallback.mm in Sources */, 510470FB2A2F7DF60053EA7E /* MTRBackwardsCompatShims.mm in Sources */, + CF4BAE472B06EB8300022ED3 /* MTRDiagnosticsLogDownloadTask.mm in Sources */, 5173A47629C0E2ED00F67F48 /* MTRFabricInfo.mm in Sources */, 5ACDDD7D27CD16D200EFD68A /* MTRClusterStateCacheContainer.mm in Sources */, 513DDB8A2761F6F900DAA01A /* MTRAttributeTLVValueDecoder.mm in Sources */, @@ -1783,7 +1791,7 @@ 5A6FEC9827B5C6AF00F25F42 /* MTRDeviceOverXPC.mm in Sources */, 514654492A72F9DF00904E61 /* MTRDemuxingStorage.mm in Sources */, 1E4D655229C30A8700BC3478 /* MTRCommissionableBrowser.mm in Sources */, - CFA0EDAD2AD9D75600C7FA40 /* MTRDiagnosticLogsTransferHandler.mm in Sources */, + CFA0EDAD2AD9D75600C7FA40 /* MTRDiagnosticsLogTransferHandler.mm in Sources */, 3DA1A3562ABAB3B4004F0BB9 /* MTRAsyncWorkQueue.mm in Sources */, 3DECCB722934AFE200585AEC /* MTRLogging.mm in Sources */, 7596A84528762729004DAE0E /* MTRDevice.mm in Sources */, diff --git a/src/lib/core/CHIPConfig.h b/src/lib/core/CHIPConfig.h index ff23e27ce2006a..f7010d5875c230 100644 --- a/src/lib/core/CHIPConfig.h +++ b/src/lib/core/CHIPConfig.h @@ -1621,6 +1621,7 @@ extern const char CHIP_NON_PRODUCTION_MARKER[]; */ #ifndef CHIP_CONFIG_ICD_OBSERVERS_POOL_SIZE #define CHIP_CONFIG_ICD_OBSERVERS_POOL_SIZE 2 +#endif /** * @def CHIP_CONFIG_ENABLE_BDX_LOG_TRANSFER diff --git a/src/protocols/bdx/TransferFacilitator.h b/src/protocols/bdx/TransferFacilitator.h index 0ed43918766aba..139abbee63c7a7 100644 --- a/src/protocols/bdx/TransferFacilitator.h +++ b/src/protocols/bdx/TransferFacilitator.h @@ -47,9 +47,6 @@ class TransferFacilitator : public Messaging::ExchangeDelegate, public Messaging TransferFacilitator() : mExchangeCtx(nullptr), mSystemLayer(nullptr), mPollFreq(kDefaultPollFreq) {} ~TransferFacilitator() override = default; - CHIP_ERROR OnMessageReceived(chip::Messaging::ExchangeContext * ec, const chip::PayloadHeader & payloadHeader, - chip::System::PacketBufferHandle && payload) override; - private: //// UnsolicitedMessageHandler Implementation //// CHIP_ERROR OnUnsolicitedMessageReceived(const PayloadHeader & payloadHeader, ExchangeDelegate *& newDelegate) override @@ -75,6 +72,8 @@ class TransferFacilitator : public Messaging::ExchangeDelegate, public Messaging virtual void HandleTransferSessionOutput(TransferSession::OutputEvent & event) = 0; protected: + CHIP_ERROR OnMessageReceived(chip::Messaging::ExchangeContext * ec, const chip::PayloadHeader & payloadHeader, + chip::System::PacketBufferHandle && payload) override; /** * The callback for when the poll timer expires. The poll timer regulates how often the TransferSession is polled. */