diff --git a/Directory.Build.props b/Directory.Build.props
index 20327e5b..06033624 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -9,7 +9,7 @@
latest
enable
enable
-
+
@@ -21,18 +21,18 @@
1.4.0
-
-
-
+
+
+
-
- https://github.com/Aaronontheweb/TurboMqtt
-
- true
-
- true
- snupkg
-
+
+ https://github.com/Aaronontheweb/TurboMqtt
+
+ true
+
+ true
+ snupkg
+
\ No newline at end of file
diff --git a/LICENSE.md b/LICENSE.md
index a0d555ae..d8e9df7d 100644
--- a/LICENSE.md
+++ b/LICENSE.md
@@ -1,9 +1,13 @@
-By accessing code under the [TurboMQTT GitHub Organization](https://github.com/Aaronontheweb/TurboMqtt) (Petabridge, LLC) here, you are agreeing to the following licensing terms. If you do not agree to these terms, do not access TurboMQTT code.
+By accessing code under the [TurboMQTT GitHub Organization](https://github.com/Aaronontheweb/TurboMqtt) (Petabridge,
+LLC) here, you are agreeing to the following licensing terms. If you do not agree to these terms, do not access
+TurboMQTT code.
-Your license to TurboMQTT source code and/or binaries is governed by the Reciprocal Public License 1.5 (RPL1.5) license as described here:
+Your license to TurboMQTT source code and/or binaries is governed by the Reciprocal Public License 1.5 (RPL1.5) license
+as described here:
https://opensource.org/license/rpl-1-5/
-If you do not wish to release the source of software you build using TurboMQTT source code and/or binaries under the terms above, you may use TurboMQTT source code and/or binaries under the License Agreement described here:
+If you do not wish to release the source of software you build using TurboMQTT source code and/or binaries under the
+terms above, you may use TurboMQTT source code and/or binaries under the License Agreement described here:
https://sdkbin.com/publisher/petabridge/product/phobos#license
\ No newline at end of file
diff --git a/README.md b/README.md
index 0c15d992..389b9482 100644
--- a/README.md
+++ b/README.md
@@ -2,4 +2,5 @@
This is a simple template designed to incorporate local [Akka.NET](https://getakka.net/) into a console application.
-See https://github.com/akkadotnet/akkadotnet-templates/blob/dev/docs/ConsoleTemplate.md for complete and current documentation on this template.
\ No newline at end of file
+See https://github.com/akkadotnet/akkadotnet-templates/blob/dev/docs/ConsoleTemplate.md for complete and current
+documentation on this template.
\ No newline at end of file
diff --git a/TurboMqtt.sln b/TurboMqtt.sln
index 99c68e5f..a8e44b35 100644
--- a/TurboMqtt.sln
+++ b/TurboMqtt.sln
@@ -3,7 +3,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31903.59
MinimumVisualStudioVersion = 10.0.40219.1
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TurboMqtt.App", "src\TurboMqtt.App\TurboMqtt.App.csproj", "{EDFEA62C-AF53-4056-A2F2-E37CEA04B24A}"
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TurboMqtt.Core", "src\TurboMqtt.Core\TurboMqtt.Core.csproj", "{BE905781-3D96-44F5-A230-E06FD5213C1C}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -14,9 +14,9 @@ Global
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
- {EDFEA62C-AF53-4056-A2F2-E37CEA04B24A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {EDFEA62C-AF53-4056-A2F2-E37CEA04B24A}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {EDFEA62C-AF53-4056-A2F2-E37CEA04B24A}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {EDFEA62C-AF53-4056-A2F2-E37CEA04B24A}.Release|Any CPU.Build.0 = Release|Any CPU
+ {BE905781-3D96-44F5-A230-E06FD5213C1C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {BE905781-3D96-44F5-A230-E06FD5213C1C}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {BE905781-3D96-44F5-A230-E06FD5213C1C}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {BE905781-3D96-44F5-A230-E06FD5213C1C}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal
diff --git a/TurboMqtt.sln.DotSettings b/TurboMqtt.sln.DotSettings
new file mode 100644
index 00000000..f019c7dc
--- /dev/null
+++ b/TurboMqtt.sln.DotSettings
@@ -0,0 +1,6 @@
+
+ -----------------------------------------------------------------------
+<copyright file="${File.FileName}" company="Petabridge, LLC">
+ Copyright (C) ${File.CreatedYear} - ${CurrentDate.Year} Petabridge, LLC <https://petabridge.com>
+</copyright>
+-----------------------------------------------------------------------
\ No newline at end of file
diff --git a/src/TurboMqtt.App/HelloActor.cs b/src/TurboMqtt.App/HelloActor.cs
deleted file mode 100644
index 9861a1fc..00000000
--- a/src/TurboMqtt.App/HelloActor.cs
+++ /dev/null
@@ -1,15 +0,0 @@
-namespace TurboMqtt.App;
-
-public class HelloActor : ReceiveActor
-{
- private readonly ILoggingAdapter _log = Context.GetLogger();
- private int _helloCounter = 0;
-
- public HelloActor()
- {
- Receive(message =>
- {
- _log.Info("{0} {1}", message, _helloCounter++);
- });
- }
-}
\ No newline at end of file
diff --git a/src/TurboMqtt.App/Program.cs b/src/TurboMqtt.App/Program.cs
deleted file mode 100644
index afb39cc2..00000000
--- a/src/TurboMqtt.App/Program.cs
+++ /dev/null
@@ -1,29 +0,0 @@
-using Akka.Hosting;
-using TurboMqtt.App;
-using Microsoft.Extensions.Hosting;
-
-var hostBuilder = new HostBuilder();
-
-hostBuilder.ConfigureServices((context, services) =>
-{
- services.AddAkka("MyActorSystem", (builder, sp) =>
- {
- builder
- .WithActors((system, registry, resolver) =>
- {
- var helloActor = system.ActorOf(Props.Create(() => new HelloActor()), "hello-actor");
- registry.Register(helloActor);
- })
- .WithActors((system, registry, resolver) =>
- {
- var timerActorProps =
- resolver.Props(); // uses Msft.Ext.DI to inject reference to helloActor
- var timerActor = system.ActorOf(timerActorProps, "timer-actor");
- registry.Register(timerActor);
- });
- });
-});
-
-var host = hostBuilder.Build();
-
-await host.RunAsync();
\ No newline at end of file
diff --git a/src/TurboMqtt.App/TimerActor.cs b/src/TurboMqtt.App/TimerActor.cs
deleted file mode 100644
index 121c7018..00000000
--- a/src/TurboMqtt.App/TimerActor.cs
+++ /dev/null
@@ -1,24 +0,0 @@
-using Akka.Hosting;
-
-namespace TurboMqtt.App;
-
-public class TimerActor : ReceiveActor, IWithTimers
-{
- private readonly IActorRef _helloActor;
-
- public TimerActor(IRequiredActor helloActor)
- {
- _helloActor = helloActor.ActorRef;
- Receive(message =>
- {
- _helloActor.Tell(message);
- });
- }
-
- protected override void PreStart()
- {
- Timers.StartPeriodicTimer("hello-key", "hello", TimeSpan.FromSeconds(1));
- }
-
- public ITimerScheduler Timers { get; set; } = null!; // gets set by Akka.NET
-}
\ No newline at end of file
diff --git a/src/TurboMqtt.App/TurboMqtt.App.csproj b/src/TurboMqtt.App/TurboMqtt.App.csproj
deleted file mode 100644
index 3ec2860f..00000000
--- a/src/TurboMqtt.App/TurboMqtt.App.csproj
+++ /dev/null
@@ -1,14 +0,0 @@
-
-
-
- Exe
- net8.0
- enable
- enable
-
-
-
-
-
-
-
diff --git a/src/TurboMqtt.App/Usings.cs b/src/TurboMqtt.App/Usings.cs
deleted file mode 100644
index 15066e87..00000000
--- a/src/TurboMqtt.App/Usings.cs
+++ /dev/null
@@ -1,2 +0,0 @@
-global using Akka.Actor;
-global using Akka.Event;
\ No newline at end of file
diff --git a/src/TurboMqtt.Core/ControlPacketHeaders.cs b/src/TurboMqtt.Core/ControlPacketHeaders.cs
new file mode 100644
index 00000000..a71ee59f
--- /dev/null
+++ b/src/TurboMqtt.Core/ControlPacketHeaders.cs
@@ -0,0 +1,34 @@
+// -----------------------------------------------------------------------
+//
+// Copyright (C) 2024 - 2024 Petabridge, LLC
+//
+// -----------------------------------------------------------------------
+
+namespace TurboMqtt.Core;
+
+///
+/// The type of MQTT packet.
+///
+///
+/// Aligns to the MQTT 3.1.1 specification: https://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html
+///
+/// Also supports MQTT 5.0.
+///
+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
+}
\ No newline at end of file
diff --git a/src/TurboMqtt.Core/MqttPubAckReasonCode.cs b/src/TurboMqtt.Core/MqttPubAckReasonCode.cs
new file mode 100644
index 00000000..fb7f34ef
--- /dev/null
+++ b/src/TurboMqtt.Core/MqttPubAckReasonCode.cs
@@ -0,0 +1,44 @@
+// -----------------------------------------------------------------------
+//
+// Copyright (C) 2024 - 2024 Petabridge, LLC
+//
+// -----------------------------------------------------------------------
+
+namespace TurboMqtt.Core;
+
+///
+/// All possible reason codes for the PubAck packet.
+///
+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)
+ };
+ }
+}
\ No newline at end of file
diff --git a/src/TurboMqtt.Core/NonZeroUInt32.cs b/src/TurboMqtt.Core/NonZeroUInt32.cs
new file mode 100644
index 00000000..04130644
--- /dev/null
+++ b/src/TurboMqtt.Core/NonZeroUInt32.cs
@@ -0,0 +1,31 @@
+// -----------------------------------------------------------------------
+//
+// Copyright (C) 2024 - 2024 Petabridge, LLC
+//
+// -----------------------------------------------------------------------
+
+namespace TurboMqtt.Core;
+
+///
+/// Subscription and packet identifiers must be greater than 0.
+///
+public readonly struct NonZeroUInt32
+{
+ ///
+ /// The value of the identifier.
+ ///
+ 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);
+}
\ No newline at end of file
diff --git a/src/TurboMqtt.Core/PacketTypes/AuthPacket.cs b/src/TurboMqtt.Core/PacketTypes/AuthPacket.cs
new file mode 100644
index 00000000..76de7f8e
--- /dev/null
+++ b/src/TurboMqtt.Core/PacketTypes/AuthPacket.cs
@@ -0,0 +1,76 @@
+// -----------------------------------------------------------------------
+//
+// Copyright (C) 2024 - 2024 Petabridge, LLC
+//
+// -----------------------------------------------------------------------
+
+namespace TurboMqtt.Core.PacketTypes;
+
+///
+/// Used for authentication exchange or error reporting concerning authentication.
+///
+///
+/// This packet is only applicable in MQTT 5.0 and is used both in the initial connection phase and for dynamic re-authentication.
+///
+public sealed class AuthPacket(string authenticationMethod, AuthReasonCode reasonCode) : MqttPacket
+{
+ // turn the reason code into a ReasonString
+
+ public override MqttPacketType PacketType => MqttPacketType.Auth;
+
+ ///
+ /// The Reason Code for the AUTH packet, which indicates the status of the authentication or any authentication errors.
+ ///
+ public AuthReasonCode ReasonCode { get; } = reasonCode;
+
+ // MQTT 5.0 - Optional Properties
+ ///
+ /// Authentication Method, used to specify the method of authentication.
+ ///
+ public string AuthenticationMethod { get; } = authenticationMethod; // Required if Auth Packet is used
+
+ ///
+ /// Authentication Data, typically containing credentials or challenge/response data, depending on the auth method.
+ ///
+ public ReadOnlyMemory AuthenticationData { get; set; }
+
+ ///
+ /// User Properties, 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.
+ ///
+ public IReadOnlyDictionary? UserProperties { get; set; }
+
+ ///
+ /// Reason String providing additional information about the authentication status.
+ ///
+ public string? ReasonString { get; set; } = reasonCode.ToReasonString();
+
+ public override string ToString()
+ {
+ return $"Auth: [ReasonCode={ReasonCode}]";
+ }
+}
+
+///
+/// Enumerates the reason codes applicable to the AUTH packet in MQTT 5.0.
+///
+public enum AuthReasonCode
+{
+ Success = 0x00,
+ ContinueAuthentication = 0x18,
+ ReAuthenticate = 0x19
+}
+
+internal static class AuthReasonCodeHelpers
+{
+ public static string ToReasonString(this AuthReasonCode reasonCode)
+ {
+ return reasonCode switch
+ {
+ AuthReasonCode.Success => "Success",
+ AuthReasonCode.ContinueAuthentication => "Continue Authentication",
+ AuthReasonCode.ReAuthenticate => "Re-Authenticate",
+ _ => "Unknown Reason Code"
+ };
+ }
+}
\ No newline at end of file
diff --git a/src/TurboMqtt.Core/PacketTypes/ConnAckPacket.cs b/src/TurboMqtt.Core/PacketTypes/ConnAckPacket.cs
new file mode 100644
index 00000000..fd235b35
--- /dev/null
+++ b/src/TurboMqtt.Core/PacketTypes/ConnAckPacket.cs
@@ -0,0 +1,52 @@
+// -----------------------------------------------------------------------
+//
+// Copyright (C) 2024 - 2024 Petabridge, LLC
+//
+// -----------------------------------------------------------------------
+
+namespace TurboMqtt.Core.PacketTypes;
+
+///
+/// Used by the broker to acknowledge a connection request from a client.
+///
+public sealed class ConnAckPacket : MqttPacket
+{
+ public override MqttPacketType PacketType => MqttPacketType.ConnAck;
+
+ public bool SessionPresent { get; set; }
+ public ConnAckReasonCode ReasonCode { get; set; } // Enum defined below
+
+ // MQTT 5.0 - Optional Properties
+ public IReadOnlyDictionary? Properties { get; set; }
+
+ public override string ToString()
+ {
+ return $"ConnAck: [SessionPresent={SessionPresent}] [ReasonCode={ReasonCode}]";
+ }
+}
+
+public enum ConnAckReasonCode
+{
+ Success = 0x00,
+ UnspecifiedError = 0x80,
+ MalformedPacket = 0x81,
+ ProtocolError = 0x82,
+ ImplementationSpecificError = 0x83,
+ UnsupportedProtocolVersion = 0x84,
+ ClientIdentifierNotValid = 0x85,
+ BadUsernameOrPassword = 0x86,
+ NotAuthorized = 0x87,
+ ServerUnavailable = 0x88,
+ ServerBusy = 0x89,
+ Banned = 0x8A,
+ BadAuthenticationMethod = 0x8C,
+ TopicNameInvalid = 0x90,
+ PacketTooLarge = 0x95,
+ QuotaExceeded = 0x97,
+ PayloadFormatInvalid = 0x99,
+ RetainNotSupported = 0x9A,
+ QoSNotSupported = 0x9B,
+ UseAnotherServer = 0x9C,
+ ServerMoved = 0x9D,
+ ConnectionRateExceeded = 0x9F
+}
\ No newline at end of file
diff --git a/src/TurboMqtt.Core/PacketTypes/ConnectPacket.cs b/src/TurboMqtt.Core/PacketTypes/ConnectPacket.cs
new file mode 100644
index 00000000..6a3a59a3
--- /dev/null
+++ b/src/TurboMqtt.Core/PacketTypes/ConnectPacket.cs
@@ -0,0 +1,37 @@
+// -----------------------------------------------------------------------
+//
+// Copyright (C) 2024 - 2024 Petabridge, LLC
+//
+// -----------------------------------------------------------------------
+
+namespace TurboMqtt.Core.PacketTypes;
+
+///
+/// Used to initiate a connection to the MQTT broker.
+///
+public class ConnectPacket(string clientId) : MqttPacket
+{
+ public override MqttPacketType PacketType => MqttPacketType.Connect;
+
+ public string ClientId { get; } = clientId;
+ public bool CleanSession { get; set; }
+ public ushort KeepAlive { get; set; }
+
+ // MQTT 5.0 - Optional Properties
+ public string? Username { get; set; }
+ public string? Password { get; set; }
+
+ public bool? WillFlag { get; set; }
+ public string? WillTopic { get; set; }
+ public ReadOnlyMemory? WillMessage { get; set; }
+ public QualityOfService? WillQos { get; set; }
+ public bool? WillRetain { get; set; }
+
+ public IReadOnlyDictionary?
+ Properties { get; set; } // Custom properties like Session Expiry Interval, Maximum Packet Size, etc.
+
+ public override string ToString()
+ {
+ return $"Connect: [ClientId={ClientId}] [CleanSession={CleanSession}] [KeepAlive={KeepAlive}]";
+ }
+}
\ No newline at end of file
diff --git a/src/TurboMqtt.Core/PacketTypes/DisconnectPacket.cs b/src/TurboMqtt.Core/PacketTypes/DisconnectPacket.cs
new file mode 100644
index 00000000..da66a009
--- /dev/null
+++ b/src/TurboMqtt.Core/PacketTypes/DisconnectPacket.cs
@@ -0,0 +1,74 @@
+// -----------------------------------------------------------------------
+//
+// Copyright (C) 2024 - 2024 Petabridge, LLC
+//
+// -----------------------------------------------------------------------
+
+namespace TurboMqtt.Core.PacketTypes;
+
+///
+/// Used by a client to indicate that it will disconnect cleanly, or by the server to notify of a disconnect.
+///
+public sealed class DisconnectPacket : MqttPacket
+{
+ public override MqttPacketType PacketType => MqttPacketType.Disconnect;
+
+ // MQTT 5.0 - Optional Reason Code and Properties
+ public DisconnectReasonCode? ReasonCode { get; set; } // MQTT 5.0 only
+
+ ///
+ /// User Properties, 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.
+ ///
+ public IReadOnlyDictionary? UserProperties { get; set; } // MQTT 5.0 only
+
+ ///
+ /// The Server Reference property, available in MQTT 5.0.
+ /// This optional property can suggest another server for the client to use.
+ ///
+ public string? ServerReference { get; set; } // MQTT 5.0 only
+
+ ///
+ /// Session Expiry Interval, available in MQTT 5.0.
+ /// This optional property can indicate the session expiry interval in seconds when the disconnect is initiated.
+ ///
+ public uint? SessionExpiryInterval { get; set; } // MQTT 5.0 only
+
+ public override string ToString()
+ {
+ return $"Disconnect: [ReasonCode={ReasonCode}]";
+ }
+}
+
+public enum DisconnectReasonCode
+{
+ NormalDisconnection = 0x00,
+ DisconnectWithWillMessage = 0x04,
+ UnspecifiedError = 0x80,
+ MalformedPacket = 0x81,
+ ProtocolError = 0x82,
+ ImplementationSpecificError = 0x83,
+ NotAuthorized = 0x87,
+ ServerBusy = 0x89,
+ ServerShuttingDown = 0x8B,
+ KeepAliveTimeout = 0x8D,
+ SessionTakenOver = 0x8E,
+ TopicFilterInvalid = 0x8F,
+ TopicNameInvalid = 0x90,
+ ReceiveMaximumExceeded = 0x93,
+ TopicAliasInvalid = 0x94,
+ PacketTooLarge = 0x95,
+ MessageRateTooHigh = 0x96,
+ QuotaExceeded = 0x97,
+ AdministrativeAction = 0x98,
+ PayloadFormatInvalid = 0x99,
+ RetainNotSupported = 0x9A,
+ QoSNotSupported = 0x9B,
+ UseAnotherServer = 0x9C,
+ ServerMoved = 0x9D,
+ SharedSubscriptionsNotSupported = 0x9E,
+ ConnectionRateExceeded = 0x9F,
+ MaximumConnectTime = 0xA0,
+ SubscriptionIdentifiersNotSupported = 0xA1,
+ WildcardSubscriptionsNotSupported = 0xA2
+}
\ No newline at end of file
diff --git a/src/TurboMqtt.Core/PacketTypes/MqttPacket.cs b/src/TurboMqtt.Core/PacketTypes/MqttPacket.cs
new file mode 100644
index 00000000..5636482e
--- /dev/null
+++ b/src/TurboMqtt.Core/PacketTypes/MqttPacket.cs
@@ -0,0 +1,41 @@
+// -----------------------------------------------------------------------
+//
+// Copyright (C) 2024 - 2024 Petabridge, LLC
+//
+// -----------------------------------------------------------------------
+
+namespace TurboMqtt.Core.PacketTypes;
+
+///
+/// Base for all MQTT packets.
+///
+public abstract class MqttPacket
+{
+ public abstract MqttPacketType PacketType { get; }
+
+ public virtual bool Duplicate => false;
+
+ public virtual QualityOfService QualityOfService => QualityOfService.AtMostOnce;
+
+ public virtual bool RetainRequested => false;
+
+ public override string ToString()
+ {
+ return
+ $"{GetType().Name}[Type={PacketType}, QualityOfService={QualityOfService}, Duplicate={Duplicate}, Retain={RetainRequested}]";
+ }
+}
+
+///
+/// Base for MQTT packets that require a packet identifier.
+///
+public abstract class MqttPacketWithId : MqttPacket
+{
+ ///
+ /// The unique identifier assigned to the packet.
+ ///
+ ///
+ /// Not all packets require an identifier.
+ ///
+ public NonZeroUInt32 PacketId { get; set; }
+}
\ No newline at end of file
diff --git a/src/TurboMqtt.Core/PacketTypes/PingReqPacket.cs b/src/TurboMqtt.Core/PacketTypes/PingReqPacket.cs
new file mode 100644
index 00000000..2ba1015b
--- /dev/null
+++ b/src/TurboMqtt.Core/PacketTypes/PingReqPacket.cs
@@ -0,0 +1,29 @@
+// -----------------------------------------------------------------------
+//
+// Copyright (C) 2024 - 2024 Petabridge, LLC
+//
+// -----------------------------------------------------------------------
+
+namespace TurboMqtt.Core.PacketTypes;
+
+///
+/// Packet sent to the client by the server in response to a .
+///
+///
+/// Used to keep the connection alive.
+///
+public sealed class PingReqPacket : MqttPacket
+{
+ public static readonly PingReqPacket Instance = new PingReqPacket();
+
+ private PingReqPacket()
+ {
+ }
+
+ public override MqttPacketType PacketType => MqttPacketType.PingReq;
+
+ public override string ToString()
+ {
+ return "PingReq";
+ }
+}
\ No newline at end of file
diff --git a/src/TurboMqtt.Core/PacketTypes/PingRespPacket.cs b/src/TurboMqtt.Core/PacketTypes/PingRespPacket.cs
new file mode 100644
index 00000000..960ea03e
--- /dev/null
+++ b/src/TurboMqtt.Core/PacketTypes/PingRespPacket.cs
@@ -0,0 +1,26 @@
+// -----------------------------------------------------------------------
+//
+// Copyright (C) 2024 - 2024 Petabridge, LLC
+//
+// -----------------------------------------------------------------------
+
+namespace TurboMqtt.Core.PacketTypes;
+
+///
+/// Packet sent to the client by the server in response to a .
+///
+public sealed class PingRespPacket : MqttPacket
+{
+ public static readonly PingRespPacket Instance = new PingRespPacket();
+
+ private PingRespPacket()
+ {
+ }
+
+ public override MqttPacketType PacketType => MqttPacketType.PingResp;
+
+ public override string ToString()
+ {
+ return "PingResp";
+ }
+}
\ No newline at end of file
diff --git a/src/TurboMqtt.Core/PacketTypes/PubCompPacket.cs b/src/TurboMqtt.Core/PacketTypes/PubCompPacket.cs
new file mode 100644
index 00000000..b2aca211
--- /dev/null
+++ b/src/TurboMqtt.Core/PacketTypes/PubCompPacket.cs
@@ -0,0 +1,47 @@
+// -----------------------------------------------------------------------
+//
+// Copyright (C) 2024 - 2024 Petabridge, LLC
+//
+// -----------------------------------------------------------------------
+
+namespace TurboMqtt.Core.PacketTypes;
+
+///
+/// Used to acknowledge the receipt of a from the client.
+/// This is the final packet in the QoS 2 message flow.
+///
+public sealed class PubCompPacket : MqttPacketWithId
+{
+ public override MqttPacketType PacketType => MqttPacketType.PubComp;
+
+ // MQTT 5.0 - Optional Reason Code and Properties
+ ///
+ /// The Reason Code for the PUBCOMP, available in MQTT 5.0.
+ ///
+ public PubCompReasonCode? ReasonCode { get; set; } // MQTT 5.0 only
+
+ ///
+ /// The Reason String for the PUBREC, available in MQTT 5.0.
+ ///
+ public string ReasonString { get; set; } = string.Empty;
+
+ ///
+ /// User Properties, 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.
+ ///
+ public IReadOnlyDictionary? UserProperties { get; set; } // MQTT 5.0 only
+
+ public override string ToString()
+ {
+ return $"PubComp: [PacketIdentifier={PacketId}], [ReasonCode={ReasonCode}], [ReasonString={ReasonString}]";
+ }
+}
+
+///
+/// Enum for PUBCOMP reason codes, using the same as PUBREC for simplicity and because MQTT 5.0 reuses these
+///
+public enum PubCompReasonCode
+{
+ Success = 0x00,
+ PacketIdentifierNotFound = 0x92
+}
\ No newline at end of file
diff --git a/src/TurboMqtt.Core/PacketTypes/PubRecPacket.cs b/src/TurboMqtt.Core/PacketTypes/PubRecPacket.cs
new file mode 100644
index 00000000..eeeaaa67
--- /dev/null
+++ b/src/TurboMqtt.Core/PacketTypes/PubRecPacket.cs
@@ -0,0 +1,54 @@
+// -----------------------------------------------------------------------
+//
+// Copyright (C) 2024 - 2024 Petabridge, LLC
+//
+// -----------------------------------------------------------------------
+
+namespace TurboMqtt.Core.PacketTypes;
+
+///
+/// Used to acknowledge the receipt of a Publish packet with .
+/// This packet type is part of the QoS 2 message flow.
+///
+public sealed class PubRecPacket : MqttPacketWithId
+{
+ public override MqttPacketType PacketType => MqttPacketType.PubRec;
+
+ // MQTT 5.0 - Optional Reason Code and Properties
+ ///
+ /// The Reason Code for the PUBREC, available in MQTT 5.0.
+ ///
+ public PubRecReasonCode? ReasonCode { get; set; } // MQTT 5.0 only
+
+ ///
+ /// The Reason String for the PUBREC, available in MQTT 5.0.
+ ///
+ public string ReasonString { get; set; } = string.Empty;
+
+ ///
+ /// User Properties, 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.
+ ///
+ public IReadOnlyDictionary? UserProperties { get; set; } // MQTT 5.0 only
+
+ public override string ToString()
+ {
+ return $"PubRec: [PacketIdentifier={PacketId}], [ReasonCode={ReasonCode}], [ReasonString={ReasonString}]";
+ }
+}
+
+///
+/// Enum for PUBREC and PUBCOMP reason codes (as they share the same codes)
+///
+public enum PubRecReasonCode
+{
+ Success = 0x00,
+ NoMatchingSubscribers = 0x10,
+ UnspecifiedError = 0x80,
+ ImplementationSpecificError = 0x83,
+ NotAuthorized = 0x87,
+ TopicNameInvalid = 0x90,
+ PacketIdentifierInUse = 0x91,
+ QuotaExceeded = 0x97,
+ PayloadFormatInvalid = 0x99
+}
\ No newline at end of file
diff --git a/src/TurboMqtt.Core/PacketTypes/PubRelPacket.cs b/src/TurboMqtt.Core/PacketTypes/PubRelPacket.cs
new file mode 100644
index 00000000..786c041a
--- /dev/null
+++ b/src/TurboMqtt.Core/PacketTypes/PubRelPacket.cs
@@ -0,0 +1,47 @@
+// -----------------------------------------------------------------------
+//
+// Copyright (C) 2024 - 2024 Petabridge, LLC
+//
+// -----------------------------------------------------------------------
+
+namespace TurboMqtt.Core.PacketTypes;
+
+///
+/// Used to acknowledge the receipt of a from the broker.
+/// This packet type is part of the QoS 2 message flow.
+///
+public sealed class PubRelPacket : MqttPacketWithId
+{
+ public override MqttPacketType PacketType => MqttPacketType.PubRel;
+
+ // MQTT 5.0 - Optional Reason Code and Properties
+ ///
+ /// The Reason Code for the PUBREL, available in MQTT 5.0.
+ ///
+ public PubRelReasonCode? ReasonCode { get; set; } // MQTT 5.0 only
+
+ ///
+ /// The Reason String for the PUBREC, available in MQTT 5.0.
+ ///
+ public string ReasonString { get; set; } = string.Empty;
+
+ ///
+ /// User Properties, 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.
+ ///
+ public IReadOnlyDictionary? UserProperties { get; set; } // MQTT 5.0 only
+
+ public override string ToString()
+ {
+ return $"PubRel: [PacketIdentifier={PacketId}], [ReasonCode={ReasonCode}], [ReasonString={ReasonString}]";
+ }
+}
+
+///
+/// Enum for PUBREL reason codes (typically these would be simpler as successful flow is usually assumed)
+///
+public enum PubRelReasonCode
+{
+ Success = 0x00,
+ PacketIdentifierNotFound = 0x92
+}
\ No newline at end of file
diff --git a/src/TurboMqtt.Core/PacketTypes/PublishAckPacket.cs b/src/TurboMqtt.Core/PacketTypes/PublishAckPacket.cs
new file mode 100644
index 00000000..7e05d58a
--- /dev/null
+++ b/src/TurboMqtt.Core/PacketTypes/PublishAckPacket.cs
@@ -0,0 +1,36 @@
+// -----------------------------------------------------------------------
+//
+// Copyright (C) 2024 - 2024 Petabridge, LLC
+//
+// -----------------------------------------------------------------------
+
+namespace TurboMqtt.Core.PacketTypes;
+
+using static MqttPubAckHelpers;
+
+///
+/// Used to acknowledge the receipt of a Publish packet.
+///
+public sealed class PublishAckPacket : MqttPacketWithId
+{
+ public override MqttPacketType PacketType => MqttPacketType.PubAck;
+
+ // MQTT 5.0 Optional Properties
+
+ ///
+ /// Reason Code for the acknowledgment, available in MQTT 5.0.
+ /// This field is optional and provides more detailed acknowledgment information.
+ ///
+ public MqttPubAckReasonCode ReasonCode { get; set; }
+
+ ///
+ /// User Properties, available in MQTT 5.0.
+ /// These are key-value pairs that can be sent to provide additional information in the acknowledgment.
+ ///
+ public string ReasonString => ReasonCodeToString(ReasonCode);
+
+ public override string ToString()
+ {
+ return $"PubAck: [PacketIdentifier={PacketId}] [ReasonCode={ReasonCode}]";
+ }
+}
\ No newline at end of file
diff --git a/src/TurboMqtt.Core/PacketTypes/PublishPacket.cs b/src/TurboMqtt.Core/PacketTypes/PublishPacket.cs
new file mode 100644
index 00000000..639d3640
--- /dev/null
+++ b/src/TurboMqtt.Core/PacketTypes/PublishPacket.cs
@@ -0,0 +1,71 @@
+// -----------------------------------------------------------------------
+//
+// Copyright (C) 2024 - 2024 Petabridge, LLC
+//
+// -----------------------------------------------------------------------
+
+namespace TurboMqtt.Core.PacketTypes;
+
+///
+/// Used to send data to the server or client.
+///
+/// The delivery guarantee for this packet.
+/// Is this packet a duplicate?
+/// Indicates whether or not this value has been retained by the MQTT broker.
+public sealed class PublishPacket(QualityOfService Qos, bool Duplicate, bool RetainRequested) : MqttPacketWithId
+{
+ public override MqttPacketType PacketType => MqttPacketType.Publish;
+
+ public override bool Duplicate { get; } = Duplicate;
+
+ public override QualityOfService QualityOfService { get; } = Qos;
+
+ public override bool RetainRequested { get; } = RetainRequested;
+
+ ///
+ /// Optional for
+ ///
+ public string? TopicName { get; set; }
+
+ // Payload
+ public ReadOnlyMemory Payload { get; set; } = ReadOnlyMemory.Empty;
+
+ // MQTT 3.1.1 and 5.0 - Optional Properties
+
+ ///
+ /// The Content Type property, available in MQTT 5.0.
+ /// This property is optional and indicates the MIME type of the application message.
+ ///
+ public string? ContentType { get; set; } // MQTT 5.0 only
+
+ ///
+ /// Response Topic property, available in MQTT 5.0.
+ /// It specifies the topic name for a response message.
+ ///
+ public string? ResponseTopic { get; set; } // MQTT 5.0 only
+
+ ///
+ /// Correlation Data property, available in MQTT 5.0.
+ /// This property is used by the sender of the request message to identify which request the response message is for when it receives a response.
+ ///
+ public ReadOnlyMemory? CorrelationData { get; set; } // MQTT 5.0 only
+
+ ///
+ /// 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.
+ ///
+ public IReadOnlyDictionary? UserProperties { get; set; } // MQTT 5.0 only
+
+ ///
+ /// Subscription Identifiers, available in MQTT 5.0.
+ /// This property allows associating the publication with multiple subscriptions.
+ /// Each identifier corresponds to a different subscription that matches the published message.
+ ///
+ public IReadOnlyList? SubscriptionIdentifiers { get; set; } // MQTT 5.0 only
+
+ public override string ToString()
+ {
+ return
+ $"Publish: [Topic={TopicName}] [PayloadLength={Payload.Length}] [QoSLevel={QualityOfService}] [Dup={Duplicate}] [Retain={RetainRequested}] [PacketIdentifier={PacketId}]";
+ }
+}
\ No newline at end of file
diff --git a/src/TurboMqtt.Core/PacketTypes/SubscribeAckPacket.cs b/src/TurboMqtt.Core/PacketTypes/SubscribeAckPacket.cs
new file mode 100644
index 00000000..8d581d95
--- /dev/null
+++ b/src/TurboMqtt.Core/PacketTypes/SubscribeAckPacket.cs
@@ -0,0 +1,36 @@
+// -----------------------------------------------------------------------
+//
+// Copyright (C) 2024 - 2024 Petabridge, LLC
+//
+// -----------------------------------------------------------------------
+
+namespace TurboMqtt.Core.PacketTypes;
+
+public enum MqttSubscribeReasonCode
+{
+ // 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
+
+ // 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
+ PacketIdentifierInUse = 0x91, // MQTT 5.0
+ QuotaExceeded = 0x97, // MQTT 5.0
+ SharedSubscriptionsNotSupported = 0x9E, // MQTT 5.0
+ SubscriptionIdentifiersNotSupported = 0xA1, // MQTT 5.0
+ WildcardSubscriptionsNotSupported = 0xA2, // MQTT 5.0
+}
+
+public class SubscribeAckPacket : MqttPacketWithId
+{
+ public override MqttPacketType PacketType => MqttPacketType.SubAck;
+
+ ///
+ /// The reason codes for each topic subscription.
+ ///
+ public IReadOnlyList ReasonCodes { get; set; } = Array.Empty();
+}
\ No newline at end of file
diff --git a/src/TurboMqtt.Core/PacketTypes/SubscribePacket.cs b/src/TurboMqtt.Core/PacketTypes/SubscribePacket.cs
new file mode 100644
index 00000000..41d7610d
--- /dev/null
+++ b/src/TurboMqtt.Core/PacketTypes/SubscribePacket.cs
@@ -0,0 +1,135 @@
+// -----------------------------------------------------------------------
+//
+// Copyright (C) 2024 - 2024 Petabridge, LLC
+//
+// -----------------------------------------------------------------------
+
+namespace TurboMqtt.Core.PacketTypes;
+
+public sealed class SubscribePacket : MqttPacketWithId
+{
+ public override MqttPacketType PacketType => MqttPacketType.Subscribe;
+
+ ///
+ /// The unique identity of this subscription for this client.
+ ///
+ ///
+ /// Must be a value greater than 0.
+ ///
+ public NonZeroUInt32 SubscriptionIdentifier { get; set; }
+
+ ///
+ /// The set of topics we're subscribing to.
+ ///
+ public IReadOnlyList Topics { get; set; } = Array.Empty();
+
+ ///
+ /// 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.
+ ///
+ public IReadOnlyDictionary? UserProperties { get; set; } // MQTT 5.0 only
+
+ public override string ToString()
+ {
+ return
+ $"Subscribe: [PacketIdentifier={PacketId}] [SubscriptionIdentifier={SubscriptionIdentifier}] [Topics={string.Join(", ", Topics.Select(c => c))}]";
+ }
+}
+
+public sealed class TopicSubscription(string topic)
+{
+ ///
+ /// The topic to subscribe to.
+ ///
+ public string Topic { get; } = topic;
+
+ ///
+ /// The subscription options - QoS, No Local, Retain As Published, Retain Handling.
+ ///
+ ///
+ /// Some of these are MQTT 5.0 features and will not be used in MQTT 3.1.1 or 3.0.
+ ///
+ 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
+{
+ ///
+ /// Gets or sets the Quality of Service level to use when sending messages to the client.
+ ///
+ public QualityOfService QoS { get; set; }
+
+ ///
+ /// MQTT 5.0 Feature: indicates whether or not the sender can receive its own messages.
+ ///
+ public bool NoLocal { get; set; }
+
+ ///
+ /// MQTT 5.0 Feature: indicates whether or not the message should be retained by the broker.
+ ///
+ public bool RetainAsPublished { get; set; }
+
+ ///
+ /// MQTT 5.0 Feature: indicates how the broker should handle retained messages.
+ ///
+ 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;
+ }
+}
\ No newline at end of file
diff --git a/src/TurboMqtt.Core/PacketTypes/UnsubscribeAckPacket.cs b/src/TurboMqtt.Core/PacketTypes/UnsubscribeAckPacket.cs
new file mode 100644
index 00000000..a55d7306
--- /dev/null
+++ b/src/TurboMqtt.Core/PacketTypes/UnsubscribeAckPacket.cs
@@ -0,0 +1,63 @@
+// -----------------------------------------------------------------------
+//
+// Copyright (C) 2024 - 2024 Petabridge, LLC
+//
+// -----------------------------------------------------------------------
+
+namespace TurboMqtt.Core.PacketTypes;
+
+///
+/// Unsubscribe Acknowledgment Reason Codes.
+///
+///
+/// This is an MQTT 5.0 feature.
+///
+public enum MqttUnsubscribeReasonCode
+{
+ // MQTT 5.0 specific reason codes
+ Success = 0x00, // The subscription is deleted successfully, MQTT 5.0
+ NoSubscriptionExisted = 0x11, // No subscription existed for the specified topic filter, MQTT 5.0
+ UnspecifiedError = 0x80, // The unsubscribe could not be completed and the reason is not specified, MQTT 5.0
+
+ ImplementationSpecificError =
+ 0x83, // The unsubscribe could not be completed due to an implementation-specific error, MQTT 5.0
+ NotAuthorized = 0x87, // The client was not authorized to unsubscribe, MQTT 5.0
+ TopicFilterInvalid = 0x8F, // The specified topic filter is invalid, MQTT 5.0
+ PacketIdentifierInUse = 0x91, // The Packet Identifier is already in use, MQTT 5.0
+}
+
+///
+/// Used to acknowledge an unsubscribe request.
+///
+public sealed class UnsubscribeAckPacket : MqttPacketWithId
+{
+ public override MqttPacketType PacketType => MqttPacketType.UnsubAck;
+
+ ///
+ /// Set of unsubscribe reason codes.
+ ///
+ ///
+ /// Available in MQTT 5.0.
+ ///
+ public IReadOnlyList ReasonCodes { get; set; } =
+ Array.Empty();
+
+ ///
+ /// Reason given by the server for the unsubscribe.
+ ///
+ ///
+ /// Available in MQTT 5.0.
+ ///
+ public string ReasonString { get; set; } = string.Empty;
+
+ ///
+ /// 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.
+ ///
+ public IReadOnlyDictionary? UserProperties { get; set; } // MQTT 5.0 only
+
+ public override string ToString()
+ {
+ return $"Unsubscribe Ack: [PacketIdentifier={PacketId}] [ReasonCodes={string.Join(", ", ReasonCodes)}]";
+ }
+}
\ No newline at end of file
diff --git a/src/TurboMqtt.Core/PacketTypes/UnsubscribePacket.cs b/src/TurboMqtt.Core/PacketTypes/UnsubscribePacket.cs
new file mode 100644
index 00000000..2f6254ea
--- /dev/null
+++ b/src/TurboMqtt.Core/PacketTypes/UnsubscribePacket.cs
@@ -0,0 +1,31 @@
+// -----------------------------------------------------------------------
+//
+// Copyright (C) 2024 - 2024 Petabridge, LLC
+//
+// -----------------------------------------------------------------------
+
+namespace TurboMqtt.Core.PacketTypes;
+
+///
+/// Used to unsubscribe from topics.
+///
+public sealed class UnsubscribePacket : MqttPacketWithId
+{
+ public override MqttPacketType PacketType => MqttPacketType.Unsubscribe;
+
+ ///
+ /// The set of topics we're unsubscribing from.
+ ///
+ public IReadOnlyList Topics { get; set; } = Array.Empty();
+
+ ///
+ /// 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.
+ ///
+ public IReadOnlyDictionary? UserProperties { get; set; } // MQTT 5.0 only
+
+ public override string ToString()
+ {
+ return $"Unsubscribe: [PacketIdentifier={PacketId}] [Topics={string.Join(", ", Topics)}]";
+ }
+}
\ No newline at end of file
diff --git a/src/TurboMqtt.Core/QualityOfService.cs b/src/TurboMqtt.Core/QualityOfService.cs
new file mode 100644
index 00000000..8a46a71b
--- /dev/null
+++ b/src/TurboMqtt.Core/QualityOfService.cs
@@ -0,0 +1,17 @@
+// -----------------------------------------------------------------------
+//
+// Copyright (C) 2024 - 2024 Petabridge, LLC
+//
+// -----------------------------------------------------------------------
+
+namespace TurboMqtt.Core;
+
+///
+/// QoS value - corresponds to the MQTT specification.
+///
+public enum QualityOfService
+{
+ AtMostOnce = 0,
+ AtLeastOnce = 1,
+ ExactlyOnce = 2
+}
\ No newline at end of file
diff --git a/src/TurboMqtt.Core/TurboMqtt.Core.csproj b/src/TurboMqtt.Core/TurboMqtt.Core.csproj
new file mode 100644
index 00000000..3a635329
--- /dev/null
+++ b/src/TurboMqtt.Core/TurboMqtt.Core.csproj
@@ -0,0 +1,9 @@
+
+
+
+ net8.0
+ enable
+ enable
+
+
+