Feature/mqttv5 subscriptions server (#1246)

* Add support for RetainHandling.

* Add support for No Local.

* Add support for _SubscriptionIdentifier_.

* Add more Unit Tests.

* Update docs.
This commit is contained in:
Christian
2021-09-23 20:30:43 +02:00
committed by GitHub
parent feb53e78c6
commit c756a4092f
83 changed files with 2032 additions and 1237 deletions
@@ -8,6 +8,7 @@ using MQTTnet.Client.Unsubscribing;
using MQTTnet.Packets;
using MQTTnet.Protocol;
using MQTTnet.Server;
using MQTTnet.Server.Internal;
namespace MQTTnet.Formatter
{
@@ -15,13 +16,13 @@ namespace MQTTnet.Formatter
{
MqttApplicationMessage CreateApplicationMessage(MqttPublishPacket publishPacket);
MqttClientAuthenticateResult CreateClientConnectResult(MqttConnAckPacket connAckPacket);
MqttClientConnectResult CreateClientConnectResult(MqttConnAckPacket connAckPacket);
MqttClientPublishResult CreateClientPublishResult(MqttPubAckPacket pubAckPacket);
MqttClientPublishResult CreateClientPublishResult(MqttPubRecPacket pubRecPacket, MqttPubCompPacket pubCompPacket);
Client.Subscribing.MqttClientSubscribeResult CreateClientSubscribeResult(MqttSubscribePacket subscribePacket, MqttSubAckPacket subAckPacket);
MqttClientSubscribeResult CreateClientSubscribeResult(MqttSubscribePacket subscribePacket, MqttSubAckPacket subAckPacket);
MqttClientUnsubscribeResult CreateClientUnsubscribeResult(MqttUnsubscribePacket unsubscribePacket, MqttUnsubAckPacket unsubAckPacket);
@@ -41,7 +42,7 @@ namespace MQTTnet.Formatter
MqttSubscribePacket CreateSubscribePacket(MqttClientSubscribeOptions options);
MqttSubAckPacket CreateSubAckPacket(MqttSubscribePacket subscribePacket, Server.MqttClientSubscribeResult subscribeResult);
MqttSubAckPacket CreateSubAckPacket(MqttSubscribePacket subscribePacket, SubscribeResult subscribeResult);
MqttUnsubscribePacket CreateUnsubscribePacket(MqttClientUnsubscribeOptions options);
@@ -11,7 +11,7 @@ using MQTTnet.Server;
using System;
using System.Collections.Generic;
using System.Linq;
using MqttClientSubscribeResult = MQTTnet.Client.Subscribing.MqttClientSubscribeResult;
using MQTTnet.Server.Internal;
namespace MQTTnet.Formatter.V3
{
@@ -85,12 +85,12 @@ namespace MQTTnet.Formatter.V3
};
}
public MqttClientAuthenticateResult CreateClientConnectResult(MqttConnAckPacket connAckPacket)
public MqttClientConnectResult CreateClientConnectResult(MqttConnAckPacket connAckPacket)
{
if (connAckPacket == null) throw new ArgumentNullException(nameof(connAckPacket));
MqttClientConnectResultCode resultCode;
switch (connAckPacket.ReturnCode.Value)
switch (connAckPacket.ReturnCode)
{
case MqttConnectReturnCode.ConnectionAccepted:
{
@@ -132,8 +132,10 @@ namespace MQTTnet.Formatter.V3
throw new MqttProtocolViolationException("Received unexpected return code.");
}
return new MqttClientAuthenticateResult
return new MqttClientConnectResult
{
RetainAvailable = true, // Always true because v3.1.1 does not have a way to "disable" that feature.
WildcardSubscriptionAvailable = true, // Always true because v3.1.1 does not have a way to "disable" that feature.
IsSessionPresent = connAckPacket.IsSessionPresent,
ResultCode = resultCode
};
@@ -205,7 +207,7 @@ namespace MQTTnet.Formatter.V3
return subscribePacket;
}
public MqttSubAckPacket CreateSubAckPacket(MqttSubscribePacket subscribePacket, Server.MqttClientSubscribeResult subscribeResult)
public MqttSubAckPacket CreateSubAckPacket(MqttSubscribePacket subscribePacket, SubscribeResult subscribeResult)
{
if (subscribePacket == null) throw new ArgumentNullException(nameof(subscribePacket));
if (subscribeResult == null) throw new ArgumentNullException(nameof(subscribeResult));
@@ -1,5 +1,6 @@
using System;
using System.Linq;
using System.Net;
using System.Runtime.CompilerServices;
using MQTTnet.Adapter;
using MQTTnet.Exceptions;
@@ -407,7 +408,7 @@ namespace MQTTnet.Formatter.V3
protected virtual byte EncodeConnAckPacket(MqttConnAckPacket packet, IMqttPacketWriter packetWriter)
{
packetWriter.Write(0); // Reserved.
packetWriter.Write((byte)packet.ReturnCode.Value);
packetWriter.Write((byte)packet.ReturnCode);
return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.ConnAck);
}
@@ -520,6 +521,21 @@ namespace MQTTnet.Formatter.V3
{
foreach (var topicFilter in packet.TopicFilters)
{
if (topicFilter.NoLocal)
{
throw new MqttProtocolViolationException("NoLocal is not supported in 3.1.1.");
}
if (topicFilter.RetainAsPublished)
{
throw new MqttProtocolViolationException("RetainAsPublished is not supported in 3.1.1.");
}
if (topicFilter.RetainHandling != MqttRetainHandling.SendAtSubscribe)
{
throw new MqttProtocolViolationException("RetainHandling is not supported in 3.1.1.");
}
packetWriter.WriteWithLengthPrefix(topicFilter.Topic);
packetWriter.Write((byte)topicFilter.QualityOfServiceLevel);
}
@@ -11,7 +11,7 @@ using MQTTnet.Server;
using System;
using System.Collections.Generic;
using System.Linq;
using MqttClientSubscribeResult = MQTTnet.Client.Subscribing.MqttClientSubscribeResult;
using MQTTnet.Server.Internal;
namespace MQTTnet.Formatter.V5
{
@@ -115,16 +115,16 @@ namespace MQTTnet.Formatter.V5
};
}
public MqttClientAuthenticateResult CreateClientConnectResult(MqttConnAckPacket connAckPacket)
public MqttClientConnectResult CreateClientConnectResult(MqttConnAckPacket connAckPacket)
{
if (connAckPacket == null) throw new ArgumentNullException(nameof(connAckPacket));
return new MqttClientAuthenticateResult
return new MqttClientConnectResult
{
IsSessionPresent = connAckPacket.IsSessionPresent,
ResultCode = (MqttClientConnectResultCode)(int)(connAckPacket.ReasonCode ?? 0),
WildcardSubscriptionAvailable = connAckPacket.Properties?.WildcardSubscriptionAvailable,
RetainAvailable = connAckPacket.Properties?.RetainAvailable,
ResultCode = (MqttClientConnectResultCode)(int)connAckPacket.ReasonCode,
WildcardSubscriptionAvailable = connAckPacket.Properties?.WildcardSubscriptionAvailable ?? true,
RetainAvailable = connAckPacket.Properties?.RetainAvailable ?? true,
AssignedClientIdentifier = connAckPacket.Properties?.AssignedClientIdentifier,
AuthenticationMethod = connAckPacket.Properties?.AuthenticationMethod,
AuthenticationData = connAckPacket.Properties?.AuthenticationData,
@@ -133,13 +133,13 @@ namespace MQTTnet.Formatter.V5
ReceiveMaximum = connAckPacket.Properties?.ReceiveMaximum,
MaximumQoS = connAckPacket.Properties?.MaximumQoS ?? MqttQualityOfServiceLevel.ExactlyOnce,
ResponseInformation = connAckPacket.Properties?.ResponseInformation,
TopicAliasMaximum = connAckPacket.Properties?.TopicAliasMaximum,
TopicAliasMaximum = connAckPacket.Properties?.TopicAliasMaximum ?? 0,
ServerReference = connAckPacket.Properties?.ServerReference,
ServerKeepAlive = connAckPacket.Properties?.ServerKeepAlive,
SessionExpiryInterval = connAckPacket.Properties?.SessionExpiryInterval,
SubscriptionIdentifiersAvailable = connAckPacket.Properties?.SubscriptionIdentifiersAvailable,
SharedSubscriptionAvailable = connAckPacket.Properties?.SharedSubscriptionAvailable,
UserProperties = connAckPacket.Properties?.UserProperties
SubscriptionIdentifiersAvailable = connAckPacket.Properties?.SubscriptionIdentifiersAvailable ?? true,
SharedSubscriptionAvailable = connAckPacket.Properties?.SharedSubscriptionAvailable ?? true,
UserProperties = connAckPacket.Properties?.UserProperties ?? new List<MqttUserProperty>()
};
}
@@ -180,12 +180,18 @@ namespace MQTTnet.Formatter.V5
ReasonCode = connectionValidatorContext.ReasonCode,
Properties = new MqttConnAckPacketProperties
{
RetainAvailable = true,
SubscriptionIdentifiersAvailable = true,
SharedSubscriptionAvailable = false,
TopicAliasMaximum = ushort.MaxValue,
WildcardSubscriptionAvailable = true,
UserProperties = connectionValidatorContext.ResponseUserProperties,
AuthenticationMethod = connectionValidatorContext.AuthenticationMethod,
AuthenticationData = connectionValidatorContext.ResponseAuthenticationData,
AssignedClientIdentifier = connectionValidatorContext.AssignedClientIdentifier,
ReasonString = connectionValidatorContext.ReasonString,
TopicAliasMaximum = ushort.MaxValue
ServerReference = connectionValidatorContext.ServerReference
}
};
}
@@ -242,7 +248,7 @@ namespace MQTTnet.Formatter.V5
return packet;
}
public MqttSubAckPacket CreateSubAckPacket(MqttSubscribePacket subscribePacket, Server.MqttClientSubscribeResult subscribeResult)
public MqttSubAckPacket CreateSubAckPacket(MqttSubscribePacket subscribePacket, SubscribeResult subscribeResult)
{
if (subscribePacket == null) throw new ArgumentNullException(nameof(subscribePacket));
if (subscribeResult == null) throw new ArgumentNullException(nameof(subscribeResult));
@@ -9,9 +9,9 @@ namespace MQTTnet.Formatter.V5
{
public class MqttV500PacketDecoder
{
private static readonly MqttPingReqPacket PingReqPacket = new MqttPingReqPacket();
static readonly MqttPingReqPacket PingReqPacket = new MqttPingReqPacket();
private static readonly MqttPingRespPacket PingRespPacket = new MqttPingRespPacket();
static readonly MqttPingRespPacket PingRespPacket = new MqttPingRespPacket();
public MqttBasePacket Decode(ReceivedMqttPacket receivedMqttPacket)
{
@@ -45,7 +45,7 @@ namespace MQTTnet.Formatter.V5
}
}
private static MqttBasePacket DecodeConnectPacket(IMqttPacketBodyReader body)
static MqttBasePacket DecodeConnectPacket(IMqttPacketBodyReader body)
{
ThrowIfBodyIsEmpty(body);
@@ -214,7 +214,7 @@ namespace MQTTnet.Formatter.V5
return packet;
}
private static MqttBasePacket DecodeConnAckPacket(IMqttPacketBodyReader body)
static MqttBasePacket DecodeConnAckPacket(IMqttPacketBodyReader body)
{
ThrowIfBodyIsEmpty(body);
@@ -225,15 +225,17 @@ namespace MQTTnet.Formatter.V5
IsSessionPresent = (acknowledgeFlags & 0x1) > 0,
ReasonCode = (MqttConnectReasonCode)body.ReadByte()
};
// Set all default values according to specification. When they are missing the often
// indicate that a feature is available.
packet.Properties.RetainAvailable = true;
packet.Properties.SharedSubscriptionAvailable = true;
packet.Properties.SubscriptionIdentifiersAvailable = true;
packet.Properties.WildcardSubscriptionAvailable = true;
var propertiesReader = new MqttV500PropertiesReader(body);
while (propertiesReader.MoveNext())
{
if (packet.Properties == null)
{
packet.Properties = new MqttConnAckPacketProperties();
}
if (propertiesReader.CurrentPropertyId == MqttPropertyId.SessionExpiryInterval)
{
packet.Properties.SessionExpiryInterval = propertiesReader.ReadSessionExpiryInterval();
@@ -316,7 +318,7 @@ namespace MQTTnet.Formatter.V5
return packet;
}
private static MqttBasePacket DecodeDisconnectPacket(IMqttPacketBodyReader body)
static MqttBasePacket DecodeDisconnectPacket(IMqttPacketBodyReader body)
{
ThrowIfBodyIsEmpty(body);
@@ -363,7 +365,7 @@ namespace MQTTnet.Formatter.V5
return packet;
}
private static MqttBasePacket DecodeSubscribePacket(IMqttPacketBodyReader body)
static MqttBasePacket DecodeSubscribePacket(IMqttPacketBodyReader body)
{
ThrowIfBodyIsEmpty(body);
@@ -422,7 +424,7 @@ namespace MQTTnet.Formatter.V5
return packet;
}
private static MqttBasePacket DecodeSubAckPacket(IMqttPacketBodyReader body)
static MqttBasePacket DecodeSubAckPacket(IMqttPacketBodyReader body)
{
ThrowIfBodyIsEmpty(body);
@@ -467,7 +469,7 @@ namespace MQTTnet.Formatter.V5
return packet;
}
private static MqttBasePacket DecodeUnsubscribePacket(IMqttPacketBodyReader body)
static MqttBasePacket DecodeUnsubscribePacket(IMqttPacketBodyReader body)
{
ThrowIfBodyIsEmpty(body);
@@ -507,7 +509,7 @@ namespace MQTTnet.Formatter.V5
return packet;
}
private static MqttBasePacket DecodeUnsubAckPacket(IMqttPacketBodyReader body)
static MqttBasePacket DecodeUnsubAckPacket(IMqttPacketBodyReader body)
{
ThrowIfBodyIsEmpty(body);
@@ -552,17 +554,17 @@ namespace MQTTnet.Formatter.V5
return packet;
}
private static MqttBasePacket DecodePingReqPacket()
static MqttBasePacket DecodePingReqPacket()
{
return PingReqPacket;
}
private static MqttBasePacket DecodePingRespPacket()
static MqttBasePacket DecodePingRespPacket()
{
return PingRespPacket;
}
private static MqttBasePacket DecodePublishPacket(byte header, IMqttPacketBodyReader body)
static MqttBasePacket DecodePublishPacket(byte header, IMqttPacketBodyReader body)
{
ThrowIfBodyIsEmpty(body);
@@ -647,7 +649,7 @@ namespace MQTTnet.Formatter.V5
return packet;
}
private static MqttBasePacket DecodePubAckPacket(IMqttPacketBodyReader body)
static MqttBasePacket DecodePubAckPacket(IMqttPacketBodyReader body)
{
ThrowIfBodyIsEmpty(body);
@@ -694,7 +696,7 @@ namespace MQTTnet.Formatter.V5
return packet;
}
private static MqttBasePacket DecodePubRecPacket(IMqttPacketBodyReader body)
static MqttBasePacket DecodePubRecPacket(IMqttPacketBodyReader body)
{
ThrowIfBodyIsEmpty(body);
@@ -741,7 +743,7 @@ namespace MQTTnet.Formatter.V5
return packet;
}
private static MqttBasePacket DecodePubRelPacket(IMqttPacketBodyReader body)
static MqttBasePacket DecodePubRelPacket(IMqttPacketBodyReader body)
{
ThrowIfBodyIsEmpty(body);
@@ -788,7 +790,7 @@ namespace MQTTnet.Formatter.V5
return packet;
}
private static MqttBasePacket DecodePubCompPacket(IMqttPacketBodyReader body)
static MqttBasePacket DecodePubCompPacket(IMqttPacketBodyReader body)
{
ThrowIfBodyIsEmpty(body);
@@ -835,7 +837,7 @@ namespace MQTTnet.Formatter.V5
return packet;
}
private static MqttBasePacket DecodeAuthPacket(IMqttPacketBodyReader body)
static MqttBasePacket DecodeAuthPacket(IMqttPacketBodyReader body)
{
ThrowIfBodyIsEmpty(body);
@@ -888,7 +890,7 @@ namespace MQTTnet.Formatter.V5
}
// ReSharper disable once ParameterOnlyUsedForPreconditionCheck.Local
private static void ThrowIfBodyIsEmpty(IMqttPacketBodyReader body)
static void ThrowIfBodyIsEmpty(IMqttPacketBodyReader body)
{
if (body == null || body.Length == 0)
{
@@ -173,12 +173,7 @@ namespace MQTTnet.Formatter.V5
{
if (packet == null) throw new ArgumentNullException(nameof(packet));
if (packetWriter == null) throw new ArgumentNullException(nameof(packetWriter));
if (!packet.ReasonCode.HasValue)
{
ThrowReasonCodeNotSetException();
}
byte connectAcknowledgeFlags = 0x0;
if (packet.IsSessionPresent)
{
@@ -186,7 +181,7 @@ namespace MQTTnet.Formatter.V5
}
packetWriter.Write(connectAcknowledgeFlags);
packetWriter.Write((byte)packet.ReasonCode.Value);
packetWriter.Write((byte)packet.ReasonCode);
var propertiesWriter = new MqttV500PropertiesWriter();
if (packet.Properties != null)
@@ -247,19 +242,18 @@ namespace MQTTnet.Formatter.V5
var propertiesWriter = new MqttV500PropertiesWriter();
if (packet.Properties != null)
{
if (packet.Properties.TopicAlias == 0)
{
throw new MqttProtocolViolationException("A Topic Alias of 0 is not permitted. A sender MUST NOT send a PUBLISH packet containing a Topic Alias which has the value 0 [MQTT-3.3.2-8].");
}
propertiesWriter.WriteContentType(packet.Properties.ContentType);
propertiesWriter.WriteCorrelationData(packet.Properties.CorrelationData);
propertiesWriter.WriteMessageExpiryInterval(packet.Properties.MessageExpiryInterval);
propertiesWriter.WritePayloadFormatIndicator(packet.Properties.PayloadFormatIndicator);
propertiesWriter.WriteResponseTopic(packet.Properties.ResponseTopic);
propertiesWriter.WriteSubscriptionIdentifiers(packet.Properties.SubscriptionIdentifiers);
propertiesWriter.WriteTopicAlias(packet.Properties.TopicAlias);
propertiesWriter.WriteUserProperties(packet.Properties.UserProperties);
if (packet.Properties.TopicAlias > 0)
{
propertiesWriter.WriteTopicAlias(packet.Properties.TopicAlias);
}
}
propertiesWriter.WriteTo(packetWriter);
@@ -411,7 +405,11 @@ namespace MQTTnet.Formatter.V5
var propertiesWriter = new MqttV500PropertiesWriter();
if (packet.Properties != null)
{
propertiesWriter.WriteSubscriptionIdentifier(packet.Properties.SubscriptionIdentifier);
if (packet.Properties.SubscriptionIdentifier > 0)
{
propertiesWriter.WriteSubscriptionIdentifier(packet.Properties.SubscriptionIdentifier);
}
propertiesWriter.WriteUserProperties(packet.Properties.UserProperties);
}
@@ -425,19 +423,19 @@ namespace MQTTnet.Formatter.V5
var options = (byte)topicFilter.QualityOfServiceLevel;
if (topicFilter.NoLocal == true)
if (topicFilter.NoLocal)
{
options |= 1 << 2;
}
if (topicFilter.RetainAsPublished == true)
if (topicFilter.RetainAsPublished)
{
options |= 1 << 3;
}
if (topicFilter.RetainHandling.HasValue)
if (topicFilter.RetainHandling != MqttRetainHandling.SendAtSubscribe)
{
options |= (byte)((byte)topicFilter.RetainHandling.Value << 4);
options |= (byte)((byte)topicFilter.RetainHandling << 4);
}
packetWriter.Write(options);
@@ -74,12 +74,12 @@ namespace MQTTnet.Formatter.V5
WriteAsFourByteInteger(MqttPropertyId.SessionExpiryInterval, value);
}
public void WriteSubscriptionIdentifier(uint? value)
public void WriteSubscriptionIdentifier(uint value)
{
WriteAsVariableLengthInteger(MqttPropertyId.SubscriptionIdentifier, value);
}
public void WriteSubscriptionIdentifiers(IEnumerable<uint> value)
public void WriteSubscriptionIdentifiers(ICollection<uint> value)
{
if (value == null)
{
@@ -148,9 +148,15 @@ namespace MQTTnet.Formatter.V5
WriteAsFourByteInteger(MqttPropertyId.MaximumPacketSize, value);
}
public void WriteRetainAvailable(bool? value)
public void WriteRetainAvailable(bool value)
{
Write(MqttPropertyId.RetainAvailable, value);
if (value)
{
// Absence of the flag means it is supported!
return;
}
Write(MqttPropertyId.RetainAvailable, false);
}
public void WriteAssignedClientIdentifier(string value)
@@ -168,14 +174,26 @@ namespace MQTTnet.Formatter.V5
Write(MqttPropertyId.WildcardSubscriptionAvailable, value);
}
public void WriteSubscriptionIdentifiersAvailable(bool? value)
public void WriteSubscriptionIdentifiersAvailable(bool value)
{
Write(MqttPropertyId.SubscriptionIdentifiersAvailable, value);
if (value)
{
// Absence of the flag means it is supported!
return;
}
Write(MqttPropertyId.SubscriptionIdentifiersAvailable, false);
}
public void WriteSharedSubscriptionAvailable(bool? value)
public void WriteSharedSubscriptionAvailable(bool value)
{
Write(MqttPropertyId.SharedSubscriptionAvailable, value);
if (value)
{
// Absence of the flag means it is supported!
return;
}
Write(MqttPropertyId.SharedSubscriptionAvailable, false);
}
public void WriteServerKeepAlive(ushort? value)
@@ -221,15 +239,10 @@ namespace MQTTnet.Formatter.V5
_packetWriter.Write(value.Value);
}
void WriteAsVariableLengthInteger(MqttPropertyId id, uint? value)
void WriteAsVariableLengthInteger(MqttPropertyId id, uint value)
{
if (!value.HasValue)
{
return;
}
_packetWriter.Write((byte)id);
_packetWriter.WriteVariableLengthInteger(value.Value);
_packetWriter.WriteVariableLengthInteger(value);
}
void WriteAsFourByteInteger(MqttPropertyId id, uint? value)