Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions .github/workflows/release-drafter.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,19 @@ name: Release Drafter

on:
push:
# branches to consider in the event; optional, defaults to all
branches:
- main

permissions:
contents: read

jobs:
update_release_draft:
permissions:
contents: write
pull-requests: write
runs-on: ubuntu-latest
steps:
# Drafts your next Release notes as Pull Requests are merged into "master"
- uses: release-drafter/[email protected]
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
165 changes: 165 additions & 0 deletions Source/HiveMQtt/Client/DisconnectOptionsBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
/*
* Copyright 2024-present HiveMQ and the HiveMQ Community
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace HiveMQtt.Client;

using HiveMQtt.Client.Options;
using HiveMQtt.MQTT5.ReasonCodes;

public class DisconnectOptionsBuilder
{
private static readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger();

private readonly DisconnectOptions options;

public DisconnectOptionsBuilder() => this.options = new DisconnectOptions();

/// <summary>
/// Sets the session expiry interval for the disconnect.
/// </summary>
/// <param name="sessionExpiryInterval">The session expiry interval in seconds.</param>
/// <returns>The builder instance.</returns>
public DisconnectOptionsBuilder WithSessionExpiryInterval(int sessionExpiryInterval)
{
this.options.SessionExpiryInterval = sessionExpiryInterval;
return this;
}

/// <summary>
/// Sets the reason code for the disconnect.
/// </summary>
/// <param name="reasonCode">The reason code for the disconnect.</param>
/// <returns>The builder instance.</returns>
public DisconnectOptionsBuilder WithReasonCode(DisconnectReasonCode reasonCode)
{
this.options.ReasonCode = reasonCode;
return this;
}

/// <summary>
/// Sets the reason string for the disconnect.
/// </summary>
/// <param name="reasonString">The reason string for the disconnect.</param>
/// <returns>The builder instance.</returns>
/// <exception cref="ArgumentException">Thrown if the reason string is not between 1 and 65535 characters.</exception>
/// <exception cref="ArgumentNullException">Thrown if the reason string is null.</exception>
public DisconnectOptionsBuilder WithReasonString(string reasonString)
{
if (reasonString is null)
{
Logger.Error("Reason string cannot be null.");
throw new ArgumentNullException(nameof(reasonString));
}

if (reasonString.Length is < 1 or > 65535)
{
Logger.Error("Reason string must be between 1 and 65535 characters.");
throw new ArgumentException("Reason string must be between 1 and 65535 characters.");
}

this.options.ReasonString = reasonString;
return this;
}

/// <summary>
/// Adds a user property to the disconnect.
/// </summary>
/// <param name="key">The key for the user property.</param>
/// <param name="value">The value for the user property.</param>
/// <returns>The builder instance.</returns>
/// <exception cref="ArgumentException">Thrown if the key or value is not between 1 and 65535 characters.</exception>
/// <exception cref="ArgumentNullException">Thrown if the key or value is null.</exception>
public DisconnectOptionsBuilder WithUserProperty(string key, string value)
{
if (key is null)
{
Logger.Error("User property key cannot be null.");
throw new ArgumentNullException(nameof(key));
}

if (value is null)
{
Logger.Error("User property value cannot be null.");
throw new ArgumentNullException(nameof(value));
}

if (key.Length is < 1 or > 65535)
{
Logger.Error("User property key must be between 1 and 65535 characters.");
throw new ArgumentException("User property key must be between 1 and 65535 characters.");
}

if (value.Length is < 1 or > 65535)
{
Logger.Error("User property value must be between 1 and 65535 characters.");
throw new ArgumentException("User property value must be between 1 and 65535 characters.");
}

this.options.UserProperties.Add(key, value);
return this;
}

/// <summary>
/// Adds a dictionary of user properties to the disconnect.
/// </summary>
/// <param name="properties">The dictionary of user properties to add.</param>
/// <returns>The builder instance.</returns>
/// <exception cref="ArgumentException">Thrown if a key or value is not between 1 and 65535 characters.</exception>
/// <exception cref="ArgumentNullException">Thrown if the key or value is null.</exception>
public DisconnectOptionsBuilder WithUserProperties(Dictionary<string, string> properties)
{
foreach (var property in properties)
{
if (property.Key is null)
{
Logger.Error("User property key cannot be null.");
throw new ArgumentNullException(nameof(properties));
}

if (property.Value is null)
{
Logger.Error("User property value cannot be null.");
throw new ArgumentNullException(nameof(properties));
}

if (property.Key.Length is < 1 or > 65535)
{
Logger.Error("User property key must be between 1 and 65535 characters.");
throw new ArgumentException("User property key must be between 1 and 65535 characters.");
}

if (property.Value.Length is < 1 or > 65535)
{
Logger.Error("User property value must be between 1 and 65535 characters.");
throw new ArgumentException("User property value must be between 1 and 65535 characters.");
}

this.options.UserProperties.Add(property.Key, property.Value);
}

return this;
}

/// <summary>
/// Builds the SubscribeOptions based on the previous calls. This
/// step will also run validation on the final SubscribeOptions.
/// </summary>
/// <returns>The constructed SubscribeOptions instance.</returns>
public DisconnectOptions Build()
{
this.options.Validate();
return this.options;
}
}
2 changes: 1 addition & 1 deletion Source/HiveMQtt/Client/HiveMQClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@
// Fire the corresponding event
this.BeforeDisconnectEventLauncher();

var disconnectPacket = new DisconnectPacket
var disconnectPacket = new DisconnectPacket(options)
{
DisconnectReasonCode = options.ReasonCode,
};
Expand Down Expand Up @@ -259,7 +259,7 @@
}
}

return publishResult;

Check warning on line 262 in Source/HiveMQtt/Client/HiveMQClient.cs

View workflow job for this annotation

GitHub Actions / pipeline-ubuntu-latest-dotnet-6.0.x

Possible null reference return.

Check warning on line 262 in Source/HiveMQtt/Client/HiveMQClient.cs

View workflow job for this annotation

GitHub Actions / pipeline-ubuntu-latest-dotnet-6.0.x

Possible null reference return.

Check warning on line 262 in Source/HiveMQtt/Client/HiveMQClient.cs

View workflow job for this annotation

GitHub Actions / pipeline-ubuntu-latest-dotnet-7.0.x

Possible null reference return.

Check warning on line 262 in Source/HiveMQtt/Client/HiveMQClient.cs

View workflow job for this annotation

GitHub Actions / pipeline-ubuntu-latest-dotnet-7.0.x

Possible null reference return.

Check warning on line 262 in Source/HiveMQtt/Client/HiveMQClient.cs

View workflow job for this annotation

GitHub Actions / pipeline-ubuntu-latest-dotnet-7.0.x

Possible null reference return.

Check warning on line 262 in Source/HiveMQtt/Client/HiveMQClient.cs

View workflow job for this annotation

GitHub Actions / pipeline-ubuntu-latest-dotnet-8.0.x

Possible null reference return.

Check warning on line 262 in Source/HiveMQtt/Client/HiveMQClient.cs

View workflow job for this annotation

GitHub Actions / pipeline-ubuntu-latest-dotnet-8.0.x

Possible null reference return.

Check warning on line 262 in Source/HiveMQtt/Client/HiveMQClient.cs

View workflow job for this annotation

GitHub Actions / pipeline-ubuntu-latest-dotnet-8.0.x

Possible null reference return.
}

throw new HiveMQttClientException("Invalid QoS value.");
Expand Down
37 changes: 37 additions & 0 deletions Source/HiveMQtt/Client/HiveMQClientOptionsBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,12 @@ public HiveMQClientOptionsBuilder WithPort(int port)
/// <returns>The HiveMQClientOptionsBuilder instance.</returns>
public HiveMQClientOptionsBuilder WithClientId(string clientId)
{
if (clientId.Length is < 0 or > 65535)
{
Logger.Error("Client Id must be between 0 and 65535 characters.");
throw new ArgumentException("Client Id must be between 0 and 65535 characters.");
}

this.options.ClientId = clientId;
return this;
}
Expand Down Expand Up @@ -275,8 +281,15 @@ public HiveMQClientOptionsBuilder WithKeepAlive(int keepAlive)
/// </summary>
/// <param name="method">The authentication method.</param>
/// <returns>The HiveMQClientOptionsBuilder instance.</returns>
/// <exception cref="ArgumentException">Thrown when the authentication method is not between 1 and 65535 characters.</exception>
public HiveMQClientOptionsBuilder WithAuthenticationMethod(string method)
{
if (method.Length is < 1 or > 65535)
{
Logger.Error("Authentication method must be between 1 and 65535 characters.");
throw new ArgumentException("Authentication method must be between 1 and 65535 characters.");
}

this.options.AuthenticationMethod = method;
return this;
}
Expand Down Expand Up @@ -309,6 +322,18 @@ public HiveMQClientOptionsBuilder WithAuthenticationData(byte[] data)
/// <returns>The HiveMQClientOptionsBuilder instance.</returns>
public HiveMQClientOptionsBuilder WithUserProperty(string key, string value)
{
if (key.Length is < 1 or > 65535)
{
Logger.Error("User property key must be between 1 and 65535 characters.");
throw new ArgumentException("User property key must be between 1 and 65535 characters.");
}

if (value.Length is < 1 or > 65535)
{
Logger.Error("User property value must be between 1 and 65535 characters.");
throw new ArgumentException("User property value must be between 1 and 65535 characters.");
}

this.options.UserProperties.Add(key, value);
return this;
}
Expand Down Expand Up @@ -432,6 +457,12 @@ public HiveMQClientOptionsBuilder WithSessionExpiryInterval(int sessionExpiryInt
/// <returns>The HiveMQClientOptionsBuilder instance.</returns>
public HiveMQClientOptionsBuilder WithUserName(string username)
{
if (username.Length is < 0 or > 65535)
{
Logger.Error("Username must be between 0 and 65535 characters.");
throw new ArgumentException("Username must be between 0 and 65535 characters.");
}

this.options.UserName = username;
return this;
}
Expand Down Expand Up @@ -462,6 +493,12 @@ public HiveMQClientOptionsBuilder WithUserName(string username)
/// <returns>The HiveMQClientOptionsBuilder instance.</returns>
public HiveMQClientOptionsBuilder WithPassword(string password)
{
if (password.Length is < 0 or > 65535)
{
Logger.Error("Password must be between 0 and 65535 characters.");
throw new ArgumentException("Password must be between 0 and 65535 characters.");
}

this.options.Password = password;
return this;
}
Expand Down
2 changes: 1 addition & 1 deletion Source/HiveMQtt/Client/HiveMQClientUtil.cs
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@

// If pattern contains a multi-level wildcard character, it must be the last character in the pattern
// and it must be preceded by a topic level separator.
var mlwcValidityRegex = new Regex(@"(?<!/)#");

Check warning on line 115 in Source/HiveMQtt/Client/HiveMQClientUtil.cs

View workflow job for this annotation

GitHub Actions / pipeline-ubuntu-latest-dotnet-6.0.x

Use 'GeneratedRegexAttribute' to generate the regular expression implementation at compile-time. (https://learn.microsoft.com/dotnet/fundamentals/syslib-diagnostics/syslib1045)

Check warning on line 115 in Source/HiveMQtt/Client/HiveMQClientUtil.cs

View workflow job for this annotation

GitHub Actions / pipeline-ubuntu-latest-dotnet-7.0.x

Use 'GeneratedRegexAttribute' to generate the regular expression implementation at compile-time.

Check warning on line 115 in Source/HiveMQtt/Client/HiveMQClientUtil.cs

View workflow job for this annotation

GitHub Actions / pipeline-ubuntu-latest-dotnet-8.0.x

Use 'GeneratedRegexAttribute' to generate the regular expression implementation at compile-time.

if (pattern.Contains("/#/") | mlwcValidityRegex.IsMatch(pattern))
{
Expand Down Expand Up @@ -173,7 +173,7 @@
Logger.Trace("HiveMQClient Dispose: Disconnecting connected client.");
_ = Task.Run(async () => await this.DisconnectAsync().ConfigureAwait(false));
}
}
}

// Call the appropriate methods to clean up
// unmanaged resources here.
Expand Down
31 changes: 30 additions & 1 deletion Source/HiveMQtt/Client/Options/DisconnectOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
namespace HiveMQtt.Client.Options;

using HiveMQtt.Client.Exceptions;
using HiveMQtt.MQTT5.ReasonCodes;

/// <summary>
Expand All @@ -41,7 +42,7 @@ public DisconnectOptions()
/// to the indicated value. The value represents the session expiration time
/// in seconds.
/// </summary>
public int? SessionExpiry { get; set; }
public int? SessionExpiryInterval { get; set; }

/// <summary>
/// Gets or sets the reason string for the disconnection. This is a human readable
Expand All @@ -53,4 +54,32 @@ public DisconnectOptions()
/// Gets or sets the user properties for the disconnection.
/// </summary>
public Dictionary<string, string> UserProperties { get; set; }

/// <summary>
/// Validate that the options in this instance are valid.
/// </summary>
/// <exception cref="HiveMQttClientException">The exception raised if some value is out of range or invalid.</exception>
public void Validate()
{
// Validate SessionExpiry is non-negative if provided
if (this.SessionExpiryInterval.HasValue && this.SessionExpiryInterval < 0)
{
throw new HiveMQttClientException("Session expiry must be a non-negative value.");
}

// Validate ReasonString length (assuming max length of 65535 characters)
if (this.ReasonString != null && this.ReasonString.Length > 65535)
{
throw new HiveMQttClientException("Reason string must not exceed 65535 characters.");
}

// Validate UserProperties for null keys or values
foreach (var kvp in this.UserProperties)
{
if (kvp.Key == null || kvp.Value == null)
{
throw new HiveMQttClientException("User properties must not have null keys or values.");
}
}
}
}
1 change: 1 addition & 0 deletions Source/HiveMQtt/Client/Options/HiveMQClientOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ public HiveMQClientOptions()
/// Examples:
/// ws://localhost:8000/mqtt
/// wss://localhost:8884/mqtt
/// .
/// </para>
/// </summary>
public string WebSocketServer { get; set; }
Expand Down
19 changes: 19 additions & 0 deletions Source/HiveMQtt/Client/PublishMessageBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,27 @@ public PublishMessageBuilder WithPayload(string payload)
/// </summary>
/// <param name="topic">The topic.</param>
/// <returns>The builder instance.</returns>
/// <exception cref="ArgumentException">Thrown when the topic is null or empty.</exception>
/// <exception cref="ArgumentException">Thrown when the topic length is greater than 65535 characters.</exception>
/// <exception cref="ArgumentException">Thrown when the topic contains wildcard characters.</exception>
public PublishMessageBuilder WithTopic(string topic)
{
if (string.IsNullOrEmpty(topic))
{
throw new ArgumentException("Topic must not be null or empty.");
}

if (topic.Length is < 1 or > 65535)
{
throw new ArgumentException("Topic must be between 1 and 65535 characters.");
}

// The topic string should not contain any wildcard characters.
if (topic.Contains('+') || topic.Contains('#'))
{
throw new ArgumentException("Topic must not contain wildcard characters. Use TopicFilter instead.");
}

this.message.Topic = topic;
return this;
}
Expand Down
2 changes: 1 addition & 1 deletion Source/HiveMQtt/Client/Transport/WebSocketTransport.cs
Original file line number Diff line number Diff line change
Expand Up @@ -117,14 +117,14 @@
do
{
result = await this.Socket.ReceiveAsync(buffer, CancellationToken.None).ConfigureAwait(false);
await ms.WriteAsync(buffer.Array, buffer.Offset, result.Count, cancellationToken).ConfigureAwait(false);

Check warning on line 120 in Source/HiveMQtt/Client/Transport/WebSocketTransport.cs

View workflow job for this annotation

GitHub Actions / pipeline-ubuntu-latest-dotnet-6.0.x

Possible null reference argument for parameter 'buffer' in 'Task MemoryStream.WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)'.

Check warning on line 120 in Source/HiveMQtt/Client/Transport/WebSocketTransport.cs

View workflow job for this annotation

GitHub Actions / pipeline-ubuntu-latest-dotnet-6.0.x

Change the 'WriteAsync' method call to use the 'Stream.WriteAsync(ReadOnlyMemory<byte>, CancellationToken)' overload (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1835)

Check warning on line 120 in Source/HiveMQtt/Client/Transport/WebSocketTransport.cs

View workflow job for this annotation

GitHub Actions / pipeline-ubuntu-latest-dotnet-6.0.x

Possible null reference argument for parameter 'buffer' in 'Task MemoryStream.WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)'.

Check warning on line 120 in Source/HiveMQtt/Client/Transport/WebSocketTransport.cs

View workflow job for this annotation

GitHub Actions / pipeline-ubuntu-latest-dotnet-6.0.x

Change the 'WriteAsync' method call to use the 'Stream.WriteAsync(ReadOnlyMemory<byte>, CancellationToken)' overload (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1835)

Check warning on line 120 in Source/HiveMQtt/Client/Transport/WebSocketTransport.cs

View workflow job for this annotation

GitHub Actions / pipeline-ubuntu-latest-dotnet-6.0.x

Possible null reference argument for parameter 'buffer' in 'Task MemoryStream.WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)'.

Check warning on line 120 in Source/HiveMQtt/Client/Transport/WebSocketTransport.cs

View workflow job for this annotation

GitHub Actions / pipeline-ubuntu-latest-dotnet-7.0.x

Possible null reference argument for parameter 'buffer' in 'Task MemoryStream.WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)'.

Check warning on line 120 in Source/HiveMQtt/Client/Transport/WebSocketTransport.cs

View workflow job for this annotation

GitHub Actions / pipeline-ubuntu-latest-dotnet-7.0.x

Change the 'WriteAsync' method call to use the 'Stream.WriteAsync(ReadOnlyMemory<byte>, CancellationToken)' overload (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1835)

Check warning on line 120 in Source/HiveMQtt/Client/Transport/WebSocketTransport.cs

View workflow job for this annotation

GitHub Actions / pipeline-ubuntu-latest-dotnet-7.0.x

Possible null reference argument for parameter 'buffer' in 'Task MemoryStream.WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)'.

Check warning on line 120 in Source/HiveMQtt/Client/Transport/WebSocketTransport.cs

View workflow job for this annotation

GitHub Actions / pipeline-ubuntu-latest-dotnet-7.0.x

Change the 'WriteAsync' method call to use the 'Stream.WriteAsync(ReadOnlyMemory<byte>, CancellationToken)' overload (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1835)

Check warning on line 120 in Source/HiveMQtt/Client/Transport/WebSocketTransport.cs

View workflow job for this annotation

GitHub Actions / pipeline-ubuntu-latest-dotnet-7.0.x

Possible null reference argument for parameter 'buffer' in 'Task MemoryStream.WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)'.

Check warning on line 120 in Source/HiveMQtt/Client/Transport/WebSocketTransport.cs

View workflow job for this annotation

GitHub Actions / pipeline-ubuntu-latest-dotnet-8.0.x

Possible null reference argument for parameter 'buffer' in 'Task MemoryStream.WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)'.

Check warning on line 120 in Source/HiveMQtt/Client/Transport/WebSocketTransport.cs

View workflow job for this annotation

GitHub Actions / pipeline-ubuntu-latest-dotnet-8.0.x

Change the 'WriteAsync' method call to use the 'Stream.WriteAsync(ReadOnlyMemory<byte>, CancellationToken)' overload (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1835)

Check warning on line 120 in Source/HiveMQtt/Client/Transport/WebSocketTransport.cs

View workflow job for this annotation

GitHub Actions / pipeline-ubuntu-latest-dotnet-8.0.x

Possible null reference argument for parameter 'buffer' in 'Task MemoryStream.WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)'.

Check warning on line 120 in Source/HiveMQtt/Client/Transport/WebSocketTransport.cs

View workflow job for this annotation

GitHub Actions / pipeline-ubuntu-latest-dotnet-8.0.x

Change the 'WriteAsync' method call to use the 'Stream.WriteAsync(ReadOnlyMemory<byte>, CancellationToken)' overload (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1835)

Check warning on line 120 in Source/HiveMQtt/Client/Transport/WebSocketTransport.cs

View workflow job for this annotation

GitHub Actions / pipeline-ubuntu-latest-dotnet-8.0.x

Possible null reference argument for parameter 'buffer' in 'Task MemoryStream.WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)'.
}
while (!result.EndOfMessage);

Logger.Trace($"Received {ms.Length} bytes");

// Development
// ms.Seek(0, SeekOrigin.Begin);

if (result.MessageType == WebSocketMessageType.Binary)
{
// Prepare the result and return
Expand Down
Loading
Loading