From 798a0e2250f40a241e0c8d2e7c9a366051be1b97 Mon Sep 17 00:00:00 2001 From: Jeff <37258233+YAJeff@users.noreply.github.com> Date: Wed, 30 Sep 2026 11:55:47 -0700 Subject: [PATCH] Validate MQTT 5 SUBSCRIBE identifiers (#2262) --- .../MqttV5SubscribeIdentifier_Tests.cs | 62 +++++++++++++++++++ .../Formatter/V5/MqttV5PacketDecoder.cs | 10 +++ 2 files changed, 72 insertions(+) create mode 100644 Source/MQTTnet.Tests/Formatter/MqttV5SubscribeIdentifier_Tests.cs diff --git a/Source/MQTTnet.Tests/Formatter/MqttV5SubscribeIdentifier_Tests.cs b/Source/MQTTnet.Tests/Formatter/MqttV5SubscribeIdentifier_Tests.cs new file mode 100644 index 00000000..3b91df98 --- /dev/null +++ b/Source/MQTTnet.Tests/Formatter/MqttV5SubscribeIdentifier_Tests.cs @@ -0,0 +1,62 @@ +// 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 MQTTnet.Adapter; +using MQTTnet.Exceptions; +using MQTTnet.Formatter; +using MQTTnet.Formatter.V5; +using MQTTnet.Packets; + +namespace MQTTnet.Tests.Formatter; + +[TestClass] +public sealed class MqttV5SubscribeIdentifier_Tests +{ + [TestMethod] + [DataRow("0B00")] + [DataRow("0B010B01")] + [DataRow("0B010B02")] + [DataRow("0B0126000000000B02")] + public void Reject_Invalid_Subscription_Identifier(string propertiesHex) + { + var properties = Convert.FromHexString(propertiesHex); + byte[] packet = [0x82, (byte)(7 + properties.Length), 0, 1, (byte)properties.Length, .. properties, 0, 1, (byte)'a', 0]; + + Assert.ThrowsExactly(() => MqttPacketSerializationHelper.DecodePacket(packet, MqttProtocolVersion.V500)); + } + + [TestMethod] + [DataRow("", 0U)] + [DataRow("0B01", 1U)] + [DataRow("0BFFFFFF7F", 268435455U)] + [DataRow("26000000000B012600000000", 1U)] + public void Accept_Valid_Subscription_Identifier(string propertiesHex, uint expected) + { + var properties = Convert.FromHexString(propertiesHex); + byte[] packet = [0x82, (byte)(7 + properties.Length), 0, 1, (byte)properties.Length, .. properties, 0, 1, (byte)'a', 0]; + var decoded = (MqttSubscribePacket)MqttPacketSerializationHelper.DecodePacket(packet, MqttProtocolVersion.V500); + + Assert.AreEqual(expected, decoded.SubscriptionIdentifier); + } + + [TestMethod] + public void Accept_Same_Identifier_In_Separate_Subscribe_Packets() + { + byte[] packet = [0x82, 9, 0, 1, 2, 0x0B, 1, 0, 1, (byte)'a', 0]; + var decoder = new MqttV5PacketDecoder(); + var received = new ReceivedMqttPacket(packet[0], new ArraySegment(packet, 2, packet.Length - 2), packet.Length); + + Assert.AreEqual(1U, ((MqttSubscribePacket)decoder.Decode(received)).SubscriptionIdentifier); + Assert.AreEqual(1U, ((MqttSubscribePacket)decoder.Decode(received)).SubscriptionIdentifier); + } + + [TestMethod] + public void Accept_Repeated_Subscription_Identifiers_In_Publish() + { + byte[] packet = [0x30, 8, 0, 1, (byte)'a', 4, 0x0B, 1, 0x0B, 1]; + var decoded = (MqttPublishPacket)MqttPacketSerializationHelper.DecodePacket(packet, MqttProtocolVersion.V500); + + CollectionAssert.AreEqual(new uint[] { 1, 1 }, decoded.SubscriptionIdentifiers); + } +} \ No newline at end of file diff --git a/Source/MQTTnet/Formatter/V5/MqttV5PacketDecoder.cs b/Source/MQTTnet/Formatter/V5/MqttV5PacketDecoder.cs index 0e3d6001..9c6b6c7a 100644 --- a/Source/MQTTnet/Formatter/V5/MqttV5PacketDecoder.cs +++ b/Source/MQTTnet/Formatter/V5/MqttV5PacketDecoder.cs @@ -673,7 +673,17 @@ public sealed class MqttV5PacketDecoder { if (propertiesReader.CurrentPropertyId == MqttPropertyId.SubscriptionIdentifier) { + // MQTT 5.0 section 3.8.2.1.2 forbids zero and repeated identifiers. + if (packet.SubscriptionIdentifier != 0) + { + throw new MqttProtocolViolationException("Subscription Identifier must not occur more than once in a SUBSCRIBE packet."); + } + packet.SubscriptionIdentifier = propertiesReader.ReadSubscriptionIdentifier(); + if (packet.SubscriptionIdentifier == 0) + { + throw new MqttProtocolViolationException("Subscription Identifier must not be zero."); + } } else {