// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Buffers; using System.Collections.Generic; using System.Runtime.CompilerServices; using MQTTnet.Adapter; using MQTTnet.Exceptions; using MQTTnet.Packets; using MQTTnet.Protocol; namespace MQTTnet.Formatter.V3; public sealed class MqttV3PacketFormatter : IMqttPacketFormatter { const int FixedHeaderSize = 1; static readonly MqttDisconnectPacket DisconnectPacket = new(); readonly MqttBufferReader _bufferReader = new(); readonly MqttBufferWriter _bufferWriter; readonly MqttProtocolVersion _mqttProtocolVersion; public MqttV3PacketFormatter(MqttBufferWriter bufferWriter, MqttProtocolVersion mqttProtocolVersion) { _bufferWriter = bufferWriter; _mqttProtocolVersion = mqttProtocolVersion; } public MqttPacket? Decode(ReceivedMqttPacket receivedPacket) { if (receivedPacket.TotalLength == 0) { return null; } var controlPacketType = receivedPacket.FixedHeader >> 4; if (controlPacketType is < 1 or > 14) { throw new MqttProtocolViolationException($"The packet type is invalid ({controlPacketType})."); } switch ((MqttControlPacketType)controlPacketType) { case MqttControlPacketType.Publish: return DecodePublishPacket(receivedPacket); case MqttControlPacketType.PubAck: return DecodePubAckPacket(receivedPacket.Body); case MqttControlPacketType.PubRec: return DecodePubRecPacket(receivedPacket.Body); case MqttControlPacketType.PubRel: return DecodePubRelPacket(receivedPacket.Body); case MqttControlPacketType.PubComp: return DecodePubCompPacket(receivedPacket.Body); case MqttControlPacketType.PingReq: return MqttPingReqPacket.Instance; case MqttControlPacketType.PingResp: return MqttPingRespPacket.Instance; case MqttControlPacketType.Connect: return DecodeConnectPacket(receivedPacket.Body); case MqttControlPacketType.ConnAck: if (_mqttProtocolVersion == MqttProtocolVersion.V311) { return DecodeConnAckPacketV311(receivedPacket.Body); } return DecodeConnAckPacket(receivedPacket.Body); case MqttControlPacketType.Disconnect: return DisconnectPacket; case MqttControlPacketType.Subscribe: return DecodeSubscribePacket(receivedPacket.Body); case MqttControlPacketType.SubAck: return DecodeSubAckPacket(receivedPacket.Body); case MqttControlPacketType.Unsubscribe: return DecodeUnsubscribePacket(receivedPacket.Body); case MqttControlPacketType.UnsubAck: return DecodeUnsubAckPacket(receivedPacket.Body); case MqttControlPacketType.Auth: default: throw new MqttProtocolViolationException($"Packet type ({controlPacketType}) not supported."); } } public MqttPacketBuffer Encode(MqttPacket packet) { ArgumentNullException.ThrowIfNull(packet); // Leave enough headspace for max header size (fixed + 4 variable remaining length = 5 bytes) _bufferWriter.Reset(5); _bufferWriter.Seek(5); var fixedHeader = EncodePacket(packet, _bufferWriter); var remainingLength = (uint)(_bufferWriter.Length - 5); ReadOnlySequence payload = default; if (packet is MqttPublishPacket publishPacket) { payload = publishPacket.Payload; remainingLength += (uint)payload.Length; } var remainingLengthSize = MqttBufferWriter.GetVariableByteIntegerSize(remainingLength); var headerSize = FixedHeaderSize + remainingLengthSize; var headerOffset = 5 - headerSize; // Position cursor on correct offset on beginning of array (has leading 0x0) _bufferWriter.Seek(headerOffset); _bufferWriter.WriteByte(fixedHeader); _bufferWriter.WriteVariableByteInteger(remainingLength); var firstSegment = new ArraySegment(_bufferWriter.GetBuffer(), headerOffset, _bufferWriter.Length - headerOffset); return payload.Length == 0 ? new MqttPacketBuffer(firstSegment) : new MqttPacketBuffer(firstSegment, payload); } MqttConnAckPacket DecodeConnAckPacket(ArraySegment body) { ThrowIfBodyIsEmpty(body); _bufferReader.SetBuffer(body.Array!, body.Offset, body.Count); var packet = new MqttConnAckPacket(); _bufferReader.ReadByte(); // Reserved. packet.ReturnCode = (MqttConnectReturnCode)_bufferReader.ReadByte(); return packet; } MqttConnAckPacket DecodeConnAckPacketV311(ArraySegment body) { ThrowIfBodyIsEmpty(body); _bufferReader.SetBuffer(body.Array!, body.Offset, body.Count); var packet = new MqttConnAckPacket(); var acknowledgeFlags = _bufferReader.ReadByte(); packet.IsSessionPresent = (acknowledgeFlags & 0x1) > 0; packet.ReturnCode = (MqttConnectReturnCode)_bufferReader.ReadByte(); return packet; } MqttConnectPacket DecodeConnectPacket(ArraySegment body) { ThrowIfBodyIsEmpty(body); _bufferReader.SetBuffer(body.Array!, body.Offset, body.Count); var protocolName = _bufferReader.ReadString(); var protocolVersion = _bufferReader.ReadByte(); if (protocolName != "MQTT" && protocolName != "MQIsdp") { throw new MqttProtocolViolationException("MQTT protocol name do not match MQTT v3."); } var tryPrivate = (protocolVersion & 0x80) > 0; protocolVersion &= 0x7F; if (protocolVersion != 3 && protocolVersion != 4) { throw new MqttProtocolViolationException("MQTT protocol version do not match MQTT v3."); } var packet = new MqttConnectPacket { TryPrivate = tryPrivate }; var connectFlags = _bufferReader.ReadByte(); if ((connectFlags & 0x1) > 0) { throw new MqttProtocolViolationException("The first bit of the Connect Flags must be set to 0."); } packet.CleanSession = (connectFlags & 0x2) > 0; var willFlag = (connectFlags & 0x4) > 0; var willQoS = (connectFlags & 0x18) >> 3; var willRetain = (connectFlags & 0x20) > 0; var passwordFlag = (connectFlags & 0x40) > 0; var usernameFlag = (connectFlags & 0x80) > 0; packet.KeepAlivePeriod = _bufferReader.ReadTwoByteInteger(); packet.ClientId = _bufferReader.ReadString(); if (willFlag) { packet.WillFlag = true; packet.WillQoS = (MqttQualityOfServiceLevel)willQoS; packet.WillRetain = willRetain; packet.WillTopic = _bufferReader.ReadString(); packet.WillMessage = _bufferReader.ReadBinaryData(); } if (usernameFlag) { packet.Username = _bufferReader.ReadString(); } if (passwordFlag) { packet.Password = _bufferReader.ReadBinaryData(); } ValidateConnectPacket(packet); return packet; } MqttPubAckPacket DecodePubAckPacket(ArraySegment body) { ThrowIfBodyIsEmpty(body); _bufferReader.SetBuffer(body.Array!, body.Offset, body.Count); return new MqttPubAckPacket { PacketIdentifier = _bufferReader.ReadTwoByteInteger() }; } MqttPubCompPacket DecodePubCompPacket(ArraySegment body) { ThrowIfBodyIsEmpty(body); _bufferReader.SetBuffer(body.Array!, body.Offset, body.Count); return new MqttPubCompPacket { PacketIdentifier = _bufferReader.ReadTwoByteInteger() }; } MqttPublishPacket DecodePublishPacket(ReceivedMqttPacket receivedMqttPacket) { ThrowIfBodyIsEmpty(receivedMqttPacket.Body); _bufferReader.SetBuffer(receivedMqttPacket.Body.Array!, receivedMqttPacket.Body.Offset, receivedMqttPacket.Body.Count); var retain = (receivedMqttPacket.FixedHeader & 0x1) > 0; var qualityOfServiceLevel = (MqttQualityOfServiceLevel)((receivedMqttPacket.FixedHeader >> 1) & 0x3); var dup = (receivedMqttPacket.FixedHeader & 0x8) > 0; var topic = _bufferReader.ReadString(); ushort packetIdentifier = 0; if (qualityOfServiceLevel > MqttQualityOfServiceLevel.AtMostOnce) { packetIdentifier = _bufferReader.ReadTwoByteInteger(); } var packet = new MqttPublishPacket { PacketIdentifier = packetIdentifier, Retain = retain, Topic = topic, QualityOfServiceLevel = qualityOfServiceLevel, Dup = dup }; if (!_bufferReader.EndOfStream) { packet.PayloadSegment = new ArraySegment(_bufferReader.ReadRemainingData()); } return packet; } MqttPubRecPacket DecodePubRecPacket(ArraySegment body) { ThrowIfBodyIsEmpty(body); _bufferReader.SetBuffer(body.Array!, body.Offset, body.Count); return new MqttPubRecPacket { PacketIdentifier = _bufferReader.ReadTwoByteInteger() }; } MqttPubRelPacket DecodePubRelPacket(ArraySegment body) { ThrowIfBodyIsEmpty(body); _bufferReader.SetBuffer(body.Array!, body.Offset, body.Count); return new MqttPubRelPacket { PacketIdentifier = _bufferReader.ReadTwoByteInteger() }; } MqttSubAckPacket DecodeSubAckPacket(ArraySegment body) { ThrowIfBodyIsEmpty(body); _bufferReader.SetBuffer(body.Array!, body.Offset, body.Count); var packet = new MqttSubAckPacket { PacketIdentifier = _bufferReader.ReadTwoByteInteger(), ReasonCodes = new List(_bufferReader.BytesLeft) }; while (!_bufferReader.EndOfStream) { packet.ReasonCodes.Add((MqttSubscribeReasonCode)_bufferReader.ReadByte()); } return packet; } MqttSubscribePacket DecodeSubscribePacket(ArraySegment body) { ThrowIfBodyIsEmpty(body); _bufferReader.SetBuffer(body.Array!, body.Offset, body.Count); var packet = new MqttSubscribePacket { PacketIdentifier = _bufferReader.ReadTwoByteInteger() }; while (!_bufferReader.EndOfStream) { var topicFilter = new MqttTopicFilter { Topic = _bufferReader.ReadString(), QualityOfServiceLevel = (MqttQualityOfServiceLevel)_bufferReader.ReadByte() }; packet.TopicFilters.Add(topicFilter); } return packet; } MqttUnsubAckPacket DecodeUnsubAckPacket(ArraySegment body) { ThrowIfBodyIsEmpty(body); _bufferReader.SetBuffer(body.Array!, body.Offset, body.Count); return new MqttUnsubAckPacket { PacketIdentifier = _bufferReader.ReadTwoByteInteger() }; } MqttUnsubscribePacket DecodeUnsubscribePacket(ArraySegment body) { ThrowIfBodyIsEmpty(body); _bufferReader.SetBuffer(body.Array!, body.Offset, body.Count); var packet = new MqttUnsubscribePacket { PacketIdentifier = _bufferReader.ReadTwoByteInteger() }; while (!_bufferReader.EndOfStream) { packet.TopicFilters!.Add(_bufferReader.ReadString()); } return packet; } static byte EncodeConnAckPacket(MqttConnAckPacket packet, MqttBufferWriter bufferWriter) { bufferWriter.WriteByte(0); // Reserved. bufferWriter.WriteByte((byte)packet.ReturnCode); return MqttBufferWriter.BuildFixedHeader(MqttControlPacketType.ConnAck); } static byte EncodeConnAckPacketV311(MqttConnAckPacket packet, MqttBufferWriter bufferWriter) { byte connectAcknowledgeFlags = 0x0; if (packet.IsSessionPresent) { connectAcknowledgeFlags |= 0x1; } bufferWriter.WriteByte(connectAcknowledgeFlags); bufferWriter.WriteByte((byte)packet.ReturnCode); return MqttBufferWriter.BuildFixedHeader(MqttControlPacketType.ConnAck); } static byte EncodeConnectPacket(MqttConnectPacket packet, MqttBufferWriter bufferWriter) { ValidateConnectPacket(packet); bufferWriter.WriteString("MQIsdp"); var protocolVersion = 3; if (packet.TryPrivate) { protocolVersion |= 0x80; } bufferWriter.WriteByte((byte)protocolVersion); byte connectFlags = 0x0; if (packet.CleanSession) { connectFlags |= 0x2; } if (packet.WillFlag) { connectFlags |= 0x4; connectFlags |= (byte)((byte)packet.WillQoS << 3); if (packet.WillRetain) { connectFlags |= 0x20; } } if (packet.Password != null && packet.Username == null) { throw new MqttProtocolViolationException("If the User Name Flag is set to 0, the Password Flag MUST be set to 0 [MQTT-3.1.2-22]."); } if (packet.Password != null) { connectFlags |= 0x40; } if (packet.Username != null) { connectFlags |= 0x80; } bufferWriter.WriteByte(connectFlags); bufferWriter.WriteTwoByteInteger(packet.KeepAlivePeriod); bufferWriter.WriteString(packet.ClientId); if (packet.WillFlag) { bufferWriter.WriteString(packet.WillTopic); bufferWriter.WriteBinary(packet.WillMessage); } if (packet.Username != null) { bufferWriter.WriteString(packet.Username); } if (packet.Password != null) { bufferWriter.WriteBinary(packet.Password); } return MqttBufferWriter.BuildFixedHeader(MqttControlPacketType.Connect); } static byte EncodeConnectPacketV311(MqttConnectPacket packet, MqttBufferWriter bufferWriter) { ValidateConnectPacket(packet); bufferWriter.WriteString("MQTT"); // 3.1.2.2 Protocol Level 4 var protocolVersion = 4; if (packet.TryPrivate) { protocolVersion |= 0x80; } bufferWriter.WriteByte((byte)protocolVersion); byte connectFlags = 0x0; if (packet.CleanSession) { connectFlags |= 0x2; } if (packet.WillFlag) { connectFlags |= 0x4; connectFlags |= (byte)((byte)packet.WillQoS << 3); if (packet.WillRetain) { connectFlags |= 0x20; } } if (packet.Password != null && packet.Username == null) { throw new MqttProtocolViolationException("If the User Name Flag is set to 0, the Password Flag MUST be set to 0 [MQTT-3.1.2-22]."); } if (packet.Password != null) { connectFlags |= 0x40; } if (packet.Username != null) { connectFlags |= 0x80; } bufferWriter.WriteByte(connectFlags); bufferWriter.WriteTwoByteInteger(packet.KeepAlivePeriod); bufferWriter.WriteString(packet.ClientId); if (packet.WillFlag) { bufferWriter.WriteString(packet.WillTopic); bufferWriter.WriteBinary(packet.WillMessage); } if (packet.Username != null) { bufferWriter.WriteString(packet.Username); } if (packet.Password != null) { bufferWriter.WriteBinary(packet.Password); } return MqttBufferWriter.BuildFixedHeader(MqttControlPacketType.Connect); } static byte EncodeEmptyPacket(MqttControlPacketType type) { return MqttBufferWriter.BuildFixedHeader(type); } byte EncodePacket(MqttPacket packet, MqttBufferWriter bufferWriter) { switch (packet) { case MqttConnectPacket connectPacket: if (_mqttProtocolVersion == MqttProtocolVersion.V311) { return EncodeConnectPacketV311(connectPacket, bufferWriter); } return EncodeConnectPacket(connectPacket, bufferWriter); case MqttConnAckPacket connAckPacket: if (_mqttProtocolVersion == MqttProtocolVersion.V311) { return EncodeConnAckPacketV311(connAckPacket, bufferWriter); } return EncodeConnAckPacket(connAckPacket, bufferWriter); case MqttDisconnectPacket _: return EncodeEmptyPacket(MqttControlPacketType.Disconnect); case MqttPingReqPacket _: return EncodeEmptyPacket(MqttControlPacketType.PingReq); case MqttPingRespPacket _: return EncodeEmptyPacket(MqttControlPacketType.PingResp); case MqttPublishPacket publishPacket: return EncodePublishPacket(publishPacket, bufferWriter); case MqttPubAckPacket pubAckPacket: return EncodePubAckPacket(pubAckPacket, bufferWriter); case MqttPubRecPacket pubRecPacket: return EncodePubRecPacket(pubRecPacket, bufferWriter); case MqttPubRelPacket pubRelPacket: return EncodePubRelPacket(pubRelPacket, bufferWriter); case MqttPubCompPacket pubCompPacket: return EncodePubCompPacket(pubCompPacket, bufferWriter); case MqttSubscribePacket subscribePacket: return EncodeSubscribePacket(subscribePacket, bufferWriter); case MqttSubAckPacket subAckPacket: return EncodeSubAckPacket(subAckPacket, bufferWriter); case MqttUnsubscribePacket unsubscribePacket: return EncodeUnsubscribePacket(unsubscribePacket, bufferWriter); case MqttUnsubAckPacket unsubAckPacket: return EncodeUnsubAckPacket(unsubAckPacket, bufferWriter); default: throw new MqttProtocolViolationException("Packet type invalid."); } } static byte EncodePubAckPacket(MqttPubAckPacket packet, MqttBufferWriter bufferWriter) { if (packet.PacketIdentifier == 0) { throw new MqttProtocolViolationException("PubAck packet has no packet identifier."); } bufferWriter.WriteTwoByteInteger(packet.PacketIdentifier); return MqttBufferWriter.BuildFixedHeader(MqttControlPacketType.PubAck); } static byte EncodePubCompPacket(MqttPubCompPacket packet, MqttBufferWriter bufferWriter) { if (packet.PacketIdentifier == 0) { throw new MqttProtocolViolationException("PubComp packet has no packet identifier."); } bufferWriter.WriteTwoByteInteger(packet.PacketIdentifier); return MqttBufferWriter.BuildFixedHeader(MqttControlPacketType.PubComp); } static byte EncodePublishPacket(MqttPublishPacket packet, MqttBufferWriter bufferWriter) { ValidatePublishPacket(packet); bufferWriter.WriteString(packet.Topic); if (packet.QualityOfServiceLevel > MqttQualityOfServiceLevel.AtMostOnce) { if (packet.PacketIdentifier == 0) { throw new MqttProtocolViolationException("Publish packet has no packet identifier."); } bufferWriter.WriteTwoByteInteger(packet.PacketIdentifier); } else { if (packet.PacketIdentifier > 0) { throw new MqttProtocolViolationException("Packet identifier must be empty if QoS == 0 [MQTT-2.3.1-5]."); } } // The payload is the past part of the packet. But it is not added here in order to keep // memory allocation low. byte fixedHeader = 0; if (packet.Retain) { fixedHeader |= 0x01; } fixedHeader |= (byte)((byte)packet.QualityOfServiceLevel << 1); if (packet.Dup) { fixedHeader |= 0x08; } return MqttBufferWriter.BuildFixedHeader(MqttControlPacketType.Publish, fixedHeader); } static byte EncodePubRecPacket(MqttPubRecPacket packet, MqttBufferWriter bufferWriter) { if (packet.PacketIdentifier == 0) { throw new MqttProtocolViolationException("PubRec packet has no packet identifier."); } bufferWriter.WriteTwoByteInteger(packet.PacketIdentifier); return MqttBufferWriter.BuildFixedHeader(MqttControlPacketType.PubRec); } static byte EncodePubRelPacket(MqttPubRelPacket packet, MqttBufferWriter bufferWriter) { if (packet.PacketIdentifier == 0) { throw new MqttProtocolViolationException("PubRel packet has no packet identifier."); } bufferWriter.WriteTwoByteInteger(packet.PacketIdentifier); return MqttBufferWriter.BuildFixedHeader(MqttControlPacketType.PubRel, 0x02); } static byte EncodeSubAckPacket(MqttSubAckPacket packet, MqttBufferWriter bufferWriter) { if (packet.PacketIdentifier == 0) { throw new MqttProtocolViolationException("SubAck packet has no packet identifier."); } bufferWriter.WriteTwoByteInteger(packet.PacketIdentifier); if (packet.ReasonCodes?.Count > 0) { foreach (var packetSubscribeReturnCode in packet.ReasonCodes!) { if (packetSubscribeReturnCode == MqttSubscribeReasonCode.GrantedQoS0) { bufferWriter.WriteByte((byte)MqttSubscribeReturnCode.SuccessMaximumQoS0); } else if (packetSubscribeReturnCode == MqttSubscribeReasonCode.GrantedQoS1) { bufferWriter.WriteByte((byte)MqttSubscribeReturnCode.SuccessMaximumQoS1); } else if (packetSubscribeReturnCode == MqttSubscribeReasonCode.GrantedQoS2) { bufferWriter.WriteByte((byte)MqttSubscribeReturnCode.SuccessMaximumQoS2); } else { bufferWriter.WriteByte((byte)MqttSubscribeReturnCode.Failure); } } } return MqttBufferWriter.BuildFixedHeader(MqttControlPacketType.SubAck); } static byte EncodeSubscribePacket(MqttSubscribePacket packet, MqttBufferWriter bufferWriter) { if (packet.TopicFilters.Count == 0) { throw new MqttProtocolViolationException("At least one topic filter must be set [MQTT-3.8.3-3]."); } if (packet.PacketIdentifier == 0) { throw new MqttProtocolViolationException("Subscribe packet has no packet identifier."); } bufferWriter.WriteTwoByteInteger(packet.PacketIdentifier); if (packet.TopicFilters?.Count > 0) { foreach (var topicFilter in packet.TopicFilters) { bufferWriter.WriteString(topicFilter.Topic); bufferWriter.WriteByte((byte)topicFilter.QualityOfServiceLevel); } } return MqttBufferWriter.BuildFixedHeader(MqttControlPacketType.Subscribe, 0x02); } static byte EncodeUnsubAckPacket(MqttUnsubAckPacket packet, MqttBufferWriter bufferWriter) { if (packet.PacketIdentifier == 0) { throw new MqttProtocolViolationException("UnsubAck packet has no packet identifier."); } bufferWriter.WriteTwoByteInteger(packet.PacketIdentifier); return MqttBufferWriter.BuildFixedHeader(MqttControlPacketType.UnsubAck); } static byte EncodeUnsubscribePacket(MqttUnsubscribePacket packet, MqttBufferWriter bufferWriter) { if (packet.TopicFilters?.Count == 0) { throw new MqttProtocolViolationException("At least one topic filter must be set [MQTT-3.10.3-2]."); } if (packet.PacketIdentifier == 0) { throw new MqttProtocolViolationException("Unsubscribe packet has no packet identifier."); } bufferWriter.WriteTwoByteInteger(packet.PacketIdentifier); if (packet.TopicFilters?.Count > 0) { foreach (var topicFilter in packet.TopicFilters) { bufferWriter.WriteString(topicFilter); } } return MqttBufferWriter.BuildFixedHeader(MqttControlPacketType.Unsubscribe, 0x02); } [MethodImpl(MethodImplOptions.AggressiveInlining)] static void ThrowIfBodyIsEmpty(ArraySegment body) { if (body.Count == 0) { throw new MqttProtocolViolationException("Data from the body is required but not present."); } } static void ValidateConnectPacket(MqttConnectPacket packet) { ArgumentNullException.ThrowIfNull(packet); if (string.IsNullOrEmpty(packet.ClientId) && !packet.CleanSession) { throw new MqttProtocolViolationException("CleanSession must be set if ClientId is empty [MQTT-3.1.3-7]."); } } // ReSharper disable once ParameterOnlyUsedForPreconditionCheck.Local static void ValidatePublishPacket(MqttPublishPacket packet) { if (packet.QualityOfServiceLevel == 0 && packet.Dup) { throw new MqttProtocolViolationException("Dup flag must be false for QoS 0 packets [MQTT-3.3.1-2]."); } } }