Skip to content

Commit 535a4e0

Browse files
authored
Add validations of variable byte integers (#2214)
* Add validations and add docs * Update release notes * Fix build
1 parent 37c1eec commit 535a4e0

File tree

8 files changed

+157
-64
lines changed

8 files changed

+157
-64
lines changed

Samples/Server/Server_Intercepting_Samples.cs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
// ReSharper disable MemberCanBeMadeStatic.Local
1010

1111
using MQTTnet.Internal;
12+
using MQTTnet.Protocol;
1213
using MQTTnet.Server;
1314

1415
namespace MQTTnet.Samples.Server;
@@ -42,4 +43,32 @@ public static async Task Intercept_Application_Messages()
4243
Console.ReadLine();
4344
await mqttServer.StopAsync();
4445
}
46+
47+
public static async Task Intercept_Subscription()
48+
{
49+
/*
50+
* This sample starts a simple MQTT server which does not allow to subscribe to '#'
51+
* Start the server and try to subscribe to '#' with a client of choice.
52+
*/
53+
54+
var mqttServerFactory = new MqttServerFactory();
55+
var mqttServerOptions = new MqttServerOptionsBuilder().WithDefaultEndpoint().Build();
56+
57+
using var mqttServer = mqttServerFactory.CreateMqttServer(mqttServerOptions);
58+
mqttServer.InterceptingSubscriptionAsync += args =>
59+
{
60+
if (args.TopicFilter.Topic.Equals("#", StringComparison.Ordinal))
61+
{
62+
args.Response.ReasonCode = MqttSubscribeReasonCode.NotAuthorized;
63+
}
64+
65+
return CompletedTask.Instance;
66+
};
67+
68+
await mqttServer.StartAsync();
69+
70+
Console.WriteLine("Press Enter to exit.");
71+
Console.ReadLine();
72+
await mqttServer.StopAsync();
73+
}
4574
}

Source/MQTTnet.Server/Options/MqttServerOptionsBuilder.cs

Lines changed: 56 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
using System.Net;
66
using System.Net.Security;
77
using System.Security.Authentication;
8-
using MQTTnet.Certificates;
98
using System.Security.Cryptography.X509Certificates;
9+
using MQTTnet.Certificates;
1010

1111
// ReSharper disable UnusedMember.Global
1212
namespace MQTTnet.Server;
@@ -47,12 +47,22 @@ public MqttServerOptionsBuilder WithDefaultEndpoint()
4747
return this;
4848
}
4949

50+
/// <summary>
51+
/// Sets the IPv4 network address to bind.
52+
/// Defaults to any available IPv4 address of the machine.
53+
/// Set this to `IPAddress.None` to disable the IPv4 support entirely.
54+
/// </summary>
5055
public MqttServerOptionsBuilder WithDefaultEndpointBoundIPAddress(IPAddress value)
5156
{
5257
_options.DefaultEndpointOptions.BoundInterNetworkAddress = value ?? IPAddress.Any;
5358
return this;
5459
}
5560

61+
/// <summary>
62+
/// Sets the IPv6 network address to bind.
63+
/// Defaults to any available IPv6 address of the machine.
64+
/// Set this to `IPAddress.None` to disable the IPv6 support entirely.
65+
/// </summary>
5666
public MqttServerOptionsBuilder WithDefaultEndpointBoundIPV6Address(IPAddress value)
5767
{
5868
_options.DefaultEndpointOptions.BoundInterNetworkV6Address = value ?? IPAddress.Any;
@@ -77,12 +87,22 @@ public MqttServerOptionsBuilder WithEncryptedEndpoint()
7787
return this;
7888
}
7989

90+
/// <summary>
91+
/// Sets the IPv4 network address to bind.
92+
/// Defaults to any available IPv4 address of the machine.
93+
/// Set this to `IPAddress.None` to disable the IPv4 support entirely.
94+
/// </summary>
8095
public MqttServerOptionsBuilder WithEncryptedEndpointBoundIPAddress(IPAddress value)
8196
{
8297
_options.TlsEndpointOptions.BoundInterNetworkAddress = value;
8398
return this;
8499
}
85100

101+
/// <summary>
102+
/// Sets the IPv6 network address to bind.
103+
/// Defaults to any available IPv6 address of the machine.
104+
/// Set this to `IPAddress.None` to disable the IPv6 support entirely.
105+
/// </summary>
86106
public MqttServerOptionsBuilder WithEncryptedEndpointBoundIPV6Address(IPAddress value)
87107
{
88108
_options.TlsEndpointOptions.BoundInterNetworkV6Address = value;
@@ -95,6 +115,35 @@ public MqttServerOptionsBuilder WithEncryptedEndpointPort(int value)
95115
return this;
96116
}
97117

118+
public MqttServerOptionsBuilder WithEncryptionCertificate(byte[] value, IMqttServerCertificateCredentials credentials = null)
119+
{
120+
ArgumentNullException.ThrowIfNull(value);
121+
122+
_options.TlsEndpointOptions.CertificateProvider = new BlobCertificateProvider(value)
123+
{
124+
Password = credentials?.Password
125+
};
126+
127+
return this;
128+
}
129+
130+
public MqttServerOptionsBuilder WithEncryptionCertificate(X509Certificate2 certificate)
131+
{
132+
ArgumentNullException.ThrowIfNull(certificate);
133+
134+
_options.TlsEndpointOptions.CertificateProvider = new X509CertificateProvider(certificate);
135+
return this;
136+
}
137+
138+
public MqttServerOptionsBuilder WithEncryptionCertificate(ICertificateProvider certificateProvider)
139+
{
140+
ArgumentNullException.ThrowIfNull(certificateProvider);
141+
142+
_options.TlsEndpointOptions.CertificateProvider = certificateProvider;
143+
144+
return this;
145+
}
146+
98147
public MqttServerOptionsBuilder WithEncryptionSslProtocol(SslProtocols value)
99148
{
100149
_options.TlsEndpointOptions.SslProtocol = value;
@@ -114,12 +163,6 @@ public MqttServerOptionsBuilder WithMaxPendingMessagesPerClient(int value)
114163
return this;
115164
}
116165

117-
public MqttServerOptionsBuilder WithPendingMessagesOverflowStrategy(MqttPendingMessagesOverflowStrategy value)
118-
{
119-
_options.PendingMessagesOverflowStrategy = value;
120-
return this;
121-
}
122-
123166
public MqttServerOptionsBuilder WithoutDefaultEndpoint()
124167
{
125168
_options.DefaultEndpointOptions.IsEnabled = false;
@@ -144,6 +187,12 @@ public MqttServerOptionsBuilder WithoutPacketFragmentation()
144187
return this;
145188
}
146189

190+
public MqttServerOptionsBuilder WithPendingMessagesOverflowStrategy(MqttPendingMessagesOverflowStrategy value)
191+
{
192+
_options.PendingMessagesOverflowStrategy = value;
193+
return this;
194+
}
195+
147196
public MqttServerOptionsBuilder WithPersistentSessions(bool value = true)
148197
{
149198
_options.EnablePersistentSessions = value;
@@ -182,33 +231,4 @@ public MqttServerOptionsBuilder WithTlsEndpointReuseAddress()
182231
_options.TlsEndpointOptions.ReuseAddress = true;
183232
return this;
184233
}
185-
186-
public MqttServerOptionsBuilder WithEncryptionCertificate(byte[] value, IMqttServerCertificateCredentials credentials = null)
187-
{
188-
ArgumentNullException.ThrowIfNull(value);
189-
190-
_options.TlsEndpointOptions.CertificateProvider = new BlobCertificateProvider(value)
191-
{
192-
Password = credentials?.Password
193-
};
194-
195-
return this;
196-
}
197-
198-
public MqttServerOptionsBuilder WithEncryptionCertificate(X509Certificate2 certificate)
199-
{
200-
ArgumentNullException.ThrowIfNull(certificate);
201-
202-
_options.TlsEndpointOptions.CertificateProvider = new X509CertificateProvider(certificate);
203-
return this;
204-
}
205-
206-
public MqttServerOptionsBuilder WithEncryptionCertificate(ICertificateProvider certificateProvider)
207-
{
208-
ArgumentNullException.ThrowIfNull(certificateProvider);
209-
210-
_options.TlsEndpointOptions.CertificateProvider = certificateProvider;
211-
212-
return this;
213-
}
214234
}

Source/MQTTnet.Server/Options/MqttServerTcpEndpointBaseOptions.cs

Lines changed: 29 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -9,27 +9,47 @@ namespace MQTTnet.Server;
99

1010
public abstract class MqttServerTcpEndpointBaseOptions
1111
{
12-
public bool IsEnabled { get; set; }
12+
/// <summary>
13+
/// Usually the MQTT packets can be sent partially. This is done by using multiple TCP packets
14+
/// or WebSocket frames etc. Unfortunately not all clients do support this feature and
15+
/// will close the connection when receiving such packets. If such clients are connecting to this
16+
/// server the flag must be set to _false_.
17+
/// </summary>
18+
public bool AllowPacketFragmentation { get; set; } = true;
1319

14-
public int Port { get; set; }
20+
/// <summary>
21+
/// Gets or sets the IPv4 network address to bind.
22+
/// Defaults to any available IPv4 address of the machine.
23+
/// Set this to `IPAddress.None` to disable the IPv4 support entirely.
24+
/// </summary>
25+
public IPAddress BoundInterNetworkAddress { get; set; } = IPAddress.Any;
1526

16-
public int ConnectionBacklog { get; set; } = 100;
27+
/// <summary>
28+
/// Gets or sets the IPv6 network address to bind.
29+
/// Defaults to any available IPv6 address of the machine.
30+
/// Set this to `IPAddress.None` to disable the IPv6 support entirely.
31+
/// </summary>
32+
public IPAddress BoundInterNetworkV6Address { get; set; } = IPAddress.IPv6Any;
1733

18-
public bool NoDelay { get; set; } = true;
34+
public int ConnectionBacklog { get; set; } = 100;
35+
public bool IsEnabled { get; set; }
1936

2037
/// <summary>
2138
/// Gets or sets whether the sockets keep alive feature should be used.
2239
/// The value _null_ indicates that the OS and framework defaults should be used.
2340
/// </summary>
2441
public bool? KeepAlive { get; set; }
2542

43+
public LingerOption LingerState { get; set; } = new(true, 0);
44+
45+
public bool NoDelay { get; set; } = true;
46+
47+
public int Port { get; set; }
48+
2649
/// <summary>
27-
/// Usually the MQTT packets can be send partially. This is done by using multiple TCP packets
28-
/// or WebSocket frames etc. Unfortunately not all clients do support this feature and
29-
/// will close the connection when receiving such packets. If such clients are connecting to this
30-
/// server the flag must be set to _false_.
50+
/// This requires admin permissions on Linux.
3151
/// </summary>
32-
public bool AllowPacketFragmentation { get; set; } = true;
52+
public bool ReuseAddress { get; set; }
3353

3454
/// <summary>
3555
/// Gets or sets the TCP keep alive interval.
@@ -48,15 +68,4 @@ public abstract class MqttServerTcpEndpointBaseOptions
4868
/// The value _null_ indicates that the OS and framework defaults should be used.
4969
/// </summary>
5070
public int? TcpKeepAliveTime { get; set; }
51-
52-
public LingerOption LingerState { get; set; } = new LingerOption(true, 0);
53-
54-
public IPAddress BoundInterNetworkAddress { get; set; } = IPAddress.Any;
55-
56-
public IPAddress BoundInterNetworkV6Address { get; set; } = IPAddress.IPv6Any;
57-
58-
/// <summary>
59-
/// This requires admin permissions on Linux.
60-
/// </summary>
61-
public bool ReuseAddress { get; set; }
6271
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using Microsoft.VisualStudio.TestTools.UnitTesting;
6+
using MQTTnet.Exceptions;
7+
8+
namespace MQTTnet.Tests.Exceptions;
9+
10+
[TestClass]
11+
public class MqttProtocolViolationException_Tests
12+
{
13+
[TestMethod]
14+
public void No_For_Max_Large_Variable_Byte_Integer()
15+
{
16+
MqttProtocolViolationException.ThrowIfVariableByteIntegerExceedsLimit(MqttProtocolViolationException.VariableByteIntegerMaxValue);
17+
}
18+
19+
[TestMethod]
20+
public void Throw_Exception_For_Too_Large_Variable_Byte_Integer()
21+
{
22+
Assert.ThrowsExactly<MqttProtocolViolationException>(() => MqttProtocolViolationException.ThrowIfVariableByteIntegerExceedsLimit(1726790899));
23+
}
24+
}

Source/MQTTnet/Exceptions/MqttProtocolViolationException.cs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,15 @@
66

77
namespace MQTTnet.Exceptions;
88

9-
public class MqttProtocolViolationException(string message) : Exception(message);
9+
public class MqttProtocolViolationException(string message) : Exception(message)
10+
{
11+
public const uint VariableByteIntegerMaxValue = 268435455;
12+
13+
public static void ThrowIfVariableByteIntegerExceedsLimit(uint value)
14+
{
15+
if (value > VariableByteIntegerMaxValue)
16+
{
17+
throw new MqttProtocolViolationException($"The value {value} is too large for a variable byte integer ({VariableByteIntegerMaxValue}).");
18+
}
19+
}
20+
}

Source/MQTTnet/Formatter/MqttBufferWriter.cs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ namespace MQTTnet.Formatter;
1919
/// </summary>
2020
public sealed class MqttBufferWriter
2121
{
22-
const uint VariableByteIntegerMaxValue = 268435455;
2322
const int EncodedStringMaxLength = 65535;
2423

2524
readonly int _maxBufferSize;
@@ -223,10 +222,7 @@ public void WriteVariableByteInteger(uint value)
223222
return;
224223
}
225224

226-
if (value > VariableByteIntegerMaxValue)
227-
{
228-
throw new MqttProtocolViolationException($"The specified value ({value}) is too large for a variable byte integer.");
229-
}
225+
MqttProtocolViolationException.ThrowIfVariableByteIntegerExceedsLimit(value);
230226

231227
var size = 0;
232228
var x = value;

Source/MQTTnet/MqttApplicationMessageBuilder.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ public sealed class MqttApplicationMessageBuilder
2121
string _contentType;
2222
byte[] _correlationData;
2323
uint _messageExpiryInterval;
24-
2524
MqttPayloadFormatIndicator _payloadFormatIndicator;
2625
ReadOnlySequence<byte> _payload;
2726
MqttQualityOfServiceLevel _qualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce;
@@ -84,6 +83,8 @@ public MqttApplicationMessageBuilder WithCorrelationData(byte[] correlationData)
8483
/// </summary>
8584
public MqttApplicationMessageBuilder WithMessageExpiryInterval(uint messageExpiryInterval)
8685
{
86+
// No validation required because this is a 4 byte integer!
87+
8788
_messageExpiryInterval = messageExpiryInterval;
8889
return this;
8990
}
@@ -228,9 +229,11 @@ public MqttApplicationMessageBuilder WithRetainFlag(bool value = true)
228229
/// </summary>
229230
public MqttApplicationMessageBuilder WithSubscriptionIdentifier(uint subscriptionIdentifier)
230231
{
232+
MqttProtocolViolationException.ThrowIfVariableByteIntegerExceedsLimit(subscriptionIdentifier);
233+
231234
if (_subscriptionIdentifiers == null)
232235
{
233-
_subscriptionIdentifiers = new List<uint>();
236+
_subscriptionIdentifiers = [];
234237
}
235238

236239
_subscriptionIdentifiers.Add(subscriptionIdentifier);

Source/ReleaseNotes.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
* Core: Used new language features across the entire library
22
* Core: Performance improvements
3+
* Core: Added validations for variable byte integers which do not match the .NET uint perfectly
34
* Server: Improved performance of retained messages when no event handler is attached (#2093, thanks to @zhaowgit)
45
* Server: The event `InterceptingClientEnqueue` is now also called for retained messages (BREAKING CHANGE!)
56
* Server: The local end point is now also exposed in the channel adapter (#2179)

0 commit comments

Comments
 (0)