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 367388c7b2f015..4491adf35b3d88 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 @@ -1522,6 +1522,51 @@ server cluster NetworkCommissioning = 49 { command access(invoke: administer) ReorderNetwork(ReorderNetworkRequest): NetworkConfigResponse = 8; } +/** The cluster provides commands for retrieving unstructured diagnostic logs from a Node that may be used to aid in diagnostics. */ +client cluster DiagnosticLogs = 50 { + enum IntentEnum : ENUM8 { + kEndUserSupport = 0; + kNetworkDiag = 1; + kCrashLogs = 2; + } + + enum StatusEnum : ENUM8 { + kSuccess = 0; + kExhausted = 1; + kNoLogs = 2; + kBusy = 3; + kDenied = 4; + } + + enum TransferProtocolEnum : ENUM8 { + kResponsePayload = 0; + kBDX = 1; + } + + readonly attribute command_id generatedCommandList[] = 65528; + readonly attribute command_id acceptedCommandList[] = 65529; + readonly attribute event_id eventList[] = 65530; + readonly attribute attrib_id attributeList[] = 65531; + readonly attribute bitmap32 featureMap = 65532; + readonly attribute int16u clusterRevision = 65533; + + request struct RetrieveLogsRequestRequest { + IntentEnum intent = 0; + TransferProtocolEnum requestedProtocol = 1; + 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; + } + + /** Retrieving diagnostic logs from a Node */ + command RetrieveLogsRequest(RetrieveLogsRequestRequest): RetrieveLogsResponse = 0; +} + /** The cluster provides commands for retrieving unstructured diagnostic logs from a Node that may be used to aid in diagnostics. */ server cluster DiagnosticLogs = 50 { enum IntentEnum : ENUM8 { @@ -1556,6 +1601,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; } @@ -5671,6 +5723,7 @@ endpoint 0 { device type ma_powersource = 17, version 1; binding cluster OtaSoftwareUpdateProvider; + binding cluster DiagnosticLogs; server cluster Identify { ram attribute identifyTime default = 0x0000; @@ -5885,10 +5938,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 b05a00644b10fc..011fd91f522250 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 @@ -17,12 +17,6 @@ } ], "package": [ - { - "pathRelativity": "relativeToZap", - "path": "../../../src/app/zap-templates/app-templates.json", - "type": "gen-templates-json", - "version": "chip-v1" - }, { "pathRelativity": "relativeToZap", "path": "../../../src/app/zap-templates/zcl/zcl-with-test-extensions.json", @@ -30,6 +24,12 @@ "category": "matter", "version": 1, "description": "Matter SDK ZCL data with some extensions" + }, + { + "pathRelativity": "relativeToZap", + "path": "../../../src/app/zap-templates/app-templates.json", + "type": "gen-templates-json", + "version": "chip-v1" } ], "endpointTypes": [ @@ -2628,6 +2628,66 @@ } ] }, + { + "name": "Diagnostic Logs", + "code": 50, + "mfgCode": null, + "define": "DIAGNOSTIC_LOGS_CLUSTER", + "side": "client", + "enabled": 1, + "commands": [ + { + "name": "RetrieveLogsRequest", + "code": 0, + "mfgCode": null, + "source": "client", + "isIncoming": 0, + "isEnabled": 1 + }, + { + "name": "RetrieveLogsResponse", + "code": 1, + "mfgCode": null, + "source": "server", + "isIncoming": 1, + "isEnabled": 1 + } + ], + "attributes": [ + { + "name": "FeatureMap", + "code": 65532, + "mfgCode": null, + "side": "client", + "type": "bitmap32", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "0", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ClusterRevision", + "code": 65533, + "mfgCode": null, + "side": "client", + "type": "int16u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "1", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + } + ] + }, { "name": "Diagnostic Logs", "code": 50, @@ -2643,9 +2703,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, @@ -6136,6 +6268,7 @@ "define": "FAULT_INJECTION_CLUSTER", "side": "server", "enabled": 1, + "apiMaturity": "internal", "commands": [ { "name": "FailAtFault", @@ -6641,6 +6774,7 @@ "define": "SCENES_CLUSTER", "side": "server", "enabled": 1, + "apiMaturity": "provisional", "commands": [ { "name": "AddScene", @@ -13698,6 +13832,7 @@ "define": "FAN_CONTROL_CLUSTER", "side": "server", "enabled": 1, + "apiMaturity": "provisional", "commands": [ { "name": "Step", @@ -15060,6 +15195,7 @@ "define": "BALLAST_CONFIGURATION_CLUSTER", "side": "server", "enabled": 1, + "apiMaturity": "provisional", "attributes": [ { "name": "PhysicalMinLevel", @@ -18982,6 +19118,7 @@ "define": "UNIT_TESTING_CLUSTER", "side": "server", "enabled": 1, + "apiMaturity": "internal", "commands": [ { "name": "Test", @@ -20943,6 +21080,7 @@ "define": "SCENES_CLUSTER", "side": "server", "enabled": 1, + "apiMaturity": "provisional", "commands": [ { "name": "AddScene", 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..eb149f6f4e113b --- /dev/null +++ b/examples/all-clusters-app/all-clusters-common/include/diagnostic-logs-provider-delegate-impl.h @@ -0,0 +1,80 @@ +/* + * + * 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() = default; + + ~LogProvider() = default; + + 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..3a06427aed6e43 --- /dev/null +++ b/examples/all-clusters-app/all-clusters-common/src/diagnostic-logs-provider-delegate-impl.cpp @@ -0,0 +1,136 @@ +/* + * + * 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 +#include +#include "diagnostic-logs-provider-delegate-impl.h" + +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); + ChipLogError(BDX, "get file name %s", fileName); + if (fileName != nullptr) { + // + /*std::ifstream mFileStream(fileName, std::ifstream::in); + //mFileStream.open(fileName, std::ifstream::in); + if (!mFileStream.good()) + { + ChipLogError(BDX, "Failed to open the log file"); + return kInvalidLogSessionHandle; + }*/ + sLogSessionHandle++; + mLogSessionHandle = sLogSessionHandle; + return mLogSessionHandle; + } else { + return kInvalidLogSessionHandle; + } +} + +uint64_t LogProvider::GetNextChunk(LogSessionHandle logSessionHandle, chip::MutableByteSpan & outBuffer, bool & outIsEOF) +{ + if (logSessionHandle != mLogSessionHandle && outBuffer.size() == 0) + { + return kChunkSizeZero; + } + + const char * fd = "/tmp/bdxlogs.txt"; + + std::ifstream logFile(fd, std::ifstream::in); + if (!logFile.good()) + { + ChipLogError(BDX, "Failed to open the log file"); + return kChunkSizeZero; + } + + logFile.seekg(static_cast(mTotalNumberOfBytesConsumed)); + logFile.read(reinterpret_cast(outBuffer.data()), kLogContentMaxSize); + + if (!(logFile.good() || logFile.eof())) + { + ChipLogError(BDX, "Failed to read the log file"); + logFile.close(); + return kChunkSizeZero; + } + + outIsEOF = (logFile.peek() == EOF); + uint64_t bytesRead = static_cast(logFile.gcount()); + + ChipLogError(BDX, "GetNextChunk bytesRead %llu outIsEOF %d", bytesRead, outIsEOF); + mTotalNumberOfBytesConsumed += bytesRead; + logFile.close(); + return bytesRead; +} + +void LogProvider::EndLogCollection(LogSessionHandle logSessionHandle) +{ + if (logSessionHandle == mLogSessionHandle) + { + //logFile.close(); + } +} + +uint64_t LogProvider::GetTotalNumberOfBytesConsumed(LogSessionHandle logSessionHandle) +{ + if (logSessionHandle == mLogSessionHandle) + { + return mTotalNumberOfBytesConsumed; + } + return kChunkSizeZero; +} + +const char * LogProvider::GetLogFilePath(IntentEnum logType) +{ + ChipLogError(BDX, "GetLogFilePath %hu", 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) +{ + ChipLogError(BDX, "SetEndUserSupportLogFileDesignator %s", 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..f50535600b9703 100644 --- a/examples/all-clusters-app/linux/AppOptions.cpp +++ b/examples/all-clusters-app/linux/AppOptions.cpp @@ -20,15 +20,20 @@ #include #include +#include "diagnostic-logs-provider-delegate-impl.h" 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; @@ -38,6 +43,7 @@ bool AppOptions::HandleOptions(const char * program, OptionSet * options, int id switch (identifier) { case kOptionDacProviderFilePath: + ChipLogError(BDX, "dac provider path"); mDacProvider.Init(value); break; case kOptionMinCommissioningTimeout: { @@ -45,6 +51,19 @@ bool AppOptions::HandleOptions(const char * program, OptionSet * options, int id commissionMgr.OverrideMinCommissioningTimeout(chip::System::Clock::Seconds16(static_cast(atoi(value)))); break; } + case kOptionEndUserSupportFilePath: { + ChipLogError(BDX, "kOptionEndUserSupportFilePath setting end user fd"); + LogProvider::getLogProvider().SetEndUserSupportLogFileDesignator(value); + break; + } + case kOptionNetworkDiagnosticsFilePath: { + LogProvider::getLogProvider().SetNetworkDiagnosticsLogFileDesignator(value); + break; + } + case kOptionCrashFilePath: { + LogProvider::getLogProvider().SetCrashLogFileDesignator(value); + break; + } default: PrintArgError("%s: INTERNAL ERROR: Unhandled option: %s\n", program, name); retval = false; @@ -59,6 +78,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 +90,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; diff --git a/examples/all-clusters-app/linux/BUILD.gn b/examples/all-clusters-app/linux/BUILD.gn index 3a50001c1ce3fb..6e6bb5bcd41378 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..22552531de684d 100644 --- a/examples/all-clusters-app/linux/main-common.cpp +++ b/examples/all-clusters-app/linux/main-common.cpp @@ -19,6 +19,7 @@ #include "AllClustersCommandDelegate.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 +31,7 @@ #include #include #include +#include #include #include #include @@ -266,3 +268,10 @@ 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()); +} diff --git a/examples/log-source-app/linux/include/CHIPProjectAppConfig.h b/examples/log-source-app/linux/include/CHIPProjectAppConfig.h new file mode 100644 index 00000000000000..47fdf6e6f67f1d --- /dev/null +++ b/examples/log-source-app/linux/include/CHIPProjectAppConfig.h @@ -0,0 +1,37 @@ +/* + * + * 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. + */ + +/** + * @file + * Example project configuration file for CHIP. + * + * This is a place to put application or project-specific overrides + * to the default configuration values for general CHIP features. + * + */ + +#pragma once + +// include the CHIPProjectConfig from config/standalone +#include + +// Allows app options (ports) to be configured on launch of app +#define CHIP_DEVICE_ENABLE_PORT_PARAMS 1 + +// Allows the app to enable BDX for file transfers +#define CHIP_CONFIG_ENABLE_BDX_LOG_TRANSFER 1 diff --git a/examples/log-source-app/log-source-common/DiagnosticLogsProvider.cpp b/examples/log-source-app/log-source-common/DiagnosticLogsProvider.cpp new file mode 100644 index 00000000000000..c166bd214e7a62 --- /dev/null +++ b/examples/log-source-app/log-source-common/DiagnosticLogsProvider.cpp @@ -0,0 +1,88 @@ +/** + * + * 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 +#include +#include "DiagnosticLogsProvider.h" + +#include +#include + +using namespace chip; +using namespace chip::app; +using namespace chip::app::Clusters::DiagnosticLogs; + +LogSessionHandle DiagnosticLogsProvider::StartLogCollection(IntentEnum logType) +{ + if (mIsInLogSession) + { + return kInvalidLogSessionHandle; + } + mIsInLogSession = true; + mTotalNumberOfBytesConsumed = 0; + + // use the log type to maybe read a different file + mLogType = logType; + + // open a log file for reading + const char * fd = "/tmp/bdxtestlog.rtf"; + //memcpy(mFileDesignator, fd, strlen(fd)); + mLogFile.open(fd, std::ifstream::in); + if (!mLogFile.good()) + { + return kInvalidLogSessionHandle; + } + + return ++(Instance().mLogSessionHandle); +} + +uint64_t DiagnosticLogsProvider::GetNextChunk(LogSessionHandle logSessionHandle, chip::MutableByteSpan & outBuffer, bool & outIsEOF) +{ + mLogFile.seekg(mTotalNumberOfBytesConsumed); + //char * buffer = new char [static_cast(outBuffer.size())]; + + mLogFile.read(reinterpret_cast(outBuffer.data()), static_cast(outBuffer.size())); + if (!(mLogFile.good())) + { + ChipLogError(BDX, "Log file read failed"); + mLogFile.close(); + return; + } + mTotalNumberOfBytesConsumed += mLogFile.gcount(); + + if (mLogFile.eof()) { + outIsEOF = true; + } + + // Figure out how to copy to MutableByteSpan + // check if the file is valid and return chunks. maintain numberofbytesread and skip that much into the file +} + +void DiagnosticLogsProvider::EndLogCollection(LogSessionHandle logSessionHandle) +{ + if (mLogFile.is_open()) + { + mLogFile.close(); + } + mTotalNumberOfBytesConsumed = 0; + mIsInLogSession = false; +} + +uint32_t DiagnosticLogsProvider::GetTotalNumberOfBytesConsumed() +{ + return mTotalNumberOfBytesConsumed; +} diff --git a/examples/log-source-app/log-source-common/DiagnosticLogsProvider.h b/examples/log-source-app/log-source-common/DiagnosticLogsProvider.h new file mode 100644 index 00000000000000..fce79ac742bf17 --- /dev/null +++ b/examples/log-source-app/log-source-common/DiagnosticLogsProvider.h @@ -0,0 +1,58 @@ +/* + * + * 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 + +class DiagnosticLogsProvider : public chip::app::Clusters::DiagnosticLogs::Delegate +{ + +public: + DiagnosticLogsProvider() { } + + chip::app::Clusters::DiagnosticLogs::LogSessionHandle StartLogCollection(chip::app::Clusters::DiagnosticLogs::IntentEnum logType); + + uint64_t GetNextChunk(chip::app::Clusters::DiagnosticLogs::LogSessionHandle logSessionHandle, chip::MutableByteSpan & outBuffer, bool & outIsEOF); + + void EndLogCollection(chip::app::Clusters::DiagnosticLogs::LogSessionHandle logSessionHandle); + + uint64_t GetTotalNumberOfBytesConsumed(); + + static DiagnosticLogsProvider & Instance() + { + static DiagnosticLogsProvider instance; + return instance; + } + + ~DiagnosticLogsProvider() = default; + +protected: + + chip::app::Clusters::DiagnosticLogs::IntentEnum mLogType; + chip::app::Clusters::DiagnosticLogs::LogSessionHandle mLogSessionHandle; + uint32_t mTotalNumberOfBytesConsumed; + + //char mFileDesignator[32]; + + std::ifstream mLogFile; + + bool mIsInLogSession; +}; \ No newline at end of file diff --git a/src/app/bdx/DiagnosticLogsBDXTransferHandler.cpp b/src/app/bdx/DiagnosticLogsBDXTransferHandler.cpp new file mode 100644 index 00000000000000..927cfbe2ae21fc --- /dev/null +++ b/src/app/bdx/DiagnosticLogsBDXTransferHandler.cpp @@ -0,0 +1,293 @@ +/* + * + * 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) { + // Prevent a new node connection since another is active. + VerifyOrReturnError(mFabricIndex.Value() == fabricIndex && mNodeId.Value() == nodeId, 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); + + 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; + + ChipLogDetail(BDX, "OutputEvent type: %s", event.ToString(event.EventType)); + switch (event.EventType) + { + case TransferSession::OutputEventType::kAckEOFReceived: + mStopPolling = true; // Stop polling the TransferSession only after receiving BlockAckEOF + Reset(); + break; + case TransferSession::OutputEventType::kStatusReceived: + ChipLogError(BDX, "Got StatusReport %x", static_cast(event.statusData.statusCode)); + Reset(); + break; + case TransferSession::OutputEventType::kInternalError: + ChipLogError(BDX, "InternalError"); + Reset(); + break; + case TransferSession::OutputEventType::kTransferTimeout: + ChipLogError(BDX, "Transfer timed out"); + 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()); + Reset(); + } + + break; + } + case TransferSession::OutputEventType::kAcceptReceived: { + 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()) + { + DiagnosticLogsServer::Instance().HandleBDXResponse(CHIP_ERROR_NO_MEMORY); + return; + } + + mLogSessionHandle = mDelegate->StartLogCollection(mIntent); + + if (mLogSessionHandle == kInvalidLogSessionHandle) + { + DiagnosticLogsServer::Instance().HandleBDXResponse(CHIP_ERROR_INCORRECT_STATE); + return; + } + + // Send a response to the RetreiveLogRequest since we got a SendAccept message from the requestor. + DiagnosticLogsServer::Instance().HandleBDXResponse(CHIP_NO_ERROR); + + MutableByteSpan buffer; + + buffer = MutableByteSpan(blockBuf->Start(), bytesToRead); + + bool isEOF = false; + + // Get the log next chunk + 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::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(); + if (mNodeId.HasValue() && mFabricIndex.HasValue()) { + ChipLogProgress(Controller, + "Resetting state for Diagnostic Logs Provider; no longer sending logs to node id 0x" ChipLogFormatX64 + ", fabric index %u", + ChipLogValueX64(mNodeId.Value()), mFabricIndex.Value()); + } else { + ChipLogProgress(Controller, "Resetting state for Diagnostic Logs Provider"); + } + + mFabricIndex.ClearValue(); + mNodeId.ClearValue(); + + Initiator::ResetTransfer(); + + if (mExchangeCtx != nullptr) { + mExchangeCtx->Close(); + mExchangeCtx = nullptr; + } + + mDelegate = nullptr; + 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..f918641d410ab1 --- /dev/null +++ b/src/app/bdx/DiagnosticLogsBDXTransferHandler.h @@ -0,0 +1,76 @@ +/* + * + * 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 { +/** + * An implementation of the handler than initiates a BDX transfer 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: + + chip::Optional mFabricIndex; + chip::Optional mNodeId; + + chip::Messaging::ExchangeContext * mExchangeCtx; + + bool mInitialized; + + uint64_t mNumBytesSent; + + LogSessionHandle mLogSessionHandle; + + LogProviderDelegate * mDelegate; + + IntentEnum mIntent; + + char mFileDesignator[chip::bdx::kMaxFileDesignatorLen]; +}; + +} // 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 7490e395eec930..9be45341bb2ef8 100644 --- a/src/app/chip_data_model.gni +++ b/src/app/chip_data_model.gni @@ -314,7 +314,15 @@ template("chip_data_model") { "${_app_root}/clusters/${cluster}/${cluster}.cpp", "${_app_root}/clusters/${cluster}/${cluster}.h", ] - } else { + } else if (cluster == "diagnostic-logs-server") { + sources += [ + "${_app_root}/clusters/${cluster}/${cluster}.cpp", + "${_app_root}/clusters/${cluster}/${cluster}.h", + "${_app_root}/bdx/DiagnosticLogsBDXTransferHandler.cpp", + "${_app_root}/bdx/DiagnosticLogsBDXTransferHandler.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..1b641028f64e71 --- /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 \ No newline at end of file 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..b72dcb3b8a74a7 100644 --- a/src/app/clusters/diagnostic-logs-server/diagnostic-logs-server.cpp +++ b/src/app/clusters/diagnostic-logs-server/diagnostic-logs-server.cpp @@ -14,105 +14,266 @@ * See the License for the specific language governing permissions and * 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; + +static constexpr size_t kDiagnosticLogsLogProviderDelegateTableSize = + EMBER_AF_DIAGNOSTIC_LOGS_CLUSTER_SERVER_ENDPOINT_COUNT + CHIP_DEVICE_CONFIG_DYNAMIC_ENDPOINT_COUNT; +static_assert(kDiagnosticLogsLogProviderDelegateTableSize < kEmberInvalidEndpointIndex, "DiagnosticLogs LogProviderDelegate table size error"); -#include +namespace chip { +namespace app { +namespace Clusters { +namespace DiagnosticLogs { -// We store our times as millisecond times, to save space. -using TimeInBufferType = chip::System::Clock::Milliseconds32::rep; +LogProviderDelegate * gLogProviderDelegateTable[kDiagnosticLogsLogProviderDelegateTableSize] = { nullptr }; -CHIP_ERROR DiagnosticLogsCommandHandler::PushLog(const chip::ByteSpan & payload) +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, "Diagnostic logs has no LogProviderDelegate 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 endpoint is found + if (ep < kDiagnosticLogsLogProviderDelegateTableSize) + { + gLogProviderDelegateTable[ep] = logProviderDelegate; + } +} + +DiagnosticLogsServer & DiagnosticLogsServer::Instance() +{ + return sInstance; +} - switch (payload.intent) +#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) +{ + // if bdx is not supported and logs fit return in log content. if node doesn't support bdx have a compile time guard for the BDX code + // a) Create a handler for BDX. we need to get the exchange mgr from the command handler and create a new exchange context with the LogProviderDelegate pointing to the handler for BDX and send Init. + // b) when we get sendaccept send a response (how to send a delayed response save the command object). hold on to the commandhandler/obj and call the setstatus/addrepsonse. no send accept -> return in log content - how muchever fits send it. + // If a failure StatusReport is received in response to the SendInit message, the Node SHALL send a RetrieveLogsResponse command with a Status of Denied. + // where the Node is able to fit the entirety of the requested logs within the LogContent field, the Status field of the RetrieveLogsResponse SHALL be set to Exhausted + + // how to send a delayed response save the command object) - Handle object in CommandHandler. use that. set i am going to respond to you and continue. + // b) register with/ask the accessory for the blocks, specify the size of the blocks based on BDX negotiated block size - can accessory not support the block size? report an error -> send no logs in completion + // where do we define this api? this needs to be supported by accessories like doorlock etc. Seems like we would need a diagnosticlogsLogProviderDelegate here. + // c) When block is received, build up the block with incremented block number and send it across. + // d) request the next block once we receive a block ack. + // e) Send the next block until accessory says its sending the last block + // f) Send block eof + // g) Wait for block EOF ack and tear down the BDX stuff. + // h) any errors occur or bdx timeouts -> tear down the BDX stuff. + + 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); + + 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(); + + if (commandHandle == nullptr) + { + ChipLogError(Controller, "DiagnosticLogsServer: Unable to handle BDX response. commandHandler is null"); + return; + } + + Commands::RetrieveLogsResponse::Type response; + if (error == CHIP_NO_ERROR) + { + response.status = StatusEnum::kSuccess; + commandHandle->AddResponse(mRequestPath,response); + } else { + // Fallback on the response payload log request + HandleLogRequestForResponsePayload(commandHandle, mRequestPath, mIntent); + mDiagnosticLogsBDXTransferHandler.Reset(); + mAsyncCommandHandle.Release(); + } +} + +void DiagnosticLogsServer::SetAsyncCommandHandleAndPath(CommandHandler * commandObj, const ConcreteCommandPath & commandPath) +{ + mAsyncCommandHandle = CommandHandler::Handle(commandObj); + mRequestPath = commandPath; +} + +#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(BDX, "buffer not allocated"); + response.status = StatusEnum::kNoLogs; + commandHandler->AddResponse(path, response); + return; + } + + mLogSessionHandle = logProviderDelegate->StartLogCollection(intent); + + 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())) + { + response.status = StatusEnum::kNoLogs; + commandObj->AddResponse(commandPath, response); + return; + } + + // if log fits just send it. + 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); + LogErrorOnFailure(err); + response.status = StatusEnum::kNoLogs; + commandObj->AddResponse(commandPath, response); + return; } - break; - case chip::app::Clusters::DiagnosticLogs::IntentEnum::kUnknownEnumValue: { - handlerContext.mCommandHandler.AddStatus(handlerContext.mRequestPath, - chip::Protocols::InteractionModel::Status::InvalidCommand); - break; - } - } - }); + 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) -{ - commandObj->AddStatus(commandPath, chip::Protocols::InteractionModel::Status::UnsupportedCommand); - return true; + CommandHandler * commandObj, const ConcreteCommandPath & commandPath, + const Commands::RetrieveLogsRequest::DecodableType & commandData) +{ + 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; } -void MatterDiagnosticLogsPluginServerInitCallback() {} +void MatterDiagnosticLogsPluginServerInitCallback() +{ + // Nothing to do, the server init routine will be done in Instance::Init() +} 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..5ba189d764577d 100644 --- a/src/app/clusters/diagnostic-logs-server/diagnostic-logs-server.h +++ b/src/app/clusters/diagnostic-logs-server/diagnostic-logs-server.h @@ -18,31 +18,78 @@ #pragma once +#include + +#if CHIP_CONFIG_ENABLE_BDX_LOG_TRANSFER +#include +#endif +#include #include #include -#include -#include +namespace chip { +namespace app { +namespace Clusters { +namespace DiagnosticLogs { + +// Spec mandated max file designator length +static constexpr uint8_t kLogFileDesignatorMaxLen = 32; + +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); - DiagnosticLogsCommandHandler() : - CommandHandlerInterface(chip::MakeOptional(chip::EndpointId(kDiagnosticLogsEndpoint)), - chip::app::Clusters::DiagnosticLogs::Id), - mBuffer(mStorage.data(), mStorage.size()) - {} + CHIP_ERROR HandleLogRequestForBDXProtocol(chip::Messaging::ExchangeContext * exchangeCtx, chip::EndpointId endpointId, IntentEnum intent, chip::CharSpan fileDesignator); + + void SetAsyncCommandHandleAndPath(CommandHandler * commandObj, const ConcreteCommandPath & commandPath); - // Inherited from CommandHandlerInterface - void InvokeCommand(HandlerContext & handlerContext) override; + bool HasValidFileDesignator(chip::CharSpan transferFileDesignator); + + bool IsBDXProtocolRequested(TransferProtocolEnum requestedProtocol); - CHIP_ERROR PushLog(const chip::ByteSpan & payload); +#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; + + // Instance + 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 2544deaa32280a..d3f5967261747e 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 @@ -62,6 +73,7 @@ typedef NS_ENUM(NSUInteger, MTRDeviceState) { */ @property (nonatomic, readonly) MTRDeviceState state; + /** * The estimated device system start time. * @@ -201,6 +213,25 @@ 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 NSURL in the MTRDiagnosticLogResult 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 pass in the URL for the requested log. + */ +- (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 cb6c8594dfddb3..a38c4f17f1cc28 100644 --- a/src/darwin/Framework/CHIP/MTRDevice.mm +++ b/src/darwin/Framework/CHIP/MTRDevice.mm @@ -16,6 +16,7 @@ */ #import +#import #import #import "MTRAsyncWorkQueue.h" @@ -23,6 +24,7 @@ #import "MTRBaseDevice_Internal.h" #import "MTRBaseSubscriptionCallback.h" #import "MTRCluster.h" +#import "MTRClusters_Internal.h" #import "MTRClusterConstants.h" #import "MTRDeviceController_Internal.h" #import "MTRDevice_Internal.h" @@ -30,6 +32,8 @@ #import "MTREventTLVValueDecoder_Internal.h" #import "MTRLogging_Internal.h" #import "zap-generated/MTRCommandPayloads_Internal.h" +#import "NSDataSpanConversion.h" +#import "NSStringSpanConversion.h" #include "lib/core/CHIPError.h" #include "lib/core/DataModelTypes.h" @@ -39,7 +43,10 @@ #include #include #include +#include #include +#include "MTRDiagnosticLogsTransferHandler.h" +#include typedef void (^MTRDeviceAttributeReportHandler)(NSArray * _Nonnull); @@ -142,6 +149,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) NSArray *> * unreportedEvents; /** @@ -1230,6 +1238,258 @@ - (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 @"NetworkDiagnostics"; + 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 + queue:(dispatch_queue_t)queue + completion:(void (^)(NSURL * _Nullable logResult, NSError * error))completion +{ + if (timeout > 0) + { + _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_source_cancel(self->_timerSource); + + dispatch_async(queue, ^{ + NSError * error = [NSError errorWithDomain:MTRErrorDomain code:MTRErrorCodeTimeout userInfo:nil]; + completion(nil, error); + }); + return; + + }); + 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 +{ + // TODO: fix the comparision with kNoLogs and kExhausted + return (response == nil || (response.status != nil && [response.status intValue] != 0 && [response.status intValue] != 1) || response.logContent.length == 0); +} + +// Once the consumer calls this API- +// a) if types are not valid, return an error. no logs +// b) start a timer with the timeout if provided. + +// do the following until all requested log types are retrieved +// c) For each type requested in the bitmap, send a command to the diagnostic logs cluster with protocol BDX/intent type - log type +// d) when timeout expires, call the completion with what has been received in filepath array so far. set the timeout expired bool in the response +// e) if one file type was requested, call completion and send the filepath otherwise save the filepath in an array. +// f) if an error occurs or BDX times out and there are no more pending log files, call the completion with what has been received in filepath array so far. +// g) if more than one file types was requested, keep track of pending request and repeat c-> f +// h) if an error occurs or BDX times out and there are more pending requests, move on to the next log type. +// h) when all requested logs have been retreived successfully or an error/timeout occured send the array of filepaths +- (void)_downloadLogOfType:(MTRDiagnosticLogType)type + timeout:(NSTimeInterval)timeout + queue:(dispatch_queue_t)queue + completion:(void (^)(NSURL * _Nullable logResult, NSError * error))completion +{ + ChipLogError(Controller, "downloadLogOfType %ld", type); + if (type != MTRDiagnosticLogTypeEndUserSupport && type != MTRDiagnosticLogTypeNetworkDiagnostics && type != MTRDiagnosticLogTypeCrash) + { + dispatch_async(queue, ^{ + NSError * error = [NSError errorWithDomain:MTRErrorDomain code:MTRErrorCodeInvalidArgument userInfo:nil]; + completion(nil, error); + }); + return; + } + + // Start a timer for the timeout and abort transfer and return the log result + [self _startTimerForDownload:timeout queue:queue completion:completion]; + + MTRClusterDiagnosticLogs * cluster = [[MTRClusterDiagnosticLogs alloc] initWithDevice:self endpointID:@(0) queue:queue]; + + if (cluster == nil) + { + if (self->_timerSource) + { + dispatch_source_cancel(self->_timerSource); + } + dispatch_async(queue, ^{ + NSError * error = [NSError errorWithDomain:MTRErrorDomain code:MTRErrorCodeInvalidState userInfo:nil]; + completion(nil, error); + }); + return; + } + + NSURL * filePath = [self _temporaryFileURLForDownload:type queue:queue completion:completion]; + + if (filePath == nil) + { + if (self->_timerSource) + { + dispatch_source_cancel(self->_timerSource); + } + dispatch_async(queue, ^{ + NSError * error = [NSError errorWithDomain:MTRErrorDomain code:MTRErrorCodeInvalidState userInfo:nil]; + completion(nil, error); + }); + } + + __block MTRDiagnosticLogsTransferHandler * diagnosticLogsTransferHandler = new MTRDiagnosticLogsTransferHandler(filePath, ^(bool result) { + if (self->_timerSource) + { + dispatch_source_cancel(self->_timerSource); + } + if (result == YES) + { + dispatch_async(queue, ^{ + completion(filePath, nil); + }); + } else { + dispatch_async(queue, ^{ + NSError * error = [NSError errorWithDomain:MTRErrorDomain code:MTRErrorCodeInvalidArgument userInfo:nil]; + completion(nil, error); + }); + } + delete(diagnosticLogsTransferHandler); + }); + + // 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) + { + dispatch_async(self.queue, ^{ + if (self->_timerSource) + { + dispatch_source_cancel(self->_timerSource); + } + }); + dispatch_async(queue, ^{ + NSError * error = [NSError errorWithDomain:MTRErrorDomain code:MTRErrorCodeInvalidArgument userInfo:nil]; + completion(nil, error); + }); + + return; + } + + commissioner->ExchangeMgr()->RegisterUnsolicitedMessageHandlerForProtocol(Protocols::BDX::Id, diagnosticLogsTransferHandler); + + dispatch_async(self.queue, ^{ + 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 (diagnosticLogsTransferHandler->IsInBDXSession() && error == nil ) { + return; + } + + if ([self _isErrorResponse:response]) + { + NSLog(@"_downloadLogOfType error response %@", response); + if (self->_timerSource) + { + dispatch_source_cancel(self->_timerSource); + } + + dispatch_async(queue, ^{ + completion(nil, error); + }); + + return; + } + + + // If the response has a log content, copy it into the temporary location and send the URL otherwise we will send the full file once BDX succeeds. + 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); + } + dispatch_async(queue, ^{ + completion(filePath, nil); + }); + + return; + } + } + }]; + }); + } + errorHandler:^(NSError * error) { + if (self->_timerSource) + { + dispatch_source_cancel(self->_timerSource); + } + dispatch_async(queue, ^{ + completion(nil, 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..071819dd31418c --- /dev/null +++ b/src/darwin/Framework/CHIP/MTRDiagnosticLogsTransferHandler.h @@ -0,0 +1,87 @@ +/** + * + * 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 Reset(); + + bool IsInBDXSession() { return mIsInBDXSession; } + +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); + + //CHIP_ERROR OnUnsolicitedMessageReceived( + //const chip::PayloadHeader & payloadHeader, chip::Messaging::ExchangeDelegate * _Nonnull & newDelegate) override; + + // 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 mIsInBDXSession = false; + + uint64_t downloadedBytes = 0; + uint64_t totalFileBytes = 0; +}; diff --git a/src/darwin/Framework/CHIP/MTRDiagnosticLogsTransferHandler.mm b/src/darwin/Framework/CHIP/MTRDiagnosticLogsTransferHandler.mm new file mode 100644 index 00000000000000..5b111f612e5b63 --- /dev/null +++ b/src/darwin/Framework/CHIP/MTRDiagnosticLogsTransferHandler.mm @@ -0,0 +1,306 @@ +/** + * + * 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; + +// TODO Expose a method onto the delegate to make that configurable. +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; + +// Need DiagnosticLogsProcessorInterface handle to process block which will be implemented in darwin/linux + +// TODO: need to check how to handle MTDeviceController being shutdown. + +CHIP_ERROR MTRDiagnosticLogsTransferHandler::PrepareForTransfer(System::Layer * _Nonnull layer, FabricIndex fabricIndex, NodeId nodeId) +{ + assertChipStackLockedByCurrentThread(); + + ReturnErrorOnFailure(ConfigureState(fabricIndex, nodeId)); + + BitFlags flags(bdx::TransferControlFlags::kReceiverDrive); + + return Responder::PrepareForTransfer(layer, kBdxRole, flags, kMaxBdxBlockSize, kBdxTimeout); +} + +MTRDiagnosticLogsTransferHandler::~MTRDiagnosticLogsTransferHandler() +{ +} + +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) + { + // TODO: Map NSError to BDX error + LogErrorOnFailure(mTransfer.AbortTransfer(GetBdxStatusCodeFromChipError(CHIP_ERROR_INCORRECT_STATE))); + return CHIP_ERROR_INCORRECT_STATE; + } + + TransferSession::TransferAcceptData acceptData; + acceptData.ControlMode = bdx::TransferControlFlags::kSenderDrive; + acceptData.MaxBlockSize = mTransfer.GetTransferBlockSize(); + acceptData.StartOffset = mTransfer.GetStartOffset(); + acceptData.Length = mTransfer.GetTransferLength(); + + mTransfer.AcceptTransfer(acceptData); + mIsInBDXSession = 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::kBlockReceived || !event.blockdata.IsEof) { + error = CHIP_ERROR_INTERNAL; + } + + if (error != CHIP_NO_ERROR) + { + // Notify the MTRDevice via the callback that the BDX transfer has completed with error + if (mCallback) + { + mCallback(NO); + } + Reset(); + return error; + } + + // Notify the MTRDevice via the callback that the BDX transfer has completed with success + if (mCallback) + { + mCallback(YES); + } + Reset(); + return CHIP_NO_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) + { + // TODO: map nserror to status code + LogErrorOnFailure(mTransfer.AbortTransfer(GetBdxStatusCodeFromChipError(CHIP_ERROR_INCORRECT_STATE))); + return CHIP_ERROR_INCORRECT_STATE; + } + } else { + LogErrorOnFailure(mTransfer.AbortTransfer(GetBdxStatusCodeFromChipError(CHIP_ERROR_INCORRECT_STATE))); + return CHIP_ERROR_INCORRECT_STATE; + } + + CHIP_ERROR err = mTransfer.PrepareBlockAck(); + LogErrorOnFailure(err); + if (err != CHIP_NO_ERROR) + { + LogErrorOnFailure(mTransfer.AbortTransfer(GetBdxStatusCodeFromChipError(CHIP_ERROR_INCORRECT_STATE))); + return err; + } + if (event.blockdata.IsEof) { + if (mFileHandle != nil) + { + [mFileHandle closeFile]; + mFileHandle = nullptr; + } + OnTransferSessionEnd(event); + } + + return CHIP_NO_ERROR; +} + +CHIP_ERROR MTRDiagnosticLogsTransferHandler::OnMessageToSend(TransferSession::OutputEvent & event) +{ + assertChipStackLockedByCurrentThread(); + + VerifyOrReturnError(mExchangeCtx != nullptr, CHIP_ERROR_INCORRECT_STATE); + //VerifyOrReturnError(mDelegate != 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 ResetState. + // TODO: If we can remove the !mInitialized check in ResetState(), just calling ResetState() will suffice here. + CHIP_ERROR err + = mExchangeCtx->SendMessage(msgTypeData.ProtocolId, msgTypeData.MessageType, std::move(event.MsgData), sendFlags); + if (err != CHIP_NO_ERROR) { + mExchangeCtx->Close(); + mExchangeCtx = nullptr; + 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 = nullptr; + Reset(); + } + 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: + ChipLogError(BDX, "HandleTransferSessionOutput called"); + err = OnTransferSessionBegin(event); + if (err != CHIP_NO_ERROR) { + LogErrorOnFailure(err); + LogErrorOnFailure(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::kMsgToSend: + err = OnMessageToSend(event); + break; + case TransferSession::OutputEventType::kQueryWithSkipReceived: + case TransferSession::OutputEventType::kQueryReceived: + case TransferSession::OutputEventType::kNone: + case TransferSession::OutputEventType::kAckReceived: + case TransferSession::OutputEventType::kAcceptReceived: + // Nothing to do. + break; + default: + // Should never happens. + chipDie(); + break; + } +} + +void MTRDiagnosticLogsTransferHandler::Reset() +{ + mIsInBDXSession = false; + mFileURL = nullptr; + mCallback = nullptr; + + if (mFileHandle != nil) + { + [mFileHandle closeFile]; + mFileHandle = nullptr; + } + if (mExchangeCtx) + { + mExchangeCtx->Close(); + mExchangeCtx = nullptr; + } + mFabricIndex.ClearValue(); + mNodeId.ClearValue(); + + mTransfer.Reset(); +} + +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) +{ + 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(Controller, "Failed to prepare for transfer for BDX"); + return err; + } + } + } + + TransferFacilitator::OnMessageReceived(ec, payloadHeader, std::move(payload)); + + return err; +} diff --git a/src/darwin/Framework/CHIP/zap-generated/MTRCommandPayloadsObjc.h b/src/darwin/Framework/CHIP/zap-generated/MTRCommandPayloadsObjc.h index f798dab013285c..784007101d57f9 100644 --- a/src/darwin/Framework/CHIP/zap-generated/MTRCommandPayloadsObjc.h +++ b/src/darwin/Framework/CHIP/zap-generated/MTRCommandPayloadsObjc.h @@ -3144,7 +3144,7 @@ MTR_AVAILABLE(ios(16.1), macos(13.0), watchos(9.1), tvos(16.1)) @property (nonatomic, copy) NSNumber * _Nonnull requestedProtocol MTR_AVAILABLE(ios(16.1), macos(13.0), watchos(9.1), tvos(16.1)); -@property (nonatomic, copy) NSString * _Nullable transferFileDesignator MTR_AVAILABLE(ios(16.5), macos(13.4), watchos(9.5), tvos(16.5)); +@property (nonatomic, copy) NSString * _Nullable transferFileDesignator MTR_AVAILABLE(ios(16.5), macos(13.0), watchos(9.5), tvos(16.5)); /** * Controls whether the command is a timed command (using Timed Invoke). * diff --git a/src/darwin/Framework/CHIPTests/MTRDeviceTests.m b/src/darwin/Framework/CHIPTests/MTRDeviceTests.m index 083d241d104263..44b4cfea6343d3 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 = 1000; static const uint16_t kTimeoutInSeconds = 3; static const uint64_t kDeviceId = 0x12344321; static NSString * kOnboardingPayload = @"MT:-24J0AFN00KA0648G00"; @@ -275,7 +276,7 @@ - (void)test000_SetUp // just exists to make the setup not look like it's happening inside other // tests. } - +/* - (void)test001_ReadAttribute { XCTestExpectation * expectation = @@ -2695,6 +2696,32 @@ - (void)test900_SubscribeAllAttributes // Wait for report [self waitForExpectations:[NSArray arrayWithObject:reportExpectation] timeout:kTimeoutInSeconds]; +}*/ + +- (void)test901_DownloadLogTypeEndUserSupport +{ + MTRDeviceController * controller = sController; + XCTAssertNotNil(controller); + XCTestExpectation * expectation = [self expectationWithDescription:@"Log Transfer Complete"]; + + dispatch_queue_t queue = dispatch_get_main_queue(); + + dispatch_async(queue, ^{ + MTRDevice * device = [MTRDevice deviceWithNodeID:@(kDeviceId) controller:controller]; + XCTAssertNotNil(device); + [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); + [expectation fulfill]; + }]; + }); + [self waitForExpectations:[NSArray arrayWithObject:expectation] timeout:kDownloadLogTimeoutInSeconds]; } - (void)test999_TearDown diff --git a/src/darwin/Framework/Matter.xcodeproj/project.pbxproj b/src/darwin/Framework/Matter.xcodeproj/project.pbxproj index 33032722024600..a4ae0d8bbe17b8 100644 --- a/src/darwin/Framework/Matter.xcodeproj/project.pbxproj +++ b/src/darwin/Framework/Matter.xcodeproj/project.pbxproj @@ -299,6 +299,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 */ @@ -660,6 +662,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 = ""; }; @@ -1083,6 +1087,8 @@ B202528F2459E34F00F97062 /* CHIP */ = { isa = PBXGroup; children = ( + CFA0EDAE2AD9D76000C7FA40 /* MTRDiagnosticLogsTransferHandler.h */, + CFA0EDAC2AD9D75600C7FA40 /* MTRDiagnosticLogsTransferHandler.mm */, 1E4D655129C30A8700BC3478 /* MTRCommissionableBrowser.mm */, 1E4D654C29C208DD00BC3478 /* MTRCommissionableBrowser.h */, 1E4D654D29C208DD00BC3478 /* MTRCommissionableBrowserDelegate.h */, @@ -1414,6 +1420,7 @@ 3CF134AF289D90FF0017A19E /* MTROperationalCertificateIssuer.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 */, @@ -1746,6 +1753,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 30d6d6b8a65b36..b580a140e87969 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/BdxTransferSession.cpp b/src/protocols/bdx/BdxTransferSession.cpp index f6c54985ab5cd1..28e3e672ebadbe 100644 --- a/src/protocols/bdx/BdxTransferSession.cpp +++ b/src/protocols/bdx/BdxTransferSession.cpp @@ -48,7 +48,6 @@ void PrepareOutgoingMessageEvent(MessageType messageType, chip::bdx::TransferSes chip::bdx::TransferSession::MessageTypeData & outputMsgType) { static_assert(std::is_same, uint8_t>::value, "Cast is not safe"); - pendingOutput = chip::bdx::TransferSession::OutputEventType::kMsgToSend; outputMsgType.ProtocolId = chip::Protocols::MessageTypeTraits::ProtocolId(); outputMsgType.MessageType = static_cast(messageType); @@ -321,7 +320,12 @@ CHIP_ERROR TransferSession::PrepareBlock(const BlockData & inData) VerifyOrReturnError(mState == TransferState::kTransferInProgress, CHIP_ERROR_INCORRECT_STATE); VerifyOrReturnError(mRole == TransferRole::kSender, CHIP_ERROR_INCORRECT_STATE); VerifyOrReturnError(mPendingOutput == OutputEventType::kNone, CHIP_ERROR_INCORRECT_STATE); - VerifyOrReturnError(!mAwaitingResponse, CHIP_ERROR_INCORRECT_STATE); + bool checkAwaitingResponse = (mRole == TransferRole::kReceiver && mControlMode == TransferControlFlags::kSenderDrive) || + (mRole == TransferRole::kSender && mControlMode == TransferControlFlags::kReceiverDrive); + if (checkAwaitingResponse) + { + VerifyOrReturnError(!mAwaitingResponse, CHIP_ERROR_INCORRECT_STATE); + } // Verify non-zero data is provided and is no longer than MaxBlockSize (BlockEOF may contain 0 length data) VerifyOrReturnError((inData.Data != nullptr) && (inData.Length <= mTransferMaxBlockSize), CHIP_ERROR_INVALID_ARGUMENT); @@ -346,8 +350,9 @@ CHIP_ERROR TransferSession::PrepareBlock(const BlockData & inData) } mAwaitingResponse = true; + mLastBlockNum = mNextBlockNum++; - + PrepareOutgoingMessageEvent(msgType, mPendingOutput, mMsgTypeData); return CHIP_NO_ERROR; @@ -527,6 +532,7 @@ CHIP_ERROR TransferSession::HandleStatusReportMessage(const PayloadHeader & head void TransferSession::HandleTransferInit(MessageType msgType, System::PacketBufferHandle msgData) { + VerifyOrReturn(mState == TransferState::kAwaitingInitMsg, PrepareStatusReport(StatusCode::kUnexpectedMessage)); if (mRole == TransferRole::kSender) @@ -564,7 +570,6 @@ void TransferSession::HandleTransferInit(MessageType msgType, System::PacketBuff mPendingOutput = OutputEventType::kInitReceived; mState = TransferState::kNegotiateTransferParams; - #if CHIP_AUTOMATION_LOGGING transferInit.LogMessage(msgType); #endif // CHIP_AUTOMATION_LOGGING @@ -687,18 +692,25 @@ void TransferSession::HandleBlockQueryWithSkip(System::PacketBufferHandle msgDat void TransferSession::HandleBlock(System::PacketBufferHandle msgData) { - VerifyOrReturn(mRole == TransferRole::kReceiver, PrepareStatusReport(StatusCode::kUnexpectedMessage)); + VerifyOrReturn(mRole == TransferRole::kReceiver || mRole == TransferRole::kSender, PrepareStatusReport(StatusCode::kUnexpectedMessage)); VerifyOrReturn(mState == TransferState::kTransferInProgress, PrepareStatusReport(StatusCode::kUnexpectedMessage)); - VerifyOrReturn(mAwaitingResponse, PrepareStatusReport(StatusCode::kUnexpectedMessage)); + bool checkAwaitingResponse = (mRole == TransferRole::kReceiver && mControlMode == TransferControlFlags::kSenderDrive) || + (mRole == TransferRole::kSender && mControlMode == TransferControlFlags::kReceiverDrive); + if (checkAwaitingResponse) + { + VerifyOrReturn(mAwaitingResponse, PrepareStatusReport(StatusCode::kUnexpectedMessage)); + } Block blockMsg; const CHIP_ERROR err = blockMsg.Parse(msgData.Retain()); VerifyOrReturn(err == CHIP_NO_ERROR, PrepareStatusReport(StatusCode::kBadMessageContents)); - - VerifyOrReturn(blockMsg.BlockCounter == mLastQueryNum, PrepareStatusReport(StatusCode::kBadBlockCounter)); + if (mControlMode == TransferControlFlags::kReceiverDrive) + { + VerifyOrReturn(blockMsg.BlockCounter == mLastQueryNum, PrepareStatusReport(StatusCode::kBadBlockCounter)); + } + VerifyOrReturn((blockMsg.DataLength > 0) && (blockMsg.DataLength <= mTransferMaxBlockSize), PrepareStatusReport(StatusCode::kBadMessageContents)); - if (IsTransferLengthDefinite()) { VerifyOrReturn(mNumBytesProcessed + blockMsg.DataLength <= mTransferLength, @@ -727,16 +739,24 @@ void TransferSession::HandleBlockEOF(System::PacketBufferHandle msgData) { VerifyOrReturn(mRole == TransferRole::kReceiver, PrepareStatusReport(StatusCode::kUnexpectedMessage)); VerifyOrReturn(mState == TransferState::kTransferInProgress, PrepareStatusReport(StatusCode::kUnexpectedMessage)); - VerifyOrReturn(mAwaitingResponse, PrepareStatusReport(StatusCode::kUnexpectedMessage)); + + bool checkAwaitingResponse = (mRole == TransferRole::kReceiver && mControlMode == TransferControlFlags::kSenderDrive) || + (mRole == TransferRole::kSender && mControlMode == TransferControlFlags::kReceiverDrive); + if (checkAwaitingResponse) + { + VerifyOrReturn(mAwaitingResponse, PrepareStatusReport(StatusCode::kUnexpectedMessage)); + } BlockEOF blockEOFMsg; const CHIP_ERROR err = blockEOFMsg.Parse(msgData.Retain()); VerifyOrReturn(err == CHIP_NO_ERROR, PrepareStatusReport(StatusCode::kBadMessageContents)); - VerifyOrReturn(blockEOFMsg.BlockCounter == mLastQueryNum, PrepareStatusReport(StatusCode::kBadBlockCounter)); + if (mControlMode == TransferControlFlags::kReceiverDrive) + { + VerifyOrReturn(blockEOFMsg.BlockCounter == mLastQueryNum, PrepareStatusReport(StatusCode::kBadBlockCounter)); + } VerifyOrReturn(blockEOFMsg.DataLength <= mTransferMaxBlockSize, PrepareStatusReport(StatusCode::kBadMessageContents)); - mBlockEventData.Data = blockEOFMsg.Data; mBlockEventData.Length = blockEOFMsg.DataLength; mBlockEventData.IsEof = true; mBlockEventData.BlockCounter = blockEOFMsg.BlockCounter; @@ -746,7 +766,7 @@ void TransferSession::HandleBlockEOF(System::PacketBufferHandle msgData) mNumBytesProcessed += blockEOFMsg.DataLength; mLastBlockNum = blockEOFMsg.BlockCounter; - + mAwaitingResponse = false; mState = TransferState::kReceivedEOF; @@ -870,7 +890,6 @@ CHIP_ERROR TransferSession::VerifyProposedMode(const BitFlags