From 5a59cd278cbb0c2b82762c269286aaba7c8ff7f5 Mon Sep 17 00:00:00 2001 From: Christian Kratky Date: Mon, 6 Aug 2018 20:36:15 +0200 Subject: [PATCH 001/124] Add serializer adapter. --- .../MqttClientConnectionContextFactory.cs | 4 +- .../MqttConnectionContext.cs | 14 +- .../MqttConnectionHandler.cs | 8 +- .../MqttWebSocketServerAdapter.cs | 4 +- Source/MQTTnet/Adapter/IMqttChannelAdapter.cs | 2 +- Source/MQTTnet/Adapter/MqttChannelAdapter.cs | 24 +-- Source/MQTTnet/Client/MqttClient.cs | 9 +- .../MqttClientAdapterFactory.cs | 8 +- .../MqttTcpServerAdapter.Uwp.cs | 2 +- .../Implementations/MqttTcpServerListener.cs | 2 +- Source/MQTTnet/MQTTnet.csproj | 4 + Source/MQTTnet/Packets/MqttConnAckPacket.cs | 14 +- Source/MQTTnet/Packets/MqttConnectPacket.cs | 24 +-- .../MQTTnet/Packets/V3/MqttV3ConnAckPacket.cs | 16 ++ .../MQTTnet/Packets/V3/MqttV3ConnectPacket.cs | 26 ++++ .../Serializer/IMqttPacketSerializer.cs | 2 - .../Serializer/MqttPacketBodyReader.cs | 7 + .../Serializer/MqttPacketSerializerAdapter.cs | 123 +++++++++++++++ Source/MQTTnet/Serializer/MqttPacketWriter.cs | 6 +- .../MQTTnet/Serializer/MqttProtocolVersion.cs | 5 +- ...ializer.cs => MqttV310PacketSerializer.cs} | 146 ++++++------------ .../Serializer/MqttV311PacketSerializer.cs | 100 ++++++++++++ .../Serializer/MqttV500PacketSerializer.cs | 24 +++ Source/MQTTnet/Server/IMqttClientSession.cs | 2 +- Source/MQTTnet/Server/MqttClientSession.cs | 13 +- .../Server/MqttClientSessionsManager.cs | 16 +- .../ReaderExtensionsTest.cs | 4 +- .../ChannelAdapterBenchmark.cs | 3 +- .../MQTTnet.Benchmarks/SerializerBenchmark.cs | 4 +- Tests/MQTTnet.Core.Tests/MqttClientTests.cs | 3 +- .../MqttKeepAliveMonitorTests.cs | 2 +- .../MqttPacketSerializerTests.cs | 64 ++++++-- Tests/MQTTnet.Core.Tests/MqttServerTests.cs | 4 +- .../TestMqttCommunicationAdapter.cs | 2 +- .../MainPage.xaml.cs | 4 +- 35 files changed, 471 insertions(+), 224 deletions(-) create mode 100644 Source/MQTTnet/Packets/V3/MqttV3ConnAckPacket.cs create mode 100644 Source/MQTTnet/Packets/V3/MqttV3ConnectPacket.cs create mode 100644 Source/MQTTnet/Serializer/MqttPacketSerializerAdapter.cs rename Source/MQTTnet/Serializer/{MqttPacketSerializer.cs => MqttV310PacketSerializer.cs} (79%) create mode 100644 Source/MQTTnet/Serializer/MqttV311PacketSerializer.cs create mode 100644 Source/MQTTnet/Serializer/MqttV500PacketSerializer.cs diff --git a/Source/MQTTnet.AspnetCore/Client/MqttClientConnectionContextFactory.cs b/Source/MQTTnet.AspnetCore/Client/MqttClientConnectionContextFactory.cs index e308ccd4..93253857 100644 --- a/Source/MQTTnet.AspnetCore/Client/MqttClientConnectionContextFactory.cs +++ b/Source/MQTTnet.AspnetCore/Client/MqttClientConnectionContextFactory.cs @@ -14,15 +14,13 @@ namespace MQTTnet.AspNetCore.Client { if (options == null) throw new ArgumentNullException(nameof(options)); - var serializer = new MqttPacketSerializer { ProtocolVersion = options.ProtocolVersion }; - switch (options.ChannelOptions) { case MqttClientTcpOptions tcpOptions: { var endpoint = new DnsEndPoint(tcpOptions.Server, tcpOptions.GetPort()); var tcpConnection = new TcpConnection(endpoint); - return new MqttConnectionContext(serializer, tcpConnection); + return new MqttConnectionContext(new MqttPacketSerializerAdapter(options.ProtocolVersion), tcpConnection); } default: { diff --git a/Source/MQTTnet.AspnetCore/MqttConnectionContext.cs b/Source/MQTTnet.AspnetCore/MqttConnectionContext.cs index 8a8469de..2d27894d 100644 --- a/Source/MQTTnet.AspnetCore/MqttConnectionContext.cs +++ b/Source/MQTTnet.AspnetCore/MqttConnectionContext.cs @@ -12,17 +12,15 @@ namespace MQTTnet.AspNetCore { public class MqttConnectionContext : IMqttChannelAdapter { - public MqttConnectionContext( - IMqttPacketSerializer packetSerializer, - ConnectionContext connection) + public MqttConnectionContext(MqttPacketSerializerAdapter packetSerializerAdapter, ConnectionContext connection) { - PacketSerializer = packetSerializer; - Connection = connection; + PacketSerializerAdapter = packetSerializerAdapter ?? throw new ArgumentNullException(nameof(packetSerializerAdapter)); + Connection = connection ?? throw new ArgumentNullException(nameof(connection)); } public string Endpoint => Connection.ConnectionId; public ConnectionContext Connection { get; } - public IMqttPacketSerializer PacketSerializer { get; } + public MqttPacketSerializerAdapter PacketSerializerAdapter { get; } public event EventHandler ReadingPacketStarted; public event EventHandler ReadingPacketCompleted; @@ -71,7 +69,7 @@ namespace MQTTnet.AspNetCore { if (!buffer.IsEmpty) { - if (PacketSerializer.TryDeserialize(buffer, out var packet, out consumed, out observed)) + if (PacketSerializerAdapter.Serializer.TryDeserialize(buffer, out var packet, out consumed, out observed)) { return packet; } @@ -106,7 +104,7 @@ namespace MQTTnet.AspNetCore public Task SendPacketAsync(MqttBasePacket packet, CancellationToken cancellationToken) { - var buffer = PacketSerializer.Serialize(packet); + var buffer = PacketSerializerAdapter.Serialize(packet); return Connection.Transport.Output.WriteAsync(buffer.AsMemory(), cancellationToken).AsTask(); } diff --git a/Source/MQTTnet.AspnetCore/MqttConnectionHandler.cs b/Source/MQTTnet.AspnetCore/MqttConnectionHandler.cs index 23903894..77a8734b 100644 --- a/Source/MQTTnet.AspnetCore/MqttConnectionHandler.cs +++ b/Source/MQTTnet.AspnetCore/MqttConnectionHandler.cs @@ -9,7 +9,7 @@ using System.Threading.Tasks; namespace MQTTnet.AspNetCore { public class MqttConnectionHandler : ConnectionHandler, IMqttServerAdapter - { + { public event EventHandler ClientAccepted; public override async Task OnConnectedAsync(ConnectionContext connection) @@ -21,9 +21,7 @@ namespace MQTTnet.AspNetCore transferFormatFeature.ActiveFormat = TransferFormat.Binary; } - - var serializer = new MqttPacketSerializer(); - using (var adapter = new MqttConnectionContext(serializer, connection)) + using (var adapter = new MqttConnectionContext(new MqttPacketSerializerAdapter(), connection)) { var args = new MqttServerAdapterClientAcceptedEventArgs(adapter); ClientAccepted?.Invoke(this, args); @@ -41,7 +39,7 @@ namespace MQTTnet.AspNetCore { return Task.CompletedTask; } - + public void Dispose() { } diff --git a/Source/MQTTnet.AspnetCore/MqttWebSocketServerAdapter.cs b/Source/MQTTnet.AspnetCore/MqttWebSocketServerAdapter.cs index dbd93604..4dc75a44 100644 --- a/Source/MQTTnet.AspnetCore/MqttWebSocketServerAdapter.cs +++ b/Source/MQTTnet.AspnetCore/MqttWebSocketServerAdapter.cs @@ -27,7 +27,7 @@ namespace MQTTnet.AspNetCore { if (webSocket == null) throw new ArgumentNullException(nameof(webSocket)); - var clientAdapter = new MqttChannelAdapter(new MqttWebSocketChannel(webSocket, endpoint), new MqttPacketSerializer(), new MqttNetLogger().CreateChildLogger(nameof(MqttWebSocketServerAdapter))); + var clientAdapter = new MqttChannelAdapter(new MqttWebSocketChannel(webSocket, endpoint), new MqttPacketSerializerAdapter(), new MqttNetLogger().CreateChildLogger(nameof(MqttWebSocketServerAdapter))); var eventArgs = new MqttServerAdapterClientAcceptedEventArgs(clientAdapter); ClientAccepted?.Invoke(this, eventArgs); @@ -37,7 +37,7 @@ namespace MQTTnet.AspNetCore await eventArgs.SessionTask.ConfigureAwait(false); } } - + public void Dispose() { StopAsync().GetAwaiter().GetResult(); diff --git a/Source/MQTTnet/Adapter/IMqttChannelAdapter.cs b/Source/MQTTnet/Adapter/IMqttChannelAdapter.cs index 43e5c8c4..9aa5cacc 100644 --- a/Source/MQTTnet/Adapter/IMqttChannelAdapter.cs +++ b/Source/MQTTnet/Adapter/IMqttChannelAdapter.cs @@ -10,7 +10,7 @@ namespace MQTTnet.Adapter { string Endpoint { get; } - IMqttPacketSerializer PacketSerializer { get; } + MqttPacketSerializerAdapter PacketSerializerAdapter { get; } event EventHandler ReadingPacketStarted; diff --git a/Source/MQTTnet/Adapter/MqttChannelAdapter.cs b/Source/MQTTnet/Adapter/MqttChannelAdapter.cs index 4e617330..01d252b0 100644 --- a/Source/MQTTnet/Adapter/MqttChannelAdapter.cs +++ b/Source/MQTTnet/Adapter/MqttChannelAdapter.cs @@ -22,26 +22,25 @@ namespace MQTTnet.Adapter private readonly IMqttNetChildLogger _logger; private readonly IMqttChannel _channel; - + private readonly byte[] _fixedHeaderBuffer = new byte[2]; - private readonly byte[] _singleByteBuffer = new byte[1]; private bool _isDisposed; - public MqttChannelAdapter(IMqttChannel channel, IMqttPacketSerializer serializer, IMqttNetChildLogger logger) + public MqttChannelAdapter(IMqttChannel channel, MqttPacketSerializerAdapter packetSerializerAdapter, IMqttNetChildLogger logger) { if (logger == null) throw new ArgumentNullException(nameof(logger)); _channel = channel ?? throw new ArgumentNullException(nameof(channel)); - PacketSerializer = serializer ?? throw new ArgumentNullException(nameof(serializer)); - + PacketSerializerAdapter = packetSerializerAdapter ?? throw new ArgumentNullException(nameof(packetSerializerAdapter)); + _logger = logger.CreateChildLogger(nameof(MqttChannelAdapter)); } public string Endpoint => _channel.Endpoint; - public IMqttPacketSerializer PacketSerializer { get; } + public MqttPacketSerializerAdapter PacketSerializerAdapter { get; } public event EventHandler ReadingPacketStarted; public event EventHandler ReadingPacketCompleted; @@ -91,15 +90,15 @@ namespace MQTTnet.Adapter WrapException(exception); } } - + public async Task SendPacketAsync(MqttBasePacket packet, CancellationToken cancellationToken) { await _writerSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false); try { - var packetData = PacketSerializer.Serialize(packet); + var packetData = PacketSerializerAdapter.Serialize(packet); await _channel.WriteAsync(packetData.Array, packetData.Offset, packetData.Count, cancellationToken).ConfigureAwait(false); - PacketSerializer.FreeBuffer(); + PacketSerializerAdapter.FreeBuffer(); _logger.Verbose("TX >>> {0}", packet); } @@ -140,7 +139,12 @@ namespace MQTTnet.Adapter return null; } - var packet = PacketSerializer.Deserialize(receivedMqttPacket); + if (!PacketSerializerAdapter.ProtocolVersion.HasValue) + { + PacketSerializerAdapter.DetectProtocolVersion(receivedMqttPacket); + } + + var packet = PacketSerializerAdapter.Deserialize(receivedMqttPacket); if (packet == null) { throw new MqttProtocolViolationException("Received malformed packet."); diff --git a/Source/MQTTnet/Client/MqttClient.cs b/Source/MQTTnet/Client/MqttClient.cs index 0fa074c2..643d0ef9 100644 --- a/Source/MQTTnet/Client/MqttClient.cs +++ b/Source/MQTTnet/Client/MqttClient.cs @@ -9,6 +9,7 @@ using MQTTnet.Diagnostics; using MQTTnet.Exceptions; using MQTTnet.Internal; using MQTTnet.Packets; +using MQTTnet.Packets.V3; using MQTTnet.Protocol; namespace MQTTnet.Client @@ -61,7 +62,7 @@ namespace MQTTnet.Client _packetDispatcher.Reset(); _adapter = _adapterFactory.CreateClientAdapter(options, _logger); - + _logger.Verbose($"Trying to connect with server ({_options.ChannelOptions})."); await _adapter.ConnectAsync(_options.CommunicationTimeout, _cancellationTokenSource.Token).ConfigureAwait(false); _logger.Verbose("Connection with server established."); @@ -189,9 +190,9 @@ namespace MQTTnet.Client _adapter?.Dispose(); } - private async Task AuthenticateAsync(MqttApplicationMessage willApplicationMessage, CancellationToken cancellationToken) + private async Task AuthenticateAsync(MqttApplicationMessage willApplicationMessage, CancellationToken cancellationToken) { - var connectPacket = new MqttConnectPacket + var connectPacket = new MqttV3ConnectPacket { ClientId = _options.ClientId, Username = _options.Credentials?.Username, @@ -201,7 +202,7 @@ namespace MQTTnet.Client WillMessage = willApplicationMessage }; - var response = await SendAndReceiveAsync(connectPacket, cancellationToken).ConfigureAwait(false); + var response = await SendAndReceiveAsync(connectPacket, cancellationToken).ConfigureAwait(false); if (response.ConnectReturnCode != MqttConnectReturnCode.ConnectionAccepted) { throw new MqttConnectingFailedException(response.ConnectReturnCode); diff --git a/Source/MQTTnet/Implementations/MqttClientAdapterFactory.cs b/Source/MQTTnet/Implementations/MqttClientAdapterFactory.cs index dd06c31a..169d18e2 100644 --- a/Source/MQTTnet/Implementations/MqttClientAdapterFactory.cs +++ b/Source/MQTTnet/Implementations/MqttClientAdapterFactory.cs @@ -11,19 +11,17 @@ namespace MQTTnet.Implementations public IMqttChannelAdapter CreateClientAdapter(IMqttClientOptions options, IMqttNetChildLogger logger) { if (options == null) throw new ArgumentNullException(nameof(options)); - - var serializer = new MqttPacketSerializer { ProtocolVersion = options.ProtocolVersion }; - + switch (options.ChannelOptions) { case MqttClientTcpOptions _: { - return new MqttChannelAdapter(new MqttTcpChannel(options), serializer, logger); + return new MqttChannelAdapter(new MqttTcpChannel(options), new MqttPacketSerializerAdapter(options.ProtocolVersion), logger); } case MqttClientWebSocketOptions webSocketOptions: { - return new MqttChannelAdapter(new MqttWebSocketChannel(webSocketOptions), serializer, logger); + return new MqttChannelAdapter(new MqttWebSocketChannel(webSocketOptions), new MqttPacketSerializerAdapter(options.ProtocolVersion), logger); } default: diff --git a/Source/MQTTnet/Implementations/MqttTcpServerAdapter.Uwp.cs b/Source/MQTTnet/Implementations/MqttTcpServerAdapter.Uwp.cs index c42db2b6..d494b910 100644 --- a/Source/MQTTnet/Implementations/MqttTcpServerAdapter.Uwp.cs +++ b/Source/MQTTnet/Implementations/MqttTcpServerAdapter.Uwp.cs @@ -72,7 +72,7 @@ namespace MQTTnet.Implementations { try { - var clientAdapter = new MqttChannelAdapter(new MqttTcpChannel(args.Socket, _options), new MqttPacketSerializer(), _logger); + var clientAdapter = new MqttChannelAdapter(new MqttTcpChannel(args.Socket, _options), new MqttPacketSerializerAdapter(), _logger); ClientAccepted?.Invoke(this, new MqttServerAdapterClientAcceptedEventArgs(clientAdapter)); } catch (Exception exception) diff --git a/Source/MQTTnet/Implementations/MqttTcpServerListener.cs b/Source/MQTTnet/Implementations/MqttTcpServerListener.cs index e3522979..d91b66e6 100644 --- a/Source/MQTTnet/Implementations/MqttTcpServerListener.cs +++ b/Source/MQTTnet/Implementations/MqttTcpServerListener.cs @@ -83,7 +83,7 @@ namespace MQTTnet.Implementations _socket.LocalEndPoint, _addressFamily == AddressFamily.InterNetwork ? "ipv4" : "ipv6"); - var clientAdapter = new MqttChannelAdapter(new MqttTcpChannel(clientSocket, sslStream), new MqttPacketSerializer(), _logger); + var clientAdapter = new MqttChannelAdapter(new MqttTcpChannel(clientSocket, sslStream), new MqttPacketSerializerAdapter(), _logger); ClientAccepted?.Invoke(this, new MqttServerAdapterClientAcceptedEventArgs(clientAdapter)); } catch (ObjectDisposedException) diff --git a/Source/MQTTnet/MQTTnet.csproj b/Source/MQTTnet/MQTTnet.csproj index 30eace10..1704d7d0 100644 --- a/Source/MQTTnet/MQTTnet.csproj +++ b/Source/MQTTnet/MQTTnet.csproj @@ -63,4 +63,8 @@ + + + + \ No newline at end of file diff --git a/Source/MQTTnet/Packets/MqttConnAckPacket.cs b/Source/MQTTnet/Packets/MqttConnAckPacket.cs index dbdeea89..a0f69fdd 100644 --- a/Source/MQTTnet/Packets/MqttConnAckPacket.cs +++ b/Source/MQTTnet/Packets/MqttConnAckPacket.cs @@ -1,16 +1,6 @@ -using MQTTnet.Protocol; - -namespace MQTTnet.Packets +namespace MQTTnet.Packets { - public class MqttConnAckPacket : MqttBasePacket + public abstract class MqttConnAckPacket : MqttBasePacket { - public bool IsSessionPresent { get; set; } - - public MqttConnectReturnCode ConnectReturnCode { get; set; } - - public override string ToString() - { - return "ConnAck: [ConnectReturnCode=" + ConnectReturnCode + "] [IsSessionPresent=" + IsSessionPresent + "]"; - } } } diff --git a/Source/MQTTnet/Packets/MqttConnectPacket.cs b/Source/MQTTnet/Packets/MqttConnectPacket.cs index 2134f4bb..1faa8c20 100644 --- a/Source/MQTTnet/Packets/MqttConnectPacket.cs +++ b/Source/MQTTnet/Packets/MqttConnectPacket.cs @@ -1,26 +1,6 @@ -using MQTTnet.Serializer; - -namespace MQTTnet.Packets +namespace MQTTnet.Packets { - public class MqttConnectPacket : MqttBasePacket + public abstract class MqttConnectPacket : MqttBasePacket { - public MqttProtocolVersion ProtocolVersion { get; set; } - - public string ClientId { get; set; } - - public string Username { get; set; } - - public string Password { get; set; } - - public ushort KeepAlivePeriod { get; set; } - - public bool CleanSession { get; set; } - - public MqttApplicationMessage WillMessage { get; set; } - - public override string ToString() - { - return "Connect: [ClientId=" + ClientId + "] [Username=" + Username + "] [Password=" + Password + "] [KeepAlivePeriod=" + KeepAlivePeriod + "] [CleanSession=" + CleanSession + "]"; - } } } diff --git a/Source/MQTTnet/Packets/V3/MqttV3ConnAckPacket.cs b/Source/MQTTnet/Packets/V3/MqttV3ConnAckPacket.cs new file mode 100644 index 00000000..3e858ec7 --- /dev/null +++ b/Source/MQTTnet/Packets/V3/MqttV3ConnAckPacket.cs @@ -0,0 +1,16 @@ +using MQTTnet.Protocol; + +namespace MQTTnet.Packets.V3 +{ + public class MqttV3ConnAckPacket : MqttBasePacket + { + public bool IsSessionPresent { get; set; } + + public MqttConnectReturnCode ConnectReturnCode { get; set; } + + public override string ToString() + { + return "ConnAck: [ConnectReturnCode=" + ConnectReturnCode + "] [IsSessionPresent=" + IsSessionPresent + "]"; + } + } +} diff --git a/Source/MQTTnet/Packets/V3/MqttV3ConnectPacket.cs b/Source/MQTTnet/Packets/V3/MqttV3ConnectPacket.cs new file mode 100644 index 00000000..2434d594 --- /dev/null +++ b/Source/MQTTnet/Packets/V3/MqttV3ConnectPacket.cs @@ -0,0 +1,26 @@ +namespace MQTTnet.Packets.V3 +{ + public class MqttV3ConnectPacket : MqttConnectPacket + { + public string ProtocolName { get; set; } + + public byte ProtocolLevel { get; set; } + + public string ClientId { get; set; } + + public string Username { get; set; } + + public string Password { get; set; } + + public ushort KeepAlivePeriod { get; set; } + + public bool CleanSession { get; set; } + + public MqttApplicationMessage WillMessage { get; set; } + + public override string ToString() + { + return "Connect: [ProtocolLevel=" + ProtocolLevel + "] [ClientId=" + ClientId + "] [Username=" + Username + "] [Password=" + Password + "] [KeepAlivePeriod=" + KeepAlivePeriod + "] [CleanSession=" + CleanSession + "]"; + } + } +} diff --git a/Source/MQTTnet/Serializer/IMqttPacketSerializer.cs b/Source/MQTTnet/Serializer/IMqttPacketSerializer.cs index e3066f9f..47d1cd8e 100644 --- a/Source/MQTTnet/Serializer/IMqttPacketSerializer.cs +++ b/Source/MQTTnet/Serializer/IMqttPacketSerializer.cs @@ -6,8 +6,6 @@ namespace MQTTnet.Serializer { public interface IMqttPacketSerializer { - MqttProtocolVersion ProtocolVersion { get; set; } - ArraySegment Serialize(MqttBasePacket mqttPacket); MqttBasePacket Deserialize(ReceivedMqttPacket receivedMqttPacket); diff --git a/Source/MQTTnet/Serializer/MqttPacketBodyReader.cs b/Source/MQTTnet/Serializer/MqttPacketBodyReader.cs index f0e1b562..ad3d789b 100644 --- a/Source/MQTTnet/Serializer/MqttPacketBodyReader.cs +++ b/Source/MQTTnet/Serializer/MqttPacketBodyReader.cs @@ -6,6 +6,7 @@ namespace MQTTnet.Serializer public class MqttPacketBodyReader { private readonly byte[] _buffer; + private readonly int _initialOffset; private readonly int _length; private int _offset; @@ -13,6 +14,7 @@ namespace MQTTnet.Serializer public MqttPacketBodyReader(byte[] buffer, int offset, int length) { _buffer = buffer; + _initialOffset = offset; _offset = offset; _length = length; } @@ -21,6 +23,11 @@ namespace MQTTnet.Serializer public bool EndOfStream => _offset == _length; + public void Seek(int position) + { + _offset = _initialOffset + position; + } + public byte ReadByte() { ValidateReceiveBuffer(1); diff --git a/Source/MQTTnet/Serializer/MqttPacketSerializerAdapter.cs b/Source/MQTTnet/Serializer/MqttPacketSerializerAdapter.cs new file mode 100644 index 00000000..9a681e90 --- /dev/null +++ b/Source/MQTTnet/Serializer/MqttPacketSerializerAdapter.cs @@ -0,0 +1,123 @@ +using System; +using MQTTnet.Adapter; +using MQTTnet.Exceptions; +using MQTTnet.Packets; + +namespace MQTTnet.Serializer +{ + public class MqttPacketSerializerAdapter + { + public MqttPacketSerializerAdapter() + { + } + + public MqttPacketSerializerAdapter(MqttProtocolVersion protocolVersion) + { + UseProtocolVersion(protocolVersion); + } + + public MqttProtocolVersion? ProtocolVersion { get; private set; } + + public IMqttPacketSerializer Serializer { get; private set; } + + public ArraySegment Serialize(MqttBasePacket packet) + { + if (Serializer == null) + { + throw new InvalidOperationException("Protocol version not set or detected."); + } + + return Serializer.Serialize(packet); + } + + public MqttBasePacket Deserialize(ReceivedMqttPacket receivedMqttPacket) + { + if (Serializer == null) + { + throw new InvalidOperationException("Protocol version nor set or detected."); + } + + return Serializer.Deserialize(receivedMqttPacket); + } + + public void FreeBuffer() + { + Serializer?.FreeBuffer(); + } + + public void DetectProtocolVersion(ReceivedMqttPacket receivedMqttPacket) + { + var protocolVersion = ParseProtocolVersion(receivedMqttPacket); + receivedMqttPacket.Body.Seek(0); + + UseProtocolVersion(protocolVersion); + } + + private void UseProtocolVersion(MqttProtocolVersion protocolVersion) + { + ProtocolVersion = protocolVersion; + + switch (protocolVersion) + { + case MqttProtocolVersion.V500: + { + Serializer = new MqttV500PacketSerializer(); + break; + } + + case MqttProtocolVersion.V311: + { + Serializer = new MqttV311PacketSerializer(); + break; + } + + case MqttProtocolVersion.V310: + { + + Serializer = new MqttV310PacketSerializer(); + break; + } + + default: + { + throw new NotSupportedException(); + } + } + } + + private MqttProtocolVersion ParseProtocolVersion(ReceivedMqttPacket receivedMqttPacket) + { + if (receivedMqttPacket == null) throw new ArgumentNullException(nameof(receivedMqttPacket)); + + var protocolName = receivedMqttPacket.Body.ReadStringWithLengthPrefix(); + var protocolLevel = receivedMqttPacket.Body.ReadByte(); + + if (protocolName == "MQTT") + { + if (protocolLevel == 5) + { + return MqttProtocolVersion.V500; + } + + if (protocolLevel == 4) + { + return MqttProtocolVersion.V311; + } + + throw new MqttProtocolViolationException($"Protocol level '{protocolLevel}' not supported."); + } + + if (protocolName == "MQIsdp") + { + if (protocolLevel == 3) + { + return MqttProtocolVersion.V310; + } + + throw new MqttProtocolViolationException($"Protocol level '{protocolLevel}' not supported."); + } + + throw new MqttProtocolViolationException($"Protocol '{protocolName}' not supported."); + } + } +} diff --git a/Source/MQTTnet/Serializer/MqttPacketWriter.cs b/Source/MQTTnet/Serializer/MqttPacketWriter.cs index 67fd4672..10ec4f43 100644 --- a/Source/MQTTnet/Serializer/MqttPacketWriter.cs +++ b/Source/MQTTnet/Serializer/MqttPacketWriter.cs @@ -100,10 +100,10 @@ namespace MQTTnet.Serializer Length = 5; } - public void Seek(int offset) + public void Seek(int position) { - EnsureCapacity(offset); - _position = offset; + EnsureCapacity(position); + _position = position; } public byte[] GetBuffer() diff --git a/Source/MQTTnet/Serializer/MqttProtocolVersion.cs b/Source/MQTTnet/Serializer/MqttProtocolVersion.cs index 15e698d3..576f7df7 100644 --- a/Source/MQTTnet/Serializer/MqttProtocolVersion.cs +++ b/Source/MQTTnet/Serializer/MqttProtocolVersion.cs @@ -2,7 +2,8 @@ { public enum MqttProtocolVersion { - V311, - V310 + V310 = 3, + V311 = 4, + V500 = 5 } } diff --git a/Source/MQTTnet/Serializer/MqttPacketSerializer.cs b/Source/MQTTnet/Serializer/MqttV310PacketSerializer.cs similarity index 79% rename from Source/MQTTnet/Serializer/MqttPacketSerializer.cs rename to Source/MQTTnet/Serializer/MqttV310PacketSerializer.cs index f1eb790a..572feb9b 100644 --- a/Source/MQTTnet/Serializer/MqttPacketSerializer.cs +++ b/Source/MQTTnet/Serializer/MqttV310PacketSerializer.cs @@ -4,17 +4,16 @@ using MQTTnet.Protocol; using System; using System.Linq; using MQTTnet.Adapter; +using MQTTnet.Packets.V3; namespace MQTTnet.Serializer { - public class MqttPacketSerializer : IMqttPacketSerializer + public class MqttV310PacketSerializer : IMqttPacketSerializer { private const int FixedHeaderSize = 1; private readonly MqttPacketWriter _packetWriter = new MqttPacketWriter(); - public MqttProtocolVersion ProtocolVersion { get; set; } = MqttProtocolVersion.V311; - public ArraySegment Serialize(MqttBasePacket packet) { if (packet == null) throw new ArgumentNullException(nameof(packet)); @@ -52,8 +51,8 @@ namespace MQTTnet.Serializer switch ((MqttControlPacketType)controlPacketType) { - case MqttControlPacketType.Connect: return DeserializeConnect(receivedMqttPacket.Body); - case MqttControlPacketType.ConnAck: return DeserializeConnAck(receivedMqttPacket.Body); + case MqttControlPacketType.Connect: return DeserializeConnectPacket(receivedMqttPacket.Body); + case MqttControlPacketType.ConnAck: return DeserializeConnAckPacket(receivedMqttPacket.Body); case MqttControlPacketType.Disconnect: return new MqttDisconnectPacket(); case MqttControlPacketType.Publish: return DeserializePublish(receivedMqttPacket); case MqttControlPacketType.PubAck: return DeserializePubAck(receivedMqttPacket.Body); @@ -63,7 +62,7 @@ namespace MQTTnet.Serializer case MqttControlPacketType.PingReq: return new MqttPingReqPacket(); case MqttControlPacketType.PingResp: return new MqttPingRespPacket(); case MqttControlPacketType.Subscribe: return DeserializeSubscribe(receivedMqttPacket.Body); - case MqttControlPacketType.SubAck: return DeserializeSubAck(receivedMqttPacket.Body); + case MqttControlPacketType.SubAck: return DeserializeSubAckPacket(receivedMqttPacket.Body); case MqttControlPacketType.Unsubscibe: return DeserializeUnsubscribe(receivedMqttPacket.Body); case MqttControlPacketType.UnsubAck: return DeserializeUnsubAck(receivedMqttPacket.Body); @@ -80,20 +79,20 @@ namespace MQTTnet.Serializer { switch (packet) { - case MqttConnectPacket connectPacket: return Serialize(connectPacket, packetWriter); - case MqttConnAckPacket connAckPacket: return Serialize(connAckPacket, packetWriter); + case MqttV3ConnectPacket connectPacket: return SerializeConnectPacket(connectPacket, packetWriter); + case MqttV3ConnAckPacket connAckPacket: return SerializeConnAckPacket(connAckPacket, packetWriter); case MqttDisconnectPacket _: return SerializeEmptyPacket(MqttControlPacketType.Disconnect); case MqttPingReqPacket _: return SerializeEmptyPacket(MqttControlPacketType.PingReq); case MqttPingRespPacket _: return SerializeEmptyPacket(MqttControlPacketType.PingResp); - case MqttPublishPacket publishPacket: return Serialize(publishPacket, packetWriter); - case MqttPubAckPacket pubAckPacket: return Serialize(pubAckPacket, packetWriter); - case MqttPubRecPacket pubRecPacket: return Serialize(pubRecPacket, packetWriter); - case MqttPubRelPacket pubRelPacket: return Serialize(pubRelPacket, packetWriter); - case MqttPubCompPacket pubCompPacket: return Serialize(pubCompPacket, packetWriter); - case MqttSubscribePacket subscribePacket: return Serialize(subscribePacket, packetWriter); - case MqttSubAckPacket subAckPacket: return Serialize(subAckPacket, packetWriter); - case MqttUnsubscribePacket unsubscribePacket: return Serialize(unsubscribePacket, packetWriter); - case MqttUnsubAckPacket unsubAckPacket: return Serialize(unsubAckPacket, packetWriter); + case MqttPublishPacket publishPacket: return SerializePublishPacket(publishPacket, packetWriter); + case MqttPubAckPacket pubAckPacket: return SerializePubAckPacket(pubAckPacket, packetWriter); + case MqttPubRecPacket pubRecPacket: return SerializePubRecPacket(pubRecPacket, packetWriter); + case MqttPubRelPacket pubRelPacket: return SerializePubRelPacket(pubRelPacket, packetWriter); + case MqttPubCompPacket pubCompPacket: return SerializePubCompPacket(pubCompPacket, packetWriter); + case MqttSubscribePacket subscribePacket: return SerializeSubscribePacket(subscribePacket, packetWriter); + case MqttSubAckPacket subAckPacket: return SerializeSubAckPacket(subAckPacket, packetWriter); + case MqttUnsubscribePacket unsubscribePacket: return SerializeUnsubscribePacket(unsubscribePacket, packetWriter); + case MqttUnsubAckPacket unsubAckPacket: return SerializeUnsubAckPacket(unsubAckPacket, packetWriter); default: throw new MqttProtocolViolationException("Packet type invalid."); } } @@ -213,49 +212,23 @@ namespace MQTTnet.Serializer return packet; } - private static MqttBasePacket DeserializeConnect(MqttPacketBodyReader body) + private MqttBasePacket DeserializeConnectPacket(MqttPacketBodyReader body) { ThrowIfBodyIsEmpty(body); - var protocolName = body.ReadStringWithLengthPrefix(); - - MqttProtocolVersion protocolVersion; - if (protocolName == "MQTT") + var packet = new MqttV3ConnectPacket { - var protocolLevel = body.ReadByte(); - if (protocolLevel != 4) - { - throw new MqttProtocolViolationException($"Protocol level ({protocolLevel}) not supported for MQTT 3.1.1."); - } - - protocolVersion = MqttProtocolVersion.V311; - } - else if (protocolName == "MQIsdp") - { - var protocolLevel = body.ReadByte(); - if (protocolLevel != 3) - { - throw new MqttProtocolViolationException($"Protocol level ({protocolLevel}) not supported for MQTT 3.1."); - } - - protocolVersion = MqttProtocolVersion.V310; - } - else - { - throw new MqttProtocolViolationException($"Protocol name ({protocolName}) is not supported."); - } - + ProtocolName = body.ReadStringWithLengthPrefix(), + ProtocolLevel = body.ReadByte() + }; + var connectFlags = body.ReadByte(); if ((connectFlags & 0x1) > 0) { throw new MqttProtocolViolationException("The first bit of the Connect Flags must be set to 0."); } - var packet = new MqttConnectPacket - { - ProtocolVersion = protocolVersion, - CleanSession = (connectFlags & 0x2) > 0 - }; + packet.CleanSession = (connectFlags & 0x2) > 0; var willFlag = (connectFlags & 0x4) > 0; var willQoS = (connectFlags & 0x18) >> 3; @@ -291,7 +264,7 @@ namespace MQTTnet.Serializer return packet; } - private static MqttBasePacket DeserializeSubAck(MqttPacketBodyReader body) + private static MqttBasePacket DeserializeSubAckPacket(MqttPacketBodyReader body) { ThrowIfBodyIsEmpty(body); @@ -308,25 +281,19 @@ namespace MQTTnet.Serializer return packet; } - private MqttBasePacket DeserializeConnAck(MqttPacketBodyReader body) + protected virtual MqttBasePacket DeserializeConnAckPacket(MqttPacketBodyReader body) { ThrowIfBodyIsEmpty(body); - var packet = new MqttConnAckPacket(); - - var acknowledgeFlags = body.ReadByte(); - - if (ProtocolVersion == MqttProtocolVersion.V311) - { - packet.IsSessionPresent = (acknowledgeFlags & 0x1) > 0; - } + var packet = new MqttV3ConnAckPacket(); + body.ReadByte(); // Reserved. packet.ConnectReturnCode = (MqttConnectReturnCode)body.ReadByte(); return packet; } - private static void ValidateConnectPacket(MqttConnectPacket packet) + protected void ValidateConnectPacket(MqttV3ConnectPacket packet) { if (packet == null) throw new ArgumentNullException(nameof(packet)); @@ -345,21 +312,12 @@ namespace MQTTnet.Serializer } } - private byte Serialize(MqttConnectPacket packet, MqttPacketWriter packetWriter) + protected virtual byte SerializeConnectPacket(MqttV3ConnectPacket packet, MqttPacketWriter packetWriter) { ValidateConnectPacket(packet); - // Write variable header - if (ProtocolVersion == MqttProtocolVersion.V311) - { - packetWriter.WriteWithLengthPrefix("MQTT"); - packetWriter.Write(4); // 3.1.2.2 Protocol Level 4 - } - else - { - packetWriter.WriteWithLengthPrefix("MQIsdp"); - packetWriter.Write(3); // Protocol Level 3 - } + packetWriter.WriteWithLengthPrefix("MQIsdp"); + packetWriter.Write(3); // Protocol Level 3 byte connectFlags = 0x0; if (packet.CleanSession) @@ -416,33 +374,15 @@ namespace MQTTnet.Serializer return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.Connect); } - private byte Serialize(MqttConnAckPacket packet, MqttPacketWriter packetWriter) + protected virtual byte SerializeConnAckPacket(MqttV3ConnAckPacket packet, MqttPacketWriter packetWriter) { - if (ProtocolVersion == MqttProtocolVersion.V310) - { - packetWriter.Write(0); - } - else if (ProtocolVersion == MqttProtocolVersion.V311) - { - byte connectAcknowledgeFlags = 0x0; - if (packet.IsSessionPresent) - { - connectAcknowledgeFlags |= 0x1; - } - - packetWriter.Write(connectAcknowledgeFlags); - } - else - { - throw new MqttProtocolViolationException("Protocol version not supported."); - } - + packetWriter.Write(0); // Reserved. packetWriter.Write((byte)packet.ConnectReturnCode); return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.ConnAck); } - private static byte Serialize(MqttPubRelPacket packet, MqttPacketWriter packetWriter) + private static byte SerializePubRelPacket(MqttPubRelPacket packet, MqttPacketWriter packetWriter) { if (!packet.PacketIdentifier.HasValue) { @@ -454,7 +394,7 @@ namespace MQTTnet.Serializer return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.PubRel, 0x02); } - private static byte Serialize(MqttPublishPacket packet, MqttPacketWriter packetWriter) + private static byte SerializePublishPacket(MqttPublishPacket packet, MqttPacketWriter packetWriter) { ValidatePublishPacket(packet); @@ -499,7 +439,7 @@ namespace MQTTnet.Serializer return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.Publish, fixedHeader); } - private static byte Serialize(MqttPubAckPacket packet, MqttPacketWriter packetWriter) + private static byte SerializePubAckPacket(MqttPubAckPacket packet, MqttPacketWriter packetWriter) { if (!packet.PacketIdentifier.HasValue) { @@ -511,7 +451,7 @@ namespace MQTTnet.Serializer return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.PubAck); } - private static byte Serialize(MqttPubRecPacket packet, MqttPacketWriter packetWriter) + private static byte SerializePubRecPacket(MqttPubRecPacket packet, MqttPacketWriter packetWriter) { if (!packet.PacketIdentifier.HasValue) { @@ -523,7 +463,7 @@ namespace MQTTnet.Serializer return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.PubRec); } - private static byte Serialize(MqttPubCompPacket packet, MqttPacketWriter packetWriter) + private static byte SerializePubCompPacket(MqttPubCompPacket packet, MqttPacketWriter packetWriter) { if (!packet.PacketIdentifier.HasValue) { @@ -535,7 +475,7 @@ namespace MQTTnet.Serializer return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.PubComp); } - private static byte Serialize(MqttSubscribePacket packet, MqttPacketWriter packetWriter) + private static byte SerializeSubscribePacket(MqttSubscribePacket packet, MqttPacketWriter packetWriter) { if (!packet.TopicFilters.Any()) throw new MqttProtocolViolationException("At least one topic filter must be set [MQTT-3.8.3-3]."); @@ -558,7 +498,7 @@ namespace MQTTnet.Serializer return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.Subscribe, 0x02); } - private static byte Serialize(MqttSubAckPacket packet, MqttPacketWriter packetWriter) + private static byte SerializeSubAckPacket(MqttSubAckPacket packet, MqttPacketWriter packetWriter) { if (!packet.PacketIdentifier.HasValue) { @@ -578,7 +518,7 @@ namespace MQTTnet.Serializer return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.SubAck); } - private static byte Serialize(MqttUnsubscribePacket packet, MqttPacketWriter packetWriter) + private static byte SerializeUnsubscribePacket(MqttUnsubscribePacket packet, MqttPacketWriter packetWriter) { if (!packet.TopicFilters.Any()) throw new MqttProtocolViolationException("At least one topic filter must be set [MQTT-3.10.3-2]."); @@ -600,7 +540,7 @@ namespace MQTTnet.Serializer return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.Unsubscibe, 0x02); } - private static byte Serialize(MqttUnsubAckPacket packet, MqttPacketWriter packetWriter) + private static byte SerializeUnsubAckPacket(MqttUnsubAckPacket packet, MqttPacketWriter packetWriter) { if (!packet.PacketIdentifier.HasValue) { @@ -617,7 +557,7 @@ namespace MQTTnet.Serializer } // ReSharper disable once ParameterOnlyUsedForPreconditionCheck.Local - private static void ThrowIfBodyIsEmpty(MqttPacketBodyReader body) + protected static void ThrowIfBodyIsEmpty(MqttPacketBodyReader body) { if (body == null || body.Length == 0) { diff --git a/Source/MQTTnet/Serializer/MqttV311PacketSerializer.cs b/Source/MQTTnet/Serializer/MqttV311PacketSerializer.cs new file mode 100644 index 00000000..f68ae02f --- /dev/null +++ b/Source/MQTTnet/Serializer/MqttV311PacketSerializer.cs @@ -0,0 +1,100 @@ +using MQTTnet.Exceptions; +using MQTTnet.Packets; +using MQTTnet.Packets.V3; +using MQTTnet.Protocol; + +namespace MQTTnet.Serializer +{ + public class MqttV311PacketSerializer : MqttV310PacketSerializer + { + protected override byte SerializeConnectPacket(MqttV3ConnectPacket packet, MqttPacketWriter packetWriter) + { + ValidateConnectPacket(packet); + + packetWriter.WriteWithLengthPrefix("MQTT"); + packetWriter.Write(4); // 3.1.2.2 Protocol Level 4 + + byte connectFlags = 0x0; + if (packet.CleanSession) + { + connectFlags |= 0x2; + } + + if (packet.WillMessage != null) + { + connectFlags |= 0x4; + connectFlags |= (byte)((byte)packet.WillMessage.QualityOfServiceLevel << 3); + + if (packet.WillMessage.Retain) + { + connectFlags |= 0x20; + } + } + + if (packet.Password != null && packet.Username == null) + { + throw new MqttProtocolViolationException("If the User Name Flag is set to 0, the Password Flag MUST be set to 0 [MQTT-3.1.2-22]."); + } + + if (packet.Password != null) + { + connectFlags |= 0x40; + } + + if (packet.Username != null) + { + connectFlags |= 0x80; + } + + packetWriter.Write(connectFlags); + packetWriter.Write(packet.KeepAlivePeriod); + packetWriter.WriteWithLengthPrefix(packet.ClientId); + + if (packet.WillMessage != null) + { + packetWriter.WriteWithLengthPrefix(packet.WillMessage.Topic); + packetWriter.WriteWithLengthPrefix(packet.WillMessage.Payload); + } + + if (packet.Username != null) + { + packetWriter.WriteWithLengthPrefix(packet.Username); + } + + if (packet.Password != null) + { + packetWriter.WriteWithLengthPrefix(packet.Password); + } + + return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.Connect); + } + + protected override byte SerializeConnAckPacket(MqttV3ConnAckPacket packet, MqttPacketWriter packetWriter) + { + byte connectAcknowledgeFlags = 0x0; + if (packet.IsSessionPresent) + { + connectAcknowledgeFlags |= 0x1; + } + + packetWriter.Write(connectAcknowledgeFlags); + packetWriter.Write((byte)packet.ConnectReturnCode); + + return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.ConnAck); + } + + protected override MqttBasePacket DeserializeConnAckPacket(MqttPacketBodyReader body) + { + ThrowIfBodyIsEmpty(body); + + var packet = new MqttV3ConnAckPacket(); + + var acknowledgeFlags = body.ReadByte(); + + packet.IsSessionPresent = (acknowledgeFlags & 0x1) > 0; + packet.ConnectReturnCode = (MqttConnectReturnCode)body.ReadByte(); + + return packet; + } + } +} diff --git a/Source/MQTTnet/Serializer/MqttV500PacketSerializer.cs b/Source/MQTTnet/Serializer/MqttV500PacketSerializer.cs new file mode 100644 index 00000000..a50a9033 --- /dev/null +++ b/Source/MQTTnet/Serializer/MqttV500PacketSerializer.cs @@ -0,0 +1,24 @@ +using System; +using MQTTnet.Adapter; +using MQTTnet.Packets; + +namespace MQTTnet.Serializer +{ + public class MqttV500PacketSerializer : IMqttPacketSerializer + { + public ArraySegment Serialize(MqttBasePacket mqttPacket) + { + throw new NotImplementedException(); + } + + public MqttBasePacket Deserialize(ReceivedMqttPacket receivedMqttPacket) + { + throw new NotImplementedException(); + } + + public void FreeBuffer() + { + throw new NotImplementedException(); + } + } +} diff --git a/Source/MQTTnet/Server/IMqttClientSession.cs b/Source/MQTTnet/Server/IMqttClientSession.cs index 9fdb0eb9..e0dd214c 100644 --- a/Source/MQTTnet/Server/IMqttClientSession.cs +++ b/Source/MQTTnet/Server/IMqttClientSession.cs @@ -14,7 +14,7 @@ namespace MQTTnet.Server void EnqueueApplicationMessage(MqttClientSession senderClientSession, MqttPublishPacket publishPacket); void ClearPendingApplicationMessages(); - Task RunAsync(MqttConnectPacket connectPacket, IMqttChannelAdapter adapter); + Task RunAsync(MqttApplicationMessage willMessage, int keepAliveInterval, IMqttChannelAdapter adapter); void Stop(MqttClientDisconnectType disconnectType); Task SubscribeAsync(IList topicFilters); diff --git a/Source/MQTTnet/Server/MqttClientSession.cs b/Source/MQTTnet/Server/MqttClientSession.cs index de3a7a48..d5448e80 100644 --- a/Source/MQTTnet/Server/MqttClientSession.cs +++ b/Source/MQTTnet/Server/MqttClientSession.cs @@ -61,21 +61,20 @@ namespace MQTTnet.Server status.ClientId = ClientId; status.IsConnected = _adapter != null; status.Endpoint = _adapter?.Endpoint; - status.ProtocolVersion = _adapter?.PacketSerializer?.ProtocolVersion; + status.ProtocolVersion = _adapter?.PacketSerializerAdapter?.ProtocolVersion; status.PendingApplicationMessagesCount = _pendingPacketsQueue.Count; status.LastPacketReceived = _keepAliveMonitor.LastPacketReceived; status.LastNonKeepAlivePacketReceived = _keepAliveMonitor.LastNonKeepAlivePacketReceived; } - public Task RunAsync(MqttConnectPacket connectPacket, IMqttChannelAdapter adapter) + public Task RunAsync(MqttApplicationMessage willMessage, int keepAlivePeriod, IMqttChannelAdapter adapter) { - _workerTask = RunInternalAsync(connectPacket, adapter); + _workerTask = RunInternalAsync(willMessage, keepAlivePeriod, adapter); return _workerTask; } - private async Task RunInternalAsync(MqttConnectPacket connectPacket, IMqttChannelAdapter adapter) + private async Task RunInternalAsync(MqttApplicationMessage willMessage, int keepAlivePeriod, IMqttChannelAdapter adapter) { - if (connectPacket == null) throw new ArgumentNullException(nameof(connectPacket)); if (adapter == null) throw new ArgumentNullException(nameof(adapter)); try @@ -94,10 +93,10 @@ namespace MQTTnet.Server //endworkaround _wasCleanDisconnect = false; - _willMessage = connectPacket.WillMessage; + _willMessage = willMessage; _pendingPacketsQueue.Start(adapter, _cancellationTokenSource.Token); - _keepAliveMonitor.Start(connectPacket.KeepAlivePeriod, _cancellationTokenSource.Token); + _keepAliveMonitor.Start(keepAlivePeriod, _cancellationTokenSource.Token); while (!_cancellationTokenSource.IsCancellationRequested) { diff --git a/Source/MQTTnet/Server/MqttClientSessionsManager.cs b/Source/MQTTnet/Server/MqttClientSessionsManager.cs index d8f4f141..a6a6b9df 100644 --- a/Source/MQTTnet/Server/MqttClientSessionsManager.cs +++ b/Source/MQTTnet/Server/MqttClientSessionsManager.cs @@ -9,6 +9,7 @@ using MQTTnet.Diagnostics; using MQTTnet.Exceptions; using MQTTnet.Internal; using MQTTnet.Packets; +using MQTTnet.Packets.V3; using MQTTnet.Protocol; namespace MQTTnet.Server @@ -217,21 +218,18 @@ namespace MQTTnet.Server return; } - if (!(firstPacket is MqttConnectPacket connectPacket)) + if (!(firstPacket is MqttV3ConnectPacket connectPacket)) { throw new MqttProtocolViolationException("The first packet from a client must be a 'CONNECT' packet [MQTT-3.1.0-1]."); } clientId = connectPacket.ClientId; - // Switch to the required protocol version before sending any response. - clientAdapter.PacketSerializer.ProtocolVersion = connectPacket.ProtocolVersion; - var connectReturnCode = ValidateConnection(connectPacket, clientAdapter); if (connectReturnCode != MqttConnectReturnCode.ConnectionAccepted) { await clientAdapter.SendPacketAsync( - new MqttConnAckPacket + new MqttV3ConnAckPacket { ConnectReturnCode = connectReturnCode }, @@ -244,7 +242,7 @@ namespace MQTTnet.Server var clientSession = result.Session; await clientAdapter.SendPacketAsync( - new MqttConnAckPacket + new MqttV3ConnAckPacket { ConnectReturnCode = connectReturnCode, IsSessionPresent = result.IsExistingSession @@ -253,7 +251,7 @@ namespace MQTTnet.Server Server.OnClientConnected(clientId); - await clientSession.RunAsync(connectPacket, clientAdapter).ConfigureAwait(false); + await clientSession.RunAsync(connectPacket.WillMessage, connectPacket.KeepAlivePeriod, clientAdapter).ConfigureAwait(false); } catch (OperationCanceledException) { @@ -271,7 +269,7 @@ namespace MQTTnet.Server } } - private MqttConnectReturnCode ValidateConnection(MqttConnectPacket connectPacket, IMqttChannelAdapter clientAdapter) + private MqttConnectReturnCode ValidateConnection(MqttV3ConnectPacket connectPacket, IMqttChannelAdapter clientAdapter) { if (_options.ConnectionValidator == null) { @@ -289,7 +287,7 @@ namespace MQTTnet.Server return context.ReturnCode; } - private PrepareClientSessionResult PrepareClientSession(MqttConnectPacket connectPacket) + private PrepareClientSessionResult PrepareClientSession(MqttV3ConnectPacket connectPacket) { lock (_sessions) { diff --git a/Tests/MQTTnet.AspNetCore.Tests/ReaderExtensionsTest.cs b/Tests/MQTTnet.AspNetCore.Tests/ReaderExtensionsTest.cs index e6cdef23..332f3eaa 100644 --- a/Tests/MQTTnet.AspNetCore.Tests/ReaderExtensionsTest.cs +++ b/Tests/MQTTnet.AspNetCore.Tests/ReaderExtensionsTest.cs @@ -1,3 +1,4 @@ +#if NETCOREAPP using System.Buffers; using Microsoft.VisualStudio.TestTools.UnitTesting; using MQTTnet.AspNetCore; @@ -12,7 +13,7 @@ namespace MQTTnet.AspNetCore.Tests [TestMethod] public void TestTryDeserialize() { - var serializer = new MqttPacketSerializer(); + var serializer = new MqttV311PacketSerializer(); var buffer = serializer.Serialize(new MqttPublishPacket() {Topic = "a", Payload = new byte[5]}); @@ -43,3 +44,4 @@ namespace MQTTnet.AspNetCore.Tests } } } +#endif \ No newline at end of file diff --git a/Tests/MQTTnet.Benchmarks/ChannelAdapterBenchmark.cs b/Tests/MQTTnet.Benchmarks/ChannelAdapterBenchmark.cs index 7f068d60..5ec3c0f6 100644 --- a/Tests/MQTTnet.Benchmarks/ChannelAdapterBenchmark.cs +++ b/Tests/MQTTnet.Benchmarks/ChannelAdapterBenchmark.cs @@ -26,7 +26,8 @@ namespace MQTTnet.Benchmarks .Build(); _packet = message.ToPublishPacket(); - var serializer = new MqttPacketSerializer(); + var serializer = new MqttPacketSerializerAdapter(MqttProtocolVersion.V311); + var serializedPacket = Join(serializer.Serialize(_packet)); _iterations = 10000; diff --git a/Tests/MQTTnet.Benchmarks/SerializerBenchmark.cs b/Tests/MQTTnet.Benchmarks/SerializerBenchmark.cs index 9b855805..ed7e5631 100644 --- a/Tests/MQTTnet.Benchmarks/SerializerBenchmark.cs +++ b/Tests/MQTTnet.Benchmarks/SerializerBenchmark.cs @@ -19,7 +19,7 @@ namespace MQTTnet.Benchmarks { private MqttBasePacket _packet; private ArraySegment _serializedPacket; - private MqttPacketSerializer _serializer; + private IMqttPacketSerializer _serializer; [GlobalSetup] public void Setup() @@ -29,7 +29,7 @@ namespace MQTTnet.Benchmarks .Build(); _packet = message.ToPublishPacket(); - _serializer = new MqttPacketSerializer(); + _serializer = new MqttV311PacketSerializer(); _serializedPacket = _serializer.Serialize(_packet); } diff --git a/Tests/MQTTnet.Core.Tests/MqttClientTests.cs b/Tests/MQTTnet.Core.Tests/MqttClientTests.cs index 212e1abb..12131a27 100644 --- a/Tests/MQTTnet.Core.Tests/MqttClientTests.cs +++ b/Tests/MQTTnet.Core.Tests/MqttClientTests.cs @@ -8,6 +8,7 @@ using MQTTnet.Client; using MQTTnet.Diagnostics; using MQTTnet.Exceptions; using MQTTnet.Packets; +using MQTTnet.Packets.V3; namespace MQTTnet.Core.Tests { @@ -50,7 +51,7 @@ namespace MQTTnet.Core.Tests Task.Run(async () => { var connect = await channel2.ReceivePacketAsync(TimeSpan.Zero, CancellationToken.None); - await channel2.SendPacketAsync(new MqttConnAckPacket() { ConnectReturnCode = Protocol.MqttConnectReturnCode.ConnectionRefusedNotAuthorized }, CancellationToken.None); + await channel2.SendPacketAsync(new MqttV3ConnAckPacket() { ConnectReturnCode = Protocol.MqttConnectReturnCode.ConnectionRefusedNotAuthorized }, CancellationToken.None); }); diff --git a/Tests/MQTTnet.Core.Tests/MqttKeepAliveMonitorTests.cs b/Tests/MQTTnet.Core.Tests/MqttKeepAliveMonitorTests.cs index b18904a0..a36555a4 100644 --- a/Tests/MQTTnet.Core.Tests/MqttKeepAliveMonitorTests.cs +++ b/Tests/MQTTnet.Core.Tests/MqttKeepAliveMonitorTests.cs @@ -77,7 +77,7 @@ namespace MQTTnet.Core.Tests throw new NotSupportedException(); } - public Task RunAsync(MqttConnectPacket connectPacket, IMqttChannelAdapter adapter) + public Task RunAsync(MqttApplicationMessage willMessage, int keepAlivePeriod, IMqttChannelAdapter adapter) { throw new NotSupportedException(); } diff --git a/Tests/MQTTnet.Core.Tests/MqttPacketSerializerTests.cs b/Tests/MQTTnet.Core.Tests/MqttPacketSerializerTests.cs index c3a75f8e..8f56f0d8 100644 --- a/Tests/MQTTnet.Core.Tests/MqttPacketSerializerTests.cs +++ b/Tests/MQTTnet.Core.Tests/MqttPacketSerializerTests.cs @@ -7,6 +7,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using MQTTnet.Adapter; using MQTTnet.Internal; using MQTTnet.Packets; +using MQTTnet.Packets.V3; using MQTTnet.Protocol; using MQTTnet.Serializer; @@ -18,7 +19,7 @@ namespace MQTTnet.Core.Tests [TestMethod] public void SerializeV310_MqttConnectPacket() { - var p = new MqttConnectPacket + var p = new MqttV3ConnectPacket { ClientId = "XYZ", Password = "PASS", @@ -33,7 +34,7 @@ namespace MQTTnet.Core.Tests [TestMethod] public void SerializeV311_MqttConnectPacket() { - var p = new MqttConnectPacket + var p = new MqttV3ConnectPacket { ClientId = "XYZ", Password = "PASS", @@ -48,7 +49,7 @@ namespace MQTTnet.Core.Tests [TestMethod] public void SerializeV311_MqttConnectPacketWithWillMessage() { - var p = new MqttConnectPacket + var p = new MqttV3ConnectPacket { ClientId = "XYZ", Password = "PASS", @@ -70,7 +71,7 @@ namespace MQTTnet.Core.Tests [TestMethod] public void DeserializeV311_MqttConnectPacket() { - var p = new MqttConnectPacket + var p = new MqttV3ConnectPacket { ClientId = "XYZ", Password = "PASS", @@ -85,7 +86,7 @@ namespace MQTTnet.Core.Tests [TestMethod] public void DeserializeV311_MqttConnectPacketWithWillMessage() { - var p = new MqttConnectPacket + var p = new MqttV3ConnectPacket { ClientId = "XYZ", Password = "PASS", @@ -107,7 +108,7 @@ namespace MQTTnet.Core.Tests [TestMethod] public void SerializeV311_MqttConnAckPacket() { - var p = new MqttConnAckPacket + var p = new MqttV3ConnAckPacket { IsSessionPresent = true, ConnectReturnCode = MqttConnectReturnCode.ConnectionRefusedNotAuthorized @@ -119,7 +120,7 @@ namespace MQTTnet.Core.Tests [TestMethod] public void SerializeV310_MqttConnAckPacket() { - var p = new MqttConnAckPacket + var p = new MqttV3ConnAckPacket { ConnectReturnCode = MqttConnectReturnCode.ConnectionRefusedNotAuthorized }; @@ -130,7 +131,7 @@ namespace MQTTnet.Core.Tests [TestMethod] public void DeserializeV311_MqttConnAckPacket() { - var p = new MqttConnAckPacket + var p = new MqttV3ConnAckPacket { IsSessionPresent = true, ConnectReturnCode = MqttConnectReturnCode.ConnectionRefusedNotAuthorized @@ -142,7 +143,7 @@ namespace MQTTnet.Core.Tests [TestMethod] public void DeserializeV310_MqttConnAckPacket() { - var p = new MqttConnAckPacket + var p = new MqttV3ConnAckPacket { ConnectReturnCode = MqttConnectReturnCode.ConnectionRefusedNotAuthorized }; @@ -153,7 +154,7 @@ namespace MQTTnet.Core.Tests [TestMethod] public void Serialize_LargePacket() { - var serializer = new MqttPacketSerializer { ProtocolVersion = MqttProtocolVersion.V311 }; + var serializer = new MqttV311PacketSerializer(); const int payloadLength = 80000; @@ -509,7 +510,20 @@ namespace MQTTnet.Core.Tests private static void SerializeAndCompare(MqttBasePacket packet, string expectedBase64Value, MqttProtocolVersion protocolVersion = MqttProtocolVersion.V311) { - var serializer = new MqttPacketSerializer { ProtocolVersion = protocolVersion }; + IMqttPacketSerializer serializer; + if (protocolVersion == MqttProtocolVersion.V311) + { + serializer = new MqttV311PacketSerializer(); + } + else if (protocolVersion == MqttProtocolVersion.V310) + { + serializer = new MqttV310PacketSerializer(); + } + else + { + throw new NotSupportedException(); + } + var data = serializer.Serialize(packet); Assert.AreEqual(expectedBase64Value, Convert.ToBase64String(Join(data))); @@ -517,7 +531,19 @@ namespace MQTTnet.Core.Tests private static void DeserializeAndCompare(MqttBasePacket packet, string expectedBase64Value, MqttProtocolVersion protocolVersion = MqttProtocolVersion.V311) { - var serializer = new MqttPacketSerializer { ProtocolVersion = protocolVersion }; + IMqttPacketSerializer serializer; + if (protocolVersion == MqttProtocolVersion.V311) + { + serializer = new MqttV311PacketSerializer(); + } + else if (protocolVersion == MqttProtocolVersion.V310) + { + serializer = new MqttV310PacketSerializer(); + } + else + { + throw new NotSupportedException(); + } var buffer1 = serializer.Serialize(packet); @@ -541,7 +567,19 @@ namespace MQTTnet.Core.Tests private static T Roundtrip(T packet, MqttProtocolVersion protocolVersion = MqttProtocolVersion.V311) where T : MqttBasePacket { - var serializer = new MqttPacketSerializer { ProtocolVersion = protocolVersion }; + IMqttPacketSerializer serializer; + if (protocolVersion == MqttProtocolVersion.V311) + { + serializer = new MqttV311PacketSerializer(); + } + else if (protocolVersion == MqttProtocolVersion.V310) + { + serializer = new MqttV310PacketSerializer(); + } + else + { + throw new NotSupportedException(); + } var buffer1 = serializer.Serialize(packet); diff --git a/Tests/MQTTnet.Core.Tests/MqttServerTests.cs b/Tests/MQTTnet.Core.Tests/MqttServerTests.cs index 0c34bdeb..941e86b2 100644 --- a/Tests/MQTTnet.Core.Tests/MqttServerTests.cs +++ b/Tests/MQTTnet.Core.Tests/MqttServerTests.cs @@ -171,14 +171,14 @@ namespace MQTTnet.Core.Tests [TestMethod] public async Task MqttServer_Publish_MultipleClients() { - var serverAdapter = new MqttTcpServerAdapter(new MqttNetLogger().CreateChildLogger()); - var s = new MqttFactory().CreateMqttServer(new[] { serverAdapter }, new MqttNetLogger()); + var s = new MqttFactory().CreateMqttServer(); var receivedMessagesCount = 0; var locked = new object(); var clientOptions = new MqttClientOptionsBuilder() .WithTcpServer("localhost") .Build(); + var clientOptions2 = new MqttClientOptionsBuilder() .WithTcpServer("localhost") .Build(); diff --git a/Tests/MQTTnet.Core.Tests/TestMqttCommunicationAdapter.cs b/Tests/MQTTnet.Core.Tests/TestMqttCommunicationAdapter.cs index 9375f07d..1902de4e 100644 --- a/Tests/MQTTnet.Core.Tests/TestMqttCommunicationAdapter.cs +++ b/Tests/MQTTnet.Core.Tests/TestMqttCommunicationAdapter.cs @@ -16,7 +16,7 @@ namespace MQTTnet.Core.Tests public string Endpoint { get; } - public IMqttPacketSerializer PacketSerializer { get; } = new MqttPacketSerializer(); + public MqttPacketSerializerAdapter PacketSerializerAdapter { get; } = new MqttPacketSerializerAdapter(MqttProtocolVersion.V311); public event EventHandler ReadingPacketStarted; public event EventHandler ReadingPacketCompleted; diff --git a/Tests/MQTTnet.TestApp.UniversalWindows/MainPage.xaml.cs b/Tests/MQTTnet.TestApp.UniversalWindows/MainPage.xaml.cs index b557fe02..633f351e 100644 --- a/Tests/MQTTnet.TestApp.UniversalWindows/MainPage.xaml.cs +++ b/Tests/MQTTnet.TestApp.UniversalWindows/MainPage.xaml.cs @@ -14,6 +14,7 @@ using MQTTnet.Extensions.ManagedClient; using MQTTnet.Extensions.Rpc; using MQTTnet.Implementations; using MQTTnet.Protocol; +using MQTTnet.Serializer; using MQTTnet.Server; using MqttClientConnectedEventArgs = MQTTnet.Client.MqttClientConnectedEventArgs; using MqttClientDisconnectedEventArgs = MQTTnet.Client.MqttClientDisconnectedEventArgs; @@ -87,7 +88,8 @@ namespace MQTTnet.TestApp.UniversalWindows var options = new MqttClientOptions { - ClientId = ClientId.Text + ClientId = ClientId.Text, + ProtocolVersion = MqttProtocolVersion.V500 }; if (UseTcp.IsChecked == true) From 8d784554146b8bbc4e28273e8af73875132bdb66 Mon Sep 17 00:00:00 2001 From: Christian Kratky Date: Fri, 16 Nov 2018 22:18:59 +0100 Subject: [PATCH 002/124] Fix breaking changes. --- Source/MQTTnet.AspnetCore/MqttConnectionContext.cs | 2 +- Tests/MQTTnet.AspNetCore.Tests/MqttConnectionContextTest.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Source/MQTTnet.AspnetCore/MqttConnectionContext.cs b/Source/MQTTnet.AspnetCore/MqttConnectionContext.cs index 56c094c0..1b54ef55 100644 --- a/Source/MQTTnet.AspnetCore/MqttConnectionContext.cs +++ b/Source/MQTTnet.AspnetCore/MqttConnectionContext.cs @@ -107,7 +107,7 @@ namespace MQTTnet.AspNetCore public async Task SendPacketAsync(MqttBasePacket packet, CancellationToken cancellationToken) { - var buffer = PacketSerializer.Serialize(packet).AsMemory(); + var buffer = PacketSerializerAdapter.Serialize(packet).AsMemory(); var output = Connection.Transport.Output; await _writerSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false); diff --git a/Tests/MQTTnet.AspNetCore.Tests/MqttConnectionContextTest.cs b/Tests/MQTTnet.AspNetCore.Tests/MqttConnectionContextTest.cs index 699aa65b..2ec621de 100644 --- a/Tests/MQTTnet.AspNetCore.Tests/MqttConnectionContextTest.cs +++ b/Tests/MQTTnet.AspNetCore.Tests/MqttConnectionContextTest.cs @@ -17,7 +17,7 @@ namespace MQTTnet.AspNetCore.Tests [TestMethod] public async Task TestReceivePacketAsyncThrowsWhenReaderCompleted() { - var serializer = new MqttPacketSerializer {}; + var serializer = new MqttPacketSerializerAdapter(MqttProtocolVersion.V311); var pipe = new DuplexPipeMockup(); var connection = new DefaultConnectionContext(); connection.Transport = pipe; @@ -31,7 +31,7 @@ namespace MQTTnet.AspNetCore.Tests [TestMethod] public async Task TestParallelWrites() { - var serializer = new MqttPacketSerializer { }; + var serializer = new MqttPacketSerializerAdapter(MqttProtocolVersion.V311); var pipe = new DuplexPipeMockup(); var connection = new DefaultConnectionContext(); connection.Transport = pipe; From ace25af0e85aa646845791e63c9989cb94dfc997 Mon Sep 17 00:00:00 2001 From: Christian Kratky Date: Fri, 16 Nov 2018 23:11:26 +0100 Subject: [PATCH 003/124] Added package properties. --- Source/MQTTnet/Client/MqttClient.cs | 2 +- Source/MQTTnet/MQTTnet.csproj | 4 +- Source/MQTTnet/Packets/MqttConnectPacket.cs | 32 ++++++- .../MQTTnet/Packets/Properties/ByteValue.cs | 22 +++++ .../Packets/Properties/IPropertyValue.cs | 9 ++ Source/MQTTnet/Packets/Properties/Property.cs | 9 ++ .../Packets/Properties/PropertyType.cs | 33 +++++++ .../MQTTnet/Packets/Properties/StringValue.cs | 22 +++++ .../Packets/Properties/TwoByteIntegerValue.cs | 22 +++++ .../MQTTnet/Packets/V3/MqttV3ConnectPacket.cs | 26 ----- .../Serializer/MqttPacketSerializerAdapter.cs | 6 +- Source/MQTTnet/Serializer/MqttPacketWriter.cs | 31 ++++-- .../Serializer/MqttV310PacketSerializer.cs | 15 ++- .../Serializer/MqttV311PacketSerializer.cs | 2 +- .../Serializer/MqttV500PacketSerializer.cs | 94 ++++++++++++++++--- .../Server/MqttClientSessionsManager.cs | 6 +- .../MqttPacketSerializerTests.cs | 10 +- .../MQTTnet.Core.Tests/RoundtripTimeTests.cs | 2 + 18 files changed, 285 insertions(+), 62 deletions(-) create mode 100644 Source/MQTTnet/Packets/Properties/ByteValue.cs create mode 100644 Source/MQTTnet/Packets/Properties/IPropertyValue.cs create mode 100644 Source/MQTTnet/Packets/Properties/Property.cs create mode 100644 Source/MQTTnet/Packets/Properties/PropertyType.cs create mode 100644 Source/MQTTnet/Packets/Properties/StringValue.cs create mode 100644 Source/MQTTnet/Packets/Properties/TwoByteIntegerValue.cs delete mode 100644 Source/MQTTnet/Packets/V3/MqttV3ConnectPacket.cs diff --git a/Source/MQTTnet/Client/MqttClient.cs b/Source/MQTTnet/Client/MqttClient.cs index ce91a487..9e073cbb 100644 --- a/Source/MQTTnet/Client/MqttClient.cs +++ b/Source/MQTTnet/Client/MqttClient.cs @@ -196,7 +196,7 @@ namespace MQTTnet.Client private async Task AuthenticateAsync(MqttApplicationMessage willApplicationMessage, CancellationToken cancellationToken) { - var connectPacket = new MqttV3ConnectPacket + var connectPacket = new MqttConnectPacket { ClientId = _options.ClientId, Username = _options.Credentials?.Username, diff --git a/Source/MQTTnet/MQTTnet.csproj b/Source/MQTTnet/MQTTnet.csproj index 5c9ba727..68099169 100644 --- a/Source/MQTTnet/MQTTnet.csproj +++ b/Source/MQTTnet/MQTTnet.csproj @@ -64,7 +64,9 @@ - + + + \ No newline at end of file diff --git a/Source/MQTTnet/Packets/MqttConnectPacket.cs b/Source/MQTTnet/Packets/MqttConnectPacket.cs index 1faa8c20..7c0b7a00 100644 --- a/Source/MQTTnet/Packets/MqttConnectPacket.cs +++ b/Source/MQTTnet/Packets/MqttConnectPacket.cs @@ -1,6 +1,34 @@ -namespace MQTTnet.Packets +using System.Collections.Generic; +using MQTTnet.Packets.Properties; + +namespace MQTTnet.Packets { - public abstract class MqttConnectPacket : MqttBasePacket + public class MqttConnectPacket : MqttBasePacket { + public string ProtocolName { get; set; } + + public byte ProtocolLevel { get; set; } + + public string ClientId { get; set; } + + public string Username { get; set; } + + public string Password { get; set; } + + public ushort KeepAlivePeriod { get; set; } + + /// + /// MQTTv5: Also called "Clean Start". + /// + public bool CleanSession { get; set; } + + public MqttApplicationMessage WillMessage { get; set; } + + public List Properties { get; set; } + + public override string ToString() + { + return "Connect: [ProtocolLevel=" + ProtocolLevel + "] [ClientId=" + ClientId + "] [Username=" + Username + "] [Password=" + Password + "] [KeepAlivePeriod=" + KeepAlivePeriod + "] [CleanSession=" + CleanSession + "]"; + } } } diff --git a/Source/MQTTnet/Packets/Properties/ByteValue.cs b/Source/MQTTnet/Packets/Properties/ByteValue.cs new file mode 100644 index 00000000..5c34cb90 --- /dev/null +++ b/Source/MQTTnet/Packets/Properties/ByteValue.cs @@ -0,0 +1,22 @@ +using System; +using MQTTnet.Serializer; + +namespace MQTTnet.Packets.Properties +{ + public class ByteValue : IPropertyValue + { + public ByteValue(byte value) + { + Value = value; + } + + public byte Value { get; } + + public void WriteTo(MqttPacketWriter writer) + { + if (writer == null) throw new ArgumentNullException(nameof(writer)); + + writer.Write(Value); + } + } +} diff --git a/Source/MQTTnet/Packets/Properties/IPropertyValue.cs b/Source/MQTTnet/Packets/Properties/IPropertyValue.cs new file mode 100644 index 00000000..005134fc --- /dev/null +++ b/Source/MQTTnet/Packets/Properties/IPropertyValue.cs @@ -0,0 +1,9 @@ +using MQTTnet.Serializer; + +namespace MQTTnet.Packets.Properties +{ + public interface IPropertyValue + { + void WriteTo(MqttPacketWriter writer); + } +} diff --git a/Source/MQTTnet/Packets/Properties/Property.cs b/Source/MQTTnet/Packets/Properties/Property.cs new file mode 100644 index 00000000..1fc71d6d --- /dev/null +++ b/Source/MQTTnet/Packets/Properties/Property.cs @@ -0,0 +1,9 @@ +namespace MQTTnet.Packets.Properties +{ + public class Property + { + public PropertyType Type { get; set; } + + public IPropertyValue Value { get; set; } + } +} diff --git a/Source/MQTTnet/Packets/Properties/PropertyType.cs b/Source/MQTTnet/Packets/Properties/PropertyType.cs new file mode 100644 index 00000000..8d60aad7 --- /dev/null +++ b/Source/MQTTnet/Packets/Properties/PropertyType.cs @@ -0,0 +1,33 @@ +namespace MQTTnet.Packets.Properties +{ + public enum PropertyType + { + PayloadFormatIndicator = 1, + MessageExpiryInterval = 2, + ContentType = 3, + ResponseTopic = 8, + CorrelationData = 9, + SubscriptionIdentifier = 11, + SessionExpiryInterval = 17, + AssignedClientIdentifer = 18, + ServerKeepAlive = 19, + AuthenticationMethod = 21, + AuthenticationData = 22, + RequestProblemInformation = 23, + WillDelayInterval = 24, + RequestResponseInformation = 25, + ResponseInformation = 26, + ServerReference = 28, + ReasonString = 31, + ReceiveMaximum = 33, + TopicAliasMaximum = 34, + TopicAlias = 35, + MaximumQoS = 36, + RetainAvailable = 37, + UserProperty = 38, + MaximumPacketSize = 39, + WildcardSubscriptionAvailable = 40, + SubscriptionIdentifierAvailable = 41, + SharedSubscriptionAvailable = 42 + } +} diff --git a/Source/MQTTnet/Packets/Properties/StringValue.cs b/Source/MQTTnet/Packets/Properties/StringValue.cs new file mode 100644 index 00000000..fc614ad1 --- /dev/null +++ b/Source/MQTTnet/Packets/Properties/StringValue.cs @@ -0,0 +1,22 @@ +using System; +using MQTTnet.Serializer; + +namespace MQTTnet.Packets.Properties +{ + public class StringValue : IPropertyValue + { + public StringValue(string value) + { + Value = value; + } + + public string Value { get; } + + public void WriteTo(MqttPacketWriter writer) + { + if (writer == null) throw new ArgumentNullException(nameof(writer)); + + writer.WriteWithLengthPrefix(Value); + } + } +} diff --git a/Source/MQTTnet/Packets/Properties/TwoByteIntegerValue.cs b/Source/MQTTnet/Packets/Properties/TwoByteIntegerValue.cs new file mode 100644 index 00000000..79fc919c --- /dev/null +++ b/Source/MQTTnet/Packets/Properties/TwoByteIntegerValue.cs @@ -0,0 +1,22 @@ +using System; +using MQTTnet.Serializer; + +namespace MQTTnet.Packets.Properties +{ + public class TwoByteIntegerValue : IPropertyValue + { + public TwoByteIntegerValue(ushort value) + { + Value = value; + } + + public ushort Value { get; } + + public void WriteTo(MqttPacketWriter writer) + { + if (writer == null) throw new ArgumentNullException(nameof(writer)); + + throw new NotImplementedException(); + } + } +} diff --git a/Source/MQTTnet/Packets/V3/MqttV3ConnectPacket.cs b/Source/MQTTnet/Packets/V3/MqttV3ConnectPacket.cs deleted file mode 100644 index 2434d594..00000000 --- a/Source/MQTTnet/Packets/V3/MqttV3ConnectPacket.cs +++ /dev/null @@ -1,26 +0,0 @@ -namespace MQTTnet.Packets.V3 -{ - public class MqttV3ConnectPacket : MqttConnectPacket - { - public string ProtocolName { get; set; } - - public byte ProtocolLevel { get; set; } - - public string ClientId { get; set; } - - public string Username { get; set; } - - public string Password { get; set; } - - public ushort KeepAlivePeriod { get; set; } - - public bool CleanSession { get; set; } - - public MqttApplicationMessage WillMessage { get; set; } - - public override string ToString() - { - return "Connect: [ProtocolLevel=" + ProtocolLevel + "] [ClientId=" + ClientId + "] [Username=" + Username + "] [Password=" + Password + "] [KeepAlivePeriod=" + KeepAlivePeriod + "] [CleanSession=" + CleanSession + "]"; - } - } -} diff --git a/Source/MQTTnet/Serializer/MqttPacketSerializerAdapter.cs b/Source/MQTTnet/Serializer/MqttPacketSerializerAdapter.cs index 9a681e90..4a4a164a 100644 --- a/Source/MQTTnet/Serializer/MqttPacketSerializerAdapter.cs +++ b/Source/MQTTnet/Serializer/MqttPacketSerializerAdapter.cs @@ -34,7 +34,7 @@ namespace MQTTnet.Serializer { if (Serializer == null) { - throw new InvalidOperationException("Protocol version nor set or detected."); + throw new InvalidOperationException("Protocol version not set or detected."); } return Serializer.Deserialize(receivedMqttPacket); @@ -48,6 +48,10 @@ namespace MQTTnet.Serializer public void DetectProtocolVersion(ReceivedMqttPacket receivedMqttPacket) { var protocolVersion = ParseProtocolVersion(receivedMqttPacket); + + // Reset the position of the stream beacuse the protocol version is part of + // the regular CONNECT packet. So it will not properly deserialized if this + // data is missing. receivedMqttPacket.Body.Seek(0); UseProtocolVersion(protocolVersion); diff --git a/Source/MQTTnet/Serializer/MqttPacketWriter.cs b/Source/MQTTnet/Serializer/MqttPacketWriter.cs index 10ec4f43..beb146fb 100644 --- a/Source/MQTTnet/Serializer/MqttPacketWriter.cs +++ b/Source/MQTTnet/Serializer/MqttPacketWriter.cs @@ -27,9 +27,9 @@ namespace MQTTnet.Serializer return (byte)fixedHeader; } - public static ArraySegment EncodeRemainingLength(int length) + public static ArraySegment EncodeVariableByteInteger(int value) { - if (length <= 0) + if (value <= 0) { return new ArraySegment(new byte[1], 0, 1); } @@ -37,7 +37,7 @@ namespace MQTTnet.Serializer var buffer = new byte[4]; var bufferOffset = 0; - var x = length; + var x = value; do { var encodedByte = x % 128; @@ -54,6 +54,11 @@ namespace MQTTnet.Serializer return new ArraySegment(buffer, 0, bufferOffset); } + public void WriteVariableLengthInteger(int value) + { + Write(EncodeVariableByteInteger(value)); + } + public void WriteWithLengthPrefix(string value) { WriteWithLengthPrefix(Encoding.UTF8.GetBytes(value ?? string.Empty)); @@ -85,16 +90,30 @@ namespace MQTTnet.Serializer IncreasePostition(1); } - public void Write(byte[] array, int offset, int count) + public void Write(byte[] buffer, int offset, int count) { - if (array == null) throw new ArgumentNullException(nameof(array)); + if (buffer == null) throw new ArgumentNullException(nameof(buffer)); EnsureAdditionalCapacity(count); - Array.Copy(array, offset, _buffer, _position, count); + Array.Copy(buffer, offset, _buffer, _position, count); IncreasePostition(count); } + public void Write(ArraySegment buffer) + { + if (buffer == null) throw new ArgumentNullException(nameof(buffer)); + + Write(buffer.Array, buffer.Offset, buffer.Count); + } + + public void Write(MqttPacketWriter propertyWriter) + { + if (propertyWriter == null) throw new ArgumentNullException(nameof(propertyWriter)); + + Write(propertyWriter._buffer, 0, propertyWriter.Length); + } + public void Reset() { Length = 5; diff --git a/Source/MQTTnet/Serializer/MqttV310PacketSerializer.cs b/Source/MQTTnet/Serializer/MqttV310PacketSerializer.cs index 572feb9b..bb72db14 100644 --- a/Source/MQTTnet/Serializer/MqttV310PacketSerializer.cs +++ b/Source/MQTTnet/Serializer/MqttV310PacketSerializer.cs @@ -25,7 +25,7 @@ namespace MQTTnet.Serializer var fixedHeader = SerializePacket(packet, _packetWriter); var remainingLength = _packetWriter.Length - 5; - var remainingLengthBuffer = MqttPacketWriter.EncodeRemainingLength(remainingLength); + var remainingLengthBuffer = MqttPacketWriter.EncodeVariableByteInteger(remainingLength); var headerSize = FixedHeaderSize + remainingLengthBuffer.Count; var headerOffset = 5 - headerSize; @@ -79,7 +79,7 @@ namespace MQTTnet.Serializer { switch (packet) { - case MqttV3ConnectPacket connectPacket: return SerializeConnectPacket(connectPacket, packetWriter); + case MqttConnectPacket connectPacket: return SerializeConnectPacket(connectPacket, packetWriter); case MqttV3ConnAckPacket connAckPacket: return SerializeConnAckPacket(connAckPacket, packetWriter); case MqttDisconnectPacket _: return SerializeEmptyPacket(MqttControlPacketType.Disconnect); case MqttPingReqPacket _: return SerializeEmptyPacket(MqttControlPacketType.PingReq); @@ -216,7 +216,7 @@ namespace MQTTnet.Serializer { ThrowIfBodyIsEmpty(body); - var packet = new MqttV3ConnectPacket + var packet = new MqttConnectPacket { ProtocolName = body.ReadStringWithLengthPrefix(), ProtocolLevel = body.ReadByte() @@ -293,7 +293,7 @@ namespace MQTTnet.Serializer return packet; } - protected void ValidateConnectPacket(MqttV3ConnectPacket packet) + protected void ValidateConnectPacket(MqttConnectPacket packet) { if (packet == null) throw new ArgumentNullException(nameof(packet)); @@ -301,6 +301,11 @@ namespace MQTTnet.Serializer { throw new MqttProtocolViolationException("CleanSession must be set if ClientId is empty [MQTT-3.1.3-7]."); } + + if (packet.Properties != null) + { + throw new MqttProtocolViolationException("Properties are only supported in MQTT Version 5."); + } } // ReSharper disable once ParameterOnlyUsedForPreconditionCheck.Local @@ -312,7 +317,7 @@ namespace MQTTnet.Serializer } } - protected virtual byte SerializeConnectPacket(MqttV3ConnectPacket packet, MqttPacketWriter packetWriter) + protected virtual byte SerializeConnectPacket(MqttConnectPacket packet, MqttPacketWriter packetWriter) { ValidateConnectPacket(packet); diff --git a/Source/MQTTnet/Serializer/MqttV311PacketSerializer.cs b/Source/MQTTnet/Serializer/MqttV311PacketSerializer.cs index f68ae02f..f124c7d1 100644 --- a/Source/MQTTnet/Serializer/MqttV311PacketSerializer.cs +++ b/Source/MQTTnet/Serializer/MqttV311PacketSerializer.cs @@ -7,7 +7,7 @@ namespace MQTTnet.Serializer { public class MqttV311PacketSerializer : MqttV310PacketSerializer { - protected override byte SerializeConnectPacket(MqttV3ConnectPacket packet, MqttPacketWriter packetWriter) + protected override byte SerializeConnectPacket(MqttConnectPacket packet, MqttPacketWriter packetWriter) { ValidateConnectPacket(packet); diff --git a/Source/MQTTnet/Serializer/MqttV500PacketSerializer.cs b/Source/MQTTnet/Serializer/MqttV500PacketSerializer.cs index a50a9033..44ec36be 100644 --- a/Source/MQTTnet/Serializer/MqttV500PacketSerializer.cs +++ b/Source/MQTTnet/Serializer/MqttV500PacketSerializer.cs @@ -1,24 +1,96 @@ using System; using MQTTnet.Adapter; +using MQTTnet.Exceptions; using MQTTnet.Packets; +using MQTTnet.Protocol; namespace MQTTnet.Serializer { - public class MqttV500PacketSerializer : IMqttPacketSerializer + public class MqttV500PacketSerializer : MqttV311PacketSerializer { - public ArraySegment Serialize(MqttBasePacket mqttPacket) - { - throw new NotImplementedException(); - } + //public ArraySegment Serialize(MqttBasePacket mqttPacket) + //{ + // throw new NotImplementedException(); + //} - public MqttBasePacket Deserialize(ReceivedMqttPacket receivedMqttPacket) - { - throw new NotImplementedException(); - } + //public MqttBasePacket Deserialize(ReceivedMqttPacket receivedMqttPacket) + //{ + // throw new NotImplementedException(); + //} - public void FreeBuffer() + protected override byte SerializeConnectPacket(MqttConnectPacket packet, MqttPacketWriter packetWriter) { - throw new NotImplementedException(); + ValidateConnectPacket(packet); + + packetWriter.WriteWithLengthPrefix("MQTT"); + packetWriter.Write(5); // [3.1.2.2 Protocol Version] + + byte connectFlags = 0x0; + if (packet.CleanSession) + { + connectFlags |= 0x2; + } + + if (packet.WillMessage != null) + { + connectFlags |= 0x4; + connectFlags |= (byte)((byte)packet.WillMessage.QualityOfServiceLevel << 3); + + if (packet.WillMessage.Retain) + { + connectFlags |= 0x20; + } + } + + if (packet.Password != null && packet.Username == null) + { + throw new MqttProtocolViolationException("If the User Name Flag is set to 0, the Password Flag MUST be set to 0 [MQTT-3.1.2-22]."); + } + + if (packet.Password != null) + { + connectFlags |= 0x40; + } + + if (packet.Username != null) + { + connectFlags |= 0x80; + } + + packetWriter.Write(connectFlags); + packetWriter.Write(packet.KeepAlivePeriod); + packetWriter.WriteWithLengthPrefix(packet.ClientId); + + if (packet.WillMessage != null) + { + packetWriter.WriteWithLengthPrefix(packet.WillMessage.Topic); + packetWriter.WriteWithLengthPrefix(packet.WillMessage.Payload); + } + + if (packet.Username != null) + { + packetWriter.WriteWithLengthPrefix(packet.Username); + } + + if (packet.Password != null) + { + packetWriter.WriteWithLengthPrefix(packet.Password); + } + + if (packet.Properties != null) + { + var propertyWriter = new MqttPacketWriter(); + foreach (var property in packet.Properties) + { + propertyWriter.Write((byte)property.Key); + property.Value.WriteTo(propertyWriter); + } + + packetWriter.WriteVariableLengthInteger(propertyWriter.Length); + packetWriter.Write(propertyWriter); + } + + return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.Connect); } } } diff --git a/Source/MQTTnet/Server/MqttClientSessionsManager.cs b/Source/MQTTnet/Server/MqttClientSessionsManager.cs index f180f685..a06e969e 100644 --- a/Source/MQTTnet/Server/MqttClientSessionsManager.cs +++ b/Source/MQTTnet/Server/MqttClientSessionsManager.cs @@ -218,7 +218,7 @@ namespace MQTTnet.Server return; } - if (!(firstPacket is MqttV3ConnectPacket connectPacket)) + if (!(firstPacket is MqttConnectPacket connectPacket)) { throw new MqttProtocolViolationException("The first packet from a client must be a 'CONNECT' packet [MQTT-3.1.0-1]."); } @@ -269,7 +269,7 @@ namespace MQTTnet.Server } } - private MqttConnectReturnCode ValidateConnection(MqttV3ConnectPacket connectPacket, IMqttChannelAdapter clientAdapter) + private MqttConnectReturnCode ValidateConnection(MqttConnectPacket connectPacket, IMqttChannelAdapter clientAdapter) { if (_options.ConnectionValidator == null) { @@ -287,7 +287,7 @@ namespace MQTTnet.Server return context.ReturnCode; } - private PrepareClientSessionResult PrepareClientSession(MqttV3ConnectPacket connectPacket) + private PrepareClientSessionResult PrepareClientSession(MqttConnectPacket connectPacket) { lock (_sessions) { diff --git a/Tests/MQTTnet.Core.Tests/MqttPacketSerializerTests.cs b/Tests/MQTTnet.Core.Tests/MqttPacketSerializerTests.cs index 8f56f0d8..5df4b69b 100644 --- a/Tests/MQTTnet.Core.Tests/MqttPacketSerializerTests.cs +++ b/Tests/MQTTnet.Core.Tests/MqttPacketSerializerTests.cs @@ -19,7 +19,7 @@ namespace MQTTnet.Core.Tests [TestMethod] public void SerializeV310_MqttConnectPacket() { - var p = new MqttV3ConnectPacket + var p = new MqttConnectPacket { ClientId = "XYZ", Password = "PASS", @@ -34,7 +34,7 @@ namespace MQTTnet.Core.Tests [TestMethod] public void SerializeV311_MqttConnectPacket() { - var p = new MqttV3ConnectPacket + var p = new MqttConnectPacket { ClientId = "XYZ", Password = "PASS", @@ -49,7 +49,7 @@ namespace MQTTnet.Core.Tests [TestMethod] public void SerializeV311_MqttConnectPacketWithWillMessage() { - var p = new MqttV3ConnectPacket + var p = new MqttConnectPacket { ClientId = "XYZ", Password = "PASS", @@ -71,7 +71,7 @@ namespace MQTTnet.Core.Tests [TestMethod] public void DeserializeV311_MqttConnectPacket() { - var p = new MqttV3ConnectPacket + var p = new MqttConnectPacket { ClientId = "XYZ", Password = "PASS", @@ -86,7 +86,7 @@ namespace MQTTnet.Core.Tests [TestMethod] public void DeserializeV311_MqttConnectPacketWithWillMessage() { - var p = new MqttV3ConnectPacket + var p = new MqttConnectPacket { ClientId = "XYZ", Password = "PASS", diff --git a/Tests/MQTTnet.Core.Tests/RoundtripTimeTests.cs b/Tests/MQTTnet.Core.Tests/RoundtripTimeTests.cs index c405f5fe..a8e4ce93 100644 --- a/Tests/MQTTnet.Core.Tests/RoundtripTimeTests.cs +++ b/Tests/MQTTnet.Core.Tests/RoundtripTimeTests.cs @@ -36,6 +36,8 @@ namespace MQTTnet.Core.Tests var times = new List(); var stopwatch = Stopwatch.StartNew(); + await Task.Delay(1000); + for (var i = 0; i < 100; i++) { response = new TaskCompletionSource(); From 2fbb44ec6643e1b6545b9139b1ec3aed81079ee8 Mon Sep 17 00:00:00 2001 From: Christian Kratky Date: Sun, 18 Nov 2018 21:54:40 +0100 Subject: [PATCH 004/124] Add more property implementations. --- Source/MQTTnet/Packets/MqttConnectPacket.cs | 2 +- .../AssignedClientIdentifierProperty.cs | 10 ++++++ .../Properties/AuthenticationDataProperty.cs | 12 +++++++ .../AuthenticationMethodProperty.cs | 10 ++++++ .../BaseTypes/BinaryDataProperty.cs | 31 +++++++++++++++++++ .../ByteProperty.cs} | 7 +++-- .../BaseTypes/FourByteIntegerProperty.cs | 27 ++++++++++++++++ .../StringProperty.cs} | 7 +++-- .../TwoByteIntegerProperty.cs} | 11 ++++--- .../BaseTypes/VariableByteIntegerProperty.cs | 26 ++++++++++++++++ .../Packets/Properties/ContentTypeProperty.cs | 10 ++++++ .../Properties/CorrelationDataProperty.cs | 12 +++++++ .../{IPropertyValue.cs => IProperty.cs} | 4 ++- .../MessageExpiryIntervalProperty.cs | 12 +++++++ .../PayloadFormatIndicatorProperty.cs | 10 ++++++ Source/MQTTnet/Packets/Properties/Property.cs | 9 ------ .../{PropertyType.cs => PropertyID.cs} | 2 +- .../RequestProblemInformationProperty.cs | 10 ++++++ .../RequestResponseInformationProperty.cs | 10 ++++++ .../Properties/ResponseTopicProperty.cs | 10 ++++++ .../Properties/ServerKeepAliveProperty.cs | 12 +++++++ .../SessionExpiryIntervalProperty.cs | 12 +++++++ .../SubscriptionIdentifierProperty.cs | 12 +++++++ .../Properties/WillDelayIntervalProperty.cs | 12 +++++++ Source/MQTTnet/Serializer/MqttPacketWriter.cs | 8 +++-- .../Serializer/MqttV310PacketSerializer.cs | 2 +- .../Serializer/MqttV500PacketSerializer.cs | 6 ++-- 27 files changed, 270 insertions(+), 26 deletions(-) create mode 100644 Source/MQTTnet/Packets/Properties/AssignedClientIdentifierProperty.cs create mode 100644 Source/MQTTnet/Packets/Properties/AuthenticationDataProperty.cs create mode 100644 Source/MQTTnet/Packets/Properties/AuthenticationMethodProperty.cs create mode 100644 Source/MQTTnet/Packets/Properties/BaseTypes/BinaryDataProperty.cs rename Source/MQTTnet/Packets/Properties/{ByteValue.cs => BaseTypes/ByteProperty.cs} (72%) create mode 100644 Source/MQTTnet/Packets/Properties/BaseTypes/FourByteIntegerProperty.cs rename Source/MQTTnet/Packets/Properties/{StringValue.cs => BaseTypes/StringProperty.cs} (71%) rename Source/MQTTnet/Packets/Properties/{TwoByteIntegerValue.cs => BaseTypes/TwoByteIntegerProperty.cs} (54%) create mode 100644 Source/MQTTnet/Packets/Properties/BaseTypes/VariableByteIntegerProperty.cs create mode 100644 Source/MQTTnet/Packets/Properties/ContentTypeProperty.cs create mode 100644 Source/MQTTnet/Packets/Properties/CorrelationDataProperty.cs rename Source/MQTTnet/Packets/Properties/{IPropertyValue.cs => IProperty.cs} (69%) create mode 100644 Source/MQTTnet/Packets/Properties/MessageExpiryIntervalProperty.cs create mode 100644 Source/MQTTnet/Packets/Properties/PayloadFormatIndicatorProperty.cs delete mode 100644 Source/MQTTnet/Packets/Properties/Property.cs rename Source/MQTTnet/Packets/Properties/{PropertyType.cs => PropertyID.cs} (97%) create mode 100644 Source/MQTTnet/Packets/Properties/RequestProblemInformationProperty.cs create mode 100644 Source/MQTTnet/Packets/Properties/RequestResponseInformationProperty.cs create mode 100644 Source/MQTTnet/Packets/Properties/ResponseTopicProperty.cs create mode 100644 Source/MQTTnet/Packets/Properties/ServerKeepAliveProperty.cs create mode 100644 Source/MQTTnet/Packets/Properties/SessionExpiryIntervalProperty.cs create mode 100644 Source/MQTTnet/Packets/Properties/SubscriptionIdentifierProperty.cs create mode 100644 Source/MQTTnet/Packets/Properties/WillDelayIntervalProperty.cs diff --git a/Source/MQTTnet/Packets/MqttConnectPacket.cs b/Source/MQTTnet/Packets/MqttConnectPacket.cs index 7c0b7a00..ec1b953a 100644 --- a/Source/MQTTnet/Packets/MqttConnectPacket.cs +++ b/Source/MQTTnet/Packets/MqttConnectPacket.cs @@ -24,7 +24,7 @@ namespace MQTTnet.Packets public MqttApplicationMessage WillMessage { get; set; } - public List Properties { get; set; } + public List Properties { get; set; } public override string ToString() { diff --git a/Source/MQTTnet/Packets/Properties/AssignedClientIdentifierProperty.cs b/Source/MQTTnet/Packets/Properties/AssignedClientIdentifierProperty.cs new file mode 100644 index 00000000..b7fd3cd7 --- /dev/null +++ b/Source/MQTTnet/Packets/Properties/AssignedClientIdentifierProperty.cs @@ -0,0 +1,10 @@ +namespace MQTTnet.Packets.Properties +{ + public class AssignedClientIdentifierProperty : StringProperty + { + public AssignedClientIdentifierProperty(string value) + : base((byte)PropertyID.AssignedClientIdentifer, value) + { + } + } +} diff --git a/Source/MQTTnet/Packets/Properties/AuthenticationDataProperty.cs b/Source/MQTTnet/Packets/Properties/AuthenticationDataProperty.cs new file mode 100644 index 00000000..021d9e94 --- /dev/null +++ b/Source/MQTTnet/Packets/Properties/AuthenticationDataProperty.cs @@ -0,0 +1,12 @@ +using System; + +namespace MQTTnet.Packets.Properties +{ + public class AuthenticationDataProperty : BinaryDataProperty + { + public AuthenticationDataProperty(ArraySegment data) + : base((byte)PropertyID.AuthenticationData, data) + { + } + } +} diff --git a/Source/MQTTnet/Packets/Properties/AuthenticationMethodProperty.cs b/Source/MQTTnet/Packets/Properties/AuthenticationMethodProperty.cs new file mode 100644 index 00000000..7de63c3d --- /dev/null +++ b/Source/MQTTnet/Packets/Properties/AuthenticationMethodProperty.cs @@ -0,0 +1,10 @@ +namespace MQTTnet.Packets.Properties +{ + public class AuthenticationMethodProperty : StringProperty + { + public AuthenticationMethodProperty(string value) + : base((byte)PropertyID.AuthenticationMethod, value) + { + } + } +} diff --git a/Source/MQTTnet/Packets/Properties/BaseTypes/BinaryDataProperty.cs b/Source/MQTTnet/Packets/Properties/BaseTypes/BinaryDataProperty.cs new file mode 100644 index 00000000..2037b8ef --- /dev/null +++ b/Source/MQTTnet/Packets/Properties/BaseTypes/BinaryDataProperty.cs @@ -0,0 +1,31 @@ +using System; +using MQTTnet.Serializer; + +namespace MQTTnet.Packets.Properties +{ + public class BinaryDataProperty : IProperty + { + public BinaryDataProperty(byte id, ArraySegment data) + { + if (data.Array == null) + { + throw new ArgumentNullException(nameof(data)); + } + + Id = id; + Data = data; + } + + public byte Id { get; } + + public ArraySegment Data { get; } + + public void WriteTo(MqttPacketWriter writer) + { + if (writer == null) throw new ArgumentNullException(nameof(writer)); + + writer.Write((ushort)Data.Count); + writer.Write(Data); + } + } +} diff --git a/Source/MQTTnet/Packets/Properties/ByteValue.cs b/Source/MQTTnet/Packets/Properties/BaseTypes/ByteProperty.cs similarity index 72% rename from Source/MQTTnet/Packets/Properties/ByteValue.cs rename to Source/MQTTnet/Packets/Properties/BaseTypes/ByteProperty.cs index 5c34cb90..e3e41210 100644 --- a/Source/MQTTnet/Packets/Properties/ByteValue.cs +++ b/Source/MQTTnet/Packets/Properties/BaseTypes/ByteProperty.cs @@ -3,13 +3,16 @@ using MQTTnet.Serializer; namespace MQTTnet.Packets.Properties { - public class ByteValue : IPropertyValue + public class ByteProperty : IProperty { - public ByteValue(byte value) + public ByteProperty(byte id, byte value) { + Id = id; Value = value; } + public byte Id { get; } + public byte Value { get; } public void WriteTo(MqttPacketWriter writer) diff --git a/Source/MQTTnet/Packets/Properties/BaseTypes/FourByteIntegerProperty.cs b/Source/MQTTnet/Packets/Properties/BaseTypes/FourByteIntegerProperty.cs new file mode 100644 index 00000000..56aafe34 --- /dev/null +++ b/Source/MQTTnet/Packets/Properties/BaseTypes/FourByteIntegerProperty.cs @@ -0,0 +1,27 @@ +using System; +using MQTTnet.Serializer; + +namespace MQTTnet.Packets.Properties.BaseTypes +{ + public class FourByteIntegerValue : IProperty + { + public FourByteIntegerValue(byte id, uint value) + { + Id = id; + Value = value; + } + + public byte Id { get; } + + public uint Value { get; } + + public void WriteTo(MqttPacketWriter writer) + { + if (writer == null) throw new ArgumentNullException(nameof(writer)); + + // TODO: Check if order must be reversed like for ushort. + var bytes = BitConverter.GetBytes(Value); + writer.Write(bytes, 0, bytes.Length); + } + } +} diff --git a/Source/MQTTnet/Packets/Properties/StringValue.cs b/Source/MQTTnet/Packets/Properties/BaseTypes/StringProperty.cs similarity index 71% rename from Source/MQTTnet/Packets/Properties/StringValue.cs rename to Source/MQTTnet/Packets/Properties/BaseTypes/StringProperty.cs index fc614ad1..b7910698 100644 --- a/Source/MQTTnet/Packets/Properties/StringValue.cs +++ b/Source/MQTTnet/Packets/Properties/BaseTypes/StringProperty.cs @@ -3,13 +3,16 @@ using MQTTnet.Serializer; namespace MQTTnet.Packets.Properties { - public class StringValue : IPropertyValue + public class StringProperty : IProperty { - public StringValue(string value) + public StringProperty(byte id, string value) { + Id = id; Value = value; } + public byte Id { get; } + public string Value { get; } public void WriteTo(MqttPacketWriter writer) diff --git a/Source/MQTTnet/Packets/Properties/TwoByteIntegerValue.cs b/Source/MQTTnet/Packets/Properties/BaseTypes/TwoByteIntegerProperty.cs similarity index 54% rename from Source/MQTTnet/Packets/Properties/TwoByteIntegerValue.cs rename to Source/MQTTnet/Packets/Properties/BaseTypes/TwoByteIntegerProperty.cs index 79fc919c..4ed67766 100644 --- a/Source/MQTTnet/Packets/Properties/TwoByteIntegerValue.cs +++ b/Source/MQTTnet/Packets/Properties/BaseTypes/TwoByteIntegerProperty.cs @@ -1,22 +1,25 @@ using System; using MQTTnet.Serializer; -namespace MQTTnet.Packets.Properties +namespace MQTTnet.Packets.Properties.BaseTypes { - public class TwoByteIntegerValue : IPropertyValue + public class TwoByteIntegerProperty : IProperty { - public TwoByteIntegerValue(ushort value) + public TwoByteIntegerProperty(byte id, ushort value) { + Id = id; Value = value; } + public byte Id { get; } + public ushort Value { get; } public void WriteTo(MqttPacketWriter writer) { if (writer == null) throw new ArgumentNullException(nameof(writer)); - throw new NotImplementedException(); + writer.Write(Value); } } } diff --git a/Source/MQTTnet/Packets/Properties/BaseTypes/VariableByteIntegerProperty.cs b/Source/MQTTnet/Packets/Properties/BaseTypes/VariableByteIntegerProperty.cs new file mode 100644 index 00000000..26eef480 --- /dev/null +++ b/Source/MQTTnet/Packets/Properties/BaseTypes/VariableByteIntegerProperty.cs @@ -0,0 +1,26 @@ +using System; +using MQTTnet.Serializer; + +namespace MQTTnet.Packets.Properties.BaseTypes +{ + public class VariableByteIntegerProperty : IProperty + { + public VariableByteIntegerProperty(byte id, uint value) + { + Id = id; + Value = value; + } + + public byte Id { get; } + + public uint Value { get; } + + public void WriteTo(MqttPacketWriter writer) + { + if (writer == null) throw new ArgumentNullException(nameof(writer)); + + var buffer = MqttPacketWriter.EncodeVariableByteInteger(Value); + writer.Write(buffer); + } + } +} diff --git a/Source/MQTTnet/Packets/Properties/ContentTypeProperty.cs b/Source/MQTTnet/Packets/Properties/ContentTypeProperty.cs new file mode 100644 index 00000000..1e7ec422 --- /dev/null +++ b/Source/MQTTnet/Packets/Properties/ContentTypeProperty.cs @@ -0,0 +1,10 @@ +namespace MQTTnet.Packets.Properties +{ + public class ContentTypeProperty : StringProperty + { + public ContentTypeProperty(string value) + : base((byte)PropertyID.ContentType, value) + { + } + } +} diff --git a/Source/MQTTnet/Packets/Properties/CorrelationDataProperty.cs b/Source/MQTTnet/Packets/Properties/CorrelationDataProperty.cs new file mode 100644 index 00000000..f9104e8c --- /dev/null +++ b/Source/MQTTnet/Packets/Properties/CorrelationDataProperty.cs @@ -0,0 +1,12 @@ +using System; + +namespace MQTTnet.Packets.Properties +{ + public class CorrelationDataProperty : BinaryDataProperty + { + public CorrelationDataProperty(ArraySegment data) + : base((byte)PropertyID.CorrelationData, data) + { + } + } +} diff --git a/Source/MQTTnet/Packets/Properties/IPropertyValue.cs b/Source/MQTTnet/Packets/Properties/IProperty.cs similarity index 69% rename from Source/MQTTnet/Packets/Properties/IPropertyValue.cs rename to Source/MQTTnet/Packets/Properties/IProperty.cs index 005134fc..739e0cd4 100644 --- a/Source/MQTTnet/Packets/Properties/IPropertyValue.cs +++ b/Source/MQTTnet/Packets/Properties/IProperty.cs @@ -2,8 +2,10 @@ namespace MQTTnet.Packets.Properties { - public interface IPropertyValue + public interface IProperty { + byte Id { get; } + void WriteTo(MqttPacketWriter writer); } } diff --git a/Source/MQTTnet/Packets/Properties/MessageExpiryIntervalProperty.cs b/Source/MQTTnet/Packets/Properties/MessageExpiryIntervalProperty.cs new file mode 100644 index 00000000..7062e8b1 --- /dev/null +++ b/Source/MQTTnet/Packets/Properties/MessageExpiryIntervalProperty.cs @@ -0,0 +1,12 @@ +using MQTTnet.Packets.Properties.BaseTypes; + +namespace MQTTnet.Packets.Properties +{ + public class MessageExpiryIntervalProperty : FourByteIntegerValue + { + public MessageExpiryInterval(uint value) + : base((byte)PropertyID.MessageExpiryInterval, value) + { + } + } +} diff --git a/Source/MQTTnet/Packets/Properties/PayloadFormatIndicatorProperty.cs b/Source/MQTTnet/Packets/Properties/PayloadFormatIndicatorProperty.cs new file mode 100644 index 00000000..f30f5b16 --- /dev/null +++ b/Source/MQTTnet/Packets/Properties/PayloadFormatIndicatorProperty.cs @@ -0,0 +1,10 @@ +namespace MQTTnet.Packets.Properties +{ + public class PayloadFormatIndicatorProperty : ByteProperty + { + public PayloadFormatIndicatorProperty(byte value) + : base((byte)PropertyID.PayloadFormatIndicator, value) + { + } + } +} diff --git a/Source/MQTTnet/Packets/Properties/Property.cs b/Source/MQTTnet/Packets/Properties/Property.cs deleted file mode 100644 index 1fc71d6d..00000000 --- a/Source/MQTTnet/Packets/Properties/Property.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace MQTTnet.Packets.Properties -{ - public class Property - { - public PropertyType Type { get; set; } - - public IPropertyValue Value { get; set; } - } -} diff --git a/Source/MQTTnet/Packets/Properties/PropertyType.cs b/Source/MQTTnet/Packets/Properties/PropertyID.cs similarity index 97% rename from Source/MQTTnet/Packets/Properties/PropertyType.cs rename to Source/MQTTnet/Packets/Properties/PropertyID.cs index 8d60aad7..69a809ea 100644 --- a/Source/MQTTnet/Packets/Properties/PropertyType.cs +++ b/Source/MQTTnet/Packets/Properties/PropertyID.cs @@ -1,6 +1,6 @@ namespace MQTTnet.Packets.Properties { - public enum PropertyType + public enum PropertyID { PayloadFormatIndicator = 1, MessageExpiryInterval = 2, diff --git a/Source/MQTTnet/Packets/Properties/RequestProblemInformationProperty.cs b/Source/MQTTnet/Packets/Properties/RequestProblemInformationProperty.cs new file mode 100644 index 00000000..456c9733 --- /dev/null +++ b/Source/MQTTnet/Packets/Properties/RequestProblemInformationProperty.cs @@ -0,0 +1,10 @@ +namespace MQTTnet.Packets.Properties +{ + public class RequestProblemInformationProperty : ByteProperty + { + public RequestProblemInformationProperty(byte value) + : base((byte)PropertyID.RequestProblemInformation, value) + { + } + } +} diff --git a/Source/MQTTnet/Packets/Properties/RequestResponseInformationProperty.cs b/Source/MQTTnet/Packets/Properties/RequestResponseInformationProperty.cs new file mode 100644 index 00000000..9119675b --- /dev/null +++ b/Source/MQTTnet/Packets/Properties/RequestResponseInformationProperty.cs @@ -0,0 +1,10 @@ +namespace MQTTnet.Packets.Properties +{ + public class RequestResponseInformationProperty : ByteProperty + { + public RequestResponseInformationProperty(byte value) + : base((byte)PropertyID.RequestResponseInformation, value) + { + } + } +} diff --git a/Source/MQTTnet/Packets/Properties/ResponseTopicProperty.cs b/Source/MQTTnet/Packets/Properties/ResponseTopicProperty.cs new file mode 100644 index 00000000..ad41bf85 --- /dev/null +++ b/Source/MQTTnet/Packets/Properties/ResponseTopicProperty.cs @@ -0,0 +1,10 @@ +namespace MQTTnet.Packets.Properties +{ + public class ResponseTopicProperty : StringProperty + { + public ResponseTopicProperty(string value) + : base((byte)PropertyID.ResponseTopic, value) + { + } + } +} diff --git a/Source/MQTTnet/Packets/Properties/ServerKeepAliveProperty.cs b/Source/MQTTnet/Packets/Properties/ServerKeepAliveProperty.cs new file mode 100644 index 00000000..a247fb47 --- /dev/null +++ b/Source/MQTTnet/Packets/Properties/ServerKeepAliveProperty.cs @@ -0,0 +1,12 @@ +using MQTTnet.Packets.Properties.BaseTypes; + +namespace MQTTnet.Packets.Properties +{ + public class ServerKeepAliveProperty : TwoByteIntegerProperty + { + public ServerKeepAliveProperty(ushort value) + : base((byte)PropertyID.ServerKeepAlive, value) + { + } + } +} diff --git a/Source/MQTTnet/Packets/Properties/SessionExpiryIntervalProperty.cs b/Source/MQTTnet/Packets/Properties/SessionExpiryIntervalProperty.cs new file mode 100644 index 00000000..f28422e3 --- /dev/null +++ b/Source/MQTTnet/Packets/Properties/SessionExpiryIntervalProperty.cs @@ -0,0 +1,12 @@ +using MQTTnet.Packets.Properties.BaseTypes; + +namespace MQTTnet.Packets.Properties +{ + public class SessionExpiryIntervalProperty : FourByteIntegerValue + { + public SessionExpiryIntervalProperty(uint value) + : base((byte)PropertyID.SessionExpiryInterval, value) + { + } + } +} diff --git a/Source/MQTTnet/Packets/Properties/SubscriptionIdentifierProperty.cs b/Source/MQTTnet/Packets/Properties/SubscriptionIdentifierProperty.cs new file mode 100644 index 00000000..dca6ad58 --- /dev/null +++ b/Source/MQTTnet/Packets/Properties/SubscriptionIdentifierProperty.cs @@ -0,0 +1,12 @@ +using MQTTnet.Packets.Properties.BaseTypes; + +namespace MQTTnet.Packets.Properties +{ + public class SubscriptionIdentifierProperty : VariableByteIntegerProperty + { + public SubscriptionIdentifierProperty(uint value) + : base((byte)PropertyID.SubscriptionIdentifier, value) + { + } + } +} diff --git a/Source/MQTTnet/Packets/Properties/WillDelayIntervalProperty.cs b/Source/MQTTnet/Packets/Properties/WillDelayIntervalProperty.cs new file mode 100644 index 00000000..e285f868 --- /dev/null +++ b/Source/MQTTnet/Packets/Properties/WillDelayIntervalProperty.cs @@ -0,0 +1,12 @@ +using MQTTnet.Packets.Properties.BaseTypes; + +namespace MQTTnet.Packets.Properties +{ + public class WillDelayIntervalProperty : FourByteIntegerValue + { + public WillDelayIntervalProperty(uint value) + : base((byte)PropertyID.WillDelayInterval, value) + { + } + } +} diff --git a/Source/MQTTnet/Serializer/MqttPacketWriter.cs b/Source/MQTTnet/Serializer/MqttPacketWriter.cs index beb146fb..3df5d964 100644 --- a/Source/MQTTnet/Serializer/MqttPacketWriter.cs +++ b/Source/MQTTnet/Serializer/MqttPacketWriter.cs @@ -1,4 +1,5 @@ using System; +using System.Runtime.CompilerServices; using System.Text; using MQTTnet.Protocol; @@ -27,7 +28,7 @@ namespace MQTTnet.Serializer return (byte)fixedHeader; } - public static ArraySegment EncodeVariableByteInteger(int value) + public static ArraySegment EncodeVariableByteInteger(uint value) { if (value <= 0) { @@ -54,7 +55,7 @@ namespace MQTTnet.Serializer return new ArraySegment(buffer, 0, bufferOffset); } - public void WriteVariableLengthInteger(int value) + public void WriteVariableLengthInteger(uint value) { Write(EncodeVariableByteInteger(value)); } @@ -146,6 +147,7 @@ namespace MQTTnet.Serializer Array.Resize(ref _buffer, MaxBufferSize); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private void EnsureAdditionalCapacity(int additionalCapacity) { var freeSpace = _buffer.Length - _position; @@ -157,6 +159,7 @@ namespace MQTTnet.Serializer EnsureCapacity(_buffer.Length + additionalCapacity - freeSpace); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private void EnsureCapacity(int capacity) { var newBufferLength = _buffer.Length; @@ -174,6 +177,7 @@ namespace MQTTnet.Serializer Array.Resize(ref _buffer, newBufferLength); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private void IncreasePostition(int length) { _position += length; diff --git a/Source/MQTTnet/Serializer/MqttV310PacketSerializer.cs b/Source/MQTTnet/Serializer/MqttV310PacketSerializer.cs index bb72db14..9d36c9e2 100644 --- a/Source/MQTTnet/Serializer/MqttV310PacketSerializer.cs +++ b/Source/MQTTnet/Serializer/MqttV310PacketSerializer.cs @@ -23,7 +23,7 @@ namespace MQTTnet.Serializer _packetWriter.Seek(5); var fixedHeader = SerializePacket(packet, _packetWriter); - var remainingLength = _packetWriter.Length - 5; + var remainingLength = (uint)(_packetWriter.Length - 5); var remainingLengthBuffer = MqttPacketWriter.EncodeVariableByteInteger(remainingLength); diff --git a/Source/MQTTnet/Serializer/MqttV500PacketSerializer.cs b/Source/MQTTnet/Serializer/MqttV500PacketSerializer.cs index 44ec36be..e0241555 100644 --- a/Source/MQTTnet/Serializer/MqttV500PacketSerializer.cs +++ b/Source/MQTTnet/Serializer/MqttV500PacketSerializer.cs @@ -82,11 +82,11 @@ namespace MQTTnet.Serializer var propertyWriter = new MqttPacketWriter(); foreach (var property in packet.Properties) { - propertyWriter.Write((byte)property.Key); - property.Value.WriteTo(propertyWriter); + propertyWriter.Write(property.Id); + property.WriteTo(propertyWriter); } - packetWriter.WriteVariableLengthInteger(propertyWriter.Length); + packetWriter.WriteVariableLengthInteger((uint)propertyWriter.Length); packetWriter.Write(propertyWriter); } From 3db22eea51c8b5e42af3d650709b7bb4446f81d1 Mon Sep 17 00:00:00 2001 From: Christian Kratky Date: Sun, 18 Nov 2018 22:04:39 +0100 Subject: [PATCH 005/124] Remove dedicated V3 messages. --- Source/MQTTnet/Client/MqttClient.cs | 5 ++--- Source/MQTTnet/Packets/MqttConnAckPacket.cs | 17 +++++++++++++++-- .../Properties/MessageExpiryIntervalProperty.cs | 2 +- .../MQTTnet/Packets/V3/MqttV3ConnAckPacket.cs | 16 ---------------- .../Serializer/MqttV310PacketSerializer.cs | 7 +++---- .../Serializer/MqttV311PacketSerializer.cs | 5 ++--- .../MQTTnet/Server/MqttClientSessionsManager.cs | 5 ++--- Tests/MQTTnet.Core.Tests/MqttClientTests.cs | 7 ++----- .../MqttPacketSerializerTests.cs | 9 ++++----- 9 files changed, 31 insertions(+), 42 deletions(-) delete mode 100644 Source/MQTTnet/Packets/V3/MqttV3ConnAckPacket.cs diff --git a/Source/MQTTnet/Client/MqttClient.cs b/Source/MQTTnet/Client/MqttClient.cs index 9e073cbb..c5c2d638 100644 --- a/Source/MQTTnet/Client/MqttClient.cs +++ b/Source/MQTTnet/Client/MqttClient.cs @@ -9,7 +9,6 @@ using MQTTnet.Diagnostics; using MQTTnet.Exceptions; using MQTTnet.Internal; using MQTTnet.Packets; -using MQTTnet.Packets.V3; using MQTTnet.Protocol; namespace MQTTnet.Client @@ -194,7 +193,7 @@ namespace MQTTnet.Client _adapter = null; } - private async Task AuthenticateAsync(MqttApplicationMessage willApplicationMessage, CancellationToken cancellationToken) + private async Task AuthenticateAsync(MqttApplicationMessage willApplicationMessage, CancellationToken cancellationToken) { var connectPacket = new MqttConnectPacket { @@ -206,7 +205,7 @@ namespace MQTTnet.Client WillMessage = willApplicationMessage }; - var response = await SendAndReceiveAsync(connectPacket, cancellationToken).ConfigureAwait(false); + var response = await SendAndReceiveAsync(connectPacket, cancellationToken).ConfigureAwait(false); if (response.ConnectReturnCode != MqttConnectReturnCode.ConnectionAccepted) { throw new MqttConnectingFailedException(response.ConnectReturnCode); diff --git a/Source/MQTTnet/Packets/MqttConnAckPacket.cs b/Source/MQTTnet/Packets/MqttConnAckPacket.cs index a0f69fdd..c4532167 100644 --- a/Source/MQTTnet/Packets/MqttConnAckPacket.cs +++ b/Source/MQTTnet/Packets/MqttConnAckPacket.cs @@ -1,6 +1,19 @@ -namespace MQTTnet.Packets +using MQTTnet.Protocol; + +namespace MQTTnet.Packets { - public abstract class MqttConnAckPacket : MqttBasePacket + public class MqttConnAckPacket : MqttBasePacket { + /// + /// Added in MQTTv3.1.1. + /// + public bool IsSessionPresent { get; set; } + + public MqttConnectReturnCode ConnectReturnCode { get; set; } + + public override string ToString() + { + return "ConnAck: [ConnectReturnCode=" + ConnectReturnCode + "] [IsSessionPresent=" + IsSessionPresent + "]"; + } } } diff --git a/Source/MQTTnet/Packets/Properties/MessageExpiryIntervalProperty.cs b/Source/MQTTnet/Packets/Properties/MessageExpiryIntervalProperty.cs index 7062e8b1..3526935d 100644 --- a/Source/MQTTnet/Packets/Properties/MessageExpiryIntervalProperty.cs +++ b/Source/MQTTnet/Packets/Properties/MessageExpiryIntervalProperty.cs @@ -4,7 +4,7 @@ namespace MQTTnet.Packets.Properties { public class MessageExpiryIntervalProperty : FourByteIntegerValue { - public MessageExpiryInterval(uint value) + public MessageExpiryIntervalProperty(uint value) : base((byte)PropertyID.MessageExpiryInterval, value) { } diff --git a/Source/MQTTnet/Packets/V3/MqttV3ConnAckPacket.cs b/Source/MQTTnet/Packets/V3/MqttV3ConnAckPacket.cs deleted file mode 100644 index 3e858ec7..00000000 --- a/Source/MQTTnet/Packets/V3/MqttV3ConnAckPacket.cs +++ /dev/null @@ -1,16 +0,0 @@ -using MQTTnet.Protocol; - -namespace MQTTnet.Packets.V3 -{ - public class MqttV3ConnAckPacket : MqttBasePacket - { - public bool IsSessionPresent { get; set; } - - public MqttConnectReturnCode ConnectReturnCode { get; set; } - - public override string ToString() - { - return "ConnAck: [ConnectReturnCode=" + ConnectReturnCode + "] [IsSessionPresent=" + IsSessionPresent + "]"; - } - } -} diff --git a/Source/MQTTnet/Serializer/MqttV310PacketSerializer.cs b/Source/MQTTnet/Serializer/MqttV310PacketSerializer.cs index 9d36c9e2..74c96a90 100644 --- a/Source/MQTTnet/Serializer/MqttV310PacketSerializer.cs +++ b/Source/MQTTnet/Serializer/MqttV310PacketSerializer.cs @@ -4,7 +4,6 @@ using MQTTnet.Protocol; using System; using System.Linq; using MQTTnet.Adapter; -using MQTTnet.Packets.V3; namespace MQTTnet.Serializer { @@ -80,7 +79,7 @@ namespace MQTTnet.Serializer switch (packet) { case MqttConnectPacket connectPacket: return SerializeConnectPacket(connectPacket, packetWriter); - case MqttV3ConnAckPacket connAckPacket: return SerializeConnAckPacket(connAckPacket, packetWriter); + case MqttConnAckPacket connAckPacket: return SerializeConnAckPacket(connAckPacket, packetWriter); case MqttDisconnectPacket _: return SerializeEmptyPacket(MqttControlPacketType.Disconnect); case MqttPingReqPacket _: return SerializeEmptyPacket(MqttControlPacketType.PingReq); case MqttPingRespPacket _: return SerializeEmptyPacket(MqttControlPacketType.PingResp); @@ -285,7 +284,7 @@ namespace MQTTnet.Serializer { ThrowIfBodyIsEmpty(body); - var packet = new MqttV3ConnAckPacket(); + var packet = new MqttConnAckPacket(); body.ReadByte(); // Reserved. packet.ConnectReturnCode = (MqttConnectReturnCode)body.ReadByte(); @@ -379,7 +378,7 @@ namespace MQTTnet.Serializer return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.Connect); } - protected virtual byte SerializeConnAckPacket(MqttV3ConnAckPacket packet, MqttPacketWriter packetWriter) + protected virtual byte SerializeConnAckPacket(MqttConnAckPacket packet, MqttPacketWriter packetWriter) { packetWriter.Write(0); // Reserved. packetWriter.Write((byte)packet.ConnectReturnCode); diff --git a/Source/MQTTnet/Serializer/MqttV311PacketSerializer.cs b/Source/MQTTnet/Serializer/MqttV311PacketSerializer.cs index f124c7d1..c70629e5 100644 --- a/Source/MQTTnet/Serializer/MqttV311PacketSerializer.cs +++ b/Source/MQTTnet/Serializer/MqttV311PacketSerializer.cs @@ -1,6 +1,5 @@ using MQTTnet.Exceptions; using MQTTnet.Packets; -using MQTTnet.Packets.V3; using MQTTnet.Protocol; namespace MQTTnet.Serializer @@ -69,7 +68,7 @@ namespace MQTTnet.Serializer return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.Connect); } - protected override byte SerializeConnAckPacket(MqttV3ConnAckPacket packet, MqttPacketWriter packetWriter) + protected override byte SerializeConnAckPacket(MqttConnAckPacket packet, MqttPacketWriter packetWriter) { byte connectAcknowledgeFlags = 0x0; if (packet.IsSessionPresent) @@ -87,7 +86,7 @@ namespace MQTTnet.Serializer { ThrowIfBodyIsEmpty(body); - var packet = new MqttV3ConnAckPacket(); + var packet = new MqttConnAckPacket(); var acknowledgeFlags = body.ReadByte(); diff --git a/Source/MQTTnet/Server/MqttClientSessionsManager.cs b/Source/MQTTnet/Server/MqttClientSessionsManager.cs index a06e969e..7c55109f 100644 --- a/Source/MQTTnet/Server/MqttClientSessionsManager.cs +++ b/Source/MQTTnet/Server/MqttClientSessionsManager.cs @@ -9,7 +9,6 @@ using MQTTnet.Diagnostics; using MQTTnet.Exceptions; using MQTTnet.Internal; using MQTTnet.Packets; -using MQTTnet.Packets.V3; using MQTTnet.Protocol; namespace MQTTnet.Server @@ -229,7 +228,7 @@ namespace MQTTnet.Server if (connectReturnCode != MqttConnectReturnCode.ConnectionAccepted) { await clientAdapter.SendPacketAsync( - new MqttV3ConnAckPacket + new MqttConnAckPacket { ConnectReturnCode = connectReturnCode }, @@ -242,7 +241,7 @@ namespace MQTTnet.Server var clientSession = result.Session; await clientAdapter.SendPacketAsync( - new MqttV3ConnAckPacket + new MqttConnAckPacket { ConnectReturnCode = connectReturnCode, IsSessionPresent = result.IsExistingSession diff --git a/Tests/MQTTnet.Core.Tests/MqttClientTests.cs b/Tests/MQTTnet.Core.Tests/MqttClientTests.cs index 12131a27..414f2538 100644 --- a/Tests/MQTTnet.Core.Tests/MqttClientTests.cs +++ b/Tests/MQTTnet.Core.Tests/MqttClientTests.cs @@ -8,7 +8,6 @@ using MQTTnet.Client; using MQTTnet.Diagnostics; using MQTTnet.Exceptions; using MQTTnet.Packets; -using MQTTnet.Packets.V3; namespace MQTTnet.Core.Tests { @@ -51,11 +50,9 @@ namespace MQTTnet.Core.Tests Task.Run(async () => { var connect = await channel2.ReceivePacketAsync(TimeSpan.Zero, CancellationToken.None); - await channel2.SendPacketAsync(new MqttV3ConnAckPacket() { ConnectReturnCode = Protocol.MqttConnectReturnCode.ConnectionRefusedNotAuthorized }, CancellationToken.None); + await channel2.SendPacketAsync(new MqttConnAckPacket { ConnectReturnCode = Protocol.MqttConnectReturnCode.ConnectionRefusedNotAuthorized }, CancellationToken.None); }); - - - + var fake = new TestMqttCommunicationAdapterFactory(channel); var client = new MqttClient(fake, new MqttNetLogger()); diff --git a/Tests/MQTTnet.Core.Tests/MqttPacketSerializerTests.cs b/Tests/MQTTnet.Core.Tests/MqttPacketSerializerTests.cs index 5df4b69b..59b68496 100644 --- a/Tests/MQTTnet.Core.Tests/MqttPacketSerializerTests.cs +++ b/Tests/MQTTnet.Core.Tests/MqttPacketSerializerTests.cs @@ -7,7 +7,6 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using MQTTnet.Adapter; using MQTTnet.Internal; using MQTTnet.Packets; -using MQTTnet.Packets.V3; using MQTTnet.Protocol; using MQTTnet.Serializer; @@ -108,7 +107,7 @@ namespace MQTTnet.Core.Tests [TestMethod] public void SerializeV311_MqttConnAckPacket() { - var p = new MqttV3ConnAckPacket + var p = new MqttConnAckPacket { IsSessionPresent = true, ConnectReturnCode = MqttConnectReturnCode.ConnectionRefusedNotAuthorized @@ -120,7 +119,7 @@ namespace MQTTnet.Core.Tests [TestMethod] public void SerializeV310_MqttConnAckPacket() { - var p = new MqttV3ConnAckPacket + var p = new MqttConnAckPacket { ConnectReturnCode = MqttConnectReturnCode.ConnectionRefusedNotAuthorized }; @@ -131,7 +130,7 @@ namespace MQTTnet.Core.Tests [TestMethod] public void DeserializeV311_MqttConnAckPacket() { - var p = new MqttV3ConnAckPacket + var p = new MqttConnAckPacket { IsSessionPresent = true, ConnectReturnCode = MqttConnectReturnCode.ConnectionRefusedNotAuthorized @@ -143,7 +142,7 @@ namespace MQTTnet.Core.Tests [TestMethod] public void DeserializeV310_MqttConnAckPacket() { - var p = new MqttV3ConnAckPacket + var p = new MqttConnAckPacket { ConnectReturnCode = MqttConnectReturnCode.ConnectionRefusedNotAuthorized }; From 48d5afa799dbebd192e822d7b722038aeb7a3b16 Mon Sep 17 00:00:00 2001 From: Christian Kratky Date: Mon, 19 Nov 2018 00:14:15 +0100 Subject: [PATCH 006/124] Refactor namings of serializers etc. --- .../MqttClientConnectionContextFactory.cs | 2 +- .../MqttConnectionContext.cs | 6 +- .../MqttConnectionHandler.cs | 2 +- .../MqttWebSocketServerAdapter.cs | 2 +- Source/MQTTnet.AspnetCore/ReaderExtensions.cs | 8 +- Source/MQTTnet/Adapter/IMqttChannelAdapter.cs | 2 +- Source/MQTTnet/Adapter/MqttChannelAdapter.cs | 6 +- Source/MQTTnet/Adapter/ReceivedMqttPacket.cs | 2 +- Source/MQTTnet/Client/IMqttClientOptions.cs | 2 +- Source/MQTTnet/Client/MqttClientOptions.cs | 2 +- .../Client/MqttClientOptionsBuilder.cs | 2 +- .../MQTTnet/Formatter/IMqttPacketFormatter.cs | 15 ++ .../MqttFixedHeader.cs | 2 +- .../MqttPacketBodyReader.cs | 2 +- .../MqttPacketReader.cs | 2 +- .../MqttPacketSerializerAdapter.cs | 27 +-- .../MqttPacketWriter.cs | 2 +- .../MqttProtocolVersion.cs | 2 +- .../V310/MqttV310PacketFormatter.cs} | 22 +-- .../V311/MqttV311PacketFormatter.cs} | 5 +- .../Formatter/V500/MqttV500PacketDecoder.cs | 6 + .../Formatter/V500/MqttV500PacketEncoder.cs | 6 + .../Formatter/V500/MqttV500PacketFormatter.cs | 187 ++++++++++++++++++ .../MqttClientAdapterFactory.cs | 2 +- .../MqttTcpServerAdapter.Uwp.cs | 2 +- .../Implementations/MqttTcpServerListener.cs | 2 +- .../ArraySegmentExtensions.cs} | 4 +- Source/MQTTnet/MQTTnet.csproj | 6 + Source/MQTTnet/Packets/MqttAuthPacket.cs | 16 ++ .../MQTTnet/Packets/MqttBasePublishPacket.cs | 10 +- Source/MQTTnet/Packets/MqttConnAckPacket.cs | 42 +++- Source/MQTTnet/Packets/MqttConnectPacket.cs | 25 ++- .../MQTTnet/Packets/MqttDisconnectPacket.cs | 10 +- Source/MQTTnet/Packets/MqttPublishPacket.cs | 9 +- Source/MQTTnet/Packets/MqttSubscribePacket.cs | 6 + Source/MQTTnet/Packets/MqttUserProperty.cs | 17 ++ .../AssignedClientIdentifierProperty.cs | 6 +- .../Properties/AuthenticationDataProperty.cs | 3 +- .../AuthenticationMethodProperty.cs | 6 +- .../BaseTypes/BinaryDataProperty.cs | 5 +- .../Properties/BaseTypes/ByteProperty.cs | 7 +- .../BaseTypes/FourByteIntegerProperty.cs | 3 +- .../Properties/BaseTypes/StringProperty.cs | 5 +- .../BaseTypes/TwoByteIntegerProperty.cs | 3 +- .../BaseTypes/VariableByteIntegerProperty.cs | 4 +- .../Packets/Properties/ContentTypeProperty.cs | 6 +- .../Properties/CorrelationDataProperty.cs | 3 +- .../MQTTnet/Packets/Properties/IProperty.cs | 2 +- .../Properties/MaximumPacketSizeProperty.cs | 12 ++ .../Packets/Properties/MaximumQoSProperty.cs | 12 ++ .../MessageExpiryIntervalProperty.cs | 2 +- ...PropertyID.cs => MqttMessagePropertyID.cs} | 2 +- .../PayloadFormatIndicatorProperty.cs | 6 +- .../Properties/ReasonStringProperty.cs | 12 ++ .../Properties/ReceiveMaximumProperty.cs | 12 ++ .../RequestProblemInformationProperty.cs | 6 +- .../RequestResponseInformationProperty.cs | 6 +- .../Properties/ResponseInformationProperty.cs | 12 ++ .../Properties/ResponseTopicProperty.cs | 6 +- .../Properties/RetainAvailableProperty.cs | 12 ++ .../Properties/ServerKeepAliveProperty.cs | 2 +- .../Properties/ServerReferenceProperty.cs | 12 ++ .../SessionExpiryIntervalProperty.cs | 2 +- .../SharedSubscriptionAvailableProperty.cs | 12 ++ .../SubscriptionIdentifierAvailable.cs | 12 ++ .../SubscriptionIdentifierProperty.cs | 2 +- .../Properties/TopicAliasMaximumProperty.cs | 12 ++ .../Packets/Properties/TopicAliasProperty.cs | 12 ++ .../Properties/UserPropertyProperty.cs | 29 +++ .../WildcardSubscriptionAvailableProperty.cs | 12 ++ .../Properties/WillDelayIntervalProperty.cs | 2 +- .../Protocol/MqttAuthenticateReasonCode.cs | 9 + .../Serializer/IMqttPacketSerializer.cs | 15 -- .../Serializer/MqttV500PacketSerializer.cs | 96 --------- .../Server/IMqttClientSessionStatus.cs | 2 +- .../MQTTnet/Server/MqttClientSessionStatus.cs | 2 +- .../MqttConnectionContextTest.cs | 2 +- .../ReaderExtensionsTest.cs | 6 +- .../ChannelAdapterBenchmark.cs | 4 +- .../MQTTnet.Benchmarks/SerializerBenchmark.cs | 13 +- .../MqttPacketReaderTests.cs | 2 +- .../MqttPacketSerializerTests.cs | 40 ++-- .../MqttPacketWriterTests.cs | 2 +- .../TestMqttCommunicationAdapter.cs | 2 +- .../MainPage.xaml | 14 +- .../MainPage.xaml.cs | 21 +- 86 files changed, 709 insertions(+), 248 deletions(-) create mode 100644 Source/MQTTnet/Formatter/IMqttPacketFormatter.cs rename Source/MQTTnet/{Serializer => Formatter}/MqttFixedHeader.cs (90%) rename Source/MQTTnet/{Serializer => Formatter}/MqttPacketBodyReader.cs (98%) rename Source/MQTTnet/{Serializer => Formatter}/MqttPacketReader.cs (99%) rename Source/MQTTnet/{Serializer => Formatter}/MqttPacketSerializerAdapter.cs (81%) rename Source/MQTTnet/{Serializer => Formatter}/MqttPacketWriter.cs (99%) rename Source/MQTTnet/{Serializer => Formatter}/MqttProtocolVersion.cs (76%) rename Source/MQTTnet/{Serializer/MqttV310PacketSerializer.cs => Formatter/V310/MqttV310PacketFormatter.cs} (97%) rename Source/MQTTnet/{Serializer/MqttV311PacketSerializer.cs => Formatter/V311/MqttV311PacketFormatter.cs} (95%) create mode 100644 Source/MQTTnet/Formatter/V500/MqttV500PacketDecoder.cs create mode 100644 Source/MQTTnet/Formatter/V500/MqttV500PacketEncoder.cs create mode 100644 Source/MQTTnet/Formatter/V500/MqttV500PacketFormatter.cs rename Source/MQTTnet/{Serializer/Extensions.cs => Internal/ArraySegmentExtensions.cs} (85%) create mode 100644 Source/MQTTnet/Packets/MqttAuthPacket.cs create mode 100644 Source/MQTTnet/Packets/MqttUserProperty.cs create mode 100644 Source/MQTTnet/Packets/Properties/MaximumPacketSizeProperty.cs create mode 100644 Source/MQTTnet/Packets/Properties/MaximumQoSProperty.cs rename Source/MQTTnet/Packets/Properties/{PropertyID.cs => MqttMessagePropertyID.cs} (96%) create mode 100644 Source/MQTTnet/Packets/Properties/ReasonStringProperty.cs create mode 100644 Source/MQTTnet/Packets/Properties/ReceiveMaximumProperty.cs create mode 100644 Source/MQTTnet/Packets/Properties/ResponseInformationProperty.cs create mode 100644 Source/MQTTnet/Packets/Properties/RetainAvailableProperty.cs create mode 100644 Source/MQTTnet/Packets/Properties/ServerReferenceProperty.cs create mode 100644 Source/MQTTnet/Packets/Properties/SharedSubscriptionAvailableProperty.cs create mode 100644 Source/MQTTnet/Packets/Properties/SubscriptionIdentifierAvailable.cs create mode 100644 Source/MQTTnet/Packets/Properties/TopicAliasMaximumProperty.cs create mode 100644 Source/MQTTnet/Packets/Properties/TopicAliasProperty.cs create mode 100644 Source/MQTTnet/Packets/Properties/UserPropertyProperty.cs create mode 100644 Source/MQTTnet/Packets/Properties/WildcardSubscriptionAvailableProperty.cs create mode 100644 Source/MQTTnet/Protocol/MqttAuthenticateReasonCode.cs delete mode 100644 Source/MQTTnet/Serializer/IMqttPacketSerializer.cs delete mode 100644 Source/MQTTnet/Serializer/MqttV500PacketSerializer.cs diff --git a/Source/MQTTnet.AspnetCore/Client/MqttClientConnectionContextFactory.cs b/Source/MQTTnet.AspnetCore/Client/MqttClientConnectionContextFactory.cs index 93253857..b5801f46 100644 --- a/Source/MQTTnet.AspnetCore/Client/MqttClientConnectionContextFactory.cs +++ b/Source/MQTTnet.AspnetCore/Client/MqttClientConnectionContextFactory.cs @@ -4,7 +4,7 @@ using MQTTnet.Adapter; using MQTTnet.AspNetCore.Client.Tcp; using MQTTnet.Client; using MQTTnet.Diagnostics; -using MQTTnet.Serializer; +using MQTTnet.Formatter; namespace MQTTnet.AspNetCore.Client { diff --git a/Source/MQTTnet.AspnetCore/MqttConnectionContext.cs b/Source/MQTTnet.AspnetCore/MqttConnectionContext.cs index 1b54ef55..aa2d7d8c 100644 --- a/Source/MQTTnet.AspnetCore/MqttConnectionContext.cs +++ b/Source/MQTTnet.AspnetCore/MqttConnectionContext.cs @@ -3,11 +3,11 @@ using MQTTnet.Adapter; using MQTTnet.AspNetCore.Client.Tcp; using MQTTnet.Exceptions; using MQTTnet.Packets; -using MQTTnet.Serializer; using System; using System.IO.Pipelines; using System.Threading; using System.Threading.Tasks; +using MQTTnet.Formatter; namespace MQTTnet.AspNetCore { @@ -72,7 +72,7 @@ namespace MQTTnet.AspNetCore { if (!buffer.IsEmpty) { - if (PacketSerializerAdapter.Serializer.TryDeserialize(buffer, out var packet, out consumed, out observed)) + if (PacketSerializerAdapter.Formatter.TryDeserialize(buffer, out var packet, out consumed, out observed)) { return packet; } @@ -107,7 +107,7 @@ namespace MQTTnet.AspNetCore public async Task SendPacketAsync(MqttBasePacket packet, CancellationToken cancellationToken) { - var buffer = PacketSerializerAdapter.Serialize(packet).AsMemory(); + var buffer = PacketSerializerAdapter.Encode(packet).AsMemory(); var output = Connection.Transport.Output; await _writerSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false); diff --git a/Source/MQTTnet.AspnetCore/MqttConnectionHandler.cs b/Source/MQTTnet.AspnetCore/MqttConnectionHandler.cs index 77a8734b..f533ed7c 100644 --- a/Source/MQTTnet.AspnetCore/MqttConnectionHandler.cs +++ b/Source/MQTTnet.AspnetCore/MqttConnectionHandler.cs @@ -1,10 +1,10 @@ using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Connections.Features; using MQTTnet.Adapter; -using MQTTnet.Serializer; using MQTTnet.Server; using System; using System.Threading.Tasks; +using MQTTnet.Formatter; namespace MQTTnet.AspNetCore { diff --git a/Source/MQTTnet.AspnetCore/MqttWebSocketServerAdapter.cs b/Source/MQTTnet.AspnetCore/MqttWebSocketServerAdapter.cs index 4dc75a44..ee310222 100644 --- a/Source/MQTTnet.AspnetCore/MqttWebSocketServerAdapter.cs +++ b/Source/MQTTnet.AspnetCore/MqttWebSocketServerAdapter.cs @@ -3,8 +3,8 @@ using System.Net.WebSockets; using System.Threading.Tasks; using MQTTnet.Adapter; using MQTTnet.Diagnostics; +using MQTTnet.Formatter; using MQTTnet.Implementations; -using MQTTnet.Serializer; using MQTTnet.Server; namespace MQTTnet.AspNetCore diff --git a/Source/MQTTnet.AspnetCore/ReaderExtensions.cs b/Source/MQTTnet.AspnetCore/ReaderExtensions.cs index 6c770b9e..d7c192b9 100644 --- a/Source/MQTTnet.AspnetCore/ReaderExtensions.cs +++ b/Source/MQTTnet.AspnetCore/ReaderExtensions.cs @@ -2,15 +2,17 @@ using System.Buffers; using MQTTnet.Adapter; using MQTTnet.Exceptions; +using MQTTnet.Formatter; using MQTTnet.Packets; -using MQTTnet.Serializer; namespace MQTTnet.AspNetCore { public static class ReaderExtensions { - public static bool TryDeserialize(this IMqttPacketSerializer serializer, in ReadOnlySequence input, out MqttBasePacket packet, out SequencePosition consumed, out SequencePosition observed) + public static bool TryDeserialize(this IMqttPacketFormatter formatter, in ReadOnlySequence input, out MqttBasePacket packet, out SequencePosition consumed, out SequencePosition observed) { + if (formatter == null) throw new ArgumentNullException(nameof(formatter)); + packet = null; consumed = input.Start; observed = input.End; @@ -33,7 +35,7 @@ namespace MQTTnet.AspNetCore var bodySlice = copy.Slice(0, bodyLength); var buffer = bodySlice.GetArray(); - packet = serializer.Deserialize(new ReceivedMqttPacket(fixedheader, new MqttPacketBodyReader(buffer, 0, buffer.Length))); + packet = formatter.Decode(new ReceivedMqttPacket(fixedheader, new MqttPacketBodyReader(buffer, 0, buffer.Length))); consumed = bodySlice.End; observed = bodySlice.End; return true; diff --git a/Source/MQTTnet/Adapter/IMqttChannelAdapter.cs b/Source/MQTTnet/Adapter/IMqttChannelAdapter.cs index 9aa5cacc..82003204 100644 --- a/Source/MQTTnet/Adapter/IMqttChannelAdapter.cs +++ b/Source/MQTTnet/Adapter/IMqttChannelAdapter.cs @@ -1,8 +1,8 @@ using System; using System.Threading; using System.Threading.Tasks; +using MQTTnet.Formatter; using MQTTnet.Packets; -using MQTTnet.Serializer; namespace MQTTnet.Adapter { diff --git a/Source/MQTTnet/Adapter/MqttChannelAdapter.cs b/Source/MQTTnet/Adapter/MqttChannelAdapter.cs index dc95152f..463b898a 100644 --- a/Source/MQTTnet/Adapter/MqttChannelAdapter.cs +++ b/Source/MQTTnet/Adapter/MqttChannelAdapter.cs @@ -7,9 +7,9 @@ using System.Threading.Tasks; using MQTTnet.Channel; using MQTTnet.Diagnostics; using MQTTnet.Exceptions; +using MQTTnet.Formatter; using MQTTnet.Internal; using MQTTnet.Packets; -using MQTTnet.Serializer; namespace MQTTnet.Adapter { @@ -96,7 +96,7 @@ namespace MQTTnet.Adapter await _writerSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false); try { - var packetData = PacketSerializerAdapter.Serialize(packet); + var packetData = PacketSerializerAdapter.Encode(packet); await _channel.WriteAsync(packetData.Array, packetData.Offset, packetData.Count, cancellationToken).ConfigureAwait(false); PacketSerializerAdapter.FreeBuffer(); @@ -144,7 +144,7 @@ namespace MQTTnet.Adapter PacketSerializerAdapter.DetectProtocolVersion(receivedMqttPacket); } - var packet = PacketSerializerAdapter.Deserialize(receivedMqttPacket); + var packet = PacketSerializerAdapter.Decode(receivedMqttPacket); if (packet == null) { throw new MqttProtocolViolationException("Received malformed packet."); diff --git a/Source/MQTTnet/Adapter/ReceivedMqttPacket.cs b/Source/MQTTnet/Adapter/ReceivedMqttPacket.cs index 3a92d4de..007b9eb6 100644 --- a/Source/MQTTnet/Adapter/ReceivedMqttPacket.cs +++ b/Source/MQTTnet/Adapter/ReceivedMqttPacket.cs @@ -1,4 +1,4 @@ -using MQTTnet.Serializer; +using MQTTnet.Formatter; namespace MQTTnet.Adapter { diff --git a/Source/MQTTnet/Client/IMqttClientOptions.cs b/Source/MQTTnet/Client/IMqttClientOptions.cs index 176f5e43..8825a64e 100644 --- a/Source/MQTTnet/Client/IMqttClientOptions.cs +++ b/Source/MQTTnet/Client/IMqttClientOptions.cs @@ -1,5 +1,5 @@ using System; -using MQTTnet.Serializer; +using MQTTnet.Formatter; namespace MQTTnet.Client { diff --git a/Source/MQTTnet/Client/MqttClientOptions.cs b/Source/MQTTnet/Client/MqttClientOptions.cs index 7d3ad5d8..1e5182cb 100644 --- a/Source/MQTTnet/Client/MqttClientOptions.cs +++ b/Source/MQTTnet/Client/MqttClientOptions.cs @@ -1,5 +1,5 @@ using System; -using MQTTnet.Serializer; +using MQTTnet.Formatter; namespace MQTTnet.Client { diff --git a/Source/MQTTnet/Client/MqttClientOptionsBuilder.cs b/Source/MQTTnet/Client/MqttClientOptionsBuilder.cs index 85e2b4d8..34ecb941 100644 --- a/Source/MQTTnet/Client/MqttClientOptionsBuilder.cs +++ b/Source/MQTTnet/Client/MqttClientOptionsBuilder.cs @@ -1,6 +1,6 @@ using System; using System.Linq; -using MQTTnet.Serializer; +using MQTTnet.Formatter; namespace MQTTnet.Client { diff --git a/Source/MQTTnet/Formatter/IMqttPacketFormatter.cs b/Source/MQTTnet/Formatter/IMqttPacketFormatter.cs new file mode 100644 index 00000000..b4a26b84 --- /dev/null +++ b/Source/MQTTnet/Formatter/IMqttPacketFormatter.cs @@ -0,0 +1,15 @@ +using System; +using MQTTnet.Adapter; +using MQTTnet.Packets; + +namespace MQTTnet.Formatter +{ + public interface IMqttPacketFormatter + { + ArraySegment Encode(MqttBasePacket mqttPacket); + + MqttBasePacket Decode(ReceivedMqttPacket receivedMqttPacket); + + void FreeBuffer(); + } +} \ No newline at end of file diff --git a/Source/MQTTnet/Serializer/MqttFixedHeader.cs b/Source/MQTTnet/Formatter/MqttFixedHeader.cs similarity index 90% rename from Source/MQTTnet/Serializer/MqttFixedHeader.cs rename to Source/MQTTnet/Formatter/MqttFixedHeader.cs index a8c2015b..c9cd3ce1 100644 --- a/Source/MQTTnet/Serializer/MqttFixedHeader.cs +++ b/Source/MQTTnet/Formatter/MqttFixedHeader.cs @@ -1,4 +1,4 @@ -namespace MQTTnet.Serializer +namespace MQTTnet.Formatter { public struct MqttFixedHeader { diff --git a/Source/MQTTnet/Serializer/MqttPacketBodyReader.cs b/Source/MQTTnet/Formatter/MqttPacketBodyReader.cs similarity index 98% rename from Source/MQTTnet/Serializer/MqttPacketBodyReader.cs rename to Source/MQTTnet/Formatter/MqttPacketBodyReader.cs index ad3d789b..3e477475 100644 --- a/Source/MQTTnet/Serializer/MqttPacketBodyReader.cs +++ b/Source/MQTTnet/Formatter/MqttPacketBodyReader.cs @@ -1,7 +1,7 @@ using System; using System.Text; -namespace MQTTnet.Serializer +namespace MQTTnet.Formatter { public class MqttPacketBodyReader { diff --git a/Source/MQTTnet/Serializer/MqttPacketReader.cs b/Source/MQTTnet/Formatter/MqttPacketReader.cs similarity index 99% rename from Source/MQTTnet/Serializer/MqttPacketReader.cs rename to Source/MQTTnet/Formatter/MqttPacketReader.cs index f115b507..4a12efc5 100644 --- a/Source/MQTTnet/Serializer/MqttPacketReader.cs +++ b/Source/MQTTnet/Formatter/MqttPacketReader.cs @@ -4,7 +4,7 @@ using MQTTnet.Channel; using MQTTnet.Exceptions; using MQTTnet.Internal; -namespace MQTTnet.Serializer +namespace MQTTnet.Formatter { public static class MqttPacketReader { diff --git a/Source/MQTTnet/Serializer/MqttPacketSerializerAdapter.cs b/Source/MQTTnet/Formatter/MqttPacketSerializerAdapter.cs similarity index 81% rename from Source/MQTTnet/Serializer/MqttPacketSerializerAdapter.cs rename to Source/MQTTnet/Formatter/MqttPacketSerializerAdapter.cs index 4a4a164a..82bfa99c 100644 --- a/Source/MQTTnet/Serializer/MqttPacketSerializerAdapter.cs +++ b/Source/MQTTnet/Formatter/MqttPacketSerializerAdapter.cs @@ -1,9 +1,12 @@ using System; using MQTTnet.Adapter; using MQTTnet.Exceptions; +using MQTTnet.Formatter.V310; +using MQTTnet.Formatter.V311; +using MQTTnet.Formatter.V500; using MQTTnet.Packets; -namespace MQTTnet.Serializer +namespace MQTTnet.Formatter { public class MqttPacketSerializerAdapter { @@ -18,31 +21,31 @@ namespace MQTTnet.Serializer public MqttProtocolVersion? ProtocolVersion { get; private set; } - public IMqttPacketSerializer Serializer { get; private set; } + public IMqttPacketFormatter Formatter { get; private set; } - public ArraySegment Serialize(MqttBasePacket packet) + public ArraySegment Encode(MqttBasePacket packet) { - if (Serializer == null) + if (Formatter == null) { throw new InvalidOperationException("Protocol version not set or detected."); } - return Serializer.Serialize(packet); + return Formatter.Encode(packet); } - public MqttBasePacket Deserialize(ReceivedMqttPacket receivedMqttPacket) + public MqttBasePacket Decode(ReceivedMqttPacket receivedMqttPacket) { - if (Serializer == null) + if (Formatter == null) { throw new InvalidOperationException("Protocol version not set or detected."); } - return Serializer.Deserialize(receivedMqttPacket); + return Formatter.Decode(receivedMqttPacket); } public void FreeBuffer() { - Serializer?.FreeBuffer(); + Formatter?.FreeBuffer(); } public void DetectProtocolVersion(ReceivedMqttPacket receivedMqttPacket) @@ -65,20 +68,20 @@ namespace MQTTnet.Serializer { case MqttProtocolVersion.V500: { - Serializer = new MqttV500PacketSerializer(); + Formatter = new MqttV500PacketFormatter(); break; } case MqttProtocolVersion.V311: { - Serializer = new MqttV311PacketSerializer(); + Formatter = new MqttV311PacketFormatter(); break; } case MqttProtocolVersion.V310: { - Serializer = new MqttV310PacketSerializer(); + Formatter = new MqttV310PacketFormatter(); break; } diff --git a/Source/MQTTnet/Serializer/MqttPacketWriter.cs b/Source/MQTTnet/Formatter/MqttPacketWriter.cs similarity index 99% rename from Source/MQTTnet/Serializer/MqttPacketWriter.cs rename to Source/MQTTnet/Formatter/MqttPacketWriter.cs index 3df5d964..9fb8ed43 100644 --- a/Source/MQTTnet/Serializer/MqttPacketWriter.cs +++ b/Source/MQTTnet/Formatter/MqttPacketWriter.cs @@ -3,7 +3,7 @@ using System.Runtime.CompilerServices; using System.Text; using MQTTnet.Protocol; -namespace MQTTnet.Serializer +namespace MQTTnet.Formatter { /// /// This is a custom implementation of a memory stream which provides only MQTTnet relevant features. diff --git a/Source/MQTTnet/Serializer/MqttProtocolVersion.cs b/Source/MQTTnet/Formatter/MqttProtocolVersion.cs similarity index 76% rename from Source/MQTTnet/Serializer/MqttProtocolVersion.cs rename to Source/MQTTnet/Formatter/MqttProtocolVersion.cs index 576f7df7..c5e05e5e 100644 --- a/Source/MQTTnet/Serializer/MqttProtocolVersion.cs +++ b/Source/MQTTnet/Formatter/MqttProtocolVersion.cs @@ -1,4 +1,4 @@ -namespace MQTTnet.Serializer +namespace MQTTnet.Formatter { public enum MqttProtocolVersion { diff --git a/Source/MQTTnet/Serializer/MqttV310PacketSerializer.cs b/Source/MQTTnet/Formatter/V310/MqttV310PacketFormatter.cs similarity index 97% rename from Source/MQTTnet/Serializer/MqttV310PacketSerializer.cs rename to Source/MQTTnet/Formatter/V310/MqttV310PacketFormatter.cs index 74c96a90..338b506d 100644 --- a/Source/MQTTnet/Serializer/MqttV310PacketSerializer.cs +++ b/Source/MQTTnet/Formatter/V310/MqttV310PacketFormatter.cs @@ -1,19 +1,20 @@ -using MQTTnet.Exceptions; -using MQTTnet.Packets; -using MQTTnet.Protocol; -using System; +using System; using System.Linq; using MQTTnet.Adapter; +using MQTTnet.Exceptions; +using MQTTnet.Internal; +using MQTTnet.Packets; +using MQTTnet.Protocol; -namespace MQTTnet.Serializer +namespace MQTTnet.Formatter.V310 { - public class MqttV310PacketSerializer : IMqttPacketSerializer + public class MqttV310PacketFormatter : IMqttPacketFormatter { private const int FixedHeaderSize = 1; private readonly MqttPacketWriter _packetWriter = new MqttPacketWriter(); - public ArraySegment Serialize(MqttBasePacket packet) + public ArraySegment Encode(MqttBasePacket packet) { if (packet == null) throw new ArgumentNullException(nameof(packet)); @@ -38,7 +39,7 @@ namespace MQTTnet.Serializer return new ArraySegment(buffer, headerOffset, _packetWriter.Length - headerOffset); } - public MqttBasePacket Deserialize(ReceivedMqttPacket receivedMqttPacket) + public MqttBasePacket Decode(ReceivedMqttPacket receivedMqttPacket) { if (receivedMqttPacket == null) throw new ArgumentNullException(nameof(receivedMqttPacket)); @@ -300,11 +301,6 @@ namespace MQTTnet.Serializer { throw new MqttProtocolViolationException("CleanSession must be set if ClientId is empty [MQTT-3.1.3-7]."); } - - if (packet.Properties != null) - { - throw new MqttProtocolViolationException("Properties are only supported in MQTT Version 5."); - } } // ReSharper disable once ParameterOnlyUsedForPreconditionCheck.Local diff --git a/Source/MQTTnet/Serializer/MqttV311PacketSerializer.cs b/Source/MQTTnet/Formatter/V311/MqttV311PacketFormatter.cs similarity index 95% rename from Source/MQTTnet/Serializer/MqttV311PacketSerializer.cs rename to Source/MQTTnet/Formatter/V311/MqttV311PacketFormatter.cs index c70629e5..a02b9b37 100644 --- a/Source/MQTTnet/Serializer/MqttV311PacketSerializer.cs +++ b/Source/MQTTnet/Formatter/V311/MqttV311PacketFormatter.cs @@ -1,10 +1,11 @@ using MQTTnet.Exceptions; +using MQTTnet.Formatter.V310; using MQTTnet.Packets; using MQTTnet.Protocol; -namespace MQTTnet.Serializer +namespace MQTTnet.Formatter.V311 { - public class MqttV311PacketSerializer : MqttV310PacketSerializer + public class MqttV311PacketFormatter : MqttV310PacketFormatter { protected override byte SerializeConnectPacket(MqttConnectPacket packet, MqttPacketWriter packetWriter) { diff --git a/Source/MQTTnet/Formatter/V500/MqttV500PacketDecoder.cs b/Source/MQTTnet/Formatter/V500/MqttV500PacketDecoder.cs new file mode 100644 index 00000000..f98ab7f6 --- /dev/null +++ b/Source/MQTTnet/Formatter/V500/MqttV500PacketDecoder.cs @@ -0,0 +1,6 @@ +namespace MQTTnet.Formatter.V500 +{ + public class MqttV500PacketDecoder + { + } +} diff --git a/Source/MQTTnet/Formatter/V500/MqttV500PacketEncoder.cs b/Source/MQTTnet/Formatter/V500/MqttV500PacketEncoder.cs new file mode 100644 index 00000000..f697d89f --- /dev/null +++ b/Source/MQTTnet/Formatter/V500/MqttV500PacketEncoder.cs @@ -0,0 +1,6 @@ +namespace MQTTnet.Formatter.V500 +{ + public class MqttV500PacketEncoder + { + } +} diff --git a/Source/MQTTnet/Formatter/V500/MqttV500PacketFormatter.cs b/Source/MQTTnet/Formatter/V500/MqttV500PacketFormatter.cs new file mode 100644 index 00000000..08b8491b --- /dev/null +++ b/Source/MQTTnet/Formatter/V500/MqttV500PacketFormatter.cs @@ -0,0 +1,187 @@ +using System; +using System.Collections.Generic; +using MQTTnet.Exceptions; +using MQTTnet.Formatter.V311; +using MQTTnet.Packets; +using MQTTnet.Packets.Properties; +using MQTTnet.Protocol; + +namespace MQTTnet.Formatter.V500 +{ + public class MqttV500PacketFormatter : MqttV311PacketFormatter + { + private readonly MqttV500PacketEncoder _encoder = new MqttV500PacketEncoder(); + private readonly MqttV500PacketDecoder _decoder = new MqttV500PacketDecoder(); + + protected override byte SerializeConnectPacket(MqttConnectPacket packet, MqttPacketWriter packetWriter) + { + ValidateConnectPacket(packet); + + packetWriter.WriteWithLengthPrefix("MQTT"); + packetWriter.Write(5); // [3.1.2.2 Protocol Version] + + byte connectFlags = 0x0; + if (packet.CleanSession) + { + connectFlags |= 0x2; + } + + if (packet.WillMessage != null) + { + connectFlags |= 0x4; + connectFlags |= (byte)((byte)packet.WillMessage.QualityOfServiceLevel << 3); + + if (packet.WillMessage.Retain) + { + connectFlags |= 0x20; + } + } + + if (packet.Password != null && packet.Username == null) + { + throw new MqttProtocolViolationException("If the User Name Flag is set to 0, the Password Flag MUST be set to 0 [MQTT-3.1.2-22]."); + } + + if (packet.Password != null) + { + connectFlags |= 0x40; + } + + if (packet.Username != null) + { + connectFlags |= 0x80; + } + + packetWriter.Write(connectFlags); + packetWriter.Write(packet.KeepAlivePeriod); + packetWriter.WriteWithLengthPrefix(packet.ClientId); + + if (packet.WillMessage != null) + { + packetWriter.WriteWithLengthPrefix(packet.WillMessage.Topic); + packetWriter.WriteWithLengthPrefix(packet.WillMessage.Payload); + } + + if (packet.Username != null) + { + packetWriter.WriteWithLengthPrefix(packet.Username); + } + + if (packet.Password != null) + { + packetWriter.WriteWithLengthPrefix(packet.Password); + } + + WriteProperty(MqttMessagePropertyID.WillDelayInterval, packet.WillDelayIntervalProperty, packetWriter); + WriteProperty(MqttMessagePropertyID.SessionExpiryInterval, packet.SessionExpiryIntervalProperty, packetWriter); + WriteProperty(MqttMessagePropertyID.AuthenticationMethod, packet.AuthenticationMethodProperty, packetWriter); + WriteProperty(MqttMessagePropertyID.AuthenticationData, packet.AuthenticationDataProperty, packetWriter); + WriteProperty(MqttMessagePropertyID.RequestProblemInformation, packet.RequestProblemInformationProperty, packetWriter); + WriteProperty(MqttMessagePropertyID.RequestResponseInformation, packet.RequestResponseInformationProperty, packetWriter); + WriteProperty(MqttMessagePropertyID.ReceiveMaximum, packet.ReceiveMaximumProperty, packetWriter); + WriteProperty(MqttMessagePropertyID.TopicAlias, packet.TopicAliasMaximumProperty, packetWriter); + WriteProperty(MqttMessagePropertyID.MaximumPacketSize, packet.MaximumPacketSizeProperty, packetWriter); + WriteUserProperties(packet.UserPropertiesProperty, packetWriter); + + return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.Connect); + } + + protected override byte SerializeConnAckPacket(MqttConnAckPacket packet, MqttPacketWriter packetWriter) + { + byte connectAcknowledgeFlags = 0x0; + if (packet.IsSessionPresent) + { + connectAcknowledgeFlags |= 0x1; + } + + packetWriter.Write(connectAcknowledgeFlags); + packetWriter.Write((byte)packet.ConnectReturnCode); + + WriteProperty(MqttMessagePropertyID.SessionExpiryInterval, packet.SessionExpiryIntervalProperty, packetWriter); + WriteProperty(MqttMessagePropertyID.AuthenticationMethod, packet.AuthenticationMethodProperty, packetWriter); + WriteProperty(MqttMessagePropertyID.AuthenticationData, packet.AuthenticationDataProperty, packetWriter); + WriteProperty(MqttMessagePropertyID.ReceiveMaximum, packet.ReceiveMaximumProperty, packetWriter); + WriteProperty(MqttMessagePropertyID.TopicAlias, packet.TopicAliasMaximumProperty, packetWriter); + WriteProperty(MqttMessagePropertyID.MaximumPacketSize, packet.MaximumPacketSizeProperty, packetWriter); + WriteUserProperties(packet.UserPropertiesProperty, packetWriter); + + return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.ConnAck); + } + + private static void WriteProperty(MqttMessagePropertyID id, bool? value, MqttPacketWriter target) + { + if (!value.HasValue) + { + return; + } + + target.Write((byte)id); + target.Write(value.Value ? (byte)0x1 : (byte)0x0); + } + + private static void WriteProperty(MqttMessagePropertyID id, ushort? value, MqttPacketWriter target) + { + if (!value.HasValue) + { + return; + } + + target.Write((byte)id); + target.Write(value.Value); + } + + private static void WriteProperty(MqttMessagePropertyID id, uint? value, MqttPacketWriter target) + { + if (!value.HasValue) + { + return; + } + + // TODO: Check if order must be reversed like for ushort. + target.Write((byte)id); + var bytes = BitConverter.GetBytes(value.Value); + target.Write(bytes, 0, bytes.Length); + } + + private static void WriteProperty(MqttMessagePropertyID id, string value, MqttPacketWriter target) + { + if (value == null) + { + return; + } + + target.Write((byte)id); + target.WriteWithLengthPrefix(value); + } + + private static void WriteProperty(MqttMessagePropertyID id, byte[] value, MqttPacketWriter target) + { + if (value == null) + { + return; + } + + target.Write((byte)id); + target.WriteWithLengthPrefix(value); + } + + private static void WriteUserProperties(List userProperties, MqttPacketWriter target) + { + if (userProperties == null || userProperties.Count == 0) + { + return; + } + + var propertyWriter = new MqttPacketWriter(); + foreach (var property in userProperties) + { + propertyWriter.WriteWithLengthPrefix(property.Name); + propertyWriter.WriteWithLengthPrefix(property.Value); + } + + target.Write((byte)MqttMessagePropertyID.UserProperty); + target.WriteVariableLengthInteger((uint)propertyWriter.Length); + target.Write(propertyWriter); + } + } +} diff --git a/Source/MQTTnet/Implementations/MqttClientAdapterFactory.cs b/Source/MQTTnet/Implementations/MqttClientAdapterFactory.cs index 169d18e2..9bf40964 100644 --- a/Source/MQTTnet/Implementations/MqttClientAdapterFactory.cs +++ b/Source/MQTTnet/Implementations/MqttClientAdapterFactory.cs @@ -2,7 +2,7 @@ using MQTTnet.Adapter; using MQTTnet.Client; using MQTTnet.Diagnostics; -using MQTTnet.Serializer; +using MQTTnet.Formatter; namespace MQTTnet.Implementations { diff --git a/Source/MQTTnet/Implementations/MqttTcpServerAdapter.Uwp.cs b/Source/MQTTnet/Implementations/MqttTcpServerAdapter.Uwp.cs index d494b910..ec226b22 100644 --- a/Source/MQTTnet/Implementations/MqttTcpServerAdapter.Uwp.cs +++ b/Source/MQTTnet/Implementations/MqttTcpServerAdapter.Uwp.cs @@ -4,7 +4,7 @@ using System.Threading.Tasks; using Windows.Networking.Sockets; using MQTTnet.Adapter; using MQTTnet.Diagnostics; -using MQTTnet.Serializer; +using MQTTnet.Formatter; using MQTTnet.Server; namespace MQTTnet.Implementations diff --git a/Source/MQTTnet/Implementations/MqttTcpServerListener.cs b/Source/MQTTnet/Implementations/MqttTcpServerListener.cs index 59da016a..8c6258e0 100644 --- a/Source/MQTTnet/Implementations/MqttTcpServerListener.cs +++ b/Source/MQTTnet/Implementations/MqttTcpServerListener.cs @@ -9,7 +9,7 @@ using System.Threading; using System.Threading.Tasks; using MQTTnet.Adapter; using MQTTnet.Diagnostics; -using MQTTnet.Serializer; +using MQTTnet.Formatter; using MQTTnet.Server; namespace MQTTnet.Implementations diff --git a/Source/MQTTnet/Serializer/Extensions.cs b/Source/MQTTnet/Internal/ArraySegmentExtensions.cs similarity index 85% rename from Source/MQTTnet/Serializer/Extensions.cs rename to Source/MQTTnet/Internal/ArraySegmentExtensions.cs index 00ad7cfb..f0054f47 100644 --- a/Source/MQTTnet/Serializer/Extensions.cs +++ b/Source/MQTTnet/Internal/ArraySegmentExtensions.cs @@ -1,8 +1,8 @@ using System; -namespace MQTTnet.Serializer +namespace MQTTnet.Internal { - public static class Extensions + public static class ArraySegmentExtensions { public static byte[] ToArray(this ArraySegment source) { diff --git a/Source/MQTTnet/MQTTnet.csproj b/Source/MQTTnet/MQTTnet.csproj index 68099169..8b4038d0 100644 --- a/Source/MQTTnet/MQTTnet.csproj +++ b/Source/MQTTnet/MQTTnet.csproj @@ -69,4 +69,10 @@ + + + C:\Program Files\dotnet\sdk\NuGetFallbackFolder\microsoft.netcore.app\2.1.0\ref\netcoreapp2.1\System.Net.Requests.dll + + + \ No newline at end of file diff --git a/Source/MQTTnet/Packets/MqttAuthPacket.cs b/Source/MQTTnet/Packets/MqttAuthPacket.cs new file mode 100644 index 00000000..aa8e9a60 --- /dev/null +++ b/Source/MQTTnet/Packets/MqttAuthPacket.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; +using MQTTnet.Packets.Properties; +using MQTTnet.Protocol; + +namespace MQTTnet.Packets +{ + /// + /// Added in MQTTv5.0.0. + /// + public class MqttAuthPacket : MqttBasePacket + { + public MqttAuthenticateReasonCode ReasonCode { get; set; } + + public List Properties { get; set; } + } +} diff --git a/Source/MQTTnet/Packets/MqttBasePublishPacket.cs b/Source/MQTTnet/Packets/MqttBasePublishPacket.cs index ffafc537..67e68879 100644 --- a/Source/MQTTnet/Packets/MqttBasePublishPacket.cs +++ b/Source/MQTTnet/Packets/MqttBasePublishPacket.cs @@ -1,7 +1,15 @@ -namespace MQTTnet.Packets +using System.Collections.Generic; +using MQTTnet.Packets.Properties; + +namespace MQTTnet.Packets { public class MqttBasePublishPacket : MqttBasePacket, IMqttPacketWithIdentifier { public ushort? PacketIdentifier { get; set; } + + /// + /// Added in MQTTv5.0.0. + /// + public List Properties { get; set; } } } diff --git a/Source/MQTTnet/Packets/MqttConnAckPacket.cs b/Source/MQTTnet/Packets/MqttConnAckPacket.cs index c4532167..d79b44af 100644 --- a/Source/MQTTnet/Packets/MqttConnAckPacket.cs +++ b/Source/MQTTnet/Packets/MqttConnAckPacket.cs @@ -1,15 +1,53 @@ -using MQTTnet.Protocol; +using System.Collections.Generic; +using MQTTnet.Protocol; namespace MQTTnet.Packets { public class MqttConnAckPacket : MqttBasePacket { + public MqttConnectReturnCode ConnectReturnCode { get; set; } + /// /// Added in MQTTv3.1.1. /// public bool IsSessionPresent { get; set; } - public MqttConnectReturnCode ConnectReturnCode { get; set; } + #region Added in MQTTv5 + + public uint? SessionExpiryIntervalProperty { get; set; } + + // TODO: Add enum + public byte? ReceiveMaximumProperty { get; set; } + + public bool? RetainAvailableProperty { get; set; } + + public uint? MaximumPacketSizeProperty { get; set; } + + public string AssignedClientIdentifierProperty { get; set; } + + public ushort? TopicAliasMaximumProperty { get; set; } + + public string ReasonStringProperty { get; set; } + + public List UserPropertiesProperty { get; set; } + + public bool? WildcardSubscriptionAvailableProperty { get; set; } + + public bool? SubscriptionIdentifiersAvailableProperty { get; set; } + + public bool? SharedSubscriptionAvailableProperty { get; set; } + + public ushort? ServerKeepAliveProperty { get; set; } + + public string ResponseInformationProperty { get; set; } + + public string ServerReferenceProperty { get; set; } + + public string AuthenticationMethodProperty { get; set; } + + public byte[] AuthenticationDataProperty { get; set; } + + #endregion public override string ToString() { diff --git a/Source/MQTTnet/Packets/MqttConnectPacket.cs b/Source/MQTTnet/Packets/MqttConnectPacket.cs index ec1b953a..0708123b 100644 --- a/Source/MQTTnet/Packets/MqttConnectPacket.cs +++ b/Source/MQTTnet/Packets/MqttConnectPacket.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using MQTTnet.Packets.Properties; namespace MQTTnet.Packets { @@ -24,7 +23,29 @@ namespace MQTTnet.Packets public MqttApplicationMessage WillMessage { get; set; } - public List Properties { get; set; } + #region Added in MQTTv5 + + public uint? WillDelayIntervalProperty { get; set; } + + public uint? SessionExpiryIntervalProperty { get; set; } + + public string AuthenticationMethodProperty { get; set; } + + public byte[] AuthenticationDataProperty { get; set; } + + public bool? RequestProblemInformationProperty { get; set; } + + public bool? RequestResponseInformationProperty { get; set; } + + public ushort? ReceiveMaximumProperty { get; set; } + + public ushort? TopicAliasMaximumProperty { get; set; } + + public uint? MaximumPacketSizeProperty { get; set; } + + public List UserPropertiesProperty { get; set; } + + #endregion public override string ToString() { diff --git a/Source/MQTTnet/Packets/MqttDisconnectPacket.cs b/Source/MQTTnet/Packets/MqttDisconnectPacket.cs index 621829a2..5dbbd497 100644 --- a/Source/MQTTnet/Packets/MqttDisconnectPacket.cs +++ b/Source/MQTTnet/Packets/MqttDisconnectPacket.cs @@ -1,7 +1,15 @@ -namespace MQTTnet.Packets +using System.Collections.Generic; +using MQTTnet.Packets.Properties; + +namespace MQTTnet.Packets { public class MqttDisconnectPacket : MqttBasePacket { + /// + /// Added in MQTTv5.0.0. + /// + public List Properties { get; set; } + public override string ToString() { return "Disconnect"; diff --git a/Source/MQTTnet/Packets/MqttPublishPacket.cs b/Source/MQTTnet/Packets/MqttPublishPacket.cs index 55144a9e..374f913b 100644 --- a/Source/MQTTnet/Packets/MqttPublishPacket.cs +++ b/Source/MQTTnet/Packets/MqttPublishPacket.cs @@ -1,4 +1,6 @@ -using MQTTnet.Protocol; +using System.Collections.Generic; +using MQTTnet.Packets.Properties; +using MQTTnet.Protocol; namespace MQTTnet.Packets { @@ -14,6 +16,11 @@ namespace MQTTnet.Packets public byte[] Payload { get; set; } + /// + /// Added in MQTTv5.0.0. + /// + public List Properties { get; set; } + public override string ToString() { return "Publish: [Topic=" + Topic + "]" + diff --git a/Source/MQTTnet/Packets/MqttSubscribePacket.cs b/Source/MQTTnet/Packets/MqttSubscribePacket.cs index 60086c47..89a1efe6 100644 --- a/Source/MQTTnet/Packets/MqttSubscribePacket.cs +++ b/Source/MQTTnet/Packets/MqttSubscribePacket.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Linq; +using MQTTnet.Packets.Properties; namespace MQTTnet.Packets { @@ -9,6 +10,11 @@ namespace MQTTnet.Packets public IList TopicFilters { get; set; } = new List(); + /// + /// Added in MQTTv5.0.0. + /// + public List Properties { get; set; } + public override string ToString() { var topicFiltersText = string.Join(",", TopicFilters.Select(f => f.Topic + "@" + f.QualityOfServiceLevel)); diff --git a/Source/MQTTnet/Packets/MqttUserProperty.cs b/Source/MQTTnet/Packets/MqttUserProperty.cs new file mode 100644 index 00000000..eaf9eae3 --- /dev/null +++ b/Source/MQTTnet/Packets/MqttUserProperty.cs @@ -0,0 +1,17 @@ +using System; + +namespace MQTTnet.Packets +{ + public class MqttUserProperty + { + public MqttUserProperty(string name, string value) + { + Name = name ?? throw new ArgumentNullException(nameof(name)); + Value = value ?? throw new ArgumentNullException(nameof(value)); + } + + public string Name { get; } + + public string Value { get; } + } +} diff --git a/Source/MQTTnet/Packets/Properties/AssignedClientIdentifierProperty.cs b/Source/MQTTnet/Packets/Properties/AssignedClientIdentifierProperty.cs index b7fd3cd7..bda8e20e 100644 --- a/Source/MQTTnet/Packets/Properties/AssignedClientIdentifierProperty.cs +++ b/Source/MQTTnet/Packets/Properties/AssignedClientIdentifierProperty.cs @@ -1,9 +1,11 @@ -namespace MQTTnet.Packets.Properties +using MQTTnet.Packets.Properties.BaseTypes; + +namespace MQTTnet.Packets.Properties { public class AssignedClientIdentifierProperty : StringProperty { public AssignedClientIdentifierProperty(string value) - : base((byte)PropertyID.AssignedClientIdentifer, value) + : base((byte)MqttMessagePropertyID.AssignedClientIdentifer, value) { } } diff --git a/Source/MQTTnet/Packets/Properties/AuthenticationDataProperty.cs b/Source/MQTTnet/Packets/Properties/AuthenticationDataProperty.cs index 021d9e94..48cba80e 100644 --- a/Source/MQTTnet/Packets/Properties/AuthenticationDataProperty.cs +++ b/Source/MQTTnet/Packets/Properties/AuthenticationDataProperty.cs @@ -1,11 +1,12 @@ using System; +using MQTTnet.Packets.Properties.BaseTypes; namespace MQTTnet.Packets.Properties { public class AuthenticationDataProperty : BinaryDataProperty { public AuthenticationDataProperty(ArraySegment data) - : base((byte)PropertyID.AuthenticationData, data) + : base((byte)MqttMessagePropertyID.AuthenticationData, data) { } } diff --git a/Source/MQTTnet/Packets/Properties/AuthenticationMethodProperty.cs b/Source/MQTTnet/Packets/Properties/AuthenticationMethodProperty.cs index 7de63c3d..9693278b 100644 --- a/Source/MQTTnet/Packets/Properties/AuthenticationMethodProperty.cs +++ b/Source/MQTTnet/Packets/Properties/AuthenticationMethodProperty.cs @@ -1,9 +1,11 @@ -namespace MQTTnet.Packets.Properties +using MQTTnet.Packets.Properties.BaseTypes; + +namespace MQTTnet.Packets.Properties { public class AuthenticationMethodProperty : StringProperty { public AuthenticationMethodProperty(string value) - : base((byte)PropertyID.AuthenticationMethod, value) + : base((byte)MqttMessagePropertyID.AuthenticationMethod, value) { } } diff --git a/Source/MQTTnet/Packets/Properties/BaseTypes/BinaryDataProperty.cs b/Source/MQTTnet/Packets/Properties/BaseTypes/BinaryDataProperty.cs index 2037b8ef..484d5ee7 100644 --- a/Source/MQTTnet/Packets/Properties/BaseTypes/BinaryDataProperty.cs +++ b/Source/MQTTnet/Packets/Properties/BaseTypes/BinaryDataProperty.cs @@ -1,7 +1,7 @@ using System; -using MQTTnet.Serializer; +using MQTTnet.Formatter; -namespace MQTTnet.Packets.Properties +namespace MQTTnet.Packets.Properties.BaseTypes { public class BinaryDataProperty : IProperty { @@ -24,6 +24,7 @@ namespace MQTTnet.Packets.Properties { if (writer == null) throw new ArgumentNullException(nameof(writer)); + writer.Write(Id); writer.Write((ushort)Data.Count); writer.Write(Data); } diff --git a/Source/MQTTnet/Packets/Properties/BaseTypes/ByteProperty.cs b/Source/MQTTnet/Packets/Properties/BaseTypes/ByteProperty.cs index e3e41210..1bfb9aab 100644 --- a/Source/MQTTnet/Packets/Properties/BaseTypes/ByteProperty.cs +++ b/Source/MQTTnet/Packets/Properties/BaseTypes/ByteProperty.cs @@ -1,7 +1,7 @@ using System; -using MQTTnet.Serializer; +using MQTTnet.Formatter; -namespace MQTTnet.Packets.Properties +namespace MQTTnet.Packets.Properties.BaseTypes { public class ByteProperty : IProperty { @@ -18,7 +18,8 @@ namespace MQTTnet.Packets.Properties public void WriteTo(MqttPacketWriter writer) { if (writer == null) throw new ArgumentNullException(nameof(writer)); - + + writer.Write(Id); writer.Write(Value); } } diff --git a/Source/MQTTnet/Packets/Properties/BaseTypes/FourByteIntegerProperty.cs b/Source/MQTTnet/Packets/Properties/BaseTypes/FourByteIntegerProperty.cs index 56aafe34..594ec3e0 100644 --- a/Source/MQTTnet/Packets/Properties/BaseTypes/FourByteIntegerProperty.cs +++ b/Source/MQTTnet/Packets/Properties/BaseTypes/FourByteIntegerProperty.cs @@ -1,5 +1,5 @@ using System; -using MQTTnet.Serializer; +using MQTTnet.Formatter; namespace MQTTnet.Packets.Properties.BaseTypes { @@ -20,6 +20,7 @@ namespace MQTTnet.Packets.Properties.BaseTypes if (writer == null) throw new ArgumentNullException(nameof(writer)); // TODO: Check if order must be reversed like for ushort. + writer.Write(Id); var bytes = BitConverter.GetBytes(Value); writer.Write(bytes, 0, bytes.Length); } diff --git a/Source/MQTTnet/Packets/Properties/BaseTypes/StringProperty.cs b/Source/MQTTnet/Packets/Properties/BaseTypes/StringProperty.cs index b7910698..ff3c1fbf 100644 --- a/Source/MQTTnet/Packets/Properties/BaseTypes/StringProperty.cs +++ b/Source/MQTTnet/Packets/Properties/BaseTypes/StringProperty.cs @@ -1,7 +1,7 @@ using System; -using MQTTnet.Serializer; +using MQTTnet.Formatter; -namespace MQTTnet.Packets.Properties +namespace MQTTnet.Packets.Properties.BaseTypes { public class StringProperty : IProperty { @@ -19,6 +19,7 @@ namespace MQTTnet.Packets.Properties { if (writer == null) throw new ArgumentNullException(nameof(writer)); + writer.Write(Id); writer.WriteWithLengthPrefix(Value); } } diff --git a/Source/MQTTnet/Packets/Properties/BaseTypes/TwoByteIntegerProperty.cs b/Source/MQTTnet/Packets/Properties/BaseTypes/TwoByteIntegerProperty.cs index 4ed67766..8e5a4da0 100644 --- a/Source/MQTTnet/Packets/Properties/BaseTypes/TwoByteIntegerProperty.cs +++ b/Source/MQTTnet/Packets/Properties/BaseTypes/TwoByteIntegerProperty.cs @@ -1,5 +1,5 @@ using System; -using MQTTnet.Serializer; +using MQTTnet.Formatter; namespace MQTTnet.Packets.Properties.BaseTypes { @@ -19,6 +19,7 @@ namespace MQTTnet.Packets.Properties.BaseTypes { if (writer == null) throw new ArgumentNullException(nameof(writer)); + writer.Write(Id); writer.Write(Value); } } diff --git a/Source/MQTTnet/Packets/Properties/BaseTypes/VariableByteIntegerProperty.cs b/Source/MQTTnet/Packets/Properties/BaseTypes/VariableByteIntegerProperty.cs index 26eef480..e1fd355c 100644 --- a/Source/MQTTnet/Packets/Properties/BaseTypes/VariableByteIntegerProperty.cs +++ b/Source/MQTTnet/Packets/Properties/BaseTypes/VariableByteIntegerProperty.cs @@ -1,5 +1,5 @@ using System; -using MQTTnet.Serializer; +using MQTTnet.Formatter; namespace MQTTnet.Packets.Properties.BaseTypes { @@ -20,6 +20,8 @@ namespace MQTTnet.Packets.Properties.BaseTypes if (writer == null) throw new ArgumentNullException(nameof(writer)); var buffer = MqttPacketWriter.EncodeVariableByteInteger(Value); + + writer.Write(Id); writer.Write(buffer); } } diff --git a/Source/MQTTnet/Packets/Properties/ContentTypeProperty.cs b/Source/MQTTnet/Packets/Properties/ContentTypeProperty.cs index 1e7ec422..459b70d6 100644 --- a/Source/MQTTnet/Packets/Properties/ContentTypeProperty.cs +++ b/Source/MQTTnet/Packets/Properties/ContentTypeProperty.cs @@ -1,9 +1,11 @@ -namespace MQTTnet.Packets.Properties +using MQTTnet.Packets.Properties.BaseTypes; + +namespace MQTTnet.Packets.Properties { public class ContentTypeProperty : StringProperty { public ContentTypeProperty(string value) - : base((byte)PropertyID.ContentType, value) + : base((byte)MqttMessagePropertyID.ContentType, value) { } } diff --git a/Source/MQTTnet/Packets/Properties/CorrelationDataProperty.cs b/Source/MQTTnet/Packets/Properties/CorrelationDataProperty.cs index f9104e8c..8b517e3c 100644 --- a/Source/MQTTnet/Packets/Properties/CorrelationDataProperty.cs +++ b/Source/MQTTnet/Packets/Properties/CorrelationDataProperty.cs @@ -1,11 +1,12 @@ using System; +using MQTTnet.Packets.Properties.BaseTypes; namespace MQTTnet.Packets.Properties { public class CorrelationDataProperty : BinaryDataProperty { public CorrelationDataProperty(ArraySegment data) - : base((byte)PropertyID.CorrelationData, data) + : base((byte)MqttMessagePropertyID.CorrelationData, data) { } } diff --git a/Source/MQTTnet/Packets/Properties/IProperty.cs b/Source/MQTTnet/Packets/Properties/IProperty.cs index 739e0cd4..484e48ba 100644 --- a/Source/MQTTnet/Packets/Properties/IProperty.cs +++ b/Source/MQTTnet/Packets/Properties/IProperty.cs @@ -1,4 +1,4 @@ -using MQTTnet.Serializer; +using MQTTnet.Formatter; namespace MQTTnet.Packets.Properties { diff --git a/Source/MQTTnet/Packets/Properties/MaximumPacketSizeProperty.cs b/Source/MQTTnet/Packets/Properties/MaximumPacketSizeProperty.cs new file mode 100644 index 00000000..be060648 --- /dev/null +++ b/Source/MQTTnet/Packets/Properties/MaximumPacketSizeProperty.cs @@ -0,0 +1,12 @@ +using MQTTnet.Packets.Properties.BaseTypes; + +namespace MQTTnet.Packets.Properties +{ + public class MaximumPacketSizeProperty : FourByteIntegerValue + { + public MaximumPacketSizeProperty(uint value) + : base((byte)MqttMessagePropertyID.MaximumPacketSize, value) + { + } + } +} diff --git a/Source/MQTTnet/Packets/Properties/MaximumQoSProperty.cs b/Source/MQTTnet/Packets/Properties/MaximumQoSProperty.cs new file mode 100644 index 00000000..168b8ca0 --- /dev/null +++ b/Source/MQTTnet/Packets/Properties/MaximumQoSProperty.cs @@ -0,0 +1,12 @@ +using MQTTnet.Packets.Properties.BaseTypes; + +namespace MQTTnet.Packets.Properties +{ + public class MaximumQoSProperty : ByteProperty + { + public MaximumQoSProperty(byte value) + : base((byte)MqttMessagePropertyID.MaximumQoS, value) + { + } + } +} diff --git a/Source/MQTTnet/Packets/Properties/MessageExpiryIntervalProperty.cs b/Source/MQTTnet/Packets/Properties/MessageExpiryIntervalProperty.cs index 3526935d..6b944f04 100644 --- a/Source/MQTTnet/Packets/Properties/MessageExpiryIntervalProperty.cs +++ b/Source/MQTTnet/Packets/Properties/MessageExpiryIntervalProperty.cs @@ -5,7 +5,7 @@ namespace MQTTnet.Packets.Properties public class MessageExpiryIntervalProperty : FourByteIntegerValue { public MessageExpiryIntervalProperty(uint value) - : base((byte)PropertyID.MessageExpiryInterval, value) + : base((byte)MqttMessagePropertyID.MessageExpiryInterval, value) { } } diff --git a/Source/MQTTnet/Packets/Properties/PropertyID.cs b/Source/MQTTnet/Packets/Properties/MqttMessagePropertyID.cs similarity index 96% rename from Source/MQTTnet/Packets/Properties/PropertyID.cs rename to Source/MQTTnet/Packets/Properties/MqttMessagePropertyID.cs index 69a809ea..2b8666d9 100644 --- a/Source/MQTTnet/Packets/Properties/PropertyID.cs +++ b/Source/MQTTnet/Packets/Properties/MqttMessagePropertyID.cs @@ -1,6 +1,6 @@ namespace MQTTnet.Packets.Properties { - public enum PropertyID + public enum MqttMessagePropertyID { PayloadFormatIndicator = 1, MessageExpiryInterval = 2, diff --git a/Source/MQTTnet/Packets/Properties/PayloadFormatIndicatorProperty.cs b/Source/MQTTnet/Packets/Properties/PayloadFormatIndicatorProperty.cs index f30f5b16..79a7d22d 100644 --- a/Source/MQTTnet/Packets/Properties/PayloadFormatIndicatorProperty.cs +++ b/Source/MQTTnet/Packets/Properties/PayloadFormatIndicatorProperty.cs @@ -1,9 +1,11 @@ -namespace MQTTnet.Packets.Properties +using MQTTnet.Packets.Properties.BaseTypes; + +namespace MQTTnet.Packets.Properties { public class PayloadFormatIndicatorProperty : ByteProperty { public PayloadFormatIndicatorProperty(byte value) - : base((byte)PropertyID.PayloadFormatIndicator, value) + : base((byte)MqttMessagePropertyID.PayloadFormatIndicator, value) { } } diff --git a/Source/MQTTnet/Packets/Properties/ReasonStringProperty.cs b/Source/MQTTnet/Packets/Properties/ReasonStringProperty.cs new file mode 100644 index 00000000..1a3d4f15 --- /dev/null +++ b/Source/MQTTnet/Packets/Properties/ReasonStringProperty.cs @@ -0,0 +1,12 @@ +using MQTTnet.Packets.Properties.BaseTypes; + +namespace MQTTnet.Packets.Properties +{ + public class ReasonStringProperty : StringProperty + { + public ReasonStringProperty(string value) + : base((byte)MqttMessagePropertyID.ReasonString, value) + { + } + } +} diff --git a/Source/MQTTnet/Packets/Properties/ReceiveMaximumProperty.cs b/Source/MQTTnet/Packets/Properties/ReceiveMaximumProperty.cs new file mode 100644 index 00000000..3263eb03 --- /dev/null +++ b/Source/MQTTnet/Packets/Properties/ReceiveMaximumProperty.cs @@ -0,0 +1,12 @@ +using MQTTnet.Packets.Properties.BaseTypes; + +namespace MQTTnet.Packets.Properties +{ + public class ReceiveMaximumProperty : TwoByteIntegerProperty + { + public ReceiveMaximumProperty(ushort value) + : base((byte)MqttMessagePropertyID.ReceiveMaximum, value) + { + } + } +} diff --git a/Source/MQTTnet/Packets/Properties/RequestProblemInformationProperty.cs b/Source/MQTTnet/Packets/Properties/RequestProblemInformationProperty.cs index 456c9733..537257c6 100644 --- a/Source/MQTTnet/Packets/Properties/RequestProblemInformationProperty.cs +++ b/Source/MQTTnet/Packets/Properties/RequestProblemInformationProperty.cs @@ -1,9 +1,11 @@ -namespace MQTTnet.Packets.Properties +using MQTTnet.Packets.Properties.BaseTypes; + +namespace MQTTnet.Packets.Properties { public class RequestProblemInformationProperty : ByteProperty { public RequestProblemInformationProperty(byte value) - : base((byte)PropertyID.RequestProblemInformation, value) + : base((byte)MqttMessagePropertyID.RequestProblemInformation, value) { } } diff --git a/Source/MQTTnet/Packets/Properties/RequestResponseInformationProperty.cs b/Source/MQTTnet/Packets/Properties/RequestResponseInformationProperty.cs index 9119675b..881f7eae 100644 --- a/Source/MQTTnet/Packets/Properties/RequestResponseInformationProperty.cs +++ b/Source/MQTTnet/Packets/Properties/RequestResponseInformationProperty.cs @@ -1,9 +1,11 @@ -namespace MQTTnet.Packets.Properties +using MQTTnet.Packets.Properties.BaseTypes; + +namespace MQTTnet.Packets.Properties { public class RequestResponseInformationProperty : ByteProperty { public RequestResponseInformationProperty(byte value) - : base((byte)PropertyID.RequestResponseInformation, value) + : base((byte)MqttMessagePropertyID.RequestResponseInformation, value) { } } diff --git a/Source/MQTTnet/Packets/Properties/ResponseInformationProperty.cs b/Source/MQTTnet/Packets/Properties/ResponseInformationProperty.cs new file mode 100644 index 00000000..e080f992 --- /dev/null +++ b/Source/MQTTnet/Packets/Properties/ResponseInformationProperty.cs @@ -0,0 +1,12 @@ +using MQTTnet.Packets.Properties.BaseTypes; + +namespace MQTTnet.Packets.Properties +{ + public class ResponseInformationProperty : StringProperty + { + public ResponseInformationProperty(string value) + : base((byte)MqttMessagePropertyID.ResponseInformation, value) + { + } + } +} diff --git a/Source/MQTTnet/Packets/Properties/ResponseTopicProperty.cs b/Source/MQTTnet/Packets/Properties/ResponseTopicProperty.cs index ad41bf85..5f221863 100644 --- a/Source/MQTTnet/Packets/Properties/ResponseTopicProperty.cs +++ b/Source/MQTTnet/Packets/Properties/ResponseTopicProperty.cs @@ -1,9 +1,11 @@ -namespace MQTTnet.Packets.Properties +using MQTTnet.Packets.Properties.BaseTypes; + +namespace MQTTnet.Packets.Properties { public class ResponseTopicProperty : StringProperty { public ResponseTopicProperty(string value) - : base((byte)PropertyID.ResponseTopic, value) + : base((byte)MqttMessagePropertyID.ResponseTopic, value) { } } diff --git a/Source/MQTTnet/Packets/Properties/RetainAvailableProperty.cs b/Source/MQTTnet/Packets/Properties/RetainAvailableProperty.cs new file mode 100644 index 00000000..f407bdcc --- /dev/null +++ b/Source/MQTTnet/Packets/Properties/RetainAvailableProperty.cs @@ -0,0 +1,12 @@ +using MQTTnet.Packets.Properties.BaseTypes; + +namespace MQTTnet.Packets.Properties +{ + public class RetainAvailableProperty : ByteProperty + { + public RetainAvailableProperty(byte value) + : base((byte)MqttMessagePropertyID.RetainAvailable, value) + { + } + } +} diff --git a/Source/MQTTnet/Packets/Properties/ServerKeepAliveProperty.cs b/Source/MQTTnet/Packets/Properties/ServerKeepAliveProperty.cs index a247fb47..272860c4 100644 --- a/Source/MQTTnet/Packets/Properties/ServerKeepAliveProperty.cs +++ b/Source/MQTTnet/Packets/Properties/ServerKeepAliveProperty.cs @@ -5,7 +5,7 @@ namespace MQTTnet.Packets.Properties public class ServerKeepAliveProperty : TwoByteIntegerProperty { public ServerKeepAliveProperty(ushort value) - : base((byte)PropertyID.ServerKeepAlive, value) + : base((byte)MqttMessagePropertyID.ServerKeepAlive, value) { } } diff --git a/Source/MQTTnet/Packets/Properties/ServerReferenceProperty.cs b/Source/MQTTnet/Packets/Properties/ServerReferenceProperty.cs new file mode 100644 index 00000000..2b50e793 --- /dev/null +++ b/Source/MQTTnet/Packets/Properties/ServerReferenceProperty.cs @@ -0,0 +1,12 @@ +using MQTTnet.Packets.Properties.BaseTypes; + +namespace MQTTnet.Packets.Properties +{ + public class ServerReferenceProperty : StringProperty + { + public ServerReferenceProperty(string value) + : base((byte)MqttMessagePropertyID.ServerReference, value) + { + } + } +} diff --git a/Source/MQTTnet/Packets/Properties/SessionExpiryIntervalProperty.cs b/Source/MQTTnet/Packets/Properties/SessionExpiryIntervalProperty.cs index f28422e3..651de5d2 100644 --- a/Source/MQTTnet/Packets/Properties/SessionExpiryIntervalProperty.cs +++ b/Source/MQTTnet/Packets/Properties/SessionExpiryIntervalProperty.cs @@ -5,7 +5,7 @@ namespace MQTTnet.Packets.Properties public class SessionExpiryIntervalProperty : FourByteIntegerValue { public SessionExpiryIntervalProperty(uint value) - : base((byte)PropertyID.SessionExpiryInterval, value) + : base((byte)MqttMessagePropertyID.SessionExpiryInterval, value) { } } diff --git a/Source/MQTTnet/Packets/Properties/SharedSubscriptionAvailableProperty.cs b/Source/MQTTnet/Packets/Properties/SharedSubscriptionAvailableProperty.cs new file mode 100644 index 00000000..8461fa38 --- /dev/null +++ b/Source/MQTTnet/Packets/Properties/SharedSubscriptionAvailableProperty.cs @@ -0,0 +1,12 @@ +using MQTTnet.Packets.Properties.BaseTypes; + +namespace MQTTnet.Packets.Properties +{ + public class SharedSubscriptionAvailableProperty : ByteProperty + { + public SharedSubscriptionAvailableProperty(byte value) + : base((byte)MqttMessagePropertyID.SharedSubscriptionAvailable, value) + { + } + } +} diff --git a/Source/MQTTnet/Packets/Properties/SubscriptionIdentifierAvailable.cs b/Source/MQTTnet/Packets/Properties/SubscriptionIdentifierAvailable.cs new file mode 100644 index 00000000..d4789b23 --- /dev/null +++ b/Source/MQTTnet/Packets/Properties/SubscriptionIdentifierAvailable.cs @@ -0,0 +1,12 @@ +using MQTTnet.Packets.Properties.BaseTypes; + +namespace MQTTnet.Packets.Properties +{ + public class SubscriptionIdentifierAvailable : ByteProperty + { + public SubscriptionIdentifierAvailable(byte value) + : base((byte)MqttMessagePropertyID.SubscriptionIdentifierAvailable, value) + { + } + } +} diff --git a/Source/MQTTnet/Packets/Properties/SubscriptionIdentifierProperty.cs b/Source/MQTTnet/Packets/Properties/SubscriptionIdentifierProperty.cs index dca6ad58..9f9a56fe 100644 --- a/Source/MQTTnet/Packets/Properties/SubscriptionIdentifierProperty.cs +++ b/Source/MQTTnet/Packets/Properties/SubscriptionIdentifierProperty.cs @@ -5,7 +5,7 @@ namespace MQTTnet.Packets.Properties public class SubscriptionIdentifierProperty : VariableByteIntegerProperty { public SubscriptionIdentifierProperty(uint value) - : base((byte)PropertyID.SubscriptionIdentifier, value) + : base((byte)MqttMessagePropertyID.SubscriptionIdentifier, value) { } } diff --git a/Source/MQTTnet/Packets/Properties/TopicAliasMaximumProperty.cs b/Source/MQTTnet/Packets/Properties/TopicAliasMaximumProperty.cs new file mode 100644 index 00000000..02c36c08 --- /dev/null +++ b/Source/MQTTnet/Packets/Properties/TopicAliasMaximumProperty.cs @@ -0,0 +1,12 @@ +using MQTTnet.Packets.Properties.BaseTypes; + +namespace MQTTnet.Packets.Properties +{ + public class TopicAliasMaximumProperty : TwoByteIntegerProperty + { + public TopicAliasMaximumProperty(ushort value) + : base((byte)MqttMessagePropertyID.TopicAliasMaximum, value) + { + } + } +} diff --git a/Source/MQTTnet/Packets/Properties/TopicAliasProperty.cs b/Source/MQTTnet/Packets/Properties/TopicAliasProperty.cs new file mode 100644 index 00000000..41c4495e --- /dev/null +++ b/Source/MQTTnet/Packets/Properties/TopicAliasProperty.cs @@ -0,0 +1,12 @@ +using MQTTnet.Packets.Properties.BaseTypes; + +namespace MQTTnet.Packets.Properties +{ + public class TopicAliasProperty : TwoByteIntegerProperty + { + public TopicAliasProperty(ushort value) + : base((byte)MqttMessagePropertyID.TopicAlias, value) + { + } + } +} diff --git a/Source/MQTTnet/Packets/Properties/UserPropertyProperty.cs b/Source/MQTTnet/Packets/Properties/UserPropertyProperty.cs new file mode 100644 index 00000000..0708d3f4 --- /dev/null +++ b/Source/MQTTnet/Packets/Properties/UserPropertyProperty.cs @@ -0,0 +1,29 @@ +using System; +using MQTTnet.Formatter; + +namespace MQTTnet.Packets.Properties +{ + public class UserPropertyProperty : IProperty + { + public UserPropertyProperty(string name, string value) + { + Name = name ?? throw new ArgumentNullException(nameof(name)); + Value = value ?? throw new ArgumentNullException(nameof(value)); + } + + public byte Id { get; } = (byte)MqttMessagePropertyID.UserProperty; + + public string Name { get; } + + public string Value { get; } + + public void WriteTo(MqttPacketWriter writer) + { + if (writer == null) throw new ArgumentNullException(nameof(writer)); + + writer.Write(Id); + writer.WriteWithLengthPrefix(Name); + writer.WriteWithLengthPrefix(Value); + } + } +} diff --git a/Source/MQTTnet/Packets/Properties/WildcardSubscriptionAvailableProperty.cs b/Source/MQTTnet/Packets/Properties/WildcardSubscriptionAvailableProperty.cs new file mode 100644 index 00000000..7c901ec4 --- /dev/null +++ b/Source/MQTTnet/Packets/Properties/WildcardSubscriptionAvailableProperty.cs @@ -0,0 +1,12 @@ +using MQTTnet.Packets.Properties.BaseTypes; + +namespace MQTTnet.Packets.Properties +{ + public class WildcardSubscriptionAvailableProperty : ByteProperty + { + public WildcardSubscriptionAvailableProperty(byte value) + : base((byte)MqttMessagePropertyID.WildcardSubscriptionAvailable, value) + { + } + } +} diff --git a/Source/MQTTnet/Packets/Properties/WillDelayIntervalProperty.cs b/Source/MQTTnet/Packets/Properties/WillDelayIntervalProperty.cs index e285f868..f40bfbc3 100644 --- a/Source/MQTTnet/Packets/Properties/WillDelayIntervalProperty.cs +++ b/Source/MQTTnet/Packets/Properties/WillDelayIntervalProperty.cs @@ -5,7 +5,7 @@ namespace MQTTnet.Packets.Properties public class WillDelayIntervalProperty : FourByteIntegerValue { public WillDelayIntervalProperty(uint value) - : base((byte)PropertyID.WillDelayInterval, value) + : base((byte)MqttMessagePropertyID.WillDelayInterval, value) { } } diff --git a/Source/MQTTnet/Protocol/MqttAuthenticateReasonCode.cs b/Source/MQTTnet/Protocol/MqttAuthenticateReasonCode.cs new file mode 100644 index 00000000..48d79481 --- /dev/null +++ b/Source/MQTTnet/Protocol/MqttAuthenticateReasonCode.cs @@ -0,0 +1,9 @@ +namespace MQTTnet.Protocol +{ + public enum MqttAuthenticateReasonCode + { + Success = 0, + ContinueAuthentication = 24, + ReAuthenticate = 25 + } +} diff --git a/Source/MQTTnet/Serializer/IMqttPacketSerializer.cs b/Source/MQTTnet/Serializer/IMqttPacketSerializer.cs deleted file mode 100644 index 47d1cd8e..00000000 --- a/Source/MQTTnet/Serializer/IMqttPacketSerializer.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; -using MQTTnet.Adapter; -using MQTTnet.Packets; - -namespace MQTTnet.Serializer -{ - public interface IMqttPacketSerializer - { - ArraySegment Serialize(MqttBasePacket mqttPacket); - - MqttBasePacket Deserialize(ReceivedMqttPacket receivedMqttPacket); - - void FreeBuffer(); - } -} \ No newline at end of file diff --git a/Source/MQTTnet/Serializer/MqttV500PacketSerializer.cs b/Source/MQTTnet/Serializer/MqttV500PacketSerializer.cs deleted file mode 100644 index e0241555..00000000 --- a/Source/MQTTnet/Serializer/MqttV500PacketSerializer.cs +++ /dev/null @@ -1,96 +0,0 @@ -using System; -using MQTTnet.Adapter; -using MQTTnet.Exceptions; -using MQTTnet.Packets; -using MQTTnet.Protocol; - -namespace MQTTnet.Serializer -{ - public class MqttV500PacketSerializer : MqttV311PacketSerializer - { - //public ArraySegment Serialize(MqttBasePacket mqttPacket) - //{ - // throw new NotImplementedException(); - //} - - //public MqttBasePacket Deserialize(ReceivedMqttPacket receivedMqttPacket) - //{ - // throw new NotImplementedException(); - //} - - protected override byte SerializeConnectPacket(MqttConnectPacket packet, MqttPacketWriter packetWriter) - { - ValidateConnectPacket(packet); - - packetWriter.WriteWithLengthPrefix("MQTT"); - packetWriter.Write(5); // [3.1.2.2 Protocol Version] - - byte connectFlags = 0x0; - if (packet.CleanSession) - { - connectFlags |= 0x2; - } - - if (packet.WillMessage != null) - { - connectFlags |= 0x4; - connectFlags |= (byte)((byte)packet.WillMessage.QualityOfServiceLevel << 3); - - if (packet.WillMessage.Retain) - { - connectFlags |= 0x20; - } - } - - if (packet.Password != null && packet.Username == null) - { - throw new MqttProtocolViolationException("If the User Name Flag is set to 0, the Password Flag MUST be set to 0 [MQTT-3.1.2-22]."); - } - - if (packet.Password != null) - { - connectFlags |= 0x40; - } - - if (packet.Username != null) - { - connectFlags |= 0x80; - } - - packetWriter.Write(connectFlags); - packetWriter.Write(packet.KeepAlivePeriod); - packetWriter.WriteWithLengthPrefix(packet.ClientId); - - if (packet.WillMessage != null) - { - packetWriter.WriteWithLengthPrefix(packet.WillMessage.Topic); - packetWriter.WriteWithLengthPrefix(packet.WillMessage.Payload); - } - - if (packet.Username != null) - { - packetWriter.WriteWithLengthPrefix(packet.Username); - } - - if (packet.Password != null) - { - packetWriter.WriteWithLengthPrefix(packet.Password); - } - - if (packet.Properties != null) - { - var propertyWriter = new MqttPacketWriter(); - foreach (var property in packet.Properties) - { - propertyWriter.Write(property.Id); - property.WriteTo(propertyWriter); - } - - packetWriter.WriteVariableLengthInteger((uint)propertyWriter.Length); - packetWriter.Write(propertyWriter); - } - - return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.Connect); - } - } -} diff --git a/Source/MQTTnet/Server/IMqttClientSessionStatus.cs b/Source/MQTTnet/Server/IMqttClientSessionStatus.cs index d9c55938..38b52e2e 100644 --- a/Source/MQTTnet/Server/IMqttClientSessionStatus.cs +++ b/Source/MQTTnet/Server/IMqttClientSessionStatus.cs @@ -1,6 +1,6 @@ using System; using System.Threading.Tasks; -using MQTTnet.Serializer; +using MQTTnet.Formatter; namespace MQTTnet.Server { diff --git a/Source/MQTTnet/Server/MqttClientSessionStatus.cs b/Source/MQTTnet/Server/MqttClientSessionStatus.cs index 4d83357e..7381f94f 100644 --- a/Source/MQTTnet/Server/MqttClientSessionStatus.cs +++ b/Source/MQTTnet/Server/MqttClientSessionStatus.cs @@ -1,6 +1,6 @@ using System; using System.Threading.Tasks; -using MQTTnet.Serializer; +using MQTTnet.Formatter; namespace MQTTnet.Server { diff --git a/Tests/MQTTnet.AspNetCore.Tests/MqttConnectionContextTest.cs b/Tests/MQTTnet.AspNetCore.Tests/MqttConnectionContextTest.cs index 2ec621de..4461a135 100644 --- a/Tests/MQTTnet.AspNetCore.Tests/MqttConnectionContextTest.cs +++ b/Tests/MQTTnet.AspNetCore.Tests/MqttConnectionContextTest.cs @@ -3,11 +3,11 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using MQTTnet.AspNetCore.Tests.Mockups; using MQTTnet.Exceptions; using MQTTnet.Packets; -using MQTTnet.Serializer; using System; using System.Linq; using System.Threading; using System.Threading.Tasks; +using MQTTnet.Formatter; namespace MQTTnet.AspNetCore.Tests { diff --git a/Tests/MQTTnet.AspNetCore.Tests/ReaderExtensionsTest.cs b/Tests/MQTTnet.AspNetCore.Tests/ReaderExtensionsTest.cs index 332f3eaa..7d5e55ac 100644 --- a/Tests/MQTTnet.AspNetCore.Tests/ReaderExtensionsTest.cs +++ b/Tests/MQTTnet.AspNetCore.Tests/ReaderExtensionsTest.cs @@ -2,8 +2,8 @@ using System.Buffers; using Microsoft.VisualStudio.TestTools.UnitTesting; using MQTTnet.AspNetCore; +using MQTTnet.Formatter.V311; using MQTTnet.Packets; -using MQTTnet.Serializer; namespace MQTTnet.AspNetCore.Tests { @@ -13,9 +13,9 @@ namespace MQTTnet.AspNetCore.Tests [TestMethod] public void TestTryDeserialize() { - var serializer = new MqttV311PacketSerializer(); + var serializer = new MqttV311PacketFormatter(); - var buffer = serializer.Serialize(new MqttPublishPacket() {Topic = "a", Payload = new byte[5]}); + var buffer = serializer.Encode(new MqttPublishPacket() {Topic = "a", Payload = new byte[5]}); var sequence = new ReadOnlySequence(buffer.Array, buffer.Offset, buffer.Count); diff --git a/Tests/MQTTnet.Benchmarks/ChannelAdapterBenchmark.cs b/Tests/MQTTnet.Benchmarks/ChannelAdapterBenchmark.cs index 5ec3c0f6..fb7443cb 100644 --- a/Tests/MQTTnet.Benchmarks/ChannelAdapterBenchmark.cs +++ b/Tests/MQTTnet.Benchmarks/ChannelAdapterBenchmark.cs @@ -3,10 +3,10 @@ using MQTTnet.Adapter; using MQTTnet.Diagnostics; using MQTTnet.Internal; using MQTTnet.Packets; -using MQTTnet.Serializer; using System; using System.IO; using System.Threading; +using MQTTnet.Formatter; namespace MQTTnet.Benchmarks { @@ -28,7 +28,7 @@ namespace MQTTnet.Benchmarks _packet = message.ToPublishPacket(); var serializer = new MqttPacketSerializerAdapter(MqttProtocolVersion.V311); - var serializedPacket = Join(serializer.Serialize(_packet)); + var serializedPacket = Join(serializer.Encode(_packet)); _iterations = 10000; diff --git a/Tests/MQTTnet.Benchmarks/SerializerBenchmark.cs b/Tests/MQTTnet.Benchmarks/SerializerBenchmark.cs index fa407085..53caec15 100644 --- a/Tests/MQTTnet.Benchmarks/SerializerBenchmark.cs +++ b/Tests/MQTTnet.Benchmarks/SerializerBenchmark.cs @@ -1,12 +1,13 @@ using BenchmarkDotNet.Attributes; using MQTTnet.Packets; -using MQTTnet.Serializer; using MQTTnet.Internal; using System; using System.Threading; using System.Threading.Tasks; using MQTTnet.Adapter; using MQTTnet.Channel; +using MQTTnet.Formatter; +using MQTTnet.Formatter.V311; namespace MQTTnet.Benchmarks { @@ -17,7 +18,7 @@ namespace MQTTnet.Benchmarks { private MqttBasePacket _packet; private ArraySegment _serializedPacket; - private IMqttPacketSerializer _serializer; + private IMqttPacketFormatter _serializer; [GlobalSetup] public void Setup() @@ -27,8 +28,8 @@ namespace MQTTnet.Benchmarks .Build(); _packet = message.ToPublishPacket(); - _serializer = new MqttV311PacketSerializer(); - _serializedPacket = _serializer.Serialize(_packet); + _serializer = new MqttV311PacketFormatter(); + _serializedPacket = _serializer.Encode(_packet); } [Benchmark] @@ -36,7 +37,7 @@ namespace MQTTnet.Benchmarks { for (var i = 0; i < 10000; i++) { - _serializer.Serialize(_packet); + _serializer.Encode(_packet); _serializer.FreeBuffer(); } } @@ -58,7 +59,7 @@ namespace MQTTnet.Benchmarks header.Flags, new MqttPacketBodyReader(_serializedPacket.Array, _serializedPacket.Count - header.RemainingLength, _serializedPacket.Array.Length)); - _serializer.Deserialize(receivedPacket); + _serializer.Decode(receivedPacket); } } diff --git a/Tests/MQTTnet.Core.Tests/MqttPacketReaderTests.cs b/Tests/MQTTnet.Core.Tests/MqttPacketReaderTests.cs index 39aeeff2..eb2ad8f7 100644 --- a/Tests/MQTTnet.Core.Tests/MqttPacketReaderTests.cs +++ b/Tests/MQTTnet.Core.Tests/MqttPacketReaderTests.cs @@ -2,8 +2,8 @@ using System.Threading; using Microsoft.VisualStudio.TestTools.UnitTesting; using MQTTnet.Exceptions; +using MQTTnet.Formatter; using MQTTnet.Internal; -using MQTTnet.Serializer; namespace MQTTnet.Core.Tests { diff --git a/Tests/MQTTnet.Core.Tests/MqttPacketSerializerTests.cs b/Tests/MQTTnet.Core.Tests/MqttPacketSerializerTests.cs index 59b68496..ebb379f6 100644 --- a/Tests/MQTTnet.Core.Tests/MqttPacketSerializerTests.cs +++ b/Tests/MQTTnet.Core.Tests/MqttPacketSerializerTests.cs @@ -5,10 +5,12 @@ using System.Text; using System.Threading; using Microsoft.VisualStudio.TestTools.UnitTesting; using MQTTnet.Adapter; +using MQTTnet.Formatter; +using MQTTnet.Formatter.V310; +using MQTTnet.Formatter.V311; using MQTTnet.Internal; using MQTTnet.Packets; using MQTTnet.Protocol; -using MQTTnet.Serializer; namespace MQTTnet.Core.Tests { @@ -153,7 +155,7 @@ namespace MQTTnet.Core.Tests [TestMethod] public void Serialize_LargePacket() { - var serializer = new MqttV311PacketSerializer(); + var serializer = new MqttV311PacketFormatter(); const int payloadLength = 80000; @@ -176,7 +178,7 @@ namespace MQTTnet.Core.Tests Payload = payload }; - var buffer = serializer.Serialize(publishPacket); + var buffer = serializer.Encode(publishPacket); var testChannel = new TestMqttChannel(new MemoryStream(buffer.Array, buffer.Offset, buffer.Count)); var header = MqttPacketReader.ReadFixedHeaderAsync( @@ -191,7 +193,7 @@ namespace MQTTnet.Core.Tests header.Flags, new MqttPacketBodyReader(buffer.Array, eof - header.RemainingLength, buffer.Count + buffer.Offset)); - var packet = (MqttPublishPacket)serializer.Deserialize(receivedPacket); + var packet = (MqttPublishPacket)serializer.Decode(receivedPacket); Assert.AreEqual(publishPacket.Topic, packet.Topic); Assert.IsTrue(publishPacket.Payload.SequenceEqual(packet.Payload)); @@ -509,42 +511,42 @@ namespace MQTTnet.Core.Tests private static void SerializeAndCompare(MqttBasePacket packet, string expectedBase64Value, MqttProtocolVersion protocolVersion = MqttProtocolVersion.V311) { - IMqttPacketSerializer serializer; + IMqttPacketFormatter serializer; if (protocolVersion == MqttProtocolVersion.V311) { - serializer = new MqttV311PacketSerializer(); + serializer = new MqttV311PacketFormatter(); } else if (protocolVersion == MqttProtocolVersion.V310) { - serializer = new MqttV310PacketSerializer(); + serializer = new MqttV310PacketFormatter(); } else { throw new NotSupportedException(); } - var data = serializer.Serialize(packet); + var data = serializer.Encode(packet); Assert.AreEqual(expectedBase64Value, Convert.ToBase64String(Join(data))); } private static void DeserializeAndCompare(MqttBasePacket packet, string expectedBase64Value, MqttProtocolVersion protocolVersion = MqttProtocolVersion.V311) { - IMqttPacketSerializer serializer; + IMqttPacketFormatter serializer; if (protocolVersion == MqttProtocolVersion.V311) { - serializer = new MqttV311PacketSerializer(); + serializer = new MqttV311PacketFormatter(); } else if (protocolVersion == MqttProtocolVersion.V310) { - serializer = new MqttV310PacketSerializer(); + serializer = new MqttV310PacketFormatter(); } else { throw new NotSupportedException(); } - var buffer1 = serializer.Serialize(packet); + var buffer1 = serializer.Encode(packet); using (var headerStream = new MemoryStream(Join(buffer1))) { @@ -555,8 +557,8 @@ namespace MQTTnet.Core.Tests using (var bodyStream = new MemoryStream(Join(buffer1), (int)headerStream.Position, header.RemainingLength)) { - var deserializedPacket = serializer.Deserialize(new ReceivedMqttPacket(header.Flags, new MqttPacketBodyReader(bodyStream.ToArray(), 0, (int)bodyStream.Length))); - var buffer2 = serializer.Serialize(deserializedPacket); + var deserializedPacket = serializer.Decode(new ReceivedMqttPacket(header.Flags, new MqttPacketBodyReader(bodyStream.ToArray(), 0, (int)bodyStream.Length))); + var buffer2 = serializer.Encode(deserializedPacket); Assert.AreEqual(expectedBase64Value, Convert.ToBase64String(Join(buffer2))); } @@ -566,21 +568,21 @@ namespace MQTTnet.Core.Tests private static T Roundtrip(T packet, MqttProtocolVersion protocolVersion = MqttProtocolVersion.V311) where T : MqttBasePacket { - IMqttPacketSerializer serializer; + IMqttPacketFormatter serializer; if (protocolVersion == MqttProtocolVersion.V311) { - serializer = new MqttV311PacketSerializer(); + serializer = new MqttV311PacketFormatter(); } else if (protocolVersion == MqttProtocolVersion.V310) { - serializer = new MqttV310PacketSerializer(); + serializer = new MqttV310PacketFormatter(); } else { throw new NotSupportedException(); } - var buffer1 = serializer.Serialize(packet); + var buffer1 = serializer.Encode(packet); using (var headerStream = new MemoryStream(Join(buffer1))) { @@ -592,7 +594,7 @@ namespace MQTTnet.Core.Tests using (var bodyStream = new MemoryStream(Join(buffer1), (int)headerStream.Position, header.RemainingLength)) { - return (T)serializer.Deserialize(new ReceivedMqttPacket(header.Flags, new MqttPacketBodyReader(bodyStream.ToArray(), 0, (int)bodyStream.Length))); + return (T)serializer.Decode(new ReceivedMqttPacket(header.Flags, new MqttPacketBodyReader(bodyStream.ToArray(), 0, (int)bodyStream.Length))); } } } diff --git a/Tests/MQTTnet.Core.Tests/MqttPacketWriterTests.cs b/Tests/MQTTnet.Core.Tests/MqttPacketWriterTests.cs index 8b73b739..a3209144 100644 --- a/Tests/MQTTnet.Core.Tests/MqttPacketWriterTests.cs +++ b/Tests/MQTTnet.Core.Tests/MqttPacketWriterTests.cs @@ -1,5 +1,5 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -using MQTTnet.Serializer; +using MQTTnet.Formatter; namespace MQTTnet.Core.Tests { diff --git a/Tests/MQTTnet.Core.Tests/TestMqttCommunicationAdapter.cs b/Tests/MQTTnet.Core.Tests/TestMqttCommunicationAdapter.cs index 1902de4e..ceb44e13 100644 --- a/Tests/MQTTnet.Core.Tests/TestMqttCommunicationAdapter.cs +++ b/Tests/MQTTnet.Core.Tests/TestMqttCommunicationAdapter.cs @@ -3,8 +3,8 @@ using System.Collections.Concurrent; using System.Threading; using System.Threading.Tasks; using MQTTnet.Adapter; +using MQTTnet.Formatter; using MQTTnet.Packets; -using MQTTnet.Serializer; namespace MQTTnet.Core.Tests { diff --git a/Tests/MQTTnet.TestApp.UniversalWindows/MainPage.xaml b/Tests/MQTTnet.TestApp.UniversalWindows/MainPage.xaml index c99ebd84..44f3486b 100644 --- a/Tests/MQTTnet.TestApp.UniversalWindows/MainPage.xaml +++ b/Tests/MQTTnet.TestApp.UniversalWindows/MainPage.xaml @@ -37,6 +37,7 @@ + Server: @@ -57,12 +58,19 @@ Keep Alive interval: - Protocol: + Transport: - TCP - WS + TCP + WS Use TLS + + Protocol: + + 5.0.0 + 3.1.1 + 3.1.0 + Clean session diff --git a/Tests/MQTTnet.TestApp.UniversalWindows/MainPage.xaml.cs b/Tests/MQTTnet.TestApp.UniversalWindows/MainPage.xaml.cs index 633f351e..6992b3cc 100644 --- a/Tests/MQTTnet.TestApp.UniversalWindows/MainPage.xaml.cs +++ b/Tests/MQTTnet.TestApp.UniversalWindows/MainPage.xaml.cs @@ -12,9 +12,9 @@ using MQTTnet.Diagnostics; using MQTTnet.Exceptions; using MQTTnet.Extensions.ManagedClient; using MQTTnet.Extensions.Rpc; +using MQTTnet.Formatter; using MQTTnet.Implementations; using MQTTnet.Protocol; -using MQTTnet.Serializer; using MQTTnet.Server; using MqttClientConnectedEventArgs = MQTTnet.Client.MqttClientConnectedEventArgs; using MqttClientDisconnectedEventArgs = MQTTnet.Client.MqttClientDisconnectedEventArgs; @@ -124,7 +124,20 @@ namespace MQTTnet.TestApp.UniversalWindows options.CleanSession = CleanSession.IsChecked == true; options.KeepAlivePeriod = TimeSpan.FromSeconds(double.Parse(KeepAliveInterval.Text)); - + + if (UseMqtt310.IsChecked == true) + { + options.ProtocolVersion = MqttProtocolVersion.V310; + } + else if (UseMqtt311.IsChecked == true) + { + options.ProtocolVersion = MqttProtocolVersion.V311; + } + else if (UseMqtt500.IsChecked == true) + { + options.ProtocolVersion = MqttProtocolVersion.V500; + } + try { if (_mqttClient != null) @@ -426,14 +439,14 @@ namespace MQTTnet.TestApp.UniversalWindows _sessions.Clear(); } - private async void RefreshSessions(object sender, RoutedEventArgs e) + private void RefreshSessions(object sender, RoutedEventArgs e) { if (_mqttServer == null) { return; } - var sessions = await _mqttServer.GetClientSessionsStatusAsync(); + var sessions = _mqttServer.GetClientSessionsStatus(); _sessions.Clear(); foreach (var session in sessions) From 7292cedd2f8c658f5400896c7f8e1297ee4f4f56 Mon Sep 17 00:00:00 2001 From: Christian Kratky Date: Mon, 19 Nov 2018 00:17:19 +0100 Subject: [PATCH 007/124] Encode ConnAck packet. --- Source/MQTTnet/Formatter/V500/MqttV500PacketFormatter.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Source/MQTTnet/Formatter/V500/MqttV500PacketFormatter.cs b/Source/MQTTnet/Formatter/V500/MqttV500PacketFormatter.cs index 08b8491b..34a93405 100644 --- a/Source/MQTTnet/Formatter/V500/MqttV500PacketFormatter.cs +++ b/Source/MQTTnet/Formatter/V500/MqttV500PacketFormatter.cs @@ -100,9 +100,18 @@ namespace MQTTnet.Formatter.V500 WriteProperty(MqttMessagePropertyID.SessionExpiryInterval, packet.SessionExpiryIntervalProperty, packetWriter); WriteProperty(MqttMessagePropertyID.AuthenticationMethod, packet.AuthenticationMethodProperty, packetWriter); WriteProperty(MqttMessagePropertyID.AuthenticationData, packet.AuthenticationDataProperty, packetWriter); + WriteProperty(MqttMessagePropertyID.RetainAvailable, packet.RetainAvailableProperty, packetWriter); WriteProperty(MqttMessagePropertyID.ReceiveMaximum, packet.ReceiveMaximumProperty, packetWriter); + WriteProperty(MqttMessagePropertyID.AssignedClientIdentifer, packet.AssignedClientIdentifierProperty, packetWriter); WriteProperty(MqttMessagePropertyID.TopicAlias, packet.TopicAliasMaximumProperty, packetWriter); + WriteProperty(MqttMessagePropertyID.ReasonString, packet.ReasonStringProperty, packetWriter); WriteProperty(MqttMessagePropertyID.MaximumPacketSize, packet.MaximumPacketSizeProperty, packetWriter); + WriteProperty(MqttMessagePropertyID.WildcardSubscriptionAvailable, packet.WildcardSubscriptionAvailableProperty, packetWriter); + WriteProperty(MqttMessagePropertyID.SubscriptionIdentifierAvailable, packet.SubscriptionIdentifiersAvailableProperty, packetWriter); + WriteProperty(MqttMessagePropertyID.SharedSubscriptionAvailable, packet.SharedSubscriptionAvailableProperty, packetWriter); + WriteProperty(MqttMessagePropertyID.ServerKeepAlive, packet.ServerKeepAliveProperty, packetWriter); + WriteProperty(MqttMessagePropertyID.ResponseInformation, packet.ResponseInformationProperty, packetWriter); + WriteProperty(MqttMessagePropertyID.ServerReference, packet.ServerReferenceProperty, packetWriter); WriteUserProperties(packet.UserPropertiesProperty, packetWriter); return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.ConnAck); From 7baff7c08028b418e838a08f904ca8796011875a Mon Sep 17 00:00:00 2001 From: Christian Kratky Date: Fri, 23 Nov 2018 22:50:44 +0100 Subject: [PATCH 008/124] Refactor property definition for packets. --- .../MqttClientConnectionContextFactory.cs | 2 +- .../MqttConnectionContext.cs | 10 +- .../MqttConnectionHandler.cs | 2 +- .../MqttWebSocketServerAdapter.cs | 2 +- Source/MQTTnet.AspnetCore/ReaderExtensions.cs | 3 +- Source/MQTTnet/Adapter/IMqttChannelAdapter.cs | 2 +- Source/MQTTnet/Adapter/MqttChannelAdapter.cs | 34 +-- Source/MQTTnet/Client/MqttClient.cs | 10 +- .../MQTTnet/Formatter/IMqttPacketFormatter.cs | 2 + Source/MQTTnet/Formatter/MqttFixedHeader.cs | 4 +- .../MQTTnet/Formatter/MqttPacketBodyReader.cs | 109 +++++++-- ...apter.cs => MqttPacketFormatterAdapter.cs} | 42 ++-- Source/MQTTnet/Formatter/MqttPacketReader.cs | 50 ++-- Source/MQTTnet/Formatter/MqttPacketWriter.cs | 27 ++- .../Formatter/V310/MqttV310PacketFormatter.cs | 41 +++- .../Formatter/V500/MqttV500PacketDecoder.cs | 131 ++++++++++- .../Formatter/V500/MqttV500PacketEncoder.cs | 219 +++++++++++++++++- .../Formatter/V500/MqttV500PacketFormatter.cs | 193 +++------------ .../V500/MqttV500PropertiesWriter.cs | 108 +++++++++ .../MqttClientAdapterFactory.cs | 4 +- .../MqttTcpServerAdapter.Uwp.cs | 2 +- .../Implementations/MqttTcpServerListener.cs | 2 +- .../MqttApplicationMessageExtensions.cs | 17 -- Source/MQTTnet/MQTTnet.csproj | 4 + Source/MQTTnet/MqttApplicationMessage.cs | 6 +- Source/MQTTnet/Packets/MqttAuthPacket.cs | 6 +- .../MQTTnet/Packets/MqttBasePublishPacket.cs | 10 +- Source/MQTTnet/Packets/MqttConnAckPacket.cs | 36 +-- .../Packets/MqttConnAckPacketProperties.cs | 40 ++++ Source/MQTTnet/Packets/MqttConnectPacket.cs | 26 +-- .../Packets/MqttConnectPacketProperties.cs | 27 +++ .../MQTTnet/Packets/MqttDisconnectPacket.cs | 10 +- Source/MQTTnet/Packets/MqttPubAckPacket.cs | 14 +- .../Packets/MqttPubAckPacketProperties.cs | 11 + Source/MQTTnet/Packets/MqttPublishPacket.cs | 13 +- .../Packets/MqttPublishPacketProperties.cs | 23 ++ Source/MQTTnet/Packets/MqttSubscribePacket.cs | 6 - .../AssignedClientIdentifierProperty.cs | 12 - .../Properties/AuthenticationDataProperty.cs | 13 -- .../AuthenticationMethodProperty.cs | 12 - .../BaseTypes/BinaryDataProperty.cs | 32 --- .../Properties/BaseTypes/ByteProperty.cs | 26 --- .../BaseTypes/FourByteIntegerProperty.cs | 28 --- .../Properties/BaseTypes/StringProperty.cs | 26 --- .../BaseTypes/TwoByteIntegerProperty.cs | 26 --- .../BaseTypes/VariableByteIntegerProperty.cs | 28 --- .../Packets/Properties/ContentTypeProperty.cs | 12 - .../Properties/CorrelationDataProperty.cs | 13 -- .../MQTTnet/Packets/Properties/IProperty.cs | 11 - .../Properties/MaximumPacketSizeProperty.cs | 12 - .../Packets/Properties/MaximumQoSProperty.cs | 12 - .../MessageExpiryIntervalProperty.cs | 12 - .../PayloadFormatIndicatorProperty.cs | 12 - .../Properties/ReasonStringProperty.cs | 12 - .../Properties/ReceiveMaximumProperty.cs | 12 - .../RequestProblemInformationProperty.cs | 12 - .../RequestResponseInformationProperty.cs | 12 - .../Properties/ResponseInformationProperty.cs | 12 - .../Properties/ResponseTopicProperty.cs | 12 - .../Properties/RetainAvailableProperty.cs | 12 - .../Properties/ServerKeepAliveProperty.cs | 12 - .../Properties/ServerReferenceProperty.cs | 12 - .../SessionExpiryIntervalProperty.cs | 12 - .../SharedSubscriptionAvailableProperty.cs | 12 - .../SubscriptionIdentifierAvailable.cs | 12 - .../SubscriptionIdentifierProperty.cs | 12 - .../Properties/TopicAliasMaximumProperty.cs | 12 - .../Packets/Properties/TopicAliasProperty.cs | 12 - .../Properties/UserPropertyProperty.cs | 29 --- .../WildcardSubscriptionAvailableProperty.cs | 12 - .../Properties/WillDelayIntervalProperty.cs | 12 - .../MQTTnet/Protocol/MqttConnectReasonCode.cs | 28 +++ .../MqttPropertyID.cs} | 4 +- .../MQTTnet/Protocol/MqttPubAckReasonCode.cs | 15 ++ Source/MQTTnet/Server/IMqttClientSession.cs | 3 +- Source/MQTTnet/Server/MqttClientSession.cs | 30 ++- .../Server/MqttClientSessionsManager.cs | 11 +- .../Server/MqttEnqueuedApplicationMessage.cs | 10 +- Source/MQTTnet/Server/MqttServer.cs | 2 +- .../MqttConnectionContextTest.cs | 4 +- .../ReaderExtensionsTest.cs | 11 +- .../ChannelAdapterBenchmark.cs | 10 +- .../MQTTnet.Benchmarks.csproj | 4 +- .../MQTTnet.Benchmarks/SerializerBenchmark.cs | 15 +- .../MqttKeepAliveMonitorTests.cs | 2 +- .../MqttPacketReaderTests.cs | 4 +- .../MqttPacketSerializerTests.cs | 16 +- .../TestMqttCommunicationAdapter.cs | 2 +- .../MQTTnet.TestApp.AspNetCore2.csproj | 4 +- .../MQTTnet.TestApp.UniversalWindows.csproj | 4 +- 90 files changed, 962 insertions(+), 985 deletions(-) rename Source/MQTTnet/Formatter/{MqttPacketSerializerAdapter.cs => MqttPacketFormatterAdapter.cs} (74%) create mode 100644 Source/MQTTnet/Formatter/V500/MqttV500PropertiesWriter.cs create mode 100644 Source/MQTTnet/Packets/MqttConnAckPacketProperties.cs create mode 100644 Source/MQTTnet/Packets/MqttConnectPacketProperties.cs create mode 100644 Source/MQTTnet/Packets/MqttPubAckPacketProperties.cs create mode 100644 Source/MQTTnet/Packets/MqttPublishPacketProperties.cs delete mode 100644 Source/MQTTnet/Packets/Properties/AssignedClientIdentifierProperty.cs delete mode 100644 Source/MQTTnet/Packets/Properties/AuthenticationDataProperty.cs delete mode 100644 Source/MQTTnet/Packets/Properties/AuthenticationMethodProperty.cs delete mode 100644 Source/MQTTnet/Packets/Properties/BaseTypes/BinaryDataProperty.cs delete mode 100644 Source/MQTTnet/Packets/Properties/BaseTypes/ByteProperty.cs delete mode 100644 Source/MQTTnet/Packets/Properties/BaseTypes/FourByteIntegerProperty.cs delete mode 100644 Source/MQTTnet/Packets/Properties/BaseTypes/StringProperty.cs delete mode 100644 Source/MQTTnet/Packets/Properties/BaseTypes/TwoByteIntegerProperty.cs delete mode 100644 Source/MQTTnet/Packets/Properties/BaseTypes/VariableByteIntegerProperty.cs delete mode 100644 Source/MQTTnet/Packets/Properties/ContentTypeProperty.cs delete mode 100644 Source/MQTTnet/Packets/Properties/CorrelationDataProperty.cs delete mode 100644 Source/MQTTnet/Packets/Properties/IProperty.cs delete mode 100644 Source/MQTTnet/Packets/Properties/MaximumPacketSizeProperty.cs delete mode 100644 Source/MQTTnet/Packets/Properties/MaximumQoSProperty.cs delete mode 100644 Source/MQTTnet/Packets/Properties/MessageExpiryIntervalProperty.cs delete mode 100644 Source/MQTTnet/Packets/Properties/PayloadFormatIndicatorProperty.cs delete mode 100644 Source/MQTTnet/Packets/Properties/ReasonStringProperty.cs delete mode 100644 Source/MQTTnet/Packets/Properties/ReceiveMaximumProperty.cs delete mode 100644 Source/MQTTnet/Packets/Properties/RequestProblemInformationProperty.cs delete mode 100644 Source/MQTTnet/Packets/Properties/RequestResponseInformationProperty.cs delete mode 100644 Source/MQTTnet/Packets/Properties/ResponseInformationProperty.cs delete mode 100644 Source/MQTTnet/Packets/Properties/ResponseTopicProperty.cs delete mode 100644 Source/MQTTnet/Packets/Properties/RetainAvailableProperty.cs delete mode 100644 Source/MQTTnet/Packets/Properties/ServerKeepAliveProperty.cs delete mode 100644 Source/MQTTnet/Packets/Properties/ServerReferenceProperty.cs delete mode 100644 Source/MQTTnet/Packets/Properties/SessionExpiryIntervalProperty.cs delete mode 100644 Source/MQTTnet/Packets/Properties/SharedSubscriptionAvailableProperty.cs delete mode 100644 Source/MQTTnet/Packets/Properties/SubscriptionIdentifierAvailable.cs delete mode 100644 Source/MQTTnet/Packets/Properties/SubscriptionIdentifierProperty.cs delete mode 100644 Source/MQTTnet/Packets/Properties/TopicAliasMaximumProperty.cs delete mode 100644 Source/MQTTnet/Packets/Properties/TopicAliasProperty.cs delete mode 100644 Source/MQTTnet/Packets/Properties/UserPropertyProperty.cs delete mode 100644 Source/MQTTnet/Packets/Properties/WildcardSubscriptionAvailableProperty.cs delete mode 100644 Source/MQTTnet/Packets/Properties/WillDelayIntervalProperty.cs create mode 100644 Source/MQTTnet/Protocol/MqttConnectReasonCode.cs rename Source/MQTTnet/{Packets/Properties/MqttMessagePropertyID.cs => Protocol/MqttPropertyID.cs} (92%) create mode 100644 Source/MQTTnet/Protocol/MqttPubAckReasonCode.cs diff --git a/Source/MQTTnet.AspnetCore/Client/MqttClientConnectionContextFactory.cs b/Source/MQTTnet.AspnetCore/Client/MqttClientConnectionContextFactory.cs index b5801f46..93314f44 100644 --- a/Source/MQTTnet.AspnetCore/Client/MqttClientConnectionContextFactory.cs +++ b/Source/MQTTnet.AspnetCore/Client/MqttClientConnectionContextFactory.cs @@ -20,7 +20,7 @@ namespace MQTTnet.AspNetCore.Client { var endpoint = new DnsEndPoint(tcpOptions.Server, tcpOptions.GetPort()); var tcpConnection = new TcpConnection(endpoint); - return new MqttConnectionContext(new MqttPacketSerializerAdapter(options.ProtocolVersion), tcpConnection); + return new MqttConnectionContext(new MqttPacketFormatterAdapter(options.ProtocolVersion), tcpConnection); } default: { diff --git a/Source/MQTTnet.AspnetCore/MqttConnectionContext.cs b/Source/MQTTnet.AspnetCore/MqttConnectionContext.cs index aa2d7d8c..b24f6e1d 100644 --- a/Source/MQTTnet.AspnetCore/MqttConnectionContext.cs +++ b/Source/MQTTnet.AspnetCore/MqttConnectionContext.cs @@ -13,15 +13,15 @@ namespace MQTTnet.AspNetCore { public class MqttConnectionContext : IMqttChannelAdapter { - public MqttConnectionContext(MqttPacketSerializerAdapter packetSerializerAdapter, ConnectionContext connection) + public MqttConnectionContext(MqttPacketFormatterAdapter packetFormatterAdapter, ConnectionContext connection) { - PacketSerializerAdapter = packetSerializerAdapter ?? throw new ArgumentNullException(nameof(packetSerializerAdapter)); + PacketFormatterAdapter = packetFormatterAdapter ?? throw new ArgumentNullException(nameof(packetFormatterAdapter)); Connection = connection ?? throw new ArgumentNullException(nameof(connection)); } public string Endpoint => Connection.ConnectionId; public ConnectionContext Connection { get; } - public MqttPacketSerializerAdapter PacketSerializerAdapter { get; } + public MqttPacketFormatterAdapter PacketFormatterAdapter { get; } public event EventHandler ReadingPacketStarted; public event EventHandler ReadingPacketCompleted; @@ -72,7 +72,7 @@ namespace MQTTnet.AspNetCore { if (!buffer.IsEmpty) { - if (PacketSerializerAdapter.Formatter.TryDeserialize(buffer, out var packet, out consumed, out observed)) + if (PacketFormatterAdapter.TryDecode(buffer, out var packet, out consumed, out observed)) { return packet; } @@ -107,7 +107,7 @@ namespace MQTTnet.AspNetCore public async Task SendPacketAsync(MqttBasePacket packet, CancellationToken cancellationToken) { - var buffer = PacketSerializerAdapter.Encode(packet).AsMemory(); + var buffer = PacketFormatterAdapter.Encode(packet).AsMemory(); var output = Connection.Transport.Output; await _writerSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false); diff --git a/Source/MQTTnet.AspnetCore/MqttConnectionHandler.cs b/Source/MQTTnet.AspnetCore/MqttConnectionHandler.cs index f533ed7c..0c09a7a9 100644 --- a/Source/MQTTnet.AspnetCore/MqttConnectionHandler.cs +++ b/Source/MQTTnet.AspnetCore/MqttConnectionHandler.cs @@ -21,7 +21,7 @@ namespace MQTTnet.AspNetCore transferFormatFeature.ActiveFormat = TransferFormat.Binary; } - using (var adapter = new MqttConnectionContext(new MqttPacketSerializerAdapter(), connection)) + using (var adapter = new MqttConnectionContext(new MqttPacketFormatterAdapter(), connection)) { var args = new MqttServerAdapterClientAcceptedEventArgs(adapter); ClientAccepted?.Invoke(this, args); diff --git a/Source/MQTTnet.AspnetCore/MqttWebSocketServerAdapter.cs b/Source/MQTTnet.AspnetCore/MqttWebSocketServerAdapter.cs index ee310222..65514d0b 100644 --- a/Source/MQTTnet.AspnetCore/MqttWebSocketServerAdapter.cs +++ b/Source/MQTTnet.AspnetCore/MqttWebSocketServerAdapter.cs @@ -27,7 +27,7 @@ namespace MQTTnet.AspNetCore { if (webSocket == null) throw new ArgumentNullException(nameof(webSocket)); - var clientAdapter = new MqttChannelAdapter(new MqttWebSocketChannel(webSocket, endpoint), new MqttPacketSerializerAdapter(), new MqttNetLogger().CreateChildLogger(nameof(MqttWebSocketServerAdapter))); + var clientAdapter = new MqttChannelAdapter(new MqttWebSocketChannel(webSocket, endpoint), new MqttPacketFormatterAdapter(), new MqttNetLogger().CreateChildLogger(nameof(MqttWebSocketServerAdapter))); var eventArgs = new MqttServerAdapterClientAcceptedEventArgs(clientAdapter); ClientAccepted?.Invoke(this, eventArgs); diff --git a/Source/MQTTnet.AspnetCore/ReaderExtensions.cs b/Source/MQTTnet.AspnetCore/ReaderExtensions.cs index d7c192b9..abe8ef7b 100644 --- a/Source/MQTTnet.AspnetCore/ReaderExtensions.cs +++ b/Source/MQTTnet.AspnetCore/ReaderExtensions.cs @@ -9,7 +9,7 @@ namespace MQTTnet.AspNetCore { public static class ReaderExtensions { - public static bool TryDeserialize(this IMqttPacketFormatter formatter, in ReadOnlySequence input, out MqttBasePacket packet, out SequencePosition consumed, out SequencePosition observed) + public static bool TryDecode(this MqttPacketFormatterAdapter formatter, in ReadOnlySequence input, out MqttBasePacket packet, out SequencePosition consumed, out SequencePosition observed) { if (formatter == null) throw new ArgumentNullException(nameof(formatter)); @@ -17,6 +17,7 @@ namespace MQTTnet.AspNetCore consumed = input.Start; observed = input.End; var copy = input; + if (copy.Length < 2) { return false; diff --git a/Source/MQTTnet/Adapter/IMqttChannelAdapter.cs b/Source/MQTTnet/Adapter/IMqttChannelAdapter.cs index 82003204..183badb4 100644 --- a/Source/MQTTnet/Adapter/IMqttChannelAdapter.cs +++ b/Source/MQTTnet/Adapter/IMqttChannelAdapter.cs @@ -10,7 +10,7 @@ namespace MQTTnet.Adapter { string Endpoint { get; } - MqttPacketSerializerAdapter PacketSerializerAdapter { get; } + MqttPacketFormatterAdapter PacketFormatterAdapter { get; } event EventHandler ReadingPacketStarted; diff --git a/Source/MQTTnet/Adapter/MqttChannelAdapter.cs b/Source/MQTTnet/Adapter/MqttChannelAdapter.cs index 463b898a..340a528b 100644 --- a/Source/MQTTnet/Adapter/MqttChannelAdapter.cs +++ b/Source/MQTTnet/Adapter/MqttChannelAdapter.cs @@ -16,31 +16,33 @@ namespace MQTTnet.Adapter public class MqttChannelAdapter : IMqttChannelAdapter { private const uint ErrorOperationAborted = 0x800703E3; - private const int ReadBufferSize = 4096; // TODO: Move buffer size to config + private const uint ReadBufferSize = 4096; // TODO: Move buffer size to config private readonly SemaphoreSlim _writerSemaphore = new SemaphoreSlim(1, 1); private readonly IMqttNetChildLogger _logger; private readonly IMqttChannel _channel; - - private readonly byte[] _fixedHeaderBuffer = new byte[2]; - private readonly byte[] _singleByteBuffer = new byte[1]; + private readonly MqttPacketReader _packetReader; + private readonly byte[] _fixedHeaderBuffer = new byte[2]; + private bool _isDisposed; - public MqttChannelAdapter(IMqttChannel channel, MqttPacketSerializerAdapter packetSerializerAdapter, IMqttNetChildLogger logger) + public MqttChannelAdapter(IMqttChannel channel, MqttPacketFormatterAdapter packetFormatterAdapter, IMqttNetChildLogger logger) { if (logger == null) throw new ArgumentNullException(nameof(logger)); _channel = channel ?? throw new ArgumentNullException(nameof(channel)); - PacketSerializerAdapter = packetSerializerAdapter ?? throw new ArgumentNullException(nameof(packetSerializerAdapter)); + PacketFormatterAdapter = packetFormatterAdapter ?? throw new ArgumentNullException(nameof(packetFormatterAdapter)); + _packetReader = new MqttPacketReader(_channel); + _logger = logger.CreateChildLogger(nameof(MqttChannelAdapter)); } public string Endpoint => _channel.Endpoint; - public MqttPacketSerializerAdapter PacketSerializerAdapter { get; } + public MqttPacketFormatterAdapter PacketFormatterAdapter { get; } public event EventHandler ReadingPacketStarted; public event EventHandler ReadingPacketCompleted; @@ -96,9 +98,9 @@ namespace MQTTnet.Adapter await _writerSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false); try { - var packetData = PacketSerializerAdapter.Encode(packet); + var packetData = PacketFormatterAdapter.Encode(packet); await _channel.WriteAsync(packetData.Array, packetData.Offset, packetData.Count, cancellationToken).ConfigureAwait(false); - PacketSerializerAdapter.FreeBuffer(); + PacketFormatterAdapter.FreeBuffer(); _logger.Verbose("TX >>> {0}", packet); } @@ -139,12 +141,12 @@ namespace MQTTnet.Adapter return null; } - if (!PacketSerializerAdapter.ProtocolVersion.HasValue) + if (!PacketFormatterAdapter.ProtocolVersion.HasValue) { - PacketSerializerAdapter.DetectProtocolVersion(receivedMqttPacket); + PacketFormatterAdapter.DetectProtocolVersion(receivedMqttPacket); } - var packet = PacketSerializerAdapter.Decode(receivedMqttPacket); + var packet = PacketFormatterAdapter.Decode(receivedMqttPacket); if (packet == null) { throw new MqttProtocolViolationException("Received malformed packet."); @@ -172,7 +174,7 @@ namespace MQTTnet.Adapter private async Task ReceiveAsync(CancellationToken cancellationToken) { - var fixedHeader = await MqttPacketReader.ReadFixedHeaderAsync(_channel, _fixedHeaderBuffer, _singleByteBuffer, cancellationToken).ConfigureAwait(false); + var fixedHeader = await _packetReader.ReadFixedHeaderAsync(_fixedHeaderBuffer, cancellationToken).ConfigureAwait(false); try { @@ -192,15 +194,15 @@ namespace MQTTnet.Adapter var bytesLeft = body.Length - bodyOffset; if (chunkSize > bytesLeft) { - chunkSize = bytesLeft; + chunkSize = (uint)bytesLeft; } #if WINDOWS_UWP - var readBytes = await _channel.ReadAsync(body, bodyOffset, chunkSize, cancellationToken).ConfigureAwait(false); + var readBytes = await _channel.ReadAsync(body, bodyOffset, (int)chunkSize, cancellationToken).ConfigureAwait(false); #else // async/await is not used to avoid the overhead of context switches. We assume that the reamining data // has been sent from the sender directly after the initial bytes. - var readBytes = _channel.ReadAsync(body, bodyOffset, chunkSize, cancellationToken).ConfigureAwait(false).GetAwaiter().GetResult(); + var readBytes = _channel.ReadAsync(body, bodyOffset, (int)chunkSize, cancellationToken).ConfigureAwait(false).GetAwaiter().GetResult(); #endif cancellationToken.ThrowIfCancellationRequested(); diff --git a/Source/MQTTnet/Client/MqttClient.cs b/Source/MQTTnet/Client/MqttClient.cs index c5c2d638..f525d2d8 100644 --- a/Source/MQTTnet/Client/MqttClient.cs +++ b/Source/MQTTnet/Client/MqttClient.cs @@ -2,11 +2,13 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using System.Net; using System.Threading; using System.Threading.Tasks; using MQTTnet.Adapter; using MQTTnet.Diagnostics; using MQTTnet.Exceptions; +using MQTTnet.Formatter; using MQTTnet.Internal; using MQTTnet.Packets; using MQTTnet.Protocol; @@ -156,9 +158,11 @@ namespace MQTTnet.Client public Task PublishAsync(MqttApplicationMessage applicationMessage) { + if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage)); + ThrowIfNotConnected(); - var publishPacket = applicationMessage.ToPublishPacket(); + var publishPacket = _adapter.PacketFormatterAdapter.ConvertApplicationMessageToPublishPacket(applicationMessage); switch (applicationMessage.QualityOfServiceLevel) { @@ -205,6 +209,9 @@ namespace MQTTnet.Client WillMessage = willApplicationMessage }; + //connectPacket.RequestProblemInformationProperty = true; + //connectPacket.RequestResponseInformationProperty = true; + var response = await SendAndReceiveAsync(connectPacket, cancellationToken).ConfigureAwait(false); if (response.ConnectReturnCode != MqttConnectReturnCode.ConnectionAccepted) { @@ -516,6 +523,7 @@ namespace MQTTnet.Client { try { + // TODO: Move conversion to formatter. var applicationMessage = publishPacket.ToApplicationMessage(); ApplicationMessageReceived?.Invoke(this, new MqttApplicationMessageReceivedEventArgs(_options.ClientId, applicationMessage)); } diff --git a/Source/MQTTnet/Formatter/IMqttPacketFormatter.cs b/Source/MQTTnet/Formatter/IMqttPacketFormatter.cs index b4a26b84..e37d2a76 100644 --- a/Source/MQTTnet/Formatter/IMqttPacketFormatter.cs +++ b/Source/MQTTnet/Formatter/IMqttPacketFormatter.cs @@ -10,6 +10,8 @@ namespace MQTTnet.Formatter MqttBasePacket Decode(ReceivedMqttPacket receivedMqttPacket); + MqttPublishPacket ConvertApplicationMessageToPublishPacket(MqttApplicationMessage applicationMessage); + void FreeBuffer(); } } \ No newline at end of file diff --git a/Source/MQTTnet/Formatter/MqttFixedHeader.cs b/Source/MQTTnet/Formatter/MqttFixedHeader.cs index c9cd3ce1..4395d875 100644 --- a/Source/MQTTnet/Formatter/MqttFixedHeader.cs +++ b/Source/MQTTnet/Formatter/MqttFixedHeader.cs @@ -2,7 +2,7 @@ { public struct MqttFixedHeader { - public MqttFixedHeader(byte flags, int remainingLength) + public MqttFixedHeader(byte flags, uint remainingLength) { Flags = flags; RemainingLength = remainingLength; @@ -10,6 +10,6 @@ public byte Flags { get; } - public int RemainingLength { get; } + public uint RemainingLength { get; } } } diff --git a/Source/MQTTnet/Formatter/MqttPacketBodyReader.cs b/Source/MQTTnet/Formatter/MqttPacketBodyReader.cs index 3e477475..db971f09 100644 --- a/Source/MQTTnet/Formatter/MqttPacketBodyReader.cs +++ b/Source/MQTTnet/Formatter/MqttPacketBodyReader.cs @@ -1,71 +1,140 @@ using System; +using System.Runtime.CompilerServices; using System.Text; +using MQTTnet.Exceptions; namespace MQTTnet.Formatter { public class MqttPacketBodyReader { private readonly byte[] _buffer; - private readonly int _initialOffset; - private readonly int _length; + private readonly ulong _initialOffset; + private readonly ulong _length; - private int _offset; - public MqttPacketBodyReader(byte[] buffer, int offset, int length) + : this(buffer, (ulong)offset, (ulong)length) + { + } + + public MqttPacketBodyReader(byte[] buffer, ulong offset, ulong length) { _buffer = buffer; _initialOffset = offset; - _offset = offset; + Offset = offset; _length = length; } - public int Length => _length - _offset; + public ulong Offset { get; private set; } - public bool EndOfStream => _offset == _length; + public ulong Length => _length - Offset; - public void Seek(int position) + public bool EndOfStream => Offset == _length; + + public void Seek(ulong position) { - _offset = _initialOffset + position; + Offset = _initialOffset + position; + } + + public ArraySegment Read(uint length) + { + ValidateReceiveBuffer(length); + + var buffer = new ArraySegment(_buffer, (int)Offset, (int)length); + Offset += length; + return buffer; } public byte ReadByte() { ValidateReceiveBuffer(1); - return _buffer[_offset++]; + return _buffer[Offset++]; + } + + public bool ReadBoolean() + { + ValidateReceiveBuffer(1); + var buffer = _buffer[Offset++]; + + if (buffer == 0) + { + return false; + } + + if (buffer == 1) + { + return true; + } + + throw new MqttProtocolViolationException("Boolean values can be 0 or 1 only."); } public ArraySegment ReadRemainingData() { - return new ArraySegment(_buffer, _offset, _length - _offset); + return new ArraySegment(_buffer, (int)Offset, (int)(_length - Offset)); } - public ushort ReadUInt16() + public ushort ReadTwoByteInteger() { ValidateReceiveBuffer(2); - var msb = _buffer[_offset++]; - var lsb = _buffer[_offset++]; + var msb = _buffer[Offset++]; + var lsb = _buffer[Offset++]; return (ushort)(msb << 8 | lsb); } + public uint ReadFourByteInteger() + { + ValidateReceiveBuffer(4); + + var byte0 = _buffer[Offset++]; + var byte1 = _buffer[Offset++]; + var byte2 = _buffer[Offset++]; + var byte3 = _buffer[Offset++]; + + return (uint)(byte0 << 24 | byte1 << 16 | byte2 << 8 | byte3); + } + public ArraySegment ReadWithLengthPrefix() { - var length = ReadUInt16(); + var length = ReadTwoByteInteger(); ValidateReceiveBuffer(length); - var result = new ArraySegment(_buffer, _offset, length); - _offset += length; + var result = new ArraySegment(_buffer, (int)Offset, length); + Offset += length; return result; } - private void ValidateReceiveBuffer(ushort length) + public uint ReadVariableLengthInteger() { - if (_length < _offset + length) + var multiplier = 1; + var value = 0U; + byte encodedByte; + + do { - throw new ArgumentOutOfRangeException(nameof(_buffer), $"expected at least {_offset + length} bytes but there are only {_length} bytes"); + encodedByte = ReadByte(); + value += (uint)((encodedByte & 127) * multiplier); + + if (multiplier > 2097152) + { + throw new MqttProtocolViolationException("Variable length integer is invalid."); + } + + multiplier *= 128; + } while ((encodedByte & 128) != 0); + + return value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ValidateReceiveBuffer(uint length) + { + if (_length < Offset + length) + { + throw new ArgumentOutOfRangeException(nameof(_buffer), $"Expected at least {Offset + length} bytes but there are only {_length} bytes"); } } diff --git a/Source/MQTTnet/Formatter/MqttPacketSerializerAdapter.cs b/Source/MQTTnet/Formatter/MqttPacketFormatterAdapter.cs similarity index 74% rename from Source/MQTTnet/Formatter/MqttPacketSerializerAdapter.cs rename to Source/MQTTnet/Formatter/MqttPacketFormatterAdapter.cs index 82bfa99c..f04467cc 100644 --- a/Source/MQTTnet/Formatter/MqttPacketSerializerAdapter.cs +++ b/Source/MQTTnet/Formatter/MqttPacketFormatterAdapter.cs @@ -8,44 +8,56 @@ using MQTTnet.Packets; namespace MQTTnet.Formatter { - public class MqttPacketSerializerAdapter + public class MqttPacketFormatterAdapter { - public MqttPacketSerializerAdapter() + private IMqttPacketFormatter _formatter; + + public MqttPacketFormatterAdapter() { } - public MqttPacketSerializerAdapter(MqttProtocolVersion protocolVersion) + public MqttPacketFormatterAdapter(MqttProtocolVersion protocolVersion) { UseProtocolVersion(protocolVersion); } public MqttProtocolVersion? ProtocolVersion { get; private set; } - - public IMqttPacketFormatter Formatter { get; private set; } - - public ArraySegment Encode(MqttBasePacket packet) + + public MqttPublishPacket ConvertApplicationMessageToPublishPacket(MqttApplicationMessage applicationMessage) { - if (Formatter == null) + if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage)); + + if (_formatter == null) { throw new InvalidOperationException("Protocol version not set or detected."); } - return Formatter.Encode(packet); + return _formatter.ConvertApplicationMessageToPublishPacket(applicationMessage); + } + + public ArraySegment Encode(MqttBasePacket packet) + { + if (_formatter == null) + { + throw new InvalidOperationException("Protocol version not set or detected."); + } + + return _formatter.Encode(packet); } public MqttBasePacket Decode(ReceivedMqttPacket receivedMqttPacket) { - if (Formatter == null) + if (_formatter == null) { throw new InvalidOperationException("Protocol version not set or detected."); } - return Formatter.Decode(receivedMqttPacket); + return _formatter.Decode(receivedMqttPacket); } public void FreeBuffer() { - Formatter?.FreeBuffer(); + _formatter?.FreeBuffer(); } public void DetectProtocolVersion(ReceivedMqttPacket receivedMqttPacket) @@ -68,20 +80,20 @@ namespace MQTTnet.Formatter { case MqttProtocolVersion.V500: { - Formatter = new MqttV500PacketFormatter(); + _formatter = new MqttV500PacketFormatter(); break; } case MqttProtocolVersion.V311: { - Formatter = new MqttV311PacketFormatter(); + _formatter = new MqttV311PacketFormatter(); break; } case MqttProtocolVersion.V310: { - Formatter = new MqttV310PacketFormatter(); + _formatter = new MqttV310PacketFormatter(); break; } diff --git a/Source/MQTTnet/Formatter/MqttPacketReader.cs b/Source/MQTTnet/Formatter/MqttPacketReader.cs index 4a12efc5..97b18279 100644 --- a/Source/MQTTnet/Formatter/MqttPacketReader.cs +++ b/Source/MQTTnet/Formatter/MqttPacketReader.cs @@ -1,4 +1,5 @@ -using System.Threading; +using System; +using System.Threading; using System.Threading.Tasks; using MQTTnet.Channel; using MQTTnet.Exceptions; @@ -6,9 +7,18 @@ using MQTTnet.Internal; namespace MQTTnet.Formatter { - public static class MqttPacketReader + public class MqttPacketReader { - public static async Task ReadFixedHeaderAsync(IMqttChannel channel, byte[] fixedHeaderBuffer, byte[] singleByteBuffer, CancellationToken cancellationToken) + private readonly byte[] _singleByteBuffer = new byte[1]; + + private readonly IMqttChannel _channel; + + public MqttPacketReader(IMqttChannel channel) + { + _channel = channel ?? throw new ArgumentNullException(nameof(channel)); + } + + public async Task ReadFixedHeaderAsync(byte[] fixedHeaderBuffer, CancellationToken cancellationToken) { // The MQTT fixed header contains 1 byte of flags and at least 1 byte for the remaining data length. // So in all cases at least 2 bytes must be read for a complete MQTT packet. @@ -19,7 +29,7 @@ namespace MQTTnet.Formatter while (totalBytesRead < buffer.Length) { - var bytesRead = await channel.ReadAsync(buffer, totalBytesRead, buffer.Length - totalBytesRead, cancellationToken).ConfigureAwait(false); + var bytesRead = await _channel.ReadAsync(buffer, totalBytesRead, buffer.Length - totalBytesRead, cancellationToken).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested(); ExceptionHelper.ThrowIfGracefulSocketClose(bytesRead); @@ -35,24 +45,24 @@ namespace MQTTnet.Formatter #if WINDOWS_UWP // UWP will have a dead lock when calling this not async. - var bodyLength = await ReadBodyLengthAsync(channel, buffer[1], singleByteBuffer, cancellationToken).ConfigureAwait(false); + var bodyLength = await ReadBodyLengthAsync(buffer[1], cancellationToken).ConfigureAwait(false); #else // Here the async/await pattern is not used becuase the overhead of context switches // is too big for reading 1 byte in a row. We expect that the remaining data was sent // directly after the initial bytes. If the client disconnects just in this moment we // will get an exception anyway. - var bodyLength = ReadBodyLength(channel, buffer[1], singleByteBuffer, cancellationToken); + var bodyLength = ReadBodyLength(buffer[1], cancellationToken); #endif return new MqttFixedHeader(buffer[0], bodyLength); } #if !WINDOWS_UWP - private static int ReadBodyLength(IMqttChannel channel, byte initialEncodedByte, byte[] singleByteBuffer, CancellationToken cancellationToken) + private uint ReadBodyLength(byte initialEncodedByte, CancellationToken cancellationToken) { var offset = 0; var multiplier = 128; - var value = initialEncodedByte & 127; + var value = (uint)(initialEncodedByte & 127); int encodedByte = initialEncodedByte; while ((encodedByte & 128) != 0) @@ -65,18 +75,18 @@ namespace MQTTnet.Formatter cancellationToken.ThrowIfCancellationRequested(); - encodedByte = ReadByte(channel, singleByteBuffer, cancellationToken); + encodedByte = ReadByte(cancellationToken); - value += (byte)(encodedByte & 127) * multiplier; + value += (uint)((encodedByte & 127) * multiplier); multiplier *= 128; } return value; } - private static byte ReadByte(IMqttChannel channel, byte[] singleByteBuffer, CancellationToken cancellationToken) + private byte ReadByte(CancellationToken cancellationToken) { - var readCount = channel.ReadAsync(singleByteBuffer, 0, 1, cancellationToken).ConfigureAwait(false).GetAwaiter().GetResult(); + var readCount = _channel.ReadAsync(_singleByteBuffer, 0, 1, cancellationToken).ConfigureAwait(false).GetAwaiter().GetResult(); cancellationToken.ThrowIfCancellationRequested(); @@ -85,16 +95,16 @@ namespace MQTTnet.Formatter ExceptionHelper.ThrowGracefulSocketClose(); } - return singleByteBuffer[0]; + return _singleByteBuffer[0]; } #else - private static async Task ReadBodyLengthAsync(IMqttChannel channel, byte initialEncodedByte, byte[] singleByteBuffer, CancellationToken cancellationToken) + private async Task ReadBodyLengthAsync(byte initialEncodedByte, CancellationToken cancellationToken) { var offset = 0; var multiplier = 128; - var value = initialEncodedByte & 127; + var value = (uint)(initialEncodedByte & 127); int encodedByte = initialEncodedByte; while ((encodedByte & 128) != 0) @@ -107,18 +117,18 @@ namespace MQTTnet.Formatter cancellationToken.ThrowIfCancellationRequested(); - encodedByte = await ReadByteAsync(channel, singleByteBuffer, cancellationToken).ConfigureAwait(false); + encodedByte = await ReadByteAsync(cancellationToken).ConfigureAwait(false); - value += (byte)(encodedByte & 127) * multiplier; + value += (uint)((encodedByte & 127) * multiplier); multiplier *= 128; } return value; } - private static async Task ReadByteAsync(IMqttChannel channel, byte[] singleByteBuffer, CancellationToken cancellationToken) + private async Task ReadByteAsync(CancellationToken cancellationToken) { - var readCount = await channel.ReadAsync(singleByteBuffer, 0, 1, cancellationToken).ConfigureAwait(false); + var readCount = await _channel.ReadAsync(_singleByteBuffer, 0, 1, cancellationToken).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested(); @@ -127,7 +137,7 @@ namespace MQTTnet.Formatter ExceptionHelper.ThrowGracefulSocketClose(); } - return singleByteBuffer[0]; + return _singleByteBuffer[0]; } #endif diff --git a/Source/MQTTnet/Formatter/MqttPacketWriter.cs b/Source/MQTTnet/Formatter/MqttPacketWriter.cs index 9fb8ed43..0bebfbeb 100644 --- a/Source/MQTTnet/Formatter/MqttPacketWriter.cs +++ b/Source/MQTTnet/Formatter/MqttPacketWriter.cs @@ -17,7 +17,7 @@ namespace MQTTnet.Formatter private byte[] _buffer = new byte[128]; - private int _position; + private int _offset; public int Length { get; private set; } @@ -77,7 +77,7 @@ namespace MQTTnet.Formatter { EnsureAdditionalCapacity(1); - _buffer[_position] = @byte; + _buffer[_offset] = @byte; IncreasePostition(1); } @@ -85,9 +85,9 @@ namespace MQTTnet.Formatter { EnsureAdditionalCapacity(2); - _buffer[_position] = (byte)(value >> 8); + _buffer[_offset] = (byte)(value >> 8); IncreasePostition(1); - _buffer[_position] = (byte)value; + _buffer[_offset] = (byte)value; IncreasePostition(1); } @@ -97,7 +97,7 @@ namespace MQTTnet.Formatter EnsureAdditionalCapacity(count); - Array.Copy(buffer, offset, _buffer, _position, count); + Array.Copy(buffer, offset, _buffer, _offset, count); IncreasePostition(count); } @@ -111,7 +111,12 @@ namespace MQTTnet.Formatter public void Write(MqttPacketWriter propertyWriter) { if (propertyWriter == null) throw new ArgumentNullException(nameof(propertyWriter)); - + + if (propertyWriter.Length == 0) + { + return; + } + Write(propertyWriter._buffer, 0, propertyWriter.Length); } @@ -123,7 +128,7 @@ namespace MQTTnet.Formatter public void Seek(int position) { EnsureCapacity(position); - _position = position; + _offset = position; } public byte[] GetBuffer() @@ -150,7 +155,7 @@ namespace MQTTnet.Formatter [MethodImpl(MethodImplOptions.AggressiveInlining)] private void EnsureAdditionalCapacity(int additionalCapacity) { - var freeSpace = _buffer.Length - _position; + var freeSpace = _buffer.Length - _offset; if (freeSpace >= additionalCapacity) { return; @@ -180,11 +185,11 @@ namespace MQTTnet.Formatter [MethodImpl(MethodImplOptions.AggressiveInlining)] private void IncreasePostition(int length) { - _position += length; + _offset += length; - if (_position > Length) + if (_offset > Length) { - Length = _position; + Length = _offset; } } } diff --git a/Source/MQTTnet/Formatter/V310/MqttV310PacketFormatter.cs b/Source/MQTTnet/Formatter/V310/MqttV310PacketFormatter.cs index 338b506d..b1edc458 100644 --- a/Source/MQTTnet/Formatter/V310/MqttV310PacketFormatter.cs +++ b/Source/MQTTnet/Formatter/V310/MqttV310PacketFormatter.cs @@ -70,6 +70,23 @@ namespace MQTTnet.Formatter.V310 } } + public virtual MqttPublishPacket ConvertApplicationMessageToPublishPacket(MqttApplicationMessage applicationMessage) + { + if (applicationMessage.UserProperties != null) + { + throw new MqttProtocolViolationException("User properties are not supported in MQTT version 3."); + } + + return new MqttPublishPacket + { + Topic = applicationMessage.Topic, + Payload = applicationMessage.Payload, + QualityOfServiceLevel = applicationMessage.QualityOfServiceLevel, + Retain = applicationMessage.Retain, + Dup = false + }; + } + public void FreeBuffer() { _packetWriter.FreeBuffer(); @@ -103,7 +120,7 @@ namespace MQTTnet.Formatter.V310 return new MqttUnsubAckPacket { - PacketIdentifier = body.ReadUInt16() + PacketIdentifier = body.ReadTwoByteInteger() }; } @@ -113,7 +130,7 @@ namespace MQTTnet.Formatter.V310 return new MqttPubCompPacket { - PacketIdentifier = body.ReadUInt16() + PacketIdentifier = body.ReadTwoByteInteger() }; } @@ -123,7 +140,7 @@ namespace MQTTnet.Formatter.V310 return new MqttPubRelPacket { - PacketIdentifier = body.ReadUInt16() + PacketIdentifier = body.ReadTwoByteInteger() }; } @@ -133,7 +150,7 @@ namespace MQTTnet.Formatter.V310 return new MqttPubRecPacket { - PacketIdentifier = body.ReadUInt16() + PacketIdentifier = body.ReadTwoByteInteger() }; } @@ -143,7 +160,7 @@ namespace MQTTnet.Formatter.V310 return new MqttPubAckPacket { - PacketIdentifier = body.ReadUInt16() + PacketIdentifier = body.ReadTwoByteInteger() }; } @@ -153,7 +170,7 @@ namespace MQTTnet.Formatter.V310 var packet = new MqttUnsubscribePacket { - PacketIdentifier = body.ReadUInt16(), + PacketIdentifier = body.ReadTwoByteInteger(), }; while (!body.EndOfStream) @@ -170,7 +187,7 @@ namespace MQTTnet.Formatter.V310 var packet = new MqttSubscribePacket { - PacketIdentifier = body.ReadUInt16() + PacketIdentifier = body.ReadTwoByteInteger() }; while (!body.EndOfStream) @@ -196,7 +213,7 @@ namespace MQTTnet.Formatter.V310 ushort? packetIdentifier = null; if (qualityOfServiceLevel > MqttQualityOfServiceLevel.AtMostOnce) { - packetIdentifier = receivedMqttPacket.Body.ReadUInt16(); + packetIdentifier = receivedMqttPacket.Body.ReadTwoByteInteger(); } var packet = new MqttPublishPacket @@ -236,7 +253,7 @@ namespace MQTTnet.Formatter.V310 var passwordFlag = (connectFlags & 0x40) > 0; var usernameFlag = (connectFlags & 0x80) > 0; - packet.KeepAlivePeriod = body.ReadUInt16(); + packet.KeepAlivePeriod = body.ReadTwoByteInteger(); packet.ClientId = body.ReadStringWithLengthPrefix(); if (willFlag) @@ -270,7 +287,7 @@ namespace MQTTnet.Formatter.V310 var packet = new MqttSubAckPacket { - PacketIdentifier = body.ReadUInt16() + PacketIdentifier = body.ReadTwoByteInteger() }; while (!body.EndOfStream) @@ -394,7 +411,7 @@ namespace MQTTnet.Formatter.V310 return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.PubRel, 0x02); } - private static byte SerializePublishPacket(MqttPublishPacket packet, MqttPacketWriter packetWriter) + protected virtual byte SerializePublishPacket(MqttPublishPacket packet, MqttPacketWriter packetWriter) { ValidatePublishPacket(packet); @@ -439,7 +456,7 @@ namespace MQTTnet.Formatter.V310 return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.Publish, fixedHeader); } - private static byte SerializePubAckPacket(MqttPubAckPacket packet, MqttPacketWriter packetWriter) + protected virtual byte SerializePubAckPacket(MqttPubAckPacket packet, MqttPacketWriter packetWriter) { if (!packet.PacketIdentifier.HasValue) { diff --git a/Source/MQTTnet/Formatter/V500/MqttV500PacketDecoder.cs b/Source/MQTTnet/Formatter/V500/MqttV500PacketDecoder.cs index f98ab7f6..348afd63 100644 --- a/Source/MQTTnet/Formatter/V500/MqttV500PacketDecoder.cs +++ b/Source/MQTTnet/Formatter/V500/MqttV500PacketDecoder.cs @@ -1,6 +1,135 @@ -namespace MQTTnet.Formatter.V500 +using System; +using System.Collections.Generic; +using System.Linq; +using MQTTnet.Exceptions; +using MQTTnet.Packets; +using MQTTnet.Protocol; + +namespace MQTTnet.Formatter.V500 { public class MqttV500PacketDecoder { + public MqttBasePacket DecodeConnAckPacket(MqttPacketBodyReader body) + { + if (body == null) throw new ArgumentNullException(nameof(body)); + + ThrowIfBodyIsEmpty(body); + + var packet = new MqttConnAckPacket(); + + var acknowledgeFlags = body.ReadByte(); + + packet.IsSessionPresent = (acknowledgeFlags & 0x1) > 0; + packet.ConnectReturnCode = (MqttConnectReturnCode)body.ReadByte(); + packet.ConnectReasonCode = (MqttConnectReasonCode)body.ReadByte(); + + var propertiesLength = body.ReadVariableLengthInteger(); + if (propertiesLength > 0) + { + packet.Properties = new MqttConnAckPacketProperties(); + + while (!body.EndOfStream) + { + var propertyID = (MqttPropertyID)body.ReadByte(); + + if (propertyID == MqttPropertyID.SessionExpiryInterval) + { + packet.Properties.SessionExpiryInterval = body.ReadFourByteInteger(); + } + else if (propertyID == MqttPropertyID.AuthenticationMethod) + { + packet.Properties.AuthenticationMethod = body.ReadStringWithLengthPrefix(); + } + else if (propertyID == MqttPropertyID.AuthenticationData) + { + packet.Properties.AuthenticationData = body.ReadWithLengthPrefix().ToArray(); + } + else if (propertyID == MqttPropertyID.RetainAvailable) + { + packet.Properties.RetainAvailable = body.ReadBoolean(); + } + else if (propertyID == MqttPropertyID.ReceiveMaximum) + { + packet.Properties.ReceiveMaximum = body.ReadTwoByteInteger(); + } + else if (propertyID == MqttPropertyID.AssignedClientIdentifer) + { + packet.Properties.AssignedClientIdentifier = body.ReadStringWithLengthPrefix(); + } + else if (propertyID == MqttPropertyID.TopicAliasMaximum) + { + packet.Properties.TopicAliasMaximum = body.ReadTwoByteInteger(); + } + else if (propertyID == MqttPropertyID.ReasonString) + { + packet.Properties.ReasonString = body.ReadStringWithLengthPrefix(); + } + else if (propertyID == MqttPropertyID.MaximumPacketSize) + { + packet.Properties.MaximumPacketSize = body.ReadFourByteInteger(); + } + else if (propertyID == MqttPropertyID.WildcardSubscriptionAvailable) + { + packet.Properties.WildcardSubscriptionAvailable = body.ReadBoolean(); + } + else if (propertyID == MqttPropertyID.SubscriptionIdentifierAvailable) + { + packet.Properties.SubscriptionIdentifiersAvailable = body.ReadBoolean(); + } + else if (propertyID == MqttPropertyID.SharedSubscriptionAvailable) + { + packet.Properties.SharedSubscriptionAvailable = body.ReadBoolean(); + } + else if (propertyID == MqttPropertyID.ServerKeepAlive) + { + packet.Properties.ServerKeepAlive = body.ReadTwoByteInteger(); + } + else if (propertyID == MqttPropertyID.ResponseInformation) + { + packet.Properties.ResponseInformation = body.ReadStringWithLengthPrefix(); + } + else if (propertyID == MqttPropertyID.ServerReference) + { + packet.Properties.ServerReference = body.ReadStringWithLengthPrefix(); + } + else if (propertyID == MqttPropertyID.UserProperty) + { + packet.Properties.UserProperties = ReadUserProperties(body); + } + } + } + + return packet; + } + + // ReSharper disable once ParameterOnlyUsedForPreconditionCheck.Local + private static void ThrowIfBodyIsEmpty(MqttPacketBodyReader body) + { + if (body == null || body.Length == 0) + { + throw new MqttProtocolViolationException("Data from the body is required but not present."); + } + } + + private static List ReadUserProperties(MqttPacketBodyReader reader) + { + var userPropertiesLength = reader.ReadVariableLengthInteger(); + if (userPropertiesLength == 0) + { + return new List(); + } + + var userProperties = new List(); + var targetPosition = reader.Offset + userPropertiesLength; + while (reader.Offset < targetPosition) + { + var name = reader.ReadStringWithLengthPrefix(); + var value = reader.ReadStringWithLengthPrefix(); + + userProperties.Add(new MqttUserProperty(name, value)); + } + + return userProperties; + } } } diff --git a/Source/MQTTnet/Formatter/V500/MqttV500PacketEncoder.cs b/Source/MQTTnet/Formatter/V500/MqttV500PacketEncoder.cs index f697d89f..9d236bea 100644 --- a/Source/MQTTnet/Formatter/V500/MqttV500PacketEncoder.cs +++ b/Source/MQTTnet/Formatter/V500/MqttV500PacketEncoder.cs @@ -1,6 +1,223 @@ -namespace MQTTnet.Formatter.V500 +using System; +using MQTTnet.Exceptions; +using MQTTnet.Packets; +using MQTTnet.Protocol; + +namespace MQTTnet.Formatter.V500 { public class MqttV500PacketEncoder { + public byte EncodeConnectPacket(MqttConnectPacket packet, MqttPacketWriter packetWriter) + { + if (packet == null) throw new ArgumentNullException(nameof(packet)); + if (packetWriter == null) throw new ArgumentNullException(nameof(packetWriter)); + + if (string.IsNullOrEmpty(packet.ClientId) && !packet.CleanSession) + { + throw new MqttProtocolViolationException("CleanSession must be set if ClientId is empty [MQTT-3.1.3-7]."); + } + + packetWriter.WriteWithLengthPrefix("MQTT"); + packetWriter.Write(5); // [3.1.2.2 Protocol Version] + + byte connectFlags = 0x0; + if (packet.CleanSession) + { + connectFlags |= 0x2; + } + + if (packet.WillMessage != null) + { + connectFlags |= 0x4; + connectFlags |= (byte)((byte)packet.WillMessage.QualityOfServiceLevel << 3); + + if (packet.WillMessage.Retain) + { + connectFlags |= 0x20; + } + } + + if (packet.Password != null && packet.Username == null) + { + throw new MqttProtocolViolationException("If the User Name Flag is set to 0, the Password Flag MUST be set to 0 [MQTT-3.1.2-22]."); + } + + if (packet.Password != null) + { + connectFlags |= 0x40; + } + + if (packet.Username != null) + { + connectFlags |= 0x80; + } + + packetWriter.Write(connectFlags); + packetWriter.Write(packet.KeepAlivePeriod); + + var propertiesWriter = new MqttV500PropertiesWriter(); + if (packet.Properties != null) + { + propertiesWriter.WriteAsFourByteInteger(MqttPropertyID.WillDelayInterval, packet.Properties.WillDelayInterval); + propertiesWriter.WriteAsFourByteInteger(MqttPropertyID.SessionExpiryInterval, packet.Properties.SessionExpiryInterval); + propertiesWriter.Write(MqttPropertyID.AuthenticationMethod, packet.Properties.AuthenticationMethod); + propertiesWriter.Write(MqttPropertyID.AuthenticationData, packet.Properties.AuthenticationData); + propertiesWriter.Write(MqttPropertyID.RequestProblemInformation, packet.Properties.RequestProblemInformation); + propertiesWriter.Write(MqttPropertyID.RequestResponseInformation, packet.Properties.RequestResponseInformation); + propertiesWriter.Write(MqttPropertyID.ReceiveMaximum, packet.Properties.ReceiveMaximum); + propertiesWriter.Write(MqttPropertyID.TopicAlias, packet.Properties.TopicAliasMaximum); + propertiesWriter.WriteAsFourByteInteger(MqttPropertyID.MaximumPacketSize, packet.Properties.MaximumPacketSize); + propertiesWriter.WriteUserProperties(packet.Properties.UserProperties); + propertiesWriter.WriteToPacket(packetWriter); + } + + packetWriter.WriteWithLengthPrefix(packet.ClientId); + + if (packet.WillMessage != null) + { + packetWriter.WriteWithLengthPrefix(packet.WillMessage.Topic); + packetWriter.WriteWithLengthPrefix(packet.WillMessage.Payload); + } + + if (packet.Username != null) + { + packetWriter.WriteWithLengthPrefix(packet.Username); + } + + if (packet.Password != null) + { + packetWriter.WriteWithLengthPrefix(packet.Password); + } + + return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.Connect); + } + + public byte EncodeConnAckPacket(MqttConnAckPacket packet, MqttPacketWriter packetWriter) + { + if (packet == null) throw new ArgumentNullException(nameof(packet)); + if (packetWriter == null) throw new ArgumentNullException(nameof(packetWriter)); + + if (!packet.ConnectReasonCode.HasValue) + { + throw new MqttProtocolViolationException("The ConnectReasonCode must be set for MQTT version 5."); + } + + byte connectAcknowledgeFlags = 0x0; + if (packet.IsSessionPresent) + { + connectAcknowledgeFlags |= 0x1; + } + + packetWriter.Write(connectAcknowledgeFlags); + packetWriter.Write((byte)packet.ConnectReturnCode); + packetWriter.Write((byte)packet.ConnectReasonCode.Value); + + var propertiesWriter = new MqttV500PropertiesWriter(); + if (packet.Properties != null) + { + propertiesWriter.WriteAsFourByteInteger(MqttPropertyID.SessionExpiryInterval, packet.Properties.SessionExpiryInterval); + propertiesWriter.Write(MqttPropertyID.AuthenticationMethod, packet.Properties.AuthenticationMethod); + propertiesWriter.Write(MqttPropertyID.AuthenticationData, packet.Properties.AuthenticationData); + propertiesWriter.Write(MqttPropertyID.RetainAvailable, packet.Properties.RetainAvailable); + propertiesWriter.Write(MqttPropertyID.ReceiveMaximum, packet.Properties.ReceiveMaximum); + propertiesWriter.Write(MqttPropertyID.AssignedClientIdentifer, packet.Properties.AssignedClientIdentifier); + propertiesWriter.Write(MqttPropertyID.TopicAliasMaximum, packet.Properties.TopicAliasMaximum); + propertiesWriter.Write(MqttPropertyID.ReasonString, packet.Properties.ReasonString); + propertiesWriter.WriteAsFourByteInteger(MqttPropertyID.MaximumPacketSize, packet.Properties.MaximumPacketSize); + propertiesWriter.Write(MqttPropertyID.WildcardSubscriptionAvailable, packet.Properties.WildcardSubscriptionAvailable); + propertiesWriter.Write(MqttPropertyID.SubscriptionIdentifierAvailable, packet.Properties.SubscriptionIdentifiersAvailable); + propertiesWriter.Write(MqttPropertyID.SharedSubscriptionAvailable, packet.Properties.SharedSubscriptionAvailable); + propertiesWriter.Write(MqttPropertyID.ServerKeepAlive, packet.Properties.ServerKeepAlive); + propertiesWriter.Write(MqttPropertyID.ResponseInformation, packet.Properties.ResponseInformation); + propertiesWriter.Write(MqttPropertyID.ServerReference, packet.Properties.ServerReference); + propertiesWriter.WriteUserProperties(packet.Properties.UserProperties); + propertiesWriter.WriteToPacket(packetWriter); + } + + return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.ConnAck); + } + + public byte EncodePublishPacket(MqttPublishPacket packet, MqttPacketWriter packetWriter) + { + if (packet == null) throw new ArgumentNullException(nameof(packet)); + if (packetWriter == null) throw new ArgumentNullException(nameof(packetWriter)); + + if (packet.QualityOfServiceLevel == 0 && packet.Dup) + { + throw new MqttProtocolViolationException("Dup flag must be false for QoS 0 packets [MQTT-3.3.1-2]."); + } + + packetWriter.WriteWithLengthPrefix(packet.Topic); + + if (packet.QualityOfServiceLevel > MqttQualityOfServiceLevel.AtMostOnce) + { + if (!packet.PacketIdentifier.HasValue) + { + throw new MqttProtocolViolationException("Publish packet has no packet identifier."); + } + + packetWriter.Write(packet.PacketIdentifier.Value); + } + else + { + if (packet.PacketIdentifier > 0) + { + throw new MqttProtocolViolationException("Packet identifier must be empty if QoS == 0 [MQTT-2.3.1-5]."); + } + } + + var propertiesWriter = new MqttV500PropertiesWriter(); + propertiesWriter.Write(MqttPropertyID.PayloadFormatIndicator, packet.Properties.PayloadFormatIndicator); + propertiesWriter.WriteAsFourByteInteger(MqttPropertyID.MessageExpiryInterval, packet.Properties.MessageExpiryInterval); + propertiesWriter.Write(MqttPropertyID.TopicAlias, packet.Properties.TopicAlias); + propertiesWriter.Write(MqttPropertyID.ResponseTopic, packet.Properties.ResponseTopic); + propertiesWriter.Write(MqttPropertyID.CorrelationData, packet.Properties.CorrelationData); + propertiesWriter.WriteAsVariableLengthInteger(MqttPropertyID.SubscriptionIdentifier, packet.Properties.SubscriptionIdentifier); + propertiesWriter.Write(MqttPropertyID.ContentType, packet.Properties.ContentType); + propertiesWriter.WriteUserProperties(packet.Properties.UserProperties); + propertiesWriter.WriteToPacket(packetWriter); + + if (packet.Payload?.Length > 0) + { + packetWriter.Write(packet.Payload, 0, packet.Payload.Length); + } + + byte fixedHeader = 0; + + if (packet.Retain) + { + fixedHeader |= 0x01; + } + + fixedHeader |= (byte)((byte)packet.QualityOfServiceLevel << 1); + + if (packet.Dup) + { + fixedHeader |= 0x08; + } + + return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.Publish, fixedHeader); + } + + public byte EncodePubAckPacket(MqttPubAckPacket packet, MqttPacketWriter packetWriter) + { + if (packet == null) throw new ArgumentNullException(nameof(packet)); + if (packetWriter == null) throw new ArgumentNullException(nameof(packetWriter)); + + if (!packet.PacketIdentifier.HasValue) + { + throw new MqttProtocolViolationException("PubAck packet has no packet identifier."); + } + + if (!packet.ConnectReasonCode.HasValue) + { + throw new MqttProtocolViolationException("PubAck packet must contain a connect reason."); + } + + packetWriter.Write(packet.PacketIdentifier.Value); + packetWriter.Write((byte)packet.ConnectReasonCode.Value); + + return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.PubAck); + } } } diff --git a/Source/MQTTnet/Formatter/V500/MqttV500PacketFormatter.cs b/Source/MQTTnet/Formatter/V500/MqttV500PacketFormatter.cs index 34a93405..d95255f0 100644 --- a/Source/MQTTnet/Formatter/V500/MqttV500PacketFormatter.cs +++ b/Source/MQTTnet/Formatter/V500/MqttV500PacketFormatter.cs @@ -1,10 +1,6 @@ using System; -using System.Collections.Generic; -using MQTTnet.Exceptions; using MQTTnet.Formatter.V311; using MQTTnet.Packets; -using MQTTnet.Packets.Properties; -using MQTTnet.Protocol; namespace MQTTnet.Formatter.V500 { @@ -13,184 +9,47 @@ namespace MQTTnet.Formatter.V500 private readonly MqttV500PacketEncoder _encoder = new MqttV500PacketEncoder(); private readonly MqttV500PacketDecoder _decoder = new MqttV500PacketDecoder(); + public override MqttPublishPacket ConvertApplicationMessageToPublishPacket(MqttApplicationMessage applicationMessage) + { + if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage)); + + return new MqttPublishPacket + { + Topic = applicationMessage.Topic, + Payload = applicationMessage.Payload, + QualityOfServiceLevel = applicationMessage.QualityOfServiceLevel, + Retain = applicationMessage.Retain, + Dup = false, + Properties = new MqttPublishPacketProperties + { + UserProperties = applicationMessage.UserProperties + } + }; + } + protected override byte SerializeConnectPacket(MqttConnectPacket packet, MqttPacketWriter packetWriter) { - ValidateConnectPacket(packet); - - packetWriter.WriteWithLengthPrefix("MQTT"); - packetWriter.Write(5); // [3.1.2.2 Protocol Version] - - byte connectFlags = 0x0; - if (packet.CleanSession) - { - connectFlags |= 0x2; - } - - if (packet.WillMessage != null) - { - connectFlags |= 0x4; - connectFlags |= (byte)((byte)packet.WillMessage.QualityOfServiceLevel << 3); - - if (packet.WillMessage.Retain) - { - connectFlags |= 0x20; - } - } - - if (packet.Password != null && packet.Username == null) - { - throw new MqttProtocolViolationException("If the User Name Flag is set to 0, the Password Flag MUST be set to 0 [MQTT-3.1.2-22]."); - } - - if (packet.Password != null) - { - connectFlags |= 0x40; - } - - if (packet.Username != null) - { - connectFlags |= 0x80; - } - - packetWriter.Write(connectFlags); - packetWriter.Write(packet.KeepAlivePeriod); - packetWriter.WriteWithLengthPrefix(packet.ClientId); - - if (packet.WillMessage != null) - { - packetWriter.WriteWithLengthPrefix(packet.WillMessage.Topic); - packetWriter.WriteWithLengthPrefix(packet.WillMessage.Payload); - } - - if (packet.Username != null) - { - packetWriter.WriteWithLengthPrefix(packet.Username); - } - - if (packet.Password != null) - { - packetWriter.WriteWithLengthPrefix(packet.Password); - } - - WriteProperty(MqttMessagePropertyID.WillDelayInterval, packet.WillDelayIntervalProperty, packetWriter); - WriteProperty(MqttMessagePropertyID.SessionExpiryInterval, packet.SessionExpiryIntervalProperty, packetWriter); - WriteProperty(MqttMessagePropertyID.AuthenticationMethod, packet.AuthenticationMethodProperty, packetWriter); - WriteProperty(MqttMessagePropertyID.AuthenticationData, packet.AuthenticationDataProperty, packetWriter); - WriteProperty(MqttMessagePropertyID.RequestProblemInformation, packet.RequestProblemInformationProperty, packetWriter); - WriteProperty(MqttMessagePropertyID.RequestResponseInformation, packet.RequestResponseInformationProperty, packetWriter); - WriteProperty(MqttMessagePropertyID.ReceiveMaximum, packet.ReceiveMaximumProperty, packetWriter); - WriteProperty(MqttMessagePropertyID.TopicAlias, packet.TopicAliasMaximumProperty, packetWriter); - WriteProperty(MqttMessagePropertyID.MaximumPacketSize, packet.MaximumPacketSizeProperty, packetWriter); - WriteUserProperties(packet.UserPropertiesProperty, packetWriter); - - return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.Connect); + return _encoder.EncodeConnectPacket(packet, packetWriter); } protected override byte SerializeConnAckPacket(MqttConnAckPacket packet, MqttPacketWriter packetWriter) { - byte connectAcknowledgeFlags = 0x0; - if (packet.IsSessionPresent) - { - connectAcknowledgeFlags |= 0x1; - } - - packetWriter.Write(connectAcknowledgeFlags); - packetWriter.Write((byte)packet.ConnectReturnCode); - - WriteProperty(MqttMessagePropertyID.SessionExpiryInterval, packet.SessionExpiryIntervalProperty, packetWriter); - WriteProperty(MqttMessagePropertyID.AuthenticationMethod, packet.AuthenticationMethodProperty, packetWriter); - WriteProperty(MqttMessagePropertyID.AuthenticationData, packet.AuthenticationDataProperty, packetWriter); - WriteProperty(MqttMessagePropertyID.RetainAvailable, packet.RetainAvailableProperty, packetWriter); - WriteProperty(MqttMessagePropertyID.ReceiveMaximum, packet.ReceiveMaximumProperty, packetWriter); - WriteProperty(MqttMessagePropertyID.AssignedClientIdentifer, packet.AssignedClientIdentifierProperty, packetWriter); - WriteProperty(MqttMessagePropertyID.TopicAlias, packet.TopicAliasMaximumProperty, packetWriter); - WriteProperty(MqttMessagePropertyID.ReasonString, packet.ReasonStringProperty, packetWriter); - WriteProperty(MqttMessagePropertyID.MaximumPacketSize, packet.MaximumPacketSizeProperty, packetWriter); - WriteProperty(MqttMessagePropertyID.WildcardSubscriptionAvailable, packet.WildcardSubscriptionAvailableProperty, packetWriter); - WriteProperty(MqttMessagePropertyID.SubscriptionIdentifierAvailable, packet.SubscriptionIdentifiersAvailableProperty, packetWriter); - WriteProperty(MqttMessagePropertyID.SharedSubscriptionAvailable, packet.SharedSubscriptionAvailableProperty, packetWriter); - WriteProperty(MqttMessagePropertyID.ServerKeepAlive, packet.ServerKeepAliveProperty, packetWriter); - WriteProperty(MqttMessagePropertyID.ResponseInformation, packet.ResponseInformationProperty, packetWriter); - WriteProperty(MqttMessagePropertyID.ServerReference, packet.ServerReferenceProperty, packetWriter); - WriteUserProperties(packet.UserPropertiesProperty, packetWriter); - - return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.ConnAck); + return _encoder.EncodeConnAckPacket(packet, packetWriter); } - private static void WriteProperty(MqttMessagePropertyID id, bool? value, MqttPacketWriter target) + protected override MqttBasePacket DeserializeConnAckPacket(MqttPacketBodyReader body) { - if (!value.HasValue) - { - return; - } - - target.Write((byte)id); - target.Write(value.Value ? (byte)0x1 : (byte)0x0); + return _decoder.DecodeConnAckPacket(body); } - private static void WriteProperty(MqttMessagePropertyID id, ushort? value, MqttPacketWriter target) + protected override byte SerializePublishPacket(MqttPublishPacket packet, MqttPacketWriter packetWriter) { - if (!value.HasValue) - { - return; - } - - target.Write((byte)id); - target.Write(value.Value); + return _encoder.EncodePublishPacket(packet, packetWriter); } - private static void WriteProperty(MqttMessagePropertyID id, uint? value, MqttPacketWriter target) + protected override byte SerializePubAckPacket(MqttPubAckPacket packet, MqttPacketWriter packetWriter) { - if (!value.HasValue) - { - return; - } - - // TODO: Check if order must be reversed like for ushort. - target.Write((byte)id); - var bytes = BitConverter.GetBytes(value.Value); - target.Write(bytes, 0, bytes.Length); - } - - private static void WriteProperty(MqttMessagePropertyID id, string value, MqttPacketWriter target) - { - if (value == null) - { - return; - } - - target.Write((byte)id); - target.WriteWithLengthPrefix(value); - } - - private static void WriteProperty(MqttMessagePropertyID id, byte[] value, MqttPacketWriter target) - { - if (value == null) - { - return; - } - - target.Write((byte)id); - target.WriteWithLengthPrefix(value); - } - - private static void WriteUserProperties(List userProperties, MqttPacketWriter target) - { - if (userProperties == null || userProperties.Count == 0) - { - return; - } - - var propertyWriter = new MqttPacketWriter(); - foreach (var property in userProperties) - { - propertyWriter.WriteWithLengthPrefix(property.Name); - propertyWriter.WriteWithLengthPrefix(property.Value); - } - - target.Write((byte)MqttMessagePropertyID.UserProperty); - target.WriteVariableLengthInteger((uint)propertyWriter.Length); - target.Write(propertyWriter); + return _encoder.EncodePubAckPacket(packet, packetWriter); } } } diff --git a/Source/MQTTnet/Formatter/V500/MqttV500PropertiesWriter.cs b/Source/MQTTnet/Formatter/V500/MqttV500PropertiesWriter.cs new file mode 100644 index 00000000..1099227d --- /dev/null +++ b/Source/MQTTnet/Formatter/V500/MqttV500PropertiesWriter.cs @@ -0,0 +1,108 @@ +using System; +using System.Collections.Generic; +using MQTTnet.Packets; +using MQTTnet.Protocol; + +namespace MQTTnet.Formatter.V500 +{ + public class MqttV500PropertiesWriter + { + private readonly MqttPacketWriter _packetWriter = new MqttPacketWriter(); + + public void Write(MqttPropertyID id, bool? value) + { + if (!value.HasValue) + { + return; + } + + _packetWriter.Write((byte)id); + _packetWriter.Write(value.Value ? (byte)0x1 : (byte)0x0); + } + + public void Write(MqttPropertyID id, ushort? value) + { + if (!value.HasValue) + { + return; + } + + _packetWriter.Write((byte)id); + _packetWriter.Write(value.Value); + } + + public void WriteAsVariableLengthInteger(MqttPropertyID id, uint? value) + { + if (!value.HasValue) + { + return; + } + + _packetWriter.Write((byte)id); + _packetWriter.WriteVariableLengthInteger(value.Value); + } + + public void WriteAsFourByteInteger(MqttPropertyID id, uint? value) + { + if (!value.HasValue) + { + return; + } + + _packetWriter.Write((byte)id); + _packetWriter.Write((byte)(value.Value >> 24)); + _packetWriter.Write((byte)(value.Value >> 16)); + _packetWriter.Write((byte)(value.Value >> 8)); + _packetWriter.Write((byte)value.Value); + } + + public void Write(MqttPropertyID id, string value) + { + if (value == null) + { + return; + } + + _packetWriter.Write((byte)id); + _packetWriter.WriteWithLengthPrefix(value); + } + + public void Write(MqttPropertyID id, byte[] value) + { + if (value == null) + { + return; + } + + _packetWriter.Write((byte)id); + _packetWriter.WriteWithLengthPrefix(value); + } + + public void WriteUserProperties(List userProperties) + { + if (userProperties == null || userProperties.Count == 0) + { + return; + } + + var propertyWriter = new MqttPacketWriter(); + foreach (var property in userProperties) + { + propertyWriter.WriteWithLengthPrefix(property.Name); + propertyWriter.WriteWithLengthPrefix(property.Value); + } + + _packetWriter.Write((byte)MqttPropertyID.UserProperty); + _packetWriter.WriteVariableLengthInteger((uint)propertyWriter.Length); + _packetWriter.Write(propertyWriter); + } + + public void WriteToPacket(MqttPacketWriter packetWriter) + { + if (packetWriter == null) throw new ArgumentNullException(nameof(packetWriter)); + + packetWriter.WriteVariableLengthInteger((uint)_packetWriter.Length); + packetWriter.Write(packetWriter); + } + } +} diff --git a/Source/MQTTnet/Implementations/MqttClientAdapterFactory.cs b/Source/MQTTnet/Implementations/MqttClientAdapterFactory.cs index 9bf40964..4f88c74d 100644 --- a/Source/MQTTnet/Implementations/MqttClientAdapterFactory.cs +++ b/Source/MQTTnet/Implementations/MqttClientAdapterFactory.cs @@ -16,12 +16,12 @@ namespace MQTTnet.Implementations { case MqttClientTcpOptions _: { - return new MqttChannelAdapter(new MqttTcpChannel(options), new MqttPacketSerializerAdapter(options.ProtocolVersion), logger); + return new MqttChannelAdapter(new MqttTcpChannel(options), new MqttPacketFormatterAdapter(options.ProtocolVersion), logger); } case MqttClientWebSocketOptions webSocketOptions: { - return new MqttChannelAdapter(new MqttWebSocketChannel(webSocketOptions), new MqttPacketSerializerAdapter(options.ProtocolVersion), logger); + return new MqttChannelAdapter(new MqttWebSocketChannel(webSocketOptions), new MqttPacketFormatterAdapter(options.ProtocolVersion), logger); } default: diff --git a/Source/MQTTnet/Implementations/MqttTcpServerAdapter.Uwp.cs b/Source/MQTTnet/Implementations/MqttTcpServerAdapter.Uwp.cs index ec226b22..dd93b144 100644 --- a/Source/MQTTnet/Implementations/MqttTcpServerAdapter.Uwp.cs +++ b/Source/MQTTnet/Implementations/MqttTcpServerAdapter.Uwp.cs @@ -72,7 +72,7 @@ namespace MQTTnet.Implementations { try { - var clientAdapter = new MqttChannelAdapter(new MqttTcpChannel(args.Socket, _options), new MqttPacketSerializerAdapter(), _logger); + var clientAdapter = new MqttChannelAdapter(new MqttTcpChannel(args.Socket, _options), new MqttPacketFormatterAdapter(), _logger); ClientAccepted?.Invoke(this, new MqttServerAdapterClientAcceptedEventArgs(clientAdapter)); } catch (Exception exception) diff --git a/Source/MQTTnet/Implementations/MqttTcpServerListener.cs b/Source/MQTTnet/Implementations/MqttTcpServerListener.cs index 8c6258e0..38036894 100644 --- a/Source/MQTTnet/Implementations/MqttTcpServerListener.cs +++ b/Source/MQTTnet/Implementations/MqttTcpServerListener.cs @@ -88,7 +88,7 @@ namespace MQTTnet.Implementations _socket.LocalEndPoint, _addressFamily == AddressFamily.InterNetwork ? "ipv4" : "ipv6"); - var clientAdapter = new MqttChannelAdapter(new MqttTcpChannel(clientSocket, sslStream), new MqttPacketSerializerAdapter(), _logger); + var clientAdapter = new MqttChannelAdapter(new MqttTcpChannel(clientSocket, sslStream), new MqttPacketFormatterAdapter(), _logger); ClientAccepted?.Invoke(this, new MqttServerAdapterClientAcceptedEventArgs(clientAdapter)); } catch (ObjectDisposedException) diff --git a/Source/MQTTnet/Internal/MqttApplicationMessageExtensions.cs b/Source/MQTTnet/Internal/MqttApplicationMessageExtensions.cs index 79eb2c43..7eebda69 100644 --- a/Source/MQTTnet/Internal/MqttApplicationMessageExtensions.cs +++ b/Source/MQTTnet/Internal/MqttApplicationMessageExtensions.cs @@ -14,22 +14,5 @@ namespace MQTTnet.Internal Retain = publishPacket.Retain }; } - - public static MqttPublishPacket ToPublishPacket(this MqttApplicationMessage applicationMessage) - { - if (applicationMessage == null) - { - return null; - } - - return new MqttPublishPacket - { - Topic = applicationMessage.Topic, - Payload = applicationMessage.Payload, - QualityOfServiceLevel = applicationMessage.QualityOfServiceLevel, - Retain = applicationMessage.Retain, - Dup = false - }; - } } } diff --git a/Source/MQTTnet/MQTTnet.csproj b/Source/MQTTnet/MQTTnet.csproj index 8b4038d0..91e455f7 100644 --- a/Source/MQTTnet/MQTTnet.csproj +++ b/Source/MQTTnet/MQTTnet.csproj @@ -69,6 +69,10 @@ + + + + C:\Program Files\dotnet\sdk\NuGetFallbackFolder\microsoft.netcore.app\2.1.0\ref\netcoreapp2.1\System.Net.Requests.dll diff --git a/Source/MQTTnet/MqttApplicationMessage.cs b/Source/MQTTnet/MqttApplicationMessage.cs index 334dfb40..54135fb0 100644 --- a/Source/MQTTnet/MqttApplicationMessage.cs +++ b/Source/MQTTnet/MqttApplicationMessage.cs @@ -1,4 +1,6 @@ -using MQTTnet.Protocol; +using System.Collections.Generic; +using MQTTnet.Packets; +using MQTTnet.Protocol; namespace MQTTnet { @@ -10,6 +12,8 @@ namespace MQTTnet public MqttQualityOfServiceLevel QualityOfServiceLevel { get; set; } + public List UserProperties { get; set; } + public bool Retain { get; set; } } } diff --git a/Source/MQTTnet/Packets/MqttAuthPacket.cs b/Source/MQTTnet/Packets/MqttAuthPacket.cs index aa8e9a60..191ec5c1 100644 --- a/Source/MQTTnet/Packets/MqttAuthPacket.cs +++ b/Source/MQTTnet/Packets/MqttAuthPacket.cs @@ -1,6 +1,4 @@ -using System.Collections.Generic; -using MQTTnet.Packets.Properties; -using MQTTnet.Protocol; +using MQTTnet.Protocol; namespace MQTTnet.Packets { @@ -10,7 +8,5 @@ namespace MQTTnet.Packets public class MqttAuthPacket : MqttBasePacket { public MqttAuthenticateReasonCode ReasonCode { get; set; } - - public List Properties { get; set; } } } diff --git a/Source/MQTTnet/Packets/MqttBasePublishPacket.cs b/Source/MQTTnet/Packets/MqttBasePublishPacket.cs index 67e68879..ffafc537 100644 --- a/Source/MQTTnet/Packets/MqttBasePublishPacket.cs +++ b/Source/MQTTnet/Packets/MqttBasePublishPacket.cs @@ -1,15 +1,7 @@ -using System.Collections.Generic; -using MQTTnet.Packets.Properties; - -namespace MQTTnet.Packets +namespace MQTTnet.Packets { public class MqttBasePublishPacket : MqttBasePacket, IMqttPacketWithIdentifier { public ushort? PacketIdentifier { get; set; } - - /// - /// Added in MQTTv5.0.0. - /// - public List Properties { get; set; } } } diff --git a/Source/MQTTnet/Packets/MqttConnAckPacket.cs b/Source/MQTTnet/Packets/MqttConnAckPacket.cs index d79b44af..1a9aa669 100644 --- a/Source/MQTTnet/Packets/MqttConnAckPacket.cs +++ b/Source/MQTTnet/Packets/MqttConnAckPacket.cs @@ -1,5 +1,4 @@ -using System.Collections.Generic; -using MQTTnet.Protocol; +using MQTTnet.Protocol; namespace MQTTnet.Packets { @@ -14,38 +13,9 @@ namespace MQTTnet.Packets #region Added in MQTTv5 - public uint? SessionExpiryIntervalProperty { get; set; } + public MqttConnectReasonCode? ConnectReasonCode { get; set; } - // TODO: Add enum - public byte? ReceiveMaximumProperty { get; set; } - - public bool? RetainAvailableProperty { get; set; } - - public uint? MaximumPacketSizeProperty { get; set; } - - public string AssignedClientIdentifierProperty { get; set; } - - public ushort? TopicAliasMaximumProperty { get; set; } - - public string ReasonStringProperty { get; set; } - - public List UserPropertiesProperty { get; set; } - - public bool? WildcardSubscriptionAvailableProperty { get; set; } - - public bool? SubscriptionIdentifiersAvailableProperty { get; set; } - - public bool? SharedSubscriptionAvailableProperty { get; set; } - - public ushort? ServerKeepAliveProperty { get; set; } - - public string ResponseInformationProperty { get; set; } - - public string ServerReferenceProperty { get; set; } - - public string AuthenticationMethodProperty { get; set; } - - public byte[] AuthenticationDataProperty { get; set; } + public MqttConnAckPacketProperties Properties { get; set; } #endregion diff --git a/Source/MQTTnet/Packets/MqttConnAckPacketProperties.cs b/Source/MQTTnet/Packets/MqttConnAckPacketProperties.cs new file mode 100644 index 00000000..31a6b130 --- /dev/null +++ b/Source/MQTTnet/Packets/MqttConnAckPacketProperties.cs @@ -0,0 +1,40 @@ +using System.Collections.Generic; + +namespace MQTTnet.Packets +{ + public class MqttConnAckPacketProperties + { + public uint? SessionExpiryInterval { get; set; } + + // TODO: Add enum + public ushort? ReceiveMaximum { get; set; } + + public bool? RetainAvailable { get; set; } + + public uint? MaximumPacketSize { get; set; } + + public string AssignedClientIdentifier { get; set; } + + public ushort? TopicAliasMaximum { get; set; } + + public string ReasonString { get; set; } + + public List UserProperties { get; set; } + + public bool? WildcardSubscriptionAvailable { get; set; } + + public bool? SubscriptionIdentifiersAvailable { get; set; } + + public bool? SharedSubscriptionAvailable { get; set; } + + public ushort? ServerKeepAlive { get; set; } + + public string ResponseInformation { get; set; } + + public string ServerReference { get; set; } + + public string AuthenticationMethod { get; set; } + + public byte[] AuthenticationData { get; set; } + } +} \ No newline at end of file diff --git a/Source/MQTTnet/Packets/MqttConnectPacket.cs b/Source/MQTTnet/Packets/MqttConnectPacket.cs index 0708123b..1fc5a42e 100644 --- a/Source/MQTTnet/Packets/MqttConnectPacket.cs +++ b/Source/MQTTnet/Packets/MqttConnectPacket.cs @@ -1,12 +1,10 @@ -using System.Collections.Generic; - -namespace MQTTnet.Packets +namespace MQTTnet.Packets { public class MqttConnectPacket : MqttBasePacket { public string ProtocolName { get; set; } - public byte ProtocolLevel { get; set; } + public byte? ProtocolLevel { get; set; } public string ClientId { get; set; } @@ -25,25 +23,7 @@ namespace MQTTnet.Packets #region Added in MQTTv5 - public uint? WillDelayIntervalProperty { get; set; } - - public uint? SessionExpiryIntervalProperty { get; set; } - - public string AuthenticationMethodProperty { get; set; } - - public byte[] AuthenticationDataProperty { get; set; } - - public bool? RequestProblemInformationProperty { get; set; } - - public bool? RequestResponseInformationProperty { get; set; } - - public ushort? ReceiveMaximumProperty { get; set; } - - public ushort? TopicAliasMaximumProperty { get; set; } - - public uint? MaximumPacketSizeProperty { get; set; } - - public List UserPropertiesProperty { get; set; } + public MqttConnectPacketProperties Properties { get; set; } #endregion diff --git a/Source/MQTTnet/Packets/MqttConnectPacketProperties.cs b/Source/MQTTnet/Packets/MqttConnectPacketProperties.cs new file mode 100644 index 00000000..4cc405e2 --- /dev/null +++ b/Source/MQTTnet/Packets/MqttConnectPacketProperties.cs @@ -0,0 +1,27 @@ +using System.Collections.Generic; + +namespace MQTTnet.Packets +{ + public class MqttConnectPacketProperties + { + public uint? WillDelayInterval { get; set; } + + public uint? SessionExpiryInterval { get; set; } + + public string AuthenticationMethod { get; set; } + + public byte[] AuthenticationData { get; set; } + + public bool? RequestProblemInformation { get; set; } + + public bool? RequestResponseInformation { get; set; } + + public ushort? ReceiveMaximum { get; set; } + + public ushort? TopicAliasMaximum { get; set; } + + public uint? MaximumPacketSize { get; set; } + + public List UserProperties { get; set; } + } +} \ No newline at end of file diff --git a/Source/MQTTnet/Packets/MqttDisconnectPacket.cs b/Source/MQTTnet/Packets/MqttDisconnectPacket.cs index 5dbbd497..621829a2 100644 --- a/Source/MQTTnet/Packets/MqttDisconnectPacket.cs +++ b/Source/MQTTnet/Packets/MqttDisconnectPacket.cs @@ -1,15 +1,7 @@ -using System.Collections.Generic; -using MQTTnet.Packets.Properties; - -namespace MQTTnet.Packets +namespace MQTTnet.Packets { public class MqttDisconnectPacket : MqttBasePacket { - /// - /// Added in MQTTv5.0.0. - /// - public List Properties { get; set; } - public override string ToString() { return "Disconnect"; diff --git a/Source/MQTTnet/Packets/MqttPubAckPacket.cs b/Source/MQTTnet/Packets/MqttPubAckPacket.cs index 5811849b..ad29f079 100644 --- a/Source/MQTTnet/Packets/MqttPubAckPacket.cs +++ b/Source/MQTTnet/Packets/MqttPubAckPacket.cs @@ -1,10 +1,20 @@ -namespace MQTTnet.Packets +using MQTTnet.Protocol; + +namespace MQTTnet.Packets { public class MqttPubAckPacket : MqttBasePublishPacket { + #region Added in MQTTv5 + + public MqttPubAckReasonCode? ConnectReasonCode { get; set; } + + public MqttPubAckPacketProperties Properties { get; set; } + + #endregion + public override string ToString() { - return $"PubAck [PacketIdentifier={PacketIdentifier}]"; + return $"PubAck: [PacketIdentifier={PacketIdentifier}] [ConnectReasonCode={ConnectReasonCode}]"; } } } diff --git a/Source/MQTTnet/Packets/MqttPubAckPacketProperties.cs b/Source/MQTTnet/Packets/MqttPubAckPacketProperties.cs new file mode 100644 index 00000000..e2debd4a --- /dev/null +++ b/Source/MQTTnet/Packets/MqttPubAckPacketProperties.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; + +namespace MQTTnet.Packets +{ + public class MqttPubAckPacketProperties + { + public string ReasonString { get; set; } + + public List UserProperties { get; set; } + } +} diff --git a/Source/MQTTnet/Packets/MqttPublishPacket.cs b/Source/MQTTnet/Packets/MqttPublishPacket.cs index 374f913b..e1644d0c 100644 --- a/Source/MQTTnet/Packets/MqttPublishPacket.cs +++ b/Source/MQTTnet/Packets/MqttPublishPacket.cs @@ -1,6 +1,4 @@ -using System.Collections.Generic; -using MQTTnet.Packets.Properties; -using MQTTnet.Protocol; +using MQTTnet.Protocol; namespace MQTTnet.Packets { @@ -16,10 +14,11 @@ namespace MQTTnet.Packets public byte[] Payload { get; set; } - /// - /// Added in MQTTv5.0.0. - /// - public List Properties { get; set; } + #region Added in MQTTv5 + + public MqttPublishPacketProperties Properties { get; set; } + + #endregion public override string ToString() { diff --git a/Source/MQTTnet/Packets/MqttPublishPacketProperties.cs b/Source/MQTTnet/Packets/MqttPublishPacketProperties.cs new file mode 100644 index 00000000..ba7254da --- /dev/null +++ b/Source/MQTTnet/Packets/MqttPublishPacketProperties.cs @@ -0,0 +1,23 @@ +using System.Collections.Generic; + +namespace MQTTnet.Packets +{ + public class MqttPublishPacketProperties + { + public byte? PayloadFormatIndicator { get; set; } + + public uint? MessageExpiryInterval { get; set; } + + public ushort? TopicAlias { get; set; } + + public string ResponseTopic { get; set; } + + public byte[] CorrelationData { get; set; } + + public List UserProperties { get; set; } + + public uint? SubscriptionIdentifier { get; set; } + + public string ContentType { get; set; } + } +} diff --git a/Source/MQTTnet/Packets/MqttSubscribePacket.cs b/Source/MQTTnet/Packets/MqttSubscribePacket.cs index 89a1efe6..60086c47 100644 --- a/Source/MQTTnet/Packets/MqttSubscribePacket.cs +++ b/Source/MQTTnet/Packets/MqttSubscribePacket.cs @@ -1,6 +1,5 @@ using System.Collections.Generic; using System.Linq; -using MQTTnet.Packets.Properties; namespace MQTTnet.Packets { @@ -10,11 +9,6 @@ namespace MQTTnet.Packets public IList TopicFilters { get; set; } = new List(); - /// - /// Added in MQTTv5.0.0. - /// - public List Properties { get; set; } - public override string ToString() { var topicFiltersText = string.Join(",", TopicFilters.Select(f => f.Topic + "@" + f.QualityOfServiceLevel)); diff --git a/Source/MQTTnet/Packets/Properties/AssignedClientIdentifierProperty.cs b/Source/MQTTnet/Packets/Properties/AssignedClientIdentifierProperty.cs deleted file mode 100644 index bda8e20e..00000000 --- a/Source/MQTTnet/Packets/Properties/AssignedClientIdentifierProperty.cs +++ /dev/null @@ -1,12 +0,0 @@ -using MQTTnet.Packets.Properties.BaseTypes; - -namespace MQTTnet.Packets.Properties -{ - public class AssignedClientIdentifierProperty : StringProperty - { - public AssignedClientIdentifierProperty(string value) - : base((byte)MqttMessagePropertyID.AssignedClientIdentifer, value) - { - } - } -} diff --git a/Source/MQTTnet/Packets/Properties/AuthenticationDataProperty.cs b/Source/MQTTnet/Packets/Properties/AuthenticationDataProperty.cs deleted file mode 100644 index 48cba80e..00000000 --- a/Source/MQTTnet/Packets/Properties/AuthenticationDataProperty.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; -using MQTTnet.Packets.Properties.BaseTypes; - -namespace MQTTnet.Packets.Properties -{ - public class AuthenticationDataProperty : BinaryDataProperty - { - public AuthenticationDataProperty(ArraySegment data) - : base((byte)MqttMessagePropertyID.AuthenticationData, data) - { - } - } -} diff --git a/Source/MQTTnet/Packets/Properties/AuthenticationMethodProperty.cs b/Source/MQTTnet/Packets/Properties/AuthenticationMethodProperty.cs deleted file mode 100644 index 9693278b..00000000 --- a/Source/MQTTnet/Packets/Properties/AuthenticationMethodProperty.cs +++ /dev/null @@ -1,12 +0,0 @@ -using MQTTnet.Packets.Properties.BaseTypes; - -namespace MQTTnet.Packets.Properties -{ - public class AuthenticationMethodProperty : StringProperty - { - public AuthenticationMethodProperty(string value) - : base((byte)MqttMessagePropertyID.AuthenticationMethod, value) - { - } - } -} diff --git a/Source/MQTTnet/Packets/Properties/BaseTypes/BinaryDataProperty.cs b/Source/MQTTnet/Packets/Properties/BaseTypes/BinaryDataProperty.cs deleted file mode 100644 index 484d5ee7..00000000 --- a/Source/MQTTnet/Packets/Properties/BaseTypes/BinaryDataProperty.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System; -using MQTTnet.Formatter; - -namespace MQTTnet.Packets.Properties.BaseTypes -{ - public class BinaryDataProperty : IProperty - { - public BinaryDataProperty(byte id, ArraySegment data) - { - if (data.Array == null) - { - throw new ArgumentNullException(nameof(data)); - } - - Id = id; - Data = data; - } - - public byte Id { get; } - - public ArraySegment Data { get; } - - public void WriteTo(MqttPacketWriter writer) - { - if (writer == null) throw new ArgumentNullException(nameof(writer)); - - writer.Write(Id); - writer.Write((ushort)Data.Count); - writer.Write(Data); - } - } -} diff --git a/Source/MQTTnet/Packets/Properties/BaseTypes/ByteProperty.cs b/Source/MQTTnet/Packets/Properties/BaseTypes/ByteProperty.cs deleted file mode 100644 index 1bfb9aab..00000000 --- a/Source/MQTTnet/Packets/Properties/BaseTypes/ByteProperty.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System; -using MQTTnet.Formatter; - -namespace MQTTnet.Packets.Properties.BaseTypes -{ - public class ByteProperty : IProperty - { - public ByteProperty(byte id, byte value) - { - Id = id; - Value = value; - } - - public byte Id { get; } - - public byte Value { get; } - - public void WriteTo(MqttPacketWriter writer) - { - if (writer == null) throw new ArgumentNullException(nameof(writer)); - - writer.Write(Id); - writer.Write(Value); - } - } -} diff --git a/Source/MQTTnet/Packets/Properties/BaseTypes/FourByteIntegerProperty.cs b/Source/MQTTnet/Packets/Properties/BaseTypes/FourByteIntegerProperty.cs deleted file mode 100644 index 594ec3e0..00000000 --- a/Source/MQTTnet/Packets/Properties/BaseTypes/FourByteIntegerProperty.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; -using MQTTnet.Formatter; - -namespace MQTTnet.Packets.Properties.BaseTypes -{ - public class FourByteIntegerValue : IProperty - { - public FourByteIntegerValue(byte id, uint value) - { - Id = id; - Value = value; - } - - public byte Id { get; } - - public uint Value { get; } - - public void WriteTo(MqttPacketWriter writer) - { - if (writer == null) throw new ArgumentNullException(nameof(writer)); - - // TODO: Check if order must be reversed like for ushort. - writer.Write(Id); - var bytes = BitConverter.GetBytes(Value); - writer.Write(bytes, 0, bytes.Length); - } - } -} diff --git a/Source/MQTTnet/Packets/Properties/BaseTypes/StringProperty.cs b/Source/MQTTnet/Packets/Properties/BaseTypes/StringProperty.cs deleted file mode 100644 index ff3c1fbf..00000000 --- a/Source/MQTTnet/Packets/Properties/BaseTypes/StringProperty.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System; -using MQTTnet.Formatter; - -namespace MQTTnet.Packets.Properties.BaseTypes -{ - public class StringProperty : IProperty - { - public StringProperty(byte id, string value) - { - Id = id; - Value = value; - } - - public byte Id { get; } - - public string Value { get; } - - public void WriteTo(MqttPacketWriter writer) - { - if (writer == null) throw new ArgumentNullException(nameof(writer)); - - writer.Write(Id); - writer.WriteWithLengthPrefix(Value); - } - } -} diff --git a/Source/MQTTnet/Packets/Properties/BaseTypes/TwoByteIntegerProperty.cs b/Source/MQTTnet/Packets/Properties/BaseTypes/TwoByteIntegerProperty.cs deleted file mode 100644 index 8e5a4da0..00000000 --- a/Source/MQTTnet/Packets/Properties/BaseTypes/TwoByteIntegerProperty.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System; -using MQTTnet.Formatter; - -namespace MQTTnet.Packets.Properties.BaseTypes -{ - public class TwoByteIntegerProperty : IProperty - { - public TwoByteIntegerProperty(byte id, ushort value) - { - Id = id; - Value = value; - } - - public byte Id { get; } - - public ushort Value { get; } - - public void WriteTo(MqttPacketWriter writer) - { - if (writer == null) throw new ArgumentNullException(nameof(writer)); - - writer.Write(Id); - writer.Write(Value); - } - } -} diff --git a/Source/MQTTnet/Packets/Properties/BaseTypes/VariableByteIntegerProperty.cs b/Source/MQTTnet/Packets/Properties/BaseTypes/VariableByteIntegerProperty.cs deleted file mode 100644 index e1fd355c..00000000 --- a/Source/MQTTnet/Packets/Properties/BaseTypes/VariableByteIntegerProperty.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; -using MQTTnet.Formatter; - -namespace MQTTnet.Packets.Properties.BaseTypes -{ - public class VariableByteIntegerProperty : IProperty - { - public VariableByteIntegerProperty(byte id, uint value) - { - Id = id; - Value = value; - } - - public byte Id { get; } - - public uint Value { get; } - - public void WriteTo(MqttPacketWriter writer) - { - if (writer == null) throw new ArgumentNullException(nameof(writer)); - - var buffer = MqttPacketWriter.EncodeVariableByteInteger(Value); - - writer.Write(Id); - writer.Write(buffer); - } - } -} diff --git a/Source/MQTTnet/Packets/Properties/ContentTypeProperty.cs b/Source/MQTTnet/Packets/Properties/ContentTypeProperty.cs deleted file mode 100644 index 459b70d6..00000000 --- a/Source/MQTTnet/Packets/Properties/ContentTypeProperty.cs +++ /dev/null @@ -1,12 +0,0 @@ -using MQTTnet.Packets.Properties.BaseTypes; - -namespace MQTTnet.Packets.Properties -{ - public class ContentTypeProperty : StringProperty - { - public ContentTypeProperty(string value) - : base((byte)MqttMessagePropertyID.ContentType, value) - { - } - } -} diff --git a/Source/MQTTnet/Packets/Properties/CorrelationDataProperty.cs b/Source/MQTTnet/Packets/Properties/CorrelationDataProperty.cs deleted file mode 100644 index 8b517e3c..00000000 --- a/Source/MQTTnet/Packets/Properties/CorrelationDataProperty.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; -using MQTTnet.Packets.Properties.BaseTypes; - -namespace MQTTnet.Packets.Properties -{ - public class CorrelationDataProperty : BinaryDataProperty - { - public CorrelationDataProperty(ArraySegment data) - : base((byte)MqttMessagePropertyID.CorrelationData, data) - { - } - } -} diff --git a/Source/MQTTnet/Packets/Properties/IProperty.cs b/Source/MQTTnet/Packets/Properties/IProperty.cs deleted file mode 100644 index 484e48ba..00000000 --- a/Source/MQTTnet/Packets/Properties/IProperty.cs +++ /dev/null @@ -1,11 +0,0 @@ -using MQTTnet.Formatter; - -namespace MQTTnet.Packets.Properties -{ - public interface IProperty - { - byte Id { get; } - - void WriteTo(MqttPacketWriter writer); - } -} diff --git a/Source/MQTTnet/Packets/Properties/MaximumPacketSizeProperty.cs b/Source/MQTTnet/Packets/Properties/MaximumPacketSizeProperty.cs deleted file mode 100644 index be060648..00000000 --- a/Source/MQTTnet/Packets/Properties/MaximumPacketSizeProperty.cs +++ /dev/null @@ -1,12 +0,0 @@ -using MQTTnet.Packets.Properties.BaseTypes; - -namespace MQTTnet.Packets.Properties -{ - public class MaximumPacketSizeProperty : FourByteIntegerValue - { - public MaximumPacketSizeProperty(uint value) - : base((byte)MqttMessagePropertyID.MaximumPacketSize, value) - { - } - } -} diff --git a/Source/MQTTnet/Packets/Properties/MaximumQoSProperty.cs b/Source/MQTTnet/Packets/Properties/MaximumQoSProperty.cs deleted file mode 100644 index 168b8ca0..00000000 --- a/Source/MQTTnet/Packets/Properties/MaximumQoSProperty.cs +++ /dev/null @@ -1,12 +0,0 @@ -using MQTTnet.Packets.Properties.BaseTypes; - -namespace MQTTnet.Packets.Properties -{ - public class MaximumQoSProperty : ByteProperty - { - public MaximumQoSProperty(byte value) - : base((byte)MqttMessagePropertyID.MaximumQoS, value) - { - } - } -} diff --git a/Source/MQTTnet/Packets/Properties/MessageExpiryIntervalProperty.cs b/Source/MQTTnet/Packets/Properties/MessageExpiryIntervalProperty.cs deleted file mode 100644 index 6b944f04..00000000 --- a/Source/MQTTnet/Packets/Properties/MessageExpiryIntervalProperty.cs +++ /dev/null @@ -1,12 +0,0 @@ -using MQTTnet.Packets.Properties.BaseTypes; - -namespace MQTTnet.Packets.Properties -{ - public class MessageExpiryIntervalProperty : FourByteIntegerValue - { - public MessageExpiryIntervalProperty(uint value) - : base((byte)MqttMessagePropertyID.MessageExpiryInterval, value) - { - } - } -} diff --git a/Source/MQTTnet/Packets/Properties/PayloadFormatIndicatorProperty.cs b/Source/MQTTnet/Packets/Properties/PayloadFormatIndicatorProperty.cs deleted file mode 100644 index 79a7d22d..00000000 --- a/Source/MQTTnet/Packets/Properties/PayloadFormatIndicatorProperty.cs +++ /dev/null @@ -1,12 +0,0 @@ -using MQTTnet.Packets.Properties.BaseTypes; - -namespace MQTTnet.Packets.Properties -{ - public class PayloadFormatIndicatorProperty : ByteProperty - { - public PayloadFormatIndicatorProperty(byte value) - : base((byte)MqttMessagePropertyID.PayloadFormatIndicator, value) - { - } - } -} diff --git a/Source/MQTTnet/Packets/Properties/ReasonStringProperty.cs b/Source/MQTTnet/Packets/Properties/ReasonStringProperty.cs deleted file mode 100644 index 1a3d4f15..00000000 --- a/Source/MQTTnet/Packets/Properties/ReasonStringProperty.cs +++ /dev/null @@ -1,12 +0,0 @@ -using MQTTnet.Packets.Properties.BaseTypes; - -namespace MQTTnet.Packets.Properties -{ - public class ReasonStringProperty : StringProperty - { - public ReasonStringProperty(string value) - : base((byte)MqttMessagePropertyID.ReasonString, value) - { - } - } -} diff --git a/Source/MQTTnet/Packets/Properties/ReceiveMaximumProperty.cs b/Source/MQTTnet/Packets/Properties/ReceiveMaximumProperty.cs deleted file mode 100644 index 3263eb03..00000000 --- a/Source/MQTTnet/Packets/Properties/ReceiveMaximumProperty.cs +++ /dev/null @@ -1,12 +0,0 @@ -using MQTTnet.Packets.Properties.BaseTypes; - -namespace MQTTnet.Packets.Properties -{ - public class ReceiveMaximumProperty : TwoByteIntegerProperty - { - public ReceiveMaximumProperty(ushort value) - : base((byte)MqttMessagePropertyID.ReceiveMaximum, value) - { - } - } -} diff --git a/Source/MQTTnet/Packets/Properties/RequestProblemInformationProperty.cs b/Source/MQTTnet/Packets/Properties/RequestProblemInformationProperty.cs deleted file mode 100644 index 537257c6..00000000 --- a/Source/MQTTnet/Packets/Properties/RequestProblemInformationProperty.cs +++ /dev/null @@ -1,12 +0,0 @@ -using MQTTnet.Packets.Properties.BaseTypes; - -namespace MQTTnet.Packets.Properties -{ - public class RequestProblemInformationProperty : ByteProperty - { - public RequestProblemInformationProperty(byte value) - : base((byte)MqttMessagePropertyID.RequestProblemInformation, value) - { - } - } -} diff --git a/Source/MQTTnet/Packets/Properties/RequestResponseInformationProperty.cs b/Source/MQTTnet/Packets/Properties/RequestResponseInformationProperty.cs deleted file mode 100644 index 881f7eae..00000000 --- a/Source/MQTTnet/Packets/Properties/RequestResponseInformationProperty.cs +++ /dev/null @@ -1,12 +0,0 @@ -using MQTTnet.Packets.Properties.BaseTypes; - -namespace MQTTnet.Packets.Properties -{ - public class RequestResponseInformationProperty : ByteProperty - { - public RequestResponseInformationProperty(byte value) - : base((byte)MqttMessagePropertyID.RequestResponseInformation, value) - { - } - } -} diff --git a/Source/MQTTnet/Packets/Properties/ResponseInformationProperty.cs b/Source/MQTTnet/Packets/Properties/ResponseInformationProperty.cs deleted file mode 100644 index e080f992..00000000 --- a/Source/MQTTnet/Packets/Properties/ResponseInformationProperty.cs +++ /dev/null @@ -1,12 +0,0 @@ -using MQTTnet.Packets.Properties.BaseTypes; - -namespace MQTTnet.Packets.Properties -{ - public class ResponseInformationProperty : StringProperty - { - public ResponseInformationProperty(string value) - : base((byte)MqttMessagePropertyID.ResponseInformation, value) - { - } - } -} diff --git a/Source/MQTTnet/Packets/Properties/ResponseTopicProperty.cs b/Source/MQTTnet/Packets/Properties/ResponseTopicProperty.cs deleted file mode 100644 index 5f221863..00000000 --- a/Source/MQTTnet/Packets/Properties/ResponseTopicProperty.cs +++ /dev/null @@ -1,12 +0,0 @@ -using MQTTnet.Packets.Properties.BaseTypes; - -namespace MQTTnet.Packets.Properties -{ - public class ResponseTopicProperty : StringProperty - { - public ResponseTopicProperty(string value) - : base((byte)MqttMessagePropertyID.ResponseTopic, value) - { - } - } -} diff --git a/Source/MQTTnet/Packets/Properties/RetainAvailableProperty.cs b/Source/MQTTnet/Packets/Properties/RetainAvailableProperty.cs deleted file mode 100644 index f407bdcc..00000000 --- a/Source/MQTTnet/Packets/Properties/RetainAvailableProperty.cs +++ /dev/null @@ -1,12 +0,0 @@ -using MQTTnet.Packets.Properties.BaseTypes; - -namespace MQTTnet.Packets.Properties -{ - public class RetainAvailableProperty : ByteProperty - { - public RetainAvailableProperty(byte value) - : base((byte)MqttMessagePropertyID.RetainAvailable, value) - { - } - } -} diff --git a/Source/MQTTnet/Packets/Properties/ServerKeepAliveProperty.cs b/Source/MQTTnet/Packets/Properties/ServerKeepAliveProperty.cs deleted file mode 100644 index 272860c4..00000000 --- a/Source/MQTTnet/Packets/Properties/ServerKeepAliveProperty.cs +++ /dev/null @@ -1,12 +0,0 @@ -using MQTTnet.Packets.Properties.BaseTypes; - -namespace MQTTnet.Packets.Properties -{ - public class ServerKeepAliveProperty : TwoByteIntegerProperty - { - public ServerKeepAliveProperty(ushort value) - : base((byte)MqttMessagePropertyID.ServerKeepAlive, value) - { - } - } -} diff --git a/Source/MQTTnet/Packets/Properties/ServerReferenceProperty.cs b/Source/MQTTnet/Packets/Properties/ServerReferenceProperty.cs deleted file mode 100644 index 2b50e793..00000000 --- a/Source/MQTTnet/Packets/Properties/ServerReferenceProperty.cs +++ /dev/null @@ -1,12 +0,0 @@ -using MQTTnet.Packets.Properties.BaseTypes; - -namespace MQTTnet.Packets.Properties -{ - public class ServerReferenceProperty : StringProperty - { - public ServerReferenceProperty(string value) - : base((byte)MqttMessagePropertyID.ServerReference, value) - { - } - } -} diff --git a/Source/MQTTnet/Packets/Properties/SessionExpiryIntervalProperty.cs b/Source/MQTTnet/Packets/Properties/SessionExpiryIntervalProperty.cs deleted file mode 100644 index 651de5d2..00000000 --- a/Source/MQTTnet/Packets/Properties/SessionExpiryIntervalProperty.cs +++ /dev/null @@ -1,12 +0,0 @@ -using MQTTnet.Packets.Properties.BaseTypes; - -namespace MQTTnet.Packets.Properties -{ - public class SessionExpiryIntervalProperty : FourByteIntegerValue - { - public SessionExpiryIntervalProperty(uint value) - : base((byte)MqttMessagePropertyID.SessionExpiryInterval, value) - { - } - } -} diff --git a/Source/MQTTnet/Packets/Properties/SharedSubscriptionAvailableProperty.cs b/Source/MQTTnet/Packets/Properties/SharedSubscriptionAvailableProperty.cs deleted file mode 100644 index 8461fa38..00000000 --- a/Source/MQTTnet/Packets/Properties/SharedSubscriptionAvailableProperty.cs +++ /dev/null @@ -1,12 +0,0 @@ -using MQTTnet.Packets.Properties.BaseTypes; - -namespace MQTTnet.Packets.Properties -{ - public class SharedSubscriptionAvailableProperty : ByteProperty - { - public SharedSubscriptionAvailableProperty(byte value) - : base((byte)MqttMessagePropertyID.SharedSubscriptionAvailable, value) - { - } - } -} diff --git a/Source/MQTTnet/Packets/Properties/SubscriptionIdentifierAvailable.cs b/Source/MQTTnet/Packets/Properties/SubscriptionIdentifierAvailable.cs deleted file mode 100644 index d4789b23..00000000 --- a/Source/MQTTnet/Packets/Properties/SubscriptionIdentifierAvailable.cs +++ /dev/null @@ -1,12 +0,0 @@ -using MQTTnet.Packets.Properties.BaseTypes; - -namespace MQTTnet.Packets.Properties -{ - public class SubscriptionIdentifierAvailable : ByteProperty - { - public SubscriptionIdentifierAvailable(byte value) - : base((byte)MqttMessagePropertyID.SubscriptionIdentifierAvailable, value) - { - } - } -} diff --git a/Source/MQTTnet/Packets/Properties/SubscriptionIdentifierProperty.cs b/Source/MQTTnet/Packets/Properties/SubscriptionIdentifierProperty.cs deleted file mode 100644 index 9f9a56fe..00000000 --- a/Source/MQTTnet/Packets/Properties/SubscriptionIdentifierProperty.cs +++ /dev/null @@ -1,12 +0,0 @@ -using MQTTnet.Packets.Properties.BaseTypes; - -namespace MQTTnet.Packets.Properties -{ - public class SubscriptionIdentifierProperty : VariableByteIntegerProperty - { - public SubscriptionIdentifierProperty(uint value) - : base((byte)MqttMessagePropertyID.SubscriptionIdentifier, value) - { - } - } -} diff --git a/Source/MQTTnet/Packets/Properties/TopicAliasMaximumProperty.cs b/Source/MQTTnet/Packets/Properties/TopicAliasMaximumProperty.cs deleted file mode 100644 index 02c36c08..00000000 --- a/Source/MQTTnet/Packets/Properties/TopicAliasMaximumProperty.cs +++ /dev/null @@ -1,12 +0,0 @@ -using MQTTnet.Packets.Properties.BaseTypes; - -namespace MQTTnet.Packets.Properties -{ - public class TopicAliasMaximumProperty : TwoByteIntegerProperty - { - public TopicAliasMaximumProperty(ushort value) - : base((byte)MqttMessagePropertyID.TopicAliasMaximum, value) - { - } - } -} diff --git a/Source/MQTTnet/Packets/Properties/TopicAliasProperty.cs b/Source/MQTTnet/Packets/Properties/TopicAliasProperty.cs deleted file mode 100644 index 41c4495e..00000000 --- a/Source/MQTTnet/Packets/Properties/TopicAliasProperty.cs +++ /dev/null @@ -1,12 +0,0 @@ -using MQTTnet.Packets.Properties.BaseTypes; - -namespace MQTTnet.Packets.Properties -{ - public class TopicAliasProperty : TwoByteIntegerProperty - { - public TopicAliasProperty(ushort value) - : base((byte)MqttMessagePropertyID.TopicAlias, value) - { - } - } -} diff --git a/Source/MQTTnet/Packets/Properties/UserPropertyProperty.cs b/Source/MQTTnet/Packets/Properties/UserPropertyProperty.cs deleted file mode 100644 index 0708d3f4..00000000 --- a/Source/MQTTnet/Packets/Properties/UserPropertyProperty.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System; -using MQTTnet.Formatter; - -namespace MQTTnet.Packets.Properties -{ - public class UserPropertyProperty : IProperty - { - public UserPropertyProperty(string name, string value) - { - Name = name ?? throw new ArgumentNullException(nameof(name)); - Value = value ?? throw new ArgumentNullException(nameof(value)); - } - - public byte Id { get; } = (byte)MqttMessagePropertyID.UserProperty; - - public string Name { get; } - - public string Value { get; } - - public void WriteTo(MqttPacketWriter writer) - { - if (writer == null) throw new ArgumentNullException(nameof(writer)); - - writer.Write(Id); - writer.WriteWithLengthPrefix(Name); - writer.WriteWithLengthPrefix(Value); - } - } -} diff --git a/Source/MQTTnet/Packets/Properties/WildcardSubscriptionAvailableProperty.cs b/Source/MQTTnet/Packets/Properties/WildcardSubscriptionAvailableProperty.cs deleted file mode 100644 index 7c901ec4..00000000 --- a/Source/MQTTnet/Packets/Properties/WildcardSubscriptionAvailableProperty.cs +++ /dev/null @@ -1,12 +0,0 @@ -using MQTTnet.Packets.Properties.BaseTypes; - -namespace MQTTnet.Packets.Properties -{ - public class WildcardSubscriptionAvailableProperty : ByteProperty - { - public WildcardSubscriptionAvailableProperty(byte value) - : base((byte)MqttMessagePropertyID.WildcardSubscriptionAvailable, value) - { - } - } -} diff --git a/Source/MQTTnet/Packets/Properties/WillDelayIntervalProperty.cs b/Source/MQTTnet/Packets/Properties/WillDelayIntervalProperty.cs deleted file mode 100644 index f40bfbc3..00000000 --- a/Source/MQTTnet/Packets/Properties/WillDelayIntervalProperty.cs +++ /dev/null @@ -1,12 +0,0 @@ -using MQTTnet.Packets.Properties.BaseTypes; - -namespace MQTTnet.Packets.Properties -{ - public class WillDelayIntervalProperty : FourByteIntegerValue - { - public WillDelayIntervalProperty(uint value) - : base((byte)MqttMessagePropertyID.WillDelayInterval, value) - { - } - } -} diff --git a/Source/MQTTnet/Protocol/MqttConnectReasonCode.cs b/Source/MQTTnet/Protocol/MqttConnectReasonCode.cs new file mode 100644 index 00000000..381c6fa5 --- /dev/null +++ b/Source/MQTTnet/Protocol/MqttConnectReasonCode.cs @@ -0,0 +1,28 @@ +namespace MQTTnet.Protocol +{ + public enum MqttConnectReasonCode + { + Success = 0, + UnspecifiedError = 128, + MalformedPacket = 129, + ProtocolError = 130, + ImplementationSpecificError = 131, + UnsupportedProtocolVersion = 132, + ClientIdentifierNotValid = 133, + BadUserNameorPassword = 134, + NotAuthorized = 135, + ServerUnavailable = 136, + ServerBusy = 137, + Banned = 138, + BadAuthenticationMethod = 140, + TopicNameInvalid = 144, + PacketTooLarge = 149, + QuotaExceeded = 151, + PayloadFormatInvalid = 153, + RetainNotSupported = 154, + QoSNotSupported = 155, + UseAnotherServer = 156, + ServerMoved = 157, + ConnectionRateExceeded = 159 + } +} diff --git a/Source/MQTTnet/Packets/Properties/MqttMessagePropertyID.cs b/Source/MQTTnet/Protocol/MqttPropertyID.cs similarity index 92% rename from Source/MQTTnet/Packets/Properties/MqttMessagePropertyID.cs rename to Source/MQTTnet/Protocol/MqttPropertyID.cs index 2b8666d9..704d8dfc 100644 --- a/Source/MQTTnet/Packets/Properties/MqttMessagePropertyID.cs +++ b/Source/MQTTnet/Protocol/MqttPropertyID.cs @@ -1,6 +1,6 @@ -namespace MQTTnet.Packets.Properties +namespace MQTTnet.Protocol { - public enum MqttMessagePropertyID + public enum MqttPropertyID { PayloadFormatIndicator = 1, MessageExpiryInterval = 2, diff --git a/Source/MQTTnet/Protocol/MqttPubAckReasonCode.cs b/Source/MQTTnet/Protocol/MqttPubAckReasonCode.cs new file mode 100644 index 00000000..b0580d6a --- /dev/null +++ b/Source/MQTTnet/Protocol/MqttPubAckReasonCode.cs @@ -0,0 +1,15 @@ +namespace MQTTnet.Protocol +{ + public enum MqttPubAckReasonCode + { + Success = 0, + NoMatchingSubscribers = 16, + UnspecifiedError = 128, + ImplementationSpecificError = 131, + NotAuthorized = 135, + TopicNameInvalid = 144, + PacketIdentifierInUse = 145, + QuotaExceeded = 151, + PayloadFormatInvalid = 153 + } +} diff --git a/Source/MQTTnet/Server/IMqttClientSession.cs b/Source/MQTTnet/Server/IMqttClientSession.cs index e0dd214c..6e29a2f2 100644 --- a/Source/MQTTnet/Server/IMqttClientSession.cs +++ b/Source/MQTTnet/Server/IMqttClientSession.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Threading.Tasks; using MQTTnet.Adapter; -using MQTTnet.Packets; namespace MQTTnet.Server { @@ -11,7 +10,7 @@ namespace MQTTnet.Server string ClientId { get; } void FillStatus(MqttClientSessionStatus status); - void EnqueueApplicationMessage(MqttClientSession senderClientSession, MqttPublishPacket publishPacket); + void EnqueueApplicationMessage(MqttClientSession senderClientSession, MqttApplicationMessage applicationMessage); void ClearPendingApplicationMessages(); Task RunAsync(MqttApplicationMessage willMessage, int keepAliveInterval, IMqttChannelAdapter adapter); diff --git a/Source/MQTTnet/Server/MqttClientSession.cs b/Source/MQTTnet/Server/MqttClientSession.cs index 1005110b..cc949a48 100644 --- a/Source/MQTTnet/Server/MqttClientSession.cs +++ b/Source/MQTTnet/Server/MqttClientSession.cs @@ -61,7 +61,7 @@ namespace MQTTnet.Server status.ClientId = ClientId; status.IsConnected = _adapter != null; status.Endpoint = _adapter?.Endpoint; - status.ProtocolVersion = _adapter?.PacketSerializerAdapter?.ProtocolVersion; + status.ProtocolVersion = _adapter?.PacketFormatterAdapter?.ProtocolVersion; status.PendingApplicationMessagesCount = _pendingPacketsQueue.Count; status.LastPacketReceived = _keepAliveMonitor.LastPacketReceived; status.LastNonKeepAlivePacketReceived = _keepAliveMonitor.LastNonKeepAlivePacketReceived; @@ -193,7 +193,7 @@ namespace MQTTnet.Server if (_willMessage != null && !_wasCleanDisconnect) { - _sessionsManager.EnqueueApplicationMessage(this, _willMessage.ToPublishPacket()); + _sessionsManager.EnqueueApplicationMessage(this, _willMessage); } _willMessage = null; @@ -211,24 +211,20 @@ namespace MQTTnet.Server } } - public void EnqueueApplicationMessage(MqttClientSession senderClientSession, MqttPublishPacket publishPacket) + public void EnqueueApplicationMessage(MqttClientSession senderClientSession, MqttApplicationMessage applicationMessage) { - if (publishPacket == null) throw new ArgumentNullException(nameof(publishPacket)); + if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage)); - var checkSubscriptionsResult = _subscriptionsManager.CheckSubscriptions(publishPacket.Topic, publishPacket.QualityOfServiceLevel); + var checkSubscriptionsResult = _subscriptionsManager.CheckSubscriptions(applicationMessage.Topic, applicationMessage.QualityOfServiceLevel); if (!checkSubscriptionsResult.IsSubscribed) { return; } - publishPacket = new MqttPublishPacket - { - Topic = publishPacket.Topic, - Payload = publishPacket.Payload, - QualityOfServiceLevel = checkSubscriptionsResult.QualityOfServiceLevel, - Retain = false, - Dup = false - }; + var publishPacket = _adapter.PacketFormatterAdapter.ConvertApplicationMessageToPublishPacket(applicationMessage); + publishPacket.QualityOfServiceLevel = checkSubscriptionsResult.QualityOfServiceLevel; + publishPacket.Retain = false; + publishPacket.Dup = false; if (publishPacket.QualityOfServiceLevel > 0) { @@ -370,7 +366,7 @@ namespace MQTTnet.Server var retainedMessages = _retainedMessagesManager.GetSubscribedMessages(topicFilters); foreach (var applicationMessage in retainedMessages) { - EnqueueApplicationMessage(null, applicationMessage.ToPublishPacket()); + EnqueueApplicationMessage(null, applicationMessage); } } @@ -422,7 +418,7 @@ namespace MQTTnet.Server private void HandleIncomingPublishPacketWithQoS0(MqttPublishPacket publishPacket) { - _sessionsManager.EnqueueApplicationMessage(this, publishPacket); + _sessionsManager.EnqueueApplicationMessage(this, publishPacket.ToApplicationMessage()); } private void HandleIncomingPublishPacketWithQoS1( @@ -430,7 +426,7 @@ namespace MQTTnet.Server MqttPublishPacket publishPacket, CancellationToken cancellationToken) { - _sessionsManager.EnqueueApplicationMessage(this, publishPacket); + _sessionsManager.EnqueueApplicationMessage(this, publishPacket.ToApplicationMessage()); var response = new MqttPubAckPacket { @@ -446,7 +442,7 @@ namespace MQTTnet.Server CancellationToken cancellationToken) { // QoS 2 is implement as method "B" (4.3.3 QoS 2: Exactly once delivery) - _sessionsManager.EnqueueApplicationMessage(this, publishPacket); + _sessionsManager.EnqueueApplicationMessage(this, publishPacket.ToApplicationMessage()); var response = new MqttPubRecPacket { diff --git a/Source/MQTTnet/Server/MqttClientSessionsManager.cs b/Source/MQTTnet/Server/MqttClientSessionsManager.cs index 7c55109f..0b886363 100644 --- a/Source/MQTTnet/Server/MqttClientSessionsManager.cs +++ b/Source/MQTTnet/Server/MqttClientSessionsManager.cs @@ -80,11 +80,11 @@ namespace MQTTnet.Server return result; } - public void EnqueueApplicationMessage(MqttClientSession senderClientSession, MqttPublishPacket publishPacket) + public void EnqueueApplicationMessage(MqttClientSession senderClientSession, MqttApplicationMessage applicationMessage) { - if (publishPacket == null) throw new ArgumentNullException(nameof(publishPacket)); + if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage)); - _messageQueue.Add(new MqttEnqueuedApplicationMessage(senderClientSession, publishPacket), _cancellationToken); + _messageQueue.Add(new MqttEnqueuedApplicationMessage(senderClientSession, applicationMessage), _cancellationToken); } public Task SubscribeAsync(string clientId, IList topicFilters) @@ -157,8 +157,9 @@ namespace MQTTnet.Server try { var enqueuedApplicationMessage = _messageQueue.Take(cancellationToken); + var sender = enqueuedApplicationMessage.Sender; - var applicationMessage = enqueuedApplicationMessage.PublishPacket.ToApplicationMessage(); + var applicationMessage = enqueuedApplicationMessage.ApplicationMessage; var interceptorContext = InterceptApplicationMessage(sender, applicationMessage); if (interceptorContext != null) @@ -185,7 +186,7 @@ namespace MQTTnet.Server foreach (var clientSession in GetSessions()) { - clientSession.EnqueueApplicationMessage(enqueuedApplicationMessage.Sender, applicationMessage.ToPublishPacket()); + clientSession.EnqueueApplicationMessage(enqueuedApplicationMessage.Sender, applicationMessage); } } catch (OperationCanceledException) diff --git a/Source/MQTTnet/Server/MqttEnqueuedApplicationMessage.cs b/Source/MQTTnet/Server/MqttEnqueuedApplicationMessage.cs index 37591d89..20ff2feb 100644 --- a/Source/MQTTnet/Server/MqttEnqueuedApplicationMessage.cs +++ b/Source/MQTTnet/Server/MqttEnqueuedApplicationMessage.cs @@ -1,17 +1,15 @@ -using MQTTnet.Packets; - -namespace MQTTnet.Server +namespace MQTTnet.Server { public class MqttEnqueuedApplicationMessage { - public MqttEnqueuedApplicationMessage(MqttClientSession sender, MqttPublishPacket publishPacket) + public MqttEnqueuedApplicationMessage(MqttClientSession sender, MqttApplicationMessage applicationMessage) { Sender = sender; - PublishPacket = publishPacket; + ApplicationMessage = applicationMessage; } public MqttClientSession Sender { get; } - public MqttPublishPacket PublishPacket { get; } + public MqttApplicationMessage ApplicationMessage { get; } } } \ No newline at end of file diff --git a/Source/MQTTnet/Server/MqttServer.cs b/Source/MQTTnet/Server/MqttServer.cs index a960ab00..e0f5a53d 100644 --- a/Source/MQTTnet/Server/MqttServer.cs +++ b/Source/MQTTnet/Server/MqttServer.cs @@ -76,7 +76,7 @@ namespace MQTTnet.Server if (_cancellationTokenSource == null) throw new InvalidOperationException("The server is not started."); - _clientSessionsManager.EnqueueApplicationMessage(null, applicationMessage.ToPublishPacket()); + _clientSessionsManager.EnqueueApplicationMessage(null, applicationMessage); return Task.FromResult(0); } diff --git a/Tests/MQTTnet.AspNetCore.Tests/MqttConnectionContextTest.cs b/Tests/MQTTnet.AspNetCore.Tests/MqttConnectionContextTest.cs index 4461a135..9edcdc2e 100644 --- a/Tests/MQTTnet.AspNetCore.Tests/MqttConnectionContextTest.cs +++ b/Tests/MQTTnet.AspNetCore.Tests/MqttConnectionContextTest.cs @@ -17,7 +17,7 @@ namespace MQTTnet.AspNetCore.Tests [TestMethod] public async Task TestReceivePacketAsyncThrowsWhenReaderCompleted() { - var serializer = new MqttPacketSerializerAdapter(MqttProtocolVersion.V311); + var serializer = new MqttPacketFormatterAdapter(MqttProtocolVersion.V311); var pipe = new DuplexPipeMockup(); var connection = new DefaultConnectionContext(); connection.Transport = pipe; @@ -31,7 +31,7 @@ namespace MQTTnet.AspNetCore.Tests [TestMethod] public async Task TestParallelWrites() { - var serializer = new MqttPacketSerializerAdapter(MqttProtocolVersion.V311); + var serializer = new MqttPacketFormatterAdapter(MqttProtocolVersion.V311); var pipe = new DuplexPipeMockup(); var connection = new DefaultConnectionContext(); connection.Transport = pipe; diff --git a/Tests/MQTTnet.AspNetCore.Tests/ReaderExtensionsTest.cs b/Tests/MQTTnet.AspNetCore.Tests/ReaderExtensionsTest.cs index 7d5e55ac..1db41c78 100644 --- a/Tests/MQTTnet.AspNetCore.Tests/ReaderExtensionsTest.cs +++ b/Tests/MQTTnet.AspNetCore.Tests/ReaderExtensionsTest.cs @@ -2,6 +2,7 @@ using System.Buffers; using Microsoft.VisualStudio.TestTools.UnitTesting; using MQTTnet.AspNetCore; +using MQTTnet.Formatter; using MQTTnet.Formatter.V311; using MQTTnet.Packets; @@ -13,7 +14,7 @@ namespace MQTTnet.AspNetCore.Tests [TestMethod] public void TestTryDeserialize() { - var serializer = new MqttV311PacketFormatter(); + var serializer = new MqttPacketFormatterAdapter(MqttProtocolVersion.V311); var buffer = serializer.Encode(new MqttPublishPacket() {Topic = "a", Payload = new byte[5]}); @@ -26,20 +27,20 @@ namespace MQTTnet.AspNetCore.Tests var result = false; part = sequence.Slice(sequence.Start, 0); // empty message should fail - result = serializer.TryDeserialize(part, out packet, out consumed, out observed); + result = serializer.TryDecode(part, out packet, out consumed, out observed); Assert.IsFalse(result); part = sequence.Slice(sequence.Start, 1); // partial fixed header should fail - result = serializer.TryDeserialize(part, out packet, out consumed, out observed); + result = serializer.TryDecode(part, out packet, out consumed, out observed); Assert.IsFalse(result); part = sequence.Slice(sequence.Start, 4); // partial body should fail - result = serializer.TryDeserialize(part, out packet, out consumed, out observed); + result = serializer.TryDecode(part, out packet, out consumed, out observed); Assert.IsFalse(result); part = sequence; // complete msg should work - result = serializer.TryDeserialize(part, out packet, out consumed, out observed); + result = serializer.TryDecode(part, out packet, out consumed, out observed); Assert.IsTrue(result); } } diff --git a/Tests/MQTTnet.Benchmarks/ChannelAdapterBenchmark.cs b/Tests/MQTTnet.Benchmarks/ChannelAdapterBenchmark.cs index fb7443cb..ea035700 100644 --- a/Tests/MQTTnet.Benchmarks/ChannelAdapterBenchmark.cs +++ b/Tests/MQTTnet.Benchmarks/ChannelAdapterBenchmark.cs @@ -21,12 +21,12 @@ namespace MQTTnet.Benchmarks [GlobalSetup] public void Setup() { - var message = new MqttApplicationMessageBuilder() - .WithTopic("A") - .Build(); + _packet = new MqttPublishPacket + { + Topic = "A" + }; - _packet = message.ToPublishPacket(); - var serializer = new MqttPacketSerializerAdapter(MqttProtocolVersion.V311); + var serializer = new MqttPacketFormatterAdapter(MqttProtocolVersion.V311); var serializedPacket = Join(serializer.Encode(_packet)); diff --git a/Tests/MQTTnet.Benchmarks/MQTTnet.Benchmarks.csproj b/Tests/MQTTnet.Benchmarks/MQTTnet.Benchmarks.csproj index 33f08667..26e2415f 100644 --- a/Tests/MQTTnet.Benchmarks/MQTTnet.Benchmarks.csproj +++ b/Tests/MQTTnet.Benchmarks/MQTTnet.Benchmarks.csproj @@ -8,9 +8,9 @@ - + - + diff --git a/Tests/MQTTnet.Benchmarks/SerializerBenchmark.cs b/Tests/MQTTnet.Benchmarks/SerializerBenchmark.cs index 53caec15..c7fdb99d 100644 --- a/Tests/MQTTnet.Benchmarks/SerializerBenchmark.cs +++ b/Tests/MQTTnet.Benchmarks/SerializerBenchmark.cs @@ -1,6 +1,5 @@ using BenchmarkDotNet.Attributes; using MQTTnet.Packets; -using MQTTnet.Internal; using System; using System.Threading; using System.Threading.Tasks; @@ -23,11 +22,11 @@ namespace MQTTnet.Benchmarks [GlobalSetup] public void Setup() { - var message = new MqttApplicationMessageBuilder() - .WithTopic("A") - .Build(); + _packet = new MqttPublishPacket + { + Topic = "A" + }; - _packet = message.ToPublishPacket(); _serializer = new MqttV311PacketFormatter(); _serializedPacket = _serializer.Encode(_packet); } @@ -47,17 +46,17 @@ namespace MQTTnet.Benchmarks { var channel = new BenchmarkMqttChannel(_serializedPacket); var fixedHeader = new byte[2]; - var singleByteBuffer = new byte[1]; + var reader = new MqttPacketReader(channel); for (var i = 0; i < 10000; i++) { channel.Reset(); - var header = MqttPacketReader.ReadFixedHeaderAsync(channel, fixedHeader, singleByteBuffer, CancellationToken.None).GetAwaiter().GetResult(); + var header = reader.ReadFixedHeaderAsync(fixedHeader, CancellationToken.None).GetAwaiter().GetResult(); var receivedPacket = new ReceivedMqttPacket( header.Flags, - new MqttPacketBodyReader(_serializedPacket.Array, _serializedPacket.Count - header.RemainingLength, _serializedPacket.Array.Length)); + new MqttPacketBodyReader(_serializedPacket.Array, (ulong)(_serializedPacket.Count - header.RemainingLength), (ulong)_serializedPacket.Array.Length)); _serializer.Decode(receivedPacket); } diff --git a/Tests/MQTTnet.Core.Tests/MqttKeepAliveMonitorTests.cs b/Tests/MQTTnet.Core.Tests/MqttKeepAliveMonitorTests.cs index a36555a4..183955a1 100644 --- a/Tests/MQTTnet.Core.Tests/MqttKeepAliveMonitorTests.cs +++ b/Tests/MQTTnet.Core.Tests/MqttKeepAliveMonitorTests.cs @@ -67,7 +67,7 @@ namespace MQTTnet.Core.Tests throw new NotSupportedException(); } - public void EnqueueApplicationMessage(MqttClientSession senderClientSession, MqttPublishPacket publishPacket) + public void EnqueueApplicationMessage(MqttClientSession senderClientSession, MqttApplicationMessage applicationMessage) { throw new NotSupportedException(); } diff --git a/Tests/MQTTnet.Core.Tests/MqttPacketReaderTests.cs b/Tests/MQTTnet.Core.Tests/MqttPacketReaderTests.cs index eb2ad8f7..51c5e073 100644 --- a/Tests/MQTTnet.Core.Tests/MqttPacketReaderTests.cs +++ b/Tests/MQTTnet.Core.Tests/MqttPacketReaderTests.cs @@ -15,8 +15,8 @@ namespace MQTTnet.Core.Tests public void MqttPacketReader_EmptyStream() { var fixedHeader = new byte[2]; - var singleByteBuffer = new byte[1]; - MqttPacketReader.ReadFixedHeaderAsync(new TestMqttChannel(new MemoryStream()), fixedHeader, singleByteBuffer, CancellationToken.None).GetAwaiter().GetResult(); + var reader = new MqttPacketReader(new TestMqttChannel(new MemoryStream())); + reader.ReadFixedHeaderAsync(fixedHeader, CancellationToken.None).GetAwaiter().GetResult(); } } } diff --git a/Tests/MQTTnet.Core.Tests/MqttPacketSerializerTests.cs b/Tests/MQTTnet.Core.Tests/MqttPacketSerializerTests.cs index ebb379f6..f8bffcdf 100644 --- a/Tests/MQTTnet.Core.Tests/MqttPacketSerializerTests.cs +++ b/Tests/MQTTnet.Core.Tests/MqttPacketSerializerTests.cs @@ -181,17 +181,15 @@ namespace MQTTnet.Core.Tests var buffer = serializer.Encode(publishPacket); var testChannel = new TestMqttChannel(new MemoryStream(buffer.Array, buffer.Offset, buffer.Count)); - var header = MqttPacketReader.ReadFixedHeaderAsync( - testChannel, + var header = new MqttPacketReader(testChannel).ReadFixedHeaderAsync( new byte[2], - new byte[1], CancellationToken.None).GetAwaiter().GetResult(); var eof = buffer.Offset + buffer.Count; var receivedPacket = new ReceivedMqttPacket( header.Flags, - new MqttPacketBodyReader(buffer.Array, eof - header.RemainingLength, buffer.Count + buffer.Offset)); + new MqttPacketBodyReader(buffer.Array, (uint)eof - header.RemainingLength, (uint)(buffer.Count + buffer.Offset))); var packet = (MqttPublishPacket)serializer.Decode(receivedPacket); @@ -552,10 +550,9 @@ namespace MQTTnet.Core.Tests { var channel = new TestMqttChannel(headerStream); var fixedHeader = new byte[2]; - var singleByteBuffer = new byte[1]; - var header = MqttPacketReader.ReadFixedHeaderAsync(channel, fixedHeader, singleByteBuffer, CancellationToken.None).GetAwaiter().GetResult(); + var header = new MqttPacketReader(channel).ReadFixedHeaderAsync(fixedHeader, CancellationToken.None).GetAwaiter().GetResult(); - using (var bodyStream = new MemoryStream(Join(buffer1), (int)headerStream.Position, header.RemainingLength)) + using (var bodyStream = new MemoryStream(Join(buffer1), (int)headerStream.Position, (int)header.RemainingLength)) { var deserializedPacket = serializer.Decode(new ReceivedMqttPacket(header.Flags, new MqttPacketBodyReader(bodyStream.ToArray(), 0, (int)bodyStream.Length))); var buffer2 = serializer.Encode(deserializedPacket); @@ -588,11 +585,10 @@ namespace MQTTnet.Core.Tests { var channel = new TestMqttChannel(headerStream); var fixedHeader = new byte[2]; - var singleByteBuffer = new byte[1]; - var header = MqttPacketReader.ReadFixedHeaderAsync(channel, fixedHeader, singleByteBuffer, CancellationToken.None).GetAwaiter().GetResult(); + var header = new MqttPacketReader(channel).ReadFixedHeaderAsync(fixedHeader, CancellationToken.None).GetAwaiter().GetResult(); - using (var bodyStream = new MemoryStream(Join(buffer1), (int)headerStream.Position, header.RemainingLength)) + using (var bodyStream = new MemoryStream(Join(buffer1), (int)headerStream.Position, (int)header.RemainingLength)) { return (T)serializer.Decode(new ReceivedMqttPacket(header.Flags, new MqttPacketBodyReader(bodyStream.ToArray(), 0, (int)bodyStream.Length))); } diff --git a/Tests/MQTTnet.Core.Tests/TestMqttCommunicationAdapter.cs b/Tests/MQTTnet.Core.Tests/TestMqttCommunicationAdapter.cs index ceb44e13..fd95cfae 100644 --- a/Tests/MQTTnet.Core.Tests/TestMqttCommunicationAdapter.cs +++ b/Tests/MQTTnet.Core.Tests/TestMqttCommunicationAdapter.cs @@ -16,7 +16,7 @@ namespace MQTTnet.Core.Tests public string Endpoint { get; } - public MqttPacketSerializerAdapter PacketSerializerAdapter { get; } = new MqttPacketSerializerAdapter(MqttProtocolVersion.V311); + public MqttPacketFormatterAdapter PacketFormatterAdapter { get; } = new MqttPacketFormatterAdapter(MqttProtocolVersion.V311); public event EventHandler ReadingPacketStarted; public event EventHandler ReadingPacketCompleted; diff --git a/Tests/MQTTnet.TestApp.AspNetCore2/MQTTnet.TestApp.AspNetCore2.csproj b/Tests/MQTTnet.TestApp.AspNetCore2/MQTTnet.TestApp.AspNetCore2.csproj index a23f309b..b8a9307f 100644 --- a/Tests/MQTTnet.TestApp.AspNetCore2/MQTTnet.TestApp.AspNetCore2.csproj +++ b/Tests/MQTTnet.TestApp.AspNetCore2/MQTTnet.TestApp.AspNetCore2.csproj @@ -10,8 +10,8 @@ - - + + diff --git a/Tests/MQTTnet.TestApp.UniversalWindows/MQTTnet.TestApp.UniversalWindows.csproj b/Tests/MQTTnet.TestApp.UniversalWindows/MQTTnet.TestApp.UniversalWindows.csproj index f8583aa1..551bd2eb 100644 --- a/Tests/MQTTnet.TestApp.UniversalWindows/MQTTnet.TestApp.UniversalWindows.csproj +++ b/Tests/MQTTnet.TestApp.UniversalWindows/MQTTnet.TestApp.UniversalWindows.csproj @@ -142,10 +142,10 @@ - 6.1.9 + 6.2.2 - 4.0.0 + 5.0.0 From 0f5fb8aa3a12f109d8534b6c6d8e81d55589584c Mon Sep 17 00:00:00 2001 From: Christian Kratky Date: Wed, 28 Nov 2018 21:11:23 +0100 Subject: [PATCH 009/124] Add more pacakges. --- Images/Logo_128x128.png | Bin 1183 -> 1269 bytes README.md | 5 +++-- .../Formatter/V500/MqttV500PacketDecoder.cs | 2 +- .../Formatter/V500/MqttV500PacketEncoder.cs | 10 ++++----- .../Formatter/V500/MqttV500PacketFormatter.cs | 3 ++- Source/MQTTnet/Packets/MqttConnAckPacket.cs | 4 ++-- Source/MQTTnet/Packets/MqttConnectPacket.cs | 2 +- Source/MQTTnet/Packets/MqttPubAckPacket.cs | 4 ++-- Source/MQTTnet/Packets/MqttPubCompPacket.cs | 14 +++++++++++-- .../Packets/MqttPubCompPacketProperties.cs | 11 ++++++++++ Source/MQTTnet/Packets/MqttPubRecPacket.cs | 14 +++++++++++-- .../Packets/MqttPubRecPacketProperties.cs | 11 ++++++++++ Source/MQTTnet/Packets/MqttPubRelPacket.cs | 14 +++++++++++-- .../Packets/MqttPubRelPacketProperties.cs | 11 ++++++++++ Source/MQTTnet/Packets/MqttPublishPacket.cs | 11 +++------- Source/MQTTnet/Packets/MqttSubAckPacket.cs | 12 +++++++++-- .../Packets/MqttSubAckPacketProperties.cs | 11 ++++++++++ Source/MQTTnet/Packets/MqttSubscribePacket.cs | 8 +++++++- .../Packets/MqttSubscribePacketProperties.cs | 11 ++++++++++ Source/MQTTnet/Packets/MqttUnsubAckPacket.cs | 2 +- Source/MQTTnet/Packets/MqttUnsubscribe.cs | 2 +- .../MQTTnet/Protocol/MqttPubCompReasonCode.cs | 8 ++++++++ .../MQTTnet/Protocol/MqttPubRecReasonCode.cs | 15 ++++++++++++++ .../MQTTnet/Protocol/MqttPubRelReasonCode.cs | 8 ++++++++ Source/MQTTnet/Protocol/MqttRetainHandling.cs | 9 +++++++++ .../Protocol/MqttSubscribeReasonCode.cs | 18 +++++++++++++++++ Source/MQTTnet/TopicFilter.cs | 19 ++++++++++++++++++ 27 files changed, 206 insertions(+), 33 deletions(-) create mode 100644 Source/MQTTnet/Packets/MqttPubCompPacketProperties.cs create mode 100644 Source/MQTTnet/Packets/MqttPubRecPacketProperties.cs create mode 100644 Source/MQTTnet/Packets/MqttPubRelPacketProperties.cs create mode 100644 Source/MQTTnet/Packets/MqttSubAckPacketProperties.cs create mode 100644 Source/MQTTnet/Packets/MqttSubscribePacketProperties.cs create mode 100644 Source/MQTTnet/Protocol/MqttPubCompReasonCode.cs create mode 100644 Source/MQTTnet/Protocol/MqttPubRecReasonCode.cs create mode 100644 Source/MQTTnet/Protocol/MqttPubRelReasonCode.cs create mode 100644 Source/MQTTnet/Protocol/MqttRetainHandling.cs create mode 100644 Source/MQTTnet/Protocol/MqttSubscribeReasonCode.cs diff --git a/Images/Logo_128x128.png b/Images/Logo_128x128.png index a528d7ce54a8fa545838b6ce8373d1bdf828436e..697acbf2be5de49921e0fe840fa9c4276485ddbb 100644 GIT binary patch literal 1269 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1SEZ8zRdwrEa{HEjtmSN`?>!lvI6;>1s;*b z3=Dh+K$tP>S|=w^P@=>&q9iy!t)x7$D3zfgF*C13FE6!3!9>qc&qUZE;|S24Vow*x zkczmsGrarmScy1qUX`Qp`~KP4`#DTF@D>cfMQOI+?P*locvLH_^WUy{9P z44Wkn zvOzaa$MNorP5H*~gx&hHBlCtWN-vc*i+axHk!$#qklx3CVS=B&bo@jhp*`u_rj1H3 zx9(vS5`AhoA>)iY&w&cpOMBm}^{7(hZ*Xy~;c2y)7LC( zp0jT#HIrQ9QKg$7reZrKR3R`>j=A$#%>@Rb&?l-BUIc_SLanT6_*YncQ|{^GL*I6s z1X?t4y2B#pNLHvT`S}l=ch~QITR53xft z;*t!VTAzeGN;FnGELvp4Ce-_LCZp7+=d}wj?$d5Hxl9~fnMTuIpg3cARNU*PV(Y~z4C5YpLpOfYv?dX9LI@@pY z#NAet_ZBa?pYnlg=Pt*?45Cl=Oz#b^dGd;FE^~~?uNBijdf7g`to>_p^%oumXc`Vv ziA`Zs$pXgT&MkkX3pAu17O4RPQeS$3+|D^g43*-MOkYntHDUfB>Y=iU<<-Q~n(Qlz zKTh{}Vgz>0{)hV+#XrfxxXUFMfRYz59V<--hQ=CTXx!cXUZt@i?pC!lvI6;>1s;*b z3=DjSK$uZf!>a)(C|TkfQ4*Y=R#Ki=l*&+$n3-3imzP?iV4`QBXK1nbV$&y}K{B2$ zjv*CsZ>I&WyJIH6QmhrP@O{7UKUsmshQj4XmQGtFSH7EDCzm$$8)oOF|x#fjQ%h*~ANfzs4YPYXVnp)jY@B#_ylK{C@xIVrGff zy4Cm2UVgv-^(6+WEvtpL1h03>4}r)kGb`M?856khY!6p%*pEU+jV&v+wn#$xJ8T-* za%ZPs*tYUQTS&%duU0c-tu2v>OcEgw^*eMLewE*qmAhhZUQ!;hu=+gL-T0Lk(%f3j zv};rvj-3g6&wJ}jNXF%0i1HZ>Y@r#(i=AeR>hUh9TW$R-Zl(1ubC9&JKg8&V$&4oJ z>Q;xnuuE!vXDfQ^%!&(Xt(*w~P}xrmufA{Ie?7nVSM3*M75!`r=H$Nme(`sQ=&tV} z5G(#gFnnDnz4zr_$Jz6M;i0uumcmFotzxh{sm z_tD(5qEP3{OFDek`f?1;m3Al=`SmswCM$d3q2!j@aHtR0GXB{jy6gD=-&+|?GKBws zSsRmMcxjOC(l4)>J8DT!GYO5NyWXcT zMI^Mov!2Ny7P0WRpIQTJ*o%FWIUl5n-g=Y5v?H?B%=+hJMwu^pT)FFSG=EFK$GZ3L zl-n#97UbK$vR003tXmHeVJ#4?eSfBWLHVk3$9(&zVT>Be)wfo#yafggFzj!h)MZ`} z8@ll9LMLe;Ig=~5tY|kw#$v54mTNBjJ;QbPJTMj}NB>hVIKp*zHZT%_qCP;YGldWE zeE9}aZp6sr^nNmg;gCNcn99^Y*Eh@#1!{vhcF9$H#w|OeLGHcEXrf)RAH>WQb!awA zkGm@m@>IOM!{h~b6IF~fOw0&oWq|;(nA(c-hI3N{(q+PZ;tA-%%8*#EKC?YUHx3vIVCg!05!-BEC2ui diff --git a/README.md b/README.md index cb405dd4..a187cc54 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ MQTTnet is a high performance .NET library for MQTT based communication. It prov * Async support * TLS 1.2 support for client and server (but not UWP servers) -* Extensible communication channels (i.e. In-Memory, TCP, TCP+TLS, WS) +* Extensible communication channels (e.g. In-Memory, TCP, TCP+TLS, WS) * Lightweight (only the low level implementation of MQTT, no overhead) * Performance optimized (processing ~70.000 messages / second)* * Interfaces included for mocking and testing @@ -48,6 +48,7 @@ MQTTnet is a high performance .NET library for MQTT based communication. It prov * WebSockets supported (via ASP.NET Core 2.0, separate nuget) * A custom message interceptor can be added which allows transforming or extending every received application message * Validate subscriptions and deny subscribing of certain topics depending on requesting clients +* Connect clients with different protocol versions at the same time. ## Supported frameworks @@ -62,7 +63,7 @@ MQTTnet is a high performance .NET library for MQTT based communication. It prov ## Supported MQTT versions -* 5.0.0 (planned) +* 5.0.0 * 3.1.1 * 3.1.0 diff --git a/Source/MQTTnet/Formatter/V500/MqttV500PacketDecoder.cs b/Source/MQTTnet/Formatter/V500/MqttV500PacketDecoder.cs index 348afd63..c0b35313 100644 --- a/Source/MQTTnet/Formatter/V500/MqttV500PacketDecoder.cs +++ b/Source/MQTTnet/Formatter/V500/MqttV500PacketDecoder.cs @@ -21,7 +21,7 @@ namespace MQTTnet.Formatter.V500 packet.IsSessionPresent = (acknowledgeFlags & 0x1) > 0; packet.ConnectReturnCode = (MqttConnectReturnCode)body.ReadByte(); - packet.ConnectReasonCode = (MqttConnectReasonCode)body.ReadByte(); + packet.ReasonCode = (MqttConnectReasonCode)body.ReadByte(); var propertiesLength = body.ReadVariableLengthInteger(); if (propertiesLength > 0) diff --git a/Source/MQTTnet/Formatter/V500/MqttV500PacketEncoder.cs b/Source/MQTTnet/Formatter/V500/MqttV500PacketEncoder.cs index 9d236bea..cb4d2a61 100644 --- a/Source/MQTTnet/Formatter/V500/MqttV500PacketEncoder.cs +++ b/Source/MQTTnet/Formatter/V500/MqttV500PacketEncoder.cs @@ -97,9 +97,9 @@ namespace MQTTnet.Formatter.V500 if (packet == null) throw new ArgumentNullException(nameof(packet)); if (packetWriter == null) throw new ArgumentNullException(nameof(packetWriter)); - if (!packet.ConnectReasonCode.HasValue) + if (!packet.ReasonCode.HasValue) { - throw new MqttProtocolViolationException("The ConnectReasonCode must be set for MQTT version 5."); + throw new MqttProtocolViolationException("The ReasonCode must be set for MQTT version 5."); } byte connectAcknowledgeFlags = 0x0; @@ -110,7 +110,7 @@ namespace MQTTnet.Formatter.V500 packetWriter.Write(connectAcknowledgeFlags); packetWriter.Write((byte)packet.ConnectReturnCode); - packetWriter.Write((byte)packet.ConnectReasonCode.Value); + packetWriter.Write((byte)packet.ReasonCode.Value); var propertiesWriter = new MqttV500PropertiesWriter(); if (packet.Properties != null) @@ -209,13 +209,13 @@ namespace MQTTnet.Formatter.V500 throw new MqttProtocolViolationException("PubAck packet has no packet identifier."); } - if (!packet.ConnectReasonCode.HasValue) + if (!packet.ReasonCode.HasValue) { throw new MqttProtocolViolationException("PubAck packet must contain a connect reason."); } packetWriter.Write(packet.PacketIdentifier.Value); - packetWriter.Write((byte)packet.ConnectReasonCode.Value); + packetWriter.Write((byte)packet.ReasonCode.Value); return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.PubAck); } diff --git a/Source/MQTTnet/Formatter/V500/MqttV500PacketFormatter.cs b/Source/MQTTnet/Formatter/V500/MqttV500PacketFormatter.cs index d95255f0..f815129b 100644 --- a/Source/MQTTnet/Formatter/V500/MqttV500PacketFormatter.cs +++ b/Source/MQTTnet/Formatter/V500/MqttV500PacketFormatter.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using MQTTnet.Formatter.V311; using MQTTnet.Packets; @@ -22,7 +23,7 @@ namespace MQTTnet.Formatter.V500 Dup = false, Properties = new MqttPublishPacketProperties { - UserProperties = applicationMessage.UserProperties + UserProperties = new List(applicationMessage.UserProperties) } }; } diff --git a/Source/MQTTnet/Packets/MqttConnAckPacket.cs b/Source/MQTTnet/Packets/MqttConnAckPacket.cs index 1a9aa669..e8caefae 100644 --- a/Source/MQTTnet/Packets/MqttConnAckPacket.cs +++ b/Source/MQTTnet/Packets/MqttConnAckPacket.cs @@ -13,7 +13,7 @@ namespace MQTTnet.Packets #region Added in MQTTv5 - public MqttConnectReasonCode? ConnectReasonCode { get; set; } + public MqttConnectReasonCode? ReasonCode { get; set; } public MqttConnAckPacketProperties Properties { get; set; } @@ -21,7 +21,7 @@ namespace MQTTnet.Packets public override string ToString() { - return "ConnAck: [ConnectReturnCode=" + ConnectReturnCode + "] [IsSessionPresent=" + IsSessionPresent + "]"; + return string.Concat("ConnAck: [ConnectReturnCode=", ConnectReturnCode, "] [ReasonCode=", ReasonCode, "] [IsSessionPresent=", IsSessionPresent, "]"); } } } diff --git a/Source/MQTTnet/Packets/MqttConnectPacket.cs b/Source/MQTTnet/Packets/MqttConnectPacket.cs index 1fc5a42e..c6b8e6e0 100644 --- a/Source/MQTTnet/Packets/MqttConnectPacket.cs +++ b/Source/MQTTnet/Packets/MqttConnectPacket.cs @@ -29,7 +29,7 @@ public override string ToString() { - return "Connect: [ProtocolLevel=" + ProtocolLevel + "] [ClientId=" + ClientId + "] [Username=" + Username + "] [Password=" + Password + "] [KeepAlivePeriod=" + KeepAlivePeriod + "] [CleanSession=" + CleanSession + "]"; + return string.Concat("Connect: [ProtocolLevel=", ProtocolLevel, "] [ClientId=", ClientId, "] [Username=", Username, "] [Password=", Password, "] [KeepAlivePeriod=", KeepAlivePeriod, "] [CleanSession=", CleanSession, "]"); } } } diff --git a/Source/MQTTnet/Packets/MqttPubAckPacket.cs b/Source/MQTTnet/Packets/MqttPubAckPacket.cs index ad29f079..2af16422 100644 --- a/Source/MQTTnet/Packets/MqttPubAckPacket.cs +++ b/Source/MQTTnet/Packets/MqttPubAckPacket.cs @@ -6,7 +6,7 @@ namespace MQTTnet.Packets { #region Added in MQTTv5 - public MqttPubAckReasonCode? ConnectReasonCode { get; set; } + public MqttPubAckReasonCode? ReasonCode { get; set; } public MqttPubAckPacketProperties Properties { get; set; } @@ -14,7 +14,7 @@ namespace MQTTnet.Packets public override string ToString() { - return $"PubAck: [PacketIdentifier={PacketIdentifier}] [ConnectReasonCode={ConnectReasonCode}]"; + return string.Concat("PubAck: [PacketIdentifier=", PacketIdentifier, "] [ConnectReasonCode=", ReasonCode, "]"); } } } diff --git a/Source/MQTTnet/Packets/MqttPubCompPacket.cs b/Source/MQTTnet/Packets/MqttPubCompPacket.cs index e9ea18eb..0798819d 100644 --- a/Source/MQTTnet/Packets/MqttPubCompPacket.cs +++ b/Source/MQTTnet/Packets/MqttPubCompPacket.cs @@ -1,10 +1,20 @@ -namespace MQTTnet.Packets +using MQTTnet.Protocol; + +namespace MQTTnet.Packets { public class MqttPubCompPacket : MqttBasePublishPacket { + #region Added in MQTTv5 + + public MqttPubCompReasonCode? ReasonCode { get; set; } + + public MqttPubCompPacketProperties Properties { get; set; } + + #endregion + public override string ToString() { - return "PubComp"; + return string.Concat("PubComp: [PacketIdentifier=", PacketIdentifier, "] [ReasonCode=", ReasonCode, "]"); } } } diff --git a/Source/MQTTnet/Packets/MqttPubCompPacketProperties.cs b/Source/MQTTnet/Packets/MqttPubCompPacketProperties.cs new file mode 100644 index 00000000..35e040f5 --- /dev/null +++ b/Source/MQTTnet/Packets/MqttPubCompPacketProperties.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; + +namespace MQTTnet.Packets +{ + public class MqttPubCompPacketProperties + { + public string ReasonString { get; set; } + + public List UserProperties { get; set; } + } +} diff --git a/Source/MQTTnet/Packets/MqttPubRecPacket.cs b/Source/MQTTnet/Packets/MqttPubRecPacket.cs index 0184d6b8..e462ce8e 100644 --- a/Source/MQTTnet/Packets/MqttPubRecPacket.cs +++ b/Source/MQTTnet/Packets/MqttPubRecPacket.cs @@ -1,10 +1,20 @@ -namespace MQTTnet.Packets +using MQTTnet.Protocol; + +namespace MQTTnet.Packets { public class MqttPubRecPacket : MqttBasePublishPacket { + #region Added in MQTTv5 + + public MqttPubRecReasonCode? ReasonCode { get; set; } + + public MqttPubRecPacketProperties Properties { get; set; } + + #endregion + public override string ToString() { - return "PubRec"; + return string.Concat("PubRec: [PacketIdentifier=", PacketIdentifier, "] [ReasonCode=", ReasonCode, "]"); } } } diff --git a/Source/MQTTnet/Packets/MqttPubRecPacketProperties.cs b/Source/MQTTnet/Packets/MqttPubRecPacketProperties.cs new file mode 100644 index 00000000..0cd72250 --- /dev/null +++ b/Source/MQTTnet/Packets/MqttPubRecPacketProperties.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; + +namespace MQTTnet.Packets +{ + public class MqttPubRecPacketProperties + { + public string ReasonString { get; set; } + + public List UserProperties { get; set; } + } +} diff --git a/Source/MQTTnet/Packets/MqttPubRelPacket.cs b/Source/MQTTnet/Packets/MqttPubRelPacket.cs index bb37655c..cf2c71b6 100644 --- a/Source/MQTTnet/Packets/MqttPubRelPacket.cs +++ b/Source/MQTTnet/Packets/MqttPubRelPacket.cs @@ -1,10 +1,20 @@ -namespace MQTTnet.Packets +using MQTTnet.Protocol; + +namespace MQTTnet.Packets { public class MqttPubRelPacket : MqttBasePublishPacket { + #region Added in MQTTv5 + + public MqttPubRelReasonCode? ReasonCode { get; set; } + + public MqttPubRelPacketProperties Properties { get; set; } + + #endregion + public override string ToString() { - return "PubRel"; + return string.Concat("PubRel: [PacketIdentifier=", PacketIdentifier, "] [ReasonCode=", ReasonCode, "]"); } } } diff --git a/Source/MQTTnet/Packets/MqttPubRelPacketProperties.cs b/Source/MQTTnet/Packets/MqttPubRelPacketProperties.cs new file mode 100644 index 00000000..aa9625db --- /dev/null +++ b/Source/MQTTnet/Packets/MqttPubRelPacketProperties.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; + +namespace MQTTnet.Packets +{ + public class MqttPubRelPacketProperties + { + public string ReasonString { get; set; } + + public List UserProperties { get; set; } + } +} diff --git a/Source/MQTTnet/Packets/MqttPublishPacket.cs b/Source/MQTTnet/Packets/MqttPublishPacket.cs index e1644d0c..2f20b143 100644 --- a/Source/MQTTnet/Packets/MqttPublishPacket.cs +++ b/Source/MQTTnet/Packets/MqttPublishPacket.cs @@ -15,19 +15,14 @@ namespace MQTTnet.Packets public byte[] Payload { get; set; } #region Added in MQTTv5 - + public MqttPublishPacketProperties Properties { get; set; } - + #endregion public override string ToString() { - return "Publish: [Topic=" + Topic + "]" + - " [Payload.Length=" + Payload?.Length + "]" + - " [QoSLevel=" + QualityOfServiceLevel + "]" + - " [Dup=" + Dup + "]" + - " [Retain=" + Retain + "]" + - " [PacketIdentifier=" + PacketIdentifier + "]"; + return string.Concat("Publish: [Topic=", Topic, "] [Payload.Length=", Payload?.Length, "] [QoSLevel=", QualityOfServiceLevel, "] [Dup=", Dup, "] [Retain=", Retain, "] [PacketIdentifier=", PacketIdentifier, "]"); } } } diff --git a/Source/MQTTnet/Packets/MqttSubAckPacket.cs b/Source/MQTTnet/Packets/MqttSubAckPacket.cs index 05e18b2b..45b0e0ce 100644 --- a/Source/MQTTnet/Packets/MqttSubAckPacket.cs +++ b/Source/MQTTnet/Packets/MqttSubAckPacket.cs @@ -8,12 +8,20 @@ namespace MQTTnet.Packets { public ushort? PacketIdentifier { get; set; } - public IList SubscribeReturnCodes { get; } = new List(); + public List SubscribeReturnCodes { get; } = new List(); + + #region Added in MQTTv5 + + public List ReasonCodes { get; set; } + + public MqttSubAckPacketProperties Properties { get; set; } + + #endregion public override string ToString() { var subscribeReturnCodesText = string.Join(",", SubscribeReturnCodes.Select(f => f.ToString())); - return "SubAck: [PacketIdentifier=" + PacketIdentifier + "] [SubscribeReturnCodes=" + subscribeReturnCodesText + "]"; + return string.Concat("SubAck: [PacketIdentifier=", PacketIdentifier, "] [SubscribeReturnCodes=", subscribeReturnCodesText, "]"); } } } diff --git a/Source/MQTTnet/Packets/MqttSubAckPacketProperties.cs b/Source/MQTTnet/Packets/MqttSubAckPacketProperties.cs new file mode 100644 index 00000000..74d3e7f8 --- /dev/null +++ b/Source/MQTTnet/Packets/MqttSubAckPacketProperties.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; + +namespace MQTTnet.Packets +{ + public class MqttSubAckPacketProperties + { + public string ReasonString { get; set; } + + public List UserProperties { get; set; } + } +} diff --git a/Source/MQTTnet/Packets/MqttSubscribePacket.cs b/Source/MQTTnet/Packets/MqttSubscribePacket.cs index 60086c47..c185f358 100644 --- a/Source/MQTTnet/Packets/MqttSubscribePacket.cs +++ b/Source/MQTTnet/Packets/MqttSubscribePacket.cs @@ -9,10 +9,16 @@ namespace MQTTnet.Packets public IList TopicFilters { get; set; } = new List(); + #region Added in MQTTv5 + + public MqttSubscribePacketProperties Properties { get; set; } + + #endregion + public override string ToString() { var topicFiltersText = string.Join(",", TopicFilters.Select(f => f.Topic + "@" + f.QualityOfServiceLevel)); - return "Subscribe: [PacketIdentifier=" + PacketIdentifier + "] [TopicFilters=" + topicFiltersText + "]"; + return string.Concat("Subscribe: [PacketIdentifier=", PacketIdentifier, "] [TopicFilters=", topicFiltersText, "]"); } } } diff --git a/Source/MQTTnet/Packets/MqttSubscribePacketProperties.cs b/Source/MQTTnet/Packets/MqttSubscribePacketProperties.cs new file mode 100644 index 00000000..34f58f2f --- /dev/null +++ b/Source/MQTTnet/Packets/MqttSubscribePacketProperties.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; + +namespace MQTTnet.Packets +{ + public class MqttSubscribePacketProperties + { + public uint? SubscriptionIdentifier { get; set; } + + public List UserProperties { get; set; } + } +} diff --git a/Source/MQTTnet/Packets/MqttUnsubAckPacket.cs b/Source/MQTTnet/Packets/MqttUnsubAckPacket.cs index ab5f9f62..8b78b537 100644 --- a/Source/MQTTnet/Packets/MqttUnsubAckPacket.cs +++ b/Source/MQTTnet/Packets/MqttUnsubAckPacket.cs @@ -6,7 +6,7 @@ public override string ToString() { - return "UnsubAck: [PacketIdentifier=" + PacketIdentifier + "]"; + return string.Concat("UnsubAck: [PacketIdentifier=", PacketIdentifier, "]"); } } } diff --git a/Source/MQTTnet/Packets/MqttUnsubscribe.cs b/Source/MQTTnet/Packets/MqttUnsubscribe.cs index 63895f12..de56386f 100644 --- a/Source/MQTTnet/Packets/MqttUnsubscribe.cs +++ b/Source/MQTTnet/Packets/MqttUnsubscribe.cs @@ -11,7 +11,7 @@ namespace MQTTnet.Packets public override string ToString() { var topicFiltersText = string.Join(",", TopicFilters); - return "Unsubscribe: [PacketIdentifier=" + PacketIdentifier + "] [TopicFilters=" + topicFiltersText + "]"; + return string.Concat("Unsubscribe: [PacketIdentifier=", PacketIdentifier, "] [TopicFilters=", topicFiltersText, "]"); } } } diff --git a/Source/MQTTnet/Protocol/MqttPubCompReasonCode.cs b/Source/MQTTnet/Protocol/MqttPubCompReasonCode.cs new file mode 100644 index 00000000..9c23927b --- /dev/null +++ b/Source/MQTTnet/Protocol/MqttPubCompReasonCode.cs @@ -0,0 +1,8 @@ +namespace MQTTnet.Protocol +{ + public enum MqttPubCompReasonCode + { + Success = 0, + PacketIdentifierNotFound = 146 + } +} diff --git a/Source/MQTTnet/Protocol/MqttPubRecReasonCode.cs b/Source/MQTTnet/Protocol/MqttPubRecReasonCode.cs new file mode 100644 index 00000000..b64eac23 --- /dev/null +++ b/Source/MQTTnet/Protocol/MqttPubRecReasonCode.cs @@ -0,0 +1,15 @@ +namespace MQTTnet.Protocol +{ + public enum MqttPubRecReasonCode + { + Success = 0, + NoMatchingSubscribers = 16, + UnspecifiedError = 128, + ImplementationSpecificError = 131, + NotAuthorized = 135, + TopicNameInvalid = 144, + PacketIdentifierInUse = 145, + QuotaExceeded = 151, + PayloadFormatInvalid = 153 + } +} diff --git a/Source/MQTTnet/Protocol/MqttPubRelReasonCode.cs b/Source/MQTTnet/Protocol/MqttPubRelReasonCode.cs new file mode 100644 index 00000000..d71da680 --- /dev/null +++ b/Source/MQTTnet/Protocol/MqttPubRelReasonCode.cs @@ -0,0 +1,8 @@ +namespace MQTTnet.Protocol +{ + public enum MqttPubRelReasonCode + { + Success = 0, + PacketIdentifierNotFound = 146 + } +} diff --git a/Source/MQTTnet/Protocol/MqttRetainHandling.cs b/Source/MQTTnet/Protocol/MqttRetainHandling.cs new file mode 100644 index 00000000..8ee50ccd --- /dev/null +++ b/Source/MQTTnet/Protocol/MqttRetainHandling.cs @@ -0,0 +1,9 @@ +namespace MQTTnet.Protocol +{ + public enum MqttRetainHandling + { + SendAtSubscribe = 0, + SendAtSubscribeIfNewSubscriptionOnly = 1, + DoNotSendOnSubscribe = 2 + } +} diff --git a/Source/MQTTnet/Protocol/MqttSubscribeReasonCode.cs b/Source/MQTTnet/Protocol/MqttSubscribeReasonCode.cs new file mode 100644 index 00000000..e3f2c0f9 --- /dev/null +++ b/Source/MQTTnet/Protocol/MqttSubscribeReasonCode.cs @@ -0,0 +1,18 @@ +namespace MQTTnet.Protocol +{ + public enum MqttSubscribeReasonCode + { + GrantedQoS0 = 0, + GrantedQoS1 = 1, + GrantedQoS2 = 2, + UnspecifiedError = 128, + ImplementationSpecificError = 131, + NotAuthorized = 135, + TopicFilterInvalid = 143, + PacketIdentifierInUse = 145, + QuotaExceeded = 151, + SharedSubscriptionsNotSupported = 158, + SubscriptionIdentifiersNotSupported = 161, + WildcardSubscriptionsNotSupported = 162 + } +} diff --git a/Source/MQTTnet/TopicFilter.cs b/Source/MQTTnet/TopicFilter.cs index 7eebbc2c..94615625 100644 --- a/Source/MQTTnet/TopicFilter.cs +++ b/Source/MQTTnet/TopicFilter.cs @@ -14,6 +14,25 @@ namespace MQTTnet public MqttQualityOfServiceLevel QualityOfServiceLevel { get; set; } + #region Added in MQTTv5 + + /// + /// This is only available when using MQTT version 5. + /// + public bool? NoLocal { get; set; } + + /// + /// This is only available when using MQTT version 5. + /// + public bool? RetainAsPublished { get; set; } + + /// + /// This is only available when using MQTT version 5. + /// + public MqttRetainHandling? RetainHandling { get; set; } + + #endregion + public override string ToString() { return Topic + "@" + QualityOfServiceLevel; From 0dba57fe75a863e21397e117a18362dc1a5c1432 Mon Sep 17 00:00:00 2001 From: Christian Kratky Date: Sun, 9 Dec 2018 21:04:14 +0100 Subject: [PATCH 010/124] Cleanup code. --- Source/MQTTnet/Server/IMqttClientSession.cs | 11 -- Source/MQTTnet/Server/MqttClientSession.cs | 151 +++++------------- .../Server/MqttClientSessionsManager.cs | 8 +- 3 files changed, 44 insertions(+), 126 deletions(-) diff --git a/Source/MQTTnet/Server/IMqttClientSession.cs b/Source/MQTTnet/Server/IMqttClientSession.cs index 6e29a2f2..51341e9a 100644 --- a/Source/MQTTnet/Server/IMqttClientSession.cs +++ b/Source/MQTTnet/Server/IMqttClientSession.cs @@ -1,22 +1,11 @@ using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using MQTTnet.Adapter; namespace MQTTnet.Server { public interface IMqttClientSession : IDisposable { string ClientId { get; } - void FillStatus(MqttClientSessionStatus status); - void EnqueueApplicationMessage(MqttClientSession senderClientSession, MqttApplicationMessage applicationMessage); - void ClearPendingApplicationMessages(); - - Task RunAsync(MqttApplicationMessage willMessage, int keepAliveInterval, IMqttChannelAdapter adapter); void Stop(MqttClientDisconnectType disconnectType); - - Task SubscribeAsync(IList topicFilters); - Task UnsubscribeAsync(IList topicFilters); } } \ No newline at end of file diff --git a/Source/MQTTnet/Server/MqttClientSession.cs b/Source/MQTTnet/Server/MqttClientSession.cs index c915dbc0..860467cf 100644 --- a/Source/MQTTnet/Server/MqttClientSession.cs +++ b/Source/MQTTnet/Server/MqttClientSession.cs @@ -6,10 +6,10 @@ using MQTTnet.Adapter; using MQTTnet.Client; using MQTTnet.Diagnostics; using MQTTnet.Exceptions; +using MQTTnet.Formatter; using MQTTnet.Internal; using MQTTnet.Packets; using MQTTnet.Protocol; -using MQTTnet.Serializer; namespace MQTTnet.Server { @@ -35,7 +35,8 @@ namespace MQTTnet.Server private string _adapterEndpoint; private MqttProtocolVersion? _adapterProtocolVersion; - + private IMqttChannelAdapter _channelAdapter; + public MqttClientSession( string clientId, IMqttServerOptions options, @@ -73,9 +74,9 @@ namespace MQTTnet.Server status.LastNonKeepAlivePacketReceived = _keepAliveMonitor.LastNonKeepAlivePacketReceived; } - public Task RunAsync(MqttApplicationMessage willMessage, int keepAlivePeriod, IMqttChannelAdapter adapter) + public Task RunAsync(MqttConnectPacket connectPacket, IMqttChannelAdapter adapter) { - _workerTask = RunInternalAsync(willMessage, keepAlivePeriod, adapter); + _workerTask = RunInternalAsync(connectPacket, adapter); return _workerTask; } @@ -84,52 +85,6 @@ namespace MQTTnet.Server Stop(type, false); } - public void EnqueueApplicationMessage(MqttClientSession senderClientSession, MqttPublishPacket publishPacket) - { - if (publishPacket == null) throw new ArgumentNullException(nameof(publishPacket)); - - var checkSubscriptionsResult = _subscriptionsManager.CheckSubscriptions(publishPacket.Topic, publishPacket.QualityOfServiceLevel); - if (!checkSubscriptionsResult.IsSubscribed) - { - return; - } - - publishPacket = new MqttPublishPacket - { - Topic = publishPacket.Topic, - Payload = publishPacket.Payload, - QualityOfServiceLevel = checkSubscriptionsResult.QualityOfServiceLevel, - Retain = publishPacket.Retain, - Dup = false - }; - - if (publishPacket.QualityOfServiceLevel > 0) - { - publishPacket.PacketIdentifier = _packetIdentifierProvider.GetNewPacketIdentifier(); - } - - if (_options.ClientMessageQueueInterceptor != null) - { - var context = new MqttClientMessageQueueInterceptorContext( - senderClientSession?.ClientId, - ClientId, - publishPacket.ToApplicationMessage()); - - _options.ClientMessageQueueInterceptor?.Invoke(context); - - if (!context.AcceptEnqueue || context.ApplicationMessage == null) - { - return; - } - - publishPacket.Topic = context.ApplicationMessage.Topic; - publishPacket.Payload = context.ApplicationMessage.Payload; - publishPacket.QualityOfServiceLevel = context.ApplicationMessage.QualityOfServiceLevel; - } - - _pendingPacketsQueue.Enqueue(publishPacket); - } - public Task SubscribeAsync(IList topicFilters) { if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters)); @@ -164,44 +119,11 @@ namespace MQTTnet.Server { _pendingPacketsQueue?.Dispose(); - _cancellationTokenSource?.Cancel (); + _cancellationTokenSource?.Cancel(); _cancellationTokenSource?.Dispose(); _cancellationTokenSource = null; } - - private void Stop(MqttClientDisconnectType type, bool isInsideSession) - { - try - { - var cts = _cancellationTokenSource; - if (cts == null || cts.IsCancellationRequested) - { - return; - } - - _cancellationTokenSource?.Cancel(false); - - _wasCleanDisconnect = type == MqttClientDisconnectType.Clean; - - if (_willMessage != null && !_wasCleanDisconnect) - { - _sessionsManager.EnqueueApplicationMessage(this, _willMessage.ToPublishPacket()); - } - - _willMessage = null; - - if (!isInsideSession) - { - _workerTask?.GetAwaiter().GetResult(); - } - } - finally - { - _logger.Info("Client '{0}': Disconnected (clean={1}).", ClientId, _wasCleanDisconnect); - _eventDispatcher.OnClientDisconnected(ClientId, _wasCleanDisconnect); - } - } - + private async Task RunInternalAsync(MqttConnectPacket connectPacket, IMqttChannelAdapter adapter) { if (adapter == null) throw new ArgumentNullException(nameof(adapter)); @@ -229,13 +151,14 @@ namespace MQTTnet.Server //end workaround _wasCleanDisconnect = false; - _willMessage = willMessage; + _willMessage = connectPacket.WillMessage; _pendingPacketsQueue.Start(adapter, _cancellationTokenSource.Token); - _keepAliveMonitor.Start(keepAlivePeriod, _cancellationTokenSource.Token); + _keepAliveMonitor.Start(connectPacket.KeepAlivePeriod, _cancellationTokenSource.Token); _adapterEndpoint = adapter.Endpoint; - _adapterProtocolVersion = adapter.PacketSerializer.ProtocolVersion; + _adapterProtocolVersion = adapter.PacketFormatterAdapter.ProtocolVersion; + _channelAdapter = adapter; while (!_cancellationTokenSource.IsCancellationRequested) { @@ -308,15 +231,10 @@ namespace MQTTnet.Server } finally { - adapter?.Dispose(); + adapter.Dispose(); } } - public void Stop(MqttClientDisconnectType type) - { - Stop(type, false); - } - private void Stop(MqttClientDisconnectType type, bool isInsideSession) { try @@ -346,12 +264,11 @@ namespace MQTTnet.Server finally { _logger.Info("Client '{0}': Session stopped.", ClientId); - - _sessionsManager.Server.OnClientDisconnected(ClientId, _wasCleanDisconnect); + _eventDispatcher.OnClientDisconnected(ClientId, _wasCleanDisconnect); } } - public void EnqueueApplicationMessage(MqttClientSession senderClientSession, MqttApplicationMessage applicationMessage) + public void EnqueueApplicationMessage(MqttClientSession senderClientSession, MqttApplicationMessage applicationMessage, bool isRetainedApplicationMessage) { if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage)); @@ -361,16 +278,38 @@ namespace MQTTnet.Server return; } - var publishPacket = _adapter.PacketFormatterAdapter.ConvertApplicationMessageToPublishPacket(applicationMessage); - publishPacket.QualityOfServiceLevel = checkSubscriptionsResult.QualityOfServiceLevel; - publishPacket.Retain = false; - publishPacket.Dup = false; - + var publishPacket = _channelAdapter.PacketFormatterAdapter.ConvertApplicationMessageToPublishPacket(applicationMessage); + + // Set the retain flag to true according to [MQTT-3.3.1-8] and [MQTT-3.3.1-9]. + publishPacket.Retain = isRetainedApplicationMessage; + if (publishPacket.QualityOfServiceLevel > 0) { publishPacket.PacketIdentifier = _packetIdentifierProvider.GetNewPacketIdentifier(); } + if (_options.ClientMessageQueueInterceptor != null) + { + var context = new MqttClientMessageQueueInterceptorContext( + senderClientSession?.ClientId, + ClientId, + applicationMessage); + + _options.ClientMessageQueueInterceptor?.Invoke(context); + + if (!context.AcceptEnqueue || context.ApplicationMessage == null) + { + return; + } + + publishPacket.Topic = context.ApplicationMessage.Topic; + publishPacket.Payload = context.ApplicationMessage.Payload; + publishPacket.QualityOfServiceLevel = context.ApplicationMessage.QualityOfServiceLevel; + } + + _pendingPacketsQueue.Enqueue(publishPacket); + } + private async Task TryDisconnectAdapterAsync(IMqttChannelAdapter adapter) { if (adapter == null) @@ -454,6 +393,7 @@ namespace MQTTnet.Server } _logger.Warning(null, "Client '{0}': Received not supported packet ({1}). Closing connection.", ClientId, packet); + Stop(MqttClientDisconnectType.NotClean, true); } @@ -462,12 +402,7 @@ namespace MQTTnet.Server var retainedMessages = _retainedMessagesManager.GetSubscribedMessages(topicFilters); foreach (var applicationMessage in retainedMessages) { - var publishPacket = applicationMessage.ToPublishPacket(); - - // Set the retain flag to true according to [MQTT-3.3.1-8]. - publishPacket.Retain = true; - - EnqueueApplicationMessage(null, publishPacket); + EnqueueApplicationMessage(null, applicationMessage, true); } } diff --git a/Source/MQTTnet/Server/MqttClientSessionsManager.cs b/Source/MQTTnet/Server/MqttClientSessionsManager.cs index 0eb6c9f1..a453915b 100644 --- a/Source/MQTTnet/Server/MqttClientSessionsManager.cs +++ b/Source/MQTTnet/Server/MqttClientSessionsManager.cs @@ -7,7 +7,6 @@ using System.Threading.Tasks; using MQTTnet.Adapter; using MQTTnet.Diagnostics; using MQTTnet.Exceptions; -using MQTTnet.Internal; using MQTTnet.Packets; using MQTTnet.Protocol; @@ -190,12 +189,7 @@ namespace MQTTnet.Server foreach (var clientSession in GetSessions()) { - var publishPacket = applicationMessage.ToPublishPacket(); - - // Set the retain flag to true according to [MQTT-3.3.1-9]. - publishPacket.Retain = false; - - clientSession.EnqueueApplicationMessage(enqueuedApplicationMessage.Sender, publishPacket); + clientSession.EnqueueApplicationMessage(enqueuedApplicationMessage.Sender, enqueuedApplicationMessage.ApplicationMessage, false); } } catch (OperationCanceledException) From 0c13e7a5044394568a921de47fd86ae10fcb2b5d Mon Sep 17 00:00:00 2001 From: Charles Quesnel Date: Thu, 13 Dec 2018 12:56:47 -0500 Subject: [PATCH 011/124] Add overload to allow passing custom request headers and cookies when connecting with WS --- .../Client/MqttClientOptionsBuilder.cs | 19 +++++++++++++++++++ ...ClientOptionsBuilderWebSocketParameters.cs | 14 ++++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 Source/MQTTnet/Client/MqttClientOptionsBuilderWebSocketParameters.cs diff --git a/Source/MQTTnet/Client/MqttClientOptionsBuilder.cs b/Source/MQTTnet/Client/MqttClientOptionsBuilder.cs index 85e2b4d8..e4754ad6 100644 --- a/Source/MQTTnet/Client/MqttClientOptionsBuilder.cs +++ b/Source/MQTTnet/Client/MqttClientOptionsBuilder.cs @@ -1,5 +1,7 @@ using System; +using System.Collections.Generic; using System.Linq; +using System.Net; using MQTTnet.Serializer; namespace MQTTnet.Client @@ -102,6 +104,23 @@ namespace MQTTnet.Client return this; } + public MqttClientOptionsBuilder WithWebSocketServer(string uri, MqttClientOptionsBuilderWebSocketParameters parameters) + { + if(parameters == null) + { + throw new ArgumentNullException(nameof(parameters)); + } + + _webSocketOptions = new MqttClientWebSocketOptions + { + Uri = uri, + RequestHeaders = parameters.RequestHeaders, + CookieContainer = parameters.CookierContainer + }; + + return this; + } + public MqttClientOptionsBuilder WithTls(MqttClientOptionsBuilderTlsParameters parameters) { _tlsParameters = parameters ?? throw new ArgumentNullException(nameof(parameters)); diff --git a/Source/MQTTnet/Client/MqttClientOptionsBuilderWebSocketParameters.cs b/Source/MQTTnet/Client/MqttClientOptionsBuilderWebSocketParameters.cs new file mode 100644 index 00000000..c58d7774 --- /dev/null +++ b/Source/MQTTnet/Client/MqttClientOptionsBuilderWebSocketParameters.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Net; +using System.Text; + +namespace MQTTnet.Client +{ + public class MqttClientOptionsBuilderWebSocketParameters + { + public IDictionary RequestHeaders { get; set; } + + public CookieContainer CookierContainer { get; set; } + } +} From a94444098a11359919f69636d3a86d089ac7b3be Mon Sep 17 00:00:00 2001 From: Charles Quesnel Date: Thu, 13 Dec 2018 13:01:34 -0500 Subject: [PATCH 012/124] Remove unused usings --- Source/MQTTnet/Client/MqttClientOptionsBuilder.cs | 2 -- .../Client/MqttClientOptionsBuilderWebSocketParameters.cs | 4 +--- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/Source/MQTTnet/Client/MqttClientOptionsBuilder.cs b/Source/MQTTnet/Client/MqttClientOptionsBuilder.cs index e4754ad6..07e98198 100644 --- a/Source/MQTTnet/Client/MqttClientOptionsBuilder.cs +++ b/Source/MQTTnet/Client/MqttClientOptionsBuilder.cs @@ -1,7 +1,5 @@ using System; -using System.Collections.Generic; using System.Linq; -using System.Net; using MQTTnet.Serializer; namespace MQTTnet.Client diff --git a/Source/MQTTnet/Client/MqttClientOptionsBuilderWebSocketParameters.cs b/Source/MQTTnet/Client/MqttClientOptionsBuilderWebSocketParameters.cs index c58d7774..7ecb2301 100644 --- a/Source/MQTTnet/Client/MqttClientOptionsBuilderWebSocketParameters.cs +++ b/Source/MQTTnet/Client/MqttClientOptionsBuilderWebSocketParameters.cs @@ -1,7 +1,5 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Net; -using System.Text; namespace MQTTnet.Client { From 40326df9a9dfe06634361f9b6e565c3acf7dcf50 Mon Sep 17 00:00:00 2001 From: Charles Quesnel Date: Thu, 13 Dec 2018 14:08:53 -0500 Subject: [PATCH 013/124] Fix typo in 'Cookie' --- Source/MQTTnet/Client/MqttClientOptionsBuilder.cs | 2 +- .../Client/MqttClientOptionsBuilderWebSocketParameters.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/MQTTnet/Client/MqttClientOptionsBuilder.cs b/Source/MQTTnet/Client/MqttClientOptionsBuilder.cs index 07e98198..e612981b 100644 --- a/Source/MQTTnet/Client/MqttClientOptionsBuilder.cs +++ b/Source/MQTTnet/Client/MqttClientOptionsBuilder.cs @@ -113,7 +113,7 @@ namespace MQTTnet.Client { Uri = uri, RequestHeaders = parameters.RequestHeaders, - CookieContainer = parameters.CookierContainer + CookieContainer = parameters.CookieContainer }; return this; diff --git a/Source/MQTTnet/Client/MqttClientOptionsBuilderWebSocketParameters.cs b/Source/MQTTnet/Client/MqttClientOptionsBuilderWebSocketParameters.cs index 7ecb2301..54e048bc 100644 --- a/Source/MQTTnet/Client/MqttClientOptionsBuilderWebSocketParameters.cs +++ b/Source/MQTTnet/Client/MqttClientOptionsBuilderWebSocketParameters.cs @@ -7,6 +7,6 @@ namespace MQTTnet.Client { public IDictionary RequestHeaders { get; set; } - public CookieContainer CookierContainer { get; set; } + public CookieContainer CookieContainer { get; set; } } } From dfda9162839842eb56e80f0c528d045e32a35b91 Mon Sep 17 00:00:00 2001 From: Charles Quesnel Date: Thu, 13 Dec 2018 14:54:31 -0500 Subject: [PATCH 014/124] Use default parameter instead of overload --- .../Client/MqttClientOptionsBuilder.cs | 21 +++---------------- 1 file changed, 3 insertions(+), 18 deletions(-) diff --git a/Source/MQTTnet/Client/MqttClientOptionsBuilder.cs b/Source/MQTTnet/Client/MqttClientOptionsBuilder.cs index e612981b..aaf4f146 100644 --- a/Source/MQTTnet/Client/MqttClientOptionsBuilder.cs +++ b/Source/MQTTnet/Client/MqttClientOptionsBuilder.cs @@ -92,28 +92,13 @@ namespace MQTTnet.Client return this; } - public MqttClientOptionsBuilder WithWebSocketServer(string uri) + public MqttClientOptionsBuilder WithWebSocketServer(string uri, MqttClientOptionsBuilderWebSocketParameters parameters = null) { - _webSocketOptions = new MqttClientWebSocketOptions - { - Uri = uri - }; - - return this; - } - - public MqttClientOptionsBuilder WithWebSocketServer(string uri, MqttClientOptionsBuilderWebSocketParameters parameters) - { - if(parameters == null) - { - throw new ArgumentNullException(nameof(parameters)); - } - _webSocketOptions = new MqttClientWebSocketOptions { Uri = uri, - RequestHeaders = parameters.RequestHeaders, - CookieContainer = parameters.CookieContainer + RequestHeaders = parameters?.RequestHeaders, + CookieContainer = parameters?.CookieContainer }; return this; From cc113a6c34ccc5b9c7361b6f4c6e0efba842638e Mon Sep 17 00:00:00 2001 From: Christian Kratky Date: Thu, 20 Dec 2018 20:53:29 +0100 Subject: [PATCH 015/124] Refactor namespaces. --- MQTTnet.sln | 2 +- README.md | 3 +- .../MqttClientConnectionContextFactory.cs | 1 + Source/MQTTnet.AspnetCore/ReaderExtensions.cs | 2 +- .../IManagedMqttClient.cs | 3 +- .../IManagedMqttClientOptions.cs | 1 + .../ManagedMqttClient.cs | 24 +- .../ManagedMqttClientOptions.cs | 1 + .../ManagedMqttClientOptionsBuilder.cs | 1 + Source/MQTTnet/Adapter/IMqttChannelAdapter.cs | 2 +- .../IMqttClientAdapterFactory.cs | 4 +- Source/MQTTnet/Adapter/MqttChannelAdapter.cs | 17 +- .../Adapter/MqttConnectingFailedException.cs | 12 +- Source/MQTTnet/Adapter/ReceivedMqttPacket.cs | 5 +- .../ApplicationMessagePublisherExtensions.cs | 11 +- .../Connecting/MqttClientConnectResult.cs | 9 + .../Connecting/MqttClientConnectResultCode.cs | 28 + .../MqttClientConnectedEventArgs.cs | 14 + .../MqttClientDisconnectOptions.cs | 9 + .../MqttClientDisconnectReason.cs | 8 + .../MqttClientDisconnectedEventArgs.cs | 2 +- Source/MQTTnet/Client/IMqttClient.cs | 11 +- Source/MQTTnet/Client/IMqttClientFactory.cs | 3 +- Source/MQTTnet/Client/MqttClient.cs | 157 ++-- .../MQTTnet/Client/MqttClientConnectResult.cs | 12 - .../Client/MqttClientConnectedEventArgs.cs | 14 - Source/MQTTnet/Client/MqttClientExtensions.cs | 18 +- Source/MQTTnet/Client/MqttSubscribeResult.cs | 17 - .../IMqttClientChannelOptions.cs | 2 +- .../{ => Options}/IMqttClientCredentials.cs | 2 +- .../{ => Options}/IMqttClientOptions.cs | 12 +- .../{ => Options}/MqttClientCredentials.cs | 2 +- .../Client/{ => Options}/MqttClientOptions.cs | 15 +- .../{ => Options}/MqttClientOptionsBuilder.cs | 51 +- .../MqttClientOptionsBuilderTlsParameters.cs | 2 +- .../{ => Options}/MqttClientTcpOptions.cs | 2 +- .../MqttClientTcpOptionsExtensions.cs | 2 +- .../{ => Options}/MqttClientTlsOptions.cs | 4 +- .../MqttClientWebSocketOptions.cs | 2 +- .../MqttClientWebSocketProxyOptions.cs | 2 +- .../PacketDispatcher/IMqttPacketAwaiter.cs | 12 + .../PacketDispatcher/MqttPacketAwaiter.cs | 37 + .../MqttPacketDispatcher.cs | 24 +- .../Publishing/MqttClientPublishReasonCode.cs | 15 + .../Publishing/MqttClientPublishResult.cs | 8 + .../Subscribing/MqttClientSubscribeResult.cs | 9 + .../MqttClientSubscribeResultCode.cs | 18 + .../MqttClientSubscribeResultItem.cs | 17 + .../MqttClientUnsubscribeResult.cs | 9 + .../MqttClientUnsubscribeResultCode.cs | 13 + .../MqttClientUnsubscribeResultItem.cs | 17 + .../MQTTnet/Formatter/IMqttDataConverter.cs | 23 + .../MQTTnet/Formatter/IMqttPacketFormatter.cs | 4 +- Source/MQTTnet/Formatter/MqttFixedHeader.cs | 7 +- .../Formatter/MqttPacketFormatterAdapter.cs | 38 +- Source/MQTTnet/Formatter/MqttPacketReader.cs | 19 +- Source/MQTTnet/Formatter/MqttPacketWriter.cs | 6 +- .../Formatter/V3/MqttV310DataConverter.cs | 147 ++++ .../{V310 => V3}/MqttV310PacketFormatter.cs | 146 ++-- .../{V311 => V3}/MqttV311PacketFormatter.cs | 13 +- .../Formatter/V5/MqttV500DataConverter.cs | 132 ++++ .../Formatter/V5/MqttV500PacketDecoder.cs | 717 ++++++++++++++++++ .../Formatter/V5/MqttV500PacketEncoder.cs | 572 ++++++++++++++ .../Formatter/V5/MqttV500PacketFormatter.cs | 34 + .../Formatter/V5/MqttV500PropertiesReader.cs | 196 +++++ .../Formatter/V5/MqttV500PropertiesWriter.cs | 238 ++++++ .../Formatter/V500/MqttV500PacketDecoder.cs | 135 ---- .../Formatter/V500/MqttV500PacketEncoder.cs | 223 ------ .../Formatter/V500/MqttV500PacketFormatter.cs | 56 -- .../V500/MqttV500PropertiesWriter.cs | 108 --- .../MQTTnet/IApplicationMessagePublisher.cs | 3 +- .../MqttClientAdapterFactory.cs | 1 + .../Implementations/MqttTcpChannel.Uwp.cs | 2 +- .../MQTTnet/Implementations/MqttTcpChannel.cs | 2 +- .../Implementations/MqttWebSocketChannel.cs | 6 +- .../MqttApplicationMessageExtensions.cs | 18 - Source/MQTTnet/MqttApplicationMessage.cs | 18 +- .../MQTTnet/MqttApplicationMessageBuilder.cs | 97 ++- Source/MQTTnet/Packets/MqttAuthPacket.cs | 2 + .../Packets/MqttAuthPacketProperties.cs | 15 + Source/MQTTnet/Packets/MqttConnAckPacket.cs | 13 +- .../Packets/MqttConnAckPacketProperties.cs | 3 +- Source/MQTTnet/Packets/MqttConnectPacket.cs | 6 +- .../Packets/MqttConnectPacketProperties.cs | 2 +- .../MQTTnet/Packets/MqttDisconnectPacket.cs | 14 +- .../Packets/MqttDisconnectPacketProperties.cs | 15 + Source/MQTTnet/Packets/MqttPubAckPacket.cs | 2 +- .../Packets/MqttPubAckPacketProperties.cs | 2 +- .../Packets/MqttPubCompPacketProperties.cs | 2 +- .../Packets/MqttPubRecPacketProperties.cs | 2 +- .../Packets/MqttPubRelPacketProperties.cs | 2 +- .../Packets/MqttPublishPacketProperties.cs | 5 +- Source/MQTTnet/Packets/MqttSubAckPacket.cs | 12 +- .../Packets/MqttSubAckPacketProperties.cs | 2 +- Source/MQTTnet/Packets/MqttSubscribePacket.cs | 2 +- .../Packets/MqttSubscribePacketProperties.cs | 2 +- Source/MQTTnet/Packets/MqttUnsubAckPacket.cs | 18 +- .../Packets/MqttUnsubAckPacketProperties.cs | 11 + ...nsubscribe.cs => MqttUnsubscribePacket.cs} | 8 +- .../MqttUnsubscribePacketProperties.cs | 9 + .../MQTTnet/Protocol/MqttConnectReasonCode.cs | 2 +- .../MQTTnet/Protocol/MqttControlPacketType.cs | 3 +- .../Protocol/MqttDisconnectReasonCode.cs | 35 + .../Protocol/MqttPayloadFormatIndicator.cs | 8 + Source/MQTTnet/Protocol/MqttPropertyID.cs | 4 +- .../Protocol/MqttUnsubscribeReasonCode.cs | 13 + Source/MQTTnet/Server/MqttClientSession.cs | 41 +- .../Server/MqttClientSessionsManager.cs | 6 +- .../Server/MqttClientSubscriptionsManager.cs | 38 +- Source/MQTTnet/Server/MqttServer.cs | 6 +- Source/MQTTnet/Server/MqttServerOptions.cs | 1 + Source/MQTTnet/Server/MqttSubscribeResult.cs | 11 - Source/MQTTnet/TopicFilter.cs | 29 +- Source/MQTTnet/TopicFilterBuilder.cs | 2 +- .../MQTTnet.AspNetCore.Tests.csproj | 5 +- .../ReaderExtensionsTest.cs | 2 - .../MessageProcessingBenchmark.cs | 1 + ...rocessingMqttConnectionContextBenchmark.cs | 1 + .../MqttTcpChannelBenchmark.cs | 1 + .../MQTTnet.Benchmarks/SerializerBenchmark.cs | 4 +- .../AsyncAutoResentEventTests.cs | 8 +- Tests/MQTTnet.Core.Tests/AsyncLockTests.cs | 2 +- .../MQTTnet.Core.Tests/BlockingQueueTests.cs | 2 +- Tests/MQTTnet.Core.Tests/ExtensionTests.cs | 2 +- ...Core.Tests.csproj => MQTTnet.Tests.csproj} | 4 +- .../MQTTv5/MqttClientTests.cs | 245 ++++++ .../ManagedMqttClientTests.cs | 6 +- .../MqttApplicationMessageBuilderTests.cs | 2 +- Tests/MQTTnet.Core.Tests/MqttClientTests.cs | 65 +- .../MqttKeepAliveMonitorTests.cs | 2 +- .../MqttPacketIdentifierProviderTests.cs | 4 +- .../MqttPacketReaderTests.cs | 2 +- .../MqttPacketSerializerTests.cs | 50 +- .../MqttPacketWriterTests.cs | 2 +- Tests/MQTTnet.Core.Tests/MqttServerTests.cs | 30 +- .../MqttSubscriptionsManagerTests.cs | 8 +- .../MQTTnet.Core.Tests/RoundtripTimeTests.cs | 3 +- .../TestMqttCommunicationAdapter.cs | 2 +- .../TestMqttCommunicationAdapterFactory.cs | 3 +- .../TestMqttServerAdapter.cs | 3 +- .../TestServerExtensions.cs | 7 +- .../TopicFilterComparerTests.cs | 2 +- .../MQTTnet.TestApp.NetCore/ClientFlowTest.cs | 1 + Tests/MQTTnet.TestApp.NetCore/ClientTest.cs | 7 +- .../MQTTnet.TestApp.NetCore.csproj | 2 +- .../ManagedClientTest.cs | 10 +- .../PerformanceTest.cs | 1 + Tests/MQTTnet.TestApp.NetCore/Program.cs | 1 + .../PublicBrokerTest.cs | 29 +- .../ServerAndClientTest.cs | 1 + .../MQTTnet.TestApp.UniversalWindows.csproj | 2 +- ....TestApp.UniversalWindows_TemporaryKey.pfx | Bin 2504 -> 2504 bytes .../MainPage.xaml | 21 +- .../MainPage.xaml.cs | 16 +- .../Package.appxmanifest | 2 +- 155 files changed, 3455 insertions(+), 1083 deletions(-) rename Source/MQTTnet/{Client => Adapter}/IMqttClientAdapterFactory.cs (76%) create mode 100644 Source/MQTTnet/Client/Connecting/MqttClientConnectResult.cs create mode 100644 Source/MQTTnet/Client/Connecting/MqttClientConnectResultCode.cs create mode 100644 Source/MQTTnet/Client/Connecting/MqttClientConnectedEventArgs.cs create mode 100644 Source/MQTTnet/Client/Disconnecting/MqttClientDisconnectOptions.cs create mode 100644 Source/MQTTnet/Client/Disconnecting/MqttClientDisconnectReason.cs rename Source/MQTTnet/Client/{ => Disconnecting}/MqttClientDisconnectedEventArgs.cs (90%) delete mode 100644 Source/MQTTnet/Client/MqttClientConnectResult.cs delete mode 100644 Source/MQTTnet/Client/MqttClientConnectedEventArgs.cs delete mode 100644 Source/MQTTnet/Client/MqttSubscribeResult.cs rename Source/MQTTnet/Client/{ => Options}/IMqttClientChannelOptions.cs (75%) rename Source/MQTTnet/Client/{ => Options}/IMqttClientCredentials.cs (77%) rename Source/MQTTnet/Client/{ => Options}/IMqttClientOptions.cs (55%) rename Source/MQTTnet/Client/{ => Options}/MqttClientCredentials.cs (82%) rename Source/MQTTnet/Client/{ => Options}/MqttClientOptions.cs (59%) rename Source/MQTTnet/Client/{ => Options}/MqttClientOptionsBuilder.cs (78%) rename Source/MQTTnet/Client/{ => Options}/MqttClientOptionsBuilderTlsParameters.cs (96%) rename Source/MQTTnet/Client/{ => Options}/MqttClientTcpOptions.cs (92%) rename Source/MQTTnet/Client/{ => Options}/MqttClientTcpOptionsExtensions.cs (92%) rename Source/MQTTnet/Client/{ => Options}/MqttClientTlsOptions.cs (95%) rename Source/MQTTnet/Client/{ => Options}/MqttClientWebSocketOptions.cs (95%) rename Source/MQTTnet/Client/{ => Options}/MqttClientWebSocketProxyOptions.cs (90%) create mode 100644 Source/MQTTnet/Client/PacketDispatcher/IMqttPacketAwaiter.cs create mode 100644 Source/MQTTnet/Client/PacketDispatcher/MqttPacketAwaiter.cs rename Source/MQTTnet/Client/{ => PacketDispatcher}/MqttPacketDispatcher.cs (68%) create mode 100644 Source/MQTTnet/Client/Publishing/MqttClientPublishReasonCode.cs create mode 100644 Source/MQTTnet/Client/Publishing/MqttClientPublishResult.cs create mode 100644 Source/MQTTnet/Client/Subscribing/MqttClientSubscribeResult.cs create mode 100644 Source/MQTTnet/Client/Subscribing/MqttClientSubscribeResultCode.cs create mode 100644 Source/MQTTnet/Client/Subscribing/MqttClientSubscribeResultItem.cs create mode 100644 Source/MQTTnet/Client/Unsubscribing/MqttClientUnsubscribeResult.cs create mode 100644 Source/MQTTnet/Client/Unsubscribing/MqttClientUnsubscribeResultCode.cs create mode 100644 Source/MQTTnet/Client/Unsubscribing/MqttClientUnsubscribeResultItem.cs create mode 100644 Source/MQTTnet/Formatter/IMqttDataConverter.cs create mode 100644 Source/MQTTnet/Formatter/V3/MqttV310DataConverter.cs rename Source/MQTTnet/Formatter/{V310 => V3}/MqttV310PacketFormatter.cs (72%) rename Source/MQTTnet/Formatter/{V311 => V3}/MqttV311PacketFormatter.cs (83%) create mode 100644 Source/MQTTnet/Formatter/V5/MqttV500DataConverter.cs create mode 100644 Source/MQTTnet/Formatter/V5/MqttV500PacketDecoder.cs create mode 100644 Source/MQTTnet/Formatter/V5/MqttV500PacketEncoder.cs create mode 100644 Source/MQTTnet/Formatter/V5/MqttV500PacketFormatter.cs create mode 100644 Source/MQTTnet/Formatter/V5/MqttV500PropertiesReader.cs create mode 100644 Source/MQTTnet/Formatter/V5/MqttV500PropertiesWriter.cs delete mode 100644 Source/MQTTnet/Formatter/V500/MqttV500PacketDecoder.cs delete mode 100644 Source/MQTTnet/Formatter/V500/MqttV500PacketEncoder.cs delete mode 100644 Source/MQTTnet/Formatter/V500/MqttV500PacketFormatter.cs delete mode 100644 Source/MQTTnet/Formatter/V500/MqttV500PropertiesWriter.cs delete mode 100644 Source/MQTTnet/Internal/MqttApplicationMessageExtensions.cs create mode 100644 Source/MQTTnet/Packets/MqttAuthPacketProperties.cs create mode 100644 Source/MQTTnet/Packets/MqttDisconnectPacketProperties.cs create mode 100644 Source/MQTTnet/Packets/MqttUnsubAckPacketProperties.cs rename Source/MQTTnet/Packets/{MqttUnsubscribe.cs => MqttUnsubscribePacket.cs} (70%) create mode 100644 Source/MQTTnet/Packets/MqttUnsubscribePacketProperties.cs create mode 100644 Source/MQTTnet/Protocol/MqttDisconnectReasonCode.cs create mode 100644 Source/MQTTnet/Protocol/MqttPayloadFormatIndicator.cs create mode 100644 Source/MQTTnet/Protocol/MqttUnsubscribeReasonCode.cs delete mode 100644 Source/MQTTnet/Server/MqttSubscribeResult.cs rename Tests/MQTTnet.Core.Tests/{MQTTnet.Core.Tests.csproj => MQTTnet.Tests.csproj} (86%) create mode 100644 Tests/MQTTnet.Core.Tests/MQTTv5/MqttClientTests.cs diff --git a/MQTTnet.sln b/MQTTnet.sln index be6a0b6f..309b11fe 100644 --- a/MQTTnet.sln +++ b/MQTTnet.sln @@ -3,7 +3,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 VisualStudioVersion = 15.0.27004.2010 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.Core.Tests", "Tests\MQTTnet.Core.Tests\MQTTnet.Core.Tests.csproj", "{A7FF0C91-25DE-4BA6-B39E-F54E8DADF1CC}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.Tests", "Tests\MQTTnet.Core.Tests\MQTTnet.Tests.csproj", "{A7FF0C91-25DE-4BA6-B39E-F54E8DADF1CC}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{9248C2E1-B9D6-40BF-81EC-86004D7765B4}" EndProject diff --git a/README.md b/README.md index 436628a9..5b5288e8 100644 --- a/README.md +++ b/README.md @@ -23,9 +23,10 @@ MQTTnet is a high performance .NET library for MQTT based communication. It prov * Extensible communication channels (e.g. In-Memory, TCP, TCP+TLS, WS) * Lightweight (only the low level implementation of MQTT, no overhead) * Performance optimized (processing ~70.000 messages / second)* +* Uniform API over all versions of MQTT protocol * Interfaces included for mocking and testing * Access to internal trace messages -* Unit tested (~100 tests) +* Unit tested (~120 tests) \* Tested on local machine (Intel i7 8700K) with MQTTnet client and server running in the same process using the TCP channel. The app for verification is part of this repository and stored in _/Tests/MQTTnet.TestApp.NetCore_. diff --git a/Source/MQTTnet.AspnetCore/Client/MqttClientConnectionContextFactory.cs b/Source/MQTTnet.AspnetCore/Client/MqttClientConnectionContextFactory.cs index 93314f44..a83fcefd 100644 --- a/Source/MQTTnet.AspnetCore/Client/MqttClientConnectionContextFactory.cs +++ b/Source/MQTTnet.AspnetCore/Client/MqttClientConnectionContextFactory.cs @@ -3,6 +3,7 @@ using System.Net; using MQTTnet.Adapter; using MQTTnet.AspNetCore.Client.Tcp; using MQTTnet.Client; +using MQTTnet.Client.Options; using MQTTnet.Diagnostics; using MQTTnet.Formatter; diff --git a/Source/MQTTnet.AspnetCore/ReaderExtensions.cs b/Source/MQTTnet.AspnetCore/ReaderExtensions.cs index abe8ef7b..81d24c0e 100644 --- a/Source/MQTTnet.AspnetCore/ReaderExtensions.cs +++ b/Source/MQTTnet.AspnetCore/ReaderExtensions.cs @@ -36,7 +36,7 @@ namespace MQTTnet.AspNetCore var bodySlice = copy.Slice(0, bodyLength); var buffer = bodySlice.GetArray(); - packet = formatter.Decode(new ReceivedMqttPacket(fixedheader, new MqttPacketBodyReader(buffer, 0, buffer.Length))); + packet = formatter.Decode(new ReceivedMqttPacket(fixedheader, new MqttPacketBodyReader(buffer, 0, buffer.Length), buffer.Length + 2)); consumed = bodySlice.End; observed = bodySlice.End; return true; diff --git a/Source/MQTTnet.Extensions.ManagedClient/IManagedMqttClient.cs b/Source/MQTTnet.Extensions.ManagedClient/IManagedMqttClient.cs index b3835e3e..12a65603 100644 --- a/Source/MQTTnet.Extensions.ManagedClient/IManagedMqttClient.cs +++ b/Source/MQTTnet.Extensions.ManagedClient/IManagedMqttClient.cs @@ -1,7 +1,8 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; -using MQTTnet.Client; +using MQTTnet.Client.Connecting; +using MQTTnet.Client.Disconnecting; namespace MQTTnet.Extensions.ManagedClient { diff --git a/Source/MQTTnet.Extensions.ManagedClient/IManagedMqttClientOptions.cs b/Source/MQTTnet.Extensions.ManagedClient/IManagedMqttClientOptions.cs index f59aca7c..589f7acc 100644 --- a/Source/MQTTnet.Extensions.ManagedClient/IManagedMqttClientOptions.cs +++ b/Source/MQTTnet.Extensions.ManagedClient/IManagedMqttClientOptions.cs @@ -1,5 +1,6 @@ using System; using MQTTnet.Client; +using MQTTnet.Client.Options; using MQTTnet.Server; namespace MQTTnet.Extensions.ManagedClient diff --git a/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClient.cs b/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClient.cs index 866a75da..e1feb8d3 100644 --- a/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClient.cs +++ b/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClient.cs @@ -4,13 +4,14 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using MQTTnet.Client; +using MQTTnet.Client.Publishing; using MQTTnet.Diagnostics; using MQTTnet.Exceptions; using MQTTnet.Internal; using MQTTnet.Protocol; using MQTTnet.Server; -using MqttClientConnectedEventArgs = MQTTnet.Client.MqttClientConnectedEventArgs; -using MqttClientDisconnectedEventArgs = MQTTnet.Client.MqttClientDisconnectedEventArgs; +using MqttClientConnectedEventArgs = MQTTnet.Client.Connecting.MqttClientConnectedEventArgs; +using MqttClientDisconnectedEventArgs = MQTTnet.Client.Disconnecting.MqttClientDisconnectedEventArgs; namespace MQTTnet.Extensions.ManagedClient { @@ -27,7 +28,7 @@ namespace MQTTnet.Extensions.ManagedClient private CancellationTokenSource _publishingCancellationToken; private ManagedMqttClientStorageManager _storageManager; - + private bool _subscriptionsNotPushed; public ManagedMqttClient(IMqttClient mqttClient, IMqttNetChildLogger logger) @@ -102,11 +103,12 @@ namespace MQTTnet.Extensions.ManagedClient return Task.FromResult(0); } - public Task PublishAsync(MqttApplicationMessage applicationMessage) + public async Task PublishAsync(MqttApplicationMessage applicationMessage) { if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage)); - return PublishAsync(new ManagedMqttApplicationMessageBuilder().WithApplicationMessage(applicationMessage).Build()); + await PublishAsync(new ManagedMqttApplicationMessageBuilder().WithApplicationMessage(applicationMessage).Build()).ConfigureAwait(false); + return new MqttClientPublishResult(); } public async Task PublishAsync(ManagedMqttApplicationMessage applicationMessage) @@ -206,7 +208,15 @@ namespace MQTTnet.Extensions.ManagedClient } finally { - await _mqttClient.DisconnectAsync().ConfigureAwait(false); + try + { + await _mqttClient.DisconnectAsync().ConfigureAwait(false); + } + catch (Exception exception) + { + _logger.Error(exception, "Error while disconnecting."); + } + _logger.Info("Stopped"); } } @@ -345,7 +355,7 @@ namespace MQTTnet.Extensions.ManagedClient lock (_subscriptions) { - subscriptions = _subscriptions.Select(i => new TopicFilter(i.Key, i.Value)).ToList(); + subscriptions = _subscriptions.Select(i => new TopicFilter { Topic = i.Key, QualityOfServiceLevel = i.Value }).ToList(); unsubscriptions = new HashSet(_unsubscriptions); _unsubscriptions.Clear(); diff --git a/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClientOptions.cs b/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClientOptions.cs index cba801bb..537e1bb9 100644 --- a/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClientOptions.cs +++ b/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClientOptions.cs @@ -1,5 +1,6 @@ using System; using MQTTnet.Client; +using MQTTnet.Client.Options; using MQTTnet.Server; namespace MQTTnet.Extensions.ManagedClient diff --git a/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClientOptionsBuilder.cs b/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClientOptionsBuilder.cs index 3ea5b050..f58453ce 100644 --- a/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClientOptionsBuilder.cs +++ b/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClientOptionsBuilder.cs @@ -1,5 +1,6 @@ using System; using MQTTnet.Client; +using MQTTnet.Client.Options; using MQTTnet.Server; namespace MQTTnet.Extensions.ManagedClient diff --git a/Source/MQTTnet/Adapter/IMqttChannelAdapter.cs b/Source/MQTTnet/Adapter/IMqttChannelAdapter.cs index 183badb4..6310664b 100644 --- a/Source/MQTTnet/Adapter/IMqttChannelAdapter.cs +++ b/Source/MQTTnet/Adapter/IMqttChannelAdapter.cs @@ -11,7 +11,7 @@ namespace MQTTnet.Adapter string Endpoint { get; } MqttPacketFormatterAdapter PacketFormatterAdapter { get; } - + event EventHandler ReadingPacketStarted; event EventHandler ReadingPacketCompleted; diff --git a/Source/MQTTnet/Client/IMqttClientAdapterFactory.cs b/Source/MQTTnet/Adapter/IMqttClientAdapterFactory.cs similarity index 76% rename from Source/MQTTnet/Client/IMqttClientAdapterFactory.cs rename to Source/MQTTnet/Adapter/IMqttClientAdapterFactory.cs index 197858b8..cb0988d8 100644 --- a/Source/MQTTnet/Client/IMqttClientAdapterFactory.cs +++ b/Source/MQTTnet/Adapter/IMqttClientAdapterFactory.cs @@ -1,7 +1,7 @@ -using MQTTnet.Adapter; +using MQTTnet.Client.Options; using MQTTnet.Diagnostics; -namespace MQTTnet.Client +namespace MQTTnet.Adapter { public interface IMqttClientAdapterFactory { diff --git a/Source/MQTTnet/Adapter/MqttChannelAdapter.cs b/Source/MQTTnet/Adapter/MqttChannelAdapter.cs index 340a528b..44ef5be3 100644 --- a/Source/MQTTnet/Adapter/MqttChannelAdapter.cs +++ b/Source/MQTTnet/Adapter/MqttChannelAdapter.cs @@ -16,7 +16,7 @@ namespace MQTTnet.Adapter public class MqttChannelAdapter : IMqttChannelAdapter { private const uint ErrorOperationAborted = 0x800703E3; - private const uint ReadBufferSize = 4096; // TODO: Move buffer size to config + private const int ReadBufferSize = 4096; // TODO: Move buffer size to config private readonly SemaphoreSlim _writerSemaphore = new SemaphoreSlim(1, 1); @@ -102,7 +102,7 @@ namespace MQTTnet.Adapter await _channel.WriteAsync(packetData.Array, packetData.Offset, packetData.Count, cancellationToken).ConfigureAwait(false); PacketFormatterAdapter.FreeBuffer(); - _logger.Verbose("TX >>> {0}", packet); + _logger.Verbose("TX ({0} bytes) >>> {1}", packetData.Count, packet); } catch (Exception exception) { @@ -152,7 +152,7 @@ namespace MQTTnet.Adapter throw new MqttProtocolViolationException("Received malformed packet."); } - _logger.Verbose("RX <<< {0}", packet); + _logger.Verbose("RX ({0} bytes) <<< {1}", receivedMqttPacket.TotalLength, packet); return packet; } @@ -182,7 +182,7 @@ namespace MQTTnet.Adapter if (fixedHeader.RemainingLength == 0) { - return new ReceivedMqttPacket(fixedHeader.Flags, null); + return new ReceivedMqttPacket(fixedHeader.Flags, null, 2); } var body = new byte[fixedHeader.RemainingLength]; @@ -194,15 +194,15 @@ namespace MQTTnet.Adapter var bytesLeft = body.Length - bodyOffset; if (chunkSize > bytesLeft) { - chunkSize = (uint)bytesLeft; + chunkSize = bytesLeft; } #if WINDOWS_UWP var readBytes = await _channel.ReadAsync(body, bodyOffset, (int)chunkSize, cancellationToken).ConfigureAwait(false); #else - // async/await is not used to avoid the overhead of context switches. We assume that the reamining data + // async/await is not used to avoid the overhead of context switches. We assume that the remaining data // has been sent from the sender directly after the initial bytes. - var readBytes = _channel.ReadAsync(body, bodyOffset, (int)chunkSize, cancellationToken).ConfigureAwait(false).GetAwaiter().GetResult(); + var readBytes = _channel.ReadAsync(body, bodyOffset, chunkSize, cancellationToken).ConfigureAwait(false).GetAwaiter().GetResult(); #endif cancellationToken.ThrowIfCancellationRequested(); @@ -211,7 +211,8 @@ namespace MQTTnet.Adapter bodyOffset += readBytes; } while (bodyOffset < body.Length); - return new ReceivedMqttPacket(fixedHeader.Flags, new MqttPacketBodyReader(body, 0, body.Length)); + var bodyReader = new MqttPacketBodyReader(body, 0, body.Length); + return new ReceivedMqttPacket(fixedHeader.Flags, bodyReader, fixedHeader.TotalLength); } finally { diff --git a/Source/MQTTnet/Adapter/MqttConnectingFailedException.cs b/Source/MQTTnet/Adapter/MqttConnectingFailedException.cs index 68445cfd..44d50ec1 100644 --- a/Source/MQTTnet/Adapter/MqttConnectingFailedException.cs +++ b/Source/MQTTnet/Adapter/MqttConnectingFailedException.cs @@ -1,16 +1,16 @@ -using MQTTnet.Exceptions; -using MQTTnet.Protocol; +using MQTTnet.Client.Connecting; +using MQTTnet.Exceptions; namespace MQTTnet.Adapter { public class MqttConnectingFailedException : MqttCommunicationException { - public MqttConnectingFailedException(MqttConnectReturnCode returnCode) - : base($"Connecting with MQTT server failed ({returnCode}).") + public MqttConnectingFailedException(MqttClientConnectResultCode resultCode) + : base($"Connecting with MQTT server failed ({resultCode.ToString()}).") { - ReturnCode = returnCode; + ResultCode = resultCode; } - public MqttConnectReturnCode ReturnCode { get; } + public MqttClientConnectResultCode ResultCode { get; } } } diff --git a/Source/MQTTnet/Adapter/ReceivedMqttPacket.cs b/Source/MQTTnet/Adapter/ReceivedMqttPacket.cs index 007b9eb6..683d6b64 100644 --- a/Source/MQTTnet/Adapter/ReceivedMqttPacket.cs +++ b/Source/MQTTnet/Adapter/ReceivedMqttPacket.cs @@ -4,14 +4,17 @@ namespace MQTTnet.Adapter { public class ReceivedMqttPacket { - public ReceivedMqttPacket(byte fixedHeader, MqttPacketBodyReader body) + public ReceivedMqttPacket(byte fixedHeader, MqttPacketBodyReader body, int totalLength) { FixedHeader = fixedHeader; Body = body; + TotalLength = totalLength; } public byte FixedHeader { get; } public MqttPacketBodyReader Body { get; } + + public int TotalLength { get; } } } diff --git a/Source/MQTTnet/ApplicationMessagePublisherExtensions.cs b/Source/MQTTnet/ApplicationMessagePublisherExtensions.cs index 81dddbf1..ea0b83b2 100644 --- a/Source/MQTTnet/ApplicationMessagePublisherExtensions.cs +++ b/Source/MQTTnet/ApplicationMessagePublisherExtensions.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; +using MQTTnet.Client.Publishing; using MQTTnet.Protocol; namespace MQTTnet @@ -29,7 +30,7 @@ namespace MQTTnet } } - public static Task PublishAsync(this IApplicationMessagePublisher publisher, string topic) + public static Task PublishAsync(this IApplicationMessagePublisher publisher, string topic) { if (publisher == null) throw new ArgumentNullException(nameof(publisher)); if (topic == null) throw new ArgumentNullException(nameof(topic)); @@ -38,7 +39,7 @@ namespace MQTTnet .WithTopic(topic)); } - public static Task PublishAsync(this IApplicationMessagePublisher publisher, string topic, string payload) + public static Task PublishAsync(this IApplicationMessagePublisher publisher, string topic, string payload) { if (publisher == null) throw new ArgumentNullException(nameof(publisher)); if (topic == null) throw new ArgumentNullException(nameof(topic)); @@ -48,7 +49,7 @@ namespace MQTTnet .WithPayload(payload)); } - public static Task PublishAsync(this IApplicationMessagePublisher publisher, string topic, string payload, MqttQualityOfServiceLevel qualityOfServiceLevel) + public static Task PublishAsync(this IApplicationMessagePublisher publisher, string topic, string payload, MqttQualityOfServiceLevel qualityOfServiceLevel) { if (publisher == null) throw new ArgumentNullException(nameof(publisher)); if (topic == null) throw new ArgumentNullException(nameof(topic)); @@ -59,7 +60,7 @@ namespace MQTTnet .WithQualityOfServiceLevel(qualityOfServiceLevel)); } - public static Task PublishAsync(this IApplicationMessagePublisher publisher, string topic, string payload, MqttQualityOfServiceLevel qualityOfServiceLevel, bool retain) + public static Task PublishAsync(this IApplicationMessagePublisher publisher, string topic, string payload, MqttQualityOfServiceLevel qualityOfServiceLevel, bool retain) { if (publisher == null) throw new ArgumentNullException(nameof(publisher)); if (topic == null) throw new ArgumentNullException(nameof(topic)); @@ -71,7 +72,7 @@ namespace MQTTnet .WithRetainFlag(retain)); } - public static Task PublishAsync(this IApplicationMessagePublisher publisher, Func builder) + public static Task PublishAsync(this IApplicationMessagePublisher publisher, Func builder) { var message = builder(new MqttApplicationMessageBuilder()).Build(); return publisher.PublishAsync(message); diff --git a/Source/MQTTnet/Client/Connecting/MqttClientConnectResult.cs b/Source/MQTTnet/Client/Connecting/MqttClientConnectResult.cs new file mode 100644 index 00000000..05f53f0d --- /dev/null +++ b/Source/MQTTnet/Client/Connecting/MqttClientConnectResult.cs @@ -0,0 +1,9 @@ +namespace MQTTnet.Client.Connecting +{ + public class MqttClientConnectResult + { + public bool IsSessionPresent { get; set; } + + public MqttClientConnectResultCode ResultCode { get; set; } + } +} diff --git a/Source/MQTTnet/Client/Connecting/MqttClientConnectResultCode.cs b/Source/MQTTnet/Client/Connecting/MqttClientConnectResultCode.cs new file mode 100644 index 00000000..a0dbae7b --- /dev/null +++ b/Source/MQTTnet/Client/Connecting/MqttClientConnectResultCode.cs @@ -0,0 +1,28 @@ +namespace MQTTnet.Client.Connecting +{ + public enum MqttClientConnectResultCode + { + Success = 0, + UnspecifiedError = 128, + MalformedPacket = 129, + ProtocolError = 130, + ImplementationSpecificError = 131, + UnsupportedProtocolVersion = 132, + ClientIdentifierNotValid = 133, + BadUserNameOrPassword = 134, + NotAuthorized = 135, + ServerUnavailable = 136, + ServerBusy = 137, + Banned = 138, + BadAuthenticationMethod = 140, + TopicNameInvalid = 144, + PacketTooLarge = 149, + QuotaExceeded = 151, + PayloadFormatInvalid = 153, + RetainNotSupported = 154, + QoSNotSupported = 155, + UseAnotherServer = 156, + ServerMoved = 157, + ConnectionRateExceeded = 159 + } +} diff --git a/Source/MQTTnet/Client/Connecting/MqttClientConnectedEventArgs.cs b/Source/MQTTnet/Client/Connecting/MqttClientConnectedEventArgs.cs new file mode 100644 index 00000000..c052986a --- /dev/null +++ b/Source/MQTTnet/Client/Connecting/MqttClientConnectedEventArgs.cs @@ -0,0 +1,14 @@ +using System; + +namespace MQTTnet.Client.Connecting +{ + public class MqttClientConnectedEventArgs : EventArgs + { + public MqttClientConnectedEventArgs(MqttClientConnectResult connectResult) + { + ConnectResult = connectResult ?? throw new ArgumentNullException(nameof(connectResult)); + } + + public MqttClientConnectResult ConnectResult { get; } + } +} diff --git a/Source/MQTTnet/Client/Disconnecting/MqttClientDisconnectOptions.cs b/Source/MQTTnet/Client/Disconnecting/MqttClientDisconnectOptions.cs new file mode 100644 index 00000000..995806e7 --- /dev/null +++ b/Source/MQTTnet/Client/Disconnecting/MqttClientDisconnectOptions.cs @@ -0,0 +1,9 @@ +namespace MQTTnet.Client.Disconnecting +{ + public class MqttClientDisconnectOptions + { + public MqttClientDisconnectReason ReasonCode { get; set; } = MqttClientDisconnectReason.NormalDisconnection; + + public string ReasonString { get; set; } + } +} diff --git a/Source/MQTTnet/Client/Disconnecting/MqttClientDisconnectReason.cs b/Source/MQTTnet/Client/Disconnecting/MqttClientDisconnectReason.cs new file mode 100644 index 00000000..3e1bb661 --- /dev/null +++ b/Source/MQTTnet/Client/Disconnecting/MqttClientDisconnectReason.cs @@ -0,0 +1,8 @@ +namespace MQTTnet.Client.Disconnecting +{ + public enum MqttClientDisconnectReason + { + NormalDisconnection = 0, + + } +} diff --git a/Source/MQTTnet/Client/MqttClientDisconnectedEventArgs.cs b/Source/MQTTnet/Client/Disconnecting/MqttClientDisconnectedEventArgs.cs similarity index 90% rename from Source/MQTTnet/Client/MqttClientDisconnectedEventArgs.cs rename to Source/MQTTnet/Client/Disconnecting/MqttClientDisconnectedEventArgs.cs index 2161cc26..7506042c 100644 --- a/Source/MQTTnet/Client/MqttClientDisconnectedEventArgs.cs +++ b/Source/MQTTnet/Client/Disconnecting/MqttClientDisconnectedEventArgs.cs @@ -1,6 +1,6 @@ using System; -namespace MQTTnet.Client +namespace MQTTnet.Client.Disconnecting { public class MqttClientDisconnectedEventArgs : EventArgs { diff --git a/Source/MQTTnet/Client/IMqttClient.cs b/Source/MQTTnet/Client/IMqttClient.cs index b60a31a8..4e9cbc1b 100644 --- a/Source/MQTTnet/Client/IMqttClient.cs +++ b/Source/MQTTnet/Client/IMqttClient.cs @@ -1,6 +1,11 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; +using MQTTnet.Client.Connecting; +using MQTTnet.Client.Disconnecting; +using MQTTnet.Client.Options; +using MQTTnet.Client.Subscribing; +using MQTTnet.Client.Unsubscribing; namespace MQTTnet.Client { @@ -13,9 +18,9 @@ namespace MQTTnet.Client event EventHandler Disconnected; Task ConnectAsync(IMqttClientOptions options); - Task DisconnectAsync(); + Task DisconnectAsync(MqttClientDisconnectOptions options); - Task> SubscribeAsync(IEnumerable topicFilters); - Task UnsubscribeAsync(IEnumerable topics); + Task SubscribeAsync(IEnumerable topicFilters); + Task UnsubscribeAsync(IEnumerable topics); } } \ No newline at end of file diff --git a/Source/MQTTnet/Client/IMqttClientFactory.cs b/Source/MQTTnet/Client/IMqttClientFactory.cs index cd687afc..95d567ea 100644 --- a/Source/MQTTnet/Client/IMqttClientFactory.cs +++ b/Source/MQTTnet/Client/IMqttClientFactory.cs @@ -1,4 +1,5 @@ -using MQTTnet.Diagnostics; +using MQTTnet.Adapter; +using MQTTnet.Diagnostics; namespace MQTTnet.Client { diff --git a/Source/MQTTnet/Client/MqttClient.cs b/Source/MQTTnet/Client/MqttClient.cs index 9b0fcc5f..fd4b8203 100644 --- a/Source/MQTTnet/Client/MqttClient.cs +++ b/Source/MQTTnet/Client/MqttClient.cs @@ -2,14 +2,19 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; -using System.Net; using System.Threading; using System.Threading.Tasks; using MQTTnet.Adapter; +using MQTTnet.Client.Connecting; +using MQTTnet.Client.Disconnecting; +using MQTTnet.Client.Options; +using MQTTnet.Client.PacketDispatcher; +using MQTTnet.Client.Publishing; +using MQTTnet.Client.Subscribing; +using MQTTnet.Client.Unsubscribing; using MQTTnet.Diagnostics; using MQTTnet.Exceptions; using MQTTnet.Formatter; -using MQTTnet.Internal; using MQTTnet.Packets; using MQTTnet.Protocol; @@ -71,7 +76,7 @@ namespace MQTTnet.Client StartReceivingPackets(_cancellationTokenSource.Token); - var connectResponse = await AuthenticateAsync(options.WillMessage, _cancellationTokenSource.Token).ConfigureAwait(false); + var connectResult = await AuthenticateAsync(options.WillMessage, _cancellationTokenSource.Token).ConfigureAwait(false); _logger.Verbose("MQTT connection with server established."); _sendTracker.Restart(); @@ -82,10 +87,11 @@ namespace MQTTnet.Client } IsConnected = true; - Connected?.Invoke(this, new MqttClientConnectedEventArgs(connectResponse.IsSessionPresent)); + Connected?.Invoke(this, new MqttClientConnectedEventArgs(connectResult)); _logger.Info("Connected."); - return new MqttClientConnectResult(connectResponse.IsSessionPresent); + + return connectResult; } catch (Exception exception) { @@ -100,7 +106,7 @@ namespace MQTTnet.Client } } - public async Task DisconnectAsync() + public async Task DisconnectAsync(MqttClientDisconnectOptions options) { try { @@ -108,7 +114,8 @@ namespace MQTTnet.Client if (IsConnected && !_cancellationTokenSource.IsCancellationRequested) { - await SendAsync(new MqttDisconnectPacket(), _cancellationTokenSource.Token).ConfigureAwait(false); + var disconnectPacket = CreateDisconnectPacket(options); + await SendAsync(disconnectPacket, _cancellationTokenSource.Token).ConfigureAwait(false); } } finally @@ -120,7 +127,7 @@ namespace MQTTnet.Client } } - public async Task> SubscribeAsync(IEnumerable topicFilters) + public async Task SubscribeAsync(IEnumerable topicFilters) { if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters)); @@ -128,21 +135,16 @@ namespace MQTTnet.Client var subscribePacket = new MqttSubscribePacket { - PacketIdentifier = _packetIdentifierProvider.GetNewPacketIdentifier(), - TopicFilters = topicFilters.ToList() + PacketIdentifier = _packetIdentifierProvider.GetNewPacketIdentifier() }; - var response = await SendAndReceiveAsync(subscribePacket, _cancellationTokenSource.Token).ConfigureAwait(false); + subscribePacket.TopicFilters.AddRange(topicFilters); - if (response.SubscribeReturnCodes.Count != subscribePacket.TopicFilters.Count) - { - throw new MqttProtocolViolationException("The return codes are not matching the topic filters [MQTT-3.9.3-1]."); - } - - return subscribePacket.TopicFilters.Select((t, i) => new MqttSubscribeResult(t, response.SubscribeReturnCodes[i])).ToList(); + var subAckPacket = await SendAndReceiveAsync(subscribePacket, _cancellationTokenSource.Token).ConfigureAwait(false); + return _adapter.PacketFormatterAdapter.DataConverter.CreateClientSubscribeResult(subscribePacket, subAckPacket); } - public Task UnsubscribeAsync(IEnumerable topicFilters) + public async Task UnsubscribeAsync(IEnumerable topicFilters) { if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters)); @@ -150,36 +152,47 @@ namespace MQTTnet.Client var unsubscribePacket = new MqttUnsubscribePacket { - PacketIdentifier = _packetIdentifierProvider.GetNewPacketIdentifier(), - TopicFilters = topicFilters.ToList() + PacketIdentifier = _packetIdentifierProvider.GetNewPacketIdentifier() }; - return SendAndReceiveAsync(unsubscribePacket, _cancellationTokenSource.Token); + unsubscribePacket.TopicFilters.AddRange(topicFilters); + + var unsubAckPacket = await SendAndReceiveAsync(unsubscribePacket, _cancellationTokenSource.Token).ConfigureAwait(false); + return _adapter.PacketFormatterAdapter.DataConverter.CreateClientUnsubscribeResult(unsubscribePacket, unsubAckPacket); } - public Task PublishAsync(MqttApplicationMessage applicationMessage) + public async Task PublishAsync(MqttApplicationMessage applicationMessage) { if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage)); ThrowIfNotConnected(); - var publishPacket = _adapter.PacketFormatterAdapter.ConvertApplicationMessageToPublishPacket(applicationMessage); + var publishPacket = _adapter.PacketFormatterAdapter.DataConverter.CreatePublishPacket(applicationMessage); switch (applicationMessage.QualityOfServiceLevel) { case MqttQualityOfServiceLevel.AtMostOnce: { // No packet identifier is used for QoS 0 [3.3.2.2 Packet Identifier] - return SendAsync(publishPacket, _cancellationTokenSource.Token); + await SendAsync(publishPacket, _cancellationTokenSource.Token).ConfigureAwait(false); + return new MqttClientPublishResult(); } case MqttQualityOfServiceLevel.AtLeastOnce: { publishPacket.PacketIdentifier = _packetIdentifierProvider.GetNewPacketIdentifier(); - return SendAndReceiveAsync(publishPacket, _cancellationTokenSource.Token); + var response = await SendAndReceiveAsync(publishPacket, _cancellationTokenSource.Token).ConfigureAwait(false); + + var result = new MqttClientPublishResult(); + if (response.ReasonCode != null) + { + result.ReasonCode = (MqttClientPublishReasonCode)response.ReasonCode; + } + + return result; } case MqttQualityOfServiceLevel.ExactlyOnce: { - return PublishExactlyOnce(publishPacket, _cancellationTokenSource.Token); + return await PublishExactlyOnceAsync(publishPacket, _cancellationTokenSource.Token).ConfigureAwait(false); } default: { @@ -198,28 +211,22 @@ namespace MQTTnet.Client _adapter = null; } - private async Task AuthenticateAsync(MqttApplicationMessage willApplicationMessage, CancellationToken cancellationToken) + private async Task AuthenticateAsync(MqttApplicationMessage willApplicationMessage, CancellationToken cancellationToken) { - var connectPacket = new MqttConnectPacket - { - ClientId = Options.ClientId, - Username = Options.Credentials?.Username, - Password = Options.Credentials?.Password, - CleanSession = Options.CleanSession, - KeepAlivePeriod = (ushort)Options.KeepAlivePeriod.TotalSeconds, - WillMessage = willApplicationMessage - }; + var connectPacket = _adapter.PacketFormatterAdapter.DataConverter.CreateConnectPacket( + willApplicationMessage, + Options); - //connectPacket.RequestProblemInformationProperty = true; - //connectPacket.RequestResponseInformationProperty = true; + var connAckPacket = await SendAndReceiveAsync(connectPacket, cancellationToken).ConfigureAwait(false); - var response = await SendAndReceiveAsync(connectPacket, cancellationToken).ConfigureAwait(false); - if (response.ConnectReturnCode != MqttConnectReturnCode.ConnectionAccepted) + var result = _adapter.PacketFormatterAdapter.DataConverter.CreateClientConnectResult(connAckPacket); + + if (result.ResultCode != MqttClientConnectResultCode.Success) { - throw new MqttConnectingFailedException(response.ConnectReturnCode); + throw new MqttConnectingFailedException(result.ResultCode); } - return response; + return result; } private void ThrowIfNotConnected() @@ -237,7 +244,7 @@ namespace MQTTnet.Client var clientWasConnected = IsConnected; InitiateDisconnect(); - + IsConnected = false; try @@ -249,7 +256,7 @@ namespace MQTTnet.Client await WaitForTaskAsync(_packetReceiverTask, sender).ConfigureAwait(false); await WaitForTaskAsync(_keepAliveMessageSenderTask, sender).ConfigureAwait(false); - + _logger.Verbose("Disconnected from adapter."); } catch (Exception adapterException) @@ -311,9 +318,9 @@ namespace MQTTnet.Client try { await _adapter.SendPacketAsync(requestPacket, cancellationToken).ConfigureAwait(false); - var respone = await Internal.TaskExtensions.TimeoutAfterAsync(ct => packetAwaiter.Task, Options.CommunicationTimeout, cancellationToken).ConfigureAwait(false); + return await packetAwaiter.WaitOneAsync(Options.CommunicationTimeout); - return (TResponsePacket)respone; + //return (TResponsePacket)await Internal.TaskExtensions.TimeoutAfterAsync(ct => packetAwaiter.Task, Options.CommunicationTimeout, cancellationToken).ConfigureAwait(false); } catch (MqttCommunicationTimedOutException) { @@ -388,8 +395,7 @@ namespace MQTTnet.Client while (!cancellationToken.IsCancellationRequested) { - var packet = await _adapter.ReceivePacketAsync(TimeSpan.Zero, cancellationToken) - .ConfigureAwait(false); + var packet = await _adapter.ReceivePacketAsync(TimeSpan.Zero, cancellationToken).ConfigureAwait(false); if (packet != null && !cancellationToken.IsCancellationRequested) { @@ -443,7 +449,7 @@ namespace MQTTnet.Client if (packet is MqttDisconnectPacket) { - return DisconnectAsync(); + return DisconnectAsync(null); } if (packet is MqttPubRelPacket pubRelPacket) @@ -466,14 +472,14 @@ namespace MQTTnet.Client if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtLeastOnce) { FireApplicationMessageReceivedEvent(publishPacket); - return SendAsync(new MqttPubAckPacket { PacketIdentifier = publishPacket.PacketIdentifier }, cancellationToken); + return SendAsync(new MqttPubAckPacket { PacketIdentifier = publishPacket.PacketIdentifier, ReasonCode = MqttPubAckReasonCode.Success }, cancellationToken); } if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.ExactlyOnce) { // QoS 2 is implement as method "B" (4.3.3 QoS 2: Exactly once delivery) FireApplicationMessageReceivedEvent(publishPacket); - return SendAsync(new MqttPubRecPacket { PacketIdentifier = publishPacket.PacketIdentifier }, cancellationToken); + return SendAsync(new MqttPubRecPacket { PacketIdentifier = publishPacket.PacketIdentifier, ReasonCode = MqttPubRecReasonCode.Success }, cancellationToken); } throw new MqttCommunicationException("Received a not supported QoS level."); @@ -483,23 +489,33 @@ namespace MQTTnet.Client { var response = new MqttPubCompPacket { - PacketIdentifier = pubRelPacket.PacketIdentifier + PacketIdentifier = pubRelPacket.PacketIdentifier, + ReasonCode = MqttPubCompReasonCode.Success }; return SendAsync(response, cancellationToken); } - private async Task PublishExactlyOnce(MqttPublishPacket publishPacket, CancellationToken cancellationToken) + private async Task PublishExactlyOnceAsync(MqttPublishPacket publishPacket, CancellationToken cancellationToken) { publishPacket.PacketIdentifier = _packetIdentifierProvider.GetNewPacketIdentifier(); var pubRecPacket = await SendAndReceiveAsync(publishPacket, cancellationToken).ConfigureAwait(false); var pubRelPacket = new MqttPubRelPacket { - PacketIdentifier = pubRecPacket.PacketIdentifier + PacketIdentifier = pubRecPacket.PacketIdentifier, + ReasonCode = MqttPubRelReasonCode.Success }; - await SendAndReceiveAsync(pubRelPacket, cancellationToken).ConfigureAwait(false); + var pubCompPacket = await SendAndReceiveAsync(pubRelPacket, cancellationToken).ConfigureAwait(false); + var result = new MqttClientPublishResult(); + + if (pubRecPacket.ReasonCode != null) + { + result.ReasonCode = (MqttClientPublishReasonCode)pubRecPacket.ReasonCode; + } + + return result; } private void StartReceivingPackets(CancellationToken cancellationToken) @@ -507,7 +523,7 @@ namespace MQTTnet.Client _packetReceiverTask = Task.Factory.StartNew( () => ReceivePacketsAsync(cancellationToken), cancellationToken, - TaskCreationOptions.LongRunning, + TaskCreationOptions.LongRunning, TaskScheduler.Default).Unwrap(); } @@ -524,8 +540,7 @@ namespace MQTTnet.Client { try { - // TODO: Move conversion to formatter. - var applicationMessage = publishPacket.ToApplicationMessage(); + var applicationMessage = _adapter.PacketFormatterAdapter.DataConverter.CreateApplicationMessage(publishPacket); ApplicationMessageReceived?.Invoke(this, new MqttApplicationMessageReceivedEventArgs(Options.ClientId, applicationMessage)); } catch (Exception exception) @@ -559,5 +574,31 @@ namespace MQTTnet.Client { return Interlocked.CompareExchange(ref _disconnectGate, 1, 0) != 0; } + + private MqttDisconnectPacket CreateDisconnectPacket(MqttClientDisconnectOptions options) + { + var packet = new MqttDisconnectPacket(); + + if (_adapter.PacketFormatterAdapter.ProtocolVersion == MqttProtocolVersion.V500) + { + if (options == null) + { + packet.ReasonCode = MqttDisconnectReasonCode.NormalDisconnection; + } + else + { + packet.ReasonCode = (MqttDisconnectReasonCode)options.ReasonCode; + } + } + else + { + if (options != null) + { + throw new MqttProtocolViolationException("Reason codes for disconnect are only supported for MQTTv5."); + } + } + + return packet; + } } } diff --git a/Source/MQTTnet/Client/MqttClientConnectResult.cs b/Source/MQTTnet/Client/MqttClientConnectResult.cs deleted file mode 100644 index 6044bf50..00000000 --- a/Source/MQTTnet/Client/MqttClientConnectResult.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace MQTTnet.Client -{ - public class MqttClientConnectResult - { - public MqttClientConnectResult(bool isSessionPresent) - { - IsSessionPresent = isSessionPresent; - } - - public bool IsSessionPresent { get; } - } -} diff --git a/Source/MQTTnet/Client/MqttClientConnectedEventArgs.cs b/Source/MQTTnet/Client/MqttClientConnectedEventArgs.cs deleted file mode 100644 index c8c6ce56..00000000 --- a/Source/MQTTnet/Client/MqttClientConnectedEventArgs.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; - -namespace MQTTnet.Client -{ - public class MqttClientConnectedEventArgs : EventArgs - { - public MqttClientConnectedEventArgs(bool isSessionPresent) - { - IsSessionPresent = isSessionPresent; - } - - public bool IsSessionPresent { get; } - } -} diff --git a/Source/MQTTnet/Client/MqttClientExtensions.cs b/Source/MQTTnet/Client/MqttClientExtensions.cs index a7548dee..00482eaa 100644 --- a/Source/MQTTnet/Client/MqttClientExtensions.cs +++ b/Source/MQTTnet/Client/MqttClientExtensions.cs @@ -1,14 +1,22 @@ using System; -using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using MQTTnet.Client.Subscribing; +using MQTTnet.Client.Unsubscribing; using MQTTnet.Protocol; namespace MQTTnet.Client { public static class MqttClientExtensions { - public static Task> SubscribeAsync(this IMqttClient client, params TopicFilter[] topicFilters) + public static Task DisconnectAsync(this IMqttClient client) + { + if (client == null) throw new ArgumentNullException(nameof(client)); + + return client.DisconnectAsync(null); + } + + public static Task SubscribeAsync(this IMqttClient client, params TopicFilter[] topicFilters) { if (client == null) throw new ArgumentNullException(nameof(client)); if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters)); @@ -16,7 +24,7 @@ namespace MQTTnet.Client return client.SubscribeAsync(topicFilters.ToList()); } - public static Task> SubscribeAsync(this IMqttClient client, string topic, MqttQualityOfServiceLevel qualityOfServiceLevel) + public static Task SubscribeAsync(this IMqttClient client, string topic, MqttQualityOfServiceLevel qualityOfServiceLevel) { if (client == null) throw new ArgumentNullException(nameof(client)); if (topic == null) throw new ArgumentNullException(nameof(topic)); @@ -24,7 +32,7 @@ namespace MQTTnet.Client return client.SubscribeAsync(new TopicFilterBuilder().WithTopic(topic).WithQualityOfServiceLevel(qualityOfServiceLevel).Build()); } - public static Task> SubscribeAsync(this IMqttClient client, string topic) + public static Task SubscribeAsync(this IMqttClient client, string topic) { if (client == null) throw new ArgumentNullException(nameof(client)); if (topic == null) throw new ArgumentNullException(nameof(topic)); @@ -32,7 +40,7 @@ namespace MQTTnet.Client return client.SubscribeAsync(new TopicFilterBuilder().WithTopic(topic).Build()); } - public static Task UnsubscribeAsync(this IMqttClient client, params string[] topicFilters) + public static Task UnsubscribeAsync(this IMqttClient client, params string[] topicFilters) { if (client == null) throw new ArgumentNullException(nameof(client)); if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters)); diff --git a/Source/MQTTnet/Client/MqttSubscribeResult.cs b/Source/MQTTnet/Client/MqttSubscribeResult.cs deleted file mode 100644 index 2b7c46da..00000000 --- a/Source/MQTTnet/Client/MqttSubscribeResult.cs +++ /dev/null @@ -1,17 +0,0 @@ -using MQTTnet.Protocol; - -namespace MQTTnet.Client -{ - public class MqttSubscribeResult - { - public MqttSubscribeResult(TopicFilter topicFilter, MqttSubscribeReturnCode returnCode) - { - TopicFilter = topicFilter; - ReturnCode = returnCode; - } - - public TopicFilter TopicFilter { get; } - - public MqttSubscribeReturnCode ReturnCode { get; } - } -} diff --git a/Source/MQTTnet/Client/IMqttClientChannelOptions.cs b/Source/MQTTnet/Client/Options/IMqttClientChannelOptions.cs similarity index 75% rename from Source/MQTTnet/Client/IMqttClientChannelOptions.cs rename to Source/MQTTnet/Client/Options/IMqttClientChannelOptions.cs index 22249632..729d12f0 100644 --- a/Source/MQTTnet/Client/IMqttClientChannelOptions.cs +++ b/Source/MQTTnet/Client/Options/IMqttClientChannelOptions.cs @@ -1,4 +1,4 @@ -namespace MQTTnet.Client +namespace MQTTnet.Client.Options { public interface IMqttClientChannelOptions { diff --git a/Source/MQTTnet/Client/IMqttClientCredentials.cs b/Source/MQTTnet/Client/Options/IMqttClientCredentials.cs similarity index 77% rename from Source/MQTTnet/Client/IMqttClientCredentials.cs rename to Source/MQTTnet/Client/Options/IMqttClientCredentials.cs index d214d64f..ec188673 100644 --- a/Source/MQTTnet/Client/IMqttClientCredentials.cs +++ b/Source/MQTTnet/Client/Options/IMqttClientCredentials.cs @@ -1,4 +1,4 @@ -namespace MQTTnet.Client +namespace MQTTnet.Client.Options { public interface IMqttClientCredentials { diff --git a/Source/MQTTnet/Client/IMqttClientOptions.cs b/Source/MQTTnet/Client/Options/IMqttClientOptions.cs similarity index 55% rename from Source/MQTTnet/Client/IMqttClientOptions.cs rename to Source/MQTTnet/Client/Options/IMqttClientOptions.cs index 8825a64e..3150baa3 100644 --- a/Source/MQTTnet/Client/IMqttClientOptions.cs +++ b/Source/MQTTnet/Client/Options/IMqttClientOptions.cs @@ -1,7 +1,7 @@ using System; using MQTTnet.Formatter; -namespace MQTTnet.Client +namespace MQTTnet.Client.Options { public interface IMqttClientOptions { @@ -16,5 +16,15 @@ namespace MQTTnet.Client TimeSpan? KeepAliveSendInterval { get; } MqttApplicationMessage WillMessage { get; } + uint? WillDelayInterval { get; } + + string AuthenticationMethod { get; } + byte[] AuthenticationData { get; } + uint? MaximumPacketSize { get; } + ushort? ReceiveMaximum { get; } + bool? RequestProblemInformation { get; } + bool? RequestResponseInformation { get; } + uint? SessionExpiryInterval { get; } + ushort? TopicAliasMaximum { get; } } } \ No newline at end of file diff --git a/Source/MQTTnet/Client/MqttClientCredentials.cs b/Source/MQTTnet/Client/Options/MqttClientCredentials.cs similarity index 82% rename from Source/MQTTnet/Client/MqttClientCredentials.cs rename to Source/MQTTnet/Client/Options/MqttClientCredentials.cs index feaa1e6c..8836e364 100644 --- a/Source/MQTTnet/Client/MqttClientCredentials.cs +++ b/Source/MQTTnet/Client/Options/MqttClientCredentials.cs @@ -1,4 +1,4 @@ -namespace MQTTnet.Client +namespace MQTTnet.Client.Options { public class MqttClientCredentials : IMqttClientCredentials { diff --git a/Source/MQTTnet/Client/MqttClientOptions.cs b/Source/MQTTnet/Client/Options/MqttClientOptions.cs similarity index 59% rename from Source/MQTTnet/Client/MqttClientOptions.cs rename to Source/MQTTnet/Client/Options/MqttClientOptions.cs index 1e5182cb..a76ecf5b 100644 --- a/Source/MQTTnet/Client/MqttClientOptions.cs +++ b/Source/MQTTnet/Client/Options/MqttClientOptions.cs @@ -1,7 +1,7 @@ using System; using MQTTnet.Formatter; -namespace MQTTnet.Client +namespace MQTTnet.Client.Options { public class MqttClientOptions : IMqttClientOptions { @@ -9,12 +9,23 @@ namespace MQTTnet.Client public bool CleanSession { get; set; } = true; public IMqttClientCredentials Credentials { get; set; } = new MqttClientCredentials(); public MqttProtocolVersion ProtocolVersion { get; set; } = MqttProtocolVersion.V311; - public IMqttClientChannelOptions ChannelOptions { get; set; } + public IMqttClientChannelOptions ChannelOptions { get; set; } public TimeSpan CommunicationTimeout { get; set; } = TimeSpan.FromSeconds(10); public TimeSpan KeepAlivePeriod { get; set; } = TimeSpan.FromSeconds(15); public TimeSpan? KeepAliveSendInterval { get; set; } public MqttApplicationMessage WillMessage { get; set; } + public uint? WillDelayInterval { get; set; } + + public string AuthenticationMethod { get; set; } + public byte[] AuthenticationData { get; set; } + + public uint? MaximumPacketSize { get; set; } + public ushort? ReceiveMaximum { get; set; } + public bool? RequestProblemInformation { get; set; } + public bool? RequestResponseInformation { get; set; } + public uint? SessionExpiryInterval { get; set; } + public ushort? TopicAliasMaximum { get; set; } } } diff --git a/Source/MQTTnet/Client/MqttClientOptionsBuilder.cs b/Source/MQTTnet/Client/Options/MqttClientOptionsBuilder.cs similarity index 78% rename from Source/MQTTnet/Client/MqttClientOptionsBuilder.cs rename to Source/MQTTnet/Client/Options/MqttClientOptionsBuilder.cs index 34ecb941..6300f6d2 100644 --- a/Source/MQTTnet/Client/MqttClientOptionsBuilder.cs +++ b/Source/MQTTnet/Client/Options/MqttClientOptionsBuilder.cs @@ -2,7 +2,7 @@ using System.Linq; using MQTTnet.Formatter; -namespace MQTTnet.Client +namespace MQTTnet.Client.Options { public class MqttClientOptionsBuilder { @@ -55,6 +55,55 @@ namespace MQTTnet.Client return this; } + public MqttClientOptionsBuilder WithAuthentication(string method, byte[] data) + { + _options.AuthenticationMethod = method; + _options.AuthenticationData = data; + return this; + } + + public MqttClientOptionsBuilder WithWillDelayInterval(uint? willDelayInterval) + { + _options.WillDelayInterval = willDelayInterval; + return this; + } + + public MqttClientOptionsBuilder WithTopicAliasMaximum(ushort? topicAliasMaximum) + { + _options.TopicAliasMaximum = topicAliasMaximum; + return this; + } + + public MqttClientOptionsBuilder WithMaximumPacketSize(uint? maximumPacketSize) + { + _options.MaximumPacketSize = maximumPacketSize; + 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 WithCredentials(string username, string password = null) { _options.Credentials = new MqttClientCredentials diff --git a/Source/MQTTnet/Client/MqttClientOptionsBuilderTlsParameters.cs b/Source/MQTTnet/Client/Options/MqttClientOptionsBuilderTlsParameters.cs similarity index 96% rename from Source/MQTTnet/Client/MqttClientOptionsBuilderTlsParameters.cs rename to Source/MQTTnet/Client/Options/MqttClientOptionsBuilderTlsParameters.cs index 5764c797..ea36baa9 100644 --- a/Source/MQTTnet/Client/MqttClientOptionsBuilderTlsParameters.cs +++ b/Source/MQTTnet/Client/Options/MqttClientOptionsBuilderTlsParameters.cs @@ -4,7 +4,7 @@ using System.Net.Security; using System.Security.Authentication; using System.Security.Cryptography.X509Certificates; -namespace MQTTnet.Client +namespace MQTTnet.Client.Options { public class MqttClientOptionsBuilderTlsParameters { diff --git a/Source/MQTTnet/Client/MqttClientTcpOptions.cs b/Source/MQTTnet/Client/Options/MqttClientTcpOptions.cs similarity index 92% rename from Source/MQTTnet/Client/MqttClientTcpOptions.cs rename to Source/MQTTnet/Client/Options/MqttClientTcpOptions.cs index 4569be33..35af34fe 100644 --- a/Source/MQTTnet/Client/MqttClientTcpOptions.cs +++ b/Source/MQTTnet/Client/Options/MqttClientTcpOptions.cs @@ -1,4 +1,4 @@ -namespace MQTTnet.Client +namespace MQTTnet.Client.Options { public class MqttClientTcpOptions : IMqttClientChannelOptions { diff --git a/Source/MQTTnet/Client/MqttClientTcpOptionsExtensions.cs b/Source/MQTTnet/Client/Options/MqttClientTcpOptionsExtensions.cs similarity index 92% rename from Source/MQTTnet/Client/MqttClientTcpOptionsExtensions.cs rename to Source/MQTTnet/Client/Options/MqttClientTcpOptionsExtensions.cs index 460069a7..1b49a58d 100644 --- a/Source/MQTTnet/Client/MqttClientTcpOptionsExtensions.cs +++ b/Source/MQTTnet/Client/Options/MqttClientTcpOptionsExtensions.cs @@ -1,6 +1,6 @@ using System; -namespace MQTTnet.Client +namespace MQTTnet.Client.Options { public static class MqttClientTcpOptionsExtensions { diff --git a/Source/MQTTnet/Client/MqttClientTlsOptions.cs b/Source/MQTTnet/Client/Options/MqttClientTlsOptions.cs similarity index 95% rename from Source/MQTTnet/Client/MqttClientTlsOptions.cs rename to Source/MQTTnet/Client/Options/MqttClientTlsOptions.cs index 73d65cbc..db4077da 100644 --- a/Source/MQTTnet/Client/MqttClientTlsOptions.cs +++ b/Source/MQTTnet/Client/Options/MqttClientTlsOptions.cs @@ -1,10 +1,10 @@ using System; using System.Collections.Generic; using System.Net.Security; -using System.Security.Cryptography.X509Certificates; using System.Security.Authentication; +using System.Security.Cryptography.X509Certificates; -namespace MQTTnet.Client +namespace MQTTnet.Client.Options { public class MqttClientTlsOptions { diff --git a/Source/MQTTnet/Client/MqttClientWebSocketOptions.cs b/Source/MQTTnet/Client/Options/MqttClientWebSocketOptions.cs similarity index 95% rename from Source/MQTTnet/Client/MqttClientWebSocketOptions.cs rename to Source/MQTTnet/Client/Options/MqttClientWebSocketOptions.cs index fe8f1d27..d62f1f17 100644 --- a/Source/MQTTnet/Client/MqttClientWebSocketOptions.cs +++ b/Source/MQTTnet/Client/Options/MqttClientWebSocketOptions.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; using System.Net; -namespace MQTTnet.Client +namespace MQTTnet.Client.Options { public class MqttClientWebSocketOptions : IMqttClientChannelOptions { diff --git a/Source/MQTTnet/Client/MqttClientWebSocketProxyOptions.cs b/Source/MQTTnet/Client/Options/MqttClientWebSocketProxyOptions.cs similarity index 90% rename from Source/MQTTnet/Client/MqttClientWebSocketProxyOptions.cs rename to Source/MQTTnet/Client/Options/MqttClientWebSocketProxyOptions.cs index 13b13667..7ff40f84 100644 --- a/Source/MQTTnet/Client/MqttClientWebSocketProxyOptions.cs +++ b/Source/MQTTnet/Client/Options/MqttClientWebSocketProxyOptions.cs @@ -1,4 +1,4 @@ -namespace MQTTnet.Client +namespace MQTTnet.Client.Options { public class MqttClientWebSocketProxyOptions { diff --git a/Source/MQTTnet/Client/PacketDispatcher/IMqttPacketAwaiter.cs b/Source/MQTTnet/Client/PacketDispatcher/IMqttPacketAwaiter.cs new file mode 100644 index 00000000..4887b1ef --- /dev/null +++ b/Source/MQTTnet/Client/PacketDispatcher/IMqttPacketAwaiter.cs @@ -0,0 +1,12 @@ +using System; +using MQTTnet.Packets; + +namespace MQTTnet.Client.PacketDispatcher +{ + public interface IMqttPacketAwaiter + { + void Complete(MqttBasePacket packet); + + void Fail(Exception exception); + } +} \ No newline at end of file diff --git a/Source/MQTTnet/Client/PacketDispatcher/MqttPacketAwaiter.cs b/Source/MQTTnet/Client/PacketDispatcher/MqttPacketAwaiter.cs new file mode 100644 index 00000000..292f2319 --- /dev/null +++ b/Source/MQTTnet/Client/PacketDispatcher/MqttPacketAwaiter.cs @@ -0,0 +1,37 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using MQTTnet.Packets; + +namespace MQTTnet.Client.PacketDispatcher +{ + public sealed class MqttPacketAwaiter : IMqttPacketAwaiter where TPacket : MqttBasePacket + { + private readonly TaskCompletionSource _packet = new TaskCompletionSource(); + + public async Task WaitOneAsync(TimeSpan timeout) + { + using (var timeoutToken = new CancellationTokenSource(timeout)) + { + timeoutToken.Token.Register(() => _packet.TrySetCanceled()); + + var packet = await _packet.Task.ConfigureAwait(false); + return (TPacket)packet; + } + } + + public void Complete(MqttBasePacket packet) + { + if (packet == null) throw new ArgumentNullException(nameof(packet)); + + _packet.TrySetResult(packet); + } + + public void Fail(Exception exception) + { + if (exception == null) throw new ArgumentNullException(nameof(exception)); + + _packet.TrySetException(exception); + } + } +} \ No newline at end of file diff --git a/Source/MQTTnet/Client/MqttPacketDispatcher.cs b/Source/MQTTnet/Client/PacketDispatcher/MqttPacketDispatcher.cs similarity index 68% rename from Source/MQTTnet/Client/MqttPacketDispatcher.cs rename to Source/MQTTnet/Client/PacketDispatcher/MqttPacketDispatcher.cs index fd205c51..8c6a7693 100644 --- a/Source/MQTTnet/Client/MqttPacketDispatcher.cs +++ b/Source/MQTTnet/Client/PacketDispatcher/MqttPacketDispatcher.cs @@ -3,17 +3,17 @@ using System.Collections.Concurrent; using System.Threading.Tasks; using MQTTnet.Packets; -namespace MQTTnet.Client +namespace MQTTnet.Client.PacketDispatcher { public class MqttPacketDispatcher { - private readonly ConcurrentDictionary, TaskCompletionSource> _awaiters = new ConcurrentDictionary, TaskCompletionSource>(); - + private readonly ConcurrentDictionary, IMqttPacketAwaiter> _awaiters = new ConcurrentDictionary, IMqttPacketAwaiter>(); + public void Dispatch(Exception exception) { foreach (var awaiter in _awaiters) { - Task.Run(() => awaiter.Value.TrySetException(exception)); // Task.Run fixes a dead lock. Without this the client only receives one message. + Task.Run(() => awaiter.Value.Fail(exception)); // Task.Run fixes a dead lock. Without this the client only receives one message. } _awaiters.Clear(); @@ -31,10 +31,10 @@ namespace MQTTnet.Client var type = packet.GetType(); var key = new Tuple(identifier, type); - + if (_awaiters.TryRemove(key, out var awaiter)) { - Task.Run(() => awaiter.TrySetResult(packet)); // Task.Run fixes a dead lock. Without this the client only receives one message. + Task.Run(() => awaiter.Complete(packet)); // Task.Run fixes a dead lock. Without this the client only receives one message. return; } @@ -46,22 +46,22 @@ namespace MQTTnet.Client _awaiters.Clear(); } - public TaskCompletionSource AddPacketAwaiter(ushort? identifier) where TResponsePacket : MqttBasePacket + public MqttPacketAwaiter AddPacketAwaiter(ushort? identifier) where TResponsePacket : MqttBasePacket { - var tcs = new TaskCompletionSource(); + var awaiter = new MqttPacketAwaiter(); if (!identifier.HasValue) { identifier = 0; } - - var key = new Tuple(identifier ?? 0, typeof(TResponsePacket)); - if (!_awaiters.TryAdd(key, tcs)) + + var key = new Tuple(identifier.Value, typeof(TResponsePacket)); + if (!_awaiters.TryAdd(key, awaiter)) { throw new InvalidOperationException($"The packet dispatcher already has an awaiter for packet of type '{key.Item2.Name}' with identifier {key.Item1}."); } - return tcs; + return awaiter; } public void RemovePacketAwaiter(ushort? identifier) where TResponsePacket : MqttBasePacket diff --git a/Source/MQTTnet/Client/Publishing/MqttClientPublishReasonCode.cs b/Source/MQTTnet/Client/Publishing/MqttClientPublishReasonCode.cs new file mode 100644 index 00000000..7b485c6b --- /dev/null +++ b/Source/MQTTnet/Client/Publishing/MqttClientPublishReasonCode.cs @@ -0,0 +1,15 @@ +namespace MQTTnet.Client.Publishing +{ + public enum MqttClientPublishReasonCode + { + Success = 0, + NoMatchingSubscribers = 16, + UnspecifiedError = 128, + ImplementationSpecificError = 131, + NotAuthorized = 135, + TopicNameInvalid = 144, + PacketIdentifierInUse = 145, + QuotaExceeded = 151, + PayloadFormatInvalid = 153 + } +} diff --git a/Source/MQTTnet/Client/Publishing/MqttClientPublishResult.cs b/Source/MQTTnet/Client/Publishing/MqttClientPublishResult.cs new file mode 100644 index 00000000..0e508137 --- /dev/null +++ b/Source/MQTTnet/Client/Publishing/MqttClientPublishResult.cs @@ -0,0 +1,8 @@ + +namespace MQTTnet.Client.Publishing +{ + public class MqttClientPublishResult + { + public MqttClientPublishReasonCode ReasonCode { get; set; } = MqttClientPublishReasonCode.Success; + } +} diff --git a/Source/MQTTnet/Client/Subscribing/MqttClientSubscribeResult.cs b/Source/MQTTnet/Client/Subscribing/MqttClientSubscribeResult.cs new file mode 100644 index 00000000..66d68d4e --- /dev/null +++ b/Source/MQTTnet/Client/Subscribing/MqttClientSubscribeResult.cs @@ -0,0 +1,9 @@ +using System.Collections.Generic; + +namespace MQTTnet.Client.Subscribing +{ + public class MqttClientSubscribeResult + { + public List Items { get; } = new List(); + } +} diff --git a/Source/MQTTnet/Client/Subscribing/MqttClientSubscribeResultCode.cs b/Source/MQTTnet/Client/Subscribing/MqttClientSubscribeResultCode.cs new file mode 100644 index 00000000..119ec4fa --- /dev/null +++ b/Source/MQTTnet/Client/Subscribing/MqttClientSubscribeResultCode.cs @@ -0,0 +1,18 @@ +namespace MQTTnet.Client.Subscribing +{ + public enum MqttClientSubscribeResultCode + { + GrantedQoS0 = 0, + GrantedQoS1 = 1, + GrantedQoS2 = 2, + UnspecifiedError = 128, + ImplementationSpecificError = 131, + NotAuthorized = 135, + TopicFilterInvalid = 143, + PacketIdentifierInUse = 145, + QuotaExceeded = 151, + SharedSubscriptionsNotSupported = 158, + SubscriptionIdentifiersNotSupported = 161, + WildcardSubscriptionsNotSupported = 162 + } +} diff --git a/Source/MQTTnet/Client/Subscribing/MqttClientSubscribeResultItem.cs b/Source/MQTTnet/Client/Subscribing/MqttClientSubscribeResultItem.cs new file mode 100644 index 00000000..286123f4 --- /dev/null +++ b/Source/MQTTnet/Client/Subscribing/MqttClientSubscribeResultItem.cs @@ -0,0 +1,17 @@ +using System; + +namespace MQTTnet.Client.Subscribing +{ + public class MqttClientSubscribeResultItem + { + public MqttClientSubscribeResultItem(TopicFilter topicFilter, MqttClientSubscribeResultCode resultCode) + { + TopicFilter = topicFilter ?? throw new ArgumentNullException(nameof(topicFilter)); + ResultCode = resultCode; + } + + public TopicFilter TopicFilter { get; } + + public MqttClientSubscribeResultCode ResultCode { get; } + } +} diff --git a/Source/MQTTnet/Client/Unsubscribing/MqttClientUnsubscribeResult.cs b/Source/MQTTnet/Client/Unsubscribing/MqttClientUnsubscribeResult.cs new file mode 100644 index 00000000..f3e4c4c9 --- /dev/null +++ b/Source/MQTTnet/Client/Unsubscribing/MqttClientUnsubscribeResult.cs @@ -0,0 +1,9 @@ +using System.Collections.Generic; + +namespace MQTTnet.Client.Unsubscribing +{ + public class MqttClientUnsubscribeResult + { + public List Items { get; } =new List(); + } +} diff --git a/Source/MQTTnet/Client/Unsubscribing/MqttClientUnsubscribeResultCode.cs b/Source/MQTTnet/Client/Unsubscribing/MqttClientUnsubscribeResultCode.cs new file mode 100644 index 00000000..269abc10 --- /dev/null +++ b/Source/MQTTnet/Client/Unsubscribing/MqttClientUnsubscribeResultCode.cs @@ -0,0 +1,13 @@ +namespace MQTTnet.Client.Unsubscribing +{ + public enum MqttClientUnsubscribeResultCode + { + Success = 0, + NoSubscriptionExisted = 17, + UnspecifiedError = 128, + ImplementationSpecificError = 131, + NotAuthorized = 135, + TopicFilterInvalid = 143, + PacketIdentifierInUse = 145 + } +} diff --git a/Source/MQTTnet/Client/Unsubscribing/MqttClientUnsubscribeResultItem.cs b/Source/MQTTnet/Client/Unsubscribing/MqttClientUnsubscribeResultItem.cs new file mode 100644 index 00000000..755ee43c --- /dev/null +++ b/Source/MQTTnet/Client/Unsubscribing/MqttClientUnsubscribeResultItem.cs @@ -0,0 +1,17 @@ +using System; + +namespace MQTTnet.Client.Unsubscribing +{ + public class MqttClientUnsubscribeResultItem + { + public MqttClientUnsubscribeResultItem(string topicFilter, MqttClientUnsubscribeResultCode reasonCode) + { + TopicFilter = topicFilter ?? throw new ArgumentNullException(nameof(topicFilter)); + ReasonCode = reasonCode; + } + + public string TopicFilter { get; } + + public MqttClientUnsubscribeResultCode ReasonCode { get; } + } +} \ No newline at end of file diff --git a/Source/MQTTnet/Formatter/IMqttDataConverter.cs b/Source/MQTTnet/Formatter/IMqttDataConverter.cs new file mode 100644 index 00000000..44516ee8 --- /dev/null +++ b/Source/MQTTnet/Formatter/IMqttDataConverter.cs @@ -0,0 +1,23 @@ +using MQTTnet.Client.Connecting; +using MQTTnet.Client.Options; +using MQTTnet.Client.Subscribing; +using MQTTnet.Client.Unsubscribing; +using MQTTnet.Packets; + +namespace MQTTnet.Formatter +{ + public interface IMqttDataConverter + { + MqttPublishPacket CreatePublishPacket(MqttApplicationMessage applicationMessage); + + MqttApplicationMessage CreateApplicationMessage(MqttPublishPacket publishPacket); + + MqttClientConnectResult CreateClientConnectResult(MqttConnAckPacket connAckPacket); + + MqttConnectPacket CreateConnectPacket(MqttApplicationMessage willApplicationMessage, IMqttClientOptions options); + + MqttClientSubscribeResult CreateClientSubscribeResult(MqttSubscribePacket subscribePacket, MqttSubAckPacket subAckPacket); + + MqttClientUnsubscribeResult CreateClientUnsubscribeResult(MqttUnsubscribePacket unsubscribePacket, MqttUnsubAckPacket unsubAckPacket); + } +} diff --git a/Source/MQTTnet/Formatter/IMqttPacketFormatter.cs b/Source/MQTTnet/Formatter/IMqttPacketFormatter.cs index e37d2a76..6dd417c5 100644 --- a/Source/MQTTnet/Formatter/IMqttPacketFormatter.cs +++ b/Source/MQTTnet/Formatter/IMqttPacketFormatter.cs @@ -6,12 +6,12 @@ namespace MQTTnet.Formatter { public interface IMqttPacketFormatter { + IMqttDataConverter DataConverter { get; } + ArraySegment Encode(MqttBasePacket mqttPacket); MqttBasePacket Decode(ReceivedMqttPacket receivedMqttPacket); - MqttPublishPacket ConvertApplicationMessageToPublishPacket(MqttApplicationMessage applicationMessage); - void FreeBuffer(); } } \ No newline at end of file diff --git a/Source/MQTTnet/Formatter/MqttFixedHeader.cs b/Source/MQTTnet/Formatter/MqttFixedHeader.cs index 4395d875..4c693337 100644 --- a/Source/MQTTnet/Formatter/MqttFixedHeader.cs +++ b/Source/MQTTnet/Formatter/MqttFixedHeader.cs @@ -2,14 +2,17 @@ { public struct MqttFixedHeader { - public MqttFixedHeader(byte flags, uint remainingLength) + public MqttFixedHeader(byte flags, int remainingLength, int totalLength) { Flags = flags; RemainingLength = remainingLength; + TotalLength = totalLength; } public byte Flags { get; } - public uint RemainingLength { get; } + public int RemainingLength { get; } + + public int TotalLength { get; } } } diff --git a/Source/MQTTnet/Formatter/MqttPacketFormatterAdapter.cs b/Source/MQTTnet/Formatter/MqttPacketFormatterAdapter.cs index f04467cc..104318e9 100644 --- a/Source/MQTTnet/Formatter/MqttPacketFormatterAdapter.cs +++ b/Source/MQTTnet/Formatter/MqttPacketFormatterAdapter.cs @@ -1,9 +1,8 @@ using System; using MQTTnet.Adapter; using MQTTnet.Exceptions; -using MQTTnet.Formatter.V310; -using MQTTnet.Formatter.V311; -using MQTTnet.Formatter.V500; +using MQTTnet.Formatter.V3; +using MQTTnet.Formatter.V5; using MQTTnet.Packets; namespace MQTTnet.Formatter @@ -22,35 +21,26 @@ namespace MQTTnet.Formatter } public MqttProtocolVersion? ProtocolVersion { get; private set; } - - public MqttPublishPacket ConvertApplicationMessageToPublishPacket(MqttApplicationMessage applicationMessage) + + public IMqttDataConverter DataConverter { - if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage)); - - if (_formatter == null) + get { - throw new InvalidOperationException("Protocol version not set or detected."); + ThrowIfFormatterNotSet(); + return _formatter.DataConverter; } - - return _formatter.ConvertApplicationMessageToPublishPacket(applicationMessage); } public ArraySegment Encode(MqttBasePacket packet) { - if (_formatter == null) - { - throw new InvalidOperationException("Protocol version not set or detected."); - } + ThrowIfFormatterNotSet(); return _formatter.Encode(packet); } public MqttBasePacket Decode(ReceivedMqttPacket receivedMqttPacket) { - if (_formatter == null) - { - throw new InvalidOperationException("Protocol version not set or detected."); - } + ThrowIfFormatterNotSet(); return _formatter.Decode(receivedMqttPacket); } @@ -64,7 +54,7 @@ namespace MQTTnet.Formatter { var protocolVersion = ParseProtocolVersion(receivedMqttPacket); - // Reset the position of the stream beacuse the protocol version is part of + // Reset the position of the stream because the protocol version is part of // the regular CONNECT packet. So it will not properly deserialized if this // data is missing. receivedMqttPacket.Body.Seek(0); @@ -138,5 +128,13 @@ namespace MQTTnet.Formatter throw new MqttProtocolViolationException($"Protocol '{protocolName}' not supported."); } + + private void ThrowIfFormatterNotSet() + { + if (_formatter == null) + { + throw new InvalidOperationException("Protocol version not set or detected."); + } + } } } diff --git a/Source/MQTTnet/Formatter/MqttPacketReader.cs b/Source/MQTTnet/Formatter/MqttPacketReader.cs index 97b18279..dfb633af 100644 --- a/Source/MQTTnet/Formatter/MqttPacketReader.cs +++ b/Source/MQTTnet/Formatter/MqttPacketReader.cs @@ -40,29 +40,30 @@ namespace MQTTnet.Formatter var hasRemainingLength = buffer[1] != 0; if (!hasRemainingLength) { - return new MqttFixedHeader(buffer[0], 0); + return new MqttFixedHeader(buffer[0], 0, totalBytesRead); } #if WINDOWS_UWP // UWP will have a dead lock when calling this not async. var bodyLength = await ReadBodyLengthAsync(buffer[1], cancellationToken).ConfigureAwait(false); #else - // Here the async/await pattern is not used becuase the overhead of context switches + // Here the async/await pattern is not used because the overhead of context switches // is too big for reading 1 byte in a row. We expect that the remaining data was sent // directly after the initial bytes. If the client disconnects just in this moment we // will get an exception anyway. var bodyLength = ReadBodyLength(buffer[1], cancellationToken); #endif - return new MqttFixedHeader(buffer[0], bodyLength); + totalBytesRead += bodyLength; + return new MqttFixedHeader(buffer[0], bodyLength, totalBytesRead); } #if !WINDOWS_UWP - private uint ReadBodyLength(byte initialEncodedByte, CancellationToken cancellationToken) + private int ReadBodyLength(byte initialEncodedByte, CancellationToken cancellationToken) { var offset = 0; var multiplier = 128; - var value = (uint)(initialEncodedByte & 127); + var value = (initialEncodedByte & 127); int encodedByte = initialEncodedByte; while ((encodedByte & 128) != 0) @@ -77,7 +78,7 @@ namespace MQTTnet.Formatter encodedByte = ReadByte(cancellationToken); - value += (uint)((encodedByte & 127) * multiplier); + value += (encodedByte & 127) * multiplier; multiplier *= 128; } @@ -100,11 +101,11 @@ namespace MQTTnet.Formatter #else - private async Task ReadBodyLengthAsync(byte initialEncodedByte, CancellationToken cancellationToken) + private async Task ReadBodyLengthAsync(byte initialEncodedByte, CancellationToken cancellationToken) { var offset = 0; var multiplier = 128; - var value = (uint)(initialEncodedByte & 127); + var value = initialEncodedByte & 127; int encodedByte = initialEncodedByte; while ((encodedByte & 128) != 0) @@ -119,7 +120,7 @@ namespace MQTTnet.Formatter encodedByte = await ReadByteAsync(cancellationToken).ConfigureAwait(false); - value += (uint)((encodedByte & 127) * multiplier); + value += (encodedByte & 127) * multiplier; multiplier *= 128; } diff --git a/Source/MQTTnet/Formatter/MqttPacketWriter.cs b/Source/MQTTnet/Formatter/MqttPacketWriter.cs index 0bebfbeb..bf417f77 100644 --- a/Source/MQTTnet/Formatter/MqttPacketWriter.cs +++ b/Source/MQTTnet/Formatter/MqttPacketWriter.cs @@ -67,12 +67,14 @@ namespace MQTTnet.Formatter public void WriteWithLengthPrefix(byte[] value) { + if (value == null) throw new ArgumentNullException(nameof(value)); + EnsureAdditionalCapacity(value.Length + 2); Write((ushort)value.Length); Write(value, 0, value.Length); } - + public void Write(byte @byte) { EnsureAdditionalCapacity(1); @@ -173,7 +175,7 @@ namespace MQTTnet.Formatter { return; } - + while (newBufferLength < capacity) { newBufferLength *= 2; diff --git a/Source/MQTTnet/Formatter/V3/MqttV310DataConverter.cs b/Source/MQTTnet/Formatter/V3/MqttV310DataConverter.cs new file mode 100644 index 00000000..b6436758 --- /dev/null +++ b/Source/MQTTnet/Formatter/V3/MqttV310DataConverter.cs @@ -0,0 +1,147 @@ +using System; +using System.Linq; +using MQTTnet.Client.Connecting; +using MQTTnet.Client.Options; +using MQTTnet.Client.Subscribing; +using MQTTnet.Client.Unsubscribing; +using MQTTnet.Exceptions; +using MQTTnet.Packets; +using MQTTnet.Protocol; + +namespace MQTTnet.Formatter.V3 +{ + public class MqttV310DataConverter : IMqttDataConverter + { + public MqttPublishPacket CreatePublishPacket(MqttApplicationMessage applicationMessage) + { + if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage)); + + if (applicationMessage.UserProperties?.Any() == true) + { + throw new MqttProtocolViolationException("User properties are not supported in MQTT version 3."); + } + + return new MqttPublishPacket + { + Topic = applicationMessage.Topic, + Payload = applicationMessage.Payload, + QualityOfServiceLevel = applicationMessage.QualityOfServiceLevel, + Retain = applicationMessage.Retain, + Dup = false + }; + } + + public MqttApplicationMessage CreateApplicationMessage(MqttPublishPacket publishPacket) + { + if (publishPacket == null) throw new ArgumentNullException(nameof(publishPacket)); + + return new MqttApplicationMessage + { + Topic = publishPacket.Topic, + Payload = publishPacket.Payload, + QualityOfServiceLevel = publishPacket.QualityOfServiceLevel, + Retain = publishPacket.Retain + }; + } + + public MqttClientConnectResult CreateClientConnectResult(MqttConnAckPacket connAckPacket) + { + if (connAckPacket == null) throw new ArgumentNullException(nameof(connAckPacket)); + + MqttClientConnectResultCode resultCode; + switch (connAckPacket.ReturnCode.Value) + { + case MqttConnectReturnCode.ConnectionAccepted: + { + resultCode = MqttClientConnectResultCode.Success; + break; + } + + case MqttConnectReturnCode.ConnectionRefusedUnacceptableProtocolVersion: + { + resultCode = MqttClientConnectResultCode.UnsupportedProtocolVersion; + break; + } + + case MqttConnectReturnCode.ConnectionRefusedNotAuthorized: + { + resultCode = MqttClientConnectResultCode.NotAuthorized; + break; + } + + case MqttConnectReturnCode.ConnectionRefusedBadUsernameOrPassword: + { + resultCode = MqttClientConnectResultCode.BadUserNameOrPassword; + break; + } + + case MqttConnectReturnCode.ConnectionRefusedIdentifierRejected: + { + resultCode = MqttClientConnectResultCode.ClientIdentifierNotValid; + break; + } + + case MqttConnectReturnCode.ConnectionRefusedServerUnavailable: + { + resultCode = MqttClientConnectResultCode.ServerUnavailable; + break; + } + + default: + throw new MqttProtocolViolationException("Received unexpected return code."); + } + + return new MqttClientConnectResult + { + IsSessionPresent = connAckPacket.IsSessionPresent, + ResultCode = resultCode + }; + } + + public MqttConnectPacket CreateConnectPacket(MqttApplicationMessage willApplicationMessage, IMqttClientOptions options) + { + if (options == null) throw new ArgumentNullException(nameof(options)); + + return new MqttConnectPacket + { + ClientId = options.ClientId, + Username = options.Credentials?.Username, + Password = options.Credentials?.Password, + CleanSession = options.CleanSession, + KeepAlivePeriod = (ushort)options.KeepAlivePeriod.TotalSeconds, + WillMessage = willApplicationMessage + }; + } + + public MqttClientSubscribeResult CreateClientSubscribeResult(MqttSubscribePacket subscribePacket, MqttSubAckPacket subAckPacket) + { + if (subscribePacket == null) throw new ArgumentNullException(nameof(subscribePacket)); + if (subAckPacket == null) throw new ArgumentNullException(nameof(subAckPacket)); + + if (subAckPacket.ReturnCodes.Count != subscribePacket.TopicFilters.Count) + { + throw new MqttProtocolViolationException("The return codes are not matching the topic filters [MQTT-3.9.3-1]."); + } + + var result = new MqttClientSubscribeResult(); + + result.Items.AddRange(subscribePacket.TopicFilters.Select((t, i) => + new MqttClientSubscribeResultItem(t, (MqttClientSubscribeResultCode)subAckPacket.ReturnCodes[i]))); + + return result; + } + + public MqttClientUnsubscribeResult CreateClientUnsubscribeResult(MqttUnsubscribePacket unsubscribePacket, MqttUnsubAckPacket unsubAckPacket) + { + if (unsubscribePacket == null) throw new ArgumentNullException(nameof(unsubscribePacket)); + if (unsubAckPacket == null) throw new ArgumentNullException(nameof(unsubAckPacket)); + + var result = new MqttClientUnsubscribeResult(); + + result.Items.AddRange(unsubscribePacket.TopicFilters.Select((t, i) => + new MqttClientUnsubscribeResultItem(t, MqttClientUnsubscribeResultCode.Success))); + + return result; + } + } +} diff --git a/Source/MQTTnet/Formatter/V310/MqttV310PacketFormatter.cs b/Source/MQTTnet/Formatter/V3/MqttV310PacketFormatter.cs similarity index 72% rename from Source/MQTTnet/Formatter/V310/MqttV310PacketFormatter.cs rename to Source/MQTTnet/Formatter/V3/MqttV310PacketFormatter.cs index b1edc458..e9f86022 100644 --- a/Source/MQTTnet/Formatter/V310/MqttV310PacketFormatter.cs +++ b/Source/MQTTnet/Formatter/V3/MqttV310PacketFormatter.cs @@ -6,7 +6,7 @@ using MQTTnet.Internal; using MQTTnet.Packets; using MQTTnet.Protocol; -namespace MQTTnet.Formatter.V310 +namespace MQTTnet.Formatter.V3 { public class MqttV310PacketFormatter : IMqttPacketFormatter { @@ -14,6 +14,8 @@ namespace MQTTnet.Formatter.V310 private readonly MqttPacketWriter _packetWriter = new MqttPacketWriter(); + public IMqttDataConverter DataConverter { get; } = new MqttV310DataConverter(); + public ArraySegment Encode(MqttBasePacket packet) { if (packet == null) throw new ArgumentNullException(nameof(packet)); @@ -22,7 +24,7 @@ namespace MQTTnet.Formatter.V310 _packetWriter.Reset(); _packetWriter.Seek(5); - var fixedHeader = SerializePacket(packet, _packetWriter); + var fixedHeader = EncodePacket(packet, _packetWriter); var remainingLength = (uint)(_packetWriter.Length - 5); var remainingLengthBuffer = MqttPacketWriter.EncodeVariableByteInteger(remainingLength); @@ -30,7 +32,7 @@ namespace MQTTnet.Formatter.V310 var headerSize = FixedHeaderSize + remainingLengthBuffer.Count; var headerOffset = 5 - headerSize; - // Position cursor on correct offset on beginining of array (has leading 0x0) + // Position cursor on correct offset on beginning of array (has leading 0x0) _packetWriter.Seek(headerOffset); _packetWriter.Write(fixedHeader); _packetWriter.Write(remainingLengthBuffer.Array, remainingLengthBuffer.Offset, remainingLengthBuffer.Count); @@ -51,70 +53,54 @@ namespace MQTTnet.Formatter.V310 switch ((MqttControlPacketType)controlPacketType) { - case MqttControlPacketType.Connect: return DeserializeConnectPacket(receivedMqttPacket.Body); - case MqttControlPacketType.ConnAck: return DeserializeConnAckPacket(receivedMqttPacket.Body); + case MqttControlPacketType.Connect: return DecodeConnectPacket(receivedMqttPacket.Body); + case MqttControlPacketType.ConnAck: return DecodeConnAckPacket(receivedMqttPacket.Body); case MqttControlPacketType.Disconnect: return new MqttDisconnectPacket(); - case MqttControlPacketType.Publish: return DeserializePublish(receivedMqttPacket); - case MqttControlPacketType.PubAck: return DeserializePubAck(receivedMqttPacket.Body); - case MqttControlPacketType.PubRec: return DeserializePubRec(receivedMqttPacket.Body); - case MqttControlPacketType.PubRel: return DeserializePubRel(receivedMqttPacket.Body); - case MqttControlPacketType.PubComp: return DeserializePubComp(receivedMqttPacket.Body); + case MqttControlPacketType.Publish: return DecodePublish(receivedMqttPacket); + case MqttControlPacketType.PubAck: return DecodePubAck(receivedMqttPacket.Body); + case MqttControlPacketType.PubRec: return DecodePubRec(receivedMqttPacket.Body); + case MqttControlPacketType.PubRel: return DecodePubRel(receivedMqttPacket.Body); + case MqttControlPacketType.PubComp: return DecodePubComp(receivedMqttPacket.Body); case MqttControlPacketType.PingReq: return new MqttPingReqPacket(); case MqttControlPacketType.PingResp: return new MqttPingRespPacket(); - case MqttControlPacketType.Subscribe: return DeserializeSubscribe(receivedMqttPacket.Body); - case MqttControlPacketType.SubAck: return DeserializeSubAckPacket(receivedMqttPacket.Body); - case MqttControlPacketType.Unsubscibe: return DeserializeUnsubscribe(receivedMqttPacket.Body); - case MqttControlPacketType.UnsubAck: return DeserializeUnsubAck(receivedMqttPacket.Body); + case MqttControlPacketType.Subscribe: return DecodeSubscribe(receivedMqttPacket.Body); + case MqttControlPacketType.SubAck: return DecodeSubAck(receivedMqttPacket.Body); + case MqttControlPacketType.Unsubscibe: return DecodeUnsubscribe(receivedMqttPacket.Body); + case MqttControlPacketType.UnsubAck: return DecodeUnsubAck(receivedMqttPacket.Body); default: throw new MqttProtocolViolationException($"Packet type ({controlPacketType}) not supported."); } } - public virtual MqttPublishPacket ConvertApplicationMessageToPublishPacket(MqttApplicationMessage applicationMessage) - { - if (applicationMessage.UserProperties != null) - { - throw new MqttProtocolViolationException("User properties are not supported in MQTT version 3."); - } - - return new MqttPublishPacket - { - Topic = applicationMessage.Topic, - Payload = applicationMessage.Payload, - QualityOfServiceLevel = applicationMessage.QualityOfServiceLevel, - Retain = applicationMessage.Retain, - Dup = false - }; - } - public void FreeBuffer() { _packetWriter.FreeBuffer(); } - private byte SerializePacket(MqttBasePacket packet, MqttPacketWriter packetWriter) + private byte EncodePacket(MqttBasePacket packet, MqttPacketWriter packetWriter) { switch (packet) { - case MqttConnectPacket connectPacket: return SerializeConnectPacket(connectPacket, packetWriter); - case MqttConnAckPacket connAckPacket: return SerializeConnAckPacket(connAckPacket, packetWriter); - case MqttDisconnectPacket _: return SerializeEmptyPacket(MqttControlPacketType.Disconnect); - case MqttPingReqPacket _: return SerializeEmptyPacket(MqttControlPacketType.PingReq); - case MqttPingRespPacket _: return SerializeEmptyPacket(MqttControlPacketType.PingResp); - case MqttPublishPacket publishPacket: return SerializePublishPacket(publishPacket, packetWriter); - case MqttPubAckPacket pubAckPacket: return SerializePubAckPacket(pubAckPacket, packetWriter); - case MqttPubRecPacket pubRecPacket: return SerializePubRecPacket(pubRecPacket, packetWriter); - case MqttPubRelPacket pubRelPacket: return SerializePubRelPacket(pubRelPacket, packetWriter); - case MqttPubCompPacket pubCompPacket: return SerializePubCompPacket(pubCompPacket, packetWriter); - case MqttSubscribePacket subscribePacket: return SerializeSubscribePacket(subscribePacket, packetWriter); - case MqttSubAckPacket subAckPacket: return SerializeSubAckPacket(subAckPacket, packetWriter); - case MqttUnsubscribePacket unsubscribePacket: return SerializeUnsubscribePacket(unsubscribePacket, packetWriter); - case MqttUnsubAckPacket unsubAckPacket: return SerializeUnsubAckPacket(unsubAckPacket, packetWriter); + case MqttConnectPacket connectPacket: return EncodeConnectPacket(connectPacket, packetWriter); + case MqttConnAckPacket connAckPacket: return EncodeConnAckPacket(connAckPacket, packetWriter); + case MqttDisconnectPacket _: return EncodeEmptyPacket(MqttControlPacketType.Disconnect); + case MqttPingReqPacket _: return EncodeEmptyPacket(MqttControlPacketType.PingReq); + case MqttPingRespPacket _: return EncodeEmptyPacket(MqttControlPacketType.PingResp); + case MqttPublishPacket publishPacket: return EncodePublishPacket(publishPacket, packetWriter); + case MqttPubAckPacket pubAckPacket: return EncodePubAckPacket(pubAckPacket, packetWriter); + case MqttPubRecPacket pubRecPacket: return EncodePubRecPacket(pubRecPacket, packetWriter); + case MqttPubRelPacket pubRelPacket: return EncodePubRelPacket(pubRelPacket, packetWriter); + case MqttPubCompPacket pubCompPacket: return EncodePubCompPacket(pubCompPacket, packetWriter); + case MqttSubscribePacket subscribePacket: return EncodeSubscribePacket(subscribePacket, packetWriter); + case MqttSubAckPacket subAckPacket: return EncodeSubAckPacket(subAckPacket, packetWriter); + case MqttUnsubscribePacket unsubscribePacket: return EncodeUnsubscribePacket(unsubscribePacket, packetWriter); + case MqttUnsubAckPacket unsubAckPacket: return EncodeUnsubAckPacket(unsubAckPacket, packetWriter); + default: throw new MqttProtocolViolationException("Packet type invalid."); } } - private static MqttBasePacket DeserializeUnsubAck(MqttPacketBodyReader body) + private static MqttBasePacket DecodeUnsubAck(MqttPacketBodyReader body) { ThrowIfBodyIsEmpty(body); @@ -124,7 +110,7 @@ namespace MQTTnet.Formatter.V310 }; } - private static MqttBasePacket DeserializePubComp(MqttPacketBodyReader body) + private static MqttBasePacket DecodePubComp(MqttPacketBodyReader body) { ThrowIfBodyIsEmpty(body); @@ -134,7 +120,7 @@ namespace MQTTnet.Formatter.V310 }; } - private static MqttBasePacket DeserializePubRel(MqttPacketBodyReader body) + private static MqttBasePacket DecodePubRel(MqttPacketBodyReader body) { ThrowIfBodyIsEmpty(body); @@ -144,7 +130,7 @@ namespace MQTTnet.Formatter.V310 }; } - private static MqttBasePacket DeserializePubRec(MqttPacketBodyReader body) + private static MqttBasePacket DecodePubRec(MqttPacketBodyReader body) { ThrowIfBodyIsEmpty(body); @@ -154,7 +140,7 @@ namespace MQTTnet.Formatter.V310 }; } - private static MqttBasePacket DeserializePubAck(MqttPacketBodyReader body) + private static MqttBasePacket DecodePubAck(MqttPacketBodyReader body) { ThrowIfBodyIsEmpty(body); @@ -164,7 +150,7 @@ namespace MQTTnet.Formatter.V310 }; } - private static MqttBasePacket DeserializeUnsubscribe(MqttPacketBodyReader body) + private static MqttBasePacket DecodeUnsubscribe(MqttPacketBodyReader body) { ThrowIfBodyIsEmpty(body); @@ -181,7 +167,7 @@ namespace MQTTnet.Formatter.V310 return packet; } - private static MqttBasePacket DeserializeSubscribe(MqttPacketBodyReader body) + private static MqttBasePacket DecodeSubscribe(MqttPacketBodyReader body) { ThrowIfBodyIsEmpty(body); @@ -192,15 +178,19 @@ namespace MQTTnet.Formatter.V310 while (!body.EndOfStream) { - packet.TopicFilters.Add(new TopicFilter( - body.ReadStringWithLengthPrefix(), - (MqttQualityOfServiceLevel)body.ReadByte())); + var topicFilter = new TopicFilter + { + Topic = body.ReadStringWithLengthPrefix(), + QualityOfServiceLevel = (MqttQualityOfServiceLevel)body.ReadByte() + }; + + packet.TopicFilters.Add(topicFilter); } return packet; } - private static MqttBasePacket DeserializePublish(ReceivedMqttPacket receivedMqttPacket) + private static MqttBasePacket DecodePublish(ReceivedMqttPacket receivedMqttPacket) { ThrowIfBodyIsEmpty(receivedMqttPacket.Body); @@ -229,7 +219,7 @@ namespace MQTTnet.Formatter.V310 return packet; } - private MqttBasePacket DeserializeConnectPacket(MqttPacketBodyReader body) + private MqttBasePacket DecodeConnectPacket(MqttPacketBodyReader body) { ThrowIfBodyIsEmpty(body); @@ -238,7 +228,7 @@ namespace MQTTnet.Formatter.V310 ProtocolName = body.ReadStringWithLengthPrefix(), ProtocolLevel = body.ReadByte() }; - + var connectFlags = body.ReadByte(); if ((connectFlags & 0x1) > 0) { @@ -281,7 +271,7 @@ namespace MQTTnet.Formatter.V310 return packet; } - private static MqttBasePacket DeserializeSubAckPacket(MqttPacketBodyReader body) + private static MqttBasePacket DecodeSubAck(MqttPacketBodyReader body) { ThrowIfBodyIsEmpty(body); @@ -292,20 +282,20 @@ namespace MQTTnet.Formatter.V310 while (!body.EndOfStream) { - packet.SubscribeReturnCodes.Add((MqttSubscribeReturnCode)body.ReadByte()); + packet.ReturnCodes.Add((MqttSubscribeReturnCode)body.ReadByte()); } return packet; } - protected virtual MqttBasePacket DeserializeConnAckPacket(MqttPacketBodyReader body) + protected virtual MqttBasePacket DecodeConnAckPacket(MqttPacketBodyReader body) { ThrowIfBodyIsEmpty(body); var packet = new MqttConnAckPacket(); body.ReadByte(); // Reserved. - packet.ConnectReturnCode = (MqttConnectReturnCode)body.ReadByte(); + packet.ReturnCode = (MqttConnectReturnCode)body.ReadByte(); return packet; } @@ -329,7 +319,7 @@ namespace MQTTnet.Formatter.V310 } } - protected virtual byte SerializeConnectPacket(MqttConnectPacket packet, MqttPacketWriter packetWriter) + protected virtual byte EncodeConnectPacket(MqttConnectPacket packet, MqttPacketWriter packetWriter) { ValidateConnectPacket(packet); @@ -391,15 +381,15 @@ namespace MQTTnet.Formatter.V310 return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.Connect); } - protected virtual byte SerializeConnAckPacket(MqttConnAckPacket packet, MqttPacketWriter packetWriter) + protected virtual byte EncodeConnAckPacket(MqttConnAckPacket packet, MqttPacketWriter packetWriter) { packetWriter.Write(0); // Reserved. - packetWriter.Write((byte)packet.ConnectReturnCode); + packetWriter.Write((byte)packet.ReturnCode.Value); return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.ConnAck); } - private static byte SerializePubRelPacket(MqttPubRelPacket packet, MqttPacketWriter packetWriter) + private static byte EncodePubRelPacket(MqttPubRelPacket packet, MqttPacketWriter packetWriter) { if (!packet.PacketIdentifier.HasValue) { @@ -411,7 +401,7 @@ namespace MQTTnet.Formatter.V310 return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.PubRel, 0x02); } - protected virtual byte SerializePublishPacket(MqttPublishPacket packet, MqttPacketWriter packetWriter) + private static byte EncodePublishPacket(MqttPublishPacket packet, MqttPacketWriter packetWriter) { ValidatePublishPacket(packet); @@ -456,7 +446,7 @@ namespace MQTTnet.Formatter.V310 return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.Publish, fixedHeader); } - protected virtual byte SerializePubAckPacket(MqttPubAckPacket packet, MqttPacketWriter packetWriter) + private static byte EncodePubAckPacket(MqttPubAckPacket packet, MqttPacketWriter packetWriter) { if (!packet.PacketIdentifier.HasValue) { @@ -468,7 +458,7 @@ namespace MQTTnet.Formatter.V310 return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.PubAck); } - private static byte SerializePubRecPacket(MqttPubRecPacket packet, MqttPacketWriter packetWriter) + private static byte EncodePubRecPacket(MqttPubRecPacket packet, MqttPacketWriter packetWriter) { if (!packet.PacketIdentifier.HasValue) { @@ -480,7 +470,7 @@ namespace MQTTnet.Formatter.V310 return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.PubRec); } - private static byte SerializePubCompPacket(MqttPubCompPacket packet, MqttPacketWriter packetWriter) + private static byte EncodePubCompPacket(MqttPubCompPacket packet, MqttPacketWriter packetWriter) { if (!packet.PacketIdentifier.HasValue) { @@ -492,7 +482,7 @@ namespace MQTTnet.Formatter.V310 return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.PubComp); } - private static byte SerializeSubscribePacket(MqttSubscribePacket packet, MqttPacketWriter packetWriter) + private static byte EncodeSubscribePacket(MqttSubscribePacket packet, MqttPacketWriter packetWriter) { if (!packet.TopicFilters.Any()) throw new MqttProtocolViolationException("At least one topic filter must be set [MQTT-3.8.3-3]."); @@ -515,7 +505,7 @@ namespace MQTTnet.Formatter.V310 return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.Subscribe, 0x02); } - private static byte SerializeSubAckPacket(MqttSubAckPacket packet, MqttPacketWriter packetWriter) + private static byte EncodeSubAckPacket(MqttSubAckPacket packet, MqttPacketWriter packetWriter) { if (!packet.PacketIdentifier.HasValue) { @@ -524,9 +514,9 @@ namespace MQTTnet.Formatter.V310 packetWriter.Write(packet.PacketIdentifier.Value); - if (packet.SubscribeReturnCodes?.Any() == true) + if (packet.ReturnCodes?.Any() == true) { - foreach (var packetSubscribeReturnCode in packet.SubscribeReturnCodes) + foreach (var packetSubscribeReturnCode in packet.ReturnCodes) { packetWriter.Write((byte)packetSubscribeReturnCode); } @@ -535,7 +525,7 @@ namespace MQTTnet.Formatter.V310 return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.SubAck); } - private static byte SerializeUnsubscribePacket(MqttUnsubscribePacket packet, MqttPacketWriter packetWriter) + private static byte EncodeUnsubscribePacket(MqttUnsubscribePacket packet, MqttPacketWriter packetWriter) { if (!packet.TopicFilters.Any()) throw new MqttProtocolViolationException("At least one topic filter must be set [MQTT-3.10.3-2]."); @@ -557,7 +547,7 @@ namespace MQTTnet.Formatter.V310 return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.Unsubscibe, 0x02); } - private static byte SerializeUnsubAckPacket(MqttUnsubAckPacket packet, MqttPacketWriter packetWriter) + private static byte EncodeUnsubAckPacket(MqttUnsubAckPacket packet, MqttPacketWriter packetWriter) { if (!packet.PacketIdentifier.HasValue) { @@ -568,7 +558,7 @@ namespace MQTTnet.Formatter.V310 return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.UnsubAck); } - private static byte SerializeEmptyPacket(MqttControlPacketType type) + private static byte EncodeEmptyPacket(MqttControlPacketType type) { return MqttPacketWriter.BuildFixedHeader(type); } diff --git a/Source/MQTTnet/Formatter/V311/MqttV311PacketFormatter.cs b/Source/MQTTnet/Formatter/V3/MqttV311PacketFormatter.cs similarity index 83% rename from Source/MQTTnet/Formatter/V311/MqttV311PacketFormatter.cs rename to Source/MQTTnet/Formatter/V3/MqttV311PacketFormatter.cs index a02b9b37..0bc60a99 100644 --- a/Source/MQTTnet/Formatter/V311/MqttV311PacketFormatter.cs +++ b/Source/MQTTnet/Formatter/V3/MqttV311PacketFormatter.cs @@ -1,13 +1,12 @@ using MQTTnet.Exceptions; -using MQTTnet.Formatter.V310; using MQTTnet.Packets; using MQTTnet.Protocol; -namespace MQTTnet.Formatter.V311 +namespace MQTTnet.Formatter.V3 { public class MqttV311PacketFormatter : MqttV310PacketFormatter { - protected override byte SerializeConnectPacket(MqttConnectPacket packet, MqttPacketWriter packetWriter) + protected override byte EncodeConnectPacket(MqttConnectPacket packet, MqttPacketWriter packetWriter) { ValidateConnectPacket(packet); @@ -69,7 +68,7 @@ namespace MQTTnet.Formatter.V311 return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.Connect); } - protected override byte SerializeConnAckPacket(MqttConnAckPacket packet, MqttPacketWriter packetWriter) + protected override byte EncodeConnAckPacket(MqttConnAckPacket packet, MqttPacketWriter packetWriter) { byte connectAcknowledgeFlags = 0x0; if (packet.IsSessionPresent) @@ -78,12 +77,12 @@ namespace MQTTnet.Formatter.V311 } packetWriter.Write(connectAcknowledgeFlags); - packetWriter.Write((byte)packet.ConnectReturnCode); + packetWriter.Write((byte)packet.ReturnCode); return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.ConnAck); } - protected override MqttBasePacket DeserializeConnAckPacket(MqttPacketBodyReader body) + protected override MqttBasePacket DecodeConnAckPacket(MqttPacketBodyReader body) { ThrowIfBodyIsEmpty(body); @@ -92,7 +91,7 @@ namespace MQTTnet.Formatter.V311 var acknowledgeFlags = body.ReadByte(); packet.IsSessionPresent = (acknowledgeFlags & 0x1) > 0; - packet.ConnectReturnCode = (MqttConnectReturnCode)body.ReadByte(); + packet.ReturnCode = (MqttConnectReturnCode)body.ReadByte(); return packet; } diff --git a/Source/MQTTnet/Formatter/V5/MqttV500DataConverter.cs b/Source/MQTTnet/Formatter/V5/MqttV500DataConverter.cs new file mode 100644 index 00000000..5a26a677 --- /dev/null +++ b/Source/MQTTnet/Formatter/V5/MqttV500DataConverter.cs @@ -0,0 +1,132 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using MQTTnet.Client.Connecting; +using MQTTnet.Client.Options; +using MQTTnet.Client.Subscribing; +using MQTTnet.Client.Unsubscribing; +using MQTTnet.Exceptions; +using MQTTnet.Packets; + +namespace MQTTnet.Formatter.V5 +{ + public class MqttV500DataConverter : IMqttDataConverter + { + public MqttPublishPacket CreatePublishPacket(MqttApplicationMessage applicationMessage) + { + if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage)); + + var packet = new MqttPublishPacket + { + Topic = applicationMessage.Topic, + Payload = applicationMessage.Payload, + QualityOfServiceLevel = applicationMessage.QualityOfServiceLevel, + Retain = applicationMessage.Retain, + Dup = false, + Properties = new MqttPublishPacketProperties + { + ContentType = applicationMessage.ContentType, + CorrelationData = applicationMessage.CorrelationData, + MessageExpiryInterval = applicationMessage.MessageExpiryInterval, + PayloadFormatIndicator = applicationMessage.PayloadFormatIndicator, + ResponseTopic = applicationMessage.ResponseTopic, + SubscriptionIdentifier = applicationMessage.SubscriptionIdentifier, + TopicAlias = applicationMessage.TopicAlias + } + }; + + if (applicationMessage.UserProperties != null) + { + packet.Properties.UserProperties.AddRange(applicationMessage.UserProperties); + } + + return packet; + } + + public MqttApplicationMessage CreateApplicationMessage(MqttPublishPacket publishPacket) + { + return new MqttApplicationMessage + { + Topic = publishPacket.Topic, + Payload = publishPacket.Payload, + QualityOfServiceLevel = publishPacket.QualityOfServiceLevel, + Retain = publishPacket.Retain, + UserProperties = publishPacket.Properties?.UserProperties ?? new List() + }; + } + + public MqttClientConnectResult CreateClientConnectResult(MqttConnAckPacket connAckPacket) + { + if (connAckPacket == null) throw new ArgumentNullException(nameof(connAckPacket)); + + return new MqttClientConnectResult + { + IsSessionPresent = connAckPacket.IsSessionPresent, + ResultCode = (MqttClientConnectResultCode)connAckPacket.ReasonCode.Value + }; + } + + public MqttConnectPacket CreateConnectPacket(MqttApplicationMessage willApplicationMessage, IMqttClientOptions options) + { + if (options == null) throw new ArgumentNullException(nameof(options)); + + return new MqttConnectPacket + { + ClientId = options.ClientId, + Username = options.Credentials?.Username, + Password = options.Credentials?.Password, + CleanSession = options.CleanSession, + KeepAlivePeriod = (ushort)options.KeepAlivePeriod.TotalSeconds, + WillMessage = willApplicationMessage, + Properties = new MqttConnectPacketProperties + { + AuthenticationMethod = options.AuthenticationMethod, + AuthenticationData = options.AuthenticationData, + WillDelayInterval = options.WillDelayInterval, + MaximumPacketSize = options.MaximumPacketSize, + ReceiveMaximum = options.ReceiveMaximum, + RequestProblemInformation = options.RequestProblemInformation, + RequestResponseInformation = options.RequestResponseInformation, + SessionExpiryInterval = options.SessionExpiryInterval, + TopicAliasMaximum = options.TopicAliasMaximum + } + }; + } + + public MqttClientSubscribeResult CreateClientSubscribeResult(MqttSubscribePacket subscribePacket, MqttSubAckPacket subAckPacket) + { + if (subscribePacket == null) throw new ArgumentNullException(nameof(subscribePacket)); + if (subAckPacket == null) throw new ArgumentNullException(nameof(subAckPacket)); + + if (subAckPacket.ReasonCodes.Count != subscribePacket.TopicFilters.Count) + { + throw new MqttProtocolViolationException("The reason codes are not matching the topic filters [MQTT-3.9.3-1]."); + } + + var result = new MqttClientSubscribeResult(); + + result.Items.AddRange(subscribePacket.TopicFilters.Select((t, i) => + new MqttClientSubscribeResultItem(t, (MqttClientSubscribeResultCode)subAckPacket.ReasonCodes[i]))); + + return result; + } + + public MqttClientUnsubscribeResult CreateClientUnsubscribeResult(MqttUnsubscribePacket unsubscribePacket, MqttUnsubAckPacket unsubAckPacket) + { + if (unsubscribePacket == null) throw new ArgumentNullException(nameof(unsubscribePacket)); + if (unsubAckPacket == null) throw new ArgumentNullException(nameof(unsubAckPacket)); + + if (unsubAckPacket.ReasonCodes.Count != unsubscribePacket.TopicFilters.Count) + { + throw new MqttProtocolViolationException("The return codes are not matching the topic filters [MQTT-3.9.3-1]."); + } + + var result = new MqttClientUnsubscribeResult(); + + result.Items.AddRange(unsubscribePacket.TopicFilters.Select((t, i) => + new MqttClientUnsubscribeResultItem(t, (MqttClientUnsubscribeResultCode)unsubAckPacket.ReasonCodes[i]))); + + return result; + } + } +} diff --git a/Source/MQTTnet/Formatter/V5/MqttV500PacketDecoder.cs b/Source/MQTTnet/Formatter/V5/MqttV500PacketDecoder.cs new file mode 100644 index 00000000..a133dc64 --- /dev/null +++ b/Source/MQTTnet/Formatter/V5/MqttV500PacketDecoder.cs @@ -0,0 +1,717 @@ +using System; +using System.Linq; +using MQTTnet.Adapter; +using MQTTnet.Exceptions; +using MQTTnet.Packets; +using MQTTnet.Protocol; + +namespace MQTTnet.Formatter.V5 +{ + public class MqttV500PacketDecoder + { + public MqttBasePacket Decode(ReceivedMqttPacket receivedMqttPacket) + { + if (receivedMqttPacket == null) throw new ArgumentNullException(nameof(receivedMqttPacket)); + + var controlPacketType = receivedMqttPacket.FixedHeader >> 4; + if (controlPacketType < 1 || controlPacketType > 15) + { + throw new MqttProtocolViolationException($"The packet type is invalid ({controlPacketType})."); + } + + switch ((MqttControlPacketType)controlPacketType) + { + case MqttControlPacketType.Connect: return DecodeConnectPacket(receivedMqttPacket.Body); + case MqttControlPacketType.ConnAck: return DecodeConnAckPacket(receivedMqttPacket.Body); + case MqttControlPacketType.Disconnect: return DecodeDisconnectPacket(receivedMqttPacket.Body); + case MqttControlPacketType.Publish: return DecodePublishPacket(receivedMqttPacket.FixedHeader, receivedMqttPacket.Body); + case MqttControlPacketType.PubAck: return DecodePubAckPacket(receivedMqttPacket.Body); + case MqttControlPacketType.PubRec: return DecodePubRecPacket(receivedMqttPacket.Body); + case MqttControlPacketType.PubRel: return DecodePubRelPacket(receivedMqttPacket.Body); + case MqttControlPacketType.PubComp: return DecodePubCompPacket(receivedMqttPacket.Body); + case MqttControlPacketType.PingReq: return DecodePingReqPacket(); + case MqttControlPacketType.PingResp: return DecodePingRespPacket(); + case MqttControlPacketType.Subscribe: return DecodeSubscribePacket(receivedMqttPacket.Body); + case MqttControlPacketType.SubAck: return DecodeSubAckPacket(receivedMqttPacket.Body); + case MqttControlPacketType.Unsubscibe: return DecodeUnsubscribePacket(receivedMqttPacket.Body); + case MqttControlPacketType.UnsubAck: return DecodeUnsubAckPacket(receivedMqttPacket.Body); + case MqttControlPacketType.Auth: return DecodeAuthPacket(receivedMqttPacket.Body); + + default: throw new MqttProtocolViolationException($"Packet type ({controlPacketType}) not supported."); + } + } + + private static MqttBasePacket DecodeConnectPacket(MqttPacketBodyReader body) + { + ThrowIfBodyIsEmpty(body); + + var packet = new MqttConnectPacket + { + Properties = new MqttConnectPacketProperties() + }; + + var protocolName = body.ReadStringWithLengthPrefix(); + var protocolVersion = body.ReadByte(); + + if (protocolName != "MQTT" && protocolVersion != 5) + { + throw new MqttProtocolViolationException("MQTT protocol name and version do not match MQTT v5."); + } + + var connectFlags = body.ReadByte(); + + var cleanSessionFlag = (connectFlags & 0x02) > 0; + var willMessageFlag = (connectFlags & 0x04) > 0; + var willMessageQoS = (byte)(connectFlags >> 3 & 3); + var willMessageRetainFlag = (connectFlags & 0x20) > 0; + var passwordFlag = (connectFlags & 0x40) > 0; + var usernameFlag = (connectFlags & 0x80) > 0; + + packet.CleanSession = cleanSessionFlag; + + if (willMessageFlag) + { + packet.WillMessage = new MqttApplicationMessage + { + QualityOfServiceLevel = (MqttQualityOfServiceLevel)willMessageQoS, + Retain = willMessageRetainFlag + }; + } + + packet.KeepAlivePeriod = body.ReadTwoByteInteger(); + + var propertiesReader = new MqttV500PropertiesReader(body); + while (propertiesReader.MoveNext()) + { + if (propertiesReader.CurrentPropertyId == MqttPropertyId.SessionExpiryInterval) + { + packet.Properties.SessionExpiryInterval = propertiesReader.ReadSessionExpiryInterval(); + } + else if (propertiesReader.CurrentPropertyId == MqttPropertyId.AuthenticationMethod) + { + packet.Properties.AuthenticationMethod = propertiesReader.ReadAuthenticationMethod(); + } + else if (propertiesReader.CurrentPropertyId == MqttPropertyId.WillDelayInterval) + { + packet.Properties.WillDelayInterval = propertiesReader.ReadWillDelayInterval(); + } + else if (propertiesReader.CurrentPropertyId == MqttPropertyId.AuthenticationData) + { + packet.Properties.AuthenticationData = propertiesReader.ReadAuthenticationData(); + } + else if (propertiesReader.CurrentPropertyId == MqttPropertyId.ReceiveMaximum) + { + packet.Properties.ReceiveMaximum = propertiesReader.ReadReceiveMaximum(); + } + else if (propertiesReader.CurrentPropertyId == MqttPropertyId.TopicAliasMaximum) + { + packet.Properties.TopicAliasMaximum = propertiesReader.ReadTopicAliasMaximum(); + } + else if (propertiesReader.CurrentPropertyId == MqttPropertyId.MaximumPacketSize) + { + packet.Properties.MaximumPacketSize = propertiesReader.ReadMaximumPacketSize(); + } + else if (propertiesReader.CurrentPropertyId == MqttPropertyId.RequestResponseInformation) + { + packet.Properties.RequestResponseInformation = propertiesReader.RequestResponseInformation(); + } + else if (propertiesReader.CurrentPropertyId == MqttPropertyId.RequestProblemInformation) + { + packet.Properties.RequestProblemInformation = propertiesReader.RequestProblemInformation(); + } + else if (propertiesReader.CurrentPropertyId == MqttPropertyId.UserProperty) + { + propertiesReader.FillUserProperties(packet.Properties.UserProperties); + } + else + { + propertiesReader.ThrowInvalidPropertyIdException(typeof(MqttConnAckPacket)); + } + } + + packet.ClientId = body.ReadStringWithLengthPrefix(); + + if (packet.WillMessage != null) + { + packet.WillMessage.Topic = body.ReadStringWithLengthPrefix(); + packet.WillMessage.Payload = body.ReadWithLengthPrefix().ToArray(); + } + + if (usernameFlag) + { + packet.Username = body.ReadStringWithLengthPrefix(); + } + + if (passwordFlag) + { + packet.Password = body.ReadStringWithLengthPrefix(); + } + + return packet; + } + + private static MqttBasePacket DecodeConnAckPacket(MqttPacketBodyReader body) + { + ThrowIfBodyIsEmpty(body); + + var acknowledgeFlags = body.ReadByte(); + + var packet = new MqttConnAckPacket + { + IsSessionPresent = (acknowledgeFlags & 0x1) > 0, + ReasonCode = (MqttConnectReasonCode)body.ReadByte(), + Properties = new MqttConnAckPacketProperties() + }; + + var propertiesReader = new MqttV500PropertiesReader(body); + while (propertiesReader.MoveNext()) + { + if (propertiesReader.CurrentPropertyId == MqttPropertyId.SessionExpiryInterval) + { + packet.Properties.SessionExpiryInterval = propertiesReader.ReadSessionExpiryInterval(); + } + else if (propertiesReader.CurrentPropertyId == MqttPropertyId.AuthenticationMethod) + { + packet.Properties.AuthenticationMethod = propertiesReader.ReadAuthenticationMethod(); + } + else if (propertiesReader.CurrentPropertyId == MqttPropertyId.AuthenticationData) + { + packet.Properties.AuthenticationData = propertiesReader.ReadAuthenticationData(); + } + else if (propertiesReader.CurrentPropertyId == MqttPropertyId.RetainAvailable) + { + packet.Properties.RetainAvailable = propertiesReader.ReadRetainAvailable(); + } + else if (propertiesReader.CurrentPropertyId == MqttPropertyId.ReceiveMaximum) + { + packet.Properties.ReceiveMaximum = propertiesReader.ReadReceiveMaximum(); + } + else if (propertiesReader.CurrentPropertyId == MqttPropertyId.AssignedClientIdentifer) + { + packet.Properties.AssignedClientIdentifier = propertiesReader.ReadAssignedClientIdentifier(); + } + else if (propertiesReader.CurrentPropertyId == MqttPropertyId.TopicAliasMaximum) + { + packet.Properties.TopicAliasMaximum = propertiesReader.ReadTopicAliasMaximum(); + } + else if (propertiesReader.CurrentPropertyId == MqttPropertyId.ReasonString) + { + packet.Properties.ReasonString = propertiesReader.ReadReasonString(); + } + else if (propertiesReader.CurrentPropertyId == MqttPropertyId.MaximumPacketSize) + { + packet.Properties.MaximumPacketSize = propertiesReader.ReadMaximumPacketSize(); + } + else if (propertiesReader.CurrentPropertyId == MqttPropertyId.WildcardSubscriptionAvailable) + { + packet.Properties.WildcardSubscriptionAvailable = propertiesReader.ReadWildcardSubscriptionAvailable(); + } + else if (propertiesReader.CurrentPropertyId == MqttPropertyId.SubscriptionIdentifiersAvailable) + { + packet.Properties.SubscriptionIdentifiersAvailable = propertiesReader.ReadSubscriptionIdentifiersAvailable(); + } + else if (propertiesReader.CurrentPropertyId == MqttPropertyId.SharedSubscriptionAvailable) + { + packet.Properties.SharedSubscriptionAvailable = propertiesReader.ReadSharedSubscriptionAvailable(); + } + else if (propertiesReader.CurrentPropertyId == MqttPropertyId.ServerKeepAlive) + { + packet.Properties.ServerKeepAlive = propertiesReader.ReadServerKeepAlive(); + } + else if (propertiesReader.CurrentPropertyId == MqttPropertyId.ResponseInformation) + { + packet.Properties.ResponseInformation = propertiesReader.ReadResponseInformation(); + } + else if (propertiesReader.CurrentPropertyId == MqttPropertyId.ServerReference) + { + packet.Properties.ServerReference = propertiesReader.ReadServerReference(); + } + else if (propertiesReader.CurrentPropertyId == MqttPropertyId.UserProperty) + { + propertiesReader.FillUserProperties(packet.Properties.UserProperties); + } + else + { + propertiesReader.ThrowInvalidPropertyIdException(typeof(MqttConnAckPacket)); + } + } + + return packet; + } + + private static MqttBasePacket DecodeDisconnectPacket(MqttPacketBodyReader body) + { + ThrowIfBodyIsEmpty(body); + + var packet = new MqttDisconnectPacket + { + ReasonCode = (MqttDisconnectReasonCode)body.ReadByte(), + Properties = new MqttDisconnectPacketProperties() + }; + + var propertiesReader = new MqttV500PropertiesReader(body); + while (propertiesReader.MoveNext()) + { + if (propertiesReader.CurrentPropertyId == MqttPropertyId.SessionExpiryInterval) + { + packet.Properties.SessionExpiryInterval = propertiesReader.ReadSessionExpiryInterval(); + } + else if (propertiesReader.CurrentPropertyId == MqttPropertyId.ReasonString) + { + packet.Properties.ReasonString = propertiesReader.ReadReasonString(); + } + else if (propertiesReader.CurrentPropertyId == MqttPropertyId.ServerReference) + { + packet.Properties.ServerReference = propertiesReader.ReadServerReference(); + } + else if (propertiesReader.CurrentPropertyId == MqttPropertyId.UserProperty) + { + propertiesReader.FillUserProperties(packet.Properties.UserProperties); + } + else + { + propertiesReader.ThrowInvalidPropertyIdException(typeof(MqttDisconnectPacket)); + } + } + + return packet; + } + + private static MqttBasePacket DecodeSubscribePacket(MqttPacketBodyReader body) + { + ThrowIfBodyIsEmpty(body); + + var packet = new MqttSubscribePacket + { + PacketIdentifier = body.ReadTwoByteInteger(), + Properties = new MqttSubscribePacketProperties() + }; + + var propertiesReader = new MqttV500PropertiesReader(body); + while (propertiesReader.MoveNext()) + { + if (propertiesReader.CurrentPropertyId == MqttPropertyId.SubscriptionIdentifier) + { + packet.Properties.SubscriptionIdentifier = propertiesReader.ReadSubscriptionIdentifier(); + } + else if (propertiesReader.CurrentPropertyId == MqttPropertyId.UserProperty) + { + propertiesReader.FillUserProperties(packet.Properties.UserProperties); + } + else + { + propertiesReader.ThrowInvalidPropertyIdException(typeof(MqttSubscribePacket)); + } + } + + while (!body.EndOfStream) + { + var topic = body.ReadStringWithLengthPrefix(); + var options = body.ReadByte(); + + var qos = (MqttQualityOfServiceLevel)(options & 3); + var noLocal = (options & (1 << 2)) > 0; + var retainAsPublished = (options & (1 << 3)) > 0; + var retainHandling = (MqttRetainHandling)((options >> 4) & 3); + + packet.TopicFilters.Add(new TopicFilter + { + Topic = topic, + QualityOfServiceLevel = qos, + NoLocal = noLocal, + RetainAsPublished = retainAsPublished, + RetainHandling = retainHandling + }); + } + + return packet; + } + + private static MqttBasePacket DecodeSubAckPacket(MqttPacketBodyReader body) + { + ThrowIfBodyIsEmpty(body); + + var packet = new MqttSubAckPacket + { + PacketIdentifier = body.ReadTwoByteInteger(), + Properties = new MqttSubAckPacketProperties() + }; + + var propertiesReader = new MqttV500PropertiesReader(body); + while (propertiesReader.MoveNext()) + { + if (propertiesReader.CurrentPropertyId == MqttPropertyId.ReasonString) + { + packet.Properties.ReasonString = propertiesReader.ReadReasonString(); + } + else if (propertiesReader.CurrentPropertyId == MqttPropertyId.UserProperty) + { + propertiesReader.FillUserProperties(packet.Properties.UserProperties); + } + else + { + propertiesReader.ThrowInvalidPropertyIdException(typeof(MqttSubAckPacket)); + } + } + + while (!body.EndOfStream) + { + var reasonCode = (MqttSubscribeReasonCode)body.ReadByte(); + packet.ReasonCodes.Add(reasonCode); + } + + return packet; + } + + private static MqttBasePacket DecodeUnsubscribePacket(MqttPacketBodyReader body) + { + ThrowIfBodyIsEmpty(body); + + var packet = new MqttUnsubscribePacket + { + PacketIdentifier = body.ReadTwoByteInteger(), + Properties = new MqttUnsubscribePacketProperties() + }; + + var propertiesReader = new MqttV500PropertiesReader(body); + while (propertiesReader.MoveNext()) + { + if (propertiesReader.CurrentPropertyId == MqttPropertyId.UserProperty) + { + propertiesReader.FillUserProperties(packet.Properties.UserProperties); + } + else + { + propertiesReader.ThrowInvalidPropertyIdException(typeof(MqttUnsubscribePacket)); + } + } + + while (!body.EndOfStream) + { + packet.TopicFilters.Add(body.ReadStringWithLengthPrefix()); + } + + return packet; + } + + private static MqttBasePacket DecodeUnsubAckPacket(MqttPacketBodyReader body) + { + ThrowIfBodyIsEmpty(body); + + var packet = new MqttUnsubAckPacket + { + PacketIdentifier = body.ReadTwoByteInteger(), + Properties = new MqttUnsubAckPacketProperties() + }; + + var propertiesReader = new MqttV500PropertiesReader(body); + while (propertiesReader.MoveNext()) + { + if (propertiesReader.CurrentPropertyId == MqttPropertyId.ReasonString) + { + packet.Properties.ReasonString = propertiesReader.ReadReasonString(); + } + else if (propertiesReader.CurrentPropertyId == MqttPropertyId.UserProperty) + { + propertiesReader.FillUserProperties(packet.Properties.UserProperties); + } + else + { + propertiesReader.ThrowInvalidPropertyIdException(typeof(MqttUnsubAckPacket)); + } + } + + while (!body.EndOfStream) + { + var reasonCode = (MqttUnsubscribeReasonCode)body.ReadByte(); + packet.ReasonCodes.Add(reasonCode); + } + + return packet; + } + + private static MqttBasePacket DecodePingReqPacket() + { + return new MqttPingReqPacket(); + } + + private static MqttBasePacket DecodePingRespPacket() + { + return new MqttPingRespPacket(); + } + + private static MqttBasePacket DecodePublishPacket(byte header, MqttPacketBodyReader body) + { + ThrowIfBodyIsEmpty(body); + + var retain = (header & 1) > 0; + var qos = (MqttQualityOfServiceLevel)(header >> 1 & 3); + var dup = (header >> 3 & 1) > 0; + + var packet = new MqttPublishPacket + { + Topic = body.ReadStringWithLengthPrefix(), + Retain = retain, + QualityOfServiceLevel = qos, + Dup = dup, + Properties = new MqttPublishPacketProperties() + }; + + if (qos > 0) + { + packet.PacketIdentifier = body.ReadTwoByteInteger(); + } + + var propertiesReader = new MqttV500PropertiesReader(body); + while (propertiesReader.MoveNext()) + { + if (propertiesReader.CurrentPropertyId == MqttPropertyId.PayloadFormatIndicator) + { + packet.Properties.PayloadFormatIndicator = propertiesReader.ReadPayloadFormatIndicator(); + } + else if (propertiesReader.CurrentPropertyId == MqttPropertyId.MessageExpiryInterval) + { + packet.Properties.MessageExpiryInterval = propertiesReader.ReadMessageExpiryInterval(); + } + else if (propertiesReader.CurrentPropertyId == MqttPropertyId.TopicAlias) + { + packet.Properties.TopicAlias = propertiesReader.ReadTopicAlias(); + } + else if (propertiesReader.CurrentPropertyId == MqttPropertyId.ResponseTopic) + { + packet.Properties.ResponseTopic = propertiesReader.ReadResponseTopic(); + } + else if (propertiesReader.CurrentPropertyId == MqttPropertyId.CorrelationData) + { + packet.Properties.CorrelationData = propertiesReader.ReadCorrelationData(); + } + else if (propertiesReader.CurrentPropertyId == MqttPropertyId.SubscriptionIdentifier) + { + packet.Properties.SubscriptionIdentifier = propertiesReader.ReadSubscriptionIdentifier(); + } + else if (propertiesReader.CurrentPropertyId == MqttPropertyId.ContentType) + { + packet.Properties.ContentType = propertiesReader.ReadContentType(); + } + else if (propertiesReader.CurrentPropertyId == MqttPropertyId.UserProperty) + { + propertiesReader.FillUserProperties(packet.Properties.UserProperties); + } + else + { + propertiesReader.ThrowInvalidPropertyIdException(typeof(MqttPublishPacket)); + } + } + + packet.Payload = body.ReadRemainingData().ToArray(); + + return packet; + } + + private static MqttBasePacket DecodePubAckPacket(MqttPacketBodyReader body) + { + ThrowIfBodyIsEmpty(body); + + var packet = new MqttPubAckPacket + { + PacketIdentifier = body.ReadTwoByteInteger(), + Properties = new MqttPubAckPacketProperties() + }; + + if (body.EndOfStream) + { + packet.ReasonCode = MqttPubAckReasonCode.Success; + return packet; + } + + packet.ReasonCode = (MqttPubAckReasonCode)body.ReadByte(); + + var propertiesReader = new MqttV500PropertiesReader(body); + while (propertiesReader.MoveNext()) + { + if (propertiesReader.CurrentPropertyId == MqttPropertyId.ReasonString) + { + packet.Properties.ReasonString = propertiesReader.ReadReasonString(); + } + else if (propertiesReader.CurrentPropertyId == MqttPropertyId.UserProperty) + { + propertiesReader.FillUserProperties(packet.Properties.UserProperties); + } + else + { + propertiesReader.ThrowInvalidPropertyIdException(typeof(MqttPubAckPacket)); + } + } + + return packet; + } + + private static MqttBasePacket DecodePubRecPacket(MqttPacketBodyReader body) + { + ThrowIfBodyIsEmpty(body); + + var packet = new MqttPubRecPacket + { + PacketIdentifier = body.ReadTwoByteInteger(), + Properties = new MqttPubRecPacketProperties() + }; + + if (body.EndOfStream) + { + packet.ReasonCode = MqttPubRecReasonCode.Success; + return packet; + } + + packet.ReasonCode = (MqttPubRecReasonCode)body.ReadByte(); + + var propertiesReader = new MqttV500PropertiesReader(body); + while (propertiesReader.MoveNext()) + { + if (propertiesReader.CurrentPropertyId == MqttPropertyId.ReasonString) + { + packet.Properties.ReasonString = propertiesReader.ReadReasonString(); + } + else if (propertiesReader.CurrentPropertyId == MqttPropertyId.UserProperty) + { + propertiesReader.FillUserProperties(packet.Properties.UserProperties); + } + else + { + propertiesReader.ThrowInvalidPropertyIdException(typeof(MqttPubRecPacket)); + } + } + + return packet; + } + + private static MqttBasePacket DecodePubRelPacket(MqttPacketBodyReader body) + { + ThrowIfBodyIsEmpty(body); + + var packet = new MqttPubRelPacket + { + PacketIdentifier = body.ReadTwoByteInteger(), + Properties = new MqttPubRelPacketProperties() + }; + + if (body.EndOfStream) + { + packet.ReasonCode = MqttPubRelReasonCode.Success; + return packet; + } + + packet.ReasonCode = (MqttPubRelReasonCode)body.ReadByte(); + + var propertiesReader = new MqttV500PropertiesReader(body); + while (propertiesReader.MoveNext()) + { + if (propertiesReader.CurrentPropertyId == MqttPropertyId.ReasonString) + { + packet.Properties.ReasonString = propertiesReader.ReadReasonString(); + } + else if (propertiesReader.CurrentPropertyId == MqttPropertyId.UserProperty) + { + propertiesReader.FillUserProperties(packet.Properties.UserProperties); + } + else + { + propertiesReader.ThrowInvalidPropertyIdException(typeof(MqttPubRelPacket)); + } + } + + return packet; + } + + private static MqttBasePacket DecodePubCompPacket(MqttPacketBodyReader body) + { + ThrowIfBodyIsEmpty(body); + + var packet = new MqttPubCompPacket + { + PacketIdentifier = body.ReadTwoByteInteger(), + Properties = new MqttPubCompPacketProperties() + }; + + if (body.EndOfStream) + { + packet.ReasonCode = MqttPubCompReasonCode.Success; + return packet; + } + + packet.ReasonCode = (MqttPubCompReasonCode)body.ReadByte(); + + var propertiesReader = new MqttV500PropertiesReader(body); + while (propertiesReader.MoveNext()) + { + if (propertiesReader.CurrentPropertyId == MqttPropertyId.ReasonString) + { + packet.Properties.ReasonString = propertiesReader.ReadReasonString(); + } + else if (propertiesReader.CurrentPropertyId == MqttPropertyId.UserProperty) + { + propertiesReader.FillUserProperties(packet.Properties.UserProperties); + } + else + { + propertiesReader.ThrowInvalidPropertyIdException(typeof(MqttPubCompPacket)); + } + } + + return packet; + } + + private static MqttBasePacket DecodeAuthPacket(MqttPacketBodyReader body) + { + ThrowIfBodyIsEmpty(body); + + var packet = new MqttAuthPacket + { + Properties = new MqttAuthPacketProperties() + }; + + if (body.EndOfStream) + { + packet.ReasonCode = MqttAuthenticateReasonCode.Success; + return packet; + } + + packet.ReasonCode = (MqttAuthenticateReasonCode)body.ReadByte(); + + var propertiesReader = new MqttV500PropertiesReader(body); + while (propertiesReader.MoveNext()) + { + if (propertiesReader.CurrentPropertyId == MqttPropertyId.AuthenticationMethod) + { + packet.Properties.AuthenticationMethod = propertiesReader.ReadAuthenticationMethod(); + } + else if (propertiesReader.CurrentPropertyId == MqttPropertyId.AuthenticationData) + { + packet.Properties.AuthenticationData = propertiesReader.ReadAuthenticationData(); + } + else if (propertiesReader.CurrentPropertyId == MqttPropertyId.ReasonString) + { + packet.Properties.ReasonString = propertiesReader.ReadReasonString(); + } + else if (propertiesReader.CurrentPropertyId == MqttPropertyId.UserProperty) + { + propertiesReader.FillUserProperties(packet.Properties.UserProperties); + } + else + { + propertiesReader.ThrowInvalidPropertyIdException(typeof(MqttAuthPacket)); + } + } + + return packet; + } + + // ReSharper disable once ParameterOnlyUsedForPreconditionCheck.Local + private static void ThrowIfBodyIsEmpty(MqttPacketBodyReader body) + { + if (body == null || body.Length == 0) + { + throw new MqttProtocolViolationException("Data from the body is required but not present."); + } + } + } +} diff --git a/Source/MQTTnet/Formatter/V5/MqttV500PacketEncoder.cs b/Source/MQTTnet/Formatter/V5/MqttV500PacketEncoder.cs new file mode 100644 index 00000000..0c0019c9 --- /dev/null +++ b/Source/MQTTnet/Formatter/V5/MqttV500PacketEncoder.cs @@ -0,0 +1,572 @@ +using System; +using System.Linq; +using MQTTnet.Exceptions; +using MQTTnet.Packets; +using MQTTnet.Protocol; + +namespace MQTTnet.Formatter.V5 +{ + public class MqttV500PacketEncoder + { + private readonly MqttPacketWriter _packetWriter = new MqttPacketWriter(); + + public ArraySegment Encode(MqttBasePacket packet) + { + if (packet == null) throw new ArgumentNullException(nameof(packet)); + + // Leave enough head space for max header size (fixed + 4 variable remaining length = 5 bytes) + _packetWriter.Reset(); + _packetWriter.Seek(5); + + var fixedHeader = EncodePacket(packet, _packetWriter); + var remainingLength = (uint)(_packetWriter.Length - 5); + + var remainingLengthBuffer = MqttPacketWriter.EncodeVariableByteInteger(remainingLength); + + var headerSize = 1 + remainingLengthBuffer.Count; + var headerOffset = 5 - headerSize; + + // Position cursor on correct offset on beginning of array (has leading 0x0) + _packetWriter.Seek(headerOffset); + _packetWriter.Write(fixedHeader); + _packetWriter.Write(remainingLengthBuffer.Array, remainingLengthBuffer.Offset, remainingLengthBuffer.Count); + + var buffer = _packetWriter.GetBuffer(); + return new ArraySegment(buffer, headerOffset, _packetWriter.Length - headerOffset); + } + + public void FreeBuffer() + { + _packetWriter.FreeBuffer(); + } + + private static byte EncodePacket(MqttBasePacket packet, MqttPacketWriter packetWriter) + { + switch (packet) + { + case MqttConnectPacket connectPacket: return EncodeConnectPacket(connectPacket, packetWriter); + case MqttConnAckPacket connAckPacket: return EncodeConnAckPacket(connAckPacket, packetWriter); + case MqttDisconnectPacket disconnectPacket: return EncodeDisconnectPacket(disconnectPacket, packetWriter); + case MqttPingReqPacket _: return EncodePingReqPacket(); + case MqttPingRespPacket _: return EncodePingRespPacket(); + case MqttPublishPacket publishPacket: return EncodePublishPacket(publishPacket, packetWriter); + case MqttPubAckPacket pubAckPacket: return EncodePubAckPacket(pubAckPacket, packetWriter); + case MqttPubRecPacket pubRecPacket: return EncodePubRecPacket(pubRecPacket, packetWriter); + case MqttPubRelPacket pubRelPacket: return EncodePubRelPacket(pubRelPacket, packetWriter); + case MqttPubCompPacket pubCompPacket: return EncodePubCompPacket(pubCompPacket, packetWriter); + case MqttSubscribePacket subscribePacket: return EncodeSubscribePacket(subscribePacket, packetWriter); + case MqttSubAckPacket subAckPacket: return EncodeSubAckPacket(subAckPacket, packetWriter); + case MqttUnsubscribePacket unsubscribePacket: return EncodeUnsubscribePacket(unsubscribePacket, packetWriter); + case MqttUnsubAckPacket unsubAckPacket: return EncodeUnsubAckPacket(unsubAckPacket, packetWriter); + case MqttAuthPacket authPacket: return EncodeAuthPacket(authPacket, packetWriter); + + default: throw new MqttProtocolViolationException("Packet type invalid."); + } + } + + private static byte EncodeConnectPacket(MqttConnectPacket packet, MqttPacketWriter packetWriter) + { + if (packet == null) throw new ArgumentNullException(nameof(packet)); + if (packetWriter == null) throw new ArgumentNullException(nameof(packetWriter)); + + if (string.IsNullOrEmpty(packet.ClientId) && !packet.CleanSession) + { + throw new MqttProtocolViolationException("CleanSession must be set if ClientId is empty [MQTT-3.1.3-7]."); + } + + packetWriter.WriteWithLengthPrefix("MQTT"); + packetWriter.Write(5); // [3.1.2.2 Protocol Version] + + byte connectFlags = 0x0; + if (packet.CleanSession) + { + connectFlags |= 0x2; + } + + if (packet.WillMessage != null) + { + connectFlags |= 0x4; + connectFlags |= (byte)((byte)packet.WillMessage.QualityOfServiceLevel << 3); + + if (packet.WillMessage.Retain) + { + connectFlags |= 0x20; + } + } + + if (packet.Password != null && packet.Username == null) + { + throw new MqttProtocolViolationException("If the User Name Flag is set to 0, the Password Flag MUST be set to 0 [MQTT-3.1.2-22]."); + } + + if (packet.Password != null) + { + connectFlags |= 0x40; + } + + if (packet.Username != null) + { + connectFlags |= 0x80; + } + + packetWriter.Write(connectFlags); + packetWriter.Write(packet.KeepAlivePeriod); + + var propertiesWriter = new MqttV500PropertiesWriter(); + if (packet.Properties != null) + { + propertiesWriter.WriteWillDelayInterval(packet.Properties.WillDelayInterval); + propertiesWriter.WriteSessionExpiryInterval(packet.Properties.SessionExpiryInterval); + propertiesWriter.WriteAuthenticationMethod(packet.Properties.AuthenticationMethod); + propertiesWriter.WriteAuthenticationData(packet.Properties.AuthenticationData); + propertiesWriter.WriteRequestProblemInformation(packet.Properties.RequestProblemInformation); + propertiesWriter.WriteRequestResponseInformation(packet.Properties.RequestResponseInformation); + propertiesWriter.WriteReceiveMaximum(packet.Properties.ReceiveMaximum); + propertiesWriter.WriteTopicAlias(packet.Properties.TopicAliasMaximum); + propertiesWriter.WriteMaximumPacketSize(packet.Properties.MaximumPacketSize); + propertiesWriter.WriteUserProperties(packet.Properties.UserProperties); + } + + propertiesWriter.WriteToPacket(packetWriter); + + packetWriter.WriteWithLengthPrefix(packet.ClientId); + + if (packet.WillMessage != null) + { + packetWriter.WriteWithLengthPrefix(packet.WillMessage.Topic); + packetWriter.WriteWithLengthPrefix(packet.WillMessage.Payload); + } + + if (packet.Username != null) + { + packetWriter.WriteWithLengthPrefix(packet.Username); + } + + if (packet.Password != null) + { + packetWriter.WriteWithLengthPrefix(packet.Password); + } + + return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.Connect); + } + + private static byte EncodeConnAckPacket(MqttConnAckPacket packet, MqttPacketWriter packetWriter) + { + if (packet == null) throw new ArgumentNullException(nameof(packet)); + if (packetWriter == null) throw new ArgumentNullException(nameof(packetWriter)); + + if (!packet.ReasonCode.HasValue) + { + ThrowReasonCodeNotSetException(); + } + + byte connectAcknowledgeFlags = 0x0; + if (packet.IsSessionPresent) + { + connectAcknowledgeFlags |= 0x1; + } + + packetWriter.Write(connectAcknowledgeFlags); + packetWriter.Write((byte)packet.ReasonCode.Value); + + var propertiesWriter = new MqttV500PropertiesWriter(); + if (packet.Properties != null) + { + propertiesWriter.WriteSessionExpiryInterval(packet.Properties.SessionExpiryInterval); + propertiesWriter.WriteAuthenticationMethod(packet.Properties.AuthenticationMethod); + propertiesWriter.WriteAuthenticationData(packet.Properties.AuthenticationData); + propertiesWriter.WriteRetainAvailable(packet.Properties.RetainAvailable); + propertiesWriter.WriteReceiveMaximum(packet.Properties.ReceiveMaximum); + propertiesWriter.WriteAssignedClientIdentifier(packet.Properties.AssignedClientIdentifier); + propertiesWriter.WriteTopicAliasMaximum(packet.Properties.TopicAliasMaximum); + propertiesWriter.WriteReasonString(packet.Properties.ReasonString); + propertiesWriter.WriteMaximumPacketSize(packet.Properties.MaximumPacketSize); + propertiesWriter.WriteWildcardSubscriptionAvailable(packet.Properties.WildcardSubscriptionAvailable); + propertiesWriter.WriteSubscriptionIdentifiersAvailable(packet.Properties.SubscriptionIdentifiersAvailable); + propertiesWriter.WriteSharedSubscriptionAvailable(packet.Properties.SharedSubscriptionAvailable); + propertiesWriter.WriteServerKeepAlive(packet.Properties.ServerKeepAlive); + propertiesWriter.WriteResponseInformation(packet.Properties.ResponseInformation); + propertiesWriter.WriteServerReference(packet.Properties.ServerReference); + propertiesWriter.WriteUserProperties(packet.Properties.UserProperties); + } + + propertiesWriter.WriteToPacket(packetWriter); + + return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.ConnAck); + } + + private static byte EncodePublishPacket(MqttPublishPacket packet, MqttPacketWriter packetWriter) + { + if (packet == null) throw new ArgumentNullException(nameof(packet)); + if (packetWriter == null) throw new ArgumentNullException(nameof(packetWriter)); + + if (packet.QualityOfServiceLevel == 0 && packet.Dup) + { + throw new MqttProtocolViolationException("Dup flag must be false for QoS 0 packets [MQTT-3.3.1-2]."); + } + + packetWriter.WriteWithLengthPrefix(packet.Topic); + + if (packet.QualityOfServiceLevel > MqttQualityOfServiceLevel.AtMostOnce) + { + if (!packet.PacketIdentifier.HasValue) + { + throw new MqttProtocolViolationException("Publish packet has no packet identifier."); + } + + packetWriter.Write(packet.PacketIdentifier.Value); + } + else + { + if (packet.PacketIdentifier > 0) + { + throw new MqttProtocolViolationException("Packet identifier must be 0 if QoS == 0 [MQTT-2.3.1-5]."); + } + } + + var propertiesWriter = new MqttV500PropertiesWriter(); + if (packet.Properties != null) + { + propertiesWriter.WritePayloadFormatIndicator(packet.Properties.PayloadFormatIndicator); + propertiesWriter.WriteMessageExpiryInterval(packet.Properties.MessageExpiryInterval); + propertiesWriter.WriteTopicAlias(packet.Properties.TopicAlias); + propertiesWriter.WriteResponseTopic(packet.Properties.ResponseTopic); + propertiesWriter.WriteCorrelationData(packet.Properties.CorrelationData); + propertiesWriter.WriteSubscriptionIdentifier(packet.Properties.SubscriptionIdentifier); + propertiesWriter.WriteContentType(packet.Properties.ContentType); + propertiesWriter.WriteUserProperties(packet.Properties.UserProperties); + } + + propertiesWriter.WriteToPacket(packetWriter); + + if (packet.Payload?.Length > 0) + { + packetWriter.Write(packet.Payload, 0, packet.Payload.Length); + } + + byte fixedHeader = 0; + + if (packet.Retain) + { + fixedHeader |= 0x01; + } + + fixedHeader |= (byte)((byte)packet.QualityOfServiceLevel << 1); + + if (packet.Dup) + { + fixedHeader |= 0x08; + } + + return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.Publish, fixedHeader); + } + + private static byte EncodePubAckPacket(MqttPubAckPacket packet, MqttPacketWriter packetWriter) + { + if (packet == null) throw new ArgumentNullException(nameof(packet)); + if (packetWriter == null) throw new ArgumentNullException(nameof(packetWriter)); + + if (!packet.PacketIdentifier.HasValue) + { + throw new MqttProtocolViolationException("PubAck packet has no packet identifier."); + } + + if (!packet.ReasonCode.HasValue) + { + throw new MqttProtocolViolationException("PubAck packet must contain a connect reason."); + } + + packetWriter.Write(packet.PacketIdentifier.Value); + + var propertiesWriter = new MqttV500PropertiesWriter(); + if (packet.Properties != null) + { + propertiesWriter.WriteReasonString(packet.Properties.ReasonString); + propertiesWriter.WriteUserProperties(packet.Properties.UserProperties); + } + + if (packetWriter.Length > 0 || packet.ReasonCode.Value != MqttPubAckReasonCode.Success) + { + packetWriter.Write((byte)packet.ReasonCode.Value); + propertiesWriter.WriteToPacket(packetWriter); + } + + return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.PubAck); + } + + private static byte EncodePubRecPacket(MqttPubRecPacket packet, MqttPacketWriter packetWriter) + { + ThrowIfPacketIdentifierIsInvalid(packet); + + if (!packet.ReasonCode.HasValue) + { + ThrowReasonCodeNotSetException(); + } + + packetWriter.Write(packet.PacketIdentifier.Value); + packetWriter.Write((byte)packet.ReasonCode.Value); + + var propertiesWriter = new MqttV500PropertiesWriter(); + if (packet.Properties != null) + { + propertiesWriter.WriteReasonString(packet.Properties.ReasonString); + propertiesWriter.WriteUserProperties(packet.Properties.UserProperties); + } + + if (packetWriter.Length > 0 || packet.ReasonCode.Value != MqttPubRecReasonCode.Success) + { + packetWriter.Write((byte)packet.ReasonCode.Value); + propertiesWriter.WriteToPacket(packetWriter); + } + + return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.PubRec); + } + + private static byte EncodePubRelPacket(MqttPubRelPacket packet, MqttPacketWriter packetWriter) + { + ThrowIfPacketIdentifierIsInvalid(packet); + + if (!packet.ReasonCode.HasValue) + { + ThrowReasonCodeNotSetException(); + } + + packetWriter.Write(packet.PacketIdentifier.Value); + packetWriter.Write((byte)packet.ReasonCode.Value); + + var propertiesWriter = new MqttV500PropertiesWriter(); + if (packet.Properties != null) + { + propertiesWriter.WriteReasonString(packet.Properties.ReasonString); + propertiesWriter.WriteUserProperties(packet.Properties.UserProperties); + } + + if (packetWriter.Length > 0 || packet.ReasonCode.Value != MqttPubRelReasonCode.Success) + { + packetWriter.Write((byte)packet.ReasonCode.Value); + propertiesWriter.WriteToPacket(packetWriter); + } + + return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.PubRel, 0x02); + } + + private static byte EncodePubCompPacket(MqttPubCompPacket packet, MqttPacketWriter packetWriter) + { + ThrowIfPacketIdentifierIsInvalid(packet); + + if (!packet.ReasonCode.HasValue) + { + ThrowReasonCodeNotSetException(); + } + + packetWriter.Write(packet.PacketIdentifier.Value); + packetWriter.Write((byte)packet.ReasonCode.Value); + + var propertiesWriter = new MqttV500PropertiesWriter(); + if (packet.Properties != null) + { + propertiesWriter.WriteReasonString(packet.Properties.ReasonString); + propertiesWriter.WriteUserProperties(packet.Properties.UserProperties); + } + + if (packetWriter.Length > 0 || packet.ReasonCode.Value != MqttPubCompReasonCode.Success) + { + packetWriter.Write((byte)packet.ReasonCode.Value); + propertiesWriter.WriteToPacket(packetWriter); + } + + return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.PubComp); + } + + private static byte EncodeSubscribePacket(MqttSubscribePacket packet, MqttPacketWriter packetWriter) + { + if (!packet.TopicFilters.Any()) throw new MqttProtocolViolationException("At least one topic filter must be set [MQTT-3.8.3-3]."); + + ThrowIfPacketIdentifierIsInvalid(packet); + + packetWriter.Write(packet.PacketIdentifier.Value); + + var propertiesWriter = new MqttV500PropertiesWriter(); + if (packet.Properties != null) + { + propertiesWriter.WriteSubscriptionIdentifier(packet.Properties.SubscriptionIdentifier); + propertiesWriter.WriteUserProperties(packet.Properties.UserProperties); + } + + propertiesWriter.WriteToPacket(packetWriter); + + if (packet.TopicFilters?.Count > 0) + { + foreach (var topicFilter in packet.TopicFilters) + { + packetWriter.WriteWithLengthPrefix(topicFilter.Topic); + + var options = (byte)topicFilter.QualityOfServiceLevel; + + if (topicFilter.NoLocal == true) + { + options |= 1 << 2; + } + + if (topicFilter.RetainAsPublished == true) + { + options |= 1 << 3; + } + + if (topicFilter.RetainHandling.HasValue) + { + options |= (byte)((byte)topicFilter.RetainHandling.Value << 4); + } + + packetWriter.Write(options); + } + } + + return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.Subscribe, 0x02); + } + + private static byte EncodeSubAckPacket(MqttSubAckPacket packet, MqttPacketWriter packetWriter) + { + ThrowIfPacketIdentifierIsInvalid(packet); + + if (packet.ReasonCodes == null || !packet.ReasonCodes.Any()) + { + throw new MqttProtocolViolationException("At least one reason code must be set[MQTT - 3.8.3 - 3]."); + } + + packetWriter.Write(packet.PacketIdentifier.Value); + + var propertiesWriter = new MqttV500PropertiesWriter(); + if (packet.Properties != null) + { + propertiesWriter.WriteReasonString(packet.Properties.ReasonString); + propertiesWriter.WriteUserProperties(packet.Properties.UserProperties); + } + + propertiesWriter.WriteToPacket(packetWriter); + + foreach (var reasonCode in packet.ReasonCodes) + { + packetWriter.Write((byte)reasonCode); + } + + return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.SubAck); + } + + private static byte EncodeUnsubscribePacket(MqttUnsubscribePacket packet, MqttPacketWriter packetWriter) + { + if (!packet.TopicFilters.Any()) throw new MqttProtocolViolationException("At least one topic filter must be set [MQTT-3.10.3-2]."); + + ThrowIfPacketIdentifierIsInvalid(packet); + + packetWriter.Write(packet.PacketIdentifier.Value); + + var propertiesWriter = new MqttV500PropertiesWriter(); + if (packet.Properties != null) + { + propertiesWriter.WriteUserProperties(packet.Properties.UserProperties); + } + + propertiesWriter.WriteToPacket(packetWriter); + + foreach (var topicFilter in packet.TopicFilters) + { + packetWriter.WriteWithLengthPrefix(topicFilter); + } + + return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.Unsubscibe, 0x02); + } + + private static byte EncodeUnsubAckPacket(MqttUnsubAckPacket packet, MqttPacketWriter packetWriter) + { + ThrowIfPacketIdentifierIsInvalid(packet); + + if (packet.ReasonCodes == null || !packet.ReasonCodes.Any()) + { + throw new MqttProtocolViolationException("At least one reason code must be set[MQTT - 3.8.3 - 3]."); + } + + packetWriter.Write(packet.PacketIdentifier.Value); + + var propertiesWriter = new MqttV500PropertiesWriter(); + if (packet.Properties != null) + { + propertiesWriter.WriteReasonString(packet.Properties.ReasonString); + propertiesWriter.WriteUserProperties(packet.Properties.UserProperties); + } + + propertiesWriter.WriteToPacket(packetWriter); + + foreach (var reasonCode in packet.ReasonCodes) + { + packetWriter.Write((byte)reasonCode); + } + + return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.UnsubAck); + } + + private static byte EncodeDisconnectPacket(MqttDisconnectPacket packet, MqttPacketWriter packetWriter) + { + if (!packet.ReasonCode.HasValue) + { + ThrowReasonCodeNotSetException(); + } + + packetWriter.Write((byte)packet.ReasonCode.Value); + + var propertiesWriter = new MqttV500PropertiesWriter(); + if (packet.Properties != null) + { + propertiesWriter.WriteServerReference(packet.Properties.ServerReference); + propertiesWriter.WriteReasonString(packet.Properties.ReasonString); + propertiesWriter.WriteSessionExpiryInterval(packet.Properties.SessionExpiryInterval); + propertiesWriter.WriteUserProperties(packet.Properties.UserProperties); + } + + propertiesWriter.WriteToPacket(packetWriter); + + return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.Disconnect); + } + + private static byte EncodePingReqPacket() + { + return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.PingReq); + } + + private static byte EncodePingRespPacket() + { + return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.PingResp); + } + + private static byte EncodeAuthPacket(MqttAuthPacket packet, MqttPacketWriter packetWriter) + { + packetWriter.Write((byte)packet.ReasonCode); + + var propertiesWriter = new MqttV500PropertiesWriter(); + if (packet.Properties != null) + { + propertiesWriter.WriteAuthenticationMethod(packet.Properties.AuthenticationMethod); + propertiesWriter.WriteAuthenticationData(packet.Properties.AuthenticationData); + propertiesWriter.WriteReasonString(packet.Properties.ReasonString); + propertiesWriter.WriteUserProperties(packet.Properties.UserProperties); + } + + propertiesWriter.WriteToPacket(packetWriter); + + return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.Auth); + } + + private static void ThrowReasonCodeNotSetException() + { + throw new MqttProtocolViolationException("The ReasonCode must be set for MQTT version 5."); + } + + private static void ThrowIfPacketIdentifierIsInvalid(IMqttPacketWithIdentifier packet) + { + if (!packet.PacketIdentifier.HasValue) + { + throw new MqttProtocolViolationException($"Packet identifier is not set for {packet.GetType().Name}."); + } + } + } +} diff --git a/Source/MQTTnet/Formatter/V5/MqttV500PacketFormatter.cs b/Source/MQTTnet/Formatter/V5/MqttV500PacketFormatter.cs new file mode 100644 index 00000000..d397985d --- /dev/null +++ b/Source/MQTTnet/Formatter/V5/MqttV500PacketFormatter.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using MQTTnet.Adapter; +using MQTTnet.Packets; + +namespace MQTTnet.Formatter.V5 +{ + public class MqttV500PacketFormatter : IMqttPacketFormatter + { + private readonly MqttV500PacketEncoder _encoder = new MqttV500PacketEncoder(); + private readonly MqttV500PacketDecoder _decoder = new MqttV500PacketDecoder(); + + public IMqttDataConverter DataConverter { get; } = new MqttV500DataConverter(); + + public ArraySegment Encode(MqttBasePacket mqttPacket) + { + if (mqttPacket == null) throw new ArgumentNullException(nameof(mqttPacket)); + + return _encoder.Encode(mqttPacket); + } + + public MqttBasePacket Decode(ReceivedMqttPacket receivedMqttPacket) + { + if (receivedMqttPacket == null) throw new ArgumentNullException(nameof(receivedMqttPacket)); + + return _decoder.Decode(receivedMqttPacket); + } + + public void FreeBuffer() + { + _encoder.FreeBuffer(); + } + } +} diff --git a/Source/MQTTnet/Formatter/V5/MqttV500PropertiesReader.cs b/Source/MQTTnet/Formatter/V5/MqttV500PropertiesReader.cs new file mode 100644 index 00000000..c7f28dd4 --- /dev/null +++ b/Source/MQTTnet/Formatter/V5/MqttV500PropertiesReader.cs @@ -0,0 +1,196 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using MQTTnet.Exceptions; +using MQTTnet.Packets; +using MQTTnet.Protocol; + +namespace MQTTnet.Formatter.V5 +{ + public class MqttV500PropertiesReader + { + private readonly MqttPacketBodyReader _body; + private readonly uint _length; + private readonly ulong _targetOffset; + + public MqttV500PropertiesReader(MqttPacketBodyReader body) + { + _body = body ?? throw new ArgumentNullException(nameof(body)); + + if (!body.EndOfStream) + { + _length = body.ReadVariableLengthInteger(); + } + + _targetOffset = body.Offset + _length; + } + + public MqttPropertyId CurrentPropertyId { get; private set; } + + public bool MoveNext() + { + if (_length == 0) + { + return false; + } + + if (_body.Offset >= _targetOffset) + { + return false; + } + + CurrentPropertyId = (MqttPropertyId)_body.ReadByte(); + return true; + } + + public void FillUserProperties(List userProperties) + { + if (userProperties == null) throw new ArgumentNullException(nameof(userProperties)); + + var userPropertiesLength = _body.ReadVariableLengthInteger(); + if (userPropertiesLength == 0) + { + return; + } + + var targetPosition = _body.Offset + userPropertiesLength; + while (_body.Offset < targetPosition) + { + var name = _body.ReadStringWithLengthPrefix(); + var value = _body.ReadStringWithLengthPrefix(); + + userProperties.Add(new MqttUserProperty(name, value)); + } + } + + public string ReadReasonString() + { + return _body.ReadStringWithLengthPrefix(); + } + + public string ReadAuthenticationMethod() + { + return _body.ReadStringWithLengthPrefix(); + } + + public byte[] ReadAuthenticationData() + { + return _body.ReadWithLengthPrefix().ToArray(); + } + + public bool? ReadRetainAvailable() + { + return _body.ReadBoolean(); + } + + public uint? ReadSessionExpiryInterval() + { + return _body.ReadFourByteInteger(); + } + + public ushort? ReadReceiveMaximum() + { + return _body.ReadTwoByteInteger(); + } + + public string ReadAssignedClientIdentifier() + { + return _body.ReadStringWithLengthPrefix(); + } + + public string ReadServerReference() + { + return _body.ReadStringWithLengthPrefix(); + } + + public ushort? ReadTopicAliasMaximum() + { + return _body.ReadTwoByteInteger(); + } + + public uint? ReadMaximumPacketSize() + { + return _body.ReadFourByteInteger(); + } + + public ushort? ReadServerKeepAlive() + { + return _body.ReadTwoByteInteger(); + } + + public string ReadResponseInformation() + { + return _body.ReadStringWithLengthPrefix(); + } + + public bool? ReadSharedSubscriptionAvailable() + { + return _body.ReadBoolean(); + } + + public bool? ReadSubscriptionIdentifiersAvailable() + { + return _body.ReadBoolean(); + } + + public bool? ReadWildcardSubscriptionAvailable() + { + return _body.ReadBoolean(); + } + + public uint? ReadSubscriptionIdentifier() + { + return _body.ReadVariableLengthInteger(); + } + + public MqttPayloadFormatIndicator? ReadPayloadFormatIndicator() + { + return (MqttPayloadFormatIndicator)_body.ReadByte(); + } + + public uint? ReadMessageExpiryInterval() + { + return _body.ReadFourByteInteger(); + } + + public ushort? ReadTopicAlias() + { + return _body.ReadTwoByteInteger(); + } + + public string ReadResponseTopic() + { + return _body.ReadStringWithLengthPrefix(); + } + + public byte[] ReadCorrelationData() + { + return _body.ReadWithLengthPrefix().ToArray(); + } + + public string ReadContentType() + { + return _body.ReadStringWithLengthPrefix(); + } + + public uint? ReadWillDelayInterval() + { + return _body.ReadFourByteInteger(); + } + + public bool? RequestResponseInformation() + { + return _body.ReadBoolean(); + } + + public bool? RequestProblemInformation() + { + return _body.ReadBoolean(); + } + + public void ThrowInvalidPropertyIdException(Type type) + { + throw new MqttProtocolViolationException($"Property ID '{CurrentPropertyId}' is not supported for package type '{type.Name}'."); + } + } +} diff --git a/Source/MQTTnet/Formatter/V5/MqttV500PropertiesWriter.cs b/Source/MQTTnet/Formatter/V5/MqttV500PropertiesWriter.cs new file mode 100644 index 00000000..e10790ce --- /dev/null +++ b/Source/MQTTnet/Formatter/V5/MqttV500PropertiesWriter.cs @@ -0,0 +1,238 @@ +using System; +using System.Collections.Generic; +using MQTTnet.Packets; +using MQTTnet.Protocol; + +namespace MQTTnet.Formatter.V5 +{ + public class MqttV500PropertiesWriter + { + private readonly MqttPacketWriter _packetWriter = new MqttPacketWriter(); + + public void WriteUserProperties(List userProperties) + { + if (userProperties == null || userProperties.Count == 0) + { + return; + } + + var propertyWriter = new MqttPacketWriter(); + foreach (var property in userProperties) + { + propertyWriter.WriteWithLengthPrefix(property.Name); + propertyWriter.WriteWithLengthPrefix(property.Value); + } + + _packetWriter.Write((byte)MqttPropertyId.UserProperty); + _packetWriter.WriteVariableLengthInteger((uint)propertyWriter.Length); + _packetWriter.Write(propertyWriter); + } + + public void WriteCorrelationData(byte[] value) + { + Write(MqttPropertyId.CorrelationData, value); + } + + public void WriteAuthenticationData(byte[] value) + { + Write(MqttPropertyId.AuthenticationData, value); + } + + public void WriteReasonString(string value) + { + Write(MqttPropertyId.ReasonString, value); + } + + public void WriteResponseTopic(string value) + { + Write(MqttPropertyId.ResponseTopic, value); + } + + public void WriteContentType(string value) + { + Write(MqttPropertyId.ContentType, value); + } + + public void WriteServerReference(string value) + { + Write(MqttPropertyId.ServerReference, value); + } + + public void WriteAuthenticationMethod(string value) + { + Write(MqttPropertyId.AuthenticationMethod, value); + } + + public void WriteToPacket(MqttPacketWriter packetWriter) + { + if (packetWriter == null) throw new ArgumentNullException(nameof(packetWriter)); + + packetWriter.WriteVariableLengthInteger((uint)_packetWriter.Length); + packetWriter.Write(_packetWriter); + } + + public void WriteSessionExpiryInterval(uint? value) + { + WriteAsFourByteInteger(MqttPropertyId.SessionExpiryInterval, value); + } + + public void WriteSubscriptionIdentifier(uint? value) + { + WriteAsVariableLengthInteger(MqttPropertyId.SubscriptionIdentifier, value); + } + + public void WriteTopicAlias(ushort? value) + { + Write(MqttPropertyId.TopicAlias, value); + } + + public void WriteMessageExpiryInterval(uint? value) + { + WriteAsFourByteInteger(MqttPropertyId.MessageExpiryInterval, value); + } + + public void WritePayloadFormatIndicator(MqttPayloadFormatIndicator? value) + { + if (!value.HasValue) + { + return; + } + + Write(MqttPropertyId.PayloadFormatIndicator, (byte)value.Value); + } + + public void WriteWillDelayInterval(uint? value) + { + WriteAsFourByteInteger(MqttPropertyId.WillDelayInterval, value); + } + + public void WriteRequestProblemInformation(bool? value) + { + Write(MqttPropertyId.RequestProblemInformation, value); + } + + public void WriteRequestResponseInformation(bool? value) + { + Write(MqttPropertyId.RequestResponseInformation, value); + } + + public void WriteReceiveMaximum(ushort? value) + { + Write(MqttPropertyId.RequestResponseInformation, value); + } + + public void WriteMaximumPacketSize(uint? value) + { + WriteAsFourByteInteger(MqttPropertyId.MaximumPacketSize, value); + } + + public void WriteRetainAvailable(bool? value) + { + Write(MqttPropertyId.RetainAvailable, value); + } + + public void WriteAssignedClientIdentifier(string value) + { + Write(MqttPropertyId.AssignedClientIdentifer, value); + } + + public void WriteTopicAliasMaximum(ushort? value) + { + Write(MqttPropertyId.TopicAliasMaximum, value); + } + + public void WriteWildcardSubscriptionAvailable(bool? value) + { + Write(MqttPropertyId.WildcardSubscriptionAvailable, value); + } + + public void WriteSubscriptionIdentifiersAvailable(bool? value) + { + Write(MqttPropertyId.SubscriptionIdentifiersAvailable, value); + } + + public void WriteSharedSubscriptionAvailable(bool? value) + { + Write(MqttPropertyId.SharedSubscriptionAvailable, value); + } + + public void WriteServerKeepAlive(ushort? value) + { + Write(MqttPropertyId.ServerKeepAlive, value); + } + + public void WriteResponseInformation(string value) + { + Write(MqttPropertyId.ResponseInformation, value); + } + + private void Write(MqttPropertyId id, bool? value) + { + if (!value.HasValue) + { + return; + } + + _packetWriter.Write((byte)id); + _packetWriter.Write(value.Value ? (byte)0x1 : (byte)0x0); + } + + private void Write(MqttPropertyId id, ushort? value) + { + if (!value.HasValue) + { + return; + } + + _packetWriter.Write((byte)id); + _packetWriter.Write(value.Value); + } + + private void WriteAsVariableLengthInteger(MqttPropertyId id, uint? value) + { + if (!value.HasValue) + { + return; + } + + _packetWriter.Write((byte)id); + _packetWriter.WriteVariableLengthInteger(value.Value); + } + + private void WriteAsFourByteInteger(MqttPropertyId id, uint? value) + { + if (!value.HasValue) + { + return; + } + + _packetWriter.Write((byte)id); + _packetWriter.Write((byte)(value.Value >> 24)); + _packetWriter.Write((byte)(value.Value >> 16)); + _packetWriter.Write((byte)(value.Value >> 8)); + _packetWriter.Write((byte)value.Value); + } + + private void Write(MqttPropertyId id, string value) + { + if (value == null) + { + return; + } + + _packetWriter.Write((byte)id); + _packetWriter.WriteWithLengthPrefix(value); + } + + private void Write(MqttPropertyId id, byte[] value) + { + if (value == null) + { + return; + } + + _packetWriter.Write((byte)id); + _packetWriter.WriteWithLengthPrefix(value); + } + } +} diff --git a/Source/MQTTnet/Formatter/V500/MqttV500PacketDecoder.cs b/Source/MQTTnet/Formatter/V500/MqttV500PacketDecoder.cs deleted file mode 100644 index c0b35313..00000000 --- a/Source/MQTTnet/Formatter/V500/MqttV500PacketDecoder.cs +++ /dev/null @@ -1,135 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using MQTTnet.Exceptions; -using MQTTnet.Packets; -using MQTTnet.Protocol; - -namespace MQTTnet.Formatter.V500 -{ - public class MqttV500PacketDecoder - { - public MqttBasePacket DecodeConnAckPacket(MqttPacketBodyReader body) - { - if (body == null) throw new ArgumentNullException(nameof(body)); - - ThrowIfBodyIsEmpty(body); - - var packet = new MqttConnAckPacket(); - - var acknowledgeFlags = body.ReadByte(); - - packet.IsSessionPresent = (acknowledgeFlags & 0x1) > 0; - packet.ConnectReturnCode = (MqttConnectReturnCode)body.ReadByte(); - packet.ReasonCode = (MqttConnectReasonCode)body.ReadByte(); - - var propertiesLength = body.ReadVariableLengthInteger(); - if (propertiesLength > 0) - { - packet.Properties = new MqttConnAckPacketProperties(); - - while (!body.EndOfStream) - { - var propertyID = (MqttPropertyID)body.ReadByte(); - - if (propertyID == MqttPropertyID.SessionExpiryInterval) - { - packet.Properties.SessionExpiryInterval = body.ReadFourByteInteger(); - } - else if (propertyID == MqttPropertyID.AuthenticationMethod) - { - packet.Properties.AuthenticationMethod = body.ReadStringWithLengthPrefix(); - } - else if (propertyID == MqttPropertyID.AuthenticationData) - { - packet.Properties.AuthenticationData = body.ReadWithLengthPrefix().ToArray(); - } - else if (propertyID == MqttPropertyID.RetainAvailable) - { - packet.Properties.RetainAvailable = body.ReadBoolean(); - } - else if (propertyID == MqttPropertyID.ReceiveMaximum) - { - packet.Properties.ReceiveMaximum = body.ReadTwoByteInteger(); - } - else if (propertyID == MqttPropertyID.AssignedClientIdentifer) - { - packet.Properties.AssignedClientIdentifier = body.ReadStringWithLengthPrefix(); - } - else if (propertyID == MqttPropertyID.TopicAliasMaximum) - { - packet.Properties.TopicAliasMaximum = body.ReadTwoByteInteger(); - } - else if (propertyID == MqttPropertyID.ReasonString) - { - packet.Properties.ReasonString = body.ReadStringWithLengthPrefix(); - } - else if (propertyID == MqttPropertyID.MaximumPacketSize) - { - packet.Properties.MaximumPacketSize = body.ReadFourByteInteger(); - } - else if (propertyID == MqttPropertyID.WildcardSubscriptionAvailable) - { - packet.Properties.WildcardSubscriptionAvailable = body.ReadBoolean(); - } - else if (propertyID == MqttPropertyID.SubscriptionIdentifierAvailable) - { - packet.Properties.SubscriptionIdentifiersAvailable = body.ReadBoolean(); - } - else if (propertyID == MqttPropertyID.SharedSubscriptionAvailable) - { - packet.Properties.SharedSubscriptionAvailable = body.ReadBoolean(); - } - else if (propertyID == MqttPropertyID.ServerKeepAlive) - { - packet.Properties.ServerKeepAlive = body.ReadTwoByteInteger(); - } - else if (propertyID == MqttPropertyID.ResponseInformation) - { - packet.Properties.ResponseInformation = body.ReadStringWithLengthPrefix(); - } - else if (propertyID == MqttPropertyID.ServerReference) - { - packet.Properties.ServerReference = body.ReadStringWithLengthPrefix(); - } - else if (propertyID == MqttPropertyID.UserProperty) - { - packet.Properties.UserProperties = ReadUserProperties(body); - } - } - } - - return packet; - } - - // ReSharper disable once ParameterOnlyUsedForPreconditionCheck.Local - private static void ThrowIfBodyIsEmpty(MqttPacketBodyReader body) - { - if (body == null || body.Length == 0) - { - throw new MqttProtocolViolationException("Data from the body is required but not present."); - } - } - - private static List ReadUserProperties(MqttPacketBodyReader reader) - { - var userPropertiesLength = reader.ReadVariableLengthInteger(); - if (userPropertiesLength == 0) - { - return new List(); - } - - var userProperties = new List(); - var targetPosition = reader.Offset + userPropertiesLength; - while (reader.Offset < targetPosition) - { - var name = reader.ReadStringWithLengthPrefix(); - var value = reader.ReadStringWithLengthPrefix(); - - userProperties.Add(new MqttUserProperty(name, value)); - } - - return userProperties; - } - } -} diff --git a/Source/MQTTnet/Formatter/V500/MqttV500PacketEncoder.cs b/Source/MQTTnet/Formatter/V500/MqttV500PacketEncoder.cs deleted file mode 100644 index cb4d2a61..00000000 --- a/Source/MQTTnet/Formatter/V500/MqttV500PacketEncoder.cs +++ /dev/null @@ -1,223 +0,0 @@ -using System; -using MQTTnet.Exceptions; -using MQTTnet.Packets; -using MQTTnet.Protocol; - -namespace MQTTnet.Formatter.V500 -{ - public class MqttV500PacketEncoder - { - public byte EncodeConnectPacket(MqttConnectPacket packet, MqttPacketWriter packetWriter) - { - if (packet == null) throw new ArgumentNullException(nameof(packet)); - if (packetWriter == null) throw new ArgumentNullException(nameof(packetWriter)); - - if (string.IsNullOrEmpty(packet.ClientId) && !packet.CleanSession) - { - throw new MqttProtocolViolationException("CleanSession must be set if ClientId is empty [MQTT-3.1.3-7]."); - } - - packetWriter.WriteWithLengthPrefix("MQTT"); - packetWriter.Write(5); // [3.1.2.2 Protocol Version] - - byte connectFlags = 0x0; - if (packet.CleanSession) - { - connectFlags |= 0x2; - } - - if (packet.WillMessage != null) - { - connectFlags |= 0x4; - connectFlags |= (byte)((byte)packet.WillMessage.QualityOfServiceLevel << 3); - - if (packet.WillMessage.Retain) - { - connectFlags |= 0x20; - } - } - - if (packet.Password != null && packet.Username == null) - { - throw new MqttProtocolViolationException("If the User Name Flag is set to 0, the Password Flag MUST be set to 0 [MQTT-3.1.2-22]."); - } - - if (packet.Password != null) - { - connectFlags |= 0x40; - } - - if (packet.Username != null) - { - connectFlags |= 0x80; - } - - packetWriter.Write(connectFlags); - packetWriter.Write(packet.KeepAlivePeriod); - - var propertiesWriter = new MqttV500PropertiesWriter(); - if (packet.Properties != null) - { - propertiesWriter.WriteAsFourByteInteger(MqttPropertyID.WillDelayInterval, packet.Properties.WillDelayInterval); - propertiesWriter.WriteAsFourByteInteger(MqttPropertyID.SessionExpiryInterval, packet.Properties.SessionExpiryInterval); - propertiesWriter.Write(MqttPropertyID.AuthenticationMethod, packet.Properties.AuthenticationMethod); - propertiesWriter.Write(MqttPropertyID.AuthenticationData, packet.Properties.AuthenticationData); - propertiesWriter.Write(MqttPropertyID.RequestProblemInformation, packet.Properties.RequestProblemInformation); - propertiesWriter.Write(MqttPropertyID.RequestResponseInformation, packet.Properties.RequestResponseInformation); - propertiesWriter.Write(MqttPropertyID.ReceiveMaximum, packet.Properties.ReceiveMaximum); - propertiesWriter.Write(MqttPropertyID.TopicAlias, packet.Properties.TopicAliasMaximum); - propertiesWriter.WriteAsFourByteInteger(MqttPropertyID.MaximumPacketSize, packet.Properties.MaximumPacketSize); - propertiesWriter.WriteUserProperties(packet.Properties.UserProperties); - propertiesWriter.WriteToPacket(packetWriter); - } - - packetWriter.WriteWithLengthPrefix(packet.ClientId); - - if (packet.WillMessage != null) - { - packetWriter.WriteWithLengthPrefix(packet.WillMessage.Topic); - packetWriter.WriteWithLengthPrefix(packet.WillMessage.Payload); - } - - if (packet.Username != null) - { - packetWriter.WriteWithLengthPrefix(packet.Username); - } - - if (packet.Password != null) - { - packetWriter.WriteWithLengthPrefix(packet.Password); - } - - return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.Connect); - } - - public byte EncodeConnAckPacket(MqttConnAckPacket packet, MqttPacketWriter packetWriter) - { - if (packet == null) throw new ArgumentNullException(nameof(packet)); - if (packetWriter == null) throw new ArgumentNullException(nameof(packetWriter)); - - if (!packet.ReasonCode.HasValue) - { - throw new MqttProtocolViolationException("The ReasonCode must be set for MQTT version 5."); - } - - byte connectAcknowledgeFlags = 0x0; - if (packet.IsSessionPresent) - { - connectAcknowledgeFlags |= 0x1; - } - - packetWriter.Write(connectAcknowledgeFlags); - packetWriter.Write((byte)packet.ConnectReturnCode); - packetWriter.Write((byte)packet.ReasonCode.Value); - - var propertiesWriter = new MqttV500PropertiesWriter(); - if (packet.Properties != null) - { - propertiesWriter.WriteAsFourByteInteger(MqttPropertyID.SessionExpiryInterval, packet.Properties.SessionExpiryInterval); - propertiesWriter.Write(MqttPropertyID.AuthenticationMethod, packet.Properties.AuthenticationMethod); - propertiesWriter.Write(MqttPropertyID.AuthenticationData, packet.Properties.AuthenticationData); - propertiesWriter.Write(MqttPropertyID.RetainAvailable, packet.Properties.RetainAvailable); - propertiesWriter.Write(MqttPropertyID.ReceiveMaximum, packet.Properties.ReceiveMaximum); - propertiesWriter.Write(MqttPropertyID.AssignedClientIdentifer, packet.Properties.AssignedClientIdentifier); - propertiesWriter.Write(MqttPropertyID.TopicAliasMaximum, packet.Properties.TopicAliasMaximum); - propertiesWriter.Write(MqttPropertyID.ReasonString, packet.Properties.ReasonString); - propertiesWriter.WriteAsFourByteInteger(MqttPropertyID.MaximumPacketSize, packet.Properties.MaximumPacketSize); - propertiesWriter.Write(MqttPropertyID.WildcardSubscriptionAvailable, packet.Properties.WildcardSubscriptionAvailable); - propertiesWriter.Write(MqttPropertyID.SubscriptionIdentifierAvailable, packet.Properties.SubscriptionIdentifiersAvailable); - propertiesWriter.Write(MqttPropertyID.SharedSubscriptionAvailable, packet.Properties.SharedSubscriptionAvailable); - propertiesWriter.Write(MqttPropertyID.ServerKeepAlive, packet.Properties.ServerKeepAlive); - propertiesWriter.Write(MqttPropertyID.ResponseInformation, packet.Properties.ResponseInformation); - propertiesWriter.Write(MqttPropertyID.ServerReference, packet.Properties.ServerReference); - propertiesWriter.WriteUserProperties(packet.Properties.UserProperties); - propertiesWriter.WriteToPacket(packetWriter); - } - - return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.ConnAck); - } - - public byte EncodePublishPacket(MqttPublishPacket packet, MqttPacketWriter packetWriter) - { - if (packet == null) throw new ArgumentNullException(nameof(packet)); - if (packetWriter == null) throw new ArgumentNullException(nameof(packetWriter)); - - if (packet.QualityOfServiceLevel == 0 && packet.Dup) - { - throw new MqttProtocolViolationException("Dup flag must be false for QoS 0 packets [MQTT-3.3.1-2]."); - } - - packetWriter.WriteWithLengthPrefix(packet.Topic); - - if (packet.QualityOfServiceLevel > MqttQualityOfServiceLevel.AtMostOnce) - { - if (!packet.PacketIdentifier.HasValue) - { - throw new MqttProtocolViolationException("Publish packet has no packet identifier."); - } - - packetWriter.Write(packet.PacketIdentifier.Value); - } - else - { - if (packet.PacketIdentifier > 0) - { - throw new MqttProtocolViolationException("Packet identifier must be empty if QoS == 0 [MQTT-2.3.1-5]."); - } - } - - var propertiesWriter = new MqttV500PropertiesWriter(); - propertiesWriter.Write(MqttPropertyID.PayloadFormatIndicator, packet.Properties.PayloadFormatIndicator); - propertiesWriter.WriteAsFourByteInteger(MqttPropertyID.MessageExpiryInterval, packet.Properties.MessageExpiryInterval); - propertiesWriter.Write(MqttPropertyID.TopicAlias, packet.Properties.TopicAlias); - propertiesWriter.Write(MqttPropertyID.ResponseTopic, packet.Properties.ResponseTopic); - propertiesWriter.Write(MqttPropertyID.CorrelationData, packet.Properties.CorrelationData); - propertiesWriter.WriteAsVariableLengthInteger(MqttPropertyID.SubscriptionIdentifier, packet.Properties.SubscriptionIdentifier); - propertiesWriter.Write(MqttPropertyID.ContentType, packet.Properties.ContentType); - propertiesWriter.WriteUserProperties(packet.Properties.UserProperties); - propertiesWriter.WriteToPacket(packetWriter); - - if (packet.Payload?.Length > 0) - { - packetWriter.Write(packet.Payload, 0, packet.Payload.Length); - } - - byte fixedHeader = 0; - - if (packet.Retain) - { - fixedHeader |= 0x01; - } - - fixedHeader |= (byte)((byte)packet.QualityOfServiceLevel << 1); - - if (packet.Dup) - { - fixedHeader |= 0x08; - } - - return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.Publish, fixedHeader); - } - - public byte EncodePubAckPacket(MqttPubAckPacket packet, MqttPacketWriter packetWriter) - { - if (packet == null) throw new ArgumentNullException(nameof(packet)); - if (packetWriter == null) throw new ArgumentNullException(nameof(packetWriter)); - - if (!packet.PacketIdentifier.HasValue) - { - throw new MqttProtocolViolationException("PubAck packet has no packet identifier."); - } - - if (!packet.ReasonCode.HasValue) - { - throw new MqttProtocolViolationException("PubAck packet must contain a connect reason."); - } - - packetWriter.Write(packet.PacketIdentifier.Value); - packetWriter.Write((byte)packet.ReasonCode.Value); - - return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.PubAck); - } - } -} diff --git a/Source/MQTTnet/Formatter/V500/MqttV500PacketFormatter.cs b/Source/MQTTnet/Formatter/V500/MqttV500PacketFormatter.cs deleted file mode 100644 index f815129b..00000000 --- a/Source/MQTTnet/Formatter/V500/MqttV500PacketFormatter.cs +++ /dev/null @@ -1,56 +0,0 @@ -using System; -using System.Collections.Generic; -using MQTTnet.Formatter.V311; -using MQTTnet.Packets; - -namespace MQTTnet.Formatter.V500 -{ - public class MqttV500PacketFormatter : MqttV311PacketFormatter - { - private readonly MqttV500PacketEncoder _encoder = new MqttV500PacketEncoder(); - private readonly MqttV500PacketDecoder _decoder = new MqttV500PacketDecoder(); - - public override MqttPublishPacket ConvertApplicationMessageToPublishPacket(MqttApplicationMessage applicationMessage) - { - if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage)); - - return new MqttPublishPacket - { - Topic = applicationMessage.Topic, - Payload = applicationMessage.Payload, - QualityOfServiceLevel = applicationMessage.QualityOfServiceLevel, - Retain = applicationMessage.Retain, - Dup = false, - Properties = new MqttPublishPacketProperties - { - UserProperties = new List(applicationMessage.UserProperties) - } - }; - } - - protected override byte SerializeConnectPacket(MqttConnectPacket packet, MqttPacketWriter packetWriter) - { - return _encoder.EncodeConnectPacket(packet, packetWriter); - } - - protected override byte SerializeConnAckPacket(MqttConnAckPacket packet, MqttPacketWriter packetWriter) - { - return _encoder.EncodeConnAckPacket(packet, packetWriter); - } - - protected override MqttBasePacket DeserializeConnAckPacket(MqttPacketBodyReader body) - { - return _decoder.DecodeConnAckPacket(body); - } - - protected override byte SerializePublishPacket(MqttPublishPacket packet, MqttPacketWriter packetWriter) - { - return _encoder.EncodePublishPacket(packet, packetWriter); - } - - protected override byte SerializePubAckPacket(MqttPubAckPacket packet, MqttPacketWriter packetWriter) - { - return _encoder.EncodePubAckPacket(packet, packetWriter); - } - } -} diff --git a/Source/MQTTnet/Formatter/V500/MqttV500PropertiesWriter.cs b/Source/MQTTnet/Formatter/V500/MqttV500PropertiesWriter.cs deleted file mode 100644 index 1099227d..00000000 --- a/Source/MQTTnet/Formatter/V500/MqttV500PropertiesWriter.cs +++ /dev/null @@ -1,108 +0,0 @@ -using System; -using System.Collections.Generic; -using MQTTnet.Packets; -using MQTTnet.Protocol; - -namespace MQTTnet.Formatter.V500 -{ - public class MqttV500PropertiesWriter - { - private readonly MqttPacketWriter _packetWriter = new MqttPacketWriter(); - - public void Write(MqttPropertyID id, bool? value) - { - if (!value.HasValue) - { - return; - } - - _packetWriter.Write((byte)id); - _packetWriter.Write(value.Value ? (byte)0x1 : (byte)0x0); - } - - public void Write(MqttPropertyID id, ushort? value) - { - if (!value.HasValue) - { - return; - } - - _packetWriter.Write((byte)id); - _packetWriter.Write(value.Value); - } - - public void WriteAsVariableLengthInteger(MqttPropertyID id, uint? value) - { - if (!value.HasValue) - { - return; - } - - _packetWriter.Write((byte)id); - _packetWriter.WriteVariableLengthInteger(value.Value); - } - - public void WriteAsFourByteInteger(MqttPropertyID id, uint? value) - { - if (!value.HasValue) - { - return; - } - - _packetWriter.Write((byte)id); - _packetWriter.Write((byte)(value.Value >> 24)); - _packetWriter.Write((byte)(value.Value >> 16)); - _packetWriter.Write((byte)(value.Value >> 8)); - _packetWriter.Write((byte)value.Value); - } - - public void Write(MqttPropertyID id, string value) - { - if (value == null) - { - return; - } - - _packetWriter.Write((byte)id); - _packetWriter.WriteWithLengthPrefix(value); - } - - public void Write(MqttPropertyID id, byte[] value) - { - if (value == null) - { - return; - } - - _packetWriter.Write((byte)id); - _packetWriter.WriteWithLengthPrefix(value); - } - - public void WriteUserProperties(List userProperties) - { - if (userProperties == null || userProperties.Count == 0) - { - return; - } - - var propertyWriter = new MqttPacketWriter(); - foreach (var property in userProperties) - { - propertyWriter.WriteWithLengthPrefix(property.Name); - propertyWriter.WriteWithLengthPrefix(property.Value); - } - - _packetWriter.Write((byte)MqttPropertyID.UserProperty); - _packetWriter.WriteVariableLengthInteger((uint)propertyWriter.Length); - _packetWriter.Write(propertyWriter); - } - - public void WriteToPacket(MqttPacketWriter packetWriter) - { - if (packetWriter == null) throw new ArgumentNullException(nameof(packetWriter)); - - packetWriter.WriteVariableLengthInteger((uint)_packetWriter.Length); - packetWriter.Write(packetWriter); - } - } -} diff --git a/Source/MQTTnet/IApplicationMessagePublisher.cs b/Source/MQTTnet/IApplicationMessagePublisher.cs index c47893af..a7c0d4a7 100644 --- a/Source/MQTTnet/IApplicationMessagePublisher.cs +++ b/Source/MQTTnet/IApplicationMessagePublisher.cs @@ -1,9 +1,10 @@ using System.Threading.Tasks; +using MQTTnet.Client.Publishing; namespace MQTTnet { public interface IApplicationMessagePublisher { - Task PublishAsync(MqttApplicationMessage applicationMessage); + Task PublishAsync(MqttApplicationMessage applicationMessage); } } diff --git a/Source/MQTTnet/Implementations/MqttClientAdapterFactory.cs b/Source/MQTTnet/Implementations/MqttClientAdapterFactory.cs index 4f88c74d..0f5df6f5 100644 --- a/Source/MQTTnet/Implementations/MqttClientAdapterFactory.cs +++ b/Source/MQTTnet/Implementations/MqttClientAdapterFactory.cs @@ -1,6 +1,7 @@ using System; using MQTTnet.Adapter; using MQTTnet.Client; +using MQTTnet.Client.Options; using MQTTnet.Diagnostics; using MQTTnet.Formatter; diff --git a/Source/MQTTnet/Implementations/MqttTcpChannel.Uwp.cs b/Source/MQTTnet/Implementations/MqttTcpChannel.Uwp.cs index 6da9da24..fa20b08d 100644 --- a/Source/MQTTnet/Implementations/MqttTcpChannel.Uwp.cs +++ b/Source/MQTTnet/Implementations/MqttTcpChannel.Uwp.cs @@ -10,7 +10,7 @@ using Windows.Networking; using Windows.Networking.Sockets; using Windows.Security.Cryptography.Certificates; using MQTTnet.Channel; -using MQTTnet.Client; +using MQTTnet.Client.Options; using MQTTnet.Server; namespace MQTTnet.Implementations diff --git a/Source/MQTTnet/Implementations/MqttTcpChannel.cs b/Source/MQTTnet/Implementations/MqttTcpChannel.cs index 58eece6a..978f8b6f 100644 --- a/Source/MQTTnet/Implementations/MqttTcpChannel.cs +++ b/Source/MQTTnet/Implementations/MqttTcpChannel.cs @@ -8,7 +8,7 @@ using System.IO; using System.Linq; using System.Threading; using MQTTnet.Channel; -using MQTTnet.Client; +using MQTTnet.Client.Options; namespace MQTTnet.Implementations { diff --git a/Source/MQTTnet/Implementations/MqttWebSocketChannel.cs b/Source/MQTTnet/Implementations/MqttWebSocketChannel.cs index 53173d27..c6926dd8 100644 --- a/Source/MQTTnet/Implementations/MqttWebSocketChannel.cs +++ b/Source/MQTTnet/Implementations/MqttWebSocketChannel.cs @@ -5,7 +5,7 @@ using System.Security.Cryptography.X509Certificates; using System.Threading; using System.Threading.Tasks; using MQTTnet.Channel; -using MQTTnet.Client; +using MQTTnet.Client.Options; namespace MQTTnet.Implementations { @@ -155,9 +155,7 @@ namespace MQTTnet.Implementations if (!string.IsNullOrEmpty(_options.ProxyOptions.Username) && !string.IsNullOrEmpty(_options.ProxyOptions.Password)) { - var credentials = - new NetworkCredential(_options.ProxyOptions.Username, _options.ProxyOptions.Password, _options.ProxyOptions.Domain); - + var credentials = new NetworkCredential(_options.ProxyOptions.Username, _options.ProxyOptions.Password, _options.ProxyOptions.Domain); return new WebProxy(proxyUri, _options.ProxyOptions.BypassOnLocal, _options.ProxyOptions.BypassList, credentials); } diff --git a/Source/MQTTnet/Internal/MqttApplicationMessageExtensions.cs b/Source/MQTTnet/Internal/MqttApplicationMessageExtensions.cs deleted file mode 100644 index 7eebda69..00000000 --- a/Source/MQTTnet/Internal/MqttApplicationMessageExtensions.cs +++ /dev/null @@ -1,18 +0,0 @@ -using MQTTnet.Packets; - -namespace MQTTnet.Internal -{ - public static class MqttApplicationMessageExtensions - { - public static MqttApplicationMessage ToApplicationMessage(this MqttPublishPacket publishPacket) - { - return new MqttApplicationMessage - { - Topic = publishPacket.Topic, - Payload = publishPacket.Payload, - QualityOfServiceLevel = publishPacket.QualityOfServiceLevel, - Retain = publishPacket.Retain - }; - } - } -} diff --git a/Source/MQTTnet/MqttApplicationMessage.cs b/Source/MQTTnet/MqttApplicationMessage.cs index 54135fb0..4996c225 100644 --- a/Source/MQTTnet/MqttApplicationMessage.cs +++ b/Source/MQTTnet/MqttApplicationMessage.cs @@ -12,8 +12,22 @@ namespace MQTTnet public MqttQualityOfServiceLevel QualityOfServiceLevel { get; set; } - public List UserProperties { get; set; } - public bool Retain { get; set; } + + public List UserProperties { get; set; } = new List(); + + public string ContentType { get; set; } + + public string ResponseTopic { get; set; } + + public MqttPayloadFormatIndicator? PayloadFormatIndicator { get; set; } + + public uint? MessageExpiryInterval { get; set; } + + public ushort? TopicAlias { get; set; } + + public byte[] CorrelationData { get; set; } + + public uint? SubscriptionIdentifier { get; set; } } } diff --git a/Source/MQTTnet/MqttApplicationMessageBuilder.cs b/Source/MQTTnet/MqttApplicationMessageBuilder.cs index 79c65ece..1df86450 100644 --- a/Source/MQTTnet/MqttApplicationMessageBuilder.cs +++ b/Source/MQTTnet/MqttApplicationMessageBuilder.cs @@ -3,16 +3,26 @@ using System.IO; using System.Linq; using System.Text; using MQTTnet.Exceptions; +using MQTTnet.Packets; using MQTTnet.Protocol; namespace MQTTnet { public class MqttApplicationMessageBuilder { + private readonly List _userProperties = new List(); + private MqttQualityOfServiceLevel _qualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce; private string _topic; private byte[] _payload; private bool _retain; + private string _contentType; + private string _responseTopic; + private byte[] _correlationData; + private ushort? _topicAlias; + private uint? _subscriptionIdentifier; + private uint? _messageExpiryInterval; + private MqttPayloadFormatIndicator? _payloadFormatIndicator; public MqttApplicationMessageBuilder WithTopic(string topic) { @@ -100,6 +110,78 @@ namespace MQTTnet return this; } + /// + /// This is only supported when using MQTTv5. + /// + public MqttApplicationMessageBuilder WithUserProperty(string name, string value) + { + _userProperties.Add(new MqttUserProperty(name, value)); + return this; + } + + /// + /// This is only supported when using MQTTv5. + /// + public MqttApplicationMessageBuilder WithContentType(string contentType) + { + _contentType = contentType; + return this; + } + + /// + /// This is only supported when using MQTTv5. + /// + public MqttApplicationMessageBuilder WithResponseTopic(string responseTopic) + { + _responseTopic = responseTopic; + return this; + } + + /// + /// This is only supported when using MQTTv5. + /// + public MqttApplicationMessageBuilder WithCorrelationData(byte[] correlationData) + { + _correlationData = correlationData; + return this; + } + + /// + /// This is only supported when using MQTTv5. + /// + public MqttApplicationMessageBuilder WithTopicAlias(ushort topicAlias) + { + _topicAlias = topicAlias; + return this; + } + + /// + /// This is only supported when using MQTTv5. + /// + public MqttApplicationMessageBuilder WithSubscriptionIdentifier(uint subscriptionIdentifier) + { + _subscriptionIdentifier = subscriptionIdentifier; + return this; + } + + /// + /// This is only supported when using MQTTv5. + /// + public MqttApplicationMessageBuilder WithMessageExpiryInterval(uint messageExpiryInterval) + { + _messageExpiryInterval = messageExpiryInterval; + return this; + } + + /// + /// This is only supported when using MQTTv5. + /// + public MqttApplicationMessageBuilder WithPayloadFormatIndicator(MqttPayloadFormatIndicator payloadFormatIndicator) + { + _payloadFormatIndicator = payloadFormatIndicator; + return this; + } + public MqttApplicationMessage Build() { if (string.IsNullOrEmpty(_topic)) @@ -107,13 +189,24 @@ namespace MQTTnet throw new MqttProtocolViolationException("Topic is not set."); } - return new MqttApplicationMessage + var applicationMessage = new MqttApplicationMessage { Topic = _topic, Payload = _payload ?? new byte[0], QualityOfServiceLevel = _qualityOfServiceLevel, - Retain = _retain + Retain = _retain, + ContentType = _contentType, + ResponseTopic = _responseTopic, + CorrelationData = _correlationData, + TopicAlias = _topicAlias, + SubscriptionIdentifier = _subscriptionIdentifier, + MessageExpiryInterval = _messageExpiryInterval, + PayloadFormatIndicator = _payloadFormatIndicator }; + + applicationMessage.UserProperties.AddRange(_userProperties); + + return applicationMessage; } } } diff --git a/Source/MQTTnet/Packets/MqttAuthPacket.cs b/Source/MQTTnet/Packets/MqttAuthPacket.cs index 191ec5c1..cf93a00a 100644 --- a/Source/MQTTnet/Packets/MqttAuthPacket.cs +++ b/Source/MQTTnet/Packets/MqttAuthPacket.cs @@ -8,5 +8,7 @@ namespace MQTTnet.Packets public class MqttAuthPacket : MqttBasePacket { public MqttAuthenticateReasonCode ReasonCode { get; set; } + + public MqttAuthPacketProperties Properties { get; set; } } } diff --git a/Source/MQTTnet/Packets/MqttAuthPacketProperties.cs b/Source/MQTTnet/Packets/MqttAuthPacketProperties.cs new file mode 100644 index 00000000..6ed25ee0 --- /dev/null +++ b/Source/MQTTnet/Packets/MqttAuthPacketProperties.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; + +namespace MQTTnet.Packets +{ + public class MqttAuthPacketProperties + { + public string AuthenticationMethod { get; set; } + + public byte[] AuthenticationData { get; set; } + + public string ReasonString { get; set; } + + public List UserProperties { get; } = new List(); + } +} diff --git a/Source/MQTTnet/Packets/MqttConnAckPacket.cs b/Source/MQTTnet/Packets/MqttConnAckPacket.cs index e8caefae..16a492c3 100644 --- a/Source/MQTTnet/Packets/MqttConnAckPacket.cs +++ b/Source/MQTTnet/Packets/MqttConnAckPacket.cs @@ -4,14 +4,15 @@ namespace MQTTnet.Packets { public class MqttConnAckPacket : MqttBasePacket { - public MqttConnectReturnCode ConnectReturnCode { get; set; } + public MqttConnectReturnCode? ReturnCode { get; set; } + + #region Added in MQTTv3.1.1 - /// - /// Added in MQTTv3.1.1. - /// public bool IsSessionPresent { get; set; } - #region Added in MQTTv5 + #endregion + + #region Added in MQTTv5.0.0 public MqttConnectReasonCode? ReasonCode { get; set; } @@ -21,7 +22,7 @@ namespace MQTTnet.Packets public override string ToString() { - return string.Concat("ConnAck: [ConnectReturnCode=", ConnectReturnCode, "] [ReasonCode=", ReasonCode, "] [IsSessionPresent=", IsSessionPresent, "]"); + return string.Concat("ConnAck: [ReturnCode=", ReturnCode, "] [ReasonCode=", ReasonCode, "] [IsSessionPresent=", IsSessionPresent, "]"); } } } diff --git a/Source/MQTTnet/Packets/MqttConnAckPacketProperties.cs b/Source/MQTTnet/Packets/MqttConnAckPacketProperties.cs index 31a6b130..262222db 100644 --- a/Source/MQTTnet/Packets/MqttConnAckPacketProperties.cs +++ b/Source/MQTTnet/Packets/MqttConnAckPacketProperties.cs @@ -6,7 +6,6 @@ namespace MQTTnet.Packets { public uint? SessionExpiryInterval { get; set; } - // TODO: Add enum public ushort? ReceiveMaximum { get; set; } public bool? RetainAvailable { get; set; } @@ -19,7 +18,7 @@ namespace MQTTnet.Packets public string ReasonString { get; set; } - public List UserProperties { get; set; } + public List UserProperties { get; } = new List(); public bool? WildcardSubscriptionAvailable { get; set; } diff --git a/Source/MQTTnet/Packets/MqttConnectPacket.cs b/Source/MQTTnet/Packets/MqttConnectPacket.cs index c6b8e6e0..8c7ae464 100644 --- a/Source/MQTTnet/Packets/MqttConnectPacket.cs +++ b/Source/MQTTnet/Packets/MqttConnectPacket.cs @@ -14,14 +14,12 @@ public ushort KeepAlivePeriod { get; set; } - /// - /// MQTTv5: Also called "Clean Start". - /// + // Also called "Clean Start" in MQTTv5. public bool CleanSession { get; set; } public MqttApplicationMessage WillMessage { get; set; } - #region Added in MQTTv5 + #region Added in MQTTv5.0.0 public MqttConnectPacketProperties Properties { get; set; } diff --git a/Source/MQTTnet/Packets/MqttConnectPacketProperties.cs b/Source/MQTTnet/Packets/MqttConnectPacketProperties.cs index 4cc405e2..25cf5bda 100644 --- a/Source/MQTTnet/Packets/MqttConnectPacketProperties.cs +++ b/Source/MQTTnet/Packets/MqttConnectPacketProperties.cs @@ -22,6 +22,6 @@ namespace MQTTnet.Packets public uint? MaximumPacketSize { get; set; } - public List UserProperties { get; set; } + public List UserProperties { get; } = new List(); } } \ No newline at end of file diff --git a/Source/MQTTnet/Packets/MqttDisconnectPacket.cs b/Source/MQTTnet/Packets/MqttDisconnectPacket.cs index 621829a2..c198d9a9 100644 --- a/Source/MQTTnet/Packets/MqttDisconnectPacket.cs +++ b/Source/MQTTnet/Packets/MqttDisconnectPacket.cs @@ -1,10 +1,20 @@ -namespace MQTTnet.Packets +using MQTTnet.Protocol; + +namespace MQTTnet.Packets { public class MqttDisconnectPacket : MqttBasePacket { + #region Added in MQTTv5 + + public MqttDisconnectReasonCode? ReasonCode { get; set; } + + public MqttDisconnectPacketProperties Properties { get; set; } + + #endregion + public override string ToString() { - return "Disconnect"; + return string.Concat("Disconnect: [ReasonCode=", ReasonCode, "]"); } } } diff --git a/Source/MQTTnet/Packets/MqttDisconnectPacketProperties.cs b/Source/MQTTnet/Packets/MqttDisconnectPacketProperties.cs new file mode 100644 index 00000000..a5385cb0 --- /dev/null +++ b/Source/MQTTnet/Packets/MqttDisconnectPacketProperties.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; + +namespace MQTTnet.Packets +{ + public class MqttDisconnectPacketProperties + { + public uint? SessionExpiryInterval { get; set; } + + public string ReasonString { get; set; } + + public List UserProperties { get; } = new List(); + + public string ServerReference { get; set; } + } +} \ No newline at end of file diff --git a/Source/MQTTnet/Packets/MqttPubAckPacket.cs b/Source/MQTTnet/Packets/MqttPubAckPacket.cs index 2af16422..c9190259 100644 --- a/Source/MQTTnet/Packets/MqttPubAckPacket.cs +++ b/Source/MQTTnet/Packets/MqttPubAckPacket.cs @@ -14,7 +14,7 @@ namespace MQTTnet.Packets public override string ToString() { - return string.Concat("PubAck: [PacketIdentifier=", PacketIdentifier, "] [ConnectReasonCode=", ReasonCode, "]"); + return string.Concat("PubAck: [PacketIdentifier=", PacketIdentifier, "] [ReasonCode=", ReasonCode, "]"); } } } diff --git a/Source/MQTTnet/Packets/MqttPubAckPacketProperties.cs b/Source/MQTTnet/Packets/MqttPubAckPacketProperties.cs index e2debd4a..061f72f9 100644 --- a/Source/MQTTnet/Packets/MqttPubAckPacketProperties.cs +++ b/Source/MQTTnet/Packets/MqttPubAckPacketProperties.cs @@ -6,6 +6,6 @@ namespace MQTTnet.Packets { public string ReasonString { get; set; } - public List UserProperties { get; set; } + public List UserProperties { get; } = new List(); } } diff --git a/Source/MQTTnet/Packets/MqttPubCompPacketProperties.cs b/Source/MQTTnet/Packets/MqttPubCompPacketProperties.cs index 35e040f5..c29ad6a6 100644 --- a/Source/MQTTnet/Packets/MqttPubCompPacketProperties.cs +++ b/Source/MQTTnet/Packets/MqttPubCompPacketProperties.cs @@ -6,6 +6,6 @@ namespace MQTTnet.Packets { public string ReasonString { get; set; } - public List UserProperties { get; set; } + public List UserProperties { get; } = new List(); } } diff --git a/Source/MQTTnet/Packets/MqttPubRecPacketProperties.cs b/Source/MQTTnet/Packets/MqttPubRecPacketProperties.cs index 0cd72250..574bc617 100644 --- a/Source/MQTTnet/Packets/MqttPubRecPacketProperties.cs +++ b/Source/MQTTnet/Packets/MqttPubRecPacketProperties.cs @@ -6,6 +6,6 @@ namespace MQTTnet.Packets { public string ReasonString { get; set; } - public List UserProperties { get; set; } + public List UserProperties { get; } = new List(); } } diff --git a/Source/MQTTnet/Packets/MqttPubRelPacketProperties.cs b/Source/MQTTnet/Packets/MqttPubRelPacketProperties.cs index aa9625db..d7a21f64 100644 --- a/Source/MQTTnet/Packets/MqttPubRelPacketProperties.cs +++ b/Source/MQTTnet/Packets/MqttPubRelPacketProperties.cs @@ -6,6 +6,6 @@ namespace MQTTnet.Packets { public string ReasonString { get; set; } - public List UserProperties { get; set; } + public List UserProperties { get; } = new List(); } } diff --git a/Source/MQTTnet/Packets/MqttPublishPacketProperties.cs b/Source/MQTTnet/Packets/MqttPublishPacketProperties.cs index ba7254da..0de4d812 100644 --- a/Source/MQTTnet/Packets/MqttPublishPacketProperties.cs +++ b/Source/MQTTnet/Packets/MqttPublishPacketProperties.cs @@ -1,10 +1,11 @@ using System.Collections.Generic; +using MQTTnet.Protocol; namespace MQTTnet.Packets { public class MqttPublishPacketProperties { - public byte? PayloadFormatIndicator { get; set; } + public MqttPayloadFormatIndicator? PayloadFormatIndicator { get; set; } public uint? MessageExpiryInterval { get; set; } @@ -14,7 +15,7 @@ namespace MQTTnet.Packets public byte[] CorrelationData { get; set; } - public List UserProperties { get; set; } + public List UserProperties { get; } = new List(); public uint? SubscriptionIdentifier { get; set; } diff --git a/Source/MQTTnet/Packets/MqttSubAckPacket.cs b/Source/MQTTnet/Packets/MqttSubAckPacket.cs index 45b0e0ce..053e7137 100644 --- a/Source/MQTTnet/Packets/MqttSubAckPacket.cs +++ b/Source/MQTTnet/Packets/MqttSubAckPacket.cs @@ -8,11 +8,11 @@ namespace MQTTnet.Packets { public ushort? PacketIdentifier { get; set; } - public List SubscribeReturnCodes { get; } = new List(); + public List ReturnCodes { get; } = new List(); - #region Added in MQTTv5 + #region Added in MQTTv5.0.0 - public List ReasonCodes { get; set; } + public List ReasonCodes { get; } = new List(); public MqttSubAckPacketProperties Properties { get; set; } @@ -20,8 +20,10 @@ namespace MQTTnet.Packets public override string ToString() { - var subscribeReturnCodesText = string.Join(",", SubscribeReturnCodes.Select(f => f.ToString())); - return string.Concat("SubAck: [PacketIdentifier=", PacketIdentifier, "] [SubscribeReturnCodes=", subscribeReturnCodesText, "]"); + var returnCodesText = string.Join(",", ReturnCodes.Select(f => f.ToString())); + var reasonCodesText = string.Join(",", ReasonCodes.Select(f => f.ToString())); + + return string.Concat("SubAck: [PacketIdentifier=", PacketIdentifier, "] [ReturnCodes=", returnCodesText, "] [ReasonCode=", reasonCodesText, "]"); } } } diff --git a/Source/MQTTnet/Packets/MqttSubAckPacketProperties.cs b/Source/MQTTnet/Packets/MqttSubAckPacketProperties.cs index 74d3e7f8..b2b3eff5 100644 --- a/Source/MQTTnet/Packets/MqttSubAckPacketProperties.cs +++ b/Source/MQTTnet/Packets/MqttSubAckPacketProperties.cs @@ -6,6 +6,6 @@ namespace MQTTnet.Packets { public string ReasonString { get; set; } - public List UserProperties { get; set; } + public List UserProperties { get; } = new List(); } } diff --git a/Source/MQTTnet/Packets/MqttSubscribePacket.cs b/Source/MQTTnet/Packets/MqttSubscribePacket.cs index c185f358..c96c75bc 100644 --- a/Source/MQTTnet/Packets/MqttSubscribePacket.cs +++ b/Source/MQTTnet/Packets/MqttSubscribePacket.cs @@ -7,7 +7,7 @@ namespace MQTTnet.Packets { public ushort? PacketIdentifier { get; set; } - public IList TopicFilters { get; set; } = new List(); + public List TopicFilters { get; } = new List(); #region Added in MQTTv5 diff --git a/Source/MQTTnet/Packets/MqttSubscribePacketProperties.cs b/Source/MQTTnet/Packets/MqttSubscribePacketProperties.cs index 34f58f2f..5b92c5f5 100644 --- a/Source/MQTTnet/Packets/MqttSubscribePacketProperties.cs +++ b/Source/MQTTnet/Packets/MqttSubscribePacketProperties.cs @@ -6,6 +6,6 @@ namespace MQTTnet.Packets { public uint? SubscriptionIdentifier { get; set; } - public List UserProperties { get; set; } + public List UserProperties { get; } = new List(); } } diff --git a/Source/MQTTnet/Packets/MqttUnsubAckPacket.cs b/Source/MQTTnet/Packets/MqttUnsubAckPacket.cs index 8b78b537..0b45df67 100644 --- a/Source/MQTTnet/Packets/MqttUnsubAckPacket.cs +++ b/Source/MQTTnet/Packets/MqttUnsubAckPacket.cs @@ -1,12 +1,26 @@ -namespace MQTTnet.Packets +using System.Collections.Generic; +using System.Linq; +using MQTTnet.Protocol; + +namespace MQTTnet.Packets { public class MqttUnsubAckPacket : MqttBasePacket, IMqttPacketWithIdentifier { public ushort? PacketIdentifier { get; set; } + #region Added in MQTTv5 + + public MqttUnsubAckPacketProperties Properties { get; set; } + + public List ReasonCodes { get; } = new List(); + + #endregion + public override string ToString() { - return string.Concat("UnsubAck: [PacketIdentifier=", PacketIdentifier, "]"); + var reasonCodesText = string.Join(",", ReasonCodes.Select(f => f.ToString())); + + return string.Concat("UnsubAck: [PacketIdentifier=", PacketIdentifier, "] [ReasonCodes=", reasonCodesText, "]"); } } } diff --git a/Source/MQTTnet/Packets/MqttUnsubAckPacketProperties.cs b/Source/MQTTnet/Packets/MqttUnsubAckPacketProperties.cs new file mode 100644 index 00000000..ca444d9e --- /dev/null +++ b/Source/MQTTnet/Packets/MqttUnsubAckPacketProperties.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; + +namespace MQTTnet.Packets +{ + public class MqttUnsubAckPacketProperties + { + public string ReasonString { get; set; } + + public List UserProperties { get; } = new List(); + } +} diff --git a/Source/MQTTnet/Packets/MqttUnsubscribe.cs b/Source/MQTTnet/Packets/MqttUnsubscribePacket.cs similarity index 70% rename from Source/MQTTnet/Packets/MqttUnsubscribe.cs rename to Source/MQTTnet/Packets/MqttUnsubscribePacket.cs index de56386f..bb1d4c2a 100644 --- a/Source/MQTTnet/Packets/MqttUnsubscribe.cs +++ b/Source/MQTTnet/Packets/MqttUnsubscribePacket.cs @@ -6,7 +6,13 @@ namespace MQTTnet.Packets { public ushort? PacketIdentifier { get; set; } - public IList TopicFilters { get; set; } = new List(); + public List TopicFilters { get; } = new List(); + + #region Added in MQTTv5 + + public MqttUnsubscribePacketProperties Properties { get; set; } + + #endregion public override string ToString() { diff --git a/Source/MQTTnet/Packets/MqttUnsubscribePacketProperties.cs b/Source/MQTTnet/Packets/MqttUnsubscribePacketProperties.cs new file mode 100644 index 00000000..5fb4d66b --- /dev/null +++ b/Source/MQTTnet/Packets/MqttUnsubscribePacketProperties.cs @@ -0,0 +1,9 @@ +using System.Collections.Generic; + +namespace MQTTnet.Packets +{ + public class MqttUnsubscribePacketProperties + { + public List UserProperties { get; } = new List(); + } +} diff --git a/Source/MQTTnet/Protocol/MqttConnectReasonCode.cs b/Source/MQTTnet/Protocol/MqttConnectReasonCode.cs index 381c6fa5..cad4d77e 100644 --- a/Source/MQTTnet/Protocol/MqttConnectReasonCode.cs +++ b/Source/MQTTnet/Protocol/MqttConnectReasonCode.cs @@ -9,7 +9,7 @@ ImplementationSpecificError = 131, UnsupportedProtocolVersion = 132, ClientIdentifierNotValid = 133, - BadUserNameorPassword = 134, + BadUserNameOrPassword = 134, NotAuthorized = 135, ServerUnavailable = 136, ServerBusy = 137, diff --git a/Source/MQTTnet/Protocol/MqttControlPacketType.cs b/Source/MQTTnet/Protocol/MqttControlPacketType.cs index adb58b66..12dce634 100644 --- a/Source/MQTTnet/Protocol/MqttControlPacketType.cs +++ b/Source/MQTTnet/Protocol/MqttControlPacketType.cs @@ -15,6 +15,7 @@ UnsubAck = 11, PingReq = 12, PingResp = 13, - Disconnect = 14 + Disconnect = 14, + Auth = 15 } } diff --git a/Source/MQTTnet/Protocol/MqttDisconnectReasonCode.cs b/Source/MQTTnet/Protocol/MqttDisconnectReasonCode.cs new file mode 100644 index 00000000..c1d2e3dc --- /dev/null +++ b/Source/MQTTnet/Protocol/MqttDisconnectReasonCode.cs @@ -0,0 +1,35 @@ +namespace MQTTnet.Protocol +{ + public enum MqttDisconnectReasonCode + { + NormalDisconnection = 0, + DisconnectWithWillMessage = 4, + UnspecifiedError = 128, + MalformedPacket = 129, + ProtocolError = 130, + ImplementationSpecificError = 131, + NotAuthorized = 135, + ServerBusy = 137, + ServerShuttingDown = 139, + KeepAliveTimeout = 141, + SessionTakenOver = 142, + TopicFilterInvalid = 143, + TopicNameInvalid = 144, + ReceiveMaximumExceeded = 147, + TopicAliasInvalid = 148, + PacketTooLarge = 149, + MessageRateTooHigh = 150, + QuotaExceeded = 151, + AdministrativeAction = 152, + PayloadFormatInvalid = 153, + RetainNotSupported = 154, + QoSNotSupported = 155, + UseAnotherServer = 156, + ServerMoved = 157, + SharedSubscriptionsNotSupported = 158, + ConnectionRateExceeded = 159, + MaximumConnectTime = 160, + SubscriptionIdentifiersNotSupported = 161, + WildcardSubscriptionsNotSupported = 162 + } +} diff --git a/Source/MQTTnet/Protocol/MqttPayloadFormatIndicator.cs b/Source/MQTTnet/Protocol/MqttPayloadFormatIndicator.cs new file mode 100644 index 00000000..bf776a11 --- /dev/null +++ b/Source/MQTTnet/Protocol/MqttPayloadFormatIndicator.cs @@ -0,0 +1,8 @@ +namespace MQTTnet.Protocol +{ + public enum MqttPayloadFormatIndicator + { + Unspecified = 0, + CharacterData = 1 + } +} diff --git a/Source/MQTTnet/Protocol/MqttPropertyID.cs b/Source/MQTTnet/Protocol/MqttPropertyID.cs index 704d8dfc..2e93a158 100644 --- a/Source/MQTTnet/Protocol/MqttPropertyID.cs +++ b/Source/MQTTnet/Protocol/MqttPropertyID.cs @@ -1,6 +1,6 @@ namespace MQTTnet.Protocol { - public enum MqttPropertyID + public enum MqttPropertyId { PayloadFormatIndicator = 1, MessageExpiryInterval = 2, @@ -27,7 +27,7 @@ UserProperty = 38, MaximumPacketSize = 39, WildcardSubscriptionAvailable = 40, - SubscriptionIdentifierAvailable = 41, + SubscriptionIdentifiersAvailable = 41, SharedSubscriptionAvailable = 42 } } diff --git a/Source/MQTTnet/Protocol/MqttUnsubscribeReasonCode.cs b/Source/MQTTnet/Protocol/MqttUnsubscribeReasonCode.cs new file mode 100644 index 00000000..af062831 --- /dev/null +++ b/Source/MQTTnet/Protocol/MqttUnsubscribeReasonCode.cs @@ -0,0 +1,13 @@ +namespace MQTTnet.Protocol +{ + public enum MqttUnsubscribeReasonCode + { + Success = 0, + NoSubscriptionExisted = 17, + UnspecifiedError = 128, + ImplementationSpecificError = 131, + NotAuthorized = 135, + TopicFilterInvalid = 143, + PacketIdentifierInUse = 145 + } +} diff --git a/Source/MQTTnet/Server/MqttClientSession.cs b/Source/MQTTnet/Server/MqttClientSession.cs index 860467cf..6eacfa67 100644 --- a/Source/MQTTnet/Server/MqttClientSession.cs +++ b/Source/MQTTnet/Server/MqttClientSession.cs @@ -7,7 +7,6 @@ using MQTTnet.Client; using MQTTnet.Diagnostics; using MQTTnet.Exceptions; using MQTTnet.Formatter; -using MQTTnet.Internal; using MQTTnet.Packets; using MQTTnet.Protocol; @@ -89,10 +88,10 @@ namespace MQTTnet.Server { if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters)); - _subscriptionsManager.Subscribe(new MqttSubscribePacket - { - TopicFilters = topicFilters - }); + var packet = new MqttSubscribePacket(); + packet.TopicFilters.AddRange(topicFilters); + + _subscriptionsManager.Subscribe(packet); EnqueueSubscribedRetainedMessages(topicFilters); return Task.FromResult(0); @@ -102,10 +101,10 @@ namespace MQTTnet.Server { if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters)); - _subscriptionsManager.Unsubscribe(new MqttUnsubscribePacket - { - TopicFilters = topicFilters - }); + var packet = new MqttUnsubscribePacket(); + packet.TopicFilters.AddRange(topicFilters); + + _subscriptionsManager.Unsubscribe(packet); return Task.FromResult(0); } @@ -278,7 +277,7 @@ namespace MQTTnet.Server return; } - var publishPacket = _channelAdapter.PacketFormatterAdapter.ConvertApplicationMessageToPublishPacket(applicationMessage); + var publishPacket = _channelAdapter.PacketFormatterAdapter.DataConverter.CreatePublishPacket(applicationMessage); // Set the retain flag to true according to [MQTT-3.3.1-8] and [MQTT-3.3.1-9]. publishPacket.Retain = isRetainedApplicationMessage; @@ -345,7 +344,8 @@ namespace MQTTnet.Server { var responsePacket = new MqttPubCompPacket { - PacketIdentifier = pubRelPacket.PacketIdentifier + PacketIdentifier = pubRelPacket.PacketIdentifier, + ReasonCode = MqttPubCompReasonCode.Success }; adapter.SendPacketAsync(responsePacket, cancellationToken).GetAwaiter().GetResult(); @@ -356,7 +356,8 @@ namespace MQTTnet.Server { var responsePacket = new MqttPubRelPacket { - PacketIdentifier = pubRecPacket.PacketIdentifier + PacketIdentifier = pubRecPacket.PacketIdentifier, + ReasonCode = MqttPubRelReasonCode.Success }; adapter.SendPacketAsync(responsePacket, cancellationToken).GetAwaiter().GetResult(); @@ -454,7 +455,9 @@ namespace MQTTnet.Server private void HandleIncomingPublishPacketWithQoS0(MqttPublishPacket publishPacket) { - _sessionsManager.EnqueueApplicationMessage(this, publishPacket.ToApplicationMessage()); + _sessionsManager.EnqueueApplicationMessage( + this, + _channelAdapter.PacketFormatterAdapter.DataConverter.CreateApplicationMessage(publishPacket)); } private void HandleIncomingPublishPacketWithQoS1( @@ -462,11 +465,14 @@ namespace MQTTnet.Server MqttPublishPacket publishPacket, CancellationToken cancellationToken) { - _sessionsManager.EnqueueApplicationMessage(this, publishPacket.ToApplicationMessage()); + _sessionsManager.EnqueueApplicationMessage( + this, + _channelAdapter.PacketFormatterAdapter.DataConverter.CreateApplicationMessage(publishPacket)); var response = new MqttPubAckPacket { - PacketIdentifier = publishPacket.PacketIdentifier + PacketIdentifier = publishPacket.PacketIdentifier, + ReasonCode = MqttPubAckReasonCode.Success }; adapter.SendPacketAsync(response, cancellationToken).GetAwaiter().GetResult(); @@ -478,11 +484,12 @@ namespace MQTTnet.Server CancellationToken cancellationToken) { // QoS 2 is implement as method "B" (4.3.3 QoS 2: Exactly once delivery) - _sessionsManager.EnqueueApplicationMessage(this, publishPacket.ToApplicationMessage()); + _sessionsManager.EnqueueApplicationMessage(this, _channelAdapter.PacketFormatterAdapter.DataConverter.CreateApplicationMessage(publishPacket)); var response = new MqttPubRecPacket { - PacketIdentifier = publishPacket.PacketIdentifier + PacketIdentifier = publishPacket.PacketIdentifier, + ReasonCode = MqttPubRecReasonCode.Success }; adapter.SendPacketAsync(response, cancellationToken).GetAwaiter().GetResult(); diff --git a/Source/MQTTnet/Server/MqttClientSessionsManager.cs b/Source/MQTTnet/Server/MqttClientSessionsManager.cs index a453915b..748594a9 100644 --- a/Source/MQTTnet/Server/MqttClientSessionsManager.cs +++ b/Source/MQTTnet/Server/MqttClientSessionsManager.cs @@ -215,6 +215,7 @@ namespace MQTTnet.Server try { + // TODO: Catch cancel exception here if the first packet was not received and log properly. var firstPacket = await clientAdapter.ReceivePacketAsync(_options.DefaultCommunicationTimeout, cancellationToken).ConfigureAwait(false); if (firstPacket == null) { @@ -234,7 +235,7 @@ namespace MQTTnet.Server await clientAdapter.SendPacketAsync( new MqttConnAckPacket { - ConnectReturnCode = connectReturnCode + ReturnCode = connectReturnCode }, cancellationToken).ConfigureAwait(false); @@ -246,7 +247,8 @@ namespace MQTTnet.Server await clientAdapter.SendPacketAsync( new MqttConnAckPacket { - ConnectReturnCode = connectReturnCode, + ReturnCode = connectReturnCode, + ReasonCode = MqttConnectReasonCode.Success, IsSessionPresent = result.IsExistingSession }, cancellationToken).ConfigureAwait(false); diff --git a/Source/MQTTnet/Server/MqttClientSubscriptionsManager.cs b/Source/MQTTnet/Server/MqttClientSubscriptionsManager.cs index 38fad1f4..9f1fff69 100644 --- a/Source/MQTTnet/Server/MqttClientSubscriptionsManager.cs +++ b/Source/MQTTnet/Server/MqttClientSubscriptionsManager.cs @@ -39,11 +39,13 @@ namespace MQTTnet.Server var interceptorContext = InterceptSubscribe(topicFilter); if (!interceptorContext.AcceptSubscription) { - result.ResponsePacket.SubscribeReturnCodes.Add(MqttSubscribeReturnCode.Failure); + result.ResponsePacket.ReturnCodes.Add(MqttSubscribeReturnCode.Failure); + result.ResponsePacket.ReasonCodes.Add(MqttSubscribeReasonCode.UnspecifiedError); } else { - result.ResponsePacket.SubscribeReturnCodes.Add(ConvertToMaximumQoS(topicFilter.QualityOfServiceLevel)); + result.ResponsePacket.ReturnCodes.Add(ConvertToSubscribeReturnCode(topicFilter.QualityOfServiceLevel)); + result.ResponsePacket.ReasonCodes.Add(ConvertToSubscribeReasonCode(topicFilter.QualityOfServiceLevel)); } if (interceptorContext.CloseConnection) @@ -69,20 +71,29 @@ namespace MQTTnet.Server { if (unsubscribePacket == null) throw new ArgumentNullException(nameof(unsubscribePacket)); + var unsubAckPacket = new MqttUnsubAckPacket + { + PacketIdentifier = unsubscribePacket.PacketIdentifier + }; + lock (_subscriptions) { foreach (var topicFilter in unsubscribePacket.TopicFilters) { - _subscriptions.Remove(topicFilter); + if (_subscriptions.Remove(topicFilter)) + { + unsubAckPacket.ReasonCodes.Add(MqttUnsubscribeReasonCode.Success); + } + else + { + unsubAckPacket.ReasonCodes.Add(MqttUnsubscribeReasonCode.NoSubscriptionExisted); + } _eventDispatcher.OnClientUnsubscribedTopic(_clientId, topicFilter); } } - return new MqttUnsubAckPacket - { - PacketIdentifier = unsubscribePacket.PacketIdentifier - }; + return unsubAckPacket; } public CheckSubscriptionsResult CheckSubscriptions(string topic, MqttQualityOfServiceLevel qosLevel) @@ -113,7 +124,7 @@ namespace MQTTnet.Server return CreateSubscriptionResult(qosLevel, qosLevels); } - private static MqttSubscribeReturnCode ConvertToMaximumQoS(MqttQualityOfServiceLevel qualityOfServiceLevel) + private static MqttSubscribeReturnCode ConvertToSubscribeReturnCode(MqttQualityOfServiceLevel qualityOfServiceLevel) { switch (qualityOfServiceLevel) { @@ -124,6 +135,17 @@ namespace MQTTnet.Server } } + private static MqttSubscribeReasonCode ConvertToSubscribeReasonCode(MqttQualityOfServiceLevel qualityOfServiceLevel) + { + switch (qualityOfServiceLevel) + { + case MqttQualityOfServiceLevel.AtMostOnce: return MqttSubscribeReasonCode.GrantedQoS0; + case MqttQualityOfServiceLevel.AtLeastOnce: return MqttSubscribeReasonCode.GrantedQoS1; + case MqttQualityOfServiceLevel.ExactlyOnce: return MqttSubscribeReasonCode.GrantedQoS2; + default: return MqttSubscribeReasonCode.UnspecifiedError; + } + } + private MqttSubscriptionInterceptorContext InterceptSubscribe(TopicFilter topicFilter) { var interceptorContext = new MqttSubscriptionInterceptorContext(_clientId, topicFilter); diff --git a/Source/MQTTnet/Server/MqttServer.cs b/Source/MQTTnet/Server/MqttServer.cs index 201216a8..15e23e76 100644 --- a/Source/MQTTnet/Server/MqttServer.cs +++ b/Source/MQTTnet/Server/MqttServer.cs @@ -4,8 +4,8 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using MQTTnet.Adapter; +using MQTTnet.Client.Publishing; using MQTTnet.Diagnostics; -using MQTTnet.Internal; namespace MQTTnet.Server { @@ -77,7 +77,7 @@ namespace MQTTnet.Server return _clientSessionsManager.UnsubscribeAsync(clientId, topicFilters); } - public Task PublishAsync(MqttApplicationMessage applicationMessage) + public Task PublishAsync(MqttApplicationMessage applicationMessage) { if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage)); @@ -85,7 +85,7 @@ namespace MQTTnet.Server _clientSessionsManager.EnqueueApplicationMessage(null, applicationMessage); - return Task.FromResult(0); + return Task.FromResult(new MqttClientPublishResult()); } public async Task StartAsync(IMqttServerOptions options) diff --git a/Source/MQTTnet/Server/MqttServerOptions.cs b/Source/MQTTnet/Server/MqttServerOptions.cs index 6c7c4f94..02b2bce3 100644 --- a/Source/MQTTnet/Server/MqttServerOptions.cs +++ b/Source/MQTTnet/Server/MqttServerOptions.cs @@ -11,6 +11,7 @@ namespace MQTTnet.Server public bool EnablePersistentSessions { get; set; } public int MaxPendingMessagesPerClient { get; set; } = 250; + public MqttPendingMessagesOverflowStrategy PendingMessagesOverflowStrategy { get; set; } = MqttPendingMessagesOverflowStrategy.DropOldestQueuedMessage; public TimeSpan DefaultCommunicationTimeout { get; set; } = TimeSpan.FromSeconds(15); diff --git a/Source/MQTTnet/Server/MqttSubscribeResult.cs b/Source/MQTTnet/Server/MqttSubscribeResult.cs deleted file mode 100644 index 2de40046..00000000 --- a/Source/MQTTnet/Server/MqttSubscribeResult.cs +++ /dev/null @@ -1,11 +0,0 @@ -using MQTTnet.Packets; - -namespace MQTTnet.Server -{ - public class MqttSubscribeResult - { - public MqttSubAckPacket ResponsePacket { get; set; } - - public bool CloseConnection { get; set; } - } -} diff --git a/Source/MQTTnet/TopicFilter.cs b/Source/MQTTnet/TopicFilter.cs index 94615625..3b62e7e0 100644 --- a/Source/MQTTnet/TopicFilter.cs +++ b/Source/MQTTnet/TopicFilter.cs @@ -4,38 +4,39 @@ namespace MQTTnet { public class TopicFilter { - public TopicFilter(string topic, MqttQualityOfServiceLevel qualityOfServiceLevel) - { - Topic = topic; - QualityOfServiceLevel = qualityOfServiceLevel; - } - public string Topic { get; set; } public MqttQualityOfServiceLevel QualityOfServiceLevel { get; set; } - #region Added in MQTTv5 - /// - /// This is only available when using MQTT version 5. + /// This is only supported when using MQTTv5. /// public bool? NoLocal { get; set; } /// - /// This is only available when using MQTT version 5. + /// This is only supported when using MQTTv5. /// public bool? RetainAsPublished { get; set; } /// - /// This is only available when using MQTT version 5. + /// This is only supported when using MQTTv5. /// public MqttRetainHandling? RetainHandling { get; set; } - #endregion - public override string ToString() { - return Topic + "@" + QualityOfServiceLevel; + return string.Concat( + "TopicFilter: [Topic=", + Topic, + "] [QualityOfServiceLevel=", + QualityOfServiceLevel, + "] [NoLocal=", + NoLocal, + "] [RetainAsPublished=", + RetainAsPublished, + "] [RetainHandling=", + RetainHandling, + "]"); } } } \ No newline at end of file diff --git a/Source/MQTTnet/TopicFilterBuilder.cs b/Source/MQTTnet/TopicFilterBuilder.cs index 38c8fbc2..a4802637 100644 --- a/Source/MQTTnet/TopicFilterBuilder.cs +++ b/Source/MQTTnet/TopicFilterBuilder.cs @@ -45,7 +45,7 @@ namespace MQTTnet throw new MqttProtocolViolationException("Topic is not set."); } - return new TopicFilter(_topic, _qualityOfServiceLevel); + return new TopicFilter { Topic = _topic, QualityOfServiceLevel = _qualityOfServiceLevel }; } } } diff --git a/Tests/MQTTnet.AspNetCore.Tests/MQTTnet.AspNetCore.Tests.csproj b/Tests/MQTTnet.AspNetCore.Tests/MQTTnet.AspNetCore.Tests.csproj index 998acd2f..520c7d6c 100644 --- a/Tests/MQTTnet.AspNetCore.Tests/MQTTnet.AspNetCore.Tests.csproj +++ b/Tests/MQTTnet.AspNetCore.Tests/MQTTnet.AspNetCore.Tests.csproj @@ -8,12 +8,13 @@ - - + + + diff --git a/Tests/MQTTnet.AspNetCore.Tests/ReaderExtensionsTest.cs b/Tests/MQTTnet.AspNetCore.Tests/ReaderExtensionsTest.cs index 1db41c78..dedeff33 100644 --- a/Tests/MQTTnet.AspNetCore.Tests/ReaderExtensionsTest.cs +++ b/Tests/MQTTnet.AspNetCore.Tests/ReaderExtensionsTest.cs @@ -1,9 +1,7 @@ #if NETCOREAPP using System.Buffers; using Microsoft.VisualStudio.TestTools.UnitTesting; -using MQTTnet.AspNetCore; using MQTTnet.Formatter; -using MQTTnet.Formatter.V311; using MQTTnet.Packets; namespace MQTTnet.AspNetCore.Tests diff --git a/Tests/MQTTnet.Benchmarks/MessageProcessingBenchmark.cs b/Tests/MQTTnet.Benchmarks/MessageProcessingBenchmark.cs index 5e99201c..c821ee07 100644 --- a/Tests/MQTTnet.Benchmarks/MessageProcessingBenchmark.cs +++ b/Tests/MQTTnet.Benchmarks/MessageProcessingBenchmark.cs @@ -1,5 +1,6 @@ using BenchmarkDotNet.Attributes; using MQTTnet.Client; +using MQTTnet.Client.Options; using MQTTnet.Server; namespace MQTTnet.Benchmarks diff --git a/Tests/MQTTnet.Benchmarks/MessageProcessingMqttConnectionContextBenchmark.cs b/Tests/MQTTnet.Benchmarks/MessageProcessingMqttConnectionContextBenchmark.cs index 1e2fbb2b..7bd085c2 100644 --- a/Tests/MQTTnet.Benchmarks/MessageProcessingMqttConnectionContextBenchmark.cs +++ b/Tests/MQTTnet.Benchmarks/MessageProcessingMqttConnectionContextBenchmark.cs @@ -9,6 +9,7 @@ using Microsoft.AspNetCore.Hosting; using MQTTnet.Server; using MQTTnet.Diagnostics; using MQTTnet.AspNetCore.Client; +using MQTTnet.Client.Options; namespace MQTTnet.Benchmarks { diff --git a/Tests/MQTTnet.Benchmarks/MqttTcpChannelBenchmark.cs b/Tests/MQTTnet.Benchmarks/MqttTcpChannelBenchmark.cs index 1f29c97a..1878c34c 100644 --- a/Tests/MQTTnet.Benchmarks/MqttTcpChannelBenchmark.cs +++ b/Tests/MQTTnet.Benchmarks/MqttTcpChannelBenchmark.cs @@ -6,6 +6,7 @@ using MQTTnet.Implementations; using MQTTnet.Server; using System.Threading; using System.Threading.Tasks; +using MQTTnet.Client.Options; namespace MQTTnet.Benchmarks { diff --git a/Tests/MQTTnet.Benchmarks/SerializerBenchmark.cs b/Tests/MQTTnet.Benchmarks/SerializerBenchmark.cs index c7fdb99d..b1797ef0 100644 --- a/Tests/MQTTnet.Benchmarks/SerializerBenchmark.cs +++ b/Tests/MQTTnet.Benchmarks/SerializerBenchmark.cs @@ -6,7 +6,7 @@ using System.Threading.Tasks; using MQTTnet.Adapter; using MQTTnet.Channel; using MQTTnet.Formatter; -using MQTTnet.Formatter.V311; +using MQTTnet.Formatter.V3; namespace MQTTnet.Benchmarks { @@ -56,7 +56,7 @@ namespace MQTTnet.Benchmarks var receivedPacket = new ReceivedMqttPacket( header.Flags, - new MqttPacketBodyReader(_serializedPacket.Array, (ulong)(_serializedPacket.Count - header.RemainingLength), (ulong)_serializedPacket.Array.Length)); + new MqttPacketBodyReader(_serializedPacket.Array, (ulong)(_serializedPacket.Count - header.RemainingLength), (ulong)_serializedPacket.Array.Length), 0); _serializer.Decode(receivedPacket); } diff --git a/Tests/MQTTnet.Core.Tests/AsyncAutoResentEventTests.cs b/Tests/MQTTnet.Core.Tests/AsyncAutoResentEventTests.cs index 188f54fb..113f3dd2 100644 --- a/Tests/MQTTnet.Core.Tests/AsyncAutoResentEventTests.cs +++ b/Tests/MQTTnet.Core.Tests/AsyncAutoResentEventTests.cs @@ -1,10 +1,10 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; -using MQTTnet.Internal; -using System; +using System; using System.Threading; using System.Threading.Tasks; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using MQTTnet.Internal; -namespace MQTTnet.Core.Tests +namespace MQTTnet.Tests { [TestClass] // Inspired from the vs-threading tests (https://github.com/Microsoft/vs-threading/blob/master/src/Microsoft.VisualStudio.Threading.Tests/AsyncAutoResetEventTests.cs) diff --git a/Tests/MQTTnet.Core.Tests/AsyncLockTests.cs b/Tests/MQTTnet.Core.Tests/AsyncLockTests.cs index 43f2486c..81b0ad04 100644 --- a/Tests/MQTTnet.Core.Tests/AsyncLockTests.cs +++ b/Tests/MQTTnet.Core.Tests/AsyncLockTests.cs @@ -3,7 +3,7 @@ using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; using MQTTnet.Internal; -namespace MQTTnet.Core.Tests +namespace MQTTnet.Tests { [TestClass] public class AsyncLockTests diff --git a/Tests/MQTTnet.Core.Tests/BlockingQueueTests.cs b/Tests/MQTTnet.Core.Tests/BlockingQueueTests.cs index 80e0d724..879302f6 100644 --- a/Tests/MQTTnet.Core.Tests/BlockingQueueTests.cs +++ b/Tests/MQTTnet.Core.Tests/BlockingQueueTests.cs @@ -3,7 +3,7 @@ using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; using MQTTnet.Internal; -namespace MQTTnet.Core.Tests +namespace MQTTnet.Tests { [TestClass] public class BlockingQueueTests diff --git a/Tests/MQTTnet.Core.Tests/ExtensionTests.cs b/Tests/MQTTnet.Core.Tests/ExtensionTests.cs index fdb5888e..a2f75629 100644 --- a/Tests/MQTTnet.Core.Tests/ExtensionTests.cs +++ b/Tests/MQTTnet.Core.Tests/ExtensionTests.cs @@ -5,7 +5,7 @@ using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; using MQTTnet.Exceptions; -namespace MQTTnet.Core.Tests +namespace MQTTnet.Tests { [TestClass] public class ExtensionTests diff --git a/Tests/MQTTnet.Core.Tests/MQTTnet.Core.Tests.csproj b/Tests/MQTTnet.Core.Tests/MQTTnet.Tests.csproj similarity index 86% rename from Tests/MQTTnet.Core.Tests/MQTTnet.Core.Tests.csproj rename to Tests/MQTTnet.Core.Tests/MQTTnet.Tests.csproj index 2baaa286..176cfcde 100644 --- a/Tests/MQTTnet.Core.Tests/MQTTnet.Core.Tests.csproj +++ b/Tests/MQTTnet.Core.Tests/MQTTnet.Tests.csproj @@ -7,8 +7,8 @@ - - + + diff --git a/Tests/MQTTnet.Core.Tests/MQTTv5/MqttClientTests.cs b/Tests/MQTTnet.Core.Tests/MQTTv5/MqttClientTests.cs new file mode 100644 index 00000000..3d7c97ce --- /dev/null +++ b/Tests/MQTTnet.Core.Tests/MQTTv5/MqttClientTests.cs @@ -0,0 +1,245 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using MQTTnet.Client; +using MQTTnet.Client.Options; +using MQTTnet.Client.Publishing; +using MQTTnet.Client.Subscribing; +using MQTTnet.Client.Unsubscribing; +using MQTTnet.Formatter; +using MQTTnet.Protocol; +using MQTTnet.Server; + +namespace MQTTnet.Tests.MQTTv5 +{ + [TestClass] + public class Client_Tests + { + [TestMethod] + public async Task Client_Connect() + { + var server = new MqttFactory().CreateMqttServer(); + var client = new MqttFactory().CreateMqttClient(); + + try + { + await server.StartAsync(new MqttServerOptions()); + await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1").WithProtocolVersion(MqttProtocolVersion.V500).Build()); + } + finally + { + await server.StopAsync(); + } + } + + [TestMethod] + public async Task Client_Connect_And_Disconnect() + { + var server = new MqttFactory().CreateMqttServer(); + var client = new MqttFactory().CreateMqttClient(); + + try + { + await server.StartAsync(new MqttServerOptions()); + + await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1").WithProtocolVersion(MqttProtocolVersion.V500).Build()); + await client.DisconnectAsync(); + } + finally + { + await server.StopAsync(); + } + } + + [TestMethod] + public async Task Client_Subscribe() + { + var server = new MqttFactory().CreateMqttServer(); + var client = new MqttFactory().CreateMqttClient(); + + try + { + await server.StartAsync(new MqttServerOptions()); + + await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1").WithProtocolVersion(MqttProtocolVersion.V500).Build()); + var result = await client.SubscribeAsync("a", MqttQualityOfServiceLevel.AtLeastOnce); + await client.DisconnectAsync(); + + Assert.AreEqual(1, result.Items.Count); + Assert.AreEqual(MqttClientSubscribeResultCode.GrantedQoS1, result.Items[0].ResultCode); + } + finally + { + await server.StopAsync(); + } + } + + [TestMethod] + public async Task Client_Unsubscribe() + { + var server = new MqttFactory().CreateMqttServer(); + var client = new MqttFactory().CreateMqttClient(); + + try + { + await server.StartAsync(new MqttServerOptions()); + + await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1").WithProtocolVersion(MqttProtocolVersion.V500).Build()); + await client.SubscribeAsync("a"); + var result = await client.UnsubscribeAsync("a"); + await client.DisconnectAsync(); + + Assert.AreEqual(1, result.Items.Count); + Assert.AreEqual(MqttClientUnsubscribeResultCode.Success, result.Items[0].ReasonCode); + } + finally + { + await server.StopAsync(); + } + } + + [TestMethod] + public async Task Client_Publish_QoS0() + { + var server = new MqttFactory().CreateMqttServer(); + var client = new MqttFactory().CreateMqttClient(); + + try + { + await server.StartAsync(new MqttServerOptions()); + + await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1").WithProtocolVersion(MqttProtocolVersion.V500).Build()); + var result = await client.PublishAsync("a", "b"); + await client.DisconnectAsync(); + + Assert.AreEqual(MqttClientPublishReasonCode.Success, result.ReasonCode); + } + finally + { + await server.StopAsync(); + } + } + + [TestMethod] + public async Task Client_Publish_QoS1() + { + var server = new MqttFactory().CreateMqttServer(); + var client = new MqttFactory().CreateMqttClient(); + + try + { + await server.StartAsync(new MqttServerOptions()); + + await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1").WithProtocolVersion(MqttProtocolVersion.V500).Build()); + var result = await client.PublishAsync("a", "b", MqttQualityOfServiceLevel.AtLeastOnce); + await client.DisconnectAsync(); + + Assert.AreEqual(MqttClientPublishReasonCode.Success, result.ReasonCode); + } + finally + { + await server.StopAsync(); + } + } + + [TestMethod] + public async Task Client_Publish_QoS2() + { + var server = new MqttFactory().CreateMqttServer(); + var client = new MqttFactory().CreateMqttClient(); + + try + { + await server.StartAsync(new MqttServerOptions()); + + await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1").WithProtocolVersion(MqttProtocolVersion.V500).Build()); + var result = await client.PublishAsync("a", "b", MqttQualityOfServiceLevel.ExactlyOnce); + await client.DisconnectAsync(); + + Assert.AreEqual(MqttClientPublishReasonCode.Success, result.ReasonCode); + } + finally + { + await server.StopAsync(); + } + } + + [TestMethod] + public async Task Client_Publish_With_Properties() + { + var server = new MqttFactory().CreateMqttServer(); + var client = new MqttFactory().CreateMqttClient(); + + try + { + await server.StartAsync(new MqttServerOptions()); + + var applicationMessage = new MqttApplicationMessageBuilder() + .WithTopic("Hello") + .WithPayload("World") + .WithAtMostOnceQoS() + .WithUserProperty("x", "1") + .WithUserProperty("y", "2") + .WithResponseTopic("response") + .WithContentType("text") + .WithMessageExpiryInterval(50) + .WithCorrelationData(new byte[12]) + .WithTopicAlias(2) + .Build(); + + await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1").WithProtocolVersion(MqttProtocolVersion.V500).Build()); + var result = await client.PublishAsync(applicationMessage); + await client.DisconnectAsync(); + + Assert.AreEqual(MqttClientPublishReasonCode.Success, result.ReasonCode); + } + finally + { + await server.StopAsync(); + } + } + + [TestMethod] + public async Task Subscribe_And_Publish() + { + var server = new MqttFactory().CreateMqttServer(); + var client1 = new MqttFactory().CreateMqttClient(); + var client2 = new MqttFactory().CreateMqttClient(); + + try + { + await server.StartAsync(new MqttServerOptions()); + + var receivedMessages = new List(); + + await client1.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1").WithClientId("client1").WithProtocolVersion(MqttProtocolVersion.V500).Build()); + client1.ApplicationMessageReceived += (s, e) => + { + lock (receivedMessages) + { + receivedMessages.Add(e); + } + }; + + await client1.SubscribeAsync("a"); + + await client2.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1").WithClientId("client2").WithProtocolVersion(MqttProtocolVersion.V500).Build()); + await client2.PublishAsync("a", "b"); + + await Task.Delay(500); + + await client2.DisconnectAsync(); + await client1.DisconnectAsync(); + + Assert.AreEqual(1, receivedMessages.Count); + Assert.AreEqual("client1", receivedMessages[0].ClientId); + Assert.AreEqual("a", receivedMessages[0].ApplicationMessage.Topic); + Assert.AreEqual("b", receivedMessages[0].ApplicationMessage.ConvertPayloadToString()); + } + finally + { + await server.StopAsync(); + } + } + } +} diff --git a/Tests/MQTTnet.Core.Tests/ManagedMqttClientTests.cs b/Tests/MQTTnet.Core.Tests/ManagedMqttClientTests.cs index 670b8a1c..559a87c1 100644 --- a/Tests/MQTTnet.Core.Tests/ManagedMqttClientTests.cs +++ b/Tests/MQTTnet.Core.Tests/ManagedMqttClientTests.cs @@ -1,9 +1,9 @@ -using MQTTnet.Extensions.ManagedClient; -using System.Threading.Tasks; +using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; +using MQTTnet.Extensions.ManagedClient; using MQTTnet.Server; -namespace MQTTnet.Core.Tests +namespace MQTTnet.Tests { [TestClass] public class ManagedMqttClientTests diff --git a/Tests/MQTTnet.Core.Tests/MqttApplicationMessageBuilderTests.cs b/Tests/MQTTnet.Core.Tests/MqttApplicationMessageBuilderTests.cs index 59afcd68..2d996113 100644 --- a/Tests/MQTTnet.Core.Tests/MqttApplicationMessageBuilderTests.cs +++ b/Tests/MQTTnet.Core.Tests/MqttApplicationMessageBuilderTests.cs @@ -4,7 +4,7 @@ using System.Text; using Microsoft.VisualStudio.TestTools.UnitTesting; using MQTTnet.Protocol; -namespace MQTTnet.Core.Tests +namespace MQTTnet.Tests { [TestClass] public class MqttApplicationMessageBuilderTests diff --git a/Tests/MQTTnet.Core.Tests/MqttClientTests.cs b/Tests/MQTTnet.Core.Tests/MqttClientTests.cs index 181a3142..6c70caf2 100644 --- a/Tests/MQTTnet.Core.Tests/MqttClientTests.cs +++ b/Tests/MQTTnet.Core.Tests/MqttClientTests.cs @@ -2,17 +2,14 @@ using System; using System.Collections.Generic; using System.Linq; using System.Net.Sockets; -using System.Threading; using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; -using MQTTnet.Adapter; using MQTTnet.Client; -using MQTTnet.Diagnostics; +using MQTTnet.Client.Options; using MQTTnet.Exceptions; -using MQTTnet.Packets; using MQTTnet.Server; -namespace MQTTnet.Core.Tests +namespace MQTTnet.Tests { [TestClass] public class MqttClientTests @@ -128,39 +125,39 @@ namespace MQTTnet.Core.Tests } } -#if DEBUG - [TestMethod] - public async Task Client_Cleanup_On_Authentification_Fails() - { - var channel = new TestMqttCommunicationAdapter(); - var channel2 = new TestMqttCommunicationAdapter(); - channel.Partner = channel2; - channel2.Partner = channel; +//#if DEBUG +// [TestMethod] +// public async Task Client_Cleanup_On_Authentification_Fails() +// { +// var channel = new TestMqttCommunicationAdapter(); +// var channel2 = new TestMqttCommunicationAdapter(); +// channel.Partner = channel2; +// channel2.Partner = channel; - Task.Run(async () => { - var connect = await channel2.ReceivePacketAsync(TimeSpan.Zero, CancellationToken.None); - await channel2.SendPacketAsync(new MqttConnAckPacket - { - ConnectReturnCode = Protocol.MqttConnectReturnCode.ConnectionRefusedNotAuthorized - }, CancellationToken.None); - }); +// Task.Run(async () => { +// var connect = await channel2.ReceivePacketAsync(TimeSpan.Zero, CancellationToken.None); +// await channel2.SendPacketAsync(new MqttConnAckPacket +// { +// ConnectReturnCode = Protocol.MqttConnectReturnCode.ConnectionRefusedNotAuthorized +// }, CancellationToken.None); +// }); - var fake = new TestMqttCommunicationAdapterFactory(channel); +// var fake = new TestMqttCommunicationAdapterFactory(channel); - var client = new MqttClient(fake, new MqttNetLogger()); +// var client = new MqttClient(fake, new MqttNetLogger()); - try - { - await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("any-server").Build()); - } - catch (Exception ex) - { - Assert.IsInstanceOfType(ex, typeof(MqttConnectingFailedException)); - } +// try +// { +// await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("any-server").Build()); +// } +// catch (Exception ex) +// { +// Assert.IsInstanceOfType(ex, typeof(MqttConnectingFailedException)); +// } - Assert.IsTrue(client._packetReceiverTask == null || client._packetReceiverTask.IsCompleted, "receive loop not completed"); - Assert.IsTrue(client._keepAliveMessageSenderTask == null || client._keepAliveMessageSenderTask.IsCompleted, "keepalive loop not completed"); - } -#endif +// Assert.IsTrue(client._packetReceiverTask == null || client._packetReceiverTask.IsCompleted, "receive loop not completed"); +// Assert.IsTrue(client._keepAliveMessageSenderTask == null || client._keepAliveMessageSenderTask.IsCompleted, "keepalive loop not completed"); +// } +//#endif } } diff --git a/Tests/MQTTnet.Core.Tests/MqttKeepAliveMonitorTests.cs b/Tests/MQTTnet.Core.Tests/MqttKeepAliveMonitorTests.cs index 183955a1..91b46dea 100644 --- a/Tests/MQTTnet.Core.Tests/MqttKeepAliveMonitorTests.cs +++ b/Tests/MQTTnet.Core.Tests/MqttKeepAliveMonitorTests.cs @@ -8,7 +8,7 @@ using MQTTnet.Diagnostics; using MQTTnet.Packets; using MQTTnet.Server; -namespace MQTTnet.Core.Tests +namespace MQTTnet.Tests { [TestClass] public class MqttKeepAliveMonitorTests diff --git a/Tests/MQTTnet.Core.Tests/MqttPacketIdentifierProviderTests.cs b/Tests/MQTTnet.Core.Tests/MqttPacketIdentifierProviderTests.cs index ab7070ee..e5d0cd71 100644 --- a/Tests/MQTTnet.Core.Tests/MqttPacketIdentifierProviderTests.cs +++ b/Tests/MQTTnet.Core.Tests/MqttPacketIdentifierProviderTests.cs @@ -1,7 +1,7 @@ -using Microsoft.VisualStudio.TestTools .UnitTesting; +using Microsoft.VisualStudio.TestTools.UnitTesting; using MQTTnet.Client; -namespace MQTTnet.Core.Tests +namespace MQTTnet.Tests { [TestClass] public class MqttPacketIdentifierProviderTests diff --git a/Tests/MQTTnet.Core.Tests/MqttPacketReaderTests.cs b/Tests/MQTTnet.Core.Tests/MqttPacketReaderTests.cs index 51c5e073..215dd0aa 100644 --- a/Tests/MQTTnet.Core.Tests/MqttPacketReaderTests.cs +++ b/Tests/MQTTnet.Core.Tests/MqttPacketReaderTests.cs @@ -5,7 +5,7 @@ using MQTTnet.Exceptions; using MQTTnet.Formatter; using MQTTnet.Internal; -namespace MQTTnet.Core.Tests +namespace MQTTnet.Tests { [TestClass] public class MqttPacketReaderTests diff --git a/Tests/MQTTnet.Core.Tests/MqttPacketSerializerTests.cs b/Tests/MQTTnet.Core.Tests/MqttPacketSerializerTests.cs index f8bffcdf..2b612748 100644 --- a/Tests/MQTTnet.Core.Tests/MqttPacketSerializerTests.cs +++ b/Tests/MQTTnet.Core.Tests/MqttPacketSerializerTests.cs @@ -6,13 +6,12 @@ using System.Threading; using Microsoft.VisualStudio.TestTools.UnitTesting; using MQTTnet.Adapter; using MQTTnet.Formatter; -using MQTTnet.Formatter.V310; -using MQTTnet.Formatter.V311; +using MQTTnet.Formatter.V3; using MQTTnet.Internal; using MQTTnet.Packets; using MQTTnet.Protocol; -namespace MQTTnet.Core.Tests +namespace MQTTnet.Tests { [TestClass] public class MqttPacketSerializerTests @@ -112,7 +111,7 @@ namespace MQTTnet.Core.Tests var p = new MqttConnAckPacket { IsSessionPresent = true, - ConnectReturnCode = MqttConnectReturnCode.ConnectionRefusedNotAuthorized + ReturnCode = MqttConnectReturnCode.ConnectionRefusedNotAuthorized }; SerializeAndCompare(p, "IAIBBQ=="); @@ -123,7 +122,7 @@ namespace MQTTnet.Core.Tests { var p = new MqttConnAckPacket { - ConnectReturnCode = MqttConnectReturnCode.ConnectionRefusedNotAuthorized + ReturnCode = MqttConnectReturnCode.ConnectionRefusedNotAuthorized }; SerializeAndCompare(p, "IAIABQ==", MqttProtocolVersion.V310); @@ -135,7 +134,7 @@ namespace MQTTnet.Core.Tests var p = new MqttConnAckPacket { IsSessionPresent = true, - ConnectReturnCode = MqttConnectReturnCode.ConnectionRefusedNotAuthorized + ReturnCode = MqttConnectReturnCode.ConnectionRefusedNotAuthorized }; DeserializeAndCompare(p, "IAIBBQ=="); @@ -146,7 +145,7 @@ namespace MQTTnet.Core.Tests { var p = new MqttConnAckPacket { - ConnectReturnCode = MqttConnectReturnCode.ConnectionRefusedNotAuthorized + ReturnCode = MqttConnectReturnCode.ConnectionRefusedNotAuthorized }; DeserializeAndCompare(p, "IAIABQ==", MqttProtocolVersion.V310); @@ -189,7 +188,8 @@ namespace MQTTnet.Core.Tests var receivedPacket = new ReceivedMqttPacket( header.Flags, - new MqttPacketBodyReader(buffer.Array, (uint)eof - header.RemainingLength, (uint)(buffer.Count + buffer.Offset))); + new MqttPacketBodyReader(buffer.Array, eof - header.RemainingLength, buffer.Count + buffer.Offset), + 0); var packet = (MqttPublishPacket)serializer.Decode(receivedPacket); @@ -401,9 +401,9 @@ namespace MQTTnet.Core.Tests PacketIdentifier = 123 }; - p.TopicFilters.Add(new TopicFilter("A/B/C", MqttQualityOfServiceLevel.ExactlyOnce)); - p.TopicFilters.Add(new TopicFilter("1/2/3", MqttQualityOfServiceLevel.AtLeastOnce)); - p.TopicFilters.Add(new TopicFilter("x/y/z", MqttQualityOfServiceLevel.AtMostOnce)); + p.TopicFilters.Add(new TopicFilter { Topic = "A/B/C", QualityOfServiceLevel = MqttQualityOfServiceLevel.ExactlyOnce }); + p.TopicFilters.Add(new TopicFilter { Topic = "1/2/3", QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce }); + p.TopicFilters.Add(new TopicFilter { Topic = "x/y/z", QualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce }); SerializeAndCompare(p, "ghoAewAFQS9CL0MCAAUxLzIvMwEABXgveS96AA=="); } @@ -416,9 +416,9 @@ namespace MQTTnet.Core.Tests PacketIdentifier = 123 }; - p.TopicFilters.Add(new TopicFilter("A/B/C", MqttQualityOfServiceLevel.ExactlyOnce)); - p.TopicFilters.Add(new TopicFilter("1/2/3", MqttQualityOfServiceLevel.AtLeastOnce)); - p.TopicFilters.Add(new TopicFilter("x/y/z", MqttQualityOfServiceLevel.AtMostOnce)); + p.TopicFilters.Add(new TopicFilter { Topic = "A/B/C", QualityOfServiceLevel = MqttQualityOfServiceLevel.ExactlyOnce }); + p.TopicFilters.Add(new TopicFilter { Topic = "1/2/3", QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce }); + p.TopicFilters.Add(new TopicFilter { Topic = "x/y/z", QualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce }); DeserializeAndCompare(p, "ghoAewAFQS9CL0MCAAUxLzIvMwEABXgveS96AA=="); } @@ -431,10 +431,10 @@ namespace MQTTnet.Core.Tests PacketIdentifier = 123 }; - p.SubscribeReturnCodes.Add(MqttSubscribeReturnCode.SuccessMaximumQoS0); - p.SubscribeReturnCodes.Add(MqttSubscribeReturnCode.SuccessMaximumQoS1); - p.SubscribeReturnCodes.Add(MqttSubscribeReturnCode.SuccessMaximumQoS2); - p.SubscribeReturnCodes.Add(MqttSubscribeReturnCode.Failure); + p.ReturnCodes.Add(MqttSubscribeReturnCode.SuccessMaximumQoS0); + p.ReturnCodes.Add(MqttSubscribeReturnCode.SuccessMaximumQoS1); + p.ReturnCodes.Add(MqttSubscribeReturnCode.SuccessMaximumQoS2); + p.ReturnCodes.Add(MqttSubscribeReturnCode.Failure); SerializeAndCompare(p, "kAYAewABAoA="); } @@ -447,10 +447,10 @@ namespace MQTTnet.Core.Tests PacketIdentifier = 123 }; - p.SubscribeReturnCodes.Add(MqttSubscribeReturnCode.SuccessMaximumQoS0); - p.SubscribeReturnCodes.Add(MqttSubscribeReturnCode.SuccessMaximumQoS1); - p.SubscribeReturnCodes.Add(MqttSubscribeReturnCode.SuccessMaximumQoS2); - p.SubscribeReturnCodes.Add(MqttSubscribeReturnCode.Failure); + p.ReturnCodes.Add(MqttSubscribeReturnCode.SuccessMaximumQoS0); + p.ReturnCodes.Add(MqttSubscribeReturnCode.SuccessMaximumQoS1); + p.ReturnCodes.Add(MqttSubscribeReturnCode.SuccessMaximumQoS2); + p.ReturnCodes.Add(MqttSubscribeReturnCode.Failure); DeserializeAndCompare(p, "kAYAewABAoA="); } @@ -552,9 +552,9 @@ namespace MQTTnet.Core.Tests var fixedHeader = new byte[2]; var header = new MqttPacketReader(channel).ReadFixedHeaderAsync(fixedHeader, CancellationToken.None).GetAwaiter().GetResult(); - using (var bodyStream = new MemoryStream(Join(buffer1), (int)headerStream.Position, (int)header.RemainingLength)) + using (var bodyStream = new MemoryStream(Join(buffer1), (int)headerStream.Position, header.RemainingLength)) { - var deserializedPacket = serializer.Decode(new ReceivedMqttPacket(header.Flags, new MqttPacketBodyReader(bodyStream.ToArray(), 0, (int)bodyStream.Length))); + var deserializedPacket = serializer.Decode(new ReceivedMqttPacket(header.Flags, new MqttPacketBodyReader(bodyStream.ToArray(), 0, (int)bodyStream.Length), 0)); var buffer2 = serializer.Encode(deserializedPacket); Assert.AreEqual(expectedBase64Value, Convert.ToBase64String(Join(buffer2))); @@ -590,7 +590,7 @@ namespace MQTTnet.Core.Tests using (var bodyStream = new MemoryStream(Join(buffer1), (int)headerStream.Position, (int)header.RemainingLength)) { - return (T)serializer.Decode(new ReceivedMqttPacket(header.Flags, new MqttPacketBodyReader(bodyStream.ToArray(), 0, (int)bodyStream.Length))); + return (T)serializer.Decode(new ReceivedMqttPacket(header.Flags, new MqttPacketBodyReader(bodyStream.ToArray(), 0, (int)bodyStream.Length), 0)); } } } diff --git a/Tests/MQTTnet.Core.Tests/MqttPacketWriterTests.cs b/Tests/MQTTnet.Core.Tests/MqttPacketWriterTests.cs index a3209144..6b4ac8f0 100644 --- a/Tests/MQTTnet.Core.Tests/MqttPacketWriterTests.cs +++ b/Tests/MQTTnet.Core.Tests/MqttPacketWriterTests.cs @@ -1,7 +1,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using MQTTnet.Formatter; -namespace MQTTnet.Core.Tests +namespace MQTTnet.Tests { [TestClass] public class MqttPacketWriterTests diff --git a/Tests/MQTTnet.Core.Tests/MqttServerTests.cs b/Tests/MQTTnet.Core.Tests/MqttServerTests.cs index ade6b1bf..83f82cdc 100644 --- a/Tests/MQTTnet.Core.Tests/MqttServerTests.cs +++ b/Tests/MQTTnet.Core.Tests/MqttServerTests.cs @@ -1,19 +1,19 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; -using MQTTnet.Client; -using MQTTnet.Diagnostics; -using MQTTnet.Protocol; -using MQTTnet.Server; -using System; -using System.Collections.Concurrent; +using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; +using Microsoft.VisualStudio.TestTools.UnitTesting; using MQTTnet.Adapter; +using MQTTnet.Client; +using MQTTnet.Client.Options; +using MQTTnet.Diagnostics; using MQTTnet.Implementations; +using MQTTnet.Protocol; +using MQTTnet.Server; -namespace MQTTnet.Core.Tests +namespace MQTTnet.Tests { [TestClass] public class MqttServerTests @@ -111,7 +111,7 @@ namespace MQTTnet.Core.Tests subscribeEventCalled = e.TopicFilter.Topic == "a" && e.ClientId == "c1"; }; - await c1.SubscribeAsync(new TopicFilter("a", MqttQualityOfServiceLevel.AtLeastOnce)); + await c1.SubscribeAsync(new TopicFilter { Topic = "a", QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce }); await Task.Delay(500); Assert.IsTrue(subscribeEventCalled, "Subscribe event not called."); @@ -158,7 +158,7 @@ namespace MQTTnet.Core.Tests c1.ApplicationMessageReceived += (_, __) => receivedMessagesCount++; var message = new MqttApplicationMessageBuilder().WithTopic("a").WithAtLeastOnceQoS().Build(); - await c1.SubscribeAsync(new TopicFilter("a", MqttQualityOfServiceLevel.AtLeastOnce)); + await c1.SubscribeAsync(new TopicFilter { Topic = "a", QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce }); await s.PublishAsync(message); await Task.Delay(500); @@ -213,8 +213,8 @@ namespace MQTTnet.Core.Tests }; var message = new MqttApplicationMessageBuilder().WithTopic("a").WithAtLeastOnceQoS().Build(); - await c1.SubscribeAsync(new TopicFilter("a", MqttQualityOfServiceLevel.AtLeastOnce)); - await c2.SubscribeAsync(new TopicFilter("a", MqttQualityOfServiceLevel.AtLeastOnce)); + await c1.SubscribeAsync(new TopicFilter { Topic = "a", QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce }); + await c2.SubscribeAsync(new TopicFilter { Topic = "a", QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce }); //await Task.WhenAll(Publish(c1, message), Publish(c2, message)); await Publish(c1, message); @@ -228,7 +228,7 @@ namespace MQTTnet.Core.Tests Assert.AreEqual(2000, receivedMessagesCount); } - + [TestMethod] public async Task MqttServer_SessionTakeover() { @@ -373,7 +373,7 @@ namespace MQTTnet.Core.Tests { await server.StopAsync(); } - + Assert.IsTrue(clientWasConnected); Assert.AreEqual(0, errors); } @@ -554,7 +554,7 @@ namespace MQTTnet.Core.Tests c2.ApplicationMessageReceived += (_, __) => receivedMessagesCount++; await Task.Delay(200); - await c2.SubscribeAsync(new TopicFilter("retained", MqttQualityOfServiceLevel.AtMostOnce)); + await c2.SubscribeAsync(new TopicFilter { Topic = "retained", QualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce }); await Task.Delay(500); } finally diff --git a/Tests/MQTTnet.Core.Tests/MqttSubscriptionsManagerTests.cs b/Tests/MQTTnet.Core.Tests/MqttSubscriptionsManagerTests.cs index 2bdd15f6..4af3b10c 100644 --- a/Tests/MQTTnet.Core.Tests/MqttSubscriptionsManagerTests.cs +++ b/Tests/MQTTnet.Core.Tests/MqttSubscriptionsManagerTests.cs @@ -3,7 +3,7 @@ using MQTTnet.Packets; using MQTTnet.Protocol; using MQTTnet.Server; -namespace MQTTnet.Core.Tests +namespace MQTTnet.Tests { [TestClass] public class MqttSubscriptionsManagerTests @@ -29,7 +29,7 @@ namespace MQTTnet.Core.Tests var sm = new MqttClientSubscriptionsManager("", new MqttServerOptions(), new MqttServerEventDispatcher()); var sp = new MqttSubscribePacket(); - sp.TopicFilters.Add(new TopicFilter("A/B/C", MqttQualityOfServiceLevel.AtMostOnce)); + sp.TopicFilters.Add(new TopicFilter { Topic = "A/B/C", QualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce }); sm.Subscribe(sp); @@ -44,8 +44,8 @@ namespace MQTTnet.Core.Tests var sm = new MqttClientSubscriptionsManager("", new MqttServerOptions(), new MqttServerEventDispatcher()); var sp = new MqttSubscribePacket(); - sp.TopicFilters.Add(new TopicFilter("#", MqttQualityOfServiceLevel.AtMostOnce)); - sp.TopicFilters.Add(new TopicFilter("A/B/C", MqttQualityOfServiceLevel.AtLeastOnce)); + sp.TopicFilters.Add(new TopicFilter { Topic = "#", QualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce }); + sp.TopicFilters.Add(new TopicFilter { Topic = "A/B/C", QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce }); sm.Subscribe(sp); diff --git a/Tests/MQTTnet.Core.Tests/RoundtripTimeTests.cs b/Tests/MQTTnet.Core.Tests/RoundtripTimeTests.cs index a8e4ce93..c8de9e40 100644 --- a/Tests/MQTTnet.Core.Tests/RoundtripTimeTests.cs +++ b/Tests/MQTTnet.Core.Tests/RoundtripTimeTests.cs @@ -4,9 +4,10 @@ using System.Diagnostics; using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; using MQTTnet.Client; +using MQTTnet.Client.Options; using MQTTnet.Server; -namespace MQTTnet.Core.Tests +namespace MQTTnet.Tests { [TestClass] public class RoundtripTimeTests diff --git a/Tests/MQTTnet.Core.Tests/TestMqttCommunicationAdapter.cs b/Tests/MQTTnet.Core.Tests/TestMqttCommunicationAdapter.cs index fd95cfae..4a7475d6 100644 --- a/Tests/MQTTnet.Core.Tests/TestMqttCommunicationAdapter.cs +++ b/Tests/MQTTnet.Core.Tests/TestMqttCommunicationAdapter.cs @@ -6,7 +6,7 @@ using MQTTnet.Adapter; using MQTTnet.Formatter; using MQTTnet.Packets; -namespace MQTTnet.Core.Tests +namespace MQTTnet.Tests { public class TestMqttCommunicationAdapter : IMqttChannelAdapter { diff --git a/Tests/MQTTnet.Core.Tests/TestMqttCommunicationAdapterFactory.cs b/Tests/MQTTnet.Core.Tests/TestMqttCommunicationAdapterFactory.cs index 6873c69e..0263ff66 100644 --- a/Tests/MQTTnet.Core.Tests/TestMqttCommunicationAdapterFactory.cs +++ b/Tests/MQTTnet.Core.Tests/TestMqttCommunicationAdapterFactory.cs @@ -1,8 +1,9 @@ using MQTTnet.Adapter; using MQTTnet.Client; +using MQTTnet.Client.Options; using MQTTnet.Diagnostics; -namespace MQTTnet.Core.Tests +namespace MQTTnet.Tests { public class TestMqttCommunicationAdapterFactory : IMqttClientAdapterFactory { diff --git a/Tests/MQTTnet.Core.Tests/TestMqttServerAdapter.cs b/Tests/MQTTnet.Core.Tests/TestMqttServerAdapter.cs index 2b24f42b..cfcba04a 100644 --- a/Tests/MQTTnet.Core.Tests/TestMqttServerAdapter.cs +++ b/Tests/MQTTnet.Core.Tests/TestMqttServerAdapter.cs @@ -3,10 +3,11 @@ using System.Threading; using System.Threading.Tasks; using MQTTnet.Adapter; using MQTTnet.Client; +using MQTTnet.Client.Options; using MQTTnet.Diagnostics; using MQTTnet.Server; -namespace MQTTnet.Core.Tests +namespace MQTTnet.Tests { public class TestMqttServerAdapter : IMqttServerAdapter { diff --git a/Tests/MQTTnet.Core.Tests/TestServerExtensions.cs b/Tests/MQTTnet.Core.Tests/TestServerExtensions.cs index 7636b1a2..5bfa2c89 100644 --- a/Tests/MQTTnet.Core.Tests/TestServerExtensions.cs +++ b/Tests/MQTTnet.Core.Tests/TestServerExtensions.cs @@ -1,9 +1,8 @@ -using MQTTnet.Client; +using System.Threading.Tasks; +using MQTTnet.Client; using MQTTnet.Server; -using System; -using System.Threading.Tasks; -namespace MQTTnet.Core.Tests +namespace MQTTnet.Tests { public static class TestServerExtensions { diff --git a/Tests/MQTTnet.Core.Tests/TopicFilterComparerTests.cs b/Tests/MQTTnet.Core.Tests/TopicFilterComparerTests.cs index e91e9885..244fa3cf 100644 --- a/Tests/MQTTnet.Core.Tests/TopicFilterComparerTests.cs +++ b/Tests/MQTTnet.Core.Tests/TopicFilterComparerTests.cs @@ -1,7 +1,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using MQTTnet.Server; -namespace MQTTnet.Core.Tests +namespace MQTTnet.Tests { [TestClass] public class TopicFilterComparerTests diff --git a/Tests/MQTTnet.TestApp.NetCore/ClientFlowTest.cs b/Tests/MQTTnet.TestApp.NetCore/ClientFlowTest.cs index 4b166236..fbeb3bfc 100644 --- a/Tests/MQTTnet.TestApp.NetCore/ClientFlowTest.cs +++ b/Tests/MQTTnet.TestApp.NetCore/ClientFlowTest.cs @@ -1,6 +1,7 @@ using System; using System.Threading.Tasks; using MQTTnet.Client; +using MQTTnet.Client.Options; namespace MQTTnet.TestApp.NetCore { diff --git a/Tests/MQTTnet.TestApp.NetCore/ClientTest.cs b/Tests/MQTTnet.TestApp.NetCore/ClientTest.cs index 98e7f0b1..8343d9e7 100644 --- a/Tests/MQTTnet.TestApp.NetCore/ClientTest.cs +++ b/Tests/MQTTnet.TestApp.NetCore/ClientTest.cs @@ -2,6 +2,7 @@ using System.Text; using System.Threading.Tasks; using MQTTnet.Client; +using MQTTnet.Client.Options; using MQTTnet.Protocol; namespace MQTTnet.TestApp.NetCore @@ -23,7 +24,7 @@ namespace MQTTnet.TestApp.NetCore Server = "127.0.0.1" } }; - + client.ApplicationMessageReceived += (s, e) => { Console.WriteLine("### RECEIVED APPLICATION MESSAGE ###"); @@ -73,14 +74,14 @@ namespace MQTTnet.TestApp.NetCore { Console.ReadLine(); - await client.SubscribeAsync(new TopicFilter("test", MqttQualityOfServiceLevel.AtMostOnce)); + await client.SubscribeAsync(new TopicFilter { Topic = "test", QualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce }); var applicationMessage = new MqttApplicationMessageBuilder() .WithTopic("A/B/C") .WithPayload("Hello World") .WithAtLeastOnceQoS() .Build(); - + await client.PublishAsync(applicationMessage); } } diff --git a/Tests/MQTTnet.TestApp.NetCore/MQTTnet.TestApp.NetCore.csproj b/Tests/MQTTnet.TestApp.NetCore/MQTTnet.TestApp.NetCore.csproj index 0dd56d73..1900203e 100644 --- a/Tests/MQTTnet.TestApp.NetCore/MQTTnet.TestApp.NetCore.csproj +++ b/Tests/MQTTnet.TestApp.NetCore/MQTTnet.TestApp.NetCore.csproj @@ -12,7 +12,7 @@ - + diff --git a/Tests/MQTTnet.TestApp.NetCore/ManagedClientTest.cs b/Tests/MQTTnet.TestApp.NetCore/ManagedClientTest.cs index 563f3430..d7cea499 100644 --- a/Tests/MQTTnet.TestApp.NetCore/ManagedClientTest.cs +++ b/Tests/MQTTnet.TestApp.NetCore/ManagedClientTest.cs @@ -3,7 +3,7 @@ using System.Threading.Tasks; using System.IO; using Newtonsoft.Json; using System.Collections.Generic; -using MQTTnet.Client; +using MQTTnet.Client.Options; using MQTTnet.Extensions.ManagedClient; using MQTTnet.Protocol; @@ -14,7 +14,7 @@ namespace MQTTnet.TestApp.NetCore public static async Task RunAsync() { var ms = new ClientRetainedMessageHandler(); - + var options = new ManagedMqttClientOptions { ClientOptions = new MqttClientOptions @@ -44,9 +44,9 @@ namespace MQTTnet.TestApp.NetCore await managedClient.StartAsync(options); - await managedClient.SubscribeAsync(new TopicFilter("xyz", MqttQualityOfServiceLevel.AtMostOnce)); - await managedClient.SubscribeAsync(new TopicFilter("abc", MqttQualityOfServiceLevel.AtMostOnce)); - + await managedClient.SubscribeAsync(new TopicFilter { Topic = "xyz", QualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce }); + await managedClient.SubscribeAsync(new TopicFilter { Topic = "abc", QualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce }); + await managedClient.PublishAsync(builder => builder.WithTopic("Step").WithPayload("3")); Console.WriteLine("Managed client started."); diff --git a/Tests/MQTTnet.TestApp.NetCore/PerformanceTest.cs b/Tests/MQTTnet.TestApp.NetCore/PerformanceTest.cs index 3f4a1d54..701f45c1 100644 --- a/Tests/MQTTnet.TestApp.NetCore/PerformanceTest.cs +++ b/Tests/MQTTnet.TestApp.NetCore/PerformanceTest.cs @@ -5,6 +5,7 @@ using System.Text; using System.Threading; using System.Threading.Tasks; using MQTTnet.Client; +using MQTTnet.Client.Options; using MQTTnet.Protocol; using MQTTnet.Server; diff --git a/Tests/MQTTnet.TestApp.NetCore/Program.cs b/Tests/MQTTnet.TestApp.NetCore/Program.cs index 908844fa..e92454e5 100644 --- a/Tests/MQTTnet.TestApp.NetCore/Program.cs +++ b/Tests/MQTTnet.TestApp.NetCore/Program.cs @@ -4,6 +4,7 @@ using System.IO; using System.Threading; using System.Threading.Tasks; using MQTTnet.Client; +using MQTTnet.Client.Options; using MQTTnet.Diagnostics; using MQTTnet.Server; using Newtonsoft.Json; diff --git a/Tests/MQTTnet.TestApp.NetCore/PublicBrokerTest.cs b/Tests/MQTTnet.TestApp.NetCore/PublicBrokerTest.cs index e46ed168..ac6d16c5 100644 --- a/Tests/MQTTnet.TestApp.NetCore/PublicBrokerTest.cs +++ b/Tests/MQTTnet.TestApp.NetCore/PublicBrokerTest.cs @@ -3,7 +3,8 @@ using System; using System.IO; using System.Threading; using System.Threading.Tasks; -using MQTTnet.Diagnostics; +using MQTTnet.Client.Options; +using MQTTnet.Formatter; using MQTTnet.Protocol; using Newtonsoft.Json; @@ -17,37 +18,37 @@ namespace MQTTnet.TestApp.NetCore // iot.eclipse.org await ExecuteTestAsync("iot.eclipse.org TCP", - new MqttClientOptionsBuilder().WithTcpServer("iot.eclipse.org", 1883).Build()); + new MqttClientOptionsBuilder().WithTcpServer("iot.eclipse.org", 1883).WithProtocolVersion(MqttProtocolVersion.V500).Build()); await ExecuteTestAsync("iot.eclipse.org WS", - new MqttClientOptionsBuilder().WithWebSocketServer("iot.eclipse.org:80/mqtt").Build()); + new MqttClientOptionsBuilder().WithWebSocketServer("iot.eclipse.org:80/mqtt").WithProtocolVersion(MqttProtocolVersion.V500).Build()); await ExecuteTestAsync("iot.eclipse.org WS TLS", - new MqttClientOptionsBuilder().WithWebSocketServer("iot.eclipse.org:443/mqtt").WithTls().Build()); + new MqttClientOptionsBuilder().WithWebSocketServer("iot.eclipse.org:443/mqtt").WithProtocolVersion(MqttProtocolVersion.V500).WithTls().Build()); // test.mosquitto.org await ExecuteTestAsync("test.mosquitto.org TCP", - new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 1883).Build()); + new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 1883).WithProtocolVersion(MqttProtocolVersion.V500).Build()); await ExecuteTestAsync("test.mosquitto.org TCP TLS", - new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 8883).WithTls().Build()); + new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 8883).WithProtocolVersion(MqttProtocolVersion.V500).WithTls().Build()); await ExecuteTestAsync("test.mosquitto.org WS", - new MqttClientOptionsBuilder().WithWebSocketServer("test.mosquitto.org:8080/mqtt").Build()); + new MqttClientOptionsBuilder().WithWebSocketServer("test.mosquitto.org:8080/mqtt").WithProtocolVersion(MqttProtocolVersion.V500).Build()); await ExecuteTestAsync("test.mosquitto.org WS TLS", - new MqttClientOptionsBuilder().WithWebSocketServer("test.mosquitto.org:8081/mqtt").WithTls().Build()); + new MqttClientOptionsBuilder().WithWebSocketServer("test.mosquitto.org:8081/mqtt").WithProtocolVersion(MqttProtocolVersion.V500).WithTls().Build()); // broker.hivemq.com await ExecuteTestAsync("broker.hivemq.com TCP", - new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com", 1883).Build()); + new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com", 1883).WithProtocolVersion(MqttProtocolVersion.V500).Build()); await ExecuteTestAsync("broker.hivemq.com WS", - new MqttClientOptionsBuilder().WithWebSocketServer("broker.hivemq.com:8000/mqtt").Build()); + new MqttClientOptionsBuilder().WithWebSocketServer("broker.hivemq.com:8000/mqtt").WithProtocolVersion(MqttProtocolVersion.V500).Build()); // mqtt.swifitch.cz await ExecuteTestAsync("mqtt.swifitch.cz", - new MqttClientOptionsBuilder().WithTcpServer("mqtt.swifitch.cz", 1883).Build()); + new MqttClientOptionsBuilder().WithTcpServer("mqtt.swifitch.cz", 1883).WithProtocolVersion(MqttProtocolVersion.V500).Build()); // CloudMQTT var configFile = Path.Combine("E:\\CloudMqttTestConfig.json"); @@ -56,13 +57,13 @@ namespace MQTTnet.TestApp.NetCore var config = JsonConvert.DeserializeObject(File.ReadAllText(configFile)); await ExecuteTestAsync("CloudMQTT TCP", - new MqttClientOptionsBuilder().WithTcpServer(config.Server, config.Port).WithCredentials(config.Username, config.Password).Build()); + new MqttClientOptionsBuilder().WithTcpServer(config.Server, config.Port).WithCredentials(config.Username, config.Password).WithProtocolVersion(MqttProtocolVersion.V500).Build()); await ExecuteTestAsync("CloudMQTT TCP TLS", - new MqttClientOptionsBuilder().WithTcpServer(config.Server, config.SslPort).WithCredentials(config.Username, config.Password).WithTls().Build()); + new MqttClientOptionsBuilder().WithTcpServer(config.Server, config.SslPort).WithCredentials(config.Username, config.Password).WithTls().WithProtocolVersion(MqttProtocolVersion.V500).Build()); await ExecuteTestAsync("CloudMQTT WS TLS", - new MqttClientOptionsBuilder().WithWebSocketServer(config.Server + ":" + config.SslWebSocketPort + "/mqtt").WithCredentials(config.Username, config.Password).WithTls().Build()); + new MqttClientOptionsBuilder().WithWebSocketServer(config.Server + ":" + config.SslWebSocketPort + "/mqtt").WithCredentials(config.Username, config.Password).WithTls().WithProtocolVersion(MqttProtocolVersion.V500).Build()); } Write("Finished.", ConsoleColor.White); diff --git a/Tests/MQTTnet.TestApp.NetCore/ServerAndClientTest.cs b/Tests/MQTTnet.TestApp.NetCore/ServerAndClientTest.cs index 30ba9140..acd01b16 100644 --- a/Tests/MQTTnet.TestApp.NetCore/ServerAndClientTest.cs +++ b/Tests/MQTTnet.TestApp.NetCore/ServerAndClientTest.cs @@ -1,6 +1,7 @@ using System.Threading; using System.Threading.Tasks; using MQTTnet.Client; +using MQTTnet.Client.Options; using MQTTnet.Server; namespace MQTTnet.TestApp.NetCore diff --git a/Tests/MQTTnet.TestApp.UniversalWindows/MQTTnet.TestApp.UniversalWindows.csproj b/Tests/MQTTnet.TestApp.UniversalWindows/MQTTnet.TestApp.UniversalWindows.csproj index 551bd2eb..e408f10a 100644 --- a/Tests/MQTTnet.TestApp.UniversalWindows/MQTTnet.TestApp.UniversalWindows.csproj +++ b/Tests/MQTTnet.TestApp.UniversalWindows/MQTTnet.TestApp.UniversalWindows.csproj @@ -18,7 +18,7 @@ {A5A43C5B-DE2A-4C0C-9213-0A381AF9435A};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} true MQTTnet.TestApp.UniversalWindows_TemporaryKey.pfx - 05F70CAF1426E296BE2F7396F0B654070D72B930 + 6B99AEE2D6C4A05C2FC9162A71D02388D66DFEEF win10;win10-arm;win10-arm-aot;win10-x86;win10-x86-aot;win10-x64;win10-x64-aot diff --git a/Tests/MQTTnet.TestApp.UniversalWindows/MQTTnet.TestApp.UniversalWindows_TemporaryKey.pfx b/Tests/MQTTnet.TestApp.UniversalWindows/MQTTnet.TestApp.UniversalWindows_TemporaryKey.pfx index 1adc8878176c4eac6fecb87e8c9868c5d1d510da..0af6b538450a408ce08dad94e50421c15f6bb8d9 100644 GIT binary patch delta 2339 zcmV+;3EcL`6UY;PFoFrh0s#Xsf(d{I2`Yw2hW8Bt2LYgh32_9132iWf3288b1^NaF zDuzgg_YDCD0ic2f>jZ)Y=P-f=<1m5+-v$dRhDe6@4FL=a0Ro_c1pY9B1o|)>1_~;M zNQUCN?=ATYhP_>+#bH)0l_q5sfQ^O zaS9UHdQ8!8)y9C!df2K3{Lul=#n_uB*>m zA!;c7$1|FnJD-GTKnRsd_3=ALov@QrsLZ;W;s}m806m?51Fzotu$SYv`7CpnJtR0J zEfp0&WGw#YiEDtykg8~lkLxtdzS*&nz}zjbFU+OsO$qH1fN?F(bImsU zYQ37GNq;YX6(|%`5;c;iz8=_l^6K!0)3NcY^oO5Elc}23nKn&*5s4F@Mv%&aV`g8( z)x!-u%7s?7t>X){*e-!RTXJNvL%Sw;Rwbt8BOb|)Hs>@E`T zIYb}Ha-2g}Vyxs)8Nh{>Oh)Un`58F9AQdn%xT}ejmx^#29??_#cWdafmOW>PelT$1 z9{H#3Yo~UKlzOv|Ybi#+lc`w2?$=`HZX3D^75JN@@ zsSUTd>Nvo~CYQ{Af;KWRSpwM^)F|n9Y{7mBn7rsSiWN^=Y=MHIChRU%DP<=~!u2Jk zVp#JdkNkj1U-YmalKFNH8uLN{S?9{?9zJ-byyufh1u91~0A>I&0Av6&05$++0A&Cz z0A>JV05kwM04)GC0ATI&05JeE0Ac_+05SkF z0AiE51)6^ezb+i>3Sct=0te85f&(DALvK!!{khH_D|GZr7(-k{#+XpMa3N2h#MnVA z(G-&{o}w=O<#WdnF74_o3na`x7@WZW7rKzJ;7NBxYp_NLj@;qQ??(!J$_u{reIit( z1PLjq3-DRaBN)F4&-OujQ6AaQ2V%P~4EM5D z;XsC$!M)MGUm3OkxIF(6(<537Ho((*XzcAl``MAwT{;PkaZ@>_pG8b_3AEc|chCo3 z8oG?<=wBVwwmpO=Fo;4#Z0W!hPw-m;KqrQ_pWG}=hgP6?he13Pso5uy-c^|?lHtbV4b+g^(5cvqGgf4!n9Ol3Z;iS2rK2=s|dF)%wY zA20_71uG5%0vZGqUAEW6&LdVdT;Aa$7Dn847zyEf1QbD7e&x`CF6TMJUPJ3P9 JS_T3F2hbq0Nx}dC delta 2339 zcmV+;3EcL`6UY;PFoFrZ0s#Xsf(dvA2`Yw2hW8Bt2LYgh326j^31u*X31KjT1@Z<7 zDuzgg_YDCD0ic2f;{<{Q-!Os&+c1I!*9HqJhDe6@4FL=a0Ro_c1okk31oAK(1_~;M zNQU+CR9U6c-`hFjyfueJPT6llsCTtkI<859?6oQO%!UC%sWQRstIr z`1X^kWxKxMXhlz^gYst)0qlbD7Cw#x~zHSJAG7^l3P}GbB9B;+Dn4-&xuH`L8 zCxDbY)hL8v@#N|d^NmHc4l8K@TKT2{RIr|WRfpn|K3|dY#x5ZlNFLUedr`L{1tZb| zIXeoR@laUa);Fy+7DVxPRhf!Q?dA^uVlxSDm9xu+kAe}J%Ka)SNRZ_K7!w~T;1lBN z)9%@S7CCB#oOgmlSS4ja9tTWzfaRO{)&>O}mpm~Su1iI#;_^?hCQzH{TEArqefu$MEJG?Cd) z6BSp5%kJnkPvL|d!njxtmD_LUnLe$5NKcuuU=3%#rQSZyK*~ncj3kaAK|wwDf3I5u zu*jzh)L)?3u*5Wg#|UIpCBlK7o93+ZU2Lm|zbYpmHTEIwNkZ2Q2)jh7SANf!y+myQ zQm4G9Fx-6CD0dUfbr*j9>5dnCul?j-;i#k%t{7VJKChHLVkAo*8LwLz|CaIQ9I>hy8RWu+X)p4f9l6Sv zxpHb9f=GK-jHF>~9zFpFcFV5D!%`6T{wQVp8K~1Kb1IcK%^dXxa4-&?D7@gjmV}UF zFPf31Nn2)r{%_qq&es2Tnh-M*u&B9E15QsKM14EsY3zLDi%!ib zDh@=VOIo*q+>fN5*(LW96<9mcF(?+dQcFW{=^MEyEXbMPfHYd9qT2;%S6!|)1mdWV zVG7H5pauN<(ddSTjqmw8a;9iaoqDZvOl<{2Ep7>0<={WyM_`UXSSZL+Ow$GgYQ)}9 z-d)fQlu}qWGc)Kw;c#ThxSTT6O$jkTKRFE(;h(9`Dj5{&UhJV|@5#77Teyn{f`IlG zrw~BHU+{tKnSBy4W@x&dfJQPd3hR?l1u91{05t$H05Je!05Sk#0A&Cz05Je$05$++ z04)GC05||P0673H0Ac`T0Av7R04)Gz05t$P05AYE05JeI0673P05fN-gMK#kvB z^-q>Un{@PU6G3Xg>0k*^U5x1Gr2n%3$!1FA(jl`e-@8?}9Kq)UAzW~-CgUG2UWRRv z0#~bZ55iR{Qn(PWD1)DTCfu)$Cux6$FB{p`-eW_e*sW7H7>W0VK`P_WF4y=fRuuE3 zi3!8$dqPudLBg+VHkEGk*f%4n#RUv_XoqPOh>A2Gsi(5w6TDpt3aW+$d4NIv(qk)| zN_QYqnRDboHb6lnoi6^kdIGq>OH@mP;meDsrh{MOUqM%qBFR9%_Fk?IjNn3>WV&Ht4LT&ENo>eb>*7*C>B^hJwf8d{09Sd>Hg#g8gQw|4#87sDA+X+_r*!9!CbY4IpE-z)-aIY|54ZP8Lw2PY;9Ew%e7tFEd}==3HX_9Er7$}%A20_71uG5% z0vZGqEzLqSA*dH^LgoLX5+&$WXgFt61QbQPP3h)LaBI#2)r diff --git a/Tests/MQTTnet.TestApp.UniversalWindows/MainPage.xaml b/Tests/MQTTnet.TestApp.UniversalWindows/MainPage.xaml index 44f3486b..bc1cdf45 100644 --- a/Tests/MQTTnet.TestApp.UniversalWindows/MainPage.xaml +++ b/Tests/MQTTnet.TestApp.UniversalWindows/MainPage.xaml @@ -101,26 +101,35 @@ + + + Topic: - Payload: - + Content type: + - Payload format: - + Payload: + + + Payload format: + Plain text Base64 string - QoS level: - + QoS level: + 0 (At most once) 1 (At least once) 2 (Exactly once) + + Response topic: + Retain diff --git a/Tests/MQTTnet.TestApp.UniversalWindows/MainPage.xaml.cs b/Tests/MQTTnet.TestApp.UniversalWindows/MainPage.xaml.cs index 6992b3cc..44bf102a 100644 --- a/Tests/MQTTnet.TestApp.UniversalWindows/MainPage.xaml.cs +++ b/Tests/MQTTnet.TestApp.UniversalWindows/MainPage.xaml.cs @@ -1,13 +1,13 @@ using System; using System.Collections.Concurrent; using System.Collections.ObjectModel; -using System.IO; using System.Text; using System.Threading.Tasks; using Windows.Security.Cryptography.Certificates; using Windows.UI.Core; using Windows.UI.Xaml; using MQTTnet.Client; +using MQTTnet.Client.Options; using MQTTnet.Diagnostics; using MQTTnet.Exceptions; using MQTTnet.Extensions.ManagedClient; @@ -16,8 +16,8 @@ using MQTTnet.Formatter; using MQTTnet.Implementations; using MQTTnet.Protocol; using MQTTnet.Server; -using MqttClientConnectedEventArgs = MQTTnet.Client.MqttClientConnectedEventArgs; -using MqttClientDisconnectedEventArgs = MQTTnet.Client.MqttClientDisconnectedEventArgs; +using MqttClientConnectedEventArgs = MQTTnet.Client.Connecting.MqttClientConnectedEventArgs; +using MqttClientDisconnectedEventArgs = MQTTnet.Client.Disconnecting.MqttClientDisconnectedEventArgs; namespace MQTTnet.TestApp.UniversalWindows { @@ -234,6 +234,8 @@ namespace MQTTnet.TestApp.UniversalWindows } var message = new MqttApplicationMessageBuilder() + .WithContentType(ContentType.Text) + .WithResponseTopic(ResponseTopic.Text) .WithTopic(Topic.Text) .WithPayload(payload) .WithQualityOfServiceLevel(qos) @@ -266,7 +268,7 @@ namespace MQTTnet.TestApp.UniversalWindows _mqttClient.Dispose(); _mqttClient = null; } - + if (_managedMqttClient != null) { await _managedMqttClient.StopAsync(); @@ -305,14 +307,16 @@ namespace MQTTnet.TestApp.UniversalWindows qos = MqttQualityOfServiceLevel.ExactlyOnce; } + var topicFilter = new TopicFilter { Topic = SubscribeTopic.Text, QualityOfServiceLevel = qos }; + if (_mqttClient != null) { - await _mqttClient.SubscribeAsync(new TopicFilter(SubscribeTopic.Text, qos)); + await _mqttClient.SubscribeAsync(topicFilter); } if (_managedMqttClient != null) { - await _managedMqttClient.SubscribeAsync(new TopicFilter(SubscribeTopic.Text, qos)); + await _managedMqttClient.SubscribeAsync(topicFilter); } } catch (Exception exception) diff --git a/Tests/MQTTnet.TestApp.UniversalWindows/Package.appxmanifest b/Tests/MQTTnet.TestApp.UniversalWindows/Package.appxmanifest index b677c2aa..81914fad 100644 --- a/Tests/MQTTnet.TestApp.UniversalWindows/Package.appxmanifest +++ b/Tests/MQTTnet.TestApp.UniversalWindows/Package.appxmanifest @@ -1,6 +1,6 @@  - + MQTTnet.TestApp.UniversalWindows From 6c87e4e039b2c0077282fa7eb0ac90ad7517e7b4 Mon Sep 17 00:00:00 2001 From: Christian Kratky Date: Sat, 22 Dec 2018 09:26:52 +0100 Subject: [PATCH 016/124] Replace nuget version with placeholder. --- Build/MQTTnet.AspNetCore.nuspec | 3 ++- Build/MQTTnet.Extensions.ManagedClient.nuspec | 2 +- Build/MQTTnet.Extensions.Rpc.nuspec | 2 +- Build/build.ps1 | 6 +++++- 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/Build/MQTTnet.AspNetCore.nuspec b/Build/MQTTnet.AspNetCore.nuspec index e57035e0..c95f21f3 100644 --- a/Build/MQTTnet.AspNetCore.nuspec +++ b/Build/MQTTnet.AspNetCore.nuspec @@ -14,7 +14,8 @@ Copyright Christian Kratky 2016-2018 MQTT Message Queue Telemetry Transport MQTTClient MQTTServer Server MQTTBroker Broker NETStandard IoT InternetOfThings Messaging Hardware Arduino Sensor Actuator M2M ESP Smart Home Cities Automation Xamarin - + + diff --git a/Build/MQTTnet.Extensions.ManagedClient.nuspec b/Build/MQTTnet.Extensions.ManagedClient.nuspec index a34c8d6e..bf3fa157 100644 --- a/Build/MQTTnet.Extensions.ManagedClient.nuspec +++ b/Build/MQTTnet.Extensions.ManagedClient.nuspec @@ -14,7 +14,7 @@ Copyright Christian Kratky 2016-2018 MQTT Message Queue Telemetry Transport MQTTClient MQTTServer Server MQTTBroker Broker NETStandard IoT InternetOfThings Messaging Hardware Arduino Sensor Actuator M2M ESP Smart Home Cities Automation Xamarin - + diff --git a/Build/MQTTnet.Extensions.Rpc.nuspec b/Build/MQTTnet.Extensions.Rpc.nuspec index bef33228..afe91468 100644 --- a/Build/MQTTnet.Extensions.Rpc.nuspec +++ b/Build/MQTTnet.Extensions.Rpc.nuspec @@ -14,7 +14,7 @@ Copyright Christian Kratky 2016-2018 MQTT Message Queue Telemetry Transport MQTTClient MQTTServer Server MQTTBroker Broker NETStandard IoT InternetOfThings Messaging Hardware Arduino Sensor Actuator M2M ESP Smart Home Cities Automation Xamarin - + diff --git a/Build/build.ps1 b/Build/build.ps1 index 63bae548..a31dc12e 100644 --- a/Build/build.ps1 +++ b/Build/build.ps1 @@ -39,7 +39,11 @@ if ($path) { Remove-Item .\NuGet -Force -Recurse -ErrorAction SilentlyContinue - New-Item -ItemType Directory -Force -Path .\NuGet + (Get-Content MQTTnet.AspNetCore.nuspec) -replace '$nugetVersion', $nugetVersion | Set-Content MQTTnet.AspNetCore.nuspec + (Get-Content MQTTnet.Extensions.Rpc.nuspec) -replace '$nugetVersion', $nugetVersion | Set-Content MQTTnet.Extensions.Rpc.nuspec + (Get-Content MQTTnet.Extensions.ManagedClient.nuspec) -replace '$nugetVersion', $nugetVersion | Set-Content Extensions.ManagedClient.nuspec + + New-Item -ItemType Directory -Force -Path .\NuGet .\nuget.exe pack MQTTnet.nuspec -Verbosity detailed -Symbols -OutputDir "NuGet" -Version $nugetVersion .\nuget.exe pack MQTTnet.AspNetCore.nuspec -Verbosity detailed -Symbols -OutputDir "NuGet" -Version $nugetVersion .\nuget.exe pack MQTTnet.Extensions.Rpc.nuspec -Verbosity detailed -Symbols -OutputDir "NuGet" -Version $nugetVersion From 89becaa19182e4ff2ce19634b6d061b2337b6033 Mon Sep 17 00:00:00 2001 From: Christian Kratky Date: Sat, 22 Dec 2018 09:28:51 +0100 Subject: [PATCH 017/124] Fix build script. --- Build/build.ps1 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Build/build.ps1 b/Build/build.ps1 index a31dc12e..79e56b7a 100644 --- a/Build/build.ps1 +++ b/Build/build.ps1 @@ -39,9 +39,9 @@ if ($path) { Remove-Item .\NuGet -Force -Recurse -ErrorAction SilentlyContinue - (Get-Content MQTTnet.AspNetCore.nuspec) -replace '$nugetVersion', $nugetVersion | Set-Content MQTTnet.AspNetCore.nuspec - (Get-Content MQTTnet.Extensions.Rpc.nuspec) -replace '$nugetVersion', $nugetVersion | Set-Content MQTTnet.Extensions.Rpc.nuspec - (Get-Content MQTTnet.Extensions.ManagedClient.nuspec) -replace '$nugetVersion', $nugetVersion | Set-Content Extensions.ManagedClient.nuspec + (Get-Content MQTTnet.AspNetCore.nuspec) -replace '\$nugetVersion', $nugetVersion | Set-Content MQTTnet.AspNetCore.nuspec + (Get-Content MQTTnet.Extensions.Rpc.nuspec) -replace '\$nugetVersion', $nugetVersion | Set-Content MQTTnet.Extensions.Rpc.nuspec + (Get-Content MQTTnet.Extensions.ManagedClient.nuspec) -replace '\$nugetVersion', $nugetVersion | Set-Content MQTTnet.Extensions.ManagedClient.nuspec New-Item -ItemType Directory -Force -Path .\NuGet .\nuget.exe pack MQTTnet.nuspec -Verbosity detailed -Symbols -OutputDir "NuGet" -Version $nugetVersion From 45df33c9a59f7a41bccf50c3fa429649f142fd07 Mon Sep 17 00:00:00 2001 From: Christian Kratky Date: Sat, 22 Dec 2018 09:33:44 +0100 Subject: [PATCH 018/124] Update nuspec. --- Build/MQTTnet.nuspec | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/Build/MQTTnet.nuspec b/Build/MQTTnet.nuspec index 5814a730..bb82dd27 100644 --- a/Build/MQTTnet.nuspec +++ b/Build/MQTTnet.nuspec @@ -10,19 +10,10 @@ https://raw.githubusercontent.com/chkr1011/MQTTnet/master/Images/Logo_128x128.png false MQTTnet is a high performance .NET library for MQTT based communication. It provides a MQTT client and a MQTT server (broker). - * [Core] Updated nuget packages due to security issues. -* [Client] Fixed wrong behavior of publish method when client is disconnecting (thanks to @PaulFake). -* [Client] Added readonly property for accessing options. -* [ManagedClient] Added max pending messages count option. -* [ManagedClient] Add pending messages overflow strategy option. -* [ManagedClient] Fixed an issue which deletes the wrong message from the internal queue (thanks to @PaulFake). -* [ManagedClient] Added readonly property for accessing options. -* [Server] Added new method which exposes all retained messages. -* [Server] Removed (wrong) setter from the server options interface. -* [Server] Fixed cpu spike in case a client disconnects (issue 421). -* [Server] Fixed concurrent sends with Aspnetcore.Connections.Abstractions based transport. -* [Server] Fixed wrong retain flag when distributing application messages (thanks to @trev0115). -* [Server] Fixed issue which closes a connection when reconnecting with the same client ID (thanks to @fogzot). + * [Core] Added support for MQTTv5 packages. +* [Client] Added new MQTTv5 features to options builder. +* [Client] Added uniform API across all supported MQTT versions (BREAKING CHANGE!) +* [Server] Added support for MQTTv5 clients. The server will still return _success_ for all cases at the moment even if more granular codes are available. Copyright Christian Kratky 2016-2018 MQTT Message Queue Telemetry Transport MQTTClient MQTTServer Server MQTTBroker Broker NETStandard IoT InternetOfThings Messaging Hardware Arduino Sensor Actuator M2M ESP Smart Home Cities Automation Xamarin From 058bb65d3148c31bbf243502260873bc18875d12 Mon Sep 17 00:00:00 2001 From: Christian Kratky Date: Sat, 22 Dec 2018 09:40:47 +0100 Subject: [PATCH 019/124] Merge from develop. --- Build/MQTTnet.nuspec | 2 ++ .../MqttClientOptionsBuilderWebSocketParameters.cs | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) rename Source/MQTTnet/Client/{ => Options}/MqttClientOptionsBuilderWebSocketParameters.cs (89%) diff --git a/Build/MQTTnet.nuspec b/Build/MQTTnet.nuspec index bb82dd27..07ee7363 100644 --- a/Build/MQTTnet.nuspec +++ b/Build/MQTTnet.nuspec @@ -14,6 +14,8 @@ * [Client] Added new MQTTv5 features to options builder. * [Client] Added uniform API across all supported MQTT versions (BREAKING CHANGE!) * [Server] Added support for MQTTv5 clients. The server will still return _success_ for all cases at the moment even if more granular codes are available. + +* [Note] Due to MQTTv5 a lot of new classes were introduced. This required adding new namespaces as well. Most classes are backward compatible but new namespaces must be added. Copyright Christian Kratky 2016-2018 MQTT Message Queue Telemetry Transport MQTTClient MQTTServer Server MQTTBroker Broker NETStandard IoT InternetOfThings Messaging Hardware Arduino Sensor Actuator M2M ESP Smart Home Cities Automation Xamarin diff --git a/Source/MQTTnet/Client/MqttClientOptionsBuilderWebSocketParameters.cs b/Source/MQTTnet/Client/Options/MqttClientOptionsBuilderWebSocketParameters.cs similarity index 89% rename from Source/MQTTnet/Client/MqttClientOptionsBuilderWebSocketParameters.cs rename to Source/MQTTnet/Client/Options/MqttClientOptionsBuilderWebSocketParameters.cs index 54e048bc..f516e837 100644 --- a/Source/MQTTnet/Client/MqttClientOptionsBuilderWebSocketParameters.cs +++ b/Source/MQTTnet/Client/Options/MqttClientOptionsBuilderWebSocketParameters.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; using System.Net; -namespace MQTTnet.Client +namespace MQTTnet.Client.Options { public class MqttClientOptionsBuilderWebSocketParameters { From 38f2453c925432db0bbf0f1f5b588f9f763516e0 Mon Sep 17 00:00:00 2001 From: Christian Date: Sat, 22 Dec 2018 20:25:53 +0100 Subject: [PATCH 020/124] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 5b5288e8..e0e0bc24 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ MQTTnet is a high performance .NET library for MQTT based communication. It prov * Extensible communication channels (e.g. In-Memory, TCP, TCP+TLS, WS) * Lightweight (only the low level implementation of MQTT, no overhead) * Performance optimized (processing ~70.000 messages / second)* -* Uniform API over all versions of MQTT protocol +* Uniform API across all supported versions of the MQTT protocol * Interfaces included for mocking and testing * Access to internal trace messages * Unit tested (~120 tests) @@ -97,7 +97,7 @@ If you use this library and want to see your project here please create a pull r ## MIT License -Copyright (c) 2017-2018 Christian Kratky +Copyright (c) 2017-2019 Christian Kratky Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From c0507fcc55d452a147950c44e24634ac642b572a Mon Sep 17 00:00:00 2001 From: Christian Kratky Date: Thu, 27 Dec 2018 22:41:41 +0100 Subject: [PATCH 021/124] Refactor interceptors etc. to support async. --- .../ManagedMqttClientStorageManager.cs | 4 +- Source/MQTTnet/Formatter/MqttPacketReader.cs | 2 +- Source/MQTTnet/Formatter/MqttPacketWriter.cs | 26 ++- .../Formatter/V3/MqttV310PacketFormatter.cs | 4 +- .../Formatter/V5/MqttV500PacketEncoder.cs | 4 +- .../MQTTnet/Implementations/MqttTcpChannel.cs | 34 ++- Source/MQTTnet/Internal/AsyncLock.cs | 23 +- Source/MQTTnet/Server/IMqttClientSession.cs | 3 +- Source/MQTTnet/Server/IMqttServer.cs | 3 - ...MqttServerApplicationMessageInterceptor.cs | 9 + ...MqttServerClientMessageQueueInterceptor.cs | 9 + .../Server/IMqttServerConnectionValidator.cs | 9 + Source/MQTTnet/Server/IMqttServerOptions.cs | 8 +- .../IMqttServerSubscriptionInterceptor.cs | 9 + .../Server/MqttClientKeepAliveMonitor.cs | 2 +- .../Server/MqttClientPendingPacketsQueue.cs | 4 +- Source/MQTTnet/Server/MqttClientSession.cs | 207 +++++++++--------- .../MQTTnet/Server/MqttClientSessionStatus.cs | 11 +- .../Server/MqttClientSessionsManager.cs | 96 ++++---- .../Server/MqttClientSubscriptionsManager.cs | 17 +- .../Server/MqttRetainedMessagesManager.cs | 49 ++--- Source/MQTTnet/Server/MqttServer.cs | 11 +- ...erApplicationMessageInterceptorDelegate.cs | 31 +++ .../MqttServerConnectionValidatorDelegate.cs | 31 +++ Source/MQTTnet/Server/MqttServerOptions.cs | 10 +- .../Server/MqttServerOptionsBuilder.cs | 24 +- ...ttServerSubscriptionInterceptorDelegate.cs | 31 +++ Tests/MQTTnet.Core.Tests/AsyncLockTests.cs | 2 +- .../MqttKeepAliveMonitorTests.cs | 3 +- Tests/MQTTnet.Core.Tests/MqttServerTests.cs | 135 +++++++++--- .../MqttSubscriptionsManagerTests.cs | 10 +- Tests/MQTTnet.TestApp.NetCore/ServerTest.cs | 13 +- .../MainPage.xaml.cs | 14 +- 33 files changed, 537 insertions(+), 311 deletions(-) create mode 100644 Source/MQTTnet/Server/IMqttServerApplicationMessageInterceptor.cs create mode 100644 Source/MQTTnet/Server/IMqttServerClientMessageQueueInterceptor.cs create mode 100644 Source/MQTTnet/Server/IMqttServerConnectionValidator.cs create mode 100644 Source/MQTTnet/Server/IMqttServerSubscriptionInterceptor.cs create mode 100644 Source/MQTTnet/Server/MqttServerApplicationMessageInterceptorDelegate.cs create mode 100644 Source/MQTTnet/Server/MqttServerConnectionValidatorDelegate.cs create mode 100644 Source/MQTTnet/Server/MqttServerSubscriptionInterceptorDelegate.cs diff --git a/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClientStorageManager.cs b/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClientStorageManager.cs index 821a7d92..772f102e 100644 --- a/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClientStorageManager.cs +++ b/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClientStorageManager.cs @@ -30,7 +30,7 @@ namespace MQTTnet.Extensions.ManagedClient { if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage)); - using (await _messagesLock.LockAsync(CancellationToken.None).ConfigureAwait(false)) + using (await _messagesLock.WaitAsync(CancellationToken.None).ConfigureAwait(false)) { _messages.Add(applicationMessage); await SaveAsync().ConfigureAwait(false); @@ -41,7 +41,7 @@ namespace MQTTnet.Extensions.ManagedClient { if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage)); - using (await _messagesLock.LockAsync(CancellationToken.None).ConfigureAwait(false)) + using (await _messagesLock.WaitAsync(CancellationToken.None).ConfigureAwait(false)) { var index = _messages.IndexOf(applicationMessage); if (index == -1) diff --git a/Source/MQTTnet/Formatter/MqttPacketReader.cs b/Source/MQTTnet/Formatter/MqttPacketReader.cs index dfb633af..eec643a1 100644 --- a/Source/MQTTnet/Formatter/MqttPacketReader.cs +++ b/Source/MQTTnet/Formatter/MqttPacketReader.cs @@ -63,7 +63,7 @@ namespace MQTTnet.Formatter { var offset = 0; var multiplier = 128; - var value = (initialEncodedByte & 127); + var value = initialEncodedByte & 127; int encodedByte = initialEncodedByte; while ((encodedByte & 128) != 0) diff --git a/Source/MQTTnet/Formatter/MqttPacketWriter.cs b/Source/MQTTnet/Formatter/MqttPacketWriter.cs index bf417f77..da483bb6 100644 --- a/Source/MQTTnet/Formatter/MqttPacketWriter.cs +++ b/Source/MQTTnet/Formatter/MqttPacketWriter.cs @@ -15,6 +15,7 @@ namespace MQTTnet.Formatter { public static int MaxBufferSize = 4096; + // TODO: Consider using the ArrayPool here together with FreeBuffer. private byte[] _buffer = new byte[128]; private int _offset; @@ -28,13 +29,18 @@ namespace MQTTnet.Formatter return (byte)fixedHeader; } - public static ArraySegment EncodeVariableByteInteger(uint value) + public static ArraySegment EncodeVariableLengthInteger(uint value) { - if (value <= 0) + if (value == 0) { return new ArraySegment(new byte[1], 0, 1); } + if (value <= 127) + { + return new ArraySegment(new[] { (byte)value }, 0, 1); + } + var buffer = new byte[4]; var bufferOffset = 0; @@ -57,7 +63,7 @@ namespace MQTTnet.Formatter public void WriteVariableLengthInteger(uint value) { - Write(EncodeVariableByteInteger(value)); + Write(EncodeVariableLengthInteger(value)); } public void WriteWithLengthPrefix(string value) @@ -80,7 +86,7 @@ namespace MQTTnet.Formatter EnsureAdditionalCapacity(1); _buffer[_offset] = @byte; - IncreasePostition(1); + IncreasePosition(1); } public void Write(ushort value) @@ -88,9 +94,9 @@ namespace MQTTnet.Formatter EnsureAdditionalCapacity(2); _buffer[_offset] = (byte)(value >> 8); - IncreasePostition(1); + IncreasePosition(1); _buffer[_offset] = (byte)value; - IncreasePostition(1); + IncreasePosition(1); } public void Write(byte[] buffer, int offset, int count) @@ -100,7 +106,7 @@ namespace MQTTnet.Formatter EnsureAdditionalCapacity(count); Array.Copy(buffer, offset, _buffer, _offset, count); - IncreasePostition(count); + IncreasePosition(count); } public void Write(ArraySegment buffer) @@ -122,9 +128,9 @@ namespace MQTTnet.Formatter Write(propertyWriter._buffer, 0, propertyWriter.Length); } - public void Reset() + public void Reset(int length) { - Length = 5; + Length = length; } public void Seek(int position) @@ -185,7 +191,7 @@ namespace MQTTnet.Formatter } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void IncreasePostition(int length) + private void IncreasePosition(int length) { _offset += length; diff --git a/Source/MQTTnet/Formatter/V3/MqttV310PacketFormatter.cs b/Source/MQTTnet/Formatter/V3/MqttV310PacketFormatter.cs index e9f86022..923b9c66 100644 --- a/Source/MQTTnet/Formatter/V3/MqttV310PacketFormatter.cs +++ b/Source/MQTTnet/Formatter/V3/MqttV310PacketFormatter.cs @@ -21,13 +21,13 @@ namespace MQTTnet.Formatter.V3 if (packet == null) throw new ArgumentNullException(nameof(packet)); // Leave enough head space for max header size (fixed + 4 variable remaining length = 5 bytes) - _packetWriter.Reset(); + _packetWriter.Reset(5); _packetWriter.Seek(5); var fixedHeader = EncodePacket(packet, _packetWriter); var remainingLength = (uint)(_packetWriter.Length - 5); - var remainingLengthBuffer = MqttPacketWriter.EncodeVariableByteInteger(remainingLength); + var remainingLengthBuffer = MqttPacketWriter.EncodeVariableLengthInteger(remainingLength); var headerSize = FixedHeaderSize + remainingLengthBuffer.Count; var headerOffset = 5 - headerSize; diff --git a/Source/MQTTnet/Formatter/V5/MqttV500PacketEncoder.cs b/Source/MQTTnet/Formatter/V5/MqttV500PacketEncoder.cs index 0c0019c9..2dfde061 100644 --- a/Source/MQTTnet/Formatter/V5/MqttV500PacketEncoder.cs +++ b/Source/MQTTnet/Formatter/V5/MqttV500PacketEncoder.cs @@ -15,13 +15,13 @@ namespace MQTTnet.Formatter.V5 if (packet == null) throw new ArgumentNullException(nameof(packet)); // Leave enough head space for max header size (fixed + 4 variable remaining length = 5 bytes) - _packetWriter.Reset(); + _packetWriter.Reset(5); _packetWriter.Seek(5); var fixedHeader = EncodePacket(packet, _packetWriter); var remainingLength = (uint)(_packetWriter.Length - 5); - var remainingLengthBuffer = MqttPacketWriter.EncodeVariableByteInteger(remainingLength); + var remainingLengthBuffer = MqttPacketWriter.EncodeVariableLengthInteger(remainingLength); var headerSize = 1 + remainingLengthBuffer.Count; var headerOffset = 5 - headerSize; diff --git a/Source/MQTTnet/Implementations/MqttTcpChannel.cs b/Source/MQTTnet/Implementations/MqttTcpChannel.cs index 978f8b6f..e389ef09 100644 --- a/Source/MQTTnet/Implementations/MqttTcpChannel.cs +++ b/Source/MQTTnet/Implementations/MqttTcpChannel.cs @@ -52,11 +52,15 @@ namespace MQTTnet.Implementations _socket = new Socket(SocketType.Stream, ProtocolType.Tcp) { NoDelay = true }; } + // Workaround for: workaround for https://github.com/dotnet/corefx/issues/24430 + using (cancellationToken.Register(() => _socket.Dispose())) + { #if NET452 || NET461 - await Task.Factory.FromAsync(_socket.BeginConnect, _socket.EndConnect, _options.Server, _options.GetPort(), null).ConfigureAwait(false); + await Task.Factory.FromAsync(_socket.BeginConnect, _socket.EndConnect, _options.Server, _options.GetPort(), null).ConfigureAwait(false); #else - await _socket.ConnectAsync(_options.Server, _options.GetPort()).ConfigureAwait(false); + await _socket.ConnectAsync(_options.Server, _options.GetPort()).ConfigureAwait(false); #endif + } SslStream sslStream = null; if (_options.TlsOptions.UseTls) @@ -74,14 +78,23 @@ namespace MQTTnet.Implementations return Task.FromResult(0); } - public Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + public async Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) { - return _stream.ReadAsync(buffer, offset, count, cancellationToken); + // Workaround for: https://github.com/dotnet/corefx/issues/24430 + using (cancellationToken.Register(() => _socket.Dispose())) + { + return await _stream.ReadAsync(buffer, offset, count, cancellationToken).ConfigureAwait(false); + } } - public Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + public async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) { - return _stream.WriteAsync(buffer, offset, count, cancellationToken); + // Workaround for: https://github.com/dotnet/corefx/issues/24430 + using (cancellationToken.Register(() => _socket.Dispose())) + { + await _stream.WriteAsync(buffer, offset, count, cancellationToken).ConfigureAwait(false); + await _stream.FlushAsync(cancellationToken); + } } public void Dispose() @@ -89,10 +102,11 @@ namespace MQTTnet.Implementations Cleanup(ref _stream, s => s.Dispose()); Cleanup(ref _socket, s => { - if (s.Connected) - { - s.Shutdown(SocketShutdown.Both); - } + //if (s.Connected) + //{ + // s.Shutdown(SocketShutdown.Both); + //} + s.Dispose(); }); } diff --git a/Source/MQTTnet/Internal/AsyncLock.cs b/Source/MQTTnet/Internal/AsyncLock.cs index 87571c2a..9b7eefdd 100644 --- a/Source/MQTTnet/Internal/AsyncLock.cs +++ b/Source/MQTTnet/Internal/AsyncLock.cs @@ -15,14 +15,23 @@ namespace MQTTnet.Internal _releaser = Task.FromResult((IDisposable)new Releaser(this)); } - public Task LockAsync(CancellationToken cancellationToken) + public Task WaitAsync() { - var wait = _semaphore.WaitAsync(cancellationToken); - return wait.IsCompleted ? - _releaser : - wait.ContinueWith((_, state) => (IDisposable)state, - _releaser.Result, cancellationToken, - TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default); + return WaitAsync(CancellationToken.None); + } + + public Task WaitAsync(CancellationToken cancellationToken) + { + var task = _semaphore.WaitAsync(cancellationToken); + if (task.IsCompleted) + { + return _releaser; + } + + return task.ContinueWith( + (_, state) => (IDisposable)state, + _releaser.Result, + cancellationToken, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default); } public void Dispose() diff --git a/Source/MQTTnet/Server/IMqttClientSession.cs b/Source/MQTTnet/Server/IMqttClientSession.cs index 51341e9a..f1e3010e 100644 --- a/Source/MQTTnet/Server/IMqttClientSession.cs +++ b/Source/MQTTnet/Server/IMqttClientSession.cs @@ -1,4 +1,5 @@ using System; +using System.Threading.Tasks; namespace MQTTnet.Server { @@ -6,6 +7,6 @@ namespace MQTTnet.Server { string ClientId { get; } - void Stop(MqttClientDisconnectType disconnectType); + Task StopAsync(MqttClientDisconnectType disconnectType); } } \ No newline at end of file diff --git a/Source/MQTTnet/Server/IMqttServer.cs b/Source/MQTTnet/Server/IMqttServer.cs index 3a45973b..e42c903b 100644 --- a/Source/MQTTnet/Server/IMqttServer.cs +++ b/Source/MQTTnet/Server/IMqttServer.cs @@ -16,11 +16,8 @@ namespace MQTTnet.Server IMqttServerOptions Options { get; } - [Obsolete("This method is no longer async. Use the not async method.")] Task> GetClientSessionsStatusAsync(); - IList GetClientSessionsStatus(); - IList GetRetainedMessages(); Task ClearRetainedMessagesAsync(); diff --git a/Source/MQTTnet/Server/IMqttServerApplicationMessageInterceptor.cs b/Source/MQTTnet/Server/IMqttServerApplicationMessageInterceptor.cs new file mode 100644 index 00000000..64fe0471 --- /dev/null +++ b/Source/MQTTnet/Server/IMqttServerApplicationMessageInterceptor.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; + +namespace MQTTnet.Server +{ + public interface IMqttServerApplicationMessageInterceptor + { + Task InterceptApplicationMessagePublishAsync(MqttApplicationMessageInterceptorContext context); + } +} diff --git a/Source/MQTTnet/Server/IMqttServerClientMessageQueueInterceptor.cs b/Source/MQTTnet/Server/IMqttServerClientMessageQueueInterceptor.cs new file mode 100644 index 00000000..c6f97a73 --- /dev/null +++ b/Source/MQTTnet/Server/IMqttServerClientMessageQueueInterceptor.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; + +namespace MQTTnet.Server +{ + public interface IMqttServerClientMessageQueueInterceptor + { + Task InterceptClientMessageQueueEnqueueAsync(MqttClientMessageQueueInterceptorContext context); + } +} diff --git a/Source/MQTTnet/Server/IMqttServerConnectionValidator.cs b/Source/MQTTnet/Server/IMqttServerConnectionValidator.cs new file mode 100644 index 00000000..bd38c831 --- /dev/null +++ b/Source/MQTTnet/Server/IMqttServerConnectionValidator.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; + +namespace MQTTnet.Server +{ + public interface IMqttServerConnectionValidator + { + Task ValidateConnection(MqttConnectionValidatorContext context); + } +} diff --git a/Source/MQTTnet/Server/IMqttServerOptions.cs b/Source/MQTTnet/Server/IMqttServerOptions.cs index f9334fd7..d840c8f1 100644 --- a/Source/MQTTnet/Server/IMqttServerOptions.cs +++ b/Source/MQTTnet/Server/IMqttServerOptions.cs @@ -11,10 +11,10 @@ namespace MQTTnet.Server TimeSpan DefaultCommunicationTimeout { get; } - Action ConnectionValidator { get; } - Action SubscriptionInterceptor { get; } - Action ApplicationMessageInterceptor { get; } - Action ClientMessageQueueInterceptor { get; } + IMqttServerConnectionValidator ConnectionValidator { get; } + IMqttServerSubscriptionInterceptor SubscriptionInterceptor { get; } + IMqttServerApplicationMessageInterceptor ApplicationMessageInterceptor { get; } + IMqttServerClientMessageQueueInterceptor ClientMessageQueueInterceptor { get; } MqttServerTcpEndpointOptions DefaultEndpointOptions { get; } MqttServerTlsTcpEndpointOptions TlsEndpointOptions { get; } diff --git a/Source/MQTTnet/Server/IMqttServerSubscriptionInterceptor.cs b/Source/MQTTnet/Server/IMqttServerSubscriptionInterceptor.cs new file mode 100644 index 00000000..a7ce95ef --- /dev/null +++ b/Source/MQTTnet/Server/IMqttServerSubscriptionInterceptor.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; + +namespace MQTTnet.Server +{ + public interface IMqttServerSubscriptionInterceptor + { + Task InterceptSubscriptionAsync(MqttSubscriptionInterceptorContext context); + } +} diff --git a/Source/MQTTnet/Server/MqttClientKeepAliveMonitor.cs b/Source/MQTTnet/Server/MqttClientKeepAliveMonitor.cs index fbe1f87f..e8e7c150 100644 --- a/Source/MQTTnet/Server/MqttClientKeepAliveMonitor.cs +++ b/Source/MQTTnet/Server/MqttClientKeepAliveMonitor.cs @@ -81,7 +81,7 @@ namespace MQTTnet.Server if (!_isPaused && _lastPacketReceivedTracker.Elapsed.TotalSeconds >= keepAlivePeriod * 1.5D) { _logger.Warning(null, "Client '{0}': Did not receive any packet or keep alive signal.", _clientSession.ClientId); - _clientSession.Stop(MqttClientDisconnectType.NotClean); + await _clientSession.StopAsync(MqttClientDisconnectType.NotClean).ConfigureAwait(false); return; } diff --git a/Source/MQTTnet/Server/MqttClientPendingPacketsQueue.cs b/Source/MQTTnet/Server/MqttClientPendingPacketsQueue.cs index 503d9926..eddd7889 100644 --- a/Source/MQTTnet/Server/MqttClientPendingPacketsQueue.cs +++ b/Source/MQTTnet/Server/MqttClientPendingPacketsQueue.cs @@ -133,7 +133,7 @@ namespace MQTTnet.Server return; } - adapter.SendPacketAsync(packet, cancellationToken).GetAwaiter().GetResult(); + await adapter.SendPacketAsync(packet, cancellationToken).ConfigureAwait(false); _logger.Verbose("Enqueued packet sent (ClientId: {0}).", _clientSession.ClientId); } @@ -167,7 +167,7 @@ namespace MQTTnet.Server if (!cancellationToken.IsCancellationRequested) { - _clientSession.Stop(MqttClientDisconnectType.NotClean); + await _clientSession.StopAsync(MqttClientDisconnectType.NotClean).ConfigureAwait(false); } } } diff --git a/Source/MQTTnet/Server/MqttClientSession.cs b/Source/MQTTnet/Server/MqttClientSession.cs index 6eacfa67..9eaab4f1 100644 --- a/Source/MQTTnet/Server/MqttClientSession.cs +++ b/Source/MQTTnet/Server/MqttClientSession.cs @@ -79,22 +79,20 @@ namespace MQTTnet.Server return _workerTask; } - public void Stop(MqttClientDisconnectType type) + public Task StopAsync(MqttClientDisconnectType type) { - Stop(type, false); + return StopAsync(type, false); } - public Task SubscribeAsync(IList topicFilters) + public async Task SubscribeAsync(IList topicFilters) { if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters)); var packet = new MqttSubscribePacket(); packet.TopicFilters.AddRange(topicFilters); - _subscriptionsManager.Subscribe(packet); - - EnqueueSubscribedRetainedMessages(topicFilters); - return Task.FromResult(0); + await _subscriptionsManager.SubscribeAsync(packet).ConfigureAwait(false); + await EnqueueSubscribedRetainedMessagesAsync(topicFilters).ConfigureAwait(false); } public Task UnsubscribeAsync(IList topicFilters) @@ -122,7 +120,7 @@ namespace MQTTnet.Server _cancellationTokenSource?.Dispose(); _cancellationTokenSource = null; } - + private async Task RunInternalAsync(MqttConnectPacket connectPacket, IMqttChannelAdapter adapter) { if (adapter == null) throw new ArgumentNullException(nameof(adapter)); @@ -131,7 +129,7 @@ namespace MQTTnet.Server { if (_cancellationTokenSource != null) { - Stop(MqttClientDisconnectType.Clean, true); + await StopAsync(MqttClientDisconnectType.Clean, true).ConfigureAwait(false); } adapter.ReadingPacketStarted += OnAdapterReadingPacketStarted; @@ -139,15 +137,15 @@ namespace MQTTnet.Server _cancellationTokenSource = new CancellationTokenSource(); - //workaround for https://github.com/dotnet/corefx/issues/24430 -#pragma warning disable 4014 - _cleanupHandle = _cancellationTokenSource.Token.Register(async () => - { - await TryDisconnectAdapterAsync(adapter).ConfigureAwait(false); - TryDisposeAdapter(adapter); - }); -#pragma warning restore 4014 - //end workaround +// //workaround for https://github.com/dotnet/corefx/issues/24430 +//#pragma warning disable 4014 +// _cleanupHandle = _cancellationTokenSource.Token.Register(async () => +// { +// await TryDisconnectAdapterAsync(adapter).ConfigureAwait(false); +// TryDisposeAdapter(adapter); +// }); +//#pragma warning restore 4014 +// //end workaround _wasCleanDisconnect = false; _willMessage = connectPacket.WillMessage; @@ -165,7 +163,7 @@ namespace MQTTnet.Server if (packet != null) { _keepAliveMonitor.PacketReceived(packet); - ProcessReceivedPacket(adapter, packet, _cancellationTokenSource.Token); + await ProcessReceivedPacketAsync(adapter, packet, _cancellationTokenSource.Token).ConfigureAwait(false); } } } @@ -190,51 +188,55 @@ namespace MQTTnet.Server _logger.Error(exception, "Client '{0}': Unhandled exception while receiving client packets.", ClientId); } - Stop(MqttClientDisconnectType.NotClean, true); + await StopAsync(MqttClientDisconnectType.NotClean, true).ConfigureAwait(false); } finally { + adapter.ReadingPacketStarted -= OnAdapterReadingPacketStarted; + adapter.ReadingPacketCompleted -= OnAdapterReadingPacketCompleted; + _adapterEndpoint = null; _adapterProtocolVersion = null; // Uncomment as soon as the workaround above is no longer needed. + // Also called in outer scope! //await TryDisconnectAdapterAsync(adapter).ConfigureAwait(false); //TryDisposeAdapter(adapter); _cleanupHandle?.Dispose(); _cleanupHandle = null; - + _cancellationTokenSource?.Cancel(false); _cancellationTokenSource?.Dispose(); _cancellationTokenSource = null; } } - private void TryDisposeAdapter(IMqttChannelAdapter adapter) - { - if (adapter == null) - { - return; - } + ////private void TryDisposeAdapter(IMqttChannelAdapter adapter) + ////{ + //// if (adapter == null) + //// { + //// return; + //// } - try - { - adapter.ReadingPacketStarted -= OnAdapterReadingPacketStarted; - adapter.ReadingPacketCompleted -= OnAdapterReadingPacketCompleted; + //// try + //// { + //// adapter.ReadingPacketStarted -= OnAdapterReadingPacketStarted; + //// adapter.ReadingPacketCompleted -= OnAdapterReadingPacketCompleted; - adapter.Dispose(); - } - catch (Exception exception) - { - _logger.Error(exception, exception.Message); - } - finally - { - adapter.Dispose(); - } - } + //// adapter.Dispose(); + //// } + //// catch (Exception exception) + //// { + //// _logger.Error(exception, exception.Message); + //// } + //// finally + //// { + //// adapter.Dispose(); + //// } + ////} - private void Stop(MqttClientDisconnectType type, bool isInsideSession) + private async Task StopAsync(MqttClientDisconnectType type, bool isInsideSession) { try { @@ -257,7 +259,10 @@ namespace MQTTnet.Server if (!isInsideSession) { - _workerTask?.GetAwaiter().GetResult(); + if (_workerTask != null) + { + await _workerTask.ConfigureAwait(false); + } } } finally @@ -267,7 +272,7 @@ namespace MQTTnet.Server } } - public void EnqueueApplicationMessage(MqttClientSession senderClientSession, MqttApplicationMessage applicationMessage, bool isRetainedApplicationMessage) + public async Task EnqueueApplicationMessageAsync(MqttClientSession senderClientSession, MqttApplicationMessage applicationMessage, bool isRetainedApplicationMessage) { if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage)); @@ -278,10 +283,10 @@ namespace MQTTnet.Server } var publishPacket = _channelAdapter.PacketFormatterAdapter.DataConverter.CreatePublishPacket(applicationMessage); - + // Set the retain flag to true according to [MQTT-3.3.1-8] and [MQTT-3.3.1-9]. publishPacket.Retain = isRetainedApplicationMessage; - + if (publishPacket.QualityOfServiceLevel > 0) { publishPacket.PacketIdentifier = _packetIdentifierProvider.GetNewPacketIdentifier(); @@ -294,7 +299,10 @@ namespace MQTTnet.Server ClientId, applicationMessage); - _options.ClientMessageQueueInterceptor?.Invoke(context); + if (_options.ClientMessageQueueInterceptor != null) + { + await _options.ClientMessageQueueInterceptor.InterceptClientMessageQueueEnqueueAsync(context).ConfigureAwait(false); + } if (!context.AcceptEnqueue || context.ApplicationMessage == null) { @@ -309,35 +317,33 @@ namespace MQTTnet.Server _pendingPacketsQueue.Enqueue(publishPacket); } - private async Task TryDisconnectAdapterAsync(IMqttChannelAdapter adapter) - { - if (adapter == null) - { - return; - } + //private async Task TryDisconnectAdapterAsync(IMqttChannelAdapter adapter) + //{ + // if (adapter == null) + // { + // return; + // } - try - { - await adapter.DisconnectAsync(_options.DefaultCommunicationTimeout, CancellationToken.None).ConfigureAwait(false); - } - catch (Exception exception) - { - _logger.Error(exception, "Error while disconnecting channel adapter."); - } - } + // try + // { + // await adapter.DisconnectAsync(_options.DefaultCommunicationTimeout, CancellationToken.None).ConfigureAwait(false); + // } + // catch (Exception exception) + // { + // _logger.Error(exception, "Error while disconnecting channel adapter."); + // } + //} - private void ProcessReceivedPacket(IMqttChannelAdapter adapter, MqttBasePacket packet, CancellationToken cancellationToken) + private Task ProcessReceivedPacketAsync(IMqttChannelAdapter adapter, MqttBasePacket packet, CancellationToken cancellationToken) { if (packet is MqttPublishPacket publishPacket) { - HandleIncomingPublishPacket(adapter, publishPacket, cancellationToken); - return; + return HandleIncomingPublishPacketAsync(adapter, publishPacket, cancellationToken); } if (packet is MqttPingReqPacket) { - adapter.SendPacketAsync(new MqttPingRespPacket(), cancellationToken).GetAwaiter().GetResult(); - return; + return adapter.SendPacketAsync(new MqttPingRespPacket(), cancellationToken); } if (packet is MqttPubRelPacket pubRelPacket) @@ -348,8 +354,7 @@ namespace MQTTnet.Server ReasonCode = MqttPubCompReasonCode.Success }; - adapter.SendPacketAsync(responsePacket, cancellationToken).GetAwaiter().GetResult(); - return; + return adapter.SendPacketAsync(responsePacket, cancellationToken); } if (packet is MqttPubRecPacket pubRecPacket) @@ -360,91 +365,83 @@ namespace MQTTnet.Server ReasonCode = MqttPubRelReasonCode.Success }; - adapter.SendPacketAsync(responsePacket, cancellationToken).GetAwaiter().GetResult(); - return; + return adapter.SendPacketAsync(responsePacket, cancellationToken); } if (packet is MqttPubAckPacket || packet is MqttPubCompPacket) { - return; + return Task.FromResult(0); } if (packet is MqttSubscribePacket subscribePacket) { - HandleIncomingSubscribePacket(adapter, subscribePacket, cancellationToken); - return; + return HandleIncomingSubscribePacketAsync(adapter, subscribePacket, cancellationToken); } if (packet is MqttUnsubscribePacket unsubscribePacket) { - HandleIncomingUnsubscribePacket(adapter, unsubscribePacket, cancellationToken); - return; + return HandleIncomingUnsubscribePacketAsync(adapter, unsubscribePacket, cancellationToken); } if (packet is MqttDisconnectPacket) { - Stop(MqttClientDisconnectType.Clean, true); - return; + return StopAsync(MqttClientDisconnectType.Clean, true); } if (packet is MqttConnectPacket) { - Stop(MqttClientDisconnectType.NotClean, true); - return; + return StopAsync(MqttClientDisconnectType.NotClean, true); } _logger.Warning(null, "Client '{0}': Received not supported packet ({1}). Closing connection.", ClientId, packet); - Stop(MqttClientDisconnectType.NotClean, true); + return StopAsync(MqttClientDisconnectType.NotClean, true); } - private void EnqueueSubscribedRetainedMessages(ICollection topicFilters) + private async Task EnqueueSubscribedRetainedMessagesAsync(ICollection topicFilters) { - var retainedMessages = _retainedMessagesManager.GetSubscribedMessages(topicFilters); + var retainedMessages = await _retainedMessagesManager.GetSubscribedMessagesAsync(topicFilters).ConfigureAwait(false); foreach (var applicationMessage in retainedMessages) { - EnqueueApplicationMessage(null, applicationMessage, true); + await EnqueueApplicationMessageAsync(null, applicationMessage, true).ConfigureAwait(false); } } - private void HandleIncomingSubscribePacket(IMqttChannelAdapter adapter, MqttSubscribePacket subscribePacket, CancellationToken cancellationToken) + private async Task HandleIncomingSubscribePacketAsync(IMqttChannelAdapter adapter, MqttSubscribePacket subscribePacket, CancellationToken cancellationToken) { - var subscribeResult = _subscriptionsManager.Subscribe(subscribePacket); - adapter.SendPacketAsync(subscribeResult.ResponsePacket, cancellationToken).GetAwaiter().GetResult(); + var subscribeResult = await _subscriptionsManager.SubscribeAsync(subscribePacket).ConfigureAwait(false); + await adapter.SendPacketAsync(subscribeResult.ResponsePacket, cancellationToken).ConfigureAwait(false); if (subscribeResult.CloseConnection) { - Stop(MqttClientDisconnectType.NotClean, true); - return; + await StopAsync(MqttClientDisconnectType.NotClean, true).ConfigureAwait(false); } - EnqueueSubscribedRetainedMessages(subscribePacket.TopicFilters); + await EnqueueSubscribedRetainedMessagesAsync(subscribePacket.TopicFilters).ConfigureAwait(false); } - private void HandleIncomingUnsubscribePacket(IMqttChannelAdapter adapter, MqttUnsubscribePacket unsubscribePacket, CancellationToken cancellationToken) + private Task HandleIncomingUnsubscribePacketAsync(IMqttChannelAdapter adapter, MqttUnsubscribePacket unsubscribePacket, CancellationToken cancellationToken) { var unsubscribeResult = _subscriptionsManager.Unsubscribe(unsubscribePacket); - adapter.SendPacketAsync(unsubscribeResult, cancellationToken).GetAwaiter().GetResult(); + return adapter.SendPacketAsync(unsubscribeResult, cancellationToken); } - private void HandleIncomingPublishPacket(IMqttChannelAdapter adapter, MqttPublishPacket publishPacket, CancellationToken cancellationToken) + private Task HandleIncomingPublishPacketAsync(IMqttChannelAdapter adapter, MqttPublishPacket publishPacket, CancellationToken cancellationToken) { switch (publishPacket.QualityOfServiceLevel) { case MqttQualityOfServiceLevel.AtMostOnce: { HandleIncomingPublishPacketWithQoS0(publishPacket); - break; + return Task.FromResult(0); } case MqttQualityOfServiceLevel.AtLeastOnce: { - HandleIncomingPublishPacketWithQoS1(adapter, publishPacket, cancellationToken); - break; + return HandleIncomingPublishPacketWithQoS1Async(adapter, publishPacket, cancellationToken); } case MqttQualityOfServiceLevel.ExactlyOnce: { - HandleIncomingPublishPacketWithQoS2(adapter, publishPacket, cancellationToken); - break; + return HandleIncomingPublishPacketWithQoS2Async(adapter, publishPacket, cancellationToken); } default: { @@ -456,17 +453,17 @@ namespace MQTTnet.Server private void HandleIncomingPublishPacketWithQoS0(MqttPublishPacket publishPacket) { _sessionsManager.EnqueueApplicationMessage( - this, + this, _channelAdapter.PacketFormatterAdapter.DataConverter.CreateApplicationMessage(publishPacket)); } - private void HandleIncomingPublishPacketWithQoS1( + private Task HandleIncomingPublishPacketWithQoS1Async( IMqttChannelAdapter adapter, MqttPublishPacket publishPacket, CancellationToken cancellationToken) { _sessionsManager.EnqueueApplicationMessage( - this, + this, _channelAdapter.PacketFormatterAdapter.DataConverter.CreateApplicationMessage(publishPacket)); var response = new MqttPubAckPacket @@ -475,10 +472,10 @@ namespace MQTTnet.Server ReasonCode = MqttPubAckReasonCode.Success }; - adapter.SendPacketAsync(response, cancellationToken).GetAwaiter().GetResult(); + return adapter.SendPacketAsync(response, cancellationToken); } - private void HandleIncomingPublishPacketWithQoS2( + private Task HandleIncomingPublishPacketWithQoS2Async( IMqttChannelAdapter adapter, MqttPublishPacket publishPacket, CancellationToken cancellationToken) @@ -492,7 +489,7 @@ namespace MQTTnet.Server ReasonCode = MqttPubRecReasonCode.Success }; - adapter.SendPacketAsync(response, cancellationToken).GetAwaiter().GetResult(); + return adapter.SendPacketAsync(response, cancellationToken); } private void OnAdapterReadingPacketCompleted(object sender, EventArgs e) diff --git a/Source/MQTTnet/Server/MqttClientSessionStatus.cs b/Source/MQTTnet/Server/MqttClientSessionStatus.cs index 7381f94f..2673d7e6 100644 --- a/Source/MQTTnet/Server/MqttClientSessionStatus.cs +++ b/Source/MQTTnet/Server/MqttClientSessionStatus.cs @@ -25,22 +25,19 @@ namespace MQTTnet.Server public Task DisconnectAsync() { - _session.Stop(MqttClientDisconnectType.NotClean); - return Task.FromResult(0); + return _session.StopAsync(MqttClientDisconnectType.NotClean); } - public Task DeleteSessionAsync() + public async Task DeleteSessionAsync() { try { - _session.Stop(MqttClientDisconnectType.NotClean); + await _session.StopAsync(MqttClientDisconnectType.NotClean).ConfigureAwait(false); } finally { - _sessionsManager.DeleteSession(ClientId); + await _sessionsManager.DeleteSessionAsync(ClientId).ConfigureAwait(false); } - - return Task.FromResult(0); } public Task ClearPendingApplicationMessagesAsync() diff --git a/Source/MQTTnet/Server/MqttClientSessionsManager.cs b/Source/MQTTnet/Server/MqttClientSessionsManager.cs index 748594a9..356782e0 100644 --- a/Source/MQTTnet/Server/MqttClientSessionsManager.cs +++ b/Source/MQTTnet/Server/MqttClientSessionsManager.cs @@ -1,12 +1,11 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; -using System.Linq; using System.Threading; using System.Threading.Tasks; using MQTTnet.Adapter; using MQTTnet.Diagnostics; -using MQTTnet.Exceptions; +using MQTTnet.Internal; using MQTTnet.Packets; using MQTTnet.Protocol; @@ -16,9 +15,7 @@ namespace MQTTnet.Server { private readonly BlockingCollection _messageQueue = new BlockingCollection(); - /// - /// manual locking dictionaries is faster than using concurrent dictionary - /// + private readonly AsyncLock _sessionsLock = new AsyncLock(); private readonly Dictionary _sessions = new Dictionary(); private readonly CancellationToken _cancellationToken; @@ -47,16 +44,16 @@ namespace MQTTnet.Server public void Start() { - Task.Factory.StartNew(() => TryProcessQueuedApplicationMessages(_cancellationToken), _cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Default); + Task.Factory.StartNew(() => TryProcessQueuedApplicationMessagesAsync(_cancellationToken), _cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Default); } - public void Stop() + public async Task StopAsync() { - lock (_sessions) + using (await _sessionsLock.WaitAsync(_cancellationToken).ConfigureAwait(false)) { foreach (var session in _sessions) { - session.Value.Stop(MqttClientDisconnectType.NotClean); + await session.Value.StopAsync(MqttClientDisconnectType.NotClean).ConfigureAwait(false); } _sessions.Clear(); @@ -68,18 +65,21 @@ namespace MQTTnet.Server return Task.Run(() => RunSessionAsync(clientAdapter, _cancellationToken), _cancellationToken); } - public IList GetClientStatus() + public async Task> GetClientStatusAsync() { var result = new List(); - foreach (var session in GetSessions()) + using (await _sessionsLock.WaitAsync(_cancellationToken).ConfigureAwait(false)) { - var status = new MqttClientSessionStatus(this, session); - session.FillStatus(status); + foreach (var session in _sessions.Values) + { + var status = new MqttClientSessionStatus(this, session); + session.FillStatus(status); - result.Add(status); + result.Add(status); + } } - + return result; } @@ -122,9 +122,9 @@ namespace MQTTnet.Server } } - public void DeleteSession(string clientId) + public async Task DeleteSessionAsync(string clientId) { - lock (_sessions) + using (await _sessionsLock.WaitAsync(_cancellationToken).ConfigureAwait(false)) { _sessions.Remove(clientId); } @@ -137,13 +137,13 @@ namespace MQTTnet.Server _messageQueue?.Dispose(); } - private void TryProcessQueuedApplicationMessages(CancellationToken cancellationToken) + private async Task TryProcessQueuedApplicationMessagesAsync(CancellationToken cancellationToken) { while (!cancellationToken.IsCancellationRequested) { try { - TryProcessNextQueuedApplicationMessage(cancellationToken); + await TryProcessNextQueuedApplicationMessageAsync(cancellationToken).ConfigureAwait(false); } catch (OperationCanceledException) { @@ -155,7 +155,7 @@ namespace MQTTnet.Server } } - private void TryProcessNextQueuedApplicationMessage(CancellationToken cancellationToken) + private async Task TryProcessNextQueuedApplicationMessageAsync(CancellationToken cancellationToken) { try { @@ -164,12 +164,12 @@ namespace MQTTnet.Server var sender = enqueuedApplicationMessage.Sender; var applicationMessage = enqueuedApplicationMessage.ApplicationMessage; - var interceptorContext = InterceptApplicationMessage(sender, applicationMessage); + var interceptorContext = await InterceptApplicationMessageAsync(sender, applicationMessage).ConfigureAwait(false); if (interceptorContext != null) { if (interceptorContext.CloseConnection) { - enqueuedApplicationMessage.Sender.Stop(MqttClientDisconnectType.NotClean); + await enqueuedApplicationMessage.Sender.StopAsync(MqttClientDisconnectType.NotClean).ConfigureAwait(false); } if (interceptorContext.ApplicationMessage == null || !interceptorContext.AcceptPublish) @@ -184,12 +184,18 @@ namespace MQTTnet.Server if (applicationMessage.Retain) { - _retainedMessagesManager.HandleMessageAsync(sender?.ClientId, applicationMessage).GetAwaiter().GetResult(); + await _retainedMessagesManager.HandleMessageAsync(sender?.ClientId, applicationMessage).ConfigureAwait(false); } - foreach (var clientSession in GetSessions()) + using (await _sessionsLock.WaitAsync(_cancellationToken).ConfigureAwait(false)) { - clientSession.EnqueueApplicationMessage(enqueuedApplicationMessage.Sender, enqueuedApplicationMessage.ApplicationMessage, false); + foreach (var clientSession in _sessions.Values) + { + await clientSession.EnqueueApplicationMessageAsync( + enqueuedApplicationMessage.Sender, + enqueuedApplicationMessage.ApplicationMessage, + false).ConfigureAwait(false); + } } } catch (OperationCanceledException) @@ -201,35 +207,22 @@ namespace MQTTnet.Server } } - private List GetSessions() - { - lock (_sessions) - { - return _sessions.Values.ToList(); - } - } - private async Task RunSessionAsync(IMqttChannelAdapter clientAdapter, CancellationToken cancellationToken) { var clientId = string.Empty; try { - // TODO: Catch cancel exception here if the first packet was not received and log properly. var firstPacket = await clientAdapter.ReceivePacketAsync(_options.DefaultCommunicationTimeout, cancellationToken).ConfigureAwait(false); - if (firstPacket == null) - { - return; - } - if (!(firstPacket is MqttConnectPacket connectPacket)) { - throw new MqttProtocolViolationException("The first packet from a client must be a 'CONNECT' packet [MQTT-3.1.0-1]."); + _logger.Warning(null, "The first packet from client '{0}' was no 'CONNECT' packet [MQTT-3.1.0-1].", clientAdapter.Endpoint); + return; } clientId = connectPacket.ClientId; - var connectReturnCode = ValidateConnection(connectPacket, clientAdapter); + var connectReturnCode = await ValidateConnectionAsync(connectPacket, clientAdapter).ConfigureAwait(false); if (connectReturnCode != MqttConnectReturnCode.ConnectionAccepted) { await clientAdapter.SendPacketAsync( @@ -242,7 +235,7 @@ namespace MQTTnet.Server return; } - var result = PrepareClientSession(connectPacket); + var result = await PrepareClientSessionAsync(connectPacket).ConfigureAwait(false); await clientAdapter.SendPacketAsync( new MqttConnAckPacket @@ -267,14 +260,17 @@ namespace MQTTnet.Server } finally { + await clientAdapter.DisconnectAsync(_options.DefaultCommunicationTimeout, cancellationToken).ConfigureAwait(false); + clientAdapter.Dispose(); + if (!_options.EnablePersistentSessions) { - DeleteSession(clientId); + await DeleteSessionAsync(clientId).ConfigureAwait(false); } } } - private MqttConnectReturnCode ValidateConnection(MqttConnectPacket connectPacket, IMqttChannelAdapter clientAdapter) + private async Task ValidateConnectionAsync(MqttConnectPacket connectPacket, IMqttChannelAdapter clientAdapter) { if (_options.ConnectionValidator == null) { @@ -288,13 +284,13 @@ namespace MQTTnet.Server connectPacket.WillMessage, clientAdapter.Endpoint); - _options.ConnectionValidator(context); + await _options.ConnectionValidator.ValidateConnection(context).ConfigureAwait(false); return context.ReturnCode; } - private PrepareClientSessionResult PrepareClientSession(MqttConnectPacket connectPacket) + private async Task PrepareClientSessionAsync(MqttConnectPacket connectPacket) { - lock (_sessions) + using (await _sessionsLock.WaitAsync(_cancellationToken).ConfigureAwait(false)) { var isSessionPresent = _sessions.TryGetValue(connectPacket.ClientId, out var clientSession); if (isSessionPresent) @@ -303,7 +299,7 @@ namespace MQTTnet.Server { _sessions.Remove(connectPacket.ClientId); - clientSession.Stop(MqttClientDisconnectType.Clean); + await clientSession.StopAsync(MqttClientDisconnectType.Clean).ConfigureAwait(false); clientSession.Dispose(); clientSession = null; @@ -330,7 +326,7 @@ namespace MQTTnet.Server } } - private MqttApplicationMessageInterceptorContext InterceptApplicationMessage(MqttClientSession sender, MqttApplicationMessage applicationMessage) + private async Task InterceptApplicationMessageAsync(MqttClientSession sender, MqttApplicationMessage applicationMessage) { var interceptor = _options.ApplicationMessageInterceptor; if (interceptor == null) @@ -339,7 +335,7 @@ namespace MQTTnet.Server } var interceptorContext = new MqttApplicationMessageInterceptorContext(sender?.ClientId, applicationMessage); - interceptor(interceptorContext); + await interceptor.InterceptApplicationMessagePublishAsync(interceptorContext).ConfigureAwait(false); return interceptorContext; } } diff --git a/Source/MQTTnet/Server/MqttClientSubscriptionsManager.cs b/Source/MQTTnet/Server/MqttClientSubscriptionsManager.cs index 9f1fff69..3186104e 100644 --- a/Source/MQTTnet/Server/MqttClientSubscriptionsManager.cs +++ b/Source/MQTTnet/Server/MqttClientSubscriptionsManager.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; using MQTTnet.Packets; using MQTTnet.Protocol; @@ -20,7 +21,7 @@ namespace MQTTnet.Server _eventDispatcher = eventDispatcher ?? throw new ArgumentNullException(nameof(eventDispatcher)); } - public MqttClientSubscribeResult Subscribe(MqttSubscribePacket subscribePacket) + public async Task SubscribeAsync(MqttSubscribePacket subscribePacket) { if (subscribePacket == null) throw new ArgumentNullException(nameof(subscribePacket)); @@ -36,7 +37,7 @@ namespace MQTTnet.Server foreach (var topicFilter in subscribePacket.TopicFilters) { - var interceptorContext = InterceptSubscribe(topicFilter); + var interceptorContext = await InterceptSubscribeAsync(topicFilter).ConfigureAwait(false); if (!interceptorContext.AcceptSubscription) { result.ResponsePacket.ReturnCodes.Add(MqttSubscribeReturnCode.Failure); @@ -146,11 +147,15 @@ namespace MQTTnet.Server } } - private MqttSubscriptionInterceptorContext InterceptSubscribe(TopicFilter topicFilter) + private async Task InterceptSubscribeAsync(TopicFilter topicFilter) { - var interceptorContext = new MqttSubscriptionInterceptorContext(_clientId, topicFilter); - _options.SubscriptionInterceptor?.Invoke(interceptorContext); - return interceptorContext; + var context = new MqttSubscriptionInterceptorContext(_clientId, topicFilter); + if (_options.SubscriptionInterceptor != null) + { + await _options.SubscriptionInterceptor.InterceptSubscriptionAsync(context).ConfigureAwait(false); + } + + return context; } private static CheckSubscriptionsResult CreateSubscriptionResult(MqttQualityOfServiceLevel qosLevel, HashSet subscribedQoSLevels) diff --git a/Source/MQTTnet/Server/MqttRetainedMessagesManager.cs b/Source/MQTTnet/Server/MqttRetainedMessagesManager.cs index bf5de984..52533d14 100644 --- a/Source/MQTTnet/Server/MqttRetainedMessagesManager.cs +++ b/Source/MQTTnet/Server/MqttRetainedMessagesManager.cs @@ -3,11 +3,14 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using MQTTnet.Diagnostics; +using MQTTnet.Internal; namespace MQTTnet.Server { public class MqttRetainedMessagesManager { + private readonly byte[] _emptyArray = new byte[0]; + private readonly AsyncLock _messagesLock = new AsyncLock(); private readonly Dictionary _messages = new Dictionary(); private readonly IMqttNetChildLogger _logger; @@ -31,7 +34,7 @@ namespace MQTTnet.Server { var retainedMessages = await _options.Storage.LoadRetainedMessagesAsync().ConfigureAwait(false); - lock (_messages) + using (await _messagesLock.WaitAsync().ConfigureAwait(false)) { _messages.Clear(); foreach (var retainedMessage in retainedMessages) @@ -52,8 +55,7 @@ namespace MQTTnet.Server try { - List messagesForSave = null; - lock (_messages) + using (await _messagesLock.WaitAsync().ConfigureAwait(false)) { var saveIsRequired = false; @@ -71,7 +73,7 @@ namespace MQTTnet.Server } else { - if (existingMessage.QualityOfServiceLevel != applicationMessage.QualityOfServiceLevel || !existingMessage.Payload.SequenceEqual(applicationMessage.Payload ?? new byte[0])) + if (existingMessage.QualityOfServiceLevel != applicationMessage.QualityOfServiceLevel || !existingMessage.Payload.SequenceEqual(applicationMessage.Payload ?? _emptyArray)) { _messages[applicationMessage.Topic] = applicationMessage; saveIsRequired = true; @@ -83,20 +85,13 @@ namespace MQTTnet.Server if (saveIsRequired) { - messagesForSave = new List(_messages.Values); + if (_options.Storage != null) + { + var messagesForSave = new List(_messages.Values); + await _options.Storage.SaveRetainedMessagesAsync(messagesForSave).ConfigureAwait(false); + } } } - - if (messagesForSave == null) - { - _logger.Verbose("Skipped saving retained messages because no changes were detected."); - return; - } - - if (_options.Storage != null) - { - await _options.Storage.SaveRetainedMessagesAsync(messagesForSave).ConfigureAwait(false); - } } catch (Exception exception) { @@ -104,11 +99,11 @@ namespace MQTTnet.Server } } - public IList GetSubscribedMessages(ICollection topicFilters) + public async Task> GetSubscribedMessagesAsync(ICollection topicFilters) { var retainedMessages = new List(); - lock (_messages) + using (await _messagesLock.WaitAsync().ConfigureAwait(false)) { foreach (var retainedMessage in _messages.Values) { @@ -128,27 +123,25 @@ namespace MQTTnet.Server return retainedMessages; } - public IList GetMessages() + public async Task> GetMessagesAsync() { - lock (_messages) + using (await _messagesLock.WaitAsync().ConfigureAwait(false)) { return _messages.Values.ToList(); } } - public Task ClearMessagesAsync() + public async Task ClearMessagesAsync() { - lock (_messages) + using (await _messagesLock.WaitAsync().ConfigureAwait(false)) { _messages.Clear(); - } - if (_options.Storage != null) - { - return _options.Storage.SaveRetainedMessagesAsync(new List()); + if (_options.Storage != null) + { + await _options.Storage.SaveRetainedMessagesAsync(new List()).ConfigureAwait(false); + } } - - return Task.FromResult((object)null); } } } diff --git a/Source/MQTTnet/Server/MqttServer.cs b/Source/MQTTnet/Server/MqttServer.cs index 15e23e76..aae37e6a 100644 --- a/Source/MQTTnet/Server/MqttServer.cs +++ b/Source/MQTTnet/Server/MqttServer.cs @@ -48,17 +48,12 @@ namespace MQTTnet.Server public Task> GetClientSessionsStatusAsync() { - return Task.FromResult(_clientSessionsManager.GetClientStatus()); - } - - public IList GetClientSessionsStatus() - { - return _clientSessionsManager.GetClientStatus(); + return _clientSessionsManager.GetClientStatusAsync(); } public IList GetRetainedMessages() { - return _retainedMessagesManager.GetMessages(); + return _retainedMessagesManager.GetMessagesAsync().GetAwaiter().GetResult(); } public Task SubscribeAsync(string clientId, IList topicFilters) @@ -123,7 +118,7 @@ namespace MQTTnet.Server _cancellationTokenSource.Cancel(false); - _clientSessionsManager.Stop(); + _clientSessionsManager.StopAsync().ConfigureAwait(false); foreach (var adapter in _adapters) { diff --git a/Source/MQTTnet/Server/MqttServerApplicationMessageInterceptorDelegate.cs b/Source/MQTTnet/Server/MqttServerApplicationMessageInterceptorDelegate.cs new file mode 100644 index 00000000..82ba0f37 --- /dev/null +++ b/Source/MQTTnet/Server/MqttServerApplicationMessageInterceptorDelegate.cs @@ -0,0 +1,31 @@ +using System; +using System.Threading.Tasks; + +namespace MQTTnet.Server +{ + public class MqttServerApplicationMessageInterceptorDelegate : IMqttServerApplicationMessageInterceptor + { + private readonly Func _callback; + + public MqttServerApplicationMessageInterceptorDelegate(Action callback) + { + if (callback == null) throw new ArgumentNullException(nameof(callback)); + + _callback = context => + { + callback(context); + return Task.FromResult(0); + }; + } + + public MqttServerApplicationMessageInterceptorDelegate(Func callback) + { + _callback = callback ?? throw new ArgumentNullException(nameof(callback)); + } + + public Task InterceptApplicationMessagePublishAsync(MqttApplicationMessageInterceptorContext context) + { + return _callback(context); + } + } +} diff --git a/Source/MQTTnet/Server/MqttServerConnectionValidatorDelegate.cs b/Source/MQTTnet/Server/MqttServerConnectionValidatorDelegate.cs new file mode 100644 index 00000000..954d10d1 --- /dev/null +++ b/Source/MQTTnet/Server/MqttServerConnectionValidatorDelegate.cs @@ -0,0 +1,31 @@ +using System; +using System.Threading.Tasks; + +namespace MQTTnet.Server +{ + public class MqttServerConnectionValidatorDelegate : IMqttServerConnectionValidator + { + private readonly Func _callback; + + public MqttServerConnectionValidatorDelegate(Action callback) + { + if (callback == null) throw new ArgumentNullException(nameof(callback)); + + _callback = context => + { + callback(context); + return Task.FromResult(0); + }; + } + + public MqttServerConnectionValidatorDelegate(Func callback) + { + _callback = callback ?? throw new ArgumentNullException(nameof(callback)); + } + + public Task ValidateConnection(MqttConnectionValidatorContext context) + { + return _callback(context); + } + } +} diff --git a/Source/MQTTnet/Server/MqttServerOptions.cs b/Source/MQTTnet/Server/MqttServerOptions.cs index 02b2bce3..f893dac6 100644 --- a/Source/MQTTnet/Server/MqttServerOptions.cs +++ b/Source/MQTTnet/Server/MqttServerOptions.cs @@ -16,13 +16,13 @@ namespace MQTTnet.Server public TimeSpan DefaultCommunicationTimeout { get; set; } = TimeSpan.FromSeconds(15); - public Action ConnectionValidator { get; set; } + public IMqttServerConnectionValidator ConnectionValidator { get; set; } - public Action ApplicationMessageInterceptor { get; set; } + public IMqttServerApplicationMessageInterceptor ApplicationMessageInterceptor { get; set; } + + public IMqttServerClientMessageQueueInterceptor ClientMessageQueueInterceptor { get; set; } - public Action ClientMessageQueueInterceptor { get; set; } - - public Action SubscriptionInterceptor { get; set; } + public IMqttServerSubscriptionInterceptor SubscriptionInterceptor { get; set; } public IMqttServerStorage Storage { get; set; } } diff --git a/Source/MQTTnet/Server/MqttServerOptionsBuilder.cs b/Source/MQTTnet/Server/MqttServerOptionsBuilder.cs index 45400620..f43ff862 100644 --- a/Source/MQTTnet/Server/MqttServerOptionsBuilder.cs +++ b/Source/MQTTnet/Server/MqttServerOptionsBuilder.cs @@ -99,24 +99,42 @@ namespace MQTTnet.Server return this; } - public MqttServerOptionsBuilder WithConnectionValidator(Action value) + public MqttServerOptionsBuilder WithConnectionValidator(IMqttServerConnectionValidator value) { _options.ConnectionValidator = value; return this; } - public MqttServerOptionsBuilder WithApplicationMessageInterceptor(Action value) + public MqttServerOptionsBuilder WithConnectionValidator(Action value) + { + _options.ConnectionValidator = new MqttServerConnectionValidatorDelegate(value); + return this; + } + + public MqttServerOptionsBuilder WithApplicationMessageInterceptor(IMqttServerApplicationMessageInterceptor value) { _options.ApplicationMessageInterceptor = value; return this; } - public MqttServerOptionsBuilder WithSubscriptionInterceptor(Action value) + public MqttServerOptionsBuilder WithApplicationMessageInterceptor(Action value) + { + _options.ApplicationMessageInterceptor = new MqttServerApplicationMessageInterceptorDelegate(value); + return this; + } + + public MqttServerOptionsBuilder WithSubscriptionInterceptor(IMqttServerSubscriptionInterceptor value) { _options.SubscriptionInterceptor = value; return this; } + public MqttServerOptionsBuilder WithSubscriptionInterceptor(Action value) + { + _options.SubscriptionInterceptor = new MqttServerSubscriptionInterceptorDelegate(value); + return this; + } + public MqttServerOptionsBuilder WithPersistentSessions() { _options.EnablePersistentSessions = true; diff --git a/Source/MQTTnet/Server/MqttServerSubscriptionInterceptorDelegate.cs b/Source/MQTTnet/Server/MqttServerSubscriptionInterceptorDelegate.cs new file mode 100644 index 00000000..f500e07e --- /dev/null +++ b/Source/MQTTnet/Server/MqttServerSubscriptionInterceptorDelegate.cs @@ -0,0 +1,31 @@ +using System; +using System.Threading.Tasks; + +namespace MQTTnet.Server +{ + public class MqttServerSubscriptionInterceptorDelegate : IMqttServerSubscriptionInterceptor + { + private readonly Func _callback; + + public MqttServerSubscriptionInterceptorDelegate(Action callback) + { + if (callback == null) throw new ArgumentNullException(nameof(callback)); + + _callback = context => + { + callback(context); + return Task.FromResult(0); + }; + } + + public MqttServerSubscriptionInterceptorDelegate(Func callback) + { + _callback = callback ?? throw new ArgumentNullException(nameof(callback)); + } + + public Task InterceptSubscriptionAsync(MqttSubscriptionInterceptorContext context) + { + return _callback(context); + } + } +} diff --git a/Tests/MQTTnet.Core.Tests/AsyncLockTests.cs b/Tests/MQTTnet.Core.Tests/AsyncLockTests.cs index 81b0ad04..7073f70e 100644 --- a/Tests/MQTTnet.Core.Tests/AsyncLockTests.cs +++ b/Tests/MQTTnet.Core.Tests/AsyncLockTests.cs @@ -21,7 +21,7 @@ namespace MQTTnet.Tests #pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed threads[i] = Task.Run(async () => { - using (var releaser = await @lock.LockAsync(CancellationToken.None)) + using (var releaser = await @lock.WaitAsync(CancellationToken.None)) { var localI = globalI; await Task.Delay(10); // Increase the chance for wrong data. diff --git a/Tests/MQTTnet.Core.Tests/MqttKeepAliveMonitorTests.cs b/Tests/MQTTnet.Core.Tests/MqttKeepAliveMonitorTests.cs index 91b46dea..75b088aa 100644 --- a/Tests/MQTTnet.Core.Tests/MqttKeepAliveMonitorTests.cs +++ b/Tests/MQTTnet.Core.Tests/MqttKeepAliveMonitorTests.cs @@ -82,9 +82,10 @@ namespace MQTTnet.Tests throw new NotSupportedException(); } - public void Stop(MqttClientDisconnectType disconnectType) + public Task StopAsync(MqttClientDisconnectType disconnectType) { StopCalledCount++; + return Task.FromResult(0); } public Task SubscribeAsync(IList topicFilters) diff --git a/Tests/MQTTnet.Core.Tests/MqttServerTests.cs b/Tests/MQTTnet.Core.Tests/MqttServerTests.cs index 83f82cdc..b3f9f21b 100644 --- a/Tests/MQTTnet.Core.Tests/MqttServerTests.cs +++ b/Tests/MQTTnet.Core.Tests/MqttServerTests.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Net.Sockets; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -52,7 +53,7 @@ namespace MQTTnet.Tests } [TestMethod] - public async Task MqttServer_WillMessage() + public async Task MqttServer_Will_Message() { var serverAdapter = new TestMqttServerAdapter(); var s = new MqttFactory().CreateMqttServer(new[] { serverAdapter }, new MqttNetLogger()); @@ -84,7 +85,7 @@ namespace MQTTnet.Tests } [TestMethod] - public async Task MqttServer_SubscribeUnsubscribe() + public async Task MqttServer_Subscribe_Unsubscribe() { var serverAdapter = new TestMqttServerAdapter(); var s = new MqttFactory().CreateMqttServer(new[] { serverAdapter }, new MqttNetLogger()); @@ -230,7 +231,7 @@ namespace MQTTnet.Tests } [TestMethod] - public async Task MqttServer_SessionTakeover() + public async Task MqttServer_Session_Takeover() { var server = new MqttFactory().CreateMqttServer(); try @@ -299,38 +300,43 @@ namespace MQTTnet.Tests } [TestMethod] - public async Task MqttServer_HandleCleanDisconnect() + public async Task MqttServer_Handle_Clean_Disconnect() { - var serverAdapter = new MqttTcpServerAdapter(new MqttNetLogger().CreateChildLogger()); - var s = new MqttFactory().CreateMqttServer(new[] { serverAdapter }, new MqttNetLogger()); + var s = new MqttFactory().CreateMqttServer(); + try + { + var clientConnectedCalled = 0; + var clientDisconnectedCalled = 0; - var clientConnectedCalled = 0; - var clientDisconnectedCalled = 0; + s.ClientConnected += (_, __) => clientConnectedCalled++; + s.ClientDisconnected += (_, __) => clientDisconnectedCalled++; - s.ClientConnected += (_, __) => clientConnectedCalled++; - s.ClientDisconnected += (_, __) => clientDisconnectedCalled++; + var clientOptions = new MqttClientOptionsBuilder() + .WithTcpServer("localhost") + .Build(); - var clientOptions = new MqttClientOptionsBuilder() - .WithTcpServer("localhost") - .Build(); + await s.StartAsync(new MqttServerOptions()); - await s.StartAsync(new MqttServerOptions()); + var c1 = new MqttFactory().CreateMqttClient(); - var c1 = new MqttFactory().CreateMqttClient(); + await c1.ConnectAsync(clientOptions); - await c1.ConnectAsync(clientOptions); + await Task.Delay(100); - await Task.Delay(100); + await c1.DisconnectAsync(); - await c1.DisconnectAsync(); + await Task.Delay(100); - await Task.Delay(100); + await s.StopAsync(); - await s.StopAsync(); + await Task.Delay(100); - await Task.Delay(100); - - Assert.AreEqual(clientConnectedCalled, clientDisconnectedCalled); + Assert.AreEqual(clientConnectedCalled, clientDisconnectedCalled); + } + finally + { + await s.StopAsync(); + } } [TestMethod] @@ -410,11 +416,13 @@ namespace MQTTnet.Tests .WithPayload("value" + j).WithRetainFlag().Build()).GetAwaiter().GetResult(); } + Thread.Sleep(100); + client.DisconnectAsync().GetAwaiter().GetResult(); } }); - await Task.Delay(100); + await Task.Delay(1000); var retainedMessages = server.GetRetainedMessages(); @@ -432,7 +440,7 @@ namespace MQTTnet.Tests } [TestMethod] - public async Task MqttServer_RetainedMessagesFlow() + public async Task MqttServer_Retained_Messages_Flow() { var retainedMessage = new MqttApplicationMessageBuilder().WithTopic("r").WithPayload("r").WithRetainFlag().Build(); var serverAdapter = new TestMqttServerAdapter(); @@ -440,9 +448,9 @@ namespace MQTTnet.Tests await s.StartAsync(new MqttServerOptions()); var c1 = await serverAdapter.ConnectTestClient("c1"); await c1.PublishAsync(retainedMessage); - Thread.Sleep(500); + await Task.Delay(500); await c1.DisconnectAsync(); - Thread.Sleep(500); + await Task.Delay(500); var receivedMessages = 0; var c2 = await serverAdapter.ConnectTestClient("c2"); @@ -468,7 +476,7 @@ namespace MQTTnet.Tests } [TestMethod] - public async Task MqttServer_NoRetainedMessage() + public async Task MqttServer_No_Retained_Message() { var serverAdapter = new TestMqttServerAdapter(); var s = new MqttFactory().CreateMqttServer(new[] { serverAdapter }, new MqttNetLogger()); @@ -498,7 +506,7 @@ namespace MQTTnet.Tests } [TestMethod] - public async Task MqttServer_RetainedMessage() + public async Task MqttServer_Retained_Message() { var serverAdapter = new TestMqttServerAdapter(); var s = new MqttFactory().CreateMqttServer(new[] { serverAdapter }, new MqttNetLogger()); @@ -535,7 +543,7 @@ namespace MQTTnet.Tests } [TestMethod] - public async Task MqttServer_ClearRetainedMessage() + public async Task MqttServer_Clear_Retained_Message() { var serverAdapter = new TestMqttServerAdapter(); var s = new MqttFactory().CreateMqttServer(new[] { serverAdapter }, new MqttNetLogger()); @@ -567,7 +575,7 @@ namespace MQTTnet.Tests } [TestMethod] - public async Task MqttServer_PersistRetainedMessage() + public async Task MqttServer_Persist_Retained_Message() { var storage = new TestStorage(); var serverAdapter = new TestMqttServerAdapter(); @@ -629,7 +637,7 @@ namespace MQTTnet.Tests try { - var options = new MqttServerOptions { ApplicationMessageInterceptor = Interceptor }; + var options = new MqttServerOptions { ApplicationMessageInterceptor = new MqttServerApplicationMessageInterceptorDelegate(c => Interceptor(c)) }; await s.StartAsync(options); @@ -692,7 +700,7 @@ namespace MQTTnet.Tests } [TestMethod] - public async Task MqttServer_ConnectionDenied() + public async Task MqttServer_Connection_Denied() { var server = new MqttFactory().CreateMqttServer(); var client = new MqttFactory().CreateMqttClient(); @@ -791,7 +799,6 @@ namespace MQTTnet.Tests Assert.AreEqual("cdcd", flow); } - [TestMethod] public async Task MqttServer_StopAndRestart() { @@ -820,6 +827,66 @@ namespace MQTTnet.Tests await server.StopAsync(); } + [TestMethod] + public async Task MqttServer_Close_Idle_Connection() + { + var server = new MqttFactory().CreateMqttServer(); + + try + { + await server.StartAsync(new MqttServerOptionsBuilder().WithDefaultCommunicationTimeout(TimeSpan.FromSeconds(4)).Build()); + + var client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + await client.ConnectAsync("localhost", 1883); + + // Don't send anything. The server should close the connection. + await Task.Delay(TimeSpan.FromSeconds(5)); + + try + { + await client.ReceiveAsync(new ArraySegment(new byte[10]), SocketFlags.Partial); + Assert.Fail("Receive should throw an exception."); + } + catch (SocketException) + { + } + } + finally + { + await server.StopAsync(); + } + } + + [TestMethod] + public async Task MqttServer_Send_Garbage() + { + var server = new MqttFactory().CreateMqttServer(); + + try + { + await server.StartAsync(new MqttServerOptionsBuilder().WithDefaultCommunicationTimeout(TimeSpan.FromSeconds(4)).Build()); + + var client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + await client.ConnectAsync("localhost", 1883); + await client.SendAsync(Encoding.UTF8.GetBytes("Garbage"), SocketFlags.None); + + await Task.Delay(TimeSpan.FromSeconds(5)); + + try + { + await client.ReceiveAsync(new ArraySegment(new byte[10]), SocketFlags.Partial); + Assert.Fail("Receive should throw an exception."); + } + catch (SocketException) + { + } + } + finally + { + await server.StopAsync(); + } + } + private class TestStorage : IMqttServerStorage { public IList Messages = new List(); diff --git a/Tests/MQTTnet.Core.Tests/MqttSubscriptionsManagerTests.cs b/Tests/MQTTnet.Core.Tests/MqttSubscriptionsManagerTests.cs index 4af3b10c..7b7e959e 100644 --- a/Tests/MQTTnet.Core.Tests/MqttSubscriptionsManagerTests.cs +++ b/Tests/MQTTnet.Core.Tests/MqttSubscriptionsManagerTests.cs @@ -16,7 +16,7 @@ namespace MQTTnet.Tests var sp = new MqttSubscribePacket(); sp.TopicFilters.Add(new TopicFilterBuilder().WithTopic("A/B/C").Build()); - sm.Subscribe(sp); + sm.SubscribeAsync(sp).GetAwaiter().GetResult(); var result = sm.CheckSubscriptions("A/B/C", MqttQualityOfServiceLevel.AtMostOnce); Assert.IsTrue(result.IsSubscribed); @@ -31,7 +31,7 @@ namespace MQTTnet.Tests var sp = new MqttSubscribePacket(); sp.TopicFilters.Add(new TopicFilter { Topic = "A/B/C", QualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce }); - sm.Subscribe(sp); + sm.SubscribeAsync(sp).GetAwaiter().GetResult(); var result = sm.CheckSubscriptions("A/B/C", MqttQualityOfServiceLevel.ExactlyOnce); Assert.IsTrue(result.IsSubscribed); @@ -47,7 +47,7 @@ namespace MQTTnet.Tests sp.TopicFilters.Add(new TopicFilter { Topic = "#", QualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce }); sp.TopicFilters.Add(new TopicFilter { Topic = "A/B/C", QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce }); - sm.Subscribe(sp); + sm.SubscribeAsync(sp).GetAwaiter().GetResult(); var result = sm.CheckSubscriptions("A/B/C", MqttQualityOfServiceLevel.ExactlyOnce); Assert.IsTrue(result.IsSubscribed); @@ -62,7 +62,7 @@ namespace MQTTnet.Tests var sp = new MqttSubscribePacket(); sp.TopicFilters.Add(new TopicFilterBuilder().WithTopic("A/B/C").Build()); - sm.Subscribe(sp); + sm.SubscribeAsync(sp).GetAwaiter().GetResult(); Assert.IsFalse(sm.CheckSubscriptions("A/B/X", MqttQualityOfServiceLevel.AtMostOnce).IsSubscribed); } @@ -75,7 +75,7 @@ namespace MQTTnet.Tests var sp = new MqttSubscribePacket(); sp.TopicFilters.Add(new TopicFilterBuilder().WithTopic("A/B/C").Build()); - sm.Subscribe(sp); + sm.SubscribeAsync(sp).GetAwaiter().GetResult(); Assert.IsTrue(sm.CheckSubscriptions("A/B/C", MqttQualityOfServiceLevel.AtMostOnce).IsSubscribed); diff --git a/Tests/MQTTnet.TestApp.NetCore/ServerTest.cs b/Tests/MQTTnet.TestApp.NetCore/ServerTest.cs index 12fa1aa7..b00e3ac7 100644 --- a/Tests/MQTTnet.TestApp.NetCore/ServerTest.cs +++ b/Tests/MQTTnet.TestApp.NetCore/ServerTest.cs @@ -23,7 +23,7 @@ namespace MQTTnet.TestApp.NetCore { var options = new MqttServerOptions { - ConnectionValidator = p => + ConnectionValidator = new MqttServerConnectionValidatorDelegate(p => { if (p.ClientId == "SpecialClient") { @@ -32,11 +32,11 @@ namespace MQTTnet.TestApp.NetCore p.ReturnCode = MqttConnectReturnCode.ConnectionRefusedBadUsernameOrPassword; } } - }, + }), Storage = new RetainedMessageHandler(), - ApplicationMessageInterceptor = context => + ApplicationMessageInterceptor = new MqttServerApplicationMessageInterceptorDelegate(context => { if (MqttTopicFilterComparer.IsMatch(context.ApplicationMessage.Topic, "/myTopic/WithTimestamp/#")) { @@ -50,8 +50,9 @@ namespace MQTTnet.TestApp.NetCore context.AcceptPublish = false; context.CloseConnection = true; } - }, - SubscriptionInterceptor = context => + }), + + SubscriptionInterceptor = new MqttServerSubscriptionInterceptorDelegate(context => { if (context.TopicFilter.Topic.StartsWith("admin/foo/bar") && context.ClientId != "theAdmin") { @@ -63,7 +64,7 @@ namespace MQTTnet.TestApp.NetCore context.AcceptSubscription = false; context.CloseConnection = true; } - } + }) }; // Extend the timestamp for all messages from clients. diff --git a/Tests/MQTTnet.TestApp.UniversalWindows/MainPage.xaml.cs b/Tests/MQTTnet.TestApp.UniversalWindows/MainPage.xaml.cs index 44bf102a..2065a975 100644 --- a/Tests/MQTTnet.TestApp.UniversalWindows/MainPage.xaml.cs +++ b/Tests/MQTTnet.TestApp.UniversalWindows/MainPage.xaml.cs @@ -450,7 +450,7 @@ namespace MQTTnet.TestApp.UniversalWindows return; } - var sessions = _mqttServer.GetClientSessionsStatus(); + var sessions = _mqttServer.GetClientSessionsStatusAsync().GetAwaiter().GetResult(); _sessions.Clear(); foreach (var session in sessions) @@ -568,7 +568,7 @@ namespace MQTTnet.TestApp.UniversalWindows { var options = new MqttServerOptions(); - options.ConnectionValidator = c => + options.ConnectionValidator = new MqttServerConnectionValidatorDelegate(c => { if (c.ClientId.Length < 10) { @@ -589,7 +589,7 @@ namespace MQTTnet.TestApp.UniversalWindows } c.ReturnCode = MqttConnectReturnCode.ConnectionAccepted; - }; + }); var factory = new MqttFactory(); var mqttServer = factory.CreateMqttServer(); @@ -633,7 +633,7 @@ namespace MQTTnet.TestApp.UniversalWindows { }; - options.ConnectionValidator = c => + options.ConnectionValidator = new MqttServerConnectionValidatorDelegate(c => { if (c.ClientId != "Highlander") { @@ -642,7 +642,7 @@ namespace MQTTnet.TestApp.UniversalWindows } c.ReturnCode = MqttConnectReturnCode.ConnectionAccepted; - }; + }); var mqttServer = new MqttFactory().CreateMqttServer(); await mqttServer.StartAsync(optionsBuilder.Build()); @@ -652,7 +652,7 @@ namespace MQTTnet.TestApp.UniversalWindows // Setup client validator. var options = new MqttServerOptions { - ConnectionValidator = c => + ConnectionValidator = new MqttServerConnectionValidatorDelegate(c => { if (c.ClientId.Length < 10) { @@ -673,7 +673,7 @@ namespace MQTTnet.TestApp.UniversalWindows } c.ReturnCode = MqttConnectReturnCode.ConnectionAccepted; - } + }) }; } From b527059b722dd093b65fb9bb7cdde6a4517bf010 Mon Sep 17 00:00:00 2001 From: Christian Kratky Date: Thu, 27 Dec 2018 23:05:57 +0100 Subject: [PATCH 022/124] Fix session takeover. --- Source/MQTTnet/Server/MqttClientSession.cs | 34 ++++++++++++------- .../Server/MqttClientSessionsManager.cs | 10 +++--- Source/MQTTnet/Server/MqttServer.cs | 2 +- 3 files changed, 28 insertions(+), 18 deletions(-) diff --git a/Source/MQTTnet/Server/MqttClientSession.cs b/Source/MQTTnet/Server/MqttClientSession.cs index 9eaab4f1..9d327b3c 100644 --- a/Source/MQTTnet/Server/MqttClientSession.cs +++ b/Source/MQTTnet/Server/MqttClientSession.cs @@ -30,7 +30,7 @@ namespace MQTTnet.Server private MqttApplicationMessage _willMessage; private bool _wasCleanDisconnect; private Task _workerTask; - private IDisposable _cleanupHandle; + //private IDisposable _cleanupHandle; private string _adapterEndpoint; private MqttProtocolVersion? _adapterProtocolVersion; @@ -73,11 +73,11 @@ namespace MQTTnet.Server status.LastNonKeepAlivePacketReceived = _keepAliveMonitor.LastNonKeepAlivePacketReceived; } - public Task RunAsync(MqttConnectPacket connectPacket, IMqttChannelAdapter adapter) - { - _workerTask = RunInternalAsync(connectPacket, adapter); - return _workerTask; - } + //public Task RunAsync(MqttConnectPacket connectPacket, IMqttChannelAdapter adapter) + //{ + // _workerTask = RunInternalAsync(connectPacket, adapter); + // return _workerTask; + //} public Task StopAsync(MqttClientDisconnectType type) { @@ -121,16 +121,22 @@ namespace MQTTnet.Server _cancellationTokenSource = null; } + public Task RunAsync(MqttConnectPacket connectPacket, IMqttChannelAdapter adapter) + { + _workerTask = RunInternalAsync(connectPacket, adapter); + return _workerTask; + } + private async Task RunInternalAsync(MqttConnectPacket connectPacket, IMqttChannelAdapter adapter) { if (adapter == null) throw new ArgumentNullException(nameof(adapter)); try { - if (_cancellationTokenSource != null) - { - await StopAsync(MqttClientDisconnectType.Clean, true).ConfigureAwait(false); - } + //if (_cancellationTokenSource != null) + //{ + // await StopAsync(MqttClientDisconnectType.Clean, true).ConfigureAwait(false); + //} adapter.ReadingPacketStarted += OnAdapterReadingPacketStarted; adapter.ReadingPacketCompleted += OnAdapterReadingPacketCompleted; @@ -203,12 +209,14 @@ namespace MQTTnet.Server //await TryDisconnectAdapterAsync(adapter).ConfigureAwait(false); //TryDisposeAdapter(adapter); - _cleanupHandle?.Dispose(); - _cleanupHandle = null; + //_cleanupHandle?.Dispose(); + //_cleanupHandle = null; _cancellationTokenSource?.Cancel(false); _cancellationTokenSource?.Dispose(); _cancellationTokenSource = null; + + _workerTask = null; } } @@ -264,6 +272,8 @@ namespace MQTTnet.Server await _workerTask.ConfigureAwait(false); } } + + await Task.FromResult(0); } finally { diff --git a/Source/MQTTnet/Server/MqttClientSessionsManager.cs b/Source/MQTTnet/Server/MqttClientSessionsManager.cs index 356782e0..9707e580 100644 --- a/Source/MQTTnet/Server/MqttClientSessionsManager.cs +++ b/Source/MQTTnet/Server/MqttClientSessionsManager.cs @@ -60,9 +60,9 @@ namespace MQTTnet.Server } } - public Task StartSession(IMqttChannelAdapter clientAdapter) + public Task HandleConnectionAsync(IMqttChannelAdapter clientAdapter) { - return Task.Run(() => RunSessionAsync(clientAdapter, _cancellationToken), _cancellationToken); + return Task.Run(() => HandleConnectionAsync(clientAdapter, _cancellationToken), _cancellationToken); } public async Task> GetClientStatusAsync() @@ -207,7 +207,7 @@ namespace MQTTnet.Server } } - private async Task RunSessionAsync(IMqttChannelAdapter clientAdapter, CancellationToken cancellationToken) + private async Task HandleConnectionAsync(IMqttChannelAdapter clientAdapter, CancellationToken cancellationToken) { var clientId = string.Empty; @@ -295,11 +295,11 @@ namespace MQTTnet.Server var isSessionPresent = _sessions.TryGetValue(connectPacket.ClientId, out var clientSession); if (isSessionPresent) { + await clientSession.StopAsync(MqttClientDisconnectType.Clean).ConfigureAwait(false); + if (connectPacket.CleanSession) { _sessions.Remove(connectPacket.ClientId); - - await clientSession.StopAsync(MqttClientDisconnectType.Clean).ConfigureAwait(false); clientSession.Dispose(); clientSession = null; diff --git a/Source/MQTTnet/Server/MqttServer.cs b/Source/MQTTnet/Server/MqttServer.cs index aae37e6a..1b5fb3be 100644 --- a/Source/MQTTnet/Server/MqttServer.cs +++ b/Source/MQTTnet/Server/MqttServer.cs @@ -148,7 +148,7 @@ namespace MQTTnet.Server private void OnClientAccepted(object sender, MqttServerAdapterClientAcceptedEventArgs eventArgs) { - eventArgs.SessionTask = _clientSessionsManager.StartSession(eventArgs.Client); + eventArgs.SessionTask = _clientSessionsManager.HandleConnectionAsync(eventArgs.Client); } } } From 4cba81471612cfe1e4a43032a818f02dfa91bfd3 Mon Sep 17 00:00:00 2001 From: Christian Kratky Date: Thu, 27 Dec 2018 23:10:52 +0100 Subject: [PATCH 023/124] Cleanup. --- Source/MQTTnet/Server/MqttClientSession.cs | 293 ++++++++------------- 1 file changed, 108 insertions(+), 185 deletions(-) diff --git a/Source/MQTTnet/Server/MqttClientSession.cs b/Source/MQTTnet/Server/MqttClientSession.cs index 9d327b3c..0457fef2 100644 --- a/Source/MQTTnet/Server/MqttClientSession.cs +++ b/Source/MQTTnet/Server/MqttClientSession.cs @@ -6,7 +6,6 @@ using MQTTnet.Adapter; using MQTTnet.Client; using MQTTnet.Diagnostics; using MQTTnet.Exceptions; -using MQTTnet.Formatter; using MQTTnet.Packets; using MQTTnet.Protocol; @@ -30,10 +29,6 @@ namespace MQTTnet.Server private MqttApplicationMessage _willMessage; private bool _wasCleanDisconnect; private Task _workerTask; - //private IDisposable _cleanupHandle; - - private string _adapterEndpoint; - private MqttProtocolVersion? _adapterProtocolVersion; private IMqttChannelAdapter _channelAdapter; public MqttClientSession( @@ -66,19 +61,13 @@ namespace MQTTnet.Server { status.ClientId = ClientId; status.IsConnected = _cancellationTokenSource != null; - status.Endpoint = _adapterEndpoint; - status.ProtocolVersion = _adapterProtocolVersion; + status.Endpoint = _channelAdapter?.Endpoint; + status.ProtocolVersion = _channelAdapter?.PacketFormatterAdapter?.ProtocolVersion; status.PendingApplicationMessagesCount = _pendingPacketsQueue.Count; status.LastPacketReceived = _keepAliveMonitor.LastPacketReceived; status.LastNonKeepAlivePacketReceived = _keepAliveMonitor.LastNonKeepAlivePacketReceived; } - //public Task RunAsync(MqttConnectPacket connectPacket, IMqttChannelAdapter adapter) - //{ - // _workerTask = RunInternalAsync(connectPacket, adapter); - // return _workerTask; - //} - public Task StopAsync(MqttClientDisconnectType type) { return StopAsync(type, false); @@ -121,167 +110,15 @@ namespace MQTTnet.Server _cancellationTokenSource = null; } - public Task RunAsync(MqttConnectPacket connectPacket, IMqttChannelAdapter adapter) + public Task RunAsync(MqttConnectPacket connectPacket, IMqttChannelAdapter channelAdapter) { - _workerTask = RunInternalAsync(connectPacket, adapter); + if (connectPacket == null) throw new ArgumentNullException(nameof(connectPacket)); + if (channelAdapter == null) throw new ArgumentNullException(nameof(channelAdapter)); + + _workerTask = RunInternalAsync(connectPacket, channelAdapter); return _workerTask; } - private async Task RunInternalAsync(MqttConnectPacket connectPacket, IMqttChannelAdapter adapter) - { - if (adapter == null) throw new ArgumentNullException(nameof(adapter)); - - try - { - //if (_cancellationTokenSource != null) - //{ - // await StopAsync(MqttClientDisconnectType.Clean, true).ConfigureAwait(false); - //} - - adapter.ReadingPacketStarted += OnAdapterReadingPacketStarted; - adapter.ReadingPacketCompleted += OnAdapterReadingPacketCompleted; - - _cancellationTokenSource = new CancellationTokenSource(); - -// //workaround for https://github.com/dotnet/corefx/issues/24430 -//#pragma warning disable 4014 -// _cleanupHandle = _cancellationTokenSource.Token.Register(async () => -// { -// await TryDisconnectAdapterAsync(adapter).ConfigureAwait(false); -// TryDisposeAdapter(adapter); -// }); -//#pragma warning restore 4014 -// //end workaround - - _wasCleanDisconnect = false; - _willMessage = connectPacket.WillMessage; - - _pendingPacketsQueue.Start(adapter, _cancellationTokenSource.Token); - _keepAliveMonitor.Start(connectPacket.KeepAlivePeriod, _cancellationTokenSource.Token); - - _adapterEndpoint = adapter.Endpoint; - _adapterProtocolVersion = adapter.PacketFormatterAdapter.ProtocolVersion; - _channelAdapter = adapter; - - while (!_cancellationTokenSource.IsCancellationRequested) - { - var packet = await adapter.ReceivePacketAsync(TimeSpan.Zero, _cancellationTokenSource.Token).ConfigureAwait(false); - if (packet != null) - { - _keepAliveMonitor.PacketReceived(packet); - await ProcessReceivedPacketAsync(adapter, packet, _cancellationTokenSource.Token).ConfigureAwait(false); - } - } - } - catch (OperationCanceledException) - { - } - catch (Exception exception) - { - if (exception is MqttCommunicationException) - { - if (exception is MqttCommunicationClosedGracefullyException) - { - _logger.Verbose("Client '{0}': Connection closed gracefully.", ClientId); - } - else - { - _logger.Warning(exception, "Client '{0}': Communication exception while receiving client packets.", ClientId); - } - } - else - { - _logger.Error(exception, "Client '{0}': Unhandled exception while receiving client packets.", ClientId); - } - - await StopAsync(MqttClientDisconnectType.NotClean, true).ConfigureAwait(false); - } - finally - { - adapter.ReadingPacketStarted -= OnAdapterReadingPacketStarted; - adapter.ReadingPacketCompleted -= OnAdapterReadingPacketCompleted; - - _adapterEndpoint = null; - _adapterProtocolVersion = null; - - // Uncomment as soon as the workaround above is no longer needed. - // Also called in outer scope! - //await TryDisconnectAdapterAsync(adapter).ConfigureAwait(false); - //TryDisposeAdapter(adapter); - - //_cleanupHandle?.Dispose(); - //_cleanupHandle = null; - - _cancellationTokenSource?.Cancel(false); - _cancellationTokenSource?.Dispose(); - _cancellationTokenSource = null; - - _workerTask = null; - } - } - - ////private void TryDisposeAdapter(IMqttChannelAdapter adapter) - ////{ - //// if (adapter == null) - //// { - //// return; - //// } - - //// try - //// { - //// adapter.ReadingPacketStarted -= OnAdapterReadingPacketStarted; - //// adapter.ReadingPacketCompleted -= OnAdapterReadingPacketCompleted; - - //// adapter.Dispose(); - //// } - //// catch (Exception exception) - //// { - //// _logger.Error(exception, exception.Message); - //// } - //// finally - //// { - //// adapter.Dispose(); - //// } - ////} - - private async Task StopAsync(MqttClientDisconnectType type, bool isInsideSession) - { - try - { - var cts = _cancellationTokenSource; - if (cts == null || cts.IsCancellationRequested) - { - return; - } - - _wasCleanDisconnect = type == MqttClientDisconnectType.Clean; - - _cancellationTokenSource?.Cancel(false); - - if (_willMessage != null && !_wasCleanDisconnect) - { - _sessionsManager.EnqueueApplicationMessage(this, _willMessage); - } - - _willMessage = null; - - if (!isInsideSession) - { - if (_workerTask != null) - { - await _workerTask.ConfigureAwait(false); - } - } - - await Task.FromResult(0); - } - finally - { - _logger.Info("Client '{0}': Session stopped.", ClientId); - _eventDispatcher.OnClientDisconnected(ClientId, _wasCleanDisconnect); - } - } - public async Task EnqueueApplicationMessageAsync(MqttClientSession senderClientSession, MqttApplicationMessage applicationMessage, bool isRetainedApplicationMessage) { if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage)); @@ -327,22 +164,108 @@ namespace MQTTnet.Server _pendingPacketsQueue.Enqueue(publishPacket); } - //private async Task TryDisconnectAdapterAsync(IMqttChannelAdapter adapter) - //{ - // if (adapter == null) - // { - // return; - // } + private async Task RunInternalAsync(MqttConnectPacket connectPacket, IMqttChannelAdapter channelAdapter) + { + if (channelAdapter == null) throw new ArgumentNullException(nameof(channelAdapter)); - // try - // { - // await adapter.DisconnectAsync(_options.DefaultCommunicationTimeout, CancellationToken.None).ConfigureAwait(false); - // } - // catch (Exception exception) - // { - // _logger.Error(exception, "Error while disconnecting channel adapter."); - // } - //} + try + { + channelAdapter.ReadingPacketStarted += OnAdapterReadingPacketStarted; + channelAdapter.ReadingPacketCompleted += OnAdapterReadingPacketCompleted; + + _cancellationTokenSource = new CancellationTokenSource(); + + _wasCleanDisconnect = false; + _willMessage = connectPacket.WillMessage; + + _pendingPacketsQueue.Start(channelAdapter, _cancellationTokenSource.Token); + _keepAliveMonitor.Start(connectPacket.KeepAlivePeriod, _cancellationTokenSource.Token); + + _channelAdapter = channelAdapter; + + while (!_cancellationTokenSource.IsCancellationRequested) + { + var packet = await channelAdapter.ReceivePacketAsync(TimeSpan.Zero, _cancellationTokenSource.Token).ConfigureAwait(false); + if (packet != null) + { + _keepAliveMonitor.PacketReceived(packet); + await ProcessReceivedPacketAsync(channelAdapter, packet, _cancellationTokenSource.Token).ConfigureAwait(false); + } + } + } + catch (OperationCanceledException) + { + } + catch (Exception exception) + { + if (exception is MqttCommunicationException) + { + if (exception is MqttCommunicationClosedGracefullyException) + { + _logger.Verbose("Client '{0}': Connection closed gracefully.", ClientId); + } + else + { + _logger.Warning(exception, "Client '{0}': Communication exception while receiving client packets.", ClientId); + } + } + else + { + _logger.Error(exception, "Client '{0}': Unhandled exception while receiving client packets.", ClientId); + } + + await StopAsync(MqttClientDisconnectType.NotClean, true).ConfigureAwait(false); + } + finally + { + channelAdapter.ReadingPacketStarted -= OnAdapterReadingPacketStarted; + channelAdapter.ReadingPacketCompleted -= OnAdapterReadingPacketCompleted; + + _cancellationTokenSource?.Cancel(false); + _cancellationTokenSource?.Dispose(); + _cancellationTokenSource = null; + + _workerTask = null; + } + } + + private async Task StopAsync(MqttClientDisconnectType type, bool isInsideSession) + { + try + { + var cts = _cancellationTokenSource; + if (cts == null || cts.IsCancellationRequested) + { + return; + } + + _wasCleanDisconnect = type == MqttClientDisconnectType.Clean; + + _cancellationTokenSource?.Cancel(false); + + if (_willMessage != null && !_wasCleanDisconnect) + { + _sessionsManager.EnqueueApplicationMessage(this, _willMessage); + } + + _willMessage = null; + + if (!isInsideSession) + { + if (_workerTask != null) + { + await _workerTask.ConfigureAwait(false); + } + } + + await Task.FromResult(0); + } + finally + { + _logger.Info("Client '{0}': Session stopped.", ClientId); + _eventDispatcher.OnClientDisconnected(ClientId, _wasCleanDisconnect); + } + } private Task ProcessReceivedPacketAsync(IMqttChannelAdapter adapter, MqttBasePacket packet, CancellationToken cancellationToken) { From 96a67579e2eeb0fb85de4c68052e6051541ac2e3 Mon Sep 17 00:00:00 2001 From: Christian Kratky Date: Tue, 1 Jan 2019 19:18:54 +0100 Subject: [PATCH 024/124] Improve session handling. --- Source/MQTTnet/Client/MqttClient.cs | 14 +- Source/MQTTnet/Client/MqttClientExtensions.cs | 5 +- .../MQTTnet/Implementations/MqttTcpChannel.cs | 2 +- .../Server/IMqttClientSessionStatus.cs | 6 +- Source/MQTTnet/Server/IMqttServer.cs | 4 +- Source/MQTTnet/Server/MqttClientSession.cs | 170 ++++++++------- ... MqttClientSessionPendingMessagesQueue.cs} | 32 +-- .../MQTTnet/Server/MqttClientSessionStatus.cs | 4 +- .../Server/MqttClientSessionsManager.cs | 115 +++++----- Source/MQTTnet/Server/MqttServer.cs | 6 +- Source/MQTTnet/Server/MqttServerExtensions.cs | 45 ++++ Tests/MQTTnet.Core.Tests/MqttServerTests.cs | 202 ++++++++++++------ Tests/MQTTnet.Core.Tests/TestSetup.cs | 92 ++++++++ 13 files changed, 478 insertions(+), 219 deletions(-) rename Source/MQTTnet/Server/{MqttClientPendingPacketsQueue.cs => MqttClientSessionPendingMessagesQueue.cs} (88%) create mode 100644 Source/MQTTnet/Server/MqttServerExtensions.cs create mode 100644 Tests/MQTTnet.Core.Tests/TestSetup.cs diff --git a/Source/MQTTnet/Client/MqttClient.cs b/Source/MQTTnet/Client/MqttClient.cs index fd4b8203..be29f5d9 100644 --- a/Source/MQTTnet/Client/MqttClient.cs +++ b/Source/MQTTnet/Client/MqttClient.cs @@ -66,24 +66,26 @@ namespace MQTTnet.Client _packetIdentifierProvider.Reset(); _packetDispatcher.Reset(); - _cancellationTokenSource = new CancellationTokenSource(); + var cancellationTokenSource = new CancellationTokenSource(); + _cancellationTokenSource = cancellationTokenSource; + _disconnectGate = 0; _adapter = _adapterFactory.CreateClientAdapter(options, _logger); _logger.Verbose($"Trying to connect with server ({Options.ChannelOptions})."); - await _adapter.ConnectAsync(Options.CommunicationTimeout, _cancellationTokenSource.Token).ConfigureAwait(false); + await _adapter.ConnectAsync(Options.CommunicationTimeout, cancellationTokenSource.Token).ConfigureAwait(false); _logger.Verbose("Connection with server established."); - StartReceivingPackets(_cancellationTokenSource.Token); + StartReceivingPackets(cancellationTokenSource.Token); - var connectResult = await AuthenticateAsync(options.WillMessage, _cancellationTokenSource.Token).ConfigureAwait(false); + var connectResult = await AuthenticateAsync(options.WillMessage, cancellationTokenSource.Token).ConfigureAwait(false); _logger.Verbose("MQTT connection with server established."); _sendTracker.Restart(); if (Options.KeepAlivePeriod != TimeSpan.Zero) { - StartSendingKeepAliveMessages(_cancellationTokenSource.Token); + StartSendingKeepAliveMessages(cancellationTokenSource.Token); } IsConnected = true; @@ -112,7 +114,7 @@ namespace MQTTnet.Client { _cleanDisconnectInitiated = true; - if (IsConnected && !_cancellationTokenSource.IsCancellationRequested) + if (IsConnected && _cancellationTokenSource?.IsCancellationRequested == false) { var disconnectPacket = CreateDisconnectPacket(options); await SendAsync(disconnectPacket, _cancellationTokenSource.Token).ConfigureAwait(false); diff --git a/Source/MQTTnet/Client/MqttClientExtensions.cs b/Source/MQTTnet/Client/MqttClientExtensions.cs index 00482eaa..b399ef67 100644 --- a/Source/MQTTnet/Client/MqttClientExtensions.cs +++ b/Source/MQTTnet/Client/MqttClientExtensions.cs @@ -1,5 +1,4 @@ using System; -using System.Linq; using System.Threading.Tasks; using MQTTnet.Client.Subscribing; using MQTTnet.Client.Unsubscribing; @@ -21,7 +20,7 @@ namespace MQTTnet.Client if (client == null) throw new ArgumentNullException(nameof(client)); if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters)); - return client.SubscribeAsync(topicFilters.ToList()); + return client.SubscribeAsync(topicFilters); } public static Task SubscribeAsync(this IMqttClient client, string topic, MqttQualityOfServiceLevel qualityOfServiceLevel) @@ -45,7 +44,7 @@ namespace MQTTnet.Client if (client == null) throw new ArgumentNullException(nameof(client)); if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters)); - return client.UnsubscribeAsync(topicFilters.ToList()); + return client.UnsubscribeAsync(topicFilters); } } } \ No newline at end of file diff --git a/Source/MQTTnet/Implementations/MqttTcpChannel.cs b/Source/MQTTnet/Implementations/MqttTcpChannel.cs index e389ef09..802463ad 100644 --- a/Source/MQTTnet/Implementations/MqttTcpChannel.cs +++ b/Source/MQTTnet/Implementations/MqttTcpChannel.cs @@ -93,7 +93,7 @@ namespace MQTTnet.Implementations using (cancellationToken.Register(() => _socket.Dispose())) { await _stream.WriteAsync(buffer, offset, count, cancellationToken).ConfigureAwait(false); - await _stream.FlushAsync(cancellationToken); + //await _stream.FlushAsync(cancellationToken); } } diff --git a/Source/MQTTnet/Server/IMqttClientSessionStatus.cs b/Source/MQTTnet/Server/IMqttClientSessionStatus.cs index 38b52e2e..d46db699 100644 --- a/Source/MQTTnet/Server/IMqttClientSessionStatus.cs +++ b/Source/MQTTnet/Server/IMqttClientSessionStatus.cs @@ -18,7 +18,11 @@ namespace MQTTnet.Server TimeSpan LastNonKeepAlivePacketReceived { get; } - int PendingApplicationMessagesCount { get; } + long PendingApplicationMessagesCount { get; } + + long ReceivedApplicationMessagesCount { get; } + + long SentApplicationMessagesCount { get; } Task DisconnectAsync(); diff --git a/Source/MQTTnet/Server/IMqttServer.cs b/Source/MQTTnet/Server/IMqttServer.cs index e42c903b..cccda2b0 100644 --- a/Source/MQTTnet/Server/IMqttServer.cs +++ b/Source/MQTTnet/Server/IMqttServer.cs @@ -21,8 +21,8 @@ namespace MQTTnet.Server IList GetRetainedMessages(); Task ClearRetainedMessagesAsync(); - Task SubscribeAsync(string clientId, IList topicFilters); - Task UnsubscribeAsync(string clientId, IList topicFilters); + Task SubscribeAsync(string clientId, IEnumerable topicFilters); + Task UnsubscribeAsync(string clientId, IEnumerable topicFilters); Task StartAsync(IMqttServerOptions options); Task StopAsync(); diff --git a/Source/MQTTnet/Server/MqttClientSession.cs b/Source/MQTTnet/Server/MqttClientSession.cs index 0457fef2..688d0945 100644 --- a/Source/MQTTnet/Server/MqttClientSession.cs +++ b/Source/MQTTnet/Server/MqttClientSession.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Threading; using System.Threading.Tasks; using MQTTnet.Adapter; @@ -18,7 +19,7 @@ namespace MQTTnet.Server private readonly MqttRetainedMessagesManager _retainedMessagesManager; private readonly MqttServerEventDispatcher _eventDispatcher; private readonly MqttClientKeepAliveMonitor _keepAliveMonitor; - private readonly MqttClientPendingPacketsQueue _pendingPacketsQueue; + private readonly MqttClientSessionPendingMessagesQueue _pendingMessagesQueue; private readonly MqttClientSubscriptionsManager _subscriptionsManager; private readonly MqttClientSessionsManager _sessionsManager; @@ -31,6 +32,9 @@ namespace MQTTnet.Server private Task _workerTask; private IMqttChannelAdapter _channelAdapter; + private long _receivedMessagesCount; + private bool _isCleanSession = true; + public MqttClientSession( string clientId, IMqttServerOptions options, @@ -52,7 +56,7 @@ namespace MQTTnet.Server _keepAliveMonitor = new MqttClientKeepAliveMonitor(this, _logger); _subscriptionsManager = new MqttClientSubscriptionsManager(clientId, _options, eventDispatcher); - _pendingPacketsQueue = new MqttClientPendingPacketsQueue(_options, this, _logger); + _pendingMessagesQueue = new MqttClientSessionPendingMessagesQueue(_options, this, _logger); } public string ClientId { get; } @@ -63,28 +67,38 @@ namespace MQTTnet.Server status.IsConnected = _cancellationTokenSource != null; status.Endpoint = _channelAdapter?.Endpoint; status.ProtocolVersion = _channelAdapter?.PacketFormatterAdapter?.ProtocolVersion; - status.PendingApplicationMessagesCount = _pendingPacketsQueue.Count; + status.PendingApplicationMessagesCount = _pendingMessagesQueue.Count; + status.ReceivedApplicationMessagesCount = _pendingMessagesQueue.SentMessagesCount; + status.SentApplicationMessagesCount = Interlocked.Read(ref _receivedMessagesCount); status.LastPacketReceived = _keepAliveMonitor.LastPacketReceived; status.LastNonKeepAlivePacketReceived = _keepAliveMonitor.LastNonKeepAlivePacketReceived; } - public Task StopAsync(MqttClientDisconnectType type) + public async Task StopAsync(MqttClientDisconnectType type) { - return StopAsync(type, false); + StopInternal(type); + + var task = _workerTask; + if (task != null && !task.IsCompleted) + { + await task.ConfigureAwait(false); + } } - public async Task SubscribeAsync(IList topicFilters) + public async Task SubscribeAsync(IEnumerable topicFilters) { if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters)); + var topicFiltersCollection = topicFilters.ToList(); + var packet = new MqttSubscribePacket(); - packet.TopicFilters.AddRange(topicFilters); + packet.TopicFilters.AddRange(topicFiltersCollection); await _subscriptionsManager.SubscribeAsync(packet).ConfigureAwait(false); - await EnqueueSubscribedRetainedMessagesAsync(topicFilters).ConfigureAwait(false); + await EnqueueSubscribedRetainedMessagesAsync(topicFiltersCollection).ConfigureAwait(false); } - public Task UnsubscribeAsync(IList topicFilters) + public Task UnsubscribeAsync(IEnumerable topicFilters) { if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters)); @@ -98,12 +112,12 @@ namespace MQTTnet.Server public void ClearPendingApplicationMessages() { - _pendingPacketsQueue.Clear(); + _pendingMessagesQueue.Clear(); } public void Dispose() { - _pendingPacketsQueue?.Dispose(); + _pendingMessagesQueue?.Dispose(); _cancellationTokenSource?.Cancel(); _cancellationTokenSource?.Dispose(); @@ -161,7 +175,7 @@ namespace MQTTnet.Server publishPacket.QualityOfServiceLevel = context.ApplicationMessage.QualityOfServiceLevel; } - _pendingPacketsQueue.Enqueue(publishPacket); + _pendingMessagesQueue.Enqueue(publishPacket); } private async Task RunInternalAsync(MqttConnectPacket connectPacket, IMqttChannelAdapter channelAdapter) @@ -170,26 +184,41 @@ namespace MQTTnet.Server try { - channelAdapter.ReadingPacketStarted += OnAdapterReadingPacketStarted; - channelAdapter.ReadingPacketCompleted += OnAdapterReadingPacketCompleted; + _logger.Info("Client '{0}': Connected.", ClientId); + _eventDispatcher.OnClientConnected(ClientId); - _cancellationTokenSource = new CancellationTokenSource(); + _channelAdapter = channelAdapter; + + _channelAdapter.ReadingPacketStarted += OnAdapterReadingPacketStarted; + _channelAdapter.ReadingPacketCompleted += OnAdapterReadingPacketCompleted; + + var cancellationTokenSource = new CancellationTokenSource(); + _cancellationTokenSource = cancellationTokenSource; _wasCleanDisconnect = false; _willMessage = connectPacket.WillMessage; - _pendingPacketsQueue.Start(channelAdapter, _cancellationTokenSource.Token); - _keepAliveMonitor.Start(connectPacket.KeepAlivePeriod, _cancellationTokenSource.Token); + _pendingMessagesQueue.Start(channelAdapter, cancellationTokenSource.Token); + _keepAliveMonitor.Start(connectPacket.KeepAlivePeriod, cancellationTokenSource.Token); - _channelAdapter = channelAdapter; + await channelAdapter.SendPacketAsync( + new MqttConnAckPacket + { + ReturnCode = MqttConnectReturnCode.ConnectionAccepted, + ReasonCode = MqttConnectReasonCode.Success, + IsSessionPresent = _isCleanSession + }, + cancellationTokenSource.Token).ConfigureAwait(false); - while (!_cancellationTokenSource.IsCancellationRequested) + _isCleanSession = false; + + while (!cancellationTokenSource.IsCancellationRequested) { - var packet = await channelAdapter.ReceivePacketAsync(TimeSpan.Zero, _cancellationTokenSource.Token).ConfigureAwait(false); + var packet = await channelAdapter.ReceivePacketAsync(TimeSpan.Zero, cancellationTokenSource.Token).ConfigureAwait(false); if (packet != null) { _keepAliveMonitor.PacketReceived(packet); - await ProcessReceivedPacketAsync(channelAdapter, packet, _cancellationTokenSource.Token).ConfigureAwait(false); + await ProcessReceivedPacketAsync(channelAdapter, packet, cancellationTokenSource.Token).ConfigureAwait(false); } } } @@ -203,6 +232,9 @@ namespace MQTTnet.Server if (exception is MqttCommunicationClosedGracefullyException) { _logger.Verbose("Client '{0}': Connection closed gracefully.", ClientId); + + StopInternal(MqttClientDisconnectType.Clean); + return; } else { @@ -214,69 +246,50 @@ namespace MQTTnet.Server _logger.Error(exception, "Client '{0}': Unhandled exception while receiving client packets.", ClientId); } - await StopAsync(MqttClientDisconnectType.NotClean, true).ConfigureAwait(false); + StopInternal(MqttClientDisconnectType.NotClean); } finally { - channelAdapter.ReadingPacketStarted -= OnAdapterReadingPacketStarted; - channelAdapter.ReadingPacketCompleted -= OnAdapterReadingPacketCompleted; - - _cancellationTokenSource?.Cancel(false); - _cancellationTokenSource?.Dispose(); - _cancellationTokenSource = null; - - _workerTask = null; - } - } - - private async Task StopAsync(MqttClientDisconnectType type, bool isInsideSession) - { - try - { - var cts = _cancellationTokenSource; - if (cts == null || cts.IsCancellationRequested) - { - return; - } - - _wasCleanDisconnect = type == MqttClientDisconnectType.Clean; - - _cancellationTokenSource?.Cancel(false); - if (_willMessage != null && !_wasCleanDisconnect) { _sessionsManager.EnqueueApplicationMessage(this, _willMessage); } _willMessage = null; - - if (!isInsideSession) - { - if (_workerTask != null) - { - await _workerTask.ConfigureAwait(false); - } - } - await Task.FromResult(0); - } - finally - { + _channelAdapter.ReadingPacketStarted -= OnAdapterReadingPacketStarted; + _channelAdapter.ReadingPacketCompleted -= OnAdapterReadingPacketCompleted; + _channelAdapter = null; + _logger.Info("Client '{0}': Session stopped.", ClientId); _eventDispatcher.OnClientDisconnected(ClientId, _wasCleanDisconnect); + + _workerTask = null; } } + + private void StopInternal(MqttClientDisconnectType type) + { + var cts = _cancellationTokenSource; + if (cts == null || cts.IsCancellationRequested) + { + return; + } - private Task ProcessReceivedPacketAsync(IMqttChannelAdapter adapter, MqttBasePacket packet, CancellationToken cancellationToken) + _wasCleanDisconnect = type == MqttClientDisconnectType.Clean; + _cancellationTokenSource?.Cancel(false); + } + + private Task ProcessReceivedPacketAsync(IMqttChannelAdapter channelAdapter, MqttBasePacket packet, CancellationToken cancellationToken) { if (packet is MqttPublishPacket publishPacket) { - return HandleIncomingPublishPacketAsync(adapter, publishPacket, cancellationToken); + return HandleIncomingPublishPacketAsync(channelAdapter, publishPacket, cancellationToken); } if (packet is MqttPingReqPacket) { - return adapter.SendPacketAsync(new MqttPingRespPacket(), cancellationToken); + return channelAdapter.SendPacketAsync(new MqttPingRespPacket(), cancellationToken); } if (packet is MqttPubRelPacket pubRelPacket) @@ -287,7 +300,7 @@ namespace MQTTnet.Server ReasonCode = MqttPubCompReasonCode.Success }; - return adapter.SendPacketAsync(responsePacket, cancellationToken); + return channelAdapter.SendPacketAsync(responsePacket, cancellationToken); } if (packet is MqttPubRecPacket pubRecPacket) @@ -298,7 +311,7 @@ namespace MQTTnet.Server ReasonCode = MqttPubRelReasonCode.Success }; - return adapter.SendPacketAsync(responsePacket, cancellationToken); + return channelAdapter.SendPacketAsync(responsePacket, cancellationToken); } if (packet is MqttPubAckPacket || packet is MqttPubCompPacket) @@ -308,27 +321,24 @@ namespace MQTTnet.Server if (packet is MqttSubscribePacket subscribePacket) { - return HandleIncomingSubscribePacketAsync(adapter, subscribePacket, cancellationToken); + return HandleIncomingSubscribePacketAsync(channelAdapter, subscribePacket, cancellationToken); } if (packet is MqttUnsubscribePacket unsubscribePacket) { - return HandleIncomingUnsubscribePacketAsync(adapter, unsubscribePacket, cancellationToken); + return HandleIncomingUnsubscribePacketAsync(channelAdapter, unsubscribePacket, cancellationToken); } if (packet is MqttDisconnectPacket) { - return StopAsync(MqttClientDisconnectType.Clean, true); + StopInternal(MqttClientDisconnectType.Clean); + return Task.FromResult(0); } - if (packet is MqttConnectPacket) - { - return StopAsync(MqttClientDisconnectType.NotClean, true); - } + _logger.Warning(null, "Client '{0}': Received invalid packet ({1}). Closing connection.", ClientId, packet); - _logger.Warning(null, "Client '{0}': Received not supported packet ({1}). Closing connection.", ClientId, packet); - - return StopAsync(MqttClientDisconnectType.NotClean, true); + StopInternal(MqttClientDisconnectType.NotClean); + return Task.FromResult(0); } private async Task EnqueueSubscribedRetainedMessagesAsync(ICollection topicFilters) @@ -347,7 +357,8 @@ namespace MQTTnet.Server if (subscribeResult.CloseConnection) { - await StopAsync(MqttClientDisconnectType.NotClean, true).ConfigureAwait(false); + StopInternal(MqttClientDisconnectType.NotClean); + return; } await EnqueueSubscribedRetainedMessagesAsync(subscribePacket.TopicFilters).ConfigureAwait(false); @@ -361,12 +372,13 @@ namespace MQTTnet.Server private Task HandleIncomingPublishPacketAsync(IMqttChannelAdapter adapter, MqttPublishPacket publishPacket, CancellationToken cancellationToken) { + Interlocked.Increment(ref _receivedMessagesCount); + switch (publishPacket.QualityOfServiceLevel) { case MqttQualityOfServiceLevel.AtMostOnce: { - HandleIncomingPublishPacketWithQoS0(publishPacket); - return Task.FromResult(0); + return HandleIncomingPublishPacketWithQoS0Async(publishPacket); } case MqttQualityOfServiceLevel.AtLeastOnce: { @@ -383,11 +395,13 @@ namespace MQTTnet.Server } } - private void HandleIncomingPublishPacketWithQoS0(MqttPublishPacket publishPacket) + private Task HandleIncomingPublishPacketWithQoS0Async(MqttPublishPacket publishPacket) { _sessionsManager.EnqueueApplicationMessage( this, _channelAdapter.PacketFormatterAdapter.DataConverter.CreateApplicationMessage(publishPacket)); + + return Task.FromResult(0); } private Task HandleIncomingPublishPacketWithQoS1Async( diff --git a/Source/MQTTnet/Server/MqttClientPendingPacketsQueue.cs b/Source/MQTTnet/Server/MqttClientSessionPendingMessagesQueue.cs similarity index 88% rename from Source/MQTTnet/Server/MqttClientPendingPacketsQueue.cs rename to Source/MQTTnet/Server/MqttClientSessionPendingMessagesQueue.cs index eddd7889..60d5869b 100644 --- a/Source/MQTTnet/Server/MqttClientPendingPacketsQueue.cs +++ b/Source/MQTTnet/Server/MqttClientSessionPendingMessagesQueue.cs @@ -11,22 +11,24 @@ using MQTTnet.Protocol; namespace MQTTnet.Server { - public class MqttClientPendingPacketsQueue : IDisposable + public class MqttClientSessionPendingMessagesQueue : IDisposable { private readonly Queue _queue = new Queue(); - private readonly AsyncAutoResetEvent _queueAutoResetEvent = new AsyncAutoResetEvent(); + private readonly AsyncAutoResetEvent _queueLock = new AsyncAutoResetEvent(); private readonly IMqttServerOptions _options; private readonly MqttClientSession _clientSession; private readonly IMqttNetChildLogger _logger; - public MqttClientPendingPacketsQueue(IMqttServerOptions options, MqttClientSession clientSession, IMqttNetChildLogger logger) + private long _sentPacketsCount; + + public MqttClientSessionPendingMessagesQueue(IMqttServerOptions options, MqttClientSession clientSession, IMqttNetChildLogger logger) { if (logger == null) throw new ArgumentNullException(nameof(logger)); _options = options ?? throw new ArgumentNullException(nameof(options)); _clientSession = clientSession ?? throw new ArgumentNullException(nameof(clientSession)); - _logger = logger.CreateChildLogger(nameof(MqttClientPendingPacketsQueue)); + _logger = logger.CreateChildLogger(nameof(MqttClientSessionPendingMessagesQueue)); } public int Count @@ -40,6 +42,8 @@ namespace MQTTnet.Server } } + public long SentMessagesCount => Interlocked.Read(ref _sentPacketsCount); + public void Start(IMqttChannelAdapter adapter, CancellationToken cancellationToken) { if (adapter == null) throw new ArgumentNullException(nameof(adapter)); @@ -52,7 +56,7 @@ namespace MQTTnet.Server Task.Run(() => SendQueuedPacketsAsync(adapter, cancellationToken), cancellationToken); } - public void Enqueue(MqttBasePacket packet) + public void Enqueue(MqttPublishPacket packet) { if (packet == null) throw new ArgumentNullException(nameof(packet)); @@ -70,11 +74,11 @@ namespace MQTTnet.Server _queue.Dequeue(); } } - + _queue.Enqueue(packet); } - _queueAutoResetEvent.Set(); + _queueLock.Set(); _logger.Verbose("Enqueued packet (ClientId: {0}).", _clientSession.ClientId); } @@ -114,6 +118,11 @@ namespace MQTTnet.Server MqttBasePacket packet = null; try { + if (cancellationToken.IsCancellationRequested) + { + return; + } + lock (_queue) { if (_queue.Count > 0) @@ -124,18 +133,15 @@ namespace MQTTnet.Server if (packet == null) { - await _queueAutoResetEvent.WaitOneAsync(cancellationToken).ConfigureAwait(false); - return; - } - - if (cancellationToken.IsCancellationRequested) - { + await _queueLock.WaitOneAsync(cancellationToken).ConfigureAwait(false); return; } await adapter.SendPacketAsync(packet, cancellationToken).ConfigureAwait(false); _logger.Verbose("Enqueued packet sent (ClientId: {0}).", _clientSession.ClientId); + + Interlocked.Increment(ref _sentPacketsCount); } catch (Exception exception) { diff --git a/Source/MQTTnet/Server/MqttClientSessionStatus.cs b/Source/MQTTnet/Server/MqttClientSessionStatus.cs index 2673d7e6..007a19ba 100644 --- a/Source/MQTTnet/Server/MqttClientSessionStatus.cs +++ b/Source/MQTTnet/Server/MqttClientSessionStatus.cs @@ -21,7 +21,9 @@ namespace MQTTnet.Server public MqttProtocolVersion? ProtocolVersion { get; set; } public TimeSpan LastPacketReceived { get; set; } public TimeSpan LastNonKeepAlivePacketReceived { get; set; } - public int PendingApplicationMessagesCount { get; set; } + public long PendingApplicationMessagesCount { get; set; } + public long ReceivedApplicationMessagesCount { get; set; } + public long SentApplicationMessagesCount { get; set; } public Task DisconnectAsync() { diff --git a/Source/MQTTnet/Server/MqttClientSessionsManager.cs b/Source/MQTTnet/Server/MqttClientSessionsManager.cs index 9707e580..35379ce3 100644 --- a/Source/MQTTnet/Server/MqttClientSessionsManager.cs +++ b/Source/MQTTnet/Server/MqttClientSessionsManager.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Linq; using System.Threading; using System.Threading.Tasks; using MQTTnet.Adapter; @@ -49,27 +50,31 @@ namespace MQTTnet.Server public async Task StopAsync() { - using (await _sessionsLock.WaitAsync(_cancellationToken).ConfigureAwait(false)) + List sessions; + using (await _sessionsLock.WaitAsync(CancellationToken.None).ConfigureAwait(false)) { - foreach (var session in _sessions) - { - await session.Value.StopAsync(MqttClientDisconnectType.NotClean).ConfigureAwait(false); - } + sessions = _sessions.Values.ToList(); + } - _sessions.Clear(); + foreach (var session in sessions) + { + await session.StopAsync(MqttClientDisconnectType.Clean).ConfigureAwait(false); } } public Task HandleConnectionAsync(IMqttChannelAdapter clientAdapter) { - return Task.Run(() => HandleConnectionAsync(clientAdapter, _cancellationToken), _cancellationToken); + return HandleConnectionAsync(clientAdapter, _cancellationToken); + + // TODO: Check if Task.Run is required. + //return Task.Run(() => HandleConnectionAsync(clientAdapter, _cancellationToken), _cancellationToken); } public async Task> GetClientStatusAsync() { var result = new List(); - using (await _sessionsLock.WaitAsync(_cancellationToken).ConfigureAwait(false)) + using (await _sessionsLock.WaitAsync(CancellationToken.None).ConfigureAwait(false)) { foreach (var session in _sessions.Values) { @@ -90,42 +95,47 @@ namespace MQTTnet.Server _messageQueue.Add(new MqttEnqueuedApplicationMessage(senderClientSession, applicationMessage), _cancellationToken); } - public Task SubscribeAsync(string clientId, IList topicFilters) + public async Task SubscribeAsync(string clientId, IEnumerable topicFilters) { if (clientId == null) throw new ArgumentNullException(nameof(clientId)); if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters)); - lock (_sessions) + using (await _sessionsLock.WaitAsync(CancellationToken.None).ConfigureAwait(false)) { if (!_sessions.TryGetValue(clientId, out var session)) { throw new InvalidOperationException($"Client session '{clientId}' is unknown."); } - return session.SubscribeAsync(topicFilters); + await session.SubscribeAsync(topicFilters).ConfigureAwait(false); } } - public Task UnsubscribeAsync(string clientId, IList topicFilters) + public async Task UnsubscribeAsync(string clientId, IEnumerable topicFilters) { if (clientId == null) throw new ArgumentNullException(nameof(clientId)); if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters)); - lock (_sessions) + using (await _sessionsLock.WaitAsync(CancellationToken.None).ConfigureAwait(false)) { if (!_sessions.TryGetValue(clientId, out var session)) { throw new InvalidOperationException($"Client session '{clientId}' is unknown."); } - return session.UnsubscribeAsync(topicFilters); + await session.UnsubscribeAsync(topicFilters).ConfigureAwait(false); } } public async Task DeleteSessionAsync(string clientId) { - using (await _sessionsLock.WaitAsync(_cancellationToken).ConfigureAwait(false)) + using (await _sessionsLock.WaitAsync(CancellationToken.None).ConfigureAwait(false)) { + if (_sessions.TryGetValue(clientId, out var session)) + { + session.Dispose(); + } + _sessions.Remove(clientId); } @@ -187,7 +197,7 @@ namespace MQTTnet.Server await _retainedMessagesManager.HandleMessageAsync(sender?.ClientId, applicationMessage).ConfigureAwait(false); } - using (await _sessionsLock.WaitAsync(_cancellationToken).ConfigureAwait(false)) + using (await _sessionsLock.WaitAsync(CancellationToken.None).ConfigureAwait(false)) { foreach (var clientSession in _sessions.Values) { @@ -207,49 +217,37 @@ namespace MQTTnet.Server } } - private async Task HandleConnectionAsync(IMqttChannelAdapter clientAdapter, CancellationToken cancellationToken) + private async Task HandleConnectionAsync(IMqttChannelAdapter channelAdapter, CancellationToken cancellationToken) { var clientId = string.Empty; - + try { - var firstPacket = await clientAdapter.ReceivePacketAsync(_options.DefaultCommunicationTimeout, cancellationToken).ConfigureAwait(false); + var firstPacket = await channelAdapter.ReceivePacketAsync(_options.DefaultCommunicationTimeout, cancellationToken).ConfigureAwait(false); if (!(firstPacket is MqttConnectPacket connectPacket)) { - _logger.Warning(null, "The first packet from client '{0}' was no 'CONNECT' packet [MQTT-3.1.0-1].", clientAdapter.Endpoint); + _logger.Warning(null, "The first packet from client '{0}' was no 'CONNECT' packet [MQTT-3.1.0-1].", channelAdapter.Endpoint); return; } clientId = connectPacket.ClientId; - var connectReturnCode = await ValidateConnectionAsync(connectPacket, clientAdapter).ConfigureAwait(false); + var connectReturnCode = await ValidateConnectionAsync(connectPacket, channelAdapter).ConfigureAwait(false); if (connectReturnCode != MqttConnectReturnCode.ConnectionAccepted) { - await clientAdapter.SendPacketAsync( + await channelAdapter.SendPacketAsync( new MqttConnAckPacket { - ReturnCode = connectReturnCode + ReturnCode = connectReturnCode, + ReasonCode = MqttConnectReasonCode.NotAuthorized }, cancellationToken).ConfigureAwait(false); return; } - var result = await PrepareClientSessionAsync(connectPacket).ConfigureAwait(false); - - await clientAdapter.SendPacketAsync( - new MqttConnAckPacket - { - ReturnCode = connectReturnCode, - ReasonCode = MqttConnectReasonCode.Success, - IsSessionPresent = result.IsExistingSession - }, - cancellationToken).ConfigureAwait(false); - - _logger.Info("Client '{0}': Connected.", clientId); - _eventDispatcher.OnClientConnected(clientId); - - await result.Session.RunAsync(connectPacket, clientAdapter).ConfigureAwait(false); + var session = await PrepareClientSessionAsync(connectPacket).ConfigureAwait(false); + await session.RunAsync(connectPacket, channelAdapter).ConfigureAwait(false); } catch (OperationCanceledException) { @@ -260,12 +258,15 @@ namespace MQTTnet.Server } finally { - await clientAdapter.DisconnectAsync(_options.DefaultCommunicationTimeout, cancellationToken).ConfigureAwait(false); - clientAdapter.Dispose(); + await TryCleanupChannelAsync(channelAdapter).ConfigureAwait(false); if (!_options.EnablePersistentSessions) { - await DeleteSessionAsync(clientId).ConfigureAwait(false); + // TODO: Check if the session will be used later. + // Consider reference counter or "Recycle" property + // Or add timer (will be required for MQTTv5 (session life time) "IsActiveProperty". + //öö + //await DeleteSessionAsync(clientId).ConfigureAwait(false); } } } @@ -288,18 +289,17 @@ namespace MQTTnet.Server return context.ReturnCode; } - private async Task PrepareClientSessionAsync(MqttConnectPacket connectPacket) + private async Task PrepareClientSessionAsync(MqttConnectPacket connectPacket) { - using (await _sessionsLock.WaitAsync(_cancellationToken).ConfigureAwait(false)) + using (await _sessionsLock.WaitAsync(CancellationToken.None).ConfigureAwait(false)) { var isSessionPresent = _sessions.TryGetValue(connectPacket.ClientId, out var clientSession); if (isSessionPresent) { - await clientSession.StopAsync(MqttClientDisconnectType.Clean).ConfigureAwait(false); - if (connectPacket.CleanSession) { - _sessions.Remove(connectPacket.ClientId); + await clientSession.StopAsync(MqttClientDisconnectType.Clean).ConfigureAwait(false); + clientSession.Dispose(); clientSession = null; @@ -307,22 +307,21 @@ namespace MQTTnet.Server } else { + await clientSession.StopAsync(MqttClientDisconnectType.Clean).ConfigureAwait(false); + _logger.Verbose("Reusing existing session of client '{0}'.", connectPacket.ClientId); } } - var isExistingSession = true; if (clientSession == null) { - isExistingSession = false; - clientSession = new MqttClientSession(connectPacket.ClientId, _options, this, _retainedMessagesManager, _eventDispatcher, _logger); _sessions[connectPacket.ClientId] = clientSession; _logger.Verbose("Created a new session for client '{0}'.", connectPacket.ClientId); } - return new PrepareClientSessionResult { IsExistingSession = isExistingSession, Session = clientSession }; + return clientSession; } } @@ -338,5 +337,21 @@ namespace MQTTnet.Server await interceptor.InterceptApplicationMessagePublishAsync(interceptorContext).ConfigureAwait(false); return interceptorContext; } + + private async Task TryCleanupChannelAsync(IMqttChannelAdapter channelAdapter) + { + try + { + await channelAdapter.DisconnectAsync(_options.DefaultCommunicationTimeout, CancellationToken.None).ConfigureAwait(false); + } + catch (Exception exception) + { + _logger.Error(exception, "Error while disconnecting client channel."); + } + finally + { + channelAdapter.Dispose(); + } + } } } \ No newline at end of file diff --git a/Source/MQTTnet/Server/MqttServer.cs b/Source/MQTTnet/Server/MqttServer.cs index 1b5fb3be..ffba8fb3 100644 --- a/Source/MQTTnet/Server/MqttServer.cs +++ b/Source/MQTTnet/Server/MqttServer.cs @@ -56,7 +56,7 @@ namespace MQTTnet.Server return _retainedMessagesManager.GetMessagesAsync().GetAwaiter().GetResult(); } - public Task SubscribeAsync(string clientId, IList topicFilters) + public Task SubscribeAsync(string clientId, IEnumerable topicFilters) { if (clientId == null) throw new ArgumentNullException(nameof(clientId)); if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters)); @@ -64,7 +64,7 @@ namespace MQTTnet.Server return _clientSessionsManager.SubscribeAsync(clientId, topicFilters); } - public Task UnsubscribeAsync(string clientId, IList topicFilters) + public Task UnsubscribeAsync(string clientId, IEnumerable topicFilters) { if (clientId == null) throw new ArgumentNullException(nameof(clientId)); if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters)); @@ -118,7 +118,7 @@ namespace MQTTnet.Server _cancellationTokenSource.Cancel(false); - _clientSessionsManager.StopAsync().ConfigureAwait(false); + await _clientSessionsManager.StopAsync().ConfigureAwait(false); foreach (var adapter in _adapters) { diff --git a/Source/MQTTnet/Server/MqttServerExtensions.cs b/Source/MQTTnet/Server/MqttServerExtensions.cs new file mode 100644 index 00000000..e320f130 --- /dev/null +++ b/Source/MQTTnet/Server/MqttServerExtensions.cs @@ -0,0 +1,45 @@ +using System; +using System.Threading.Tasks; +using MQTTnet.Protocol; + +namespace MQTTnet.Server +{ + public static class MqttServerExtensions + { + public static Task SubscribeAsync(this IMqttServer server, string clientId, params TopicFilter[] topicFilters) + { + if (server == null) throw new ArgumentNullException(nameof(server)); + if (clientId == null) throw new ArgumentNullException(nameof(clientId)); + if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters)); + + return server.SubscribeAsync(clientId, topicFilters); + } + + public static Task SubscribeAsync(this IMqttServer server, string clientId, string topic, MqttQualityOfServiceLevel qualityOfServiceLevel) + { + if (server == null) throw new ArgumentNullException(nameof(server)); + if (clientId == null) throw new ArgumentNullException(nameof(clientId)); + if (topic == null) throw new ArgumentNullException(nameof(topic)); + + return server.SubscribeAsync(clientId, new TopicFilterBuilder().WithTopic(topic).WithQualityOfServiceLevel(qualityOfServiceLevel).Build()); + } + + public static Task SubscribeAsync(this IMqttServer server, string clientId, string topic) + { + if (server == null) throw new ArgumentNullException(nameof(server)); + if (clientId == null) throw new ArgumentNullException(nameof(clientId)); + if (topic == null) throw new ArgumentNullException(nameof(topic)); + + return server.SubscribeAsync(clientId, new TopicFilterBuilder().WithTopic(topic).Build()); + } + + public static Task UnsubscribeAsync(this IMqttServer server, string clientId, params string[] topicFilters) + { + if (server == null) throw new ArgumentNullException(nameof(server)); + if (clientId == null) throw new ArgumentNullException(nameof(clientId)); + if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters)); + + return server.UnsubscribeAsync(clientId, topicFilters); + } + } +} diff --git a/Tests/MQTTnet.Core.Tests/MqttServerTests.cs b/Tests/MQTTnet.Core.Tests/MqttServerTests.cs index b3f9f21b..915bc807 100644 --- a/Tests/MQTTnet.Core.Tests/MqttServerTests.cs +++ b/Tests/MQTTnet.Core.Tests/MqttServerTests.cs @@ -17,7 +17,7 @@ using MQTTnet.Server; namespace MQTTnet.Tests { [TestClass] - public class MqttServerTests + public partial class MqttServerTests { [TestMethod] public void MqttServer_PublishSimple_AtMostOnce() @@ -263,6 +263,91 @@ namespace MQTTnet.Tests } } + [TestMethod] + public async Task MqttServer_No_Messages_If_No_Subscription() + { + var server = new MqttFactory().CreateMqttServer(); + try + { + await server.StartAsync(new MqttServerOptions()); + + var client = new MqttFactory().CreateMqttClient(); + var receivedMessages = new List(); + + var options = new MqttClientOptionsBuilder() + .WithTcpServer("localhost").Build(); + + client.Connected += async (s, e) => + { + await client.PublishAsync("Connected"); + }; + + client.ApplicationMessageReceived += (s, e) => + { + lock (receivedMessages) + { + receivedMessages.Add(e.ApplicationMessage); + } + }; + + await client.ConnectAsync(options); + + await Task.Delay(500); + + await client.PublishAsync("Hello"); + + await Task.Delay(500); + + Assert.AreEqual(0, receivedMessages.Count); + } + finally + { + await server.StopAsync(); + } + } + + [TestMethod] + public async Task MqttServer_Set_Subscription_At_Server() + { + var server = new MqttFactory().CreateMqttServer(); + try + { + await server.StartAsync(new MqttServerOptions()); + server.ClientConnected += async (s, e) => + { + await server.SubscribeAsync(e.ClientId, "topic1"); + }; + + var client = new MqttFactory().CreateMqttClient(); + var receivedMessages = new List(); + + var options = new MqttClientOptionsBuilder() + .WithTcpServer("localhost").Build(); + + client.ApplicationMessageReceived += (s, e) => + { + lock (receivedMessages) + { + receivedMessages.Add(e.ApplicationMessage); + } + }; + + await client.ConnectAsync(options); + + await Task.Delay(500); + + await client.PublishAsync("Hello"); + + await Task.Delay(500); + + Assert.AreEqual(0, receivedMessages.Count); + } + finally + { + await server.StopAsync(); + } + } + private static async Task Publish(IMqttClient c1, MqttApplicationMessage message) { for (int i = 0; i < 1000; i++) @@ -302,40 +387,29 @@ namespace MQTTnet.Tests [TestMethod] public async Task MqttServer_Handle_Clean_Disconnect() { - var s = new MqttFactory().CreateMqttServer(); - try + using (var testSetup = new TestSetup()) { + var server = await testSetup.StartServerAsync(new MqttServerOptionsBuilder()); + var clientConnectedCalled = 0; var clientDisconnectedCalled = 0; - s.ClientConnected += (_, __) => clientConnectedCalled++; - s.ClientDisconnected += (_, __) => clientDisconnectedCalled++; + server.ClientConnected += (_, __) => Interlocked.Increment(ref clientConnectedCalled); + server.ClientDisconnected += (_, __) => Interlocked.Increment(ref clientDisconnectedCalled); + + var c1 = await testSetup.ConnectClient(new MqttClientOptionsBuilder()); - var clientOptions = new MqttClientOptionsBuilder() - .WithTcpServer("localhost") - .Build(); + Assert.AreEqual(1, clientConnectedCalled); + Assert.AreEqual(0, clientDisconnectedCalled); - await s.StartAsync(new MqttServerOptions()); - - var c1 = new MqttFactory().CreateMqttClient(); - - await c1.ConnectAsync(clientOptions); - - await Task.Delay(100); + await Task.Delay(500); await c1.DisconnectAsync(); - await Task.Delay(100); + await Task.Delay(500); - await s.StopAsync(); - - await Task.Delay(100); - - Assert.AreEqual(clientConnectedCalled, clientDisconnectedCalled); - } - finally - { - await s.StopAsync(); + Assert.AreEqual(1, clientConnectedCalled); + Assert.AreEqual(1, clientDisconnectedCalled); } } @@ -385,7 +459,7 @@ namespace MQTTnet.Tests } [TestMethod] - public async Task MqttServer_LotsOfRetainedMessages() + public async Task MqttServer_Lots_Of_Retained_Messages() { const int ClientCount = 100; @@ -745,58 +819,64 @@ namespace MQTTnet.Tests } [TestMethod] - public async Task MqttServer_SameClientIdConnectDisconnectEventOrder() + public async Task MqttServer_Same_Client_Id_Connect_Disconnect_Event_Order() { - var s = new MqttFactory().CreateMqttServer(); - - var events = new List(); - - s.ClientConnected += (_, __) => + using (var testSetup = new TestSetup()) { - lock (events) + var server = await testSetup.StartServerAsync(new MqttServerOptionsBuilder()); + + var events = new List(); + + server.ClientConnected += (_, __) => { - events.Add("c"); - } - }; + lock (events) + { + events.Add("c"); + } + }; - s.ClientDisconnected += (_, __) => - { - lock (events) + server.ClientDisconnected += (_, __) => { - events.Add("d"); - } - }; + lock (events) + { + events.Add("d"); + } + }; - var clientOptions = new MqttClientOptionsBuilder() - .WithTcpServer("localhost") - .WithClientId("same_id") - .Build(); + var clientOptions = new MqttClientOptionsBuilder() + .WithClientId("same_id"); - await s.StartAsync(new MqttServerOptions()); + // c + var c1 = await testSetup.ConnectClient(clientOptions); + + await Task.Delay(500); - var c1 = new MqttFactory().CreateMqttClient(); - var c2 = new MqttFactory().CreateMqttClient(); + var flow = string.Join(string.Empty, events); + Assert.AreEqual("c", flow); - await c1.ConnectAsync(clientOptions); + // dc + var c2 = await testSetup.ConnectClient(clientOptions); - await Task.Delay(250); + await Task.Delay(500); - await c2.ConnectAsync(clientOptions); + flow = string.Join(string.Empty, events); + Assert.AreEqual("cdc", flow); - await Task.Delay(250); + // nothing + await c1.DisconnectAsync(); - await c1.DisconnectAsync(); + await Task.Delay(500); - await Task.Delay(250); + // d + await c2.DisconnectAsync(); - await c2.DisconnectAsync(); + await Task.Delay(500); - await Task.Delay(250); + await server.StopAsync(); - await s.StopAsync(); - - var flow = string.Join(string.Empty, events); - Assert.AreEqual("cdcd", flow); + flow = string.Join(string.Empty, events); + Assert.AreEqual("cdcd", flow); + } } [TestMethod] diff --git a/Tests/MQTTnet.Core.Tests/TestSetup.cs b/Tests/MQTTnet.Core.Tests/TestSetup.cs new file mode 100644 index 00000000..2c07170d --- /dev/null +++ b/Tests/MQTTnet.Core.Tests/TestSetup.cs @@ -0,0 +1,92 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using MQTTnet.Client; +using MQTTnet.Client.Options; +using MQTTnet.Diagnostics; +using MQTTnet.Server; + +namespace MQTTnet.Tests +{ + public class TestSetup : IDisposable + { + private readonly MqttFactory _mqttFactory = new MqttFactory(); + private readonly List _clients = new List(); + private readonly IMqttNetLogger _serverLogger = new MqttNetLogger("server"); + private readonly IMqttNetLogger _clientLogger = new MqttNetLogger("client"); + + private IMqttServer _server; + + private long _serverErrorsCount; + private long _clientErrorsCount; + + public TestSetup() + { + _serverLogger.LogMessagePublished += (s, e) => + { + if (e.TraceMessage.Level == MqttNetLogLevel.Error) + { + Interlocked.Increment(ref _serverErrorsCount); + } + }; + + _clientLogger.LogMessagePublished += (s, e) => + { + if (e.TraceMessage.Level == MqttNetLogLevel.Error) + { + Interlocked.Increment(ref _clientErrorsCount); + } + }; + } + + public async Task StartServerAsync(MqttServerOptionsBuilder options) + { + if (_server != null) + { + throw new InvalidOperationException("Server already started."); + } + + _server = _mqttFactory.CreateMqttServer(_serverLogger); + await _server.StartAsync(options.WithDefaultEndpointPort(1888).Build()); + + return _server; + } + + public async Task ConnectClient(MqttClientOptionsBuilder options) + { + var client = _mqttFactory.CreateMqttClient(_clientLogger); + _clients.Add(client); + + await client.ConnectAsync(options.WithTcpServer("localhost", 1888).Build()); + + return client; + } + + public void ThrowIfLogErrors() + { + if (_serverErrorsCount > 0) + { + throw new Exception($"Server had {_serverErrorsCount} errors."); + } + + if (_clientErrorsCount > 0) + { + throw new Exception($"Client(s) had {_clientErrorsCount} errors."); + } + } + + public void Dispose() + { + ThrowIfLogErrors(); + + foreach (var mqttClient in _clients) + { + mqttClient.DisconnectAsync().GetAwaiter().GetResult(); + mqttClient.Dispose(); + } + + _server.StopAsync().GetAwaiter().GetResult(); + } + } +} From 0d6e244f841773c3b4a35c0398f7855b35ab44d2 Mon Sep 17 00:00:00 2001 From: Christian Kratky Date: Sun, 6 Jan 2019 13:31:27 +0100 Subject: [PATCH 025/124] Refactor UnitTests. --- Tests/MQTTnet.Core.Tests/MqttServerTests.cs | 114 ++++++++++---------- Tests/MQTTnet.Core.Tests/TestSetup.cs | 2 +- 2 files changed, 56 insertions(+), 60 deletions(-) diff --git a/Tests/MQTTnet.Core.Tests/MqttServerTests.cs b/Tests/MQTTnet.Core.Tests/MqttServerTests.cs index 915bc807..ed6faa9f 100644 --- a/Tests/MQTTnet.Core.Tests/MqttServerTests.cs +++ b/Tests/MQTTnet.Core.Tests/MqttServerTests.cs @@ -10,14 +10,13 @@ using MQTTnet.Adapter; using MQTTnet.Client; using MQTTnet.Client.Options; using MQTTnet.Diagnostics; -using MQTTnet.Implementations; using MQTTnet.Protocol; using MQTTnet.Server; namespace MQTTnet.Tests { [TestClass] - public partial class MqttServerTests + public class MqttServerTests { [TestMethod] public void MqttServer_PublishSimple_AtMostOnce() @@ -173,7 +172,7 @@ namespace MQTTnet.Tests } [TestMethod] - public async Task MqttServer_Publish_MultipleClients() + public async Task MqttServer_Publish_Multiple_Clients() { var s = new MqttFactory().CreateMqttServer(); var receivedMessagesCount = 0; @@ -357,31 +356,25 @@ namespace MQTTnet.Tests } [TestMethod] - public async Task MqttServer_ShutdownDisconnectsClientsGracefully() + public async Task MqttServer_Shutdown_Disconnects_Clients_Gracefully() { - var serverAdapter = new MqttTcpServerAdapter(new MqttNetLogger().CreateChildLogger()); - var s = new MqttFactory().CreateMqttServer(new[] { serverAdapter }, new MqttNetLogger()); + using (var testSetup = new TestSetup()) + { + var server = await testSetup.StartServerAsync(new MqttServerOptionsBuilder()); - var clientOptions = new MqttClientOptionsBuilder() - .WithTcpServer("localhost") - .Build(); + var disconnectCalled = 0; - var disconnectCalled = 0; + var c1 = await testSetup.ConnectClientAsync(new MqttClientOptionsBuilder()); + c1.Disconnected += (sender, args) => disconnectCalled++; - await s.StartAsync(new MqttServerOptions()); + await Task.Delay(100); - var c1 = new MqttFactory().CreateMqttClient(); - c1.Disconnected += (sender, args) => disconnectCalled++; + await server.StopAsync(); - await c1.ConnectAsync(clientOptions); + await Task.Delay(100); - await Task.Delay(100); - - await s.StopAsync(); - - await Task.Delay(100); - - Assert.AreEqual(1, disconnectCalled); + Assert.AreEqual(1, disconnectCalled); + } } [TestMethod] @@ -397,7 +390,7 @@ namespace MQTTnet.Tests server.ClientConnected += (_, __) => Interlocked.Increment(ref clientConnectedCalled); server.ClientDisconnected += (_, __) => Interlocked.Increment(ref clientDisconnectedCalled); - var c1 = await testSetup.ConnectClient(new MqttClientOptionsBuilder()); + var c1 = await testSetup.ConnectClientAsync(new MqttClientOptionsBuilder()); Assert.AreEqual(1, clientConnectedCalled); Assert.AreEqual(0, clientDisconnectedCalled); @@ -416,46 +409,30 @@ namespace MQTTnet.Tests [TestMethod] public async Task MqttServer_Client_Disconnect_Without_Errors() { - var errors = 0; - - MqttNetGlobalLogger.LogMessagePublished += (_, e) => + using (var testSetup = new TestSetup()) { - System.Diagnostics.Debug.WriteLine($"[{e.TraceMessage.Timestamp:s}] {e.TraceMessage.Source} {e.TraceMessage.Message}"); + bool clientWasConnected; - if (e.TraceMessage.Level == MqttNetLogLevel.Error) + var server = await testSetup.StartServerAsync(new MqttServerOptionsBuilder()); + try { - errors++; + var client = await testSetup.ConnectClientAsync(new MqttClientOptionsBuilder()); + + clientWasConnected = true; + + await client.DisconnectAsync(); + + await Task.Delay(500); + } + finally + { + await server.StopAsync(); } - }; - bool clientWasConnected; + Assert.IsTrue(clientWasConnected); - var server = new MqttFactory().CreateMqttServer(); - try - { - var options = new MqttServerOptionsBuilder().Build(); - await server.StartAsync(options); - - var client = new MqttFactory().CreateMqttClient(); - var clientOptions = new MqttClientOptionsBuilder() - .WithTcpServer("localhost") - .Build(); - - await client.ConnectAsync(clientOptions); - - clientWasConnected = true; - - await client.DisconnectAsync(); - - await Task.Delay(500); + testSetup.ThrowIfLogErrors(); } - finally - { - await server.StopAsync(); - } - - Assert.IsTrue(clientWasConnected); - Assert.AreEqual(0, errors); } [TestMethod] @@ -699,7 +676,7 @@ namespace MQTTnet.Tests } [TestMethod] - public async Task MqttServer_InterceptMessage() + public async Task MqttServer_Intercept_Message() { void Interceptor(MqttApplicationMessageInterceptorContext context) { @@ -847,7 +824,7 @@ namespace MQTTnet.Tests .WithClientId("same_id"); // c - var c1 = await testSetup.ConnectClient(clientOptions); + var c1 = await testSetup.ConnectClientAsync(clientOptions); await Task.Delay(500); @@ -855,7 +832,7 @@ namespace MQTTnet.Tests Assert.AreEqual("c", flow); // dc - var c2 = await testSetup.ConnectClient(clientOptions); + var c2 = await testSetup.ConnectClientAsync(clientOptions); await Task.Delay(500); @@ -880,7 +857,26 @@ namespace MQTTnet.Tests } [TestMethod] - public async Task MqttServer_StopAndRestart() + public async Task MqttServer_Remove_Session() + { + using (var testSetup = new TestSetup()) + { + var server = await testSetup.StartServerAsync(new MqttServerOptionsBuilder()); + + var clientOptions = new MqttClientOptionsBuilder(); + var c1 = await testSetup.ConnectClientAsync(clientOptions); + await Task.Delay(500); + Assert.AreEqual(1, (await server.GetClientSessionsStatusAsync()).Count); + + await c1.DisconnectAsync(); + await Task.Delay(500); + + Assert.AreEqual(0, (await server.GetClientSessionsStatusAsync()).Count); + } + } + + [TestMethod] + public async Task MqttServer_Stop_And_Restart() { var server = new MqttFactory().CreateMqttServer(); await server.StartAsync(new MqttServerOptions()); diff --git a/Tests/MQTTnet.Core.Tests/TestSetup.cs b/Tests/MQTTnet.Core.Tests/TestSetup.cs index 2c07170d..4ffc3ec9 100644 --- a/Tests/MQTTnet.Core.Tests/TestSetup.cs +++ b/Tests/MQTTnet.Core.Tests/TestSetup.cs @@ -53,7 +53,7 @@ namespace MQTTnet.Tests return _server; } - public async Task ConnectClient(MqttClientOptionsBuilder options) + public async Task ConnectClientAsync(MqttClientOptionsBuilder options) { var client = _mqttFactory.CreateMqttClient(_clientLogger); _clients.Add(client); From ebf0e91ab87122dc1b0d6c2e2cb6d8d4ba63b2b6 Mon Sep 17 00:00:00 2001 From: Christian Kratky Date: Mon, 21 Jan 2019 21:38:39 +0100 Subject: [PATCH 026/124] Refactor subscribe and unsubscribe to support MQTTv5. --- Build/MQTTnet.nuspec | 1 + MQTTnet.noUWP.sln | 38 +-- .../ManagedMqttClient.cs | 11 +- Source/MQTTnet/Adapter/MqttChannelAdapter.cs | 19 +- Source/MQTTnet/Client/IMqttClient.cs | 5 +- Source/MQTTnet/Client/MqttClient.cs | 313 +++++++++++------- Source/MQTTnet/Client/MqttClientExtensions.cs | 56 +++- .../Client/MqttPacketIdentifierProvider.cs | 2 +- .../Client/Options/MqttClientOptions.cs | 2 +- .../PacketDispatcher/MqttPacketAwaiter.cs | 37 --- .../IMqttApplicationMessageHandler.cs | 9 + .../MqttApplicationMessageHandlerContext.cs | 15 + .../MqttApplicationMessageHandlerDelegate.cs | 31 ++ .../Subscribing/MqttClientSubscribeOptions.cs | 12 + .../MqttClientUnsubscribeOptions.cs | 12 + ...tCommunicationClosedGracefullyException.cs | 6 - .../MQTTnet/Formatter/IMqttDataConverter.cs | 4 + Source/MQTTnet/Formatter/MqttPacketReader.cs | 87 +++-- .../Formatter/ReadFixedHeaderResult.cs | 9 + .../Formatter/V3/MqttV310DataConverter.cs | 30 ++ .../Formatter/V5/MqttV500DataConverter.cs | 30 ++ Source/MQTTnet/IApplicationMessageReceiver.cs | 4 + Source/MQTTnet/Internal/ExceptionHelper.cs | 20 -- Source/MQTTnet/MQTTnet.csproj | 12 - .../MessageStream/MqttMessageStream.cs | 79 +++++ .../PacketDispatcher/IMqttPacketAwaiter.cs | 6 +- .../PacketDispatcher/MqttPacketAwaiter.cs | 59 ++++ .../PacketDispatcher/MqttPacketDispatcher.cs | 57 +++- Source/MQTTnet/Server/MqttClientSession.cs | 157 +++++---- .../MqttClientSessionPendingMessagesQueue.cs | 53 ++- Source/MQTTnet/Server/MqttServer.cs | 15 +- .../MQTTnet.Benchmarks/SerializerBenchmark.cs | 2 +- .../ManagedMqttClientTests.cs | 36 +- Tests/MQTTnet.Core.Tests/MqttClientTests.cs | 200 +++++++++-- .../MqttPacketIdentifierProviderTests.cs | 10 +- .../MqttPacketReaderTests.cs | 9 +- .../MqttPacketSerializerTests.cs | 6 +- Tests/MQTTnet.Core.Tests/MqttServerTests.cs | 134 ++++---- Tests/MQTTnet.Core.Tests/TestSetup.cs | 71 +++- 39 files changed, 1183 insertions(+), 476 deletions(-) delete mode 100644 Source/MQTTnet/Client/PacketDispatcher/MqttPacketAwaiter.cs create mode 100644 Source/MQTTnet/Client/Receiving/IMqttApplicationMessageHandler.cs create mode 100644 Source/MQTTnet/Client/Receiving/MqttApplicationMessageHandlerContext.cs create mode 100644 Source/MQTTnet/Client/Receiving/MqttApplicationMessageHandlerDelegate.cs create mode 100644 Source/MQTTnet/Client/Subscribing/MqttClientSubscribeOptions.cs create mode 100644 Source/MQTTnet/Client/Unsubscribing/MqttClientUnsubscribeOptions.cs delete mode 100644 Source/MQTTnet/Exceptions/MqttCommunicationClosedGracefullyException.cs create mode 100644 Source/MQTTnet/Formatter/ReadFixedHeaderResult.cs delete mode 100644 Source/MQTTnet/Internal/ExceptionHelper.cs create mode 100644 Source/MQTTnet/MessageStream/MqttMessageStream.cs rename Source/MQTTnet/{Client => }/PacketDispatcher/IMqttPacketAwaiter.cs (55%) create mode 100644 Source/MQTTnet/PacketDispatcher/MqttPacketAwaiter.cs rename Source/MQTTnet/{Client => }/PacketDispatcher/MqttPacketDispatcher.cs (53%) diff --git a/Build/MQTTnet.nuspec b/Build/MQTTnet.nuspec index 07ee7363..564e47e0 100644 --- a/Build/MQTTnet.nuspec +++ b/Build/MQTTnet.nuspec @@ -13,6 +13,7 @@ * [Core] Added support for MQTTv5 packages. * [Client] Added new MQTTv5 features to options builder. * [Client] Added uniform API across all supported MQTT versions (BREAKING CHANGE!) +* [Client] The client will now avoid sending an ACK if an exception has been thrown in message handler (thanks to @ramonsmits). * [Server] Added support for MQTTv5 clients. The server will still return _success_ for all cases at the moment even if more granular codes are available. * [Note] Due to MQTTv5 a lot of new classes were introduced. This required adding new namespaces as well. Most classes are backward compatible but new namespaces must be added. diff --git a/MQTTnet.noUWP.sln b/MQTTnet.noUWP.sln index 8d9130ed..f68cbb59 100644 --- a/MQTTnet.noUWP.sln +++ b/MQTTnet.noUWP.sln @@ -3,8 +3,6 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 VisualStudioVersion = 15.0.27004.2010 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.Core.Tests", "Tests\MQTTnet.Core.Tests\MQTTnet.Core.Tests.csproj", "{A7FF0C91-25DE-4BA6-B39E-F54E8DADF1CC}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{9248C2E1-B9D6-40BF-81EC-86004D7765B4}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Source", "Source", "{32A630A7-2598-41D7-B625-204CD906F5FB}" @@ -45,6 +43,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.Extensions.ManagedC EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.AspNetCore.Tests", "Tests\MQTTnet.AspNetCore.Tests\MQTTnet.AspNetCore.Tests.csproj", "{61B62223-F5D0-48E4-BBD6-2CBA9353CB5E}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.Tests", "Tests\MQTTnet.Core.Tests\MQTTnet.Tests.csproj", "{9C7106CA-96B8-4ABE-B3B4-9357AB8ACB41}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -57,22 +57,6 @@ Global Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {A7FF0C91-25DE-4BA6-B39E-F54E8DADF1CC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A7FF0C91-25DE-4BA6-B39E-F54E8DADF1CC}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A7FF0C91-25DE-4BA6-B39E-F54E8DADF1CC}.Debug|ARM.ActiveCfg = Debug|Any CPU - {A7FF0C91-25DE-4BA6-B39E-F54E8DADF1CC}.Debug|ARM.Build.0 = Debug|Any CPU - {A7FF0C91-25DE-4BA6-B39E-F54E8DADF1CC}.Debug|x64.ActiveCfg = Debug|Any CPU - {A7FF0C91-25DE-4BA6-B39E-F54E8DADF1CC}.Debug|x64.Build.0 = Debug|Any CPU - {A7FF0C91-25DE-4BA6-B39E-F54E8DADF1CC}.Debug|x86.ActiveCfg = Debug|Any CPU - {A7FF0C91-25DE-4BA6-B39E-F54E8DADF1CC}.Debug|x86.Build.0 = Debug|Any CPU - {A7FF0C91-25DE-4BA6-B39E-F54E8DADF1CC}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A7FF0C91-25DE-4BA6-B39E-F54E8DADF1CC}.Release|Any CPU.Build.0 = Release|Any CPU - {A7FF0C91-25DE-4BA6-B39E-F54E8DADF1CC}.Release|ARM.ActiveCfg = Release|Any CPU - {A7FF0C91-25DE-4BA6-B39E-F54E8DADF1CC}.Release|ARM.Build.0 = Release|Any CPU - {A7FF0C91-25DE-4BA6-B39E-F54E8DADF1CC}.Release|x64.ActiveCfg = Release|Any CPU - {A7FF0C91-25DE-4BA6-B39E-F54E8DADF1CC}.Release|x64.Build.0 = Release|Any CPU - {A7FF0C91-25DE-4BA6-B39E-F54E8DADF1CC}.Release|x86.ActiveCfg = Release|Any CPU - {A7FF0C91-25DE-4BA6-B39E-F54E8DADF1CC}.Release|x86.Build.0 = Release|Any CPU {3587E506-55A2-4EB3-99C7-DC01E42D25D2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {3587E506-55A2-4EB3-99C7-DC01E42D25D2}.Debug|Any CPU.Build.0 = Debug|Any CPU {3587E506-55A2-4EB3-99C7-DC01E42D25D2}.Debug|ARM.ActiveCfg = Debug|Any CPU @@ -201,12 +185,27 @@ Global {61B62223-F5D0-48E4-BBD6-2CBA9353CB5E}.Release|x64.Build.0 = Release|Any CPU {61B62223-F5D0-48E4-BBD6-2CBA9353CB5E}.Release|x86.ActiveCfg = Release|Any CPU {61B62223-F5D0-48E4-BBD6-2CBA9353CB5E}.Release|x86.Build.0 = Release|Any CPU + {9C7106CA-96B8-4ABE-B3B4-9357AB8ACB41}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9C7106CA-96B8-4ABE-B3B4-9357AB8ACB41}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9C7106CA-96B8-4ABE-B3B4-9357AB8ACB41}.Debug|ARM.ActiveCfg = Debug|Any CPU + {9C7106CA-96B8-4ABE-B3B4-9357AB8ACB41}.Debug|ARM.Build.0 = Debug|Any CPU + {9C7106CA-96B8-4ABE-B3B4-9357AB8ACB41}.Debug|x64.ActiveCfg = Debug|Any CPU + {9C7106CA-96B8-4ABE-B3B4-9357AB8ACB41}.Debug|x64.Build.0 = Debug|Any CPU + {9C7106CA-96B8-4ABE-B3B4-9357AB8ACB41}.Debug|x86.ActiveCfg = Debug|Any CPU + {9C7106CA-96B8-4ABE-B3B4-9357AB8ACB41}.Debug|x86.Build.0 = Debug|Any CPU + {9C7106CA-96B8-4ABE-B3B4-9357AB8ACB41}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9C7106CA-96B8-4ABE-B3B4-9357AB8ACB41}.Release|Any CPU.Build.0 = Release|Any CPU + {9C7106CA-96B8-4ABE-B3B4-9357AB8ACB41}.Release|ARM.ActiveCfg = Release|Any CPU + {9C7106CA-96B8-4ABE-B3B4-9357AB8ACB41}.Release|ARM.Build.0 = Release|Any CPU + {9C7106CA-96B8-4ABE-B3B4-9357AB8ACB41}.Release|x64.ActiveCfg = Release|Any CPU + {9C7106CA-96B8-4ABE-B3B4-9357AB8ACB41}.Release|x64.Build.0 = Release|Any CPU + {9C7106CA-96B8-4ABE-B3B4-9357AB8ACB41}.Release|x86.ActiveCfg = Release|Any CPU + {9C7106CA-96B8-4ABE-B3B4-9357AB8ACB41}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution - {A7FF0C91-25DE-4BA6-B39E-F54E8DADF1CC} = {9248C2E1-B9D6-40BF-81EC-86004D7765B4} {3587E506-55A2-4EB3-99C7-DC01E42D25D2} = {32A630A7-2598-41D7-B625-204CD906F5FB} {3D283AAD-AAA8-4339-8394-52F80B6304DB} = {9248C2E1-B9D6-40BF-81EC-86004D7765B4} {C6FF8AEA-0855-41EC-A1F3-AC262225BAB9} = {9248C2E1-B9D6-40BF-81EC-86004D7765B4} @@ -215,6 +214,7 @@ Global {998D04DD-7CB0-45F5-A393-E2495C16399E} = {9248C2E1-B9D6-40BF-81EC-86004D7765B4} {C400533A-8EBA-4F0B-BF4D-295C3708604B} = {12816BCC-AF9E-44A9-9AE5-C246AF2A0587} {61B62223-F5D0-48E4-BBD6-2CBA9353CB5E} = {9248C2E1-B9D6-40BF-81EC-86004D7765B4} + {9C7106CA-96B8-4ABE-B3B4-9357AB8ACB41} = {9248C2E1-B9D6-40BF-81EC-86004D7765B4} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {07536672-5CBC-4BE3-ACE0-708A431A7894} diff --git a/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClient.cs b/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClient.cs index e1feb8d3..6fcbee9a 100644 --- a/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClient.cs +++ b/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClient.cs @@ -5,6 +5,7 @@ using System.Threading; using System.Threading.Tasks; using MQTTnet.Client; using MQTTnet.Client.Publishing; +using MQTTnet.Client.Receiving; using MQTTnet.Diagnostics; using MQTTnet.Exceptions; using MQTTnet.Internal; @@ -52,6 +53,12 @@ namespace MQTTnet.Extensions.ManagedClient public event EventHandler Connected; public event EventHandler Disconnected; + public IMqttApplicationMessageHandler ReceivedApplicationMessageHandler + { + get => _mqttClient.ReceivedApplicationMessageHandler; + set => _mqttClient.ReceivedApplicationMessageHandler = value; + } + public event EventHandler ApplicationMessageReceived; public event EventHandler ApplicationMessageProcessed; public event EventHandler ApplicationMessageSkipped; @@ -372,12 +379,12 @@ namespace MQTTnet.Extensions.ManagedClient { if (unsubscriptions.Any()) { - await _mqttClient.UnsubscribeAsync(unsubscriptions).ConfigureAwait(false); + await _mqttClient.UnsubscribeAsync(unsubscriptions.ToArray()).ConfigureAwait(false); } if (subscriptions.Any()) { - await _mqttClient.SubscribeAsync(subscriptions).ConfigureAwait(false); + await _mqttClient.SubscribeAsync(subscriptions.ToArray()).ConfigureAwait(false); } } catch (Exception exception) diff --git a/Source/MQTTnet/Adapter/MqttChannelAdapter.cs b/Source/MQTTnet/Adapter/MqttChannelAdapter.cs index 44ef5be3..ded2a1fd 100644 --- a/Source/MQTTnet/Adapter/MqttChannelAdapter.cs +++ b/Source/MQTTnet/Adapter/MqttChannelAdapter.cs @@ -174,12 +174,18 @@ namespace MQTTnet.Adapter private async Task ReceiveAsync(CancellationToken cancellationToken) { - var fixedHeader = await _packetReader.ReadFixedHeaderAsync(_fixedHeaderBuffer, cancellationToken).ConfigureAwait(false); + var readFixedHeaderResult = await _packetReader.ReadFixedHeaderAsync(_fixedHeaderBuffer, cancellationToken).ConfigureAwait(false); try { + if (readFixedHeaderResult.ConnectionClosed) + { + return null; + } + ReadingPacketStarted?.Invoke(this, EventArgs.Empty); + var fixedHeader = readFixedHeaderResult.FixedHeader; if (fixedHeader.RemainingLength == 0) { return new ReceivedMqttPacket(fixedHeader.Flags, null, 2); @@ -205,9 +211,16 @@ namespace MQTTnet.Adapter var readBytes = _channel.ReadAsync(body, bodyOffset, chunkSize, cancellationToken).ConfigureAwait(false).GetAwaiter().GetResult(); #endif - cancellationToken.ThrowIfCancellationRequested(); - ExceptionHelper.ThrowIfGracefulSocketClose(readBytes); + if (cancellationToken.IsCancellationRequested) + { + return null; + } + if (readBytes == 0) + { + return null; + } + bodyOffset += readBytes; } while (bodyOffset < body.Length); diff --git a/Source/MQTTnet/Client/IMqttClient.cs b/Source/MQTTnet/Client/IMqttClient.cs index 4e9cbc1b..3c9ded36 100644 --- a/Source/MQTTnet/Client/IMqttClient.cs +++ b/Source/MQTTnet/Client/IMqttClient.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Threading.Tasks; using MQTTnet.Client.Connecting; using MQTTnet.Client.Disconnecting; @@ -20,7 +19,7 @@ namespace MQTTnet.Client Task ConnectAsync(IMqttClientOptions options); Task DisconnectAsync(MqttClientDisconnectOptions options); - Task SubscribeAsync(IEnumerable topicFilters); - Task UnsubscribeAsync(IEnumerable topics); + Task SubscribeAsync(MqttClientSubscribeOptions options); + Task UnsubscribeAsync(MqttClientUnsubscribeOptions options); } } \ No newline at end of file diff --git a/Source/MQTTnet/Client/MqttClient.cs b/Source/MQTTnet/Client/MqttClient.cs index be29f5d9..90c5fa00 100644 --- a/Source/MQTTnet/Client/MqttClient.cs +++ b/Source/MQTTnet/Client/MqttClient.cs @@ -1,20 +1,19 @@ using System; -using System.Collections.Generic; using System.Diagnostics; -using System.Linq; using System.Threading; using System.Threading.Tasks; using MQTTnet.Adapter; using MQTTnet.Client.Connecting; using MQTTnet.Client.Disconnecting; using MQTTnet.Client.Options; -using MQTTnet.Client.PacketDispatcher; using MQTTnet.Client.Publishing; +using MQTTnet.Client.Receiving; using MQTTnet.Client.Subscribing; using MQTTnet.Client.Unsubscribing; using MQTTnet.Diagnostics; using MQTTnet.Exceptions; using MQTTnet.Formatter; +using MQTTnet.PacketDispatcher; using MQTTnet.Packets; using MQTTnet.Protocol; @@ -23,16 +22,18 @@ namespace MQTTnet.Client public class MqttClient : IMqttClient { private readonly MqttPacketIdentifierProvider _packetIdentifierProvider = new MqttPacketIdentifierProvider(); + private readonly MqttPacketDispatcher _packetDispatcher = new MqttPacketDispatcher(); private readonly Stopwatch _sendTracker = new Stopwatch(); private readonly object _disconnectLock = new object(); - private readonly MqttPacketDispatcher _packetDispatcher = new MqttPacketDispatcher(); private readonly IMqttClientAdapterFactory _adapterFactory; private readonly IMqttNetChildLogger _logger; private CancellationTokenSource _cancellationTokenSource; - internal Task _packetReceiverTask; - internal Task _keepAliveMessageSenderTask; + private Task _packetReceiverTask; + private Task _keepAlivePacketsSenderTask; + private Task _backgroundWorkerTask; + private IMqttChannelAdapter _adapter; private bool _cleanDisconnectInitiated; private long _disconnectGate; @@ -47,6 +48,8 @@ namespace MQTTnet.Client public event EventHandler Connected; public event EventHandler Disconnected; + + public IMqttApplicationMessageHandler ReceivedApplicationMessageHandler { get; set; } public event EventHandler ApplicationMessageReceived; public bool IsConnected { get; private set; } @@ -66,28 +69,31 @@ namespace MQTTnet.Client _packetIdentifierProvider.Reset(); _packetDispatcher.Reset(); - var cancellationTokenSource = new CancellationTokenSource(); - _cancellationTokenSource = cancellationTokenSource; + _cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = _cancellationTokenSource.Token; _disconnectGate = 0; - _adapter = _adapterFactory.CreateClientAdapter(options, _logger); + var adapter = _adapterFactory.CreateClientAdapter(options, _logger); + _adapter = adapter; _logger.Verbose($"Trying to connect with server ({Options.ChannelOptions})."); - await _adapter.ConnectAsync(Options.CommunicationTimeout, cancellationTokenSource.Token).ConfigureAwait(false); + await _adapter.ConnectAsync(Options.CommunicationTimeout, cancellationToken).ConfigureAwait(false); _logger.Verbose("Connection with server established."); - StartReceivingPackets(cancellationTokenSource.Token); + _packetReceiverTask = Task.Run(() => TryReceivePacketsAsync(cancellationToken), cancellationToken); - var connectResult = await AuthenticateAsync(options.WillMessage, cancellationTokenSource.Token).ConfigureAwait(false); + var connectResult = await AuthenticateAsync(adapter, options.WillMessage, cancellationToken).ConfigureAwait(false); _logger.Verbose("MQTT connection with server established."); _sendTracker.Restart(); if (Options.KeepAlivePeriod != TimeSpan.Zero) { - StartSendingKeepAliveMessages(cancellationTokenSource.Token); + _keepAlivePacketsSenderTask = Task.Run(() => TrySendKeepAliveMessagesAsync(cancellationToken), cancellationToken); } + _backgroundWorkerTask = Task.Run(() => TryProcessReceivedPacketsAsync(cancellationToken), cancellationToken); + IsConnected = true; Connected?.Invoke(this, new MqttClientConnectedEventArgs(connectResult)); @@ -129,35 +135,27 @@ namespace MQTTnet.Client } } - public async Task SubscribeAsync(IEnumerable topicFilters) + public async Task SubscribeAsync(MqttClientSubscribeOptions options) { - if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters)); + if (options == null) throw new ArgumentNullException(nameof(options)); ThrowIfNotConnected(); - var subscribePacket = new MqttSubscribePacket - { - PacketIdentifier = _packetIdentifierProvider.GetNewPacketIdentifier() - }; - - subscribePacket.TopicFilters.AddRange(topicFilters); + var subscribePacket = _adapter.PacketFormatterAdapter.DataConverter.CreateSubscribePacket(options); + subscribePacket.PacketIdentifier = _packetIdentifierProvider.GetNextPacketIdentifier(); var subAckPacket = await SendAndReceiveAsync(subscribePacket, _cancellationTokenSource.Token).ConfigureAwait(false); return _adapter.PacketFormatterAdapter.DataConverter.CreateClientSubscribeResult(subscribePacket, subAckPacket); } - public async Task UnsubscribeAsync(IEnumerable topicFilters) + public async Task UnsubscribeAsync(MqttClientUnsubscribeOptions options) { - if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters)); + if (options == null) throw new ArgumentNullException(nameof(options)); ThrowIfNotConnected(); - var unsubscribePacket = new MqttUnsubscribePacket - { - PacketIdentifier = _packetIdentifierProvider.GetNewPacketIdentifier() - }; - - unsubscribePacket.TopicFilters.AddRange(topicFilters); + var unsubscribePacket = _adapter.PacketFormatterAdapter.DataConverter.CreateUnsubscribePacket(options); + unsubscribePacket.PacketIdentifier = _packetIdentifierProvider.GetNextPacketIdentifier(); var unsubAckPacket = await SendAndReceiveAsync(unsubscribePacket, _cancellationTokenSource.Token).ConfigureAwait(false); return _adapter.PacketFormatterAdapter.DataConverter.CreateClientUnsubscribeResult(unsubscribePacket, unsubAckPacket); @@ -181,7 +179,7 @@ namespace MQTTnet.Client } case MqttQualityOfServiceLevel.AtLeastOnce: { - publishPacket.PacketIdentifier = _packetIdentifierProvider.GetNewPacketIdentifier(); + publishPacket.PacketIdentifier = _packetIdentifierProvider.GetNextPacketIdentifier(); var response = await SendAndReceiveAsync(publishPacket, _cancellationTokenSource.Token).ConfigureAwait(false); var result = new MqttClientPublishResult(); @@ -213,15 +211,14 @@ namespace MQTTnet.Client _adapter = null; } - private async Task AuthenticateAsync(MqttApplicationMessage willApplicationMessage, CancellationToken cancellationToken) + private async Task AuthenticateAsync(IMqttChannelAdapter channelAdapter, MqttApplicationMessage willApplicationMessage, CancellationToken cancellationToken) { - var connectPacket = _adapter.PacketFormatterAdapter.DataConverter.CreateConnectPacket( + var connectPacket = channelAdapter.PacketFormatterAdapter.DataConverter.CreateConnectPacket( willApplicationMessage, Options); var connAckPacket = await SendAndReceiveAsync(connectPacket, cancellationToken).ConfigureAwait(false); - - var result = _adapter.PacketFormatterAdapter.DataConverter.CreateClientConnectResult(connAckPacket); + var result = channelAdapter.PacketFormatterAdapter.DataConverter.CreateClientConnectResult(connAckPacket); if (result.ResultCode != MqttClientConnectResultCode.Success) { @@ -257,7 +254,8 @@ namespace MQTTnet.Client } await WaitForTaskAsync(_packetReceiverTask, sender).ConfigureAwait(false); - await WaitForTaskAsync(_keepAliveMessageSenderTask, sender).ConfigureAwait(false); + await WaitForTaskAsync(_keepAlivePacketsSenderTask, sender).ConfigureAwait(false); + await WaitForTaskAsync(_backgroundWorkerTask, sender).ConfigureAwait(false); _logger.Verbose("Disconnected from adapter."); } @@ -316,26 +314,22 @@ namespace MQTTnet.Client identifier = packetWithIdentifier.PacketIdentifier.Value; } - var packetAwaiter = _packetDispatcher.AddPacketAwaiter(identifier); - try + using (var packetAwaiter = _packetDispatcher.AddPacketAwaiter(identifier)) { - await _adapter.SendPacketAsync(requestPacket, cancellationToken).ConfigureAwait(false); - return await packetAwaiter.WaitOneAsync(Options.CommunicationTimeout); - - //return (TResponsePacket)await Internal.TaskExtensions.TimeoutAfterAsync(ct => packetAwaiter.Task, Options.CommunicationTimeout, cancellationToken).ConfigureAwait(false); - } - catch (MqttCommunicationTimedOutException) - { - _logger.Warning(null, "Timeout while waiting for packet of type '{0}'.", typeof(TResponsePacket).Namespace); - throw; - } - finally - { - _packetDispatcher.RemovePacketAwaiter(identifier); + try + { + await _adapter.SendPacketAsync(requestPacket, cancellationToken).ConfigureAwait(false); + return await packetAwaiter.WaitOneAsync(Options.CommunicationTimeout).ConfigureAwait(false); + } + catch (MqttCommunicationTimedOutException) + { + _logger.Warning(null, "Timeout while waiting for packet of type '{0}'.", typeof(TResponsePacket).Namespace); + throw; + } } } - private async Task SendKeepAliveMessagesAsync(CancellationToken cancellationToken) + private async Task TrySendKeepAliveMessagesAsync(CancellationToken cancellationToken) { try { @@ -380,7 +374,7 @@ namespace MQTTnet.Client if (!DisconnectIsPending()) { - await DisconnectInternalAsync(_keepAliveMessageSenderTask, exception).ConfigureAwait(false); + await DisconnectInternalAsync(_keepAlivePacketsSenderTask, exception).ConfigureAwait(false); } } finally @@ -389,7 +383,7 @@ namespace MQTTnet.Client } } - private async Task ReceivePacketsAsync(CancellationToken cancellationToken) + private async Task TryReceivePacketsAsync(CancellationToken cancellationToken) { try { @@ -399,10 +393,17 @@ namespace MQTTnet.Client { var packet = await _adapter.ReceivePacketAsync(TimeSpan.Zero, cancellationToken).ConfigureAwait(false); - if (packet != null && !cancellationToken.IsCancellationRequested) + if (cancellationToken.IsCancellationRequested) { - await ProcessReceivedPacketAsync(packet, cancellationToken).ConfigureAwait(false); + return; } + + if (packet == null && !DisconnectIsPending()) + { + await DisconnectInternalAsync(_packetReceiverTask, null).ConfigureAwait(false); + } + + _packetDispatcher.Dispatch(packet); } } catch (Exception exception) @@ -437,79 +438,146 @@ namespace MQTTnet.Client } } - private Task ProcessReceivedPacketAsync(MqttBasePacket packet, CancellationToken cancellationToken) + private async Task TryProcessReceivedPacketAsync(MqttBasePacket packet, CancellationToken cancellationToken) { - if (packet is MqttPublishPacket publishPacket) + try { - return ProcessReceivedPublishPacketAsync(publishPacket, cancellationToken); + if (packet is MqttPublishPacket publishPacket) + { + await TryProcessReceivedPublishPacketAsync(publishPacket, cancellationToken).ConfigureAwait(false); + } + else if (packet is MqttPingReqPacket) + { + await SendAsync(new MqttPingRespPacket(), cancellationToken).ConfigureAwait(false); + } + else if (packet is MqttDisconnectPacket) + { + await DisconnectAsync(null).ConfigureAwait(false); + } + else + { + throw new MqttProtocolViolationException($"Received packet '{packet}' at an unexpected time."); + } } - - if (packet is MqttPingReqPacket) + catch (Exception exception) { - return SendAsync(new MqttPingRespPacket(), cancellationToken); - } + if (_cleanDisconnectInitiated) + { + return; + } - if (packet is MqttDisconnectPacket) - { - return DisconnectAsync(null); - } + if (exception is OperationCanceledException) + { + } + else if (exception is MqttCommunicationException) + { + _logger.Warning(exception, "MQTT communication exception while receiving packets."); + } + else + { + _logger.Error(exception, "Unhandled exception while receiving packets."); + } - if (packet is MqttPubRelPacket pubRelPacket) - { - return ProcessReceivedPubRelPacket(pubRelPacket, cancellationToken); - } + _packetDispatcher.Dispatch(exception); - _packetDispatcher.Dispatch(packet); - return Task.FromResult(0); + if (!DisconnectIsPending()) + { + await DisconnectInternalAsync(_packetReceiverTask, exception).ConfigureAwait(false); + } + } } - private Task ProcessReceivedPublishPacketAsync(MqttPublishPacket publishPacket, CancellationToken cancellationToken) + private async Task TryProcessReceivedPublishPacketAsync(MqttPublishPacket publishPacket, CancellationToken cancellationToken) { - if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtMostOnce) + try { - FireApplicationMessageReceivedEvent(publishPacket); - return Task.FromResult(0); - } + if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtMostOnce) + { + await HandleReceivedApplicationMessageAsync(publishPacket).ConfigureAwait(false); + } + else if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtLeastOnce) + { + await HandleReceivedApplicationMessageAsync(publishPacket).ConfigureAwait(false); - if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtLeastOnce) + await SendAsync(new MqttPubAckPacket + { + PacketIdentifier = publishPacket.PacketIdentifier, + ReasonCode = MqttPubAckReasonCode.Success + }, cancellationToken).ConfigureAwait(false); + } + else if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.ExactlyOnce) + { + var pubRecPacket = new MqttPubRecPacket + { + PacketIdentifier = publishPacket.PacketIdentifier, + ReasonCode = MqttPubRecReasonCode.Success + }; + + var pubRelPacket = await SendAndReceiveAsync(pubRecPacket, cancellationToken).ConfigureAwait(false); + + // QoS 2 is implement as method "B" (4.3.3 QoS 2: Exactly once delivery) + await HandleReceivedApplicationMessageAsync(publishPacket).ConfigureAwait(false); + + await SendAsync(new MqttPubCompPacket + { + PacketIdentifier = pubRelPacket.PacketIdentifier, + ReasonCode = MqttPubCompReasonCode.Success + }, cancellationToken).ConfigureAwait(false); + } + else + { + throw new MqttProtocolViolationException("Received a not supported QoS level."); + } + } + catch (Exception exception) { - FireApplicationMessageReceivedEvent(publishPacket); - return SendAsync(new MqttPubAckPacket { PacketIdentifier = publishPacket.PacketIdentifier, ReasonCode = MqttPubAckReasonCode.Success }, cancellationToken); + _logger.Error(exception, "Unhandled exception while handling application message."); } - - if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.ExactlyOnce) - { - // QoS 2 is implement as method "B" (4.3.3 QoS 2: Exactly once delivery) - FireApplicationMessageReceivedEvent(publishPacket); - return SendAsync(new MqttPubRecPacket { PacketIdentifier = publishPacket.PacketIdentifier, ReasonCode = MqttPubRecReasonCode.Success }, cancellationToken); - } - - throw new MqttCommunicationException("Received a not supported QoS level."); } - private Task ProcessReceivedPubRelPacket(MqttPubRelPacket pubRelPacket, CancellationToken cancellationToken) + private async Task TryProcessReceivedPacketsAsync(CancellationToken cancellationToken) { - var response = new MqttPubCompPacket + try { - PacketIdentifier = pubRelPacket.PacketIdentifier, - ReasonCode = MqttPubCompReasonCode.Success - }; + while (!cancellationToken.IsCancellationRequested) + { + var packet = _packetDispatcher.Take(cancellationToken); - return SendAsync(response, cancellationToken); + if (cancellationToken.IsCancellationRequested) + { + return; + } + + await TryProcessReceivedPacketAsync(packet, cancellationToken).ConfigureAwait(false); + } + } + catch (OperationCanceledException) + { + } + catch (Exception exception) + { + _logger.Error(exception, "Error while processing packet."); + } } private async Task PublishExactlyOnceAsync(MqttPublishPacket publishPacket, CancellationToken cancellationToken) { - publishPacket.PacketIdentifier = _packetIdentifierProvider.GetNewPacketIdentifier(); + publishPacket.PacketIdentifier = _packetIdentifierProvider.GetNextPacketIdentifier(); var pubRecPacket = await SendAndReceiveAsync(publishPacket, cancellationToken).ConfigureAwait(false); + + // TODO: Check response code. + var pubRelPacket = new MqttPubRelPacket { - PacketIdentifier = pubRecPacket.PacketIdentifier, + PacketIdentifier = publishPacket.PacketIdentifier, ReasonCode = MqttPubRelReasonCode.Success }; var pubCompPacket = await SendAndReceiveAsync(pubRelPacket, cancellationToken).ConfigureAwait(false); + + // TODO: Check response code. + var result = new MqttClientPublishResult(); if (pubRecPacket.ReasonCode != null) @@ -520,35 +588,38 @@ namespace MQTTnet.Client return result; } - private void StartReceivingPackets(CancellationToken cancellationToken) - { - _packetReceiverTask = Task.Factory.StartNew( - () => ReceivePacketsAsync(cancellationToken), - cancellationToken, - TaskCreationOptions.LongRunning, - TaskScheduler.Default).Unwrap(); - } + ////private void StartReceivingPackets(CancellationToken cancellationToken) + ////{ + //// _packetReceiverTask = Task.Factory.StartNew( + //// () => TryReceivePacketsAsync(cancellationToken), + //// cancellationToken, + //// TaskCreationOptions.LongRunning, + //// TaskScheduler.Default).Unwrap(); + ////} - private void StartSendingKeepAliveMessages(CancellationToken cancellationToken) - { - _keepAliveMessageSenderTask = Task.Factory.StartNew( - () => SendKeepAliveMessagesAsync(cancellationToken), - cancellationToken, - TaskCreationOptions.LongRunning, - TaskScheduler.Default).Unwrap(); - } + ////private void StartSendingKeepAliveMessages(CancellationToken cancellationToken) + ////{ + //// _keepAlivePacketsSenderTask = Task.Factory.StartNew( + //// () => TrySendKeepAliveMessagesAsync(cancellationToken), + //// cancellationToken, + //// TaskCreationOptions.LongRunning, + //// TaskScheduler.Default).Unwrap(); + ////} - private void FireApplicationMessageReceivedEvent(MqttPublishPacket publishPacket) + private Task HandleReceivedApplicationMessageAsync(MqttPublishPacket publishPacket) { - try + var applicationMessage = _adapter.PacketFormatterAdapter.DataConverter.CreateApplicationMessage(publishPacket); + + ApplicationMessageReceived?.Invoke(this, new MqttApplicationMessageReceivedEventArgs(Options.ClientId, applicationMessage)); + + var handler = ReceivedApplicationMessageHandler; + if (handler != null) { - var applicationMessage = _adapter.PacketFormatterAdapter.DataConverter.CreateApplicationMessage(publishPacket); - ApplicationMessageReceived?.Invoke(this, new MqttApplicationMessageReceivedEventArgs(Options.ClientId, applicationMessage)); - } - catch (Exception exception) - { - _logger.Error(exception, "Unhandled exception while handling application message."); + return handler.HandleApplicationMessageAsync( + new MqttApplicationMessageHandlerContext(Options.ClientId, applicationMessage)); } + + return Task.FromResult(0); } private static async Task WaitForTaskAsync(Task task, Task sender) diff --git a/Source/MQTTnet/Client/MqttClientExtensions.cs b/Source/MQTTnet/Client/MqttClientExtensions.cs index b399ef67..ef6f5ab4 100644 --- a/Source/MQTTnet/Client/MqttClientExtensions.cs +++ b/Source/MQTTnet/Client/MqttClientExtensions.cs @@ -1,5 +1,6 @@ using System; using System.Threading.Tasks; +using MQTTnet.Client.Receiving; using MQTTnet.Client.Subscribing; using MQTTnet.Client.Unsubscribing; using MQTTnet.Protocol; @@ -8,6 +9,51 @@ namespace MQTTnet.Client { public static class MqttClientExtensions { + public static IMqttClient UseReceivedApplicationMessageHandler(this IMqttClient client, Func handler) + { + if (handler == null) + { + client.ReceivedApplicationMessageHandler = null; + return client; + } + + client.ReceivedApplicationMessageHandler = new MqttApplicationMessageHandlerDelegate(handler); + + return client; + } + + public static IMqttClient UseReceivedApplicationMessageHandler(this IMqttClient client, Action handler) + { + if (handler == null) + { + client.ReceivedApplicationMessageHandler = null; + return client; + } + + client.ReceivedApplicationMessageHandler = new MqttApplicationMessageHandlerDelegate(handler); + + return client; + } + + public static IMqttClient UseReceivedApplicationMessageHandler(this IMqttClient client, IMqttApplicationMessageHandler handler) + { + client.ReceivedApplicationMessageHandler = handler; + + return client; + } + + public static Task ReconnectAsync(this IMqttClient client) + { + if (client == null) throw new ArgumentNullException(nameof(client)); + + if (client.Options == null) + { + throw new InvalidOperationException("_ReconnectAsync_ can be used only if _ConnectAsync_ was called before."); + } + + return client.ConnectAsync(client.Options); + } + public static Task DisconnectAsync(this IMqttClient client) { if (client == null) throw new ArgumentNullException(nameof(client)); @@ -20,7 +66,10 @@ namespace MQTTnet.Client if (client == null) throw new ArgumentNullException(nameof(client)); if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters)); - return client.SubscribeAsync(topicFilters); + var options = new MqttClientSubscribeOptions(); + options.TopicFilters.AddRange(topicFilters); + + return client.SubscribeAsync(options); } public static Task SubscribeAsync(this IMqttClient client, string topic, MqttQualityOfServiceLevel qualityOfServiceLevel) @@ -44,7 +93,10 @@ namespace MQTTnet.Client if (client == null) throw new ArgumentNullException(nameof(client)); if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters)); - return client.UnsubscribeAsync(topicFilters); + var options = new MqttClientUnsubscribeOptions(); + options.TopicFilters.AddRange(topicFilters); + + return client.UnsubscribeAsync(options); } } } \ No newline at end of file diff --git a/Source/MQTTnet/Client/MqttPacketIdentifierProvider.cs b/Source/MQTTnet/Client/MqttPacketIdentifierProvider.cs index dc099183..bfee3861 100644 --- a/Source/MQTTnet/Client/MqttPacketIdentifierProvider.cs +++ b/Source/MQTTnet/Client/MqttPacketIdentifierProvider.cs @@ -13,7 +13,7 @@ } } - public ushort GetNewPacketIdentifier() + public ushort GetNextPacketIdentifier() { lock (_syncRoot) { diff --git a/Source/MQTTnet/Client/Options/MqttClientOptions.cs b/Source/MQTTnet/Client/Options/MqttClientOptions.cs index a76ecf5b..be9dc7b0 100644 --- a/Source/MQTTnet/Client/Options/MqttClientOptions.cs +++ b/Source/MQTTnet/Client/Options/MqttClientOptions.cs @@ -7,7 +7,7 @@ namespace MQTTnet.Client.Options { public string ClientId { get; set; } = Guid.NewGuid().ToString("N"); public bool CleanSession { get; set; } = true; - public IMqttClientCredentials Credentials { get; set; } = new MqttClientCredentials(); + public IMqttClientCredentials Credentials { get; set; } public MqttProtocolVersion ProtocolVersion { get; set; } = MqttProtocolVersion.V311; public IMqttClientChannelOptions ChannelOptions { get; set; } diff --git a/Source/MQTTnet/Client/PacketDispatcher/MqttPacketAwaiter.cs b/Source/MQTTnet/Client/PacketDispatcher/MqttPacketAwaiter.cs deleted file mode 100644 index 292f2319..00000000 --- a/Source/MQTTnet/Client/PacketDispatcher/MqttPacketAwaiter.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; -using MQTTnet.Packets; - -namespace MQTTnet.Client.PacketDispatcher -{ - public sealed class MqttPacketAwaiter : IMqttPacketAwaiter where TPacket : MqttBasePacket - { - private readonly TaskCompletionSource _packet = new TaskCompletionSource(); - - public async Task WaitOneAsync(TimeSpan timeout) - { - using (var timeoutToken = new CancellationTokenSource(timeout)) - { - timeoutToken.Token.Register(() => _packet.TrySetCanceled()); - - var packet = await _packet.Task.ConfigureAwait(false); - return (TPacket)packet; - } - } - - public void Complete(MqttBasePacket packet) - { - if (packet == null) throw new ArgumentNullException(nameof(packet)); - - _packet.TrySetResult(packet); - } - - public void Fail(Exception exception) - { - if (exception == null) throw new ArgumentNullException(nameof(exception)); - - _packet.TrySetException(exception); - } - } -} \ No newline at end of file diff --git a/Source/MQTTnet/Client/Receiving/IMqttApplicationMessageHandler.cs b/Source/MQTTnet/Client/Receiving/IMqttApplicationMessageHandler.cs new file mode 100644 index 00000000..fdc5bf6a --- /dev/null +++ b/Source/MQTTnet/Client/Receiving/IMqttApplicationMessageHandler.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; + +namespace MQTTnet.Client.Receiving +{ + public interface IMqttApplicationMessageHandler + { + Task HandleApplicationMessageAsync(MqttApplicationMessageHandlerContext context); + } +} diff --git a/Source/MQTTnet/Client/Receiving/MqttApplicationMessageHandlerContext.cs b/Source/MQTTnet/Client/Receiving/MqttApplicationMessageHandlerContext.cs new file mode 100644 index 00000000..87fb959f --- /dev/null +++ b/Source/MQTTnet/Client/Receiving/MqttApplicationMessageHandlerContext.cs @@ -0,0 +1,15 @@ +namespace MQTTnet.Client.Receiving +{ + public class MqttApplicationMessageHandlerContext + { + public MqttApplicationMessageHandlerContext(string senderClientId, MqttApplicationMessage applicationMessage) + { + SenderClientId = senderClientId; + ApplicationMessage = applicationMessage; + } + + public string SenderClientId { get; } + + public MqttApplicationMessage ApplicationMessage { get; } + } +} diff --git a/Source/MQTTnet/Client/Receiving/MqttApplicationMessageHandlerDelegate.cs b/Source/MQTTnet/Client/Receiving/MqttApplicationMessageHandlerDelegate.cs new file mode 100644 index 00000000..cde89443 --- /dev/null +++ b/Source/MQTTnet/Client/Receiving/MqttApplicationMessageHandlerDelegate.cs @@ -0,0 +1,31 @@ +using System; +using System.Threading.Tasks; + +namespace MQTTnet.Client.Receiving +{ + public class MqttApplicationMessageHandlerDelegate : IMqttApplicationMessageHandler + { + private readonly Func _handler; + + public MqttApplicationMessageHandlerDelegate(Action handler) + { + if (handler == null) throw new ArgumentNullException(nameof(handler)); + + _handler = context => + { + handler(context); + return Task.FromResult(0); + }; + } + + public MqttApplicationMessageHandlerDelegate(Func handler) + { + _handler = handler ?? throw new ArgumentNullException(nameof(handler)); + } + + public Task HandleApplicationMessageAsync(MqttApplicationMessageHandlerContext context) + { + return _handler(context); + } + } +} diff --git a/Source/MQTTnet/Client/Subscribing/MqttClientSubscribeOptions.cs b/Source/MQTTnet/Client/Subscribing/MqttClientSubscribeOptions.cs new file mode 100644 index 00000000..8d2adbf8 --- /dev/null +++ b/Source/MQTTnet/Client/Subscribing/MqttClientSubscribeOptions.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; +using MQTTnet.Packets; + +namespace MQTTnet.Client.Subscribing +{ + public class MqttClientSubscribeOptions + { + public List TopicFilters { get; set; } = new List(); + + public List UserProperties { get; set; } = new List(); + } +} diff --git a/Source/MQTTnet/Client/Unsubscribing/MqttClientUnsubscribeOptions.cs b/Source/MQTTnet/Client/Unsubscribing/MqttClientUnsubscribeOptions.cs new file mode 100644 index 00000000..73c21ef7 --- /dev/null +++ b/Source/MQTTnet/Client/Unsubscribing/MqttClientUnsubscribeOptions.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; +using MQTTnet.Packets; + +namespace MQTTnet.Client.Unsubscribing +{ + public class MqttClientUnsubscribeOptions + { + public List TopicFilters { get; set; } = new List(); + + public List UserProperties { get; set; } = new List(); + } +} diff --git a/Source/MQTTnet/Exceptions/MqttCommunicationClosedGracefullyException.cs b/Source/MQTTnet/Exceptions/MqttCommunicationClosedGracefullyException.cs deleted file mode 100644 index 21a66b90..00000000 --- a/Source/MQTTnet/Exceptions/MqttCommunicationClosedGracefullyException.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace MQTTnet.Exceptions -{ - public class MqttCommunicationClosedGracefullyException : MqttCommunicationException - { - } -} diff --git a/Source/MQTTnet/Formatter/IMqttDataConverter.cs b/Source/MQTTnet/Formatter/IMqttDataConverter.cs index 44516ee8..19c2a282 100644 --- a/Source/MQTTnet/Formatter/IMqttDataConverter.cs +++ b/Source/MQTTnet/Formatter/IMqttDataConverter.cs @@ -19,5 +19,9 @@ namespace MQTTnet.Formatter MqttClientSubscribeResult CreateClientSubscribeResult(MqttSubscribePacket subscribePacket, MqttSubAckPacket subAckPacket); MqttClientUnsubscribeResult CreateClientUnsubscribeResult(MqttUnsubscribePacket unsubscribePacket, MqttUnsubAckPacket unsubAckPacket); + + MqttSubscribePacket CreateSubscribePacket(MqttClientSubscribeOptions options); + + MqttUnsubscribePacket CreateUnsubscribePacket(MqttClientUnsubscribeOptions options); } } diff --git a/Source/MQTTnet/Formatter/MqttPacketReader.cs b/Source/MQTTnet/Formatter/MqttPacketReader.cs index eec643a1..b4e7b676 100644 --- a/Source/MQTTnet/Formatter/MqttPacketReader.cs +++ b/Source/MQTTnet/Formatter/MqttPacketReader.cs @@ -18,7 +18,7 @@ namespace MQTTnet.Formatter _channel = channel ?? throw new ArgumentNullException(nameof(channel)); } - public async Task ReadFixedHeaderAsync(byte[] fixedHeaderBuffer, CancellationToken cancellationToken) + public async Task ReadFixedHeaderAsync(byte[] fixedHeaderBuffer, CancellationToken cancellationToken) { // The MQTT fixed header contains 1 byte of flags and at least 1 byte for the remaining data length. // So in all cases at least 2 bytes must be read for a complete MQTT packet. @@ -32,15 +32,25 @@ namespace MQTTnet.Formatter var bytesRead = await _channel.ReadAsync(buffer, totalBytesRead, buffer.Length - totalBytesRead, cancellationToken).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested(); - ExceptionHelper.ThrowIfGracefulSocketClose(bytesRead); + if (bytesRead == 0) + { + return new ReadFixedHeaderResult + { + ConnectionClosed = true + }; + } + totalBytesRead += bytesRead; } var hasRemainingLength = buffer[1] != 0; if (!hasRemainingLength) { - return new MqttFixedHeader(buffer[0], 0, totalBytesRead); + return new ReadFixedHeaderResult + { + FixedHeader = new MqttFixedHeader(buffer[0], 0, totalBytesRead) + }; } #if WINDOWS_UWP @@ -54,12 +64,23 @@ namespace MQTTnet.Formatter var bodyLength = ReadBodyLength(buffer[1], cancellationToken); #endif - totalBytesRead += bodyLength; - return new MqttFixedHeader(buffer[0], bodyLength, totalBytesRead); + if (!bodyLength.HasValue) + { + return new ReadFixedHeaderResult + { + ConnectionClosed = true + }; + } + + totalBytesRead += bodyLength.Value; + return new ReadFixedHeaderResult + { + FixedHeader = new MqttFixedHeader(buffer[0], bodyLength.Value, totalBytesRead) + }; } #if !WINDOWS_UWP - private int ReadBodyLength(byte initialEncodedByte, CancellationToken cancellationToken) + private int? ReadBodyLength(byte initialEncodedByte, CancellationToken cancellationToken) { var offset = 0; var multiplier = 128; @@ -74,9 +95,18 @@ namespace MQTTnet.Formatter throw new MqttProtocolViolationException("Remaining length is invalid."); } - cancellationToken.ThrowIfCancellationRequested(); + if (cancellationToken.IsCancellationRequested) + { + return null; + } - encodedByte = ReadByte(cancellationToken); + var buffer = ReadByte(cancellationToken); + if (!buffer.HasValue) + { + return null; + } + + encodedByte = buffer.Value; value += (encodedByte & 127) * multiplier; multiplier *= 128; @@ -85,15 +115,18 @@ namespace MQTTnet.Formatter return value; } - private byte ReadByte(CancellationToken cancellationToken) + private byte? ReadByte(CancellationToken cancellationToken) { var readCount = _channel.ReadAsync(_singleByteBuffer, 0, 1, cancellationToken).ConfigureAwait(false).GetAwaiter().GetResult(); - cancellationToken.ThrowIfCancellationRequested(); - - if (readCount <= 0) + if (cancellationToken.IsCancellationRequested) { - ExceptionHelper.ThrowGracefulSocketClose(); + return null; + } + + if (readCount == 0) + { + return null; } return _singleByteBuffer[0]; @@ -101,7 +134,7 @@ namespace MQTTnet.Formatter #else - private async Task ReadBodyLengthAsync(byte initialEncodedByte, CancellationToken cancellationToken) + private async Task ReadBodyLengthAsync(byte initialEncodedByte, CancellationToken cancellationToken) { var offset = 0; var multiplier = 128; @@ -116,9 +149,18 @@ namespace MQTTnet.Formatter throw new MqttProtocolViolationException("Remaining length is invalid."); } - cancellationToken.ThrowIfCancellationRequested(); + if (cancellationToken.IsCancellationRequested) + { + return null; + } - encodedByte = await ReadByteAsync(cancellationToken).ConfigureAwait(false); + var buffer = await ReadByteAsync(cancellationToken).ConfigureAwait(false); + if (!buffer.HasValue) + { + return null; + } + + encodedByte = buffer.Value; value += (encodedByte & 127) * multiplier; multiplier *= 128; @@ -127,15 +169,18 @@ namespace MQTTnet.Formatter return value; } - private async Task ReadByteAsync(CancellationToken cancellationToken) + private async Task ReadByteAsync(CancellationToken cancellationToken) { var readCount = await _channel.ReadAsync(_singleByteBuffer, 0, 1, cancellationToken).ConfigureAwait(false); - cancellationToken.ThrowIfCancellationRequested(); - - if (readCount <= 0) + if (cancellationToken.IsCancellationRequested) { - ExceptionHelper.ThrowGracefulSocketClose(); + return null; + } + + if (readCount == 0) + { + return null; } return _singleByteBuffer[0]; diff --git a/Source/MQTTnet/Formatter/ReadFixedHeaderResult.cs b/Source/MQTTnet/Formatter/ReadFixedHeaderResult.cs new file mode 100644 index 00000000..30ad73ec --- /dev/null +++ b/Source/MQTTnet/Formatter/ReadFixedHeaderResult.cs @@ -0,0 +1,9 @@ +namespace MQTTnet.Formatter +{ + public class ReadFixedHeaderResult + { + public bool ConnectionClosed { get; set; } + + public MqttFixedHeader FixedHeader { get; set; } + } +} diff --git a/Source/MQTTnet/Formatter/V3/MqttV310DataConverter.cs b/Source/MQTTnet/Formatter/V3/MqttV310DataConverter.cs index b6436758..1fad36b8 100644 --- a/Source/MQTTnet/Formatter/V3/MqttV310DataConverter.cs +++ b/Source/MQTTnet/Formatter/V3/MqttV310DataConverter.cs @@ -143,5 +143,35 @@ namespace MQTTnet.Formatter.V3 return result; } + + public MqttSubscribePacket CreateSubscribePacket(MqttClientSubscribeOptions options) + { + if (options == null) throw new ArgumentNullException(nameof(options)); + + if (options.UserProperties?.Any() == true) + { + throw new MqttProtocolViolationException("User properties are not supported in MQTT version 3."); + } + + var subscribePacket = new MqttSubscribePacket(); + subscribePacket.TopicFilters.AddRange(options.TopicFilters); + + return subscribePacket; + } + + public MqttUnsubscribePacket CreateUnsubscribePacket(MqttClientUnsubscribeOptions options) + { + if (options == null) throw new ArgumentNullException(nameof(options)); + + if (options.UserProperties?.Any() == true) + { + throw new MqttProtocolViolationException("User properties are not supported in MQTT version 3."); + } + + var unsubscribePacket = new MqttUnsubscribePacket(); + unsubscribePacket.TopicFilters.AddRange(options.TopicFilters); + + return unsubscribePacket; + } } } diff --git a/Source/MQTTnet/Formatter/V5/MqttV500DataConverter.cs b/Source/MQTTnet/Formatter/V5/MqttV500DataConverter.cs index 5a26a677..b5522e1e 100644 --- a/Source/MQTTnet/Formatter/V5/MqttV500DataConverter.cs +++ b/Source/MQTTnet/Formatter/V5/MqttV500DataConverter.cs @@ -128,5 +128,35 @@ namespace MQTTnet.Formatter.V5 return result; } + + public MqttSubscribePacket CreateSubscribePacket(MqttClientSubscribeOptions options) + { + if (options == null) throw new ArgumentNullException(nameof(options)); + + var packet = new MqttSubscribePacket + { + Properties = new MqttSubscribePacketProperties() + }; + + packet.TopicFilters.AddRange(options.TopicFilters); + packet.Properties.UserProperties.AddRange(options.UserProperties); + + return packet; + } + + public MqttUnsubscribePacket CreateUnsubscribePacket(MqttClientUnsubscribeOptions options) + { + if (options == null) throw new ArgumentNullException(nameof(options)); + + var packet = new MqttUnsubscribePacket + { + Properties = new MqttUnsubscribePacketProperties() + }; + + packet.TopicFilters.AddRange(options.TopicFilters); + packet.Properties.UserProperties.AddRange(options.UserProperties); + + return packet; + } } } diff --git a/Source/MQTTnet/IApplicationMessageReceiver.cs b/Source/MQTTnet/IApplicationMessageReceiver.cs index b315067c..806bb757 100644 --- a/Source/MQTTnet/IApplicationMessageReceiver.cs +++ b/Source/MQTTnet/IApplicationMessageReceiver.cs @@ -1,9 +1,13 @@ using System; +using MQTTnet.Client.Receiving; namespace MQTTnet { public interface IApplicationMessageReceiver { + IMqttApplicationMessageHandler ReceivedApplicationMessageHandler { get; set; } + + [Obsolete("Use _ReceivedApplicationMessageHandler_ instead.")] event EventHandler ApplicationMessageReceived; } } diff --git a/Source/MQTTnet/Internal/ExceptionHelper.cs b/Source/MQTTnet/Internal/ExceptionHelper.cs deleted file mode 100644 index 5bc8e435..00000000 --- a/Source/MQTTnet/Internal/ExceptionHelper.cs +++ /dev/null @@ -1,20 +0,0 @@ -using MQTTnet.Exceptions; - -namespace MQTTnet.Internal -{ - public static class ExceptionHelper - { - public static void ThrowGracefulSocketClose() - { - throw new MqttCommunicationClosedGracefullyException(); - } - - public static void ThrowIfGracefulSocketClose(int readBytesCount) - { - if (readBytesCount <= 0) - { - throw new MqttCommunicationClosedGracefullyException(); - } - } - } -} diff --git a/Source/MQTTnet/MQTTnet.csproj b/Source/MQTTnet/MQTTnet.csproj index 91e455f7..d1819258 100644 --- a/Source/MQTTnet/MQTTnet.csproj +++ b/Source/MQTTnet/MQTTnet.csproj @@ -63,20 +63,8 @@ - - - - - - - - - C:\Program Files\dotnet\sdk\NuGetFallbackFolder\microsoft.netcore.app\2.1.0\ref\netcoreapp2.1\System.Net.Requests.dll - - - \ No newline at end of file diff --git a/Source/MQTTnet/MessageStream/MqttMessageStream.cs b/Source/MQTTnet/MessageStream/MqttMessageStream.cs new file mode 100644 index 00000000..ef881473 --- /dev/null +++ b/Source/MQTTnet/MessageStream/MqttMessageStream.cs @@ -0,0 +1,79 @@ +using System; +using System.Collections.Generic; + +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; +using MQTTnet.Internal; +using MQTTnet.Packets; + +namespace MQTTnet.MessageStream +{ + public class MqttMessageStream + { + private readonly LinkedList _queue = new LinkedList(); + private readonly LinkedList> _waitHandles = new LinkedList>(); + + private readonly SemaphoreSlim _lock = new SemaphoreSlim(1, 1); + + private readonly BlockingQueue _packets = new BlockingQueue(); + + public void Enqueue(MqttBasePacket packet) + { + if (packet == null) throw new ArgumentNullException(nameof(packet)); + + + + lock (_queue) + { + //_queue.AddLast(packet); + + _packets.Enqueue(packet); + } + + //lock (_queue) + //{ + // _queue.AddLast(packet); + + // foreach (var waitHandle in _waitHandles) + // { + // waitHandle.TrySetResult(true); + // } + + // _waitHandles.Clear(); + //} + } + + public Task TakeAsync(CancellationToken cancellationToken) + { + lock (_packets) + { + var packet = _packets.Dequeue(); + return Task.FromResult(packet); + } + + + //while (!cancellationToken.IsCancellationRequested) + //{ + // TaskCompletionSource waitHandle; + // lock (_queue) + // { + // if (_queue.Count > 0) + // { + // var node = _queue.First; + // _queue.RemoveFirst(); + + // return node.Value; + // } + + // waitHandle = new TaskCompletionSource(); + // _waitHandles.Add(waitHandle); + // } + + // await waitHandle.Task; + //} + + //return null; + } + } +} diff --git a/Source/MQTTnet/Client/PacketDispatcher/IMqttPacketAwaiter.cs b/Source/MQTTnet/PacketDispatcher/IMqttPacketAwaiter.cs similarity index 55% rename from Source/MQTTnet/Client/PacketDispatcher/IMqttPacketAwaiter.cs rename to Source/MQTTnet/PacketDispatcher/IMqttPacketAwaiter.cs index 4887b1ef..786dc97d 100644 --- a/Source/MQTTnet/Client/PacketDispatcher/IMqttPacketAwaiter.cs +++ b/Source/MQTTnet/PacketDispatcher/IMqttPacketAwaiter.cs @@ -1,12 +1,14 @@ using System; using MQTTnet.Packets; -namespace MQTTnet.Client.PacketDispatcher +namespace MQTTnet.PacketDispatcher { - public interface IMqttPacketAwaiter + public interface IMqttPacketAwaiter : IDisposable { void Complete(MqttBasePacket packet); void Fail(Exception exception); + + void Cancel(); } } \ No newline at end of file diff --git a/Source/MQTTnet/PacketDispatcher/MqttPacketAwaiter.cs b/Source/MQTTnet/PacketDispatcher/MqttPacketAwaiter.cs new file mode 100644 index 00000000..c30a6f45 --- /dev/null +++ b/Source/MQTTnet/PacketDispatcher/MqttPacketAwaiter.cs @@ -0,0 +1,59 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using MQTTnet.Packets; + +namespace MQTTnet.PacketDispatcher +{ + public sealed class MqttPacketAwaiter : IMqttPacketAwaiter where TPacket : MqttBasePacket + { + private readonly TaskCompletionSource _taskCompletionSource = new TaskCompletionSource(); + private readonly ushort? _packetIdentifier; + private readonly MqttPacketDispatcher _owningPacketDispatcher; + + public MqttPacketAwaiter(ushort? packetIdentifier, MqttPacketDispatcher owningPacketDispatcher) + { + _packetIdentifier = packetIdentifier; + _owningPacketDispatcher = owningPacketDispatcher ?? throw new ArgumentNullException(nameof(owningPacketDispatcher)); + } + + public async Task WaitOneAsync(TimeSpan timeout) + { + using (var timeoutToken = new CancellationTokenSource(timeout)) + { + timeoutToken.Token.Register(() => _taskCompletionSource.TrySetCanceled()); + + var packet = await _taskCompletionSource.Task.ConfigureAwait(false); + return (TPacket)packet; + } + } + + public void Complete(MqttBasePacket packet) + { + if (packet == null) throw new ArgumentNullException(nameof(packet)); + + // To prevent deadlocks it is required to call the _TrySetResult_ method + // from a new thread because the awaiting code will not(!) be executed in + // a new thread automatically (due to await). Furthermore _this_ thread will + // do it. But _this_ thread is also reading incoming packets -> deadlock. + Task.Run(() => _taskCompletionSource.TrySetResult(packet)); + } + + public void Fail(Exception exception) + { + if (exception == null) throw new ArgumentNullException(nameof(exception)); + + Task.Run(() => _taskCompletionSource.TrySetException(exception)); + } + + public void Cancel() + { + Task.Run(() => _taskCompletionSource.TrySetCanceled()); + } + + public void Dispose() + { + _owningPacketDispatcher.RemovePacketAwaiter(_packetIdentifier); + } + } +} \ No newline at end of file diff --git a/Source/MQTTnet/Client/PacketDispatcher/MqttPacketDispatcher.cs b/Source/MQTTnet/PacketDispatcher/MqttPacketDispatcher.cs similarity index 53% rename from Source/MQTTnet/Client/PacketDispatcher/MqttPacketDispatcher.cs rename to Source/MQTTnet/PacketDispatcher/MqttPacketDispatcher.cs index 8c6a7693..b4a26c2f 100644 --- a/Source/MQTTnet/Client/PacketDispatcher/MqttPacketDispatcher.cs +++ b/Source/MQTTnet/PacketDispatcher/MqttPacketDispatcher.cs @@ -1,22 +1,24 @@ using System; using System.Collections.Concurrent; -using System.Threading.Tasks; +using System.Threading; using MQTTnet.Packets; -namespace MQTTnet.Client.PacketDispatcher +namespace MQTTnet.PacketDispatcher { public class MqttPacketDispatcher { - private readonly ConcurrentDictionary, IMqttPacketAwaiter> _awaiters = new ConcurrentDictionary, IMqttPacketAwaiter>(); + private readonly ConcurrentDictionary, IMqttPacketAwaiter> _packetAwaiters = new ConcurrentDictionary, IMqttPacketAwaiter>(); + + private BlockingCollection _inboundPackagesQueue = new BlockingCollection(); public void Dispatch(Exception exception) { - foreach (var awaiter in _awaiters) + foreach (var awaiter in _packetAwaiters) { - Task.Run(() => awaiter.Value.Fail(exception)); // Task.Run fixes a dead lock. Without this the client only receives one message. + awaiter.Value.Fail(exception); } - _awaiters.Clear(); + _packetAwaiters.Clear(); } public void Dispatch(MqttBasePacket packet) @@ -32,31 +34,56 @@ namespace MQTTnet.Client.PacketDispatcher var type = packet.GetType(); var key = new Tuple(identifier, type); - if (_awaiters.TryRemove(key, out var awaiter)) + if (_packetAwaiters.TryRemove(key, out var awaiter)) { - Task.Run(() => awaiter.Complete(packet)); // Task.Run fixes a dead lock. Without this the client only receives one message. + awaiter.Complete(packet); return; } - throw new InvalidOperationException($"Packet of type '{type.Name}' not handled or dispatched."); + lock (_inboundPackagesQueue) + { + _inboundPackagesQueue.Add(packet); + } + } + + public MqttBasePacket Take(CancellationToken cancellationToken) + { + BlockingCollection inboundPackagesQueue; + lock (_inboundPackagesQueue) + { + inboundPackagesQueue = _inboundPackagesQueue; + } + + return inboundPackagesQueue.Take(cancellationToken); } public void Reset() { - _awaiters.Clear(); + foreach (var awaiter in _packetAwaiters) + { + awaiter.Value.Cancel(); + } + + lock (_inboundPackagesQueue) + { + _inboundPackagesQueue?.Dispose(); + _inboundPackagesQueue = new BlockingCollection(); + } + + _packetAwaiters.Clear(); } public MqttPacketAwaiter AddPacketAwaiter(ushort? identifier) where TResponsePacket : MqttBasePacket { - var awaiter = new MqttPacketAwaiter(); - if (!identifier.HasValue) { identifier = 0; } + var awaiter = new MqttPacketAwaiter(identifier, this); + var key = new Tuple(identifier.Value, typeof(TResponsePacket)); - if (!_awaiters.TryAdd(key, awaiter)) + if (!_packetAwaiters.TryAdd(key, awaiter)) { throw new InvalidOperationException($"The packet dispatcher already has an awaiter for packet of type '{key.Item2.Name}' with identifier {key.Item1}."); } @@ -71,8 +98,8 @@ namespace MQTTnet.Client.PacketDispatcher identifier = 0; } - var key = new Tuple(identifier ?? 0, typeof(TResponsePacket)); - _awaiters.TryRemove(key, out _); + var key = new Tuple(identifier.Value, typeof(TResponsePacket)); + _packetAwaiters.TryRemove(key, out _); } } } \ No newline at end of file diff --git a/Source/MQTTnet/Server/MqttClientSession.cs b/Source/MQTTnet/Server/MqttClientSession.cs index 688d0945..ad6fdfa9 100644 --- a/Source/MQTTnet/Server/MqttClientSession.cs +++ b/Source/MQTTnet/Server/MqttClientSession.cs @@ -7,6 +7,8 @@ using MQTTnet.Adapter; using MQTTnet.Client; using MQTTnet.Diagnostics; using MQTTnet.Exceptions; +using MQTTnet.MessageStream; +using MQTTnet.PacketDispatcher; using MQTTnet.Packets; using MQTTnet.Protocol; @@ -15,6 +17,7 @@ namespace MQTTnet.Server public class MqttClientSession : IMqttClientSession { private readonly MqttPacketIdentifierProvider _packetIdentifierProvider = new MqttPacketIdentifierProvider(); + private readonly MqttPacketDispatcher _packetDispatcher = new MqttPacketDispatcher(); private readonly MqttRetainedMessagesManager _retainedMessagesManager; private readonly MqttServerEventDispatcher _eventDispatcher; @@ -56,7 +59,7 @@ namespace MQTTnet.Server _keepAliveMonitor = new MqttClientKeepAliveMonitor(this, _logger); _subscriptionsManager = new MqttClientSubscriptionsManager(clientId, _options, eventDispatcher); - _pendingMessagesQueue = new MqttClientSessionPendingMessagesQueue(_options, this, _logger); + _pendingMessagesQueue = new MqttClientSessionPendingMessagesQueue(_options, this, _packetDispatcher, _logger); } public string ClientId { get; } @@ -144,13 +147,14 @@ namespace MQTTnet.Server } var publishPacket = _channelAdapter.PacketFormatterAdapter.DataConverter.CreatePublishPacket(applicationMessage); + publishPacket.QualityOfServiceLevel = checkSubscriptionsResult.QualityOfServiceLevel; // Set the retain flag to true according to [MQTT-3.3.1-8] and [MQTT-3.3.1-9]. publishPacket.Retain = isRetainedApplicationMessage; if (publishPacket.QualityOfServiceLevel > 0) { - publishPacket.PacketIdentifier = _packetIdentifierProvider.GetNewPacketIdentifier(); + publishPacket.PacketIdentifier = _packetIdentifierProvider.GetNextPacketIdentifier(); } if (_options.ClientMessageQueueInterceptor != null) @@ -178,6 +182,8 @@ namespace MQTTnet.Server _pendingMessagesQueue.Enqueue(publishPacket); } + + private async Task RunInternalAsync(MqttConnectPacket connectPacket, IMqttChannelAdapter channelAdapter) { if (channelAdapter == null) throw new ArgumentNullException(nameof(channelAdapter)); @@ -212,13 +218,41 @@ namespace MQTTnet.Server _isCleanSession = false; + Task.Run(async () => + { + while (!cancellationTokenSource.IsCancellationRequested) + { + var packet = _packetDispatcher.Take(cancellationTokenSource.Token); + await ProcessReceivedPacketAsync(packet, cancellationTokenSource.Token).ConfigureAwait(false); + } + }, cancellationTokenSource.Token); + + Task.Run(async () => + { + while (!cancellationTokenSource.IsCancellationRequested) + { + try + { + var packet = await _outboundMessageStream.TakeAsync(cancellationTokenSource.Token); + await channelAdapter.SendPacketAsync(packet, cancellationTokenSource.Token); + } + catch (Exception e) + { + _logger.Error(e, "sdfsdf"); + await StopAsync(MqttClientDisconnectType.NotClean); + + } + + } + },cancellationTokenSource.Token); + while (!cancellationTokenSource.IsCancellationRequested) { var packet = await channelAdapter.ReceivePacketAsync(TimeSpan.Zero, cancellationTokenSource.Token).ConfigureAwait(false); if (packet != null) { _keepAliveMonitor.PacketReceived(packet); - await ProcessReceivedPacketAsync(channelAdapter, packet, cancellationTokenSource.Token).ConfigureAwait(false); + _packetDispatcher.Dispatch(packet); } } } @@ -229,17 +263,7 @@ namespace MQTTnet.Server { if (exception is MqttCommunicationException) { - if (exception is MqttCommunicationClosedGracefullyException) - { - _logger.Verbose("Client '{0}': Connection closed gracefully.", ClientId); - - StopInternal(MqttClientDisconnectType.Clean); - return; - } - else - { - _logger.Warning(exception, "Client '{0}': Communication exception while receiving client packets.", ClientId); - } + _logger.Warning(exception, "Client '{0}': Communication exception while receiving client packets.", ClientId); } else { @@ -256,18 +280,18 @@ namespace MQTTnet.Server } _willMessage = null; - + _channelAdapter.ReadingPacketStarted -= OnAdapterReadingPacketStarted; _channelAdapter.ReadingPacketCompleted -= OnAdapterReadingPacketCompleted; _channelAdapter = null; - + _logger.Info("Client '{0}': Session stopped.", ClientId); _eventDispatcher.OnClientDisconnected(ClientId, _wasCleanDisconnect); _workerTask = null; } } - + private void StopInternal(MqttClientDisconnectType type) { var cts = _cancellationTokenSource; @@ -278,55 +302,33 @@ namespace MQTTnet.Server _wasCleanDisconnect = type == MqttClientDisconnectType.Clean; _cancellationTokenSource?.Cancel(false); + _packetDispatcher.Reset(); } - private Task ProcessReceivedPacketAsync(IMqttChannelAdapter channelAdapter, MqttBasePacket packet, CancellationToken cancellationToken) + private readonly MqttMessageStream _outboundMessageStream = new MqttMessageStream(); + + private Task ProcessReceivedPacketAsync(MqttBasePacket packet, CancellationToken cancellationToken) { if (packet is MqttPublishPacket publishPacket) { - return HandleIncomingPublishPacketAsync(channelAdapter, publishPacket, cancellationToken); + return HandleIncomingPublishPacketAsync(publishPacket, cancellationToken); } if (packet is MqttPingReqPacket) { - return channelAdapter.SendPacketAsync(new MqttPingRespPacket(), cancellationToken); - } - - if (packet is MqttPubRelPacket pubRelPacket) - { - var responsePacket = new MqttPubCompPacket - { - PacketIdentifier = pubRelPacket.PacketIdentifier, - ReasonCode = MqttPubCompReasonCode.Success - }; - - return channelAdapter.SendPacketAsync(responsePacket, cancellationToken); - } - - if (packet is MqttPubRecPacket pubRecPacket) - { - var responsePacket = new MqttPubRelPacket - { - PacketIdentifier = pubRecPacket.PacketIdentifier, - ReasonCode = MqttPubRelReasonCode.Success - }; - - return channelAdapter.SendPacketAsync(responsePacket, cancellationToken); - } - - if (packet is MqttPubAckPacket || packet is MqttPubCompPacket) - { + //return channelAdapter.SendPacketAsync(new MqttPingRespPacket(), cancellationToken); + _outboundMessageStream.Enqueue(new MqttPingRespPacket()); return Task.FromResult(0); } if (packet is MqttSubscribePacket subscribePacket) { - return HandleIncomingSubscribePacketAsync(channelAdapter, subscribePacket, cancellationToken); + return HandleIncomingSubscribePacketAsync(subscribePacket, cancellationToken); } if (packet is MqttUnsubscribePacket unsubscribePacket) { - return HandleIncomingUnsubscribePacketAsync(channelAdapter, unsubscribePacket, cancellationToken); + return HandleIncomingUnsubscribePacketAsync(unsubscribePacket, cancellationToken); } if (packet is MqttDisconnectPacket) @@ -335,6 +337,18 @@ namespace MQTTnet.Server return Task.FromResult(0); } + //if (packet is MqttAuthPacket || + // packet is MqttSubAckPacket || + // packet is MqttUnsubAckPacket || + // packet is MqttPubAckPacket || + // packet is MqttPubCompPacket || + // packet is MqttPubRecPacket || + // packet is MqttPubRelPacket) + //{ + // _packetDispatcher.TryDispatch(packet); + // return Task.FromResult(0); + //} + _logger.Warning(null, "Client '{0}': Received invalid packet ({1}). Closing connection.", ClientId, packet); StopInternal(MqttClientDisconnectType.NotClean); @@ -350,10 +364,14 @@ namespace MQTTnet.Server } } - private async Task HandleIncomingSubscribePacketAsync(IMqttChannelAdapter adapter, MqttSubscribePacket subscribePacket, CancellationToken cancellationToken) + private async Task HandleIncomingSubscribePacketAsync(MqttSubscribePacket subscribePacket, CancellationToken cancellationToken) { var subscribeResult = await _subscriptionsManager.SubscribeAsync(subscribePacket).ConfigureAwait(false); - await adapter.SendPacketAsync(subscribeResult.ResponsePacket, cancellationToken).ConfigureAwait(false); + + _outboundMessageStream.Enqueue(subscribeResult.ResponsePacket); + //await adapter.SendPacketAsync(subscribeResult.ResponsePacket, cancellationToken).ConfigureAwait(false); + + // TODO: Add "WaitForDelivery". if (subscribeResult.CloseConnection) { @@ -364,13 +382,17 @@ namespace MQTTnet.Server await EnqueueSubscribedRetainedMessagesAsync(subscribePacket.TopicFilters).ConfigureAwait(false); } - private Task HandleIncomingUnsubscribePacketAsync(IMqttChannelAdapter adapter, MqttUnsubscribePacket unsubscribePacket, CancellationToken cancellationToken) + private Task HandleIncomingUnsubscribePacketAsync(MqttUnsubscribePacket unsubscribePacket, CancellationToken cancellationToken) { var unsubscribeResult = _subscriptionsManager.Unsubscribe(unsubscribePacket); - return adapter.SendPacketAsync(unsubscribeResult, cancellationToken); + + _outboundMessageStream.Enqueue(unsubscribeResult); + + //return adapter.SendPacketAsync(unsubscribeResult, cancellationToken); + return Task.FromResult(0); } - private Task HandleIncomingPublishPacketAsync(IMqttChannelAdapter adapter, MqttPublishPacket publishPacket, CancellationToken cancellationToken) + private Task HandleIncomingPublishPacketAsync(MqttPublishPacket publishPacket, CancellationToken cancellationToken) { Interlocked.Increment(ref _receivedMessagesCount); @@ -382,11 +404,11 @@ namespace MQTTnet.Server } case MqttQualityOfServiceLevel.AtLeastOnce: { - return HandleIncomingPublishPacketWithQoS1Async(adapter, publishPacket, cancellationToken); + return HandleIncomingPublishPacketWithQoS1Async(publishPacket, cancellationToken); } case MqttQualityOfServiceLevel.ExactlyOnce: { - return HandleIncomingPublishPacketWithQoS2Async(adapter, publishPacket, cancellationToken); + return HandleIncomingPublishPacketWithQoS2Async(publishPacket, cancellationToken); } default: { @@ -405,7 +427,6 @@ namespace MQTTnet.Server } private Task HandleIncomingPublishPacketWithQoS1Async( - IMqttChannelAdapter adapter, MqttPublishPacket publishPacket, CancellationToken cancellationToken) { @@ -419,24 +440,32 @@ namespace MQTTnet.Server ReasonCode = MqttPubAckReasonCode.Success }; - return adapter.SendPacketAsync(response, cancellationToken); + _outboundMessageStream.Enqueue(response); + + //return adapter.SendPacketAsync(response, cancellationToken); + return Task.FromResult(0); } - private Task HandleIncomingPublishPacketWithQoS2Async( - IMqttChannelAdapter adapter, + private async Task HandleIncomingPublishPacketWithQoS2Async( MqttPublishPacket publishPacket, CancellationToken cancellationToken) { // QoS 2 is implement as method "B" (4.3.3 QoS 2: Exactly once delivery) _sessionsManager.EnqueueApplicationMessage(this, _channelAdapter.PacketFormatterAdapter.DataConverter.CreateApplicationMessage(publishPacket)); - var response = new MqttPubRecPacket + using (var pubRelPacketAwaiter = _packetDispatcher.AddPacketAwaiter(publishPacket.PacketIdentifier)) { - PacketIdentifier = publishPacket.PacketIdentifier, - ReasonCode = MqttPubRecReasonCode.Success - }; + var pubRecPacket = new MqttPubRecPacket + { + PacketIdentifier = publishPacket.PacketIdentifier, + ReasonCode = MqttPubRecReasonCode.Success + }; - return adapter.SendPacketAsync(response, cancellationToken); + //await adapter.SendPacketAsync(pubRecPacket, cancellationToken).ConfigureAwait(false); + _outboundMessageStream.Enqueue(pubRecPacket); + + await pubRelPacketAwaiter.WaitOneAsync(_options.DefaultCommunicationTimeout).ConfigureAwait(false); + } } private void OnAdapterReadingPacketCompleted(object sender, EventArgs e) diff --git a/Source/MQTTnet/Server/MqttClientSessionPendingMessagesQueue.cs b/Source/MQTTnet/Server/MqttClientSessionPendingMessagesQueue.cs index 60d5869b..1fd19e8c 100644 --- a/Source/MQTTnet/Server/MqttClientSessionPendingMessagesQueue.cs +++ b/Source/MQTTnet/Server/MqttClientSessionPendingMessagesQueue.cs @@ -6,6 +6,7 @@ using MQTTnet.Adapter; using MQTTnet.Diagnostics; using MQTTnet.Exceptions; using MQTTnet.Internal; +using MQTTnet.PacketDispatcher; using MQTTnet.Packets; using MQTTnet.Protocol; @@ -13,20 +14,26 @@ namespace MQTTnet.Server { public class MqttClientSessionPendingMessagesQueue : IDisposable { - private readonly Queue _queue = new Queue(); + private readonly Queue _queue = new Queue(); private readonly AsyncAutoResetEvent _queueLock = new AsyncAutoResetEvent(); private readonly IMqttServerOptions _options; private readonly MqttClientSession _clientSession; + private readonly MqttPacketDispatcher _packetDispatcher; private readonly IMqttNetChildLogger _logger; private long _sentPacketsCount; - public MqttClientSessionPendingMessagesQueue(IMqttServerOptions options, MqttClientSession clientSession, IMqttNetChildLogger logger) + public MqttClientSessionPendingMessagesQueue( + IMqttServerOptions options, + MqttClientSession clientSession, + MqttPacketDispatcher packetDispatcher, + IMqttNetChildLogger logger) { if (logger == null) throw new ArgumentNullException(nameof(logger)); _options = options ?? throw new ArgumentNullException(nameof(options)); _clientSession = clientSession ?? throw new ArgumentNullException(nameof(clientSession)); + _packetDispatcher = packetDispatcher ?? throw new ArgumentNullException(nameof(packetDispatcher)); _logger = logger.CreateChildLogger(nameof(MqttClientSessionPendingMessagesQueue)); } @@ -115,7 +122,7 @@ namespace MQTTnet.Server private async Task TrySendNextQueuedPacketAsync(IMqttChannelAdapter adapter, CancellationToken cancellationToken) { - MqttBasePacket packet = null; + MqttPublishPacket packet = null; try { if (cancellationToken.IsCancellationRequested) @@ -137,7 +144,34 @@ namespace MQTTnet.Server return; } - await adapter.SendPacketAsync(packet, cancellationToken).ConfigureAwait(false); + if (packet.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtMostOnce) + { + await adapter.SendPacketAsync(packet, cancellationToken).ConfigureAwait(false); + } + else if (packet.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtLeastOnce) + { + var awaiter = _packetDispatcher.AddPacketAwaiter(packet.PacketIdentifier); + await adapter.SendPacketAsync(packet, cancellationToken).ConfigureAwait(false); + await awaiter.WaitOneAsync(_options.DefaultCommunicationTimeout).ConfigureAwait(false); + } + else if (packet.QualityOfServiceLevel == MqttQualityOfServiceLevel.ExactlyOnce) + { + var awaiter1 = _packetDispatcher.AddPacketAwaiter(packet.PacketIdentifier); + var awaiter2 = _packetDispatcher.AddPacketAwaiter(packet.PacketIdentifier); + try + { + await adapter.SendPacketAsync(packet, cancellationToken).ConfigureAwait(false); + await awaiter1.WaitOneAsync(_options.DefaultCommunicationTimeout).ConfigureAwait(false); + + await adapter.SendPacketAsync(new MqttPubRelPacket { PacketIdentifier = packet.PacketIdentifier }, cancellationToken).ConfigureAwait(false); + await awaiter2.WaitOneAsync(_options.DefaultCommunicationTimeout).ConfigureAwait(false); + } + finally + { + _packetDispatcher.RemovePacketAwaiter(packet.PacketIdentifier); + _packetDispatcher.RemovePacketAwaiter(packet.PacketIdentifier); + } + } _logger.Verbose("Enqueued packet sent (ClientId: {0}).", _clientSession.ClientId); @@ -153,7 +187,7 @@ namespace MQTTnet.Server { _logger.Warning(exception, "Sending publish packet failed: Communication exception (ClientId: {0}).", _clientSession.ClientId); } - else if (exception is OperationCanceledException) + else if (exception is OperationCanceledException && cancellationToken.IsCancellationRequested) { } else @@ -161,14 +195,11 @@ namespace MQTTnet.Server _logger.Error(exception, "Sending publish packet failed (ClientId: {0}).", _clientSession.ClientId); } - if (packet is MqttPublishPacket publishPacket) + if (packet?.QualityOfServiceLevel > MqttQualityOfServiceLevel.AtMostOnce) { - if (publishPacket.QualityOfServiceLevel > MqttQualityOfServiceLevel.AtMostOnce) - { - publishPacket.Dup = true; + packet.Dup = true; - Enqueue(publishPacket); - } + Enqueue(packet); } if (!cancellationToken.IsCancellationRequested) diff --git a/Source/MQTTnet/Server/MqttServer.cs b/Source/MQTTnet/Server/MqttServer.cs index ffba8fb3..d2145bda 100644 --- a/Source/MQTTnet/Server/MqttServer.cs +++ b/Source/MQTTnet/Server/MqttServer.cs @@ -5,6 +5,7 @@ using System.Threading; using System.Threading.Tasks; using MQTTnet.Adapter; using MQTTnet.Client.Publishing; +using MQTTnet.Client.Receiving; using MQTTnet.Diagnostics; namespace MQTTnet.Server @@ -31,7 +32,18 @@ namespace MQTTnet.Server _eventDispatcher.ClientDisconnected += (s, e) => ClientDisconnected?.Invoke(s, e); _eventDispatcher.ClientSubscribedTopic += (s, e) => ClientSubscribedTopic?.Invoke(s, e); _eventDispatcher.ClientUnsubscribedTopic += (s, e) => ClientUnsubscribedTopic?.Invoke(s, e); - _eventDispatcher.ApplicationMessageReceived += (s, e) => ApplicationMessageReceived?.Invoke(s, e); + _eventDispatcher.ApplicationMessageReceived += async (s, e) => + { + // TODO: Migrate EventDispatcher to proper handlers and no events anymore. + ApplicationMessageReceived?.Invoke(s, e); + + var handler = ReceivedApplicationMessageHandler; + if (handler != null) + { + await handler.HandleApplicationMessageAsync( + new MqttApplicationMessageHandlerContext(e.ClientId, e.ApplicationMessage)).ConfigureAwait(false); + } + }; } public event EventHandler Started; @@ -42,6 +54,7 @@ namespace MQTTnet.Server public event EventHandler ClientSubscribedTopic; public event EventHandler ClientUnsubscribedTopic; + public IMqttApplicationMessageHandler ReceivedApplicationMessageHandler { get; set; } public event EventHandler ApplicationMessageReceived; public IMqttServerOptions Options { get; private set; } diff --git a/Tests/MQTTnet.Benchmarks/SerializerBenchmark.cs b/Tests/MQTTnet.Benchmarks/SerializerBenchmark.cs index b1797ef0..346626e2 100644 --- a/Tests/MQTTnet.Benchmarks/SerializerBenchmark.cs +++ b/Tests/MQTTnet.Benchmarks/SerializerBenchmark.cs @@ -52,7 +52,7 @@ namespace MQTTnet.Benchmarks { channel.Reset(); - var header = reader.ReadFixedHeaderAsync(fixedHeader, CancellationToken.None).GetAwaiter().GetResult(); + var header = reader.ReadFixedHeaderAsync(fixedHeader, CancellationToken.None).GetAwaiter().GetResult().FixedHeader; var receivedPacket = new ReceivedMqttPacket( header.Flags, diff --git a/Tests/MQTTnet.Core.Tests/ManagedMqttClientTests.cs b/Tests/MQTTnet.Core.Tests/ManagedMqttClientTests.cs index 559a87c1..1f19521d 100644 --- a/Tests/MQTTnet.Core.Tests/ManagedMqttClientTests.cs +++ b/Tests/MQTTnet.Core.Tests/ManagedMqttClientTests.cs @@ -13,26 +13,32 @@ namespace MQTTnet.Tests { var factory = new MqttFactory(); var managedClient = factory.CreateManagedMqttClient(); + try + { + var clientOptions = new ManagedMqttClientOptionsBuilder() + .WithMaxPendingMessages(5) + .WithPendingMessagesOverflowStrategy(MqttPendingMessagesOverflowStrategy.DropNewMessage); - var clientOptions = new ManagedMqttClientOptionsBuilder() - .WithMaxPendingMessages(5) - .WithPendingMessagesOverflowStrategy(MqttPendingMessagesOverflowStrategy.DropNewMessage); + clientOptions.WithClientOptions(o => o.WithTcpServer("localhost")); - clientOptions.WithClientOptions(o => o.WithTcpServer("localhost")); + await managedClient.StartAsync(clientOptions.Build()); - await managedClient.StartAsync(clientOptions.Build()); + await managedClient.PublishAsync(new MqttApplicationMessage { Topic = "1" }); + await managedClient.PublishAsync(new MqttApplicationMessage { Topic = "2" }); + await managedClient.PublishAsync(new MqttApplicationMessage { Topic = "3" }); + await managedClient.PublishAsync(new MqttApplicationMessage { Topic = "4" }); + await managedClient.PublishAsync(new MqttApplicationMessage { Topic = "5" }); - await managedClient.PublishAsync(new MqttApplicationMessage { Topic = "1" }); - await managedClient.PublishAsync(new MqttApplicationMessage { Topic = "2" }); - await managedClient.PublishAsync(new MqttApplicationMessage { Topic = "3" }); - await managedClient.PublishAsync(new MqttApplicationMessage { Topic = "4" }); - await managedClient.PublishAsync(new MqttApplicationMessage { Topic = "5" }); + await managedClient.PublishAsync(new MqttApplicationMessage { Topic = "6" }); + await managedClient.PublishAsync(new MqttApplicationMessage { Topic = "7" }); + await managedClient.PublishAsync(new MqttApplicationMessage { Topic = "8" }); - await managedClient.PublishAsync(new MqttApplicationMessage { Topic = "6" }); - await managedClient.PublishAsync(new MqttApplicationMessage { Topic = "7" }); - await managedClient.PublishAsync(new MqttApplicationMessage { Topic = "8" }); - - Assert.AreEqual(5, managedClient.PendingApplicationMessagesCount); + Assert.AreEqual(5, managedClient.PendingApplicationMessagesCount); + } + finally + { + await managedClient.StopAsync(); + } } } } diff --git a/Tests/MQTTnet.Core.Tests/MqttClientTests.cs b/Tests/MQTTnet.Core.Tests/MqttClientTests.cs index 6c70caf2..9725fee0 100644 --- a/Tests/MQTTnet.Core.Tests/MqttClientTests.cs +++ b/Tests/MQTTnet.Core.Tests/MqttClientTests.cs @@ -2,11 +2,14 @@ using System; using System.Collections.Generic; using System.Linq; using System.Net.Sockets; +using System.Threading; using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; using MQTTnet.Client; using MQTTnet.Client.Options; +using MQTTnet.Client.Receiving; using MQTTnet.Exceptions; +using MQTTnet.Protocol; using MQTTnet.Server; namespace MQTTnet.Tests @@ -39,11 +42,100 @@ namespace MQTTnet.Tests Assert.IsInstanceOfType(ex.InnerException, typeof(SocketException)); } + [TestMethod] + public async Task Client_Preserve_Message_Order() + { + // The messages are sent in reverse or to ensure that the delay in the handler + // needs longer for the first messages and later messages may be processed earlier (if there + // is an issue). + const int MessagesCount = 50; + + using (var testSetup = new TestSetup()) + { + await testSetup.StartServerAsync(); + + var client1 = await testSetup.ConnectClientAsync(); + await client1.SubscribeAsync("x"); + + var receivedValues = new List(); + + async Task Handler1(MqttApplicationMessageHandlerContext context) + { + var value = int.Parse(context.ApplicationMessage.ConvertPayloadToString()); + await Task.Delay(value); + + lock (receivedValues) + { + receivedValues.Add(value); + } + } + + client1.UseReceivedApplicationMessageHandler(Handler1); + + var client2 = await testSetup.ConnectClientAsync(); + for (var i = MessagesCount; i > 0; i--) + { + await client2.PublishAsync("x", i.ToString()); + } + + await Task.Delay(5000); + + for (var i = MessagesCount; i > 0; i--) + { + Assert.AreEqual(i, receivedValues[MessagesCount - i]); + } + } + } + + [TestMethod] + public async Task Client_Send_Reply_For_Any_Received_Message() + { + using (var testSetup = new TestSetup()) + { + await testSetup.StartServerAsync(); + + var client1 = await testSetup.ConnectClientAsync(); + await client1.SubscribeAsync("request/+"); + + async Task Handler1(MqttApplicationMessageHandlerContext context) + { + await client1.PublishAsync($"reply/{context.ApplicationMessage.Topic}"); + } + + client1.UseReceivedApplicationMessageHandler(Handler1); + + var client2 = await testSetup.ConnectClientAsync(); + await client2.SubscribeAsync("reply/#"); + + var replies = new List(); + + void Handler2(MqttApplicationMessageHandlerContext context) + { + lock (replies) + { + replies.Add(context.ApplicationMessage.Topic); + } + } + + client2.UseReceivedApplicationMessageHandler((Action) Handler2); + + await Task.Delay(500); + + await client2.PublishAsync("request/a"); + await client2.PublishAsync("request/b"); + await client2.PublishAsync("request/c"); + + await Task.Delay(500); + + Assert.AreEqual("reply/request/a,reply/request/b,reply/request/c", string.Join("," , replies)); + } + } + [TestMethod] public async Task Client_Publish() { var server = new MqttFactory().CreateMqttServer(); - + try { var receivedMessages = new List(); @@ -125,39 +217,85 @@ namespace MQTTnet.Tests } } -//#if DEBUG -// [TestMethod] -// public async Task Client_Cleanup_On_Authentification_Fails() -// { -// var channel = new TestMqttCommunicationAdapter(); -// var channel2 = new TestMqttCommunicationAdapter(); -// channel.Partner = channel2; -// channel2.Partner = channel; + [TestMethod] + public async Task Client_Exception_In_Application_Message_Handler() + { + using (var testSetup = new TestSetup()) + { + testSetup.IgnoreClientLogErrors = true; + testSetup.IgnoreServerLogErrors = true; -// Task.Run(async () => { -// var connect = await channel2.ReceivePacketAsync(TimeSpan.Zero, CancellationToken.None); -// await channel2.SendPacketAsync(new MqttConnAckPacket -// { -// ConnectReturnCode = Protocol.MqttConnectReturnCode.ConnectionRefusedNotAuthorized -// }, CancellationToken.None); -// }); - -// var fake = new TestMqttCommunicationAdapterFactory(channel); + await testSetup.StartServerAsync( + new MqttServerOptionsBuilder() + .WithPersistentSessions() + .WithDefaultCommunicationTimeout(TimeSpan.FromMilliseconds(50))); -// var client = new MqttClient(fake, new MqttNetLogger()); + var client1 = await testSetup.ConnectClientAsync(new MqttClientOptionsBuilder() + .WithCleanSession(false)); -// try -// { -// await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("any-server").Build()); -// } -// catch (Exception ex) -// { -// Assert.IsInstanceOfType(ex, typeof(MqttConnectingFailedException)); -// } + await client1.SubscribeAsync("x", MqttQualityOfServiceLevel.AtLeastOnce); -// Assert.IsTrue(client._packetReceiverTask == null || client._packetReceiverTask.IsCompleted, "receive loop not completed"); -// Assert.IsTrue(client._keepAliveMessageSenderTask == null || client._keepAliveMessageSenderTask.IsCompleted, "keepalive loop not completed"); -// } -//#endif + var retries = 0; + + async Task Handler1(MqttApplicationMessageHandlerContext context) + { + retries++; + + await Task.Delay(50); + throw new Exception("Broken!"); + } + + client1.UseReceivedApplicationMessageHandler(Handler1); + + var client2 = await testSetup.ConnectClientAsync(); + await client2.PublishAsync("x"); + + await Task.Delay(1000); + + // The server should disconnect clients which are not responding. + Assert.IsFalse(client1.IsConnected); + + await client1.ReconnectAsync().ConfigureAwait(false); + + await Task.Delay(1000); + + Assert.AreEqual(2, retries); + } + } + + //#if DEBUG + // [TestMethod] + // public async Task Client_Cleanup_On_Authentification_Fails() + // { + // var channel = new TestMqttCommunicationAdapter(); + // var channel2 = new TestMqttCommunicationAdapter(); + // channel.Partner = channel2; + // channel2.Partner = channel; + + // Task.Run(async () => { + // var connect = await channel2.ReceivePacketAsync(TimeSpan.Zero, CancellationToken.None); + // await channel2.SendPacketAsync(new MqttConnAckPacket + // { + // ConnectReturnCode = Protocol.MqttConnectReturnCode.ConnectionRefusedNotAuthorized + // }, CancellationToken.None); + // }); + + // var fake = new TestMqttCommunicationAdapterFactory(channel); + + // var client = new MqttClient(fake, new MqttNetLogger()); + + // try + // { + // await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("any-server").Build()); + // } + // catch (Exception ex) + // { + // Assert.IsInstanceOfType(ex, typeof(MqttConnectingFailedException)); + // } + + // Assert.IsTrue(client._packetReceiverTask == null || client._packetReceiverTask.IsCompleted, "receive loop not completed"); + // Assert.IsTrue(client._keepAliveMessageSenderTask == null || client._keepAliveMessageSenderTask.IsCompleted, "keepalive loop not completed"); + // } + //#endif } } diff --git a/Tests/MQTTnet.Core.Tests/MqttPacketIdentifierProviderTests.cs b/Tests/MQTTnet.Core.Tests/MqttPacketIdentifierProviderTests.cs index e5d0cd71..e2f733aa 100644 --- a/Tests/MQTTnet.Core.Tests/MqttPacketIdentifierProviderTests.cs +++ b/Tests/MQTTnet.Core.Tests/MqttPacketIdentifierProviderTests.cs @@ -10,10 +10,10 @@ namespace MQTTnet.Tests public void Reset() { var p = new MqttPacketIdentifierProvider(); - Assert.AreEqual(1, p.GetNewPacketIdentifier()); - Assert.AreEqual(2, p.GetNewPacketIdentifier()); + Assert.AreEqual(1, p.GetNextPacketIdentifier()); + Assert.AreEqual(2, p.GetNextPacketIdentifier()); p.Reset(); - Assert.AreEqual(1, p.GetNewPacketIdentifier()); + Assert.AreEqual(1, p.GetNextPacketIdentifier()); } [TestMethod] @@ -23,10 +23,10 @@ namespace MQTTnet.Tests for (ushort i = 0; i < ushort.MaxValue; i++) { - Assert.AreEqual(i + 1, p.GetNewPacketIdentifier()); + Assert.AreEqual(i + 1, p.GetNextPacketIdentifier()); } - Assert.AreEqual(1, p.GetNewPacketIdentifier()); + Assert.AreEqual(1, p.GetNextPacketIdentifier()); } } } diff --git a/Tests/MQTTnet.Core.Tests/MqttPacketReaderTests.cs b/Tests/MQTTnet.Core.Tests/MqttPacketReaderTests.cs index 215dd0aa..4ae204b1 100644 --- a/Tests/MQTTnet.Core.Tests/MqttPacketReaderTests.cs +++ b/Tests/MQTTnet.Core.Tests/MqttPacketReaderTests.cs @@ -1,7 +1,7 @@ using System.IO; using System.Threading; +using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; -using MQTTnet.Exceptions; using MQTTnet.Formatter; using MQTTnet.Internal; @@ -11,12 +11,13 @@ namespace MQTTnet.Tests public class MqttPacketReaderTests { [TestMethod] - [ExpectedException(typeof(MqttCommunicationClosedGracefullyException))] - public void MqttPacketReader_EmptyStream() + public async Task MqttPacketReader_EmptyStream() { var fixedHeader = new byte[2]; var reader = new MqttPacketReader(new TestMqttChannel(new MemoryStream())); - reader.ReadFixedHeaderAsync(fixedHeader, CancellationToken.None).GetAwaiter().GetResult(); + var readResult = await reader.ReadFixedHeaderAsync(fixedHeader, CancellationToken.None); + + Assert.IsTrue(readResult.ConnectionClosed); } } } diff --git a/Tests/MQTTnet.Core.Tests/MqttPacketSerializerTests.cs b/Tests/MQTTnet.Core.Tests/MqttPacketSerializerTests.cs index 2b612748..47ef0fd0 100644 --- a/Tests/MQTTnet.Core.Tests/MqttPacketSerializerTests.cs +++ b/Tests/MQTTnet.Core.Tests/MqttPacketSerializerTests.cs @@ -182,7 +182,7 @@ namespace MQTTnet.Tests var header = new MqttPacketReader(testChannel).ReadFixedHeaderAsync( new byte[2], - CancellationToken.None).GetAwaiter().GetResult(); + CancellationToken.None).GetAwaiter().GetResult().FixedHeader; var eof = buffer.Offset + buffer.Count; @@ -550,7 +550,7 @@ namespace MQTTnet.Tests { var channel = new TestMqttChannel(headerStream); var fixedHeader = new byte[2]; - var header = new MqttPacketReader(channel).ReadFixedHeaderAsync(fixedHeader, CancellationToken.None).GetAwaiter().GetResult(); + var header = new MqttPacketReader(channel).ReadFixedHeaderAsync(fixedHeader, CancellationToken.None).GetAwaiter().GetResult().FixedHeader; using (var bodyStream = new MemoryStream(Join(buffer1), (int)headerStream.Position, header.RemainingLength)) { @@ -586,7 +586,7 @@ namespace MQTTnet.Tests var channel = new TestMqttChannel(headerStream); var fixedHeader = new byte[2]; - var header = new MqttPacketReader(channel).ReadFixedHeaderAsync(fixedHeader, CancellationToken.None).GetAwaiter().GetResult(); + var header = new MqttPacketReader(channel).ReadFixedHeaderAsync(fixedHeader, CancellationToken.None).GetAwaiter().GetResult().FixedHeader; using (var bodyStream = new MemoryStream(Join(buffer1), (int)headerStream.Position, (int)header.RemainingLength)) { diff --git a/Tests/MQTTnet.Core.Tests/MqttServerTests.cs b/Tests/MQTTnet.Core.Tests/MqttServerTests.cs index ed6faa9f..fc66c2df 100644 --- a/Tests/MQTTnet.Core.Tests/MqttServerTests.cs +++ b/Tests/MQTTnet.Core.Tests/MqttServerTests.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Net.Sockets; @@ -19,36 +20,36 @@ namespace MQTTnet.Tests public class MqttServerTests { [TestMethod] - public void MqttServer_PublishSimple_AtMostOnce() + public async Task MqttServer_PublishSimple_AtMostOnce() { - TestPublishAsync( + await TestPublishAsync( "A/B/C", MqttQualityOfServiceLevel.AtMostOnce, "A/B/C", MqttQualityOfServiceLevel.AtMostOnce, - 1).Wait(); + 1); } [TestMethod] - public void MqttServer_PublishSimple_AtLeastOnce() + public async Task MqttServer_PublishSimple_AtLeastOnce() { - TestPublishAsync( + await TestPublishAsync( "A/B/C", MqttQualityOfServiceLevel.AtLeastOnce, "A/B/C", MqttQualityOfServiceLevel.AtLeastOnce, - 1).Wait(); + 1); } [TestMethod] - public void MqttServer_PublishSimple_ExactlyOnce() + public async Task MqttServer_PublishSimple_ExactlyOnce() { - TestPublishAsync( + await TestPublishAsync( "A/B/C", MqttQualityOfServiceLevel.ExactlyOnce, "A/B/C", MqttQualityOfServiceLevel.ExactlyOnce, - 1).Wait(); + 1); } [TestMethod] @@ -143,32 +144,27 @@ namespace MQTTnet.Tests } [TestMethod] - public async Task MqttServer_Publish() + public async Task MqttServer_Publish_From_Server() { - var serverAdapter = new TestMqttServerAdapter(); - var s = new MqttFactory().CreateMqttServer(new[] { serverAdapter }, new MqttNetLogger()); - var receivedMessagesCount = 0; - - try + using (var testSetup = new TestSetup()) { - await s.StartAsync(new MqttServerOptions()); + var server = await testSetup.StartServerAsync(); - var c1 = await serverAdapter.ConnectTestClient("c1"); + var receivedMessagesCount = 0; - c1.ApplicationMessageReceived += (_, __) => receivedMessagesCount++; + var client = await testSetup.ConnectClientAsync(); + client.UseReceivedApplicationMessageHandler(c => Interlocked.Increment(ref receivedMessagesCount)); var message = new MqttApplicationMessageBuilder().WithTopic("a").WithAtLeastOnceQoS().Build(); - await c1.SubscribeAsync(new TopicFilter { Topic = "a", QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce }); + await client.SubscribeAsync(new TopicFilter { Topic = "a", QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce }); - await s.PublishAsync(message); - await Task.Delay(500); - } - finally - { - await s.StopAsync(); - } + await server.PublishAsync(message); - Assert.AreEqual(1, receivedMessagesCount); + await Task.Delay(1000); + await server.StopAsync(); + + Assert.AreEqual(1, receivedMessagesCount); + } } [TestMethod] @@ -438,40 +434,43 @@ namespace MQTTnet.Tests [TestMethod] public async Task MqttServer_Lots_Of_Retained_Messages() { - const int ClientCount = 100; + const int ClientCount = 25; - var server = new MqttFactory().CreateMqttServer(); - try + using (var testSetup = new TestSetup()) { - await server.StartAsync(new MqttServerOptionsBuilder().Build()); - - Parallel.For( - 0, - ClientCount, - new ParallelOptions { MaxDegreeOfParallelism = 10 }, - i => + var server = await testSetup.StartServerAsync(); + + var tasks = new ConcurrentBag(); + for (var i = 0; i < ClientCount; i++) { - using (var client = new MqttFactory().CreateMqttClient()) + var clientId = i; + tasks.Add(Task.Run(async () => { - client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("localhost").Build()) - .GetAwaiter().GetResult(); - - for (var j = 0; j < 10; j++) + try { - // Clear retained message. - client.PublishAsync(new MqttApplicationMessageBuilder().WithTopic("r" + i) - .WithPayload(new byte[0]).WithRetainFlag().Build()).GetAwaiter().GetResult(); + using (var client = await testSetup.ConnectClientAsync()) + { + // Clear retained message. + await client.PublishAsync(new MqttApplicationMessageBuilder().WithTopic("r" + clientId) + .WithPayload(new byte[0]).WithRetainFlag().Build()); - // Set retained message. - client.PublishAsync(new MqttApplicationMessageBuilder().WithTopic("r" + i) - .WithPayload("value" + j).WithRetainFlag().Build()).GetAwaiter().GetResult(); + // Set retained message. + await client.PublishAsync(new MqttApplicationMessageBuilder().WithTopic("r" + clientId) + .WithPayload("value").WithRetainFlag().Build()); + + await Task.Delay(10); + + await client.DisconnectAsync(); + } } + catch (Exception exception) + { + testSetup.TrackException(exception); + } + })); + } - Thread.Sleep(100); - - client.DisconnectAsync().GetAwaiter().GetResult(); - } - }); + await Task.WhenAll(tasks.ToArray()); await Task.Delay(1000); @@ -484,10 +483,6 @@ namespace MQTTnet.Tests Assert.IsTrue(retainedMessages.Any(m => m.Topic == "r" + i)); } } - finally - { - await server.StopAsync(); - } } [TestMethod] @@ -986,6 +981,23 @@ namespace MQTTnet.Tests MqttQualityOfServiceLevel filterQualityOfServiceLevel, int expectedReceivedMessagesCount) { + //using (var testSetup = new TestSetup()) + //{ + // var server = await testSetup.StartServerAsync(new MqttServerOptionsBuilder()); + + // var clientOptions = new MqttClientOptionsBuilder(); + // var c1 = await testSetup.ConnectClientAsync(clientOptions); + // await Task.Delay(500); + // Assert.AreEqual(1, (await server.GetClientSessionsStatusAsync()).Count); + + // await c1.DisconnectAsync(); + // await Task.Delay(500); + + // Assert.AreEqual(0, (await server.GetClientSessionsStatusAsync()).Count); + //} + + + var s = new MqttFactory().CreateMqttServer(); var receivedMessagesCount = 0; @@ -994,13 +1006,15 @@ namespace MQTTnet.Tests await s.StartAsync(new MqttServerOptions()); var c1 = new MqttFactory().CreateMqttClient(); - c1.ApplicationMessageReceived += (_, __) => receivedMessagesCount++; - await c1.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("localhost").Build()); + c1.UseReceivedApplicationMessageHandler(c => receivedMessagesCount++); + + await c1.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build()); await c1.SubscribeAsync(new TopicFilterBuilder().WithTopic(topicFilter).WithQualityOfServiceLevel(filterQualityOfServiceLevel).Build()); var c2 = new MqttFactory().CreateMqttClient(); - await c2.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("localhost").Build()); + await c2.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build()); await c2.PublishAsync(builder => builder.WithTopic(topic).WithPayload(new byte[0]).WithQualityOfServiceLevel(qualityOfServiceLevel)); + await c2.DisconnectAsync().ConfigureAwait(false); await Task.Delay(500); await c1.UnsubscribeAsync(topicFilter); diff --git a/Tests/MQTTnet.Core.Tests/TestSetup.cs b/Tests/MQTTnet.Core.Tests/TestSetup.cs index 4ffc3ec9..b1515ca5 100644 --- a/Tests/MQTTnet.Core.Tests/TestSetup.cs +++ b/Tests/MQTTnet.Core.Tests/TestSetup.cs @@ -1,6 +1,6 @@ using System; using System.Collections.Generic; -using System.Threading; +using System.Linq; using System.Threading.Tasks; using MQTTnet.Client; using MQTTnet.Client.Options; @@ -16,10 +16,15 @@ namespace MQTTnet.Tests private readonly IMqttNetLogger _serverLogger = new MqttNetLogger("server"); private readonly IMqttNetLogger _clientLogger = new MqttNetLogger("client"); + private readonly List _serverErrors = new List(); + private readonly List _clientErrors = new List(); + + private readonly List _exceptions = new List(); + private IMqttServer _server; - private long _serverErrorsCount; - private long _clientErrorsCount; + public bool IgnoreClientLogErrors { get; set; } + public bool IgnoreServerLogErrors { get; set; } public TestSetup() { @@ -27,19 +32,30 @@ namespace MQTTnet.Tests { if (e.TraceMessage.Level == MqttNetLogLevel.Error) { - Interlocked.Increment(ref _serverErrorsCount); + lock (_serverErrors) + { + _serverErrors.Add(e.TraceMessage.ToString()); + } } }; _clientLogger.LogMessagePublished += (s, e) => { - if (e.TraceMessage.Level == MqttNetLogLevel.Error) + lock (_clientErrors) { - Interlocked.Increment(ref _clientErrorsCount); + if (e.TraceMessage.Level == MqttNetLogLevel.Error) + { + _clientErrors.Add(e.TraceMessage.ToString()); + } } }; } + public Task StartServerAsync() + { + return StartServerAsync(new MqttServerOptionsBuilder()); + } + public async Task StartServerAsync(MqttServerOptionsBuilder options) { if (_server != null) @@ -53,40 +69,63 @@ namespace MQTTnet.Tests return _server; } + public Task ConnectClientAsync() + { + return ConnectClientAsync(new MqttClientOptionsBuilder()); + } + public async Task ConnectClientAsync(MqttClientOptionsBuilder options) { var client = _mqttFactory.CreateMqttClient(_clientLogger); - _clients.Add(client); - await client.ConnectAsync(options.WithTcpServer("localhost", 1888).Build()); + _clients.Add(client); return client; } public void ThrowIfLogErrors() { - if (_serverErrorsCount > 0) + lock (_serverErrors) { - throw new Exception($"Server had {_serverErrorsCount} errors."); + if (!IgnoreServerLogErrors && _serverErrors.Count > 0) + { + throw new Exception($"Server had {_serverErrors.Count} errors (${string.Join(Environment.NewLine, _serverErrors)})."); + } } - if (_clientErrorsCount > 0) + lock (_clientErrors) { - throw new Exception($"Client(s) had {_clientErrorsCount} errors."); + if (!IgnoreClientLogErrors && _clientErrors.Count > 0) + { + throw new Exception($"Client(s) had {_clientErrors.Count} errors (${string.Join(Environment.NewLine, _clientErrors)})."); + } } } public void Dispose() { - ThrowIfLogErrors(); - foreach (var mqttClient in _clients) { - mqttClient.DisconnectAsync().GetAwaiter().GetResult(); - mqttClient.Dispose(); + mqttClient?.DisconnectAsync().GetAwaiter().GetResult(); + mqttClient?.Dispose(); } _server.StopAsync().GetAwaiter().GetResult(); + + ThrowIfLogErrors(); + + if (_exceptions.Any()) + { + throw new Exception($"{_exceptions.Count} exceptions tracked."); + } + } + + public void TrackException(Exception exception) + { + lock (_exceptions) + { + _exceptions.Add(exception); + } } } } From 6a2bded18184a8420fb18e88318dfc162ed09efd Mon Sep 17 00:00:00 2001 From: Christian Kratky Date: Sun, 27 Jan 2019 11:15:32 +0100 Subject: [PATCH 027/124] Refactor session and connection handling in server. Fix QoS level 2 issues. --- Build/MQTTnet.nuspec | 3 + README.md | 2 +- .../MqttConnectionContext.cs | 2 +- .../ManagedMqttClient.cs | 2 +- .../ManagedMqttClientExtensions.cs | 87 ++ Source/MQTTnet/Adapter/IMqttChannelAdapter.cs | 2 +- Source/MQTTnet/Adapter/MqttChannelAdapter.cs | 48 +- .../ApplicationMessagePublisherExtensions.cs | 81 -- Source/MQTTnet/Channel/IMqttChannel.cs | 2 +- ...ult.cs => MqttClientAuthenticateResult.cs} | 2 +- .../MqttClientConnectedEventArgs.cs | 6 +- Source/MQTTnet/Client/IMqttClient.cs | 9 +- Source/MQTTnet/Client/MqttClient.cs | 214 ++-- Source/MQTTnet/Client/MqttClientExtensions.cs | 114 ++ .../Options/MqttClientOptionsBuilder.cs | 5 + .../MQTTnet/Formatter/IMqttDataConverter.cs | 10 +- .../Formatter/V3/MqttV310DataConverter.cs | 40 +- .../Formatter/V5/MqttV500DataConverter.cs | 67 +- .../MQTTnet/IApplicationMessagePublisher.cs | 5 +- .../Implementations/MqttTcpChannel.Uwp.cs | 2 +- .../MQTTnet/Implementations/MqttTcpChannel.cs | 39 +- .../Implementations/MqttTcpServerListener.cs | 1 - .../Implementations/MqttWebSocketChannel.cs | 4 +- .../{TaskExtensions.cs => MqttTaskTimeout.cs} | 6 +- Source/MQTTnet/Internal/TestMqttChannel.cs | 2 +- .../MQTTnet/MqttApplicationMessageBuilder.cs | 17 +- .../PacketDispatcher/MqttPacketAwaiter.cs | 3 +- .../PacketDispatcher/MqttPacketDispatcher.cs | 28 +- Source/MQTTnet/Server/IMqttClientSession.cs | 7 +- .../Server/IMqttClientSessionStatus.cs | 33 - Source/MQTTnet/Server/IMqttServer.cs | 10 +- .../Server/IMqttServerConnectionValidator.cs | 2 +- Source/MQTTnet/Server/MqttClientConnection.cs | 568 +++++++++ .../Server/MqttClientDisconnectType.cs | 3 +- .../Server/MqttClientDisconnectedEventArgs.cs | 6 +- .../Server/MqttClientKeepAliveMonitor.cs | 21 +- Source/MQTTnet/Server/MqttClientSession.cs | 481 -------- ...ttClientSessionApplicationMessagesQueue.cs | 117 ++ .../MqttClientSessionPendingMessagesQueue.cs | 212 ---- .../MQTTnet/Server/MqttClientSessionStatus.cs | 52 - .../Server/MqttClientSessionsManager.cs | 233 ++-- .../Server/MqttClientSubscriptionsManager.cs | 43 +- .../Server/MqttEnqueuedApplicationMessage.cs | 4 +- .../Server/MqttRetainedMessagesManager.cs | 37 +- Source/MQTTnet/Server/MqttServer.cs | 20 +- .../MqttServerConnectionValidatorDelegate.cs | 2 +- .../Server/MqttServerEventDispatcher.cs | 4 +- Source/MQTTnet/Server/MqttServerExtensions.cs | 88 ++ .../Server/PrepareClientSessionResult.cs | 2 +- .../Server/Status/IMqttClientStatus.cs | 31 + .../Server/Status/IMqttSessionStatus.cs | 17 + .../MQTTnet/Server/Status/MqttClientStatus.cs | 43 + .../Server/Status/MqttSessionStatus.cs | 36 + .../MqttConnectionContextTest.cs | 4 +- .../ChannelAdapterBenchmark.cs | 2 +- .../MQTTnet.Benchmarks/SerializerBenchmark.cs | 2 +- Tests/MQTTnet.Core.Tests/ExtensionTests.cs | 13 +- .../{MqttClientTests.cs => Client_Tests.cs} | 16 +- .../TestEnvironment.cs} | 23 +- .../TestMqttCommunicationAdapter.cs | 29 +- .../TestMqttCommunicationAdapterFactory.cs | 3 +- .../{ => Mockups}/TestMqttServerAdapter.cs | 2 +- .../{ => Mockups}/TestServerExtensions.cs | 2 +- .../Mockups/TestServerStorage.cs | 22 + Tests/MQTTnet.Core.Tests/MqttClientTests.cs | 193 ++- .../MqttKeepAliveMonitorTests.cs | 11 +- Tests/MQTTnet.Core.Tests/MqttServerTests.cs | 1031 ----------------- .../MqttSubscriptionsManagerTests.cs | 12 +- .../MQTTnet.Core.Tests/Server_Status_Tests.cs | 159 +++ Tests/MQTTnet.Core.Tests/Server_Tests.cs | 970 ++++++++++++++++ .../MainPage.xaml | 6 +- .../MainPage.xaml.cs | 5 +- 72 files changed, 2919 insertions(+), 2461 deletions(-) delete mode 100644 Source/MQTTnet/ApplicationMessagePublisherExtensions.cs rename Source/MQTTnet/Client/Connecting/{MqttClientConnectResult.cs => MqttClientAuthenticateResult.cs} (79%) rename Source/MQTTnet/Internal/{TaskExtensions.cs => MqttTaskTimeout.cs} (84%) delete mode 100644 Source/MQTTnet/Server/IMqttClientSessionStatus.cs create mode 100644 Source/MQTTnet/Server/MqttClientConnection.cs delete mode 100644 Source/MQTTnet/Server/MqttClientSession.cs create mode 100644 Source/MQTTnet/Server/MqttClientSessionApplicationMessagesQueue.cs delete mode 100644 Source/MQTTnet/Server/MqttClientSessionPendingMessagesQueue.cs delete mode 100644 Source/MQTTnet/Server/MqttClientSessionStatus.cs create mode 100644 Source/MQTTnet/Server/Status/IMqttClientStatus.cs create mode 100644 Source/MQTTnet/Server/Status/IMqttSessionStatus.cs create mode 100644 Source/MQTTnet/Server/Status/MqttClientStatus.cs create mode 100644 Source/MQTTnet/Server/Status/MqttSessionStatus.cs rename Tests/MQTTnet.Core.Tests/MQTTv5/{MqttClientTests.cs => Client_Tests.cs} (95%) rename Tests/MQTTnet.Core.Tests/{TestSetup.cs => Mockups/TestEnvironment.cs} (88%) rename Tests/MQTTnet.Core.Tests/{ => Mockups}/TestMqttCommunicationAdapter.cs (67%) rename Tests/MQTTnet.Core.Tests/{ => Mockups}/TestMqttCommunicationAdapterFactory.cs (92%) rename Tests/MQTTnet.Core.Tests/{ => Mockups}/TestMqttServerAdapter.cs (98%) rename Tests/MQTTnet.Core.Tests/{ => Mockups}/TestServerExtensions.cs (97%) create mode 100644 Tests/MQTTnet.Core.Tests/Mockups/TestServerStorage.cs delete mode 100644 Tests/MQTTnet.Core.Tests/MqttServerTests.cs create mode 100644 Tests/MQTTnet.Core.Tests/Server_Status_Tests.cs create mode 100644 Tests/MQTTnet.Core.Tests/Server_Tests.cs diff --git a/Build/MQTTnet.nuspec b/Build/MQTTnet.nuspec index 564e47e0..2a8a763f 100644 --- a/Build/MQTTnet.nuspec +++ b/Build/MQTTnet.nuspec @@ -11,10 +11,13 @@ false MQTTnet is a high performance .NET library for MQTT based communication. It provides a MQTT client and a MQTT server (broker). * [Core] Added support for MQTTv5 packages. +* [Core] Performance improvements (removed several exceptions). * [Client] Added new MQTTv5 features to options builder. * [Client] Added uniform API across all supported MQTT versions (BREAKING CHANGE!) * [Client] The client will now avoid sending an ACK if an exception has been thrown in message handler (thanks to @ramonsmits). +* [Client] Fixed issues in QoS 2 handling which leads to message loss. * [Server] Added support for MQTTv5 clients. The server will still return _success_ for all cases at the moment even if more granular codes are available. +* [Server] Fixed issues in QoS 2 handling which leads to message loss. * [Note] Due to MQTTv5 a lot of new classes were introduced. This required adding new namespaces as well. Most classes are backward compatible but new namespaces must be added. diff --git a/README.md b/README.md index e0e0bc24..b74c1fbe 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ MQTTnet is a high performance .NET library for MQTT based communication. It prov * Uniform API across all supported versions of the MQTT protocol * Interfaces included for mocking and testing * Access to internal trace messages -* Unit tested (~120 tests) +* Unit tested (~130 tests) \* Tested on local machine (Intel i7 8700K) with MQTTnet client and server running in the same process using the TCP channel. The app for verification is part of this repository and stored in _/Tests/MQTTnet.TestApp.NetCore_. diff --git a/Source/MQTTnet.AspnetCore/MqttConnectionContext.cs b/Source/MQTTnet.AspnetCore/MqttConnectionContext.cs index b24f6e1d..999b340c 100644 --- a/Source/MQTTnet.AspnetCore/MqttConnectionContext.cs +++ b/Source/MQTTnet.AspnetCore/MqttConnectionContext.cs @@ -105,7 +105,7 @@ namespace MQTTnet.AspNetCore return null; } - public async Task SendPacketAsync(MqttBasePacket packet, CancellationToken cancellationToken) + public async Task SendPacketAsync(MqttBasePacket packet, TimeSpan timeout, CancellationToken cancellationToken) { var buffer = PacketFormatterAdapter.Encode(packet).AsMemory(); var output = Connection.Transport.Output; diff --git a/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClient.cs b/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClient.cs index 6fcbee9a..b4eec1c2 100644 --- a/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClient.cs +++ b/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClient.cs @@ -110,7 +110,7 @@ namespace MQTTnet.Extensions.ManagedClient return Task.FromResult(0); } - public async Task PublishAsync(MqttApplicationMessage applicationMessage) + public async Task PublishAsync(MqttApplicationMessage applicationMessage, CancellationToken cancellationToken) { if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage)); diff --git a/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClientExtensions.cs b/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClientExtensions.cs index ba5a16f7..9a635657 100644 --- a/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClientExtensions.cs +++ b/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClientExtensions.cs @@ -1,5 +1,8 @@ using System; +using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; +using MQTTnet.Client.Publishing; using MQTTnet.Protocol; namespace MQTTnet.Extensions.ManagedClient @@ -35,5 +38,89 @@ namespace MQTTnet.Extensions.ManagedClient return managedClient.UnsubscribeAsync(topicFilters); } + + public static async Task PublishAsync(this IManagedMqttClient client, IEnumerable applicationMessages) + { + if (client == null) throw new ArgumentNullException(nameof(client)); + if (applicationMessages == null) throw new ArgumentNullException(nameof(applicationMessages)); + + foreach (var applicationMessage in applicationMessages) + { + await client.PublishAsync(applicationMessage).ConfigureAwait(false); + } + } + + public static Task PublishAsync(this IManagedMqttClient client, MqttApplicationMessage applicationMessage) + { + if (client == null) throw new ArgumentNullException(nameof(client)); + if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage)); + + return client.PublishAsync(applicationMessage, CancellationToken.None); + } + + public static async Task PublishAsync(this IManagedMqttClient client, params MqttApplicationMessage[] applicationMessages) + { + if (client == null) throw new ArgumentNullException(nameof(client)); + if (applicationMessages == null) throw new ArgumentNullException(nameof(applicationMessages)); + + foreach (var applicationMessage in applicationMessages) + { + await client.PublishAsync(applicationMessage, CancellationToken.None).ConfigureAwait(false); + } + } + + public static Task PublishAsync(this IManagedMqttClient client, string topic) + { + if (client == null) throw new ArgumentNullException(nameof(client)); + if (topic == null) throw new ArgumentNullException(nameof(topic)); + + return client.PublishAsync(builder => builder + .WithTopic(topic)); + } + + public static Task PublishAsync(this IManagedMqttClient client, string topic, string payload) + { + if (client == null) throw new ArgumentNullException(nameof(client)); + if (topic == null) throw new ArgumentNullException(nameof(topic)); + + return client.PublishAsync(builder => builder + .WithTopic(topic) + .WithPayload(payload)); + } + + public static Task PublishAsync(this IManagedMqttClient client, string topic, string payload, MqttQualityOfServiceLevel qualityOfServiceLevel) + { + if (client == null) throw new ArgumentNullException(nameof(client)); + if (topic == null) throw new ArgumentNullException(nameof(topic)); + + return client.PublishAsync(builder => builder + .WithTopic(topic) + .WithPayload(payload) + .WithQualityOfServiceLevel(qualityOfServiceLevel)); + } + + public static Task PublishAsync(this IManagedMqttClient client, string topic, string payload, MqttQualityOfServiceLevel qualityOfServiceLevel, bool retain) + { + if (client == null) throw new ArgumentNullException(nameof(client)); + if (topic == null) throw new ArgumentNullException(nameof(topic)); + + return client.PublishAsync(builder => builder + .WithTopic(topic) + .WithPayload(payload) + .WithQualityOfServiceLevel(qualityOfServiceLevel) + .WithRetainFlag(retain)); + } + + public static Task PublishAsync(this IManagedMqttClient client, Func builder, CancellationToken cancellationToken) + { + var message = builder(new MqttApplicationMessageBuilder()).Build(); + return client.PublishAsync(message, cancellationToken); + } + + public static Task PublishAsync(this IManagedMqttClient client, Func builder) + { + var message = builder(new MqttApplicationMessageBuilder()).Build(); + return client.PublishAsync(message, CancellationToken.None); + } } } diff --git a/Source/MQTTnet/Adapter/IMqttChannelAdapter.cs b/Source/MQTTnet/Adapter/IMqttChannelAdapter.cs index 6310664b..f4993224 100644 --- a/Source/MQTTnet/Adapter/IMqttChannelAdapter.cs +++ b/Source/MQTTnet/Adapter/IMqttChannelAdapter.cs @@ -20,7 +20,7 @@ namespace MQTTnet.Adapter Task DisconnectAsync(TimeSpan timeout, CancellationToken cancellationToken); - Task SendPacketAsync(MqttBasePacket packet, CancellationToken cancellationToken); + Task SendPacketAsync(MqttBasePacket packet, TimeSpan timeout, CancellationToken cancellationToken); Task ReceivePacketAsync(TimeSpan timeout, CancellationToken cancellationToken); } diff --git a/Source/MQTTnet/Adapter/MqttChannelAdapter.cs b/Source/MQTTnet/Adapter/MqttChannelAdapter.cs index ded2a1fd..1bfcff02 100644 --- a/Source/MQTTnet/Adapter/MqttChannelAdapter.cs +++ b/Source/MQTTnet/Adapter/MqttChannelAdapter.cs @@ -53,11 +53,14 @@ namespace MQTTnet.Adapter try { - _logger.Verbose("Connecting [Timeout={0}]", timeout); - - await Internal.TaskExtensions - .TimeoutAfterAsync(ct => _channel.ConnectAsync(ct), timeout, cancellationToken) - .ConfigureAwait(false); + if (timeout == TimeSpan.Zero) + { + await _channel.ConnectAsync(cancellationToken).ConfigureAwait(false); + } + else + { + await MqttTaskTimeout.WaitAsync(t => _channel.ConnectAsync(t), timeout, cancellationToken).ConfigureAwait(false); + } } catch (Exception exception) { @@ -76,11 +79,15 @@ namespace MQTTnet.Adapter try { - _logger.Verbose("Disconnecting [Timeout={0}]", timeout); - - await Internal.TaskExtensions - .TimeoutAfterAsync(ct => _channel.DisconnectAsync(), timeout, cancellationToken) - .ConfigureAwait(false); + if (timeout == TimeSpan.Zero) + { + await _channel.DisconnectAsync(cancellationToken).ConfigureAwait(false); + } + else + { + await MqttTaskTimeout.WaitAsync( + t => _channel.DisconnectAsync(t), timeout, cancellationToken).ConfigureAwait(false); + } } catch (Exception exception) { @@ -93,13 +100,23 @@ namespace MQTTnet.Adapter } } - public async Task SendPacketAsync(MqttBasePacket packet, CancellationToken cancellationToken) + public async Task SendPacketAsync(MqttBasePacket packet, TimeSpan timeout, CancellationToken cancellationToken) { await _writerSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false); try { var packetData = PacketFormatterAdapter.Encode(packet); - await _channel.WriteAsync(packetData.Array, packetData.Offset, packetData.Count, cancellationToken).ConfigureAwait(false); + + if (timeout == TimeSpan.Zero) + { + await _channel.WriteAsync(packetData.Array, packetData.Offset, packetData.Count, cancellationToken).ConfigureAwait(false); + } + else + { + await MqttTaskTimeout.WaitAsync( + t => _channel.WriteAsync(packetData.Array, packetData.Offset, packetData.Count, t), timeout, cancellationToken).ConfigureAwait(false); + } + PacketFormatterAdapter.FreeBuffer(); _logger.Verbose("TX ({0} bytes) >>> {1}", packetData.Count, packet); @@ -126,14 +143,13 @@ namespace MQTTnet.Adapter try { ReceivedMqttPacket receivedMqttPacket; - - if (timeout > TimeSpan.Zero) + if (timeout == TimeSpan.Zero) { - receivedMqttPacket = await Internal.TaskExtensions.TimeoutAfterAsync(ReceiveAsync, timeout, cancellationToken).ConfigureAwait(false); + receivedMqttPacket = await ReceiveAsync(cancellationToken).ConfigureAwait(false); } else { - receivedMqttPacket = await ReceiveAsync(cancellationToken).ConfigureAwait(false); + receivedMqttPacket = await MqttTaskTimeout.WaitAsync(ReceiveAsync, timeout, cancellationToken).ConfigureAwait(false); } if (receivedMqttPacket == null || cancellationToken.IsCancellationRequested) diff --git a/Source/MQTTnet/ApplicationMessagePublisherExtensions.cs b/Source/MQTTnet/ApplicationMessagePublisherExtensions.cs deleted file mode 100644 index ea0b83b2..00000000 --- a/Source/MQTTnet/ApplicationMessagePublisherExtensions.cs +++ /dev/null @@ -1,81 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using MQTTnet.Client.Publishing; -using MQTTnet.Protocol; - -namespace MQTTnet -{ - public static class ApplicationMessagePublisherExtensions - { - public static async Task PublishAsync(this IApplicationMessagePublisher publisher, IEnumerable applicationMessages) - { - if (publisher == null) throw new ArgumentNullException(nameof(publisher)); - if (applicationMessages == null) throw new ArgumentNullException(nameof(applicationMessages)); - - foreach (var applicationMessage in applicationMessages) - { - await publisher.PublishAsync(applicationMessage).ConfigureAwait(false); - } - } - - public static async Task PublishAsync(this IApplicationMessagePublisher publisher, params MqttApplicationMessage[] applicationMessages) - { - if (publisher == null) throw new ArgumentNullException(nameof(publisher)); - if (applicationMessages == null) throw new ArgumentNullException(nameof(applicationMessages)); - - foreach (var applicationMessage in applicationMessages) - { - await publisher.PublishAsync(applicationMessage).ConfigureAwait(false); - } - } - - public static Task PublishAsync(this IApplicationMessagePublisher publisher, string topic) - { - if (publisher == null) throw new ArgumentNullException(nameof(publisher)); - if (topic == null) throw new ArgumentNullException(nameof(topic)); - - return publisher.PublishAsync(builder => builder - .WithTopic(topic)); - } - - public static Task PublishAsync(this IApplicationMessagePublisher publisher, string topic, string payload) - { - if (publisher == null) throw new ArgumentNullException(nameof(publisher)); - if (topic == null) throw new ArgumentNullException(nameof(topic)); - - return publisher.PublishAsync(builder => builder - .WithTopic(topic) - .WithPayload(payload)); - } - - public static Task PublishAsync(this IApplicationMessagePublisher publisher, string topic, string payload, MqttQualityOfServiceLevel qualityOfServiceLevel) - { - if (publisher == null) throw new ArgumentNullException(nameof(publisher)); - if (topic == null) throw new ArgumentNullException(nameof(topic)); - - return publisher.PublishAsync(builder => builder - .WithTopic(topic) - .WithPayload(payload) - .WithQualityOfServiceLevel(qualityOfServiceLevel)); - } - - public static Task PublishAsync(this IApplicationMessagePublisher publisher, string topic, string payload, MqttQualityOfServiceLevel qualityOfServiceLevel, bool retain) - { - if (publisher == null) throw new ArgumentNullException(nameof(publisher)); - if (topic == null) throw new ArgumentNullException(nameof(topic)); - - return publisher.PublishAsync(builder => builder - .WithTopic(topic) - .WithPayload(payload) - .WithQualityOfServiceLevel(qualityOfServiceLevel) - .WithRetainFlag(retain)); - } - - public static Task PublishAsync(this IApplicationMessagePublisher publisher, Func builder) - { - var message = builder(new MqttApplicationMessageBuilder()).Build(); - return publisher.PublishAsync(message); - } - } -} diff --git a/Source/MQTTnet/Channel/IMqttChannel.cs b/Source/MQTTnet/Channel/IMqttChannel.cs index 6050bcb1..4dcb6684 100644 --- a/Source/MQTTnet/Channel/IMqttChannel.cs +++ b/Source/MQTTnet/Channel/IMqttChannel.cs @@ -9,7 +9,7 @@ namespace MQTTnet.Channel string Endpoint { get; } Task ConnectAsync(CancellationToken cancellationToken); - Task DisconnectAsync(); + Task DisconnectAsync(CancellationToken cancellationToken); Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken); Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken); diff --git a/Source/MQTTnet/Client/Connecting/MqttClientConnectResult.cs b/Source/MQTTnet/Client/Connecting/MqttClientAuthenticateResult.cs similarity index 79% rename from Source/MQTTnet/Client/Connecting/MqttClientConnectResult.cs rename to Source/MQTTnet/Client/Connecting/MqttClientAuthenticateResult.cs index 05f53f0d..db6e5be5 100644 --- a/Source/MQTTnet/Client/Connecting/MqttClientConnectResult.cs +++ b/Source/MQTTnet/Client/Connecting/MqttClientAuthenticateResult.cs @@ -1,6 +1,6 @@ namespace MQTTnet.Client.Connecting { - public class MqttClientConnectResult + public class MqttClientAuthenticateResult { public bool IsSessionPresent { get; set; } diff --git a/Source/MQTTnet/Client/Connecting/MqttClientConnectedEventArgs.cs b/Source/MQTTnet/Client/Connecting/MqttClientConnectedEventArgs.cs index c052986a..e7f97b9e 100644 --- a/Source/MQTTnet/Client/Connecting/MqttClientConnectedEventArgs.cs +++ b/Source/MQTTnet/Client/Connecting/MqttClientConnectedEventArgs.cs @@ -4,11 +4,11 @@ namespace MQTTnet.Client.Connecting { public class MqttClientConnectedEventArgs : EventArgs { - public MqttClientConnectedEventArgs(MqttClientConnectResult connectResult) + public MqttClientConnectedEventArgs(MqttClientAuthenticateResult authenticateResult) { - ConnectResult = connectResult ?? throw new ArgumentNullException(nameof(connectResult)); + AuthenticateResult = authenticateResult ?? throw new ArgumentNullException(nameof(authenticateResult)); } - public MqttClientConnectResult ConnectResult { get; } + public MqttClientAuthenticateResult AuthenticateResult { get; } } } diff --git a/Source/MQTTnet/Client/IMqttClient.cs b/Source/MQTTnet/Client/IMqttClient.cs index 3c9ded36..0e00f009 100644 --- a/Source/MQTTnet/Client/IMqttClient.cs +++ b/Source/MQTTnet/Client/IMqttClient.cs @@ -1,4 +1,5 @@ using System; +using System.Threading; using System.Threading.Tasks; using MQTTnet.Client.Connecting; using MQTTnet.Client.Disconnecting; @@ -16,10 +17,10 @@ namespace MQTTnet.Client event EventHandler Connected; event EventHandler Disconnected; - Task ConnectAsync(IMqttClientOptions options); - Task DisconnectAsync(MqttClientDisconnectOptions options); + Task ConnectAsync(IMqttClientOptions options, CancellationToken cancellationToken); + Task DisconnectAsync(MqttClientDisconnectOptions options, CancellationToken cancellationToken); - Task SubscribeAsync(MqttClientSubscribeOptions options); - Task UnsubscribeAsync(MqttClientUnsubscribeOptions options); + Task SubscribeAsync(MqttClientSubscribeOptions options, CancellationToken cancellationToken); + Task UnsubscribeAsync(MqttClientUnsubscribeOptions options, CancellationToken cancellationToken); } } \ No newline at end of file diff --git a/Source/MQTTnet/Client/MqttClient.cs b/Source/MQTTnet/Client/MqttClient.cs index 90c5fa00..97cd3f25 100644 --- a/Source/MQTTnet/Client/MqttClient.cs +++ b/Source/MQTTnet/Client/MqttClient.cs @@ -12,7 +12,6 @@ using MQTTnet.Client.Subscribing; using MQTTnet.Client.Unsubscribing; using MQTTnet.Diagnostics; using MQTTnet.Exceptions; -using MQTTnet.Formatter; using MQTTnet.PacketDispatcher; using MQTTnet.Packets; using MQTTnet.Protocol; @@ -29,10 +28,9 @@ namespace MQTTnet.Client private readonly IMqttClientAdapterFactory _adapterFactory; private readonly IMqttNetChildLogger _logger; - private CancellationTokenSource _cancellationTokenSource; + private CancellationTokenSource _backgroundCancellationTokenSource; private Task _packetReceiverTask; private Task _keepAlivePacketsSenderTask; - private Task _backgroundWorkerTask; private IMqttChannelAdapter _adapter; private bool _cleanDisconnectInitiated; @@ -47,15 +45,18 @@ namespace MQTTnet.Client } public event EventHandler Connected; + public event EventHandler Disconnected; public IMqttApplicationMessageHandler ReceivedApplicationMessageHandler { get; set; } + public event EventHandler ApplicationMessageReceived; public bool IsConnected { get; private set; } + public IMqttClientOptions Options { get; private set; } - public async Task ConnectAsync(IMqttClientOptions options) + public async Task ConnectAsync(IMqttClientOptions options, CancellationToken cancellationToken) { if (options == null) throw new ArgumentNullException(nameof(options)); if (options.ChannelOptions == null) throw new ArgumentException("ChannelOptions are not set."); @@ -69,37 +70,34 @@ namespace MQTTnet.Client _packetIdentifierProvider.Reset(); _packetDispatcher.Reset(); - _cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = _cancellationTokenSource.Token; + _backgroundCancellationTokenSource = new CancellationTokenSource(); + var backgroundCancellationToken = _backgroundCancellationTokenSource.Token; _disconnectGate = 0; var adapter = _adapterFactory.CreateClientAdapter(options, _logger); _adapter = adapter; - _logger.Verbose($"Trying to connect with server ({Options.ChannelOptions})."); - await _adapter.ConnectAsync(Options.CommunicationTimeout, cancellationToken).ConfigureAwait(false); + _logger.Verbose($"Trying to connect with server '{options.ChannelOptions}' (Timeout={options.CommunicationTimeout})."); + await _adapter.ConnectAsync(options.CommunicationTimeout, cancellationToken).ConfigureAwait(false); _logger.Verbose("Connection with server established."); - _packetReceiverTask = Task.Run(() => TryReceivePacketsAsync(cancellationToken), cancellationToken); + _packetReceiverTask = Task.Run(() => TryReceivePacketsAsync(backgroundCancellationToken), backgroundCancellationToken); - var connectResult = await AuthenticateAsync(adapter, options.WillMessage, cancellationToken).ConfigureAwait(false); - _logger.Verbose("MQTT connection with server established."); + var authenticateResult = await AuthenticateAsync(adapter, options.WillMessage, cancellationToken).ConfigureAwait(false); _sendTracker.Restart(); if (Options.KeepAlivePeriod != TimeSpan.Zero) { - _keepAlivePacketsSenderTask = Task.Run(() => TrySendKeepAliveMessagesAsync(cancellationToken), cancellationToken); + _keepAlivePacketsSenderTask = Task.Run(() => TrySendKeepAliveMessagesAsync(backgroundCancellationToken), backgroundCancellationToken); } - _backgroundWorkerTask = Task.Run(() => TryProcessReceivedPacketsAsync(cancellationToken), cancellationToken); - IsConnected = true; - Connected?.Invoke(this, new MqttClientConnectedEventArgs(connectResult)); + Connected?.Invoke(this, new MqttClientConnectedEventArgs(authenticateResult)); _logger.Info("Connected."); - return connectResult; + return authenticateResult; } catch (Exception exception) { @@ -114,16 +112,16 @@ namespace MQTTnet.Client } } - public async Task DisconnectAsync(MqttClientDisconnectOptions options) + public async Task DisconnectAsync(MqttClientDisconnectOptions options, CancellationToken cancellationToken) { try { _cleanDisconnectInitiated = true; - if (IsConnected && _cancellationTokenSource?.IsCancellationRequested == false) + if (IsConnected) { - var disconnectPacket = CreateDisconnectPacket(options); - await SendAsync(disconnectPacket, _cancellationTokenSource.Token).ConfigureAwait(false); + var disconnectPacket = _adapter.PacketFormatterAdapter.DataConverter.CreateDisconnectPacket(options); + await SendAsync(disconnectPacket, cancellationToken).ConfigureAwait(false); } } finally @@ -135,7 +133,7 @@ namespace MQTTnet.Client } } - public async Task SubscribeAsync(MqttClientSubscribeOptions options) + public async Task SubscribeAsync(MqttClientSubscribeOptions options, CancellationToken cancellationToken) { if (options == null) throw new ArgumentNullException(nameof(options)); @@ -144,11 +142,11 @@ namespace MQTTnet.Client var subscribePacket = _adapter.PacketFormatterAdapter.DataConverter.CreateSubscribePacket(options); subscribePacket.PacketIdentifier = _packetIdentifierProvider.GetNextPacketIdentifier(); - var subAckPacket = await SendAndReceiveAsync(subscribePacket, _cancellationTokenSource.Token).ConfigureAwait(false); + var subAckPacket = await SendAndReceiveAsync(subscribePacket, cancellationToken).ConfigureAwait(false); return _adapter.PacketFormatterAdapter.DataConverter.CreateClientSubscribeResult(subscribePacket, subAckPacket); } - public async Task UnsubscribeAsync(MqttClientUnsubscribeOptions options) + public async Task UnsubscribeAsync(MqttClientUnsubscribeOptions options, CancellationToken cancellationToken) { if (options == null) throw new ArgumentNullException(nameof(options)); @@ -157,11 +155,11 @@ namespace MQTTnet.Client var unsubscribePacket = _adapter.PacketFormatterAdapter.DataConverter.CreateUnsubscribePacket(options); unsubscribePacket.PacketIdentifier = _packetIdentifierProvider.GetNextPacketIdentifier(); - var unsubAckPacket = await SendAndReceiveAsync(unsubscribePacket, _cancellationTokenSource.Token).ConfigureAwait(false); + var unsubAckPacket = await SendAndReceiveAsync(unsubscribePacket, cancellationToken).ConfigureAwait(false); return _adapter.PacketFormatterAdapter.DataConverter.CreateClientUnsubscribeResult(unsubscribePacket, unsubAckPacket); } - public async Task PublishAsync(MqttApplicationMessage applicationMessage) + public Task PublishAsync(MqttApplicationMessage applicationMessage, CancellationToken cancellationToken) { if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage)); @@ -173,26 +171,15 @@ namespace MQTTnet.Client { case MqttQualityOfServiceLevel.AtMostOnce: { - // No packet identifier is used for QoS 0 [3.3.2.2 Packet Identifier] - await SendAsync(publishPacket, _cancellationTokenSource.Token).ConfigureAwait(false); - return new MqttClientPublishResult(); + return PublishAtMostOnce(publishPacket, cancellationToken); } case MqttQualityOfServiceLevel.AtLeastOnce: { - publishPacket.PacketIdentifier = _packetIdentifierProvider.GetNextPacketIdentifier(); - var response = await SendAndReceiveAsync(publishPacket, _cancellationTokenSource.Token).ConfigureAwait(false); - - var result = new MqttClientPublishResult(); - if (response.ReasonCode != null) - { - result.ReasonCode = (MqttClientPublishReasonCode)response.ReasonCode; - } - - return result; + return PublishAtLeastOnceAsync(publishPacket, cancellationToken); } case MqttQualityOfServiceLevel.ExactlyOnce: { - return await PublishExactlyOnceAsync(publishPacket, _cancellationTokenSource.Token).ConfigureAwait(false); + return PublishExactlyOnceAsync(publishPacket, cancellationToken); } default: { @@ -203,15 +190,15 @@ namespace MQTTnet.Client public void Dispose() { - _cancellationTokenSource?.Cancel(false); - _cancellationTokenSource?.Dispose(); - _cancellationTokenSource = null; + _backgroundCancellationTokenSource?.Cancel(false); + _backgroundCancellationTokenSource?.Dispose(); + _backgroundCancellationTokenSource = null; _adapter?.Dispose(); _adapter = null; } - private async Task AuthenticateAsync(IMqttChannelAdapter channelAdapter, MqttApplicationMessage willApplicationMessage, CancellationToken cancellationToken) + private async Task AuthenticateAsync(IMqttChannelAdapter channelAdapter, MqttApplicationMessage willApplicationMessage, CancellationToken cancellationToken) { var connectPacket = channelAdapter.PacketFormatterAdapter.DataConverter.CreateConnectPacket( willApplicationMessage, @@ -225,6 +212,8 @@ namespace MQTTnet.Client throw new MqttConnectingFailedException(result.ResultCode); } + _logger.Verbose("Authenticated MQTT connection with server established."); + return result; } @@ -250,12 +239,12 @@ namespace MQTTnet.Client { if (_adapter != null) { + _logger.Verbose("Disconnecting [Timeout={0}]", Options.CommunicationTimeout); await _adapter.DisconnectAsync(Options.CommunicationTimeout, CancellationToken.None).ConfigureAwait(false); } await WaitForTaskAsync(_packetReceiverTask, sender).ConfigureAwait(false); await WaitForTaskAsync(_keepAlivePacketsSenderTask, sender).ConfigureAwait(false); - await WaitForTaskAsync(_backgroundWorkerTask, sender).ConfigureAwait(false); _logger.Verbose("Disconnected from adapter."); } @@ -279,12 +268,12 @@ namespace MQTTnet.Client { try { - if (_cancellationTokenSource?.IsCancellationRequested == true) + if (_backgroundCancellationTokenSource?.IsCancellationRequested == true) { return; } - _cancellationTokenSource?.Cancel(false); + _backgroundCancellationTokenSource?.Cancel(false); } catch (Exception exception) { @@ -295,11 +284,14 @@ namespace MQTTnet.Client private Task SendAsync(MqttBasePacket packet, CancellationToken cancellationToken) { - cancellationToken.ThrowIfCancellationRequested(); + if (cancellationToken.IsCancellationRequested) + { + return Task.FromResult(0); + } _sendTracker.Restart(); - return _adapter.SendPacketAsync(packet, cancellationToken); + return _adapter.SendPacketAsync(packet, Options.CommunicationTimeout, cancellationToken); } private async Task SendAndReceiveAsync(MqttBasePacket requestPacket, CancellationToken cancellationToken) where TResponsePacket : MqttBasePacket @@ -318,7 +310,7 @@ namespace MQTTnet.Client { try { - await _adapter.SendPacketAsync(requestPacket, cancellationToken).ConfigureAwait(false); + await _adapter.SendPacketAsync(requestPacket, Options.CommunicationTimeout, cancellationToken).ConfigureAwait(false); return await packetAwaiter.WaitOneAsync(Options.CommunicationTimeout).ConfigureAwait(false); } catch (MqttCommunicationTimedOutException) @@ -398,12 +390,17 @@ namespace MQTTnet.Client return; } - if (packet == null && !DisconnectIsPending()) + if (packet == null) { - await DisconnectInternalAsync(_packetReceiverTask, null).ConfigureAwait(false); + if (!DisconnectIsPending()) + { + await DisconnectInternalAsync(_packetReceiverTask, null).ConfigureAwait(false); + } + + return; } - _packetDispatcher.Dispatch(packet); + await TryProcessReceivedPacketAsync(packet, cancellationToken).ConfigureAwait(false); } } catch (Exception exception) @@ -446,17 +443,25 @@ namespace MQTTnet.Client { await TryProcessReceivedPublishPacketAsync(publishPacket, cancellationToken).ConfigureAwait(false); } + else if (packet is MqttPubRelPacket pubRelPacket) + { + await SendAsync(new MqttPubCompPacket + { + PacketIdentifier = pubRelPacket.PacketIdentifier, + ReasonCode = MqttPubCompReasonCode.Success + }, cancellationToken).ConfigureAwait(false); + } else if (packet is MqttPingReqPacket) { await SendAsync(new MqttPingRespPacket(), cancellationToken).ConfigureAwait(false); } else if (packet is MqttDisconnectPacket) { - await DisconnectAsync(null).ConfigureAwait(false); + await DisconnectAsync(null, cancellationToken).ConfigureAwait(false); } else { - throw new MqttProtocolViolationException($"Received packet '{packet}' at an unexpected time."); + _packetDispatcher.Dispatch(packet); } } catch (Exception exception) @@ -507,22 +512,15 @@ namespace MQTTnet.Client } else if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.ExactlyOnce) { + await HandleReceivedApplicationMessageAsync(publishPacket).ConfigureAwait(false); + var pubRecPacket = new MqttPubRecPacket { PacketIdentifier = publishPacket.PacketIdentifier, ReasonCode = MqttPubRecReasonCode.Success }; - var pubRelPacket = await SendAndReceiveAsync(pubRecPacket, cancellationToken).ConfigureAwait(false); - - // QoS 2 is implement as method "B" (4.3.3 QoS 2: Exactly once delivery) - await HandleReceivedApplicationMessageAsync(publishPacket).ConfigureAwait(false); - - await SendAsync(new MqttPubCompPacket - { - PacketIdentifier = pubRelPacket.PacketIdentifier, - ReasonCode = MqttPubCompReasonCode.Success - }, cancellationToken).ConfigureAwait(false); + await SendAsync(pubRecPacket, cancellationToken).ConfigureAwait(false); } else { @@ -535,29 +533,18 @@ namespace MQTTnet.Client } } - private async Task TryProcessReceivedPacketsAsync(CancellationToken cancellationToken) + private async Task PublishAtMostOnce(MqttPublishPacket publishPacket, CancellationToken cancellationToken) { - try - { - while (!cancellationToken.IsCancellationRequested) - { - var packet = _packetDispatcher.Take(cancellationToken); + // No packet identifier is used for QoS 0 [3.3.2.2 Packet Identifier] + await SendAsync(publishPacket, cancellationToken).ConfigureAwait(false); + return _adapter.PacketFormatterAdapter.DataConverter.CreatePublishResult(null); + } - if (cancellationToken.IsCancellationRequested) - { - return; - } - - await TryProcessReceivedPacketAsync(packet, cancellationToken).ConfigureAwait(false); - } - } - catch (OperationCanceledException) - { - } - catch (Exception exception) - { - _logger.Error(exception, "Error while processing packet."); - } + private async Task PublishAtLeastOnceAsync(MqttPublishPacket publishPacket, CancellationToken cancellationToken) + { + publishPacket.PacketIdentifier = _packetIdentifierProvider.GetNextPacketIdentifier(); + var response = await SendAndReceiveAsync(publishPacket, cancellationToken).ConfigureAwait(false); + return _adapter.PacketFormatterAdapter.DataConverter.CreatePublishResult(response); } private async Task PublishExactlyOnceAsync(MqttPublishPacket publishPacket, CancellationToken cancellationToken) @@ -566,8 +553,6 @@ namespace MQTTnet.Client var pubRecPacket = await SendAndReceiveAsync(publishPacket, cancellationToken).ConfigureAwait(false); - // TODO: Check response code. - var pubRelPacket = new MqttPubRelPacket { PacketIdentifier = publishPacket.PacketIdentifier, @@ -576,36 +561,9 @@ namespace MQTTnet.Client var pubCompPacket = await SendAndReceiveAsync(pubRelPacket, cancellationToken).ConfigureAwait(false); - // TODO: Check response code. - - var result = new MqttClientPublishResult(); - - if (pubRecPacket.ReasonCode != null) - { - result.ReasonCode = (MqttClientPublishReasonCode)pubRecPacket.ReasonCode; - } - - return result; + return _adapter.PacketFormatterAdapter.DataConverter.CreatePublishResult(pubRecPacket, pubCompPacket); } - ////private void StartReceivingPackets(CancellationToken cancellationToken) - ////{ - //// _packetReceiverTask = Task.Factory.StartNew( - //// () => TryReceivePacketsAsync(cancellationToken), - //// cancellationToken, - //// TaskCreationOptions.LongRunning, - //// TaskScheduler.Default).Unwrap(); - ////} - - ////private void StartSendingKeepAliveMessages(CancellationToken cancellationToken) - ////{ - //// _keepAlivePacketsSenderTask = Task.Factory.StartNew( - //// () => TrySendKeepAliveMessagesAsync(cancellationToken), - //// cancellationToken, - //// TaskCreationOptions.LongRunning, - //// TaskScheduler.Default).Unwrap(); - ////} - private Task HandleReceivedApplicationMessageAsync(MqttPublishPacket publishPacket) { var applicationMessage = _adapter.PacketFormatterAdapter.DataConverter.CreateApplicationMessage(publishPacket); @@ -647,31 +605,5 @@ namespace MQTTnet.Client { return Interlocked.CompareExchange(ref _disconnectGate, 1, 0) != 0; } - - private MqttDisconnectPacket CreateDisconnectPacket(MqttClientDisconnectOptions options) - { - var packet = new MqttDisconnectPacket(); - - if (_adapter.PacketFormatterAdapter.ProtocolVersion == MqttProtocolVersion.V500) - { - if (options == null) - { - packet.ReasonCode = MqttDisconnectReasonCode.NormalDisconnection; - } - else - { - packet.ReasonCode = (MqttDisconnectReasonCode)options.ReasonCode; - } - } - else - { - if (options != null) - { - throw new MqttProtocolViolationException("Reason codes for disconnect are only supported for MQTTv5."); - } - } - - return packet; - } } } diff --git a/Source/MQTTnet/Client/MqttClientExtensions.cs b/Source/MQTTnet/Client/MqttClientExtensions.cs index ef6f5ab4..752763dd 100644 --- a/Source/MQTTnet/Client/MqttClientExtensions.cs +++ b/Source/MQTTnet/Client/MqttClientExtensions.cs @@ -1,5 +1,11 @@ using System; +using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; +using MQTTnet.Client.Connecting; +using MQTTnet.Client.Disconnecting; +using MQTTnet.Client.Options; +using MQTTnet.Client.Publishing; using MQTTnet.Client.Receiving; using MQTTnet.Client.Subscribing; using MQTTnet.Client.Unsubscribing; @@ -98,5 +104,113 @@ namespace MQTTnet.Client return client.UnsubscribeAsync(options); } + + public static Task ConnectAsync(this IMqttClient client, IMqttClientOptions options) + { + return client.ConnectAsync(options, CancellationToken.None); + } + + public static Task DisconnectAsync(this IMqttClient client, MqttClientDisconnectOptions options) + { + return client.DisconnectAsync(options, CancellationToken.None); + } + + public static Task SubscribeAsync(this IMqttClient client, MqttClientSubscribeOptions options) + { + return client.SubscribeAsync(options, CancellationToken.None); + } + + public static Task UnsubscribeAsync(this IMqttClient client, MqttClientUnsubscribeOptions options) + { + return client.UnsubscribeAsync(options, CancellationToken.None); + } + + public static Task PublishAsync(this IMqttClient client, MqttApplicationMessage applicationMessage) + { + if (client == null) throw new ArgumentNullException(nameof(client)); + if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage)); + + return client.PublishAsync(applicationMessage, CancellationToken.None); + } + + public static async Task PublishAsync(this IMqttClient client, IEnumerable applicationMessages) + { + if (client == null) throw new ArgumentNullException(nameof(client)); + if (applicationMessages == null) throw new ArgumentNullException(nameof(applicationMessages)); + + foreach (var applicationMessage in applicationMessages) + { + await client.PublishAsync(applicationMessage).ConfigureAwait(false); + } + } + + public static Task PublishAsync(this IMqttClient client, string topic) + { + if (client == null) throw new ArgumentNullException(nameof(client)); + if (topic == null) throw new ArgumentNullException(nameof(topic)); + + return client.PublishAsync(new MqttApplicationMessageBuilder() + .WithTopic(topic) + .Build()); + } + + public static Task PublishAsync(this IMqttClient client, string topic, IEnumerable payload) + { + if (client == null) throw new ArgumentNullException(nameof(client)); + if (topic == null) throw new ArgumentNullException(nameof(topic)); + + return client.PublishAsync(new MqttApplicationMessageBuilder() + .WithTopic(topic) + .WithPayload(payload) + .Build()); + } + + public static Task PublishAsync(this IMqttClient client, string topic, string payload) + { + if (client == null) throw new ArgumentNullException(nameof(client)); + if (topic == null) throw new ArgumentNullException(nameof(topic)); + + return client.PublishAsync(new MqttApplicationMessageBuilder() + .WithTopic(topic) + .WithPayload(payload) + .Build()); + } + + public static Task PublishAsync(this IMqttClient client, string topic, string payload, MqttQualityOfServiceLevel qualityOfServiceLevel) + { + if (client == null) throw new ArgumentNullException(nameof(client)); + if (topic == null) throw new ArgumentNullException(nameof(topic)); + + return client.PublishAsync(new MqttApplicationMessageBuilder() + .WithTopic(topic) + .WithPayload(payload) + .WithQualityOfServiceLevel(qualityOfServiceLevel) + .Build()); + } + + public static Task PublishAsync(this IMqttClient client, string topic, string payload, MqttQualityOfServiceLevel qualityOfServiceLevel, bool retain) + { + if (client == null) throw new ArgumentNullException(nameof(client)); + if (topic == null) throw new ArgumentNullException(nameof(topic)); + + return client.PublishAsync(new MqttApplicationMessageBuilder() + .WithTopic(topic) + .WithPayload(payload) + .WithQualityOfServiceLevel(qualityOfServiceLevel) + .WithRetainFlag(retain) + .Build()); + } + + public static Task PublishAsync(this IMqttClient client, string topic, string payload, bool retain) + { + if (client == null) throw new ArgumentNullException(nameof(client)); + if (topic == null) throw new ArgumentNullException(nameof(topic)); + + return client.PublishAsync(new MqttApplicationMessageBuilder() + .WithTopic(topic) + .WithPayload(payload) + .WithRetainFlag(retain) + .Build()); + } } } \ No newline at end of file diff --git a/Source/MQTTnet/Client/Options/MqttClientOptionsBuilder.cs b/Source/MQTTnet/Client/Options/MqttClientOptionsBuilder.cs index 832c8663..e3780c78 100644 --- a/Source/MQTTnet/Client/Options/MqttClientOptionsBuilder.cs +++ b/Source/MQTTnet/Client/Options/MqttClientOptionsBuilder.cs @@ -31,6 +31,11 @@ namespace MQTTnet.Client.Options return this; } + public MqttClientOptionsBuilder WithNoKeepAlive() + { + return WithKeepAlivePeriod(TimeSpan.Zero); + } + public MqttClientOptionsBuilder WithKeepAlivePeriod(TimeSpan value) { _options.KeepAlivePeriod = value; diff --git a/Source/MQTTnet/Formatter/IMqttDataConverter.cs b/Source/MQTTnet/Formatter/IMqttDataConverter.cs index 19c2a282..5b347e52 100644 --- a/Source/MQTTnet/Formatter/IMqttDataConverter.cs +++ b/Source/MQTTnet/Formatter/IMqttDataConverter.cs @@ -1,5 +1,7 @@ using MQTTnet.Client.Connecting; +using MQTTnet.Client.Disconnecting; using MQTTnet.Client.Options; +using MQTTnet.Client.Publishing; using MQTTnet.Client.Subscribing; using MQTTnet.Client.Unsubscribing; using MQTTnet.Packets; @@ -12,7 +14,7 @@ namespace MQTTnet.Formatter MqttApplicationMessage CreateApplicationMessage(MqttPublishPacket publishPacket); - MqttClientConnectResult CreateClientConnectResult(MqttConnAckPacket connAckPacket); + MqttClientAuthenticateResult CreateClientConnectResult(MqttConnAckPacket connAckPacket); MqttConnectPacket CreateConnectPacket(MqttApplicationMessage willApplicationMessage, IMqttClientOptions options); @@ -23,5 +25,11 @@ namespace MQTTnet.Formatter MqttSubscribePacket CreateSubscribePacket(MqttClientSubscribeOptions options); MqttUnsubscribePacket CreateUnsubscribePacket(MqttClientUnsubscribeOptions options); + + MqttDisconnectPacket CreateDisconnectPacket(MqttClientDisconnectOptions options); + + MqttClientPublishResult CreatePublishResult(MqttPubAckPacket pubAckPacket); + + MqttClientPublishResult CreatePublishResult(MqttPubRecPacket pubRecPacket, MqttPubCompPacket pubCompPacket); } } diff --git a/Source/MQTTnet/Formatter/V3/MqttV310DataConverter.cs b/Source/MQTTnet/Formatter/V3/MqttV310DataConverter.cs index 1fad36b8..49c25a0c 100644 --- a/Source/MQTTnet/Formatter/V3/MqttV310DataConverter.cs +++ b/Source/MQTTnet/Formatter/V3/MqttV310DataConverter.cs @@ -1,7 +1,9 @@ using System; using System.Linq; using MQTTnet.Client.Connecting; +using MQTTnet.Client.Disconnecting; using MQTTnet.Client.Options; +using MQTTnet.Client.Publishing; using MQTTnet.Client.Subscribing; using MQTTnet.Client.Unsubscribing; using MQTTnet.Exceptions; @@ -44,7 +46,7 @@ namespace MQTTnet.Formatter.V3 }; } - public MqttClientConnectResult CreateClientConnectResult(MqttConnAckPacket connAckPacket) + public MqttClientAuthenticateResult CreateClientConnectResult(MqttConnAckPacket connAckPacket) { if (connAckPacket == null) throw new ArgumentNullException(nameof(connAckPacket)); @@ -91,7 +93,7 @@ namespace MQTTnet.Formatter.V3 throw new MqttProtocolViolationException("Received unexpected return code."); } - return new MqttClientConnectResult + return new MqttClientAuthenticateResult { IsSessionPresent = connAckPacket.IsSessionPresent, ResultCode = resultCode @@ -173,5 +175,39 @@ namespace MQTTnet.Formatter.V3 return unsubscribePacket; } + + public MqttDisconnectPacket CreateDisconnectPacket(MqttClientDisconnectOptions options) + { + if (options != null) + { + throw new MqttProtocolViolationException("Reason codes for disconnect are only supported for MQTTv5."); + } + + return new MqttDisconnectPacket(); + } + + public MqttClientPublishResult CreatePublishResult(MqttPubAckPacket pubAckPacket) + { + return new MqttClientPublishResult + { + ReasonCode = MqttClientPublishReasonCode.Success + }; + } + + public MqttClientPublishResult CreatePublishResult(MqttPubRecPacket pubRecPacket, MqttPubCompPacket pubCompPacket) + { + if (pubRecPacket == null || pubCompPacket == null) + { + return new MqttClientPublishResult + { + ReasonCode = MqttClientPublishReasonCode.UnspecifiedError + }; + } + + return new MqttClientPublishResult + { + ReasonCode = MqttClientPublishReasonCode.Success + }; + } } } diff --git a/Source/MQTTnet/Formatter/V5/MqttV500DataConverter.cs b/Source/MQTTnet/Formatter/V5/MqttV500DataConverter.cs index b5522e1e..e3bcb4ed 100644 --- a/Source/MQTTnet/Formatter/V5/MqttV500DataConverter.cs +++ b/Source/MQTTnet/Formatter/V5/MqttV500DataConverter.cs @@ -2,11 +2,14 @@ using System.Collections.Generic; using System.Linq; using MQTTnet.Client.Connecting; +using MQTTnet.Client.Disconnecting; using MQTTnet.Client.Options; +using MQTTnet.Client.Publishing; using MQTTnet.Client.Subscribing; using MQTTnet.Client.Unsubscribing; using MQTTnet.Exceptions; using MQTTnet.Packets; +using MQTTnet.Protocol; namespace MQTTnet.Formatter.V5 { @@ -55,11 +58,11 @@ namespace MQTTnet.Formatter.V5 }; } - public MqttClientConnectResult CreateClientConnectResult(MqttConnAckPacket connAckPacket) + public MqttClientAuthenticateResult CreateClientConnectResult(MqttConnAckPacket connAckPacket) { if (connAckPacket == null) throw new ArgumentNullException(nameof(connAckPacket)); - return new MqttClientConnectResult + return new MqttClientAuthenticateResult { IsSessionPresent = connAckPacket.IsSessionPresent, ResultCode = (MqttClientConnectResultCode)connAckPacket.ReasonCode.Value @@ -158,5 +161,65 @@ namespace MQTTnet.Formatter.V5 return packet; } + + public MqttDisconnectPacket CreateDisconnectPacket(MqttClientDisconnectOptions options) + { + var packet = new MqttDisconnectPacket(); + + if (options == null) + { + packet.ReasonCode = MqttDisconnectReasonCode.NormalDisconnection; + } + else + { + packet.ReasonCode = (MqttDisconnectReasonCode)options.ReasonCode; + } + + return packet; + } + + public MqttClientPublishResult CreatePublishResult(MqttPubAckPacket pubAckPacket) + { + var result = new MqttClientPublishResult(); + + if (pubAckPacket?.ReasonCode != null) + { + result.ReasonCode = (MqttClientPublishReasonCode)pubAckPacket.ReasonCode; + } + + return result; + } + + public MqttClientPublishResult CreatePublishResult(MqttPubRecPacket pubRecPacket, MqttPubCompPacket pubCompPacket) + { + if (pubRecPacket == null || pubCompPacket == null) + { + return new MqttClientPublishResult + { + ReasonCode = MqttClientPublishReasonCode.UnspecifiedError + }; + } + + if (pubCompPacket.ReasonCode == MqttPubCompReasonCode.PacketIdentifierNotFound) + { + return new MqttClientPublishResult + { + ReasonCode = MqttClientPublishReasonCode.UnspecifiedError + }; + } + + var result = new MqttClientPublishResult + { + ReasonCode = MqttClientPublishReasonCode.Success + }; + + if (pubRecPacket.ReasonCode.HasValue) + { + // Both enums share the same values. + result.ReasonCode = (MqttClientPublishReasonCode)pubRecPacket.ReasonCode.Value; + } + + return result; + } } } diff --git a/Source/MQTTnet/IApplicationMessagePublisher.cs b/Source/MQTTnet/IApplicationMessagePublisher.cs index a7c0d4a7..bf17718c 100644 --- a/Source/MQTTnet/IApplicationMessagePublisher.cs +++ b/Source/MQTTnet/IApplicationMessagePublisher.cs @@ -1,10 +1,11 @@ -using System.Threading.Tasks; +using System.Threading; +using System.Threading.Tasks; using MQTTnet.Client.Publishing; namespace MQTTnet { public interface IApplicationMessagePublisher { - Task PublishAsync(MqttApplicationMessage applicationMessage); + Task PublishAsync(MqttApplicationMessage applicationMessage, CancellationToken cancellationToken); } } diff --git a/Source/MQTTnet/Implementations/MqttTcpChannel.Uwp.cs b/Source/MQTTnet/Implementations/MqttTcpChannel.Uwp.cs index fa20b08d..ea5d6ccf 100644 --- a/Source/MQTTnet/Implementations/MqttTcpChannel.Uwp.cs +++ b/Source/MQTTnet/Implementations/MqttTcpChannel.Uwp.cs @@ -81,7 +81,7 @@ namespace MQTTnet.Implementations CreateStreams(); } - public Task DisconnectAsync() + public Task DisconnectAsync(CancellationToken cancellationToken) { Dispose(); return Task.FromResult(0); diff --git a/Source/MQTTnet/Implementations/MqttTcpChannel.cs b/Source/MQTTnet/Implementations/MqttTcpChannel.cs index 802463ad..4a178855 100644 --- a/Source/MQTTnet/Implementations/MqttTcpChannel.cs +++ b/Source/MQTTnet/Implementations/MqttTcpChannel.cs @@ -72,7 +72,7 @@ namespace MQTTnet.Implementations CreateStream(sslStream); } - public Task DisconnectAsync() + public Task DisconnectAsync(CancellationToken cancellationToken) { Dispose(); return Task.FromResult(0); @@ -81,8 +81,13 @@ namespace MQTTnet.Implementations public async Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) { // Workaround for: https://github.com/dotnet/corefx/issues/24430 - using (cancellationToken.Register(() => _socket.Dispose())) + using (cancellationToken.Register(Dispose)) { + if (cancellationToken.IsCancellationRequested) + { + return 0; + } + return await _stream.ReadAsync(buffer, offset, count, cancellationToken).ConfigureAwait(false); } } @@ -90,8 +95,13 @@ namespace MQTTnet.Implementations public async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) { // Workaround for: https://github.com/dotnet/corefx/issues/24430 - using (cancellationToken.Register(() => _socket.Dispose())) + using (cancellationToken.Register(Dispose)) { + if (cancellationToken.IsCancellationRequested) + { + return; + } + await _stream.WriteAsync(buffer, offset, count, cancellationToken).ConfigureAwait(false); //await _stream.FlushAsync(cancellationToken); } @@ -99,16 +109,21 @@ namespace MQTTnet.Implementations public void Dispose() { - Cleanup(ref _stream, s => s.Dispose()); - Cleanup(ref _socket, s => - { - //if (s.Connected) - //{ - // s.Shutdown(SocketShutdown.Both); - //} + _socket = null; - s.Dispose(); - }); + // When the stream is disposed it will also close the socket and this will also dispose it. + // So there is no need to dispose the socket again. + // https://stackoverflow.com/questions/3601521/should-i-manually-dispose-the-socket-after-closing-it + try + { + _stream?.Dispose(); + } + catch (ObjectDisposedException) + { + } + catch (NullReferenceException) + { + } } private bool InternalUserCertificateValidationCallback(object sender, X509Certificate x509Certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) diff --git a/Source/MQTTnet/Implementations/MqttTcpServerListener.cs b/Source/MQTTnet/Implementations/MqttTcpServerListener.cs index 38036894..c550ebb0 100644 --- a/Source/MQTTnet/Implementations/MqttTcpServerListener.cs +++ b/Source/MQTTnet/Implementations/MqttTcpServerListener.cs @@ -3,7 +3,6 @@ using System; using System.Net; using System.Net.Security; using System.Net.Sockets; -using System.Security.Authentication; using System.Security.Cryptography.X509Certificates; using System.Threading; using System.Threading.Tasks; diff --git a/Source/MQTTnet/Implementations/MqttWebSocketChannel.cs b/Source/MQTTnet/Implementations/MqttWebSocketChannel.cs index c6926dd8..6240f25f 100644 --- a/Source/MQTTnet/Implementations/MqttWebSocketChannel.cs +++ b/Source/MQTTnet/Implementations/MqttWebSocketChannel.cs @@ -85,7 +85,7 @@ namespace MQTTnet.Implementations _webSocket = clientWebSocket; } - public async Task DisconnectAsync() + public async Task DisconnectAsync(CancellationToken cancellationToken) { if (_webSocket == null) { @@ -94,7 +94,7 @@ namespace MQTTnet.Implementations if (_webSocket.State == WebSocketState.Open || _webSocket.State == WebSocketState.Connecting) { - await _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None).ConfigureAwait(false); + await _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, cancellationToken).ConfigureAwait(false); } Dispose(); diff --git a/Source/MQTTnet/Internal/TaskExtensions.cs b/Source/MQTTnet/Internal/MqttTaskTimeout.cs similarity index 84% rename from Source/MQTTnet/Internal/TaskExtensions.cs rename to Source/MQTTnet/Internal/MqttTaskTimeout.cs index 1356d97d..ba4ec7fd 100644 --- a/Source/MQTTnet/Internal/TaskExtensions.cs +++ b/Source/MQTTnet/Internal/MqttTaskTimeout.cs @@ -5,9 +5,9 @@ using MQTTnet.Exceptions; namespace MQTTnet.Internal { - public static class TaskExtensions + public static class MqttTaskTimeout { - public static async Task TimeoutAfterAsync(Func action, TimeSpan timeout, CancellationToken cancellationToken) + public static async Task WaitAsync(Func action, TimeSpan timeout, CancellationToken cancellationToken) { if (action == null) throw new ArgumentNullException(nameof(action)); @@ -31,7 +31,7 @@ namespace MQTTnet.Internal } } - public static async Task TimeoutAfterAsync(Func> action, TimeSpan timeout, CancellationToken cancellationToken) + public static async Task WaitAsync(Func> action, TimeSpan timeout, CancellationToken cancellationToken) { if (action == null) throw new ArgumentNullException(nameof(action)); diff --git a/Source/MQTTnet/Internal/TestMqttChannel.cs b/Source/MQTTnet/Internal/TestMqttChannel.cs index 08920e3c..03f23b14 100644 --- a/Source/MQTTnet/Internal/TestMqttChannel.cs +++ b/Source/MQTTnet/Internal/TestMqttChannel.cs @@ -21,7 +21,7 @@ namespace MQTTnet.Internal return Task.FromResult(0); } - public Task DisconnectAsync() + public Task DisconnectAsync(CancellationToken cancellationToken) { return Task.FromResult(0); } diff --git a/Source/MQTTnet/MqttApplicationMessageBuilder.cs b/Source/MQTTnet/MqttApplicationMessageBuilder.cs index 1df86450..ffd9031e 100644 --- a/Source/MQTTnet/MqttApplicationMessageBuilder.cs +++ b/Source/MQTTnet/MqttApplicationMessageBuilder.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; @@ -38,12 +39,24 @@ namespace MQTTnet return this; } - _payload = payload.ToArray(); + _payload = payload as byte[]; + + if (_payload == null) + { + _payload = payload.ToArray(); + } + return this; } public MqttApplicationMessageBuilder WithPayload(Stream payload) { + if (payload == null) + { + _payload = null; + return this; + } + return WithPayload(payload, payload.Length - payload.Position); } diff --git a/Source/MQTTnet/PacketDispatcher/MqttPacketAwaiter.cs b/Source/MQTTnet/PacketDispatcher/MqttPacketAwaiter.cs index c30a6f45..19df6d45 100644 --- a/Source/MQTTnet/PacketDispatcher/MqttPacketAwaiter.cs +++ b/Source/MQTTnet/PacketDispatcher/MqttPacketAwaiter.cs @@ -1,6 +1,7 @@ using System; using System.Threading; using System.Threading.Tasks; +using MQTTnet.Exceptions; using MQTTnet.Packets; namespace MQTTnet.PacketDispatcher @@ -21,7 +22,7 @@ namespace MQTTnet.PacketDispatcher { using (var timeoutToken = new CancellationTokenSource(timeout)) { - timeoutToken.Token.Register(() => _taskCompletionSource.TrySetCanceled()); + timeoutToken.Token.Register(() => _taskCompletionSource.TrySetException(new MqttCommunicationTimedOutException())); var packet = await _taskCompletionSource.Task.ConfigureAwait(false); return (TPacket)packet; diff --git a/Source/MQTTnet/PacketDispatcher/MqttPacketDispatcher.cs b/Source/MQTTnet/PacketDispatcher/MqttPacketDispatcher.cs index b4a26c2f..26b36ec1 100644 --- a/Source/MQTTnet/PacketDispatcher/MqttPacketDispatcher.cs +++ b/Source/MQTTnet/PacketDispatcher/MqttPacketDispatcher.cs @@ -1,6 +1,6 @@ using System; using System.Collections.Concurrent; -using System.Threading; +using MQTTnet.Exceptions; using MQTTnet.Packets; namespace MQTTnet.PacketDispatcher @@ -9,8 +9,6 @@ namespace MQTTnet.PacketDispatcher { private readonly ConcurrentDictionary, IMqttPacketAwaiter> _packetAwaiters = new ConcurrentDictionary, IMqttPacketAwaiter>(); - private BlockingCollection _inboundPackagesQueue = new BlockingCollection(); - public void Dispatch(Exception exception) { foreach (var awaiter in _packetAwaiters) @@ -40,34 +38,14 @@ namespace MQTTnet.PacketDispatcher return; } - lock (_inboundPackagesQueue) - { - _inboundPackagesQueue.Add(packet); - } - } - - public MqttBasePacket Take(CancellationToken cancellationToken) - { - BlockingCollection inboundPackagesQueue; - lock (_inboundPackagesQueue) - { - inboundPackagesQueue = _inboundPackagesQueue; - } - - return inboundPackagesQueue.Take(cancellationToken); + throw new MqttProtocolViolationException($"Received packet '{packet}' at an unexpected time."); } public void Reset() { foreach (var awaiter in _packetAwaiters) { - awaiter.Value.Cancel(); - } - - lock (_inboundPackagesQueue) - { - _inboundPackagesQueue?.Dispose(); - _inboundPackagesQueue = new BlockingCollection(); + awaiter.Value.Cancel(); } _packetAwaiters.Clear(); diff --git a/Source/MQTTnet/Server/IMqttClientSession.cs b/Source/MQTTnet/Server/IMqttClientSession.cs index f1e3010e..d7db2b13 100644 --- a/Source/MQTTnet/Server/IMqttClientSession.cs +++ b/Source/MQTTnet/Server/IMqttClientSession.cs @@ -1,12 +1,11 @@ -using System; -using System.Threading.Tasks; +using System.Threading.Tasks; namespace MQTTnet.Server { - public interface IMqttClientSession : IDisposable + public interface IMqttClientSession { string ClientId { get; } - Task StopAsync(MqttClientDisconnectType disconnectType); + Task StopAsync(); } } \ No newline at end of file diff --git a/Source/MQTTnet/Server/IMqttClientSessionStatus.cs b/Source/MQTTnet/Server/IMqttClientSessionStatus.cs deleted file mode 100644 index d46db699..00000000 --- a/Source/MQTTnet/Server/IMqttClientSessionStatus.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System; -using System.Threading.Tasks; -using MQTTnet.Formatter; - -namespace MQTTnet.Server -{ - public interface IMqttClientSessionStatus - { - string ClientId { get; } - - string Endpoint { get; } - - bool IsConnected { get; } - - MqttProtocolVersion? ProtocolVersion { get; } - - TimeSpan LastPacketReceived { get; } - - TimeSpan LastNonKeepAlivePacketReceived { get; } - - long PendingApplicationMessagesCount { get; } - - long ReceivedApplicationMessagesCount { get; } - - long SentApplicationMessagesCount { get; } - - Task DisconnectAsync(); - - Task DeleteSessionAsync(); - - Task ClearPendingApplicationMessagesAsync(); - } -} diff --git a/Source/MQTTnet/Server/IMqttServer.cs b/Source/MQTTnet/Server/IMqttServer.cs index cccda2b0..aceceeee 100644 --- a/Source/MQTTnet/Server/IMqttServer.cs +++ b/Source/MQTTnet/Server/IMqttServer.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; +using MQTTnet.Server.Status; namespace MQTTnet.Server { @@ -16,13 +17,14 @@ namespace MQTTnet.Server IMqttServerOptions Options { get; } - Task> GetClientSessionsStatusAsync(); + Task> GetClientStatusAsync(); + Task> GetSessionStatusAsync(); - IList GetRetainedMessages(); + Task> GetRetainedMessagesAsync(); Task ClearRetainedMessagesAsync(); - Task SubscribeAsync(string clientId, IEnumerable topicFilters); - Task UnsubscribeAsync(string clientId, IEnumerable topicFilters); + Task SubscribeAsync(string clientId, ICollection topicFilters); + Task UnsubscribeAsync(string clientId, ICollection topicFilters); Task StartAsync(IMqttServerOptions options); Task StopAsync(); diff --git a/Source/MQTTnet/Server/IMqttServerConnectionValidator.cs b/Source/MQTTnet/Server/IMqttServerConnectionValidator.cs index bd38c831..68b5be6f 100644 --- a/Source/MQTTnet/Server/IMqttServerConnectionValidator.cs +++ b/Source/MQTTnet/Server/IMqttServerConnectionValidator.cs @@ -4,6 +4,6 @@ namespace MQTTnet.Server { public interface IMqttServerConnectionValidator { - Task ValidateConnection(MqttConnectionValidatorContext context); + Task ValidateConnectionAsync(MqttConnectionValidatorContext context); } } diff --git a/Source/MQTTnet/Server/MqttClientConnection.cs b/Source/MQTTnet/Server/MqttClientConnection.cs new file mode 100644 index 00000000..fa1d6a05 --- /dev/null +++ b/Source/MQTTnet/Server/MqttClientConnection.cs @@ -0,0 +1,568 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using MQTTnet.Adapter; +using MQTTnet.Client; +using MQTTnet.Diagnostics; +using MQTTnet.Exceptions; +using MQTTnet.Formatter; +using MQTTnet.PacketDispatcher; +using MQTTnet.Packets; +using MQTTnet.Protocol; +using MQTTnet.Server.Status; + +namespace MQTTnet.Server +{ + public class MqttClientSession + { + private readonly DateTime _createdTimestamp = DateTime.UtcNow; + + public MqttClientSession(string clientId, MqttServerEventDispatcher eventDispatcher, IMqttServerOptions serverOptions) + { + ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId)); + + SubscriptionsManager = new MqttClientSubscriptionsManager(clientId, eventDispatcher, serverOptions); + ApplicationMessagesQueue = new MqttClientSessionApplicationMessagesQueue(serverOptions); + } + + public string ClientId { get; } + + public bool IsCleanSession { get; set; } = true; + + public MqttApplicationMessage WillMessage { get; set; } + + public MqttClientSubscriptionsManager SubscriptionsManager { get; } + + public MqttClientSessionApplicationMessagesQueue ApplicationMessagesQueue { get; } + + public void EnqueueApplicationMessage(MqttApplicationMessage applicationMessage, string senderClientId, bool isRetainedApplicationMessage) + { + var checkSubscriptionsResult = SubscriptionsManager.CheckSubscriptions(applicationMessage.Topic, applicationMessage.QualityOfServiceLevel); + if (!checkSubscriptionsResult.IsSubscribed) + { + return; + } + + ApplicationMessagesQueue.Enqueue(applicationMessage, senderClientId, checkSubscriptionsResult.QualityOfServiceLevel, isRetainedApplicationMessage); + } + + public async Task SubscribeAsync(ICollection topicFilters, MqttRetainedMessagesManager retainedMessagesManager) + { + await SubscriptionsManager.SubscribeAsync(topicFilters).ConfigureAwait(false); + var matchingRetainedMessages = await retainedMessagesManager.GetSubscribedMessagesAsync(topicFilters).ConfigureAwait(false); + foreach (var matchingRetainedMessage in matchingRetainedMessages) + { + EnqueueApplicationMessage(matchingRetainedMessage, null, true); + } + } + + public Task UnsubscribeAsync(IEnumerable topicFilters) + { + return SubscriptionsManager.UnsubscribeAsync(topicFilters); + } + + public void FillStatus(MqttSessionStatus status) + { + status.ClientId = ClientId; + status.CreatedTimestamp = _createdTimestamp; + status.PendingApplicationMessagesCount = ApplicationMessagesQueue.Count; + } + } + + public class MqttClientConnection : IMqttClientSession, IDisposable + { + private readonly MqttPacketIdentifierProvider _packetIdentifierProvider = new MqttPacketIdentifierProvider(); + private readonly MqttPacketDispatcher _packetDispatcher = new MqttPacketDispatcher(); + private readonly CancellationTokenSource _cancellationToken = new CancellationTokenSource(); + + private readonly MqttRetainedMessagesManager _retainedMessagesManager; + private readonly MqttClientKeepAliveMonitor _keepAliveMonitor; + private readonly MqttClientSessionsManager _sessionsManager; + + private readonly IMqttNetChildLogger _logger; + private readonly IMqttServerOptions _serverOptions; + + private Task _packageReceiverTask; + private readonly IMqttChannelAdapter _channelAdapter; + private readonly IMqttDataConverter _dataConverter; + private readonly string _endpoint; + private readonly MqttConnectPacket _connectPacket; + + private DateTime _lastPacketReceivedTimestamp; + private long _receivedPacketsCount; + private long _sentPacketsCount; + private long _receivedApplicationMessagesCount; + private long _sentApplicationMessagesCount; + + public MqttClientConnection( + MqttConnectPacket connectPacket, + IMqttChannelAdapter channelAdapter, + MqttClientSession session, + IMqttServerOptions serverOptions, + MqttClientSessionsManager sessionsManager, + MqttRetainedMessagesManager retainedMessagesManager, + IMqttNetChildLogger logger) + { + Session = session ?? throw new ArgumentNullException(nameof(session)); + _serverOptions = serverOptions ?? throw new ArgumentNullException(nameof(serverOptions)); + _sessionsManager = sessionsManager ?? throw new ArgumentNullException(nameof(sessionsManager)); + _retainedMessagesManager = retainedMessagesManager ?? throw new ArgumentNullException(nameof(retainedMessagesManager)); + + _channelAdapter = channelAdapter ?? throw new ArgumentNullException(nameof(channelAdapter)); + _dataConverter = _channelAdapter.PacketFormatterAdapter.DataConverter; + _endpoint = _channelAdapter.Endpoint; + _connectPacket = connectPacket ?? throw new ArgumentNullException(nameof(connectPacket)); + + if (logger == null) throw new ArgumentNullException(nameof(logger)); + _logger = logger.CreateChildLogger(nameof(MqttClientConnection)); + + _keepAliveMonitor = new MqttClientKeepAliveMonitor(this, _logger); + + _lastPacketReceivedTimestamp = DateTime.UtcNow; + } + + public string ClientId => _connectPacket.ClientId; + + public MqttClientSession Session { get; } + + public async Task StopAsync() + { + StopInternal(); + + var task = _packageReceiverTask; + if (task != null && !task.IsCompleted) + { + await task.ConfigureAwait(false); + } + } + + public void FillStatus(MqttClientStatus status) + { + status.ClientId = ClientId; + status.Endpoint = _endpoint; + status.ProtocolVersion = _channelAdapter.PacketFormatterAdapter.ProtocolVersion.Value; + + status.ReceivedApplicationMessagesCount = Interlocked.Read(ref _receivedApplicationMessagesCount); + status.SentApplicationMessagesCount = Interlocked.Read(ref _sentApplicationMessagesCount); + + status.ReceivedPacketsCount = Interlocked.Read(ref _receivedPacketsCount); + status.SentPacketsCount = Interlocked.Read(ref _sentPacketsCount); + + status.LastPacketReceivedTimestamp = _lastPacketReceivedTimestamp; + + //status.LastNonKeepAlivePacketReceived = _keepAliveMonitor.LastNonKeepAlivePacketReceived; + } + + //public void ClearPendingApplicationMessages() + //{ + // Session.ApplicationMessagesQueue.Clear(); + + // //_applicationMessagesQueue.Clear(); + //} + + public void Dispose() + { + _cancellationToken.Dispose(); + } + + public Task RunAsync() + { + _packageReceiverTask = RunInternalAsync(); + return _packageReceiverTask; + } + + public void EnqueueApplicationMessage(MqttApplicationMessage applicationMessage, string senderClientId, bool isRetainedApplicationMessage) + { + if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage)); + + Session.EnqueueApplicationMessage(applicationMessage, senderClientId, isRetainedApplicationMessage); + + _logger.Verbose("Enqueued application message (ClientId: {0}).", ClientId); + } + + private async Task RunInternalAsync() + { + var disconnectType = MqttClientDisconnectType.NotClean; + try + { + _logger.Info("Client '{0}': Session started.", ClientId); + //_eventDispatcher.OnClientConnected(ClientId); + + _channelAdapter.ReadingPacketStarted += OnAdapterReadingPacketStarted; + _channelAdapter.ReadingPacketCompleted += OnAdapterReadingPacketCompleted; + + Session.WillMessage = _connectPacket.WillMessage; + + Task.Run(() => SendPendingPacketsAsync(), _cancellationToken.Token); + + // TODO: Change to single thread in SessionManager. Or use SessionManager and stats from KeepAliveMonitor. + _keepAliveMonitor.Start(_connectPacket.KeepAlivePeriod, _cancellationToken.Token); + + await SendAsync( + new MqttConnAckPacket + { + ReturnCode = MqttConnectReturnCode.ConnectionAccepted, + ReasonCode = MqttConnectReasonCode.Success, + IsSessionPresent = Session.IsCleanSession + }).ConfigureAwait(false); + + Session.IsCleanSession = false; + + while (!_cancellationToken.IsCancellationRequested) + { + var packet = await _channelAdapter.ReceivePacketAsync(TimeSpan.Zero, _cancellationToken.Token).ConfigureAwait(false); + if (packet == null) + { + // The client has closed the connection gracefully. + break; + } + + Interlocked.Increment(ref _sentPacketsCount); + _lastPacketReceivedTimestamp = DateTime.UtcNow; + + _keepAliveMonitor.PacketReceived(); + + if (packet is MqttPublishPacket publishPacket) + { + await HandleIncomingPublishPacketAsync(publishPacket).ConfigureAwait(false); + continue; + } + + if (packet is MqttPubRelPacket pubRelPacket) + { + var pubCompPacket = new MqttPubCompPacket + { + PacketIdentifier = pubRelPacket.PacketIdentifier, + ReasonCode = MqttPubCompReasonCode.Success + }; + + await SendAsync(pubCompPacket).ConfigureAwait(false); + continue; + } + + if (packet is MqttSubscribePacket subscribePacket) + { + await HandleIncomingSubscribePacketAsync(subscribePacket).ConfigureAwait(false); + continue; + } + + if (packet is MqttUnsubscribePacket unsubscribePacket) + { + await HandleIncomingUnsubscribePacketAsync(unsubscribePacket).ConfigureAwait(false); + continue; + } + + if (packet is MqttPingReqPacket) + { + await SendAsync(new MqttPingRespPacket()).ConfigureAwait(false); + continue; + } + + if (packet is MqttDisconnectPacket) + { + Session.WillMessage = null; + disconnectType = MqttClientDisconnectType.Clean; + + StopInternal(); + break; + } + + _packetDispatcher.Dispatch(packet); + } + } + catch (OperationCanceledException) + { + } + catch (Exception exception) + { + if (exception is MqttCommunicationException) + { + _logger.Warning(exception, "Client '{0}': Communication exception while receiving client packets.", ClientId); + } + else + { + _logger.Error(exception, "Client '{0}': Unhandled exception while receiving client packets.", ClientId); + } + + StopInternal(); + } + finally + { + if (Session.WillMessage != null) + { + _sessionsManager.DispatchApplicationMessage(Session.WillMessage, this); + Session.WillMessage = null; + } + + _packetDispatcher.Reset(); + + _channelAdapter.ReadingPacketStarted -= OnAdapterReadingPacketStarted; + _channelAdapter.ReadingPacketCompleted -= OnAdapterReadingPacketCompleted; + + _logger.Info("Client '{0}': Session stopped.", ClientId); + //_eventDispatcher.OnClientDisconnected(ClientId); + + _packageReceiverTask = null; + } + + return disconnectType; + } + + private void StopInternal() + { + _cancellationToken.Cancel(false); + } + + private async Task EnqueueSubscribedRetainedMessagesAsync(ICollection topicFilters) + { + var retainedMessages = await _retainedMessagesManager.GetSubscribedMessagesAsync(topicFilters).ConfigureAwait(false); + foreach (var applicationMessage in retainedMessages) + { + EnqueueApplicationMessage(applicationMessage, ClientId, true); + } + } + + private async Task HandleIncomingSubscribePacketAsync(MqttSubscribePacket subscribePacket) + { + // TODO: Let the channel adapter create the packet. + var subscribeResult = await Session.SubscriptionsManager.SubscribeAsync(subscribePacket).ConfigureAwait(false); + + await SendAsync(subscribeResult.ResponsePacket).ConfigureAwait(false); + + if (subscribeResult.CloseConnection) + { + StopInternal(); + return; + } + + await EnqueueSubscribedRetainedMessagesAsync(subscribePacket.TopicFilters).ConfigureAwait(false); + } + + private async Task HandleIncomingUnsubscribePacketAsync(MqttUnsubscribePacket unsubscribePacket) + { + // TODO: Let the channel adapter create the packet. + var unsubscribeResult = await Session.SubscriptionsManager.UnsubscribeAsync(unsubscribePacket).ConfigureAwait(false); + await SendAsync(unsubscribeResult).ConfigureAwait(false); + } + + private Task HandleIncomingPublishPacketAsync(MqttPublishPacket publishPacket) + { + Interlocked.Increment(ref _sentApplicationMessagesCount); + + switch (publishPacket.QualityOfServiceLevel) + { + case MqttQualityOfServiceLevel.AtMostOnce: + { + return HandleIncomingPublishPacketWithQoS0Async(publishPacket); + } + case MqttQualityOfServiceLevel.AtLeastOnce: + { + return HandleIncomingPublishPacketWithQoS1Async(publishPacket); + } + case MqttQualityOfServiceLevel.ExactlyOnce: + { + return HandleIncomingPublishPacketWithQoS2Async(publishPacket); + } + default: + { + throw new MqttCommunicationException("Received a not supported QoS level."); + } + } + } + + private Task HandleIncomingPublishPacketWithQoS0Async(MqttPublishPacket publishPacket) + { + var applicationMessage = _dataConverter.CreateApplicationMessage(publishPacket); + + _sessionsManager.DispatchApplicationMessage(applicationMessage, this); + + return Task.FromResult(0); + } + + private Task HandleIncomingPublishPacketWithQoS1Async(MqttPublishPacket publishPacket) + { + var applicationMessage = _dataConverter.CreateApplicationMessage(publishPacket); + _sessionsManager.DispatchApplicationMessage(applicationMessage, this); + + // TODO: Create ACK packet. + + var response = new MqttPubAckPacket + { + PacketIdentifier = publishPacket.PacketIdentifier, + ReasonCode = MqttPubAckReasonCode.Success + }; + + return SendAsync(response); + } + + private async Task HandleIncomingPublishPacketWithQoS2Async(MqttPublishPacket publishPacket) + { + var applicationMessage = _dataConverter.CreateApplicationMessage(publishPacket); + _sessionsManager.DispatchApplicationMessage(applicationMessage, this); + + var pubRecPacket = new MqttPubRecPacket + { + PacketIdentifier = publishPacket.PacketIdentifier, + ReasonCode = MqttPubRecReasonCode.Success + }; + + await SendAsync(pubRecPacket).ConfigureAwait(false); + + ////Task.Run(async () => + ////{ + //// using (var pubRelPacketAwaiter = _packetDispatcher.AddPacketAwaiter(publishPacket.PacketIdentifier)) + //// { + //// await pubRelPacketAwaiter.WaitOneAsync(_options.DefaultCommunicationTimeout).ConfigureAwait(false); + + //// var pubCompPacket = new MqttPubCompPacket + //// { + //// PacketIdentifier = publishPacket.PacketIdentifier, + //// ReasonCode = MqttPubCompReasonCode.Success + //// }; + + //// await SendAsync(pubCompPacket).ConfigureAwait(false); + //// } + ////}); + + await Task.FromResult(0); + } + + private async Task SendPendingPacketsAsync() + { + MqttPendingApplicationMessage enqueuedApplicationMessage = null; + MqttPublishPacket publishPacket = null; + + try + { + while (!_cancellationToken.IsCancellationRequested) + { + enqueuedApplicationMessage = await Session.ApplicationMessagesQueue.TakeAsync(_cancellationToken.Token).ConfigureAwait(false); + if (enqueuedApplicationMessage == null) + { + return; + } + + if (_cancellationToken.IsCancellationRequested) + { + return; + } + + publishPacket = _dataConverter.CreatePublishPacket(enqueuedApplicationMessage.ApplicationMessage); + publishPacket.QualityOfServiceLevel = enqueuedApplicationMessage.QualityOfServiceLevel; + + // Set the retain flag to true according to [MQTT-3.3.1-8] and [MQTT-3.3.1-9]. + publishPacket.Retain = enqueuedApplicationMessage.IsRetainedMessage; + + if (publishPacket.QualityOfServiceLevel > 0) + { + publishPacket.PacketIdentifier = _packetIdentifierProvider.GetNextPacketIdentifier(); + } + + if (_serverOptions.ClientMessageQueueInterceptor != null) + { + var context = new MqttClientMessageQueueInterceptorContext( + enqueuedApplicationMessage.SenderClientId, + ClientId, + enqueuedApplicationMessage.ApplicationMessage); + + if (_serverOptions.ClientMessageQueueInterceptor != null) + { + await _serverOptions.ClientMessageQueueInterceptor.InterceptClientMessageQueueEnqueueAsync(context).ConfigureAwait(false); + } + + if (!context.AcceptEnqueue || context.ApplicationMessage == null) + { + return; + } + + publishPacket.Topic = context.ApplicationMessage.Topic; + publishPacket.Payload = context.ApplicationMessage.Payload; + publishPacket.QualityOfServiceLevel = context.ApplicationMessage.QualityOfServiceLevel; + } + + if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtMostOnce) + { + await SendAsync(publishPacket).ConfigureAwait(false); + } + else if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtLeastOnce) + { + var awaiter = _packetDispatcher.AddPacketAwaiter(publishPacket.PacketIdentifier); + await SendAsync(publishPacket).ConfigureAwait(false); + await awaiter.WaitOneAsync(_serverOptions.DefaultCommunicationTimeout).ConfigureAwait(false); + } + else if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.ExactlyOnce) + { + using (var awaiter1 = _packetDispatcher.AddPacketAwaiter(publishPacket.PacketIdentifier)) + using (var awaiter2 = _packetDispatcher.AddPacketAwaiter(publishPacket.PacketIdentifier)) + { + await SendAsync(publishPacket).ConfigureAwait(false); + await awaiter1.WaitOneAsync(_serverOptions.DefaultCommunicationTimeout).ConfigureAwait(false); + + await SendAsync(new MqttPubRelPacket { PacketIdentifier = publishPacket.PacketIdentifier }).ConfigureAwait(false); + await awaiter2.WaitOneAsync(_serverOptions.DefaultCommunicationTimeout).ConfigureAwait(false); + } + } + + _logger.Verbose("Enqueued application message sent (ClientId: {0}).", ClientId); + + // TODO: + //Interlocked.Increment(ref _sentPacketsCount); + } + } + catch (Exception exception) + { + if (exception is MqttCommunicationTimedOutException) + { + _logger.Warning(exception, "Sending publish packet failed: Timeout (ClientId: {0}).", ClientId); + } + else if (exception is MqttCommunicationException) + { + _logger.Warning(exception, "Sending publish packet failed: Communication exception (ClientId: {0}).", ClientId); + } + else if (exception is OperationCanceledException && _cancellationToken.Token.IsCancellationRequested) + { + // The cancellation was triggered externally. + } + else + { + _logger.Error(exception, "Sending publish packet failed (ClientId: {0}).", ClientId); + } + + if (publishPacket?.QualityOfServiceLevel > MqttQualityOfServiceLevel.AtMostOnce) + { + enqueuedApplicationMessage.IsDuplicate = true; + + Session.ApplicationMessagesQueue.Enqueue(enqueuedApplicationMessage); + } + + if (!_cancellationToken.Token.IsCancellationRequested) + { + await StopAsync().ConfigureAwait(false); + } + } + } + + private async Task SendAsync(MqttBasePacket packet) + { + await _channelAdapter.SendPacketAsync(packet, _serverOptions.DefaultCommunicationTimeout, _cancellationToken.Token).ConfigureAwait(false); + + Interlocked.Increment(ref _receivedPacketsCount); + + if (packet is MqttPublishPacket) + { + Interlocked.Increment(ref _receivedApplicationMessagesCount); + } + } + + private void OnAdapterReadingPacketCompleted(object sender, EventArgs e) + { + _keepAliveMonitor?.Resume(); + } + + private void OnAdapterReadingPacketStarted(object sender, EventArgs e) + { + _keepAliveMonitor?.Pause(); + } + } +} diff --git a/Source/MQTTnet/Server/MqttClientDisconnectType.cs b/Source/MQTTnet/Server/MqttClientDisconnectType.cs index 19e9da8d..c4d6f595 100644 --- a/Source/MQTTnet/Server/MqttClientDisconnectType.cs +++ b/Source/MQTTnet/Server/MqttClientDisconnectType.cs @@ -3,6 +3,7 @@ public enum MqttClientDisconnectType { Clean, - NotClean + NotClean, + Takeover } } diff --git a/Source/MQTTnet/Server/MqttClientDisconnectedEventArgs.cs b/Source/MQTTnet/Server/MqttClientDisconnectedEventArgs.cs index 7e88548e..225d0250 100644 --- a/Source/MQTTnet/Server/MqttClientDisconnectedEventArgs.cs +++ b/Source/MQTTnet/Server/MqttClientDisconnectedEventArgs.cs @@ -4,14 +4,14 @@ namespace MQTTnet.Server { public class MqttClientDisconnectedEventArgs : EventArgs { - public MqttClientDisconnectedEventArgs(string clientId, bool wasCleanDisconnect) + public MqttClientDisconnectedEventArgs(string clientId, MqttClientDisconnectType disconnectType) { ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId)); - WasCleanDisconnect = wasCleanDisconnect; + DisconnectType = disconnectType; } public string ClientId { get; } - public bool WasCleanDisconnect { get; } + public MqttClientDisconnectType DisconnectType { get; } } } diff --git a/Source/MQTTnet/Server/MqttClientKeepAliveMonitor.cs b/Source/MQTTnet/Server/MqttClientKeepAliveMonitor.cs index e8e7c150..3f89d19d 100644 --- a/Source/MQTTnet/Server/MqttClientKeepAliveMonitor.cs +++ b/Source/MQTTnet/Server/MqttClientKeepAliveMonitor.cs @@ -10,7 +10,6 @@ namespace MQTTnet.Server public class MqttClientKeepAliveMonitor { private readonly Stopwatch _lastPacketReceivedTracker = new Stopwatch(); - private readonly Stopwatch _lastNonKeepAlivePacketReceivedTracker = new Stopwatch(); private readonly IMqttClientSession _clientSession; private readonly IMqttNetChildLogger _logger; @@ -26,10 +25,6 @@ namespace MQTTnet.Server _logger = logger.CreateChildLogger(nameof(MqttClientKeepAliveMonitor)); } - public TimeSpan LastPacketReceived => _lastPacketReceivedTracker.Elapsed; - - public TimeSpan LastNonKeepAlivePacketReceived => _lastNonKeepAlivePacketReceivedTracker.Elapsed; - public void Start(int keepAlivePeriod, CancellationToken cancellationToken) { if (keepAlivePeriod == 0) @@ -50,20 +45,9 @@ namespace MQTTnet.Server _isPaused = false; } - public void Reset() + public void PacketReceived() { _lastPacketReceivedTracker.Restart(); - _lastNonKeepAlivePacketReceivedTracker.Restart(); - } - - public void PacketReceived(MqttBasePacket packet) - { - _lastPacketReceivedTracker.Restart(); - - if (!(packet is MqttPingReqPacket)) - { - _lastNonKeepAlivePacketReceivedTracker.Restart(); - } } private async Task RunAsync(int keepAlivePeriod, CancellationToken cancellationToken) @@ -71,7 +55,6 @@ namespace MQTTnet.Server try { _lastPacketReceivedTracker.Restart(); - _lastNonKeepAlivePacketReceivedTracker.Restart(); while (!cancellationToken.IsCancellationRequested) { @@ -81,7 +64,7 @@ namespace MQTTnet.Server if (!_isPaused && _lastPacketReceivedTracker.Elapsed.TotalSeconds >= keepAlivePeriod * 1.5D) { _logger.Warning(null, "Client '{0}': Did not receive any packet or keep alive signal.", _clientSession.ClientId); - await _clientSession.StopAsync(MqttClientDisconnectType.NotClean).ConfigureAwait(false); + await _clientSession.StopAsync().ConfigureAwait(false); return; } diff --git a/Source/MQTTnet/Server/MqttClientSession.cs b/Source/MQTTnet/Server/MqttClientSession.cs deleted file mode 100644 index ad6fdfa9..00000000 --- a/Source/MQTTnet/Server/MqttClientSession.cs +++ /dev/null @@ -1,481 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using MQTTnet.Adapter; -using MQTTnet.Client; -using MQTTnet.Diagnostics; -using MQTTnet.Exceptions; -using MQTTnet.MessageStream; -using MQTTnet.PacketDispatcher; -using MQTTnet.Packets; -using MQTTnet.Protocol; - -namespace MQTTnet.Server -{ - public class MqttClientSession : IMqttClientSession - { - private readonly MqttPacketIdentifierProvider _packetIdentifierProvider = new MqttPacketIdentifierProvider(); - private readonly MqttPacketDispatcher _packetDispatcher = new MqttPacketDispatcher(); - - private readonly MqttRetainedMessagesManager _retainedMessagesManager; - private readonly MqttServerEventDispatcher _eventDispatcher; - private readonly MqttClientKeepAliveMonitor _keepAliveMonitor; - private readonly MqttClientSessionPendingMessagesQueue _pendingMessagesQueue; - private readonly MqttClientSubscriptionsManager _subscriptionsManager; - private readonly MqttClientSessionsManager _sessionsManager; - - private readonly IMqttNetChildLogger _logger; - private readonly IMqttServerOptions _options; - - private CancellationTokenSource _cancellationTokenSource; - private MqttApplicationMessage _willMessage; - private bool _wasCleanDisconnect; - private Task _workerTask; - private IMqttChannelAdapter _channelAdapter; - - private long _receivedMessagesCount; - private bool _isCleanSession = true; - - public MqttClientSession( - string clientId, - IMqttServerOptions options, - MqttClientSessionsManager sessionsManager, - MqttRetainedMessagesManager retainedMessagesManager, - MqttServerEventDispatcher eventDispatcher, - IMqttNetChildLogger logger) - { - if (logger == null) throw new ArgumentNullException(nameof(logger)); - - _options = options ?? throw new ArgumentNullException(nameof(options)); - _sessionsManager = sessionsManager ?? throw new ArgumentNullException(nameof(sessionsManager)); - _retainedMessagesManager = retainedMessagesManager ?? throw new ArgumentNullException(nameof(retainedMessagesManager)); - _eventDispatcher = eventDispatcher ?? throw new ArgumentNullException(nameof(eventDispatcher)); - - ClientId = clientId; - - _logger = logger.CreateChildLogger(nameof(MqttClientSession)); - - _keepAliveMonitor = new MqttClientKeepAliveMonitor(this, _logger); - _subscriptionsManager = new MqttClientSubscriptionsManager(clientId, _options, eventDispatcher); - _pendingMessagesQueue = new MqttClientSessionPendingMessagesQueue(_options, this, _packetDispatcher, _logger); - } - - public string ClientId { get; } - - public void FillStatus(MqttClientSessionStatus status) - { - status.ClientId = ClientId; - status.IsConnected = _cancellationTokenSource != null; - status.Endpoint = _channelAdapter?.Endpoint; - status.ProtocolVersion = _channelAdapter?.PacketFormatterAdapter?.ProtocolVersion; - status.PendingApplicationMessagesCount = _pendingMessagesQueue.Count; - status.ReceivedApplicationMessagesCount = _pendingMessagesQueue.SentMessagesCount; - status.SentApplicationMessagesCount = Interlocked.Read(ref _receivedMessagesCount); - status.LastPacketReceived = _keepAliveMonitor.LastPacketReceived; - status.LastNonKeepAlivePacketReceived = _keepAliveMonitor.LastNonKeepAlivePacketReceived; - } - - public async Task StopAsync(MqttClientDisconnectType type) - { - StopInternal(type); - - var task = _workerTask; - if (task != null && !task.IsCompleted) - { - await task.ConfigureAwait(false); - } - } - - public async Task SubscribeAsync(IEnumerable topicFilters) - { - if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters)); - - var topicFiltersCollection = topicFilters.ToList(); - - var packet = new MqttSubscribePacket(); - packet.TopicFilters.AddRange(topicFiltersCollection); - - await _subscriptionsManager.SubscribeAsync(packet).ConfigureAwait(false); - await EnqueueSubscribedRetainedMessagesAsync(topicFiltersCollection).ConfigureAwait(false); - } - - public Task UnsubscribeAsync(IEnumerable topicFilters) - { - if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters)); - - var packet = new MqttUnsubscribePacket(); - packet.TopicFilters.AddRange(topicFilters); - - _subscriptionsManager.Unsubscribe(packet); - - return Task.FromResult(0); - } - - public void ClearPendingApplicationMessages() - { - _pendingMessagesQueue.Clear(); - } - - public void Dispose() - { - _pendingMessagesQueue?.Dispose(); - - _cancellationTokenSource?.Cancel(); - _cancellationTokenSource?.Dispose(); - _cancellationTokenSource = null; - } - - public Task RunAsync(MqttConnectPacket connectPacket, IMqttChannelAdapter channelAdapter) - { - if (connectPacket == null) throw new ArgumentNullException(nameof(connectPacket)); - if (channelAdapter == null) throw new ArgumentNullException(nameof(channelAdapter)); - - _workerTask = RunInternalAsync(connectPacket, channelAdapter); - return _workerTask; - } - - public async Task EnqueueApplicationMessageAsync(MqttClientSession senderClientSession, MqttApplicationMessage applicationMessage, bool isRetainedApplicationMessage) - { - if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage)); - - var checkSubscriptionsResult = _subscriptionsManager.CheckSubscriptions(applicationMessage.Topic, applicationMessage.QualityOfServiceLevel); - if (!checkSubscriptionsResult.IsSubscribed) - { - return; - } - - var publishPacket = _channelAdapter.PacketFormatterAdapter.DataConverter.CreatePublishPacket(applicationMessage); - publishPacket.QualityOfServiceLevel = checkSubscriptionsResult.QualityOfServiceLevel; - - // Set the retain flag to true according to [MQTT-3.3.1-8] and [MQTT-3.3.1-9]. - publishPacket.Retain = isRetainedApplicationMessage; - - if (publishPacket.QualityOfServiceLevel > 0) - { - publishPacket.PacketIdentifier = _packetIdentifierProvider.GetNextPacketIdentifier(); - } - - if (_options.ClientMessageQueueInterceptor != null) - { - var context = new MqttClientMessageQueueInterceptorContext( - senderClientSession?.ClientId, - ClientId, - applicationMessage); - - if (_options.ClientMessageQueueInterceptor != null) - { - await _options.ClientMessageQueueInterceptor.InterceptClientMessageQueueEnqueueAsync(context).ConfigureAwait(false); - } - - if (!context.AcceptEnqueue || context.ApplicationMessage == null) - { - return; - } - - publishPacket.Topic = context.ApplicationMessage.Topic; - publishPacket.Payload = context.ApplicationMessage.Payload; - publishPacket.QualityOfServiceLevel = context.ApplicationMessage.QualityOfServiceLevel; - } - - _pendingMessagesQueue.Enqueue(publishPacket); - } - - - - private async Task RunInternalAsync(MqttConnectPacket connectPacket, IMqttChannelAdapter channelAdapter) - { - if (channelAdapter == null) throw new ArgumentNullException(nameof(channelAdapter)); - - try - { - _logger.Info("Client '{0}': Connected.", ClientId); - _eventDispatcher.OnClientConnected(ClientId); - - _channelAdapter = channelAdapter; - - _channelAdapter.ReadingPacketStarted += OnAdapterReadingPacketStarted; - _channelAdapter.ReadingPacketCompleted += OnAdapterReadingPacketCompleted; - - var cancellationTokenSource = new CancellationTokenSource(); - _cancellationTokenSource = cancellationTokenSource; - - _wasCleanDisconnect = false; - _willMessage = connectPacket.WillMessage; - - _pendingMessagesQueue.Start(channelAdapter, cancellationTokenSource.Token); - _keepAliveMonitor.Start(connectPacket.KeepAlivePeriod, cancellationTokenSource.Token); - - await channelAdapter.SendPacketAsync( - new MqttConnAckPacket - { - ReturnCode = MqttConnectReturnCode.ConnectionAccepted, - ReasonCode = MqttConnectReasonCode.Success, - IsSessionPresent = _isCleanSession - }, - cancellationTokenSource.Token).ConfigureAwait(false); - - _isCleanSession = false; - - Task.Run(async () => - { - while (!cancellationTokenSource.IsCancellationRequested) - { - var packet = _packetDispatcher.Take(cancellationTokenSource.Token); - await ProcessReceivedPacketAsync(packet, cancellationTokenSource.Token).ConfigureAwait(false); - } - }, cancellationTokenSource.Token); - - Task.Run(async () => - { - while (!cancellationTokenSource.IsCancellationRequested) - { - try - { - var packet = await _outboundMessageStream.TakeAsync(cancellationTokenSource.Token); - await channelAdapter.SendPacketAsync(packet, cancellationTokenSource.Token); - } - catch (Exception e) - { - _logger.Error(e, "sdfsdf"); - await StopAsync(MqttClientDisconnectType.NotClean); - - } - - } - },cancellationTokenSource.Token); - - while (!cancellationTokenSource.IsCancellationRequested) - { - var packet = await channelAdapter.ReceivePacketAsync(TimeSpan.Zero, cancellationTokenSource.Token).ConfigureAwait(false); - if (packet != null) - { - _keepAliveMonitor.PacketReceived(packet); - _packetDispatcher.Dispatch(packet); - } - } - } - catch (OperationCanceledException) - { - } - catch (Exception exception) - { - if (exception is MqttCommunicationException) - { - _logger.Warning(exception, "Client '{0}': Communication exception while receiving client packets.", ClientId); - } - else - { - _logger.Error(exception, "Client '{0}': Unhandled exception while receiving client packets.", ClientId); - } - - StopInternal(MqttClientDisconnectType.NotClean); - } - finally - { - if (_willMessage != null && !_wasCleanDisconnect) - { - _sessionsManager.EnqueueApplicationMessage(this, _willMessage); - } - - _willMessage = null; - - _channelAdapter.ReadingPacketStarted -= OnAdapterReadingPacketStarted; - _channelAdapter.ReadingPacketCompleted -= OnAdapterReadingPacketCompleted; - _channelAdapter = null; - - _logger.Info("Client '{0}': Session stopped.", ClientId); - _eventDispatcher.OnClientDisconnected(ClientId, _wasCleanDisconnect); - - _workerTask = null; - } - } - - private void StopInternal(MqttClientDisconnectType type) - { - var cts = _cancellationTokenSource; - if (cts == null || cts.IsCancellationRequested) - { - return; - } - - _wasCleanDisconnect = type == MqttClientDisconnectType.Clean; - _cancellationTokenSource?.Cancel(false); - _packetDispatcher.Reset(); - } - - private readonly MqttMessageStream _outboundMessageStream = new MqttMessageStream(); - - private Task ProcessReceivedPacketAsync(MqttBasePacket packet, CancellationToken cancellationToken) - { - if (packet is MqttPublishPacket publishPacket) - { - return HandleIncomingPublishPacketAsync(publishPacket, cancellationToken); - } - - if (packet is MqttPingReqPacket) - { - //return channelAdapter.SendPacketAsync(new MqttPingRespPacket(), cancellationToken); - _outboundMessageStream.Enqueue(new MqttPingRespPacket()); - return Task.FromResult(0); - } - - if (packet is MqttSubscribePacket subscribePacket) - { - return HandleIncomingSubscribePacketAsync(subscribePacket, cancellationToken); - } - - if (packet is MqttUnsubscribePacket unsubscribePacket) - { - return HandleIncomingUnsubscribePacketAsync(unsubscribePacket, cancellationToken); - } - - if (packet is MqttDisconnectPacket) - { - StopInternal(MqttClientDisconnectType.Clean); - return Task.FromResult(0); - } - - //if (packet is MqttAuthPacket || - // packet is MqttSubAckPacket || - // packet is MqttUnsubAckPacket || - // packet is MqttPubAckPacket || - // packet is MqttPubCompPacket || - // packet is MqttPubRecPacket || - // packet is MqttPubRelPacket) - //{ - // _packetDispatcher.TryDispatch(packet); - // return Task.FromResult(0); - //} - - _logger.Warning(null, "Client '{0}': Received invalid packet ({1}). Closing connection.", ClientId, packet); - - StopInternal(MqttClientDisconnectType.NotClean); - return Task.FromResult(0); - } - - private async Task EnqueueSubscribedRetainedMessagesAsync(ICollection topicFilters) - { - var retainedMessages = await _retainedMessagesManager.GetSubscribedMessagesAsync(topicFilters).ConfigureAwait(false); - foreach (var applicationMessage in retainedMessages) - { - await EnqueueApplicationMessageAsync(null, applicationMessage, true).ConfigureAwait(false); - } - } - - private async Task HandleIncomingSubscribePacketAsync(MqttSubscribePacket subscribePacket, CancellationToken cancellationToken) - { - var subscribeResult = await _subscriptionsManager.SubscribeAsync(subscribePacket).ConfigureAwait(false); - - _outboundMessageStream.Enqueue(subscribeResult.ResponsePacket); - //await adapter.SendPacketAsync(subscribeResult.ResponsePacket, cancellationToken).ConfigureAwait(false); - - // TODO: Add "WaitForDelivery". - - if (subscribeResult.CloseConnection) - { - StopInternal(MqttClientDisconnectType.NotClean); - return; - } - - await EnqueueSubscribedRetainedMessagesAsync(subscribePacket.TopicFilters).ConfigureAwait(false); - } - - private Task HandleIncomingUnsubscribePacketAsync(MqttUnsubscribePacket unsubscribePacket, CancellationToken cancellationToken) - { - var unsubscribeResult = _subscriptionsManager.Unsubscribe(unsubscribePacket); - - _outboundMessageStream.Enqueue(unsubscribeResult); - - //return adapter.SendPacketAsync(unsubscribeResult, cancellationToken); - return Task.FromResult(0); - } - - private Task HandleIncomingPublishPacketAsync(MqttPublishPacket publishPacket, CancellationToken cancellationToken) - { - Interlocked.Increment(ref _receivedMessagesCount); - - switch (publishPacket.QualityOfServiceLevel) - { - case MqttQualityOfServiceLevel.AtMostOnce: - { - return HandleIncomingPublishPacketWithQoS0Async(publishPacket); - } - case MqttQualityOfServiceLevel.AtLeastOnce: - { - return HandleIncomingPublishPacketWithQoS1Async(publishPacket, cancellationToken); - } - case MqttQualityOfServiceLevel.ExactlyOnce: - { - return HandleIncomingPublishPacketWithQoS2Async(publishPacket, cancellationToken); - } - default: - { - throw new MqttCommunicationException("Received a not supported QoS level."); - } - } - } - - private Task HandleIncomingPublishPacketWithQoS0Async(MqttPublishPacket publishPacket) - { - _sessionsManager.EnqueueApplicationMessage( - this, - _channelAdapter.PacketFormatterAdapter.DataConverter.CreateApplicationMessage(publishPacket)); - - return Task.FromResult(0); - } - - private Task HandleIncomingPublishPacketWithQoS1Async( - MqttPublishPacket publishPacket, - CancellationToken cancellationToken) - { - _sessionsManager.EnqueueApplicationMessage( - this, - _channelAdapter.PacketFormatterAdapter.DataConverter.CreateApplicationMessage(publishPacket)); - - var response = new MqttPubAckPacket - { - PacketIdentifier = publishPacket.PacketIdentifier, - ReasonCode = MqttPubAckReasonCode.Success - }; - - _outboundMessageStream.Enqueue(response); - - //return adapter.SendPacketAsync(response, cancellationToken); - return Task.FromResult(0); - } - - private async Task HandleIncomingPublishPacketWithQoS2Async( - MqttPublishPacket publishPacket, - CancellationToken cancellationToken) - { - // QoS 2 is implement as method "B" (4.3.3 QoS 2: Exactly once delivery) - _sessionsManager.EnqueueApplicationMessage(this, _channelAdapter.PacketFormatterAdapter.DataConverter.CreateApplicationMessage(publishPacket)); - - using (var pubRelPacketAwaiter = _packetDispatcher.AddPacketAwaiter(publishPacket.PacketIdentifier)) - { - var pubRecPacket = new MqttPubRecPacket - { - PacketIdentifier = publishPacket.PacketIdentifier, - ReasonCode = MqttPubRecReasonCode.Success - }; - - //await adapter.SendPacketAsync(pubRecPacket, cancellationToken).ConfigureAwait(false); - _outboundMessageStream.Enqueue(pubRecPacket); - - await pubRelPacketAwaiter.WaitOneAsync(_options.DefaultCommunicationTimeout).ConfigureAwait(false); - } - } - - private void OnAdapterReadingPacketCompleted(object sender, EventArgs e) - { - _keepAliveMonitor?.Resume(); - } - - private void OnAdapterReadingPacketStarted(object sender, EventArgs e) - { - _keepAliveMonitor?.Pause(); - } - } -} diff --git a/Source/MQTTnet/Server/MqttClientSessionApplicationMessagesQueue.cs b/Source/MQTTnet/Server/MqttClientSessionApplicationMessagesQueue.cs new file mode 100644 index 00000000..7fbd9e2e --- /dev/null +++ b/Source/MQTTnet/Server/MqttClientSessionApplicationMessagesQueue.cs @@ -0,0 +1,117 @@ +using MQTTnet.Internal; +using MQTTnet.Protocol; +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace MQTTnet.Server +{ + public class MqttPendingApplicationMessage + { + public MqttApplicationMessage ApplicationMessage { get; set; } + + public string SenderClientId { get; set; } + + public bool IsRetainedMessage { get; set; } + + public MqttQualityOfServiceLevel QualityOfServiceLevel { get; set; } + + public bool IsDuplicate { get; set; } + } + + public class MqttClientSessionApplicationMessagesQueue : IDisposable + { + private readonly Queue _messageQueue = new Queue(); + private readonly AsyncAutoResetEvent _messageQueueLock = new AsyncAutoResetEvent(); + + private readonly IMqttServerOptions _options; + + public MqttClientSessionApplicationMessagesQueue(IMqttServerOptions options) + { + _options = options ?? throw new ArgumentNullException(nameof(options)); + + } + + public int Count + { + get + { + lock (_messageQueue) + { + return _messageQueue.Count; + } + } + } + + public void Enqueue(MqttApplicationMessage applicationMessage, string senderClientId, MqttQualityOfServiceLevel qualityOfServiceLevel, bool isRetainedMessage) + { + if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage)); + + Enqueue(new MqttPendingApplicationMessage + { + ApplicationMessage = applicationMessage, + SenderClientId = senderClientId, + QualityOfServiceLevel = qualityOfServiceLevel, + IsRetainedMessage = isRetainedMessage + }); + } + + public void Clear() + { + lock (_messageQueue) + { + _messageQueue.Clear(); + } + } + + public void Dispose() + { + } + + public async Task TakeAsync(CancellationToken cancellationToken) + { + // TODO: Create a blocking queue from this. + + while (!cancellationToken.IsCancellationRequested) + { + lock (_messageQueue) + { + if (_messageQueue.Count > 0) + { + return _messageQueue.Dequeue(); + } + } + + await _messageQueueLock.WaitOneAsync(cancellationToken).ConfigureAwait(false); + } + + return null; + } + + public void Enqueue(MqttPendingApplicationMessage enqueuedApplicationMessage) + { + if (enqueuedApplicationMessage == null) throw new ArgumentNullException(nameof(enqueuedApplicationMessage)); + + lock (_messageQueue) + { + if (_messageQueue.Count >= _options.MaxPendingMessagesPerClient) + { + if (_options.PendingMessagesOverflowStrategy == MqttPendingMessagesOverflowStrategy.DropNewMessage) + { + return; + } + + if (_options.PendingMessagesOverflowStrategy == MqttPendingMessagesOverflowStrategy.DropOldestQueuedMessage) + { + _messageQueue.Dequeue(); + } + } + + _messageQueue.Enqueue(enqueuedApplicationMessage); + } + + _messageQueueLock.Set(); + } + } +} diff --git a/Source/MQTTnet/Server/MqttClientSessionPendingMessagesQueue.cs b/Source/MQTTnet/Server/MqttClientSessionPendingMessagesQueue.cs deleted file mode 100644 index 1fd19e8c..00000000 --- a/Source/MQTTnet/Server/MqttClientSessionPendingMessagesQueue.cs +++ /dev/null @@ -1,212 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using MQTTnet.Adapter; -using MQTTnet.Diagnostics; -using MQTTnet.Exceptions; -using MQTTnet.Internal; -using MQTTnet.PacketDispatcher; -using MQTTnet.Packets; -using MQTTnet.Protocol; - -namespace MQTTnet.Server -{ - public class MqttClientSessionPendingMessagesQueue : IDisposable - { - private readonly Queue _queue = new Queue(); - private readonly AsyncAutoResetEvent _queueLock = new AsyncAutoResetEvent(); - - private readonly IMqttServerOptions _options; - private readonly MqttClientSession _clientSession; - private readonly MqttPacketDispatcher _packetDispatcher; - private readonly IMqttNetChildLogger _logger; - - private long _sentPacketsCount; - - public MqttClientSessionPendingMessagesQueue( - IMqttServerOptions options, - MqttClientSession clientSession, - MqttPacketDispatcher packetDispatcher, - IMqttNetChildLogger logger) - { - if (logger == null) throw new ArgumentNullException(nameof(logger)); - _options = options ?? throw new ArgumentNullException(nameof(options)); - _clientSession = clientSession ?? throw new ArgumentNullException(nameof(clientSession)); - _packetDispatcher = packetDispatcher ?? throw new ArgumentNullException(nameof(packetDispatcher)); - - _logger = logger.CreateChildLogger(nameof(MqttClientSessionPendingMessagesQueue)); - } - - public int Count - { - get - { - lock (_queue) - { - return _queue.Count; - } - } - } - - public long SentMessagesCount => Interlocked.Read(ref _sentPacketsCount); - - public void Start(IMqttChannelAdapter adapter, CancellationToken cancellationToken) - { - if (adapter == null) throw new ArgumentNullException(nameof(adapter)); - - if (cancellationToken.IsCancellationRequested) - { - return; - } - - Task.Run(() => SendQueuedPacketsAsync(adapter, cancellationToken), cancellationToken); - } - - public void Enqueue(MqttPublishPacket packet) - { - if (packet == null) throw new ArgumentNullException(nameof(packet)); - - lock (_queue) - { - if (_queue.Count >= _options.MaxPendingMessagesPerClient) - { - if (_options.PendingMessagesOverflowStrategy == MqttPendingMessagesOverflowStrategy.DropNewMessage) - { - return; - } - - if (_options.PendingMessagesOverflowStrategy == MqttPendingMessagesOverflowStrategy.DropOldestQueuedMessage) - { - _queue.Dequeue(); - } - } - - _queue.Enqueue(packet); - } - - _queueLock.Set(); - - _logger.Verbose("Enqueued packet (ClientId: {0}).", _clientSession.ClientId); - } - - public void Clear() - { - lock (_queue) - { - _queue.Clear(); - } - } - - public void Dispose() - { - } - - private async Task SendQueuedPacketsAsync(IMqttChannelAdapter adapter, CancellationToken cancellationToken) - { - try - { - while (!cancellationToken.IsCancellationRequested) - { - await TrySendNextQueuedPacketAsync(adapter, cancellationToken).ConfigureAwait(false); - } - } - catch (OperationCanceledException) - { - } - catch (Exception exception) - { - _logger.Error(exception, "Unhandled exception while sending enqueued packet (ClientId: {0}).", _clientSession.ClientId); - } - } - - private async Task TrySendNextQueuedPacketAsync(IMqttChannelAdapter adapter, CancellationToken cancellationToken) - { - MqttPublishPacket packet = null; - try - { - if (cancellationToken.IsCancellationRequested) - { - return; - } - - lock (_queue) - { - if (_queue.Count > 0) - { - packet = _queue.Dequeue(); - } - } - - if (packet == null) - { - await _queueLock.WaitOneAsync(cancellationToken).ConfigureAwait(false); - return; - } - - if (packet.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtMostOnce) - { - await adapter.SendPacketAsync(packet, cancellationToken).ConfigureAwait(false); - } - else if (packet.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtLeastOnce) - { - var awaiter = _packetDispatcher.AddPacketAwaiter(packet.PacketIdentifier); - await adapter.SendPacketAsync(packet, cancellationToken).ConfigureAwait(false); - await awaiter.WaitOneAsync(_options.DefaultCommunicationTimeout).ConfigureAwait(false); - } - else if (packet.QualityOfServiceLevel == MqttQualityOfServiceLevel.ExactlyOnce) - { - var awaiter1 = _packetDispatcher.AddPacketAwaiter(packet.PacketIdentifier); - var awaiter2 = _packetDispatcher.AddPacketAwaiter(packet.PacketIdentifier); - try - { - await adapter.SendPacketAsync(packet, cancellationToken).ConfigureAwait(false); - await awaiter1.WaitOneAsync(_options.DefaultCommunicationTimeout).ConfigureAwait(false); - - await adapter.SendPacketAsync(new MqttPubRelPacket { PacketIdentifier = packet.PacketIdentifier }, cancellationToken).ConfigureAwait(false); - await awaiter2.WaitOneAsync(_options.DefaultCommunicationTimeout).ConfigureAwait(false); - } - finally - { - _packetDispatcher.RemovePacketAwaiter(packet.PacketIdentifier); - _packetDispatcher.RemovePacketAwaiter(packet.PacketIdentifier); - } - } - - _logger.Verbose("Enqueued packet sent (ClientId: {0}).", _clientSession.ClientId); - - Interlocked.Increment(ref _sentPacketsCount); - } - catch (Exception exception) - { - if (exception is MqttCommunicationTimedOutException) - { - _logger.Warning(exception, "Sending publish packet failed: Timeout (ClientId: {0}).", _clientSession.ClientId); - } - else if (exception is MqttCommunicationException) - { - _logger.Warning(exception, "Sending publish packet failed: Communication exception (ClientId: {0}).", _clientSession.ClientId); - } - else if (exception is OperationCanceledException && cancellationToken.IsCancellationRequested) - { - } - else - { - _logger.Error(exception, "Sending publish packet failed (ClientId: {0}).", _clientSession.ClientId); - } - - if (packet?.QualityOfServiceLevel > MqttQualityOfServiceLevel.AtMostOnce) - { - packet.Dup = true; - - Enqueue(packet); - } - - if (!cancellationToken.IsCancellationRequested) - { - await _clientSession.StopAsync(MqttClientDisconnectType.NotClean).ConfigureAwait(false); - } - } - } - } -} diff --git a/Source/MQTTnet/Server/MqttClientSessionStatus.cs b/Source/MQTTnet/Server/MqttClientSessionStatus.cs deleted file mode 100644 index 007a19ba..00000000 --- a/Source/MQTTnet/Server/MqttClientSessionStatus.cs +++ /dev/null @@ -1,52 +0,0 @@ -using System; -using System.Threading.Tasks; -using MQTTnet.Formatter; - -namespace MQTTnet.Server -{ - public class MqttClientSessionStatus : IMqttClientSessionStatus - { - private readonly MqttClientSessionsManager _sessionsManager; - private readonly MqttClientSession _session; - - public MqttClientSessionStatus(MqttClientSessionsManager sessionsManager, MqttClientSession session) - { - _sessionsManager = sessionsManager; - _session = session; - } - - public string ClientId { get; set; } - public string Endpoint { get; set; } - public bool IsConnected { get; set; } - public MqttProtocolVersion? ProtocolVersion { get; set; } - public TimeSpan LastPacketReceived { get; set; } - public TimeSpan LastNonKeepAlivePacketReceived { get; set; } - public long PendingApplicationMessagesCount { get; set; } - public long ReceivedApplicationMessagesCount { get; set; } - public long SentApplicationMessagesCount { get; set; } - - public Task DisconnectAsync() - { - return _session.StopAsync(MqttClientDisconnectType.NotClean); - } - - public async Task DeleteSessionAsync() - { - try - { - await _session.StopAsync(MqttClientDisconnectType.NotClean).ConfigureAwait(false); - } - finally - { - await _sessionsManager.DeleteSessionAsync(ClientId).ConfigureAwait(false); - } - } - - public Task ClearPendingApplicationMessagesAsync() - { - _session.ClearPendingApplicationMessages(); - - return Task.FromResult(0); - } - } -} diff --git a/Source/MQTTnet/Server/MqttClientSessionsManager.cs b/Source/MQTTnet/Server/MqttClientSessionsManager.cs index 35379ce3..cf58ec7c 100644 --- a/Source/MQTTnet/Server/MqttClientSessionsManager.cs +++ b/Source/MQTTnet/Server/MqttClientSessionsManager.cs @@ -1,14 +1,13 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; -using System.Linq; using System.Threading; using System.Threading.Tasks; using MQTTnet.Adapter; using MQTTnet.Diagnostics; -using MQTTnet.Internal; using MQTTnet.Packets; using MQTTnet.Protocol; +using MQTTnet.Server.Status; namespace MQTTnet.Server { @@ -16,9 +15,9 @@ namespace MQTTnet.Server { private readonly BlockingCollection _messageQueue = new BlockingCollection(); - private readonly AsyncLock _sessionsLock = new AsyncLock(); - private readonly Dictionary _sessions = new Dictionary(); - + private readonly ConcurrentDictionary _connections = new ConcurrentDictionary(); + private readonly ConcurrentDictionary _sessions = new ConcurrentDictionary(); + private readonly CancellationToken _cancellationToken; private readonly MqttServerEventDispatcher _eventDispatcher; @@ -27,8 +26,8 @@ namespace MQTTnet.Server private readonly IMqttNetChildLogger _logger; public MqttClientSessionsManager( - IMqttServerOptions options, - MqttRetainedMessagesManager retainedMessagesManager, + IMqttServerOptions options, + MqttRetainedMessagesManager retainedMessagesManager, CancellationToken cancellationToken, MqttServerEventDispatcher eventDispatcher, IMqttNetChildLogger logger) @@ -45,98 +44,100 @@ namespace MQTTnet.Server public void Start() { - Task.Factory.StartNew(() => TryProcessQueuedApplicationMessagesAsync(_cancellationToken), _cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Default); + Task.Run(() => TryProcessQueuedApplicationMessagesAsync(_cancellationToken), _cancellationToken); } public async Task StopAsync() { - List sessions; - using (await _sessionsLock.WaitAsync(CancellationToken.None).ConfigureAwait(false)) - { - sessions = _sessions.Values.ToList(); - } + //using (await _sessionsLock.WaitAsync(CancellationToken.None).ConfigureAwait(false)) - foreach (var session in sessions) + foreach (var connection in _connections.Values) { - await session.StopAsync(MqttClientDisconnectType.Clean).ConfigureAwait(false); + await connection.StopAsync().ConfigureAwait(false); } } public Task HandleConnectionAsync(IMqttChannelAdapter clientAdapter) { return HandleConnectionAsync(clientAdapter, _cancellationToken); - - // TODO: Check if Task.Run is required. - //return Task.Run(() => HandleConnectionAsync(clientAdapter, _cancellationToken), _cancellationToken); } - public async Task> GetClientStatusAsync() + public Task> GetClientStatusAsync() { - var result = new List(); + var result = new List(); - using (await _sessionsLock.WaitAsync(CancellationToken.None).ConfigureAwait(false)) + foreach (var connection in _connections.Values) { - foreach (var session in _sessions.Values) - { - var status = new MqttClientSessionStatus(this, session); - session.FillStatus(status); + var clientStatus = new MqttClientStatus(connection, this); + connection.FillStatus(clientStatus); + + var sessionStatus = new MqttSessionStatus(connection.Session, this); + connection.Session.FillStatus(sessionStatus); + clientStatus.Session = sessionStatus; - result.Add(status); - } + result.Add(clientStatus); } - - return result; + + return Task.FromResult((IList)result); } - public void EnqueueApplicationMessage(MqttClientSession senderClientSession, MqttApplicationMessage applicationMessage) + public Task> GetSessionStatusAsync() + { + var result = new List(); + + foreach (var session in _sessions.Values) + { + var sessionStatus = new MqttSessionStatus(session, this); + session.FillStatus(sessionStatus); + + result.Add(sessionStatus); + } + + return Task.FromResult((IList)result); + } + + public void DispatchApplicationMessage(MqttApplicationMessage applicationMessage, MqttClientConnection sender) { if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage)); - _messageQueue.Add(new MqttEnqueuedApplicationMessage(senderClientSession, applicationMessage), _cancellationToken); + _messageQueue.Add(new MqttEnqueuedApplicationMessage(applicationMessage, sender), _cancellationToken); } - public async Task SubscribeAsync(string clientId, IEnumerable topicFilters) + public Task SubscribeAsync(string clientId, ICollection topicFilters) { if (clientId == null) throw new ArgumentNullException(nameof(clientId)); if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters)); - using (await _sessionsLock.WaitAsync(CancellationToken.None).ConfigureAwait(false)) + if (!_sessions.TryGetValue(clientId, out var session)) { - if (!_sessions.TryGetValue(clientId, out var session)) - { - throw new InvalidOperationException($"Client session '{clientId}' is unknown."); - } - - await session.SubscribeAsync(topicFilters).ConfigureAwait(false); + throw new InvalidOperationException($"Client session '{clientId}' is unknown."); } + + return session.SubscribeAsync(topicFilters, _retainedMessagesManager); } - public async Task UnsubscribeAsync(string clientId, IEnumerable topicFilters) + public Task UnsubscribeAsync(string clientId, IEnumerable topicFilters) { if (clientId == null) throw new ArgumentNullException(nameof(clientId)); if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters)); - using (await _sessionsLock.WaitAsync(CancellationToken.None).ConfigureAwait(false)) + if (!_sessions.TryGetValue(clientId, out var session)) { - if (!_sessions.TryGetValue(clientId, out var session)) - { - throw new InvalidOperationException($"Client session '{clientId}' is unknown."); - } - - await session.UnsubscribeAsync(topicFilters).ConfigureAwait(false); + throw new InvalidOperationException($"Client session '{clientId}' is unknown."); } + + return session.UnsubscribeAsync(topicFilters); } public async Task DeleteSessionAsync(string clientId) { - using (await _sessionsLock.WaitAsync(CancellationToken.None).ConfigureAwait(false)) + if (_connections.TryGetValue(clientId, out var connection)) { - if (_sessions.TryGetValue(clientId, out var session)) - { - session.Dispose(); - } + await connection.StopAsync(); + } - _sessions.Remove(clientId); + if (_sessions.TryRemove(clientId, out var session)) + { } _logger.Verbose("Session for client '{0}' deleted.", clientId); @@ -179,7 +180,7 @@ namespace MQTTnet.Server { if (interceptorContext.CloseConnection) { - await enqueuedApplicationMessage.Sender.StopAsync(MqttClientDisconnectType.NotClean).ConfigureAwait(false); + await enqueuedApplicationMessage.Sender.StopAsync().ConfigureAwait(false); } if (interceptorContext.ApplicationMessage == null || !interceptorContext.AcceptPublish) @@ -197,15 +198,12 @@ namespace MQTTnet.Server await _retainedMessagesManager.HandleMessageAsync(sender?.ClientId, applicationMessage).ConfigureAwait(false); } - using (await _sessionsLock.WaitAsync(CancellationToken.None).ConfigureAwait(false)) + foreach (var clientSession in _sessions.Values) { - foreach (var clientSession in _sessions.Values) - { - await clientSession.EnqueueApplicationMessageAsync( - enqueuedApplicationMessage.Sender, - enqueuedApplicationMessage.ApplicationMessage, - false).ConfigureAwait(false); - } + clientSession.EnqueueApplicationMessage( + enqueuedApplicationMessage.ApplicationMessage, + sender?.ClientId, + false); } } catch (OperationCanceledException) @@ -219,6 +217,7 @@ namespace MQTTnet.Server private async Task HandleConnectionAsync(IMqttChannelAdapter channelAdapter, CancellationToken cancellationToken) { + var disconnectType = MqttClientDisconnectType.NotClean; var clientId = string.Empty; try @@ -232,22 +231,31 @@ namespace MQTTnet.Server clientId = connectPacket.ClientId; - var connectReturnCode = await ValidateConnectionAsync(connectPacket, channelAdapter).ConfigureAwait(false); - if (connectReturnCode != MqttConnectReturnCode.ConnectionAccepted) + var validatorContext = await ValidateConnectionAsync(connectPacket, channelAdapter).ConfigureAwait(false); + + if (validatorContext.ReturnCode != MqttConnectReturnCode.ConnectionAccepted) { + // TODO: Move to channel adapter data converter. + + // Send failure response here without preparing a session. The result for a successful connect + // will be sent from the session itself. await channelAdapter.SendPacketAsync( new MqttConnAckPacket { - ReturnCode = connectReturnCode, + ReturnCode = validatorContext.ReturnCode, ReasonCode = MqttConnectReasonCode.NotAuthorized }, + _options.DefaultCommunicationTimeout, cancellationToken).ConfigureAwait(false); return; } - var session = await PrepareClientSessionAsync(connectPacket).ConfigureAwait(false); - await session.RunAsync(connectPacket, channelAdapter).ConfigureAwait(false); + var connection = await CreateConnectionAsync(channelAdapter, connectPacket).ConfigureAwait(false); + + _eventDispatcher.OnClientConnected(clientId); + + disconnectType = await connection.RunAsync().ConfigureAwait(false); } catch (OperationCanceledException) { @@ -258,26 +266,29 @@ namespace MQTTnet.Server } finally { + _connections.TryRemove(clientId, out _); + + ////connection?.ReferenceCounter.Decrement(); + ////if (connection?.ReferenceCounter.HasReferences == true) + ////{ + //// disconnectType = MqttClientDisconnectType.Takeover; + ////} + ////else + { + if (!_options.EnablePersistentSessions) + { + await DeleteSessionAsync(clientId).ConfigureAwait(false); + } + } + await TryCleanupChannelAsync(channelAdapter).ConfigureAwait(false); - if (!_options.EnablePersistentSessions) - { - // TODO: Check if the session will be used later. - // Consider reference counter or "Recycle" property - // Or add timer (will be required for MQTTv5 (session life time) "IsActiveProperty". - //öö - //await DeleteSessionAsync(clientId).ConfigureAwait(false); - } + _eventDispatcher.OnClientDisconnected(clientId, disconnectType); } } - private async Task ValidateConnectionAsync(MqttConnectPacket connectPacket, IMqttChannelAdapter clientAdapter) + private async Task ValidateConnectionAsync(MqttConnectPacket connectPacket, IMqttChannelAdapter clientAdapter) { - if (_options.ConnectionValidator == null) - { - return MqttConnectReturnCode.ConnectionAccepted; - } - var context = new MqttConnectionValidatorContext( connectPacket.ClientId, connectPacket.Username, @@ -285,47 +296,69 @@ namespace MQTTnet.Server connectPacket.WillMessage, clientAdapter.Endpoint); - await _options.ConnectionValidator.ValidateConnection(context).ConfigureAwait(false); - return context.ReturnCode; + var connectionValidator = _options.ConnectionValidator; + + if (connectionValidator == null) + { + context.ReturnCode = MqttConnectReturnCode.ConnectionAccepted; + return context; + } + + await connectionValidator.ValidateConnectionAsync(context).ConfigureAwait(false); + return context; } - private async Task PrepareClientSessionAsync(MqttConnectPacket connectPacket) + private readonly SemaphoreSlim _createConnectionGate = new SemaphoreSlim(1, 1); + + private async Task CreateConnectionAsync(IMqttChannelAdapter channelAdapter, MqttConnectPacket connectPacket) { - using (await _sessionsLock.WaitAsync(CancellationToken.None).ConfigureAwait(false)) + await _createConnectionGate.WaitAsync(_cancellationToken).ConfigureAwait(false); + try { - var isSessionPresent = _sessions.TryGetValue(connectPacket.ClientId, out var clientSession); + var isSessionPresent = _sessions.TryGetValue(connectPacket.ClientId, out var session); + + var isConnectionPresent = _connections.TryGetValue(connectPacket.ClientId, out var existingConnection); + if (isConnectionPresent) + { + await existingConnection.StopAsync().ConfigureAwait(false); + } + if (isSessionPresent) { if (connectPacket.CleanSession) { - await clientSession.StopAsync(MqttClientDisconnectType.Clean).ConfigureAwait(false); - - clientSession.Dispose(); - clientSession = null; - - _logger.Verbose("Stopped existing session of client '{0}'.", connectPacket.ClientId); + // TODO: Check if required. + //session.Dispose(); + session = null; + + _logger.Verbose("Deleting existing session of client '{0}'.", connectPacket.ClientId); } else { - await clientSession.StopAsync(MqttClientDisconnectType.Clean).ConfigureAwait(false); - _logger.Verbose("Reusing existing session of client '{0}'.", connectPacket.ClientId); } } - if (clientSession == null) + if (session == null) { - clientSession = new MqttClientSession(connectPacket.ClientId, _options, this, _retainedMessagesManager, _eventDispatcher, _logger); - _sessions[connectPacket.ClientId] = clientSession; - + session = new MqttClientSession(connectPacket.ClientId, _eventDispatcher, _options); _logger.Verbose("Created a new session for client '{0}'.", connectPacket.ClientId); } - return clientSession; + var connection = new MqttClientConnection(connectPacket, channelAdapter, session, _options, this, _retainedMessagesManager, _logger); + + _connections[connection.ClientId] = connection; + _sessions[session.ClientId] = session; + + return connection; + } + finally + { + _createConnectionGate.Release(); } } - private async Task InterceptApplicationMessageAsync(MqttClientSession sender, MqttApplicationMessage applicationMessage) + private async Task InterceptApplicationMessageAsync(MqttClientConnection sender, MqttApplicationMessage applicationMessage) { var interceptor = _options.ApplicationMessageInterceptor; if (interceptor == null) diff --git a/Source/MQTTnet/Server/MqttClientSubscriptionsManager.cs b/Source/MQTTnet/Server/MqttClientSubscriptionsManager.cs index 3186104e..d4c2eead 100644 --- a/Source/MQTTnet/Server/MqttClientSubscriptionsManager.cs +++ b/Source/MQTTnet/Server/MqttClientSubscriptionsManager.cs @@ -14,7 +14,7 @@ namespace MQTTnet.Server private readonly MqttServerEventDispatcher _eventDispatcher; private readonly string _clientId; - public MqttClientSubscriptionsManager(string clientId, IMqttServerOptions options, MqttServerEventDispatcher eventDispatcher) + public MqttClientSubscriptionsManager(string clientId, MqttServerEventDispatcher eventDispatcher, IMqttServerOptions options) { _clientId = clientId ?? throw new ArgumentNullException(nameof(clientId)); _options = options ?? throw new ArgumentNullException(nameof(options)); @@ -68,7 +68,29 @@ namespace MQTTnet.Server return result; } - public MqttUnsubAckPacket Unsubscribe(MqttUnsubscribePacket unsubscribePacket) + public async Task SubscribeAsync(IEnumerable topicFilters) + { + foreach (var topicFilter in topicFilters) + { + var interceptorContext = await InterceptSubscribeAsync(topicFilter).ConfigureAwait(false); + if (!interceptorContext.AcceptSubscription) + { + continue; + } + + if (interceptorContext.AcceptSubscription) + { + lock (_subscriptions) + { + _subscriptions[topicFilter.Topic] = topicFilter.QualityOfServiceLevel; + } + + _eventDispatcher.OnClientSubscribedTopic(_clientId, topicFilter); + } + } + } + + public Task UnsubscribeAsync(MqttUnsubscribePacket unsubscribePacket) { if (unsubscribePacket == null) throw new ArgumentNullException(nameof(unsubscribePacket)); @@ -94,7 +116,22 @@ namespace MQTTnet.Server } } - return unsubAckPacket; + return Task.FromResult(unsubAckPacket); + } + + public Task UnsubscribeAsync(IEnumerable topicFilters) + { + if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters)); + + lock (_subscriptions) + { + foreach (var topicFilter in topicFilters) + { + _subscriptions.Remove(topicFilter); + } + } + + return Task.FromResult(0); } public CheckSubscriptionsResult CheckSubscriptions(string topic, MqttQualityOfServiceLevel qosLevel) diff --git a/Source/MQTTnet/Server/MqttEnqueuedApplicationMessage.cs b/Source/MQTTnet/Server/MqttEnqueuedApplicationMessage.cs index 20ff2feb..47112692 100644 --- a/Source/MQTTnet/Server/MqttEnqueuedApplicationMessage.cs +++ b/Source/MQTTnet/Server/MqttEnqueuedApplicationMessage.cs @@ -2,13 +2,13 @@ { public class MqttEnqueuedApplicationMessage { - public MqttEnqueuedApplicationMessage(MqttClientSession sender, MqttApplicationMessage applicationMessage) + public MqttEnqueuedApplicationMessage(MqttApplicationMessage applicationMessage, MqttClientConnection sender) { Sender = sender; ApplicationMessage = applicationMessage; } - public MqttClientSession Sender { get; } + public MqttClientConnection Sender { get; } public MqttApplicationMessage ApplicationMessage { get; } } diff --git a/Source/MQTTnet/Server/MqttRetainedMessagesManager.cs b/Source/MQTTnet/Server/MqttRetainedMessagesManager.cs index 52533d14..7fb7b533 100644 --- a/Source/MQTTnet/Server/MqttRetainedMessagesManager.cs +++ b/Source/MQTTnet/Server/MqttRetainedMessagesManager.cs @@ -101,29 +101,34 @@ namespace MQTTnet.Server public async Task> GetSubscribedMessagesAsync(ICollection topicFilters) { - var retainedMessages = new List(); + if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters)); + var matchingRetainedMessages = new List(); + + List retainedMessages; using (await _messagesLock.WaitAsync().ConfigureAwait(false)) { - foreach (var retainedMessage in _messages.Values) - { - foreach (var topicFilter in topicFilters) - { - if (!MqttTopicFilterComparer.IsMatch(retainedMessage.Topic, topicFilter.Topic)) - { - continue; - } - - retainedMessages.Add(retainedMessage); - break; - } - } + retainedMessages = _messages.Values.ToList(); } - return retainedMessages; + foreach (var retainedMessage in retainedMessages) + { + foreach (var topicFilter in topicFilters) + { + if (!MqttTopicFilterComparer.IsMatch(retainedMessage.Topic, topicFilter.Topic)) + { + continue; + } + + matchingRetainedMessages.Add(retainedMessage); + break; + } + } + + return matchingRetainedMessages; } - public async Task> GetMessagesAsync() + public async Task> GetMessagesAsync() { using (await _messagesLock.WaitAsync().ConfigureAwait(false)) { diff --git a/Source/MQTTnet/Server/MqttServer.cs b/Source/MQTTnet/Server/MqttServer.cs index d2145bda..4d36e4cf 100644 --- a/Source/MQTTnet/Server/MqttServer.cs +++ b/Source/MQTTnet/Server/MqttServer.cs @@ -7,6 +7,7 @@ using MQTTnet.Adapter; using MQTTnet.Client.Publishing; using MQTTnet.Client.Receiving; using MQTTnet.Diagnostics; +using MQTTnet.Server.Status; namespace MQTTnet.Server { @@ -59,17 +60,22 @@ namespace MQTTnet.Server public IMqttServerOptions Options { get; private set; } - public Task> GetClientSessionsStatusAsync() + public Task> GetClientStatusAsync() { return _clientSessionsManager.GetClientStatusAsync(); } - public IList GetRetainedMessages() + public Task> GetSessionStatusAsync() { - return _retainedMessagesManager.GetMessagesAsync().GetAwaiter().GetResult(); + return _clientSessionsManager.GetSessionStatusAsync(); } - public Task SubscribeAsync(string clientId, IEnumerable topicFilters) + public Task> GetRetainedMessagesAsync() + { + return _retainedMessagesManager.GetMessagesAsync(); + } + + public Task SubscribeAsync(string clientId, ICollection topicFilters) { if (clientId == null) throw new ArgumentNullException(nameof(clientId)); if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters)); @@ -77,7 +83,7 @@ namespace MQTTnet.Server return _clientSessionsManager.SubscribeAsync(clientId, topicFilters); } - public Task UnsubscribeAsync(string clientId, IEnumerable topicFilters) + public Task UnsubscribeAsync(string clientId, ICollection topicFilters) { if (clientId == null) throw new ArgumentNullException(nameof(clientId)); if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters)); @@ -85,13 +91,13 @@ namespace MQTTnet.Server return _clientSessionsManager.UnsubscribeAsync(clientId, topicFilters); } - public Task PublishAsync(MqttApplicationMessage applicationMessage) + public Task PublishAsync(MqttApplicationMessage applicationMessage, CancellationToken cancellationToken) { if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage)); if (_cancellationTokenSource == null) throw new InvalidOperationException("The server is not started."); - _clientSessionsManager.EnqueueApplicationMessage(null, applicationMessage); + _clientSessionsManager.DispatchApplicationMessage(applicationMessage, null); return Task.FromResult(new MqttClientPublishResult()); } diff --git a/Source/MQTTnet/Server/MqttServerConnectionValidatorDelegate.cs b/Source/MQTTnet/Server/MqttServerConnectionValidatorDelegate.cs index 954d10d1..8be08c8d 100644 --- a/Source/MQTTnet/Server/MqttServerConnectionValidatorDelegate.cs +++ b/Source/MQTTnet/Server/MqttServerConnectionValidatorDelegate.cs @@ -23,7 +23,7 @@ namespace MQTTnet.Server _callback = callback ?? throw new ArgumentNullException(nameof(callback)); } - public Task ValidateConnection(MqttConnectionValidatorContext context) + public Task ValidateConnectionAsync(MqttConnectionValidatorContext context) { return _callback(context); } diff --git a/Source/MQTTnet/Server/MqttServerEventDispatcher.cs b/Source/MQTTnet/Server/MqttServerEventDispatcher.cs index 8fd5652d..03ed93d5 100644 --- a/Source/MQTTnet/Server/MqttServerEventDispatcher.cs +++ b/Source/MQTTnet/Server/MqttServerEventDispatcher.cs @@ -24,9 +24,9 @@ namespace MQTTnet.Server ClientUnsubscribedTopic?.Invoke(this, new MqttClientUnsubscribedTopicEventArgs(clientId, topicFilter)); } - public void OnClientDisconnected(string clientId, bool wasCleanDisconnect) + public void OnClientDisconnected(string clientId, MqttClientDisconnectType disconnectType) { - ClientDisconnected?.Invoke(this, new MqttClientDisconnectedEventArgs(clientId, wasCleanDisconnect)); + ClientDisconnected?.Invoke(this, new MqttClientDisconnectedEventArgs(clientId, disconnectType)); } public void OnApplicationMessageReceived(string senderClientId, MqttApplicationMessage applicationMessage) diff --git a/Source/MQTTnet/Server/MqttServerExtensions.cs b/Source/MQTTnet/Server/MqttServerExtensions.cs index e320f130..c7050718 100644 --- a/Source/MQTTnet/Server/MqttServerExtensions.cs +++ b/Source/MQTTnet/Server/MqttServerExtensions.cs @@ -1,5 +1,9 @@ using System; +using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; +using MQTTnet.Client; +using MQTTnet.Client.Publishing; using MQTTnet.Protocol; namespace MQTTnet.Server @@ -41,5 +45,89 @@ namespace MQTTnet.Server return server.UnsubscribeAsync(clientId, topicFilters); } + + public static async Task PublishAsync(this IMqttServer server, IEnumerable applicationMessages) + { + if (server == null) throw new ArgumentNullException(nameof(server)); + if (applicationMessages == null) throw new ArgumentNullException(nameof(applicationMessages)); + + foreach (var applicationMessage in applicationMessages) + { + await server.PublishAsync(applicationMessage).ConfigureAwait(false); + } + } + + public static Task PublishAsync(this IMqttServer server, MqttApplicationMessage applicationMessage) + { + if (server == null) throw new ArgumentNullException(nameof(server)); + if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage)); + + return server.PublishAsync(applicationMessage, CancellationToken.None); + } + + public static async Task PublishAsync(this IMqttServer server, params MqttApplicationMessage[] applicationMessages) + { + if (server == null) throw new ArgumentNullException(nameof(server)); + if (applicationMessages == null) throw new ArgumentNullException(nameof(applicationMessages)); + + foreach (var applicationMessage in applicationMessages) + { + await server.PublishAsync(applicationMessage, CancellationToken.None).ConfigureAwait(false); + } + } + + public static Task PublishAsync(this IMqttServer server, string topic) + { + if (server == null) throw new ArgumentNullException(nameof(server)); + if (topic == null) throw new ArgumentNullException(nameof(topic)); + + return server.PublishAsync(builder => builder + .WithTopic(topic)); + } + + public static Task PublishAsync(this IMqttServer server, string topic, string payload) + { + if (server == null) throw new ArgumentNullException(nameof(server)); + if (topic == null) throw new ArgumentNullException(nameof(topic)); + + return server.PublishAsync(builder => builder + .WithTopic(topic) + .WithPayload(payload)); + } + + public static Task PublishAsync(this IMqttServer server, string topic, string payload, MqttQualityOfServiceLevel qualityOfServiceLevel) + { + if (server == null) throw new ArgumentNullException(nameof(server)); + if (topic == null) throw new ArgumentNullException(nameof(topic)); + + return server.PublishAsync(builder => builder + .WithTopic(topic) + .WithPayload(payload) + .WithQualityOfServiceLevel(qualityOfServiceLevel)); + } + + public static Task PublishAsync(this IMqttServer server, string topic, string payload, MqttQualityOfServiceLevel qualityOfServiceLevel, bool retain) + { + if (server == null) throw new ArgumentNullException(nameof(server)); + if (topic == null) throw new ArgumentNullException(nameof(topic)); + + return server.PublishAsync(builder => builder + .WithTopic(topic) + .WithPayload(payload) + .WithQualityOfServiceLevel(qualityOfServiceLevel) + .WithRetainFlag(retain)); + } + + public static Task PublishAsync(this IMqttServer server, Func builder, CancellationToken cancellationToken) + { + var message = builder(new MqttApplicationMessageBuilder()).Build(); + return server.PublishAsync(message, cancellationToken); + } + + public static Task PublishAsync(this IMqttServer server, Func builder) + { + var message = builder(new MqttApplicationMessageBuilder()).Build(); + return server.PublishAsync(message, CancellationToken.None); + } } } diff --git a/Source/MQTTnet/Server/PrepareClientSessionResult.cs b/Source/MQTTnet/Server/PrepareClientSessionResult.cs index 9a655bec..75090376 100644 --- a/Source/MQTTnet/Server/PrepareClientSessionResult.cs +++ b/Source/MQTTnet/Server/PrepareClientSessionResult.cs @@ -4,6 +4,6 @@ { public bool IsExistingSession { get; set; } - public MqttClientSession Session { get; set; } + public MqttClientConnection Session { get; set; } } } diff --git a/Source/MQTTnet/Server/Status/IMqttClientStatus.cs b/Source/MQTTnet/Server/Status/IMqttClientStatus.cs new file mode 100644 index 00000000..b751f29a --- /dev/null +++ b/Source/MQTTnet/Server/Status/IMqttClientStatus.cs @@ -0,0 +1,31 @@ +using System; +using System.Threading.Tasks; +using MQTTnet.Formatter; + +namespace MQTTnet.Server.Status +{ + public interface IMqttClientStatus + { + string ClientId { get; } + + string Endpoint { get; } + + MqttProtocolVersion ProtocolVersion { get; } + + DateTime LastPacketReceivedTimestamp { get; } + + DateTime LastNonKeepAlivePacketReceivedTimestamp { get; } + + long ReceivedApplicationMessagesCount { get; } + + long SentApplicationMessagesCount { get; } + + long ReceivedPacketsCount { get; } + + long SentPacketsCount { get; } + + IMqttSessionStatus Session { get; } + + Task DisconnectAsync(); + } +} diff --git a/Source/MQTTnet/Server/Status/IMqttSessionStatus.cs b/Source/MQTTnet/Server/Status/IMqttSessionStatus.cs new file mode 100644 index 00000000..ada8efed --- /dev/null +++ b/Source/MQTTnet/Server/Status/IMqttSessionStatus.cs @@ -0,0 +1,17 @@ +using System.Threading.Tasks; + +namespace MQTTnet.Server.Status +{ + public interface IMqttSessionStatus + { + string ClientId { get; set; } + + bool IsConnected { get; } + + long PendingApplicationMessagesCount { get; set; } + + Task ClearPendingApplicationMessagesAsync(); + + Task DeleteAsync(); + } +} \ No newline at end of file diff --git a/Source/MQTTnet/Server/Status/MqttClientStatus.cs b/Source/MQTTnet/Server/Status/MqttClientStatus.cs new file mode 100644 index 00000000..79daa467 --- /dev/null +++ b/Source/MQTTnet/Server/Status/MqttClientStatus.cs @@ -0,0 +1,43 @@ +using System; +using System.Threading.Tasks; +using MQTTnet.Formatter; + +namespace MQTTnet.Server.Status +{ + public class MqttClientStatus : IMqttClientStatus + { + private readonly MqttClientSessionsManager _sessionsManager; + private readonly MqttClientConnection _connection; + + public MqttClientStatus(MqttClientConnection connection, MqttClientSessionsManager sessionsManager) + { + _connection = connection; + _sessionsManager = sessionsManager; + } + + public string ClientId { get; set; } + + public string Endpoint { get; set; } + + public MqttProtocolVersion ProtocolVersion { get; set; } + + public DateTime LastPacketReceivedTimestamp { get; set; } + + public DateTime LastNonKeepAlivePacketReceivedTimestamp { get; set; } + + public long ReceivedApplicationMessagesCount { get; set; } + + public long SentApplicationMessagesCount { get; set; } + + public long ReceivedPacketsCount { get; set; } + + public long SentPacketsCount { get; set; } + + public IMqttSessionStatus Session { get; set; } + + public Task DisconnectAsync() + { + return _connection.StopAsync(); + } + } +} diff --git a/Source/MQTTnet/Server/Status/MqttSessionStatus.cs b/Source/MQTTnet/Server/Status/MqttSessionStatus.cs new file mode 100644 index 00000000..401fc379 --- /dev/null +++ b/Source/MQTTnet/Server/Status/MqttSessionStatus.cs @@ -0,0 +1,36 @@ +using System; +using System.Threading.Tasks; + +namespace MQTTnet.Server.Status +{ + public class MqttSessionStatus : IMqttSessionStatus + { + private readonly MqttClientSession _session; + private readonly MqttClientSessionsManager _sessionsManager; + + public MqttSessionStatus(MqttClientSession session, MqttClientSessionsManager sessionsManager) + { + _session = session ?? throw new ArgumentNullException(nameof(session)); + _sessionsManager = sessionsManager ?? throw new ArgumentNullException(nameof(sessionsManager)); + } + + public string ClientId { get; set; } + + public long PendingApplicationMessagesCount { get; set; } + + public bool IsConnected { get; set; } + + public DateTime CreatedTimestamp { get; set; } + + public Task DeleteAsync() + { + return _sessionsManager.DeleteSessionAsync(ClientId); + } + + public Task ClearPendingApplicationMessagesAsync() + { + _session.ApplicationMessagesQueue.Clear(); + return Task.FromResult(0); + } + } +} diff --git a/Tests/MQTTnet.AspNetCore.Tests/MqttConnectionContextTest.cs b/Tests/MQTTnet.AspNetCore.Tests/MqttConnectionContextTest.cs index 9edcdc2e..8fb74ae8 100644 --- a/Tests/MQTTnet.AspNetCore.Tests/MqttConnectionContextTest.cs +++ b/Tests/MQTTnet.AspNetCore.Tests/MqttConnectionContextTest.cs @@ -25,7 +25,7 @@ namespace MQTTnet.AspNetCore.Tests pipe.Receive.Writer.Complete(); - await Assert.ThrowsExceptionAsync(() => ctx.ReceivePacketAsync(TimeSpan.FromSeconds(1), CancellationToken.None)); + await Assert.ThrowsExceptionAsync(() => ctx.ReceivePacketAsync(TimeSpan.Zero, CancellationToken.None)); } [TestMethod] @@ -41,7 +41,7 @@ namespace MQTTnet.AspNetCore.Tests { for (int i = 0; i < 100; i++) { - await ctx.SendPacketAsync(new MqttPublishPacket(), CancellationToken.None).ConfigureAwait(false); + await ctx.SendPacketAsync(new MqttPublishPacket(), TimeSpan.Zero, CancellationToken.None).ConfigureAwait(false); } })); diff --git a/Tests/MQTTnet.Benchmarks/ChannelAdapterBenchmark.cs b/Tests/MQTTnet.Benchmarks/ChannelAdapterBenchmark.cs index ea035700..3a5c6961 100644 --- a/Tests/MQTTnet.Benchmarks/ChannelAdapterBenchmark.cs +++ b/Tests/MQTTnet.Benchmarks/ChannelAdapterBenchmark.cs @@ -66,7 +66,7 @@ namespace MQTTnet.Benchmarks for (var i = 0; i < 10000; i++) { - _channelAdapter.SendPacketAsync(_packet, CancellationToken.None).GetAwaiter().GetResult(); + _channelAdapter.SendPacketAsync(_packet, TimeSpan.Zero, CancellationToken.None).GetAwaiter().GetResult(); } _stream.Position = 0; diff --git a/Tests/MQTTnet.Benchmarks/SerializerBenchmark.cs b/Tests/MQTTnet.Benchmarks/SerializerBenchmark.cs index 346626e2..d713dfca 100644 --- a/Tests/MQTTnet.Benchmarks/SerializerBenchmark.cs +++ b/Tests/MQTTnet.Benchmarks/SerializerBenchmark.cs @@ -85,7 +85,7 @@ namespace MQTTnet.Benchmarks throw new NotImplementedException(); } - public Task DisconnectAsync() + public Task DisconnectAsync(CancellationToken cancellationToken) { throw new NotImplementedException(); } diff --git a/Tests/MQTTnet.Core.Tests/ExtensionTests.cs b/Tests/MQTTnet.Core.Tests/ExtensionTests.cs index a2f75629..d1b5c1c7 100644 --- a/Tests/MQTTnet.Core.Tests/ExtensionTests.cs +++ b/Tests/MQTTnet.Core.Tests/ExtensionTests.cs @@ -4,6 +4,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; using MQTTnet.Exceptions; +using MQTTnet.Internal; namespace MQTTnet.Tests { @@ -14,20 +15,20 @@ namespace MQTTnet.Tests [TestMethod] public async Task TimeoutAfter() { - await MQTTnet.Internal.TaskExtensions.TimeoutAfterAsync(ct => Task.Delay(TimeSpan.FromMilliseconds(500), ct), TimeSpan.FromMilliseconds(100), CancellationToken.None); + await MqttTaskTimeout.WaitAsync(ct => Task.Delay(TimeSpan.FromMilliseconds(500), ct), TimeSpan.FromMilliseconds(100), CancellationToken.None); } [ExpectedException(typeof(MqttCommunicationTimedOutException))] [TestMethod] public async Task TimeoutAfterWithResult() { - await MQTTnet.Internal.TaskExtensions.TimeoutAfterAsync(ct => Task.Delay(TimeSpan.FromMilliseconds(500), ct).ContinueWith(t => 5, ct), TimeSpan.FromMilliseconds(100), CancellationToken.None); + await MqttTaskTimeout.WaitAsync(ct => Task.Delay(TimeSpan.FromMilliseconds(500), ct).ContinueWith(t => 5, ct), TimeSpan.FromMilliseconds(100), CancellationToken.None); } [TestMethod] public async Task TimeoutAfterCompleteInTime() { - var result = await MQTTnet.Internal.TaskExtensions.TimeoutAfterAsync(ct => Task.Delay(TimeSpan.FromMilliseconds(100), ct).ContinueWith(t => 5, ct), TimeSpan.FromMilliseconds(500), CancellationToken.None); + var result = await MqttTaskTimeout.WaitAsync(ct => Task.Delay(TimeSpan.FromMilliseconds(100), ct).ContinueWith(t => 5, ct), TimeSpan.FromMilliseconds(500), CancellationToken.None); Assert.AreEqual(5, result); } @@ -36,7 +37,7 @@ namespace MQTTnet.Tests { try { - await MQTTnet.Internal.TaskExtensions.TimeoutAfterAsync(ct => Task.Run(() => + await MqttTaskTimeout.WaitAsync(ct => Task.Run(() => { var iis = new int[0]; iis[1] = 0; @@ -55,7 +56,7 @@ namespace MQTTnet.Tests { try { - await MQTTnet.Internal.TaskExtensions.TimeoutAfterAsync(ct => Task.Run(() => + await MqttTaskTimeout.WaitAsync(ct => Task.Run(() => { var iis = new int[0]; iis[1] = 0; @@ -76,7 +77,7 @@ namespace MQTTnet.Tests var tasks = Enumerable.Range(0, 100000) .Select(i => { - return MQTTnet.Internal.TaskExtensions.TimeoutAfterAsync(ct => Task.Delay(TimeSpan.FromMilliseconds(1), ct), TimeSpan.FromMinutes(1), CancellationToken.None); + return MqttTaskTimeout.WaitAsync(ct => Task.Delay(TimeSpan.FromMilliseconds(1), ct), TimeSpan.FromMinutes(1), CancellationToken.None); }); await Task.WhenAll(tasks); diff --git a/Tests/MQTTnet.Core.Tests/MQTTv5/MqttClientTests.cs b/Tests/MQTTnet.Core.Tests/MQTTv5/Client_Tests.cs similarity index 95% rename from Tests/MQTTnet.Core.Tests/MQTTv5/MqttClientTests.cs rename to Tests/MQTTnet.Core.Tests/MQTTv5/Client_Tests.cs index 3d7c97ce..6c13dbbc 100644 --- a/Tests/MQTTnet.Core.Tests/MQTTv5/MqttClientTests.cs +++ b/Tests/MQTTnet.Core.Tests/MQTTv5/Client_Tests.cs @@ -16,7 +16,7 @@ namespace MQTTnet.Tests.MQTTv5 public class Client_Tests { [TestMethod] - public async Task Client_Connect() + public async Task Connect() { var server = new MqttFactory().CreateMqttServer(); var client = new MqttFactory().CreateMqttClient(); @@ -33,7 +33,7 @@ namespace MQTTnet.Tests.MQTTv5 } [TestMethod] - public async Task Client_Connect_And_Disconnect() + public async Task Connect_And_Disconnect() { var server = new MqttFactory().CreateMqttServer(); var client = new MqttFactory().CreateMqttClient(); @@ -52,7 +52,7 @@ namespace MQTTnet.Tests.MQTTv5 } [TestMethod] - public async Task Client_Subscribe() + public async Task Subscribe() { var server = new MqttFactory().CreateMqttServer(); var client = new MqttFactory().CreateMqttClient(); @@ -75,7 +75,7 @@ namespace MQTTnet.Tests.MQTTv5 } [TestMethod] - public async Task Client_Unsubscribe() + public async Task Unsubscribe() { var server = new MqttFactory().CreateMqttServer(); var client = new MqttFactory().CreateMqttClient(); @@ -99,7 +99,7 @@ namespace MQTTnet.Tests.MQTTv5 } [TestMethod] - public async Task Client_Publish_QoS0() + public async Task Publish_QoS_0() { var server = new MqttFactory().CreateMqttServer(); var client = new MqttFactory().CreateMqttClient(); @@ -121,7 +121,7 @@ namespace MQTTnet.Tests.MQTTv5 } [TestMethod] - public async Task Client_Publish_QoS1() + public async Task Publish_QoS_1() { var server = new MqttFactory().CreateMqttServer(); var client = new MqttFactory().CreateMqttClient(); @@ -143,7 +143,7 @@ namespace MQTTnet.Tests.MQTTv5 } [TestMethod] - public async Task Client_Publish_QoS2() + public async Task Publish_QoS_2() { var server = new MqttFactory().CreateMqttServer(); var client = new MqttFactory().CreateMqttClient(); @@ -165,7 +165,7 @@ namespace MQTTnet.Tests.MQTTv5 } [TestMethod] - public async Task Client_Publish_With_Properties() + public async Task Publish_With_Properties() { var server = new MqttFactory().CreateMqttServer(); var client = new MqttFactory().CreateMqttClient(); diff --git a/Tests/MQTTnet.Core.Tests/TestSetup.cs b/Tests/MQTTnet.Core.Tests/Mockups/TestEnvironment.cs similarity index 88% rename from Tests/MQTTnet.Core.Tests/TestSetup.cs rename to Tests/MQTTnet.Core.Tests/Mockups/TestEnvironment.cs index b1515ca5..d15d9280 100644 --- a/Tests/MQTTnet.Core.Tests/TestSetup.cs +++ b/Tests/MQTTnet.Core.Tests/Mockups/TestEnvironment.cs @@ -7,9 +7,9 @@ using MQTTnet.Client.Options; using MQTTnet.Diagnostics; using MQTTnet.Server; -namespace MQTTnet.Tests +namespace MQTTnet.Tests.Mockups { - public class TestSetup : IDisposable + public class TestEnvironment : IDisposable { private readonly MqttFactory _mqttFactory = new MqttFactory(); private readonly List _clients = new List(); @@ -24,9 +24,12 @@ namespace MQTTnet.Tests private IMqttServer _server; public bool IgnoreClientLogErrors { get; set; } + public bool IgnoreServerLogErrors { get; set; } - public TestSetup() + public int ServerPort { get; set; } = 1888; + + public TestEnvironment() { _serverLogger.LogMessagePublished += (s, e) => { @@ -51,6 +54,11 @@ namespace MQTTnet.Tests }; } + public IMqttClient CreateClient() + { + return _mqttFactory.CreateMqttClient(_clientLogger); + } + public Task StartServerAsync() { return StartServerAsync(new MqttServerOptionsBuilder()); @@ -64,7 +72,7 @@ namespace MQTTnet.Tests } _server = _mqttFactory.CreateMqttServer(_serverLogger); - await _server.StartAsync(options.WithDefaultEndpointPort(1888).Build()); + await _server.StartAsync(options.WithDefaultEndpointPort(ServerPort).Build()); return _server; } @@ -77,7 +85,7 @@ namespace MQTTnet.Tests public async Task ConnectClientAsync(MqttClientOptionsBuilder options) { var client = _mqttFactory.CreateMqttClient(_clientLogger); - await client.ConnectAsync(options.WithTcpServer("localhost", 1888).Build()); + await client.ConnectAsync(options.WithTcpServer("localhost", ServerPort).Build()); _clients.Add(client); return client; @@ -106,17 +114,16 @@ namespace MQTTnet.Tests { foreach (var mqttClient in _clients) { - mqttClient?.DisconnectAsync().GetAwaiter().GetResult(); mqttClient?.Dispose(); } - _server.StopAsync().GetAwaiter().GetResult(); + _server?.StopAsync().GetAwaiter().GetResult(); ThrowIfLogErrors(); if (_exceptions.Any()) { - throw new Exception($"{_exceptions.Count} exceptions tracked."); + throw new Exception($"{_exceptions.Count} exceptions tracked.\r\n" + string.Join(Environment.NewLine, _exceptions)); } } diff --git a/Tests/MQTTnet.Core.Tests/TestMqttCommunicationAdapter.cs b/Tests/MQTTnet.Core.Tests/Mockups/TestMqttCommunicationAdapter.cs similarity index 67% rename from Tests/MQTTnet.Core.Tests/TestMqttCommunicationAdapter.cs rename to Tests/MQTTnet.Core.Tests/Mockups/TestMqttCommunicationAdapter.cs index 4a7475d6..73676ddd 100644 --- a/Tests/MQTTnet.Core.Tests/TestMqttCommunicationAdapter.cs +++ b/Tests/MQTTnet.Core.Tests/Mockups/TestMqttCommunicationAdapter.cs @@ -6,7 +6,7 @@ using MQTTnet.Adapter; using MQTTnet.Formatter; using MQTTnet.Packets; -namespace MQTTnet.Tests +namespace MQTTnet.Tests.Mockups { public class TestMqttCommunicationAdapter : IMqttChannelAdapter { @@ -35,7 +35,7 @@ namespace MQTTnet.Tests return Task.FromResult(0); } - public Task SendPacketAsync(MqttBasePacket packet, CancellationToken cancellationToken) + public Task SendPacketAsync(MqttBasePacket packet, TimeSpan timeout, CancellationToken cancellationToken) { ThrowIfPartnerIsNull(); @@ -44,30 +44,11 @@ namespace MQTTnet.Tests return Task.FromResult(0); } - public async Task ReceivePacketAsync(TimeSpan timeout, CancellationToken cancellationToken) + public Task ReceivePacketAsync(TimeSpan timeout, CancellationToken cancellationToken) { ThrowIfPartnerIsNull(); - - if (timeout > TimeSpan.Zero) - { - using (var timeoutCts = new CancellationTokenSource(timeout)) - using (var cts = CancellationTokenSource.CreateLinkedTokenSource(timeoutCts.Token, cancellationToken)) - { - return await Task.Run(() => - { - try - { - return _incomingPackets.Take(cts.Token); - } - catch - { - return null; - } - }, cts.Token); - } - } - - return await Task.Run(() => + + return Task.Run(() => { try { diff --git a/Tests/MQTTnet.Core.Tests/TestMqttCommunicationAdapterFactory.cs b/Tests/MQTTnet.Core.Tests/Mockups/TestMqttCommunicationAdapterFactory.cs similarity index 92% rename from Tests/MQTTnet.Core.Tests/TestMqttCommunicationAdapterFactory.cs rename to Tests/MQTTnet.Core.Tests/Mockups/TestMqttCommunicationAdapterFactory.cs index 0263ff66..ff47f710 100644 --- a/Tests/MQTTnet.Core.Tests/TestMqttCommunicationAdapterFactory.cs +++ b/Tests/MQTTnet.Core.Tests/Mockups/TestMqttCommunicationAdapterFactory.cs @@ -1,9 +1,8 @@ using MQTTnet.Adapter; -using MQTTnet.Client; using MQTTnet.Client.Options; using MQTTnet.Diagnostics; -namespace MQTTnet.Tests +namespace MQTTnet.Tests.Mockups { public class TestMqttCommunicationAdapterFactory : IMqttClientAdapterFactory { diff --git a/Tests/MQTTnet.Core.Tests/TestMqttServerAdapter.cs b/Tests/MQTTnet.Core.Tests/Mockups/TestMqttServerAdapter.cs similarity index 98% rename from Tests/MQTTnet.Core.Tests/TestMqttServerAdapter.cs rename to Tests/MQTTnet.Core.Tests/Mockups/TestMqttServerAdapter.cs index cfcba04a..014a9063 100644 --- a/Tests/MQTTnet.Core.Tests/TestMqttServerAdapter.cs +++ b/Tests/MQTTnet.Core.Tests/Mockups/TestMqttServerAdapter.cs @@ -7,7 +7,7 @@ using MQTTnet.Client.Options; using MQTTnet.Diagnostics; using MQTTnet.Server; -namespace MQTTnet.Tests +namespace MQTTnet.Tests.Mockups { public class TestMqttServerAdapter : IMqttServerAdapter { diff --git a/Tests/MQTTnet.Core.Tests/TestServerExtensions.cs b/Tests/MQTTnet.Core.Tests/Mockups/TestServerExtensions.cs similarity index 97% rename from Tests/MQTTnet.Core.Tests/TestServerExtensions.cs rename to Tests/MQTTnet.Core.Tests/Mockups/TestServerExtensions.cs index 5bfa2c89..1cbd1b75 100644 --- a/Tests/MQTTnet.Core.Tests/TestServerExtensions.cs +++ b/Tests/MQTTnet.Core.Tests/Mockups/TestServerExtensions.cs @@ -2,7 +2,7 @@ using MQTTnet.Client; using MQTTnet.Server; -namespace MQTTnet.Tests +namespace MQTTnet.Tests.Mockups { public static class TestServerExtensions { diff --git a/Tests/MQTTnet.Core.Tests/Mockups/TestServerStorage.cs b/Tests/MQTTnet.Core.Tests/Mockups/TestServerStorage.cs new file mode 100644 index 00000000..55ca6bb1 --- /dev/null +++ b/Tests/MQTTnet.Core.Tests/Mockups/TestServerStorage.cs @@ -0,0 +1,22 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using MQTTnet.Server; + +namespace MQTTnet.Tests.Mockups +{ + public class TestServerStorage : IMqttServerStorage + { + public IList Messages = new List(); + + public Task SaveRetainedMessagesAsync(IList messages) + { + Messages = messages; + return Task.CompletedTask; + } + + public Task> LoadRetainedMessagesAsync() + { + return Task.FromResult(Messages); + } + } +} diff --git a/Tests/MQTTnet.Core.Tests/MqttClientTests.cs b/Tests/MQTTnet.Core.Tests/MqttClientTests.cs index 9725fee0..9604a616 100644 --- a/Tests/MQTTnet.Core.Tests/MqttClientTests.cs +++ b/Tests/MQTTnet.Core.Tests/MqttClientTests.cs @@ -2,7 +2,6 @@ using System; using System.Collections.Generic; using System.Linq; using System.Net.Sockets; -using System.Threading; using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; using MQTTnet.Client; @@ -11,50 +10,73 @@ using MQTTnet.Client.Receiving; using MQTTnet.Exceptions; using MQTTnet.Protocol; using MQTTnet.Server; +using MQTTnet.Tests.Mockups; namespace MQTTnet.Tests { [TestClass] - public class MqttClientTests + public class Client_Tests { [TestMethod] - public async Task Client_Disconnect_Exception() + public async Task Invalid_Connect_Throws_Exception() { var factory = new MqttFactory(); - var client = factory.CreateMqttClient(); - - Exception ex = null; - client.Disconnected += (s, e) => + using (var client = factory.CreateMqttClient()) { - ex = e.Exception; - }; + try + { + await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("wrong-server").Build()); - try - { - await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("wrong-server").Build()); + Assert.Fail("Must fail!"); + } + catch (Exception exception) + { + Assert.IsNotNull(exception); + Assert.IsInstanceOfType(exception, typeof(MqttCommunicationException)); + Assert.IsInstanceOfType(exception.InnerException, typeof(SocketException)); + } } - catch - { - } - - Assert.IsNotNull(ex); - Assert.IsInstanceOfType(ex, typeof(MqttCommunicationException)); - Assert.IsInstanceOfType(ex.InnerException, typeof(SocketException)); } [TestMethod] - public async Task Client_Preserve_Message_Order() + public async Task Disconnect_Event_Contains_Exception() + { + var factory = new MqttFactory(); + using (var client = factory.CreateMqttClient()) + { + Exception ex = null; + client.Disconnected += (s, e) => + { + ex = e.Exception; + }; + + try + { + await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("wrong-server").Build()); + } + catch + { + } + + Assert.IsNotNull(ex); + Assert.IsInstanceOfType(ex, typeof(MqttCommunicationException)); + Assert.IsInstanceOfType(ex.InnerException, typeof(SocketException)); + } + } + + [TestMethod] + public async Task Preserve_Message_Order() { // The messages are sent in reverse or to ensure that the delay in the handler // needs longer for the first messages and later messages may be processed earlier (if there // is an issue). const int MessagesCount = 50; - using (var testSetup = new TestSetup()) + using (var testEnvironment = new TestEnvironment()) { - await testSetup.StartServerAsync(); + await testEnvironment.StartServerAsync(); - var client1 = await testSetup.ConnectClientAsync(); + var client1 = await testEnvironment.ConnectClientAsync(); await client1.SubscribeAsync("x"); var receivedValues = new List(); @@ -72,7 +94,7 @@ namespace MQTTnet.Tests client1.UseReceivedApplicationMessageHandler(Handler1); - var client2 = await testSetup.ConnectClientAsync(); + var client2 = await testEnvironment.ConnectClientAsync(); for (var i = MessagesCount; i > 0; i--) { await client2.PublishAsync("x", i.ToString()); @@ -88,13 +110,13 @@ namespace MQTTnet.Tests } [TestMethod] - public async Task Client_Send_Reply_For_Any_Received_Message() + public async Task Send_Reply_For_Any_Received_Message() { - using (var testSetup = new TestSetup()) + using (var testEnvironment = new TestEnvironment()) { - await testSetup.StartServerAsync(); + await testEnvironment.StartServerAsync(); - var client1 = await testSetup.ConnectClientAsync(); + var client1 = await testEnvironment.ConnectClientAsync(); await client1.SubscribeAsync("request/+"); async Task Handler1(MqttApplicationMessageHandlerContext context) @@ -104,7 +126,7 @@ namespace MQTTnet.Tests client1.UseReceivedApplicationMessageHandler(Handler1); - var client2 = await testSetup.ConnectClientAsync(); + var client2 = await testEnvironment.ConnectClientAsync(); await client2.SubscribeAsync("reply/#"); var replies = new List(); @@ -132,30 +154,26 @@ namespace MQTTnet.Tests } [TestMethod] - public async Task Client_Publish() + public async Task Publish_With_Correct_Retain_Flag() { - var server = new MqttFactory().CreateMqttServer(); - - try + using (var testEnvironment = new TestEnvironment()) { + await testEnvironment.StartServerAsync(); + var receivedMessages = new List(); - await server.StartAsync(new MqttServerOptions()); - - var client1 = new MqttFactory().CreateMqttClient(); - client1.ApplicationMessageReceived += (_, e) => + var client1 = await testEnvironment.ConnectClientAsync(); + client1.UseReceivedApplicationMessageHandler(c => { lock (receivedMessages) { - receivedMessages.Add(e.ApplicationMessage); + receivedMessages.Add(c.ApplicationMessage); } - }; + }); - await client1.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1").Build()); await client1.SubscribeAsync("a"); - var client2 = new MqttFactory().CreateMqttClient(); - await client2.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1").Build()); + var client2 = await testEnvironment.ConnectClientAsync(); var message = new MqttApplicationMessageBuilder().WithTopic("a").WithRetainFlag().Build(); await client2.PublishAsync(message); @@ -164,26 +182,18 @@ namespace MQTTnet.Tests Assert.AreEqual(1, receivedMessages.Count); Assert.IsFalse(receivedMessages.First().Retain); // Must be false even if set above! } - finally - { - await server.StopAsync(); - } } [TestMethod] - public async Task Publish_Special_Content() + public async Task Subscribe_In_Callback_Events() { - var factory = new MqttFactory(); - var server = factory.CreateMqttServer(); - var serverOptions = new MqttServerOptionsBuilder().Build(); - - var receivedMessages = new List(); - - var client = factory.CreateMqttClient(); - - try + using (var testEnvironment = new TestEnvironment()) { - await server.StartAsync(serverOptions); + await testEnvironment.StartServerAsync(); + + var receivedMessages = new List(); + + var client = testEnvironment.CreateClient(); client.Connected += async (s, e) => { @@ -196,43 +206,37 @@ namespace MQTTnet.Tests await client.PublishAsync(msg.Build()); }; - client.ApplicationMessageReceived += (s, e) => + client.UseReceivedApplicationMessageHandler(c => { lock (receivedMessages) { - receivedMessages.Add(e.ApplicationMessage); + receivedMessages.Add(c.ApplicationMessage); } - }; + }); - await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("localhost").Build()); + await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("localhost", testEnvironment.ServerPort).Build()); await Task.Delay(500); Assert.AreEqual(1, receivedMessages.Count); Assert.AreEqual("DA|18RS00SC00XI0000RV00R100R200R300R400L100L200L300L400Y100Y200AC0102031800BELK0000BM0000|", receivedMessages.First().ConvertPayloadToString()); } - finally - { - await server.StopAsync(); - } } [TestMethod] - public async Task Client_Exception_In_Application_Message_Handler() + public async Task Message_Send_Retry() { - using (var testSetup = new TestSetup()) + using (var testEnvironment = new TestEnvironment()) { - testSetup.IgnoreClientLogErrors = true; - testSetup.IgnoreServerLogErrors = true; + testEnvironment.IgnoreClientLogErrors = true; + testEnvironment.IgnoreServerLogErrors = true; - await testSetup.StartServerAsync( + await testEnvironment.StartServerAsync( new MqttServerOptionsBuilder() .WithPersistentSessions() - .WithDefaultCommunicationTimeout(TimeSpan.FromMilliseconds(50))); - - var client1 = await testSetup.ConnectClientAsync(new MqttClientOptionsBuilder() - .WithCleanSession(false)); + .WithDefaultCommunicationTimeout(TimeSpan.FromMilliseconds(250))); + var client1 = await testEnvironment.ConnectClientAsync(new MqttClientOptionsBuilder().WithCleanSession(false)); await client1.SubscribeAsync("x", MqttQualityOfServiceLevel.AtLeastOnce); var retries = 0; @@ -241,16 +245,16 @@ namespace MQTTnet.Tests { retries++; - await Task.Delay(50); + await Task.Delay(1000); throw new Exception("Broken!"); } client1.UseReceivedApplicationMessageHandler(Handler1); - var client2 = await testSetup.ConnectClientAsync(); + var client2 = await testEnvironment.ConnectClientAsync(); await client2.PublishAsync("x"); - await Task.Delay(1000); + await Task.Delay(3000); // The server should disconnect clients which are not responding. Assert.IsFalse(client1.IsConnected); @@ -262,40 +266,5 @@ namespace MQTTnet.Tests Assert.AreEqual(2, retries); } } - - //#if DEBUG - // [TestMethod] - // public async Task Client_Cleanup_On_Authentification_Fails() - // { - // var channel = new TestMqttCommunicationAdapter(); - // var channel2 = new TestMqttCommunicationAdapter(); - // channel.Partner = channel2; - // channel2.Partner = channel; - - // Task.Run(async () => { - // var connect = await channel2.ReceivePacketAsync(TimeSpan.Zero, CancellationToken.None); - // await channel2.SendPacketAsync(new MqttConnAckPacket - // { - // ConnectReturnCode = Protocol.MqttConnectReturnCode.ConnectionRefusedNotAuthorized - // }, CancellationToken.None); - // }); - - // var fake = new TestMqttCommunicationAdapterFactory(channel); - - // var client = new MqttClient(fake, new MqttNetLogger()); - - // try - // { - // await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("any-server").Build()); - // } - // catch (Exception ex) - // { - // Assert.IsInstanceOfType(ex, typeof(MqttConnectingFailedException)); - // } - - // Assert.IsTrue(client._packetReceiverTask == null || client._packetReceiverTask.IsCompleted, "receive loop not completed"); - // Assert.IsTrue(client._keepAliveMessageSenderTask == null || client._keepAliveMessageSenderTask.IsCompleted, "keepalive loop not completed"); - // } - //#endif } } diff --git a/Tests/MQTTnet.Core.Tests/MqttKeepAliveMonitorTests.cs b/Tests/MQTTnet.Core.Tests/MqttKeepAliveMonitorTests.cs index 75b088aa..493f5ab8 100644 --- a/Tests/MQTTnet.Core.Tests/MqttKeepAliveMonitorTests.cs +++ b/Tests/MQTTnet.Core.Tests/MqttKeepAliveMonitorTests.cs @@ -7,6 +7,7 @@ using MQTTnet.Adapter; using MQTTnet.Diagnostics; using MQTTnet.Packets; using MQTTnet.Server; +using MQTTnet.Server.Status; namespace MQTTnet.Tests { @@ -44,9 +45,9 @@ namespace MQTTnet.Tests // Simulate traffic. Thread.Sleep(1000); // Internally the keep alive timeout is multiplied with 1.5 as per protocol specification. - monitor.PacketReceived(new MqttPublishPacket()); + monitor.PacketReceived(); Thread.Sleep(1000); - monitor.PacketReceived(new MqttPublishPacket()); + monitor.PacketReceived(); Thread.Sleep(1000); Assert.AreEqual(0, clientSession.StopCalledCount); @@ -62,12 +63,12 @@ namespace MQTTnet.Tests public int StopCalledCount { get; private set; } - public void FillStatus(MqttClientSessionStatus status) + public void FillStatus(MqttClientStatus status) { throw new NotSupportedException(); } - public void EnqueueApplicationMessage(MqttClientSession senderClientSession, MqttApplicationMessage applicationMessage) + public void EnqueueApplicationMessage(MqttClientConnection senderClientSession, MqttApplicationMessage applicationMessage) { throw new NotSupportedException(); } @@ -82,7 +83,7 @@ namespace MQTTnet.Tests throw new NotSupportedException(); } - public Task StopAsync(MqttClientDisconnectType disconnectType) + public Task StopAsync() { StopCalledCount++; return Task.FromResult(0); diff --git a/Tests/MQTTnet.Core.Tests/MqttServerTests.cs b/Tests/MQTTnet.Core.Tests/MqttServerTests.cs deleted file mode 100644 index fc66c2df..00000000 --- a/Tests/MQTTnet.Core.Tests/MqttServerTests.cs +++ /dev/null @@ -1,1031 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; -using System.Net.Sockets; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using MQTTnet.Adapter; -using MQTTnet.Client; -using MQTTnet.Client.Options; -using MQTTnet.Diagnostics; -using MQTTnet.Protocol; -using MQTTnet.Server; - -namespace MQTTnet.Tests -{ - [TestClass] - public class MqttServerTests - { - [TestMethod] - public async Task MqttServer_PublishSimple_AtMostOnce() - { - await TestPublishAsync( - "A/B/C", - MqttQualityOfServiceLevel.AtMostOnce, - "A/B/C", - MqttQualityOfServiceLevel.AtMostOnce, - 1); - } - - [TestMethod] - public async Task MqttServer_PublishSimple_AtLeastOnce() - { - await TestPublishAsync( - "A/B/C", - MqttQualityOfServiceLevel.AtLeastOnce, - "A/B/C", - MqttQualityOfServiceLevel.AtLeastOnce, - 1); - } - - [TestMethod] - public async Task MqttServer_PublishSimple_ExactlyOnce() - { - await TestPublishAsync( - "A/B/C", - MqttQualityOfServiceLevel.ExactlyOnce, - "A/B/C", - MqttQualityOfServiceLevel.ExactlyOnce, - 1); - } - - [TestMethod] - public async Task MqttServer_Will_Message() - { - var serverAdapter = new TestMqttServerAdapter(); - var s = new MqttFactory().CreateMqttServer(new[] { serverAdapter }, new MqttNetLogger()); - - var receivedMessagesCount = 0; - try - { - await s.StartAsync(new MqttServerOptions()); - - var willMessage = new MqttApplicationMessageBuilder().WithTopic("My/last/will").WithAtMostOnceQoS().Build(); - var c1 = await serverAdapter.ConnectTestClient("c1"); - var c2 = await serverAdapter.ConnectTestClient("c2", willMessage); - - c1.ApplicationMessageReceived += (_, __) => receivedMessagesCount++; - await c1.SubscribeAsync(new TopicFilterBuilder().WithTopic("#").Build()); - - await c2.DisconnectAsync(); - - await Task.Delay(1000); - - await c1.DisconnectAsync(); - } - finally - { - await s.StopAsync(); - } - - Assert.AreEqual(0, receivedMessagesCount); - } - - [TestMethod] - public async Task MqttServer_Subscribe_Unsubscribe() - { - var serverAdapter = new TestMqttServerAdapter(); - var s = new MqttFactory().CreateMqttServer(new[] { serverAdapter }, new MqttNetLogger()); - - var receivedMessagesCount = 0; - - try - { - await s.StartAsync(new MqttServerOptions()); - - var c1 = await serverAdapter.ConnectTestClient("c1"); - var c2 = await serverAdapter.ConnectTestClient("c2"); - c1.ApplicationMessageReceived += (_, __) => receivedMessagesCount++; - - var message = new MqttApplicationMessageBuilder().WithTopic("a").WithAtLeastOnceQoS().Build(); - - await c2.PublishAsync(message); - await Task.Delay(1000); - Assert.AreEqual(0, receivedMessagesCount); - - var subscribeEventCalled = false; - s.ClientSubscribedTopic += (_, e) => - { - subscribeEventCalled = e.TopicFilter.Topic == "a" && e.ClientId == "c1"; - }; - - await c1.SubscribeAsync(new TopicFilter { Topic = "a", QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce }); - await Task.Delay(500); - Assert.IsTrue(subscribeEventCalled, "Subscribe event not called."); - - await c2.PublishAsync(message); - await Task.Delay(500); - Assert.AreEqual(1, receivedMessagesCount); - - var unsubscribeEventCalled = false; - s.ClientUnsubscribedTopic += (_, e) => - { - unsubscribeEventCalled = e.TopicFilter == "a" && e.ClientId == "c1"; - }; - - await c1.UnsubscribeAsync("a"); - await Task.Delay(500); - Assert.IsTrue(unsubscribeEventCalled, "Unsubscribe event not called."); - - await c2.PublishAsync(message); - await Task.Delay(1000); - Assert.AreEqual(1, receivedMessagesCount); - } - finally - { - await s.StopAsync(); - } - await Task.Delay(500); - - Assert.AreEqual(1, receivedMessagesCount); - } - - [TestMethod] - public async Task MqttServer_Publish_From_Server() - { - using (var testSetup = new TestSetup()) - { - var server = await testSetup.StartServerAsync(); - - var receivedMessagesCount = 0; - - var client = await testSetup.ConnectClientAsync(); - client.UseReceivedApplicationMessageHandler(c => Interlocked.Increment(ref receivedMessagesCount)); - - var message = new MqttApplicationMessageBuilder().WithTopic("a").WithAtLeastOnceQoS().Build(); - await client.SubscribeAsync(new TopicFilter { Topic = "a", QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce }); - - await server.PublishAsync(message); - - await Task.Delay(1000); - await server.StopAsync(); - - Assert.AreEqual(1, receivedMessagesCount); - } - } - - [TestMethod] - public async Task MqttServer_Publish_Multiple_Clients() - { - var s = new MqttFactory().CreateMqttServer(); - var receivedMessagesCount = 0; - var locked = new object(); - - var clientOptions = new MqttClientOptionsBuilder() - .WithTcpServer("localhost") - .Build(); - - var clientOptions2 = new MqttClientOptionsBuilder() - .WithTcpServer("localhost") - .Build(); - - try - { - await s.StartAsync(new MqttServerOptions()); - - var c1 = new MqttFactory().CreateMqttClient(); - var c2 = new MqttFactory().CreateMqttClient(); - - await c1.ConnectAsync(clientOptions); - await c2.ConnectAsync(clientOptions2); - - c1.ApplicationMessageReceived += (_, __) => - { - lock (locked) - { - receivedMessagesCount++; - } - }; - - c2.ApplicationMessageReceived += (_, __) => - { - lock (locked) - { - receivedMessagesCount++; - } - }; - - var message = new MqttApplicationMessageBuilder().WithTopic("a").WithAtLeastOnceQoS().Build(); - await c1.SubscribeAsync(new TopicFilter { Topic = "a", QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce }); - await c2.SubscribeAsync(new TopicFilter { Topic = "a", QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce }); - - //await Task.WhenAll(Publish(c1, message), Publish(c2, message)); - await Publish(c1, message); - - await Task.Delay(500); - } - finally - { - await s.StopAsync(); - } - - Assert.AreEqual(2000, receivedMessagesCount); - } - - [TestMethod] - public async Task MqttServer_Session_Takeover() - { - var server = new MqttFactory().CreateMqttServer(); - try - { - await server.StartAsync(new MqttServerOptions()); - - var client1 = new MqttFactory().CreateMqttClient(); - var client2 = new MqttFactory().CreateMqttClient(); - - var options = new MqttClientOptionsBuilder() - .WithTcpServer("localhost") - .WithCleanSession(false) - .WithClientId("a").Build(); - - await client1.ConnectAsync(options); - - await Task.Delay(500); - - await client2.ConnectAsync(options); - - await Task.Delay(500); - - Assert.IsFalse(client1.IsConnected); - Assert.IsTrue(client2.IsConnected); - } - finally - { - await server.StopAsync(); - } - } - - [TestMethod] - public async Task MqttServer_No_Messages_If_No_Subscription() - { - var server = new MqttFactory().CreateMqttServer(); - try - { - await server.StartAsync(new MqttServerOptions()); - - var client = new MqttFactory().CreateMqttClient(); - var receivedMessages = new List(); - - var options = new MqttClientOptionsBuilder() - .WithTcpServer("localhost").Build(); - - client.Connected += async (s, e) => - { - await client.PublishAsync("Connected"); - }; - - client.ApplicationMessageReceived += (s, e) => - { - lock (receivedMessages) - { - receivedMessages.Add(e.ApplicationMessage); - } - }; - - await client.ConnectAsync(options); - - await Task.Delay(500); - - await client.PublishAsync("Hello"); - - await Task.Delay(500); - - Assert.AreEqual(0, receivedMessages.Count); - } - finally - { - await server.StopAsync(); - } - } - - [TestMethod] - public async Task MqttServer_Set_Subscription_At_Server() - { - var server = new MqttFactory().CreateMqttServer(); - try - { - await server.StartAsync(new MqttServerOptions()); - server.ClientConnected += async (s, e) => - { - await server.SubscribeAsync(e.ClientId, "topic1"); - }; - - var client = new MqttFactory().CreateMqttClient(); - var receivedMessages = new List(); - - var options = new MqttClientOptionsBuilder() - .WithTcpServer("localhost").Build(); - - client.ApplicationMessageReceived += (s, e) => - { - lock (receivedMessages) - { - receivedMessages.Add(e.ApplicationMessage); - } - }; - - await client.ConnectAsync(options); - - await Task.Delay(500); - - await client.PublishAsync("Hello"); - - await Task.Delay(500); - - Assert.AreEqual(0, receivedMessages.Count); - } - finally - { - await server.StopAsync(); - } - } - - private static async Task Publish(IMqttClient c1, MqttApplicationMessage message) - { - for (int i = 0; i < 1000; i++) - { - await c1.PublishAsync(message); - } - } - - [TestMethod] - public async Task MqttServer_Shutdown_Disconnects_Clients_Gracefully() - { - using (var testSetup = new TestSetup()) - { - var server = await testSetup.StartServerAsync(new MqttServerOptionsBuilder()); - - var disconnectCalled = 0; - - var c1 = await testSetup.ConnectClientAsync(new MqttClientOptionsBuilder()); - c1.Disconnected += (sender, args) => disconnectCalled++; - - await Task.Delay(100); - - await server.StopAsync(); - - await Task.Delay(100); - - Assert.AreEqual(1, disconnectCalled); - } - } - - [TestMethod] - public async Task MqttServer_Handle_Clean_Disconnect() - { - using (var testSetup = new TestSetup()) - { - var server = await testSetup.StartServerAsync(new MqttServerOptionsBuilder()); - - var clientConnectedCalled = 0; - var clientDisconnectedCalled = 0; - - server.ClientConnected += (_, __) => Interlocked.Increment(ref clientConnectedCalled); - server.ClientDisconnected += (_, __) => Interlocked.Increment(ref clientDisconnectedCalled); - - var c1 = await testSetup.ConnectClientAsync(new MqttClientOptionsBuilder()); - - Assert.AreEqual(1, clientConnectedCalled); - Assert.AreEqual(0, clientDisconnectedCalled); - - await Task.Delay(500); - - await c1.DisconnectAsync(); - - await Task.Delay(500); - - Assert.AreEqual(1, clientConnectedCalled); - Assert.AreEqual(1, clientDisconnectedCalled); - } - } - - [TestMethod] - public async Task MqttServer_Client_Disconnect_Without_Errors() - { - using (var testSetup = new TestSetup()) - { - bool clientWasConnected; - - var server = await testSetup.StartServerAsync(new MqttServerOptionsBuilder()); - try - { - var client = await testSetup.ConnectClientAsync(new MqttClientOptionsBuilder()); - - clientWasConnected = true; - - await client.DisconnectAsync(); - - await Task.Delay(500); - } - finally - { - await server.StopAsync(); - } - - Assert.IsTrue(clientWasConnected); - - testSetup.ThrowIfLogErrors(); - } - } - - [TestMethod] - public async Task MqttServer_Lots_Of_Retained_Messages() - { - const int ClientCount = 25; - - using (var testSetup = new TestSetup()) - { - var server = await testSetup.StartServerAsync(); - - var tasks = new ConcurrentBag(); - for (var i = 0; i < ClientCount; i++) - { - var clientId = i; - tasks.Add(Task.Run(async () => - { - try - { - using (var client = await testSetup.ConnectClientAsync()) - { - // Clear retained message. - await client.PublishAsync(new MqttApplicationMessageBuilder().WithTopic("r" + clientId) - .WithPayload(new byte[0]).WithRetainFlag().Build()); - - // Set retained message. - await client.PublishAsync(new MqttApplicationMessageBuilder().WithTopic("r" + clientId) - .WithPayload("value").WithRetainFlag().Build()); - - await Task.Delay(10); - - await client.DisconnectAsync(); - } - } - catch (Exception exception) - { - testSetup.TrackException(exception); - } - })); - } - - await Task.WhenAll(tasks.ToArray()); - - await Task.Delay(1000); - - var retainedMessages = server.GetRetainedMessages(); - - Assert.AreEqual(ClientCount, retainedMessages.Count); - - for (var i = 0; i < ClientCount; i++) - { - Assert.IsTrue(retainedMessages.Any(m => m.Topic == "r" + i)); - } - } - } - - [TestMethod] - public async Task MqttServer_Retained_Messages_Flow() - { - var retainedMessage = new MqttApplicationMessageBuilder().WithTopic("r").WithPayload("r").WithRetainFlag().Build(); - var serverAdapter = new TestMqttServerAdapter(); - var s = new MqttFactory().CreateMqttServer(new[] { serverAdapter }, new MqttNetLogger()); - await s.StartAsync(new MqttServerOptions()); - var c1 = await serverAdapter.ConnectTestClient("c1"); - await c1.PublishAsync(retainedMessage); - await Task.Delay(500); - await c1.DisconnectAsync(); - await Task.Delay(500); - - var receivedMessages = 0; - var c2 = await serverAdapter.ConnectTestClient("c2"); - c2.ApplicationMessageReceived += (_, e) => - { - receivedMessages++; - }; - - for (var i = 0; i < 5; i++) - { - await c2.UnsubscribeAsync("r"); - await Task.Delay(500); - Assert.AreEqual(i, receivedMessages); - - await c2.SubscribeAsync("r"); - await Task.Delay(500); - Assert.AreEqual(i + 1, receivedMessages); - } - - await c2.DisconnectAsync(); - - await s.StopAsync(); - } - - [TestMethod] - public async Task MqttServer_No_Retained_Message() - { - var serverAdapter = new TestMqttServerAdapter(); - var s = new MqttFactory().CreateMqttServer(new[] { serverAdapter }, new MqttNetLogger()); - - var receivedMessagesCount = 0; - - try - { - await s.StartAsync(new MqttServerOptions()); - - var c1 = await serverAdapter.ConnectTestClient("c1"); - await c1.PublishAsync(builder => builder.WithTopic("retained").WithPayload(new byte[3])); - await c1.DisconnectAsync(); - - var c2 = await serverAdapter.ConnectTestClient("c2"); - c2.ApplicationMessageReceived += (_, __) => receivedMessagesCount++; - await c2.SubscribeAsync(new TopicFilterBuilder().WithTopic("retained").Build()); - - await Task.Delay(500); - } - finally - { - await s.StopAsync(); - } - - Assert.AreEqual(0, receivedMessagesCount); - } - - [TestMethod] - public async Task MqttServer_Retained_Message() - { - var serverAdapter = new TestMqttServerAdapter(); - var s = new MqttFactory().CreateMqttServer(new[] { serverAdapter }, new MqttNetLogger()); - - var receivedMessages = new List(); - try - { - await s.StartAsync(new MqttServerOptions()); - - var c1 = await serverAdapter.ConnectTestClient("c1"); - await c1.PublishAndWaitForAsync(s, new MqttApplicationMessageBuilder().WithTopic("retained").WithPayload(new byte[3]).WithRetainFlag().Build()); - await c1.DisconnectAsync(); - - var c2 = await serverAdapter.ConnectTestClient("c2"); - c2.ApplicationMessageReceived += (_, e) => - { - lock (receivedMessages) - { - receivedMessages.Add(e.ApplicationMessage); - } - }; - - await c2.SubscribeAsync(new TopicFilterBuilder().WithTopic("retained").Build()); - - await Task.Delay(500); - } - finally - { - await s.StopAsync(); - } - - Assert.AreEqual(1, receivedMessages.Count); - Assert.IsTrue(receivedMessages.First().Retain); - } - - [TestMethod] - public async Task MqttServer_Clear_Retained_Message() - { - var serverAdapter = new TestMqttServerAdapter(); - var s = new MqttFactory().CreateMqttServer(new[] { serverAdapter }, new MqttNetLogger()); - - var receivedMessagesCount = 0; - try - { - await s.StartAsync(new MqttServerOptions()); - - var c1 = await serverAdapter.ConnectTestClient("c1"); - await c1.PublishAsync(builder => builder.WithTopic("retained").WithPayload(new byte[3]).WithRetainFlag()); - await c1.PublishAsync(builder => builder.WithTopic("retained").WithPayload(new byte[0]).WithRetainFlag()); - await c1.DisconnectAsync(); - - var c2 = await serverAdapter.ConnectTestClient("c2"); - c2.ApplicationMessageReceived += (_, __) => receivedMessagesCount++; - - await Task.Delay(200); - await c2.SubscribeAsync(new TopicFilter { Topic = "retained", QualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce }); - await Task.Delay(500); - } - finally - { - await s.StopAsync(); - } - - - Assert.AreEqual(0, receivedMessagesCount); - } - - [TestMethod] - public async Task MqttServer_Persist_Retained_Message() - { - var storage = new TestStorage(); - var serverAdapter = new TestMqttServerAdapter(); - var s = new MqttFactory().CreateMqttServer(new[] { serverAdapter }, new MqttNetLogger()); - - try - { - var options = new MqttServerOptions { Storage = storage }; - - await s.StartAsync(options); - - var c1 = await serverAdapter.ConnectTestClient("c1"); - - await c1.PublishAndWaitForAsync(s, new MqttApplicationMessageBuilder().WithTopic("retained").WithPayload(new byte[3]).WithRetainFlag().Build()); - - await Task.Delay(250); - - await c1.DisconnectAsync(); - } - finally - { - await s.StopAsync(); - } - - Assert.AreEqual(1, storage.Messages.Count); - - s = new MqttFactory().CreateMqttServer(new[] { serverAdapter }, new MqttNetLogger()); - - var receivedMessagesCount = 0; - try - { - var options = new MqttServerOptions { Storage = storage }; - await s.StartAsync(options); - - var c2 = await serverAdapter.ConnectTestClient("c2"); - c2.ApplicationMessageReceived += (_, __) => receivedMessagesCount++; - await c2.SubscribeAsync(new TopicFilterBuilder().WithTopic("retained").Build()); - - await Task.Delay(250); - } - finally - { - await s.StopAsync(); - } - - Assert.AreEqual(1, receivedMessagesCount); - } - - [TestMethod] - public async Task MqttServer_Intercept_Message() - { - void Interceptor(MqttApplicationMessageInterceptorContext context) - { - context.ApplicationMessage.Payload = Encoding.ASCII.GetBytes("extended"); - } - - var serverAdapter = new TestMqttServerAdapter(); - var s = new MqttFactory().CreateMqttServer(new[] { serverAdapter }, new MqttNetLogger()); - - try - { - var options = new MqttServerOptions { ApplicationMessageInterceptor = new MqttServerApplicationMessageInterceptorDelegate(c => Interceptor(c)) }; - - await s.StartAsync(options); - - var c1 = await serverAdapter.ConnectTestClient("c1"); - var c2 = await serverAdapter.ConnectTestClient("c2"); - await c2.SubscribeAsync(new TopicFilterBuilder().WithTopic("test").Build()); - - var isIntercepted = false; - c2.ApplicationMessageReceived += (sender, args) => - { - isIntercepted = string.Compare("extended", Encoding.UTF8.GetString(args.ApplicationMessage.Payload), StringComparison.Ordinal) == 0; - }; - - await c1.PublishAsync(builder => builder.WithTopic("test")); - await c1.DisconnectAsync(); - - await Task.Delay(500); - - Assert.IsTrue(isIntercepted); - } - finally - { - await s.StopAsync(); - } - } - - [TestMethod] - public async Task MqttServer_Body() - { - var serverAdapter = new TestMqttServerAdapter(); - var s = new MqttFactory().CreateMqttServer(new[] { serverAdapter }, new MqttNetLogger()); - - var bodyIsMatching = false; - try - { - await s.StartAsync(new MqttServerOptions()); - - var c1 = await serverAdapter.ConnectTestClient("c1"); - var c2 = await serverAdapter.ConnectTestClient("c2"); - - c1.ApplicationMessageReceived += (_, e) => - { - if (Encoding.UTF8.GetString(e.ApplicationMessage.Payload) == "The body") - { - bodyIsMatching = true; - } - }; - - await c1.SubscribeAsync("A", MqttQualityOfServiceLevel.AtMostOnce); - await c2.PublishAsync(builder => builder.WithTopic("A").WithPayload(Encoding.UTF8.GetBytes("The body"))); - - await Task.Delay(1000); - } - finally - { - await s.StopAsync(); - } - - Assert.IsTrue(bodyIsMatching); - } - - [TestMethod] - public async Task MqttServer_Connection_Denied() - { - var server = new MqttFactory().CreateMqttServer(); - var client = new MqttFactory().CreateMqttClient(); - - try - { - var options = new MqttServerOptionsBuilder().WithConnectionValidator(context => - { - context.ReturnCode = MqttConnectReturnCode.ConnectionRefusedNotAuthorized; - }).Build(); - - await server.StartAsync(options); - - - var clientOptions = new MqttClientOptionsBuilder() - .WithTcpServer("localhost").Build(); - - try - { - await client.ConnectAsync(clientOptions); - Assert.Fail("An exception should be raised."); - } - catch (Exception exception) - { - if (exception is MqttConnectingFailedException) - { - - } - else - { - Assert.Fail("Wrong exception."); - } - } - } - finally - { - await client.DisconnectAsync(); - await server.StopAsync(); - - client.Dispose(); - } - } - - [TestMethod] - public async Task MqttServer_Same_Client_Id_Connect_Disconnect_Event_Order() - { - using (var testSetup = new TestSetup()) - { - var server = await testSetup.StartServerAsync(new MqttServerOptionsBuilder()); - - var events = new List(); - - server.ClientConnected += (_, __) => - { - lock (events) - { - events.Add("c"); - } - }; - - server.ClientDisconnected += (_, __) => - { - lock (events) - { - events.Add("d"); - } - }; - - var clientOptions = new MqttClientOptionsBuilder() - .WithClientId("same_id"); - - // c - var c1 = await testSetup.ConnectClientAsync(clientOptions); - - await Task.Delay(500); - - var flow = string.Join(string.Empty, events); - Assert.AreEqual("c", flow); - - // dc - var c2 = await testSetup.ConnectClientAsync(clientOptions); - - await Task.Delay(500); - - flow = string.Join(string.Empty, events); - Assert.AreEqual("cdc", flow); - - // nothing - await c1.DisconnectAsync(); - - await Task.Delay(500); - - // d - await c2.DisconnectAsync(); - - await Task.Delay(500); - - await server.StopAsync(); - - flow = string.Join(string.Empty, events); - Assert.AreEqual("cdcd", flow); - } - } - - [TestMethod] - public async Task MqttServer_Remove_Session() - { - using (var testSetup = new TestSetup()) - { - var server = await testSetup.StartServerAsync(new MqttServerOptionsBuilder()); - - var clientOptions = new MqttClientOptionsBuilder(); - var c1 = await testSetup.ConnectClientAsync(clientOptions); - await Task.Delay(500); - Assert.AreEqual(1, (await server.GetClientSessionsStatusAsync()).Count); - - await c1.DisconnectAsync(); - await Task.Delay(500); - - Assert.AreEqual(0, (await server.GetClientSessionsStatusAsync()).Count); - } - } - - [TestMethod] - public async Task MqttServer_Stop_And_Restart() - { - var server = new MqttFactory().CreateMqttServer(); - await server.StartAsync(new MqttServerOptions()); - - var client = new MqttFactory().CreateMqttClient(); - await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("localhost").Build()); - await server.StopAsync(); - - try - { - var client2 = new MqttFactory().CreateMqttClient(); - await client2.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("localhost").Build()); - - Assert.Fail("Connecting should fail."); - } - catch (Exception) - { - } - - await server.StartAsync(new MqttServerOptions()); - var client3 = new MqttFactory().CreateMqttClient(); - await client3.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("localhost").Build()); - - await server.StopAsync(); - } - - [TestMethod] - public async Task MqttServer_Close_Idle_Connection() - { - var server = new MqttFactory().CreateMqttServer(); - - try - { - await server.StartAsync(new MqttServerOptionsBuilder().WithDefaultCommunicationTimeout(TimeSpan.FromSeconds(4)).Build()); - - var client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); - await client.ConnectAsync("localhost", 1883); - - // Don't send anything. The server should close the connection. - await Task.Delay(TimeSpan.FromSeconds(5)); - - try - { - await client.ReceiveAsync(new ArraySegment(new byte[10]), SocketFlags.Partial); - Assert.Fail("Receive should throw an exception."); - } - catch (SocketException) - { - } - } - finally - { - await server.StopAsync(); - } - } - - [TestMethod] - public async Task MqttServer_Send_Garbage() - { - var server = new MqttFactory().CreateMqttServer(); - - try - { - await server.StartAsync(new MqttServerOptionsBuilder().WithDefaultCommunicationTimeout(TimeSpan.FromSeconds(4)).Build()); - - var client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); - await client.ConnectAsync("localhost", 1883); - await client.SendAsync(Encoding.UTF8.GetBytes("Garbage"), SocketFlags.None); - - await Task.Delay(TimeSpan.FromSeconds(5)); - - try - { - await client.ReceiveAsync(new ArraySegment(new byte[10]), SocketFlags.Partial); - Assert.Fail("Receive should throw an exception."); - } - catch (SocketException) - { - } - } - finally - { - await server.StopAsync(); - } - } - - private class TestStorage : IMqttServerStorage - { - public IList Messages = new List(); - - public Task SaveRetainedMessagesAsync(IList messages) - { - Messages = messages; - return Task.CompletedTask; - } - - public Task> LoadRetainedMessagesAsync() - { - return Task.FromResult(Messages); - } - } - - private static async Task TestPublishAsync( - string topic, - MqttQualityOfServiceLevel qualityOfServiceLevel, - string topicFilter, - MqttQualityOfServiceLevel filterQualityOfServiceLevel, - int expectedReceivedMessagesCount) - { - //using (var testSetup = new TestSetup()) - //{ - // var server = await testSetup.StartServerAsync(new MqttServerOptionsBuilder()); - - // var clientOptions = new MqttClientOptionsBuilder(); - // var c1 = await testSetup.ConnectClientAsync(clientOptions); - // await Task.Delay(500); - // Assert.AreEqual(1, (await server.GetClientSessionsStatusAsync()).Count); - - // await c1.DisconnectAsync(); - // await Task.Delay(500); - - // Assert.AreEqual(0, (await server.GetClientSessionsStatusAsync()).Count); - //} - - - - var s = new MqttFactory().CreateMqttServer(); - - var receivedMessagesCount = 0; - try - { - await s.StartAsync(new MqttServerOptions()); - - var c1 = new MqttFactory().CreateMqttClient(); - c1.UseReceivedApplicationMessageHandler(c => receivedMessagesCount++); - - await c1.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build()); - await c1.SubscribeAsync(new TopicFilterBuilder().WithTopic(topicFilter).WithQualityOfServiceLevel(filterQualityOfServiceLevel).Build()); - - var c2 = new MqttFactory().CreateMqttClient(); - await c2.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build()); - await c2.PublishAsync(builder => builder.WithTopic(topic).WithPayload(new byte[0]).WithQualityOfServiceLevel(qualityOfServiceLevel)); - await c2.DisconnectAsync().ConfigureAwait(false); - - await Task.Delay(500); - await c1.UnsubscribeAsync(topicFilter); - await Task.Delay(500); - } - finally - { - await s.StopAsync(); - } - - Assert.AreEqual(expectedReceivedMessagesCount, receivedMessagesCount); - } - } -} diff --git a/Tests/MQTTnet.Core.Tests/MqttSubscriptionsManagerTests.cs b/Tests/MQTTnet.Core.Tests/MqttSubscriptionsManagerTests.cs index 7b7e959e..8bfae0bb 100644 --- a/Tests/MQTTnet.Core.Tests/MqttSubscriptionsManagerTests.cs +++ b/Tests/MQTTnet.Core.Tests/MqttSubscriptionsManagerTests.cs @@ -11,7 +11,7 @@ namespace MQTTnet.Tests [TestMethod] public void MqttSubscriptionsManager_SubscribeSingleSuccess() { - var sm = new MqttClientSubscriptionsManager("", new MqttServerOptions(), new MqttServerEventDispatcher()); + var sm = new MqttClientSubscriptionsManager("", new MqttServerEventDispatcher(), new MqttServerOptions()); var sp = new MqttSubscribePacket(); sp.TopicFilters.Add(new TopicFilterBuilder().WithTopic("A/B/C").Build()); @@ -26,7 +26,7 @@ namespace MQTTnet.Tests [TestMethod] public void MqttSubscriptionsManager_SubscribeDifferentQoSSuccess() { - var sm = new MqttClientSubscriptionsManager("", new MqttServerOptions(), new MqttServerEventDispatcher()); + var sm = new MqttClientSubscriptionsManager("", new MqttServerEventDispatcher(), new MqttServerOptions()); var sp = new MqttSubscribePacket(); sp.TopicFilters.Add(new TopicFilter { Topic = "A/B/C", QualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce }); @@ -41,7 +41,7 @@ namespace MQTTnet.Tests [TestMethod] public void MqttSubscriptionsManager_SubscribeTwoTimesSuccess() { - var sm = new MqttClientSubscriptionsManager("", new MqttServerOptions(), new MqttServerEventDispatcher()); + var sm = new MqttClientSubscriptionsManager("", new MqttServerEventDispatcher(), new MqttServerOptions()); var sp = new MqttSubscribePacket(); sp.TopicFilters.Add(new TopicFilter { Topic = "#", QualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce }); @@ -57,7 +57,7 @@ namespace MQTTnet.Tests [TestMethod] public void MqttSubscriptionsManager_SubscribeSingleNoSuccess() { - var sm = new MqttClientSubscriptionsManager("", new MqttServerOptions(), new MqttServerEventDispatcher()); + var sm = new MqttClientSubscriptionsManager("", new MqttServerEventDispatcher(), new MqttServerOptions()); var sp = new MqttSubscribePacket(); sp.TopicFilters.Add(new TopicFilterBuilder().WithTopic("A/B/C").Build()); @@ -70,7 +70,7 @@ namespace MQTTnet.Tests [TestMethod] public void MqttSubscriptionsManager_SubscribeAndUnsubscribeSingle() { - var sm = new MqttClientSubscriptionsManager("", new MqttServerOptions(), new MqttServerEventDispatcher()); + var sm = new MqttClientSubscriptionsManager("", new MqttServerEventDispatcher(), new MqttServerOptions()); var sp = new MqttSubscribePacket(); sp.TopicFilters.Add(new TopicFilterBuilder().WithTopic("A/B/C").Build()); @@ -81,7 +81,7 @@ namespace MQTTnet.Tests var up = new MqttUnsubscribePacket(); up.TopicFilters.Add("A/B/C"); - sm.Unsubscribe(up); + sm.UnsubscribeAsync(up); Assert.IsFalse(sm.CheckSubscriptions("A/B/C", MqttQualityOfServiceLevel.AtMostOnce).IsSubscribed); } diff --git a/Tests/MQTTnet.Core.Tests/Server_Status_Tests.cs b/Tests/MQTTnet.Core.Tests/Server_Status_Tests.cs new file mode 100644 index 00000000..4822beaf --- /dev/null +++ b/Tests/MQTTnet.Core.Tests/Server_Status_Tests.cs @@ -0,0 +1,159 @@ +using System; +using System.Linq; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System.Threading.Tasks; +using MQTTnet.Client.Options; +using MQTTnet.Tests.Mockups; +using MQTTnet.Client; +using MQTTnet.Protocol; +using MQTTnet.Server; + +namespace MQTTnet.Tests +{ + [TestClass] + public class Server_Status_Tests + { + [TestMethod] + public async Task Show_Client_And_Session_Statistics() + { + using (var testEnvironment = new TestEnvironment()) + { + var server = await testEnvironment.StartServerAsync(); + + var c1 = await testEnvironment.ConnectClientAsync(new MqttClientOptionsBuilder().WithClientId("client1")); + var c2 = await testEnvironment.ConnectClientAsync(new MqttClientOptionsBuilder().WithClientId("client2")); + + await Task.Delay(500); + + var clientStatus = await server.GetClientStatusAsync(); + var sessionStatus = await server.GetSessionStatusAsync(); + + Assert.AreEqual(2, clientStatus.Count); + Assert.AreEqual(2, sessionStatus.Count); + + Assert.IsTrue(clientStatus.Any(s => s.ClientId == "client1")); + Assert.IsTrue(clientStatus.Any(s => s.ClientId == "client2")); + + await c1.DisconnectAsync(); + await c2.DisconnectAsync(); + + await Task.Delay(500); + + clientStatus = await server.GetClientStatusAsync(); + sessionStatus = await server.GetSessionStatusAsync(); + + Assert.AreEqual(0, clientStatus.Count); + Assert.AreEqual(0, sessionStatus.Count); + } + } + + [TestMethod] + public async Task Disconnect_Client() + { + using (var testEnvironment = new TestEnvironment()) + { + var server = await testEnvironment.StartServerAsync(); + + var c1 = await testEnvironment.ConnectClientAsync(new MqttClientOptionsBuilder().WithClientId("client1")); + + await Task.Delay(500); + + var clientStatus = await server.GetClientStatusAsync(); + + Assert.AreEqual(1, clientStatus.Count); + + Assert.IsTrue(clientStatus.Any(s => s.ClientId == "client1")); + + await clientStatus.First().DisconnectAsync(); + + await Task.Delay(500); + + Assert.IsFalse(c1.IsConnected); + + clientStatus = await server.GetClientStatusAsync(); + + Assert.AreEqual(0, clientStatus.Count); + } + } + + [TestMethod] + public async Task Keep_Persistent_Session() + { + using (var testEnvironment = new TestEnvironment()) + { + var server = await testEnvironment.StartServerAsync(new MqttServerOptionsBuilder().WithPersistentSessions()); + + var c1 = await testEnvironment.ConnectClientAsync(new MqttClientOptionsBuilder().WithClientId("client1")); + var c2 = await testEnvironment.ConnectClientAsync(new MqttClientOptionsBuilder().WithClientId("client2")); + + await c1.DisconnectAsync(); + + await Task.Delay(500); + + var clientStatus = await server.GetClientStatusAsync(); + var sessionStatus = await server.GetSessionStatusAsync(); + + Assert.AreEqual(1, clientStatus.Count); + Assert.AreEqual(2, sessionStatus.Count); + + await c2.DisconnectAsync(); + + await Task.Delay(500); + + clientStatus = await server.GetClientStatusAsync(); + sessionStatus = await server.GetSessionStatusAsync(); + + Assert.AreEqual(0, clientStatus.Count); + Assert.AreEqual(2, sessionStatus.Count); + } + } + + [TestMethod] + public async Task Track_Sent_Application_Messages() + { + using (var testEnvironment = new TestEnvironment()) + { + var server = await testEnvironment.StartServerAsync(new MqttServerOptionsBuilder().WithPersistentSessions()); + + var c1 = await testEnvironment.ConnectClientAsync(); + + for (var i = 1; i < 25; i++) + { + await c1.PublishAsync("a"); + await Task.Delay(50); + + var clientStatus = await server.GetClientStatusAsync(); + Assert.AreEqual(i, clientStatus.First().SentApplicationMessagesCount); + Assert.AreEqual(0, clientStatus.First().ReceivedApplicationMessagesCount); + } + } + } + + [TestMethod] + public async Task Track_Sent_Packets() + { + using (var testEnvironment = new TestEnvironment()) + { + var server = await testEnvironment.StartServerAsync(new MqttServerOptionsBuilder().WithPersistentSessions()); + + var c1 = await testEnvironment.ConnectClientAsync(new MqttClientOptionsBuilder().WithNoKeepAlive()); + + for (var i = 1; i < 25; i++) + { + // At most once will send one packet to the client and the server will reply + // with an additional ACK packet. + await c1.PublishAsync("a", string.Empty, MqttQualityOfServiceLevel.AtLeastOnce); + await Task.Delay(50); + + var clientStatus = await server.GetClientStatusAsync(); + + Assert.AreEqual(i, clientStatus.First().SentApplicationMessagesCount, "SAMC invalid!"); + Assert.AreEqual(i, clientStatus.First().SentPacketsCount, "SPC invalid!"); + + // +1 because ConnACK package is already counted. + Assert.AreEqual(i + 1, clientStatus.First().ReceivedPacketsCount, "RPC invalid!"); + } + } + } + } +} diff --git a/Tests/MQTTnet.Core.Tests/Server_Tests.cs b/Tests/MQTTnet.Core.Tests/Server_Tests.cs new file mode 100644 index 00000000..b9efc7ac --- /dev/null +++ b/Tests/MQTTnet.Core.Tests/Server_Tests.cs @@ -0,0 +1,970 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Sockets; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using MQTTnet.Adapter; +using MQTTnet.Client; +using MQTTnet.Client.Connecting; +using MQTTnet.Client.Options; +using MQTTnet.Protocol; +using MQTTnet.Server; +using MQTTnet.Tests.Mockups; + +namespace MQTTnet.Tests +{ + [TestClass] + public class Server_Tests + { + [TestMethod] + public async Task Publish_At_Most_Once_0x00() + { + await TestPublishAsync( + "A/B/C", + MqttQualityOfServiceLevel.AtMostOnce, + "A/B/C", + MqttQualityOfServiceLevel.AtMostOnce, + 1); + } + + [TestMethod] + public async Task Publish_At_Least_Once_0x01() + { + await TestPublishAsync( + "A/B/C", + MqttQualityOfServiceLevel.AtLeastOnce, + "A/B/C", + MqttQualityOfServiceLevel.AtLeastOnce, + 1); + } + + [TestMethod] + public async Task Publish_Exactly_Once_0x02() + { + await TestPublishAsync( + "A/B/C", + MqttQualityOfServiceLevel.ExactlyOnce, + "A/B/C", + MqttQualityOfServiceLevel.ExactlyOnce, + 1); + } + + [TestMethod] + public async Task Will_Message_Do_Not_Send() + { + using (var testEnvironment = new TestEnvironment()) + { + var receivedMessagesCount = 0; + + await testEnvironment.StartServerAsync(new MqttServerOptionsBuilder()); + + var willMessage = new MqttApplicationMessageBuilder().WithTopic("My/last/will").WithAtMostOnceQoS().Build(); + + var clientOptions = new MqttClientOptionsBuilder().WithWillMessage(willMessage); + + var c1 = await testEnvironment.ConnectClientAsync(); + c1.UseReceivedApplicationMessageHandler(c => Interlocked.Increment(ref receivedMessagesCount)); + await c1.SubscribeAsync(new TopicFilterBuilder().WithTopic("#").Build()); + + var c2 = await testEnvironment.ConnectClientAsync(clientOptions); + await c2.DisconnectAsync().ConfigureAwait(false); + + await Task.Delay(1000); + + Assert.AreEqual(0, receivedMessagesCount); + } + } + + [TestMethod] + public async Task Will_Message_Send() + { + using (var testEnvironment = new TestEnvironment()) + { + var receivedMessagesCount = 0; + + await testEnvironment.StartServerAsync(); + + var willMessage = new MqttApplicationMessageBuilder().WithTopic("My/last/will").WithAtMostOnceQoS().Build(); + + var clientOptions = new MqttClientOptionsBuilder().WithWillMessage(willMessage); + + var c1 = await testEnvironment.ConnectClientAsync(); + c1.UseReceivedApplicationMessageHandler(c => Interlocked.Increment(ref receivedMessagesCount)); + await c1.SubscribeAsync(new TopicFilterBuilder().WithTopic("#").Build()); + + var c2 = await testEnvironment.ConnectClientAsync(clientOptions); + c2.Dispose(); // Dispose will not send a DISCONNECT pattern first so the will message must be sent. + + await Task.Delay(1000); + + Assert.AreEqual(1, receivedMessagesCount); + } + } + + [TestMethod] + public async Task Subscribe_Unsubscribe() + { + using (var testEnvironment = new TestEnvironment()) + { + var receivedMessagesCount = 0; + + var server = await testEnvironment.StartServerAsync(); + + var c1 = await testEnvironment.ConnectClientAsync(new MqttClientOptionsBuilder().WithClientId("c1")); + c1.UseReceivedApplicationMessageHandler(c => Interlocked.Increment(ref receivedMessagesCount)); + + var c2 = await testEnvironment.ConnectClientAsync(new MqttClientOptionsBuilder().WithClientId("c2")); + + var message = new MqttApplicationMessageBuilder().WithTopic("a").WithAtLeastOnceQoS().Build(); + await c2.PublishAsync(message); + + await Task.Delay(500); + Assert.AreEqual(0, receivedMessagesCount); + + var subscribeEventCalled = false; + server.ClientSubscribedTopic += (_, e) => + { + subscribeEventCalled = e.TopicFilter.Topic == "a" && e.ClientId == "c1"; + }; + + await c1.SubscribeAsync(new TopicFilter { Topic = "a", QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce }); + await Task.Delay(250); + Assert.IsTrue(subscribeEventCalled, "Subscribe event not called."); + + await c2.PublishAsync(message); + await Task.Delay(250); + Assert.AreEqual(1, receivedMessagesCount); + + var unsubscribeEventCalled = false; + server.ClientUnsubscribedTopic += (_, e) => + { + unsubscribeEventCalled = e.TopicFilter == "a" && e.ClientId == "c1"; + }; + + await c1.UnsubscribeAsync("a"); + await Task.Delay(250); + Assert.IsTrue(unsubscribeEventCalled, "Unsubscribe event not called."); + + await c2.PublishAsync(message); + await Task.Delay(500); + Assert.AreEqual(1, receivedMessagesCount); + + await Task.Delay(500); + + Assert.AreEqual(1, receivedMessagesCount); + } + } + + [TestMethod] + public async Task Publish_From_Server() + { + using (var testEnvironment = new TestEnvironment()) + { + var server = await testEnvironment.StartServerAsync(); + + var receivedMessagesCount = 0; + + var client = await testEnvironment.ConnectClientAsync(); + client.UseReceivedApplicationMessageHandler(c => Interlocked.Increment(ref receivedMessagesCount)); + + var message = new MqttApplicationMessageBuilder().WithTopic("a").WithAtLeastOnceQoS().Build(); + await client.SubscribeAsync(new TopicFilter { Topic = "a", QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce }); + + await server.PublishAsync(message); + + await Task.Delay(1000); + + Assert.AreEqual(1, receivedMessagesCount); + } + } + + [TestMethod] + public async Task Publish_Multiple_Clients() + { + var receivedMessagesCount = 0; + var locked = new object(); + + using (var testEnvironment = new TestEnvironment()) + { + await testEnvironment.StartServerAsync(); + + var c1 = await testEnvironment.ConnectClientAsync(); + var c2 = await testEnvironment.ConnectClientAsync(); + + c1.UseReceivedApplicationMessageHandler(c => + { + lock (locked) + { + receivedMessagesCount++; + } + }); + + c2.UseReceivedApplicationMessageHandler(c => + { + lock (locked) + { + receivedMessagesCount++; + } + }); + + await c1.SubscribeAsync(new TopicFilter { Topic = "a", QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce }); + await c2.SubscribeAsync(new TopicFilter { Topic = "a", QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce }); + + var message = new MqttApplicationMessageBuilder().WithTopic("a").WithAtLeastOnceQoS().Build(); + + for (var i = 0; i < 1000; i++) + { + await c1.PublishAsync(message); + } + + await Task.Delay(500); + + Assert.AreEqual(2000, receivedMessagesCount); + } + } + + [TestMethod] + public async Task Session_Takeover() + { + using (var testEnvironment = new TestEnvironment()) + { + await testEnvironment.StartServerAsync(); + + var options = new MqttClientOptionsBuilder() + .WithCleanSession(false) + .WithClientId("a"); + + var client1 = await testEnvironment.ConnectClientAsync(options); + await Task.Delay(500); + + var client2 = await testEnvironment.ConnectClientAsync(options); + await Task.Delay(500); + + Assert.IsFalse(client1.IsConnected); + Assert.IsTrue(client2.IsConnected); + } + } + + [TestMethod] + public async Task No_Messages_If_No_Subscription() + { + using (var testEnvironment = new TestEnvironment()) + { + await testEnvironment.StartServerAsync(); + + var client = await testEnvironment.ConnectClientAsync(); + var receivedMessages = new List(); + + client.Connected += async (s, e) => + { + await client.PublishAsync("Connected"); + }; + + client.UseReceivedApplicationMessageHandler(c => + { + lock (receivedMessages) + { + receivedMessages.Add(c.ApplicationMessage); + } + }); + + await Task.Delay(500); + + await client.PublishAsync("Hello"); + + await Task.Delay(500); + + Assert.AreEqual(0, receivedMessages.Count); + } + } + + [TestMethod] + public async Task Set_Subscription_At_Server() + { + using (var testEnvironment = new TestEnvironment()) + { + var server = await testEnvironment.StartServerAsync(); + server.ClientConnected += async (s, e) => + { + await server.SubscribeAsync(e.ClientId, "topic1"); + }; + + var client = await testEnvironment.ConnectClientAsync(); + var receivedMessages = new List(); + + client.UseReceivedApplicationMessageHandler(c => + { + lock (receivedMessages) + { + receivedMessages.Add(c.ApplicationMessage); + } + }); + + await Task.Delay(500); + + await client.PublishAsync("Hello"); + await Task.Delay(100); + Assert.AreEqual(0, receivedMessages.Count); + + await client.PublishAsync("topic1"); + await Task.Delay(100); + Assert.AreEqual(1, receivedMessages.Count); + } + } + + [TestMethod] + public async Task Shutdown_Disconnects_Clients_Gracefully() + { + using (var testEnvironment = new TestEnvironment()) + { + var server = await testEnvironment.StartServerAsync(new MqttServerOptionsBuilder()); + + var disconnectCalled = 0; + + var c1 = await testEnvironment.ConnectClientAsync(new MqttClientOptionsBuilder()); + c1.Disconnected += (sender, args) => disconnectCalled++; + + await Task.Delay(100); + + await server.StopAsync(); + + await Task.Delay(100); + + Assert.AreEqual(1, disconnectCalled); + } + } + + [TestMethod] + public async Task Handle_Clean_Disconnect() + { + using (var testEnvironment = new TestEnvironment()) + { + var server = await testEnvironment.StartServerAsync(new MqttServerOptionsBuilder()); + + var clientConnectedCalled = 0; + var clientDisconnectedCalled = 0; + + server.ClientConnected += (_, __) => Interlocked.Increment(ref clientConnectedCalled); + server.ClientDisconnected += (_, __) => Interlocked.Increment(ref clientDisconnectedCalled); + + var c1 = await testEnvironment.ConnectClientAsync(new MqttClientOptionsBuilder()); + + Assert.AreEqual(1, clientConnectedCalled); + Assert.AreEqual(0, clientDisconnectedCalled); + + await Task.Delay(500); + + await c1.DisconnectAsync(); + + await Task.Delay(500); + + Assert.AreEqual(1, clientConnectedCalled); + Assert.AreEqual(1, clientDisconnectedCalled); + } + } + + [TestMethod] + public async Task Client_Disconnect_Without_Errors() + { + using (var testEnvironment = new TestEnvironment()) + { + bool clientWasConnected; + + var server = await testEnvironment.StartServerAsync(new MqttServerOptionsBuilder()); + try + { + var client = await testEnvironment.ConnectClientAsync(new MqttClientOptionsBuilder()); + + clientWasConnected = true; + + await client.DisconnectAsync(); + + await Task.Delay(500); + } + finally + { + await server.StopAsync(); + } + + Assert.IsTrue(clientWasConnected); + + testEnvironment.ThrowIfLogErrors(); + } + } + + [TestMethod] + public async Task Handle_Lots_Of_Parallel_Retained_Messages() + { + const int ClientCount = 50; + + using (var testEnvironment = new TestEnvironment()) + { + var server = await testEnvironment.StartServerAsync(); + + var tasks = new List(); + for (var i = 0; i < ClientCount; i++) + { + var i2 = i; + var testEnvironment2 = testEnvironment; + + tasks.Add(Task.Run(async () => + { + try + { + using (var client = await testEnvironment2.ConnectClientAsync()) + { + // Clear retained message. + await client.PublishAsync(new MqttApplicationMessageBuilder().WithTopic("r" + i2) + .WithPayload(new byte[0]).WithRetainFlag().Build()); + + // Set retained message. + await client.PublishAsync(new MqttApplicationMessageBuilder().WithTopic("r" + i2) + .WithPayload("value").WithRetainFlag().Build()); + + await client.DisconnectAsync(); + } + } + catch (Exception exception) + { + testEnvironment2.TrackException(exception); + } + })); + } + + await Task.WhenAll(tasks); + + await Task.Delay(1000); + + var retainedMessages = await server.GetRetainedMessagesAsync(); + + Assert.AreEqual(ClientCount, retainedMessages.Count); + + for (var i = 0; i < ClientCount; i++) + { + Assert.IsTrue(retainedMessages.Any(m => m.Topic == "r" + i)); + } + } + } + + [TestMethod] + public async Task Retained_Messages_Flow() + { + using (var testEnvironment = new TestEnvironment()) + { + var retainedMessage = new MqttApplicationMessageBuilder().WithTopic("r").WithPayload("r").WithRetainFlag().Build(); + + await testEnvironment.StartServerAsync(); + var c1 = await testEnvironment.ConnectClientAsync(); + + var receivedMessages = 0; + + var c2 = await testEnvironment.ConnectClientAsync(); + c2.UseReceivedApplicationMessageHandler(c => + { + Interlocked.Increment(ref receivedMessages); + }); + + await c1.PublishAsync(retainedMessage); + await c1.DisconnectAsync(); + await Task.Delay(500); + + for (var i = 0; i < 5; i++) + { + await c2.UnsubscribeAsync("r"); + await Task.Delay(100); + Assert.AreEqual(i, receivedMessages); + + await c2.SubscribeAsync("r"); + await Task.Delay(100); + Assert.AreEqual(i + 1, receivedMessages); + } + + await c2.DisconnectAsync(); + } + } + + [TestMethod] + public async Task Receive_No_Retained_Message_After_Subscribe() + { + using (var testEnvironment = new TestEnvironment()) + { + await testEnvironment.StartServerAsync(); + + var c1 = await testEnvironment.ConnectClientAsync(); + await c1.PublishAsync(new MqttApplicationMessageBuilder().WithTopic("retained").WithPayload(new byte[3]).WithRetainFlag().Build()); + await c1.DisconnectAsync(); + + var receivedMessagesCount = 0; + + var c2 = await testEnvironment.ConnectClientAsync(); + c2.UseReceivedApplicationMessageHandler(c => Interlocked.Increment(ref receivedMessagesCount)); + await c2.SubscribeAsync(new TopicFilterBuilder().WithTopic("retained_other").Build()); + + await Task.Delay(500); + + Assert.AreEqual(0, receivedMessagesCount); + } + } + + [TestMethod] + public async Task Receive_Retained_Message_After_Subscribe() + { + using (var testEnvironment = new TestEnvironment()) + { + await testEnvironment.StartServerAsync(); + + var c1 = await testEnvironment.ConnectClientAsync(); + await c1.PublishAsync(new MqttApplicationMessageBuilder().WithTopic("retained").WithPayload(new byte[3]).WithRetainFlag().Build()); + await c1.DisconnectAsync(); + + var receivedMessages = new List(); + + var c2 = await testEnvironment.ConnectClientAsync(); + c2.UseReceivedApplicationMessageHandler(c => + { + lock (receivedMessages) + { + receivedMessages.Add(c.ApplicationMessage); + } + }); + + await c2.SubscribeAsync(new TopicFilterBuilder().WithTopic("retained").Build()); + + await Task.Delay(500); + + Assert.AreEqual(1, receivedMessages.Count); + Assert.IsTrue(receivedMessages.First().Retain); + } + } + + [TestMethod] + public async Task Clear_Retained_Message() + { + using (var testEnvironment = new TestEnvironment()) + { + var receivedMessagesCount = 0; + + await testEnvironment.StartServerAsync(); + + var c1 = await testEnvironment.ConnectClientAsync(); + + await c1.PublishAsync(new MqttApplicationMessageBuilder().WithTopic("retained").WithPayload(new byte[3]).WithRetainFlag().Build()); + await c1.PublishAsync(new MqttApplicationMessageBuilder().WithTopic("retained").WithPayload(new byte[0]).WithRetainFlag().Build()); + + await c1.DisconnectAsync(); + + var c2 = await testEnvironment.ConnectClientAsync(); + + c2.UseReceivedApplicationMessageHandler(c => Interlocked.Increment(ref receivedMessagesCount)); + + await Task.Delay(200); + await c2.SubscribeAsync(new TopicFilter { Topic = "retained", QualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce }); + await Task.Delay(500); + + Assert.AreEqual(0, receivedMessagesCount); + } + } + + [TestMethod] + public async Task Persist_Retained_Message() + { + var serverStorage = new TestServerStorage(); + + using (var testEnvironment = new TestEnvironment()) + { + await testEnvironment.StartServerAsync(new MqttServerOptionsBuilder().WithStorage(serverStorage)); + + var c1 = await testEnvironment.ConnectClientAsync(); + + await c1.PublishAsync(new MqttApplicationMessageBuilder().WithTopic("retained").WithPayload(new byte[3]).WithRetainFlag().Build()); + + await Task.Delay(500); + + Assert.AreEqual(1, serverStorage.Messages.Count); + } + } + + [TestMethod] + public async Task Intercept_Message() + { + void Interceptor(MqttApplicationMessageInterceptorContext context) + { + context.ApplicationMessage.Payload = Encoding.ASCII.GetBytes("extended"); + } + + using (var testEnvironment = new TestEnvironment()) + { + await testEnvironment.StartServerAsync(new MqttServerOptionsBuilder().WithApplicationMessageInterceptor(Interceptor)); + + var c1 = await testEnvironment.ConnectClientAsync(); + var c2 = await testEnvironment.ConnectClientAsync(); + await c2.SubscribeAsync(new TopicFilterBuilder().WithTopic("test").Build()); + + var isIntercepted = false; + c2.UseReceivedApplicationMessageHandler(c => + { + isIntercepted = string.Compare("extended", Encoding.UTF8.GetString(c.ApplicationMessage.Payload), StringComparison.Ordinal) == 0; + }); + + await c1.PublishAsync(new MqttApplicationMessageBuilder().WithTopic("test").Build()); + await c1.DisconnectAsync(); + + await Task.Delay(500); + + Assert.IsTrue(isIntercepted); + } + } + + [TestMethod] + public async Task Send_Long_Body() + { + using (var testEnvironment = new TestEnvironment()) + { + const int PayloadSizeInMB = 30; + const int CharCount = PayloadSizeInMB * 1024 * 1024; + + var longBody = new byte[CharCount]; + byte @char = 32; + + for (long i = 0; i < PayloadSizeInMB * 1024L * 1024L; i++) + { + longBody[i] = @char; + + @char++; + + if (@char > 126) + { + @char = 32; + } + } + + byte[] receivedBody = null; + + await testEnvironment.StartServerAsync(new MqttServerOptionsBuilder()); + var client1 = await testEnvironment.ConnectClientAsync(); + client1.UseReceivedApplicationMessageHandler(c => + { + receivedBody = c.ApplicationMessage.Payload; + }); + + await client1.SubscribeAsync("string"); + + var client2 = await testEnvironment.ConnectClientAsync(); + await client2.PublishAsync("string", longBody); + + await Task.Delay(500); + + Assert.IsTrue(longBody.SequenceEqual(receivedBody ?? new byte[0])); + } + } + + [TestMethod] + public async Task Deny_Connection() + { + var serverOptions = new MqttServerOptionsBuilder().WithConnectionValidator(context => + { + context.ReturnCode = MqttConnectReturnCode.ConnectionRefusedNotAuthorized; + }); + + using (var testEnvironment = new TestEnvironment()) + { + testEnvironment.IgnoreClientLogErrors = true; + + await testEnvironment.StartServerAsync(serverOptions); + + try + { + await testEnvironment.ConnectClientAsync(); + Assert.Fail("An exception should be raised."); + } + catch (Exception exception) + { + if (exception is MqttConnectingFailedException connectingFailedException) + { + Assert.AreEqual(MqttClientConnectResultCode.NotAuthorized, connectingFailedException.ResultCode); + } + else + { + Assert.Fail("Wrong exception."); + } + } + } + } + + [TestMethod] + public async Task Same_Client_Id_Connect_Disconnect_Event_Order() + { + using (var testEnvironment = new TestEnvironment()) + { + var server = await testEnvironment.StartServerAsync(new MqttServerOptionsBuilder()); + + var events = new List(); + + server.ClientConnected += (_, __) => + { + lock (events) + { + events.Add("c"); + } + }; + + server.ClientDisconnected += (_, __) => + { + lock (events) + { + events.Add("d"); + } + }; + + var clientOptions = new MqttClientOptionsBuilder() + .WithClientId("same_id"); + + // c + var c1 = await testEnvironment.ConnectClientAsync(clientOptions); + + await Task.Delay(500); + + var flow = string.Join(string.Empty, events); + Assert.AreEqual("c", flow); + + // dc + var c2 = await testEnvironment.ConnectClientAsync(clientOptions); + + await Task.Delay(500); + + flow = string.Join(string.Empty, events); + Assert.AreEqual("cdc", flow); + + // nothing + await c1.DisconnectAsync(); + + await Task.Delay(500); + + // d + await c2.DisconnectAsync(); + + await Task.Delay(500); + + await server.StopAsync(); + + flow = string.Join(string.Empty, events); + Assert.AreEqual("cdcd", flow); + } + } + + [TestMethod] + public async Task Remove_Session() + { + using (var testEnvironment = new TestEnvironment()) + { + var server = await testEnvironment.StartServerAsync(new MqttServerOptionsBuilder()); + + var clientOptions = new MqttClientOptionsBuilder(); + var c1 = await testEnvironment.ConnectClientAsync(clientOptions); + await Task.Delay(500); + Assert.AreEqual(1, (await server.GetClientStatusAsync()).Count); + + await c1.DisconnectAsync(); + await Task.Delay(500); + + Assert.AreEqual(0, (await server.GetClientStatusAsync()).Count); + } + } + + [TestMethod] + public async Task Stop_And_Restart() + { + using (var testEnvironment = new TestEnvironment()) + { + testEnvironment.IgnoreClientLogErrors = true; + + var server = await testEnvironment.StartServerAsync(); + + await testEnvironment.ConnectClientAsync(); + await server.StopAsync(); + + try + { + await testEnvironment.ConnectClientAsync(); + Assert.Fail("Connecting should fail."); + } + catch (Exception) + { + } + + await server.StartAsync(new MqttServerOptionsBuilder().WithDefaultEndpointPort(testEnvironment.ServerPort).Build()); + await testEnvironment.ConnectClientAsync(); + } + } + + [TestMethod] + public async Task Close_Idle_Connection() + { + using (var testEnvironment = new TestEnvironment()) + { + await testEnvironment.StartServerAsync(new MqttServerOptionsBuilder().WithDefaultCommunicationTimeout(TimeSpan.FromSeconds(1))); + + var client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + await client.ConnectAsync("localhost", testEnvironment.ServerPort); + + // Don't send anything. The server should close the connection. + await Task.Delay(TimeSpan.FromSeconds(3)); + + try + { + var receivedBytes = await client.ReceiveAsync(new ArraySegment(new byte[10]), SocketFlags.Partial); + if (receivedBytes == 0) + { + return; + } + + Assert.Fail("Receive should throw an exception."); + } + catch (SocketException) + { + } + } + } + + [TestMethod] + public async Task Send_Garbage() + { + using (var testEnvironment = new TestEnvironment()) + { + await testEnvironment.StartServerAsync(new MqttServerOptionsBuilder().WithDefaultCommunicationTimeout(TimeSpan.FromSeconds(1))); + + // Send an invalid packet and ensure that the server will close the connection and stay in a waiting state + // forever. This is security related. + var client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + await client.ConnectAsync("localhost", testEnvironment.ServerPort); + await client.SendAsync(Encoding.UTF8.GetBytes("Garbage"), SocketFlags.None); + + await Task.Delay(TimeSpan.FromSeconds(3)); + + try + { + var receivedBytes = await client.ReceiveAsync(new ArraySegment(new byte[10]), SocketFlags.Partial); + if (receivedBytes == 0) + { + return; + } + + Assert.Fail("Receive should throw an exception."); + } + catch (SocketException) + { + } + } + } + + [TestMethod] + public async Task Do_Not_Send_Retained_Messages_For_Denied_Subscription() + { + using (var testEnvironment = new TestEnvironment()) + { + await testEnvironment.StartServerAsync(new MqttServerOptionsBuilder().WithSubscriptionInterceptor(c => + { + // This should lead to no subscriptions for "n" at all. So also no sending of retained messages. + if (c.TopicFilter.Topic == "n") + { + c.AcceptSubscription = false; + } + })); + + // Prepare some retained messages. + var client1 = await testEnvironment.ConnectClientAsync(); + await client1.PublishAsync(new MqttApplicationMessageBuilder().WithTopic("y").WithPayload("x").WithRetainFlag().Build()); + await client1.PublishAsync(new MqttApplicationMessageBuilder().WithTopic("n").WithPayload("x").WithRetainFlag().Build()); + await client1.DisconnectAsync(); + + await Task.Delay(500); + + // Subscribe to all retained message types. + // It is important to do this in a range of filters to ensure that a subscription is not "hidden". + var client2 = await testEnvironment.ConnectClientAsync(); + + var buffer = new StringBuilder(); + + client2.UseReceivedApplicationMessageHandler(c => + { + lock (buffer) + { + buffer.Append(c.ApplicationMessage.Topic); + } + }); + + await client2.SubscribeAsync(new TopicFilter { Topic = "y" }, new TopicFilter { Topic = "n" }); + + await Task.Delay(500); + + Assert.AreEqual("y", buffer.ToString()); + } + } + + [TestMethod] + public async Task Collect_Messages_In_Disconnected_Session() + { + using (var testEnvironment = new TestEnvironment()) + { + var server = await testEnvironment.StartServerAsync(new MqttServerOptionsBuilder().WithPersistentSessions()); + + // Create the session including the subscription. + var client1 = await testEnvironment.ConnectClientAsync(new MqttClientOptionsBuilder().WithClientId("a")); + await client1.SubscribeAsync("x"); + await client1.DisconnectAsync(); + await Task.Delay(500); + + var clientStatus = await server.GetClientStatusAsync(); + Assert.AreEqual(0, clientStatus.Count); + + var client2 = await testEnvironment.ConnectClientAsync(new MqttClientOptionsBuilder().WithClientId("b")); + await client2.PublishAsync("x", "1"); + await client2.PublishAsync("x", "2"); + await client2.PublishAsync("x", "3"); + await client2.DisconnectAsync(); + + await Task.Delay(500); + + clientStatus = await server.GetClientStatusAsync(); + var sessionStatus = await server.GetSessionStatusAsync(); + + Assert.AreEqual(0, clientStatus.Count); + Assert.AreEqual(2, sessionStatus.Count); + + Assert.AreEqual(3, sessionStatus.First(s => s.ClientId == "a").PendingApplicationMessagesCount); + } + } + + private static async Task TestPublishAsync( + string topic, + MqttQualityOfServiceLevel qualityOfServiceLevel, + string topicFilter, + MqttQualityOfServiceLevel filterQualityOfServiceLevel, + int expectedReceivedMessagesCount) + { + using (var testEnvironment = new TestEnvironment()) + { + var receivedMessagesCount = 0; + + await testEnvironment.StartServerAsync(new MqttServerOptionsBuilder()); + + var c1 = await testEnvironment.ConnectClientAsync(new MqttClientOptionsBuilder().WithClientId("receiver")); + c1.UseReceivedApplicationMessageHandler(c => Interlocked.Increment(ref receivedMessagesCount)); + await c1.SubscribeAsync(new TopicFilterBuilder().WithTopic(topicFilter).WithQualityOfServiceLevel(filterQualityOfServiceLevel).Build()); + + var c2 = await testEnvironment.ConnectClientAsync(new MqttClientOptionsBuilder().WithClientId("sender")); + await c2.PublishAsync(new MqttApplicationMessageBuilder().WithTopic(topic).WithPayload(new byte[0]).WithQualityOfServiceLevel(qualityOfServiceLevel).Build()); + await c2.DisconnectAsync().ConfigureAwait(false); + + await Task.Delay(500); + await c1.UnsubscribeAsync(topicFilter); + await Task.Delay(500); + + Assert.AreEqual(expectedReceivedMessagesCount, receivedMessagesCount); + } + } + } +} diff --git a/Tests/MQTTnet.TestApp.UniversalWindows/MainPage.xaml b/Tests/MQTTnet.TestApp.UniversalWindows/MainPage.xaml index bc1cdf45..210f0595 100644 --- a/Tests/MQTTnet.TestApp.UniversalWindows/MainPage.xaml +++ b/Tests/MQTTnet.TestApp.UniversalWindows/MainPage.xaml @@ -5,6 +5,7 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:server="using:MQTTnet.Server" + xmlns:status="using:MQTTnet.Server.Status" d:DesignHeight="800" d:DesignWidth="800" mc:Ignorable="d"> @@ -228,7 +229,7 @@ - +