Files
MQTTnet/Source/MQTTnet/Options/MqttClientOptionsBuilder.cs
T
Alexander Köpke 07409e3a35 Allow user properties as pre-encoded UTF-8 binary buffer (#2228)
* Add support for binary user properties in MQTT 5.0

* Optimize byte array reversal in SubscriptionTopicHashTests

* Use InvariantCulture for topic string conversion in Load_Tests

* switched to `Microsoft.Testing.Platform`

* Refactor CI workflow to separate signed and unsigned package builds

* Add condition to sign job to prevent execution on external pull requests

* Remove fallback to value as string in MqttUserProperty

* Refactor MqttUserProperty handling to use byte arrays for improved performance and add deprecation warnings for legacy methods
2025-12-18 17:46:35 +01:00

510 lines
16 KiB
C#

// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using MQTTnet.Formatter;
using MQTTnet.Packets;
using MQTTnet.Protocol;
namespace MQTTnet;
public sealed class MqttClientOptionsBuilder
{
readonly MqttClientOptions _options = new();
int? _port;
EndPoint _remoteEndPoint;
MqttClientTcpOptions _tcpOptions;
MqttClientTlsOptions _tlsOptions;
MqttClientWebSocketOptions _webSocketOptions;
public MqttClientOptions Build()
{
if (_tcpOptions == null && _webSocketOptions == null)
{
throw new InvalidOperationException("A channel must be set.");
}
// The user can specify the TCP options with already configured TLS options
// or start with TLS settings not knowing which transport will be used (depending
// on the order of called methods from the builder).
// The builder prefers the explicitly set TLS options!
var tlsOptions = _tlsOptions ?? _tcpOptions?.TlsOptions;
if (_tcpOptions != null)
{
_tcpOptions.TlsOptions = tlsOptions;
if (_remoteEndPoint == null)
{
throw new ArgumentException("No endpoint is set.");
}
if (_remoteEndPoint is DnsEndPoint dns)
{
if (dns.Port == 0)
{
if (_port.HasValue)
{
_remoteEndPoint = new DnsEndPoint(dns.Host, _port.Value, dns.AddressFamily);
}
else
{
_remoteEndPoint = new DnsEndPoint(dns.Host, tlsOptions?.UseTls == false ? MqttPorts.Default : MqttPorts.Secure, dns.AddressFamily);
}
}
}
if (_remoteEndPoint is IPEndPoint ip)
{
if (ip.Port == 0)
{
if (_port.HasValue)
{
_remoteEndPoint = new IPEndPoint(ip.Address, _port.Value);
}
else
{
_remoteEndPoint = new IPEndPoint(ip.Address, tlsOptions?.UseTls == false ? MqttPorts.Default : MqttPorts.Secure);
}
}
}
if (_tcpOptions.RemoteEndpoint == null)
{
_tcpOptions.RemoteEndpoint = _remoteEndPoint;
}
}
else if (_webSocketOptions != null)
{
_webSocketOptions.TlsOptions = tlsOptions;
}
_options.ChannelOptions = (IMqttClientChannelOptions)_tcpOptions ?? _webSocketOptions;
MqttClientOptionsValidator.ThrowIfNotSupported(_options);
return _options;
}
public MqttClientOptionsBuilder WithAddressFamily(AddressFamily addressFamily)
{
_tcpOptions.AddressFamily = addressFamily;
return this;
}
/// <summary>
/// Clean session is used in MQTT versions below 5.0.0. It is the same as setting "CleanStart".
/// </summary>
public MqttClientOptionsBuilder WithCleanSession(bool value = true)
{
_options.CleanSession = value;
return this;
}
/// <summary>
/// Clean start is used in MQTT versions 5.0.0 and higher. It is the same as setting "CleanSession".
/// </summary>
public MqttClientOptionsBuilder WithCleanStart(bool value = true)
{
_options.CleanSession = value;
return this;
}
public MqttClientOptionsBuilder WithClientId(string value)
{
_options.ClientId = value;
return this;
}
public MqttClientOptionsBuilder WithConnectionUri(Uri uri)
{
ArgumentNullException.ThrowIfNull(uri);
var port = uri.IsDefaultPort ? null : (int?)uri.Port;
switch (uri.Scheme.ToLowerInvariant())
{
case "tcp":
case "mqtt":
WithTcpServer(uri.Host, port).WithAddressFamily(AddressFamily.Unspecified).WithProtocolType(ProtocolType.Tcp).WithTlsOptions(o => o.UseTls(false));
break;
case "mqtts":
WithTcpServer(uri.Host, port).WithAddressFamily(AddressFamily.Unspecified).WithProtocolType(ProtocolType.Tcp).WithTlsOptions(o => o.UseTls());
break;
case "ws":
WithWebSocketServer(o => o.WithUri(uri.ToString())).WithTlsOptions(o => o.UseTls(false));
break;
case "wss":
WithWebSocketServer(o => o.WithUri(uri.ToString())).WithTlsOptions(o => o.UseTls());
break;
// unix:///path/to/socket
case "unix":
WithEndPoint(new UnixDomainSocketEndPoint(uri.AbsolutePath))
.WithAddressFamily(AddressFamily.Unix)
.WithProtocolType(ProtocolType.Unspecified)
.WithTlsOptions(o => o.UseTls(false));
break;
default:
throw new ArgumentException("Unexpected scheme in uri.");
}
if (!string.IsNullOrEmpty(uri.UserInfo))
{
var userInfo = uri.UserInfo.Split(':');
var username = userInfo[0];
var password = userInfo.Length > 1 ? userInfo[1] : "";
WithCredentials(username, password);
}
return this;
}
public MqttClientOptionsBuilder WithConnectionUri(string uri)
{
return WithConnectionUri(new Uri(uri, UriKind.Absolute));
}
public MqttClientOptionsBuilder WithCredentials(string username, string password)
{
byte[] passwordBuffer = null;
if (password != null)
{
passwordBuffer = Encoding.UTF8.GetBytes(password);
}
return WithCredentials(username, passwordBuffer);
}
public MqttClientOptionsBuilder WithCredentials(string username, byte[] password = null)
{
return WithCredentials(new MqttClientCredentials(username, password));
}
public MqttClientOptionsBuilder WithCredentials(IMqttClientCredentialsProvider credentials)
{
_options.Credentials = credentials;
return this;
}
public MqttClientOptionsBuilder WithEndPoint(EndPoint endPoint)
{
_remoteEndPoint = endPoint ?? throw new ArgumentNullException(nameof(endPoint));
_tcpOptions = new MqttClientTcpOptions();
return this;
}
public MqttClientOptionsBuilder WithEnhancedAuthentication(string method, byte[] data = null)
{
_options.AuthenticationMethod = method;
_options.AuthenticationData = data;
return this;
}
public MqttClientOptionsBuilder WithEnhancedAuthenticationHandler(IMqttEnhancedAuthenticationHandler handler)
{
_options.EnhancedAuthenticationHandler = handler;
return this;
}
public MqttClientOptionsBuilder WithKeepAlivePeriod(TimeSpan value)
{
_options.KeepAlivePeriod = value;
return this;
}
public MqttClientOptionsBuilder WithMaximumPacketSize(uint maximumPacketSize)
{
_options.MaximumPacketSize = maximumPacketSize;
return this;
}
public MqttClientOptionsBuilder WithNoKeepAlive()
{
return WithKeepAlivePeriod(TimeSpan.Zero);
}
/// <summary>
/// Usually the MQTT packets can be sent partially. This is done by using multiple TCP packets
/// or WebSocket frames etc. Unfortunately not all brokers (like Amazon Web Services (AWS)) do support this feature and
/// will close the connection when receiving such packets. If such a service is used this flag must
/// be set to _true_.
/// </summary>
public MqttClientOptionsBuilder WithoutPacketFragmentation()
{
_options.AllowPacketFragmentation = false;
return this;
}
public MqttClientOptionsBuilder WithProtocolType(ProtocolType protocolType)
{
_tcpOptions.ProtocolType = protocolType;
return this;
}
public MqttClientOptionsBuilder WithProtocolVersion(MqttProtocolVersion value)
{
if (value == MqttProtocolVersion.Unknown)
{
throw new ArgumentException("Protocol version is invalid.");
}
_options.ProtocolVersion = value;
return this;
}
public MqttClientOptionsBuilder WithReceiveMaximum(ushort receiveMaximum)
{
_options.ReceiveMaximum = receiveMaximum;
return this;
}
public MqttClientOptionsBuilder WithRequestProblemInformation(bool requestProblemInformation = true)
{
_options.RequestProblemInformation = requestProblemInformation;
return this;
}
public MqttClientOptionsBuilder WithRequestResponseInformation(bool requestResponseInformation = true)
{
_options.RequestResponseInformation = requestResponseInformation;
return this;
}
public MqttClientOptionsBuilder WithSessionExpiryInterval(uint sessionExpiryInterval)
{
_options.SessionExpiryInterval = sessionExpiryInterval;
return this;
}
public MqttClientOptionsBuilder WithTcpServer(string host, int? port = null, AddressFamily addressFamily = AddressFamily.Unspecified)
{
ArgumentNullException.ThrowIfNull(host);
_tcpOptions = new MqttClientTcpOptions();
// The value 0 will be updated when building the options.
// This a backward compatibility feature.
_remoteEndPoint = new DnsEndPoint(host, port ?? 0, addressFamily);
_port = port;
return this;
}
public MqttClientOptionsBuilder WithTcpServer(Action<MqttClientTcpOptions> optionsBuilder)
{
ArgumentNullException.ThrowIfNull(optionsBuilder);
_tcpOptions = new MqttClientTcpOptions();
optionsBuilder(_tcpOptions);
return this;
}
/// <summary>
/// Sets the timeout which will be applied at socket level and internal operations.
/// The default value is the same as for sockets in .NET in general.
/// </summary>
public MqttClientOptionsBuilder WithTimeout(TimeSpan value)
{
_options.Timeout = value;
return this;
}
public MqttClientOptionsBuilder WithTlsOptions(MqttClientTlsOptions tlsOptions)
{
_tlsOptions = tlsOptions;
return this;
}
public MqttClientOptionsBuilder WithTlsOptions(Action<MqttClientTlsOptionsBuilder> configure)
{
ArgumentNullException.ThrowIfNull(configure);
var builder = new MqttClientTlsOptionsBuilder();
configure.Invoke(builder);
_tlsOptions = builder.Build();
return this;
}
public MqttClientOptionsBuilder WithTopicAliasMaximum(ushort topicAliasMaximum)
{
_options.TopicAliasMaximum = topicAliasMaximum;
return this;
}
/// <summary>
/// If set to true, the bridge will attempt to indicate to the remote broker that it is a bridge not an ordinary
/// client.
/// If successful, this means that loop detection will be more effective and that retained messages will be propagated
/// correctly.
/// Not all brokers support this feature so it may be necessary to set it to false if your bridge does not connect
/// properly.
/// </summary>
public MqttClientOptionsBuilder WithTryPrivate(bool value = true)
{
_options.TryPrivate = value;
return this;
}
[Obsolete("Please use more performance `WithUserProperty` with ArraySegment<byte> or ReadOnlyMemory<byte> for the value.")]
public MqttClientOptionsBuilder WithUserProperty(string name, string value)
{
if (_options.UserProperties == null)
{
_options.UserProperties = new List<MqttUserProperty>();
}
_options.UserProperties.Add(new MqttUserProperty(name, value));
return this;
}
public MqttClientOptionsBuilder WithUserProperty(string name, ReadOnlyMemory<byte> value)
{
if (_options.UserProperties == null)
{
_options.UserProperties = new List<MqttUserProperty>();
}
_options.UserProperties.Add(new MqttUserProperty(name, value));
return this;
}
public MqttClientOptionsBuilder WithUserProperty(string name, ArraySegment<byte> value)
{
if (_options.UserProperties == null)
{
_options.UserProperties = new List<MqttUserProperty>();
}
_options.UserProperties.Add(new MqttUserProperty(name, value));
return this;
}
public MqttClientOptionsBuilder WithWebSocketServer(Action<MqttClientWebSocketOptionsBuilder> configure)
{
ArgumentNullException.ThrowIfNull(configure);
var webSocketOptionsBuilder = new MqttClientWebSocketOptionsBuilder();
configure.Invoke(webSocketOptionsBuilder);
_webSocketOptions = webSocketOptionsBuilder.Build();
return this;
}
public MqttClientOptionsBuilder WithWillContentType(string willContentType)
{
_options.WillContentType = willContentType;
return this;
}
public MqttClientOptionsBuilder WithWillCorrelationData(byte[] willCorrelationData)
{
_options.WillCorrelationData = willCorrelationData;
return this;
}
public MqttClientOptionsBuilder WithWillDelayInterval(uint willDelayInterval)
{
_options.WillDelayInterval = willDelayInterval;
return this;
}
public MqttClientOptionsBuilder WithWillMessageExpiryInterval(uint willMessageExpiryInterval)
{
_options.WillMessageExpiryInterval = willMessageExpiryInterval;
return this;
}
public MqttClientOptionsBuilder WithWillPayload(byte[] willPayload)
{
_options.WillPayload = willPayload;
return this;
}
public MqttClientOptionsBuilder WithWillPayload(ArraySegment<byte> willPayload)
{
if (willPayload.Count == 0)
{
_options.WillPayload = null;
return this;
}
_options.WillPayload = willPayload.ToArray();
return this;
}
public MqttClientOptionsBuilder WithWillPayload(string willPayload)
{
if (string.IsNullOrEmpty(willPayload))
{
return WithWillPayload((byte[])null);
}
_options.WillPayload = Encoding.UTF8.GetBytes(willPayload);
return this;
}
public MqttClientOptionsBuilder WithWillPayloadFormatIndicator(MqttPayloadFormatIndicator willPayloadFormatIndicator)
{
_options.WillPayloadFormatIndicator = willPayloadFormatIndicator;
return this;
}
public MqttClientOptionsBuilder WithWillQualityOfServiceLevel(MqttQualityOfServiceLevel willQualityOfServiceLevel)
{
_options.WillQualityOfServiceLevel = willQualityOfServiceLevel;
return this;
}
public MqttClientOptionsBuilder WithWillResponseTopic(string willResponseTopic)
{
_options.WillResponseTopic = willResponseTopic;
return this;
}
public MqttClientOptionsBuilder WithWillRetain(bool willRetain = true)
{
_options.WillRetain = willRetain;
return this;
}
public MqttClientOptionsBuilder WithWillTopic(string willTopic)
{
_options.WillTopic = willTopic;
return this;
}
[Obsolete("Please use more performance `WithWillUserProperty` with ArraySegment<byte> or ReadOnlyMemory<byte> for the value.")]
public MqttClientOptionsBuilder WithWillUserProperty(string name, string value)
{
_options.WillUserProperties ??= [];
_options.WillUserProperties.Add(new MqttUserProperty(name, value));
return this;
}
public MqttClientOptionsBuilder WithWillUserProperty(string name, ReadOnlyMemory<byte> value)
{
_options.WillUserProperties ??= [];
_options.WillUserProperties.Add(new MqttUserProperty(name, value));
return this;
}
public MqttClientOptionsBuilder WithWillUserProperty(string name, ArraySegment<byte> value)
{
_options.WillUserProperties ??= [];
_options.WillUserProperties.Add(new MqttUserProperty(name, value));
return this;
}
}