CodeLogic.Mqtt 5.0.118-preview

This is a prerelease version of CodeLogic.Mqtt.
This package has a SemVer 2.0.0 package version: 5.0.118-preview+21f8da9.
dotnet add package CodeLogic.Mqtt --version 5.0.118-preview
                    
NuGet\Install-Package CodeLogic.Mqtt -Version 5.0.118-preview
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="CodeLogic.Mqtt" Version="5.0.118-preview" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="CodeLogic.Mqtt" Version="5.0.118-preview" />
                    
Directory.Packages.props
<PackageReference Include="CodeLogic.Mqtt" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add CodeLogic.Mqtt --version 5.0.118-preview
                    
#r "nuget: CodeLogic.Mqtt, 5.0.118-preview"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package CodeLogic.Mqtt@5.0.118-preview
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=CodeLogic.Mqtt&version=5.0.118-preview&prerelease
                    
Install as a Cake Addin
#tool nuget:?package=CodeLogic.Mqtt&version=5.0.118-preview&prerelease
                    
Install as a Cake Tool

CodeLogic.Mqtt

NuGet License: MIT

MQTT for CodeLogic 5: named client connections with automatic reconnect, and an optional embedded MQTT broker.

  • Clients: connect to any MQTT 3.1.1 or 5.0 broker, with TLS, credentials and a last will.
    • Reconnect: a lost connection is retried with exponential back-off.
    • Subscriptions: they belong to the client, so they survive reconnects and can be registered before the first connect.
    • Publishing: text, bytes or JSON.
  • Embedded broker: run an MQTT server inside your app.
    • Access: user accounts (clear or hashed passwords) with per-user topic permissions and optional pinned client ids; it binds to 127.0.0.1 by default, and has a TLS listener for anything else.
    • Protection: a client id belongs to the user holding it (no session takeover), $ ids are reserved, sessions per user and retained messages are capped, idle persistent sessions expire.
    • Retained messages: kept across restarts.
    • Taps and publishing: taps see what clients publish, and the broker can publish as itself.
  • CodeLogic conventions:
    • Errors: every fallible call returns Result / Result<T> with a stable mqtt.* error code (MqttErrorCodes).
    • Config and events: settings live in config.mqtt.json and apply live; connection events go to the CodeLogic event bus.
    • Health: the health check reports the broker and every connection.

Built on MQTTnet 5. MQTTnet types do not appear in the public API, except the documented RawClient / RawServer escape hatches.

Install

dotnet add package CodeLogic.Mqtt

Quick start

using CL.Mqtt;
using CL.Mqtt.Models;

await Libraries.LoadAsync<MqttLibrary>();
await CodeLogic.ConfigureAsync();   // writes config.mqtt.json with a "Default" connection to localhost:1883
await CodeLogic.StartAsync();       // connects (and starts the broker when enabled)

var mqtt = Libraries.Get<MqttLibrary>();
var client = mqtt.Client().ValueOrThrow();

// Subscribe: wildcards work, the handler gets a copy of the message.
var subscription = (await client.SubscribeAsync("home/+/temperature", message =>
{
    Console.WriteLine($"{message.Topic}: {message.PayloadAsString()}");
})).ValueOrThrow();

// Publish text, bytes or JSON.
await client.PublishAsync("home/kitchen/temperature", "21.5");
await client.PublishJsonAsync("home/kitchen/state", new { on = true, level = 80 }, options: MqttPublishOptions.Retained);

await subscription.DisposeAsync();  // stop receiving

Embedded broker

{
  "enabled": true,
  "connections": { "Default": { "host": "127.0.0.1", "port": 1883 } },
  "broker": {
    "enabled": true,
    "port": 1883,
    "bindAddress": "0.0.0.0",
    "tls": { "enabled": true, "port": 8883, "certificatePath": "C:/certs/broker.pfx", "certificatePassword": "…", "disablePlainPort": true },
    "allowAnonymous": false,
    "users": [
      { "username": "zigbee2mqtt", "password": "…", "publishTopics": ["zigbee2mqtt/#"], "subscribeTopics": ["zigbee2mqtt/#"], "clientIds": ["zigbee2mqtt"] },
      { "username": "dashboard",   "password": "$pbkdf2-sha256$600000$…", "canPublish": false, "subscribeTopics": ["home/#"] }
    ]
  }
}

Empty publishTopics / subscribeTopics allow every topic; use canPublish: false / canSubscribe: false to forbid one direction. MqttBrokerUser.HashPassword("…") makes the hashed form of a password. Open the broker to a network only with tls on: without it, passwords and payloads travel in clear (the broker logs a warning).

var broker = mqtt.Broker;

// See what clients publish, without a client of your own.
using var tap = broker.Tap("zigbee2mqtt/#", (message, ct) =>
{
    Console.WriteLine($"{message.SenderClientId} → {message.Topic}");
    return Task.CompletedTask;
}).ValueOrThrow();

// Publish as the broker itself.
await broker.PublishAsync("home/announcements", "Dinner is ready");

// Bring your own accounts: replace the authorizer between Initialize and Start.
broker.Authorizer = new MyAccountAuthorizer();

Documentation

Full guide, configuration reference, error codes and current limits: docs/libs/mqtt.md.

License

MIT

Product Compatible and additional computed target framework versions.
.NET net11.0 is compatible. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
5.0.118-preview 27 11/8/2026
5.0.117-preview 33 11/7/2026

# CL.Mqtt — Changelog

All notable changes to **CodeLogic.Mqtt** are documented here. Versions follow
[Semantic Versioning](https://semver.org/).

## 5.0 (unreleased)

### Changed

- Capture the sweeper's cancellation token before scheduling so an immediate stop cannot clear its source first. Broker listeners and cancellation sources are released even if a sweeper fails during shutdown; deferred-start and faulted-sweep regressions cover both paths.

- Make session ownership and quotas atomic, protect pending/replaced sessions during sweeping, warn for every network-visible plain listener, stop retrying refused credentials, and make synchronous Dispose nonblocking. Add concurrency, writer-interleaving and disposal regressions.


First release. Targets **.NET 11** (`net11.0`) and **CodeLogic 5.0**; built on MQTTnet 5.2.

### Added

- **`MqttLibrary`** (`CL.Mqtt`): a CodeLogic `ILibrary` with `config.mqtt.json`.
 - **Connections:** named client connections (`Client()`, `Client(name)`, `Clients`), plus `CreateClient` for clients outside the configuration.
 - **Broker:** an optional embedded broker (`Broker`, `HasBroker`).
 - **Health:** the check reports the broker and every auto-connect connection.
 - **Live config changes:**
   - connections are added, removed or reconfigured in place, keeping their subscriptions;
   - broker user changes apply at once, and listener changes restart the broker.
- **`MqttClientService`:**
 - **Connection:** MQTT 3.1.1 and 5.0, TLS (optionally accepting untrusted certificates), credentials, last will, clean or persistent sessions.
 - **Reconnect:** automatic, with exponential back-off and jitter; state is reported as `MqttConnectionState`.
 - **Subscriptions:**
   - they are kept by the client and sent again on every connect, and can be registered before the first connect;
   - several handlers can share a filter, and disposing one does not affect the others.
 - **Publishing:** raw bytes, UTF-8 text and JSON (reflection or source-generated `JsonTypeInfo<T>`), with `MqttPublishOptions` for QoS, retain and MQTT 5 properties.
 - **Live settings:** `ReconfigureAsync` applies new settings and keeps subscriptions.
- **`MqttBrokerService`:**
 - **Listening:** binds to exactly the configured address (`127.0.0.1` by default; no stray IPv6 listener). An optional TLS
   listener (`broker.tls`: PKCS#12 certificate, port 8883, `disablePlainPort`) for brokers opened to a network; a broker
   reachable from the network without TLS logs a warning at start.
 - **Access:** `IMqttBrokerAuthorizer` decides connections, publishes and subscriptions. The default `ConfigBrokerAuthorizer`
   uses the configured users, compares passwords in constant time (clear text or a `MqttBrokerUser.HashPassword` PBKDF2-SHA256
   hash), checks topic permissions with filter coverage, honours `canPublish` / `canSubscribe` and the optional `clientIds` a
   user may connect with.
 - **Session protection:** client ids starting with `$` are refused (`$broker` is the broker's own); a client id belongs to
   the user that holds its session, so another user can neither take the session over (and inherit its subscriptions and
   queued messages) nor disconnect the client; `maxSessionsPerUser` (100) caps connected plus persistent sessions per user;
   `persistentSessionExpiryMinutes` (1 day) deletes persistent sessions nobody reconnects to. Refusals carry a `Reason` on
   `MqttBrokerConnectionRejectedEvent`.
 - **Taps and publishing:** `Tap` observes client publishes, and `PublishAsync` publishes as the broker. The broker's own
   messages are recognised by their session, never by the client id. `GetClientsAsync` lists connected clients.
 - **Retained messages:** persisted to `retained.json` in the library's data directory (batched writes by a single writer,
   atomic replace); `maxRetainedMessages` (10 000) and `maxRetainedPayloadBytes` (1 MiB) bound what clients can store
   (MQTT 5 publishers get "quota exceeded"; a clear always passes).
 - **Queues:** `maxPendingMessagesPerClient` drops the oldest queued message, as documented (the overflow strategy is set
   explicitly); clients have 15 s to send CONNECT.
- **Messages:** `MqttMessage` carries a payload copy, text/JSON readers and MQTT 5 properties.
- **Topics:** `MqttTopic` validates topics and filters, matches topics against filters, and checks filter coverage (`Covers`).
- **Events** on the CodeLogic bus: `MqttConnectedEvent`, `MqttDisconnectedEvent`, `MqttBrokerStartedEvent`,
 `MqttBrokerStoppedEvent`, `MqttBrokerClientConnectedEvent`, `MqttBrokerClientDisconnectedEvent`,
 `MqttBrokerConnectionRejectedEvent`.
- **Errors:** stable codes in `MqttErrorCodes` (`mqtt.not_connected`, `mqtt.authentication_failed`,
 `mqtt.subscribe_rejected`, ...).

### Security

- A client could connect with the client id `$broker` and publish anywhere unchecked and unseen by taps, because the
 broker's own injected messages were recognised by that id. Reserved ids are now refused and injected messages are
 recognised by their session items.
- Any authenticated client could resume another client's persistent session (inheriting its subscriptions and queued
 messages) or kick it off by reusing its client id. Client ids are now bound to the user holding them.
- Persistent sessions and retained messages had no bound; see the caps above.
- `cleanSession: false` did nothing on MQTT 5 because no session expiry was sent; `sessionExpirySeconds` (1 day) is now
 sent on `V500`.
- Refused credentials were retried like a network outage; they now stop automatic retries until an explicit ConnectAsync call.
 MQTTnet 5.2 exposes failure codes through exception text; the parser matches known enum names.
- The retained-message store could run two writers on the same temporary file and lose a snapshot; it has one writer.
- `publishTopics` / `subscribeTopics` set to `null` in JSON no longer throw; user names cannot contain whitespace or start
 with `$`; client ids in the connection config cannot start with `$`.
- `MqttLibrary` implements `IAsyncDisposable`; `Dispose()` no longer blocks on network I/O after a normal stop.

### Documentation

- The docs list `empty client id` as a broker refusal reason, and say that a `maxPendingMessagesPerClient` change
 restarts the broker. The README's trailing relative link, which broke on nuget.org, is gone.