diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..2dcaf2b --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,29 @@ +#Copyright (c) Microsoft. All rights reserved. +#Licensed under the MIT license. See LICENSE file in the project root for full license information. + +cmake_minimum_required(VERSION 2.8.11) + +project(azure_external_unit_tests) + +# Set up the C SDK options to build as little as possible +option(skip_samples "set skip_samples to ON to skip building samples (default is OFF)[if possible, they are always build]" ON) +option(compileOption_C "passes a string to the command line of the C compiler" OFF) +option(compileOption_CXX "passes a string to the command line of the C++ compiler" OFF) +option(no_logging "disable logging" OFF) +option(run_e2e_tests "set run_e2e_tests to ON to run e2e tests (default is OFF)" OFF) +option(run_unittests "set run_unittests to ON to run unittests (default is OFF)" OFF) +option(use_cppunittest "set use_cppunittest to ON to build CppUnitTest tests on Windows (default is ON)" ON) + +# include the sdk for its header files and libs +set(run_unittests OFF) +include("sdk/c-utility/configs/azure_iot_external_pal_unit_test_setup.cmake") + +# Configure this external repo +# Currently this CMakeLists.txt file is only used for unit tests +# EXTERNAL_PAL_REPO_DIR contains this top-level CMakeLists.txt +set(EXTERNAL_PAL_REPO_DIR ${CMAKE_CURRENT_LIST_DIR}) +include_directories(${SHARED_UTIL_PAL_INC_FOLDER}) + + +set(run_unittests ON) +add_subdirectory(sdk_tests) diff --git a/component.mk b/component.mk index 016290d..e1afccd 100644 --- a/component.mk +++ b/component.mk @@ -13,13 +13,14 @@ CFLAGS += -DUSE_LWIP_SOCKET_FOR_AZURE_IOT COMPONENT_ADD_INCLUDEDIRS := \ pal \ +pal/inc \ sdk/c-utility/inc \ sdk/c-utility/inc/azure_c_shared_utility \ sdk/c-utility/pal/inc \ sdk/iothub_client/inc \ sdk/umqtt/inc \ sdk/umqtt/inc/azure_umqtt_c \ -sdk/parson +sdk/deps/parson COMPONENT_OBJS = \ sdk/c-utility/src/xlogging.o \ @@ -57,6 +58,7 @@ sdk/iothub_client/src/iothub_client_ll.o \ sdk/iothub_client/src/iothub_client_ll_uploadtoblob.o \ sdk/iothub_client/src/iothub_client_authorization.o \ sdk/iothub_client/src/iothub_client_retry_control.o \ +sdk/iothub_client/src/iothub_client_diagnostic.o \ sdk/iothub_client/src/iothub_message.o \ sdk/iothub_client/src/iothubtransport.o \ sdk/iothub_client/src/iothubtransportmqtt.o \ @@ -76,17 +78,17 @@ sdk/c-utility/src/singlylinkedlist.o \ \ sdk/c-utility/pal/dns_async.o \ sdk/c-utility/pal/socket_async.o \ -sdk/c-utility/pal/free_rtos/threadapi.o \ -sdk/c-utility/pal/free_rtos/tickcounter.o \ +sdk/c-utility/pal/freertos/threadapi.o \ +sdk/c-utility/pal/freertos/tickcounter.o \ sdk/c-utility/pal/lwip/sntp_lwip.o \ \ -pal/platform_openssl_compact.o \ -pal/tlsio_openssl_compact.o +pal/src/platform_openssl_compact.o \ +pal/src/tlsio_openssl_compact.o COMPONENT_SRCDIRS := \ -pal \ +pal/src \ sdk/c-utility/pal \ -sdk/c-utility/pal/free_rtos \ +sdk/c-utility/pal/freertos \ sdk/c-utility/pal/lwip \ sdk/c-utility/src \ sdk/c-utility/adapters \ diff --git a/jenkins/esp32_c.sh b/jenkins/esp32_c.sh new file mode 100755 index 0000000..c03d715 --- /dev/null +++ b/jenkins/esp32_c.sh @@ -0,0 +1,43 @@ +echo "Cross-compiling a non-working ESP32 example" + +export PATH=$PATH:$ESP32_TOOLS/xtensa-esp32-elf/bin + +# Verify that the ESP32 SDK is at a tested commit +pushd $IDF_PATH +idf_commit=$(git rev-parse HEAD) +tested_idf_commit=53893297299e207029679dc99b7fb33151bdd415 +popd +if [ $idf_commit = $tested_idf_commit ] + then echo "ESP32 SDK commit is okay" +else + echo "ESP32 SDK commit is " $idf_commit + echo "ESP32 SDK commit should be " $tested_idf_commit + echo "Error: ESP32 SDK commit is wrong" + exit 1 +fi + +cd $IDF_PATH/components/azure-iot/sample +echo "building in" $(pwd) + +# Copy the sample files +sample_dir=$IDF_PATH/components/azure-iot/sdk/iothub_client/samples/iothub_client_sample_mqtt +proj_dir=$IDF_PATH/components/azure-iot/sample/main +cp $sample_dir/iothub_client_sample_mqtt.c $proj_dir +cp $sample_dir/iothub_client_sample_mqtt.h $proj_dir + +make defconfig +if [ $? = 0 ] + then echo "config generated okay" +else + echo "!!!FAILED!!! config generation failed" + exit $? +fi + +make +if [ $? = 0 ] + then echo "built okay" + exit 0 +else + echo "!!!FAILED!!! make failed" + exit $? +fi diff --git a/jenkins/ubuntu1510_c.sh b/jenkins/ubuntu1510_c.sh new file mode 100755 index 0000000..176bff0 --- /dev/null +++ b/jenkins/ubuntu1510_c.sh @@ -0,0 +1,11 @@ +#!/bin/bash +# Copyright (c) Microsoft. All rights reserved. +# Licensed under the MIT license. See LICENSE file in the project root for full license information. + +build_root=$(cd "$(dirname "$0")/.." && pwd) +cd $build_root/sdk/c-utility/build_all/linux + +# -- C -- +./build.sh --run-unittests --run_valgrind --build-root $build_root "$@" #-x +[ $? -eq 0 ] || exit $? + diff --git a/jenkins/ubuntu1604_c.sh b/jenkins/ubuntu1604_c.sh new file mode 100755 index 0000000..176bff0 --- /dev/null +++ b/jenkins/ubuntu1604_c.sh @@ -0,0 +1,11 @@ +#!/bin/bash +# Copyright (c) Microsoft. All rights reserved. +# Licensed under the MIT license. See LICENSE file in the project root for full license information. + +build_root=$(cd "$(dirname "$0")/.." && pwd) +cd $build_root/sdk/c-utility/build_all/linux + +# -- C -- +./build.sh --run-unittests --run_valgrind --build-root $build_root "$@" #-x +[ $? -eq 0 ] || exit $? + diff --git a/jenkins/windows_c.cmd b/jenkins/windows_c.cmd new file mode 100644 index 0000000..68d78b7 --- /dev/null +++ b/jenkins/windows_c.cmd @@ -0,0 +1,17 @@ +@REM Copyright (c) Microsoft. All rights reserved. +@REM Licensed under the MIT license. See LICENSE file in the project root for full license information. + +setlocal + +set build-root=%~dp0.. +rem // resolve to fully qualified path +for %%i in ("%build-root%") do set build-root=%%~fi + +echo %build-root% + +REM -- C -- +cd %build-root%\sdk\c-utility\build_all\windows + +call build.cmd %* --build-root %build-root% --solution-name azure_external_unit_tests +if errorlevel 1 goto :eof +cd %build-root% \ No newline at end of file diff --git a/pal/sntp_os.h b/pal/inc/sntp_os.h similarity index 100% rename from pal/sntp_os.h rename to pal/inc/sntp_os.h diff --git a/pal/socket_async_os.h b/pal/inc/socket_async_os.h similarity index 100% rename from pal/socket_async_os.h rename to pal/inc/socket_async_os.h diff --git a/pal/tlsio_openssl_compact.h b/pal/inc/tlsio_pal.h similarity index 59% rename from pal/tlsio_openssl_compact.h rename to pal/inc/tlsio_pal.h index 9f29ed1..04cb9e0 100644 --- a/pal/tlsio_openssl_compact.h +++ b/pal/inc/tlsio_pal.h @@ -1,19 +1,20 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -#ifndef TLSIO_OPENSSL_COMPACT_H -#define TLSIO_OPENSSL_COMPACT_H +#ifndef TLSIO_PAL_H +#define TLSIO_PAL_H + +#include "azure_c_shared_utility/tlsio.h" +#include "azure_c_shared_utility/umock_c_prod.h" #ifdef __cplusplus extern "C" { #endif /* __cplusplus */ -#include "azure_c_shared_utility/tlsio.h" - - MOCKABLE_FUNCTION(, const IO_INTERFACE_DESCRIPTION*, tlsio_openssl_compact_get_interface_description); +MOCKABLE_FUNCTION(, const IO_INTERFACE_DESCRIPTION*, tlsio_pal_get_interface_description); #ifdef __cplusplus } #endif /* __cplusplus */ -#endif /* TLSIO_OPENSSL_COMPACT_H */ +#endif /* TLSIO_PAL_H */ diff --git a/pal/platform_openssl_compact.c b/pal/src/platform_openssl_compact.c similarity index 93% rename from pal/platform_openssl_compact.c rename to pal/src/platform_openssl_compact.c index b5b8df2..ec3f7ea 100644 --- a/pal/platform_openssl_compact.c +++ b/pal/src/platform_openssl_compact.c @@ -3,7 +3,7 @@ #include "azure_c_shared_utility/platform.h" #include "sntp.h" -#include "tlsio_openssl_compact.h" +#include "tlsio_pal.h" static const char* const ntpServer = "pool.ntp.org"; @@ -24,7 +24,7 @@ int platform_init(void) /* Codes_SRS_PLATFORM_OPENSSL_COMPACT_30_008: [ The platform_get_default_tlsio shall return a set of tlsio functions provided by the OpenSSL micro tlsio implementation. ] */ const IO_INTERFACE_DESCRIPTION* platform_get_default_tlsio(void) { - return tlsio_openssl_compact_get_interface_description(); + return tlsio_pal_get_interface_description(); } STRING_HANDLE platform_get_platform_info(void) diff --git a/pal/tlsio_openssl_compact.c b/pal/src/tlsio_openssl_compact.c similarity index 99% rename from pal/tlsio_openssl_compact.c rename to pal/src/tlsio_openssl_compact.c index 691f1e9..3cc5a83 100644 --- a/pal/tlsio_openssl_compact.c +++ b/pal/src/tlsio_openssl_compact.c @@ -9,10 +9,9 @@ #include #include #include "socket_async.h" -#include "tlsio_openssl_compact.h" #include "dns_async.h" +#include "tlsio_pal.h" #include "azure_c_shared_utility/gballoc.h" -#include "azure_c_shared_utility/tlsio.h" #include "azure_c_shared_utility/xlogging.h" #include "azure_c_shared_utility/agenttime.h" #include "azure_c_shared_utility/singlylinkedlist.h" @@ -873,7 +872,7 @@ static const IO_INTERFACE_DESCRIPTION tlsio_openssl_interface_description = }; /* Codes_SRS_TLSIO_30_001: [ The tlsio_openssl_compact shall implement and export all the Concrete functions in the VTable IO_INTERFACE_DESCRIPTION defined in the xio.h. ]*/ -const IO_INTERFACE_DESCRIPTION* tlsio_openssl_compact_get_interface_description(void) +const IO_INTERFACE_DESCRIPTION* tlsio_pal_get_interface_description(void) { return &tlsio_openssl_interface_description; } diff --git a/sdk b/sdk index 04aa51b..c75e27b 160000 --- a/sdk +++ b/sdk @@ -1 +1 @@ -Subproject commit 04aa51bf78ce819b6ebfda448f8c3a57c9c42b99 +Subproject commit c75e27bb85b5742f50749c5ba2473a27dbdccf0c diff --git a/sdk_tests/CMakeLists.txt b/sdk_tests/CMakeLists.txt new file mode 100644 index 0000000..cb56476 --- /dev/null +++ b/sdk_tests/CMakeLists.txt @@ -0,0 +1,9 @@ +#Copyright (c) Microsoft. All rights reserved. +#Licensed under the MIT license. See LICENSE file in the project root for full license information. + +#this is CMakeLists.txt for the folder tests of C shared utility +set(SHARED_UTIL_REAL_TEST_FOLDER ${CMAKE_CURRENT_LIST_DIR}/real_test_files CACHE INTERNAL "this is what needs to be included when doing test sources" FORCE) +message(STATUS "including external unit test directory " ${CMAKE_CURRENT_LIST_DIR}) +if(NOT DEFINED MACOSX) + add_subdirectory(tlsio_openssl_compact_ut) +endif() diff --git a/sdk_tests/tlsio_openssl_compact_ut/CMakeLists.txt b/sdk_tests/tlsio_openssl_compact_ut/CMakeLists.txt new file mode 100644 index 0000000..b5a4252 --- /dev/null +++ b/sdk_tests/tlsio_openssl_compact_ut/CMakeLists.txt @@ -0,0 +1,30 @@ +#Copyright (c) Microsoft. All rights reserved. +#Licensed under the MIT license. See LICENSE file in the project root for full license information. + +cmake_minimum_required(VERSION 2.8.11) + +compileAsC11() +set(theseTestsName tlsio_openssl_compact_ut) + +set(${theseTestsName}_test_files +${theseTestsName}.c +) + +set(${theseTestsName}_c_files +${SHARED_UTIL_SRC_FOLDER}/singlylinkedlist.c +${SHARED_UTIL_SRC_FOLDER}/crt_abstractions.c +${EXTERNAL_PAL_REPO_DIR}/pal/src/tlsio_openssl_compact.c +) + +set(${theseTestsName}_h_files +ssl_impl.h +test_defines.h +gballoc_ut_impl_1.h +gballoc_ut_impl_2.h +callbacks.h +) + +include_directories(.) +include_directories(${EXTERNAL_PAL_REPO_DIR}/pal/inc) + +build_c_test_artifacts(${theseTestsName} ON "tests/azure_c_shared_utility_tests") diff --git a/sdk_tests/tlsio_openssl_compact_ut/callbacks.h b/sdk_tests/tlsio_openssl_compact_ut/callbacks.h new file mode 100644 index 0000000..9d8da15 --- /dev/null +++ b/sdk_tests/tlsio_openssl_compact_ut/callbacks.h @@ -0,0 +1,199 @@ +// Copyright(c) Microsoft.All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +// This file is made an integral part of tlsio_openssl_compact.c with a #include. It +// is broken out for readability. + +#ifndef CALLBACKS_H +#define CALLBACKS_H + + +#define MAX_MESSAGE_COUNT 3 + +// Keep track of whether callbacks were performed as expected +static int on_io_open_complete_call_count; +static bool on_io_open_complete_context_ok; +static IO_OPEN_RESULT on_io_open_complete_result; + +static int on_io_error_call_count; +static bool on_io_error_context_ok; + +static int on_io_close_call_count; +static bool on_io_close_context_ok; + +static int on_io_send_complete_call_count; +static bool on_io_send_complete_context_ok; +static IO_SEND_RESULT on_io_send_complete_result[MAX_MESSAGE_COUNT]; + +static int on_bytes_received_call_count; +static bool on_bytes_received_context_ok; + +// Context pointers for the callbacks +#define IO_OPEN_COMPLETE_CONTEXT (void*)55 +#define IO_ERROR_CONTEXT (void*)66 +#define IO_BYTES_RECEIVED_CONTEXT (void*)77 +#define IO_CLOSE_COMPLETE_CONTEXT (void*)231 +#define IO_SEND_COMPLETE_CONTEXT (void*)7658 + +static void reset_callback_context_records() +{ + int i; + on_io_open_complete_call_count = 0; + on_io_open_complete_context_ok = true; + on_io_open_complete_result = (IO_OPEN_RESULT)-1; + on_io_error_call_count = 0; + on_io_error_context_ok = true; + on_io_close_call_count = 0; + on_io_close_context_ok = true; + on_io_send_complete_call_count = 0; + on_io_send_complete_context_ok = true; + for (i = 0; i < MAX_MESSAGE_COUNT; i++) + { + on_io_send_complete_result[i] = (IO_SEND_RESULT)-1; + } + on_bytes_received_call_count = 0; + on_bytes_received_context_ok = true; +} + +// Callbacks used by the tlsio adapter + +static void on_io_open_complete(void* context, IO_OPEN_RESULT open_result) +{ + bool result_valid = open_result == IO_OPEN_OK || open_result == IO_OPEN_ERROR || open_result == IO_OPEN_CANCELLED; + ASSERT_IS_TRUE_WITH_MSG(result_valid, "Invalid IO_OPEN_RESULT"); + on_io_open_complete_call_count++; + on_io_open_complete_result = open_result; + if (context != IO_OPEN_COMPLETE_CONTEXT) + { + on_io_open_complete_context_ok = false; + } +} + +static void on_io_send_complete(void* context, IO_SEND_RESULT send_result) +{ + on_io_send_complete_result[on_io_send_complete_call_count] = send_result; + on_io_send_complete_call_count++; + if (context != IO_SEND_COMPLETE_CONTEXT) + { + on_io_send_complete_context_ok = false; + } +} + +static void on_io_close_complete(void* context) +{ + on_io_close_call_count++; + if (context != IO_CLOSE_COMPLETE_CONTEXT) + { + on_io_close_context_ok = false; + } +} + +static void on_bytes_received(void* context, const unsigned char* buffer, size_t size) +{ + size_t i; + on_bytes_received_call_count++; + + for (i = 0; i < size; i++) + { + ASSERT_BYTE_RECEIVED(buffer[i]); + } + if (context != IO_BYTES_RECEIVED_CONTEXT) + { + on_bytes_received_context_ok = false; + } +} + +static void on_io_error(void* context) +{ + on_io_error_call_count = true; + if (context != IO_ERROR_CONTEXT) + { + on_io_error_context_ok = false; + } +} + +static void ASSERT_IO_ERROR_CALLBACK(bool called) +{ + int count = called ? 1 : 0; + ASSERT_ARE_EQUAL_WITH_MSG(int, count, on_io_error_call_count, "io_error_callback count mismatch"); + if (count > 0) + { + ASSERT_IS_TRUE_WITH_MSG(on_io_error_context_ok, "io_error_callback missing context"); + } +} + +static void ASSERT_IO_OPEN_CALLBACK(bool called, int open_result) +{ + if (called) + { + ASSERT_ARE_EQUAL_WITH_MSG(int, 1, on_io_open_complete_call_count, "on_io_open_complete_callback count mismatch"); + ASSERT_ARE_EQUAL_WITH_MSG(int, on_io_open_complete_result, open_result, "on_io_open_complete result mismatch"); + ASSERT_IS_TRUE_WITH_MSG(on_io_open_complete_context_ok, "io_open_complete_context not passed"); + } + else + { + ASSERT_ARE_EQUAL_WITH_MSG(int, 0, on_io_open_complete_call_count, "unexpected on_io_open_complete_callback"); + } +} + +static void ASSERT_IO_SEND_CALLBACK(bool called, int send_result) +{ + if (called) + { + ASSERT_ARE_EQUAL_WITH_MSG(int, 1, on_io_send_complete_call_count, "on_io_send_complete_callback count mismatch"); + ASSERT_ARE_EQUAL_WITH_MSG(int, on_io_send_complete_result[0], send_result, "on_io_send_complete result mismatch"); + ASSERT_IS_TRUE_WITH_MSG(on_io_send_complete_context_ok, "io_send_complete_context not passed"); + } + else + { + ASSERT_ARE_EQUAL_WITH_MSG(int, 0, on_io_open_complete_call_count, "unexpected on_io_open_complete_callback"); + } +} + +static void ASSERT_IO_SEND_ABANDONED(int count) +{ + int i; + ASSERT_ARE_EQUAL_WITH_MSG(int, count, on_io_send_complete_call_count, "on_io_send_complete_callback count mismatch"); + ASSERT_IS_TRUE_WITH_MSG(on_io_send_complete_context_ok, "io_send_complete_context not passed"); + for (i = 0; i < on_io_send_complete_call_count; i++) + { + ASSERT_ARE_EQUAL_WITH_MSG(int, IO_SEND_CANCELLED, on_io_send_complete_result[i], "send result should be IO_SEND_CANCELLED"); + } +} + +static void ASSERT_IO_CLOSE_CALLBACK(bool called) +{ + if (called) + { + ASSERT_ARE_EQUAL_WITH_MSG(int, 1, on_io_close_call_count, "on_io_close_complete_callback count mismatch"); + ASSERT_IS_TRUE_WITH_MSG(on_io_close_context_ok, "io_close_complete_context not passed"); + } + else + { + ASSERT_ARE_EQUAL_WITH_MSG(int, 0, on_io_close_call_count, "unexpected on_io_close_complete_callback"); + } +} + +static void ASSERT_BYTES_RECEIVED_CALLBACK(bool called, size_t message_size) +{ + if (called) + { + ASSERT_ARE_EQUAL_WITH_MSG(size_t, message_size, fake_read_bytes_received, "bytes_received count mismatch"); + ASSERT_IS_TRUE_WITH_MSG(on_bytes_received_context_ok, "bytes_received_context not passed"); + } + else + { + ASSERT_ARE_EQUAL_WITH_MSG(int, 0, on_bytes_received_call_count, "unexpected bytes_received_callback"); + } +} + +static void ASSERT_NO_CALLBACKS() +{ + ASSERT_IO_ERROR_CALLBACK(false); + ASSERT_IO_OPEN_CALLBACK(false, IO_OPEN_ERROR); + ASSERT_IO_SEND_CALLBACK(false, IO_SEND_ERROR); + ASSERT_IO_CLOSE_CALLBACK(false); + ASSERT_BYTES_RECEIVED_CALLBACK(false, 0); +} + +#endif // CALLBACKS_H diff --git a/sdk_tests/tlsio_openssl_compact_ut/gballoc_ut_impl_1.h b/sdk_tests/tlsio_openssl_compact_ut/gballoc_ut_impl_1.h new file mode 100644 index 0000000..db9bf05 --- /dev/null +++ b/sdk_tests/tlsio_openssl_compact_ut/gballoc_ut_impl_1.h @@ -0,0 +1,44 @@ +// Copyright(c) Microsoft.All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +// This file is made an integral part of a unit test implementation with a #include. It +// is broken out for reuse and readability. + +#ifndef GBALLOC_UT_IMPL_1_H +#define GBALLOC_UT_IMPL_1_H + +// These functions add memory leak checking to the unit test itself rather than +// relying on Valgrind, which is inconvenient for troubleshooting. +static void add_gballoc_memory_block(void* block); +static void remove_gballoc_memory_block(void* block); + +/** +* The gballoc.h will replace the malloc, free, and realloc by the my_gballoc functions, in this case, +* if you define these mock functions after include the gballoc.h, you will create an infinity recursion, +* so, places the my_gballoc functions before the #include "azure_c_shared_utility/gballoc.h" +*/ +static void* my_gballoc_malloc(size_t size) +{ + void* result = malloc(size); + add_gballoc_memory_block(result); + return result; +} + +static void* my_gballoc_realloc(void* ptr, size_t size) +{ + void* result; + remove_gballoc_memory_block(ptr); + result = realloc(ptr, size); + add_gballoc_memory_block(result); + return result; +} + +static void my_gballoc_free(void* ptr) +{ + remove_gballoc_memory_block(ptr); + free(ptr); +} + +#endif // GBALLOC_UT_IMPL_1_H + + diff --git a/sdk_tests/tlsio_openssl_compact_ut/gballoc_ut_impl_2.h b/sdk_tests/tlsio_openssl_compact_ut/gballoc_ut_impl_2.h new file mode 100644 index 0000000..0dbabd7 --- /dev/null +++ b/sdk_tests/tlsio_openssl_compact_ut/gballoc_ut_impl_2.h @@ -0,0 +1,85 @@ +// Copyright(c) Microsoft.All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +// This file is made an integral part of a unit test implementation with a #include. It +// is broken out for reuse and readability. + +#ifndef GBALLOC_UT_IMPL_2_H +#define GBALLOC_UT_IMPL_2_H + +#include + +// This adds memory checking to the gballoc mocking +// NOTE: Using ASSERTs wouuld be nice, but this file must preceed their definition +// to avoid the malloc infinite recursion problem + + +#ifndef GBALLOC_UT_IMPL_MAX_ALLOCS +#define GBALLOC_UT_IMPL_MAX_ALLOCS 1000 +#endif + +static void* memory_blocks[GBALLOC_UT_IMPL_MAX_ALLOCS]; +static uint32_t memory_block_count; + + +static void init_gballoc_checks() +{ + uint32_t i; + memory_block_count = 0; + for (i = 0; i < GBALLOC_UT_IMPL_MAX_ALLOCS; i++) + { + memory_blocks[i] = NULL; + } +} + +static void add_gballoc_memory_block(void* block) +{ + if (block != NULL) + { + if (memory_block_count < GBALLOC_UT_IMPL_MAX_ALLOCS) + { + memory_blocks[memory_block_count] = block; + memory_block_count++; + } + else + { + ASSERT_FAIL("GBALLOC_UT_IMPL_MAX_ALLOCS is too small in add_gballoc_memory_block"); + } + } +} + +static void remove_gballoc_memory_block(void* block) +{ + if (block != NULL) + { + bool found = false; + uint32_t i; + for (i = 0; i < memory_block_count; i++) + { + if (memory_blocks[i] == block) + { + memory_blocks[i] = NULL; + found = true; + break; + } + } + if (!found) + { + ASSERT_FAIL("unknown block in remove_gballoc_memory_block"); + } + } +} + +static void assert_gballoc_checks() +{ + uint32_t i; + for (i = 0; i < memory_block_count; i++) + { + if (memory_blocks[i] != NULL) + { + ASSERT_FAIL("undeleted memory block"); + } + } +} + +#endif // GBALLOC_UT_IMPL_2_H diff --git a/sdk_tests/tlsio_openssl_compact_ut/main.c b/sdk_tests/tlsio_openssl_compact_ut/main.c new file mode 100644 index 0000000..cb56cfe --- /dev/null +++ b/sdk_tests/tlsio_openssl_compact_ut/main.c @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +#include "testrunnerswitcher.h" + +int main(void) +{ + size_t failedTestCount = 0; + /** + * Identify the test suite to run here. + */ + RUN_TEST_SUITE(tlsio_openssl_compact_unittests, failedTestCount); + + return failedTestCount; +} diff --git a/sdk_tests/tlsio_openssl_compact_ut/openssl/ssl.h b/sdk_tests/tlsio_openssl_compact_ut/openssl/ssl.h new file mode 100644 index 0000000..2d594e7 --- /dev/null +++ b/sdk_tests/tlsio_openssl_compact_ut/openssl/ssl.h @@ -0,0 +1,44 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +#ifndef OPEN_SSL_H +#define OPEN_SSL_H + +#ifdef __cplusplus + +extern "C" { +#include +#else +#include +#endif /* __cplusplus */ + +#include "azure_c_shared_utility/umock_c_prod.h" + +// This header mocks the small subset of the the OpenSSL ssl.h needed for tlsio_openssl_compact testing + +typedef void SSL; +typedef void SSL_CTX; + +#define SSL_ERROR_WANT_READ 2 +#define SSL_ERROR_WANT_WRITE 3 + +int TLSv1_2_client_method(); +void SSL_CTX_set_default_read_buffer_len(SSL_CTX* dummy, int dummy2); + + +MOCKABLE_FUNCTION(, void, SSL_free, SSL*, ssl); +MOCKABLE_FUNCTION(, void, SSL_CTX_free, SSL_CTX*, ctx); +MOCKABLE_FUNCTION(, SSL_CTX*, SSL_CTX_new, int, dummy); +MOCKABLE_FUNCTION(, SSL*, SSL_new, SSL_CTX*, dummy); +MOCKABLE_FUNCTION(, int, SSL_set_fd, SSL*, dummy, int, dummy2); +MOCKABLE_FUNCTION(, int, SSL_connect, SSL*, dummy); +MOCKABLE_FUNCTION(, int, SSL_write, SSL*, dummy, uint8_t*, buffer, size_t, size); +MOCKABLE_FUNCTION(, int, SSL_read, SSL*, dummy, uint8_t*, buffer, size_t, size); +MOCKABLE_FUNCTION(, int, SSL_shutdown, SSL*, dummy); +MOCKABLE_FUNCTION(, int, SSL_get_error, SSL_CTX*, dummy, int, last_value); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif // OPEN_SSL_H diff --git a/sdk_tests/tlsio_openssl_compact_ut/ssl_impl.h b/sdk_tests/tlsio_openssl_compact_ut/ssl_impl.h new file mode 100644 index 0000000..baf3125 --- /dev/null +++ b/sdk_tests/tlsio_openssl_compact_ut/ssl_impl.h @@ -0,0 +1,132 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +// This file is made an integral part of tlsio_openssl_compact.c with a #include. It +// is broken out for readability. + +#ifndef SSL_ERRORS_H +#define SSL_ERRORS_H + +// +#define SSL_ERROR__plus__WANT_READ -2 +#define SSL_ERROR__plus__WANT_WRITE -3 +#define SSL_ERROR__plus__HARD_FAIL -4 + +#define SSL_ERROR_HARD_FAIL 99 +#define SSL_Good_Ptr (void*)22 +#define SSL_Good_Context_Ptr (SSL_CTX*)33 +#define SSL_Good_Socket 44 +#define SSL_CONNECT_SUCCESS 0 +#define SSL_SET_FD_SUCCESS 1 +#define SSL_SET_FD_FAILURE 0 +#define SSL_READ_NO_DATA 0 + +#define SSL_Get_IPv4_OK (uint32_t)0x11223344 +#define SSL_Get_IPv4_FAIL 0 + +#define SSL_good_port_number 447 +#define SSL_port_number_too_low -1 +#define SSL_port_number_too_high 0xffff + 1 +#define SSL_good_host_name "fakehost.com" +#define SSL_good_old_host_name "fakehost.com" +uint8_t* SSL_send_buffer = (uint8_t*)"111111112222222233333333"; +size_t SSL_send_message_size = sizeof(SSL_send_buffer) - 1; + +#define DOWORK_RECV_XFER_BUFFER_SIZE 64 +#define SSL_TEST_MESSAGE_SIZE 64 +#define SSL_WRITE_MAX_TEST_SIZE 60 +#define SSL_SHORT_SENT_MESSAGE_SIZE 30 +#define SSL_FAIL_ME_SENT_MESSAGE_SIZE 1700 +#define SSL_SHORT_RECEIVED_MESSAGE_SIZE 15 +#define SSL_LONG_RECEIVED_MESSAGE_SIZE 1500 + +static size_t fake_read_byte_out_count = 0; +static size_t fake_read_current_byte_out_count = 0; +static size_t fake_read_bytes_received = 0; + + +// The fact that SSL_get_error requires the previous error allows a mocking strategy that +// permits encoding the extended error into the main failure +static int my_SSL_get_error(SSL* ssl, int callReturn) +{ + (void)ssl; + switch (callReturn) + { + case SSL_ERROR__plus__WANT_READ: + return SSL_ERROR_WANT_READ; + case SSL_ERROR__plus__WANT_WRITE: + return SSL_ERROR_WANT_WRITE; + case SSL_ERROR__plus__HARD_FAIL: + return SSL_ERROR_HARD_FAIL; + } + // This foolish-looking code dances around conflicting warnings + // in the C and C++ compilers about no return path + if (callReturn <= 0) + { + ASSERT_FAIL("bad enum"); + } + return 0; +} + +static void init_fake_read(size_t byte_count) +{ + fake_read_byte_out_count = byte_count; + fake_read_current_byte_out_count = 0; + fake_read_bytes_received = 0; +} + +static void ASSERT_BYTE_RECEIVED(uint8_t byte) +{ + ASSERT_ARE_EQUAL(size_t, (size_t)byte, (size_t)(fake_read_bytes_received % 256)); + fake_read_bytes_received++; +} + +int my_SSL_read(SSL* ssl, uint8_t* buffer, size_t size) +{ + size_t bytes_to_receive; + size_t i; + (void)size; + ASSERT_ARE_EQUAL(size_t, (size_t)ssl, (size_t)SSL_Good_Ptr); + bytes_to_receive = fake_read_byte_out_count - fake_read_current_byte_out_count; + bytes_to_receive = bytes_to_receive <= size ? bytes_to_receive : size; + for (i = 0; i < bytes_to_receive; i++) + { + buffer[i] = (uint8_t)(fake_read_current_byte_out_count % 256); + fake_read_current_byte_out_count++; + } + return (int)bytes_to_receive; +} + +int my_SSL_write(SSL* ssl, uint8_t* buffer, size_t size) +{ + int result; + // "Send" no more than SSL_WRITE_MAX_TEST_SIZE bytes + (void)buffer; // not used + ASSERT_ARE_EQUAL(size_t, (size_t)ssl, (size_t)SSL_Good_Ptr); + if (size == SSL_FAIL_ME_SENT_MESSAGE_SIZE) + { + result = SSL_ERROR__plus__HARD_FAIL; + } + else + { + if (size > SSL_WRITE_MAX_TEST_SIZE) + { + result = SSL_WRITE_MAX_TEST_SIZE; + } + else + { + result = (int)size; + } + } + return result; +} + +///////////////////////////////////////////////////////////////////// +// Empty functions. These must be available to call, but they have no effect +int TLSv1_2_client_method() { return 0; } +void SSL_CTX_set_default_read_buffer_len(SSL_CTX* dummy, int dummy2) { (void)dummy; (void)dummy2; } + +// End of empty functions +///////////////////////////////////////////////////////////////////// + +#endif // SSL_ERRORS_H diff --git a/sdk_tests/tlsio_openssl_compact_ut/test_defines.h b/sdk_tests/tlsio_openssl_compact_ut/test_defines.h new file mode 100644 index 0000000..7940196 --- /dev/null +++ b/sdk_tests/tlsio_openssl_compact_ut/test_defines.h @@ -0,0 +1,43 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +// This file is made an integral part of tlsio_openssl_compact.c with a #include. It +// is broken out for readability. + +#ifndef TEST_DEFINES_H +#define TEST_DEFINES_H + +#define GOOD_DNS_ASYNC_HANDLE (void*)0x12345678 + +#define SETOPTION_PV_COUNT 3 +#define OPEN_PV_COUNT 4 +#define SEND_PV_COUNT 4 +#define CLOSE_PV_COUNT 2 + +static TLSIO_CONFIG good_config = { SSL_good_host_name, SSL_good_port_number, NULL, NULL }; +static TLSIO_CONFIG tlsio_config = { NULL, SSL_good_port_number, NULL, NULL }; + +static bool bool_true = true; +static bool bool_false = false; +static size_t sizeof_bool = sizeof(bool); + +typedef struct +{ + TLSIO_CONFIG* config; + const char* fail_msg; +} create_parameters_t; + +void populate_create_parameters(create_parameters_t* p, TLSIO_CONFIG* config, const char* hostname, int port, const char* fail_msg) +{ + p->config = config; + if (config != NULL) + { + config->hostname = hostname; + config->port = port; + config->underlying_io_interface = NULL; + config->underlying_io_parameters = NULL; + } + p->fail_msg = fail_msg; +} + +#endif // TEST_DEFINES_H diff --git a/sdk_tests/tlsio_openssl_compact_ut/tlsio_openssl_compact_ut.c b/sdk_tests/tlsio_openssl_compact_ut/tlsio_openssl_compact_ut.c new file mode 100644 index 0000000..e29e336 --- /dev/null +++ b/sdk_tests/tlsio_openssl_compact_ut/tlsio_openssl_compact_ut.c @@ -0,0 +1,1598 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +#include + +#ifdef __cplusplus +#include +#else +#include +#include +#endif + +/** + * Include the C standards here. + */ +#ifdef __cplusplus +#include +#include +#else +#include +#include +#endif + +/** + * The gballoc.h will replace the malloc, free, and realloc by the my_gballoc functions, in this case, + * if you define these mock functions after include the gballoc.h, you will create an infinity recursion, + * so, places the my_gballoc functions before the #include "azure_c_shared_utility/gballoc.h" + */ +#include "gballoc_ut_impl_1.h" + + /** + * Include the mockable headers here. + * These are the headers that contains the functions that you will replace to execute the test. + */ +#define ENABLE_MOCKS +#include "azure_c_shared_utility/gballoc.h" +#include "dns_async.h" +#include "socket_async.h" +#include "openssl/ssl.h" + +static int my_socket_async_is_create_complete(SOCKET_ASYNC_HANDLE sock, bool* is_complete) +{ + (void)sock; + *is_complete = true; + return 0; +} +#undef ENABLE_MOCKS + +/** + * Include the test tools. + */ +#include "tlsio_pal.h" +#include "testrunnerswitcher.h" +#include "umock_c.h" +#include "umocktypes_charptr.h" +#include "umocktypes_bool.h" +#include "umocktypes_stdint.h" +#include "umock_c_negative_tests.h" +#include "azure_c_shared_utility/macro_utils.h" +#include "azure_c_shared_utility/xio.h" +#include "azure_c_shared_utility/tlsio.h" + +const IO_INTERFACE_DESCRIPTION* tlsio_id; +// These "headers" are actually source files that are broken out of this file for readability +//#include "unit_test_api.h" +#include "ssl_impl.h" +#include "callbacks.h" +#include "test_defines.h" +#include "gballoc_ut_impl_2.h" + +DEFINE_ENUM_STRINGS(UMOCK_C_ERROR_CODE, UMOCK_C_ERROR_CODE_VALUES) + +static void on_umock_c_error(UMOCK_C_ERROR_CODE error_code) +{ + char temp_str[256]; + (void)snprintf(temp_str, sizeof(temp_str), "umock_c reported error :%s", ENUM_TO_STRING(UMOCK_C_ERROR_CODE, error_code)); + ASSERT_FAIL(temp_str); +} + +/** + * This is necessary for the test suite, just keep as is. + */ +static TEST_MUTEX_HANDLE g_testByTest; +static TEST_MUTEX_HANDLE g_dllByDll; +static bool negative_mocks_used = false; + + +BEGIN_TEST_SUITE(tlsio_openssl_compact_unittests) + + TEST_SUITE_INITIALIZE(a) + { + int result; + size_t type_size; + TEST_INITIALIZE_MEMORY_DEBUG(g_dllByDll); + g_testByTest = TEST_MUTEX_CREATE(); + ASSERT_IS_NOT_NULL(g_testByTest); + tlsio_id = tlsio_pal_get_interface_description(); + + (void)umock_c_init(on_umock_c_error); + + result = umocktypes_charptr_register_types(); + ASSERT_ARE_EQUAL(int, 0, result); + result = umocktypes_bool_register_types(); + ASSERT_ARE_EQUAL(int, 0, result); + umocktypes_stdint_register_types(); + ASSERT_ARE_EQUAL(int, 0, result); + + REGISTER_UMOCK_ALIAS_TYPE(SSL, void*); + REGISTER_UMOCK_ALIAS_TYPE(SSL_CTX, void*); + REGISTER_UMOCK_ALIAS_TYPE(SOCKET_ASYNC_OPTIONS_HANDLE, void*); + REGISTER_UMOCK_ALIAS_TYPE(SOCKET_ASYNC_HANDLE, int); + REGISTER_UMOCK_ALIAS_TYPE(DNS_ASYNC_HANDLE, void*); + type_size = sizeof(time_t); + if (type_size == sizeof(uint64_t)) + { + REGISTER_UMOCK_ALIAS_TYPE(time_t, uint64_t); + } + else if (type_size == sizeof(uint32_t)) + { + REGISTER_UMOCK_ALIAS_TYPE(time_t, uint32_t); + } + else + { + ASSERT_FAIL("Bad size_t size"); + } + + REGISTER_GLOBAL_MOCK_RETURNS(dns_async_create, GOOD_DNS_ASYNC_HANDLE, NULL); + REGISTER_GLOBAL_MOCK_RETURNS(dns_async_is_lookup_complete, true, false); + REGISTER_GLOBAL_MOCK_RETURNS(dns_async_get_ipv4, SSL_Get_IPv4_OK, SSL_Get_IPv4_FAIL); + + REGISTER_GLOBAL_MOCK_RETURNS(socket_async_create, SSL_Good_Socket, -1); + REGISTER_GLOBAL_MOCK_HOOK(socket_async_is_create_complete, my_socket_async_is_create_complete); + + REGISTER_GLOBAL_MOCK_RETURNS(SSL_new, SSL_Good_Ptr, NULL); + REGISTER_GLOBAL_MOCK_RETURNS(SSL_CTX_new, SSL_Good_Context_Ptr, NULL); + REGISTER_GLOBAL_MOCK_RETURNS(SSL_set_fd, SSL_SET_FD_SUCCESS, SSL_SET_FD_FAILURE); + REGISTER_GLOBAL_MOCK_RETURNS(SSL_connect, SSL_CONNECT_SUCCESS, SSL_ERROR__plus__HARD_FAIL); + REGISTER_GLOBAL_MOCK_RETURNS(SSL_get_error, SSL_ERROR_WANT_READ, SSL_ERROR_HARD_FAIL); + REGISTER_GLOBAL_MOCK_HOOK(SSL_write, my_SSL_write); + REGISTER_GLOBAL_MOCK_HOOK(SSL_read, my_SSL_read); + REGISTER_GLOBAL_MOCK_HOOK(SSL_get_error, my_SSL_get_error); + + /** + * Or you can combine, for example, in the success case malloc will call my_gballoc_malloc, and for + * the failed cases, it will return NULL. + */ + REGISTER_GLOBAL_MOCK_HOOK(gballoc_malloc, my_gballoc_malloc); + REGISTER_GLOBAL_MOCK_FAIL_RETURN(gballoc_malloc, NULL); + REGISTER_GLOBAL_MOCK_HOOK(gballoc_free, my_gballoc_free); + + tlsio_config.hostname = SSL_good_old_host_name; + } + + static void use_negative_mocks() + { + int negativeTestsInitResult = umock_c_negative_tests_init(); + negative_mocks_used = true; + ASSERT_ARE_EQUAL(int, 0, negativeTestsInitResult); + } + + /** + * The test suite will call this function to cleanup your machine. + * It is called only once, after all tests is done. + */ + TEST_SUITE_CLEANUP(TestClassCleanup) + { + umock_c_deinit(); + + TEST_MUTEX_DESTROY(g_testByTest); + TEST_DEINITIALIZE_MEMORY_DEBUG(g_dllByDll); + } + + /** + * The test suite will call this function to prepare the machine for the new test. + * It is called before execute each test. + */ + TEST_FUNCTION_INITIALIZE(initialize) + { + if (TEST_MUTEX_ACQUIRE(g_testByTest)) + { + ASSERT_FAIL("Could not acquire test serialization mutex."); + } + + umock_c_reset_all_calls(); + reset_callback_context_records(); + init_gballoc_checks(); + } + + /** + * The test suite will call this function to cleanup your machine for the next test. + * It is called after execute each test. + */ + TEST_FUNCTION_CLEANUP(cleans) + { + if (negative_mocks_used) + { + negative_mocks_used = false; + umock_c_negative_tests_deinit(); + } + TEST_MUTEX_RELEASE(g_testByTest); + } + + static void open_helper(CONCRETE_IO_HANDLE tlsio) + { + int open_result; + reset_callback_context_records(); + open_result = tlsio_id->concrete_io_open(tlsio, on_io_open_complete, IO_OPEN_COMPLETE_CONTEXT, on_bytes_received, + IO_BYTES_RECEIVED_CONTEXT, on_io_error, IO_ERROR_CONTEXT); + ASSERT_ARE_EQUAL(int, open_result, 0); + + // Pump dowork until it opens + tlsio_id->concrete_io_dowork(tlsio); // dowork_poll_dns (done) + tlsio_id->concrete_io_dowork(tlsio); // dowork_poll_socket (done) + ASSERT_IO_OPEN_CALLBACK(false, IO_OPEN_ERROR); + tlsio_id->concrete_io_dowork(tlsio); // dowork_poll_open_ssl (finishes Open) + ASSERT_IO_OPEN_CALLBACK(true, IO_OPEN_OK); + } + + /* Tests_SRS_TLSIO_30_201: [ The "high-level retry sequence" shall succeed after an injected fault which causes on_io_error to be called. ]*/ + TEST_FUNCTION(tlsio_openssl_compact__retry_open_after_io_failure__succeeds) + { + int send_result; + int open_result; + ///arrange + CONCRETE_IO_HANDLE tlsio = tlsio_id->concrete_io_create(&good_config); + reset_callback_context_records(); + open_helper(tlsio); + + // Send the message to eventually fail on + send_result = tlsio_id->concrete_io_send(tlsio, SSL_send_buffer, + SSL_FAIL_ME_SENT_MESSAGE_SIZE, on_io_send_complete, IO_SEND_COMPLETE_CONTEXT); + ASSERT_ARE_EQUAL(int, 0, send_result); + + umock_c_reset_all_calls(); + reset_callback_context_records(); + // Make sure the io fails + tlsio_id->concrete_io_dowork(tlsio); + ASSERT_IO_ERROR_CALLBACK(true); + + // Close the error'd tlsio + tlsio_id->concrete_io_close(tlsio, on_io_close_complete, IO_CLOSE_COMPLETE_CONTEXT); + ASSERT_IO_CLOSE_CALLBACK(true); + + // Retry the open + open_result = tlsio_id->concrete_io_open(tlsio, on_io_open_complete, IO_OPEN_COMPLETE_CONTEXT, on_bytes_received, + IO_BYTES_RECEIVED_CONTEXT, on_io_error, IO_ERROR_CONTEXT); + ASSERT_ARE_EQUAL(int, open_result, 0); + + tlsio_id->concrete_io_dowork(tlsio); // dowork_poll_dns (done) + tlsio_id->concrete_io_dowork(tlsio); // dowork_poll_socket (done) + + reset_callback_context_records(); + + ///act + // dowork_poll_open_ssl (done) + tlsio_id->concrete_io_dowork(tlsio); + + ///assert + // Check that we got the on_open callback for our retry + ASSERT_IO_OPEN_CALLBACK(true, IO_OPEN_OK); + + ///cleanup + tlsio_id->concrete_io_close(tlsio, on_io_close_complete, NULL); + tlsio_id->concrete_io_destroy(tlsio); + assert_gballoc_checks(); + } + + /* Tests_SRS_TLSIO_30_200: [ The "high-level retry sequence" shall succeed after an injected fault which causes on_io_open_complete to return with IO_OPEN_ERROR. ]*/ + TEST_FUNCTION(tlsio_openssl_compact__retry_open_after_open_failure__succeeds) + { + ///arrange + CONCRETE_IO_HANDLE tlsio = tlsio_id->concrete_io_create(&good_config); + int open_result = tlsio_id->concrete_io_open(tlsio, on_io_open_complete, IO_OPEN_COMPLETE_CONTEXT, on_bytes_received, + IO_BYTES_RECEIVED_CONTEXT, on_io_error, IO_ERROR_CONTEXT); + ASSERT_ARE_EQUAL(int, open_result, 0); + ASSERT_IO_OPEN_CALLBACK(false, IO_OPEN_ERROR); + umock_c_reset_all_calls(); + + // dowork_poll_open_ssl (done) Fail the SSL_connect call + STRICT_EXPECTED_CALL(SSL_connect(SSL_Good_Ptr)).SetReturn(SSL_ERROR__plus__HARD_FAIL); + + tlsio_id->concrete_io_dowork(tlsio); // dowork_poll_dns (done) + tlsio_id->concrete_io_dowork(tlsio); // dowork_poll_socket (done) + tlsio_id->concrete_io_dowork(tlsio); // dowork_poll_open_ssl (done) + ASSERT_IO_OPEN_CALLBACK(true, IO_OPEN_ERROR); + + // Close the error'd tlsio + tlsio_id->concrete_io_close(tlsio, on_io_close_complete, NULL); + + // Retry the open + umock_c_reset_all_calls(); + open_result = tlsio_id->concrete_io_open(tlsio, on_io_open_complete, IO_OPEN_COMPLETE_CONTEXT, on_bytes_received, + IO_BYTES_RECEIVED_CONTEXT, on_io_error, IO_ERROR_CONTEXT); + ASSERT_ARE_EQUAL(int, open_result, 0); + tlsio_id->concrete_io_dowork(tlsio); // dowork_poll_dns (done) + tlsio_id->concrete_io_dowork(tlsio); // dowork_poll_socket (done) + reset_callback_context_records(); + + ///act + tlsio_id->concrete_io_dowork(tlsio); + + ///assert + // Check that we got the on_open callback for our retry + ASSERT_IO_OPEN_CALLBACK(true, IO_OPEN_OK); + + ///cleanup + tlsio_id->concrete_io_close(tlsio, on_io_close_complete, NULL); + tlsio_id->concrete_io_destroy(tlsio); + assert_gballoc_checks(); + } + + /* Tests_SRS_TLSIO_30_050: [ If the tlsio_handle parameter is NULL, tlsio_close shall log an error and return FAILURE. ]*/ + /* Tests_SRS_TLSIO_30_055: [ If the on_io_close_complete parameter is NULL, tlsio_close shall log an error and return FAILURE. ]*/ + /* Tests_SRS_TLSIO_30_054: [ On failure, the adapter shall not call on_io_close_complete. ]*/ + TEST_FUNCTION(tlsio_openssl_compact__close_parameter_validation__fails) + { + int k; + bool p0[CLOSE_PV_COUNT]; + ON_IO_CLOSE_COMPLETE p1[CLOSE_PV_COUNT]; + const char* fm[CLOSE_PV_COUNT]; + int i; + + ///arrange + CONCRETE_IO_HANDLE tlsio = tlsio_id->concrete_io_create(&good_config); + + umock_c_reset_all_calls(); + + k = 0; + p0[k] = false; p1[k] = on_io_close_complete; fm[k] = "Unexpected close success when tlsio_handle is NULL"; /* */ k++; + p0[k] = true; p1[k] = NULL; /* */ fm[k] = "Unexpected close success when on_io_close_complete is NULL"; k++; + + // Cycle through each failing combo of parameters + for (i = 0; i < CLOSE_PV_COUNT; i++) + { + int close_result; + reset_callback_context_records(); + ///arrange + + ///act + close_result = tlsio_id->concrete_io_close(p0[i] ? tlsio : NULL, p1[i], IO_CLOSE_COMPLETE_CONTEXT); + + ///assert + ASSERT_ARE_NOT_EQUAL_WITH_MSG(int, 0, close_result, fm[i]); + ASSERT_IO_CLOSE_CALLBACK(false); + } + + ///cleanup + tlsio_id->concrete_io_destroy(tlsio); + assert_gballoc_checks(); + } + + /* Tests_SRS_TLSIO_30_009: [ The phrase "enter TLSIO_STATE_EXT_CLOSING" means the adapter shall iterate through any unsent messages in the queue and shall delete each message after calling its on_send_complete with the associated callback_context and IO_SEND_CANCELLED . ]*/ + /* Tests_SRS_TLSIO_30_056: [ On success the adapter shall enter TLSIO_STATE_EX_CLOSING. ]*/ + TEST_FUNCTION(tlsio_openssl_compact__close_with_unsent_messages__succeeds) + { + ///arrange + int send_result; + int close_result; + CONCRETE_IO_HANDLE tlsio = tlsio_id->concrete_io_create(&good_config); + reset_callback_context_records(); + open_helper(tlsio); + + // Make sure the arrangement is correct + ASSERT_IO_OPEN_CALLBACK(true, IO_OPEN_OK); + + send_result = tlsio_id->concrete_io_send(tlsio, SSL_send_buffer, + SSL_SHORT_SENT_MESSAGE_SIZE, on_io_send_complete, IO_SEND_COMPLETE_CONTEXT); + ASSERT_ARE_EQUAL(int, send_result, 0); + send_result = tlsio_id->concrete_io_send(tlsio, SSL_send_buffer, + SSL_SHORT_SENT_MESSAGE_SIZE, on_io_send_complete, IO_SEND_COMPLETE_CONTEXT); + ASSERT_ARE_EQUAL(int, send_result, 0); + + + umock_c_reset_all_calls(); + STRICT_EXPECTED_CALL(SSL_shutdown(SSL_Good_Ptr)); + STRICT_EXPECTED_CALL(SSL_free(SSL_Good_Ptr)); + STRICT_EXPECTED_CALL(SSL_CTX_free(SSL_Good_Context_Ptr)); + STRICT_EXPECTED_CALL(socket_async_destroy(SSL_Good_Socket)); + // Message 1 delete + STRICT_EXPECTED_CALL(gballoc_free(IGNORED_PTR_ARG)); + STRICT_EXPECTED_CALL(gballoc_free(IGNORED_PTR_ARG)); + STRICT_EXPECTED_CALL(gballoc_free(IGNORED_PTR_ARG)); + // Message 2 delete + STRICT_EXPECTED_CALL(gballoc_free(IGNORED_PTR_ARG)); + STRICT_EXPECTED_CALL(gballoc_free(IGNORED_PTR_ARG)); + STRICT_EXPECTED_CALL(gballoc_free(IGNORED_PTR_ARG)); + reset_callback_context_records(); + // End of arrange + + ///act + close_result = tlsio_id->concrete_io_close(tlsio, on_io_close_complete, IO_CLOSE_COMPLETE_CONTEXT); + + ///assert + ASSERT_ARE_EQUAL(int, 0, close_result); + ASSERT_IO_CLOSE_CALLBACK(true); + ASSERT_IO_SEND_ABANDONED(2); // 2 messages in this test + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + + + ///cleanup + tlsio_id->concrete_io_close(tlsio, on_io_close_complete, NULL); + tlsio_id->concrete_io_destroy(tlsio); + assert_gballoc_checks(); + } + + /* Tests_SSRS_TLSIO_30_053: [ If the adapter is in any state other than TLSIO_STATE_EXT_OPEN or TLSIO_STATE_EXT_ERROR then tlsio_close_async shall log that tlsio_close_async has been called and then continue normally. ]*/ + TEST_FUNCTION(tlsio_openssl_compact__close_while_closed__succeeds) + { + CONCRETE_IO_HANDLE tlsio; + int close_result; + ///arrange + reset_callback_context_records(); + tlsio_id = tlsio_pal_get_interface_description(); + tlsio = tlsio_id->concrete_io_create(&good_config); + ASSERT_IO_OPEN_CALLBACK(false, IO_OPEN_OK); + umock_c_reset_all_calls(); + reset_callback_context_records(); + + ///act + close_result = tlsio_id->concrete_io_close(tlsio, on_io_close_complete, IO_CLOSE_COMPLETE_CONTEXT); + + ///assert + ASSERT_ARE_EQUAL(int, 0, close_result); + ASSERT_IO_CLOSE_CALLBACK(true); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + + ///cleanup + tlsio_id->concrete_io_destroy(tlsio); + assert_gballoc_checks(); + } + + /* Tests_SRS_TLSIO_30_009: [ The phrase "enter TLSIO_STATE_EXT_CLOSING" means the adapter shall iterate through any unsent messages in the queue and shall delete each message after calling its on_send_complete with the associated callback_context and IO_SEND_CANCELLED. ]*/ + /* Tests_SRS_TLSIO_30_006: [ The phrase "enter TLSIO_STATE_EXT_CLOSED" means the adapter shall forcibly close any existing connections then call the on_io_close_complete function and pass the on_io_close_complete_context that was supplied in tlsio_close_async. ]*/ + /* Tests_SRS_TLSIO_30_056: [ On success the adapter shall enter TLSIO_STATE_EX_CLOSING. ]*/ + /* Tests_SRS_TLSIO_30_051: [ On success, if the underlying TLS does not support asynchronous closing, then the adapter shall enter TLSIO_STATE_EX_CLOSED immediately after entering TLSIO_STATE_EX_CLOSING. ]*/ + /* Tests_SRS_TLSIO_30_052: [ On success tlsio_close shall return 0. ]*/ + // For this case, tlsio_openssl_compact_open has been called previously + TEST_FUNCTION(tlsio_openssl_compact__close_after_open__succeeds) + { + int close_result; + ///arrange + CONCRETE_IO_HANDLE tlsio = tlsio_id->concrete_io_create(&good_config); + reset_callback_context_records(); + open_helper(tlsio); + + // Make sure the arrangement is correct + ASSERT_IO_OPEN_CALLBACK(true, IO_OPEN_OK); + + umock_c_reset_all_calls(); + STRICT_EXPECTED_CALL(SSL_shutdown(SSL_Good_Ptr)); + STRICT_EXPECTED_CALL(SSL_free(SSL_Good_Ptr)); + STRICT_EXPECTED_CALL(SSL_CTX_free(SSL_Good_Context_Ptr)); + STRICT_EXPECTED_CALL(socket_async_destroy(SSL_Good_Socket)); + reset_callback_context_records(); + // End of arrange + + ///act + close_result = tlsio_id->concrete_io_close(tlsio, on_io_close_complete, IO_CLOSE_COMPLETE_CONTEXT); + + ///assert + ASSERT_ARE_EQUAL(int, 0, close_result); + ASSERT_IO_CLOSE_CALLBACK(true); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + + ///cleanup + tlsio_id->concrete_io_close(tlsio, on_io_close_complete, NULL); + tlsio_id->concrete_io_destroy(tlsio); + assert_gballoc_checks(); + } + + /* Tests_SRS_TLSIO_30_053: [ If the adapter is in any state other than TLSIO_STATE_EXT_OPEN or TLSIO_STATE_EXT_ERROR then tlsio_close_async shall log that tlsio_close_async has been called and then continue normally. ]*/ + /* Tests_SRS_TLSIO_30_057: [ On success, if the adapter is in TLSIO_STATE_EXT_OPENING, it shall call on_io_open_complete with the on_io_open_complete_context supplied in tlsio_open_async and IO_OPEN_CANCELLED. This callback shall be made before changing the internal state of the adapter. ]*/ + TEST_FUNCTION(tlsio_openssl_compact__close_while_opening__succeeds) + { + int open_result; + CONCRETE_IO_HANDLE tlsio; + int close_result; + ///arrange + reset_callback_context_records(); + tlsio = tlsio_id->concrete_io_create(&good_config); + open_result = tlsio_id->concrete_io_open(tlsio, on_io_open_complete, IO_OPEN_COMPLETE_CONTEXT, on_bytes_received, + IO_BYTES_RECEIVED_CONTEXT, on_io_error, IO_ERROR_CONTEXT); + ASSERT_ARE_EQUAL(int, open_result, 0); + ASSERT_IO_OPEN_CALLBACK(false, IO_OPEN_ERROR); + umock_c_reset_all_calls(); + + // dowork_poll_dns (waiting) + STRICT_EXPECTED_CALL(dns_async_is_lookup_complete(GOOD_DNS_ASYNC_HANDLE)).SetReturn(false); + + // dowork_poll_dns (done) + STRICT_EXPECTED_CALL(dns_async_is_lookup_complete(GOOD_DNS_ASYNC_HANDLE)); + STRICT_EXPECTED_CALL(dns_async_get_ipv4(GOOD_DNS_ASYNC_HANDLE)); + STRICT_EXPECTED_CALL(dns_async_destroy(GOOD_DNS_ASYNC_HANDLE)); + STRICT_EXPECTED_CALL(socket_async_create(SSL_Get_IPv4_OK, SSL_good_port_number, false, NULL)); + + // dowork_poll_socket (waiting) + STRICT_EXPECTED_CALL(socket_async_is_create_complete(SSL_Good_Socket, IGNORED_PTR_ARG)).CopyOutArgumentBuffer_is_complete(&bool_false, sizeof_bool); + + // dowork_poll_socket (done) + STRICT_EXPECTED_CALL(socket_async_is_create_complete(SSL_Good_Socket, IGNORED_PTR_ARG)).CopyOutArgumentBuffer_is_complete(&bool_true, sizeof_bool); + STRICT_EXPECTED_CALL(SSL_CTX_new(IGNORED_NUM_ARG)); + STRICT_EXPECTED_CALL(SSL_new(IGNORED_PTR_ARG)); + STRICT_EXPECTED_CALL(SSL_set_fd(IGNORED_PTR_ARG, IGNORED_NUM_ARG)); + + // dowork_poll_open_ssl (waiting SSL_ERROR_WANT_READ) + STRICT_EXPECTED_CALL(SSL_connect(SSL_Good_Ptr)).SetReturn(SSL_ERROR__plus__WANT_READ); + STRICT_EXPECTED_CALL(SSL_get_error(SSL_Good_Ptr, SSL_ERROR__plus__WANT_READ)); + + // dowork_poll_open_ssl (waiting SSL_ERROR_WANT_WRITE) + STRICT_EXPECTED_CALL(SSL_connect(SSL_Good_Ptr)).SetReturn(SSL_ERROR__plus__WANT_WRITE); + STRICT_EXPECTED_CALL(SSL_get_error(SSL_Good_Ptr, SSL_ERROR__plus__WANT_WRITE)); + + // dowork_poll_open_ssl (done) + + tlsio_id->concrete_io_dowork(tlsio); // dowork_poll_dns (waiting) + tlsio_id->concrete_io_dowork(tlsio); // dowork_poll_dns (done) + tlsio_id->concrete_io_dowork(tlsio); // dowork_poll_socket (waiting) + tlsio_id->concrete_io_dowork(tlsio); // dowork_poll_socket (done) + tlsio_id->concrete_io_dowork(tlsio); // dowork_poll_open_ssl (waiting SSL_ERROR_WANT_READ) + tlsio_id->concrete_io_dowork(tlsio); // dowork_poll_open_ssl (waiting SSL_ERROR_WANT_WRITE) + + // Make sure the arrangement is correct so far + ASSERT_IO_OPEN_CALLBACK(false, IO_OPEN_OK); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + + umock_c_reset_all_calls(); + STRICT_EXPECTED_CALL(SSL_free(SSL_Good_Ptr)); + STRICT_EXPECTED_CALL(SSL_CTX_free(SSL_Good_Context_Ptr)); + STRICT_EXPECTED_CALL(socket_async_destroy(SSL_Good_Socket)); + reset_callback_context_records(); + // End of arrange + + ///act + close_result = tlsio_id->concrete_io_close(tlsio, on_io_close_complete, IO_CLOSE_COMPLETE_CONTEXT); + + ///assert + ASSERT_ARE_EQUAL(int, 0, close_result); + ASSERT_IO_OPEN_CALLBACK(true, IO_OPEN_CANCELLED); + ASSERT_IO_CLOSE_CALLBACK(true); + + ///cleanup + tlsio_id->concrete_io_close(tlsio, on_io_close_complete, NULL); + tlsio_id->concrete_io_destroy(tlsio); + assert_gballoc_checks(); + } + + /* Tests_SRS_TLSIO_30_063: [ The tlsio_openssl_compact_send shall enqueue for transmission the on_send_complete, the callback_context, the size, and the contents of buffer. ]*/ + TEST_FUNCTION(tlsio_openssl_compact__send__succeeds) + { + int send_result; + ///arrange + CONCRETE_IO_HANDLE tlsio = tlsio_id->concrete_io_create(&good_config); + open_helper(tlsio); + reset_callback_context_records(); + umock_c_reset_all_calls(); + + STRICT_EXPECTED_CALL(gballoc_malloc(IGNORED_NUM_ARG)); // PENDING_SOCKET_IO + STRICT_EXPECTED_CALL(gballoc_malloc(IGNORED_NUM_ARG)); // message bytes + STRICT_EXPECTED_CALL(gballoc_malloc(IGNORED_NUM_ARG)); // singlylinkedlist_add + + ///act + send_result = tlsio_id->concrete_io_send(tlsio, SSL_send_buffer, + SSL_send_message_size, on_io_send_complete, IO_SEND_COMPLETE_CONTEXT); + + ///assert + ASSERT_ARE_EQUAL_WITH_MSG(int, send_result, 0, "Unexpected send failure"); + + ///cleanup + tlsio_id->concrete_io_close(tlsio, on_io_close_complete, NULL); + tlsio_id->concrete_io_destroy(tlsio); + assert_gballoc_checks(); + } + + /* Tests_SRS_TLSIO_30_064: [ If the supplied message cannot be enqueued for transmission, tlsio_openssl_compact_send shall log an error and return FAILURE. ]*/ + /* Tests_SRS_TLSIO_30_066: [ On failure, on_send_complete shall not be called. ]*/ + TEST_FUNCTION(tlsio_openssl_compact__send_unhappy_paths__fails) + { + ///arrange + size_t i; + use_negative_mocks(); + + STRICT_EXPECTED_CALL(gballoc_malloc(IGNORED_NUM_ARG)); // PENDING_TRANSMISSION + STRICT_EXPECTED_CALL(gballoc_malloc(IGNORED_NUM_ARG)); // message bytes + STRICT_EXPECTED_CALL(gballoc_malloc(IGNORED_NUM_ARG)); // singlylinkedlist_add + umock_c_negative_tests_snapshot(); + + for (i = 0; i < umock_c_negative_tests_call_count(); i++) + { + int send_result; + ///arrange + CONCRETE_IO_HANDLE tlsio = tlsio_id->concrete_io_create(&good_config); + open_helper(tlsio); + reset_callback_context_records(); + + umock_c_reset_all_calls(); + + umock_c_negative_tests_reset(); + umock_c_negative_tests_fail_call(i); + + ///act + send_result = tlsio_id->concrete_io_send(tlsio, SSL_send_buffer, + SSL_send_message_size, on_io_send_complete, IO_SEND_COMPLETE_CONTEXT); + + ///assert + ASSERT_ARE_NOT_EQUAL_WITH_MSG(int, send_result, 0, "Unexpected send success on unhappy path"); + ASSERT_IO_SEND_CALLBACK(false, IO_SEND_ERROR); + + ///cleanup + tlsio_id->concrete_io_close(tlsio, on_io_close_complete, NULL); + tlsio_id->concrete_io_destroy(tlsio); + } + + ///cleanup + assert_gballoc_checks(); + } + + /* Tests_SRS_TLSIO_30_060: [ If the tlsio_handle parameter is NULL, tlsio_openssl_compact_send shall log an error and return FAILURE. ]*/ + /* Tests_SRS_TLSIO_30_061: [ If the buffer is NULL, tlsio_openssl_compact_send shall log the error and return FAILURE. ]*/ + /* Tests_SRS_TLSIO_30_062: [ If the on_send_complete is NULL, tlsio_openssl_compact_send shall log the error and return FAILURE. ]*/ + /* Tests_SRS_TLSIO_30_067: [ If the size is 0, tlsio_send shall log the error and return FAILURE. ]*/ + /* Tests_SRS_TLSIO_30_066: [ On failure, on_send_complete shall not be called. ]*/ + TEST_FUNCTION(tlsio_openssl_compact__send_parameter_validation__fails) + { + // Parameters arrays + bool p0[SEND_PV_COUNT]; + const void* p1[SEND_PV_COUNT]; + size_t p2[SEND_PV_COUNT]; + ON_SEND_COMPLETE p3[SEND_PV_COUNT]; + const char* fm[SEND_PV_COUNT]; + int k; + int i; + + ///arrange + CONCRETE_IO_HANDLE tlsio = tlsio_id->concrete_io_create(&good_config); + open_helper(tlsio); + + k = 0; + p0[k] = false; p1[k] = SSL_send_buffer; p2[k] = SSL_send_message_size; p3[k] = on_io_send_complete; fm[k] = "Unexpected send success when tlsio_handle is NULL"; k++; + p0[k] = true; p1[k] = NULL; /* */ p2[k] = SSL_send_message_size; p3[k] = on_io_send_complete; fm[k] = "Unexpected send success when send buffer is NULL"; k++; + p0[k] = true; p1[k] = SSL_send_buffer; p2[k] = 0; /* */ p3[k] = on_io_send_complete; fm[k] = "Unexpected send success when size is 0"; k++; + p0[k] = true; p1[k] = SSL_send_buffer; p2[k] = SSL_send_message_size; p3[k] = NULL; /* */ fm[k] = "Unexpected send success when on_send_complete is NULL"; k++; + + // Cycle through each failing combo of parameters + for (i = 0; i < SEND_PV_COUNT; i++) + { + int send_result; + ///arrange + reset_callback_context_records(); + + ///act + send_result = tlsio_id->concrete_io_send(p0[i] ? tlsio : NULL, p1[i], p2[i], p3[i], IO_SEND_COMPLETE_CONTEXT); + + ///assert + ASSERT_ARE_NOT_EQUAL_WITH_MSG(int, send_result, 0, fm[i]); + ASSERT_IO_SEND_CALLBACK(false, IO_SEND_ERROR); + + ///cleanup + } + + ///cleanup + tlsio_id->concrete_io_close(tlsio, on_io_close_complete, NULL); + tlsio_id->concrete_io_destroy(tlsio); + assert_gballoc_checks(); + } + + /* Tests_SRS_TLSIO_30_065: [ If tlsio_openssl_compact_open has not been called or the opening process has not been completed, tlsio_openssl_compact_send shall log an error and return FAILURE. ]*/ + /* Tests_SRS_TLSIO_30_066: [ On failure, on_send_complete shall not be called. ]*/ + TEST_FUNCTION(tlsio_openssl_compact__send_not_open__fails) + { + int send_result; + ///arrange + CONCRETE_IO_HANDLE tlsio = tlsio_id->concrete_io_create(&good_config); + reset_callback_context_records(); + + ///act + send_result = tlsio_id->concrete_io_send(tlsio, SSL_send_buffer, + SSL_send_message_size, on_io_send_complete, IO_SEND_COMPLETE_CONTEXT); + + ///assert + ASSERT_ARE_NOT_EQUAL_WITH_MSG(int, send_result, 0, "Unexpected success in sending from wrong state"); + ASSERT_IO_SEND_CALLBACK(false, IO_SEND_ERROR); + + ///cleanup + tlsio_id->concrete_io_destroy(tlsio); + assert_gballoc_checks(); + } + + /* Tests_SRS_TLSIO_30_071: [ If the adapter is in TLSIO_STATE_EXT_ERROR then tlsio_dowork shall do nothing. ]*/ + TEST_FUNCTION(tlsio_openssl_compact__dowork_send_post_error_do_nothing__succeeds) + { + int send_result; + ///arrange + CONCRETE_IO_HANDLE tlsio = tlsio_id->concrete_io_create(&good_config); + open_helper(tlsio); + + // Send two messages, one to fail and one that should be ignored by the dowork + send_result = tlsio_id->concrete_io_send(tlsio, SSL_send_buffer, + SSL_SHORT_SENT_MESSAGE_SIZE, on_io_send_complete, IO_SEND_COMPLETE_CONTEXT); + ASSERT_ARE_EQUAL(int, send_result, 0); + send_result = tlsio_id->concrete_io_send(tlsio, SSL_send_buffer, + SSL_SHORT_SENT_MESSAGE_SIZE, on_io_send_complete, IO_SEND_COMPLETE_CONTEXT); + ASSERT_ARE_EQUAL(int, send_result, 0); + + reset_callback_context_records(); + init_fake_read(0); + umock_c_reset_all_calls(); + + STRICT_EXPECTED_CALL(SSL_read(SSL_Good_Ptr, IGNORED_PTR_ARG, IGNORED_NUM_ARG)); + STRICT_EXPECTED_CALL(SSL_write(SSL_Good_Ptr, IGNORED_PTR_ARG, SSL_SHORT_SENT_MESSAGE_SIZE)).SetReturn(SSL_ERROR__plus__HARD_FAIL); + STRICT_EXPECTED_CALL(SSL_get_error(SSL_Good_Ptr, IGNORED_NUM_ARG)); + STRICT_EXPECTED_CALL(gballoc_free(IGNORED_NUM_ARG)); + STRICT_EXPECTED_CALL(gballoc_free(IGNORED_NUM_ARG)); + STRICT_EXPECTED_CALL(gballoc_free(IGNORED_NUM_ARG)); + + tlsio_id->concrete_io_dowork(tlsio); + + ASSERT_IO_SEND_CALLBACK(true, IO_SEND_ERROR); + ASSERT_IO_ERROR_CALLBACK(true); + + reset_callback_context_records(); + umock_c_reset_all_calls(); + + ///act + tlsio_id->concrete_io_dowork(tlsio); + + ///assert + ASSERT_NO_CALLBACKS(); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + + ///cleanup + tlsio_id->concrete_io_close(tlsio, on_io_close_complete, NULL); + tlsio_id->concrete_io_destroy(tlsio); + assert_gballoc_checks(); + } + + /* Tests_SRS_TLSIO_30_002: [ The phrase "destroy the failed message" means that the adapter shall remove the message from the queue and destroy it after calling the message's on_send_complete along with its associated callback_context and IO_SEND_ERROR. ]*/ + /* Tests_SRS_TLSIO_30_005: [ When the adapter enters TLSIO_STATE_EXT_ERROR it shall call the on_io_error function and pass the on_io_error_context that were supplied in tlsio_open . ]*/ + /* Tests_SRS_TLSIO_30_095: [ If the send process fails before sending all of the bytes in an enqueued message, tlsio_dowork shall destroy the failed message and enter TLSIO_STATE_EX_ERROR. ]*/ + TEST_FUNCTION(tlsio_openssl_compact__dowork_send_unhappy_path__fails) + { + int send_result; + ///arrange + CONCRETE_IO_HANDLE tlsio = tlsio_id->concrete_io_create(&good_config); + open_helper(tlsio); + + send_result = tlsio_id->concrete_io_send(tlsio, SSL_send_buffer, + SSL_SHORT_SENT_MESSAGE_SIZE, on_io_send_complete, IO_SEND_COMPLETE_CONTEXT); + ASSERT_ARE_EQUAL(int, send_result, 0); + + init_fake_read(0); + reset_callback_context_records(); + umock_c_reset_all_calls(); + + STRICT_EXPECTED_CALL(SSL_read(SSL_Good_Ptr, IGNORED_PTR_ARG, IGNORED_NUM_ARG)); + STRICT_EXPECTED_CALL(SSL_write(SSL_Good_Ptr, IGNORED_PTR_ARG, SSL_SHORT_SENT_MESSAGE_SIZE)).SetReturn(SSL_ERROR__plus__HARD_FAIL); + STRICT_EXPECTED_CALL(SSL_get_error(SSL_Good_Ptr, IGNORED_NUM_ARG)); + STRICT_EXPECTED_CALL(gballoc_free(IGNORED_NUM_ARG)); + STRICT_EXPECTED_CALL(gballoc_free(IGNORED_NUM_ARG)); + STRICT_EXPECTED_CALL(gballoc_free(IGNORED_NUM_ARG)); + + ///act + tlsio_id->concrete_io_dowork(tlsio); + + ///assert + ASSERT_IO_SEND_CALLBACK(true, IO_SEND_ERROR); + ASSERT_IO_ERROR_CALLBACK(true); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + + ///cleanup + tlsio_id->concrete_io_close(tlsio, on_io_close_complete, NULL); + tlsio_id->concrete_io_destroy(tlsio); + assert_gballoc_checks(); + } + + /* Tests_SRS_TLSIO_30_093: [ If the TLS connection was not able to send an entire enqueued message at once, subsequent calls to tlsio_dowork shall continue to send the remaining bytes. ]*/ + TEST_FUNCTION(tlsio_openssl_compact__dowork_send_big_message__succeeds) + { + int send_result; + ///arrange + CONCRETE_IO_HANDLE tlsio = tlsio_id->concrete_io_create(&good_config); + open_helper(tlsio); + + send_result = tlsio_id->concrete_io_send(tlsio, SSL_send_buffer, + SSL_TEST_MESSAGE_SIZE, on_io_send_complete, IO_SEND_COMPLETE_CONTEXT); + ASSERT_ARE_EQUAL(int, send_result, 0); + + init_fake_read(0); + reset_callback_context_records(); + umock_c_reset_all_calls(); + + STRICT_EXPECTED_CALL(SSL_read(SSL_Good_Ptr, IGNORED_PTR_ARG, IGNORED_NUM_ARG)); + STRICT_EXPECTED_CALL(SSL_write(SSL_Good_Ptr, IGNORED_PTR_ARG, SSL_TEST_MESSAGE_SIZE)); + + STRICT_EXPECTED_CALL(SSL_read(SSL_Good_Ptr, IGNORED_PTR_ARG, IGNORED_NUM_ARG)); + STRICT_EXPECTED_CALL(SSL_write(SSL_Good_Ptr, IGNORED_PTR_ARG, SSL_TEST_MESSAGE_SIZE - SSL_WRITE_MAX_TEST_SIZE)); + STRICT_EXPECTED_CALL(gballoc_free(IGNORED_NUM_ARG)); + STRICT_EXPECTED_CALL(gballoc_free(IGNORED_NUM_ARG)); + STRICT_EXPECTED_CALL(gballoc_free(IGNORED_NUM_ARG)); + tlsio_id->concrete_io_dowork(tlsio); + ASSERT_IO_ERROR_CALLBACK(false); + + ///act + tlsio_id->concrete_io_dowork(tlsio); + + ///assert + ASSERT_IO_SEND_CALLBACK(true, IO_SEND_OK); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + ASSERT_IO_ERROR_CALLBACK(false); + + ///cleanup + tlsio_id->concrete_io_close(tlsio, on_io_close_complete, NULL); + tlsio_id->concrete_io_destroy(tlsio); + assert_gballoc_checks(); + } + + /* Tests_SRS_TLSIO_30_091: [ If tlsio_openssl_compact_dowork is able to send all the bytes in an enqueued message, it shall call the messages's on_send_complete along with its associated callback_context and IO_SEND_OK. ]*/ + TEST_FUNCTION(tlsio_openssl_compact__dowork_send__succeeds) + { + int send_result; + ///arrange + CONCRETE_IO_HANDLE tlsio = tlsio_id->concrete_io_create(&good_config); + open_helper(tlsio); + + send_result = tlsio_id->concrete_io_send(tlsio, SSL_send_buffer, + SSL_SHORT_SENT_MESSAGE_SIZE, on_io_send_complete, IO_SEND_COMPLETE_CONTEXT); + ASSERT_ARE_EQUAL(int, send_result, 0); + ASSERT_IO_ERROR_CALLBACK(false); + + init_fake_read(0); + reset_callback_context_records(); + umock_c_reset_all_calls(); + + STRICT_EXPECTED_CALL(SSL_read(SSL_Good_Ptr, IGNORED_PTR_ARG, IGNORED_NUM_ARG)); + STRICT_EXPECTED_CALL(SSL_write(SSL_Good_Ptr, IGNORED_PTR_ARG, SSL_SHORT_SENT_MESSAGE_SIZE)); + STRICT_EXPECTED_CALL(gballoc_free(IGNORED_NUM_ARG)); + STRICT_EXPECTED_CALL(gballoc_free(IGNORED_NUM_ARG)); + STRICT_EXPECTED_CALL(gballoc_free(IGNORED_NUM_ARG)); + + ///act + tlsio_id->concrete_io_dowork(tlsio); + + ///assert + ASSERT_IO_SEND_CALLBACK(true, IO_SEND_OK); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + ASSERT_IO_ERROR_CALLBACK(false); + + ///cleanup + tlsio_id->concrete_io_close(tlsio, on_io_close_complete, NULL); + tlsio_id->concrete_io_destroy(tlsio); + assert_gballoc_checks(); + } + + /* Tests_SRS_TLSIO_30_096: [ If there are no enqueued messages available, tlsio_openssl_compact_dowork shall do nothing. ]*/ + TEST_FUNCTION(tlsio_openssl_compact__dowork_send_empty_queue__succeeds) + { + ///arrange + CONCRETE_IO_HANDLE tlsio = tlsio_id->concrete_io_create(&good_config); + open_helper(tlsio); + + init_fake_read(0); + reset_callback_context_records(); + umock_c_reset_all_calls(); + + // We do expect an empty read when we call dowork + STRICT_EXPECTED_CALL(SSL_read(SSL_Good_Ptr, IGNORED_PTR_ARG, IGNORED_NUM_ARG)); + + ///act + tlsio_id->concrete_io_dowork(tlsio); + + ///assert + // Verify we got no callback for 0 messages + ASSERT_IO_SEND_CALLBACK(false, IO_SEND_OK); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + ASSERT_IO_ERROR_CALLBACK(false); + + ///cleanup + tlsio_id->concrete_io_close(tlsio, on_io_close_complete, NULL); + tlsio_id->concrete_io_destroy(tlsio); + assert_gballoc_checks(); + } + + /* Tests_SRS_TLSIO_30_102: [ If the TLS connection receives no data then tlsio_dowork shall not call the on_bytes_received callback. ]*/ + TEST_FUNCTION(tlsio_openssl_compact__dowork_receive_no_data__succeeds) + { + ///arrange + CONCRETE_IO_HANDLE tlsio = tlsio_id->concrete_io_create(&good_config); + open_helper(tlsio); + + init_fake_read(0); + reset_callback_context_records(); + umock_c_reset_all_calls(); + + STRICT_EXPECTED_CALL(SSL_read(SSL_Good_Ptr, IGNORED_PTR_ARG, IGNORED_NUM_ARG)); + + ///act + tlsio_id->concrete_io_dowork(tlsio); // dowork_poll_dns (done) + + ///assert + // Verify we got no callback for 0 bytes + ASSERT_BYTES_RECEIVED_CALLBACK(false, 0); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + ASSERT_IO_ERROR_CALLBACK(false); + + ///cleanup + tlsio_id->concrete_io_close(tlsio, on_io_close_complete, NULL); + tlsio_id->concrete_io_destroy(tlsio); + assert_gballoc_checks(); + } + + /* Tests_SRS_TLSIO_30_100: [ As long as the TLS connection is able to provide received data, tlsio_dowork shall repeatedly read this data and call on_bytes_received with the pointer to the buffer containing the data, the number of bytes received, and the on_bytes_received_context. ]*/ + TEST_FUNCTION(tlsio_openssl_compact__dowork_receive_short_message__succeeds) + { + ///arrange + // Create + CONCRETE_IO_HANDLE tlsio = tlsio_id->concrete_io_create(&good_config); + open_helper(tlsio); + + init_fake_read(SSL_SHORT_RECEIVED_MESSAGE_SIZE); + reset_callback_context_records(); + umock_c_reset_all_calls(); + + STRICT_EXPECTED_CALL(SSL_read(SSL_Good_Ptr, IGNORED_PTR_ARG, IGNORED_NUM_ARG)); // Gets some data + STRICT_EXPECTED_CALL(SSL_read(SSL_Good_Ptr, IGNORED_PTR_ARG, IGNORED_NUM_ARG)); // Gets zero bytes + + ///act + tlsio_id->concrete_io_dowork(tlsio); + + ///assert + // Verify we got the bytes and their callback context + ASSERT_BYTES_RECEIVED_CALLBACK(true, SSL_SHORT_RECEIVED_MESSAGE_SIZE); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + ASSERT_IO_ERROR_CALLBACK(false); + + ///cleanup + tlsio_id->concrete_io_close(tlsio, on_io_close_complete, NULL); + tlsio_id->concrete_io_destroy(tlsio); + assert_gballoc_checks(); + } + + /* Tests_SRS_TLSIO_30_100: [ As long as the TLS connection is able to provide received data, tlsio_dowork shall repeatedly read this data and call on_bytes_received with the pointer to the buffer containing the data, the number of bytes received, and the on_bytes_received_context. ]*/ + TEST_FUNCTION(tlsio_openssl_compact__dowork_receive_long_message__succeeds) + { + ///arrange + // Create + CONCRETE_IO_HANDLE tlsio = tlsio_id->concrete_io_create(&good_config); + open_helper(tlsio); + + init_fake_read(SSL_LONG_RECEIVED_MESSAGE_SIZE); + reset_callback_context_records(); + umock_c_reset_all_calls(); + + ///act + tlsio_id->concrete_io_dowork(tlsio); + + ///assert + // Verify we got the bytes and their callback context + ASSERT_BYTES_RECEIVED_CALLBACK(true, SSL_LONG_RECEIVED_MESSAGE_SIZE); + ASSERT_IO_ERROR_CALLBACK(false); + + ///cleanup + tlsio_id->concrete_io_close(tlsio, on_io_close_complete, NULL); + tlsio_id->concrete_io_destroy(tlsio); + assert_gballoc_checks(); + } + + /* Tests_SRS_TLSIO_30_075: [ If the adapter is in TLSIO_STATE_EXT_CLOSED then tlsio_dowork shall do nothing. ]*/ + TEST_FUNCTION(tlsio_openssl_compact__dowork_post_close__succeeds) + { + CONCRETE_IO_HANDLE tlsio; + int open_result; + ///arrange + // Create + reset_callback_context_records(); + tlsio = tlsio_id->concrete_io_create(&good_config); + open_result = tlsio_id->concrete_io_open(tlsio, on_io_open_complete, IO_OPEN_COMPLETE_CONTEXT, on_bytes_received, + IO_BYTES_RECEIVED_CONTEXT, on_io_error, IO_ERROR_CONTEXT); + ASSERT_ARE_EQUAL(int, open_result, 0); + ASSERT_IO_OPEN_CALLBACK(false, IO_OPEN_ERROR); + + // Pump dowork until it opens + STRICT_EXPECTED_CALL(socket_async_is_create_complete(SSL_Good_Socket, IGNORED_PTR_ARG)).CopyOutArgumentBuffer_is_complete(&bool_true, sizeof_bool); + tlsio_id->concrete_io_dowork(tlsio); // dowork_poll_dns (done) + tlsio_id->concrete_io_dowork(tlsio); // dowork_poll_socket (done) + tlsio_id->concrete_io_dowork(tlsio); // dowork_poll_open_ssl (done) + ASSERT_IO_OPEN_CALLBACK(true, IO_OPEN_OK); + + // close it + tlsio_id->concrete_io_close(tlsio, on_io_close_complete, IO_CLOSE_COMPLETE_CONTEXT); + ASSERT_IO_CLOSE_CALLBACK(true); + reset_callback_context_records(); + umock_c_reset_all_calls(); + + ///act + tlsio_id->concrete_io_dowork(tlsio); // dowork_poll_dns (done) + + ///assert + // Verify that the dowork did nothing + ASSERT_NO_CALLBACKS(); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + + ///cleanup + tlsio_id->concrete_io_destroy(tlsio); + assert_gballoc_checks(); + } + + + /* Tests_SRS_TLSIO_30_082: [ If the connection process fails for any reason, tlsio_dowork shall log an error, call on_io_open_complete with the on_io_open_complete_context parameter provided in tlsio_open and IO_OPEN_ERROR, and enter TLSIO_STATE_EX_ERROR. ]*/ + TEST_FUNCTION(tlsio_openssl_compact__dowork_open_unhappy_paths__fails) + { + int k; + bool fails[100]; + size_t i; + ///arrange + k = 0; + use_negative_mocks(); + + // dowork_poll_dns (waiting) + fails[k++] = false; STRICT_EXPECTED_CALL(dns_async_is_lookup_complete(GOOD_DNS_ASYNC_HANDLE)).SetReturn(false); + + // dowork_poll_dns (done) + fails[k++] = false; STRICT_EXPECTED_CALL(dns_async_is_lookup_complete(GOOD_DNS_ASYNC_HANDLE)); + fails[k++] = true; STRICT_EXPECTED_CALL(dns_async_get_ipv4(GOOD_DNS_ASYNC_HANDLE)); + fails[k++] = false; STRICT_EXPECTED_CALL(dns_async_destroy(GOOD_DNS_ASYNC_HANDLE)); + fails[k++] = true; STRICT_EXPECTED_CALL(socket_async_create(SSL_Get_IPv4_OK, SSL_good_port_number, false, NULL)); + + // dowork_poll_socket (waiting) + fails[k++] = false; STRICT_EXPECTED_CALL(socket_async_is_create_complete(SSL_Good_Socket, IGNORED_PTR_ARG)).CopyOutArgumentBuffer_is_complete(&bool_false, sizeof_bool); + + // dowork_poll_socket (done) + fails[k++] = false; STRICT_EXPECTED_CALL(socket_async_is_create_complete(SSL_Good_Socket, IGNORED_PTR_ARG)).CopyOutArgumentBuffer_is_complete(&bool_true, sizeof_bool); + fails[k++] = true; STRICT_EXPECTED_CALL(SSL_CTX_new(IGNORED_NUM_ARG)); + fails[k++] = true; STRICT_EXPECTED_CALL(SSL_new(IGNORED_PTR_ARG)); + fails[k++] = true; STRICT_EXPECTED_CALL(SSL_set_fd(IGNORED_PTR_ARG, IGNORED_NUM_ARG)); + + // dowork_poll_open_ssl (timeout) + fails[k++] = false; STRICT_EXPECTED_CALL(SSL_connect(SSL_Good_Ptr)).SetReturn(SSL_ERROR__plus__WANT_READ); + fails[k++] = false; STRICT_EXPECTED_CALL(SSL_get_error(SSL_Good_Ptr, SSL_ERROR__plus__WANT_READ)); + + // dowork_poll_open_ssl (hard failure) + fails[k++] = true; STRICT_EXPECTED_CALL(SSL_connect(SSL_Good_Ptr)); + + umock_c_negative_tests_snapshot(); + + for (i = 0; i < umock_c_negative_tests_call_count(); i++) + { + CONCRETE_IO_HANDLE tlsio; + int open_result; + reset_callback_context_records(); + tlsio = tlsio_id->concrete_io_create(&good_config); + open_result = tlsio_id->concrete_io_open(tlsio, on_io_open_complete, IO_OPEN_COMPLETE_CONTEXT, on_bytes_received, + IO_BYTES_RECEIVED_CONTEXT, on_io_error, IO_ERROR_CONTEXT); + ASSERT_ARE_EQUAL(int, open_result, 0); + ASSERT_IO_OPEN_CALLBACK(false, IO_OPEN_ERROR); + umock_c_reset_all_calls(); + + umock_c_negative_tests_reset(); + if (fails[i]) + { + umock_c_negative_tests_fail_call(i); + } + tlsio_id->concrete_io_dowork(tlsio); + tlsio_id->concrete_io_dowork(tlsio); + tlsio_id->concrete_io_dowork(tlsio); + tlsio_id->concrete_io_dowork(tlsio); + tlsio_id->concrete_io_dowork(tlsio); + + ///act + tlsio_id->concrete_io_dowork(tlsio); + + ///assert + // A few of the iterations have no failures + ASSERT_IO_OPEN_CALLBACK(true, fails[i] ? IO_OPEN_ERROR : IO_OPEN_OK); + + ///cleanup + tlsio_id->concrete_io_close(tlsio, on_io_close_complete, NULL); + tlsio_id->concrete_io_destroy(tlsio); + } + + ///cleanup + assert_gballoc_checks(); + } + + /* Tests_SRS_TLSIO_30_080: [ The tlsio_dowork shall establish a TLS connection using the hostName and port provided during tlsio_open. ]*/ + /* Tests_SRS_TLSIO_30_007: [ The phrase "enter TLSIO_STATE_EXT_OPEN" means the adapter shall call the on_io_open_complete function and pass IO_OPEN_OK and the on_io_open_complete_context that was supplied in tlsio_open . ]*/ + /* Tests_SRS_TLSIO_30_083: [ If tlsio_dowork successfully opens the TLS connection it shall enter TLSIO_STATE_EX_OPEN. ]*/ + TEST_FUNCTION(tlsio_openssl_compact__dowork_open__succeeds) + { + CONCRETE_IO_HANDLE tlsio; + int open_result; + ///arrange + reset_callback_context_records(); + tlsio = tlsio_id->concrete_io_create(&good_config); + open_result = tlsio_id->concrete_io_open(tlsio, on_io_open_complete, IO_OPEN_COMPLETE_CONTEXT, on_bytes_received, + IO_BYTES_RECEIVED_CONTEXT, on_io_error, IO_ERROR_CONTEXT); + ASSERT_ARE_EQUAL(int, open_result, 0); + ASSERT_IO_OPEN_CALLBACK(false, IO_OPEN_ERROR); + umock_c_reset_all_calls(); + + // dowork_poll_dns (waiting) + STRICT_EXPECTED_CALL(dns_async_is_lookup_complete(GOOD_DNS_ASYNC_HANDLE)).SetReturn(false); + + // dowork_poll_dns (done) + STRICT_EXPECTED_CALL(dns_async_is_lookup_complete(GOOD_DNS_ASYNC_HANDLE)); + STRICT_EXPECTED_CALL(dns_async_get_ipv4(GOOD_DNS_ASYNC_HANDLE)); + STRICT_EXPECTED_CALL(dns_async_destroy(GOOD_DNS_ASYNC_HANDLE)); + STRICT_EXPECTED_CALL(socket_async_create(SSL_Get_IPv4_OK, SSL_good_port_number, false, NULL)); + + // dowork_poll_socket (waiting) + STRICT_EXPECTED_CALL(socket_async_is_create_complete(SSL_Good_Socket, IGNORED_PTR_ARG)).CopyOutArgumentBuffer_is_complete(&bool_false, sizeof_bool); + + // dowork_poll_socket (done) + STRICT_EXPECTED_CALL(socket_async_is_create_complete(SSL_Good_Socket, IGNORED_PTR_ARG)).CopyOutArgumentBuffer_is_complete(&bool_true, sizeof_bool); + STRICT_EXPECTED_CALL(SSL_CTX_new(IGNORED_NUM_ARG)); + STRICT_EXPECTED_CALL(SSL_new(IGNORED_PTR_ARG)); + STRICT_EXPECTED_CALL(SSL_set_fd(IGNORED_PTR_ARG, IGNORED_NUM_ARG)); + + // dowork_poll_open_ssl (waiting SSL_ERROR_WANT_READ) + STRICT_EXPECTED_CALL(SSL_connect(SSL_Good_Ptr)).SetReturn(SSL_ERROR__plus__WANT_READ); + STRICT_EXPECTED_CALL(SSL_get_error(SSL_Good_Ptr, SSL_ERROR__plus__WANT_READ)); + + // dowork_poll_open_ssl (waiting SSL_ERROR_WANT_WRITE) + STRICT_EXPECTED_CALL(SSL_connect(SSL_Good_Ptr)).SetReturn(SSL_ERROR__plus__WANT_WRITE); + STRICT_EXPECTED_CALL(SSL_get_error(SSL_Good_Ptr, SSL_ERROR__plus__WANT_WRITE)); + + // dowork_poll_open_ssl (done) + STRICT_EXPECTED_CALL(SSL_connect(SSL_Good_Ptr)).SetReturn(SSL_CONNECT_SUCCESS); + tlsio_id->concrete_io_dowork(tlsio); // dowork_poll_dns (waiting) + tlsio_id->concrete_io_dowork(tlsio); // dowork_poll_dns (done) + tlsio_id->concrete_io_dowork(tlsio); // dowork_poll_socket (waiting) + tlsio_id->concrete_io_dowork(tlsio); // dowork_poll_socket (done) + tlsio_id->concrete_io_dowork(tlsio); // dowork_poll_open_ssl (waiting SSL_ERROR_WANT_READ) + tlsio_id->concrete_io_dowork(tlsio); // dowork_poll_open_ssl (waiting SSL_ERROR_WANT_WRITE) + ASSERT_IO_OPEN_CALLBACK(false, IO_OPEN_OK); + + ///act + tlsio_id->concrete_io_dowork(tlsio); // dowork_poll_open_ssl (done) + + ///assert + // Check that we got the on_open callback + ASSERT_IO_OPEN_CALLBACK(true, IO_OPEN_OK); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + + ///cleanup + tlsio_id->concrete_io_close(tlsio, on_io_close_complete, NULL); + tlsio_id->concrete_io_destroy(tlsio); + assert_gballoc_checks(); + } + + /* Tests_SRS_TLSIO_30_075: [ If the adapter is in TLSIO_STATE_EXT_CLOSED then tlsio_dowork shall do nothing. ]*/ + TEST_FUNCTION(tlsio_openssl_compact__dowork_pre_open__succeeds) + { + ///arrange + CONCRETE_IO_HANDLE tlsio = tlsio_id->concrete_io_create(&good_config); + umock_c_reset_all_calls(); + reset_callback_context_records(); + + ///act + tlsio_id->concrete_io_dowork(tlsio); + + ///assert + ASSERT_NO_CALLBACKS(); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + + ///cleanup + tlsio_id->concrete_io_destroy(tlsio); + assert_gballoc_checks(); + } + + /* Tests_SRS_TLSIO_30_070: [ If the tlsio_handle parameter is NULL, tlsio_dowork shall do nothing except log an error. ]*/ + TEST_FUNCTION(tlsio_openssl_compact__dowork_parameter_validation__fails) + { + ///arrange + + ///act + tlsio_id->concrete_io_dowork(NULL); + + ///assert + ASSERT_NO_CALLBACKS(); + + ///cleanup + assert_gballoc_checks(); + } + + /* Tests_SRS_TLSIO_30_035: [ On tlsio_open success the adapter shall enter TLSIO_STATE_EX_OPENING and return 0. ]*/ + /* Tests_SRS_TLSIO_30_034: [ The tlsio_open shall store the provided on_bytes_received, on_bytes_received_context, on_io_error, on_io_error_context, on_io_open_complete, and on_io_open_complete_context parameters for later use as specified and tested per other line entries in this document. ]*/ + TEST_FUNCTION(tlsio_openssl_compact__open__succeeds) + { + int open_result; + ///arrange + CONCRETE_IO_HANDLE tlsio = tlsio_id->concrete_io_create(&good_config); + umock_c_reset_all_calls(); + reset_callback_context_records(); + + STRICT_EXPECTED_CALL(dns_async_create(IGNORED_PTR_ARG, NULL)); + + ///act + open_result = tlsio_id->concrete_io_open(tlsio, on_io_open_complete, IO_OPEN_COMPLETE_CONTEXT, on_bytes_received, + IO_BYTES_RECEIVED_CONTEXT, on_io_error, IO_ERROR_CONTEXT); + + ///assert + ASSERT_ARE_EQUAL(int, open_result, 0); + // Should not have made any callbacks yet + ASSERT_IO_OPEN_CALLBACK(false, IO_OPEN_ERROR); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + // Ensure that the callbacks were stored properly in the internal instance + + ///cleanup + tlsio_id->concrete_io_destroy(tlsio); + assert_gballoc_checks(); + } + + /* Tests_SRS_TLSIO_30_038: [ If tlsio_open fails to enter TLSIO_STATE_EX_OPENING it shall return FAILURE. ]*/ + /* Tests_SRS_TLSIO_30_039: [ On failure, tlsio_open_async shall not call on_io_open_complete. ]*/ + TEST_FUNCTION(tlsio_openssl_compact__open_unhappy_path__fails) + { + int open_result; + ///arrange + CONCRETE_IO_HANDLE tlsio = tlsio_id->concrete_io_create(&good_config); + umock_c_reset_all_calls(); + reset_callback_context_records(); + + STRICT_EXPECTED_CALL(dns_async_create(IGNORED_PTR_ARG, NULL)).SetReturn(NULL); + + ///act + open_result = tlsio_id->concrete_io_open(tlsio, on_io_open_complete, IO_OPEN_COMPLETE_CONTEXT, on_bytes_received, + IO_BYTES_RECEIVED_CONTEXT, on_io_error, IO_ERROR_CONTEXT); + + ///assert + ASSERT_ARE_NOT_EQUAL(int, open_result, 0); + // Should not get a callback + ASSERT_IO_OPEN_CALLBACK(false, IO_OPEN_ERROR); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + + ///cleanup + tlsio_id->concrete_io_destroy(tlsio); + assert_gballoc_checks(); + } + + /* Tests_SRS_TLSIO_30_037: [ If the adapter is in any state other than TLSIO_STATE_EXT_CLOSED when tlsio_open is called, it shall log an error, and return FAILURE. ]*/ + /* Tests_SRS_TLSIO_30_039: [ On failure, tlsio_open_async shall not call on_io_open_complete. ]*/ + TEST_FUNCTION(tlsio_openssl_compact__open_wrong_state__fails) + { + int open_result_2; + ///arrange + CONCRETE_IO_HANDLE tlsio = tlsio_id->concrete_io_create(&good_config); + int open_result = tlsio_id->concrete_io_open(tlsio, on_io_open_complete, IO_OPEN_COMPLETE_CONTEXT, on_bytes_received, + IO_BYTES_RECEIVED_CONTEXT, on_io_error, IO_ERROR_CONTEXT); + ASSERT_ARE_EQUAL(int, open_result, 0); + reset_callback_context_records(); + + ///act + open_result_2 = tlsio_id->concrete_io_open(tlsio, on_io_open_complete, IO_OPEN_COMPLETE_CONTEXT, on_bytes_received, + IO_BYTES_RECEIVED_CONTEXT, on_io_error, IO_ERROR_CONTEXT); + + ///assert + ASSERT_ARE_NOT_EQUAL_WITH_MSG(int, open_result_2, 0, "Unexpected 2nd open success"); + ASSERT_IO_OPEN_CALLBACK(false, IO_OPEN_ERROR); + + ///cleanup + tlsio_id->concrete_io_destroy(tlsio); + assert_gballoc_checks(); + } + + /* Tests_SRS_TLSIO_30_030: [ If the tlsio_handle parameter is NULL, tlsio_open shall log an error and return FAILURE. ]*/ + /* Tests_SRS_TLSIO_30_031: [ If the on_io_open_complete parameter is NULL, tlsio_open shall log an error and return FAILURE. ]*/ + /* Tests_SRS_TLSIO_30_032: [ If the on_bytes_received parameter is NULL, tlsio_open shall log an error and return FAILURE. ]*/ + /* Tests_SRS_TLSIO_30_033: [ If the on_io_error parameter is NULL, tlsio_openss_open shall log an error and return FAILURE. ]*/ + /* Tests_SRS_TLSIO_30_039: [ On failure, tlsio_open_async shall not call on_io_open_complete. ]*/ + TEST_FUNCTION(tlsio_openssl_compact__open_parameter_validation_fails__fails) + { + ///arrange + + // Parameters arrays + bool p0[OPEN_PV_COUNT]; + ON_IO_OPEN_COMPLETE p1[OPEN_PV_COUNT]; + ON_BYTES_RECEIVED p2[OPEN_PV_COUNT]; + ON_IO_ERROR p3[OPEN_PV_COUNT]; + const char* fm[OPEN_PV_COUNT]; + int i; + + int k = 0; + p0[k] = false; p1[k] = on_io_open_complete; p2[k] = on_bytes_received; p3[k] = on_io_error; fm[k] = "Unexpected open success when tlsio_handle is NULL"; /* */ k++; + p0[k] = true; p1[k] = NULL; /* */ p2[k] = on_bytes_received; p3[k] = on_io_error; fm[k] = "Unexpected open success when on_io_open_complete is NULL"; k++; + p0[k] = true; p1[k] = on_io_open_complete; p2[k] = NULL; /* */ p3[k] = on_io_error; fm[k] = "Unexpected open success when on_bytes_received is NULL"; k++; + p0[k] = true; p1[k] = on_io_open_complete; p2[k] = on_bytes_received; p3[k] = NULL; /* */ fm[k] = "Unexpected open success when on_io_error is NULL"; /* */ k++; + + // Cycle through each failing combo of parameters + for (i = 0; i < OPEN_PV_COUNT; i++) + { + CONCRETE_IO_HANDLE tlsio; + int open_result; + ///arrange + reset_callback_context_records(); + tlsio = tlsio_id->concrete_io_create(&good_config); + + ///act + open_result = tlsio_id->concrete_io_open(p0[i] ? tlsio : NULL, p1[i], IO_OPEN_COMPLETE_CONTEXT, p2[i], + IO_BYTES_RECEIVED_CONTEXT, p3[i], IO_ERROR_CONTEXT); + + ///assert + ASSERT_ARE_NOT_EQUAL_WITH_MSG(int, open_result, 0, fm[i]); + ASSERT_IO_OPEN_CALLBACK(false, IO_OPEN_ERROR); + + ///cleanup + tlsio_id->concrete_io_destroy(tlsio); + } + assert_gballoc_checks(); + } + + /* Tests_SRS_TLSIO_OPENSSL_COMPACT_30_520 [ The tlsio_setoption shall do nothing and return 0. ]*/ + TEST_FUNCTION(tlsio_openssl_compact__setoption__succeeds) + { + int result; + ///arrange + CONCRETE_IO_HANDLE tlsio = tlsio_id->concrete_io_create(&good_config); + ASSERT_IS_NOT_NULL(tlsio); + umock_c_reset_all_calls(); + + ///act + result = tlsio_id->concrete_io_setoption(tlsio, "fake name", "fake value"); + + ///assert + ASSERT_ARE_EQUAL(int, 0, result); + + ///cleanup + tlsio_id->concrete_io_destroy(tlsio); + assert_gballoc_checks(); + } + + /* Tests_SRS_TLSIO_30_120: [ If the tlsio_handle parameter is NULL, tlsio_openssl_compact_setoption shall do nothing except log an error and return FAILURE. ]*/ + /* Tests_SRS_TLSIO_30_121: [ If the optionName parameter is NULL, tlsio_openssl_compact_setoption shall do nothing except log an error and return FAILURE. ]*/ + /* Tests_SRS_TLSIO_30_122: [ If the value parameter is NULL, tlsio_openssl_compact_setoption shall do nothing except log an error and return FAILURE. ]*/ + TEST_FUNCTION(tlsio_openssl_compact__setoption_parameter_validation__fails) + { + int k; + // Parameters arrays + bool p0[SETOPTION_PV_COUNT]; + const char* p1[SETOPTION_PV_COUNT]; + const char* p2[SETOPTION_PV_COUNT]; + const char* fm[SETOPTION_PV_COUNT]; + int i; + + ///arrange + umock_c_reset_all_calls(); + + k = 0; + p0[k] = false; p1[k] = "fake name"; p2[k] = "fake value"; fm[k] = "Unexpected setoption success when tlsio_handle is NULL"; /* */ k++; + p0[k] = true; p1[k] = NULL; /* */ p2[k] = "fake value"; fm[k] = "Unexpected setoption success when option_name is NULL"; /* */ k++; + p0[k] = true; p1[k] = "fake name"; p2[k] = NULL; /* */ fm[k] = "Unexpected setoption success when option_value is NULL"; /* */ k++; + + + // Cycle through each failing combo of parameters + for (i = 0; i < SETOPTION_PV_COUNT; i++) + { + int result; + ///arrange + CONCRETE_IO_HANDLE tlsio = tlsio_id->concrete_io_create(&good_config); + ASSERT_IS_NOT_NULL(tlsio); + + ///act + result = tlsio_id->concrete_io_setoption(p0[i] ? tlsio : NULL, p1[i], p2[i]); + + ///assert + ASSERT_ARE_NOT_EQUAL_WITH_MSG(int, 0, result, fm[i]); + + ///cleanup + tlsio_id->concrete_io_destroy(tlsio); + } + assert_gballoc_checks(); + } + + /* Tests_SRS_TLSIO_30_160: [ If the tlsio_handle parameter is NULL, tlsio_openssl_compact_retrieveoptions shall do nothing except log an error and return FAILURE. ]*/ + TEST_FUNCTION(tlsio_openssl_compact__retrieveoptions_parameter_validation__fails) + { + ///arrange + + ///act + OPTIONHANDLER_HANDLE result = tlsio_id->concrete_io_retrieveoptions(NULL); + + ///assert + ASSERT_IS_NULL((void*)result); + + ///cleanup + assert_gballoc_checks(); + } + + /* Tests_SRS_TLSIO_OPENSSL_COMPACT_30_560: [ The tlsio_retrieveoptions shall do nothing and return NULL. ]*/ + TEST_FUNCTION(tlsio_openssl_compact__retrieveoptions__fails) + { + OPTIONHANDLER_HANDLE result; + ///arrange + CONCRETE_IO_HANDLE tlsio = tlsio_id->concrete_io_create(&good_config); + ASSERT_IS_NOT_NULL(tlsio); + umock_c_reset_all_calls(); + + ///act + result = tlsio_id->concrete_io_retrieveoptions(tlsio); + + ///assert + ASSERT_IS_NULL((void*)result); + + ///cleanup + tlsio_id->concrete_io_destroy(tlsio); + assert_gballoc_checks(); + } + + /* Tests_SRS_TLSIO_30_013: [ If the io_create_parameters value is NULL, tlsio_openssl_compact_create shall log an error and return NULL. ]*/ + /* Tests_SRS_TLSIO_30_014: [ If the hostname member of io_create_parameters value is NULL, tlsio_create shall log an error and return NULL. ]*/ + /* Tests_SRS_TLSIO_30_015: [ If the port member of io_create_parameters value is less than 0 or greater than 0xffff, tlsio_create shall log an error and return NULL. ]*/ + TEST_FUNCTION(tlsio_openssl_compact__create_parameter_validation_fails__fails) + { + ///arrange + TLSIO_CONFIG config[4]; + create_parameters_t p[4]; + int i; + // config hostname port number failure message + populate_create_parameters(p + 0, NULL, /* */ SSL_good_host_name, SSL_good_port_number, "Should fail with NULL config"); + populate_create_parameters(p + 1, config + 1, NULL, /* */ SSL_good_port_number, "Should fail with NULL hostname"); + populate_create_parameters(p + 2, config + 2, SSL_good_host_name, SSL_port_number_too_low, "Should fail with port number too low"); + populate_create_parameters(p + 3, config + 3, SSL_good_host_name, SSL_port_number_too_high, "Should fail with port number too high"); + + // Cycle through each failing combo of parameters + for (i = 0; i < sizeof(config) / sizeof(TLSIO_CONFIG); i++) + { + ///act + CONCRETE_IO_HANDLE result = tlsio_id->concrete_io_create(p[i].config); + + ///assert + ASSERT_IS_NULL_WITH_MSG(result, p[i].fail_msg); + } + assert_gballoc_checks(); + } + + /* Tests_SRS_TLSIO_30_011: [ If any resource allocation fails, tlsio_create shall return NULL. ]*/ + TEST_FUNCTION(tlsio_openssl_compact__create_unhappy_paths__fails) + { + size_t i; + ///arrange + use_negative_mocks(); + + STRICT_EXPECTED_CALL(gballoc_malloc(IGNORED_NUM_ARG)); // concrete_io struct + STRICT_EXPECTED_CALL(gballoc_malloc(IGNORED_NUM_ARG)); // copy hostname + STRICT_EXPECTED_CALL(gballoc_malloc(IGNORED_NUM_ARG)); // singlylinkedlist_create + umock_c_negative_tests_snapshot(); + + for (i = 0; i < umock_c_negative_tests_call_count(); i++) + { + CONCRETE_IO_HANDLE result; + umock_c_negative_tests_reset(); + umock_c_negative_tests_fail_call(i); + + ///act + result = tlsio_id->concrete_io_create(&good_config); + + ///assert + ASSERT_IS_NULL(result); + } + + ///cleanup + assert_gballoc_checks(); + } + + /* Tests_SRS_TLSIO_30_010: [ The tlsio_create shall allocate and initialize all necessary resources and return an instance of the tlsio_openssl_compact. ]*/ + /* Tests_SRS_TLSIO_30_016: [ tlsio_create shall make a copy of the hostname member of io_create_parameters to allow deletion of hostname immediately after the call. ]*/ + /* Tests_SRS_TLSIO_30_012: [ The tlsio_create shall receive the connection configuration as a TLSIO_CONFIG* in io_create_parameters. ]*/ + TEST_FUNCTION(tlsio_openssl_compact__create__succeeds) + { + CONCRETE_IO_HANDLE result; + ///arrange + + STRICT_EXPECTED_CALL(gballoc_malloc(IGNORED_NUM_ARG)); // concrete_io struct + STRICT_EXPECTED_CALL(gballoc_malloc(IGNORED_NUM_ARG)); // copy hostname + STRICT_EXPECTED_CALL(gballoc_malloc(IGNORED_NUM_ARG)); // singlylinkedlist_create + // + + ///act + result = tlsio_id->concrete_io_create(&good_config); + + ///assert + ASSERT_IS_NOT_NULL(result); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + + ///cleanup + tlsio_id->concrete_io_destroy(result); + assert_gballoc_checks(); + } + + + /* Tests_SRS_TLSIO_30_022: [ If the adapter is in any state other than TLSIO_STATE_EX_CLOSED when tlsio_destroy is called, the adapter shall enter TLSIO_STATE_EX_CLOSING and then enter TLSIO_STATE_EX_CLOSED before completing the destroy process. ]*/ + TEST_FUNCTION(tlsio_openssl_compact__destroy_with_unsent_messages__succeeds) + { + CONCRETE_IO_HANDLE tlsio; + int send_result; + ///arrange + reset_callback_context_records(); + tlsio = tlsio_id->concrete_io_create(&good_config); + open_helper(tlsio); + + // Make sure the arrangement is correct + ASSERT_IO_OPEN_CALLBACK(true, IO_OPEN_OK); + + send_result = tlsio_id->concrete_io_send(tlsio, SSL_send_buffer, + SSL_SHORT_SENT_MESSAGE_SIZE, on_io_send_complete, IO_SEND_COMPLETE_CONTEXT); + ASSERT_ARE_EQUAL(int, send_result, 0); + send_result = tlsio_id->concrete_io_send(tlsio, SSL_send_buffer, + SSL_SHORT_SENT_MESSAGE_SIZE, on_io_send_complete, IO_SEND_COMPLETE_CONTEXT); + ASSERT_ARE_EQUAL(int, send_result, 0); + + + umock_c_reset_all_calls(); + STRICT_EXPECTED_CALL(SSL_shutdown(SSL_Good_Ptr)); + STRICT_EXPECTED_CALL(SSL_free(SSL_Good_Ptr)); + STRICT_EXPECTED_CALL(SSL_CTX_free(SSL_Good_Context_Ptr)); + STRICT_EXPECTED_CALL(socket_async_destroy(SSL_Good_Socket)); + // Message 1 delete + STRICT_EXPECTED_CALL(gballoc_free(IGNORED_PTR_ARG)); + STRICT_EXPECTED_CALL(gballoc_free(IGNORED_PTR_ARG)); + STRICT_EXPECTED_CALL(gballoc_free(IGNORED_PTR_ARG)); + // Message 2 delete + STRICT_EXPECTED_CALL(gballoc_free(IGNORED_PTR_ARG)); + STRICT_EXPECTED_CALL(gballoc_free(IGNORED_PTR_ARG)); + STRICT_EXPECTED_CALL(gballoc_free(IGNORED_PTR_ARG)); + // Close and delete tlsio + STRICT_EXPECTED_CALL(gballoc_free(IGNORED_PTR_ARG)); + STRICT_EXPECTED_CALL(gballoc_free(IGNORED_PTR_ARG)); + STRICT_EXPECTED_CALL(gballoc_free(IGNORED_PTR_ARG)); + // End of arrange + + ///act + tlsio_id->concrete_io_destroy(tlsio); + + ///assert + ASSERT_IO_SEND_ABANDONED(2); // 2 messages in this test + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + + + ///cleanup + assert_gballoc_checks(); + } + + /* Tests_SRS_TLSIO_30_020: [ If tlsio_handle is NULL, tlsio_destroy shall do nothing. ]*/ + TEST_FUNCTION(tlsio_openssl_compact__destroy_parameter_validation__fails) + { + ///arrange + + ///act + tlsio_id->concrete_io_destroy(NULL); + + ///assert + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + + ///cleanup + assert_gballoc_checks(); + } + + /* Tests_SRS_TLSIO_30_021: [ The tlsio__destroy shall release all allocated resources and then release tlsio_handle. ]*/ + TEST_FUNCTION(tlsio_openssl_compact__destroy_unopened__succeeds) + { + ///arrange + CONCRETE_IO_HANDLE result = tlsio_id->concrete_io_create(&good_config); + ASSERT_IS_NOT_NULL(result); + umock_c_reset_all_calls(); + + STRICT_EXPECTED_CALL(gballoc_free(IGNORED_NUM_ARG)); // copy hostname + STRICT_EXPECTED_CALL(gballoc_free(IGNORED_NUM_ARG)); // singlylinkedlist_create + STRICT_EXPECTED_CALL(gballoc_free(IGNORED_NUM_ARG)); // concrete_io struct + + ///act + tlsio_id->concrete_io_destroy(result); + + ///assert + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + + ///cleanup + assert_gballoc_checks(); + } + + /* Tests_SRS_TLSIO_30_008: [ The tlsio_get_interface_description shall return the VTable IO_INTERFACE_DESCRIPTION. ]*/ + /* Tests_SRS_TLSIO_30_001: [ The tlsio_openssl_compact shall implement and export all the Concrete functions in the VTable IO_INTERFACE_DESCRIPTION defined in the xio.h. ] */ + TEST_FUNCTION(tlsio_openssl_compact__tlsio_get_interface_description) + { + ///act + const IO_INTERFACE_DESCRIPTION* tlsio_id_ut = tlsio_pal_get_interface_description(); + + ///assert + // Later specific tests will verify the identity of each function + ASSERT_IS_NOT_NULL(tlsio_id_ut->concrete_io_close); + ASSERT_IS_NOT_NULL(tlsio_id_ut->concrete_io_create); + ASSERT_IS_NOT_NULL(tlsio_id_ut->concrete_io_destroy); + ASSERT_IS_NOT_NULL(tlsio_id_ut->concrete_io_dowork); + ASSERT_IS_NOT_NULL(tlsio_id_ut->concrete_io_open); + ASSERT_IS_NOT_NULL(tlsio_id_ut->concrete_io_retrieveoptions); + ASSERT_IS_NOT_NULL(tlsio_id_ut->concrete_io_send); + ASSERT_IS_NOT_NULL(tlsio_id_ut->concrete_io_setoption); + assert_gballoc_checks(); + } + +END_TEST_SUITE(tlsio_openssl_compact_unittests)