Skip to content

Latest commit

 

History

History
487 lines (397 loc) · 24 KB

MQTT5-UserGuide.md

File metadata and controls

487 lines (397 loc) · 24 KB

MQTT 5

Table of contents

Complete API documentation for the CRT's MQTT5 client can be found for

What's Different? (relative to the MQTT311 implementation)

SDK MQTT5 support comes from a separate client implementation. By doing so, we took the opportunity to incorporate feedback about the MQTT311 implementation that we could not apply without making breaking changes. If you're used to the MQTT311 implementation's API contract, there are a number of differences.

Major changes

  • The MQTT5 client does not treat initial connection failures differently from later connection failures. With the MQTT311 implementation, a failure during initial connect would halt reconnects.
  • The set of client lifecycle events is expanded and contains more detailed information whenever possible. All protocol data is exposed to the user.
  • MQTT operations are completed with the full associated ACK packet when possible.
  • New, optional behavioral configuration:
    • IoT Core specific validation - will validate and fail operations that break IoT Core specific restrictions
    • IoT Core specific flow control - will apply flow control to honor IoT Core specific per-connection limits and quotas
    • Flexible queue control - provides a number of options to control what happens to incomplete operations on a disconnection event
  • A new API has been added to query the internal state of the client's operation queue. This API allows the user to make more informed flow control decisions before submitting operations to the client. Simple, let-the-client-handle-it backpressure mechanisms are planned for future development.
  • Data can no longer back up on the socket. At most one frame of data is ever pending-write on the socket.
  • The MQTT5 client has a single message-received callback. Per-subscription callbacks are not supported.

Minor changes

  • Public API terminology has changed. You start or stop the MQTT5 client. This removes the semantic confusion with connect/disconnect as client-level controls vs. internal recurrent networking events.
  • With the MQTT311 implementation, there were two separate objects, a client and a connection. With MQTT5, there is only the client.

Not supported

Not all parts of the MQTT5 spec are supported by the implementation. We currently do not support:

  • AUTH packets and the authentication fields in the CONNECT packet
  • QoS 2

Client connection management

Once created, an MQTT5 client's configuration is immutable. Invoking start() on the client will put it into an active state where it recurrently establishes a connection to the configured remote endpoint. Reconnecting continues until you invoke stop().

    // Create the client
    let config : Mqtt5ClientConfig = {
        ...
    };

    let client : Mqtt5Client = new Mqtt5Client(config);

    // Attach event listeners
    client.on("messageReceived",(eventData: MessageReceivedEvent) : void => {
        console.log("Message Received event: " + JSON.stringify(eventData.message));
    });
    // etc...

    // Use the client
    client.start();
    ...

Invoking stop() breaks the current connection (if any) and moves the client into an idle state. When finished with an MQTT5 client, you must call close() on it or any associated native resources may leak.

    // Shutdown and clean up
    const stopped = once(client, Mqtt5Client.STOPPED);
    client.stop();
    await stopped;

    // release any native resources associated with the client
    client.close();

Client Events

The MQTT5 client emits a set of events related to state and network status changes. Event listeners may be attached for each event you wish to react to. Each event emits a single collection of event data (which may be empty, based on the event).

AttemptingConnect

Emitted when the client begins to open a connection to the configured endpoint. The AttemptingConnectEvent contains no further data.

ConnectionSuccess

Emitted when a connection attempt succeeds based on receipt of an affirmative CONNACK packet from the MQTT broker. A ConnectionSuccessEvent includes the MQTT broker's CONNACK packet, as well as a NegotiatedSettings structure which contains the final values for all variable MQTT session settings (based on protocol defaults, client configuration, and server response).

ConnectionFailure

Emitted when a connection attempt fails at any point between DNS resolution and CONNACK receipt. In addition to an error code, additional data may be present in the ConnectionFailureEvent based on the context. For example, if the remote endpoint sent a CONNACK with a failing reason code, the CONNACK packet will be included in the event data.

Disconnect

Emitted when the client's network connection is shut down, either by a local action, event, or a remote close or reset. Only emitted after a ConnectionSuccess event: a network connection that is shut down during the connecting process manifests as a ConnectionFailure event. A DisconnectEvent will always include an error code. If the event is due to the receipt of a server-sent DISCONNECT packet, the packet will also be included with the event data.

Stopped

Emitted once the client has shutdown any associated network connection and entered an idle state where it will no longer attempt to reconnect. A StoppedEvent contains no additional data. Only emitted after an invocation of stop() on the client. A stopped client may always be started again.

MessageReceived

Emitted for each PUBLISH packet received by the client. MessageReceivedEvent data contains the complete PublishPacket received.

Connecting To AWS IoT Core

We strongly recommend using the AwsIotMqtt5ClientConfigBuilder class to configure MQTT5 clients when connecting to AWS IoT Core. The builder simplifies configuration for all authentication methods supported by AWS IoT Core. This section shows samples for all authentication possibilities. There are slight differences in the APIs for the browser implementation vs. the NodeJS implementation, so each environment is given separate samples.

NodeJS

The MQTT5 implementation for NodeJS supports both (direct) MQTT-over-TCP and MQTT-over-websockets. All connections are protected by TLS.

Direct MQTT with X509-based mutual TLS

For X509 based mutual TLS, you can create a client where the certificate and private key are configured by path:

    let builder = AwsIotMqtt5ClientConfigBuilder.newDirectMqttBuilderWithMtlsFromPath(
        <account-specific endpoint>,
        <path-to-X509-certificate-pem-file>,
        <path-to-private-key-pem-file>
    );

    // other builder configuration
    // ...
    let client : Mqtt5Client = new Mqtt5Client(builder.build()));

You can also create a client where the certificate and private key are in memory:

    let cert = fs.readFileSync(<path to certificate pem file>,'utf8');
    let key = fs.readFileSync(<path to private key pem file>,'utf8');
    let builder = AwsIotMqtt5ClientConfigBuilder.newDirectMqttBuilderWithMtlsFromMemory(
        <account-specific endpoint>,
        cert,
        key
    );

    // other builder configuration
    // ...
    let client : Mqtt5Client = new Mqtt5Client(builder.build());

MQTT over Websockets with Sigv4 authentication

Sigv4-based authentication requires a credentials provider capable of sourcing valid AWS credentials. Sourced credentials will sign the websocket upgrade request made by the client while connecting. The default credentials provider chain supported by the SDK is capable of resolving credentials in a variety of environments according to a chain of priorities:

    Environment -> Profile (local file system) -> STS Web Identity -> IMDS (ec2) or ECS

If the default credentials provider chain and built-in AWS region extraction logic are sufficient, you do not need to specify any additional configuration:

    let builder = AwsIotMqtt5ClientConfigBuilder.newWebsocketMqttBuilderWithSigv4Auth(
        <account-specific endpoint>
    );
    // other builder configuration
    // ...
    let client : Mqtt5Client = new Mqtt5Client(builder.build());

Alternatively, if you're connecting to a special region for which standard pattern matching does not work, or if you need a specific credentials provider, you can specify advanced websocket configuration options.

    // sourcing credentials from the Cognito service in this example
    let cognitoConfig: CognitoCredentialsProviderConfig = {
        endpoint: "<cognito endpoint to query credentials from>",
        identity: "<cognito identity to query credentials relative to>"
    };

    let overrideProvider: CredentialsProvider = AwsCredentialsProvider.newCognito(cognitoConfig);

    let wsConfig : WebsocketSigv4Config = {
        credentialsProvider: overrideProvider,
        region: "<special case region>"
    };

    let builder = AwsIotMqtt5ClientConfigBuilder.newWebsocketMqttBuilderWithSigv4Auth(
        "<account-specific endpoint>",
        wsConfig
    );
    // other builder configuration
    // ...
    let client : Mqtt5Client = new Mqtt5Client(builder.build());

Direct MQTT with Custom Authentication

AWS IoT Core Custom Authentication allows you to use a lambda to gate access to IoT Core resources. For this authentication method, you must supply an additional configuration structure containing fields relevant to AWS IoT Core Custom Authentication.

If your custom authenticator does not use signing, you don't specify anything related to the token signature:

    let customAuthConfig : MqttConnectCustomAuthConfig = {
        authorizerName: "<Name of your custom authorizer>",
        username: "<Value of the username field that should be passed to the authorizer's lambda>",
        password: <Binary data value of the password field to be passed to the authorizer lambda>
    };
    let builder = AwsIotMqtt5ClientConfigBuilder.newDirectMqttBuilderWithCustomAuth(
        "<account-specific endpoint>",
        customAuthConfig
    );
    let client : Mqtt5Client = new mqtt5.Mqtt5Client(builder.build());

If your custom authorizer uses signing, you must specify the three signed token properties as well. The token signature must be the URI-encoding of the base64 encoding of the digital signature of the token value via the private key associated with the public key that was registered with the custom authorizer. It is your responsibility to URI-encode the token signature.

    let customAuthConfig : MqttConnectCustomAuthConfig = {
        authorizerName: "<Name of your custom authorizer>",
        username: "<Value of the username field that should be passed to the authorizer's lambda>",
        password: <Binary data value of the password field to be passed to the authorizer lambda>,
        tokenKeyName: "<Name of the username query param that will contain the token value>",
        tokenValue: "<Value of the username query param that holds the token value that has been signed>",
        tokenSignature: "<URI-encoded base64-encoded digital signature of tokenValue>"
    };
    let builder = AwsIotMqtt5ClientConfigBuilder.newDirectMqttBuilderWithCustomAuth(
        "<account-specific endpoint>",
        customAuthConfig
    );
    let client : Mqtt5Client = new mqtt5.Mqtt5Client(builder.build());

In both cases, the builder will construct a final CONNECT packet username field value for you based on the values configured. Do not add the token-signing fields to the value of the username that you assign within the custom authentication config structure. Similarly, do not add any custom authentication related values to the username in the CONNECT configuration optionally attached to the client configuration. The builder will do everything for you.

Direct MQTT with PKCS11 Method

A MQTT5 direct connection can be made using a PKCS11 device rather than using a PEM encoded private key, the private key for mutual TLS is stored on a PKCS#11 compatible smart card or Hardware Security Module (HSM). To create a MQTT5 builder configured for this connection, see the following code:

    let pkcs11Options : Pkcs11Options = {
        pkcs11_lib: "<path to PKCS11 library>",
        user_pin: "<Optional pin for PKCS11 device>",
        slot_id: "<Optional slot ID containing PKCS11 token>",
        token_label: "<Optional label of the PKCS11 token>",
        private_key_object_label: "<Optional label of the private key object on the PKCS#11 token>",
        cert_file_path: "<Path to certificate file. Not necessary if cert_file_contents is used>",
        cert_file_contents: "<Contents of certificate file. Not necessary if cert_file_path is used>"
    };
    let builder = AwsIotMqtt5ClientConfigBuilder.newDirectMqttBuilderWithMtlsFromPkcs11(
        "<account-specific endpoint>",
        pkcs11Options
    );
    let client : Mqtt5Client = new mqtt5.Mqtt5Client(builder.build());

Note: Currently, TLS integration with PKCS#11 is only available on Unix devices.

Direct MQTT with PKCS12 Method

A MQTT5 direct connection can be made using a PKCS12 file rather than using a PEM encoded private key. To create a MQTT5 builder configured for this connection, see the following code:

    let builder = AwsIotMqtt5ClientConfigBuilder.newDirectMqttBuilderWithMtlsFromPkcs12(
        "<account-specific endpoint>",
        "<PKCS12 file>",
        "<PKCS12 password>"
    );
    let client : Mqtt5Client = new mqtt5.Mqtt5Client(builder.build());

Note: Currently, TLS integration with PKCS#12 is only available on MacOS devices.

Direct MQTT with Windows Certificate Store Method

A MQTT5 direct connection can be made with mutual TLS with the certificate and private key in the Windows certificate store, rather than simply being files on disk. To create a MQTT5 builder configured for this connection, see the following code:

    // Certificate store path below is an example.
    let certificateStorePath : string = "CurrentUser\\MY\\A11F8A9B5DF5B98BA3508FBCA575D09570E0D2C6";
    let builder = AwsIotMqtt5ClientConfigBuilder.newDirectMqttBuilderWithMtlsFromWindowsCertStorePath(
        "<account-specific endpoint>",
        certificateStorePath
    );
    let client : Mqtt5Client = new mqtt5.Mqtt5Client(builder.build());

Note: Windows Certificate Store connection support is only available on Windows devices.

HTTP Proxy

No matter what your connection transport or authentication method is, you may connect through an HTTP proxy by applying proxy configuration to the builder:

    let builder = AwsIoTMqtt5ClientConfigBuilder.<authentication method>(...);
    let proxyOptions : HttpProxyOptions = new HttpProxyOptions("<proxy host>", <proxy port>);
    builder.withHttpProxyOptions(proxyOptions);

    let client : Mqtt5Client = new Mqtt5Client(builder.build());

SDK Proxy support also includes support for basic authentication and TLS-to-proxy. SDK proxy support does not include any additional proxy authentication methods (kerberos, NTLM, etc...) nor does it include non-HTTP proxies (SOCKS5, for example).

Browser

The MQTT5 implementation for the browser supports MQTT-over-websockets using either Sigv4 authentication or AWS IoT Core Custom Authentication. All connections are protected by TLS.

MQTT over Websockets with Sigv4 authentication

Within the SDK, there is very limited support for browser-based credentials providers. By default, only static credential providers are supported:

    let staticProvider: StaticCredentialProvider = new StaticCredentialProvider({
        aws_access_id: "<AWS Credentials Access Key>",
        aws_secret_key: "<AWS Credentials Secret Access Key>",
        aws_region: "<AWS Region>"
    });
    let wsConfig: WebsocketSigv4Config = {
        credentialsProvider: staticProvider
    };

    let builder = aws_iot_mqtt5.AwsIotMqtt5ClientConfigBuilder.newWebsocketMqttBuilderWithSigv4Auth(
        "<account-specific endpoint>",
        wsConfig
    );
    let client : Mqtt5Client = new Mqtt5Client(builder.build());

However, you can also create adapters to credentials providers from third party libraries (like the AWS SDK for JS v3). Coordinating the refresh of session-based credentials is your responsibility.

MQTT over Websockets with Custom Authentication

In the browser, custom authentication works exactly the same as NodeJS. The only difference is that the browser makes the connection via MQTT-over-websockets, while in NodeJS, we always make the connection direct-over-TCP.

If your custom authenticator does not use signing, you don't specify anything related to the token signature:

    let customAuthConfig : MqttConnectCustomAuthConfig = {
        authorizerName: "<Name of your custom authorizer>",
        username: "<Value of the username field that should be passed to the authorizer's lambda>",
        password: <Binary data value of the password field to be passed to the authorizer lambda>
    };
    let builder = AwsIotMqtt5ClientConfigBuilder.newDirectMqttBuilderWithCustomAuth(
        "<account-specific endpoint>",
        customAuthConfig
    );
    let client : Mqtt5Client = new mqtt5.Mqtt5Client(builder.build());

If your custom authorizer uses signing, you must specify the three signed token properties as well. The token signature should be the base64 encoding of the digital signature of the token value via the private key associated with the public key that was registered with the custom authorizer:

    let customAuthConfig : MqttConnectCustomAuthConfig = {
        authorizerName: "<Name of your custom authorizer>",
        username: "<Value of the username field that should be passed to the authorizer's lambda>",
        password: <Binary data value of the password field to be passed to the authorizer lambda>,
        tokenKeyName: "<Name of the username query param that will contain the token value>",
        tokenValue: "<Value of the username query param that holds the token value that has been signed>",
        tokenSignature: "<base64-encoded digital signature of tokenValue>"
    };
    let builder = AwsIotMqtt5ClientConfigBuilder.newDirectMqttBuilderWithCustomAuth(
        "<account-specific endpoint>",
        customAuthConfig
    );
    let client : Mqtt5Client = new mqtt5.Mqtt5Client(builder.build());

In both cases, the builder will construct a final CONNECT packet username value for you based on the values configured. Do not add the token-signing fields to the value of the username that you assign within the custom authentication config structure. Similarly, do not add any custom authentication related values to the username in the CONNECT configuration optionally attached to the client configuration. The builder will do everything for you.

HTTP Proxy

Similar to NodeJS, you can specify http proxy options to enable connections that pass through an HTTP proxy. Unlike NodeJS, these options are in a form expected by the underlying third-party transport library.

    import url from "url";
    let builder = AwsIoTMqtt5ClientConfigBuilder.<authentication method>(...);
    let urlOptions: url.UrlWithStringQuery = url.parse(`<proxy url including domain and port>`);
    let agent: HttpsProxyAgent = new HttpsProxyAgent(urlOptions);
    let wsOptions : any = {
        agent: agent
    }
    builder.withWebsocketTransportOptions(wsOptions);

    let client : Mqtt5Client = new Mqtt5Client(builder.build());

Client Operations

There are four basic MQTT operations you can perform with the MQTT5 client.

Subscribe

The Subscribe operation takes a description of the SUBSCRIBE packet you wish to send and returns a promise that resolves successfully with the corresponding SUBACK returned by the broker; the promise is rejected with an error if anything goes wrong before the SUBACK is received. You should always check the reason codes of a SUBACK completion to determine if the subscribe operation actually succeeded.

    const suback: SubackPacket = await client.subscribe({
        subscriptions: [
            { qos: mqtt5_packet.QoS.AtLeastOnce, topicFilter: "hello/world/qos1" }
        ]
    });

A fully-accurate SUBACK is only available in NodeJS. The third-party browser MQTT library does not expose the full SUBACK packet.

Unsubscribe

The Unsubscribe operation takes a description of the UNSUBSCRIBE packet you wish to send and returns a promise that resolves successfully with the corresponding UNSUBACK returned by the broker; the promise is rejected with an error if anything goes wrong before the UNSUBACK is received. You should always check the reason codes of an UNSUBACK completion to determine if the unsubscribe operation actually succeeded.

    let unsuback: UnsubackPacket = await client.unsubscribe({
        topicFilters: [
            "hello/world/qos1"
        ]
    });

An accurate UNSUBACK is only available in NodeJS. The third-party browser MQTT library does not expose the UNSUBACK packet.

Publish

The Publish operation takes a description of the PUBLISH packet you wish to send and returns a promise of polymorphic value.

  • If the PUBLISH was a QoS 0 publish, then the promise has a unit (void) value and is completed as soon as the packet has been written to the socket.
  • If the PUBLISH was a QoS 1 publish, then the promise has a PUBACK packet value and is completed as soon as the PUBACK is received from the broker.

If the operation fails for any reason before these respective completion events, the promise is rejected with a descriptive error. You should always check the reason code of a PUBACK completion to determine if a QoS 1 publish operation actually succeeded.

    const publishResult = await client.publish({
        qos: mqtt5_packet.QoS.AtLeastOnce,
        topicName: "hello/world/qos1",
        payload: "This is the payload of a QoS 1 publish"
    });

    // on successful broker response, publishResult will be a PubackPacket

Disconnect

The stop() API supports a DISCONNECT packet as an optional parameter. If supplied, the DISCONNECT packet will be sent to the server prior to closing the socket. The DISCONNECT packet will not be sent if there is no network connection currently active.

There is no promise returned by a call to stop() but you may listen for the 'stopped' event on the client.

let disconnect: DisconnectPacket = {
    reasonCode: DisconnectReasonCode.DisconnectWithWillMessage,
    sessionExpiryIntervalSeconds: 3600
};
client.stop(disconnect);