Skip to content

Commit fbfbb3d

Browse files
authored
ConnectOptions & v1.0 Test Plan Updates (#232)
1 parent e86f259 commit fbfbb3d

File tree

17 files changed

+373
-41
lines changed

17 files changed

+373
-41
lines changed
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# ConnectOptions
2+
3+
The `ConnectOptions` class provides options for a connect call made with `ConnectAsync`. These options can override settings that were originally set in `HiveMQClientOptions`.
4+
5+
## Constructors
6+
7+
* `ConnectOptions()`: Initializes a new instance of the `ConnectOptions` class with defaults.
8+
9+
## Properties
10+
11+
* `SessionExpiryInterval`: Gets or sets the session expiry interval in seconds. This overrides any value set in HiveMQClientOptions.
12+
+ Example: `SessionExpiryInterval = 3600` sets the session to expire in 1 hour.
13+
14+
* `KeepAlive`: Gets or sets the keep alive period in seconds. This overrides any value set in HiveMQClientOptions.
15+
+ Example: `KeepAlive = 60` sets the keep alive to 60 seconds.
16+
17+
* `CleanStart`: Gets or sets whether to use a clean start. This overrides any value set in HiveMQClientOptions.
18+
+ Example: `CleanStart = true` starts a new session, discarding any existing session.
19+
20+
## Examples
21+
22+
```csharp
23+
ConnectOptions connectOptions = new ConnectOptions();
24+
connectOptions.SessionExpiryInterval = 3600; // 1 hour session expiry
25+
connectOptions.KeepAlive = 60; // 60 second keep alive
26+
connectOptions.CleanStart = true; // Start with a clean session
27+
28+
await client.ConnectAsync(connectOptions);
29+
```
30+
31+
## See Also
32+
33+
* [HiveMQClientOptions Reference](/docs/reference/client_options)
34+
* [Connecting to an MQTT Broker](/docs/connecting)
35+
* [Session Handling](/docs/how-to/session-handling)
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
# ConnectOptionsBuilder
2+
3+
The `ConnectOptionsBuilder` class is a builder pattern implementation that provides a convenient way to construct `ConnectOptions` objects for configuring connect behavior in HiveMQtt client applications.
4+
5+
## Methods
6+
7+
### `WithSessionExpiryInterval(long sessionExpiryInterval)`
8+
9+
Sets the session expiry interval for the connection.
10+
11+
- **Description:** Specifies the duration, in seconds, for which the session state will be maintained by the broker. This overrides any value set in HiveMQClientOptions.
12+
- **Example:**
13+
```csharp
14+
.WithSessionExpiryInterval(3600) // 1 hour session expiry
15+
```
16+
17+
### `WithKeepAlive(int keepAlive)`
18+
19+
Sets the keep alive period for the connection.
20+
21+
- **Description:** Specifies the maximum time interval that is permitted to elapse between the point at which the Client finishes transmitting one Control Packet and the point it starts sending the next. This overrides any value set in HiveMQClientOptions.
22+
- **Example:**
23+
```csharp
24+
.WithKeepAlive(60) // 60 second keep alive
25+
```
26+
27+
### `WithCleanStart(bool cleanStart)`
28+
29+
Sets whether to use a clean start for the connection.
30+
31+
- **Description:** Specifies whether the Connection starts a new Session or is a continuation of an existing Session. This overrides any value set in HiveMQClientOptions.
32+
- **Example:**
33+
```csharp
34+
.WithCleanStart(true) // Start with a clean session
35+
```
36+
37+
### `Build()`
38+
39+
Builds the ConnectOptions instance.
40+
41+
- **Description:** Creates and returns a new ConnectOptions object with all the configured settings.
42+
- **Example:**
43+
```csharp
44+
ConnectOptions options = new ConnectOptionsBuilder()
45+
.WithSessionExpiryInterval(3600)
46+
.WithKeepAlive(60)
47+
.WithCleanStart(true)
48+
.Build();
49+
```
50+
51+
## Complete Example
52+
53+
```csharp
54+
var connectOptions = new ConnectOptionsBuilder()
55+
.WithSessionExpiryInterval(3600) // 1 hour session expiry
56+
.WithKeepAlive(60) // 60 second keep alive
57+
.WithCleanStart(true) // Start with a clean session
58+
.Build();
59+
60+
await client.ConnectAsync(connectOptions);
61+
```
62+
63+
## See Also
64+
65+
* [ConnectOptions Reference](/docs/reference/connect_options)
66+
* [HiveMQClientOptions Reference](/docs/reference/client_options)
67+
* [Connecting to an MQTT Broker](/docs/connecting)
68+
* [Session Handling](/docs/how-to/session-handling)
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/*
2+
* Copyright 2025-present HiveMQ and the HiveMQ Community
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
namespace HiveMQtt.Client;
17+
18+
using HiveMQtt.Client.Options;
19+
20+
/// <summary>
21+
/// Builder class for the ConnectOptions class.
22+
/// </summary>
23+
public class ConnectOptionsBuilder
24+
{
25+
// private static readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger();
26+
private readonly ConnectOptions options;
27+
28+
public ConnectOptionsBuilder() => this.options = new ConnectOptions();
29+
30+
/// <summary>
31+
/// Sets the session expiry interval for the connect options.
32+
/// </summary>
33+
/// <param name="sessionExpiryInterval">The session expiry interval in seconds.</param>
34+
/// <returns>The ConnectOptionsBuilder instance.</returns>
35+
public ConnectOptionsBuilder WithSessionExpiryInterval(long sessionExpiryInterval)
36+
{
37+
this.options.SessionExpiryInterval = sessionExpiryInterval;
38+
return this;
39+
}
40+
41+
/// <summary>
42+
/// Sets the keep alive for the connect options.
43+
/// </summary>
44+
/// <param name="keepAlive">The keep alive in seconds.</param>
45+
/// <returns>The ConnectOptionsBuilder instance.</returns>
46+
public ConnectOptionsBuilder WithKeepAlive(int keepAlive)
47+
{
48+
this.options.KeepAlive = keepAlive;
49+
return this;
50+
}
51+
52+
/// <summary>
53+
/// Sets the clean start for the connect options.
54+
/// </summary>
55+
/// <param name="cleanStart">The clean start flag.</param>
56+
/// <returns>The ConnectOptionsBuilder instance.</returns>
57+
public ConnectOptionsBuilder WithCleanStart(bool cleanStart)
58+
{
59+
this.options.CleanStart = cleanStart;
60+
return this;
61+
}
62+
63+
/// <summary>
64+
/// Builds the ConnectOptions instance.
65+
/// </summary>
66+
/// <returns>The ConnectOptions instance.</returns>
67+
public ConnectOptions Build() => this.options;
68+
}

Source/HiveMQtt/Client/Connection/ConnectionManagerHandlers.cs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,21 @@ public partial class ConnectionManager
1919
internal void HandleIncomingConnAckPacket(ConnAckPacket connAckPacket)
2020
{
2121
Logger.Trace($"{this.Client.Options.ClientId}-(RPH)- <-- Received ConnAck");
22+
23+
// If SessionPresent is false, we need to reset any in-flight transactions
24+
// To manage disconnections, users should subscribe to the OnPublishSent event and timeout
25+
// if no response is received from the broker. This is done to make this client simpler,
26+
// and to avoid the complexity of managing in-flight transactions across connections.
27+
if (!connAckPacket.SessionPresent)
28+
{
29+
this.IPubTransactionQueue.Clear();
30+
this.OPubTransactionQueue.Clear();
31+
}
32+
2233
if (connAckPacket.ReasonCode == ConnAckReasonCode.Success && connAckPacket.Properties.ReceiveMaximum != null)
2334
{
2435
Logger.Debug($"{this.Client.Options.ClientId}-(RPH)- <-- Broker ReceiveMaximum is {connAckPacket.Properties.ReceiveMaximum}.");
2536

26-
// FIXME: A resize would be better to not lose any existing. Can we send publishes before the CONNACK?
2737
// Replace the OPubTransactionQueue BoundedDictionary with a new one with the broker's ReceiveMaximum
2838
this.OPubTransactionQueue = new BoundedDictionaryX<int, List<ControlPacket>>((int)connAckPacket.Properties.ReceiveMaximum);
2939
}

Source/HiveMQtt/Client/HiveMQClient.cs

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,12 +70,31 @@ public HiveMQClient(HiveMQClientOptions? options = null)
7070
public bool IsConnected() => this.Connection.State == ConnectState.Connected;
7171

7272
/// <inheritdoc />
73-
public async Task<ConnectResult> ConnectAsync()
73+
public async Task<ConnectResult> ConnectAsync(ConnectOptions? connectOptions = null)
7474
{
7575
this.Connection.State = ConnectState.Connecting;
7676

7777
Logger.Info("Connecting to broker at {0}:{1}", this.Options.Host, this.Options.Port);
7878

79+
// Apply the connect override options if provided
80+
if (connectOptions != null)
81+
{
82+
if (connectOptions.SessionExpiryInterval != null)
83+
{
84+
this.Options.SessionExpiryInterval = connectOptions.SessionExpiryInterval.Value;
85+
}
86+
87+
if (connectOptions.KeepAlive != null)
88+
{
89+
this.Options.KeepAlive = connectOptions.KeepAlive.Value;
90+
}
91+
92+
if (connectOptions.CleanStart != null)
93+
{
94+
this.Options.CleanStart = connectOptions.CleanStart.Value;
95+
}
96+
}
97+
7998
// Fire the corresponding event
8099
this.BeforeConnectEventLauncher(this.Options);
81100

@@ -193,6 +212,13 @@ public async Task<PublishResult> PublishAsync(MQTT5PublishMessage message, Cance
193212
{
194213
message.Validate();
195214

215+
if (message.QoS.HasValue && this.Connection.ConnectionProperties.MaximumQoS.HasValue &&
216+
(ushort)message.QoS.Value > this.Connection.ConnectionProperties.MaximumQoS.Value)
217+
{
218+
Logger.Debug($"Reducing message QoS from {message.QoS} to broker enforced maximum of {this.Connection.ConnectionProperties.MaximumQoS}");
219+
message.QoS = (QualityOfService)this.Connection.ConnectionProperties.MaximumQoS.Value;
220+
}
221+
196222
// QoS 0: Fast Service
197223
if (message.QoS == QualityOfService.AtMostOnceDelivery)
198224
{

Source/HiveMQtt/Client/IHiveMQClient.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,15 +58,17 @@ public interface IHiveMQClient : IDisposable
5858
/// Asynchronously makes a TCP connection to the remote specified in HiveMQClientOptions and then
5959
/// proceeds to make an MQTT Connect request.
6060
/// </summary>
61+
/// <param name="connectOptions">The connect override options for the MQTT Connect call. These settings
62+
/// will override the settings in HiveMQClientOptions.</param>
6163
/// <returns>A ConnectResult class representing the result of the MQTT connect call.</returns>
62-
public Task<ConnectResult> ConnectAsync();
64+
public Task<ConnectResult> ConnectAsync(ConnectOptions? connectOptions);
6365

6466
/// <summary>
6567
/// Asynchronous disconnect from the previously connected MQTT broker.
6668
/// </summary>
6769
/// <param name="options">The options for the MQTT Disconnect call.</param>
6870
/// <returns>A boolean indicating on success or failure.</returns>
69-
public Task<bool> DisconnectAsync(DisconnectOptions options);
71+
public Task<bool> DisconnectAsync(DisconnectOptions? options);
7072

7173
/// <summary>
7274
/// Publish a message to an MQTT topic.
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/*
2+
* Copyright 2025-present HiveMQ and the HiveMQ Community
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
namespace HiveMQtt.Client.Options;
17+
18+
/// <summary>
19+
/// The options class for a Connect call. The settings here can override the settings that may have
20+
/// been set in the HiveMQClientOptions.
21+
/// </summary>
22+
public class ConnectOptions
23+
{
24+
public long? SessionExpiryInterval { get; set; }
25+
26+
public int? KeepAlive { get; set; }
27+
28+
public bool? CleanStart { get; set; }
29+
}

Source/HiveMQtt/Client/Transport/WebSocketTransport.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -117,9 +117,7 @@ public override async Task<TransportReadResult> ReadAsync(CancellationToken canc
117117
do
118118
{
119119
result = await this.Socket.ReceiveAsync(buffer, CancellationToken.None).ConfigureAwait(false);
120-
#pragma warning disable CA1835 // Prefer the 'Memory'-based overloads for 'ReadAsync' and 'WriteAsync'
121-
await ms.WriteAsync(buffer.Array, buffer.Offset, result.Count, cancellationToken).ConfigureAwait(false);
122-
#pragma warning restore CA1835 // Prefer the 'Memory'-based overloads for 'ReadAsync' and 'WriteAsync'
120+
await ms.WriteAsync(buffer.AsMemory(buffer.Offset, result.Count), cancellationToken).ConfigureAwait(false);
123121
}
124122
while (!result.EndOfMessage);
125123

Source/HiveMQtt/Client/internal/BoundedDictionaryX.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,12 @@ public bool Clear()
168168
{
169169
var numItems = this.dictionary.Count;
170170
this.dictionary.Clear();
171-
this.semaphore.Release(numItems);
171+
172+
if (numItems > 0)
173+
{
174+
this.semaphore.Release(numItems);
175+
}
176+
172177
return true;
173178
}
174179
catch (ArgumentNullException ex)

Source/HiveMQtt/Client/internal/Validator.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ public static void ValidateClientId(string clientId)
4040
}
4141

4242
// Regular expression to match any character that is NOT in the specified set
43+
// We can't use GeneratedRegexAttribute because it's not available in .net 6.0
4344
var regex = new Regex("[^0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ]");
4445

4546
// Check if the input string contains any character that does not match the pattern

0 commit comments

Comments
 (0)