diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000000..2e2a6ed456 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Use unix line endings everywhere, even on Windows +* text=auto eol=lf diff --git a/LICENSE b/LICENSE index 1bdb8f3542..51b6a76e54 100644 --- a/LICENSE +++ b/LICENSE @@ -1,21 +1,21 @@ - MIT License - - Copyright (c) Microsoft Corporation. All rights reserved. - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - SOFTWARE. + MIT License + + Copyright (c) Microsoft Corporation. All rights reserved. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. diff --git a/README.md b/README.md index 0cc0f81f8e..d11846ab6a 100644 --- a/README.md +++ b/README.md @@ -1,515 +1,515 @@ -# Azure SDK for Embedded C - -[![Build Status](https://dev.azure.com/azure-sdk/public/_apis/build/status/c/c%20-%20client%20-%20ci?branchName=master)](https://dev.azure.com/azure-sdk/public/_build/latest?definitionId=722&branchName=master) - -The Azure SDK for Embedded C is designed to allow small embedded (IoT) devices to communicate with Azure services. Since we expect our client library code to run on microcontrollers, which have very limited amounts of flash and RAM, and have slower CPUs, our C SDK does things very differently than the SDKs we offer for other languages. - -With this in mind, there are many tenets or principles that we follow in order to properly address this target audience: - -- Customers of our SDK compile our source code along with their own. - -- We target the C99 programming language and test with gcc, clang, & MS Visual C compilers. - -- We offer very few abstractions making our code easy to understand and debug. - -- Our SDK is non allocating. That is, customers must allocate our data structures where they desire (global memory, heap, stack, etc.) and then pass the address of the allocated structure into our functions to initialize them and in order to perform various operations. - -- Unlike our other language SDKs, many things (such as composing an HTTP pipeline of policies) are done in source code as opposed to runtime. This reduces code size, improves execution speed and locks-in behavior, reducing the chance of bugs at runtime. - -- We support microcontrollers with no operating system, microcontrollers with a real-time operating system (like [Azure RTOS](https://azure.microsoft.com/services/rtos/)), Linux, and Windows. Customers can implement custom platform layers to use our SDK on custom devices. We provide some platform layers, and encourage the community to submit platform layers to increase the out-of-the-box supported platforms. - -## Table of Contents - -- [Azure SDK for Embedded C](#azure-sdk-for-embedded-c) - - [Table of Contents](#table-of-contents) - - [Documentation](#documentation) - - [The GitHub Repository](#the-github-repository) - - [Services](#services) - - [Structure](#structure) - - [Master Branch](#master-branch) - - [Release Branches and Release Tagging](#release-branches-and-release-tagging) - - [Getting Started Using the SDK](#getting-started-using-the-sdk) - - [CMake](#cmake) - - [CMake Options](#cmake-options) - - [VSCode](#vscode) - - [Source Files (IDE, command line, etc)](#source-files-ide-command-line-etc) - - [Consume SDK for C as Dependency with CMake](#consume-sdk-for-c-as-dependency-with-cmake) - - [Running Samples](#running-samples) - - [Libcurl Global Init and Global Clean Up](#libcurl-global-init-and-global-clean-up) - - [Development Environment](#development-environment) - - [Windows](#windows) - - [Linux](#linux) - - [Mac](#mac) - - [Using your own HTTP stack implementation](#using-your-own-http-stack-implementation) - - [Link your application with your own HTTP stack](#link-your-application-with-your-own-http-stack) - - [SDK Architecture](#sdk-architecture) - - [Contributing](#contributing) - - [Additional Helpful Links for Contributors](#additional-helpful-links-for-contributors) - - [Community](#community) - - [Reporting Security Issues and Security Bugs](#reporting-security-issues-and-security-bugs) - - [License](#license) - -## Documentation - -We use [doxygen](https://www.doxygen.nl) to generate documentation for source code. You can find the generated, versioned documentation [here](https://azure.github.io/azure-sdk-for-c). - -## The GitHub Repository - -To get help with the SDK: - -- File a [Github Issue](https://github.com/Azure/azure-sdk-for-c/issues/new/choose). -- Ask new questions or see others' questions on [Stack Overflow](https://stackoverflow.com/questions/tagged/azure+c) using the `azure` and `c` tags. - -### Services - -The Azure SDK for Embedded C repo has been structured around the service libraries it provides: - -1. [IoT](https://github.com/Azure/azure-sdk-for-c/blob/master/sdk/docs/iot) - Library to connect Embedded Devices to Azure IoT services - -### Structure - -This repo is structured with two priorities: - -1. Separation of services/features to make it easier to find relevant information and resources. -2. Simplified source file structuring to easily integrate features into a user's project. - -`/sdk` - folder containing docs, sources, samples, tests for all SDK packages
-    `/docs` - documentation for each service (iot, etc)
-    `/inc` - include directory - can be singularly included in your project to resolve all headers
-    `/samples` - samples for each service
-    `/src` - source files for each service
-    `/tests` - tests for each service
- -For instructions on how to consume the libraries via CMake, please see [here](#cmake). For instructions on how consume the source code in an IDE, command line, or other build systems, please see [here](#source-files-ide-command-line-etc). - -### Master Branch - -The master branch has the most recent code with new features and bug fixes. It does **not** represent the latest General Availability (**GA**) release of the SDK. - -### Release Branches and Release Tagging - -When we make an official release, we will create a unique git tag containing the name and version to mark the commit. We'll use this tag for servicing via hotfix branches as well as debugging the code for a particular preview or stable release version. A release tag looks like this: - - `_` - - The latest release can be found in the [release section](https://github.com/Azure/azure-sdk-for-c/releases) of this repo. - - For more information, please see this [branching strategy](https://github.com/Azure/azure-sdk/blob/master/docs/policies/repobranching.md#release-tagging) document. - -## Getting Started Using the SDK - -The SDK can be conveniently consumed either via CMake or other non-CMake methods (IDE workspaces, command line, and others). - -### CMake - -1. Install the required prerequisites: - - [CMake](https://cmake.org/download/) version 3.10 or later - - C compiler: [MSVC](https://visualstudio.microsoft.com/downloads/#build-tools-for-visual-studio-2019), [gcc](https://gcc.gnu.org/) or [clang](https://clang.llvm.org/) are recommended - - [git](https://git-scm.com/downloads) to clone our Azure SDK repository with the desired tag - -2. Clone our Azure SDK repository, optionally using the desired version tag. - - git clone https://github.com/Azure/azure-sdk-for-c - - git checkout - - For information about using a specific client library, see the README file located in the client library's folder which is a subdirectory under the [`/sdk/docs`](https://github.com/Azure/azure-sdk-for-c/blob/master/sdk/docs) folder. - -3. Ensure the SDK builds correctly. - - - Create an output directory for your build artifacts (in this example, we named it `build`, but you can pick any name). - - mkdir build - - - Navigate to that newly created directory. - - cd build - - - Run `cmake` pointing to the sources at the root of the repo to generate the builds files. - - cmake .. - - - Launch the underlying build system to compile the libraries. - - cmake --build . - - This results in building each library as a static library file, placed in the output directory you created (for example `build\sdk\core\az_core\Debug`). At a minimum, you must have an `Azure Core` library, a `Platform` library, and an `HTTP` library. Then, you can build any additional Azure service client library you intend to use from within your application (for example `build\sdk\iot\Debug`). To use our client libraries in your application, just `#include` our public header files and then link your application's object files with our library files. - -4. Provide platform-specific implementations for functionality required by `Azure Core`. For more information, see the [Azure Core Porting Guide](https://github.com/Azure/azure-sdk-for-c/tree/master/sdk/docs/core#porting-the-azure-sdk-to-another-platform). - -### CMake Options - -By default, when building the project with no options, the following static libraries are generated: - -- ``Libraries``: - - az_core - - az_span, az_http, az_json, etc. - - az_iot - - iot_provisioning, iot_hub, etc. - - az_noplatform - - A platform abstraction which will compile but returns `AZ_ERROR_DEPENDENCY_NOT_PROVIDED` from all its functions. This ensures the project can be compiled without the need to provide any specific platform implementation. This is useful if you want to use az_core without platform specific functions like `time` or `sleep`. - - az_nohttp - - Library that provides a no-op HTTP stack, returning `AZ_ERROR_DEPENDENCY_NOT_PROVIDED`. Similar to `az_noplatform`, this library ensures the project can be compiled without requiring any HTTP stack implementation. This is useful if you want to use `az_core` without `az_http` functionality. - -The following CMake options are available for adding/removing project features. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
OptionDescriptionDefault Value
UNIT_TESTINGGenerates Unit Test for compilation. When turning this option ON, cmocka is a required dependency for compilation.
After Compiling, use `ctest` to run Unit Test.
OFF
UNIT_TESTING_MOCKSThis option works only with GCC. It uses -ld option from linker to mock functions during unit test. This is used to test platform or HTTP functions by mocking the return values.OFF
PRECONDITIONSTurning this option OFF would remove all method contracts. This is typically for shipping libraries for production to make it as optimized as possible.ON
TRANSPORT_CURLThis option requires Libcurl dependency to be available. It generates an HTTP stack with libcurl for az_http to be able to send requests thru the wire. This library would replace the no_http.OFF
TRANSPORT_PAHOThis option requires paho-mqtt dependency to be available. Provides Paho MQTT support for IoT.OFF
AZ_PLATFORM_IMPLThis option can be set to any of the next values:
- No_value: default value is used and no_platform library is used.
- "POSIX": Provides implementation for Linux and Mac systems.
- "WIN32": Provides platform implementation for Windows based system
- "USER": Tells cmake to use an specific implementation provided by user. When setting this option, user must provide an implementation library and set option `AZ_USER_PLATFORM_IMPL_NAME` with the name of the library (i.e. -DAZ_PLATFORM_IMPL=USER -DAZ_USER_PLATFORM_IMPL_NAME=user_platform_lib). cmake will look for this library to link az_core
No_value
- -- ``Samples``: Storage Samples are built by default using the default PAL and HTTP adapter (see [running samples](#running-samples)). This means that running samples without building an HTTP transport adapter would throw errors like: - - ./blobs_client_example.exe - Running sample with no_op HTTP implementation. - Recompile az_core with an HTTP client implementation like CURL to see sample sending network requests. - - i.e. cmake -DTRANSPORT_CURL=ON .. - -### Consume SDK for C as Dependency with CMake -Azure SDK for C can be automatically checked out by cmake and become a build dependency. This is done by using [FetchContent](https://cmake.org/cmake/help/v3.11/module/FetchContent.html). - -Using this option would skip manually getting the Azure SDK for C source code to build and installing it (or making it available from some include path). Instead, CMake would do this for us. - -Azure SDK for C provides a CMake module that can be copied and used for this purpose. - -### VSCode - -For convenience, you can quickly get started using [VSCode](https://code.visualstudio.com/) and the [CMake Extension by Microsoft](https://marketplace.visualstudio.com/items?itemName=ms-vscode.cmake-tools&ssr=false#overview). Included in the repo is a `settings.json` file [here](https://github.com/Azure/azure-sdk-for-c/blob/master/.vscode-config/settings.json) which the extension will use to configure a CMake project. To use it, copy the `settings.json` file from `.vscode-config` to your own `.vscode` directory. With this, you can run and debug samples and tests. Modify the variables in the file to your liking or as instructed by sample documentation and then select the following button in the extension: - -![VSCode CMake Config](./sdk/docs/resources/vscode_cmake_config.png) - -From there you can select targets to build and debug. - -**NOTE**: Especially on Windows, make sure you select a compiler platform version that matches the dependencies installed via VCPKG (i.e. `x64` or `x86`). Additionally, the triplet to use should be specified in the `VCPKG_DEFAULT_TRIPLET` field in `settings.json`. - -### Source Files (IDE, command line, etc) - -We have set up the repo for easy integration into other projects which don't use CMake. Two main features make this possible: - -- To resolve all header file relative paths, you only need to include `sdk/inc` in your project. All header files are included in the sdk with relative paths to clearly demarcate the services they belong to. A couple examples being: - -```c -#include -#include -``` - -- All source files are placed in a directory structure similar to the headers: `sdk/src`. Each service has its own subdirectory to separate files which you may be singularly interested in. - -To use a specific service/feature, you may include the header file with the function declaration and compile the according `.c` containing the function implementation with your project. - -The specific dependencies of each service may vary, but a couple rules of thumb should resolve the most typical of issues. - -1. All services depend on `core` ([source files here](https://github.com/Azure/azure-sdk-for-c/tree/master/sdk/src/azure/core)). You may compile these files with your project to resolve core dependencies. -2. Most services will require a platform file to be compiled with your project ([see here for porting instructions](https://github.com/Azure/azure-sdk-for-c/tree/master/sdk/docs/core#porting-the-azure-sdk-to-another-platform)). We have provided several implementations already [here](https://github.com/Azure/azure-sdk-for-c/tree/master/sdk/src/azure/platform) for [`windows`](https://github.com/Azure/azure-sdk-for-c/blob/master/sdk/src/azure/platform/az_win32.c), [`posix`](https://github.com/Azure/azure-sdk-for-c/blob/master/sdk/src/azure/platform/az_posix.c), and a [`no_platform`](https://github.com/Azure/azure-sdk-for-c/blob/master/sdk/src/azure/platform/az_noplatform.c) for no-op stubs. Please compile one of these, for your respective platform, with your project. - -The following compilation, preprocessor options will add or remove functionality in the SDK. - -| Option | Description | -| ------ | ----------- | -| `AZ_NO_PRECONDITION_CHECKING` | Turns off precondition checks to maximize performance with removal of function precondition checking. | -| `AZ_NO_LOGGING` | Removes all logging code and artifacts from the SDK (helps reduce code size). | - -## Running Samples - -See [cmake options](#cmake-options) to learn about how to build an HTTP transport adapter, how to build IoT samples, and to turn logging on. - - -### Storage Sample -The storage sample expects a storage account with a container and SaS token used for authentication to be set in an environment variable `AZURE_STORAGE_URL`. - -Note: Building samples can be disabled by setting `AZ_SDK_C_NO_SAMPLES` environment variable. - -```bash -# On linux, set env var like this. For Windows, do it from advanced settings/ env variables - -export ENV_URL="https://??????????????" -``` - -### Libcurl Global Init and Global Clean Up - -When you select to build the libcurl http stack implementation, you have to make sure to call `curl_global_init` before using SDK client to send HTTP request to Azure. - -You need to also call `curl_global_cleanup` once you no longer need to perform SDk client API calls. - -Note how you can use function `atexit()` to set libcurl global clean up. - -The reason for this is the fact of this functions are not thread-safe, and a customer can use libcurl not only for Azure SDK library but for some other purpose. More info [here](https://curl.haxx.se/libcurl/c/curl_global_init.html). - -**This is libcurl specific only.** - -### IoT samples -Samples for IoT will be built only when CMake option `TRANSPORT_PAHO` is set. -See [compiler options](#compiler-options). -For more information about IoT APIs and samples, see [Azure IoT Clients](https://github.com/Azure/azure-sdk-for-c/tree/master/sdk/docs/iot#azure-iot-clients). - - -### Development Environment - -Project contains files to work on Windows, Mac or Linux based OS. - -**Note** For any environment variables set to use with CMake, the environment variables must be set -BEFORE the first cmake generation command (`cmake ..`). The environment variables will NOT be picked up -if you have already generated the build files, set environment variables, and then regenerate. In that -case, you must either delete the `CMakeCache.txt` file or delete the folder in which you are generating build -files and start again. - -### Windows - -vcpkg is the easiest way to have dependencies installed. It downloads packages sources, headers and build libraries for whatever TRIPLET is set up (platform/arq). -VCPKG maintains any installed package inside its own folder, allowing to have multiple vcpkg folder with different dependencies installed on each. This is also great because you don't have to install dependencies globally on your system. - -Follow next steps to install VCPKG and have it linked to cmake. The vcpkg repository is checked out at the commit in [vcpkg-commit.txt](https://github.com/Azure/azure-sdk-for-c/blob/master/eng/vcpkg-commit.txt). Azure SDK code in this version is known to work at that vcpkg ref. - -```bash -# Clone vcpkg: -git clone https://github.com/Microsoft/vcpkg.git -# (consider this path as PATH_TO_VCPKG) -cd vcpkg -# Checkout the vcpkg commit from the vcpkg-commit.txt file (link above) -# git checkout - -# build vcpkg (remove .bat on Linux/Mac) -.\bootstrap-vcpkg.bat -# install dependencies (remove .exe in Linux/Mac) and update triplet -.\vcpkg.exe install --triplet x64-windows-static curl[winssl] cmocka paho-mqtt -# Add this environment variables to link this VCPKG folder with cmake: -# VCPKG_DEFAULT_TRIPLET=x64-windows-static -# VCPKG_ROOT=PATH_TO_VCPKG (replace PATH_TO_VCPKG for where vcpkg is installed) -``` - -If you previously installed VCPKG and dependencies, you may need to run `.\vcpkg.exe upgrade --no-dry-run` to upgrade to the latest packages. - -> Note: Setting up a development environment in windows without VCPKG is not supported. It requires installing all dev-dependencies globally and manually setting cmake files to link each of them. - -Follow next steps to build project from command prompt: - -```bash -# cd to project folder -cd azure-sdk-for-c -# create a new folder to generate cmake files for building (i.e. build) -mkdir build -cd build -# generate files -# cmake will automatically detect what C compiler is used by system by default and will generate files for it -cmake .. -# compile files. Cmake would call compiler and linker to generate libs -cmake --build . -``` - -> Note: The steps above would compile and generate the default output for azure-sdk-for-c which includes static libraries only. See section [CMake Options](#cmake-options) - -#### Visual Studio 2019 - -Open project folder with Visual Studio. If VCPKG has been previously installed and set up like mentioned [above](#VCPKG). Everything will be ready to build. -Right after opening project, Visual Studio will read cmake files and generate cache files automatically. - -### Linux - -#### VCPKG - -VCPKG can be used to download packages sources, headers and build libraries for whatever TRIPLET is set up (platform/architecture). -VCPKG maintains any installed package inside its own folder, allowing to have multiple vcpkg folder with different dependencies installed on each. This is also great because you don't have to install dependencies globally on your system. - -Follow next steps to install VCPKG and have it linked to cmake. Follow next steps to install VCPKG and have it linked to cmake. The vcpkg repository is checked out at the commit in [vcpkg-commit.txt](https://github.com/Azure/azure-sdk-for-c/blob/master/eng/vcpkg-commit.txt). Azure SDK code in this version is known to work at that vcpkg ref. - -```bash -# Clone vcpkg: -git clone https://github.com/Microsoft/vcpkg.git -# (consider this path as PATH_TO_VCPKG) -cd vcpkg -# Checkout the vcpkg commit from the vcpkg-commit.txt file (link above) -# git checkout - -# build vcpkg -./bootstrap-vcpkg.sh -./vcpkg install --triplet x64-linux curl cmocka paho-mqtt -export VCPKG_DEFAULT_TRIPLET=x64-linux -export VCPKG_ROOT=PATH_TO_VCPKG #replace PATH_TO_VCPKG for where vcpkg is installed -``` - -If you previously installed VCPKG and dependencies, you may need to run `./vcpkg upgrade --no-dry-run` to upgrade to the latest packages. - -#### Debian - -Alternatively, for Ubuntu 18.04 you can use: - -`sudo apt install build-essential cmake libcmocka-dev libcmocka0 gcovr lcov doxygen curl libcurl4-openssl-dev libssl-dev ca-certificates` - -#### Build - -```bash -# cd to project folder -cd azure-sdk-for-c -# create a new folder to generate cmake files for building (i.e. build) -mkdir build -cd build -# generate files -# cmake will automatically detect what C compiler is used by system by default and will generate files for it -cmake .. -# compile files. Cmake would call compiler and linker to generate libs -make -``` - -> Note: The steps above would compile and generate the default output for azure-sdk-for-c which includes static libraries only. See section [CMake Options](#cmake-options) - -### Mac - -#### VCPKG - -VCPKG can be used to download packages sources, headers and build libraries for whatever TRIPLET is set up (platform/architecture). -VCPKG maintains any installed package inside its own folder, allowing to have multiple vcpkg folder with different dependencies installed on each. This is also great because you don't have to install dependencies globally on your system. - -First, ensure that you have the latest `gcc` installed: - - brew update - brew upgrade - brew info gcc - brew install gcc - brew cleanup - -Follow next steps to install VCPKG and have it linked to cmake. Follow next steps to install VCPKG and have it linked to cmake. The vcpkg repository is checked out at the commit in [vcpkg-commit.txt](https://github.com/Azure/azure-sdk-for-c/blob/master/eng/vcpkg-commit.txt). Azure SDK code in this version is known to work at that vcpkg ref. - -```bash -# Clone vcpkg: -git clone https://github.com/Microsoft/vcpkg.git -# (consider this path as PATH_TO_VCPKG) -cd vcpkg -# Checkout the vcpkg commit from the vcpkg-commit.txt file (link above) -# git checkout - -# build vcpkg -./bootstrap-vcpkg.sh -./vcpkg install --triplet x64-osx curl cmocka paho-mqtt -export VCPKG_DEFAULT_TRIPLET=x64-osx -export VCPKG_ROOT=PATH_TO_VCPKG #replace PATH_TO_VCPKG for where vcpkg is installed -``` - -If you previously installed VCPKG and dependencies, you may need to run `./vcpkg upgrade --no-dry-run` to upgrade to the latest packages. - -#### Build - -```bash -# cd to project folder -cd azure-sdk-for-c -# create a new folder to generate cmake files for building (i.e. build) -mkdir build -cd build -# generate files -# cmake will automatically detect what C compiler is used by system by default and will generate files for it -cmake .. -# compile files. Cmake would call compiler and linker to generate libs -make -``` - -> Note: The steps above would compile and generate the default output for azure-sdk-for-c which includes static libraries only. See section [CMake Options](#cmake-options) - -### Using your own HTTP stack implementation - -You can create and use your own HTTP stack and adapter. This is to avoid the libcurl implementation from Azure SDK. - -The first step is to understand the two components that are required. The first one is an **HTTP stack implementation** that is capable of sending bits through the wire. Some examples of these are libcurl, win32, etc. - -The second component is an **HTTP transport adapter**. This is the implementation code which takes an http request from Azure SDK Core and uses it to send it using the specific HTTP stack implementation. Azure SDK Core provides the next contract that this component needs to implement: - -```c -AZ_NODISCARD az_result -az_http_client_send_request(az_http_request const* request, az_http_response* ref_response); -``` - -For example, Azure SDK provides a cmake target `az_curl` (find it [here](https://github.com/Azure/azure-sdk-for-c/tree/master/sdk/src/azure/platform/az_curl.c)) with the implementation code for the contract function mentioned before. It uses an `az_http_request` reference to create an specific `libcurl` request and send it though the wire. Then it uses `libcurl` response to fill the `az_http_response` reference structure. - -### Link your application with your own HTTP stack - -Create your own http adapter for an Http stack and then use the following cmake command to have it linked to your application -```cmake -target_link_libraries(your_application_target PRIVATE lib_adapter http_stack_lib) - -# For instance, this is how we link libcurl and its adapter -target_link_libraries(blobs_client_example PRIVATE az_curl CURL::libcurl) -``` - -See the complete cmake file and how to link your own library [here](https://github.com/Azure/azure-sdk-for-c/blob/master/sdk/src/azure/iot/CMakeLists.txt) - -## SDK Architecture - -At the heart of our SDK is, what we refer to as, [Azure Core](https://github.com/Azure/azure-sdk-for-c/tree/master/sdk/docs/core). This code defines several data types and functions for use by the client libraries that build on top of us such as the [Azure IoT client libraries](https://github.com/Azure/azure-sdk-for-c/tree/master/sdk/docs/iot). Here are some of the features that customers use directly: - -- **Spans**: A span represents a byte buffer and is used for string manipulations, HTTP requests/responses, reading/writing JSON payloads. It allows us to return a substring within a larger string without any memory allocations. See the [Working With Spans](https://github.com/Azure/azure-sdk-for-c/tree/master/sdk/docs/core#working-with-spans) section of the `Azure Core` README for more information. - -- **Logging**: As our SDK performs operations, it can send log messages to a customer-defined callback. Customers can enable this to assist with debugging and diagnosing issues when leveraging our SDK code. See the [Logging SDK Operations](https://github.com/Azure/azure-sdk-for-c/tree/master/sdk/docs/core#logging-sdk-operations) section of the `Azure Core` README for more information. - -- **Contexts**: Contexts offer an I/O cancellation mechanism. Multiple contexts can be composed together in your application's call tree. When a context is canceled, its children are also canceled. See the [Canceling an Operation](https://github.com/Azure/azure-sdk-for-c/tree/master/sdk/docs/core#canceling-an-operation) section of the `Azure Core` README for more information. - -- **JSON**: Non-allocating JSON reading and JSON writing data structures and operations. - -- **HTTP**: Non-allocating HTTP request and HTTP response data structures and operations. - -- **Argument Validation**: The SDK validates function arguments and invokes a callback when validation fails. By default, this callback suspends the calling thread _forever_. However, you can override this behavior and, in fact, you can disable all argument validation to get smaller and faster code. See the [SDK Function Argument Validation](https://github.com/Azure/azure-sdk-for-c/tree/master/sdk/docs/core#sdk-function-argument-validation) section of the `Azure Core` README for more information. - -In addition to the above features, `Azure Core` provides features available to client libraries written to access other Azure services. Customers use these features indirectly by way of interacting with a client library. By providing these features in `Azure Core`, the client libraries built on top of us will share a common implementation and many features will behave identically across client libraries. For example, `Azure Core` offers a standard set of credential types and an HTTP pipeline with logging, retry, and telemetry policies. - -## Contributing - -For details on contributing to this repository, see the [contributing guide](https://github.com/Azure/azure-sdk-for-c/blob/master/CONTRIBUTING.md). - -This project welcomes contributions and suggestions. Most contributions require you to agree to a Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us the rights to use your contribution. For details, visit [https://cla.microsoft.com](https://cla.microsoft.com). - -When you submit a pull request, a CLA-bot will automatically determine whether you need to provide a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the instructions provided by the bot. You will only need to do this once across all repositories using our CLA. - -This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). -For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact -[opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. - -### Additional Helpful Links for Contributors - -Many people all over the world have helped make this project better. You'll want to check out: - -- [What are some good first issues for new contributors to the repo?](https://github.com/azure/azure-sdk-for-c/issues?q=is%3Aopen+is%3Aissue+label%3A%22up+for+grabs%22) -- [How to build and test your change](https://github.com/Azure/azure-sdk-for-c/blob/master/CONTRIBUTING.md#developer-guide) -- [How you can make a change happen!](https://github.com/Azure/azure-sdk-for-c/blob/master/CONTRIBUTING.md#pull-requests) - -### Community - -- Chat with other community members [![Join the chat at https://gitter.im/azure/azure-sdk-for-c](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/azure/azure-sdk-for-c?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) - -### Reporting Security Issues and Security Bugs - -Security issues and bugs should be reported privately, via email, to the Microsoft Security Response Center (MSRC) . You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Further information, including the MSRC PGP key, can be found in the [Security TechCenter](https://www.microsoft.com/msrc/faqs-report-an-issue). - -### License - -Azure SDK for Embedded C is licensed under the [MIT](https://github.com/Azure/azure-sdk-for-c/blob/master/LICENSE) license. +# Azure SDK for Embedded C + +[![Build Status](https://dev.azure.com/azure-sdk/public/_apis/build/status/c/c%20-%20client%20-%20ci?branchName=master)](https://dev.azure.com/azure-sdk/public/_build/latest?definitionId=722&branchName=master) + +The Azure SDK for Embedded C is designed to allow small embedded (IoT) devices to communicate with Azure services. Since we expect our client library code to run on microcontrollers, which have very limited amounts of flash and RAM, and have slower CPUs, our C SDK does things very differently than the SDKs we offer for other languages. + +With this in mind, there are many tenets or principles that we follow in order to properly address this target audience: + +- Customers of our SDK compile our source code along with their own. + +- We target the C99 programming language and test with gcc, clang, & MS Visual C compilers. + +- We offer very few abstractions making our code easy to understand and debug. + +- Our SDK is non allocating. That is, customers must allocate our data structures where they desire (global memory, heap, stack, etc.) and then pass the address of the allocated structure into our functions to initialize them and in order to perform various operations. + +- Unlike our other language SDKs, many things (such as composing an HTTP pipeline of policies) are done in source code as opposed to runtime. This reduces code size, improves execution speed and locks-in behavior, reducing the chance of bugs at runtime. + +- We support microcontrollers with no operating system, microcontrollers with a real-time operating system (like [Azure RTOS](https://azure.microsoft.com/services/rtos/)), Linux, and Windows. Customers can implement custom platform layers to use our SDK on custom devices. We provide some platform layers, and encourage the community to submit platform layers to increase the out-of-the-box supported platforms. + +## Table of Contents + +- [Azure SDK for Embedded C](#azure-sdk-for-embedded-c) + - [Table of Contents](#table-of-contents) + - [Documentation](#documentation) + - [The GitHub Repository](#the-github-repository) + - [Services](#services) + - [Structure](#structure) + - [Master Branch](#master-branch) + - [Release Branches and Release Tagging](#release-branches-and-release-tagging) + - [Getting Started Using the SDK](#getting-started-using-the-sdk) + - [CMake](#cmake) + - [CMake Options](#cmake-options) + - [VSCode](#vscode) + - [Source Files (IDE, command line, etc)](#source-files-ide-command-line-etc) + - [Consume SDK for C as Dependency with CMake](#consume-sdk-for-c-as-dependency-with-cmake) + - [Running Samples](#running-samples) + - [Libcurl Global Init and Global Clean Up](#libcurl-global-init-and-global-clean-up) + - [Development Environment](#development-environment) + - [Windows](#windows) + - [Linux](#linux) + - [Mac](#mac) + - [Using your own HTTP stack implementation](#using-your-own-http-stack-implementation) + - [Link your application with your own HTTP stack](#link-your-application-with-your-own-http-stack) + - [SDK Architecture](#sdk-architecture) + - [Contributing](#contributing) + - [Additional Helpful Links for Contributors](#additional-helpful-links-for-contributors) + - [Community](#community) + - [Reporting Security Issues and Security Bugs](#reporting-security-issues-and-security-bugs) + - [License](#license) + +## Documentation + +We use [doxygen](https://www.doxygen.nl) to generate documentation for source code. You can find the generated, versioned documentation [here](https://azure.github.io/azure-sdk-for-c). + +## The GitHub Repository + +To get help with the SDK: + +- File a [Github Issue](https://github.com/Azure/azure-sdk-for-c/issues/new/choose). +- Ask new questions or see others' questions on [Stack Overflow](https://stackoverflow.com/questions/tagged/azure+c) using the `azure` and `c` tags. + +### Services + +The Azure SDK for Embedded C repo has been structured around the service libraries it provides: + +1. [IoT](https://github.com/Azure/azure-sdk-for-c/blob/master/sdk/docs/iot) - Library to connect Embedded Devices to Azure IoT services + +### Structure + +This repo is structured with two priorities: + +1. Separation of services/features to make it easier to find relevant information and resources. +2. Simplified source file structuring to easily integrate features into a user's project. + +`/sdk` - folder containing docs, sources, samples, tests for all SDK packages
+    `/docs` - documentation for each service (iot, etc)
+    `/inc` - include directory - can be singularly included in your project to resolve all headers
+    `/samples` - samples for each service
+    `/src` - source files for each service
+    `/tests` - tests for each service
+ +For instructions on how to consume the libraries via CMake, please see [here](#cmake). For instructions on how consume the source code in an IDE, command line, or other build systems, please see [here](#source-files-ide-command-line-etc). + +### Master Branch + +The master branch has the most recent code with new features and bug fixes. It does **not** represent the latest General Availability (**GA**) release of the SDK. + +### Release Branches and Release Tagging + +When we make an official release, we will create a unique git tag containing the name and version to mark the commit. We'll use this tag for servicing via hotfix branches as well as debugging the code for a particular preview or stable release version. A release tag looks like this: + + `_` + + The latest release can be found in the [release section](https://github.com/Azure/azure-sdk-for-c/releases) of this repo. + + For more information, please see this [branching strategy](https://github.com/Azure/azure-sdk/blob/master/docs/policies/repobranching.md#release-tagging) document. + +## Getting Started Using the SDK + +The SDK can be conveniently consumed either via CMake or other non-CMake methods (IDE workspaces, command line, and others). + +### CMake + +1. Install the required prerequisites: + - [CMake](https://cmake.org/download/) version 3.10 or later + - C compiler: [MSVC](https://visualstudio.microsoft.com/downloads/#build-tools-for-visual-studio-2019), [gcc](https://gcc.gnu.org/) or [clang](https://clang.llvm.org/) are recommended + - [git](https://git-scm.com/downloads) to clone our Azure SDK repository with the desired tag + +2. Clone our Azure SDK repository, optionally using the desired version tag. + + git clone https://github.com/Azure/azure-sdk-for-c + + git checkout + + For information about using a specific client library, see the README file located in the client library's folder which is a subdirectory under the [`/sdk/docs`](https://github.com/Azure/azure-sdk-for-c/blob/master/sdk/docs) folder. + +3. Ensure the SDK builds correctly. + + - Create an output directory for your build artifacts (in this example, we named it `build`, but you can pick any name). + + mkdir build + + - Navigate to that newly created directory. + + cd build + + - Run `cmake` pointing to the sources at the root of the repo to generate the builds files. + + cmake .. + + - Launch the underlying build system to compile the libraries. + + cmake --build . + + This results in building each library as a static library file, placed in the output directory you created (for example `build\sdk\core\az_core\Debug`). At a minimum, you must have an `Azure Core` library, a `Platform` library, and an `HTTP` library. Then, you can build any additional Azure service client library you intend to use from within your application (for example `build\sdk\iot\Debug`). To use our client libraries in your application, just `#include` our public header files and then link your application's object files with our library files. + +4. Provide platform-specific implementations for functionality required by `Azure Core`. For more information, see the [Azure Core Porting Guide](https://github.com/Azure/azure-sdk-for-c/tree/master/sdk/docs/core#porting-the-azure-sdk-to-another-platform). + +### CMake Options + +By default, when building the project with no options, the following static libraries are generated: + +- ``Libraries``: + - az_core + - az_span, az_http, az_json, etc. + - az_iot + - iot_provisioning, iot_hub, etc. + - az_noplatform + - A platform abstraction which will compile but returns `AZ_ERROR_DEPENDENCY_NOT_PROVIDED` from all its functions. This ensures the project can be compiled without the need to provide any specific platform implementation. This is useful if you want to use az_core without platform specific functions like `time` or `sleep`. + - az_nohttp + - Library that provides a no-op HTTP stack, returning `AZ_ERROR_DEPENDENCY_NOT_PROVIDED`. Similar to `az_noplatform`, this library ensures the project can be compiled without requiring any HTTP stack implementation. This is useful if you want to use `az_core` without `az_http` functionality. + +The following CMake options are available for adding/removing project features. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
OptionDescriptionDefault Value
UNIT_TESTINGGenerates Unit Test for compilation. When turning this option ON, cmocka is a required dependency for compilation.
After Compiling, use `ctest` to run Unit Test.
OFF
UNIT_TESTING_MOCKSThis option works only with GCC. It uses -ld option from linker to mock functions during unit test. This is used to test platform or HTTP functions by mocking the return values.OFF
PRECONDITIONSTurning this option OFF would remove all method contracts. This is typically for shipping libraries for production to make it as optimized as possible.ON
TRANSPORT_CURLThis option requires Libcurl dependency to be available. It generates an HTTP stack with libcurl for az_http to be able to send requests thru the wire. This library would replace the no_http.OFF
TRANSPORT_PAHOThis option requires paho-mqtt dependency to be available. Provides Paho MQTT support for IoT.OFF
AZ_PLATFORM_IMPLThis option can be set to any of the next values:
- No_value: default value is used and no_platform library is used.
- "POSIX": Provides implementation for Linux and Mac systems.
- "WIN32": Provides platform implementation for Windows based system
- "USER": Tells cmake to use an specific implementation provided by user. When setting this option, user must provide an implementation library and set option `AZ_USER_PLATFORM_IMPL_NAME` with the name of the library (i.e. -DAZ_PLATFORM_IMPL=USER -DAZ_USER_PLATFORM_IMPL_NAME=user_platform_lib). cmake will look for this library to link az_core
No_value
+ +- ``Samples``: Storage Samples are built by default using the default PAL and HTTP adapter (see [running samples](#running-samples)). This means that running samples without building an HTTP transport adapter would throw errors like: + + ./blobs_client_example.exe + Running sample with no_op HTTP implementation. + Recompile az_core with an HTTP client implementation like CURL to see sample sending network requests. + + i.e. cmake -DTRANSPORT_CURL=ON .. + +### Consume SDK for C as Dependency with CMake +Azure SDK for C can be automatically checked out by cmake and become a build dependency. This is done by using [FetchContent](https://cmake.org/cmake/help/v3.11/module/FetchContent.html). + +Using this option would skip manually getting the Azure SDK for C source code to build and installing it (or making it available from some include path). Instead, CMake would do this for us. + +Azure SDK for C provides a CMake module that can be copied and used for this purpose. + +### VSCode + +For convenience, you can quickly get started using [VSCode](https://code.visualstudio.com/) and the [CMake Extension by Microsoft](https://marketplace.visualstudio.com/items?itemName=ms-vscode.cmake-tools&ssr=false#overview). Included in the repo is a `settings.json` file [here](https://github.com/Azure/azure-sdk-for-c/blob/master/.vscode-config/settings.json) which the extension will use to configure a CMake project. To use it, copy the `settings.json` file from `.vscode-config` to your own `.vscode` directory. With this, you can run and debug samples and tests. Modify the variables in the file to your liking or as instructed by sample documentation and then select the following button in the extension: + +![VSCode CMake Config](./sdk/docs/resources/vscode_cmake_config.png) + +From there you can select targets to build and debug. + +**NOTE**: Especially on Windows, make sure you select a compiler platform version that matches the dependencies installed via VCPKG (i.e. `x64` or `x86`). Additionally, the triplet to use should be specified in the `VCPKG_DEFAULT_TRIPLET` field in `settings.json`. + +### Source Files (IDE, command line, etc) + +We have set up the repo for easy integration into other projects which don't use CMake. Two main features make this possible: + +- To resolve all header file relative paths, you only need to include `sdk/inc` in your project. All header files are included in the sdk with relative paths to clearly demarcate the services they belong to. A couple examples being: + +```c +#include +#include +``` + +- All source files are placed in a directory structure similar to the headers: `sdk/src`. Each service has its own subdirectory to separate files which you may be singularly interested in. + +To use a specific service/feature, you may include the header file with the function declaration and compile the according `.c` containing the function implementation with your project. + +The specific dependencies of each service may vary, but a couple rules of thumb should resolve the most typical of issues. + +1. All services depend on `core` ([source files here](https://github.com/Azure/azure-sdk-for-c/tree/master/sdk/src/azure/core)). You may compile these files with your project to resolve core dependencies. +2. Most services will require a platform file to be compiled with your project ([see here for porting instructions](https://github.com/Azure/azure-sdk-for-c/tree/master/sdk/docs/core#porting-the-azure-sdk-to-another-platform)). We have provided several implementations already [here](https://github.com/Azure/azure-sdk-for-c/tree/master/sdk/src/azure/platform) for [`windows`](https://github.com/Azure/azure-sdk-for-c/blob/master/sdk/src/azure/platform/az_win32.c), [`posix`](https://github.com/Azure/azure-sdk-for-c/blob/master/sdk/src/azure/platform/az_posix.c), and a [`no_platform`](https://github.com/Azure/azure-sdk-for-c/blob/master/sdk/src/azure/platform/az_noplatform.c) for no-op stubs. Please compile one of these, for your respective platform, with your project. + +The following compilation, preprocessor options will add or remove functionality in the SDK. + +| Option | Description | +| ------ | ----------- | +| `AZ_NO_PRECONDITION_CHECKING` | Turns off precondition checks to maximize performance with removal of function precondition checking. | +| `AZ_NO_LOGGING` | Removes all logging code and artifacts from the SDK (helps reduce code size). | + +## Running Samples + +See [cmake options](#cmake-options) to learn about how to build an HTTP transport adapter, how to build IoT samples, and to turn logging on. + + +### Storage Sample +The storage sample expects a storage account with a container and SaS token used for authentication to be set in an environment variable `AZURE_STORAGE_URL`. + +Note: Building samples can be disabled by setting `AZ_SDK_C_NO_SAMPLES` environment variable. + +```bash +# On linux, set env var like this. For Windows, do it from advanced settings/ env variables + +export ENV_URL="https://??????????????" +``` + +### Libcurl Global Init and Global Clean Up + +When you select to build the libcurl http stack implementation, you have to make sure to call `curl_global_init` before using SDK client to send HTTP request to Azure. + +You need to also call `curl_global_cleanup` once you no longer need to perform SDk client API calls. + +Note how you can use function `atexit()` to set libcurl global clean up. + +The reason for this is the fact of this functions are not thread-safe, and a customer can use libcurl not only for Azure SDK library but for some other purpose. More info [here](https://curl.haxx.se/libcurl/c/curl_global_init.html). + +**This is libcurl specific only.** + +### IoT samples +Samples for IoT will be built only when CMake option `TRANSPORT_PAHO` is set. +See [compiler options](#compiler-options). +For more information about IoT APIs and samples, see [Azure IoT Clients](https://github.com/Azure/azure-sdk-for-c/tree/master/sdk/docs/iot#azure-iot-clients). + + +### Development Environment + +Project contains files to work on Windows, Mac or Linux based OS. + +**Note** For any environment variables set to use with CMake, the environment variables must be set +BEFORE the first cmake generation command (`cmake ..`). The environment variables will NOT be picked up +if you have already generated the build files, set environment variables, and then regenerate. In that +case, you must either delete the `CMakeCache.txt` file or delete the folder in which you are generating build +files and start again. + +### Windows + +vcpkg is the easiest way to have dependencies installed. It downloads packages sources, headers and build libraries for whatever TRIPLET is set up (platform/arq). +VCPKG maintains any installed package inside its own folder, allowing to have multiple vcpkg folder with different dependencies installed on each. This is also great because you don't have to install dependencies globally on your system. + +Follow next steps to install VCPKG and have it linked to cmake. The vcpkg repository is checked out at the commit in [vcpkg-commit.txt](https://github.com/Azure/azure-sdk-for-c/blob/master/eng/vcpkg-commit.txt). Azure SDK code in this version is known to work at that vcpkg ref. + +```bash +# Clone vcpkg: +git clone https://github.com/Microsoft/vcpkg.git +# (consider this path as PATH_TO_VCPKG) +cd vcpkg +# Checkout the vcpkg commit from the vcpkg-commit.txt file (link above) +# git checkout + +# build vcpkg (remove .bat on Linux/Mac) +.\bootstrap-vcpkg.bat +# install dependencies (remove .exe in Linux/Mac) and update triplet +.\vcpkg.exe install --triplet x64-windows-static curl[winssl] cmocka paho-mqtt +# Add this environment variables to link this VCPKG folder with cmake: +# VCPKG_DEFAULT_TRIPLET=x64-windows-static +# VCPKG_ROOT=PATH_TO_VCPKG (replace PATH_TO_VCPKG for where vcpkg is installed) +``` + +If you previously installed VCPKG and dependencies, you may need to run `.\vcpkg.exe upgrade --no-dry-run` to upgrade to the latest packages. + +> Note: Setting up a development environment in windows without VCPKG is not supported. It requires installing all dev-dependencies globally and manually setting cmake files to link each of them. + +Follow next steps to build project from command prompt: + +```bash +# cd to project folder +cd azure-sdk-for-c +# create a new folder to generate cmake files for building (i.e. build) +mkdir build +cd build +# generate files +# cmake will automatically detect what C compiler is used by system by default and will generate files for it +cmake .. +# compile files. Cmake would call compiler and linker to generate libs +cmake --build . +``` + +> Note: The steps above would compile and generate the default output for azure-sdk-for-c which includes static libraries only. See section [CMake Options](#cmake-options) + +#### Visual Studio 2019 + +Open project folder with Visual Studio. If VCPKG has been previously installed and set up like mentioned [above](#VCPKG). Everything will be ready to build. +Right after opening project, Visual Studio will read cmake files and generate cache files automatically. + +### Linux + +#### VCPKG + +VCPKG can be used to download packages sources, headers and build libraries for whatever TRIPLET is set up (platform/architecture). +VCPKG maintains any installed package inside its own folder, allowing to have multiple vcpkg folder with different dependencies installed on each. This is also great because you don't have to install dependencies globally on your system. + +Follow next steps to install VCPKG and have it linked to cmake. Follow next steps to install VCPKG and have it linked to cmake. The vcpkg repository is checked out at the commit in [vcpkg-commit.txt](https://github.com/Azure/azure-sdk-for-c/blob/master/eng/vcpkg-commit.txt). Azure SDK code in this version is known to work at that vcpkg ref. + +```bash +# Clone vcpkg: +git clone https://github.com/Microsoft/vcpkg.git +# (consider this path as PATH_TO_VCPKG) +cd vcpkg +# Checkout the vcpkg commit from the vcpkg-commit.txt file (link above) +# git checkout + +# build vcpkg +./bootstrap-vcpkg.sh +./vcpkg install --triplet x64-linux curl cmocka paho-mqtt +export VCPKG_DEFAULT_TRIPLET=x64-linux +export VCPKG_ROOT=PATH_TO_VCPKG #replace PATH_TO_VCPKG for where vcpkg is installed +``` + +If you previously installed VCPKG and dependencies, you may need to run `./vcpkg upgrade --no-dry-run` to upgrade to the latest packages. + +#### Debian + +Alternatively, for Ubuntu 18.04 you can use: + +`sudo apt install build-essential cmake libcmocka-dev libcmocka0 gcovr lcov doxygen curl libcurl4-openssl-dev libssl-dev ca-certificates` + +#### Build + +```bash +# cd to project folder +cd azure-sdk-for-c +# create a new folder to generate cmake files for building (i.e. build) +mkdir build +cd build +# generate files +# cmake will automatically detect what C compiler is used by system by default and will generate files for it +cmake .. +# compile files. Cmake would call compiler and linker to generate libs +make +``` + +> Note: The steps above would compile and generate the default output for azure-sdk-for-c which includes static libraries only. See section [CMake Options](#cmake-options) + +### Mac + +#### VCPKG + +VCPKG can be used to download packages sources, headers and build libraries for whatever TRIPLET is set up (platform/architecture). +VCPKG maintains any installed package inside its own folder, allowing to have multiple vcpkg folder with different dependencies installed on each. This is also great because you don't have to install dependencies globally on your system. + +First, ensure that you have the latest `gcc` installed: + + brew update + brew upgrade + brew info gcc + brew install gcc + brew cleanup + +Follow next steps to install VCPKG and have it linked to cmake. Follow next steps to install VCPKG and have it linked to cmake. The vcpkg repository is checked out at the commit in [vcpkg-commit.txt](https://github.com/Azure/azure-sdk-for-c/blob/master/eng/vcpkg-commit.txt). Azure SDK code in this version is known to work at that vcpkg ref. + +```bash +# Clone vcpkg: +git clone https://github.com/Microsoft/vcpkg.git +# (consider this path as PATH_TO_VCPKG) +cd vcpkg +# Checkout the vcpkg commit from the vcpkg-commit.txt file (link above) +# git checkout + +# build vcpkg +./bootstrap-vcpkg.sh +./vcpkg install --triplet x64-osx curl cmocka paho-mqtt +export VCPKG_DEFAULT_TRIPLET=x64-osx +export VCPKG_ROOT=PATH_TO_VCPKG #replace PATH_TO_VCPKG for where vcpkg is installed +``` + +If you previously installed VCPKG and dependencies, you may need to run `./vcpkg upgrade --no-dry-run` to upgrade to the latest packages. + +#### Build + +```bash +# cd to project folder +cd azure-sdk-for-c +# create a new folder to generate cmake files for building (i.e. build) +mkdir build +cd build +# generate files +# cmake will automatically detect what C compiler is used by system by default and will generate files for it +cmake .. +# compile files. Cmake would call compiler and linker to generate libs +make +``` + +> Note: The steps above would compile and generate the default output for azure-sdk-for-c which includes static libraries only. See section [CMake Options](#cmake-options) + +### Using your own HTTP stack implementation + +You can create and use your own HTTP stack and adapter. This is to avoid the libcurl implementation from Azure SDK. + +The first step is to understand the two components that are required. The first one is an **HTTP stack implementation** that is capable of sending bits through the wire. Some examples of these are libcurl, win32, etc. + +The second component is an **HTTP transport adapter**. This is the implementation code which takes an http request from Azure SDK Core and uses it to send it using the specific HTTP stack implementation. Azure SDK Core provides the next contract that this component needs to implement: + +```c +AZ_NODISCARD az_result +az_http_client_send_request(az_http_request const* request, az_http_response* ref_response); +``` + +For example, Azure SDK provides a cmake target `az_curl` (find it [here](https://github.com/Azure/azure-sdk-for-c/tree/master/sdk/src/azure/platform/az_curl.c)) with the implementation code for the contract function mentioned before. It uses an `az_http_request` reference to create an specific `libcurl` request and send it though the wire. Then it uses `libcurl` response to fill the `az_http_response` reference structure. + +### Link your application with your own HTTP stack + +Create your own http adapter for an Http stack and then use the following cmake command to have it linked to your application +```cmake +target_link_libraries(your_application_target PRIVATE lib_adapter http_stack_lib) + +# For instance, this is how we link libcurl and its adapter +target_link_libraries(blobs_client_example PRIVATE az_curl CURL::libcurl) +``` + +See the complete cmake file and how to link your own library [here](https://github.com/Azure/azure-sdk-for-c/blob/master/sdk/src/azure/iot/CMakeLists.txt) + +## SDK Architecture + +At the heart of our SDK is, what we refer to as, [Azure Core](https://github.com/Azure/azure-sdk-for-c/tree/master/sdk/docs/core). This code defines several data types and functions for use by the client libraries that build on top of us such as the [Azure IoT client libraries](https://github.com/Azure/azure-sdk-for-c/tree/master/sdk/docs/iot). Here are some of the features that customers use directly: + +- **Spans**: A span represents a byte buffer and is used for string manipulations, HTTP requests/responses, reading/writing JSON payloads. It allows us to return a substring within a larger string without any memory allocations. See the [Working With Spans](https://github.com/Azure/azure-sdk-for-c/tree/master/sdk/docs/core#working-with-spans) section of the `Azure Core` README for more information. + +- **Logging**: As our SDK performs operations, it can send log messages to a customer-defined callback. Customers can enable this to assist with debugging and diagnosing issues when leveraging our SDK code. See the [Logging SDK Operations](https://github.com/Azure/azure-sdk-for-c/tree/master/sdk/docs/core#logging-sdk-operations) section of the `Azure Core` README for more information. + +- **Contexts**: Contexts offer an I/O cancellation mechanism. Multiple contexts can be composed together in your application's call tree. When a context is canceled, its children are also canceled. See the [Canceling an Operation](https://github.com/Azure/azure-sdk-for-c/tree/master/sdk/docs/core#canceling-an-operation) section of the `Azure Core` README for more information. + +- **JSON**: Non-allocating JSON reading and JSON writing data structures and operations. + +- **HTTP**: Non-allocating HTTP request and HTTP response data structures and operations. + +- **Argument Validation**: The SDK validates function arguments and invokes a callback when validation fails. By default, this callback suspends the calling thread _forever_. However, you can override this behavior and, in fact, you can disable all argument validation to get smaller and faster code. See the [SDK Function Argument Validation](https://github.com/Azure/azure-sdk-for-c/tree/master/sdk/docs/core#sdk-function-argument-validation) section of the `Azure Core` README for more information. + +In addition to the above features, `Azure Core` provides features available to client libraries written to access other Azure services. Customers use these features indirectly by way of interacting with a client library. By providing these features in `Azure Core`, the client libraries built on top of us will share a common implementation and many features will behave identically across client libraries. For example, `Azure Core` offers a standard set of credential types and an HTTP pipeline with logging, retry, and telemetry policies. + +## Contributing + +For details on contributing to this repository, see the [contributing guide](https://github.com/Azure/azure-sdk-for-c/blob/master/CONTRIBUTING.md). + +This project welcomes contributions and suggestions. Most contributions require you to agree to a Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us the rights to use your contribution. For details, visit [https://cla.microsoft.com](https://cla.microsoft.com). + +When you submit a pull request, a CLA-bot will automatically determine whether you need to provide a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the instructions provided by the bot. You will only need to do this once across all repositories using our CLA. + +This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). +For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact +[opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. + +### Additional Helpful Links for Contributors + +Many people all over the world have helped make this project better. You'll want to check out: + +- [What are some good first issues for new contributors to the repo?](https://github.com/azure/azure-sdk-for-c/issues?q=is%3Aopen+is%3Aissue+label%3A%22up+for+grabs%22) +- [How to build and test your change](https://github.com/Azure/azure-sdk-for-c/blob/master/CONTRIBUTING.md#developer-guide) +- [How you can make a change happen!](https://github.com/Azure/azure-sdk-for-c/blob/master/CONTRIBUTING.md#pull-requests) + +### Community + +- Chat with other community members [![Join the chat at https://gitter.im/azure/azure-sdk-for-c](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/azure/azure-sdk-for-c?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) + +### Reporting Security Issues and Security Bugs + +Security issues and bugs should be reported privately, via email, to the Microsoft Security Response Center (MSRC) . You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Further information, including the MSRC PGP key, can be found in the [Security TechCenter](https://www.microsoft.com/msrc/faqs-report-an-issue). + +### License + +Azure SDK for Embedded C is licensed under the [MIT](https://github.com/Azure/azure-sdk-for-c/blob/master/LICENSE) license. diff --git a/sdk/docs/iot/README.md b/sdk/docs/iot/README.md index e4553634dd..5017f94d31 100644 --- a/sdk/docs/iot/README.md +++ b/sdk/docs/iot/README.md @@ -1,338 +1,338 @@ -# Azure IoT Clients - -This is the main page for the Azure SDK for Embedded C official IoT client libraries. - -Here you will find everything you need to get devices connected to Azure. - -## Table of Contents - -- [Azure IoT Clients](#azure-iot-clients) - - [Table of Contents](#table-of-contents) - - [Getting Started](#getting-started) - - [Docs](#docs) - - [Build](#build) - - [Samples](#samples) - - [Prerequisites](#prerequisites) - - [Key Features](#key-features) - - [Examples](#examples) - - [IoT Hub Client Initialization](#iot-hub-client-initialization) - - [Properties](#properties) - - [Telemetry](#telemetry) - - [IoT Hub Client with MQTT Stack](#iot-hub-client-with-mqtt-stack) - - [Need Help?](#need-help) - - [Contributing](#contributing) - - [License](#license) - -## Getting Started - -The Azure IoT Client library is created to facilitate connectivity to Azure IoT services alongside an MQTT and TLS stack of the user's choice. This means that this SDK is **NOT** a platform but instead is a true SDK library. - -![Methods](./resources/embc_high_level_arch.png) - -From a functional perspective, this means that the user's application code (not the SDK) calls directly to the MQTT stack of their choice. The SDK provides utilities (in the form of functions, default values, etc) which help make the connection and feature set easier. Some examples of those utilities include: - -- Publish topics to which messages can be sent and subscription topics to which users can subscribe for incoming messages. -- Functions to parse incoming message topics which populate structs with crucial message information. -- Default values for MQTT connect keep alive and connection port. - -A full list of features can be found in the doxygen docs listed below in [Docs](#docs). - -**Note**: this therefore requires a different programming model as compared to the earlier version of the C SDK ([found here](https://github.com/Azure/azure-iot-sdk-c)). To better understand the responsibilities of the user application code and the SDK, please take a look at the [State Machine diagram](https://github.com/Azure/azure-sdk-for-c/blob/master/sdk/docs/iot/mqtt_state_machine.md) that explains the high-level architecture, SDK components, and a clear view of SDK x Application responsibilities. - -### Docs - -For API documentation, please see the doxygen generated docs [here][azure_sdk_for_c_doxygen_docs]. You can find the IoT specific docs by navigating to the **Files -> File List** section near the top and choosing any of the header files prefixed with `az_iot_`. - -### Build - -The Azure IoT library is compiled following the same steps listed on the root [README](https://github.com/Azure/azure-sdk-for-c/blob/master/README.md) documentation, under ["Getting Started Using the SDK"](https://github.com/Azure/azure-sdk-for-c/blob/master/README.md#getting-started-using-the-sdk). - -The library targets made available via CMake are the following: - -- `az::iot::hub` - For Azure IoT Hub features ([API documentation here][azure_sdk_for_c_doxygen_hub_docs]) -- `az::iot::provisioning` - For Azure IoT Provisioning features ([API documentation here][azure_sdk_for_c_doxygen_provisioning_docs]) - -### Samples - -[This page](https://github.com/Azure/azure-sdk-for-c/blob/master/sdk/samples/iot/README.md) explains samples for the Azure Embedded C SDK IoT Hub Client and the Provisioning Clients and how to use them. - - For step-by-step guides starting from scratch, you may refer to these documents: - -- Linux: [How to Setup and Run Azure SDK for Embedded C IoT Hub Samples on Linux](https://github.com/Azure/azure-sdk-for-c/blob/master/sdk/samples/iot/docs/how_to_iot_hub_samples_linux.md) - -- Windows: [How to Setup and Run Azure SDK for Embedded C IoT Hub Samples on Microsoft Windows](https://github.com/Azure/azure-sdk-for-c/blob/master/sdk/samples/iot/docs/how_to_iot_hub_samples_windows.md). - -- ESP8266: [How to Setup and Run Azure SDK for Embedded C IoT Hub Client on Esp8266 NodeMCU](https://github.com/Azure/azure-sdk-for-c/blob/master/sdk/samples/iot/docs/how_to_iot_hub_esp8266_nodemcu.md) - - **Important Note on Linux and Windows Samples**: While Windows and Linux devices are not likely to be considered as constrained ones, these samples were created to make it simpler to test the Azure SDK for Embedded C libraries, debug and step through the code to learn about it, even without a real device. We understand not everyone will have a real device to test and - sometimes - these devices won't have debugging capabilities. - -For extra guidance, please feel free to watch our Deep Dive Video below which goes over building the SDK, running the samples, and the architecture of the samples. - -[![Watch the video](./resources/deep_dive_screenshot.png)](https://youtu.be/qdb3QIq8msg) - -### Prerequisites - -For compiling the Azure SDK for Embedded C for the most common platforms (Windows and Linux), no further prerequisites are necessary. -Please follow the instructions in the [Getting Started](#Getting-Started) section above. -For compiling for specific target devices, please refer to their specific toolchain documentation. - -## Key Features - -√ feature available √* feature partially available (see Description for details) × feature planned but not supported - -Feature | Azure SDK for Embedded C | Description ----------|----------|--------------------- - [Send device-to-cloud message](https://docs.microsoft.com/azure/iot-hub/iot-hub-devguide-messages-d2c) | √ | Send device-to-cloud messages to IoT Hub with the option to add custom message properties. - [Receive cloud-to-device messages](https://docs.microsoft.com/azure/iot-hub/iot-hub-devguide-messages-c2d) | √ | Receive cloud-to-device messages and associated properties from IoT Hub. - [Device Twins](https://docs.microsoft.com/azure/iot-hub/iot-hub-devguide-device-twins) | √ | IoT Hub persists a device twin for each device that you connect to IoT Hub. The device can perform operations like get twin document, subscribe to desired property updates. - [Direct Methods](https://docs.microsoft.com/azure/iot-hub/iot-hub-devguide-direct-methods) | √ | IoT Hub gives you the ability to invoke direct methods on devices from the cloud. - [DPS - Device Provisioning Service](https://docs.microsoft.com/azure/iot-dps/) | √ | This SDK supports connecting your device to the Device Provisioning Service via, for example, [individual enrollment](https://docs.microsoft.com/azure/iot-dps/concepts-service#enrollment) using an [X.509 leaf certificate](https://docs.microsoft.com/azure/iot-dps/concepts-security#leaf-certificate). - Protocol | MQTT | The Azure SDK for Embedded C supports only MQTT. - Retry Policies | √* | The Azure SDK for Embedded C provides guidelines for retries, but actual retries should be handled by the application. - [IoT Plug and Play](https://docs.microsoft.com/azure/iot-pnp/overview-iot-plug-and-play) | √ | IoT Plug and Play Preview enables solution developers to integrate devices with their solutions without writing any embedded code. - -## Examples - -### IoT Hub Client Initialization - -To use IoT Hub connectivity, the first action by a developer should be to initialize the -client with the `az_iot_hub_client_init()` API. Once that is initialized, you may use the -`az_iot_hub_client_get_user_name()` and `az_iot_hub_client_get_client_id()` to get the -user name and client id to establish a connection with IoT Hub. - -An example use case is below. - -```C -//FOR SIMPLICITY THIS DOES NOT HAVE ERROR CHECKING. IN PRODUCTION ENSURE PROPER ERROR CHECKING. - -az_iot_hub_client my_client; -static az_span my_iothub_hostname = AZ_SPAN_LITERAL_FROM_STR("contoso.azure-devices.net"); -static az_span my_device_id = AZ_SPAN_LITERAL_FROM_STR("contoso_device"); - -//Make sure to size the buffer to fit the user name (100 is an example) -static char my_mqtt_user_name[100]; -static size_t my_mqtt_user_name_length; - -//Make sure to size the buffer to fit the client id (16 is an example) -static char my_mqtt_client_id_buffer[16]; -static size_t my_mqtt_client_id_length; - -int main() -{ - //Get the default IoT Hub options - az_iot_hub_client_options options = az_iot_hub_client_options_default(); - - //Initialize the client with hostname, device id, and options - az_iot_hub_client_init(&my_client, my_iothub_hostname, my_device_id, &options); - - //Get the MQTT user name to connect - az_iot_hub_client_get_user_name(&my_client, my_mqtt_user_name, - sizeof(my_mqtt_user_name), &my_mqtt_user_name_length); - - //Get the MQTT client id to connect - sizeof(my_mqtt_client_id), &my_mqtt_client_id_length); - - //At this point you are free to use my_mqtt_client_id and my_mqtt_user_name to connect using - //your MQTT client. -} -``` - -### Properties - -Included in the Azure SDK for Embedded C are helper functions to form and manage properties for IoT Hub services. Implementation starts by using the `az_iot_message_properties_init()` API. The user is free to initialize using an empty, but appropriately sized, span to later append properties or an already populated span containing a properly formatted property buffer. "Properly formatted" properties follow the form `{key}={value}&{key}={value}`. - -Below is an example use case of appending properties. - -```C -//FOR SIMPLICITY THIS DOES NOT HAVE ERROR CHECKING. IN PRODUCTION ENSURE PROPER ERROR CHECKING. -void my_property_func() -{ - //Allocate a span to put the properties - uint8_t property_buffer[64]; - az_span property_span = az_span_create(property_buffer, sizeof(property_buffer)); - - //Initialize the property struct with the span - az_iot_message_properties props; - az_iot_message_properties_init(&props, property_span, 0); - //Append properties - az_iot_message_properties_append(&props, AZ_SPAN_FROM_STR("key"), AZ_SPAN_FROM_STR("value")); - //At this point, you are able to pass the `props` to other APIs with property parameters. -} -``` - -Below is an example use case of initializing an already populated property span. - -```C -//FOR SIMPLICITY THIS DOES NOT HAVE ERROR CHECKING. IN PRODUCTION ENSURE PROPER ERROR CHECKING. -static az_span my_prop_span = AZ_SPAN_LITERAL_FROM_STR("my_device=contoso&my_key=my_value"); -void my_property_func() -{ - //Initialize the property struct with the span - az_iot_message_properties props; - az_iot_message_properties_init(&props, my_prop_span, az_span_size(my_prop_span)); - //At this point, you are able to pass the `props` to other APIs with property parameters. -} -``` - -### Telemetry - -Telemetry functionality can be achieved by sending a user payload to a specific topic. In order to get the appropriate topic to which to send, use the `az_iot_hub_client_telemetry_get_publish_topic()` API. An example use case is below. - -```C -//FOR SIMPLICITY THIS DOES NOT HAVE ERROR CHECKING. IN PRODUCTION ENSURE PROPER ERROR CHECKING. - -static az_iot_hub_client my_client; -static az_span my_iothub_hostname = AZ_SPAN_LITERAL_FROM_STR("contoso.azure-devices.net"); -static az_span my_device_id = AZ_SPAN_LITERAL_FROM_STR("contoso_device"); - -void my_telemetry_func() -{ - //Initialize the client to then pass to the telemetry API - az_iot_hub_client_init(&my_client, my_iothub_hostname, my_device_id, NULL); - - //Allocate a char buffer with capacity large enough to put the telemetry topic. - char telemetry_topic[64]; - size_t telemetry_topic_length; - - //Get the NULL terminated topic and put in telemetry_topic to send the telemetry - az_iot_hub_client_telemetry_get_publish_topic(&my_client, NULL, telemetry_topic, - sizeof(telemetry_topic), &telemetry_topic_length); -} -``` - -### IoT Hub Client with MQTT Stack - -Below is an implementation for using the IoT Hub Client SDK. This is meant to guide users in incorporating their MQTT stack with the IoT Hub Client SDK. Note for simplicity reasons, this code will not compile. Ideally, guiding principles can be inferred from reading through this snippet to create an IoT solution. - -```C -#include -#include -#include - -az_iot_hub_client my_client; -static az_span my_iothub_hostname = AZ_SPAN_LITERAL_FROM_STR(""); -static az_span my_device_id = AZ_SPAN_LITERAL_FROM_STR(""); - -//Make sure the buffer is large enough to fit the user name (100 is an example) -static char my_mqtt_user_name[100]; - -//Make sure the buffer is large enough to fit the client id (16 is an example) -static char my_mqtt_client_id[16]; - -//This assumes an X509 Cert. SAS keys may also be used. -static const char my_device_cert[]= "-----BEGIN CERTIFICATE-----abcdefg-----END CERTIFICATE-----"; - -static char telemetry_topic[128]; -static char telemetry_payload[] = "Hello World"; - -void handle_iot_message(mqtt_client_message* msg); - -int main() -{ - //Get the default IoT Hub options - az_iot_hub_client_options options = az_iot_hub_client_options_default(); - - //Initialize the client with hostname, device id, and options - az_iot_hub_client_init(&my_client, my_iothub_hostname, my_device_id, &options); - - //Get the MQTT user name to connect - az_iot_hub_client_get_user_name(&my_client, my_mqtt_user_name, - sizeof(my_mqtt_user_name), NULL); - - //Get the MQTT client id to connect - az_iot_hub_client_get_client_id(&my_client, my_mqtt_client_id, - sizeof(my_mqtt_client_id), NULL); - - //Initialize MQTT client with necessary parameters (example params shown) - mqtt_client my_mqtt_client; - mqtt_client_init(&my_mqtt_client, my_iothub_hostname, my_mqtt_client_id); - - //Subscribe to c2d messages - mqtt_client_subscribe(&my_mqtt_client, AZ_IOT_HUB_CLIENT_C2D_SUBSCRIBE_TOPIC); - - //Subscribe to device methods - mqtt_client_subscribe(&my_mqtt_client, AZ_IOT_HUB_CLIENT_METHODS_SUBSCRIBE_TOPIC); - - //Subscribe to twin patch topic - mqtt_client_subscribe(&my_mqtt_client, AZ_IOT_HUB_CLIENT_TWIN_PATCH_SUBSCRIBE_TOPIC); - - //Subscribe to twin response topic - mqtt_client_subscribe(&my_mqtt_client, AZ_IOT_HUB_CLIENT_TWIN_RESPONSE_SUBSCRIBE_TOPIC); - - //Connect to the IoT Hub with your chosen mqtt stack - mqtt_client_connect(&my_mqtt_client, my_mqtt_user_name, my_device_cert); - - //This example would run to receive any incoming message and send a telemetry message five times - int iterations = 0; - mqtt_client_message msg; - while(iterations++ < 5) - { - if(mqtt_client_receive(&msg)) - { - handle_iot_message(&msg); - } - - send_telemetry_message(); - } - - //Disconnect from the IoT Hub - mqtt_client_disconnect(&my_mqtt_client); - - //Destroy the mqtt client - mqtt_client_destroy(&my_mqtt_client); -} - -void send_telemetry_message() -{ - //Get the topic to send a telemetry message - az_iot_hub_client_telemetry_get_publish_topic(&client, NULL, telemetry_topic, sizeof(telemetry_topic), NULL); - - //Send the telemetry message with the MQTT client - mqtt_client_publish(telemetry_topic, telemetry_payload, AZ_HUB_CLIENT_DEFAULT_MQTT_TELEMETRY_QOS); -} - -void handle_iot_message(mqtt_client_message* msg) -{ - //Initialize the incoming topic to a span - az_span incoming_topic = az_span_create(msg->topic, msg->topic_len); - - //The message could be for three features so parse the topic to see which it is for - az_iot_hub_client_method_request method_request; - az_iot_hub_client_c2d_request c2d_request; - az_iot_hub_client_twin_response twin_response; - if (az_result_succeeded(az_iot_hub_client_methods_parse_received_topic(&client, incoming_topic, &method_request))) - { - //Handle the method request - } - else if (az_result_succeeded(az_iot_hub_client_c2d_parse_received_topic(&client, incoming_topic, &c2d_request))) - { - //Handle the c2d message - } - else if (az_result_succeeded(az_iot_hub_client_twin_parse_received_topic(&client, incoming_topic, &twin_response))) - { - //Handle the twin message - } -} - -``` - -## Need Help? - -- File an issue via [Github Issues](https://github.com/Azure/azure-sdk-for-c/issues/new/choose). -- Check [previous questions](https://stackoverflow.com/questions/tagged/azure+c) or ask new ones on StackOverflow using - the `azure` and `c` tags. - -## Contributing - -If you'd like to contribute to this library, please read the [contributing guide][azure_sdk_for_c_contributing] to learn more about how to build and test the code. - -### License - -Azure SDK for Embedded C is licensed under the [MIT][azure_sdk_for_c_license] license. - - -[azure_sdk_for_c_contributing]: https://github.com/Azure/azure-sdk-for-c/blob/master/CONTRIBUTING.md -[azure_sdk_for_c_doxygen_docs]: https://azure.github.io/azure-sdk-for-c -[azure_sdk_for_c_doxygen_hub_docs]: https://azuresdkdocs.blob.core.windows.net/$web/c/docs/1.0.0-preview.2/az__iot__hub__client_8h.html -[azure_sdk_for_c_doxygen_provisioning_docs]: https://azuresdkdocs.blob.core.windows.net/$web/c/docs/1.0.0-preview.2/az__iot__provisioning__client_8h.html -[azure_sdk_for_c_license]: https://github.com/Azure/azure-sdk-for-c/blob/master/LICENSE +# Azure IoT Clients + +This is the main page for the Azure SDK for Embedded C official IoT client libraries. + +Here you will find everything you need to get devices connected to Azure. + +## Table of Contents + +- [Azure IoT Clients](#azure-iot-clients) + - [Table of Contents](#table-of-contents) + - [Getting Started](#getting-started) + - [Docs](#docs) + - [Build](#build) + - [Samples](#samples) + - [Prerequisites](#prerequisites) + - [Key Features](#key-features) + - [Examples](#examples) + - [IoT Hub Client Initialization](#iot-hub-client-initialization) + - [Properties](#properties) + - [Telemetry](#telemetry) + - [IoT Hub Client with MQTT Stack](#iot-hub-client-with-mqtt-stack) + - [Need Help?](#need-help) + - [Contributing](#contributing) + - [License](#license) + +## Getting Started + +The Azure IoT Client library is created to facilitate connectivity to Azure IoT services alongside an MQTT and TLS stack of the user's choice. This means that this SDK is **NOT** a platform but instead is a true SDK library. + +![Methods](./resources/embc_high_level_arch.png) + +From a functional perspective, this means that the user's application code (not the SDK) calls directly to the MQTT stack of their choice. The SDK provides utilities (in the form of functions, default values, etc) which help make the connection and feature set easier. Some examples of those utilities include: + +- Publish topics to which messages can be sent and subscription topics to which users can subscribe for incoming messages. +- Functions to parse incoming message topics which populate structs with crucial message information. +- Default values for MQTT connect keep alive and connection port. + +A full list of features can be found in the doxygen docs listed below in [Docs](#docs). + +**Note**: this therefore requires a different programming model as compared to the earlier version of the C SDK ([found here](https://github.com/Azure/azure-iot-sdk-c)). To better understand the responsibilities of the user application code and the SDK, please take a look at the [State Machine diagram](https://github.com/Azure/azure-sdk-for-c/blob/master/sdk/docs/iot/mqtt_state_machine.md) that explains the high-level architecture, SDK components, and a clear view of SDK x Application responsibilities. + +### Docs + +For API documentation, please see the doxygen generated docs [here][azure_sdk_for_c_doxygen_docs]. You can find the IoT specific docs by navigating to the **Files -> File List** section near the top and choosing any of the header files prefixed with `az_iot_`. + +### Build + +The Azure IoT library is compiled following the same steps listed on the root [README](https://github.com/Azure/azure-sdk-for-c/blob/master/README.md) documentation, under ["Getting Started Using the SDK"](https://github.com/Azure/azure-sdk-for-c/blob/master/README.md#getting-started-using-the-sdk). + +The library targets made available via CMake are the following: + +- `az::iot::hub` - For Azure IoT Hub features ([API documentation here][azure_sdk_for_c_doxygen_hub_docs]) +- `az::iot::provisioning` - For Azure IoT Provisioning features ([API documentation here][azure_sdk_for_c_doxygen_provisioning_docs]) + +### Samples + +[This page](https://github.com/Azure/azure-sdk-for-c/blob/master/sdk/samples/iot/README.md) explains samples for the Azure Embedded C SDK IoT Hub Client and the Provisioning Clients and how to use them. + + For step-by-step guides starting from scratch, you may refer to these documents: + +- Linux: [How to Setup and Run Azure SDK for Embedded C IoT Hub Samples on Linux](https://github.com/Azure/azure-sdk-for-c/blob/master/sdk/samples/iot/docs/how_to_iot_hub_samples_linux.md) + +- Windows: [How to Setup and Run Azure SDK for Embedded C IoT Hub Samples on Microsoft Windows](https://github.com/Azure/azure-sdk-for-c/blob/master/sdk/samples/iot/docs/how_to_iot_hub_samples_windows.md). + +- ESP8266: [How to Setup and Run Azure SDK for Embedded C IoT Hub Client on Esp8266 NodeMCU](https://github.com/Azure/azure-sdk-for-c/blob/master/sdk/samples/iot/docs/how_to_iot_hub_esp8266_nodemcu.md) + + **Important Note on Linux and Windows Samples**: While Windows and Linux devices are not likely to be considered as constrained ones, these samples were created to make it simpler to test the Azure SDK for Embedded C libraries, debug and step through the code to learn about it, even without a real device. We understand not everyone will have a real device to test and - sometimes - these devices won't have debugging capabilities. + +For extra guidance, please feel free to watch our Deep Dive Video below which goes over building the SDK, running the samples, and the architecture of the samples. + +[![Watch the video](./resources/deep_dive_screenshot.png)](https://youtu.be/qdb3QIq8msg) + +### Prerequisites + +For compiling the Azure SDK for Embedded C for the most common platforms (Windows and Linux), no further prerequisites are necessary. +Please follow the instructions in the [Getting Started](#Getting-Started) section above. +For compiling for specific target devices, please refer to their specific toolchain documentation. + +## Key Features + +√ feature available √* feature partially available (see Description for details) × feature planned but not supported + +Feature | Azure SDK for Embedded C | Description +---------|----------|--------------------- + [Send device-to-cloud message](https://docs.microsoft.com/azure/iot-hub/iot-hub-devguide-messages-d2c) | √ | Send device-to-cloud messages to IoT Hub with the option to add custom message properties. + [Receive cloud-to-device messages](https://docs.microsoft.com/azure/iot-hub/iot-hub-devguide-messages-c2d) | √ | Receive cloud-to-device messages and associated properties from IoT Hub. + [Device Twins](https://docs.microsoft.com/azure/iot-hub/iot-hub-devguide-device-twins) | √ | IoT Hub persists a device twin for each device that you connect to IoT Hub. The device can perform operations like get twin document, subscribe to desired property updates. + [Direct Methods](https://docs.microsoft.com/azure/iot-hub/iot-hub-devguide-direct-methods) | √ | IoT Hub gives you the ability to invoke direct methods on devices from the cloud. + [DPS - Device Provisioning Service](https://docs.microsoft.com/azure/iot-dps/) | √ | This SDK supports connecting your device to the Device Provisioning Service via, for example, [individual enrollment](https://docs.microsoft.com/azure/iot-dps/concepts-service#enrollment) using an [X.509 leaf certificate](https://docs.microsoft.com/azure/iot-dps/concepts-security#leaf-certificate). + Protocol | MQTT | The Azure SDK for Embedded C supports only MQTT. + Retry Policies | √* | The Azure SDK for Embedded C provides guidelines for retries, but actual retries should be handled by the application. + [IoT Plug and Play](https://docs.microsoft.com/azure/iot-pnp/overview-iot-plug-and-play) | √ | IoT Plug and Play Preview enables solution developers to integrate devices with their solutions without writing any embedded code. + +## Examples + +### IoT Hub Client Initialization + +To use IoT Hub connectivity, the first action by a developer should be to initialize the +client with the `az_iot_hub_client_init()` API. Once that is initialized, you may use the +`az_iot_hub_client_get_user_name()` and `az_iot_hub_client_get_client_id()` to get the +user name and client id to establish a connection with IoT Hub. + +An example use case is below. + +```C +//FOR SIMPLICITY THIS DOES NOT HAVE ERROR CHECKING. IN PRODUCTION ENSURE PROPER ERROR CHECKING. + +az_iot_hub_client my_client; +static az_span my_iothub_hostname = AZ_SPAN_LITERAL_FROM_STR("contoso.azure-devices.net"); +static az_span my_device_id = AZ_SPAN_LITERAL_FROM_STR("contoso_device"); + +//Make sure to size the buffer to fit the user name (100 is an example) +static char my_mqtt_user_name[100]; +static size_t my_mqtt_user_name_length; + +//Make sure to size the buffer to fit the client id (16 is an example) +static char my_mqtt_client_id_buffer[16]; +static size_t my_mqtt_client_id_length; + +int main() +{ + //Get the default IoT Hub options + az_iot_hub_client_options options = az_iot_hub_client_options_default(); + + //Initialize the client with hostname, device id, and options + az_iot_hub_client_init(&my_client, my_iothub_hostname, my_device_id, &options); + + //Get the MQTT user name to connect + az_iot_hub_client_get_user_name(&my_client, my_mqtt_user_name, + sizeof(my_mqtt_user_name), &my_mqtt_user_name_length); + + //Get the MQTT client id to connect + sizeof(my_mqtt_client_id), &my_mqtt_client_id_length); + + //At this point you are free to use my_mqtt_client_id and my_mqtt_user_name to connect using + //your MQTT client. +} +``` + +### Properties + +Included in the Azure SDK for Embedded C are helper functions to form and manage properties for IoT Hub services. Implementation starts by using the `az_iot_message_properties_init()` API. The user is free to initialize using an empty, but appropriately sized, span to later append properties or an already populated span containing a properly formatted property buffer. "Properly formatted" properties follow the form `{key}={value}&{key}={value}`. + +Below is an example use case of appending properties. + +```C +//FOR SIMPLICITY THIS DOES NOT HAVE ERROR CHECKING. IN PRODUCTION ENSURE PROPER ERROR CHECKING. +void my_property_func() +{ + //Allocate a span to put the properties + uint8_t property_buffer[64]; + az_span property_span = az_span_create(property_buffer, sizeof(property_buffer)); + + //Initialize the property struct with the span + az_iot_message_properties props; + az_iot_message_properties_init(&props, property_span, 0); + //Append properties + az_iot_message_properties_append(&props, AZ_SPAN_FROM_STR("key"), AZ_SPAN_FROM_STR("value")); + //At this point, you are able to pass the `props` to other APIs with property parameters. +} +``` + +Below is an example use case of initializing an already populated property span. + +```C +//FOR SIMPLICITY THIS DOES NOT HAVE ERROR CHECKING. IN PRODUCTION ENSURE PROPER ERROR CHECKING. +static az_span my_prop_span = AZ_SPAN_LITERAL_FROM_STR("my_device=contoso&my_key=my_value"); +void my_property_func() +{ + //Initialize the property struct with the span + az_iot_message_properties props; + az_iot_message_properties_init(&props, my_prop_span, az_span_size(my_prop_span)); + //At this point, you are able to pass the `props` to other APIs with property parameters. +} +``` + +### Telemetry + +Telemetry functionality can be achieved by sending a user payload to a specific topic. In order to get the appropriate topic to which to send, use the `az_iot_hub_client_telemetry_get_publish_topic()` API. An example use case is below. + +```C +//FOR SIMPLICITY THIS DOES NOT HAVE ERROR CHECKING. IN PRODUCTION ENSURE PROPER ERROR CHECKING. + +static az_iot_hub_client my_client; +static az_span my_iothub_hostname = AZ_SPAN_LITERAL_FROM_STR("contoso.azure-devices.net"); +static az_span my_device_id = AZ_SPAN_LITERAL_FROM_STR("contoso_device"); + +void my_telemetry_func() +{ + //Initialize the client to then pass to the telemetry API + az_iot_hub_client_init(&my_client, my_iothub_hostname, my_device_id, NULL); + + //Allocate a char buffer with capacity large enough to put the telemetry topic. + char telemetry_topic[64]; + size_t telemetry_topic_length; + + //Get the NULL terminated topic and put in telemetry_topic to send the telemetry + az_iot_hub_client_telemetry_get_publish_topic(&my_client, NULL, telemetry_topic, + sizeof(telemetry_topic), &telemetry_topic_length); +} +``` + +### IoT Hub Client with MQTT Stack + +Below is an implementation for using the IoT Hub Client SDK. This is meant to guide users in incorporating their MQTT stack with the IoT Hub Client SDK. Note for simplicity reasons, this code will not compile. Ideally, guiding principles can be inferred from reading through this snippet to create an IoT solution. + +```C +#include +#include +#include + +az_iot_hub_client my_client; +static az_span my_iothub_hostname = AZ_SPAN_LITERAL_FROM_STR(""); +static az_span my_device_id = AZ_SPAN_LITERAL_FROM_STR(""); + +//Make sure the buffer is large enough to fit the user name (100 is an example) +static char my_mqtt_user_name[100]; + +//Make sure the buffer is large enough to fit the client id (16 is an example) +static char my_mqtt_client_id[16]; + +//This assumes an X509 Cert. SAS keys may also be used. +static const char my_device_cert[]= "-----BEGIN CERTIFICATE-----abcdefg-----END CERTIFICATE-----"; + +static char telemetry_topic[128]; +static char telemetry_payload[] = "Hello World"; + +void handle_iot_message(mqtt_client_message* msg); + +int main() +{ + //Get the default IoT Hub options + az_iot_hub_client_options options = az_iot_hub_client_options_default(); + + //Initialize the client with hostname, device id, and options + az_iot_hub_client_init(&my_client, my_iothub_hostname, my_device_id, &options); + + //Get the MQTT user name to connect + az_iot_hub_client_get_user_name(&my_client, my_mqtt_user_name, + sizeof(my_mqtt_user_name), NULL); + + //Get the MQTT client id to connect + az_iot_hub_client_get_client_id(&my_client, my_mqtt_client_id, + sizeof(my_mqtt_client_id), NULL); + + //Initialize MQTT client with necessary parameters (example params shown) + mqtt_client my_mqtt_client; + mqtt_client_init(&my_mqtt_client, my_iothub_hostname, my_mqtt_client_id); + + //Subscribe to c2d messages + mqtt_client_subscribe(&my_mqtt_client, AZ_IOT_HUB_CLIENT_C2D_SUBSCRIBE_TOPIC); + + //Subscribe to device methods + mqtt_client_subscribe(&my_mqtt_client, AZ_IOT_HUB_CLIENT_METHODS_SUBSCRIBE_TOPIC); + + //Subscribe to twin patch topic + mqtt_client_subscribe(&my_mqtt_client, AZ_IOT_HUB_CLIENT_TWIN_PATCH_SUBSCRIBE_TOPIC); + + //Subscribe to twin response topic + mqtt_client_subscribe(&my_mqtt_client, AZ_IOT_HUB_CLIENT_TWIN_RESPONSE_SUBSCRIBE_TOPIC); + + //Connect to the IoT Hub with your chosen mqtt stack + mqtt_client_connect(&my_mqtt_client, my_mqtt_user_name, my_device_cert); + + //This example would run to receive any incoming message and send a telemetry message five times + int iterations = 0; + mqtt_client_message msg; + while(iterations++ < 5) + { + if(mqtt_client_receive(&msg)) + { + handle_iot_message(&msg); + } + + send_telemetry_message(); + } + + //Disconnect from the IoT Hub + mqtt_client_disconnect(&my_mqtt_client); + + //Destroy the mqtt client + mqtt_client_destroy(&my_mqtt_client); +} + +void send_telemetry_message() +{ + //Get the topic to send a telemetry message + az_iot_hub_client_telemetry_get_publish_topic(&client, NULL, telemetry_topic, sizeof(telemetry_topic), NULL); + + //Send the telemetry message with the MQTT client + mqtt_client_publish(telemetry_topic, telemetry_payload, AZ_HUB_CLIENT_DEFAULT_MQTT_TELEMETRY_QOS); +} + +void handle_iot_message(mqtt_client_message* msg) +{ + //Initialize the incoming topic to a span + az_span incoming_topic = az_span_create(msg->topic, msg->topic_len); + + //The message could be for three features so parse the topic to see which it is for + az_iot_hub_client_method_request method_request; + az_iot_hub_client_c2d_request c2d_request; + az_iot_hub_client_twin_response twin_response; + if (az_result_succeeded(az_iot_hub_client_methods_parse_received_topic(&client, incoming_topic, &method_request))) + { + //Handle the method request + } + else if (az_result_succeeded(az_iot_hub_client_c2d_parse_received_topic(&client, incoming_topic, &c2d_request))) + { + //Handle the c2d message + } + else if (az_result_succeeded(az_iot_hub_client_twin_parse_received_topic(&client, incoming_topic, &twin_response))) + { + //Handle the twin message + } +} + +``` + +## Need Help? + +- File an issue via [Github Issues](https://github.com/Azure/azure-sdk-for-c/issues/new/choose). +- Check [previous questions](https://stackoverflow.com/questions/tagged/azure+c) or ask new ones on StackOverflow using + the `azure` and `c` tags. + +## Contributing + +If you'd like to contribute to this library, please read the [contributing guide][azure_sdk_for_c_contributing] to learn more about how to build and test the code. + +### License + +Azure SDK for Embedded C is licensed under the [MIT][azure_sdk_for_c_license] license. + + +[azure_sdk_for_c_contributing]: https://github.com/Azure/azure-sdk-for-c/blob/master/CONTRIBUTING.md +[azure_sdk_for_c_doxygen_docs]: https://azure.github.io/azure-sdk-for-c +[azure_sdk_for_c_doxygen_hub_docs]: https://azuresdkdocs.blob.core.windows.net/$web/c/docs/1.0.0-preview.2/az__iot__hub__client_8h.html +[azure_sdk_for_c_doxygen_provisioning_docs]: https://azuresdkdocs.blob.core.windows.net/$web/c/docs/1.0.0-preview.2/az__iot__provisioning__client_8h.html +[azure_sdk_for_c_license]: https://github.com/Azure/azure-sdk-for-c/blob/master/LICENSE diff --git a/sdk/docs/iot/mqtt_state_machine.md b/sdk/docs/iot/mqtt_state_machine.md index ea531f4117..f7a86cd78a 100644 --- a/sdk/docs/iot/mqtt_state_machine.md +++ b/sdk/docs/iot/mqtt_state_machine.md @@ -1,247 +1,247 @@ -# Azure IoT Client MQTT State Machine - -## High-level Architecture - -Device Provisioning and IoT Hub service protocols require additional state management on top of the MQTT protocol. The Azure IoT Hub and Provisioning clients for C provide a common programming model. The clients must be layered on top of an MQTT client selected by the application developer. - -The following aspects are being handled by the IoT Clients: - -1. Generate MQTT CONNECT credentials. -1. Obtain SUBSCRIBE topic filters and PUBLISH topic strings required by various service features. -1. Parse service errors and output an uniform error object model. -1. Provide the correct sequence of events required to perform an operation. -1. Provide suggested timing information when retrying operations. - -The following aspects need to be handled by the application or convenience layers: - -1. Ensure secure TLS communication using either server or mutual X509 authentication. -1. Perform MQTT transport-level operations. -1. Delay execution for retry purposes. -1. (Optional) Provide real-time clock information and perform HMAC-SHA256 operations for SAS token generation. - -For more information about Azure IoT services using MQTT see [this article](https://docs.microsoft.com/azure/iot-hub/iot-hub-mqtt-support). - -## Components - -### IoT Hub - -![iot_hub_flow](https://www.plantuml.com/plantuml/svg/0/jLTjRzis4FxENt7VcWppDXHhWHb5K5anPgz9x4KADgme25gwsaGaKYMff5wn_pvIBxRZYBAzTlbW1EBTynmVtXsFRxLXofHvHeY-vw9WYkLWlnc4BmJituWbzqibIv66CfFgpPjWFh-u0FjxDGs3U3gxwJQBujkxCBQML-m1HOgA_6CfAk34MT14fbmi6vPw8RfyHuFvTE_BPH07T3RwRHp6iCNTCTg9XOQpop4qGJh65p0Lt93ttts0mU02sD-KIqDNvO8c6KTXABVBGuZYu3OctQlGwRT4GqkDYWgM8pcmxeTeUL9oOBs2A8gC9ynmAMZ-oTXLAGGcDlu_N_tq2uILBGIbMGsXO5e_IfK2ru1vOTQD-3wCZMXMibdXbba6KH21aVSeO8a2LUKrF2hIPoQQwSfCywLWfbG8mmgUsMZ9CUPx-r_bB7dvwZNf-DOVG2iCEMnIfunFiASQiwrO7KgtaUs876E6EHgr9bw6GTQv4zDwri6MYuph5JMou8aDQeCB2HEmGB1PcCBZZn9qkT29uWjNTaO24sked7uWft5qgjAOa9xTTBcrCI7f2C86i9AffWfr8ON6huzbG-Vf1kQH2vhAfCpRj7v2Hqxs-5y2B9X9LiT7vp4_4DeB26MUIm7R8_9qa6tCRNhtRQk3Ks62_C5rXElVunWoljYIfiH7sqIwhIcFNOlUNbrVmEsDPTd2tbcGsaHVMcjR4oicqWpLiBfQ_3T6yZAraYgRZWrokgczs-O3pEz6LzDe0ZbgpPPO8Pw0r4tSbeE7N4W35X4KPbQ7rCFP2zARSrxI1l80iH_5iJgvQpHdju5o9_NoycJvVCUcy5E9shLk9rHYSm1Qruh3qbtNcnd-9_dyfxP3NN_eO3_ekryxzMgTjZlpaDTcKoeLczUgD_dylRXLCBO7U7eEU1El9RJALQdnwpW6gM0UnE5agryzwiaO9kK2QlUXRvuRmW3QxokfZMU2szYc-C0JT3EQvN2tVEnkUKsE6t86zL10yNF1wmcAOY5jGpQyatP_27488wRnzSzda-iW74IJm4uIPnkAxo5QCOLfa2IssRp3zVdvaQZtkAwdLtBgZ37eEAn2oGALuQWtdVXGFkzCyuTi7MVpR_Um_DpSjxN3cNmasI48fqBPeyPkH-gLucdhEYJ-wJFAtdvFdIFstNtxVLhElJlUhtn7q4XjrWKUwT5pWi_DczCwnlG1bRPrxDf1iEsYm8L1zP7PCYGzZmRBXCeYqC_spKQDAhMzwAYIqlHxlM_2gTmITzWVdIr-w9iYdUEPR9vpvn3yHQUpc4xkHZIycevlOcflLnhTZQBmTZeQqtWsduPV0tSzZYUpo-XRGiP_J6_exJYQpswYyJS7hgwHPwYThygcPS9PpjRkNVN8pShUH_QHDOckpmbvYo8jy-nV "iot_hub_flow") - -### Device Provisioning Service - -![iot_provisioning_flow](https://www.plantuml.com/plantuml/svg/0/hLTjRzis4FxENy5ljOUv68Qrm8mYgEmOZTOazYnb65OK12tICc69L4dAZLVil-yeoR8RlqYscmz68DuzTtpk-F3utbYgRPuc29cba1dLDCNmpNBy3M7u6z1e6MkLIpPKYTBltdcJoSqDXjzxV-Y5_lZni5aGJf-68LZUqnMNZ6lq7uGeW2DdGIB5X8ohAEL2SFkHU1F_nPTRB_J8UF37Q1ZYBUwEqKukP3Y-7U4gIHVn5VQbiEZJJvznhtU3wRz4A4iohLR222KX0n8bWajB1DZrSH1wqn0rc3L1nToIt71D94qvQclO1dMJ1CDz_FURNtx7DBUBYbQQPOc8g0KladwnOAoav96jwMYDNIHo1CrHEixSPvhWm0kmLKikSSmbruLr_umu9_Thg2diZOWbjRdci6SNiDZoMNuljhD8QVJhcxVemBUMAMNKcAzACtI6xKbMOMirvdurDybj2Wab2FAPYRIOSNGKyc0yK_iTQPhQe8xsweJDmHYQHroiNBaU_Wn3d8WPhZsOItxAOs4iI48Jyd5sTGLHKfpF4c4MnAfCHEWYhyCxgKn-96H5pZ7wAuHjgO_ORJZUwF4t3BmMT4-U1hXQPOL8jj0-a7t3DvYiJ9zGoM1XdIZA-llGUAvLYcSQJh_6dIXHESDAun6BURS8UqMuriNNt6HA6lv30ZgU-bWgYtSXOCC9L03wjY76NbBjI5TPfPl4LJetgDrUNher1TlQHohD4cuRETlts66fNiJrOiVaKSIDSLqmfo81uUrU66EtsndGl_ukef2kG37GR7Q6W7sVpH4gSjXRRU3hIyxrkkBsWCOqxEkqzscjAOQMoMu7bg3zngcaZtiuJjIC9WGcham9M5WeOvkcaDfPC68Y8BgLSTdlLVb4eUlkTWxTE63GcVTwhnmZ27_EH0MPI_5Ch83KqJKdkyHkk1TQeLJ_rv9yZXJipArd-RcubIwSxRSzN89TutrfVCqj708wTmk6mrJ2BWBPwJ9OoPFJ1-4GoklNFGp35RGRr7QHpreTYcD0ZAz4n2-Wr3bphSkNpqMX-RDrBob6heGfl5EGJnfV0G15YPRE-XegtFc6_aepTCuexHfZG0r_-rldxi4Rze8x3yRZJYqeF-DWGJLdNd4XVUJhRlTS-2LVbwytTBdCxnr2a4XlA_jmaG4lSmeveXFl0X00hbT0u3Eyzu3r7DXaH2jbJtjzgf8it2AkhPWxM_xvnDJj_Bn_Y3yEoMGw_iiFu4djFtuWmtj_z1YIwUY37uIZAGcU1i5mwWz691nF_24wT65_9m07treLpxYJWWEC_5-5NS3bJYM-2nww2Bs7Ri_Jv7y0 "iot_provisioning_flow") - -## Porting the IoT Clients - -In order to port the clients to a target platform the following items are required: - -- Support for a C99 compiler. -- Types such as `uint8_t` must be defined. -- The target platform supports a stack of several kB (actual requirement depends on features being used and data sizes). -- An MQTT over TLS client supporting QoS 0 and 1 messages. - -Optionally, the IoT services support MQTT tunneling over WebSocket Secure which allows bypassing firewalls where port 8883 is not open. Using WebSockets also allows usage of devices that must go through a WebProxy. Application developers are responsible with setting up the wss:// tunnel. - -## API - -### Connecting - -The application code is required to initialize the TLS and MQTT stacks. -Two authentication schemes are currently supported: _X509 Client Certificate Authentication_ and _Shared Access Signature_ authentication. - -When X509 client authentication is used, the MQTT password field should be an empty string. - -If SAS tokens are used the following APIs provide a way to create as well as refresh the lifetime of the used token upon reconnect. - -_Example:_ - -```C -if(az_result_failed(az_iot_hub_client_sas_get_signature(client, unix_time + 3600, signature, &signature))); -{ - // error. -} - -// Application will Base64Encode the HMAC256 of the az_span_ptr(signature) containing az_span_size(signature) bytes with the Shared Access Key. - -if(az_result_failed(az_iot_hub_client_sas_get_password(client, NULL, base64_hmac_sha256_signature, password, password_size, &password_length))) -{ - // error. -} -``` - -Recommended defaults: - - MQTT Keep-Alive Interval: `AZ_IOT_DEFAULT_MQTT_CONNECT_KEEPALIVE_SECONDS` - - MQTT Clean Session: false. - -#### MQTT Clean Session - -We recommend to always use [Clean Session](http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718030) false when connecting to IoT Hub. -Connecting with Clean Session true will remove all enqueued C2D messages. - -### Subscribe to Topics - -Each service requiring a subscription implements a function similar to the following: - -_Example:_ - -```C -// AZ_IOT_HUB_CLIENT_METHODS_SUBSCRIBE_TOPIC contains the methods topic filter. -MQTTClient_subscribe(mqtt_client, AZ_IOT_HUB_CLIENT_METHODS_SUBSCRIBE_TOPIC, 1); -``` - -__Note:__ If the MQTT stack allows, it is recommended to subscribe prior to connecting. - -### Sending APIs - -Each action (e.g. send telemetry, request twin) is represented by a separate public API. -The application is responsible for filling in the MQTT payload with the format expected by the service. - -_Example:_ - -```C -if(az_result_failed(az_iot_hub_client_telemetry_get_publish_topic(client, NULL, topic, topic_size, NULL))) -{ - // error. -} -``` - -__Note:__ To limit overheads, when publishing, it is recommended to serialize as many MQTT messages within the same TLS record. This feature may not be available on all MQTT/TLS/Sockets stacks. - -### Receiving APIs - -We recommend that the handling of incoming MQTT PUB messages is implemented by a chain-of-responsibility architecture. Each handler is passed the topic and will either accept and return a response, or pass it to the next handler. - -_Example:_ - -```C - az_iot_hub_client_c2d_request c2d_request; - az_iot_hub_client_method_request method_request; - az_iot_hub_client_twin_response twin_response; - - //az_span received_topic is filled by the application. - - if (az_result_succeeded(az_iot_hub_client_c2d_parse_received_topic(client, received_topic, &c2d_request))) - { - // This is a C2D message: - // c2d_request.properties contain the properties of the message. - // the MQTT message payload contains the data. - } - else if (az_result_succeeded(ret = az_iot_hub_client_methods_parse_received_topic(client, received_topic, &method_request))) - { - // This is a Method request: - // method_request.name contains the method - // method_request.request_id contains the request ID that must be used to submit the response using az_iot_hub_client_methods_response_get_publish_topic() - } - else if (az_result_succeeded(ret = az_iot_hub_client_twin_parse_received_topic(client, received_topic, &twin_response))) - { - // This is a Twin operation. - switch (twin_response.response_type) - { - case AZ_IOT_CLIENT_TWIN_RESPONSE_TYPE_GET: - // This is a response to a az_iot_hub_client_twin_document_get_publish_topic. - break; - case AZ_IOT_CLIENT_TWIN_RESPONSE_TYPE_DESIRED_PROPERTIES: - // This is received as the Twin desired properties were changed using the service client. - break; - case AZ_IOT_CLIENT_TWIN_RESPONSE_TYPE_REPORTED_PROPERTIES: - // This is a response received after patching the reported properties using az_iot_hub_client_twin_patch_get_publish_topic(). - break; - default: - // error. - } - } -``` - -__Important:__ C2D messages are not enqueued until the device establishes the first MQTT session (connects for the first time to IoT Hub). The C2D message queue is preserved (according to the per-message time-to-live) as long as the device connects with Clean Session `false`. - -### Retrying Operations - -Retrying operations requires understanding two aspects: error evaluation (did the operation fail, should the operation be retried) and retry timing (how long to delay before retrying the operation). The IoT client library is supplying optional APIs for error classification and retry timing. - -#### Error Policy - -The SDK will not handle protocol-level (WebSocket, MQTT, TLS or TCP) errors. The application-developer is expected to classify and handle errors the following way: - -- Operations failing due to authentication errors should not be retried. -- Operations failing due to communication-related errors other than ones security-related (e.g. TLS Alert) may be retried. - -Both IoT Hub and Provisioning services will use `MQTT CONNACK` as described in Section 3.2.2.3 of the [MQTT v3 specification](http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Table_3.1_-). - -##### IoT Service Errors - -APIs using `az_iot_status` report service-side errors to the client through the IoT protocols. - -The following APIs may be used to determine if the status indicates an error and if the operation should be retried: - -```C -az_iot_status status = response.status; -if (az_iot_status_succeeded(status)) -{ - // success case -} -else -{ - if (az_iot_status_retriable(status)) - { - // retry - } - else - { - // fail - } -} -``` - -#### Retry Timing - -Network timeouts and the MQTT keep-alive interval should be configured considering tradeoffs between how fast network issues are detected vs traffic overheads. [This document](https://docs.microsoft.com/azure/iot-hub/iot-hub-mqtt-support#default-keep-alive-timeout) describes the recommended keep-alive timeouts as well as the minimum idle timeout supported by Azure IoT services. - -For connectivity issues at all layers (TCP, TLS, MQTT) as well as cases where there is no `retry-after` sent by the service, we suggest using an exponential back-off with random jitter function. `az_iot_retry_calc_delay` is available in Azure IoT Common: - -```C -// The previous operation took operation_msec. -// The application calculates random_jitter_msec between 0 and max_random_jitter_msec. - -int32_t delay_msec = az_iot_calculate_retry_delay(operation_msec, attempt, min_retry_delay_msec, max_retry_delay_msec, random_jitter_msec); -``` - -_Note 1_: The network stack may have used more time than the recommended delay before timing out. (e.g. The operation timed out after 2 minutes while the delay between operations is 1 second). In this case there is no need to delay the next operation. - -_Note 2_: To determine the parameters of the exponential with back-off retry strategy, we recommend modeling the network characteristics (including failure-modes). Compare the results with defined SLAs for device connectivity (e.g. 1M devices must be connected in under 30 minutes) and with the available [IoT Azure scale](https://docs.microsoft.com/azure/iot-hub/iot-hub-devguide-quotas-throttling) (especially consider _throttling_, _quotas_ and maximum _requests/connects per second_). - -In the absence of modeling, we recommend the following default: - -```C - min_retry_delay_msec = 1000; - max_retry_delay_msec = 100000; - max_random_jitter_msec = 5000; -``` - -For service-level errors, the Provisioning Service is providing a `retry-after` (in seconds) parameter: - -```C -// az_iot_provisioning_client_received_topic_payload_parse was successful and created a az_iot_provisioning_client_register_response response - -int32_t delay_ms; -if ( response.retry_after_seconds > 0 ) -{ - delay_ms = response.retry_after_seconds; -} -else -{ - delay_ms = az_iot_calculate_retry_delay(operation_msec, attempt, min_retry_delay_msec, max_retry_delay_msec, random_jitter_msec); -} -``` - -#### Suggested Retry Strategy - -Combining the functions above we recommend the following flow: - -![iot_retry_flow](https://www.plantuml.com/plantuml/svg/0/bPNDQkCm4CVlUegvBHIy3n1AQ3RBie7G13TxMefHd6agwaf6abEwfUzUsObSoHedsnnya7xpd-_enbYkRVDSCVCaPCqrVmPtP17U6BZV3ru-xRLgv6wkAgMlhsVhzNGAxhjSp6URnUgMnkus-P_vnf5BVa2vGqrZlsQBfODMciizidV6_bxTGvPDOQtLGHYXf91xTWmeF08Vo1lhX8z4ZdjXBEhY9nv4YHxgY6-nVOvM2xwj451hfKt7UES3dT15A59eBr8yS54r6dr2dS4mcc5QgNbdTXv9LKfUbKtbOYjsMF7NL6C0f0gyhWDR8iyU20jA4rJzO09j7fT3cq3cI5ChQV1xPvBn1tkQdKk6_5yXbEqgzjhT1pbH4t2hPDQN5_wlO_Ba8EqQKJKIZYRaCfw6aAlMKp7Nk4Df1Q_CcF-KXD7-4UoN6ZbYtoxK1AG2PHzHGzbVitTWB6f7Yo_KflZTR9t9NLEMQ0mxhRw_-DpwpvpTUR6gKNFhbA8C_Jf713lDGYj7_Wd4Ujx-NDV9-wZH9D5hKnjCdFSyjQ_HULY4w28jHzHImkcvpGegIImJNSTB6pJA9FKStvVZrEMOrNx0sfV5pz1menowsbek94Xy2KRK3T-DUxdSq_W1 "iot_retry_flow") - -When devices are using IoT Hub without Provisioning Service, we recommend attempting to rotate the IoT Credentials (SAS Token or X509 Certificate) on authentication issues. - -_Note:_ Authentication issues observed in the following cases do not require credentials to be rotated: - -- DNS issues (such as WiFi Captive Portal redirects) -- WebSockets Proxy server authentication +# Azure IoT Client MQTT State Machine + +## High-level Architecture + +Device Provisioning and IoT Hub service protocols require additional state management on top of the MQTT protocol. The Azure IoT Hub and Provisioning clients for C provide a common programming model. The clients must be layered on top of an MQTT client selected by the application developer. + +The following aspects are being handled by the IoT Clients: + +1. Generate MQTT CONNECT credentials. +1. Obtain SUBSCRIBE topic filters and PUBLISH topic strings required by various service features. +1. Parse service errors and output an uniform error object model. +1. Provide the correct sequence of events required to perform an operation. +1. Provide suggested timing information when retrying operations. + +The following aspects need to be handled by the application or convenience layers: + +1. Ensure secure TLS communication using either server or mutual X509 authentication. +1. Perform MQTT transport-level operations. +1. Delay execution for retry purposes. +1. (Optional) Provide real-time clock information and perform HMAC-SHA256 operations for SAS token generation. + +For more information about Azure IoT services using MQTT see [this article](https://docs.microsoft.com/azure/iot-hub/iot-hub-mqtt-support). + +## Components + +### IoT Hub + +![iot_hub_flow](https://www.plantuml.com/plantuml/svg/0/jLTjRzis4FxENt7VcWppDXHhWHb5K5anPgz9x4KADgme25gwsaGaKYMff5wn_pvIBxRZYBAzTlbW1EBTynmVtXsFRxLXofHvHeY-vw9WYkLWlnc4BmJituWbzqibIv66CfFgpPjWFh-u0FjxDGs3U3gxwJQBujkxCBQML-m1HOgA_6CfAk34MT14fbmi6vPw8RfyHuFvTE_BPH07T3RwRHp6iCNTCTg9XOQpop4qGJh65p0Lt93ttts0mU02sD-KIqDNvO8c6KTXABVBGuZYu3OctQlGwRT4GqkDYWgM8pcmxeTeUL9oOBs2A8gC9ynmAMZ-oTXLAGGcDlu_N_tq2uILBGIbMGsXO5e_IfK2ru1vOTQD-3wCZMXMibdXbba6KH21aVSeO8a2LUKrF2hIPoQQwSfCywLWfbG8mmgUsMZ9CUPx-r_bB7dvwZNf-DOVG2iCEMnIfunFiASQiwrO7KgtaUs876E6EHgr9bw6GTQv4zDwri6MYuph5JMou8aDQeCB2HEmGB1PcCBZZn9qkT29uWjNTaO24sked7uWft5qgjAOa9xTTBcrCI7f2C86i9AffWfr8ON6huzbG-Vf1kQH2vhAfCpRj7v2Hqxs-5y2B9X9LiT7vp4_4DeB26MUIm7R8_9qa6tCRNhtRQk3Ks62_C5rXElVunWoljYIfiH7sqIwhIcFNOlUNbrVmEsDPTd2tbcGsaHVMcjR4oicqWpLiBfQ_3T6yZAraYgRZWrokgczs-O3pEz6LzDe0ZbgpPPO8Pw0r4tSbeE7N4W35X4KPbQ7rCFP2zARSrxI1l80iH_5iJgvQpHdju5o9_NoycJvVCUcy5E9shLk9rHYSm1Qruh3qbtNcnd-9_dyfxP3NN_eO3_ekryxzMgTjZlpaDTcKoeLczUgD_dylRXLCBO7U7eEU1El9RJALQdnwpW6gM0UnE5agryzwiaO9kK2QlUXRvuRmW3QxokfZMU2szYc-C0JT3EQvN2tVEnkUKsE6t86zL10yNF1wmcAOY5jGpQyatP_27488wRnzSzda-iW74IJm4uIPnkAxo5QCOLfa2IssRp3zVdvaQZtkAwdLtBgZ37eEAn2oGALuQWtdVXGFkzCyuTi7MVpR_Um_DpSjxN3cNmasI48fqBPeyPkH-gLucdhEYJ-wJFAtdvFdIFstNtxVLhElJlUhtn7q4XjrWKUwT5pWi_DczCwnlG1bRPrxDf1iEsYm8L1zP7PCYGzZmRBXCeYqC_spKQDAhMzwAYIqlHxlM_2gTmITzWVdIr-w9iYdUEPR9vpvn3yHQUpc4xkHZIycevlOcflLnhTZQBmTZeQqtWsduPV0tSzZYUpo-XRGiP_J6_exJYQpswYyJS7hgwHPwYThygcPS9PpjRkNVN8pShUH_QHDOckpmbvYo8jy-nV "iot_hub_flow") + +### Device Provisioning Service + +![iot_provisioning_flow](https://www.plantuml.com/plantuml/svg/0/hLTjRzis4FxENy5ljOUv68Qrm8mYgEmOZTOazYnb65OK12tICc69L4dAZLVil-yeoR8RlqYscmz68DuzTtpk-F3utbYgRPuc29cba1dLDCNmpNBy3M7u6z1e6MkLIpPKYTBltdcJoSqDXjzxV-Y5_lZni5aGJf-68LZUqnMNZ6lq7uGeW2DdGIB5X8ohAEL2SFkHU1F_nPTRB_J8UF37Q1ZYBUwEqKukP3Y-7U4gIHVn5VQbiEZJJvznhtU3wRz4A4iohLR222KX0n8bWajB1DZrSH1wqn0rc3L1nToIt71D94qvQclO1dMJ1CDz_FURNtx7DBUBYbQQPOc8g0KladwnOAoav96jwMYDNIHo1CrHEixSPvhWm0kmLKikSSmbruLr_umu9_Thg2diZOWbjRdci6SNiDZoMNuljhD8QVJhcxVemBUMAMNKcAzACtI6xKbMOMirvdurDybj2Wab2FAPYRIOSNGKyc0yK_iTQPhQe8xsweJDmHYQHroiNBaU_Wn3d8WPhZsOItxAOs4iI48Jyd5sTGLHKfpF4c4MnAfCHEWYhyCxgKn-96H5pZ7wAuHjgO_ORJZUwF4t3BmMT4-U1hXQPOL8jj0-a7t3DvYiJ9zGoM1XdIZA-llGUAvLYcSQJh_6dIXHESDAun6BURS8UqMuriNNt6HA6lv30ZgU-bWgYtSXOCC9L03wjY76NbBjI5TPfPl4LJetgDrUNher1TlQHohD4cuRETlts66fNiJrOiVaKSIDSLqmfo81uUrU66EtsndGl_ukef2kG37GR7Q6W7sVpH4gSjXRRU3hIyxrkkBsWCOqxEkqzscjAOQMoMu7bg3zngcaZtiuJjIC9WGcham9M5WeOvkcaDfPC68Y8BgLSTdlLVb4eUlkTWxTE63GcVTwhnmZ27_EH0MPI_5Ch83KqJKdkyHkk1TQeLJ_rv9yZXJipArd-RcubIwSxRSzN89TutrfVCqj708wTmk6mrJ2BWBPwJ9OoPFJ1-4GoklNFGp35RGRr7QHpreTYcD0ZAz4n2-Wr3bphSkNpqMX-RDrBob6heGfl5EGJnfV0G15YPRE-XegtFc6_aepTCuexHfZG0r_-rldxi4Rze8x3yRZJYqeF-DWGJLdNd4XVUJhRlTS-2LVbwytTBdCxnr2a4XlA_jmaG4lSmeveXFl0X00hbT0u3Eyzu3r7DXaH2jbJtjzgf8it2AkhPWxM_xvnDJj_Bn_Y3yEoMGw_iiFu4djFtuWmtj_z1YIwUY37uIZAGcU1i5mwWz691nF_24wT65_9m07treLpxYJWWEC_5-5NS3bJYM-2nww2Bs7Ri_Jv7y0 "iot_provisioning_flow") + +## Porting the IoT Clients + +In order to port the clients to a target platform the following items are required: + +- Support for a C99 compiler. +- Types such as `uint8_t` must be defined. +- The target platform supports a stack of several kB (actual requirement depends on features being used and data sizes). +- An MQTT over TLS client supporting QoS 0 and 1 messages. + +Optionally, the IoT services support MQTT tunneling over WebSocket Secure which allows bypassing firewalls where port 8883 is not open. Using WebSockets also allows usage of devices that must go through a WebProxy. Application developers are responsible with setting up the wss:// tunnel. + +## API + +### Connecting + +The application code is required to initialize the TLS and MQTT stacks. +Two authentication schemes are currently supported: _X509 Client Certificate Authentication_ and _Shared Access Signature_ authentication. + +When X509 client authentication is used, the MQTT password field should be an empty string. + +If SAS tokens are used the following APIs provide a way to create as well as refresh the lifetime of the used token upon reconnect. + +_Example:_ + +```C +if(az_result_failed(az_iot_hub_client_sas_get_signature(client, unix_time + 3600, signature, &signature))); +{ + // error. +} + +// Application will Base64Encode the HMAC256 of the az_span_ptr(signature) containing az_span_size(signature) bytes with the Shared Access Key. + +if(az_result_failed(az_iot_hub_client_sas_get_password(client, NULL, base64_hmac_sha256_signature, password, password_size, &password_length))) +{ + // error. +} +``` + +Recommended defaults: + - MQTT Keep-Alive Interval: `AZ_IOT_DEFAULT_MQTT_CONNECT_KEEPALIVE_SECONDS` + - MQTT Clean Session: false. + +#### MQTT Clean Session + +We recommend to always use [Clean Session](http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718030) false when connecting to IoT Hub. +Connecting with Clean Session true will remove all enqueued C2D messages. + +### Subscribe to Topics + +Each service requiring a subscription implements a function similar to the following: + +_Example:_ + +```C +// AZ_IOT_HUB_CLIENT_METHODS_SUBSCRIBE_TOPIC contains the methods topic filter. +MQTTClient_subscribe(mqtt_client, AZ_IOT_HUB_CLIENT_METHODS_SUBSCRIBE_TOPIC, 1); +``` + +__Note:__ If the MQTT stack allows, it is recommended to subscribe prior to connecting. + +### Sending APIs + +Each action (e.g. send telemetry, request twin) is represented by a separate public API. +The application is responsible for filling in the MQTT payload with the format expected by the service. + +_Example:_ + +```C +if(az_result_failed(az_iot_hub_client_telemetry_get_publish_topic(client, NULL, topic, topic_size, NULL))) +{ + // error. +} +``` + +__Note:__ To limit overheads, when publishing, it is recommended to serialize as many MQTT messages within the same TLS record. This feature may not be available on all MQTT/TLS/Sockets stacks. + +### Receiving APIs + +We recommend that the handling of incoming MQTT PUB messages is implemented by a chain-of-responsibility architecture. Each handler is passed the topic and will either accept and return a response, or pass it to the next handler. + +_Example:_ + +```C + az_iot_hub_client_c2d_request c2d_request; + az_iot_hub_client_method_request method_request; + az_iot_hub_client_twin_response twin_response; + + //az_span received_topic is filled by the application. + + if (az_result_succeeded(az_iot_hub_client_c2d_parse_received_topic(client, received_topic, &c2d_request))) + { + // This is a C2D message: + // c2d_request.properties contain the properties of the message. + // the MQTT message payload contains the data. + } + else if (az_result_succeeded(ret = az_iot_hub_client_methods_parse_received_topic(client, received_topic, &method_request))) + { + // This is a Method request: + // method_request.name contains the method + // method_request.request_id contains the request ID that must be used to submit the response using az_iot_hub_client_methods_response_get_publish_topic() + } + else if (az_result_succeeded(ret = az_iot_hub_client_twin_parse_received_topic(client, received_topic, &twin_response))) + { + // This is a Twin operation. + switch (twin_response.response_type) + { + case AZ_IOT_CLIENT_TWIN_RESPONSE_TYPE_GET: + // This is a response to a az_iot_hub_client_twin_document_get_publish_topic. + break; + case AZ_IOT_CLIENT_TWIN_RESPONSE_TYPE_DESIRED_PROPERTIES: + // This is received as the Twin desired properties were changed using the service client. + break; + case AZ_IOT_CLIENT_TWIN_RESPONSE_TYPE_REPORTED_PROPERTIES: + // This is a response received after patching the reported properties using az_iot_hub_client_twin_patch_get_publish_topic(). + break; + default: + // error. + } + } +``` + +__Important:__ C2D messages are not enqueued until the device establishes the first MQTT session (connects for the first time to IoT Hub). The C2D message queue is preserved (according to the per-message time-to-live) as long as the device connects with Clean Session `false`. + +### Retrying Operations + +Retrying operations requires understanding two aspects: error evaluation (did the operation fail, should the operation be retried) and retry timing (how long to delay before retrying the operation). The IoT client library is supplying optional APIs for error classification and retry timing. + +#### Error Policy + +The SDK will not handle protocol-level (WebSocket, MQTT, TLS or TCP) errors. The application-developer is expected to classify and handle errors the following way: + +- Operations failing due to authentication errors should not be retried. +- Operations failing due to communication-related errors other than ones security-related (e.g. TLS Alert) may be retried. + +Both IoT Hub and Provisioning services will use `MQTT CONNACK` as described in Section 3.2.2.3 of the [MQTT v3 specification](http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Table_3.1_-). + +##### IoT Service Errors + +APIs using `az_iot_status` report service-side errors to the client through the IoT protocols. + +The following APIs may be used to determine if the status indicates an error and if the operation should be retried: + +```C +az_iot_status status = response.status; +if (az_iot_status_succeeded(status)) +{ + // success case +} +else +{ + if (az_iot_status_retriable(status)) + { + // retry + } + else + { + // fail + } +} +``` + +#### Retry Timing + +Network timeouts and the MQTT keep-alive interval should be configured considering tradeoffs between how fast network issues are detected vs traffic overheads. [This document](https://docs.microsoft.com/azure/iot-hub/iot-hub-mqtt-support#default-keep-alive-timeout) describes the recommended keep-alive timeouts as well as the minimum idle timeout supported by Azure IoT services. + +For connectivity issues at all layers (TCP, TLS, MQTT) as well as cases where there is no `retry-after` sent by the service, we suggest using an exponential back-off with random jitter function. `az_iot_retry_calc_delay` is available in Azure IoT Common: + +```C +// The previous operation took operation_msec. +// The application calculates random_jitter_msec between 0 and max_random_jitter_msec. + +int32_t delay_msec = az_iot_calculate_retry_delay(operation_msec, attempt, min_retry_delay_msec, max_retry_delay_msec, random_jitter_msec); +``` + +_Note 1_: The network stack may have used more time than the recommended delay before timing out. (e.g. The operation timed out after 2 minutes while the delay between operations is 1 second). In this case there is no need to delay the next operation. + +_Note 2_: To determine the parameters of the exponential with back-off retry strategy, we recommend modeling the network characteristics (including failure-modes). Compare the results with defined SLAs for device connectivity (e.g. 1M devices must be connected in under 30 minutes) and with the available [IoT Azure scale](https://docs.microsoft.com/azure/iot-hub/iot-hub-devguide-quotas-throttling) (especially consider _throttling_, _quotas_ and maximum _requests/connects per second_). + +In the absence of modeling, we recommend the following default: + +```C + min_retry_delay_msec = 1000; + max_retry_delay_msec = 100000; + max_random_jitter_msec = 5000; +``` + +For service-level errors, the Provisioning Service is providing a `retry-after` (in seconds) parameter: + +```C +// az_iot_provisioning_client_received_topic_payload_parse was successful and created a az_iot_provisioning_client_register_response response + +int32_t delay_ms; +if ( response.retry_after_seconds > 0 ) +{ + delay_ms = response.retry_after_seconds; +} +else +{ + delay_ms = az_iot_calculate_retry_delay(operation_msec, attempt, min_retry_delay_msec, max_retry_delay_msec, random_jitter_msec); +} +``` + +#### Suggested Retry Strategy + +Combining the functions above we recommend the following flow: + +![iot_retry_flow](https://www.plantuml.com/plantuml/svg/0/bPNDQkCm4CVlUegvBHIy3n1AQ3RBie7G13TxMefHd6agwaf6abEwfUzUsObSoHedsnnya7xpd-_enbYkRVDSCVCaPCqrVmPtP17U6BZV3ru-xRLgv6wkAgMlhsVhzNGAxhjSp6URnUgMnkus-P_vnf5BVa2vGqrZlsQBfODMciizidV6_bxTGvPDOQtLGHYXf91xTWmeF08Vo1lhX8z4ZdjXBEhY9nv4YHxgY6-nVOvM2xwj451hfKt7UES3dT15A59eBr8yS54r6dr2dS4mcc5QgNbdTXv9LKfUbKtbOYjsMF7NL6C0f0gyhWDR8iyU20jA4rJzO09j7fT3cq3cI5ChQV1xPvBn1tkQdKk6_5yXbEqgzjhT1pbH4t2hPDQN5_wlO_Ba8EqQKJKIZYRaCfw6aAlMKp7Nk4Df1Q_CcF-KXD7-4UoN6ZbYtoxK1AG2PHzHGzbVitTWB6f7Yo_KflZTR9t9NLEMQ0mxhRw_-DpwpvpTUR6gKNFhbA8C_Jf713lDGYj7_Wd4Ujx-NDV9-wZH9D5hKnjCdFSyjQ_HULY4w28jHzHImkcvpGegIImJNSTB6pJA9FKStvVZrEMOrNx0sfV5pz1menowsbek94Xy2KRK3T-DUxdSq_W1 "iot_retry_flow") + +When devices are using IoT Hub without Provisioning Service, we recommend attempting to rotate the IoT Credentials (SAS Token or X509 Certificate) on authentication issues. + +_Note:_ Authentication issues observed in the following cases do not require credentials to be rotated: + +- DNS issues (such as WiFi Captive Portal redirects) +- WebSockets Proxy server authentication diff --git a/sdk/docs/iot/resources/iot_hub_flow.puml b/sdk/docs/iot/resources/iot_hub_flow.puml index f62f2d95ff..e6fdcb8a48 100644 --- a/sdk/docs/iot/resources/iot_hub_flow.puml +++ b/sdk/docs/iot/resources/iot_hub_flow.puml @@ -1,110 +1,110 @@ -@startuml - -skinparam state { - BackgroundColor<> APPLICATION - BackgroundColor<> Lavender -} - -state color_coding { - state SDK_API - state SDK_DATA_OBJECT <> - state APPLICATION_CODE <> -} - -' Init -[*] --> az_iot_hub_client_init: START -az_iot_hub_client_init -> az_iot_hub_client_get_user_name -az_iot_hub_client_get_user_name -> az_iot_hub_client_get_client_id -az_iot_hub_client_get_client_id -> application_mqtt_connect: X509 Client Auth: password is empty -state application_mqtt_connect <> - -' Optional SAS token generation: -az_iot_hub_client_get_client_id -> az_iot_hub_client_sas_get_signature : SAS auth -az_iot_hub_client_sas_get_signature -> application_hmac256 -application_hmac256 -> az_iot_hub_client_sas_get_password -az_iot_hub_client_sas_get_password --> application_mqtt_connect : password is a SAS token -state application_hmac256 <> - -' Telemetry -application_mqtt_connect -l-> az_iot_hub_client_telemetry_get_publish_topic : Telemetry can be used w/o subscribing to any topic. -az_iot_hub_client_telemetry_get_publish_topic --> application_mqtt_publish - -application_mqtt_connect --> application_mqtt_subscribe - -az_iot_hub_client_methods_response_get_publish_topic --> application_mqtt_publish - -application_mqtt_subscribe --> az_iot_hub_client_twin_document_get_publish_topic -az_iot_hub_client_twin_document_get_publish_topic --> application_mqtt_publish - -application_mqtt_subscribe --> az_iot_hub_client_twin_patch_get_publish_topic -az_iot_hub_client_twin_patch_get_publish_topic --> application_mqtt_publish - -' Common subscribe -state application_mqtt_subscribe <> -application_mqtt_subscribe -> application_mqtt_receive : MQTT lib subscribed - -state application_mqtt_publish <> - -state application_mqtt_receive <> { -' Callback delegating handler: - [*] --> az_iot_hub_client_c2d_parse_received_topic : Received PUBLISH message - az_iot_hub_client_c2d_parse_received_topic --> az_iot_hub_client_methods_parse_received_topic : not c2d related - az_iot_hub_client_methods_parse_received_topic --> az_iot_hub_client_twin_parse_received_topic : not methods related - az_iot_hub_client_twin_parse_received_topic --> [*] : not twin related - -' C2D - az_iot_hub_client_c2d_parse_received_topic -> az_iot_hub_client_c2d_request : c2d call received - -' Methods: - az_iot_hub_client_methods_parse_received_topic -> az_iot_hub_client_method_request : method call received - az_iot_hub_client_method_request -> application_method_handle - state application_method_handle <> - application_method_handle -> az_iot_hub_client_methods_response_get_publish_topic - -' Twin - az_iot_hub_client_twin_parse_received_topic -> az_iot_hub_client_twin_response : twin GET or PATCH received -} - -' IoT Hub client: -az_iot_hub_client_init : - iot_hub_hostname -az_iot_hub_client_init : - device_id - -' SAS Tokens -az_iot_hub_client_sas_get_signature : - token_expiration_epoch_time -az_iot_hub_client_sas_get_password: - Base64(HMAC-SHA256(signature, SharedAccessKey)) -az_iot_hub_client_sas_get_password: - key_name - -az_iot_hub_client_telemetry_get_publish_topic : - az_iot_message_properties - -state az_iot_hub_client_method_request <> -az_iot_hub_client_method_request: - request_id -az_iot_hub_client_method_request: - name - -az_iot_hub_client_methods_response_get_publish_topic: - request_id -az_iot_hub_client_methods_response_get_publish_topic: - status - -state az_iot_hub_client_c2d_request <> -az_iot_hub_client_c2d_request : - az_iot_message_properties - -az_iot_hub_client_twin_document_get_publish_topic : - request_id - -az_iot_hub_client_twin_patch_get_publish_topic : - request_id - -state az_iot_hub_client_twin_response <> -az_iot_hub_client_twin_response : - response_type -az_iot_hub_client_twin_response : - request_id -az_iot_hub_client_twin_response : - status -az_iot_hub_client_twin_response : - version - -' Application interfaces -application_mqtt_connect : - server_x509_trusted_root -application_mqtt_connect : - [client_x509_certificate] - -application_mqtt_subscribe: - (optional) AZ_IOT_HUB_CLIENT_C2D_SUBSCRIBE_TOPIC -application_mqtt_subscribe: - (optional) AZ_IOT_HUB_CLIENT_METHODS_SUBSCRIBE_TOPIC -application_mqtt_subscribe: - (optional) AZ_IOT_HUB_CLIENT_TWIN_RESPONSE_SUBSCRIBE_TOPIC -application_mqtt_subscribe: - (optional) AZ_IOT_HUB_CLIENT_TWIN_PATCH_SUBSCRIBE_TOPIC - -application_hmac256 : - key (may be within an HSM) - -@enduml +@startuml + +skinparam state { + BackgroundColor<> APPLICATION + BackgroundColor<> Lavender +} + +state color_coding { + state SDK_API + state SDK_DATA_OBJECT <> + state APPLICATION_CODE <> +} + +' Init +[*] --> az_iot_hub_client_init: START +az_iot_hub_client_init -> az_iot_hub_client_get_user_name +az_iot_hub_client_get_user_name -> az_iot_hub_client_get_client_id +az_iot_hub_client_get_client_id -> application_mqtt_connect: X509 Client Auth: password is empty +state application_mqtt_connect <> + +' Optional SAS token generation: +az_iot_hub_client_get_client_id -> az_iot_hub_client_sas_get_signature : SAS auth +az_iot_hub_client_sas_get_signature -> application_hmac256 +application_hmac256 -> az_iot_hub_client_sas_get_password +az_iot_hub_client_sas_get_password --> application_mqtt_connect : password is a SAS token +state application_hmac256 <> + +' Telemetry +application_mqtt_connect -l-> az_iot_hub_client_telemetry_get_publish_topic : Telemetry can be used w/o subscribing to any topic. +az_iot_hub_client_telemetry_get_publish_topic --> application_mqtt_publish + +application_mqtt_connect --> application_mqtt_subscribe + +az_iot_hub_client_methods_response_get_publish_topic --> application_mqtt_publish + +application_mqtt_subscribe --> az_iot_hub_client_twin_document_get_publish_topic +az_iot_hub_client_twin_document_get_publish_topic --> application_mqtt_publish + +application_mqtt_subscribe --> az_iot_hub_client_twin_patch_get_publish_topic +az_iot_hub_client_twin_patch_get_publish_topic --> application_mqtt_publish + +' Common subscribe +state application_mqtt_subscribe <> +application_mqtt_subscribe -> application_mqtt_receive : MQTT lib subscribed + +state application_mqtt_publish <> + +state application_mqtt_receive <> { +' Callback delegating handler: + [*] --> az_iot_hub_client_c2d_parse_received_topic : Received PUBLISH message + az_iot_hub_client_c2d_parse_received_topic --> az_iot_hub_client_methods_parse_received_topic : not c2d related + az_iot_hub_client_methods_parse_received_topic --> az_iot_hub_client_twin_parse_received_topic : not methods related + az_iot_hub_client_twin_parse_received_topic --> [*] : not twin related + +' C2D + az_iot_hub_client_c2d_parse_received_topic -> az_iot_hub_client_c2d_request : c2d call received + +' Methods: + az_iot_hub_client_methods_parse_received_topic -> az_iot_hub_client_method_request : method call received + az_iot_hub_client_method_request -> application_method_handle + state application_method_handle <> + application_method_handle -> az_iot_hub_client_methods_response_get_publish_topic + +' Twin + az_iot_hub_client_twin_parse_received_topic -> az_iot_hub_client_twin_response : twin GET or PATCH received +} + +' IoT Hub client: +az_iot_hub_client_init : - iot_hub_hostname +az_iot_hub_client_init : - device_id + +' SAS Tokens +az_iot_hub_client_sas_get_signature : - token_expiration_epoch_time +az_iot_hub_client_sas_get_password: - Base64(HMAC-SHA256(signature, SharedAccessKey)) +az_iot_hub_client_sas_get_password: - key_name + +az_iot_hub_client_telemetry_get_publish_topic : - az_iot_message_properties + +state az_iot_hub_client_method_request <> +az_iot_hub_client_method_request: - request_id +az_iot_hub_client_method_request: - name + +az_iot_hub_client_methods_response_get_publish_topic: - request_id +az_iot_hub_client_methods_response_get_publish_topic: - status + +state az_iot_hub_client_c2d_request <> +az_iot_hub_client_c2d_request : - az_iot_message_properties + +az_iot_hub_client_twin_document_get_publish_topic : - request_id + +az_iot_hub_client_twin_patch_get_publish_topic : - request_id + +state az_iot_hub_client_twin_response <> +az_iot_hub_client_twin_response : - response_type +az_iot_hub_client_twin_response : - request_id +az_iot_hub_client_twin_response : - status +az_iot_hub_client_twin_response : - version + +' Application interfaces +application_mqtt_connect : - server_x509_trusted_root +application_mqtt_connect : - [client_x509_certificate] + +application_mqtt_subscribe: - (optional) AZ_IOT_HUB_CLIENT_C2D_SUBSCRIBE_TOPIC +application_mqtt_subscribe: - (optional) AZ_IOT_HUB_CLIENT_METHODS_SUBSCRIBE_TOPIC +application_mqtt_subscribe: - (optional) AZ_IOT_HUB_CLIENT_TWIN_RESPONSE_SUBSCRIBE_TOPIC +application_mqtt_subscribe: - (optional) AZ_IOT_HUB_CLIENT_TWIN_PATCH_SUBSCRIBE_TOPIC + +application_hmac256 : - key (may be within an HSM) + +@enduml diff --git a/sdk/docs/iot/resources/iot_provisioning_flow.puml b/sdk/docs/iot/resources/iot_provisioning_flow.puml index f78dc31175..cfcfb4809e 100644 --- a/sdk/docs/iot/resources/iot_provisioning_flow.puml +++ b/sdk/docs/iot/resources/iot_provisioning_flow.puml @@ -1,92 +1,92 @@ -@startuml - -skinparam state { - BackgroundColor<> APPLICATION - BackgroundColor<> Lavender -} - -state color_coding { - state SDK_API - state SDK_DATA_OBJECT <> - state APPLICATION_CODE <> -} - -' Init -[*] --> az_iot_provisioning_client_init: START -az_iot_provisioning_client_init --> az_iot_provisioning_client_get_user_name -az_iot_provisioning_client_get_user_name --> az_iot_provisioning_client_get_client_id : X509 auth -state application_mqtt_connect <> -az_iot_provisioning_client_get_client_id --> application_mqtt_connect - -' Optional SAS token generation: -az_iot_provisioning_client_get_client_id -> az_iot_provisioning_client_sas_get_signature : SAS auth -az_iot_provisioning_client_sas_get_signature -> application_hmac256 -application_hmac256 -> az_iot_provisioning_client_sas_get_password -az_iot_provisioning_client_sas_get_password --> application_mqtt_connect : password -state application_hmac256 <> - -' Subscribe -application_mqtt_connect --> application_mqtt_subscribe -state application_mqtt_subscribe <> -'application_mqtt_subscribe --> application_mqtt_receive : MQTT lib subscribed - -' Register -application_mqtt_subscribe --> az_iot_provisioning_client_register_get_publish_topic -az_iot_provisioning_client_register_get_publish_topic --> application_mqtt_publish -az_iot_provisioning_client_register_get_publish_topic --> application_mqtt_receive -state application_mqtt_publish <> - -state application_mqtt_receive <> { -' MQTT PUB received - [*] --> az_iot_provisioning_client_parse_received_topic_and_payload: MQTT PUB received from broker (cloud or Edge) - az_iot_provisioning_client_parse_received_topic_and_payload --> az_iot_provisioning_client_register_response - az_iot_provisioning_client_parse_received_topic_and_payload --> [*] : not provisioning related - - az_iot_provisioning_client_register_response --> az_iot_provisioning_client_parse_operation_status - - az_iot_provisioning_client_parse_operation_status --> az_iot_provisioning_client_operation_complete - az_iot_provisioning_client_operation_complete --> [*] : operation complete (success or failure) - az_iot_provisioning_client_operation_complete --> application_delay : operation in progress - state application_delay <> - application_delay --> az_iot_provisioning_client_query_status_get_publish_topic - az_iot_provisioning_client_query_status_get_publish_topic --> application_mqtt_publish -} - -' Provisioning client: -az_iot_provisioning_client_init : - global_endpoint_hostname -az_iot_provisioning_client_init : - id_scope -az_iot_provisioning_client_init : - registration_id - -' SAS Tokens -az_iot_provisioning_client_sas_get_signature : - token_expiration_unix_time - -az_iot_provisioning_client_parse_received_topic_and_payload : - topic -az_iot_provisioning_client_parse_received_topic_and_payload : - payload - -state az_iot_provisioning_client_register_response <> -az_iot_provisioning_client_register_response : - status -az_iot_provisioning_client_register_response : - operation_id -az_iot_provisioning_client_register_response : - operation_status -az_iot_provisioning_client_register_response : - retry_after_seconds -az_iot_provisioning_client_register_response : - registration_state: -az_iot_provisioning_client_register_response : ..- assigned_hub_hostname -az_iot_provisioning_client_register_response : ..- device_id -az_iot_provisioning_client_register_response : ..- error_code -az_iot_provisioning_client_register_response : ..- extended_error_code -az_iot_provisioning_client_register_response : ..- error_message -az_iot_provisioning_client_register_response : ..- error_tracking_id -az_iot_provisioning_client_register_response : ..- error_timestamp - -az_iot_provisioning_client_sas_get_password: - Base64(HMAC-SHA256(signature, SharedAccessKey)) -az_iot_provisioning_client_sas_get_password: - key_name - -az_iot_provisioning_client_query_status_get_publish_topic : - operation_id - -' Application interfaces -application_mqtt_connect : - server_x509_trusted_root -application_mqtt_connect : - [client_x509_certificate] -application_mqtt_subscribe: - AZ_IOT_PROVISIONING_CLIENT_REGISTER_SUBSCRIBE_TOPIC -application_delay : - retry_after -application_hmac256 : - key (may be within an HSM) - -@enduml +@startuml + +skinparam state { + BackgroundColor<> APPLICATION + BackgroundColor<> Lavender +} + +state color_coding { + state SDK_API + state SDK_DATA_OBJECT <> + state APPLICATION_CODE <> +} + +' Init +[*] --> az_iot_provisioning_client_init: START +az_iot_provisioning_client_init --> az_iot_provisioning_client_get_user_name +az_iot_provisioning_client_get_user_name --> az_iot_provisioning_client_get_client_id : X509 auth +state application_mqtt_connect <> +az_iot_provisioning_client_get_client_id --> application_mqtt_connect + +' Optional SAS token generation: +az_iot_provisioning_client_get_client_id -> az_iot_provisioning_client_sas_get_signature : SAS auth +az_iot_provisioning_client_sas_get_signature -> application_hmac256 +application_hmac256 -> az_iot_provisioning_client_sas_get_password +az_iot_provisioning_client_sas_get_password --> application_mqtt_connect : password +state application_hmac256 <> + +' Subscribe +application_mqtt_connect --> application_mqtt_subscribe +state application_mqtt_subscribe <> +'application_mqtt_subscribe --> application_mqtt_receive : MQTT lib subscribed + +' Register +application_mqtt_subscribe --> az_iot_provisioning_client_register_get_publish_topic +az_iot_provisioning_client_register_get_publish_topic --> application_mqtt_publish +az_iot_provisioning_client_register_get_publish_topic --> application_mqtt_receive +state application_mqtt_publish <> + +state application_mqtt_receive <> { +' MQTT PUB received + [*] --> az_iot_provisioning_client_parse_received_topic_and_payload: MQTT PUB received from broker (cloud or Edge) + az_iot_provisioning_client_parse_received_topic_and_payload --> az_iot_provisioning_client_register_response + az_iot_provisioning_client_parse_received_topic_and_payload --> [*] : not provisioning related + + az_iot_provisioning_client_register_response --> az_iot_provisioning_client_parse_operation_status + + az_iot_provisioning_client_parse_operation_status --> az_iot_provisioning_client_operation_complete + az_iot_provisioning_client_operation_complete --> [*] : operation complete (success or failure) + az_iot_provisioning_client_operation_complete --> application_delay : operation in progress + state application_delay <> + application_delay --> az_iot_provisioning_client_query_status_get_publish_topic + az_iot_provisioning_client_query_status_get_publish_topic --> application_mqtt_publish +} + +' Provisioning client: +az_iot_provisioning_client_init : - global_endpoint_hostname +az_iot_provisioning_client_init : - id_scope +az_iot_provisioning_client_init : - registration_id + +' SAS Tokens +az_iot_provisioning_client_sas_get_signature : - token_expiration_unix_time + +az_iot_provisioning_client_parse_received_topic_and_payload : - topic +az_iot_provisioning_client_parse_received_topic_and_payload : - payload + +state az_iot_provisioning_client_register_response <> +az_iot_provisioning_client_register_response : - status +az_iot_provisioning_client_register_response : - operation_id +az_iot_provisioning_client_register_response : - operation_status +az_iot_provisioning_client_register_response : - retry_after_seconds +az_iot_provisioning_client_register_response : - registration_state: +az_iot_provisioning_client_register_response : ..- assigned_hub_hostname +az_iot_provisioning_client_register_response : ..- device_id +az_iot_provisioning_client_register_response : ..- error_code +az_iot_provisioning_client_register_response : ..- extended_error_code +az_iot_provisioning_client_register_response : ..- error_message +az_iot_provisioning_client_register_response : ..- error_tracking_id +az_iot_provisioning_client_register_response : ..- error_timestamp + +az_iot_provisioning_client_sas_get_password: - Base64(HMAC-SHA256(signature, SharedAccessKey)) +az_iot_provisioning_client_sas_get_password: - key_name + +az_iot_provisioning_client_query_status_get_publish_topic : - operation_id + +' Application interfaces +application_mqtt_connect : - server_x509_trusted_root +application_mqtt_connect : - [client_x509_certificate] +application_mqtt_subscribe: - AZ_IOT_PROVISIONING_CLIENT_REGISTER_SUBSCRIBE_TOPIC +application_delay : - retry_after +application_hmac256 : - key (may be within an HSM) + +@enduml diff --git a/sdk/docs/iot/resources/iot_retry_flow.puml b/sdk/docs/iot/resources/iot_retry_flow.puml index b7f317a04b..002dddbc30 100644 --- a/sdk/docs/iot/resources/iot_retry_flow.puml +++ b/sdk/docs/iot/resources/iot_retry_flow.puml @@ -1,56 +1,56 @@ -@startuml -skinparam state { - BackgroundColor<> APPLICATION - BackgroundColor<> Lavender - BackgroundColor<> Orange -} - -[*] --> Provisioning -state Provisioning <> { - - state Register_Device <> - ' Provisioning Retriable errors - [*] --> Register_Device - - state Provisioning_retriable_failure<> - Register_Device --> Provisioning_retriable_failure - Provisioning_retriable_failure --> Provisioning_calculate_delay - Provisioning_calculate_delay --> Provisioning_app_delay - state Provisioning_app_delay<> - Provisioning_app_delay --> Register_Device : retry - Provisioning_calculate_delay: - response.retry-after - Provisioning_calculate_delay: - az_iot_calculate_retry_delay - - ' Provisioning Non-retriable - Register_Device --> Provisioning_not_retriable_failure - state Provisioning_not_retriable_failure<> - Provisioning_not_retriable_failure --> Provisioning_Rotate_Credentials - state Provisioning_Rotate_Credentials <> - Provisioning_Rotate_Credentials --> Register_Device : retry - Provisioning_Rotate_Credentials --> [*] : no other credential -} - -Provisioning --> IoT_Hub : Success - -state IoT_Hub <> { - state Hub_Operation <> - [*] --> Hub_Operation - - ' Hub Retriable errors - Hub_Operation --> Hub_retriable_failure - state Hub_retriable_failure<> - Hub_retriable_failure --> Hub_calculate_delay - Hub_calculate_delay --> Hub_app_delay - state Hub_app_delay<> - Hub_app_delay --> Hub_Operation : retry - Hub_calculate_delay: - az_iot_calculate_retry_delay - - ' Hub Non-retriable - Hub_Operation --> Hub_not_retriable_failure - state Hub_not_retriable_failure<> - Hub_not_retriable_failure --> [*] : Re-provision -} - -IoT_Hub --> Provisioning : Obtain new credentials - -@enduml +@startuml +skinparam state { + BackgroundColor<> APPLICATION + BackgroundColor<> Lavender + BackgroundColor<> Orange +} + +[*] --> Provisioning +state Provisioning <> { + + state Register_Device <> + ' Provisioning Retriable errors + [*] --> Register_Device + + state Provisioning_retriable_failure<> + Register_Device --> Provisioning_retriable_failure + Provisioning_retriable_failure --> Provisioning_calculate_delay + Provisioning_calculate_delay --> Provisioning_app_delay + state Provisioning_app_delay<> + Provisioning_app_delay --> Register_Device : retry + Provisioning_calculate_delay: - response.retry-after + Provisioning_calculate_delay: - az_iot_calculate_retry_delay + + ' Provisioning Non-retriable + Register_Device --> Provisioning_not_retriable_failure + state Provisioning_not_retriable_failure<> + Provisioning_not_retriable_failure --> Provisioning_Rotate_Credentials + state Provisioning_Rotate_Credentials <> + Provisioning_Rotate_Credentials --> Register_Device : retry + Provisioning_Rotate_Credentials --> [*] : no other credential +} + +Provisioning --> IoT_Hub : Success + +state IoT_Hub <> { + state Hub_Operation <> + [*] --> Hub_Operation + + ' Hub Retriable errors + Hub_Operation --> Hub_retriable_failure + state Hub_retriable_failure<> + Hub_retriable_failure --> Hub_calculate_delay + Hub_calculate_delay --> Hub_app_delay + state Hub_app_delay<> + Hub_app_delay --> Hub_Operation : retry + Hub_calculate_delay: - az_iot_calculate_retry_delay + + ' Hub Non-retriable + Hub_Operation --> Hub_not_retriable_failure + state Hub_not_retriable_failure<> + Hub_not_retriable_failure --> [*] : Re-provision +} + +IoT_Hub --> Provisioning : Obtain new credentials + +@enduml diff --git a/sdk/inc/azure/iot/az_iot_hub_client.h b/sdk/inc/azure/iot/az_iot_hub_client.h index 3f79f0b5a8..d04dc1953c 100644 --- a/sdk/inc/azure/iot/az_iot_hub_client.h +++ b/sdk/inc/azure/iot/az_iot_hub_client.h @@ -1,535 +1,535 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// SPDX-License-Identifier: MIT - -/** - * @file az_iot_hub_client.h - * - * @brief Definition for the Azure IoT Hub client. - * @note The IoT Hub MQTT protocol is described at - * https://docs.microsoft.com/en-us/azure/iot-hub/iot-hub-mqtt-support - * - * @note You MUST NOT use any symbols (macros, functions, structures, enums, etc.) - * prefixed with an underscore ('_') directly in your application code. These symbols - * are part of Azure SDK's internal implementation; we do not document these symbols - * and they are subject to change in future versions of the SDK which would break your code. - */ - -#ifndef _az_IOT_HUB_CLIENT_H -#define _az_IOT_HUB_CLIENT_H - -#include -#include -#include - -#include -#include - -#include - -/** - * @brief Azure IoT service MQTT bit field properties for telemetry publish messages. - * - */ -enum -{ - AZ_HUB_CLIENT_DEFAULT_MQTT_TELEMETRY_QOS = 0 -}; - -/** - * @brief Azure IoT Hub Client options. - * - */ -typedef struct -{ - /** - * The module name (if a module identity is used). - */ - az_span module_id; - - /** - * The user-agent is a formatted string that will be used for Azure IoT usage statistics. - */ - az_span user_agent; - - /** - * The model ID used to identify the capabilities of a device based on the Digital Twin document. - */ - az_span model_id; -} az_iot_hub_client_options; - -/** - * @brief Azure IoT Hub Client. - */ -typedef struct -{ - struct - { - az_span iot_hub_hostname; - az_span device_id; - az_iot_hub_client_options options; - } _internal; -} az_iot_hub_client; - -/** - * @brief Gets the default Azure IoT Hub Client options. - * @details Call this to obtain an initialized #az_iot_hub_client_options structure that can be - * afterwards modified and passed to #az_iot_hub_client_init. - * - * @return #az_iot_hub_client_options. - */ -AZ_NODISCARD az_iot_hub_client_options az_iot_hub_client_options_default(); - -/** - * @brief Initializes an Azure IoT Hub Client. - * - * @param[out] client The #az_iot_hub_client to use for this call. - * @param[in] iot_hub_hostname The IoT Hub Hostname. - * @param[in] device_id The Device ID. If the ID contains any of the following characters, they must - * be percent-encoded as follows: - * - `/` : `%2F` - * - `%` : `%25` - * - `#` : `%23` - * - `&` : `%26` - * @param[in] options A reference to an #az_iot_hub_client_options structure. If `NULL` is passed, - * the hub client will use the default options. If using custom options, please initialize first by - * calling az_iot_hub_client_options_default() and then populating relevant options with your own - * values. - * @return An #az_result value indicating the result of the operation. - */ -AZ_NODISCARD az_result az_iot_hub_client_init( - az_iot_hub_client* client, - az_span iot_hub_hostname, - az_span device_id, - az_iot_hub_client_options const* options); - -/** - * @brief The HTTP URI Path necessary when connecting to IoT Hub using WebSockets. - */ -#define AZ_IOT_HUB_CLIENT_WEB_SOCKET_PATH "/$iothub/websocket" - -/** - * @brief The HTTP URI Path necessary when connecting to IoT Hub using WebSockets without an X509 - * client certificate. - * @note Most devices should use #AZ_IOT_HUB_CLIENT_WEB_SOCKET_PATH. This option is available for - * devices not using X509 client certificates that fail to connect to IoT Hub. - */ -#define AZ_IOT_HUB_CLIENT_WEB_SOCKET_PATH_NO_X509_CLIENT_CERT \ - AZ_IOT_HUB_CLIENT_WEB_SOCKET_PATH "?iothub-no-client-cert=true" - -/** - * @brief Gets the MQTT user name. - * - * The user name will be of the following format: - * - * **Format without module ID** - * - * `{iothubhostname}/{device_id}/?api-version=2018-06-30&{user_agent}` - * - * **Format with module ID** - * - * `{iothubhostname}/{device_id}/{module_id}/?api-version=2018-06-30&{user_agent}` - * - * @param[in] client The #az_iot_hub_client to use for this call. - * @param[out] mqtt_user_name A buffer with sufficient capacity to hold the MQTT user name. If - * successful, contains a null-terminated string with the user name that needs to be passed to the - * MQTT client. - * @param[in] mqtt_user_name_size The size, in bytes of \p mqtt_user_name. - * @param[out] out_mqtt_user_name_length __[nullable]__ Contains the string length, in bytes, of \p - * mqtt_user_name. Can be `NULL`. - * @return An #az_result value indicating the result of the operation. - */ -AZ_NODISCARD az_result az_iot_hub_client_get_user_name( - az_iot_hub_client const* client, - char* mqtt_user_name, - size_t mqtt_user_name_size, - size_t* out_mqtt_user_name_length); - -/** - * @brief Gets the MQTT client ID. - * - * The client ID will be of the following format: - * - * **Format without module ID** - * - * `{device_id}` - * - * **Format with module ID** - * - * `{device_id}/{module_id}` - * - * @param[in] client The #az_iot_hub_client to use for this call. - * @param[out] mqtt_client_id A buffer with sufficient capacity to hold the MQTT client ID. If - * successful, contains a null-terminated string with the client ID that needs to be passed to the - * MQTT client. - * @param[in] mqtt_client_id_size The size, in bytes of \p mqtt_client_id. - * @param[out] out_mqtt_client_id_length __[nullable]__ Contains the string length, in bytes, of - * \p mqtt_client_id. Can be `NULL`. - * @return An #az_result value indicating the result of the operation. - */ -AZ_NODISCARD az_result az_iot_hub_client_get_client_id( - az_iot_hub_client const* client, - char* mqtt_client_id, - size_t mqtt_client_id_size, - size_t* out_mqtt_client_id_length); - -/* - * - * SAS Token APIs - * - * Use the following APIs when the Shared Access Key is available to the application or stored - * within a Hardware Security Module. The APIs are not necessary if X509 Client Certificate - * Authentication is used. - */ - -/** - * @brief Gets the Shared Access clear-text signature. - * @details The application must obtain a valid clear-text signature using this API, sign it using - * HMAC-SHA256 using the Shared Access Key as password then Base64 encode the result. - * - * Use the following APIs when the Shared Access Key is available to the application or stored - * within a Hardware Security Module. The APIs are not necessary if X509 Client Certificate - * Authentication is used. - * - * @note This API should be used in conjunction with az_iot_hub_client_sas_get_password(). - * - * @note More information available at - * https://docs.microsoft.com/en-us/azure/iot-hub/iot-hub-devguide-security#security-tokens - * - * A typical flow for using these two APIs might look something like the following (note the size - * of buffers and non-SDK APIs are for demo purposes only): - * - * @code - * const char* const signature_str = "TST+J9i1F8tE6dLYCtuQcu10u7umGO+aWGqPQhd9AAo="; - * az_span signature = AZ_SPAN_FROM_STR(signature_str); - * az_iot_hub_client_sas_get_signature(&client, expiration_time_in_seconds, signature, &signature); - * - * char decoded_sas_key[128] = { 0 }; - * base64_decode(base64_encoded_sas_key, decoded_sas_key); - * - * char signed_bytes[256] = { 0 }; - * hmac_256(az_span_ptr(signature), az_span_size(signature), decoded_sas_key, signed_bytes); - * - * char signed_bytes_base64_encoded[256] = { 0 }; - * base64_encode(signed_bytes, signed_bytes_base64_encoded); - * - * char final_password[512] = { 0 }; - * az_iot_hub_client_sas_get_password(client, expiration_time_in_seconds, - * AZ_SPAN_FROM_STR(signed_bytes_base64_encoded), final_password, sizeof(final_password), NULL); - * - * mqtt_set_password(&mqtt_client, final_password); - * @endcode - * - * @param[in] client The #az_iot_hub_client to use for this call. - * @param[in] token_expiration_epoch_time The time, in seconds, from 1/1/1970. - * @param[in] signature An empty #az_span with sufficient capacity to hold the SAS signature. - * @param[out] out_signature The output #az_span containing the SAS signature. - * @return An #az_result value indicating the result of the operation. - */ -AZ_NODISCARD az_result az_iot_hub_client_sas_get_signature( - az_iot_hub_client const* client, - uint64_t token_expiration_epoch_time, - az_span signature, - az_span* out_signature); - -/** - * @brief Gets the MQTT password. - * @note The MQTT password must be an empty string if X509 Client certificates are used. Use this - * API only when authenticating with SAS tokens. - * - * @note This API should be used in conjunction with az_iot_hub_client_sas_get_signature(). - * - * @param[in] client The #az_iot_hub_client to use for this call. - * @param[in] base64_hmac_sha256_signature The Base64 encoded value of the HMAC-SHA256(signature, - * SharedAccessKey). The signature is obtained by using az_iot_hub_client_sas_get_signature(). - * @param[in] token_expiration_epoch_time The time, in seconds, from 1/1/1970. It MUST be the same - * value passed to az_iot_hub_client_sas_get_signature(). - * @param[in] key_name The Shared Access Key Name (Policy Name). This is optional. For security - * reasons we recommend using one key per device instead of using a global policy key. - * @param[out] mqtt_password A char buffer with sufficient capacity to hold the MQTT password. - * @param[in] mqtt_password_size The size, in bytes of \p mqtt_password. - * @param[out] out_mqtt_password_length __[nullable]__ Contains the string length, in bytes, of \p - * mqtt_password. Can be `NULL`. - * @return An #az_result value indicating the result of the operation. - * @retval #AZ_OK The operation was successful. In this case, \p mqtt_password will contain a - * null-terminated string with the password that needs to be passed to the MQTT client. - * @retval #AZ_ERROR_NOT_ENOUGH_SPACE The \p mqtt_password does not have enough size. - */ -AZ_NODISCARD az_result az_iot_hub_client_sas_get_password( - az_iot_hub_client const* client, - uint64_t token_expiration_epoch_time, - az_span base64_hmac_sha256_signature, - az_span key_name, - char* mqtt_password, - size_t mqtt_password_size, - size_t* out_mqtt_password_length); - -/* - * - * Telemetry APIs - * - */ - -/** - * @brief Gets the MQTT topic that must be used for device to cloud telemetry messages. - * @note This topic can also be used to set the MQTT Will message in the Connect message. - * - * @param[in] client The #az_iot_hub_client to use for this call. - * @param[in] properties An optional #az_iot_message_properties object (can be NULL). - * @param[out] mqtt_topic A buffer with sufficient capacity to hold the MQTT topic. If successful, - * contains a null-terminated string with the topic that needs to be passed to the MQTT client. - * @param[in] mqtt_topic_size The size, in bytes of \p mqtt_topic. - * @param[out] out_mqtt_topic_length __[nullable]__ Contains the string length, in bytes, of \p - * mqtt_topic. Can be `NULL`. - * @return An #az_result value indicating the result of the operation. - * @retval #AZ_OK The topic was retrieved successfully. - */ -AZ_NODISCARD az_result az_iot_hub_client_telemetry_get_publish_topic( - az_iot_hub_client const* client, - az_iot_message_properties const* properties, - char* mqtt_topic, - size_t mqtt_topic_size, - size_t* out_mqtt_topic_length); - -/* - * - * Cloud-to-device (C2D) APIs - * - */ - -/** - * @brief The MQTT topic filter to subscribe to Cloud-to-Device requests. - * @note C2D MQTT Publish messages will have QoS At least once (1). - */ -#define AZ_IOT_HUB_CLIENT_C2D_SUBSCRIBE_TOPIC "devices/+/messages/devicebound/#" - -/** - * @brief The Cloud-To-Device Request. - * - */ -typedef struct -{ - /** - * The properties associated with this C2D request. - */ - az_iot_message_properties properties; -} az_iot_hub_client_c2d_request; - -/** - * @brief Attempts to parse a received message's topic for C2D features. - * - * @param[in] client The #az_iot_hub_client to use for this call. - * @param[in] received_topic An #az_span containing the received topic. - * @param[out] out_request If the message is a C2D request, this will contain the - * #az_iot_hub_client_c2d_request - * @return An #az_result value indicating the result of the operation. - * @retval #AZ_OK The topic is meant for this feature and the \p out_request was populated - * with relevant information. - * @retval #AZ_ERROR_IOT_TOPIC_NO_MATCH The topic does not match the expected format. This could - * be due to either a malformed topic OR the message which came in on this topic is not meant for - * this feature. - */ -AZ_NODISCARD az_result az_iot_hub_client_c2d_parse_received_topic( - az_iot_hub_client const* client, - az_span received_topic, - az_iot_hub_client_c2d_request* out_request); - -/* - * - * Methods APIs - * - */ - -/** - * @brief The MQTT topic filter to subscribe to method requests. - * @note Methods MQTT Publish messages will have QoS At most once (0). - */ -#define AZ_IOT_HUB_CLIENT_METHODS_SUBSCRIBE_TOPIC "$iothub/methods/POST/#" - -/** - * @brief A method request received from IoT Hub. - * - */ -typedef struct -{ - /** - * The request ID. - * @note The application must match the method request and method response. - */ - az_span request_id; - - /** - * The method name. - */ - az_span name; -} az_iot_hub_client_method_request; - -/** - * @brief Attempts to parse a received message's topic for method features. - * - * @param[in] client The #az_iot_hub_client to use for this call. - * @param[in] received_topic An #az_span containing the received topic. - * @param[out] out_request If the message is a method request, this will contain the - * #az_iot_hub_client_method_request. - * @return An #az_result value indicating the result of the operation. - * @retval #AZ_OK The topic is meant for this feature and the \p out_request was populated - * with relevant information. - * @retval #AZ_ERROR_IOT_TOPIC_NO_MATCH The topic does not match the expected format. This could - * be due to either a malformed topic OR the message which came in on this topic is not meant for - * this feature. - */ -AZ_NODISCARD az_result az_iot_hub_client_methods_parse_received_topic( - az_iot_hub_client const* client, - az_span received_topic, - az_iot_hub_client_method_request* out_request); - -/** - * @brief Gets the MQTT topic that must be used to respond to method requests. - * - * @param[in] client The #az_iot_hub_client to use for this call. - * @param[in] request_id The request ID. Must match a received #az_iot_hub_client_method_request - * request_id. - * @param[in] status A code that indicates the result of the method, as defined by the user. - * @param[out] mqtt_topic A buffer with sufficient capacity to hold the MQTT topic. If successful, - * contains a null-terminated string with the topic that needs to be passed to the MQTT client. - * @param[in] mqtt_topic_size The size, in bytes of \p mqtt_topic. - * @param[out] out_mqtt_topic_length __[nullable]__ Contains the string length, in bytes, of \p - * mqtt_topic. Can be `NULL`. - * @return An #az_result value indicating the result of the operation. - * @retval #AZ_OK The topic was retrieved successfully. - */ -AZ_NODISCARD az_result az_iot_hub_client_methods_response_get_publish_topic( - az_iot_hub_client const* client, - az_span request_id, - uint16_t status, - char* mqtt_topic, - size_t mqtt_topic_size, - size_t* out_mqtt_topic_length); - -/* - * - * Twin APIs - * - */ - -/** - * @brief The MQTT topic filter to subscribe to twin operation responses. - * @note Twin MQTT Publish messages will have QoS At most once (0). - */ -#define AZ_IOT_HUB_CLIENT_TWIN_RESPONSE_SUBSCRIBE_TOPIC "$iothub/twin/res/#" - -/** - * @brief Gets the MQTT topic filter to subscribe to twin desired property changes. - * @note Twin MQTT Publish messages will have QoS At most once (0). - */ -#define AZ_IOT_HUB_CLIENT_TWIN_PATCH_SUBSCRIBE_TOPIC "$iothub/twin/PATCH/properties/desired/#" - -/** - * @brief Twin response type. - * - */ -typedef enum -{ - AZ_IOT_HUB_CLIENT_TWIN_RESPONSE_TYPE_GET = 1, - AZ_IOT_HUB_CLIENT_TWIN_RESPONSE_TYPE_DESIRED_PROPERTIES = 2, - AZ_IOT_HUB_CLIENT_TWIN_RESPONSE_TYPE_REPORTED_PROPERTIES = 3, -} az_iot_hub_client_twin_response_type; - -/** - * @brief Twin response. - * - */ -typedef struct -{ - /** - * Request ID matches the ID specified when issuing a Get or Patch command. - */ - az_span request_id; - - // Avoid using enum as the first field within structs, to allow for { 0 } initialization. - // This is a workaround for IAR compiler warning [Pe188]: enumerated type mixed with another type. - - /** - * Twin response type. - */ - az_iot_hub_client_twin_response_type response_type; - - /** - * The operation status. - */ - az_iot_status status; - - /** - * The Twin object version. - * @note This is only returned when - * `response_type == AZ_IOT_CLIENT_TWIN_RESPONSE_TYPE_DESIRED_PROPERTIES` - * or - * `response_type == AZ_IOT_CLIENT_TWIN_RESPONSE_TYPE_REPORTED_PROPERTIES`. - */ - az_span version; -} az_iot_hub_client_twin_response; - -/** - * @brief Attempts to parse a received message's topic for twin features. - * - * @param[in] client The #az_iot_hub_client to use for this call. - * @param[in] received_topic An #az_span containing the received topic. - * @param[out] out_response If the message is twin-operation related, this will contain the - * #az_iot_hub_client_twin_response. - * @return An #az_result value indicating the result of the operation. - * @retval #AZ_OK The topic is meant for this feature and the \p out_response was populated - * with relevant information. - * @retval #AZ_ERROR_IOT_TOPIC_NO_MATCH The topic does not match the expected format. This could - * be due to either a malformed topic OR the message which came in on this topic is not meant for - * this feature. - */ -AZ_NODISCARD az_result az_iot_hub_client_twin_parse_received_topic( - az_iot_hub_client const* client, - az_span received_topic, - az_iot_hub_client_twin_response* out_response); - -/** - * @brief Gets the MQTT topic that must be used to submit a Twin GET request. - * @note The payload of the MQTT publish message should be empty. - * - * @param[in] client The #az_iot_hub_client to use for this call. - * @param[in] request_id The request ID. - * @param[out] mqtt_topic A buffer with sufficient capacity to hold the MQTT topic. If successful, - * contains a null-terminated string with the topic that needs to be passed to the MQTT client. - * @param[in] mqtt_topic_size The size, in bytes of \p mqtt_topic. - * @param[out] out_mqtt_topic_length __[nullable]__ Contains the string length, in bytes, of \p - * mqtt_topic. Can be `NULL`. - * @return An #az_result value indicating the result of the operation. - * @retval #AZ_OK The topic was retrieved successfully. - */ -AZ_NODISCARD az_result az_iot_hub_client_twin_document_get_publish_topic( - az_iot_hub_client const* client, - az_span request_id, - char* mqtt_topic, - size_t mqtt_topic_size, - size_t* out_mqtt_topic_length); - -/** - * @brief Gets the MQTT topic that must be used to submit a Twin PATCH request. - * @note The payload of the MQTT publish message should contain a JSON document formatted according - * to the Twin specification. - * - * @param[in] client The #az_iot_hub_client to use for this call. - * @param[in] request_id The request ID. - * @param[out] mqtt_topic A buffer with sufficient capacity to hold the MQTT topic. If successful, - * contains a null-terminated string with the topic that needs to be passed to the MQTT client. - * @param[in] mqtt_topic_size The size, in bytes of \p mqtt_topic. - * @param[out] out_mqtt_topic_length __[nullable]__ Contains the string length, in bytes, of \p - * mqtt_topic. Can be `NULL`. - * @return An #az_result value indicating the result of the operation. - * @retval #AZ_OK The topic was retrieved successfully. - */ -AZ_NODISCARD az_result az_iot_hub_client_twin_patch_get_publish_topic( - az_iot_hub_client const* client, - az_span request_id, - char* mqtt_topic, - size_t mqtt_topic_size, - size_t* out_mqtt_topic_length); - -#include - -#endif // _az_IOT_HUB_CLIENT_H +// Copyright (c) Microsoft Corporation. All rights reserved. +// SPDX-License-Identifier: MIT + +/** + * @file az_iot_hub_client.h + * + * @brief Definition for the Azure IoT Hub client. + * @note The IoT Hub MQTT protocol is described at + * https://docs.microsoft.com/en-us/azure/iot-hub/iot-hub-mqtt-support + * + * @note You MUST NOT use any symbols (macros, functions, structures, enums, etc.) + * prefixed with an underscore ('_') directly in your application code. These symbols + * are part of Azure SDK's internal implementation; we do not document these symbols + * and they are subject to change in future versions of the SDK which would break your code. + */ + +#ifndef _az_IOT_HUB_CLIENT_H +#define _az_IOT_HUB_CLIENT_H + +#include +#include +#include + +#include +#include + +#include + +/** + * @brief Azure IoT service MQTT bit field properties for telemetry publish messages. + * + */ +enum +{ + AZ_HUB_CLIENT_DEFAULT_MQTT_TELEMETRY_QOS = 0 +}; + +/** + * @brief Azure IoT Hub Client options. + * + */ +typedef struct +{ + /** + * The module name (if a module identity is used). + */ + az_span module_id; + + /** + * The user-agent is a formatted string that will be used for Azure IoT usage statistics. + */ + az_span user_agent; + + /** + * The model ID used to identify the capabilities of a device based on the Digital Twin document. + */ + az_span model_id; +} az_iot_hub_client_options; + +/** + * @brief Azure IoT Hub Client. + */ +typedef struct +{ + struct + { + az_span iot_hub_hostname; + az_span device_id; + az_iot_hub_client_options options; + } _internal; +} az_iot_hub_client; + +/** + * @brief Gets the default Azure IoT Hub Client options. + * @details Call this to obtain an initialized #az_iot_hub_client_options structure that can be + * afterwards modified and passed to #az_iot_hub_client_init. + * + * @return #az_iot_hub_client_options. + */ +AZ_NODISCARD az_iot_hub_client_options az_iot_hub_client_options_default(); + +/** + * @brief Initializes an Azure IoT Hub Client. + * + * @param[out] client The #az_iot_hub_client to use for this call. + * @param[in] iot_hub_hostname The IoT Hub Hostname. + * @param[in] device_id The Device ID. If the ID contains any of the following characters, they must + * be percent-encoded as follows: + * - `/` : `%2F` + * - `%` : `%25` + * - `#` : `%23` + * - `&` : `%26` + * @param[in] options A reference to an #az_iot_hub_client_options structure. If `NULL` is passed, + * the hub client will use the default options. If using custom options, please initialize first by + * calling az_iot_hub_client_options_default() and then populating relevant options with your own + * values. + * @return An #az_result value indicating the result of the operation. + */ +AZ_NODISCARD az_result az_iot_hub_client_init( + az_iot_hub_client* client, + az_span iot_hub_hostname, + az_span device_id, + az_iot_hub_client_options const* options); + +/** + * @brief The HTTP URI Path necessary when connecting to IoT Hub using WebSockets. + */ +#define AZ_IOT_HUB_CLIENT_WEB_SOCKET_PATH "/$iothub/websocket" + +/** + * @brief The HTTP URI Path necessary when connecting to IoT Hub using WebSockets without an X509 + * client certificate. + * @note Most devices should use #AZ_IOT_HUB_CLIENT_WEB_SOCKET_PATH. This option is available for + * devices not using X509 client certificates that fail to connect to IoT Hub. + */ +#define AZ_IOT_HUB_CLIENT_WEB_SOCKET_PATH_NO_X509_CLIENT_CERT \ + AZ_IOT_HUB_CLIENT_WEB_SOCKET_PATH "?iothub-no-client-cert=true" + +/** + * @brief Gets the MQTT user name. + * + * The user name will be of the following format: + * + * **Format without module ID** + * + * `{iothubhostname}/{device_id}/?api-version=2018-06-30&{user_agent}` + * + * **Format with module ID** + * + * `{iothubhostname}/{device_id}/{module_id}/?api-version=2018-06-30&{user_agent}` + * + * @param[in] client The #az_iot_hub_client to use for this call. + * @param[out] mqtt_user_name A buffer with sufficient capacity to hold the MQTT user name. If + * successful, contains a null-terminated string with the user name that needs to be passed to the + * MQTT client. + * @param[in] mqtt_user_name_size The size, in bytes of \p mqtt_user_name. + * @param[out] out_mqtt_user_name_length __[nullable]__ Contains the string length, in bytes, of \p + * mqtt_user_name. Can be `NULL`. + * @return An #az_result value indicating the result of the operation. + */ +AZ_NODISCARD az_result az_iot_hub_client_get_user_name( + az_iot_hub_client const* client, + char* mqtt_user_name, + size_t mqtt_user_name_size, + size_t* out_mqtt_user_name_length); + +/** + * @brief Gets the MQTT client ID. + * + * The client ID will be of the following format: + * + * **Format without module ID** + * + * `{device_id}` + * + * **Format with module ID** + * + * `{device_id}/{module_id}` + * + * @param[in] client The #az_iot_hub_client to use for this call. + * @param[out] mqtt_client_id A buffer with sufficient capacity to hold the MQTT client ID. If + * successful, contains a null-terminated string with the client ID that needs to be passed to the + * MQTT client. + * @param[in] mqtt_client_id_size The size, in bytes of \p mqtt_client_id. + * @param[out] out_mqtt_client_id_length __[nullable]__ Contains the string length, in bytes, of + * \p mqtt_client_id. Can be `NULL`. + * @return An #az_result value indicating the result of the operation. + */ +AZ_NODISCARD az_result az_iot_hub_client_get_client_id( + az_iot_hub_client const* client, + char* mqtt_client_id, + size_t mqtt_client_id_size, + size_t* out_mqtt_client_id_length); + +/* + * + * SAS Token APIs + * + * Use the following APIs when the Shared Access Key is available to the application or stored + * within a Hardware Security Module. The APIs are not necessary if X509 Client Certificate + * Authentication is used. + */ + +/** + * @brief Gets the Shared Access clear-text signature. + * @details The application must obtain a valid clear-text signature using this API, sign it using + * HMAC-SHA256 using the Shared Access Key as password then Base64 encode the result. + * + * Use the following APIs when the Shared Access Key is available to the application or stored + * within a Hardware Security Module. The APIs are not necessary if X509 Client Certificate + * Authentication is used. + * + * @note This API should be used in conjunction with az_iot_hub_client_sas_get_password(). + * + * @note More information available at + * https://docs.microsoft.com/en-us/azure/iot-hub/iot-hub-devguide-security#security-tokens + * + * A typical flow for using these two APIs might look something like the following (note the size + * of buffers and non-SDK APIs are for demo purposes only): + * + * @code + * const char* const signature_str = "TST+J9i1F8tE6dLYCtuQcu10u7umGO+aWGqPQhd9AAo="; + * az_span signature = AZ_SPAN_FROM_STR(signature_str); + * az_iot_hub_client_sas_get_signature(&client, expiration_time_in_seconds, signature, &signature); + * + * char decoded_sas_key[128] = { 0 }; + * base64_decode(base64_encoded_sas_key, decoded_sas_key); + * + * char signed_bytes[256] = { 0 }; + * hmac_256(az_span_ptr(signature), az_span_size(signature), decoded_sas_key, signed_bytes); + * + * char signed_bytes_base64_encoded[256] = { 0 }; + * base64_encode(signed_bytes, signed_bytes_base64_encoded); + * + * char final_password[512] = { 0 }; + * az_iot_hub_client_sas_get_password(client, expiration_time_in_seconds, + * AZ_SPAN_FROM_STR(signed_bytes_base64_encoded), final_password, sizeof(final_password), NULL); + * + * mqtt_set_password(&mqtt_client, final_password); + * @endcode + * + * @param[in] client The #az_iot_hub_client to use for this call. + * @param[in] token_expiration_epoch_time The time, in seconds, from 1/1/1970. + * @param[in] signature An empty #az_span with sufficient capacity to hold the SAS signature. + * @param[out] out_signature The output #az_span containing the SAS signature. + * @return An #az_result value indicating the result of the operation. + */ +AZ_NODISCARD az_result az_iot_hub_client_sas_get_signature( + az_iot_hub_client const* client, + uint64_t token_expiration_epoch_time, + az_span signature, + az_span* out_signature); + +/** + * @brief Gets the MQTT password. + * @note The MQTT password must be an empty string if X509 Client certificates are used. Use this + * API only when authenticating with SAS tokens. + * + * @note This API should be used in conjunction with az_iot_hub_client_sas_get_signature(). + * + * @param[in] client The #az_iot_hub_client to use for this call. + * @param[in] base64_hmac_sha256_signature The Base64 encoded value of the HMAC-SHA256(signature, + * SharedAccessKey). The signature is obtained by using az_iot_hub_client_sas_get_signature(). + * @param[in] token_expiration_epoch_time The time, in seconds, from 1/1/1970. It MUST be the same + * value passed to az_iot_hub_client_sas_get_signature(). + * @param[in] key_name The Shared Access Key Name (Policy Name). This is optional. For security + * reasons we recommend using one key per device instead of using a global policy key. + * @param[out] mqtt_password A char buffer with sufficient capacity to hold the MQTT password. + * @param[in] mqtt_password_size The size, in bytes of \p mqtt_password. + * @param[out] out_mqtt_password_length __[nullable]__ Contains the string length, in bytes, of \p + * mqtt_password. Can be `NULL`. + * @return An #az_result value indicating the result of the operation. + * @retval #AZ_OK The operation was successful. In this case, \p mqtt_password will contain a + * null-terminated string with the password that needs to be passed to the MQTT client. + * @retval #AZ_ERROR_NOT_ENOUGH_SPACE The \p mqtt_password does not have enough size. + */ +AZ_NODISCARD az_result az_iot_hub_client_sas_get_password( + az_iot_hub_client const* client, + uint64_t token_expiration_epoch_time, + az_span base64_hmac_sha256_signature, + az_span key_name, + char* mqtt_password, + size_t mqtt_password_size, + size_t* out_mqtt_password_length); + +/* + * + * Telemetry APIs + * + */ + +/** + * @brief Gets the MQTT topic that must be used for device to cloud telemetry messages. + * @note This topic can also be used to set the MQTT Will message in the Connect message. + * + * @param[in] client The #az_iot_hub_client to use for this call. + * @param[in] properties An optional #az_iot_message_properties object (can be NULL). + * @param[out] mqtt_topic A buffer with sufficient capacity to hold the MQTT topic. If successful, + * contains a null-terminated string with the topic that needs to be passed to the MQTT client. + * @param[in] mqtt_topic_size The size, in bytes of \p mqtt_topic. + * @param[out] out_mqtt_topic_length __[nullable]__ Contains the string length, in bytes, of \p + * mqtt_topic. Can be `NULL`. + * @return An #az_result value indicating the result of the operation. + * @retval #AZ_OK The topic was retrieved successfully. + */ +AZ_NODISCARD az_result az_iot_hub_client_telemetry_get_publish_topic( + az_iot_hub_client const* client, + az_iot_message_properties const* properties, + char* mqtt_topic, + size_t mqtt_topic_size, + size_t* out_mqtt_topic_length); + +/* + * + * Cloud-to-device (C2D) APIs + * + */ + +/** + * @brief The MQTT topic filter to subscribe to Cloud-to-Device requests. + * @note C2D MQTT Publish messages will have QoS At least once (1). + */ +#define AZ_IOT_HUB_CLIENT_C2D_SUBSCRIBE_TOPIC "devices/+/messages/devicebound/#" + +/** + * @brief The Cloud-To-Device Request. + * + */ +typedef struct +{ + /** + * The properties associated with this C2D request. + */ + az_iot_message_properties properties; +} az_iot_hub_client_c2d_request; + +/** + * @brief Attempts to parse a received message's topic for C2D features. + * + * @param[in] client The #az_iot_hub_client to use for this call. + * @param[in] received_topic An #az_span containing the received topic. + * @param[out] out_request If the message is a C2D request, this will contain the + * #az_iot_hub_client_c2d_request + * @return An #az_result value indicating the result of the operation. + * @retval #AZ_OK The topic is meant for this feature and the \p out_request was populated + * with relevant information. + * @retval #AZ_ERROR_IOT_TOPIC_NO_MATCH The topic does not match the expected format. This could + * be due to either a malformed topic OR the message which came in on this topic is not meant for + * this feature. + */ +AZ_NODISCARD az_result az_iot_hub_client_c2d_parse_received_topic( + az_iot_hub_client const* client, + az_span received_topic, + az_iot_hub_client_c2d_request* out_request); + +/* + * + * Methods APIs + * + */ + +/** + * @brief The MQTT topic filter to subscribe to method requests. + * @note Methods MQTT Publish messages will have QoS At most once (0). + */ +#define AZ_IOT_HUB_CLIENT_METHODS_SUBSCRIBE_TOPIC "$iothub/methods/POST/#" + +/** + * @brief A method request received from IoT Hub. + * + */ +typedef struct +{ + /** + * The request ID. + * @note The application must match the method request and method response. + */ + az_span request_id; + + /** + * The method name. + */ + az_span name; +} az_iot_hub_client_method_request; + +/** + * @brief Attempts to parse a received message's topic for method features. + * + * @param[in] client The #az_iot_hub_client to use for this call. + * @param[in] received_topic An #az_span containing the received topic. + * @param[out] out_request If the message is a method request, this will contain the + * #az_iot_hub_client_method_request. + * @return An #az_result value indicating the result of the operation. + * @retval #AZ_OK The topic is meant for this feature and the \p out_request was populated + * with relevant information. + * @retval #AZ_ERROR_IOT_TOPIC_NO_MATCH The topic does not match the expected format. This could + * be due to either a malformed topic OR the message which came in on this topic is not meant for + * this feature. + */ +AZ_NODISCARD az_result az_iot_hub_client_methods_parse_received_topic( + az_iot_hub_client const* client, + az_span received_topic, + az_iot_hub_client_method_request* out_request); + +/** + * @brief Gets the MQTT topic that must be used to respond to method requests. + * + * @param[in] client The #az_iot_hub_client to use for this call. + * @param[in] request_id The request ID. Must match a received #az_iot_hub_client_method_request + * request_id. + * @param[in] status A code that indicates the result of the method, as defined by the user. + * @param[out] mqtt_topic A buffer with sufficient capacity to hold the MQTT topic. If successful, + * contains a null-terminated string with the topic that needs to be passed to the MQTT client. + * @param[in] mqtt_topic_size The size, in bytes of \p mqtt_topic. + * @param[out] out_mqtt_topic_length __[nullable]__ Contains the string length, in bytes, of \p + * mqtt_topic. Can be `NULL`. + * @return An #az_result value indicating the result of the operation. + * @retval #AZ_OK The topic was retrieved successfully. + */ +AZ_NODISCARD az_result az_iot_hub_client_methods_response_get_publish_topic( + az_iot_hub_client const* client, + az_span request_id, + uint16_t status, + char* mqtt_topic, + size_t mqtt_topic_size, + size_t* out_mqtt_topic_length); + +/* + * + * Twin APIs + * + */ + +/** + * @brief The MQTT topic filter to subscribe to twin operation responses. + * @note Twin MQTT Publish messages will have QoS At most once (0). + */ +#define AZ_IOT_HUB_CLIENT_TWIN_RESPONSE_SUBSCRIBE_TOPIC "$iothub/twin/res/#" + +/** + * @brief Gets the MQTT topic filter to subscribe to twin desired property changes. + * @note Twin MQTT Publish messages will have QoS At most once (0). + */ +#define AZ_IOT_HUB_CLIENT_TWIN_PATCH_SUBSCRIBE_TOPIC "$iothub/twin/PATCH/properties/desired/#" + +/** + * @brief Twin response type. + * + */ +typedef enum +{ + AZ_IOT_HUB_CLIENT_TWIN_RESPONSE_TYPE_GET = 1, + AZ_IOT_HUB_CLIENT_TWIN_RESPONSE_TYPE_DESIRED_PROPERTIES = 2, + AZ_IOT_HUB_CLIENT_TWIN_RESPONSE_TYPE_REPORTED_PROPERTIES = 3, +} az_iot_hub_client_twin_response_type; + +/** + * @brief Twin response. + * + */ +typedef struct +{ + /** + * Request ID matches the ID specified when issuing a Get or Patch command. + */ + az_span request_id; + + // Avoid using enum as the first field within structs, to allow for { 0 } initialization. + // This is a workaround for IAR compiler warning [Pe188]: enumerated type mixed with another type. + + /** + * Twin response type. + */ + az_iot_hub_client_twin_response_type response_type; + + /** + * The operation status. + */ + az_iot_status status; + + /** + * The Twin object version. + * @note This is only returned when + * `response_type == AZ_IOT_CLIENT_TWIN_RESPONSE_TYPE_DESIRED_PROPERTIES` + * or + * `response_type == AZ_IOT_CLIENT_TWIN_RESPONSE_TYPE_REPORTED_PROPERTIES`. + */ + az_span version; +} az_iot_hub_client_twin_response; + +/** + * @brief Attempts to parse a received message's topic for twin features. + * + * @param[in] client The #az_iot_hub_client to use for this call. + * @param[in] received_topic An #az_span containing the received topic. + * @param[out] out_response If the message is twin-operation related, this will contain the + * #az_iot_hub_client_twin_response. + * @return An #az_result value indicating the result of the operation. + * @retval #AZ_OK The topic is meant for this feature and the \p out_response was populated + * with relevant information. + * @retval #AZ_ERROR_IOT_TOPIC_NO_MATCH The topic does not match the expected format. This could + * be due to either a malformed topic OR the message which came in on this topic is not meant for + * this feature. + */ +AZ_NODISCARD az_result az_iot_hub_client_twin_parse_received_topic( + az_iot_hub_client const* client, + az_span received_topic, + az_iot_hub_client_twin_response* out_response); + +/** + * @brief Gets the MQTT topic that must be used to submit a Twin GET request. + * @note The payload of the MQTT publish message should be empty. + * + * @param[in] client The #az_iot_hub_client to use for this call. + * @param[in] request_id The request ID. + * @param[out] mqtt_topic A buffer with sufficient capacity to hold the MQTT topic. If successful, + * contains a null-terminated string with the topic that needs to be passed to the MQTT client. + * @param[in] mqtt_topic_size The size, in bytes of \p mqtt_topic. + * @param[out] out_mqtt_topic_length __[nullable]__ Contains the string length, in bytes, of \p + * mqtt_topic. Can be `NULL`. + * @return An #az_result value indicating the result of the operation. + * @retval #AZ_OK The topic was retrieved successfully. + */ +AZ_NODISCARD az_result az_iot_hub_client_twin_document_get_publish_topic( + az_iot_hub_client const* client, + az_span request_id, + char* mqtt_topic, + size_t mqtt_topic_size, + size_t* out_mqtt_topic_length); + +/** + * @brief Gets the MQTT topic that must be used to submit a Twin PATCH request. + * @note The payload of the MQTT publish message should contain a JSON document formatted according + * to the Twin specification. + * + * @param[in] client The #az_iot_hub_client to use for this call. + * @param[in] request_id The request ID. + * @param[out] mqtt_topic A buffer with sufficient capacity to hold the MQTT topic. If successful, + * contains a null-terminated string with the topic that needs to be passed to the MQTT client. + * @param[in] mqtt_topic_size The size, in bytes of \p mqtt_topic. + * @param[out] out_mqtt_topic_length __[nullable]__ Contains the string length, in bytes, of \p + * mqtt_topic. Can be `NULL`. + * @return An #az_result value indicating the result of the operation. + * @retval #AZ_OK The topic was retrieved successfully. + */ +AZ_NODISCARD az_result az_iot_hub_client_twin_patch_get_publish_topic( + az_iot_hub_client const* client, + az_span request_id, + char* mqtt_topic, + size_t mqtt_topic_size, + size_t* out_mqtt_topic_length); + +#include + +#endif // _az_IOT_HUB_CLIENT_H diff --git a/sdk/inc/azure/iot/az_iot_provisioning_client.h b/sdk/inc/azure/iot/az_iot_provisioning_client.h index f6dd2fbe09..d965dc1faa 100644 --- a/sdk/inc/azure/iot/az_iot_provisioning_client.h +++ b/sdk/inc/azure/iot/az_iot_provisioning_client.h @@ -1,398 +1,398 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// SPDX-License-Identifier: MIT - -/** - * @file az_iot_provisioning_client.h - * - * @brief Definition for the Azure Device Provisioning client. - * @remark The Device Provisioning MQTT protocol is described at - * https://docs.microsoft.com/en-us/azure/iot-dps/iot-dps-mqtt-support - * - * @note You MUST NOT use any symbols (macros, functions, structures, enums, etc.) - * prefixed with an underscore ('_') directly in your application code. These symbols - * are part of Azure SDK's internal implementation; we do not document these symbols - * and they are subject to change in future versions of the SDK which would break your code. - */ - -#ifndef _az_IOT_PROVISIONING_CLIENT_H -#define _az_IOT_PROVISIONING_CLIENT_H - -#include -#include -#include - -#include -#include - -#include - -/** - * @brief The client is fixed to a specific version of the Azure IoT Provisioning service. - */ -#define AZ_IOT_PROVISIONING_SERVICE_VERSION "2019-03-31" - -/** - * @brief Azure IoT Provisioning Client options. - * - */ -typedef struct -{ - /** - * The user-agent is a formatted string that will be used for Azure IoT usage statistics. - */ - az_span user_agent; -} az_iot_provisioning_client_options; - -/** - * @brief Azure IoT Provisioning Client. - * - */ -typedef struct -{ - struct - { - az_span global_device_endpoint; - az_span id_scope; - az_span registration_id; - az_iot_provisioning_client_options options; - } _internal; -} az_iot_provisioning_client; - -/** - * @brief Gets the default Azure IoT Provisioning Client options. - * - * Call this to obtain an initialized #az_iot_provisioning_client_options structure that - * can be afterwards modified and passed to az_iot_provisioning_client_init(). - * - * @return #az_iot_provisioning_client_options. - */ -AZ_NODISCARD az_iot_provisioning_client_options az_iot_provisioning_client_options_default(); - -/** - * @brief Initializes an Azure IoT Provisioning Client. - * - * @param[in] client The #az_iot_provisioning_client to use for this call. - * @param[in] global_device_hostname The device provisioning services global host name. - * @param[in] id_scope The ID Scope. - * @param[in] registration_id The Registration ID. This must match the client certificate name (CN - * part of the certificate subject). - * @param[in] options __[nullable]__ A reference to an #az_iot_provisioning_client_options - * structure. Can be `NULL` for default options. - * @return An #az_result value indicating the result of the operation. - */ -AZ_NODISCARD az_result az_iot_provisioning_client_init( - az_iot_provisioning_client* client, - az_span global_device_hostname, - az_span id_scope, - az_span registration_id, - az_iot_provisioning_client_options const* options); - -/** - * @brief Gets the MQTT user name. - * - * @param[in] client The #az_iot_provisioning_client to use for this call. - * @param[out] mqtt_user_name A buffer with sufficient capacity to hold the MQTT user name. If - * successful, contains a null-terminated string with the user name that needs to be passed to the - * MQTT client. - * @param[in] mqtt_user_name_size The size, in bytes of \p mqtt_user_name. - * @param[out] out_mqtt_user_name_length __[nullable]__ Contains the string length, in bytes, of \p - * mqtt_user_name. Can be `NULL`. - * @return An #az_result value indicating the result of the operation. - */ -AZ_NODISCARD az_result az_iot_provisioning_client_get_user_name( - az_iot_provisioning_client const* client, - char* mqtt_user_name, - size_t mqtt_user_name_size, - size_t* out_mqtt_user_name_length); - -/** - * @brief Gets the MQTT client id. - * - * @param[in] client The #az_iot_provisioning_client to use for this call. - * @param[out] mqtt_client_id A buffer with sufficient capacity to hold the MQTT client id. If - * successful, contains a null-terminated string with the client id that needs to be passed to the - * MQTT client. - * @param[in] mqtt_client_id_size The size, in bytes of \p mqtt_client_id. - * @param[out] out_mqtt_client_id_length __[nullable]__ Contains the string length, in bytes, of of - * \p mqtt_client_id. Can be `NULL`. - * @return An #az_result value indicating the result of the operation. - */ -AZ_NODISCARD az_result az_iot_provisioning_client_get_client_id( - az_iot_provisioning_client const* client, - char* mqtt_client_id, - size_t mqtt_client_id_size, - size_t* out_mqtt_client_id_length); - -/* - * - * SAS Token APIs - * - * Use the following APIs when the Shared Access Key is available to the application or stored - * within a Hardware Security Module. The APIs are not necessary if X509 Client Certificate - * Authentication is used. - * - * The TPM Asymmetric Device Provisioning protocol is not supported on the MQTT protocol. TPMs can - * still be used to securely store and perform HMAC-SHA256 operations for SAS tokens. - */ - -/** - * @brief Gets the Shared Access clear-text signature. - * - * The application must obtain a valid clear-text signature using this API, sign it using - * HMAC-SHA256 using the Shared Access Key as password then Base64 encode the result. - * - * @remark More information available at - * https://docs.microsoft.com/en-us/azure/iot-dps/concepts-symmetric-key-attestation#detailed-attestation-process - * - * @param[in] client The #az_iot_provisioning_client to use for this call. - * @param[in] token_expiration_epoch_time The time, in seconds, from 1/1/1970. - * @param[in] signature An empty #az_span with sufficient capacity to hold the SAS signature. - * @param[out] out_signature The output #az_span containing the SAS signature. - * @return An #az_result value indicating the result of the operation. - */ -AZ_NODISCARD az_result az_iot_provisioning_client_sas_get_signature( - az_iot_provisioning_client const* client, - uint64_t token_expiration_epoch_time, - az_span signature, - az_span* out_signature); - -/** - * @brief Gets the MQTT password. - * @remark The MQTT password must be an empty string if X509 Client certificates are used. Use this - * API only when authenticating with SAS tokens. - * - * @param[in] client The #az_iot_provisioning_client to use for this call. - * @param[in] base64_hmac_sha256_signature The Base64 encoded value of the HMAC-SHA256(signature, - * SharedAccessKey). The signature is obtained by using - * #az_iot_provisioning_client_sas_get_signature. - * @param[in] token_expiration_epoch_time The time, in seconds, from 1/1/1970. - * @param[in] key_name The Shared Access Key Name (Policy Name). This is optional. For security - * reasons we recommend using one key per device instead of using a global policy key. - * @param[out] mqtt_password A buffer with sufficient capacity to hold the MQTT password. If - * successful, contains a null-terminated string with the password that needs to be passed to the - * MQTT client. - * @param[in] mqtt_password_size The size, in bytes of \p mqtt_password. - * @param[out] out_mqtt_password_length __[nullable]__ Contains the string length, in bytes, of \p - * mqtt_password. Can be `NULL`. - * @return An #az_result value indicating the result of the operation.. - */ -AZ_NODISCARD az_result az_iot_provisioning_client_sas_get_password( - az_iot_provisioning_client const* client, - az_span base64_hmac_sha256_signature, - uint64_t token_expiration_epoch_time, - az_span key_name, - char* mqtt_password, - size_t mqtt_password_size, - size_t* out_mqtt_password_length); - -/* - * - * Register APIs - * - * Use the following APIs when the Shared Access Key is available to the application or stored - * within a Hardware Security Module. The APIs are not necessary if X509 Client Certificate - * Authentication is used. - */ - -/** - * @brief The MQTT topic filter to subscribe to register responses. - * @remark Register MQTT Publish messages will have QoS At most once (0). - */ -#define AZ_IOT_PROVISIONING_CLIENT_REGISTER_SUBSCRIBE_TOPIC "$dps/registrations/res/#" - -/** - * @brief The registration operation state. - * @remark This is returned only when the operation completed. - * - */ -typedef struct -{ - /** - * Assigned Azure IoT Hub hostname. - * @remark This is only available if `error_code` is success. - */ - az_span assigned_hub_hostname; - - /** - * Assigned device ID. - */ - az_span device_id; - - /** - * The error code. - */ - az_iot_status error_code; - - /** - * The extended, 6 digit error code. - */ - uint32_t extended_error_code; - - /** - * Error description. - */ - az_span error_message; - - /** - * Submit this ID when asking for Azure IoT service-desk help. - */ - az_span error_tracking_id; - - /** - * Submit this timestamp when asking for Azure IoT service-desk help. - */ - az_span error_timestamp; -} az_iot_provisioning_client_registration_state; - -/** - * @brief Azure IoT Provisioning Service operation status. - * - */ -typedef enum -{ - /** - * Starting state (not assigned). - */ - AZ_IOT_PROVISIONING_STATUS_UNASSIGNED, - /** - * Assigning in progress. - */ - AZ_IOT_PROVISIONING_STATUS_ASSIGNING, - - // Device assignment operation complete. - /** - * Device was assigned successfully. - */ - AZ_IOT_PROVISIONING_STATUS_ASSIGNED, - - /** - * The provisioning for the device failed. - */ - AZ_IOT_PROVISIONING_STATUS_FAILED, - - /** - * The provisioning for this device was disabled. - */ - AZ_IOT_PROVISIONING_STATUS_DISABLED, -} az_iot_provisioning_client_operation_status; - -/** - * @brief Register or query operation response. - * - */ -typedef struct -{ - /** - * The id of the register operation. - */ - az_span operation_id; - - // Avoid using enum as the first field within structs, to allow for { 0 } initialization. - // This is a workaround for IAR compiler warning [Pe188]: enumerated type mixed with another type. - - /** - * The current request status. - * @remark The authoritative response for the device registration operation (which may require - * several requests) is available only through #operation_status. - */ - az_iot_status status; - - /** - * The status of the register operation. - */ - az_iot_provisioning_client_operation_status operation_status; - - /** - * Recommended timeout before sending the next MQTT publish. - */ - uint32_t retry_after_seconds; - - /** - * If the operation is complete (success or error), the registration state will contain the hub - * and device id in case of success. - */ - az_iot_provisioning_client_registration_state registration_state; -} az_iot_provisioning_client_register_response; - -/** - * @brief Attempts to parse a received message's topic. - * - * @param[in] client The #az_iot_provisioning_client to use for this call. - * @param[in] received_topic An #az_span containing the received MQTT topic. - * @param[in] received_payload An #az_span containing the received MQTT payload. - * @param[out] out_response If the message is register-operation related, this will contain the - * #az_iot_provisioning_client_register_response. - * @return An #az_result value indicating the result of the operation. - * @retval #AZ_ERROR_IOT_TOPIC_NO_MATCH If the topic is not matching the expected format. - */ -AZ_NODISCARD az_result az_iot_provisioning_client_parse_received_topic_and_payload( - az_iot_provisioning_client const* client, - az_span received_topic, - az_span received_payload, - az_iot_provisioning_client_register_response* out_response); - -/** - * @brief Checks if the status indicates that the service has an authoritative result of the - * register operation. The operation may have completed in either success or error. Completed - * states are: - * - * - #AZ_IOT_PROVISIONING_STATUS_ASSIGNED - * - #AZ_IOT_PROVISIONING_STATUS_FAILED - * - #AZ_IOT_PROVISIONING_STATUS_DISABLED - * - * @param[in] operation_status The status used to check if the operation completed. - * @return `true` if the operation completed. `false` otherwise. - */ -AZ_INLINE bool az_iot_provisioning_client_operation_complete( - az_iot_provisioning_client_operation_status operation_status) -{ - return (operation_status > AZ_IOT_PROVISIONING_STATUS_ASSIGNING); -} - -/** - * @brief Gets the MQTT topic that must be used to submit a Register request. - * @remark The payload of the MQTT publish message may contain a JSON document formatted according - * to the [Provisioning Service's Device Registration document] - * (https://docs.microsoft.com/en-us/rest/api/iot-dps/runtimeregistration/registerdevice#deviceregistration) - * specification. - * - * @param[in] client The #az_iot_provisioning_client to use for this call. - * @param[out] mqtt_topic A buffer with sufficient capacity to hold the MQTT topic filter. If - * successful, contains a null-terminated string with the topic filter that needs to be passed to - * the MQTT client. - * @param[in] mqtt_topic_size The size, in bytes of \p mqtt_topic. - * @param[out] out_mqtt_topic_length __[nullable]__ Contains the string length, in bytes, of \p - * mqtt_topic. Can be `NULL`. - * @return An #az_result value indicating the result of the operation. - */ -AZ_NODISCARD az_result az_iot_provisioning_client_register_get_publish_topic( - az_iot_provisioning_client const* client, - char* mqtt_topic, - size_t mqtt_topic_size, - size_t* out_mqtt_topic_length); - -/** - * @brief Gets the MQTT topic that must be used to submit a Register Status request. - * @remark The payload of the MQTT publish message should be empty. - * - * @param[in] client The #az_iot_provisioning_client to use for this call. - * @param[in] operation_id The received operation_id from the - * #az_iot_provisioning_client_register_response response. - * @param[out] mqtt_topic A buffer with sufficient capacity to hold the MQTT topic filter. If - * successful, contains a null-terminated string with the topic filter that needs to be passed to - * the MQTT client. - * @param[in] mqtt_topic_size The size, in bytes of \p mqtt_topic. - * @param[out] out_mqtt_topic_length __[nullable]__ Contains the string length, in bytes, of \p - * mqtt_topic. Can be `NULL`. - * @return An #az_result value indicating the result of the operation. - */ -AZ_NODISCARD az_result az_iot_provisioning_client_query_status_get_publish_topic( - az_iot_provisioning_client const* client, - az_span operation_id, - char* mqtt_topic, - size_t mqtt_topic_size, - size_t* out_mqtt_topic_length); - -#include - -#endif // _az_IOT_PROVISIONING_CLIENT_H +// Copyright (c) Microsoft Corporation. All rights reserved. +// SPDX-License-Identifier: MIT + +/** + * @file az_iot_provisioning_client.h + * + * @brief Definition for the Azure Device Provisioning client. + * @remark The Device Provisioning MQTT protocol is described at + * https://docs.microsoft.com/en-us/azure/iot-dps/iot-dps-mqtt-support + * + * @note You MUST NOT use any symbols (macros, functions, structures, enums, etc.) + * prefixed with an underscore ('_') directly in your application code. These symbols + * are part of Azure SDK's internal implementation; we do not document these symbols + * and they are subject to change in future versions of the SDK which would break your code. + */ + +#ifndef _az_IOT_PROVISIONING_CLIENT_H +#define _az_IOT_PROVISIONING_CLIENT_H + +#include +#include +#include + +#include +#include + +#include + +/** + * @brief The client is fixed to a specific version of the Azure IoT Provisioning service. + */ +#define AZ_IOT_PROVISIONING_SERVICE_VERSION "2019-03-31" + +/** + * @brief Azure IoT Provisioning Client options. + * + */ +typedef struct +{ + /** + * The user-agent is a formatted string that will be used for Azure IoT usage statistics. + */ + az_span user_agent; +} az_iot_provisioning_client_options; + +/** + * @brief Azure IoT Provisioning Client. + * + */ +typedef struct +{ + struct + { + az_span global_device_endpoint; + az_span id_scope; + az_span registration_id; + az_iot_provisioning_client_options options; + } _internal; +} az_iot_provisioning_client; + +/** + * @brief Gets the default Azure IoT Provisioning Client options. + * + * Call this to obtain an initialized #az_iot_provisioning_client_options structure that + * can be afterwards modified and passed to az_iot_provisioning_client_init(). + * + * @return #az_iot_provisioning_client_options. + */ +AZ_NODISCARD az_iot_provisioning_client_options az_iot_provisioning_client_options_default(); + +/** + * @brief Initializes an Azure IoT Provisioning Client. + * + * @param[in] client The #az_iot_provisioning_client to use for this call. + * @param[in] global_device_hostname The device provisioning services global host name. + * @param[in] id_scope The ID Scope. + * @param[in] registration_id The Registration ID. This must match the client certificate name (CN + * part of the certificate subject). + * @param[in] options __[nullable]__ A reference to an #az_iot_provisioning_client_options + * structure. Can be `NULL` for default options. + * @return An #az_result value indicating the result of the operation. + */ +AZ_NODISCARD az_result az_iot_provisioning_client_init( + az_iot_provisioning_client* client, + az_span global_device_hostname, + az_span id_scope, + az_span registration_id, + az_iot_provisioning_client_options const* options); + +/** + * @brief Gets the MQTT user name. + * + * @param[in] client The #az_iot_provisioning_client to use for this call. + * @param[out] mqtt_user_name A buffer with sufficient capacity to hold the MQTT user name. If + * successful, contains a null-terminated string with the user name that needs to be passed to the + * MQTT client. + * @param[in] mqtt_user_name_size The size, in bytes of \p mqtt_user_name. + * @param[out] out_mqtt_user_name_length __[nullable]__ Contains the string length, in bytes, of \p + * mqtt_user_name. Can be `NULL`. + * @return An #az_result value indicating the result of the operation. + */ +AZ_NODISCARD az_result az_iot_provisioning_client_get_user_name( + az_iot_provisioning_client const* client, + char* mqtt_user_name, + size_t mqtt_user_name_size, + size_t* out_mqtt_user_name_length); + +/** + * @brief Gets the MQTT client id. + * + * @param[in] client The #az_iot_provisioning_client to use for this call. + * @param[out] mqtt_client_id A buffer with sufficient capacity to hold the MQTT client id. If + * successful, contains a null-terminated string with the client id that needs to be passed to the + * MQTT client. + * @param[in] mqtt_client_id_size The size, in bytes of \p mqtt_client_id. + * @param[out] out_mqtt_client_id_length __[nullable]__ Contains the string length, in bytes, of of + * \p mqtt_client_id. Can be `NULL`. + * @return An #az_result value indicating the result of the operation. + */ +AZ_NODISCARD az_result az_iot_provisioning_client_get_client_id( + az_iot_provisioning_client const* client, + char* mqtt_client_id, + size_t mqtt_client_id_size, + size_t* out_mqtt_client_id_length); + +/* + * + * SAS Token APIs + * + * Use the following APIs when the Shared Access Key is available to the application or stored + * within a Hardware Security Module. The APIs are not necessary if X509 Client Certificate + * Authentication is used. + * + * The TPM Asymmetric Device Provisioning protocol is not supported on the MQTT protocol. TPMs can + * still be used to securely store and perform HMAC-SHA256 operations for SAS tokens. + */ + +/** + * @brief Gets the Shared Access clear-text signature. + * + * The application must obtain a valid clear-text signature using this API, sign it using + * HMAC-SHA256 using the Shared Access Key as password then Base64 encode the result. + * + * @remark More information available at + * https://docs.microsoft.com/en-us/azure/iot-dps/concepts-symmetric-key-attestation#detailed-attestation-process + * + * @param[in] client The #az_iot_provisioning_client to use for this call. + * @param[in] token_expiration_epoch_time The time, in seconds, from 1/1/1970. + * @param[in] signature An empty #az_span with sufficient capacity to hold the SAS signature. + * @param[out] out_signature The output #az_span containing the SAS signature. + * @return An #az_result value indicating the result of the operation. + */ +AZ_NODISCARD az_result az_iot_provisioning_client_sas_get_signature( + az_iot_provisioning_client const* client, + uint64_t token_expiration_epoch_time, + az_span signature, + az_span* out_signature); + +/** + * @brief Gets the MQTT password. + * @remark The MQTT password must be an empty string if X509 Client certificates are used. Use this + * API only when authenticating with SAS tokens. + * + * @param[in] client The #az_iot_provisioning_client to use for this call. + * @param[in] base64_hmac_sha256_signature The Base64 encoded value of the HMAC-SHA256(signature, + * SharedAccessKey). The signature is obtained by using + * #az_iot_provisioning_client_sas_get_signature. + * @param[in] token_expiration_epoch_time The time, in seconds, from 1/1/1970. + * @param[in] key_name The Shared Access Key Name (Policy Name). This is optional. For security + * reasons we recommend using one key per device instead of using a global policy key. + * @param[out] mqtt_password A buffer with sufficient capacity to hold the MQTT password. If + * successful, contains a null-terminated string with the password that needs to be passed to the + * MQTT client. + * @param[in] mqtt_password_size The size, in bytes of \p mqtt_password. + * @param[out] out_mqtt_password_length __[nullable]__ Contains the string length, in bytes, of \p + * mqtt_password. Can be `NULL`. + * @return An #az_result value indicating the result of the operation.. + */ +AZ_NODISCARD az_result az_iot_provisioning_client_sas_get_password( + az_iot_provisioning_client const* client, + az_span base64_hmac_sha256_signature, + uint64_t token_expiration_epoch_time, + az_span key_name, + char* mqtt_password, + size_t mqtt_password_size, + size_t* out_mqtt_password_length); + +/* + * + * Register APIs + * + * Use the following APIs when the Shared Access Key is available to the application or stored + * within a Hardware Security Module. The APIs are not necessary if X509 Client Certificate + * Authentication is used. + */ + +/** + * @brief The MQTT topic filter to subscribe to register responses. + * @remark Register MQTT Publish messages will have QoS At most once (0). + */ +#define AZ_IOT_PROVISIONING_CLIENT_REGISTER_SUBSCRIBE_TOPIC "$dps/registrations/res/#" + +/** + * @brief The registration operation state. + * @remark This is returned only when the operation completed. + * + */ +typedef struct +{ + /** + * Assigned Azure IoT Hub hostname. + * @remark This is only available if `error_code` is success. + */ + az_span assigned_hub_hostname; + + /** + * Assigned device ID. + */ + az_span device_id; + + /** + * The error code. + */ + az_iot_status error_code; + + /** + * The extended, 6 digit error code. + */ + uint32_t extended_error_code; + + /** + * Error description. + */ + az_span error_message; + + /** + * Submit this ID when asking for Azure IoT service-desk help. + */ + az_span error_tracking_id; + + /** + * Submit this timestamp when asking for Azure IoT service-desk help. + */ + az_span error_timestamp; +} az_iot_provisioning_client_registration_state; + +/** + * @brief Azure IoT Provisioning Service operation status. + * + */ +typedef enum +{ + /** + * Starting state (not assigned). + */ + AZ_IOT_PROVISIONING_STATUS_UNASSIGNED, + /** + * Assigning in progress. + */ + AZ_IOT_PROVISIONING_STATUS_ASSIGNING, + + // Device assignment operation complete. + /** + * Device was assigned successfully. + */ + AZ_IOT_PROVISIONING_STATUS_ASSIGNED, + + /** + * The provisioning for the device failed. + */ + AZ_IOT_PROVISIONING_STATUS_FAILED, + + /** + * The provisioning for this device was disabled. + */ + AZ_IOT_PROVISIONING_STATUS_DISABLED, +} az_iot_provisioning_client_operation_status; + +/** + * @brief Register or query operation response. + * + */ +typedef struct +{ + /** + * The id of the register operation. + */ + az_span operation_id; + + // Avoid using enum as the first field within structs, to allow for { 0 } initialization. + // This is a workaround for IAR compiler warning [Pe188]: enumerated type mixed with another type. + + /** + * The current request status. + * @remark The authoritative response for the device registration operation (which may require + * several requests) is available only through #operation_status. + */ + az_iot_status status; + + /** + * The status of the register operation. + */ + az_iot_provisioning_client_operation_status operation_status; + + /** + * Recommended timeout before sending the next MQTT publish. + */ + uint32_t retry_after_seconds; + + /** + * If the operation is complete (success or error), the registration state will contain the hub + * and device id in case of success. + */ + az_iot_provisioning_client_registration_state registration_state; +} az_iot_provisioning_client_register_response; + +/** + * @brief Attempts to parse a received message's topic. + * + * @param[in] client The #az_iot_provisioning_client to use for this call. + * @param[in] received_topic An #az_span containing the received MQTT topic. + * @param[in] received_payload An #az_span containing the received MQTT payload. + * @param[out] out_response If the message is register-operation related, this will contain the + * #az_iot_provisioning_client_register_response. + * @return An #az_result value indicating the result of the operation. + * @retval #AZ_ERROR_IOT_TOPIC_NO_MATCH If the topic is not matching the expected format. + */ +AZ_NODISCARD az_result az_iot_provisioning_client_parse_received_topic_and_payload( + az_iot_provisioning_client const* client, + az_span received_topic, + az_span received_payload, + az_iot_provisioning_client_register_response* out_response); + +/** + * @brief Checks if the status indicates that the service has an authoritative result of the + * register operation. The operation may have completed in either success or error. Completed + * states are: + * + * - #AZ_IOT_PROVISIONING_STATUS_ASSIGNED + * - #AZ_IOT_PROVISIONING_STATUS_FAILED + * - #AZ_IOT_PROVISIONING_STATUS_DISABLED + * + * @param[in] operation_status The status used to check if the operation completed. + * @return `true` if the operation completed. `false` otherwise. + */ +AZ_INLINE bool az_iot_provisioning_client_operation_complete( + az_iot_provisioning_client_operation_status operation_status) +{ + return (operation_status > AZ_IOT_PROVISIONING_STATUS_ASSIGNING); +} + +/** + * @brief Gets the MQTT topic that must be used to submit a Register request. + * @remark The payload of the MQTT publish message may contain a JSON document formatted according + * to the [Provisioning Service's Device Registration document] + * (https://docs.microsoft.com/en-us/rest/api/iot-dps/runtimeregistration/registerdevice#deviceregistration) + * specification. + * + * @param[in] client The #az_iot_provisioning_client to use for this call. + * @param[out] mqtt_topic A buffer with sufficient capacity to hold the MQTT topic filter. If + * successful, contains a null-terminated string with the topic filter that needs to be passed to + * the MQTT client. + * @param[in] mqtt_topic_size The size, in bytes of \p mqtt_topic. + * @param[out] out_mqtt_topic_length __[nullable]__ Contains the string length, in bytes, of \p + * mqtt_topic. Can be `NULL`. + * @return An #az_result value indicating the result of the operation. + */ +AZ_NODISCARD az_result az_iot_provisioning_client_register_get_publish_topic( + az_iot_provisioning_client const* client, + char* mqtt_topic, + size_t mqtt_topic_size, + size_t* out_mqtt_topic_length); + +/** + * @brief Gets the MQTT topic that must be used to submit a Register Status request. + * @remark The payload of the MQTT publish message should be empty. + * + * @param[in] client The #az_iot_provisioning_client to use for this call. + * @param[in] operation_id The received operation_id from the + * #az_iot_provisioning_client_register_response response. + * @param[out] mqtt_topic A buffer with sufficient capacity to hold the MQTT topic filter. If + * successful, contains a null-terminated string with the topic filter that needs to be passed to + * the MQTT client. + * @param[in] mqtt_topic_size The size, in bytes of \p mqtt_topic. + * @param[out] out_mqtt_topic_length __[nullable]__ Contains the string length, in bytes, of \p + * mqtt_topic. Can be `NULL`. + * @return An #az_result value indicating the result of the operation. + */ +AZ_NODISCARD az_result az_iot_provisioning_client_query_status_get_publish_topic( + az_iot_provisioning_client const* client, + az_span operation_id, + char* mqtt_topic, + size_t mqtt_topic_size, + size_t* out_mqtt_topic_length); + +#include + +#endif // _az_IOT_PROVISIONING_CLIENT_H diff --git a/sdk/samples/iot/iot_sample_common.c b/sdk/samples/iot/iot_sample_common.c index fb3d543bbf..f0850a7f6b 100644 --- a/sdk/samples/iot/iot_sample_common.c +++ b/sdk/samples/iot/iot_sample_common.c @@ -1,551 +1,551 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// SPDX-License-Identifier: MIT - -#ifdef _MSC_VER -// warning C4996: 'getenv': This function or variable may be unsafe. Consider using _dupenv_s -// instead. -#pragma warning(disable : 4996) -#endif - -#ifdef _WIN32 -// Required for Sleep(DWORD) -#include -#else -// Required for sleep(unsigned int) -#include -#endif - -#include "iot_sample_common.h" - -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#include -#include -#include -#include - -#define IOT_SAMPLE_PRECONDITION_NOT_NULL(arg) \ - do \ - { \ - if ((arg) == NULL) \ - { \ - IOT_SAMPLE_LOG_ERROR("Pointer is NULL."); \ - exit(1); \ - } \ - } while (0) - -// -// MQTT endpoints -// -//#define USE_WEB_SOCKET // Comment to use MQTT without WebSockets. -#ifdef USE_WEB_SOCKET -static az_span const mqtt_url_prefix = AZ_SPAN_LITERAL_FROM_STR("wss://"); -// Note: Paho fails to connect to Hub when using AZ_IOT_HUB_CLIENT_WEB_SOCKET_PATH or an X509 -// certificate. -static az_span const mqtt_url_suffix - = AZ_SPAN_LITERAL_FROM_STR(":443" AZ_IOT_HUB_CLIENT_WEB_SOCKET_PATH_NO_X509_CLIENT_CERT); -#else -static az_span const mqtt_url_prefix = AZ_SPAN_LITERAL_FROM_STR("ssl://"); -static az_span const mqtt_url_suffix = AZ_SPAN_LITERAL_FROM_STR(":8883"); -#endif -static az_span const provisioning_global_endpoint - = AZ_SPAN_LITERAL_FROM_STR("ssl://global.azure-devices-provisioning.net:8883"); - -// -// Functions -// -void build_error_message(char* out_full_message, char const* const error_message, ...) -{ - char const* const append_message = ": az_result return code 0x%08x."; - - strcpy(out_full_message, error_message); - strcat(out_full_message, append_message); -} - -bool get_az_span(az_span* out_span, char const* const error_message, ...) -{ - va_list args; - va_start(args, error_message); - - *out_span = va_arg(args, az_span); - va_end(args); - - if (az_span_size(*out_span) == 0) // There was no span - { - return false; - } - - return true; -} - -static void read_configuration_entry( - char const* env_name, - char* default_value, - bool show_value, - az_span destination, - az_span* out_env_value) -{ - char* env_value = getenv(env_name); - - if (env_value == NULL && default_value != NULL) - { - env_value = default_value; - } - - if (env_value != NULL) - { - (void)printf("%s = %s\n", env_name, show_value ? env_value : "***"); - az_span env_span = az_span_create_from_str(env_value); - - // Check the buffer is large enough to store the environment variable. - if ((az_span_size(destination) < az_span_size(env_span)) || (az_span_size(env_span) < 0)) - { - IOT_SAMPLE_LOG_ERROR( - "Failed to read configuration from environment variables: Buffer is too small."); - exit(1); - } - - az_span_copy(destination, env_span); - *out_env_value = az_span_slice(destination, 0, az_span_size(env_span)); - } - else - { - IOT_SAMPLE_LOG_ERROR( - "Failed to read configuration from environment variables: Environment variable %s not set.", - env_name); - exit(1); - } -} - -void iot_sample_read_environment_variables( - iot_sample_type type, - iot_sample_name name, - iot_sample_environment_variables* out_env_vars) -{ - IOT_SAMPLE_PRECONDITION_NOT_NULL(out_env_vars); - - bool show_value = true; - - if (type == PAHO_IOT_HUB) - { - out_env_vars->hub_hostname = AZ_SPAN_FROM_BUFFER(iot_sample_hub_hostname_buffer); - read_configuration_entry( - IOT_SAMPLE_ENV_HUB_HOSTNAME, - NULL, - show_value, - out_env_vars->hub_hostname, - &(out_env_vars->hub_hostname)); - - switch (name) - { - case PAHO_IOT_HUB_C2D_SAMPLE: - case PAHO_IOT_HUB_METHODS_SAMPLE: - case PAHO_IOT_HUB_PNP_COMPONENT_SAMPLE: - case PAHO_IOT_HUB_PNP_SAMPLE: - case PAHO_IOT_HUB_TELEMETRY_SAMPLE: - case PAHO_IOT_HUB_TWIN_SAMPLE: - out_env_vars->hub_device_id = AZ_SPAN_FROM_BUFFER(iot_sample_hub_device_id_buffer); - read_configuration_entry( - IOT_SAMPLE_ENV_HUB_DEVICE_ID, - NULL, - show_value, - out_env_vars->hub_device_id, - &(out_env_vars->hub_device_id)); - - out_env_vars->x509_cert_pem_file_path - = AZ_SPAN_FROM_BUFFER(iot_sample_x509_cert_pem_file_path_buffer); - read_configuration_entry( - IOT_SAMPLE_ENV_DEVICE_X509_CERT_PEM_FILE_PATH, - NULL, - show_value, - out_env_vars->x509_cert_pem_file_path, - &(out_env_vars->x509_cert_pem_file_path)); - break; - - case PAHO_IOT_HUB_SAS_TELEMETRY_SAMPLE: - out_env_vars->hub_device_id = AZ_SPAN_FROM_BUFFER(iot_sample_hub_device_id_buffer); - read_configuration_entry( - IOT_SAMPLE_ENV_HUB_SAS_DEVICE_ID, - NULL, - show_value, - out_env_vars->hub_device_id, - &(out_env_vars->hub_device_id)); - - out_env_vars->hub_sas_key = AZ_SPAN_FROM_BUFFER(iot_sample_hub_sas_key_buffer); - read_configuration_entry( - IOT_SAMPLE_ENV_HUB_SAS_KEY, - NULL, - !show_value, - out_env_vars->hub_sas_key, - &(out_env_vars->hub_sas_key)); - - char duration_buffer[IOT_SAMPLE_SAS_KEY_DURATION_TIME_DIGITS]; - az_span duration = AZ_SPAN_FROM_BUFFER(duration_buffer); - read_configuration_entry( - IOT_SAMPLE_ENV_SAS_KEY_DURATION_MINUTES, "120", show_value, duration, &duration); - - az_result rc = az_span_atou32(duration, &(out_env_vars->sas_key_duration_minutes)); - if (az_result_failed(rc)) - { - IOT_SAMPLE_LOG_ERROR( - "Failed to read environment variables: az_result return code 0x%08x.", rc); - exit(rc); - } - break; - - default: - IOT_SAMPLE_LOG_ERROR("Failed to read environment variables: Hub sample name undefined."); - exit(1); - } - } - else if (type == PAHO_IOT_PROVISIONING) - { - out_env_vars->provisioning_id_scope - = AZ_SPAN_FROM_BUFFER(iot_sample_provisioning_id_scope_buffer); - read_configuration_entry( - IOT_SAMPLE_ENV_PROVISIONING_ID_SCOPE, - NULL, - show_value, - out_env_vars->provisioning_id_scope, - &(out_env_vars->provisioning_id_scope)); - - switch (name) - { - case PAHO_IOT_PROVISIONING_SAMPLE: - out_env_vars->provisioning_registration_id - = AZ_SPAN_FROM_BUFFER(iot_sample_provisioning_registration_id_buffer); - read_configuration_entry( - IOT_SAMPLE_ENV_PROVISIONING_REGISTRATION_ID, - NULL, - show_value, - out_env_vars->provisioning_registration_id, - &(out_env_vars->provisioning_registration_id)); - - out_env_vars->x509_cert_pem_file_path - = AZ_SPAN_FROM_BUFFER(iot_sample_x509_cert_pem_file_path_buffer); - read_configuration_entry( - IOT_SAMPLE_ENV_DEVICE_X509_CERT_PEM_FILE_PATH, - NULL, - show_value, - out_env_vars->x509_cert_pem_file_path, - &(out_env_vars->x509_cert_pem_file_path)); - break; - - case PAHO_IOT_PROVISIONING_SAS_SAMPLE: - out_env_vars->provisioning_registration_id - = AZ_SPAN_FROM_BUFFER(iot_sample_provisioning_registration_id_buffer); - read_configuration_entry( - IOT_SAMPLE_ENV_PROVISIONING_SAS_REGISTRATION_ID, - NULL, - show_value, - out_env_vars->provisioning_registration_id, - &(out_env_vars->provisioning_registration_id)); - - out_env_vars->provisioning_sas_key - = AZ_SPAN_FROM_BUFFER(iot_sample_provisioning_sas_key_buffer); - read_configuration_entry( - IOT_SAMPLE_ENV_PROVISIONING_SAS_KEY, - NULL, - !show_value, - out_env_vars->provisioning_sas_key, - &(out_env_vars->provisioning_sas_key)); - - char duration_buffer[IOT_SAMPLE_SAS_KEY_DURATION_TIME_DIGITS]; - az_span duration = AZ_SPAN_FROM_BUFFER(duration_buffer); - read_configuration_entry( - IOT_SAMPLE_ENV_SAS_KEY_DURATION_MINUTES, "120", show_value, duration, &duration); - - az_result rc = az_span_atou32(duration, &(out_env_vars->sas_key_duration_minutes)); - if (az_result_failed(rc)) - { - IOT_SAMPLE_LOG_ERROR( - "Failed to read environment variables: az_result return code 0x%08x.", rc); - exit(rc); - } - break; - - default: - IOT_SAMPLE_LOG_ERROR( - "Failed to read environment variables: Provisioning sample name undefined."); - exit(1); - } - } - else - { - IOT_SAMPLE_LOG_ERROR("Failed to read environment variables: Sample type undefined."); - exit(1); - } - - out_env_vars->x509_trust_pem_file_path - = AZ_SPAN_FROM_BUFFER(iot_sample_x509_trust_pem_file_path_buffer); - read_configuration_entry( - IOT_SAMPLE_ENV_DEVICE_X509_TRUST_PEM_FILE_PATH, - "", - show_value, - out_env_vars->x509_trust_pem_file_path, - &(out_env_vars->x509_trust_pem_file_path)); - - IOT_SAMPLE_LOG(" "); // Formatting -} - -void iot_sample_create_mqtt_endpoint( - iot_sample_type type, - iot_sample_environment_variables const* env_vars, - char* out_endpoint, - size_t endpoint_size) -{ - IOT_SAMPLE_PRECONDITION_NOT_NULL(env_vars); - IOT_SAMPLE_PRECONDITION_NOT_NULL(out_endpoint); - - if (type == PAHO_IOT_HUB) - { - int32_t const required_size = az_span_size(mqtt_url_prefix) - + az_span_size(env_vars->hub_hostname) + az_span_size(mqtt_url_suffix) - + (int32_t)sizeof('\0'); - - if ((size_t)required_size > endpoint_size) - { - IOT_SAMPLE_LOG_ERROR("Failed to create MQTT endpoint: Buffer is too small."); - exit(1); - } - - az_span hub_mqtt_endpoint = az_span_create((uint8_t*)out_endpoint, (int32_t)endpoint_size); - az_span remainder = az_span_copy(hub_mqtt_endpoint, mqtt_url_prefix); - remainder = az_span_copy(remainder, env_vars->hub_hostname); - remainder = az_span_copy(remainder, mqtt_url_suffix); - az_span_copy_u8(remainder, '\0'); - } - else if (type == PAHO_IOT_PROVISIONING) - { - int32_t const required_size - = az_span_size(provisioning_global_endpoint) + (int32_t)sizeof('\0'); - - if ((size_t)required_size > endpoint_size) - { - IOT_SAMPLE_LOG_ERROR("Failed to create MQTT endpoint: Buffer is too small."); - exit(1); - } - - az_span provisioning_mqtt_endpoint - = az_span_create((uint8_t*)out_endpoint, (int32_t)endpoint_size); - az_span remainder = az_span_copy(provisioning_mqtt_endpoint, provisioning_global_endpoint); - az_span_copy_u8(remainder, '\0'); - } - else - { - IOT_SAMPLE_LOG_ERROR("Failed to create MQTT endpoint: Sample type undefined."); - exit(1); - } - - IOT_SAMPLE_LOG_SUCCESS("MQTT endpoint created at \"%s\".", out_endpoint); -} - -void iot_sample_sleep_for_seconds(uint32_t seconds) -{ -#ifdef _WIN32 - Sleep((DWORD)seconds * 1000); -#else - sleep(seconds); -#endif -} - -uint32_t iot_sample_get_epoch_expiration_time_from_minutes(uint32_t minutes) -{ - return (uint32_t)(time(NULL) + minutes * 60); -} - -static void decode_base64_bytes( - az_span base64_encoded_bytes, - az_span decoded_bytes, - az_span* out_decoded_bytes) -{ - BIO* base64_decoder; - BIO* source_mem_bio; - - memset(az_span_ptr(decoded_bytes), 0, (size_t)az_span_size(decoded_bytes)); - - // Create a BIO filter to process the bytes. - base64_decoder = BIO_new(BIO_f_base64()); - if (base64_decoder == NULL) - { - IOT_SAMPLE_LOG_ERROR("Could not decode the SAS key: Failed to create BIO."); - exit(1); - } - - // Get the source BIO to push through the filter. - source_mem_bio - = BIO_new_mem_buf(az_span_ptr(base64_encoded_bytes), (int)az_span_size(base64_encoded_bytes)); - if (source_mem_bio == NULL) - { - BIO_free(base64_decoder); - IOT_SAMPLE_LOG_ERROR("Could not decode the SAS key: Failed to create BIO new memory buffer."); - exit(1); - } - - // Push the memory through the filter. - source_mem_bio = BIO_push(base64_decoder, source_mem_bio); - if (source_mem_bio == NULL) - { - BIO_free(base64_decoder); - BIO_free(source_mem_bio); - IOT_SAMPLE_LOG_ERROR("Could not decode the SAS key: Failed to push memory through filter."); - exit(1); - } - - // Set flags to not have a newline and close the BIO. - BIO_set_flags(source_mem_bio, BIO_FLAGS_BASE64_NO_NL); - BIO_set_close(source_mem_bio, BIO_CLOSE); - - // Read the memory which was pushed through the filter. - int read_data = BIO_read(source_mem_bio, az_span_ptr(decoded_bytes), az_span_size(decoded_bytes)); - - // Set the output span. - if (read_data > 0) - { - *out_decoded_bytes = az_span_create(az_span_ptr(decoded_bytes), (int32_t)read_data); - } - else - { - IOT_SAMPLE_LOG_ERROR("Could not decode the SAS key: Data could not be read from BIO."); - exit(1); - } - - // Free the BIO chain. - BIO_free_all(source_mem_bio); -} - -static void hmac_sha256_sign_signature( - az_span decoded_key, - az_span signature, - az_span signed_signature, - az_span* out_signed_signature) -{ - unsigned int hmac_encode_len; - unsigned char const* hmac = HMAC( - EVP_sha256(), - (void*)az_span_ptr(decoded_key), - az_span_size(decoded_key), - az_span_ptr(signature), - (size_t)az_span_size(signature), - az_span_ptr(signed_signature), - &hmac_encode_len); - - if (hmac != NULL) - { - *out_signed_signature = az_span_create(az_span_ptr(signed_signature), (int32_t)hmac_encode_len); - } - else - { - IOT_SAMPLE_LOG_ERROR("Could not sign the signature: Buffer is too small."); - exit(1); - } -} - -static void base64_encode_bytes( - az_span decoded_bytes, - az_span base64_encoded_bytes, - az_span* out_base64_encoded_bytes) -{ - BIO* base64_encoder; - BIO* sink_mem_bio; - BUF_MEM* encoded_mem_ptr; - - // Create a BIO filter to process the bytes. - base64_encoder = BIO_new(BIO_f_base64()); - if (base64_encoder == NULL) - { - IOT_SAMPLE_LOG_ERROR("Could not base64 encode the password: Failed to create BIO."); - exit(1); - } - - // Create a memory sink BIO to process bytes to. - sink_mem_bio = BIO_new(BIO_s_mem()); - if (sink_mem_bio == NULL) - { - BIO_free(base64_encoder); - IOT_SAMPLE_LOG_ERROR("Could not base64 encode the password: Failed to create BIO."); - exit(1); - } - - // Push the sink to the encoder. - base64_encoder = BIO_push(base64_encoder, sink_mem_bio); - if (base64_encoder == NULL) - { - BIO_free(sink_mem_bio); - BIO_free(base64_encoder); - IOT_SAMPLE_LOG_ERROR( - "Could not base64 encode the password: Failed to push memory through filter."); - exit(1); - } - - // Set no newline flag for the encoder. - BIO_set_flags(base64_encoder, BIO_FLAGS_BASE64_NO_NL); - - // Write the bytes to be encoded. - int const bytes_written - = BIO_write(base64_encoder, az_span_ptr(decoded_bytes), (int)az_span_size(decoded_bytes)); - if (bytes_written < 1) - { - BIO_free(sink_mem_bio); - BIO_free(base64_encoder); - IOT_SAMPLE_LOG_ERROR("Could not base64 encode the password: Failed to write bytes."); - exit(1); - } - - // Flush the BIO - BIO_flush(base64_encoder); - - // Get the pointer to the encoded bytes. - BIO_get_mem_ptr(base64_encoder, &encoded_mem_ptr); - - if ((size_t)az_span_size(base64_encoded_bytes) >= encoded_mem_ptr->length) - { - // Copy the bytes to the output and initialize output span. - memcpy(az_span_ptr(base64_encoded_bytes), encoded_mem_ptr->data, encoded_mem_ptr->length); - *out_base64_encoded_bytes - = az_span_create(az_span_ptr(base64_encoded_bytes), (int32_t)encoded_mem_ptr->length); - } - else - { - IOT_SAMPLE_LOG_ERROR("Could not base64 encode the password: Buffer is too small."); - exit(1); - } - - // Free the BIO chain. - BIO_free_all(base64_encoder); -} - -void iot_sample_generate_sas_base64_encoded_signed_signature( - az_span sas_base64_encoded_key, - az_span sas_signature, - az_span sas_base64_encoded_signed_signature, - az_span* out_sas_base64_encoded_signed_signature) -{ - IOT_SAMPLE_PRECONDITION_NOT_NULL(out_sas_base64_encoded_signed_signature); - - // Decode the sas base64 encoded key to use for HMAC signing. - char sas_decoded_key_buffer[64]; - az_span sas_decoded_key = AZ_SPAN_FROM_BUFFER(sas_decoded_key_buffer); - decode_base64_bytes(sas_base64_encoded_key, sas_decoded_key, &sas_decoded_key); - - // HMAC-SHA256 sign the signature with the decoded key. - char sas_hmac256_signed_signature_buffer[128]; - az_span sas_hmac256_signed_signature = AZ_SPAN_FROM_BUFFER(sas_hmac256_signed_signature_buffer); - hmac_sha256_sign_signature( - sas_decoded_key, sas_signature, sas_hmac256_signed_signature, &sas_hmac256_signed_signature); - - // Base64 encode the result of the HMAC signing. - base64_encode_bytes( - sas_hmac256_signed_signature, - sas_base64_encoded_signed_signature, - out_sas_base64_encoded_signed_signature); -} +// Copyright (c) Microsoft Corporation. All rights reserved. +// SPDX-License-Identifier: MIT + +#ifdef _MSC_VER +// warning C4996: 'getenv': This function or variable may be unsafe. Consider using _dupenv_s +// instead. +#pragma warning(disable : 4996) +#endif + +#ifdef _WIN32 +// Required for Sleep(DWORD) +#include +#else +// Required for sleep(unsigned int) +#include +#endif + +#include "iot_sample_common.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +#define IOT_SAMPLE_PRECONDITION_NOT_NULL(arg) \ + do \ + { \ + if ((arg) == NULL) \ + { \ + IOT_SAMPLE_LOG_ERROR("Pointer is NULL."); \ + exit(1); \ + } \ + } while (0) + +// +// MQTT endpoints +// +//#define USE_WEB_SOCKET // Comment to use MQTT without WebSockets. +#ifdef USE_WEB_SOCKET +static az_span const mqtt_url_prefix = AZ_SPAN_LITERAL_FROM_STR("wss://"); +// Note: Paho fails to connect to Hub when using AZ_IOT_HUB_CLIENT_WEB_SOCKET_PATH or an X509 +// certificate. +static az_span const mqtt_url_suffix + = AZ_SPAN_LITERAL_FROM_STR(":443" AZ_IOT_HUB_CLIENT_WEB_SOCKET_PATH_NO_X509_CLIENT_CERT); +#else +static az_span const mqtt_url_prefix = AZ_SPAN_LITERAL_FROM_STR("ssl://"); +static az_span const mqtt_url_suffix = AZ_SPAN_LITERAL_FROM_STR(":8883"); +#endif +static az_span const provisioning_global_endpoint + = AZ_SPAN_LITERAL_FROM_STR("ssl://global.azure-devices-provisioning.net:8883"); + +// +// Functions +// +void build_error_message(char* out_full_message, char const* const error_message, ...) +{ + char const* const append_message = ": az_result return code 0x%08x."; + + strcpy(out_full_message, error_message); + strcat(out_full_message, append_message); +} + +bool get_az_span(az_span* out_span, char const* const error_message, ...) +{ + va_list args; + va_start(args, error_message); + + *out_span = va_arg(args, az_span); + va_end(args); + + if (az_span_size(*out_span) == 0) // There was no span + { + return false; + } + + return true; +} + +static void read_configuration_entry( + char const* env_name, + char* default_value, + bool show_value, + az_span destination, + az_span* out_env_value) +{ + char* env_value = getenv(env_name); + + if (env_value == NULL && default_value != NULL) + { + env_value = default_value; + } + + if (env_value != NULL) + { + (void)printf("%s = %s\n", env_name, show_value ? env_value : "***"); + az_span env_span = az_span_create_from_str(env_value); + + // Check the buffer is large enough to store the environment variable. + if ((az_span_size(destination) < az_span_size(env_span)) || (az_span_size(env_span) < 0)) + { + IOT_SAMPLE_LOG_ERROR( + "Failed to read configuration from environment variables: Buffer is too small."); + exit(1); + } + + az_span_copy(destination, env_span); + *out_env_value = az_span_slice(destination, 0, az_span_size(env_span)); + } + else + { + IOT_SAMPLE_LOG_ERROR( + "Failed to read configuration from environment variables: Environment variable %s not set.", + env_name); + exit(1); + } +} + +void iot_sample_read_environment_variables( + iot_sample_type type, + iot_sample_name name, + iot_sample_environment_variables* out_env_vars) +{ + IOT_SAMPLE_PRECONDITION_NOT_NULL(out_env_vars); + + bool show_value = true; + + if (type == PAHO_IOT_HUB) + { + out_env_vars->hub_hostname = AZ_SPAN_FROM_BUFFER(iot_sample_hub_hostname_buffer); + read_configuration_entry( + IOT_SAMPLE_ENV_HUB_HOSTNAME, + NULL, + show_value, + out_env_vars->hub_hostname, + &(out_env_vars->hub_hostname)); + + switch (name) + { + case PAHO_IOT_HUB_C2D_SAMPLE: + case PAHO_IOT_HUB_METHODS_SAMPLE: + case PAHO_IOT_HUB_PNP_COMPONENT_SAMPLE: + case PAHO_IOT_HUB_PNP_SAMPLE: + case PAHO_IOT_HUB_TELEMETRY_SAMPLE: + case PAHO_IOT_HUB_TWIN_SAMPLE: + out_env_vars->hub_device_id = AZ_SPAN_FROM_BUFFER(iot_sample_hub_device_id_buffer); + read_configuration_entry( + IOT_SAMPLE_ENV_HUB_DEVICE_ID, + NULL, + show_value, + out_env_vars->hub_device_id, + &(out_env_vars->hub_device_id)); + + out_env_vars->x509_cert_pem_file_path + = AZ_SPAN_FROM_BUFFER(iot_sample_x509_cert_pem_file_path_buffer); + read_configuration_entry( + IOT_SAMPLE_ENV_DEVICE_X509_CERT_PEM_FILE_PATH, + NULL, + show_value, + out_env_vars->x509_cert_pem_file_path, + &(out_env_vars->x509_cert_pem_file_path)); + break; + + case PAHO_IOT_HUB_SAS_TELEMETRY_SAMPLE: + out_env_vars->hub_device_id = AZ_SPAN_FROM_BUFFER(iot_sample_hub_device_id_buffer); + read_configuration_entry( + IOT_SAMPLE_ENV_HUB_SAS_DEVICE_ID, + NULL, + show_value, + out_env_vars->hub_device_id, + &(out_env_vars->hub_device_id)); + + out_env_vars->hub_sas_key = AZ_SPAN_FROM_BUFFER(iot_sample_hub_sas_key_buffer); + read_configuration_entry( + IOT_SAMPLE_ENV_HUB_SAS_KEY, + NULL, + !show_value, + out_env_vars->hub_sas_key, + &(out_env_vars->hub_sas_key)); + + char duration_buffer[IOT_SAMPLE_SAS_KEY_DURATION_TIME_DIGITS]; + az_span duration = AZ_SPAN_FROM_BUFFER(duration_buffer); + read_configuration_entry( + IOT_SAMPLE_ENV_SAS_KEY_DURATION_MINUTES, "120", show_value, duration, &duration); + + az_result rc = az_span_atou32(duration, &(out_env_vars->sas_key_duration_minutes)); + if (az_result_failed(rc)) + { + IOT_SAMPLE_LOG_ERROR( + "Failed to read environment variables: az_result return code 0x%08x.", rc); + exit(rc); + } + break; + + default: + IOT_SAMPLE_LOG_ERROR("Failed to read environment variables: Hub sample name undefined."); + exit(1); + } + } + else if (type == PAHO_IOT_PROVISIONING) + { + out_env_vars->provisioning_id_scope + = AZ_SPAN_FROM_BUFFER(iot_sample_provisioning_id_scope_buffer); + read_configuration_entry( + IOT_SAMPLE_ENV_PROVISIONING_ID_SCOPE, + NULL, + show_value, + out_env_vars->provisioning_id_scope, + &(out_env_vars->provisioning_id_scope)); + + switch (name) + { + case PAHO_IOT_PROVISIONING_SAMPLE: + out_env_vars->provisioning_registration_id + = AZ_SPAN_FROM_BUFFER(iot_sample_provisioning_registration_id_buffer); + read_configuration_entry( + IOT_SAMPLE_ENV_PROVISIONING_REGISTRATION_ID, + NULL, + show_value, + out_env_vars->provisioning_registration_id, + &(out_env_vars->provisioning_registration_id)); + + out_env_vars->x509_cert_pem_file_path + = AZ_SPAN_FROM_BUFFER(iot_sample_x509_cert_pem_file_path_buffer); + read_configuration_entry( + IOT_SAMPLE_ENV_DEVICE_X509_CERT_PEM_FILE_PATH, + NULL, + show_value, + out_env_vars->x509_cert_pem_file_path, + &(out_env_vars->x509_cert_pem_file_path)); + break; + + case PAHO_IOT_PROVISIONING_SAS_SAMPLE: + out_env_vars->provisioning_registration_id + = AZ_SPAN_FROM_BUFFER(iot_sample_provisioning_registration_id_buffer); + read_configuration_entry( + IOT_SAMPLE_ENV_PROVISIONING_SAS_REGISTRATION_ID, + NULL, + show_value, + out_env_vars->provisioning_registration_id, + &(out_env_vars->provisioning_registration_id)); + + out_env_vars->provisioning_sas_key + = AZ_SPAN_FROM_BUFFER(iot_sample_provisioning_sas_key_buffer); + read_configuration_entry( + IOT_SAMPLE_ENV_PROVISIONING_SAS_KEY, + NULL, + !show_value, + out_env_vars->provisioning_sas_key, + &(out_env_vars->provisioning_sas_key)); + + char duration_buffer[IOT_SAMPLE_SAS_KEY_DURATION_TIME_DIGITS]; + az_span duration = AZ_SPAN_FROM_BUFFER(duration_buffer); + read_configuration_entry( + IOT_SAMPLE_ENV_SAS_KEY_DURATION_MINUTES, "120", show_value, duration, &duration); + + az_result rc = az_span_atou32(duration, &(out_env_vars->sas_key_duration_minutes)); + if (az_result_failed(rc)) + { + IOT_SAMPLE_LOG_ERROR( + "Failed to read environment variables: az_result return code 0x%08x.", rc); + exit(rc); + } + break; + + default: + IOT_SAMPLE_LOG_ERROR( + "Failed to read environment variables: Provisioning sample name undefined."); + exit(1); + } + } + else + { + IOT_SAMPLE_LOG_ERROR("Failed to read environment variables: Sample type undefined."); + exit(1); + } + + out_env_vars->x509_trust_pem_file_path + = AZ_SPAN_FROM_BUFFER(iot_sample_x509_trust_pem_file_path_buffer); + read_configuration_entry( + IOT_SAMPLE_ENV_DEVICE_X509_TRUST_PEM_FILE_PATH, + "", + show_value, + out_env_vars->x509_trust_pem_file_path, + &(out_env_vars->x509_trust_pem_file_path)); + + IOT_SAMPLE_LOG(" "); // Formatting +} + +void iot_sample_create_mqtt_endpoint( + iot_sample_type type, + iot_sample_environment_variables const* env_vars, + char* out_endpoint, + size_t endpoint_size) +{ + IOT_SAMPLE_PRECONDITION_NOT_NULL(env_vars); + IOT_SAMPLE_PRECONDITION_NOT_NULL(out_endpoint); + + if (type == PAHO_IOT_HUB) + { + int32_t const required_size = az_span_size(mqtt_url_prefix) + + az_span_size(env_vars->hub_hostname) + az_span_size(mqtt_url_suffix) + + (int32_t)sizeof('\0'); + + if ((size_t)required_size > endpoint_size) + { + IOT_SAMPLE_LOG_ERROR("Failed to create MQTT endpoint: Buffer is too small."); + exit(1); + } + + az_span hub_mqtt_endpoint = az_span_create((uint8_t*)out_endpoint, (int32_t)endpoint_size); + az_span remainder = az_span_copy(hub_mqtt_endpoint, mqtt_url_prefix); + remainder = az_span_copy(remainder, env_vars->hub_hostname); + remainder = az_span_copy(remainder, mqtt_url_suffix); + az_span_copy_u8(remainder, '\0'); + } + else if (type == PAHO_IOT_PROVISIONING) + { + int32_t const required_size + = az_span_size(provisioning_global_endpoint) + (int32_t)sizeof('\0'); + + if ((size_t)required_size > endpoint_size) + { + IOT_SAMPLE_LOG_ERROR("Failed to create MQTT endpoint: Buffer is too small."); + exit(1); + } + + az_span provisioning_mqtt_endpoint + = az_span_create((uint8_t*)out_endpoint, (int32_t)endpoint_size); + az_span remainder = az_span_copy(provisioning_mqtt_endpoint, provisioning_global_endpoint); + az_span_copy_u8(remainder, '\0'); + } + else + { + IOT_SAMPLE_LOG_ERROR("Failed to create MQTT endpoint: Sample type undefined."); + exit(1); + } + + IOT_SAMPLE_LOG_SUCCESS("MQTT endpoint created at \"%s\".", out_endpoint); +} + +void iot_sample_sleep_for_seconds(uint32_t seconds) +{ +#ifdef _WIN32 + Sleep((DWORD)seconds * 1000); +#else + sleep(seconds); +#endif +} + +uint32_t iot_sample_get_epoch_expiration_time_from_minutes(uint32_t minutes) +{ + return (uint32_t)(time(NULL) + minutes * 60); +} + +static void decode_base64_bytes( + az_span base64_encoded_bytes, + az_span decoded_bytes, + az_span* out_decoded_bytes) +{ + BIO* base64_decoder; + BIO* source_mem_bio; + + memset(az_span_ptr(decoded_bytes), 0, (size_t)az_span_size(decoded_bytes)); + + // Create a BIO filter to process the bytes. + base64_decoder = BIO_new(BIO_f_base64()); + if (base64_decoder == NULL) + { + IOT_SAMPLE_LOG_ERROR("Could not decode the SAS key: Failed to create BIO."); + exit(1); + } + + // Get the source BIO to push through the filter. + source_mem_bio + = BIO_new_mem_buf(az_span_ptr(base64_encoded_bytes), (int)az_span_size(base64_encoded_bytes)); + if (source_mem_bio == NULL) + { + BIO_free(base64_decoder); + IOT_SAMPLE_LOG_ERROR("Could not decode the SAS key: Failed to create BIO new memory buffer."); + exit(1); + } + + // Push the memory through the filter. + source_mem_bio = BIO_push(base64_decoder, source_mem_bio); + if (source_mem_bio == NULL) + { + BIO_free(base64_decoder); + BIO_free(source_mem_bio); + IOT_SAMPLE_LOG_ERROR("Could not decode the SAS key: Failed to push memory through filter."); + exit(1); + } + + // Set flags to not have a newline and close the BIO. + BIO_set_flags(source_mem_bio, BIO_FLAGS_BASE64_NO_NL); + BIO_set_close(source_mem_bio, BIO_CLOSE); + + // Read the memory which was pushed through the filter. + int read_data = BIO_read(source_mem_bio, az_span_ptr(decoded_bytes), az_span_size(decoded_bytes)); + + // Set the output span. + if (read_data > 0) + { + *out_decoded_bytes = az_span_create(az_span_ptr(decoded_bytes), (int32_t)read_data); + } + else + { + IOT_SAMPLE_LOG_ERROR("Could not decode the SAS key: Data could not be read from BIO."); + exit(1); + } + + // Free the BIO chain. + BIO_free_all(source_mem_bio); +} + +static void hmac_sha256_sign_signature( + az_span decoded_key, + az_span signature, + az_span signed_signature, + az_span* out_signed_signature) +{ + unsigned int hmac_encode_len; + unsigned char const* hmac = HMAC( + EVP_sha256(), + (void*)az_span_ptr(decoded_key), + az_span_size(decoded_key), + az_span_ptr(signature), + (size_t)az_span_size(signature), + az_span_ptr(signed_signature), + &hmac_encode_len); + + if (hmac != NULL) + { + *out_signed_signature = az_span_create(az_span_ptr(signed_signature), (int32_t)hmac_encode_len); + } + else + { + IOT_SAMPLE_LOG_ERROR("Could not sign the signature: Buffer is too small."); + exit(1); + } +} + +static void base64_encode_bytes( + az_span decoded_bytes, + az_span base64_encoded_bytes, + az_span* out_base64_encoded_bytes) +{ + BIO* base64_encoder; + BIO* sink_mem_bio; + BUF_MEM* encoded_mem_ptr; + + // Create a BIO filter to process the bytes. + base64_encoder = BIO_new(BIO_f_base64()); + if (base64_encoder == NULL) + { + IOT_SAMPLE_LOG_ERROR("Could not base64 encode the password: Failed to create BIO."); + exit(1); + } + + // Create a memory sink BIO to process bytes to. + sink_mem_bio = BIO_new(BIO_s_mem()); + if (sink_mem_bio == NULL) + { + BIO_free(base64_encoder); + IOT_SAMPLE_LOG_ERROR("Could not base64 encode the password: Failed to create BIO."); + exit(1); + } + + // Push the sink to the encoder. + base64_encoder = BIO_push(base64_encoder, sink_mem_bio); + if (base64_encoder == NULL) + { + BIO_free(sink_mem_bio); + BIO_free(base64_encoder); + IOT_SAMPLE_LOG_ERROR( + "Could not base64 encode the password: Failed to push memory through filter."); + exit(1); + } + + // Set no newline flag for the encoder. + BIO_set_flags(base64_encoder, BIO_FLAGS_BASE64_NO_NL); + + // Write the bytes to be encoded. + int const bytes_written + = BIO_write(base64_encoder, az_span_ptr(decoded_bytes), (int)az_span_size(decoded_bytes)); + if (bytes_written < 1) + { + BIO_free(sink_mem_bio); + BIO_free(base64_encoder); + IOT_SAMPLE_LOG_ERROR("Could not base64 encode the password: Failed to write bytes."); + exit(1); + } + + // Flush the BIO + BIO_flush(base64_encoder); + + // Get the pointer to the encoded bytes. + BIO_get_mem_ptr(base64_encoder, &encoded_mem_ptr); + + if ((size_t)az_span_size(base64_encoded_bytes) >= encoded_mem_ptr->length) + { + // Copy the bytes to the output and initialize output span. + memcpy(az_span_ptr(base64_encoded_bytes), encoded_mem_ptr->data, encoded_mem_ptr->length); + *out_base64_encoded_bytes + = az_span_create(az_span_ptr(base64_encoded_bytes), (int32_t)encoded_mem_ptr->length); + } + else + { + IOT_SAMPLE_LOG_ERROR("Could not base64 encode the password: Buffer is too small."); + exit(1); + } + + // Free the BIO chain. + BIO_free_all(base64_encoder); +} + +void iot_sample_generate_sas_base64_encoded_signed_signature( + az_span sas_base64_encoded_key, + az_span sas_signature, + az_span sas_base64_encoded_signed_signature, + az_span* out_sas_base64_encoded_signed_signature) +{ + IOT_SAMPLE_PRECONDITION_NOT_NULL(out_sas_base64_encoded_signed_signature); + + // Decode the sas base64 encoded key to use for HMAC signing. + char sas_decoded_key_buffer[64]; + az_span sas_decoded_key = AZ_SPAN_FROM_BUFFER(sas_decoded_key_buffer); + decode_base64_bytes(sas_base64_encoded_key, sas_decoded_key, &sas_decoded_key); + + // HMAC-SHA256 sign the signature with the decoded key. + char sas_hmac256_signed_signature_buffer[128]; + az_span sas_hmac256_signed_signature = AZ_SPAN_FROM_BUFFER(sas_hmac256_signed_signature_buffer); + hmac_sha256_sign_signature( + sas_decoded_key, sas_signature, sas_hmac256_signed_signature, &sas_hmac256_signed_signature); + + // Base64 encode the result of the HMAC signing. + base64_encode_bytes( + sas_hmac256_signed_signature, + sas_base64_encoded_signed_signature, + out_sas_base64_encoded_signed_signature); +} diff --git a/sdk/samples/iot/iot_sample_common.h b/sdk/samples/iot/iot_sample_common.h index f77b403223..3a03a01f7a 100644 --- a/sdk/samples/iot/iot_sample_common.h +++ b/sdk/samples/iot/iot_sample_common.h @@ -1,221 +1,221 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// SPDX-License-Identifier: MIT - -#ifndef IOT_SAMPLE_COMMON_H -#define IOT_SAMPLE_COMMON_H - -#include -#include -#include -#include -#include -#include - -#include -#include - -#define IOT_SAMPLE_SAS_KEY_DURATION_TIME_DIGITS 4 -#define IOT_SAMPLE_MQTT_PUBLISH_QOS 0 - -// -// Logging -// -#define IOT_SAMPLE_LOG_ERROR(...) \ - do \ - { \ - (void)fprintf(stderr, "ERROR:\t\t%s:%s():%d: ", __FILE__, __func__, __LINE__); \ - (void)fprintf(stderr, __VA_ARGS__); \ - (void)fprintf(stderr, "\n"); \ - fflush(stdout); \ - fflush(stderr); \ - } while (0) - -#define IOT_SAMPLE_LOG_SUCCESS(...) \ - do \ - { \ - (void)printf("SUCCESS:\t"); \ - (void)printf(__VA_ARGS__); \ - (void)printf("\n"); \ - } while (0) - -#define IOT_SAMPLE_LOG(...) \ - do \ - { \ - (void)printf("\t\t"); \ - (void)printf(__VA_ARGS__); \ - (void)printf("\n"); \ - } while (0) - -#define IOT_SAMPLE_LOG_AZ_SPAN(span_description, span) \ - do \ - { \ - (void)printf("\t\t%s ", span_description); \ - (void)fwrite((char*)az_span_ptr(span), sizeof(uint8_t), (size_t)az_span_size(span), stdout); \ - (void)printf("\n"); \ - } while (0) - -// -// Error handling -// -// Note: Only handles a single variadic parameter of type char const*, or two variadic parameters of -// type char const* and az_span. -void build_error_message(char* out_full_message, char const* const error_message, ...); -bool get_az_span(az_span* out_span, char const* const error_message, ...); -#define IOT_SAMPLE_EXIT_IF_AZ_FAILED(azfn, ...) \ - do \ - { \ - az_result const result = (azfn); \ - \ - if (az_result_failed(result)) \ - { \ - char full_message[256]; \ - build_error_message(full_message, __VA_ARGS__); \ - \ - az_span span; \ - bool has_az_span = get_az_span(&span, __VA_ARGS__, AZ_SPAN_EMPTY); \ - if (has_az_span) \ - { \ - IOT_SAMPLE_LOG_ERROR(full_message, az_span_size(span), az_span_ptr(span), result); \ - } \ - else \ - { \ - IOT_SAMPLE_LOG_ERROR(full_message, result); \ - } \ - exit(1); \ - } \ - } while (0) - -// -// Environment Variables -// -// DO NOT MODIFY: Service information -#define IOT_SAMPLE_ENV_HUB_HOSTNAME "AZ_IOT_HUB_HOSTNAME" -#define IOT_SAMPLE_ENV_PROVISIONING_ID_SCOPE "AZ_IOT_PROVISIONING_ID_SCOPE" - -// DO NOT MODIFY: Device information -#define IOT_SAMPLE_ENV_HUB_DEVICE_ID "AZ_IOT_HUB_DEVICE_ID" -#define IOT_SAMPLE_ENV_HUB_SAS_DEVICE_ID "AZ_IOT_HUB_SAS_DEVICE_ID" -#define IOT_SAMPLE_ENV_PROVISIONING_REGISTRATION_ID "AZ_IOT_PROVISIONING_REGISTRATION_ID" -#define IOT_SAMPLE_ENV_PROVISIONING_SAS_REGISTRATION_ID "AZ_IOT_PROVISIONING_SAS_REGISTRATION_ID" - -// DO NOT MODIFY: SAS Key -#define IOT_SAMPLE_ENV_HUB_SAS_KEY "AZ_IOT_HUB_SAS_KEY" -#define IOT_SAMPLE_ENV_PROVISIONING_SAS_KEY "AZ_IOT_PROVISIONING_SAS_KEY" -#define IOT_SAMPLE_ENV_SAS_KEY_DURATION_MINUTES \ - "AZ_IOT_SAS_KEY_DURATION_MINUTES" // default is 2 hrs. - -// DO NOT MODIFY: the path to a PEM file containing the device certificate and -// key as well as any intermediate certificates chaining to an uploaded group certificate. -#define IOT_SAMPLE_ENV_DEVICE_X509_CERT_PEM_FILE_PATH "AZ_IOT_DEVICE_X509_CERT_PEM_FILE_PATH" - -// DO NOT MODIFY: the path to a PEM file containing the server trusted CA -// This is usually not needed on Linux or Mac but needs to be set on Windows. -#define IOT_SAMPLE_ENV_DEVICE_X509_TRUST_PEM_FILE_PATH "AZ_IOT_DEVICE_X509_TRUST_PEM_FILE_PATH" - -char iot_sample_hub_hostname_buffer[128]; -char iot_sample_provisioning_id_scope_buffer[16]; - -char iot_sample_hub_device_id_buffer[64]; -char iot_sample_provisioning_registration_id_buffer[256]; - -char iot_sample_hub_sas_key_buffer[128]; -char iot_sample_provisioning_sas_key_buffer[128]; - -char iot_sample_x509_cert_pem_file_path_buffer[256]; -char iot_sample_x509_trust_pem_file_path_buffer[256]; - -typedef struct -{ - az_span hub_device_id; - az_span hub_hostname; - az_span hub_sas_key; - az_span provisioning_id_scope; - az_span provisioning_registration_id; - az_span provisioning_sas_key; - az_span x509_cert_pem_file_path; - az_span x509_trust_pem_file_path; - uint32_t sas_key_duration_minutes; -} iot_sample_environment_variables; - -typedef enum -{ - PAHO_IOT_HUB, - PAHO_IOT_PROVISIONING -} iot_sample_type; - -typedef enum -{ - PAHO_IOT_HUB_C2D_SAMPLE, - PAHO_IOT_HUB_METHODS_SAMPLE, - PAHO_IOT_HUB_PNP_COMPONENT_SAMPLE, - PAHO_IOT_HUB_PNP_SAMPLE, - PAHO_IOT_HUB_SAS_TELEMETRY_SAMPLE, - PAHO_IOT_HUB_TELEMETRY_SAMPLE, - PAHO_IOT_HUB_TWIN_SAMPLE, - PAHO_IOT_PROVISIONING_SAMPLE, - PAHO_IOT_PROVISIONING_SAS_SAMPLE -} iot_sample_name; - -extern bool is_device_operational; - -/* - * @brief Reads in environment variables set by user for purposes of running sample. - * - * @param[in] type The enumerated type of the sample. - * @param[in] name The enumerated name of the sample. - * @param[out] out_env_vars A pointer to the struct containing all read-in environment variables. - */ -void iot_sample_read_environment_variables( - iot_sample_type type, - iot_sample_name name, - iot_sample_environment_variables* out_env_vars); - -/* - * @brief Builds an MQTT endpoint c-string for an Azure IoT Hub or provisioning service. - * - * @param[in] type The enumerated type of the sample. - * @param[in] env_vars A pointer to environment variable struct. - * @param[out] endpoint A buffer with sufficient capacity to hold the built endpoint. If - * successful, contains a null-terminated string of the endpoint. - * @param[in] endpoint_size The size of \p out_endpoint in bytes. - */ -void iot_sample_create_mqtt_endpoint( - iot_sample_type type, - iot_sample_environment_variables const* env_vars, - char* endpoint, - size_t endpoint_size); - -/* - * @brief Sleep for given seconds. - * - * @param[in] seconds Number of seconds to sleep. - */ -void iot_sample_sleep_for_seconds(uint32_t seconds); - -/* - * @brief Return total seconds passed including supplied minutes. - * - * @param[in] minutes Number of minutes to include in total seconds returned. - * - * @return Total time in seconds. - */ -uint32_t iot_sample_get_epoch_expiration_time_from_minutes(uint32_t minutes); - -/* - * @brief Generate the base64 encoded and signed signature using HMAC-SHA256 signing. - * - * @param[in] sas_base64_encoded_key An #az_span containing the SAS key that will be used for - * signing. - * @param[in] sas_signature An #az_span containing the signature. - * @param[in] sas_base64_encoded_signed_signature An #az_span with sufficient capacity to hold the - * encoded signed signature. - * @param[out] out_sas_base64_encoded_signed_signature A pointer to the #az_span containing the - * encoded signed signature. - */ -void iot_sample_generate_sas_base64_encoded_signed_signature( - az_span sas_base64_encoded_key, - az_span sas_signature, - az_span sas_base64_encoded_signed_signature, - az_span* out_sas_base64_encoded_signed_signature); - -#endif // IOT_SAMPLE_COMMON_H +// Copyright (c) Microsoft Corporation. All rights reserved. +// SPDX-License-Identifier: MIT + +#ifndef IOT_SAMPLE_COMMON_H +#define IOT_SAMPLE_COMMON_H + +#include +#include +#include +#include +#include +#include + +#include +#include + +#define IOT_SAMPLE_SAS_KEY_DURATION_TIME_DIGITS 4 +#define IOT_SAMPLE_MQTT_PUBLISH_QOS 0 + +// +// Logging +// +#define IOT_SAMPLE_LOG_ERROR(...) \ + do \ + { \ + (void)fprintf(stderr, "ERROR:\t\t%s:%s():%d: ", __FILE__, __func__, __LINE__); \ + (void)fprintf(stderr, __VA_ARGS__); \ + (void)fprintf(stderr, "\n"); \ + fflush(stdout); \ + fflush(stderr); \ + } while (0) + +#define IOT_SAMPLE_LOG_SUCCESS(...) \ + do \ + { \ + (void)printf("SUCCESS:\t"); \ + (void)printf(__VA_ARGS__); \ + (void)printf("\n"); \ + } while (0) + +#define IOT_SAMPLE_LOG(...) \ + do \ + { \ + (void)printf("\t\t"); \ + (void)printf(__VA_ARGS__); \ + (void)printf("\n"); \ + } while (0) + +#define IOT_SAMPLE_LOG_AZ_SPAN(span_description, span) \ + do \ + { \ + (void)printf("\t\t%s ", span_description); \ + (void)fwrite((char*)az_span_ptr(span), sizeof(uint8_t), (size_t)az_span_size(span), stdout); \ + (void)printf("\n"); \ + } while (0) + +// +// Error handling +// +// Note: Only handles a single variadic parameter of type char const*, or two variadic parameters of +// type char const* and az_span. +void build_error_message(char* out_full_message, char const* const error_message, ...); +bool get_az_span(az_span* out_span, char const* const error_message, ...); +#define IOT_SAMPLE_EXIT_IF_AZ_FAILED(azfn, ...) \ + do \ + { \ + az_result const result = (azfn); \ + \ + if (az_result_failed(result)) \ + { \ + char full_message[256]; \ + build_error_message(full_message, __VA_ARGS__); \ + \ + az_span span; \ + bool has_az_span = get_az_span(&span, __VA_ARGS__, AZ_SPAN_EMPTY); \ + if (has_az_span) \ + { \ + IOT_SAMPLE_LOG_ERROR(full_message, az_span_size(span), az_span_ptr(span), result); \ + } \ + else \ + { \ + IOT_SAMPLE_LOG_ERROR(full_message, result); \ + } \ + exit(1); \ + } \ + } while (0) + +// +// Environment Variables +// +// DO NOT MODIFY: Service information +#define IOT_SAMPLE_ENV_HUB_HOSTNAME "AZ_IOT_HUB_HOSTNAME" +#define IOT_SAMPLE_ENV_PROVISIONING_ID_SCOPE "AZ_IOT_PROVISIONING_ID_SCOPE" + +// DO NOT MODIFY: Device information +#define IOT_SAMPLE_ENV_HUB_DEVICE_ID "AZ_IOT_HUB_DEVICE_ID" +#define IOT_SAMPLE_ENV_HUB_SAS_DEVICE_ID "AZ_IOT_HUB_SAS_DEVICE_ID" +#define IOT_SAMPLE_ENV_PROVISIONING_REGISTRATION_ID "AZ_IOT_PROVISIONING_REGISTRATION_ID" +#define IOT_SAMPLE_ENV_PROVISIONING_SAS_REGISTRATION_ID "AZ_IOT_PROVISIONING_SAS_REGISTRATION_ID" + +// DO NOT MODIFY: SAS Key +#define IOT_SAMPLE_ENV_HUB_SAS_KEY "AZ_IOT_HUB_SAS_KEY" +#define IOT_SAMPLE_ENV_PROVISIONING_SAS_KEY "AZ_IOT_PROVISIONING_SAS_KEY" +#define IOT_SAMPLE_ENV_SAS_KEY_DURATION_MINUTES \ + "AZ_IOT_SAS_KEY_DURATION_MINUTES" // default is 2 hrs. + +// DO NOT MODIFY: the path to a PEM file containing the device certificate and +// key as well as any intermediate certificates chaining to an uploaded group certificate. +#define IOT_SAMPLE_ENV_DEVICE_X509_CERT_PEM_FILE_PATH "AZ_IOT_DEVICE_X509_CERT_PEM_FILE_PATH" + +// DO NOT MODIFY: the path to a PEM file containing the server trusted CA +// This is usually not needed on Linux or Mac but needs to be set on Windows. +#define IOT_SAMPLE_ENV_DEVICE_X509_TRUST_PEM_FILE_PATH "AZ_IOT_DEVICE_X509_TRUST_PEM_FILE_PATH" + +char iot_sample_hub_hostname_buffer[128]; +char iot_sample_provisioning_id_scope_buffer[16]; + +char iot_sample_hub_device_id_buffer[64]; +char iot_sample_provisioning_registration_id_buffer[256]; + +char iot_sample_hub_sas_key_buffer[128]; +char iot_sample_provisioning_sas_key_buffer[128]; + +char iot_sample_x509_cert_pem_file_path_buffer[256]; +char iot_sample_x509_trust_pem_file_path_buffer[256]; + +typedef struct +{ + az_span hub_device_id; + az_span hub_hostname; + az_span hub_sas_key; + az_span provisioning_id_scope; + az_span provisioning_registration_id; + az_span provisioning_sas_key; + az_span x509_cert_pem_file_path; + az_span x509_trust_pem_file_path; + uint32_t sas_key_duration_minutes; +} iot_sample_environment_variables; + +typedef enum +{ + PAHO_IOT_HUB, + PAHO_IOT_PROVISIONING +} iot_sample_type; + +typedef enum +{ + PAHO_IOT_HUB_C2D_SAMPLE, + PAHO_IOT_HUB_METHODS_SAMPLE, + PAHO_IOT_HUB_PNP_COMPONENT_SAMPLE, + PAHO_IOT_HUB_PNP_SAMPLE, + PAHO_IOT_HUB_SAS_TELEMETRY_SAMPLE, + PAHO_IOT_HUB_TELEMETRY_SAMPLE, + PAHO_IOT_HUB_TWIN_SAMPLE, + PAHO_IOT_PROVISIONING_SAMPLE, + PAHO_IOT_PROVISIONING_SAS_SAMPLE +} iot_sample_name; + +extern bool is_device_operational; + +/* + * @brief Reads in environment variables set by user for purposes of running sample. + * + * @param[in] type The enumerated type of the sample. + * @param[in] name The enumerated name of the sample. + * @param[out] out_env_vars A pointer to the struct containing all read-in environment variables. + */ +void iot_sample_read_environment_variables( + iot_sample_type type, + iot_sample_name name, + iot_sample_environment_variables* out_env_vars); + +/* + * @brief Builds an MQTT endpoint c-string for an Azure IoT Hub or provisioning service. + * + * @param[in] type The enumerated type of the sample. + * @param[in] env_vars A pointer to environment variable struct. + * @param[out] endpoint A buffer with sufficient capacity to hold the built endpoint. If + * successful, contains a null-terminated string of the endpoint. + * @param[in] endpoint_size The size of \p out_endpoint in bytes. + */ +void iot_sample_create_mqtt_endpoint( + iot_sample_type type, + iot_sample_environment_variables const* env_vars, + char* endpoint, + size_t endpoint_size); + +/* + * @brief Sleep for given seconds. + * + * @param[in] seconds Number of seconds to sleep. + */ +void iot_sample_sleep_for_seconds(uint32_t seconds); + +/* + * @brief Return total seconds passed including supplied minutes. + * + * @param[in] minutes Number of minutes to include in total seconds returned. + * + * @return Total time in seconds. + */ +uint32_t iot_sample_get_epoch_expiration_time_from_minutes(uint32_t minutes); + +/* + * @brief Generate the base64 encoded and signed signature using HMAC-SHA256 signing. + * + * @param[in] sas_base64_encoded_key An #az_span containing the SAS key that will be used for + * signing. + * @param[in] sas_signature An #az_span containing the signature. + * @param[in] sas_base64_encoded_signed_signature An #az_span with sufficient capacity to hold the + * encoded signed signature. + * @param[out] out_sas_base64_encoded_signed_signature A pointer to the #az_span containing the + * encoded signed signature. + */ +void iot_sample_generate_sas_base64_encoded_signed_signature( + az_span sas_base64_encoded_key, + az_span sas_signature, + az_span sas_base64_encoded_signed_signature, + az_span* out_sas_base64_encoded_signed_signature); + +#endif // IOT_SAMPLE_COMMON_H