diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 45d80b617..bab946d6e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -51,7 +51,7 @@ jobs: python -c "from urllib.request import urlretrieve; urlretrieve('${{ env.BUILDER_HOST }}/${{ env.BUILDER_SOURCE }}/${{ env.BUILDER_VERSION }}/builder.pyz?run=${{ env.RUN }}', 'builder.pyz')" python builder.pyz build -p ${{ env.PACKAGE_NAME }} --spec=downstream - name: configure AWS credentials (MQTT5) - uses: aws-actions/configure-aws-credentials@v2 + uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: ${{ env.CI_MQTT5_ROLE }} aws-region: ${{ env.AWS_DEFAULT_REGION }} @@ -102,7 +102,7 @@ jobs: chmod a+x builder ./builder build -p ${{ env.PACKAGE_NAME }} --spec=downstream - name: configure AWS credentials (MQTT5) - uses: aws-actions/configure-aws-credentials@v2 + uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: ${{ env.CI_MQTT5_ROLE }} aws-region: ${{ env.AWS_DEFAULT_REGION }} @@ -150,7 +150,7 @@ jobs: mvn compile mvn install -Dmaven.test.skip - name: configure AWS credentials (MQTT5) - uses: aws-actions/configure-aws-credentials@v2 + uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: ${{ env.CI_MQTT5_ROLE }} aws-region: ${{ env.AWS_DEFAULT_REGION }} @@ -196,7 +196,7 @@ jobs: - name: Mirror ANDROID_HOME → ANDROID_SDK_ROOT run: echo "ANDROID_SDK_ROOT=$ANDROID_HOME" >> "$GITHUB_ENV" - name: Configure AWS credentials for Device Farm - uses: aws-actions/configure-aws-credentials@v2 + uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: ${{ env.CI_ANDROID_DEVICE_TESTING_ROLE }} aws-region: ${{ env.AWS_DEVICE_FARM_REGION }} @@ -284,98 +284,3 @@ jobs: - name: Check for edits to code-generated files run: | ./utils/check_codegen_edits.py - - # Runs the samples and ensures that everything is working - linux-smoke-tests: - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - version: - - 17 - permissions: - id-token: write # This is required for requesting the JWT - steps: - - name: Checkout Sources - uses: actions/checkout@v2 - - name: Setup Java - uses: actions/setup-java@v3.14.1 - with: - distribution: temurin - java-version: ${{ matrix.version }} - cache: maven - - name: Build ${{ env.PACKAGE_NAME }} + consumers - run: | - java -version - mvn install -Dmaven.test.skip - - name: Running samples and service client tests in CI setup - run: | - python3 -m pip install boto3 - sudo apt-get update -y - sudo apt-get install softhsm2 -y - softhsm2-util --version - - name: configure AWS credentials (Fleet provisioning) - uses: aws-actions/configure-aws-credentials@v2 - with: - role-to-assume: ${{ env.CI_FLEET_PROVISIONING_ROLE }} - aws-region: ${{ env.AWS_DEFAULT_REGION }} - - name: run Fleet Provisioning service client test for MQTT311 - working-directory: ./servicetests - run: | - export PYTHONPATH=${{ github.workspace }}/utils - python3 ./test_cases/test_fleet_provisioning.py --thing-name-prefix Fleet_Thing_ --mqtt-version 3 - - name: run Fleet Provisioning service client test for MQTT5 - working-directory: ./servicetests - run: | - export PYTHONPATH=${{ github.workspace }}/utils - python3 ./test_cases/test_fleet_provisioning.py --thing-name-prefix Fleet_Thing_ --mqtt-version 5 - - name: run Fleet Provisioning with CSR service client test for MQTT311 - working-directory: ./servicetests - run: | - export PYTHONPATH=${{ github.workspace }}/utils - python3 ./test_cases/test_fleet_provisioning.py --thing-name-prefix Fleet_Thing_ --mqtt-version 3 --use-csr - - name: run Fleet Provisioning with CSR service client test for MQTT5 - working-directory: ./servicetests - run: | - export PYTHONPATH=${{ github.workspace }}/utils - python3 ./test_cases/test_fleet_provisioning.py --thing-name-prefix Fleet_Thing_ --mqtt-version 5 --use-csr - - name: configure AWS credentials (Shadow) - uses: aws-actions/configure-aws-credentials@v2 - with: - role-to-assume: ${{ env.CI_SHADOW_SERVICE_CLIENT_ROLE }} - aws-region: ${{ env.AWS_DEFAULT_REGION }} - - name: run Shadow service client test for MQTT311 - working-directory: ./servicetests - run: | - export PYTHONPATH=${{ github.workspace }}/utils - python3 ./test_cases/test_shadow_update.py --mqtt-version 3 - - name: run Shadow service client test for MQTT5 - working-directory: ./servicetests - run: | - export PYTHONPATH=${{ github.workspace }}/utils - python3 ./test_cases/test_shadow_update.py --mqtt-version 5 - - name: run Named Shadow service client test for MQTT311 - working-directory: ./servicetests - run: | - export PYTHONPATH=${{ github.workspace }}/utils - python3 ./test_cases/test_shadow_update.py --mqtt-version 3 --use-named-shadow - - name: run Named Shadow service client test for MQTT5 - working-directory: ./servicetests - run: | - export PYTHONPATH=${{ github.workspace }}/utils - python3 ./test_cases/test_shadow_update.py --mqtt-version 5 --use-named-shadow - - name: configure AWS credentials (Jobs) - uses: aws-actions/configure-aws-credentials@v2 - with: - role-to-assume: ${{ env.CI_JOBS_SERVICE_CLIENT_ROLE }} - aws-region: ${{ env.AWS_DEFAULT_REGION }} - - name: run Jobs service client test for MQTT311 - working-directory: ./servicetests - run: | - export PYTHONPATH=${{ github.workspace }}/utils - python3 ./test_cases/test_jobs_execution.py --mqtt-version 3 - - name: run Jobs service client test for MQTT5 - working-directory: ./servicetests - run: | - export PYTHONPATH=${{ github.workspace }}/utils - python3 ./test_cases/test_jobs_execution.py --mqtt-version 5 diff --git a/codebuild/common-linux.sh b/codebuild/common-linux.sh deleted file mode 100755 index 85db5f56d..000000000 --- a/codebuild/common-linux.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/bash - -set -e - -env - -# build java package -cd $CODEBUILD_SRC_DIR - -ulimit -c unlimited -mvn compile diff --git a/codebuild/linux-ci.yml b/codebuild/linux-ci.yml deleted file mode 100644 index b7bec3d97..000000000 --- a/codebuild/linux-ci.yml +++ /dev/null @@ -1,14 +0,0 @@ -version: 0.2 -#this build spec assumes the ubuntu aws/codebuild/java:openjdk-8 image -phases: - install: - runtime-versions: - java: openjdk8 - build: - commands: - - echo Build started on `date` - - $CODEBUILD_SRC_DIR/codebuild/common-linux.sh - post_build: - commands: - - echo Build completed on `date` - diff --git a/codebuild/samples/linux-smoke-tests.yml b/codebuild/samples/linux-smoke-tests.yml deleted file mode 100644 index 2970da458..000000000 --- a/codebuild/samples/linux-smoke-tests.yml +++ /dev/null @@ -1,31 +0,0 @@ -# Assumes are running using the Ubuntu Codebuild standard image -# NOTE: This script assumes that the AWS CLI-V2 is pre-installed! -# - AWS CLI-V2 is a requirement to run this script. -version: 0.2 -phases: - install: - commands: - - sudo add-apt-repository ppa:openjdk-r/ppa - - sudo add-apt-repository ppa:ubuntu-toolchain-r/test - - sudo apt-get update -y - - sudo apt-get install softhsm -y - - echo "\nBuild version data:" - - echo "\nJava Version:"; java -version - - echo "\nMaven Version:"; mvn --version - - echo "\nSoftHSM (PKCS11) version:"; softhsm2-util --version - - echo "\n" - build: - commands: - - echo Build started on `date` - - $CODEBUILD_SRC_DIR/codebuild/samples/setup-linux.sh - - $CODEBUILD_SRC_DIR/codebuild/samples/pubsub-mqtt5-linux.sh - post_build: - commands: - - echo Build completed on `date` - -artifacts: - discard-paths: yes - files: - - "target/surefire-reports/**" - - "hs_err_pid*" - - "core*" diff --git a/codebuild/samples/pubsub-mqtt5-linux.sh b/codebuild/samples/pubsub-mqtt5-linux.sh deleted file mode 100755 index 196ce4acb..000000000 --- a/codebuild/samples/pubsub-mqtt5-linux.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/bin/bash - -set -e -set -o pipefail - -env - -pushd $CODEBUILD_SRC_DIR/samples/Mqtt5/PubSub - -ENDPOINT=$(aws secretsmanager get-secret-value --secret-id "ci/endpoint" --query "SecretString" | cut -f2 -d":" | sed -e 's/[\\\"\}]//g') - -mvn compile - -echo "MQTT5 PubSub test" -mvn exec:java -Dexec.mainClass="mqtt5.pubsub.PubSub" -Daws.crt.ci="True" -Dexec.arguments="--endpoint,$ENDPOINT,--key,/tmp/privatekey.pem,--cert,/tmp/certificate.pem" - -popd diff --git a/codebuild/samples/setup-linux.sh b/codebuild/samples/setup-linux.sh deleted file mode 100755 index 7382cb8f3..000000000 --- a/codebuild/samples/setup-linux.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/bin/bash - -set -e -set -o pipefail - -env - -# build java package -cd $CODEBUILD_SRC_DIR - -ulimit -c unlimited -mvn compile -mvn install -DskipTests=true - -cert=$(aws secretsmanager get-secret-value --secret-id "ci/CodeBuild/cert" --query "SecretString" | cut -f2 -d":" | cut -f2 -d\") && echo -e "$cert" > /tmp/certificate.pem -key=$(aws secretsmanager get-secret-value --secret-id "ci/CodeBuild/key" --query "SecretString" | cut -f2 -d":" | cut -f2 -d\") && echo -e "$key" > /tmp/privatekey.pem -key_p8=$(aws secretsmanager get-secret-value --secret-id "ci/CodeBuild/keyp8" --query "SecretString" | cut -f2 -d":" | cut -f2 -d\") && echo -e "$key_p8" > /tmp/privatekey_p8.pem diff --git a/documents/FAQ.md b/documents/FAQ.md index 71656dece..a91b66d8c 100644 --- a/documents/FAQ.md +++ b/documents/FAQ.md @@ -3,6 +3,7 @@ *__Jump To:__* * [Where should I start](#where-should-i-start) * [How do I enable logging](#how-do-i-enable-logging) +* [How do I get more information from an error code?](#how-do-i-get-more-information-from-an-error-code) * [I keep getting AWS_ERROR_MQTT_UNEXPECTED_HANGUP](#i-keep-getting-aws_error_mqtt_unexpected_hangup) * [I am experiencing deadlocks](#i-am-experiencing-deadlocks) * [How do debug in VSCode?](#how-do-debug-in-vscode) @@ -34,11 +35,24 @@ To enable logging in the samples, you will need to set the following system prop For example, to run `BasicPubSub` with logging you could use the following: ```sh -mvn compile exec:java -pl samples/BasicPubSub -Daws.crt.debugnative=true -Daws.crt.log.level=Debug -Daws.crt.log.destionation=Stdout -Dexec.mainClass=pubsub.PubSub -Dexec.args='--endpoint --cert --key --ca_file ' +mvn compile exec:java -pl samples/Mqtt/Mqtt5X509 -Daws.crt.debugnative=true -Daws.crt.log.level=Debug -Daws.crt.log.destionation=Stdout -Dexec.mainClass=pubsub.PubSub -Dexec.args='--endpoint --cert --key ' ``` 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. +### How do I get more information from an error code? +When error codes are returned from the aws-crt-java they can be translated into human readable errors using the following: + +``` +import software.amazon.awssdk.crt.CRT; + +// Print out the error code name +System.out.println(CRT.awsErrorName(errorCode)); + +// Print out a description of the error code +System.out.println(CRT.awsErrorString(errorCode)); +``` + ### I keep getting AWS_ERROR_MQTT_UNEXPECTED_HANGUP This could be many different things but it most likely is a policy issue. Start with using a super permissive IAM policy called AWSIOTFullAccess which looks like this: @@ -76,11 +90,11 @@ Here is an example launch.json file to run the pubsub sample "configurations": [ { "type": "java", - "name": "PubSub", + "name": "x509", "request": "launch", - "mainClass": "pubsub.PubSub", - "projectName": "BasicPubSub", - "args": "--endpoint -ats.iot..amazonaws.com --ca_file --cert --key --client-id test-client", + "mainClass": "mqtt5x509.Mqtt5X509", + "projectName": "Mqtt5X509", + "args": "--endpoint -ats.iot..amazonaws.com --cert --key --client-id test-client", "console": "externalTerminal" } ] diff --git a/documents/MIGRATION_GUIDE.md b/documents/MIGRATION_GUIDE.md index 46bf5346b..a17d68888 100644 --- a/documents/MIGRATION_GUIDE.md +++ b/documents/MIGRATION_GUIDE.md @@ -966,8 +966,6 @@ method in the `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-java-v2/tree/main/samples/Mqtt5/SharedSubscription) -in the v2 SDK. > [!NOTE] > AWS IoT Core supports Shared Subscriptions for both MQTT3 and MQTT5. For more information, see [Shared Subscriptions](https://docs.aws.amazon.com/iot/latest/developerguide/mqtt.html#mqtt5-shared-subscription) from the AWS IoT Core developer guide. diff --git a/documents/MQTT5_Userguide.md b/documents/MQTT5_Userguide.md index 1b34693a4..794aca993 100644 --- a/documents/MQTT5_Userguide.md +++ b/documents/MQTT5_Userguide.md @@ -18,9 +18,11 @@ * [Websocket Connection with Cognito Authentication Method](#websocket-connection-with-cognito-authentication-method) + [Adding an HTTP Proxy](#adding-an-http-proxy) + [How to create a MQTT5 client](#how-to-create-a-mqtt5-client) + * [Lifecycle Management](#lifecycle-management) + [How to Start and Stop](#how-to-start-and-stop) - + [How to Publish](#how-to-publish) - + [How to Subscribe and Unsubscribe](#how-to-subscribe-and-unsubscribe) + + [Client Operations](#client-operations) + + [Publish](#publish) + + [Subscribe and Unsubscribe](#subscribe-and-unsubscribe) + [MQTT5 Best Practices](#mqtt5-best-practices) # Introduction @@ -351,6 +353,8 @@ SDK Proxy support also includes support for basic authentication and TLS-to-prox Once a MQTT5 client builder has been created, it is ready to make a [MQTT5 client](https://awslabs.github.io/aws-crt-java/software/amazon/awssdk/crt/mqtt5/Mqtt5Client.html). Something important to note is that once a MQTT5 client is built and finalized, the resulting MQTT5 client cannot have its settings modified! Further, modifications to the MQTT5 client builder will not change the settings of already created the MQTT5 clients. Before building a MQTT5 client from a MQTT5 client builder, make sure to have everything fully setup. +### Lifecycle Management + For almost every MQTT5 client, it is extremely important to setup [LifecycleEvents](https://awslabs.github.io/aws-crt-java/software/amazon/awssdk/crt/mqtt5/Mqtt5ClientOptions.LifecycleEvents.html) callbacks. [LifecycleEvents](https://awslabs.github.io/aws-crt-java/software/amazon/awssdk/crt/mqtt5/Mqtt5ClientOptions.LifecycleEvents.html) are invoked when the MQTT5 client connects, fails to connect, disconnects, and is stopped. Without these callbacks setup, it will be incredibly hard to determine the state of the MQTT5 client. To setup [LifecycleEvents](https://awslabs.github.io/aws-crt-java/software/amazon/awssdk/crt/mqtt5/Mqtt5ClientOptions.LifecycleEvents.html) callbacks, see the following code: ~~~ java @@ -482,7 +486,9 @@ client.stop(disconnectBuilder.build()); client.close(); ~~~ -## How to Publish +## Client Operations + +### Publish The [publish](https://awslabs.github.io/aws-crt-java/software/amazon/awssdk/crt/mqtt5/Mqtt5Client.html#publish(software.amazon.awssdk.crt.mqtt5.packets.PublishPacket)) operation takes a description of the PUBLISH packet you wish to send and returns a promise containing a [PublishResult](https://awslabs.github.io/aws-crt-java/software/amazon/awssdk/crt/mqtt5/PublishResult.html). The returned [PublishResult](https://awslabs.github.io/aws-crt-java/software/amazon/awssdk/crt/mqtt5/PublishResult.html) will contain different data depending on the QoS used in the publish. @@ -518,7 +524,7 @@ PublishResult result = published.get(60, TimeUnit.SECONDS); Note that publishes made while a MQTT5 client is disconnected and offline will be put into a queue. Once reconnected, the MQTT5 client will send any publishes made while disconnected and offline automatically. -## How to Subscribe and Unsubscribe +### Subscribe and Unsubscribe The [subscribe](https://awslabs.github.io/aws-crt-java/software/amazon/awssdk/crt/mqtt5/Mqtt5Client.html#subscribe(software.amazon.awssdk.crt.mqtt5.packets.SubscribePacket)) operation takes a description of the [SubscribePacket](https://awslabs.github.io/aws-crt-java/software/amazon/awssdk/crt/mqtt5/packets/SubscribePacket.html) you wish to send and returns a promise that resolves successfully with the corresponding [SubAckPacket](https://awslabs.github.io/aws-crt-java/software/amazon/awssdk/crt/mqtt5/packets/SubAckPacket.html) returned by the broker; the promise is rejected with an error if anything goes wrong before the [SubAckPacket](https://awslabs.github.io/aws-crt-java/software/amazon/awssdk/crt/mqtt5/packets/SubAckPacket.html) is received. You should always check the reason codes of a [SubAckPacket](https://awslabs.github.io/aws-crt-java/software/amazon/awssdk/crt/mqtt5/packets/SubAckPacket.html) completion to determine if the subscribe operation actually succeeded. diff --git a/pom.xml b/pom.xml index 1bb259484..d8dd14a6d 100644 --- a/pom.xml +++ b/pom.xml @@ -9,15 +9,18 @@ sdk samples/BasicPubSub - samples/Greengrass - samples/GreengrassIPC - samples/Provisioning/Basic - samples/Provisioning/Csr - samples/JobsSandbox - samples/CommandsSandbox - samples/ShadowSandbox - samples/Mqtt5/PubSub - samples/Mqtt5/SharedSubscription + samples/Mqtt/Mqtt5X509 + samples/Mqtt/Mqtt5AwsWebsocket + samples/Mqtt/Mqtt5CustomAuthSigned + samples/Mqtt/Mqtt5CustomAuthUnsigned + samples/Mqtt/Mqtt5Pkcs11 + samples/Greengrass/Discovery + samples/Greengrass/GreengrassIPC + samples/ServiceClients/Provisioning/Basic + samples/ServiceClients/Provisioning/Csr + samples/ServiceClients/JobsSandbox + samples/ServiceClients/CommandsSandbox + samples/ServiceClients/ShadowSandbox diff --git a/samples/Android/AndroidKeyChainPubSub/src/main/java/androidkeychainpubsub/AndroidKeyChainPubSub.java b/samples/Android/AndroidKeyChainPubSub/src/main/java/androidkeychainpubsub/AndroidKeyChainPubSub.java index f645bada7..dffb0b398 100644 --- a/samples/Android/AndroidKeyChainPubSub/src/main/java/androidkeychainpubsub/AndroidKeyChainPubSub.java +++ b/samples/Android/AndroidKeyChainPubSub/src/main/java/androidkeychainpubsub/AndroidKeyChainPubSub.java @@ -15,184 +15,250 @@ import software.amazon.awssdk.iot.AndroidKeyChainHandlerBuilder; import java.util.List; +import java.nio.charset.StandardCharsets; +import java.time.Duration; +import java.util.Arrays; +import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import android.content.Context; -import utils.commandlineutils.CommandLineUtils; - public class AndroidKeyChainPubSub { - // When run normally, we want to exit nicely even if something goes wrong - // When run from CI, we want to let an exception escape which in turn causes the - // exec:java task to return a non-zero exit code - static String ciPropValue = System.getProperty("aws.crt.ci"); - static boolean isCI = ciPropValue != null && Boolean.valueOf(ciPropValue); - - static CommandLineUtils cmdUtils; - - /* - * When called during a CI run, throw an exception that will escape and fail the exec:java task - * When called otherwise, print what went wrong (if anything) and just continue (return from main) - */ - static void onApplicationFailure(Throwable cause) { - if (isCI) { - throw new RuntimeException("AndroidKeyChainPubSub execution failure", cause); - } else if (cause != null) { - System.out.println("Exception encountered: " + cause.toString()); - } + // ------------------------- ARGUMENT PARSING ------------------------- + static class Args { + String endpoint; + String keyChainAlias; + String keyPath; + String clientId = "mqtt5-sample-" + UUID.randomUUID().toString().replace("-", "").substring(0, 8); + String topic = "test/topic"; + String message = "Hello from mqtt5 sample"; + int count = 5; } - static final class SampleLifecycleEvents implements Mqtt5ClientOptions.LifecycleEvents { - CompletableFuture connectedFuture = new CompletableFuture<>(); - CompletableFuture stoppedFuture = new CompletableFuture<>(); - - @Override - public void onAttemptingConnect(Mqtt5Client client, OnAttemptingConnectReturn onAttemptingConnectReturn) { - System.out.println("Mqtt5 Client: Attempting connection..."); - } - - @Override - public void onConnectionSuccess(Mqtt5Client client, OnConnectionSuccessReturn onConnectionSuccessReturn) { - System.out.println("Mqtt5 Client: Connection success, client ID: " - + onConnectionSuccessReturn.getNegotiatedSettings().getAssignedClientID()); - connectedFuture.complete(null); - } + private static void printHelpAndExit(int code) { + System.out.println("MQTT5 X509 Sample (mTLS)\n"); + System.out.println("Required:"); + System.out.println(" --endpoint IoT endpoint hostname"); + System.out.println(" --keychain_alias Alias of Private Key and Certificate to access from Android KeyChain"); + System.out.println("\nOptional:"); + System.out.println(" --client_id MQTT client ID (default: generated)"); + System.out.println(" --topic Topic to use (default: test/topic)"); + System.out.println(" --message Message payload (default: \"Hello from mqtt5 sample\")"); + System.out.println(" --count Messages to publish (0 = infinite, default: 5)"); + System.exit(code); + } - @Override - public void onConnectionFailure(Mqtt5Client client, OnConnectionFailureReturn onConnectionFailureReturn) { - String errorString = CRT.awsErrorString(onConnectionFailureReturn.getErrorCode()); - System.out.println("Mqtt5 Client: Connection failed with error: " + errorString); - connectedFuture.completeExceptionally(new Exception("Could not connect: " + errorString)); + private static Args parseArgs(String[] argv) { + if (argv.length == 0 || Arrays.asList(argv).contains("--help")) { + printHelpAndExit(0); } - - @Override - public void onDisconnection(Mqtt5Client client, OnDisconnectionReturn onDisconnectionReturn) { - System.out.println("Mqtt5 Client: Disconnected"); - DisconnectPacket disconnectPacket = onDisconnectionReturn.getDisconnectPacket(); - if (disconnectPacket != null) { - System.out.println("\tDisconnection packet code: " + disconnectPacket.getReasonCode()); - System.out.println("\tDisconnection packet reason: " + disconnectPacket.getReasonString()); + Args a = new Args(); + for (int i = 0; i < argv.length; i++) { + String k = argv[i]; + String v = (i + 1 < argv.length) ? argv[i + 1] : null; + + switch (k) { + case "--endpoint": a.endpoint = v; i++; break; + case "--keychain_alias": a.keyChainAlias = v; i++; break; + case "--key": a.keyPath = v; i++; break; + case "--client_id": a.clientId = v; i++; break; + case "--topic": a.topic = v; i++; break; + case "--message": a.message = v; i++; break; + case "--count": a.count = Integer.parseInt(v); i++; break; + default: + System.err.println("Unknown arg: " + k); + printHelpAndExit(2); } } - - @Override - public void onStopped(Mqtt5Client client, OnStoppedReturn onStoppedReturn) { - System.out.println("Mqtt5 Client: Stopped"); - stoppedFuture.complete(null); + if (a.endpoint == null || a.keyChainAlias == null) { + System.err.println("Missing required arguments."); + printHelpAndExit(2); } + return a; } + // ------------------------- ARGUMENT PARSING END --------------------- - static final class SamplePublishEvents implements Mqtt5ClientOptions.PublishEvents { - CountDownLatch messagesReceived; + public static void main(String[] argv, Context context) { + Args args = parseArgs(argv); - SamplePublishEvents(int messageCount) { - messagesReceived = new CountDownLatch(messageCount); - } + System.out.println("\nStarting Android KeyChain Sample\n"); + final int TIMEOUT_SECONDS = 100; - @Override - public void onMessageReceived(Mqtt5Client client, PublishReturn publishReturn) { - PublishPacket publishPacket = publishReturn.getPublishPacket(); + /* + * Latches for flow control of Sample + */ + CountDownLatch connected = new CountDownLatch(1); + CountDownLatch stopped = new CountDownLatch(1); + CountDownLatch receivedAll = new CountDownLatch(args.count > 0 ? args.count : 1); - System.out.println("Publish received on topic: " + publishPacket.getTopic()); - System.out.println("Message: " + new String(publishPacket.getPayload())); + /* + * Handle MQTT5 Client lifecycle events + */ + Mqtt5ClientOptions.LifecycleEvents lifecycleEvents = new Mqtt5ClientOptions.LifecycleEvents() { + @Override + public void onAttemptingConnect(Mqtt5Client client, OnAttemptingConnectReturn onAttemptingConnectReturn) { + System.out.printf("Lifecycle Connection Attempt%nConnecting to endpoint: '%s' with client ID '%s'%n", + args.endpoint, args.clientId); + } - List packetProperties = publishPacket.getUserProperties(); - if (packetProperties != null) { - for (int i = 0; i < packetProperties.size(); i++) { - UserProperty property = packetProperties.get(i); - System.out.println("\twith UserProperty: (" + property.key + ", " + property.value + ")"); + @Override + public void onConnectionSuccess(Mqtt5Client client, OnConnectionSuccessReturn onConnectionSuccessReturn) { + System.out.println("Lifecycle Connection Success with reason code: " + + onConnectionSuccessReturn.getConnAckPacket().getReasonCode() + "\n"); + connected.countDown(); + } + + @Override + public void onConnectionFailure(Mqtt5Client client, OnConnectionFailureReturn onConnectionFailureReturn) { + System.out.println("Lifecycle Connection Failure with error code: " + + onConnectionFailureReturn.getErrorCode() + " : " + + CRT.awsErrorName(onConnectionFailureReturn.getErrorCode()) + " : " + + CRT.awsErrorString(onConnectionFailureReturn.getErrorCode()) + "\n"); + } + + @Override + public void onDisconnection(Mqtt5Client client, OnDisconnectionReturn onDisconnectionReturn) { + System.out.println("Mqtt5 Client: Disconnected"); + DisconnectPacket disconnectPacket = onDisconnectionReturn.getDisconnectPacket(); + if (disconnectPacket != null) { + System.out.println("\nDisconnection packet code: " + disconnectPacket.getReasonCode() + + "\nDisconnection packet reason: " + disconnectPacket.getReasonString()); } } - messagesReceived.countDown(); - } - } - public static void main(String[] args, Context context) { - /** - * 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. + @Override + public void onStopped(Mqtt5Client client, OnStoppedReturn onStoppedReturn) { + System.out.println("Lifecycle Stopped\n"); + stopped.countDown(); + } + }; + + /* + * Handle Publishes received by the MQTT5 Client */ - CommandLineUtils.SampleCommandLineData cmdData = CommandLineUtils.getInputForIoTSample("AndroidKeyChainPubSub", args); + Mqtt5ClientOptions.PublishEvents publishEvents = new Mqtt5ClientOptions.PublishEvents() { + @Override + public void onMessageReceived(Mqtt5Client client, PublishReturn publishReturn) { + PublishPacket publish = publishReturn.getPublishPacket(); + String payload = publish.getPayload() == null + ? "" + : new String(publish.getPayload(), StandardCharsets.UTF_8); + System.out.printf("==== Received message from topic '%s': %s ====%n%n", + publish.getTopic(), payload); + if (args.count > 0) { + receivedAll.countDown(); + } + } + }; + + Mqtt5Client client; + /* + * AndroidKeyChainHandlerBuilder is used to handle PrivateKey extraction from Android KeyChain. + * If you have a PrivateKey, you may pass it directly into the builder instead of providing a + * context and alias. + */ + AndroidKeyChainHandlerBuilder keyChainHandlerBuilder = + AndroidKeyChainHandlerBuilder.newKeyChainHandlerWithAlias(context, args.keyChainAlias); + + AwsIotMqtt5ClientBuilder builder = AwsIotMqtt5ClientBuilder.newDirectMtlsCustomKeyOperationsBuilder( + args.endpoint, keyChainHandlerBuilder.build()); + builder.withLifeCycleEvents(lifecycleEvents); + builder.withPublishEvents(publishEvents); + builder.withClientId(args.clientId); + client = builder.build(); + // You must call `close()` on AwsIotMqtt5ClientBuilder or it will leak memory! Builder is `AutoClosable` and rely on + // scope-based cleanup via try-with-resources. + builder.close(); + + System.out.println("==== Starting client ===="); + client.start(); try { - SampleLifecycleEvents lifecycleEvents = new SampleLifecycleEvents(); - SamplePublishEvents publishEvents = new SamplePublishEvents(cmdData.input_count); - Mqtt5Client client; - - /* - * AndroidKeyChainHandlerBuilder is used to handle PrivateKey extraction from Android KeyChain. - * If you have a PrivateKey, you may pass it directly into the builder instead of providing a - * context and alias. - */ - AndroidKeyChainHandlerBuilder keyChainHandlerBuilder = - AndroidKeyChainHandlerBuilder.newKeyChainHandlerWithAlias(context, cmdData.input_KeyChainAlias); - - AwsIotMqtt5ClientBuilder builder = AwsIotMqtt5ClientBuilder.newDirectMtlsCustomKeyOperationsBuilder( - cmdData.input_endpoint, keyChainHandlerBuilder.build()); - - ConnectPacket.ConnectPacketBuilder connectProperties = new ConnectPacket.ConnectPacketBuilder(); - connectProperties.withClientId(cmdData.input_clientId); - builder.withConnectProperties(connectProperties); - builder.withLifeCycleEvents(lifecycleEvents); - builder.withPublishEvents(publishEvents); - client = builder.build(); - builder.close(); - - /* Connect */ - client.start(); - try { - lifecycleEvents.connectedFuture.get(60, TimeUnit.SECONDS); - } catch (Exception ex) { - throw new RuntimeException("Exception occurred during connect", ex); + if (!connected.await(TIMEOUT_SECONDS, TimeUnit.SECONDS)) { + throw new RuntimeException("Connection timeout"); } + } catch (InterruptedException ex) { + throw new RuntimeException("Mqtt5 X509: execution failure", ex); + } - /* Subscribe */ - SubscribePacket.SubscribePacketBuilder subscribeBuilder = new SubscribePacket.SubscribePacketBuilder(); - subscribeBuilder.withSubscription("test_topic_android", QOS.AT_LEAST_ONCE, false, false, SubscribePacket.RetainHandlingType.DONT_SEND); - try { - client.subscribe(subscribeBuilder.build()).get(60, TimeUnit.SECONDS); - } catch (Exception ex) { - onApplicationFailure(ex); - } + /* Subscribe */ + System.out.printf("==== Subscribing to topic '%s' ====%n", args.topic); + SubscribePacket subscribePacket = SubscribePacket.of(args.topic, QOS.AT_LEAST_ONCE); + try { + SubAckPacket subAckPacket = client.subscribe(subscribePacket).get(TIMEOUT_SECONDS, TimeUnit.SECONDS); + System.out.println("SubAck received with reason code:" + subAckPacket.getReasonCodes() + "\n"); + } catch (Exception ex) { + throw new RuntimeException("Mqtt5 X509: execution failure", ex); + } - /* Publish */ - PublishPacket.PublishPacketBuilder publishBuilder = new PublishPacket.PublishPacketBuilder(); - publishBuilder.withTopic("test_topic_android").withQOS(QOS.AT_LEAST_ONCE); - int count = 0; + /* Publish */ + if (args.count == 0) { + System.out.println("==== Sending messages until program killed ====\n"); + } else { + System.out.printf("==== Sending %d message(s) ====%n%n", args.count); + } + int publishCount = 1; + while (args.count == 0 || publishCount <= args.count) { + String payload = args.message + " [" + publishCount + "]"; + System.out.printf("Publishing message to topic '%s': %s%n", args.topic, payload); + PublishPacket publishPacket = PublishPacket.of( + args.topic, + QOS.AT_LEAST_ONCE, + payload.getBytes(StandardCharsets.UTF_8)); try { - while (count++ < cmdData.input_count) { - publishBuilder.withPayload(("\"" + cmdData.input_message + ": " + String.valueOf(count) + "\"").getBytes()); - CompletableFuture published = client.publish(publishBuilder.build()); - published.get(60, TimeUnit.SECONDS); - Thread.sleep(1000); - } + PubAckPacket pubAck = client.publish(publishPacket).get(TIMEOUT_SECONDS, TimeUnit.SECONDS).getResultPubAck(); + System.out.println("PubAck received with reason: " + pubAck.getReasonCode() + "\n"); } catch (Exception ex) { - onApplicationFailure(ex); + throw new RuntimeException("Mqtt5 X509: execution failure", ex); } - publishEvents.messagesReceived.await(120, TimeUnit.SECONDS); - - /* Disconnect */ - DisconnectPacket.DisconnectPacketBuilder disconnectBuilder = new DisconnectPacket.DisconnectPacketBuilder(); - disconnectBuilder.withReasonCode(DisconnectPacket.DisconnectReasonCode.NORMAL_DISCONNECTION); - client.stop(disconnectBuilder.build()); try { - lifecycleEvents.stoppedFuture.get(60, TimeUnit.SECONDS); - } catch (Exception ex) { - onApplicationFailure(ex); + Thread.sleep(Duration.ofMillis(1500).toMillis()); + } catch (InterruptedException ex) { + throw new RuntimeException("Mqtt5 X509: execution failure", ex); } + publishCount++; + } + if (args.count > 0) { + long remaining = receivedAll.getCount(); + if (remaining > 0) { + try { + receivedAll.await(TIMEOUT_SECONDS, TimeUnit.SECONDS); + } catch (InterruptedException ex) { + throw new RuntimeException("Mqtt5 X509: execution failure", ex); + } + } + long received = (args.count - receivedAll.getCount()); + System.out.printf("%d message(s) received.%n%n", received); + } - /* Close the client to free memory */ - client.close(); - + // ---------- Unsubscribe ---------- + System.out.printf("==== Unsubscribing from topic '%s' ====%n", args.topic); + UnsubscribePacket unsubscribePacket = UnsubscribePacket.of(args.topic); + try { + UnsubAckPacket unsubAckPacket = client.unsubscribe(unsubscribePacket).get(TIMEOUT_SECONDS, TimeUnit.SECONDS); + System.out.println("UnsubAck received with reason code:" + unsubAckPacket.getReasonCodes() + "\n"); } catch (Exception ex) { - onApplicationFailure(ex); + throw new RuntimeException("Mqtt5 X509: execution failure", ex); } + System.out.println("==== Stopping Client ===="); + client.stop(); + try { + if (!stopped.await(TIMEOUT_SECONDS, TimeUnit.SECONDS)) { + throw new RuntimeException("Stop timeout"); + } + } catch (InterruptedException ex) { + throw new RuntimeException("Mqtt5 X509: execution failure", ex); + } + System.out.println("==== Client Stopped! ===="); + + /* Close the client to free memory */ + client.close(); + CrtResource.waitForNoResources(); System.out.println("Complete!"); - } + } } diff --git a/samples/Android/AndroidKeyChainPubSub/src/main/java/androidkeychainpubsub/pom.xml b/samples/Android/AndroidKeyChainPubSub/src/main/java/androidkeychainpubsub/pom.xml index 35b710f4e..44e8b8063 100644 --- a/samples/Android/AndroidKeyChainPubSub/src/main/java/androidkeychainpubsub/pom.xml +++ b/samples/Android/AndroidKeyChainPubSub/src/main/java/androidkeychainpubsub/pom.xml @@ -48,25 +48,7 @@ androidkeychainpubsub.AndroidKeyChainPubSub - - org.codehaus.mojo - build-helper-maven-plugin - 3.2.0 - - - add-source - generate-sources - - add-source - - - - ../Utils/CommandLineUtils - - - - - + diff --git a/samples/Android/README.md b/samples/Android/README.md index 3b39efaaa..8aa79dcca 100644 --- a/samples/Android/README.md +++ b/samples/Android/README.md @@ -3,7 +3,7 @@ The Android sample builds an app that can be installed and run on an Android Device. The app builds and allows you to run the following [samples](#links-to-individual-sample-readme-files) from aws-iot-device-sdk-java-v2: -* [Mqtt5PubSub](../Mqtt5/PubSub/README.md) +* [Mqtt5PubSub](../Mqtt/Mqtt5X509/README.md) * [KeyChainPubSub](./AndroidKeyChainPubSub/README.md) *__Jump To:__* @@ -26,7 +26,7 @@ files linked below. ### Files required by all samples: * `endpoint.txt` - IoT ATS Endpoint -### Required to run Mqtt5PubSub sample +### Required to run Mqtt5X509 sample * `certificate.pem` - IoT Thing Certificate * `privatekey.pem` - IoT Thing Private Key @@ -40,10 +40,7 @@ files linked below. * `count.txt` - specifies --count CLI argument ### Optional files for all Samples: -* `rootca.pem` - override the default system trust store * `clientId.txt` - specifies --clientId CLI argument -* `port.txt` - specifies --port CLI argument -* `verbosity.txt` - specifies --verbosity CLI argument # Build and install sample app @@ -61,11 +58,21 @@ cd samples/Android/app The following links will provide more details on the individual samples available in the Android sample app. -[**Mqtt5PubSub**](../Mqtt5/PubSub/README.md) +[**Mqtt5PubSub**](../Mqtt/Mqtt5X509/README.md) -[**KeyChainPubSub**](AndroidKeyChainPubSub/README.md) +[**KeyChainPubSub**](./AndroidKeyChainPubSub/README.md) # Trouble Shooting ### Error: The file name must end with .xml -This error typically occurs when non-XML files are placed in the `app/src/main/res/` directory. Android enforces strict rules on what can be included in the `res/` folder. If you're working with test or data files (e.g., .txt in our sample), you **MUST** place them in the `app/src/main/assets/` directory instead. \ No newline at end of file +This error typically occurs when non-XML files are placed in the `app/src/main/res/` directory. Android enforces strict rules on what can be included in the `res/` folder. If you're working with test or data files (e.g., .txt in our sample), you **MUST** place them in the `app/src/main/assets/` directory instead. + +## ⚠️ Usage disclaimer + +These code examples interact with services that may incur charges to your AWS account. For more information, see [AWS Pricing](https://aws.amazon.com/pricing/). + +Additionally, example code might theoretically modify or delete existing AWS resources. As a matter of due diligence, do the following: + +- Be aware of the resources that these examples create or delete. +- Be aware of the costs that might be charged to your account as a result. +- Back up your important data. diff --git a/samples/Android/app/build.gradle b/samples/Android/app/build.gradle index 43199a1c5..d4ff7a11f 100644 --- a/samples/Android/app/build.gradle +++ b/samples/Android/app/build.gradle @@ -51,8 +51,7 @@ android { sourceSets { main { - java.srcDir '../../Utils/CommandLineUtils' - java.srcDir '../../Mqtt5/PubSub/src/main/java' + java.srcDir '../../Mqtt/Mqtt5X509/src/main/java' java.srcDir '../AndroidKeyChainPubSub/src/main/java' java.srcDir 'src/main/java' } diff --git a/samples/Android/app/src/main/AndroidManifest.xml b/samples/Android/app/src/main/AndroidManifest.xml index 50932453f..d80241afb 100644 --- a/samples/Android/app/src/main/AndroidManifest.xml +++ b/samples/Android/app/src/main/AndroidManifest.xml @@ -4,7 +4,8 @@ + android:supportsRtl="true" + android:theme="@style/AppTheme"> SAMPLES = new LinkedHashMap() {{ put("Select a Sample",""); // empty default - put("MQTT5 Publish/Subscribe", "mqtt5.pubsub.PubSub"); + put("MQTT5 X509 Publish/Subscribe", "mqtt5x509.Mqtt5X509"); put("KeyChain Publish/Subscribe", "androidkeychainpubsub.AndroidKeyChainPubSub"); put("KeyChain Alias Permission", "load.privateKey"); }}; @@ -152,17 +152,11 @@ private void loadAssets(){ resourceNames.add("endpoint.txt"); resourceNames.add("privatekey.pem"); resourceNames.add("certificate.pem"); - resourceNames.add("cognitoIdentity.txt"); - resourceNames.add("signingRegion.txt"); resourceNames.add("keychainAlias.txt"); - resourceNames.add("verbosity.txt"); resourceNames.add("clientId.txt"); - resourceNames.add("thingName.txt"); - resourceNames.add("port.txt"); resourceNames.add("topic.txt"); resourceNames.add("message.txt"); resourceNames.add("count.txt"); - resourceNames.add("rootca.pem"); // Copy to cache and store file locations for file assets and contents for .txt assets for (String resourceName : resourceNames) { @@ -222,14 +216,11 @@ private String[] sampleArgs(String sampleClassName){ } // Shared optional arguments - argSetOptional("--verbosity", "verbosity.txt", args); - argSetOptional("--ca_file", "rootca.pem", args); - argSetOptional("--port", "port.txt", args); argSetOptional("--client_id", "clientId.txt", args); // Missing required arguments will return null switch(sampleClassName){ - case "mqtt5.pubsub.PubSub": + case "mqtt5x509.Mqtt5X509": if (!argSetRequired("--cert", "certificate.pem", args) || !argSetRequired("--key", "privatekey.pem", args)) { return null; diff --git a/samples/BasicPubSub/README.md b/samples/BasicPubSub/README.md index 416a56da3..af3d8ee13 100644 --- a/samples/BasicPubSub/README.md +++ b/samples/BasicPubSub/README.md @@ -361,3 +361,13 @@ builder.withHttpProxyOptions(proxyOptions); ~~~ SDK Proxy support also includes support for basic authentication and TLS-to-proxy. SDK proxy support does not include any additional proxy authentication methods (kerberos, NTLM, etc...) nor does it include non-HTTP proxies (SOCKS5, for example). + +## ⚠️ Usage disclaimer + +These code examples interact with services that may incur charges to your AWS account. For more information, see [AWS Pricing](https://aws.amazon.com/pricing/). + +Additionally, example code might theoretically modify or delete existing AWS resources. As a matter of due diligence, do the following: + +- Be aware of the resources that these examples create or delete. +- Be aware of the costs that might be charged to your account as a result. +- Back up your important data. diff --git a/samples/Greengrass/README.md b/samples/Greengrass/Discovery/README.md similarity index 91% rename from samples/Greengrass/README.md rename to samples/Greengrass/Discovery/README.md index d82293fe0..5e33dff50 100644 --- a/samples/Greengrass/README.md +++ b/samples/Greengrass/Discovery/README.md @@ -1,6 +1,6 @@ # Greengrass Discovery -[**Return to main sample list**](../README.md) +[**Return to main sample list**](../../README.md) This sample is intended for use with the following tutorials in the AWS IoT Greengrass documentation: diff --git a/samples/ShadowV2/pom.xml b/samples/Greengrass/Discovery/pom.xml similarity index 87% rename from samples/ShadowV2/pom.xml rename to samples/Greengrass/Discovery/pom.xml index 0de9e038e..a6482318a 100644 --- a/samples/ShadowV2/pom.xml +++ b/samples/Greengrass/Discovery/pom.xml @@ -2,7 +2,7 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> 4.0.0 software.amazon.awssdk.iotdevicesdk - ShadowV2 + GreengrassDiscovery jar 1.0-SNAPSHOT ${project.groupId}:${project.artifactId} @@ -13,13 +13,6 @@ 1.8 UTF-8 - - - commons-cli - commons-cli - 1.9.0 - - latest-release @@ -45,4 +38,5 @@ + diff --git a/samples/Greengrass/src/main/java/greengrass/BasicDiscovery.java b/samples/Greengrass/Discovery/src/main/java/greengrass/BasicDiscovery.java similarity index 60% rename from samples/Greengrass/src/main/java/greengrass/BasicDiscovery.java rename to samples/Greengrass/Discovery/src/main/java/greengrass/BasicDiscovery.java index 4fbe79c27..d6d1b65e0 100644 --- a/samples/Greengrass/src/main/java/greengrass/BasicDiscovery.java +++ b/samples/Greengrass/Discovery/src/main/java/greengrass/BasicDiscovery.java @@ -28,67 +28,104 @@ import static software.amazon.awssdk.iot.discovery.DiscoveryClient.TLS_EXT_ALPN; -import utils.commandlineutils.CommandLineUtils; - public class BasicDiscovery { - // When run normally, we want to exit nicely even if something goes wrong. - // When run from CI, we want to let an exception escape which in turn causes the - // exec:java task to return a non-zero exit code. - static String ciPropValue = System.getProperty("aws.crt.ci"); - static boolean isCI = ciPropValue != null && Boolean.valueOf(ciPropValue); - - // Needed to access command line input data in getClientFromDiscovery - static String input_thingName; - static String input_certPath; - static String input_keyPath; + // ------------------------- ARGUMENT PARSING ------------------------- + static class Args { + String certPath; + String keyPath; + String region; + String thingName; + Boolean printDiscoveryRespOnly = false; + String mode; + String proxyHost; + String caPath; + int proxyPort = 0; + String topic = "test/topic"; + } - static CommandLineUtils cmdUtils; + private static void printHelpAndExit(int code) { + System.out.println("Basic Discovery Sample\n"); + System.out.println("Required:"); + System.out.println(" --cert Path to certificate file (PEM)"); + System.out.println(" --key Path to private key file (PEM)"); + System.out.println(" --region The region to connect through"); + System.out.println(" --thing_name The name assigned to your IoT Thing"); + System.out.println("\nOptional:"); + System.out.println(" --print_discover_resp_only (optional, default='False')"); + System.out.println(" --mode The operation mode can be set to 'subscribe' 'publish' or 'both'(default)"); + System.out.println(" --ca_file Path to optional CA bundle (PEM)"); + System.out.println(" --proxy_host HTTP proxy host"); + System.out.println(" --proxy_port HTTP proxy port"); + System.out.println(" --topic Topic to use (default: test/topic)"); + System.exit(code); + } - public static void main(String[] args) { + private static Args parseArgs(String[] argv) { + if (argv.length == 0 || Arrays.asList(argv).contains("--help")) { + printHelpAndExit(0); + } + Args a = new Args(); + for (int i = 0; i < argv.length; i++) { + String k = argv[i]; + String v = (i + 1 < argv.length) ? argv[i + 1] : null; + + switch (k) { + case "--cert": a.certPath = v; i++; break; + case "--key": a.keyPath = v; i++; break; + case "--region": a.region = v; i++; break; + case "--thing_name": a.thingName = v; i++; break; + case "--print_discover_resp_only": a.printDiscoveryRespOnly = Boolean.valueOf(v); + case "--mode": a.mode = v; i++; break; + case "--proxy_host": a.proxyHost = v; i++; break; + case "--proxy_port": a.proxyPort = Integer.parseInt(v); i++; break; + case "--ca_file": a.caPath = v; i++; break; + case "--topic": a.topic = v; i++; break; + default: + System.err.println("Unknown arg: " + k); + printHelpAndExit(2); + } + } + if (a.certPath == null || a.keyPath == null || a.region == null || a.thingName == null) { + System.err.println("Missing required arguments."); + printHelpAndExit(2); + } + return a; + } + // ------------------------- ARGUMENT PARSING END --------------------- - /* - * 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. - */ - CommandLineUtils.SampleCommandLineData cmdData = CommandLineUtils.getInputForIoTSample("BasicDiscovery", args); + static Args args; - input_thingName = cmdData.input_thingName; - input_certPath = cmdData.input_cert; - input_keyPath = cmdData.input_key; + public static void main(String[] argv) { + args = parseArgs(argv); - try (final TlsContextOptions tlsCtxOptions = TlsContextOptions.createWithMtlsFromPath(cmdData.input_cert, cmdData.input_key)) { + try (final TlsContextOptions tlsCtxOptions = TlsContextOptions.createWithMtlsFromPath(args.certPath, args.keyPath)) { if (TlsContextOptions.isAlpnSupported()) { tlsCtxOptions.withAlpnList(TLS_EXT_ALPN); } - if (cmdData.input_ca != null) { - tlsCtxOptions.overrideDefaultTrustStoreFromPath(null, cmdData.input_ca); + if (args.caPath != null) { + tlsCtxOptions.overrideDefaultTrustStoreFromPath(null, args.caPath); } HttpProxyOptions proxyOptions = null; - if (cmdData.input_proxyHost != null && cmdData.input_proxyPort > 0) { + if (args.proxyHost != null && args.proxyPort > 0) { proxyOptions = new HttpProxyOptions(); - proxyOptions.setHost(cmdData.input_proxyHost); - proxyOptions.setPort(cmdData.input_proxyPort); + proxyOptions.setHost(args.proxyHost); + proxyOptions.setPort(args.proxyPort); } try ( final SocketOptions socketOptions = new SocketOptions(); final DiscoveryClientConfig discoveryClientConfig = - new DiscoveryClientConfig(tlsCtxOptions, socketOptions, cmdData.input_signingRegion, 1, proxyOptions); + new DiscoveryClientConfig(tlsCtxOptions, socketOptions, args.region, 1, proxyOptions); final DiscoveryClient discoveryClient = new DiscoveryClient(discoveryClientConfig)) { - DiscoverResponse response = discoveryClient.discover(input_thingName).get(60, TimeUnit.SECONDS); - if (isCI) { - System.out.println("Received a greengrass discovery result! Not showing result in CI for possible data sensitivity."); - } else { - printGreengrassGroupList(response.getGGGroups(), ""); - } + DiscoverResponse response = discoveryClient.discover(args.thingName).get(60, TimeUnit.SECONDS); + printGreengrassGroupList(response.getGGGroups(), ""); - if (cmdData.inputPrintDiscoverRespOnly == false) { + if (args.printDiscoveryRespOnly == false) { try (final MqttClientConnection connection = getClientFromDiscovery(discoveryClient)) { - if ("subscribe".equals(cmdData.input_mode) || "both".equals(cmdData.input_mode)) { - final CompletableFuture subFuture = connection.subscribe(cmdData.input_topic, QualityOfService.AT_MOST_ONCE, message -> { + if ("subscribe".equals(args.mode) || "both".equals(args.mode)) { + final CompletableFuture subFuture = connection.subscribe(args.topic, QualityOfService.AT_MOST_ONCE, message -> { System.out.println(String.format("Message received on topic %s: %s", message.getTopic(), new String(message.getPayload(), StandardCharsets.UTF_8))); }); @@ -98,8 +135,8 @@ public static void main(String[] args) { final Scanner scanner = new Scanner(System.in); while (true) { String input = null; - if ("publish".equals(cmdData.input_mode) || "both".equals(cmdData.input_mode)) { - System.out.println("Enter the message you want to publish to topic " + cmdData.input_topic + " and press Enter. " + + if ("publish".equals(args.mode) || "both".equals(args.mode)) { + System.out.println("Enter the message you want to publish to topic " + args.topic + " and press Enter. " + "Type 'exit' or 'quit' to exit this program: "); input = scanner.nextLine(); } @@ -109,8 +146,8 @@ public static void main(String[] args) { break; } - if ("publish".equals(cmdData.input_mode) || "both".equals(cmdData.input_mode)) { - final CompletableFuture publishResult = connection.publish(new MqttMessage(cmdData.input_topic, + if ("publish".equals(args.mode) || "both".equals(args.mode)) { + final CompletableFuture publishResult = connection.publish(new MqttMessage(args.topic, input.getBytes(StandardCharsets.UTF_8), QualityOfService.AT_MOST_ONCE, false)); Integer result = publishResult.get(); } @@ -153,15 +190,15 @@ private static void printGreengrassConnectivityList(List conne private static MqttClientConnection getClientFromDiscovery(final DiscoveryClient discoveryClient ) throws ExecutionException, InterruptedException { - final CompletableFuture futureResponse = discoveryClient.discover(input_thingName); + final CompletableFuture futureResponse = discoveryClient.discover(args.thingName); final DiscoverResponse response = futureResponse.get(); if (response.getGGGroups() == null) { - throw new RuntimeException("ThingName " + input_thingName + " does not have a Greengrass group/core configuration"); + throw new RuntimeException("ThingName " + args.thingName + " does not have a Greengrass group/core configuration"); } final Optional groupOpt = response.getGGGroups().stream().findFirst(); if (!groupOpt.isPresent()) { - throw new RuntimeException("ThingName " + input_thingName + " does not have a Greengrass group/core configuration"); + throw new RuntimeException("ThingName " + args.thingName + " does not have a Greengrass group/core configuration"); } final GGGroup group = groupOpt.get(); @@ -174,8 +211,8 @@ private static MqttClientConnection getClientFromDiscovery(final DiscoveryClient System.out.printf("Connecting to group ID %s, with thing arn %s, using endpoint %s:%d%n", group.getGGGroupId(), core.getThingArn(), dnsOrIp, port); - try (final AwsIotMqttConnectionBuilder connectionBuilder = AwsIotMqttConnectionBuilder.newMtlsBuilderFromPath(input_certPath, input_keyPath) - .withClientId(input_thingName) + try (final AwsIotMqttConnectionBuilder connectionBuilder = AwsIotMqttConnectionBuilder.newMtlsBuilderFromPath(args.certPath, args.keyPath) + .withClientId(args.thingName) .withPort(port) .withEndpoint(dnsOrIp) .withConnectionEventCallbacks(new MqttClientConnectionEvents() { @@ -209,6 +246,6 @@ public void onConnectionResumed(boolean sessionPresent) { } } - throw new RuntimeException("ThingName " + input_thingName + " could not connect to the green grass core using any of the endpoint connectivity options"); + throw new RuntimeException("ThingName " + args.thingName + " could not connect to the green grass core using any of the endpoint connectivity options"); } } diff --git a/samples/GreengrassIPC/README.md b/samples/Greengrass/GreengrassIPC/README.md similarity index 98% rename from samples/GreengrassIPC/README.md rename to samples/Greengrass/GreengrassIPC/README.md index 94353ff5f..648592bc9 100644 --- a/samples/GreengrassIPC/README.md +++ b/samples/Greengrass/GreengrassIPC/README.md @@ -1,6 +1,6 @@ # Greengrass IPC -[**Return to main sample list**](../README.md) +[**Return to main sample list**](../../README.md) This sample uses [AWS IoT Greengrass V2](https://docs.aws.amazon.com/greengrass/index.html) to publish messages from the Greengrass device to the AWS IoT MQTT broker. diff --git a/samples/GreengrassIPC/pom.xml b/samples/Greengrass/GreengrassIPC/pom.xml similarity index 100% rename from samples/GreengrassIPC/pom.xml rename to samples/Greengrass/GreengrassIPC/pom.xml diff --git a/samples/GreengrassIPC/src/main/java/greengrass/GreengrassIPC.java b/samples/Greengrass/GreengrassIPC/src/main/java/greengrass/GreengrassIPC.java similarity index 100% rename from samples/GreengrassIPC/src/main/java/greengrass/GreengrassIPC.java rename to samples/Greengrass/GreengrassIPC/src/main/java/greengrass/GreengrassIPC.java diff --git a/samples/Greengrass/pom.xml b/samples/Greengrass/pom.xml deleted file mode 100644 index 47ae593f5..000000000 --- a/samples/Greengrass/pom.xml +++ /dev/null @@ -1,64 +0,0 @@ - - 4.0.0 - software.amazon.awssdk.iotdevicesdk - GreengrassDiscovery - jar - 1.0-SNAPSHOT - ${project.groupId}:${project.artifactId} - Java bindings for the AWS IoT Core Service - https://github.com/awslabs/aws-iot-device-sdk-java-v2 - - 1.8 - 1.8 - UTF-8 - - - - latest-release - - - software.amazon.awssdk.iotdevicesdk - aws-iot-device-sdk - 1.27.4 - - - - - default - - true - - - - software.amazon.awssdk.iotdevicesdk - aws-iot-device-sdk - 1.0.0-SNAPSHOT - - - - - - - - org.codehaus.mojo - build-helper-maven-plugin - 3.2.0 - - - add-source - generate-sources - - add-source - - - - ../Utils/CommandLineUtils - - - - - - - - diff --git a/samples/JavaKeystoreConnect/README.md b/samples/JavaKeystoreConnect/README.md deleted file mode 100644 index f5bf8e61f..000000000 --- a/samples/JavaKeystoreConnect/README.md +++ /dev/null @@ -1,62 +0,0 @@ -# Java Keystore Connect - -[**Return to main sample list**](../README.md) - -This sample is similar to the [Basic Connect](../BasicConnect/README.md) sample, in that it connects via Mutual TLS (mTLS) using a certificate and key file. However, unlike the Basic Connect where the certificate and private key file are stored on disk, this sample uses the Java Keystore to get the certificate and private key files. This adds a layer of security because the private key and certificate files is not just sitting on the computer and instead is hidden securely away in the Java Keystore. - -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. 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:Connect"
-      ],
-      "Resource": [
-        "arn:aws:iot:region:account:client/test-*"
-      ]
-    }
-  ]
-}
-
- -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 - -To run the Java keystore connect sample use the following command: - -```sh -mvn compile exec:java -pl samples/JavaKeystoreConnect -Dexec.mainClass=javakeystoreconnect.JavaKeystoreConnect -Dexec.args='--endpoint --keystore --keystore_password --certificate_alias --certificate_password ' -``` - -### How to setup and run - -To use the certificate and key files provided by AWS IoT Core, you will need to convert them into PKCS#12 format and then import them into your Java keystore. You can convert the certificate and key file to PKCS12 using the following command: - -```sh -openssl pkcs12 -export -in -inkey -out my-pkcs12-key.p12 -name -password pass: -``` - -Once you have a PKCS12 certificate and key, you can import it into a Java keystore using the following: - -```sh -keytool -importkeystore -srckeystore my-pkcs12-key.p12 -destkeystore -srcstoretype pkcs12 -alias -srcstorepass -deststorepass -``` - -Then you can run the sample using the following command: - -```sh -mvn compile exec:java -pl samples/JavaKeystoreConnect -Dexec.mainClass=javakeystoreconnect.JavaKeystoreConnect -Dexec.args='--endpoint --keystore --keystore_password --certificate_alias --certificate_password ' -``` diff --git a/samples/JavaKeystoreConnect/pom.xml b/samples/JavaKeystoreConnect/pom.xml deleted file mode 100644 index baaa544f1..000000000 --- a/samples/JavaKeystoreConnect/pom.xml +++ /dev/null @@ -1,72 +0,0 @@ - - 4.0.0 - software.amazon.awssdk.iotdevicesdk - JavaKeystoreConnect - jar - 1.0-SNAPSHOT - ${project.groupId}:${project.artifactId} - Java bindings for the AWS IoT Core Service - https://github.com/awslabs/aws-iot-device-sdk-java-v2 - - 1.8 - 1.8 - UTF-8 - - - - latest-release - - - software.amazon.awssdk.iotdevicesdk - aws-iot-device-sdk - 1.27.4 - - - - - default - - true - - - - software.amazon.awssdk.iotdevicesdk - aws-iot-device-sdk - 1.0.0-SNAPSHOT - - - - - - - - org.codehaus.mojo - exec-maven-plugin - 1.4.0 - - javakeystoreconnect.JavaKeystoreConnect - - - - org.codehaus.mojo - build-helper-maven-plugin - 3.2.0 - - - add-source - generate-sources - - add-source - - - - ../Utils/CommandLineUtils - - - - - - - - diff --git a/samples/JavaKeystoreConnect/src/main/java/javakeystoreconnect/JavaKeystoreConnect.java b/samples/JavaKeystoreConnect/src/main/java/javakeystoreconnect/JavaKeystoreConnect.java deleted file mode 100644 index fd0b4a3cb..000000000 --- a/samples/JavaKeystoreConnect/src/main/java/javakeystoreconnect/JavaKeystoreConnect.java +++ /dev/null @@ -1,132 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0. - */ - -package javakeystoreconnect; - -import software.amazon.awssdk.crt.CRT; -import software.amazon.awssdk.crt.CrtResource; -import software.amazon.awssdk.crt.CrtRuntimeException; -import software.amazon.awssdk.crt.mqtt.MqttClientConnection; -import software.amazon.awssdk.crt.mqtt.MqttClientConnectionEvents; -import software.amazon.awssdk.crt.http.HttpProxyOptions; -import software.amazon.awssdk.iot.AwsIotMqttConnectionBuilder; - -import java.util.concurrent.ExecutionException; -import java.util.concurrent.CompletableFuture; - -import utils.commandlineutils.CommandLineUtils; - -public class JavaKeystoreConnect { - - static CommandLineUtils cmdUtils; - - /* - * When called during a CI run, throw an exception that will escape and fail the exec:java task - * When called otherwise, print what went wrong (if anything) and just continue (return from main) - */ - static void onApplicationFailure(Throwable cause) { - throw new RuntimeException("JavaKeystoreConnect execution failure", cause); - } - - public static void main(String[] args) { - - /** - * 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. - */ - CommandLineUtils.SampleCommandLineData cmdData = CommandLineUtils.getInputForIoTSample("JavaKeystoreConnect", args); - - MqttClientConnectionEvents callbacks = new MqttClientConnectionEvents() { - @Override - public void onConnectionInterrupted(int errorCode) { - if (errorCode != 0) { - System.out.println("Connection interrupted: " + errorCode + ": " + CRT.awsErrorString(errorCode)); - } - } - - @Override - public void onConnectionResumed(boolean sessionPresent) { - System.out.println("Connection resumed: " + (sessionPresent ? "existing session" : "clean session")); - } - }; - - try { - - /** - * Create the MQTT connection from the builder - */ - java.security.KeyStore keyStore; - try { - keyStore = java.security.KeyStore.getInstance(cmdData.input_keystoreFormat); - } catch (java.security.KeyStoreException ex) { - throw new CrtRuntimeException("Could not get instance of Java keystore with format " + cmdData.input_keystoreFormat); - } - try (java.io.FileInputStream fileInputStream = new java.io.FileInputStream(cmdData.input_keystore)) { - keyStore.load(fileInputStream, cmdData.input_keystorePassword.toCharArray()); - } catch (java.io.FileNotFoundException ex) { - throw new CrtRuntimeException("Could not open Java keystore file"); - } catch (java.io.IOException | java.security.NoSuchAlgorithmException | java.security.cert.CertificateException ex) { - throw new CrtRuntimeException("Could not load Java keystore"); - } - AwsIotMqttConnectionBuilder builder = AwsIotMqttConnectionBuilder.newJavaKeystoreBuilder( - keyStore, - cmdData.input_certificateAlias, - cmdData.input_certificatePassword); - if (cmdData.input_ca != "") { - builder.withCertificateAuthorityFromPath(null, cmdData.input_ca); - } - builder.withConnectionEventCallbacks(callbacks) - .withClientId(cmdData.input_clientId) - .withEndpoint(cmdData.input_endpoint) - .withPort(cmdData.input_port) - .withCleanSession(true) - .withProtocolOperationTimeoutMs(60000); - if (cmdData.input_proxyHost != "" && cmdData.input_proxyPort > 0) { - HttpProxyOptions proxyOptions = new HttpProxyOptions(); - proxyOptions.setHost(cmdData.input_proxyHost); - proxyOptions.setPort(cmdData.input_proxyPort); - builder.withHttpProxyOptions(proxyOptions); - } - MqttClientConnection connection = builder.build(); - builder.close(); - - /** - * Verify the connection was created - */ - if (connection == null) - { - onApplicationFailure(new RuntimeException("MQTT connection creation failed!")); - } - - /** - * Connect and disconnect - */ - CompletableFuture connected = connection.connect(); - try { - boolean sessionPresent = connected.get(); - System.out.println("Connected to " + (!sessionPresent ? "new" : "existing") + " session!"); - } catch (Exception ex) { - throw new RuntimeException("Exception occurred during connect", ex); - } - System.out.println("Disconnecting..."); - CompletableFuture disconnected = connection.disconnect(); - disconnected.get(); - System.out.println("Disconnected."); - - /** - * Close the connection now that it is complete - */ - connection.close(); - - } catch (CrtRuntimeException | InterruptedException | ExecutionException ex) { - onApplicationFailure(ex); - } - - CrtResource.waitForNoResources(); - System.out.println("Complete!"); - } - -} diff --git a/samples/Mqtt/Mqtt5AwsWebsocket/README.md b/samples/Mqtt/Mqtt5AwsWebsocket/README.md new file mode 100644 index 000000000..7106fd0f9 --- /dev/null +++ b/samples/Mqtt/Mqtt5AwsWebsocket/README.md @@ -0,0 +1,118 @@ +# MQTT5 AWS Websocket PubSub + +[**Return to main sample list**](../../README.md) + +*__Jump To:__* +* [Introduction](#introduction) +* [Requirements](#requirements) +* [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. [`DefaultChainCredentialsProvider`](https://github.com/awslabs/aws-crt-java/blob/main/src/main/java/software/amazon/awssdk/crt/auth/credentials/DefaultChainCredentialsProvider.java) is used to source credentials via the default credentials provider chain to sign the websocket handshake. + +You can read more about MQTT5 for the Java IoT Device SDK V2 in the [MQTT5 user guide](../../../documents/MQTT5_Userguide.md). + +## Requirements + +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 run + +To run this sample from the `aws-iot-device-sdk-java-v2` folder use the following command: + +```sh +mvn compile exec:java \ + -pl samples/Mqtt/Mqtt5AwsWebsocket \ + -Dexec.args="\ + --endpoint \ + --signing_region " +``` + +If you would like to see what optional arguments are available, use the `--help` argument: +```sh +mvn compile exec:java \ + -pl samples/Mqtt/Mqtt5AwsWebsocket \ + -Dexec.args="\ + --help" +``` + +This will result in the following output: +``` +MQTT5 AWS Websocket Sample + +Required: + --endpoint IoT endpoint hostname + --signing_region Signing region for websocket connection + +Optional: + --client_id MQTT client ID (default: generated) + --topic Topic to use (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. + +## 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#lifecycle-management), [connection methods](../../../documents/MQTT5_Userguide.md#how-to-setup-mqtt5-builder-based-on-desired-connection-method), and other useful information. + +## ⚠️ Usage disclaimer + +These code examples interact with services that may incur charges to your AWS account. For more information, see [AWS Pricing](https://aws.amazon.com/pricing/). + +Additionally, example code might theoretically modify or delete existing AWS resources. As a matter of due diligence, do the following: + +- Be aware of the resources that these examples create or delete. +- Be aware of the costs that might be charged to your account as a result. +- Back up your important data. \ No newline at end of file diff --git a/servicetests/tests/JobsExecution/pom.xml b/samples/Mqtt/Mqtt5AwsWebsocket/pom.xml similarity index 54% rename from servicetests/tests/JobsExecution/pom.xml rename to samples/Mqtt/Mqtt5AwsWebsocket/pom.xml index 8ba112c5a..4e82d5953 100644 --- a/servicetests/tests/JobsExecution/pom.xml +++ b/samples/Mqtt/Mqtt5AwsWebsocket/pom.xml @@ -2,10 +2,12 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> 4.0.0 software.amazon.awssdk.iotdevicesdk - JobsServiceClientTest + mqtt5-aws-websocket jar 1.0-SNAPSHOT ${project.groupId}:${project.artifactId} + MQTT5 Aws Websocket Sample + https://github.com/awslabs/aws-iot-device-sdk-java-v2 1.8 1.8 @@ -18,28 +20,27 @@ 1.0.0-SNAPSHOT + + + latest-release + + + software.amazon.awssdk.iotdevicesdk + aws-iot-device-sdk + 1.27.4 + + + + org.codehaus.mojo - build-helper-maven-plugin - 3.2.0 - - - add-source - generate-sources - - add-source - - - - ../../../samples/Utils/CommandLineUtils - ../Utils/MqttClientConnectionWrapper - ../ServiceTestLifecycleEvents - - - - + exec-maven-plugin + 3.1.0 + + mqtt5awswebsocket.Mqtt5AwsWebsocket + diff --git a/samples/Mqtt/Mqtt5AwsWebsocket/src/main/java/awswebsocket/Mqtt5AwsWebsocket.java b/samples/Mqtt/Mqtt5AwsWebsocket/src/main/java/awswebsocket/Mqtt5AwsWebsocket.java new file mode 100644 index 000000000..659d945af --- /dev/null +++ b/samples/Mqtt/Mqtt5AwsWebsocket/src/main/java/awswebsocket/Mqtt5AwsWebsocket.java @@ -0,0 +1,255 @@ +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ + +package mqtt5awswebsocket; + +import java.nio.charset.StandardCharsets; +import java.time.Duration; +import java.util.Arrays; +import java.util.UUID; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import software.amazon.awssdk.crt.CRT; +import software.amazon.awssdk.crt.CrtResource; +import software.amazon.awssdk.crt.mqtt5.*; +import software.amazon.awssdk.crt.mqtt5.packets.*; +import software.amazon.awssdk.iot.AwsIotMqtt5ClientBuilder; + +/** + * MQTT5 AWS Websocket Sample + */ +public class Mqtt5AwsWebsocket { + + // ------------------------- ARGUMENT PARSING ------------------------- + static class Args { + String endpoint; + String signingRegion; + String caPath = null; + String clientId = "mqtt5-sample-" + UUID.randomUUID().toString().replace("-", "").substring(0, 8); + String topic = "test/topic"; + String message = "Hello from mqtt5 sample"; + int count = 5; + } + + private static void printHelpAndExit(int code) { + System.out.println("MQTT5 AWS Websocket Sample\n"); + System.out.println("Required:"); + System.out.println(" --endpoint IoT endpoint hostname"); + System.out.println(" --signing_region Signing region for websocket connection"); + System.out.println("\nOptional:"); + System.out.println(" --client_id MQTT client ID (default: generated)"); + System.out.println(" --topic Topic to use (default: test/topic)"); + System.out.println(" --message Message payload (default: \"Hello from mqtt5 sample\")"); + System.out.println(" --count Messages to publish (0 = infinite, default: 5)"); + System.exit(code); + } + + private static Args parseArgs(String[] argv) { + if (argv.length == 0 || Arrays.asList(argv).contains("--help")) { + printHelpAndExit(0); + } + Args a = new Args(); + for (int i = 0; i < argv.length; i++) { + String k = argv[i]; + String v = (i + 1 < argv.length) ? argv[i + 1] : null; + + switch (k) { + case "--endpoint": a.endpoint = v; i++; break; + case "--signing_region": a.signingRegion = v; i++; break; + case "--client_id": a.clientId = v; i++; break; + case "--topic": a.topic = v; i++; break; + case "--message": a.message = v; i++; break; + case "--count": + a.count = Integer.parseInt(v); i++; break; + default: + System.err.println("Unknown arg: " + k); + printHelpAndExit(2); + } + } + if (a.endpoint == null || a.signingRegion == null) { + System.err.println("Missing required arguments."); + printHelpAndExit(2); + } + return a; + } + // ------------------------- ARGUMENT PARSING END --------------------- + + public static void main(String[] argv) { + Args args = parseArgs(argv); + + System.out.println("\nStarting MQTT5 AWS Websocket Sample\n"); + final int TIMEOUT_SECONDS = 100; + + /* + * Latches for flow control of Sample + */ + CountDownLatch connected = new CountDownLatch(1); + CountDownLatch stopped = new CountDownLatch(1); + CountDownLatch receivedAll = new CountDownLatch(args.count > 0 ? args.count : 1); + + /* + * Handle MQTT5 Client lifecycle events + */ + Mqtt5ClientOptions.LifecycleEvents lifecycleEvents = new Mqtt5ClientOptions.LifecycleEvents() { + @Override + public void onAttemptingConnect(Mqtt5Client client, OnAttemptingConnectReturn onAttemptingConnectReturn) { + System.out.printf("Lifecycle Connection Attempt%nConnecting to endpoint: '%s' with client ID '%s'%n", + args.endpoint, args.clientId); + } + + @Override + public void onConnectionSuccess(Mqtt5Client client, OnConnectionSuccessReturn onConnectionSuccessReturn) { + System.out.println("Lifecycle Connection Success with reason code: " + + onConnectionSuccessReturn.getConnAckPacket().getReasonCode() + "\n"); + connected.countDown(); + } + + @Override + public void onConnectionFailure(Mqtt5Client client, OnConnectionFailureReturn onConnectionFailureReturn) { + System.out.println("Lifecycle Connection Failure with error code: " + + onConnectionFailureReturn.getErrorCode() + " : " + + CRT.awsErrorName(onConnectionFailureReturn.getErrorCode()) + " : " + + CRT.awsErrorString(onConnectionFailureReturn.getErrorCode()) + "\n"); + } + + @Override + public void onDisconnection(Mqtt5Client client, OnDisconnectionReturn onDisconnectionReturn) { + System.out.println("Mqtt5 Client: Disconnected"); + DisconnectPacket disconnectPacket = onDisconnectionReturn.getDisconnectPacket(); + if (disconnectPacket != null) { + System.out.println("\nDisconnection packet code: " + disconnectPacket.getReasonCode() + + "\nDisconnection packet reason: " + disconnectPacket.getReasonString()); + } + } + + @Override + public void onStopped(Mqtt5Client client, OnStoppedReturn onStoppedReturn) { + System.out.println("Lifecycle Stopped\n"); + stopped.countDown(); + } + }; + + /* + * Handle Publishes received by the MQTT5 Client + */ + Mqtt5ClientOptions.PublishEvents publishEvents = new Mqtt5ClientOptions.PublishEvents() { + @Override + public void onMessageReceived(Mqtt5Client client, PublishReturn publishReturn) { + PublishPacket publish = publishReturn.getPublishPacket(); + String payload = publish.getPayload() == null + ? "" + : new String(publish.getPayload(), StandardCharsets.UTF_8); + System.out.printf("==== Received message from topic '%s': %s ====%n%n", + publish.getTopic(), payload); + if (args.count > 0) { + receivedAll.countDown(); + } + } + }; + + Mqtt5Client client; + + /** + * Create MQTT5 client that uses a default AWS credentials provider to sign the websocket handshake + */ + System.out.println("==== Creating MQTT5 Client ====\n"); + AwsIotMqtt5ClientBuilder builder = AwsIotMqtt5ClientBuilder.newWebsocketMqttBuilderWithSigv4Auth(args.endpoint, null); + builder.withLifeCycleEvents(lifecycleEvents); + builder.withPublishEvents(publishEvents); + builder.withClientId(args.clientId); + /* Build the MQTT5 client with the configured builder */ + client = builder.build(); + // You must call `close()` on AwsIotMqtt5ClientBuilder or it will leak memory! + builder.close(); + + System.out.println("==== Starting client ===="); + client.start(); + try { + if (!connected.await(TIMEOUT_SECONDS, TimeUnit.SECONDS)) { + throw new RuntimeException("Connection timeout"); + } + } catch (InterruptedException ex) { + throw new RuntimeException("Mqtt5 Aws Websocket: execution failure", ex); + } + + /* Subscribe */ + System.out.printf("==== Subscribing to topic '%s' ====%n", args.topic); + SubscribePacket subscribePacket = SubscribePacket.of(args.topic, QOS.AT_LEAST_ONCE); + try { + SubAckPacket subAckPacket = client.subscribe(subscribePacket).get(TIMEOUT_SECONDS, TimeUnit.SECONDS); + System.out.println("SubAck received with reason code:" + subAckPacket.getReasonCodes() + "\n"); + } catch (Exception ex) { + throw new RuntimeException("Mqtt5 Aws Websocket: execution failure", ex); + } + + /* Publish */ + if (args.count == 0) { + System.out.println("==== Sending messages until program killed ====\n"); + } else { + System.out.printf("==== Sending %d message(s) ====%n%n", args.count); + } + int publishCount = 1; + while (args.count == 0 || publishCount <= args.count) { + String payload = args.message + " [" + publishCount + "]"; + System.out.printf("Publishing message to topic '%s': %s%n", args.topic, payload); + PublishPacket publishPacket = PublishPacket.of( + args.topic, + QOS.AT_LEAST_ONCE, + payload.getBytes(StandardCharsets.UTF_8)); + try { + PubAckPacket pubAck = client.publish(publishPacket).get(TIMEOUT_SECONDS, TimeUnit.SECONDS).getResultPubAck(); + System.out.println("PubAck received with reason: " + pubAck.getReasonCode() + "\n"); + } catch (Exception ex) { + throw new RuntimeException("Mqtt5 Aws Websocket: execution failure", ex); + } + try { + Thread.sleep(Duration.ofMillis(1500).toMillis()); + } catch (InterruptedException ex) { + throw new RuntimeException("Mqtt5 Aws Websocket: execution failure", ex); + } + publishCount++; + } + if (args.count > 0) { + long remaining = receivedAll.getCount(); + if (remaining > 0) { + try { + receivedAll.await(TIMEOUT_SECONDS, TimeUnit.SECONDS); + } catch (InterruptedException ex) { + throw new RuntimeException("Mqtt5 Aws Websocket: execution failure", ex); + } + } + long received = (args.count - receivedAll.getCount()); + System.out.printf("%d message(s) received.%n%n", received); + } + + // ---------- Unsubscribe ---------- + System.out.printf("==== Unsubscribing from topic '%s' ====%n", args.topic); + UnsubscribePacket unsubscribePacket = UnsubscribePacket.of(args.topic); + try { + UnsubAckPacket unsubAckPacket = client.unsubscribe(unsubscribePacket).get(TIMEOUT_SECONDS, TimeUnit.SECONDS); + System.out.println("UnsubAck received with reason code:" + unsubAckPacket.getReasonCodes() + "\n"); + } catch (Exception ex) { + throw new RuntimeException("Mqtt5 Aws Websocket: execution failure", ex); + } + + System.out.println("==== Stopping Client ===="); + client.stop(); + try { + if (!stopped.await(TIMEOUT_SECONDS, TimeUnit.SECONDS)) { + throw new RuntimeException("Stop timeout"); + } + } catch (InterruptedException ex) { + throw new RuntimeException("Mqtt5 Aws Websocket: execution failure", ex); + } + System.out.println("==== Client Stopped! ===="); + + /* Close the client to free memory */ + client.close(); + + CrtResource.waitForNoResources(); + System.out.println("Complete!"); + } +} diff --git a/samples/Mqtt/Mqtt5CustomAuthSigned/README.md b/samples/Mqtt/Mqtt5CustomAuthSigned/README.md new file mode 100644 index 000000000..10d14cd4e --- /dev/null +++ b/samples/Mqtt/Mqtt5CustomAuthSigned/README.md @@ -0,0 +1,127 @@ +# MQTT5 Signed Custom Authorizer PubSub + +[**Return to main sample list**](../../README.md) +*__Jump To:__* +* [Introduction](#introduction) +* [Requirements](#requirements) +* [How To Run](#how-to-run) +* [Additional Information](#additional-information) + +## Introduction +The Custom Authorizer samples 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 or 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 Java 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 run + +To run this sample from the `aws-iot-device-sdk-java-v2` folder use the following command: + +```sh +mvn compile exec:java \ + -pl samples/Mqtt/Mqtt5CustomAuthSigned \ + -Dexec.args=" \ + --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 +mvn compile exec:java \ + -pl samples/Mqtt/Mqtt5CustomAuthSigned \ + -Dexec.args=" \ + --help" +``` + +This will result in the following output: +``` +MQTT5 Custom Authorizer Signed Sample + +Required: + --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: + --client_id MQTT client ID (default: generated) + --topic Topic to use (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. + +## 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#lifecycle-management), [connection methods](../../../documents/MQTT5_Userguide.md#how-to-setup-mqtt5-builder-based-on-desired-connection-method), and other useful information. + +## ⚠️ Usage disclaimer + +These code examples interact with services that may incur charges to your AWS account. For more information, see [AWS Pricing](https://aws.amazon.com/pricing/). + +Additionally, example code might theoretically modify or delete existing AWS resources. As a matter of due diligence, do the following: + +- Be aware of the resources that these examples create or delete. +- Be aware of the costs that might be charged to your account as a result. +- Back up your important data. diff --git a/servicetests/tests/ShadowUpdate/pom.xml b/samples/Mqtt/Mqtt5CustomAuthSigned/pom.xml similarity index 53% rename from servicetests/tests/ShadowUpdate/pom.xml rename to samples/Mqtt/Mqtt5CustomAuthSigned/pom.xml index 91b40946a..ca1864ebe 100644 --- a/servicetests/tests/ShadowUpdate/pom.xml +++ b/samples/Mqtt/Mqtt5CustomAuthSigned/pom.xml @@ -2,11 +2,11 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> 4.0.0 software.amazon.awssdk.iotdevicesdk - ShadowServiceTest + mqtt5-custom-auth-signed jar 1.0-SNAPSHOT ${project.groupId}:${project.artifactId} - Java bindings for the AWS IoT Core Service + MQTT5 Custom Auth Signed Sample https://github.com/awslabs/aws-iot-device-sdk-java-v2 1.8 @@ -20,28 +20,27 @@ 1.0.0-SNAPSHOT + + + latest-release + + + software.amazon.awssdk.iotdevicesdk + aws-iot-device-sdk + 1.27.4 + + + + org.codehaus.mojo - build-helper-maven-plugin - 3.2.0 - - - add-source - generate-sources - - add-source - - - - ../../../samples/Utils/CommandLineUtils - ../Utils/MqttClientConnectionWrapper - ../ServiceTestLifecycleEvents - - - - + exec-maven-plugin + 3.1.0 + + mqtt5customauthsigned.Mqtt5CustomAuthSigned + diff --git a/samples/Mqtt/Mqtt5CustomAuthSigned/src/main/java/mqtt5customauthsigned/Mqtt5CustomAuthSigned.java b/samples/Mqtt/Mqtt5CustomAuthSigned/src/main/java/mqtt5customauthsigned/Mqtt5CustomAuthSigned.java new file mode 100644 index 000000000..755920edc --- /dev/null +++ b/samples/Mqtt/Mqtt5CustomAuthSigned/src/main/java/mqtt5customauthsigned/Mqtt5CustomAuthSigned.java @@ -0,0 +1,284 @@ +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ + +package mqtt5customauthsigned; + +import java.nio.charset.StandardCharsets; +import java.time.Duration; +import java.util.Arrays; +import java.util.UUID; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import software.amazon.awssdk.crt.CRT; +import software.amazon.awssdk.crt.CrtResource; +import software.amazon.awssdk.crt.mqtt5.*; +import software.amazon.awssdk.crt.mqtt5.packets.*; +import software.amazon.awssdk.iot.AwsIotMqtt5ClientBuilder; + +/** + * MQTT5 Custom Authorizer Signed Sample + */ +public class Mqtt5CustomAuthSigned { + + // ------------------------- ARGUMENT PARSING ------------------------- + static class Args { + String endpoint; + String authorizerName; + String authSignature; + String authTokenKeyName; + String authTokenKeyValue; + String authUsername; + String authPassword; + String clientId = "mqtt5-sample-" + UUID.randomUUID().toString().replace("-", "").substring(0, 8); + String topic = "test/topic"; + String message = "Hello from mqtt5 sample"; + int count = 5; + } + + private static void printHelpAndExit(int code) { + System.out.println("MQTT5 Custom Authorizer Signed Sample\n"); + System.out.println("Required:"); + System.out.println(" --endpoint IoT endpoint hostname"); + System.out.println(" --authorizer_name The name of the custom authorizer to connect to invoke"); + System.out.println(" --auth_signature Custom authorizer signature"); + System.out.println(" --auth_token_key_name Authorizer token key name"); + System.out.println(" --auth_token_key_value Authorizer token key value"); + System.out.println(" --auth_username The name to send when connecting through the custom authorizer"); + System.out.println(" --auth_password The password to send when connecting through a custom authorizer"); + System.out.println("\nOptional:"); + System.out.println(" --client_id MQTT client ID (default: generated)"); + System.out.println(" --topic Topic to use (default: test/topic)"); + System.out.println(" --message Message payload (default: \"Hello from mqtt5 sample\")"); + System.out.println(" --count Messages to publish (0 = infinite, default: 5)"); + System.exit(code); + } + + private static Args parseArgs(String[] argv) { + if (argv.length == 0 || Arrays.asList(argv).contains("--help")) { + printHelpAndExit(0); + } + Args a = new Args(); + for (int i = 0; i < argv.length; i++) { + String k = argv[i]; + String v = (i + 1 < argv.length) ? argv[i + 1] : null; + + switch (k) { + case "--endpoint": a.endpoint = v; i++; break; + case "--authorizer_name": a.authorizerName = v; i++; break; + case "--auth_signature": a.authSignature = v; i++; break; + case "--auth_token_key_name": a.authTokenKeyName = v; i++; break; + case "--auth_token_key_value": a.authTokenKeyValue = v; i++; break; + case "--auth_username": a.authUsername = v; i++; break; + case "--auth_password": a.authPassword = v; i++; break; + case "--client_id": a.clientId = v; i++; break; + case "--topic": a.topic = v; i++; break; + case "--message": a.message = v; i++; break; + case "--count": + a.count = Integer.parseInt(v); i++; break; + default: + System.err.println("Unknown arg: " + k); + printHelpAndExit(2); + } + } + if (a.endpoint == null || + a.authorizerName == null || + a.authSignature == null || + a.authTokenKeyName == null || + a.authTokenKeyValue == null || + a.authUsername == null || + a.authPassword == null) { + System.err.println("Missing required arguments."); + printHelpAndExit(2); + } + return a; + } + // ------------------------- ARGUMENT PARSING END --------------------- + + public static void main(String[] argv) { + Args args = parseArgs(argv); + + System.out.println("\nStarting MQTT5 Custom Authorizer Signed Sample\n"); + final int TIMEOUT_SECONDS = 100; + + /* + * Latches for flow control of Sample + */ + CountDownLatch connected = new CountDownLatch(1); + CountDownLatch stopped = new CountDownLatch(1); + CountDownLatch receivedAll = new CountDownLatch(args.count > 0 ? args.count : 1); + + /* + * Handle MQTT5 Client lifecycle events + */ + Mqtt5ClientOptions.LifecycleEvents lifecycleEvents = new Mqtt5ClientOptions.LifecycleEvents() { + @Override + public void onAttemptingConnect(Mqtt5Client client, OnAttemptingConnectReturn onAttemptingConnectReturn) { + System.out.printf("Lifecycle Connection Attempt%nConnecting to endpoint: '%s' with client ID '%s'%n", + args.endpoint, args.clientId); + } + + @Override + public void onConnectionSuccess(Mqtt5Client client, OnConnectionSuccessReturn onConnectionSuccessReturn) { + System.out.println("Lifecycle Connection Success with reason code: " + + onConnectionSuccessReturn.getConnAckPacket().getReasonCode() + "\n"); + connected.countDown(); + } + + @Override + public void onConnectionFailure(Mqtt5Client client, OnConnectionFailureReturn onConnectionFailureReturn) { + System.out.println("Lifecycle Connection Failure with error code: " + + onConnectionFailureReturn.getErrorCode() + " : " + + CRT.awsErrorName(onConnectionFailureReturn.getErrorCode()) + " : " + + CRT.awsErrorString(onConnectionFailureReturn.getErrorCode()) + "\n"); + } + + @Override + public void onDisconnection(Mqtt5Client client, OnDisconnectionReturn onDisconnectionReturn) { + System.out.println("Mqtt5 Client: Disconnected"); + DisconnectPacket disconnectPacket = onDisconnectionReturn.getDisconnectPacket(); + if (disconnectPacket != null) { + System.out.println("\nDisconnection packet code: " + disconnectPacket.getReasonCode() + + "\nDisconnection packet reason: " + disconnectPacket.getReasonString()); + } + } + + @Override + public void onStopped(Mqtt5Client client, OnStoppedReturn onStoppedReturn) { + System.out.println("Lifecycle Stopped\n"); + stopped.countDown(); + } + }; + + /* + * Handle Publishes received by the MQTT5 Client + */ + Mqtt5ClientOptions.PublishEvents publishEvents = new Mqtt5ClientOptions.PublishEvents() { + @Override + public void onMessageReceived(Mqtt5Client client, PublishReturn publishReturn) { + PublishPacket publish = publishReturn.getPublishPacket(); + String payload = publish.getPayload() == null + ? "" + : new String(publish.getPayload(), StandardCharsets.UTF_8); + System.out.printf("==== Received message from topic '%s': %s ====%n%n", + publish.getTopic(), payload); + if (args.count > 0) { + receivedAll.countDown(); + } + } + }; + + Mqtt5Client client; + + /** + * Create MQTT5 client using mutual TLS via X509 Certificate and Private Key + */ + System.out.println("==== Creating MQTT5 Client ====\n"); + + AwsIotMqtt5ClientBuilder.MqttConnectCustomAuthConfig customAuthConfig = new AwsIotMqtt5ClientBuilder.MqttConnectCustomAuthConfig(); + customAuthConfig.authorizerName = args.authorizerName; + customAuthConfig.tokenSignature = args.authSignature; + customAuthConfig.tokenValue = args.authTokenKeyValue; + customAuthConfig.tokenKeyName = args.authTokenKeyName; + customAuthConfig.username = args.authUsername; + customAuthConfig.password = args.authPassword.getBytes(); + + AwsIotMqtt5ClientBuilder builder = AwsIotMqtt5ClientBuilder.newWebsocketMqttBuilderWithCustomAuth( + args.endpoint, customAuthConfig); + builder.withLifeCycleEvents(lifecycleEvents); + builder.withPublishEvents(publishEvents); + builder.withClientId(args.clientId); + client = builder.build(); + // You must call `close()` on AwsIotMqtt5ClientBuilder or it will leak memory! + builder.close(); + + System.out.println("==== Starting client ===="); + client.start(); + try { + if (!connected.await(TIMEOUT_SECONDS, TimeUnit.SECONDS)) { + throw new RuntimeException("Connection timeout"); + } + } catch (InterruptedException ex) { + throw new RuntimeException("Mqtt5 Custom Authorizer Signed: execution failure", ex); + } + + /* Subscribe */ + System.out.printf("==== Subscribing to topic '%s' ====%n", args.topic); + SubscribePacket subscribePacket = SubscribePacket.of(args.topic, QOS.AT_LEAST_ONCE); + try { + SubAckPacket subAckPacket = client.subscribe(subscribePacket).get(TIMEOUT_SECONDS, TimeUnit.SECONDS); + System.out.println("SubAck received with reason code:" + subAckPacket.getReasonCodes() + "\n"); + } catch (Exception ex) { + throw new RuntimeException("Mqtt5 Custom Authorizer Signed: execution failure", ex); + } + + /* Publish */ + if (args.count == 0) { + System.out.println("==== Sending messages until program killed ====\n"); + } else { + System.out.printf("==== Sending %d message(s) ====%n%n", args.count); + } + int publishCount = 1; + while (args.count == 0 || publishCount <= args.count) { + String payload = args.message + " [" + publishCount + "]"; + System.out.printf("Publishing message to topic '%s': %s%n", args.topic, payload); + PublishPacket publishPacket = PublishPacket.of( + args.topic, + QOS.AT_LEAST_ONCE, + payload.getBytes(StandardCharsets.UTF_8)); + try { + PubAckPacket pubAck = client.publish(publishPacket).get(TIMEOUT_SECONDS, TimeUnit.SECONDS).getResultPubAck(); + System.out.println("PubAck received with reason: " + pubAck.getReasonCode() + "\n"); + } catch (Exception ex) { + throw new RuntimeException("Mqtt5 Custom Authorizer Signed: execution failure", ex); + } + try { + Thread.sleep(Duration.ofMillis(1500).toMillis()); + } catch (InterruptedException ex) { + throw new RuntimeException("Mqtt5 Custom Authorizer Signed: execution failure", ex); + } + publishCount++; + } + if (args.count > 0) { + long remaining = receivedAll.getCount(); + if (remaining > 0) { + try { + receivedAll.await(TIMEOUT_SECONDS, TimeUnit.SECONDS); + } catch (InterruptedException ex) { + throw new RuntimeException("Mqtt5 Custom Authorizer Signed: execution failure", ex); + } + } + long received = (args.count - receivedAll.getCount()); + System.out.printf("%d message(s) received.%n%n", received); + } + + // ---------- Unsubscribe ---------- + System.out.printf("==== Unsubscribing from topic '%s' ====%n", args.topic); + UnsubscribePacket unsubscribePacket = UnsubscribePacket.of(args.topic); + try { + UnsubAckPacket unsubAckPacket = client.unsubscribe(unsubscribePacket).get(TIMEOUT_SECONDS, TimeUnit.SECONDS); + System.out.println("UnsubAck received with reason code:" + unsubAckPacket.getReasonCodes() + "\n"); + } catch (Exception ex) { + throw new RuntimeException("Mqtt5 Custom Authorizer Signed: execution failure", ex); + } + + System.out.println("==== Stopping Client ===="); + client.stop(); + try { + if (!stopped.await(TIMEOUT_SECONDS, TimeUnit.SECONDS)) { + throw new RuntimeException("Stop timeout"); + } + } catch (InterruptedException ex) { + throw new RuntimeException("Mqtt5 Custom Authorizer Signed: execution failure", ex); + } + System.out.println("==== Client Stopped! ===="); + + /* Close the client to free memory */ + client.close(); + + CrtResource.waitForNoResources(); + System.out.println("Complete!"); + } +} diff --git a/samples/Mqtt/Mqtt5CustomAuthUnsigned/README.md b/samples/Mqtt/Mqtt5CustomAuthUnsigned/README.md new file mode 100644 index 000000000..c1767a412 --- /dev/null +++ b/samples/Mqtt/Mqtt5CustomAuthUnsigned/README.md @@ -0,0 +1,121 @@ +# MQTT5 Unsigned Custom Authorizer PubSub + +[**Return to main sample list**](../../README.md) +*__Jump To:__* +* [Introduction](#introduction) +* [Requirements](#requirements) +* [How To Run](#how-to-run) +* [Additional Information](#additional-information) + +## Introduction +The Custom Authorizer samples 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 or 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 Java 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 run + +To run this sample from the `aws-iot-device-sdk-java-v2` folder use the following command: + +```sh +mvn compile exec:java \ + -pl samples/Mqtt/Mqtt5CustomAuthUnsigned \ + -Dexec.args=" \ + --endpoint \ + --authorizer_name \ + --auth_username \ + --auth_password " +``` + +If you would like to see what optional arguments are available, use the `--help` argument: +```sh +mvn compile exec:java \ + -pl samples/Mqtt/Mqtt5CustomAuthUnsigned \ + -Dexec.args=" \ + --help" +``` + +This will result in the following output: +``` +MQTT5 Custom Authorizer Unsigned Sample + +Required: + --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: + --client_id MQTT client ID (default: generated) + --topic Topic to use (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. + +## 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#lifecycle-management), [connection methods](../../../documents/MQTT5_Userguide.md#how-to-setup-mqtt5-builder-based-on-desired-connection-method), and other useful information. + +## ⚠️ Usage disclaimer + +These code examples interact with services that may incur charges to your AWS account. For more information, see [AWS Pricing](https://aws.amazon.com/pricing/). + +Additionally, example code might theoretically modify or delete existing AWS resources. As a matter of due diligence, do the following: + +- Be aware of the resources that these examples create or delete. +- Be aware of the costs that might be charged to your account as a result. +- Back up your important data. diff --git a/servicetests/tests/FleetProvisioning/pom.xml b/samples/Mqtt/Mqtt5CustomAuthUnsigned/pom.xml similarity index 53% rename from servicetests/tests/FleetProvisioning/pom.xml rename to samples/Mqtt/Mqtt5CustomAuthUnsigned/pom.xml index 470b7790c..bb8b381e3 100644 --- a/servicetests/tests/FleetProvisioning/pom.xml +++ b/samples/Mqtt/Mqtt5CustomAuthUnsigned/pom.xml @@ -2,11 +2,11 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> 4.0.0 software.amazon.awssdk.iotdevicesdk - FleetProvisioningServiceTest + mqtt5-custom-auth-unsigned jar 1.0-SNAPSHOT ${project.groupId}:${project.artifactId} - Java bindings for the AWS IoT Core Service + MQTT5 Custom Auth Unsigned Sample https://github.com/awslabs/aws-iot-device-sdk-java-v2 1.8 @@ -20,28 +20,27 @@ 1.0.0-SNAPSHOT + + + latest-release + + + software.amazon.awssdk.iotdevicesdk + aws-iot-device-sdk + 1.27.4 + + + + org.codehaus.mojo - build-helper-maven-plugin - 3.2.0 - - - add-source - generate-sources - - add-source - - - - ../../../samples/Utils/CommandLineUtils - ../Utils/MqttClientConnectionWrapper - ../ServiceTestLifecycleEvents - - - - + exec-maven-plugin + 3.1.0 + + mqtt5customauthunsigned.Mqtt5CustomAuthUnsigned + diff --git a/samples/Mqtt/Mqtt5CustomAuthUnsigned/src/main/java/mqtt5customauthunsigned/Mqtt5CustomAuthUnsigned.java b/samples/Mqtt/Mqtt5CustomAuthUnsigned/src/main/java/mqtt5customauthunsigned/Mqtt5CustomAuthUnsigned.java new file mode 100644 index 000000000..e85132c67 --- /dev/null +++ b/samples/Mqtt/Mqtt5CustomAuthUnsigned/src/main/java/mqtt5customauthunsigned/Mqtt5CustomAuthUnsigned.java @@ -0,0 +1,268 @@ +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ + +package mqtt5customauthunsigned; + +import java.nio.charset.StandardCharsets; +import java.time.Duration; +import java.util.Arrays; +import java.util.UUID; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import software.amazon.awssdk.crt.CRT; +import software.amazon.awssdk.crt.CrtResource; +import software.amazon.awssdk.crt.mqtt5.*; +import software.amazon.awssdk.crt.mqtt5.packets.*; +import software.amazon.awssdk.iot.AwsIotMqtt5ClientBuilder; + +/** + * MQTT5 Custom Authorizer Unigned Sample + */ +public class Mqtt5CustomAuthUnsigned { + + // ------------------------- ARGUMENT PARSING ------------------------- + static class Args { + String endpoint; + String authorizerName; + String authUsername; + String authPassword; + String clientId = "mqtt5-sample-" + UUID.randomUUID().toString().replace("-", "").substring(0, 8); + String topic = "test/topic"; + String message = "Hello from mqtt5 sample"; + int count = 5; + } + + private static void printHelpAndExit(int code) { + System.out.println("MQTT5 Custom Authorizer Unsigned Sample\n"); + System.out.println("Required:"); + System.out.println(" --endpoint IoT endpoint hostname"); + System.out.println(" --authorizer_name The name of the custom authorizer to connect to invoke"); + System.out.println(" --auth_username The name to send when connecting through the custom authorizer"); + System.out.println(" --auth_password The password to send when connecting through a custom authorizer"); + System.out.println("\nOptional:"); + System.out.println(" --client_id MQTT client ID (default: generated)"); + System.out.println(" --topic Topic to use (default: test/topic)"); + System.out.println(" --message Message payload (default: \"Hello from mqtt5 sample\")"); + System.out.println(" --count Messages to publish (0 = infinite, default: 5)"); + System.exit(code); + } + + private static Args parseArgs(String[] argv) { + if (argv.length == 0 || Arrays.asList(argv).contains("--help")) { + printHelpAndExit(0); + } + Args a = new Args(); + for (int i = 0; i < argv.length; i++) { + String k = argv[i]; + String v = (i + 1 < argv.length) ? argv[i + 1] : null; + + switch (k) { + case "--endpoint": a.endpoint = v; i++; break; + case "--authorizer_name": a.authorizerName = v; i++; break; + case "--auth_username": a.authUsername = v; i++; break; + case "--auth_password": a.authPassword = v; i++; break; + case "--client_id": a.clientId = v; i++; break; + case "--topic": a.topic = v; i++; break; + case "--message": a.message = v; i++; break; + case "--count": a.count = Integer.parseInt(v); i++; break; + default: + System.err.println("Unknown arg: " + k); + printHelpAndExit(2); + } + } + if (a.endpoint == null || + a.authorizerName == null || + a.authUsername == null || + a.authPassword == null) { + System.err.println("Missing required arguments."); + printHelpAndExit(2); + } + return a; + } + // ------------------------- ARGUMENT PARSING END --------------------- + + public static void main(String[] argv) { + Args args = parseArgs(argv); + + System.out.println("\nStarting MQTT5 Custom Authorizer Unsigned Sample\n"); + final int TIMEOUT_SECONDS = 100; + + /* + * Latches for flow control of Sample + */ + CountDownLatch connected = new CountDownLatch(1); + CountDownLatch stopped = new CountDownLatch(1); + CountDownLatch receivedAll = new CountDownLatch(args.count > 0 ? args.count : 1); + + /* + * Handle MQTT5 Client lifecycle events + */ + Mqtt5ClientOptions.LifecycleEvents lifecycleEvents = new Mqtt5ClientOptions.LifecycleEvents() { + @Override + public void onAttemptingConnect(Mqtt5Client client, OnAttemptingConnectReturn onAttemptingConnectReturn) { + System.out.printf("Lifecycle Connection Attempt%nConnecting to endpoint: '%s' with client ID '%s'%n", + args.endpoint, args.clientId); + } + + @Override + public void onConnectionSuccess(Mqtt5Client client, OnConnectionSuccessReturn onConnectionSuccessReturn) { + System.out.println("Lifecycle Connection Success with reason code: " + + onConnectionSuccessReturn.getConnAckPacket().getReasonCode() + "\n"); + connected.countDown(); + } + + @Override + public void onConnectionFailure(Mqtt5Client client, OnConnectionFailureReturn onConnectionFailureReturn) { + System.out.println("Lifecycle Connection Failure with error code: " + + onConnectionFailureReturn.getErrorCode() + " : " + + CRT.awsErrorName(onConnectionFailureReturn.getErrorCode()) + " : " + + CRT.awsErrorString(onConnectionFailureReturn.getErrorCode()) + "\n"); + } + + @Override + public void onDisconnection(Mqtt5Client client, OnDisconnectionReturn onDisconnectionReturn) { + System.out.println("Mqtt5 Client: Disconnected"); + DisconnectPacket disconnectPacket = onDisconnectionReturn.getDisconnectPacket(); + if (disconnectPacket != null) { + System.out.println("\nDisconnection packet code: " + disconnectPacket.getReasonCode() + + "\nDisconnection packet reason: " + disconnectPacket.getReasonString()); + } + } + + @Override + public void onStopped(Mqtt5Client client, OnStoppedReturn onStoppedReturn) { + System.out.println("Lifecycle Stopped\n"); + stopped.countDown(); + } + }; + + /* + * Handle Publishes received by the MQTT5 Client + */ + Mqtt5ClientOptions.PublishEvents publishEvents = new Mqtt5ClientOptions.PublishEvents() { + @Override + public void onMessageReceived(Mqtt5Client client, PublishReturn publishReturn) { + PublishPacket publish = publishReturn.getPublishPacket(); + String payload = publish.getPayload() == null + ? "" + : new String(publish.getPayload(), StandardCharsets.UTF_8); + System.out.printf("==== Received message from topic '%s': %s ====%n%n", + publish.getTopic(), payload); + if (args.count > 0) { + receivedAll.countDown(); + } + } + }; + + Mqtt5Client client; + + /** + * Create MQTT5 client using mutual TLS via X509 Certificate and Private Key + */ + System.out.println("==== Creating MQTT5 Client ====\n"); + + AwsIotMqtt5ClientBuilder.MqttConnectCustomAuthConfig customAuthConfig = new AwsIotMqtt5ClientBuilder.MqttConnectCustomAuthConfig(); + customAuthConfig.authorizerName = args.authorizerName; + customAuthConfig.username = args.authUsername; + customAuthConfig.password = args.authPassword.getBytes(); + + AwsIotMqtt5ClientBuilder builder = AwsIotMqtt5ClientBuilder.newWebsocketMqttBuilderWithCustomAuth( + args.endpoint, customAuthConfig); + builder.withLifeCycleEvents(lifecycleEvents); + builder.withPublishEvents(publishEvents); + builder.withClientId(args.clientId); + client = builder.build(); + // You must call `close()` on AwsIotMqtt5ClientBuilder or it will leak memory! + builder.close(); + + System.out.println("==== Starting client ===="); + client.start(); + try { + if (!connected.await(TIMEOUT_SECONDS, TimeUnit.SECONDS)) { + throw new RuntimeException("Connection timeout"); + } + } catch (InterruptedException ex) { + throw new RuntimeException("Mqtt5 Custom Authorizer Unsigned: execution failure", ex); + } + + /* Subscribe */ + System.out.printf("==== Subscribing to topic '%s' ====%n", args.topic); + SubscribePacket subscribePacket = SubscribePacket.of(args.topic, QOS.AT_LEAST_ONCE); + try { + SubAckPacket subAckPacket = client.subscribe(subscribePacket).get(TIMEOUT_SECONDS, TimeUnit.SECONDS); + System.out.println("SubAck received with reason code:" + subAckPacket.getReasonCodes() + "\n"); + } catch (Exception ex) { + throw new RuntimeException("Mqtt5 Custom Authorizer Unsigned: execution failure", ex); + } + + /* Publish */ + if (args.count == 0) { + System.out.println("==== Sending messages until program killed ====\n"); + } else { + System.out.printf("==== Sending %d message(s) ====%n%n", args.count); + } + int publishCount = 1; + while (args.count == 0 || publishCount <= args.count) { + String payload = args.message + " [" + publishCount + "]"; + System.out.printf("Publishing message to topic '%s': %s%n", args.topic, payload); + PublishPacket publishPacket = PublishPacket.of( + args.topic, + QOS.AT_LEAST_ONCE, + payload.getBytes(StandardCharsets.UTF_8)); + try { + PubAckPacket pubAck = client.publish(publishPacket).get(TIMEOUT_SECONDS, TimeUnit.SECONDS).getResultPubAck(); + System.out.println("PubAck received with reason: " + pubAck.getReasonCode() + "\n"); + } catch (Exception ex) { + throw new RuntimeException("Mqtt5 Custom Authorizer Unsigned: execution failure", ex); + } + try { + Thread.sleep(Duration.ofMillis(1500).toMillis()); + } catch (InterruptedException ex) { + throw new RuntimeException("Mqtt5 Custom Authorizer Unsigned: execution failure", ex); + } + publishCount++; + } + if (args.count > 0) { + long remaining = receivedAll.getCount(); + if (remaining > 0) { + try { + receivedAll.await(TIMEOUT_SECONDS, TimeUnit.SECONDS); + } catch (InterruptedException ex) { + throw new RuntimeException("Mqtt5 Custom Authorizer Unsigned: execution failure", ex); + } + } + long received = (args.count - receivedAll.getCount()); + System.out.printf("%d message(s) received.%n%n", received); + } + + // ---------- Unsubscribe ---------- + System.out.printf("==== Unsubscribing from topic '%s' ====%n", args.topic); + UnsubscribePacket unsubscribePacket = UnsubscribePacket.of(args.topic); + try { + UnsubAckPacket unsubAckPacket = client.unsubscribe(unsubscribePacket).get(TIMEOUT_SECONDS, TimeUnit.SECONDS); + System.out.println("UnsubAck received with reason code:" + unsubAckPacket.getReasonCodes() + "\n"); + } catch (Exception ex) { + throw new RuntimeException("Mqtt5 Custom Authorizer Unsigned: execution failure", ex); + } + + System.out.println("==== Stopping Client ===="); + client.stop(); + try { + if (!stopped.await(TIMEOUT_SECONDS, TimeUnit.SECONDS)) { + throw new RuntimeException("Stop timeout"); + } + } catch (InterruptedException ex) { + throw new RuntimeException("Mqtt5 Custom Authorizer Unsigned: execution failure", ex); + } + System.out.println("==== Client Stopped! ===="); + + /* Close the client to free memory */ + client.close(); + + CrtResource.waitForNoResources(); + System.out.println("Complete!"); + } +} diff --git a/samples/Mqtt/Mqtt5Pkcs11/README.md b/samples/Mqtt/Mqtt5Pkcs11/README.md new file mode 100644 index 000000000..1188cda82 --- /dev/null +++ b/samples/Mqtt/Mqtt5Pkcs11/README.md @@ -0,0 +1,177 @@ +# MQTT5 PKCS11 PubSub + +[**Return to main sample list**](../../README.md) +*__Jump To:__* +* [Introduction](#introduction) +* [Requirements](#requirements) +* [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](../mqtt5x509/README.md) 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 Java 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 run + +To run this sample from the `aws-iot-device-sdk-java-v2` folder use the following command: + +```sh +mvn compile exec:java \ + -pl samples/Mqtt/Mqtt5Pkcs11 \ + -Dexec.args="\ + --endpoint \ + --cert \ + --pkcs11_path \ + --pin \ + --token_label