diff --git a/CHANGELOG b/CHANGELOG index 7d3271c0..6421181e 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,9 @@ +Unreleased +--------- + +New features and improvements: + +- Added optional test-only instrumentation build option `LIB61850_ENABLE_TEST_API` (OFF by default). When enabled exports `MmsConnection_setFileReadArtificialDelay` (marked with `LIB61850_TEST_API`) to inject artificial delays before MMS file read responses for deterministic ObtainFile request timeout testing. Production builds remain unaffected when disabled. Changes to version 1.6.1 ------------------------ diff --git a/CMakeLists.txt b/CMakeLists.txt index 5ec9a27f..dccccb35 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -58,6 +58,12 @@ option(CONFIG_IEC61850_R_GOOSE "Build with support for R-GOOSE (mbedtls required option(CONFIG_IEC61850_R_SMV "Build with support for R-SMV (mbedtls required)" ON) option(CONFIG_IEC61850_SNTP_CLIENT "Build with SNTP client code" ON) +# Option to enable test-only instrumentation APIs (artificial delays, etc.) +option(LIB61850_ENABLE_TEST_API "Enable test-only instrumentation APIs (e.g. artificial delays)" OFF) +if(LIB61850_ENABLE_TEST_API) + add_compile_definitions(LIB61850_ENABLE_TEST_API) +endif() + set(CONFIG_IEC61850_SG_RESVTMS 300 CACHE STRING "Configure the maximum number of SG RESVTMS") set(CONFIG_REPORTING_DEFAULT_REPORT_BUFFER_SIZE "65536" CACHE STRING "Default buffer size for buffered reports in byte" ) @@ -230,6 +236,12 @@ endif(BUILD_EXAMPLES) add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/src) +# Build tests if requested +option(BUILD_TESTS "Build unit tests" ON) +if(BUILD_TESTS) + add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/tests) +endif(BUILD_TESTS) + install(FILES ${API_HEADERS} DESTINATION include/libiec61850 COMPONENT Development) if(BUILD_PYTHON_BINDINGS) diff --git a/README.md b/README.md index 6fea2ba4..6d3f4337 100644 --- a/README.md +++ b/README.md @@ -168,6 +168,29 @@ Optionally execute the following stop to install the library and header files in `sudo make install` +### Optional test-only instrumentation + +For deterministic testing (e.g. of MMS file ObtainFile request timeout behavior) the library provides a test-only instrumentation API that can be used to modify the standard behavior. This is disabled by default and excluded from production builds. + +Enable it via CMake: + +``` +cmake -DLIB61850_ENABLE_TEST_API=ON .. +``` + +When enabled some test specific API functions are available (exported with the `LIB61850_TEST_API` attribute). + +Usage example: + +```c +#ifdef LIB61850_ENABLE_TEST_API + /* Introduce 250 ms delay before each FileRead response */ + MmsConnection_setFileReadArtificialDelay(conn, 250); +#endif +``` + +If the option is OFF the symbols and related code are omitted entirely and have zero impact on runtime behavior or memory footprint. + ### Build on Windows with Visual Studio @@ -229,6 +252,9 @@ Support and commercial license options are provided by MZ Automation GmbH. Pleas ## Contributing -If you want to contribute to the improvement and development of the library please send me comments, feature requests, bug reports, or patches. For more than trivial contributions I require you to sign a Contributor License Agreement. Please contact info@libiec61850.com. +If you want to contribute to the improvement and development of the library please send comments, feature requests, bug reports, or patches. + +More than trivial contributions require you to sign a Contributor License Agreement. Please contact info@libiec61850.com before you plan such a contribution. + +Please note: pull requests to the github repository (https://github.com/mz-automation/libiec61850) are not accepted. The github repository serves as a read-only archive. -Please don't send pull requests before signing the Contributor License Agreement! Such pull requests may be silently ignored. diff --git a/src/common/inc/libiec61850_common_api.h b/src/common/inc/libiec61850_common_api.h index a233ccc9..4b0ebf3a 100644 --- a/src/common/inc/libiec61850_common_api.h +++ b/src/common/inc/libiec61850_common_api.h @@ -28,6 +28,7 @@ #if defined _WIN32 || defined __CYGWIN__ #ifdef EXPORT_FUNCTIONS_FOR_DLL #define LIB61850_API __declspec(dllexport) + #define LIB61850_TEST_API __declspec(dllexport) #else #define LIB61850_API #endif @@ -36,10 +37,12 @@ #else #if __GNUC__ >= 4 #define LIB61850_API __attribute__ ((visibility ("default"))) + #define LIB61850_TEST_API __attribute__ ((visibility ("default"))) #define LIB61850_INTERNAL __attribute__ ((visibility ("hidden"))) #else #define LIB61850_API #define LIB61850_INTERNAL + #define LIB61850_TEST_API #endif #endif diff --git a/src/mms/inc/mms_client_connection.h b/src/mms/inc/mms_client_connection.h index 73e61cb1..09ffda1d 100644 --- a/src/mms/inc/mms_client_connection.h +++ b/src/mms/inc/mms_client_connection.h @@ -151,6 +151,24 @@ MmsConnection_setRawMessageHandler(MmsConnection self, MmsRawMessageHandler hand LIB61850_API void MmsConnection_setFilestoreBasepath(MmsConnection self, const char* basepath); +/* Test-only API: available only when LIB61850_ENABLE_TEST_API is defined at build time */ +#ifdef LIB61850_ENABLE_TEST_API +/** + * \brief (TEST ONLY) Set an artificial delay (in ms) for sending file-read responses (OBTAIN-FILE upload path) + * + * This is intended for testing timeout behavior. When set > 0 the client waits the specified + * time before replying to a file-read-request from the server, potentially triggering the server's + * request timeout. + * + * Requires CMake option LIB61850_ENABLE_TEST_API=ON. + * + * \param self MmsConnection instance to operate on + * \param delayMs delay in milliseconds (0 disables) + */ +LIB61850_TEST_API void +MmsConnection_setFileReadArtificialDelay(MmsConnection self, uint32_t delayMs); +#endif + /** * \brief Set the request timeout in ms for this connection * diff --git a/src/mms/inc_private/mms_client_internal.h b/src/mms/inc_private/mms_client_internal.h index 5a7b2242..bf233908 100644 --- a/src/mms/inc_private/mms_client_internal.h +++ b/src/mms/inc_private/mms_client_internal.h @@ -141,6 +141,10 @@ struct sMmsConnection { char* filestoreBasepath; #endif +#if defined(LIB61850_ENABLE_TEST_API) + /* TEST ONLY: Artificial delay (ms) before sending file-read responses (instrumentation) */ + uint32_t fileReadArtificialDelayMs; +#endif #endif /* (MMS_OBTAIN_FILE_SERVICE == 1) */ }; diff --git a/src/mms/iso_mms/client/mms_client_connection.c b/src/mms/iso_mms/client/mms_client_connection.c index 3522fda0..6eb3f76a 100644 --- a/src/mms/iso_mms/client/mms_client_connection.c +++ b/src/mms/iso_mms/client/mms_client_connection.c @@ -1649,6 +1649,10 @@ MmsConnection_createInternal(TLSConfiguration tlsConfig, bool createThread) self->connectionHandlingThread = NULL; self->connectionThreadRunning = false; #endif + +#if defined(LIB61850_ENABLE_TEST_API) + self->fileReadArtificialDelayMs = 0; /* default no artificial delay */ +#endif } return self; @@ -1737,6 +1741,19 @@ MmsConnection_setFilestoreBasepath(MmsConnection self, const char* basepath) #endif } +#if defined(LIB61850_ENABLE_TEST_API) +void +MmsConnection_setFileReadArtificialDelay(MmsConnection self, uint32_t delayMs) +{ +#if (MMS_OBTAIN_FILE_SERVICE == 1) + self->fileReadArtificialDelayMs = delayMs; +#else + (void)self; + (void)delayMs; +#endif +} +#endif + char* MmsConnection_getFilestoreBasepath(MmsConnection self) { diff --git a/src/mms/iso_mms/client/mms_client_files.c b/src/mms/iso_mms/client/mms_client_files.c index 0d8d5006..aae6928f 100644 --- a/src/mms/iso_mms/client/mms_client_files.c +++ b/src/mms/iso_mms/client/mms_client_files.c @@ -25,6 +25,7 @@ #include "stack_config.h" #include "mms_common.h" #include "mms_client_connection.h" +#include "hal_thread.h" #include "byte_buffer.h" #include "mms_client_internal.h" @@ -198,6 +199,11 @@ mmsClient_handleFileReadRequest( if (frsm->obtainRequest) frsm->obtainRequest->timeout = Hal_getTimeInMs() + connection->requestTimeout; +#if defined(LIB61850_ENABLE_TEST_API) + if (connection->fileReadArtificialDelayMs > 0) + Thread_sleep(connection->fileReadArtificialDelayMs); +#endif + mmsMsg_createFileReadResponse(connection->parameters.maxPduSize, invokeId, response, frsm); } else