Compare commits

...
26 Commits
Author SHA1 Message Date
Christian 8ecf658059 Update MQTTnet.nuspec 2019-12-28 14:05:24 +01:00
Christian Kratky 851db2e808 Merge remote-tracking branch 'origin/master' 2019-10-03 15:23:42 +02:00
Christian Kratky 073cd5243f Update docs. Add UnitTests. 2019-10-03 15:23:35 +02:00
SeppPenner 2918be18c6 Added automatic labels to the issue templates. 2019-09-05 10:37:55 +02:00
Christian Kratky 9302e80d73 Merge branch 'develop' 2019-08-18 21:00:40 +02:00
Christian Kratky cf6f0ece0e Add test execution in build script. 2019-08-18 20:56:26 +02:00
Christian Kratky 50d65f2fed Update nugets. 2019-08-14 23:06:45 +02:00
Christian Kratky 4614df7fa5 Update docs. 2019-08-14 22:57:17 +02:00
Christian 22aedcba09 Merge pull request #744 from JanEggers/pr/fix#743
fixed #743
2019-08-14 22:53:55 +02:00
Christian Kratky 2767d897a4 Fix serialization issue of MQTTv5 PubRec packet. 2019-08-14 22:45:15 +02:00
JanEggers a4420063c0 fixed #743 2019-08-14 22:29:51 +02:00
Christian Kratky 2320352124 Update docs. 2019-08-13 20:09:21 +02:00
Christian Kratky c0b4155ab4 Update docs. 2019-08-13 20:07:14 +02:00
Christian Kratky b09f31d654 Fix TCP server adpater when using empty password. 2019-08-13 20:07:10 +02:00
Christian Kratky 30c33d46f9 Merge remote-tracking branch 'origin/develop' into develop 2019-08-13 19:56:36 +02:00
Christian 651b79a124 Merge pull request #740 from cslutgen/chain
Fix Client SslStream CRL verification
2019-08-13 19:56:26 +02:00
Christian Kratky 2e3cf54f11 Add support for different strategies when generating RPC topics. 2019-08-13 19:53:55 +02:00
Craig Lutgen d7948afd98 Fix client SslStream certificate chain CRL validation. The logic was inverse and CRLs were never checked.
Add a missing dispose in managed client.
2019-08-12 17:34:05 -05:00
cslutgen 4cefa1dfa9 Merge pull request #2 from chkr1011/develop
Develop
2019-08-12 17:28:31 -05:00
Christian Kratky 0cfb8b4020 Remove not async task handling. 2019-08-11 22:25:05 +02:00
Christian Kratky 9ac726b271 Revert "Remove dedicated UWP builds and code."
This reverts commit 6d0e2c2022.
2019-08-11 22:19:49 +02:00
Christian Kratky 232605304e Revert "Update nugets."
This reverts commit 6c53677e7d.
2019-08-11 22:19:29 +02:00
Christian Kratky 6c53677e7d Update nugets. 2019-08-11 22:10:49 +02:00
Christian Kratky 6d0e2c2022 Remove dedicated UWP builds and code. 2019-08-11 22:07:57 +02:00
Christian Kratky 8ad1fb2871 Refactor server certificate classes (breaking change). 2019-08-11 21:46:13 +02:00
Christian Kratky 4a7ff9e9ed Add TLS certificate password support for MQTTnet.Server. 2019-08-11 21:45:41 +02:00
38 changed files with 387 additions and 141 deletions
+1 -1
View File
@@ -2,7 +2,7 @@
name: Bug report name: Bug report
about: Create a report to help us improve. about: Create a report to help us improve.
title: '' title: ''
labels: '' labels: 'bug'
assignees: '' assignees: ''
--- ---
+1 -1
View File
@@ -2,7 +2,7 @@
name: Custom issue template name: Custom issue template
about: Do you have a question related to the project? Use this template. about: Do you have a question related to the project? Use this template.
title: '' title: ''
labels: '' labels: 'question'
assignees: '' assignees: ''
--- ---
+1 -1
View File
@@ -2,7 +2,7 @@
name: Feature request name: Feature request
about: Suggest an idea for this project. about: Suggest an idea for this project.
title: '' title: ''
labels: '' labels: 'feature-request'
assignees: '' assignees: ''
--- ---
+3 -11
View File
@@ -11,16 +11,8 @@
<requireLicenseAcceptance>false</requireLicenseAcceptance> <requireLicenseAcceptance>false</requireLicenseAcceptance>
<description>MQTTnet is a high performance .NET library for MQTT based communication. It provides a MQTT client and a MQTT server (broker) and supports v3.1.0, v3.1.1 and v5.0.0 of the MQTT protocol.</description> <description>MQTTnet is a high performance .NET library for MQTT based communication. It provides a MQTT client and a MQTT server (broker) and supports v3.1.0, v3.1.1 and v5.0.0 of the MQTT protocol.</description>
<releaseNotes> <releaseNotes>
* [Core] Nuget packages with symbols are now also published to improve debugging. * [ManagedClient] Added support for persisted sessions (thansk to @PMExtra).
* [Core] Improve task handling (thanks to @mwinterb) * [Server] Added support for assigned client IDs (MQTTv5 only) (thanks to @bcrosnier).
* [ManagedClient] Fix a race condition in the message storage (thanks to @PaulFake).
* [Server] Added items dictionary to client session in order to share data across interceptors as along as the session exists.
* [Server] Exposed CONNECT packet properties in Application Message and Subscription interceptor.
* [Server] Fixed: Sending Large packets with AspnetCore based connection throws System.ArgumentException.
* [Server] Fixed wrong usage of socket option _NoDelay_.
* [Server] Added remote certificate validation callback (thanks to @rudacs).
* [Server] Add support for certificate passwords (thanks to @cslutgen).
* [MQTTnet.Server] Added REST API for publishing basic messages.
</releaseNotes> </releaseNotes>
<copyright>Copyright Christian Kratky 2016-2019</copyright> <copyright>Copyright Christian Kratky 2016-2019</copyright>
<tags>MQTT Message Queue Telemetry Transport MQTTClient MQTTServer Server MQTTBroker Broker NETStandard IoT InternetOfThings Messaging Hardware Arduino Sensor Actuator M2M ESP Smart Home Cities Automation Xamarin</tags> <tags>MQTT Message Queue Telemetry Transport MQTTClient MQTTServer Server MQTTBroker Broker NETStandard IoT InternetOfThings Messaging Hardware Arduino Sensor Actuator M2M ESP Smart Home Cities Automation Xamarin</tags>
@@ -64,4 +56,4 @@
<!-- Everything above .NET Framework 4.6.1 will use the binaries from 4.6.1. --> <!-- Everything above .NET Framework 4.6.1 will use the binaries from 4.6.1. -->
</files> </files>
</package> </package>
+7 -1
View File
@@ -12,6 +12,13 @@ Write-Host "Nuget version = $nugetVersion"
Write-Host "MSBuild path = $msbuild" Write-Host "MSBuild path = $msbuild"
Write-Host Write-Host
# Build and execute tests
&$msbuild ..\Tests\MQTTnet.Core.Tests\MQTTnet.Tests.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="netcoreapp2.1" /verbosity:m
&$msbuild ..\Tests\MQTTnet.AspNetCore.Tests\MQTTnet.AspNetCore.Tests.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="netcoreapp2.1" /verbosity:m
vstest.console.exe ..\Tests\MQTTnet.Core.Tests\bin\Release\netcoreapp2.1\MQTTnet.Tests.dll
vstest.console.exe ..\Tests\MQTTnet.AspNetCore.Tests\bin\Release\netcoreapp2.1\MQTTnet.AspNetCore.Tests.dll
# Build the core library # Build the core library
&$msbuild ..\Source\MQTTnet\MQTTnet.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="net452" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx" &$msbuild ..\Source\MQTTnet\MQTTnet.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="net452" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx"
&$msbuild ..\Source\MQTTnet\MQTTnet.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="net461" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx" &$msbuild ..\Source\MQTTnet\MQTTnet.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="net461" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx"
@@ -44,7 +51,6 @@ Write-Host
&$msbuild ..\Source\MQTTnet.Extensions.WebSocket4Net\MQTTnet.Extensions.WebSocket4Net.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="uap10.0" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx" &$msbuild ..\Source\MQTTnet.Extensions.WebSocket4Net\MQTTnet.Extensions.WebSocket4Net.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="uap10.0" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx"
# Create NuGet packages. # Create NuGet packages.
Invoke-WebRequest -Uri "https://dist.nuget.org/win-x86-commandline/latest/nuget.exe" -OutFile "nuget.exe" Invoke-WebRequest -Uri "https://dist.nuget.org/win-x86-commandline/latest/nuget.exe" -OutFile "nuget.exe"
Remove-Item .\NuGet -Force -Recurse -ErrorAction SilentlyContinue Remove-Item .\NuGet -Force -Recurse -ErrorAction SilentlyContinue
@@ -64,7 +64,6 @@ namespace MQTTnet.AspNetCore
GrowIfNeeded(propertyWriter.Length); GrowIfNeeded(propertyWriter.Length);
Write(propertyWriter.GetBuffer(), 0, propertyWriter.Length); Write(propertyWriter.GetBuffer(), 0, propertyWriter.Length);
Commit(propertyWriter.Length);
} }
public void Write(byte[] payload, int start, int length) public void Write(byte[] payload, int start, int length)
@@ -252,6 +252,7 @@ namespace MQTTnet.Extensions.ManagedClient
_maintainConnectionTask = null; _maintainConnectionTask = null;
} }
_messageQueueLock.Dispose();
_mqttClient.Dispose(); _mqttClient.Dispose();
} }
+30 -3
View File
@@ -5,6 +5,8 @@ using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MQTTnet.Client; using MQTTnet.Client;
using MQTTnet.Exceptions; using MQTTnet.Exceptions;
using MQTTnet.Extensions.Rpc.Options;
using MQTTnet.Extensions.Rpc.Options.TopicGeneration;
using MQTTnet.Protocol; using MQTTnet.Protocol;
namespace MQTTnet.Extensions.Rpc namespace MQTTnet.Extensions.Rpc
@@ -13,11 +15,18 @@ namespace MQTTnet.Extensions.Rpc
{ {
private readonly ConcurrentDictionary<string, TaskCompletionSource<byte[]>> _waitingCalls = new ConcurrentDictionary<string, TaskCompletionSource<byte[]>>(); private readonly ConcurrentDictionary<string, TaskCompletionSource<byte[]>> _waitingCalls = new ConcurrentDictionary<string, TaskCompletionSource<byte[]>>();
private readonly IMqttClient _mqttClient; private readonly IMqttClient _mqttClient;
private readonly IMqttRpcClientOptions _options;
private readonly RpcAwareApplicationMessageReceivedHandler _applicationMessageReceivedHandler; private readonly RpcAwareApplicationMessageReceivedHandler _applicationMessageReceivedHandler;
public MqttRpcClient(IMqttClient mqttClient) [Obsolete("Use MqttRpcClient(IMqttClient mqttClient, IMqttRpcClientOptions options).")]
public MqttRpcClient(IMqttClient mqttClient) : this(mqttClient, new MqttRpcClientOptions())
{
}
public MqttRpcClient(IMqttClient mqttClient, IMqttRpcClientOptions options)
{ {
_mqttClient = mqttClient ?? throw new ArgumentNullException(nameof(mqttClient)); _mqttClient = mqttClient ?? throw new ArgumentNullException(nameof(mqttClient));
_options = options ?? throw new ArgumentNullException(nameof(options));
_applicationMessageReceivedHandler = new RpcAwareApplicationMessageReceivedHandler( _applicationMessageReceivedHandler = new RpcAwareApplicationMessageReceivedHandler(
mqttClient.ApplicationMessageReceivedHandler, mqttClient.ApplicationMessageReceivedHandler,
@@ -55,8 +64,26 @@ namespace MQTTnet.Extensions.Rpc
throw new InvalidOperationException("The application message received handler was modified."); throw new InvalidOperationException("The application message received handler was modified.");
} }
var requestTopic = $"MQTTnet.RPC/{Guid.NewGuid():N}/{methodName}"; var topicNames = _options.TopicGenerationStrategy.CreateRpcTopics(new TopicGenerationContext
var responseTopic = requestTopic + "/response"; {
MethodName = methodName,
QualityOfServiceLevel = qualityOfServiceLevel,
MqttClient = _mqttClient,
Options = _options
});
var requestTopic = topicNames.RequestTopic;
var responseTopic = topicNames.ResponseTopic;
if (string.IsNullOrWhiteSpace(requestTopic))
{
throw new MqttProtocolViolationException("RPC request topic is empty.");
}
if (string.IsNullOrWhiteSpace(responseTopic))
{
throw new MqttProtocolViolationException("RPC response topic is empty.");
}
var requestMessage = new MqttApplicationMessageBuilder() var requestMessage = new MqttApplicationMessageBuilder()
.WithTopic(requestTopic) .WithTopic(requestTopic)
@@ -0,0 +1,9 @@
using MQTTnet.Extensions.Rpc.Options.TopicGeneration;
namespace MQTTnet.Extensions.Rpc.Options
{
public interface IMqttRpcClientOptions
{
IMqttRpcClientTopicGenerationStrategy TopicGenerationStrategy { get; set; }
}
}
@@ -0,0 +1,9 @@
using MQTTnet.Extensions.Rpc.Options.TopicGeneration;
namespace MQTTnet.Extensions.Rpc.Options
{
public class MqttRpcClientOptions : IMqttRpcClientOptions
{
public IMqttRpcClientTopicGenerationStrategy TopicGenerationStrategy { get; set; } = new DefaultMqttRpcClientTopicGenerationStrategy();
}
}
@@ -0,0 +1,25 @@
using MQTTnet.Extensions.Rpc.Options.TopicGeneration;
using System;
namespace MQTTnet.Extensions.Rpc.Options
{
public class MqttRpcClientOptionsBuilder
{
IMqttRpcClientTopicGenerationStrategy _topicGenerationStrategy = new DefaultMqttRpcClientTopicGenerationStrategy();
public MqttRpcClientOptionsBuilder WithTopicGenerationStrategy(IMqttRpcClientTopicGenerationStrategy value)
{
_topicGenerationStrategy = value ?? throw new ArgumentNullException(nameof(value));
return this;
}
public IMqttRpcClientOptions Build()
{
return new MqttRpcClientOptions
{
TopicGenerationStrategy = _topicGenerationStrategy
};
}
}
}
@@ -0,0 +1,9 @@
namespace MQTTnet.Extensions.Rpc.Options
{
public class MqttRpcTopicPair
{
public string RequestTopic { get; set; }
public string ResponseTopic { get; set; }
}
}
@@ -0,0 +1,20 @@
using MQTTnet.Extensions.Rpc.Options.TopicGeneration;
using System;
namespace MQTTnet.Extensions.Rpc.Options
{
public class DefaultMqttRpcClientTopicGenerationStrategy : IMqttRpcClientTopicGenerationStrategy
{
public MqttRpcTopicPair CreateRpcTopics(TopicGenerationContext context)
{
var requestTopic = $"MQTTnet.RPC/{Guid.NewGuid():N}/{context.MethodName}";
var responseTopic = requestTopic + "/response";
return new MqttRpcTopicPair
{
RequestTopic = requestTopic,
ResponseTopic = responseTopic
};
}
}
}
@@ -0,0 +1,7 @@
namespace MQTTnet.Extensions.Rpc.Options.TopicGeneration
{
public interface IMqttRpcClientTopicGenerationStrategy
{
MqttRpcTopicPair CreateRpcTopics(TopicGenerationContext context);
}
}
@@ -0,0 +1,16 @@
using MQTTnet.Client;
using MQTTnet.Protocol;
namespace MQTTnet.Extensions.Rpc.Options.TopicGeneration
{
public class TopicGenerationContext
{
public string MethodName { get; set; }
public MqttQualityOfServiceLevel QualityOfServiceLevel { get; set; }
public IMqttClient MqttClient { get; set; }
public IMqttRpcClientOptions Options { get; set; }
}
}
@@ -0,0 +1,35 @@
using System.IO;
namespace MQTTnet.Server.Configuration
{
public class CertificateSettingsModel
{
/// <summary>
/// Path to certificate.
/// </summary>
public string Path { get; set; }
/// <summary>
/// Password of certificate.
/// </summary>
public string Password { get; set; }
/// <summary>
/// Read certificate file.
/// </summary>
public byte[] ReadCertificate()
{
if (string.IsNullOrEmpty(Path) || string.IsNullOrWhiteSpace(Path))
{
throw new FileNotFoundException("No path set");
}
if (!File.Exists(Path))
{
throw new FileNotFoundException($"Could not find Certificate in path: {Path}");
}
return File.ReadAllBytes(Path);
}
}
}
@@ -9,9 +9,9 @@ namespace MQTTnet.Server.Configuration
public class TcpEndPointModel public class TcpEndPointModel
{ {
/// <summary> /// <summary>
/// Path to Certificate /// Certificate settings.
/// </summary> /// </summary>
public string CertificatePath { get; set; } public CertificateSettingsModel Certificate { get; set; }
/// <summary> /// <summary>
/// Enabled / Disable /// Enabled / Disable
@@ -33,25 +33,6 @@ namespace MQTTnet.Server.Configuration
/// </summary> /// </summary>
public int Port { get; set; } = 1883; public int Port { get; set; } = 1883;
/// <summary>
/// Read Certificate file
/// </summary>
/// <returns></returns>
public byte[] ReadCertificate()
{
if (string.IsNullOrEmpty(CertificatePath) || string.IsNullOrWhiteSpace(CertificatePath))
{
throw new FileNotFoundException("No path set");
}
if (!File.Exists(CertificatePath))
{
throw new FileNotFoundException($"Could not find Certificate in path: {CertificatePath}");
}
return File.ReadAllBytes(CertificatePath);
}
/// <summary> /// <summary>
/// Read IPv4 /// Read IPv4
/// </summary> /// </summary>
@@ -22,7 +22,8 @@ namespace MQTTnet.Server.Mqtt
{ {
try try
{ {
var sessionItems = (PythonDictionary)context.SessionItems[MqttServerConnectionValidator.WrappedSessionItemsKey]; // This might be not set when a message was published by the server instead of a client.
context.SessionItems.TryGetValue(MqttServerConnectionValidator.WrappedSessionItemsKey, out var sessionItems);
var pythonContext = new PythonDictionary var pythonContext = new PythonDictionary
{ {
@@ -50,4 +51,4 @@ namespace MQTTnet.Server.Mqtt
return Task.CompletedTask; return Task.CompletedTask;
} }
} }
} }
@@ -47,7 +47,7 @@ namespace MQTTnet.Server.Mqtt
MqttSubscriptionInterceptor mqttSubscriptionInterceptor, MqttSubscriptionInterceptor mqttSubscriptionInterceptor,
MqttApplicationMessageInterceptor mqttApplicationMessageInterceptor, MqttApplicationMessageInterceptor mqttApplicationMessageInterceptor,
MqttServerStorage mqttServerStorage, MqttServerStorage mqttServerStorage,
PythonScriptHostService pythonScriptHostService, PythonScriptHostService pythonScriptHostService,
ILogger<MqttServerService> logger) ILogger<MqttServerService> logger)
{ {
_settings = mqttSettings ?? throw new ArgumentNullException(nameof(mqttSettings)); _settings = mqttSettings ?? throw new ArgumentNullException(nameof(mqttSettings));
@@ -179,7 +179,7 @@ namespace MQTTnet.Server.Mqtt
.WithApplicationMessageInterceptor(_mqttApplicationMessageInterceptor) .WithApplicationMessageInterceptor(_mqttApplicationMessageInterceptor)
.WithSubscriptionInterceptor(_mqttSubscriptionInterceptor) .WithSubscriptionInterceptor(_mqttSubscriptionInterceptor)
.WithStorage(_mqttServerStorage); .WithStorage(_mqttServerStorage);
// Configure unencrypted connections // Configure unencrypted connections
if (_settings.TcpEndPoint.Enabled) if (_settings.TcpEndPoint.Enabled)
{ {
@@ -210,9 +210,23 @@ namespace MQTTnet.Server.Mqtt
{ {
options options
.WithEncryptedEndpoint() .WithEncryptedEndpoint()
.WithEncryptionSslProtocol(SslProtocols.Tls12) .WithEncryptionSslProtocol(SslProtocols.Tls12);
.WithEncryptionCertificate(_settings.EncryptedTcpEndPoint.ReadCertificate());
if (!string.IsNullOrEmpty(_settings.EncryptedTcpEndPoint?.Certificate?.Path))
{
IMqttServerCertificateCredentials certificateCredentials = null;
if (!string.IsNullOrEmpty(_settings.EncryptedTcpEndPoint?.Certificate?.Password))
{
certificateCredentials = new MqttServerCertificateCredentials
{
Password = _settings.EncryptedTcpEndPoint.Certificate.Password
};
}
options.WithEncryptionCertificate(_settings.EncryptedTcpEndPoint.Certificate.ReadCertificate(), certificateCredentials);
}
if (_settings.EncryptedTcpEndPoint.TryReadIPv4(out var address4)) if (_settings.EncryptedTcpEndPoint.TryReadIPv4(out var address4))
{ {
options.WithEncryptedEndpointBoundIPAddress(address4); options.WithEncryptedEndpointBoundIPAddress(address4);
+5 -2
View File
@@ -27,7 +27,10 @@
"IPv4": "*", "IPv4": "*",
"IPv6": "*", "IPv6": "*",
"Port": 8883, "Port": 8883,
"CertificatePath": "/absolute/path/to/pfx" "Certificate": {
"Path": "/absolute/path/to/pfx",
"Password": ""
}
}, },
"WebSocketEndPoint": { "WebSocketEndPoint": {
"Enabled": true, "Enabled": true,
@@ -63,4 +66,4 @@
} }
}, },
"AllowedHosts": "*" "AllowedHosts": "*"
} }
+18 -8
View File
@@ -5,15 +5,25 @@
int Length { get; } int Length { get; }
void WriteWithLengthPrefix(string value); void WriteWithLengthPrefix(string value);
void Write(byte returnCode);
void WriteWithLengthPrefix(byte[] payload); void Write(byte value);
void Write(ushort keepAlivePeriod);
void Write(IMqttPacketWriter propertyWriter); void WriteWithLengthPrefix(byte[] value);
void WriteVariableLengthInteger(uint length);
void Write(byte[] payload, int v, int length); void Write(ushort value);
void Reset(int v);
void Seek(int v); void Write(IMqttPacketWriter value);
void WriteVariableLengthInteger(uint value);
void Write(byte[] value, int offset, int length);
void Reset(int length);
void Seek(int offset);
void FreeBuffer(); void FreeBuffer();
byte[] GetBuffer(); byte[] GetBuffer();
} }
} }
@@ -21,8 +21,6 @@ namespace MQTTnet.Formatter
{ {
// The MQTT fixed header contains 1 byte of flags and at least 1 byte for the remaining data length. // 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. // So in all cases at least 2 bytes must be read for a complete MQTT packet.
// async/await is used here because the next packet is received in a couple of minutes so the performance
// impact is acceptable according to a useless waiting thread.
var buffer = fixedHeaderBuffer; var buffer = fixedHeaderBuffer;
var totalBytesRead = 0; var totalBytesRead = 0;
@@ -55,16 +53,7 @@ namespace MQTTnet.Formatter
}; };
} }
#if WINDOWS_UWP
// UWP will have a dead lock when calling this not async.
var bodyLength = await ReadBodyLengthAsync(buffer[1], cancellationToken).ConfigureAwait(false); var bodyLength = await ReadBodyLengthAsync(buffer[1], cancellationToken).ConfigureAwait(false);
#else
// Here the async/await pattern is not used because the overhead of context switches
// is too big for reading 1 byte in a row. We expect that the remaining data was sent
// directly after the initial bytes. If the client disconnects just in this moment we
// will get an exception anyway.
var bodyLength = ReadBodyLength(buffer[1], cancellationToken);
#endif
if (!bodyLength.HasValue) if (!bodyLength.HasValue)
{ {
@@ -81,49 +70,6 @@ namespace MQTTnet.Formatter
}; };
} }
#if !WINDOWS_UWP
private int? ReadBodyLength(byte initialEncodedByte, CancellationToken cancellationToken)
{
var offset = 0;
var multiplier = 128;
var value = initialEncodedByte & 127;
int encodedByte = initialEncodedByte;
while ((encodedByte & 128) != 0)
{
offset++;
if (offset > 3)
{
throw new MqttProtocolViolationException("Remaining length is invalid.");
}
if (cancellationToken.IsCancellationRequested)
{
return null;
}
var readCount = _channel.ReadAsync(_singleByteBuffer, 0, 1, cancellationToken).GetAwaiter().GetResult();
if (cancellationToken.IsCancellationRequested)
{
return null;
}
if (readCount == 0)
{
return null;
}
encodedByte = _singleByteBuffer[0];
value += (encodedByte & 127) * multiplier;
multiplier *= 128;
}
return value;
}
#else
private async Task<int?> ReadBodyLengthAsync(byte initialEncodedByte, CancellationToken cancellationToken) private async Task<int?> ReadBodyLengthAsync(byte initialEncodedByte, CancellationToken cancellationToken)
{ {
var offset = 0; var offset = 0;
@@ -164,6 +110,5 @@ namespace MQTTnet.Formatter
return value; return value;
} }
#endif
} }
} }
@@ -328,10 +328,7 @@ namespace MQTTnet.Formatter.V5
{ {
ThrowReasonCodeNotSetException(); ThrowReasonCodeNotSetException();
} }
packetWriter.Write(packet.PacketIdentifier.Value);
packetWriter.Write((byte)packet.ReasonCode.Value);
var propertiesWriter = new MqttV500PropertiesWriter(); var propertiesWriter = new MqttV500PropertiesWriter();
if (packet.Properties != null) if (packet.Properties != null)
{ {
@@ -339,6 +336,8 @@ namespace MQTTnet.Formatter.V5
propertiesWriter.WriteUserProperties(packet.Properties.UserProperties); propertiesWriter.WriteUserProperties(packet.Properties.UserProperties);
} }
packetWriter.Write(packet.PacketIdentifier.Value);
if (packetWriter.Length > 0 || packet.ReasonCode.Value != MqttPubRecReasonCode.Success) if (packetWriter.Length > 0 || packet.ReasonCode.Value != MqttPubRecReasonCode.Success)
{ {
packetWriter.Write((byte)packet.ReasonCode.Value); packetWriter.Write((byte)packet.ReasonCode.Value);
@@ -86,7 +86,7 @@ namespace MQTTnet.Implementations
var sslStream = new SslStream(networkStream, false, InternalUserCertificateValidationCallback); var sslStream = new SslStream(networkStream, false, InternalUserCertificateValidationCallback);
_stream = sslStream; _stream = sslStream;
await sslStream.AuthenticateAsClientAsync(_options.Server, LoadCertificates(), _options.TlsOptions.SslProtocol, _options.TlsOptions.IgnoreCertificateRevocationErrors).ConfigureAwait(false); await sslStream.AuthenticateAsClientAsync(_options.Server, LoadCertificates(), _options.TlsOptions.SslProtocol, !_options.TlsOptions.IgnoreCertificateRevocationErrors).ConfigureAwait(false);
} }
else else
{ {
@@ -41,14 +41,24 @@ namespace MQTTnet.Implementations
RegisterListeners(options.DefaultEndpointOptions, null, _cancellationTokenSource.Token); RegisterListeners(options.DefaultEndpointOptions, null, _cancellationTokenSource.Token);
} }
if (options.TlsEndpointOptions.IsEnabled) if (options.TlsEndpointOptions?.IsEnabled == true)
{ {
if (options.TlsEndpointOptions.Certificate == null) if (options.TlsEndpointOptions.Certificate == null)
{ {
throw new ArgumentException("TLS certificate is not set."); throw new ArgumentException("TLS certificate is not set.");
} }
var tlsCertificate = new X509Certificate2(options.TlsEndpointOptions.Certificate, options.TlsEndpointOptions.CertificateCredentials.Password); X509Certificate2 tlsCertificate;
if (string.IsNullOrEmpty(options.TlsEndpointOptions.CertificateCredentials?.Password))
{
// Use a different overload when no password is specified. Otherwise the constructor will fail.
tlsCertificate = new X509Certificate2(options.TlsEndpointOptions.Certificate);
}
else
{
tlsCertificate = new X509Certificate2(options.TlsEndpointOptions.Certificate, options.TlsEndpointOptions.CertificateCredentials.Password);
}
if (!tlsCertificate.HasPrivateKey) if (!tlsCertificate.HasPrivateKey)
{ {
throw new InvalidOperationException("The certificate for TLS encryption must contain the private key."); throw new InvalidOperationException("The certificate for TLS encryption must contain the private key.");
@@ -13,5 +13,31 @@ namespace MQTTnet.Packets
public string Name { get; } public string Name { get; }
public string Value { get; } public string Value { get; }
public override int GetHashCode()
{
return Name.GetHashCode() ^ Value.GetHashCode();
}
public override bool Equals(object other)
{
return Equals(other as MqttUserProperty);
}
public bool Equals(MqttUserProperty other)
{
if (other == null)
{
return false;
}
if (ReferenceEquals(other, this))
{
return true;
}
return string.Equals(Name, other.Name, StringComparison.Ordinal) &&
string.Equals(Value, other.Value, StringComparison.Ordinal);
}
} }
} }
@@ -0,0 +1,4 @@
public interface IMqttServerCertificateCredentials
{
string Password { get; }
}
@@ -1,6 +0,0 @@
using System;
public interface IMqttServerCredentials
{
String Password { get; }
}
@@ -0,0 +1,7 @@
namespace MQTTnet.Server
{
public class MqttServerCertificateCredentials : IMqttServerCertificateCredentials
{
public string Password { get; set; }
}
}
@@ -82,7 +82,7 @@ namespace MQTTnet.Server
return this; return this;
} }
public MqttServerOptionsBuilder WithEncryptionCertificate(byte[] value, IMqttServerCredentials credentials = null) public MqttServerOptionsBuilder WithEncryptionCertificate(byte[] value, IMqttServerCertificateCredentials credentials = null)
{ {
_options.TlsEndpointOptions.Certificate = value; _options.TlsEndpointOptions.Certificate = value;
_options.TlsEndpointOptions.CertificateCredentials = credentials; _options.TlsEndpointOptions.CertificateCredentials = credentials;
@@ -12,7 +12,7 @@ namespace MQTTnet.Server
public byte[] Certificate { get; set; } public byte[] Certificate { get; set; }
public IMqttServerCredentials CertificateCredentials { get; set; } public IMqttServerCertificateCredentials CertificateCredentials { get; set; }
public bool ClientCertificateRequired { get; set; } public bool ClientCertificateRequired { get; set; }
@@ -2,12 +2,11 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>netcoreapp2.1</TargetFramework> <TargetFramework>netcoreapp2.1</TargetFramework>
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.0.1" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.2.0" />
<PackageReference Include="MSTest.TestAdapter" Version="1.4.0" /> <PackageReference Include="MSTest.TestAdapter" Version="1.4.0" />
<PackageReference Include="MSTest.TestFramework" Version="1.4.0" /> <PackageReference Include="MSTest.TestFramework" Version="1.4.0" />
</ItemGroup> </ItemGroup>
@@ -1,15 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType>
<DebugType>Full</DebugType>
<TargetFramework>netcoreapp2.1</TargetFramework> <TargetFramework>netcoreapp2.1</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="MSTest.TestAdapter" Version="1.4.0" /> <PackageReference Include="MSTest.TestAdapter" Version="1.4.0" />
<PackageReference Include="MSTest.TestFramework" Version="1.4.0" /> <PackageReference Include="MSTest.TestFramework" Version="1.4.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.0.1" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.2.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@@ -293,5 +293,52 @@ namespace MQTTnet.Tests.MQTTv5
await server.StopAsync(); await server.StopAsync();
} }
} }
[TestMethod]
public async Task Publish_And_Receive_New_Properties()
{
using (var testEnvironment = new TestEnvironment())
{
await testEnvironment.StartServerAsync();
var receiver = await testEnvironment.ConnectClientAsync(new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V500));
await receiver.SubscribeAsync("#");
MqttApplicationMessage receivedMessage = null;
receiver.UseApplicationMessageReceivedHandler(c =>
{
receivedMessage = c.ApplicationMessage;
});
var sender = await testEnvironment.ConnectClientAsync(new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V500));
var applicationMessage = new MqttApplicationMessageBuilder()
.WithTopic("Hello")
.WithPayload("World")
.WithAtMostOnceQoS()
.WithUserProperty("x", "1")
.WithUserProperty("y", "2")
.WithResponseTopic("response")
.WithContentType("text")
.WithMessageExpiryInterval(50)
.WithCorrelationData(new byte[12])
.WithTopicAlias(2)
.Build();
await sender.PublishAsync(applicationMessage);
await Task.Delay(500);
Assert.IsNotNull(receivedMessage);
Assert.AreEqual(applicationMessage.Topic, receivedMessage.Topic);
Assert.AreEqual(applicationMessage.TopicAlias, receivedMessage.TopicAlias);
Assert.AreEqual(applicationMessage.ContentType, receivedMessage.ContentType);
Assert.AreEqual(applicationMessage.ResponseTopic, receivedMessage.ResponseTopic);
Assert.AreEqual(applicationMessage.MessageExpiryInterval, receivedMessage.MessageExpiryInterval);
CollectionAssert.AreEqual(applicationMessage.CorrelationData, receivedMessage.CorrelationData);
CollectionAssert.AreEqual(applicationMessage.Payload, receivedMessage.Payload);
CollectionAssert.AreEqual(applicationMessage.UserProperties, receivedMessage.UserProperties);
}
}
} }
} }
@@ -14,7 +14,7 @@ namespace MQTTnet.Tests
public class MqttKeepAliveMonitor_Tests public class MqttKeepAliveMonitor_Tests
{ {
[TestMethod] [TestMethod]
public void KeepAlive_Timeout() public async Task KeepAlive_Timeout()
{ {
var counter = 0; var counter = 0;
@@ -31,13 +31,13 @@ namespace MQTTnet.Tests
Assert.AreEqual(0, counter); Assert.AreEqual(0, counter);
Thread.Sleep(2000); // Internally the keep alive timeout is multiplied with 1.5 as per protocol specification. await Task.Delay(2000); // Internally the keep alive timeout is multiplied with 1.5 as per protocol specification.
Assert.AreEqual(1, counter); Assert.AreEqual(1, counter);
} }
[TestMethod] [TestMethod]
public void KeepAlive_NoTimeout() public async Task KeepAlive_NoTimeout()
{ {
var counter = 0; var counter = 0;
@@ -55,15 +55,15 @@ namespace MQTTnet.Tests
Assert.AreEqual(0, counter); Assert.AreEqual(0, counter);
// Simulate traffic. // Simulate traffic.
Thread.Sleep(1000); // Internally the keep alive timeout is multiplied with 1.5 as per protocol specification. await Task.Delay(1000); // Internally the keep alive timeout is multiplied with 1.5 as per protocol specification.
monitor.PacketReceived(); monitor.PacketReceived();
Thread.Sleep(1000); await Task.Delay(1000);
monitor.PacketReceived(); monitor.PacketReceived();
Thread.Sleep(1000); await Task.Delay(1000);
Assert.AreEqual(0, counter); Assert.AreEqual(0, counter);
Thread.Sleep(2000); await Task.Delay(2000);
Assert.AreEqual(1, counter); Assert.AreEqual(1, counter);
} }
@@ -8,6 +8,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Adapter; using MQTTnet.Adapter;
using MQTTnet.Formatter; using MQTTnet.Formatter;
using MQTTnet.Formatter.V3; using MQTTnet.Formatter.V3;
using MQTTnet.Formatter.V5;
using MQTTnet.Internal; using MQTTnet.Internal;
using MQTTnet.Packets; using MQTTnet.Packets;
using MQTTnet.Protocol; using MQTTnet.Protocol;
@@ -258,6 +259,26 @@ namespace MQTTnet.Tests
Assert.IsTrue(deserialized.Properties.UserProperties.Any(x => x.Name == "Foo")); Assert.IsTrue(deserialized.Properties.UserProperties.Any(x => x.Name == "Foo"));
} }
[TestMethod]
public void SerializeV500_MqttPublishPacket_CorrelationData()
{
var data = "123456789";
var req = new MqttApplicationMessageBuilder()
.WithTopic("Foo")
.WithResponseTopic($"_")
.WithCorrelationData(Guid.NewGuid().ToByteArray())
.WithPayload(data)
.WithQualityOfServiceLevel(MqttQualityOfServiceLevel.AtMostOnce)
.Build();
var p = new MqttV500DataConverter().CreatePublishPacket(req);
var deserialized = Roundtrip(p, MqttProtocolVersion.V500);
Assert.IsTrue(p.Payload.SequenceEqual(deserialized.Payload));
}
[TestMethod] [TestMethod]
public void DeserializeV311_MqttPublishPacket() public void DeserializeV311_MqttPublishPacket()
{ {
+31 -2
View File
@@ -10,6 +10,8 @@ using MQTTnet.Extensions.Rpc;
using MQTTnet.Protocol; using MQTTnet.Protocol;
using MQTTnet.Client.Options; using MQTTnet.Client.Options;
using MQTTnet.Formatter; using MQTTnet.Formatter;
using MQTTnet.Extensions.Rpc.Options;
using MQTTnet.Extensions.Rpc.Options.TopicGeneration;
namespace MQTTnet.Tests namespace MQTTnet.Tests
{ {
@@ -62,7 +64,22 @@ namespace MQTTnet.Tests
var requestSender = await testEnvironment.ConnectClientAsync(); var requestSender = await testEnvironment.ConnectClientAsync();
var rpcClient = new MqttRpcClient(requestSender); var rpcClient = new MqttRpcClient(requestSender, new MqttRpcClientOptionsBuilder().Build());
await rpcClient.ExecuteAsync(TimeSpan.FromSeconds(2), "ping", "", MqttQualityOfServiceLevel.AtMostOnce);
}
}
[TestMethod]
[ExpectedException(typeof(MqttCommunicationTimedOutException))]
public async Task Execute_With_Custom_Topic_Names()
{
using (var testEnvironment = new TestEnvironment())
{
await testEnvironment.StartServerAsync();
var requestSender = await testEnvironment.ConnectClientAsync();
var rpcClient = new MqttRpcClient(requestSender, new MqttRpcClientOptionsBuilder().WithTopicGenerationStrategy(new TestTopicStrategy()) .Build());
await rpcClient.ExecuteAsync(TimeSpan.FromSeconds(2), "ping", "", MqttQualityOfServiceLevel.AtMostOnce); await rpcClient.ExecuteAsync(TimeSpan.FromSeconds(2), "ping", "", MqttQualityOfServiceLevel.AtMostOnce);
} }
} }
@@ -82,11 +99,23 @@ namespace MQTTnet.Tests
var requestSender = await testEnvironment.ConnectClientAsync(); var requestSender = await testEnvironment.ConnectClientAsync();
var rpcClient = new MqttRpcClient(requestSender); var rpcClient = new MqttRpcClient(requestSender, new MqttRpcClientOptionsBuilder().Build());
var response = await rpcClient.ExecuteAsync(TimeSpan.FromSeconds(5), "ping", "", qosLevel); var response = await rpcClient.ExecuteAsync(TimeSpan.FromSeconds(5), "ping", "", qosLevel);
Assert.AreEqual("pong", Encoding.UTF8.GetString(response)); Assert.AreEqual("pong", Encoding.UTF8.GetString(response));
} }
} }
private class TestTopicStrategy : IMqttRpcClientTopicGenerationStrategy
{
public MqttRpcTopicPair CreateRpcTopics(TopicGenerationContext context)
{
return new MqttRpcTopicPair
{
RequestTopic = "a",
ResponseTopic = "b"
};
}
}
} }
} }
+3 -1
View File
@@ -1063,7 +1063,9 @@ namespace MQTTnet.Tests
// forever. This is security related. // forever. This is security related.
var client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); var client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
await client.ConnectAsync("localhost", testEnvironment.ServerPort); await client.ConnectAsync("localhost", testEnvironment.ServerPort);
await client.SendAsync(Encoding.UTF8.GetBytes("Garbage"), SocketFlags.None);
var buffer = Encoding.UTF8.GetBytes("Garbage");
client.Send(buffer, buffer.Length, SocketFlags.None);
await Task.Delay(TimeSpan.FromSeconds(3)); await Task.Delay(TimeSpan.FromSeconds(3));