From d01addd7087f16e2b9b03869bb4e648a4635c7af Mon Sep 17 00:00:00 2001 From: Vivien Nicolas Date: Tue, 23 Jan 2024 00:09:30 +0100 Subject: [PATCH] Add support for diagnostic logs server cluster with BDX support (#31198) * [CI] Add Automation category to the logs that are accepted when in interactive mode to make sure it does not crash when using BDX * [CI] Ensure that WaitForMessage does not read content that it has already consumed * [tracing] Implement BDX message decoding in examples/common/tracing/decoder/bdx/Decoder.cpp * Add a single implementation for GetBdxStatusCodeFromChipError * Update Diagnostic Logs server implementation with an application provided delegate * Add Diagnostic Logs server delegate implementation to the all-clusters-app * Add some YAML tests for the diagnostic logs cluster * Add BDX support to src/app/clusters/diagnostic-logs-server Make sure to reset the exchange context in the TransferFacilitator to make it possible to reuse it for a new exchange * Add BDXTransferServer to src/protocols/bdx * Add an instance of BDXTransferServer to be started by the CHIPDeviceControllerFactory * Add chip-tool BDXTransferServerDelegate support * Add more YAML tests for the diagnostic logs cluster but related to BDX this time --- .../all-clusters-app.matter | 5 + .../all-clusters-common/all-clusters-app.zap | 72 ++ .../all-clusters-app/linux/AppOptions.cpp | 61 +- examples/all-clusters-app/linux/AppOptions.h | 7 +- examples/all-clusters-app/linux/BUILD.gn | 1 + ...diagnostic-logs-provider-delegate-impl.cpp | 177 +++++ .../linux/include/CHIPProjectAppConfig.h | 2 + .../diagnostic-logs-provider-delegate-impl.h | 78 +++ .../all-clusters-app/linux/main-common.cpp | 14 + examples/chip-tool/BUILD.gn | 1 + .../commands/clusters/ClusterCommand.h | 18 + .../BDXDiagnosticLogsServerDelegate.cpp | 199 ++++++ .../common/BDXDiagnosticLogsServerDelegate.h | 45 ++ .../chip-tool/commands/common/CHIPCommand.cpp | 8 + .../chip-tool/commands/common/CHIPCommand.h | 2 + .../interactive/InteractiveCommands.cpp | 4 + examples/common/tracing/TraceDecoder.h | 2 +- .../common/tracing/decoder/bdx/Decoder.cpp | 222 +++++- .../common/tracing/decoder/logging/Log.cpp | 20 +- examples/common/tracing/decoder/logging/Log.h | 2 + .../tv-casting-app/tv-casting-common/BUILD.gn | 1 + .../clusters/accessory_server_bridge.py | 25 +- .../clusters/system_commands.py | 18 + scripts/tests/chiptest/__init__.py | 1 + scripts/tests/chiptest/accessories.py | 12 + scripts/tests/chiptest/runner.py | 2 +- scripts/tests/chiptest/test_definition.py | 5 + src/app/chip_data_model.gni | 8 + .../BDXDiagnosticLogsProvider.cpp | 307 ++++++++ .../BDXDiagnosticLogsProvider.h | 99 +++ .../DiagnosticLogsProviderDelegate.h | 104 +++ .../diagnostic-logs-server.cpp | 240 ++++--- .../diagnostic-logs-server.h | 50 +- src/app/tests/suites/TestDiagnosticLogs.yaml | 660 ++++++++++++++++++ .../CHIPDeviceControllerFactory.cpp | 9 + .../CHIPDeviceControllerSystemState.h | 9 +- src/controller/java/BdxOTASender.cpp | 13 - src/controller/java/BdxOTASender.h | 2 - .../CHIP/MTROTAProviderDelegateBridge.mm | 11 - src/lib/core/CHIPConfig.h | 10 + src/protocols/bdx/BUILD.gn | 7 + src/protocols/bdx/BdxMessages.h | 19 +- src/protocols/bdx/BdxTransferProxy.h | 48 ++ .../bdx/BdxTransferProxyDiagnosticLog.cpp | 98 +++ .../bdx/BdxTransferProxyDiagnosticLog.h | 56 ++ src/protocols/bdx/BdxTransferServer.cpp | 210 ++++++ src/protocols/bdx/BdxTransferServer.h | 115 +++ src/protocols/bdx/BdxTransferServerDelegate.h | 53 ++ src/protocols/bdx/DiagnosticLogs.h | 32 + src/protocols/bdx/StatusCode.cpp | 41 ++ src/protocols/bdx/StatusCode.h | 47 ++ src/protocols/bdx/TransferFacilitator.cpp | 7 + src/protocols/bdx/TransferFacilitator.h | 13 +- 53 files changed, 3101 insertions(+), 171 deletions(-) create mode 100644 examples/all-clusters-app/linux/diagnostic-logs-provider-delegate-impl.cpp create mode 100644 examples/all-clusters-app/linux/include/diagnostic-logs-provider-delegate-impl.h create mode 100644 examples/chip-tool/commands/common/BDXDiagnosticLogsServerDelegate.cpp create mode 100644 examples/chip-tool/commands/common/BDXDiagnosticLogsServerDelegate.h create mode 100644 src/app/clusters/diagnostic-logs-server/BDXDiagnosticLogsProvider.cpp create mode 100644 src/app/clusters/diagnostic-logs-server/BDXDiagnosticLogsProvider.h create mode 100644 src/app/clusters/diagnostic-logs-server/DiagnosticLogsProviderDelegate.h create mode 100644 src/app/tests/suites/TestDiagnosticLogs.yaml create mode 100644 src/protocols/bdx/BdxTransferProxy.h create mode 100644 src/protocols/bdx/BdxTransferProxyDiagnosticLog.cpp create mode 100644 src/protocols/bdx/BdxTransferProxyDiagnosticLog.h create mode 100644 src/protocols/bdx/BdxTransferServer.cpp create mode 100644 src/protocols/bdx/BdxTransferServer.h create mode 100644 src/protocols/bdx/BdxTransferServerDelegate.h create mode 100644 src/protocols/bdx/DiagnosticLogs.h create mode 100644 src/protocols/bdx/StatusCode.cpp create mode 100644 src/protocols/bdx/StatusCode.h 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 d2e33e86376e59..c273dbbda6ad59 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 @@ -7228,10 +7228,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 7206a9da14d7aa..47ecf27106eff5 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 @@ -2723,9 +2723,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, diff --git a/examples/all-clusters-app/linux/AppOptions.cpp b/examples/all-clusters-app/linux/AppOptions.cpp index ef42660972f8d9..50be6561c6bb4a 100644 --- a/examples/all-clusters-app/linux/AppOptions.cpp +++ b/examples/all-clusters-app/linux/AppOptions.cpp @@ -27,11 +27,23 @@ using chip::ArgParser::OptionDef; using chip::ArgParser::OptionSet; using chip::ArgParser::PrintArgError; -constexpr uint16_t kOptionDacProviderFilePath = 0xFF01; -constexpr uint16_t kOptionMinCommissioningTimeout = 0xFF02; +constexpr uint16_t kOptionDacProviderFilePath = 0xFF01; +constexpr uint16_t kOptionMinCommissioningTimeout = 0xFF02; +constexpr uint16_t kOptionEndUserSupportFilePath = 0xFF03; +constexpr uint16_t kOptionNetworkDiagnosticsFilePath = 0xFF04; +constexpr uint16_t kOptionCrashFilePath = 0xFF05; static chip::Credentials::Examples::TestHarnessDACProvider mDacProvider; +static chip::Optional sEndUserSupportLogFilePath; +static chip::Optional sNetworkDiagnosticsLogFilePath; +static chip::Optional sCrashLogFilePath; + +bool AppOptions::IsEmptyString(const char * value) +{ + return (value == nullptr || strlen(value) == 0); +} + bool AppOptions::HandleOptions(const char * program, OptionSet * options, int identifier, const char * name, const char * value) { bool retval = true; @@ -45,6 +57,27 @@ bool AppOptions::HandleOptions(const char * program, OptionSet * options, int id commissionMgr.OverrideMinCommissioningTimeout(chip::System::Clock::Seconds16(static_cast(atoi(value)))); break; } + case kOptionEndUserSupportFilePath: { + if (!IsEmptyString(value)) + { + sEndUserSupportLogFilePath.SetValue(value); + } + break; + } + case kOptionNetworkDiagnosticsFilePath: { + if (!IsEmptyString(value)) + { + sNetworkDiagnosticsLogFilePath.SetValue(value); + } + break; + } + case kOptionCrashFilePath: { + if (!IsEmptyString(value)) + { + sCrashLogFilePath.SetValue(value); + } + break; + } default: PrintArgError("%s: INTERNAL ERROR: Unhandled option: %s\n", program, name); retval = false; @@ -59,6 +92,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 +104,12 @@ OptionSet * AppOptions::GetOptions() " A json file with data used by the example dac provider to validate device attestation procedure.\n" " --min_commissioning_timeout \n" " The minimum time in seconds during which commissioning session establishment is allowed by the Node.\n" + " --end_user_support_log \n" + " The end user support log file to be used for diagnostic logs transfer.\n" + " --network_diagnostics_log \n" + " The network diagnostics log file to be used for diagnostic logs transfer.\n" + " --crash_log \n" + " The crash log file to be used for diagnostic logs transfer.\n" }; return &options; @@ -77,3 +119,18 @@ chip::Credentials::DeviceAttestationCredentialsProvider * AppOptions::GetDACProv { return &mDacProvider; } + +chip::Optional AppOptions::GetEndUserSupportLogFilePath() +{ + return sEndUserSupportLogFilePath; +} + +chip::Optional AppOptions::GetNetworkDiagnosticsLogFilePath() +{ + return sNetworkDiagnosticsLogFilePath; +} + +chip::Optional AppOptions::GetCrashLogFilePath() +{ + return sCrashLogFilePath; +} diff --git a/examples/all-clusters-app/linux/AppOptions.h b/examples/all-clusters-app/linux/AppOptions.h index 3073c66176331f..dfdd2c0e66fe70 100644 --- a/examples/all-clusters-app/linux/AppOptions.h +++ b/examples/all-clusters-app/linux/AppOptions.h @@ -1,6 +1,6 @@ /* * - * Copyright (c) 2022 Project CHIP Authors + * Copyright (c) 2022-2023 Project CHIP Authors * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -27,8 +27,13 @@ class AppOptions public: static chip::ArgParser::OptionSet * GetOptions(); static chip::Credentials::DeviceAttestationCredentialsProvider * GetDACProvider(); + static chip::Optional GetEndUserSupportLogFilePath(); + static chip::Optional GetNetworkDiagnosticsLogFilePath(); + static chip::Optional GetCrashLogFilePath(); private: static bool HandleOptions(const char * program, chip::ArgParser::OptionSet * options, int identifier, const char * name, const char * value); + + static bool IsEmptyString(const char * value); }; diff --git a/examples/all-clusters-app/linux/BUILD.gn b/examples/all-clusters-app/linux/BUILD.gn index 960d3aadbc9ed7..20dd248abf2a1a 100644 --- a/examples/all-clusters-app/linux/BUILD.gn +++ b/examples/all-clusters-app/linux/BUILD.gn @@ -45,6 +45,7 @@ source_set("chip-all-clusters-common") { "${chip_root}/examples/all-clusters-app/all-clusters-common/src/static-supported-modes-manager.cpp", "${chip_root}/examples/all-clusters-app/all-clusters-common/src/static-supported-temperature-levels.cpp", "${chip_root}/examples/all-clusters-app/all-clusters-common/src/tcc-mode.cpp", + "${chip_root}/examples/all-clusters-app/linux/diagnostic-logs-provider-delegate-impl.cpp", "${chip_root}/examples/energy-management-app/energy-management-common/src/DeviceEnergyManagementDelegateImpl.cpp", "${chip_root}/examples/energy-management-app/energy-management-common/src/DeviceEnergyManagementManager.cpp", "${chip_root}/examples/energy-management-app/energy-management-common/src/EnergyEvseDelegateImpl.cpp", diff --git a/examples/all-clusters-app/linux/diagnostic-logs-provider-delegate-impl.cpp b/examples/all-clusters-app/linux/diagnostic-logs-provider-delegate-impl.cpp new file mode 100644 index 00000000000000..12636a9c0799f2 --- /dev/null +++ b/examples/all-clusters-app/linux/diagnostic-logs-provider-delegate-impl.cpp @@ -0,0 +1,177 @@ +/* + * + * 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/diagnostic-logs-provider-delegate-impl.h" + +#include + +using namespace chip; +using namespace chip::app::Clusters::DiagnosticLogs; + +LogProvider LogProvider::sInstance; + +namespace { +bool IsValidIntent(IntentEnum intent) +{ + return intent != IntentEnum::kUnknownEnumValue; +} + +size_t GetFileSize(FILE * fp) +{ + VerifyOrReturnValue(nullptr != fp, 0); + + auto offset = ftell(fp); + VerifyOrReturnValue(offset != -1, 0); + + auto rv = fseek(fp, 0, SEEK_END); + VerifyOrReturnValue(rv == 0, 0); + + auto fileSize = ftell(fp); + VerifyOrReturnValue(fileSize != -1, 0); + + rv = fseek(fp, offset, SEEK_SET); + VerifyOrReturnValue(rv == 0, 0); + + return static_cast(fileSize); +} +} // namespace + +LogProvider::~LogProvider() +{ + for (auto f : mFiles) + { + auto rv = fclose(f.second); + if (rv != 0) + { + ChipLogError(NotSpecified, "Error when closing file pointer: %p (%d)", f.second, errno); + } + } + mFiles.clear(); +} + +CHIP_ERROR LogProvider::GetLogForIntent(IntentEnum intent, MutableByteSpan & outBuffer, Optional & outTimeStamp, + Optional & outTimeSinceBoot) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + LogSessionHandle sessionHandle = kInvalidLogSessionHandle; + + err = StartLogCollection(intent, sessionHandle, outTimeStamp, outTimeSinceBoot); + VerifyOrReturnError(CHIP_NO_ERROR == err, err, outBuffer.reduce_size(0)); + + bool unusedOutIsEndOfLog; + err = CollectLog(sessionHandle, outBuffer, unusedOutIsEndOfLog); + VerifyOrReturnError(CHIP_NO_ERROR == err, err, outBuffer.reduce_size(0)); + + err = EndLogCollection(sessionHandle); + VerifyOrReturnError(CHIP_NO_ERROR == err, err, outBuffer.reduce_size(0)); + + return CHIP_NO_ERROR; +} + +CHIP_ERROR LogProvider::StartLogCollection(IntentEnum intent, LogSessionHandle & outHandle, Optional & outTimeStamp, + Optional & outTimeSinceBoot) +{ + VerifyOrReturnValue(IsValidIntent(intent), CHIP_ERROR_INVALID_ARGUMENT); + + auto filePath = GetFilePathForIntent(intent); + VerifyOrReturnValue(filePath.HasValue(), CHIP_ERROR_NOT_FOUND); + + auto fp = fopen(filePath.Value().c_str(), "rb"); + VerifyOrReturnValue(!(nullptr == fp && errno == ENOENT), CHIP_ERROR_NOT_FOUND); + VerifyOrReturnValue(nullptr != fp, CHIP_ERROR_INTERNAL); + + mLogSessionHandle++; + // If the session handle rolls over to UINT16_MAX which is invalid, reset to 0. + VerifyOrDo(mLogSessionHandle != kInvalidLogSessionHandle, mLogSessionHandle = 0); + + outHandle = mLogSessionHandle; + mFiles[mLogSessionHandle] = fp; + return CHIP_NO_ERROR; +} + +CHIP_ERROR LogProvider::EndLogCollection(LogSessionHandle sessionHandle) +{ + VerifyOrReturnValue(sessionHandle != kInvalidLogSessionHandle, CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrReturnValue(mFiles.count(sessionHandle), CHIP_ERROR_INVALID_ARGUMENT); + + auto fp = mFiles[sessionHandle]; + mFiles.erase(sessionHandle); + + auto rv = fclose(fp); + VerifyOrReturnError(rv == 0, CHIP_ERROR_POSIX(errno)); + + return CHIP_NO_ERROR; +} + +CHIP_ERROR LogProvider::CollectLog(LogSessionHandle sessionHandle, MutableByteSpan & outBuffer, bool & outIsEndOfLog) +{ + VerifyOrReturnValue(sessionHandle != kInvalidLogSessionHandle, CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrReturnValue(mFiles.count(sessionHandle), CHIP_ERROR_INVALID_ARGUMENT); + + auto fp = mFiles[sessionHandle]; + auto fileSize = GetFileSize(fp); + auto count = std::min(fileSize, outBuffer.size()); + + VerifyOrReturnError(CanCastTo(count), CHIP_ERROR_INVALID_ARGUMENT, outBuffer.reduce_size(0)); + + auto bytesRead = fread(outBuffer.data(), 1, count, fp); + VerifyOrReturnError(CanCastTo(bytesRead), CHIP_ERROR_INTERNAL, outBuffer.reduce_size(0)); + + outBuffer.reduce_size(static_cast(bytesRead)); + outIsEndOfLog = fileSize == static_cast(ftell(fp)); + return CHIP_NO_ERROR; +} + +size_t LogProvider::GetSizeForIntent(IntentEnum intent) +{ + VerifyOrReturnValue(IsValidIntent(intent), 0); + + auto filePath = GetFilePathForIntent(intent); + VerifyOrReturnValue(filePath.HasValue(), 0); + + auto fp = fopen(filePath.Value().c_str(), "rb"); + VerifyOrReturnValue(nullptr != fp, 0); + + auto fileSize = GetFileSize(fp); + + auto rv = fclose(fp); + if (rv != 0) + { + ChipLogError(NotSpecified, "Error when closing file pointer: %p (%d)", fp, errno); + } + + return fileSize; +} + +Optional LogProvider::GetFilePathForIntent(IntentEnum intent) const +{ + switch (intent) + { + case IntentEnum::kEndUserSupport: + return mEndUserSupportLogFilePath; + case IntentEnum::kNetworkDiag: + return mNetworkDiagnosticsLogFilePath; + case IntentEnum::kCrashLogs: + return mCrashLogFilePath; + case IntentEnum::kUnknownEnumValue: + // It should never happen. + chipDie(); + } + + return NullOptional; +} diff --git a/examples/all-clusters-app/linux/include/CHIPProjectAppConfig.h b/examples/all-clusters-app/linux/include/CHIPProjectAppConfig.h index 2553ccd9cd4ed1..588d77da1ce652 100644 --- a/examples/all-clusters-app/linux/include/CHIPProjectAppConfig.h +++ b/examples/all-clusters-app/linux/include/CHIPProjectAppConfig.h @@ -45,3 +45,5 @@ // Enable batching of up to 5 commands. #define CHIP_CONFIG_MAX_PATHS_PER_INVOKE 5 + +#define CHIP_CONFIG_ENABLE_BDX_LOG_TRANSFER 1 diff --git a/examples/all-clusters-app/linux/include/diagnostic-logs-provider-delegate-impl.h b/examples/all-clusters-app/linux/include/diagnostic-logs-provider-delegate-impl.h new file mode 100644 index 00000000000000..c0e69f1c5bde0b --- /dev/null +++ b/examples/all-clusters-app/linux/include/diagnostic-logs-provider-delegate-impl.h @@ -0,0 +1,78 @@ +/* + * + * Copyright (c) 2023 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include + +#include +#include + +namespace chip { +namespace app { +namespace Clusters { +namespace DiagnosticLogs { + +/** + * The LogProvider class serves as the sole instance delegate for handling diagnostic logs. + * + * It implements the DiagnosticLogsProviderDelegate interface and provides mechanisms to set file paths for different types of logs + * (end-user support, network diagnostics, and crash logs). + */ + +class LogProvider : public DiagnosticLogsProviderDelegate +{ +public: + static inline LogProvider & GetInstance() { return sInstance; } + + void SetEndUserSupportLogFilePath(Optional logFilePath) { mEndUserSupportLogFilePath = logFilePath; } + void SetNetworkDiagnosticsLogFilePath(Optional logFilePath) { mNetworkDiagnosticsLogFilePath = logFilePath; } + void SetCrashLogFilePath(Optional logFilePath) { mCrashLogFilePath = logFilePath; } + + /////////// DiagnosticLogsProviderDelegate Interface ///////// + CHIP_ERROR StartLogCollection(IntentEnum intent, LogSessionHandle & outHandle, Optional & outTimeStamp, + Optional & outTimeSinceBoot) override; + CHIP_ERROR EndLogCollection(LogSessionHandle sessionHandle) override; + CHIP_ERROR CollectLog(LogSessionHandle sessionHandle, MutableByteSpan & outBuffer, bool & outIsEndOfLog) override; + size_t GetSizeForIntent(IntentEnum intent) override; + CHIP_ERROR GetLogForIntent(IntentEnum intent, MutableByteSpan & outBuffer, Optional & outTimeStamp, + Optional & outTimeSinceBoot) override; + +private: + static LogProvider sInstance; + LogProvider() = default; + ~LogProvider(); + + LogProvider(const LogProvider &) = delete; + LogProvider & operator=(const LogProvider &) = delete; + + Optional GetFilePathForIntent(IntentEnum intent) const; + + Optional mEndUserSupportLogFilePath; + Optional mNetworkDiagnosticsLogFilePath; + Optional mCrashLogFilePath; + + std::map mFiles; + + LogSessionHandle mLogSessionHandle = kInvalidLogSessionHandle; +}; + +} // namespace DiagnosticLogs +} // namespace Clusters +} // namespace app +} // namespace chip diff --git a/examples/all-clusters-app/linux/main-common.cpp b/examples/all-clusters-app/linux/main-common.cpp index a3dd33c5e79de0..1d849dc5f2751b 100644 --- a/examples/all-clusters-app/linux/main-common.cpp +++ b/examples/all-clusters-app/linux/main-common.cpp @@ -17,12 +17,14 @@ */ #include "AllClustersCommandDelegate.h" +#include "AppOptions.h" #include "ValveControlDelegate.h" #include "WindowCoveringManager.h" #include "air-quality-instance.h" #include "device-energy-management-modes.h" #include "dishwasher-mode.h" #include "energy-evse-modes.h" +#include "include/diagnostic-logs-provider-delegate-impl.h" #include "include/tv-callbacks.h" #include "laundry-dryer-controls-delegate-impl.h" #include "laundry-washer-controls-delegate-impl.h" @@ -39,6 +41,7 @@ #include #include #include +#include #include #include #include @@ -286,3 +289,14 @@ 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) +{ + auto & logProvider = LogProvider::GetInstance(); + logProvider.SetEndUserSupportLogFilePath(AppOptions::GetEndUserSupportLogFilePath()); + logProvider.SetNetworkDiagnosticsLogFilePath(AppOptions::GetNetworkDiagnosticsLogFilePath()); + logProvider.SetCrashLogFilePath(AppOptions::GetCrashLogFilePath()); + + DiagnosticLogsServer::Instance().SetDiagnosticLogsProviderDelegate(endpoint, &logProvider); +} diff --git a/examples/chip-tool/BUILD.gn b/examples/chip-tool/BUILD.gn index 53568316114c9a..c6f5edc819ba95 100644 --- a/examples/chip-tool/BUILD.gn +++ b/examples/chip-tool/BUILD.gn @@ -55,6 +55,7 @@ static_library("chip-tool-utils") { "${chip_root}/zzz_generated/chip-tool/zap-generated/cluster/logging/DataModelLogger.cpp", "commands/clusters/ModelCommand.cpp", "commands/clusters/ModelCommand.h", + "commands/common/BDXDiagnosticLogsServerDelegate.cpp", "commands/common/CHIPCommand.cpp", "commands/common/CHIPCommand.h", "commands/common/Command.cpp", diff --git a/examples/chip-tool/commands/clusters/ClusterCommand.h b/examples/chip-tool/commands/clusters/ClusterCommand.h index 6b68e1b04bfea0..792588a886dffb 100644 --- a/examples/chip-tool/commands/clusters/ClusterCommand.h +++ b/examples/chip-tool/commands/clusters/ClusterCommand.h @@ -55,6 +55,22 @@ class ClusterCommand : public InteractionModelCommands, public ModelCommand, pub return InteractionModelCommands::SendCommand(device, endpointId, clusterId, commandId, value); } + CHIP_ERROR SendCommand(chip::DeviceProxy * device, chip::EndpointId endpointId, chip::ClusterId clusterId, + chip::CommandId commandId, + const chip::app::Clusters::DiagnosticLogs::Commands::RetrieveLogsRequest::Type & value) + { + ReturnErrorOnFailure(InteractionModelCommands::SendCommand(device, endpointId, clusterId, commandId, value)); + + if (value.transferFileDesignator.HasValue() && + value.requestedProtocol == chip::app::Clusters::DiagnosticLogs::TransferProtocolEnum::kBdx) + { + auto sender = mCommandSender.back().get(); + auto fileDesignator = value.transferFileDesignator.Value(); + BDXDiagnosticLogsServerDelegate::GetInstance().AddFileDesignator(sender, fileDesignator); + } + return CHIP_NO_ERROR; + } + CHIP_ERROR SendGroupCommand(chip::GroupId groupId, chip::FabricIndex fabricIndex) override { return InteractionModelCommands::SendGroupCommand(groupId, fabricIndex, mClusterId, mCommandId, mPayload); @@ -120,6 +136,8 @@ class ClusterCommand : public InteractionModelCommands, public ModelCommand, pub shouldStop = mRepeatCount.Value() == 0; } + BDXDiagnosticLogsServerDelegate::GetInstance().RemoveFileDesignator(client); + if (shouldStop) { SetCommandExitStatus(mError); diff --git a/examples/chip-tool/commands/common/BDXDiagnosticLogsServerDelegate.cpp b/examples/chip-tool/commands/common/BDXDiagnosticLogsServerDelegate.cpp new file mode 100644 index 00000000000000..27260a54f57887 --- /dev/null +++ b/examples/chip-tool/commands/common/BDXDiagnosticLogsServerDelegate.cpp @@ -0,0 +1,199 @@ +/* + * 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 "BDXDiagnosticLogsServerDelegate.h" + +#include + +constexpr const char kTmpDir[] = "/tmp/"; +constexpr uint8_t kMaxFileDesignatorLen = 32; +constexpr uint16_t kMaxFilePathLen = kMaxFileDesignatorLen + sizeof(kTmpDir) + 1; + +// For testing a few file names trigger an error depending on the current 'phase'. +constexpr char kErrorOnTransferBegin[] = "Error:OnTransferBegin"; +constexpr char kErrorOnTransferData[] = "Error:OnTransferData"; +constexpr char kErrorOnTransferEnd[] = "Error:OnTransferEnd"; + +BDXDiagnosticLogsServerDelegate BDXDiagnosticLogsServerDelegate::sInstance; + +CHIP_ERROR CheckForErrorRequested(const chip::CharSpan & phaseErrorTarget, const chip::CharSpan & fileDesignator) +{ + VerifyOrReturnError(!phaseErrorTarget.data_equal(fileDesignator), CHIP_ERROR_INTERNAL); + return CHIP_NO_ERROR; +} + +CHIP_ERROR CheckFileDesignatorAllowed(const std::map & fileDesignators, + const chip::CharSpan & fileDesignator) +{ + std::string targetFileDesignator(fileDesignator.data(), fileDesignator.size()); + + for (auto it = fileDesignators.begin(); it != fileDesignators.end(); it++) + { + auto expectedFileDesignator = it->second; + if (targetFileDesignator.compare(expectedFileDesignator) == 0) + { + return CHIP_NO_ERROR; + } + } + + return CHIP_ERROR_NOT_FOUND; +} + +CHIP_ERROR GetFilePath(const chip::CharSpan & fileDesignator, chip::MutableCharSpan & outFilePath) +{ + VerifyOrReturnError(fileDesignator.size() <= kMaxFileDesignatorLen, CHIP_ERROR_INVALID_STRING_LENGTH); + // sizeof(kTmpDir) includes the trailing null. + VerifyOrReturnError(outFilePath.size() >= sizeof(kTmpDir) - 1 + fileDesignator.size(), CHIP_ERROR_INTERNAL); + + memcpy(outFilePath.data(), kTmpDir, sizeof(kTmpDir) - 1); + memcpy(outFilePath.data() + sizeof(kTmpDir) - 1, fileDesignator.data(), fileDesignator.size()); + outFilePath.reduce_size(sizeof(kTmpDir) - 1 + fileDesignator.size()); + + return CHIP_NO_ERROR; +} + +CHIP_ERROR CheckFileExists(const char * filePath) +{ + if (access(filePath, F_OK) != 0) + { + ChipLogError(chipTool, "The file '%s' for dumping the logs does not exist.", filePath); + return CHIP_ERROR_INCORRECT_STATE; + } + + return CHIP_NO_ERROR; +} + +CHIP_ERROR CheckFileDoesNotExist(const char * filePath) +{ + if (access(filePath, F_OK) == 0) + { + ChipLogError(chipTool, "The file '%s' for dumping the logs already exists.", filePath); + return CHIP_ERROR_INCORRECT_STATE; + } + + return CHIP_NO_ERROR; +} + +void LogFileDesignator(const char * prefix, const chip::CharSpan & fileDesignator, CHIP_ERROR error = CHIP_NO_ERROR) +{ +#if CHIP_PROGRESS_LOGGING + auto size = static_cast(fileDesignator.size()); + auto data = fileDesignator.data(); + ChipLogProgress(chipTool, "%s (%u): %.*s", prefix, size, size, data); +#endif // CHIP_PROGRESS_LOGGING + + if (CHIP_NO_ERROR != error) + { + ChipLogError(chipTool, "%s: %s", prefix, ErrorStr(error)); + } +} + +CHIP_ERROR CreateFile(const char * filePath) +{ + VerifyOrReturnError(nullptr != filePath, CHIP_ERROR_INVALID_ARGUMENT); + + auto fd = fopen(filePath, "w+"); + VerifyOrReturnError(nullptr != fd, CHIP_ERROR_WRITE_FAILED); + + auto rv = fclose(fd); + VerifyOrReturnError(EOF != rv, CHIP_ERROR_INTERNAL); + + return CHIP_NO_ERROR; +} + +CHIP_ERROR AppendToFile(const char * filePath, const chip::ByteSpan & data) +{ + VerifyOrReturnError(nullptr != filePath, CHIP_ERROR_INVALID_ARGUMENT); + + auto fd = fopen(filePath, "a"); + VerifyOrReturnError(nullptr != fd, CHIP_ERROR_WRITE_FAILED); + + fwrite(data.data(), data.size(), 1, fd); + + auto rv = fclose(fd); + VerifyOrReturnError(EOF != rv, CHIP_ERROR_INTERNAL); + + return CHIP_NO_ERROR; +} + +CHIP_ERROR BDXDiagnosticLogsServerDelegate::OnTransferBegin(chip::bdx::BDXTransferProxy * transfer) +{ + auto fileDesignator = transfer->GetFileDesignator(); + LogFileDesignator("OnTransferBegin", fileDesignator); + + chip::CharSpan phaseErrorTarget(kErrorOnTransferBegin, sizeof(kErrorOnTransferBegin) - 1); + ReturnErrorOnFailure(CheckForErrorRequested(phaseErrorTarget, fileDesignator)); + ReturnErrorOnFailure(CheckFileDesignatorAllowed(mFileDesignators, fileDesignator)); + + char outputFilePath[kMaxFilePathLen] = { 0 }; + chip::MutableCharSpan outputFilePathSpan(outputFilePath); + ReturnErrorOnFailure(GetFilePath(fileDesignator, outputFilePathSpan)); + + // Ensure null-termination of the filename, since we will be passing it around as a char *. + VerifyOrReturnError(outputFilePathSpan.size() < ArraySize(outputFilePath), CHIP_ERROR_INTERNAL); + outputFilePath[outputFilePathSpan.size()] = '\0'; + + ReturnErrorOnFailure(CheckFileDoesNotExist(outputFilePath)); + ReturnErrorOnFailure(CreateFile(outputFilePath)); + + return transfer->Accept(); +} + +CHIP_ERROR BDXDiagnosticLogsServerDelegate::OnTransferEnd(chip::bdx::BDXTransferProxy * transfer, CHIP_ERROR error) +{ + auto fileDesignator = transfer->GetFileDesignator(); + LogFileDesignator("OnTransferEnd", fileDesignator, error); + + chip::CharSpan phaseErrorTarget(kErrorOnTransferEnd, sizeof(kErrorOnTransferEnd) - 1); + ReturnErrorOnFailure(CheckForErrorRequested(phaseErrorTarget, fileDesignator)); + + char outputFilePath[kMaxFilePathLen] = { 0 }; + chip::MutableCharSpan outputFilePathSpan(outputFilePath); + ReturnErrorOnFailure(GetFilePath(fileDesignator, outputFilePathSpan)); + ReturnErrorOnFailure(CheckFileExists(outputFilePath)); + + return CHIP_NO_ERROR; +} + +CHIP_ERROR BDXDiagnosticLogsServerDelegate::OnTransferData(chip::bdx::BDXTransferProxy * transfer, const chip::ByteSpan & data) +{ + auto fileDesignator = transfer->GetFileDesignator(); + LogFileDesignator("OnTransferData", fileDesignator); + + chip::CharSpan phaseErrorTarget(kErrorOnTransferData, sizeof(kErrorOnTransferData) - 1); + ReturnErrorOnFailure(CheckForErrorRequested(phaseErrorTarget, fileDesignator)); + + char outputFilePath[kMaxFilePathLen] = { 0 }; + chip::MutableCharSpan outputFilePathSpan(outputFilePath); + ReturnErrorOnFailure(GetFilePath(fileDesignator, outputFilePathSpan)); + ReturnErrorOnFailure(CheckFileExists(outputFilePath)); + ReturnErrorOnFailure(AppendToFile(outputFilePath, data)); + + return transfer->Continue(); +} + +void BDXDiagnosticLogsServerDelegate::AddFileDesignator(chip::app::CommandSender * sender, const chip::CharSpan & fileDesignator) +{ + std::string entry(fileDesignator.data(), fileDesignator.size()); + mFileDesignators[sender] = entry; +} + +void BDXDiagnosticLogsServerDelegate::RemoveFileDesignator(chip::app::CommandSender * sender) +{ + mFileDesignators.erase(sender); +} diff --git a/examples/chip-tool/commands/common/BDXDiagnosticLogsServerDelegate.h b/examples/chip-tool/commands/common/BDXDiagnosticLogsServerDelegate.h new file mode 100644 index 00000000000000..5aea4b56632e79 --- /dev/null +++ b/examples/chip-tool/commands/common/BDXDiagnosticLogsServerDelegate.h @@ -0,0 +1,45 @@ +/* + * 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 +#include + +class BDXDiagnosticLogsServerDelegate : public chip::bdx::BDXTransferServerDelegate +{ +public: + static BDXDiagnosticLogsServerDelegate & GetInstance() { return sInstance; } + + void AddFileDesignator(chip::app::CommandSender * sender, const chip::CharSpan & fileDesignator); + void RemoveFileDesignator(chip::app::CommandSender * sender); + + /////////// BDXTransferServerDelegate Interface ///////// + CHIP_ERROR OnTransferBegin(chip::bdx::BDXTransferProxy * transfer) override; + CHIP_ERROR OnTransferEnd(chip::bdx::BDXTransferProxy * transfer, CHIP_ERROR error) override; + CHIP_ERROR OnTransferData(chip::bdx::BDXTransferProxy * transfer, const chip::ByteSpan & data) override; + +private: + BDXDiagnosticLogsServerDelegate() = default; + + std::map mFileDesignators; + static BDXDiagnosticLogsServerDelegate sInstance; +}; diff --git a/examples/chip-tool/commands/common/CHIPCommand.cpp b/examples/chip-tool/commands/common/CHIPCommand.cpp index 45fc2105183cdc..78abff12ada208 100644 --- a/examples/chip-tool/commands/common/CHIPCommand.cpp +++ b/examples/chip-tool/commands/common/CHIPCommand.cpp @@ -139,6 +139,14 @@ CHIP_ERROR CHIPCommand::MaybeSetUpStack() factoryInitParams.listenPort = port; ReturnLogErrorOnFailure(DeviceControllerFactory::GetInstance().Init(factoryInitParams)); + auto systemState = chip::Controller::DeviceControllerFactory::GetInstance().GetSystemState(); + VerifyOrReturnError(nullptr != systemState, CHIP_ERROR_INCORRECT_STATE); + + auto server = systemState->BDXTransferServer(); + VerifyOrReturnError(nullptr != server, CHIP_ERROR_INCORRECT_STATE); + + server->SetDelegate(&BDXDiagnosticLogsServerDelegate::GetInstance()); + ReturnErrorOnFailure(GetAttestationTrustStore(mPaaTrustStorePath.ValueOr(nullptr), &sTrustStore)); ReturnLogErrorOnFailure(sCheckInDelegate.Init(&sICDClientStorage)); diff --git a/examples/chip-tool/commands/common/CHIPCommand.h b/examples/chip-tool/commands/common/CHIPCommand.h index 2caf6fe9cc94ec..55817b5213e589 100644 --- a/examples/chip-tool/commands/common/CHIPCommand.h +++ b/examples/chip-tool/commands/common/CHIPCommand.h @@ -24,6 +24,8 @@ #include "Command.h" +#include "BDXDiagnosticLogsServerDelegate.h" + #include #include #include diff --git a/examples/chip-tool/commands/interactive/InteractiveCommands.cpp b/examples/chip-tool/commands/interactive/InteractiveCommands.cpp index d0f27acad60d34..284c145cfe0569 100644 --- a/examples/chip-tool/commands/interactive/InteractiveCommands.cpp +++ b/examples/chip-tool/commands/interactive/InteractiveCommands.cpp @@ -28,6 +28,7 @@ constexpr char kInteractiveModeStopCommand[] = "quit()"; constexpr char kCategoryError[] = "Error"; constexpr char kCategoryProgress[] = "Info"; constexpr char kCategoryDetail[] = "Debug"; +constexpr char kCategoryAutomation[] = "Automation"; namespace { @@ -146,6 +147,9 @@ struct InteractiveServerResult case chip::Logging::kLogCategory_Detail: messageType = kCategoryDetail; break; + case chip::Logging::kLogCategory_Automation: + messageType = kCategoryAutomation; + return; default: // This should not happen. chipDie(); diff --git a/examples/common/tracing/TraceDecoder.h b/examples/common/tracing/TraceDecoder.h index 4e70f5f12f1535..4630b556e329b7 100644 --- a/examples/common/tracing/TraceDecoder.h +++ b/examples/common/tracing/TraceDecoder.h @@ -40,7 +40,7 @@ class TraceDecoder : public TraceStream void AddField(const std::string & tag, const std::string & data) override { - char buffer[2048] = {}; + char buffer[4096] = {}; snprintf(buffer, sizeof(buffer), " %s\t %s", tag.c_str(), data.c_str()); CHIP_ERROR err = ReadString(buffer); diff --git a/examples/common/tracing/decoder/bdx/Decoder.cpp b/examples/common/tracing/decoder/bdx/Decoder.cpp index 7a6181fcf73bb3..268b4c78be7ef1 100644 --- a/examples/common/tracing/decoder/bdx/Decoder.cpp +++ b/examples/common/tracing/decoder/bdx/Decoder.cpp @@ -18,6 +18,9 @@ #include "Decoder.h" +#include "../logging/Log.h" + +#include #include namespace { @@ -34,14 +37,30 @@ constexpr char kBlockEOF[] = "Block End Of File"; constexpr char kBlockAck[] = "Block Ack"; constexpr char kBlockAckEOF[] = "Block Ack End Of File"; constexpr char kBlockQueryWithSkip[] = "Block Query With Skip"; + +constexpr char kDataHeader[] = "Data"; } // namespace -using MessageType = chip::bdx::MessageType; +using MessageType = chip::bdx::MessageType; +using RangeControlFlags = chip::bdx::RangeControlFlags; +using TransferControlFlags = chip::bdx::TransferControlFlags; namespace chip { namespace trace { namespace bdx { +using namespace logging; + +CHIP_ERROR DecodeTransferInit(System::PacketBufferHandle msgData); +CHIP_ERROR DecodeSendAccept(System::PacketBufferHandle msgData); +CHIP_ERROR DecodeReceiveAccept(System::PacketBufferHandle msgData); +CHIP_ERROR DecodeBlockCounter(System::PacketBufferHandle msgData); +CHIP_ERROR DecodeDataBlock(System::PacketBufferHandle msgData); +CHIP_ERROR DecodeBlockQueryWithSkip(System::PacketBufferHandle msgData); +void DecodeAndPrintTransferControl(const char * header, BitFlags & flags); +void DecodeAndPrintRangeControl(const char * header, BitFlags & flags); +void DecodeAndPrintMetadata(const ByteSpan & data); + const char * ToProtocolName() { return kProtocolName; @@ -71,39 +90,210 @@ const char * ToProtocolMessageTypeName(uint8_t protocolCode) return kBlockAckEOF; case to_underlying(MessageType::BlockQueryWithSkip): return kBlockQueryWithSkip; - default: - return kUnknown; } + + return kUnknown; } CHIP_ERROR LogAsProtocolMessage(uint8_t protocolCode, const uint8_t * data, size_t len) { - // TODO Implement messages decoding + auto msgData = System::PacketBufferHandle::NewWithData(data, len); switch (protocolCode) { case to_underlying(MessageType::SendInit): - return CHIP_ERROR_NOT_IMPLEMENTED; + return DecodeTransferInit(std::move(msgData)); case to_underlying(MessageType::SendAccept): - return CHIP_ERROR_NOT_IMPLEMENTED; + return DecodeSendAccept(std::move(msgData)); case to_underlying(MessageType::ReceiveInit): - return CHIP_ERROR_NOT_IMPLEMENTED; + return DecodeTransferInit(std::move(msgData)); case to_underlying(MessageType::ReceiveAccept): - return CHIP_ERROR_NOT_IMPLEMENTED; + return DecodeReceiveAccept(std::move(msgData)); case to_underlying(MessageType::BlockQuery): - return CHIP_ERROR_NOT_IMPLEMENTED; + return DecodeBlockCounter(std::move(msgData)); case to_underlying(MessageType::Block): - return CHIP_ERROR_NOT_IMPLEMENTED; + return DecodeDataBlock(std::move(msgData)); case to_underlying(MessageType::BlockEOF): - return CHIP_ERROR_NOT_IMPLEMENTED; + return DecodeDataBlock(std::move(msgData)); case to_underlying(MessageType::BlockAck): - return CHIP_ERROR_NOT_IMPLEMENTED; + return DecodeBlockCounter(std::move(msgData)); case to_underlying(MessageType::BlockAckEOF): - return CHIP_ERROR_NOT_IMPLEMENTED; + return DecodeBlockCounter(std::move(msgData)); case to_underlying(MessageType::BlockQueryWithSkip): - return CHIP_ERROR_NOT_IMPLEMENTED; - default: - return CHIP_ERROR_NOT_IMPLEMENTED; + return DecodeBlockQueryWithSkip(std::move(msgData)); + } + + return CHIP_ERROR_NOT_IMPLEMENTED; +} + +CHIP_ERROR DecodeTransferInit(System::PacketBufferHandle msgData) +{ + auto scopedIndent = ScopedLogIndentWithSize(kDataHeader, msgData->DataLength()); + + chip::bdx::TransferInit msg; + ReturnErrorOnFailure(msg.Parse(msgData.Retain())); + + auto ptc = msg.TransferCtlOptions; + auto rc = msg.mRangeCtlFlags; + auto pmbs = msg.MaxBlockSize; + auto startofs = msg.StartOffset; + auto len = msg.MaxLength; + auto fdl = msg.FileDesLength; + auto fd = CharSpan(Uint8::to_const_char(msg.FileDesignator), msg.FileDesLength); + auto mdata = ByteSpan(msg.Metadata, msg.MetadataLength); + + DecodeAndPrintTransferControl("Proposed Transfer Control", ptc); + DecodeAndPrintRangeControl("Range Control", rc); + Log("Proposed Max Block Size", pmbs); + + if (rc.Has(RangeControlFlags::kStartOffset)) + { + LogAsHex("Start Offset", startofs); + } + + if (rc.Has(RangeControlFlags::kDefLen)) + { + LogAsHex("Proposed Max Length", len); + } + + Log("File Designator Length", fdl); + Log("File Designator", fd); + DecodeAndPrintMetadata(mdata); + + return CHIP_NO_ERROR; +} + +CHIP_ERROR DecodeSendAccept(System::PacketBufferHandle msgData) +{ + auto scopedIndent = ScopedLogIndentWithSize(kDataHeader, msgData->DataLength()); + + chip::bdx::SendAccept msg; + ReturnErrorOnFailure(msg.Parse(msgData.Retain())); + + auto tc = msg.TransferCtlFlags; + auto mbs = msg.MaxBlockSize; + auto mdata = ByteSpan(msg.Metadata, msg.MetadataLength); + + DecodeAndPrintTransferControl("Transfer Control", tc); + Log("Max Block Size", mbs); + DecodeAndPrintMetadata(mdata); + + return CHIP_NO_ERROR; +} + +CHIP_ERROR DecodeReceiveAccept(System::PacketBufferHandle msgData) +{ + auto scopedIndent = ScopedLogIndentWithSize(kDataHeader, msgData->DataLength()); + + chip::bdx::ReceiveAccept msg; + ReturnErrorOnFailure(msg.Parse(msgData.Retain())); + + auto tc = msg.TransferCtlFlags; + auto rc = msg.mRangeCtlFlags; + auto mbs = msg.MaxBlockSize; + auto len = msg.Length; + auto mdata = ByteSpan(msg.Metadata, msg.MetadataLength); + + DecodeAndPrintTransferControl("Transfer Control", tc); + DecodeAndPrintRangeControl("Range Control", rc); + Log("Max Block Size", mbs); + + if (rc.Has(RangeControlFlags::kDefLen)) + { + LogAsHex("Length", len); + } + + DecodeAndPrintMetadata(mdata); + + return CHIP_NO_ERROR; +} + +CHIP_ERROR DecodeBlockCounter(System::PacketBufferHandle msgData) +{ + auto scopedIndent = ScopedLogIndent(kDataHeader); + + chip::bdx::CounterMessage msg; + ReturnErrorOnFailure(msg.Parse(msgData.Retain())); + + Log("BlockCounter", msg.BlockCounter); + + return CHIP_NO_ERROR; +} + +CHIP_ERROR DecodeDataBlock(System::PacketBufferHandle msgData) +{ + auto scopedIndent = ScopedLogIndentWithSize(kDataHeader, msgData->DataLength()); + + chip::bdx::DataBlock msg; + ReturnErrorOnFailure(msg.Parse(msgData.Retain())); + + Log("BlockCounter", msg.BlockCounter); + + auto data = ByteSpan(msg.Data, msg.DataLength); + Log("Data", data); + + return CHIP_NO_ERROR; +} + +CHIP_ERROR DecodeBlockQueryWithSkip(System::PacketBufferHandle msgData) +{ + auto scopedIndent = ScopedLogIndent(kDataHeader); + + chip::bdx::BlockQueryWithSkip msg; + ReturnErrorOnFailure(msg.Parse(msgData.Retain())); + + Log("BlockCounter", msg.BlockCounter); + LogAsHex("BytesToSkip", msg.BytesToSkip); + + return CHIP_NO_ERROR; +} + +void DecodeAndPrintTransferControl(const char * header, BitFlags & flags) +{ + auto scopedIndent = ScopedLogIndentWithFlags(header, flags.Raw()); + + if (flags.Has(TransferControlFlags::kSenderDrive)) + { + Log("SenderDrive"); + } + + if (flags.Has(TransferControlFlags::kReceiverDrive)) + { + Log("ReceivedDrive"); + } + + if (flags.Has(TransferControlFlags::kAsync)) + { + Log("Async"); + } +} + +void DecodeAndPrintRangeControl(const char * header, BitFlags & flags) +{ + auto scopedIndent = ScopedLogIndentWithFlags(header, flags.Raw()); + + if (flags.Has(chip::bdx::RangeControlFlags::kDefLen)) + { + Log("DefLen"); + } + + if (flags.Has(RangeControlFlags::kStartOffset)) + { + Log("StartOffset"); + } + + if (flags.Has(RangeControlFlags::kWiderange)) + { + Log("Widerange"); + } +} + +void DecodeAndPrintMetadata(const ByteSpan & data) +{ + if (data.size()) + { + // TODO Make metadata decoding prettier. + Log("HasMetadata"); } } diff --git a/examples/common/tracing/decoder/logging/Log.cpp b/examples/common/tracing/decoder/logging/Log.cpp index 4fa5d9dfeb8bfc..b0645bb3980d77 100644 --- a/examples/common/tracing/decoder/logging/Log.cpp +++ b/examples/common/tracing/decoder/logging/Log.cpp @@ -34,7 +34,7 @@ uint8_t gIndentLevel = 0; void ENFORCE_FORMAT(1, 2) LogFormatted(const char * format, ...) { - char buffer[CHIP_CONFIG_LOG_MESSAGE_MAX_SIZE] = {}; + char buffer[CHIP_CONFIG_LOG_MESSAGE_MAX_SIZE + 1] = {}; int indentation = gIndentLevel * kSpacePerIndent; snprintf(buffer, sizeof(buffer), "%*s", indentation, ""); @@ -76,9 +76,20 @@ void Log(const char * name, ByteSpan & data) return; } - char buffer[CHIP_CONFIG_LOG_MESSAGE_MAX_SIZE]; + // If the size of the data is larger than half of the maximum size for a log message (minus 1 for null-termination), + // reduce the size of the data to fit within this limit. + // The limit is half the size of the message because we will be logging a hex representation of that data, at 2 chars per byte. + if (data.size() > (CHIP_CONFIG_LOG_MESSAGE_MAX_SIZE / 2) - 1) + { + data.reduce_size((CHIP_CONFIG_LOG_MESSAGE_MAX_SIZE / 2) - 1); + } + + char buffer[CHIP_CONFIG_LOG_MESSAGE_MAX_SIZE] = { 0 }; chip::MutableCharSpan destination(buffer); + // Check if the size of the data can be cast to uint16_t. + // If yes, log the name and size of the data followed by its hexadecimal string representation. + // If no, log the name and a marker indicating the size exceeds UINT16_MAX, followed by the data's hexadecimal string. CanCastTo(data.size()) ? LogFormatted("%s (%u) = %s", name, static_cast(data.size()), ToHexString(data, destination)) : LogFormatted("%s (>UINT16_MAX) = %s", name, ToHexString(data, destination)); @@ -116,6 +127,11 @@ void Log(const char * name) LogFormatted("%s", name); } +void LogAsHex(const char * name, uint8_t value) +{ + LogFormatted("%s = 0x%02x", name, value); +} + void LogAsHex(const char * name, uint16_t value) { LogFormatted("%s = 0x%04x", name, value); diff --git a/examples/common/tracing/decoder/logging/Log.h b/examples/common/tracing/decoder/logging/Log.h index b2ff6915eb4604..39f656546cc4c2 100644 --- a/examples/common/tracing/decoder/logging/Log.h +++ b/examples/common/tracing/decoder/logging/Log.h @@ -60,6 +60,8 @@ void Log(const char * name); void Log(const char * name, const char * value); +void LogAsHex(const char * name, uint8_t value); + void LogAsHex(const char * name, uint16_t value); void LogAsHex(const char * name, uint64_t value); diff --git a/examples/tv-casting-app/tv-casting-common/BUILD.gn b/examples/tv-casting-app/tv-casting-common/BUILD.gn index e8b62bce23969f..38b983485c1800 100644 --- a/examples/tv-casting-app/tv-casting-common/BUILD.gn +++ b/examples/tv-casting-app/tv-casting-common/BUILD.gn @@ -39,6 +39,7 @@ chip_data_model("tv-casting-common") { sources = [ "${chip_root}/examples/chip-tool/commands/clusters/ModelCommand.h", + "${chip_root}/examples/chip-tool/commands/common/BDXDiagnosticLogsServerDelegate.cpp", "${chip_root}/examples/chip-tool/commands/common/CHIPCommand.h", "${chip_root}/examples/chip-tool/commands/common/Command.cpp", "${chip_root}/examples/chip-tool/commands/common/Command.h", diff --git a/scripts/py_matter_yamltests/matter_yamltests/pseudo_clusters/clusters/accessory_server_bridge.py b/scripts/py_matter_yamltests/matter_yamltests/pseudo_clusters/clusters/accessory_server_bridge.py index dce65187763a81..d3e3403fef4954 100644 --- a/scripts/py_matter_yamltests/matter_yamltests/pseudo_clusters/clusters/accessory_server_bridge.py +++ b/scripts/py_matter_yamltests/matter_yamltests/pseudo_clusters/clusters/accessory_server_bridge.py @@ -67,6 +67,15 @@ def _get_start_options(request): elif name == 'otaDownloadPath': options.append('--otaDownloadPath') options.append(str(value)) + elif name == 'endUserSupportLogPath': + options.append('--end_user_support_log') + options.append(str(value)) + elif name == 'networkDiagnosticsLogPath': + options.append('--network_diagnostics_log') + options.append(str(value)) + elif name == 'crashLogPath': + options.append('--crash_log') + options.append(str(value)) elif name == 'registerKey': pass else: @@ -114,7 +123,8 @@ def createOtaImage(request): rawImageContent = _get_option(request, 'rawImageContent') with xmlrpc.client.ServerProxy(_make_url(), allow_none=True) as proxy: - proxy.createOtaImage(otaImageFilePath, rawImageFilePath, rawImageContent) + proxy.createOtaImage( + otaImageFilePath, rawImageFilePath, rawImageContent) def compareFiles(request): file1 = _get_option(request, 'file1') @@ -122,3 +132,16 @@ def compareFiles(request): with xmlrpc.client.ServerProxy(_make_url(), allow_none=True) as proxy: proxy.compareFiles(file1, file2) + + def createFile(request): + filePath = _get_option(request, 'filePath') + fileContent = _get_option(request, 'fileContent') + + with xmlrpc.client.ServerProxy(_make_url(), allow_none=True) as proxy: + proxy.createFile(filePath, fileContent) + + def deleteFile(request): + filePath = _get_option(request, 'filePath') + + with xmlrpc.client.ServerProxy(_make_url(), allow_none=True) as proxy: + proxy.deleteFile(filePath) diff --git a/scripts/py_matter_yamltests/matter_yamltests/pseudo_clusters/clusters/system_commands.py b/scripts/py_matter_yamltests/matter_yamltests/pseudo_clusters/clusters/system_commands.py index 4f082e710b22ed..77028c39d72a9a 100644 --- a/scripts/py_matter_yamltests/matter_yamltests/pseudo_clusters/clusters/system_commands.py +++ b/scripts/py_matter_yamltests/matter_yamltests/pseudo_clusters/clusters/system_commands.py @@ -30,6 +30,9 @@ + + + @@ -55,6 +58,15 @@ + + + + + + + + + ''' @@ -81,3 +93,9 @@ async def CreateOtaImage(self, request): async def CompareFiles(self, request): AccessoryServerBridge.compareFiles(request) + + async def CreateFile(self, request): + AccessoryServerBridge.createFile(request) + + async def DeleteFile(self, request): + AccessoryServerBridge.deleteFile(request) diff --git a/scripts/tests/chiptest/__init__.py b/scripts/tests/chiptest/__init__.py index ad2cc6a4782df0..378e3df2e02669 100644 --- a/scripts/tests/chiptest/__init__.py +++ b/scripts/tests/chiptest/__init__.py @@ -203,6 +203,7 @@ def _GetChipReplUnsupportedTests() -> Set[str]: "Test_TC_ACL_2_6.yaml", # chip-repl does not support LastReceivedEventNumber : https://github.com/project-chip/connectedhomeip/issues/28884 "Test_TC_RVCCLEANM_3_3.yaml", # chip-repl does not support EqualityCommands pseudo-cluster "Test_TC_BINFO_2_1.yaml", # chip-repl does not support EqualityCommands pseudo-cluster + "TestDiagnosticLogs.yaml", # chip-repl does not implement a BDXTransferServerDelegate } diff --git a/scripts/tests/chiptest/accessories.py b/scripts/tests/chiptest/accessories.py index d8ca1c02945cc9..60e2136209ec19 100644 --- a/scripts/tests/chiptest/accessories.py +++ b/scripts/tests/chiptest/accessories.py @@ -126,6 +126,16 @@ def compareFiles(self, file1, file2): raise Exception('Files %s and %s do not match' % (file1, file2)) return True + def createFile(self, filePath, fileContent): + with open(filePath, 'w') as rawFile: + rawFile.write(fileContent) + return True + + def deleteFile(self, filePath): + if os.path.exists(filePath): + os.remove(filePath) + return True + def __startXMLRPCServer(self): self.server = SimpleXMLRPCServer((IP, PORT)) @@ -136,6 +146,8 @@ def __startXMLRPCServer(self): self.server.register_function(self.waitForMessage, 'waitForMessage') self.server.register_function(self.compareFiles, 'compareFiles') self.server.register_function(self.createOtaImage, 'createOtaImage') + self.server.register_function(self.createFile, 'createFile') + self.server.register_function(self.deleteFile, 'deleteFile') self.server_thread = threading.Thread(target=self.server.serve_forever) self.server_thread.start() diff --git a/scripts/tests/chiptest/runner.py b/scripts/tests/chiptest/runner.py index fdba938224d45e..b8ede3f79b147b 100644 --- a/scripts/tests/chiptest/runner.py +++ b/scripts/tests/chiptest/runner.py @@ -52,7 +52,7 @@ def __init__(self, level, capture_delegate=None, name=None): def CapturedLogContains(self, txt: str, index=0): for i, line in enumerate(self.captured_logs[index:]): if txt in line: - return True, i + return True, index + i return False, len(self.captured_logs) def FindLastMatchingLine(self, matcher): diff --git a/scripts/tests/chiptest/test_definition.py b/scripts/tests/chiptest/test_definition.py index bae92baff5d001..89281c114365a8 100644 --- a/scripts/tests/chiptest/test_definition.py +++ b/scripts/tests/chiptest/test_definition.py @@ -133,6 +133,9 @@ def __waitFor(self, waitForString, server_process, outpipe): start_time = time.monotonic() ready, self.lastLogIndex = outpipe.CapturedLogContains( waitForString, self.lastLogIndex) + if ready: + self.lastLogIndex += 1 + while not ready: if server_process.poll() is not None: died_str = ('Server died while waiting for %s, returncode %d' % @@ -144,6 +147,8 @@ def __waitFor(self, waitForString, server_process, outpipe): time.sleep(0.1) ready, self.lastLogIndex = outpipe.CapturedLogContains( waitForString, self.lastLogIndex) + if ready: + self.lastLogIndex += 1 logging.debug('Success waiting for: %s' % waitForString) diff --git a/src/app/chip_data_model.gni b/src/app/chip_data_model.gni index 31551b55256d13..7f8f477c19a2cd 100644 --- a/src/app/chip_data_model.gni +++ b/src/app/chip_data_model.gni @@ -344,6 +344,14 @@ template("chip_data_model") { "${_app_root}/clusters/${cluster}/EnergyEvseTestEventTriggerDelegate.cpp", "${_app_root}/clusters/${cluster}/EnergyEvseTestEventTriggerDelegate.h", ] + } else if (cluster == "diagnostic-logs-server") { + sources += [ + "${_app_root}/clusters/${cluster}/${cluster}.cpp", + "${_app_root}/clusters/${cluster}/${cluster}.h", + "${_app_root}/clusters/${cluster}/BDXDiagnosticLogsProvider.cpp", + "${_app_root}/clusters/${cluster}/BDXDiagnosticLogsProvider.h", + "${_app_root}/clusters/${cluster}/DiagnosticLogsProviderDelegate.h", + ] } else { sources += [ "${_app_root}/clusters/${cluster}/${cluster}.cpp" ] } diff --git a/src/app/clusters/diagnostic-logs-server/BDXDiagnosticLogsProvider.cpp b/src/app/clusters/diagnostic-logs-server/BDXDiagnosticLogsProvider.cpp new file mode 100644 index 00000000000000..6119652990679f --- /dev/null +++ b/src/app/clusters/diagnostic-logs-server/BDXDiagnosticLogsProvider.cpp @@ -0,0 +1,307 @@ +/* + * + * 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 +#include "BDXDiagnosticLogsProvider.h" +#include +#include +#include + +using namespace chip::bdx; + +namespace chip { +namespace app { +namespace Clusters { +namespace DiagnosticLogs { + +// BDX transfer session polling interval. +constexpr System::Clock::Timeout kBdxPollIntervalMs = System::Clock::Milliseconds32(50); + +// Timeout for the BDX transfer session +constexpr System::Clock::Timeout kBdxTimeout = System::Clock::Seconds16(5 * 60); + +constexpr uint16_t kBdxMaxBlockSize = 1024; + +CHIP_ERROR BDXDiagnosticLogsProvider::InitializeTransfer(CommandHandler * commandObj, const ConcreteCommandPath & path, + DiagnosticLogsProviderDelegate * delegate, IntentEnum intent, + CharSpan fileDesignator) +{ + auto exchangeCtx = commandObj->GetExchangeContext(); + VerifyOrReturnError(nullptr != exchangeCtx, CHIP_ERROR_INVALID_ARGUMENT); + + auto exchangeMgr = exchangeCtx->GetExchangeMgr(); + VerifyOrReturnError(nullptr != exchangeMgr, CHIP_ERROR_INVALID_ARGUMENT); + + auto sessionHandle = exchangeCtx->GetSessionHandle(); + VerifyOrReturnError(sessionHandle->IsSecureSession(), CHIP_ERROR_INVALID_ARGUMENT); + + ScopedNodeId scopedPeerNodeId = sessionHandle->AsSecureSession()->GetPeer(); + auto fabricIndex = scopedPeerNodeId.GetFabricIndex(); + VerifyOrReturnError(kUndefinedFabricIndex != fabricIndex, CHIP_ERROR_INVALID_ARGUMENT); + + auto peerNodeId = scopedPeerNodeId.GetNodeId(); + VerifyOrReturnError(kUndefinedNodeId != peerNodeId, CHIP_ERROR_INVALID_ARGUMENT); + + LogSessionHandle logSessionHandle; + Optional timeStamp; + Optional timeSinceBoot; + VerifyOrReturnError(delegate != nullptr, CHIP_ERROR_INVALID_ARGUMENT); + ReturnErrorOnFailure(delegate->StartLogCollection(intent, logSessionHandle, timeStamp, timeSinceBoot)); + + // Create a new exchange context to use for the BDX transfer session + // and send the SendInit message over the exchange. + auto transferExchangeCtx = exchangeMgr->NewContext(sessionHandle, this); + VerifyOrReturnError(nullptr != transferExchangeCtx, CHIP_ERROR_NO_MEMORY); + + TransferSession::TransferInitData initOptions; + initOptions.TransferCtlFlags = TransferControlFlags::kSenderDrive; + initOptions.MaxBlockSize = kBdxMaxBlockSize; + initOptions.FileDesLength = static_cast(fileDesignator.size()); + initOptions.FileDesignator = Uint8::from_const_char(fileDesignator.data()); + + CHIP_ERROR err = Initiator::InitiateTransfer(&DeviceLayer::SystemLayer(), TransferRole::kSender, initOptions, kBdxTimeout, + kBdxPollIntervalMs); + if (CHIP_NO_ERROR != err) + { + LogErrorOnFailure(err); + transferExchangeCtx->Close(); + return err; + } + + mBDXTransferExchangeCtx = transferExchangeCtx; + mDelegate = delegate; + mFabricIndex.SetValue(fabricIndex); + mPeerNodeId.SetValue(peerNodeId); + mIsAcceptReceived = false; + mLogSessionHandle = logSessionHandle; + mTimeStamp = timeStamp; + mTimeSinceBoot = timeSinceBoot; + mAsyncCommandHandle = CommandHandler::Handle(commandObj); + mRequestPath = path; + mInitialized = true; + + return CHIP_NO_ERROR; +} + +void BDXDiagnosticLogsProvider::HandleTransferSessionOutput(TransferSession::OutputEvent & event) +{ + if (event.EventType != TransferSession::OutputEventType::kNone) + { + ChipLogDetail(BDX, "OutputEvent type: %s", event.ToString(event.EventType)); + } + + switch (event.EventType) + { + case TransferSession::OutputEventType::kMsgToSend: + OnMsgToSend(event); + break; + case TransferSession::OutputEventType::kAcceptReceived: + OnAcceptReceived(); + // Upon acceptance of the transfer, the OnAckReceived method initiates the process of sending logs. + OnAckReceived(); + break; + case TransferSession::OutputEventType::kAckReceived: + OnAckReceived(); + break; + case TransferSession::OutputEventType::kAckEOFReceived: + OnAckEOFReceived(); + break; + case TransferSession::OutputEventType::kStatusReceived: + OnStatusReceived(event); + break; + case TransferSession::OutputEventType::kInternalError: + OnInternalError(); + break; + case TransferSession::OutputEventType::kTransferTimeout: + OnTimeout(); + break; + case TransferSession::OutputEventType::kNone: + case TransferSession::OutputEventType::kInitReceived: + break; + case TransferSession::OutputEventType::kBlockReceived: + case TransferSession::OutputEventType::kQueryReceived: + case TransferSession::OutputEventType::kQueryWithSkipReceived: + // TransferSession should prevent this case from happening. + ChipLogError(BDX, "Unsupported event type"); + break; + } +} + +void BDXDiagnosticLogsProvider::OnMsgToSend(TransferSession::OutputEvent & event) +{ + VerifyOrReturn(mBDXTransferExchangeCtx != nullptr); + + auto & msgTypeData = event.msgTypeData; + bool isStatusReport = msgTypeData.HasMessageType(Protocols::SecureChannel::MsgType::StatusReport); + + // All messages sent from the Sender expect a response, except for a StatusReport which would indicate an error and the + // end of the transfer. + Messaging::SendFlags sendFlags; + if (!isStatusReport) + { + sendFlags.Set(Messaging::SendMessageFlags::kExpectResponse); + } + + auto err = + mBDXTransferExchangeCtx->SendMessage(msgTypeData.ProtocolId, msgTypeData.MessageType, std::move(event.MsgData), sendFlags); + + VerifyOrDo(CHIP_NO_ERROR == err, Reset()); +} + +void BDXDiagnosticLogsProvider::OnAcceptReceived() +{ + mIsAcceptReceived = true; + + // On reception of a BDX SendAccept message the Node SHALL send a RetrieveLogsResponse command with a Status field set to + // Success and proceed with the log transfer over BDX. + SendCommandResponse(StatusEnum::kSuccess); +} + +void BDXDiagnosticLogsProvider::OnAckReceived() +{ + uint16_t blockSize = mTransfer.GetTransferBlockSize(); + + auto blockBuf = System::PacketBufferHandle::New(blockSize); + VerifyOrReturn(!blockBuf.IsNull(), mTransfer.AbortTransfer(GetBdxStatusCodeFromChipError(CHIP_ERROR_NO_MEMORY))); + + auto buffer = MutableByteSpan(blockBuf->Start(), blockSize); + bool isEndOfLog = false; + + // Get the log next chunk and see if it fits i.e. if is end of log is reported + auto err = mDelegate->CollectLog(mLogSessionHandle, buffer, isEndOfLog); + VerifyOrReturn(CHIP_NO_ERROR == err, mTransfer.AbortTransfer(GetBdxStatusCodeFromChipError(err))); + + // If the buffer has empty space, end the log collection session. + if (isEndOfLog) + { + mDelegate->EndLogCollection(mLogSessionHandle); + mLogSessionHandle = kInvalidLogSessionHandle; + } + + // Prepare the BDX block to send to the requestor + TransferSession::BlockData blockData; + blockData.Data = blockBuf->Start(); + blockData.Length = static_cast(buffer.size()); + blockData.IsEof = isEndOfLog; + + err = mTransfer.PrepareBlock(blockData); + if (err != CHIP_NO_ERROR) + { + ChipLogError(BDX, "PrepareBlock failed: %" CHIP_ERROR_FORMAT, err.Format()); + mTransfer.AbortTransfer(GetBdxStatusCodeFromChipError(err)); + } +} + +void BDXDiagnosticLogsProvider::OnAckEOFReceived() +{ + ChipLogProgress(BDX, "Diagnostic logs transfer: Success"); + + Reset(); +} + +void BDXDiagnosticLogsProvider::OnStatusReceived(TransferSession::OutputEvent & event) +{ + ChipLogError(BDX, "Diagnostic logs transfer: StatusReport Error %x", to_underlying(event.statusData.statusCode)); + + // If a failure StatusReport is received in response to the SendInit message, the Node SHALL send a RetrieveLogsResponse command + // with a Status of Denied. + VerifyOrDo(mIsAcceptReceived, SendCommandResponse(StatusEnum::kDenied)); + Reset(); +} + +void BDXDiagnosticLogsProvider::OnInternalError() +{ + ChipLogError(BDX, "Internal Error"); + VerifyOrDo(mIsAcceptReceived, SendCommandResponse(StatusEnum::kDenied)); + Reset(); +} + +void BDXDiagnosticLogsProvider::OnTimeout() +{ + ChipLogError(BDX, "Timeout"); + VerifyOrDo(mIsAcceptReceived, SendCommandResponse(StatusEnum::kDenied)); + Reset(); +} + +void BDXDiagnosticLogsProvider::SendCommandResponse(StatusEnum status) +{ + auto commandHandleRef = std::move(mAsyncCommandHandle); + auto commandHandle = commandHandleRef.Get(); + + VerifyOrReturn(nullptr != commandHandle, ChipLogError(Zcl, "SendCommandResponse - commandHandler is null")); + + Commands::RetrieveLogsResponse::Type response; + response.status = status; + if (status == StatusEnum::kSuccess) + { + if (mTimeStamp.HasValue()) + { + response.UTCTimeStamp = mTimeStamp; + } + + if (mTimeSinceBoot.HasValue()) + { + response.timeSinceBoot = mTimeSinceBoot; + } + } + commandHandle->AddResponse(mRequestPath, response); +} + +void BDXDiagnosticLogsProvider::Reset() +{ + assertChipStackLockedByCurrentThread(); + + Initiator::ResetTransfer(); + mExchangeCtx = nullptr; + + if (mBDXTransferExchangeCtx != nullptr) + { + mBDXTransferExchangeCtx->Close(); + mBDXTransferExchangeCtx = nullptr; + } + + if (mDelegate != nullptr) + { + mDelegate->EndLogCollection(mLogSessionHandle); + mDelegate = nullptr; + } + + mFabricIndex.ClearValue(); + mPeerNodeId.ClearValue(); + mIsAcceptReceived = false; + mLogSessionHandle = kInvalidLogSessionHandle; + mTimeStamp.ClearValue(); + mTimeSinceBoot.ClearValue(); + mAsyncCommandHandle = nullptr; + mRequestPath = ConcreteCommandPath(kInvalidEndpointId, kInvalidClusterId, kInvalidCommandId); + mInitialized = false; +} + +void BDXDiagnosticLogsProvider::OnExchangeClosing(Messaging::ExchangeContext * ec) +{ + mBDXTransferExchangeCtx = nullptr; +} + +} // namespace DiagnosticLogs +} // namespace Clusters +} // namespace app +} // namespace chip + +#endif diff --git a/src/app/clusters/diagnostic-logs-server/BDXDiagnosticLogsProvider.h b/src/app/clusters/diagnostic-logs-server/BDXDiagnosticLogsProvider.h new file mode 100644 index 00000000000000..6122998eae986c --- /dev/null +++ b/src/app/clusters/diagnostic-logs-server/BDXDiagnosticLogsProvider.h @@ -0,0 +1,99 @@ +/* + * + * 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 + +#if CHIP_CONFIG_ENABLE_BDX_LOG_TRANSFER + +#include +#include +#include + +namespace chip { +namespace app { +namespace Clusters { +namespace DiagnosticLogs { + +class BDXDiagnosticLogsProvider : public bdx::Initiator +{ +public: + BDXDiagnosticLogsProvider(){}; + ~BDXDiagnosticLogsProvider(){}; + + /** + * Intializes the BDX transfer session by creating a new exchange context for the transfer session. + * It starts the BDX transfer session by calling InitiateTransfer which sends the SendInit BDX message + * to the log requestor. + * + * @param commandObj The command handler object from the RetrieveLogsRequest command + * @param path The command path from the RetrieveLogsRequest command + * @param delegate The log provider delegate that will provide the log chunks + * @param intent The log type requested + * @param fileDesignator The file designator to use for the BDX transfer + */ + CHIP_ERROR InitializeTransfer(CommandHandler * commandObj, const ConcreteCommandPath & path, + DiagnosticLogsProviderDelegate * delegate, IntentEnum intent, CharSpan fileDesignator); + + /** + * This method handles BDX messages and other TransferSession events. + * + * @param[in] event An OutputEvent that contains output from the TransferSession object. + */ + void HandleTransferSessionOutput(bdx::TransferSession::OutputEvent & event) override; + + bool IsBusy() const { return mInitialized; } + + void OnExchangeClosing(Messaging::ExchangeContext * ec) override; + +private: + void OnMsgToSend(bdx::TransferSession::OutputEvent & event); + void OnAcceptReceived(); + void OnAckReceived(); + void OnAckEOFReceived(); + void OnStatusReceived(bdx::TransferSession::OutputEvent & event); + void OnInternalError(); + void OnTimeout(); + + void SendCommandResponse(StatusEnum status); + + /** + * This method is called to reset state. It resets the transfer, cleans up the + * exchange and ends log collection. + */ + void Reset(); + + Messaging::ExchangeContext * mBDXTransferExchangeCtx; + DiagnosticLogsProviderDelegate * mDelegate; + Optional mFabricIndex; + Optional mPeerNodeId; + bool mIsAcceptReceived = false; + LogSessionHandle mLogSessionHandle = kInvalidLogSessionHandle; + Optional mTimeStamp; + Optional mTimeSinceBoot; + CommandHandler::Handle mAsyncCommandHandle; + ConcreteCommandPath mRequestPath = ConcreteCommandPath(kInvalidEndpointId, kInvalidClusterId, kInvalidCommandId); + bool mInitialized = false; +}; + +} // namespace DiagnosticLogs +} // namespace Clusters +} // namespace app +} // namespace chip +#endif diff --git a/src/app/clusters/diagnostic-logs-server/DiagnosticLogsProviderDelegate.h b/src/app/clusters/diagnostic-logs-server/DiagnosticLogsProviderDelegate.h new file mode 100644 index 00000000000000..dc511fdacadeaa --- /dev/null +++ b/src/app/clusters/diagnostic-logs-server/DiagnosticLogsProviderDelegate.h @@ -0,0 +1,104 @@ +/* + * + * 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 + +namespace chip { +namespace app { +namespace Clusters { +namespace DiagnosticLogs { + +typedef uint16_t LogSessionHandle; + +// The value UINT16_MAX will be used as an invalid log session handle and must not be used as a valid value for LogSessionHandle +constexpr LogSessionHandle kInvalidLogSessionHandle = UINT16_MAX; + +/** @brief + * Defines methods for implementing application-specific logic for getting the log data from the diagnostic logs provider + * DiagnosticLogsProviderDelegate. + */ +class DiagnosticLogsProviderDelegate +{ +public: + DiagnosticLogsProviderDelegate() = default; + + virtual ~DiagnosticLogsProviderDelegate() = default; + + /** + * Called to start log collection for the log type passed in. + * + * @param[in] intent The type of log for which the start of log collection is requested. + * @param[out] outHandle The unique log session handle that identifies the log collection session that has been started. + * @param[out] outTimeStamp An optional value with the timestamp of the oldest log entry + * @param[out] outTimeSinceBoot An optional value with the time since boot of the oldest log entry + * Returns kInvalidLogSessionHandle if there are no logs of the intent. + */ + virtual CHIP_ERROR StartLogCollection(IntentEnum intent, LogSessionHandle & outHandle, Optional & outTimeStamp, + Optional & outTimeSinceBoot) = 0; + + /** + * Called to end log collection for the log session identified by sessionHandle. + * This must be called if StartLogCollection happens successfully and a valid sessionHandle has been + * returned from StartLogCollection. + * + * @param[in] sessionHandle The unique handle for this log session returned from a call to StartLogCollection. + * + */ + virtual CHIP_ERROR EndLogCollection(LogSessionHandle sessionHandle) = 0; + + /** + * Called to get the next chunk for the log session identified by sessionHandle. + * The outBuffer is resized to the actual size of data that was successfully read from the file. + * Should return CHIP_NO_ERROR if we were able to read successfully from the file into the buffer, otherwise + * an appropriate error code is returned. + * + * @param[in] sessionHandle 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[out] outIsEndOfLog Set to true if there is no more log data otherwise set to false. + */ + virtual CHIP_ERROR CollectLog(LogSessionHandle sessionHandle, MutableByteSpan & outBuffer, bool & outIsEndOfLog) = 0; + + /** + * Called to get the file size for the log type passed in. + * + * @param[in] intent The type of log for which the file size is requested. + * + * Returns the size of the logs for the target intent. Retuns 0 if there are no logs for the given intent, otherwise returns the + * size in bytes. + */ + virtual size_t GetSizeForIntent(IntentEnum intent) = 0; + + /** + * Called to get the newest diagnostic log entries for the target intent. + * + * @param[in] intent The intent for which the log size is requested. + * @param[out] outBuffer The buffer thats passed in by the caller to write to. + * @param[out] outTimeStamp An optional value with the timestamp of the oldest log entry + * @param[out] outTimeSinceBoot An optional value with the time since boot of the oldest log entry + * + */ + virtual CHIP_ERROR GetLogForIntent(IntentEnum intent, MutableByteSpan & outBuffer, Optional & outTimeStamp, + Optional & outTimeSinceBoot) = 0; +}; + +} // namespace DiagnosticLogs +} // namespace Clusters +} // namespace app +} // namespace chip diff --git a/src/app/clusters/diagnostic-logs-server/diagnostic-logs-server.cpp b/src/app/clusters/diagnostic-logs-server/diagnostic-logs-server.cpp index 2965beff147d3b..5d7de3b077a982 100644 --- a/src/app/clusters/diagnostic-logs-server/diagnostic-logs-server.cpp +++ b/src/app/clusters/diagnostic-logs-server/diagnostic-logs-server.cpp @@ -15,104 +15,176 @@ * limitations under the License. */ -#include -#include -#include -#include #include -#include + #include -#include +#include #include +#include + +#include "BDXDiagnosticLogsProvider.h" + +#ifdef EMBER_AF_DIAGNOSTIC_LOGS_CLUSTER_SERVER_ENDPOINT_COUNT +static constexpr size_t kDiagnosticLogsDiagnosticLogsProviderDelegateTableSize = + EMBER_AF_DIAGNOSTIC_LOGS_CLUSTER_SERVER_ENDPOINT_COUNT + CHIP_DEVICE_CONFIG_DYNAMIC_ENDPOINT_COUNT; +static_assert(kDiagnosticLogsDiagnosticLogsProviderDelegateTableSize < kEmberInvalidEndpointIndex, + "DiagnosticLogs: log provider delegate table size error"); + +using namespace chip::app::Clusters::DiagnosticLogs; +using chip::Protocols::InteractionModel::Status; + +using chip::bdx::DiagnosticLogs::kMaxFileDesignatorLen; +using chip::bdx::DiagnosticLogs::kMaxLogContentSize; + +namespace chip { +namespace app { +namespace Clusters { +namespace DiagnosticLogs { + +namespace { + +DiagnosticLogsProviderDelegate * gDiagnosticLogsProviderDelegateTable[kDiagnosticLogsDiagnosticLogsProviderDelegateTableSize] = { + nullptr +}; + +DiagnosticLogsProviderDelegate * GetDiagnosticLogsProviderDelegate(EndpointId endpoint) +{ + uint16_t ep = emberAfGetClusterServerEndpointIndex(endpoint, Id, EMBER_AF_DIAGNOSTIC_LOGS_CLUSTER_SERVER_ENDPOINT_COUNT); + auto delegate = (ep >= ArraySize(gDiagnosticLogsProviderDelegateTable) ? nullptr : gDiagnosticLogsProviderDelegateTable[ep]); + + if (delegate == nullptr) + { + ChipLogProgress(Zcl, "Diagnosticlogs: no log provider delegate set for endpoint:%u", endpoint); + } + + return delegate; +} + +void AddResponse(CommandHandler * commandObj, const ConcreteCommandPath & path, StatusEnum status) +{ + Commands::RetrieveLogsResponse::Type response; + response.status = status; + commandObj->AddResponse(path, response); +} + +void AddResponse(CommandHandler * commandObj, const ConcreteCommandPath & path, StatusEnum status, MutableByteSpan & logContent, + const Optional & timeStamp, const Optional & timeSinceBoot) +{ + Commands::RetrieveLogsResponse::Type response; + response.status = status; + response.logContent = ByteSpan(logContent); + response.UTCTimeStamp = timeStamp; + response.timeSinceBoot = timeSinceBoot; + + commandObj->AddResponse(path, response); +} + +#if CHIP_CONFIG_ENABLE_BDX_LOG_TRANSFER +BDXDiagnosticLogsProvider gBDXDiagnosticLogsProvider; +#endif // CHIP_CONFIG_ENABLE_BDX_LOG_TRANSFER + +} // anonymous namespace -#include +DiagnosticLogsServer DiagnosticLogsServer::sInstance; -// We store our times as millisecond times, to save space. -using TimeInBufferType = chip::System::Clock::Milliseconds32::rep; +void DiagnosticLogsServer::SetDiagnosticLogsProviderDelegate(EndpointId endpoint, DiagnosticLogsProviderDelegate * delegate) +{ + uint16_t ep = emberAfGetClusterServerEndpointIndex(endpoint, Id, EMBER_AF_DIAGNOSTIC_LOGS_CLUSTER_SERVER_ENDPOINT_COUNT); + if (ep < kDiagnosticLogsDiagnosticLogsProviderDelegateTableSize) + { + gDiagnosticLogsProviderDelegateTable[ep] = delegate; + } +} -CHIP_ERROR DiagnosticLogsCommandHandler::PushLog(const chip::ByteSpan & payload) +DiagnosticLogsServer & DiagnosticLogsServer::Instance() { - 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); + return sInstance; } -void DiagnosticLogsCommandHandler::InvokeCommand(HandlerContext & handlerContext) +void DiagnosticLogsServer::HandleLogRequestForResponsePayload(CommandHandler * commandObj, const ConcreteCommandPath & path, + IntentEnum intent, StatusEnum status) { - 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; - } - - switch (payload.intent) - { - case chip::app::Clusters::DiagnosticLogs::IntentEnum::kEndUserSupport: { - chip::app::Clusters::DiagnosticLogs::Commands::RetrieveLogsResponse::Type response; - if (mBuffer.IsEmpty()) - { - response.status = chip::app::Clusters::DiagnosticLogs::StatusEnum::kNoLogs; - handlerContext.mCommandHandler.AddResponse(handlerContext.mRequestPath, response); - break; - } - - size_t logSize = mBuffer.GetFrontSize(); - TimeInBufferType timeFromBuffer; - VerifyOrDie(logSize > sizeof(timeFromBuffer)); - - chip::Platform::ScopedMemoryBuffer buf; - if (!buf.Calloc(logSize)) - { - response.status = chip::app::Clusters::DiagnosticLogs::StatusEnum::kBusy; - handlerContext.mCommandHandler.AddResponse(handlerContext.mRequestPath, response); - break; - } - - // The entry is | time (4 bytes) | content (var size) | - chip::MutableByteSpan entry(buf.Get(), logSize); - CHIP_ERROR err = mBuffer.ReadFront(entry); - VerifyOrDie(err == CHIP_NO_ERROR); - memcpy(&timeFromBuffer, buf.Get(), sizeof(timeFromBuffer)); - - auto timestamp = chip::System::Clock::Milliseconds32(timeFromBuffer); - - response.status = chip::app::Clusters::DiagnosticLogs::StatusEnum::kSuccess; - response.logContent = chip::ByteSpan(buf.Get() + sizeof(timeFromBuffer), logSize - sizeof(timeFromBuffer)); - response.timeSinceBoot.SetValue(chip::System::Clock::Microseconds64(timestamp).count()); - handlerContext.mCommandHandler.AddResponse(handlerContext.mRequestPath, response); - } - break; - case chip::app::Clusters::DiagnosticLogs::IntentEnum::kNetworkDiag: { - chip::app::Clusters::DiagnosticLogs::Commands::RetrieveLogsResponse::Type response; - response.status = chip::app::Clusters::DiagnosticLogs::StatusEnum::kNoLogs; - handlerContext.mCommandHandler.AddResponse(handlerContext.mRequestPath, response); - } - break; - case chip::app::Clusters::DiagnosticLogs::IntentEnum::kCrashLogs: { - chip::app::Clusters::DiagnosticLogs::Commands::RetrieveLogsResponse::Type response; - response.status = chip::app::Clusters::DiagnosticLogs::StatusEnum::kNoLogs; - handlerContext.mCommandHandler.AddResponse(handlerContext.mRequestPath, response); - } - break; - case chip::app::Clusters::DiagnosticLogs::IntentEnum::kUnknownEnumValue: { - handlerContext.mCommandHandler.AddStatus(handlerContext.mRequestPath, - chip::Protocols::InteractionModel::Status::InvalidCommand); - break; - } - } - }); + // If there is no delegate, there is no mechanism to read the logs. Assume those are empty and return NoLogs + auto * delegate = GetDiagnosticLogsProviderDelegate(path.mEndpointId); + VerifyOrReturn(nullptr != delegate, AddResponse(commandObj, path, StatusEnum::kNoLogs)); + + Platform::ScopedMemoryBuffer buffer; + VerifyOrReturn(buffer.Alloc(kMaxLogContentSize), AddResponse(commandObj, path, StatusEnum::kDenied)); + + auto logContent = MutableByteSpan(buffer.Get(), kMaxLogContentSize); + Optional timeStamp; + Optional timeSinceBoot; + + auto err = delegate->GetLogForIntent(intent, logContent, timeStamp, timeSinceBoot); + VerifyOrReturn(CHIP_ERROR_NOT_FOUND != err, AddResponse(commandObj, path, StatusEnum::kNoLogs)); + VerifyOrReturn(CHIP_NO_ERROR == err, AddResponse(commandObj, path, StatusEnum::kDenied)); + + AddResponse(commandObj, path, status, logContent, timeStamp, timeSinceBoot); } -bool emberAfDiagnosticLogsClusterRetrieveLogsRequestCallback( - chip::app::CommandHandler * commandObj, const chip::app::ConcreteCommandPath & commandPath, - const chip::app::Clusters::DiagnosticLogs::Commands::RetrieveLogsRequest::DecodableType & commandData) +void DiagnosticLogsServer::HandleLogRequestForBdx(CommandHandler * commandObj, const ConcreteCommandPath & path, IntentEnum intent, + Optional transferFileDesignator) { - commandObj->AddStatus(commandPath, chip::Protocols::InteractionModel::Status::UnsupportedCommand); + // If the RequestedProtocol is set to BDX and there is no TransferFileDesignator the command SHALL fail with a Status Code of + // INVALID_COMMAND. + VerifyOrReturn(transferFileDesignator.HasValue(), commandObj->AddStatus(path, Status::InvalidCommand)); + + VerifyOrReturn(transferFileDesignator.Value().size() <= kMaxFileDesignatorLen, + commandObj->AddStatus(path, Status::ConstraintError)); + + // If there is no delegate, there is no mechanism to read the logs. Assume those are empty and return NoLogs + auto * delegate = GetDiagnosticLogsProviderDelegate(path.mEndpointId); + VerifyOrReturn(nullptr != delegate, AddResponse(commandObj, path, StatusEnum::kNoLogs)); + + // In the case where the Node is able to fit the entirety of the requested logs within the LogContent field, the Status field of + // the RetrieveLogsResponse SHALL be set to Exhausted and a BDX session SHALL NOT be initiated. + VerifyOrReturn(delegate->GetSizeForIntent(intent) > kMaxLogContentSize, + HandleLogRequestForResponsePayload(commandObj, path, intent, StatusEnum::kExhausted)); + + // If the RequestedProtocol is set to BDX and either the Node does not support BDX or it is not possible for the Node + // to establish a BDX session, then the Node SHALL utilize the LogContent field of the RetrieveLogsResponse command + // to transfer as much of the current logs as it can fit within the response, and the Status field of the + // RetrieveLogsResponse SHALL be set to Exhausted. +#if CHIP_CONFIG_ENABLE_BDX_LOG_TRANSFER + VerifyOrReturn(!gBDXDiagnosticLogsProvider.IsBusy(), AddResponse(commandObj, path, StatusEnum::kBusy)); + auto err = gBDXDiagnosticLogsProvider.InitializeTransfer(commandObj, path, delegate, intent, transferFileDesignator.Value()); + VerifyOrReturn(CHIP_NO_ERROR == err, AddResponse(commandObj, path, StatusEnum::kDenied)); +#else + HandleLogRequestForResponsePayload(commandObj, path, intent, StatusEnum::kExhausted); +#endif // CHIP_CONFIG_ENABLE_BDX_LOG_TRANSFER +} + +} // namespace DiagnosticLogs +} // namespace Clusters +} // namespace app +} // namespace chip + +bool emberAfDiagnosticLogsClusterRetrieveLogsRequestCallback(chip::app::CommandHandler * commandObj, + const chip::app::ConcreteCommandPath & commandPath, + const Commands::RetrieveLogsRequest::DecodableType & commandData) +{ + // If the Intent and/or the RequestedProtocol arguments contain invalid (out of range) values the command SHALL fail with a + // Status Code of INVALID_COMMAND. + auto intent = commandData.intent; + auto protocol = commandData.requestedProtocol; + if (intent == IntentEnum::kUnknownEnumValue || protocol == TransferProtocolEnum::kUnknownEnumValue) + { + commandObj->AddStatus(commandPath, Status::InvalidCommand); + return true; + } + + auto instance = DiagnosticLogsServer::Instance(); + if (protocol == TransferProtocolEnum::kResponsePayload) + { + instance.HandleLogRequestForResponsePayload(commandObj, commandPath, intent); + } + else + { + instance.HandleLogRequestForBdx(commandObj, commandPath, intent, commandData.transferFileDesignator); + } + return true; } void MatterDiagnosticLogsPluginServerInitCallback() {} +#endif // #ifdef EMBER_AF_DIAGNOSTIC_LOGS_CLUSTER_SERVER_ENDPOINT_COUNT 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..68627245f2a7d9 100644 --- a/src/app/clusters/diagnostic-logs-server/diagnostic-logs-server.h +++ b/src/app/clusters/diagnostic-logs-server/diagnostic-logs-server.h @@ -20,29 +20,49 @@ #include #include -#include +#include -#include +namespace chip { +namespace app { +namespace Clusters { +namespace DiagnosticLogs { /// 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(); - DiagnosticLogsCommandHandler() : - CommandHandlerInterface(chip::MakeOptional(chip::EndpointId(kDiagnosticLogsEndpoint)), - chip::app::Clusters::DiagnosticLogs::Id), - mBuffer(mStorage.data(), mStorage.size()) - {} + /** + * Set the default delegate of the diagnostic logs cluster for the specified endpoint + * + * @param endpoint ID of the endpoint + * + * @param delegate The log provider delegate at the endpoint + */ + void SetDiagnosticLogsProviderDelegate(EndpointId endpoint, DiagnosticLogsProviderDelegate * delegate); - // Inherited from CommandHandlerInterface - void InvokeCommand(HandlerContext & handlerContext) override; + /** + * Handles the request to download diagnostic logs of type specified in the intent argument for protocol type ResponsePayload + * This should return whatever fits in the logContent field of the RetrieveLogsResponse command + * + * @param commandObj The command handler object from the RetrieveLogsRequest command + * @param path The command path from the RetrieveLogsRequest command + * @param intent The log type requested in the RetrieveLogsRequest command + * @param status The status to be returned on success + * + */ + void HandleLogRequestForResponsePayload(CommandHandler * commandObj, const ConcreteCommandPath & path, IntentEnum intent, + StatusEnum status = StatusEnum::kSuccess); - CHIP_ERROR PushLog(const chip::ByteSpan & payload); + void HandleLogRequestForBdx(CommandHandler * commandObj, const ConcreteCommandPath & path, IntentEnum intent, + Optional transferFileDesignator); private: - std::array mStorage; - chip::BytesCircularBuffer mBuffer; + static DiagnosticLogsServer sInstance; }; + +} // namespace DiagnosticLogs +} // namespace Clusters +} // namespace app +} // namespace chip diff --git a/src/app/tests/suites/TestDiagnosticLogs.yaml b/src/app/tests/suites/TestDiagnosticLogs.yaml new file mode 100644 index 00000000000000..cf31a96b8ddf58 --- /dev/null +++ b/src/app/tests/suites/TestDiagnosticLogs.yaml @@ -0,0 +1,660 @@ +# 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. + +name: Diagnostic Logs Tests + +config: + nodeId: 0x12344321 + cluster: "Diagnostic Logs" + endpoint: 0 + end_user_support_log_file_path: "/tmp/end_user_support_log.txt" + end_user_support_log_file_content: "End User Support Log Content" + end_user_support_log_file_content_long: + "End User Support Log Content is more than 1024 + bytes............................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................X" + network_diagnostics_log_file_path: "/tmp/network_diagnostics_log.txt" + network_diagnostics_log_file_content: "Network Diagnostic Log Content" + crash_log_file_path: "/tmp/crash_log.txt" + crash_log_file_content: "Crash Log Content" + + bdx_transfer_file_path: "/tmp/end_user_support_bdx_output.txt" + bdx_transfer_file_name: "end_user_support_bdx_output.txt" + +tests: + # + # Set up the test by adding some destination log files for the target accessory: + # 1. End User Support + # 2. Network Diagnostics + # 3. Crash + # + # The first thing to do is to delete them if they exist. It could be some + # left over from a previous test run. + # + + - label: "Delete EndUserSupport logs" + cluster: "SystemCommands" + command: "DeleteFile" + arguments: + values: + - name: "filePath" + value: end_user_support_log_file_path + + - label: "Delete NetworkDiag logs" + cluster: "SystemCommands" + command: "DeleteFile" + arguments: + values: + - name: "filePath" + value: network_diagnostics_log_file_path + + - label: "Delete CrashLogs logs" + cluster: "SystemCommands" + command: "DeleteFile" + arguments: + values: + - name: "filePath" + value: crash_log_file_path + + - label: "Stop the accessory" + cluster: "SystemCommands" + command: "Stop" + + - label: "Create End User Support logs" + cluster: "SystemCommands" + command: "CreateFile" + arguments: + values: + - name: "filePath" + value: end_user_support_log_file_path + - name: "fileContent" + value: end_user_support_log_file_content + + - label: "Create NetworkDiag logs" + cluster: "SystemCommands" + command: "CreateFile" + arguments: + values: + - name: "filePath" + value: network_diagnostics_log_file_path + - name: "fileContent" + value: network_diagnostics_log_file_content + + - label: "Create CrashLogs logs" + cluster: "SystemCommands" + command: "CreateFile" + arguments: + values: + - name: "filePath" + value: crash_log_file_path + - name: "fileContent" + value: crash_log_file_content + + - label: "Start the accessory with the destination logs files" + cluster: "SystemCommands" + command: "Start" + arguments: + values: + - name: "endUserSupportLogPath" + value: end_user_support_log_file_path + - name: "networkDiagnosticsLogPath" + value: network_diagnostics_log_file_path + - name: "crashLogPath" + value: crash_log_file_path + + - label: "Wait for the commissioned device to be retrieved" + cluster: "DelayCommands" + command: "WaitForCommissionee" + arguments: + values: + - name: "nodeId" + value: nodeId + + - label: "Read End User Support log intent" + command: "RetrieveLogsRequest" + arguments: + values: + - name: "Intent" + value: 0 # EndUserSupport + - name: "RequestedProtocol" + value: 0 # ResponsePayload + response: + values: + - name: "Status" + value: 0 # Success + - name: "LogContent" + value: end_user_support_log_file_content + + - label: "Read NetworkDiag intent" + command: "RetrieveLogsRequest" + arguments: + values: + - name: "Intent" + value: 1 # NetworkDiag + - name: "RequestedProtocol" + value: 0 # ResponsePayload + response: + values: + - name: "Status" + value: 0 # Success + - name: "LogContent" + value: network_diagnostics_log_file_content + + - label: "Read CrashLogs intent" + command: "RetrieveLogsRequest" + arguments: + values: + - name: "Intent" + value: 2 # CrashLogs + - name: "RequestedProtocol" + value: 0 # ResponsePayload + response: + values: + - name: "Status" + value: 0 # Success + - name: "LogContent" + value: crash_log_file_content + + - label: "Write End User Support logs to be over the 1024 bytes limit" + cluster: "SystemCommands" + command: "CreateFile" + arguments: + values: + - name: "filePath" + value: end_user_support_log_file_path + - name: "fileContent" + value: end_user_support_log_file_content_long + + - label: "Read End User Support log intent" + command: "RetrieveLogsRequest" + arguments: + values: + - name: "Intent" + value: 0 # EndUserSupport + - name: "RequestedProtocol" + value: 0 # ResponsePayload + response: + values: + - name: "Status" + value: 0 # Success + - name: "LogContent" + constraints: + minLength: 1024 + maxLength: 1024 + + - label: "Write End User Support logs back to the previous value" + cluster: "SystemCommands" + command: "CreateFile" + arguments: + values: + - name: "filePath" + value: end_user_support_log_file_path + - name: "fileContent" + value: end_user_support_log_file_content + + # + # Check BDX Implementation + # + + # In the case where the Node is able to fit the entirety of the requested logs within the LogContent field, the Status field of + # the RetrieveLogsResponse SHALL be set to Exhausted and a BDX session SHALL NOT be initiated. + - label: "Read End User Support log using BDX" + command: "RetrieveLogsRequest" + arguments: + values: + - name: "Intent" + value: 0 # EndUserSupport + - name: "RequestedProtocol" + value: 1 # BDX + - name: "TransferFileDesignator" + value: "ItDoesNotMatterForThisTest" + response: + values: + - name: "Status" + value: 1 # Exhausted + - name: "LogContent" + value: end_user_support_log_file_content + + - label: "Write End User Support logs to be over the 1024 bytes limit" + cluster: "SystemCommands" + command: "CreateFile" + arguments: + values: + - name: "filePath" + value: end_user_support_log_file_path + - name: "fileContent" + value: end_user_support_log_file_content_long + + # If the RequestedProtocol is set to BDX the Node SHOULD immediately realize the RetrieveLogsResponse command by initiating a BDX + # Transfer, sending a BDX SendInit message with the File Designator field of the message set to the value of the TransferFileDesignator + # field of the RetrieveLogsRequest. + # On reception of a BDX SendAccept message the Node SHALL send a RetrieveLogsResponse command with a Status field set to Success and + # proceed with the log transfer over BDX. + - label: "Delete possible leftover from previous run" + cluster: "SystemCommands" + command: "DeleteFile" + arguments: + values: + - name: "filePath" + value: bdx_transfer_file_path + + - label: "BDX: Request End User Support" + command: "RetrieveLogsRequest" + arguments: + values: + - name: "Intent" + value: 0 # EndUserSupport + - name: "RequestedProtocol" + value: 1 # BDX + - name: "TransferFileDesignator" + value: bdx_transfer_file_name + response: + values: + - name: "Status" + value: 0 # Success + - name: "LogContent" + value: "" + + - label: + "BDX: Request End User Support while the previous request is running" + command: "RetrieveLogsRequest" + arguments: + values: + - name: "Intent" + value: 0 # EndUserSupport + - name: "RequestedProtocol" + value: 1 # BDX + - name: "TransferFileDesignator" + value: "ItDoesNotMatterForThisBusyTest" + response: + values: + - name: "Status" + value: 3 # Busy + - name: "LogContent" + value: "" + + - label: "BDX: Wait for 'Diagnostic logs transfer: Success' message" + cluster: "DelayCommands" + command: "WaitForMessage" + arguments: + values: + - name: "registerKey" + value: "default" + - name: "message" + value: "Diagnostic logs transfer: Success" + + - label: + "Compare the content the original log and the file that has been + created as the result of the BDX transfer" + cluster: "SystemCommands" + command: "CompareFiles" + arguments: + values: + - name: "file1" + value: end_user_support_log_file_path + - name: "file2" + value: bdx_transfer_file_path + + # If a failure StatusReport is received in response to the SendInit message, the Node SHALL send a RetrieveLogsResponse command with a + # Status of Denied. + - label: + "BDX: Request End User Support log with a special + TransferFileDesignator name that forces the responder to issue an + error when SendInit is received" + command: "RetrieveLogsRequest" + arguments: + values: + - name: "Intent" + value: 0 # EndUserSupport + - name: "RequestedProtocol" + value: 1 # BDX + - name: "TransferFileDesignator" + value: "Error:OnTransferBegin" # Should issue an error when SendInit is received. + response: + values: + - name: "Status" + value: 4 # Denied + - name: "LogContent" + value: "" + + # + # If the Intent and/or the RequestedProtocol arguments contain invalid (out of range) values the command SHALL fail with a Status Code of INVALID_COMMAND. + # + - label: "Read unknown log intent" + command: "RetrieveLogsRequest" + arguments: + values: + - name: "Intent" + value: 128 # undefined value + - name: "RequestedProtocol" + value: 0 # ResponsePayload + response: + error: "INVALID_COMMAND" + + - label: "Read known log intent on unknown protocol" + command: "RetrieveLogsRequest" + arguments: + values: + - name: "Intent" + value: 0 # EndUserSupport + - name: "RequestedProtocol" + value: 128 # undefined value + response: + error: "INVALID_COMMAND" + + # + # If the RequestedProtocol is set to BDX and there is no TransferFileDesignator the command SHALL fail with a Status Code of INVALID_COMMAND. + # + - label: + "Read End User Support log intent using BDX but without a + TransferFileDesignator" + command: "RetrieveLogsRequest" + arguments: + values: + - name: "Intent" + value: 0 # EndUserSupport + - name: "RequestedProtocol" + value: 1 # BDX + response: + error: "INVALID_COMMAND" + + - label: + "Read End User Support log intent using BDX but with a + TransferFileDesignator that is longer than the kMaxFileDesignatorLen + character limit" + command: "RetrieveLogsRequest" + arguments: + values: + - name: "Intent" + value: 0 # EndUserSupport + - name: "RequestedProtocol" + value: 1 # BDX + - name: "TransferFileDesignator" + value: "ThisNameIsLongerThanThekMaxFileDesignatorLenCharacterConstraint" + response: + error: "CONSTRAINT_ERROR" + + # + # Validate that a new BDX transfer can be started after a previous successful BDX transfer + # + - label: "Delete the result of the previous run" + cluster: "SystemCommands" + command: "DeleteFile" + arguments: + values: + - name: "filePath" + value: bdx_transfer_file_path + + - label: "BDX: Request End User Support" + command: "RetrieveLogsRequest" + arguments: + values: + - name: "Intent" + value: 0 # EndUserSupport + - name: "RequestedProtocol" + value: 1 # BDX + - name: "TransferFileDesignator" + value: bdx_transfer_file_name + response: + values: + - name: "Status" + value: 0 # Success + - name: "LogContent" + value: "" + + - label: "BDX: Wait for 'Diagnostic logs transfer: Success' message" + cluster: "DelayCommands" + command: "WaitForMessage" + arguments: + values: + - name: "registerKey" + value: "default" + - name: "message" + value: "Diagnostic logs transfer: Success" + + - label: + "Compare the content the original log and the file that has been + created as the result of the BDX transfer" + cluster: "SystemCommands" + command: "CompareFiles" + arguments: + values: + - name: "file1" + value: end_user_support_log_file_path + - name: "file2" + value: bdx_transfer_file_path + + - label: "Delete the result of the previous run" + cluster: "SystemCommands" + command: "DeleteFile" + arguments: + values: + - name: "filePath" + value: bdx_transfer_file_path + + - label: "BDX: Request End User Support" + command: "RetrieveLogsRequest" + arguments: + values: + - name: "Intent" + value: 0 # EndUserSupport + - name: "RequestedProtocol" + value: 1 # BDX + - name: "TransferFileDesignator" + value: bdx_transfer_file_name + response: + values: + - name: "Status" + value: 0 # Success + - name: "LogContent" + value: "" + + - label: "BDX: Wait for 'Diagnostic logs transfer: Success' message" + cluster: "DelayCommands" + command: "WaitForMessage" + arguments: + values: + - name: "registerKey" + value: "default" + - name: "message" + value: "Diagnostic logs transfer: Success" + + - label: + "Compare the content the original log and the file that has been + created as the result of the BDX transfer" + cluster: "SystemCommands" + command: "CompareFiles" + arguments: + values: + - name: "file1" + value: end_user_support_log_file_path + - name: "file2" + value: bdx_transfer_file_path + + - label: "Delete the result of the previous run" + cluster: "SystemCommands" + command: "DeleteFile" + arguments: + values: + - name: "filePath" + value: bdx_transfer_file_path + + # + # Validate that a new BDX transfer can be started after a previous unsuccessful BDX transfer + # + - label: "Delete the result of the previous run" + cluster: "SystemCommands" + command: "DeleteFile" + arguments: + values: + - name: "filePath" + value: bdx_transfer_file_path + + - label: "BDX: Request End User Support" + command: "RetrieveLogsRequest" + arguments: + values: + - name: "Intent" + value: 0 # EndUserSupport + - name: "RequestedProtocol" + value: 1 # BDX + - name: "TransferFileDesignator" + value: "Error:OnTransferBegin" # Should issue an error when SendInit is received. + response: + values: + - name: "Status" + value: 4 # kDenied + - name: "LogContent" + value: "" + + - label: "BDX: Request End User Support" + command: "RetrieveLogsRequest" + arguments: + values: + - name: "Intent" + value: 0 # EndUserSupport + - name: "RequestedProtocol" + value: 1 # BDX + - name: "TransferFileDesignator" + value: bdx_transfer_file_name + response: + values: + - name: "Status" + value: 0 # Success + - name: "LogContent" + value: "" + + - label: "BDX: Wait for 'Diagnostic logs transfer: Success' message" + cluster: "DelayCommands" + command: "WaitForMessage" + arguments: + values: + - name: "registerKey" + value: "default" + - name: "message" + value: "Diagnostic logs transfer: Success" + + - label: + "Compare the content the original log and the file that has been + created as the result of the BDX transfer" + cluster: "SystemCommands" + command: "CompareFiles" + arguments: + values: + - name: "file1" + value: end_user_support_log_file_path + - name: "file2" + value: bdx_transfer_file_path + + - label: "Delete the result of the previous run" + cluster: "SystemCommands" + command: "DeleteFile" + arguments: + values: + - name: "filePath" + value: bdx_transfer_file_path + + # + # Validate that a new BDX transfer can be started after a previous unsuccessful BDX transfer + # stopped once the RetrieveLogsResponse has been sent/received. + # + - label: "Delete the result of the previous run" + cluster: "SystemCommands" + command: "DeleteFile" + arguments: + values: + - name: "filePath" + value: bdx_transfer_file_path + + - label: "Delete possible leftover from previous run" + cluster: "SystemCommands" + command: "DeleteFile" + arguments: + values: + - name: "filePath" + value: "/tmp/Error:OnTransferData" + + - label: "BDX: Request End User Support" + command: "RetrieveLogsRequest" + arguments: + values: + - name: "Intent" + value: 0 # EndUserSupport + - name: "RequestedProtocol" + value: 1 # BDX + - name: "TransferFileDesignator" + value: "Error:OnTransferData" # Should issue an error after SendInit is received. + response: + values: + - name: "Status" + value: 0 # kSuccess + - name: "LogContent" + value: "" + + - label: + "BDX: Wait for 'Diagnostic logs transfer: StatusReport Error' message" + cluster: "DelayCommands" + command: "WaitForMessage" + arguments: + values: + - name: "registerKey" + value: "default" + - name: "message" + value: "Diagnostic logs transfer: StatusReport Error" + + - label: "BDX: Request End User Support" + command: "RetrieveLogsRequest" + arguments: + values: + - name: "Intent" + value: 0 # EndUserSupport + - name: "RequestedProtocol" + value: 1 # BDX + - name: "TransferFileDesignator" + value: bdx_transfer_file_name + response: + values: + - name: "Status" + value: 0 # Success + - name: "LogContent" + value: "" + + - label: "BDX: Wait for 'Diagnostic logs transfer: Success' message" + cluster: "DelayCommands" + command: "WaitForMessage" + arguments: + values: + - name: "registerKey" + value: "default" + - name: "message" + value: "Diagnostic logs transfer: Success" + + - label: + "Compare the content the original log and the file that has been + created as the result of the BDX transfer" + cluster: "SystemCommands" + command: "CompareFiles" + arguments: + values: + - name: "file1" + value: end_user_support_log_file_path + - name: "file2" + value: bdx_transfer_file_path + + - label: "Delete the result of the previous run" + cluster: "SystemCommands" + command: "DeleteFile" + arguments: + values: + - name: "filePath" + value: bdx_transfer_file_path diff --git a/src/controller/CHIPDeviceControllerFactory.cpp b/src/controller/CHIPDeviceControllerFactory.cpp index 60a29f1afb2363..8268b07b7f3dcc 100644 --- a/src/controller/CHIPDeviceControllerFactory.cpp +++ b/src/controller/CHIPDeviceControllerFactory.cpp @@ -177,6 +177,7 @@ CHIP_ERROR DeviceControllerFactory::InitSystemState(FactoryInitParams params) stateParams.timerDelegate = chip::Platform::New(); stateParams.reportScheduler = chip::Platform::New(stateParams.timerDelegate); stateParams.sessionKeystore = params.sessionKeystore; + stateParams.bdxTransferServer = chip::Platform::New(); // if no fabricTable was provided, create one and track it in stateParams for cleanup stateParams.fabricTable = params.fabricTable; @@ -225,6 +226,7 @@ CHIP_ERROR DeviceControllerFactory::InitSystemState(FactoryInitParams params) ReturnErrorOnFailure(stateParams.exchangeMgr->Init(stateParams.sessionMgr)); ReturnErrorOnFailure(stateParams.messageCounterManager->Init(stateParams.exchangeMgr)); ReturnErrorOnFailure(stateParams.unsolicitedStatusHandler->Init(stateParams.exchangeMgr)); + ReturnErrorOnFailure(stateParams.bdxTransferServer->ListenForSendInit(stateParams.systemLayer, stateParams.exchangeMgr)); InitDataModelHandler(); @@ -438,6 +440,13 @@ void DeviceControllerSystemState::Shutdown() mFabricTableDelegate = nullptr; } + if (mBDXTransferServer != nullptr) + { + mBDXTransferServer->Shutdown(); + chip::Platform::Delete(mBDXTransferServer); + mBDXTransferServer = nullptr; + } + if (mCASEServer != nullptr) { mCASEServer->Shutdown(); diff --git a/src/controller/CHIPDeviceControllerSystemState.h b/src/controller/CHIPDeviceControllerSystemState.h index 5c1731651cd2a1..bc89d6c1d21ec2 100644 --- a/src/controller/CHIPDeviceControllerSystemState.h +++ b/src/controller/CHIPDeviceControllerSystemState.h @@ -36,6 +36,7 @@ #include #include #include +#include #include #include #include @@ -102,6 +103,7 @@ struct DeviceControllerSystemStateParams Protocols::SecureChannel::UnsolicitedStatusHandler * unsolicitedStatusHandler = nullptr; Messaging::ExchangeManager * exchangeMgr = nullptr; secure_channel::MessageCounterManager * messageCounterManager = nullptr; + bdx::BDXTransferServer * bdxTransferServer = nullptr; CASEServer * caseServer = nullptr; CASESessionManager * caseSessionManager = nullptr; SessionSetupPool * sessionSetupPool = nullptr; @@ -136,7 +138,8 @@ class DeviceControllerSystemState mSystemLayer(params.systemLayer), mTCPEndPointManager(params.tcpEndPointManager), mUDPEndPointManager(params.udpEndPointManager), mTransportMgr(params.transportMgr), mSessionMgr(params.sessionMgr), mUnsolicitedStatusHandler(params.unsolicitedStatusHandler), mExchangeMgr(params.exchangeMgr), - mMessageCounterManager(params.messageCounterManager), mFabrics(params.fabricTable), mCASEServer(params.caseServer), + mMessageCounterManager(params.messageCounterManager), mFabrics(params.fabricTable), + mBDXTransferServer(params.bdxTransferServer), mCASEServer(params.caseServer), mCASESessionManager(params.caseSessionManager), mSessionSetupPool(params.sessionSetupPool), mCASEClientPool(params.caseClientPool), mGroupDataProvider(params.groupDataProvider), mTimerDelegate(params.timerDelegate), mReportScheduler(params.reportScheduler), mSessionKeystore(params.sessionKeystore), @@ -190,7 +193,7 @@ class DeviceControllerSystemState mUnsolicitedStatusHandler != nullptr && mExchangeMgr != nullptr && mMessageCounterManager != nullptr && mFabrics != nullptr && mCASESessionManager != nullptr && mSessionSetupPool != nullptr && mCASEClientPool != nullptr && mGroupDataProvider != nullptr && mReportScheduler != nullptr && mTimerDelegate != nullptr && - mSessionKeystore != nullptr && mSessionResumptionStorage != nullptr; + mSessionKeystore != nullptr && mSessionResumptionStorage != nullptr && mBDXTransferServer != nullptr; }; System::Layer * SystemLayer() const { return mSystemLayer; }; @@ -214,6 +217,7 @@ class DeviceControllerSystemState mTempFabricTable = tempFabricTable; mEnableServerInteractions = enableServerInteractions; } + bdx::BDXTransferServer * BDXTransferServer() const { return mBDXTransferServer; } private: DeviceControllerSystemState() {} @@ -230,6 +234,7 @@ class DeviceControllerSystemState Messaging::ExchangeManager * mExchangeMgr = nullptr; secure_channel::MessageCounterManager * mMessageCounterManager = nullptr; FabricTable * mFabrics = nullptr; + bdx::BDXTransferServer * mBDXTransferServer = nullptr; CASEServer * mCASEServer = nullptr; CASESessionManager * mCASESessionManager = nullptr; SessionSetupPool * mSessionSetupPool = nullptr; diff --git a/src/controller/java/BdxOTASender.cpp b/src/controller/java/BdxOTASender.cpp index 294d8ebc0f88a5..41f116762f9f8b 100644 --- a/src/controller/java/BdxOTASender.cpp +++ b/src/controller/java/BdxOTASender.cpp @@ -160,19 +160,6 @@ CHIP_ERROR BdxOTASender::OnMessageToSend(TransferSession::OutputEvent & event) return err; } -bdx::StatusCode BdxOTASender::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 BdxOTASender::OnTransferSessionBegin(TransferSession::OutputEvent & event) { assertChipStackLockedByCurrentThread(); diff --git a/src/controller/java/BdxOTASender.h b/src/controller/java/BdxOTASender.h index b4130540d43ec5..b339a4455827ca 100644 --- a/src/controller/java/BdxOTASender.h +++ b/src/controller/java/BdxOTASender.h @@ -51,8 +51,6 @@ class BdxOTASender : public chip::bdx::Responder CHIP_ERROR OnMessageToSend(chip::bdx::TransferSession::OutputEvent & event); - chip::bdx::StatusCode GetBdxStatusCodeFromChipError(CHIP_ERROR err); - CHIP_ERROR OnTransferSessionBegin(chip::bdx::TransferSession::OutputEvent & event); CHIP_ERROR OnTransferSessionEnd(chip::bdx::TransferSession::OutputEvent & event); diff --git a/src/darwin/Framework/CHIP/MTROTAProviderDelegateBridge.mm b/src/darwin/Framework/CHIP/MTROTAProviderDelegateBridge.mm index 6fe3c48a17548a..5e4843312ba4ff 100644 --- a/src/darwin/Framework/CHIP/MTROTAProviderDelegateBridge.mm +++ b/src/darwin/Framework/CHIP/MTROTAProviderDelegateBridge.mm @@ -198,17 +198,6 @@ CHIP_ERROR OnMessageToSend(TransferSession::OutputEvent & event) return err; } - 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 OnTransferSessionBegin(TransferSession::OutputEvent & event) { assertChipStackLockedByCurrentThread(); diff --git a/src/lib/core/CHIPConfig.h b/src/lib/core/CHIPConfig.h index 0dd1bb85de3fd7..4fa06b2548b513 100644 --- a/src/lib/core/CHIPConfig.h +++ b/src/lib/core/CHIPConfig.h @@ -1721,6 +1721,16 @@ extern const char CHIP_NON_PRODUCTION_MARKER[]; #define CHIP_CONFIG_ICD_OBSERVERS_POOL_SIZE 2 #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/BUILD.gn b/src/protocols/bdx/BUILD.gn index c9cfe91ac45a39..c78b91e9d907a8 100644 --- a/src/protocols/bdx/BUILD.gn +++ b/src/protocols/bdx/BUILD.gn @@ -20,10 +20,17 @@ static_library("bdx") { sources = [ "BdxMessages.cpp", "BdxMessages.h", + "BdxTransferProxy.h", + "BdxTransferProxyDiagnosticLog.cpp", + "BdxTransferProxyDiagnosticLog.h", + "BdxTransferServer.cpp", + "BdxTransferServer.h", "BdxTransferSession.cpp", "BdxTransferSession.h", "BdxUri.cpp", "BdxUri.h", + "StatusCode.cpp", + "StatusCode.h", "TransferFacilitator.cpp", "TransferFacilitator.h", ] diff --git a/src/protocols/bdx/BdxMessages.h b/src/protocols/bdx/BdxMessages.h index 66cfe33a2bd349..99aabc46510bd1 100644 --- a/src/protocols/bdx/BdxMessages.h +++ b/src/protocols/bdx/BdxMessages.h @@ -28,6 +28,7 @@ #include #include #include +#include #include namespace chip { @@ -51,24 +52,6 @@ enum class MessageType : uint8_t BlockQueryWithSkip = 0x15, }; -enum class StatusCode : uint16_t -{ - kLengthTooLarge = 0x0012, - kLengthTooShort = 0x0013, - kLengthMismatch = 0x0014, - kLengthRequired = 0x0015, - kBadMessageContents = 0x0016, - kBadBlockCounter = 0x0017, - kUnexpectedMessage = 0x0018, - kResponderBusy = 0x0019, - kTransferFailedUnknownError = 0x001F, - kTransferMethodNotSupported = 0x0050, - kFileDesignatorUnknown = 0x0051, - kStartOffsetNotSupported = 0x0052, - kVersionNotSupported = 0x0053, - kUnknown = 0x005F, -}; - enum class TransferControlFlags : uint8_t { // first 4 bits reserved for version diff --git a/src/protocols/bdx/BdxTransferProxy.h b/src/protocols/bdx/BdxTransferProxy.h new file mode 100644 index 00000000000000..b48ef7cb890f29 --- /dev/null +++ b/src/protocols/bdx/BdxTransferProxy.h @@ -0,0 +1,48 @@ +/* + * 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 +#include + +namespace chip { +namespace bdx { + +/** + * A proxy object to control the state of a specific BDX transfer. + */ + +class BDXTransferProxy +{ +public: + virtual ~BDXTransferProxy(){}; + + virtual CHIP_ERROR Accept() = 0; + virtual CHIP_ERROR Reject(CHIP_ERROR error) = 0; + virtual CHIP_ERROR Continue() = 0; + + virtual CharSpan GetFileDesignator() const = 0; + virtual FabricIndex GetFabricIndex() const = 0; + virtual NodeId GetPeerNodeId() const = 0; +}; + +} // namespace bdx +} // namespace chip diff --git a/src/protocols/bdx/BdxTransferProxyDiagnosticLog.cpp b/src/protocols/bdx/BdxTransferProxyDiagnosticLog.cpp new file mode 100644 index 00000000000000..3f8915462941fb --- /dev/null +++ b/src/protocols/bdx/BdxTransferProxyDiagnosticLog.cpp @@ -0,0 +1,98 @@ +/* + * + * 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 "BdxTransferProxyDiagnosticLog.h" + +#include +#include +#include + +namespace chip { +namespace bdx { + +CHIP_ERROR BDXTransferProxyDiagnosticLog::Init(TransferSession * transferSession) +{ + VerifyOrReturnError(nullptr != transferSession, CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrReturnError(nullptr == mTransfer, CHIP_ERROR_INCORRECT_STATE); + + uint16_t fileDesignatorLength = 0; + auto fileDesignator = transferSession->GetFileDesignator(fileDesignatorLength); + + VerifyOrReturnError(fileDesignatorLength > 0, CHIP_ERROR_INVALID_STRING_LENGTH); + VerifyOrReturnError(fileDesignatorLength <= ArraySize(mFileDesignator), CHIP_ERROR_INVALID_STRING_LENGTH); + + mTransfer = transferSession; + mFileDesignatorLen = static_cast(fileDesignatorLength); + memcpy(mFileDesignator, fileDesignator, fileDesignatorLength); + return CHIP_NO_ERROR; +} + +CHIP_ERROR BDXTransferProxyDiagnosticLog::Accept() +{ + ReturnErrorOnFailure(EnsureState()); + VerifyOrReturnError(nullptr != mTransfer, CHIP_ERROR_INCORRECT_STATE); + + TransferSession::TransferAcceptData acceptData; + acceptData.ControlMode = TransferControlFlags::kSenderDrive; + acceptData.MaxBlockSize = mTransfer->GetTransferBlockSize(); + acceptData.StartOffset = mTransfer->GetStartOffset(); + acceptData.Length = mTransfer->GetTransferLength(); + + return mTransfer->AcceptTransfer(acceptData); +} + +CHIP_ERROR BDXTransferProxyDiagnosticLog::Reject(CHIP_ERROR error) +{ + ReturnErrorOnFailure(EnsureState()); + VerifyOrReturnError(nullptr != mTransfer, CHIP_ERROR_INCORRECT_STATE); + + auto statusCode = GetBdxStatusCodeFromChipError(error); + return mTransfer->AbortTransfer(statusCode); +} + +CHIP_ERROR BDXTransferProxyDiagnosticLog::Continue() +{ + ReturnErrorOnFailure(EnsureState()); + VerifyOrReturnError(nullptr != mTransfer, CHIP_ERROR_INCORRECT_STATE); + + return mTransfer->PrepareBlockAck(); +} + +void BDXTransferProxyDiagnosticLog::Reset() +{ + ReturnOnFailure(EnsureState()); + VerifyOrReturn(nullptr != mTransfer); + + memset(mFileDesignator, 0, sizeof(mFileDesignator)); + mFileDesignatorLen = 0; + mTransfer = nullptr; + mFabricIndex = kUndefinedFabricIndex; + mPeerNodeId = kUndefinedNodeId; +} + +CHIP_ERROR BDXTransferProxyDiagnosticLog::EnsureState() const +{ + assertChipStackLockedByCurrentThread(); + VerifyOrReturnError(kUndefinedFabricIndex != mFabricIndex, CHIP_ERROR_INCORRECT_STATE); + VerifyOrReturnError(kUndefinedNodeId != mPeerNodeId, CHIP_ERROR_INCORRECT_STATE); + + return CHIP_NO_ERROR; +} + +} // namespace bdx +} // namespace chip diff --git a/src/protocols/bdx/BdxTransferProxyDiagnosticLog.h b/src/protocols/bdx/BdxTransferProxyDiagnosticLog.h new file mode 100644 index 00000000000000..f4c74a71e48b0c --- /dev/null +++ b/src/protocols/bdx/BdxTransferProxyDiagnosticLog.h @@ -0,0 +1,56 @@ +/* + * 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 bdx { + +class BDXTransferProxyDiagnosticLog : public BDXTransferProxy +{ +public: + CHIP_ERROR Init(TransferSession * transferSession); + void Reset(); + + void SetFabricIndex(FabricIndex fabricIndex) { mFabricIndex = fabricIndex; } + void SetPeerNodeId(NodeId nodeId) { mPeerNodeId = nodeId; } + + CHIP_ERROR Accept() override; + CHIP_ERROR Reject(CHIP_ERROR error) override; + CHIP_ERROR Continue() override; + + FabricIndex GetFabricIndex() const override { return mFabricIndex; } + NodeId GetPeerNodeId() const override { return mPeerNodeId; } + CharSpan GetFileDesignator() const override { return CharSpan(mFileDesignator, mFileDesignatorLen); }; + +private: + CHIP_ERROR EnsureState() const; + + uint8_t mFileDesignatorLen = 0; + char mFileDesignator[DiagnosticLogs::kMaxFileDesignatorLen] = {}; + TransferSession * mTransfer = nullptr; + FabricIndex mFabricIndex = kUndefinedFabricIndex; + NodeId mPeerNodeId = kUndefinedNodeId; +}; + +} // namespace bdx +} // namespace chip diff --git a/src/protocols/bdx/BdxTransferServer.cpp b/src/protocols/bdx/BdxTransferServer.cpp new file mode 100644 index 00000000000000..b276dba51ab521 --- /dev/null +++ b/src/protocols/bdx/BdxTransferServer.cpp @@ -0,0 +1,210 @@ +/* + * + * 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 "BdxTransferServer.h" + +namespace chip { +namespace bdx { + +namespace { +// Max block size for the BDX transfer. +constexpr uint32_t kMaxBdxBlockSize = 1024; + +// Timeout for the BDX transfer session.. +constexpr System::Clock::Timeout kBdxTimeout = System::Clock::Seconds16(5 * 60); +constexpr TransferRole kBdxRole = TransferRole::kReceiver; +} // namespace + +CHIP_ERROR BDXTransferServer::ListenForSendInit(System::Layer * systemLayer, Messaging::ExchangeManager * exchangeMgr) +{ + VerifyOrReturnError(nullptr != systemLayer, CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrReturnError(nullptr != exchangeMgr, CHIP_ERROR_INVALID_ARGUMENT); + + mSystemLayer = systemLayer; + mExchangeMgr = exchangeMgr; + return mExchangeMgr->RegisterUnsolicitedMessageHandlerForType(MessageType::SendInit, this); +} + +void BDXTransferServer::Shutdown() +{ + VerifyOrReturn(nullptr != mSystemLayer); + VerifyOrReturn(nullptr != mExchangeMgr); + + LogErrorOnFailure(mExchangeMgr->UnregisterUnsolicitedMessageHandlerForType(MessageType::SendInit)); + + mSystemLayer = nullptr; + mExchangeMgr = nullptr; +} + +void BDXTransferServer::HandleTransferSessionOutput(TransferSession::OutputEvent & event) +{ + assertChipStackLockedByCurrentThread(); + + ChipLogDetail(BDX, "Got an event %s", event.ToString(event.EventType)); + + switch (event.EventType) + { + case TransferSession::OutputEventType::kInitReceived: + AbortTransferOnFailure(OnTransferSessionBegin(event)); + break; + case TransferSession::OutputEventType::kStatusReceived: + ChipLogError(BDX, "Got StatusReport %x", static_cast(event.statusData.statusCode)); + LogErrorOnFailure(OnTransferSessionEnd(CHIP_ERROR_INTERNAL)); + break; + case TransferSession::OutputEventType::kInternalError: + LogErrorOnFailure(OnTransferSessionEnd(CHIP_ERROR_INTERNAL)); + break; + case TransferSession::OutputEventType::kTransferTimeout: + LogErrorOnFailure(OnTransferSessionEnd(CHIP_ERROR_TIMEOUT)); + break; + case TransferSession::OutputEventType::kBlockReceived: + AbortTransferOnFailure(OnBlockReceived(event)); + break; + case TransferSession::OutputEventType::kMsgToSend: + LogErrorOnFailure(OnMessageToSend(event)); + + if (event.msgTypeData.HasMessageType(MessageType::BlockAckEOF)) + { + LogErrorOnFailure(OnTransferSessionEnd(CHIP_NO_ERROR)); + } + break; + case TransferSession::OutputEventType::kAckEOFReceived: + case TransferSession::OutputEventType::kNone: + case TransferSession::OutputEventType::kQueryWithSkipReceived: + case TransferSession::OutputEventType::kQueryReceived: + case TransferSession::OutputEventType::kAckReceived: + case TransferSession::OutputEventType::kAcceptReceived: + // Nothing to do. + break; + default: + // Should never happen. + chipDie(); + break; + } +} + +CHIP_ERROR BDXTransferServer::OnMessageReceived(Messaging::ExchangeContext * ec, const PayloadHeader & payloadHeader, + System::PacketBufferHandle && payload) +{ + assertChipStackLockedByCurrentThread(); + + VerifyOrReturnError(ec != nullptr, CHIP_ERROR_INCORRECT_STATE); + + // If we receive a SendInit message, then we prepare for transfer + if (payloadHeader.HasMessageType(MessageType::SendInit)) + { + FabricIndex fabricIndex = ec->GetSessionHandle()->GetFabricIndex(); + NodeId peerNodeId = ec->GetSessionHandle()->GetPeer().GetNodeId(); + VerifyOrReturnError(fabricIndex != kUndefinedFabricIndex, CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrReturnError(peerNodeId != kUndefinedNodeId, CHIP_ERROR_INVALID_ARGUMENT); + + mTransferProxy.SetFabricIndex(fabricIndex); + mTransferProxy.SetPeerNodeId(peerNodeId); + auto flags(TransferControlFlags::kSenderDrive); + ReturnLogErrorOnFailure(Responder::PrepareForTransfer(mSystemLayer, kBdxRole, flags, kMaxBdxBlockSize, kBdxTimeout)); + } + + return TransferFacilitator::OnMessageReceived(ec, payloadHeader, std::move(payload)); +} + +CHIP_ERROR BDXTransferServer::OnMessageToSend(TransferSession::OutputEvent & event) +{ + assertChipStackLockedByCurrentThread(); + + VerifyOrReturnError(mExchangeCtx != nullptr, CHIP_ERROR_INCORRECT_STATE); + + auto & msgTypeData = event.msgTypeData; + bool isStatusReport = msgTypeData.HasMessageType(Protocols::SecureChannel::MsgType::StatusReport); + + // All messages sent from the Sender expect a response, except for a StatusReport which would indicate an error and + // the end of the transfer. + Messaging::SendFlags sendFlags; + VerifyOrDo(isStatusReport, sendFlags.Set(Messaging::SendMessageFlags::kExpectResponse)); + + // If there's an error sending the message, close the exchange by calling Reset. + auto err = mExchangeCtx->SendMessage(msgTypeData.ProtocolId, msgTypeData.MessageType, std::move(event.MsgData), sendFlags); + VerifyOrDo(CHIP_NO_ERROR == err, Reset()); + + return err; +} + +CHIP_ERROR BDXTransferServer::OnTransferSessionBegin(TransferSession::OutputEvent & event) +{ + assertChipStackLockedByCurrentThread(); + VerifyOrReturnError(nullptr != mDelegate, CHIP_ERROR_INCORRECT_STATE); + + ReturnErrorOnFailure(mTransferProxy.Init(&mTransfer)); + return mDelegate->OnTransferBegin(&mTransferProxy); +} + +CHIP_ERROR BDXTransferServer::OnTransferSessionEnd(CHIP_ERROR error) +{ + assertChipStackLockedByCurrentThread(); + VerifyOrReturnError(nullptr != mDelegate, CHIP_ERROR_INCORRECT_STATE); + + LogErrorOnFailure(mDelegate->OnTransferEnd(&mTransferProxy, error)); + Reset(); + return CHIP_NO_ERROR; +} + +CHIP_ERROR BDXTransferServer::OnBlockReceived(TransferSession::OutputEvent & event) +{ + assertChipStackLockedByCurrentThread(); + VerifyOrReturnError(nullptr != mDelegate, CHIP_ERROR_INCORRECT_STATE); + + ByteSpan blockData(event.blockdata.Data, event.blockdata.Length); + return mDelegate->OnTransferData(&mTransferProxy, blockData); +} + +void BDXTransferServer::AbortTransferOnFailure(CHIP_ERROR error) +{ + VerifyOrReturn(CHIP_NO_ERROR != error); + LogErrorOnFailure(error); + LogErrorOnFailure(mTransfer.AbortTransfer(GetBdxStatusCodeFromChipError(error))); +} + +void BDXTransferServer::Reset() +{ + assertChipStackLockedByCurrentThread(); + + Responder::ResetTransfer(); + + if (mExchangeCtx) + { + mIsExchangeClosing = true; + mExchangeCtx->Close(); + mIsExchangeClosing = false; + mExchangeCtx = nullptr; + } + + mTransferProxy.Reset(); +} + +void BDXTransferServer::OnExchangeClosing(Messaging::ExchangeContext * ec) +{ + // This block checks and handles the scenario where the exchange is closed externally (e.g., receiving a StatusReport). + // Continuing to use it could lead to a use-after-free error and such an error might occur when the poll timer triggers and + // OnTransferSessionEnd is called. + // We know it's not just us normally closing the exchange if mIsExchangeClosing is false. + VerifyOrReturn(!mIsExchangeClosing); + mExchangeCtx = nullptr; + LogErrorOnFailure(OnTransferSessionEnd(CHIP_ERROR_INTERNAL)); +} + +} // namespace bdx +} // namespace chip diff --git a/src/protocols/bdx/BdxTransferServer.h b/src/protocols/bdx/BdxTransferServer.h new file mode 100644 index 00000000000000..83da8b97ecb6a8 --- /dev/null +++ b/src/protocols/bdx/BdxTransferServer.h @@ -0,0 +1,115 @@ +/* + * + * 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 "BdxTransferServerDelegate.h" + +#include +#include +#include +#include + +namespace chip { +namespace bdx { + +class BDXTransferServer : public Responder +{ +public: + BDXTransferServer(){}; + + ~BDXTransferServer() { Shutdown(); }; + + CHIP_ERROR ListenForSendInit(System::Layer * systemLayer, Messaging::ExchangeManager * exchangeMgr); + + void Shutdown(); + + void SetDelegate(BDXTransferServerDelegate * delegate) { mDelegate = delegate; } + + /** + * This method handles BDX messages and other TransferSession events. + * + * @param[in] event An OutputEvent that contains output from the TransferSession object. + */ + void HandleTransferSessionOutput(TransferSession::OutputEvent & event) override; + + void OnExchangeClosing(Messaging::ExchangeContext * ec) override; + +protected: + /** + * Called when a BDX message is received over the exchange context + * + * @param[in] ec The exchange context + * + * @param[in] payloadHeader The payload header of the message + * + * @param[in] payload The payload of the message + */ + CHIP_ERROR OnMessageReceived(Messaging::ExchangeContext * ec, const PayloadHeader & payloadHeader, + System::PacketBufferHandle && payload) override; + +private: + /** + * Called to send a BDX MsgToSend message over the exchange + * + * + * @param[in] event The output event to be send + */ + CHIP_ERROR OnMessageToSend(TransferSession::OutputEvent & event); + + /** + * Called to begin the transfer session when an init message has been received + * + * @param[in] event The output event received + */ + CHIP_ERROR OnTransferSessionBegin(TransferSession::OutputEvent & event); + + /** + * Called to end the transfer session when a BlockAckEOF message has been sent over the exchange + * or an error has occurred during the BDX session + * + * @param[in] error The error type + */ + CHIP_ERROR OnTransferSessionEnd(CHIP_ERROR error); + + /** + * Called when a block has been received from the Sender. The block is processed + * and written to a file and a block ack is sent back to the sender. + * + * @param[in] event The output event received + */ + CHIP_ERROR OnBlockReceived(TransferSession::OutputEvent & event); + + /** + * This method is called to reset state. It resets the transfer and cleans up the + * exchange and the fabric index and peer node id. + */ + void Reset(); + + void AbortTransferOnFailure(CHIP_ERROR error); + + System::Layer * mSystemLayer; + Messaging::ExchangeManager * mExchangeMgr; + BDXTransferServerDelegate * mDelegate; + + BDXTransferProxyDiagnosticLog mTransferProxy; + bool mIsExchangeClosing = false; +}; + +} // namespace bdx +} // namespace chip diff --git a/src/protocols/bdx/BdxTransferServerDelegate.h b/src/protocols/bdx/BdxTransferServerDelegate.h new file mode 100644 index 00000000000000..e151ed8c5f09f9 --- /dev/null +++ b/src/protocols/bdx/BdxTransferServerDelegate.h @@ -0,0 +1,53 @@ +/* + * 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 "BdxTransferProxy.h" + +#include + +namespace chip { +namespace bdx { + +class BDXTransferServerDelegate +{ +public: + virtual ~BDXTransferServerDelegate() {} + + /** + * @brief + * This method is invoked when the transfer begins. + */ + virtual CHIP_ERROR OnTransferBegin(BDXTransferProxy * transfer) = 0; + + /** + * @brief + * This method is invoked when the transfer ends. + */ + virtual CHIP_ERROR OnTransferEnd(BDXTransferProxy * transfer, CHIP_ERROR error) = 0; + + /** + * @brief + * This method is invoked when a block is received. + */ + virtual CHIP_ERROR OnTransferData(BDXTransferProxy * transfer, const ByteSpan & data) = 0; +}; + +} // namespace bdx +} // namespace chip diff --git a/src/protocols/bdx/DiagnosticLogs.h b/src/protocols/bdx/DiagnosticLogs.h new file mode 100644 index 00000000000000..60ec2a33080c9c --- /dev/null +++ b/src/protocols/bdx/DiagnosticLogs.h @@ -0,0 +1,32 @@ +/* + * + * 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. + */ + +#pragma once + +namespace chip { +namespace bdx { +namespace DiagnosticLogs { +// Spec mandated max file designator length +static constexpr uint8_t kMaxFileDesignatorLen = 32; + +// Spec mandated max size of the log content field in the Response payload +static constexpr uint16_t kMaxLogContentSize = 1024; + +} // namespace DiagnosticLogs +} // namespace bdx +} // namespace chip diff --git a/src/protocols/bdx/StatusCode.cpp b/src/protocols/bdx/StatusCode.cpp new file mode 100644 index 00000000000000..2f739b5e831de2 --- /dev/null +++ b/src/protocols/bdx/StatusCode.cpp @@ -0,0 +1,41 @@ +/* + * + * 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 "StatusCode.h" + +namespace chip { +namespace bdx { + +StatusCode GetBdxStatusCodeFromChipError(CHIP_ERROR error) +{ + auto status = StatusCode::kUnknown; + + if (error == CHIP_ERROR_INCORRECT_STATE) + { + status = StatusCode::kUnexpectedMessage; + } + else if (error == CHIP_ERROR_INVALID_ARGUMENT) + { + status = StatusCode::kBadMessageContents; + } + + return status; +} + +} // namespace bdx +} // namespace chip diff --git a/src/protocols/bdx/StatusCode.h b/src/protocols/bdx/StatusCode.h new file mode 100644 index 00000000000000..d0e54f8e829098 --- /dev/null +++ b/src/protocols/bdx/StatusCode.h @@ -0,0 +1,47 @@ +/* + * + * 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 + +namespace chip { +namespace bdx { + +enum class StatusCode : uint16_t +{ + kLengthTooLarge = 0x0012, + kLengthTooShort = 0x0013, + kLengthMismatch = 0x0014, + kLengthRequired = 0x0015, + kBadMessageContents = 0x0016, + kBadBlockCounter = 0x0017, + kUnexpectedMessage = 0x0018, + kResponderBusy = 0x0019, + kTransferFailedUnknownError = 0x001F, + kTransferMethodNotSupported = 0x0050, + kFileDesignatorUnknown = 0x0051, + kStartOffsetNotSupported = 0x0052, + kVersionNotSupported = 0x0053, + kUnknown = 0x005F, +}; + +StatusCode GetBdxStatusCodeFromChipError(CHIP_ERROR error); + +} // namespace bdx +} // namespace chip diff --git a/src/protocols/bdx/TransferFacilitator.cpp b/src/protocols/bdx/TransferFacilitator.cpp index f5deb2ea5bd2c4..fbe563187b6fb9 100644 --- a/src/protocols/bdx/TransferFacilitator.cpp +++ b/src/protocols/bdx/TransferFacilitator.cpp @@ -132,5 +132,12 @@ CHIP_ERROR Initiator::InitiateTransfer(System::Layer * layer, TransferRole role, return CHIP_NO_ERROR; } +void Initiator::ResetTransfer() +{ + mTransfer.Reset(); + ChipLogProgress(BDX, "Stop polling for messages"); + mStopPolling = true; +} + } // namespace bdx } // namespace chip diff --git a/src/protocols/bdx/TransferFacilitator.h b/src/protocols/bdx/TransferFacilitator.h index 6dd00e4c0dfab9..97e981ac693ad8 100644 --- a/src/protocols/bdx/TransferFacilitator.h +++ b/src/protocols/bdx/TransferFacilitator.h @@ -58,8 +58,6 @@ class TransferFacilitator : public Messaging::ExchangeDelegate, public Messaging } // Inherited from ExchangeContext - CHIP_ERROR OnMessageReceived(chip::Messaging::ExchangeContext * ec, const chip::PayloadHeader & payloadHeader, - chip::System::PacketBufferHandle && payload) override; void OnResponseTimeout(Messaging::ExchangeContext * ec) override; /** @@ -73,6 +71,10 @@ class TransferFacilitator : public Messaging::ExchangeDelegate, public Messaging virtual void HandleTransferSessionOutput(TransferSession::OutputEvent & event) = 0; protected: + // Inherited from ExchangeContext + CHIP_ERROR OnMessageReceived(chip::Messaging::ExchangeContext * ec, const chip::PayloadHeader & payloadHeader, + chip::System::PacketBufferHandle && payload) override; + /** * The callback for when the poll timer expires. The poll timer regulates how often the TransferSession is polled. */ @@ -120,6 +122,9 @@ class Responder : public TransferFacilitator uint16_t maxBlockSize, System::Clock::Timeout timeout, System::Clock::Timeout pollFreq = TransferFacilitator::kDefaultPollFreq); + /** + * Calls reset on the TransferSession object and stops the poll timer. + */ void ResetTransfer(); }; @@ -145,6 +150,10 @@ class Initiator : public TransferFacilitator CHIP_ERROR InitiateTransfer(System::Layer * layer, TransferRole role, const TransferSession::TransferInitData & initData, System::Clock::Timeout timeout, System::Clock::Timeout pollFreq = TransferFacilitator::kDefaultPollFreq); + /** + * Calls reset on the TransferSession object and stops the poll timer. + */ + void ResetTransfer(); }; } // namespace bdx