Merge proces, enable signing of the assembly.

This commit is contained in:
Christian Kratky
2017-11-25 19:55:26 +01:00
parent ebfc6aab6b
commit f9d67e2cd2
153 changed files with 323 additions and 383 deletions
@@ -1,8 +1,8 @@
using System;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using MQTTnet.Core.Server;
using System.Linq;
using MQTTnet.Server;
namespace MQTTnet.AspNetCore
{
@@ -3,9 +3,9 @@ using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Hosting;
using MQTTnet.Core.Adapter;
using MQTTnet.Core.Diagnostics;
using MQTTnet.Core.Server;
using MQTTnet.Adapter;
using MQTTnet.Diagnostics;
using MQTTnet.Server;
namespace MQTTnet.AspNetCore
{
@@ -1,10 +1,10 @@
using System;
using System.Net.WebSockets;
using System.Threading.Tasks;
using MQTTnet.Core.Adapter;
using MQTTnet.Core.Diagnostics;
using MQTTnet.Core.Serializer;
using MQTTnet.Core.Server;
using MQTTnet.Adapter;
using MQTTnet.Diagnostics;
using MQTTnet.Serializer;
using MQTTnet.Server;
namespace MQTTnet.AspNetCore
{
@@ -3,7 +3,7 @@ using System.IO;
using System.Net.WebSockets;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Core.Channel;
using MQTTnet.Channel;
using MQTTnet.Implementations;
namespace MQTTnet.AspNetCore
@@ -1,9 +1,9 @@
using System;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using MQTTnet.Core.Adapter;
using MQTTnet.Core.Diagnostics;
using MQTTnet.Core.Server;
using MQTTnet.Adapter;
using MQTTnet.Diagnostics;
using MQTTnet.Server;
namespace MQTTnet.AspNetCore
{
@@ -0,0 +1,22 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Packets;
using MQTTnet.Serializer;
namespace MQTTnet.Adapter
{
public interface IMqttChannelAdapter
{
IMqttPacketSerializer PacketSerializer { get; }
Task ConnectAsync(TimeSpan timeout);
Task DisconnectAsync(TimeSpan timeout);
Task SendPacketsAsync(TimeSpan timeout, CancellationToken cancellationToken, IEnumerable<MqttBasePacket> packets);
Task<MqttBasePacket> ReceivePacketAsync(TimeSpan timeout, CancellationToken cancellationToken);
}
}
@@ -0,0 +1,14 @@
using System;
using System.Threading.Tasks;
using MQTTnet.Server;
namespace MQTTnet.Adapter
{
public interface IMqttServerAdapter
{
event EventHandler<MqttServerAdapterClientAcceptedEventArgs> ClientAccepted;
Task StartAsync(MqttServerOptions options);
Task StopAsync();
}
}
@@ -0,0 +1,184 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Channel;
using MQTTnet.Diagnostics;
using MQTTnet.Exceptions;
using MQTTnet.Internal;
using MQTTnet.Packets;
using MQTTnet.Serializer;
namespace MQTTnet.Adapter
{
public class MqttChannelAdapter : IMqttChannelAdapter
{
private const uint ErrorOperationAborted = 0x800703E3;
private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
private readonly IMqttNetLogger _logger;
private readonly IMqttChannel _channel;
public MqttChannelAdapter(IMqttChannel channel, IMqttPacketSerializer serializer, IMqttNetLogger logger)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_channel = channel ?? throw new ArgumentNullException(nameof(channel));
PacketSerializer = serializer ?? throw new ArgumentNullException(nameof(serializer));
}
public IMqttPacketSerializer PacketSerializer { get; }
public async Task ConnectAsync(TimeSpan timeout)
{
_logger.Info<MqttChannelAdapter>("Connecting [Timeout={0}]", timeout);
await ExecuteAndWrapExceptionAsync(() => _channel.ConnectAsync().TimeoutAfter(timeout));
}
public async Task DisconnectAsync(TimeSpan timeout)
{
_logger.Info<MqttChannelAdapter>("Disconnecting [Timeout={0}]", timeout);
await ExecuteAndWrapExceptionAsync(() => _channel.DisconnectAsync().TimeoutAfter(timeout));
}
public async Task SendPacketsAsync(TimeSpan timeout, CancellationToken cancellationToken, IEnumerable<MqttBasePacket> packets)
{
await ExecuteAndWrapExceptionAsync(async () =>
{
await _semaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
try
{
foreach (var packet in packets)
{
if (packet == null)
{
continue;
}
_logger.Trace<MqttChannelAdapter>("TX >>> {0} [Timeout={1}]", packet, timeout);
var writeBuffer = PacketSerializer.Serialize(packet);
await _channel.SendStream.WriteAsync(writeBuffer, 0, writeBuffer.Length, cancellationToken).ConfigureAwait(false);
}
if (timeout > TimeSpan.Zero)
{
await _channel.SendStream.FlushAsync(cancellationToken).TimeoutAfter(timeout).ConfigureAwait(false);
}
else
{
await _channel.SendStream.FlushAsync(cancellationToken).ConfigureAwait(false);
}
}
finally
{
_semaphore.Release();
}
});
}
public async Task<MqttBasePacket> ReceivePacketAsync(TimeSpan timeout, CancellationToken cancellationToken)
{
MqttBasePacket packet = null;
await ExecuteAndWrapExceptionAsync(async () =>
{
ReceivedMqttPacket receivedMqttPacket = null;
try
{
if (timeout > TimeSpan.Zero)
{
receivedMqttPacket = await ReceiveAsync(_channel.ReceiveStream, cancellationToken).TimeoutAfter(timeout).ConfigureAwait(false);
}
else
{
receivedMqttPacket = await ReceiveAsync(_channel.ReceiveStream, cancellationToken).ConfigureAwait(false);
}
if (receivedMqttPacket == null || cancellationToken.IsCancellationRequested)
{
throw new TaskCanceledException();
}
packet = PacketSerializer.Deserialize(receivedMqttPacket);
if (packet == null)
{
throw new MqttProtocolViolationException("Received malformed packet.");
}
_logger.Trace<MqttChannelAdapter>("RX <<< {0}", packet);
}
finally
{
receivedMqttPacket?.Dispose();
}
});
return packet;
}
private static async Task<ReceivedMqttPacket> ReceiveAsync(Stream stream, CancellationToken cancellationToken)
{
var header = MqttPacketReader.ReadHeaderFromSource(stream, cancellationToken);
if (header == null)
{
return null;
}
if (header.BodyLength == 0)
{
return new ReceivedMqttPacket(header, new MemoryStream(0));
}
var body = new byte[header.BodyLength];
var offset = 0;
do
{
var readBytesCount = await stream.ReadAsync(body, offset, body.Length - offset, cancellationToken).ConfigureAwait(false);
offset += readBytesCount;
} while (offset < header.BodyLength);
return new ReceivedMqttPacket(header, new MemoryStream(body, 0, body.Length));
}
private static async Task ExecuteAndWrapExceptionAsync(Func<Task> action)
{
try
{
await action().ConfigureAwait(false);
}
catch (TaskCanceledException)
{
throw;
}
catch (OperationCanceledException)
{
throw;
}
catch (MqttCommunicationTimedOutException)
{
throw;
}
catch (MqttCommunicationException)
{
throw;
}
catch (COMException comException)
{
if ((uint)comException.HResult == ErrorOperationAborted)
{
throw new OperationCanceledException();
}
throw new MqttCommunicationException(comException);
}
catch (Exception exception)
{
throw new MqttCommunicationException(exception);
}
}
}
}
@@ -0,0 +1,17 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Packets;
namespace MQTTnet.Adapter
{
public static class MqttChannelAdapterExtensions
{
public static Task SendPacketsAsync(this IMqttChannelAdapter adapter, TimeSpan timeout, CancellationToken cancellationToken, params MqttBasePacket[] packets)
{
if (adapter == null) throw new ArgumentNullException(nameof(adapter));
return adapter.SendPacketsAsync(timeout, cancellationToken, packets);
}
}
}
@@ -0,0 +1,16 @@
using MQTTnet.Exceptions;
using MQTTnet.Protocol;
namespace MQTTnet.Adapter
{
public class MqttConnectingFailedException : MqttCommunicationException
{
public MqttConnectingFailedException(MqttConnectReturnCode returnCode)
: base($"Connecting with MQTT server failed ({returnCode}).")
{
ReturnCode = returnCode;
}
public MqttConnectReturnCode ReturnCode { get; }
}
}
@@ -0,0 +1,17 @@
using System;
using System.Threading.Tasks;
namespace MQTTnet.Adapter
{
public class MqttServerAdapterClientAcceptedEventArgs : EventArgs
{
public MqttServerAdapterClientAcceptedEventArgs(IMqttChannelAdapter client)
{
Client = client ?? throw new ArgumentNullException(nameof(client));
}
public IMqttChannelAdapter Client { get; }
public Task SessionTask { get; set; }
}
}
@@ -0,0 +1,24 @@
using System;
using System.IO;
using MQTTnet.Packets;
namespace MQTTnet.Adapter
{
public sealed class ReceivedMqttPacket : IDisposable
{
public ReceivedMqttPacket(MqttPacketHeader header, MemoryStream body)
{
Header = header ?? throw new ArgumentNullException(nameof(header));
Body = body ?? throw new ArgumentNullException(nameof(body));
}
public MqttPacketHeader Header { get; }
public MemoryStream Body { get; }
public void Dispose()
{
Body?.Dispose();
}
}
}
@@ -0,0 +1,16 @@
using System;
using System.Threading.Tasks;
namespace MQTTnet
{
public static class ApplicationMessagePublisherExtensions
{
public static Task PublishAsync(this IApplicationMessagePublisher client, params MqttApplicationMessage[] applicationMessages)
{
if (client == null) throw new ArgumentNullException(nameof(client));
if (applicationMessages == null) throw new ArgumentNullException(nameof(applicationMessages));
return client.PublishAsync(applicationMessages);
}
}
}
@@ -0,0 +1,14 @@
using System.IO;
using System.Threading.Tasks;
namespace MQTTnet.Channel
{
public interface IMqttChannel
{
Stream SendStream { get; }
Stream ReceiveStream { get; }
Task ConnectAsync();
Task DisconnectAsync();
}
}
@@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace MQTTnet.Client
{
public interface IMqttClient : IApplicationMessageReceiver, IApplicationMessagePublisher
{
bool IsConnected { get; }
event EventHandler<MqttClientConnectedEventArgs> Connected;
event EventHandler<MqttClientDisconnectedEventArgs> Disconnected;
Task<MqttClientConnectResult> ConnectAsync(IMqttClientOptions options);
Task DisconnectAsync();
Task<IList<MqttSubscribeResult>> SubscribeAsync(IEnumerable<TopicFilter> topicFilters);
Task UnsubscribeAsync(IEnumerable<string> topicFilters);
}
}
@@ -0,0 +1,10 @@
using MQTTnet.Adapter;
using MQTTnet.Diagnostics;
namespace MQTTnet.Client
{
public interface IMqttClientAdapterFactory
{
IMqttChannelAdapter CreateClientAdapter(IMqttClientChannelOptions options, IMqttNetLogger logger);
}
}
@@ -0,0 +1,7 @@
namespace MQTTnet.Client
{
public interface IMqttClientChannelOptions
{
MqttClientTlsOptions TlsOptions { get; }
}
}
@@ -0,0 +1,8 @@
namespace MQTTnet.Client
{
public interface IMqttClientCredentials
{
string Password { get; }
string Username { get; }
}
}
@@ -0,0 +1,16 @@
using MQTTnet.Diagnostics;
using MQTTnet.ManagedClient;
namespace MQTTnet.Client
{
public interface IMqttClientFactory
{
IMqttClient CreateMqttClient();
IMqttClient CreateMqttClient(IMqttNetLogger logger);
IManagedMqttClient CreateManagedMqttClient();
IManagedMqttClient CreateManagedMqttClient(IMqttNetLogger logger);
}
}
@@ -0,0 +1,20 @@
using System;
using MQTTnet.Serializer;
namespace MQTTnet.Client
{
public interface IMqttClientOptions
{
string ClientId { get; }
IMqttClientCredentials Credentials { get; }
bool CleanSession { get; }
MqttApplicationMessage WillMessage { get; }
TimeSpan CommunicationTimeout { get; }
TimeSpan KeepAlivePeriod { get; }
MqttProtocolVersion ProtocolVersion { get; }
IMqttClientChannelOptions ChannelOptions { get; }
}
}
@@ -0,0 +1,487 @@
using System;
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;
namespace MQTTnet.Client
{
public class MqttClient : IMqttClient
{
private readonly HashSet<ushort> _unacknowledgedPublishPackets = new HashSet<ushort>();
private readonly IMqttClientAdapterFactory _adapterFactory;
private readonly MqttPacketDispatcher _packetDispatcher;
private readonly IMqttNetLogger _logger;
private IMqttClientOptions _options;
private bool _isReceivingPackets;
private int _latestPacketIdentifier;
private CancellationTokenSource _cancellationTokenSource;
private IMqttChannelAdapter _adapter;
public MqttClient(IMqttClientAdapterFactory channelFactory, IMqttNetLogger logger)
{
_adapterFactory = channelFactory ?? throw new ArgumentNullException(nameof(channelFactory));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_packetDispatcher = new MqttPacketDispatcher(logger);
}
public event EventHandler<MqttClientConnectedEventArgs> Connected;
public event EventHandler<MqttClientDisconnectedEventArgs> Disconnected;
public event EventHandler<MqttApplicationMessageReceivedEventArgs> ApplicationMessageReceived;
public bool IsConnected { get; private set; }
public async Task<MqttClientConnectResult> ConnectAsync(IMqttClientOptions options)
{
if (options == null) throw new ArgumentNullException(nameof(options));
if (options.ChannelOptions == null) throw new ArgumentException("ChannelOptions are not set.");
ThrowIfConnected("It is not allowed to connect with a server after the connection is established.");
try
{
_options = options;
_cancellationTokenSource = new CancellationTokenSource();
_latestPacketIdentifier = 0;
_packetDispatcher.Reset();
_adapter = _adapterFactory.CreateClientAdapter(options.ChannelOptions, _logger);
_logger.Trace<MqttClient>("Trying to connect with server.");
await _adapter.ConnectAsync(_options.CommunicationTimeout).ConfigureAwait(false);
_logger.Trace<MqttClient>("Connection with server established.");
await StartReceivingPacketsAsync(_cancellationTokenSource.Token).ConfigureAwait(false);
var connectResponse = await AuthenticateAsync(options.WillMessage).ConfigureAwait(false);
_logger.Trace<MqttClient>("MQTT connection with server established.");
if (_options.KeepAlivePeriod != TimeSpan.Zero)
{
StartSendingKeepAliveMessages(_cancellationTokenSource.Token);
}
IsConnected = true;
Connected?.Invoke(this, new MqttClientConnectedEventArgs(connectResponse.IsSessionPresent));
return new MqttClientConnectResult(connectResponse.IsSessionPresent);
}
catch (Exception exception)
{
_logger.Error<MqttClient>(exception, "Error while connecting with server.");
await DisconnectInternalAsync().ConfigureAwait(false);
throw;
}
}
public async Task DisconnectAsync()
{
if (!IsConnected)
{
return;
}
try
{
await SendAsync(new MqttDisconnectPacket()).ConfigureAwait(false);
}
finally
{
await DisconnectInternalAsync().ConfigureAwait(false);
}
}
public async Task<IList<MqttSubscribeResult>> SubscribeAsync(IEnumerable<TopicFilter> topicFilters)
{
if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters));
ThrowIfNotConnected();
var subscribePacket = new MqttSubscribePacket
{
PacketIdentifier = GetNewPacketIdentifier(),
TopicFilters = topicFilters.ToList()
};
var response = await SendAndReceiveAsync<MqttSubAckPacket>(subscribePacket).ConfigureAwait(false);
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();
}
public async Task UnsubscribeAsync(IEnumerable<string> topicFilters)
{
if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters));
ThrowIfNotConnected();
var unsubscribePacket = new MqttUnsubscribePacket
{
PacketIdentifier = GetNewPacketIdentifier(),
TopicFilters = topicFilters.ToList()
};
await SendAndReceiveAsync<MqttUnsubAckPacket>(unsubscribePacket);
}
public async Task PublishAsync(IEnumerable<MqttApplicationMessage> applicationMessages)
{
ThrowIfNotConnected();
var publishPackets = applicationMessages.Select(m => m.ToPublishPacket());
foreach (var qosGroup in publishPackets.GroupBy(p => p.QualityOfServiceLevel))
{
var qosPackets = qosGroup.ToArray();
switch (qosGroup.Key)
{
case MqttQualityOfServiceLevel.AtMostOnce:
{
// No packet identifier is used for QoS 0 [3.3.2.2 Packet Identifier]
await _adapter.SendPacketsAsync(_options.CommunicationTimeout, _cancellationTokenSource.Token, qosPackets).ConfigureAwait(false);
break;
}
case MqttQualityOfServiceLevel.AtLeastOnce:
{
foreach (var publishPacket in qosPackets)
{
publishPacket.PacketIdentifier = GetNewPacketIdentifier();
await SendAndReceiveAsync<MqttPubAckPacket>(publishPacket).ConfigureAwait(false);
}
break;
}
case MqttQualityOfServiceLevel.ExactlyOnce:
{
foreach (var publishPacket in qosPackets)
{
publishPacket.PacketIdentifier = GetNewPacketIdentifier();
var pubRecPacket = await SendAndReceiveAsync<MqttPubRecPacket>(publishPacket).ConfigureAwait(false);
await SendAndReceiveAsync<MqttPubCompPacket>(pubRecPacket.CreateResponse<MqttPubRelPacket>()).ConfigureAwait(false);
}
break;
}
default:
{
throw new InvalidOperationException();
}
}
}
}
private async Task<MqttConnAckPacket> AuthenticateAsync(MqttApplicationMessage willApplicationMessage)
{
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 response = await SendAndReceiveAsync<MqttConnAckPacket>(connectPacket).ConfigureAwait(false);
if (response.ConnectReturnCode != MqttConnectReturnCode.ConnectionAccepted)
{
throw new MqttConnectingFailedException(response.ConnectReturnCode);
}
return response;
}
private void ThrowIfNotConnected()
{
if (!IsConnected) throw new MqttCommunicationException("The client is not connected.");
}
private void ThrowIfConnected(string message)
{
if (IsConnected) throw new MqttProtocolViolationException(message);
}
private async Task DisconnectInternalAsync()
{
var clientWasConnected = IsConnected;
IsConnected = false;
var cts = _cancellationTokenSource;
if (cts == null || cts.IsCancellationRequested)
{
return;
}
cts.Cancel(false);
cts.Dispose();
_cancellationTokenSource = null;
try
{
await _adapter.DisconnectAsync(_options.CommunicationTimeout).ConfigureAwait(false);
_logger.Info<MqttClient>("Disconnected from adapter.");
}
catch (Exception exception)
{
_logger.Warning<MqttClient>(exception, "Error while disconnecting from adapter.");
}
finally
{
_logger.Info<MqttClient>("Disconnected.");
Disconnected?.Invoke(this, new MqttClientDisconnectedEventArgs(clientWasConnected));
}
}
private async Task ProcessReceivedPacketAsync(MqttBasePacket packet)
{
try
{
_logger.Info<MqttClient>("Received <<< {0}", packet);
if (packet is MqttPingReqPacket)
{
await SendAsync(new MqttPingRespPacket());
return;
}
if (packet is MqttDisconnectPacket)
{
await DisconnectAsync();
return;
}
if (packet is MqttPublishPacket publishPacket)
{
await ProcessReceivedPublishPacket(publishPacket);
return;
}
if (packet is MqttPubRelPacket pubRelPacket)
{
await ProcessReceivedPubRelPacket(pubRelPacket);
return;
}
_packetDispatcher.Dispatch(packet);
}
catch (Exception exception)
{
_logger.Error<MqttClient>(exception, "Unhandled exception while processing received packet.");
}
}
private void FireApplicationMessageReceivedEvent(MqttPublishPacket publishPacket)
{
try
{
var applicationMessage = publishPacket.ToApplicationMessage();
ApplicationMessageReceived?.Invoke(this, new MqttApplicationMessageReceivedEventArgs(_options.ClientId, applicationMessage));
}
catch (Exception exception)
{
_logger.Error<MqttClient>(exception, "Unhandled exception while handling application message.");
}
}
private async Task ProcessReceivedPublishPacket(MqttPublishPacket publishPacket)
{
if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtMostOnce)
{
FireApplicationMessageReceivedEvent(publishPacket);
return;
}
if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtLeastOnce)
{
FireApplicationMessageReceivedEvent(publishPacket);
await SendAsync(new MqttPubAckPacket { PacketIdentifier = publishPacket.PacketIdentifier });
return;
}
if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.ExactlyOnce)
{
// QoS 2 is implement as method "B" [4.3.3 QoS 2: Exactly once delivery]
lock (_unacknowledgedPublishPackets)
{
_unacknowledgedPublishPackets.Add(publishPacket.PacketIdentifier);
}
FireApplicationMessageReceivedEvent(publishPacket);
await SendAsync(new MqttPubRecPacket { PacketIdentifier = publishPacket.PacketIdentifier });
return;
}
throw new MqttCommunicationException("Received a not supported QoS level.");
}
private Task ProcessReceivedPubRelPacket(MqttPubRelPacket pubRelPacket)
{
lock (_unacknowledgedPublishPackets)
{
_unacknowledgedPublishPackets.Remove(pubRelPacket.PacketIdentifier);
}
return SendAsync(pubRelPacket.CreateResponse<MqttPubCompPacket>());
}
private Task SendAsync(MqttBasePacket packet)
{
return _adapter.SendPacketsAsync(_options.CommunicationTimeout, _cancellationTokenSource.Token, packet);
}
private async Task<TResponsePacket> SendAndReceiveAsync<TResponsePacket>(MqttBasePacket requestPacket) where TResponsePacket : MqttBasePacket
{
var packetAwaiter = _packetDispatcher.WaitForPacketAsync(requestPacket, typeof(TResponsePacket), _options.CommunicationTimeout);
await _adapter.SendPacketsAsync(_options.CommunicationTimeout, _cancellationTokenSource.Token, requestPacket).ConfigureAwait(false);
return (TResponsePacket)await packetAwaiter.ConfigureAwait(false);
}
private ushort GetNewPacketIdentifier()
{
return (ushort)Interlocked.Increment(ref _latestPacketIdentifier);
}
private async Task SendKeepAliveMessagesAsync(CancellationToken cancellationToken)
{
_logger.Info<MqttClient>("Start sending keep alive packets.");
try
{
while (!cancellationToken.IsCancellationRequested)
{
await Task.Delay(_options.KeepAlivePeriod, cancellationToken).ConfigureAwait(false);
if (cancellationToken.IsCancellationRequested)
{
return;
}
await SendAndReceiveAsync<MqttPingRespPacket>(new MqttPingReqPacket()).ConfigureAwait(false);
}
}
catch (OperationCanceledException)
{
if (cancellationToken.IsCancellationRequested)
{
return;
}
await DisconnectInternalAsync().ConfigureAwait(false);
}
catch (MqttCommunicationException exception)
{
if (cancellationToken.IsCancellationRequested)
{
return;
}
_logger.Warning<MqttClient>(exception, "MQTT communication exception while sending/receiving keep alive packets.");
await DisconnectInternalAsync().ConfigureAwait(false);
}
catch (Exception exception)
{
_logger.Warning<MqttClient>(exception, "Unhandled exception while sending/receiving keep alive packets.");
await DisconnectInternalAsync().ConfigureAwait(false);
}
finally
{
_logger.Info<MqttClient>("Stopped sending keep alive packets.");
}
}
private async Task ReceivePacketsAsync(CancellationToken cancellationToken)
{
_logger.Info<MqttClient>("Start receiving packets.");
try
{
while (!cancellationToken.IsCancellationRequested)
{
_isReceivingPackets = true;
var packet = await _adapter.ReceivePacketAsync(TimeSpan.Zero, cancellationToken).ConfigureAwait(false);
if (cancellationToken.IsCancellationRequested)
{
return;
}
StartProcessReceivedPacket(packet, cancellationToken);
}
}
catch (OperationCanceledException)
{
if (cancellationToken.IsCancellationRequested)
{
return;
}
await DisconnectInternalAsync().ConfigureAwait(false);
}
catch (MqttCommunicationException exception)
{
if (cancellationToken.IsCancellationRequested)
{
return;
}
_logger.Warning<MqttClient>(exception, "MQTT communication exception while receiving packets.");
await DisconnectInternalAsync().ConfigureAwait(false);
}
catch (Exception exception)
{
_logger.Error<MqttClient>(exception, "Unhandled exception while receiving packets.");
await DisconnectInternalAsync().ConfigureAwait(false);
}
finally
{
_logger.Info<MqttClient>("Stopped receiving packets.");
}
}
private void StartProcessReceivedPacket(MqttBasePacket packet, CancellationToken cancellationToken)
{
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
Task.Run(
() => ProcessReceivedPacketAsync(packet),
cancellationToken).ConfigureAwait(false);
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
}
private async Task StartReceivingPacketsAsync(CancellationToken cancellationToken)
{
_isReceivingPackets = false;
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
Task.Run(
async () => await ReceivePacketsAsync(cancellationToken),
cancellationToken).ConfigureAwait(false);
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
while (!_isReceivingPackets && !cancellationToken.IsCancellationRequested)
{
await Task.Delay(TimeSpan.FromMilliseconds(100), cancellationToken).ConfigureAwait(false);
}
}
private void StartSendingKeepAliveMessages(CancellationToken cancellationToken)
{
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
Task.Run(
async () => await SendKeepAliveMessagesAsync(cancellationToken),
cancellationToken).ConfigureAwait(false);
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
}
}
}
@@ -0,0 +1,12 @@
namespace MQTTnet.Client
{
public class MqttClientConnectResult
{
public MqttClientConnectResult(bool isSessionPresent)
{
IsSessionPresent = isSessionPresent;
}
public bool IsSessionPresent { get; }
}
}
@@ -0,0 +1,14 @@
using System;
namespace MQTTnet.Client
{
public class MqttClientConnectedEventArgs : EventArgs
{
public MqttClientConnectedEventArgs(bool isSessionPresent)
{
IsSessionPresent = isSessionPresent;
}
public bool IsSessionPresent { get; }
}
}
@@ -0,0 +1,9 @@
namespace MQTTnet.Client
{
public class MqttClientCredentials : IMqttClientCredentials
{
public string Username { get; set; }
public string Password { get; set; }
}
}
@@ -0,0 +1,14 @@
using System;
namespace MQTTnet.Client
{
public class MqttClientDisconnectedEventArgs : EventArgs
{
public MqttClientDisconnectedEventArgs(bool clientWasConnected)
{
ClientWasConnected = clientWasConnected;
}
public bool ClientWasConnected { get; }
}
}
@@ -0,0 +1,26 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace MQTTnet.Client
{
public static class MqttClientExtensions
{
public static Task<IList<MqttSubscribeResult>> SubscribeAsync(this IMqttClient client, params TopicFilter[] topicFilters)
{
if (client == null) throw new ArgumentNullException(nameof(client));
if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters));
return client.SubscribeAsync(topicFilters.ToList());
}
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));
return client.UnsubscribeAsync(topicFilters.ToList());
}
}
}
@@ -0,0 +1,24 @@
using System;
using MQTTnet.Serializer;
namespace MQTTnet.Client
{
public class MqttClientOptions : IMqttClientOptions
{
public MqttApplicationMessage WillMessage { get; set; }
public string ClientId { get; set; } = Guid.NewGuid().ToString("N");
public bool CleanSession { get; set; } = true;
public IMqttClientCredentials Credentials { get; set; } = new MqttClientCredentials();
public TimeSpan KeepAlivePeriod { get; set; } = TimeSpan.FromSeconds(5);
public TimeSpan CommunicationTimeout { get; set; } = TimeSpan.FromSeconds(10);
public MqttProtocolVersion ProtocolVersion { get; set; } = MqttProtocolVersion.V311;
public IMqttClientChannelOptions ChannelOptions { get; set; }
}
}
@@ -0,0 +1,135 @@
using System;
using System.Linq;
using MQTTnet.Serializer;
namespace MQTTnet.Client
{
public class MqttClientOptionsBuilder
{
private readonly MqttClientOptions _options = new MqttClientOptions();
private MqttClientTcpOptions _tcpOptions;
private MqttClientWebSocketOptions _webSocketOptions;
private MqttClientTlsOptions _tlsOptions;
public MqttClientOptionsBuilder WithProtocolVersion(MqttProtocolVersion value)
{
_options.ProtocolVersion = value;
return this;
}
public MqttClientOptionsBuilder WithCommunicationTimeout(TimeSpan value)
{
_options.CommunicationTimeout = value;
return this;
}
public MqttClientOptionsBuilder WithCleanSession(bool value = true)
{
_options.CleanSession = value;
return this;
}
public MqttClientOptionsBuilder WithKeepAlivePeriod(TimeSpan value)
{
_options.KeepAlivePeriod = value;
return this;
}
public MqttClientOptionsBuilder WithClientId(string value)
{
_options.ClientId = value;
return this;
}
public MqttClientOptionsBuilder WithWillMessage(MqttApplicationMessage value)
{
_options.WillMessage = value;
return this;
}
public MqttClientOptionsBuilder WithCredentials(string username, string password = null)
{
_options.Credentials = new MqttClientCredentials
{
Username = username,
Password = password
};
return this;
}
public MqttClientOptionsBuilder WithTcpServer(string server, int? port = null)
{
_tcpOptions = new MqttClientTcpOptions
{
Server = server,
Port = port
};
return this;
}
public MqttClientOptionsBuilder WithWebSocketServer(string uri)
{
_webSocketOptions = new MqttClientWebSocketOptions
{
Uri = uri
};
return this;
}
public MqttClientOptionsBuilder WithTls(
bool allowUntrustedCertificates = false,
bool ignoreCertificateChainErrors = false,
bool ignoreCertificateRevocationErrors = false,
params byte[][] certificates)
{
_tlsOptions = new MqttClientTlsOptions
{
UseTls = true,
AllowUntrustedCertificates = allowUntrustedCertificates,
IgnoreCertificateChainErrors = ignoreCertificateChainErrors,
IgnoreCertificateRevocationErrors = ignoreCertificateRevocationErrors,
Certificates = certificates?.ToList()
};
return this;
}
public MqttClientOptionsBuilder WithTls()
{
_tlsOptions = new MqttClientTlsOptions
{
UseTls = true
};
return this;
}
public IMqttClientOptions Build()
{
if (_tlsOptions != null)
{
if (_tcpOptions == null && _webSocketOptions == null)
{
throw new InvalidOperationException("A channel (TCP or WebSocket) must be set if TLS is configured.");
}
if (_tcpOptions != null)
{
_options.ChannelOptions = _tcpOptions;
}
else
{
_options.ChannelOptions = _webSocketOptions;
}
}
_options.ChannelOptions = (IMqttClientChannelOptions)_tcpOptions ?? _webSocketOptions;
return _options;
}
}
}
@@ -0,0 +1,11 @@
namespace MQTTnet.Client
{
public class MqttClientTcpOptions : IMqttClientChannelOptions
{
public string Server { get; set; }
public int? Port { get; set; }
public MqttClientTlsOptions TlsOptions { get; set; } = new MqttClientTlsOptions();
}
}
@@ -0,0 +1,19 @@
using System;
namespace MQTTnet.Client
{
public static class MqttClientTcpOptionsExtensions
{
public static int GetPort(this MqttClientTcpOptions options)
{
if (options == null) throw new ArgumentNullException(nameof(options));
if (options.Port.HasValue)
{
return options.Port.Value;
}
return !options.TlsOptions.UseTls ? 1883 : 8883;
}
}
}
@@ -0,0 +1,17 @@
using System.Collections.Generic;
namespace MQTTnet.Client
{
public class MqttClientTlsOptions
{
public bool UseTls { get; set; }
public bool IgnoreCertificateRevocationErrors { get; set; }
public bool IgnoreCertificateChainErrors { get; set; }
public bool AllowUntrustedCertificates { get; set; }
public List<byte[]> Certificates { get; set; }
}
}
@@ -0,0 +1,18 @@
using System.Collections.Generic;
using System.Net;
namespace MQTTnet.Client
{
public class MqttClientWebSocketOptions : IMqttClientChannelOptions
{
public string Uri { get; set; }
public IDictionary<string, string> RequestHeaders { get; set; }
public ICollection<string> SubProtocols { get; set; }
public CookieContainer CookieContainer { get; set; }
public MqttClientTlsOptions TlsOptions { get; set; } = new MqttClientTlsOptions();
}
}
@@ -0,0 +1,103 @@
using System;
using System.Collections.Concurrent;
using System.Threading.Tasks;
using MQTTnet.Diagnostics;
using MQTTnet.Exceptions;
using MQTTnet.Internal;
using MQTTnet.Packets;
namespace MQTTnet.Client
{
public class MqttPacketDispatcher
{
private readonly ConcurrentDictionary<Type, TaskCompletionSource<MqttBasePacket>> _packetByResponseType = new ConcurrentDictionary<Type, TaskCompletionSource<MqttBasePacket>>();
private readonly ConcurrentDictionary<Type, ConcurrentDictionary<ushort, TaskCompletionSource<MqttBasePacket>>> _packetByResponseTypeAndIdentifier = new ConcurrentDictionary<Type, ConcurrentDictionary<ushort, TaskCompletionSource<MqttBasePacket>>>();
private readonly IMqttNetLogger _logger;
public MqttPacketDispatcher(IMqttNetLogger logger)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
public async Task<MqttBasePacket> WaitForPacketAsync(MqttBasePacket request, Type responseType, TimeSpan timeout)
{
if (request == null) throw new ArgumentNullException(nameof(request));
var packetAwaiter = AddPacketAwaiter(request, responseType);
try
{
return await packetAwaiter.Task.TimeoutAfter(timeout).ConfigureAwait(false);
}
catch (MqttCommunicationTimedOutException)
{
_logger.Warning<MqttPacketDispatcher>("Timeout while waiting for packet of type '{0}'.", responseType.Name);
throw;
}
finally
{
RemovePacketAwaiter(request, responseType);
}
}
public void Dispatch(MqttBasePacket packet)
{
if (packet == null) throw new ArgumentNullException(nameof(packet));
var type = packet.GetType();
if (packet is IMqttPacketWithIdentifier withIdentifier)
{
if (_packetByResponseTypeAndIdentifier.TryGetValue(type, out var byid))
{
if (byid.TryRemove(withIdentifier.PacketIdentifier, out var tcs))
{
tcs.TrySetResult(packet);
return;
}
}
}
else if (_packetByResponseType.TryRemove(type, out var tcs))
{
tcs.TrySetResult(packet);
return;
}
throw new InvalidOperationException($"Packet of type '{type.Name}' not handled or dispatched.");
}
public void Reset()
{
_packetByResponseTypeAndIdentifier.Clear();
_packetByResponseType.Clear();
}
private TaskCompletionSource<MqttBasePacket> AddPacketAwaiter(MqttBasePacket request, Type responseType)
{
var tcs = new TaskCompletionSource<MqttBasePacket>();
if (request is IMqttPacketWithIdentifier requestWithIdentifier)
{
var byId = _packetByResponseTypeAndIdentifier.GetOrAdd(responseType, key => new ConcurrentDictionary<ushort, TaskCompletionSource<MqttBasePacket>>());
byId[requestWithIdentifier.PacketIdentifier] = tcs;
}
else
{
_packetByResponseType[responseType] = tcs;
}
return tcs;
}
private void RemovePacketAwaiter(MqttBasePacket request, Type responseType)
{
if (request is IMqttPacketWithIdentifier requestWithIdentifier)
{
var byId = _packetByResponseTypeAndIdentifier.GetOrAdd(responseType, key => new ConcurrentDictionary<ushort, TaskCompletionSource<MqttBasePacket>>());
byId.TryRemove(requestWithIdentifier.PacketIdentifier, out var _);
}
else
{
_packetByResponseType.TryRemove(responseType, out var _);
}
}
}
}
@@ -0,0 +1,17 @@
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; }
}
}
@@ -0,0 +1,21 @@
using System;
namespace MQTTnet.Diagnostics
{
public interface IMqttNetLogger
{
event EventHandler<MqttNetLogMessagePublishedEventArgs> LogMessagePublished;
void Trace<TSource>(string message, params object[] parameters);
void Info<TSource>(string message, params object[] parameters);
void Warning<TSource>(Exception exception, string message, params object[] parameters);
void Warning<TSource>(string message, params object[] parameters);
void Error<TSource>(Exception exception, string message, params object[] parameters);
void Error<TSource>(string message, params object[] parameters);
}
}
@@ -1,50 +0,0 @@
using System;
using System.Text;
using MQTTnet.Core.Diagnostics;
namespace MQTTnet.Diagnostics
{
public static class MqttNetConsoleLogger
{
private static readonly object Lock = new object();
public static void ForwardToConsole()
{
MqttNetGlobalLog.LogMessagePublished -= PrintToConsole;
MqttNetGlobalLog.LogMessagePublished += PrintToConsole;
}
private static void PrintToConsole(object sender, MqttNetLogMessagePublishedEventArgs e)
{
var output = new StringBuilder();
output.AppendLine($">> [{e.TraceMessage.Timestamp:O}] [{e.TraceMessage.ThreadId}] [{e.TraceMessage.Source}] [{e.TraceMessage.Level}]: {e.TraceMessage.Message}");
if (e.TraceMessage.Exception != null)
{
output.AppendLine(e.TraceMessage.Exception.ToString());
}
lock (Lock)
{
var backupColor = Console.ForegroundColor;
switch (e.TraceMessage.Level)
{
case MqttNetLogLevel.Error:
Console.ForegroundColor = ConsoleColor.Red;
break;
case MqttNetLogLevel.Warning:
Console.ForegroundColor = ConsoleColor.Yellow;
break;
case MqttNetLogLevel.Info:
Console.ForegroundColor = ConsoleColor.Green;
break;
case MqttNetLogLevel.Verbose:
Console.ForegroundColor = ConsoleColor.Gray;
break;
}
Console.Write(output);
Console.ForegroundColor = backupColor;
}
}
}
}
@@ -0,0 +1,18 @@
using System;
namespace MQTTnet.Diagnostics
{
public static class MqttNetGlobalLogger
{
public static event EventHandler<MqttNetLogMessagePublishedEventArgs> LogMessagePublished;
public static bool HasListeners => LogMessagePublished != null;
public static void Publish(MqttNetLogMessage logMessage)
{
if (logMessage == null) throw new ArgumentNullException(nameof(logMessage));
LogMessagePublished?.Invoke(null, new MqttNetLogMessagePublishedEventArgs(logMessage));
}
}
}
@@ -0,0 +1,13 @@
namespace MQTTnet.Diagnostics
{
public enum MqttNetLogLevel
{
Verbose,
Info,
Warning,
Error
}
}
@@ -0,0 +1,32 @@
using System;
namespace MQTTnet.Diagnostics
{
public sealed class MqttNetLogMessage
{
public MqttNetLogMessage(string logId, DateTime timestamp, int threadId, string source, MqttNetLogLevel level, string message, Exception exception)
{
LogId = logId;
Timestamp = timestamp;
ThreadId = threadId;
Source = source;
Level = level;
Message = message;
Exception = exception;
}
public string LogId { get; }
public DateTime Timestamp { get; }
public int ThreadId { get; }
public string Source { get; }
public MqttNetLogLevel Level { get; }
public string Message { get; }
public Exception Exception { get; }
}
}
@@ -0,0 +1,14 @@
using System;
namespace MQTTnet.Diagnostics
{
public sealed class MqttNetLogMessagePublishedEventArgs : EventArgs
{
public MqttNetLogMessagePublishedEventArgs(MqttNetLogMessage logMessage)
{
TraceMessage = logMessage ?? throw new ArgumentNullException(nameof(logMessage));
}
public MqttNetLogMessage TraceMessage { get; }
}
}
@@ -0,0 +1,74 @@
using System;
namespace MQTTnet.Diagnostics
{
public class MqttNetLogger : IMqttNetLogger
{
private readonly string _logId;
public MqttNetLogger(string logId = null)
{
_logId = logId;
}
public event EventHandler<MqttNetLogMessagePublishedEventArgs> LogMessagePublished;
public void Trace<TSource>(string message, params object[] parameters)
{
Publish<TSource>(MqttNetLogLevel.Verbose, null, message, parameters);
}
public void Info<TSource>(string message, params object[] parameters)
{
Publish<TSource>(MqttNetLogLevel.Info, null, message, parameters);
}
public void Warning<TSource>(Exception exception, string message, params object[] parameters)
{
Publish<TSource>(MqttNetLogLevel.Warning, exception, message, parameters);
}
public void Warning<TSource>(string message, params object[] parameters)
{
Warning<TSource>(null, message, parameters);
}
public void Error<TSource>(Exception exception, string message, params object[] parameters)
{
Publish<TSource>(MqttNetLogLevel.Error, exception, message, parameters);
}
public void Error<TSource>(string message, params object[] parameters)
{
Warning<TSource>(null, message, parameters);
}
private void Publish<TSource>(MqttNetLogLevel logLevel, Exception exception, string message, object[] parameters)
{
var hasLocalListeners = LogMessagePublished != null;
var hasGlobalListeners = MqttNetGlobalLogger.HasListeners;
if (!hasLocalListeners && !hasGlobalListeners)
{
return;
}
if (parameters.Length > 0)
{
message = string.Format(message, parameters);
}
var traceMessage = new MqttNetLogMessage(_logId, DateTime.Now, Environment.CurrentManagedThreadId, typeof(TSource).Name, logLevel, message, exception);
if (hasGlobalListeners)
{
MqttNetGlobalLogger.Publish(traceMessage);
}
if (hasLocalListeners)
{
LogMessagePublished?.Invoke(this, new MqttNetLogMessagePublishedEventArgs(traceMessage));
}
}
}
}
@@ -1,4 +1,4 @@
namespace MQTTnet
namespace MQTTnet.Diagnostics
{
public static class TargetFrameworkInfoProvider
{
@@ -0,0 +1,21 @@
using System;
namespace MQTTnet.Exceptions
{
public class MqttCommunicationException : Exception
{
protected MqttCommunicationException()
{
}
public MqttCommunicationException(Exception innerException)
: base(innerException.Message, innerException)
{
}
public MqttCommunicationException(string message)
: base(message)
{
}
}
}
@@ -0,0 +1,6 @@
namespace MQTTnet.Exceptions
{
public sealed class MqttCommunicationTimedOutException : MqttCommunicationException
{
}
}
@@ -0,0 +1,12 @@
using System;
namespace MQTTnet.Exceptions
{
public sealed class MqttProtocolViolationException : Exception
{
public MqttProtocolViolationException(string message)
: base(message)
{
}
}
}
@@ -0,0 +1,10 @@
using System.Collections.Generic;
using System.Threading.Tasks;
namespace MQTTnet
{
public interface IApplicationMessagePublisher
{
Task PublishAsync(IEnumerable<MqttApplicationMessage> applicationMessages);
}
}
@@ -0,0 +1,9 @@
using System;
namespace MQTTnet
{
public interface IApplicationMessageReceiver
{
event EventHandler<MqttApplicationMessageReceivedEventArgs> ApplicationMessageReceived;
}
}
@@ -1,8 +1,8 @@
using System;
using MQTTnet.Core.Adapter;
using MQTTnet.Core.Client;
using MQTTnet.Core.Diagnostics;
using MQTTnet.Core.Serializer;
using MQTTnet.Adapter;
using MQTTnet.Client;
using MQTTnet.Diagnostics;
using MQTTnet.Serializer;
namespace MQTTnet.Implementations
{
@@ -1,11 +1,11 @@
#if WINDOWS_UWP
using System;
using System.Threading.Tasks;
using MQTTnet.Core.Adapter;
using MQTTnet.Core.Server;
using Windows.Networking.Sockets;
using MQTTnet.Core.Diagnostics;
using MQTTnet.Core.Serializer;
using MQTTnet.Adapter;
using MQTTnet.Diagnostics;
using MQTTnet.Serializer;
using MQTTnet.Server;
namespace MQTTnet.Implementations
{
@@ -7,10 +7,10 @@ using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Core.Adapter;
using MQTTnet.Core.Server;
using MQTTnet.Core.Diagnostics;
using MQTTnet.Core.Serializer;
using MQTTnet.Adapter;
using MQTTnet.Diagnostics;
using MQTTnet.Serializer;
using MQTTnet.Server;
namespace MQTTnet.Implementations
{
@@ -8,8 +8,8 @@ using System.Threading.Tasks;
using Windows.Networking;
using Windows.Networking.Sockets;
using Windows.Security.Cryptography.Certificates;
using MQTTnet.Core.Channel;
using MQTTnet.Core.Client;
using MQTTnet.Channel;
using MQTTnet.Client;
namespace MQTTnet.Implementations
{
@@ -5,10 +5,10 @@ using System.Net.Sockets;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;
using MQTTnet.Core.Channel;
using MQTTnet.Core.Client;
using System.IO;
using System.Linq;
using MQTTnet.Channel;
using MQTTnet.Client;
namespace MQTTnet.Implementations
{
@@ -1,11 +1,11 @@
using MQTTnet.Core.Channel;
using MQTTnet.Core.Client;
using System;
using System;
using System.IO;
using System.Net.WebSockets;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Channel;
using MQTTnet.Client;
namespace MQTTnet.Implementations
{
@@ -5,7 +5,7 @@ using System.Linq;
using System.Net.WebSockets;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Core.Exceptions;
using MQTTnet.Exceptions;
namespace MQTTnet.Implementations
{
@@ -0,0 +1,35 @@
using MQTTnet.Packets;
namespace MQTTnet.Internal
{
internal 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
};
}
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
};
}
}
}
@@ -0,0 +1,74 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Exceptions;
namespace MQTTnet.Internal
{
public static class TaskExtensions
{
public static async Task TimeoutAfter(this Task task, TimeSpan timeout)
{
using (var cancellationTokenSource = new CancellationTokenSource())
{
try
{
var timeoutTask = Task.Delay(timeout, cancellationTokenSource.Token);
var finishedTask = await Task.WhenAny(timeoutTask, task).ConfigureAwait(false);
if (finishedTask == timeoutTask)
{
throw new MqttCommunicationTimedOutException();
}
if (task.IsCanceled)
{
throw new TaskCanceledException();
}
if (task.IsFaulted)
{
throw new MqttCommunicationException(task.Exception.GetBaseException());
}
}
finally
{
cancellationTokenSource.Cancel();
}
}
}
public static async Task<TResult> TimeoutAfter<TResult>(this Task<TResult> task, TimeSpan timeout)
{
using (var cancellationTokenSource = new CancellationTokenSource())
{
try
{
var timeoutTask = Task.Delay(timeout, cancellationTokenSource.Token);
var finishedTask = await Task.WhenAny(timeoutTask, task).ConfigureAwait(false);
if (finishedTask == timeoutTask)
{
throw new MqttCommunicationTimedOutException();
}
if (task.IsCanceled)
{
throw new TaskCanceledException();
}
if (task.IsFaulted)
{
throw new MqttCommunicationException(task.Exception.GetBaseException());
}
return task.Result;
}
finally
{
cancellationTokenSource.Cancel();
}
}
}
}
}
@@ -14,6 +14,8 @@
<Description />
<Authors />
<PackageId />
<SignAssembly>true</SignAssembly>
<AssemblyOriginatorKeyFile>codeSigningKey.pfx</AssemblyOriginatorKeyFile>
</PropertyGroup>
<PropertyGroup Condition="'$(TargetFramework)' == 'uap10.0'">
@@ -33,10 +35,6 @@
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" />
<ItemGroup>
<ProjectReference Include="..\..\MQTTnet.Core\MQTTnet.Core.csproj" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)'=='netstandard2.0'">
<PackageReference Include="System.Net.Security" Version="4.3.2" />
<PackageReference Include="System.Net.WebSockets" Version="4.3.0" />
@@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using MQTTnet.Client;
namespace MQTTnet.ManagedClient
{
public interface IManagedMqttClient : IApplicationMessageReceiver, IApplicationMessagePublisher
{
bool IsConnected { get; }
event EventHandler<MqttClientConnectedEventArgs> Connected;
event EventHandler<MqttClientDisconnectedEventArgs> Disconnected;
Task StartAsync(IManagedMqttClientOptions options);
Task StopAsync();
Task SubscribeAsync(IEnumerable<TopicFilter> topicFilters);
Task UnsubscribeAsync(IEnumerable<TopicFilter> topicFilters);
}
}
@@ -0,0 +1,14 @@
using System;
using MQTTnet.Client;
namespace MQTTnet.ManagedClient
{
public interface IManagedMqttClientOptions
{
IMqttClientOptions ClientOptions { get; }
TimeSpan AutoReconnectDelay { get; }
IManagedMqttClientStorage Storage { get; }
}
}
@@ -0,0 +1,12 @@
using System.Collections.Generic;
using System.Threading.Tasks;
namespace MQTTnet.ManagedClient
{
public interface IManagedMqttClientStorage
{
Task SaveQueuedMessagesAsync(IList<MqttApplicationMessage> messages);
Task<IList<MqttApplicationMessage>> LoadQueuedMessagesAsync();
}
}
@@ -0,0 +1,303 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Client;
using MQTTnet.Diagnostics;
using MQTTnet.Exceptions;
using MQTTnet.Protocol;
namespace MQTTnet.ManagedClient
{
public class ManagedMqttClient : IManagedMqttClient
{
private readonly ManagedMqttClientStorageManager _storageManager = new ManagedMqttClientStorageManager();
private readonly BlockingCollection<MqttApplicationMessage> _messageQueue = new BlockingCollection<MqttApplicationMessage>();
private readonly HashSet<TopicFilter> _subscriptions = new HashSet<TopicFilter>();
private readonly IMqttClient _mqttClient;
private readonly IMqttNetLogger _logger;
private CancellationTokenSource _connectionCancellationToken;
private CancellationTokenSource _publishingCancellationToken;
private IManagedMqttClientOptions _options;
private bool _subscriptionsNotPushed;
public ManagedMqttClient(IMqttClient mqttClient, IMqttNetLogger logger)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_mqttClient = mqttClient ?? throw new ArgumentNullException(nameof(mqttClient));
_mqttClient.Connected += OnConnected;
_mqttClient.Disconnected += OnDisconnected;
_mqttClient.ApplicationMessageReceived += OnApplicationMessageReceived;
}
public bool IsConnected => _mqttClient.IsConnected;
public event EventHandler<MqttClientConnectedEventArgs> Connected;
public event EventHandler<MqttClientDisconnectedEventArgs> Disconnected;
public event EventHandler<MqttApplicationMessageReceivedEventArgs> ApplicationMessageReceived;
public async Task StartAsync(IManagedMqttClientOptions options)
{
if (options == null) throw new ArgumentNullException(nameof(options));
if (options.ClientOptions == null) throw new ArgumentException("The client options are not set.", nameof(options));
if (!options.ClientOptions.CleanSession)
{
throw new NotSupportedException("The managed client does not support existing sessions.");
}
if (_connectionCancellationToken != null) throw new InvalidOperationException("The managed client is already started.");
_options = options;
await _storageManager.SetStorageAsync(_options.Storage).ConfigureAwait(false);
if (_options.Storage != null)
{
var loadedMessages = await _options.Storage.LoadQueuedMessagesAsync().ConfigureAwait(false);
foreach (var loadedMessage in loadedMessages)
{
_messageQueue.Add(loadedMessage);
}
}
_connectionCancellationToken = new CancellationTokenSource();
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
Task.Run(async () => await MaintainConnectionAsync(_connectionCancellationToken.Token), _connectionCancellationToken.Token).ConfigureAwait(false);
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
_logger.Info<ManagedMqttClient>("Started");
}
public Task StopAsync()
{
_connectionCancellationToken?.Cancel(false);
_connectionCancellationToken = null;
_publishingCancellationToken?.Cancel(false);
_publishingCancellationToken = null;
while (_messageQueue.Any())
{
_messageQueue.Take();
}
return Task.FromResult(0);
}
public async Task PublishAsync(IEnumerable<MqttApplicationMessage> applicationMessages)
{
if (applicationMessages == null) throw new ArgumentNullException(nameof(applicationMessages));
foreach (var applicationMessage in applicationMessages)
{
await _storageManager.AddAsync(applicationMessage).ConfigureAwait(false);
_messageQueue.Add(applicationMessage);
}
}
public Task SubscribeAsync(IEnumerable<TopicFilter> topicFilters)
{
if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters));
lock (_subscriptions)
{
foreach (var topicFilter in topicFilters)
{
if (_subscriptions.Add(topicFilter))
{
_subscriptionsNotPushed = true;
}
}
}
return Task.FromResult(0);
}
public Task UnsubscribeAsync(IEnumerable<TopicFilter> topicFilters)
{
lock (_subscriptions)
{
foreach (var topicFilter in topicFilters)
{
if (_subscriptions.Remove(topicFilter))
{
_subscriptionsNotPushed = true;
}
}
}
return Task.FromResult(0);
}
private async Task MaintainConnectionAsync(CancellationToken cancellationToken)
{
try
{
while (!cancellationToken.IsCancellationRequested)
{
var connectionState = await ReconnectIfRequiredAsync().ConfigureAwait(false);
if (connectionState == ReconnectionResult.NotConnected)
{
_publishingCancellationToken?.Cancel(false);
_publishingCancellationToken = null;
await Task.Delay(_options.AutoReconnectDelay, cancellationToken).ConfigureAwait(false);
continue;
}
if (connectionState == ReconnectionResult.Reconnected || _subscriptionsNotPushed)
{
await PushSubscriptionsAsync();
_publishingCancellationToken = new CancellationTokenSource();
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
Task.Run(async () => await PublishQueuedMessagesAsync(_publishingCancellationToken.Token), _publishingCancellationToken.Token).ConfigureAwait(false);
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
continue;
}
if (connectionState == ReconnectionResult.StillConnected)
{
await Task.Delay(TimeSpan.FromSeconds(1), _connectionCancellationToken.Token).ConfigureAwait(false);
}
}
}
catch (OperationCanceledException)
{
}
catch (MqttCommunicationException exception)
{
_logger.Warning<ManagedMqttClient>(exception, "Communication exception while maintaining connection.");
}
catch (Exception exception)
{
_logger.Error<ManagedMqttClient>(exception, "Unhandled exception while maintaining connection.");
}
finally
{
await _mqttClient.DisconnectAsync().ConfigureAwait(false);
_logger.Info<ManagedMqttClient>("Stopped");
}
}
private async Task PublishQueuedMessagesAsync(CancellationToken cancellationToken)
{
try
{
while (!cancellationToken.IsCancellationRequested)
{
var message = _messageQueue.Take(cancellationToken);
if (message == null)
{
continue;
}
if (cancellationToken.IsCancellationRequested)
{
continue;
}
await TryPublishQueuedMessageAsync(message).ConfigureAwait(false);
await _storageManager.RemoveAsync(message).ConfigureAwait(false);
}
}
catch (OperationCanceledException)
{
}
finally
{
_logger.Info<ManagedMqttClient>("Stopped publishing messages");
}
}
private async Task TryPublishQueuedMessageAsync(MqttApplicationMessage message)
{
try
{
await _mqttClient.PublishAsync(message).ConfigureAwait(false);
}
catch (MqttCommunicationException exception)
{
_logger.Warning<ManagedMqttClient>(exception, "Publishing application message failed.");
if (message.QualityOfServiceLevel > MqttQualityOfServiceLevel.AtMostOnce)
{
_messageQueue.Add(message);
}
}
catch (Exception exception)
{
_logger.Error<ManagedMqttClient>(exception, "Unhandled exception while publishing queued application message.");
}
}
private async Task PushSubscriptionsAsync()
{
_logger.Info<ManagedMqttClient>(nameof(ManagedMqttClient), "Synchronizing subscriptions");
List<TopicFilter> subscriptions;
lock (_subscriptions)
{
subscriptions = _subscriptions.ToList();
_subscriptionsNotPushed = false;
}
if (!_subscriptions.Any())
{
return;
}
try
{
await _mqttClient.SubscribeAsync(subscriptions).ConfigureAwait(false);
}
catch (Exception exception)
{
_logger.Warning<ManagedMqttClient>(exception, "Synchronizing subscriptions failed");
_subscriptionsNotPushed = true;
}
}
private async Task<ReconnectionResult> ReconnectIfRequiredAsync()
{
if (_mqttClient.IsConnected)
{
return ReconnectionResult.StillConnected;
}
try
{
await _mqttClient.ConnectAsync(_options.ClientOptions).ConfigureAwait(false);
return ReconnectionResult.Reconnected;
}
catch (Exception)
{
return ReconnectionResult.NotConnected;
}
}
private void OnApplicationMessageReceived(object sender, MqttApplicationMessageReceivedEventArgs eventArgs)
{
ApplicationMessageReceived?.Invoke(this, eventArgs);
}
private void OnDisconnected(object sender, MqttClientDisconnectedEventArgs eventArgs)
{
Disconnected?.Invoke(this, eventArgs);
}
private void OnConnected(object sender, MqttClientConnectedEventArgs eventArgs)
{
Connected?.Invoke(this, eventArgs);
}
}
}
@@ -0,0 +1,22 @@
using System;
using System.Threading.Tasks;
namespace MQTTnet.ManagedClient
{
public static class ManagedMqttClientExtensions
{
public static Task SubscribeAsync(this IManagedMqttClient managedClient, params TopicFilter[] topicFilters)
{
if (managedClient == null) throw new ArgumentNullException(nameof(managedClient));
return managedClient.SubscribeAsync(topicFilters);
}
public static Task UnsubscribeAsync(this IManagedMqttClient managedClient, params TopicFilter[] topicFilters)
{
if (managedClient == null) throw new ArgumentNullException(nameof(managedClient));
return managedClient.UnsubscribeAsync(topicFilters);
}
}
}
@@ -0,0 +1,14 @@
using System;
using MQTTnet.Client;
namespace MQTTnet.ManagedClient
{
public class ManagedMqttClientOptions : IManagedMqttClientOptions
{
public IMqttClientOptions ClientOptions { get; set; }
public TimeSpan AutoReconnectDelay { get; set; } = TimeSpan.FromSeconds(5);
public IManagedMqttClientStorage Storage { get; set; }
}
}
@@ -0,0 +1,49 @@
using System;
using MQTTnet.Client;
namespace MQTTnet.ManagedClient
{
public class ManagedMqttClientOptionsBuilder
{
private readonly ManagedMqttClientOptions _options = new ManagedMqttClientOptions();
public ManagedMqttClientOptionsBuilder WithAutoReconnectDelay(TimeSpan value)
{
_options.AutoReconnectDelay = value;
return this;
}
public ManagedMqttClientOptionsBuilder WithStorage(IManagedMqttClientStorage value)
{
_options.Storage = value;
return this;
}
public ManagedMqttClientOptionsBuilder WithClientOptions(IMqttClientOptions value)
{
_options.ClientOptions = value ?? throw new ArgumentNullException(nameof(value));
return this;
}
public ManagedMqttClientOptionsBuilder WithClientOptions(Action<MqttClientOptionsBuilder> options)
{
if (options == null) throw new ArgumentNullException(nameof(options));
var builder = new MqttClientOptionsBuilder();
options(builder);
_options.ClientOptions = builder.Build();
return this;
}
public ManagedMqttClientOptions Build()
{
if (_options.ClientOptions == null)
{
throw new InvalidOperationException("The ClientOptions cannot be null.");
}
return _options;
}
}
}
@@ -0,0 +1,75 @@
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace MQTTnet.ManagedClient
{
public class ManagedMqttClientStorageManager
{
private readonly List<MqttApplicationMessage> _applicationMessages = new List<MqttApplicationMessage>();
private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
private IManagedMqttClientStorage _storage;
public async Task SetStorageAsync(IManagedMqttClientStorage storage)
{
await _semaphore.WaitAsync().ConfigureAwait(false);
try
{
_storage = storage;
}
finally
{
_semaphore.Release();
}
}
public async Task AddAsync(MqttApplicationMessage applicationMessage)
{
await _semaphore.WaitAsync().ConfigureAwait(false);
try
{
if (_storage == null)
{
return;
}
_applicationMessages.Add(applicationMessage);
await SaveAsync().ConfigureAwait(false);
}
finally
{
_semaphore.Release();
}
}
public async Task RemoveAsync(MqttApplicationMessage applicationMessage)
{
await _semaphore.WaitAsync().ConfigureAwait(false);
try
{
if (_storage == null)
{
return;
}
var index = _applicationMessages.IndexOf(applicationMessage);
if (index == -1)
{
return;
}
_applicationMessages.RemoveAt(index);
await SaveAsync().ConfigureAwait(false);
}
finally
{
_semaphore.Release();
}
}
private Task SaveAsync()
{
return _storage.SaveQueuedMessagesAsync(_applicationMessages);
}
}
}
@@ -0,0 +1,9 @@
namespace MQTTnet.ManagedClient
{
public enum ReconnectionResult
{
StillConnected,
Reconnected,
NotConnected
}
}
@@ -0,0 +1,29 @@
using System;
using MQTTnet.Protocol;
namespace MQTTnet
{
public sealed class MqttApplicationMessage
{
public MqttApplicationMessage()
{
}
[Obsolete("Use object initializer or _MqttApplicationMessageBuilder_ instead.")]
public MqttApplicationMessage(string topic, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel, bool retain)
{
Topic = topic ?? throw new ArgumentNullException(nameof(topic));
Payload = payload ?? throw new ArgumentNullException(nameof(payload));
QualityOfServiceLevel = qualityOfServiceLevel;
Retain = retain;
}
public string Topic { get; set; }
public byte[] Payload { get; set; }
public MqttQualityOfServiceLevel QualityOfServiceLevel { get; set; }
public bool Retain { get; set; }
}
}
@@ -0,0 +1,114 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using MQTTnet.Exceptions;
using MQTTnet.Protocol;
namespace MQTTnet
{
public class MqttApplicationMessageBuilder
{
private MqttQualityOfServiceLevel _qualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce;
private string _topic;
private byte[] _payload;
private bool _retain;
public MqttApplicationMessageBuilder WithTopic(string topic)
{
_topic = topic;
return this;
}
public MqttApplicationMessageBuilder WithPayload(IEnumerable<byte> payload)
{
if (payload == null)
{
_payload = null;
return this;
}
_payload = payload.ToArray();
return this;
}
public MqttApplicationMessageBuilder WithPayload(MemoryStream payload)
{
if (payload == null)
{
_payload = null;
return this;
}
if (payload.Length == 0)
{
_payload = new byte[0];
}
else
{
_payload = new byte[payload.Length - payload.Position];
payload.Read(_payload, 0, _payload.Length);
}
return this;
}
public MqttApplicationMessageBuilder WithPayload(string payload)
{
if (payload == null)
{
_payload = null;
return this;
}
_payload = string.IsNullOrEmpty(payload) ? new byte[0] : Encoding.UTF8.GetBytes(payload);
return this;
}
public MqttApplicationMessageBuilder WithQualityOfServiceLevel(MqttQualityOfServiceLevel qualityOfServiceLevel)
{
_qualityOfServiceLevel = qualityOfServiceLevel;
return this;
}
public MqttApplicationMessageBuilder WithRetainFlag(bool value = true)
{
_retain = value;
return this;
}
public MqttApplicationMessageBuilder WithAtLeastOnceQoS()
{
_qualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce;
return this;
}
public MqttApplicationMessageBuilder WithAtMostOnceQoS()
{
_qualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce;
return this;
}
public MqttApplicationMessageBuilder WithExactlyOnceQoS()
{
_qualityOfServiceLevel = MqttQualityOfServiceLevel.ExactlyOnce;
return this;
}
public MqttApplicationMessage Build()
{
if (string.IsNullOrEmpty(_topic))
{
throw new MqttProtocolViolationException("Topic is not set.");
}
return new MqttApplicationMessage
{
Topic = _topic,
Payload = _payload ?? new byte[0],
QualityOfServiceLevel = _qualityOfServiceLevel,
Retain = _retain
};
}
}
}
@@ -0,0 +1,17 @@
using System;
namespace MQTTnet
{
public sealed class MqttApplicationMessageReceivedEventArgs : EventArgs
{
public MqttApplicationMessageReceivedEventArgs(string clientId, MqttApplicationMessage applicationMessage)
{
ClientId = clientId;
ApplicationMessage = applicationMessage ?? throw new ArgumentNullException(nameof(applicationMessage));
}
public string ClientId { get; }
public MqttApplicationMessage ApplicationMessage { get; }
}
}
@@ -1,11 +1,11 @@
using System;
using System.Collections.Generic;
using MQTTnet.Core.Adapter;
using MQTTnet.Core.Client;
using MQTTnet.Adapter;
using MQTTnet.Client;
using MQTTnet.Diagnostics;
using MQTTnet.Implementations;
using MQTTnet.Core.ManagedClient;
using MQTTnet.Core.Server;
using MQTTnet.Core.Diagnostics;
using MQTTnet.ManagedClient;
using MQTTnet.Server;
namespace MQTTnet
{
@@ -0,0 +1,7 @@
namespace MQTTnet.Packets
{
public interface IMqttPacketWithIdentifier
{
ushort PacketIdentifier { get; set; }
}
}
@@ -0,0 +1,6 @@
namespace MQTTnet.Packets
{
public abstract class MqttBasePacket
{
}
}
@@ -0,0 +1,7 @@
namespace MQTTnet.Packets
{
public class MqttBasePublishPacket : MqttBasePacket, IMqttPacketWithIdentifier
{
public ushort PacketIdentifier { get; set; }
}
}
@@ -0,0 +1,16 @@
using MQTTnet.Protocol;
namespace MQTTnet.Packets
{
public sealed class MqttConnAckPacket : MqttBasePacket
{
public bool IsSessionPresent { get; set; }
public MqttConnectReturnCode ConnectReturnCode { get; set; }
public override string ToString()
{
return "ConnAck: [ConnectReturnCode=" + ConnectReturnCode + "] [IsSessionPresent=" + IsSessionPresent + "]";
}
}
}
@@ -0,0 +1,26 @@
using MQTTnet.Serializer;
namespace MQTTnet.Packets
{
public sealed 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 + "]";
}
}
}
@@ -0,0 +1,10 @@
namespace MQTTnet.Packets
{
public sealed class MqttDisconnectPacket : MqttBasePacket
{
public override string ToString()
{
return "Disconnect";
}
}
}
@@ -0,0 +1,26 @@
using System;
namespace MQTTnet.Packets
{
public static class MqttPacketExtensions
{
public static TResponsePacket CreateResponse<TResponsePacket>(this MqttBasePacket packet)
{
if (packet == null) throw new ArgumentNullException(nameof(packet));
var responsePacket = Activator.CreateInstance<TResponsePacket>();
if (responsePacket is IMqttPacketWithIdentifier responsePacketWithIdentifier)
{
if (!(packet is IMqttPacketWithIdentifier requestPacketWithIdentifier))
{
throw new InvalidOperationException("Response packet has PacketIdentifier but request packet does not.");
}
responsePacketWithIdentifier.PacketIdentifier = requestPacketWithIdentifier.PacketIdentifier;
}
return responsePacket;
}
}
}
@@ -0,0 +1,13 @@
using MQTTnet.Protocol;
namespace MQTTnet.Packets
{
public class MqttPacketHeader
{
public MqttControlPacketType ControlPacketType { get; set; }
public byte FixedHeader { get; set; }
public int BodyLength { get; set; }
}
}
@@ -0,0 +1,10 @@
namespace MQTTnet.Packets
{
public sealed class MqttPingReqPacket : MqttBasePacket
{
public override string ToString()
{
return "PingReq";
}
}
}
@@ -0,0 +1,10 @@
namespace MQTTnet.Packets
{
public sealed class MqttPingRespPacket : MqttBasePacket
{
public override string ToString()
{
return "PingResp";
}
}
}
@@ -0,0 +1,10 @@
namespace MQTTnet.Packets
{
public sealed class MqttPubAckPacket : MqttBasePublishPacket
{
public override string ToString()
{
return "PubAck";
}
}
}
@@ -0,0 +1,10 @@
namespace MQTTnet.Packets
{
public sealed class MqttPubCompPacket : MqttBasePublishPacket
{
public override string ToString()
{
return "PubComp";
}
}
}
@@ -0,0 +1,10 @@
namespace MQTTnet.Packets
{
public sealed class MqttPubRecPacket : MqttBasePublishPacket
{
public override string ToString()
{
return "PubRec";
}
}
}
@@ -0,0 +1,10 @@
namespace MQTTnet.Packets
{
public sealed class MqttPubRelPacket : MqttBasePublishPacket
{
public override string ToString()
{
return "PubRel";
}
}
}
@@ -0,0 +1,27 @@
using MQTTnet.Protocol;
namespace MQTTnet.Packets
{
public sealed class MqttPublishPacket : MqttBasePublishPacket
{
public bool Retain { get; set; }
public MqttQualityOfServiceLevel QualityOfServiceLevel { get; set; }
public bool Dup { get; set; }
public string Topic { get; set; }
public byte[] Payload { get; set; }
public override string ToString()
{
return "Publish: [Topic=" + Topic + "]" +
" [Payload.Length=" + Payload?.Length + "]" +
" [QoSLevel=" + QualityOfServiceLevel + "]" +
" [Dup=" + Dup + "]" +
" [Retain=" + Retain + "]" +
" [PacketIdentifier=" + PacketIdentifier + "]";
}
}
}
@@ -0,0 +1,19 @@
using System.Collections.Generic;
using System.Linq;
using MQTTnet.Protocol;
namespace MQTTnet.Packets
{
public sealed class MqttSubAckPacket : MqttBasePacket, IMqttPacketWithIdentifier
{
public ushort PacketIdentifier { get; set; }
public IList<MqttSubscribeReturnCode> SubscribeReturnCodes { get; } = new List<MqttSubscribeReturnCode>();
public override string ToString()
{
var subscribeReturnCodesText = string.Join(",", SubscribeReturnCodes.Select(f => f.ToString()));
return "SubAck: [PacketIdentifier=" + PacketIdentifier + "] [SubscribeReturnCodes=" + subscribeReturnCodesText + "]";
}
}
}
@@ -0,0 +1,18 @@
using System.Collections.Generic;
using System.Linq;
namespace MQTTnet.Packets
{
public sealed class MqttSubscribePacket : MqttBasePacket, IMqttPacketWithIdentifier
{
public ushort PacketIdentifier { get; set; }
public IList<TopicFilter> TopicFilters { get; set; } = new List<TopicFilter>();
public override string ToString()
{
var topicFiltersText = string.Join(",", TopicFilters.Select(f => f.Topic + "@" + f.QualityOfServiceLevel));
return "Subscribe: [PacketIdentifier=" + PacketIdentifier + "] [TopicFilters=" + topicFiltersText + "]";
}
}
}
@@ -0,0 +1,12 @@
namespace MQTTnet.Packets
{
public sealed class MqttUnsubAckPacket : MqttBasePacket, IMqttPacketWithIdentifier
{
public ushort PacketIdentifier { get; set; }
public override string ToString()
{
return "UnsubAck: [PacketIdentifier=" + PacketIdentifier + "]";
}
}
}
@@ -0,0 +1,17 @@
using System.Collections.Generic;
namespace MQTTnet.Packets
{
public sealed class MqttUnsubscribePacket : MqttBasePacket, IMqttPacketWithIdentifier
{
public ushort PacketIdentifier { get; set; }
public IList<string> TopicFilters { get; set; } = new List<string>();
public override string ToString()
{
var topicFiltersText = string.Join(",", TopicFilters);
return "Subscribe: [PacketIdentifier=" + PacketIdentifier + "] [TopicFilters=" + topicFiltersText + "]";
}
}
}
@@ -0,0 +1,12 @@
namespace MQTTnet.Protocol
{
public enum MqttConnectReturnCode
{
ConnectionAccepted = 0x00,
ConnectionRefusedUnacceptableProtocolVersion = 0x01,
ConnectionRefusedIdentifierRejected = 0x02,
ConnectionRefusedServerUnavailable = 0x03,
ConnectionRefusedBadUsernameOrPassword = 0x04,
ConnectionRefusedNotAuthorized = 0x05
}
}
@@ -0,0 +1,20 @@
namespace MQTTnet.Protocol
{
public enum MqttControlPacketType
{
Connect = 1,
ConnAck = 2,
Publish = 3,
PubAck = 4,
PubRec = 5,
PubRel = 6,
PubComp = 7,
Subscribe = 8,
SubAck = 9,
Unsubscibe = 10,
UnsubAck = 11,
PingReq = 12,
PingResp = 13,
Disconnect = 14
}
}
@@ -0,0 +1,9 @@
namespace MQTTnet.Protocol
{
public enum MqttQualityOfServiceLevel
{
AtMostOnce = 0x00,
AtLeastOnce = 0x01,
ExactlyOnce = 0x02
}
}
@@ -0,0 +1,10 @@
namespace MQTTnet.Protocol
{
public enum MqttSubscribeReturnCode
{
SuccessMaximumQoS0 = 0x00,
SuccessMaximumQoS1 = 0x01,
SuccessMaximumQoS2 = 0x02,
Failure = 0x80
}
}
@@ -0,0 +1,48 @@
using System;
namespace MQTTnet.Serializer
{
public sealed class ByteReader
{
private readonly int _source;
private int _index;
public ByteReader(int source)
{
_source = source;
}
public bool Read()
{
if (_index >= 8)
{
throw new InvalidOperationException("End of byte reached.");
}
var result = ((1 << _index) & _source) > 0;
_index++;
return result;
}
public int Read(int count)
{
if (_index + count > 8)
{
throw new InvalidOperationException("End of byte will be reached.");
}
var result = 0;
for (var i = 0; i < count; i++)
{
if (((1 << _index) & _source) > 0)
{
result |= 1 << i;
}
_index++;
}
return result;
}
}
}
@@ -0,0 +1,36 @@
using System;
namespace MQTTnet.Serializer
{
public sealed class ByteWriter
{
private int _index;
private int _byte;
public byte Value => (byte)_byte;
public void Write(int @byte, int count)
{
for (var i = 0; i < count; i++)
{
var value = ((1 << i) & @byte) > 0;
Write(value);
}
}
public void Write(bool bit)
{
if (_index >= 8)
{
throw new InvalidOperationException("End of the byte reached.");
}
if (bit)
{
_byte |= 1 << _index;
}
_index++;
}
}
}
@@ -0,0 +1,14 @@
using MQTTnet.Adapter;
using MQTTnet.Packets;
namespace MQTTnet.Serializer
{
public interface IMqttPacketSerializer
{
MqttProtocolVersion ProtocolVersion { get; set; }
byte[] Serialize(MqttBasePacket mqttPacket);
MqttBasePacket Deserialize(ReceivedMqttPacket receivedMqttPacket);
}
}
@@ -0,0 +1,109 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Adapter;
using MQTTnet.Exceptions;
using MQTTnet.Packets;
using MQTTnet.Protocol;
namespace MQTTnet.Serializer
{
public sealed class MqttPacketReader : BinaryReader
{
private readonly ReceivedMqttPacket _receivedMqttPacket;
public MqttPacketReader(ReceivedMqttPacket receivedMqttPacket)
: base(receivedMqttPacket.Body, Encoding.UTF8, true)
{
_receivedMqttPacket = receivedMqttPacket;
}
public bool EndOfRemainingData => BaseStream.Position == _receivedMqttPacket.Header.BodyLength;
public static MqttPacketHeader ReadHeaderFromSource(Stream stream, CancellationToken cancellationToken)
{
var buffer = stream.ReadByte();
if (buffer == -1)
{
return null;
}
var fixedHeader = (byte)buffer;
var controlPacketType = (MqttControlPacketType)(fixedHeader >> 4);
var bodyLength = ReadBodyLengthFromSource(stream, cancellationToken);
return new MqttPacketHeader
{
FixedHeader = fixedHeader,
ControlPacketType = controlPacketType,
BodyLength = bodyLength
};
}
public override ushort ReadUInt16()
{
var buffer = ReadBytes(2);
var temp = buffer[0];
buffer[0] = buffer[1];
buffer[1] = temp;
return BitConverter.ToUInt16(buffer, 0);
}
public string ReadStringWithLengthPrefix()
{
var buffer = ReadWithLengthPrefix();
return Encoding.UTF8.GetString(buffer, 0, buffer.Length);
}
public byte[] ReadWithLengthPrefix()
{
var length = ReadUInt16();
return ReadBytes(length);
}
public byte[] ReadRemainingData()
{
return ReadBytes(_receivedMqttPacket.Header.BodyLength - (int)BaseStream.Position);
}
private static int ReadBodyLengthFromSource(Stream stream, CancellationToken cancellationToken)
{
// Alorithm taken from http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html.
var multiplier = 1;
var value = 0;
byte encodedByte;
var readBytes = new List<int>();
do
{
if (cancellationToken.IsCancellationRequested)
{
throw new TaskCanceledException();
}
var buffer = stream.ReadByte();
readBytes.Add(buffer);
if (buffer == -1)
{
throw new MqttCommunicationException("Connection closed while reading remaining length data.");
}
encodedByte = (byte)buffer;
value += (byte)(encodedByte & 127) * multiplier;
multiplier *= 128;
if (multiplier > 128 * 128 * 128)
{
throw new MqttProtocolViolationException($"Remaining length is invalid (Data={string.Join(",", readBytes)}).");
}
} while ((encodedByte & 128) != 0);
return value;
}
}
}
@@ -0,0 +1,514 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using MQTTnet.Adapter;
using MQTTnet.Exceptions;
using MQTTnet.Packets;
using MQTTnet.Protocol;
namespace MQTTnet.Serializer
{
public sealed class MqttPacketSerializer : IMqttPacketSerializer
{
private static byte[] ProtocolVersionV311Name { get; } = Encoding.UTF8.GetBytes("MQTT");
private static byte[] ProtocolVersionV310Name { get; } = Encoding.UTF8.GetBytes("MQIs");
public MqttProtocolVersion ProtocolVersion { get; set; } = MqttProtocolVersion.V311;
public byte[] Serialize(MqttBasePacket packet)
{
if (packet == null) throw new ArgumentNullException(nameof(packet));
using (var stream = new MemoryStream())
using (var writer = new MqttPacketWriter(stream))
{
var fixedHeader = SerializePacket(packet, writer);
var headerBuffer = new List<byte> { fixedHeader };
MqttPacketWriter.WriteRemainingLength((int)stream.Length, headerBuffer);
var header = headerBuffer.ToArray();
var body = stream.ToArray();
var buffer = new byte[header.Length + body.Length];
Buffer.BlockCopy(header, 0, buffer, 0, header.Length);
Buffer.BlockCopy(body, 0, buffer, header.Length, body.Length);
return buffer;
}
}
public MqttBasePacket Deserialize(ReceivedMqttPacket receivedMqttPacket)
{
if (receivedMqttPacket == null) throw new ArgumentNullException(nameof(receivedMqttPacket));
using (var reader = new MqttPacketReader(receivedMqttPacket))
{
return Deserialize(receivedMqttPacket.Header, reader);
}
}
private byte SerializePacket(MqttBasePacket packet, MqttPacketWriter writer)
{
switch (packet)
{
case MqttConnectPacket connectPacket: return Serialize(connectPacket, writer);
case MqttConnAckPacket connAckPacket: return Serialize(connAckPacket, writer);
case MqttDisconnectPacket _: return SerializeEmptyPacket(MqttControlPacketType.Disconnect);
case MqttPingReqPacket _: return SerializeEmptyPacket(MqttControlPacketType.PingReq);
case MqttPingRespPacket _: return SerializeEmptyPacket(MqttControlPacketType.PingResp);
case MqttPublishPacket publishPacket: return Serialize(publishPacket, writer);
case MqttPubAckPacket pubAckPacket: return Serialize(pubAckPacket, writer);
case MqttPubRecPacket pubRecPacket: return Serialize(pubRecPacket, writer);
case MqttPubRelPacket pubRelPacket: return Serialize(pubRelPacket, writer);
case MqttPubCompPacket pubCompPacket: return Serialize(pubCompPacket, writer);
case MqttSubscribePacket subscribePacket: return Serialize(subscribePacket, writer);
case MqttSubAckPacket subAckPacket: return Serialize(subAckPacket, writer);
case MqttUnsubscribePacket unsubscribePacket: return Serialize(unsubscribePacket, writer);
case MqttUnsubAckPacket unsubAckPacket: return Serialize(unsubAckPacket, writer);
default: throw new MqttProtocolViolationException("Packet type invalid.");
}
}
private static MqttBasePacket Deserialize(MqttPacketHeader header, MqttPacketReader reader)
{
switch (header.ControlPacketType)
{
case MqttControlPacketType.Connect: return DeserializeConnect(reader);
case MqttControlPacketType.ConnAck: return DeserializeConnAck(reader);
case MqttControlPacketType.Disconnect: return new MqttDisconnectPacket();
case MqttControlPacketType.Publish: return DeserializePublish(reader, header);
case MqttControlPacketType.PubAck: return DeserializePubAck(reader);
case MqttControlPacketType.PubRec: return DeserializePubRec(reader);
case MqttControlPacketType.PubRel: return DeserializePubRel(reader);
case MqttControlPacketType.PubComp: return DeserializePubComp(reader);
case MqttControlPacketType.PingReq: return new MqttPingReqPacket();
case MqttControlPacketType.PingResp: return new MqttPingRespPacket();
case MqttControlPacketType.Subscribe: return DeserializeSubscribe(reader);
case MqttControlPacketType.SubAck: return DeserializeSubAck(reader);
case MqttControlPacketType.Unsubscibe: return DeserializeUnsubscribe(reader);
case MqttControlPacketType.UnsubAck: return DeserializeUnsubAck(reader);
default: throw new MqttProtocolViolationException($"Packet type ({(int)header.ControlPacketType}) not supported.");
}
}
private static MqttBasePacket DeserializeUnsubAck(MqttPacketReader reader)
{
return new MqttUnsubAckPacket
{
PacketIdentifier = reader.ReadUInt16()
};
}
private static MqttBasePacket DeserializePubComp(MqttPacketReader reader)
{
return new MqttPubCompPacket
{
PacketIdentifier = reader.ReadUInt16()
};
}
private static MqttBasePacket DeserializePubRel(MqttPacketReader reader)
{
return new MqttPubRelPacket
{
PacketIdentifier = reader.ReadUInt16()
};
}
private static MqttBasePacket DeserializePubRec(MqttPacketReader reader)
{
return new MqttPubRecPacket
{
PacketIdentifier = reader.ReadUInt16()
};
}
private static MqttBasePacket DeserializePubAck(MqttPacketReader reader)
{
return new MqttPubAckPacket
{
PacketIdentifier = reader.ReadUInt16()
};
}
private static MqttBasePacket DeserializeUnsubscribe(MqttPacketReader reader)
{
var packet = new MqttUnsubscribePacket
{
PacketIdentifier = reader.ReadUInt16(),
};
while (!reader.EndOfRemainingData)
{
packet.TopicFilters.Add(reader.ReadStringWithLengthPrefix());
}
return packet;
}
private static MqttBasePacket DeserializeSubscribe(MqttPacketReader reader)
{
var packet = new MqttSubscribePacket
{
PacketIdentifier = reader.ReadUInt16()
};
while (!reader.EndOfRemainingData)
{
packet.TopicFilters.Add(new TopicFilter(
reader.ReadStringWithLengthPrefix(),
(MqttQualityOfServiceLevel)reader.ReadByte()));
}
return packet;
}
private static MqttBasePacket DeserializePublish(MqttPacketReader reader, MqttPacketHeader mqttPacketHeader)
{
var fixedHeader = new ByteReader(mqttPacketHeader.FixedHeader);
var retain = fixedHeader.Read();
var qualityOfServiceLevel = (MqttQualityOfServiceLevel)fixedHeader.Read(2);
var dup = fixedHeader.Read();
var topic = reader.ReadStringWithLengthPrefix();
ushort packetIdentifier = 0;
if (qualityOfServiceLevel > MqttQualityOfServiceLevel.AtMostOnce)
{
packetIdentifier = reader.ReadUInt16();
}
var packet = new MqttPublishPacket
{
Retain = retain,
QualityOfServiceLevel = qualityOfServiceLevel,
Dup = dup,
Topic = topic,
Payload = reader.ReadRemainingData(),
PacketIdentifier = packetIdentifier
};
return packet;
}
private static MqttBasePacket DeserializeConnect(MqttPacketReader reader)
{
reader.ReadBytes(2); // Skip 2 bytes
MqttProtocolVersion protocolVersion;
var protocolName = reader.ReadBytes(4);
if (protocolName.SequenceEqual(ProtocolVersionV310Name))
{
reader.ReadBytes(2);
protocolVersion = MqttProtocolVersion.V310;
}
else if (protocolName.SequenceEqual(ProtocolVersionV311Name))
{
protocolVersion = MqttProtocolVersion.V311;
}
else
{
throw new MqttProtocolViolationException("Protocol name is not supported.");
}
reader.ReadByte(); // Skip protocol level
var connectFlags = reader.ReadByte();
var connectFlagsReader = new ByteReader(connectFlags);
connectFlagsReader.Read(); // Reserved.
var packet = new MqttConnectPacket
{
ProtocolVersion = protocolVersion,
CleanSession = connectFlagsReader.Read()
};
var willFlag = connectFlagsReader.Read();
var willQoS = connectFlagsReader.Read(2);
var willRetain = connectFlagsReader.Read();
var passwordFlag = connectFlagsReader.Read();
var usernameFlag = connectFlagsReader.Read();
packet.KeepAlivePeriod = reader.ReadUInt16();
packet.ClientId = reader.ReadStringWithLengthPrefix();
if (willFlag)
{
packet.WillMessage = new MqttApplicationMessage
{
Topic = reader.ReadStringWithLengthPrefix(),
Payload = reader.ReadWithLengthPrefix(),
QualityOfServiceLevel = (MqttQualityOfServiceLevel)willQoS,
Retain = willRetain
};
}
if (usernameFlag)
{
packet.Username = reader.ReadStringWithLengthPrefix();
}
if (passwordFlag)
{
packet.Password = reader.ReadStringWithLengthPrefix();
}
ValidateConnectPacket(packet);
return packet;
}
private static MqttBasePacket DeserializeSubAck(MqttPacketReader reader)
{
var packet = new MqttSubAckPacket
{
PacketIdentifier = reader.ReadUInt16()
};
while (!reader.EndOfRemainingData)
{
packet.SubscribeReturnCodes.Add((MqttSubscribeReturnCode)reader.ReadByte());
}
return packet;
}
private static MqttBasePacket DeserializeConnAck(MqttPacketReader reader)
{
var variableHeader1 = reader.ReadByte();
var variableHeader2 = reader.ReadByte();
var packet = new MqttConnAckPacket
{
IsSessionPresent = new ByteReader(variableHeader1).Read(),
ConnectReturnCode = (MqttConnectReturnCode)variableHeader2
};
return packet;
}
private static void ValidateConnectPacket(MqttConnectPacket packet)
{
if (packet == null) throw new ArgumentNullException(nameof(packet));
if (string.IsNullOrEmpty(packet.ClientId) && !packet.CleanSession)
{
throw new MqttProtocolViolationException("CleanSession must be set if ClientId is empty [MQTT-3.1.3-7].");
}
}
private static void ValidatePublishPacket(MqttPublishPacket packet)
{
if (packet == null) throw new ArgumentNullException(nameof(packet));
if (packet.QualityOfServiceLevel == 0 && packet.Dup)
{
throw new MqttProtocolViolationException("Dup flag must be false for QoS 0 packets [MQTT-3.3.1-2].");
}
}
private byte Serialize(MqttConnectPacket packet, MqttPacketWriter writer)
{
ValidateConnectPacket(packet);
// Write variable header
writer.Write(0x00, 0x04); // 3.1.2.1 Protocol Name
if (ProtocolVersion == MqttProtocolVersion.V311)
{
writer.Write(ProtocolVersionV311Name);
writer.Write(0x04); // 3.1.2.2 Protocol Level (4)
}
else
{
writer.Write(ProtocolVersionV310Name);
writer.Write(0x64, 0x70, 0x03); // Protocol Level (0x03)
}
var connectFlags = new ByteWriter(); // 3.1.2.3 Connect Flags
connectFlags.Write(false); // Reserved
connectFlags.Write(packet.CleanSession);
connectFlags.Write(packet.WillMessage != null);
if (packet.WillMessage != null)
{
connectFlags.Write((int)packet.WillMessage.QualityOfServiceLevel, 2);
connectFlags.Write(packet.WillMessage.Retain);
}
else
{
connectFlags.Write(0, 2);
connectFlags.Write(false);
}
connectFlags.Write(packet.Password != null);
connectFlags.Write(packet.Username != null);
writer.Write(connectFlags);
writer.Write(packet.KeepAlivePeriod);
writer.WriteWithLengthPrefix(packet.ClientId);
if (packet.WillMessage != null)
{
writer.WriteWithLengthPrefix(packet.WillMessage.Topic);
writer.WriteWithLengthPrefix(packet.WillMessage.Payload);
}
if (packet.Username != null)
{
writer.WriteWithLengthPrefix(packet.Username);
}
if (packet.Password != null)
{
writer.WriteWithLengthPrefix(packet.Password);
}
return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.Connect);
}
private byte Serialize(MqttConnAckPacket packet, MqttPacketWriter writer)
{
var connectAcknowledgeFlags = new ByteWriter();
if (ProtocolVersion == MqttProtocolVersion.V311)
{
connectAcknowledgeFlags.Write(packet.IsSessionPresent);
}
writer.Write(connectAcknowledgeFlags);
writer.Write((byte)packet.ConnectReturnCode);
return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.ConnAck);
}
private static byte Serialize(MqttPubRelPacket packet, MqttPacketWriter writer)
{
writer.Write(packet.PacketIdentifier);
return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.PubRel, 0x02);
}
private static byte Serialize(MqttPublishPacket packet, MqttPacketWriter writer)
{
ValidatePublishPacket(packet);
writer.WriteWithLengthPrefix(packet.Topic);
if (packet.QualityOfServiceLevel > MqttQualityOfServiceLevel.AtMostOnce)
{
writer.Write(packet.PacketIdentifier);
}
else
{
if (packet.PacketIdentifier > 0)
{
throw new MqttProtocolViolationException("Packet identifier must be empty if QoS == 0 [MQTT-2.3.1-5].");
}
}
if (packet.Payload?.Length > 0)
{
writer.Write(packet.Payload);
}
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 Serialize(MqttPubAckPacket packet, MqttPacketWriter writer)
{
writer.Write(packet.PacketIdentifier);
return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.PubAck);
}
private static byte Serialize(MqttPubRecPacket packet, MqttPacketWriter writer)
{
writer.Write(packet.PacketIdentifier);
return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.PubRec);
}
private static byte Serialize(MqttPubCompPacket packet, MqttPacketWriter writer)
{
writer.Write(packet.PacketIdentifier);
return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.PubComp);
}
private static byte Serialize(MqttSubscribePacket packet, MqttPacketWriter writer)
{
if (!packet.TopicFilters.Any()) throw new MqttProtocolViolationException("At least one topic filter must be set [MQTT-3.8.3-3].");
writer.Write(packet.PacketIdentifier);
if (packet.TopicFilters?.Count > 0)
{
foreach (var topicFilter in packet.TopicFilters)
{
writer.WriteWithLengthPrefix(topicFilter.Topic);
writer.Write((byte)topicFilter.QualityOfServiceLevel);
}
}
return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.Subscribe, 0x02);
}
private static byte Serialize(MqttSubAckPacket packet, MqttPacketWriter writer)
{
writer.Write(packet.PacketIdentifier);
if (packet.SubscribeReturnCodes?.Any() == true)
{
foreach (var packetSubscribeReturnCode in packet.SubscribeReturnCodes)
{
writer.Write((byte)packetSubscribeReturnCode);
}
}
return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.SubAck);
}
private static byte Serialize(MqttUnsubscribePacket packet, MqttPacketWriter writer)
{
if (!packet.TopicFilters.Any()) throw new MqttProtocolViolationException("At least one topic filter must be set [MQTT-3.10.3-2].");
writer.Write(packet.PacketIdentifier);
if (packet.TopicFilters?.Any() == true)
{
foreach (var topicFilter in packet.TopicFilters)
{
writer.WriteWithLengthPrefix(topicFilter);
}
}
return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.Unsubscibe, 0x02);
}
private static byte Serialize(IMqttPacketWithIdentifier packet, BinaryWriter writer)
{
writer.Write(packet.PacketIdentifier);
return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.UnsubAck);
}
private static byte SerializeEmptyPacket(MqttControlPacketType type)
{
return MqttPacketWriter.BuildFixedHeader(type);
}
}
}
@@ -0,0 +1,82 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using MQTTnet.Protocol;
namespace MQTTnet.Serializer
{
public sealed class MqttPacketWriter : BinaryWriter
{
public MqttPacketWriter(Stream stream)
: base(stream, Encoding.UTF8, true)
{
}
public static byte BuildFixedHeader(MqttControlPacketType packetType, byte flags = 0)
{
var fixedHeader = (int)packetType << 4;
fixedHeader |= flags;
return (byte)fixedHeader;
}
public override void Write(ushort value)
{
var buffer = BitConverter.GetBytes(value);
Write(buffer[1], buffer[0]);
}
public new void Write(params byte[] values)
{
base.Write(values);
}
public new void Write(byte value)
{
base.Write(value);
}
public void Write(ByteWriter value)
{
if (value == null) throw new ArgumentNullException(nameof(value));
Write(value.Value);
}
public void WriteWithLengthPrefix(string value)
{
WriteWithLengthPrefix(Encoding.UTF8.GetBytes(value ?? string.Empty));
}
public void WriteWithLengthPrefix(byte[] value)
{
var length = (ushort)value.Length;
Write(length);
Write(value);
}
public static void WriteRemainingLength(int length, List<byte> target)
{
if (length == 0)
{
target.Add(0);
return;
}
// Alorithm taken from http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html.
var x = length;
do
{
var encodedByte = x % 128;
x = x / 128;
if (x > 0)
{
encodedByte = encodedByte | 128;
}
target.Add((byte)encodedByte);
} while (x > 0);
}
}
}
@@ -0,0 +1,8 @@
namespace MQTTnet.Serializer
{
public enum MqttProtocolVersion
{
V311,
V310
}
}

Some files were not shown because too many files have changed in this diff Show More