metadataEntries) { this.metadataEntries = metadataEntries; }
- /**
- * Maps InboundTopicAliasBehaviorType value to its metrics character.
- *
- * @param value the int value from InboundTopicAliasBehaviorType.getValue()
- * @return single-character metrics value, or null if Default (should be omitted)
- */
- private static String inboundTopicAliasBehaviorValue(int value) {
- switch (value) {
- case 1: return "A"; // Enabled
- case 2: return "B"; // Disabled
- default: return null;
- }
- }
/**
- * Maps TlsCipherPreference value to its metrics character.
- *
- * @param value the int value from TlsCipherPreference.getValue()
- * @return single-character metrics value, or null if SYSTEM_DEFAULT (should be omitted)
- */
- private static String tlsCipherPreferenceValue(int value) {
- switch (value) {
- case 6: return "A"; // TLS_CIPHER_PREF_PQ_TLSv1_0_2021_05
- case 8: return "B"; // TLS_CIPHER_PQ_DEFAULT
- case 9: return "C"; // TLS_CIPHER_PREF_TLSv1_2_2025
- default: return null;
- }
+ * Builds the final metrics object for an MQTT5 client by encoding the CRT
+ * feature list from {@code clientOptions} and merging it with any
+ * user-supplied metadata.
+ *
+ * @param clientOptions MQTT5 client options containing connection configuration and user metrics
+ * @return the merged metrics object ready to be passed to JNI
+ */
+ public static IoTDeviceSDKMetrics createMetricsMqtt5(Mqtt5ClientOptions clientOptions) {
+ String crtFeatureList = getEncodedFeatureListMqtt5(clientOptions);
+ return createMetrics(clientOptions.getUserMetrics(), crtFeatureList);
}
/**
- * Maps TlsVersions value to its metrics character.
- *
- * @param value the int value from TlsVersions.getValue()
- * @return single-character metrics value, or null if SYS_DEFAULTS (should be omitted)
- */
- private static String minimumTlsVersionValue(int value) {
- switch (value) {
- case 0: return "A"; // SSLv3
- case 1: return "B"; // TLSv1
- case 2: return "C"; // TLSv1_1
- case 3: return "D"; // TLSv1_2
- case 4: return "E"; // TLSv1_3
- default: return null;
+ * Builds the final metrics object for an MQTT3 connection by encoding the CRT
+ * feature list from the connection configuration and merging it
+ * with any user-supplied metadata.
+ *
+ * @param config the MQTT3 connection configuration containing proxy, TLS, and user metrics
+ * @return the merged metrics object ready to be passed to JNI
+ */
+ public static IoTDeviceSDKMetrics createMetricsMqtt3(MqttConnectionConfig config) {
+ TlsContext tls = null;
+ if (config.getMqttClient() != null) {
+ tls = config.getMqttClient().getTlsContext();
+ } else if (config.getMqtt5Client() != null) {
+ tls = config.getMqtt5Client().getClientOptions().getTlsContext();
}
+ String crtFeatureList = getEncodedFeatureListMqtt3(config.getHttpProxyOptions(), tls);
+ return createMetrics(config.getMetrics(), crtFeatureList);
}
- // Feature List Encoding
/**
* Generates the encoded feature list string for metrics from MQTT5 client options.
*
+ *
* Format: "ID/Value,ID/Value,..."
* Example: "A/B,C/A,F/5,G/A" means retry_jitter_mode=FULL,
* offline_queue_behavior=FAIL_NON_QOS1, protocol=MQTT5, socket=POSIX.
@@ -204,65 +108,59 @@ private static String minimumTlsVersionValue(int value) {
* - G (socket_implementation): detected from platform
*
* Conditionally includes (only when not DEFAULT):
- * A (retry_jitter_mode), B (session_behavior), C (offline_queue_behavior),
- * D (outbound_topic_alias), E (inbound_topic_alias), H (http_proxy_type),
- * J (tls_cipher_preference), K (minimum_tls_version)
+ * A (retry_jitter_mode),
+ * B (session_behavior),
+ * C (offline_queue_behavior),
+ * D (outbound_topic_alias),
+ * E (inbound_topic_alias),
+ * H (http_proxy_type),
+ * J (tls_cipher_preference),
+ * K (minimum_tls_version)
*
* Feature I (certificate_source) is set at the IoT SDK level, not here.
*
* @param clientOptions MQTT5 client options containing connection configuration
* @return the encoded feature list string
*/
- public static String getEncodedFeatureListMqtt5(Mqtt5ClientOptions clientOptions) {
+ private static String getEncodedFeatureListMqtt5(Mqtt5ClientOptions clientOptions) {
List features = new ArrayList<>();
- // A: retry_jitter_mode
if (clientOptions.getRetryJitterMode() != null) {
String val = retryJitterModeValue(clientOptions.getRetryJitterMode());
if (val != null) features.add(RETRY_JITTER_MODE + "/" + val);
}
- // B: session_behavior
if (clientOptions.getSessionBehavior() != null) {
- String val = sessionBehaviorValue(clientOptions.getSessionBehavior().getValue());
+ String val = sessionBehaviorValue(clientOptions.getSessionBehavior());
if (val != null) features.add(SESSION_BEHAVIOR + "/" + val);
}
- // C: offline_queue_behavior
if (clientOptions.getOfflineQueueBehavior() != null) {
- String val = offlineQueueBehaviorValue(clientOptions.getOfflineQueueBehavior().getValue());
+ String val = offlineQueueBehaviorValue(clientOptions.getOfflineQueueBehavior());
if (val != null) features.add(OFFLINE_QUEUE_BEHAVIOR + "/" + val);
}
- // D & E: topic aliasing
TopicAliasingOptions topicAliasing = clientOptions.getTopicAliasingOptions();
if (topicAliasing != null) {
if (topicAliasing.getOutboundBehavior() != null) {
- String val = outboundTopicAliasBehaviorValue(topicAliasing.getOutboundBehavior().getValue());
+ String val = outboundTopicAliasBehaviorValue(topicAliasing.getOutboundBehavior());
if (val != null) features.add(OUTBOUND_TOPIC_ALIAS_BEHAVIOR + "/" + val);
}
if (topicAliasing.getInboundBehavior() != null) {
- String val = inboundTopicAliasBehaviorValue(topicAliasing.getInboundBehavior().getValue());
+ String val = inboundTopicAliasBehaviorValue(topicAliasing.getInboundBehavior());
if (val != null) features.add(INBOUND_TOPIC_ALIAS_BEHAVIOR + "/" + val);
}
}
- // F: protocol_version - always MQTT5
features.add(PROTOCOL_VERSION + "/" + protocolVersionValue(true));
-
- // G: socket_implementation - always present
features.add(SOCKET_IMPLEMENTATION + "/" + socketImplementationValue());
- // H: http_proxy_type
HttpProxyOptions proxyOptions = clientOptions.getHttpProxyOptions();
if (proxyOptions != null) {
boolean proxyUsesTls = proxyOptions.getTlsContext() != null;
features.add(HTTP_PROXY_TYPE + "/" + httpProxyTypeValue(proxyUsesTls));
}
- // I: certificate_source - set at IoT SDK level, not here
-
- // J & K: TLS properties
TlsContext tlsCtx = clientOptions.getTlsContext();
if (tlsCtx != null) {
String val = tlsCipherPreferenceValue(tlsCtx.getCipherPreference());
@@ -277,6 +175,9 @@ public static String getEncodedFeatureListMqtt5(Mqtt5ClientOptions clientOptions
/**
* Generates the encoded feature list string for metrics from MQTT3 connection parameters.
+ *
+ *
+ * Format: "ID/Value,ID/Value,..."
*
* MQTT3 connections always include:
* - F (protocol_version): set to MQTT311
@@ -289,22 +190,17 @@ public static String getEncodedFeatureListMqtt5(Mqtt5ClientOptions clientOptions
* @param tlsCtx optional TLS context used by the connection
* @return the encoded feature list string
*/
- public static String getEncodedFeatureListMqtt3(HttpProxyOptions proxyOptions, TlsContext tlsCtx) {
+ private static String getEncodedFeatureListMqtt3(HttpProxyOptions proxyOptions, TlsContext tlsCtx) {
List features = new ArrayList<>();
- // F: protocol_version - always MQTT311
features.add(PROTOCOL_VERSION + "/" + protocolVersionValue(false));
-
- // G: socket_implementation
features.add(SOCKET_IMPLEMENTATION + "/" + socketImplementationValue());
- // H: http_proxy_type
if (proxyOptions != null) {
boolean proxyUsesTls = proxyOptions.getTlsContext() != null;
features.add(HTTP_PROXY_TYPE + "/" + httpProxyTypeValue(proxyUsesTls));
}
- // J & K: TLS properties
if (tlsCtx != null) {
String val = tlsCipherPreferenceValue(tlsCtx.getCipherPreference());
if (val != null) features.add(TLS_CIPHER_PREFERENCE + "/" + val);
@@ -316,18 +212,15 @@ public static String getEncodedFeatureListMqtt3(HttpProxyOptions proxyOptions, T
return String.join(",", features);
}
- // ======================== Feature List Merging ========================
-
/**
* Merges CRT-generated features with user-provided (IoT SDK) features.
- * When both lists contain the same feature ID, the user-provided value
- * takes precedence. Insertion order is preserved.
+ * When both lists contain the same feature ID, the user-provided value takes precedence.
*
- * @param crtFeatures CRT-generated feature list string (e.g., "F/5,G/A")
- * @param userFeatures user-provided feature list from the IoT SDK (may be null or empty)
- * @return the merged feature list string
+ * @param crtFeatures CRT-generated feature list (e.g. {@code "F/5,G/A"}); may be {@code null} or empty
+ * @param userFeatures user-provided feature list from the IoT SDK; may be {@code null} or empty
+ * @return the merged feature list string (never {@code null}; empty if both inputs are empty)
*/
- public static String mergeFeatureLists(String crtFeatures, String userFeatures) {
+ private static String mergeFeatureLists(String crtFeatures, String userFeatures) {
LinkedHashMap merged = new LinkedHashMap<>();
parseFeatures(crtFeatures, merged);
parseFeatures(userFeatures, merged);
@@ -340,49 +233,6 @@ public static String mergeFeatureLists(String crtFeatures, String userFeatures)
return sb.toString();
}
- /**
- * Parses a feature string into key-value pairs in the given map.
- * Later entries overwrite earlier ones with the same key.
- *
- * @param featureStr comma-separated feature string (e.g., "A/B,F/5")
- * @param map target map to populate
- */
- private static void parseFeatures(String featureStr, Map map) {
- if (featureStr == null || featureStr.isEmpty()) return;
- for (String pair : featureStr.split(",")) {
- int slash = pair.indexOf('/');
- if (slash > 0) {
- map.put(pair.substring(0, slash), pair.substring(slash + 1));
- }
- }
- }
-
- // ======================== Metrics Creation ========================
-
- /**
- * Creates the final IoTDeviceSDKMetrics object for an MQTT5 client.
- *
- * @param clientOptions MQTT5 client options containing connection configuration
- * @param userMetrics optional metrics configuration from the IoT SDK (may be null)
- * @return the final metrics object ready for JNI passing
- */
- public static IoTDeviceSDKMetrics createMetricsMqtt5(Mqtt5ClientOptions clientOptions, IoTDeviceSDKMetrics userMetrics) {
- String crtFeatureList = getEncodedFeatureListMqtt5(clientOptions);
- return createMetrics(userMetrics, crtFeatureList);
- }
-
- /**
- * Creates the final IoTDeviceSDKMetrics object for an MQTT3 connection.
- *
- * @param userMetrics optional metrics configuration from the IoT SDK (may be null)
- * @param proxyOptions optional HTTP proxy options from the connection
- * @param tlsCtx optional TLS context used by the connection
- * @return the final metrics object ready for JNI passing
- */
- public static IoTDeviceSDKMetrics createMetricsMqtt3(IoTDeviceSDKMetrics userMetrics, HttpProxyOptions proxyOptions, TlsContext tlsCtx) {
- String crtFeatureList = getEncodedFeatureListMqtt3(proxyOptions, tlsCtx);
- return createMetrics(userMetrics, crtFeatureList);
- }
/**
* Metrics creation logic:
@@ -391,19 +241,17 @@ public static IoTDeviceSDKMetrics createMetricsMqtt3(IoTDeviceSDKMetrics userMet
* 3. IoTSDKFeature: merged if user version matches, else CRT only
* 4. Other user metadata: passed through unchanged
*
- * @param userMetrics optional metrics from IoT SDK (may be null)
+ * @param userMetrics optional metrics from a higher-level IoT SDK (may be {@code null})
* @param crtFeatureList encoded CRT feature list string
* @return the final metrics object
*/
private static IoTDeviceSDKMetrics createMetrics(IoTDeviceSDKMetrics userMetrics, String crtFeatureList) {
String libraryName = (userMetrics != null) ? userMetrics.getLibraryName() : "IoTDeviceSDK/Java";
- // CRTVersion: not modifiable by user
String crtVersion = new PackageInfo().version.toString();
LinkedHashMap metadata = new LinkedHashMap<>();
metadata.put("CRTVersion", crtVersion);
- // Extract user metadata
String userMetricsVersion = null;
String userFeature = "";
@@ -419,7 +267,6 @@ private static IoTDeviceSDKMetrics createMetrics(IoTDeviceSDKMetrics userMetrics
}
}
- // Merge features: if version matches, merge CRT + SDK; otherwise CRT only
if (userMetricsVersion != null && isNumeric(userMetricsVersion)
&& Integer.parseInt(userMetricsVersion) == IOT_SDK_METRICS_FEATURE_VERSION) {
metadata.put("IoTSDKFeature", mergeFeatureLists(crtFeatureList, userFeature));
@@ -427,10 +274,8 @@ private static IoTDeviceSDKMetrics createMetrics(IoTDeviceSDKMetrics userMetrics
metadata.put("IoTSDKFeature", mergeFeatureLists(crtFeatureList, ""));
}
- // Always set current metrics version
metadata.put("IoTSDKMetricsVersion", String.valueOf(IOT_SDK_METRICS_FEATURE_VERSION));
- // Build final metrics object
List entries = new ArrayList<>();
for (Map.Entry entry : metadata.entrySet()) {
entries.add(new IoTMetricsMetadata(entry.getKey(), entry.getValue()));
@@ -439,11 +284,161 @@ private static IoTDeviceSDKMetrics createMetrics(IoTDeviceSDKMetrics userMetrics
return new IoTDeviceSDKMetrics(libraryName, entries);
}
+ private static void parseFeatures(String featureStr, Map map) {
+ if (featureStr == null || featureStr.isEmpty()) return;
+ for (String pair : featureStr.split(",")) {
+ int slash = pair.indexOf('/');
+ if (slash > 0) {
+ map.put(pair.substring(0, slash), pair.substring(slash + 1));
+ }
+ }
+ }
+
+ /**
+ * @param isMqtt5 {@code true} for MQTT5, {@code false} for MQTT3
+ * @return the encoded protocol version value ({@code "5"} or {@code "3"})
+ */
+ private static String protocolVersionValue(boolean isMqtt5) {
+ return isMqtt5 ? "5" : "3";
+ }
+
+ /**
+ * @return {@code "B"} on Windows (IOCP), {@code "A"} otherwise (POSIX). Defaults to {@code "A"} if OS detection fails.
+ */
+ private static String socketImplementationValue() {
+ try {
+ return "windows".equals(CRT.getOSIdentifier()) ? "B" : "A";
+ } catch (Exception e) {
+ return "A";
+ }
+ }
+
+ /**
+ * @param proxyUsesTls whether the proxy connection is TLS-tunneled
+ * @return {@code "B"} for TLS proxy, {@code "A"} for plaintext proxy
+ */
+ private static String httpProxyTypeValue(boolean proxyUsesTls) {
+ return proxyUsesTls ? "B" : "A";
+ }
+
+ /**
+ * Encodes a {@link JitterMode} into its single-character metrics value.
+ *
+ * @param mode the configured jitter mode
+ * @return {@code "A"} for None, {@code "B"} for Full, {@code "C"} for Decorrelated, or {@code null} for Default/unknown
+ */
+ private static String retryJitterModeValue(JitterMode mode) {
+ switch (mode) {
+ case None: return "A";
+ case Full: return "B";
+ case Decorrelated: return "C";
+ default: return null;
+ }
+ }
+
+ /**
+ * Encodes an MQTT5 session behavior enum value.
+ *
+ * @param value the {@code ClientSessionBehavior} ordinal
+ * @return {@code "A"} for CLEAN, {@code "B"} for REJOIN_POST_SUCCESS, {@code "C"} for REJOIN_ALWAYS,
+ * or {@code null} for DEFAULT/unknown
+ */
+ private static String sessionBehaviorValue(Mqtt5ClientOptions.ClientSessionBehavior behavior) {
+ switch (behavior) {
+ case CLEAN: return "A";
+ case REJOIN_POST_SUCCESS: return "B";
+ case REJOIN_ALWAYS: return "C";
+ default: return null;
+ }
+ }
+
+ /**
+ * Encodes an MQTT5 offline queue behavior enum value.
+ *
+ * @param value the {@code ClientOfflineQueueBehavior} ordinal
+ * @return {@code "A"} for FAIL_NON_QOS1_PUBLISH_ON_DISCONNECT, {@code "B"} for FAIL_QOS0_PUBLISH_ON_DISCONNECT,
+ * {@code "C"} for FAIL_ALL_ON_DISCONNECT, or {@code null} for DEFAULT/unknown
+ */
+ private static String offlineQueueBehaviorValue(Mqtt5ClientOptions.ClientOfflineQueueBehavior behavior) {
+ switch (behavior) {
+ case FAIL_NON_QOS1_PUBLISH_ON_DISCONNECT: return "A";
+ case FAIL_QOS0_PUBLISH_ON_DISCONNECT: return "B";
+ case FAIL_ALL_ON_DISCONNECT: return "C";
+ default: return null;
+ }
+ }
+
+ /**
+ * Encodes an outbound topic alias behavior enum value.
+ *
+ * @param value the {@code OutboundTopicAliasBehaviorType} ordinal
+ * @return {@code "A"} for Manual, {@code "B"} for LRU, {@code "C"} for Disabled,
+ * or {@code null} for Default/unknown
+ */
+ private static String outboundTopicAliasBehaviorValue(TopicAliasingOptions.OutboundTopicAliasBehaviorType behavior) {
+ switch (behavior) {
+ case Manual: return "A";
+ case LRU: return "B";
+ case Disabled: return "C";
+ default: return null;
+ }
+ }
+
/**
- * Checks if a string can be parsed as an integer.
+ * Encodes an inbound topic alias behavior enum value.
*
- * @param str the string to check
- * @return true if the string is a valid integer, false otherwise
+ * @param value the {@code InboundTopicAliasBehaviorType} ordinal
+ * @return {@code "A"} for Enabled, {@code "B"} for Disabled, or {@code null} for Default/unknown
+ */
+ private static String inboundTopicAliasBehaviorValue(TopicAliasingOptions.InboundTopicAliasBehaviorType behavior) {
+ switch (behavior) {
+ case Enabled: return "A";
+ case Disabled: return "B";
+ default: return null;
+ }
+ }
+
+ /**
+ * Encodes a {@link TlsCipherPreference} into its single-character metrics value.
+ *
+ * @param pref the configured TLS cipher preference
+ * @return {@code "A"} for {@code TLS_CIPHER_PREF_PQ_TLSv1_0_2021_05},
+ * {@code "B"} for {@code TLS_CIPHER_PQ_DEFAULT},
+ * {@code "C"} for {@code TLS_CIPHER_PREF_TLSv1_2_2025},
+ * or {@code null} for system default/unknown
+ */
+ private static String tlsCipherPreferenceValue(TlsCipherPreference pref) {
+ switch (pref) {
+ case TLS_CIPHER_PREF_PQ_TLSv1_0_2021_05: return "A"; // TLS_CIPHER_PREF_PQ_TLSv1_0_2021_05
+ case TLS_CIPHER_PQ_DEFAULT: return "B"; // TLS_CIPHER_PQ_DEFAULT
+ case TLS_CIPHER_PREF_TLSv1_2_2025: return "C"; // TLS_CIPHER_PREF_TLSv1_2_2025
+ default: return null;
+ }
+ }
+
+ /**
+ * Encodes a minimum {@link TlsContextOptions.TlsVersions TLS version} into its single-character metrics value.
+ *
+ * @param version the configured minimum TLS version
+ * @return {@code "A"} for SSLv3, {@code "B"} for TLSv1, {@code "C"} for TLSv1.1,
+ * {@code "D"} for TLSv1.2, {@code "E"} for TLSv1.3, or {@code null} for system default/unknown
+ */
+ private static String minimumTlsVersionValue(TlsContextOptions.TlsVersions version) {
+ switch (version) {
+ case SSLv3: return "A"; // SSLv3
+ case TLSv1: return "B"; // TLSv1
+ case TLSv1_1: return "C"; // TLSv1_1
+ case TLSv1_2: return "D"; // TLSv1_2
+ case TLSv1_3: return "E"; // TLSv1_3
+ default: return null;
+ }
+ }
+
+ /**
+ * Tests whether a string parses as a base-10 integer.
+ *
+ * @param str the string to test (may be {@code null})
+ * @return {@code true} if {@code str} parses via {@link Integer#parseInt(String)}, {@code false} otherwise
*/
private static boolean isNumeric(String str) {
try {
@@ -454,4 +449,3 @@ private static boolean isNumeric(String str) {
}
}
}
-
diff --git a/src/main/java/software/amazon/awssdk/crt/internal/IoTMetricsMetadata.java b/src/main/java/software/amazon/awssdk/crt/iot/IoTMetricsMetadata.java
similarity index 91%
rename from src/main/java/software/amazon/awssdk/crt/internal/IoTMetricsMetadata.java
rename to src/main/java/software/amazon/awssdk/crt/iot/IoTMetricsMetadata.java
index 19032cf65..26c29d920 100644
--- a/src/main/java/software/amazon/awssdk/crt/internal/IoTMetricsMetadata.java
+++ b/src/main/java/software/amazon/awssdk/crt/iot/IoTMetricsMetadata.java
@@ -2,10 +2,9 @@
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0.
*/
-package software.amazon.awssdk.crt.internal;
+package software.amazon.awssdk.crt.iot;
/**
- * @internal
* A key-value pair for IoT SDK metrics metadata.
* Metadata entries are appended to the MQTT CONNECT packet username field
* as part of the Metadata query parameter.
diff --git a/src/main/java/software/amazon/awssdk/crt/mqtt/MqttClientConnection.java b/src/main/java/software/amazon/awssdk/crt/mqtt/MqttClientConnection.java
index 325d60718..29c30f1d4 100644
--- a/src/main/java/software/amazon/awssdk/crt/mqtt/MqttClientConnection.java
+++ b/src/main/java/software/amazon/awssdk/crt/mqtt/MqttClientConnection.java
@@ -17,8 +17,7 @@
import software.amazon.awssdk.crt.mqtt5.Mqtt5Client;
import software.amazon.awssdk.crt.mqtt5.Mqtt5ClientOptions;
import software.amazon.awssdk.crt.mqtt5.packets.ConnectPacket;
-import software.amazon.awssdk.crt.internal.IoTDeviceSDKMetrics;
-import software.amazon.awssdk.crt.internal.IoTMetricEncoder;
+import software.amazon.awssdk.crt.iot.IoTDeviceSDKMetrics;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
@@ -166,13 +165,7 @@ private void SetupConfig(MqttConnectionConfig config) throws MqttException {
}
if (!config.getDisableMetrics()) {
- TlsContext tls = null;
- if (config.getMqttClient()!=null){
- tls = config.getMqttClient().getTlsContext();
- } else if (config.getMqtt5Client()!=null){
- tls = config.getMqtt5Client().getClientOptions().getTlsContext();
- }
- IoTDeviceSDKMetrics metrics = IoTMetricEncoder.createMetricsMqtt3(config.getMetrics(), config.getHttpProxyOptions(), tls);
+ IoTDeviceSDKMetrics metrics = IoTDeviceSDKMetrics.createMetricsMqtt3(config);
mqttClientConnectionSetMetrics(getNativeHandle(), metrics);
}
diff --git a/src/main/java/software/amazon/awssdk/crt/mqtt/MqttConnectionConfig.java b/src/main/java/software/amazon/awssdk/crt/mqtt/MqttConnectionConfig.java
index 0b5ef6d52..12d3b6db9 100644
--- a/src/main/java/software/amazon/awssdk/crt/mqtt/MqttConnectionConfig.java
+++ b/src/main/java/software/amazon/awssdk/crt/mqtt/MqttConnectionConfig.java
@@ -9,7 +9,7 @@
import software.amazon.awssdk.crt.CrtResource;
import software.amazon.awssdk.crt.http.HttpProxyOptions;
-import software.amazon.awssdk.crt.internal.IoTDeviceSDKMetrics;
+import software.amazon.awssdk.crt.iot.IoTDeviceSDKMetrics;
import software.amazon.awssdk.crt.io.ClientTlsContext;
import software.amazon.awssdk.crt.io.SocketOptions;
import software.amazon.awssdk.crt.mqtt5.Mqtt5Client;
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 b81c905e0..d98e988b5 100644
--- a/src/main/java/software/amazon/awssdk/crt/mqtt5/Mqtt5ClientOptions.java
+++ b/src/main/java/software/amazon/awssdk/crt/mqtt5/Mqtt5ClientOptions.java
@@ -12,8 +12,7 @@
import software.amazon.awssdk.crt.mqtt5.packets.ConnectPacket;
import software.amazon.awssdk.crt.mqtt.MqttConnectionConfig;
-import software.amazon.awssdk.crt.internal.IoTDeviceSDKMetrics;
-import software.amazon.awssdk.crt.internal.IoTMetricEncoder;
+import software.amazon.awssdk.crt.iot.IoTDeviceSDKMetrics;
import java.util.Map;
import java.util.function.Function;
@@ -50,6 +49,7 @@ public class Mqtt5ClientOptions {
// Opt-out flag for AWS IoT Metrics. When true, metrics are disabled.
// Default is false (metrics enabled).
private boolean disableMetrics = false;
+ private IoTDeviceSDKMetrics userMetrics;
private IoTDeviceSDKMetrics iotDeviceSDKMetrics;
@@ -279,6 +279,15 @@ public boolean getDisableMetrics() {
return this.disableMetrics;
}
+ /**
+ * Returns the user-provided metrics configuration from the IoT SDK layer.
+ *
+ * @return the user metrics, or null if none were provided
+ */
+ public IoTDeviceSDKMetrics getUserMetrics() {
+ return this.userMetrics;
+ }
+
/**
* Disables IoT Device SDK metrics collection. The metrics includes SDK name, version, and platform.
* Default is false (metrics enabled).
@@ -316,10 +325,11 @@ public Mqtt5ClientOptions(Mqtt5ClientOptionsBuilder builder) {
this.publishEvents = builder.publishEvents;
this.topicAliasingOptions = builder.topicAliasingOptions;
this.disableMetrics = builder.disableMetrics;
+ this.userMetrics = builder.metrics;
if (this.disableMetrics) {
this.iotDeviceSDKMetrics = null;
} else {
- this.iotDeviceSDKMetrics = IoTMetricEncoder.createMetricsMqtt5(this, builder.metrics);
+ this.iotDeviceSDKMetrics = IoTDeviceSDKMetrics.createMetricsMqtt5(this);
}
}
diff --git a/src/main/resources/META-INF/native-image/software.amazon.awssdk/crt/aws-crt/jni-config.json b/src/main/resources/META-INF/native-image/software.amazon.awssdk/crt/aws-crt/jni-config.json
index 82e69edd3..7e348dfc8 100644
--- a/src/main/resources/META-INF/native-image/software.amazon.awssdk/crt/aws-crt/jni-config.json
+++ b/src/main/resources/META-INF/native-image/software.amazon.awssdk/crt/aws-crt/jni-config.json
@@ -1268,7 +1268,7 @@
]
},
{
- "name": "software.amazon.awssdk.crt.internal.IoTDeviceSDKMetrics",
+ "name": "software.amazon.awssdk.crt.iot.IoTDeviceSDKMetrics",
"fields": [
{
"name": "libraryName"
@@ -1279,7 +1279,7 @@
]
},
{
- "name": "software.amazon.awssdk.crt.internal.IoTMetricsMetadata",
+ "name": "software.amazon.awssdk.crt.iot.IoTMetricsMetadata",
"fields": [
{
"name": "key"
diff --git a/src/native/java_class_ids.c b/src/native/java_class_ids.c
index 951be72be..5331af998 100644
--- a/src/native/java_class_ids.c
+++ b/src/native/java_class_ids.c
@@ -1767,7 +1767,7 @@ static void s_cache_mqtt5_client_options(JNIEnv *env) {
env,
mqtt5_client_options_properties.client_options_class,
"iotDeviceSDKMetrics",
- "Lsoftware/amazon/awssdk/crt/internal/IoTDeviceSDKMetrics;");
+ "Lsoftware/amazon/awssdk/crt/iot/IoTDeviceSDKMetrics;");
AWS_FATAL_ASSERT(mqtt5_client_options_properties.iot_device_sdk_metrics_field_id);
}
@@ -2671,7 +2671,7 @@ static void s_cache_cognito_credentials_provider(JNIEnv *env) {
struct java_iot_device_sdk_metrics_properties iot_device_sdk_metrics_properties;
static void s_cache_iot_device_sdk_metrics(JNIEnv *env) {
- jclass cls = (*env)->FindClass(env, "software/amazon/awssdk/crt/internal/IoTDeviceSDKMetrics");
+ jclass cls = (*env)->FindClass(env, "software/amazon/awssdk/crt/iot/IoTDeviceSDKMetrics");
AWS_FATAL_ASSERT(cls);
iot_device_sdk_metrics_properties.iot_device_sdk_metrics_class = (*env)->NewGlobalRef(env, cls);
AWS_FATAL_ASSERT(iot_device_sdk_metrics_properties.iot_device_sdk_metrics_class);
@@ -2688,7 +2688,7 @@ static void s_cache_iot_device_sdk_metrics(JNIEnv *env) {
struct java_iot_metrics_metadata_properties iot_metrics_metadata_properties;
static void s_cache_iot_metrics_metadata(JNIEnv *env) {
- jclass cls = (*env)->FindClass(env, "software/amazon/awssdk/crt/internal/IoTMetricsMetadata");
+ jclass cls = (*env)->FindClass(env, "software/amazon/awssdk/crt/iot/IoTMetricsMetadata");
AWS_FATAL_ASSERT(cls);
iot_metrics_metadata_properties.iot_metrics_metadata_class = (*env)->NewGlobalRef(env, cls);
AWS_FATAL_ASSERT(iot_metrics_metadata_properties.iot_metrics_metadata_class);
diff --git a/src/test/java/software/amazon/awssdk/crt/test/IoTMetricEncoderTest.java b/src/test/java/software/amazon/awssdk/crt/test/IoTMetricEncoderTest.java
index 2d0bfc006..351622592 100644
--- a/src/test/java/software/amazon/awssdk/crt/test/IoTMetricEncoderTest.java
+++ b/src/test/java/software/amazon/awssdk/crt/test/IoTMetricEncoderTest.java
@@ -7,9 +7,9 @@
import org.junit.Test;
import static org.junit.Assert.*;
-import software.amazon.awssdk.crt.internal.IoTMetricEncoder;
-import software.amazon.awssdk.crt.internal.IoTDeviceSDKMetrics;
-import software.amazon.awssdk.crt.internal.IoTMetricsMetadata;
+import software.amazon.awssdk.crt.iot.IoTDeviceSDKMetrics;
+import software.amazon.awssdk.crt.iot.IoTMetricsMetadata;
+import software.amazon.awssdk.crt.mqtt.MqttConnectionConfig;
import software.amazon.awssdk.crt.mqtt5.Mqtt5ClientOptions;
import software.amazon.awssdk.crt.mqtt5.Mqtt5ClientOptions.Mqtt5ClientOptionsBuilder;
import software.amazon.awssdk.crt.mqtt5.Mqtt5ClientOptions.ClientSessionBehavior;
@@ -20,42 +20,47 @@
import software.amazon.awssdk.crt.io.ExponentialBackoffRetryOptions.JitterMode;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.List;
public class IoTMetricEncoderTest extends CrtTestFixture {
public IoTMetricEncoderTest() {}
+ private MqttConnectionConfig createMqtt3Config(IoTDeviceSDKMetrics userMetrics) {
+ MqttConnectionConfig config = new MqttConnectionConfig();
+ if (userMetrics != null) {
+ config.setMetrics(userMetrics);
+ }
+ return config;
+ }
+
// ======================== Minimal Options Encoding ========================
@Test
public void testMqtt5Minimal() {
Mqtt5ClientOptionsBuilder builder = new Mqtt5ClientOptionsBuilder("localhost", 8883L);
- builder.withDisableMetrics(true); // disable so constructor doesn't call encoder
+ builder.withDisableMetrics(true);
Mqtt5ClientOptions options = builder.build();
- String result = IoTMetricEncoder.getEncodedFeatureListMqtt5(options);
-
- // Should only have F (protocol=5) and G (socket)
- assertTrue(result.contains("F/5"));
- assertTrue(result.contains("G/"));
- // Should NOT have A, B, C, D, E since all are DEFAULT
- assertFalse(result.contains("A/"));
- assertFalse(result.contains("B/"));
- assertFalse(result.contains("C/"));
- assertFalse(result.contains("D/"));
- assertFalse(result.contains("E/"));
+ IoTDeviceSDKMetrics result = IoTDeviceSDKMetrics.createMetricsMqtt5(options);
+ String feature = findMetadataValue(result.getMetadataEntries(), "IoTSDKFeature");
+
+ assertTrue(feature.contains("F/5"));
+ assertTrue(feature.contains("G/"));
+ assertFalse(feature.contains("A/"));
+ assertFalse(feature.contains("B/"));
+ assertFalse(feature.contains("C/"));
+ assertFalse(feature.contains("D/"));
+ assertFalse(feature.contains("E/"));
}
@Test
public void testMqtt3Minimal() {
- String result = IoTMetricEncoder.getEncodedFeatureListMqtt3(null, null);
+ IoTDeviceSDKMetrics result = IoTDeviceSDKMetrics.createMetricsMqtt3(createMqtt3Config(null));
+ String feature = findMetadataValue(result.getMetadataEntries(), "IoTSDKFeature");
- assertTrue(result.contains("F/3"));
- assertTrue(result.contains("G/"));
- // Only F and G
- assertEquals(2, result.split(",").length);
+ assertTrue(feature.contains("F/3"));
+ assertTrue(feature.contains("G/"));
}
// ======================== Non-Default Features Encoding ========================
@@ -74,14 +79,15 @@ public void testMqtt5WithNonDefaultFeatures() {
builder.withDisableMetrics(true);
Mqtt5ClientOptions options = builder.build();
- String result = IoTMetricEncoder.getEncodedFeatureListMqtt5(options);
+ IoTDeviceSDKMetrics result = IoTDeviceSDKMetrics.createMetricsMqtt5(options);
+ String feature = findMetadataValue(result.getMetadataEntries(), "IoTSDKFeature");
- assertTrue(result.contains("A/B")); // Full
- assertTrue(result.contains("B/A")); // CLEAN
- assertTrue(result.contains("C/C")); // FAIL_ALL
- assertTrue(result.contains("D/B")); // LRU
- assertTrue(result.contains("E/A")); // Enabled
- assertTrue(result.contains("F/5")); // MQTT5
+ assertTrue(feature.contains("A/B")); // Full
+ assertTrue(feature.contains("B/A")); // CLEAN
+ assertTrue(feature.contains("C/C")); // FAIL_ALL
+ assertTrue(feature.contains("D/B")); // LRU
+ assertTrue(feature.contains("E/A")); // Enabled
+ assertTrue(feature.contains("F/5")); // MQTT5
}
@Test
@@ -93,67 +99,57 @@ public void testDefaultEnumValuesOmitted() {
builder.withDisableMetrics(true);
Mqtt5ClientOptions options = builder.build();
- String result = IoTMetricEncoder.getEncodedFeatureListMqtt5(options);
+ IoTDeviceSDKMetrics result = IoTDeviceSDKMetrics.createMetricsMqtt5(options);
+ String feature = findMetadataValue(result.getMetadataEntries(), "IoTSDKFeature");
- assertFalse(result.contains("A/"));
- assertFalse(result.contains("B/"));
- assertFalse(result.contains("C/"));
+ assertFalse(feature.contains("A/"));
+ assertFalse(feature.contains("B/"));
+ assertFalse(feature.contains("C/"));
}
- // ======================== Feature List Merging ========================
+ // ======================== Feature Merging ========================
@Test
public void testUserOverridesCrt() {
- String result = IoTMetricEncoder.mergeFeatureLists("A/B,F/5", "A/C");
- assertEquals("A/C,F/5", result);
- }
-
- @Test
- public void testUserOverridesMultipleCrtFeatures() {
- String result = IoTMetricEncoder.mergeFeatureLists("A/B,F/5,G/A,K/D", "A/C,F/3,K/E");
- assertEquals("A/C,F/3,G/A,K/E", result);
- }
+ IoTDeviceSDKMetrics user = new IoTDeviceSDKMetrics();
+ List userEntries = new ArrayList<>();
+ userEntries.add(new IoTMetricsMetadata("IoTSDKMetricsVersion", "1"));
+ userEntries.add(new IoTMetricsMetadata("IoTSDKFeature", "F/9"));
+ user.setMetadataEntries(userEntries);
- @Test
- public void testEmptyUserFeatures() {
- String result = IoTMetricEncoder.mergeFeatureLists("F/5,G/A", "");
- assertEquals("F/5,G/A", result);
- }
+ IoTDeviceSDKMetrics result = IoTDeviceSDKMetrics.createMetricsMqtt3(createMqtt3Config(user));
+ String feature = findMetadataValue(result.getMetadataEntries(), "IoTSDKFeature");
- @Test
- public void testEmptyCrtFeatures() {
- String result = IoTMetricEncoder.mergeFeatureLists("", "A/B");
- assertEquals("A/B", result);
+ assertTrue(feature.contains("F/9"));
+ assertFalse(feature.contains("F/3"));
}
@Test
- public void testBothEmpty() {
- String result = IoTMetricEncoder.mergeFeatureLists("", "");
- assertEquals("", result);
- }
+ public void testDisjointFeaturesAreMerged() {
+ IoTDeviceSDKMetrics user = new IoTDeviceSDKMetrics();
+ List userEntries = new ArrayList<>();
+ userEntries.add(new IoTMetricsMetadata("IoTSDKMetricsVersion", "1"));
+ userEntries.add(new IoTMetricsMetadata("IoTSDKFeature", "I/A,K/D"));
+ user.setMetadataEntries(userEntries);
- @Test
- public void testDisjointFeatures() {
- String result = IoTMetricEncoder.mergeFeatureLists("F/5,G/A", "I/A,K/D");
- assertEquals("F/5,G/A,I/A,K/D", result);
- }
+ IoTDeviceSDKMetrics result = IoTDeviceSDKMetrics.createMetricsMqtt3(createMqtt3Config(user));
+ String feature = findMetadataValue(result.getMetadataEntries(), "IoTSDKFeature");
- @Test
- public void testInsertionOrderPreserved() {
- String result = IoTMetricEncoder.mergeFeatureLists("G/A,F/5", "A/B");
- assertEquals("G/A,F/5,A/B", result);
+ assertTrue(feature.contains("F/3"));
+ assertTrue(feature.contains("G/"));
+ assertTrue(feature.contains("I/A"));
+ assertTrue(feature.contains("K/D"));
}
// ======================== Create Metrics - Default Options ========================
@Test
public void testCreateMetricsNullUserMetrics() {
- IoTDeviceSDKMetrics result = IoTMetricEncoder.createMetricsMqtt3(null, null, null);
+ IoTDeviceSDKMetrics result = IoTDeviceSDKMetrics.createMetricsMqtt3(createMqtt3Config(null));
assertEquals("IoTDeviceSDK/Java", result.getLibraryName());
assertNotNull(result.getMetadataEntries());
- // Should have CRTVersion, IoTSDKFeature, IoTSDKMetricsVersion
List entries = result.getMetadataEntries();
String crtVersion = findMetadataValue(entries, "CRTVersion");
String feature = findMetadataValue(entries, "IoTSDKFeature");
@@ -167,11 +163,11 @@ public void testCreateMetricsNullUserMetrics() {
@Test
public void testCreateMetricsEmptyUserMetrics() {
IoTDeviceSDKMetrics user = new IoTDeviceSDKMetrics();
- IoTDeviceSDKMetrics result = IoTMetricEncoder.createMetricsMqtt3(user, null, null);
+ IoTDeviceSDKMetrics result = IoTDeviceSDKMetrics.createMetricsMqtt3(createMqtt3Config(user));
assertEquals("IoTDeviceSDK/Java", result.getLibraryName());
String feature = findMetadataValue(result.getMetadataEntries(), "IoTSDKFeature");
- assertTrue(feature.contains("F/3")); // MQTT3 protocol
+ assertTrue(feature.contains("F/3"));
}
// ======================== Create Metrics - User Features Merged ========================
@@ -184,7 +180,7 @@ public void testUserFeatureAddedWhenVersionMatches() {
userEntries.add(new IoTMetricsMetadata("IoTSDKFeature", "I/A"));
user.setMetadataEntries(userEntries);
- IoTDeviceSDKMetrics result = IoTMetricEncoder.createMetricsMqtt3(user, null, null);
+ IoTDeviceSDKMetrics result = IoTDeviceSDKMetrics.createMetricsMqtt3(createMqtt3Config(user));
String feature = findMetadataValue(result.getMetadataEntries(), "IoTSDKFeature");
assertTrue(feature.contains("I/A"));
@@ -200,11 +196,10 @@ public void testUserFeatureOverridesCrt() {
userEntries.add(new IoTMetricsMetadata("IoTSDKFeature", "F/3,I/B"));
user.setMetadataEntries(userEntries);
- IoTDeviceSDKMetrics result = IoTMetricEncoder.createMetricsMqtt3(user, null, null);
+ IoTDeviceSDKMetrics result = IoTDeviceSDKMetrics.createMetricsMqtt3(createMqtt3Config(user));
String feature = findMetadataValue(result.getMetadataEntries(), "IoTSDKFeature");
assertTrue(feature.contains("F/3"));
- assertFalse(feature.contains("F/5"));
assertTrue(feature.contains("I/B"));
}
@@ -218,7 +213,7 @@ public void testUserFeaturesIgnoredOnHigherVersion() {
userEntries.add(new IoTMetricsMetadata("IoTSDKFeature", "I/A"));
user.setMetadataEntries(userEntries);
- IoTDeviceSDKMetrics result = IoTMetricEncoder.createMetricsMqtt3(user, null, null);
+ IoTDeviceSDKMetrics result = IoTDeviceSDKMetrics.createMetricsMqtt3(createMqtt3Config(user));
String feature = findMetadataValue(result.getMetadataEntries(), "IoTSDKFeature");
assertFalse(feature.contains("I/A"));
@@ -233,7 +228,7 @@ public void testUserFeaturesIgnoredOnNonNumericVersion() {
userEntries.add(new IoTMetricsMetadata("IoTSDKFeature", "I/A"));
user.setMetadataEntries(userEntries);
- IoTDeviceSDKMetrics result = IoTMetricEncoder.createMetricsMqtt3(user, null, null);
+ IoTDeviceSDKMetrics result = IoTDeviceSDKMetrics.createMetricsMqtt3(createMqtt3Config(user));
String feature = findMetadataValue(result.getMetadataEntries(), "IoTSDKFeature");
assertFalse(feature.contains("I/A"));
@@ -246,7 +241,7 @@ public void testUserFeaturesIgnoredWhenNoVersion() {
userEntries.add(new IoTMetricsMetadata("IoTSDKFeature", "I/A"));
user.setMetadataEntries(userEntries);
- IoTDeviceSDKMetrics result = IoTMetricEncoder.createMetricsMqtt3(user, null, null);
+ IoTDeviceSDKMetrics result = IoTDeviceSDKMetrics.createMetricsMqtt3(createMqtt3Config(user));
String feature = findMetadataValue(result.getMetadataEntries(), "IoTSDKFeature");
assertFalse(feature.contains("I/A"));
@@ -261,7 +256,7 @@ public void testCrtVersionCannotBeOverridden() {
userEntries.add(new IoTMetricsMetadata("CRTVersion", "fake_version"));
user.setMetadataEntries(userEntries);
- IoTDeviceSDKMetrics result = IoTMetricEncoder.createMetricsMqtt3(user, null, null);
+ IoTDeviceSDKMetrics result = IoTDeviceSDKMetrics.createMetricsMqtt3(createMqtt3Config(user));
String crtVersion = findMetadataValue(result.getMetadataEntries(), "CRTVersion");
assertNotEquals("fake_version", crtVersion);
@@ -276,7 +271,7 @@ public void testSdkVersionPreserved() {
userEntries.add(new IoTMetricsMetadata("IoTSDKVersion", "2.0.0"));
user.setMetadataEntries(userEntries);
- IoTDeviceSDKMetrics result = IoTMetricEncoder.createMetricsMqtt3(user, null, null);
+ IoTDeviceSDKMetrics result = IoTDeviceSDKMetrics.createMetricsMqtt3(createMqtt3Config(user));
String sdkVersion = findMetadataValue(result.getMetadataEntries(), "IoTSDKVersion");
assertEquals("2.0.0", sdkVersion);
@@ -285,14 +280,14 @@ public void testSdkVersionPreserved() {
@Test
public void testCustomLibraryName() {
IoTDeviceSDKMetrics user = new IoTDeviceSDKMetrics("MyCustomSDK/1.0", null);
- IoTDeviceSDKMetrics result = IoTMetricEncoder.createMetricsMqtt3(user, null, null);
+ IoTDeviceSDKMetrics result = IoTDeviceSDKMetrics.createMetricsMqtt3(createMqtt3Config(user));
assertEquals("MyCustomSDK/1.0", result.getLibraryName());
}
@Test
public void testMetricsVersionAlwaysSet() {
- IoTDeviceSDKMetrics result = IoTMetricEncoder.createMetricsMqtt3(null, null, null);
+ IoTDeviceSDKMetrics result = IoTDeviceSDKMetrics.createMetricsMqtt3(createMqtt3Config(null));
String metricsVersion = findMetadataValue(result.getMetadataEntries(), "IoTSDKMetricsVersion");
assertEquals("1", metricsVersion);
From e8f71961aa832ebb858832baca43ec15e57ff398 Mon Sep 17 00:00:00 2001
From: Rakshil Modi
Date: Thu, 11 Jun 2026 10:30:47 -0700
Subject: [PATCH 08/16] logging in Jni
---
src/native/iot_device_sdk_metrics.c | 94 ++++++++++++++++++-----------
1 file changed, 59 insertions(+), 35 deletions(-)
diff --git a/src/native/iot_device_sdk_metrics.c b/src/native/iot_device_sdk_metrics.c
index db8879f1d..fdcc03c1f 100644
--- a/src/native/iot_device_sdk_metrics.c
+++ b/src/native/iot_device_sdk_metrics.c
@@ -2,6 +2,16 @@
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0.
*/
+
+/**
+ * JNI bridge for IoT Device SDK Metrics.
+ *
+ * This file is responsible for converting the Java IoTDeviceSDKMetrics object
+ * (library name + metadata key-value entries) into a native aws_mqtt_iot_metrics
+ * struct that the C MQTT layer uses to append SDK telemetry to the CONNECT packet
+ * username field.
+ *
+ */
#include
#include "iot_device_sdk_metrics.h"
@@ -12,6 +22,7 @@
static char s_iot_device_sdk_metrics_string[] = "IoTDeviceSDKMetrics";
+/* Frees all native memory associated with a parsed metrics struct. */
void aws_mqtt_iot_metrics_java_jni_destroy(
JNIEnv *env,
struct aws_allocator *allocator,
@@ -38,14 +49,17 @@ void aws_mqtt_iot_metrics_java_jni_destroy(
aws_mem_release(allocator, java_metrics);
}
+/* Parses a Java IoTDeviceSDKMetrics object into a native metrics struct for the C MQTT layer. */
struct aws_mqtt_iot_metrics_java_jni *aws_mqtt_iot_metrics_java_jni_create_from_java(
JNIEnv *env,
struct aws_allocator *allocator,
jobject java_iot_device_sdk_metrics) {
+ /* Allocate the holder struct (zero-initialized) */
struct aws_mqtt_iot_metrics_java_jni *java_metrics =
aws_mem_calloc(allocator, 1, sizeof(struct aws_mqtt_iot_metrics_java_jni));
+ /* Extract the library name string (e.g. "IoTDeviceSDK/Java") */
if (aws_get_string_from_jobject(
env,
java_iot_device_sdk_metrics,
@@ -67,6 +81,7 @@ struct aws_mqtt_iot_metrics_java_jni *aws_mqtt_iot_metrics_java_jni_create_from_
if (metadata_list != NULL && !aws_jni_check_and_clear_exception(env)) {
jint count = (*env)->CallIntMethod(env, metadata_list, boxed_list_properties.list_size_id);
if (count > 0 && !aws_jni_check_and_clear_exception(env)) {
+ /* Pre-allocate entry array for all metadata key-value pairs */
java_metrics->metadata_entries =
aws_mem_calloc(allocator, (size_t)count, sizeof(struct aws_mqtt_metadata_entry));
@@ -75,29 +90,37 @@ struct aws_mqtt_iot_metrics_java_jni *aws_mqtt_iot_metrics_java_jni_create_from_
for (jint i = 0; i < count; i++) {
jobject entry = (*env)->CallObjectMethod(env, metadata_list, boxed_list_properties.list_get_id, i);
if (entry == NULL || aws_jni_check_and_clear_exception(env)) {
- continue;
+ AWS_LOGF_ERROR(
+ AWS_LS_MQTT_GENERAL, "IoTDeviceSDKMetrics: failed to get metadata entry at index %d", (int)i);
+ goto on_error;
}
jstring key_jstr =
(jstring)(*env)->GetObjectField(env, entry, iot_metrics_metadata_properties.key_field_id);
jstring value_jstr =
(jstring)(*env)->GetObjectField(env, entry, iot_metrics_metadata_properties.value_field_id);
- if (key_jstr != NULL && value_jstr != NULL) {
- total_size += (size_t)(*env)->GetStringUTFLength(env, key_jstr);
- total_size += (size_t)(*env)->GetStringUTFLength(env, value_jstr);
- }
+ total_size += (size_t)(*env)->GetStringUTFLength(env, key_jstr);
+ total_size += (size_t)(*env)->GetStringUTFLength(env, value_jstr);
(*env)->DeleteLocalRef(env, key_jstr);
(*env)->DeleteLocalRef(env, value_jstr);
(*env)->DeleteLocalRef(env, entry);
}
+ AWS_LOGF_INFO(
+ AWS_LS_MQTT_GENERAL,
+ "IoTDeviceSDKMetrics: first pass complete, total_size=%zu for %d entries",
+ total_size,
+ (int)count);
+
/* Allocate exact size */
aws_byte_buf_init(&java_metrics->metadata_storage, allocator, total_size);
- /* Second pass: copy strings and create cursors */
+ /* Second pass: copy strings into contiguous buffer and set cursors pointing into it */
for (jint i = 0; i < count; i++) {
jobject entry = (*env)->CallObjectMethod(env, metadata_list, boxed_list_properties.list_get_id, i);
if (entry == NULL || aws_jni_check_and_clear_exception(env)) {
- continue;
+ AWS_LOGF_ERROR(
+ AWS_LS_MQTT_GENERAL, "IoTDeviceSDKMetrics: failed to get metadata entry at index %d", (int)i);
+ goto on_error;
}
jstring key_jstr =
@@ -105,42 +128,43 @@ struct aws_mqtt_iot_metrics_java_jni *aws_mqtt_iot_metrics_java_jni_create_from_
jstring value_jstr =
(jstring)(*env)->GetObjectField(env, entry, iot_metrics_metadata_properties.value_field_id);
- if (key_jstr != NULL && value_jstr != NULL) {
- const char *key_chars = (*env)->GetStringUTFChars(env, key_jstr, NULL);
- size_t key_len = (size_t)(*env)->GetStringUTFLength(env, key_jstr);
- const char *value_chars = (*env)->GetStringUTFChars(env, value_jstr, NULL);
- size_t value_len = (size_t)(*env)->GetStringUTFLength(env, value_jstr);
-
- /* Append key to storage buffer */
- size_t key_offset = java_metrics->metadata_storage.len;
- aws_byte_buf_append(
- &java_metrics->metadata_storage,
- &(struct aws_byte_cursor){.ptr = (uint8_t *)key_chars, .len = key_len});
-
- /* Append value to storage buffer */
- size_t value_offset = java_metrics->metadata_storage.len;
- aws_byte_buf_append(
- &java_metrics->metadata_storage,
- &(struct aws_byte_cursor){.ptr = (uint8_t *)value_chars, .len = value_len});
-
- java_metrics->metadata_entries[java_metrics->metadata_count].key =
- aws_byte_cursor_from_array(java_metrics->metadata_storage.buffer + key_offset, key_len);
- java_metrics->metadata_entries[java_metrics->metadata_count].value =
- aws_byte_cursor_from_array(java_metrics->metadata_storage.buffer + value_offset, value_len);
- java_metrics->metadata_count++;
-
- (*env)->ReleaseStringUTFChars(env, key_jstr, key_chars);
- (*env)->ReleaseStringUTFChars(env, value_jstr, value_chars);
- }
+ struct aws_byte_cursor key_cursor = aws_jni_byte_cursor_from_jstring_acquire(env, key_jstr);
+ size_t key_offset = java_metrics->metadata_storage.len;
+ aws_byte_buf_append(&java_metrics->metadata_storage, &key_cursor);
+ aws_jni_byte_cursor_from_jstring_release(env, key_jstr, key_cursor);
+
+ struct aws_byte_cursor value_cursor = aws_jni_byte_cursor_from_jstring_acquire(env, value_jstr);
+ size_t value_offset = java_metrics->metadata_storage.len;
+ aws_byte_buf_append(&java_metrics->metadata_storage, &value_cursor);
+ aws_jni_byte_cursor_from_jstring_release(env, value_jstr, value_cursor);
+
+ java_metrics->metadata_entries[java_metrics->metadata_count].key =
+ aws_byte_cursor_from_array(java_metrics->metadata_storage.buffer + key_offset, key_cursor.len);
+ java_metrics->metadata_entries[java_metrics->metadata_count].value =
+ aws_byte_cursor_from_array(java_metrics->metadata_storage.buffer + value_offset, value_cursor.len);
+
+ AWS_LOGF_INFO(
+ AWS_LS_MQTT_GENERAL,
+ "IoTDeviceSDKMetrics metadata[%zu]: key=\"" PRInSTR "\" value=\"" PRInSTR "\"",
+ java_metrics->metadata_count,
+ AWS_BYTE_CURSOR_PRI(java_metrics->metadata_entries[java_metrics->metadata_count].key),
+ AWS_BYTE_CURSOR_PRI(java_metrics->metadata_entries[java_metrics->metadata_count].value));
+
+ java_metrics->metadata_count++;
(*env)->DeleteLocalRef(env, key_jstr);
(*env)->DeleteLocalRef(env, value_jstr);
(*env)->DeleteLocalRef(env, entry);
}
- /* Set the metrics struct fields */
+ /* Wire up the C metrics struct to point at our parsed data */
java_metrics->metrics.metadata_entries = java_metrics->metadata_entries;
java_metrics->metrics.metadata_count = java_metrics->metadata_count;
+
+ AWS_LOGF_INFO(
+ AWS_LS_MQTT_GENERAL,
+ "IoTDeviceSDKMetrics: parsing complete, %zu metadata entries set",
+ java_metrics->metadata_count);
}
(*env)->DeleteLocalRef(env, metadata_list);
From d3113feeda520f02f6027d2e327a564b19b88d91 Mon Sep 17 00:00:00 2001
From: Rakshil Modi
Date: Thu, 11 Jun 2026 10:36:12 -0700
Subject: [PATCH 09/16] refactoring parameter
---
.../amazon/awssdk/crt/mqtt/MqttConnectionConfig.java | 6 +++---
.../amazon/awssdk/crt/mqtt5/Mqtt5ClientOptions.java | 12 ++++++------
2 files changed, 9 insertions(+), 9 deletions(-)
diff --git a/src/main/java/software/amazon/awssdk/crt/mqtt/MqttConnectionConfig.java b/src/main/java/software/amazon/awssdk/crt/mqtt/MqttConnectionConfig.java
index 12d3b6db9..92e834b72 100644
--- a/src/main/java/software/amazon/awssdk/crt/mqtt/MqttConnectionConfig.java
+++ b/src/main/java/software/amazon/awssdk/crt/mqtt/MqttConnectionConfig.java
@@ -547,10 +547,10 @@ public Consumer getWebsocketHandshakeTransform(
* Disables IoT Device SDK metrics collection. The metrics includes SDK name, version, and platform.
* Default is false (metrics enabled).
*
- * @param disable true to disable metrics, false to enable (default)
+ * @param disableMetrics true to disable metrics, false to enable (default)
*/
- public void setDisableMetrics(boolean disable) {
- this.disableMetrics = disable;
+ public void setDisableMetrics(boolean disableMetrics) {
+ this.disableMetrics = disableMetrics;
}
/**
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 d98e988b5..5421127b6 100644
--- a/src/main/java/software/amazon/awssdk/crt/mqtt5/Mqtt5ClientOptions.java
+++ b/src/main/java/software/amazon/awssdk/crt/mqtt5/Mqtt5ClientOptions.java
@@ -292,10 +292,10 @@ public IoTDeviceSDKMetrics getUserMetrics() {
* Disables IoT Device SDK metrics collection. The metrics includes SDK name, version, and platform.
* Default is false (metrics enabled).
*
- * @param disable true to disable metrics, false to enable (default)
+ * @param disableMetrics true to disable metrics, false to enable (default)
*/
- public void setDisableMetrics(boolean disable) {
- this.disableMetrics = disable;
+ public void setDisableMetrics(boolean disableMetrics) {
+ this.disableMetrics = disableMetrics;
}
/**
@@ -906,11 +906,11 @@ public Mqtt5ClientOptionsBuilder withTopicAliasingOptions(TopicAliasingOptions o
* Disables IoT Device SDK metrics collection. The metrics includes SDK name, version, and platform.
* Default is false (metrics enabled).
*
- * @param disable true to disable metrics, false to enable (default)
+ * @param disableMetrics true to disable metrics, false to enable (default)
* @return The Mqtt5ClientOptionsBuilder after setting the metrics option
*/
- public Mqtt5ClientOptionsBuilder withDisableMetrics(boolean disable) {
- this.disableMetrics = disable;
+ public Mqtt5ClientOptionsBuilder withDisableMetrics(boolean disableMetrics) {
+ this.disableMetrics = disableMetrics;
return this;
}
From 381599e7828eb0f8418ccaff19785ae3cbd8fe27 Mon Sep 17 00:00:00 2001
From: Rakshil Modi
Date: Thu, 11 Jun 2026 15:16:46 -0700
Subject: [PATCH 10/16] common helper function
---
.../awssdk/crt/iot/IoTDeviceSDKMetrics.java | 46 +--
src/native/iot_device_sdk_metrics.c | 267 ++++++++++++------
2 files changed, 199 insertions(+), 114 deletions(-)
diff --git a/src/main/java/software/amazon/awssdk/crt/iot/IoTDeviceSDKMetrics.java b/src/main/java/software/amazon/awssdk/crt/iot/IoTDeviceSDKMetrics.java
index 3e7a9409d..4a31cd6ed 100644
--- a/src/main/java/software/amazon/awssdk/crt/iot/IoTDeviceSDKMetrics.java
+++ b/src/main/java/software/amazon/awssdk/crt/iot/IoTDeviceSDKMetrics.java
@@ -46,6 +46,7 @@ public class IoTDeviceSDKMetrics {
public IoTDeviceSDKMetrics() {
this.libraryName = "IoTDeviceSDK/Java";
+ this.metadataEntries = new ArrayList<>();
}
public IoTDeviceSDKMetrics(String libraryName, List metadataEntries) {
@@ -108,14 +109,14 @@ public static IoTDeviceSDKMetrics createMetricsMqtt3(MqttConnectionConfig config
* - G (socket_implementation): detected from platform
*
* Conditionally includes (only when not DEFAULT):
- * A (retry_jitter_mode),
- * B (session_behavior),
- * C (offline_queue_behavior),
- * D (outbound_topic_alias),
- * E (inbound_topic_alias),
- * H (http_proxy_type),
- * J (tls_cipher_preference),
- * K (minimum_tls_version)
+ * - A (retry_jitter_mode),
+ * - B (session_behavior),
+ * - C (offline_queue_behavior),
+ * - D (outbound_topic_alias),
+ * - E (inbound_topic_alias),
+ * - H (http_proxy_type),
+ * - J (tls_cipher_preference),
+ * - K (minimum_tls_version)
*
* Feature I (certificate_source) is set at the IoT SDK level, not here.
*
@@ -184,7 +185,9 @@ private static String getEncodedFeatureListMqtt5(Mqtt5ClientOptions clientOption
* - G (socket_implementation): detected from platform
*
* Conditionally includes:
- * H (http_proxy_type), J (tls_cipher_preference), K (minimum_tls_version)
+ * - H (http_proxy_type)
+ * - J (tls_cipher_preference)
+ * - K (minimum_tls_version)
*
* @param proxyOptions optional HTTP proxy options from the connection
* @param tlsCtx optional TLS context used by the connection
@@ -235,11 +238,12 @@ private static String mergeFeatureLists(String crtFeatures, String userFeatures)
/**
+ *
* Metrics creation logic:
- * 1. CRTVersion: auto-set from package version, not overridable by user
- * 2. IoTSDKMetricsVersion: always set to current IOT_SDK_METRICS_FEATURE_VERSION
- * 3. IoTSDKFeature: merged if user version matches, else CRT only
- * 4. Other user metadata: passed through unchanged
+ * - CRTVersion: auto-set from package version, not overridable by user
+ * - IoTSDKMetricsVersion: always set to current IOT_SDK_METRICS_FEATURE_VERSION
+ * - IoTSDKFeature: merged if user version matches, else CRT only
+ * - Other user metadata: passed through unchanged
*
* @param userMetrics optional metrics from a higher-level IoT SDK (may be {@code null})
* @param crtFeatureList encoded CRT feature list string
@@ -267,8 +271,7 @@ private static IoTDeviceSDKMetrics createMetrics(IoTDeviceSDKMetrics userMetrics
}
}
- if (userMetricsVersion != null && isNumeric(userMetricsVersion)
- && Integer.parseInt(userMetricsVersion) == IOT_SDK_METRICS_FEATURE_VERSION) {
+ if (parsedVersionMatches(userMetricsVersion)) {
metadata.put("IoTSDKFeature", mergeFeatureLists(crtFeatureList, userFeature));
} else {
metadata.put("IoTSDKFeature", mergeFeatureLists(crtFeatureList, ""));
@@ -435,15 +438,16 @@ private static String minimumTlsVersionValue(TlsContextOptions.TlsVersions versi
}
/**
- * Tests whether a string parses as a base-10 integer.
+ * Returns {@code true} when {@code userMetricsVersion} parses as an integer equal to
+ * {@link #IOT_SDK_METRICS_FEATURE_VERSION}. Null and non-numeric inputs return {@code false}.
*
- * @param str the string to test (may be {@code null})
- * @return {@code true} if {@code str} parses via {@link Integer#parseInt(String)}, {@code false} otherwise
+ * @param userMetricsVersion the user-supplied {@code IoTSDKMetricsVersion} string (may be {@code null})
+ * @return {@code true} if the userMetricsVersion matches the current schema, {@code false} otherwise
*/
- private static boolean isNumeric(String str) {
+ private static boolean parsedVersionMatches(String userMetricsVersion) {
+ if (userMetricsVersion == null) return false;
try {
- Integer.parseInt(str);
- return true;
+ return Integer.parseInt(userMetricsVersion) == IOT_SDK_METRICS_FEATURE_VERSION;
} catch (NumberFormatException e) {
return false;
}
diff --git a/src/native/iot_device_sdk_metrics.c b/src/native/iot_device_sdk_metrics.c
index fdcc03c1f..5fdcdfb74 100644
--- a/src/native/iot_device_sdk_metrics.c
+++ b/src/native/iot_device_sdk_metrics.c
@@ -22,6 +22,130 @@
static char s_iot_device_sdk_metrics_string[] = "IoTDeviceSDKMetrics";
+/*******************************************************************************
+ * HELPER FUNCTIONS
+ ******************************************************************************/
+/**
+ * Parse a java list of objects (each having a String key field and a String value field)
+ * into a contiguous buffer.
+ *
+ * Two-pass approach:
+ * Pass 1 - measure total byte size of all keys and value
+ * Pass 2 - copy into one buffer, set cursors, pointing into it.
+ *
+ * @param env JNI environment
+ * @param allocator CRT allocator
+ * @param java_list Java List jobject (must not be NULL, count must be > 0)
+ * @param count Number of elements in the list
+ * @param key_field_id JNI field ID for the String key on each list element
+ * @param value_field_id JNI field ID for the String value on each list element
+ * @param out_storage Output buffer that will own all string bytes
+ * @param out_entries Pre-allocated array of count entries to populate
+ *
+ * @return AWS_OP_SUCCESS or AWS_OP_ERR
+ */
+static int s_parse_string_pair_list(
+ JNIEnv *env,
+ struct aws_allocator *allocator,
+ jobject java_list,
+ size_t count,
+ jfieldID key_field_id,
+ jfieldID value_field_id,
+ struct aws_byte_buf *out_storage,
+ struct aws_mqtt_metadata_entry *out_entries) {
+
+ /*
+ * First pass: iterate all entries to compute the total byte size needed for
+ * all key and value strings combined. This lets us do a single allocation.
+ */
+ size_t total_size = 0;
+ for (size_t i = 0; i < count; i++) {
+ /* Call List.get(i) to retrieve the list object at this index */
+ jobject entry = (*env)->CallObjectMethod(env, java_list, boxed_list_properties.list_get_id, (jint)i);
+ if (entry == NULL || aws_jni_check_and_clear_exception(env)) {
+ AWS_LOGF_ERROR(AWS_LS_MQTT_GENERAL, "IoTDeviceSDKMetrics: failed to get entry at index %d", (int)i);
+ return AWS_OP_ERR;
+ }
+
+ /* Read the key and value String fields from the entry */
+ jstring key_jstr = (jstring)(*env)->GetObjectField(env, entry, key_field_id);
+ jstring value_jstr = (jstring)(*env)->GetObjectField(env, entry, value_field_id);
+
+ /* Accumulate byte lengths (modified-UTF8). Empty strings contribute 0 — that's valid. */
+ total_size += (size_t)(*env)->GetStringUTFLength(env, key_jstr);
+ total_size += (size_t)(*env)->GetStringUTFLength(env, value_jstr);
+
+ /* Release local refs to avoid exhausting JNI local ref table in large lists */
+ (*env)->DeleteLocalRef(env, key_jstr);
+ (*env)->DeleteLocalRef(env, value_jstr);
+ (*env)->DeleteLocalRef(env, entry);
+ }
+ AWS_LOGF_DEBUG(
+ AWS_LS_MQTT_GENERAL,
+ "IoTDeviceSDKMetrics: first pass complete, total_size=%zu for %d entries",
+ total_size,
+ (int)count);
+ /* Allocate one contiguous buffer for all strings */
+ if (aws_byte_buf_init(out_storage, allocator, total_size)) {
+ AWS_LOGF_ERROR(
+ AWS_LS_MQTT_GENERAL, "IoTDeviceSDKMetrics: failed to allocate %zu bytes for metadata storage", total_size);
+ return AWS_OP_ERR;
+ }
+
+ /*
+ * Second pass: copy each key/value string into the contiguous buffer and
+ * build aws_byte_cursor structs that point into it at the correct offsets.
+ */
+ for (size_t i = 0; i < count; i++) {
+ jobject entry = (*env)->CallObjectMethod(env, java_list, boxed_list_properties.list_get_id, (jint)i);
+ if (entry == NULL || aws_jni_check_and_clear_exception(env)) {
+ AWS_LOGF_ERROR(AWS_LS_MQTT_GENERAL, "IoTDeviceSDKMetrics: failed to get entry at index %d", (int)i);
+ return AWS_OP_ERR;
+ }
+
+ jstring key_jstr = (jstring)(*env)->GetObjectField(env, entry, key_field_id);
+ jstring value_jstr = (jstring)(*env)->GetObjectField(env, entry, value_field_id);
+
+ /* Acquire the raw UTF-8 bytes from the JVM for the key string */
+ struct aws_byte_cursor key_cursor = aws_jni_byte_cursor_from_jstring_acquire(env, key_jstr);
+
+ /* Record where in the buffer this key will start */
+ size_t key_offset = out_storage->len;
+ /* Copy key bytes into our contiguous buffer */
+ aws_byte_buf_append(out_storage, &key_cursor);
+ /* Release the JVM's internal string memory*/
+ aws_jni_byte_cursor_from_jstring_release(env, key_jstr, key_cursor);
+
+ /* same pattern as above */
+ struct aws_byte_cursor value_cursor = aws_jni_byte_cursor_from_jstring_acquire(env, value_jstr);
+ size_t value_offset = out_storage->len;
+ aws_byte_buf_append(out_storage, &value_cursor);
+ aws_jni_byte_cursor_from_jstring_release(env, value_jstr, value_cursor);
+
+ out_entries[i].key = aws_byte_cursor_from_array(out_storage->buffer + key_offset, key_cursor.len);
+ out_entries[i].value = aws_byte_cursor_from_array(out_storage->buffer + value_offset, value_cursor.len);
+
+ AWS_LOGF_DEBUG(
+ AWS_LS_MQTT_GENERAL,
+ "IoTDeviceSDKMetrics: metadata[%d] key=\"" PRInSTR "\" value=\"" PRInSTR "\"",
+ (int)i,
+ AWS_BYTE_CURSOR_PRI(out_entries[i].key),
+ AWS_BYTE_CURSOR_PRI(out_entries[i].value));
+
+ (*env)->DeleteLocalRef(env, key_jstr);
+ (*env)->DeleteLocalRef(env, value_jstr);
+ (*env)->DeleteLocalRef(env, entry);
+ }
+
+ AWS_LOGF_DEBUG(
+ AWS_LS_MQTT_GENERAL,
+ "IoTDeviceSDKMetrics: second pass complete, %d entries parsed into %zu bytes",
+ (int)count,
+ out_storage->len);
+
+ return AWS_OP_SUCCESS;
+}
+
/* Frees all native memory associated with a parsed metrics struct. */
void aws_mqtt_iot_metrics_java_jni_destroy(
JNIEnv *env,
@@ -55,11 +179,13 @@ struct aws_mqtt_iot_metrics_java_jni *aws_mqtt_iot_metrics_java_jni_create_from_
struct aws_allocator *allocator,
jobject java_iot_device_sdk_metrics) {
- /* Allocate the holder struct (zero-initialized) */
+ jobject metadata_list = NULL;
+
struct aws_mqtt_iot_metrics_java_jni *java_metrics =
aws_mem_calloc(allocator, 1, sizeof(struct aws_mqtt_iot_metrics_java_jni));
- /* Extract the library name string (e.g. "IoTDeviceSDK/Java") */
+ AWS_LOGF_DEBUG(AWS_LS_MQTT_GENERAL, "id=%p: Creating IoTDeviceSDKMetrics from Java object", (void *)java_metrics);
+ /* Extract the library name string */
if (aws_get_string_from_jobject(
env,
java_iot_device_sdk_metrics,
@@ -74,106 +200,61 @@ struct aws_mqtt_iot_metrics_java_jni *aws_mqtt_iot_metrics_java_jni_create_from_
goto on_error;
}
- /* Parse metadataEntries list */
- jobject metadata_list = (*env)->GetObjectField(
+ /* Get the metadata list */
+ metadata_list = (*env)->GetObjectField(
env, java_iot_device_sdk_metrics, iot_device_sdk_metrics_properties.metadata_entries_field_id);
- if (metadata_list != NULL && !aws_jni_check_and_clear_exception(env)) {
- jint count = (*env)->CallIntMethod(env, metadata_list, boxed_list_properties.list_size_id);
- if (count > 0 && !aws_jni_check_and_clear_exception(env)) {
- /* Pre-allocate entry array for all metadata key-value pairs */
- java_metrics->metadata_entries =
- aws_mem_calloc(allocator, (size_t)count, sizeof(struct aws_mqtt_metadata_entry));
-
- /* First pass: compute exact buffer size needed */
- size_t total_size = 0;
- for (jint i = 0; i < count; i++) {
- jobject entry = (*env)->CallObjectMethod(env, metadata_list, boxed_list_properties.list_get_id, i);
- if (entry == NULL || aws_jni_check_and_clear_exception(env)) {
- AWS_LOGF_ERROR(
- AWS_LS_MQTT_GENERAL, "IoTDeviceSDKMetrics: failed to get metadata entry at index %d", (int)i);
- goto on_error;
- }
- jstring key_jstr =
- (jstring)(*env)->GetObjectField(env, entry, iot_metrics_metadata_properties.key_field_id);
- jstring value_jstr =
- (jstring)(*env)->GetObjectField(env, entry, iot_metrics_metadata_properties.value_field_id);
- total_size += (size_t)(*env)->GetStringUTFLength(env, key_jstr);
- total_size += (size_t)(*env)->GetStringUTFLength(env, value_jstr);
- (*env)->DeleteLocalRef(env, key_jstr);
- (*env)->DeleteLocalRef(env, value_jstr);
- (*env)->DeleteLocalRef(env, entry);
- }
-
- AWS_LOGF_INFO(
- AWS_LS_MQTT_GENERAL,
- "IoTDeviceSDKMetrics: first pass complete, total_size=%zu for %d entries",
- total_size,
- (int)count);
-
- /* Allocate exact size */
- aws_byte_buf_init(&java_metrics->metadata_storage, allocator, total_size);
-
- /* Second pass: copy strings into contiguous buffer and set cursors pointing into it */
- for (jint i = 0; i < count; i++) {
- jobject entry = (*env)->CallObjectMethod(env, metadata_list, boxed_list_properties.list_get_id, i);
- if (entry == NULL || aws_jni_check_and_clear_exception(env)) {
- AWS_LOGF_ERROR(
- AWS_LS_MQTT_GENERAL, "IoTDeviceSDKMetrics: failed to get metadata entry at index %d", (int)i);
- goto on_error;
- }
-
- jstring key_jstr =
- (jstring)(*env)->GetObjectField(env, entry, iot_metrics_metadata_properties.key_field_id);
- jstring value_jstr =
- (jstring)(*env)->GetObjectField(env, entry, iot_metrics_metadata_properties.value_field_id);
-
- struct aws_byte_cursor key_cursor = aws_jni_byte_cursor_from_jstring_acquire(env, key_jstr);
- size_t key_offset = java_metrics->metadata_storage.len;
- aws_byte_buf_append(&java_metrics->metadata_storage, &key_cursor);
- aws_jni_byte_cursor_from_jstring_release(env, key_jstr, key_cursor);
-
- struct aws_byte_cursor value_cursor = aws_jni_byte_cursor_from_jstring_acquire(env, value_jstr);
- size_t value_offset = java_metrics->metadata_storage.len;
- aws_byte_buf_append(&java_metrics->metadata_storage, &value_cursor);
- aws_jni_byte_cursor_from_jstring_release(env, value_jstr, value_cursor);
-
- java_metrics->metadata_entries[java_metrics->metadata_count].key =
- aws_byte_cursor_from_array(java_metrics->metadata_storage.buffer + key_offset, key_cursor.len);
- java_metrics->metadata_entries[java_metrics->metadata_count].value =
- aws_byte_cursor_from_array(java_metrics->metadata_storage.buffer + value_offset, value_cursor.len);
-
- AWS_LOGF_INFO(
- AWS_LS_MQTT_GENERAL,
- "IoTDeviceSDKMetrics metadata[%zu]: key=\"" PRInSTR "\" value=\"" PRInSTR "\"",
- java_metrics->metadata_count,
- AWS_BYTE_CURSOR_PRI(java_metrics->metadata_entries[java_metrics->metadata_count].key),
- AWS_BYTE_CURSOR_PRI(java_metrics->metadata_entries[java_metrics->metadata_count].value));
-
- java_metrics->metadata_count++;
-
- (*env)->DeleteLocalRef(env, key_jstr);
- (*env)->DeleteLocalRef(env, value_jstr);
- (*env)->DeleteLocalRef(env, entry);
- }
-
- /* Wire up the C metrics struct to point at our parsed data */
- java_metrics->metrics.metadata_entries = java_metrics->metadata_entries;
- java_metrics->metrics.metadata_count = java_metrics->metadata_count;
-
- AWS_LOGF_INFO(
- AWS_LS_MQTT_GENERAL,
- "IoTDeviceSDKMetrics: parsing complete, %zu metadata entries set",
- java_metrics->metadata_count);
- }
+ if (metadata_list == NULL || aws_jni_check_and_clear_exception(env)) {
+ AWS_LOGF_DEBUG(
+ AWS_LS_MQTT_GENERAL,
+ "id=%p: IoTDeviceSDKMetrics no metadata entries, returning with library name only",
+ (void *)java_metrics);
+ return java_metrics;
+ }
+ jint count = (*env)->CallIntMethod(env, metadata_list, boxed_list_properties.list_size_id);
+ if (aws_jni_check_and_clear_exception(env) || count <= 0) {
+ AWS_LOGF_DEBUG(
+ AWS_LS_MQTT_GENERAL,
+ "id=%p: IoTDeviceSDKMetrics metadata list empty or size() failed",
+ (void *)java_metrics);
(*env)->DeleteLocalRef(env, metadata_list);
+ return java_metrics;
}
+ /* Allocate entry array */
+ java_metrics->metadata_entries = aws_mem_calloc(allocator, (size_t)count, sizeof(struct aws_mqtt_metadata_entry));
+
+ /* Use helper to do the two-pass parse */
+ if (s_parse_string_pair_list(
+ env,
+ allocator,
+ metadata_list,
+ (size_t)count,
+ iot_metrics_metadata_properties.key_field_id,
+ iot_metrics_metadata_properties.value_field_id,
+ &java_metrics->metadata_storage,
+ java_metrics->metadata_entries) == AWS_OP_ERR) {
+ goto on_error;
+ }
+
+ /* Wire up the C struct */
+ java_metrics->metadata_count = (size_t)count;
+ java_metrics->metrics.metadata_entries = java_metrics->metadata_entries;
+ java_metrics->metrics.metadata_count = (size_t)count;
+
+ AWS_LOGF_DEBUG(
+ AWS_LS_MQTT_GENERAL,
+ "id=%p: IoTDeviceSDKMetrics creation complete, %d metadata entries",
+ (void *)java_metrics,
+ (int)count);
+ (*env)->DeleteLocalRef(env, metadata_list);
return java_metrics;
on_error:
- /* clean up */
+ if (metadata_list != NULL) {
+ (*env)->DeleteLocalRef(env, metadata_list);
+ }
aws_mqtt_iot_metrics_java_jni_destroy(env, allocator, java_metrics);
return NULL;
}
From 13d4d1e3f007c6761cd6903dd1e45d1cd3c4af90 Mon Sep 17 00:00:00 2001
From: Rakshil Modi
Date: Thu, 11 Jun 2026 16:10:35 -0700
Subject: [PATCH 11/16] refactoring
---
.../awssdk/crt/iot/IoTDeviceSDKMetrics.java | 8 +-------
.../awssdk/crt/mqtt/MqttConnectionConfig.java | 17 +++++++++++++++++
src/native/iot_device_sdk_metrics.c | 2 +-
3 files changed, 19 insertions(+), 8 deletions(-)
diff --git a/src/main/java/software/amazon/awssdk/crt/iot/IoTDeviceSDKMetrics.java b/src/main/java/software/amazon/awssdk/crt/iot/IoTDeviceSDKMetrics.java
index 4a31cd6ed..857e081f9 100644
--- a/src/main/java/software/amazon/awssdk/crt/iot/IoTDeviceSDKMetrics.java
+++ b/src/main/java/software/amazon/awssdk/crt/iot/IoTDeviceSDKMetrics.java
@@ -85,13 +85,7 @@ public static IoTDeviceSDKMetrics createMetricsMqtt5(Mqtt5ClientOptions clientOp
* @return the merged metrics object ready to be passed to JNI
*/
public static IoTDeviceSDKMetrics createMetricsMqtt3(MqttConnectionConfig config) {
- TlsContext tls = null;
- if (config.getMqttClient() != null) {
- tls = config.getMqttClient().getTlsContext();
- } else if (config.getMqtt5Client() != null) {
- tls = config.getMqtt5Client().getClientOptions().getTlsContext();
- }
- String crtFeatureList = getEncodedFeatureListMqtt3(config.getHttpProxyOptions(), tls);
+ String crtFeatureList = getEncodedFeatureListMqtt3(config.getHttpProxyOptions(), config.getTlsContext());
return createMetrics(config.getMetrics(), crtFeatureList);
}
diff --git a/src/main/java/software/amazon/awssdk/crt/mqtt/MqttConnectionConfig.java b/src/main/java/software/amazon/awssdk/crt/mqtt/MqttConnectionConfig.java
index 92e834b72..4d15832a9 100644
--- a/src/main/java/software/amazon/awssdk/crt/mqtt/MqttConnectionConfig.java
+++ b/src/main/java/software/amazon/awssdk/crt/mqtt/MqttConnectionConfig.java
@@ -13,6 +13,8 @@
import software.amazon.awssdk.crt.io.ClientTlsContext;
import software.amazon.awssdk.crt.io.SocketOptions;
import software.amazon.awssdk.crt.mqtt5.Mqtt5Client;
+import software.amazon.awssdk.crt.io.TlsContext;
+
/**
* Encapsulates all per-mqtt-connection configuration
@@ -581,6 +583,21 @@ public IoTDeviceSDKMetrics getMetrics() {
return metrics;
}
+ /**
+ * Returns the {@link TlsContext} this connection will be resolved from
+ * the underlying {@link MqttClient} or {@link Mqtt5Client}.
+ *
+ * @return the TLS context, or {@code null} if neither client is set or no TLS is configured
+ */
+ public TlsContext getTlsContext() {
+ if (mqttClient != null) {
+ return mqttClient.getTlsContext();
+ } else if (mqtt5Client != null) {
+ return mqtt5Client.getClientOptions().getTlsContext();
+ }
+ return null;
+ }
+
/**
* Creates a (shallow) clone of this config object
diff --git a/src/native/iot_device_sdk_metrics.c b/src/native/iot_device_sdk_metrics.c
index 5fdcdfb74..41c054093 100644
--- a/src/native/iot_device_sdk_metrics.c
+++ b/src/native/iot_device_sdk_metrics.c
@@ -71,7 +71,7 @@ static int s_parse_string_pair_list(
jstring key_jstr = (jstring)(*env)->GetObjectField(env, entry, key_field_id);
jstring value_jstr = (jstring)(*env)->GetObjectField(env, entry, value_field_id);
- /* Accumulate byte lengths (modified-UTF8). Empty strings contribute 0 — that's valid. */
+ /* Accumulate byte lengths (modified-UTF8). */
total_size += (size_t)(*env)->GetStringUTFLength(env, key_jstr);
total_size += (size_t)(*env)->GetStringUTFLength(env, value_jstr);
From 37d736f9f647c24f9e1e14fe112e6ce58621c889 Mon Sep 17 00:00:00 2001
From: Rakshil Modi
Date: Thu, 11 Jun 2026 18:36:54 -0700
Subject: [PATCH 12/16] updating list in jni
---
.../awssdk/crt/iot/IoTDeviceSDKMetrics.java | 11 +-
.../awssdk/crt/mqtt/MqttConnectionConfig.java | 1 -
src/native/iot_device_sdk_metrics.c | 246 +++++++-----------
src/native/iot_device_sdk_metrics.h | 2 +-
4 files changed, 98 insertions(+), 162 deletions(-)
diff --git a/src/main/java/software/amazon/awssdk/crt/iot/IoTDeviceSDKMetrics.java b/src/main/java/software/amazon/awssdk/crt/iot/IoTDeviceSDKMetrics.java
index 857e081f9..2525bc798 100644
--- a/src/main/java/software/amazon/awssdk/crt/iot/IoTDeviceSDKMetrics.java
+++ b/src/main/java/software/amazon/awssdk/crt/iot/IoTDeviceSDKMetrics.java
@@ -62,7 +62,6 @@ public IoTDeviceSDKMetrics(String libraryName, List metadata
public void setMetadataEntries(List metadataEntries) { this.metadataEntries = metadataEntries; }
-
/**
* Builds the final metrics object for an MQTT5 client by encoding the CRT
* feature list from {@code clientOptions} and merging it with any
@@ -89,7 +88,6 @@ public static IoTDeviceSDKMetrics createMetricsMqtt3(MqttConnectionConfig config
return createMetrics(config.getMetrics(), crtFeatureList);
}
-
/**
* Generates the encoded feature list string for metrics from MQTT5 client options.
*
@@ -230,7 +228,6 @@ private static String mergeFeatureLists(String crtFeatures, String userFeatures)
return sb.toString();
}
-
/**
*
* Metrics creation logic:
@@ -336,7 +333,7 @@ private static String retryJitterModeValue(JitterMode mode) {
/**
* Encodes an MQTT5 session behavior enum value.
*
- * @param value the {@code ClientSessionBehavior} ordinal
+ * @param behavior the {@code ClientSessionBehavior} ordinal
* @return {@code "A"} for CLEAN, {@code "B"} for REJOIN_POST_SUCCESS, {@code "C"} for REJOIN_ALWAYS,
* or {@code null} for DEFAULT/unknown
*/
@@ -352,7 +349,7 @@ private static String sessionBehaviorValue(Mqtt5ClientOptions.ClientSessionBehav
/**
* Encodes an MQTT5 offline queue behavior enum value.
*
- * @param value the {@code ClientOfflineQueueBehavior} ordinal
+ * @param behavior the {@code ClientOfflineQueueBehavior} ordinal
* @return {@code "A"} for FAIL_NON_QOS1_PUBLISH_ON_DISCONNECT, {@code "B"} for FAIL_QOS0_PUBLISH_ON_DISCONNECT,
* {@code "C"} for FAIL_ALL_ON_DISCONNECT, or {@code null} for DEFAULT/unknown
*/
@@ -368,7 +365,7 @@ private static String offlineQueueBehaviorValue(Mqtt5ClientOptions.ClientOffline
/**
* Encodes an outbound topic alias behavior enum value.
*
- * @param value the {@code OutboundTopicAliasBehaviorType} ordinal
+ * @param behavior the {@code OutboundTopicAliasBehaviorType} ordinal
* @return {@code "A"} for Manual, {@code "B"} for LRU, {@code "C"} for Disabled,
* or {@code null} for Default/unknown
*/
@@ -384,7 +381,7 @@ private static String outboundTopicAliasBehaviorValue(TopicAliasingOptions.Outbo
/**
* Encodes an inbound topic alias behavior enum value.
*
- * @param value the {@code InboundTopicAliasBehaviorType} ordinal
+ * @param behavior the {@code InboundTopicAliasBehaviorType} ordinal
* @return {@code "A"} for Enabled, {@code "B"} for Disabled, or {@code null} for Default/unknown
*/
private static String inboundTopicAliasBehaviorValue(TopicAliasingOptions.InboundTopicAliasBehaviorType behavior) {
diff --git a/src/main/java/software/amazon/awssdk/crt/mqtt/MqttConnectionConfig.java b/src/main/java/software/amazon/awssdk/crt/mqtt/MqttConnectionConfig.java
index 4d15832a9..f3b5ce645 100644
--- a/src/main/java/software/amazon/awssdk/crt/mqtt/MqttConnectionConfig.java
+++ b/src/main/java/software/amazon/awssdk/crt/mqtt/MqttConnectionConfig.java
@@ -598,7 +598,6 @@ public TlsContext getTlsContext() {
return null;
}
-
/**
* Creates a (shallow) clone of this config object
*
diff --git a/src/native/iot_device_sdk_metrics.c b/src/native/iot_device_sdk_metrics.c
index 41c054093..bc10f005c 100644
--- a/src/native/iot_device_sdk_metrics.c
+++ b/src/native/iot_device_sdk_metrics.c
@@ -6,10 +6,9 @@
/**
* JNI bridge for IoT Device SDK Metrics.
*
- * This file is responsible for converting the Java IoTDeviceSDKMetrics object
- * (library name + metadata key-value entries) into a native aws_mqtt_iot_metrics
- * struct that the C MQTT layer uses to append SDK telemetry to the CONNECT packet
- * username field.
+ * Converts a Java IoTDeviceSDKMetrics object (library name + metadata key-value
+ * entries) into a native aws_mqtt_iot_metrics struct that the C MQTT layer uses
+ * to append SDK telemetry to the CONNECT packet username field.
*
*/
#include
@@ -21,130 +20,11 @@
#include
static char s_iot_device_sdk_metrics_string[] = "IoTDeviceSDKMetrics";
-
-/*******************************************************************************
- * HELPER FUNCTIONS
- ******************************************************************************/
-/**
- * Parse a java list of objects (each having a String key field and a String value field)
- * into a contiguous buffer.
- *
- * Two-pass approach:
- * Pass 1 - measure total byte size of all keys and value
- * Pass 2 - copy into one buffer, set cursors, pointing into it.
- *
- * @param env JNI environment
- * @param allocator CRT allocator
- * @param java_list Java List jobject (must not be NULL, count must be > 0)
- * @param count Number of elements in the list
- * @param key_field_id JNI field ID for the String key on each list element
- * @param value_field_id JNI field ID for the String value on each list element
- * @param out_storage Output buffer that will own all string bytes
- * @param out_entries Pre-allocated array of count entries to populate
- *
- * @return AWS_OP_SUCCESS or AWS_OP_ERR
- */
-static int s_parse_string_pair_list(
- JNIEnv *env,
- struct aws_allocator *allocator,
- jobject java_list,
- size_t count,
- jfieldID key_field_id,
- jfieldID value_field_id,
- struct aws_byte_buf *out_storage,
- struct aws_mqtt_metadata_entry *out_entries) {
-
- /*
- * First pass: iterate all entries to compute the total byte size needed for
- * all key and value strings combined. This lets us do a single allocation.
- */
- size_t total_size = 0;
- for (size_t i = 0; i < count; i++) {
- /* Call List.get(i) to retrieve the list object at this index */
- jobject entry = (*env)->CallObjectMethod(env, java_list, boxed_list_properties.list_get_id, (jint)i);
- if (entry == NULL || aws_jni_check_and_clear_exception(env)) {
- AWS_LOGF_ERROR(AWS_LS_MQTT_GENERAL, "IoTDeviceSDKMetrics: failed to get entry at index %d", (int)i);
- return AWS_OP_ERR;
- }
-
- /* Read the key and value String fields from the entry */
- jstring key_jstr = (jstring)(*env)->GetObjectField(env, entry, key_field_id);
- jstring value_jstr = (jstring)(*env)->GetObjectField(env, entry, value_field_id);
-
- /* Accumulate byte lengths (modified-UTF8). */
- total_size += (size_t)(*env)->GetStringUTFLength(env, key_jstr);
- total_size += (size_t)(*env)->GetStringUTFLength(env, value_jstr);
-
- /* Release local refs to avoid exhausting JNI local ref table in large lists */
- (*env)->DeleteLocalRef(env, key_jstr);
- (*env)->DeleteLocalRef(env, value_jstr);
- (*env)->DeleteLocalRef(env, entry);
- }
- AWS_LOGF_DEBUG(
- AWS_LS_MQTT_GENERAL,
- "IoTDeviceSDKMetrics: first pass complete, total_size=%zu for %d entries",
- total_size,
- (int)count);
- /* Allocate one contiguous buffer for all strings */
- if (aws_byte_buf_init(out_storage, allocator, total_size)) {
- AWS_LOGF_ERROR(
- AWS_LS_MQTT_GENERAL, "IoTDeviceSDKMetrics: failed to allocate %zu bytes for metadata storage", total_size);
- return AWS_OP_ERR;
- }
-
- /*
- * Second pass: copy each key/value string into the contiguous buffer and
- * build aws_byte_cursor structs that point into it at the correct offsets.
- */
- for (size_t i = 0; i < count; i++) {
- jobject entry = (*env)->CallObjectMethod(env, java_list, boxed_list_properties.list_get_id, (jint)i);
- if (entry == NULL || aws_jni_check_and_clear_exception(env)) {
- AWS_LOGF_ERROR(AWS_LS_MQTT_GENERAL, "IoTDeviceSDKMetrics: failed to get entry at index %d", (int)i);
- return AWS_OP_ERR;
- }
-
- jstring key_jstr = (jstring)(*env)->GetObjectField(env, entry, key_field_id);
- jstring value_jstr = (jstring)(*env)->GetObjectField(env, entry, value_field_id);
-
- /* Acquire the raw UTF-8 bytes from the JVM for the key string */
- struct aws_byte_cursor key_cursor = aws_jni_byte_cursor_from_jstring_acquire(env, key_jstr);
-
- /* Record where in the buffer this key will start */
- size_t key_offset = out_storage->len;
- /* Copy key bytes into our contiguous buffer */
- aws_byte_buf_append(out_storage, &key_cursor);
- /* Release the JVM's internal string memory*/
- aws_jni_byte_cursor_from_jstring_release(env, key_jstr, key_cursor);
-
- /* same pattern as above */
- struct aws_byte_cursor value_cursor = aws_jni_byte_cursor_from_jstring_acquire(env, value_jstr);
- size_t value_offset = out_storage->len;
- aws_byte_buf_append(out_storage, &value_cursor);
- aws_jni_byte_cursor_from_jstring_release(env, value_jstr, value_cursor);
-
- out_entries[i].key = aws_byte_cursor_from_array(out_storage->buffer + key_offset, key_cursor.len);
- out_entries[i].value = aws_byte_cursor_from_array(out_storage->buffer + value_offset, value_cursor.len);
-
- AWS_LOGF_DEBUG(
- AWS_LS_MQTT_GENERAL,
- "IoTDeviceSDKMetrics: metadata[%d] key=\"" PRInSTR "\" value=\"" PRInSTR "\"",
- (int)i,
- AWS_BYTE_CURSOR_PRI(out_entries[i].key),
- AWS_BYTE_CURSOR_PRI(out_entries[i].value));
-
- (*env)->DeleteLocalRef(env, key_jstr);
- (*env)->DeleteLocalRef(env, value_jstr);
- (*env)->DeleteLocalRef(env, entry);
- }
-
- AWS_LOGF_DEBUG(
- AWS_LS_MQTT_GENERAL,
- "IoTDeviceSDKMetrics: second pass complete, %d entries parsed into %zu bytes",
- (int)count,
- out_storage->len);
-
- return AWS_OP_SUCCESS;
-}
+ struct aws_metadata_buf_holder {
+ struct aws_byte_cursor cursor;
+ struct aws_byte_buf buffer;
+ };
+
/* Frees all native memory associated with a parsed metrics struct. */
void aws_mqtt_iot_metrics_java_jni_destroy(
@@ -158,18 +38,28 @@ void aws_mqtt_iot_metrics_java_jni_destroy(
}
AWS_LOGF_DEBUG(AWS_LS_MQTT_GENERAL, "id=%p: Destroying IoTDeviceSDKMetrics", (void *)java_metrics);
+ /* Free the library name buffer */
if (aws_byte_buf_is_valid(&java_metrics->library_name_buf)) {
aws_byte_buf_clean_up(&java_metrics->library_name_buf);
}
- if (aws_byte_buf_is_valid(&java_metrics->metadata_storage)) {
- aws_byte_buf_clean_up(&java_metrics->metadata_storage);
+ /* Free each individual key/value buffer stored in the array_list */
+ if (aws_array_list_is_valid(&java_metrics->metadata_bufs)) {
+ size_t buf_count = aws_array_list_length(&java_metrics->metadata_bufs);
+ for (size_t i = 0; i < buf_count; i++) {
+ struct aws_metadata_buf_holder *holder = NULL;
+ aws_array_list_get_at_ptr(&java_metrics->metadata_bufs, (void **)&holder, i);
+ aws_byte_buf_clean_up(&holder->buffer);
+ }
+ aws_array_list_clean_up(&java_metrics->metadata_bufs);
}
+ /* Free the pre-allocated entries array */
if (java_metrics->metadata_entries) {
aws_mem_release(allocator, java_metrics->metadata_entries);
}
+ /* Free the wrapper struct itself */
aws_mem_release(allocator, java_metrics);
}
@@ -181,11 +71,17 @@ struct aws_mqtt_iot_metrics_java_jni *aws_mqtt_iot_metrics_java_jni_create_from_
jobject metadata_list = NULL;
+ /* Zero-initialize so all fields are safe for cleanup on any error path */
struct aws_mqtt_iot_metrics_java_jni *java_metrics =
aws_mem_calloc(allocator, 1, sizeof(struct aws_mqtt_iot_metrics_java_jni));
AWS_LOGF_DEBUG(AWS_LS_MQTT_GENERAL, "id=%p: Creating IoTDeviceSDKMetrics from Java object", (void *)java_metrics);
- /* Extract the library name string */
+
+ /*
+ * Extract the library name (e.g. "IoTDeviceSDK/Java").
+ * Copies the Java String into library_name_buf and sets metrics.library_name
+ * as a cursor pointing into that buffer.
+ */
if (aws_get_string_from_jobject(
env,
java_iot_device_sdk_metrics,
@@ -200,54 +96,98 @@ struct aws_mqtt_iot_metrics_java_jni *aws_mqtt_iot_metrics_java_jni_create_from_
goto on_error;
}
- /* Get the metadata list */
+ /* Read the Java List field */
metadata_list = (*env)->GetObjectField(
env, java_iot_device_sdk_metrics, iot_device_sdk_metrics_properties.metadata_entries_field_id);
+ /* Null list is valid — return metrics with just library name */
if (metadata_list == NULL || aws_jni_check_and_clear_exception(env)) {
- AWS_LOGF_DEBUG(
- AWS_LS_MQTT_GENERAL,
- "id=%p: IoTDeviceSDKMetrics no metadata entries, returning with library name only",
- (void *)java_metrics);
+ AWS_LOGF_DEBUG(AWS_LS_MQTT_GENERAL, "id=%p: IoTDeviceSDKMetrics no metadata entries", (void *)java_metrics);
return java_metrics;
}
+ /* Get list size */
jint count = (*env)->CallIntMethod(env, metadata_list, boxed_list_properties.list_size_id);
+
+ /* Empty list is valid — return metrics with just library name */
if (aws_jni_check_and_clear_exception(env) || count <= 0) {
- AWS_LOGF_DEBUG(
- AWS_LS_MQTT_GENERAL,
- "id=%p: IoTDeviceSDKMetrics metadata list empty or size() failed",
- (void *)java_metrics);
+ AWS_LOGF_DEBUG(AWS_LS_MQTT_GENERAL, "id=%p: IoTDeviceSDKMetrics metadata list empty", (void *)java_metrics);
(*env)->DeleteLocalRef(env, metadata_list);
return java_metrics;
}
- /* Allocate entry array */
+ /* Pre-allocate entries array since we know the count */
java_metrics->metadata_entries = aws_mem_calloc(allocator, (size_t)count, sizeof(struct aws_mqtt_metadata_entry));
- /* Use helper to do the two-pass parse */
- if (s_parse_string_pair_list(
- env,
+ /* Init array_list to hold individual byte_bufs (2 per entry: key + value) */
+ if (aws_array_list_init_dynamic(
+ &java_metrics->metadata_bufs,
allocator,
- metadata_list,
- (size_t)count,
- iot_metrics_metadata_properties.key_field_id,
- iot_metrics_metadata_properties.value_field_id,
- &java_metrics->metadata_storage,
- java_metrics->metadata_entries) == AWS_OP_ERR) {
+ (size_t)count * 2,
+ sizeof(struct aws_metadata_buf_holder))) {
goto on_error;
}
- /* Wire up the C struct */
- java_metrics->metadata_count = (size_t)count;
+ for (jint i = 0; i < count; i++) {
+ /* Call List.get(i) to get the IoTMetricsMetadata object */
+ jobject entry = (*env)->CallObjectMethod(env, metadata_list, boxed_list_properties.list_get_id, i);
+ if (entry == NULL || aws_jni_check_and_clear_exception(env)) {
+ AWS_LOGF_ERROR(AWS_LS_MQTT_GENERAL, "IoTDeviceSDKMetrics: failed to get entry at index %d", (int)i);
+ goto on_error;
+ }
+
+ /* Read key and value jstrings from the entry */
+ jstring key_jstr = (jstring)(*env)->GetObjectField(env, entry, iot_metrics_metadata_properties.key_field_id);
+ jstring value_jstr =
+ (jstring)(*env)->GetObjectField(env, entry, iot_metrics_metadata_properties.value_field_id);
+
+ /* Key: acquire JVM bytes → copy into own buffer → release JVM bytes */
+ struct aws_metadata_buf_holder holder_key;
+ struct aws_byte_cursor tmp_cursor = aws_jni_byte_cursor_from_jstring_acquire(env, key_jstr);
+ aws_byte_buf_init_copy_from_cursor(&holder_key.buffer, allocator, tmp_cursor);
+ holder_key.cursor = aws_byte_cursor_from_buf(&holder_key.buffer);
+ aws_jni_byte_cursor_from_jstring_release(env, key_jstr, tmp_cursor);
+
+ /* Value: same pattern */
+ struct aws_metadata_buf_holder holder_value;
+ tmp_cursor = aws_jni_byte_cursor_from_jstring_acquire(env, value_jstr);
+ aws_byte_buf_init_copy_from_cursor(&holder_value.buffer, allocator, tmp_cursor);
+ holder_value.cursor = aws_byte_cursor_from_buf(&holder_value.buffer);
+ aws_jni_byte_cursor_from_jstring_release(env, value_jstr, tmp_cursor);
+
+ /* Store holders so destroy can free each buffer later */
+ aws_array_list_push_back(&java_metrics->metadata_bufs, &holder_key);
+ aws_array_list_push_back(&java_metrics->metadata_bufs, &holder_value);
+
+ /* Set entry cursors — point at heap memory owned by the holders */
+ java_metrics->metadata_entries[i].key = holder_key.cursor;
+ java_metrics->metadata_entries[i].value = holder_value.cursor;
+
+ AWS_LOGF_DEBUG(
+ AWS_LS_MQTT_GENERAL,
+ "IoTDeviceSDKMetrics: metadata[%d] key=\"" PRInSTR "\" value=\"" PRInSTR "\"",
+ (int)i,
+ AWS_BYTE_CURSOR_PRI(java_metrics->metadata_entries[i].key),
+ AWS_BYTE_CURSOR_PRI(java_metrics->metadata_entries[i].value));
+
+ java_metrics->metadata_count++;
+
+ /* Release JNI local refs for this iteration */
+ (*env)->DeleteLocalRef(env, key_jstr);
+ (*env)->DeleteLocalRef(env, value_jstr);
+ (*env)->DeleteLocalRef(env, entry);
+ }
+
+ /* Point the C metrics struct at our parsed data */
java_metrics->metrics.metadata_entries = java_metrics->metadata_entries;
- java_metrics->metrics.metadata_count = (size_t)count;
+ java_metrics->metrics.metadata_count = java_metrics->metadata_count;
AWS_LOGF_DEBUG(
AWS_LS_MQTT_GENERAL,
"id=%p: IoTDeviceSDKMetrics creation complete, %d metadata entries",
(void *)java_metrics,
(int)count);
+
(*env)->DeleteLocalRef(env, metadata_list);
return java_metrics;
diff --git a/src/native/iot_device_sdk_metrics.h b/src/native/iot_device_sdk_metrics.h
index e7c8c1bad..c846e0974 100644
--- a/src/native/iot_device_sdk_metrics.h
+++ b/src/native/iot_device_sdk_metrics.h
@@ -12,7 +12,7 @@
struct aws_mqtt_iot_metrics_java_jni {
struct aws_mqtt_iot_metrics metrics;
struct aws_byte_buf library_name_buf;
- struct aws_byte_buf metadata_storage;
+ struct aws_array_list metadata_bufs;
struct aws_mqtt_metadata_entry *metadata_entries;
size_t metadata_count;
};
From 8e3586ed20946c79bab1ddd17e88095c73470565 Mon Sep 17 00:00:00 2001
From: Rakshil Modi
Date: Thu, 11 Jun 2026 18:37:54 -0700
Subject: [PATCH 13/16] lint
---
src/native/iot_device_sdk_metrics.c | 14 +++++---------
1 file changed, 5 insertions(+), 9 deletions(-)
diff --git a/src/native/iot_device_sdk_metrics.c b/src/native/iot_device_sdk_metrics.c
index bc10f005c..97688959e 100644
--- a/src/native/iot_device_sdk_metrics.c
+++ b/src/native/iot_device_sdk_metrics.c
@@ -20,11 +20,10 @@
#include
static char s_iot_device_sdk_metrics_string[] = "IoTDeviceSDKMetrics";
- struct aws_metadata_buf_holder {
- struct aws_byte_cursor cursor;
- struct aws_byte_buf buffer;
- };
-
+struct aws_metadata_buf_holder {
+ struct aws_byte_cursor cursor;
+ struct aws_byte_buf buffer;
+};
/* Frees all native memory associated with a parsed metrics struct. */
void aws_mqtt_iot_metrics_java_jni_destroy(
@@ -121,10 +120,7 @@ struct aws_mqtt_iot_metrics_java_jni *aws_mqtt_iot_metrics_java_jni_create_from_
/* Init array_list to hold individual byte_bufs (2 per entry: key + value) */
if (aws_array_list_init_dynamic(
- &java_metrics->metadata_bufs,
- allocator,
- (size_t)count * 2,
- sizeof(struct aws_metadata_buf_holder))) {
+ &java_metrics->metadata_bufs, allocator, (size_t)count * 2, sizeof(struct aws_metadata_buf_holder))) {
goto on_error;
}
From 546ea262bd9288012e216cc5426488a07a304eb7 Mon Sep 17 00:00:00 2001
From: Rakshil Modi
Date: Thu, 11 Jun 2026 19:44:14 -0700
Subject: [PATCH 14/16] cleanup
---
.../awssdk/crt/iot/IoTDeviceSDKMetrics.java | 4 ++-
src/native/iot_device_sdk_metrics.c | 29 +++++++++++++------
2 files changed, 23 insertions(+), 10 deletions(-)
diff --git a/src/main/java/software/amazon/awssdk/crt/iot/IoTDeviceSDKMetrics.java b/src/main/java/software/amazon/awssdk/crt/iot/IoTDeviceSDKMetrics.java
index 2525bc798..b35c2b60d 100644
--- a/src/main/java/software/amazon/awssdk/crt/iot/IoTDeviceSDKMetrics.java
+++ b/src/main/java/software/amazon/awssdk/crt/iot/IoTDeviceSDKMetrics.java
@@ -38,6 +38,7 @@ public class IoTDeviceSDKMetrics {
private static final String PROTOCOL_VERSION = "F";
private static final String SOCKET_IMPLEMENTATION = "G";
private static final String HTTP_PROXY_TYPE = "H";
+ @SuppressWarnings("unused")
private static final String CERTIFICATE_SOURCE = "I";
private static final String TLS_CIPHER_PREFERENCE = "J";
private static final String MINIMUM_TLS_VERSION = "K";
@@ -46,7 +47,6 @@ public class IoTDeviceSDKMetrics {
public IoTDeviceSDKMetrics() {
this.libraryName = "IoTDeviceSDK/Java";
- this.metadataEntries = new ArrayList<>();
}
public IoTDeviceSDKMetrics(String libraryName, List metadataEntries) {
@@ -402,6 +402,7 @@ private static String inboundTopicAliasBehaviorValue(TopicAliasingOptions.Inboun
* or {@code null} for system default/unknown
*/
private static String tlsCipherPreferenceValue(TlsCipherPreference pref) {
+ if (pref == null) return null;
switch (pref) {
case TLS_CIPHER_PREF_PQ_TLSv1_0_2021_05: return "A"; // TLS_CIPHER_PREF_PQ_TLSv1_0_2021_05
case TLS_CIPHER_PQ_DEFAULT: return "B"; // TLS_CIPHER_PQ_DEFAULT
@@ -418,6 +419,7 @@ private static String tlsCipherPreferenceValue(TlsCipherPreference pref) {
* {@code "D"} for TLSv1.2, {@code "E"} for TLSv1.3, or {@code null} for system default/unknown
*/
private static String minimumTlsVersionValue(TlsContextOptions.TlsVersions version) {
+ if (version == null) return null;
switch (version) {
case SSLv3: return "A"; // SSLv3
case TLSv1: return "B"; // TLSv1
diff --git a/src/native/iot_device_sdk_metrics.c b/src/native/iot_device_sdk_metrics.c
index 97688959e..48d39af60 100644
--- a/src/native/iot_device_sdk_metrics.c
+++ b/src/native/iot_device_sdk_metrics.c
@@ -127,22 +127,34 @@ struct aws_mqtt_iot_metrics_java_jni *aws_mqtt_iot_metrics_java_jni_create_from_
for (jint i = 0; i < count; i++) {
/* Call List.get(i) to get the IoTMetricsMetadata object */
jobject entry = (*env)->CallObjectMethod(env, metadata_list, boxed_list_properties.list_get_id, i);
- if (entry == NULL || aws_jni_check_and_clear_exception(env)) {
+ if (!entry || aws_jni_check_and_clear_exception(env)) {
AWS_LOGF_ERROR(AWS_LS_MQTT_GENERAL, "IoTDeviceSDKMetrics: failed to get entry at index %d", (int)i);
goto on_error;
}
- /* Read key and value jstrings from the entry */
+ /* Read the key field. Empty string is allowed; null Java field is not. */
jstring key_jstr = (jstring)(*env)->GetObjectField(env, entry, iot_metrics_metadata_properties.key_field_id);
+ if (aws_jni_check_and_clear_exception(env) || !key_jstr) {
+ AWS_LOGF_ERROR(AWS_LS_MQTT_GENERAL, "IoTDeviceSDKMetrics: exception or null key at index %d", (int)i);
+ goto on_error;
+ }
+
+ /* Read the value field. Empty string is allowed; null Java field is not. */
jstring value_jstr =
(jstring)(*env)->GetObjectField(env, entry, iot_metrics_metadata_properties.value_field_id);
+ if (aws_jni_check_and_clear_exception(env) || !value_jstr) {
+ AWS_LOGF_ERROR(AWS_LS_MQTT_GENERAL, "IoTDeviceSDKMetrics: exception or null value at index %d", (int)i);
+ goto on_error;
+ }
- /* Key: acquire JVM bytes → copy into own buffer → release JVM bytes */
+ /* Key: acquire JVM bytes → copy into own buffer → release JVM bytes.
+ * The local copy lets us hold onto the data after the JVM release. */
struct aws_metadata_buf_holder holder_key;
struct aws_byte_cursor tmp_cursor = aws_jni_byte_cursor_from_jstring_acquire(env, key_jstr);
aws_byte_buf_init_copy_from_cursor(&holder_key.buffer, allocator, tmp_cursor);
holder_key.cursor = aws_byte_cursor_from_buf(&holder_key.buffer);
aws_jni_byte_cursor_from_jstring_release(env, key_jstr, tmp_cursor);
+ key_jstr = NULL;
/* Value: same pattern */
struct aws_metadata_buf_holder holder_value;
@@ -150,6 +162,7 @@ struct aws_mqtt_iot_metrics_java_jni *aws_mqtt_iot_metrics_java_jni_create_from_
aws_byte_buf_init_copy_from_cursor(&holder_value.buffer, allocator, tmp_cursor);
holder_value.cursor = aws_byte_cursor_from_buf(&holder_value.buffer);
aws_jni_byte_cursor_from_jstring_release(env, value_jstr, tmp_cursor);
+ value_jstr = NULL;
/* Store holders so destroy can free each buffer later */
aws_array_list_push_back(&java_metrics->metadata_bufs, &holder_key);
@@ -166,15 +179,13 @@ struct aws_mqtt_iot_metrics_java_jni *aws_mqtt_iot_metrics_java_jni_create_from_
AWS_BYTE_CURSOR_PRI(java_metrics->metadata_entries[i].key),
AWS_BYTE_CURSOR_PRI(java_metrics->metadata_entries[i].value));
- java_metrics->metadata_count++;
-
- /* Release JNI local refs for this iteration */
- (*env)->DeleteLocalRef(env, key_jstr);
- (*env)->DeleteLocalRef(env, value_jstr);
+ /* Release JNI local ref for this iteration. Per JNI spec, DeleteLocalRef
+ * with NULL is a no-op, so the jstrings (now NULL after release) are safe. */
(*env)->DeleteLocalRef(env, entry);
}
- /* Point the C metrics struct at our parsed data */
+ /* Point the C metrics struct at our parsed data.*/
+ java_metrics->metadata_count = (size_t)count;
java_metrics->metrics.metadata_entries = java_metrics->metadata_entries;
java_metrics->metrics.metadata_count = java_metrics->metadata_count;
From 75abe66fec0ea14da069162213b47102596fdcb8 Mon Sep 17 00:00:00 2001
From: Rakshil Modi
Date: Thu, 11 Jun 2026 20:03:29 -0700
Subject: [PATCH 15/16] code cleanup
---
.../amazon/awssdk/crt/iot/IoTDeviceSDKMetrics.java | 9 +++++----
src/native/iot_device_sdk_metrics.c | 6 ++++--
2 files changed, 9 insertions(+), 6 deletions(-)
diff --git a/src/main/java/software/amazon/awssdk/crt/iot/IoTDeviceSDKMetrics.java b/src/main/java/software/amazon/awssdk/crt/iot/IoTDeviceSDKMetrics.java
index b35c2b60d..f3281f03f 100644
--- a/src/main/java/software/amazon/awssdk/crt/iot/IoTDeviceSDKMetrics.java
+++ b/src/main/java/software/amazon/awssdk/crt/iot/IoTDeviceSDKMetrics.java
@@ -47,6 +47,7 @@ public class IoTDeviceSDKMetrics {
public IoTDeviceSDKMetrics() {
this.libraryName = "IoTDeviceSDK/Java";
+ this.metadataEntries = new ArrayList<>();
}
public IoTDeviceSDKMetrics(String libraryName, List metadataEntries) {
@@ -333,7 +334,7 @@ private static String retryJitterModeValue(JitterMode mode) {
/**
* Encodes an MQTT5 session behavior enum value.
*
- * @param behavior the {@code ClientSessionBehavior} ordinal
+ * @param behavior the {@code ClientSessionBehavior} enum
* @return {@code "A"} for CLEAN, {@code "B"} for REJOIN_POST_SUCCESS, {@code "C"} for REJOIN_ALWAYS,
* or {@code null} for DEFAULT/unknown
*/
@@ -349,7 +350,7 @@ private static String sessionBehaviorValue(Mqtt5ClientOptions.ClientSessionBehav
/**
* Encodes an MQTT5 offline queue behavior enum value.
*
- * @param behavior the {@code ClientOfflineQueueBehavior} ordinal
+ * @param behavior the {@code ClientOfflineQueueBehavior} enum
* @return {@code "A"} for FAIL_NON_QOS1_PUBLISH_ON_DISCONNECT, {@code "B"} for FAIL_QOS0_PUBLISH_ON_DISCONNECT,
* {@code "C"} for FAIL_ALL_ON_DISCONNECT, or {@code null} for DEFAULT/unknown
*/
@@ -365,7 +366,7 @@ private static String offlineQueueBehaviorValue(Mqtt5ClientOptions.ClientOffline
/**
* Encodes an outbound topic alias behavior enum value.
*
- * @param behavior the {@code OutboundTopicAliasBehaviorType} ordinal
+ * @param behavior the {@code OutboundTopicAliasBehaviorType} enum
* @return {@code "A"} for Manual, {@code "B"} for LRU, {@code "C"} for Disabled,
* or {@code null} for Default/unknown
*/
@@ -381,7 +382,7 @@ private static String outboundTopicAliasBehaviorValue(TopicAliasingOptions.Outbo
/**
* Encodes an inbound topic alias behavior enum value.
*
- * @param behavior the {@code InboundTopicAliasBehaviorType} ordinal
+ * @param behavior the {@code InboundTopicAliasBehaviorType} enum
* @return {@code "A"} for Enabled, {@code "B"} for Disabled, or {@code null} for Default/unknown
*/
private static String inboundTopicAliasBehaviorValue(TopicAliasingOptions.InboundTopicAliasBehaviorType behavior) {
diff --git a/src/native/iot_device_sdk_metrics.c b/src/native/iot_device_sdk_metrics.c
index 48d39af60..87d17109b 100644
--- a/src/native/iot_device_sdk_metrics.c
+++ b/src/native/iot_device_sdk_metrics.c
@@ -129,6 +129,7 @@ struct aws_mqtt_iot_metrics_java_jni *aws_mqtt_iot_metrics_java_jni_create_from_
jobject entry = (*env)->CallObjectMethod(env, metadata_list, boxed_list_properties.list_get_id, i);
if (!entry || aws_jni_check_and_clear_exception(env)) {
AWS_LOGF_ERROR(AWS_LS_MQTT_GENERAL, "IoTDeviceSDKMetrics: failed to get entry at index %d", (int)i);
+ (*env)->DeleteLocalRef(env, entry);
goto on_error;
}
@@ -136,6 +137,8 @@ struct aws_mqtt_iot_metrics_java_jni *aws_mqtt_iot_metrics_java_jni_create_from_
jstring key_jstr = (jstring)(*env)->GetObjectField(env, entry, iot_metrics_metadata_properties.key_field_id);
if (aws_jni_check_and_clear_exception(env) || !key_jstr) {
AWS_LOGF_ERROR(AWS_LS_MQTT_GENERAL, "IoTDeviceSDKMetrics: exception or null key at index %d", (int)i);
+ (*env)->DeleteLocalRef(env, key_jstr);
+ (*env)->DeleteLocalRef(env, entry);
goto on_error;
}
@@ -179,8 +182,7 @@ struct aws_mqtt_iot_metrics_java_jni *aws_mqtt_iot_metrics_java_jni_create_from_
AWS_BYTE_CURSOR_PRI(java_metrics->metadata_entries[i].key),
AWS_BYTE_CURSOR_PRI(java_metrics->metadata_entries[i].value));
- /* Release JNI local ref for this iteration. Per JNI spec, DeleteLocalRef
- * with NULL is a no-op, so the jstrings (now NULL after release) are safe. */
+ /* Release JNI local ref for this iteration. */
(*env)->DeleteLocalRef(env, entry);
}
From d40c0f847cffb6bd7382997f7dc5b7d82048f93d Mon Sep 17 00:00:00 2001
From: Rakshil Modi
Date: Fri, 12 Jun 2026 11:17:16 -0700
Subject: [PATCH 16/16] dereferencing values
---
src/native/iot_device_sdk_metrics.c | 3 +++
1 file changed, 3 insertions(+)
diff --git a/src/native/iot_device_sdk_metrics.c b/src/native/iot_device_sdk_metrics.c
index 87d17109b..7c621b832 100644
--- a/src/native/iot_device_sdk_metrics.c
+++ b/src/native/iot_device_sdk_metrics.c
@@ -147,6 +147,9 @@ struct aws_mqtt_iot_metrics_java_jni *aws_mqtt_iot_metrics_java_jni_create_from_
(jstring)(*env)->GetObjectField(env, entry, iot_metrics_metadata_properties.value_field_id);
if (aws_jni_check_and_clear_exception(env) || !value_jstr) {
AWS_LOGF_ERROR(AWS_LS_MQTT_GENERAL, "IoTDeviceSDKMetrics: exception or null value at index %d", (int)i);
+ (*env)->DeleteLocalRef(env, key_jstr);
+ (*env)->DeleteLocalRef(env, value_jstr);
+ (*env)->DeleteLocalRef(env, entry);
goto on_error;
}