Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MQTT 3.1.1 Packet Decoding and Encoding #8

Merged
merged 36 commits into from
Apr 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
a39bea2
fixed issues with the MQTT header encoder / decoder methods
Aaronontheweb Apr 13, 2024
94d1ef8
working on more MQTT 3.1.1 decoding
Aaronontheweb Apr 14, 2024
fe0ae84
working on Connect packet
Aaronontheweb Apr 14, 2024
4c0abd6
finished with `ConnectPacket` decoding
Aaronontheweb Apr 14, 2024
71f5bbb
finished decoder and all compilation errors
Aaronontheweb Apr 14, 2024
7c14a7d
fixed all unit tests
Aaronontheweb Apr 14, 2024
dcc290c
finished writing encoder
Aaronontheweb Apr 14, 2024
8225681
fixing encoding and decoding issues
Aaronontheweb Apr 14, 2024
cdd5b54
added specs to validate uint encoding correctly
Aaronontheweb Apr 14, 2024
55fd3ce
fixing decoding errors
Aaronontheweb Apr 14, 2024
14e14a7
added some specs to cover our setting of auto-properties
Aaronontheweb Apr 14, 2024
634ecfb
added specs to ensure that will flag is being encoded / decoded corre…
Aaronontheweb Apr 14, 2024
445550b
fixing issues with ConnectFlag encoding
Aaronontheweb Apr 14, 2024
5a3b3af
got connect packet encoding to pass
Aaronontheweb Apr 14, 2024
6ee2ddd
fixed encoding length errors for will payloads
Aaronontheweb Apr 14, 2024
1f7c7ea
completed connect packet encoding specs
Aaronontheweb Apr 14, 2024
4d33e1e
fixed tests
Aaronontheweb Apr 14, 2024
4ebd2e6
standardized test helper utilities for ConnAck packets
Aaronontheweb Apr 14, 2024
fe8ab9d
added missing test case for enabling clean session
Aaronontheweb Apr 14, 2024
3c7a9c7
passed MQTT decoder specs for `ConnAck`
Aaronontheweb Apr 14, 2024
cad56fd
improve zero-packetId handling
Aaronontheweb Apr 14, 2024
f03b57f
fixed most happy path cases for `Publish` messages
Aaronontheweb Apr 14, 2024
741bd8d
removed unused code
Aaronontheweb Apr 14, 2024
c174685
added file
Aaronontheweb Apr 14, 2024
9976bf7
fixed repeated source of N+1 errors in MQTT 3.1.1 decoder
Aaronontheweb Apr 14, 2024
1c5f065
renamed folder so git would stop ignoring Publish packet specs
Aaronontheweb Apr 14, 2024
7c71ed0
finished covering all of the Publish-related packets
Aaronontheweb Apr 14, 2024
ed3194b
validated Subscribe packets
Aaronontheweb Apr 14, 2024
2f43067
fixed errors with SubAck size estimates
Aaronontheweb Apr 14, 2024
4ca1ec4
added PingReq / PingResp support
Aaronontheweb Apr 14, 2024
ba34c41
fixed issues with `UnsubscribePacket` encoding
Aaronontheweb Apr 14, 2024
d88de8b
added support for `UnsubAck`
Aaronontheweb Apr 14, 2024
0dea90e
all packets covered
Aaronontheweb Apr 14, 2024
239c313
adding tests to assert bulk encoding / decoding
Aaronontheweb Apr 14, 2024
0e38d74
validated that multiple packets work
Aaronontheweb Apr 14, 2024
cd14e2d
fixed stupid issue with PacketId numbers
Aaronontheweb Apr 14, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,11 @@
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>

<!-- Akka.NET Package Versions -->
<ItemGroup>
<PackageVersion Include="Akka.Hosting" Version="1.5.18" />
<PackageVersion Include="CommunityToolkit.HighPerformance" Version="8.2.2" />
</ItemGroup>

<!-- Test Package Versions -->
<ItemGroup>
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
Expand All @@ -18,4 +17,4 @@
<PackageVersion Include="Verify.Xunit" Version="17.10.2" />
<PackageVersion Include="Verify.DiffPlex" Version="1.3.0" />
</ItemGroup>
</Project>
</Project>
3 changes: 2 additions & 1 deletion TurboMqtt.sln.DotSettings
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@
&lt;copyright file="${File.FileName}" company="Petabridge, LLC"&gt;
Copyright (C) ${File.CreatedYear} - ${CurrentDate.Year} Petabridge, LLC &lt;https://petabridge.com&gt;
&lt;/copyright&gt;
-----------------------------------------------------------------------</s:String></wpf:ResourceDictionary>
-----------------------------------------------------------------------</s:String>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EFeature_002EServices_002ECodeCleanup_002EFileHeader_002EFileHeaderSettingsMigrate/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
33 changes: 16 additions & 17 deletions src/TurboMqtt.Core/ControlPacketHeaders.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
// Copyright (C) 2024 - 2024 Petabridge, LLC <https://petabridge.com>
// </copyright>
// -----------------------------------------------------------------------

namespace TurboMqtt.Core;

/// <summary>
Expand All @@ -14,21 +13,21 @@ namespace TurboMqtt.Core;
///
/// Also supports MQTT 5.0.
/// </remarks>
public enum MqttPacketType : byte
public enum MqttPacketType
{
Connect = 0x10,
ConnAck = 0x20,
Publish = 0x30,
PubAck = 0x40,
PubRec = 0x50,
PubRel = 0x60,
PubComp = 0x70,
Subscribe = 0x80,
SubAck = 0x90,
Unsubscribe = 0xA0,
UnsubAck = 0xB0,
PingReq = 0xC0,
PingResp = 0xD0,
Disconnect = 0xE0,
Auth = 0xF0
Connect = 1,
ConnAck =2,
Publish = 3,
PubAck = 4,
PubRec = 5,
PubRel = 6,
PubComp = 7,
Subscribe = 8,
SubAck = 9,
Unsubscribe = 10,
UnsubAck = 11,
PingReq = 12,
PingResp = 13,
Disconnect = 14,
Auth = 15
}
6 changes: 6 additions & 0 deletions src/TurboMqtt.Core/NonZeroUInt32.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,17 @@ namespace TurboMqtt.Core;
/// </summary>
public readonly struct NonZeroUInt32
{
public static readonly NonZeroUInt32 MinValue = new(1);

/// <summary>
/// The value of the identifier.
/// </summary>
public uint Value { get; }

public NonZeroUInt32() : this(1)
{
}

public NonZeroUInt32(uint value)
{
if (value == 0)
Expand Down
93 changes: 68 additions & 25 deletions src/TurboMqtt.Core/PacketTypes/ConnectPacket.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,53 @@ namespace TurboMqtt.Core.PacketTypes;
/// <summary>
/// Used to initiate a connection to the MQTT broker.
/// </summary>
public class ConnectPacket(string clientId, MqttProtocolVersion protocolVersion) : MqttPacket
public sealed class ConnectPacket(MqttProtocolVersion protocolVersion) : MqttPacket
{
public const string DefaultClientId = "turbomqtt";

public override MqttPacketType PacketType => MqttPacketType.Connect;

public string ClientId { get; } = clientId;
public ushort KeepAlive { get; set; }
public string ClientId { get; set; } = DefaultClientId;
public ushort KeepAliveSeconds { get; set; }
public ConnectFlags Flags { get; set; }

public MqttLastWill? Will { get; set; }

public string? Username { get; set; }
public ReadOnlyMemory<byte>? Password { get; set; }

public MqttLastWill? Will
{
get;
set;
}

private string? _username;
public string? Username
{
get => _username;
set
{
_username = value;

// ensure that the username flag is set or unset
var flags = Flags;
flags.UsernameFlag = !string.IsNullOrEmpty(value);
Flags = flags;
}
}

private string? _password;

public string? Password
{
get => _password;
set
{
_password = value;
// ensure that the password flag is set or unset
var flags = Flags;
flags.PasswordFlag = !string.IsNullOrEmpty(value);
Flags = flags;
}
}

public string ProtocolName { get; set; } = "MQTT";

public MqttProtocolVersion ProtocolVersion { get; } = protocolVersion;

Expand All @@ -37,7 +72,11 @@ public class ConnectPacket(string clientId, MqttProtocolVersion protocolVersion)
public ReadOnlyMemory<byte>? AuthenticationData { get; set; } // MQTT 5.0 only
public IReadOnlyDictionary<string, string>? UserProperties { get; set; } // MQTT 5.0 custom properties


public override string ToString()
{
return $"ConnectPacket(ClientId={ClientId}, KeepAliveSeconds={KeepAliveSeconds}, Flags={Flags}, Will={Will}, Username={Username}, Password={Password})";

}
}

/// <summary>
Expand Down Expand Up @@ -79,38 +118,42 @@ public struct ConnectFlags
public bool WillFlag { get; set; }
public bool CleanSession { get; set; } // Renamed from CleanStart for 3.1.1 compatibility

public byte Encode(MqttProtocolVersion version)
public byte Encode()
{
byte result = 0;
int result = 0;
if (UsernameFlag)
result |= 0b1000_0000;
result |= 0x80;
if (PasswordFlag)
result |= 0b0100_0000;
if (version == MqttProtocolVersion.V5_0 && WillRetain)
result |= 0b0010_0000;
result |= 0x40;
if (WillRetain)
result |= 0x20;
if (WillFlag)
result |= 0b0000_0100;
result |= 0x04;
if (CleanSession)
result |= 0b0000_0010;
result |= 0x02;
if (WillFlag) // Only encode Will QoS if Will is set
result |= (byte)(((int)WillQoS & 0x03) << 3);
result |= (((int)WillQoS & 0x03) << 3);

return result;
return (byte)result;
}

public static ConnectFlags Decode(byte flags)
{
var result = new ConnectFlags
{
UsernameFlag = (flags & 0b1000_0000) != 0,
PasswordFlag = (flags & 0b0100_0000) != 0,
WillRetain = (flags & 0b0010_0000) != 0,
WillFlag = (flags & 0b0000_0100) != 0,
CleanSession = (flags & 0b0000_0010) != 0
UsernameFlag = (flags & 0x80) == 0x80,
PasswordFlag = (flags & 0x40) == 0x40,
WillRetain = (flags & 0x20) == 0x20,
WillFlag = (flags & 0x04) == 0x04,
CleanSession = (flags & 0x02) == 0x02
};

if (result.WillFlag)
result.WillQoS = (QualityOfService)((flags & 0b0001_1000) >> 3);
result.WillQoS = (QualityOfService)((flags & 0x18) >> 3);
else if ((flags & 0x38) != 0) // reserved bit for Will 3,4,5
{
throw new ArgumentOutOfRangeException(nameof(flags), "[MQTT-3.1.2-11]");
}

return result;
}
Expand Down
5 changes: 5 additions & 0 deletions src/TurboMqtt.Core/PacketTypes/DisconnectPacket.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ namespace TurboMqtt.Core.PacketTypes;
/// </summary>
public sealed class DisconnectPacket : MqttPacket
{
/// <summary>
/// Used for MQTT 3.1.1, since no additional properties are supported.
/// </summary>
internal static readonly DisconnectPacket Instance = new();

public override MqttPacketType PacketType => MqttPacketType.Disconnect;

// MQTT 5.0 - Optional Reason Code and Properties
Expand Down
2 changes: 1 addition & 1 deletion src/TurboMqtt.Core/PacketTypes/PublishAckPacket.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public static string ReasonCodeToString(MqttPubAckReasonCode reasonCode)
/// <summary>
/// Used to acknowledge the receipt of a Publish packet.
/// </summary>
public sealed class PublishAckPacket : MqttPacketWithId
public sealed class PubAckPacket : MqttPacketWithId
{
public override MqttPacketType PacketType => MqttPacketType.PubAck;

Expand Down
6 changes: 3 additions & 3 deletions src/TurboMqtt.Core/PacketTypes/SubscribeAckPacket.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@

namespace TurboMqtt.Core.PacketTypes;

public enum MqttSubscribeReasonCode
public enum MqttSubscribeReasonCode : byte
{
// Common reason codes in MQTT 3.1.1 and earlier versions (implicitly used, typically not explicitly specified in these versions)
GrantedQoS0 = 0x00, // Maximum QoS 0, MQTT 3.0, 3.1.1
GrantedQoS1 = 0x01, // Maximum QoS 1, MQTT 3.0, 3.1.1
GrantedQoS2 = 0x02, // Maximum QoS 2, MQTT 3.0, 3.1.1
UnspecifiedError = 0x80, // MQTT 3.0, 3.1.1, MQTT 5.0

// MQTT 5.0 specific reason codes
UnspecifiedError = 0x80, // MQTT 5.0
ImplementationSpecificError = 0x83, // MQTT 5.0
NotAuthorized = 0x87, // MQTT 5.0
TopicFilterInvalid = 0x8F, // MQTT 5.0
Expand All @@ -28,7 +28,7 @@ public enum MqttSubscribeReasonCode
/// <summary>
/// Represents the acknowledgement packet for a subscription request.
/// </summary>
public sealed class SubscribeAckPacket : MqttPacketWithId
public sealed class SubAckPacket : MqttPacketWithId
{
public override MqttPacketType PacketType => MqttPacketType.SubAck;

Expand Down
Loading