Reject duplicate MQTT 5 PUBLISH singleton properties (#2261)

This commit is contained in:
Jeff
2026-09-30 20:25:24 +02:00
committed by GitHub
parent 483447e783
commit 6e5351ce75
2 changed files with 60 additions and 0 deletions
@@ -0,0 +1,40 @@
// 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.Exceptions;
using MQTTnet.Formatter;
using MQTTnet.Packets;
namespace MQTTnet.Tests.Formatter;
[TestClass]
public sealed class MqttV5PublishPropertyValidation_Tests
{
[TestMethod]
[DataRow("0100")]
[DataRow("0200000000")]
[DataRow("030000")]
[DataRow("08000161")]
[DataRow("090000")]
[DataRow("230001")]
public void Publish_Rejects_Duplicate_Singleton_Property(string propertyHex)
{
var property = Convert.FromHexString(propertyHex);
byte[] packet = [0x30, (byte)(4 + 2 * property.Length), 0, 1, (byte)'a', (byte)(2 * property.Length), .. property, .. property];
Assert.ThrowsExactly<MqttProtocolViolationException>(() => MqttPacketSerializationHelper.DecodePacket(packet, MqttProtocolVersion.V500));
}
[TestMethod]
public void Publish_Allows_Repeated_Subscription_Identifiers_And_User_Properties()
{
byte[] packet = [0x30, 18, 0, 1, (byte)'a', 14, 0x0B, 1, 0x26, 0, 0, 0, 0, 0x0B, 1, 0x26, 0, 0, 0, 0];
var decoded = (MqttPublishPacket)MqttPacketSerializationHelper.DecodePacket(packet, MqttProtocolVersion.V500);
CollectionAssert.AreEqual(new uint[] { 1, 1 }, decoded.SubscriptionIdentifiers);
Assert.HasCount(2, decoded.UserProperties);
}
}
@@ -476,8 +476,28 @@ public sealed class MqttV5PacketDecoder
}
var propertiesReader = new MqttV5PropertiesReader(_bufferReader);
var seenSingletonProperties = 0;
while (propertiesReader.MoveNext())
{
// MQTT 5.0 section 3.3.2.3 permits only User Properties and
// Subscription Identifiers to occur more than once in PUBLISH.
var propertyFlag = propertiesReader.CurrentPropertyId switch
{
MqttPropertyId.PayloadFormatIndicator => 1,
MqttPropertyId.MessageExpiryInterval => 2,
MqttPropertyId.ContentType => 4,
MqttPropertyId.ResponseTopic => 8,
MqttPropertyId.CorrelationData => 16,
MqttPropertyId.TopicAlias => 32,
_ => 0
};
if ((seenSingletonProperties & propertyFlag) != 0)
{
throw new MqttProtocolViolationException($"Property '{propertiesReader.CurrentPropertyId}' must not occur more than once in a PUBLISH packet.");
}
seenSingletonProperties |= propertyFlag;
if (propertiesReader.CurrentPropertyId == MqttPropertyId.PayloadFormatIndicator)
{
packet.PayloadFormatIndicator = propertiesReader.ReadPayloadFormatIndicator();