diff --git a/.github/workflows/darwin.yaml b/.github/workflows/darwin.yaml index 04648f4da135ee..5acbf4a8065a94 100644 --- a/.github/workflows/darwin.yaml +++ b/.github/workflows/darwin.yaml @@ -103,7 +103,7 @@ jobs: # target versions instead? run: | mkdir -p /tmp/darwin/framework-tests - ../../../out/debug/chip-all-clusters-app --interface-id -1 > >(tee /tmp/darwin/framework-tests/all-cluster-app.log) 2> >(tee /tmp/darwin/framework-tests/all-cluster-app-err.log >&2) & + ../../../out/debug/chip-all-clusters-app --interface-id -1 --end_user_support_log /tmp/endusersupportlog.txt --network_diagnostics_log /tmp/networkdiagnosticslog.txt --crash_log /tmp/crashlog.txt > >(tee /tmp/darwin/framework-tests/all-cluster-app.log) 2> >(tee /tmp/darwin/framework-tests/all-cluster-app-err.log >&2) & ../../../out/debug/chip-all-clusters-app --interface-id -1 --dac_provider ../../../credentials/development/commissioner_dut/struct_cd_origin_pid_vid_correct/test_case_vector.json --product-id 32768 --discriminator 3839 --secured-device-port 5539 --KVS /tmp/chip-all-clusters-app-kvs2 > >(tee /tmp/darwin/framework-tests/all-cluster-app-origin-vid.log) 2> >(tee /tmp/darwin/framework-tests/all-cluster-app-origin-vid-err.log >&2) & # Disable BLE because the app does not have the permission to use # it and that may crash the CI. diff --git a/examples/all-clusters-app/all-clusters-common/all-clusters-app.matter b/examples/all-clusters-app/all-clusters-common/all-clusters-app.matter index 191a87a0088dc2..ab51be8d7e561c 100644 --- a/examples/all-clusters-app/all-clusters-common/all-clusters-app.matter +++ b/examples/all-clusters-app/all-clusters-common/all-clusters-app.matter @@ -1570,6 +1570,13 @@ server cluster DiagnosticLogs = 50 { optional char_string<32> transferFileDesignator = 2; } + response struct RetrieveLogsResponse = 1 { + StatusEnum status = 0; + LONG_OCTET_STRING logContent = 1; + optional epoch_us UTCTimeStamp = 2; + optional systime_us timeSinceBoot = 3; + } + command RetrieveLogsRequest(RetrieveLogsRequestRequest): RetrieveLogsResponse = 0; } @@ -5386,10 +5393,15 @@ endpoint 0 { } server cluster DiagnosticLogs { + callback attribute generatedCommandList; + callback attribute acceptedCommandList; + callback attribute eventList; + callback attribute attributeList; ram attribute featureMap default = 0; ram attribute clusterRevision default = 1; handle command RetrieveLogsRequest; + handle command RetrieveLogsResponse; } server cluster GeneralDiagnostics { diff --git a/examples/all-clusters-app/all-clusters-common/all-clusters-app.zap b/examples/all-clusters-app/all-clusters-common/all-clusters-app.zap index 7984e376753968..4da89f94b8f8d7 100644 --- a/examples/all-clusters-app/all-clusters-common/all-clusters-app.zap +++ b/examples/all-clusters-app/all-clusters-common/all-clusters-app.zap @@ -2643,9 +2643,81 @@ "source": "client", "isIncoming": 1, "isEnabled": 1 + }, + { + "name": "RetrieveLogsResponse", + "code": 1, + "mfgCode": null, + "source": "server", + "isIncoming": 0, + "isEnabled": 1 } ], "attributes": [ + { + "name": "GeneratedCommandList", + "code": 65528, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AcceptedCommandList", + "code": 65529, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "EventList", + "code": 65530, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AttributeList", + "code": 65531, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, { "name": "FeatureMap", "code": 65532, @@ -14856,7 +14928,7 @@ "code": 3, "mfgCode": null, "side": "server", - "type": "temperature", + "type": "int16u", "included": 1, "storageOption": "RAM", "singleton": 0, @@ -21901,5 +21973,6 @@ "endpointId": 65534, "networkId": 0 } - ] + ], + "log": [] } \ No newline at end of file diff --git a/examples/all-clusters-app/all-clusters-common/include/diagnostic-logs-provider-delegate-impl.h b/examples/all-clusters-app/all-clusters-common/include/diagnostic-logs-provider-delegate-impl.h new file mode 100644 index 00000000000000..f39b5dfc595fa3 --- /dev/null +++ b/examples/all-clusters-app/all-clusters-common/include/diagnostic-logs-provider-delegate-impl.h @@ -0,0 +1,78 @@ +/* + * + * Copyright (c) 2023 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include + +#include + +namespace chip { +namespace app { +namespace Clusters { +namespace DiagnosticLogs { + +/** + * The application delegate to statically define the options. + */ + +class LogProvider : public LogProviderDelegate +{ + static LogSessionHandle sLogSessionHandle; + static LogProvider sInstance; + +public: + LogSessionHandle StartLogCollection(IntentEnum logType); + + uint64_t GetNextChunk(LogSessionHandle logSessionHandle, chip::MutableByteSpan & outBuffer, bool & outIsEOF); + + void EndLogCollection(LogSessionHandle logSessionHandle); + + uint64_t GetTotalNumberOfBytesConsumed(LogSessionHandle logSessionHandle); + + void SetEndUserSupportLogFileDesignator(const char * logFileName); + + void SetNetworkDiagnosticsLogFileDesignator(const char * logFileName); + + void SetCrashLogFileDesignator(const char * logFileName); + + LogProvider(){}; + + ~LogProvider(){}; + + static inline LogProvider & getLogProvider() { return sInstance; } + +private: + const char * GetLogFilePath(IntentEnum logType); + + char mEndUserSupportLogFileDesignator[kLogFileDesignatorMaxLen]; + char mNetworkDiagnosticsLogFileDesignator[kLogFileDesignatorMaxLen]; + char mCrashLogFileDesignator[kLogFileDesignatorMaxLen]; + + std::ifstream mFileStream; + + LogSessionHandle mLogSessionHandle; + + uint64_t mTotalNumberOfBytesConsumed; +}; + +} // namespace DiagnosticLogs +} // namespace Clusters +} // namespace app +} // namespace chip diff --git a/examples/all-clusters-app/all-clusters-common/src/diagnostic-logs-provider-delegate-impl.cpp b/examples/all-clusters-app/all-clusters-common/src/diagnostic-logs-provider-delegate-impl.cpp new file mode 100644 index 00000000000000..401afc1e7359f8 --- /dev/null +++ b/examples/all-clusters-app/all-clusters-common/src/diagnostic-logs-provider-delegate-impl.cpp @@ -0,0 +1,131 @@ +/* + * + * Copyright (c) 2023 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "diagnostic-logs-provider-delegate-impl.h" +#include +#include + +using namespace chip; +using namespace chip::app::Clusters::DiagnosticLogs; + +constexpr uint16_t kChunkSizeZero = 0; + +LogProvider LogProvider::sInstance; + +LogSessionHandle LogProvider::sLogSessionHandle; + +LogSessionHandle LogProvider::StartLogCollection(IntentEnum logType) +{ + + mTotalNumberOfBytesConsumed = 0; + + // Open the file of type + const char * fileName = GetLogFilePath(logType); + if (fileName != nullptr) + { + mFileStream.open(fileName, std::ios_base::binary | std::ios_base::in); + if (!mFileStream.good()) + { + ChipLogError(BDX, "Failed to open the log file"); + return kInvalidLogSessionHandle; + } + sLogSessionHandle++; + mLogSessionHandle = sLogSessionHandle; + } + else + { + mLogSessionHandle = kInvalidLogSessionHandle; + } + return mLogSessionHandle; +} + +uint64_t LogProvider::GetNextChunk(LogSessionHandle logSessionHandle, chip::MutableByteSpan & outBuffer, bool & outIsEOF) +{ + if (logSessionHandle != mLogSessionHandle && outBuffer.size() == 0) + { + return kChunkSizeZero; + } + + if (!mFileStream.is_open()) + { + ChipLogError(BDX, "File is not open"); + return kChunkSizeZero; + } + + mFileStream.seekg(static_cast(mTotalNumberOfBytesConsumed)); + mFileStream.read(reinterpret_cast(outBuffer.data()), kLogContentMaxSize); + + if (!(mFileStream.good() || mFileStream.eof())) + { + ChipLogError(BDX, "Failed to read the log file"); + mFileStream.close(); + return kChunkSizeZero; + } + + uint64_t bytesRead = static_cast(mFileStream.gcount()); + outIsEOF = (mFileStream.peek() == EOF); + + mTotalNumberOfBytesConsumed += bytesRead; + return bytesRead; +} + +void LogProvider::EndLogCollection(LogSessionHandle logSessionHandle) +{ + if (logSessionHandle == mLogSessionHandle && mFileStream.is_open()) + { + mFileStream.close(); + } +} + +uint64_t LogProvider::GetTotalNumberOfBytesConsumed(LogSessionHandle logSessionHandle) +{ + if (logSessionHandle == mLogSessionHandle) + { + return mTotalNumberOfBytesConsumed; + } + return kChunkSizeZero; +} + +const char * LogProvider::GetLogFilePath(IntentEnum logType) +{ + switch (logType) + { + case IntentEnum::kEndUserSupport: + return mEndUserSupportLogFileDesignator; + case IntentEnum::kNetworkDiag: + return mNetworkDiagnosticsLogFileDesignator; + case IntentEnum::kCrashLogs: + return mCrashLogFileDesignator; + default: + return nullptr; + } +} + +void LogProvider::SetEndUserSupportLogFileDesignator(const char * logFileName) +{ + strncpy(mEndUserSupportLogFileDesignator, logFileName, strlen(logFileName)); +} + +void LogProvider::SetNetworkDiagnosticsLogFileDesignator(const char * logFileName) +{ + strncpy(mNetworkDiagnosticsLogFileDesignator, logFileName, strlen(logFileName)); +} + +void LogProvider::SetCrashLogFileDesignator(const char * logFileName) +{ + strncpy(mCrashLogFileDesignator, logFileName, strlen(logFileName)); +} diff --git a/examples/all-clusters-app/linux/AppOptions.cpp b/examples/all-clusters-app/linux/AppOptions.cpp index ef42660972f8d9..0345945ba1a0ab 100644 --- a/examples/all-clusters-app/linux/AppOptions.cpp +++ b/examples/all-clusters-app/linux/AppOptions.cpp @@ -18,20 +18,29 @@ #include "AppOptions.h" +#include #include #include using namespace chip::ArgParser; +using namespace chip::app::Clusters::DiagnosticLogs; using chip::ArgParser::OptionDef; using chip::ArgParser::OptionSet; using chip::ArgParser::PrintArgError; -constexpr uint16_t kOptionDacProviderFilePath = 0xFF01; -constexpr uint16_t kOptionMinCommissioningTimeout = 0xFF02; +constexpr uint16_t kOptionDacProviderFilePath = 0xFF01; +constexpr uint16_t kOptionMinCommissioningTimeout = 0xFF02; +constexpr uint16_t kOptionEndUserSupportFilePath = 0xFF03; +constexpr uint16_t kOptionNetworkDiagnosticsFilePath = 0xFF04; +constexpr uint16_t kOptionCrashFilePath = 0xFF05; static chip::Credentials::Examples::TestHarnessDACProvider mDacProvider; +static char mEndUserSupportLogFileDesignator[kLogFileDesignatorMaxLen]; +static char mNetworkDiagnosticsLogFileDesignator[kLogFileDesignatorMaxLen]; +static char mCrashLogFileDesignator[kLogFileDesignatorMaxLen]; + bool AppOptions::HandleOptions(const char * program, OptionSet * options, int identifier, const char * name, const char * value) { bool retval = true; @@ -45,6 +54,33 @@ bool AppOptions::HandleOptions(const char * program, OptionSet * options, int id commissionMgr.OverrideMinCommissioningTimeout(chip::System::Clock::Seconds16(static_cast(atoi(value)))); break; } + case kOptionEndUserSupportFilePath: { + if (strlen(value) > kLogFileDesignatorMaxLen) + { + PrintArgError("%s: Invalid file path length. Must be less that %d: %d\n", program, kLogFileDesignatorMaxLen, + strlen(value)); + } + strncpy(mEndUserSupportLogFileDesignator, value, strlen(value)); + break; + } + case kOptionNetworkDiagnosticsFilePath: { + if (strlen(value) > kLogFileDesignatorMaxLen) + { + PrintArgError("%s: Invalid file path length. Must be less that %d: %d\n", program, kLogFileDesignatorMaxLen, + strlen(value)); + } + strncpy(mNetworkDiagnosticsLogFileDesignator, value, strlen(value)); + break; + } + case kOptionCrashFilePath: { + if (strlen(value) > kLogFileDesignatorMaxLen) + { + PrintArgError("%s: Invalid file path length. Must be less that %d: %d\n", program, kLogFileDesignatorMaxLen, + strlen(value)); + } + strncpy(mCrashLogFileDesignator, value, strlen(value)); + break; + } default: PrintArgError("%s: INTERNAL ERROR: Unhandled option: %s\n", program, name); retval = false; @@ -59,6 +95,9 @@ OptionSet * AppOptions::GetOptions() static OptionDef optionsDef[] = { { "dac_provider", kArgumentRequired, kOptionDacProviderFilePath }, { "min_commissioning_timeout", kArgumentRequired, kOptionMinCommissioningTimeout }, + { "end_user_support_log", kArgumentRequired, kOptionEndUserSupportFilePath }, + { "network_diagnostics_log", kArgumentRequired, kOptionNetworkDiagnosticsFilePath }, + { "crash_log", kArgumentRequired, kOptionCrashFilePath }, {}, }; @@ -68,6 +107,12 @@ OptionSet * AppOptions::GetOptions() " A json file with data used by the example dac provider to validate device attestation procedure.\n" " --min_commissioning_timeout \n" " The minimum time in seconds during which commissioning session establishment is allowed by the Node.\n" + " --end_user_support_log \n" + " The end user support log file to be used for diagnostic logs transfer.\n" + " --network_diagnostics_log \n" + " The network diagnostics log file to be used for diagnostic logs transfer.\n" + " --crash_log \n" + " The crash log file to be used for diagnostic logs transfer\n" }; return &options; @@ -77,3 +122,18 @@ chip::Credentials::DeviceAttestationCredentialsProvider * AppOptions::GetDACProv { return &mDacProvider; } + +char * AppOptions::GetEndUserSupportLogFileDesignator() +{ + return mEndUserSupportLogFileDesignator; +} + +char * AppOptions::GetNetworkDiagnosticsLogFileDesignator() +{ + return mNetworkDiagnosticsLogFileDesignator; +} + +char * AppOptions::GetCrashLogFileDesignator() +{ + return mCrashLogFileDesignator; +} diff --git a/examples/all-clusters-app/linux/AppOptions.h b/examples/all-clusters-app/linux/AppOptions.h index 3073c66176331f..8a0fb4d3fba965 100644 --- a/examples/all-clusters-app/linux/AppOptions.h +++ b/examples/all-clusters-app/linux/AppOptions.h @@ -27,6 +27,9 @@ class AppOptions public: static chip::ArgParser::OptionSet * GetOptions(); static chip::Credentials::DeviceAttestationCredentialsProvider * GetDACProvider(); + static char * GetEndUserSupportLogFileDesignator(); + static char * GetNetworkDiagnosticsLogFileDesignator(); + static char * GetCrashLogFileDesignator(); private: static bool HandleOptions(const char * program, chip::ArgParser::OptionSet * options, int identifier, const char * name, diff --git a/examples/all-clusters-app/linux/BUILD.gn b/examples/all-clusters-app/linux/BUILD.gn index e41cb9843c42c7..df6c44d59d9d6c 100644 --- a/examples/all-clusters-app/linux/BUILD.gn +++ b/examples/all-clusters-app/linux/BUILD.gn @@ -25,6 +25,7 @@ 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/all-clusters-common/src/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", diff --git a/examples/all-clusters-app/linux/include/CHIPProjectAppConfig.h b/examples/all-clusters-app/linux/include/CHIPProjectAppConfig.h index 7346ebd2807bfb..efe6b19c26bb89 100644 --- a/examples/all-clusters-app/linux/include/CHIPProjectAppConfig.h +++ b/examples/all-clusters-app/linux/include/CHIPProjectAppConfig.h @@ -42,3 +42,5 @@ // Marks that a ModeBase Derived cluster is being used. #define EMBER_AF_PLUGIN_MODE_BASE + +#define CHIP_CONFIG_ENABLE_BDX_LOG_TRANSFER 1 diff --git a/examples/all-clusters-app/linux/main-common.cpp b/examples/all-clusters-app/linux/main-common.cpp index a6deafde128983..9a8678428d7209 100644 --- a/examples/all-clusters-app/linux/main-common.cpp +++ b/examples/all-clusters-app/linux/main-common.cpp @@ -17,8 +17,10 @@ */ #include "AllClustersCommandDelegate.h" +#include "AppOptions.h" #include "WindowCoveringManager.h" #include "air-quality-instance.h" +#include "diagnostic-logs-provider-delegate-impl.h" #include "dishwasher-mode.h" #include "include/tv-callbacks.h" #include "laundry-washer-controls-delegate-impl.h" @@ -30,6 +32,7 @@ #include #include #include +#include #include #include #include @@ -266,3 +269,13 @@ void emberAfWindowCoveringClusterInitCallback(chip::EndpointId endpoint) Clusters::WindowCovering::SetDefaultDelegate(endpoint, &sWindowCoveringManager); Clusters::WindowCovering::ConfigStatusUpdateFeatures(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()); +} diff --git a/src/app/bdx/DiagnosticLogsBDXTransferHandler.cpp b/src/app/bdx/DiagnosticLogsBDXTransferHandler.cpp new file mode 100644 index 00000000000000..3bcb9656c10de3 --- /dev/null +++ b/src/app/bdx/DiagnosticLogsBDXTransferHandler.cpp @@ -0,0 +1,250 @@ +/* + * + * Copyright (c) 2021 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#if CHIP_CONFIG_ENABLE_BDX_LOG_TRANSFER +#include "DiagnosticLogsBDXTransferHandler.h" +#include +#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; + +// BDX Transfer Params +constexpr System::Clock::Timeout kBdxPollIntervalMs = System::Clock::Milliseconds32(50); + +// Timeout for the BDX transfer session. The OTA Spec mandates this should be >= 5 minutes. +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) +{ + if (mInitialized) + { + // Reset stale connection from the same Node if exists. + Reset(); + } + + VerifyOrReturnError(exchangeCtx != nullptr, CHIP_ERROR_INCORRECT_STATE); + VerifyOrReturnError(delegate != nullptr, CHIP_ERROR_INCORRECT_STATE); + + mExchangeCtx = exchangeCtx->GetExchangeMgr()->NewContext(exchangeCtx->GetSessionHandle(), this); + VerifyOrReturnError(mExchangeCtx != nullptr, CHIP_ERROR_NO_MEMORY); + + mIntent = intent; + mDelegate = delegate; + mFabricIndex.SetValue(fabricIndex); + mNodeId.SetValue(nodeId); + mNumBytesSent = 0; + + TransferSession::TransferInitData initOptions; + initOptions.TransferCtlFlags = bdx::TransferControlFlags::kSenderDrive; + initOptions.MaxBlockSize = kBdxMaxBlockSize; + initOptions.FileDesLength = static_cast(fileDesignator.size()); + initOptions.FileDesignator = reinterpret_cast(fileDesignator.data()); + + CHIP_ERROR err = Initiator::InitiateTransfer(&DeviceLayer::SystemLayer(), bdx::TransferRole::kSender, initOptions, kBdxTimeout, + kBdxPollIntervalMs); + if (err != CHIP_NO_ERROR) + { + LogErrorOnFailure(err); + return err; + } + + mInitialized = true; + return CHIP_NO_ERROR; +} + +void DiagnosticLogsBDXTransferHandler::HandleTransferSessionOutput(TransferSession::OutputEvent & event) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + if (event.EventType != TransferSession::OutputEventType::kNone) + { + ChipLogDetail(BDX, "OutputEvent type: %s", event.ToString(event.EventType)); + } + + switch (event.EventType) + { + case TransferSession::OutputEventType::kAckEOFReceived: + mStopPolling = true; + Reset(); + break; + case TransferSession::OutputEventType::kStatusReceived: + ChipLogError(BDX, "Got StatusReport %x", static_cast(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(); + break; + case TransferSession::OutputEventType::kMsgToSend: { + Messaging::SendFlags sendFlags; + if (!event.msgTypeData.HasMessageType(chip::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); + } + VerifyOrReturn(mExchangeCtx != nullptr); + err = mExchangeCtx->SendMessage(event.msgTypeData.ProtocolId, event.msgTypeData.MessageType, std::move(event.MsgData), + sendFlags); + + if (err == CHIP_NO_ERROR) + { + if (!sendFlags.Has(chip::Messaging::SendMessageFlags::kExpectResponse)) + { + // After sending the StatusReport, exchange context gets closed so, set mExchangeCtx to null + mExchangeCtx = nullptr; + } + } + else + { + ChipLogError(BDX, "SendMessage failed: %" CHIP_ERROR_FORMAT, err.Format()); + DiagnosticLogsServer::Instance().HandleBDXResponse(err); + Reset(); + } + + break; + } + case TransferSession::OutputEventType::kAcceptReceived: { + mLogSessionHandle = mDelegate->StartLogCollection(mIntent); + + if (mLogSessionHandle == kInvalidLogSessionHandle) + { + ChipLogError(BDX, "Invalid log session handle"); + DiagnosticLogsServer::Instance().HandleBDXResponse(CHIP_ERROR_INCORRECT_STATE); + mTransfer.AbortTransfer(StatusCode::kUnknown); + return; + } + // Send a response to the RetreiveLogRequest since we got a SendAccept message. + DiagnosticLogsServer::Instance().HandleBDXResponse(CHIP_NO_ERROR); + [[fallthrough]]; + } + case TransferSession::OutputEventType::kAckReceived: { + uint16_t blockSize = mTransfer.GetTransferBlockSize(); + uint16_t bytesToRead = blockSize; + + if (mTransfer.GetTransferLength() > 0 && mNumBytesSent + blockSize > mTransfer.GetTransferLength()) + { + // cast should be safe because of condition above + bytesToRead = static_cast(mTransfer.GetTransferLength() - mNumBytesSent); + } + + chip::System::PacketBufferHandle blockBuf = chip::System::PacketBufferHandle::New(bytesToRead); + if (blockBuf.IsNull()) + { + mTransfer.AbortTransfer(StatusCode::kUnknown); + return; + } + + MutableByteSpan buffer; + + buffer = MutableByteSpan(blockBuf->Start(), bytesToRead); + + 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); + + if (bytesRead == 0) + { + mTransfer.AbortTransfer(StatusCode::kUnknown); + return; + } + + if (isEOF) + { + mDelegate->EndLogCollection(mLogSessionHandle); + mLogSessionHandle = kInvalidLogSessionHandle; + } + + TransferSession::BlockData blockData; + blockData.Data = blockBuf->Start(); + blockData.Length = static_cast(bytesRead); + blockData.IsEof = isEOF; + mNumBytesSent += static_cast(blockData.Length); + + err = mTransfer.PrepareBlock(blockData); + if (err != CHIP_NO_ERROR) + { + ChipLogError(BDX, "PrepareBlock failed: %" CHIP_ERROR_FORMAT, err.Format()); + mTransfer.AbortTransfer(StatusCode::kUnknown); + } + break; + } + case TransferSession::OutputEventType::kNone: + case TransferSession::OutputEventType::kInitReceived: + case TransferSession::OutputEventType::kQueryReceived: + break; + default: + // TransferSession should prevent this case from happening. + ChipLogError(BDX, "Unsupported event type"); + break; + } +} + +void DiagnosticLogsBDXTransferHandler::Reset() +{ + assertChipStackLockedByCurrentThread(); + + mFabricIndex.ClearValue(); + mNodeId.ClearValue(); + + Initiator::ResetTransfer(); + + if (mExchangeCtx != nullptr) + { + mExchangeCtx->Close(); + mExchangeCtx = nullptr; + } + + if (mDelegate != nullptr) + { + mDelegate->EndLogCollection(mLogSessionHandle); + mDelegate = nullptr; + } + mLogSessionHandle = kInvalidLogSessionHandle; + mInitialized = false; + mNumBytesSent = 0; +} + +#endif diff --git a/src/app/bdx/DiagnosticLogsBDXTransferHandler.h b/src/app/bdx/DiagnosticLogsBDXTransferHandler.h new file mode 100644 index 00000000000000..cce34f5778940f --- /dev/null +++ b/src/app/bdx/DiagnosticLogsBDXTransferHandler.h @@ -0,0 +1,77 @@ +/* + * + * Copyright (c) 2023 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#if CHIP_CONFIG_ENABLE_BDX_LOG_TRANSFER + +#pragma once + +#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 from the accessory and sends the block accross to the receiver until + * all the blocks have been transferred and the accessory reports that end of file is reached. + */ +class DiagnosticLogsBDXTransferHandler : public chip::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; + + void Reset(); + +private: + void SendNextBlock(MutableByteSpan & buffer); + + chip::Optional mFabricIndex; + chip::Optional mNodeId; + + chip::Messaging::ExchangeContext * mExchangeCtx; + + bool mInitialized; + + uint64_t mNumBytesSent; + + LogSessionHandle mLogSessionHandle; + + LogProviderDelegate * mDelegate; + + IntentEnum mIntent; +}; + +} // namespace DiagnosticLogs +} // namespace Clusters +} // namespace app +} // namespace chip +#endif diff --git a/src/app/chip_data_model.gni b/src/app/chip_data_model.gni index 85cc2025a586dd..d6612ebe68e821 100644 --- a/src/app/chip_data_model.gni +++ b/src/app/chip_data_model.gni @@ -320,6 +320,14 @@ template("chip_data_model") { "${_app_root}/clusters/${cluster}/${cluster}.cpp", "${_app_root}/clusters/${cluster}/${cluster}.h", ] + } else if (cluster == "diagnostic-logs-server") { + sources += [ + "${_app_root}/bdx/DiagnosticLogsBDXTransferHandler.cpp", + "${_app_root}/bdx/DiagnosticLogsBDXTransferHandler.h", + "${_app_root}/clusters/${cluster}/${cluster}.cpp", + "${_app_root}/clusters/${cluster}/${cluster}.h", + "${_app_root}/clusters/${cluster}/diagnostic-logs-provider-delegate.h", + ] } else { sources += [ "${_app_root}/clusters/${cluster}/${cluster}.cpp" ] } 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 new file mode 100644 index 00000000000000..99905874a379a8 --- /dev/null +++ b/src/app/clusters/diagnostic-logs-server/diagnostic-logs-provider-delegate.h @@ -0,0 +1,86 @@ +/* + * + * Copyright (c) 2023 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include + +namespace chip { +namespace app { +namespace Clusters { +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; + +/** @brief + * Defines methods for implementing application-specific logic for getting the log data from the diagnostic logs provider + * LogProviderDelegate. + */ +class LogProviderDelegate +{ +public: + LogProviderDelegate() = default; + + virtual ~LogProviderDelegate() = default; + + /** + * Called to start log collection for the log type passed in. + * + * @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. + */ + 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. + * + * @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; + + /** + * Called to end log collection for the log session identified by logSessionHandle + * + * @param[in] logSessionHandle The unique handle for this log session returned from a call to StartLogCollection. + * + */ + virtual void EndLogCollection(LogSessionHandle logSessionHandle) = 0; + + /** + * Called to get the total number of bytes consumed from the log session identified by logSessionHandle + * + * @param[in] logSessionHandle The unique handle for this log session returned from a call to StartLogCollection. + * + */ + virtual uint64_t GetTotalNumberOfBytesConsumed(LogSessionHandle logSessionHandle) = 0; +}; + +} // namespace DiagnosticLogs +} // namespace Clusters +} // namespace app +} // namespace chip 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 2965beff147d3b..19cdad18e6db45 100644 --- a/src/app/clusters/diagnostic-logs-server/diagnostic-logs-server.cpp +++ b/src/app/clusters/diagnostic-logs-server/diagnostic-logs-server.cpp @@ -1,6 +1,6 @@ /** * - * Copyright (c) 2021 Project CHIP Authors + * 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. @@ -15,103 +15,276 @@ * limitations under the License. */ +#include + +#include "diagnostic-logs-server.h" + #include #include #include #include -#include -#include -#include -#include +#include +#include #include +#include + +using namespace chip; +using namespace chip::app; +using namespace chip::app::Clusters::DiagnosticLogs; +using chip::Protocols::InteractionModel::Status; -#include +static constexpr size_t kDiagnosticLogsLogProviderDelegateTableSize = + EMBER_AF_DIAGNOSTIC_LOGS_CLUSTER_SERVER_ENDPOINT_COUNT + CHIP_DEVICE_CONFIG_DYNAMIC_ENDPOINT_COUNT; +static_assert(kDiagnosticLogsLogProviderDelegateTableSize < kEmberInvalidEndpointIndex, + "DiagnosticLogs: log provider delegate table size error"); -// We store our times as millisecond times, to save space. -using TimeInBufferType = chip::System::Clock::Milliseconds32::rep; +namespace chip { +namespace app { +namespace Clusters { +namespace DiagnosticLogs { -CHIP_ERROR DiagnosticLogsCommandHandler::PushLog(const chip::ByteSpan & payload) +LogProviderDelegate * gLogProviderDelegateTable[kDiagnosticLogsLogProviderDelegateTableSize] = { nullptr }; + +LogProviderDelegate * GetLogProviderDelegate(EndpointId endpoint) { - auto now = std::chrono::duration_cast(chip::Server::GetInstance().TimeSinceInit()); - TimeInBufferType timeMs = now.count(); - chip::ByteSpan payloadTime(reinterpret_cast(&timeMs), sizeof(timeMs)); - return mBuffer.Push(payloadTime, payload); + uint16_t ep = + emberAfGetClusterServerEndpointIndex(endpoint, DiagnosticLogs::Id, EMBER_AF_DIAGNOSTIC_LOGS_CLUSTER_SERVER_ENDPOINT_COUNT); + return (ep >= kDiagnosticLogsLogProviderDelegateTableSize ? nullptr : gLogProviderDelegateTable[ep]); } -void DiagnosticLogsCommandHandler::InvokeCommand(HandlerContext & handlerContext) +bool isLogProviderDelegateNull(LogProviderDelegate * logProviderDelegate, EndpointId endpoint) { - HandleCommand( - handlerContext, [&](auto & _u, auto & payload) { - if (payload.requestedProtocol == chip::app::Clusters::DiagnosticLogs::TransferProtocolEnum::kUnknownEnumValue) - { - handlerContext.mCommandHandler.AddStatus(handlerContext.mRequestPath, - chip::Protocols::InteractionModel::Status::InvalidCommand); - return; - } + if (logProviderDelegate == nullptr) + { + ChipLogProgress(Zcl, "Diagnosticlogs: no log provider delegate set for endpoint:%u", endpoint); + return true; + } + return false; +} + +} // namespace DiagnosticLogs +} // namespace Clusters +} // namespace app +} // namespace chip + +DiagnosticLogsServer DiagnosticLogsServer::sInstance; + +void DiagnosticLogsServer::SetDefaultLogProviderDelegate(EndpointId endpoint, LogProviderDelegate * logProviderDelegate) +{ + uint16_t ep = + emberAfGetClusterServerEndpointIndex(endpoint, DiagnosticLogs::Id, EMBER_AF_DIAGNOSTIC_LOGS_CLUSTER_SERVER_ENDPOINT_COUNT); + if (ep < kDiagnosticLogsLogProviderDelegateTableSize) + { + gLogProviderDelegateTable[ep] = logProviderDelegate; + } +} + +DiagnosticLogsServer & DiagnosticLogsServer::Instance() +{ + return sInstance; +} + +#if CHIP_CONFIG_ENABLE_BDX_LOG_TRANSFER + +bool DiagnosticLogsServer::IsBDXProtocolRequested(TransferProtocolEnum requestedProtocol) +{ + return requestedProtocol == TransferProtocolEnum::kBdx; +} + +bool DiagnosticLogsServer::HasValidFileDesignator(CharSpan transferFileDesignator) +{ + return (transferFileDesignator.size() > 0 && transferFileDesignator.size() <= kLogFileDesignatorMaxLen); +} + +CHIP_ERROR DiagnosticLogsServer::HandleLogRequestForBDXProtocol(Messaging::ExchangeContext * exchangeCtx, EndpointId endpointId, + IntentEnum intent, CharSpan fileDesignator) +{ + + VerifyOrReturnError(exchangeCtx != nullptr, CHIP_ERROR_INCORRECT_STATE); + + mIntent = intent; + + auto scopedPeerNodeId = exchangeCtx->GetSessionHandle()->AsSecureSession()->GetPeer(); + + LogProviderDelegate * logProviderDelegate = DiagnosticLogs::GetLogProviderDelegate(endpointId); + + VerifyOrReturnError(!(DiagnosticLogs::isLogProviderDelegateNull(logProviderDelegate, endpointId)), CHIP_ERROR_INCORRECT_STATE); + + mDiagnosticLogsBDXTransferHandler = new DiagnosticLogsBDXTransferHandler(); + CHIP_ERROR error = mDiagnosticLogsBDXTransferHandler->InitializeTransfer( + exchangeCtx, scopedPeerNodeId.GetFabricIndex(), scopedPeerNodeId.GetNodeId(), logProviderDelegate, intent, fileDesignator); + return error; +} + +void DiagnosticLogsServer::HandleBDXResponse(CHIP_ERROR error) +{ + LogErrorOnFailure(error); + + auto commandHandleRef = std::move(mAsyncCommandHandle); + auto commandHandle = commandHandleRef.Get(); - switch (payload.intent) + if (commandHandle == nullptr) + { + ChipLogError(Zcl, "HandleBDXResponse - commandHandler is null"); + return; + } + + Commands::RetrieveLogsResponse::Type response; + if (error == CHIP_NO_ERROR) + { + response.status = StatusEnum::kSuccess; + commandHandle->AddResponse(mRequestPath, response); + } + else + { + SendErrorResponseAndReset(commandHandle, mRequestPath, StatusEnum::kNoLogs); + } +} + +void DiagnosticLogsServer::SetAsyncCommandHandleAndPath(CommandHandler * commandObj, const ConcreteCommandPath & commandPath) +{ + mAsyncCommandHandle = CommandHandler::Handle(commandObj); + mRequestPath = commandPath; +} + +void DiagnosticLogsServer::SendErrorResponseAndReset(chip::app::CommandHandler * commandHandler, + chip::app::ConcreteCommandPath path, StatusEnum status) +{ + Commands::RetrieveLogsResponse::Type response; + if (commandHandler != nullptr) + { + response.status = status; + commandHandler->AddResponse(path, response); + } + // mDiagnosticLogsBDXTransferHandler->Reset(); + // delete(mDiagnosticLogsBDXTransferHandler); +} + +#endif + +void DiagnosticLogsServer::HandleLogRequestForResponsePayload(CommandHandler * commandHandler, ConcreteCommandPath path, + IntentEnum intent) +{ + Commands::RetrieveLogsResponse::Type response; + mIntent = intent; + + EndpointId endpoint = path.mEndpointId; + LogProviderDelegate * logProviderDelegate = GetLogProviderDelegate(endpoint); + + if (isLogProviderDelegateNull(logProviderDelegate, endpoint)) + { + response.status = StatusEnum::kNoLogs; + commandHandler->AddResponse(path, response); + return; + } + + Platform::ScopedMemoryBuffer buffer; + + if (!buffer.Alloc(kLogContentMaxSize)) + { + ChipLogError(Zcl, "buffer not allocated"); + response.status = StatusEnum::kNoLogs; + commandHandler->AddResponse(path, response); + return; + } + + mLogSessionHandle = logProviderDelegate->StartLogCollection(intent); + + if (mLogSessionHandle == kInvalidLogSessionHandle) + { + response.status = StatusEnum::kNoLogs; + commandHandler->AddResponse(path, response); + return; + } + + MutableByteSpan mutableBuffer; + + mutableBuffer = MutableByteSpan(buffer.Get(), kLogContentMaxSize); + + bool isEOF = false; + + // Get the log next chunk + uint64_t bytesRead = logProviderDelegate->GetNextChunk(mLogSessionHandle, mutableBuffer, isEOF); + + if (bytesRead == 0) + { + 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); + + commandHandler->AddResponse(path, response); + mLogSessionHandle = kInvalidLogSessionHandle; +} + +static void HandleRetrieveLogRequest(CommandHandler * commandObj, const ConcreteCommandPath & commandPath, + TransferProtocolEnum protocol, IntentEnum intent, Optional transferFileDesignator) +{ + + if (protocol == TransferProtocolEnum::kResponsePayload) + { + DiagnosticLogsServer::Instance().HandleLogRequestForResponsePayload(commandObj, commandPath, intent); + } +#if CHIP_CONFIG_ENABLE_BDX_LOG_TRANSFER + else + { + Commands::RetrieveLogsResponse::Type response; + if (!transferFileDesignator.HasValue() || + !DiagnosticLogsServer::Instance().HasValidFileDesignator(transferFileDesignator.Value())) + { + ChipLogError(Zcl, "HandleRetrieveLogRequest - fileDesignator not valid for BDX protocol"); + response.status = StatusEnum::kNoLogs; + commandObj->AddResponse(commandPath, response); + return; + } + + if (DiagnosticLogsServer::Instance().IsBDXProtocolRequested(protocol)) + { + CHIP_ERROR err = DiagnosticLogsServer::Instance().HandleLogRequestForBDXProtocol( + commandObj->GetExchangeContext(), commandPath.mEndpointId, intent, transferFileDesignator.Value()); + if (err != CHIP_NO_ERROR) { - case chip::app::Clusters::DiagnosticLogs::IntentEnum::kEndUserSupport: { - chip::app::Clusters::DiagnosticLogs::Commands::RetrieveLogsResponse::Type response; - if (mBuffer.IsEmpty()) - { - response.status = chip::app::Clusters::DiagnosticLogs::StatusEnum::kNoLogs; - handlerContext.mCommandHandler.AddResponse(handlerContext.mRequestPath, response); - break; - } - - size_t logSize = mBuffer.GetFrontSize(); - TimeInBufferType timeFromBuffer; - VerifyOrDie(logSize > sizeof(timeFromBuffer)); - - chip::Platform::ScopedMemoryBuffer buf; - if (!buf.Calloc(logSize)) - { - response.status = chip::app::Clusters::DiagnosticLogs::StatusEnum::kBusy; - handlerContext.mCommandHandler.AddResponse(handlerContext.mRequestPath, response); - break; - } - - // The entry is | time (4 bytes) | content (var size) | - chip::MutableByteSpan entry(buf.Get(), logSize); - CHIP_ERROR err = mBuffer.ReadFront(entry); - VerifyOrDie(err == CHIP_NO_ERROR); - memcpy(&timeFromBuffer, buf.Get(), sizeof(timeFromBuffer)); - - auto timestamp = chip::System::Clock::Milliseconds32(timeFromBuffer); - - response.status = chip::app::Clusters::DiagnosticLogs::StatusEnum::kSuccess; - response.logContent = chip::ByteSpan(buf.Get() + sizeof(timeFromBuffer), logSize - sizeof(timeFromBuffer)); - response.timeSinceBoot.SetValue(chip::System::Clock::Microseconds64(timestamp).count()); - handlerContext.mCommandHandler.AddResponse(handlerContext.mRequestPath, response); - } - break; - case chip::app::Clusters::DiagnosticLogs::IntentEnum::kNetworkDiag: { - chip::app::Clusters::DiagnosticLogs::Commands::RetrieveLogsResponse::Type response; - response.status = chip::app::Clusters::DiagnosticLogs::StatusEnum::kNoLogs; - handlerContext.mCommandHandler.AddResponse(handlerContext.mRequestPath, response); - } - break; - case chip::app::Clusters::DiagnosticLogs::IntentEnum::kCrashLogs: { - chip::app::Clusters::DiagnosticLogs::Commands::RetrieveLogsResponse::Type response; - response.status = chip::app::Clusters::DiagnosticLogs::StatusEnum::kNoLogs; - handlerContext.mCommandHandler.AddResponse(handlerContext.mRequestPath, response); - } - break; - case chip::app::Clusters::DiagnosticLogs::IntentEnum::kUnknownEnumValue: { - handlerContext.mCommandHandler.AddStatus(handlerContext.mRequestPath, - chip::Protocols::InteractionModel::Status::InvalidCommand); - break; - } + LogErrorOnFailure(err); + response.status = StatusEnum::kNoLogs; + commandObj->AddResponse(commandPath, response); + return; } - }); + DiagnosticLogsServer::Instance().SetAsyncCommandHandleAndPath(std::move(commandObj), std::move(commandPath)); + } + } +#endif } -bool emberAfDiagnosticLogsClusterRetrieveLogsRequestCallback( - chip::app::CommandHandler * commandObj, const chip::app::ConcreteCommandPath & commandPath, - const chip::app::Clusters::DiagnosticLogs::Commands::RetrieveLogsRequest::DecodableType & commandData) +bool emberAfDiagnosticLogsClusterRetrieveLogsRequestCallback(CommandHandler * commandObj, const ConcreteCommandPath & commandPath, + const Commands::RetrieveLogsRequest::DecodableType & commandData) { - commandObj->AddStatus(commandPath, chip::Protocols::InteractionModel::Status::UnsupportedCommand); + if (commandData.requestedProtocol == TransferProtocolEnum::kUnknownEnumValue || + commandData.intent == IntentEnum::kUnknownEnumValue) + { + commandObj->AddStatus(commandPath, Protocols::InteractionModel::Status::InvalidCommand); + return true; + } + HandleRetrieveLogRequest(commandObj, commandPath, commandData.requestedProtocol, commandData.intent, + commandData.transferFileDesignator); return true; } 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 91348717c481c5..ffec67fed78bc4 100644 --- a/src/app/clusters/diagnostic-logs-server/diagnostic-logs-server.h +++ b/src/app/clusters/diagnostic-logs-server/diagnostic-logs-server.h @@ -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"); @@ -18,31 +18,81 @@ #pragma once +#include + +#if CHIP_CONFIG_ENABLE_BDX_LOG_TRANSFER +#include +#endif #include #include -#include +#include + +namespace chip { +namespace app { +namespace Clusters { +namespace DiagnosticLogs { + +// Spec mandated max file designator length +static constexpr uint8_t kLogFileDesignatorMaxLen = 32; -#include +static constexpr const uint16_t kDiagnosticLogsEndpoint = 0; + +// Spec mandated max size of the log content field in the Response paylod +static constexpr uint16_t kLogContentMaxSize = 1024; /// A reference implementation for DiagnosticLogs source. -class DiagnosticLogsCommandHandler : public chip::app::CommandHandlerInterface +class DiagnosticLogsServer { public: - static constexpr const uint16_t kDiagnosticLogsEndpoint = 0; - static constexpr const uint16_t kDiagnosticLogsBufferSize = 4 * 1024; // 4K internal memory to store text logs + static DiagnosticLogsServer & Instance(); + + /** + * Set the default delegate of the diagnostic logs cluster for the specified endpoint + * + * @param endpoint ID of the endpoint + * + * @param delegate The default lofg provider delegate at the endpoint + */ + void SetDefaultLogProviderDelegate(EndpointId endpoint, LogProviderDelegate * delegate); + + void HandleLogRequestForResponsePayload(chip::app::CommandHandler * commandHandler, chip::app::ConcreteCommandPath path, + IntentEnum intent); + +#if CHIP_CONFIG_ENABLE_BDX_LOG_TRANSFER + + void HandleBDXResponse(CHIP_ERROR error); + + CHIP_ERROR HandleLogRequestForBDXProtocol(chip::Messaging::ExchangeContext * exchangeCtx, chip::EndpointId endpointId, + IntentEnum intent, chip::CharSpan fileDesignator); - DiagnosticLogsCommandHandler() : - CommandHandlerInterface(chip::MakeOptional(chip::EndpointId(kDiagnosticLogsEndpoint)), - chip::app::Clusters::DiagnosticLogs::Id), - mBuffer(mStorage.data(), mStorage.size()) - {} + void SetAsyncCommandHandleAndPath(CommandHandler * commandObj, const ConcreteCommandPath & commandPath); - // Inherited from CommandHandlerInterface - void InvokeCommand(HandlerContext & handlerContext) override; + bool HasValidFileDesignator(chip::CharSpan transferFileDesignator); - CHIP_ERROR PushLog(const chip::ByteSpan & payload); + bool IsBDXProtocolRequested(TransferProtocolEnum requestedProtocol); + + void SendErrorResponseAndReset(chip::app::CommandHandler * commandHandler, chip::app::ConcreteCommandPath path, + StatusEnum status); + +#endif private: - std::array mStorage; - chip::BytesCircularBuffer mBuffer; +#if CHIP_CONFIG_ENABLE_BDX_LOG_TRANSFER + + DiagnosticLogsBDXTransferHandler * mDiagnosticLogsBDXTransferHandler; + +#endif + + LogSessionHandle mLogSessionHandle; + + chip::app::CommandHandler::Handle mAsyncCommandHandle; + chip::app::ConcreteCommandPath mRequestPath = chip::app::ConcreteCommandPath(0, 0, 0); + IntentEnum mIntent; + + static DiagnosticLogsServer sInstance; }; + +} // namespace DiagnosticLogs +} // namespace Clusters +} // namespace app +} // namespace chip diff --git a/src/darwin/Framework/CHIP/MTRDevice.h b/src/darwin/Framework/CHIP/MTRDevice.h index 52399e7fd9f078..86fab36bf1bb34 100644 --- a/src/darwin/Framework/CHIP/MTRDevice.h +++ b/src/darwin/Framework/CHIP/MTRDevice.h @@ -29,6 +29,17 @@ typedef NS_ENUM(NSUInteger, MTRDeviceState) { MTRDeviceStateUnreachable = 2 }; +/** + * This enum is used to specify the type of log requested from this device. + * + * The log types are : End User Support, Network Diagnostics and Crash logs. + */ +typedef NS_ENUM(NSInteger, MTRDiagnosticLogType) { + MTRDiagnosticLogTypeEndUserSupport = 0, // End user support log is requested + MTRDiagnosticLogTypeNetworkDiagnostics = 1, // Network Diagnostics log is requested + MTRDiagnosticLogTypeCrash = 2 // Crash log is requested +}; + @protocol MTRDeviceDelegate; @interface MTRDevice : NSObject @@ -240,6 +251,26 @@ typedef NS_ENUM(NSUInteger, MTRDeviceState) { completion:(MTRDeviceOpenCommissioningWindowHandler)completion MTR_AVAILABLE(ios(17.0), macos(14.0), watchos(10.0), tvos(17.0)); +/** + * Download log of the desired type from the device. + * + * Note: The consumer of this API should move the file that the logResult points to or open it for reading before the + * completion handler returns. Otherwise, the file will be deleted, and the data will be lost. + * + * @param type The type of log being requested. This should correspond to a value in the enum MTRDiagnosticLogType. + * @param timeout The timeout for getting the log. If the timeout expires, completion will be called with whatever + * has been retrieved by that point (which might be none or a partial log). + * If the timeout is set to 0, the request will not expire and completion will not be called until + * the log is fully retrieved or an error occurs. + * @param queue The queue on which completion will be called. + * @param completion The completion that will be called to return the URL of the requested log if successful. Otherwise + * returns an error. + */ +- (void)downloadLogOfType:(MTRDiagnosticLogType)type + timeout:(NSTimeInterval)timeout + queue:(dispatch_queue_t)queue + completion:(void (^)(NSURL * _Nullable logResult, NSError * error))completion; + @end @protocol MTRDeviceDelegate diff --git a/src/darwin/Framework/CHIP/MTRDevice.mm b/src/darwin/Framework/CHIP/MTRDevice.mm index 8806d95b0001c4..e4c76cd6a4a4dd 100644 --- a/src/darwin/Framework/CHIP/MTRDevice.mm +++ b/src/darwin/Framework/CHIP/MTRDevice.mm @@ -15,6 +15,7 @@ * limitations under the License. */ +#import #import #import @@ -31,16 +32,21 @@ #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); @@ -144,6 +150,8 @@ @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) NSArray *> * unreportedEvents; /** @@ -1313,6 +1321,198 @@ - (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 +{ + if (self->_diagnosticLogsTransferHandler != nil) { + delete (self->_diagnosticLogsTransferHandler); + } + dispatch_async(queue, ^{ + completion(filepath, error); + }); +} + +- (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]]; + } + + if (self->_diagnosticLogsTransferHandler != nil && self->_diagnosticLogsTransferHandler->IsInBDXSession()) { + [self _invokeCompletionWithError:completion queue:queue error:[NSError errorWithDomain:MTRInteractionErrorDomain code:MTRInteractionErrorCodeBusy 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 = [NSNumber numberWithInteger:type]; + requestParams.requestedProtocol = [NSNumber numberWithUnsignedChar: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 && self->_diagnosticLogsTransferHandler->IsInBDXSession() && 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]; +} + #pragma mark - Cache management // assume lock is held diff --git a/src/darwin/Framework/CHIP/MTRDiagnosticLogsTransferHandler.h b/src/darwin/Framework/CHIP/MTRDiagnosticLogsTransferHandler.h new file mode 100644 index 00000000000000..d6c2f0ebe76806 --- /dev/null +++ b/src/darwin/Framework/CHIP/MTRDiagnosticLogsTransferHandler.h @@ -0,0 +1,89 @@ +/** + * + * 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); + + bool IsInBDXSession() { return mInitialized; } + +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; + + bool mInitialized = false; +}; diff --git a/src/darwin/Framework/CHIP/MTRDiagnosticLogsTransferHandler.mm b/src/darwin/Framework/CHIP/MTRDiagnosticLogsTransferHandler.mm new file mode 100644 index 00000000000000..8caada05554dbb --- /dev/null +++ b/src/darwin/Framework/CHIP/MTRDiagnosticLogsTransferHandler.mm @@ -0,0 +1,275 @@ +/** + * + * 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 "MTRDeviceControllerFactory_Internal.h" +#import "MTRDeviceController_Internal.h" +#import "NSDataSpanConversion.h" + +#include "MTRDiagnosticLogsTransferHandler.h" +#include + +using namespace chip; +using namespace chip::bdx; +using namespace chip::app; + +constexpr uint32_t kMaxBdxBlockSize = 1024; + +// Timeout for the BDX transfer session. The OTA Spec mandates this should be >= 5 minutes. +constexpr System::Clock::Timeout kBdxTimeout = System::Clock::Seconds16(5 * 60); +constexpr bdx::TransferRole kBdxRole = bdx::TransferRole::kReceiver; + +CHIP_ERROR MTRDiagnosticLogsTransferHandler::PrepareForTransfer(System::Layer * _Nonnull layer, FabricIndex fabricIndex, NodeId nodeId) +{ + assertChipStackLockedByCurrentThread(); + + ReturnErrorOnFailure(ConfigureState(fabricIndex, nodeId)); + + BitFlags flags(bdx::TransferControlFlags::kSenderDrive); + + return Responder::PrepareForTransfer(layer, kBdxRole, flags, kMaxBdxBlockSize, kBdxTimeout); +} + +bdx::StatusCode GetBdxStatusCodeFromChipError(CHIP_ERROR err) +{ + if (err == CHIP_ERROR_INCORRECT_STATE) { + return bdx::StatusCode::kUnexpectedMessage; + } + if (err == CHIP_ERROR_INVALID_ARGUMENT) { + return bdx::StatusCode::kBadMessageContents; + } + return bdx::StatusCode::kUnknown; +} + +CHIP_ERROR MTRDiagnosticLogsTransferHandler::OnTransferSessionBegin(TransferSession::OutputEvent & event) +{ + assertChipStackLockedByCurrentThread(); + + VerifyOrReturnError(mFabricIndex.HasValue(), CHIP_ERROR_INCORRECT_STATE); + VerifyOrReturnError(mNodeId.HasValue(), CHIP_ERROR_INCORRECT_STATE); + NSError * error = nil; + + mFileHandle = [NSFileHandle fileHandleForWritingToURL:mFileURL error:&error]; + + if (mFileHandle == nil || error != nil) { + CHIP_ERROR err = [MTRError errorToCHIPErrorCode:error]; + LogErrorOnFailure(mTransfer.AbortTransfer(GetBdxStatusCodeFromChipError(err))); + return err; + } + + TransferSession::TransferAcceptData acceptData; + acceptData.ControlMode = bdx::TransferControlFlags::kSenderDrive; + acceptData.MaxBlockSize = mTransfer.GetTransferBlockSize(); + acceptData.StartOffset = mTransfer.GetStartOffset(); + acceptData.Length = mTransfer.GetTransferLength(); + + mTransfer.AcceptTransfer(acceptData); + mInitialized = true; + return CHIP_NO_ERROR; +} + +CHIP_ERROR MTRDiagnosticLogsTransferHandler::OnTransferSessionEnd(TransferSession::OutputEvent & event) +{ + assertChipStackLockedByCurrentThread(); + VerifyOrReturnError(mFabricIndex.HasValue(), CHIP_ERROR_INCORRECT_STATE); + VerifyOrReturnError(mNodeId.HasValue(), CHIP_ERROR_INCORRECT_STATE); + CHIP_ERROR error = CHIP_NO_ERROR; + + if (event.EventType == TransferSession::OutputEventType::kTransferTimeout) { + error = CHIP_ERROR_TIMEOUT; + } else if (event.EventType != TransferSession::OutputEventType::kMsgToSend || !event.msgTypeData.HasMessageType(MessageType::BlockAckEOF)) { + error = CHIP_ERROR_INTERNAL; + } + Reset(); + + // Notify the MTRDevice via the callback that the BDX transfer has completed with error or success. + if (mCallback) { + mCallback(error != CHIP_NO_ERROR ? NO : YES); + } + return error; +} + +CHIP_ERROR MTRDiagnosticLogsTransferHandler::OnBlockReceived(TransferSession::OutputEvent & event) +{ + assertChipStackLockedByCurrentThread(); + + VerifyOrReturnError(mFabricIndex.HasValue(), CHIP_ERROR_INCORRECT_STATE); + VerifyOrReturnError(mNodeId.HasValue(), CHIP_ERROR_INCORRECT_STATE); + + chip::ByteSpan blockData(event.blockdata.Data, event.blockdata.Length); + + if (mFileHandle != nil) { + [mFileHandle seekToEndOfFile]; + NSError * error = nil; + [mFileHandle writeData:AsData(blockData) error:&error]; + + if (error != nil) { + CHIP_ERROR err = [MTRError errorToCHIPErrorCode:error]; + return mTransfer.AbortTransfer(GetBdxStatusCodeFromChipError(err)); + } + } else { + return mTransfer.AbortTransfer(GetBdxStatusCodeFromChipError(CHIP_ERROR_INCORRECT_STATE)); + } + + CHIP_ERROR err = mTransfer.PrepareBlockAck(); + LogErrorOnFailure(err); + if (err != CHIP_NO_ERROR) { + return mTransfer.AbortTransfer(GetBdxStatusCodeFromChipError(err)); + } + if (event.blockdata.IsEof) { + mFileHandle = nil; + } + + return CHIP_NO_ERROR; +} + +CHIP_ERROR MTRDiagnosticLogsTransferHandler::OnMessageToSend(TransferSession::OutputEvent & event) +{ + assertChipStackLockedByCurrentThread(); + + VerifyOrReturnError(mExchangeCtx != nil, CHIP_ERROR_INCORRECT_STATE); + + Messaging::SendFlags sendFlags; + + // All messages sent from the Sender expect a response, except for a StatusReport which would indicate an error and + // the end of the transfer. + if (!event.msgTypeData.HasMessageType(Protocols::SecureChannel::MsgType::StatusReport)) { + sendFlags.Set(Messaging::SendMessageFlags::kExpectResponse); + } + + auto & msgTypeData = event.msgTypeData; + // If there's an error sending the message, close the exchange and call Reset. + CHIP_ERROR err + = mExchangeCtx->SendMessage(msgTypeData.ProtocolId, msgTypeData.MessageType, std::move(event.MsgData), sendFlags); + if (err != CHIP_NO_ERROR) { + mExchangeCtx->Close(); + mExchangeCtx = nil; + Reset(); + } else if (event.msgTypeData.HasMessageType(Protocols::SecureChannel::MsgType::StatusReport)) { + // If the send was successful for a status report, since we are not expecting a response the exchange context is + // already closed. We need to null out the reference to avoid having a dangling pointer. + mExchangeCtx = nil; + } + return err; +} + +void MTRDiagnosticLogsTransferHandler::HandleTransferSessionOutput(TransferSession::OutputEvent & event) +{ + assertChipStackLockedByCurrentThread(); + ChipLogError(BDX, "Got an event %s", event.ToString(event.EventType)); + + CHIP_ERROR err = CHIP_NO_ERROR; + switch (event.EventType) { + case TransferSession::OutputEventType::kInitReceived: + err = OnTransferSessionBegin(event); + if (err != CHIP_NO_ERROR) { + LogErrorOnFailure(err); + mTransfer.AbortTransfer(GetBdxStatusCodeFromChipError(err)); + } + break; + case TransferSession::OutputEventType::kStatusReceived: + ChipLogError(BDX, "Got StatusReport %x", static_cast(event.statusData.statusCode)); + [[fallthrough]]; + case TransferSession::OutputEventType::kInternalError: + case TransferSession::OutputEventType::kTransferTimeout: + err = OnTransferSessionEnd(event); + break; + case TransferSession::OutputEventType::kBlockReceived: + err = OnBlockReceived(event); + if (err != CHIP_NO_ERROR) { + LogErrorOnFailure(err); + mTransfer.AbortTransfer(GetBdxStatusCodeFromChipError(err)); + } + break; + case TransferSession::OutputEventType::kAckEOFReceived: + break; + case TransferSession::OutputEventType::kMsgToSend: + err = OnMessageToSend(event); + if (event.msgTypeData.HasMessageType(MessageType::BlockAckEOF)) { + err = OnTransferSessionEnd(event); + } + break; + case TransferSession::OutputEventType::kNone: + case TransferSession::OutputEventType::kQueryWithSkipReceived: + case TransferSession::OutputEventType::kQueryReceived: + case TransferSession::OutputEventType::kAckReceived: + case TransferSession::OutputEventType::kAcceptReceived: + // Nothing to do. + break; + default: + // Should never happens. + chipDie(); + break; + } +} + +void MTRDiagnosticLogsTransferHandler::AbortTransfer(chip::bdx::StatusCode reason) +{ + assertChipStackLockedByCurrentThread(); + mTransfer.AbortTransfer(reason); +} + +void MTRDiagnosticLogsTransferHandler::Reset() +{ + assertChipStackLockedByCurrentThread(); + mInitialized = false; + mFileURL = nil; + mFileHandle = nil; + + Responder::ResetTransfer(); + if (mExchangeCtx) { + mExchangeCtx->Close(); + mExchangeCtx = nil; + } + mFabricIndex.ClearValue(); + mNodeId.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( + Messaging::ExchangeContext * _Nonnull ec, const PayloadHeader & payloadHeader, System::PacketBufferHandle && payload) +{ + VerifyOrReturnError(ec != nil, CHIP_ERROR_INCORRECT_STATE); + CHIP_ERROR err; + mExchangeCtx = ec; + + // If we receive a ReceiveInit message, then we prepare for transfer + if (payloadHeader.HasMessageType(MessageType::SendInit)) { + NodeId nodeId = ec->GetSessionHandle()->GetPeer().GetNodeId(); + FabricIndex fabricIndex = ec->GetSessionHandle()->GetFabricIndex(); + + if (nodeId != kUndefinedNodeId && fabricIndex != kUndefinedFabricIndex) { + err = PrepareForTransfer(&(DeviceLayer::SystemLayer()), fabricIndex, nodeId); + 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 c6b5951eeea936..fab836753b5b73 100644 --- a/src/darwin/Framework/CHIPTests/MTRDeviceTests.m +++ b/src/darwin/Framework/CHIPTests/MTRDeviceTests.m @@ -38,6 +38,7 @@ #import static const uint16_t kPairingTimeoutInSeconds = 10; +static const uint16_t kDownloadLogTimeoutInSeconds = 50; static const uint16_t kTimeoutInSeconds = 3; static const uint64_t kDeviceId = 0x12344321; static NSString * kOnboardingPayload = @"MT:-24J0AFN00KA0648G00"; @@ -2605,6 +2606,154 @@ - (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 +{ + NSTask * appTask = [self createTaskForPath:@"out/debug/chip-all-clusters-app"]; + + // Remove the file if one exists + [[NSFileManager defaultManager] removeItemAtPath:outFile error:nil]; + + // Create the file + [[NSFileManager defaultManager] createFileAtPath:outFile contents:nil attributes:nil]; + + NSFileHandle * handle = [NSFileHandle fileHandleForUpdatingAtPath:outFile]; + appTask.standardOutput = handle; + NSError * error = nil; + + [appTask launchAndReturnError:&error]; + return error; +} + +- (void)test029_DownloadEndUserSupportLog_NoTimeout +{ + MTRDeviceController * controller = sController; + XCTAssertNotNil(controller); + XCTestExpectation * expectation = [self expectationWithDescription:@"End User Support 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/endusersupportlog.txt"]; + NSError * error = [self generateLogFile:outFile]; + + 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); + NSLog(@"test901_DownloadEndUserSupportLog_NoTimeout expectation fulfill"); + [expectation fulfill]; + }]; + }); + [self waitForExpectations:[NSArray arrayWithObject:expectation] timeout:kDownloadLogTimeoutInSeconds]; +} + +- (void)test030_DownloadNetworkDiagnosticsLog_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]; + + if (error != nil) { + NSLog(@"Failed to generate log file"); + return; + } + + [device downloadLogOfType:MTRDiagnosticLogTypeNetworkDiagnostics 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]; + }]; + }); + [self waitForExpectations:[NSArray arrayWithObject:expectation] timeout:kDownloadLogTimeoutInSeconds]; +} + +- (void)test031_DownloadCrashLog_NoTimeout +{ + MTRDeviceController * controller = sController; + XCTAssertNotNil(controller); + XCTestExpectation * expectation = [self expectationWithDescription:@"Crash 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/crashlog.txt"]; + NSError * error = [self generateLogFile:outFile]; + + 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]; + }]; + }); + [self waitForExpectations:[NSArray arrayWithObject:expectation] timeout:kDownloadLogTimeoutInSeconds]; +} + - (void)test900_SubscribeAllAttributes { MTRBaseDevice * device = GetConnectedDevice(); diff --git a/src/darwin/Framework/Matter.xcodeproj/project.pbxproj b/src/darwin/Framework/Matter.xcodeproj/project.pbxproj index 53ce6ed70871b9..69f047314a2253 100644 --- a/src/darwin/Framework/Matter.xcodeproj/project.pbxproj +++ b/src/darwin/Framework/Matter.xcodeproj/project.pbxproj @@ -304,6 +304,8 @@ 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 */; }; D4772A46285AE98400383630 /* MTRClusterConstants.h in Headers */ = {isa = PBXBuildFile; fileRef = D4772A45285AE98300383630 /* MTRClusterConstants.h */; settings = {ATTRIBUTES = (Public, ); }; }; /* End PBXBuildFile section */ @@ -675,6 +677,8 @@ 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 = ""; }; 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 = ""; }; @@ -1105,6 +1109,8 @@ B202528F2459E34F00F97062 /* CHIP */ = { isa = PBXGroup; children = ( + CFA0EDAE2AD9D76000C7FA40 /* MTRDiagnosticLogsTransferHandler.h */, + CFA0EDAC2AD9D75600C7FA40 /* MTRDiagnosticLogsTransferHandler.mm */, 1E4D655129C30A8700BC3478 /* MTRCommissionableBrowser.mm */, 1E4D654C29C208DD00BC3478 /* MTRCommissionableBrowser.h */, 1E4D654D29C208DD00BC3478 /* MTRCommissionableBrowserDelegate.h */, @@ -1441,6 +1447,7 @@ 5178E6822AE098520069DF72 /* MTRCommissionableBrowserResult_Internal.h in Headers */, 3CF134AB289D8DF70017A19E /* MTRDeviceAttestationInfo.h in Headers */, B2E0D7B2245B0B5C003C5B48 /* MTRManualSetupPayloadParser.h in Headers */, + CFA0EDAF2AD9D76000C7FA40 /* MTRDiagnosticLogsTransferHandler.h in Headers */, 3CF134A7289D8ADA0017A19E /* MTRCSRInfo.h in Headers */, B2E0D7B1245B0B5C003C5B48 /* Matter.h in Headers */, 7596A84428762729004DAE0E /* MTRDevice.h in Headers */, @@ -1776,6 +1783,7 @@ 5A6FEC9827B5C6AF00F25F42 /* MTRDeviceOverXPC.mm in Sources */, 514654492A72F9DF00904E61 /* MTRDemuxingStorage.mm in Sources */, 1E4D655229C30A8700BC3478 /* MTRCommissionableBrowser.mm in Sources */, + CFA0EDAD2AD9D75600C7FA40 /* MTRDiagnosticLogsTransferHandler.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 7363fb2b8d42ee..f791a46e11e795 100644 --- a/src/lib/core/CHIPConfig.h +++ b/src/lib/core/CHIPConfig.h @@ -1,6 +1,6 @@ /* * - * Copyright (c) 2020-2022 Project CHIP Authors + * Copyright (c) 2020-2023 Project CHIP Authors * Copyright (c) 2019 Google LLC. * Copyright (c) 2013-2018 Nest Labs, Inc. * @@ -1600,6 +1600,16 @@ extern const char CHIP_NON_PRODUCTION_MARKER[]; #define CHIP_CONFIG_SYNCHRONOUS_REPORTS_ENABLED 0 #endif +/** + * @def CHIP_CONFIG_ENABLE_BDX_LOG_TRANSFER + * + * @brief Enables support for diagnostic logs transfer using the BDX protocol + * + */ +#ifndef CHIP_CONFIG_ENABLE_BDX_LOG_TRANSFER +#define CHIP_CONFIG_ENABLE_BDX_LOG_TRANSFER 0 +#endif + /** * @} */ diff --git a/src/protocols/bdx/TransferFacilitator.cpp b/src/protocols/bdx/TransferFacilitator.cpp index f5deb2ea5bd2c4..fbe563187b6fb9 100644 --- a/src/protocols/bdx/TransferFacilitator.cpp +++ b/src/protocols/bdx/TransferFacilitator.cpp @@ -132,5 +132,12 @@ CHIP_ERROR Initiator::InitiateTransfer(System::Layer * layer, TransferRole role, return CHIP_NO_ERROR; } +void Initiator::ResetTransfer() +{ + mTransfer.Reset(); + ChipLogProgress(BDX, "Stop polling for messages"); + mStopPolling = true; +} + } // namespace bdx } // namespace chip diff --git a/src/protocols/bdx/TransferFacilitator.h b/src/protocols/bdx/TransferFacilitator.h index 6dd00e4c0dfab9..0ed43918766aba 100644 --- a/src/protocols/bdx/TransferFacilitator.h +++ b/src/protocols/bdx/TransferFacilitator.h @@ -47,6 +47,9 @@ 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 @@ -58,8 +61,7 @@ class TransferFacilitator : public Messaging::ExchangeDelegate, public Messaging } // Inherited from ExchangeContext - CHIP_ERROR OnMessageReceived(chip::Messaging::ExchangeContext * ec, const chip::PayloadHeader & payloadHeader, - chip::System::PacketBufferHandle && payload) override; + void OnResponseTimeout(Messaging::ExchangeContext * ec) override; /** @@ -145,6 +147,8 @@ class Initiator : public TransferFacilitator CHIP_ERROR InitiateTransfer(System::Layer * layer, TransferRole role, const TransferSession::TransferInitData & initData, System::Clock::Timeout timeout, System::Clock::Timeout pollFreq = TransferFacilitator::kDefaultPollFreq); + + void ResetTransfer(); }; } // namespace bdx