Add server implementation

This commit is contained in:
Christian Kratky
2017-03-26 21:48:50 +02:00
parent 18538ebbb9
commit ee25228c0b
110 changed files with 2164 additions and 496 deletions
@@ -0,0 +1,18 @@
using System;
using System.Threading.Tasks;
using MQTTnet.Core.Client;
using MQTTnet.Core.Packets;
namespace MQTTnet.Core.Adapter
{
public interface IMqttCommunicationAdapter
{
Task ConnectAsync(MqttClientOptions options, TimeSpan timeout);
Task DisconnectAsync();
Task SendPacketAsync(MqttBasePacket packet, TimeSpan timeout);
Task<MqttBasePacket> ReceivePacketAsync(TimeSpan timeout);
}
}
@@ -0,0 +1,14 @@
using System;
using MQTTnet.Core.Server;
namespace MQTTnet.Core.Adapter
{
public interface IMqttServerAdapter
{
event EventHandler<MqttClientConnectedEventArgs> ClientConnected;
void Start(MqttServerOptions options);
void Stop();
}
}
@@ -0,0 +1,88 @@
using System;
using System.Threading.Tasks;
using MQTTnet.Core.Channel;
using MQTTnet.Core.Client;
using MQTTnet.Core.Diagnostics;
using MQTTnet.Core.Exceptions;
using MQTTnet.Core.Packets;
using MQTTnet.Core.Serializer;
namespace MQTTnet.Core.Adapter
{
public class MqttChannelCommunicationAdapter : IMqttCommunicationAdapter
{
private readonly IMqttPacketSerializer _serializer;
private readonly IMqttCommunicationChannel _channel;
public MqttChannelCommunicationAdapter(IMqttCommunicationChannel channel, IMqttPacketSerializer serializer)
{
_channel = channel ?? throw new ArgumentNullException(nameof(channel));
_serializer = serializer ?? throw new ArgumentNullException(nameof(serializer));
}
public async Task ConnectAsync(MqttClientOptions options, TimeSpan timeout)
{
var task = _channel.ConnectAsync(options);
if (await Task.WhenAny(Task.Delay(timeout), task) != task)
{
throw new MqttCommunicationTimedOutException();
}
}
public async Task DisconnectAsync()
{
await _channel.DisconnectAsync();
}
public async Task SendPacketAsync(MqttBasePacket packet, TimeSpan timeout)
{
MqttTrace.Information(nameof(MqttChannelCommunicationAdapter), $"TX >>> {packet} [Timeout={timeout}]");
bool hasTimeout;
try
{
var task = _serializer.SerializeAsync(packet, _channel);
hasTimeout = await Task.WhenAny(Task.Delay(timeout), task) != task;
}
catch (Exception exception)
{
throw new MqttCommunicationException(exception);
}
if (hasTimeout)
{
throw new MqttCommunicationTimedOutException();
}
}
public async Task<MqttBasePacket> ReceivePacketAsync(TimeSpan timeout)
{
MqttBasePacket packet;
if (timeout > TimeSpan.Zero)
{
var workerTask = _serializer.DeserializeAsync(_channel);
var timeoutTask = Task.Delay(timeout);
var hasTimeout = Task.WhenAny(timeoutTask, workerTask) == timeoutTask;
if (hasTimeout)
{
throw new MqttCommunicationTimedOutException();
}
packet = workerTask.Result;
}
else
{
packet = await _serializer.DeserializeAsync(_channel);
}
if (packet == null)
{
throw new MqttProtocolViolationException("Received malformed packet.");
}
MqttTrace.Information(nameof(MqttChannelCommunicationAdapter), $"RX <<< {packet}");
return packet;
}
}
}
@@ -0,0 +1,17 @@
using System;
namespace MQTTnet.Core.Adapter
{
public class MqttClientConnectedEventArgs : EventArgs
{
public MqttClientConnectedEventArgs(string identifier, IMqttCommunicationAdapter clientAdapter)
{
Identifier = identifier ?? throw new ArgumentNullException(nameof(identifier));
ClientAdapter = clientAdapter ?? throw new ArgumentNullException(nameof(clientAdapter));
}
public string Identifier { get; }
public IMqttCommunicationAdapter ClientAdapter { get; }
}
}
@@ -0,0 +1,16 @@
using MQTTnet.Core.Exceptions;
using MQTTnet.Core.Protocol;
namespace MQTTnet.Core.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,16 @@
using System.Threading.Tasks;
using MQTTnet.Core.Client;
namespace MQTTnet.Core.Channel
{
public interface IMqttCommunicationChannel
{
Task ConnectAsync(MqttClientOptions options);
Task DisconnectAsync();
Task WriteAsync(byte[] buffer);
Task ReadAsync(byte[] buffer);
}
}
+374
View File
@@ -0,0 +1,374 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Core.Adapter;
using MQTTnet.Core.Diagnostics;
using MQTTnet.Core.Exceptions;
using MQTTnet.Core.Internal;
using MQTTnet.Core.Packets;
using MQTTnet.Core.Protocol;
namespace MQTTnet.Core.Client
{
public class MqttClient
{
private readonly ConcurrentDictionary<ushort, MqttPublishPacket> _pendingExactlyOncePublishPackets = new ConcurrentDictionary<ushort, MqttPublishPacket>();
private readonly HashSet<ushort> _processedPublishPackets = new HashSet<ushort>();
private readonly MqttPacketDispatcher _packetDispatcher = new MqttPacketDispatcher();
private readonly MqttClientOptions _options;
private readonly IMqttCommunicationAdapter _adapter;
private int _latestPacketIdentifier;
private CancellationTokenSource _cancellationTokenSource;
public MqttClient(MqttClientOptions options, IMqttCommunicationAdapter adapter)
{
_options = options ?? throw new ArgumentNullException(nameof(options));
_adapter = adapter ?? throw new ArgumentNullException(nameof(adapter));
}
public event EventHandler Connected;
public event EventHandler Disconnected;
public event EventHandler<MqttApplicationMessageReceivedEventArgs> ApplicationMessageReceived;
public bool IsConnected { get; private set; }
public async Task ConnectAsync(MqttApplicationMessage willApplicationMessage = null)
{
MqttTrace.Verbose(nameof(MqttClient), "Trying to connect.");
if (IsConnected)
{
throw new MqttProtocolViolationException("It is not allowed to connect with a server after the connection is established.");
}
var connectPacket = new MqttConnectPacket
{
ClientId = _options.ClientId,
Username = _options.UserName,
Password = _options.Password,
KeepAlivePeriod = (ushort)_options.KeepAlivePeriod.TotalSeconds,
WillMessage = willApplicationMessage
};
await _adapter.ConnectAsync(_options, _options.DefaultCommunicationTimeout);
MqttTrace.Verbose(nameof(MqttClient), "Connection with server established.");
_cancellationTokenSource = new CancellationTokenSource();
_latestPacketIdentifier = 0;
_processedPublishPackets.Clear();
_packetDispatcher.Reset();
IsConnected = true;
Task.Run(async () => await ReceivePackets(_cancellationTokenSource.Token), _cancellationTokenSource.Token).Forget();
var response = await SendAndReceiveAsync<MqttConnAckPacket>(connectPacket);
if (response.ConnectReturnCode != MqttConnectReturnCode.ConnectionAccepted)
{
throw new MqttConnectingFailedException(response.ConnectReturnCode);
}
if (_options.KeepAlivePeriod != TimeSpan.Zero)
{
Task.Run(async () => await SendKeepAliveMessagesAsync(_cancellationTokenSource.Token), _cancellationTokenSource.Token).Forget();
}
Connected?.Invoke(this, EventArgs.Empty);
}
public async Task DisconnectAsync()
{
await SendAsync(new MqttDisconnectPacket());
await DisconnectInternalAsync();
}
public async Task<IList<MqttSubscribeResult>> SubscribeAsync(params TopicFilter[] topicFilters)
{
if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters));
return await SubscribeAsync(topicFilters.ToList());
}
public async Task<IList<MqttSubscribeResult>> SubscribeAsync(IList<TopicFilter> topicFilters)
{
if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters));
if (!topicFilters.Any()) throw new MqttProtocolViolationException("At least one topic filter must be set [MQTT-3.8.3-3].");
ThrowIfNotConnected();
var subscribePacket = new MqttSubscribePacket
{
PacketIdentifier = GetNewPacketIdentifier(),
TopicFilters = topicFilters
};
var response = await SendAndReceiveAsync<MqttSubAckPacket>(subscribePacket);
if (response.SubscribeReturnCodes.Count != topicFilters.Count)
{
throw new MqttProtocolViolationException("The return codes are not matching the topic filters [MQTT-3.9.3-1].");
}
var result = new List<MqttSubscribeResult>();
for (var i = 0; i < topicFilters.Count; i++)
{
result.Add(new MqttSubscribeResult(topicFilters[i], response.SubscribeReturnCodes[i]));
}
return result;
}
public async Task Unsubscribe(params string[] topicFilters)
{
if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters));
await Unsubscribe(topicFilters.ToList());
}
public async Task Unsubscribe(IList<string> topicFilters)
{
if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters));
if (!topicFilters.Any()) throw new MqttProtocolViolationException("At least one topic filter must be set [MQTT-3.10.3-2].");
ThrowIfNotConnected();
var unsubscribePacket = new MqttUnsubscribePacket
{
PacketIdentifier = GetNewPacketIdentifier(),
TopicFilters = topicFilters
};
await SendAndReceiveAsync<MqttUnsubAckPacket>(unsubscribePacket);
}
public async Task PublishAsync(MqttApplicationMessage applicationMessage)
{
if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage));
ThrowIfNotConnected();
var publishPacket = applicationMessage.ToPublishPacket();
if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtMostOnce)
{
await SendAsync(publishPacket);
}
else if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtLeastOnce)
{
publishPacket.PacketIdentifier = GetNewPacketIdentifier();
await SendAndReceiveAsync<MqttPubAckPacket>(publishPacket);
}
else if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.ExactlyOnce)
{
publishPacket.PacketIdentifier = GetNewPacketIdentifier();
await SendAndReceiveAsync<MqttPubRecPacket>(publishPacket);
await SendAsync(publishPacket.CreateResponse<MqttPubCompPacket>());
}
}
private void ThrowIfNotConnected()
{
if (!IsConnected) throw new MqttCommunicationException("The client is not connected.");
}
private async Task DisconnectInternalAsync()
{
try
{
await _adapter.DisconnectAsync();
}
catch
{
}
finally
{
_cancellationTokenSource?.Cancel();
IsConnected = false;
Disconnected?.Invoke(this, EventArgs.Empty);
}
}
private async void ProcessReceivedPacket(MqttBasePacket mqttPacket)
{
try
{
if (mqttPacket is MqttPingReqPacket)
{
await SendAsync(new MqttPingRespPacket());
return;
}
if (mqttPacket is MqttDisconnectPacket)
{
await DisconnectAsync();
return;
}
var publishPacket = mqttPacket as MqttPublishPacket;
if (publishPacket != null)
{
await ProcessReceivedPublishPacket(publishPacket);
return;
}
var pubRelPacket = mqttPacket as MqttPubRelPacket;
if (pubRelPacket != null)
{
await ProcessReceivedPubRelPacket(pubRelPacket);
return;
}
_packetDispatcher.Dispatch(mqttPacket);
}
catch (Exception exception)
{
MqttTrace.Error(nameof(MqttClient), exception, "Error while processing received packet.");
}
}
private void FireApplicationMessageReceivedEvent(MqttPublishPacket publishPacket)
{
if (publishPacket.QualityOfServiceLevel != MqttQualityOfServiceLevel.AtMostOnce)
{
_processedPublishPackets.Add(publishPacket.PacketIdentifier);
}
var applicationMessage = new MqttApplicationMessage(
publishPacket.Topic,
publishPacket.Payload,
publishPacket.QualityOfServiceLevel,
publishPacket.Retain
);
ApplicationMessageReceived?.Invoke(this, new MqttApplicationMessageReceivedEventArgs(applicationMessage));
}
private async Task ProcessReceivedPublishPacket(MqttPublishPacket publishPacket)
{
if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtMostOnce)
{
FireApplicationMessageReceivedEvent(publishPacket);
}
else
{
if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtLeastOnce)
{
FireApplicationMessageReceivedEvent(publishPacket);
await SendAsync(new MqttPubAckPacket { PacketIdentifier = publishPacket.PacketIdentifier });
}
else if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.ExactlyOnce)
{
_pendingExactlyOncePublishPackets[publishPacket.PacketIdentifier] = publishPacket;
await SendAsync(new MqttPubRecPacket { PacketIdentifier = publishPacket.PacketIdentifier });
}
}
}
private async Task ProcessReceivedPubRelPacket(MqttPubRelPacket pubRelPacket)
{
MqttPublishPacket originalPublishPacket;
if (!_pendingExactlyOncePublishPackets.TryRemove(pubRelPacket.PacketIdentifier, out originalPublishPacket))
{
throw new MqttCommunicationException();
}
await SendAsync(originalPublishPacket.CreateResponse<MqttPubCompPacket>());
FireApplicationMessageReceivedEvent(originalPublishPacket);
}
private async Task SendAsync(MqttBasePacket packet)
{
await _adapter.SendPacketAsync(packet, _options.DefaultCommunicationTimeout);
}
private async Task<TResponsePacket> SendAndReceiveAsync<TResponsePacket>(MqttBasePacket requestPacket) where TResponsePacket : MqttBasePacket
{
Func<MqttBasePacket, bool> responsePacketSelector = p =>
{
var p1 = p as TResponsePacket;
if (p1 == null)
{
return false;
}
var pi1 = requestPacket as IPacketWithIdentifier;
var pi2 = p as IPacketWithIdentifier;
if (pi1 != null && pi2 != null)
{
if (pi1.PacketIdentifier != pi2.PacketIdentifier)
{
return false;
}
}
return true;
};
await _adapter.SendPacketAsync(requestPacket, _options.DefaultCommunicationTimeout);
return (TResponsePacket)await _packetDispatcher.WaitForPacketAsync(responsePacketSelector, _options.DefaultCommunicationTimeout);
}
private ushort GetNewPacketIdentifier()
{
return (ushort)Interlocked.Increment(ref _latestPacketIdentifier);
}
private async Task SendKeepAliveMessagesAsync(CancellationToken cancellationToken)
{
MqttTrace.Information(nameof(MqttClient), "Start sending keep alive packets.");
try
{
while (!cancellationToken.IsCancellationRequested)
{
await Task.Delay(_options.KeepAlivePeriod, cancellationToken);
await SendAndReceiveAsync<MqttPingRespPacket>(new MqttPingReqPacket());
}
}
catch (MqttCommunicationException)
{
}
catch (Exception exception)
{
MqttTrace.Warning(nameof(MqttClient), exception, "Error while sending/receiving keep alive packets.");
}
finally
{
MqttTrace.Information(nameof(MqttClient), "Stopped sending keep alive packets.");
await DisconnectInternalAsync();
}
}
private async Task ReceivePackets(CancellationToken cancellationToken)
{
MqttTrace.Information(nameof(MqttClient), "Start receiving packets.");
try
{
while (!cancellationToken.IsCancellationRequested)
{
var mqttPacket = await _adapter.ReceivePacketAsync(TimeSpan.Zero);
MqttTrace.Information(nameof(MqttClient), $"Received <<< {mqttPacket}");
Task.Run(() => ProcessReceivedPacket(mqttPacket), cancellationToken).Forget();
}
}
catch (MqttCommunicationException)
{
}
catch (Exception exception)
{
MqttTrace.Error(nameof(MqttClient), exception, "Error while receiving packets.");
}
finally
{
MqttTrace.Information(nameof(MqttClient), "Stopped receiving packets.");
await DisconnectInternalAsync();
}
}
}
}
+21
View File
@@ -0,0 +1,21 @@
using System;
namespace MQTTnet.Core.Client
{
public class MqttClientOptions
{
public string Server { get; set; }
public int Port { get; set; } = 1883;
public string UserName { get; set; }
public string Password { get; set; }
public string ClientId { get; set; } = Guid.NewGuid().ToString().Replace("-", string.Empty);
public TimeSpan KeepAlivePeriod { get; set; } = TimeSpan.FromSeconds(5);
public TimeSpan DefaultCommunicationTimeout { get; set; } = TimeSpan.FromSeconds(10);
}
}
+16
View File
@@ -0,0 +1,16 @@
using System;
using System.Threading.Tasks;
using MQTTnet.Core.Packets;
namespace MQTTnet.Core.Client
{
public class MqttPacketAwaiter : TaskCompletionSource<MqttBasePacket>
{
public MqttPacketAwaiter(Func<MqttBasePacket, bool> packetSelector)
{
PacketSelector = packetSelector ?? throw new ArgumentNullException(nameof(packetSelector));
}
public Func<MqttBasePacket, bool> PacketSelector { get; }
}
}
+112
View File
@@ -0,0 +1,112 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using MQTTnet.Core.Diagnostics;
using MQTTnet.Core.Exceptions;
using MQTTnet.Core.Packets;
namespace MQTTnet.Core.Client
{
public class MqttPacketDispatcher
{
private readonly object _syncRoot = new object();
private readonly List<MqttBasePacket> _receivedPackets = new List<MqttBasePacket>();
private readonly List<MqttPacketAwaiter> _packetAwaiters = new List<MqttPacketAwaiter>();
public async Task<MqttBasePacket> WaitForPacketAsync(Func<MqttBasePacket, bool> selector, TimeSpan timeout)
{
if (selector == null) throw new ArgumentNullException(nameof(selector));
var packetAwaiter = AddPacketAwaiter(selector);
DispatchPendingPackets();
var hasTimeout = await Task.WhenAny(Task.Delay(timeout), packetAwaiter.Task) != packetAwaiter.Task;
RemovePacketAwaiter(packetAwaiter);
if (hasTimeout)
{
MqttTrace.Warning(nameof(MqttPacketDispatcher), "Timeout while waiting for packet.");
throw new MqttCommunicationTimedOutException();
}
return packetAwaiter.Task.Result;
}
public void Dispatch(MqttBasePacket packet)
{
if (packet == null) throw new ArgumentNullException(nameof(packet));
var packetDispatched = false;
foreach (var packetAwaiter in GetPacketAwaiters())
{
if (packetAwaiter.PacketSelector(packet))
{
packetAwaiter.SetResult(packet);
packetDispatched = true;
break;
}
}
lock (_syncRoot)
{
if (!packetDispatched)
{
_receivedPackets.Add(packet);
}
else
{
_receivedPackets.Remove(packet);
}
}
}
public void Reset()
{
lock (_syncRoot)
{
_packetAwaiters.Clear();
_receivedPackets.Clear();
}
}
private List<MqttPacketAwaiter> GetPacketAwaiters()
{
lock (_packetAwaiters)
{
return new List<MqttPacketAwaiter>(_packetAwaiters);
}
}
private MqttPacketAwaiter AddPacketAwaiter(Func<MqttBasePacket, bool> selector)
{
lock (_syncRoot)
{
var packetAwaiter = new MqttPacketAwaiter(selector);
_packetAwaiters.Add(packetAwaiter);
return packetAwaiter;
}
}
private void RemovePacketAwaiter(MqttPacketAwaiter packetAwaiter)
{
lock (_syncRoot)
{
_packetAwaiters.Remove(packetAwaiter);
}
}
private void DispatchPendingPackets()
{
List<MqttBasePacket> receivedPackets;
lock (_syncRoot)
{
receivedPackets = new List<MqttBasePacket>(_receivedPackets);
}
foreach (var pendingPacket in receivedPackets)
{
Dispatch(pendingPacket);
}
}
}
}
@@ -0,0 +1,18 @@
using MQTTnet.Core.Packets;
using MQTTnet.Core.Protocol;
namespace MQTTnet.Core.Client
{
public class MqttSubscribeResult
{
public MqttSubscribeResult(TopicFilter topicFilter, MqttSubscribeReturnCode returnCode)
{
TopicFilter = topicFilter;
ReturnCode = returnCode;
}
public TopicFilter TopicFilter { get; }
public MqttSubscribeReturnCode ReturnCode { get; }
}
}
+44
View File
@@ -0,0 +1,44 @@
using System;
namespace MQTTnet.Core.Diagnostics
{
public static class MqttTrace
{
public static event EventHandler<MqttTraceMessagePublishedEventArgs> TraceMessagePublished;
public static void Verbose(string source, string message)
{
Publish(source, MqttTraceLevel.Verbose, null, message);
}
public static void Information(string source, string message)
{
Publish(source, MqttTraceLevel.Information, null, message);
}
public static void Warning(string source, string message)
{
Publish(source, MqttTraceLevel.Warning, null, message);
}
public static void Warning(string source, Exception exception, string message)
{
Publish(source, MqttTraceLevel.Warning, exception, message);
}
public static void Error(string source, string message)
{
Publish(source, MqttTraceLevel.Error, null, message);
}
public static void Error(string source, Exception exception, string message)
{
Publish(source, MqttTraceLevel.Error, exception, message);
}
private static void Publish(string source, MqttTraceLevel traceLevel, Exception exception, string message)
{
TraceMessagePublished?.Invoke(null, new MqttTraceMessagePublishedEventArgs(Environment.CurrentManagedThreadId, source, traceLevel, message, exception));
}
}
}
@@ -0,0 +1,10 @@
namespace MQTTnet.Core.Diagnostics
{
public enum MqttTraceLevel
{
Verbose,
Information,
Warning,
Error
}
}
@@ -0,0 +1,26 @@
using System;
namespace MQTTnet.Core.Diagnostics
{
public class MqttTraceMessagePublishedEventArgs : EventArgs
{
public MqttTraceMessagePublishedEventArgs(int threadId, string source, MqttTraceLevel level, string message, Exception exception)
{
ThreadId = threadId;
Source = source;
Level = level;
Message = message;
Exception = exception;
}
public int ThreadId { get; }
public string Source { get; }
public MqttTraceLevel Level { get; }
public string Message { get; }
public Exception Exception { get; }
}
}
@@ -0,0 +1,21 @@
using System;
namespace MQTTnet.Core.Exceptions
{
public class MqttCommunicationException : Exception
{
public MqttCommunicationException()
{
}
public MqttCommunicationException(Exception innerException)
: base(innerException.Message, innerException)
{
}
public MqttCommunicationException(string message)
: base(message)
{
}
}
}
@@ -0,0 +1,6 @@
namespace MQTTnet.Core.Exceptions
{
public class MqttCommunicationTimedOutException : MqttCommunicationException
{
}
}
@@ -0,0 +1,12 @@
using System;
namespace MQTTnet.Core.Exceptions
{
public class MqttProtocolViolationException : Exception
{
public MqttProtocolViolationException(string message)
: base(message)
{
}
}
}
@@ -0,0 +1,24 @@
using MQTTnet.Core.Packets;
namespace MQTTnet.Core.Internal
{
internal static class MqttApplicationMessageExtensions
{
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
};
}
}
}
+11
View File
@@ -0,0 +1,11 @@
using System.Threading.Tasks;
namespace MQTTnet.Core.Internal
{
internal static class TaskExtensions
{
public static void Forget(this Task task)
{
}
}
}
+107
View File
@@ -0,0 +1,107 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<MinimumVisualStudioVersion>10.0</MinimumVisualStudioVersion>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{99C884F3-B4B9-417D-AA92-DC7DD1C4CFEE}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>MQTTnet.Core</RootNamespace>
<AssemblyName>MQTTnet.Core</AssemblyName>
<DefaultLanguage>en-US</DefaultLanguage>
<FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<TargetFrameworkProfile>Profile111</TargetFrameworkProfile>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<!-- A reference to the entire .NET Framework is automatically included -->
</ItemGroup>
<ItemGroup>
<Compile Include="Adapter\IMqttCommunicationAdapter.cs" />
<Compile Include="Adapter\IMqttServerAdapter.cs" />
<Compile Include="Adapter\MqttClientConnectedEventArgs.cs" />
<Compile Include="Adapter\MqttConnectingFailedException.cs" />
<Compile Include="Adapter\MqttChannelCommunicationAdapter.cs" />
<Compile Include="Channel\IMqttCommunicationChannel.cs" />
<Compile Include="Client\MqttPacketDispatcher.cs" />
<Compile Include="Client\MqttSubscribeResult.cs" />
<Compile Include="Client\MqttPacketAwaiter.cs" />
<Compile Include="Diagnostics\MqttTrace.cs" />
<Compile Include="Diagnostics\MqttTraceLevel.cs" />
<Compile Include="Diagnostics\MqttTraceMessagePublishedEventArgs.cs" />
<Compile Include="Exceptions\MqttCommunicationException.cs" />
<Compile Include="Exceptions\MqttCommunicationTimedOutException.cs" />
<Compile Include="Exceptions\MqttProtocolViolationException.cs" />
<Compile Include="Internal\MqttApplicationMessageExtensions.cs" />
<Compile Include="MqttApplicationMessageReceivedEventArgs.cs" />
<Compile Include="Packets\IPacketWithPacketIdentifier.cs" />
<Compile Include="Packets\MqttConnAckPacket.cs" />
<Compile Include="Packets\MqttBasePacket.cs" />
<Compile Include="Packets\MqttConnectPacket.cs" />
<Compile Include="Packets\MqttDisconnectPacket.cs" />
<Compile Include="MqttApplicationMessage.cs" />
<Compile Include="Client\MqttClient.cs" />
<Compile Include="Client\MqttClientOptions.cs" />
<Compile Include="Packets\MqttPingReqPacket.cs" />
<Compile Include="Packets\MqttPubCompPacket.cs" />
<Compile Include="Packets\MqttBasePublishPacket.cs" />
<Compile Include="Packets\MqttUnsubAckPacket.cs" />
<Compile Include="Packets\MqttSubAckPacket.cs" />
<Compile Include="Packets\MqttPubRelPacket.cs" />
<Compile Include="Packets\MqttPingRespPacket.cs" />
<Compile Include="Packets\MqttPubAckPacket.cs" />
<Compile Include="Packets\MqttPublishPacket.cs" />
<Compile Include="Packets\MqttPubRecPacket.cs" />
<Compile Include="Packets\MqttUnsubscribe.cs" />
<Compile Include="Packets\MqttSubscribePacket.cs" />
<Compile Include="Packets\TopicFilter.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Protocol\MqttConnectReturnCode.cs" />
<Compile Include="Protocol\MqttControlPacketType.cs" />
<Compile Include="Protocol\MqttQualityOfServiceLevel.cs" />
<Compile Include="Protocol\MqttSubscribeReturnCode.cs" />
<Compile Include="Serializer\ByteReader.cs" />
<Compile Include="Serializer\ByteWriter.cs" />
<Compile Include="Serializer\DefaultMqttV311PacketSerializer.cs" />
<Compile Include="Serializer\IMqttPacketSerializer.cs" />
<Compile Include="Serializer\MqttPacketReader.cs" />
<Compile Include="Serializer\MqttPacketWriter.cs" />
<Compile Include="Server\MqttClientSession.cs" />
<Compile Include="Server\MqttClientSessionManager.cs" />
<Compile Include="Server\MqttOutgoingPublicationsManager.cs" />
<Compile Include="Server\MqttServer.cs" />
<Compile Include="Server\MqttServerOptions.cs" />
<Compile Include="Server\MqttClientPublishPacketContext.cs" />
<Compile Include="Server\MqttTopicFilterComparer.cs" />
<Compile Include="Server\MqttClientSubscriptionsManager.cs" />
<Compile Include="Internal\TaskExtensions.cs" />
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\Portable\$(TargetFrameworkVersion)\Microsoft.Portable.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>
+24
View File
@@ -0,0 +1,24 @@
using System;
using MQTTnet.Core.Protocol;
namespace MQTTnet.Core
{
public class MqttApplicationMessage
{
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; }
public byte[] Payload { get; }
public MqttQualityOfServiceLevel QualityOfServiceLevel { get; }
public bool Retain { get; }
}
}
@@ -0,0 +1,14 @@
using System;
namespace MQTTnet.Core
{
public class MqttApplicationMessageReceivedEventArgs : EventArgs
{
public MqttApplicationMessageReceivedEventArgs(MqttApplicationMessage applicationMessage)
{
ApplicationMessage = applicationMessage ?? throw new ArgumentNullException(nameof(applicationMessage));
}
public MqttApplicationMessage ApplicationMessage { get; }
}
}
@@ -0,0 +1,7 @@
namespace MQTTnet.Core.Packets
{
public interface IPacketWithIdentifier
{
ushort PacketIdentifier { get; set; }
}
}
+25
View File
@@ -0,0 +1,25 @@
using System;
namespace MQTTnet.Core.Packets
{
public abstract class MqttBasePacket
{
public TResponsePacket CreateResponse<TResponsePacket>()
{
var responsePacket = Activator.CreateInstance<TResponsePacket>();
var responsePacketWithIdentifier = responsePacket as IPacketWithIdentifier;
if (responsePacketWithIdentifier != null)
{
var requestPacketWithIdentifier = this as IPacketWithIdentifier;
if (requestPacketWithIdentifier == null)
{
throw new InvalidOperationException("Response packet has PacketIdentifier but request packet does not.");
}
responsePacketWithIdentifier.PacketIdentifier = requestPacketWithIdentifier.PacketIdentifier;
}
return responsePacket;
}
}
}
@@ -0,0 +1,7 @@
namespace MQTTnet.Core.Packets
{
public class MqttBasePublishPacket : MqttBasePacket, IPacketWithIdentifier
{
public ushort PacketIdentifier { get; set; }
}
}
+16
View File
@@ -0,0 +1,16 @@
using MQTTnet.Core.Protocol;
namespace MQTTnet.Core.Packets
{
public class MqttConnAckPacket : MqttBasePacket
{
public bool IsSessionPresent { get; set; }
public MqttConnectReturnCode ConnectReturnCode { get; set; }
public override string ToString()
{
return $"{nameof(MqttConnAckPacket)}: [ConnectReturnCode={ConnectReturnCode}] [IsSessionPresent={IsSessionPresent}]";
}
}
}
+22
View File
@@ -0,0 +1,22 @@
namespace MQTTnet.Core.Packets
{
public class MqttConnectPacket: MqttBasePacket
{
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 $"{nameof(MqttConnectPacket)} [ClientId={ClientId}] [Username={Username}] [Password={Password}] [KeepAlivePeriod={KeepAlivePeriod}] [CleanSession={CleanSession}]";
}
}
}
@@ -0,0 +1,6 @@
namespace MQTTnet.Core.Packets
{
public class MqttDisconnectPacket : MqttBasePacket
{
}
}
+10
View File
@@ -0,0 +1,10 @@
namespace MQTTnet.Core.Packets
{
public class MqttPingReqPacket : MqttBasePacket
{
public override string ToString()
{
return nameof(MqttPingReqPacket);
}
}
}
@@ -0,0 +1,10 @@
namespace MQTTnet.Core.Packets
{
public class MqttPingRespPacket : MqttBasePacket
{
public override string ToString()
{
return nameof(MqttPingRespPacket);
}
}
}
+6
View File
@@ -0,0 +1,6 @@
namespace MQTTnet.Core.Packets
{
public class MqttPubAckPacket : MqttBasePublishPacket
{
}
}
@@ -0,0 +1,6 @@
namespace MQTTnet.Core.Packets
{
public class MqttPubCompPacket : MqttBasePublishPacket
{
}
}
+6
View File
@@ -0,0 +1,6 @@
namespace MQTTnet.Core.Packets
{
public class MqttPubRecPacket : MqttBasePublishPacket
{
}
}
+6
View File
@@ -0,0 +1,6 @@
namespace MQTTnet.Core.Packets
{
public class MqttPubRelPacket : MqttBasePublishPacket
{
}
}
+24
View File
@@ -0,0 +1,24 @@
using System.Text;
using MQTTnet.Core.Protocol;
namespace MQTTnet.Core.Packets
{
public 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
$"{nameof(MqttPublishPacket)} [Topic={Topic}] [Payload={Encoding.UTF8.GetString(Payload, 0, Payload.Length)}] [QoSLevel={QualityOfServiceLevel}] [Dup={Dup}] [Retain={Retain}] [PacketIdentifier={PacketIdentifier}]";
}
}
}
+20
View File
@@ -0,0 +1,20 @@
using System.Collections.Generic;
using System.Linq;
using MQTTnet.Core.Protocol;
namespace MQTTnet.Core.Packets
{
public class MqttSubAckPacket : MqttBasePacket, IPacketWithIdentifier
{
public ushort PacketIdentifier { get; set; }
public IList<MqttSubscribeReturnCode> SubscribeReturnCodes { get; set; } = new List<MqttSubscribeReturnCode>();
public override string ToString()
{
var subscribeReturnCodesText = string.Join(",", SubscribeReturnCodes.Select(f => f.ToString()));
return
$"{nameof(MqttSubAckPacket)} [PacketIdentifier={PacketIdentifier}] [SubscribeReturnCodes={subscribeReturnCodesText}]";
}
}
}
@@ -0,0 +1,19 @@
using System.Collections.Generic;
using System.Linq;
namespace MQTTnet.Core.Packets
{
public class MqttSubscribePacket : MqttBasePacket, IPacketWithIdentifier
{
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
$"{nameof(MqttSubscribePacket)} [PacketIdentifier={PacketIdentifier}] [TopicFilters={topicFiltersText}]";
}
}
}
@@ -0,0 +1,7 @@
namespace MQTTnet.Core.Packets
{
public class MqttUnsubAckPacket : MqttBasePacket, IPacketWithIdentifier
{
public ushort PacketIdentifier { get; set; }
}
}
+11
View File
@@ -0,0 +1,11 @@
using System.Collections.Generic;
namespace MQTTnet.Core.Packets
{
public class MqttUnsubscribePacket : MqttBasePacket, IPacketWithIdentifier
{
public ushort PacketIdentifier { get; set; }
public IList<string> TopicFilters { get; set; } = new List<string>();
}
}
+17
View File
@@ -0,0 +1,17 @@
using MQTTnet.Core.Protocol;
namespace MQTTnet.Core.Packets
{
public class TopicFilter
{
public TopicFilter(string topic, MqttQualityOfServiceLevel qualityOfServiceLevel)
{
Topic = topic;
QualityOfServiceLevel = qualityOfServiceLevel;
}
public string Topic { get; }
public MqttQualityOfServiceLevel QualityOfServiceLevel { get; }
}
}
+15
View File
@@ -0,0 +1,15 @@
using System.Reflection;
using System.Runtime.InteropServices;
[assembly: AssemblyTitle("MQTTnet")]
[assembly: AssemblyDescription("The core library of MQTTnet")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Christian Kratky")]
[assembly: AssemblyProduct("MQTTnet")]
[assembly: AssemblyCopyright("Copyright © Christian Kratky 2016-2017")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
[assembly: ComVisible(false)]
[assembly: Guid("0e10d0ea-8e4b-4903-ab9e-93a187d07922")]
[assembly: AssemblyVersion("2.1.0.4")]
[assembly: AssemblyFileVersion("2.1.0.4")]
@@ -0,0 +1,12 @@
namespace MQTTnet.Core.Protocol
{
public enum MqttConnectReturnCode
{
ConnectionAccepted = 0x00,
ConnectionRefusedUnacceptableProtocolVersion = 0x01,
ConnectionRefusedIdentifierRejected = 0x02,
ConnectionRefusedServerUnavailable = 0x03,
ConnectionRefusedBadUsernameOrPassword = 0x04,
ConnectionRefusedNotAuthorized = 0x05
}
}
@@ -0,0 +1,20 @@
namespace MQTTnet.Core.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.Core.Protocol
{
public enum MqttQualityOfServiceLevel
{
AtMostOnce = 0x00,
AtLeastOnce = 0x01,
ExactlyOnce = 0x02
}
}
@@ -0,0 +1,10 @@
namespace MQTTnet.Core.Protocol
{
public enum MqttSubscribeReturnCode
{
SuccessMaximumQoS0 = 0x00,
SuccessMaximumQoS1 = 0x01,
SuccessMaximumQoS2 = 0x02,
Failure = 0x80
}
}
+42
View File
@@ -0,0 +1,42 @@
using System;
namespace MQTTnet.Core.Serializer
{
public class ByteReader
{
private int _index;
private readonly int _byte;
public ByteReader(byte @byte)
{
_byte = @byte;
}
public bool Read()
{
if (_index >= 8)
{
throw new InvalidOperationException("End of the byte reached.");
}
var result = ((1 << _index) & _byte) > 0;
_index++;
return result;
}
public byte Read(int count)
{
var result = 0;
for (var i = 0; i < count; i++)
{
if (Read())
{
result |= 1 << i;
}
}
return (byte)result;
}
}
}
+36
View File
@@ -0,0 +1,36 @@
using System;
namespace MQTTnet.Core.Serializer
{
public class ByteWriter
{
private int _index;
private int _byte;
public byte Value => (byte)_byte;
public void Write(byte @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,632 @@
using System;
using System.Linq;
using System.Net;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using MQTTnet.Core.Channel;
using MQTTnet.Core.Exceptions;
using MQTTnet.Core.Packets;
using MQTTnet.Core.Protocol;
namespace MQTTnet.Core.Serializer
{
public class DefaultMqttV311PacketSerializer : IMqttPacketSerializer
{
public async Task SerializeAsync(MqttBasePacket packet, IMqttCommunicationChannel destination)
{
if (packet == null) throw new ArgumentNullException(nameof(packet));
if (destination == null) throw new ArgumentNullException(nameof(destination));
var connectPacket = packet as MqttConnectPacket;
if (connectPacket != null)
{
await SerializeAsync(connectPacket, destination);
return;
}
var connAckPacket = packet as MqttConnAckPacket;
if (connAckPacket != null)
{
await SerializeAsync(connAckPacket, destination);
return;
}
var disconnectPacket = packet as MqttDisconnectPacket;
if (disconnectPacket != null)
{
await SerializeAsync(disconnectPacket, destination);
return;
}
var pingReqPacket = packet as MqttPingReqPacket;
if (pingReqPacket != null)
{
await SerializeAsync(pingReqPacket, destination);
return;
}
var pingRespPacket = packet as MqttPingRespPacket;
if (pingRespPacket != null)
{
await SerializeAsync(pingRespPacket, destination);
return;
}
var publishPacket = packet as MqttPublishPacket;
if (publishPacket != null)
{
await SerializeAsync(publishPacket, destination);
return;
}
var pubAckPacket = packet as MqttPubAckPacket;
if (pubAckPacket != null)
{
await SerializeAsync(pubAckPacket, destination);
return;
}
var pubRecPacket = packet as MqttPubRecPacket;
if (pubRecPacket != null)
{
await SerializeAsync(pubRecPacket, destination);
return;
}
var pubRelPacket = packet as MqttPubRelPacket;
if (pubRelPacket != null)
{
await SerializeAsync(pubRelPacket, destination);
return;
}
var pubCompPacket = packet as MqttPubCompPacket;
if (pubCompPacket != null)
{
await SerializeAsync(pubCompPacket, destination);
return;
}
var subscribePacket = packet as MqttSubscribePacket;
if (subscribePacket != null)
{
await SerializeAsync(subscribePacket, destination);
return;
}
var subAckPacket = packet as MqttSubAckPacket;
if (subAckPacket != null)
{
await SerializeAsync(subAckPacket, destination);
return;
}
var unsubscribePacket = packet as MqttUnsubscribePacket;
if (unsubscribePacket != null)
{
await SerializeAsync(unsubscribePacket, destination);
return;
}
var unsubAckPacket = packet as MqttUnsubAckPacket;
if (unsubAckPacket != null)
{
await SerializeAsync(unsubAckPacket, destination);
return;
}
throw new MqttProtocolViolationException("Packet type invalid.");
}
public async Task<MqttBasePacket> DeserializeAsync(IMqttCommunicationChannel source)
{
if (source == null) throw new ArgumentNullException(nameof(source));
using (var mqttPacketReader = new MqttPacketReader(source))
{
await mqttPacketReader.ReadToEndAsync();
switch (mqttPacketReader.ControlPacketType)
{
case MqttControlPacketType.Connect:
{
return await DeserializeConnectAsync(mqttPacketReader);
}
case MqttControlPacketType.ConnAck:
{
return await DeserializeConnAck(mqttPacketReader);
}
case MqttControlPacketType.Disconnect:
{
return new MqttDisconnectPacket();
}
case MqttControlPacketType.Publish:
{
return await DeserializePublishAsync(mqttPacketReader);
}
case MqttControlPacketType.PubAck:
{
return new MqttPubAckPacket
{
PacketIdentifier = await mqttPacketReader.ReadRemainingDataUShortAsync()
};
}
case MqttControlPacketType.PubRec:
{
return new MqttPubRecPacket
{
PacketIdentifier = await mqttPacketReader.ReadRemainingDataUShortAsync()
};
}
case MqttControlPacketType.PubRel:
{
return new MqttPubRelPacket
{
PacketIdentifier = await mqttPacketReader.ReadRemainingDataUShortAsync()
};
}
case MqttControlPacketType.PubComp:
{
return new MqttPubCompPacket
{
PacketIdentifier = await mqttPacketReader.ReadRemainingDataUShortAsync()
};
}
case MqttControlPacketType.PingReq:
{
return new MqttPingReqPacket();
}
case MqttControlPacketType.PingResp:
{
return new MqttPingRespPacket();
}
case MqttControlPacketType.Subscribe:
{
return await DeserializeSubscribeAsync(mqttPacketReader);
}
case MqttControlPacketType.SubAck:
{
return await DeserializeSubAck(mqttPacketReader);
}
case MqttControlPacketType.Unsubscibe:
{
return await DeserializeUnsubscribeAsync(mqttPacketReader);
}
case MqttControlPacketType.UnsubAck:
{
return new MqttUnsubAckPacket
{
PacketIdentifier = await mqttPacketReader.ReadRemainingDataUShortAsync()
};
}
default:
{
throw new ProtocolViolationException();
}
}
}
}
private async Task<MqttBasePacket> DeserializeUnsubscribeAsync(MqttPacketReader reader)
{
var packet = new MqttUnsubscribePacket
{
PacketIdentifier = await reader.ReadRemainingDataUShortAsync(),
};
while (!reader.EndOfRemainingData)
{
packet.TopicFilters.Add(await reader.ReadRemainingDataStringWithLengthPrefixAsync());
}
return packet;
}
private async Task<MqttBasePacket> DeserializeSubscribeAsync(MqttPacketReader reader)
{
var packet = new MqttSubscribePacket
{
PacketIdentifier = await reader.ReadRemainingDataUShortAsync(),
};
while (!reader.EndOfRemainingData)
{
packet.TopicFilters.Add(new TopicFilter(
await reader.ReadRemainingDataStringWithLengthPrefixAsync(),
(MqttQualityOfServiceLevel)await reader.ReadRemainingDataByteAsync()));
}
return packet;
}
private async Task<MqttBasePacket> DeserializePublishAsync(MqttPacketReader reader)
{
var fixedHeader = new ByteReader(reader.FixedHeader);
var retain = fixedHeader.Read();
var qualityOfServiceLevel = (MqttQualityOfServiceLevel)fixedHeader.Read(2);
var dup = fixedHeader.Read();
var topic = await reader.ReadRemainingDataStringWithLengthPrefixAsync();
ushort packetIdentifier = 0;
if (qualityOfServiceLevel > MqttQualityOfServiceLevel.AtMostOnce)
{
packetIdentifier = await reader.ReadRemainingDataUShortAsync();
}
var packet = new MqttPublishPacket
{
Retain = retain,
QualityOfServiceLevel = qualityOfServiceLevel,
Dup = dup,
Topic = topic,
Payload = await reader.ReadRemainingDataAsync(),
PacketIdentifier = packetIdentifier
};
return packet;
}
private async Task<MqttBasePacket> DeserializeConnectAsync(MqttPacketReader reader)
{
var packet = new MqttConnectPacket();
await reader.ReadRemainingDataByteAsync();
await reader.ReadRemainingDataByteAsync();
var protocolName = await reader.ReadRemainingDataAsync(4);
if (Encoding.UTF8.GetString(protocolName, 0, protocolName.Length) != "MQTT")
{
throw new ProtocolViolationException("Protocol name is not 'MQTT'.");
}
var protocolLevel = await reader.ReadRemainingDataByteAsync();
var connectFlags = await reader.ReadRemainingDataByteAsync();
var connectFlagsReader = new ByteReader(connectFlags);
connectFlagsReader.Read(); // Reserved.
packet.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 = await reader.ReadRemainingDataUShortAsync();
packet.ClientId = await reader.ReadRemainingDataStringWithLengthPrefixAsync();
if (willFlag)
{
packet.WillMessage = new MqttApplicationMessage(
await reader.ReadRemainingDataStringWithLengthPrefixAsync(),
await reader.ReadRemainingDataWithLengthPrefixAsync(),
(MqttQualityOfServiceLevel)willQoS,
willRetain);
}
if (usernameFlag)
{
packet.Username = await reader.ReadRemainingDataStringWithLengthPrefixAsync();
}
if (passwordFlag)
{
packet.Password = await reader.ReadRemainingDataStringWithLengthPrefixAsync();
}
ValidateConnectPacket(packet);
return packet;
}
private async Task<MqttBasePacket> DeserializeSubAck(MqttPacketReader reader)
{
var packet = new MqttSubAckPacket
{
PacketIdentifier = await reader.ReadRemainingDataUShortAsync()
};
while (!reader.EndOfRemainingData)
{
packet.SubscribeReturnCodes.Add((MqttSubscribeReturnCode)await reader.ReadRemainingDataByteAsync());
}
return packet;
}
private async Task<MqttBasePacket> DeserializeConnAck(MqttPacketReader reader)
{
var variableHeader1 = await reader.ReadRemainingDataByteAsync();
var variableHeader2 = await reader.ReadRemainingDataByteAsync();
var packet = new MqttConnAckPacket
{
IsSessionPresent = new ByteReader(variableHeader1).Read(),
ConnectReturnCode = (MqttConnectReturnCode)variableHeader2
};
return packet;
}
private void ValidateConnectPacket(MqttConnectPacket packet)
{
if (string.IsNullOrEmpty(packet.ClientId) && !packet.CleanSession)
{
throw new ProtocolViolationException("CleanSession must be set if ClientId is empty [MQTT-3.1.3-7].");
}
if (!string.IsNullOrEmpty(packet.ClientId) && !Regex.IsMatch(packet.ClientId, "^[a-zA-Z0-9]*$"))
{
throw new ProtocolViolationException("ClientId contains invalid characters [MQTT-3.1.3-5].");
}
}
private void ValidatePublishPacket(MqttPublishPacket packet)
{
if (packet.QualityOfServiceLevel == 0 && packet.Dup)
{
throw new ProtocolViolationException("Dup flag must be false for QoS 0 packets [MQTT-3.3.1-2].");
}
}
private async Task SerializeAsync(MqttConnectPacket packet, IMqttCommunicationChannel destination)
{
ValidateConnectPacket(packet);
using (var output = new MqttPacketWriter())
{
// Write variable header
output.Write(0x00); // 3.1.2.1 Protocol Name
output.Write(0x04); // ""
output.Write('M');
output.Write('Q');
output.Write('T');
output.Write('T');
output.Write(0x04); // 3.1.2.2 Protocol Level
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((byte)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);
output.Write(connectFlags);
output.Write(packet.KeepAlivePeriod);
output.WriteWithLengthPrefix(packet.ClientId);
if (packet.WillMessage != null)
{
output.WriteWithLengthPrefix(packet.WillMessage.Topic);
output.WriteWithLengthPrefix(packet.WillMessage.Payload);
}
if (packet.Username != null)
{
output.WriteWithLengthPrefix(packet.Username);
}
if (packet.Password != null)
{
output.WriteWithLengthPrefix(packet.Password);
}
output.InjectFixedHeader(MqttControlPacketType.Connect);
await output.WriteToAsync(destination);
}
}
private async Task SerializeAsync(MqttConnAckPacket packet, IMqttCommunicationChannel destination)
{
using (var output = new MqttPacketWriter())
{
var connectAcknowledgeFlags = new ByteWriter();
connectAcknowledgeFlags.Write(packet.IsSessionPresent);
output.Write(connectAcknowledgeFlags);
output.Write((byte)packet.ConnectReturnCode);
output.InjectFixedHeader(MqttControlPacketType.ConnAck);
await output.WriteToAsync(destination);
}
}
private async Task SerializeAsync(MqttDisconnectPacket packet, IMqttCommunicationChannel destination)
{
await SerializeEmptyPacketAsync(MqttControlPacketType.Disconnect, destination);
}
private async Task SerializeAsync(MqttPingReqPacket packet, IMqttCommunicationChannel destination)
{
await SerializeEmptyPacketAsync(MqttControlPacketType.PingReq, destination);
}
private async Task SerializeAsync(MqttPingRespPacket packet, IMqttCommunicationChannel destination)
{
await SerializeEmptyPacketAsync(MqttControlPacketType.PingResp, destination);
}
private async Task SerializeAsync(MqttPublishPacket packet, IMqttCommunicationChannel destination)
{
ValidatePublishPacket(packet);
using (var output = new MqttPacketWriter())
{
output.WriteWithLengthPrefix(packet.Topic);
if (packet.QualityOfServiceLevel > MqttQualityOfServiceLevel.AtMostOnce)
{
output.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)
{
output.Write(packet.Payload);
}
var fixedHeader = new ByteWriter();
fixedHeader.Write(packet.Retain);
fixedHeader.Write((byte)packet.QualityOfServiceLevel, 2);
fixedHeader.Write(packet.Dup);
output.InjectFixedHeader(MqttControlPacketType.Publish, fixedHeader.Value);
await output.WriteToAsync(destination);
}
}
private async Task SerializeAsync(MqttPubAckPacket packet, IMqttCommunicationChannel destination)
{
using (var output = new MqttPacketWriter())
{
output.Write(packet.PacketIdentifier);
output.InjectFixedHeader(MqttControlPacketType.PubAck);
await output.WriteToAsync(destination);
}
}
private async Task SerializeAsync(MqttPubRecPacket packet, IMqttCommunicationChannel destination)
{
using (var output = new MqttPacketWriter())
{
output.Write(packet.PacketIdentifier);
output.InjectFixedHeader(MqttControlPacketType.PubRec);
await output.WriteToAsync(destination);
}
}
private async Task SerializeAsync(MqttPubRelPacket packet, IMqttCommunicationChannel destination)
{
using (var output = new MqttPacketWriter())
{
output.Write(packet.PacketIdentifier);
output.InjectFixedHeader(MqttControlPacketType.PubRel, 0x02);
await output.WriteToAsync(destination);
}
}
private async Task SerializeAsync(MqttPubCompPacket packet, IMqttCommunicationChannel destination)
{
using (var output = new MqttPacketWriter())
{
output.Write(packet.PacketIdentifier);
output.InjectFixedHeader(MqttControlPacketType.PubComp);
await output.WriteToAsync(destination);
}
}
private async Task SerializeAsync(MqttSubscribePacket packet, IMqttCommunicationChannel destination)
{
using (var output = new MqttPacketWriter())
{
output.Write(packet.PacketIdentifier);
if (packet.TopicFilters?.Any() == true)
{
foreach (var topicFilter in packet.TopicFilters)
{
output.WriteWithLengthPrefix(topicFilter.Topic);
output.Write((byte)topicFilter.QualityOfServiceLevel);
}
}
output.InjectFixedHeader(MqttControlPacketType.Subscribe, 0x02);
await output.WriteToAsync(destination);
}
}
private async Task SerializeAsync(MqttSubAckPacket packet, IMqttCommunicationChannel destination)
{
using (var output = new MqttPacketWriter())
{
output.Write(packet.PacketIdentifier);
if (packet.SubscribeReturnCodes?.Any() == true)
{
foreach (var packetSubscribeReturnCode in packet.SubscribeReturnCodes)
{
output.Write((byte)packetSubscribeReturnCode);
}
}
output.InjectFixedHeader(MqttControlPacketType.SubAck);
await output.WriteToAsync(destination);
}
}
private async Task SerializeAsync(MqttUnsubscribePacket packet, IMqttCommunicationChannel destination)
{
using (var output = new MqttPacketWriter())
{
output.Write(packet.PacketIdentifier);
if (packet.TopicFilters?.Any() == true)
{
foreach (var topicFilter in packet.TopicFilters)
{
output.WriteWithLengthPrefix(topicFilter);
}
}
output.InjectFixedHeader(MqttControlPacketType.Unsubscibe, 0x02);
await output.WriteToAsync(destination);
}
}
private async Task SerializeAsync(MqttUnsubAckPacket packet, IMqttCommunicationChannel destination)
{
using (var output = new MqttPacketWriter())
{
output.Write(packet.PacketIdentifier);
output.InjectFixedHeader(MqttControlPacketType.UnsubAck);
await output.WriteToAsync(destination);
}
}
private async Task SerializeEmptyPacketAsync(MqttControlPacketType type, IMqttCommunicationChannel destination)
{
using (var output = new MqttPacketWriter())
{
output.InjectFixedHeader(type);
await output.WriteToAsync(destination);
}
}
}
}
@@ -0,0 +1,13 @@
using System.Threading.Tasks;
using MQTTnet.Core.Channel;
using MQTTnet.Core.Packets;
namespace MQTTnet.Core.Serializer
{
public interface IMqttPacketSerializer
{
Task SerializeAsync(MqttBasePacket mqttPacket, IMqttCommunicationChannel destination);
Task<MqttBasePacket> DeserializeAsync(IMqttCommunicationChannel source);
}
}
+127
View File
@@ -0,0 +1,127 @@
using System;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using MQTTnet.Core.Channel;
using MQTTnet.Core.Exceptions;
using MQTTnet.Core.Protocol;
namespace MQTTnet.Core.Serializer
{
public sealed class MqttPacketReader : IDisposable
{
private readonly MemoryStream _remainingData = new MemoryStream();
private readonly IMqttCommunicationChannel _source;
public MqttPacketReader(IMqttCommunicationChannel source)
{
_source = source ?? throw new ArgumentNullException(nameof(source));
}
public MqttControlPacketType ControlPacketType { get; private set; }
public byte FixedHeader { get; private set; }
public int RemainingLength { get; private set; }
public bool EndOfRemainingData => _remainingData.Position == _remainingData.Length;
public async Task ReadToEndAsync()
{
await ReadFixedHeaderAsync();
await ReadRemainingLengthAsync();
if (RemainingLength == 0)
{
return;
}
var buffer = new byte[RemainingLength];
await _source.ReadAsync(buffer);
_remainingData.Write(buffer, 0, buffer.Length);
_remainingData.Position = 0;
}
private async Task ReadFixedHeaderAsync()
{
FixedHeader = await ReadStreamByteAsync();
var byteReader = new ByteReader(FixedHeader);
byteReader.Read(4);
ControlPacketType = (MqttControlPacketType)byteReader.Read(4);
}
private async Task<byte> ReadStreamByteAsync()
{
var buffer = new byte[1];
await _source.ReadAsync(buffer);
return buffer[0];
}
private async Task ReadRemainingLengthAsync()
{
// 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;
do
{
encodedByte = await ReadStreamByteAsync();
value += (encodedByte & 127) * multiplier;
multiplier *= 128;
if (multiplier > 128 * 128 * 128)
{
throw new MqttProtocolViolationException("Remaining length is ivalid.");
}
} while ((encodedByte & 128) != 0);
RemainingLength = value;
}
public async Task<byte> ReadRemainingDataByteAsync()
{
return (await ReadRemainingDataAsync(1))[0];
}
public async Task<ushort> ReadRemainingDataUShortAsync()
{
var buffer = await ReadRemainingDataAsync(2);
var temp = buffer[0];
buffer[0] = buffer[1];
buffer[1] = temp;
return BitConverter.ToUInt16(buffer, 0);
}
public async Task<string> ReadRemainingDataStringWithLengthPrefixAsync()
{
var buffer = await ReadRemainingDataWithLengthPrefixAsync();
return Encoding.UTF8.GetString(buffer, 0, buffer.Length);
}
public async Task<byte[]> ReadRemainingDataWithLengthPrefixAsync()
{
var length = await ReadRemainingDataUShortAsync();
return await ReadRemainingDataAsync(length);
}
public async Task<byte[]> ReadRemainingDataAsync()
{
return await ReadRemainingDataAsync(RemainingLength - (int)_remainingData.Position);
}
public async Task<byte[]> ReadRemainingDataAsync(int length)
{
var buffer = new byte[length];
await _remainingData.ReadAsync(buffer, 0, buffer.Length);
return buffer;
}
public void Dispose()
{
_remainingData?.Dispose();
}
}
}
+107
View File
@@ -0,0 +1,107 @@
using System;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using MQTTnet.Core.Channel;
using MQTTnet.Core.Protocol;
namespace MQTTnet.Core.Serializer
{
public sealed class MqttPacketWriter : IDisposable
{
private readonly MemoryStream _buffer = new MemoryStream();
public void InjectFixedHeader(byte fixedHeader)
{
if (_buffer.Length == 0)
{
Write(fixedHeader);
Write(0);
return;
}
var remainingLength = (int)_buffer.Length;
using (var buffer = new MemoryStream())
{
_buffer.WriteTo(buffer);
_buffer.SetLength(0);
_buffer.WriteByte(fixedHeader);
// Alorithm taken from http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html.
var x = remainingLength;
do
{
var encodedByte = (byte)(x % 128);
x = x / 128;
if (x > 0)
{
encodedByte = (byte)(encodedByte | 128);
}
_buffer.WriteByte(encodedByte);
} while (x > 0);
buffer.Position = 0;
buffer.WriteTo(_buffer);
}
}
public void InjectFixedHeader(MqttControlPacketType packetType, byte flags = 0)
{
var fixedHeader = (byte)((byte)packetType << 4);
fixedHeader |= flags;
InjectFixedHeader(fixedHeader);
}
public void Write(byte value)
{
_buffer.WriteByte(value);
}
public void Write(char value)
{
_buffer.WriteByte((byte)value);
}
public void Write(ushort value)
{
var buffer = BitConverter.GetBytes(value);
_buffer.WriteByte(buffer[1]);
_buffer.WriteByte(buffer[0]);
}
public void Write(ByteWriter value)
{
_buffer.WriteByte(value.Value);
}
public void Write(byte[] value)
{
_buffer.Write(value, 0, value.Length);
}
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 void Dispose()
{
_buffer?.Dispose();
}
public async Task WriteToAsync(IMqttCommunicationChannel destination)
{
await destination.WriteAsync(_buffer.ToArray());
}
}
}
@@ -0,0 +1,22 @@
using System;
using MQTTnet.Core.Packets;
namespace MQTTnet.Core.Server
{
public class MqttClientPublishPacketContext
{
public MqttClientPublishPacketContext(MqttClientSession senderClientSession, MqttPublishPacket publishPacket)
{
SenderClientSession = senderClientSession ?? throw new ArgumentNullException(nameof(senderClientSession));
PublishPacket = publishPacket ?? throw new ArgumentNullException(nameof(publishPacket));
}
public MqttClientSession SenderClientSession { get; }
public MqttPublishPacket PublishPacket { get; }
public int SendTries { get; set; }
public bool IsSent { get; set; }
}
}
+179
View File
@@ -0,0 +1,179 @@
using System;
using System.Collections.Concurrent;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Core.Adapter;
using MQTTnet.Core.Diagnostics;
using MQTTnet.Core.Exceptions;
using MQTTnet.Core.Internal;
using MQTTnet.Core.Packets;
using MQTTnet.Core.Protocol;
namespace MQTTnet.Core.Server
{
public class MqttClientSession
{
private readonly ConcurrentDictionary<ushort, MqttPublishPacket> _pendingIncomingPublications = new ConcurrentDictionary<ushort, MqttPublishPacket>();
private readonly MqttClientSubscriptionsManager _subscriptionsManager = new MqttClientSubscriptionsManager();
private readonly MqttOutgoingPublicationsManager _outgoingPublicationsManager;
private readonly Action<MqttClientSession, MqttPublishPacket> _publishPacketReceivedCallback;
private readonly MqttServerOptions _options;
private CancellationTokenSource _cancellationTokenSource;
private IMqttCommunicationAdapter _adapter;
private string _identifier;
private MqttApplicationMessage _willApplicationMessage;
public MqttClientSession(MqttServerOptions options, Action<MqttClientSession, MqttPublishPacket> publishPacketReceivedCallback)
{
_options = options ?? throw new ArgumentNullException(nameof(options));
_publishPacketReceivedCallback = publishPacketReceivedCallback ?? throw new ArgumentNullException(nameof(publishPacketReceivedCallback));
_outgoingPublicationsManager = new MqttOutgoingPublicationsManager(options);
}
public async Task RunAsync(string identifier, MqttApplicationMessage willApplicationMessage, IMqttCommunicationAdapter adapter)
{
if (adapter == null) throw new ArgumentNullException(nameof(adapter));
_willApplicationMessage = willApplicationMessage;
try
{
_identifier = identifier;
_adapter = adapter;
_cancellationTokenSource = new CancellationTokenSource();
_outgoingPublicationsManager.Start(adapter);
while (!_cancellationTokenSource.IsCancellationRequested)
{
var packet = await adapter.ReceivePacketAsync(TimeSpan.Zero);
await HandleIncomingPacketAsync(packet);
}
}
catch (MqttCommunicationException)
{
}
catch (Exception exception)
{
MqttTrace.Error(nameof(MqttClientSession), exception, $"Client '{_identifier}': Unhandled exception while processing client packets.");
}
finally
{
if (willApplicationMessage != null)
{
_publishPacketReceivedCallback(this, _willApplicationMessage.ToPublishPacket());
}
_outgoingPublicationsManager.Stop();
_cancellationTokenSource.Cancel();
_adapter = null;
MqttTrace.Information(nameof(MqttClientSession), $"Client '{_identifier}': Disconnected.");
}
}
public void DispatchPublishPacket(MqttClientSession senderClientSession, MqttPublishPacket publishPacket)
{
if (senderClientSession == null) throw new ArgumentNullException(nameof(senderClientSession));
if (publishPacket == null) throw new ArgumentNullException(nameof(publishPacket));
if (!_subscriptionsManager.IsTopicSubscribed(publishPacket))
{
return;
}
_outgoingPublicationsManager.Enqueue(senderClientSession, publishPacket);
MqttTrace.Verbose(nameof(MqttClientSession), $"Client '{_identifier}: Enqueued pending publish packet.");
}
private async Task HandleIncomingPacketAsync(MqttBasePacket packet)
{
var subscribePacket = packet as MqttSubscribePacket;
if (subscribePacket != null)
{
await _adapter.SendPacketAsync(_subscriptionsManager.Subscribe(subscribePacket), _options.DefaultCommunicationTimeout);
return;
}
var unsubscribePacket = packet as MqttUnsubscribePacket;
if (unsubscribePacket != null)
{
await _adapter.SendPacketAsync(_subscriptionsManager.Unsubscribe(unsubscribePacket), _options.DefaultCommunicationTimeout);
return;
}
var publishPacket = packet as MqttPublishPacket;
if (publishPacket != null)
{
await HandleIncomingPublishPacketAsync(publishPacket);
return;
}
var pubRelPacket = packet as MqttPubRelPacket;
if (pubRelPacket != null)
{
await HandleIncomingPubRelPacketAsync(pubRelPacket);
return;
}
var pubAckPacket = packet as MqttPubAckPacket;
if (pubAckPacket != null)
{
await HandleIncomingPubAckPacketAsync(pubAckPacket);
return;
}
if (packet is MqttPingReqPacket)
{
await _adapter.SendPacketAsync(new MqttPingRespPacket(), _options.DefaultCommunicationTimeout);
return;
}
if (packet is MqttDisconnectPacket || packet is MqttConnectPacket)
{
_cancellationTokenSource.Cancel();
return;
}
MqttTrace.Warning(nameof(MqttClientSession), $"Client '{_identifier}': Received not supported packet ({packet}). Closing connection.");
_cancellationTokenSource.Cancel();
}
private async Task HandleIncomingPubAckPacketAsync(MqttPubAckPacket pubAckPacket)
{
await Task.FromResult(0);
}
private async Task HandleIncomingPublishPacketAsync(MqttPublishPacket publishPacket)
{
if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtMostOnce)
{
_publishPacketReceivedCallback(this, publishPacket);
}
else if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtLeastOnce)
{
await _adapter.SendPacketAsync(new MqttPubAckPacket { PacketIdentifier = publishPacket.PacketIdentifier }, _options.DefaultCommunicationTimeout);
_publishPacketReceivedCallback(this, publishPacket);
}
else if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.ExactlyOnce)
{
_pendingIncomingPublications[publishPacket.PacketIdentifier] = publishPacket;
await _adapter.SendPacketAsync(new MqttPubRecPacket { PacketIdentifier = publishPacket.PacketIdentifier }, _options.DefaultCommunicationTimeout);
}
}
private async Task HandleIncomingPubRelPacketAsync(MqttPubRelPacket pubRelPacket)
{
MqttPublishPacket publishPacket;
if (!_pendingIncomingPublications.TryRemove(pubRelPacket.PacketIdentifier, out publishPacket))
{
return;
}
await _adapter.SendPacketAsync(new MqttPubCompPacket { PacketIdentifier = publishPacket.PacketIdentifier }, _options.DefaultCommunicationTimeout);
_publishPacketReceivedCallback(this, publishPacket);
}
}
}
@@ -0,0 +1,93 @@
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Threading.Tasks;
using MQTTnet.Core.Adapter;
using MQTTnet.Core.Diagnostics;
using MQTTnet.Core.Exceptions;
using MQTTnet.Core.Packets;
using MQTTnet.Core.Protocol;
namespace MQTTnet.Core.Server
{
public class MqttClientSessionManager
{
private readonly ConcurrentDictionary<string, MqttClientSession> _clientSessions = new ConcurrentDictionary<string, MqttClientSession>();
private readonly MqttServerOptions _options;
public MqttClientSessionManager(MqttServerOptions options)
{
_options = options ?? throw new ArgumentNullException(nameof(options));
}
public async Task RunClientSessionAsync(MqttClientConnectedEventArgs eventArgs)
{
try
{
var connectPacket = await eventArgs.ClientAdapter.ReceivePacketAsync(_options.DefaultCommunicationTimeout) as MqttConnectPacket;
if (connectPacket == null)
{
throw new MqttProtocolViolationException("The first packet from a client must be a 'Connect' packet [MQTT-3.1.0-1].");
}
var connectReturnCode = MqttConnectReturnCode.ConnectionAccepted;
if (_options.ConnectionValidator != null)
{
connectReturnCode = _options.ConnectionValidator(connectPacket);
}
MqttClientSession clientSession = null;
var isSessionPresent = _clientSessions.ContainsKey(connectPacket.ClientId);
if (isSessionPresent && connectPacket.CleanSession)
{
MqttClientSession _;
_clientSessions.TryRemove(connectPacket.ClientId, out _);
}
else if (!connectPacket.CleanSession)
{
_clientSessions.TryGetValue(connectPacket.ClientId, out clientSession);
}
await eventArgs.ClientAdapter.SendPacketAsync(new MqttConnAckPacket
{
ConnectReturnCode = connectReturnCode,
IsSessionPresent = clientSession != null
}, _options.DefaultCommunicationTimeout);
if (connectReturnCode != MqttConnectReturnCode.ConnectionAccepted)
{
return;
}
if (clientSession == null)
{
clientSession = new MqttClientSession(_options, DispatchPublishPacket);
_clientSessions.TryAdd(connectPacket.ClientId, clientSession);
}
await clientSession.RunAsync(eventArgs.Identifier, connectPacket.WillMessage, eventArgs.ClientAdapter);
}
catch (Exception exception)
{
MqttTrace.Error(nameof(MqttServer), exception, exception.Message);
}
finally
{
await eventArgs.ClientAdapter.DisconnectAsync();
}
}
public void Clear()
{
_clientSessions.Clear();
}
private void DispatchPublishPacket(MqttClientSession senderClientSession, MqttPublishPacket publishPacket)
{
foreach (var clientSession in _clientSessions.Values.ToList())
{
clientSession.DispatchPublishPacket(senderClientSession, publishPacket);
}
}
}
}
@@ -0,0 +1,61 @@
using System;
using System.Collections.Concurrent;
using MQTTnet.Core.Packets;
using MQTTnet.Core.Protocol;
namespace MQTTnet.Core.Server
{
public class MqttClientSubscriptionsManager
{
private readonly ConcurrentDictionary<string, MqttQualityOfServiceLevel> _subscribedTopics = new ConcurrentDictionary<string, MqttQualityOfServiceLevel>();
public MqttSubAckPacket Subscribe(MqttSubscribePacket subscribePacket)
{
if (subscribePacket == null) throw new ArgumentNullException(nameof(subscribePacket));
var responsePacket = subscribePacket.CreateResponse<MqttSubAckPacket>();
foreach (var topicFilter in subscribePacket.TopicFilters)
{
_subscribedTopics[topicFilter.Topic] = topicFilter.QualityOfServiceLevel;
responsePacket.SubscribeReturnCodes.Add(MqttSubscribeReturnCode.SuccessMaximumQoS1); // TODO: Add support for QoS 2.
}
return responsePacket;
}
public MqttUnsubAckPacket Unsubscribe(MqttUnsubscribePacket unsubscribePacket)
{
if (unsubscribePacket == null) throw new ArgumentNullException(nameof(unsubscribePacket));
foreach (var topicFilter in unsubscribePacket.TopicFilters)
{
MqttQualityOfServiceLevel _;
_subscribedTopics.TryRemove(topicFilter, out _);
}
return unsubscribePacket.CreateResponse<MqttUnsubAckPacket>();
}
public bool IsTopicSubscribed(MqttPublishPacket publishPacket)
{
if (publishPacket == null) throw new ArgumentNullException(nameof(publishPacket));
foreach (var subscribedTopic in _subscribedTopics)
{
if (!MqttTopicFilterComparer.IsMatch(publishPacket.Topic, subscribedTopic.Key))
{
continue;
}
if (subscribedTopic.Value < publishPacket.QualityOfServiceLevel)
{
continue;
}
return true;
}
return false;
}
}
}
@@ -0,0 +1,129 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Core.Adapter;
using MQTTnet.Core.Diagnostics;
using MQTTnet.Core.Exceptions;
using MQTTnet.Core.Internal;
using MQTTnet.Core.Packets;
namespace MQTTnet.Core.Server
{
public class MqttOutgoingPublicationsManager
{
private readonly AutoResetEvent _resetEvent = new AutoResetEvent(false);
private readonly List<MqttClientPublishPacketContext> _pendingPublishPackets = new List<MqttClientPublishPacketContext>();
private readonly MqttServerOptions _options;
private CancellationTokenSource _cancellationTokenSource;
private IMqttCommunicationAdapter _adapter;
public MqttOutgoingPublicationsManager(MqttServerOptions options)
{
_options = options ?? throw new ArgumentNullException(nameof(options));
}
public void Start(IMqttCommunicationAdapter adapter)
{
if (_cancellationTokenSource != null)
{
throw new InvalidOperationException($"{nameof(MqttOutgoingPublicationsManager)} already started.");
}
_adapter = adapter ?? throw new ArgumentNullException(nameof(adapter));
_cancellationTokenSource = new CancellationTokenSource();
Task.Run(async () => await SendPendingPublishPacketsAsync(_cancellationTokenSource.Token)).Forget();
}
public void Stop()
{
_cancellationTokenSource?.Cancel();
_cancellationTokenSource = null;
}
public void Enqueue(MqttClientSession senderClientSession, MqttPublishPacket publishPacket)
{
if (senderClientSession == null) throw new ArgumentNullException(nameof(senderClientSession));
if (publishPacket == null) throw new ArgumentNullException(nameof(publishPacket));
lock (_pendingPublishPackets)
{
_pendingPublishPackets.Add(new MqttClientPublishPacketContext(senderClientSession, publishPacket));
_resetEvent.Set();
}
}
private async Task SendPendingPublishPacketsAsync(CancellationToken cancellationToken)
{
while (!cancellationToken.IsCancellationRequested)
{
try
{
_resetEvent.WaitOne();
if (cancellationToken.IsCancellationRequested)
{
return;
}
List<MqttClientPublishPacketContext> pendingPublishPackets;
lock (_pendingPublishPackets)
{
pendingPublishPackets = _pendingPublishPackets.ToList();
}
foreach (var publishPacket in pendingPublishPackets)
{
await TrySendPendingPublishPacketAsync(publishPacket);
}
}
catch (Exception e)
{
MqttTrace.Error(nameof(MqttOutgoingPublicationsManager), e, "Error while sending pending publish packets.");
}
finally
{
Cleanup();
}
}
}
private async Task TrySendPendingPublishPacketAsync(MqttClientPublishPacketContext publishPacketContext)
{
try
{
if (_adapter == null)
{
return;
}
publishPacketContext.PublishPacket.Dup = publishPacketContext.SendTries > 0;
await _adapter.SendPacketAsync(publishPacketContext.PublishPacket, _options.DefaultCommunicationTimeout);
publishPacketContext.IsSent = true;
}
catch (MqttCommunicationException exception)
{
MqttTrace.Warning(nameof(MqttOutgoingPublicationsManager), exception, "Sending publish packet failed.");
}
catch (Exception exception)
{
MqttTrace.Error(nameof(MqttOutgoingPublicationsManager), exception, "Sending publish packet failed.");
}
finally
{
publishPacketContext.SendTries++;
}
}
private void Cleanup()
{
lock (_pendingPublishPackets)
{
_pendingPublishPackets.RemoveAll(p => p.IsSent);
}
}
}
}
+67
View File
@@ -0,0 +1,67 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Core.Adapter;
using MQTTnet.Core.Diagnostics;
using MQTTnet.Core.Internal;
namespace MQTTnet.Core.Server
{
public class MqttServer
{
private readonly MqttClientSessionManager _clientSessionManager;
private readonly IMqttServerAdapter _adapter;
private readonly MqttServerOptions _options;
private CancellationTokenSource _cancellationTokenSource;
public MqttServer(MqttServerOptions options, IMqttServerAdapter adapter)
{
_options = options ?? throw new ArgumentNullException(nameof(options));
_adapter = adapter ?? throw new ArgumentNullException(nameof(adapter));
_clientSessionManager = new MqttClientSessionManager(options);
}
public void InjectClient(string identifier, IMqttCommunicationAdapter adapter)
{
if (adapter == null) throw new ArgumentNullException(nameof(adapter));
OnClientConnected(this, new MqttClientConnectedEventArgs(identifier, adapter));
}
public void Start()
{
if (_cancellationTokenSource != null)
{
throw new InvalidOperationException("The server is already started.");
}
_cancellationTokenSource = new CancellationTokenSource();
_adapter.ClientConnected += OnClientConnected;
_adapter.Start(_options);
MqttTrace.Information(nameof(MqttServer), "Started.");
}
public void Stop()
{
_cancellationTokenSource?.Cancel();
_cancellationTokenSource = null;
_adapter.ClientConnected -= OnClientConnected;
_adapter.Stop();
_clientSessionManager.Clear();
MqttTrace.Information(nameof(MqttServer), "Stopped.");
}
private void OnClientConnected(object sender, MqttClientConnectedEventArgs eventArgs)
{
MqttTrace.Information(nameof(MqttServer), $"Client '{eventArgs.Identifier}': Connected.");
Task.Run(async () => await _clientSessionManager.RunClientSessionAsync(eventArgs), _cancellationTokenSource.Token).Forget();
}
}
}
+17
View File
@@ -0,0 +1,17 @@
using System;
using MQTTnet.Core.Packets;
using MQTTnet.Core.Protocol;
namespace MQTTnet.Core.Server
{
public class MqttServerOptions
{
public int Port { get; set; } = 1883;
public int ConnectionBacklog { get; set; } = 10;
public TimeSpan DefaultCommunicationTimeout { get; set; } = TimeSpan.FromSeconds(10);
public Func<MqttConnectPacket, MqttConnectReturnCode> ConnectionValidator { get; set; }
}
}
@@ -0,0 +1,53 @@
using System;
namespace MQTTnet.Core.Server
{
public static class MqttTopicFilterComparer
{
private const char TopicLevelSeparator = '/';
public static bool IsMatch(string topic, string filter)
{
if (topic == null) throw new ArgumentNullException(nameof(topic));
if (filter == null) throw new ArgumentNullException(nameof(filter));
if (string.Equals(topic, filter, StringComparison.Ordinal))
{
return true;
}
var fragmentsTopic = topic.Split(new[] { TopicLevelSeparator }, StringSplitOptions.None);
var fragmentsFilter = filter.Split(new[] { TopicLevelSeparator }, StringSplitOptions.None);
for (var i = 0; i < fragmentsFilter.Length; i++)
{
if (fragmentsFilter[i] == "+")
{
continue;
}
if (fragmentsFilter[i] == "#" && i == fragmentsFilter.Length - 1)
{
return true;
}
if (i >= fragmentsTopic.Length)
{
return false;
}
if (!string.Equals(fragmentsFilter[i], fragmentsTopic[i]))
{
return false;
}
}
if (fragmentsTopic.Length > fragmentsFilter.Length)
{
return false;
}
return true;
}
}
}