// 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.Collections; using System.Net; using System.Security.Cryptography.X509Certificates; using System.Text; using MQTTnet.Adapter; using MQTTnet.Exceptions; using MQTTnet.Formatter; using MQTTnet.Internal; using MQTTnet.Packets; using MQTTnet.Protocol; using MQTTnet.Server.EnhancedAuthentication; namespace MQTTnet.Server; public sealed class ValidatingConnectionEventArgs : EventArgs { readonly MqttConnectPacket _connectPacket; public ValidatingConnectionEventArgs(MqttConnectPacket connectPacket, IMqttChannelAdapter clientAdapter, IDictionary sessionItems, CancellationToken cancellationToken) { _connectPacket = connectPacket ?? throw new ArgumentNullException(nameof(connectPacket)); ChannelAdapter = clientAdapter ?? throw new ArgumentNullException(nameof(clientAdapter)); SessionItems = sessionItems ?? throw new ArgumentNullException(nameof(sessionItems)); CancellationToken = cancellationToken; } /// /// Gets or sets the assigned client identifier. /// MQTTv5 only. /// public string AssignedClientIdentifier { get; set; } /// /// Gets or sets the authentication data. /// MQTT 5.0.0+ feature. /// public byte[] AuthenticationData => _connectPacket.AuthenticationData; /// /// Gets or sets the authentication method. /// MQTT 5.0.0+ feature. /// public string AuthenticationMethod => _connectPacket.AuthenticationMethod; public CancellationToken CancellationToken { get; } /// /// Gets the channel adapter. This can be a _MqttConnectionContext_ (used in ASP.NET), a _MqttChannelAdapter_ (used for /// TCP or WebSockets) or a custom implementation. /// public IMqttChannelAdapter ChannelAdapter { get; } /// /// Gets or sets a value indicating whether clean sessions are used or not. /// When a client connects to a broker it can connect using either a non persistent connection (clean session) or a /// persistent connection. /// With a non persistent connection the broker doesn't store any subscription information or undelivered messages for /// the client. /// This mode is ideal when the client only publishes messages. /// It can also connect as a durable client using a persistent connection. /// In this mode, the broker will store subscription information, and undelivered messages for the client. /// public bool? CleanSession => _connectPacket.CleanSession; public X509Certificate2 ClientCertificate => ChannelAdapter.ClientCertificate; /// /// Gets the client identifier. /// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues. /// public string ClientId => _connectPacket.ClientId; [Obsolete("Use RemoteEndPoint instead.")] public string Endpoint => RemoteEndPoint?.ToString(); public bool IsSecureConnection => ChannelAdapter.IsSecureConnection; /// /// Gets or sets the keep alive period. /// The connection is normally left open by the client so that is can send and receive data at any time. /// If no data flows over an open connection for a certain time period then the client will generate a PINGREQ and /// expect to receive a PINGRESP from the broker. /// This message exchange confirms that the connection is open and working. /// This period is known as the keep alive period. /// public ushort? KeepAlivePeriod => _connectPacket.KeepAlivePeriod; /// /// A value of 0 indicates that the value is not used. /// public uint MaximumPacketSize => _connectPacket.MaximumPacketSize; public string Password => Encoding.UTF8.GetString(RawPassword ?? EmptyBuffer.Array); public MqttProtocolVersion ProtocolVersion => ChannelAdapter.PacketFormatterAdapter.ProtocolVersion; public byte[] RawPassword => _connectPacket.Password; /// /// Gets or sets the reason code. When a MQTTv3 client connects the enum value must be one which is /// also supported in MQTTv3. Otherwise the connection attempt will fail because not all codes can be /// converted properly. /// MQTT 5.0.0+ feature. /// public MqttConnectReasonCode ReasonCode { get; set; } = MqttConnectReasonCode.Success; public string ReasonString { get; set; } /// /// Gets or sets the receive maximum. /// This gives the maximum length of the received messages. /// A value of 0 indicates that the value is not used. /// public ushort ReceiveMaximum => _connectPacket.ReceiveMaximum; public EndPoint RemoteEndPoint => ChannelAdapter.RemoteEndPoint; /// /// Gets the request problem information. /// MQTT 5.0.0+ feature. /// public bool RequestProblemInformation => _connectPacket.RequestProblemInformation; /// /// Gets the request response information. /// MQTT 5.0.0+ feature. /// public bool RequestResponseInformation => _connectPacket.RequestResponseInformation; /// /// Gets or sets the response authentication data. /// MQTT 5.0.0+ feature. /// public byte[] ResponseAuthenticationData { get; set; } /// /// Gets or sets the response user properties. /// In MQTT 5, user properties are basic UTF-8 string key-value pairs that you can append to almost every type of MQTT /// packet. /// As long as you don’t exceed the maximum message size, you can use an unlimited number of user properties to add /// metadata to MQTT messages and pass information between publisher, broker, and subscriber. /// The feature is very similar to the HTTP header concept. /// MQTT 5.0.0+ feature. /// public List ResponseUserProperties { get; set; } /// /// Gets or sets the server reference. This can be used together with i.e. "Server Moved" to send /// a different server address to the client. /// MQTT 5.0.0+ feature. /// public string ServerReference { get; set; } /// /// Gets the session expiry interval. /// The time after a session expires when it's not actively used. /// A value of 0 means no expiation. /// public uint SessionExpiryInterval => _connectPacket.SessionExpiryInterval; /// /// Gets or sets a key/value collection that can be used to share data within the scope of this session. /// public IDictionary SessionItems { get; } /// /// Gets or sets the topic alias maximum. /// This gives the maximum length of the topic alias. /// A value of 0 indicates that the value is not used. /// public ushort TopicAliasMaximum => _connectPacket.TopicAliasMaximum; public string UserName => _connectPacket.Username; /// /// Gets or sets the user properties. /// In MQTT 5, user properties are basic UTF-8 string key-value pairs that you can append to almost every type of MQTT /// packet. /// As long as you don’t exceed the maximum message size, you can use an unlimited number of user properties to add /// metadata to MQTT messages and pass information between publisher, broker, and subscriber. /// The feature is very similar to the HTTP header concept. /// MQTT 5.0.0+ feature. /// public List UserProperties => _connectPacket.UserProperties; /// /// Gets or sets the delay interval for the will message. /// This is the time between the client disconnect and the time the will message will be sent. /// A value of 0 indicates that the value is not used. /// public uint WillDelayInterval => _connectPacket.WillDelayInterval; public async Task ExchangeEnhancedAuthenticationAsync( ExchangeEnhancedAuthenticationOptions options, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(options); var requestAuthPacket = new MqttAuthPacket { // From RFC: If the initial CONNECT packet included an Authentication Method property then all AUTH packets, // and any successful CONNACK packet MUST include an Authentication Method Property with the same value as in the CONNECT packet [MQTT-4.12.0-5]. AuthenticationMethod = AuthenticationMethod, // The reason code will stay at continue all the time when connecting. The server will respond with the // CONNACK packet when authentication is done! ReasonCode = MqttAuthenticateReasonCode.ContinueAuthentication, AuthenticationData = options.AuthenticationData, ReasonString = options.ReasonString, UserProperties = options.UserProperties }; await ChannelAdapter.SendPacketAsync(requestAuthPacket, cancellationToken).ConfigureAwait(false); var responsePacket = await ChannelAdapter.ReceivePacketAsync(cancellationToken).ConfigureAwait(false); if (responsePacket == null) { throw new MqttCommunicationException("The client closed the connection."); } if (responsePacket is MqttAuthPacket responseAuthPacket) { if (!string.Equals(AuthenticationMethod, responseAuthPacket.AuthenticationMethod, StringComparison.Ordinal)) { throw new MqttProtocolViolationException("The authentication method cannot change while authenticating the client."); } return ExchangeEnhancedAuthenticationResultFactory.Create(responseAuthPacket); } if (responsePacket is MqttDisconnectPacket disconnectPacket) { return ExchangeEnhancedAuthenticationResultFactory.Create(disconnectPacket); } throw new MqttProtocolViolationException("Received other packet than AUTH while authenticating."); } }