diff --git a/.builder/actions/build_samples.py b/.builder/actions/build_samples.py index 337e3053ae..0ab0d83a84 100644 --- a/.builder/actions/build_samples.py +++ b/.builder/actions/build_samples.py @@ -19,23 +19,26 @@ def run(self, env): steps = [] samples = [ - 'samples/commands/commands-sandbox', + 'samples/service_clients/commands/commands-sandbox', 'samples/greengrass/basic_discovery', 'samples/greengrass/ipc', - 'samples/fleet_provisioning/provision-basic', - 'samples/fleet_provisioning/provision-csr', - 'samples/jobs/jobs-sandbox', - 'samples/mqtt5/mqtt5_pubsub', - 'samples/secure_tunneling/secure_tunnel', - 'samples/secure_tunneling/tunnel_notification', - 'samples/shadow/shadow-sandbox', + 'samples/service_clients/fleet_provisioning/provision-basic', + 'samples/service_clients/fleet_provisioning/provision-csr', + 'samples/service_clients/jobs/jobs-sandbox', + 'samples/mqtt/mqtt5_x509', + 'samples/mqtt/mqtt5_aws_websocket', + 'samples/mqtt/mqtt5_custom_auth_signed', + 'samples/mqtt/mqtt5_custom_auth_unsigned', + 'samples/mqtt/mqtt5_pkcs11', + 'samples/others/secure_tunneling/secure_tunnel', + 'samples/others/secure_tunneling/tunnel_notification', + 'samples/service_clients/shadow/shadow-sandbox', ] defender_samples = [] # Linux only builds if sys.platform == "linux" or sys.platform == "linux2": - defender_samples.append('samples/device_defender/basic_report') - defender_samples.append('samples/device_defender/mqtt5_basic_report') + defender_samples.append('samples/others/device_defender/mqtt5_basic_report') servicetests = [ 'servicetests/tests/JobsExecution/', diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4f9765c0ca..2d501a6c34 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -240,7 +240,7 @@ jobs: run: | cd ${{ env.CI_FOLDER }} echo "Starting to run AppVerifier with mqtt5 pub-sub sample" - python ${{ env.CI_UTILS_FOLDER }}/appverifier_launch_sample.py --sample_file ".\aws-iot-device-sdk-cpp-v2\build\samples\mqtt5\mqtt5_pubsub\RelWithDebInfo\mqtt5_pubsub.exe" --sample_secret_endpoint 'ci/endpoint' --sample_secret_certificate 'ci/mqtt5/us/mqtt5_thing/cert' --sample_secret_private_key 'ci/mqtt5/us/mqtt5_thing/key' + python ${{ env.CI_UTILS_FOLDER }}/appverifier_launch_sample.py --sample_file ".\aws-iot-device-sdk-cpp-v2\build\samples\mqtt\mqtt5_x509\RelWithDebInfo\mqtt5_x509.exe" --sample_secret_endpoint 'ci/endpoint' --sample_secret_certificate 'ci/mqtt5/us/mqtt5_thing/cert' --sample_secret_private_key 'ci/mqtt5/us/mqtt5_thing/key' windows-shared-lib: runs-on: windows-latest diff --git a/codebuild/linux-integration-tests.yml b/codebuild/linux-integration-tests.yml index 1ae3cbe224..4bccf6370e 100644 --- a/codebuild/linux-integration-tests.yml +++ b/codebuild/linux-integration-tests.yml @@ -15,7 +15,7 @@ phases: commands: - echo Build started on `date` # Building of dependencies happens in setup-linux - - $CODEBUILD_SRC_DIR/codebuild/samples/setup-linux.sh + - $CODEBUILD_SRC_DIR/codebuild/setup-linux.sh # Run the integration tests - $CODEBUILD_SRC_DIR/codebuild/integration-tests.sh diff --git a/codebuild/samples/linux-smoke-tests.yml b/codebuild/samples/linux-smoke-tests.yml deleted file mode 100644 index 96a8e8cde0..0000000000 --- a/codebuild/samples/linux-smoke-tests.yml +++ /dev/null @@ -1,16 +0,0 @@ -version: 0.2 -phases: - install: - commands: - - wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | apt-key add - - - add-apt-repository ppa:ubuntu-toolchain-r/test - - apt-add-repository "deb http://apt.llvm.org/bionic/ llvm-toolchain-bionic-8 main" - - apt-get update -y - - apt-get install cmake softhsm -y -f - build: - commands: - - echo Build started on `date` - - $CODEBUILD_SRC_DIR/codebuild/samples/setup-linux.sh - post_build: - commands: - - echo Build completed on `date` diff --git a/codebuild/samples/setup-linux.sh b/codebuild/setup-linux.sh similarity index 100% rename from codebuild/samples/setup-linux.sh rename to codebuild/setup-linux.sh diff --git a/devicedefender/script/DDTestRun.py b/devicedefender/script/DDTestRun.py index 862daff48b..8b0c6f7400 100644 --- a/devicedefender/script/DDTestRun.py +++ b/devicedefender/script/DDTestRun.py @@ -43,10 +43,6 @@ def delete_thing_with_certi(thingName, certiId, certiArn): thing_arn = None client_made_thing = False client_made_policy = False -use_mqtt5 = False -if len(sys.argv) > 1: - use_mqtt5 = (sys.argv[1] == "mqtt5") - print("Run Device Defender with Mqtt5 Client") ############################################## # create a test thing @@ -227,22 +223,13 @@ def delete_thing_with_certi(thingName, certiId, certiArn): print("[Device Defender]Info: Running sample (this should take ~60 seconds).") - if use_mqtt5: - # Run the sample: - exe_path = "build/samples/device_defender/mqtt5_basic_report/" - # If running locally, comment out the line above and uncomment the line below: - #exe_path = "samples/device_defender/basic_report/build/" + # Run the sample: + exe_path = "build/samples/others/device_defender/mqtt5_basic_report/" + # If running locally, comment out the line above and uncomment the line below: + #exe_path = "samples/others/device_defender/mqtt5_basic_report/build/" - # Windows has a different build folder structure, but this ONLY runs on Linux currently so we do not need to worry about it - exe_path = os.path.join(exe_path, "mqtt5-basic-report") - else: - # Run the sample: - exe_path = "build/samples/device_defender/basic_report/" - # If running locally, comment out the line above and uncomment the line below: - #exe_path = "samples/device_defender/basic_report/build/" - - # Windows has a different build folder structure, but this ONLY runs on Linux currently so we do not need to worry about it - exe_path = os.path.join(exe_path, "basic-report") + # Windows has a different build folder structure, but this ONLY runs on Linux currently so we do not need to worry about it + exe_path = os.path.join(exe_path, "mqtt5-basic-report") print("[Device Defender]Info: Start to run: " + exe_path) # The Device Defender sample will take ~1 minute to run even if successful @@ -250,7 +237,7 @@ def delete_thing_with_certi(thingName, certiId, certiArn): arguments = [exe_path, "--endpoint", endpoint_response, "--cert", certificate_path, "--key", key_path, "--thing_name", thing_name, "--count", "2"] result = subprocess.run(arguments, timeout=60*2, check=True) - print("[Device Defender]Info: Sample finished running.") + print(f"[Device Defender]Info: Sample finished running, with result {result.returncode}") # There does not appear to be any way to get the metrics from the device - so we'll assume that if it didn't return -1, then it worked @@ -272,7 +259,7 @@ def delete_thing_with_certi(thingName, certiId, certiArn): if client_made_policy: client.delete_policy(policyName=thing_name + "_policy") - print("[Device Defender]Error: Failed to test: Basic Report") + print(f"[Device Defender]Error: Failed to test: Basic Report {e}") exit(-1) print("[Device Defender]Info: Basic Report sample test passed") diff --git a/documents/FAQ.md b/documents/FAQ.md index b814bde387..f553baa55f 100644 --- a/documents/FAQ.md +++ b/documents/FAQ.md @@ -20,9 +20,14 @@ If you are just getting started make sure you [install this sdk](https://github. ### How do I enable logging? ``` c++ -ApiHandle apiHandle; -apiHandle.InitializeLogging(Aws::Crt::LogLevel::Error, stderr); +#include + +Aws::Crt::ApiHandle apiHandle; +apiHandle.InitializeLogging(Aws::Crt::LogLevel::Debug, stderr); ``` + +**LogLevel**: LogLevel has the following options: `Trace`, `Debug`, `Info`, `Warn`, `Error`, `Fatal`, or `None`. Defaults to `Warn`. + You can also enable [CloudWatch logging](https://docs.aws.amazon.com/iot/latest/developerguide/cloud-watch-logs.html) for IoT which will provide you with additional information that is not available on the client side sdk. ### I keep getting AWS_ERROR_MQTT_UNEXPECTED_HANGUP @@ -99,7 +104,6 @@ Here is an example launch.json file to run the pubsub sample "program": "${workspaceFolder}/samples/pub_sub/basic_pub_sub/build/basic-pub-sub", "args": [ "--endpoint", "-ats.iot..amazonaws.com", - "--ca_file", "", "--cert", "", "--key", "", "--client-id", "test-client" @@ -116,7 +120,6 @@ Here is an example launch.json file to run the pubsub sample * Root CA Certificates * Download the root CA certificate file that corresponds to the type of data endpoint and cipher suite you're using (You most likely want Amazon Root CA 1) * Generated and provided by Amazon. You can download it [here](https://www.amazontrust.com/repository/) or download it when getting the other certificates from the AWS console - * When using samples it can look like this: `--ca_file root-CA.crt` * Device certificate * Intermediate device certificate that is used to generate the key below * When using samples it can look like this: `--cert abcde12345-certificate.pem.crt` diff --git a/documents/MIGRATION_GUIDE.md b/documents/MIGRATION_GUIDE.md index 9064cc274a..44147b35d9 100644 --- a/documents/MIGRATION_GUIDE.md +++ b/documents/MIGRATION_GUIDE.md @@ -1369,8 +1369,8 @@ samples. It's always helpful to look at a working example to see how new functionality works, to be able to tweak different options, to compare with existing code. -For that reason, we implemented a [Publish/Subscribe example](https://github.com/aws/aws-iot-device-sdk-cpp-v2/tree/main/samples/mqtt5/mqtt5_pubsub) -([source code](https://github.com/aws/aws-iot-device-sdk-cpp-v2/blob/main/samples/mqtt5/mqtt5_pubsub/main.cpp)) +For that reason, we implemented a [X509 Publish/Subscribe example](https://github.com/aws/aws-iot-device-sdk-cpp-v2/tree/main/samples/mqtt/mqtt5_x509) +([source code](https://github.com/aws/aws-iot-device-sdk-cpp-v2/blob/main/samples/mqtt/mqtt5_x509/main.cpp)) in the v2 SDK similar to a sample provided by the v1 SDK (see a corresponding [readme section](https://github.com/aws/aws-iot-device-sdk-cpp/blob/master/samples/README.md) and [source code](https://github.com/aws/aws-iot-device-sdk-cpp/blob/master/samples/PubSub/PubSub.cpp)). @@ -1438,9 +1438,7 @@ method in PublishPacketBuilder class. **Shared Subscriptions**\ Shared Subscriptions allow multiple clients to share a subscription to a topic and only one client -will receive messages published to that topic using a random distribution.\ -For more information, see a [shared subscription sample](https://github.com/aws/aws-iot-device-sdk-cpp-v2/blob/main/samples/mqtt5/mqtt5_shared_subscription/README.md) -in the v2 SDK. +will receive messages published to that topic using a random distribution. > [!NOTE] > AWS Iot Core supports Shared Subscriptions for both MQTT3 and MQTT5. For more information, see diff --git a/documents/Secure_Tunnel_Userguide.md b/documents/Secure_Tunnel_Userguide.md index eb7fc7f4f4..e71aa23c2d 100644 --- a/documents/Secure_Tunnel_Userguide.md +++ b/documents/Secure_Tunnel_Userguide.md @@ -249,9 +249,9 @@ secureTunnel->SendMessage(message); # Secure Tunnel Best Practices * You MUST NOT perform blocking operations on any callback, or you will cause a deadlock. -* If you do not provide a Client Token during creation of the Secure Tunnel, one will be automatically generated for you to use in reconnections. This token is not saved outside of the current Secure Tunnel Client. If the Client is destroyed, the original access tokens must be rotated to connect to the secure tunnel again. Information on rotating tokens can be found here: https://docs.aws.amazon.com/iot/latest/developerguide/iot-secure-tunneling-troubleshooting.html +* If you do not provide a Client Token during creation of the Secure Tunnel, one will be automatically generated for you to use in reconnection. This token is not saved outside of the current Secure Tunnel Client. If the Client is destroyed, the original access tokens must be rotated to connect to the secure tunnel again. Information on rotating tokens can be found here: https://docs.aws.amazon.com/iot/latest/developerguide/iot-secure-tunneling-troubleshooting.html * Client tokens MUST be unique. You cannot for example, pair a Client Token with an Access Token on one secure tunnel, and then use the same Client Token with a different Access Token on a separate secure tunnel. The Secure Tunnel Service will not allow a Client Token to be paired with more than one Access Token. * A Secure Tunnel Client that has called `Start()` will continue to attempt to connect the Secure Tunnel Service until `Stop()` is called, even if the Secure Tunnel it is trying to connect with has been closed. You MUST call `Stop()` to cease future connection attempts. -* The [onStreamStarted](#onstreamstarted) and [onConnectionStarted](#onconnectionstarted) callbacks should be set to detect and store the service id and/or connection id of streams started by a source device for use with messages. The [Secure Tunnel sample](../samples/secure_tunneling/secure_tunnel/README.md) provides an basic example of how this can be done. +* The [onStreamStarted](#onstreamstarted) and [onConnectionStarted](#onconnectionstarted) callbacks should be set to detect and store the service id and/or connection id of streams started by a source device for use with messages. The [Secure Tunnel sample](../samples/others/secure_tunneling/secure_tunnel/README.md) provides an basic example of how this can be done. * Outgoing messages MUST be assigned a service id and/or a connection id if the established stream contains a service id or a connection id or the message will be rejected. e.g. If a stream is started using service id "ssh" and connection id (1), a message sent in response must also include the service id "ssh" and connection id (1) or it will not find an active stream to send it on. Refer to the [Send Message](#send-message) code block for instruction on adding a service id and/or connection id to your message. diff --git a/samples/CMakeLists.txt b/samples/CMakeLists.txt index a2ce916072..65b033d446 100644 --- a/samples/CMakeLists.txt +++ b/samples/CMakeLists.txt @@ -2,15 +2,18 @@ cmake_minimum_required(VERSION 3.9...3.31) project(aws-iot-device-sdk-cpp-v2-samples) -add_subdirectory(device_defender/basic_report) -add_subdirectory(device_defender/mqtt5_basic_report) -add_subdirectory(fleet_provisioning/provision-basic) -add_subdirectory(fleet_provisioning/provision-csr) +add_subdirectory(others/device_defender/mqtt5_basic_report) +add_subdirectory(service_clients/fleet_provisioning/provision-basic) +add_subdirectory(service_clients/fleet_provisioning/provision-csr) add_subdirectory(greengrass/ipc) add_subdirectory(greengrass/basic_discovery) -add_subdirectory(jobs/jobs-sandbox) -add_subdirectory(mqtt5/mqtt5_pubsub) -add_subdirectory(secure_tunneling/secure_tunnel) -add_subdirectory(secure_tunneling/tunnel_notification) -add_subdirectory(shadow/shadow_sync) -add_subdirectory(commands/commands-sandbox) +add_subdirectory(service_clients/jobs/jobs-sandbox) +add_subdirectory(mqtt/mqtt5_x509) +add_subdirectory(mqtt/mqtt5_aws_websocket) +add_subdirectory(mqtt/mqtt5_custom_auth_signed) +add_subdirectory(mqtt/mqtt5_custom_auth_unsigned) +add_subdirectory(mqtt/mqtt5_pkcs11) +add_subdirectory(others/secure_tunneling/secure_tunnel) +add_subdirectory(others/secure_tunneling/tunnel_notification) +add_subdirectory(service_clients/shadow/shadow-sandbox) +add_subdirectory(service_clients/commands/commands-sandbox) diff --git a/samples/README.md b/samples/README.md index f184961717..05f7da7831 100644 --- a/samples/README.md +++ b/samples/README.md @@ -1,28 +1,58 @@ -# Sample apps for the AWS IoT Device SDK for C++ v2 - -## MQTT5 Samples -#### MQTT5 is the recommended MQTT Client. It has many benefits over MQTT311 outlined in the [MQTT5 User Guide](../documents/MQTT5_Userguide.md) -* [Mqtt5 Pub-Sub](./mqtt5/mqtt5_pubsub/README.md) - + [Direct MQTT with X509-based mutual TLS](./mqtt5/mqtt5_pubsub/README.md#direct-mqtt-with-x509-based-mutual-tls) - + [MQTT over Websockets with Sigv4 authentication](./mqtt5/mqtt5_pubsub/README.md#mqtt-over-websockets-with-sigv4-authentication) - + [Direct MQTT with Custom Authentication](./mqtt5/mqtt5_pubsub/README.md#direct-mqtt-with-custom-authentication) - + [MQTT over Websockets with Cognito](./mqtt5/mqtt5_pubsub/README.md#mqtt-over-websockets-with-cognito) - + [HTTP Proxy](./mqtt5/mqtt5_pubsub/README.md#http-proxy) -## Other Samples -* [Jobs Sandbox](./jobs/jobs-sandbox/README.md) -* [Shadow Sandbox](./shadow/shadow-sandbox/README.md) -* [Basic Fleet Provisioning](./fleet_provisioning/provision-basic/README.md) -* [CSR Fleet Provisioning](./fleet_provisioning/provision-csr/README.md) -* [Commands Sandbox](./commands/commands-sandbox/README.md) -* [Secure Tunnel](./secure_tunneling/secure_tunnel/README.md) -* [Secure Tunnel Notification](./secure_tunneling/tunnel_notification/README.md) -* [Cycle Pub-Sub](./pub_sub/cycle_pub_sub/README.md) -* [Greengrass discovery](./greengrass/basic_discovery/README.md) -* [Greengrass IPC](./greengrass/ipc/README.md) -* [Mqtt5 Device Defender](./device_defender/mqtt5_basic_report/README.md) -* [Mqtt311 Device Defender](./device_defender/basic_report/README.md) - -## Build Instruction +# Sample for the AWS IoT Device SDK v2 for C++ +This directory contains sample applications for [aws-iot-device-sdk-cpp-v2](../README.md). + +### Table of Contents +* [Samples](#samples) + * [MQTT5 Client Samples](#mqtt5-client-samples) + * [Service Client Samples](#service-client-samples) + * [Greengrass Samples](#greengrass-samples) + * [Others](#others) +* [Instructions](#instructions) +* [Sample Help](#sample-help) +* [Enable Logging](#enable-logging) + + +## Samples +### MQTT5 Client Samples +##### MQTT5 is the recommended MQTT Client. Additional information and usage instructions can be found in the [MQTT5 User Guide](../documents/MQTT5_Userguide.md). The samples below will create an MQTT5 client, connect using the selected method, subscribe to a topic, publish to the topic, and then disconnect. +| MQTT5 Client Sample | Description | +|--------|-------------| +| [X509-based mutual TLS](./mqtt/mqtt5_x509/README.md) | Demonstrates connecting to AWS IoT Core using X.509 certificates and private keys. +| [Websockets with Sigv4 authentication](./mqtt/mqtt5_aws_websocket/README.md) | Shows how to authenticate over websockets using AWS Signature Version 4 credentials. | +| [AWS Signed Custom Authorizer Lambda Function](./mqtt/mqtt5_custom_auth_signed/README.md) | Connecting with a signed Lambda-backed custom authorizer. +| [AWS Unsigned Custom Authorizer Lambda Function](./mqtt/mqtt5_custom_auth_unsigned/README.md) | Connecting with an unsigned Lambda-backed custom authorizer. +| [PKCS11](./mqtt/mqtt5_pkcs11/README.md) | Demonstrates connecting using a hardware security module (HSM) or smartcard with PKCS#11. | +| [Other Connection Methods](../documents/MQTT5_Userguide.md#connecting-to-aws-iot-core) | More connection methods are available for review in the MQTT5 Userguide + +### Service Client Samples +##### AWS offers a number of IoT related services using MQTT. The samples below demonstrate how to use the service clients provided by the SDK to interact with those services. +| Service Client Sample | Description | +|--------|-------------| +| [Shadow](./service_clients/shadow/shadow-sandbox/README.md) | Manage and sync device state using the IoT Device Shadow service. | +| [Jobs](./service_clients/jobs/jobs-sandbox/README.md) | Receive and execute remote operations sent from the Jobs service. | +| [Basic Fleet Provisioning](./service_clients/fleet_provisioning/provision-basic/README.md) | Provision a device using the Fleet Provisioning template. | +| [CSR Fleet Provisioning](./service_clients/fleet_provisioning/provision-csr/README.md) | Demonstrates CSR-based device certificate provisioning. | +| [Commands](./service_clients/commands/commands-sandbox/README.md) | Receive and process remote instructions using AWS IoT Device Management commands | + + +### Greengrass Samples +##### Samples that interact with [AWS Greengrass](https://aws.amazon.com/greengrass/). +| Greengrass Sample | Description | +|--------|-------------| +| [Greengrass Discovery](./greengrass/basic_discovery/README.md) | Discover and connect to a local Greengrass core. | +| [Greengrass IPC](./greengrass/ipc/README.md) | Demonstrates Inter-Process Communication (IPC) with Greengrass components. | + +### Others +##### Samples that interact with other AWS IoT Services +| Sample | Description | +|--------|-------------| +| [Device Defender](./others/device_defender/mqtt5_basic_report/README.md) | Monitor the health of your IoT device using AWS IoT Device Defender. | +| [Secure Tunneling](./others/secure_tunneling/secure_tunnel/README.md) | Connect a destination or a source Secure Tunnel Client to an AWS IoT Secure Tunnel endpoint. | +| [Secure Tunneling Notification](./others/secure_tunneling/tunnel_notification/README.md) | Receive a tunnel notification using a Secure Tunnel Client. | + + + +## Instructions First build and install aws-iot-devices-sdk-cpp-v2 with following instructions from [Installation](../README.md#Installation). @@ -53,13 +83,7 @@ cmake -B build -S . -DCMAKE_PREFIX_PATH="" cmake --build build --config "" ``` -This will compile all the samples at once and place the executables under the `build` directory relative to their file path. To view the commands for a given sample, run the compiled program and pass `--help`. For example, with the MQTT5 PubSub sample: - -```sh -./build/mqtt5/mqtt5_pubsub/mqtt5_pubsub --help -``` - -This will compile all of the samples at once. You can then find the samples in the `aws-iot-device-sdk-cpp-v2/samples/build` folder. For example, the MQTT5 PubSub sample will be located at `aws-iot-device-sdk-cpp-v2/samples/build/mqtt5/mqtt5_pubsub`. +This will compile all of the samples at once. You can then find the samples in the `aws-iot-device-sdk-cpp-v2/samples/build` folder. For example, the MQTT5 X509 sample will be located at `aws-iot-device-sdk-cpp-v2/samples/build/mqtt/mqtt5_x509`. For CMake versions that do not support the `-B` command, go to the `aws-iot-device-sdk-cpp-v2/samples` directory and run the following commands: @@ -78,23 +102,30 @@ Note that building all the samples at once is currently only available in the V2 * `-DCMAKE_BUILD_TYPE` and `--config` needs to match the `CMAKE_BUILD_TYPE` when aws-iot-device-sdk-cpp-v2 built. `--config` is only REQUIRED for multi-configuration build tools. -### Sample help +## Sample help -All samples will show their options by passing in `--help`. For example: +All samples will show their options and arguments by passing in `--help`. For example: ```sh -./build/mqtt5/mqtt5_pubsub/mqtt5_pubsub --help +./build/mqtt/mqtt5_x509/mqtt5_x509 --help ``` - -Which will result in output showing all of the options that can be passed in at the command line, along with descriptions of what each does and whether or not they are optional or not. - -### Enable logging in samples - -To enable logging in the samples, you can pass in `--verbosity`, and optionally `--log_file`, to the sample: - -```sh -./build/basic-pub-sub --verbosity "Trace" --log_file "log.txt" +will result in the following print output: ``` +MQTT5 X509 Sample (mTLS) +options: + --help show this help message and exit +required arguments: + --endpoint IoT endpoint hostname + --cert Path to the certificate file to use during mTLS connection establishment + --key Path to the private key file to use during mTLS connection establishment +optional arguments: + --client_id Client ID (default: mqtt5-sample-) + --topic Topic (default: test/topic) + --message Message payload (default: Hello from mqtt5 sample) + --count Messages to publish (0 = infinite) (default: 5) +``` +The sample will not run without the required arguments and will notify you of missing arguments. + +## Enable logging in samples -* `--verbosity`: The level of logging shown. Can be `Trace`, `Debug`, `Info`, `Warn`, `Error`, `Fatal` or `None`. Logging will not occur if this is not passed in with `None` or greater logging level. -* `--log_file`: The filepath to store the logs at. This is optional, and if undefined the logs will be printed to `stdout` instead. +Instructions to enable logging are available in the [FAQ](../documents/FAQ.md) under [How do I enable logging](../documents/FAQ.md#how-do-i-enable-logging). \ No newline at end of file diff --git a/samples/device_defender/basic_report/CMakeLists.txt b/samples/device_defender/basic_report/CMakeLists.txt deleted file mode 100644 index af0871878d..0000000000 --- a/samples/device_defender/basic_report/CMakeLists.txt +++ /dev/null @@ -1,33 +0,0 @@ -cmake_minimum_required(VERSION 3.9...3.31) - -# note: cxx-17 requires cmake 3.8, cxx-20 requires cmake 3.12 -project(basic-report CXX) - -# Device Defender is only supported on Linux -if (UNIX AND NOT APPLE) - - file(GLOB SRC_FILES - "*.cpp" - "../../utils/CommandLineUtils.cpp" - "../../utils/CommandLineUtils.h" - ) - - add_executable(${PROJECT_NAME} ${SRC_FILES}) - - set_target_properties(${PROJECT_NAME} PROPERTIES - CXX_STANDARD 14) - - #set warnings - target_compile_options(${PROJECT_NAME} PRIVATE -Wall -Wno-long-long -pedantic -Werror) - - find_package(aws-crt-cpp REQUIRED) - find_package(IotDeviceCommon-cpp REQUIRED) - find_package(IotDeviceDefender-cpp REQUIRED) - - include(AwsSanitizers) - enable_language(C) - aws_add_sanitizers(${PROJECT_NAME}) - - target_link_libraries(${PROJECT_NAME} PRIVATE AWS::aws-crt-cpp AWS::IotDeviceCommon-cpp AWS::IotDeviceDefender-cpp) - -endif() diff --git a/samples/device_defender/basic_report/README.md b/samples/device_defender/basic_report/README.md deleted file mode 100644 index 9507077c68..0000000000 --- a/samples/device_defender/basic_report/README.md +++ /dev/null @@ -1,84 +0,0 @@ -# Device Defender - -[**Return to main sample list**](../../README.md) - -This sample uses the AWS IoT [Device Defender](https://aws.amazon.com/iot-device-defender/) Service to send on device metrics to AWS. Device Defender is an AWS IoT Core service that allows you to monitor the health of your IoT device through sending periodic updates containing device data to AWS IoT Core. - -**Note**: Device Defender is **only supported on Unix (Linux).** - -On startup, the sample will make a MQTT connection and a Device Defender task to send metrics every minute or at the time interval passed as a CLI argument. This sample shows how to send custom metrics in addition to the standard metrics that are always sent with Device Defender. - -Your IoT Core Thing's [Policy](https://docs.aws.amazon.com/iot/latest/developerguide/iot-policies.html) must provide privileges for this sample to connect, subscribe, publish, and receive. Below is a sample policy that can be used on your IoT Core Thing that will allow this sample to run as intended. - -
-(see sample policy) -
-{
-  "Version": "2012-10-17",
-  "Statement": [
-    {
-      "Effect": "Allow",
-      "Action": [
-        "iot:Publish",
-        "iot:Subscribe",
-        "iot:RetainPublish"
-      ],
-      "Resource": "arn:aws:iot:region:account:*/$aws/things/*/defender/metrics/*"
-    },
-    {
-      "Effect": "Allow",
-      "Action": "iot:Connect",
-      "Resource": "arn:aws:iot:region:account:client-*"
-    }
-  ]
-}
-
- -Replace with the following with the data from your AWS account: -* ``: The AWS IoT Core region where you created your AWS IoT Core thing you wish to use with this sample. For example `us-east-1`. -* ``: Your AWS IoT Core account ID. This is the set of numbers in the top right next to your AWS account name when using the AWS IoT Core website. - -Note that in a real application, you may want to avoid the use of wildcards in your ClientID or use them selectively. Please follow best practices when working with AWS on production applications using the SDK. Also, for the purposes of this sample, please make sure your policy allows a client ID of `test-*` to connect or use `--client_id ` to send the client ID your policy supports. - -
- -## How to run - -You will need to create a **Security Profile** to see the metric results in the AWS console. You can create a Security Profile by going to `Detect -> Security Profiles` from the AWS IOT Console. To see the custom metrics, you will need to add them in `Detect -> Metrics` and then press the `Create` button to make a new custom metric. - -This sample expects and requires the following custom metrics: -* `CustomNumber` - * type: `number` - * info: always sends the number `10`. -* `CustomNumberTwo` - * type `number` - * info: sends a random number from `-50` to `50`. -* `CustomNumberList` - * type `number-list` - * info: sends a predefined list of numbers. -* `CustomStringList` - * type `string-list` - * info: sends a predefined list of strings. -* `CustomIPList` - * type `ip-list` - * info: sends a predefined list of documentation IP addresses. - -To run the Device Defender sample, use the following command: - -``` sh -./basic-report --endpoint --cert --key --thing_name --ca_file -``` - -You can also pass a Certificate Authority file (CA) if your certificate and key combination requires it: - -``` sh -./basic-report --endpoint --cert --key --thing_name --ca_file -``` - -### Device Defender Data Requirements - -The formatting and data requirements for custom metrics in Device Defender are listed below. If you input data that does not follow the requirements, it will be sent in the JSON packet but Device Defender will not show/retain the data! If your data is not showing up in Device Defender, please double check that your data fits into the requirements below. - -* `number` and `number-list` types: Supports precision up to 15 digits. Supports both positive and negative values. -* `string-list` type: Supports letters A through Z (uppercase and lowercase) and the characters `:`, `_`, `-`, `/`, and `. -* `ip-list` type: Supports only valid IPV4 and IPV6 addresses. diff --git a/samples/device_defender/basic_report/main.cpp b/samples/device_defender/basic_report/main.cpp deleted file mode 100644 index 014b2eba73..0000000000 --- a/samples/device_defender/basic_report/main.cpp +++ /dev/null @@ -1,267 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0. - */ -#include -#include -#include -#include - -#include - -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -#include "../../utils/CommandLineUtils.h" - -using namespace Aws::Crt; - -int s_getCustomMetricNumber(double *output) -{ - *output = 100; - return AWS_OP_SUCCESS; -} - -int s_getCustomMetricNumberList(Vector *output) -{ - output->push_back(1.5); - output->push_back(2.2); - output->push_back(3.9); - output->push_back(9.9); - return AWS_OP_SUCCESS; -} - -int s_getCustomMetricStringList(Vector *output) -{ - output->push_back("One Fish"); - output->push_back("Two Fish"); - output->push_back("Red Fish"); - output->push_back("Blue Fish"); - return AWS_OP_SUCCESS; -} - -int s_getCustomMetricIpAddressList(Vector *output) -{ - output->push_back("192.0.2.0"); - output->push_back("198.51.100.0"); - output->push_back("203.0.113.0"); - output->push_back("233.252.0.0"); - return AWS_OP_SUCCESS; -} - -int main(int argc, char *argv[]) -{ - /************************ Setup ****************************/ - - // Do the global initialization for the API. - ApiHandle apiHandle; - - /** - * cmdData is the arguments/input from the command line placed into a single struct for - * use in this sample. This handles all of the command line parsing, validating, etc. - * See the Utils/CommandLineUtils for more information. - */ - Utils::cmdData cmdData = Utils::parseSampleInputDeviceDefender(argc, argv, &apiHandle); - - // Create the MQTT builder and populate it with data from cmdData. - auto clientConfigBuilder = - Aws::Iot::MqttClientConnectionConfigBuilder(cmdData.input_cert.c_str(), cmdData.input_key.c_str()); - clientConfigBuilder.WithEndpoint(cmdData.input_endpoint); - if (cmdData.input_ca != "") - { - clientConfigBuilder.WithCertificateAuthority(cmdData.input_ca.c_str()); - } - if (cmdData.input_proxyHost != "") - { - Aws::Crt::Http::HttpClientConnectionProxyOptions proxyOptions; - proxyOptions.HostName = cmdData.input_proxyHost; - proxyOptions.Port = static_cast(cmdData.input_proxyPort); - proxyOptions.AuthType = Aws::Crt::Http::AwsHttpProxyAuthenticationType::None; - clientConfigBuilder.WithHttpProxyOptions(proxyOptions); - } - if (cmdData.input_port != 0) - { - clientConfigBuilder.WithPortOverride(static_cast(cmdData.input_port)); - } - - // Create the MQTT connection from the MQTT builder - auto clientConfig = clientConfigBuilder.Build(); - if (!clientConfig) - { - fprintf( - stderr, - "Client Configuration initialization failed with error %s\n", - Aws::Crt::ErrorDebugString(clientConfig.LastError())); - exit(-1); - } - Aws::Iot::MqttClient client = Aws::Iot::MqttClient(); - auto connection = client.NewConnection(clientConfig); - if (!*connection) - { - fprintf( - stderr, - "MQTT Connection Creation failed with error %s\n", - Aws::Crt::ErrorDebugString(connection->LastError())); - exit(-1); - } - - /** - * In a real world application you probably don't want to enforce synchronous behavior - * but this is a sample console application, so we'll just do that with a condition variable. - */ - std::promise connectionCompletedPromise; - std::promise connectionClosedPromise; - - // Invoked when a MQTT connect has completed or failed - auto onConnectionCompleted = [&](Mqtt::MqttConnection &, int errorCode, Mqtt::ReturnCode returnCode, bool) { - if (errorCode) - { - fprintf(stdout, "Connection failed with error %s\n", ErrorDebugString(errorCode)); - connectionCompletedPromise.set_value(false); - } - else - { - if (returnCode != AWS_MQTT_CONNECT_ACCEPTED) - { - fprintf(stdout, "Connection failed with mqtt return code %d\n", (int)returnCode); - connectionCompletedPromise.set_value(false); - } - else - { - fprintf(stdout, "Connection completed successfully.\n"); - connectionCompletedPromise.set_value(true); - } - } - }; - - // Invoked when a MQTT connection was interrupted/lost - auto onInterrupted = [&](Mqtt::MqttConnection &, int error) { - fprintf(stdout, "Connection interrupted with error %s\n", ErrorDebugString(error)); - }; - - // Invoked when a MQTT connection was interrupted/lost, but then reconnected successfully - auto onResumed = [&](Mqtt::MqttConnection &, Mqtt::ReturnCode, bool) { fprintf(stdout, "Connection resumed\n"); }; - - // Invoked when a disconnect has been completed - auto onDisconnect = [&](Mqtt::MqttConnection &) { - { - fprintf(stdout, "Disconnect completed\n"); - connectionClosedPromise.set_value(); - } - }; - - // Assign callbacks - connection->OnConnectionCompleted = std::move(onConnectionCompleted); - connection->OnDisconnect = std::move(onDisconnect); - connection->OnConnectionInterrupted = std::move(onInterrupted); - connection->OnConnectionResumed = std::move(onResumed); - - /************************ Run the sample ****************************/ - - // Connect - fprintf(stdout, "Connecting...\n"); - if (!connection->Connect(cmdData.input_clientId.c_str(), false /*cleanSession*/, 1000 /*keepAliveTimeSecs*/)) - { - fprintf(stderr, "MQTT Connection failed with error %s\n", ErrorDebugString(connection->LastError())); - exit(-1); - } - if (connectionCompletedPromise.get_future().get()) - { - // Device defender setup and metric registration - // ====================================================================== - Aws::Crt::Allocator *allocator = Aws::Crt::DefaultAllocator(); - Aws::Crt::Io::EventLoopGroup *eventLoopGroup = Aws::Crt::ApiHandle::GetOrCreateStaticDefaultEventLoopGroup(); - - bool callbackSuccess = false; - auto onCancelled = [&](void *a) -> void { - auto *data = reinterpret_cast(a); - *data = true; - }; - - Aws::Iotdevicedefenderv1::ReportTaskBuilder taskBuilder( - allocator, connection, *eventLoopGroup, cmdData.input_thingName); - taskBuilder.WithTaskPeriodSeconds((uint32_t)cmdData.input_reportTime) - .WithNetworkConnectionSamplePeriodSeconds((uint32_t)cmdData.input_reportTime) - .WithTaskCancelledHandler(onCancelled) - .WithTaskCancellationUserData(&callbackSuccess); - std::shared_ptr task = taskBuilder.Build(); - - // Add the custom metrics (Inline function example) - Aws::Iotdevicedefenderv1::CustomMetricNumberFunction s_localGetCustomMetricNumber = [](double *output) { - *output = 8.4; - return AWS_OP_SUCCESS; - }; - task->RegisterCustomMetricNumber("CustomNumber", std::move(s_localGetCustomMetricNumber)); - - Aws::Iotdevicedefenderv1::CustomMetricNumberFunction s_getCustomMetricNumberFunc = s_getCustomMetricNumber; - task->RegisterCustomMetricNumber("CustomNumberTwo", std::move(s_getCustomMetricNumberFunc)); - Aws::Iotdevicedefenderv1::CustomMetricNumberListFunction s_getCustomMetricNumberListFunc = - s_getCustomMetricNumberList; - task->RegisterCustomMetricNumberList("CustomNumberList", std::move(s_getCustomMetricNumberListFunc)); - Aws::Iotdevicedefenderv1::CustomMetricStringListFunction s_getCustomMetricStringListFunc = - s_getCustomMetricStringList; - task->RegisterCustomMetricStringList("CustomStringList", std::move(s_getCustomMetricStringListFunc)); - Aws::Iotdevicedefenderv1::CustomMetricIpListFunction s_getCustomMetricIpAddressListFunc = - s_getCustomMetricIpAddressList; - task->RegisterCustomMetricIpAddressList("CustomIPList", std::move(s_getCustomMetricIpAddressListFunc)); - - // Start the Device Defender task - if (task->StartTask() != AWS_OP_SUCCESS) - { - fprintf(stdout, "Device Defender failed to initialize task.\n"); - exit(-1); - } - else - { - fprintf(stdout, "Device Defender initialized.\n"); - } - - if ((int)task->GetStatus() == (int)Aws::Iotdevicedefenderv1::ReportTaskStatus::Running) - { - fprintf(stdout, "Device Defender task running.\n"); - } - else - { - fprintf(stdout, "Device Defender task in unknown status. Status: %d\n", (int)task->GetStatus()); - exit(-1); - } - // ====================================================================== - - // Wait until the the desire amount of publishes has been complete - uint64_t publishedCount = 0; - while (publishedCount < cmdData.input_count && - (int)task->GetStatus() == (int)Aws::Iotdevicedefenderv1::ReportTaskStatus::Running) - { - ++publishedCount; - fprintf(stdout, "Publishing next Device Defender report...\n"); - - if (publishedCount != cmdData.input_count) - { - std::this_thread::sleep_for(std::chrono::milliseconds(cmdData.input_reportTime * 1000)); - } - } - - // Stop the task so we stop sending device defender metrics - task->StopTask(); - - // Disconnect - if (connection->Disconnect()) - { - connectionClosedPromise.get_future().wait(); - } - } - else - { - exit(-1); - } - - return 0; -} diff --git a/samples/greengrass/basic_discovery/CMakeLists.txt b/samples/greengrass/basic_discovery/CMakeLists.txt index dfd8aaabbb..1869ffd9d0 100644 --- a/samples/greengrass/basic_discovery/CMakeLists.txt +++ b/samples/greengrass/basic_discovery/CMakeLists.txt @@ -4,8 +4,6 @@ project(basic-discovery CXX) file(GLOB SRC_FILES "*.cpp" - "../../utils/CommandLineUtils.cpp" - "../../utils/CommandLineUtils.h" ) add_executable(${PROJECT_NAME} ${SRC_FILES}) diff --git a/samples/greengrass/basic_discovery/main.cpp b/samples/greengrass/basic_discovery/main.cpp index c89b687a64..45920ab528 100644 --- a/samples/greengrass/basic_discovery/main.cpp +++ b/samples/greengrass/basic_discovery/main.cpp @@ -15,15 +15,121 @@ #include #include -#include "../../utils/CommandLineUtils.h" - using namespace Aws::Crt; using namespace Aws::Discovery; +/* --------------------------------- ARGUMENT PARSING ----------------------------------------- */ +struct CmdArgs +{ + String endpoint; + String cert; + String key; + String thingName; + String topic = "test/topic"; + String message; + String mode = "both"; + String signingRegion = "us-east-1"; + String proxyHost; + uint32_t proxyPort = 0; + bool printDiscoverRespOnly = false; +}; + +void printHelp() +{ + printf("Greengrass Discovery Sample\n"); + printf("options:\n"); + printf(" --help show this help message and exit\n"); + printf("required arguments:\n"); + printf(" --cert Path to the certificate file\n"); + printf(" --key Path to the private key file\n"); + printf(" --thing_name Thing name\n"); + printf("optional arguments:\n"); + printf(" --client_id Client ID (default: test-)\n"); + printf(" --topic Topic (default: test/topic)\n"); + printf(" --message Message to publish\n"); + printf(" --mode Mode: publish, subscribe, both (default: both)\n"); + printf(" --signing_region Signing region (default: us-east-1)\n"); + printf(" --proxy_host HTTP proxy host\n"); + printf(" --proxy_port HTTP proxy port\n"); + printf(" --print_discover_resp_only Print discovery response only\n"); +} + +CmdArgs parseArgs(int argc, char *argv[]) +{ + CmdArgs args; + for (int i = 1; i < argc; i++) + { + if (strcmp(argv[i], "--help") == 0) + { + printHelp(); + exit(0); + } + else if (i < argc - 1) + { + if (strcmp(argv[i], "--cert") == 0) + { + args.cert = argv[++i]; + } + else if (strcmp(argv[i], "--key") == 0) + { + args.key = argv[++i]; + } + else if (strcmp(argv[i], "--thing_name") == 0) + { + args.thingName = argv[++i]; + } + + else if (strcmp(argv[i], "--topic") == 0) + { + args.topic = argv[++i]; + } + else if (strcmp(argv[i], "--message") == 0) + { + args.message = argv[++i]; + } + else if (strcmp(argv[i], "--mode") == 0) + { + args.mode = argv[++i]; + } + else if (strcmp(argv[i], "--signing_region") == 0) + { + args.signingRegion = argv[++i]; + } + else if (strcmp(argv[i], "--proxy_host") == 0) + { + args.proxyHost = argv[++i]; + } + else if (strcmp(argv[i], "--proxy_port") == 0) + { + args.proxyPort = atoi(argv[++i]); + } + else + { + fprintf(stderr, "Unknown argument: %s\n", argv[i]); + printHelp(); + exit(1); + } + } + else if (strcmp(argv[i], "--print_discover_resp_only") == 0) + { + args.printDiscoverRespOnly = true; + } + } + if (args.cert.empty() || args.key.empty() || args.thingName.empty()) + { + fprintf(stderr, "Error: --cert, --key, and --thing_name are required\n"); + printHelp(); + exit(1); + } + return args; +} + +/* --------------------------------- ARGUMENT PARSING END ----------------------------------------- */ + static std::shared_ptr getMqttConnection( Aws::Iot::MqttClient &mqttClient, const Aws::Crt::Vector &ggGroups, - Utils::cmdData &cmdData, + CmdArgs &cmdData, std::promise &shutdownCompletedPromise) { std::shared_ptr connection; @@ -41,7 +147,7 @@ static std::shared_ptr getMqttConnection( (int)connectivityInfo.Port.value()); connection = mqttClient.NewConnection( - Aws::Iot::MqttClientConnectionConfigBuilder(cmdData.input_cert.c_str(), cmdData.input_key.c_str()) + Aws::Iot::MqttClientConnectionConfigBuilder(cmdData.cert.c_str(), cmdData.key.c_str()) .WithCertificateAuthority(ByteCursorFromCString(groupToUse.CAs->at(0).c_str())) .WithPortOverride(connectivityInfo.Port.value()) .WithEndpoint(connectivityInfo.HostAddress.value()) @@ -98,7 +204,7 @@ static std::shared_ptr getMqttConnection( shutdownCompletedPromise.set_value(); }; - if (!connection->Connect(cmdData.input_thingName.c_str(), false)) + if (!connection->Connect(cmdData.thingName.c_str(), false)) { fprintf(stderr, "Connect failed with error %s\n", aws_error_debug_str(aws_last_error())); continue; @@ -134,21 +240,16 @@ static void printGreengrassResponse(const Aws::Crt::Vector &ggGroups) int main(int argc, char *argv[]) { - /************************ Setup ****************************/ + // Parse command line arguments + CmdArgs cmdData = parseArgs(argc, argv); + /************************ Setup ****************************/ // Do the global initialization for the API. ApiHandle apiHandle; - /** - * cmdData is the arguments/input from the command line placed into a single struct for - * use in this sample. This handles all of the command line parsing, validating, etc. - * See the Utils/CommandLineUtils for more information. - */ - Utils::cmdData cmdData = Utils::parseSampleInputGreengrassDiscovery(argc, argv, &apiHandle); - // We're using Mutual TLS for MQTT, so we need to load our client certificates Io::TlsContextOptions tlsCtxOptions = - Io::TlsContextOptions::InitClientWithMtls(cmdData.input_cert.c_str(), cmdData.input_key.c_str()); + Io::TlsContextOptions::InitClientWithMtls(cmdData.cert.c_str(), cmdData.key.c_str()); if (!tlsCtxOptions) { @@ -156,11 +257,6 @@ int main(int argc, char *argv[]) exit(-1); } - if (!cmdData.input_ca.empty()) - { - tlsCtxOptions.OverrideDefaultTrustStore(nullptr, cmdData.input_ca.c_str()); - } - Io::TlsContext tlsCtx(tlsCtxOptions, Io::TlsMode::CLIENT); if (!tlsCtx) @@ -189,13 +285,13 @@ int main(int argc, char *argv[]) DiscoveryClientConfig clientConfig; clientConfig.SocketOptions = socketOptions; clientConfig.TlsContext = tlsCtx; - clientConfig.Region = cmdData.input_signingRegion; + clientConfig.Region = cmdData.signingRegion; Aws::Crt::Http::HttpClientConnectionProxyOptions proxyOptions; - if (cmdData.input_proxyHost.length() > 0 && cmdData.input_proxyPort != 0) + if (cmdData.proxyHost.length() > 0 && cmdData.proxyPort != 0) { - proxyOptions.HostName = cmdData.input_proxyHost; - proxyOptions.Port = static_cast(cmdData.input_proxyPort); + proxyOptions.HostName = cmdData.proxyHost; + proxyOptions.Port = cmdData.proxyPort; clientConfig.ProxyOptions = proxyOptions; } @@ -217,7 +313,7 @@ int main(int argc, char *argv[]) // NOTE: This is an asynchronous operation, so it completes before the results are actually ready. You need to use // synchronization techniques to obtain its results. For simplicity, we use promise/future in this sample. discoveryClient->Discover( - cmdData.input_thingName, + cmdData.thingName, [&discoverResponse, &discoveryStatusPromise](DiscoverResponse *response, int error, int httpResponseCode) { fprintf(stdout, "Discovery completed with error code %d; http code %d\n", error, httpResponseCode); @@ -245,7 +341,7 @@ int main(int argc, char *argv[]) } // Print the discovery response information and then exit. Does not use the discovery info. - if (cmdData.input_PrintDiscoverRespOnly) + if (cmdData.printDiscoverRespOnly) { printGreengrassResponse(*discoverResponse.GGGroups); return 0; @@ -260,7 +356,7 @@ int main(int argc, char *argv[]) } // Now, with the established connection to a Greengrass core, we can perform MQTT-related actions. - if (cmdData.input_mode == "both" || cmdData.input_mode == "subscribe") + if (cmdData.mode == "both" || cmdData.mode == "subscribe") { auto onMessage = [&](Mqtt::MqttConnection & /*connection*/, const String &receivedOnTopic, @@ -294,30 +390,30 @@ int main(int argc, char *argv[]) } }; - connection->Subscribe(cmdData.input_topic.c_str(), AWS_MQTT_QOS_AT_MOST_ONCE, onMessage, onSubAck); + connection->Subscribe(cmdData.topic.c_str(), AWS_MQTT_QOS_AT_MOST_ONCE, onMessage, onSubAck); } bool first_input = true; while (true) { String input; - if (cmdData.input_mode == "both" || cmdData.input_mode == "publish") + if (cmdData.mode == "both" || cmdData.mode == "publish") { - if (cmdData.input_message.empty()) + if (cmdData.message.empty()) { fprintf( stdout, "Enter the message you want to publish to topic %s and press enter. Enter 'exit' to exit this " "program.\n", - cmdData.input_topic.c_str()); + cmdData.topic.c_str()); std::getline(std::cin, input); - cmdData.input_message = input; + cmdData.message = input; } else if (!first_input) { fprintf(stdout, "Enter a new message or enter 'exit' or 'quit' to exit the program.\n"); std::getline(std::cin, input); - cmdData.input_message = input; + cmdData.message = input; } first_input = false; } @@ -333,7 +429,7 @@ int main(int argc, char *argv[]) break; } - if (cmdData.input_mode == "both" || cmdData.input_mode == "publish") + if (cmdData.mode == "both" || cmdData.mode == "publish") { ByteBuf payload = ByteBufNewCopy(DefaultAllocator(), (const uint8_t *)input.data(), input.length()); ByteBuf *payloadPtr = &payload; @@ -351,7 +447,7 @@ int main(int argc, char *argv[]) } }; connection->Publish( - cmdData.input_topic.c_str(), AWS_MQTT_QOS_AT_LEAST_ONCE, false, payload, onPublishComplete); + cmdData.topic.c_str(), AWS_MQTT_QOS_AT_LEAST_ONCE, false, payload, onPublishComplete); } } diff --git a/samples/greengrass/ipc/CMakeLists.txt b/samples/greengrass/ipc/CMakeLists.txt index 91be8c9a40..ffea203f36 100644 --- a/samples/greengrass/ipc/CMakeLists.txt +++ b/samples/greengrass/ipc/CMakeLists.txt @@ -4,8 +4,6 @@ project(greengrass-ipc CXX) file(GLOB SRC_FILES "*.cpp" - "../../utils/CommandLineUtils.cpp" - "../../utils/CommandLineUtils.h" ) add_executable(${PROJECT_NAME} ${SRC_FILES}) diff --git a/samples/greengrass/ipc/main.cpp b/samples/greengrass/ipc/main.cpp index d75eb89b1d..8461f61dec 100644 --- a/samples/greengrass/ipc/main.cpp +++ b/samples/greengrass/ipc/main.cpp @@ -7,8 +7,6 @@ #include -#include "../../utils/CommandLineUtils.h" - using namespace Aws::Crt; using namespace Aws::Greengrass; @@ -22,12 +20,54 @@ int main(int argc, char *argv[]) // Do the global initialization for the API. ApiHandle apiHandle; - /** - * cmdData is the arguments/input from the command line placed into a single struct for - * use in this sample. This handles all of the command line parsing, validating, etc. - * See the Utils/CommandLineUtils for more information. - */ - Utils::cmdData cmdData = Utils::parseSampleInputGreengrassIPC(argc, argv, &apiHandle); + /* --------------------------------- ARGUMENT PARSING ----------------------------------------- */ + struct CmdArgs + { + String topic = "test/topic"; + String message = "Hello from Greengrass IPC"; + }; + + auto printHelp = []() { + printf("Greengrass IPC Sample\n"); + printf("options:\n"); + printf(" --help show this help message and exit\n"); + printf("optional arguments:\n"); + printf(" --topic Topic (default: test/topic)\n"); + printf(" --message Message to publish (default: Hello from Greengrass IPC)\n"); + }; + + auto parseArgs = [&](int argc, char *argv[]) -> CmdArgs { + CmdArgs args; + for (int i = 1; i < argc; i++) + { + if (strcmp(argv[i], "--help") == 0) + { + printHelp(); + exit(0); + } + else if (i < argc - 1) + { + if (strcmp(argv[i], "--topic") == 0) + { + args.topic = argv[++i]; + } + else if (strcmp(argv[i], "--message") == 0) + { + args.message = argv[++i]; + } + else + { + fprintf(stderr, "Unknown argument: %s\n", argv[i]); + printHelp(); + exit(1); + } + } + } + return args; + }; + + CmdArgs cmdData = parseArgs(argc, argv); + /* --------------------------------- ARGUMENT PARSING END ----------------------------------------- */ fprintf(stdout, "Running Greengrass IPC sample\n"); @@ -113,13 +153,13 @@ int main(int argc, char *argv[]) auto subscribeOperation = client.NewSubscribeToIoTCore(streamHandler); SubscribeToIoTCoreRequest subscribeRequest; subscribeRequest.SetQos(QOS_AT_LEAST_ONCE); - subscribeRequest.SetTopicName(cmdData.input_topic); + subscribeRequest.SetTopicName(cmdData.topic); - fprintf(stdout, "Attempting to subscribe to %s topic\n", cmdData.input_topic.c_str()); + fprintf(stdout, "Attempting to subscribe to %s topic\n", cmdData.topic.c_str()); auto requestStatus = subscribeOperation->Activate(subscribeRequest).get(); if (!requestStatus) { - fprintf(stderr, "Failed to send subscription request to %s topic\n", cmdData.input_topic.c_str()); + fprintf(stderr, "Failed to send subscription request to %s topic\n", cmdData.topic.c_str()); exit(-1); } @@ -127,7 +167,7 @@ int main(int argc, char *argv[]) auto subscribeResult = subscribeResultFuture.get(); if (subscribeResult) { - fprintf(stdout, "Successfully subscribed to %s topic\n", cmdData.input_topic.c_str()); + fprintf(stdout, "Successfully subscribed to %s topic\n", cmdData.topic.c_str()); } else { @@ -156,19 +196,19 @@ int main(int argc, char *argv[]) // Publish to the same topic that is currently subscribed to. auto publishOperation = client.NewPublishToIoTCore(); PublishToIoTCoreRequest publishRequest; - publishRequest.SetTopicName(cmdData.input_topic); - Vector payload(cmdData.input_message.begin(), cmdData.input_message.end()); + publishRequest.SetTopicName(cmdData.topic); + Vector payload(cmdData.message.begin(), cmdData.message.end()); publishRequest.SetPayload(payload); publishRequest.SetQos(QOS_AT_LEAST_ONCE); - fprintf(stdout, "Attempting to publish to %s topic\n", cmdData.input_topic.c_str()); + fprintf(stdout, "Attempting to publish to %s topic\n", cmdData.topic.c_str()); requestStatus = publishOperation->Activate(publishRequest).get(); if (!requestStatus) { fprintf( stderr, "Failed to publish to %s topic with error %s\n", - cmdData.input_topic.c_str(), + cmdData.topic.c_str(), requestStatus.StatusToString().c_str()); exit(-1); } @@ -178,7 +218,7 @@ int main(int argc, char *argv[]) auto publishResult = publishResultFuture.get(); if (publishResult) { - fprintf(stdout, "Successfully published to %s topic\n", cmdData.input_topic.c_str()); + fprintf(stdout, "Successfully published to %s topic\n", cmdData.topic.c_str()); auto *response = publishResult.GetOperationResponse(); (void)response; } diff --git a/samples/mqtt5/mqtt5_pubsub/CMakeLists.txt b/samples/mqtt/mqtt5_aws_websocket/CMakeLists.txt similarity index 85% rename from samples/mqtt5/mqtt5_pubsub/CMakeLists.txt rename to samples/mqtt/mqtt5_aws_websocket/CMakeLists.txt index 521434c8f1..6bc991c16a 100644 --- a/samples/mqtt5/mqtt5_pubsub/CMakeLists.txt +++ b/samples/mqtt/mqtt5_aws_websocket/CMakeLists.txt @@ -1,11 +1,9 @@ cmake_minimum_required(VERSION 3.9...3.31) # note: cxx-17 requires cmake 3.8, cxx-20 requires cmake 3.12 -project(mqtt5_pubsub CXX) +project(mqtt5_aws_websocket CXX) file(GLOB SRC_FILES "*.cpp" - "../../utils/CommandLineUtils.cpp" - "../../utils/CommandLineUtils.h" ) add_executable(${PROJECT_NAME} ${SRC_FILES}) diff --git a/samples/mqtt/mqtt5_aws_websocket/README.md b/samples/mqtt/mqtt5_aws_websocket/README.md new file mode 100644 index 0000000000..a7c467dde1 --- /dev/null +++ b/samples/mqtt/mqtt5_aws_websocket/README.md @@ -0,0 +1,114 @@ +# MQTT5 AWS Websocket PubSub + +[**Return to main sample list**](../../README.md) + +*__Jump To:__* +* [Introduction](#introduction) +* [Requirements](#requirements) +* [How To Build](#how-to-build) +* [How To Run](#how-to-run) +* [Additional Information](#additional-information) + +## Introduction +This sample uses the +[Message Broker](https://docs.aws.amazon.com/iot/latest/developerguide/iot-message-broker.html) +for AWS IoT to send and receive messages through an MQTT connection using MQTT5 and a websocket as transport. Using websockets as transport requires the initial handshake request to be signed with the AWS Sigv4 signing algorithm. [`CredentialsProvider::CreateCredentialsProviderChainDefault`](https://awslabs.github.io/aws-crt-cpp/class_aws_1_1_crt_1_1_auth_1_1_credentials_provider.html#aa943e53da72a758b2e921ee8866e3d94) is used to source credentials via the default credentials provider chain to sign the websocket handshake. + +You can read more about MQTT5 for the CPP IoT Device SDK V2 in the [MQTT5 user guide](../../../documents/MQTT5_Userguide.md). + +## Requirements + +The AWS IAM permission policy associated with the AWS credentials resolved by the default credentials provider chain must provide privileges for the sample to connect, subscribe, publish, and receive. Below is a sample policy will allow this sample to run as intended. + +
+(see sample policy) +
+{
+  "Version": "2012-10-17",
+  "Statement": [
+    {
+      "Effect": "Allow",
+      "Action": [
+        "iot:Publish",
+        "iot:Receive"
+      ],
+      "Resource": [
+        "arn:aws:iot:region:account:topic/test/topic"
+      ]
+    },
+    {
+      "Effect": "Allow",
+      "Action": [
+        "iot:Subscribe"
+      ],
+      "Resource": [
+        "arn:aws:iot:region:account:topicfilter/test/topic"
+      ]
+    },
+    {
+      "Effect": "Allow",
+      "Action": [
+        "iot:Connect"
+      ],
+      "Resource": [
+        "arn:aws:iot:region:account:client/mqtt5-sample-*"
+      ]
+    }
+  ]
+}
+
+ +Replace with the following with the data from your AWS account: +* ``: The AWS IoT Core region where you created your AWS IoT Core thing you wish to use with this sample. For example `us-east-1`. +* ``: Your AWS IoT Core account ID. This is the set of numbers in the top right next to your AWS account name when using the AWS IoT Core website. + +Note that in a real application, you may want to avoid the use of wildcards in your ClientID or use them selectively. Please follow best practices when working with AWS on production applications using the SDK. Also, for the purposes of this sample, please make sure your policy allows a client ID of `mqtt5-sample-*` to connect or use `--client_id ` to send the client ID your policy supports. + +
+ +## How to build + +To build the sample, change directory into the samples, and run the cmake commands +```sh +cd samples/mqtt/mqtt5_aws_websocket/ +# If you followed the SDK build instruction, you would use the path to `sdk-workspace` folder for `CMAKE_PREFIX_PATH` here +cmake -B build -S . -DCMAKE_PREFIX_PATH="" -DCMAKE_BUILD_TYPE="Debug" . +cmake --build build --config "Debug" +``` + +## How to run + +To Run this sample from the `samples\mqtt\mqtt5_aws_websocket` folder, use the following command: + +```sh +.mqtt5_aws_websocket \ + --endpoint \ + --signing_region +``` + +If you would like to see what optional arguments are available, use the `--help` argument: +```sh +./mqtt5_aws_websocket --help +``` + +will result in the following output: +``` +MQTT5 AWS Websocket Sample. + +options: + -h, --help show this help message and exit + +required arguments: + --endpoint IoT endpoint hostname + --signing_region Signing region for websocket connection + +optional arguments: + --client_id Client ID (default: mqtt5-sample-) + --topic Topic (default: test/topic) + --message Message payload (default: Hello from mqtt5 sample) + --count Messages to publish (0 = infinite) (default: 5) +``` +The sample will not run without the required arguments and will notify you of missing arguments. + +## Additional Information +Additional help with the MQTT5 Client can be found in the [MQTT5 Userguide](../../../documents/MQTT5_Userguide.md). This guide will provide more details on MQTT5 [operations](../../../documents/MQTT5_Userguide.md#client-operations), [lifecycle events](../../documents/MQTT5_Userguide.md#client-lifecycle-management), [connection methods](../../../documents/MQTT5_Userguide.md#connecting-to-aws-iot-core), and other useful information. diff --git a/samples/mqtt/mqtt5_aws_websocket/main.cpp b/samples/mqtt/mqtt5_aws_websocket/main.cpp new file mode 100644 index 0000000000..5efbbe9eba --- /dev/null +++ b/samples/mqtt/mqtt5_aws_websocket/main.cpp @@ -0,0 +1,392 @@ +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ +#include +#include +#include +#include +#include + +#include + +using namespace Aws::Crt; + +/* --------------------------------- ARGUMENT PARSING ----------------------------------------- */ +struct CmdArgs +{ + String endpoint; + String signingRegion; + String clientId; + String topic = "test/topic"; + String message = "Hello from mqtt5 sample"; + uint32_t count = 5; +}; + +void printHelp() +{ + printf("MQTT5 AWS Websocket Sample.\n"); + printf("\n"); + printf("options:\n"); + printf(" -h, --help show this help message and exit\n"); + printf("\n"); + printf("required arguments:\n"); + printf(" --endpoint IoT endpoint hostname \n"); + printf(" --signing_region Signing region for websocket connection \n"); + printf("\n"); + printf("optional arguments:\n"); + printf(" --client_id Client ID (default: mqtt5-sample-)\n"); + printf(" --topic Topic (default: test/topic)\n"); + printf(" --message Message payload (default: Hello from mqtt5 sample)\n"); + printf(" --count Messages to publish (0 = infinite) (default: 5)\n"); +} + +CmdArgs parseArgs(int argc, char *argv[]) +{ + CmdArgs args; + for (int i = 1; i < argc; i++) + { + if (strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0) + { + printHelp(); + exit(0); + } + else if (i < argc - 1) + { + if (strcmp(argv[i], "--endpoint") == 0) + { + args.endpoint = argv[++i]; + } + else if (strcmp(argv[i], "--signing_region") == 0) + { + args.signingRegion = argv[++i]; + } + + else if (strcmp(argv[i], "--client_id") == 0) + { + args.clientId = argv[++i]; + } + else if (strcmp(argv[i], "--topic") == 0) + { + args.topic = argv[++i]; + } + else if (strcmp(argv[i], "--message") == 0) + { + args.message = argv[++i]; + } + else if (strcmp(argv[i], "--count") == 0) + { + args.count = atoi(argv[++i]); + } + else + { + fprintf(stderr, "Unknown argument: %s\n", argv[i]); + printHelp(); + exit(1); + } + } + } + if (args.endpoint.empty() || args.signingRegion.empty()) + { + fprintf(stderr, "Error: --endpoint and --signing_region are required\n"); + printHelp(); + exit(1); + } + if (args.clientId.empty()) + { + args.clientId = String("mqtt5-sample-") + UUID().ToString(); + } + return args; +} +/* --------------------------------- ARGUMENT PARSING END ----------------------------------------- */ + +int main(int argc, char *argv[]) +{ + // Parse command line arguments + CmdArgs cmdData = parseArgs(argc, argv); + + // Variables needed for the sample + std::mutex receiveMutex; + std::condition_variable receiveSignal; + uint32_t receivedCount = 0; + std::promise connectionPromise; + std::promise stoppedPromise; + std::promise disconnectPromise; + std::promise subscribeSuccess; + std::promise unsubscribeFinishedPromise; + + /* Do the global initialization for the API. */ + ApiHandle apiHandle; + + /** + * Create MQTT5 client builder using mutual TLS via X509 Certificate and Private Key, + * The builder will be used to create the final client + */ + + // Create websocket configuration + Aws::Crt::Auth::CredentialsProviderChainDefaultConfig defaultConfig; + std::shared_ptr provider = Aws::Crt::Auth::CredentialsProvider::CreateCredentialsProviderChainDefault(defaultConfig); + if (!provider) + { + fprintf(stderr, "Failure to create credentials provider!\n"); + exit(-1); + } + Aws::Iot::WebsocketConfig websocketConfig(cmdData.signingRegion, provider); + + // Create a Client using Mqtt5ClientBuilder + auto builder = std::unique_ptr( + Aws::Iot::Mqtt5ClientBuilder::NewMqtt5ClientBuilderWithWebsocket( + cmdData.endpoint, websocketConfig)); + + // Check if the builder setup correctly. + if (builder == nullptr) + { + printf( + "Failed to setup Mqtt5 client builder with error code %d: %s\n", LastError(), ErrorDebugString(LastError())); + exit(1); + } + + // Setup connection options + std::shared_ptr connectOptions = + Aws::Crt::MakeShared(Aws::Crt::DefaultAllocatorImplementation()); + connectOptions->WithClientId(cmdData.clientId); + builder->WithConnectOptions(connectOptions); + + /* Setup lifecycle callbacks */ + // Callback when any publish is received + builder->WithPublishReceivedCallback( + [&receiveMutex, &receivedCount, &receiveSignal](const Mqtt5::PublishReceivedEventData &eventData) + { + if (eventData.publishPacket == nullptr) + return; + + std::lock_guard lock(receiveMutex); + ++receivedCount; + fprintf(stdout, "==== Received message from topic '%s': ", eventData.publishPacket->getTopic().c_str()); + fwrite(eventData.publishPacket->getPayload().ptr, 1, eventData.publishPacket->getPayload().len, stdout); + fprintf(stdout, " ====\n"); + + receiveSignal.notify_all(); + }); + + // Callback for the lifecycle event the client Stopped + builder->WithClientStoppedCallback( + [&stoppedPromise](const Mqtt5::OnStoppedEventData &) + { + fprintf(stdout, "Lifecycle Stopped.\n"); + stoppedPromise.set_value(); + }); + + // Callback for lifecycle event Attempting Connect + builder->WithClientAttemptingConnectCallback( + [&cmdData](const Mqtt5::OnAttemptingConnectEventData &) + { + fprintf( + stdout, + "Lifecycle Connection Attempt\nConnecting to endpoint:'%s' with client ID '%s'\n", + cmdData.endpoint.c_str(), + cmdData.clientId.c_str()); + }); + + // Callback for the lifecycle event Connection Success + builder->WithClientConnectionSuccessCallback( + [&connectionPromise](const Mqtt5::OnConnectionSuccessEventData &eventData) + { + fprintf( + stdout, + "Lifecycle Connection Success with reason code: %d\n", + eventData.connAckPacket->getReasonCode()); + connectionPromise.set_value(true); + }); + + // Callback for the lifecycle event Connection Failure + builder->WithClientConnectionFailureCallback( + [&connectionPromise](const Mqtt5::OnConnectionFailureEventData &eventData) + { + fprintf(stdout, "Lifecycle Connection Failure with error: %s.\n", aws_error_debug_str(eventData.errorCode)); + connectionPromise.set_value(false); + }); + + // Callback for the lifecycle event Connection get disconnected + builder->WithClientDisconnectionCallback( + [&disconnectPromise](const Mqtt5::OnDisconnectionEventData &eventData) + { + fprintf(stdout, "Lifecycle Disconnected.\n"); + if (eventData.disconnectPacket != nullptr) + { + Mqtt5::DisconnectReasonCode reason_code = eventData.disconnectPacket->getReasonCode(); + fprintf(stdout, "Disconnection packet code: %d.\n", reason_code); + fprintf(stdout, "Disconnection packet reason: %s.\n", aws_error_debug_str(reason_code)); + } + disconnectPromise.set_value(); + }); + + /* Create Mqtt5Client from the builder */ + fprintf(stdout, "==== Starting client ====\n"); + std::shared_ptr client = builder->Build(); + + if (client == nullptr) + { + fprintf( + stdout, "Failed to init Mqtt5Client with error code %d: %s\n", LastError(), ErrorDebugString(LastError())); + exit(1); + } + + /** + * Start the client, instructing the client to desire a connected state. The client will try to + * establish a connection with the provided settings. If the client is disconnected while in this + * state it will attempt to reconnect automatically. + */ + fprintf(stdout, "==== Starting client ====\n"); + client->Start(); + + // We await the `ClientConnectionSuccessCallback` callback to be invoked. + if (connectionPromise.get_future().get()) + { + /** + * Subscribe + */ + // Setup the callback that will be triggered on receiveing SUBACK from the server + fprintf(stdout, "==== Subscribing to topic '%s' ==== \n", cmdData.topic.c_str()); + auto onSubAck = [&subscribeSuccess](int error_code, std::shared_ptr suback) + { + if (error_code != 0) + { + fprintf( + stdout, + "Subscription failed with error code: (%d)%s\n", + error_code, + aws_error_debug_str(error_code)); + } + if (suback != nullptr) + { + for (Mqtt5::SubAckReasonCode reasonCode : suback->getReasonCodes()) + { + fprintf(stdout, "Suback received with reason code: %d\n", reasonCode); + } + } + subscribeSuccess.set_value(); + }; + + // Create a subscription object, and add it to a subscribe packet + Mqtt5::Subscription subscription(cmdData.topic, Mqtt5::QOS::AWS_MQTT5_QOS_AT_LEAST_ONCE); + subscription.WithNoLocal(false); + std::shared_ptr subPacket = + Aws::Crt::MakeShared(Aws::Crt::DefaultAllocatorImplementation()); + subPacket->WithSubscription(std::move(subscription)); + + // Subscribe & wait for the subscription to complete + if (client->Subscribe(subPacket, onSubAck)) + { + subscribeSuccess.get_future().wait(); + } + + /** + * Publish to the topics + */ + // Setup publish completion callback. The callback will get triggered when the publish completes (when + // the client received the PubAck from the server). + auto onPublishComplete = [](int, std::shared_ptr result) + { + if (!result->wasSuccessful()) + { + fprintf(stdout, "Publish failed with error code: %d\n", result->getErrorCode()); + } + else if (result != nullptr) + { + std::shared_ptr puback = + std::dynamic_pointer_cast(result->getAck()); + + fprintf(stdout, "PubAck received with: %d\n", puback->getReasonCode()); + } + }; + + if (cmdData.count == 0) + { + fprintf(stdout, "==== Sending messages until program killed ====\n"); + } + else + { + fprintf(stdout, "==== Sending %d message(s) ====\n", cmdData.count); + } + + uint32_t publishedCount = 0; + while (publishedCount < cmdData.count || cmdData.count == 0) + { + // Add \" to 'JSON-ify' the message + String message = "\"" + cmdData.message + "[" + std::to_string(publishedCount + 1).c_str() + "]\""; + ByteCursor payload = ByteCursorFromString(message); + + fprintf(stdout, "Publishing message to topic '%s': %s\n", cmdData.topic.c_str(), message.c_str()); + + // Create a publish packet + std::shared_ptr publish = Aws::Crt::MakeShared( + Aws::Crt::DefaultAllocatorImplementation(), + cmdData.topic, + payload, + Mqtt5::QOS::AWS_MQTT5_QOS_AT_LEAST_ONCE); + // Publish + if (client->Publish(publish, onPublishComplete)) + { + ++publishedCount; + } + + // Sleep between publishes to avoid flooding the server + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + } + + // Wait to receive all the messages we sent. + { + std::unique_lock receivedLock(receiveMutex); + receiveSignal.wait(receivedLock, [&] { return receivedCount >= cmdData.count; }); + } + fprintf(stdout, "%d message(s) received.\n", receivedCount); + + /** + * Unsubscribe from the topic. + */ + fprintf(stdout, "==== Unsubscribing from topic '%s' ==== \n", cmdData.topic.c_str()); + // Setup the callback that will be triggered on receiveing UNSUBACK from the server + auto onUnSubAck = [&unsubscribeFinishedPromise](int error_code, std::shared_ptr unsuback) + { + if (error_code != 0) + { + fprintf( + stdout, + "Unsubscription failed with error code: (%d)%s\n", + error_code, + aws_error_debug_str(error_code)); + } + if (unsuback != nullptr) + { + for (Mqtt5::UnSubAckReasonCode reasonCode : unsuback->getReasonCodes()) + { + fprintf(stdout, "Unsubscribed with reason code: %d\n", reasonCode); + } + } + + unsubscribeFinishedPromise.set_value(); + }; + + // Create an unsubscribe packet + std::shared_ptr unsub = + Aws::Crt::MakeShared(Aws::Crt::DefaultAllocatorImplementation()); + unsub->WithTopicFilter(cmdData.topic); + + // Unsubscribe + if (client->Unsubscribe(unsub, onUnSubAck)) + { + // Wait for unsubscription to finish + unsubscribeFinishedPromise.get_future().wait(); + } + } + + fprintf(stdout, "==== Stopping Client ====\n"); + /* Stop the client. Instructs the client to disconnect and remain in a disconnected state. */ + if (client->Stop()) + { + stoppedPromise.get_future().wait(); + fprintf(stdout, "==== Client Stopped! ====\n"); + } + exit(0); +} diff --git a/samples/mqtt/mqtt5_custom_auth_signed/CMakeLists.txt b/samples/mqtt/mqtt5_custom_auth_signed/CMakeLists.txt new file mode 100644 index 0000000000..30461f8e58 --- /dev/null +++ b/samples/mqtt/mqtt5_custom_auth_signed/CMakeLists.txt @@ -0,0 +1,27 @@ +cmake_minimum_required(VERSION 3.9...3.31) +# note: cxx-17 requires cmake 3.8, cxx-20 requires cmake 3.12 +project(mqtt5_custom_auth_signed CXX) + +file(GLOB SRC_FILES + "*.cpp" +) + +add_executable(${PROJECT_NAME} ${SRC_FILES}) + +set_target_properties(${PROJECT_NAME} PROPERTIES + CXX_STANDARD 14) + +#set warnings +if (MSVC) + target_compile_options(${PROJECT_NAME} PRIVATE /W4 /WX /wd4068) +else () + target_compile_options(${PROJECT_NAME} PRIVATE -Wall -Wno-long-long -pedantic -Werror) +endif () + +find_package(aws-crt-cpp REQUIRED) + +include(AwsSanitizers) +enable_language(C) +aws_add_sanitizers(${PROJECT_NAME}) + +target_link_libraries(${PROJECT_NAME} PRIVATE AWS::aws-crt-cpp) diff --git a/samples/mqtt/mqtt5_custom_auth_signed/README.md b/samples/mqtt/mqtt5_custom_auth_signed/README.md new file mode 100644 index 0000000000..52210855f8 --- /dev/null +++ b/samples/mqtt/mqtt5_custom_auth_signed/README.md @@ -0,0 +1,125 @@ +# MQTT5 Custom Authorizer Signed PubSub + +[**Return to main sample list**](../../README.md) + +*__Jump To:__* +* [Introduction](#introduction) +* [Requirements](#requirements) +* [How To Build](#how-to-build) +* [How To Run](#how-to-run) +* [Additional Information](#additional-information) + +## Introduction +The Custom Authorizer Signed sample illustrate how to connect to the [AWS IoT Message Broker](https://docs.aws.amazon.com/iot/latest/developerguide/iot-message-broker.html) with the MQTT5 Client by authenticating with a signed [Custom Authorizer Lambda Function](https://docs.aws.amazon.com/iot/latest/developerguide/custom-auth-tutorial.html) + +You can read more about MQTT5 for the CPP IoT Device SDK V2 in the [MQTT5 user guide](../../../documents/MQTT5_Userguide.md). + +## Requirements + +You will need to setup your Custom Authorizer so the Lambda function returns a policy document. See [this page on the documentation](https://docs.aws.amazon.com/iot/latest/developerguide/config-custom-auth.html) for more details and example return result. You can customize this lambda function as needed for your application to provide your own security measures based on the needs of your application. + +The policy [Policy](https://docs.aws.amazon.com/iot/latest/developerguide/iot-policies.html) provided by your Custom Authorizer Lambda must provide iot connect, subscribe, publish, and receive privileges for this sample to run successfully. + +Below is a sample policy that provides the necessary privileges. + +
+(see sample policy) +
+{
+  "Version": "2012-10-17",
+  "Statement": [
+    {
+      "Effect": "Allow",
+      "Action": [
+        "iot:Publish",
+        "iot:Receive"
+      ],
+      "Resource": [
+        "arn:aws:iot:region:account:topic/test/topic"
+      ]
+    },
+    {
+      "Effect": "Allow",
+      "Action": [
+        "iot:Subscribe"
+      ],
+      "Resource": [
+        "arn:aws:iot:region:account:topicfilter/test/topic"
+      ]
+    },
+    {
+      "Effect": "Allow",
+      "Action": [
+        "iot:Connect"
+      ],
+      "Resource": [
+        "arn:aws:iot:region:account:client/mqtt5-sample-*"
+      ]
+    }
+  ]
+}
+
+ +Replace with the following with the data from your AWS account: +* ``: The AWS IoT Core region where you created your AWS IoT Core thing you wish to use with this sample. For example `us-east-1`. +* ``: Your AWS IoT Core account ID. This is the set of numbers in the top right next to your AWS account name when using the AWS IoT Core website. + +Note that in a real application, you may want to avoid the use of wildcards in your ClientID or use them selectively. Please follow best practices when working with AWS on production applications using the SDK. Also, for the purposes of this sample, please make sure your policy allows a client ID of `mqtt5-sample-*` to connect or use `--client_id ` to send the client ID your policy supports. + +
+ +## How to build + +To build the sample, change directory into the samples, and run the cmake commands +```sh +cd samples/mqtt/mqtt5_custom_auth_signed/ +# If you followed the SDK build instruction, you would use the path to `sdk-workspace` folder for `CMAKE_PREFIX_PATH` here +cmake -B build -S . -DCMAKE_PREFIX_PATH="" -DCMAKE_BUILD_TYPE="Debug" . +cmake --build build --config "Debug" +``` + +## How to run + +To Run this sample from the `samples\mqtt\mqtt5_custom_auth_signed` folder, use the following command: + +```sh +# For a signed custom authorizer +./mqtt5_custom_auth_signed \ + --endpoint \ + --authorizer_name \ + --auth_token_key_name \ + --auth_token_key_value \ + --auth_signature \ + --auth_username \ + --auth_password + +``` + +If you would like to see what optional arguments are available, use the `--help` argument: +```sh +./mqtt5_custom_auth_signed --help +``` + +will result in the following output: +``` +MQTT5 Signed Custom Authorizer Sample +options: + --help show this help message and exit +required arguments: + --endpoint IoT endpoint hostname + --authorizer_name The name of the custom authorizer to connect to invoke + --auth_signature Custom authorizer signature + --auth_token_key_name Authorizer token key name + --auth_token_key_value Authorizer token key value + --auth_username The name to send when connecting through the custom authorizer + --auth_password The password to send when connecting through a custom authorizer +optional arguments: + --client_id Client ID (default: mqtt5-sample-) + --topic Topic (default: test/topic) + --message Message payload (default: Hello from mqtt5 sample) + --count Messages to publish (0 = infinite) (default: 5) +``` +The sample will not run without the required arguments and will notify you of missing arguments. + +## Additional Information +Additional help with the MQTT5 Client can be found in the [MQTT5 Userguide](../../../documents/MQTT5_Userguide.md). This guide will provide more details on MQTT5 [operations](../../../documents/MQTT5_Userguide.md#client-operations), [lifecycle events](../../documents/MQTT5_Userguide.md#client-lifecycle-management), [connection methods](../../../documents/MQTT5_Userguide.md#connecting-to-aws-iot-core), and other useful information. diff --git a/samples/mqtt/mqtt5_custom_auth_signed/main.cpp b/samples/mqtt/mqtt5_custom_auth_signed/main.cpp new file mode 100644 index 0000000000..034b3e5fde --- /dev/null +++ b/samples/mqtt/mqtt5_custom_auth_signed/main.cpp @@ -0,0 +1,423 @@ +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ +#include +#include +#include +#include + +#include + +using namespace Aws::Crt; + +/* --------------------------------- ARGUMENT PARSING ----------------------------------------- */ +struct CmdArgs +{ + String endpoint; + String authorizerName; + String authSignature; + String authTokenKeyName; + String authTokenKeyValue; + String authUsername; + String authPassword; + String clientId; + String topic = "test/topic"; + String message = "Hello from mqtt5 sample"; + uint32_t count = 5; +}; + +void printHelp() +{ + printf("MQTT5 Signed Custom Authorizer Sample\n"); + printf("options:\n"); + printf(" --help show this help message and exit\n"); + printf("required arguments:\n"); + printf(" --endpoint IoT endpoint hostname\n"); + printf(" --authorizer_name The name of the custom authorizer to connect to invoke\n"); + printf(" --auth_signature Custom authorizer signature\n"); + printf(" --auth_token_key_name Authorizer token key name\n"); + printf(" --auth_token_key_value Authorizer token key value\n"); + printf(" --auth_username The name to send when connecting through the custom authorizer\n"); + printf(" --auth_password The password to send when connecting through a custom authorizer\n"); + printf("optional arguments:\n"); + printf(" --client_id Client ID (default: mqtt5-sample-)\n"); + printf(" --topic Topic (default: test/topic)\n"); + printf(" --message Message payload (default: Hello from mqtt5 sample)\n"); + printf(" --count Messages to publish (0 = infinite) (default: 5)\n"); +} + +CmdArgs parseArgs(int argc, char *argv[]) +{ + CmdArgs args; + for (int i = 1; i < argc; i++) + { + if (strcmp(argv[i], "--help") == 0) + { + printHelp(); + exit(0); + } + else if (i < argc - 1) + { + if (strcmp(argv[i], "--endpoint") == 0) + { + args.endpoint = argv[++i]; + } + else if (strcmp(argv[i], "--authorizer_name") == 0) + { + args.authorizerName = argv[++i]; + } + else if (strcmp(argv[i], "--auth_signature") == 0) + { + args.authSignature = argv[++i]; + } + else if (strcmp(argv[i], "--auth_token_key_name") == 0) + { + args.authTokenKeyName = argv[++i]; + } + else if (strcmp(argv[i], "--auth_token_key_value") == 0) + { + args.authTokenKeyValue = argv[++i]; + } + else if (strcmp(argv[i], "--auth_username") == 0) + { + args.authUsername = argv[++i]; + } + else if (strcmp(argv[i], "--auth_password") == 0) + { + args.authPassword = argv[++i]; + } + else if (strcmp(argv[i], "--client_id") == 0) + { + args.clientId = argv[++i]; + } + else if (strcmp(argv[i], "--topic") == 0) + { + args.topic = argv[++i]; + } + else if (strcmp(argv[i], "--message") == 0) + { + args.message = argv[++i]; + } + else if (strcmp(argv[i], "--count") == 0) + { + args.count = atoi(argv[++i]); + } + else + { + fprintf(stderr, "Unknown argument: %s\n", argv[i]); + printHelp(); + exit(1); + } + } + } + if (args.endpoint.empty() || args.authorizerName.empty() || args.authSignature.empty() || + args.authTokenKeyName.empty() || args.authTokenKeyValue.empty() || args.authUsername.empty() || + args.authPassword.empty()) + { + fprintf( + stderr, + "Error: --endpoint, --authorizer_name, --auth_signature, --auth_token_key_name, --auth_token_key_value, " + "--auth_username, and --auth_password are required\n"); + printHelp(); + exit(1); + } + if (args.clientId.empty()) + { + args.clientId = String("mqtt5-sample-") + UUID().ToString().substr(0, 8); + } + return args; +} +/* --------------------------------- ARGUMENT PARSING END ----------------------------------------- */ + +int main(int argc, char *argv[]) +{ + // Parse command line arguments + CmdArgs cmdData = parseArgs(argc, argv); + + // Variables needed for the sample + std::mutex receiveMutex; + std::condition_variable receiveSignal; + uint32_t receivedCount = 0; + std::promise connectionPromise; + std::promise stoppedPromise; + std::promise disconnectPromise; + std::promise subscribeSuccess; + std::promise unsubscribeFinishedPromise; + + /* Do the global initialization for the API. */ + ApiHandle apiHandle; + + printf("\n==== Starting MQTT5 Custom Auth Signed PubSub Sample ====\n\n"); + + /** + * Create MQTT5 client builder using custom authorizer, + * The builder will be used to create the final client + */ + + // Setup custom authorization config + Aws::Iot::Mqtt5CustomAuthConfig customAuth; + customAuth.WithAuthorizerName(cmdData.authorizerName.c_str()); + customAuth.WithUsername(cmdData.authUsername.c_str()); + customAuth.WithPassword(ByteCursorFromCString(cmdData.authPassword.c_str())); + customAuth.WithTokenSignature(cmdData.authSignature.c_str()); + customAuth.WithTokenKeyName(cmdData.authTokenKeyName.c_str()); + customAuth.WithTokenValue(cmdData.authTokenKeyValue.c_str()); + + auto builder = std::unique_ptr( + Aws::Iot::Mqtt5ClientBuilder::NewMqtt5ClientBuilderWithCustomAuthorizer(cmdData.endpoint, customAuth, DefaultAllocatorImplementation())); + + // Check if the builder setup correctly. + if (builder == nullptr) + { + printf( + "Failed to setup Mqtt5 client builder with error code %d: %s\n", + LastError(), + ErrorDebugString(LastError())); + exit(1); + } + + // Setup connection options + std::shared_ptr connectOptions = + Aws::Crt::MakeShared(Aws::Crt::DefaultAllocatorImplementation()); + connectOptions->WithClientId(cmdData.clientId); + builder->WithConnectOptions(connectOptions); + + /* Setup lifecycle callbacks */ + // Callback when any publish is received + builder->WithPublishReceivedCallback( + [&receiveMutex, &receivedCount, &receiveSignal](const Mqtt5::PublishReceivedEventData &eventData) + { + if (eventData.publishPacket == nullptr) + return; + + std::lock_guard lock(receiveMutex); + ++receivedCount; + fprintf(stdout, "==== Received message from topic '%s': ", eventData.publishPacket->getTopic().c_str()); + fwrite(eventData.publishPacket->getPayload().ptr, 1, eventData.publishPacket->getPayload().len, stdout); + fprintf(stdout, " ====\n"); + + receiveSignal.notify_all(); + }); + + // Callback for the lifecycle event the client Stopped + builder->WithClientStoppedCallback( + [&stoppedPromise](const Mqtt5::OnStoppedEventData &) + { + fprintf(stdout, "Lifecycle Stopped.\n"); + stoppedPromise.set_value(); + }); + + // Callback for lifecycle event Attempting Connect + builder->WithClientAttemptingConnectCallback( + [&cmdData](const Mqtt5::OnAttemptingConnectEventData &) + { + fprintf( + stdout, + "Lifecycle Connection Attempt\nConnecting to endpoint:'%s' with client ID '%s'\n", + cmdData.endpoint.c_str(), + cmdData.clientId.c_str()); + }); + + // Callback for the lifecycle event Connection Success + builder->WithClientConnectionSuccessCallback( + [&connectionPromise](const Mqtt5::OnConnectionSuccessEventData &eventData) + { + fprintf( + stdout, + "Lifecycle Connection Success with reason code: %d\n", + eventData.connAckPacket->getReasonCode()); + connectionPromise.set_value(true); + }); + + // Callback for the lifecycle event Connection Failure + builder->WithClientConnectionFailureCallback( + [&connectionPromise](const Mqtt5::OnConnectionFailureEventData &eventData) + { + fprintf(stdout, "Lifecycle Connection Failure with error: %s.\n", aws_error_debug_str(eventData.errorCode)); + connectionPromise.set_value(false); + }); + + // Callback for the lifecycle event Connection get disconnected + builder->WithClientDisconnectionCallback( + [&disconnectPromise](const Mqtt5::OnDisconnectionEventData &eventData) + { + fprintf(stdout, "Lifecycle Disconnected.\n"); + if (eventData.disconnectPacket != nullptr) + { + Mqtt5::DisconnectReasonCode reason_code = eventData.disconnectPacket->getReasonCode(); + fprintf(stdout, "Disconnection packet code: %d.\n", reason_code); + fprintf(stdout, "Disconnection packet reason: %s.\n", aws_error_debug_str(reason_code)); + } + disconnectPromise.set_value(); + }); + + /* Create Mqtt5Client from the builder */ + fprintf(stdout, "==== Creating MQTT5 Client ====\n"); + std::shared_ptr client = builder->Build(); + + if (client == nullptr) + { + fprintf( + stdout, "Failed to init Mqtt5Client with error code %d: %s\n", LastError(), ErrorDebugString(LastError())); + exit(1); + } + + /** + * Start the client, instructing the client to desire a connected state. The client will try to + * establish a connection with the provided settings. If the client is disconnected while in this + * state it will attempt to reconnect automatically. + */ + fprintf(stdout, "==== Starting client ====\n"); + client->Start(); + + // We await the `ClientConnectionSuccessCallback` callback to be invoked. + if (connectionPromise.get_future().get()) + { + /** + * Subscribe + */ + // Setup the callback that will be triggered on receiveing SUBACK from the server + fprintf(stdout, "==== Subscribing to topic '%s' ====\n", cmdData.topic.c_str()); + auto onSubAck = [&subscribeSuccess](int error_code, std::shared_ptr suback) + { + if (error_code != 0) + { + fprintf( + stdout, + "Subscription failed with error code: (%d)%s\n", + error_code, + aws_error_debug_str(error_code)); + } + if (suback != nullptr) + { + for (Mqtt5::SubAckReasonCode reasonCode : suback->getReasonCodes()) + { + fprintf(stdout, "Suback received with reason code: %d\n", reasonCode); + } + } + subscribeSuccess.set_value(); + }; + + // Create a subscription object, and add it to a subscribe packet + Mqtt5::Subscription subscription(cmdData.topic, Mqtt5::QOS::AWS_MQTT5_QOS_AT_LEAST_ONCE); + subscription.WithNoLocal(false); + std::shared_ptr subPacket = + Aws::Crt::MakeShared(Aws::Crt::DefaultAllocatorImplementation()); + subPacket->WithSubscription(std::move(subscription)); + + // Subscribe & wait for the subscription to complete + if (client->Subscribe(subPacket, onSubAck)) + { + subscribeSuccess.get_future().wait(); + } + + /** + * Publish to the topic + */ + // Setup publish completion callback. The callback will get triggered when the publish completes (when + // the client received the PubAck from the server). + auto onPublishComplete = [](int, std::shared_ptr result) + { + if (!result->wasSuccessful()) + { + fprintf(stdout, "Publish failed with error code: %d\n", result->getErrorCode()); + } + else if (result != nullptr) + { + std::shared_ptr puback = + std::dynamic_pointer_cast(result->getAck()); + + fprintf(stdout, "PubAck received with: %d\n", puback->getReasonCode()); + } + }; + + if (cmdData.count == 0) + { + fprintf(stdout, "==== Sending messages until program killed ====\n"); + } + else + { + fprintf(stdout, "==== Sending %d message(s) ====\n", cmdData.count); + } + + uint32_t publishedCount = 0; + while (publishedCount < cmdData.count || cmdData.count == 0) + { + // Add \" to 'JSON-ify' the message + String message = "\"" + cmdData.message + "[" + std::to_string(publishedCount + 1).c_str() + "]\""; + ByteCursor payload = ByteCursorFromString(message); + + fprintf(stdout, "Publishing message to topic '%s': %s\n", cmdData.topic.c_str(), message.c_str()); + + // Create a publish packet + std::shared_ptr publish = Aws::Crt::MakeShared( + Aws::Crt::DefaultAllocatorImplementation(), + cmdData.topic, + payload, + Mqtt5::QOS::AWS_MQTT5_QOS_AT_LEAST_ONCE); + // Publish + if (client->Publish(publish, onPublishComplete)) + { + ++publishedCount; + } + + // Sleep between publishes to avoid flooding the server + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + } + + // Wait to receive all the messages we sent. + { + std::unique_lock receivedLock(receiveMutex); + receiveSignal.wait(receivedLock, [&] { return receivedCount >= cmdData.count; }); + } + fprintf(stdout, "%d message(s) received.\n", receivedCount); + + /** + * Unsubscribe from the topic. + */ + fprintf(stdout, "==== Unsubscribing from topic '%s' ====\n", cmdData.topic.c_str()); + // Setup the callback that will be triggered on receiveing UNSUBACK from the server + auto onUnSubAck = [&unsubscribeFinishedPromise](int error_code, std::shared_ptr unsuback) + { + if (error_code != 0) + { + fprintf( + stdout, + "Unsubscription failed with error code: (%d)%s\n", + error_code, + aws_error_debug_str(error_code)); + } + if (unsuback != nullptr) + { + for (Mqtt5::UnSubAckReasonCode reasonCode : unsuback->getReasonCodes()) + { + fprintf(stdout, "Unsubscribed with reason code: %d\n", reasonCode); + } + } + + unsubscribeFinishedPromise.set_value(); + }; + + // Create an unsubscribe packet + std::shared_ptr unsub = + Aws::Crt::MakeShared(Aws::Crt::DefaultAllocatorImplementation()); + unsub->WithTopicFilter(cmdData.topic); + + // Unsubscribe + if (client->Unsubscribe(unsub, onUnSubAck)) + { + // Wait for unsubscription to finish + unsubscribeFinishedPromise.get_future().wait(); + } + } + + fprintf(stdout, "==== Stopping Client ====\n"); + /* Stop the client. Instructs the client to disconnect and remain in a disconnected state. */ + if (client->Stop()) + { + stoppedPromise.get_future().wait(); + fprintf(stdout, "==== Client Stopped! ====\n"); + } + exit(0); +} \ No newline at end of file diff --git a/samples/mqtt/mqtt5_custom_auth_unsigned/CMakeLists.txt b/samples/mqtt/mqtt5_custom_auth_unsigned/CMakeLists.txt new file mode 100644 index 0000000000..bb6889d803 --- /dev/null +++ b/samples/mqtt/mqtt5_custom_auth_unsigned/CMakeLists.txt @@ -0,0 +1,27 @@ +cmake_minimum_required(VERSION 3.9...3.31) +# note: cxx-17 requires cmake 3.8, cxx-20 requires cmake 3.12 +project(mqtt5_custom_auth_unsigned CXX) + +file(GLOB SRC_FILES + "*.cpp" +) + +add_executable(${PROJECT_NAME} ${SRC_FILES}) + +set_target_properties(${PROJECT_NAME} PROPERTIES + CXX_STANDARD 14) + +#set warnings +if (MSVC) + target_compile_options(${PROJECT_NAME} PRIVATE /W4 /WX /wd4068) +else () + target_compile_options(${PROJECT_NAME} PRIVATE -Wall -Wno-long-long -pedantic -Werror) +endif () + +find_package(aws-crt-cpp REQUIRED) + +include(AwsSanitizers) +enable_language(C) +aws_add_sanitizers(${PROJECT_NAME}) + +target_link_libraries(${PROJECT_NAME} PRIVATE AWS::aws-crt-cpp) diff --git a/samples/mqtt/mqtt5_custom_auth_unsigned/README.md b/samples/mqtt/mqtt5_custom_auth_unsigned/README.md new file mode 100644 index 0000000000..4a49571ba5 --- /dev/null +++ b/samples/mqtt/mqtt5_custom_auth_unsigned/README.md @@ -0,0 +1,118 @@ +# MQTT5 Custom Authorizer Unsigned PubSub + +[**Return to main sample list**](../../README.md) + +*__Jump To:__* +* [Introduction](#introduction) +* [Requirements](#requirements) +* [How To Build](#how-to-build) +* [How To Run](#how-to-run) +* [Additional Information](#additional-information) + +## Introduction +The Custom Authorizer Unsigned sample illustrate how to connect to the [AWS IoT Message Broker](https://docs.aws.amazon.com/iot/latest/developerguide/iot-message-broker.html) with the MQTT5 Client by authenticating with a unsigned [Custom Authorizer Lambda Function](https://docs.aws.amazon.com/iot/latest/developerguide/custom-auth-tutorial.html) + +You can read more about MQTT5 for the CPP IoT Device SDK V2 in the [MQTT5 user guide](../../../documents/MQTT5_Userguide.md). + +## Requirements + +You will need to setup your Custom Authorizer so the Lambda function returns a policy document. See [this page on the documentation](https://docs.aws.amazon.com/iot/latest/developerguide/config-custom-auth.html) for more details and example return result. You can customize this lambda function as needed for your application to provide your own security measures based on the needs of your application. + +The policy [Policy](https://docs.aws.amazon.com/iot/latest/developerguide/iot-policies.html) provided by your Custom Authorizer Lambda must provide iot connect, subscribe, publish, and receive privileges for this sample to run successfully. + +Below is a sample policy that provides the necessary privileges. + +
+(see sample policy) +
+{
+  "Version": "2012-10-17",
+  "Statement": [
+    {
+      "Effect": "Allow",
+      "Action": [
+        "iot:Publish",
+        "iot:Receive"
+      ],
+      "Resource": [
+        "arn:aws:iot:region:account:topic/test/topic"
+      ]
+    },
+    {
+      "Effect": "Allow",
+      "Action": [
+        "iot:Subscribe"
+      ],
+      "Resource": [
+        "arn:aws:iot:region:account:topicfilter/test/topic"
+      ]
+    },
+    {
+      "Effect": "Allow",
+      "Action": [
+        "iot:Connect"
+      ],
+      "Resource": [
+        "arn:aws:iot:region:account:client/mqtt5-sample-*"
+      ]
+    }
+  ]
+}
+
+ +Replace with the following with the data from your AWS account: +* ``: The AWS IoT Core region where you created your AWS IoT Core thing you wish to use with this sample. For example `us-east-1`. +* ``: Your AWS IoT Core account ID. This is the set of numbers in the top right next to your AWS account name when using the AWS IoT Core website. + +Note that in a real application, you may want to avoid the use of wildcards in your ClientID or use them selectively. Please follow best practices when working with AWS on production applications using the SDK. Also, for the purposes of this sample, please make sure your policy allows a client ID of `mqtt5-sample-*` to connect or use `--client_id ` to send the client ID your policy supports. + +
+ +## How to build + +To build the sample, change directory into the samples, and run the cmake commands +```sh +cd samples/mqtt/mqtt5_custom_auth_unsigned/ +# If you followed the SDK build instruction, you would use the path to `sdk-workspace` folder for `CMAKE_PREFIX_PATH` here +cmake -B build -S . -DCMAKE_PREFIX_PATH="" -DCMAKE_BUILD_TYPE="Debug" . +cmake --build build --config "Debug" +``` + +## How to run + +To Run this sample from the `samples\mqtt\mqtt5_custom_auth_unsigned` folder, use the following command: + +```sh +./mqtt5_custom_auth_unsigned \ + --endpoint \ + --authorizer_name \ + --auth_username \ + --auth_password +``` + +If you would like to see what optional arguments are available, use the `--help` argument: +```sh +./mqtt5_custom_auth_unsigned --help +``` + +will result in the following output: +``` +MQTT5 Unsigned Custom Authorizer Sample +options: + --help show this help message and exit +required arguments: + --endpoint IoT endpoint hostname + --authorizer_name The name of the custom authorizer to connect to invoke + --auth_username The name to send when connecting through the custom authorizer + --auth_password The password to send when connecting through a custom authorizer +optional arguments: + --client_id Client ID (default: mqtt5-sample-) + --topic Topic (default: test/topic) + --message Message payload (default: Hello from mqtt5 sample) + --count Messages to publish (0 = infinite) (default: 5) +``` + +The sample will not run without the required arguments and will notify you of missing arguments. + +## Additional Information +Additional help with the MQTT5 Client can be found in the [MQTT5 Userguide](../../../documents/MQTT5_Userguide.md). This guide will provide more details on MQTT5 [operations](../../../documents/MQTT5_Userguide.md#client-operations), [lifecycle events](../../documents/MQTT5_Userguide.md#client-lifecycle-management), [connection methods](../../../documents/MQTT5_Userguide.md#connecting-to-aws-iot-core), and other useful information. diff --git a/samples/mqtt/mqtt5_custom_auth_unsigned/main.cpp b/samples/mqtt/mqtt5_custom_auth_unsigned/main.cpp new file mode 100644 index 0000000000..c78420735b --- /dev/null +++ b/samples/mqtt/mqtt5_custom_auth_unsigned/main.cpp @@ -0,0 +1,398 @@ +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ +#include +#include +#include +#include + +#include + +using namespace Aws::Crt; + +/* --------------------------------- ARGUMENT PARSING ----------------------------------------- */ +struct CmdArgs +{ + String endpoint; + String authorizerName; + String authUsername; + String authPassword; + String clientId; + String topic = "test/topic"; + String message = "Hello from mqtt5 sample"; + uint32_t count = 5; +}; + +void printHelp() +{ + printf("MQTT5 Unsigned Custom Authorizer Sample\n"); + printf("options:\n"); + printf(" --help show this help message and exit\n"); + printf("required arguments:\n"); + printf(" --endpoint IoT endpoint hostname\n"); + printf(" --authorizer_name The name of the custom authorizer to connect to invoke\n"); + printf(" --auth_username The name to send when connecting through the custom authorizer\n"); + printf(" --auth_password The password to send when connecting through a custom authorizer\n"); + printf("optional arguments:\n"); + printf(" --client_id Client ID (default: mqtt5-sample-)\n"); + printf(" --topic Topic (default: test/topic)\n"); + printf(" --message Message payload (default: Hello from mqtt5 sample)\n"); + printf(" --count Messages to publish (0 = infinite) (default: 5)\n"); +} + +CmdArgs parseArgs(int argc, char *argv[]) +{ + CmdArgs args; + for (int i = 1; i < argc; i++) + { + if (strcmp(argv[i], "--help") == 0) + { + printHelp(); + exit(0); + } + else if (i < argc - 1) + { + if (strcmp(argv[i], "--endpoint") == 0) + { + args.endpoint = argv[++i]; + } + else if (strcmp(argv[i], "--authorizer_name") == 0) + { + args.authorizerName = argv[++i]; + } + else if (strcmp(argv[i], "--auth_username") == 0) + { + args.authUsername = argv[++i]; + } + else if (strcmp(argv[i], "--auth_password") == 0) + { + args.authPassword = argv[++i]; + } + else if (strcmp(argv[i], "--client_id") == 0) + { + args.clientId = argv[++i]; + } + else if (strcmp(argv[i], "--topic") == 0) + { + args.topic = argv[++i]; + } + else if (strcmp(argv[i], "--message") == 0) + { + args.message = argv[++i]; + } + else if (strcmp(argv[i], "--count") == 0) + { + args.count = atoi(argv[++i]); + } + else + { + fprintf(stderr, "Unknown argument: %s\n", argv[i]); + printHelp(); + exit(1); + } + } + } + if (args.endpoint.empty() || args.authorizerName.empty() || args.authUsername.empty() || args.authPassword.empty()) + { + fprintf(stderr, "Error: --endpoint, --authorizer_name, --auth_username, and --auth_password are required\n"); + printHelp(); + exit(1); + } + if (args.clientId.empty()) + { + args.clientId = String("mqtt5-sample-") + UUID().ToString().substr(0, 8); + } + return args; +} +/* --------------------------------- ARGUMENT PARSING END ----------------------------------------- */ + +int main(int argc, char *argv[]) +{ + // Parse command line arguments + CmdArgs cmdData = parseArgs(argc, argv); + + // Variables needed for the sample + std::mutex receiveMutex; + std::condition_variable receiveSignal; + uint32_t receivedCount = 0; + std::promise connectionPromise; + std::promise stoppedPromise; + std::promise disconnectPromise; + std::promise subscribeSuccess; + std::promise unsubscribeFinishedPromise; + + /* Do the global initialization for the API. */ + ApiHandle apiHandle; + + printf("\n==== Starting MQTT5 Custom Auth Signed PubSub Sample ====\n\n"); + + /** + * Create MQTT5 client builder using custom authorizer, + * The builder will be used to create the final client + */ + + // Setup custom authorization config + Aws::Iot::Mqtt5CustomAuthConfig customAuth; + customAuth.WithAuthorizerName(cmdData.authorizerName.c_str()); + customAuth.WithUsername(cmdData.authUsername.c_str()); + customAuth.WithPassword(ByteCursorFromCString(cmdData.authPassword.c_str())); + + auto builder = std::unique_ptr( + Aws::Iot::Mqtt5ClientBuilder::NewMqtt5ClientBuilderWithCustomAuthorizer( + cmdData.endpoint, customAuth, DefaultAllocatorImplementation())); + + // Check if the builder setup correctly. + if (builder == nullptr) + { + printf( + "Failed to setup Mqtt5 client builder with error code %d: %s\n", + LastError(), + ErrorDebugString(LastError())); + exit(1); + } + + // Setup connection options + std::shared_ptr connectOptions = + Aws::Crt::MakeShared(Aws::Crt::DefaultAllocatorImplementation()); + connectOptions->WithClientId(cmdData.clientId); + builder->WithConnectOptions(connectOptions); + + /* Setup lifecycle callbacks */ + // Callback when any publish is received + builder->WithPublishReceivedCallback( + [&receiveMutex, &receivedCount, &receiveSignal](const Mqtt5::PublishReceivedEventData &eventData) + { + if (eventData.publishPacket == nullptr) + return; + + std::lock_guard lock(receiveMutex); + ++receivedCount; + fprintf(stdout, "==== Received message from topic '%s': ", eventData.publishPacket->getTopic().c_str()); + fwrite(eventData.publishPacket->getPayload().ptr, 1, eventData.publishPacket->getPayload().len, stdout); + fprintf(stdout, " ====\n"); + + receiveSignal.notify_all(); + }); + + // Callback for the lifecycle event the client Stopped + builder->WithClientStoppedCallback( + [&stoppedPromise](const Mqtt5::OnStoppedEventData &) + { + fprintf(stdout, "Lifecycle Stopped.\n"); + stoppedPromise.set_value(); + }); + + // Callback for lifecycle event Attempting Connect + builder->WithClientAttemptingConnectCallback( + [&cmdData](const Mqtt5::OnAttemptingConnectEventData &) + { + fprintf( + stdout, + "Lifecycle Connection Attempt\nConnecting to endpoint:'%s' with client ID '%s'\n", + cmdData.endpoint.c_str(), + cmdData.clientId.c_str()); + }); + + // Callback for the lifecycle event Connection Success + builder->WithClientConnectionSuccessCallback( + [&connectionPromise](const Mqtt5::OnConnectionSuccessEventData &eventData) + { + fprintf( + stdout, + "Lifecycle Connection Success with reason code: %d\n", + eventData.connAckPacket->getReasonCode()); + connectionPromise.set_value(true); + }); + + // Callback for the lifecycle event Connection Failure + builder->WithClientConnectionFailureCallback( + [&connectionPromise](const Mqtt5::OnConnectionFailureEventData &eventData) + { + fprintf(stdout, "Lifecycle Connection Failure with error: %s.\n", aws_error_debug_str(eventData.errorCode)); + connectionPromise.set_value(false); + }); + + // Callback for the lifecycle event Connection get disconnected + builder->WithClientDisconnectionCallback( + [&disconnectPromise](const Mqtt5::OnDisconnectionEventData &eventData) + { + fprintf(stdout, "Lifecycle Disconnected.\n"); + if (eventData.disconnectPacket != nullptr) + { + Mqtt5::DisconnectReasonCode reason_code = eventData.disconnectPacket->getReasonCode(); + fprintf(stdout, "Disconnection packet code: %d.\n", reason_code); + fprintf(stdout, "Disconnection packet reason: %s.\n", aws_error_debug_str(reason_code)); + } + disconnectPromise.set_value(); + }); + + /* Create Mqtt5Client from the builder */ + fprintf(stdout, "==== Creating MQTT5 Client ====\n"); + std::shared_ptr client = builder->Build(); + + if (client == nullptr) + { + fprintf( + stdout, "Failed to init Mqtt5Client with error code %d: %s\n", LastError(), ErrorDebugString(LastError())); + exit(1); + } + + /** + * Start the client, instructing the client to desire a connected state. The client will try to + * establish a connection with the provided settings. If the client is disconnected while in this + * state it will attempt to reconnect automatically. + */ + fprintf(stdout, "==== Starting client ====\n"); + client->Start(); + + // We await the `ClientConnectionSuccessCallback` callback to be invoked. + if (connectionPromise.get_future().get()) + { + /** + * Subscribe + */ + // Setup the callback that will be triggered on receiveing SUBACK from the server + fprintf(stdout, "==== Subscribing to topic '%s' ====\n", cmdData.topic.c_str()); + auto onSubAck = [&subscribeSuccess](int error_code, std::shared_ptr suback) + { + if (error_code != 0) + { + fprintf( + stdout, + "Subscription failed with error code: (%d)%s\n", + error_code, + aws_error_debug_str(error_code)); + } + if (suback != nullptr) + { + for (Mqtt5::SubAckReasonCode reasonCode : suback->getReasonCodes()) + { + fprintf(stdout, "Suback received with reason code: %d\n", reasonCode); + } + } + subscribeSuccess.set_value(); + }; + + // Create a subscription object, and add it to a subscribe packet + Mqtt5::Subscription subscription(cmdData.topic, Mqtt5::QOS::AWS_MQTT5_QOS_AT_LEAST_ONCE); + subscription.WithNoLocal(false); + std::shared_ptr subPacket = + Aws::Crt::MakeShared(Aws::Crt::DefaultAllocatorImplementation()); + subPacket->WithSubscription(std::move(subscription)); + + // Subscribe & wait for the subscription to complete + if (client->Subscribe(subPacket, onSubAck)) + { + subscribeSuccess.get_future().wait(); + } + + /** + * Publish to the topic + */ + // Setup publish completion callback. The callback will get triggered when the publish completes (when + // the client received the PubAck from the server). + auto onPublishComplete = [](int, std::shared_ptr result) + { + if (!result->wasSuccessful()) + { + fprintf(stdout, "Publish failed with error code: %d\n", result->getErrorCode()); + } + else if (result != nullptr) + { + std::shared_ptr puback = + std::dynamic_pointer_cast(result->getAck()); + + fprintf(stdout, "PubAck received with: %d\n", puback->getReasonCode()); + } + }; + + if (cmdData.count == 0) + { + fprintf(stdout, "==== Sending messages until program killed ====\n"); + } + else + { + fprintf(stdout, "==== Sending %d message(s) ====\n", cmdData.count); + } + + uint32_t publishedCount = 0; + while (publishedCount < cmdData.count || cmdData.count == 0) + { + // Add \" to 'JSON-ify' the message + String message = "\"" + cmdData.message + "[" + std::to_string(publishedCount + 1).c_str() + "]\""; + ByteCursor payload = ByteCursorFromString(message); + + fprintf(stdout, "Publishing message to topic '%s': %s\n", cmdData.topic.c_str(), message.c_str()); + + // Create a publish packet + std::shared_ptr publish = Aws::Crt::MakeShared( + Aws::Crt::DefaultAllocatorImplementation(), + cmdData.topic, + payload, + Mqtt5::QOS::AWS_MQTT5_QOS_AT_LEAST_ONCE); + // Publish + if (client->Publish(publish, onPublishComplete)) + { + ++publishedCount; + } + + // Sleep between publishes to avoid flooding the server + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + } + + // Wait to receive all the messages we sent. + { + std::unique_lock receivedLock(receiveMutex); + receiveSignal.wait(receivedLock, [&] { return receivedCount >= cmdData.count; }); + } + fprintf(stdout, "%d message(s) received.\n", receivedCount); + + /** + * Unsubscribe from the topic. + */ + fprintf(stdout, "==== Unsubscribing from topic '%s' ====\n", cmdData.topic.c_str()); + // Setup the callback that will be triggered on receiveing UNSUBACK from the server + auto onUnSubAck = [&unsubscribeFinishedPromise](int error_code, std::shared_ptr unsuback) + { + if (error_code != 0) + { + fprintf( + stdout, + "Unsubscription failed with error code: (%d)%s\n", + error_code, + aws_error_debug_str(error_code)); + } + if (unsuback != nullptr) + { + for (Mqtt5::UnSubAckReasonCode reasonCode : unsuback->getReasonCodes()) + { + fprintf(stdout, "Unsubscribed with reason code: %d\n", reasonCode); + } + } + + unsubscribeFinishedPromise.set_value(); + }; + + // Create an unsubscribe packet + std::shared_ptr unsub = + Aws::Crt::MakeShared(Aws::Crt::DefaultAllocatorImplementation()); + unsub->WithTopicFilter(cmdData.topic); + + // Unsubscribe + if (client->Unsubscribe(unsub, onUnSubAck)) + { + // Wait for unsubscription to finish + unsubscribeFinishedPromise.get_future().wait(); + } + } + + fprintf(stdout, "==== Stopping Client ====\n"); + /* Stop the client. Instructs the client to disconnect and remain in a disconnected state. */ + if (client->Stop()) + { + stoppedPromise.get_future().wait(); + fprintf(stdout, "==== Client Stopped! ====\n"); + } + exit(0); +} \ No newline at end of file diff --git a/samples/mqtt/mqtt5_pkcs11/CMakeLists.txt b/samples/mqtt/mqtt5_pkcs11/CMakeLists.txt new file mode 100644 index 0000000000..883e64d96e --- /dev/null +++ b/samples/mqtt/mqtt5_pkcs11/CMakeLists.txt @@ -0,0 +1,27 @@ +cmake_minimum_required(VERSION 3.9...3.31) +# note: cxx-17 requires cmake 3.8, cxx-20 requires cmake 3.12 +project(mqtt5_pkcs11 CXX) + +file(GLOB SRC_FILES + "*.cpp" +) + +add_executable(${PROJECT_NAME} ${SRC_FILES}) + +set_target_properties(${PROJECT_NAME} PROPERTIES + CXX_STANDARD 14) + +#set warnings +if (MSVC) + target_compile_options(${PROJECT_NAME} PRIVATE /W4 /WX /wd4068) +else () + target_compile_options(${PROJECT_NAME} PRIVATE -Wall -Wno-long-long -pedantic -Werror) +endif () + +find_package(aws-crt-cpp REQUIRED) + +include(AwsSanitizers) +enable_language(C) +aws_add_sanitizers(${PROJECT_NAME}) + +target_link_libraries(${PROJECT_NAME} PRIVATE AWS::aws-crt-cpp) diff --git a/samples/mqtt/mqtt5_pkcs11/README.md b/samples/mqtt/mqtt5_pkcs11/README.md new file mode 100644 index 0000000000..16601b8a28 --- /dev/null +++ b/samples/mqtt/mqtt5_pkcs11/README.md @@ -0,0 +1,182 @@ +# MQTT5 PKCS#11 PubSub + +[**Return to main sample list**](../../README.md) + +*__Jump To:__* +* [Introduction](#introduction) +* [Requirements](#requirements) +* [How To Build](#how-to-build) +* [How To Run](#how-to-run) +* [Run Sample with Soft HSM](#run-sample-with-softhsm) +* [Additional Information](#additional-information) + +## Introduction +This sample is similar to the [MQTT5 X509](../mqtt5_x509/) sample in that it connects via Mutual TLS (mTLS) using a certificate and key file. However, unlike the x509 sample where the certificate and private key file are stored on disk, this sample uses a PKCS#11 compatible smart card or Hardware Security Module (HSM) to store and access the private key file. This adds a layer of security because the private key file is not openly on the computer but instead is hidden securely away behind the PKCS#11 device. + +You can read more about MQTT5 for the CPP IoT Device SDK V2 in the [MQTT5 user guide](../../../documents/MQTT5_Userguide.md). + +## Requirements + +**WARNING: Unix (Linux) only**. Currently, TLS integration with PKCS#11 is only available on Unix devices. + +This sample assumes you have the required AWS IoT resources available. Information about AWS IoT can be found [HERE](https://docs.aws.amazon.com/iot/latest/developerguide/what-is-aws-iot.html) and instructions on creating AWS IoT resources (AWS IoT Policy, Device Certificate, Private Key) can be found [HERE](https://docs.aws.amazon.com/iot/latest/developerguide/create-iot-resources.html). + +Your IoT Core Thing's [Policy](https://docs.aws.amazon.com/iot/latest/developerguide/iot-policies.html) must provide privileges for this sample to connect, subscribe, publish, and receive. Below is a sample policy that can be used on your IoT Core Thing that will allow this sample to run as intended. + +
+(see sample policy) +
+{
+  "Version": "2012-10-17",
+  "Statement": [
+    {
+      "Effect": "Allow",
+      "Action": [
+        "iot:Publish",
+        "iot:Receive"
+      ],
+      "Resource": [
+        "arn:aws:iot:region:account:topic/test/topic"
+      ]
+    },
+    {
+      "Effect": "Allow",
+      "Action": [
+        "iot:Subscribe"
+      ],
+      "Resource": [
+        "arn:aws:iot:region:account:topicfilter/test/topic"
+      ]
+    },
+    {
+      "Effect": "Allow",
+      "Action": [
+        "iot:Connect"
+      ],
+      "Resource": [
+        "arn:aws:iot:region:account:client/mqtt5-sample-*"
+      ]
+    }
+  ]
+}
+
+ +Replace with the following with the data from your AWS account: +* ``: The AWS IoT Core region where you created your AWS IoT Core thing you wish to use with this sample. For example `us-east-1`. +* ``: Your AWS IoT Core account ID. This is the set of numbers in the top right next to your AWS account name when using the AWS IoT Core website. + +Note that in a real application, you may want to avoid the use of wildcards in your ClientID or use them selectively. Please follow best practices when working with AWS on production applications using the SDK. Also, for the purposes of this sample, please make sure your policy allows a client ID of `mqtt5-sample-*` to connect or use `--client_id ` to send the client ID your policy supports. + +
+ +## How to build + +To build the sample, change directory into the samples, and run the cmake commands +```sh +cd samples/mqtt/mqtt5_pkcs11/ +# If you followed the SDK build instruction, you would use the path to `sdk-workspace` folder for `CMAKE_PREFIX_PATH` here +cmake -B build -S . -DCMAKE_PREFIX_PATH="" -DCMAKE_BUILD_TYPE="Debug" . +cmake --build build --config "Debug" +``` + +## How to run + +To Run this sample from the `samples/mqtt/mqtt5_pkcs11` folder, use the following command: + +```sh +./mqtt5_pkcs11 \ + --endpoint \ + --cert \ + --pkcs11_lib \ + --pin \ + --token_label