Refactor session and connection handling in server. Fix QoS level 2 issues.

This commit is contained in:
Christian Kratky
2019-01-27 11:15:32 +01:00
parent ebf0e91ab8
commit 6a2bded181
72 changed files with 2919 additions and 2461 deletions
@@ -0,0 +1,138 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using MQTTnet.Client;
using MQTTnet.Client.Options;
using MQTTnet.Diagnostics;
using MQTTnet.Server;
namespace MQTTnet.Tests.Mockups
{
public class TestEnvironment : IDisposable
{
private readonly MqttFactory _mqttFactory = new MqttFactory();
private readonly List<IMqttClient> _clients = new List<IMqttClient>();
private readonly IMqttNetLogger _serverLogger = new MqttNetLogger("server");
private readonly IMqttNetLogger _clientLogger = new MqttNetLogger("client");
private readonly List<string> _serverErrors = new List<string>();
private readonly List<string> _clientErrors = new List<string>();
private readonly List<Exception> _exceptions = new List<Exception>();
private IMqttServer _server;
public bool IgnoreClientLogErrors { get; set; }
public bool IgnoreServerLogErrors { get; set; }
public int ServerPort { get; set; } = 1888;
public TestEnvironment()
{
_serverLogger.LogMessagePublished += (s, e) =>
{
if (e.TraceMessage.Level == MqttNetLogLevel.Error)
{
lock (_serverErrors)
{
_serverErrors.Add(e.TraceMessage.ToString());
}
}
};
_clientLogger.LogMessagePublished += (s, e) =>
{
lock (_clientErrors)
{
if (e.TraceMessage.Level == MqttNetLogLevel.Error)
{
_clientErrors.Add(e.TraceMessage.ToString());
}
}
};
}
public IMqttClient CreateClient()
{
return _mqttFactory.CreateMqttClient(_clientLogger);
}
public Task<IMqttServer> StartServerAsync()
{
return StartServerAsync(new MqttServerOptionsBuilder());
}
public async Task<IMqttServer> StartServerAsync(MqttServerOptionsBuilder options)
{
if (_server != null)
{
throw new InvalidOperationException("Server already started.");
}
_server = _mqttFactory.CreateMqttServer(_serverLogger);
await _server.StartAsync(options.WithDefaultEndpointPort(ServerPort).Build());
return _server;
}
public Task<IMqttClient> ConnectClientAsync()
{
return ConnectClientAsync(new MqttClientOptionsBuilder());
}
public async Task<IMqttClient> ConnectClientAsync(MqttClientOptionsBuilder options)
{
var client = _mqttFactory.CreateMqttClient(_clientLogger);
await client.ConnectAsync(options.WithTcpServer("localhost", ServerPort).Build());
_clients.Add(client);
return client;
}
public void ThrowIfLogErrors()
{
lock (_serverErrors)
{
if (!IgnoreServerLogErrors && _serverErrors.Count > 0)
{
throw new Exception($"Server had {_serverErrors.Count} errors (${string.Join(Environment.NewLine, _serverErrors)}).");
}
}
lock (_clientErrors)
{
if (!IgnoreClientLogErrors && _clientErrors.Count > 0)
{
throw new Exception($"Client(s) had {_clientErrors.Count} errors (${string.Join(Environment.NewLine, _clientErrors)}).");
}
}
}
public void Dispose()
{
foreach (var mqttClient in _clients)
{
mqttClient?.Dispose();
}
_server?.StopAsync().GetAwaiter().GetResult();
ThrowIfLogErrors();
if (_exceptions.Any())
{
throw new Exception($"{_exceptions.Count} exceptions tracked.\r\n" + string.Join(Environment.NewLine, _exceptions));
}
}
public void TrackException(Exception exception)
{
lock (_exceptions)
{
_exceptions.Add(exception);
}
}
}
}
@@ -0,0 +1,79 @@
using System;
using System.Collections.Concurrent;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Adapter;
using MQTTnet.Formatter;
using MQTTnet.Packets;
namespace MQTTnet.Tests.Mockups
{
public class TestMqttCommunicationAdapter : IMqttChannelAdapter
{
private readonly BlockingCollection<MqttBasePacket> _incomingPackets = new BlockingCollection<MqttBasePacket>();
public TestMqttCommunicationAdapter Partner { get; set; }
public string Endpoint { get; }
public MqttPacketFormatterAdapter PacketFormatterAdapter { get; } = new MqttPacketFormatterAdapter(MqttProtocolVersion.V311);
public event EventHandler ReadingPacketStarted;
public event EventHandler ReadingPacketCompleted;
public void Dispose()
{
}
public Task ConnectAsync(TimeSpan timeout, CancellationToken cancellationToken)
{
return Task.FromResult(0);
}
public Task DisconnectAsync(TimeSpan timeout, CancellationToken cancellationToken)
{
return Task.FromResult(0);
}
public Task SendPacketAsync(MqttBasePacket packet, TimeSpan timeout, CancellationToken cancellationToken)
{
ThrowIfPartnerIsNull();
Partner.EnqueuePacketInternal(packet);
return Task.FromResult(0);
}
public Task<MqttBasePacket> ReceivePacketAsync(TimeSpan timeout, CancellationToken cancellationToken)
{
ThrowIfPartnerIsNull();
return Task.Run(() =>
{
try
{
return _incomingPackets.Take(cancellationToken);
}
catch
{
return null;
}
}, cancellationToken);
}
private void EnqueuePacketInternal(MqttBasePacket packet)
{
if (packet == null) throw new ArgumentNullException(nameof(packet));
_incomingPackets.Add(packet);
}
private void ThrowIfPartnerIsNull()
{
if (Partner == null)
{
throw new InvalidOperationException("Partner is not set.");
}
}
}
}
@@ -0,0 +1,21 @@
using MQTTnet.Adapter;
using MQTTnet.Client.Options;
using MQTTnet.Diagnostics;
namespace MQTTnet.Tests.Mockups
{
public class TestMqttCommunicationAdapterFactory : IMqttClientAdapterFactory
{
private readonly IMqttChannelAdapter _adapter;
public TestMqttCommunicationAdapterFactory(IMqttChannelAdapter adapter)
{
_adapter = adapter;
}
public IMqttChannelAdapter CreateClientAdapter(IMqttClientOptions options, IMqttNetChildLogger logger)
{
return _adapter;
}
}
}
@@ -0,0 +1,61 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Adapter;
using MQTTnet.Client;
using MQTTnet.Client.Options;
using MQTTnet.Diagnostics;
using MQTTnet.Server;
namespace MQTTnet.Tests.Mockups
{
public class TestMqttServerAdapter : IMqttServerAdapter
{
public event EventHandler<MqttServerAdapterClientAcceptedEventArgs> ClientAccepted;
public async Task<IMqttClient> ConnectTestClient(string clientId, MqttApplicationMessage willMessage = null)
{
var adapterA = new TestMqttCommunicationAdapter();
var adapterB = new TestMqttCommunicationAdapter();
adapterA.Partner = adapterB;
adapterB.Partner = adapterA;
var client = new MqttClient(
new TestMqttCommunicationAdapterFactory(adapterA),
new MqttNetLogger());
FireClientAcceptedEvent(adapterB);
var options = new MqttClientOptions
{
ClientId = clientId,
WillMessage = willMessage,
ChannelOptions = new MqttClientTcpOptions()
};
await client.ConnectAsync(options);
SpinWait.SpinUntil(() => client.IsConnected);
return client;
}
private void FireClientAcceptedEvent(IMqttChannelAdapter adapter)
{
ClientAccepted?.Invoke(this, new MqttServerAdapterClientAcceptedEventArgs(adapter));
}
public Task StartAsync(IMqttServerOptions options)
{
return Task.FromResult(0);
}
public Task StopAsync()
{
return Task.FromResult(0);
}
public void Dispose()
{
}
}
}
@@ -0,0 +1,38 @@
using System.Threading.Tasks;
using MQTTnet.Client;
using MQTTnet.Server;
namespace MQTTnet.Tests.Mockups
{
public static class TestServerExtensions
{
/// <summary>
/// publishes a message with a client and waits in the server until a message with the same topic is received
/// </summary>
/// <returns></returns>
public static async Task PublishAndWaitForAsync(this IMqttClient client, IMqttServer server, MqttApplicationMessage message)
{
var tcs = new TaskCompletionSource<object>();
void Handler(object sender, MqttApplicationMessageReceivedEventArgs args)
{
if (args.ApplicationMessage.Topic == message.Topic)
{
tcs.SetResult(true);
}
}
server.ApplicationMessageReceived += Handler;
try
{
await client.PublishAsync(message).ConfigureAwait(false);
await tcs.Task.ConfigureAwait(false);
}
finally
{
server.ApplicationMessageReceived -= Handler;
}
}
}
}
@@ -0,0 +1,22 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using MQTTnet.Server;
namespace MQTTnet.Tests.Mockups
{
public class TestServerStorage : IMqttServerStorage
{
public IList<MqttApplicationMessage> Messages = new List<MqttApplicationMessage>();
public Task SaveRetainedMessagesAsync(IList<MqttApplicationMessage> messages)
{
Messages = messages;
return Task.CompletedTask;
}
public Task<IList<MqttApplicationMessage>> LoadRetainedMessagesAsync()
{
return Task.FromResult(Messages);
}
}
}