Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import software.amazon.awssdk.crt.CRT;
import software.amazon.awssdk.crt.auth.credentials.CredentialsProvider;
import software.amazon.awssdk.crt.auth.credentials.DefaultChainCredentialsProvider;
import software.amazon.awssdk.crt.auth.signing.AwsSigningConfig;
Expand All @@ -35,9 +34,9 @@
import software.amazon.awssdk.crt.mqtt5.packets.ConnectPacket.ConnectPacketBuilder;
import software.amazon.awssdk.crt.mqtt5.packets.PublishPacket;
import software.amazon.awssdk.crt.mqtt5.TopicAliasingOptions;
import software.amazon.awssdk.crt.utils.PackageInfo;
import software.amazon.awssdk.crt.mqtt5.packets.UserProperty;


/**
* Builders for making MQTT5 clients with different connection methods for AWS IoT Core.
*/
Expand All @@ -50,6 +49,8 @@ public class AwsIotMqtt5ClientBuilder extends software.amazon.awssdk.crt.CrtReso
private ConnectPacketBuilder configConnect;
private TlsContextOptions configTls;
private MqttConnectCustomAuthConfig configCustomAuth;
/** Whether to disable SDK metrics collection. Defaults to {@code false} (metrics enabled). */
private boolean disableMetrics = false;

private AwsIotMqtt5ClientBuilder(String hostName, Long port, TlsContextOptions tlsContext) {
config = new Mqtt5ClientOptionsBuilder(hostName, port);
Expand Down Expand Up @@ -834,7 +835,19 @@ public AwsIotMqtt5ClientBuilder withUserProperties(List<UserProperty> userProper
{
this.configConnect.withUserProperties(userProperties);
return this;
}
}

/**
* Disables IoT SDK metrics in the CONNECT packet username field.
* Defaults to false (metrics enabled).
*
* @param disableMetrics true to disable metrics collection. By default {@code false} [metrics enabled].
* @return The AwsIotMqtt5ClientBuilder after setting the disable metrics flag.
*/
public AwsIotMqtt5ClientBuilder withDisableMetrics(boolean disableMetrics) {
this.disableMetrics = disableMetrics;
return this;
}

/**
* Constructs an MQTT5 client object configured with the options set.
Expand Down Expand Up @@ -866,6 +879,13 @@ public Mqtt5Client build() {

this.config.withConnectOptions(this.configConnect.build());

this.config.withDisableMetrics(this.disableMetrics);
if (this.disableMetrics) {
this.config.withMetrics(null);
} else {
this.config.withMetrics(IoTSdkMetrics.buildSdkMetrics());
}

Mqtt5Client returnClient = new Mqtt5Client(this.config.build());

// Keep a reference to the TLS configuration so any possible Websockets-related CrtResources are kept alive
Expand Down Expand Up @@ -1049,7 +1069,7 @@ private String formUsernameFromParam(List<String> paramList) throws Exception {

/**
* Builds the final value for the CONNECT packet's username property based on AWS IoT custom auth configuration
* and SDK metrics properties.
*
*
* @param config - The intended AWS IoT custom auth client configuration (optional - leave null if not used)
* @return The final username string
Expand Down Expand Up @@ -1118,13 +1138,6 @@ private String buildMqtt5FinalUsername(MqttConnectCustomAuthConfig config) throw
}
}

if(CRT.getOSIdentifier() == "android"){
addToUsernameParam(paramList, "SDK", "AndroidV2");
} else {
addToUsernameParam(paramList, "SDK", "JavaV2");
}
addToUsernameParam(paramList, "Version", new PackageInfo().version.toString());

return formUsernameFromParam(paramList);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,11 @@
*/
package software.amazon.awssdk.iot;

import software.amazon.awssdk.crt.utils.PackageInfo;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.function.Consumer;

import software.amazon.awssdk.crt.CRT;
import software.amazon.awssdk.crt.CrtResource;
import software.amazon.awssdk.crt.CrtRuntimeException;
import software.amazon.awssdk.crt.Log;
Expand Down Expand Up @@ -58,6 +55,8 @@ public final class AwsIotMqttConnectionBuilder extends CrtResource {
private ClientBootstrap bootstrap;

private boolean resetLazilyCreatedResources = true;
/** Whether to disable SDK metrics collection. Defaults to {@code false} (metrics enabled). */
private boolean disableMetrics = false;
// Used to detect if we need to set the ALPN list for custom authorizer
private boolean isUsingCustomAuthorizer = false;

Expand Down Expand Up @@ -696,6 +695,18 @@ public AwsIotMqtt5ClientBuilder toAwsIotMqtt5ClientBuilder() throws Exception {
return AwsIotMqtt5ClientBuilder.newMqttBuilderFromMqtt311ConnectionConfig(config, tlsOptions);
}

/**
* Disables IoT SDK metrics in the CONNECT packet username field.
* Defaults to false (metrics enabled).
*
* @param disableMetrics true to disable metrics collection
* @return The AwsIotMqttConnectionBuilder after setting the disable metrics flag.
*/
public AwsIotMqttConnectionBuilder withDisableMetrics(boolean disableMetrics) {
this.disableMetrics = disableMetrics;
return this;
}

/**
* Builds a new mqtt connection from the configuration stored in the builder. Because some objects are created
* lazily, certain properties should not be modified after this is first invoked (tls options, bootstrap).
Expand Down Expand Up @@ -759,29 +770,15 @@ public MqttClientConnection build() {

resetLazilyCreatedResources = false;


// Connection create
try (MqttConnectionConfig connectionConfig = config.clone()) {

// Whether or not a username has been added, append our metrics tokens
String usernameOrEmpty = "";
if (connectionConfig.getUsername() != null) {
usernameOrEmpty = connectionConfig.getUsername();
}
String queryStringConcatenation = "?";
if (usernameOrEmpty.contains("?")) {
queryStringConcatenation = "&";
}

if(CRT.getOSIdentifier() == "android"){
connectionConfig.setUsername(String.format("%s%sSDK=AndroidV2&Version=%s",
usernameOrEmpty,
queryStringConcatenation,
new PackageInfo().version.toString()));
connectionConfig.setDisableMetrics(this.disableMetrics);
if (this.disableMetrics) {
connectionConfig.setMetrics(null);
} else {
connectionConfig.setUsername(String.format("%s%sSDK=JavaV2&Version=%s",
usernameOrEmpty,
queryStringConcatenation,
new PackageInfo().version.toString()));
connectionConfig.setMetrics(IoTSdkMetrics.buildSdkMetrics());
}

if (connectionConfig.getUseWebsockets() && connectionConfig.getWebsocketHandshakeTransform() == null) {
Expand Down
87 changes: 87 additions & 0 deletions sdk/src/main/java/software/amazon/awssdk/iot/IoTSdkMetrics.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/**
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0.
*/

package software.amazon.awssdk.iot;

import java.util.ArrayList;
import java.util.List;

import software.amazon.awssdk.crt.CRT;
import software.amazon.awssdk.crt.iot.IoTDeviceSDKMetrics;
import software.amazon.awssdk.crt.iot.IoTMetricsMetadata;

/**
* Provides SDK-level metadata (version info) to the CRT layer.
* The CRT handles all feature detection (certificate source, TLS settings, etc.)
* and embeds the combined metrics in the MQTT CONNECT packet username field.
*/
class IoTSdkMetrics {

private static final String SDK_LIBRARY_NAME_JAVA = "IoTDeviceSDK/Java";
private static final String SDK_LIBRARY_NAME_ANDROID = "IoTDeviceSDK/Android";

private static String getLibraryName() {
if ("android".equals(CRT.getOSIdentifier())) {
return SDK_LIBRARY_NAME_ANDROID;
}
return SDK_LIBRARY_NAME_JAVA;
}

/**
* The current version of the IoT SDK metrics format.
* This must match the version expected by the CRT layer.
*/
private static final String IOT_SDK_METRICS_VERSION = "1";

/**
* Returns the installed SDK version string.
*
* <p>Attempts to read the specification version from the package manifest first,
* falling back to the implementation version. Returns {@code "dev"} if the package
* metadata is unavailable (e.g. when running from a source checkout without installing).
*
* @return a version string such as {@code "1.32.0"} or {@code "dev"}
*/
private static String getSdkVersion() {
try {
Package pkg = IoTSdkMetrics.class.getPackage();
String version = pkg.getSpecificationVersion();
if (version == null) {
version = pkg.getImplementationVersion();
}
if (version == null) {
version = "dev";
}
return version;
} catch (Exception e) {
return "dev";
}
}

/**
* Builds the SDK-level {@link IoTDeviceSDKMetrics} payload that is passed down to the CRT layer.
*
* <p>The returned object carries SDK identity and the metrics format version via two metadata entries:
* <ul>
* <li>{@code IoTSDKVersion} — the installed SDK package version, used to identify the
* SDK release on the server side.</li>
* <li>{@code IoTSDKMetricsVersion} — the metrics format version this SDK supports.</li>
* </ul>
*
* <p>The CRT layer is responsible for detecting connection-level features (protocol version,
* certificate source, TLS settings, proxy type, etc.) and appending them to the metadata
* before embedding the result in the MQTT CONNECT packet username field.
*
* @return a populated {@link IoTDeviceSDKMetrics} object ready to attach to an
* MQTT5 client or MQTT3 connection configuration
*/
static IoTDeviceSDKMetrics buildSdkMetrics() {
List<IoTMetricsMetadata> metadata = new ArrayList<>();
metadata.add(new IoTMetricsMetadata("IoTSDKVersion", getSdkVersion()));
metadata.add(new IoTMetricsMetadata("IoTSDKMetricsVersion", IOT_SDK_METRICS_VERSION));
return new IoTDeviceSDKMetrics(getLibraryName(), metadata);
}

}
50 changes: 50 additions & 0 deletions sdk/tests/software/amazon/awssdk/iot/IoTSdkMetricsTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/**
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0.
*/
package software.amazon.awssdk.iot;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;

import org.junit.jupiter.api.Test;

import software.amazon.awssdk.crt.iot.IoTDeviceSDKMetrics;
import software.amazon.awssdk.crt.iot.IoTMetricsMetadata;

import java.util.List;

public class IoTSdkMetricsTest {

private String findMetadataValue(List<IoTMetricsMetadata> entries, String key) {
for (IoTMetricsMetadata entry : entries) {
if (key.equals(entry.getKey())) {
return entry.getValue();
}
}
return null;
}

@Test
public void testBuildSdkMetricsReturnsValidObject() {
IoTDeviceSDKMetrics metrics = IoTSdkMetrics.buildSdkMetrics();
assertNotNull(metrics);
}

@Test
public void testLibraryName() {
IoTDeviceSDKMetrics metrics = IoTSdkMetrics.buildSdkMetrics();
assertEquals("IoTDeviceSDK/Java", metrics.getLibraryName());
}

@Test
public void testSdkVersionPresent() {
IoTDeviceSDKMetrics metrics = IoTSdkMetrics.buildSdkMetrics();
String version = findMetadataValue(metrics.getMetadataEntries(), "IoTSDKVersion");
assertNotNull(version);
assertFalse(version.isEmpty());
}
}
Loading