diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..fcbc6e0 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "sdk"] + path = sdk + url = https://github.com/Azure/azure-iot-sdk-c.git diff --git a/README.md b/README.md index 72f1506..e2ba56c 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,100 @@ +# Get Started with Microsoft Azure IoT Starter Kit - ESP32-DevKitC ("Core Board") + +This sample was tested with Espressif's **ESP32-DevKitC ("Core Board")**, but many other kits would work as well. + +Don't have a kit yet? Click [here](http://esp32.net/) + +This sample was modified from [this one](https://github.com/ustccw/AzureESP32.git). + +## Step 1 - Download the ESP32 SDK + +Clone the [Espressif IoT Development Framework](https://github.com/espressif/esp-idf) repository with the following command: + +`git clone https://github.com/espressif/esp-idf.git --recursive` + +## Step 2 - Set up the ESP32 toolchain + +Follow the instructions for setting up the ESP32 toolchain found [here](http://esp-idf.readthedocs.io/en/latest/#setup-toolchain). + +## Step 3 - Set IDF_PATH + +Set the IDF_PATH environment variable to point to the location of the **esp-idf** directory that you +cloned in Step 1. + +If you're using MSYS on Windows, a good place to set the IDF_PATH variable is in the +`~\msys32\home\user\.bashrc` file that gets created the first time you run `msys2_shell.cmd`. + +## Step 4 - Install the Azure IoT C SDK + +Change to the ESP32 SDK's `components` directory:
+`cd $IDF_PATH/components`
+ +Clone this repository into the `components` directory as `azure-iot`:
+`git clone --recursive https://github.com/Azure-Samples/iot-hub-c-esp32.git azure-iot` + +## Step 5 - Assemble the sample project + +Your `$IDF_PATH/components/azure-iot/sample` directory contains a sample project skeleton. You may +compile it from that location, or you may copy it to a new location, whichever you prefer. +To flesh out the skeleton, +copy the sample files from the Azure IoT SDK into `main` directory of the copy of the sample that +you're using: + +`sloc=$IDF_PATH/components/azure-iot/sdk/iothub_client/samples/iothub_client_sample_mqtt`
+`cp $sloc/iothub_client_sample_mqtt.h /main`
+`cp $sloc/iothub_client_sample_mqtt.c /main` + +## Step 6 - Set your device's connection string + +Create an IoT Hub and an associated device identity +[as shown here](https://docs.microsoft.com/en-us/azure/iot-hub/iot-hub-csharp-csharp-getstarted). +Then open the `main/iothub_client_sample_mqtt.c` file in the `main` directory of your +project and find the line near the top that reads + +```c +static const char* connectionString = ""; + +``` + +and set the value of the connectionString variable to the be the connection string of the device +identity that you created. + +## Step 7- Configure the make process + +Using the toolchain you installed in Step 2 (MSYS, for example), navigate to the location of the +sample you assembled in Step 4 and run the following command: + +`make menuconfig` + +This command will bring up a configuration dialog. + +1. Under "Serial flasher config --->Default serial port" set the serial port ID to that of your ESP32 device. (On Windows you can find the serial port ID under Computer Management.) + +1. Under "Example Configuration --->" enter your WiFi router SSID and password. + +1. Save the configuration and exit the dialog. + +## Step 8 - Run the make process + +Build the sample with the simple command: + +`make` + +This will produce a iothub_client_sample_mqtt.bin file, a partitions_singleapp.bin file, a bootloader/bootloader.bin file, plus associated maps. + +## Step 9 - Flash the ESP32 device + +Make sure the ESP32 device is plugged in and run the command: + +`make flash` + +This will flash the project onto the ESP32 device. Alternate methods of flashing the device can be found [here](https://espressif.com/en/support/download/other-tools) + +## Step 10 - Monitor the device output + +The sample program sends status output to the device's serial port at a default 115200 baud. You monitor this output by connecting to the serial port with any terminal program such as [Putty](http://www.putty.org/). + # Contributing This project welcomes contributions and suggestions. Most contributions require you to agree to a diff --git a/component.mk b/component.mk new file mode 100644 index 0000000..016290d --- /dev/null +++ b/component.mk @@ -0,0 +1,110 @@ +# +# Component Makefile +# + +# Component configuration in preprocessor defines +CFLAGS += -DUSE_LWIP_SOCKET_FOR_AZURE_IOT + + + +#COMPONENT_ADD_INCLUDEDIRS := sdk/iothub_client/inc sdk/c-utility/inc sdk/uamqp/inc + +#COMPONENT_SRCDIRS := sdk/iothub_client/src sdk/c-utility/src sdk/uamqp/src + +COMPONENT_ADD_INCLUDEDIRS := \ +pal \ +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 + +COMPONENT_OBJS = \ +sdk/c-utility/src/xlogging.o \ +sdk/c-utility/src/buffer.o \ +sdk/c-utility/src/consolelogger.o \ +sdk/c-utility/src/constbuffer.o \ +sdk/c-utility/src/constmap.o \ +sdk/c-utility/src/crt_abstractions.o \ +sdk/c-utility/src/doublylinkedlist.o \ +sdk/c-utility/src/gballoc.o \ +sdk/c-utility/src/gb_stdio.o \ +sdk/c-utility/src/gb_time.o \ +sdk/c-utility/src/hmac.o \ +sdk/c-utility/src/hmacsha256.o \ +sdk/c-utility/src/httpapiex.o \ +sdk/c-utility/src/httpapiexsas.o \ +sdk/c-utility/src/httpheaders.o \ +sdk/c-utility/src/map.o \ +sdk/c-utility/src/optionhandler.o \ +sdk/c-utility/src/sastoken.o \ +sdk/c-utility/src/sha1.o \ +sdk/c-utility/src/sha224.o \ +sdk/c-utility/src/sha384-512.o \ +sdk/c-utility/src/strings.o \ +sdk/c-utility/src/string_tokenizer.o \ +sdk/c-utility/src/urlencode.o \ +sdk/c-utility/src/usha.o \ +sdk/c-utility/src/vector.o \ +sdk/c-utility/src/xio.o \ +sdk/c-utility/src/base64.o \ +\ +\ +sdk/iothub_client/src/iothub_client.o \ +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_message.o \ +sdk/iothub_client/src/iothubtransport.o \ +sdk/iothub_client/src/iothubtransportmqtt.o \ +sdk/iothub_client/src/iothubtransport_mqtt_common.o \ +sdk/iothub_client/src/version.o \ +\ +\ +sdk/umqtt/src/mqtt_client.o \ +sdk/umqtt/src/mqtt_codec.o \ +sdk/umqtt/src/mqtt_message.o \ +\ +\ +\ +sdk/c-utility/adapters/agenttime.o \ +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/lwip/sntp_lwip.o \ +\ +pal/platform_openssl_compact.o \ +pal/tlsio_openssl_compact.o + +COMPONENT_SRCDIRS := \ +pal \ +sdk/c-utility/pal \ +sdk/c-utility/pal/free_rtos \ +sdk/c-utility/pal/lwip \ +sdk/c-utility/src \ +sdk/c-utility/adapters \ +sdk/umqtt/src \ +sdk/iothub_client/src \ +sdk/parson \ + + + + + + + + + + + + + + + diff --git a/pal/platform_openssl_compact.c b/pal/platform_openssl_compact.c new file mode 100644 index 0000000..b5b8df2 --- /dev/null +++ b/pal/platform_openssl_compact.c @@ -0,0 +1,42 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +#include "azure_c_shared_utility/platform.h" +#include "sntp.h" +#include "tlsio_openssl_compact.h" + +static const char* const ntpServer = "pool.ntp.org"; + +/* Codes_SRS_PLATFORM_OPENSSL_COMPACT_30_004: [ The platform_init shall initialize the tlsio adapter. ] */ +/* Codes_SRS_PLATFORM_OPENSSL_COMPACT_30_005: [ The platform_init shall initialize the sntp client. ] */ +int platform_init(void) +{ + // SNTP_SetServerName logs any necessary errors + int result = SNTP_SetServerName(ntpServer); + if (result == 0) + { + // SNTP_Init will have logged its own errors if necessary + result = SNTP_Init(); + } + return result; +} + +/* 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(); +} + +STRING_HANDLE platform_get_platform_info(void) +{ + return STRING_construct("(openssl_compact)"); +} + +/* Codes_SRS_PLATFORM_OPENSSL_COMPACT_30_006: [ The platform_deinit shall deinitialize the sntp client. ] */ +/* Codes_SRS_PLATFORM_OPENSSL_COMPACT_30_007: [ The platform_deinit shall deinitialize the tlsio adapter. ] */ +void platform_deinit(void) +{ + SNTP_Deinit(); + + // The tlsio adapter for this platform does not need (or support) deinitialization +} diff --git a/pal/sntp_os.h b/pal/sntp_os.h new file mode 100644 index 0000000..72d7140 --- /dev/null +++ b/pal/sntp_os.h @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +//This file pulls in OS-specific header files to allow compilation of socket_async.c under +// most OS's except for Windows. + +// For ESP32 lwIP systems which use the ESP-IDF's non-standard lwIP include structure +// Tested with: +// ESP32 + +#ifndef LWIP_SNTP_OS_H +#define LWIP_SNTP_OS_H + +#include "apps/sntp/sntp.h" + +#endif // LWIP_SNTP_OS_H \ No newline at end of file diff --git a/pal/socket_async_os.h b/pal/socket_async_os.h new file mode 100644 index 0000000..f91398e --- /dev/null +++ b/pal/socket_async_os.h @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +//This file pulls in OS-specific header files to allow compilation of socket_async.c under +// most OS's except for Windows. + +// For lwIP systems +// Tested with: +// ESP32 + +#ifndef SOCKET_ASYNC_OS_H +#define SOCKET_ASYNC_OS_H + +#include "lwip/sockets.h" +#include "lwip/netdb.h" + +#endif // SOCKET_ASYNC_OS_H \ No newline at end of file diff --git a/pal/tlsio_openssl_compact.c b/pal/tlsio_openssl_compact.c new file mode 100644 index 0000000..691f1e9 --- /dev/null +++ b/pal/tlsio_openssl_compact.c @@ -0,0 +1,879 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +#include + +#include "openssl/ssl.h" + +#include +#include +#include +#include "socket_async.h" +#include "tlsio_openssl_compact.h" +#include "dns_async.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" +#include "azure_c_shared_utility/crt_abstractions.h" + +typedef struct +{ + unsigned char* bytes; + size_t size; + size_t unsent_size; + ON_SEND_COMPLETE on_send_complete; + void* callback_context; +} PENDING_TRANSMISSION; + +#define MAX_VALID_PORT 0xffff + +// The TLSIO_RECEIVE_BUFFER_SIZE has very little effect on performance, and is kept small +// to minimize memory consumption. +#define TLSIO_RECEIVE_BUFFER_SIZE 64 + + +typedef enum TLSIO_STATE_TAG +{ + TLSIO_STATE_CLOSED, + TLSIO_STATE_OPENING_WAITING_DNS, + TLSIO_STATE_OPENING_WAITING_SOCKET, + TLSIO_STATE_OPENING_WAITING_SSL, + TLSIO_STATE_OPEN, + TLSIO_STATE_ERROR, +} TLSIO_STATE; + +bool is_an_opening_state(TLSIO_STATE state) +{ + return state == TLSIO_STATE_OPENING_WAITING_DNS || + state == TLSIO_STATE_OPENING_WAITING_SOCKET || + state == TLSIO_STATE_OPENING_WAITING_SSL; +} + +// This structure definition is mirrored in the unit tests, so if you change +// this struct, keep it in sync with the one in tlsio_openssl_compact_ut.c +typedef struct TLS_IO_INSTANCE_TAG +{ + ON_BYTES_RECEIVED on_bytes_received; + ON_IO_ERROR on_io_error; + ON_IO_OPEN_COMPLETE on_open_complete; + void* on_bytes_received_context; + void* on_io_error_context; + void* on_open_complete_context; + SSL* ssl; + SSL_CTX* ssl_context; + TLSIO_STATE tlsio_state; + DNS_ASYNC_HANDLE dns; + char* hostname; + uint16_t port; + SOCKET_ASYNC_HANDLE sock; + SINGLYLINKEDLIST_HANDLE pending_transmission_list; +} TLS_IO_INSTANCE; + +/* Codes_SRS_TLSIO_30_005: [ The phrase "enter TLSIO_STATE_EXT_ERROR" means the adapter shall call the on_io_error function and pass the on_io_error_context that was supplied in tlsio_open_async. ]*/ +static void enter_tlsio_error_state(TLS_IO_INSTANCE* tls_io_instance) +{ + if (tls_io_instance->tlsio_state != TLSIO_STATE_ERROR) + { + tls_io_instance->tlsio_state = TLSIO_STATE_ERROR; + tls_io_instance->on_io_error(tls_io_instance->on_io_error_context); + } +} + +/* Codes_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 . ]*/ +static void enter_open_error_state(TLS_IO_INSTANCE* tls_io_instance) +{ + enter_tlsio_error_state(tls_io_instance); + // on_open_complete has already been checked for non-NULL + tls_io_instance->on_open_complete(tls_io_instance->on_open_complete_context, IO_OPEN_ERROR); +} + +// Return true if a message was available to remove +static bool process_and_destroy_head_message(TLS_IO_INSTANCE* tls_io_instance, IO_SEND_RESULT send_result) +{ + bool result; + LIST_ITEM_HANDLE head_pending_io; + if (send_result == IO_SEND_ERROR) + { + /* Codes_SRS_TLSIO_30_095: [ If the send process fails before sending all of the bytes in an enqueued message, the tlsio_dowork shall call the message's on_send_complete along with its associated callback_context and IO_SEND_ERROR. ]*/ + enter_tlsio_error_state(tls_io_instance); + } + head_pending_io = singlylinkedlist_get_head_item(tls_io_instance->pending_transmission_list); + if (head_pending_io != NULL) + { + PENDING_TRANSMISSION* head_message = (PENDING_TRANSMISSION*)singlylinkedlist_item_get_value(head_pending_io); + // on_send_complete is checked for NULL during PENDING_TRANSMISSION creation + /* Codes_SRS_TLSIO_30_095: [ If the send process fails before sending all of the bytes in an enqueued message, the tlsio_dowork shall call the message's on_send_complete along with its associated callback_context and IO_SEND_ERROR. ]*/ + head_message->on_send_complete(head_message->callback_context, send_result); + + free(head_message->bytes); + free(head_message); + if (singlylinkedlist_remove(tls_io_instance->pending_transmission_list, head_pending_io) != 0) + { + // This particular situation is a bizarre and unrecoverable internal error + /* Codes_SRS_TLSIO_30_094: [ If the send process encounters an internal error or calls on_send_complete with IO_SEND_ERROR due to either failure or timeout, it shall also call on_io_error and pass in the associated on_io_error_context. ]*/ + enter_tlsio_error_state(tls_io_instance); + LogError("Failed to remove message from list"); + } + result = true; + } + else + { + result = false; + } + return result; +} + +static void internal_close(TLS_IO_INSTANCE* tls_io_instance) +{ + /* Codes_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. ]*/ + /* Codes_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. ]*/ + /* Codes_SRS_TLSIO_30_051: [ On success, if the underlying TLS does not support asynchronous closing, then the adapter shall enter TLSIO_STATE_EXT_CLOSED immediately after entering TLSIO_STATE_EX_CLOSING. ]*/ + if (tls_io_instance->tlsio_state == TLSIO_STATE_OPEN) + { + // From the OpenSSL manual pages: "According to the TLS standard, it is acceptable + // for an application to only send its shutdown alert and then close the + // underlying connection without waiting for the peer's response...". It goes + // on to say that waiting for shutdown only makes sense if the underlying + // connection is being re-used, which we do not do. So there's no need + // to wait for shutdown. The SSL_shutdown result is not logged because the + // return values are of no interest for unidirectional shutdown, which is + // what we use. + (void)SSL_shutdown(tls_io_instance->ssl); + } + + if (tls_io_instance->dns != NULL) + { + dns_async_destroy(tls_io_instance->dns); + tls_io_instance->dns = NULL; + } + if (tls_io_instance->ssl != NULL) + { + SSL_free(tls_io_instance->ssl); + tls_io_instance->ssl = NULL; + } + if (tls_io_instance->ssl_context != NULL) + { + SSL_CTX_free(tls_io_instance->ssl_context); + tls_io_instance->ssl_context = NULL; + } + if (tls_io_instance->sock >= 0) + { + // The underlying socket API does not support waiting for close + // to complete, so it isn't possible to do so. + socket_async_destroy(tls_io_instance->sock); + tls_io_instance->sock = -1; + } + + while (process_and_destroy_head_message(tls_io_instance, IO_SEND_CANCELLED)); + // singlylinkedlist_destroy gets called in the main destroy + + tls_io_instance->on_bytes_received = NULL; + tls_io_instance->on_io_error = NULL; + tls_io_instance->on_bytes_received_context = NULL; + tls_io_instance->on_io_error_context = NULL; + tls_io_instance->tlsio_state = TLSIO_STATE_CLOSED; + tls_io_instance->on_open_complete = NULL; + tls_io_instance->on_open_complete_context = NULL; +} + +// This method tests for hard errors returned from either SSL_write or SSL_connect. +// Returns +// 0 for SSL_ERROR_WANT_READ or SSL_ERROR_WANT_WRITE +// The actual error for other errors (real failures) +static int is_hard_ssl_error(SSL* ssl, int callReturn) +{ + int result = SSL_get_error(ssl, callReturn); + if (result == SSL_ERROR_WANT_READ || result == SSL_ERROR_WANT_WRITE) + { + result = 0; + } + return result; +} + +static void tlsio_openssl_destroy(CONCRETE_IO_HANDLE tls_io) +{ + if (tls_io == NULL) + { + /* Codes_SRS_TLSIO_30_020: [ If tlsio_handle is NULL, tlsio_destroy shall do nothing. ]*/ + LogError("NULL tlsio"); + } + else + { + TLS_IO_INSTANCE* tls_io_instance = (TLS_IO_INSTANCE*)tls_io; + if (tls_io_instance->tlsio_state != TLSIO_STATE_CLOSED) + { + /* Codes_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. ]*/ + LogError("tlsio_openssl_destroy called while not in TLSIO_STATE_CLOSED."); + internal_close(tls_io_instance); + } + /* Codes_SRS_TLSIO_30_021: [ The tlsio_destroy shall release all allocated resources and then release tlsio_handle. ]*/ + if (tls_io_instance->hostname != NULL) + { + free(tls_io_instance->hostname); + } + + if (tls_io_instance->pending_transmission_list != NULL) + { + /* Pending messages were cleared in internal_close */ + singlylinkedlist_destroy(tls_io_instance->pending_transmission_list); + } + + free(tls_io_instance); + } +} + +/* Codes_SRS_TLSIO_30_010: [ The tlsio_create shall allocate and initialize all necessary resources and return an instance of the tlsio_openssl_compact. ]*/ +static CONCRETE_IO_HANDLE tlsio_openssl_create(void* io_create_parameters) +{ + TLS_IO_INSTANCE* result; + + if (io_create_parameters == NULL) + { + /* Codes_SRS_TLSIO_30_013: [ If the io_create_parameters value is NULL, tlsio_create shall log an error and return NULL. ]*/ + LogError("NULL tls_io_config"); + result = NULL; + } + else + { + /* Codes_SRS_TLSIO_30_012: [ The tlsio_create shall receive the connection configuration as a TLSIO_CONFIG* in io_create_parameters. ]*/ + TLSIO_CONFIG* tls_io_config = (TLSIO_CONFIG*)io_create_parameters; + if (tls_io_config->hostname == NULL) + { + /* Codes_SRS_TLSIO_30_014: [ If the hostname member of io_create_parameters value is NULL, tlsio_create shall log an error and return NULL. ]*/ + LogError("NULL tls_io_config->hostname"); + result = NULL; + } + else + { + if (tls_io_config->port < 0 || tls_io_config->port > MAX_VALID_PORT) + { + /* Codes_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. ]*/ + LogError("tls_io_config->port out of range"); + result = NULL; + } + else + { + result = malloc(sizeof(TLS_IO_INSTANCE)); + if (result == NULL) + { + /* Codes_SRS_TLSIO_30_011: [ If any resource allocation fails, tlsio_create shall return NULL. ]*/ + LogError("malloc failed"); + } + else + { + int ms_result; + memset(result, 0, sizeof(TLS_IO_INSTANCE)); + result->port = (uint16_t)tls_io_config->port; + result->tlsio_state = TLSIO_STATE_CLOSED; + result->sock = SOCKET_ASYNC_INVALID_SOCKET; + result->hostname = NULL; + result->dns = NULL; + result->pending_transmission_list = NULL; + /* Codes_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. ]*/ + ms_result = mallocAndStrcpy_s(&result->hostname, tls_io_config->hostname); + if (ms_result != 0) + { + /* Codes_SRS_TLSIO_30_011: [ If any resource allocation fails, tlsio_create shall return NULL. ]*/ + LogError("malloc failed"); + tlsio_openssl_destroy(result); + result = NULL; + } + else + { + // Create the message queue + result->pending_transmission_list = singlylinkedlist_create(); + if (result->pending_transmission_list == NULL) + { + /* Codes_SRS_TLSIO_30_011: [ If any resource allocation fails, tlsio_create shall return NULL. ]*/ + LogError("Failed singlylinkedlist_create"); + tlsio_openssl_destroy(result); + result = NULL; + } + } + } + } + } + } + + return (CONCRETE_IO_HANDLE)result; +} + + +static int tlsio_openssl_open_async(CONCRETE_IO_HANDLE tls_io, + ON_IO_OPEN_COMPLETE on_io_open_complete, void* on_io_open_complete_context, + ON_BYTES_RECEIVED on_bytes_received, void* on_bytes_received_context, + ON_IO_ERROR on_io_error, void* on_io_error_context) +{ + + int result; + if (on_io_open_complete == NULL) + { + /* Codes_SRS_TLSIO_30_031: [ If the on_io_open_complete parameter is NULL, tlsio_open shall log an error and return FAILURE. ]*/ + LogError("Required parameter on_io_open_complete is NULL"); + result = __FAILURE__; + } + else + { + if (tls_io == NULL) + { + /* Codes_SRS_TLSIO_30_030: [ If the tlsio_handle parameter is NULL, tlsio_open shall log an error and return FAILURE. ]*/ + result = __FAILURE__; + LogError("NULL tlsio"); + } + else + { + if (on_bytes_received == NULL) + { + /* Codes_SRS_TLSIO_30_032: [ If the on_bytes_received parameter is NULL, tlsio_open shall log an error and return FAILURE. ]*/ + LogError("Required parameter on_bytes_received is NULL"); + result = __FAILURE__; + } + else + { + if (on_io_error == NULL) + { + /* Codes_SRS_TLSIO_30_033: [ If the on_io_error parameter is NULL, tlsio_open shall log an error and return FAILURE. ]*/ + LogError("Required parameter on_io_error is NULL"); + result = __FAILURE__; + } + else + { + TLS_IO_INSTANCE* tls_io_instance = (TLS_IO_INSTANCE*)tls_io; + + if (tls_io_instance->tlsio_state != TLSIO_STATE_CLOSED) + { + /* Codes_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. ]*/ + LogError("Invalid tlsio_state. Expected state is TLSIO_STATE_CLOSED."); + result = __FAILURE__; + } + else + { + tls_io_instance->dns = dns_async_create(tls_io_instance->hostname, NULL); + if (tls_io_instance->dns == NULL) + { + /* Codes_SRS_TLSIO_30_038: [ If tlsio_open fails to enter TLSIO_STATE_EX_OPENING it shall return FAILURE. ]*/ + LogError("dns_async_create failed"); + result = __FAILURE__; + } + else + { + /* Codes_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. ]*/ + tls_io_instance->on_bytes_received = on_bytes_received; + tls_io_instance->on_bytes_received_context = on_bytes_received_context; + + tls_io_instance->on_io_error = on_io_error; + tls_io_instance->on_io_error_context = on_io_error_context; + + tls_io_instance->on_open_complete = on_io_open_complete; + tls_io_instance->on_open_complete_context = on_io_open_complete_context; + + /* Codes_SRS_TLSIO_30_035: [ On tlsio_open success the adapter shall enter TLSIO_STATE_EX_OPENING and return 0. ]*/ + // All the real work happens in dowork + tls_io_instance->tlsio_state = TLSIO_STATE_OPENING_WAITING_DNS; + result = 0; + } + } + } + } + } + /* Codes_SRS_TLSIO_30_039: [ On failure, tlsio_open_async shall not call on_io_open_complete. ]*/ + } + + return result; +} + +// This implementation does not have asynchronous close, but uses the _async name for consistencty with the spec +static int tlsio_openssl_close_async(CONCRETE_IO_HANDLE tls_io, ON_IO_CLOSE_COMPLETE on_io_close_complete, void* callback_context) +{ + int result; + + if (tls_io == NULL) + { + /* Codes_SRS_TLSIO_30_050: [ If the tlsio_handle parameter is NULL, tlsio_openssl_close_async shall log an error and return FAILURE. ]*/ + LogError("NULL tlsio"); + result = __FAILURE__; + } + else + { + if (on_io_close_complete == NULL) + { + /* Codes_SRS_TLSIO_30_055: [ If the on_io_close_complete parameter is NULL, tlsio_openssl_close_async shall log an error and return FAILURE. ]*/ + LogError("NULL on_io_close_complete"); + result = __FAILURE__; + } + else + { + TLS_IO_INSTANCE* tls_io_instance = (TLS_IO_INSTANCE*)tls_io; + + if (tls_io_instance->tlsio_state != TLSIO_STATE_OPEN && + tls_io_instance->tlsio_state != TLSIO_STATE_ERROR) + { + /* Codes_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. ]*/ + // LogInfo rather than LogError because this is an unusual but not erroneous situation + LogInfo("tlsio_openssl_close has been called when in neither TLSIO_STATE_OPEN nor TLSIO_STATE_ERROR."); + } + + if (is_an_opening_state(tls_io_instance->tlsio_state)) + { + /* Codes_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. ]*/ + tls_io_instance->on_open_complete(tls_io_instance->on_open_complete_context, IO_OPEN_CANCELLED); + } + // This adapter does not support asynchronous closing + /* Codes_SRS_TLSIO_30_056: [ On success the adapter shall enter TLSIO_STATE_EX_CLOSING. ]*/ + /* Codes_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. ]*/ + /* Codes_SRS_TLSIO_30_052: [ On success tlsio_close shall return 0. ]*/ + internal_close(tls_io_instance); + on_io_close_complete(callback_context); + result = 0; + } + } + /* Codes_SRS_TLSIO_30_054: [ On failure, the adapter shall not call on_io_close_complete. ]*/ + + return result; +} + +static int tlsio_openssl_send_async(CONCRETE_IO_HANDLE tls_io, const void* buffer, size_t size, ON_SEND_COMPLETE on_send_complete, void* callback_context) +{ + int result; + if (on_send_complete == NULL) + { + /* Codes_SRS_TLSIO_30_062: [ If the on_send_complete is NULL, tlsio_openssl_compact_send shall log the error and return FAILURE. ]*/ + result = __FAILURE__; + LogError("NULL on_send_complete"); + } + else + { + if (tls_io == NULL) + { + /* Codes_SRS_TLSIO_30_060: [ If the tlsio_handle parameter is NULL, tlsio_openssl_compact_send shall log an error and return FAILURE. ]*/ + result = __FAILURE__; + LogError("NULL tlsio"); + } + else + { + if (buffer == NULL) + { + /* Codes_SRS_TLSIO_30_061: [ If the buffer is NULL, tlsio_openssl_compact_send shall log the error and return FAILURE. ]*/ + result = __FAILURE__; + LogError("NULL buffer"); + } + else + { + if (size == 0) + { + /* Codes_SRS_TLSIO_30_067: [ If the size is 0, tlsio_send shall log the error and return FAILURE. ]*/ + result = __FAILURE__; + LogError("0 size"); + } + else + { + TLS_IO_INSTANCE* tls_io_instance = (TLS_IO_INSTANCE*)tls_io; + if (tls_io_instance->tlsio_state != TLSIO_STATE_OPEN) + { + /* Codes_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. ]*/ + result = __FAILURE__; + LogError("tlsio_openssl_send_async without a prior successful open"); + } + else + { + PENDING_TRANSMISSION* pending_transmission = (PENDING_TRANSMISSION*)malloc(sizeof(PENDING_TRANSMISSION)); + if (pending_transmission == NULL) + { + /* Codes_SRS_TLSIO_30_064: [ If the supplied message cannot be enqueued for transmission, tlsio_openssl_compact_send shall log an error and return FAILURE. ]*/ + result = __FAILURE__; + LogError("malloc failed"); + } + else + { + /* Codes_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. ]*/ + pending_transmission->bytes = (unsigned char*)malloc(size); + + if (pending_transmission->bytes == NULL) + { + /* Codes_SRS_TLSIO_30_064: [ If the supplied message cannot be enqueued for transmission, tlsio_openssl_compact_send shall log an error and return FAILURE. ]*/ + LogError("malloc failed"); + free(pending_transmission); + result = __FAILURE__; + } + else + { + pending_transmission->size = size; + pending_transmission->unsent_size = size; + pending_transmission->on_send_complete = on_send_complete; + pending_transmission->callback_context = callback_context; + (void)memcpy(pending_transmission->bytes, buffer, size); + + if (singlylinkedlist_add(tls_io_instance->pending_transmission_list, pending_transmission) == NULL) + { + /* Codes_SRS_TLSIO_30_064: [ If the supplied message cannot be enqueued for transmission, tlsio_openssl_compact_send shall log an error and return FAILURE. ]*/ + LogError("Unable to add socket to pending list."); + free(pending_transmission->bytes); + free(pending_transmission); + result = __FAILURE__; + } + else + { + /* Codes_SRS_TLSIO_30_063: [ On success, tlsio_send shall enqueue for transmission the on_send_complete , the callback_context , the size , and the contents of buffer and then return 0. ]*/ + result = 0; + } + } + } + } + } + } + } + /* Codes_SRS_TLSIO_30_066: [ On failure, on_send_complete shall not be called. ]*/ + } + return result; +} + +static void dowork_read(TLS_IO_INSTANCE* tls_io_instance) +{ + // TRANSFER_BUFFER_SIZE is not very important because if the message is bigger + // then the framework just calls dowork repeatedly until it gets everything. So + // a bigger buffer would just use memory without buying anything. + // Putting this buffer in a small function also allows it to exist on the stack + // rather than adding to heap fragmentation. + unsigned char buffer[TLSIO_RECEIVE_BUFFER_SIZE]; + int rcv_bytes; + + if (tls_io_instance->tlsio_state == TLSIO_STATE_OPEN) + { + // SSL_read is not checked for errors because the "no data" condition is reported as a + // failure, but the docs do not guarantee that it will always be the same failure, + // so we have no reliable way to distinguish "no data" from something else. + // + // Pump all of the bytes currently available out + rcv_bytes = SSL_read(tls_io_instance->ssl, buffer, sizeof(buffer)); + while (rcv_bytes > 0) + { + // tls_io_instance->on_bytes_received was already checked for NULL + // in the call to tlsio_openssl_open_async + /* Codes_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. ]*/ + tls_io_instance->on_bytes_received(tls_io_instance->on_bytes_received_context, buffer, rcv_bytes); + rcv_bytes = SSL_read(tls_io_instance->ssl, buffer, sizeof(buffer)); + } + /* Codes_SRS_TLSIO_30_102: [ If the TLS connection receives no data then tlsio_dowork shall not call the on_bytes_received callback. ]*/ + } +} + + +static int create_ssl(TLS_IO_INSTANCE* tls_io_instance) +{ + int result; + int ret; + + tls_io_instance->ssl_context = SSL_CTX_new(TLSv1_2_client_method()); + if (tls_io_instance->ssl_context == NULL) + { + /* Codes_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. ]*/ + result = __FAILURE__; + LogError("create new SSL CTX failed"); + } + else + { + tls_io_instance->ssl = SSL_new(tls_io_instance->ssl_context); + if (tls_io_instance->ssl == NULL) + { + /* Codes_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. ]*/ + result = __FAILURE__; + LogError("SSL_new failed"); + } + else + { + // returns 1 on success + ret = SSL_set_fd(tls_io_instance->ssl, tls_io_instance->sock); + if (ret != 1) + { + /* Codes_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. ]*/ + result = __FAILURE__; + LogError("SSL_set_fd failed"); + } + else + { + result = 0; + } + } + } + + return result; +} + +static void dowork_send(TLS_IO_INSTANCE* tls_io_instance) +{ + LIST_ITEM_HANDLE first_pending_io = singlylinkedlist_get_head_item(tls_io_instance->pending_transmission_list); + if (first_pending_io != NULL) + { + PENDING_TRANSMISSION* pending_message = (PENDING_TRANSMISSION*)singlylinkedlist_item_get_value(first_pending_io); + uint8_t* buffer = ((uint8_t*)pending_message->bytes) + + pending_message->size - pending_message->unsent_size; + int write_result = SSL_write(tls_io_instance->ssl, buffer, pending_message->unsent_size); + // https://wiki.openssl.org/index.php/Manual:SSL_write(3) + + if (write_result > 0) + { + pending_message->unsent_size -= write_result; + if (pending_message->unsent_size == 0) + { + /* Codes_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. ]*/ + // The whole message has been sent successfully + process_and_destroy_head_message(tls_io_instance, IO_SEND_OK); + } + else + { + /* Codes_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. ]*/ + // Repeat the send on the next pass with the rest of the message + // This empty else compiles to nothing but helps readability + } + } + else + { + // SSL_write returned non-success. It may just be busy, or it may be broken. + int hard_error = is_hard_ssl_error(tls_io_instance->ssl, write_result); + if (hard_error != 0) + { + /* Codes_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. ]*/ + /* Codes_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 . ]*/ + /* Codes_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. ]*/ + // This is an unexpected error, and we need to bail out. Probably lost internet connection. + LogInfo("Error from SSL_write: %d", hard_error); + process_and_destroy_head_message(tls_io_instance, IO_SEND_ERROR); + } + } + } + else + { + /* Codes_SRS_TLSIO_30_096: [ If there are no enqueued messages available, tlsio_openssl_compact_dowork shall do nothing. ]*/ + } +} + +static void dowork_poll_dns(TLS_IO_INSTANCE* tls_io_instance) +{ + bool dns_is_complete = dns_async_is_lookup_complete(tls_io_instance->dns); + + if (dns_is_complete) + { + uint32_t host_ipV4_address = dns_async_get_ipv4(tls_io_instance->dns); + dns_async_destroy(tls_io_instance->dns); + tls_io_instance->dns = NULL; + if (host_ipV4_address == 0) + { + // Transition to TSLIO_STATE_ERROR + /* Codes_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. ]*/ + // The DNS failure has already been logged + enter_open_error_state(tls_io_instance); + } + else + { + SOCKET_ASYNC_HANDLE sock = socket_async_create(host_ipV4_address, tls_io_instance->port, false, NULL); + if (sock < 0) + { + // This is a communication interruption rather than a program bug + /* Codes_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. ]*/ + LogInfo("Could not open the socket"); + enter_open_error_state(tls_io_instance); + } + else + { + // The socket has been created successfully, so now wait for it to + // finish the TCP handshake. + tls_io_instance->sock = sock; + tls_io_instance->tlsio_state = TLSIO_STATE_OPENING_WAITING_SOCKET; + } + } + } +} + +static void dowork_poll_socket(TLS_IO_INSTANCE* tls_io_instance) +{ + bool is_complete; + int result = socket_async_is_create_complete(tls_io_instance->sock, &is_complete); + if (result != 0) + { + // Transition to TSLIO_STATE_ERROR + LogInfo("socket_async_is_create_complete failure"); + enter_open_error_state(tls_io_instance); + } + else + { + if (is_complete) + { + // Attempt to transition to TLSIO_STATE_OPENING_WAITING_SSL + int create_ssl_result = create_ssl(tls_io_instance); + if (create_ssl_result != 0) + { + // Transition to TSLIO_STATE_ERROR + // create_ssl already did error logging + enter_open_error_state(tls_io_instance); + } + else + { + tls_io_instance->tlsio_state = TLSIO_STATE_OPENING_WAITING_SSL; + } + } + } +} + +static void dowork_poll_open_ssl(TLS_IO_INSTANCE* tls_io_instance) +{ + // https://www.openssl.org/docs/man1.0.2/ssl/SSL_connect.html + + // "If the underlying BIO is non - blocking, SSL_connect() will also + // return when the underlying BIO could not satisfy the needs of + // SSL_connect() to continue the handshake, indicating the + // problem by the return value -1. In this case a call to + // SSL_get_error() with the return value of SSL_connect() will + // yield SSL_ERROR_WANT_READ or SSL_ERROR_WANT_WRITE.The calling + // process then must repeat the call after taking appropriate + // action to satisfy the needs of SSL_connect().The action + // depends on the underlying BIO. When using a non - blocking + // socket, nothing is to be done, but select() can be used to + // check for the required condition." + + int connect_result = SSL_connect(tls_io_instance->ssl); + + // The following note applies to the Espressif ESP32 implementation + // of OpenSSL: + // The manual pages say that 0 is a failure, + // but by experiment, 0 is the success result, at least when using + // SSL_set_fd instead of custom BIO. + // https://www.openssl.org/docs/man1.0.2/ssl/SSL_connect.html + if (connect_result == 1 || connect_result == 0) + { + /* Codes_SRS_TLSIO_30_080: [ The tlsio_dowork shall establish a TLS connection using the hostName and port provided during tlsio_open. ]*/ + // Connect succeeded + tls_io_instance->tlsio_state = TLSIO_STATE_OPEN; + /* Codes_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 . ]*/ + /* Codes_SRS_TLSIO_30_083: [ If tlsio_dowork successfully opens the TLS connection it shall enter TLSIO_STATE_EX_OPEN. ]*/ + tls_io_instance->on_open_complete(tls_io_instance->on_open_complete_context, IO_OPEN_OK); + } + else + { + int hard_error = is_hard_ssl_error(tls_io_instance->ssl, connect_result); + if (hard_error != 0) + { + LogInfo("Hard error from SSL_connect: %d", hard_error); + enter_open_error_state(tls_io_instance); + } + } +} + +static void tlsio_openssl_dowork(CONCRETE_IO_HANDLE tls_io) +{ + if (tls_io == NULL) + { + /* Codes_SRS_TLSIO_30_070: [ If the tlsio_handle parameter is NULL, tlsio_dowork shall do nothing except log an error. ]*/ + LogError("NULL tlsio"); + } + else + { + TLS_IO_INSTANCE* tls_io_instance = (TLS_IO_INSTANCE*)tls_io; + + // This switch statement handles all of the state transitions during the opening process + switch (tls_io_instance->tlsio_state) + { + case TLSIO_STATE_CLOSED: + /* Codes_SRS_TLSIO_30_075: [ If the adapter is in TLSIO_STATE_EXT_CLOSED then tlsio_dowork shall do nothing. ]*/ + // Waiting to be opened, nothing to do + break; + case TLSIO_STATE_OPENING_WAITING_DNS: + //LogInfo("dowork_poll_dns"); + dowork_poll_dns(tls_io_instance); + break; + case TLSIO_STATE_OPENING_WAITING_SOCKET: + //LogInfo("dowork_poll_socket"); + dowork_poll_socket(tls_io_instance); + break; + case TLSIO_STATE_OPENING_WAITING_SSL: + //LogInfo("dowork_poll_ssl"); + dowork_poll_open_ssl(tls_io_instance); + break; + case TLSIO_STATE_OPEN: + dowork_read(tls_io_instance); + dowork_send(tls_io_instance); + break; + case TLSIO_STATE_ERROR: + /* Codes_SRS_TLSIO_30_071: [ If the adapter is in TLSIO_STATE_EXT_ERROR then tlsio_dowork shall do nothing. ]*/ + // There's nothing valid to do here but wait to be retried + break; + default: + LogError("Unexpected internal tlsio state"); + break; + } + } +} + +static int tlsio_openssl_setoption(CONCRETE_IO_HANDLE tls_io, const char* optionName, const void* value) +{ + TLS_IO_INSTANCE* tls_io_instance = (TLS_IO_INSTANCE*)tls_io; + /* Codes_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. ]*/ + int result; + if (tls_io_instance == NULL) + { + LogError("NULL tlsio"); + result = __FAILURE__; + } + else + { + /* Codes_SRS_TLSIO_30_121: [ If the optionName parameter is NULL, tlsio_openssl_compact_setoption shall do nothing except log an error and return FAILURE. ]*/ + if (optionName == NULL) + { + LogError("Required optionName parameter is NULL"); + result = __FAILURE__; + } + else + { + /* Codes_SRS_TLSIO_30_122: [ If the value parameter is NULL, tlsio_openssl_compact_setoption shall do nothing except log an error and return FAILURE. ]*/ + if (value == NULL) + { + LogError("Required value parameter is NULL"); + result = __FAILURE__; + } + else + { + /* Codes_SRS_TLSIO_OPENSSL_COMPACT_30_520 [ The tlsio_setoption shall do nothing and return 0. ]*/ + result = 0; + } + } + } + return result; +} + +/* Codes_SRS_TLSIO_OPENSSL_COMPACT_30_560: [ The tlsio_retrieveoptions shall do nothing and return NULL. ]*/ +static OPTIONHANDLER_HANDLE tlsio_openssl_retrieveoptions(CONCRETE_IO_HANDLE tls_io) +{ + TLS_IO_INSTANCE* tls_io_instance = (TLS_IO_INSTANCE*)tls_io; + /* Codes_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. ]*/ + OPTIONHANDLER_HANDLE result; + if (tls_io_instance == NULL) + { + LogError("NULL tlsio"); + result = NULL; + } + else + { + result = NULL; + } + return result; +} + +/* Codes_SRS_TLSIO_30_008: [ The tlsio_get_interface_description shall return the VTable IO_INTERFACE_DESCRIPTION. ]*/ +static const IO_INTERFACE_DESCRIPTION tlsio_openssl_interface_description = +{ + tlsio_openssl_retrieveoptions, + tlsio_openssl_create, + tlsio_openssl_destroy, + tlsio_openssl_open_async, + tlsio_openssl_close_async, + tlsio_openssl_send_async, + tlsio_openssl_dowork, + tlsio_openssl_setoption +}; + +/* 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) +{ + return &tlsio_openssl_interface_description; +} diff --git a/pal/tlsio_openssl_compact.h b/pal/tlsio_openssl_compact.h new file mode 100644 index 0000000..9f29ed1 --- /dev/null +++ b/pal/tlsio_openssl_compact.h @@ -0,0 +1,19 @@ +// 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 + +#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); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* TLSIO_OPENSSL_COMPACT_H */ diff --git a/sample/Makefile b/sample/Makefile new file mode 100644 index 0000000..9b28497 --- /dev/null +++ b/sample/Makefile @@ -0,0 +1,8 @@ +# +# This is a project Makefile. It is assumed the directory this Makefile resides in is a +# project subdirectory. +# + +PROJECT_NAME := esp32 + +include $(IDF_PATH)/make/project.mk diff --git a/sample/main/Kconfig.projbuild b/sample/main/Kconfig.projbuild new file mode 100644 index 0000000..02ac330 --- /dev/null +++ b/sample/main/Kconfig.projbuild @@ -0,0 +1,17 @@ +menu "Example Configuration" + +config WIFI_SSID + string "WiFi SSID" + default "myssid" + help + SSID (network name) for the example to connect to. + +config WIFI_PASSWORD + string "WiFi Password" + default "myssid" + help + WiFi password (WPA or WPA2) for the example to use. + + Can be left blank if the network has no security set. + +endmenu diff --git a/sample/main/azure_main.c b/sample/main/azure_main.c new file mode 100644 index 0000000..2afc93d --- /dev/null +++ b/sample/main/azure_main.c @@ -0,0 +1,97 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +#include + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/event_groups.h" + +#include "esp_system.h" +#include "esp_system.h" +#include "esp_wifi.h" +#include "esp_event_loop.h" +#include "esp_log.h" + +#include "nvs_flash.h" +#include "iothub_client_sample_mqtt.h" + + + +#define EXAMPLE_WIFI_SSID CONFIG_WIFI_SSID +#define EXAMPLE_WIFI_PASS CONFIG_WIFI_PASSWORD + +/* FreeRTOS event group to signal when we are connected & ready to make a request */ +static EventGroupHandle_t wifi_event_group; + +/* The event group allows multiple bits for each event, + but we only care about one event - are we connected + to the AP with an IP? */ +const int CONNECTED_BIT = BIT0; + +static const char *TAG = "azure"; + +static esp_err_t event_handler(void *ctx, system_event_t *event) +{ + switch(event->event_id) { + case SYSTEM_EVENT_STA_START: + esp_wifi_connect(); + break; + case SYSTEM_EVENT_STA_GOT_IP: + xEventGroupSetBits(wifi_event_group, CONNECTED_BIT); + break; + case SYSTEM_EVENT_STA_DISCONNECTED: + /* This is a workaround as ESP32 WiFi libs don't currently + auto-reassociate. */ + esp_wifi_connect(); + xEventGroupClearBits(wifi_event_group, CONNECTED_BIT); + break; + default: + break; + } + return ESP_OK; +} + +static void initialise_wifi(void) +{ + tcpip_adapter_init(); + wifi_event_group = xEventGroupCreate(); + ESP_ERROR_CHECK( esp_event_loop_init(event_handler, NULL) ); + wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); + ESP_ERROR_CHECK( esp_wifi_init(&cfg) ); + ESP_ERROR_CHECK( esp_wifi_set_storage(WIFI_STORAGE_RAM) ); + wifi_config_t wifi_config = { + .sta = { + .ssid = EXAMPLE_WIFI_SSID, + .password = EXAMPLE_WIFI_PASS, + }, + }; + ESP_LOGI(TAG, "Setting WiFi configuration SSID %s...", wifi_config.sta.ssid); + ESP_ERROR_CHECK( esp_wifi_set_mode(WIFI_MODE_STA) ); + ESP_ERROR_CHECK( esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config) ); + ESP_ERROR_CHECK( esp_wifi_start() ); +} + +void azure_task(void *pvParameter) +{ + ESP_LOGI(TAG, "Waiting for WiFi access point ..."); + xEventGroupWaitBits(wifi_event_group, CONNECTED_BIT, + false, true, portMAX_DELAY); + ESP_LOGI(TAG, "Connected to access point success"); + + + iothub_client_sample_mqtt_run(); + while(1) + { + vTaskDelay(1000); + } + + +} + +void app_main() +{ + nvs_flash_init(); + initialise_wifi(); + xTaskCreate(&azure_task, "azure_task", 8192, NULL, 5, NULL); +} diff --git a/sample/main/component.mk b/sample/main/component.mk new file mode 100644 index 0000000..0b9d758 --- /dev/null +++ b/sample/main/component.mk @@ -0,0 +1,5 @@ +# +# "main" pseudo-component makefile. +# +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) + diff --git a/sdk b/sdk new file mode 160000 index 0000000..04aa51b --- /dev/null +++ b/sdk @@ -0,0 +1 @@ +Subproject commit 04aa51bf78ce819b6ebfda448f8c3a57c9c42b99