Topic Aliasing Support (#722)

Co-authored-by: Bret Ambrose <[email protected]>
diff --git a/src/main/java/software/amazon/awssdk/crt/mqtt5/Mqtt5ClientOptions.java b/src/main/java/software/amazon/awssdk/crt/mqtt5/Mqtt5ClientOptions.java
index 10c227b..db54ca1 100644
--- a/src/main/java/software/amazon/awssdk/crt/mqtt5/Mqtt5ClientOptions.java
+++ b/src/main/java/software/amazon/awssdk/crt/mqtt5/Mqtt5ClientOptions.java
@@ -48,6 +48,7 @@
     private LifecycleEvents lifecycleEvents;
     private Consumer<Mqtt5WebsocketHandshakeTransformArgs> websocketHandshakeTransform;
     private PublishEvents publishEvents;
+    private TopicAliasingOptions topicAliasingOptions;
 
     /**
      * Returns the host name of the MQTT server to connect to.
@@ -258,6 +259,15 @@
     }
 
     /**
+     * Returns the topic aliasing options to be used by the client
+     *
+     * @return the topic aliasing options to be used by the client
+     */
+    public TopicAliasingOptions getTopicAliasingOptions() {
+        return this.topicAliasingOptions;
+    }
+
+    /**
      * Creates a Mqtt5ClientOptionsBuilder instance
      * @param builder The builder to get the Mqtt5ClientOptions values from
      */
@@ -282,6 +292,7 @@
         this.lifecycleEvents = builder.lifecycleEvents;
         this.websocketHandshakeTransform = builder.websocketHandshakeTransform;
         this.publishEvents = builder.publishEvents;
+        this.topicAliasingOptions = builder.topicAliasingOptions;
     }
 
     /*******************************************************************************
@@ -579,6 +590,7 @@
         private LifecycleEvents lifecycleEvents;
         private Consumer<Mqtt5WebsocketHandshakeTransformArgs> websocketHandshakeTransform;
         private PublishEvents publishEvents;
+        private TopicAliasingOptions topicAliasingOptions;
 
         /**
          * Sets the host name of the MQTT server to connect to.
@@ -836,6 +848,17 @@
         }
 
         /**
+         * Sets the topic aliasing options for clients constructed from this builder
+         *
+         * @param options topic aliasing options that the client should use
+         * @return The Mqtt5ClientOptionsBuilder object
+         */
+        public Mqtt5ClientOptionsBuilder withTopicAliasingOptions(TopicAliasingOptions options) {
+            this.topicAliasingOptions = options;
+            return this;
+        }
+
+        /**
          * Creates a new Mqtt5ClientOptionsBuilder instance
          *
          * @param hostName The host name of the MQTT server to connect to.
diff --git a/src/main/java/software/amazon/awssdk/crt/mqtt5/NegotiatedSettings.java b/src/main/java/software/amazon/awssdk/crt/mqtt5/NegotiatedSettings.java
index f6c2785..27a63bf 100644
--- a/src/main/java/software/amazon/awssdk/crt/mqtt5/NegotiatedSettings.java
+++ b/src/main/java/software/amazon/awssdk/crt/mqtt5/NegotiatedSettings.java
@@ -22,6 +22,8 @@
     private long sessionExpiryInterval;
     private int receiveMaximumFromServer;
     private long maximumPacketSizeToServer;
+    private int topicAliasMaximumToServer;
+    private int topicAliasMaximumToClient;
     private int serverKeepAlive;
     private boolean retainAvailable;
     private boolean wildcardSubscriptionsAvailable;
@@ -59,6 +61,21 @@
     }
 
     /**
+     * @return returns the maximum allowed topic alias value on publishes sent from client to server
+     */
+    public int getTopicAliasMaximumToServer() {
+        return this.topicAliasMaximumToServer;
+    }
+
+    /**
+     * @return returns the maximum allowed topic alias value on publishes sent from server to client
+     */
+    public int getTopicAliasMaximumToClient() {
+        return this.topicAliasMaximumToClient;
+    }
+
+
+    /**
      * Returns the maximum amount of time in seconds between client packets. The client should use PINGREQs to ensure this
      * limit is not breached.  The server will disconnect the client for inactivity if no MQTT packet is received
      * in a time interval equal to 1.5 x this value.
diff --git a/src/main/java/software/amazon/awssdk/crt/mqtt5/TopicAliasingOptions.java b/src/main/java/software/amazon/awssdk/crt/mqtt5/TopicAliasingOptions.java
new file mode 100644
index 0000000..73b85b0
--- /dev/null
+++ b/src/main/java/software/amazon/awssdk/crt/mqtt5/TopicAliasingOptions.java
@@ -0,0 +1,222 @@
+package software.amazon.awssdk.crt.mqtt5;
+
+import java.util.Map;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * Configuration for all client topic aliasing behavior.
+ */
+public class TopicAliasingOptions {
+
+    private OutboundTopicAliasBehaviorType outboundBehavior;
+    private Integer outboundCacheMaxSize;
+    private InboundTopicAliasBehaviorType inboundBehavior;
+    private Integer inboundCacheMaxSize;
+
+    /**
+     * Default constructor
+     */
+    public TopicAliasingOptions() {
+        this.outboundBehavior = OutboundTopicAliasBehaviorType.Default;
+        this.outboundCacheMaxSize = 0;
+        this.inboundBehavior = InboundTopicAliasBehaviorType.Default;
+        this.inboundCacheMaxSize = 0;
+    }
+
+    /**
+     * Controls what kind of outbound topic aliasing behavior the client should attempt to use.
+     *
+     * If topic aliasing is not supported by the server, this setting has no effect and any attempts to directly
+     * manipulate the topic alias id in outbound publishes will be ignored.
+     *
+     * By default, outbound topic aliasing is disabled.
+     *
+     * @param behavior outbound topic alias behavior to use
+     *
+     * @return the topic aliasing options object
+     */
+    public TopicAliasingOptions withOutboundBehavior(OutboundTopicAliasBehaviorType behavior) {
+        this.outboundBehavior = behavior;
+        return this;
+    }
+
+    /**
+     * If outbound topic aliasing is set to LRU, this controls the maximum size of the cache.  If outbound topic
+     * aliasing is set to LRU and this is zero or undefined, a sensible default is used (25).  If outbound topic
+     * aliasing is not set to LRU, then this setting has no effect.
+     *
+     * The final size of the cache is determined by the minimum of this setting and the value of the
+     * topic_alias_maximum property of the received CONNACK.  If the received CONNACK does not have an explicit
+     * positive value for that field, outbound topic aliasing is disabled for the duration of that connection.
+     *
+     * @param size maximum size to use for the outbound alias cache
+     *
+     * @return the topic aliasing options object
+     */
+    public TopicAliasingOptions withOutboundCacheMaxSize(int size) {
+        this.outboundCacheMaxSize = size;
+        return this;
+    }
+
+    /**
+     * Controls whether or not the client allows the broker to use topic aliasing when sending publishes.  Even if
+     * inbound topic aliasing is enabled, it is up to the server to choose whether or not to use it.
+     *
+     * If left undefined, then inbound topic aliasing is disabled.
+     *
+     * @param behavior inbound topic alias behavior to use
+     *
+     * @return the topic aliasing options object
+     */
+    public TopicAliasingOptions withInboundBehavior(InboundTopicAliasBehaviorType behavior) {
+        this.inboundBehavior = behavior;
+        return this;
+    }
+
+    /**
+     * If inbound topic aliasing is enabled, this will control the size of the inbound alias cache.  If inbound
+     * aliases are enabled and this is zero or undefined, then a sensible default will be used (25).  If inbound
+     * aliases are disabled, this setting has no effect.
+     *
+     * Behaviorally, this value overrides anything present in the topic_alias_maximum field of
+     * the CONNECT packet options.
+     *
+     * @param size maximum size to use for the inbound alias cache
+     *
+     * @return the topic aliasing options object
+     */
+    public TopicAliasingOptions withInboundCacheMaxSize(int size) {
+        this.inboundCacheMaxSize = size;
+        return this;
+    }
+
+    /**
+     * An enumeration that controls how the client applies topic aliasing to outbound publish packets.
+     *
+     * Topic alias behavior is described in <a href="https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901113">MQTT5 Topic Aliasing</a>
+     */
+    public enum OutboundTopicAliasBehaviorType {
+
+        /**
+         * Maps to Disabled.  This keeps the client from being broken (by default) if the broker
+         * topic aliasing implementation has a problem.
+         */
+        Default(0),
+
+        /**
+         * Outbound aliasing is the user's responsibility.  Client will cache and use
+         * previously-established aliases if they fall within the negotiated limits of the connection.
+         *
+         * The user must still always submit a full topic in their publishes because disconnections disrupt
+         * topic alias mappings unpredictably.  The client will properly use a requested alias when the most-recently-seen
+         * binding for a topic alias value matches the alias and topic in the publish packet.
+         */
+        Manual(1),
+
+        /**
+         * (Recommended) The client will ignore any user-specified topic aliasing and instead use an LRU cache to drive
+         * alias usage.
+         */
+        LRU(2),
+
+        /**
+         * Completely disable outbound topic aliasing.
+         */
+        Disabled(3);
+
+        private int value;
+
+        private OutboundTopicAliasBehaviorType(int value) {
+            this.value = value;
+        }
+
+        /**
+         * @return The native enum integer value associated with this Java enum value
+         */
+        public int getValue() {
+            return this.value;
+        }
+
+        /**
+         * Creates a Java OutboundTopicAliasBehaviorType enum value from a native integer value.
+         *
+         * @param value native integer value for the OutboundTopicAliasBehaviorType value
+         * @return a new OutboundTopicAliasBehaviorType value
+         */
+        public static OutboundTopicAliasBehaviorType getEnumValueFromInteger(int value) {
+            OutboundTopicAliasBehaviorType enumValue = enumMapping.get(value);
+            if (enumValue != null) {
+                return enumValue;
+            }
+            throw new RuntimeException("Illegal OutboundTopicAliasBehaviorType");
+        }
+
+        private static Map<Integer, OutboundTopicAliasBehaviorType> buildEnumMapping() {
+            return Stream.of(OutboundTopicAliasBehaviorType.values())
+                    .collect(Collectors.toMap(OutboundTopicAliasBehaviorType::getValue, Function.identity()));
+        }
+
+        private static Map<Integer, OutboundTopicAliasBehaviorType> enumMapping = buildEnumMapping();
+    }
+
+    /**
+     * An enumeration that controls whether or not the client allows the broker to send publishes that use topic
+     * aliasing.
+     *
+     * Topic alias behavior is described in https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901113
+     */
+    public enum InboundTopicAliasBehaviorType {
+
+        /**
+         * Maps to Disabled.  This keeps the client from being broken (by default) if the broker
+         * topic aliasing implementation has a problem.
+         */
+        Default(0),
+
+        /**
+         * Allow the server to send PUBLISH packets to the client that use topic aliasing
+         */
+        Enabled(1),
+
+        /**
+         * Forbid the server from sending PUBLISH packets to the client that use topic aliasing
+         */
+        Disabled(2);
+
+        private int value;
+
+        private InboundTopicAliasBehaviorType(int value) {
+            this.value = value;
+        }
+
+        /**
+         * @return The native enum integer value associated with this Java enum value
+         */
+        public int getValue() {
+            return this.value;
+        }
+
+        /**
+         * Creates a Java InboundTopicAliasBehaviorType enum value from a native integer value.
+         *
+         * @param value native integer value for the InboundTopicAliasBehaviorType value
+         * @return a new InboundTopicAliasBehaviorType value
+         */
+        public static InboundTopicAliasBehaviorType getEnumValueFromInteger(int value) {
+            InboundTopicAliasBehaviorType enumValue = enumMapping.get(value);
+            if (enumValue != null) {
+                return enumValue;
+            }
+            throw new RuntimeException("Illegal InboundTopicAliasBehaviorType");
+        }
+
+        private static Map<Integer, InboundTopicAliasBehaviorType> buildEnumMapping() {
+            return Stream.of(InboundTopicAliasBehaviorType.values())
+                    .collect(Collectors.toMap(InboundTopicAliasBehaviorType::getValue, Function.identity()));
+        }
+
+        private static Map<Integer, InboundTopicAliasBehaviorType> enumMapping = buildEnumMapping();
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/software/amazon/awssdk/crt/mqtt5/packets/ConnAckPacket.java b/src/main/java/software/amazon/awssdk/crt/mqtt5/packets/ConnAckPacket.java
index aa4c8c3..cab1436 100644
--- a/src/main/java/software/amazon/awssdk/crt/mqtt5/packets/ConnAckPacket.java
+++ b/src/main/java/software/amazon/awssdk/crt/mqtt5/packets/ConnAckPacket.java
@@ -27,6 +27,7 @@
     private Boolean retainAvailable;
     private Long maximumPacketSize;
     private String assignedClientIdentifier;
+    private Integer topicAliasMaximum;
     private String reasonString;
 
     private List<UserProperty> userProperties;
@@ -133,6 +134,17 @@
     }
 
     /**
+     * Returns the maximum topic alias value that the server will accept from the client.
+     *
+     * See <a href="https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901088">MQTT5 Topic Alias Maximum</a>
+     *
+     * @return maximum allowed topic alias value
+     */
+    public Integer getTopicAliasMaximum() {
+        return this.topicAliasMaximum;
+    }
+
+    /**
      * Returns additional diagnostic information about the result of the connection attempt.
      *
      * See <a href="https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901089">MQTT5 Reason String</a>
diff --git a/src/main/java/software/amazon/awssdk/crt/mqtt5/packets/PublishPacket.java b/src/main/java/software/amazon/awssdk/crt/mqtt5/packets/PublishPacket.java
index 8c3036f..c8ff2ae 100644
--- a/src/main/java/software/amazon/awssdk/crt/mqtt5/packets/PublishPacket.java
+++ b/src/main/java/software/amazon/awssdk/crt/mqtt5/packets/PublishPacket.java
@@ -23,6 +23,7 @@
     private String topic;
     private PayloadFormatIndicator payloadFormat;
     private Long messageExpiryIntervalSeconds;
+    private Long topicAlias;
     private String responseTopic;
     private byte[] correlationData;
     private List<Long> subscriptionIdentifiers;
@@ -108,6 +109,20 @@
     }
 
     /**
+     * Sent publishes - topic alias to use, if possible, when encoding this packet.  Only used if the
+     * client's outbound topic aliasing mode is set to Manual.
+     *
+     * Received publishes - topic alias used by the server when transmitting the publish to the client.
+     *
+     * See <a href="https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901113">MQTT5 Topic Alias</a>
+     *
+     * @return The topic alias associated with this PublishPacket.
+     */
+    public Long getTopicAlias() {
+        return this.topicAlias;
+    }
+
+    /**
      * Returns a opaque topic string intended to assist with request/response implementations.  Not internally meaningful to
      * MQTT5 or this client.
      *
@@ -177,6 +192,7 @@
         this.topic = builder.topic;
         this.payloadFormat = builder.payloadFormat;
         this.messageExpiryIntervalSeconds = builder.messageExpiryIntervalSeconds;
+        this.topicAlias = builder.topicAlias;
         this.responseTopic = builder.responseTopic;
         this.correlationData = builder.correlationData;
         this.contentType = builder.contentType;
@@ -270,6 +286,7 @@
         private String topic;
         private PayloadFormatIndicator payloadFormat;
         private Long messageExpiryIntervalSeconds;
+        private Long topicAlias;
         private String responseTopic;
         private byte[] correlationData;
         private String contentType;
@@ -360,6 +377,21 @@
         }
 
         /**
+         * Sets the topic alias to use when sending this publish.  Will only be used if the outbound topic aliasing
+         * behavior has been set to Manual.
+         *
+         * See <a href="https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901113">MQTT5 Topic Alias</a>
+         *
+         * @param topicAlias alias value to use.  Must be greater than 0 and less than 65536.
+         *
+         * @return The PublishPacketBuilder after setting the topic alias.
+         */
+        public PublishPacketBuilder withTopicAlias(long topicAlias) {
+            this.topicAlias = topicAlias;
+            return this;
+        }
+
+        /**
          * Sets the opaque topic string intended to assist with request/response implementations.  Not internally meaningful to
          * MQTT5 or this client.
          *
diff --git a/src/native/java_class_ids.c b/src/native/java_class_ids.c
index 53ed5ad..b143cce 100644
--- a/src/native/java_class_ids.c
+++ b/src/native/java_class_ids.c
@@ -1010,6 +1010,9 @@
     mqtt5_connack_packet_properties.connack_assigned_client_identifier_field_id = (*env)->GetFieldID(
         env, mqtt5_connack_packet_properties.connack_packet_class, "assignedClientIdentifier", "Ljava/lang/String;");
     AWS_FATAL_ASSERT(mqtt5_connack_packet_properties.connack_assigned_client_identifier_field_id);
+    mqtt5_connack_packet_properties.connack_topic_alias_maximum_field_id = (*env)->GetFieldID(
+        env, mqtt5_connack_packet_properties.connack_packet_class, "topicAliasMaximum", "Ljava/lang/Integer;");
+    AWS_FATAL_ASSERT(mqtt5_connack_packet_properties.connack_topic_alias_maximum_field_id);
     mqtt5_connack_packet_properties.connack_reason_string_field_id = (*env)->GetFieldID(
         env, mqtt5_connack_packet_properties.connack_packet_class, "reasonString", "Ljava/lang/String;");
     AWS_FATAL_ASSERT(mqtt5_connack_packet_properties.connack_reason_string_field_id);
@@ -1285,6 +1288,9 @@
     mqtt5_publish_packet_properties.publish_message_expiry_interval_seconds_field_id = (*env)->GetFieldID(
         env, mqtt5_publish_packet_properties.publish_packet_class, "messageExpiryIntervalSeconds", "Ljava/lang/Long;");
     AWS_FATAL_ASSERT(mqtt5_publish_packet_properties.publish_message_expiry_interval_seconds_field_id);
+    mqtt5_publish_packet_properties.publish_topic_alias_field_id =
+        (*env)->GetFieldID(env, mqtt5_publish_packet_properties.publish_packet_class, "topicAlias", "Ljava/lang/Long;");
+    AWS_FATAL_ASSERT(mqtt5_publish_packet_properties.publish_topic_alias_field_id);
     mqtt5_publish_packet_properties.publish_response_topic_field_id = (*env)->GetFieldID(
         env, mqtt5_publish_packet_properties.publish_packet_class, "responseTopic", "Ljava/lang/String;");
     AWS_FATAL_ASSERT(mqtt5_publish_packet_properties.publish_response_topic_field_id);
@@ -1354,6 +1360,14 @@
         (*env)->GetFieldID(
             env, mqtt5_negotiated_settings_properties.negotiated_settings_class, "maximumPacketSizeToServer", "J");
     AWS_FATAL_ASSERT(mqtt5_negotiated_settings_properties.negotiated_settings_maximum_packet_size_to_server_field_id);
+    mqtt5_negotiated_settings_properties.negotiated_settings_topic_alias_maximum_to_server_field_id =
+        (*env)->GetFieldID(
+            env, mqtt5_negotiated_settings_properties.negotiated_settings_class, "topicAliasMaximumToServer", "I");
+    AWS_FATAL_ASSERT(mqtt5_negotiated_settings_properties.negotiated_settings_topic_alias_maximum_to_server_field_id);
+    mqtt5_negotiated_settings_properties.negotiated_settings_topic_alias_maximum_to_client_field_id =
+        (*env)->GetFieldID(
+            env, mqtt5_negotiated_settings_properties.negotiated_settings_class, "topicAliasMaximumToClient", "I");
+    AWS_FATAL_ASSERT(mqtt5_negotiated_settings_properties.negotiated_settings_topic_alias_maximum_to_client_field_id);
     mqtt5_negotiated_settings_properties.negotiated_settings_server_keep_alive_field_id =
         (*env)->GetFieldID(env, mqtt5_negotiated_settings_properties.negotiated_settings_class, "serverKeepAlive", "I");
     AWS_FATAL_ASSERT(mqtt5_negotiated_settings_properties.negotiated_settings_server_keep_alive_field_id);
@@ -1564,6 +1578,48 @@
         "lifecycleEvents",
         "Lsoftware/amazon/awssdk/crt/mqtt5/Mqtt5ClientOptions$LifecycleEvents;");
     AWS_FATAL_ASSERT(mqtt5_client_options_properties.lifecycle_events_field_id);
+    mqtt5_client_options_properties.topic_aliasing_options_field_id = (*env)->GetFieldID(
+        env,
+        mqtt5_client_options_properties.client_options_class,
+        "topicAliasingOptions",
+        "Lsoftware/amazon/awssdk/crt/mqtt5/TopicAliasingOptions;");
+    AWS_FATAL_ASSERT(mqtt5_client_options_properties.topic_aliasing_options_field_id);
+}
+
+struct java_aws_mqtt5_topic_aliasing_options_properties mqtt5_topic_aliasing_options_properties;
+static void s_cache_topic_aliasing_options(JNIEnv *env) {
+    jclass cls = (*env)->FindClass(env, "software/amazon/awssdk/crt/mqtt5/TopicAliasingOptions");
+    AWS_FATAL_ASSERT(cls);
+    mqtt5_topic_aliasing_options_properties.mqtt5_topic_aliasing_options_class = (*env)->NewGlobalRef(env, cls);
+    AWS_FATAL_ASSERT(mqtt5_topic_aliasing_options_properties.mqtt5_topic_aliasing_options_class);
+
+    mqtt5_topic_aliasing_options_properties.outbound_behavior_field_id = (*env)->GetFieldID(
+        env,
+        mqtt5_topic_aliasing_options_properties.mqtt5_topic_aliasing_options_class,
+        "outboundBehavior",
+        "Lsoftware/amazon/awssdk/crt/mqtt5/TopicAliasingOptions$OutboundTopicAliasBehaviorType;");
+    AWS_FATAL_ASSERT(mqtt5_topic_aliasing_options_properties.outbound_behavior_field_id);
+
+    mqtt5_topic_aliasing_options_properties.outbound_cache_max_size_field_id = (*env)->GetFieldID(
+        env,
+        mqtt5_topic_aliasing_options_properties.mqtt5_topic_aliasing_options_class,
+        "outboundCacheMaxSize",
+        "Ljava/lang/Integer;");
+    AWS_FATAL_ASSERT(mqtt5_topic_aliasing_options_properties.outbound_cache_max_size_field_id);
+
+    mqtt5_topic_aliasing_options_properties.inbound_behavior_field_id = (*env)->GetFieldID(
+        env,
+        mqtt5_topic_aliasing_options_properties.mqtt5_topic_aliasing_options_class,
+        "inboundBehavior",
+        "Lsoftware/amazon/awssdk/crt/mqtt5/TopicAliasingOptions$InboundTopicAliasBehaviorType;");
+    AWS_FATAL_ASSERT(mqtt5_topic_aliasing_options_properties.inbound_behavior_field_id);
+
+    mqtt5_topic_aliasing_options_properties.inbound_cache_max_size_field_id = (*env)->GetFieldID(
+        env,
+        mqtt5_topic_aliasing_options_properties.mqtt5_topic_aliasing_options_class,
+        "inboundCacheMaxSize",
+        "Ljava/lang/Integer;");
+    AWS_FATAL_ASSERT(mqtt5_topic_aliasing_options_properties.inbound_cache_max_size_field_id);
 }
 
 struct java_aws_mqtt5_client_properties mqtt5_client_properties;
@@ -1682,6 +1738,43 @@
     AWS_FATAL_ASSERT(mqtt5_client_jitter_mode_properties.client_get_value_id);
 }
 
+struct java_aws_mqtt5_outbound_topic_alias_behavior_type_properties mqtt5_outbound_topic_alias_behavior_type_properties;
+
+static void s_cache_mqtt5_outbound_topic_alias_behavior_type(JNIEnv *env) {
+    jclass cls =
+        (*env)->FindClass(env, "software/amazon/awssdk/crt/mqtt5/TopicAliasingOptions$OutboundTopicAliasBehaviorType");
+    AWS_FATAL_ASSERT(cls);
+    mqtt5_outbound_topic_alias_behavior_type_properties.mqtt5_outbound_topic_alias_behavior_type_class =
+        (*env)->NewGlobalRef(env, cls);
+    AWS_FATAL_ASSERT(
+        mqtt5_outbound_topic_alias_behavior_type_properties.mqtt5_outbound_topic_alias_behavior_type_class);
+    // Functions
+    mqtt5_outbound_topic_alias_behavior_type_properties.get_value_method_id = (*env)->GetMethodID(
+        env,
+        mqtt5_outbound_topic_alias_behavior_type_properties.mqtt5_outbound_topic_alias_behavior_type_class,
+        "getValue",
+        "()I");
+    AWS_FATAL_ASSERT(mqtt5_outbound_topic_alias_behavior_type_properties.get_value_method_id);
+}
+
+struct java_aws_mqtt5_inbound_topic_alias_behavior_type_properties mqtt5_inbound_topic_alias_behavior_type_properties;
+
+static void s_cache_mqtt5_inbound_topic_alias_behavior_type(JNIEnv *env) {
+    jclass cls =
+        (*env)->FindClass(env, "software/amazon/awssdk/crt/mqtt5/TopicAliasingOptions$InboundTopicAliasBehaviorType");
+    AWS_FATAL_ASSERT(cls);
+    mqtt5_inbound_topic_alias_behavior_type_properties.mqtt5_inbound_topic_alias_behavior_type_class =
+        (*env)->NewGlobalRef(env, cls);
+    AWS_FATAL_ASSERT(mqtt5_inbound_topic_alias_behavior_type_properties.mqtt5_inbound_topic_alias_behavior_type_class);
+    // Functions
+    mqtt5_inbound_topic_alias_behavior_type_properties.get_value_method_id = (*env)->GetMethodID(
+        env,
+        mqtt5_inbound_topic_alias_behavior_type_properties.mqtt5_inbound_topic_alias_behavior_type_class,
+        "getValue",
+        "()I");
+    AWS_FATAL_ASSERT(mqtt5_inbound_topic_alias_behavior_type_properties.get_value_method_id);
+}
+
 struct java_aws_mqtt5_subscribe_packet_properties mqtt5_subscribe_packet_properties;
 
 static void s_cache_mqtt5_subscribe_packet(JNIEnv *env) {
@@ -2236,6 +2329,9 @@
     s_cache_boxed_boolean(env);
     s_cache_boxed_list(env);
     s_cache_boxed_array_list(env);
+    s_cache_mqtt5_outbound_topic_alias_behavior_type(env);
+    s_cache_mqtt5_inbound_topic_alias_behavior_type(env);
+    s_cache_topic_aliasing_options(env);
 }
 
 static aws_thread_once s_cache_once_init = AWS_THREAD_ONCE_STATIC_INIT;
diff --git a/src/native/java_class_ids.h b/src/native/java_class_ids.h
index fe0b2de..bca694a 100644
--- a/src/native/java_class_ids.h
+++ b/src/native/java_class_ids.h
@@ -463,6 +463,7 @@
     jfieldID connack_retain_available_field_id;
     jfieldID connack_maximum_packet_size_field_id;
     jfieldID connack_assigned_client_identifier_field_id;
+    jfieldID connack_topic_alias_maximum_field_id;
     jfieldID connack_reason_string_field_id;
     jfieldID connack_wildcard_subscriptions_available_field_id;
     jfieldID connack_subscription_identifiers_available_field_id;
@@ -561,6 +562,7 @@
     jfieldID publish_topic_field_id;
     jfieldID publish_payload_format_field_id;
     jfieldID publish_message_expiry_interval_seconds_field_id;
+    jfieldID publish_topic_alias_field_id;
     jfieldID publish_response_topic_field_id;
     jfieldID publish_correlation_data_field_id;
     jfieldID publish_content_type_field_id;
@@ -588,6 +590,8 @@
     jfieldID negotiated_settings_session_expiry_interval_field_id;
     jfieldID negotiated_settings_receive_maximum_from_server_field_id;
     jfieldID negotiated_settings_maximum_packet_size_to_server_field_id;
+    jfieldID negotiated_settings_topic_alias_maximum_to_server_field_id;
+    jfieldID negotiated_settings_topic_alias_maximum_to_client_field_id;
     jfieldID negotiated_settings_server_keep_alive_field_id;
     jfieldID negotiated_settings_retain_available_field_id;
     jfieldID negotiated_settings_wildcard_subscriptions_available_field_id;
@@ -650,6 +654,7 @@
     jfieldID ack_timeout_seconds_field_id;
     jfieldID publish_events_field_id;
     jfieldID lifecycle_events_field_id;
+    jfieldID topic_aliasing_options_field_id;
 };
 extern struct java_aws_mqtt5_client_options_properties mqtt5_client_options_properties;
 
@@ -703,6 +708,33 @@
 };
 extern struct java_aws_mqtt5_client_jitter_mode_properties mqtt5_client_jitter_mode_properties;
 
+/* mqtt5.Mqtt5ClientOptions.OutboundTopicAliasBehaviorType */
+struct java_aws_mqtt5_outbound_topic_alias_behavior_type_properties {
+    jclass mqtt5_outbound_topic_alias_behavior_type_class;
+    jmethodID get_value_method_id;
+};
+extern struct java_aws_mqtt5_outbound_topic_alias_behavior_type_properties
+    mqtt5_outbound_topic_alias_behavior_type_properties;
+
+/* mqtt5.Mqtt5ClientOptions.InboundTopicAliasBehaviorType */
+struct java_aws_mqtt5_inbound_topic_alias_behavior_type_properties {
+    jclass mqtt5_inbound_topic_alias_behavior_type_class;
+    jmethodID get_value_method_id;
+};
+extern struct java_aws_mqtt5_inbound_topic_alias_behavior_type_properties
+    mqtt5_inbound_topic_alias_behavior_type_properties;
+
+/* mqtt5.Mqtt5ClientOptions.TopicAliasingOptions */
+struct java_aws_mqtt5_topic_aliasing_options_properties {
+    jclass mqtt5_topic_aliasing_options_class;
+
+    jfieldID outbound_behavior_field_id;
+    jfieldID outbound_cache_max_size_field_id;
+    jfieldID inbound_behavior_field_id;
+    jfieldID inbound_cache_max_size_field_id;
+};
+extern struct java_aws_mqtt5_topic_aliasing_options_properties mqtt5_topic_aliasing_options_properties;
+
 /* mqtt5.packets.SubscribePacket */
 struct java_aws_mqtt5_subscribe_packet_properties {
     jclass subscribe_packet_class;
diff --git a/src/native/mqtt5_client.c b/src/native/mqtt5_client.c
index 2f9a92c..0a42126 100644
--- a/src/native/mqtt5_client.c
+++ b/src/native/mqtt5_client.c
@@ -1612,6 +1612,70 @@
     s_ws_handshake_destroy(ws_handshake);
 }
 
+static int s_initialize_topic_aliasing_options(
+    JNIEnv *env,
+    struct aws_mqtt5_client_topic_alias_options *topic_aliasing_options,
+    jobject jni_topic_aliasing_options) {
+
+    jobject jni_outbound_behavior = (*env)->GetObjectField(
+        env, jni_topic_aliasing_options, mqtt5_topic_aliasing_options_properties.outbound_behavior_field_id);
+    if (jni_outbound_behavior != NULL) {
+        jint enum_value = (*env)->CallIntMethod(
+            env, jni_outbound_behavior, mqtt5_outbound_topic_alias_behavior_type_properties.get_value_method_id);
+        if (aws_jni_check_and_clear_exception(env)) {
+            AWS_LOGF_ERROR(AWS_LS_MQTT_CLIENT, "Error getting native value from OutboundTopicAliasBehaviorType");
+            return aws_raise_error(AWS_ERROR_INVALID_STATE);
+        }
+
+        topic_aliasing_options->outbound_topic_alias_behavior =
+            (enum aws_mqtt5_client_outbound_topic_alias_behavior_type)enum_value;
+    }
+
+    jobject jni_outbound_cache_max_size = (*env)->GetObjectField(
+        env, jni_topic_aliasing_options, mqtt5_topic_aliasing_options_properties.outbound_cache_max_size_field_id);
+    if (jni_outbound_cache_max_size != NULL) {
+        jint int_value =
+            (*env)->CallIntMethod(env, jni_outbound_cache_max_size, boxed_integer_properties.integer_get_value_id);
+
+        if (int_value < 0 || int_value > UINT16_MAX) {
+            AWS_LOGF_ERROR(AWS_LS_MQTT_CLIENT, "Invalid outbound cache size value: %d", int_value);
+            return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT);
+        }
+
+        topic_aliasing_options->outbound_alias_cache_max_size = (uint16_t)int_value;
+    }
+
+    jobject jni_inbound_behavior = (*env)->GetObjectField(
+        env, jni_topic_aliasing_options, mqtt5_topic_aliasing_options_properties.inbound_behavior_field_id);
+    if (jni_inbound_behavior != NULL) {
+        jint enum_value = (*env)->CallIntMethod(
+            env, jni_inbound_behavior, mqtt5_inbound_topic_alias_behavior_type_properties.get_value_method_id);
+        if (aws_jni_check_and_clear_exception(env)) {
+            AWS_LOGF_ERROR(AWS_LS_MQTT_CLIENT, "Error getting native value from InboundTopicAliasBehaviorType");
+            return aws_raise_error(AWS_ERROR_INVALID_STATE);
+        }
+
+        topic_aliasing_options->inbound_topic_alias_behavior =
+            (enum aws_mqtt5_client_inbound_topic_alias_behavior_type)enum_value;
+    }
+
+    jobject jni_inbound_cache_max_size = (*env)->GetObjectField(
+        env, jni_topic_aliasing_options, mqtt5_topic_aliasing_options_properties.inbound_cache_max_size_field_id);
+    if (jni_inbound_cache_max_size != NULL) {
+        jint int_value =
+            (*env)->CallIntMethod(env, jni_inbound_cache_max_size, boxed_integer_properties.integer_get_value_id);
+
+        if (int_value < 0 || int_value > UINT16_MAX) {
+            AWS_LOGF_ERROR(AWS_LS_MQTT_CLIENT, "Invalid inbound cache size value: %d", int_value);
+            return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT);
+        }
+
+        topic_aliasing_options->inbound_alias_cache_size = (uint16_t)int_value;
+    }
+
+    return AWS_OP_SUCCESS;
+}
+
 /*******************************************************************************
  * JNI FUNCTIONS
  ******************************************************************************/
@@ -2032,6 +2096,18 @@
         java_client->jni_lifecycle_events = (*env)->NewGlobalRef(env, jni_lifecycle_events);
     }
 
+    struct aws_mqtt5_client_topic_alias_options topic_aliasing_options;
+    AWS_ZERO_STRUCT(topic_aliasing_options);
+    jobject jni_topic_aliasing_options =
+        (*env)->GetObjectField(env, jni_options, mqtt5_client_options_properties.topic_aliasing_options_field_id);
+
+    if (jni_topic_aliasing_options != NULL) {
+        if (s_initialize_topic_aliasing_options(env, &topic_aliasing_options, jni_topic_aliasing_options) ==
+            AWS_OP_SUCCESS) {
+            client_options.topic_aliasing_options = &topic_aliasing_options;
+        }
+    }
+
     client_options.client_termination_handler = &s_aws_mqtt5_client_java_termination;
     client_options.client_termination_handler_user_data = (void *)java_client;
 
diff --git a/src/native/mqtt5_packets.c b/src/native/mqtt5_packets.c
index bdcf82d..d7d3fea 100644
--- a/src/native/mqtt5_packets.c
+++ b/src/native/mqtt5_packets.c
@@ -1215,6 +1215,21 @@
         java_packet->packet.message_expiry_interval_seconds = &java_packet->message_expiry_interval_seconds;
     }
 
+    if (aws_get_uint16_from_jobject(
+            env,
+            java_publish_packet,
+            mqtt5_publish_packet_properties.publish_topic_alias_field_id,
+            s_publish_packet_string,
+            "topic alias",
+            &java_packet->topic_alias,
+            true,
+            &was_value_set) == AWS_OP_ERR) {
+        goto on_error;
+    }
+    if (was_value_set) {
+        java_packet->packet.topic_alias = &java_packet->topic_alias;
+    }
+
     if (aws_get_string_from_jobject(
             env,
             java_publish_packet,
diff --git a/src/native/mqtt5_utils.c b/src/native/mqtt5_utils.c
index ad4cff5..2aa46e9 100644
--- a/src/native/mqtt5_utils.c
+++ b/src/native/mqtt5_utils.c
@@ -339,6 +339,15 @@
             true) != AWS_OP_SUCCESS) {
         return NULL;
     }
+    if (s_set_jni_uint16_t_field_in_packet(
+            env,
+            native_connack_data->topic_alias_maximum,
+            connack_data,
+            mqtt5_connack_packet_properties.connack_topic_alias_maximum_field_id,
+            "topic alias maximum",
+            true) != AWS_OP_SUCCESS) {
+        return NULL;
+    };
     if (s_set_jni_string_field_in_packet(
             env,
             native_connack_data->reason_string,
@@ -514,6 +523,16 @@
     (*env)->SetIntField(
         env,
         negotiated_settings_data,
+        mqtt5_negotiated_settings_properties.negotiated_settings_topic_alias_maximum_to_server_field_id,
+        (jint)native_negotiated_settings_data->topic_alias_maximum_to_server);
+    (*env)->SetIntField(
+        env,
+        negotiated_settings_data,
+        mqtt5_negotiated_settings_properties.negotiated_settings_topic_alias_maximum_to_client_field_id,
+        (jint)native_negotiated_settings_data->topic_alias_maximum_to_client);
+    (*env)->SetIntField(
+        env,
+        negotiated_settings_data,
         mqtt5_negotiated_settings_properties.negotiated_settings_server_keep_alive_field_id,
         (jint)native_negotiated_settings_data->server_keep_alive);
     (*env)->SetBooleanField(
@@ -614,6 +633,16 @@
         return NULL;
     }
 
+    if (s_set_jni_uint16_t_field_in_packet(
+            env,
+            publish->topic_alias,
+            publish_packet_data,
+            mqtt5_publish_packet_properties.publish_topic_alias_field_id,
+            "topic alias",
+            true) != AWS_OP_SUCCESS) {
+        return NULL;
+    }
+
     if (s_set_jni_string_field_in_packet(
             env,
             publish->response_topic,