diff --git a/README.md b/README.md index 81d6241c..af218f67 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,7 @@ The primary purpose of the AWS IoT Device SDK for Java v2 is to simplify the pro * Integrated service clients for AWS IoT Core services * Secure device connections to AWS IoT Core using MQTT protocol including MQTT 5.0 * Support for [multiple authentication methods and connection types](./documents/MQTT5_Userguide.md#how-to-setup-mqtt5-builder-based-on-desired-connection-method) +* Support for [manual publish acknowledgement](./documents/MQTT5_Userguide.md#manual-publish-acknowledgement) for control over QoS 1 PUBACK delivery * Android [support](./documents/ANDROID.md) #### Supported AWS IoT Core services diff --git a/android/iotdevicesdk/build.gradle b/android/iotdevicesdk/build.gradle index e66a12b2..50612401 100644 --- a/android/iotdevicesdk/build.gradle +++ b/android/iotdevicesdk/build.gradle @@ -114,7 +114,7 @@ repositories { } dependencies { - api 'software.amazon.awssdk.crt:aws-crt-android:0.43.8' + api 'software.amazon.awssdk.crt:aws-crt-android:0.45.0' coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5' implementation 'org.slf4j:slf4j-api:1.7.30' implementation 'com.google.code.gson:gson:2.9.0' diff --git a/documents/FAQ.md b/documents/FAQ.md index 11385dab..8b8bcda7 100644 --- a/documents/FAQ.md +++ b/documents/FAQ.md @@ -13,6 +13,7 @@ * [Where can I find MQTT 311 Samples?](#where-can-i-find-mqtt-311-samples) * [How can I improve the library size?](#how-can-i-improve-the-library-size) * [Certificate and Private Key Usage Across Different Versions of the SDK on macOS](#certificate-and-private-key-usage-across-different-versions-of-the-sdk-on-macos) +* [Manual Publish Acknowledgement and QoS 1 Redelivery](#manual-publish-acknowledgement-and-qos-1-redelivery) * [I still have more questions about this sdk?](#i-still-have-more-questions-about-this-sdk) ### Where should I start? @@ -176,6 +177,18 @@ For maximum control, build both CRT and SDK locally: ### Certificate and Private Key Usage Across Different Versions of the SDK on macOS A certificate and private key pair cannot be shared on a macOS device between aws-iot-device-sdk-java-v2 v1.29.0 and other versions. In the update to v1.29.0 we migrated macOS from using Apple's deprecated Security Framework to SecItem API. In doing so, certificate and private keys are imported in a non-backwards compatible manner into the Apple Keychain. +### Manual Publish Acknowledgement and QoS 1 Redelivery + +When using [manual publish acknowledgement](./MQTT5_Userguide.md#manual-publish-acknowledgement), there are two important behaviors to be aware of regarding QoS 1 message redelivery: + +**Broker redelivery of unacknowledged publishes** + +The AWS IoT broker will periodically resend unacknowledged QoS 1 PUBLISH packets. These redeliveries should be treated as duplicates even if the DUP flag in the PUBLISH packet is not set. If the manual publish acknowledgement is not acquired again for a redelivered packet, the acknowledgement will be sent automatically. + +**Session resumption after disconnect/reconnect** + +Upon a disconnect and reconnect of the MQTT5 client, if a session is resumed, any previously acquired acknowledgement handle is void. The broker will resend the unacknowledged PUBLISH packet, and the acknowledgement must be reacquired from that resent packet. If the resent packet is not handled for manual acknowledgement, the acknowledgement will be sent automatically. + ### I still have more questions about this sdk? * [Here](https://docs.aws.amazon.com/iot/latest/developerguide/what-is-aws-iot.html) are the AWS IoT Core docs for more details about IoT Core diff --git a/documents/MQTT5_Userguide.md b/documents/MQTT5_Userguide.md index 794aca99..3a2441bd 100644 --- a/documents/MQTT5_Userguide.md +++ b/documents/MQTT5_Userguide.md @@ -23,6 +23,8 @@ + [Client Operations](#client-operations) + [Publish](#publish) + [Subscribe and Unsubscribe](#subscribe-and-unsubscribe) + + [Advanced Operations and Settings](#advanced-operations-and-settings) + * [Manual Publish Acknowledgement](#manual-publish-acknowledgement) + [MQTT5 Best Practices](#mqtt5-best-practices) # Introduction @@ -554,13 +556,72 @@ unsubBuilder.withSubscription("hello/world/qos1"); client.unsubscribe(unsubBuilder.build()).get(60, TimeUnit.SECONDS); ~~~ +## Advanced Operations and Settings + +### Manual Publish Acknowledgement + +By default, the MQTT5 client automatically sends a PUBACK for every QoS 1 PUBLISH it receives, immediately after the `onMessageReceived` callback returns. Manual publish acknowledgement gives you control over when that PUBACK is sent, allowing you to defer acknowledgement until after your application has fully processed the message — for example, after persisting it to a database or forwarding it to another service. + +To take manual control of the PUBACK, call `publishReturn.acquirePublishAcknowledgementControl()` **within** the `onMessageReceived` callback. This returns a `Mqtt5PublishAcknowledgementControlHandle` that you can store and use later to send the PUBACK by calling `client.invokePublishAcknowledgement()`. + +**Important constraints:** +* `acquirePublishAcknowledgementControl()` must be called within the `onMessageReceived` callback. Calling it outside the callback after it returns or from a different thread will return `null`. +* `acquirePublishAcknowledgementControl()` may only be called once per received PUBLISH. Subsequent calls will return `null`. +* This is only relevant for QoS 1 messages. Calling it on a QoS 0 message will return `null`. +* If `acquirePublishAcknowledgementControl()` is not called, the client will automatically send the PUBACK when the callback returns. + +The following example shows how to acquire the acknowledgement handle within the callback and invoke it later: + +~~~ java +// A shared location to store the acknowledgement handle for later use +final AtomicReference pendingAck = new AtomicReference<>(); + +class MyPublishEvents implements Mqtt5ClientOptions.PublishEvents { + @Override + public void onMessageReceived(Mqtt5Client client, PublishReturn publishReturn) { + System.out.println("Message received on topic: " + + publishReturn.getPublishPacket().getTopic()); + + if (publishReturn.getPublishPacket().getQOS() == QOS.AT_LEAST_ONCE) { + // Acquire manual control of the PUBACK for this QoS 1 message. + // This must be called within the callback. Calling it outside the callback + // or after it returns will return null. + Mqtt5PublishAcknowledgementControlHandle handle = + publishReturn.acquirePublishAcknowledgementControl(); + + if (handle != null) { + // The PUBACK will NOT be sent automatically because we acquired the handle. + // Store it for later use after processing is complete. + pendingAck.set(handle); + } + } + } +} + +// ... build client, connect, and subscribe ... + +// After processing is complete, send the PUBACK by invoking the acknowledgement. +Mqtt5PublishAcknowledgementControlHandle handle = pendingAck.getAndSet(null); +if (handle != null) { + client.invokePublishAcknowledgement(handle); +} +~~~ + +**AWS IoT broker redelivery behavior** + +The AWS IoT broker will periodically resend unacknowledged QoS 1 PUBLISH packets. These redeliveries should be treated as duplicates even if the DUP flag in the PUBLISH packet is not set. If `acquirePublishAcknowledgementControl()` is not called again for a redelivered packet, the acknowledgement will be sent automatically. + +**Session resumption after disconnect/reconnect** + +Upon a disconnect and reconnect of the MQTT5 client, if a session is resumed, any previously acquired `Mqtt5PublishAcknowledgementControlHandle` is void. The broker will resend the unacknowledged PUBLISH packet, and `acquirePublishAcknowledgementControl()` must be called again within the callback for that resent packet. If the resent packet is not handled for manual acknowledgement, the acknowledgement will be sent automatically. + ## MQTT5 Best Practices Below are some best practices for the MQTT5 client that are recommended to follow for the best development experience: * When creating MQTT5 clients, make sure to use ClientIDs that are unique! If you connect two MQTT5 clients with the same ClientID, they will Disconnect each other! If you do not configure a ClientID, the MQTT5 server will automatically assign one. * Use the minimum QoS you can get away with for the lowest latency and bandwidth costs. For example, if you are sending data consistently multiple times per second and do not have to have a guarantee the server got each and every publish, using QoS 0 may be ideal compared to QoS 1. Of course, this heavily depends on your use case but generally it is recommended to use the lowest QoS possible. -* If you are getting unexpected disconnects when trying to connect to AWS IoT Core, make sure to check your IoT Core Thing’s policy and permissions to make sure your device is has the permissions it needs to connect! +* If you are getting unexpected disconnects when trying to connect to AWS IoT Core, make sure to check your IoT Core Thing's policy and permissions to make sure your device is has the permissions it needs to connect! * Make sure to always call `close()` when finished a MQTT5 client to avoid native resource leaks! * For [publish](https://awslabs.github.io/aws-crt-java/software/amazon/awssdk/crt/mqtt5/Mqtt5Client.html#publish(software.amazon.awssdk.crt.mqtt5.packets.PublishPacket)), [subscribe](https://awslabs.github.io/aws-crt-java/software/amazon/awssdk/crt/mqtt5/Mqtt5Client.html#subscribe(software.amazon.awssdk.crt.mqtt5.packets.SubscribePacket)), and [unsubscribe](https://awslabs.github.io/aws-crt-java/software/amazon/awssdk/crt/mqtt5/Mqtt5Client.html#unsubscribe(software.amazon.awssdk.crt.mqtt5.packets.UnsubscribePacket)), make sure to check the reason codes in the ACK ([PubAckPacket](https://awslabs.github.io/aws-crt-java/software/amazon/awssdk/crt/mqtt5/packets/PubAckPacket.html), [SubAckPacket](https://awslabs.github.io/aws-crt-java/software/amazon/awssdk/crt/mqtt5/packets/SubAckPacket.html), and [UnsubAckPacket](https://awslabs.github.io/aws-crt-java/software/amazon/awssdk/crt/mqtt5/packets/UnsubAckPacket.html) respectively) to see if the operation actually succeeded. * You MUST NOT perform blocking operations on any callback, or you will cause a deadlock. For example: in the `onMessageReceived` callback, do not send a publish, and then wait for the future to complete within the callback. The Client cannot do work until your callback returns, so the thread will be stuck. diff --git a/sdk/pom.xml b/sdk/pom.xml index 1992becd..263e8356 100644 --- a/sdk/pom.xml +++ b/sdk/pom.xml @@ -60,7 +60,7 @@ software.amazon.awssdk.crt aws-crt - 0.43.8 + 0.45.0 org.slf4j