diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 4165ecf2f3..88ec3645a3 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,26 +1,26 @@ # Instructions for CODEOWNERS file format and automatic build failure notifications: -# https://github.com/Azure/azure-sdk/blob/master/docs/policies/opensource.md#codeowners +# https://github.com/Azure/azure-sdk/blob/main/docs/policies/opensource.md#codeowners ########### # SDK ########### # Catch all for non-code project files -* @rickwinter @joshfree +* @rickwinter # SDK team -/sdk/ @ahsonkhan @antkmsft @rickwinter @vhvb1989 +/sdk/ @rickwinter # Samples -/samples/ @rickwinter +/samples/ @ahsonkhan @antkmsft @rickwinter @vhvb1989 @gearama # Service teams # Core -sdk/docs/core/ @ahsonkhan @antkmsft @rickwinter @vhvb1989 -sdk/inc/azure/core/ @ahsonkhan @antkmsft @rickwinter @vhvb1989 -sdk/samples/core/ @ahsonkhan @antkmsft @rickwinter @vhvb1989 -sdk/src/azure/core/ @ahsonkhan @antkmsft @rickwinter @vhvb1989 -sdk/tests/core/ @ahsonkhan @antkmsft @rickwinter @vhvb1989 +sdk/docs/core/ @ahsonkhan @antkmsft @rickwinter @vhvb1989 @gearama +sdk/inc/azure/core/ @ahsonkhan @antkmsft @rickwinter @vhvb1989 @gearama +sdk/samples/core/ @ahsonkhan @antkmsft @rickwinter @vhvb1989 @gearama +sdk/src/azure/core/ @ahsonkhan @antkmsft @rickwinter @vhvb1989 @gearama +sdk/tests/core/ @ahsonkhan @antkmsft @rickwinter @vhvb1989 @gearama # IoT sdk/docs/iot/ @CIPop @danewalton @ericwol-msft @ewertons @hihigupt @jspaith @momuno @@ -30,7 +30,7 @@ sdk/src/azure/iot/ @CIPop @danewalton @ericwol-msft @ewertons @hihigu sdk/tests/iot/ @CIPop @danewalton @ericwol-msft @ewertons @hihigupt @jspaith @momuno # Platform -sdk/src/azure/platform/ @ahsonkhan @antkmsft @rickwinter @vhvb1989 +sdk/src/azure/platform/ @ahsonkhan @antkmsft @rickwinter @vhvb1989 @gearama ########### # Eng Sys diff --git a/CHANGELOG.md b/CHANGELOG.md index bae3a2d149..54d2861bab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,11 @@ ## 1.2.0-beta.1 (Unreleased) +### New Features + +- Added a current depth field to the JSON reader. +- Added base64 encoding and decoding APIs that accept `az_span`, available from the `azure/core/az_base64.h` header. + ### Bug Fixes - [[#1640]](https://github.com/Azure/azure-sdk-for-c/pull/1640) Update precondition on `az_iot_provisioning_client_parse_received_topic_and_payload()` to require topic and payload minimum size of 1 instead of 0. @@ -14,7 +19,7 @@ - Compared to the previous 1.0.0 release, there are **no** breaking changes. - Removed `az_iot_pnp_client.h`, which included some beta APIs related to IoT Plug and Play such as `az_iot_pnp_client()`. - These will ship in a future release and will continue to be available as beta from [this feature branch](https://github.com/Azure/azure-sdk-for-c/tree/feature/iot_pnp). - + ### Bug Fixes - [[#1600]](https://github.com/Azure/azure-sdk-for-c/pull/1600) Make sure `az_json_writer_append_json_text()` appends a comma between elements of a JSON array. @@ -197,4 +202,4 @@ ## 1.0.0-preview.1 (2020-05-12) -Initial release. Please see the [README](https://github.com/Azure/azure-sdk-for-c/blob/master/README.md) for more information. +Initial release. Please see the [README](https://github.com/Azure/azure-sdk-for-c/blob/main/README.md) for more information. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6aa88961c4..cfc20fec25 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -161,5 +161,5 @@ make ${project_name}_cov_xml //i.e. az_core_cov_xml or az_iot_cov_xml ``` -[vcpkg]: https://github.com/Azure/azure-sdk-for-c/blob/master/README.md#development-environment +[vcpkg]: https://github.com/Azure/azure-sdk-for-c/blob/main/README.md#development-environment [azure_sdk_for_c_cmake_options]: https://github.com/Azure/azure-sdk-for-c#cmake-options diff --git a/README.md b/README.md index c3d5c2fcb5..fef254cb8d 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # 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) +[![Build Status](https://dev.azure.com/azure-sdk/public/_apis/build/status/c/c%20-%20client%20-%20ci?branchName=main)](https://dev.azure.com/azure-sdk/public/_build/latest?definitionId=722&branchName=main) 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. @@ -26,7 +26,7 @@ With this in mind, there are many tenets or principles that we follow in order t - [The GitHub Repository](#the-github-repository) - [Services](#services) - [Structure](#structure) - - [Master Branch](#master-branch) + - [Main Branch](#main-branch) - [Release Branches and Release Tagging](#release-branches-and-release-tagging) - [Getting Started Using the SDK](#getting-started-using-the-sdk) - [CMake](#cmake) @@ -64,7 +64,7 @@ To get help with the SDK: 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 +1. [IoT](https://github.com/Azure/azure-sdk-for-c/blob/main/sdk/docs/iot) - Library to connect Embedded Devices to Azure IoT services ### Structure @@ -82,9 +82,9 @@ This repo is structured with two priorities: 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 +### Main 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. +The main 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 @@ -94,7 +94,7 @@ When we make an official release, we will create a unique git tag containing the 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. + For more information, please see this [branching strategy](https://github.com/Azure/azure-sdk/blob/main/docs/policies/repobranching.md#release-tagging) document. ## Getting Started Using the SDK @@ -113,7 +113,7 @@ The SDK can be conveniently consumed either via CMake or other non-CMake methods 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. + 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/main/sdk/docs) folder. 3. Ensure the SDK builds correctly. @@ -135,7 +135,7 @@ The SDK can be conveniently consumed either via CMake or other non-CMake methods 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). +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/main/sdk/docs/core#porting-the-azure-sdk-to-another-platform). ### CMake Options @@ -204,11 +204,11 @@ Azure SDK for C can be automatically checked out by cmake and become a build dep 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](https://github.com/Azure/azure-sdk-for-c/blob/master/cmake-modules/AddAzureSDKforC.cmake) that can be copied and used for this purpose. +Azure SDK for C provides a [CMake module](https://github.com/Azure/azure-sdk-for-c/blob/main/cmake-modules/AddAzureSDKforC.cmake) that can be copied and used for this purpose. ### Visual Studio Code -For convenience, you can quickly get started using [Visual Studio Code](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: +For convenience, you can quickly get started using [Visual Studio Code](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/main/.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) @@ -233,8 +233,8 @@ To use a specific service/feature, you may include the header file with the func 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. +1. All services depend on `core` ([source files here](https://github.com/Azure/azure-sdk-for-c/tree/main/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/main/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/main/sdk/src/azure/platform) for [`windows`](https://github.com/Azure/azure-sdk-for-c/blob/main/sdk/src/azure/platform/az_win32.c), [`posix`](https://github.com/Azure/azure-sdk-for-c/blob/main/sdk/src/azure/platform/az_posix.c), and a [`no_platform`](https://github.com/Azure/azure-sdk-for-c/blob/main/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. @@ -274,7 +274,7 @@ The reason for this is the fact of this functions are not thread-safe, and a cus ### 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). +For more information about IoT APIs and samples, see [Azure IoT Clients](https://github.com/Azure/azure-sdk-for-c/tree/main/sdk/docs/iot#azure-iot-clients). ### Development Environment @@ -294,7 +294,7 @@ vcpkg maintains any installed package inside its own folder, allowing to have mu Use the following steps to install vcpkg and have it linked to CMake. -> **Note:** The Azure SDK is only officially supported against certain versions of vcpkg. Use the commit in [vcpkg-commit.txt](https://github.com/Azure/azure-sdk-for-c/blob/master/eng/vcpkg-commit.txt) to get a known working version. +> **Note:** The Azure SDK is only officially supported against certain versions of vcpkg. Use the commit in [vcpkg-commit.txt](https://github.com/Azure/azure-sdk-for-c/blob/main/eng/vcpkg-commit.txt) to get a known working version. ```bash # Clone vcpkg: @@ -346,7 +346,7 @@ vcpkg maintains any installed package inside its own folder, allowing to have mu Use the following steps to install vcpkg and have it linked to CMake. -> **Note:** The Azure SDK is only officially supported against certain versions of vcpkg. Use the commit in [vcpkg-commit.txt](https://github.com/Azure/azure-sdk-for-c/blob/master/eng/vcpkg-commit.txt) to get a known working version. +> **Note:** The Azure SDK is only officially supported against certain versions of vcpkg. Use the commit in [vcpkg-commit.txt](https://github.com/Azure/azure-sdk-for-c/blob/main/eng/vcpkg-commit.txt) to get a known working version. ```bash # Clone vcpkg: @@ -403,7 +403,7 @@ First, ensure that you have the latest `gcc` installed: Use the following steps to install vcpkg and have it linked to CMake. -> **Note:** The Azure SDK is only officially supported against certain versions of vcpkg. Use the commit in [vcpkg-commit.txt](https://github.com/Azure/azure-sdk-for-c/blob/master/eng/vcpkg-commit.txt) to get a known working version. +> **Note:** The Azure SDK is only officially supported against certain versions of vcpkg. Use the commit in [vcpkg-commit.txt](https://github.com/Azure/azure-sdk-for-c/blob/main/eng/vcpkg-commit.txt) to get a known working version. ```bash # Clone vcpkg: @@ -450,7 +450,7 @@ 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. +For example, Azure SDK provides a cmake target `az_curl` (find it [here](https://github.com/Azure/azure-sdk-for-c/tree/main/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 @@ -462,29 +462,29 @@ target_link_libraries(your_application_target PRIVATE lib_adapter http_stack_lib 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) +See the complete cmake file and how to link your own library [here](https://github.com/Azure/azure-sdk-for-c/blob/main/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: +At the heart of our SDK is, what we refer to as, [Azure Core](https://github.com/Azure/azure-sdk-for-c/tree/main/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/main/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. +- **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/main/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. +- **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/main/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. +- **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/main/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. +- **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/main/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). +For details on contributing to this repository, see the [contributing guide](https://github.com/Azure/azure-sdk-for-c/blob/main/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). @@ -499,8 +499,8 @@ For more information see the [Code of Conduct FAQ](https://opensource.microsoft. 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) +- [How to build and test your change](https://github.com/Azure/azure-sdk-for-c/blob/main/CONTRIBUTING.md#developer-guide) +- [How you can make a change happen!](https://github.com/Azure/azure-sdk-for-c/blob/main/CONTRIBUTING.md#pull-requests) ### Community @@ -512,4 +512,4 @@ Security issues and bugs should be reported privately, via email, to the Microso ### 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 is licensed under the [MIT](https://github.com/Azure/azure-sdk-for-c/blob/main/LICENSE) license. diff --git a/eng/cmake/global_compile_options.txt b/eng/cmake/global_compile_options.txt index 9fd36bbddd..56223ce526 100644 --- a/eng/cmake/global_compile_options.txt +++ b/eng/cmake/global_compile_options.txt @@ -1,28 +1,33 @@ if(MSVC) - if(WARNINGS_AS_ERRORS) - set(WARNINGS_AS_ERRORS_FLAG "/WX") - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /WX") - endif() - - add_compile_options(/W4 ${WARNINGS_AS_ERRORS_FLAG} /wd5031 /wd4668 /wd4820 /wd4255 /wd4710 /analyze) - add_compile_options( - $<$:/MT> #---------| - $<$:/MTd> #---|-- Statically link the runtime libraries - $<$:/MT> #--| + $<$:/MT> #---------| + $<$:/MTd> #---|-- Statically link the runtime libraries + $<$:/MT> #--| ) -elseif(CMAKE_C_COMPILER_ID MATCHES "Clang") - if(WARNINGS_AS_ERRORS) - set(WARNINGS_AS_ERRORS_FLAG "-Werror") - endif() +endif() - add_compile_options(-Xclang -Wall -Wextra -pedantic ${WARNINGS_AS_ERRORS_FLAG} -Wdocumentation -Wdocumentation-unknown-command -fcomment-block-commands=retval -Wcast-qual -Wunused -Wuninitialized -Wmissing-declarations -Wconversion -Wpointer-arith -Wshadow -Wfloat-equal) -elseif(CMAKE_C_COMPILER_ID MATCHES "GNU") - if(WARNINGS_AS_ERRORS) - set(WARNINGS_AS_ERRORS_FLAG "-Werror") - endif() +# Turn on strict compiler flags only for testing to allow better compatability with diverse platforms. +if(UNIT_TESTING) + if(MSVC) + if(WARNINGS_AS_ERRORS) + set(WARNINGS_AS_ERRORS_FLAG "/WX") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /WX") + endif() + + add_compile_options(/W4 ${WARNINGS_AS_ERRORS_FLAG} /wd5031 /wd4668 /wd4820 /wd4255 /wd4710 /analyze) + elseif(CMAKE_C_COMPILER_ID MATCHES "Clang") + if(WARNINGS_AS_ERRORS) + set(WARNINGS_AS_ERRORS_FLAG "-Werror") + endif() - add_compile_options(-Wall -Wextra -pedantic ${WARNINGS_AS_ERRORS_FLAG} -Wcast-qual -Wunused -Wuninitialized -Wmissing-declarations -Wconversion -Wpointer-arith -Wshadow -Wlogical-op -Wfloat-equal) -else() - message(WARNING "Using an unsupported compiler. Disabling stricter compiler flags.") + add_compile_options(-Xclang -Wall -Wextra -pedantic ${WARNINGS_AS_ERRORS_FLAG} -Wdocumentation -Wdocumentation-unknown-command -fcomment-block-commands=retval -Wcast-qual -Wunused -Wuninitialized -Wmissing-declarations -Wconversion -Wpointer-arith -Wshadow -Wfloat-equal) + elseif(CMAKE_C_COMPILER_ID MATCHES "GNU") + if(WARNINGS_AS_ERRORS) + set(WARNINGS_AS_ERRORS_FLAG "-Werror") + endif() + + add_compile_options(-Wall -Wextra -pedantic ${WARNINGS_AS_ERRORS_FLAG} -Wcast-qual -Wunused -Wuninitialized -Wmissing-declarations -Wconversion -Wpointer-arith -Wshadow -Wlogical-op -Wfloat-equal) + else() + message(WARNING "Using an unsupported compiler. Disabling stricter compiler flags.") + endif() endif() diff --git a/eng/common/README.md b/eng/common/README.md index b9867f0705..732688daad 100644 --- a/eng/common/README.md +++ b/eng/common/README.md @@ -1,3 +1,3 @@ # Common Engineering System -Updates under this directory should only be made in the `azure-sdk-tools` repo as any changes under this directory outside of that repo will end up getting overwritten with future updates. For information about making updates see [common engineering system docs](https://github.com/Azure/azure-sdk-tools/blob/master/doc/common/common_engsys.md) +Updates under this directory should only be made in the `azure-sdk-tools` repo as any changes under this directory outside of that repo will end up getting overwritten with future updates. For information about making updates see [common engineering system docs](https://github.com/Azure/azure-sdk-tools/blob/main/doc/common/common_engsys.md) diff --git a/eng/common/TestResources/New-TestResources.ps1 b/eng/common/TestResources/New-TestResources.ps1 index aa95298be5..a38155cba8 100644 --- a/eng/common/TestResources/New-TestResources.ps1 +++ b/eng/common/TestResources/New-TestResources.ps1 @@ -119,6 +119,26 @@ function MergeHashes([hashtable] $source, [psvariable] $dest) { } } +function BuildBicepFile([System.IO.FileSystemInfo] $file) { + if (!(Get-Command bicep -ErrorAction Ignore)) { + Write-Error "A bicep file was found at '$($file.FullName)' but the Azure Bicep CLI is not installed. See https://aka.ms/install-bicep-pwsh" + throw + } + + $tmp = $env:TEMP ? $env:TEMP : [System.IO.Path]::GetTempPath() + $templateFilePath = Join-Path $tmp "test-resources.$(New-Guid).compiled.json" + + # Az can deploy bicep files natively, but by compiling here it becomes easier to parse the + # outputted json for mismatched parameter declarations. + bicep build $file.FullName --outfile $templateFilePath + if ($LASTEXITCODE) { + Write-Error "Failure building bicep file '$($file.FullName)'" + throw + } + + return $templateFilePath +} + # Support actions to invoke on exit. $exitActions = @({ if ($exitActions.Count -gt 1) { @@ -140,15 +160,18 @@ try { # Enumerate test resources to deploy. Fail if none found. $repositoryRoot = "$PSScriptRoot/../../.." | Resolve-Path $root = [System.IO.Path]::Combine($repositoryRoot, "sdk", $ServiceDirectory) | Resolve-Path - $templateFileName = 'test-resources.json' $templateFiles = @() - Write-Verbose "Checking for '$templateFileName' files under '$root'" - Get-ChildItem -Path $root -Filter $templateFileName -Recurse | ForEach-Object { - $templateFile = $_.FullName - - Write-Verbose "Found template '$templateFile'" - $templateFiles += $templateFile + 'test-resources.json', 'test-resources.bicep' | ForEach-Object { + Write-Verbose "Checking for '$_' files under '$root'" + Get-ChildItem -Path $root -Filter "$_" -Recurse | ForEach-Object { + Write-Verbose "Found template '$($_.FullName)'" + if ($_.Extension -eq '.bicep') { + $templateFiles += (BuildBicepFile $_) + } else { + $templateFiles += $_.FullName + } + } } if (!$templateFiles) { @@ -556,6 +579,11 @@ try { Log "Invoking post-deployment script '$postDeploymentScript'" &$postDeploymentScript -ResourceGroupName $ResourceGroupName -DeploymentOutputs $deploymentOutputs @PSBoundParameters } + + if ($templateFile.EndsWith('.compiled.json')) { + Write-Verbose "Removing compiled bicep file $templateFile" + Remove-Item $templateFile + } } } finally { diff --git a/eng/common/TestResources/deploy-test-resources.yml b/eng/common/TestResources/deploy-test-resources.yml index ac05e14c79..7eeeda847f 100644 --- a/eng/common/TestResources/deploy-test-resources.yml +++ b/eng/common/TestResources/deploy-test-resources.yml @@ -58,3 +58,5 @@ steps: -Force ` -Verbose | Out-Null displayName: Deploy test resources + env: + TEMP: $(Agent.TempDirectory) diff --git a/eng/common/docgeneration/Generate-DocIndex.ps1 b/eng/common/docgeneration/Generate-DocIndex.ps1 index 82b3a75e4e..cf9b5f06ff 100644 --- a/eng/common/docgeneration/Generate-DocIndex.ps1 +++ b/eng/common/docgeneration/Generate-DocIndex.ps1 @@ -177,5 +177,5 @@ else { LogWarning "The function for 'GetGithubIoDocIndexFn' was not found.` Make sure it is present in eng/scripts/Language-Settings.ps1 and referenced in eng/common/scripts/common.ps1.` - See https://github.com/Azure/azure-sdk-tools/blob/master/doc/common/common_engsys.md#code-structure" + See https://github.com/Azure/azure-sdk-tools/blob/main/doc/common/common_engsys.md#code-structure" } diff --git a/eng/common/pipelines/templates/jobs/archetype-sdk-tests-generate.yml b/eng/common/pipelines/templates/jobs/archetype-sdk-tests-generate.yml index 2bf2bd97cd..a392065ad3 100644 --- a/eng/common/pipelines/templates/jobs/archetype-sdk-tests-generate.yml +++ b/eng/common/pipelines/templates/jobs/archetype-sdk-tests-generate.yml @@ -27,10 +27,10 @@ parameters: default: [] - name: Pool type: string - default: azsdk-pool-mms-ubuntu-1804-general + default: azsdk-pool-mms-ubuntu-2004-general - name: OsVmImage type: string - default: MMSUbuntu18.04 + default: MMSUbuntu20.04 # This parameter is only necessary if there are multiple invocations of this template within the SAME STAGE. # When that occurs, provide a name other than the default value. - name: GenerateJobName diff --git a/eng/common/pipelines/templates/steps/bypass-local-dns.yml b/eng/common/pipelines/templates/steps/bypass-local-dns.yml index c0608dbf64..8b4887870f 100644 --- a/eng/common/pipelines/templates/steps/bypass-local-dns.yml +++ b/eng/common/pipelines/templates/steps/bypass-local-dns.yml @@ -8,7 +8,9 @@ steps: succeededOrFailed(), or( eq(variables['OSVmImage'], 'ubuntu-18.04'), - eq(variables['OSVmImage'], 'MMSUbuntu18.04') + eq(variables['OSVmImage'], 'ubuntu-20.04'), + eq(variables['OSVmImage'], 'MMSUbuntu18.04'), + eq(variables['OSVmImage'], 'MMSUbuntu20.04') ), eq(variables['Container'], '') ) diff --git a/eng/common/pipelines/templates/steps/check-spelling.yml b/eng/common/pipelines/templates/steps/check-spelling.yml index 3865a3f26e..986d729fc3 100644 --- a/eng/common/pipelines/templates/steps/check-spelling.yml +++ b/eng/common/pipelines/templates/steps/check-spelling.yml @@ -2,7 +2,7 @@ # and some ref (branch, tag, etc.) or commit hash. Only runs on PRs. # ContinueOnError - true: Pipeline warns on spelling error # false: Pipeline fails on spelling error -# TargetBranch - Target ref (e.g. master) to compare to create file change +# TargetBranch - Target ref (e.g. main) to compare to create file change # list. # CspellConfigPath - Path to cspell.json config location diff --git a/eng/common/pipelines/templates/steps/docs-metadata-release.yml b/eng/common/pipelines/templates/steps/docs-metadata-release.yml index b3c8444225..2f58b90d4d 100644 --- a/eng/common/pipelines/templates/steps/docs-metadata-release.yml +++ b/eng/common/pipelines/templates/steps/docs-metadata-release.yml @@ -26,7 +26,7 @@ parameters: default: '' - name: PRBranchName type: string - default: 'master-rdme' + default: 'main-rdme' - name: PRLabels type: string default: 'auto-merge' diff --git a/eng/common/pipelines/templates/steps/enable-long-path-support.yml b/eng/common/pipelines/templates/steps/enable-long-path-support.yml new file mode 100644 index 0000000000..1e0db13d75 --- /dev/null +++ b/eng/common/pipelines/templates/steps/enable-long-path-support.yml @@ -0,0 +1,10 @@ +steps: +- pwsh: | + if ($IsWindows) { + REG ADD HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\FileSystem /f /v LongPathsEnabled /t REG_DWORD /d 1 + git config --system core.longpaths true + } + else { + Write-Host "This script is not executing on Windows, skipping registry modification." + } + displayName: Enable long path support if necessary diff --git a/eng/common/pipelines/templates/steps/eng-common-workflow-enforcer.yml b/eng/common/pipelines/templates/steps/eng-common-workflow-enforcer.yml index 0125e6cbac..b68e1cd0c2 100644 --- a/eng/common/pipelines/templates/steps/eng-common-workflow-enforcer.yml +++ b/eng/common/pipelines/templates/steps/eng-common-workflow-enforcer.yml @@ -16,7 +16,7 @@ steps: if (($LASTEXITCODE -eq 0) -and ($filesInCommonDir.Count -gt 0)) { Write-Host "##vso[task.LogIssue type=error;]Changes to files under 'eng/common' directory should not be made in this Repo`n${filesInCommonDir}" - Write-Host "##vso[task.LogIssue type=error;]Please follow workflow at https://github.com/Azure/azure-sdk-tools/blob/master/doc/common/common_engsys.md" + Write-Host "##vso[task.LogIssue type=error;]Please follow workflow at https://github.com/Azure/azure-sdk-tools/blob/main/doc/common/common_engsys.md" exit 1 } } diff --git a/eng/common/pipelines/templates/steps/install-pipeline-generation.yml b/eng/common/pipelines/templates/steps/install-pipeline-generation.yml index d0da703285..d368d52595 100644 --- a/eng/common/pipelines/templates/steps/install-pipeline-generation.yml +++ b/eng/common/pipelines/templates/steps/install-pipeline-generation.yml @@ -9,7 +9,7 @@ steps: - script: > dotnet tool install Azure.Sdk.Tools.PipelineGenerator - --version 1.0.2-dev.20210309.1 + --version 1.0.2-dev.20210621.4 --add-source https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk/nuget/v3/index.json --tool-path ${{parameters.ToolPath}} workingDirectory: $(Pipeline.Workspace)/pipeline-generator diff --git a/eng/common/pipelines/templates/steps/set-daily-docs-branch-name.yml b/eng/common/pipelines/templates/steps/set-daily-docs-branch-name.yml new file mode 100644 index 0000000000..1548a9326f --- /dev/null +++ b/eng/common/pipelines/templates/steps/set-daily-docs-branch-name.yml @@ -0,0 +1,14 @@ +parameters: + - name: DailyBranchVariableName + type: string + default: TargetBranchName + +steps: + - pwsh: | + $branchName = $env:DAILYDOCSBRANCHNAMEOVERRIDE + if (!$branchName) { + $branchName = "daily/$(Get-Date -Format 'yyyy-MM-dd')" + } + Write-Host "Daily Branch Name: $branchName" + Write-Host "##vso[task.setvariable variable=${{ parameters.DailyBranchVariableName }};]$branchName" + displayName: Set daily docs branch name in $(${{ parameters.DailyBranchVariableName }}) diff --git a/eng/common/pipelines/templates/steps/set-default-branch.yml b/eng/common/pipelines/templates/steps/set-default-branch.yml index e3eed4512d..31bf289361 100644 --- a/eng/common/pipelines/templates/steps/set-default-branch.yml +++ b/eng/common/pipelines/templates/steps/set-default-branch.yml @@ -1,15 +1,16 @@ parameters: WorkingDirectory: '$(System.DefaultWorkingDirectory)' RemoteRepo: 'origin' + DefaultBranchVariableName: DefaultBranch steps: - pwsh: | $setDefaultBranch = (git remote show ${{ parameters.RemoteRepo }} | Out-String) -replace "(?ms).*HEAD branch: (\w+).*", '$1' if ($LASTEXITCODE -ne 0) { - Write-Host "Not able to fetch the default branch from git command. Set to master." - $setDefaultBranch = 'master' + Write-Host "Not able to fetch the default branch from git command. Set to main." + $setDefaultBranch = 'main' } - Write-Host "Setting DefaultBranch=$setDefaultBranch" - Write-Host "##vso[task.setvariable variable=DefaultBranch]$setDefaultBranch" + Write-Host "Setting ${{ parameters.DefaultBranchVariableName }}=$setDefaultBranch" + Write-Host "##vso[task.setvariable variable=${{ parameters.DefaultBranchVariableName }}]$setDefaultBranch" displayName: "Setup Default Branch" workingDirectory: ${{ parameters.workingDirectory }} ignoreLASTEXITCODE: true diff --git a/eng/common/pipelines/templates/steps/update-docsms-metadata.yml b/eng/common/pipelines/templates/steps/update-docsms-metadata.yml new file mode 100644 index 0000000000..341169b373 --- /dev/null +++ b/eng/common/pipelines/templates/steps/update-docsms-metadata.yml @@ -0,0 +1,95 @@ +parameters: + - name: PackageInfoLocations + type: object + default: [] + - name: RepoId + type: string + default: $(Build.Repository.Name) + - name: WorkingDirectory + type: string + default: '' + - name: ScriptDirectory + type: string + default: eng/common/scripts + - name: TargetDocRepoName + type: string + default: '' + - name: TargetDocRepoOwner + type: string + - name: Language + type: string + default: '' + - name: DailyDocsBuild + type: boolean + default: false + - name: SparseCheckoutPaths + type: object + default: + - '**' + +steps: +- template: /eng/common/pipelines/templates/steps/enable-long-path-support.yml + +- pwsh: | + Write-Host "###vso[task.setvariable variable=DocRepoLocation]${{ parameters.WorkingDirectory }}/doc" + displayName: Set $(DocRepoLocation) + +- template: /eng/common/pipelines/templates/steps/sparse-checkout.yml + parameters: + SkipDefaultCheckout: true + Repositories: + - Name: ${{ parameters.TargetDocRepoOwner }}/${{ parameters.TargetDocRepoName }} + WorkingDirectory: $(DocRepoLocation) + Paths: ${{ parameters.SparseCheckoutPaths }} + +# If performing a daily docs build set the $(TargetBranchName) to a daily branch +# name and attempt to checkout the daily docs branch. If the branch doesn't +# exist, create it +- ${{ if eq(parameters.DailyDocsBuild, 'true') }}: + - template: /eng/common/pipelines/templates/steps/set-daily-docs-branch-name.yml + + - pwsh: | + $ErrorActionPreference = "Continue" + $RemoteName = "origin" + $BranchName = "$(TargetBranchName)" + # Fetch and checkout remote branch if it already exists otherwise create a new branch. + git ls-remote --exit-code --heads $RemoteName $BranchName + if ($LASTEXITCODE -eq 0) { + Write-Host "git fetch $RemoteName $BranchName" + git fetch $RemoteName $BranchName + Write-Host "git checkout $BranchName." + git checkout $BranchName + } else { + Write-Host "git checkout -b $BranchName." + git checkout -b $BranchName + } + displayName: Checkout daily docs branch if it exists + workingDirectory: $(DocRepoLocation) + +# If NOT performing a daily docs build, set the $(TargetBranchName) to the +# default branch of the documentation repository. +- ${{ if ne(parameters.DailyDocsBuild, 'true') }}: + - template: /eng/common/pipelines/templates/steps/set-default-branch.yml + parameters: + WorkingDirectory: $(DocRepoLocation) + DefaultBranchVariableName: TargetBranchName + +- pwsh: | + $packageInfoJson = '${{ convertToJson(parameters.PackageInfoLocations) }}'.Trim('"') + $packageInfoLocations = ConvertFrom-Json $packageInfoJson + ${{ parameters.ScriptDirectory }}/Update-DocsMsMetadata.ps1 ` + -PackageInfoJsonLocations $packageInfoLocations ` + -DocRepoLocation "$(DocRepoLocation)" ` + -Language '${{parameters.Language}}' ` + -RepoId '${{ parameters.RepoId }}' + displayName: Apply Documentation Updates + +- template: /eng/common/pipelines/templates/steps/git-push-changes.yml + parameters: + BaseRepoBranch: $(TargetBranchName) + BaseRepoOwner: ${{ parameters.TargetDocRepoOwner }} + CommitMsg: "Update docs metadata" + TargetRepoName: ${{ parameters.TargetDocRepoName }} + TargetRepoOwner: ${{ parameters.TargetDocRepoOwner }} + WorkingDirectory: $(DocRepoLocation) + ScriptDirectory: ${{ parameters.WorkingDirectory }}/${{ parameters.ScriptDirectory }} diff --git a/eng/common/scripts/ChangeLog-Operations.ps1 b/eng/common/scripts/ChangeLog-Operations.ps1 index 6a161c1b4d..25423b0ea8 100644 --- a/eng/common/scripts/ChangeLog-Operations.ps1 +++ b/eng/common/scripts/ChangeLog-Operations.ps1 @@ -5,6 +5,7 @@ $RELEASE_TITLE_REGEX = "(?^\#+\s+(?$([AzureEngSemanticVersion]::SEMVER_REGEX))(\s+(?\(.+\))))" $CHANGELOG_UNRELEASED_STATUS = "(Unreleased)" $CHANGELOG_DATE_FORMAT = "yyyy-MM-dd" +$RecommendedSectionHeaders = @("Features Added", "Breaking Changes", "Bugs Fixed", "Other Changes") # Returns a Collection of changeLogEntry object containing changelog info for all version present in the gived CHANGELOG function Get-ChangeLogEntries { @@ -109,7 +110,6 @@ function Get-ChangeLogEntryAsString { return ChangeLogEntryAsString $changeLogEntry } - function ChangeLogEntryAsString($changeLogEntry) { if (!$changeLogEntry) { return "[Missing change log entry]" @@ -141,13 +141,13 @@ function Confirm-ChangeLogEntry { Write-Host "-----" if ([System.String]::IsNullOrEmpty($changeLogEntry.ReleaseStatus)) { - LogError "Entry does not have a correct release status. Please ensure the status is set to a date '($CHANGELOG_DATE_FORMAT)' or '$CHANGELOG_UNRELEASED_STATUS' if not yet released." + LogError "Entry does not have a correct release status. Please ensure the status is set to a date '($CHANGELOG_DATE_FORMAT)' or '$CHANGELOG_UNRELEASED_STATUS' if not yet released. See https://aka.ms/azsdk/guideline/changelogs for more info." return $false } if ($ForRelease -eq $True) { if ($changeLogEntry.ReleaseStatus -eq $CHANGELOG_UNRELEASED_STATUS) { - LogError "Entry has no release date set. Please ensure to set a release date with format '$CHANGELOG_DATE_FORMAT'." + LogError "Entry has no release date set. Please ensure to set a release date with format '$CHANGELOG_DATE_FORMAT'. See https://aka.ms/azsdk/guideline/changelogs for more info." return $false } else { @@ -156,26 +156,27 @@ function Confirm-ChangeLogEntry { $releaseDate = [DateTime]$status if ($status -ne ($releaseDate.ToString($CHANGELOG_DATE_FORMAT))) { - LogError "Date must be in the format $($CHANGELOG_DATE_FORMAT)" + LogError "Date must be in the format $($CHANGELOG_DATE_FORMAT). See https://aka.ms/azsdk/guideline/changelogs for more info." return $false } if (((Get-Date).AddMonths(-1) -gt $releaseDate) -or ($releaseDate -gt (Get-Date).AddMonths(1))) { - LogError "The date must be within +/- one month from today." + LogError "The date must be within +/- one month from today. See https://aka.ms/azsdk/guideline/changelogs for more info." return $false } } catch { - LogError "Invalid date [ $status ] passed as status for Version [$($changeLogEntry.ReleaseVersion)]." + LogError "Invalid date [ $status ] passed as status for Version [$($changeLogEntry.ReleaseVersion)]. See https://aka.ms/azsdk/guideline/changelogs for more info." return $false } } if ([System.String]::IsNullOrWhiteSpace($changeLogEntry.ReleaseContent)) { - LogError "Entry has no content. Please ensure to provide some content of what changed in this version." + LogError "Entry has no content. Please ensure to provide some content of what changed in this version. See https://aka.ms/azsdk/guideline/changelogs for more info." return $false } + $foundRecomendedSection = $false $emptySections = @() foreach ($key in $changeLogEntry.Sections.Keys) { @@ -184,12 +185,20 @@ function Confirm-ChangeLogEntry { { $emptySections += $key } + if ($RecommendedSectionHeaders -contains $key) + { + $foundRecomendedSection = $true + } } if ($emptySections.Count -gt 0) { LogError "The changelog entry has the following sections with no content ($($emptySections -join ', ')). Please ensure to either remove the empty sections or add content to the section." return $false } + if (!$foundRecomendedSection) + { + LogWarning "The changelog entry did not contain any of the recommended sections ($($RecommendedSectionHeaders -join ', ')), pease add at least one. See https://aka.ms/azsdk/guideline/changelogs for more info." + } } return $true } @@ -228,15 +237,12 @@ function New-ChangeLogEntry { if (!$Content) { $Content = @() $Content += "" - $Content += "### Features Added" - $Content += "" - $Content += "### Breaking Changes" - $Content += "" - $Content += "### Key Bugs Fixed" - $Content += "" - $Content += "### Fixed" - $Content += "" - $Content += "" + + foreach ($recommendedHeader in $RecommendedSectionHeaders) + { + $Content += "### $recommendedHeader" + $Content += "" + } } $newChangeLogEntry = [pscustomobject]@{ diff --git a/eng/common/scripts/Create-APIReview.ps1 b/eng/common/scripts/Create-APIReview.ps1 index 8b90072532..456d249c9c 100644 --- a/eng/common/scripts/Create-APIReview.ps1 +++ b/eng/common/scripts/Create-APIReview.ps1 @@ -71,7 +71,7 @@ else { Write-Host "The function for 'FindArtifactForApiReviewFn' was not found.` Make sure it is present in eng/scripts/Language-Settings.ps1 and referenced in eng/common/scripts/common.ps1.` - See https://github.com/Azure/azure-sdk-tools/blob/master/doc/common/common_engsys.md#code-structure" + See https://github.com/Azure/azure-sdk-tools/blob/main/doc/common/common_engsys.md#code-structure" exit(1) } @@ -104,7 +104,7 @@ if ($packages) Write-Host "Version: $($version)" Write-Host "SDK Type: $($pkgInfo.SdkType)" - # Run create review step only if build is triggered from master branch or if version is GA. + # Run create review step only if build is triggered from main branch or if version is GA. # This is to avoid invalidating review status by a build triggered from feature branch if ( ($SourceBranch -eq $DefaultBranch) -or (-not $version.IsPrerelease)) { @@ -121,6 +121,10 @@ if ($packages) # Ignore API review status for prerelease version Write-Host "Package version is not GA. Ignoring API view approval status" } + elseif (!$pkgInfo.ReleaseStatus -or $pkgInfo.ReleaseStatus -eq "Unreleased") + { + Write-Host "Release date is not set for current version in change log file for package. Ignoring API review approval status since package is not yet ready for release." + } else { # Return error code if status code is 201 for new data plane package diff --git a/eng/common/scripts/Helpers/DevOps-WorkItem-Helpers.ps1 b/eng/common/scripts/Helpers/DevOps-WorkItem-Helpers.ps1 index 3897cfa816..aca34080da 100644 --- a/eng/common/scripts/Helpers/DevOps-WorkItem-Helpers.ps1 +++ b/eng/common/scripts/Helpers/DevOps-WorkItem-Helpers.ps1 @@ -12,6 +12,77 @@ function Invoke-AzBoardsCmd($subCmd, $parameters, $output = $true) return Invoke-Expression "$azCmdStr" | ConvertFrom-Json -AsHashTable } +function Invoke-Query($fields, $wiql, $output = $true) +{ + #POST https://dev.azure.com/{organization}/{project}/{team}/_apis/wit/wiql?timePrecision={timePrecision}&$top={$top}&api-version=6.1-preview.2 + + $body = @" +{ + "query": "$wiql" +} +"@ + + if ($output) { + Write-Host "Executing query $wiql" + } + + $headers = $null + if (Get-Variable -Name "devops_pat" -ValueOnly -ErrorAction "Ignore") + { + $encodedToken = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes([string]::Format("{0}:{1}", "", $devops_pat))) + $headers = @{ Authorization = "Basic $encodedToken" } + } + else + { + # Get a temp access token from the logged in az cli user for azure devops resource + $jwt_accessToken = (az account get-access-token --resource "499b84ac-1321-427f-aa17-267ca6975798" --query "accessToken" --output tsv) + $headers = @{ Authorization = "Bearer $jwt_accessToken" } + } + $response = Invoke-RestMethod -Method POST ` + -Uri "https://dev.azure.com/azure-sdk/Release/_apis/wit/wiql/?`$top=10000&api-version=6.0" ` + -Headers $headers -Body $body -ContentType "application/json" | ConvertTo-Json -Depth 10 | ConvertFrom-Json -AsHashTable + + if (!$response.workItems) { + Write-Verbose "Query returned no items. $wiql" + return ,@() + } + + $workItems = @() + $i = 0 + do + { + $idBatch = @() + while ($idBatch.Count -lt 200 -and $i -lt $response.workItems.Count) + { + $idBatch += $response.workItems[$i].id + $i++ + } + + $uri = "https://dev.azure.com/azure-sdk/Release/_apis/wit/workitems?ids=$($idBatch -join ',')&fields=$($fields -join ',')&api-version=6.0" + + Write-Verbose "Pulling work items $uri " + + $batchResponse = Invoke-RestMethod -Method GET -Uri $uri ` + -Headers $headers -ContentType "application/json" -MaximumRetryCount 3 | ConvertTo-Json -Depth 10 | ConvertFrom-Json -AsHashTable + + if ($batchResponse.value) + { + $batchResponse.value | % { $workItems += $_ } + } + else + { + Write-Warning "Batch return no items from $uri" + } + } + while ($i -lt $response.workItems.Count) + + if ($output) { + Write-Host "Query return $($workItems.Count) items" + } + + return $workItems +} + function LoginToAzureDevops([string]$devops_pat) { if (!$devops_pat) { @@ -64,13 +135,14 @@ function FindParentWorkItem($serviceName, $packageDisplayName, $outputCommand = $serviceCondition = "[ServiceName] <> ''" } - $parameters = $ReleaseDevOpsCommonParametersWithProject - $parameters += "--wiql" - $parameters += "`"SELECT [ID], [ServiceName], [PackageDisplayName], [Parent] FROM WorkItems WHERE [Work Item Type] = 'Epic' AND ${serviceCondition}`"" + $query = "SELECT [ID], [ServiceName], [PackageDisplayName], [Parent] FROM WorkItems WHERE [Work Item Type] = 'Epic' AND ${serviceCondition}" - $workItems = Invoke-AzBoardsCmd "query" $parameters $outputCommand + $fields = @("System.Id", "Custom.ServiceName", "Custom.PackageDisplayName", "System.Parent") - foreach ($wi in $workItems) { + $workItems = Invoke-Query $fields $query $outputCommand + + foreach ($wi in $workItems) + { $localKey = BuildHashKey $wi.fields["Custom.ServiceName"] $wi.fields["Custom.PackageDisplayName"] if (!$localKey) { continue } if ($parentWorkItems.ContainsKey($localKey) -and $parentWorkItems[$localKey].id -ne $wi.id) { @@ -107,9 +179,7 @@ function FindLatestPackageWorkItem($lang, $packageName, $outputCommand = $true) continue } - # Note this only does string sorting which is enough for our current usages - # if we need absolute sorting at some point we would need to parse these versions - if ($wi.fields["Custom.PackageVersionMajorMinor"] -gt $latestWI.fields["Custom.PackageVersionMajorMinor"]) { + if (($wi.fields["Custom.PackageVersionMajorMinor"] -as [Version]) -gt ($latestWI.fields["Custom.PackageVersionMajorMinor"] -as [Version])) { $latestWI = $wi } } @@ -124,26 +194,26 @@ function FindPackageWorkItem($lang, $packageName, $version, $outputCommand = $tr } $fields = @() - $fields += "ID" - $fields += "State" + $fields += "System.ID" + $fields += "System.State" $fields += "System.AssignedTo" - $fields += "Parent" - $fields += "Language" - $fields += "Package" - $fields += "PackageDisplayName" - $fields += "Title" - $fields += "PackageType" - $fields += "PackageTypeNewLibrary" - $fields += "PackageVersionMajorMinor" - $fields += "PackageRepoPath" - $fields += "ServiceName" - $fields += "Planned Packages" - $fields += "Shipped Packages" - $fields += "PackageBetaVersions" - $fields += "PackageGAVersion" - $fields += "PackagePatchVersions" - $fields += "Generated" - $fields += "RoadmapState" + $fields += "System.Parent" + $fields += "Custom.Language" + $fields += "Custom.Package" + $fields += "Custom.PackageDisplayName" + $fields += "System.Title" + $fields += "Custom.PackageType" + $fields += "Custom.PackageTypeNewLibrary" + $fields += "Custom.PackageVersionMajorMinor" + $fields += "Custom.PackageRepoPath" + $fields += "Custom.ServiceName" + $fields += "Custom.PlannedPackages" + $fields += "Custom.ShippedPackages" + $fields += "Custom.PackageBetaVersions" + $fields += "Custom.PackageGAVersion" + $fields += "Custom.PackagePatchVersions" + $fields += "Custom.Generated" + $fields += "Custom.RoadmapState" $fieldList = ($fields | ForEach-Object { "[$_]"}) -join ", " $query = "SELECT ${fieldList} FROM WorkItems WHERE [Work Item Type] = 'Package'" @@ -160,14 +230,8 @@ function FindPackageWorkItem($lang, $packageName, $version, $outputCommand = $tr if ($version) { $query += " AND [PackageVersionMajorMinor] = '${version}'" } - $parameters = $ReleaseDevOpsCommonParametersWithProject - $parameters += "--wiql", "`"${query}`"" - - $workItems = Invoke-AzBoardsCmd "query" $parameters $outputCommand - if ($workItems -and $workItems.Count -eq 1000) { - Write-Warning "Retrieved the max of 1000 items so item list might not be complete." - } + $workItems = Invoke-Query $fields $query $outputCommand foreach ($wi in $workItems) { @@ -316,8 +380,13 @@ function FindOrCreateClonePackageWorkItem($lang, $pkg, $verMajorMinor, $allowPro $pkg.RepoPath = $pkg.fields["Custom.PackageRepoPath"] } - $extraFields += "`"Generated=" + $latestVersionItem.fields["Custom.Generated"] + "`"" - $extraFields += "`"RoadmapState=" + $latestVersionItem.fields["Custom.RoadmapState"] + "`"" + if ($latestVersionItem.fields["Custom.Generated"]) { + $extraFields += "`"Generated=" + $latestVersionItem.fields["Custom.Generated"] + "`"" + } + + if ($latestVersionItem.fields["Custom.RoadmapState"]) { + $extraFields += "`"RoadmapState=" + $latestVersionItem.fields["Custom.RoadmapState"] + "`"" + } } if ($allowPrompt) { diff --git a/eng/common/scripts/Invoke-DevOpsAPI.ps1 b/eng/common/scripts/Invoke-DevOpsAPI.ps1 index 32c3569a3c..72d61edd08 100644 --- a/eng/common/scripts/Invoke-DevOpsAPI.ps1 +++ b/eng/common/scripts/Invoke-DevOpsAPI.ps1 @@ -19,7 +19,9 @@ function Start-DevOpsBuild { $DefinitionId, [ValidateNotNullOrEmpty()] [Parameter(Mandatory = $true)] - $Base64EncodedAuthToken + $Base64EncodedAuthToken, + [Parameter(Mandatory = $false)] + [string]$BuildParametersJson ) $uri = "$DevOpsAPIBaseURI" -F $Organization, $Project , "build" , "builds", "" @@ -27,6 +29,7 @@ function Start-DevOpsBuild { $parameters = @{ sourceBranch = $SourceBranch definition = @{ id = $DefinitionId } + parameters = $BuildParametersJson } return Invoke-RestMethod ` @@ -157,4 +160,4 @@ function Add-RetentionLease { -MaximumRetryCount 3 ` -ContentType "application/json" -} \ No newline at end of file +} diff --git a/eng/common/scripts/Package-Properties.ps1 b/eng/common/scripts/Package-Properties.ps1 index 3aadc8d580..acbb66e9d2 100644 --- a/eng/common/scripts/Package-Properties.ps1 +++ b/eng/common/scripts/Package-Properties.ps1 @@ -5,6 +5,7 @@ class PackageProps { [string]$Name [string]$Version + [string]$DevVersion [string]$DirectoryPath [string]$ServiceDirectory [string]$ReadMePath @@ -13,6 +14,7 @@ class PackageProps [string]$SdkType [boolean]$IsNewSdk [string]$ArtifactName + [string]$ReleaseStatus PackageProps([string]$name, [string]$version, [string]$directoryPath, [string]$serviceDirectory) { @@ -48,6 +50,12 @@ class PackageProps if (Test-Path (Join-Path $directoryPath "CHANGELOG.md")) { $this.ChangeLogPath = Join-Path $directoryPath "CHANGELOG.md" + # Get release date for current version and set in package property + $changeLogEntry = Get-ChangeLogEntry -ChangeLogLocation $this.ChangeLogPath -VersionString $this.Version + if ($changeLogEntry -and $changeLogEntry.ReleaseStatus) + { + $this.ReleaseStatus = $changeLogEntry.ReleaseStatus.Trim().Trim("()") + } } else { @@ -126,7 +134,7 @@ function Get-AllPkgProperties ([string]$ServiceDirectory = $null) return $pkgPropsResult } -# Given the metadata url under https://github.com/Azure/azure-sdk/tree/master/_data/releases/latest, +# Given the metadata url under https://github.com/Azure/azure-sdk/tree/main/_data/releases/latest, # the function will return the csv metadata back as part of response. function Get-CSVMetadata ([string]$MetadataUri=$MetadataUri) { @@ -143,7 +151,7 @@ function Get-PkgPropsForEntireService ($serviceDirectoryPath) { LogError "The function for '$GetPackageInfoFromRepoFn' was not found.` Make sure it is present in eng/scripts/Language-Settings.ps1 and referenced in eng/common/scripts/common.ps1.` - See https://github.com/Azure/azure-sdk-tools/blob/master/doc/common/common_engsys.md#code-structure" + See https://github.com/Azure/azure-sdk-tools/blob/main/doc/common/common_engsys.md#code-structure" } foreach ($directory in (Get-ChildItem $serviceDirectoryPath -Directory)) diff --git a/eng/common/scripts/Prepare-Release.ps1 b/eng/common/scripts/Prepare-Release.ps1 index eed113299a..95f175d3fd 100644 --- a/eng/common/scripts/Prepare-Release.ps1 +++ b/eng/common/scripts/Prepare-Release.ps1 @@ -180,7 +180,7 @@ else { LogError "The function 'SetPackageVersion' was not found.` Make sure it is present in eng/scripts/Language-Settings.ps1.` - See https://github.com/Azure/azure-sdk-tools/blob/master/doc/common/common_engsys.md#code-structure" + See https://github.com/Azure/azure-sdk-tools/blob/main/doc/common/common_engsys.md#code-structure" exit 1 } diff --git a/eng/common/scripts/Queue-Pipeline.ps1 b/eng/common/scripts/Queue-Pipeline.ps1 index a8c147e2ef..30a4282c8c 100644 --- a/eng/common/scripts/Queue-Pipeline.ps1 +++ b/eng/common/scripts/Queue-Pipeline.ps1 @@ -18,7 +18,10 @@ param( [string]$VsoQueuedPipelines, [Parameter(Mandatory = $true)] - [string]$Base64EncodedAuthToken + [string]$Base64EncodedAuthToken, + + [Parameter(Mandatory = $false)] + [string]$BuildParametersJson ) . (Join-Path $PSScriptRoot common.ps1) @@ -46,7 +49,13 @@ if ($CancelPreviousBuilds) } try { - $resp = Start-DevOpsBuild -SourceBranch $SourceBranch -DefinitionId $DefinitionId -Base64EncodedAuthToken $Base64EncodedAuthToken + $resp = Start-DevOpsBuild ` + -Organization $Organization ` + -Project $Project ` + -SourceBranch $SourceBranch ` + -DefinitionId $DefinitionId ` + -Base64EncodedAuthToken $Base64EncodedAuthToken ` + -BuildParametersJson $BuildParametersJson } catch { LogError "Start-DevOpsBuild failed with exception:`n$_" @@ -64,4 +73,4 @@ if ($VsoQueuedPipelines) { } $QueuedPipelineLinks Write-Host "##vso[task.setvariable variable=$VsoQueuedPipelines]$QueuedPipelineLinks" -} \ No newline at end of file +} diff --git a/eng/common/scripts/Save-Package-Properties.ps1 b/eng/common/scripts/Save-Package-Properties.ps1 index 3de8943490..7e51813830 100644 --- a/eng/common/scripts/Save-Package-Properties.ps1 +++ b/eng/common/scripts/Save-Package-Properties.ps1 @@ -1,12 +1,79 @@ +<# +.SYNOPSIS +Saves package properties from source into JSON files + +.DESCRIPTION +Saves package properties in source of a given service directory to JSON files. +JSON files are named in the form .json or .json if +an artifact name property is available in the package properties. + +Can optionally add a dev version property which can be used logic for daily +builds. + +.PARAMETER serviceDirectory +Service directory in which to search for packages + +.PARAMETER outDirectory +Output location (generally a package artifact directory in DevOps) for JSON +files + +.PARAMETER addDevVersion +Reads the version out of the source and adds a DevVersion property to the +package properties JSON file. If the package properties JSON file already +exists, read the Version property from the existing package properties JSON file +and set that as the Version property for the new output. This has the effect of +"adding" a DevVersion property to the file which could be different from the +Verison property in that file. +#> + [CmdletBinding()] Param ( [Parameter(Mandatory=$True)] [string] $serviceDirectory, [Parameter(Mandatory=$True)] - [string] $outDirectory + [string] $outDirectory, + [switch] $addDevVersion ) . (Join-Path $PSScriptRoot common.ps1) + +function SetOutput($outputPath, $incomingPackageSpec) { + $outputObject = $incomingPackageSpec + + if ($addDevVersion) { + # Use the "Version" property which was provided by the incoming package spec + # as the DevVersion. This may be overridden later. + $outputObject.DevVersion = $incomingPackageSpec.Version + + # If there is an exsiting package info json file read that and set the + # Version property from that JSON file. + if (Test-Path $outputPath) { + $originalObject = ConvertFrom-Json (Get-Content $outputPath -Raw) + $outputObject.Version = $originalObject.Version + } + } + + # Set file paths to relative paths + $outputObject.DirectoryPath = GetRelativePath $outputObject.DirectoryPath + $outputObject.ReadMePath = GetRelativePath $outputObject.ReadMePath + $outputObject.ChangeLogPath = GetRelativePath $outputObject.ChangeLogPath + + Set-Content ` + -Path $outputPath ` + -Value (ConvertTo-Json -InputObject $outputObject -Depth 100) +} + +function GetRelativePath($path) { + # If the path is empty return an empty string + if (!$path) { + return '' + } + $relativeTo = Resolve-Path $PSScriptRoot/../../../ + # Replace "\" with "/" so the path is valid across other platforms and tools + $relativePath = [IO.Path]::GetRelativePath($relativeTo, $path) -replace "\\", '/' + return $relativePath +} + $allPackageProperties = Get-AllPkgProperties $serviceDirectory if ($allPackageProperties) { @@ -22,15 +89,15 @@ if ($allPackageProperties) Write-Host "Package Version: $($pkg.Version)" Write-Host "Package SDK Type: $($pkg.SdkType)" Write-Host "Artifact Name: $($pkg.ArtifactName)" + Write-Host "Release date: $($pkg.ReleaseStatus)" $configFilePrefix = $pkg.Name if ($pkg.ArtifactName) { $configFilePrefix = $pkg.ArtifactName } $outputPath = Join-Path -Path $outDirectory "$configFilePrefix.json" - $outputObject = $pkg | ConvertTo-Json - Set-Content -Path $outputPath -Value $outputObject - } + SetOutput $outputPath $pkg + } } Get-ChildItem -Path $outDirectory diff --git a/eng/common/scripts/Update-DocsMsMetadata.ps1 b/eng/common/scripts/Update-DocsMsMetadata.ps1 new file mode 100644 index 0000000000..fccb663fa5 --- /dev/null +++ b/eng/common/scripts/Update-DocsMsMetadata.ps1 @@ -0,0 +1,164 @@ +<# +.SYNOPSIS +Updates package README.md for publishing to docs.microsoft.com + +.DESCRIPTION +Given a PackageInfo .json file, format the package README.md file with metadata +and other information needed to release reference docs: + +* Adjust README.md content to include metadata +* Insert the package verison number in the README.md title +* Copy file to the appropriate location in the documentation repository +* Copy PackageInfo .json file to the metadata location in the reference docs + repository. This enables the Docs CI build to onboard packages which have not + shipped and for which there are no entries in the metadata CSV files. + +.PARAMETER PackageInfoJsonLocations +List of locations of the artifact information .json file. This is usually stored +in build artifacts under packages/PackageInfo/.json. Can also be +a single item. + +.PARAMETER DocRepoLocation +Location of the root of the docs.microsoft.com reference doc location. Further +path information is provided by $GetDocsMsMetadataForPackageFn + +.PARAMETER Language +Programming language to supply to metadata + +.PARAMETER RepoId +GitHub repository ID of the SDK. Typically of the form: 'Azure/azure-sdk-for-js' + +#> + +param( + [Parameter(Mandatory = $true)] + [array]$PackageInfoJsonLocations, + + [Parameter(Mandatory = $true)] + [string]$DocRepoLocation, + + [Parameter(Mandatory = $true)] + [string]$Language, + + [Parameter(Mandatory = $true)] + [string]$RepoId +) + +. (Join-Path $PSScriptRoot common.ps1) + +$releaseReplaceRegex = "(https://github.com/$RepoId/(?:blob|tree)/)(?:master|main)" +$TITLE_REGEX = "(\#\s+(?Azure .+? (?:client|plugin|shared) library for (?:JavaScript|Java|Python|\.NET|C)))" + +function GetAdjustedReadmeContent($ReadmeContent, $PackageInfo, $PackageMetadata) { + # The $PackageMetadata could be $null if there is no associated metadata entry + # based on how the metadata CSV is filtered + $service = $PackageInfo.ServiceDirectory.ToLower() + if ($PackageMetadata -and $PackageMetadata.ServiceName) { + # Normalize service name "Key Vault" -> "keyvault" + # TODO: Use taxonomy for service name -- https://github.com/Azure/azure-sdk-tools/issues/1442 + # probably from metadata + $service = $PackageMetadata.ServiceName.ToLower().Replace(" ", "") + } + + # Generate the release tag for use in link substitution + $tag = "$($PackageInfo.Name)_$($PackageInfo.Version)" + $date = Get-Date -Format "MM/dd/yyyy" + + + $foundTitle = "" + if ($ReadmeContent -match $TITLE_REGEX) { + $ReadmeContent = $ReadmeContent -replace $TITLE_REGEX, "`${0} - Version $($PackageInfo.Version) `n" + $foundTitle = $matches["filetitle"] + } + + # If this is not a daily dev package, perform link replacement + if (!$packageInfo.DevVersion) { + $replacementPattern = "`${1}$tag" + $ReadmeContent = $ReadmeContent -replace $releaseReplaceRegex, $replacementPattern + } + + $header = @" +--- +title: $foundTitle +keywords: Azure, $Language, SDK, API, $($PackageInfo.Name), $service +author: maggiepint +ms.author: magpint +ms.date: $date +ms.topic: article +ms.prod: azure +ms.technology: azure +ms.devlang: $Language +ms.service: $service +--- + +"@ + + return "$header`n$ReadmeContent" +} + +function UpdateDocsMsMetadataForPackage($packageInfoJsonLocation) { + $packageInfoJson = Get-Content $packageInfoJsonLocation -Raw + $packageInfo = ConvertFrom-Json $packageInfoJson + + $originalVersion = [AzureEngSemanticVersion]::ParseVersionString($packageInfo.Version) + if ($packageInfo.DevVersion) { + # If the package is of a dev version there may be language-specific needs to + # specify the appropriate version. For example, in the case of JS, the dev + # version is always 'dev' when interacting with NPM. + if ($GetDocsMsDevLanguageSpecificPackageInfoFn -and (Test-Path "Function:$GetDocsMsDevLanguageSpecificPackageInfoFn")) { + $packageInfo = &$GetDocsMsDevLanguageSpecificPackageInfoFn $packageInfo + } else { + # Default: use the dev version from package info as the version for + # downstream processes + $packageInfo.Version = $packageInfo.DevVersion + } + } + + $packageMetadataArray = (Get-CSVMetadata).Where({ $_.Package -eq $packageInfo.Name -and $_.GroupId -eq $packageInfo.Group -and $_.Hide -ne 'true' -and $_.New -eq 'true' }) + if ($packageMetadataArray.Count -eq 0) { + LogWarning "Could not retrieve metadata for $($packageInfo.Name) from metadata CSV. Using best effort defaults." + $packageMetadata = $null + } elseif ($packageMetadataArray.Count -gt 1) { + LogWarning "Multiple metadata entries for $($packageInfo.Name) in metadata CSV. Using first entry." + $packageMetadata = $packageMetadataArray[0] + } else { + $packageMetadata = $packageMetadataArray[0] + } + + $readmeContent = Get-Content $packageInfo.ReadMePath -Raw + $outputReadmeContent = "" + if ($readmeContent) { + $outputReadmeContent = GetAdjustedReadmeContent $readmeContent $packageInfo $packageMetadata + } + + $docsMsMetadata = &$GetDocsMsMetadataForPackageFn $packageInfo + $readMePath = $docsMsMetadata.LatestReadMeLocation + if ($originalVersion.IsPrerelease) { + $readMePath = $docsMsMetadata.PreviewReadMeLocation + } + + $suffix = $docsMsMetadata.Suffix + $readMeName = "$($docsMsMetadata.DocsMsReadMeName.ToLower())-readme${suffix}.md" + + $readmeLocation = Join-Path $DocRepoLocation $readMePath $readMeName + + Set-Content -Path $readmeLocation -Value $outputReadmeContent + + # Copy package info file to the docs repo + $metadataMoniker = 'latest' + if ($originalVersion.IsPrerelease) { + $metadataMoniker = 'preview' + } + $packageMetadataName = Split-Path $packageInfoJsonLocation -Leaf + $packageInfoLocation = Join-Path $DocRepoLocation "metadata/$metadataMoniker" + $packageInfoJson = ConvertTo-Json $packageInfo + New-Item -ItemType Directory -Path $packageInfoLocation -Force + Set-Content ` + -Path $packageInfoLocation/$packageMetadataName ` + -Value $packageInfoJson +} + +foreach ($packageInfo in $PackageInfoJsonLocations) { + Write-Host "Updating metadata for package: $packageInfo" + UpdateDocsMsMetadataForPackage $packageInfo +} diff --git a/eng/common/scripts/Update-DocsMsPackages.ps1 b/eng/common/scripts/Update-DocsMsPackages.ps1 index 2d43127b2b..576159e778 100644 --- a/eng/common/scripts/Update-DocsMsPackages.ps1 +++ b/eng/common/scripts/Update-DocsMsPackages.ps1 @@ -1,10 +1,24 @@ -# This script is intended to update docs.ms CI configuration (currently supports Java, Python, C#, JS) in nightly build -# For details on calling, check `docindex.yml`. +<# +.SYNOPSIS +Update docs.microsoft.com CI configuration with provided metadata -# In this script, we will do the following business logic. -# 1. Filter out the packages from release csv file by `New=true`, `Hide!=true` -# 2. Compare current package list with the csv packages, and keep them in sync. Leave other packages as they are. -# 3. Update the tarage packages back to CI config files. +.DESCRIPTION +Update docs.microsoft.com CI configuration with metadata in the Azure/azure-sdk +metadata CSV file and information in the docs.microsoft.com repo's own /metadata +folder. The docs.microsoft.com repo's /metadata folder allows onboarding of +packages which have not released to a central package manager. + +* Use packages in the Azure/azure-sdk metadata CSV where New == true and + Hide != true +* Add metadata from files in the metadata/ folder to the CSV metadata +* Onboard new packages, update existing tracked packages, leave other packages + in place. (This is implemented on a per-language basis by + $UpdateDocsMsPackagesFn) + +.PARAMETER DocRepoLocation +Location of the docs.microsoft.com reference docs repo. + +#> param ( [Parameter(Mandatory = $true)] $DocRepoLocation # the location of the cloned doc repo @@ -12,8 +26,93 @@ param ( . (Join-Path $PSScriptRoot common.ps1) -function GetDocsMetadata() { - (Get-CSVMetadata).Where({ $_.New -eq 'true' -and $_.Hide -ne 'true' }) +function GetDocsMetadataForMoniker($moniker) { + $searchPath = Join-Path $DocRepoLocation 'metadata' $moniker + if (!(Test-Path $searchPath)) { + return @() + } + $paths = Get-ChildItem -Path $searchPath -Filter *.json + + $metadata = @() + foreach ($path in $paths) { + $fileContents = Get-Content $path -Raw + $fileObject = ConvertFrom-Json -InputObject $fileContents + $versionGa = '' + $versionPreview = '' + if ($moniker -eq 'latest') { + $versionGa = $fileObject.Version + } else { + $versionPreview = $fileObject.Version + } + + $metadata += @{ + Package = $fileObject.Name; + VersionGA = $versionGa; + VersionPreview = $versionPreview; + RepoPath = $fileObject.ServiceDirectory; + Type = $fileObject.SdkType; + New = $fileObject.IsNewSdk; + } + } + + return $metadata +} +function GetDocsMetadata() { + # Read metadata from CSV + $csvMetadata = (Get-CSVMetadata).Where({ $_.New -eq 'true' -and $_.Hide -ne 'true' }) + + # Read metadata from docs repo + $metadataByPackage = @{} + foreach ($package in GetDocsMetadataForMoniker 'latest') { + if ($metadataByPackage.ContainsKey($package.Package)) { + LogWarning "Duplicate package in latest metadata: $($package.Package)" + } + Write-Host "Adding latest package: $($package.Package)" + $metadataByPackage[$package.Package] = $package + } + + foreach ($package in GetDocsMetadataForMoniker 'preview') { + if ($metadataByPackage.ContainsKey($package.Package)) { + # Merge VersionPreview of each object + Write-Host "Merging preview package version for $($package.Package))" + $metadataByPackage[$package.Package].VersionPreview = $package.VersionPreview + } else { + Write-Host "Adding preview package: $($package.Package)" + $metadataByPackage[$package.Package] = $package + } + } + + # Override CSV metadata version information before returning + $outputMetadata = @() + foreach ($item in $csvMetadata) { + if ($metadataByPackage.ContainsKey($item.Package)) { + Write-Host "Overriding CSV metadata from docs repo for $($item.Package)" + $matchingPackage = $metadataByPackage[$item.Package] + + # Only update the version from metadata present in the docs repo IF there + # is a specified version. The absence of package metadata in the docs repo + # (e.g. no GA version) does not imply that the CSV metadata is incorrect. + if ($matchingPackage.VersionGA) { + $item.VersionGA = $matchingPackage.VersionGA + } + if ($matchingPackage.VersionPreview) { + $item.VersionPreview = $matchingPackage.VersionPreview + } + } + $outputMetadata += $item + } + + # Add entries present in the docs repo which are not present in CSV. These are + # usually packages which have not yet published a preview or GA version. + foreach ($item in $metadataByPackage.Values) { + $matchingPackagesInCsvMetadata = $csvMetadata.Where({ $_.Package -eq $item.Package }) + if (!$matchingPackagesInCsvMetadata) { + Write-Host "Adding package from docs metadata that is not found in CSV metadata: $($item.Package)" + $outputMetadata += $item + } + } + + return $outputMetadata } if ($UpdateDocsMsPackagesFn -and (Test-Path "Function:$UpdateDocsMsPackagesFn")) { @@ -31,6 +130,6 @@ if ($UpdateDocsMsPackagesFn -and (Test-Path "Function:$UpdateDocsMsPackagesFn")) } else { LogError "The function for '$UpdateFn' was not found.` Make sure it is present in eng/scripts/Language-Settings.ps1 and referenced in eng/common/scripts/common.ps1.` - See https://github.com/Azure/azure-sdk-tools/blob/master/doc/common/common_engsys.md#code-structure" + See https://github.com/Azure/azure-sdk-tools/blob/main/doc/common/common_engsys.md#code-structure" exit 1 } diff --git a/eng/common/scripts/Verify-Links.ps1 b/eng/common/scripts/Verify-Links.ps1 index 6267d4d1b2..3808d1f8bb 100644 --- a/eng/common/scripts/Verify-Links.ps1 +++ b/eng/common/scripts/Verify-Links.ps1 @@ -27,7 +27,7 @@ List of http status codes that count as broken links. Defaults to 400, 401, 404, SocketError.HostNotFound = 11001, SocketError.NoData = 11004. .PARAMETER branchReplaceRegex - Regex to check if the link needs to be replaced. E.g. ^(https://github.com/.*/(?:blob|tree)/)master(/.*)$ + Regex to check if the link needs to be replaced. E.g. ^(https://github.com/.*/(?:blob|tree)/)main(/.*)$ .PARAMETER branchReplacementName The substitute branch name or SHA commit. diff --git a/eng/common/scripts/artifact-metadata-parsing.ps1 b/eng/common/scripts/artifact-metadata-parsing.ps1 index 2339155b35..dc96ee9172 100644 --- a/eng/common/scripts/artifact-metadata-parsing.ps1 +++ b/eng/common/scripts/artifact-metadata-parsing.ps1 @@ -101,7 +101,7 @@ function RetrievePackages($artifactLocation) { { LogError "The function for '$GetPackageInfoFromPackageFileFn' was not found.` Make sure it is present in eng/scripts/Language-Settings.ps1 and referenced in eng/common/scripts/common.ps1.` - See https://github.com/Azure/azure-sdk-tools/blob/master/doc/common/common_engsys.md#code-structure" + See https://github.com/Azure/azure-sdk-tools/blob/main/doc/common/common_engsys.md#code-structure" } } diff --git a/eng/common/scripts/common.ps1 b/eng/common/scripts/common.ps1 index b92235c208..4e0b0847cd 100644 --- a/eng/common/scripts/common.ps1 +++ b/eng/common/scripts/common.ps1 @@ -40,5 +40,7 @@ $GetPackageInfoFromRepoFn = "Get-${Language}-PackageInfoFromRepo" $GetPackageInfoFromPackageFileFn = "Get-${Language}-PackageInfoFromPackageFile" $PublishGithubIODocsFn = "Publish-${Language}-GithubIODocs" $UpdateDocsMsPackagesFn = "Update-${Language}-DocsMsPackages" +$GetDocsMsMetadataForPackageFn = "Get-${Language}-DocsMsMetadataForPackage" +$GetDocsMsDevLanguageSpecificPackageInfoFn = "Get-${Language}-DocsMsDevLanguageSpecificPackageInfo" $GetGithubIoDocIndexFn = "Get-${Language}-GithubIoDocIndex" $FindArtifactForApiReviewFn = "Find-${Language}-Artifacts-For-Apireview" diff --git a/eng/common/scripts/copy-docs-to-blobstorage.ps1 b/eng/common/scripts/copy-docs-to-blobstorage.ps1 index 7864e9680f..f037dcf51e 100644 --- a/eng/common/scripts/copy-docs-to-blobstorage.ps1 +++ b/eng/common/scripts/copy-docs-to-blobstorage.ps1 @@ -240,6 +240,6 @@ else { LogWarning "The function for '$PublishGithubIODocsFn' was not found.` Make sure it is present in eng/scripts/Language-Settings.ps1 and referenced in eng/common/scripts/common.ps1.` - See https://github.com/Azure/azure-sdk-tools/blob/master/doc/common/common_engsys.md#code-structure" + See https://github.com/Azure/azure-sdk-tools/blob/main/doc/common/common_engsys.md#code-structure" } diff --git a/eng/common/scripts/update-docs-metadata.ps1 b/eng/common/scripts/update-docs-metadata.ps1 index fc16fea9e5..bc9d426cc5 100644 --- a/eng/common/scripts/update-docs-metadata.ps1 +++ b/eng/common/scripts/update-docs-metadata.ps1 @@ -24,7 +24,7 @@ param ( . (Join-Path $PSScriptRoot common.ps1) -$releaseReplaceRegex = "(https://github.com/$RepoId/(?:blob|tree)/)master" +$releaseReplaceRegex = "(https://github.com/$RepoId/(?:blob|tree)/)main" function GetMetaData { if (Test-Path Variable:MetadataUri) { @@ -67,10 +67,10 @@ function GetAdjustedReadmeContent($pkgInfo){ $fileContent = $fileContent -replace $titleRegex, "`${0} - Version $($pkgInfo.PackageVersion) `n" $foundTitle = $matches["filetitle"] } - # Replace github master link with release tag. + # Replace github main link with release tag. $ReplacementPattern = "`${1}$($pkgInfo.Tag)" $fileContent = $fileContent -replace $releaseReplaceRegex, $ReplacementPattern - + $header = "---`ntitle: $foundTitle`nkeywords: Azure, $Language, SDK, API, $($pkgInfo.PackageId), $service`nauthor: maggiepint`nms.author: magpint`nms.date: $date`nms.topic: article`nms.prod: azure`nms.technology: azure`nms.devlang: $Language`nms.service: $service`n---`n" if ($fileContent) { @@ -101,7 +101,7 @@ foreach ($config in $targets) { if ($pkgsFiltered) { Write-Host "Given the visible artifacts, $($config.mode) Readme updates against $($config.path_to_config) will be processed for the following packages." Write-Host ($pkgsFiltered | % { $_.PackageId + " " + $_.PackageVersion }) - + foreach ($packageInfo in $pkgsFiltered) { $readmeName = "$($packageInfo.DocsReadMeName.ToLower())-readme${suffix}.md" $readmeFolder = Join-Path $DocRepoLocation $config.content_folder @@ -115,12 +115,12 @@ foreach ($config in $targets) { if ($packageInfo.ReadmeContent) { $adjustedContent = GetAdjustedReadmeContent -pkgInfo $packageInfo } - + if ($adjustedContent) { try { Push-Location $DocRepoLocation Set-Content -Path $readmeLocation -Value $adjustedContent -Force - + Write-Host "Updated readme for $readmeName." } catch { Write-Host $_ diff --git a/eng/scripts/Language-Settings.ps1 b/eng/scripts/Language-Settings.ps1 index 4b6ff0c64d..6763071a0d 100644 --- a/eng/scripts/Language-Settings.ps1 +++ b/eng/scripts/Language-Settings.ps1 @@ -1,7 +1,7 @@ $Language = "c" $PackageRepository = "C" -$packagePattern = "*.json" -$MetadataUri = "https://raw.githubusercontent.com/Azure/azure-sdk/master/_data/releases/latest/c-packages.csv" +$packagePattern = "package-info.json" +$MetadataUri = "https://raw.githubusercontent.com/Azure/azure-sdk/main/_data/releases/latest/c-packages.csv" $BlobStorageUrl = "https://azuresdkdocs.blob.core.windows.net/%24web?restype=container&comp=list&prefix=c%2F&delimiter=%2F" # Parse out package publishing information given a vcpkg format. @@ -19,7 +19,7 @@ function Get-c-PackageInfoFromPackageFile ($pkg, $workingDirectory) { $releaseNotes = Get-ChangeLogEntryAsString -ChangeLogLocation $changeLogLoc -VersionString $pkgVersion } - + $readmeContentLoc = @(Get-ChildItem -Path $packageArtifactLocation -Recurse -Include "README.md")[0] if ($readmeContentLoc) { $readmeContent = Get-Content -Raw $readmeContentLoc @@ -47,10 +47,9 @@ function Publish-c-GithubIODocs ($DocLocation, $PublicArtifactLocation) # Those loops are left over from previous versions of this script which were # used to publish multiple docs packages in a single invocation. $pkgInfo = Get-Content $DocLocation/package-info.json | ConvertFrom-Json - $releaseTag = RetrieveReleaseTag "C" $PublicArtifactLocation + $releaseTag = RetrieveReleaseTag -artifactLocation $PublicArtifactLocation Upload-Blobs -DocDir $DocLocation -PkgName 'az_core' -DocVersion $pkgInfo.version -ReleaseTag $releaseTag Upload-Blobs -DocDir $DocLocation -PkgName 'az_iot' -DocVersion $pkgInfo.version -ReleaseTag $releaseTag - Upload-Blobs -DocDir $DocLocation -PkgName 'az_storage_blobs' -DocVersion $pkgInfo.version -ReleaseTag $releaseTag } function Get-c-GithubIoDocIndex() { diff --git a/eng/scripts/Update-SdkVersion.ps1 b/eng/scripts/Update-SdkVersion.ps1 index f10af804e8..5ea5f95282 100644 --- a/eng/scripts/Update-SdkVersion.ps1 +++ b/eng/scripts/Update-SdkVersion.ps1 @@ -3,7 +3,7 @@ Bumps up sdk version after release .DESCRIPTION -This script bumps up the sdk version found in az_version.h following conventions defined at https://github.com/Azure/azure-sdk/blob/master/docs/policies/releases.md#incrementing-after-release-c +This script bumps up the sdk version found in az_version.h following conventions defined at https://github.com/Azure/azure-sdk/blob/main/docs/policies/releases.md#incrementing-after-release-c We use the version number defined in AZ_SDK_VERSION_STRING, and then overwrite other #defines .PARAMETER NewVersionString diff --git a/samples/README.md b/samples/README.md index 5dfdf18c57..a6b2951a99 100644 --- a/samples/README.md +++ b/samples/README.md @@ -29,9 +29,9 @@ This section lists how-to guides for the most commonly used APIs and most common ### General How-To Guides -- [How to port the Azure SDK to another platform](https://github.com/Azure/azure-sdk-for-c/tree/master/sdk/docs/core#porting-the-azure-sdk-to-another-platform) -- [How to configure, access, and analyze logging information](https://github.com/Azure/azure-sdk-for-c/tree/master/sdk/docs/core#logging-sdk-operations) +- [How to port the Azure SDK to another platform](https://github.com/Azure/azure-sdk-for-c/tree/main/sdk/docs/core#porting-the-azure-sdk-to-another-platform) +- [How to configure, access, and analyze logging information](https://github.com/Azure/azure-sdk-for-c/tree/main/sdk/docs/core#logging-sdk-operations) ### Azure.IoT -- [IoT samples](https://github.com/Azure/azure-sdk-for-c/blob/master/sdk/samples/iot/README.md) +- [IoT samples](https://github.com/Azure/azure-sdk-for-c/blob/main/sdk/samples/iot/README.md) diff --git a/sdk/ci.yml b/sdk/ci.yml index 836b5d9fae..9640fdd549 100644 --- a/sdk/ci.yml +++ b/sdk/ci.yml @@ -2,7 +2,7 @@ trigger: branches: include: - - master + - main - feature/* - release/* - hotfix/* @@ -11,7 +11,7 @@ trigger: pr: branches: include: - - master + - main - feature/* - release/* - hotfix/* diff --git a/sdk/docs/core/README.md b/sdk/docs/core/README.md index ce72e16a90..a5738c4103 100644 --- a/sdk/docs/core/README.md +++ b/sdk/docs/core/README.md @@ -12,11 +12,11 @@ The `Azure Core` library requires you to implement a few functions to provide pl ### Function Results -Many SDK functions return an `az_result` as defined in [inc/az_result.h](https://github.com/Azure/azure-sdk-for-c/blob/master/sdk/inc/azure/core/az_result.h) header file. An `az_result` is a 32-bit enum value. When a function succeeds, it typically returns AZ_OK. When a function fails, it returns an `az_result` symbol prefixed with `AZ_ERROR_`. A few functions return a reason for success; these symbols will be prefixed with `AZ_` but will **not** contain `ERROR` in the symbol. For functions that need to return an `az_result` and some other value; the other value is returned via an output parameter. If you simply want to know if an `az_result` value indicates generic success or failure, call either the `az_result_succeeded` or `az_result_failed` function, respectively. Both of these functions take an `az_result` value and return `true` or `false`. +Many SDK functions return an `az_result` as defined in [inc/az_result.h](https://github.com/Azure/azure-sdk-for-c/blob/main/sdk/inc/azure/core/az_result.h) header file. An `az_result` is a 32-bit enum value. When a function succeeds, it typically returns AZ_OK. When a function fails, it returns an `az_result` symbol prefixed with `AZ_ERROR_`. A few functions return a reason for success; these symbols will be prefixed with `AZ_` but will **not** contain `ERROR` in the symbol. For functions that need to return an `az_result` and some other value; the other value is returned via an output parameter. If you simply want to know if an `az_result` value indicates generic success or failure, call either the `az_result_succeeded` or `az_result_failed` function, respectively. Both of these functions take an `az_result` value and return `true` or `false`. ### Working with Spans -An `az_span` is a small data structure (defined in our [az_span.h](https://github.com/Azure/azure-sdk-for-c/blob/master/sdk/inc/azure/core/az_span.h) file) wrapping a byte buffer. Specifically, an `az_span` instance contains: +An `az_span` is a small data structure (defined in our [az_span.h](https://github.com/Azure/azure-sdk-for-c/blob/main/sdk/inc/azure/core/az_span.h) file) wrapping a byte buffer. Specifically, an `az_span` instance contains: - a byte pointer - an integer size @@ -72,7 +72,7 @@ There are many functions to manipulate `az_span` instances. You can slice (subse ### Strings -A string is a span of UTF-8 characters. It's not a zero-terminated string. Defined in [inc/az_span.h](https://github.com/Azure/azure-sdk-for-c/blob/master/sdk/inc/azure/core/az_span.h). +A string is a span of UTF-8 characters. It's not a zero-terminated string. Defined in [inc/az_span.h](https://github.com/Azure/azure-sdk-for-c/blob/main/sdk/inc/azure/core/az_span.h). ```c az_span hello_world = AZ_SPAN_FROM_STR("Hello world!"); @@ -82,7 +82,7 @@ az_span hello_world = AZ_SPAN_FROM_STR("Hello world!"); 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. -To enable logging, you must first write a callback function that our logging mechanism will invoke periodically with messages. The function signature must match this type definition (defined in the [az_log.h](https://github.com/Azure/azure-sdk-for-c/blob/master/sdk/inc/azure/core/az_log.h) file): +To enable logging, you must first write a callback function that our logging mechanism will invoke periodically with messages. The function signature must match this type definition (defined in the [az_log.h](https://github.com/Azure/azure-sdk-for-c/blob/main/sdk/inc/azure/core/az_log.h) file): ```C typedef void (*az_log_message_fn)(az_log_classification classification, az_span message); @@ -153,7 +153,7 @@ Also, if you define the `AZ_NO_PRECONDITION_CHECKING` symbol when compiling the ### Canceling an Operation -`Azure Core` provides a rich cancellation mechanism by way of its `az_context` type (defined in the [az_context.h](https://github.com/Azure/azure-sdk-for-c/blob/master/sdk/inc/azure/core/az_context.h) file). As your code executes and functions call other functions, a pointer to an `az_context` is passed as an argument through the functions. At any point, a function can create a new `az_context` specifying a parent `az_context` and a timeout period and then, this new `az_context` is passed down to more functions. When a parent `az_context` instance expires or is canceled, all of its children are canceled as well. +`Azure Core` provides a rich cancellation mechanism by way of its `az_context` type (defined in the [az_context.h](https://github.com/Azure/azure-sdk-for-c/blob/main/sdk/inc/azure/core/az_context.h) file). As your code executes and functions call other functions, a pointer to an `az_context` is passed as an argument through the functions. At any point, a function can create a new `az_context` specifying a parent `az_context` and a timeout period and then, this new `az_context` is passed down to more functions. When a parent `az_context` instance expires or is canceled, all of its children are canceled as well. There is a special singleton instance of the `az_context` type called `az_context_application`. This instance represents your entire application and this `az_context` instance never expires. It is common to use this instance as the ultimate root of all `az_context` instances. So then, as functions call other functions, these functions can create child `az_context` instances and pass the child down through the call tree. Imagine you have the following `az_context` tree: @@ -186,10 +186,10 @@ If you'd like to contribute to this library, please read the [contributing guide 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_license]: https://github.com/Azure/azure-sdk-for-c/blob/master/LICENSE -[azure_sdk_for_c_contributing_developer_guide]: https://github.com/Azure/azure-sdk-for-c/blob/master/CONTRIBUTING.md#developer-guide -[azure_sdk_for_c_contributing_pull_requests]: https://github.com/Azure/azure-sdk-for-c/blob/master/CONTRIBUTING.md#pull-requests +[azure_sdk_for_c_contributing]: https://github.com/Azure/azure-sdk-for-c/blob/main/CONTRIBUTING.md +[azure_sdk_for_c_license]: https://github.com/Azure/azure-sdk-for-c/blob/main/LICENSE +[azure_sdk_for_c_contributing_developer_guide]: https://github.com/Azure/azure-sdk-for-c/blob/main/CONTRIBUTING.md#developer-guide +[azure_sdk_for_c_contributing_pull_requests]: https://github.com/Azure/azure-sdk-for-c/blob/main/CONTRIBUTING.md#pull-requests [azure_cli]: https://docs.microsoft.com/cli/azure [azure_pattern_circuit_breaker]: https://docs.microsoft.com/azure/architecture/patterns/circuit-breaker [azure_pattern_retry]: https://docs.microsoft.com/azure/architecture/patterns/retry diff --git a/sdk/docs/iot/README.md b/sdk/docs/iot/README.md index a324e716d0..015e8d7fcd 100644 --- a/sdk/docs/iot/README.md +++ b/sdk/docs/iot/README.md @@ -17,7 +17,7 @@ From a functional perspective, this means that the user's application code (not - Functions to parse incoming message topics, which populate structs with crucial message information. - Default values for MQTT connect keep alive and connection port. -To better understand the responsibilities of the user application code and the Embedded C 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), which explains the high-level architecture, SDK components, and a clear view of SDK x Application responsibilities. +To better understand the responsibilities of the user application code and the Embedded C SDK, please take a look at the [State Machine diagram](https://github.com/Azure/azure-sdk-for-c/blob/main/sdk/docs/iot/mqtt_state_machine.md), which explains the high-level architecture, SDK components, and a clear view of SDK x Application responsibilities. For more details about the Azure IoT Hub, see the following [Azure IoT Hub documentation](https://docs.microsoft.com/azure/iot-hub/). For more details about the Azure IoT Device Provisioning Service (DPS), see the [Azure IOT DPS documentation](https://docs.microsoft.com/azure/iot-dps/). @@ -30,7 +30,7 @@ Please view the API documentation [here](https://azuresdkdocs.blob.core.windows. ### Build -The Azure IoT library is compiled following the same steps listed on the root [Azure SDK for Embedded C 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 Azure IoT library is compiled following the same steps listed on the root [Azure SDK for Embedded C README](https://github.com/Azure/azure-sdk-for-c/blob/main/README.md) documentation, under ["Getting Started Using the SDK"](https://github.com/Azure/azure-sdk-for-c/blob/main/README.md#getting-started-using-the-sdk). The library targets made available via CMake are the following: @@ -40,19 +40,19 @@ The library targets made available via CMake are the following: ### Samples -View the [Azure Embedded C SDK IoT Samples README](https://github.com/Azure/azure-sdk-for-c/blob/master/sdk/samples/iot/README.md) to learn how to run samples for the Azure Embedded C SDK IoT Hub Client and the Provisioning Clients. The README will provide general prerequisites, environment setup instructions, sample descriptions, as well as directions on how to build and run all the samples on either a Linux or Windows environment. +View the [Azure Embedded C SDK IoT Samples README](https://github.com/Azure/azure-sdk-for-c/blob/main/sdk/samples/iot/README.md) to learn how to run samples for the Azure Embedded C SDK IoT Hub Client and the Provisioning Clients. The README will provide general prerequisites, environment setup instructions, sample descriptions, as well as directions on how to build and run all the samples on either a Linux or Windows environment. For more detailed, step-by-step guides to setup and run the IoT Hub certificate samples from scratch, refer to these documents: -- Linux: [How to Setup and Run Azure SDK for Embedded C IoT Hub Certificate Samples on Linux](https://github.com/Azure/azure-sdk-for-c/blob/master/sdk/samples/iot/docs/how_to_iot_hub_samples_linux.md) +- Linux: [How to Setup and Run Azure SDK for Embedded C IoT Hub Certificate Samples on Linux](https://github.com/Azure/azure-sdk-for-c/blob/main/sdk/samples/iot/docs/how_to_iot_hub_samples_linux.md) -- Windows: [How to Setup and Run Azure SDK for Embedded C IoT Hub Certificate 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) +- Windows: [How to Setup and Run Azure SDK for Embedded C IoT Hub Certificate Samples on Microsoft Windows](https://github.com/Azure/azure-sdk-for-c/blob/main/sdk/samples/iot/docs/how_to_iot_hub_samples_windows.md) -- Realtek Ameba D: [How to Setup and Run Azure SDK for Embedded C IoT Hub Client on Realtek AmebaD](https://github.com/Azure/azure-sdk-for-c/blob/master/sdk/samples/iot/aziot_realtek_amebaD/readme.md) +- Realtek Ameba D: [How to Setup and Run Azure SDK for Embedded C IoT Hub Client on Realtek AmebaD](https://github.com/Azure/azure-sdk-for-c/blob/main/sdk/samples/iot/aziot_realtek_amebaD/readme.md) -- Espressif 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/aziot_esp8266/readme.md) +- Espressif 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/main/sdk/samples/iot/aziot_esp8266/readme.md) -- Espressif ESP32: [How to Setup and Run Azure SDK for Embedded C IoT Hub Client on ESP32](https://github.com/Azure/azure-sdk-for-c/blob/master/sdk/samples/iot/aziot_esp32/readme.md) +- Espressif ESP32: [How to Setup and Run Azure SDK for Embedded C IoT Hub Client on ESP32](https://github.com/Azure/azure-sdk-for-c/blob/main/sdk/samples/iot/aziot_esp32/readme.md) **Important Note on Linux and Windows Samples**: While Windows and Linux devices are not likely to be considered constrained, these samples enable developers to test the Azure SDK for Embedded C libraries, debug, and step through the code, even without a real device. We understand not everyone will have a real device to test and that sometimes these devices won't have debugging capabilities. @@ -91,7 +91,7 @@ The Pic24 sample includes both Hub and DPS services. The table below shows RAM/R | | Embedded C SDK size | | Total Size | | |---------|----------|---------|---------|--------- -|**Sample** | **Program/ROM** | **Data/RAM** | **Program/ROM** | **Data/RAM** | +|**Sample** | **Program/ROM** | **Data/RAM** | **Program/ROM** | **Data/RAM** | | PIC24 (Hub + DPS + IoT Plug and Play) | 26.15KB | 0 | 103.61KB | 10.57KB | PIC24 Telemetry only | 2.58KB | 0 | 74.16KB | 8.26KB @@ -105,9 +105,9 @@ These examples are scenario-focused and introduce you to the API calls for a few - [IoT Message Properties](#iot-message-properties) - [IoT Telemetry](#iot-telemetry) -General [coding patterns](https://github.com/Azure/azure-sdk-for-c/blob/master/sdk/docs/iot/coding_patterns.md) that are MQTT stack agnostic are also available to view. These patterns can give you an overview of the API calls and structure needed to use the Azure IoT Embedded C SDK features. +General [coding patterns](https://github.com/Azure/azure-sdk-for-c/blob/main/sdk/docs/iot/coding_patterns.md) that are MQTT stack agnostic are also available to view. These patterns can give you an overview of the API calls and structure needed to use the Azure IoT Embedded C SDK features. -For a more extensive demonstration of the API, please view and run the [sample code](https://github.com/Azure/azure-sdk-for-c/blob/master/sdk/samples/iot/), which uses Paho MQTT. +For a more extensive demonstration of the API, please view and run the [sample code](https://github.com/Azure/azure-sdk-for-c/blob/main/sdk/samples/iot/), which uses Paho MQTT. ### IoT Hub Client Initialization @@ -225,14 +225,14 @@ void my_telemetry_func(void) ## Troubleshooting -- The error policy for the Embedded C SDK client library is documented [here](https://github.com/Azure/azure-sdk-for-c/blob/master/sdk/docs/iot/mqtt_state_machine.md#error-policy). +- The error policy for the Embedded C SDK client library is documented [here](https://github.com/Azure/azure-sdk-for-c/blob/main/sdk/docs/iot/mqtt_state_machine.md#error-policy). - 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 -This project welcomes contributions and suggestions. Find more contributing details [here](https://github.com/Azure/azure-sdk-for-c/blob/master/CONTRIBUTING.md). +This project welcomes contributions and suggestions. Find more contributing details [here](https://github.com/Azure/azure-sdk-for-c/blob/main/CONTRIBUTING.md). ### License -Azure SDK for Embedded C is licensed under the [MIT](https://github.com/Azure/azure-sdk-for-c/blob/master/LICENSE) license. \ No newline at end of file +Azure SDK for Embedded C is licensed under the [MIT](https://github.com/Azure/azure-sdk-for-c/blob/main/LICENSE) license. \ No newline at end of file diff --git a/sdk/docs/iot/coding_patterns.md b/sdk/docs/iot/coding_patterns.md index f3e997ddcc..12fac494b7 100644 --- a/sdk/docs/iot/coding_patterns.md +++ b/sdk/docs/iot/coding_patterns.md @@ -4,9 +4,9 @@ This page introduces you to coding patterns that are MQTT stack agnostic. These examples will give you a general overview of the API calls and structure needed to use the Azure IoT Embedded C SDK features. -To view scenario-focused examples using the API calls, please view the [introductory examples](https://github.com/Azure/azure-sdk-for-c/blob/master/sdk/docs/iot/README.md#examples) on the Azure IoT Client README file. +To view scenario-focused examples using the API calls, please view the [introductory examples](https://github.com/Azure/azure-sdk-for-c/blob/main/sdk/docs/iot/README.md#examples) on the Azure IoT Client README file. -For a more extensive demonstration of the API, please view and run the [sample code](https://github.com/Azure/azure-sdk-for-c/blob/master/sdk/samples/iot/), which uses Paho MQTT. +For a more extensive demonstration of the API, please view and run the [sample code](https://github.com/Azure/azure-sdk-for-c/blob/main/sdk/samples/iot/), which uses Paho MQTT. ## Examples diff --git a/sdk/docs/platform/README.md b/sdk/docs/platform/README.md index 175eada359..23c7604b4c 100644 --- a/sdk/docs/platform/README.md +++ b/sdk/docs/platform/README.md @@ -24,7 +24,7 @@ The Azure SDK also provides empty HTTP adapter (`az_nohttp`). This transport all >Note: An `AZ_ERROR_DEPENDENCY_NOT_PROVIDED` will be returned from the `az_nohttp` transport APIs. -You can also implement your own HTTP transport adapter and use it. This allows you to use a different HTTP stack other than `libcurl`. Follow the instructions on [using your own HTTP stack implementation](https://github.com/Azure/azure-sdk-for-c/blob/master/README.md#using-your-own-http-stack-implementation). +You can also implement your own HTTP transport adapter and use it. This allows you to use a different HTTP stack other than `libcurl`. Follow the instructions on [using your own HTTP stack implementation](https://github.com/Azure/azure-sdk-for-c/blob/main/README.md#using-your-own-http-stack-implementation). ## Contributing @@ -36,6 +36,6 @@ If you'd like to contribute to this library, please read the [contributing guide 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_license]: https://github.com/Azure/azure-sdk-for-c/blob/master/LICENSE -[azure_sdk_cmake_options]: https://github.com/Azure/azure-sdk-for-c/blob/master/README.md#cmake-options +[azure_sdk_for_c_contributing]: https://github.com/Azure/azure-sdk-for-c/blob/main/CONTRIBUTING.md +[azure_sdk_for_c_license]: https://github.com/Azure/azure-sdk-for-c/blob/main/LICENSE +[azure_sdk_cmake_options]: https://github.com/Azure/azure-sdk-for-c/blob/main/README.md#cmake-options diff --git a/sdk/inc/azure/az_core.h b/sdk/inc/azure/az_core.h index d9e404bac5..c3c65c3ca6 100644 --- a/sdk/inc/azure/az_core.h +++ b/sdk/inc/azure/az_core.h @@ -15,6 +15,7 @@ #ifndef _az_CORE_H #define _az_CORE_H +#include #include #include #include diff --git a/sdk/inc/azure/core/az_base64.h b/sdk/inc/azure/core/az_base64.h new file mode 100644 index 0000000000..2710a0a10f --- /dev/null +++ b/sdk/inc/azure/core/az_base64.h @@ -0,0 +1,89 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// SPDX-License-Identifier: MIT + +/** + * @file + * + * @brief Defines APIs to convert between binary data and UTF-8 encoded text that is represented in + * base 64. + * + * @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_BASE64_H +#define _az_BASE64_H + +#include +#include + +#include + +#include + +/** + * @brief Encodes the span of binary data into UTF-8 encoded text represented as base 64. + * + * @param destination_base64_text The output #az_span where the encoded base 64 text should be + * copied to as a result of the operation. + * @param[in] source_bytes The input #az_span that contains binary data to be encoded. + * @param[out] out_written A pointer to an `int32_t` that receives the number of bytes written into + * the destination #az_span. This can be used to slice the output for subsequent calls, if + * necessary. + * + * @return An #az_result value indicating the result of the operation. + * @retval #AZ_OK Success. + * @retval #AZ_ERROR_NOT_ENOUGH_SPACE The \p destination_base64_text is not large enough to contain + * the encoded bytes. + */ +AZ_NODISCARD az_result +az_base64_encode(az_span destination_base64_text, az_span source_bytes, int32_t* out_written); + +/** + * @brief Returns the maximum length of the result if you were to encode an #az_span of the + * specified length which contained binary data. + * + * @param source_bytes_size The size of the span containing binary data. + * + * @return The maximum length of the result. + */ +AZ_NODISCARD int32_t az_base64_get_max_encoded_size(int32_t source_bytes_size); + +/** + * @brief Decodes the span of UTF-8 encoded text represented as base 64 into binary data. + * + * @param destination_bytes The output #az_span where the decoded binary data should be copied to as + * a result of the operation. + * @param[in] source_base64_text The input #az_span that contains the base 64 text to be decoded. + * @param[out] out_written A pointer to an `int32_t` that receives the number of bytes written into + * the destination #az_span. This can be used to slice the output for subsequent calls, if + * necessary. + * + * @return An #az_result value indicating the result of the operation. + * @retval #AZ_OK Success. + * @retval #AZ_ERROR_NOT_ENOUGH_SPACE The \p destination_bytes is not large enough to contain + * the decoded text. + * @retval #AZ_ERROR_UNEXPECTED_CHAR The input \p source_base64_text contains characters outside of + * the expected base 64 range, has invalid or more than two padding characters, or is incomplete + * (that is, not a multiple of 4). + * @retval #AZ_ERROR_UNEXPECTED_END The input \p source_base64_text is incomplete (that is, it is + * not of a size which is a multiple of 4). + */ +AZ_NODISCARD az_result +az_base64_decode(az_span destination_bytes, az_span source_base64_text, int32_t* out_written); + +/** + * @brief Returns the maximum length of the result if you were to decode an #az_span of the + * specified length which contained base 64 encoded text. + * + * @param source_base64_text_size The size of the span containing base 64 encoded text. + * + * @return The maximum length of the result. + */ +AZ_NODISCARD int32_t az_base64_get_max_decoded_size(int32_t source_base64_text_size); + +#include + +#endif // _az_BASE64_H diff --git a/sdk/inc/azure/core/az_json.h b/sdk/inc/azure/core/az_json.h index 41c7994025..944be7c998 100644 --- a/sdk/inc/azure/core/az_json.h +++ b/sdk/inc/azure/core/az_json.h @@ -614,6 +614,11 @@ typedef struct /// and it shouldn't be modified by the caller. az_json_token token; + /// The depth of the current token. This read-only field tracks the recursive depth of the nested + /// objects or arrays within the JSON text processed so far, and it shouldn't be modified by the + /// caller. + int32_t current_depth; + struct { /// The first buffer containing the JSON payload. diff --git a/sdk/samples/core/README.md b/sdk/samples/core/README.md index 27e3e9244c..01072f409f 100644 --- a/sdk/samples/core/README.md +++ b/sdk/samples/core/README.md @@ -28,6 +28,6 @@ This document explains samples and how to use them. This project welcomes contributions and suggestions. Find [more contributing][SDK_README_CONTRIBUTING] details here. -[SDK_README_CONTRIBUTING]: https://github.com/Azure/azure-sdk-for-c/blob/master/CONTRIBUTING.md +[SDK_README_CONTRIBUTING]: https://github.com/Azure/azure-sdk-for-c/blob/main/CONTRIBUTING.md ![Impressions](https://azure-sdk-impressions.azurewebsites.net/api/impressions/azure-sdk-for-c%2Fsdk%2Fcore%2Fcore%2Fsamples%2FREADME.png) diff --git a/sdk/samples/iot/README.md b/sdk/samples/iot/README.md index 5dbf6bef6e..c67979a45c 100644 --- a/sdk/samples/iot/README.md +++ b/sdk/samples/iot/README.md @@ -38,7 +38,7 @@ This document explains samples for the Azure Embedded C SDK IoT Hub Client and Device Provisioning Client. -Samples are designed to highlight the function calls required to connect with the Azure IoT Hub or the Azure IoT Hub Device Provisioning Service (DPS). These calls illustrate the happy path of the [mqtt state machine](https://github.com/Azure/azure-sdk-for-c/blob/master/sdk/docs/iot/mqtt_state_machine.md). As a result, **these samples are NOT designed to be used as production-level code**. Production code needs to incorporate other elements, such as connection retries and more extensive error-handling, which these samples do not include. These samples also utilize OpenSSL, which is **NOT recommended to use in production-level code on Windows or macOS**. +Samples are designed to highlight the function calls required to connect with the Azure IoT Hub or the Azure IoT Hub Device Provisioning Service (DPS). These calls illustrate the happy path of the [mqtt state machine](https://github.com/Azure/azure-sdk-for-c/blob/main/sdk/docs/iot/mqtt_state_machine.md). As a result, **these samples are NOT designed to be used as production-level code**. Production code needs to incorporate other elements, such as connection retries and more extensive error-handling, which these samples do not include. These samples also utilize OpenSSL, which is **NOT recommended to use in production-level code on Windows or macOS**. The samples' instructions include specifics for both Windows and Linux based systems. For Windows, the command line examples are based on PowerShell. The Linux examples are tailored to Debian/Ubuntu environments. Samples are also designed to work on macOS systems, but the instructions do not yet include specific command line examples for this environment. While Windows and Linux devices are not likely to be considered constrained, these samples enable developers to test the Azure SDK for Embedded C libraries, debug, and step through the code, even without a real device. We understand not everyone will have a real device to test and that sometimes these devices won't have debugging capabilities. @@ -46,17 +46,17 @@ The samples' instructions include specifics for both Windows and Linux based sys More detailed step-by-step guides on how to run an IoT Hub Client sample from scratch can be found below: -- Linux: [How to Setup and Run Azure SDK for Embedded C IoT Hub Certificate Samples on Linux](https://github.com/Azure/azure-sdk-for-c/blob/master/sdk/samples/iot/docs/how_to_iot_hub_samples_linux.md) +- Linux: [How to Setup and Run Azure SDK for Embedded C IoT Hub Certificate Samples on Linux](https://github.com/Azure/azure-sdk-for-c/blob/main/sdk/samples/iot/docs/how_to_iot_hub_samples_linux.md) -- Windows: [How to Setup and Run Azure SDK for Embedded C IoT Hub Certificate 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) +- Windows: [How to Setup and Run Azure SDK for Embedded C IoT Hub Certificate Samples on Microsoft Windows](https://github.com/Azure/azure-sdk-for-c/blob/main/sdk/samples/iot/docs/how_to_iot_hub_samples_windows.md) -- Realtek Ameba D: [How to Setup and Run Azure SDK for Embedded C IoT Hub Client on Realtek AmebaD](https://github.com/Azure/azure-sdk-for-c/blob/master/sdk/samples/iot/aziot_realtek_amebaD/readme.md) +- Realtek Ameba D: [How to Setup and Run Azure SDK for Embedded C IoT Hub Client on Realtek AmebaD](https://github.com/Azure/azure-sdk-for-c/blob/main/sdk/samples/iot/aziot_realtek_amebaD/readme.md) -- Espressif 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/aziot_esp8266/readme.md) +- Espressif 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/main/sdk/samples/iot/aziot_esp8266/readme.md) -- Espressif ESP32: [How to Setup and Run Azure SDK for Embedded C IoT Hub Client on ESP32](https://github.com/Azure/azure-sdk-for-c/blob/master/sdk/samples/iot/aziot_esp32/readme.md) +- Espressif ESP32: [How to Setup and Run Azure SDK for Embedded C IoT Hub Client on ESP32](https://github.com/Azure/azure-sdk-for-c/blob/main/sdk/samples/iot/aziot_esp32/readme.md) -To view scenario-focused examples using the API calls, please view the Azure IoT Client [introductory examples](https://github.com/Azure/azure-sdk-for-c/blob/master/sdk/docs/iot/README.md#examples). General [coding patterns](https://github.com/Azure/azure-sdk-for-c/blob/master/sdk/docs/iot/coding_patterns.md) that are MQTT stack agnostic are also available to view. +To view scenario-focused examples using the API calls, please view the Azure IoT Client [introductory examples](https://github.com/Azure/azure-sdk-for-c/blob/main/sdk/docs/iot/README.md#examples). General [coding patterns](https://github.com/Azure/azure-sdk-for-c/blob/main/sdk/docs/iot/coding_patterns.md) that are MQTT stack agnostic are also available to view. ## Prerequisites @@ -90,7 +90,7 @@ To run the samples, ensure you have the following programs and tools installed o
Instructions:

- NOTE: For the correct vcpkg commit, see [vcpkg-commit.txt](https://github.com/Azure/azure-sdk-for-c/blob/master/eng/vcpkg-commit.txt). + NOTE: For the correct vcpkg commit, see [vcpkg-commit.txt](https://github.com/Azure/azure-sdk-for-c/blob/main/eng/vcpkg-commit.txt). Linux: @@ -319,9 +319,9 @@ Set the following environment variables for all samples: 2. Set the trust pem filepath. **Only when testing on Windows or OSX.** _Important:_ We recommend using a managed trusted store for production deployments. Paho/OpenSSL on Windows is meant for testing purposes only. - + Create a PEM certificate file based store by concatenating the following files: - + * RSA Certificate Authority Roots: - [Baltimore CyberTrust Root](https://cacerts.digicert.com/BaltimoreCyberTrustRoot.crt.pem) @@ -331,7 +331,6 @@ Set the following environment variables for all samples: * ECC Certificate Authority Roots - [DigiCert Global Root G3](https://cacerts.digicert.com/DigiCertGlobalRootG3.crt.pem) - [Microsoft ECC Root Certificate Authority 2017](https://www.microsoft.com/pkiops/certs/Microsoft%20ECC%20Root%20Certificate%20Authority%202017.crt) - Make sure the files are in PEM format. If they are not, use `openssl x509 -inform DER -outform PEM -in my_certificate.crt -out my_certificate.pem` to convert them to PEM format. Concatenate all the files into CAStore.pem. Configure the AZ_IOT_DEVICE_X509_TRUST_PEM_FILE_PATH to point to this PEM file. @@ -519,7 +518,7 @@ This section provides an overview of the different samples available to run and - *Executable:* `paho_iot_hub_c2d_sample` - This [sample](https://github.com/Azure/azure-sdk-for-c/blob/master/sdk/samples/iot/paho_iot_hub_c2d_sample.c) receives incoming cloud-to-device (C2D) messages sent from the Azure IoT Hub to the device. It will successfully receive up to 5 messages sent from the service. If a timeout occurs while waiting for a message, the sample will exit. X509 authentication is used. + This [sample](https://github.com/Azure/azure-sdk-for-c/blob/main/sdk/samples/iot/paho_iot_hub_c2d_sample.c) receives incoming cloud-to-device (C2D) messages sent from the Azure IoT Hub to the device. It will successfully receive up to 5 messages sent from the service. If a timeout occurs while waiting for a message, the sample will exit. X509 authentication is used.

How to interact with the C2D sample:

@@ -533,7 +532,7 @@ This section provides an overview of the different samples available to run and - *Executable:* `paho_iot_hub_methods_sample` - This [sample](https://github.com/Azure/azure-sdk-for-c/blob/master/sdk/samples/iot/paho_iot_hub_methods_sample.c) receives incoming method commands invoked from the the Azure IoT Hub to the device. It will successfully receive up to 5 method commands sent from the service. If a timeout occurs while waiting for a message, the sample will exit. X509 authentication is used. + This [sample](https://github.com/Azure/azure-sdk-for-c/blob/main/sdk/samples/iot/paho_iot_hub_methods_sample.c) receives incoming method commands invoked from the the Azure IoT Hub to the device. It will successfully receive up to 5 method commands sent from the service. If a timeout occurs while waiting for a message, the sample will exit. X509 authentication is used.

How to interact with the Methods sample:

@@ -555,19 +554,19 @@ This section provides an overview of the different samples available to run and - *Executable:* `paho_iot_hub_telemetry_sample` - This [sample](https://github.com/Azure/azure-sdk-for-c/blob/master/sdk/samples/iot/paho_iot_hub_telemetry_sample.c) sends five telemetry messages to the Azure IoT Hub. X509 authentication is used. + This [sample](https://github.com/Azure/azure-sdk-for-c/blob/main/sdk/samples/iot/paho_iot_hub_telemetry_sample.c) sends five telemetry messages to the Azure IoT Hub. X509 authentication is used. ### IoT Hub SAS Telemetry Sample - *Executable:* `paho_iot_hub_sas_telemetry_sample` - This [sample](https://github.com/Azure/azure-sdk-for-c/blob/master/sdk/samples/iot/paho_iot_hub_sas_telemetry_sample.c) sends five telemetry messages to the Azure IoT Hub. SAS authentication is used. + This [sample](https://github.com/Azure/azure-sdk-for-c/blob/main/sdk/samples/iot/paho_iot_hub_sas_telemetry_sample.c) sends five telemetry messages to the Azure IoT Hub. SAS authentication is used. ### IoT Hub Twin Sample - *Executable:* `paho_iot_hub_twin_sample` - This [sample](https://github.com/Azure/azure-sdk-for-c/blob/master/sdk/samples/iot/paho_iot_hub_twin_sample.c) utilizes the Azure IoT Hub to get the device twin document, send a reported property message, and receive up to 5 desired property messages. If a timeout occurs while waiting for a message from the Azure IoT Hub, the sample will exit. Upon receiving a desired property message, the sample will update the twin property locally and send a reported property message back to the service. X509 authentication is used. + This [sample](https://github.com/Azure/azure-sdk-for-c/blob/main/sdk/samples/iot/paho_iot_hub_twin_sample.c) utilizes the Azure IoT Hub to get the device twin document, send a reported property message, and receive up to 5 desired property messages. If a timeout occurs while waiting for a message from the Azure IoT Hub, the sample will exit. Upon receiving a desired property message, the sample will update the twin property locally and send a reported property message back to the service. X509 authentication is used.

How to interact with the Twin sample:

@@ -591,7 +590,7 @@ This section provides an overview of the different samples available to run and ### IoT Plug and Play Sample -- *Executable:* `paho_iot_pnp_sample` + This [sample](https://github.com/Azure/azure-sdk-for-c/blob/main/sdk/samples/iot/paho_iot_hub_pnp_sample.c) connects an IoT Plug and Play enabled device (a thermostat) with the Digital Twin Model ID (DTMI) detailed [here](https://github.com/Azure/opendigitaltwins-dtdl/blob/master/DTDL/v2/samples/Thermostat.json). If a timeout occurs while waiting for a message from the Azure IoT Explorer, the sample will continue. If 3 timeouts occur consecutively, the sample will disconnect. X509 authentication is used. This [sample](https://github.com/Azure/azure-sdk-for-c/blob/feature/iot_pnp/sdk/samples/iot/paho_iot_pnp_sample.c) connects an Azure IoT Plug and Play enabled device simulating a thermostat directly to the Azure IoT Hub. This device is described via the Digital Twin Model ID (DTMI) detailed [here](https://github.com/Azure/opendigitaltwins-dtdl/blob/master/DTDL/v2/samples/Thermostat.json). This sample demonstrates sending telemetry and properties from the device and receiving commands and writeable properties from the service. X509 authentication is used. @@ -621,7 +620,7 @@ This section provides an overview of the different samples available to run and ### IoT Plug and Play Multiple Component Sample -- *Executable:* `paho_iot_pnp_component_sample` + This [sample](https://github.com/Azure/azure-sdk-for-c/blob/main/sdk/samples/iot/paho_iot_hub_pnp_component_sample.c) extends the IoT Hub Plug and Play Sample above to mimic a Temperature Controller and connects the IoT Plug and Play enabled device (the Temperature Controller) with the Digital Twin Model ID (DTMI) detailed [here](https://github.com/Azure/opendigitaltwins-dtdl/blob/master/DTDL/v2/samples/TemperatureController.json). If a timeout occurs while waiting for a message from the Azure IoT Explorer, the sample will continue. If 3 timeouts occur consecutively, the sample will disconnect. X509 authentication is used. This [sample](https://github.com/Azure/azure-sdk-for-c/blob/feature/iot_pnp/sdk/samples/iot/paho_iot_pnp_component_sample.c) connects an Azure IoT Plug and Play enabled device simulating a temperature controller directly to Azure IoT Hub. This device is described via the Digital Twin Model ID (DTMI) detailed [here](https://github.com/Azure/opendigitaltwins-dtdl/blob/master/DTDL/v2/samples/TemperatureController.json). X509 authentication is used. @@ -654,13 +653,22 @@ This section provides an overview of the different samples available to run and - *Executable:* `paho_iot_provisioning_sample` - This [sample](https://github.com/Azure/azure-sdk-for-c/blob/master/sdk/samples/iot/paho_iot_provisioning_sample.c) registers a device with the Azure IoT Device Provisioning Service. It will wait to receive the registration status before disconnecting. X509 authentication is used. + This [sample](https://github.com/Azure/azure-sdk-for-c/blob/main/sdk/samples/iot/paho_iot_provisioning_sample.c) registers a device with the Azure IoT Device Provisioning Service. It will wait to receive the registration status before disconnecting. X509 authentication is used. ### IoT Provisioning SAS Sample - *Executable:* `paho_iot_provisioning_sas_sample` - This [sample](https://github.com/Azure/azure-sdk-for-c/blob/master/sdk/samples/iot/paho_iot_provisioning_sas_sample.c) registers a device with the Azure IoT Device Provisioning Service. It will wait to receive the registration status before disconnecting. SAS authentication is used. + This [sample](https://github.com/Azure/azure-sdk-for-c/blob/main/sdk/samples/iot/paho_iot_provisioning_sas_sample.c) registers a device with the Azure IoT Device Provisioning Service. It will wait to receive the registration status before disconnecting. SAS authentication is used. + +## Using IoT Hub with an ECC Server Certificate Chain +To work with the new Azure Cloud ECC server certificate chain, the TLS stack must be configured to prevent RSA cipher-suites from being advertised, as described [here](https://docs.microsoft.com/azure/iot-hub/iot-hub-tls-support#elliptic-curve-cryptography-ecc-server-tls-certificate-preview). + +When using Paho MQTT for C, modify the samples by adding the following TLS option: + +```C +mqtt_ssl_options.enabledCipherSuites = "ECDH+ECDSA+HIGH"; +``` ## Using IoT Hub with an ECC Server Certificate Chain To work with the new Azure Cloud ECC server certificate chain, the TLS stack must be configured to prevent RSA cipher-suites from being advertised, as described [here](https://docs.microsoft.com/azure/iot-hub/iot-hub-tls-support#elliptic-curve-cryptography-ecc-server-tls-certificate-preview). @@ -676,21 +684,21 @@ mqtt_ssl_options.enabledCipherSuites = "ECDH+ECDSA+HIGH"; Start using the Azure Embedded C SDK IoT Clients in your solutions! - A general overview of the Embedded C SDK and additional background on running samples can be found in the [Azure SDK for Embedded C README](https://github.com/Azure/azure-sdk-for-c#azure-sdk-for-embedded-c). -- More SDK details pertaining to the Azure IoT Client library can be found in the [Azure IoT Client README](https://github.com/Azure/azure-sdk-for-c/tree/master/sdk/docs/iot#azure-iot-clients). -- The [Azure IoT Client MQTT State Machine](https://github.com/Azure/azure-sdk-for-c/blob/master/sdk/docs/iot/mqtt_state_machine.md) provides a high-level architecture and API information. +- More SDK details pertaining to the Azure IoT Client library can be found in the [Azure IoT Client README](https://github.com/Azure/azure-sdk-for-c/tree/main/sdk/docs/iot#azure-iot-clients). +- The [Azure IoT Client MQTT State Machine](https://github.com/Azure/azure-sdk-for-c/blob/main/sdk/docs/iot/mqtt_state_machine.md) provides a high-level architecture and API information. - For extensive documentation on Azure IoT Hub, see the [Microsoft API reference documentation](https://docs.microsoft.com/azure/iot-hub/about-iot-hub). - For extensive documentation on Azure IoT Hub Device Provisioning Service, see the [Microsoft API reference documentation](https://docs.microsoft.com/azure/iot-dps/). ## Troubleshooting -- The error policy for the Embedded C SDK client library is documented [here](https://github.com/Azure/azure-sdk-for-c/blob/master/sdk/docs/iot/mqtt_state_machine.md#error-policy). +- The error policy for the Embedded C SDK client library is documented [here](https://github.com/Azure/azure-sdk-for-c/blob/main/sdk/docs/iot/mqtt_state_machine.md#error-policy). - 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 -This project welcomes contributions and suggestions. Find more contributing details [here](https://github.com/Azure/azure-sdk-for-c/blob/master/CONTRIBUTING.md). +This project welcomes contributions and suggestions. Find more contributing details [here](https://github.com/Azure/azure-sdk-for-c/blob/main/CONTRIBUTING.md). ### 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 is licensed under the [MIT](https://github.com/Azure/azure-sdk-for-c/blob/main/LICENSE) license. diff --git a/sdk/samples/iot/aziot_esp32/readme.md b/sdk/samples/iot/aziot_esp32/readme.md index d7d06c7384..7b2c722b28 100644 --- a/sdk/samples/iot/aziot_esp32/readme.md +++ b/sdk/samples/iot/aziot_esp32/readme.md @@ -78,7 +78,7 @@ _The following was run on Windows 10 and Ubuntu Desktop 20.04 environments, with On Windows: Use the PowerShell commands below. ```powershell - PS C:\> Invoke-WebRequest -Uri https://raw.githubusercontent.com/Azure/azure-sdk-for-c/master/sdk/samples/iot/aziot_esp32/New-ArduinoZipLibrary.ps1 -OutFile New-ArduinoZipLibrary.ps1 + PS C:\> Invoke-WebRequest -Uri https://raw.githubusercontent.com/Azure/azure-sdk-for-c/main/sdk/samples/iot/aziot_esp32/New-ArduinoZipLibrary.ps1 -OutFile New-ArduinoZipLibrary.ps1 PS C:\> .\New-ArduinoZipLibrary.ps1 ``` @@ -97,14 +97,14 @@ _The following was run on Windows 10 and Ubuntu Desktop 20.04 environments, with On Linux: ```bash - $ wget https://raw.githubusercontent.com/Azure/azure-sdk-for-c/master/sdk/samples/iot/aziot_esp32/generate_arduino_zip_library.sh + $ wget https://raw.githubusercontent.com/Azure/azure-sdk-for-c/main/sdk/samples/iot/aziot_esp32/generate_arduino_zip_library.sh $ chmod 777 generate_arduino_zip_library.sh $ ./generate_arduino_zip_library.sh ``` This will create a local file named `azure-sdk-for-c.zip` containing the entire [Azure SDK for Embedded C](https://github.com/Azure/azure-sdk-for-c) repository as an Arduino library. - NOTE: If you are using WSL, do not run these commands from the Windows system drive (e.g. `/mnt/c/`). + NOTE: If you are using WSL, do not run these commands from the Windows system drive (e.g. `/mnt/c/`). 2. Run the Arduino IDE. @@ -140,7 +140,7 @@ _The following was run on Windows 10 and Ubuntu Desktop 20.04 environments, with ./create_trusted_cert_header.sh ``` - - Open the [ESP32 sample](https://github.com/Azure/azure-sdk-for-c/blob/master/sdk/samples/iot/aziot_esp32) (from the local clone) on the Arduino IDE. + - Open the [ESP32 sample](https://github.com/Azure/azure-sdk-for-c/blob/main/sdk/samples/iot/aziot_esp32) (from the local clone) on the Arduino IDE. - Edit the following parameters in `iot_configs.h`, filling in your own information: @@ -306,18 +306,18 @@ For other regions (and private cloud environments), please use the appropriate r ### Additional Information -For important information and additional guidance about certificates, please refer to [this blog post](https://techcommunity.microsoft.com/t5/internet-of-things/azure-iot-tls-changes-are-coming-and-why-you-should-care/ba-p/1658456) from the security team. +For important information and additional guidance about certificates, please refer to [this blog post](https://techcommunity.microsoft.com/t5/internet-of-things/azure-iot-tls-changes-are-coming-and-why-you-should-care/ba-p/1658456) from the security team. ## Troubleshooting -- The error policy for the Embedded C SDK client library is documented [here](https://github.com/Azure/azure-sdk-for-c/blob/master/sdk/docs/iot/mqtt_state_machine.md#error-policy). +- The error policy for the Embedded C SDK client library is documented [here](https://github.com/Azure/azure-sdk-for-c/blob/main/sdk/docs/iot/mqtt_state_machine.md#error-policy). - 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 -This project welcomes contributions and suggestions. Find more contributing details [here](https://github.com/Azure/azure-sdk-for-c/blob/master/CONTRIBUTING.md). +This project welcomes contributions and suggestions. Find more contributing details [here](https://github.com/Azure/azure-sdk-for-c/blob/main/CONTRIBUTING.md). ### 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 is licensed under the [MIT](https://github.com/Azure/azure-sdk-for-c/blob/main/LICENSE) license. diff --git a/sdk/samples/iot/aziot_esp8266/readme.md b/sdk/samples/iot/aziot_esp8266/readme.md index 9f14f9b56d..d7156b5170 100644 --- a/sdk/samples/iot/aziot_esp8266/readme.md +++ b/sdk/samples/iot/aziot_esp8266/readme.md @@ -80,7 +80,7 @@ _The following was run on Windows 10 and Ubuntu Desktop 20.04 environments, with On Windows: Use the PowerShell commands below. ```powershell - PS C:\> Invoke-WebRequest -Uri https://raw.githubusercontent.com/Azure/azure-sdk-for-c/master/sdk/samples/iot/aziot_esp8266/New-ArduinoZipLibrary.ps1 -OutFile New-ArduinoZipLibrary.ps1 + PS C:\> Invoke-WebRequest -Uri https://raw.githubusercontent.com/Azure/azure-sdk-for-c/main/sdk/samples/iot/aziot_esp8266/New-ArduinoZipLibrary.ps1 -OutFile New-ArduinoZipLibrary.ps1 PS C:\> .\New-ArduinoZipLibrary.ps1 ``` @@ -99,14 +99,14 @@ _The following was run on Windows 10 and Ubuntu Desktop 20.04 environments, with On Linux: ```bash - $ wget https://raw.githubusercontent.com/Azure/azure-sdk-for-c/master/sdk/samples/iot/aziot_esp8266/generate_arduino_zip_library.sh + $ wget https://raw.githubusercontent.com/Azure/azure-sdk-for-c/main/sdk/samples/iot/aziot_esp8266/generate_arduino_zip_library.sh $ chmod 777 generate_arduino_zip_library.sh $ ./generate_arduino_zip_library.sh ``` This will create a local file named `azure-sdk-for-c.zip` containing the entire [Azure SDK for Embedded C](https://github.com/Azure/azure-sdk-for-c) repository as an Arduino library. - NOTE: If you are using WSL, do not run these commands from the Windows system drive (e.g. `/mnt/c/`). + NOTE: If you are using WSL, do not run these commands from the Windows system drive (e.g. `/mnt/c/`). 2. Run the Arduino IDE. @@ -148,7 +148,7 @@ _The following was run on Windows 10 and Ubuntu Desktop 20.04 environments, with ./create_trusted_cert_header.sh ``` - - Open the [ESP8266 sample](https://github.com/Azure/azure-sdk-for-c/blob/master/sdk/samples/iot/aziot_esp8266) (from the local clone) on the Arduino IDE. + - Open the [ESP8266 sample](https://github.com/Azure/azure-sdk-for-c/blob/main/sdk/samples/iot/aziot_esp8266) (from the local clone) on the Arduino IDE. - Edit the following parameters in `iot_configs.h`, filling in your own information: @@ -314,18 +314,18 @@ For other regions (and private cloud environments), please use the appropriate r ### Additional Information -For important information and additional guidance about certificates, please refer to [this blog post](https://techcommunity.microsoft.com/t5/internet-of-things/azure-iot-tls-changes-are-coming-and-why-you-should-care/ba-p/1658456) from the security team. +For important information and additional guidance about certificates, please refer to [this blog post](https://techcommunity.microsoft.com/t5/internet-of-things/azure-iot-tls-changes-are-coming-and-why-you-should-care/ba-p/1658456) from the security team. ## Troubleshooting -- The error policy for the Embedded C SDK client library is documented [here](https://github.com/Azure/azure-sdk-for-c/blob/master/sdk/docs/iot/mqtt_state_machine.md#error-policy). +- The error policy for the Embedded C SDK client library is documented [here](https://github.com/Azure/azure-sdk-for-c/blob/main/sdk/docs/iot/mqtt_state_machine.md#error-policy). - 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 -This project welcomes contributions and suggestions. Find more contributing details [here](https://github.com/Azure/azure-sdk-for-c/blob/master/CONTRIBUTING.md). +This project welcomes contributions and suggestions. Find more contributing details [here](https://github.com/Azure/azure-sdk-for-c/blob/main/CONTRIBUTING.md). ### 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 is licensed under the [MIT](https://github.com/Azure/azure-sdk-for-c/blob/main/LICENSE) license. diff --git a/sdk/samples/iot/aziot_realtek_amebaD/readme.md b/sdk/samples/iot/aziot_realtek_amebaD/readme.md index 2630bfc9d5..f2ff84a93b 100644 --- a/sdk/samples/iot/aziot_realtek_amebaD/readme.md +++ b/sdk/samples/iot/aziot_realtek_amebaD/readme.md @@ -84,7 +84,7 @@ _The following was run on Windows 10 and Ubuntu Desktop 20.04 environments, with On Windows: Use the PowerShell commands below. ```powershell - PS C:\> Invoke-WebRequest -Uri https://raw.githubusercontent.com/Azure/azure-sdk-for-c/master/sdk/samples/iot/aziot_realtek_amebaD/New-ArduinoZipLibrary.ps1 -OutFile New-ArduinoZipLibrary.ps1 + PS C:\> Invoke-WebRequest -Uri https://raw.githubusercontent.com/Azure/azure-sdk-for-c/main/sdk/samples/iot/aziot_realtek_amebaD/New-ArduinoZipLibrary.ps1 -OutFile New-ArduinoZipLibrary.ps1 PS C:\> .\New-ArduinoZipLibrary.ps1 ``` @@ -103,14 +103,14 @@ _The following was run on Windows 10 and Ubuntu Desktop 20.04 environments, with On Linux: ```bash - $ wget https://raw.githubusercontent.com/Azure/azure-sdk-for-c/master/sdk/samples/iot/aziot_realtek_amebaD/generate_arduino_zip_library.sh + $ wget https://raw.githubusercontent.com/Azure/azure-sdk-for-c/main/sdk/samples/iot/aziot_realtek_amebaD/generate_arduino_zip_library.sh $ chmod 777 generate_arduino_zip_library.sh $ ./generate_arduino_zip_library.sh ``` This will create a local file named `azure-sdk-for-c.zip` containing the entire [Azure SDK for Embedded C](https://github.com/Azure/azure-sdk-for-c) repository as an Arduino library. - NOTE: If you are using WSL, do not run these commands from the Windows system drive (e.g. `/mnt/c/`). + NOTE: If you are using WSL, do not run these commands from the Windows system drive (e.g. `/mnt/c/`). 2. Run the Arduino IDE. @@ -152,7 +152,7 @@ _The following was run on Windows 10 and Ubuntu Desktop 20.04 environments, with ./create_trusted_cert_header.sh ``` - - Open the [Realtek AmebaD sample](https://github.com/Azure/azure-sdk-for-c/blob/master/sdk/samples/iot/aziot_realtek_amebaD) (from the local clone) on the Arduino IDE. + - Open the [Realtek AmebaD sample](https://github.com/Azure/azure-sdk-for-c/blob/main/sdk/samples/iot/aziot_realtek_amebaD) (from the local clone) on the Arduino IDE. - Edit the following parameters in `iot_configs.h`, filling in your own information: @@ -261,18 +261,18 @@ For other regions (and private cloud environments), please use the appropriate r ### Additional Information -For important information and additional guidance about certificates, please refer to [this blog post](https://techcommunity.microsoft.com/t5/internet-of-things/azure-iot-tls-changes-are-coming-and-why-you-should-care/ba-p/1658456) from the security team. +For important information and additional guidance about certificates, please refer to [this blog post](https://techcommunity.microsoft.com/t5/internet-of-things/azure-iot-tls-changes-are-coming-and-why-you-should-care/ba-p/1658456) from the security team. ## Troubleshooting -- The error policy for the Embedded C SDK client library is documented [here](https://github.com/Azure/azure-sdk-for-c/blob/master/sdk/docs/iot/mqtt_state_machine.md#error-policy). +- The error policy for the Embedded C SDK client library is documented [here](https://github.com/Azure/azure-sdk-for-c/blob/main/sdk/docs/iot/mqtt_state_machine.md#error-policy). - 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 -This project welcomes contributions and suggestions. Find more contributing details [here](https://github.com/Azure/azure-sdk-for-c/blob/master/CONTRIBUTING.md). +This project welcomes contributions and suggestions. Find more contributing details [here](https://github.com/Azure/azure-sdk-for-c/blob/main/CONTRIBUTING.md). ### 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 is licensed under the [MIT](https://github.com/Azure/azure-sdk-for-c/blob/main/LICENSE) license. diff --git a/sdk/samples/iot/docs/how_to_iot_hub_samples_linux.md b/sdk/samples/iot/docs/how_to_iot_hub_samples_linux.md index 0d5a518c18..9fc187ca7c 100644 --- a/sdk/samples/iot/docs/how_to_iot_hub_samples_linux.md +++ b/sdk/samples/iot/docs/how_to_iot_hub_samples_linux.md @@ -21,7 +21,7 @@ This is a step-by-step guide of how to start from scratch and get the Azure SDK for Embedded C IoT Hub Certificate Samples running on Linux. -Samples are designed to highlight the function calls required to connect with the Azure IoT Hub. These calls illustrate the happy path of the [mqtt state machine](https://github.com/Azure/azure-sdk-for-c/blob/master/sdk/docs/iot/mqtt_state_machine.md). As a result, **these samples are NOT designed to be used as production-level code**. Production code needs to incorporate other elements, such as connection retries and more extensive error-handling, which these samples do not include. +Samples are designed to highlight the function calls required to connect with the Azure IoT Hub. These calls illustrate the happy path of the [mqtt state machine](https://github.com/Azure/azure-sdk-for-c/blob/main/sdk/docs/iot/mqtt_state_machine.md). As a result, **these samples are NOT designed to be used as production-level code**. Production code needs to incorporate other elements, such as connection retries and more extensive error-handling, which these samples do not include. For Linux, the command line examples are tailored to Debian/Ubuntu environments. While Linux devices are not likely to be considered constrained, these samples enable developers to test the Azure SDK for Embedded C libraries, debug, and step through the code, even without a real device. We understand not everyone will have a real device to test and that sometimes these devices won't have debugging capabilities. @@ -53,7 +53,7 @@ To run the samples, ensure you have the following programs and tools installed o 1. Install Microsoft [vcpkg](https://github.com/microsoft/vcpkg) package manager and [Eclipse Paho MQTT C client](https://www.eclipse.org/paho/). This installation may take an extended amount of time (~15-20 minutes). - NOTE: For the correct vcpkg commit, see [vcpkg-commit.txt](https://github.com/Azure/azure-sdk-for-c/blob/master/eng/vcpkg-commit.txt). + NOTE: For the correct vcpkg commit, see [vcpkg-commit.txt](https://github.com/Azure/azure-sdk-for-c/blob/main/eng/vcpkg-commit.txt). ```bash cd ~ # Run this command from any directory to go to your user home directory. @@ -224,48 +224,48 @@ To run the samples, ensure you have the following programs and tools installed o - *Executable:* `paho_iot_hub_c2d_sample` -For the sample description and interaction instructions, please go [here](https://github.com/Azure/azure-sdk-for-c/blob/master/sdk/samples/iot/README.md#iot-hub-c2d-sample). +For the sample description and interaction instructions, please go [here](https://github.com/Azure/azure-sdk-for-c/blob/main/sdk/samples/iot/README.md#iot-hub-c2d-sample). ### IoT Hub Methods Sample - *Executable:* `paho_iot_hub_methods_sample` -For the sample description and interaction instructions, please go [here](https://github.com/Azure/azure-sdk-for-c/blob/master/sdk/samples/iot/README.md#iot-hub-methods-sample). +For the sample description and interaction instructions, please go [here](https://github.com/Azure/azure-sdk-for-c/blob/main/sdk/samples/iot/README.md#iot-hub-methods-sample). ### IoT Hub Telemetry Sample - *Executable:* `paho_iot_hub_telemetry_sample` -For the sample description and interaction instructions, please go [here](https://github.com/Azure/azure-sdk-for-c/blob/master/sdk/samples/iot/README.md#iot-hub-telemetry-sample). +For the sample description and interaction instructions, please go [here](https://github.com/Azure/azure-sdk-for-c/blob/main/sdk/samples/iot/README.md#iot-hub-telemetry-sample). ### IoT Hub Twin Sample - *Executable:* `paho_iot_hub_twin_sample` -For the sample description and interaction instructions, please go [here](https://github.com/Azure/azure-sdk-for-c/blob/master/sdk/samples/iot/README.md#iot-hub-twin-sample). +For the sample description and interaction instructions, please go [here](https://github.com/Azure/azure-sdk-for-c/blob/main/sdk/samples/iot/README.md#iot-hub-twin-sample). ### IoT Hub Plug and Play Sample - *Executable:* `paho_iot_hub_pnp_sample` -For the sample description and interaction instructions, please go [here](https://github.com/Azure/azure-sdk-for-c/blob/master/sdk/samples/iot/README.md#iot-hub-plug-and-play-sample). +For the sample description and interaction instructions, please go [here](https://github.com/Azure/azure-sdk-for-c/blob/main/sdk/samples/iot/README.md#iot-hub-plug-and-play-sample). ### IoT Hub Plug and Play Multiple Component Sample - *Executable:* `paho_iot_hub_pnp_component_sample` -For the sample description and interaction instructions, please go [here](https://github.com/Azure/azure-sdk-for-c/blob/master/sdk/samples/iot/README.md#iot-hub-plug-and-play-multiple-component-sample). +For the sample description and interaction instructions, please go [here](https://github.com/Azure/azure-sdk-for-c/blob/main/sdk/samples/iot/README.md#iot-hub-plug-and-play-multiple-component-sample). ## Troubleshooting -- The error policy for the Embedded C SDK client library is documented [here](https://github.com/Azure/azure-sdk-for-c/blob/master/sdk/docs/iot/mqtt_state_machine.md#error-policy). +- The error policy for the Embedded C SDK client library is documented [here](https://github.com/Azure/azure-sdk-for-c/blob/main/sdk/docs/iot/mqtt_state_machine.md#error-policy). - 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 -This project welcomes contributions and suggestions. Find more contributing details [here](https://github.com/Azure/azure-sdk-for-c/blob/master/CONTRIBUTING.md). +This project welcomes contributions and suggestions. Find more contributing details [here](https://github.com/Azure/azure-sdk-for-c/blob/main/CONTRIBUTING.md). ### 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 is licensed under the [MIT](https://github.com/Azure/azure-sdk-for-c/blob/main/LICENSE) license. diff --git a/sdk/samples/iot/docs/how_to_iot_hub_samples_windows.md b/sdk/samples/iot/docs/how_to_iot_hub_samples_windows.md index a876929121..660eb2bb92 100644 --- a/sdk/samples/iot/docs/how_to_iot_hub_samples_windows.md +++ b/sdk/samples/iot/docs/how_to_iot_hub_samples_windows.md @@ -21,7 +21,7 @@ This is a step-by-step guide of how to start from scratch and get the Azure SDK for Embedded C IoT Hub Certificate Samples running on Microsoft Windows. -Samples are designed to highlight the function calls required to connect with the Azure IoT Hub. These calls illustrate the happy path of the [mqtt state machine](https://github.com/Azure/azure-sdk-for-c/blob/master/sdk/docs/iot/mqtt_state_machine.md). As a result, **these samples are NOT designed to be used as production-level code**. Production code needs to incorporate other elements, such as connection retries and more extensive error-handling, which these samples do not include. These samples also utilize OpenSSL, which is **NOT recommended to use in production-level code on Windows or macOS**. +Samples are designed to highlight the function calls required to connect with the Azure IoT Hub. These calls illustrate the happy path of the [mqtt state machine](https://github.com/Azure/azure-sdk-for-c/blob/main/sdk/docs/iot/mqtt_state_machine.md). As a result, **these samples are NOT designed to be used as production-level code**. Production code needs to incorporate other elements, such as connection retries and more extensive error-handling, which these samples do not include. These samples also utilize OpenSSL, which is **NOT recommended to use in production-level code on Windows or macOS**. For Windows, the command line examples are based on PowerShell. While Windows devices are not likely to be considered constrained, these samples enable developers to test the Azure SDK for Embedded C libraries, debug, and step through the code, even without a real device. We understand not everyone will have a real device to test and that sometimes these devices won't have debugging capabilities. @@ -50,7 +50,7 @@ To run the samples, ensure you have the following programs and tools installed o 1. From PowerShell, install Microsoft [vcpkg](https://github.com/microsoft/vcpkg) package manager and [Eclipse Paho MQTT C client](https://www.eclipse.org/paho/). This installation may take an extended amount of time (~15-20 minutes). - NOTE: For the correct vcpkg commit, see [vcpkg-commit.txt](https://github.com/Azure/azure-sdk-for-c/blob/master/eng/vcpkg-commit.txt). + NOTE: For the correct vcpkg commit, see [vcpkg-commit.txt](https://github.com/Azure/azure-sdk-for-c/blob/main/eng/vcpkg-commit.txt). ```powershell PS C:\> git clone https://github.com/Microsoft/vcpkg.git @@ -226,48 +226,48 @@ To run the samples, ensure you have the following programs and tools installed o - *Executable:* `paho_iot_hub_c2d_sample` -For the sample description and interaction instructions, please go [here](https://github.com/Azure/azure-sdk-for-c/blob/master/sdk/samples/iot/README.md#iot-hub-c2d-sample). +For the sample description and interaction instructions, please go [here](https://github.com/Azure/azure-sdk-for-c/blob/main/sdk/samples/iot/README.md#iot-hub-c2d-sample). ### IoT Hub Methods Sample - *Executable:* `paho_iot_hub_methods_sample` -For the sample description and interaction instructions, please go [here](https://github.com/Azure/azure-sdk-for-c/blob/master/sdk/samples/iot/README.md#iot-hub-methods-sample). +For the sample description and interaction instructions, please go [here](https://github.com/Azure/azure-sdk-for-c/blob/main/sdk/samples/iot/README.md#iot-hub-methods-sample). ### IoT Hub Telemetry Sample - *Executable:* `paho_iot_hub_telemetry_sample` -For the sample description and interaction instructions, please go [here](https://github.com/Azure/azure-sdk-for-c/blob/master/sdk/samples/iot/README.md#iot-hub-telemetry-sample). +For the sample description and interaction instructions, please go [here](https://github.com/Azure/azure-sdk-for-c/blob/main/sdk/samples/iot/README.md#iot-hub-telemetry-sample). ### IoT Hub Twin Sample - *Executable:* `paho_iot_hub_twin_sample` -For the sample description and interaction instructions, please go [here](https://github.com/Azure/azure-sdk-for-c/blob/master/sdk/samples/iot/README.md#iot-hub-twin-sample). +For the sample description and interaction instructions, please go [here](https://github.com/Azure/azure-sdk-for-c/blob/main/sdk/samples/iot/README.md#iot-hub-twin-sample). ### IoT Hub Plug and Play Sample - *Executable:* `paho_iot_hub_pnp_sample` -For the sample description and interaction instructions, please go [here](https://github.com/Azure/azure-sdk-for-c/blob/master/sdk/samples/iot/README.md#iot-hub-plug-and-play-sample). +For the sample description and interaction instructions, please go [here](https://github.com/Azure/azure-sdk-for-c/blob/main/sdk/samples/iot/README.md#iot-hub-plug-and-play-sample). ### IoT Hub Plug and Play Multiple Component Sample - *Executable:* `paho_iot_hub_pnp_component_sample` -For the sample description and interaction instructions, please go [here](https://github.com/Azure/azure-sdk-for-c/blob/master/sdk/samples/iot/README.md#iot-hub-plug-and-play-multiple-component-sample). +For the sample description and interaction instructions, please go [here](https://github.com/Azure/azure-sdk-for-c/blob/main/sdk/samples/iot/README.md#iot-hub-plug-and-play-multiple-component-sample). ## Troubleshooting -- The error policy for the Embedded C SDK client library is documented [here](https://github.com/Azure/azure-sdk-for-c/blob/master/sdk/docs/iot/mqtt_state_machine.md#error-policy). +- The error policy for the Embedded C SDK client library is documented [here](https://github.com/Azure/azure-sdk-for-c/blob/main/sdk/docs/iot/mqtt_state_machine.md#error-policy). - 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 -This project welcomes contributions and suggestions. Find more contributing details [here](https://github.com/Azure/azure-sdk-for-c/blob/master/CONTRIBUTING.md). +This project welcomes contributions and suggestions. Find more contributing details [here](https://github.com/Azure/azure-sdk-for-c/blob/main/CONTRIBUTING.md). ### 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 is licensed under the [MIT](https://github.com/Azure/azure-sdk-for-c/blob/main/LICENSE) license. diff --git a/sdk/samples/iot/paho_iot_hub_pnp_component_sample.c b/sdk/samples/iot/paho_iot_hub_pnp_component_sample.c new file mode 100644 index 0000000000..ac19b8cddd --- /dev/null +++ b/sdk/samples/iot/paho_iot_hub_pnp_component_sample.c @@ -0,0 +1,1225 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// SPDX-License-Identifier: MIT + +#include +#include +#include +#include +#include +#include + +#ifdef _MSC_VER +#pragma warning(push) +// warning C4201: nonstandard extension used: nameless struct/union +#pragma warning(disable : 4201) +#endif +#include +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#include +#include + +#include "iot_sample_common.h" +#include "pnp/pnp_device_info_component.h" +#include "pnp/pnp_mqtt_message.h" +#include "pnp/pnp_protocol.h" +#include "pnp/pnp_thermostat_component.h" + +#define SAMPLE_TYPE PAHO_IOT_HUB +#define SAMPLE_NAME PAHO_IOT_HUB_PNP_COMPONENT_SAMPLE + +#define DEFAULT_START_TEMP_CELSIUS 22.0 +#define DOUBLE_DECIMAL_PLACE_DIGITS 2 + +bool is_device_operational = true; + +// * PnP Values * +// The model id is the JSON document (also called the Digital Twins Model Identifier or DTMI) which +// defines the capability of your device. The functionality of the device should match what is +// described in the corresponding DTMI. Should you choose to program your own PnP capable device, +// the functionality would need to match the DTMI and you would need to update the below 'model_id'. +// Please see the sample README for more information on this DTMI. +static az_span const model_id + = AZ_SPAN_LITERAL_FROM_STR("dtmi:com:example:TemperatureController;1"); + +// Components +static pnp_thermostat_component thermostat_1; +static pnp_thermostat_component thermostat_2; +static az_span const thermostat_1_name = AZ_SPAN_LITERAL_FROM_STR("thermostat1"); +static az_span const thermostat_2_name = AZ_SPAN_LITERAL_FROM_STR("thermostat2"); +static az_span const device_information_name = AZ_SPAN_LITERAL_FROM_STR("deviceInformation"); +static az_span const* pnp_components[] + = { &thermostat_1_name, &thermostat_2_name, &device_information_name }; +static int32_t const pnp_components_num = sizeof(pnp_components) / sizeof(pnp_components[0]); + +// IoT Hub Device Twin Values +static az_span const twin_reported_serial_number_property_name + = AZ_SPAN_LITERAL_FROM_STR("serialNumber"); +static az_span twin_reported_serial_number_property_value = AZ_SPAN_LITERAL_FROM_STR("ABCDEFG"); +static az_span const twin_response_failed = AZ_SPAN_LITERAL_FROM_STR("failed"); + +// IoT Hub Method (Command) Values +static az_span const command_reboot_name = AZ_SPAN_LITERAL_FROM_STR("reboot"); +static az_span const command_empty_response_payload = AZ_SPAN_LITERAL_FROM_STR("{}"); +static char command_property_scratch_buffer[64]; + +// IoT Hub Telemetry Values +static az_span const telemetry_working_set_name = AZ_SPAN_LITERAL_FROM_STR("workingSet"); + +static iot_sample_environment_variables env_vars; +static az_iot_hub_client hub_client; +static MQTTClient mqtt_client; +static char mqtt_client_username_buffer[512]; +static pnp_mqtt_message publish_message; + +// +// Functions +// +static void create_and_configure_mqtt_client(void); +static void connect_mqtt_client_to_iot_hub(void); +static void subscribe_mqtt_client_to_iot_hub_topics(void); +static void initialize_components(void); +static void send_device_info(void); +static void send_serial_number(void); +static void request_device_twin_document(void); +static void receive_messages(void); +static void disconnect_mqtt_client_from_iot_hub(void); + +// General message sending and receiving functions +static void publish_mqtt_message(char const* topic, az_span payload, int qos); +static void receive_mqtt_message(void); +static void on_message_received( + char* topic, + int topic_len, + MQTTClient_message const* receive_message); + +// Device twin, command request, telemetry functions +static void handle_device_twin_message( + MQTTClient_message const* receive_message, + az_iot_hub_client_twin_response const* twin_response); +static void handle_command_request( + MQTTClient_message const* receive_message, + az_iot_hub_client_method_request const* command_request); +static void send_telemetry_messages(void); + +// Temperature controller functions +static void temp_controller_build_telemetry_message(az_span payload, az_span* out_payload); +static void temp_controller_build_serial_number_reported_property( + az_span payload, + az_span* out_payload); +static void temp_controller_build_error_reported_property_with_status( + az_span component_name, + az_span property_name, + az_json_reader* property_value, + az_iot_status status, + int32_t version, + az_span payload, + az_span* out_payload); +static bool temp_controller_process_property_update( + az_span component_name, + az_json_token const* property_name, + az_json_reader const* property_value, + int32_t version, + az_span payload, + az_span* out_payload); +static bool temp_controller_process_command_request( + az_span command_name, + az_span command_payload, + az_span payload, + az_span* out_payload, + az_iot_status* out_status); +static void temp_controller_invoke_reboot(void); + +// Callbacks +static void property_callback( + az_span component_name, + az_json_token const* property_name, + az_json_reader property_value, + int32_t version, + void* user_context_callback); +static az_result append_int32_callback(az_json_writer* jw, void* value); +static az_result append_json_token_callback(az_json_writer* jw, void* value); +static az_result append_string_callback(az_json_writer* jw, void* value); + +/* + * This sample extends the IoT Hub Plug and Play Sample above to mimic a Temperature Controller + * and connects the IoT Plug and Play enabled device (the Temperature Controller) with the Digital + * Twin Model ID (DTMI). If a timeout occurs while waiting for a message from the Azure IoT + * Explorer, the sample will continue. If PNP_MQTT_TIMEOUT_RECEIVE_MAX_COUNT timeouts occur + * consecutively, the sample will disconnect. X509 self-certification is used. + * + * This Temperature Controller is made up of the following components: + * - Device Info + * - Temperature Sensor 1 + * - Temperature Sensor 2 + * + * To interact with this sample, you must use the Azure IoT Explorer. The capabilities are Device + * Twin, Direct Method (Command), and Telemetry: + * + * Device Twin: The following device twin properties are supported in this sample: + * + * Temperature Controller: + * - A reported property named `serialNumber` with a `string` value for the device serial number. + * + * Device Info: + * - A reported property named `manufacturer` with a `string` value for the name of the device + * manufacturer. + * - A reported property named `model` with a `string` value for the name of the device model. + * - A reported property named `swVersion` with a `string` value for the software version running on + * the device. + * - A reported property named `osName` with a `string` value for the name of the operating system + * running on the device. + * - A reported property named `processorArchitecture` with a `string` value for the name of the + * device architecture. + * - A reported property named `processorManufacturer` with a `string` value for the name of the + * device's processor manufacturer. + * - A reported property named `totalStorage` with a `double` value for the total storage in KiB on + * the device. + * - A reported property named `totalMemory` with a `double` value for the total memory in KiB on + * the device. + * + * Temperature Sensor: + * - A desired property named `targetTemperature` with a `double` value for the desired temperature. + * - A reported property named `maxTempSinceLastReboot` with a `double` value for the highest + * temperature reached since boot. + * + * On initial bootup of the device, the sample will send the Temperature Controller reported + * properties to the service. You will see the following in the device twin JSON. + * { + * "properties": { + * "reported": { + * "manufacturer": "Sample-Manufacturer", + * "model": "pnp-sample-Model-123", + * "swVersion": "1.0.0.0", + * "osName": "Contoso", + * "processorArchitecture": "Contoso-Arch-64bit", + * "processorManufacturer": "Processor Manufacturer(TM)", + * "totalStorage": 1024, + * "totalMemory": 128, + * "serialNumber": "ABCDEFG", + * } + * } + * } + * + * To send a Temperature Sensor device twin desired property message, select your device's Device + * Twin tab in the Azure IoT Explorer. Add the property targetTemperature along with a corresponding + * value to the corresponding thermostat in the desired section of the JSON. Select Save to update + * the twin document and send the twin message to the device. + * { + * "properties": { + * "desired": { + * "thermostat1": { + * "targetTemperature": 34.8 + * }, + * "thermostat2": { + * "targetTemperature": 68.5 + * } + * } + * } + * } + * + * Upon receiving a desired property message, the sample will update the twin property locally and + * send a reported property of the same name back to the service. This message will include a set of + * "ack" values: `ac` for the HTTP-like ack code, `av` for ack version of the property, and an + * optional `ad` for an ack description. + * { + * "properties": { + * "reported": { + * "thermostat1": { + * "__t": "c", + * "maxTempSinceLastReboot": 38.2, + * "targetTemperature": { + * "value": 34.8, + * "ac": 200, + * "av": 27, + * "ad": "success" + * } + * }, + * "thermostat2": { + * "__t": "c", + * "maxTempSinceLastReboot": 69.1, + * "targetTemperature": { + * "value": 68.5, + * "ac": 200, + * "av": 28, + * "ad": "success" + * } + * } + * } + * } + * } + * + * Direct Method (Command): Two device commands are supported in this sample: `reboot` and + * `getMaxMinReport`. If any other commands are attempted to be invoked, the log will report the + * command is not found. To invoke a command, select your device's Direct Method tab in the Azure + * IoT Explorer. + * + * - To invoke `reboot` on the Temperature Controller, enter the command name `reboot`. Select + * Invoke method. + * - To invoke `getMaxMinReport` on Temperature Sensor 1, enter the command name + * `thermostat1/getMaxMinReport` along with a payload using an ISO8601 time format. Select Invoke + * method. + * - To invoke `getMaxMinReport` on Temperature Sensor 2, enter the command name + * `thermostat2/getMaxMinReport` along with a payload using an ISO8601 time format. Select Invoke + * method. + * + * "2020-08-18T17:09:29-0700" + * + * The command will send back to the service a response containing the following JSON payload with + * updated values in each field: + * { + * "maxTemp": 74.3, + * "minTemp": 65.2, + * "avgTemp": 68.79, + * "startTime": "2020-08-18T17:09:29-0700", + * "endTime": "2020-08-18T17:24:32-0700" + * } + * + * Telemetry: The Temperature Controller sends a JSON message with the property name `workingSet` + * and a `double` value for the current working set of the device memory in KiB. Also, each + * Temperature Sensor sends a JSON message with the property name `temperature` and a `double` + * value for the current temperature. + */ +int main(void) +{ + create_and_configure_mqtt_client(); + IOT_SAMPLE_LOG_SUCCESS("Client created and configured."); + + connect_mqtt_client_to_iot_hub(); + IOT_SAMPLE_LOG_SUCCESS("Client connected to IoT Hub."); + + subscribe_mqtt_client_to_iot_hub_topics(); + IOT_SAMPLE_LOG_SUCCESS("Client subscribed to IoT Hub topics."); + + // Initializations + az_result rc = pnp_mqtt_message_init(&publish_message); + if (az_result_failed(rc)) + { + IOT_SAMPLE_LOG_ERROR( + "Failed to initialize pnp_mqtt_message: az_result return code 0x%08x.", rc); + exit(rc); + } + initialize_components(); + IOT_SAMPLE_LOG_SUCCESS("Client initialized all components."); + + // Messaging + send_device_info(); + send_serial_number(); + request_device_twin_document(); + receive_messages(); + + disconnect_mqtt_client_from_iot_hub(); + IOT_SAMPLE_LOG_SUCCESS("Client disconnected from IoT Hub."); + + return 0; +} + +static void create_and_configure_mqtt_client(void) +{ + int rc; + + // Reads in environment variables set by user for purposes of running sample. + iot_sample_read_environment_variables(SAMPLE_TYPE, SAMPLE_NAME, &env_vars); + + // Build an MQTT endpoint c-string. + char mqtt_endpoint_buffer[128]; + iot_sample_create_mqtt_endpoint( + SAMPLE_TYPE, &env_vars, mqtt_endpoint_buffer, sizeof(mqtt_endpoint_buffer)); + + // Initialize the hub client with the connection options. + az_iot_hub_client_options options = az_iot_hub_client_options_default(); + options.model_id = model_id; + + rc = az_iot_hub_client_init(&hub_client, env_vars.hub_hostname, env_vars.hub_device_id, &options); + if (az_result_failed(rc)) + { + IOT_SAMPLE_LOG_ERROR("Failed to initialize hub client: az_result return code 0x%08x.", rc); + exit(rc); + } + + // Get the MQTT client id used for the MQTT connection. + char mqtt_client_id_buffer[128]; + rc = az_iot_hub_client_get_client_id( + &hub_client, mqtt_client_id_buffer, sizeof(mqtt_client_id_buffer), NULL); + if (az_result_failed(rc)) + { + IOT_SAMPLE_LOG_ERROR("Failed to get MQTT client id: az_result return code 0x%08x.", rc); + exit(rc); + } + + // Create the Paho MQTT client. + rc = MQTTClient_create( + &mqtt_client, mqtt_endpoint_buffer, mqtt_client_id_buffer, MQTTCLIENT_PERSISTENCE_NONE, NULL); + if (rc != MQTTCLIENT_SUCCESS) + { + IOT_SAMPLE_LOG_ERROR("Failed to create MQTT client: MQTTClient return code %d.", rc); + exit(rc); + } +} + +static void connect_mqtt_client_to_iot_hub(void) +{ + int rc; + + // Get the MQTT client username. + rc = az_iot_hub_client_get_user_name( + &hub_client, mqtt_client_username_buffer, sizeof(mqtt_client_username_buffer), NULL); + if (az_result_failed(rc)) + { + IOT_SAMPLE_LOG_ERROR("Failed to get MQTT client username: az_result return code 0x%08x.", rc); + exit(rc); + } + + IOT_SAMPLE_LOG("MQTT client username: %s\n", mqtt_client_username_buffer); + + // Set MQTT connection options. + MQTTClient_connectOptions mqtt_connect_options = MQTTClient_connectOptions_initializer; + mqtt_connect_options.username = mqtt_client_username_buffer; + mqtt_connect_options.password = NULL; // This sample uses x509 authentication. + mqtt_connect_options.cleansession = false; // Set to false so can receive any pending messages. + mqtt_connect_options.keepAliveInterval = AZ_IOT_DEFAULT_MQTT_CONNECT_KEEPALIVE_SECONDS; + + MQTTClient_SSLOptions mqtt_ssl_options = MQTTClient_SSLOptions_initializer; + mqtt_ssl_options.verify = 1; + mqtt_ssl_options.enableServerCertAuth = 1; + mqtt_ssl_options.keyStore = (char*)az_span_ptr(env_vars.x509_cert_pem_file_path); + if (az_span_size(env_vars.x509_trust_pem_file_path) != 0) // Is only set if required by OS. + { + mqtt_ssl_options.trustStore = (char*)az_span_ptr(env_vars.x509_trust_pem_file_path); + } + mqtt_connect_options.ssl = &mqtt_ssl_options; + + // Connect MQTT client to the Azure IoT Hub. + rc = MQTTClient_connect(mqtt_client, &mqtt_connect_options); + if (rc != MQTTCLIENT_SUCCESS) + { + IOT_SAMPLE_LOG_ERROR( + "Failed to connect: MQTTClient return code %d.\n" + "If on Windows, confirm the AZ_IOT_DEVICE_X509_TRUST_PEM_FILE_PATH environment variable is " + "set correctly.", + rc); + exit(rc); + } +} + +static void subscribe_mqtt_client_to_iot_hub_topics(void) +{ + int rc; + + // Messages received on the Methods topic will be commands to be invoked. + rc = MQTTClient_subscribe(mqtt_client, AZ_IOT_HUB_CLIENT_METHODS_SUBSCRIBE_TOPIC, 1); + if (rc != MQTTCLIENT_SUCCESS) + { + IOT_SAMPLE_LOG_ERROR( + "Failed to subscribe to the Methods topic: MQTTClient return code %d.", rc); + exit(rc); + } + + // Messages received on the Twin Patch topic will be updates to the desired properties. + rc = MQTTClient_subscribe(mqtt_client, AZ_IOT_HUB_CLIENT_TWIN_PATCH_SUBSCRIBE_TOPIC, 1); + if (rc != MQTTCLIENT_SUCCESS) + { + IOT_SAMPLE_LOG_ERROR( + "Failed to subscribe to the Twin Patch topic: MQTTClient return code %d.", rc); + exit(rc); + } + + // Messages received on Twin Response topic will be response statuses from the server. + rc = MQTTClient_subscribe(mqtt_client, AZ_IOT_HUB_CLIENT_TWIN_RESPONSE_SUBSCRIBE_TOPIC, 1); + if (rc != MQTTCLIENT_SUCCESS) + { + IOT_SAMPLE_LOG_ERROR( + "Failed to subscribe to the Twin Response topic: MQTTClient return code %d.", rc); + exit(rc); + } +} + +static void initialize_components(void) +{ + az_result rc; + + // Initialize thermostats 1 and 2. + rc = pnp_thermostat_init(&thermostat_1, thermostat_1_name, DEFAULT_START_TEMP_CELSIUS); + if (az_result_failed(rc)) + { + IOT_SAMPLE_LOG_ERROR( + "Failed to initialize Temperature Sensor 1: az_result return code 0x%08x.", rc); + exit(rc); + } + + rc = pnp_thermostat_init(&thermostat_2, thermostat_2_name, DEFAULT_START_TEMP_CELSIUS); + if (az_result_failed(rc)) + { + IOT_SAMPLE_LOG_ERROR( + "Failed to initialize Temperature Sensor 2: az_result return code 0x%08x.", rc); + exit(rc); + } +} + +static void send_device_info(void) +{ + // Get the Twin Patch topic to send a reported property update. + az_result rc = az_iot_hub_client_twin_patch_get_publish_topic( + &hub_client, + pnp_mqtt_get_request_id(), + publish_message.topic, + publish_message.topic_length, + NULL); + if (az_result_failed(rc)) + { + IOT_SAMPLE_LOG_ERROR("Failed to get the Twin Patch topic: az_result return code 0x%08x.", rc); + exit(rc); + } + + // Build the device info reported property message. + pnp_device_info_build_reported_property(publish_message.payload, &publish_message.out_payload); + + // Publish the device info reported property update. + publish_mqtt_message( + publish_message.topic, publish_message.out_payload, IOT_SAMPLE_MQTT_PUBLISH_QOS); + IOT_SAMPLE_LOG_SUCCESS("Client sent reported property message for device info."); + IOT_SAMPLE_LOG_AZ_SPAN("Payload:", publish_message.out_payload); + IOT_SAMPLE_LOG(" "); // Formatting + + // Receive the response from the server. + receive_mqtt_message(); +} + +static void send_serial_number(void) +{ + // Get the Twin Patch topic to send a reported property update. + az_result rc = az_iot_hub_client_twin_patch_get_publish_topic( + &hub_client, + pnp_mqtt_get_request_id(), + publish_message.topic, + publish_message.topic_length, + NULL); + if (az_result_failed(rc)) + { + IOT_SAMPLE_LOG_ERROR("Failed to get the Twin Patch topic: az_result return code 0x%08x.", rc); + exit(rc); + } + + // Build the serial number reported property message. + temp_controller_build_serial_number_reported_property( + publish_message.payload, &publish_message.out_payload); + + // Publish the serial number reported property update. + publish_mqtt_message( + publish_message.topic, publish_message.out_payload, IOT_SAMPLE_MQTT_PUBLISH_QOS); + IOT_SAMPLE_LOG_SUCCESS( + "Client sent `%.*s` reported property message.", + az_span_size(twin_reported_serial_number_property_name), + az_span_ptr(twin_reported_serial_number_property_name)); + IOT_SAMPLE_LOG_AZ_SPAN("Payload:", publish_message.out_payload); + IOT_SAMPLE_LOG(" "); // Formatting + + // Receive the response from the server. + receive_mqtt_message(); +} + +static void request_device_twin_document(void) +{ + IOT_SAMPLE_LOG("Client requesting device twin document from service."); + + // Get the Twin Document topic to publish the twin document request. + az_result rc = az_iot_hub_client_twin_document_get_publish_topic( + &hub_client, + pnp_mqtt_get_request_id(), + publish_message.topic, + publish_message.topic_length, + NULL); + if (az_result_failed(rc)) + { + IOT_SAMPLE_LOG_ERROR( + "Failed to get the Twin Document topic: az_result return code 0x%08x.", rc); + exit(rc); + } + + // Publish the twin document request. + publish_mqtt_message(publish_message.topic, AZ_SPAN_EMPTY, IOT_SAMPLE_MQTT_PUBLISH_QOS); + IOT_SAMPLE_LOG(" "); // Formatting + + // Receive the response from the server. + receive_mqtt_message(); +} + +static void receive_messages(void) +{ + // Continue to receive commands or device twin messages while device is operational. + while (is_device_operational) + { + az_result rc; + + // Send max temp for each thermostat since boot, if needed. + if (thermostat_1.send_maximum_temperature_property) + { + // Get the Twin Patch topic to send a reported property update. + rc = az_iot_hub_client_twin_patch_get_publish_topic( + &hub_client, + pnp_mqtt_get_request_id(), + publish_message.topic, + publish_message.topic_length, + NULL); + if (az_result_failed(rc)) + { + IOT_SAMPLE_LOG_ERROR( + "Failed to get the Twin Patch topic: az_result return code 0x%08x.", rc); + exit(rc); + } + + // Build the maximum temperature reported property message. + az_span property_name; + pnp_thermostat_build_maximum_temperature_reported_property( + &thermostat_1, publish_message.payload, &publish_message.out_payload, &property_name); + + // Publish the maximum temperature reported property update. + publish_mqtt_message( + publish_message.topic, publish_message.out_payload, IOT_SAMPLE_MQTT_PUBLISH_QOS); + IOT_SAMPLE_LOG_SUCCESS( + "Client sent Temperature Sensor 1 the `%.*s` reported property message.", + az_span_size(property_name), + az_span_ptr(property_name)); + IOT_SAMPLE_LOG_AZ_SPAN("Payload:", publish_message.out_payload); + IOT_SAMPLE_LOG(" "); // Formatting + + thermostat_1.send_maximum_temperature_property = false; // Only send again if new max set. + + // Receive the response from the server. + receive_mqtt_message(); + } + + if (thermostat_2.send_maximum_temperature_property) + { + // Get the Twin Patch topic to send a reported property update. + rc = az_iot_hub_client_twin_patch_get_publish_topic( + &hub_client, + pnp_mqtt_get_request_id(), + publish_message.topic, + publish_message.topic_length, + NULL); + if (az_result_failed(rc)) + { + IOT_SAMPLE_LOG_ERROR( + "Failed to get the Twin Patch topic: az_result return code 0x%08x.", rc); + exit(rc); + } + + // Build the maximum temperature reported property message. + az_span property_name; + pnp_thermostat_build_maximum_temperature_reported_property( + &thermostat_2, publish_message.payload, &publish_message.out_payload, &property_name); + + // Publish the maximum temperature reported property update. + publish_mqtt_message( + publish_message.topic, publish_message.out_payload, IOT_SAMPLE_MQTT_PUBLISH_QOS); + + IOT_SAMPLE_LOG_SUCCESS( + "Client sent Temperature Sensor 2 the `%.*s` reported property message.", + az_span_size(property_name), + az_span_ptr(property_name)); + IOT_SAMPLE_LOG_AZ_SPAN("Payload:", publish_message.out_payload); + IOT_SAMPLE_LOG(" "); // Formatting + + thermostat_2.send_maximum_temperature_property = false; // Only send again if new max set. + + // Receive the response from the server. + receive_mqtt_message(); + } + + // Send telemetry messages. No response requested from server. + send_telemetry_messages(); + IOT_SAMPLE_LOG(" "); // Formatting. + + // Wait for any server-initiated messages. + receive_mqtt_message(); + } +} + +static void disconnect_mqtt_client_from_iot_hub(void) +{ + int rc = MQTTClient_disconnect(mqtt_client, PNP_MQTT_TIMEOUT_DISCONNECT_MS); + if (rc != MQTTCLIENT_SUCCESS) + { + IOT_SAMPLE_LOG_ERROR("Failed to disconnect MQTT client: MQTTClient return code %d.", rc); + exit(rc); + } + + MQTTClient_destroy(&mqtt_client); +} + +static void publish_mqtt_message(char const* topic, az_span payload, int qos) +{ + int rc = MQTTClient_publish( + mqtt_client, topic, az_span_size(payload), az_span_ptr(payload), qos, 0, NULL); + + if (rc != MQTTCLIENT_SUCCESS) + { + IOT_SAMPLE_LOG_ERROR("Failed to publish message: MQTTClient return code %d", rc); + exit(rc); + } +} + +static void receive_mqtt_message(void) +{ + char* topic = NULL; + int topic_len = 0; + MQTTClient_message* receive_message = NULL; + static int8_t timeout_counter; + + IOT_SAMPLE_LOG("Waiting for command request or device twin message.\n"); + + // MQTTCLIENT_SUCCESS or MQTTCLIENT_TOPICNAME_TRUNCATED if a message is received. + // MQTTCLIENT_SUCCESS can also indicate that the timeout expired, in which case message is NULL. + // MQTTCLIENT_TOPICNAME_TRUNCATED if the topic contains embedded NULL characters. + // An error code is returned if there was a problem trying to receive a message. + int rc = MQTTClient_receive( + mqtt_client, &topic, &topic_len, &receive_message, PNP_MQTT_TIMEOUT_RECEIVE_MS); + if ((rc != MQTTCLIENT_SUCCESS) && (rc != MQTTCLIENT_TOPICNAME_TRUNCATED)) + { + IOT_SAMPLE_LOG_ERROR("Failed to receive message: MQTTClient return code %d.", rc); + exit(rc); + } + else if (receive_message == NULL) + { + // Allow up to TIMEOUT_MQTT_RECEIVE_MAX_COUNT before disconnecting. + if (++timeout_counter >= PNP_MQTT_TIMEOUT_RECEIVE_MAX_MESSAGE_COUNT) + { + IOT_SAMPLE_LOG( + "Receive message timeout expiration count of %d reached.", + PNP_MQTT_TIMEOUT_RECEIVE_MAX_MESSAGE_COUNT); + is_device_operational = false; + } + } + else + { + IOT_SAMPLE_LOG_SUCCESS("Client received a message from the service."); + timeout_counter = 0; // Reset + + if (rc == MQTTCLIENT_TOPICNAME_TRUNCATED) + { + topic_len = (int)strlen(topic); + } + + on_message_received(topic, topic_len, receive_message); + IOT_SAMPLE_LOG(" "); // Formatting + + MQTTClient_freeMessage(&receive_message); + MQTTClient_free(topic); + } +} + +static void on_message_received( + char* topic, + int topic_len, + MQTTClient_message const* receive_message) +{ + az_result rc; + + az_span const topic_span = az_span_create((uint8_t*)topic, topic_len); + az_span const message_span + = az_span_create((uint8_t*)receive_message->payload, receive_message->payloadlen); + + az_iot_hub_client_twin_response twin_response; + az_iot_hub_client_method_request command_request; + + // Parse the incoming message topic and handle appropriately. + rc = az_iot_hub_client_twin_parse_received_topic(&hub_client, topic_span, &twin_response); + if (az_result_succeeded(rc)) + { + IOT_SAMPLE_LOG_SUCCESS("Client received a valid topic response."); + IOT_SAMPLE_LOG_AZ_SPAN("Topic:", topic_span); + IOT_SAMPLE_LOG_AZ_SPAN("Payload:", message_span); + IOT_SAMPLE_LOG("Status: %d", twin_response.status); + + handle_device_twin_message(receive_message, &twin_response); + } + else + { + rc = az_iot_hub_client_methods_parse_received_topic(&hub_client, topic_span, &command_request); + if (az_result_succeeded(rc)) + { + IOT_SAMPLE_LOG_SUCCESS("Client received a valid topic response."); + IOT_SAMPLE_LOG_AZ_SPAN("Topic:", topic_span); + IOT_SAMPLE_LOG_AZ_SPAN("Payload:", message_span); + + handle_command_request(receive_message, &command_request); + } + else + { + IOT_SAMPLE_LOG_ERROR("Message from unknown topic: az_result return code 0x%08x.", rc); + IOT_SAMPLE_LOG_AZ_SPAN("Topic:", topic_span); + exit(rc); + } + } +} + +static void handle_device_twin_message( + MQTTClient_message const* receive_message, + az_iot_hub_client_twin_response const* twin_response) +{ + az_span const message_span + = az_span_create((uint8_t*)receive_message->payload, receive_message->payloadlen); + + // Invoke appropriate action per response type (3 types only). + switch (twin_response->response_type) + { + // A response from a twin GET publish message with the twin document as a payload. + case AZ_IOT_HUB_CLIENT_TWIN_RESPONSE_TYPE_GET: + IOT_SAMPLE_LOG("Message Type: GET"); + pnp_process_device_twin_message( + message_span, false, pnp_components, pnp_components_num, property_callback, NULL); + break; + + // An update to the desired properties with the properties as a payload. + case AZ_IOT_HUB_CLIENT_TWIN_RESPONSE_TYPE_DESIRED_PROPERTIES: + IOT_SAMPLE_LOG("Message Type: Desired Properties"); + pnp_process_device_twin_message( + message_span, true, pnp_components, pnp_components_num, property_callback, NULL); + break; + + // A response from a twin reported properties publish message. + case AZ_IOT_HUB_CLIENT_TWIN_RESPONSE_TYPE_REPORTED_PROPERTIES: + IOT_SAMPLE_LOG("Message Type: Reported Properties"); + break; + + // An error response. + case AZ_IOT_HUB_CLIENT_TWIN_RESPONSE_TYPE_REQUEST_ERROR: + IOT_SAMPLE_LOG_ERROR("Message Type: Request Error"); + break; + } +} + +static void handle_command_request( + MQTTClient_message const* receive_message, + az_iot_hub_client_method_request const* command_request) +{ + az_span component_name; + az_span command_name; + pnp_parse_command_name(command_request->name, &component_name, &command_name); + + az_span const message_span + = az_span_create((uint8_t*)receive_message->payload, receive_message->payloadlen); + az_iot_status status = AZ_IOT_STATUS_UNKNOWN; + + // Invoke command and retrieve status and response payload to send to server. + if (az_span_is_content_equal(thermostat_1.component_name, component_name)) + { + if (pnp_thermostat_process_command_request( + &thermostat_1, + command_name, + message_span, + publish_message.payload, + &publish_message.out_payload, + &status)) + { + IOT_SAMPLE_LOG_AZ_SPAN("Client invoked command on Temperature Sensor 1:", command_name); + } + } + else if (az_span_is_content_equal(thermostat_2.component_name, component_name)) + { + if (pnp_thermostat_process_command_request( + &thermostat_2, + command_name, + message_span, + publish_message.payload, + &publish_message.out_payload, + &status)) + { + IOT_SAMPLE_LOG_AZ_SPAN("Client invoked command on Temperature Sensor 2:", command_name); + } + } + else if (az_span_size(component_name) == 0) + { + if (temp_controller_process_command_request( + command_name, + message_span, + publish_message.payload, + &publish_message.out_payload, + &status)) + { + IOT_SAMPLE_LOG_AZ_SPAN("Client invoked command on Temperature Controller:", command_name); + } + } + else + { + IOT_SAMPLE_LOG_AZ_SPAN("Command not supported:", command_request->name); + publish_message.out_payload = command_empty_response_payload; + status = AZ_IOT_STATUS_NOT_FOUND; + } + + // Get the Methods response topic to publish the command response. + az_result rc = az_iot_hub_client_methods_response_get_publish_topic( + &hub_client, + command_request->request_id, + (uint16_t)status, + publish_message.topic, + publish_message.topic_length, + NULL); + if (az_result_failed(rc)) + { + IOT_SAMPLE_LOG_ERROR( + "Failed to get the Methods response topic: az_result return code 0x%08x.", rc); + exit(rc); + } + + // Publish the command response. + publish_mqtt_message( + publish_message.topic, publish_message.out_payload, IOT_SAMPLE_MQTT_PUBLISH_QOS); + IOT_SAMPLE_LOG_SUCCESS("Client published command response."); + IOT_SAMPLE_LOG("Status: %d", status); + IOT_SAMPLE_LOG_AZ_SPAN("Payload:", publish_message.out_payload); +} + +static void send_telemetry_messages(void) +{ + // Temperature Sensor 1 + // Get the Telemetry topic to publish the telemetry message. + pnp_telemetry_get_publish_topic( + &hub_client, + NULL, + thermostat_1.component_name, + publish_message.topic, + publish_message.topic_length, + NULL); + + // Build the Telemetry message. + pnp_thermostat_build_telemetry_message( + &thermostat_1, publish_message.payload, &publish_message.out_payload); + + // Publish the telemetry message. + publish_mqtt_message( + publish_message.topic, publish_message.out_payload, IOT_SAMPLE_MQTT_PUBLISH_QOS); + IOT_SAMPLE_LOG_SUCCESS("Client published the Telemetry message for Temperature Sensor 1."); + IOT_SAMPLE_LOG_AZ_SPAN("Payload:", publish_message.out_payload); + + // Temperature Sensor 2 + // Get the Telemetry topic to publish the telemetry message. + pnp_telemetry_get_publish_topic( + &hub_client, + NULL, + thermostat_2.component_name, + publish_message.topic, + publish_message.topic_length, + NULL); + + // Build the Telemetry message. + pnp_thermostat_build_telemetry_message( + &thermostat_2, publish_message.payload, &publish_message.out_payload); + + // Publish the telemetry message. + publish_mqtt_message( + publish_message.topic, publish_message.out_payload, IOT_SAMPLE_MQTT_PUBLISH_QOS); + IOT_SAMPLE_LOG_SUCCESS("Client published the Telemetry message for Temperature Sensor 2."); + IOT_SAMPLE_LOG_AZ_SPAN("Payload:", publish_message.out_payload); + + // Temperature Controller + // Get the Telemetry topic to publish the telemetry message. + pnp_telemetry_get_publish_topic( + &hub_client, NULL, AZ_SPAN_EMPTY, publish_message.topic, publish_message.topic_length, NULL); + + // Build the Telemetry message. + temp_controller_build_telemetry_message(publish_message.payload, &publish_message.out_payload); + + // Publish the Telemetry message. + publish_mqtt_message( + publish_message.topic, publish_message.out_payload, IOT_SAMPLE_MQTT_PUBLISH_QOS); + IOT_SAMPLE_LOG_SUCCESS("Client published the Telemetry message for the Temperature Controller."); + IOT_SAMPLE_LOG_AZ_SPAN("Payload:", publish_message.out_payload); +} + +static void temp_controller_build_telemetry_message(az_span payload, az_span* out_payload) +{ + int32_t working_set_ram_in_kibibytes = rand() % 128; + + pnp_build_telemetry_message( + payload, + telemetry_working_set_name, + append_int32_callback, + (void*)&working_set_ram_in_kibibytes, + out_payload); +} + +static void temp_controller_build_serial_number_reported_property( + az_span payload, + az_span* out_payload) +{ + pnp_build_reported_property( + payload, + AZ_SPAN_EMPTY, + twin_reported_serial_number_property_name, + append_string_callback, + (void*)&twin_reported_serial_number_property_value, + out_payload); +} + +static void temp_controller_build_error_reported_property_with_status( + az_span component_name, + az_span property_name, + az_json_reader* property_value, + az_iot_status status, + int32_t version, + az_span payload, + az_span* out_payload) +{ + pnp_build_reported_property_with_status( + payload, + component_name, + property_name, + append_json_token_callback, + (void*)property_value, + (int32_t)status, + version, + twin_response_failed, + out_payload); +} + +static bool temp_controller_process_property_update( + az_span component_name, + az_json_token const* property_name, + az_json_reader const* property_value, + int32_t version, + az_span payload, + az_span* out_payload) +{ + // Not implemented because no properties currently supported to update. + (void)component_name; + (void)property_name; + (void)property_value; + (void)version; + (void)payload; + (void)out_payload; + + return false; +} + +static bool temp_controller_process_command_request( + az_span command_name, + az_span command_received_payload, + az_span payload, + az_span* out_payload, + az_iot_status* out_status) +{ + (void)command_received_payload; // May be used in future. + (void)payload; // May be used in future. + + if (az_span_is_content_equal(command_reboot_name, command_name)) + { + // Invoke command. + temp_controller_invoke_reboot(); + *out_payload = command_empty_response_payload; + *out_status = AZ_IOT_STATUS_OK; + } + else // Unsupported command + { + *out_payload = command_empty_response_payload; + *out_status = AZ_IOT_STATUS_NOT_FOUND; + IOT_SAMPLE_LOG_AZ_SPAN("Command not supported on Temperature Controller:", command_name); + return false; + } + + return true; +} + +static void temp_controller_invoke_reboot(void) +{ + IOT_SAMPLE_LOG("Client invoking reboot command on Temperature Controller.\n"); + IOT_SAMPLE_LOG("Client rebooting.\n"); + + disconnect_mqtt_client_from_iot_hub(); + IOT_SAMPLE_LOG_SUCCESS("Client disconnected from IoT Hub."); + + create_and_configure_mqtt_client(); + IOT_SAMPLE_LOG_SUCCESS("Client created and configured."); + + connect_mqtt_client_to_iot_hub(); + IOT_SAMPLE_LOG_SUCCESS("Client connected to IoT Hub."); + + subscribe_mqtt_client_to_iot_hub_topics(); + IOT_SAMPLE_LOG_SUCCESS("Client subscribed to IoT Hub topics."); + + // Initializations + az_result rc = pnp_mqtt_message_init(&publish_message); + if (az_result_failed(rc)) + { + IOT_SAMPLE_LOG_ERROR( + "Failed to initialize pnp_mqtt_message: az_result return code 0x%08x.", rc); + exit(rc); + } + initialize_components(); + IOT_SAMPLE_LOG_SUCCESS("Client initialized all components."); + + // Messaging + send_device_info(); + send_serial_number(); + request_device_twin_document(); +} + +static void property_callback( + az_span component_name, + az_json_token const* property_name, + az_json_reader property_value, + int32_t version, + void* user_context_callback) +{ + az_result rc; + + (void)user_context_callback; + + // Get the Twin Patch topic to send a property update. + rc = az_iot_hub_client_twin_patch_get_publish_topic( + &hub_client, + pnp_mqtt_get_request_id(), + publish_message.topic, + publish_message.topic_length, + NULL); + if (az_result_failed(rc)) + { + IOT_SAMPLE_LOG_ERROR("Failed to get the Twin Patch topic: az_result return code 0x%08x.", rc); + exit(rc); + } + + // Attempt to process property update per component until find success or exit on error. + if (az_span_is_content_equal(thermostat_1.component_name, component_name)) + { + if (!pnp_thermostat_process_property_update( + &thermostat_1, + property_name, + &property_value, + version, + publish_message.payload, + &publish_message.out_payload)) + { + IOT_SAMPLE_LOG_ERROR( + "Temperature Sensor 1 does not support writeable property `%.*s`.", + az_span_size(property_name->slice), + az_span_ptr(property_name->slice)); + + // Build the component error message. + pnp_thermostat_build_error_reported_property_with_status( + component_name, + property_name->slice, + &property_value, + AZ_IOT_STATUS_NOT_FOUND, + version, + publish_message.payload, + &publish_message.out_payload); + } + } + else if (az_span_is_content_equal(thermostat_2.component_name, component_name)) + { + if (!pnp_thermostat_process_property_update( + &thermostat_2, + property_name, + &property_value, + version, + publish_message.payload, + &publish_message.out_payload)) + { + IOT_SAMPLE_LOG_ERROR( + "Temperature Sensor 2 does not support writeable property `%.*s`.", + az_span_size(property_name->slice), + az_span_ptr(property_name->slice)); + + // Build the component error message. + pnp_thermostat_build_error_reported_property_with_status( + component_name, + property_name->slice, + &property_value, + AZ_IOT_STATUS_NOT_FOUND, + version, + publish_message.payload, + &publish_message.out_payload); + } + } + else if (az_span_size(component_name) == 0) + { + if (!temp_controller_process_property_update( + component_name, + property_name, + &property_value, + version, + publish_message.payload, + &publish_message.out_payload)) + { + IOT_SAMPLE_LOG_ERROR( + "Temperature Controller does not support writable property `%.*s`. All writeable " + "properties are on sub-components.", + az_span_size(property_name->slice), + az_span_ptr(property_name->slice)); + + // Build the root component error message with status. + temp_controller_build_error_reported_property_with_status( + component_name, + property_name->slice, + &property_value, + AZ_IOT_STATUS_NOT_FOUND, + version, + publish_message.payload, + &publish_message.out_payload); + } + } + else + { + IOT_SAMPLE_LOG("No components recognized to update a property."); + return; + } + + // Send response. On a successfull property update above, out_payload will already be set. + publish_mqtt_message( + publish_message.topic, publish_message.out_payload, IOT_SAMPLE_MQTT_PUBLISH_QOS); + IOT_SAMPLE_LOG_SUCCESS("Client sent reported property with status message."); + IOT_SAMPLE_LOG_AZ_SPAN("Payload:", publish_message.out_payload); + IOT_SAMPLE_LOG(" "); // Formatting. + + // Receive the response from the server. + receive_mqtt_message(); +} + +static az_result append_int32_callback(az_json_writer* jw, void* value) +{ + return az_json_writer_append_int32(jw, *(int32_t*)value); +} + +static az_result append_json_token_callback(az_json_writer* jw, void* value) +{ + char const* const log = "Failed to append json token callback"; + + az_json_token value_token = *(az_json_token*)value; + + double value_as_double; + int32_t string_length; + + switch (value_token.kind) + { + case AZ_JSON_TOKEN_NUMBER: + IOT_SAMPLE_EXIT_IF_AZ_FAILED(az_json_token_get_double(&value_token, &value_as_double), log); + IOT_SAMPLE_EXIT_IF_AZ_FAILED( + az_json_writer_append_double(jw, value_as_double, DOUBLE_DECIMAL_PLACE_DIGITS), log); + break; + case AZ_JSON_TOKEN_STRING: + IOT_SAMPLE_EXIT_IF_AZ_FAILED( + az_json_token_get_string( + &value_token, + command_property_scratch_buffer, + sizeof(command_property_scratch_buffer), + &string_length), + log); + IOT_SAMPLE_EXIT_IF_AZ_FAILED( + az_json_writer_append_string( + jw, az_span_create((uint8_t*)command_property_scratch_buffer, string_length)), + log); + break; + default: + return AZ_ERROR_ITEM_NOT_FOUND; + } + + return AZ_OK; +} + +static az_result append_string_callback(az_json_writer* jw, void* value) +{ + return az_json_writer_append_string(jw, *(az_span*)value); +} diff --git a/sdk/samples/iot/paho_iot_hub_pnp_sample.c b/sdk/samples/iot/paho_iot_hub_pnp_sample.c new file mode 100644 index 0000000000..513d4b78a5 --- /dev/null +++ b/sdk/samples/iot/paho_iot_hub_pnp_sample.c @@ -0,0 +1,995 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// SPDX-License-Identifier: MIT + +#ifdef _MSC_VER +// warning C4204: nonstandard extension used: non-constant aggregate initializer +#pragma warning(disable : 4204) +// warning C4996: 'localtime': This function or variable may be unsafe. Consider using localtime_s +// instead. +#pragma warning(disable : 4996) +#endif + +#include +#include +#include +#include +#include +#include + +#ifdef _MSC_VER +#pragma warning(push) +// warning C4201: nonstandard extension used: nameless struct/union +#pragma warning(disable : 4201) +#endif +#include +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#include +#include + +#include "iot_sample_common.h" + +#define SAMPLE_TYPE PAHO_IOT_HUB +#define SAMPLE_NAME PAHO_IOT_HUB_PNP_SAMPLE + +#define MQTT_TIMEOUT_RECEIVE_MAX_MESSAGE_COUNT 3 +#define MQTT_TIMEOUT_RECEIVE_MS (8 * 1000) +#define MQTT_TIMEOUT_DISCONNECT_MS (10 * 1000) + +#define DEFAULT_START_TEMP_COUNT 1 +#define DEFAULT_START_TEMP_CELSIUS 22.0 +#define DOUBLE_DECIMAL_PLACE_DIGITS 2 + +bool is_device_operational = true; +static char const iso_spec_time_format[] = "%Y-%m-%dT%H:%M:%S%z"; // ISO8601 Time Format + +// * PnP Values * +// The model id is the JSON document (also called the Digital Twins Model Identifier or DTMI) which +// defines the capability of your device. The functionality of the device should match what is +// described in the corresponding DTMI. Should you choose to program your own PnP capable device, +// the functionality would need to match the DTMI and you would need to update the below 'model_id'. +// Please see the sample README for more information on this DTMI. +static az_span const model_id = AZ_SPAN_LITERAL_FROM_STR("dtmi:com:example:Thermostat;1"); + +// IoT Hub Connection Values +static uint32_t connection_request_id_int = 0; +static char connection_request_id_buffer[16]; + +// IoT Hub Device Twin Values +static az_span const twin_desired_name = AZ_SPAN_LITERAL_FROM_STR("desired"); +static az_span const twin_version_name = AZ_SPAN_LITERAL_FROM_STR("$version"); +static az_span const twin_success_name = AZ_SPAN_LITERAL_FROM_STR("success"); +static az_span const twin_value_name = AZ_SPAN_LITERAL_FROM_STR("value"); +static az_span const twin_ack_code_name = AZ_SPAN_LITERAL_FROM_STR("ac"); +static az_span const twin_ack_version_name = AZ_SPAN_LITERAL_FROM_STR("av"); +static az_span const twin_ack_description_name = AZ_SPAN_LITERAL_FROM_STR("ad"); +static az_span const twin_desired_temperature_property_name + = AZ_SPAN_LITERAL_FROM_STR("targetTemperature"); +static az_span const twin_reported_maximum_temperature_property_name + = AZ_SPAN_LITERAL_FROM_STR("maxTempSinceLastReboot"); + +// IoT Hub Method (Command) Values +static az_span const command_getMaxMinReport_name = AZ_SPAN_LITERAL_FROM_STR("getMaxMinReport"); +static az_span const command_max_temp_name = AZ_SPAN_LITERAL_FROM_STR("maxTemp"); +static az_span const command_min_temp_name = AZ_SPAN_LITERAL_FROM_STR("minTemp"); +static az_span const command_avg_temp_name = AZ_SPAN_LITERAL_FROM_STR("avgTemp"); +static az_span const command_start_time_name = AZ_SPAN_LITERAL_FROM_STR("startTime"); +static az_span const command_end_time_name = AZ_SPAN_LITERAL_FROM_STR("endTime"); +static az_span const command_empty_response_payload = AZ_SPAN_LITERAL_FROM_STR("{}"); +static char command_start_time_value_buffer[32]; +static char command_end_time_value_buffer[32]; +static char command_response_payload_buffer[256]; + +// IoT Hub Telemetry Values +static az_span const telemetry_temperature_name = AZ_SPAN_LITERAL_FROM_STR("temperature"); + +// PnP Device Values +static double device_current_temperature = DEFAULT_START_TEMP_CELSIUS; +static double device_maximum_temperature = DEFAULT_START_TEMP_CELSIUS; +static double device_minimum_temperature = DEFAULT_START_TEMP_CELSIUS; +static double device_temperature_summation = DEFAULT_START_TEMP_CELSIUS; +static uint32_t device_temperature_count = DEFAULT_START_TEMP_COUNT; +static double device_average_temperature = DEFAULT_START_TEMP_CELSIUS; + +static iot_sample_environment_variables env_vars; +static az_iot_hub_client hub_client; +static MQTTClient mqtt_client; +static char mqtt_client_username_buffer[256]; + +// +// Functions +// +static void create_and_configure_mqtt_client(void); +static void connect_mqtt_client_to_iot_hub(void); +static void subscribe_mqtt_client_to_iot_hub_topics(void); +static void request_device_twin_document(void); +static void receive_messages(void); +static void disconnect_mqtt_client_from_iot_hub(void); + +static az_span get_request_id(void); +static void publish_mqtt_message(char const* topic, az_span payload, int qos); +static void on_message_received(char* topic, int topic_len, MQTTClient_message const* message); + +// Device Twin functions +static void handle_device_twin_message( + MQTTClient_message const* message, + az_iot_hub_client_twin_response const* twin_response); +static void process_device_twin_message(az_span message_span, bool is_twin_get); +static bool parse_desired_temperature_property( + az_span message_span, + bool is_twin_get, + double* out_parsed_temperature, + int32_t* out_parsed_version_number); +static void update_device_temperature_property(double temperature, bool* out_is_max_temp_changed); +static void send_reported_property(az_span name, double value, int32_t version, bool confirm); + +// Command functions +static void handle_command_request( + MQTTClient_message const* message, + az_iot_hub_client_method_request const* command_request); +static void send_command_response( + az_iot_hub_client_method_request const* command_request, + az_iot_status status, + az_span response); +static bool invoke_getMaxMinReport(az_span payload, az_span response, az_span* out_response); + +// Telemetry functions +static void send_telemetry_message(void); + +// JSON build functions +static void build_property_payload( + uint8_t property_count, + az_span const names[], + double const values[], + az_span const times[], + az_span property_payload, + az_span* out_property_payload); +static void build_property_payload_with_status( + az_span name, + double value, + int32_t ack_code_value, + int32_t ack_version_value, + az_span ack_description_value, + az_span property_payload, + az_span* out_property_payload); + +/* + * This sample connects an IoT Plug and Play enabled device with the Digital Twin Model ID (DTMI). + * If a timeout occurs while waiting for a message from the Azure IoT Explorer, the sample will + * continue. If MQTT_TIMEOUT_RECEIVE_MAX_MESSAGE_COUNT timeouts occur consecutively, the sample will + * disconnect. X509 self-certification is used. + * + * To interact with this sample, you must use the Azure IoT Explorer. The capabilities are Device + * Twin, Direct Method (Command), and Telemetry: + * + * Device Twin: Two device twin properties are supported in this sample. + * - A desired property named `targetTemperature` with a `double` value for the desired + * temperature. + * - A reported property named `maxTempSinceLastReboot` with a `double` value for the highest + * temperature reached since device boot. + * + * To send a device twin desired property message, select your device's Device Twin tab in the Azure + * IoT Explorer. Add the property targetTemperature along with a corresponding value to the desired + * section of the JSON. Select Save to update the twin document and send the twin message to the + * device. + * { + * "properties": { + * "desired": { + * "targetTemperature": 68.5, + * } + * } + * } + * + * Upon receiving a desired property message, the sample will update the twin property locally and + * send a reported property of the same name back to the service. This message will include a set of + * "ack" values: `ac` for the HTTP-like ack code, `av` for ack version of the property, and an + * optional `ad` for an ack description. + * { + * "properties": { + * "reported": { + * "targetTemperature": { + * "value": 68.5, + * "ac": 200, + * "av": 14, + * "ad": "success" + * }, + * "maxTempSinceLastReboot": 74.3, + * } + * } + * } + * + * Direct Method (Command): One device command is supported in this sample: `getMaxMinReport`. If + * any other commands are attempted to be invoked, the log will report the command is not found. To + * invoke a command, select your device's Direct Method tab in the Azure IoT Explorer. Enter the + * command name `getMaxMinReport` along with a payload using an ISO8061 time format and select + * Invoke method. + * + * "2020-08-18T17:09:29-0700" + * + * The command will send back to the service a response containing the following JSON payload with + * updated values in each field: + * { + * "maxTemp": 74.3, + * "minTemp": 65.2, + * "avgTemp": 68.79, + * "startTime": "2020-08-18T17:09:29-0700", + * "endTime": "2020-08-18T17:24:32-0700" + * } + * + * Telemetry: Device sends a JSON message with the field name `temperature` and the `double` value + * of the current temperature. + */ +int main(void) +{ + create_and_configure_mqtt_client(); + IOT_SAMPLE_LOG_SUCCESS("Client created and configured."); + + connect_mqtt_client_to_iot_hub(); + IOT_SAMPLE_LOG_SUCCESS("Client connected to IoT Hub."); + + subscribe_mqtt_client_to_iot_hub_topics(); + IOT_SAMPLE_LOG_SUCCESS("Client subscribed to IoT Hub topics."); + + request_device_twin_document(); + receive_messages(); + + disconnect_mqtt_client_from_iot_hub(); + IOT_SAMPLE_LOG_SUCCESS("Client disconnected from IoT Hub."); + + return 0; +} + +static void create_and_configure_mqtt_client(void) +{ + int rc; + + // Reads in environment variables set by user for purposes of running sample. + iot_sample_read_environment_variables(SAMPLE_TYPE, SAMPLE_NAME, &env_vars); + + // Build an MQTT endpoint c-string. + char mqtt_endpoint_buffer[128]; + iot_sample_create_mqtt_endpoint( + SAMPLE_TYPE, &env_vars, mqtt_endpoint_buffer, sizeof(mqtt_endpoint_buffer)); + + // Initialize the hub client with the connection options. + az_iot_hub_client_options options = az_iot_hub_client_options_default(); + options.model_id = model_id; + + rc = az_iot_hub_client_init(&hub_client, env_vars.hub_hostname, env_vars.hub_device_id, &options); + if (az_result_failed(rc)) + { + IOT_SAMPLE_LOG_ERROR("Failed to initialize hub client: az_result return code 0x%08x.", rc); + exit(rc); + } + + // Get the MQTT client id used for the MQTT connection. + char mqtt_client_id_buffer[128]; + rc = az_iot_hub_client_get_client_id( + &hub_client, mqtt_client_id_buffer, sizeof(mqtt_client_id_buffer), NULL); + if (az_result_failed(rc)) + { + IOT_SAMPLE_LOG_ERROR("Failed to get MQTT client id: az_result return code 0x%08x.", rc); + exit(rc); + } + + // Create the Paho MQTT client. + rc = MQTTClient_create( + &mqtt_client, mqtt_endpoint_buffer, mqtt_client_id_buffer, MQTTCLIENT_PERSISTENCE_NONE, NULL); + if (rc != MQTTCLIENT_SUCCESS) + { + IOT_SAMPLE_LOG_ERROR("Failed to create MQTT client: MQTTClient return code %d.", rc); + exit(rc); + } +} + +static void connect_mqtt_client_to_iot_hub(void) +{ + int rc; + + // Get the MQTT client username. + rc = az_iot_hub_client_get_user_name( + &hub_client, mqtt_client_username_buffer, sizeof(mqtt_client_username_buffer), NULL); + if (az_result_failed(rc)) + { + IOT_SAMPLE_LOG_ERROR("Failed to get MQTT client username: az_result return code 0x%08x.", rc); + exit(rc); + } + + IOT_SAMPLE_LOG("MQTT client username: %s\n", mqtt_client_username_buffer); + + // Set MQTT connection options. + MQTTClient_connectOptions mqtt_connect_options = MQTTClient_connectOptions_initializer; + mqtt_connect_options.username = mqtt_client_username_buffer; + mqtt_connect_options.password = NULL; // This sample uses x509 authentication. + mqtt_connect_options.cleansession = false; // Set to false so can receive any pending messages. + mqtt_connect_options.keepAliveInterval = AZ_IOT_DEFAULT_MQTT_CONNECT_KEEPALIVE_SECONDS; + + MQTTClient_SSLOptions mqtt_ssl_options = MQTTClient_SSLOptions_initializer; + mqtt_ssl_options.verify = 1; + mqtt_ssl_options.enableServerCertAuth = 1; + mqtt_ssl_options.keyStore = (char*)az_span_ptr(env_vars.x509_cert_pem_file_path); + if (az_span_size(env_vars.x509_trust_pem_file_path) != 0) // Is only set if required by OS. + { + mqtt_ssl_options.trustStore = (char*)az_span_ptr(env_vars.x509_trust_pem_file_path); + } + mqtt_connect_options.ssl = &mqtt_ssl_options; + + // Connect MQTT client to the Azure IoT Hub. + rc = MQTTClient_connect(mqtt_client, &mqtt_connect_options); + if (rc != MQTTCLIENT_SUCCESS) + { + IOT_SAMPLE_LOG_ERROR( + "Failed to connect: MQTTClient return code %d.\n" + "If on Windows, confirm the AZ_IOT_DEVICE_X509_TRUST_PEM_FILE_PATH environment variable is " + "set correctly.", + rc); + exit(rc); + } +} + +static void subscribe_mqtt_client_to_iot_hub_topics(void) +{ + int rc; + + // Messages received on the Methods topic will be commands to be invoked. + rc = MQTTClient_subscribe(mqtt_client, AZ_IOT_HUB_CLIENT_METHODS_SUBSCRIBE_TOPIC, 1); + if (rc != MQTTCLIENT_SUCCESS) + { + IOT_SAMPLE_LOG_ERROR( + "Failed to subscribe to the Methods topic: MQTTClient return code %d.", rc); + exit(rc); + } + + // Messages received on the Twin Patch topic will be updates to the desired properties. + rc = MQTTClient_subscribe(mqtt_client, AZ_IOT_HUB_CLIENT_TWIN_PATCH_SUBSCRIBE_TOPIC, 1); + if (rc != MQTTCLIENT_SUCCESS) + { + IOT_SAMPLE_LOG_ERROR( + "Failed to subscribe to the Twin Patch topic: MQTTClient return code %d.", rc); + exit(rc); + } + + // Messages received on Twin Response topic will be response statuses from the server. + rc = MQTTClient_subscribe(mqtt_client, AZ_IOT_HUB_CLIENT_TWIN_RESPONSE_SUBSCRIBE_TOPIC, 1); + if (rc != MQTTCLIENT_SUCCESS) + { + IOT_SAMPLE_LOG_ERROR( + "Failed to subscribe to the Twin Response topic: MQTTClient return code %d.", rc); + exit(rc); + } +} + +static void request_device_twin_document(void) +{ + az_result rc; + + IOT_SAMPLE_LOG("Client requesting device twin document from service."); + + // Get the Twin Document topic to publish the twin document request. + char twin_document_topic_buffer[128]; + rc = az_iot_hub_client_twin_document_get_publish_topic( + &hub_client, + get_request_id(), + twin_document_topic_buffer, + sizeof(twin_document_topic_buffer), + NULL); + + if (az_result_failed(rc)) + { + IOT_SAMPLE_LOG_ERROR("Failed to get the Twin Document topic: az_result return code %04x", rc); + exit(rc); + } + + // Publish the twin document request. + publish_mqtt_message(twin_document_topic_buffer, AZ_SPAN_EMPTY, IOT_SAMPLE_MQTT_PUBLISH_QOS); +} + +static void receive_messages(void) +{ + char* topic = NULL; + int topic_len = 0; + MQTTClient_message* message = NULL; + uint8_t timeout_counter = 0; + + // Continue to receive commands or device twin messages while device is operational. + while (is_device_operational) + { + IOT_SAMPLE_LOG(" "); // Formatting + IOT_SAMPLE_LOG("Waiting for command request or device twin message.\n"); + + // MQTTCLIENT_SUCCESS or MQTTCLIENT_TOPICNAME_TRUNCATED if a message is received. + // MQTTCLIENT_SUCCESS can also indicate that the timeout expired, in which case message is NULL. + // MQTTCLIENT_TOPICNAME_TRUNCATED if the topic contains embedded NULL characters. + // An error code is returned if there was a problem trying to receive a message. + int rc = MQTTClient_receive(mqtt_client, &topic, &topic_len, &message, MQTT_TIMEOUT_RECEIVE_MS); + if ((rc != MQTTCLIENT_SUCCESS) && (rc != MQTTCLIENT_TOPICNAME_TRUNCATED)) + { + IOT_SAMPLE_LOG_ERROR("Failed to receive message: MQTTClient return code %d.", rc); + exit(rc); + } + else if (message == NULL) + { + // Allow up to MQTT_TIMEOUT_RECEIVE_MAX_COUNT before disconnecting. + if (++timeout_counter >= MQTT_TIMEOUT_RECEIVE_MAX_MESSAGE_COUNT) + { + IOT_SAMPLE_LOG( + "Receive message timeout expiration count of %d reached.", + MQTT_TIMEOUT_RECEIVE_MAX_MESSAGE_COUNT); + return; + } + } + else + { + IOT_SAMPLE_LOG_SUCCESS("Client received a message from the service."); + timeout_counter = 0; // Reset + + if (rc == MQTTCLIENT_TOPICNAME_TRUNCATED) + { + topic_len = (int)strlen(topic); + } + + on_message_received(topic, topic_len, message); + IOT_SAMPLE_LOG(" "); // Formatting + + MQTTClient_freeMessage(&message); + MQTTClient_free(topic); + } + + send_telemetry_message(); + } +} + +static void disconnect_mqtt_client_from_iot_hub(void) +{ + int rc = MQTTClient_disconnect(mqtt_client, MQTT_TIMEOUT_DISCONNECT_MS); + if (rc != MQTTCLIENT_SUCCESS) + { + IOT_SAMPLE_LOG_ERROR("Failed to disconnect MQTT client: MQTTClient return code %d.", rc); + exit(rc); + } + + MQTTClient_destroy(&mqtt_client); +} + +static az_span get_request_id(void) +{ + az_span remainder; + az_span out_span = az_span_create( + (uint8_t*)connection_request_id_buffer, sizeof(connection_request_id_buffer)); + + az_result rc = az_span_u32toa(out_span, connection_request_id_int++, &remainder); + if (az_result_failed(rc)) + { + IOT_SAMPLE_LOG_ERROR("Failed to get request id: az_result return code 0x%08x.", rc); + exit(rc); + } + + return az_span_slice(out_span, 0, az_span_size(out_span) - az_span_size(remainder)); +} + +static void publish_mqtt_message(const char* topic, az_span payload, int qos) +{ + int rc = MQTTClient_publish( + mqtt_client, topic, az_span_size(payload), az_span_ptr(payload), qos, 0, NULL); + if (rc != MQTTCLIENT_SUCCESS) + { + IOT_SAMPLE_LOG_ERROR("Failed to publish message: MQTTClient return code %d", rc); + exit(rc); + } +} + +static void on_message_received(char* topic, int topic_len, MQTTClient_message const* message) +{ + az_result rc; + + az_span const topic_span = az_span_create((uint8_t*)topic, topic_len); + az_span const message_span = az_span_create((uint8_t*)message->payload, message->payloadlen); + + az_iot_hub_client_twin_response twin_response; + az_iot_hub_client_method_request command_request; + + // Parse the incoming message topic and handle appropriately. + rc = az_iot_hub_client_twin_parse_received_topic(&hub_client, topic_span, &twin_response); + if (az_result_succeeded(rc)) + { + IOT_SAMPLE_LOG_SUCCESS("Client received a valid topic response."); + IOT_SAMPLE_LOG_AZ_SPAN("Topic:", topic_span); + IOT_SAMPLE_LOG_AZ_SPAN("Payload:", message_span); + IOT_SAMPLE_LOG("Status: %d", twin_response.status); + + handle_device_twin_message(message, &twin_response); + } + else + { + rc = az_iot_hub_client_methods_parse_received_topic(&hub_client, topic_span, &command_request); + if (az_result_succeeded(rc)) + { + IOT_SAMPLE_LOG_SUCCESS("Client received a valid topic response."); + IOT_SAMPLE_LOG_AZ_SPAN("Topic:", topic_span); + IOT_SAMPLE_LOG_AZ_SPAN("Payload:", message_span); + + handle_command_request(message, &command_request); + } + else + { + IOT_SAMPLE_LOG_ERROR("Message from unknown topic: az_result return code 0x%08x.", rc); + IOT_SAMPLE_LOG_AZ_SPAN("Topic:", topic_span); + exit(rc); + } + } +} + +static void handle_device_twin_message( + MQTTClient_message const* message, + az_iot_hub_client_twin_response const* twin_response) +{ + bool is_twin_get = false; + az_span const message_span = az_span_create((uint8_t*)message->payload, message->payloadlen); + + // Invoke appropriate action per response type (3 types only). + switch (twin_response->response_type) + { + // A response from a twin GET publish message with the twin document as a payload. + case AZ_IOT_HUB_CLIENT_TWIN_RESPONSE_TYPE_GET: + IOT_SAMPLE_LOG("Message Type: GET"); + is_twin_get = true; + process_device_twin_message(message_span, is_twin_get); + break; + + // An update to the desired properties with the properties as a payload. + case AZ_IOT_HUB_CLIENT_TWIN_RESPONSE_TYPE_DESIRED_PROPERTIES: + IOT_SAMPLE_LOG("Message Type: Desired Properties"); + process_device_twin_message(message_span, is_twin_get); + break; + + // A response from a twin reported properties publish message. + case AZ_IOT_HUB_CLIENT_TWIN_RESPONSE_TYPE_REPORTED_PROPERTIES: + IOT_SAMPLE_LOG("Message Type: Reported Properties"); + break; + + // An error response. + case AZ_IOT_HUB_CLIENT_TWIN_RESPONSE_TYPE_REQUEST_ERROR: + IOT_SAMPLE_LOG_ERROR("Message Type: Request Error"); + break; + } +} + +static void process_device_twin_message(az_span message_span, bool is_twin_get) +{ + double desired_temperature; + int32_t version_number; + + // Parse for the desired temperature property. + if (parse_desired_temperature_property( + message_span, is_twin_get, &desired_temperature, &version_number)) + { + IOT_SAMPLE_LOG(" "); // Formatting + + bool confirm = true; + bool is_max_temp_changed; + + // Update device temperature locally and report update to server. + update_device_temperature_property(desired_temperature, &is_max_temp_changed); + send_reported_property( + twin_desired_temperature_property_name, desired_temperature, version_number, confirm); + + if (is_max_temp_changed) + { + confirm = false; + send_reported_property( + twin_reported_maximum_temperature_property_name, device_maximum_temperature, -1, confirm); + } + } +} + +static bool parse_desired_temperature_property( + az_span message_span, + bool is_twin_get, + double* out_parsed_temperature, + int32_t* out_parsed_version_number) +{ + char const* const log = "Failed to parse for `%.*s` property"; + az_span property = twin_desired_temperature_property_name; + + *out_parsed_temperature = 0.0; + *out_parsed_version_number = 0; + + // Parse message_span. + az_json_reader jr; + IOT_SAMPLE_EXIT_IF_AZ_FAILED(az_json_reader_init(&jr, message_span, NULL), log, property); + IOT_SAMPLE_EXIT_IF_AZ_FAILED(az_json_reader_next_token(&jr), log, property); + + if (jr.token.kind != AZ_JSON_TOKEN_BEGIN_OBJECT) + { + IOT_SAMPLE_LOG( + "`%.*s` property object not found in device twin GET response.", + az_span_size(twin_desired_name), + az_span_ptr(twin_desired_name)); + return false; + } + + // Device twin GET response: Parse to the "desired" wrapper if it exists. + bool desired_found = false; + if (is_twin_get) + { + IOT_SAMPLE_EXIT_IF_AZ_FAILED(az_json_reader_next_token(&jr), log, property); + + while (jr.token.kind != AZ_JSON_TOKEN_END_OBJECT) + { + if (az_json_token_is_text_equal(&jr.token, twin_desired_name)) + { + IOT_SAMPLE_EXIT_IF_AZ_FAILED(az_json_reader_next_token(&jr), log, property); + desired_found = true; + break; + } + else + { + IOT_SAMPLE_EXIT_IF_AZ_FAILED(az_json_reader_skip_children(&jr), log, property); + } + IOT_SAMPLE_EXIT_IF_AZ_FAILED(az_json_reader_next_token(&jr), log, property); + } + + if (!desired_found) + { + IOT_SAMPLE_LOG( + "`%.*s` property object not found in device twin GET response.", + az_span_size(twin_desired_name), + az_span_ptr(twin_desired_name)); + return false; + } + } + + // Device twin get response OR desired property response: + // Parse for the desired temperature property + bool temp_found = false; + bool version_found = false; + + IOT_SAMPLE_EXIT_IF_AZ_FAILED(az_json_reader_next_token(&jr), log, property); + while (!(temp_found && version_found) && (jr.token.kind != AZ_JSON_TOKEN_END_OBJECT)) + { + if (az_json_token_is_text_equal(&jr.token, twin_desired_temperature_property_name)) + { + IOT_SAMPLE_EXIT_IF_AZ_FAILED(az_json_reader_next_token(&jr), log, property); + IOT_SAMPLE_EXIT_IF_AZ_FAILED( + az_json_token_get_double(&jr.token, out_parsed_temperature), log, property); + temp_found = true; + } + else if (az_json_token_is_text_equal(&jr.token, twin_version_name)) + { + IOT_SAMPLE_EXIT_IF_AZ_FAILED(az_json_reader_next_token(&jr), log, property); + IOT_SAMPLE_EXIT_IF_AZ_FAILED( + az_json_token_get_int32(&jr.token, out_parsed_version_number), log, property); + version_found = true; + } + else + { + IOT_SAMPLE_EXIT_IF_AZ_FAILED(az_json_reader_skip_children(&jr), log, property); + } + IOT_SAMPLE_EXIT_IF_AZ_FAILED(az_json_reader_next_token(&jr), log, property); + } + + if (temp_found && version_found) + { + IOT_SAMPLE_LOG( + "Parsed desired `%.*s`: %2f", + az_span_size(property), + az_span_ptr(property), + *out_parsed_temperature); + IOT_SAMPLE_LOG( + "Parsed `%.*s` number: %d", + az_span_size(twin_version_name), + az_span_ptr(twin_version_name), + *out_parsed_version_number); + } + else + { + IOT_SAMPLE_LOG( + "Either `%.*s` or `%.*s` were not found in desired property response.", + az_span_size(property), + az_span_ptr(property), + az_span_size(twin_version_name), + az_span_ptr(twin_version_name)); + return false; + } + + return true; +} + +static void update_device_temperature_property(double temperature, bool* out_is_max_temp_changed) +{ + if (device_maximum_temperature < device_minimum_temperature) + { + exit(1); + } + + *out_is_max_temp_changed = false; + device_current_temperature = temperature; + + // Update maximum or minimum temperatures. + if (device_current_temperature > device_maximum_temperature) + { + device_maximum_temperature = device_current_temperature; + *out_is_max_temp_changed = true; + } + else if (device_current_temperature < device_minimum_temperature) + { + device_minimum_temperature = device_current_temperature; + } + + // Calculate the new average temperature. + device_temperature_count++; + device_temperature_summation += device_current_temperature; + device_average_temperature = device_temperature_summation / device_temperature_count; + + IOT_SAMPLE_LOG_SUCCESS("Client updated desired temperature variables locally."); + IOT_SAMPLE_LOG("Current Temperature: %2f", device_current_temperature); + IOT_SAMPLE_LOG("Maximum Temperature: %2f", device_maximum_temperature); + IOT_SAMPLE_LOG("Minimum Temperature: %2f", device_minimum_temperature); + IOT_SAMPLE_LOG("Average Temperature: %2f", device_average_temperature); +} + +static void send_reported_property(az_span name, double value, int32_t version, bool confirm) +{ + az_result rc; + + // Get the Twin Patch topic to send a reported property update. + char twin_patch_topic_buffer[128]; + rc = az_iot_hub_client_twin_patch_get_publish_topic( + &hub_client, + get_request_id(), + twin_patch_topic_buffer, + sizeof(twin_patch_topic_buffer), + NULL); + if (az_result_failed(rc)) + { + IOT_SAMPLE_LOG_ERROR("Failed to get the Twin Patch topic: az_result return code 0x%08x.", rc); + exit(rc); + } + + // Build the updated reported property message. + char reported_property_payload_buffer[128]; + az_span reported_property_payload = AZ_SPAN_FROM_BUFFER(reported_property_payload_buffer); + + if (confirm) + { + build_property_payload_with_status( + name, + value, + AZ_IOT_STATUS_OK, + version, + twin_success_name, + reported_property_payload, + &reported_property_payload); + } + else + { + uint8_t count = 1; + az_span const names[1] = { name }; + double const values[1] = { value }; + + build_property_payload( + count, names, values, NULL, reported_property_payload, &reported_property_payload); + } + + // Publish the reported property update. + publish_mqtt_message( + twin_patch_topic_buffer, reported_property_payload, IOT_SAMPLE_MQTT_PUBLISH_QOS); + IOT_SAMPLE_LOG_SUCCESS("Client published the Twin Patch reported property message."); + IOT_SAMPLE_LOG_AZ_SPAN("Payload:", reported_property_payload); +} + +static void handle_command_request( + MQTTClient_message const* message, + az_iot_hub_client_method_request const* command_request) +{ + az_span const message_span = az_span_create((uint8_t*)message->payload, message->payloadlen); + + if (az_span_is_content_equal(command_getMaxMinReport_name, command_request->name)) + { + az_iot_status status; + az_span command_response_payload = AZ_SPAN_FROM_BUFFER(command_response_payload_buffer); + + // Invoke command. + if (invoke_getMaxMinReport(message_span, command_response_payload, &command_response_payload)) + { + status = AZ_IOT_STATUS_BAD_REQUEST; + } + else + { + status = AZ_IOT_STATUS_OK; + } + IOT_SAMPLE_LOG_SUCCESS("Client invoked command 'getMaxMinReport'."); + + send_command_response(command_request, status, command_response_payload); + } + else + { + IOT_SAMPLE_LOG_AZ_SPAN("Command not supported:", command_request->name); + send_command_response(command_request, AZ_IOT_STATUS_NOT_FOUND, command_empty_response_payload); + } +} + +static void send_command_response( + az_iot_hub_client_method_request const* command_request, + az_iot_status status, + az_span response) +{ + az_result rc; + + // Get the Methods response topic to publish the command response. + char methods_response_topic_buffer[128]; + rc = az_iot_hub_client_methods_response_get_publish_topic( + &hub_client, + command_request->request_id, + (uint16_t)status, + methods_response_topic_buffer, + sizeof(methods_response_topic_buffer), + NULL); + if (az_result_failed(rc)) + { + IOT_SAMPLE_LOG_ERROR( + "Failed to get the Methods Response topic: az_result return code 0x%08x.", rc); + exit(rc); + } + + // Publish the command response. + publish_mqtt_message(methods_response_topic_buffer, response, IOT_SAMPLE_MQTT_PUBLISH_QOS); + IOT_SAMPLE_LOG_SUCCESS("Client published the Command response."); + IOT_SAMPLE_LOG("Status: %d", status); + IOT_SAMPLE_LOG_AZ_SPAN("Payload:", response); +} + +static bool invoke_getMaxMinReport(az_span payload, az_span response, az_span* out_response) +{ + int32_t incoming_since_value_len = 0; + + // Parse the `since` field in the payload. + char const* const log = "Failed to parse for `since` field in payload"; + + az_json_reader jr; + IOT_SAMPLE_EXIT_IF_AZ_FAILED(az_json_reader_init(&jr, payload, NULL), log); + IOT_SAMPLE_EXIT_IF_AZ_FAILED(az_json_reader_next_token(&jr), log); + IOT_SAMPLE_EXIT_IF_AZ_FAILED( + az_json_token_get_string( + &jr.token, + command_start_time_value_buffer, + sizeof(command_start_time_value_buffer), + &incoming_since_value_len), + log); + + // Set the response payload to error if the `since` value was empty. + if (incoming_since_value_len == 0) + { + *out_response = command_empty_response_payload; + return false; + } + + az_span start_time_span + = az_span_create((uint8_t*)command_start_time_value_buffer, incoming_since_value_len); + + IOT_SAMPLE_LOG_AZ_SPAN("Start time:", start_time_span); + + // Get the current time as a string. + time_t rawtime; + struct tm* timeinfo; + time(&rawtime); + timeinfo = localtime(&rawtime); + size_t length = strftime( + command_end_time_value_buffer, + sizeof(command_end_time_value_buffer), + iso_spec_time_format, + timeinfo); + az_span end_time_span = az_span_create((uint8_t*)command_end_time_value_buffer, (int32_t)length); + + IOT_SAMPLE_LOG_AZ_SPAN("End Time:", end_time_span); + + // Build command response message. + uint8_t count = 3; + az_span const names[3] = { command_max_temp_name, command_min_temp_name, command_avg_temp_name }; + double const values[3] + = { device_maximum_temperature, device_minimum_temperature, device_average_temperature }; + az_span const times[2] = { start_time_span, end_time_span }; + + build_property_payload(count, names, values, times, response, out_response); + + return true; +} + +static void send_telemetry_message(void) +{ + az_result rc; + + // Get the Telemetry topic to publish the telemetry message. + char telemetry_topic_buffer[128]; + rc = az_iot_hub_client_telemetry_get_publish_topic( + &hub_client, NULL, telemetry_topic_buffer, sizeof(telemetry_topic_buffer), NULL); + if (az_result_failed(rc)) + { + IOT_SAMPLE_LOG_ERROR("Failed to get the Telemetry topic: az_result return code 0x%08x.", rc); + exit(rc); + } + + // Build the telemetry message. + uint8_t count = 1; + az_span const names[1] = { telemetry_temperature_name }; + double const values[1] = { device_current_temperature }; + + char telemetry_payload_buffer[128]; + az_span telemetry_payload = AZ_SPAN_FROM_BUFFER(telemetry_payload_buffer); + build_property_payload(count, names, values, NULL, telemetry_payload, &telemetry_payload); + + // Publish the telemetry message. + publish_mqtt_message(telemetry_topic_buffer, telemetry_payload, IOT_SAMPLE_MQTT_PUBLISH_QOS); + IOT_SAMPLE_LOG_SUCCESS("Client published the Telemetry message."); + IOT_SAMPLE_LOG_AZ_SPAN("Payload:", telemetry_payload); +} + +static void build_property_payload( + uint8_t property_count, + az_span const names[], + double const values[], + az_span const times[], + az_span property_payload, + az_span* out_property_payload) +{ + char const* const log = "Failed to build property payload"; + + az_json_writer jw; + IOT_SAMPLE_EXIT_IF_AZ_FAILED(az_json_writer_init(&jw, property_payload, NULL), log); + IOT_SAMPLE_EXIT_IF_AZ_FAILED(az_json_writer_append_begin_object(&jw), log); + + for (uint8_t i = 0; i < property_count; i++) + { + IOT_SAMPLE_EXIT_IF_AZ_FAILED(az_json_writer_append_property_name(&jw, names[i]), log); + IOT_SAMPLE_EXIT_IF_AZ_FAILED( + az_json_writer_append_double(&jw, values[i], DOUBLE_DECIMAL_PLACE_DIGITS), log); + } + + if (times != NULL) + { + IOT_SAMPLE_EXIT_IF_AZ_FAILED( + az_json_writer_append_property_name(&jw, command_start_time_name), log); + IOT_SAMPLE_EXIT_IF_AZ_FAILED(az_json_writer_append_string(&jw, times[0]), log); + IOT_SAMPLE_EXIT_IF_AZ_FAILED( + az_json_writer_append_property_name(&jw, command_end_time_name), log); + IOT_SAMPLE_EXIT_IF_AZ_FAILED(az_json_writer_append_string(&jw, times[1]), log); + } + + IOT_SAMPLE_EXIT_IF_AZ_FAILED(az_json_writer_append_end_object(&jw), log); + *out_property_payload = az_json_writer_get_bytes_used_in_destination(&jw); +} + +static void build_property_payload_with_status( + az_span name, + double value, + int32_t ack_code_value, + int32_t ack_version_value, + az_span ack_description_value, + az_span property_payload, + az_span* out_property_payload) +{ + char const* const log = "Failed to build property payload with status"; + + az_json_writer jw; + IOT_SAMPLE_EXIT_IF_AZ_FAILED(az_json_writer_init(&jw, property_payload, NULL), log); + IOT_SAMPLE_EXIT_IF_AZ_FAILED(az_json_writer_append_begin_object(&jw), log); + IOT_SAMPLE_EXIT_IF_AZ_FAILED(az_json_writer_append_property_name(&jw, name), log); + IOT_SAMPLE_EXIT_IF_AZ_FAILED(az_json_writer_append_begin_object(&jw), log); + IOT_SAMPLE_EXIT_IF_AZ_FAILED(az_json_writer_append_property_name(&jw, twin_value_name), log); + IOT_SAMPLE_EXIT_IF_AZ_FAILED( + az_json_writer_append_double(&jw, value, DOUBLE_DECIMAL_PLACE_DIGITS), log); + IOT_SAMPLE_EXIT_IF_AZ_FAILED(az_json_writer_append_property_name(&jw, twin_ack_code_name), log); + IOT_SAMPLE_EXIT_IF_AZ_FAILED(az_json_writer_append_int32(&jw, ack_code_value), log); + IOT_SAMPLE_EXIT_IF_AZ_FAILED( + az_json_writer_append_property_name(&jw, twin_ack_version_name), log); + IOT_SAMPLE_EXIT_IF_AZ_FAILED(az_json_writer_append_int32(&jw, ack_version_value), log); + IOT_SAMPLE_EXIT_IF_AZ_FAILED( + az_json_writer_append_property_name(&jw, twin_ack_description_name), log); + IOT_SAMPLE_EXIT_IF_AZ_FAILED(az_json_writer_append_string(&jw, ack_description_value), log); + IOT_SAMPLE_EXIT_IF_AZ_FAILED(az_json_writer_append_end_object(&jw), log); + IOT_SAMPLE_EXIT_IF_AZ_FAILED(az_json_writer_append_end_object(&jw), log); + + *out_property_payload = az_json_writer_get_bytes_used_in_destination(&jw); +} diff --git a/sdk/src/azure/core/CMakeLists.txt b/sdk/src/azure/core/CMakeLists.txt index f70cc2e610..fd76e8e9e6 100644 --- a/sdk/src/azure/core/CMakeLists.txt +++ b/sdk/src/azure/core/CMakeLists.txt @@ -44,6 +44,7 @@ include(CheckAndIncludeCodeCov) add_library ( az_core + ${CMAKE_CURRENT_LIST_DIR}/az_base64.c ${CMAKE_CURRENT_LIST_DIR}/az_context.c ${CMAKE_CURRENT_LIST_DIR}/az_http_pipeline.c ${CMAKE_CURRENT_LIST_DIR}/az_http_policy.c diff --git a/sdk/src/azure/core/az_base64.c b/sdk/src/azure/core/az_base64.c new file mode 100644 index 0000000000..4c7587774c --- /dev/null +++ b/sdk/src/azure/core/az_base64.c @@ -0,0 +1,521 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// SPDX-License-Identifier: MIT + +#include +#include + +#include + +// The maximum integer length of binary data that can be encoded into base 64 text and still fit +// into an az_span which has an int32_t length, i.e. (INT32_MAX / 4) * 3; +#define _az_MAX_SAFE_ENCODED_LENGTH 1610612733 + +#define _az_ENCODING_PAD '=' + +static char const _az_base64_encode_array[65] + = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +static int8_t const _az_base64_decode_array[256] = { + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + 62, + -1, + -1, + -1, + 63, // 62 is placed at index 43 (for +), 63 at index 47 (for /) + 52, + 53, + 54, + 55, + 56, + 57, + 58, + 59, + 60, + 61, + -1, + -1, + -1, + -1, + -1, + -1, // 52-61 are placed at index 48-57 (for 0-9), 64 at index 61 (for =) + -1, + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + -1, + -1, + -1, + -1, + -1, // 0-25 are placed at index 65-90 (for A-Z) + -1, + 26, + 27, + 28, + 29, + 30, + 31, + 32, + 33, + 34, + 35, + 36, + 37, + 38, + 39, + 40, + 41, + 42, + 43, + 44, + 45, + 46, + 47, + 48, + 49, + 50, + 51, + -1, + -1, + -1, + -1, + -1, // 26-51 are placed at index 97-122 (for a-z) + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, // Bytes over 122 ('z') are invalid and cannot be decoded. Hence, padding the map with 255, + // which indicates invalid input + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, +}; + +static AZ_NODISCARD int32_t _az_base64_encode(uint8_t* three_bytes) +{ + int32_t i = (*three_bytes << 16) | (*(three_bytes + 1) << 8) | *(three_bytes + 2); + + int32_t i0 = _az_base64_encode_array[i >> 18]; + int32_t i1 = _az_base64_encode_array[(i >> 12) & 0x3F]; + int32_t i2 = _az_base64_encode_array[(i >> 6) & 0x3F]; + int32_t i3 = _az_base64_encode_array[i & 0x3F]; + + return i0 | (i1 << 8) | (i2 << 16) | (i3 << 24); +} + +static AZ_NODISCARD int32_t _az_base64_encode_and_pad_one(uint8_t* two_bytes) +{ + int32_t i = (*two_bytes << 16) | (*(two_bytes + 1) << 8); + + int32_t i0 = _az_base64_encode_array[i >> 18]; + int32_t i1 = _az_base64_encode_array[(i >> 12) & 0x3F]; + int32_t i2 = _az_base64_encode_array[(i >> 6) & 0x3F]; + + return i0 | (i1 << 8) | (i2 << 16) | (_az_ENCODING_PAD << 24); +} + +static AZ_NODISCARD int32_t _az_base64_encode_and_pad_two(uint8_t* one_byte) +{ + int32_t i = (*one_byte << 8); + + int32_t i0 = _az_base64_encode_array[i >> 10]; + int32_t i1 = _az_base64_encode_array[(i >> 4) & 0x3F]; + + return i0 | (i1 << 8) | (_az_ENCODING_PAD << 16) | (_az_ENCODING_PAD << 24); +} + +static void _az_base64_write_int_as_four_bytes(uint8_t* destination, int32_t value) +{ + *(destination + 3) = (uint8_t)((value >> 24) & 0xFF); + *(destination + 2) = (uint8_t)((value >> 16) & 0xFF); + *(destination + 1) = (uint8_t)((value >> 8) & 0xFF); + *(destination + 0) = (uint8_t)(value & 0xFF); +} + +AZ_NODISCARD az_result +az_base64_encode(az_span destination_base64_text, az_span source_bytes, int32_t* out_written) +{ + _az_PRECONDITION_VALID_SPAN(destination_base64_text, 4, false); + _az_PRECONDITION_VALID_SPAN(source_bytes, 1, false); + _az_PRECONDITION_NOT_NULL(out_written); + + int32_t source_length = az_span_size(source_bytes); + uint8_t* source_ptr = az_span_ptr(source_bytes); + + int32_t destination_length = az_span_size(destination_base64_text); + uint8_t* destination_ptr = az_span_ptr(destination_base64_text); + + if (destination_length < az_base64_get_max_encoded_size(source_length)) + { + return AZ_ERROR_NOT_ENOUGH_SPACE; + } + + int32_t source_index = 0; + int32_t result = 0; + + while (source_index < source_length - 2) + { + result = _az_base64_encode(source_ptr + source_index); + _az_base64_write_int_as_four_bytes(destination_ptr, result); + destination_ptr += 4; + source_index += 3; + } + + if (source_index == source_length - 1) + { + result = _az_base64_encode_and_pad_two(source_ptr + source_index); + _az_base64_write_int_as_four_bytes(destination_ptr, result); + destination_ptr += 4; + source_index += 1; + } + else if (source_index == source_length - 2) + { + result = _az_base64_encode_and_pad_one(source_ptr + source_index); + _az_base64_write_int_as_four_bytes(destination_ptr, result); + destination_ptr += 4; + source_index += 2; + } + + *out_written = (int32_t)(destination_ptr - az_span_ptr(destination_base64_text)); + return AZ_OK; +} + +AZ_NODISCARD int32_t az_base64_get_max_encoded_size(int32_t source_bytes_size) +{ + _az_PRECONDITION_RANGE(0, source_bytes_size, _az_MAX_SAFE_ENCODED_LENGTH); + return (((source_bytes_size + 2) / 3) * 4); +} + +static AZ_NODISCARD int32_t _az_base64_decode(uint8_t* encoded_bytes) +{ + int32_t i0 = *encoded_bytes; + int32_t i1 = *(encoded_bytes + 1); + int32_t i2 = *(encoded_bytes + 2); + int32_t i3 = *(encoded_bytes + 3); + + i0 = _az_base64_decode_array[i0]; + i1 = _az_base64_decode_array[i1]; + i2 = _az_base64_decode_array[i2]; + i3 = _az_base64_decode_array[i3]; + + i0 <<= 18; + i1 <<= 12; + i2 <<= 6; + + i0 |= i3; + i1 |= i2; + + i0 |= i1; + return i0; +} + +static void _az_base64_write_three_low_order_bytes(uint8_t* destination, int32_t value) +{ + *destination = (uint8_t)(value >> 16); + *(destination + 1) = (uint8_t)(value >> 8); + *(destination + 2) = (uint8_t)(value); +} + +AZ_NODISCARD az_result +az_base64_decode(az_span destination_bytes, az_span source_base64_text, int32_t* out_written) +{ + _az_PRECONDITION_VALID_SPAN(destination_bytes, 1, false); + _az_PRECONDITION_VALID_SPAN(source_base64_text, 4, false); + _az_PRECONDITION_NOT_NULL(out_written); + + int32_t source_length = az_span_size(source_base64_text); + uint8_t* source_ptr = az_span_ptr(source_base64_text); + + int32_t destination_length = az_span_size(destination_bytes); + uint8_t* destination_ptr = az_span_ptr(destination_bytes); + + // The input must be non-empty and a multiple of 4 to be valid. + if (source_length == 0 || source_length % 4 != 0) + { + return AZ_ERROR_UNEXPECTED_END; + } + + if (destination_length < az_base64_get_max_decoded_size(source_length) - 2) + { + return AZ_ERROR_NOT_ENOUGH_SPACE; + } + + int32_t source_index = 0; + int32_t destination_index = 0; + + while (source_index < source_length - 4) + { + int32_t result = _az_base64_decode(source_ptr + source_index); + if (result < 0) + { + return AZ_ERROR_UNEXPECTED_CHAR; + } + _az_base64_write_three_low_order_bytes(destination_ptr, result); + destination_ptr += 3; + destination_index += 3; + source_index += 4; + } + + // We are guaranteed to have an input with at least 4 bytes at this point, with a size that is a + // multiple of 4. + int32_t i0 = *(source_ptr + source_length - 4); + int32_t i1 = *(source_ptr + source_length - 3); + int32_t i2 = *(source_ptr + source_length - 2); + int32_t i3 = *(source_ptr + source_length - 1); + + i0 = _az_base64_decode_array[i0]; + i1 = _az_base64_decode_array[i1]; + + i0 <<= 18; + i1 <<= 12; + + i0 |= i1; + + if (i3 != _az_ENCODING_PAD) + { + i2 = _az_base64_decode_array[i2]; + i3 = _az_base64_decode_array[i3]; + + i2 <<= 6; + + i0 |= i3; + i0 |= i2; + + if (i0 < 0) + { + return AZ_ERROR_UNEXPECTED_CHAR; + } + if (destination_index > destination_length - 3) + { + return AZ_ERROR_NOT_ENOUGH_SPACE; + } + _az_base64_write_three_low_order_bytes(destination_ptr, i0); + destination_ptr += 3; + } + else if (i2 != _az_ENCODING_PAD) + { + i2 = _az_base64_decode_array[i2]; + + i2 <<= 6; + + i0 |= i2; + + if (i0 < 0) + { + return AZ_ERROR_UNEXPECTED_CHAR; + } + if (destination_index > destination_length - 2) + { + return AZ_ERROR_NOT_ENOUGH_SPACE; + } + *(destination_ptr + 1) = (uint8_t)(i0 >> 8); + *destination_ptr = (uint8_t)(i0 >> 16); + destination_ptr += 2; + } + else + { + if (i0 < 0) + { + return AZ_ERROR_UNEXPECTED_CHAR; + } + if (destination_index > destination_length - 1) + { + return AZ_ERROR_NOT_ENOUGH_SPACE; + } + *destination_ptr = (uint8_t)(i0 >> 16); + destination_ptr += 1; + } + + *out_written = (int32_t)(destination_ptr - az_span_ptr(destination_bytes)); + return AZ_OK; +} + +AZ_NODISCARD int32_t az_base64_get_max_decoded_size(int32_t source_base64_text_size) +{ + _az_PRECONDITION(source_base64_text_size >= 0); + return (source_base64_text_size / 4) * 3; +} diff --git a/sdk/src/azure/core/az_json_reader.c b/sdk/src/azure/core/az_json_reader.c index 084abb4884..c4c30a62c7 100644 --- a/sdk/src/azure/core/az_json_reader.c +++ b/sdk/src/azure/core/az_json_reader.c @@ -33,6 +33,7 @@ AZ_NODISCARD az_result az_json_reader_init( .end_buffer_offset = -1, }, }, + .current_depth = 0, ._internal = { .json_buffer = json_buffer, .json_buffers = &AZ_SPAN_EMPTY, @@ -57,7 +58,8 @@ AZ_NODISCARD az_result az_json_reader_chunked_init( _az_PRECONDITION(number_of_buffers >= 1); _az_PRECONDITION(az_span_size(json_buffers[0]) >= 1); - *out_json_reader = (az_json_reader){ + *out_json_reader = (az_json_reader) + { .token = (az_json_token){ .kind = AZ_JSON_TOKEN_NONE, .slice = AZ_SPAN_EMPTY, @@ -72,6 +74,7 @@ AZ_NODISCARD az_result az_json_reader_chunked_init( .end_buffer_offset = -1, }, }, + .current_depth = 0, ._internal = { .json_buffer = json_buffers[0], .json_buffers = json_buffers, @@ -104,6 +107,15 @@ static void _az_json_reader_update_state( { ref_json_reader->token.kind = token_kind; ref_json_reader->token.size = consumed; + ref_json_reader->current_depth = ref_json_reader->_internal.bit_stack._internal.current_depth; + + // The depth of the start of the container will be one less than the bit stack managing the state. + // That is because we push on the stack when we see a start of the container (above in the call + // stack), but its actual depth and "indentation" level is one lower. + if (token_kind == AZ_JSON_TOKEN_BEGIN_ARRAY || token_kind == AZ_JSON_TOKEN_BEGIN_OBJECT) + { + ref_json_reader->current_depth--; + } ref_json_reader->_internal.bytes_consumed += current_segment_consumed; ref_json_reader->_internal.total_bytes_consumed += consumed; diff --git a/sdk/tests/core/CMakeLists.txt b/sdk/tests/core/CMakeLists.txt index 75f181771d..9ff332f129 100644 --- a/sdk/tests/core/CMakeLists.txt +++ b/sdk/tests/core/CMakeLists.txt @@ -23,6 +23,7 @@ endif() add_cmocka_test(az_core_test SOURCES main.c + test_az_base64.c test_az_context.c test_az_http.c test_az_json.c diff --git a/sdk/tests/core/az_test_definitions.h b/sdk/tests/core/az_test_definitions.h index 27bb9d6d11..82b25ac347 100644 --- a/sdk/tests/core/az_test_definitions.h +++ b/sdk/tests/core/az_test_definitions.h @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // SPDX-License-Identifier: MIT +int test_az_base64(); int test_az_context(); int test_az_http(); int test_az_json(); diff --git a/sdk/tests/core/main.c b/sdk/tests/core/main.c index 128e45ee4a..b588ab635b 100644 --- a/sdk/tests/core/main.c +++ b/sdk/tests/core/main.c @@ -19,6 +19,7 @@ int main() // every test function returns the number of tests failed, 0 means success (there shouldn't be // negative numbers + result += test_az_base64(); result += test_az_context(); result += test_az_http(); result += test_az_json(); diff --git a/sdk/tests/core/test_az_base64.c b/sdk/tests/core/test_az_base64.c new file mode 100644 index 0000000000..d761eb2a54 --- /dev/null +++ b/sdk/tests/core/test_az_base64.c @@ -0,0 +1,361 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// SPDX-License-Identifier: MIT + +#include "az_test_definitions.h" +#include + +#include +#include +#include + +#include + +#include + +static void az_base64_max_encode_test(void** state) +{ + (void)state; + assert_int_equal(az_base64_get_max_encoded_size(1), 4); + assert_int_equal(az_base64_get_max_encoded_size(2), 4); + assert_int_equal(az_base64_get_max_encoded_size(3), 4); + assert_int_equal(az_base64_get_max_encoded_size(4), 8); + assert_int_equal(az_base64_get_max_encoded_size(5), 8); + assert_int_equal(az_base64_get_max_encoded_size(30), 40); + assert_int_equal(az_base64_get_max_encoded_size(1610612729), 2147483640); + assert_int_equal(az_base64_get_max_encoded_size(1610612730), 2147483640); + assert_int_equal(az_base64_get_max_encoded_size(1610612731), 2147483644); + assert_int_equal(az_base64_get_max_encoded_size(1610612732), 2147483644); + assert_int_equal(az_base64_get_max_encoded_size(1610612733), 2147483644); +} + +static void az_base64_max_decode_test(void** state) +{ + (void)state; + assert_int_equal(az_base64_get_max_decoded_size(1), 0); + assert_int_equal(az_base64_get_max_decoded_size(2), 0); + assert_int_equal(az_base64_get_max_decoded_size(3), 0); + assert_int_equal(az_base64_get_max_decoded_size(4), 3); + assert_int_equal(az_base64_get_max_decoded_size(5), 3); + assert_int_equal(az_base64_get_max_decoded_size(30), 21); + assert_int_equal(az_base64_get_max_decoded_size(40), 30); + assert_int_equal(az_base64_get_max_decoded_size(1610612733), 1207959549); + assert_int_equal(az_base64_get_max_decoded_size(INT32_MAX - 4), 1610612730); + assert_int_equal(az_base64_get_max_decoded_size(INT32_MAX - 3), 1610612733); + assert_int_equal(az_base64_get_max_decoded_size(INT32_MAX - 2), 1610612733); + assert_int_equal(az_base64_get_max_decoded_size(INT32_MAX - 1), 1610612733); + assert_int_equal(az_base64_get_max_decoded_size(INT32_MAX), 1610612733); +} + +static void _az_base64_encode_test_helper( + int32_t input_length, + const char* expected, + int32_t expected_length) +{ + uint8_t input_buffer[7]; + for (int i = 0; i < input_length; i++) + { + input_buffer[i] = (uint8_t)(i + 1); + } + + az_span source = AZ_SPAN_FROM_BUFFER(input_buffer); + + uint8_t destination_buffer[12]; + az_span destination = AZ_SPAN_FROM_BUFFER(destination_buffer); + + int32_t bytes_written = 0; + assert_true(az_result_succeeded( + az_base64_encode(destination, az_span_slice(source, 0, input_length), &bytes_written))); + assert_int_equal(bytes_written, expected_length); + + char actual[13]; + az_span_to_str(actual, 13, az_span_slice(destination, 0, bytes_written)); + assert_string_equal(actual, expected); +} + +static void az_base64_encode_test(void** state) +{ + (void)state; + + uint8_t input_buffer[1]; + input_buffer[0] = 0; + az_span source = AZ_SPAN_FROM_BUFFER(input_buffer); + + uint8_t destination_buffer[4]; + az_span destination = AZ_SPAN_FROM_BUFFER(destination_buffer); + + int32_t bytes_written = 0; + assert_true(az_result_succeeded(az_base64_encode(destination, source, &bytes_written))); + assert_int_equal(bytes_written, 4); + + char actual[5]; + az_span_to_str(actual, 5, destination); + assert_string_equal(actual, "AA=="); + + _az_base64_encode_test_helper(1, "AQ==", 4); + _az_base64_encode_test_helper(2, "AQI=", 4); + _az_base64_encode_test_helper(3, "AQID", 4); + _az_base64_encode_test_helper(4, "AQIDBA==", 8); + _az_base64_encode_test_helper(5, "AQIDBAU=", 8); + _az_base64_encode_test_helper(6, "AQIDBAUG", 8); + _az_base64_encode_test_helper(7, "AQIDBAUGBw==", 12); +} + +static void az_base64_encode_destination_small_test(void** state) +{ + (void)state; + + uint8_t input_buffer[10] = { 23, 51, 61, 250, 131, 184, 127, 228, 250, 66 }; + az_span source = AZ_SPAN_FROM_BUFFER(input_buffer); + + uint8_t destination_buffer[16]; + az_span destination = AZ_SPAN_FROM_BUFFER(destination_buffer); + + int32_t bytes_written = 0; + assert_int_equal( + az_base64_encode(az_span_slice(destination, 0, 4), source, &bytes_written), + AZ_ERROR_NOT_ENOUGH_SPACE); + assert_int_equal(bytes_written, 0); + + assert_int_equal( + az_base64_encode(az_span_slice(destination, 0, 14), source, &bytes_written), + AZ_ERROR_NOT_ENOUGH_SPACE); + assert_int_equal(bytes_written, 0); + + assert_int_equal( + az_base64_encode(az_span_slice(destination, 0, 15), source, &bytes_written), + AZ_ERROR_NOT_ENOUGH_SPACE); + assert_int_equal(bytes_written, 0); + + assert_true(az_result_succeeded(az_base64_encode(destination, source, &bytes_written))); + assert_int_equal(bytes_written, 16); + + char actual[17]; + az_span_to_str(actual, 17, destination); + assert_string_equal(actual, "FzM9+oO4f+T6Qg=="); +} + +static void _az_base64_decode_test_helper(az_span source, az_span expected) +{ + uint8_t destination_buffer[7]; + az_span destination = AZ_SPAN_FROM_BUFFER(destination_buffer); + + int32_t bytes_written = 0; + assert_true(az_result_succeeded(az_base64_decode(destination, source, &bytes_written))); + assert_int_equal(bytes_written, az_span_size(expected)); + + assert_true(az_span_is_content_equal(az_span_slice(destination, 0, bytes_written), expected)); +} + +static void az_base64_decode_test(void** state) +{ + (void)state; + + az_span source = AZ_SPAN_FROM_STR("AA=="); + + uint8_t destination_buffer[1]; + az_span destination = AZ_SPAN_FROM_BUFFER(destination_buffer); + + int32_t bytes_written = 0; + assert_true(az_result_succeeded(az_base64_decode(destination, source, &bytes_written))); + assert_int_equal(bytes_written, 1); + + uint8_t input_buffer[1] = { 0 }; + az_span expected = AZ_SPAN_FROM_BUFFER(input_buffer); + assert_true(az_span_is_content_equal(destination, expected)); + + uint8_t expected_buffer1[1] = { 1 }; + _az_base64_decode_test_helper(AZ_SPAN_FROM_STR("AQ=="), AZ_SPAN_FROM_BUFFER(expected_buffer1)); + uint8_t expected_buffer2[2] = { 1, 2 }; + _az_base64_decode_test_helper(AZ_SPAN_FROM_STR("AQI="), AZ_SPAN_FROM_BUFFER(expected_buffer2)); + uint8_t expected_buffer3[3] = { 1, 2, 3 }; + _az_base64_decode_test_helper(AZ_SPAN_FROM_STR("AQID"), AZ_SPAN_FROM_BUFFER(expected_buffer3)); + uint8_t expected_buffer4[4] = { 1, 2, 3, 4 }; + _az_base64_decode_test_helper( + AZ_SPAN_FROM_STR("AQIDBA=="), AZ_SPAN_FROM_BUFFER(expected_buffer4)); + uint8_t expected_buffer5[5] = { 1, 2, 3, 4, 5 }; + _az_base64_decode_test_helper( + AZ_SPAN_FROM_STR("AQIDBAU="), AZ_SPAN_FROM_BUFFER(expected_buffer5)); + uint8_t expected_buffer6[6] = { 1, 2, 3, 4, 5, 6 }; + _az_base64_decode_test_helper( + AZ_SPAN_FROM_STR("AQIDBAUG"), AZ_SPAN_FROM_BUFFER(expected_buffer6)); + uint8_t expected_buffer7[7] = { 1, 2, 3, 4, 5, 6, 7 }; + _az_base64_decode_test_helper( + AZ_SPAN_FROM_STR("AQIDBAUGBw=="), AZ_SPAN_FROM_BUFFER(expected_buffer7)); +} + +static void az_base64_decode_destination_small_test(void** state) +{ + (void)state; + + uint8_t expected_buffer[10] = { 23, 51, 61, 250, 131, 184, 127, 228, 250, 66 }; + az_span source = AZ_SPAN_FROM_STR("FzM9+oO4f+T6Qg=="); + + uint8_t destination_buffer[10]; + az_span destination = AZ_SPAN_FROM_BUFFER(destination_buffer); + + int32_t bytes_written = 0; + assert_int_equal( + az_base64_decode(az_span_slice(destination, 0, 4), source, &bytes_written), + AZ_ERROR_NOT_ENOUGH_SPACE); + assert_int_equal(bytes_written, 0); + + assert_int_equal( + az_base64_decode(az_span_slice(destination, 0, 8), source, &bytes_written), + AZ_ERROR_NOT_ENOUGH_SPACE); + assert_int_equal(bytes_written, 0); + + assert_int_equal( + az_base64_decode(az_span_slice(destination, 0, 9), source, &bytes_written), + AZ_ERROR_NOT_ENOUGH_SPACE); + assert_int_equal(bytes_written, 0); + + assert_true(az_result_succeeded(az_base64_decode(destination, source, &bytes_written))); + assert_int_equal(bytes_written, 10); + + assert_true(az_span_is_content_equal(destination, AZ_SPAN_FROM_BUFFER(expected_buffer))); + + source = AZ_SPAN_FROM_STR("AQI="); + assert_int_equal( + az_base64_decode(az_span_slice(destination, 0, 1), source, &bytes_written), + AZ_ERROR_NOT_ENOUGH_SPACE); + assert_int_equal(bytes_written, 10); + + source = AZ_SPAN_FROM_STR("AQID"); + assert_int_equal( + az_base64_decode(az_span_slice(destination, 0, 2), source, &bytes_written), + AZ_ERROR_NOT_ENOUGH_SPACE); + assert_int_equal(bytes_written, 10); + + source = AZ_SPAN_FROM_STR("AQIDBA=="); + assert_int_equal( + az_base64_decode(az_span_slice(destination, 0, 3), source, &bytes_written), + AZ_ERROR_NOT_ENOUGH_SPACE); + assert_int_equal(bytes_written, 10); +} + +static void az_base64_decode_source_small_test(void** state) +{ + (void)state; + + uint8_t destination_buffer[10]; + az_span destination = AZ_SPAN_FROM_BUFFER(destination_buffer); + + int32_t bytes_written = 0; + + assert_int_equal( + az_base64_decode(destination, AZ_SPAN_FROM_STR("AQIDB"), &bytes_written), + AZ_ERROR_UNEXPECTED_END); + assert_int_equal(bytes_written, 0); + + assert_int_equal( + az_base64_decode(destination, AZ_SPAN_FROM_STR("AQIDBA"), &bytes_written), + AZ_ERROR_UNEXPECTED_END); + assert_int_equal(bytes_written, 0); + + assert_int_equal( + az_base64_decode(destination, AZ_SPAN_FROM_STR("AQIDBA="), &bytes_written), + AZ_ERROR_UNEXPECTED_END); + assert_int_equal(bytes_written, 0); + + assert_int_equal( + az_base64_decode(destination, AZ_SPAN_FROM_STR("AQIDBAU"), &bytes_written), + AZ_ERROR_UNEXPECTED_END); + assert_int_equal(bytes_written, 0); +} + +static void az_base64_decode_invalid_test(void** state) +{ + (void)state; + + // Invalid Bytes: + // 0-42 + // 44-46 + // 58-64 + // 91-96 + // 123-255 + + uint8_t destination_buffer[20]; + az_span destination = AZ_SPAN_FROM_BUFFER(destination_buffer); + + int32_t bytes_written = 0; + + assert_int_equal( + az_base64_decode(destination, AZ_SPAN_FROM_STR("A---"), &bytes_written), + AZ_ERROR_UNEXPECTED_CHAR); + assert_int_equal(bytes_written, 0); + + assert_int_equal( + az_base64_decode(destination, AZ_SPAN_FROM_STR("A-=="), &bytes_written), + AZ_ERROR_UNEXPECTED_CHAR); + assert_int_equal(bytes_written, 0); + + assert_int_equal( + az_base64_decode(destination, AZ_SPAN_FROM_STR("A!B="), &bytes_written), + AZ_ERROR_UNEXPECTED_CHAR); + assert_int_equal(bytes_written, 0); + + assert_int_equal( + az_base64_decode(destination, AZ_SPAN_FROM_STR("A:BC"), &bytes_written), + AZ_ERROR_UNEXPECTED_CHAR); + assert_int_equal(bytes_written, 0); + + assert_int_equal( + az_base64_decode(destination, AZ_SPAN_FROM_STR("AQ-_"), &bytes_written), + AZ_ERROR_UNEXPECTED_CHAR); + assert_int_equal(bytes_written, 0); + + assert_int_equal( + az_base64_decode(destination, AZ_SPAN_FROM_STR("AQ=_"), &bytes_written), + AZ_ERROR_UNEXPECTED_CHAR); + assert_int_equal(bytes_written, 0); + + assert_int_equal( + az_base64_decode(destination, AZ_SPAN_FROM_STR("AQI_"), &bytes_written), + AZ_ERROR_UNEXPECTED_CHAR); + assert_int_equal(bytes_written, 0); + + assert_int_equal( + az_base64_decode(destination, AZ_SPAN_FROM_STR("AQIDB..."), &bytes_written), + AZ_ERROR_UNEXPECTED_CHAR); + assert_int_equal(bytes_written, 0); + + assert_int_equal( + az_base64_decode(destination, AZ_SPAN_FROM_STR("AQIDBA.."), &bytes_written), + AZ_ERROR_UNEXPECTED_CHAR); + assert_int_equal(bytes_written, 0); + + assert_int_equal( + az_base64_decode(destination, AZ_SPAN_FROM_STR("AQIDBA=|"), &bytes_written), + AZ_ERROR_UNEXPECTED_CHAR); + assert_int_equal(bytes_written, 0); + + assert_int_equal( + az_base64_decode(destination, AZ_SPAN_FROM_STR("AQIDBAU?"), &bytes_written), + AZ_ERROR_UNEXPECTED_CHAR); + assert_int_equal(bytes_written, 0); + + assert_int_equal( + az_base64_decode(destination, AZ_SPAN_FROM_STR("FzM9+oO4f+T6Qg==}}}}"), &bytes_written), + AZ_ERROR_UNEXPECTED_CHAR); + assert_int_equal(bytes_written, 0); + + assert_int_equal( + az_base64_decode(destination, AZ_SPAN_FROM_STR("\\zM9+oO4f+T6Qg=="), &bytes_written), + AZ_ERROR_UNEXPECTED_CHAR); + assert_int_equal(bytes_written, 0); +} + +int test_az_base64() +{ + const struct CMUnitTest tests[] = { + cmocka_unit_test(az_base64_max_encode_test), + cmocka_unit_test(az_base64_max_decode_test), + cmocka_unit_test(az_base64_encode_test), + cmocka_unit_test(az_base64_encode_destination_small_test), + cmocka_unit_test(az_base64_decode_test), + cmocka_unit_test(az_base64_decode_destination_small_test), + cmocka_unit_test(az_base64_decode_source_small_test), + cmocka_unit_test(az_base64_decode_invalid_test), + }; + return cmocka_run_group_tests_name("az_core_base64", tests, NULL, NULL); +} diff --git a/sdk/tests/core/test_az_json.c b/sdk/tests/core/test_az_json.c index f682c7f7f4..0e1e0b7a73 100644 --- a/sdk/tests/core/test_az_json.c +++ b/sdk/tests/core/test_az_json.c @@ -47,9 +47,13 @@ static void test_json_reader_init(void** state) az_json_reader reader = { 0 }; + assert_int_equal(reader.current_depth, 0); + assert_int_equal(az_json_reader_init(&reader, AZ_SPAN_FROM_STR("{}"), NULL), AZ_OK); assert_int_equal(az_json_reader_init(&reader, AZ_SPAN_FROM_STR("{}"), &options), AZ_OK); + assert_int_equal(reader.current_depth, 0); + // Verify that initialization doesn't process any JSON text, even if it is invalid or incomplete. assert_int_equal(az_json_reader_init(&reader, AZ_SPAN_FROM_STR(" "), NULL), AZ_OK); assert_int_equal(az_json_reader_init(&reader, AZ_SPAN_FROM_STR(" "), &options), AZ_OK); @@ -59,6 +63,119 @@ static void test_json_reader_init(void** state) assert_int_equal(az_json_reader_init(&reader, AZ_SPAN_FROM_STR("\""), &options), AZ_OK); TEST_JSON_TOKEN_HELPER(reader.token, AZ_JSON_TOKEN_NONE, AZ_SPAN_EMPTY); + + assert_int_equal(reader.current_depth, 0); +} + +static void test_json_reader_current_depth_array(void** state) +{ + (void)state; + + az_json_reader reader = { 0 }; + + assert_int_equal(az_json_reader_init(&reader, AZ_SPAN_FROM_STR("[ ]"), NULL), AZ_OK); + TEST_EXPECT_SUCCESS(az_json_reader_next_token(&reader)); + assert_int_equal(reader.current_depth, 0); + TEST_EXPECT_SUCCESS(az_json_reader_next_token(&reader)); + assert_int_equal(reader.current_depth, 0); + + assert_int_equal(az_json_reader_init(&reader, AZ_SPAN_FROM_STR("[ [ 1, 2, 3] ]"), NULL), AZ_OK); + assert_int_equal(reader._internal.bit_stack._internal.current_depth, 0); + assert_int_equal(reader.current_depth, 0); + + TEST_EXPECT_SUCCESS(az_json_reader_next_token(&reader)); + assert_int_equal(reader._internal.bit_stack._internal.current_depth, 1); + assert_int_equal(reader.current_depth, 0); + + TEST_EXPECT_SUCCESS(az_json_reader_next_token(&reader)); + assert_int_equal(reader._internal.bit_stack._internal.current_depth, 2); + assert_int_equal(reader.current_depth, 1); + + TEST_EXPECT_SUCCESS(az_json_reader_next_token(&reader)); + assert_int_equal(reader._internal.bit_stack._internal.current_depth, 2); + assert_int_equal(reader.current_depth, 2); + + TEST_EXPECT_SUCCESS(az_json_reader_next_token(&reader)); + assert_int_equal(reader._internal.bit_stack._internal.current_depth, 2); + assert_int_equal(reader.current_depth, 2); + + TEST_EXPECT_SUCCESS(az_json_reader_next_token(&reader)); + assert_int_equal(reader._internal.bit_stack._internal.current_depth, 2); + assert_int_equal(reader.current_depth, 2); + + TEST_EXPECT_SUCCESS(az_json_reader_next_token(&reader)); + assert_int_equal(reader._internal.bit_stack._internal.current_depth, 1); + assert_int_equal(reader.current_depth, 1); + + TEST_EXPECT_SUCCESS(az_json_reader_next_token(&reader)); + assert_int_equal(reader._internal.bit_stack._internal.current_depth, 0); + assert_int_equal(reader.current_depth, 0); + + assert_int_equal(az_json_reader_next_token(&reader), AZ_ERROR_JSON_READER_DONE); + assert_int_equal(reader._internal.bit_stack._internal.current_depth, 0); + assert_int_equal(reader.current_depth, 0); +} + +static void test_json_reader_current_depth_object(void** state) +{ + (void)state; + + az_json_reader reader = { 0 }; + + assert_int_equal(az_json_reader_init(&reader, AZ_SPAN_FROM_STR("{}"), NULL), AZ_OK); + TEST_EXPECT_SUCCESS(az_json_reader_next_token(&reader)); + assert_int_equal(reader.current_depth, 0); + TEST_EXPECT_SUCCESS(az_json_reader_next_token(&reader)); + assert_int_equal(reader.current_depth, 0); + + assert_int_equal( + az_json_reader_init(&reader, AZ_SPAN_FROM_STR("{\"array\": [1,2,3,{}]}"), NULL), AZ_OK); + assert_int_equal(reader._internal.bit_stack._internal.current_depth, 0); + assert_int_equal(reader.current_depth, 0); + + TEST_EXPECT_SUCCESS(az_json_reader_next_token(&reader)); + assert_int_equal(reader._internal.bit_stack._internal.current_depth, 1); + assert_int_equal(reader.current_depth, 0); + + TEST_EXPECT_SUCCESS(az_json_reader_next_token(&reader)); + assert_int_equal(reader._internal.bit_stack._internal.current_depth, 1); + assert_int_equal(reader.current_depth, 1); + + TEST_EXPECT_SUCCESS(az_json_reader_next_token(&reader)); + assert_int_equal(reader._internal.bit_stack._internal.current_depth, 2); + assert_int_equal(reader.current_depth, 1); + + TEST_EXPECT_SUCCESS(az_json_reader_next_token(&reader)); + assert_int_equal(reader._internal.bit_stack._internal.current_depth, 2); + assert_int_equal(reader.current_depth, 2); + + TEST_EXPECT_SUCCESS(az_json_reader_next_token(&reader)); + assert_int_equal(reader._internal.bit_stack._internal.current_depth, 2); + assert_int_equal(reader.current_depth, 2); + + TEST_EXPECT_SUCCESS(az_json_reader_next_token(&reader)); + assert_int_equal(reader._internal.bit_stack._internal.current_depth, 2); + assert_int_equal(reader.current_depth, 2); + + TEST_EXPECT_SUCCESS(az_json_reader_next_token(&reader)); + assert_int_equal(reader._internal.bit_stack._internal.current_depth, 3); + assert_int_equal(reader.current_depth, 2); + + TEST_EXPECT_SUCCESS(az_json_reader_next_token(&reader)); + assert_int_equal(reader._internal.bit_stack._internal.current_depth, 2); + assert_int_equal(reader.current_depth, 2); + + TEST_EXPECT_SUCCESS(az_json_reader_next_token(&reader)); + assert_int_equal(reader._internal.bit_stack._internal.current_depth, 1); + assert_int_equal(reader.current_depth, 1); + + TEST_EXPECT_SUCCESS(az_json_reader_next_token(&reader)); + assert_int_equal(reader._internal.bit_stack._internal.current_depth, 0); + assert_int_equal(reader.current_depth, 0); + + assert_int_equal(az_json_reader_next_token(&reader), AZ_ERROR_JSON_READER_DONE); + assert_int_equal(reader._internal.bit_stack._internal.current_depth, 0); + assert_int_equal(reader.current_depth, 0); } /** Json writer **/ @@ -1033,48 +1150,56 @@ static void test_json_reader(void** state) az_json_reader reader = { 0 }; TEST_EXPECT_SUCCESS(az_json_reader_init(&reader, AZ_SPAN_FROM_STR(" "), NULL)); assert_true(az_json_reader_next_token(&reader) == AZ_ERROR_UNEXPECTED_END); + assert_int_equal(reader.current_depth, 0); TEST_JSON_TOKEN_HELPER(reader.token, AZ_JSON_TOKEN_NONE, AZ_SPAN_EMPTY); } { az_json_reader reader = { 0 }; TEST_EXPECT_SUCCESS(az_json_reader_init(&reader, AZ_SPAN_FROM_STR(" null "), NULL)); TEST_EXPECT_SUCCESS(az_json_reader_next_token(&reader)); + assert_int_equal(reader.current_depth, 0); TEST_JSON_TOKEN_HELPER(reader.token, AZ_JSON_TOKEN_NULL, AZ_SPAN_FROM_STR("null")); } { az_json_reader reader = { 0 }; TEST_EXPECT_SUCCESS(az_json_reader_init(&reader, AZ_SPAN_FROM_STR(" nul"), NULL)); assert_true(az_json_reader_next_token(&reader) == AZ_ERROR_UNEXPECTED_END); + assert_int_equal(reader.current_depth, 0); TEST_JSON_TOKEN_HELPER(reader.token, AZ_JSON_TOKEN_NONE, AZ_SPAN_EMPTY); } { az_json_reader reader = { 0 }; TEST_EXPECT_SUCCESS(az_json_reader_init(&reader, AZ_SPAN_FROM_STR(" false"), NULL)); TEST_EXPECT_SUCCESS(az_json_reader_next_token(&reader)); + assert_int_equal(reader.current_depth, 0); TEST_JSON_TOKEN_HELPER(reader.token, AZ_JSON_TOKEN_FALSE, AZ_SPAN_FROM_STR("false")); } { az_json_reader reader = { 0 }; TEST_EXPECT_SUCCESS(az_json_reader_init(&reader, AZ_SPAN_FROM_STR(" falsx "), NULL)); assert_true(az_json_reader_next_token(&reader) == AZ_ERROR_UNEXPECTED_CHAR); + assert_int_equal(reader.current_depth, 0); TEST_JSON_TOKEN_HELPER(reader.token, AZ_JSON_TOKEN_NONE, AZ_SPAN_EMPTY); } { az_json_reader reader = { 0 }; TEST_EXPECT_SUCCESS(az_json_reader_init(&reader, AZ_SPAN_FROM_STR("true "), NULL)); TEST_EXPECT_SUCCESS(az_json_reader_next_token(&reader)); + assert_int_equal(reader.current_depth, 0); TEST_JSON_TOKEN_HELPER(reader.token, AZ_JSON_TOKEN_TRUE, AZ_SPAN_FROM_STR("true")); } { az_json_reader reader = { 0 }; TEST_EXPECT_SUCCESS(az_json_reader_init(&reader, AZ_SPAN_FROM_STR(" truem"), NULL)); TEST_EXPECT_SUCCESS(az_json_reader_next_token(&reader)); + assert_int_equal(reader.current_depth, 0); TEST_JSON_TOKEN_HELPER(reader.token, AZ_JSON_TOKEN_TRUE, AZ_SPAN_FROM_STR("true")); } { az_json_reader reader = { 0 }; TEST_EXPECT_SUCCESS(az_json_reader_init(&reader, AZ_SPAN_FROM_STR(" 123a"), NULL)); assert_true(az_json_reader_next_token(&reader) == AZ_ERROR_UNEXPECTED_CHAR); + assert_int_equal(reader.current_depth, 0); TEST_JSON_TOKEN_HELPER(reader.token, AZ_JSON_TOKEN_NONE, AZ_SPAN_EMPTY); } { @@ -1082,6 +1207,7 @@ static void test_json_reader(void** state) az_json_reader reader = { 0 }; TEST_EXPECT_SUCCESS(az_json_reader_init(&reader, s, NULL)); TEST_EXPECT_SUCCESS(az_json_reader_next_token(&reader)); + assert_int_equal(reader.current_depth, 0); TEST_JSON_TOKEN_HELPER(reader.token, AZ_JSON_TOKEN_STRING, AZ_SPAN_FROM_STR("tr\\\"ue\\t")); assert_true(az_span_ptr(reader.token.slice) == (az_span_ptr(s) + 2)); } @@ -1090,6 +1216,7 @@ static void test_json_reader(void** state) az_json_reader reader = { 0 }; TEST_EXPECT_SUCCESS(az_json_reader_init(&reader, s, NULL)); TEST_EXPECT_SUCCESS(az_json_reader_next_token(&reader)); + assert_int_equal(reader.current_depth, 0); TEST_JSON_TOKEN_HELPER(reader.token, AZ_JSON_TOKEN_STRING, AZ_SPAN_FROM_STR("\\uFf0F")); assert_true(az_span_ptr(reader.token.slice) == az_span_ptr(s) + 1); } @@ -1097,6 +1224,7 @@ static void test_json_reader(void** state) az_span const s = AZ_SPAN_FROM_STR("\"\\uFf0\""); az_json_reader reader = { 0 }; TEST_EXPECT_SUCCESS(az_json_reader_init(&reader, s, NULL)); + assert_int_equal(reader.current_depth, 0); assert_true(az_json_reader_next_token(&reader) == AZ_ERROR_UNEXPECTED_CHAR); } /* Testing reading number and converting to double */ @@ -1105,6 +1233,7 @@ static void test_json_reader(void** state) az_json_reader reader = { 0 }; TEST_EXPECT_SUCCESS(az_json_reader_init(&reader, AZ_SPAN_FROM_STR(" 23 "), NULL)); TEST_EXPECT_SUCCESS(az_json_reader_next_token(&reader)); + assert_int_equal(reader.current_depth, 0); TEST_JSON_TOKEN_HELPER(reader.token, AZ_JSON_TOKEN_NUMBER, AZ_SPAN_FROM_STR("23")); uint64_t actual_u64 = 0; @@ -1230,14 +1359,19 @@ static void test_json_reader(void** state) az_json_reader reader = { 0 }; TEST_EXPECT_SUCCESS(az_json_reader_init(&reader, AZ_SPAN_FROM_STR(" [ true, 0.25 ]"), NULL)); TEST_EXPECT_SUCCESS(az_json_reader_next_token(&reader)); + assert_int_equal(reader.current_depth, 0); TEST_JSON_TOKEN_HELPER(reader.token, AZ_JSON_TOKEN_BEGIN_ARRAY, AZ_SPAN_FROM_STR("[")); TEST_EXPECT_SUCCESS(az_json_reader_next_token(&reader)); + assert_int_equal(reader.current_depth, 1); TEST_JSON_TOKEN_HELPER(reader.token, AZ_JSON_TOKEN_TRUE, AZ_SPAN_FROM_STR("true")); TEST_EXPECT_SUCCESS(az_json_reader_next_token(&reader)); + assert_int_equal(reader.current_depth, 1); TEST_JSON_TOKEN_HELPER(reader.token, AZ_JSON_TOKEN_NUMBER, AZ_SPAN_FROM_STR("0.25")); TEST_EXPECT_SUCCESS(az_json_reader_next_token(&reader)); + assert_int_equal(reader.current_depth, 0); TEST_JSON_TOKEN_HELPER(reader.token, AZ_JSON_TOKEN_END_ARRAY, AZ_SPAN_FROM_STR("]")); assert_true(az_json_reader_next_token(&reader) == AZ_ERROR_JSON_READER_DONE); + assert_int_equal(reader.current_depth, 0); TEST_JSON_TOKEN_HELPER(reader.token, AZ_JSON_TOKEN_END_ARRAY, AZ_SPAN_FROM_STR("]")); } { @@ -1245,14 +1379,19 @@ static void test_json_reader(void** state) az_json_reader reader = { 0 }; TEST_EXPECT_SUCCESS(az_json_reader_init(&reader, json, NULL)); TEST_EXPECT_SUCCESS(az_json_reader_next_token(&reader)); + assert_int_equal(reader.current_depth, 0); TEST_JSON_TOKEN_HELPER(reader.token, AZ_JSON_TOKEN_BEGIN_OBJECT, AZ_SPAN_FROM_STR("{")); TEST_EXPECT_SUCCESS(az_json_reader_next_token(&reader)); + assert_int_equal(reader.current_depth, 1); TEST_JSON_TOKEN_HELPER(reader.token, AZ_JSON_TOKEN_PROPERTY_NAME, AZ_SPAN_FROM_STR("a")); TEST_EXPECT_SUCCESS(az_json_reader_next_token(&reader)); + assert_int_equal(reader.current_depth, 1); TEST_JSON_TOKEN_HELPER(reader.token, AZ_JSON_TOKEN_STRING, AZ_SPAN_FROM_STR("Hello world!")); TEST_EXPECT_SUCCESS(az_json_reader_next_token(&reader)); + assert_int_equal(reader.current_depth, 0); TEST_JSON_TOKEN_HELPER(reader.token, AZ_JSON_TOKEN_END_OBJECT, AZ_SPAN_FROM_STR("}")); assert_true(az_json_reader_next_token(&reader) == AZ_ERROR_JSON_READER_DONE); + assert_int_equal(reader.current_depth, 0); TEST_JSON_TOKEN_HELPER(reader.token, AZ_JSON_TOKEN_END_OBJECT, AZ_SPAN_FROM_STR("}")); } { @@ -1670,6 +1809,7 @@ static void test_json_skip_children(void** state) TEST_EXPECT_SUCCESS(az_json_reader_skip_children(&reader)); assert_int_equal(reader.token.kind, AZ_JSON_TOKEN_END_OBJECT); assert_int_equal(reader._internal.bit_stack._internal.current_depth, 0); + assert_int_equal(reader.current_depth, 0); TEST_EXPECT_SUCCESS(az_json_reader_init(&reader, AZ_SPAN_FROM_STR("{\"foo\":{}}"), NULL)); TEST_EXPECT_SUCCESS(az_json_reader_next_token(&reader)); @@ -1677,6 +1817,7 @@ static void test_json_skip_children(void** state) TEST_EXPECT_SUCCESS(az_json_reader_skip_children(&reader)); assert_int_equal(reader.token.kind, AZ_JSON_TOKEN_END_OBJECT); assert_int_equal(reader._internal.bit_stack._internal.current_depth, 0); + assert_int_equal(reader.current_depth, 0); TEST_EXPECT_SUCCESS(az_json_reader_init(&reader, AZ_SPAN_FROM_STR("{\"foo\":{}}"), NULL)); TEST_EXPECT_SUCCESS(az_json_reader_next_token(&reader)); @@ -1685,6 +1826,7 @@ static void test_json_skip_children(void** state) TEST_EXPECT_SUCCESS(az_json_reader_skip_children(&reader)); assert_int_equal(reader.token.kind, AZ_JSON_TOKEN_END_OBJECT); assert_int_equal(reader._internal.bit_stack._internal.current_depth, 1); + assert_int_equal(reader.current_depth, 1); TEST_EXPECT_SUCCESS(az_json_reader_init(&reader, AZ_SPAN_FROM_STR("{\"foo\":{}}"), NULL)); TEST_EXPECT_SUCCESS(az_json_reader_next_token(&reader)); @@ -1694,6 +1836,7 @@ static void test_json_skip_children(void** state) TEST_EXPECT_SUCCESS(az_json_reader_skip_children(&reader)); assert_int_equal(reader.token.kind, AZ_JSON_TOKEN_END_OBJECT); assert_int_equal(reader._internal.bit_stack._internal.current_depth, 1); + assert_int_equal(reader.current_depth, 1); } /** Json Value **/ @@ -2826,6 +2969,8 @@ int test_az_json() { const struct CMUnitTest tests[] = { cmocka_unit_test(test_json_reader_init), + cmocka_unit_test(test_json_reader_current_depth_array), + cmocka_unit_test(test_json_reader_current_depth_object), cmocka_unit_test(test_json_writer), cmocka_unit_test(test_json_writer_append_nested), cmocka_unit_test(test_json_writer_append_nested_invalid),