The purpose of this document is to provide guidance on how to port the C Shared utility library to platforms not supported out of the box. The C shared utility library is used by C SDKs like IoTHub client SDK and EventHub client SDK.
The document does not cover the specifics of any particular platform.
- tickcounter FreeRTOS reference
- agenttime adapter specification
- threadapi and sleep adapter specification
- platform adapter specification
- tlsio specification
- xio specification
- lock adapter specification
- Overview
- tickcounter adapter
- agenttime adapter
- sleep adapter
- platform adapter
- tlsio adapter overview
- socketio adapter overview (optional)
- tlsio adapter implementation
- threadapi and lock adapters (optional)
The C shared utility library is written in C99 for the purpose of portability to most platforms. However, several components rely on platform-specific resources in order to achieve the functionality required. Thus, porting the C shared utility library is done by porting the PAL components (adapters) that are part of the library. Below is a rough overview of the components:
There are several mandatory components for which an adapter must be provided:
-
A
tickcounter
implementation: this provides the SDK an adapter for getting a tick counter expressed in ms. The precision does not have to be milliseconds, but rather the value provided to the SDK has to be expressed in milliseconds. -
An
agenttime
implementation: this provides the SDK adapters for the C time management functions liketime
,difftime
, etc. This is needed due to the very wide spread differences in the way time is handled by different platforms. -
A
sleep
implementation to provide a device-independent sleep function. -
A
platform
implementation to perform global init and de-init. -
A
tlsio
implementation to allow the SDK to communicate over TLS. The IoT Hub does not support insecure communication.
Additionally, there are two optional components, threadapi
and lock
, that allow the SDK to communicate with
an IoT Hub on a dedicated thread. Yet another optional component is the socketio
adapter, which is used with
some kinds of tlsio
adapter implementations.
There are several existing adapters that can be found under the SDK's adapters directory and source directory. If any of these adapters work for your device, simply include that file in your project directly.
Tickcounter behavior is exemplified by the tickcounter FreeRTOS reference.
To get started, copy this version of tickcounter.c file and modify it to suit your device.
The agenttime adapter is specified in agenttime adapter specification and provides platform-independent time functions.
For most platforms/OSs you can include the standard agenttime.c
file in your build. This adapter simply calls the C functions time
, difftime
, ctime
, etc.
If this file does not work for your implementation, make a copy of it and modify it appropriately.
The Azure IoT SDK only requires the get_time
and get_difftime
functions. The other functions
in this adapter -- get_gmtime
, get_mktime
, and get_ctime
-- are deprecated and
may be omitted or left non-functional.
The sleep adapter is a single function that provides a device-independent thread sleep. Unlike other adapters, it does not have its own header file. Instead, its declaration is contained in the threadapi.h file along with the declarations for the optional threadapi adapter, and its implementation is typically contained in an associated threadapi.c file.
Unlike the rest of the functions in
threadapi.h,
however, ThreadAPI_Sleep
is required by the SDK and must always be functional.
The specification for the sleep adapter is found in the threadapi and sleep adapter specification.
The platform adapter performs one time initialization and deinitialization for the platform and also supplies the SDK with an appropriate TLSIO.
The platform adapter specification gives full instructions for writing the platform adapter.
To get started creating your platform adapter, copy this Windows platform.c file and modify it to suit your needs.
The threadapi and lock adapters must exist for the SDK to compile, but their functionality is optional. Their specification documents (see below) detail what each empty function should do if threading functionality is not needed.
These components that allow the SDK to communicate with an IoT Hub within a dedicated thread. The use of a dedicated thread has some cost in memory consumption because of the need for a separate stack, which may make the dedicated thread difficult to use on devices with little free memory.
The upside of the dedicated thread is that all current tlsio adapters may repeatedly block for some fraction of a minute when attempting to connect to their IoT Hub when the network is unavailable, and having a thread dedicated to the Azure IoT SDK will allow other device functions to remain responsive while the SDK is blocked.
Future versions of the SDK may eliminate this potential blocking behavior, but for now, devices which must be responsive will need to use a dedicated thread for the SDK, which requires implementing the ThreadApi and Lock adapters.
Here are the specs for the threadapi and lock adapters:
These specs explain how to create null lock and threadapi adapters for when threading is not desired.
To get started creating your threadapi and lock adapters, copy these Windows adapter files and modify them appropriately:
A tlsio adapter provides the SDK with secure TLS communication wtih the Azure IoT Hub.
Tlsio adapters expose their functionality to the SDK via xio
, which
is a generic C-language bits-in-bits-out interface
defined here.
A tlsio adapter instance is created via the adapter's
xio_create
function, and the configuration parameter const void* io_create_parameters
must be of the specialized type
TLSIO_CONFIG
when creating the tlsio instance.
Implementation of tlsio adapters for new devices is done by selecting the existing tlsio adapter (and perhaps also a socketio adapter) that most closely fits your needs and modifying it accordingly.
There are two possible design patterns for a tlsio adapter: direct, and chained. In the direct style, the tlsio adapter owns a TCP socket which it uses to directly perform TLS communication with the remote host. In the chained style, the tlsio adapter does not own a TCP socket and does not communicate directly with the remote host. Instead, it still does all the TLS chores like encryption, decryption, and negotiation, but it communicates with the remote host indirectly via yet another xio-style adapter of some sort.
The direct style adapters require less code and avoid extra memory buffer copying, so they are more suited to microcontrollers. The tlsio adapters for Arduino and ESP32 are both direct types. The chained style adapters are more resource-hungry, but they offer more flexibilty. The tlsio adapters for Windows, Linux, and Mac are all chained style adapters.
The choice of TLS implementation may dictate the style of tlsio. For example, the TLS implementations for Arduino and Espressif's OpenSSL implementation for ESP32 can only work directly with their own internal TCP socket, so they can only be used as part of a direct style tlsio. By contrast, the full official version of OpenSSL can be used either way.
In order to connect to the internet, a chained tlsio must at some point talk through an xio
adapter
that contains
a TCP socket. In the Azure IoT SDK, an xio
adapter managing a TCP socket is called a socketio
adapter.
Multiple xio components can be chained together if desired. Here is a diagram illustrating the differences
between a direct tlsio, a chained
tlsio, and a chained tlsio with an xio-based http proxy component:
The xio_http_proxy
component is only shown to illustrate xio capability. Its details are beyond the
scope of this document.
Adapters other than tlsio are easy to implement, even for inexperienced developers. The tlsio adapters, however, are quite complicated, and writing a tlsio adapter is a task only for experienced developers who are comfortable setting up include directories and external libraries without instruction.
The most up-to-date information on tlsio implementation is contained in the tlsio specification.
All of the existing tlsio adapters use TLS libraries that are not part of the Azure IoT SDK. Refer to the documentation for the specific TSL libraries for instructions on library usage such as setting include directories and linking library files.
There are two existing direct adapter implementations:
Of these two, the tlsio_openssl_compact for ESP32 is probably the better candidate for copying for re-use because it is more likely to resemble newer devices, and it was written in tandem with the tlsio specification.
The tlsio_openssl_compact for ESP32 abstracts its operating system dependencies using these two files:
It is recommended that all direct tlsio implementatons follow this pattern.
The socket_async.c and dns_resolver_ares.c files can be re-used without change for most socket implementations by merely changing the content of the included "socket_async_os.h" file, which contains os-specific headers.
Chained adapter implementations include:
- tlsio_openssl for Windows, Linux, and Mac
- tlsio_schannel for Windows only
- tlsio_wolfssl for embedded devices
- tlsio_mbedtls for embedded devices
There is no spec for socketio adapters, so it is necessary to copy the behavior of an existing one, and socketio_berkeley is the best candidate for copying. However, be aware that all existing socketio adapters including socketio_berkeley purge their pending io lists during socketio_destroy, which is incorrect. The pending io lists should be purged during socketio_close instead.
- socketio_berkeley for Linux, easily adapted to any Berkeley socket
- socketio_win32 for Windows
- tlsio_cyclonessl_socket for use with cyclonessl
Newly supported devices are likely to use a wide variety of possible build systems, so no single standard source tree can be prescribed. The support repository for ESP32 is the one we recommend considering as a model for creating a new device support repository.