mirror of
https://github.com/dotnet/MQTTnet.git
synced 2026-10-21 04:03:05 +08:00
Reorganize solution structure.
This commit is contained in:
@@ -1,52 +0,0 @@
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using System.Linq;
|
||||
using MQTTnet.Server;
|
||||
|
||||
namespace MQTTnet.AspNetCore
|
||||
{
|
||||
public static class ApplicationBuilderExtensions
|
||||
{
|
||||
public static IApplicationBuilder UseMqttEndpoint(this IApplicationBuilder app, string path = "/mqtt")
|
||||
{
|
||||
app.UseWebSockets();
|
||||
app.Use(async (context, next) =>
|
||||
{
|
||||
if (!context.WebSockets.IsWebSocketRequest || context.Request.Path != path)
|
||||
{
|
||||
await next();
|
||||
return;
|
||||
}
|
||||
|
||||
string subProtocol = null;
|
||||
|
||||
if (context.Request.Headers.TryGetValue("Sec-WebSocket-Protocol", out var requestedSubProtocolValues))
|
||||
{
|
||||
// Order the protocols to also match "mqtt", "mqttv-3.1", "mqttv-3.11" etc.
|
||||
subProtocol = requestedSubProtocolValues
|
||||
.OrderByDescending(p => p.Length)
|
||||
.FirstOrDefault(p => p.ToLower().StartsWith("mqtt"));
|
||||
}
|
||||
|
||||
var adapter = app.ApplicationServices.GetRequiredService<MqttWebSocketServerAdapter>();
|
||||
using (var webSocket = await context.WebSockets.AcceptWebSocketAsync(subProtocol))
|
||||
{
|
||||
var endpoint = $"{context.Connection.RemoteIpAddress}:{context.Connection.RemotePort}";
|
||||
await adapter.RunWebSocketConnectionAsync(webSocket, endpoint);
|
||||
}
|
||||
});
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
public static IApplicationBuilder UseMqttServer(this IApplicationBuilder app, Action<IMqttServer> configure)
|
||||
{
|
||||
var server = app.ApplicationServices.GetRequiredService<IMqttServer>();
|
||||
|
||||
configure(server);
|
||||
|
||||
return app;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<AssemblyVersion>0.0.0.0</AssemblyVersion>
|
||||
<FileVersion>0.0.0.0</FileVersion>
|
||||
<Version>0.0.0.0</Version>
|
||||
<Product />
|
||||
<Company />
|
||||
<Authors />
|
||||
<PackageId />
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||
<DefineConstants>RELEASE;NETSTANDARD2_0</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.0.1" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.WebSockets" Version="2.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="2.0.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\MQTTnet.NetStandard\MQTTnet.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -1,31 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using MQTTnet.Adapter;
|
||||
using MQTTnet.Diagnostics;
|
||||
using MQTTnet.Server;
|
||||
|
||||
namespace MQTTnet.AspNetCore
|
||||
{
|
||||
public class MqttHostedServer : MqttServer, IHostedService
|
||||
{
|
||||
private readonly IMqttServerOptions _options;
|
||||
|
||||
public MqttHostedServer(IMqttServerOptions options, IEnumerable<IMqttServerAdapter> adapters, IMqttNetLogger logger) : base(adapters, logger.CreateChildLogger(nameof(MqttHostedServer)))
|
||||
{
|
||||
_options = options ?? throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
public Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
return StartAsync(_options);
|
||||
}
|
||||
|
||||
public Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
return StopAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
using System;
|
||||
using System.Net.WebSockets;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Adapter;
|
||||
using MQTTnet.Diagnostics;
|
||||
using MQTTnet.Implementations;
|
||||
using MQTTnet.Serializer;
|
||||
using MQTTnet.Server;
|
||||
|
||||
namespace MQTTnet.AspNetCore
|
||||
{
|
||||
public class MqttWebSocketServerAdapter : IMqttServerAdapter
|
||||
{
|
||||
public event EventHandler<MqttServerAdapterClientAcceptedEventArgs> ClientAccepted;
|
||||
|
||||
public Task StartAsync(IMqttServerOptions options)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task StopAsync()
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public async Task RunWebSocketConnectionAsync(WebSocket webSocket, string endpoint)
|
||||
{
|
||||
if (webSocket == null) throw new ArgumentNullException(nameof(webSocket));
|
||||
|
||||
var clientAdapter = new MqttChannelAdapter(new MqttWebSocketChannel(webSocket, endpoint), new MqttPacketSerializer(), new MqttNetLogger().CreateChildLogger(nameof(MqttWebSocketServerAdapter)));
|
||||
|
||||
var eventArgs = new MqttServerAdapterClientAcceptedEventArgs(clientAdapter);
|
||||
ClientAccepted?.Invoke(this, eventArgs);
|
||||
|
||||
if (eventArgs.SessionTask != null)
|
||||
{
|
||||
await eventArgs.SessionTask.ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
StopAsync().GetAwaiter().GetResult();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
using System;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using MQTTnet.Adapter;
|
||||
using MQTTnet.Diagnostics;
|
||||
using MQTTnet.Server;
|
||||
using MQTTnet.Implementations;
|
||||
|
||||
namespace MQTTnet.AspNetCore
|
||||
{
|
||||
public static class ServiceCollectionExtensions
|
||||
{
|
||||
public static IServiceCollection AddHostedMqttServer(this IServiceCollection services, IMqttServerOptions options)
|
||||
{
|
||||
if (options == null) throw new ArgumentNullException(nameof(options));
|
||||
|
||||
var logger = new MqttNetLogger();
|
||||
var childLogger = logger.CreateChildLogger();
|
||||
|
||||
services.AddSingleton(options);
|
||||
services.AddSingleton<IMqttNetLogger>(logger);
|
||||
services.AddSingleton(childLogger);
|
||||
services.AddSingleton<MqttHostedServer>();
|
||||
services.AddSingleton<IHostedService>(s => s.GetService<MqttHostedServer>());
|
||||
services.AddSingleton<IMqttServer>(s => s.GetService<MqttHostedServer>());
|
||||
|
||||
services.AddSingleton<MqttWebSocketServerAdapter>();
|
||||
services.AddSingleton<MqttTcpServerAdapter>();
|
||||
services.AddSingleton<IMqttServerAdapter>(s => s.GetService<MqttWebSocketServerAdapter>());
|
||||
|
||||
if (options.DefaultEndpointOptions.IsEnabled)
|
||||
{
|
||||
services.AddSingleton<IMqttServerAdapter>(s => s.GetService<MqttTcpServerAdapter>());
|
||||
}
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
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 : IDisposable
|
||||
{
|
||||
string Endpoint { get; }
|
||||
|
||||
IMqttPacketSerializer PacketSerializer { get; }
|
||||
|
||||
event EventHandler ReadingPacketStarted;
|
||||
|
||||
event EventHandler ReadingPacketCompleted;
|
||||
|
||||
Task ConnectAsync(TimeSpan timeout, CancellationToken cancellationToken);
|
||||
|
||||
Task DisconnectAsync(TimeSpan timeout, CancellationToken cancellationToken);
|
||||
|
||||
Task SendPacketsAsync(TimeSpan timeout, IEnumerable<MqttBasePacket> packets, CancellationToken cancellationToken);
|
||||
|
||||
Task<MqttBasePacket> ReceivePacketAsync(TimeSpan timeout, CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Server;
|
||||
|
||||
namespace MQTTnet.Adapter
|
||||
{
|
||||
public interface IMqttServerAdapter : IDisposable
|
||||
{
|
||||
event EventHandler<MqttServerAdapterClientAcceptedEventArgs> ClientAccepted;
|
||||
|
||||
Task StartAsync(IMqttServerOptions options);
|
||||
Task StopAsync();
|
||||
}
|
||||
}
|
||||
@@ -1,220 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Net.Sockets;
|
||||
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 const int ReadBufferSize = 4096; // TODO: Move buffer size to config
|
||||
|
||||
private readonly IMqttNetChildLogger _logger;
|
||||
private readonly IMqttChannel _channel;
|
||||
|
||||
private bool _isDisposed;
|
||||
|
||||
public MqttChannelAdapter(IMqttChannel channel, IMqttPacketSerializer serializer, IMqttNetChildLogger logger)
|
||||
{
|
||||
if (logger == null) throw new ArgumentNullException(nameof(logger));
|
||||
|
||||
_channel = channel ?? throw new ArgumentNullException(nameof(channel));
|
||||
PacketSerializer = serializer ?? throw new ArgumentNullException(nameof(serializer));
|
||||
|
||||
_logger = logger.CreateChildLogger(nameof(MqttChannelAdapter));
|
||||
}
|
||||
|
||||
public string Endpoint => _channel.Endpoint;
|
||||
|
||||
public IMqttPacketSerializer PacketSerializer { get; }
|
||||
|
||||
public event EventHandler ReadingPacketStarted;
|
||||
public event EventHandler ReadingPacketCompleted;
|
||||
|
||||
public Task ConnectAsync(TimeSpan timeout, CancellationToken cancellationToken)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
_logger.Verbose("Connecting [Timeout={0}]", timeout);
|
||||
|
||||
return ExecuteAndWrapExceptionAsync(() =>
|
||||
Internal.TaskExtensions.TimeoutAfter(ct => _channel.ConnectAsync(ct), timeout, cancellationToken));
|
||||
}
|
||||
|
||||
public Task DisconnectAsync(TimeSpan timeout, CancellationToken cancellationToken)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
_logger.Verbose("Disconnecting [Timeout={0}]", timeout);
|
||||
|
||||
return ExecuteAndWrapExceptionAsync(() =>
|
||||
Internal.TaskExtensions.TimeoutAfter(ct => _channel.DisconnectAsync(), timeout, cancellationToken));
|
||||
}
|
||||
|
||||
public async Task SendPacketsAsync(TimeSpan timeout, IEnumerable<MqttBasePacket> packets, CancellationToken cancellationToken)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
|
||||
foreach (var packet in packets)
|
||||
{
|
||||
await SendPacketAsync(timeout, cancellationToken, packet).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
private Task SendPacketAsync(TimeSpan timeout, CancellationToken cancellationToken, MqttBasePacket packet)
|
||||
{
|
||||
return ExecuteAndWrapExceptionAsync(() =>
|
||||
{
|
||||
_logger.Verbose("TX >>> {0} [Timeout={1}]", packet, timeout);
|
||||
|
||||
var packetData = PacketSerializer.Serialize(packet);
|
||||
|
||||
return Internal.TaskExtensions.TimeoutAfter(ct => _channel.WriteAsync(
|
||||
packetData.Array,
|
||||
packetData.Offset,
|
||||
packetData.Count,
|
||||
ct), timeout, cancellationToken);
|
||||
});
|
||||
}
|
||||
|
||||
public async Task<MqttBasePacket> ReceivePacketAsync(TimeSpan timeout, CancellationToken cancellationToken)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
|
||||
MqttBasePacket packet = null;
|
||||
await ExecuteAndWrapExceptionAsync(async () =>
|
||||
{
|
||||
ReceivedMqttPacket receivedMqttPacket;
|
||||
|
||||
if (timeout > TimeSpan.Zero)
|
||||
{
|
||||
receivedMqttPacket = await Internal.TaskExtensions.TimeoutAfter(ct => ReceiveAsync(_channel, ct), timeout, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
receivedMqttPacket = await ReceiveAsync(_channel, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (receivedMqttPacket == null || cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
packet = PacketSerializer.Deserialize(receivedMqttPacket);
|
||||
if (packet == null)
|
||||
{
|
||||
throw new MqttProtocolViolationException("Received malformed packet.");
|
||||
}
|
||||
|
||||
_logger.Verbose("RX <<< {0}", packet);
|
||||
}).ConfigureAwait(false);
|
||||
|
||||
return packet;
|
||||
}
|
||||
|
||||
private async Task<ReceivedMqttPacket> ReceiveAsync(IMqttChannel channel, CancellationToken cancellationToken)
|
||||
{
|
||||
var fixedHeader = await MqttPacketReader.ReadFixedHeaderAsync(channel, cancellationToken).ConfigureAwait(false);
|
||||
if (fixedHeader == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
ReadingPacketStarted?.Invoke(this, EventArgs.Empty);
|
||||
|
||||
if (fixedHeader.RemainingLength == 0)
|
||||
{
|
||||
return new ReceivedMqttPacket(fixedHeader.Flags, null);
|
||||
}
|
||||
|
||||
var body = new byte[fixedHeader.RemainingLength];
|
||||
var bodyOffset = 0;
|
||||
var chunkSize = Math.Min(ReadBufferSize, fixedHeader.RemainingLength);
|
||||
|
||||
do
|
||||
{
|
||||
var bytesLeft = body.Length - bodyOffset;
|
||||
if (chunkSize > bytesLeft)
|
||||
{
|
||||
chunkSize = bytesLeft;
|
||||
}
|
||||
|
||||
var readBytes = await channel.ReadAsync(body, bodyOffset, chunkSize, cancellationToken) .ConfigureAwait(false);
|
||||
if (readBytes <= 0)
|
||||
{
|
||||
ExceptionHelper.ThrowGracefulSocketClose();
|
||||
}
|
||||
|
||||
bodyOffset += readBytes;
|
||||
} while (bodyOffset < body.Length);
|
||||
|
||||
return new ReceivedMqttPacket(fixedHeader.Flags, new MqttPacketBodyReader(body));
|
||||
}
|
||||
finally
|
||||
{
|
||||
ReadingPacketCompleted?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task ExecuteAndWrapExceptionAsync(Func<Task> action)
|
||||
{
|
||||
try
|
||||
{
|
||||
await action().ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
if (exception is TaskCanceledException ||
|
||||
exception is OperationCanceledException ||
|
||||
exception is MqttCommunicationTimedOutException ||
|
||||
exception is MqttCommunicationException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
|
||||
if (exception is IOException && exception.InnerException is SocketException socketException)
|
||||
{
|
||||
if (socketException.SocketErrorCode == SocketError.ConnectionAborted)
|
||||
{
|
||||
throw new OperationCanceledException();
|
||||
}
|
||||
}
|
||||
|
||||
if (exception is COMException comException)
|
||||
{
|
||||
if ((uint)comException.HResult == ErrorOperationAborted)
|
||||
{
|
||||
throw new OperationCanceledException();
|
||||
}
|
||||
}
|
||||
|
||||
throw new MqttCommunicationException(exception);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_isDisposed = true;
|
||||
|
||||
_channel?.Dispose();
|
||||
}
|
||||
|
||||
private void ThrowIfDisposed()
|
||||
{
|
||||
if (_isDisposed)
|
||||
{
|
||||
throw new ObjectDisposedException(nameof(MqttChannelAdapter));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
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; }
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
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; }
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
using MQTTnet.Serializer;
|
||||
|
||||
namespace MQTTnet.Adapter
|
||||
{
|
||||
public class ReceivedMqttPacket
|
||||
{
|
||||
public ReceivedMqttPacket(byte fixedHeader, MqttPacketBodyReader body)
|
||||
{
|
||||
FixedHeader = fixedHeader;
|
||||
Body = body;
|
||||
}
|
||||
|
||||
public byte FixedHeader { get; }
|
||||
|
||||
public MqttPacketBodyReader Body { get; }
|
||||
}
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Protocol;
|
||||
|
||||
namespace MQTTnet
|
||||
{
|
||||
public static class ApplicationMessagePublisherExtensions
|
||||
{
|
||||
public static Task PublishAsync(this IApplicationMessagePublisher publisher, params MqttApplicationMessage[] applicationMessages)
|
||||
{
|
||||
if (publisher == null) throw new ArgumentNullException(nameof(publisher));
|
||||
if (applicationMessages == null) throw new ArgumentNullException(nameof(applicationMessages));
|
||||
|
||||
return publisher.PublishAsync(applicationMessages);
|
||||
}
|
||||
|
||||
public static Task PublishAsync(this IApplicationMessagePublisher publisher, string topic)
|
||||
{
|
||||
if (publisher == null) throw new ArgumentNullException(nameof(publisher));
|
||||
if (topic == null) throw new ArgumentNullException(nameof(topic));
|
||||
|
||||
return publisher.PublishAsync(builder => builder
|
||||
.WithTopic(topic)
|
||||
);
|
||||
}
|
||||
|
||||
public static Task PublishAsync(this IApplicationMessagePublisher publisher, string topic, string payload)
|
||||
{
|
||||
if (publisher == null) throw new ArgumentNullException(nameof(publisher));
|
||||
if (topic == null) throw new ArgumentNullException(nameof(topic));
|
||||
|
||||
return publisher.PublishAsync(builder => builder
|
||||
.WithTopic(topic)
|
||||
.WithPayload(payload)
|
||||
);
|
||||
}
|
||||
|
||||
public static Task PublishAsync(this IApplicationMessagePublisher publisher, string topic, string payload, MqttQualityOfServiceLevel qualityOfServiceLevel)
|
||||
{
|
||||
if (publisher == null) throw new ArgumentNullException(nameof(publisher));
|
||||
if (topic == null) throw new ArgumentNullException(nameof(topic));
|
||||
|
||||
return publisher.PublishAsync(builder => builder
|
||||
.WithTopic(topic)
|
||||
.WithPayload(payload)
|
||||
.WithQualityOfServiceLevel(qualityOfServiceLevel)
|
||||
);
|
||||
}
|
||||
|
||||
public static Task PublishAsync(this IApplicationMessagePublisher publisher, Func<MqttApplicationMessageBuilder, MqttApplicationMessageBuilder> builder)
|
||||
{
|
||||
var message = builder(new MqttApplicationMessageBuilder()).Build();
|
||||
return publisher.PublishAsync(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MQTTnet.Channel
|
||||
{
|
||||
public interface IMqttChannel : IDisposable
|
||||
{
|
||||
string Endpoint { get; }
|
||||
|
||||
Task ConnectAsync(CancellationToken cancellationToken);
|
||||
Task DisconnectAsync();
|
||||
|
||||
Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken);
|
||||
Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MQTTnet.Client
|
||||
{
|
||||
public interface IMqttClient : IApplicationMessageReceiver, IApplicationMessagePublisher, IDisposable
|
||||
{
|
||||
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> topics);
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
using MQTTnet.Adapter;
|
||||
using MQTTnet.Diagnostics;
|
||||
|
||||
namespace MQTTnet.Client
|
||||
{
|
||||
public interface IMqttClientAdapterFactory
|
||||
{
|
||||
IMqttChannelAdapter CreateClientAdapter(IMqttClientOptions options, IMqttNetChildLogger logger);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
namespace MQTTnet.Client
|
||||
{
|
||||
public interface IMqttClientChannelOptions
|
||||
{
|
||||
MqttClientTlsOptions TlsOptions { get; }
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
namespace MQTTnet.Client
|
||||
{
|
||||
public interface IMqttClientCredentials
|
||||
{
|
||||
string Password { get; }
|
||||
string Username { get; }
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
using MQTTnet.Diagnostics;
|
||||
|
||||
namespace MQTTnet.Client
|
||||
{
|
||||
public interface IMqttClientFactory
|
||||
{
|
||||
IMqttClient CreateMqttClient();
|
||||
|
||||
IMqttClient CreateMqttClient(IMqttNetLogger logger);
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
using System;
|
||||
using MQTTnet.Serializer;
|
||||
|
||||
namespace MQTTnet.Client
|
||||
{
|
||||
public interface IMqttClientOptions
|
||||
{
|
||||
string ClientId { get; }
|
||||
bool CleanSession { get; }
|
||||
IMqttClientCredentials Credentials { get; }
|
||||
MqttProtocolVersion ProtocolVersion { get; }
|
||||
IMqttClientChannelOptions ChannelOptions { get; }
|
||||
|
||||
TimeSpan CommunicationTimeout { get; }
|
||||
TimeSpan KeepAlivePeriod { get; }
|
||||
TimeSpan? KeepAliveSendInterval { get; }
|
||||
|
||||
MqttApplicationMessage WillMessage { get; }
|
||||
}
|
||||
}
|
||||
@@ -1,549 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
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 MqttPacketIdentifierProvider _packetIdentifierProvider = new MqttPacketIdentifierProvider();
|
||||
private readonly Stopwatch _sendTracker = new Stopwatch();
|
||||
private readonly SemaphoreSlim _disconnectLock = new SemaphoreSlim(1, 1);
|
||||
private readonly MqttPacketDispatcher _packetDispatcher = new MqttPacketDispatcher();
|
||||
|
||||
private readonly IMqttClientAdapterFactory _adapterFactory;
|
||||
private readonly IMqttNetChildLogger _logger;
|
||||
|
||||
private IMqttClientOptions _options;
|
||||
private CancellationTokenSource _cancellationTokenSource;
|
||||
private Task _packetReceiverTask;
|
||||
private Task _keepAliveMessageSenderTask;
|
||||
private IMqttChannelAdapter _adapter;
|
||||
private bool _cleanDisconnectInitiated;
|
||||
|
||||
public MqttClient(IMqttClientAdapterFactory channelFactory, IMqttNetLogger logger)
|
||||
{
|
||||
if (logger == null) throw new ArgumentNullException(nameof(logger));
|
||||
|
||||
_adapterFactory = channelFactory ?? throw new ArgumentNullException(nameof(channelFactory));
|
||||
_logger = logger.CreateChildLogger(nameof(MqttClient));
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
_cancellationTokenSource = new CancellationTokenSource();
|
||||
_options = options;
|
||||
_packetIdentifierProvider.Reset();
|
||||
_packetDispatcher.Reset();
|
||||
|
||||
_adapter = _adapterFactory.CreateClientAdapter(options, _logger);
|
||||
|
||||
_logger.Verbose($"Trying to connect with server ({_options.ChannelOptions}).");
|
||||
await _adapter.ConnectAsync(_options.CommunicationTimeout, _cancellationTokenSource.Token).ConfigureAwait(false);
|
||||
_logger.Verbose("Connection with server established.");
|
||||
|
||||
StartReceivingPackets(_cancellationTokenSource.Token);
|
||||
|
||||
var connectResponse = await AuthenticateAsync(options.WillMessage, _cancellationTokenSource.Token).ConfigureAwait(false);
|
||||
_logger.Verbose("MQTT connection with server established.");
|
||||
|
||||
_sendTracker.Restart();
|
||||
|
||||
if (_options.KeepAlivePeriod != TimeSpan.Zero)
|
||||
{
|
||||
StartSendingKeepAliveMessages(_cancellationTokenSource.Token);
|
||||
}
|
||||
|
||||
IsConnected = true;
|
||||
Connected?.Invoke(this, new MqttClientConnectedEventArgs(connectResponse.IsSessionPresent));
|
||||
|
||||
_logger.Info("Connected.");
|
||||
return new MqttClientConnectResult(connectResponse.IsSessionPresent);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
_logger.Error(exception, "Error while connecting with server.");
|
||||
await DisconnectInternalAsync(null, exception).ConfigureAwait(false);
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task DisconnectAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
_cleanDisconnectInitiated = true;
|
||||
|
||||
if (IsConnected && !_cancellationTokenSource.IsCancellationRequested)
|
||||
{
|
||||
await SendAsync(new MqttDisconnectPacket(), _cancellationTokenSource.Token).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
await DisconnectInternalAsync(null, null).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 = _packetIdentifierProvider.GetNewPacketIdentifier(),
|
||||
TopicFilters = topicFilters.ToList()
|
||||
};
|
||||
|
||||
var response = await SendAndReceiveAsync<MqttSubAckPacket>(subscribePacket, _cancellationTokenSource.Token).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 = _packetIdentifierProvider.GetNewPacketIdentifier(),
|
||||
TopicFilters = topicFilters.ToList()
|
||||
};
|
||||
|
||||
await SendAndReceiveAsync<MqttUnsubAckPacket>(unsubscribePacket, _cancellationTokenSource.Token).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task PublishAsync(IEnumerable<MqttApplicationMessage> applicationMessages)
|
||||
{
|
||||
ThrowIfNotConnected();
|
||||
|
||||
var publishPackets = applicationMessages.Select(m => m.ToPublishPacket());
|
||||
var packetGroups = publishPackets.GroupBy(p => p.QualityOfServiceLevel).OrderBy(g => g.Key);
|
||||
|
||||
foreach (var qosGroup in packetGroups)
|
||||
{
|
||||
switch (qosGroup.Key)
|
||||
{
|
||||
case MqttQualityOfServiceLevel.AtMostOnce:
|
||||
{
|
||||
// No packet identifier is used for QoS 0 [3.3.2.2 Packet Identifier]
|
||||
await SendAsync(qosGroup, _cancellationTokenSource.Token).ConfigureAwait(false);
|
||||
break;
|
||||
}
|
||||
case MqttQualityOfServiceLevel.AtLeastOnce:
|
||||
{
|
||||
foreach (var publishPacket in qosGroup)
|
||||
{
|
||||
publishPacket.PacketIdentifier = _packetIdentifierProvider.GetNewPacketIdentifier();
|
||||
await SendAndReceiveAsync<MqttPubAckPacket>(publishPacket, _cancellationTokenSource.Token).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case MqttQualityOfServiceLevel.ExactlyOnce:
|
||||
{
|
||||
foreach (var publishPacket in qosGroup)
|
||||
{
|
||||
publishPacket.PacketIdentifier = _packetIdentifierProvider.GetNewPacketIdentifier();
|
||||
|
||||
var pubRecPacket = await SendAndReceiveAsync<MqttPubRecPacket>(publishPacket, _cancellationTokenSource.Token).ConfigureAwait(false);
|
||||
var pubRelPacket = new MqttPubRelPacket
|
||||
{
|
||||
PacketIdentifier = pubRecPacket.PacketIdentifier
|
||||
};
|
||||
|
||||
await SendAndReceiveAsync<MqttPubCompPacket>(pubRelPacket, _cancellationTokenSource.Token).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_cancellationTokenSource?.Dispose();
|
||||
_cancellationTokenSource = null;
|
||||
|
||||
_adapter?.Dispose();
|
||||
}
|
||||
|
||||
private async Task<MqttConnAckPacket> AuthenticateAsync(MqttApplicationMessage willApplicationMessage, CancellationToken cancellationToken)
|
||||
{
|
||||
var connectPacket = new MqttConnectPacket
|
||||
{
|
||||
ClientId = _options.ClientId,
|
||||
Username = _options.Credentials?.Username,
|
||||
Password = _options.Credentials?.Password,
|
||||
CleanSession = _options.CleanSession,
|
||||
KeepAlivePeriod = (ushort)_options.KeepAlivePeriod.TotalSeconds,
|
||||
WillMessage = willApplicationMessage
|
||||
};
|
||||
|
||||
var response = await SendAndReceiveAsync<MqttConnAckPacket>(connectPacket, cancellationToken).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(Task sender, Exception exception)
|
||||
{
|
||||
await InitiateDisconnectAsync().ConfigureAwait(false);
|
||||
|
||||
var clientWasConnected = IsConnected;
|
||||
IsConnected = false;
|
||||
|
||||
try
|
||||
{
|
||||
await WaitForTaskAsync(_packetReceiverTask, sender).ConfigureAwait(false);
|
||||
await WaitForTaskAsync(_keepAliveMessageSenderTask, sender).ConfigureAwait(false);
|
||||
|
||||
if (_adapter != null)
|
||||
{
|
||||
await _adapter.DisconnectAsync(_options.CommunicationTimeout, CancellationToken.None).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
_logger.Verbose("Disconnected from adapter.");
|
||||
}
|
||||
catch (Exception adapterException)
|
||||
{
|
||||
_logger.Warning(adapterException, "Error while disconnecting from adapter.");
|
||||
}
|
||||
finally
|
||||
{
|
||||
_adapter?.Dispose();
|
||||
_adapter = null;
|
||||
_cancellationTokenSource?.Dispose();
|
||||
_cancellationTokenSource = null;
|
||||
_cleanDisconnectInitiated = false;
|
||||
|
||||
_logger.Info("Disconnected.");
|
||||
Disconnected?.Invoke(this, new MqttClientDisconnectedEventArgs(clientWasConnected, exception));
|
||||
}
|
||||
}
|
||||
|
||||
private async Task InitiateDisconnectAsync()
|
||||
{
|
||||
await _disconnectLock.WaitAsync().ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
if (_cancellationTokenSource == null || _cancellationTokenSource.IsCancellationRequested)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_cancellationTokenSource.Cancel(false);
|
||||
}
|
||||
catch (Exception adapterException)
|
||||
{
|
||||
_logger.Warning(adapterException, "Error while initiating disconnect.");
|
||||
}
|
||||
finally
|
||||
{
|
||||
_disconnectLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
private Task SendAsync(MqttBasePacket packet, CancellationToken cancellationToken)
|
||||
{
|
||||
return SendAsync(new[] { packet }, cancellationToken);
|
||||
}
|
||||
|
||||
private Task SendAsync(IEnumerable<MqttBasePacket> packets, CancellationToken cancellationToken)
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
throw new TaskCanceledException();
|
||||
}
|
||||
|
||||
_sendTracker.Restart();
|
||||
return _adapter.SendPacketsAsync(_options.CommunicationTimeout, packets, cancellationToken);
|
||||
}
|
||||
|
||||
private async Task<TResponsePacket> SendAndReceiveAsync<TResponsePacket>(MqttBasePacket requestPacket, CancellationToken cancellationToken) where TResponsePacket : MqttBasePacket
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
throw new TaskCanceledException();
|
||||
}
|
||||
|
||||
_sendTracker.Restart();
|
||||
|
||||
ushort identifier = 0;
|
||||
if (requestPacket is IMqttPacketWithIdentifier packetWithIdentifier && packetWithIdentifier.PacketIdentifier.HasValue)
|
||||
{
|
||||
identifier = packetWithIdentifier.PacketIdentifier.Value;
|
||||
}
|
||||
|
||||
var packetAwaiter = _packetDispatcher.AddPacketAwaiter<TResponsePacket>(identifier);
|
||||
try
|
||||
{
|
||||
await _adapter.SendPacketsAsync(_options.CommunicationTimeout, new[] { requestPacket }, cancellationToken).ConfigureAwait(false);
|
||||
var respone = await Internal.TaskExtensions.TimeoutAfter(ct => packetAwaiter.Task, _options.CommunicationTimeout, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
return (TResponsePacket)respone;
|
||||
}
|
||||
catch (MqttCommunicationTimedOutException)
|
||||
{
|
||||
_logger.Warning(null, "Timeout while waiting for packet of type '{0}'.", typeof(TResponsePacket).Namespace);
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_packetDispatcher.RemovePacketAwaiter<TResponsePacket>(identifier);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SendKeepAliveMessagesAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
_logger.Verbose("Start sending keep alive packets.");
|
||||
|
||||
try
|
||||
{
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
var keepAliveSendInterval = TimeSpan.FromSeconds(_options.KeepAlivePeriod.TotalSeconds * 0.75);
|
||||
if (_options.KeepAliveSendInterval.HasValue)
|
||||
{
|
||||
keepAliveSendInterval = _options.KeepAliveSendInterval.Value;
|
||||
}
|
||||
|
||||
var waitTime = keepAliveSendInterval - _sendTracker.Elapsed;
|
||||
if (waitTime <= TimeSpan.Zero)
|
||||
{
|
||||
await SendAndReceiveAsync<MqttPingRespPacket>(new MqttPingReqPacket(), cancellationToken).ConfigureAwait(false);
|
||||
waitTime = keepAliveSendInterval;
|
||||
}
|
||||
|
||||
await Task.Delay(waitTime, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
if (_cleanDisconnectInitiated)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (exception is OperationCanceledException)
|
||||
{
|
||||
}
|
||||
else if (exception is MqttCommunicationException)
|
||||
{
|
||||
_logger.Warning(exception, "MQTT communication exception while sending/receiving keep alive packets.");
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Error(exception, "Unhandled exception while sending/receiving keep alive packets.");
|
||||
}
|
||||
|
||||
await DisconnectInternalAsync(_keepAliveMessageSenderTask, exception).ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_logger.Verbose("Stopped sending keep alive packets.");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ReceivePacketsAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
_logger.Verbose("Start receiving packets.");
|
||||
|
||||
try
|
||||
{
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
var packet = await _adapter.ReceivePacketAsync(TimeSpan.Zero, cancellationToken).ConfigureAwait(false);
|
||||
if (packet != null)
|
||||
{
|
||||
await ProcessReceivedPacketAsync(packet, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
if (_cleanDisconnectInitiated)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (exception is OperationCanceledException)
|
||||
{
|
||||
}
|
||||
else if (exception is MqttCommunicationException)
|
||||
{
|
||||
_logger.Warning(exception, "MQTT communication exception while receiving packets.");
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Error(exception, "Unhandled exception while receiving packets.");
|
||||
}
|
||||
|
||||
_packetDispatcher.Dispatch(exception);
|
||||
await DisconnectInternalAsync(_packetReceiverTask, exception).ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_logger.Verbose("Stopped receiving packets.");
|
||||
}
|
||||
}
|
||||
|
||||
private Task ProcessReceivedPacketAsync(MqttBasePacket packet, CancellationToken cancellationToken)
|
||||
{
|
||||
if (packet is MqttPublishPacket publishPacket)
|
||||
{
|
||||
return ProcessReceivedPublishPacketAsync(publishPacket, cancellationToken);
|
||||
}
|
||||
|
||||
if (packet is MqttPingReqPacket)
|
||||
{
|
||||
return SendAsync(new MqttPingRespPacket(), cancellationToken);
|
||||
}
|
||||
|
||||
if (packet is MqttDisconnectPacket)
|
||||
{
|
||||
return DisconnectAsync();
|
||||
}
|
||||
|
||||
if (packet is MqttPubRelPacket pubRelPacket)
|
||||
{
|
||||
return ProcessReceivedPubRelPacket(pubRelPacket, cancellationToken);
|
||||
}
|
||||
|
||||
_packetDispatcher.Dispatch(packet);
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
private Task ProcessReceivedPublishPacketAsync(MqttPublishPacket publishPacket, CancellationToken cancellationToken)
|
||||
{
|
||||
if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtMostOnce)
|
||||
{
|
||||
FireApplicationMessageReceivedEvent(publishPacket);
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtLeastOnce)
|
||||
{
|
||||
FireApplicationMessageReceivedEvent(publishPacket);
|
||||
return SendAsync(new MqttPubAckPacket { PacketIdentifier = publishPacket.PacketIdentifier }, cancellationToken);
|
||||
}
|
||||
|
||||
if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.ExactlyOnce)
|
||||
{
|
||||
// QoS 2 is implement as method "B" (4.3.3 QoS 2: Exactly once delivery)
|
||||
FireApplicationMessageReceivedEvent(publishPacket);
|
||||
return SendAsync(new MqttPubRecPacket { PacketIdentifier = publishPacket.PacketIdentifier }, cancellationToken);
|
||||
}
|
||||
|
||||
throw new MqttCommunicationException("Received a not supported QoS level.");
|
||||
}
|
||||
|
||||
private Task ProcessReceivedPubRelPacket(MqttPubRelPacket pubRelPacket, CancellationToken cancellationToken)
|
||||
{
|
||||
var response = new MqttPubCompPacket
|
||||
{
|
||||
PacketIdentifier = pubRelPacket.PacketIdentifier
|
||||
};
|
||||
|
||||
return SendAsync(response, cancellationToken);
|
||||
}
|
||||
|
||||
private void StartReceivingPackets(CancellationToken cancellationToken)
|
||||
{
|
||||
_packetReceiverTask = Task.Run(
|
||||
() => ReceivePacketsAsync(cancellationToken),
|
||||
cancellationToken);
|
||||
}
|
||||
|
||||
private void StartSendingKeepAliveMessages(CancellationToken cancellationToken)
|
||||
{
|
||||
_keepAliveMessageSenderTask = Task.Run(
|
||||
() => SendKeepAliveMessagesAsync(cancellationToken),
|
||||
cancellationToken);
|
||||
}
|
||||
|
||||
private void FireApplicationMessageReceivedEvent(MqttPublishPacket publishPacket)
|
||||
{
|
||||
try
|
||||
{
|
||||
var applicationMessage = publishPacket.ToApplicationMessage();
|
||||
ApplicationMessageReceived?.Invoke(this, new MqttApplicationMessageReceivedEventArgs(_options.ClientId, applicationMessage));
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
_logger.Error(exception, "Unhandled exception while handling application message.");
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task WaitForTaskAsync(Task task, Task sender)
|
||||
{
|
||||
if (task == sender || task == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (task.IsCanceled || task.IsCompleted || task.IsFaulted)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await task.ConfigureAwait(false);
|
||||
}
|
||||
catch (TaskCanceledException)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
namespace MQTTnet.Client
|
||||
{
|
||||
public class MqttClientConnectResult
|
||||
{
|
||||
public MqttClientConnectResult(bool isSessionPresent)
|
||||
{
|
||||
IsSessionPresent = isSessionPresent;
|
||||
}
|
||||
|
||||
public bool IsSessionPresent { get; }
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace MQTTnet.Client
|
||||
{
|
||||
public class MqttClientConnectedEventArgs : EventArgs
|
||||
{
|
||||
public MqttClientConnectedEventArgs(bool isSessionPresent)
|
||||
{
|
||||
IsSessionPresent = isSessionPresent;
|
||||
}
|
||||
|
||||
public bool IsSessionPresent { get; }
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
namespace MQTTnet.Client
|
||||
{
|
||||
public class MqttClientCredentials : IMqttClientCredentials
|
||||
{
|
||||
public string Username { get; set; }
|
||||
|
||||
public string Password { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace MQTTnet.Client
|
||||
{
|
||||
public class MqttClientDisconnectedEventArgs : EventArgs
|
||||
{
|
||||
public MqttClientDisconnectedEventArgs(bool clientWasConnected, Exception exception)
|
||||
{
|
||||
ClientWasConnected = clientWasConnected;
|
||||
Exception = exception;
|
||||
}
|
||||
|
||||
public bool ClientWasConnected { get; }
|
||||
|
||||
public Exception Exception { get; }
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Protocol;
|
||||
|
||||
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<IList<MqttSubscribeResult>> SubscribeAsync(this IMqttClient client, string topic, MqttQualityOfServiceLevel qualityOfServiceLevel)
|
||||
{
|
||||
if (client == null) throw new ArgumentNullException(nameof(client));
|
||||
if (topic == null) throw new ArgumentNullException(nameof(topic));
|
||||
|
||||
return client.SubscribeAsync(new TopicFilterBuilder().WithTopic(topic).WithQualityOfServiceLevel(qualityOfServiceLevel).Build());
|
||||
}
|
||||
|
||||
public static Task<IList<MqttSubscribeResult>> SubscribeAsync(this IMqttClient client, string topic)
|
||||
{
|
||||
if (client == null) throw new ArgumentNullException(nameof(client));
|
||||
if (topic == null) throw new ArgumentNullException(nameof(topic));
|
||||
|
||||
return client.SubscribeAsync(new TopicFilterBuilder().WithTopic(topic).Build());
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
using System;
|
||||
using MQTTnet.Serializer;
|
||||
|
||||
namespace MQTTnet.Client
|
||||
{
|
||||
public class MqttClientOptions : IMqttClientOptions
|
||||
{
|
||||
public string ClientId { get; set; } = Guid.NewGuid().ToString("N");
|
||||
public bool CleanSession { get; set; } = true;
|
||||
public IMqttClientCredentials Credentials { get; set; } = new MqttClientCredentials();
|
||||
public MqttProtocolVersion ProtocolVersion { get; set; } = MqttProtocolVersion.V311;
|
||||
public IMqttClientChannelOptions ChannelOptions { get; set; }
|
||||
|
||||
public TimeSpan CommunicationTimeout { get; set; } = TimeSpan.FromSeconds(10);
|
||||
public TimeSpan KeepAlivePeriod { get; set; } = TimeSpan.FromSeconds(15);
|
||||
public TimeSpan? KeepAliveSendInterval { get; set; }
|
||||
|
||||
public MqttApplicationMessage WillMessage { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,141 +0,0 @@
|
||||
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 WithKeepAliveSendInterval(TimeSpan value)
|
||||
{
|
||||
_options.KeepAliveSendInterval = 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)
|
||||
{
|
||||
_tcpOptions.TlsOptions = _tlsOptions;
|
||||
}
|
||||
else if (_webSocketOptions != null)
|
||||
{
|
||||
_webSocketOptions.TlsOptions = _tlsOptions;
|
||||
}
|
||||
}
|
||||
|
||||
_options.ChannelOptions = (IMqttClientChannelOptions)_tcpOptions ?? _webSocketOptions;
|
||||
|
||||
return _options;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
namespace MQTTnet.Client
|
||||
{
|
||||
public class MqttClientTcpOptions : IMqttClientChannelOptions
|
||||
{
|
||||
public string Server { get; set; }
|
||||
|
||||
public int? Port { get; set; }
|
||||
|
||||
public int BufferSize { get; set; } = 4096;
|
||||
|
||||
public MqttClientTlsOptions TlsOptions { get; set; } = new MqttClientTlsOptions();
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Server + ":" + this.GetPort();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
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; }
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
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; } = new List<string> { "mqtt" };
|
||||
|
||||
public CookieContainer CookieContainer { get; set; }
|
||||
|
||||
public MqttClientTlsOptions TlsOptions { get; set; } = new MqttClientTlsOptions();
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Uri;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Packets;
|
||||
|
||||
namespace MQTTnet.Client
|
||||
{
|
||||
public class MqttPacketDispatcher
|
||||
{
|
||||
private readonly ConcurrentDictionary<Tuple<ushort, Type>, TaskCompletionSource<MqttBasePacket>> _awaiters = new ConcurrentDictionary<Tuple<ushort, Type>, TaskCompletionSource<MqttBasePacket>>();
|
||||
|
||||
public void Dispatch(Exception exception)
|
||||
{
|
||||
foreach (var awaiter in _awaiters)
|
||||
{
|
||||
Task.Run(() => awaiter.Value.TrySetException(exception)); // Task.Run fixes a dead lock. Without this the client only receives one message.
|
||||
}
|
||||
|
||||
_awaiters.Clear();
|
||||
}
|
||||
|
||||
public void Dispatch(MqttBasePacket packet)
|
||||
{
|
||||
if (packet == null) throw new ArgumentNullException(nameof(packet));
|
||||
|
||||
ushort identifier = 0;
|
||||
if (packet is IMqttPacketWithIdentifier packetWithIdentifier && packetWithIdentifier.PacketIdentifier.HasValue)
|
||||
{
|
||||
identifier = packetWithIdentifier.PacketIdentifier.Value;
|
||||
}
|
||||
|
||||
var type = packet.GetType();
|
||||
var key = new Tuple<ushort, Type>(identifier, type);
|
||||
|
||||
if (_awaiters.TryRemove(key, out var awaiter))
|
||||
{
|
||||
Task.Run(() => awaiter.TrySetResult(packet)); // Task.Run fixes a dead lock. Without this the client only receives one message.
|
||||
return;
|
||||
}
|
||||
|
||||
throw new InvalidOperationException($"Packet of type '{type.Name}' not handled or dispatched.");
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
_awaiters.Clear();
|
||||
}
|
||||
|
||||
public TaskCompletionSource<MqttBasePacket> AddPacketAwaiter<TResponsePacket>(ushort? identifier) where TResponsePacket : MqttBasePacket
|
||||
{
|
||||
var tcs = new TaskCompletionSource<MqttBasePacket>();
|
||||
|
||||
if (!identifier.HasValue)
|
||||
{
|
||||
identifier = 0;
|
||||
}
|
||||
|
||||
var key = new Tuple<ushort, Type>(identifier ?? 0, typeof(TResponsePacket));
|
||||
if (!_awaiters.TryAdd(key, tcs))
|
||||
{
|
||||
throw new InvalidOperationException($"The packet dispatcher already has an awaiter for packet of type '{key.Item2.Name}' with identifier {key.Item1}.");
|
||||
}
|
||||
|
||||
return tcs;
|
||||
}
|
||||
|
||||
public void RemovePacketAwaiter<TResponsePacket>(ushort? identifier) where TResponsePacket : MqttBasePacket
|
||||
{
|
||||
if (!identifier.HasValue)
|
||||
{
|
||||
identifier = 0;
|
||||
}
|
||||
|
||||
var key = new Tuple<ushort, Type>(identifier ?? 0, typeof(TResponsePacket));
|
||||
_awaiters.TryRemove(key, out _);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
namespace MQTTnet.Client
|
||||
{
|
||||
public class MqttPacketIdentifierProvider
|
||||
{
|
||||
private readonly object _syncRoot = new object();
|
||||
private ushort _value;
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
lock (_syncRoot)
|
||||
{
|
||||
_value = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public ushort GetNewPacketIdentifier()
|
||||
{
|
||||
lock (_syncRoot)
|
||||
{
|
||||
_value++;
|
||||
|
||||
if (_value == 0)
|
||||
{
|
||||
// As per official MQTT documentation the package identifier should never be 0.
|
||||
_value = 1;
|
||||
}
|
||||
|
||||
return _value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
using MQTTnet.Protocol;
|
||||
|
||||
namespace MQTTnet.Client
|
||||
{
|
||||
public class MqttSubscribeResult
|
||||
{
|
||||
public MqttSubscribeResult(TopicFilter topicFilter, MqttSubscribeReturnCode returnCode)
|
||||
{
|
||||
TopicFilter = topicFilter;
|
||||
ReturnCode = returnCode;
|
||||
}
|
||||
|
||||
public TopicFilter TopicFilter { get; }
|
||||
|
||||
public MqttSubscribeReturnCode ReturnCode { get; }
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace MQTTnet.Diagnostics
|
||||
{
|
||||
public interface IMqttNetChildLogger
|
||||
{
|
||||
IMqttNetChildLogger CreateChildLogger(string source = null);
|
||||
|
||||
void Verbose(string message, params object[] parameters);
|
||||
|
||||
void Info(string message, params object[] parameters);
|
||||
|
||||
void Warning(Exception exception, string message, params object[] parameters);
|
||||
|
||||
void Error(Exception exception, string message, params object[] parameters);
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace MQTTnet.Diagnostics
|
||||
{
|
||||
public interface IMqttNetLogger
|
||||
{
|
||||
event EventHandler<MqttNetLogMessagePublishedEventArgs> LogMessagePublished;
|
||||
|
||||
IMqttNetChildLogger CreateChildLogger(string source = null);
|
||||
|
||||
void Publish(MqttNetLogLevel logLevel, string source, string message, object[] parameters, Exception exception);
|
||||
}
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace MQTTnet.Diagnostics
|
||||
{
|
||||
public class MqttNetChildLogger : IMqttNetChildLogger
|
||||
{
|
||||
private readonly IMqttNetLogger _logger;
|
||||
private readonly string _source;
|
||||
|
||||
public MqttNetChildLogger(IMqttNetLogger logger, string source)
|
||||
{
|
||||
_logger = logger;
|
||||
_source = source;
|
||||
}
|
||||
|
||||
public IMqttNetChildLogger CreateChildLogger(string source)
|
||||
{
|
||||
string childSource;
|
||||
if (!string.IsNullOrEmpty(_source))
|
||||
{
|
||||
childSource = _source + "." + source;
|
||||
}
|
||||
else
|
||||
{
|
||||
childSource = source;
|
||||
}
|
||||
|
||||
return new MqttNetChildLogger(_logger, childSource);
|
||||
}
|
||||
|
||||
public void Verbose(string message, params object[] parameters)
|
||||
{
|
||||
_logger.Publish(MqttNetLogLevel.Verbose, _source, message, parameters, null);
|
||||
}
|
||||
|
||||
public void Info(string message, params object[] parameters)
|
||||
{
|
||||
_logger.Publish(MqttNetLogLevel.Info, _source, message, parameters, null);
|
||||
}
|
||||
|
||||
public void Warning(Exception exception, string message, params object[] parameters)
|
||||
{
|
||||
_logger.Publish(MqttNetLogLevel.Warning, _source, message, parameters, exception);
|
||||
}
|
||||
|
||||
public void Error(Exception exception, string message, params object[] parameters)
|
||||
{
|
||||
_logger.Publish(MqttNetLogLevel.Error, _source, message, parameters, exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
namespace MQTTnet.Diagnostics
|
||||
{
|
||||
public enum MqttNetLogLevel
|
||||
{
|
||||
Verbose,
|
||||
|
||||
Info,
|
||||
|
||||
Warning,
|
||||
|
||||
Error
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace MQTTnet.Diagnostics
|
||||
{
|
||||
public 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; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
var result = $"[{Timestamp:O}] [{LogId}] [{ThreadId}] [{Source}] [{Level}]: {Message}";
|
||||
if (Exception != null)
|
||||
{
|
||||
result += Environment.NewLine + Exception;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace MQTTnet.Diagnostics
|
||||
{
|
||||
public class MqttNetLogMessagePublishedEventArgs : EventArgs
|
||||
{
|
||||
public MqttNetLogMessagePublishedEventArgs(MqttNetLogMessage logMessage)
|
||||
{
|
||||
TraceMessage = logMessage ?? throw new ArgumentNullException(nameof(logMessage));
|
||||
}
|
||||
|
||||
public MqttNetLogMessage TraceMessage { get; }
|
||||
}
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
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 IMqttNetChildLogger CreateChildLogger(string source = null)
|
||||
{
|
||||
return new MqttNetChildLogger(this, source);
|
||||
}
|
||||
|
||||
public void Publish(MqttNetLogLevel logLevel, string source, string message, object[] parameters, Exception exception)
|
||||
{
|
||||
var hasLocalListeners = LogMessagePublished != null;
|
||||
var hasGlobalListeners = MqttNetGlobalLogger.HasListeners;
|
||||
|
||||
if (!hasLocalListeners && !hasGlobalListeners)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (parameters?.Length > 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
message = string.Format(message, parameters);
|
||||
}
|
||||
catch
|
||||
{
|
||||
message = "MESSAGE FORMAT INVALID: " + message;
|
||||
}
|
||||
}
|
||||
|
||||
var traceMessage = new MqttNetLogMessage(_logId, DateTime.UtcNow, Environment.CurrentManagedThreadId, source, logLevel, message, exception);
|
||||
|
||||
if (hasGlobalListeners)
|
||||
{
|
||||
MqttNetGlobalLogger.Publish(traceMessage);
|
||||
}
|
||||
|
||||
if (hasLocalListeners)
|
||||
{
|
||||
LogMessagePublished?.Invoke(this, new MqttNetLogMessagePublishedEventArgs(traceMessage));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
namespace MQTTnet.Diagnostics
|
||||
{
|
||||
public static class TargetFrameworkInfoProvider
|
||||
{
|
||||
public static string TargetFramework
|
||||
{
|
||||
get
|
||||
{
|
||||
#if NET452
|
||||
return "net452";
|
||||
#elif NET461
|
||||
return "net461";
|
||||
#elif NETSTANDARD1_3
|
||||
return "netstandard1.3";
|
||||
#elif NETSTANDARD2_0
|
||||
return "netstandard2.0";
|
||||
#elif WINDOWS_UWP
|
||||
return "uap10.0";
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace MQTTnet.Exceptions
|
||||
{
|
||||
public class MqttCommunicationTimedOutException : MqttCommunicationException
|
||||
{
|
||||
public MqttCommunicationTimedOutException()
|
||||
{
|
||||
}
|
||||
|
||||
public MqttCommunicationTimedOutException(Exception innerException) : base(innerException)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace MQTTnet.Exceptions
|
||||
{
|
||||
public class MqttProtocolViolationException : Exception
|
||||
{
|
||||
public MqttProtocolViolationException(string message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MQTTnet
|
||||
{
|
||||
public interface IApplicationMessagePublisher
|
||||
{
|
||||
Task PublishAsync(IEnumerable<MqttApplicationMessage> applicationMessages);
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace MQTTnet
|
||||
{
|
||||
public interface IApplicationMessageReceiver
|
||||
{
|
||||
event EventHandler<MqttApplicationMessageReceivedEventArgs> ApplicationMessageReceived;
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
using System;
|
||||
using MQTTnet.Adapter;
|
||||
using MQTTnet.Client;
|
||||
using MQTTnet.Diagnostics;
|
||||
using MQTTnet.Serializer;
|
||||
|
||||
namespace MQTTnet.Implementations
|
||||
{
|
||||
public class MqttClientAdapterFactory : IMqttClientAdapterFactory
|
||||
{
|
||||
public IMqttChannelAdapter CreateClientAdapter(IMqttClientOptions options, IMqttNetChildLogger logger)
|
||||
{
|
||||
if (options == null) throw new ArgumentNullException(nameof(options));
|
||||
|
||||
var serializer = new MqttPacketSerializer { ProtocolVersion = options.ProtocolVersion };
|
||||
|
||||
switch (options.ChannelOptions)
|
||||
{
|
||||
case MqttClientTcpOptions tcpOptions:
|
||||
{
|
||||
return new MqttChannelAdapter(new MqttTcpChannel(tcpOptions), serializer, logger);
|
||||
}
|
||||
|
||||
case MqttClientWebSocketOptions webSocketOptions:
|
||||
{
|
||||
return new MqttChannelAdapter(new MqttWebSocketChannel(webSocketOptions), serializer, logger);
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,169 +0,0 @@
|
||||
#if WINDOWS_UWP
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices.WindowsRuntime;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.Networking;
|
||||
using Windows.Networking.Sockets;
|
||||
using Windows.Security.Cryptography.Certificates;
|
||||
using MQTTnet.Channel;
|
||||
using MQTTnet.Client;
|
||||
|
||||
namespace MQTTnet.Implementations
|
||||
{
|
||||
public class MqttTcpChannel : IMqttChannel
|
||||
{
|
||||
// ReSharper disable once MemberCanBePrivate.Global
|
||||
// ReSharper disable once AutoPropertyCanBeMadeGetOnly.Global
|
||||
public static int BufferSize { get; set; } = 4096; // Can be changed for fine tuning by library user.
|
||||
|
||||
private readonly int _bufferSize = BufferSize;
|
||||
private readonly MqttClientTcpOptions _options;
|
||||
|
||||
private StreamSocket _socket;
|
||||
private Stream _readStream;
|
||||
private Stream _writeStream;
|
||||
|
||||
public MqttTcpChannel(MqttClientTcpOptions options)
|
||||
{
|
||||
_options = options ?? throw new ArgumentNullException(nameof(options));
|
||||
|
||||
_bufferSize = options.BufferSize;
|
||||
}
|
||||
|
||||
public MqttTcpChannel(StreamSocket socket)
|
||||
{
|
||||
_socket = socket ?? throw new ArgumentNullException(nameof(socket));
|
||||
|
||||
CreateStreams();
|
||||
}
|
||||
|
||||
public static Func<MqttClientTcpOptions, IEnumerable<ChainValidationResult>> CustomIgnorableServerCertificateErrorsResolver { get; set; }
|
||||
|
||||
public string Endpoint => _socket?.Information?.RemoteAddress?.ToString(); // TODO: Check if contains also the port.
|
||||
|
||||
public async Task ConnectAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
if (_socket == null)
|
||||
{
|
||||
_socket = new StreamSocket();
|
||||
_socket.Control.NoDelay = true;
|
||||
_socket.Control.KeepAlive = true;
|
||||
}
|
||||
|
||||
if (!_options.TlsOptions.UseTls)
|
||||
{
|
||||
await _socket.ConnectAsync(new HostName(_options.Server), _options.GetPort().ToString());
|
||||
}
|
||||
else
|
||||
{
|
||||
_socket.Control.ClientCertificate = LoadCertificate(_options);
|
||||
|
||||
foreach (var ignorableChainValidationResult in ResolveIgnorableServerCertificateErrors())
|
||||
{
|
||||
_socket.Control.IgnorableServerCertificateErrors.Add(ignorableChainValidationResult);
|
||||
}
|
||||
|
||||
await _socket.ConnectAsync(new HostName(_options.Server), _options.GetPort().ToString(), SocketProtectionLevel.Tls12);
|
||||
}
|
||||
|
||||
CreateStreams();
|
||||
}
|
||||
|
||||
public Task DisconnectAsync()
|
||||
{
|
||||
Dispose();
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
||||
{
|
||||
return _readStream.ReadAsync(buffer, offset, count, cancellationToken);
|
||||
}
|
||||
|
||||
public async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
||||
{
|
||||
await _writeStream.WriteAsync(buffer, offset, count, cancellationToken);
|
||||
await _writeStream.FlushAsync(cancellationToken);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
TryDispose(_readStream, () => _readStream = null);
|
||||
TryDispose(_writeStream, () => _writeStream = null);
|
||||
TryDispose(_socket, () => _socket = null);
|
||||
}
|
||||
|
||||
private static Certificate LoadCertificate(MqttClientTcpOptions options)
|
||||
{
|
||||
if (options.TlsOptions.Certificates == null || !options.TlsOptions.Certificates.Any())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (options.TlsOptions.Certificates.Count > 1)
|
||||
{
|
||||
throw new NotSupportedException("Only one client certificate is supported for UWP.");
|
||||
}
|
||||
|
||||
return new Certificate(options.TlsOptions.Certificates.First().AsBuffer());
|
||||
}
|
||||
|
||||
private IEnumerable<ChainValidationResult> ResolveIgnorableServerCertificateErrors()
|
||||
{
|
||||
if (CustomIgnorableServerCertificateErrorsResolver != null)
|
||||
{
|
||||
return CustomIgnorableServerCertificateErrorsResolver(_options);
|
||||
}
|
||||
|
||||
var result = new List<ChainValidationResult>();
|
||||
|
||||
if (_options.TlsOptions.IgnoreCertificateRevocationErrors)
|
||||
{
|
||||
result.Add(ChainValidationResult.RevocationInformationMissing);
|
||||
//_socket.Control.IgnorableServerCertificateErrors.Add(ChainValidationResult.Revoked); Not supported.
|
||||
result.Add(ChainValidationResult.RevocationFailure);
|
||||
}
|
||||
|
||||
if (_options.TlsOptions.IgnoreCertificateChainErrors)
|
||||
{
|
||||
result.Add(ChainValidationResult.IncompleteChain);
|
||||
}
|
||||
|
||||
if (_options.TlsOptions.AllowUntrustedCertificates)
|
||||
{
|
||||
result.Add(ChainValidationResult.Untrusted);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private void CreateStreams()
|
||||
{
|
||||
_readStream = _socket.InputStream.AsStreamForRead(_bufferSize);
|
||||
_writeStream = _socket.OutputStream.AsStreamForWrite(_bufferSize);
|
||||
}
|
||||
|
||||
private static void TryDispose(IDisposable disposable, Action afterDispose)
|
||||
{
|
||||
try
|
||||
{
|
||||
disposable?.Dispose();
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
}
|
||||
catch (NullReferenceException)
|
||||
{
|
||||
}
|
||||
finally
|
||||
{
|
||||
afterDispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -1,169 +0,0 @@
|
||||
#if NET452 || NET461 || NETSTANDARD1_3 || NETSTANDARD2_0
|
||||
using System;
|
||||
using System.Net.Security;
|
||||
using System.Net.Sockets;
|
||||
using System.Security.Authentication;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Threading.Tasks;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using MQTTnet.Channel;
|
||||
using MQTTnet.Client;
|
||||
|
||||
namespace MQTTnet.Implementations
|
||||
{
|
||||
public class MqttTcpChannel : IMqttChannel
|
||||
{
|
||||
private readonly MqttClientTcpOptions _options;
|
||||
|
||||
private Socket _socket;
|
||||
private Stream _stream;
|
||||
|
||||
/// <summary>
|
||||
/// called on client sockets are created in connect
|
||||
/// </summary>
|
||||
public MqttTcpChannel(MqttClientTcpOptions options)
|
||||
{
|
||||
_options = options ?? throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// called on server, sockets are passed in
|
||||
/// connect will not be called
|
||||
/// </summary>
|
||||
public MqttTcpChannel(Socket socket, SslStream sslStream)
|
||||
{
|
||||
_socket = socket ?? throw new ArgumentNullException(nameof(socket));
|
||||
|
||||
CreateStream(sslStream);
|
||||
}
|
||||
|
||||
public static Func<X509Certificate, X509Chain, SslPolicyErrors, MqttClientTcpOptions, bool> CustomCertificateValidationCallback { get; set; }
|
||||
|
||||
public string Endpoint => _socket?.RemoteEndPoint?.ToString();
|
||||
|
||||
public async Task ConnectAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
if (_socket == null)
|
||||
{
|
||||
_socket = new Socket(SocketType.Stream, ProtocolType.Tcp) { NoDelay = true };
|
||||
}
|
||||
|
||||
#if NET452 || NET461
|
||||
await Task.Factory.FromAsync(_socket.BeginConnect, _socket.EndConnect, _options.Server, _options.GetPort(), null).ConfigureAwait(false);
|
||||
#else
|
||||
await _socket.ConnectAsync(_options.Server, _options.GetPort()).ConfigureAwait(false);
|
||||
#endif
|
||||
|
||||
SslStream sslStream = null;
|
||||
if (_options.TlsOptions.UseTls)
|
||||
{
|
||||
sslStream = new SslStream(new NetworkStream(_socket, true), false, InternalUserCertificateValidationCallback);
|
||||
await sslStream.AuthenticateAsClientAsync(_options.Server, LoadCertificates(), SslProtocols.Tls12, _options.TlsOptions.IgnoreCertificateRevocationErrors).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
CreateStream(sslStream);
|
||||
}
|
||||
|
||||
public Task DisconnectAsync()
|
||||
{
|
||||
Dispose();
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
||||
{
|
||||
return _stream.ReadAsync(buffer, offset, count, cancellationToken);
|
||||
}
|
||||
|
||||
public Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
||||
{
|
||||
return _stream.WriteAsync(buffer, offset, count, cancellationToken);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
TryDispose(_stream, () => _stream = null);
|
||||
TryDispose(_socket, () => _socket = null);
|
||||
}
|
||||
|
||||
private bool InternalUserCertificateValidationCallback(object sender, X509Certificate x509Certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
|
||||
{
|
||||
if (CustomCertificateValidationCallback != null)
|
||||
{
|
||||
return CustomCertificateValidationCallback(x509Certificate, chain, sslPolicyErrors, _options);
|
||||
}
|
||||
|
||||
if (sslPolicyErrors == SslPolicyErrors.None)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (chain.ChainStatus.Any(c => c.Status == X509ChainStatusFlags.RevocationStatusUnknown || c.Status == X509ChainStatusFlags.Revoked || c.Status == X509ChainStatusFlags.RevocationStatusUnknown))
|
||||
{
|
||||
if (!_options.TlsOptions.IgnoreCertificateRevocationErrors)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (chain.ChainStatus.Any(c => c.Status == X509ChainStatusFlags.PartialChain))
|
||||
{
|
||||
if (!_options.TlsOptions.IgnoreCertificateChainErrors)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return _options.TlsOptions.AllowUntrustedCertificates;
|
||||
}
|
||||
|
||||
private X509CertificateCollection LoadCertificates()
|
||||
{
|
||||
var certificates = new X509CertificateCollection();
|
||||
if (_options.TlsOptions.Certificates == null)
|
||||
{
|
||||
return certificates;
|
||||
}
|
||||
|
||||
foreach (var certificate in _options.TlsOptions.Certificates)
|
||||
{
|
||||
certificates.Add(new X509Certificate2(certificate));
|
||||
}
|
||||
|
||||
return certificates;
|
||||
}
|
||||
|
||||
private void CreateStream(Stream stream)
|
||||
{
|
||||
if (stream != null)
|
||||
{
|
||||
_stream = stream;
|
||||
}
|
||||
else
|
||||
{
|
||||
_stream = new NetworkStream(_socket, true);
|
||||
}
|
||||
}
|
||||
|
||||
private static void TryDispose(IDisposable disposable, Action afterDispose)
|
||||
{
|
||||
try
|
||||
{
|
||||
disposable?.Dispose();
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
}
|
||||
catch (NullReferenceException)
|
||||
{
|
||||
}
|
||||
finally
|
||||
{
|
||||
afterDispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -1,84 +0,0 @@
|
||||
#if WINDOWS_UWP
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.Networking.Sockets;
|
||||
using MQTTnet.Adapter;
|
||||
using MQTTnet.Diagnostics;
|
||||
using MQTTnet.Serializer;
|
||||
using MQTTnet.Server;
|
||||
|
||||
namespace MQTTnet.Implementations
|
||||
{
|
||||
public class MqttTcpServerAdapter : IMqttServerAdapter
|
||||
{
|
||||
private readonly IMqttNetChildLogger _logger;
|
||||
private StreamSocketListener _defaultEndpointSocket;
|
||||
|
||||
public MqttTcpServerAdapter(IMqttNetChildLogger logger)
|
||||
{
|
||||
if (logger == null) throw new ArgumentNullException(nameof(logger));
|
||||
|
||||
_logger = logger.CreateChildLogger(nameof(MqttTcpServerAdapter));
|
||||
}
|
||||
|
||||
public event EventHandler<MqttServerAdapterClientAcceptedEventArgs> ClientAccepted;
|
||||
|
||||
public async Task StartAsync(IMqttServerOptions options)
|
||||
{
|
||||
if (options == null) throw new ArgumentNullException(nameof(options));
|
||||
|
||||
if (_defaultEndpointSocket != null) throw new InvalidOperationException("Server is already started.");
|
||||
|
||||
if (options.DefaultEndpointOptions.IsEnabled)
|
||||
{
|
||||
_defaultEndpointSocket = new StreamSocketListener();
|
||||
|
||||
// This also affects the client sockets.
|
||||
_defaultEndpointSocket.Control.NoDelay = true;
|
||||
_defaultEndpointSocket.Control.KeepAlive = true;
|
||||
_defaultEndpointSocket.Control.QualityOfService = SocketQualityOfService.LowLatency;
|
||||
_defaultEndpointSocket.ConnectionReceived += AcceptDefaultEndpointConnectionsAsync;
|
||||
|
||||
await _defaultEndpointSocket.BindServiceNameAsync(options.DefaultEndpointOptions.Port.ToString(), SocketProtectionLevel.PlainSocket);
|
||||
|
||||
}
|
||||
|
||||
if (options.TlsEndpointOptions.IsEnabled)
|
||||
{
|
||||
throw new NotSupportedException("TLS servers are not supported for UWP apps.");
|
||||
}
|
||||
}
|
||||
|
||||
public Task StopAsync()
|
||||
{
|
||||
if (_defaultEndpointSocket != null)
|
||||
{
|
||||
_defaultEndpointSocket.ConnectionReceived -= AcceptDefaultEndpointConnectionsAsync;
|
||||
}
|
||||
|
||||
_defaultEndpointSocket?.Dispose();
|
||||
_defaultEndpointSocket = null;
|
||||
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
StopAsync().GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
private void AcceptDefaultEndpointConnectionsAsync(StreamSocketListener sender, StreamSocketListenerConnectionReceivedEventArgs args)
|
||||
{
|
||||
try
|
||||
{
|
||||
var clientAdapter = new MqttChannelAdapter(new MqttTcpChannel(args.Socket), new MqttPacketSerializer(), _logger);
|
||||
ClientAccepted?.Invoke(this, new MqttServerAdapterClientAcceptedEventArgs(clientAdapter));
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
_logger.Error(exception, "Error while accepting connection at default endpoint.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -1,111 +0,0 @@
|
||||
#if NET452 || NET461 || NETSTANDARD1_3 || NETSTANDARD2_0
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Sockets;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Adapter;
|
||||
using MQTTnet.Diagnostics;
|
||||
using MQTTnet.Server;
|
||||
|
||||
namespace MQTTnet.Implementations
|
||||
{
|
||||
public class MqttTcpServerAdapter : IMqttServerAdapter
|
||||
{
|
||||
private readonly List<MqttTcpServerListener> _listeners = new List<MqttTcpServerListener>();
|
||||
private readonly IMqttNetChildLogger _logger;
|
||||
|
||||
private CancellationTokenSource _cancellationTokenSource;
|
||||
|
||||
public MqttTcpServerAdapter(IMqttNetChildLogger logger)
|
||||
{
|
||||
if (logger == null) throw new ArgumentNullException(nameof(logger));
|
||||
|
||||
_logger = logger.CreateChildLogger(nameof(MqttTcpServerAdapter));
|
||||
}
|
||||
|
||||
public event EventHandler<MqttServerAdapterClientAcceptedEventArgs> ClientAccepted;
|
||||
|
||||
public Task StartAsync(IMqttServerOptions options)
|
||||
{
|
||||
if (_cancellationTokenSource != null) throw new InvalidOperationException("Server is already started.");
|
||||
|
||||
_cancellationTokenSource = new CancellationTokenSource();
|
||||
|
||||
if (options.DefaultEndpointOptions.IsEnabled)
|
||||
{
|
||||
RegisterListeners(options.DefaultEndpointOptions, null);
|
||||
}
|
||||
|
||||
if (options.TlsEndpointOptions.IsEnabled)
|
||||
{
|
||||
if (options.TlsEndpointOptions.Certificate == null)
|
||||
{
|
||||
throw new ArgumentException("TLS certificate is not set.");
|
||||
}
|
||||
|
||||
var tlsCertificate = new X509Certificate2(options.TlsEndpointOptions.Certificate);
|
||||
if (!tlsCertificate.HasPrivateKey)
|
||||
{
|
||||
throw new InvalidOperationException("The certificate for TLS encryption must contain the private key.");
|
||||
}
|
||||
|
||||
RegisterListeners(options.TlsEndpointOptions, tlsCertificate);
|
||||
}
|
||||
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public Task StopAsync()
|
||||
{
|
||||
Dispose();
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_cancellationTokenSource?.Cancel(false);
|
||||
_cancellationTokenSource?.Dispose();
|
||||
_cancellationTokenSource = null;
|
||||
|
||||
foreach (var listener in _listeners)
|
||||
{
|
||||
listener.Dispose();
|
||||
}
|
||||
|
||||
_listeners.Clear();
|
||||
}
|
||||
|
||||
private void RegisterListeners(MqttServerTcpEndpointBaseOptions options, X509Certificate2 tlsCertificate)
|
||||
{
|
||||
var listenerV4 = new MqttTcpServerListener(
|
||||
AddressFamily.InterNetwork,
|
||||
options,
|
||||
tlsCertificate,
|
||||
_cancellationTokenSource.Token,
|
||||
_logger);
|
||||
|
||||
listenerV4.ClientAccepted += OnClientAccepted;
|
||||
listenerV4.Start();
|
||||
_listeners.Add(listenerV4);
|
||||
|
||||
var listenerV6 = new MqttTcpServerListener(
|
||||
AddressFamily.InterNetworkV6,
|
||||
options,
|
||||
tlsCertificate,
|
||||
_cancellationTokenSource.Token,
|
||||
_logger);
|
||||
|
||||
listenerV6.ClientAccepted += OnClientAccepted;
|
||||
listenerV6.Start();
|
||||
_listeners.Add(listenerV6);
|
||||
}
|
||||
|
||||
private void OnClientAccepted(object sender, MqttServerAdapterClientAcceptedEventArgs e)
|
||||
{
|
||||
ClientAccepted?.Invoke(this, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -1,109 +0,0 @@
|
||||
#if NET452 || NET461 || NETSTANDARD1_3 || NETSTANDARD2_0
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Net.Security;
|
||||
using System.Net.Sockets;
|
||||
using System.Security.Authentication;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Adapter;
|
||||
using MQTTnet.Diagnostics;
|
||||
using MQTTnet.Serializer;
|
||||
using MQTTnet.Server;
|
||||
|
||||
namespace MQTTnet.Implementations
|
||||
{
|
||||
public class MqttTcpServerListener : IDisposable
|
||||
{
|
||||
private readonly IMqttNetChildLogger _logger;
|
||||
private readonly CancellationToken _cancellationToken;
|
||||
private readonly AddressFamily _addressFamily;
|
||||
private readonly MqttServerTcpEndpointBaseOptions _options;
|
||||
private readonly X509Certificate2 _tlsCertificate;
|
||||
|
||||
private Socket _socket;
|
||||
|
||||
public MqttTcpServerListener(
|
||||
AddressFamily addressFamily,
|
||||
MqttServerTcpEndpointBaseOptions options,
|
||||
X509Certificate2 tlsCertificate,
|
||||
CancellationToken cancellationToken,
|
||||
IMqttNetChildLogger logger)
|
||||
{
|
||||
_addressFamily = addressFamily;
|
||||
_options = options;
|
||||
_tlsCertificate = tlsCertificate;
|
||||
_cancellationToken = cancellationToken;
|
||||
_logger = logger.CreateChildLogger(nameof(MqttTcpServerListener));
|
||||
}
|
||||
|
||||
public event EventHandler<MqttServerAdapterClientAcceptedEventArgs> ClientAccepted;
|
||||
|
||||
public void Start()
|
||||
{
|
||||
var boundIp = _options.BoundInterNetworkAddress;
|
||||
if (_addressFamily == AddressFamily.InterNetworkV6)
|
||||
{
|
||||
boundIp = _options.BoundInterNetworkV6Address;
|
||||
}
|
||||
|
||||
_socket = new Socket(_addressFamily, SocketType.Stream, ProtocolType.Tcp);
|
||||
_socket.Bind(new IPEndPoint(boundIp, _options.Port));
|
||||
|
||||
_logger.Info($"Starting TCP listener for {_socket.LocalEndPoint} TLS={_tlsCertificate != null}.");
|
||||
|
||||
_socket.Listen(_options.ConnectionBacklog);
|
||||
Task.Run(AcceptClientConnectionsAsync, _cancellationToken);
|
||||
}
|
||||
|
||||
private async Task AcceptClientConnectionsAsync()
|
||||
{
|
||||
while (!_cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
#if NET452 || NET461
|
||||
var clientSocket = await Task.Factory.FromAsync(_socket.BeginAccept, _socket.EndAccept, null).ConfigureAwait(false);
|
||||
#else
|
||||
var clientSocket = await _socket.AcceptAsync().ConfigureAwait(false);
|
||||
#endif
|
||||
clientSocket.NoDelay = true;
|
||||
|
||||
if (_tlsCertificate != null)
|
||||
{
|
||||
var sslStream = new SslStream(new NetworkStream(clientSocket), false);
|
||||
await sslStream.AuthenticateAsServerAsync(_tlsCertificate, false, SslProtocols.Tls12, false).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
var clientAdapter = new MqttChannelAdapter(new MqttTcpChannel(clientSocket, null), new MqttPacketSerializer(), _logger);
|
||||
ClientAccepted?.Invoke(this, new MqttServerAdapterClientAcceptedEventArgs(clientAdapter));
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
// It can happen that the listener socket is accessed after the cancellation token is already set and the listener socket is disposed.
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
if (exception is SocketException s && s.SocketErrorCode == SocketError.OperationAborted)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_logger.Error(exception, $"Error while accepting connection at TCP listener {_socket.LocalEndPoint} TLS={_tlsCertificate != null}.");
|
||||
await Task.Delay(TimeSpan.FromSeconds(1), _cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_socket?.Dispose();
|
||||
|
||||
#if NETSTANDARD1_3 || NETSTANDARD2_0 || NET461
|
||||
_tlsCertificate?.Dispose();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -1,136 +0,0 @@
|
||||
using System;
|
||||
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
|
||||
{
|
||||
public class MqttWebSocketChannel : IMqttChannel
|
||||
{
|
||||
private readonly SemaphoreSlim _sendLock = new SemaphoreSlim(1, 1);
|
||||
private readonly MqttClientWebSocketOptions _options;
|
||||
|
||||
private WebSocket _webSocket;
|
||||
|
||||
public MqttWebSocketChannel(MqttClientWebSocketOptions options)
|
||||
{
|
||||
_options = options ?? throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
public MqttWebSocketChannel(WebSocket webSocket, string endpoint)
|
||||
{
|
||||
_webSocket = webSocket ?? throw new ArgumentNullException(nameof(webSocket));
|
||||
Endpoint = endpoint;
|
||||
}
|
||||
|
||||
public string Endpoint { get; }
|
||||
|
||||
public async Task ConnectAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
var uri = _options.Uri;
|
||||
if (!uri.StartsWith("ws://", StringComparison.OrdinalIgnoreCase) && !uri.StartsWith("wss://", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (_options.TlsOptions?.UseTls == false)
|
||||
{
|
||||
uri = "ws://" + uri;
|
||||
}
|
||||
else
|
||||
{
|
||||
uri = "wss://" + uri;
|
||||
}
|
||||
}
|
||||
|
||||
var clientWebSocket = new ClientWebSocket();
|
||||
|
||||
if (_options.RequestHeaders != null)
|
||||
{
|
||||
foreach (var requestHeader in _options.RequestHeaders)
|
||||
{
|
||||
clientWebSocket.Options.SetRequestHeader(requestHeader.Key, requestHeader.Value);
|
||||
}
|
||||
}
|
||||
|
||||
if (_options.SubProtocols != null)
|
||||
{
|
||||
foreach (var subProtocol in _options.SubProtocols)
|
||||
{
|
||||
clientWebSocket.Options.AddSubProtocol(subProtocol);
|
||||
}
|
||||
}
|
||||
|
||||
if (_options.CookieContainer != null)
|
||||
{
|
||||
clientWebSocket.Options.Cookies = _options.CookieContainer;
|
||||
}
|
||||
|
||||
if (_options.TlsOptions?.UseTls == true && _options.TlsOptions?.Certificates != null)
|
||||
{
|
||||
clientWebSocket.Options.ClientCertificates = new X509CertificateCollection();
|
||||
foreach (var certificate in _options.TlsOptions.Certificates)
|
||||
{
|
||||
clientWebSocket.Options.ClientCertificates.Add(new X509Certificate(certificate));
|
||||
}
|
||||
}
|
||||
|
||||
await clientWebSocket.ConnectAsync(new Uri(uri), cancellationToken).ConfigureAwait(false);
|
||||
_webSocket = clientWebSocket;
|
||||
}
|
||||
|
||||
public async Task DisconnectAsync()
|
||||
{
|
||||
if (_webSocket == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_webSocket.State == WebSocketState.Open || _webSocket.State == WebSocketState.Connecting)
|
||||
{
|
||||
await _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
Dispose();
|
||||
}
|
||||
|
||||
public async Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
||||
{
|
||||
var response = await _webSocket.ReceiveAsync(new ArraySegment<byte>(buffer, offset, count), cancellationToken).ConfigureAwait(false);
|
||||
return response.Count;
|
||||
}
|
||||
|
||||
public async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
||||
{
|
||||
// This lock is required because the client will throw an exception if _SendAsync_ is
|
||||
// called from multiple threads at the same time. But this issue only happens with several
|
||||
// framework versions.
|
||||
await _sendLock.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
await _webSocket.SendAsync(new ArraySegment<byte>(buffer, offset, count), WebSocketMessageType.Binary, true, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_sendLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_sendLock?.Dispose();
|
||||
|
||||
try
|
||||
{
|
||||
_webSocket?.Dispose();
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
}
|
||||
finally
|
||||
{
|
||||
_webSocket = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,108 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MQTTnet.Internal
|
||||
{
|
||||
// Inspired from Stephen Toub (https://blogs.msdn.microsoft.com/pfxteam/2012/02/11/building-async-coordination-primitives-part-2-asyncautoresetevent/) and Chris Gillum (https://stackoverflow.com/a/43012490)
|
||||
public class AsyncAutoResetEvent
|
||||
{
|
||||
private readonly LinkedList<TaskCompletionSource<bool>> _waiters = new LinkedList<TaskCompletionSource<bool>>();
|
||||
private bool _isSignaled;
|
||||
|
||||
public AsyncAutoResetEvent() : this(false)
|
||||
{ }
|
||||
|
||||
public AsyncAutoResetEvent(bool signaled)
|
||||
{
|
||||
_isSignaled = signaled;
|
||||
}
|
||||
|
||||
public Task<bool> WaitOneAsync()
|
||||
{
|
||||
return WaitOneAsync(CancellationToken.None);
|
||||
}
|
||||
|
||||
public Task<bool> WaitOneAsync(TimeSpan timeout)
|
||||
{
|
||||
return WaitOneAsync(timeout, CancellationToken.None);
|
||||
}
|
||||
|
||||
public Task<bool> WaitOneAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
return WaitOneAsync(Timeout.InfiniteTimeSpan, cancellationToken);
|
||||
}
|
||||
|
||||
public async Task<bool> WaitOneAsync(TimeSpan timeout, CancellationToken cancellationToken)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
TaskCompletionSource<bool> tcs;
|
||||
|
||||
lock (_waiters)
|
||||
{
|
||||
if (_isSignaled)
|
||||
{
|
||||
_isSignaled = false;
|
||||
return true;
|
||||
}
|
||||
else if (timeout == TimeSpan.Zero)
|
||||
{
|
||||
return _isSignaled;
|
||||
}
|
||||
else
|
||||
{
|
||||
tcs = new TaskCompletionSource<bool>();
|
||||
_waiters.AddLast(tcs);
|
||||
}
|
||||
}
|
||||
|
||||
Task winner = await Task.WhenAny(tcs.Task, Task.Delay(timeout, cancellationToken)).ConfigureAwait(false);
|
||||
if (winner == tcs.Task)
|
||||
{
|
||||
// The task was signaled.
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// We timed-out; remove our reference to the task.
|
||||
// This is an O(n) operation since waiters is a LinkedList<T>.
|
||||
lock (_waiters)
|
||||
{
|
||||
_waiters.Remove(tcs);
|
||||
if (winner.Status == TaskStatus.Canceled)
|
||||
{
|
||||
throw new OperationCanceledException(cancellationToken);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new TimeoutException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Set()
|
||||
{
|
||||
TaskCompletionSource<bool> toRelease = null;
|
||||
|
||||
lock (_waiters)
|
||||
{
|
||||
if (_waiters.Count > 0)
|
||||
{
|
||||
// Signal the first task in the waiters list.
|
||||
toRelease = _waiters.First.Value;
|
||||
_waiters.RemoveFirst();
|
||||
}
|
||||
else if (!_isSignaled)
|
||||
{
|
||||
// No tasks are pending
|
||||
_isSignaled = true;
|
||||
}
|
||||
}
|
||||
|
||||
toRelease?.SetResult(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MQTTnet.Internal
|
||||
{
|
||||
// From Stephen Toub (https://blogs.msdn.microsoft.com/pfxteam/2012/02/12/building-async-coordination-primitives-part-6-asynclock/)
|
||||
public class AsyncLock : IDisposable
|
||||
{
|
||||
private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
|
||||
private readonly Task<IDisposable> _releaser;
|
||||
|
||||
public AsyncLock()
|
||||
{
|
||||
_releaser = Task.FromResult((IDisposable)new Releaser(this));
|
||||
}
|
||||
|
||||
public Task<IDisposable> LockAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
Task wait = _semaphore.WaitAsync(cancellationToken);
|
||||
return wait.IsCompleted ?
|
||||
_releaser :
|
||||
wait.ContinueWith((_, state) => (IDisposable)state,
|
||||
_releaser.Result, cancellationToken,
|
||||
TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_semaphore?.Dispose();
|
||||
}
|
||||
|
||||
private class Releaser : IDisposable
|
||||
{
|
||||
private readonly AsyncLock _toRelease;
|
||||
|
||||
internal Releaser(AsyncLock toRelease)
|
||||
{
|
||||
_toRelease = toRelease;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_toRelease._semaphore.Release();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
using MQTTnet.Exceptions;
|
||||
|
||||
namespace MQTTnet.Internal
|
||||
{
|
||||
public static class ExceptionHelper
|
||||
{
|
||||
public static void ThrowGracefulSocketClose()
|
||||
{
|
||||
throw new MqttCommunicationException("Connection gracefully closed from the remote party.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
using MQTTnet.Packets;
|
||||
|
||||
namespace MQTTnet.Internal
|
||||
{
|
||||
public static class MqttApplicationMessageExtensions
|
||||
{
|
||||
public static MqttApplicationMessage ToApplicationMessage(this MqttPublishPacket publishPacket)
|
||||
{
|
||||
return new MqttApplicationMessage
|
||||
{
|
||||
Topic = publishPacket.Topic,
|
||||
Payload = publishPacket.Payload,
|
||||
QualityOfServiceLevel = publishPacket.QualityOfServiceLevel,
|
||||
Retain = publishPacket.Retain
|
||||
};
|
||||
}
|
||||
|
||||
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
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Exceptions;
|
||||
|
||||
namespace MQTTnet.Internal
|
||||
{
|
||||
public static class TaskExtensions
|
||||
{
|
||||
public static async Task TimeoutAfter(Func<CancellationToken, Task> action, TimeSpan timeout, CancellationToken cancellationToken)
|
||||
{
|
||||
if (action == null) throw new ArgumentNullException(nameof(action));
|
||||
|
||||
using (var timeoutCts = new CancellationTokenSource(timeout))
|
||||
using (var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(timeoutCts.Token, cancellationToken))
|
||||
{
|
||||
try
|
||||
{
|
||||
await action(linkedCts.Token).ConfigureAwait(false);
|
||||
}
|
||||
catch (OperationCanceledException exception)
|
||||
{
|
||||
var timeoutReached = timeoutCts.IsCancellationRequested && !cancellationToken.IsCancellationRequested;
|
||||
if (timeoutReached)
|
||||
{
|
||||
throw new MqttCommunicationTimedOutException(exception);
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<TResult> TimeoutAfter<TResult>(Func<CancellationToken, Task<TResult>> action, TimeSpan timeout, CancellationToken cancellationToken)
|
||||
{
|
||||
if (action == null) throw new ArgumentNullException(nameof(action));
|
||||
|
||||
using (var timeoutCts = new CancellationTokenSource(timeout))
|
||||
using (var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(timeoutCts.Token, cancellationToken))
|
||||
{
|
||||
try
|
||||
{
|
||||
return await action(linkedCts.Token).ConfigureAwait(false);
|
||||
}
|
||||
catch (OperationCanceledException exception)
|
||||
{
|
||||
var timeoutReached = timeoutCts.IsCancellationRequested && !cancellationToken.IsCancellationRequested;
|
||||
if (timeoutReached)
|
||||
{
|
||||
throw new MqttCommunicationTimedOutException(exception);
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Channel;
|
||||
|
||||
namespace MQTTnet.Internal
|
||||
{
|
||||
public class TestMqttChannel : IMqttChannel
|
||||
{
|
||||
private readonly MemoryStream _stream;
|
||||
|
||||
public TestMqttChannel(MemoryStream stream)
|
||||
{
|
||||
_stream = stream;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
|
||||
public string Endpoint { get; }
|
||||
|
||||
public Task ConnectAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public Task DisconnectAsync()
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
||||
{
|
||||
return _stream.ReadAsync(buffer, offset, count, cancellationToken);
|
||||
}
|
||||
|
||||
public Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
||||
{
|
||||
return _stream.WriteAsync(buffer, offset, count, cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">netstandard1.3;netstandard2.0;net452;net461;uap10.0</TargetFrameworks>
|
||||
<TargetFrameworks Condition=" '$(OS)' != 'Windows_NT' ">netstandard1.3;netstandard2.0</TargetFrameworks>
|
||||
<AssemblyName>MQTTnet</AssemblyName>
|
||||
<RootNamespace>MQTTnet</RootNamespace>
|
||||
<GeneratePackageOnBuild>False</GeneratePackageOnBuild>
|
||||
<AssemblyVersion>0.0.0.0</AssemblyVersion>
|
||||
<FileVersion>0.0.0.0</FileVersion>
|
||||
<Version>0.0.0.0</Version>
|
||||
<Company />
|
||||
<Product />
|
||||
<Description />
|
||||
<Authors />
|
||||
<PackageId />
|
||||
<SignAssembly>false</SignAssembly>
|
||||
<DelaySign>false</DelaySign>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(TargetFramework)'=='uap10.0'">
|
||||
<CopyLocalLockFileAssemblies>false</CopyLocalLockFileAssemblies>
|
||||
<NugetTargetMoniker>UAP,Version=v10.0</NugetTargetMoniker>
|
||||
<TargetPlatformIdentifier>UAP</TargetPlatformIdentifier>
|
||||
<TargetPlatformVersion>10.0.17134.0</TargetPlatformVersion>
|
||||
<TargetPlatformMinVersion>10.0.10240.0</TargetPlatformMinVersion>
|
||||
<TargetFrameworkIdentifier>.NETCore</TargetFrameworkIdentifier>
|
||||
<TargetFrameworkVersion>v5.0</TargetFrameworkVersion>
|
||||
<DefineConstants>$(DefineConstants);WINDOWS_UWP</DefineConstants>
|
||||
<DefaultLanguage>en</DefaultLanguage>
|
||||
<LanguageTargets>$(MSBuildExtensionsPath)\Microsoft\WindowsXaml\v$(VisualStudioVersion)\Microsoft.Windows.UI.Xaml.CSharp.targets</LanguageTargets>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)'=='Debug'">
|
||||
<DebugType>Full</DebugType>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x86'" />
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" />
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|netstandard1.3|AnyCPU'">
|
||||
<DefineConstants>RELEASE;NETSTANDARD1_3</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)'=='netstandard2.0'">
|
||||
<PackageReference Include="System.Net.Security" Version="4.3.2" />
|
||||
<PackageReference Include="System.Net.WebSockets" Version="4.3.0" />
|
||||
<PackageReference Include="System.Net.WebSockets.Client" Version="4.3.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)'=='netstandard1.3'">
|
||||
<PackageReference Include="System.Net.Security" Version="4.3.2" />
|
||||
<PackageReference Include="System.Net.WebSockets" Version="4.3.0" />
|
||||
<PackageReference Include="System.Net.WebSockets.Client" Version="4.3.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)'=='uap10.0'">
|
||||
<PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform" Version="6.1.4" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)'=='net452'">
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)'=='net461'">
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -1,15 +0,0 @@
|
||||
using MQTTnet.Protocol;
|
||||
|
||||
namespace MQTTnet
|
||||
{
|
||||
public class MqttApplicationMessage
|
||||
{
|
||||
public string Topic { get; set; }
|
||||
|
||||
public byte[] Payload { get; set; }
|
||||
|
||||
public MqttQualityOfServiceLevel QualityOfServiceLevel { get; set; }
|
||||
|
||||
public bool Retain { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,119 +0,0 @@
|
||||
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(Stream payload)
|
||||
{
|
||||
return WithPayload(payload, payload.Length - payload.Position);
|
||||
}
|
||||
|
||||
public MqttApplicationMessageBuilder WithPayload(Stream payload, long length)
|
||||
{
|
||||
if (payload == null)
|
||||
{
|
||||
_payload = null;
|
||||
return this;
|
||||
}
|
||||
|
||||
if (payload.Length == 0)
|
||||
{
|
||||
_payload = new byte[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
_payload = new byte[length];
|
||||
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
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
|
||||
namespace MQTTnet
|
||||
{
|
||||
public static class MqttApplicationMessageExtensions
|
||||
{
|
||||
public static string ConvertPayloadToString(this MqttApplicationMessage applicationMessage)
|
||||
{
|
||||
if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage));
|
||||
|
||||
if (applicationMessage.Payload == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (applicationMessage.Payload.Length == 0)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
return Encoding.UTF8.GetString(applicationMessage.Payload, 0, applicationMessage.Payload.Length);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace MQTTnet
|
||||
{
|
||||
public 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,46 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using MQTTnet.Adapter;
|
||||
using MQTTnet.Client;
|
||||
using MQTTnet.Diagnostics;
|
||||
using MQTTnet.Implementations;
|
||||
using MQTTnet.Server;
|
||||
|
||||
namespace MQTTnet
|
||||
{
|
||||
public class MqttFactory : IMqttClientFactory, IMqttServerFactory
|
||||
{
|
||||
public IMqttClient CreateMqttClient()
|
||||
{
|
||||
return CreateMqttClient(new MqttNetLogger());
|
||||
}
|
||||
|
||||
public IMqttClient CreateMqttClient(IMqttNetLogger logger)
|
||||
{
|
||||
if (logger == null) throw new ArgumentNullException(nameof(logger));
|
||||
|
||||
return new MqttClient(new MqttClientAdapterFactory(), logger);
|
||||
}
|
||||
|
||||
public IMqttServer CreateMqttServer()
|
||||
{
|
||||
var logger = new MqttNetLogger();
|
||||
return CreateMqttServer(logger);
|
||||
}
|
||||
|
||||
public IMqttServer CreateMqttServer(IMqttNetLogger logger)
|
||||
{
|
||||
if (logger == null) throw new ArgumentNullException(nameof(logger));
|
||||
|
||||
return CreateMqttServer(new List<IMqttServerAdapter> { new MqttTcpServerAdapter(logger.CreateChildLogger()) }, logger);
|
||||
}
|
||||
|
||||
public IMqttServer CreateMqttServer(IEnumerable<IMqttServerAdapter> adapters, IMqttNetLogger logger)
|
||||
{
|
||||
if (adapters == null) throw new ArgumentNullException(nameof(adapters));
|
||||
if (logger == null) throw new ArgumentNullException(nameof(logger));
|
||||
|
||||
return new MqttServer(adapters, logger.CreateChildLogger());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
namespace MQTTnet.Packets
|
||||
{
|
||||
public interface IMqttPacketWithIdentifier
|
||||
{
|
||||
ushort? PacketIdentifier { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
namespace MQTTnet.Packets
|
||||
{
|
||||
public abstract class MqttBasePacket
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
namespace MQTTnet.Packets
|
||||
{
|
||||
public class MqttBasePublishPacket : MqttBasePacket, IMqttPacketWithIdentifier
|
||||
{
|
||||
public ushort? PacketIdentifier { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
using MQTTnet.Protocol;
|
||||
|
||||
namespace MQTTnet.Packets
|
||||
{
|
||||
public class MqttConnAckPacket : MqttBasePacket
|
||||
{
|
||||
public bool IsSessionPresent { get; set; }
|
||||
|
||||
public MqttConnectReturnCode ConnectReturnCode { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return "ConnAck: [ConnectReturnCode=" + ConnectReturnCode + "] [IsSessionPresent=" + IsSessionPresent + "]";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
using MQTTnet.Serializer;
|
||||
|
||||
namespace MQTTnet.Packets
|
||||
{
|
||||
public 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 + "]";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
namespace MQTTnet.Packets
|
||||
{
|
||||
public class MqttDisconnectPacket : MqttBasePacket
|
||||
{
|
||||
public override string ToString()
|
||||
{
|
||||
return "Disconnect";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
namespace MQTTnet.Packets
|
||||
{
|
||||
public class MqttPingReqPacket : MqttBasePacket
|
||||
{
|
||||
public override string ToString()
|
||||
{
|
||||
return "PingReq";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
namespace MQTTnet.Packets
|
||||
{
|
||||
public class MqttPingRespPacket : MqttBasePacket
|
||||
{
|
||||
public override string ToString()
|
||||
{
|
||||
return "PingResp";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
namespace MQTTnet.Packets
|
||||
{
|
||||
public class MqttPubAckPacket : MqttBasePublishPacket
|
||||
{
|
||||
public override string ToString()
|
||||
{
|
||||
return "PubAck";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
namespace MQTTnet.Packets
|
||||
{
|
||||
public class MqttPubCompPacket : MqttBasePublishPacket
|
||||
{
|
||||
public override string ToString()
|
||||
{
|
||||
return "PubComp";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
namespace MQTTnet.Packets
|
||||
{
|
||||
public class MqttPubRecPacket : MqttBasePublishPacket
|
||||
{
|
||||
public override string ToString()
|
||||
{
|
||||
return "PubRec";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
namespace MQTTnet.Packets
|
||||
{
|
||||
public class MqttPubRelPacket : MqttBasePublishPacket
|
||||
{
|
||||
public override string ToString()
|
||||
{
|
||||
return "PubRel";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
using MQTTnet.Protocol;
|
||||
|
||||
namespace MQTTnet.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 "Publish: [Topic=" + Topic + "]" +
|
||||
" [Payload.Length=" + Payload?.Length + "]" +
|
||||
" [QoSLevel=" + QualityOfServiceLevel + "]" +
|
||||
" [Dup=" + Dup + "]" +
|
||||
" [Retain=" + Retain + "]" +
|
||||
" [PacketIdentifier=" + PacketIdentifier + "]";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using MQTTnet.Protocol;
|
||||
|
||||
namespace MQTTnet.Packets
|
||||
{
|
||||
public 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 + "]";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace MQTTnet.Packets
|
||||
{
|
||||
public 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 + "]";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
namespace MQTTnet.Packets
|
||||
{
|
||||
public class MqttUnsubAckPacket : MqttBasePacket, IMqttPacketWithIdentifier
|
||||
{
|
||||
public ushort? PacketIdentifier { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return "UnsubAck: [PacketIdentifier=" + PacketIdentifier + "]";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace MQTTnet.Packets
|
||||
{
|
||||
public 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 "Unsubscribe: [PacketIdentifier=" + PacketIdentifier + "] [TopicFilters=" + topicFiltersText + "]";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
namespace MQTTnet.Protocol
|
||||
{
|
||||
public enum MqttConnectReturnCode
|
||||
{
|
||||
ConnectionAccepted = 0x00,
|
||||
ConnectionRefusedUnacceptableProtocolVersion = 0x01,
|
||||
ConnectionRefusedIdentifierRejected = 0x02,
|
||||
ConnectionRefusedServerUnavailable = 0x03,
|
||||
ConnectionRefusedBadUsernameOrPassword = 0x04,
|
||||
ConnectionRefusedNotAuthorized = 0x05
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
namespace MQTTnet.Protocol
|
||||
{
|
||||
public enum MqttQualityOfServiceLevel
|
||||
{
|
||||
AtMostOnce = 0x00,
|
||||
AtLeastOnce = 0x01,
|
||||
ExactlyOnce = 0x02
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
namespace MQTTnet.Protocol
|
||||
{
|
||||
public enum MqttSubscribeReturnCode
|
||||
{
|
||||
SuccessMaximumQoS0 = 0x00,
|
||||
SuccessMaximumQoS1 = 0x01,
|
||||
SuccessMaximumQoS2 = 0x02,
|
||||
Failure = 0x80
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace MQTTnet.Serializer
|
||||
{
|
||||
public 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace MQTTnet.Serializer
|
||||
{
|
||||
public 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++;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace MQTTnet.Serializer
|
||||
{
|
||||
public static class Extensions
|
||||
{
|
||||
public static byte[] ToArray(this ArraySegment<byte> source)
|
||||
{
|
||||
if (source.Array == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var buffer = new byte[source.Count];
|
||||
Buffer.BlockCopy(source.Array, source.Offset, buffer, 0, buffer.Length);
|
||||
|
||||
return buffer;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
using System;
|
||||
using MQTTnet.Adapter;
|
||||
using MQTTnet.Packets;
|
||||
|
||||
namespace MQTTnet.Serializer
|
||||
{
|
||||
public interface IMqttPacketSerializer
|
||||
{
|
||||
MqttProtocolVersion ProtocolVersion { get; set; }
|
||||
|
||||
ArraySegment<byte> Serialize(MqttBasePacket mqttPacket);
|
||||
|
||||
MqttBasePacket Deserialize(ReceivedMqttPacket receivedMqttPacket);
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
namespace MQTTnet.Serializer
|
||||
{
|
||||
public class MqttFixedHeader
|
||||
{
|
||||
public MqttFixedHeader(byte flags, int remainingLength)
|
||||
{
|
||||
Flags = flags;
|
||||
RemainingLength = remainingLength;
|
||||
}
|
||||
|
||||
public byte Flags { get; }
|
||||
|
||||
public int RemainingLength { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
|
||||
namespace MQTTnet.Serializer
|
||||
{
|
||||
public class MqttPacketBodyReader
|
||||
{
|
||||
private readonly byte[] _buffer;
|
||||
private int _offset;
|
||||
|
||||
public MqttPacketBodyReader(byte[] buffer)
|
||||
{
|
||||
_buffer = buffer;
|
||||
}
|
||||
|
||||
public int Length => _buffer.Length - _offset;
|
||||
|
||||
public bool EndOfStream => _offset == _buffer.Length;
|
||||
|
||||
public byte ReadByte()
|
||||
{
|
||||
return _buffer[_offset++];
|
||||
}
|
||||
|
||||
public ArraySegment<byte> ReadRemainingData()
|
||||
{
|
||||
return new ArraySegment<byte>(_buffer, _offset, _buffer.Length - _offset);
|
||||
}
|
||||
|
||||
public ushort ReadUInt16()
|
||||
{
|
||||
var msb = _buffer[_offset++];
|
||||
var lsb = _buffer[_offset++];
|
||||
|
||||
return (ushort)(msb << 8 | lsb);
|
||||
}
|
||||
|
||||
public ArraySegment<byte> ReadWithLengthPrefix()
|
||||
{
|
||||
var length = ReadUInt16();
|
||||
|
||||
var result = new ArraySegment<byte>(_buffer, _offset, length);
|
||||
_offset += length;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public string ReadStringWithLengthPrefix()
|
||||
{
|
||||
var buffer = ReadWithLengthPrefix();
|
||||
return Encoding.UTF8.GetString(buffer.Array, buffer.Offset, buffer.Count);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,79 +0,0 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Channel;
|
||||
using MQTTnet.Exceptions;
|
||||
using MQTTnet.Internal;
|
||||
|
||||
namespace MQTTnet.Serializer
|
||||
{
|
||||
public static class MqttPacketReader
|
||||
{
|
||||
public static async Task<MqttFixedHeader> ReadFixedHeaderAsync(IMqttChannel channel, CancellationToken cancellationToken)
|
||||
{
|
||||
// The MQTT fixed header contains 1 byte of flags and at least 1 byte for the remaining data length.
|
||||
// So in all cases at least 2 bytes must be read for a complete MQTT packet.
|
||||
var buffer = new byte[2];
|
||||
var totalBytesRead = 0;
|
||||
|
||||
while (totalBytesRead < buffer.Length)
|
||||
{
|
||||
var bytesRead = await channel.ReadAsync(buffer, 0, buffer.Length - totalBytesRead, cancellationToken).ConfigureAwait(false);
|
||||
if (bytesRead <= 0)
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
ExceptionHelper.ThrowGracefulSocketClose();
|
||||
}
|
||||
|
||||
totalBytesRead += bytesRead;
|
||||
}
|
||||
|
||||
var hasRemainingLength = buffer[1] != 0;
|
||||
if (!hasRemainingLength)
|
||||
{
|
||||
return new MqttFixedHeader(buffer[0], 0);
|
||||
}
|
||||
|
||||
var bodyLength = await ReadBodyLengthAsync(channel, buffer[1], cancellationToken).ConfigureAwait(false);
|
||||
return new MqttFixedHeader(buffer[0], bodyLength);
|
||||
}
|
||||
|
||||
private static async Task<int> ReadBodyLengthAsync(IMqttChannel channel, byte initialEncodedByte, CancellationToken cancellationToken)
|
||||
{
|
||||
// Alorithm taken from https://docs.oasis-open.org/mqtt/mqtt/v3.1.1/errata01/os/mqtt-v3.1.1-errata01-os-complete.html.
|
||||
var multiplier = 128;
|
||||
var value = initialEncodedByte & 127;
|
||||
int encodedByte = initialEncodedByte;
|
||||
|
||||
while ((encodedByte & 128) != 0)
|
||||
{
|
||||
encodedByte = await ReadByteAsync(channel, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
value += (byte)(encodedByte & 127) * multiplier;
|
||||
if (multiplier > 128 * 128 * 128)
|
||||
{
|
||||
throw new MqttProtocolViolationException("Remaining length is invalid.");
|
||||
}
|
||||
|
||||
multiplier *= 128;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
private static async Task<byte> ReadByteAsync(IMqttChannel channel, CancellationToken cancellationToken)
|
||||
{
|
||||
var buffer = new byte[1];
|
||||
var readCount = await channel.ReadAsync(buffer, 0, 1, cancellationToken).ConfigureAwait(false);
|
||||
if (readCount <= 0)
|
||||
{
|
||||
ExceptionHelper.ThrowGracefulSocketClose();
|
||||
}
|
||||
|
||||
return buffer[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,625 +0,0 @@
|
||||
using MQTTnet.Exceptions;
|
||||
using MQTTnet.Packets;
|
||||
using MQTTnet.Protocol;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using MQTTnet.Adapter;
|
||||
|
||||
namespace MQTTnet.Serializer
|
||||
{
|
||||
public class MqttPacketSerializer : IMqttPacketSerializer
|
||||
{
|
||||
private const int FixedHeaderSize = 1;
|
||||
|
||||
public MqttProtocolVersion ProtocolVersion { get; set; } = MqttProtocolVersion.V311;
|
||||
|
||||
public ArraySegment<byte> Serialize(MqttBasePacket packet)
|
||||
{
|
||||
if (packet == null) throw new ArgumentNullException(nameof(packet));
|
||||
|
||||
using (var stream = new MemoryStream(128))
|
||||
{
|
||||
// Leave enough head space for max header size (fixed + 4 variable remaining length = 5 bytes)
|
||||
stream.Seek(5, SeekOrigin.Begin);
|
||||
|
||||
var fixedHeader = SerializePacket(packet, stream);
|
||||
var remainingLength = (int)stream.Length - 5;
|
||||
|
||||
var remainingLengthBuffer = MqttPacketWriter.EncodeRemainingLength(remainingLength);
|
||||
|
||||
var headerSize = FixedHeaderSize + remainingLengthBuffer.Count;
|
||||
var headerOffset = 5 - headerSize;
|
||||
|
||||
// Position cursor on correct offset on beginining of array (has leading 0x0)
|
||||
stream.Seek(headerOffset, SeekOrigin.Begin);
|
||||
stream.WriteByte(fixedHeader);
|
||||
stream.Write(remainingLengthBuffer.Array, remainingLengthBuffer.Offset, remainingLengthBuffer.Count);
|
||||
|
||||
#if NET461 || NET452 || NETSTANDARD2_0
|
||||
var buffer = stream.GetBuffer();
|
||||
return new ArraySegment<byte>(buffer, headerOffset, (int)stream.Length - headerOffset);
|
||||
#else
|
||||
if (stream.TryGetBuffer(out var segment))
|
||||
{
|
||||
return new ArraySegment<byte>(segment.Array, headerOffset, segment.Count - headerOffset);
|
||||
}
|
||||
|
||||
var buffer = stream.ToArray();
|
||||
return new ArraySegment<byte>(buffer, headerOffset, buffer.Length - headerOffset);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
private byte SerializePacket(MqttBasePacket packet, Stream stream)
|
||||
{
|
||||
switch (packet)
|
||||
{
|
||||
case MqttConnectPacket connectPacket: return Serialize(connectPacket, stream);
|
||||
case MqttConnAckPacket connAckPacket: return Serialize(connAckPacket, stream);
|
||||
case MqttDisconnectPacket _: return SerializeEmptyPacket(MqttControlPacketType.Disconnect);
|
||||
case MqttPingReqPacket _: return SerializeEmptyPacket(MqttControlPacketType.PingReq);
|
||||
case MqttPingRespPacket _: return SerializeEmptyPacket(MqttControlPacketType.PingResp);
|
||||
case MqttPublishPacket publishPacket: return Serialize(publishPacket, stream);
|
||||
case MqttPubAckPacket pubAckPacket: return Serialize(pubAckPacket, stream);
|
||||
case MqttPubRecPacket pubRecPacket: return Serialize(pubRecPacket, stream);
|
||||
case MqttPubRelPacket pubRelPacket: return Serialize(pubRelPacket, stream);
|
||||
case MqttPubCompPacket pubCompPacket: return Serialize(pubCompPacket, stream);
|
||||
case MqttSubscribePacket subscribePacket: return Serialize(subscribePacket, stream);
|
||||
case MqttSubAckPacket subAckPacket: return Serialize(subAckPacket, stream);
|
||||
case MqttUnsubscribePacket unsubscribePacket: return Serialize(unsubscribePacket, stream);
|
||||
case MqttUnsubAckPacket unsubAckPacket: return Serialize(unsubAckPacket, stream);
|
||||
default: throw new MqttProtocolViolationException("Packet type invalid.");
|
||||
}
|
||||
}
|
||||
|
||||
public MqttBasePacket Deserialize(ReceivedMqttPacket receivedMqttPacket)
|
||||
{
|
||||
if (receivedMqttPacket == null) throw new ArgumentNullException(nameof(receivedMqttPacket));
|
||||
|
||||
var controlPacketType = receivedMqttPacket.FixedHeader >> 4;
|
||||
if (controlPacketType < 1 || controlPacketType > 14)
|
||||
{
|
||||
throw new MqttProtocolViolationException($"The packet type is invalid ({controlPacketType}).");
|
||||
}
|
||||
|
||||
switch ((MqttControlPacketType)controlPacketType)
|
||||
{
|
||||
case MqttControlPacketType.Connect: return DeserializeConnect(receivedMqttPacket.Body);
|
||||
case MqttControlPacketType.ConnAck: return DeserializeConnAck(receivedMqttPacket.Body);
|
||||
case MqttControlPacketType.Disconnect: return new MqttDisconnectPacket();
|
||||
case MqttControlPacketType.Publish: return DeserializePublish(receivedMqttPacket);
|
||||
case MqttControlPacketType.PubAck: return DeserializePubAck(receivedMqttPacket.Body);
|
||||
case MqttControlPacketType.PubRec: return DeserializePubRec(receivedMqttPacket.Body);
|
||||
case MqttControlPacketType.PubRel: return DeserializePubRel(receivedMqttPacket.Body);
|
||||
case MqttControlPacketType.PubComp: return DeserializePubComp(receivedMqttPacket.Body);
|
||||
case MqttControlPacketType.PingReq: return new MqttPingReqPacket();
|
||||
case MqttControlPacketType.PingResp: return new MqttPingRespPacket();
|
||||
case MqttControlPacketType.Subscribe: return DeserializeSubscribe(receivedMqttPacket.Body);
|
||||
case MqttControlPacketType.SubAck: return DeserializeSubAck(receivedMqttPacket.Body);
|
||||
case MqttControlPacketType.Unsubscibe: return DeserializeUnsubscribe(receivedMqttPacket.Body);
|
||||
case MqttControlPacketType.UnsubAck: return DeserializeUnsubAck(receivedMqttPacket.Body);
|
||||
|
||||
default: throw new MqttProtocolViolationException($"Packet type ({controlPacketType}) not supported.");
|
||||
}
|
||||
}
|
||||
|
||||
private static MqttBasePacket DeserializeUnsubAck(MqttPacketBodyReader body)
|
||||
{
|
||||
ThrowIfBodyIsEmpty(body);
|
||||
|
||||
return new MqttUnsubAckPacket
|
||||
{
|
||||
PacketIdentifier = body.ReadUInt16()
|
||||
};
|
||||
}
|
||||
|
||||
private static MqttBasePacket DeserializePubComp(MqttPacketBodyReader body)
|
||||
{
|
||||
ThrowIfBodyIsEmpty(body);
|
||||
|
||||
return new MqttPubCompPacket
|
||||
{
|
||||
PacketIdentifier = body.ReadUInt16()
|
||||
};
|
||||
}
|
||||
|
||||
private static MqttBasePacket DeserializePubRel(MqttPacketBodyReader body)
|
||||
{
|
||||
ThrowIfBodyIsEmpty(body);
|
||||
|
||||
return new MqttPubRelPacket
|
||||
{
|
||||
PacketIdentifier = body.ReadUInt16()
|
||||
};
|
||||
}
|
||||
|
||||
private static MqttBasePacket DeserializePubRec(MqttPacketBodyReader body)
|
||||
{
|
||||
ThrowIfBodyIsEmpty(body);
|
||||
|
||||
return new MqttPubRecPacket
|
||||
{
|
||||
PacketIdentifier = body.ReadUInt16()
|
||||
};
|
||||
}
|
||||
|
||||
private static MqttBasePacket DeserializePubAck(MqttPacketBodyReader body)
|
||||
{
|
||||
ThrowIfBodyIsEmpty(body);
|
||||
|
||||
return new MqttPubAckPacket
|
||||
{
|
||||
PacketIdentifier = body.ReadUInt16()
|
||||
};
|
||||
}
|
||||
|
||||
private static MqttBasePacket DeserializeUnsubscribe(MqttPacketBodyReader body)
|
||||
{
|
||||
ThrowIfBodyIsEmpty(body);
|
||||
|
||||
var packet = new MqttUnsubscribePacket
|
||||
{
|
||||
PacketIdentifier = body.ReadUInt16(),
|
||||
};
|
||||
|
||||
while (!body.EndOfStream)
|
||||
{
|
||||
packet.TopicFilters.Add(body.ReadStringWithLengthPrefix());
|
||||
}
|
||||
|
||||
return packet;
|
||||
}
|
||||
|
||||
private static MqttBasePacket DeserializeSubscribe(MqttPacketBodyReader body)
|
||||
{
|
||||
ThrowIfBodyIsEmpty(body);
|
||||
|
||||
var packet = new MqttSubscribePacket
|
||||
{
|
||||
PacketIdentifier = body.ReadUInt16()
|
||||
};
|
||||
|
||||
while (!body.EndOfStream)
|
||||
{
|
||||
packet.TopicFilters.Add(new TopicFilter(
|
||||
body.ReadStringWithLengthPrefix(),
|
||||
(MqttQualityOfServiceLevel)body.ReadByte()));
|
||||
}
|
||||
|
||||
return packet;
|
||||
}
|
||||
|
||||
private static MqttBasePacket DeserializePublish(ReceivedMqttPacket receivedMqttPacket)
|
||||
{
|
||||
var body = receivedMqttPacket.Body;
|
||||
ThrowIfBodyIsEmpty(body);
|
||||
|
||||
var fixedHeader = new ByteReader(receivedMqttPacket.FixedHeader);
|
||||
var retain = fixedHeader.Read();
|
||||
var qualityOfServiceLevel = (MqttQualityOfServiceLevel)fixedHeader.Read(2);
|
||||
var dup = fixedHeader.Read();
|
||||
|
||||
var topic = body.ReadStringWithLengthPrefix();
|
||||
|
||||
ushort? packetIdentifier = null;
|
||||
if (qualityOfServiceLevel > MqttQualityOfServiceLevel.AtMostOnce)
|
||||
{
|
||||
packetIdentifier = body.ReadUInt16();
|
||||
}
|
||||
|
||||
var packet = new MqttPublishPacket
|
||||
{
|
||||
PacketIdentifier = packetIdentifier,
|
||||
Retain = retain,
|
||||
Topic = topic,
|
||||
Payload = body.ReadRemainingData().ToArray(),
|
||||
QualityOfServiceLevel = qualityOfServiceLevel,
|
||||
Dup = dup
|
||||
};
|
||||
|
||||
return packet;
|
||||
}
|
||||
|
||||
private static MqttBasePacket DeserializeConnect(MqttPacketBodyReader body)
|
||||
{
|
||||
ThrowIfBodyIsEmpty(body);
|
||||
|
||||
var protocolName = body.ReadStringWithLengthPrefix();
|
||||
|
||||
MqttProtocolVersion protocolVersion;
|
||||
if (protocolName == "MQTT")
|
||||
{
|
||||
var protocolLevel = body.ReadByte();
|
||||
if (protocolLevel != 4)
|
||||
{
|
||||
throw new MqttProtocolViolationException($"Protocol level ({protocolLevel}) not supported for MQTT 3.1.1.");
|
||||
}
|
||||
|
||||
protocolVersion = MqttProtocolVersion.V311;
|
||||
}
|
||||
else if (protocolName == "MQIsdp")
|
||||
{
|
||||
var protocolLevel = body.ReadByte();
|
||||
if (protocolLevel != 3)
|
||||
{
|
||||
throw new MqttProtocolViolationException($"Protocol level ({protocolLevel}) not supported for MQTT 3.1.");
|
||||
}
|
||||
|
||||
protocolVersion = MqttProtocolVersion.V310;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new MqttProtocolViolationException($"Protocol name ({protocolName}) is not supported.");
|
||||
}
|
||||
|
||||
var connectFlags = new ByteReader(body.ReadByte());
|
||||
if (connectFlags.Read())
|
||||
{
|
||||
throw new MqttProtocolViolationException("The first bit of the Connect Flags must be set to 0.");
|
||||
}
|
||||
|
||||
var packet = new MqttConnectPacket
|
||||
{
|
||||
ProtocolVersion = protocolVersion,
|
||||
CleanSession = connectFlags.Read()
|
||||
};
|
||||
|
||||
var willFlag = connectFlags.Read();
|
||||
var willQoS = connectFlags.Read(2);
|
||||
var willRetain = connectFlags.Read();
|
||||
var passwordFlag = connectFlags.Read();
|
||||
var usernameFlag = connectFlags.Read();
|
||||
|
||||
packet.KeepAlivePeriod = body.ReadUInt16();
|
||||
packet.ClientId = body.ReadStringWithLengthPrefix();
|
||||
|
||||
if (willFlag)
|
||||
{
|
||||
packet.WillMessage = new MqttApplicationMessage
|
||||
{
|
||||
Topic = body.ReadStringWithLengthPrefix(),
|
||||
Payload = body.ReadWithLengthPrefix().ToArray(),
|
||||
QualityOfServiceLevel = (MqttQualityOfServiceLevel)willQoS,
|
||||
Retain = willRetain
|
||||
};
|
||||
}
|
||||
|
||||
if (usernameFlag)
|
||||
{
|
||||
packet.Username = body.ReadStringWithLengthPrefix();
|
||||
}
|
||||
|
||||
if (passwordFlag)
|
||||
{
|
||||
packet.Password = body.ReadStringWithLengthPrefix();
|
||||
}
|
||||
|
||||
ValidateConnectPacket(packet);
|
||||
return packet;
|
||||
}
|
||||
|
||||
private static MqttBasePacket DeserializeSubAck(MqttPacketBodyReader body)
|
||||
{
|
||||
ThrowIfBodyIsEmpty(body);
|
||||
|
||||
var packet = new MqttSubAckPacket
|
||||
{
|
||||
PacketIdentifier = body.ReadUInt16()
|
||||
};
|
||||
|
||||
while (!body.EndOfStream)
|
||||
{
|
||||
packet.SubscribeReturnCodes.Add((MqttSubscribeReturnCode)body.ReadByte());
|
||||
}
|
||||
|
||||
return packet;
|
||||
}
|
||||
|
||||
private MqttBasePacket DeserializeConnAck(MqttPacketBodyReader body)
|
||||
{
|
||||
ThrowIfBodyIsEmpty(body);
|
||||
|
||||
var packet = new MqttConnAckPacket();
|
||||
|
||||
var firstByteReader = new ByteReader(body.ReadByte());
|
||||
|
||||
if (ProtocolVersion == MqttProtocolVersion.V311)
|
||||
{
|
||||
packet.IsSessionPresent = firstByteReader.Read();
|
||||
}
|
||||
|
||||
packet.ConnectReturnCode = (MqttConnectReturnCode)body.ReadByte();
|
||||
|
||||
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, Stream stream)
|
||||
{
|
||||
ValidateConnectPacket(packet);
|
||||
|
||||
// Write variable header
|
||||
if (ProtocolVersion == MqttProtocolVersion.V311)
|
||||
{
|
||||
stream.WriteWithLengthPrefix("MQTT");
|
||||
stream.WriteByte(4); // 3.1.2.2 Protocol Level 4
|
||||
}
|
||||
else
|
||||
{
|
||||
stream.WriteWithLengthPrefix("MQIsdp");
|
||||
stream.WriteByte(3); // Protocol Level 3
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
if (packet.Password != null && packet.Username == null)
|
||||
{
|
||||
throw new MqttProtocolViolationException("If the User Name Flag is set to 0, the Password Flag MUST be set to 0 [MQTT-3.1.2-22].");
|
||||
}
|
||||
|
||||
connectFlags.Write(packet.Password != null);
|
||||
connectFlags.Write(packet.Username != null);
|
||||
|
||||
stream.Write(connectFlags);
|
||||
stream.Write(packet.KeepAlivePeriod);
|
||||
stream.WriteWithLengthPrefix(packet.ClientId);
|
||||
|
||||
if (packet.WillMessage != null)
|
||||
{
|
||||
stream.WriteWithLengthPrefix(packet.WillMessage.Topic);
|
||||
stream.WriteWithLengthPrefix(packet.WillMessage.Payload);
|
||||
}
|
||||
|
||||
if (packet.Username != null)
|
||||
{
|
||||
stream.WriteWithLengthPrefix(packet.Username);
|
||||
}
|
||||
|
||||
if (packet.Password != null)
|
||||
{
|
||||
stream.WriteWithLengthPrefix(packet.Password);
|
||||
}
|
||||
|
||||
return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.Connect);
|
||||
}
|
||||
|
||||
private byte Serialize(MqttConnAckPacket packet, Stream stream)
|
||||
{
|
||||
if (ProtocolVersion == MqttProtocolVersion.V310)
|
||||
{
|
||||
stream.WriteByte(0);
|
||||
}
|
||||
else if (ProtocolVersion == MqttProtocolVersion.V311)
|
||||
{
|
||||
var connectAcknowledgeFlags = new ByteWriter();
|
||||
connectAcknowledgeFlags.Write(packet.IsSessionPresent);
|
||||
|
||||
stream.Write(connectAcknowledgeFlags);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new MqttProtocolViolationException("Protocol version not supported.");
|
||||
}
|
||||
|
||||
stream.WriteByte((byte)packet.ConnectReturnCode);
|
||||
|
||||
return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.ConnAck);
|
||||
}
|
||||
|
||||
private static byte Serialize(MqttPubRelPacket packet, Stream stream)
|
||||
{
|
||||
if (!packet.PacketIdentifier.HasValue)
|
||||
{
|
||||
throw new MqttProtocolViolationException("PubRel packet has no packet identifier.");
|
||||
}
|
||||
|
||||
stream.Write(packet.PacketIdentifier.Value);
|
||||
|
||||
return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.PubRel, 0x02);
|
||||
}
|
||||
|
||||
private static byte Serialize(MqttPublishPacket packet, Stream stream)
|
||||
{
|
||||
ValidatePublishPacket(packet);
|
||||
|
||||
stream.WriteWithLengthPrefix(packet.Topic);
|
||||
|
||||
if (packet.QualityOfServiceLevel > MqttQualityOfServiceLevel.AtMostOnce)
|
||||
{
|
||||
if (!packet.PacketIdentifier.HasValue)
|
||||
{
|
||||
throw new MqttProtocolViolationException("Publish packet has no packet identifier.");
|
||||
}
|
||||
|
||||
stream.Write(packet.PacketIdentifier.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (packet.PacketIdentifier > 0)
|
||||
{
|
||||
throw new MqttProtocolViolationException("Packet identifier must be empty if QoS == 0 [MQTT-2.3.1-5].");
|
||||
}
|
||||
}
|
||||
|
||||
if (packet.Payload?.Length > 0)
|
||||
{
|
||||
stream.Write(packet.Payload, 0, packet.Payload.Length);
|
||||
}
|
||||
|
||||
byte fixedHeader = 0;
|
||||
|
||||
if (packet.Retain)
|
||||
{
|
||||
fixedHeader |= 0x01;
|
||||
}
|
||||
|
||||
fixedHeader |= (byte)((byte)packet.QualityOfServiceLevel << 1);
|
||||
|
||||
if (packet.Dup)
|
||||
{
|
||||
fixedHeader |= 0x08;
|
||||
}
|
||||
|
||||
return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.Publish, fixedHeader);
|
||||
}
|
||||
|
||||
private static byte Serialize(MqttPubAckPacket packet, Stream stream)
|
||||
{
|
||||
if (!packet.PacketIdentifier.HasValue)
|
||||
{
|
||||
throw new MqttProtocolViolationException("PubAck packet has no packet identifier.");
|
||||
}
|
||||
|
||||
stream.Write(packet.PacketIdentifier.Value);
|
||||
|
||||
return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.PubAck);
|
||||
}
|
||||
|
||||
private static byte Serialize(MqttPubRecPacket packet, Stream stream)
|
||||
{
|
||||
if (!packet.PacketIdentifier.HasValue)
|
||||
{
|
||||
throw new MqttProtocolViolationException("PubRec packet has no packet identifier.");
|
||||
}
|
||||
|
||||
stream.Write(packet.PacketIdentifier.Value);
|
||||
|
||||
return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.PubRec);
|
||||
}
|
||||
|
||||
private static byte Serialize(MqttPubCompPacket packet, Stream stream)
|
||||
{
|
||||
if (!packet.PacketIdentifier.HasValue)
|
||||
{
|
||||
throw new MqttProtocolViolationException("PubComp packet has no packet identifier.");
|
||||
}
|
||||
|
||||
stream.Write(packet.PacketIdentifier.Value);
|
||||
|
||||
return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.PubComp);
|
||||
}
|
||||
|
||||
private static byte Serialize(MqttSubscribePacket packet, Stream stream)
|
||||
{
|
||||
if (!packet.TopicFilters.Any()) throw new MqttProtocolViolationException("At least one topic filter must be set [MQTT-3.8.3-3].");
|
||||
|
||||
if (!packet.PacketIdentifier.HasValue)
|
||||
{
|
||||
throw new MqttProtocolViolationException("Subscribe packet has no packet identifier.");
|
||||
}
|
||||
|
||||
stream.Write(packet.PacketIdentifier.Value);
|
||||
|
||||
if (packet.TopicFilters?.Count > 0)
|
||||
{
|
||||
foreach (var topicFilter in packet.TopicFilters)
|
||||
{
|
||||
stream.WriteWithLengthPrefix(topicFilter.Topic);
|
||||
stream.WriteByte((byte)topicFilter.QualityOfServiceLevel);
|
||||
}
|
||||
}
|
||||
|
||||
return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.Subscribe, 0x02);
|
||||
}
|
||||
|
||||
private static byte Serialize(MqttSubAckPacket packet, Stream stream)
|
||||
{
|
||||
if (!packet.PacketIdentifier.HasValue)
|
||||
{
|
||||
throw new MqttProtocolViolationException("SubAck packet has no packet identifier.");
|
||||
}
|
||||
|
||||
stream.Write(packet.PacketIdentifier.Value);
|
||||
|
||||
if (packet.SubscribeReturnCodes?.Any() == true)
|
||||
{
|
||||
foreach (var packetSubscribeReturnCode in packet.SubscribeReturnCodes)
|
||||
{
|
||||
stream.WriteByte((byte)packetSubscribeReturnCode);
|
||||
}
|
||||
}
|
||||
|
||||
return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.SubAck);
|
||||
}
|
||||
|
||||
private static byte Serialize(MqttUnsubscribePacket packet, Stream stream)
|
||||
{
|
||||
if (!packet.TopicFilters.Any()) throw new MqttProtocolViolationException("At least one topic filter must be set [MQTT-3.10.3-2].");
|
||||
|
||||
if (!packet.PacketIdentifier.HasValue)
|
||||
{
|
||||
throw new MqttProtocolViolationException("Unsubscribe packet has no packet identifier.");
|
||||
}
|
||||
|
||||
stream.Write(packet.PacketIdentifier.Value);
|
||||
|
||||
if (packet.TopicFilters?.Any() == true)
|
||||
{
|
||||
foreach (var topicFilter in packet.TopicFilters)
|
||||
{
|
||||
stream.WriteWithLengthPrefix(topicFilter);
|
||||
}
|
||||
}
|
||||
|
||||
return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.Unsubscibe, 0x02);
|
||||
}
|
||||
|
||||
private static byte Serialize(MqttUnsubAckPacket packet, Stream stream)
|
||||
{
|
||||
if (!packet.PacketIdentifier.HasValue)
|
||||
{
|
||||
throw new MqttProtocolViolationException("UnsubAck packet has no packet identifier.");
|
||||
}
|
||||
|
||||
stream.Write(packet.PacketIdentifier.Value);
|
||||
return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.UnsubAck);
|
||||
}
|
||||
|
||||
private static byte SerializeEmptyPacket(MqttControlPacketType type)
|
||||
{
|
||||
return MqttPacketWriter.BuildFixedHeader(type);
|
||||
}
|
||||
|
||||
private static void ThrowIfBodyIsEmpty(MqttPacketBodyReader body)
|
||||
{
|
||||
if (body == null || body.Length == 0)
|
||||
{
|
||||
throw new MqttProtocolViolationException("Data from the body is required but not present.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using MQTTnet.Protocol;
|
||||
|
||||
namespace MQTTnet.Serializer
|
||||
{
|
||||
public static class MqttPacketWriter
|
||||
{
|
||||
public static byte BuildFixedHeader(MqttControlPacketType packetType, byte flags = 0)
|
||||
{
|
||||
var fixedHeader = (int)packetType << 4;
|
||||
fixedHeader |= flags;
|
||||
return (byte)fixedHeader;
|
||||
}
|
||||
|
||||
public static void Write(this Stream stream, ushort value)
|
||||
{
|
||||
var buffer = BitConverter.GetBytes(value);
|
||||
stream.WriteByte(buffer[1]);
|
||||
stream.WriteByte(buffer[0]);
|
||||
}
|
||||
|
||||
public static void Write(this Stream stream, ByteWriter value)
|
||||
{
|
||||
if (value == null) throw new ArgumentNullException(nameof(value));
|
||||
|
||||
stream.WriteByte(value.Value);
|
||||
}
|
||||
|
||||
public static void WriteWithLengthPrefix(this Stream stream, string value)
|
||||
{
|
||||
stream.WriteWithLengthPrefix(Encoding.UTF8.GetBytes(value ?? string.Empty));
|
||||
}
|
||||
|
||||
public static void WriteWithLengthPrefix(this Stream stream, byte[] value)
|
||||
{
|
||||
var length = (ushort)value.Length;
|
||||
|
||||
stream.Write(length);
|
||||
stream.Write(value, 0, length);
|
||||
}
|
||||
|
||||
public static ArraySegment<byte> EncodeRemainingLength(int length)
|
||||
{
|
||||
// write the encoded remaining length right aligned on the 4 byte buffer
|
||||
if (length <= 0)
|
||||
{
|
||||
return new ArraySegment<byte>(new byte[1], 0, 1);
|
||||
}
|
||||
|
||||
var buffer = new byte[4];
|
||||
var bufferOffset = 0;
|
||||
|
||||
// Algorithm 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;
|
||||
}
|
||||
|
||||
buffer[bufferOffset] = (byte)encodedByte;
|
||||
bufferOffset++;
|
||||
} while (x > 0);
|
||||
|
||||
return new ArraySegment<byte>(buffer, 0, bufferOffset);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
namespace MQTTnet.Serializer
|
||||
{
|
||||
public enum MqttProtocolVersion
|
||||
{
|
||||
V311,
|
||||
V310
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
using MQTTnet.Protocol;
|
||||
|
||||
namespace MQTTnet.Server
|
||||
{
|
||||
public class CheckSubscriptionsResult
|
||||
{
|
||||
public bool IsSubscribed { get; set; }
|
||||
|
||||
public MqttQualityOfServiceLevel QualityOfServiceLevel { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
namespace MQTTnet.Server
|
||||
{
|
||||
public class GetOrCreateClientSessionResult
|
||||
{
|
||||
public bool IsExistingSession { get; set; }
|
||||
|
||||
public MqttClientSession Session { get; set; }
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user