From 469ea8f50bba6fc73ada82da04688d879dd171cb Mon Sep 17 00:00:00 2001 From: Daniel Adam Date: Fri, 19 May 2023 21:06:15 +0200 Subject: [PATCH] Improve monitoring of DTLS peers' activity using monotonic timer The previous implementation of monitoring DTLS peers' activity relied on the absolute system time to track the last activity timestamp. However, this approach had the unintended consequence of being affected by changes in the system time. For instance, modifying the system time by an interval equivalent to the DTLS inactivity timeout would trigger a false inactivity timeout and result in the closure of the DTLS session. To address this issue, the monitoring mechanism has been updated to utilize a monotonic timer. This timer is independent of system time changes and provides a more accurate measure of the duration of inactivity. By using a monotonic timer, the DTLS session will only be closed if no activity occurs within the specified timeout period, regardless of any changes in the system time. This refactor improves the reliability and accuracy of DTLS peer monitoring, ensuring that the DTLS sessions are closed based on actual inactivity rather than being influenced by system time adjustments. --- .github/workflows/cmake-linux.yml | 15 +- .github/workflows/sonar-cloud-analysis.yml | 11 +- .github/workflows/unit-test-with-cfg.yml | 19 +- CMakeLists.txt | 87 +++++++--- api/oc_endpoint.c | 10 +- api/oc_endpoint_internal.h | 6 +- api/oc_uuid.c | 19 +- api/unittest/buffertest.cpp | 5 + api/unittest/eptest.cpp | 7 +- api/unittest/ocapitest.cpp | 58 ++++--- include/oc_log.h | 2 +- port/oc_log_internal.h | 2 +- port/windows/clock.c | 71 ++++++-- port/windows/oc_clock_internal.h | 41 +++++ security/oc_tls.c | 8 +- security/unittest/dtlstest.cpp | 162 +++++++++++++++++ security/unittest/tls_peer_test.cpp | 91 ---------- tests/gtest/tls/DTLS.cpp | 50 ++++++ tests/gtest/tls/DTLS.h | 40 +++++ tests/gtest/tls/DTLSClient.cpp | 28 ++- tests/gtest/tls/DTLSClient.h | 9 +- tests/libfaketime/unittest/clocktest.cpp | 84 +++++++++ tests/libfaketime/unittest/dtlstest.cpp | 192 +++++++++++++++++++++ tests/libfaketime/unittest/utility.cpp | 79 +++++++++ tests/libfaketime/unittest/utility.h | 38 ++++ 25 files changed, 934 insertions(+), 200 deletions(-) create mode 100644 port/windows/oc_clock_internal.h create mode 100644 security/unittest/dtlstest.cpp create mode 100644 tests/gtest/tls/DTLS.cpp create mode 100644 tests/gtest/tls/DTLS.h create mode 100644 tests/libfaketime/unittest/clocktest.cpp create mode 100644 tests/libfaketime/unittest/dtlstest.cpp create mode 100644 tests/libfaketime/unittest/utility.cpp create mode 100644 tests/libfaketime/unittest/utility.h diff --git a/.github/workflows/cmake-linux.yml b/.github/workflows/cmake-linux.yml index 13299473fc..b298abc47a 100644 --- a/.github/workflows/cmake-linux.yml +++ b/.github/workflows/cmake-linux.yml @@ -49,8 +49,8 @@ jobs: # ipv4 on, tcp on, pki off - args: "-DOC_IPV4_ENABLED=ON -DOC_TCP_ENABLED=ON -DOC_PKI_ENABLED=OFF" # cloud on (ipv4+tcp on), collections create on, maintenance resource on - - args: "-DOC_CLOUD_ENABLED=ON -DOC_COLLECTIONS_IF_CREATE_ENABLED=ON " - # debug on, well-known core resource on + - args: "-DOC_CLOUD_ENABLED=ON -DOC_COLLECTIONS_IF_CREATE_ENABLED=ON" + # debug on - args: "-DOC_DEBUG_ENABLED=ON" # debug on, cloud on (ipv4+tcp on) - args: "-DOC_CLOUD_ENABLED=ON -DOC_DEBUG_ENABLED=ON " @@ -60,8 +60,8 @@ jobs: - args: "-DOC_SECURITY_ENABLED=OFF -DOC_TCP_ENABLED=ON" # secure off, ipv4 on, tcp on - args: "-DOC_SECURITY_ENABLED=OFF -DOC_TCP_ENABLED=ON -DOC_IPV4_ENABLED=ON" - # collection create if on, push notification on - - args: "-DOC_COLLECTIONS_IF_CREATE_ENABLED=ON" + # /oic/res observable on, rep realloc on + - args: "-DOC_DISCOVERY_RESOURCE_OBSERVABLE_ENABLED=ON -DOC_REPRESENTATION_REALLOC_ENCODING_ENABLED=ON" # everything off (dynamic allocation off, secure off, pki off, idd off, oscore off, well-known core resource off, software update off, push notifications off, plgd-time off) - args: "-DOC_DYNAMIC_ALLOCATION_ENABLED=OFF -DOC_SECURITY_ENABLED=OFF -DOC_PKI_ENABLED=OFF -DOC_IDD_API_ENABLED=OFF -DOC_OSCORE_ENABLED=OFF -DOC_WKCORE_ENABLED=OFF -DOC_SOFTWARE_UPDATE_ENABLED=OFF -DOC_MNT_ENABLED=OFF -DOC_PUSH_ENABLED=OFF -DPLGD_DEV_TIME_ENABLED=OFF" uses: ./.github/workflows/unit-test-with-cfg.yml @@ -91,14 +91,20 @@ jobs: include: # address sanitizer - args: -DOC_ASAN_ENABLED=ON + install_faketime: true # leak sanitizer - args: -DOC_LSAN_ENABLED=ON + # there is some linker issue when LSAN and faketime are used together + install_faketime: false # thread sanitizer - args: -DOC_TSAN_ENABLED=ON + install_faketime: true # undefined behaviour sanitizer - args: -DOC_UBSAN_ENABLED=ON + install_faketime: true # TODO: update gtest # - args: -DOC_MSAN_ENABLED=ON + # install_faketime: true uses: ./.github/workflows/unit-test-with-cfg.yml with: build_args: -DOC_CLOUD_ENABLED=ON -DOC_COLLECTIONS_IF_CREATE_ENABLED=ON -DOC_MNT_ENABLED=ON -DOC_WKCORE_ENABLED=ON -DOC_SOFTWARE_UPDATE_ENABLED=ON -DOC_PUSH_ENABLED=ON -DOC_RESOURCE_ACCESS_IN_RFOTM_ENABLED=ON -DPLGD_DEV_TIME_ENABLED=ON ${{ matrix.args }} @@ -107,3 +113,4 @@ jobs: coverage: false install_mbedtls: ${{ github.event_name == 'workflow_dispatch' && inputs.install_mbedtls }} install_tinycbor: ${{ github.event_name == 'workflow_dispatch' && inputs.install_tinycbor }} + install_faketime: ${{ matrix.install_faketime }} diff --git a/.github/workflows/sonar-cloud-analysis.yml b/.github/workflows/sonar-cloud-analysis.yml index 95ae77a581..ff00d02664 100644 --- a/.github/workflows/sonar-cloud-analysis.yml +++ b/.github/workflows/sonar-cloud-analysis.yml @@ -21,10 +21,13 @@ jobs: fail-fast: false matrix: include: + # cloud (ipv4+tcp) on, collection create on, push on, rfotm on - build_args: "-DOC_CLOUD_ENABLED=ON -DOC_COLLECTIONS_IF_CREATE_ENABLED=ON -DOC_PUSH_ENABLED=ON -DOC_RESOURCE_ACCESS_IN_RFOTM_ENABLED=ON" - # dynamic allocation off - - build_args: "-DOC_DYNAMIC_ALLOCATION_ENABLED=OFF -DOC_RESOURCE_ACCESS_IN_RFOTM_ENABLED=ON" - # security off + # ipv6 dns on, oscore off, rep realloc on + - build_args: "-DOC_DNS_LOOKUP_IPV6_ENABLED=ON -DOC_OSCORE_ENABLED=OFF -DOC_REP_ENCODING_REALLOC=ON" + # ipv4 on, tcp on, dynamic allocation off, rfotm on + - build_args: "-DOC_IPV4_ENABLED=ON -DOC_TCP_ENABLED=ON -DOC_DYNAMIC_ALLOCATION_ENABLED=OFF -DOC_RESOURCE_ACCESS_IN_RFOTM_ENABLED=ON" + # security off, cloud (ipv4+tcp), collection create on, push on - build_args: "-DOC_SECURITY_ENABLED=OFF -DOC_CLOUD_ENABLED=ON -DOC_COLLECTIONS_IF_CREATE_ENABLED=ON -DOC_PUSH_ENABLED=ON" uses: ./.github/workflows/unit-test-with-cfg.yml @@ -37,7 +40,7 @@ jobs: uses: ./.github/workflows/plgd-device-test-with-cfg.yml with: name: cloud-server - build_args: "-DOC_COLLECTIONS_IF_CREATE_ENABLED=ON -DOC_MNT_ENABLED=ON -DOC_OSCORE_ENABLED=OFF -DOC_COVERAGE_ENABLED=ON -DPLGD_DEV_TIME_ENABLED=ON" + build_args: "-DOC_COLLECTIONS_IF_CREATE_ENABLED=ON -DOC_MNT_ENABLED=ON -DOC_OSCORE_ENABLED=OFF -DPLGD_DEV_TIME_ENABLED=ON -DOC_COVERAGE_ENABLED=ON " build_type: Debug # try with SHA384 cert_signature_algorithm: ECDSA-SHA384 diff --git a/.github/workflows/unit-test-with-cfg.yml b/.github/workflows/unit-test-with-cfg.yml index 8dc3b5eea6..70aa0279ad 100644 --- a/.github/workflows/unit-test-with-cfg.yml +++ b/.github/workflows/unit-test-with-cfg.yml @@ -23,6 +23,10 @@ on: type: boolean required: false default: false + install_faketime: + type: boolean + required: false + default: true install_mbedtls: type: boolean required: false @@ -47,11 +51,24 @@ jobs: run: | echo compiler='-DCMAKE_CXX_COMPILER=clang++ -DCMAKE_C_COMPILER=clang' >> $GITHUB_OUTPUT + - name: Checkout libfaketime + uses: actions/checkout@v3 + with: + repository: wolfcw/libfaketime + path: libfaketime + + - name: Install libfaketime + if: ${{ inputs.install_faketime }} + working-directory: libfaketime + run: | + make FAKETIME_COMPILE_CFLAGS="-DFAKE_SETTIME" + sudo make install + - name: Install mbedTLS if: ${{ inputs.install_mbedtls }} run: | mkdir build_mbedtls && cd build_mbedtls - cmake -DCMAKE_VERBOSE_MAKEFILE=ON -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} ${{ steps.cmake_flags.outputs.compiler }} ${{ inputs.build_args }} -DBUILD_TESTING=OFF .. + cmake -DCMAKE_VERBOSE_MAKEFILE=ON -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} ${{ steps.cmake_flags.outputs.compiler }} ${{ inputs.build_args }} -DBUILD_TESTING=ON .. OC_SECURITY_ENABLED=$(cmake -LA -N . | grep OC_SECURITY_ENABLED | cut -d "=" -f2) if [ "${OC_SECURITY_ENABLED}" = "ON" ]; then make mbedtls mbedx509 mbedcrypto diff --git a/CMakeLists.txt b/CMakeLists.txt index e8ae1163cc..9eb16e3b95 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -255,8 +255,9 @@ elseif(OC_LOG_MAXIMUM_LOG_LEVEL STREQUAL "TRACE") else() message(FATAL_ERROR "Invalid OC_LOG_MAXIMUM_LOG_LEVEL: ${OC_LOG_MAXIMUM_LOG_LEVEL}") endif() -list(APPEND PRIVATE_COMPILE_DEFINITIONS "OC_LOG_MAXIMUM_LEVEL=${OC_LOG_MAXIMUM_LEVEL}") -list(APPEND TEST_COMPILE_DEFINITIONS "OC_LOG_MAXIMUM_LEVEL=${OC_LOG_MAXIMUM_LEVEL}") +# clang-tidy triggers bugprone-macro-parentheses if the value is not in () +list(APPEND PRIVATE_COMPILE_DEFINITIONS "OC_LOG_MAXIMUM_LEVEL=(${OC_LOG_MAXIMUM_LEVEL})") +list(APPEND TEST_COMPILE_DEFINITIONS "OC_LOG_MAXIMUM_LEVEL=(${OC_LOG_MAXIMUM_LEVEL})") if(OC_PUSH_ENABLED) list(APPEND PUBLIC_COMPILE_DEFINITIONS "OC_PUSH") @@ -833,13 +834,17 @@ if(BUILD_TESTING AND(UNIX OR MINGW)) set(OC_UNITTESTS) # Helper macro to build unit test - macro(package_add_test TESTNAME) - add_executable(${TESTNAME} ${ARGN}) - target_compile_options(${TESTNAME} PRIVATE ${TEST_COMPILE_OPTIONS}) - target_compile_features(${TESTNAME} PRIVATE cxx_nullptr) - target_compile_definitions(${TESTNAME} PRIVATE ${PUBLIC_COMPILE_DEFINITIONS} ${TEST_COMPILE_DEFINITIONS}) - target_include_directories(${TESTNAME} SYSTEM PRIVATE ${PROJECT_SOURCE_DIR}/deps/gtest/include) - target_include_directories(${TESTNAME} PRIVATE + macro(oc_package_add_test) + set(options) + set(oneValueArgs TARGET) + set(multiValueArgs ENVIRONMENT SOURCES) + cmake_parse_arguments(OC_PACKAGE_ADD_TEST "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + add_executable(${OC_PACKAGE_ADD_TEST_TARGET} ${OC_PACKAGE_ADD_TEST_SOURCES}) + target_compile_options(${OC_PACKAGE_ADD_TEST_TARGET} PRIVATE ${TEST_COMPILE_OPTIONS}) + target_compile_definitions(${OC_PACKAGE_ADD_TEST_TARGET} PRIVATE ${PUBLIC_COMPILE_DEFINITIONS} ${TEST_COMPILE_DEFINITIONS}) + target_include_directories(${OC_PACKAGE_ADD_TEST_TARGET} SYSTEM PRIVATE ${PROJECT_SOURCE_DIR}/deps/gtest/include) + target_include_directories(${OC_PACKAGE_ADD_TEST_TARGET} PRIVATE ${PROJECT_SOURCE_DIR} ${PROJECT_SOURCE_DIR}/include ${PORT_INCLUDE_DIR} @@ -847,45 +852,49 @@ if(BUILD_TESTING AND(UNIX OR MINGW)) ) if(OC_SECURITY_ENABLED) - target_include_directories(${TESTNAME} PRIVATE + target_include_directories(${OC_PACKAGE_ADD_TEST_TARGET} PRIVATE ${PROJECT_SOURCE_DIR}/security ) endif() if(OC_CLOUD_ENABLED) - target_include_directories(${TESTNAME} PRIVATE ${PROJECT_SOURCE_DIR}/api/cloud) + target_include_directories(${OC_PACKAGE_ADD_TEST_TARGET} PRIVATE ${PROJECT_SOURCE_DIR}/api/cloud) endif() - target_link_libraries(${TESTNAME} PRIVATE ${TEST_LINK_LIBS}) + target_link_libraries(${OC_PACKAGE_ADD_TEST_TARGET} PRIVATE ${TEST_LINK_LIBS}) if(OC_COMPILER_IS_GCC OR OC_COMPILER_IS_CLANG) - target_link_libraries(${TESTNAME} PRIVATE "-Wl,--unresolved-symbols=ignore-in-shared-libs") + target_link_libraries(${OC_PACKAGE_ADD_TEST_TARGET} PRIVATE "-Wl,--unresolved-symbols=ignore-in-shared-libs") endif() - add_test(NAME ${TESTNAME} COMMAND ${TESTNAME}) - set_target_properties(${TESTNAME} PROPERTIES FOLDER unittests) - set_tests_properties(${TESTNAME} PROPERTIES LABELS oc-unittest) + add_test(NAME ${OC_PACKAGE_ADD_TEST_TARGET} COMMAND ${OC_PACKAGE_ADD_TEST_TARGET}) - list(APPEND OC_UNITTESTS ${TESTNAME}) + set_property(TEST ${OC_PACKAGE_ADD_TEST_TARGET} PROPERTY FOLDER unittests) + set_property(TEST ${OC_PACKAGE_ADD_TEST_TARGET} PROPERTY LABELS oc-unittest) + if (OC_PACKAGE_ADD_TEST_ENVIRONMENT) + set_property(TEST ${OC_PACKAGE_ADD_TEST_TARGET} PROPERTY ENVIRONMENT "${OC_PACKAGE_ADD_TEST_ENVIRONMENT}") + endif() + + list(APPEND OC_UNITTESTS ${OC_PACKAGE_ADD_TEST_TARGET}) endmacro() file(GLOB COMMONTEST_SRC tests/gtest/*.cpp tests/gtest/tls/*.cpp) # Unit tests file(GLOB APITEST_SRC api/unittest/*.cpp api/client/unittest/*.cpp) - package_add_test(apitest ${COMMONTEST_SRC} ${APITEST_SRC}) + oc_package_add_test(TARGET apitest SOURCES ${COMMONTEST_SRC} ${APITEST_SRC}) file(GLOB TIMESTAMPTEST_SRC api/c-timestamp/unittest/*.cpp) - package_add_test(timestamptest ${TIMESTAMPTEST_SRC}) + oc_package_add_test(TARGET timestamptest SOURCES ${TIMESTAMPTEST_SRC}) file(GLOB PLATFORMTEST_SRC port/unittest/*.cpp) file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/storage_test) - package_add_test(platformtest ${COMMONTEST_SRC} ${PLATFORMTEST_SRC}) + oc_package_add_test(TARGET platformtest SOURCES ${COMMONTEST_SRC} ${PLATFORMTEST_SRC}) file(GLOB MESSAGINGTEST_SRC messaging/coap/unittest/*.cpp) - package_add_test(messagingtest ${MESSAGINGTEST_SRC}) + oc_package_add_test(TARGET messagingtest SOURCES ${MESSAGINGTEST_SRC}) if(OC_SECURITY_ENABLED) file(GLOB SECURITYTEST_SRC security/unittest/*.cpp) - package_add_test(securitytest ${COMMONTEST_SRC} ${SECURITYTEST_SRC}) + oc_package_add_test(TARGET securitytest SOURCES ${COMMONTEST_SRC} ${SECURITYTEST_SRC}) file(COPY ${PROJECT_SOURCE_DIR}/apps/pki_certs DESTINATION ${CMAKE_CURRENT_BINARY_DIR} @@ -896,7 +905,39 @@ if(BUILD_TESTING AND(UNIX OR MINGW)) if(OC_CLOUD_ENABLED) file(GLOB CLOUDTEST_SRC api/cloud/unittest/*.cpp) file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/storage_cloud) - package_add_test(cloudtest ${COMMONTEST_SRC} ${CLOUDTEST_SRC}) + oc_package_add_test(TARGET cloudtest SOURCES ${COMMONTEST_SRC} ${CLOUDTEST_SRC}) + endif() + + if(UNIX) + # install https://github.com/wolfcw/libfaketime for this test suit to run + find_library(FAKETIME_LIBRARY + NAMES libfaketimeMT.so.1 + PATH_SUFFIXES faketime + ) + if (FAKETIME_LIBRARY) + file(GLOB FAKETIMETEST_SRC tests/libfaketime/unittest/*.cpp) + oc_package_add_test( + TARGET faketimetest + SOURCES ${COMMONTEST_SRC} ${FAKETIMETEST_SRC} + ENVIRONMENT "ASAN_OPTIONS=verify_asan_link_order=0" + "LD_PRELOAD=/usr/local/lib/faketime/libfaketimeMT.so.1" + "FAKETIME_DONT_FAKE_MONOTONIC=1" + "FAKETIME_TIMESTAMP_FILE=" + "FAKETIME_UPDATE_TIMESTAMP_FILE=" + "FAKETIME_DONT_RESET=" + "FAKETIME_NO_CACHE=" + "FAKETIME_CACHE_DURATION=" + ) + target_link_libraries(faketimetest PRIVATE ${FAKETIME_LIBRARY}) + endif() + endif() + + if(OC_FAKETIMETEST_FORCED) + file(GLOB FAKETIMETEST_SRC tests/libfaketime/unittest/*.cpp) + oc_package_add_test( + TARGET faketimetest + SOURCES ${COMMONTEST_SRC} ${FAKETIMETEST_SRC} + ) endif() add_custom_target( diff --git a/api/oc_endpoint.c b/api/oc_endpoint.c index ec6b18d7a1..1f88925910 100644 --- a/api/oc_endpoint.c +++ b/api/oc_endpoint.c @@ -31,10 +31,10 @@ #define OC_IPV6_ADDRLEN (16) #define OC_IPV4_ADDRLEN (4) -// IPv6 address + 3 ('[',']' and ':') + 5 (uint16_t for port) -#define OC_IPV6_MINSTRLEN (OC_IPV6_ADDRSTRLEN + 8) -// IPv4 address + 1 (':') + 5 (uint16_t for port) -#define OC_IPV4_MINSTRLEN (OC_IPV4_ADDRSTRLEN + 6) +// Max. IPv6 address + 3 ('[',']' and ':') + 5 (uint16_t for port) +#define OC_IPV6_MAXSTRLEN (OC_IPV6_MAXADDRSTRLEN + 8) +// Max. IPv4 address + 1 (':') + 5 (uint16_t for port) +#define OC_IPV4_MAXSTRLEN (OC_IPV4_MAXADDRSTRLEN + 6) OC_MEMB(oc_endpoints_s, oc_endpoint_t, OC_MAX_NUM_ENDPOINTS); @@ -121,7 +121,7 @@ oc_endpoint_to_string(const oc_endpoint_t *endpoint, oc_string_t *endpoint_str) return -1; } - char ip[OC_IPV6_MINSTRLEN] = { 0 }; + char ip[OC_IPV6_MAXSTRLEN] = { 0 }; if (oc_endpoint_to_cstring(endpoint, ip, OC_ARRAY_SIZE(ip)) != 0) { return -1; } diff --git a/api/oc_endpoint_internal.h b/api/oc_endpoint_internal.h index d7267416a4..da35284632 100644 --- a/api/oc_endpoint_internal.h +++ b/api/oc_endpoint_internal.h @@ -28,8 +28,10 @@ extern "C" { #endif -#define OC_IPV6_ADDRSTRLEN (46) -#define OC_IPV4_ADDRSTRLEN (16) +// maximal length of a valid IPv6 address +#define OC_IPV6_MAXADDRSTRLEN (46) +// maximal length of a valid IPv4 address +#define OC_IPV4_MAXADDRSTRLEN (16) #define OC_SCHEME_COAP "coap://" #define OC_SCHEME_COAPS "coaps://" diff --git a/api/oc_uuid.c b/api/oc_uuid.c index 71ca8218cb..f8c49dc760 100644 --- a/api/oc_uuid.c +++ b/api/oc_uuid.c @@ -18,6 +18,7 @@ #include "oc_uuid.h" #include "port/oc_random.h" +#include "util/oc_macros.h" #include #include #include @@ -71,13 +72,15 @@ oc_str_to_uuid(const char *str, oc_uuid_t *uuid) c |= 0x0f; break; } - } else + } else { c |= str[i] - 48; + } if ((j + 1) * 2 == k) { uuid->id[j++] = c; c = 0; - } else + } else { c = c << 4; + } k++; } } @@ -85,19 +88,19 @@ oc_str_to_uuid(const char *str, oc_uuid_t *uuid) void oc_uuid_to_str(const oc_uuid_t *uuid, char *buffer, size_t buflen) { - int i, j = 0; - if (buflen < OC_UUID_LEN || !uuid) + if (buflen < OC_UUID_LEN || !uuid) { return; + } if (uuid->id[0] == '*') { - uint8_t zeros[15] = { 0 }; - if (memcmp(&uuid->id[1], zeros, 15) == 0) { + uint8_t zeros[OC_ARRAY_SIZE(uuid->id) - 1] = { 0 }; + if (memcmp(&uuid->id[1], zeros, OC_ARRAY_SIZE(zeros)) == 0) { memset(buffer, 0, buflen); buffer[0] = '*'; buffer[1] = '\0'; return; } } - for (i = 0; i < 16; i++) { + for (size_t i = 0, j = 0; i < OC_ARRAY_SIZE(uuid->id); ++i) { switch (i) { case 4: case 6: @@ -119,7 +122,7 @@ oc_gen_uuid(oc_uuid_t *uuid) for (unsigned i = 0; i < 4; i++) { r = oc_random_value(); - memcpy((uint8_t *)&uuid->id[i * 4UL], (uint8_t *)&r, sizeof(r)); + memcpy(&uuid->id[i * 4UL], (uint8_t *)&r, sizeof(r)); } /* From RFC 4122 diff --git a/api/unittest/buffertest.cpp b/api/unittest/buffertest.cpp index 38b0a35902..b058a28866 100644 --- a/api/unittest/buffertest.cpp +++ b/api/unittest/buffertest.cpp @@ -20,6 +20,8 @@ #include "api/oc_ri_internal.h" #include "oc_buffer.h" #include "oc_config.h" +#include "port/oc_network_event_handler_internal.h" +#include "util/oc_features.h" #include "util/oc_memb.h" #include "util/oc_process_internal.h" @@ -39,8 +41,11 @@ class TestMessage : public testing::Test { oc_memb_init(&oc_test_messages); oc_set_buffers_avail_cb(onIncomingBufferAvailable); oc_memb_set_buffers_avail_cb(&oc_test_messages, onTestBufferAvailable); + oc_network_event_handler_mutex_init(); } + static void TearDownTestCase() { oc_network_event_handler_mutex_destroy(); } + void SetUp() override { incomingBufferAvailableCount_ = -1; diff --git a/api/unittest/eptest.cpp b/api/unittest/eptest.cpp index 75fd68c4b1..a985a8b2b9 100644 --- a/api/unittest/eptest.cpp +++ b/api/unittest/eptest.cpp @@ -734,7 +734,7 @@ TEST_F(TestEndpoint, ListCopy) TEST_F(TestEndpoint, EndpointHostInvalid) { oc_endpoint_t ep{}; - std::array buffer{}; + std::array buffer{}; EXPECT_EQ(-1, oc_endpoint_host(&ep, buffer.data(), buffer.size())); } @@ -771,7 +771,7 @@ TEST_F(TestEndpoint, EndpointHost) for (size_t i = 0; i < addrs.size(); ++i) { oc_endpoint_t ep = oc::endpoint::FromString(addrs[i]); - std::array buffer{}; + std::array buffer{}; EXPECT_LT(0, oc_endpoint_host(&ep, buffer.data(), buffer.size())); EXPECT_STREQ(expected[i].c_str(), buffer.data()); } @@ -792,7 +792,6 @@ TEST_F(TestEndpoint, EndpointHost) #ifdef OC_SECURITY "1.2.3.4", #endif /* OC_SECURITY */ - #ifdef OC_TCP "192.193.194.195", #endif /* OC_TCP */ @@ -801,7 +800,7 @@ TEST_F(TestEndpoint, EndpointHost) for (size_t i = 0; i < addrs.size(); ++i) { oc_endpoint_t ep = oc::endpoint::FromString(addrs[i]); - std::array buffer{}; + std::array buffer{}; EXPECT_LT(0, oc_endpoint_host(&ep, buffer.data(), buffer.size())); EXPECT_STREQ(expected[i].c_str(), buffer.data()); } diff --git a/api/unittest/ocapitest.cpp b/api/unittest/ocapitest.cpp index 0acbce1a0b..88eeb8852e 100644 --- a/api/unittest/ocapitest.cpp +++ b/api/unittest/ocapitest.cpp @@ -693,6 +693,16 @@ TEST_F(TestServerClient, DiscoverDeviceIDWithResources) #if !defined(OC_SECURITY) || defined(OC_HAS_FEATURE_RESOURCE_ACCESS_IN_RFOTM) +#ifdef _WIN32 +// windows implementation or github testing machine seems to be slower, +// resulting in failures, so we use longer timeouts +static constexpr uint16_t kTimeout = 4; +static constexpr uint16_t kShortTimeout = 2; +#else /* !_WIN32 */ +static constexpr uint16_t kTimeout = 2; +static constexpr uint16_t kShortTimeout = 1; +#endif /* _WIN32 */ + static void failResponse(oc_client_response_t *) { @@ -717,21 +727,21 @@ TEST_F(TestServerClient, GetWithTimeout) }; int expected = ApiHelper::s_TestResource.onGet.response; - doGet(ApiHelper::s_TestResource, onGetDevice, 2); - ApiHelper::poolEvents(4); + doGet(ApiHelper::s_TestResource, onGetDevice, kTimeout); + ApiHelper::poolEvents(kTimeout * 2); EXPECT_EQ(expected, code); ApiHelper::s_TestResource.onGet.response = -1; // disable sending of response expected = OC_REQUEST_TIMEOUT; - doGet(ApiHelper::s_TestResource, onGetDevice, 1); - ApiHelper::poolEvents(2); + doGet(ApiHelper::s_TestResource, onGetDevice, kShortTimeout); + ApiHelper::poolEvents(kShortTimeout * 2); EXPECT_EQ(expected, code); // test clean-up code = OC_STATUS_OK; - doGet(ApiHelper::s_TestResource, failResponse, 1); + doGet(ApiHelper::s_TestResource, failResponse, kShortTimeout); ApiHelper::getAndRemoveClientCb(ApiHelper::s_TestResource, OC_GET); - ApiHelper::poolEvents(2); // wait for timeout + ApiHelper::poolEvents(kShortTimeout * 2); // wait for timeout EXPECT_EQ(OC_STATUS_OK, code); } @@ -753,20 +763,20 @@ TEST_F(TestServerClient, DeleteWithTimeout) }; int expected = ApiHelper::s_TestResource.onDelete.response; - doDelete(ApiHelper::s_TestResource, onDeleteDevice, 2); - ApiHelper::poolEvents(4); + doDelete(ApiHelper::s_TestResource, onDeleteDevice, kTimeout); + ApiHelper::poolEvents(kTimeout * 2); EXPECT_EQ(expected, code); ApiHelper::s_SwitchResource.onDelete.response = -1; // disable response - doDelete(ApiHelper::s_SwitchResource, onDeleteDevice, 1); - ApiHelper::poolEvents(2); + doDelete(ApiHelper::s_SwitchResource, onDeleteDevice, kShortTimeout); + ApiHelper::poolEvents(kShortTimeout * 2); EXPECT_EQ(OC_REQUEST_TIMEOUT, code); // test clean-up code = OC_STATUS_OK; - doDelete(ApiHelper::s_TestResource, failResponse, 1); + doDelete(ApiHelper::s_TestResource, failResponse, kShortTimeout); ApiHelper::getAndRemoveClientCb(ApiHelper::s_TestResource, OC_DELETE); - ApiHelper::poolEvents(2); // wait for timeout + ApiHelper::poolEvents(kShortTimeout * 2); // wait for timeout EXPECT_EQ(OC_STATUS_OK, code); } @@ -788,20 +798,20 @@ TEST_F(TestServerClient, PostWithTimeout) }; int expected = ApiHelper::s_TestResource.onPost.response; - doPost(ApiHelper::s_TestResource, onPostDevice, 2); - ApiHelper::poolEvents(4); + doPost(ApiHelper::s_TestResource, onPostDevice, kTimeout); + ApiHelper::poolEvents(kTimeout * 2); EXPECT_EQ(expected, code); ApiHelper::s_TestResource.onPost.response = -1; // disable response - doPost(ApiHelper::s_TestResource, onPostDevice, 1); - ApiHelper::poolEvents(2); + doPost(ApiHelper::s_TestResource, onPostDevice, kShortTimeout); + ApiHelper::poolEvents(kShortTimeout * 2); EXPECT_EQ(OC_REQUEST_TIMEOUT, code); // test clean-up code = OC_STATUS_OK; - doPost(ApiHelper::s_TestResource, failResponse, 1); + doPost(ApiHelper::s_TestResource, failResponse, kShortTimeout); ApiHelper::getAndRemoveClientCb(ApiHelper::s_TestResource, OC_POST); - ApiHelper::poolEvents(2); // wait for timeout + ApiHelper::poolEvents(kShortTimeout * 2); // wait for timeout EXPECT_EQ(OC_STATUS_OK, code); } @@ -823,20 +833,20 @@ TEST_F(TestServerClient, PutWithTimeout) }; int expected = ApiHelper::s_TestResource.onPut.response; - doPut(ApiHelper::s_TestResource, onPutDevice, 2); - ApiHelper::poolEvents(4); + doPut(ApiHelper::s_TestResource, onPutDevice, kTimeout); + ApiHelper::poolEvents(kTimeout * 2); EXPECT_EQ(expected, code); ApiHelper::s_TestResource.onPut.response = -1; // disable response - doPut(ApiHelper::s_TestResource, onPutDevice, 1); - ApiHelper::poolEvents(2); + doPut(ApiHelper::s_TestResource, onPutDevice, kShortTimeout); + ApiHelper::poolEvents(kShortTimeout * 2); EXPECT_EQ(OC_REQUEST_TIMEOUT, code); // test clean-up code = OC_STATUS_OK; - doPut(ApiHelper::s_TestResource, failResponse, 1); + doPut(ApiHelper::s_TestResource, failResponse, kShortTimeout); ApiHelper::getAndRemoveClientCb(ApiHelper::s_TestResource, OC_PUT); - ApiHelper::poolEvents(2); // wait for timeout + ApiHelper::poolEvents(kShortTimeout * 2); // wait for timeout EXPECT_EQ(OC_STATUS_OK, code); } diff --git a/include/oc_log.h b/include/oc_log.h index 72a50cdb05..b5a2dcf4a9 100644 --- a/include/oc_log.h +++ b/include/oc_log.h @@ -40,7 +40,7 @@ extern "C" { #include #ifdef OC_LOG_MAXIMUM_LEVEL -#define OC_LOG_LEVEL_IS_ENABLED(level) ((level) <= OC_LOG_MAXIMUM_LEVEL) +#define OC_LOG_LEVEL_IS_ENABLED(level) ((level) <= (OC_LOG_MAXIMUM_LEVEL)) #else #define OC_LOG_LEVEL_IS_ENABLED(level) (0) #endif /* OC_LOG_MAXIMUM_LEVEL */ diff --git a/port/oc_log_internal.h b/port/oc_log_internal.h index f6fb119590..e2733df809 100644 --- a/port/oc_log_internal.h +++ b/port/oc_log_internal.h @@ -110,7 +110,7 @@ #endif /* !OC_LOG */ #ifndef OC_LOG_MAXIMUM_LEVEL -#define OC_LOG_MAXIMUM_LEVEL OC_LOG_LEVEL_DISABLED_MACRO +#define OC_LOG_MAXIMUM_LEVEL (OC_LOG_LEVEL_DISABLED_MACRO) #endif /* !OC_LOG_MAXIMUM_LEVEL */ #define OC_TRACE_IS_ENABLED OC_LOG_LEVEL_IS_ENABLED(OC_LOG_LEVEL_TRACE_MACRO) diff --git a/port/windows/clock.c b/port/windows/clock.c index cd2fc1e436..ef0d570776 100644 --- a/port/windows/clock.c +++ b/port/windows/clock.c @@ -16,6 +16,7 @@ * ****************************************************************************/ +#include "oc_clock_internal.h" #include "port/oc_clock.h" #define WIN32_LEAN_AND_MEAN @@ -23,10 +24,16 @@ #include #include +#include #include -static LONGLONG g_query_perm_frequency = 0; +// This magic number is the number of 100 nanosecond intervals since January 1, +// 1601 (UTC) until 00:00:00 January 1, 1970 +#define kMILLISECOND_TO_FTIME_INTERNAL (10000ULL) +#define kSECOND_TO_FTIME_INTERVAL (kMILLISECOND_TO_FTIME_INTERNAL * 1000ULL) +#define kWINDOWS_EPOCH (11644473600ULL * kSECOND_TO_FTIME_INTERVAL) +static LONGLONG g_query_perm_frequency = 0; void oc_clock_init(void) { @@ -36,26 +43,50 @@ oc_clock_init(void) } oc_clock_time_t -oc_clock_time(void) +oc_clock_time_from_filetime(FILETIME ftime) { oc_clock_time_t time = 0; + time = ((uint64_t)ftime.dwLowDateTime); + time += ((uint64_t)ftime.dwHighDateTime) << sizeof(DWORD) * CHAR_BIT; + // avoid float computation if we have ticks in milliseconds +#if (OC_CLOCK_SECOND == 1000) + return (oc_clock_time_t)((time - kWINDOWS_EPOCH) / + kMILLISECOND_TO_FTIME_INTERNAL); +#else + return (oc_clock_time_t)(((time - kWINDOWS_EPOCH) / + ((double)kSECOND_TO_FTIME_INTERVAL) / + OC_CLOCK_SECOND)); +#endif +} - // This magic number is the number of 100 nanosecond intervals since January - // 1, 1601 (UTC) - // until 00:00:00 January 1, 1970 - static const uint64_t EPOCH = ((uint64_t)116444736000000000ULL); - - SYSTEMTIME system_time; - FILETIME file_time; - - GetSystemTime(&system_time); - SystemTimeToFileTime(&system_time, &file_time); +FILETIME +oc_clock_time_to_filetime(oc_clock_time_t time) +{ +#if (OC_CLOCK_SECOND == 1000) + oc_clock_time_t time_ft = time * kMILLISECOND_TO_FTIME_INTERNAL; +#else + oc_clock_time_t time_ft = + (time / (double)OC_CLOCK_SECOND) * kSECOND_TO_FTIME_INTERVAL; +#endif + + time_ft += kWINDOWS_EPOCH; + FILETIME ftime; + ftime.dwLowDateTime = (DWORD)time_ft; + ftime.dwHighDateTime = (DWORD)(time_ft >> sizeof(DWORD) * CHAR_BIT); + return ftime; +} - time = ((uint64_t)file_time.dwLowDateTime); - time += ((uint64_t)file_time.dwHighDateTime) << 32; - time = (oc_clock_time_t)((time - EPOCH) / 10000L); +oc_clock_time_t +oc_clock_time(void) +{ + SYSTEMTIME stime; + GetSystemTime(&stime); - return time; + FILETIME ftime; + if (!SystemTimeToFileTime(&stime, &ftime)) { + return (oc_clock_time_t)-1; + } + return oc_clock_time_from_filetime(ftime); } oc_clock_time_t @@ -93,6 +124,10 @@ oc_clock_seconds(void) void oc_clock_wait(oc_clock_time_t t) { - DWORD interval = t * (OC_CLOCK_SECOND / 1000); - Sleep(interval); +#if (OC_CLOCK_SECOND == 1000) + DWORD interval_ms = t; +#else + DWORD interval_ms = (DWORD)(t * (OC_CLOCK_SECOND / (double)1000)); +#endif + Sleep(interval_ms); } diff --git a/port/windows/oc_clock_internal.h b/port/windows/oc_clock_internal.h new file mode 100644 index 0000000000..95b82af9b8 --- /dev/null +++ b/port/windows/oc_clock_internal.h @@ -0,0 +1,41 @@ +/****************************************************************** + * + * Copyright (c) 2023 plgd.dev s.r.o. + * + * 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. + * + ******************************************************************/ + +#ifndef OC_PORT_WINDOWS_CLOCK_INTERNAL_H +#define OC_PORT_WINDOWS_CLOCK_INTERNAL_H + +#include "port/oc_clock.h" + +#define WIN32_LEAN_AND_MEAN +#include "windows.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** @brief Convert oc_clock_time_t to Windows filetime. */ +FILETIME oc_clock_time_to_filetime(oc_clock_time_t time); + +/** @brief Convert Windows filetime to oc_clock_time_t. */ +oc_clock_time_t oc_clock_time_from_filetime(FILETIME ftime); + +#ifdef __cplusplus +} +#endif + +#endif /* OC_PORT_WINDOWS_CLOCK_INTERNAL_H */ diff --git a/security/oc_tls.c b/security/oc_tls.c index c3b4e09321..bf107c2afe 100644 --- a/security/oc_tls.c +++ b/security/oc_tls.c @@ -557,13 +557,13 @@ oc_tls_handler_schedule_write(oc_tls_peer_t *peer) static oc_event_callback_retval_t oc_dtls_inactive(void *data) { - OC_DBG("oc_tls: DTLS inactivity callback"); oc_tls_peer_t *peer = (oc_tls_peer_t *)data; + OC_DBG("oc_tls: DTLS inactivity callback for peer(%p)", (void *)peer); if (!is_peer_active(peer)) { OC_DBG("oc_tls: Terminating DTLS inactivity callback"); return OC_EVENT_DONE; } - oc_clock_time_t time = oc_clock_time(); + oc_clock_time_t time = oc_clock_time_monotonic(); time -= peer->timestamp; if (time < DTLS_INACTIVITY_TIMEOUT_TICKS) { OC_DBG("oc_tls: Resetting DTLS inactivity callback"); @@ -612,7 +612,7 @@ static int ssl_send(void *ctx, const unsigned char *buf, size_t len) { oc_tls_peer_t *peer = (oc_tls_peer_t *)ctx; - peer->timestamp = oc_clock_time(); // TODO monotonic timer + peer->timestamp = oc_clock_time_monotonic(); size_t max_len = oc_message_buffer_size(); size_t send_len = (len < max_len) ? len : max_len; oc_message_t *message = oc_message_allocate_outgoing_with_size(send_len); @@ -2856,7 +2856,7 @@ oc_tls_recv_message(oc_message_t *message) #endif /* OC_DBG_IS_ENABLED */ oc_list_add(peer->recv_q, message); - peer->timestamp = oc_clock_time(); // TODO monotonic timer + peer->timestamp = oc_clock_time_monotonic(); oc_tls_handler_schedule_read(peer); } diff --git a/security/unittest/dtlstest.cpp b/security/unittest/dtlstest.cpp new file mode 100644 index 0000000000..6bf745fd0a --- /dev/null +++ b/security/unittest/dtlstest.cpp @@ -0,0 +1,162 @@ +/****************************************************************** + * + * Copyright (c) 2023 plgd.dev s.r.o. + * + * 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. + * + ******************************************************************/ + +#ifdef OC_SECURITY + +#include "api/oc_core_res_internal.h" +#include "api/oc_endpoint_internal.h" +#include "port/oc_clock.h" +#include "security/oc_pstat.h" +#include "security/oc_tls_internal.h" +#include "tests/gtest/Device.h" +#include "tests/gtest/tls/DTLS.h" +#include "tests/gtest/tls/DTLSClient.h" +#include "util/oc_macros.h" + +#include +#include + +// TODO: upgrade mingw, because on v10.2 std::thread doesn't work correctly +#if defined(__MINGW32__) && defined(__GNUC__) && (__GNUC__ < 12) +#define MINGW_WINTHREAD +#include +#else /* __MINGW32__ */ +#include +#endif /* __MINGW32__ */ + +#if defined(MBEDTLS_NET_C) && defined(MBEDTLS_TIMING_C) + +static constexpr size_t kDeviceID{ 0 }; + +class TestDTLSWithServer : public testing::Test { +public: + static void SetUpTestCase() + { + ASSERT_TRUE(oc::TestDevice::StartServer()); + oc_sec_pstat_t *pstat = oc_sec_get_pstat(kDeviceID); + pstat->s = OC_DOS_RFNOP; + } + + static void TearDownTestCase() { oc::TestDevice::StopServer(); } + + void TearDown() override + { + oc_tls_close_peers(nullptr, nullptr); + oc_sec_pstat_t *pstat = oc_sec_get_pstat(kDeviceID); + pstat->s = OC_DOS_RFNOP; + } +}; + +enum class DTLS_STATUS : int { + DTLS_INIT = 0, + DTLS_HANDSHAKE_DONE = 1, + DTLS_ERROR = -1, +}; + +struct DTLSData +{ + oc::tls::DTLSClient *client; + const oc_endpoint_t *ep; + std::atomic *status; +}; + +#ifdef MINGW_WINTHREAD +static DWORD +dtls_thread_win32(void *data) +{ + DTLSData *obj = static_cast(data); + OC_DBG("dtls helper thread started"); + if (!obj->client->ConnectWithHandshake( + "::1", static_cast(oc_endpoint_port(obj->ep)))) { + obj->status->store(DTLS_STATUS::DTLS_ERROR); + ADD_FAILURE(); + return -1; + } + obj->status->store(DTLS_STATUS::DTLS_HANDSHAKE_DONE); + obj->client->Run(); + return 0; +} +#endif /* MINGW_WINTHREAD */ + +TEST_F(TestDTLSWithServer, DTLSInactivityMonitor) +{ + oc_clock_time_t timeout_default = oc_dtls_inactivity_timeout(); + oc_dtls_set_inactivity_timeout(2 * OC_CLOCK_SECOND); + + // DTLS endpoint + const oc_endpoint_t *ep = + oc::TestDevice::GetEndpoint(/*device*/ 0, SECURED, TCP); + ASSERT_NE(nullptr, ep); + + oc::tls::PreSharedKey psk = { + 0xD1, 0xD0, 0xDB, 0x1F, 0x8B, 0xB2, 0x40, 0x55, + 0x9B, 0x07, 0xB8, 0x76, 0x50, 0x7E, 0x25, 0xCF, + }; + auto hint = oc::tls::AddPresharedKey(kDeviceID, psk); + ASSERT_TRUE(hint.has_value()); + + std::atomic dtls_status{ DTLS_STATUS::DTLS_INIT }; + oc::tls::DTLSClient dtls{}; + dtls.SetPresharedKey(psk, *hint); + DTLSData data{}; + data.client = &dtls; + data.ep = ep; + data.status = &dtls_status; + +#ifdef MINGW_WINTHREAD + DWORD dtls_thread_id; + HANDLE dtls_thread = + CreateThread(nullptr, 0, dtls_thread_win32, &data, 0, &dtls_thread_id); +#else /* !MINGW_WINTHREAD */ + std::thread dtls_thread{ [&data] { + OC_DBG("DTLS helper thread started"); + if (!data.client->ConnectWithHandshake( + "::1", static_cast(oc_endpoint_port(data.ep)))) { + data.status->store(DTLS_STATUS::DTLS_ERROR); + GTEST_FAIL(); + } + data.status->store(DTLS_STATUS::DTLS_HANDSHAKE_DONE); + data.client->Run(); + } }; +#endif /* MINGW_WINTHREAD */ + + while (dtls_status.load() == DTLS_STATUS::DTLS_INIT) { + oc::TestDevice::PoolEventsMs(200); + } + + EXPECT_EQ(1, oc_tls_num_peers(kDeviceID)); + uint64_t timeout_msecs = + (oc_dtls_inactivity_timeout() / OC_CLOCK_SECOND) * 1000; + oc::TestDevice::PoolEventsMs(timeout_msecs * 2); + EXPECT_EQ(0, oc_tls_num_peers(kDeviceID)); + + dtls.Stop(); +#ifdef MINGW_WINTHREAD + WaitForSingleObject(dtls_thread, INFINITE); + TerminateThread(dtls_thread, 0); +#else /* !MINGW_WINTHREAD */ + dtls_thread.join(); +#endif /* MINGW_WINTHREAD */ + + /* restore defaults */ + oc_dtls_set_inactivity_timeout(timeout_default); +} + +#endif /* MBEDTLS_NET_C && MBEDTLS_TIMING_C */ + +#endif /* OC_SECURITY */ diff --git a/security/unittest/tls_peer_test.cpp b/security/unittest/tls_peer_test.cpp index 485861f6cf..f5ea05bbcd 100644 --- a/security/unittest/tls_peer_test.cpp +++ b/security/unittest/tls_peer_test.cpp @@ -33,9 +33,7 @@ #include "tests/gtest/Device.h" #include "tests/gtest/Endpoint.h" #include "tests/gtest/RepPool.h" -#include "tests/gtest/tls/DTLSClient.h" #include "tests/gtest/tls/Peer.h" -#include "util/oc_macros.h" #ifdef OC_HAS_FEATURE_PUSH #include "api/oc_push_internal.h" @@ -46,12 +44,10 @@ #endif /* _WIN32 */ #include -#include #include #include #include #include -#include #include static constexpr size_t kDeviceID{ 0 }; @@ -336,91 +332,4 @@ TEST_F(TestTLSPeerWithServer, ResetDevice) ASSERT_EQ(0, oc_tls_num_peers(kDeviceID)); } -#if defined(MBEDTLS_NET_C) && defined(MBEDTLS_TIMING_C) - -// TODO: upgrade mingw, because on v10.2 std::thread doesn't work correctly -#ifndef __MINGW32__ - -TEST_F(TestTLSPeerWithServer, DTLSInactivityMonitor) -{ - oc_clock_time_t timeout_default = oc_dtls_inactivity_timeout(); - oc_dtls_set_inactivity_timeout(2 * OC_CLOCK_SECOND); - - // DTLS endpoint - const oc_endpoint_t *ep = - oc::TestDevice::GetEndpoint(/*device*/ 0, SECURED, TCP); - ASSERT_NE(nullptr, ep); - - std::vector psk = { 0xD1, 0xD0, 0xDB, 0x1F, 0x8B, 0xB2, 0x40, 0x55, - 0x9B, 0x07, 0xB8, 0x76, 0x50, 0x7E, 0x25, 0xCF }; - oc_uuid_t *uuid = oc_core_get_device_id(kDeviceID); - std::vector hint{}; - hint.reserve(OC_ARRAY_SIZE(uuid->id)); - std::copy(std::begin(uuid->id), std::end(uuid->id), std::back_inserter(hint)); - - enum class DTLS_STATUS : int { - INIT = 0, - THREAD_STARTED = 1, - HANDSHAKE_DONE = 2, - - ERROR = -1, - }; - std::atomic dtls_status{ DTLS_STATUS::INIT }; - oc::tls::DTLSClient dtls{}; - dtls.SetPresharedKey(psk, hint); - auto dtls_execute = [&dtls, &ep, &dtls_status] { - OC_DBG("dtls helper thread started"); - dtls_status = DTLS_STATUS::THREAD_STARTED; - - std::string host{ "::1" }; - int port = oc_endpoint_port(ep); - if (port < 0) { - dtls_status = DTLS_STATUS::ERROR; - GTEST_FAIL(); - } - if (int socket = dtls.Connect(host, static_cast(port)); - socket < 0) { - OC_ERR("DTLS connect failed with error(%d, errno=%d)", socket, errno); - dtls_status = DTLS_STATUS::ERROR; - GTEST_FAIL(); - } - if (int hs = dtls.Handshake(); hs != 0) { - OC_ERR("DTLS handshake failed with error(%d)", hs); - dtls_status = DTLS_STATUS::ERROR; - GTEST_FAIL(); - } - dtls_status = DTLS_STATUS::HANDSHAKE_DONE; - dtls.Run(); - }; - - std::array uuid_str{}; - oc_uuid_to_str(uuid, uuid_str.data(), uuid_str.size()); - int credid = oc_sec_add_new_cred( - kDeviceID, false, nullptr, -1, OC_CREDTYPE_PSK, OC_CREDUSAGE_NULL, - uuid_str.data(), OC_ENCODING_RAW, psk.size(), psk.data(), - OC_ENCODING_UNSUPPORTED, 0, nullptr, nullptr, nullptr, nullptr, nullptr); - ASSERT_NE(-1, credid); - - std::thread dtls_thread{ dtls_execute }; - while (dtls_status.load() != DTLS_STATUS::HANDSHAKE_DONE) { - oc::TestDevice::PoolEventsMs(200); - } - - EXPECT_EQ(1, oc_tls_num_peers(kDeviceID)); - uint64_t timeout_msecs = - (oc_dtls_inactivity_timeout() / OC_CLOCK_SECOND) * 1000; - oc::TestDevice::PoolEventsMs(timeout_msecs * 2); - EXPECT_EQ(0, oc_tls_num_peers(kDeviceID)); - - dtls.Stop(); - dtls_thread.join(); - - /* restore defaults */ - oc_dtls_set_inactivity_timeout(timeout_default); -} - -#endif /* __MINGW32__ */ - -#endif /* MBEDTLS_NET_C && MBEDTLS_TIMING_C */ - #endif /* OC_SECURITY */ diff --git a/tests/gtest/tls/DTLS.cpp b/tests/gtest/tls/DTLS.cpp new file mode 100644 index 0000000000..e0b3792212 --- /dev/null +++ b/tests/gtest/tls/DTLS.cpp @@ -0,0 +1,50 @@ +/**************************************************************************** + * + * Copyright 2023 Daniel Adam, 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 "oc_config.h" + +#ifdef OC_SECURITY + +#include "DTLS.h" + +#include "api/oc_core_res_internal.h" +#include "security/oc_cred_internal.h" +#include "util/oc_macros.h" + +namespace oc::tls { + +std::optional +AddPresharedKey(size_t device, const PreSharedKey &psk) +{ + oc_uuid_t *uuid = oc_core_get_device_id(device); + IdentityHint hint{}; + std::copy(std::begin(uuid->id), std::end(uuid->id), std::begin(hint)); + std::array uuid_str{}; + oc_uuid_to_str(uuid, uuid_str.data(), uuid_str.size()); + if (oc_sec_add_new_cred(device, false, nullptr, -1, OC_CREDTYPE_PSK, + OC_CREDUSAGE_NULL, uuid_str.data(), OC_ENCODING_RAW, + psk.size(), psk.data(), OC_ENCODING_UNSUPPORTED, 0, + nullptr, nullptr, nullptr, nullptr, nullptr) == -1) { + return {}; + } + return hint; +} + +} // namespace oc::tls + +#endif /* OC_SECURITY */ diff --git a/tests/gtest/tls/DTLS.h b/tests/gtest/tls/DTLS.h new file mode 100644 index 0000000000..8786b9132e --- /dev/null +++ b/tests/gtest/tls/DTLS.h @@ -0,0 +1,40 @@ +/**************************************************************************** + * + * Copyright 2023 Daniel Adam, 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 "oc_config.h" + +#ifdef OC_SECURITY + +#include +#include +#include +#include + +namespace oc::tls { + +using IdentityHint = std::array; +using PreSharedKey = std::array; + +std::optional AddPresharedKey(size_t device, + const PreSharedKey &psk); + +} // namespace oc::tls + +#endif /* OC_SECURITY */ diff --git a/tests/gtest/tls/DTLSClient.cpp b/tests/gtest/tls/DTLSClient.cpp index 3bfe60c6ef..8e4a96adaa 100644 --- a/tests/gtest/tls/DTLSClient.cpp +++ b/tests/gtest/tls/DTLSClient.cpp @@ -45,6 +45,9 @@ static void dtls_debug(void *ctx, int level, const char *file, int line, const char *str) { (void)ctx; + (void)file; + (void)line; + (void)str; if (level == 1) { OC_ERR("%s:%04d: %s", file, line, str); return; @@ -155,13 +158,12 @@ DTLSClient::DTLSClient() } int -DTLSClient::SetPresharedKey(const std::vector &key, - const std::vector &identityHint) +DTLSClient::SetPresharedKey(const PreSharedKey &psk, const IdentityHint &hint) { - psk_ = key; - identityHint_ = identityHint; - return mbedtls_ssl_conf_psk(&config_, psk_.data(), psk_.size(), - identityHint_.data(), identityHint_.size()); + psk_ = psk; + hint_ = hint; + return mbedtls_ssl_conf_psk(&config_, psk_.data(), psk_.size(), hint_.data(), + hint_.size()); } int @@ -193,6 +195,20 @@ DTLSClient::Handshake() return ret; } +bool +DTLSClient::ConnectWithHandshake(const std::string &host, uint16_t port) +{ + if (int socket = Connect(host, port); socket < 0) { + OC_ERR("DTLS connect failed with error(%d, errno=%d)", socket, errno); + return false; + } + if (int hs = Handshake(); hs != 0) { + OC_ERR("DTLS handshake failed with error(%d)", hs); + return false; + } + return true; +} + DTLSClient::~DTLSClient() { service_.Stop(); diff --git a/tests/gtest/tls/DTLSClient.h b/tests/gtest/tls/DTLSClient.h index d757d24b00..4ffa465da1 100644 --- a/tests/gtest/tls/DTLSClient.h +++ b/tests/gtest/tls/DTLSClient.h @@ -26,6 +26,7 @@ #if defined(MBEDTLS_NET_C) && defined(MBEDTLS_TIMING_C) +#include "DTLS.h" #include "Service.h" #include @@ -55,13 +56,13 @@ class DTLSClient : public MbedTLSService { mbedtls_ssl_context *GetSSLContext() override { return &ssl_; }; mbedtls_net_context *GetNetContext() override { return &serverFd_; }; - int SetPresharedKey(const std::vector &key, - const std::vector &identityHint); + int SetPresharedKey(const PreSharedKey &psk, const IdentityHint &hint); int Connect(const std::string &host, uint16_t port); void CloseNotify(); int Handshake(); + bool ConnectWithHandshake(const std::string &host, uint16_t port); int Run() { return service_.Run(); } void Stop() { service_.Stop(); } @@ -78,8 +79,8 @@ class DTLSClient : public MbedTLSService { mbedtls_ssl_cookie_ctx cookieCtx_; mbedtls_timing_delay_context timer_; - std::vector psk_{}; - std::vector identityHint_{}; + PreSharedKey psk_{}; + IdentityHint hint_{}; std::vector ciphers_{}; }; diff --git a/tests/libfaketime/unittest/clocktest.cpp b/tests/libfaketime/unittest/clocktest.cpp new file mode 100644 index 0000000000..957aa4b9bc --- /dev/null +++ b/tests/libfaketime/unittest/clocktest.cpp @@ -0,0 +1,84 @@ +/****************************************************************** + * + * Copyright (c) 2023 plgd.dev s.r.o. + * + * 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 "utility.h" + +#include "port/oc_clock.h" +#include "port/oc_log_internal.h" + +#include +#include + +class TestClock : public testing::Test { +public: + static void SetUpTestCase() + { + oc_clock_init(); + TestClock::start = oc_clock_time(); + TestClock::startMt = oc_clock_time_monotonic(); + } + + void TearDown() override + { +#if defined(__unix__) || defined(_WIN32) + oc_clock_time_t elapsed = oc_clock_time_monotonic() - TestClock::startMt; + std::chrono::milliseconds shift{ static_cast((elapsed * 1.e03) / + OC_CLOCK_SECOND) }; + oc::SetSystemTime(TestClock::start, shift); +#endif /* __unix__ || _WIN32 */ + } + +private: + static oc_clock_time_t start; + static oc_clock_time_t startMt; +}; + +oc_clock_time_t TestClock::start{}; +oc_clock_time_t TestClock::startMt{}; + +#if defined(__unix__) || defined(_WIN32) + +TEST_F(TestClock, MonotonicTime) +{ + OC_INFO("start"); + oc_clock_time_t start_mt = oc_clock_time_monotonic(); + oc_clock_time_t start = oc_clock_time(); + + // go 5 secs in the past + ASSERT_TRUE(oc::SetSystemTime(start, std::chrono::seconds{ -5 })); + OC_INFO("time change"); + + // wait two seconds + oc_clock_time_t delay = 2 * OC_CLOCK_SECOND; + oc_clock_wait(delay); + + oc_clock_time_t end_mt = oc_clock_time_monotonic(); + oc_clock_time_t end = oc_clock_time(); + + // elapsed time based on system time should be negative + OC_INFO("start: %" PRIu64 ", end: %" PRIu64, start, end); + int64_t elapsed = static_cast(end) - start; + EXPECT_GT(0, elapsed); + + // elapsed time based on monotonic time should be around two seconds + OC_INFO("start_mt: %" PRIu64 ", end_mt: %" PRIu64, start_mt, end_mt); + int64_t elapsed_mt = static_cast(end_mt) - start_mt; + EXPECT_LT(0, elapsed_mt); +} + +#endif /* __unix__ || _WIN32 */ diff --git a/tests/libfaketime/unittest/dtlstest.cpp b/tests/libfaketime/unittest/dtlstest.cpp new file mode 100644 index 0000000000..ec86179e33 --- /dev/null +++ b/tests/libfaketime/unittest/dtlstest.cpp @@ -0,0 +1,192 @@ +/****************************************************************** + * + * Copyright (c) 2023 plgd.dev s.r.o. + * + * 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. + * + ******************************************************************/ + +#ifdef OC_SECURITY + +#include "utility.h" + +#include "api/oc_endpoint_internal.h" +#include "port/oc_clock.h" +#include "security/oc_pstat.h" +#include "security/oc_tls_internal.h" +#include "tests/gtest/Device.h" +#include "tests/gtest/tls/DTLS.h" +#include "tests/gtest/tls/DTLSClient.h" + +#include +#include +#include + +// TODO: upgrade mingw, because on v10.2 std::thread doesn't work correctly +#if defined(__MINGW32__) && defined(__GNUC__) && (__GNUC__ < 12) +#define MINGW_WINTHREAD +#include +#else /* __MINGW32__ */ +#include +#endif /* __MINGW32__ */ + +static constexpr size_t kDeviceID{ 0 }; + +class TestDTLSWithServer : public testing::Test { +public: + static void SetUpTestCase() + { + oc_clock_init(); + TestDTLSWithServer::start = oc_clock_time(); + TestDTLSWithServer::startMt = oc_clock_time_monotonic(); + } + + static void TearDownTestCase() {} + + void SetUp() override + { + ASSERT_TRUE(oc::TestDevice::StartServer()); + oc_sec_pstat_t *pstat = oc_sec_get_pstat(kDeviceID); + pstat->s = OC_DOS_RFNOP; + } + + void TearDown() override + { + oc::TestDevice::StopServer(); + +#if defined(__unix__) || defined(_WIN32) + oc_clock_time_t elapsed = + oc_clock_time_monotonic() - TestDTLSWithServer::startMt; + std::chrono::milliseconds shift{ static_cast((elapsed * 1.e03) / + OC_CLOCK_SECOND) }; + oc::SetSystemTime(TestDTLSWithServer::start, shift); +#endif /* __unix__ || _WIN32 */ + } + +private: + static oc_clock_time_t start; + static oc_clock_time_t startMt; +}; + +oc_clock_time_t TestDTLSWithServer::start{}; +oc_clock_time_t TestDTLSWithServer::startMt{}; + +enum class DTLS_STATUS : int { + DTLS_INIT = 0, + DTLS_HANDSHAKE_DONE = 1, + DTLS_ERROR = -1, +}; + +struct DTLSData +{ + oc::tls::DTLSClient *client; + const oc_endpoint_t *ep; + std::atomic *status; +}; + +#ifdef MINGW_WINTHREAD +static DWORD +dtls_thread_win32(void *data) +{ + DTLSData *obj = static_cast(data); + OC_DBG("dtls helper thread started"); + if (!obj->client->ConnectWithHandshake( + "::1", static_cast(oc_endpoint_port(obj->ep)))) { + obj->status->store(DTLS_STATUS::DTLS_ERROR); + ADD_FAILURE(); + return -1; + } + obj->status->store(DTLS_STATUS::DTLS_HANDSHAKE_DONE); + obj->client->Run(); + return 0; +} +#endif /* MINGW_WINTHREAD */ + +TEST_F(TestDTLSWithServer, InactivityMonitorChangeTimeForwards) +{ + oc_clock_time_t timeout_default = oc_dtls_inactivity_timeout(); + oc_dtls_set_inactivity_timeout(2 * OC_CLOCK_SECOND); + + // DTLS endpoint + const oc_endpoint_t *ep = + oc::TestDevice::GetEndpoint(/*device*/ 0, SECURED, TCP); + ASSERT_NE(nullptr, ep); + + oc::tls::PreSharedKey psk = { + 0xD1, 0xD0, 0xDB, 0x1F, 0x8B, 0xB2, 0x40, 0x55, + 0x9B, 0x07, 0xB8, 0x76, 0x50, 0x7E, 0x25, 0xCF, + }; + auto hint = oc::tls::AddPresharedKey(kDeviceID, psk); + ASSERT_TRUE(hint.has_value()); + + std::atomic dtls_status{ DTLS_STATUS::DTLS_INIT }; + oc::tls::DTLSClient dtls{}; + dtls.SetPresharedKey(psk, *hint); + DTLSData data{}; + data.client = &dtls; + data.ep = ep; + data.status = &dtls_status; +#ifdef MINGW_WINTHREAD + DWORD dtls_thread_id; + HANDLE dtls_thread = + CreateThread(nullptr, 0, dtls_thread_win32, &data, 0, &dtls_thread_id); +#else /* !MINGW_WINTHREAD */ + std::thread dtls_thread{ [&data] { + OC_DBG("DTLS helper thread started"); + if (!data.client->ConnectWithHandshake( + "::1", static_cast(oc_endpoint_port(data.ep)))) { + data.status->store(DTLS_STATUS::DTLS_ERROR); + GTEST_FAIL(); + } + data.status->store(DTLS_STATUS::DTLS_HANDSHAKE_DONE); + data.client->Run(); + } }; +#endif /* MINGW_WINTHREAD */ + + while (dtls_status.load() == DTLS_STATUS::DTLS_INIT) { + oc::TestDevice::PoolEventsMs(200); + } + OC_INFO("Inactivity monitoring start"); + + // change absolute time so the inactivity timeout would occurr if it was + // driven by absolute time + uint64_t timeout_secs = (oc_dtls_inactivity_timeout() / OC_CLOCK_SECOND); + ASSERT_TRUE(oc::SetSystemTime(oc_clock_time(), + std::chrono::seconds{ 2 * timeout_secs })); + OC_INFO("Change system time"); + // wait a bit + oc::TestDevice::PoolEventsMs(500); + + OC_INFO("Verifying peers"); + EXPECT_EQ(1, oc_tls_num_peers(kDeviceID)); + oc::TestDevice::PoolEvents(2 * timeout_secs); + EXPECT_EQ(0, oc_tls_num_peers(kDeviceID)); + + dtls.Stop(); +#ifdef MINGW_WINTHREAD + WaitForSingleObject(dtls_thread, INFINITE); + TerminateThread(dtls_thread, 0); +#else /* !MINGW_WINTHREAD */ + dtls_thread.join(); +#endif /* MINGW_WINTHREAD */ + + /* restore defaults */ + oc_dtls_set_inactivity_timeout(timeout_default); +} + +TEST_F(TestDTLSWithServer, InactivityMonitorChangeTimeBackwards) +{ + // TODO: for this test struct oc_timer needs to be fixed +} + +#endif /* OC_SECURITY */ diff --git a/tests/libfaketime/unittest/utility.cpp b/tests/libfaketime/unittest/utility.cpp new file mode 100644 index 0000000000..90c80f22a6 --- /dev/null +++ b/tests/libfaketime/unittest/utility.cpp @@ -0,0 +1,79 @@ +/****************************************************************** + * + * Copyright (c) 2023 plgd.dev s.r.o. + * + * 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 "utility.h" + +#include "port/oc_clock.h" + +#ifdef __unix__ +#include +#endif /* __unix__ */ + +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#include + +#include "port/windows/oc_clock_internal.h" +#endif /* _WIN32 */ + +#include + +namespace oc { + +#ifdef __unix__ +bool +SetSystemTimeUnix(oc_clock_time_t now, std::chrono::milliseconds shift) +{ + struct timeval time = {}; + time.tv_sec = (now / OC_CLOCK_SECOND) + + std::chrono::duration_cast(shift).count(); + oc_clock_time_t rem_ticks = now % OC_CLOCK_SECOND; + time.tv_usec = static_cast( + (static_cast(rem_ticks) * 1.e06) / OC_CLOCK_SECOND); + return settimeofday(&time, nullptr) == 0; +} +#endif /* __unix__ */ + +#ifdef _WIN32 +bool +SetSystemTimeWin(oc_clock_time_t now, std::chrono::milliseconds shift) +{ + oc_clock_time_t ct = + now + (oc_clock_time_t)((shift.count() / (double)1000) * OC_CLOCK_SECOND); + FILETIME ftime{ oc_clock_time_to_filetime(ct) }; + SYSTEMTIME stime{}; + if (!FileTimeToSystemTime(&ftime, &stime)) { + return false; + } + return SetSystemTime(&stime); +} +#endif /* _WIN32 */ + +bool +SetSystemTime(oc_clock_time_t now, std::chrono::milliseconds shift) +{ +#ifdef __unix__ + return SetSystemTimeUnix(now, shift); +#endif /* __unix__ */ +#ifdef _WIN32 + return SetSystemTimeWin(now, shift); +#endif /* _WIN32 */ + return false; +} + +} // namespace oc diff --git a/tests/libfaketime/unittest/utility.h b/tests/libfaketime/unittest/utility.h new file mode 100644 index 0000000000..3fe82cd06a --- /dev/null +++ b/tests/libfaketime/unittest/utility.h @@ -0,0 +1,38 @@ +/****************************************************************** + * + * Copyright (c) 2023 plgd.dev s.r.o. + * + * 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 "port/oc_clock.h" + +#include +#include + +namespace oc { + +bool SetSystemTime(oc_clock_time_t now, std::chrono::milliseconds shift); + +#ifdef __unix__ +bool SetSystemTimeUnix(oc_clock_time_t now, std::chrono::milliseconds shift); +#endif /* __unix__ */ + +#ifdef _WIN32 +bool SetSystemTimeWin(oc_clock_time_t now, std::chrono::milliseconds shift); +#endif /* _WIN32 */ + +} // namespace oc