Skip to content

Commit

Permalink
defining more packet types
Browse files Browse the repository at this point in the history
  • Loading branch information
Aaronontheweb committed Apr 12, 2024
1 parent 0b441f5 commit 8794b4b
Show file tree
Hide file tree
Showing 7 changed files with 217 additions and 16 deletions.
10 changes: 1 addition & 9 deletions src/TurboMqtt.Core/ControlPacketHeaders.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,4 @@ public enum MqttPacketType
PingResp = 0xD0,
Disconnect = 0xE0,
// Reserved = 0xF0, // Normally reserved values are not included in enums
}

public static class MqttHelpers
{
public static byte GetControlPacketType(MqttPacketType packetType)
{
return (byte)packetType;
}
}
}
38 changes: 38 additions & 0 deletions src/TurboMqtt.Core/MqttPubAckReasonCode.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
namespace TurboMqtt.Core;

/// <summary>
/// All possible reason codes for the PubAck packet.
/// </summary>
public enum MqttPubAckReasonCode
{
Success = 0x00,
NoMatchingSubscribers = 0x10,
UnspecifiedError = 0x80,
ImplementationSpecificError = 0x83,
NotAuthorized = 0x87,
TopicNameInvalid = 0x90,
PacketIdentifierInUse = 0x91,
QuotaExceeded = 0x97,
PayloadFormatInvalid = 0x99
}

// add a static helper method that can turn a MqttPubAckReason code into a hard-coded string representation
internal static class MqttPubAckHelpers
{
public static string ReasonCodeToString(MqttPubAckReasonCode reasonCode)
{
return reasonCode switch
{
MqttPubAckReasonCode.Success => "Success",
MqttPubAckReasonCode.NoMatchingSubscribers => "NoMatchingSubscribers",
MqttPubAckReasonCode.UnspecifiedError => "UnspecifiedError",
MqttPubAckReasonCode.ImplementationSpecificError => "ImplementationSpecificError",
MqttPubAckReasonCode.NotAuthorized => "NotAuthorized",
MqttPubAckReasonCode.TopicNameInvalid => "TopicNameInvalid",
MqttPubAckReasonCode.PacketIdentifierInUse => "PacketIdentifierInUse",
MqttPubAckReasonCode.QuotaExceeded => "QuotaExceeded",
MqttPubAckReasonCode.PayloadFormatInvalid => "PayloadFormatInvalid",
_ => throw new ArgumentOutOfRangeException(nameof(reasonCode), reasonCode, null)
};
}
}
25 changes: 25 additions & 0 deletions src/TurboMqtt.Core/NonZeroUInt32.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
namespace TurboMqtt.Core;

/// <summary>
/// Subscription and packet identifiers must be greater than 0.
/// </summary>
public readonly struct NonZeroUInt32
{
/// <summary>
/// The value of the identifier.
/// </summary>
public uint Value { get; }

public NonZeroUInt32(uint value)
{
if (value == 0)
{
throw new ArgumentOutOfRangeException(nameof(value), "Value must be greater than 0.");
}

Value = value;
}

public static implicit operator uint(NonZeroUInt32 value) => value.Value;
public static implicit operator NonZeroUInt32(uint value) => new(value);
}
2 changes: 1 addition & 1 deletion src/TurboMqtt.Core/PacketTypes/MqttPacket.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,5 @@ public abstract class MqttPacketWithId : MqttPacket
/// <remarks>
/// Not all packets require an identifier.
/// </remarks>
public int PacketId { get; set; }
public NonZeroUInt32 PacketId { get; set; }
}
30 changes: 30 additions & 0 deletions src/TurboMqtt.Core/PacketTypes/PublishAckPacket.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
namespace TurboMqtt.Core.PacketTypes;

using static MqttPubAckHelpers;

/// <summary>
/// Used to acknowledge the receipt of a Publish packet.
/// </summary>
public sealed class PublishAckPacket : MqttPacketWithId
{
public override MqttPacketType PacketType => MqttPacketType.PubAck;

// MQTT 5.0 Optional Properties

/// <summary>
/// Reason Code for the acknowledgment, available in MQTT 5.0.
/// This field is optional and provides more detailed acknowledgment information.
/// </summary>
public MqttPubAckReasonCode ReasonCode { get; set; }

/// <summary>
/// User Properties, available in MQTT 5.0.
/// These are key-value pairs that can be sent to provide additional information in the acknowledgment.
/// </summary>
public string ReasonString => ReasonCodeToString(ReasonCode);

public override string ToString()
{
return $"PubAck: [PacketIdentifier={PacketId}] [ReasonCode={ReasonCode}]";
}
}
9 changes: 3 additions & 6 deletions src/TurboMqtt.Core/PacketTypes/PublishPacket.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,10 @@ namespace TurboMqtt.Core.PacketTypes;
/// <summary>
/// Used to send data to the server or client.
/// </summary>
/// <param name="PacketId">The unique id of this packet - may not be needed with <see cref="QualityOfService.AtMostOnce"/>.</param>
/// <param name="Qos">The delivery guarantee for this packet.</param>
/// <param name="Duplicate">Is this packet a duplicate?</param>
/// <param name="RetainRequested">Indicates whether or not this value has been retained by the MQTT broker.</param>
public sealed class PublishPacket(int PacketId, QualityOfService Qos, bool Duplicate, bool RetainRequested) : MqttPacketWithId
public sealed class PublishPacket(QualityOfService Qos, bool Duplicate, bool RetainRequested) : MqttPacketWithId
{
public override MqttPacketType PacketType => MqttPacketType.Publish;

Expand All @@ -21,8 +20,6 @@ public sealed class PublishPacket(int PacketId, QualityOfService Qos, bool Dupli
/// Optional for <see cref="QualityOfService.AtMostOnce"/>
/// </summary>
public string? TopicName { get; set; }

public int PacketIdentifier { get; set; } // Only needed for QoS 1 and 2

// Payload
public ReadOnlyMemory<byte> Payload { get; set; } = ReadOnlyMemory<byte>.Empty;
Expand Down Expand Up @@ -51,7 +48,7 @@ public sealed class PublishPacket(int PacketId, QualityOfService Qos, bool Dupli
/// User Property, available in MQTT 5.0.
/// This is a key-value pair that can be sent multiple times to convey additional information that is not covered by other means.
/// </summary>
public IDictionary<string, string>? UserProperties { get; set; } // MQTT 5.0 only
public Dictionary<string, string>? UserProperties { get; set; } // MQTT 5.0 only

/// <summary>
/// Subscription Identifiers, available in MQTT 5.0.
Expand All @@ -62,6 +59,6 @@ public sealed class PublishPacket(int PacketId, QualityOfService Qos, bool Dupli

public override string ToString()
{
return $"Publish: [Topic={TopicName}] [PayloadLength={Payload.Length}] [QoSLevel={QualityOfService}] [Dup={Duplicate}] [Retain={RetainRequested}] [PacketIdentifier={PacketIdentifier}]";
return $"Publish: [Topic={TopicName}] [PayloadLength={Payload.Length}] [QoSLevel={QualityOfService}] [Dup={Duplicate}] [Retain={RetainRequested}] [PacketIdentifier={PacketId}]";
}
}
119 changes: 119 additions & 0 deletions src/TurboMqtt.Core/PacketTypes/SubscribePacket.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
namespace TurboMqtt.Core.PacketTypes;

public sealed class SubscribePacket : MqttPacketWithId
{
public override MqttPacketType PacketType { get; }

/// <summary>
/// The unique identity of this subscription for this client.
/// </summary>
/// <remarks>
/// Must be a value greater than 0.
/// </remarks>
public NonZeroUInt32 SubscriptionIdentifier { get; set; }

public TopicSubscription[] Topics { get; set; } = Array.Empty<TopicSubscription>();

/// <summary>
/// User Property, available in MQTT 5.0.
/// This is a key-value pair that can be sent multiple times to convey additional information that is not covered by other means.
/// </summary>
public Dictionary<string, string>? UserProperties { get; set; } // MQTT 5.0 only
}

public sealed class TopicSubscription(string topic)
{
/// <summary>
/// The topic to subscribe to.
/// </summary>
public string Topic { get; } = topic;

/// <summary>
/// The subscription options - QoS, No Local, Retain As Published, Retain Handling.
/// </summary>
/// <remarks>
/// Some of these are MQTT 5.0 features and will not be used in MQTT 3.1.1 or 3.0.
/// </remarks>
public SubscriptionOptions Options { get; set; }

public override string ToString()
{
return $"Topic: {Topic}, Options: {Options}";
}
}

public enum RetainHandlingOption
{
SendAtSubscribe = 0, // 00 binary
SendAtSubscribeIfNew = 1, // 01 binary
DoNotSendAtSubscribe = 2 // 10 binary
}

public struct SubscriptionOptions
{
/// <summary>
/// Gets or sets the Quality of Service level to use when sending messages to the client.
/// </summary>
public QualityOfService QoS { get; set; }

/// <summary>
/// MQTT 5.0 Feature: indicates whether or not the sender can receive its own messages.
/// </summary>
public bool NoLocal { get; set; }

/// <summary>
/// MQTT 5.0 Feature: indicates whether or not the message should be retained by the broker.
/// </summary>
public bool RetainAsPublished { get; set; }

/// <summary>
/// MQTT 5.0 Feature: indicates how the broker should handle retained messages.
/// </summary>
public RetainHandlingOption RetainHandling { get; set; }

public override string ToString()
{
return $"QoS: {QoS}, No Local: {NoLocal}, Retain As Published: {RetainAsPublished}, Retain Handling: {RetainHandling}";
}
}

internal static class SubscriptionOptionsHelpers
{
public static byte ToByte(this SubscriptionOptions subscriptionOptions)
{
byte result = 0;

// Set the QoS bits (bit 0 and 1)
result |= (byte)subscriptionOptions.QoS;

// Set the No Local bit (bit 2)
if (subscriptionOptions.NoLocal)
{
result |= 1 << 2;
}

// Set the Retain As Published bit (bit 3)
if (subscriptionOptions.RetainAsPublished)
{
result |= 1 << 3;
}

// Set the Retain Handling bits (bit 4 and 5)
result |= (byte)((int)subscriptionOptions.RetainHandling << 4);

return result;
}

public static SubscriptionOptions ToSubscriptionOptions(this byte subscriptionOptions)
{
var result = new SubscriptionOptions
{
QoS = (QualityOfService)(subscriptionOptions & 0b11),
NoLocal = (subscriptionOptions & (1 << 2)) != 0,
RetainAsPublished = (subscriptionOptions & (1 << 3)) != 0,
RetainHandling = (RetainHandlingOption)((subscriptionOptions & 0b11000) >> 3)
};

return result;
}
}

0 comments on commit 8794b4b

Please sign in to comment.