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 10c227bc3..db54ca1b0 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 @@ public class Mqtt5ClientOptions { private LifecycleEvents lifecycleEvents; private Consumer websocketHandshakeTransform; private PublishEvents publishEvents; + private TopicAliasingOptions topicAliasingOptions; /** * Returns the host name of the MQTT server to connect to. @@ -257,6 +258,15 @@ public PublishEvents getPublishEvents() { return this.publishEvents; } + /** + * 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 @@ public Mqtt5ClientOptions(Mqtt5ClientOptionsBuilder builder) { this.lifecycleEvents = builder.lifecycleEvents; this.websocketHandshakeTransform = builder.websocketHandshakeTransform; this.publishEvents = builder.publishEvents; + this.topicAliasingOptions = builder.topicAliasingOptions; } /******************************************************************************* @@ -579,6 +590,7 @@ static final public class Mqtt5ClientOptionsBuilder { private LifecycleEvents lifecycleEvents; private Consumer websocketHandshakeTransform; private PublishEvents publishEvents; + private TopicAliasingOptions topicAliasingOptions; /** * Sets the host name of the MQTT server to connect to. @@ -835,6 +847,17 @@ public Mqtt5ClientOptionsBuilder withPublishEvents(PublishEvents publishEvents) return this; } + /** + * 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 * 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 f6c278559..27a63bf13 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 @@ public class NegotiatedSettings { private long sessionExpiryInterval; private int receiveMaximumFromServer; private long maximumPacketSizeToServer; + private int topicAliasMaximumToServer; + private int topicAliasMaximumToClient; private int serverKeepAlive; private boolean retainAvailable; private boolean wildcardSubscriptionsAvailable; @@ -58,6 +60,21 @@ public long getMaximumPacketSizeToServer() { return this.maximumPacketSizeToServer; } + /** + * @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 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 000000000..73b85b00f --- /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 MQTT5 Topic Aliasing + */ + 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 buildEnumMapping() { + return Stream.of(OutboundTopicAliasBehaviorType.values()) + .collect(Collectors.toMap(OutboundTopicAliasBehaviorType::getValue, Function.identity())); + } + + private static Map 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 buildEnumMapping() { + return Stream.of(InboundTopicAliasBehaviorType.values()) + .collect(Collectors.toMap(InboundTopicAliasBehaviorType::getValue, Function.identity())); + } + + private static Map 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 aa4c8c318..cab14368f 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 @@ public class ConnAckPacket { private Boolean retainAvailable; private Long maximumPacketSize; private String assignedClientIdentifier; + private Integer topicAliasMaximum; private String reasonString; private List userProperties; @@ -132,6 +133,17 @@ public String getAssignedClientIdentifier() { return this.assignedClientIdentifier; } + /** + * Returns the maximum topic alias value that the server will accept from the client. + * + * See MQTT5 Topic Alias Maximum + * + * @return maximum allowed topic alias value + */ + public Integer getTopicAliasMaximum() { + return this.topicAliasMaximum; + } + /** * Returns additional diagnostic information about the result of the connection attempt. * 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 8c3036f2d..c8ff2ae54 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 @@ public class PublishPacket { private String topic; private PayloadFormatIndicator payloadFormat; private Long messageExpiryIntervalSeconds; + private Long topicAlias; private String responseTopic; private byte[] correlationData; private List subscriptionIdentifiers; @@ -107,6 +108,20 @@ public Long getMessageExpiryIntervalSeconds() { return this.messageExpiryIntervalSeconds; } + /** + * 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 MQTT5 Topic Alias + * + * @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 @@ private PublishPacket(PublishPacketBuilder builder) { 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 @@ static final public class PublishPacketBuilder { private String topic; private PayloadFormatIndicator payloadFormat; private Long messageExpiryIntervalSeconds; + private Long topicAlias; private String responseTopic; private byte[] correlationData; private String contentType; @@ -359,6 +376,21 @@ public PublishPacketBuilder withMessageExpiryIntervalSeconds(Long messageExpiryI return this; } + /** + * 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 MQTT5 Topic Alias + * + * @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 53ed5adbc..b143cce96 100644 --- a/src/native/java_class_ids.c +++ b/src/native/java_class_ids.c @@ -1010,6 +1010,9 @@ static void s_cache_mqtt5_connack_packet(JNIEnv *env) { 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 @@ static void s_cache_mqtt5_publish_packet(JNIEnv *env) { 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 @@ static void s_cache_mqtt5_negotiated_settings(JNIEnv *env) { (*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 @@ static void s_cache_mqtt5_client_options(JNIEnv *env) { "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 @@ static void s_cache_mqtt5_client_jitter_mode(JNIEnv *env) { 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 @@ static void s_cache_java_class_ids(void *user_data) { 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 fe0b2debd..bca694a83 100644 --- a/src/native/java_class_ids.h +++ b/src/native/java_class_ids.h @@ -463,6 +463,7 @@ struct java_aws_mqtt5_connack_packet_properties { 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 @@ struct java_aws_mqtt5_publish_packet_properties { 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 @@ struct java_aws_mqtt5_negotiated_settings_properties { 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 @@ struct java_aws_mqtt5_client_options_properties { 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 @@ struct java_aws_mqtt5_client_jitter_mode_properties { }; 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 2f9a92c12..0a421267e 100644 --- a/src/native/mqtt5_client.c +++ b/src/native/mqtt5_client.c @@ -1612,6 +1612,70 @@ JNIEXPORT void JNICALL Java_software_amazon_awssdk_crt_mqtt5_Mqtt5Client_mqtt5Cl 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 @@ JNIEXPORT jlong JNICALL Java_software_amazon_awssdk_crt_mqtt5_Mqtt5Client_mqtt5C 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 bdcf82d59..d7d3fea2e 100644 --- a/src/native/mqtt5_packets.c +++ b/src/native/mqtt5_packets.c @@ -1215,6 +1215,21 @@ struct aws_mqtt5_packet_publish_view_java_jni *aws_mqtt5_packet_publish_view_cre 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 ad4cff5d7..2aa46e928 100644 --- a/src/native/mqtt5_utils.c +++ b/src/native/mqtt5_utils.c @@ -339,6 +339,15 @@ jobject s_aws_mqtt5_client_create_jni_connack_packet_from_native( 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, @@ -511,6 +520,16 @@ jobject s_aws_mqtt5_client_create_jni_negotiated_settings_from_native( negotiated_settings_data, mqtt5_negotiated_settings_properties.negotiated_settings_maximum_packet_size_to_server_field_id, (jlong)native_negotiated_settings_data->maximum_packet_size_to_server); + (*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, @@ -614,6 +633,16 @@ jobject s_aws_mqtt5_client_create_jni_publish_packet_from_native( 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,