MQTT NIO

repository·main·Indexed 42 days ago

https://github.com/swift-server-community/mqtt-nio

A Swift NIO-based implementation of an MQTT client supporting versions 3.1.1 and 5.0. Designed for high-performance IoT messaging, it supports various transport layers including TLS, WebSockets, POSIX and Unix domain sockets, and Apple's Network framework via NIOTransportServices. It provides scoped connection management via MQTTConnection.withConnection and session state persistence through MQTTSession.

Tokens
7.5K
Snippets
22
Records
30
Agent score
47%

What's inside MQTT NIO

  1. Overview of MQTTNIO features

    main

    MQTTNIO is a Swift NIO-based implementation of an MQTT client supporting both MQTT v3.1.1 and v5.0.

    Key capabilities include:

    • Transport Support: Unencrypted, TLS-encrypted, WebSocket, POSIX, and Unix domain sockets.
    • Platform Compatibility: Supports Apple's Network framework via NIOTransportServices (required for iOS).
    • Configuration: Integrates with Swift Configuration for MQTTConnectionConfiguration.
    • Logging: Supports task-local loggers via SwiftLog.
  2. MQTT NIO supported features and protocols

    main

    MQTT NIO is a Swift NIO-based MQTT client supporting the following features:

    • Protocols: MQTT v3.1.1 and v5.0.
    • Transport:
      • Unencrypted and encrypted (via TLS) connections.
      • WebSocket connections.
      • POSIX and Unix domain sockets.
      • Apple's Network framework via NIOTransportServices (required for iOS).
    • Configuration: Supports Swift Configuration to create MQTTConnectionConfiguration from configuration files.
    • Logging: Uses Task-local loggers from SwiftLog.
  3. Configure context propagation for distributed tracing

    main

    Context propagation defines how the trace context is injected into and extracted from PUBLISH packets.

    • Default Behavior: MQTTNIO uses UserPropertiesPropagator, which utilizes MQTT v5 User Properties.
    • Requirement: To use the default context propagation, you must connect as an MQTT v5 client.
    • Customization: You can provide a custom implementation of the MQTTContextPropagator protocol via the contextPropagator property in MQTTTracingConfiguration to support different propagation methods used by specific MQTT brokers.
  4. Use NIO Transport Services (NTS) on macOS and iOS

    main

    On Apple platforms, you can use NIOTransportServices to leverage Network.framework. The selection between NIOSSL and NIOTransportServices is determined by the combination of the eventLoopGroup and the TLSConfiguration provided:

    • To use NIOSSL: Provide a MultiThreadedEventLoopGroup and use .niossl(tlsConfiguration) in the TLS configuration.
    • To use NIOTransportServices: Provide a NIOTSEventLoopGroup and use .ts(configuration) in the TLS configuration.

    Important: If you are running on iOS, you should always choose NIOTransportServices. Providing a MultiThreadedEventLoopGroup with a .ts TLS configuration will result in an error.

  5. How MQTT sessions work in MQTTNIO

    main

    MQTT sessions allow a client to maintain state (such as subscriptions and pending QoS 1/2 messages) across multiple connections to a broker. In MQTTNIO, sessions are managed via the MQTTSession type.

    To use a session, create an MQTTSession with a unique clientID and pass it to the MQTTConnection.withConnection method. When connecting, the closure provided to withConnection receives a Bool parameter (sessionPresent) which indicates whether the server already has an existing session for that specific client ID.

    let session = MQTTSession(clientID: "My Client")
    
    try await MQTTConnection.withConnection(
        address: .hostname("localhost"),
        session: session
    ) { connection, sessionPresent in
        if sessionPresent {
            print("The server has an existing session for this client ID")
        } else {
            print("The server does not have an existing session for this client ID")
        }
    }
  6. Connect to an MQTT v5 broker

    main

    To establish a connection using the MQTT v5 protocol, configure the MQTTConnectionConfiguration with the .v5_0 version configuration. You can optionally provide MQTTProperties for the CONNECT and DISCONNECT messages, and specify an MQTTAuthenticator for v5 authentication workflows within the .v5_0 case.

    try await MQTTConnection.withConnection(
        address: .hostname(host),
        configuration: .init(versionConfiguration: .v5_0()),
        identifier: "My V5 Client"
    ) { connection in
        // You are now connected to the MQTT v5 broker
    }
  7. Establish an MQTT connection using `withConnection`

    main

    In MQTT NIO, connections are managed using the withConnection methods on MQTTConnection. These methods follow a scoped pattern: you provide a closure containing your MQTT operations, and the connection remains active only for the duration of that closure.

    Key behaviors:

    • CONNECT packets are sent automatically before the closure executes.
    • DISCONNECT packets are sent automatically after the closure returns or if an error is thrown.
    • The connection is closed automatically when the closure returns.
    • You can wait for the connection to be closed by the server using MQTTConnection/waitOnClose().

    To connect with a clean session (where you cannot resume a previous session), use the standard withConnection variant. To resume a session, use the variant that accepts a session parameter, which also provides a Bool in the closure indicating if a previous session was successfully resumed.

    try await MQTTConnection.withConnection(
        address: .hostname("mqtt.eclipse.org"),
        identifier: "My Client"
    ) { connection in
        // You are now connected to the MQTT broker
        // The connection will be active only inside this closure
    }
  8. Maintain subscriptions across connections using MQTTSession

    main

    Instead of subscribing via a specific connection, you can use MQTTSession.subscribe(to:subscribeProperties:unsubscribeProperties:process:). This keeps the subscription active on the broker as long as the session has not expired.

    When a connection is established using that session, it will automatically receive messages for those subscriptions. Even if a connection is closed, the subscription remains active and will resume receiving messages as soon as a new connection with the same session is opened.

    let session = MQTTSession(clientID: "My Client")
    
    await withThrowingTaskGroup { group in
        group.addTask {
            try await session.subscribe(to: [.init(topicFilter: "my-topics", qos: .atLeastOnce)]) { subscription in
                for try await message in subscription {
                    var buffer = message.payload
                    let string = buffer.readString(length: buffer.readableBytes)
                    print(string)
                    // The subscription remains active even if the connection task below finishes
                }
            }
        }
    
        group.addTask {
            try await MQTTConnection.withConnection(
                address: .hostname("localhost"),
                session: session
            ) { connection, _ in
                // The subscription will receive messages as normal while this connection is active.
            }
        }
    }
  9. Connect to AWS IoT using AWS Signature V4 via WebSockets

    main

    To connect to an AWS IoT broker using WebSocket authentication, you must sign your initial request using AWS Signature V4. This is typically done using the AWSSigner from the Soto library.

    1. Define your AWS IoT endpoint host.
    2. Create an AWSSigner with your credentials, the service name iotdata, and your target region.
    3. Sign the URL for the /mqtt path.
    4. Construct the requestURI by appending the signed query parameters to the path.
    5. Use MQTTConnection.withConnection with a .webSocket transport configuration, passing the requestURI as the urlPath.
    import SotoSignerV4
    import MQTTNIO
    
    let host = "MY_AWS_IOT_ENDPOINT.iot.eu-west-1.amazonaws.com"
    let headers = HTTPHeaders([("host", host)])
    let signer = AWSSigner(
        credentials: StaticCredential(accessKeyId: "MYACCESSKEY", secretAccessKey: "MYSECRETKEY"),
        name: "iotdata",
        region: "eu-west-1"
    )
    let signedURL = signer.signURL(
        url: URL(string: "https://\(host)/mqtt")!,
        method: .GET,
        headers: headers,
        body: .none,
        expires: .minutes(30)
    )
    let requestURI = "/mqtt?\(signedURL.query!)"
    
    try await MQTTConnection.withConnection(
        address: .hostname(host),
        configuration: .init(transport: .webSocket(.init(urlPath: requestURI), tls: .enable(...))),
        identifier: "My AWS Client"
    ) { connection in
        // You are now connected to AWS IoT
    }
  10. Connect to AWS IoT using X.509 Client Certificates

    main

    You can connect to AWS IoT brokers using a standard MQTT connection authenticated with an X.509 client certificate.

    Important: When using X.509 certificates, ensure that your AWS IAM role/policy is updated to allow your specific client ID to connect and to grant the necessary permissions for the topics you intend to subscribe to or publish to.

  11. Connect to an MQTT broker using withConnection

    main

    The recommended way to manage an MQTT connection is using MQTTConnection.withConnection. This method handles the lifecycle of the connection: it automatically sends a CONNECT packet before executing your closure and a DISCONNECT packet after the closure returns. The connection remains active only for the duration of the closure.

    Use address to specify the broker location (e.g., .hostname("...")) and identifier to set the client ID.

    try await MQTTConnection.withConnection(
        address: .hostname("mqtt.eclipse.org"),
        identifier: "My Client"
    ) { connection in
        // You are now connected to the MQTT broker
        // The connection will be active only inside this closure
    }