Skip to content

Commit c7ec5cc

Browse files
authored
E2E Test Coverage Part 2 & Add .NET 9.0 Support (#218)
1 parent 04593d4 commit c7ec5cc

26 files changed

+339
-104
lines changed

.github/workflows/build.yml

Lines changed: 35 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -11,19 +11,15 @@ on:
1111
workflow_dispatch:
1212

1313
env:
14-
# Disable the .NET logo in the console output.
1514
DOTNET_NOLOGO: true
16-
# Disable the .NET first time experience to skip caching NuGet packages and speed up the build.
1715
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
18-
# Disable sending .NET CLI telemetry to Microsoft.
1916
DOTNET_CLI_TELEMETRY_OPTOUT: true
20-
# Set the build number in MinVer.
21-
MINVERBUILDMETADATA: build.${{github.run_number}}
17+
MINVERBUILDMETADATA: build.${{ github.run_number }}
2218

2319
jobs:
2420
build:
25-
name: pipeline-${{matrix.os}}-dotnet-${{matrix.dotnet-version}}
26-
runs-on: ${{matrix.os}}
21+
name: pipeline-${{ matrix.os }}-dotnet-${{ matrix.dotnet-version }}
22+
runs-on: ${{ matrix.os }}
2723

2824
services:
2925
hivemq:
@@ -41,33 +37,43 @@ jobs:
4137
4238
strategy:
4339
matrix:
44-
# Docker containers not supported on windows and macOS in Github runners.
45-
# os: [ubuntu-latest, windows-latest, macOS-latest]
4640
os: [ubuntu-latest]
47-
dotnet-version: ['6.0.x', '7.0.x', '8.0.x']
41+
dotnet-version: ['6.0.x', '7.0.x', '8.0.x', '9.0.x']
4842
steps:
49-
- name: "Checkout"
43+
- name: Checkout
5044
uses: actions/checkout@v4.2.2
5145
with:
5246
lfs: true
5347
fetch-depth: 0
54-
- name: "Install .NET Core SDK"
48+
49+
- name: Install .NET Core SDK
5550
uses: actions/setup-dotnet@v4
5651
with:
5752
dotnet-version: '${{ matrix.dotnet-version }}'
58-
- name: "Dotnet Tool Restore"
53+
54+
- name: List Installed SDKs
55+
run: dotnet --list-sdks
56+
57+
- name: Create temporary global.json
58+
run: echo '{"sdk":{"version":"${{ matrix.dotnet-version }}"}}' > global.json
59+
60+
- name: Dotnet Tool Restore
5961
run: dotnet tool restore
6062
shell: pwsh
61-
- name: "Dotnet Cake Build"
63+
64+
- name: Dotnet Cake Build
6265
run: dotnet cake --target=Build
6366
shell: pwsh
64-
- name: "Dotnet Cake Test"
65-
run: dotnet cake --target=Test
67+
68+
- name: Dotnet Cake Test
69+
run: dotnet cake --target=Test --framework net${{ matrix.dotnet-version }}
6670
shell: pwsh
67-
- name: "Dotnet Cake Pack"
71+
72+
- name: Dotnet Cake Pack
6873
run: dotnet cake --target=Pack
6974
shell: pwsh
70-
- name: "Upload NuGet packages"
75+
76+
- name: Upload NuGet packages
7177
uses: actions/upload-artifact@v4
7278
with:
7379
name: nuget-packages-${{ matrix.dotnet-version }}
@@ -77,22 +83,30 @@ jobs:
7783
needs: build
7884
runs-on: ubuntu-latest
7985
steps:
80-
- name: "Download NuGet packages"
86+
- name: Download NuGet packages for .NET 6.0
8187
uses: actions/download-artifact@v4
8288
with:
8389
name: nuget-packages-6.0.x
8490
path: ./Artifacts
85-
- name: "Download NuGet packages"
91+
92+
- name: Download NuGet packages for .NET 7.0
8693
uses: actions/download-artifact@v4
8794
with:
8895
name: nuget-packages-7.0.x
8996
path: ./Artifacts
90-
- name: "Download NuGet packages"
97+
98+
- name: Download NuGet packages for .NET 8.0
9199
uses: actions/download-artifact@v4
92100
with:
93101
name: nuget-packages-8.0.x
94102
path: ./Artifacts
95103

104+
- name: Download NuGet packages for .NET 9.0
105+
uses: actions/download-artifact@v4
106+
with:
107+
name: nuget-packages-9.0.x
108+
path: ./Artifacts
109+
96110
- name: Push to NuGet
97111
if: github.event_name == 'release'
98112
run: dotnet nuget push ./Artifacts/*.nupkg --api-key ${{ secrets.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json --skip-duplicate

README.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,15 @@
99
[![Nuget](https://img.shields.io/nuget/dt/HiveMQtt?style=for-the-badge)](https://www.nuget.org/packages/HiveMQtt)
1010
[![GitHub](https://img.shields.io/github/license/hivemq/hivemq-mqtt-client-dotnet?style=for-the-badge)](https://github.com/hivemq/hivemq-mqtt-client-dotnet/blob/main/LICENSE)
1111

12-
13-
![Static Badge](https://img.shields.io/badge/.NET%20Core-6-%23512BD4?style=for-the-badge)
14-
![Static Badge](https://img.shields.io/badge/.NET%20Core-7-%23512BD4?style=for-the-badge)
15-
![Static Badge](https://img.shields.io/badge/.NET%20Core-8-%23512BD4?style=for-the-badge)
12+
![Static Badge](https://img.shields.io/badge/.NET-6.0-%23512BD4?style=for-the-badge)
13+
![Static Badge](https://img.shields.io/badge/.NET-7.0-%23512BD4?style=for-the-badge)
14+
![Static Badge](https://img.shields.io/badge/.NET-8.0-%23512BD4?style=for-the-badge)
15+
![Static Badge](https://img.shields.io/badge/.NET-9.0-%23512BD4?style=for-the-badge)
1616

1717
### 💽 Installation & Compatibility
1818
* **Easy-to-Install**: Available as a [Nuget package](https://www.nuget.org/packages/HiveMQtt).
1919
* **Globally Compatible**: Built to be a fully compliant MQTT 5.0 client compatible with all modern MQTT brokers.
20-
* **Multi-Targeted**: Supports .NET 6.0, 7.0 & 8.0
20+
* **Multi-Targeted**: Supports .NET 6.0, 7.0, 8.0 & 9.0
2121

2222
### 🚀 Features
2323
* **MQTT 5.0 Support**: Fully compliant with the latest [MQTT 5.0 specification](https://docs.oasis-open.org/mqtt/mqtt/v5.0/mqtt-v5.0.html), ensuring compatibility with modern MQTT brokers.

Source/HiveMQtt/Client/Connection/ConnectionManager.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ internal async Task CancelBackgroundTasksAsync()
152152
}
153153
else
154154
{
155-
Logger.Error("ConnectionPublishWriterTask did not complete");
155+
Logger.Trace("ConnectionPublishWriterTask did not complete in time");
156156
}
157157

158158
if (this.ConnectionWriterTask is not null && this.ConnectionWriterTask.IsCompleted)
@@ -161,7 +161,7 @@ internal async Task CancelBackgroundTasksAsync()
161161
}
162162
else
163163
{
164-
Logger.Error("ConnectionWriterTask did not complete");
164+
Logger.Trace("ConnectionWriterTask did not complete in time");
165165
}
166166

167167
if (this.ConnectionReaderTask is not null && this.ConnectionReaderTask.IsCompleted)
@@ -170,7 +170,7 @@ internal async Task CancelBackgroundTasksAsync()
170170
}
171171
else
172172
{
173-
Logger.Error("ConnectionReaderTask did not complete");
173+
Logger.Trace("ConnectionReaderTask did not complete in time");
174174
}
175175

176176
if (this.ReceivedPacketsHandlerTask is not null && this.ReceivedPacketsHandlerTask.IsCompleted)
@@ -179,7 +179,7 @@ internal async Task CancelBackgroundTasksAsync()
179179
}
180180
else
181181
{
182-
Logger.Error("ReceivedPacketsHandlerTask did not complete");
182+
Logger.Trace("ReceivedPacketsHandlerTask did not complete in time");
183183
}
184184

185185
if (this.ConnectionMonitorTask is not null && this.ConnectionMonitorTask.IsCompleted)
@@ -188,7 +188,7 @@ internal async Task CancelBackgroundTasksAsync()
188188
}
189189
else
190190
{
191-
Logger.Error("ConnectionMonitorTask did not complete");
191+
Logger.Trace("ConnectionMonitorTask did not complete in time");
192192
}
193193
}
194194

Source/HiveMQtt/Client/Connection/ConnectionManagerTasks.cs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,13 @@ private async Task RunTaskHealthCheckAsync(Task? task, string taskName)
5151
private Task ConnectionMonitorAsync(CancellationToken cancellationToken) => Task.Run(
5252
async () =>
5353
{
54-
var keepAlivePeriod = this.Client.Options.KeepAlive / 2;
5554
Logger.Trace($"{this.Client.Options.ClientId}-(CM)- Starting...{this.State}");
55+
if (this.Client.Options.KeepAlive == 0)
56+
{
57+
Logger.Debug($"{this.Client.Options.ClientId}-(CM)- KeepAlive is 0. No pings will be sent.");
58+
}
59+
60+
var keepAlivePeriod = this.Client.Options.KeepAlive;
5661
this.lastCommunicationTimer.Start();
5762

5863
while (true)
@@ -62,7 +67,7 @@ private Task ConnectionMonitorAsync(CancellationToken cancellationToken) => Task
6267
// If connected and no recent packets have been sent, send a ping
6368
if (this.State == ConnectState.Connected)
6469
{
65-
if (this.lastCommunicationTimer.Elapsed > TimeSpan.FromSeconds(keepAlivePeriod))
70+
if (this.Client.Options.KeepAlive > 0 && this.lastCommunicationTimer.Elapsed > TimeSpan.FromSeconds(keepAlivePeriod))
6671
{
6772
// Send PingReq
6873
Logger.Trace($"{this.Client.Options.ClientId}-(CM)- --> PingReq");

Source/HiveMQtt/Client/Exceptions/HiveMQttClientException.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,17 @@
1515
*/
1616
namespace HiveMQtt.Client.Exceptions;
1717

18+
using System.Runtime.Serialization;
19+
1820
[Serializable]
1921

2022
public class
2123
HiveMQttClientException : Exception
2224
{
25+
protected HiveMQttClientException(SerializationInfo info, StreamingContext context)
26+
{
27+
}
28+
2329
public HiveMQttClientException()
2430
{
2531
}

Source/HiveMQtt/Client/HiveMQClient.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,7 @@ public async Task<PublishResult> PublishAsync(MQTT5PublishMessage message, Cance
232232
// QoS 2: Assured Delivery
233233
var packetIdentifier = await this.Connection.PacketIDManager.GetAvailablePacketIDAsync().ConfigureAwait(false);
234234
var publishPacket = new PublishPacket(message, (ushort)packetIdentifier);
235-
PublishResult? publishResult = null;
235+
var publishResult = new PublishResult(publishPacket.Message);
236236

237237
Logger.Trace($"Queuing QoS 2 publish packet for send: {publishPacket.GetType().Name} id={publishPacket.PacketIdentifier}");
238238
this.Connection.OutgoingPublishQueue.Enqueue(publishPacket);
@@ -472,4 +472,7 @@ public async Task<UnsubscribeResult> UnsubscribeAsync(UnsubscribeOptions unsubOp
472472
this.AfterUnsubscribeEventLauncher(unsubscribeResult);
473473
return unsubscribeResult;
474474
}
475+
476+
// Method to check if ping are sent based on the KeepAlive option
477+
public bool IsPingSent() => this.Options.KeepAlive > 0;
475478
}

Source/HiveMQtt/Client/HiveMQClientUtil.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,9 @@ public static bool MatchTopic(string pattern, string candidate)
112112

113113
// If pattern contains a multi-level wildcard character, it must be the last character in the pattern
114114
// and it must be preceded by a topic level separator.
115+
#pragma warning disable SYSLIB1045 // Convert to 'GeneratedRegexAttribute'.
115116
var mlwcValidityRegex = new Regex(@"(?<!/)#");
117+
#pragma warning restore SYSLIB1045 // Convert to 'GeneratedRegexAttribute'.
116118

117119
if (pattern.Contains("/#/") | mlwcValidityRegex.IsMatch(pattern))
118120
{

Source/HiveMQtt/Client/Transport/WebSocketTransport.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,9 @@ 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'
120121
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'
121123
}
122124
while (!result.EndOfMessage);
123125

Source/HiveMQtt/Client/internal/Validator.cs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ public static void ValidateTopicName(string topic)
9090
/// <exception cref="ArgumentNullException">Thrown when the topic filter is null.</exception>
9191
/// <exception cref="HiveMQttClientException">Thrown when the topic filter is longer than 65535 characters or empty.</exception>
9292
/// <exception cref="HiveMQttClientException">Thrown when the topic filter contains any null characters.</exception>
93+
/// <exception cref="ArgumentException">Thrown when the topic filter contains invalid wildcard usage.</exception>
9394
public static void ValidateTopicFilter(string topic)
9495
{
9596
ArgumentNullException.ThrowIfNull(topic);
@@ -108,5 +109,29 @@ public static void ValidateTopicFilter(string topic)
108109
{
109110
throw new HiveMQttClientException("A topic name cannot contain any null characters.");
110111
}
112+
113+
// Check for invalid usage of '#' wildcard
114+
if (topic.Contains('#'))
115+
{
116+
if (topic.IndexOf('#') != topic.Length - 1)
117+
{
118+
throw new ArgumentException("The '#' wildcard must be the last character in the topic filter.");
119+
}
120+
121+
if (topic.Length > 1 && topic[^2] != '/')
122+
{
123+
throw new ArgumentException("The '#' wildcard must be preceded by a topic level separator or be the only character.");
124+
}
125+
}
126+
127+
// Check for invalid usage of '+' wildcard
128+
var segments = topic.Split('/');
129+
foreach (var segment in segments)
130+
{
131+
if (segment.Contains('+') && segment != "+")
132+
{
133+
throw new ArgumentException("The '+' wildcard must stand alone and cannot be part of another string.");
134+
}
135+
}
111136
}
112137
}

Source/HiveMQtt/HiveMQtt.csproj

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,24 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup Label="Build">
4-
<TargetFrameworks>net6.0;net7.0;net8.0;</TargetFrameworks>
4+
<!-- Include net6.0 only if the SDK supports it -->
5+
<TargetFrameworks Condition="'$(NETCoreSdkVersion)' >= '6.0.0'">net6.0</TargetFrameworks>
6+
7+
<!-- Add net7.0 conditionally if the SDK supports it -->
8+
<TargetFrameworks Condition="'$(NETCoreSdkVersion)' >= '7.0.0'">
9+
$(TargetFrameworks);net7.0
10+
</TargetFrameworks>
11+
12+
<!-- Add net8.0 conditionally if the SDK supports it -->
13+
<TargetFrameworks Condition="'$(NETCoreSdkVersion)' >= '8.0.0'">
14+
$(TargetFrameworks);net8.0
15+
</TargetFrameworks>
16+
17+
<!-- Add net9.0 conditionally if the SDK supports it -->
18+
<TargetFrameworks Condition="'$(NETCoreSdkVersion)' >= '9.0.0'">
19+
$(TargetFrameworks);net9.0
20+
</TargetFrameworks>
21+
522
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
623
<GenerateDocumentationFile>true</GenerateDocumentationFile>
724

0 commit comments

Comments
 (0)