mirror of
https://github.com/dotnet/MQTTnet.git
synced 2026-10-16 17:23:04 +08:00
Compare commits
6
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4fb3604ad6 | ||
|
|
7636cd0fdb | ||
|
|
740d605fd2 | ||
|
|
9177b6ae68 | ||
|
|
f0ea5c9ecd | ||
|
|
76479ebabe |
@@ -1 +1,3 @@
|
|||||||
* [Client] Restored _Server_ and _Port_ behavior of client options (#2005).
|
* Client: Fixed wrong timeout for keep alive check (thanks to @Erw1nT, #2129)
|
||||||
|
* Client: Fixed extended authentication (thanks to @chkr1011, #2132).
|
||||||
|
* Server: Fixed extended authentication (thanks to @chkr1011, #2132).
|
||||||
+31
-13
@@ -3,7 +3,7 @@ name: CI
|
|||||||
on: [push, pull_request]
|
on: [push, pull_request]
|
||||||
|
|
||||||
env:
|
env:
|
||||||
VERSION: "4.3.6.${{github.run_number}}"
|
VERSION: "4.3.8.${{github.run_number}}"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
@@ -12,12 +12,12 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Setup Windows SDK
|
- name: Setup Windows SDK
|
||||||
uses: GuillaumeFalourd/setup-windows10-sdk-action@v1
|
uses: GuillaumeFalourd/setup-windows10-sdk-action@v2.4
|
||||||
with:
|
with:
|
||||||
sdk-version: 18362
|
sdk-version: 18362
|
||||||
|
|
||||||
- name: Setup .NET SDK
|
- name: Setup .NET SDK
|
||||||
uses: actions/setup-dotnet@v1.9.0
|
uses: actions/setup-dotnet@v4
|
||||||
with:
|
with:
|
||||||
dotnet-version: |
|
dotnet-version: |
|
||||||
3.1.x
|
3.1.x
|
||||||
@@ -28,7 +28,7 @@ jobs:
|
|||||||
uses: microsoft/setup-msbuild@v1.1
|
uses: microsoft/setup-msbuild@v1.1
|
||||||
|
|
||||||
- name: Checkout Code
|
- name: Checkout Code
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Setup Signing Certificate
|
- name: Setup Signing Certificate
|
||||||
run: |
|
run: |
|
||||||
@@ -42,14 +42,6 @@ jobs:
|
|||||||
- name: Build solution
|
- name: Build solution
|
||||||
run: msbuild MQTTnet.sln /t:Build /p:Configuration="Release" /verbosity:m /p:FileVersion=${{ env.VERSION }} /p:AssemblyVersion=${{ env.VERSION }} /p:PackageVersion=${{ env.VERSION }} /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=${{ github.workspace }}\certificate.snk
|
run: msbuild MQTTnet.sln /t:Build /p:Configuration="Release" /verbosity:m /p:FileVersion=${{ env.VERSION }} /p:AssemblyVersion=${{ env.VERSION }} /p:PackageVersion=${{ env.VERSION }} /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=${{ github.workspace }}\certificate.snk
|
||||||
|
|
||||||
- name: Collect nuget Packages
|
|
||||||
uses: actions/upload-artifact@v2
|
|
||||||
with:
|
|
||||||
name: nuget Packages
|
|
||||||
path: |
|
|
||||||
**\*.nupkg
|
|
||||||
**\*.snupkg
|
|
||||||
|
|
||||||
- name: Setup VSTest
|
- name: Setup VSTest
|
||||||
uses: darenm/Setup-VSTest@v1
|
uses: darenm/Setup-VSTest@v1
|
||||||
|
|
||||||
@@ -59,6 +51,32 @@ jobs:
|
|||||||
- name: ASP.NET Tests
|
- name: ASP.NET Tests
|
||||||
run: vstest.console.exe Source\MQTTnet.AspNetCore.Tests\bin\Release\netcoreapp3.1\MQTTnet.AspNetCore.Tests.dll
|
run: vstest.console.exe Source\MQTTnet.AspNetCore.Tests\bin\Release\netcoreapp3.1\MQTTnet.AspNetCore.Tests.dll
|
||||||
|
|
||||||
|
- name: Install sign CLI tool
|
||||||
|
run: dotnet tool install --tool-path . sign --version 0.9.0-beta.23127.3
|
||||||
|
|
||||||
|
- name: Sign nugets
|
||||||
|
shell: pwsh
|
||||||
|
run: >
|
||||||
|
./sign code azure-key-vault `
|
||||||
|
"**/*.nupkg" `
|
||||||
|
--base-directory "${{ github.workspace }}/Source" `
|
||||||
|
--publisher-name "MQTTnet" `
|
||||||
|
--description "MQTTnet" `
|
||||||
|
--description-url "https://github.com/dotnet/MQTTnet" `
|
||||||
|
--azure-key-vault-tenant-id "${{ secrets.AZURE_TENANT_ID }}" `
|
||||||
|
--azure-key-vault-client-id "${{ secrets.AZURE_CLIENT_ID }}" `
|
||||||
|
--azure-key-vault-client-secret "${{ secrets.AZURE_CLIENT_SECRET }}" `
|
||||||
|
--azure-key-vault-certificate "${{ secrets.KEY_VAULT_CERTIFICATE_ID }}" `
|
||||||
|
--azure-key-vault-url "${{ secrets.KEY_VAULT_URL }}"
|
||||||
|
|
||||||
|
- name: Upload signed nuget packages
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: signed-nugets
|
||||||
|
path: |
|
||||||
|
${{ github.workspace }}/Source/**/*.nupkg
|
||||||
|
${{ github.workspace }}/Source/**/*.snupkg
|
||||||
|
|
||||||
- name: Publish MyGet nugets
|
- name: Publish MyGet nugets
|
||||||
if: ${{ github.event_name == 'push' }}
|
if: ${{ github.event_name == 'push' }}
|
||||||
run: dotnet nuget push **/*.nupkg -k ${{ secrets.MYGET_API_KEY }} -s https://www.myget.org/F/mqttnet/api/v3/index.json --skip-duplicate
|
run: dotnet nuget push ${{ github.workspace }}/Source/**/*.nupkg -k ${{ secrets.MYGET_API_KEY }} -s https://www.myget.org/F/mqttnet/api/v3/index.json --skip-duplicate
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
|
|||||||
CODE-OF-CONDUCT.md = CODE-OF-CONDUCT.md
|
CODE-OF-CONDUCT.md = CODE-OF-CONDUCT.md
|
||||||
LICENSE = LICENSE
|
LICENSE = LICENSE
|
||||||
README.md = README.md
|
README.md = README.md
|
||||||
|
.github\workflows\ReleaseNotes.md = .github\workflows\ReleaseNotes.md
|
||||||
|
.github\workflows\ci.yml = .github\workflows\ci.yml
|
||||||
EndProjectSection
|
EndProjectSection
|
||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.AspNetCore", "Source\MQTTnet.AspnetCore\MQTTnet.AspNetCore.csproj", "{F10C4060-F7EE-4A83-919F-FF723E72F94A}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.AspNetCore", "Source\MQTTnet.AspnetCore\MQTTnet.AspNetCore.csproj", "{F10C4060-F7EE-4A83-919F-FF723E72F94A}"
|
||||||
|
|||||||
@@ -8,10 +8,13 @@
|
|||||||
|
|
||||||
using System.Security.Authentication;
|
using System.Security.Authentication;
|
||||||
using System.Security.Cryptography.X509Certificates;
|
using System.Security.Cryptography.X509Certificates;
|
||||||
|
using System.Text;
|
||||||
using MQTTnet.Client;
|
using MQTTnet.Client;
|
||||||
using MQTTnet.Extensions.WebSocket4Net;
|
using MQTTnet.Extensions.WebSocket4Net;
|
||||||
using MQTTnet.Formatter;
|
using MQTTnet.Formatter;
|
||||||
|
using MQTTnet.Protocol;
|
||||||
using MQTTnet.Samples.Helpers;
|
using MQTTnet.Samples.Helpers;
|
||||||
|
using MQTTnet.Server;
|
||||||
|
|
||||||
namespace MQTTnet.Samples.Client;
|
namespace MQTTnet.Samples.Client;
|
||||||
|
|
||||||
@@ -100,6 +103,105 @@ public static class Client_Connection_Samples
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static async Task Connect_Using_Extended_Authentication()
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* This sample uses enhanced (aka extended) authentication (Kerberos) when creating the connection.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Server part...
|
||||||
|
*/
|
||||||
|
|
||||||
|
var mqttFactory = new MqttFactory();
|
||||||
|
|
||||||
|
var serverOptions = mqttFactory.CreateServerOptionsBuilder().WithDefaultEndpoint().Build();
|
||||||
|
var server = mqttFactory.CreateMqttServer(serverOptions);
|
||||||
|
|
||||||
|
server.ValidatingConnectionAsync += async args =>
|
||||||
|
{
|
||||||
|
if (args.AuthenticationMethod == "GS2-KRB5")
|
||||||
|
{
|
||||||
|
var result = await args.SendExtendedAuthenticationExchangeDataAsync(new MqttExtendedAuthenticationExchangeOptions());
|
||||||
|
|
||||||
|
Console.WriteLine($"Received AUTH data from client: {Encoding.UTF8.GetString(result.AuthenticationData)}");
|
||||||
|
|
||||||
|
var authOptions = mqttFactory.CreateMqttExtendedAuthenticationExchangeOptionsBuilder().WithAuthenticationData("reply context token").Build();
|
||||||
|
|
||||||
|
result = await args.SendExtendedAuthenticationExchangeDataAsync(authOptions);
|
||||||
|
|
||||||
|
Console.WriteLine($"Received AUTH data from client: {Encoding.UTF8.GetString(result.AuthenticationData)}");
|
||||||
|
|
||||||
|
args.ResponseAuthenticationData = "outcome of authentication"u8.ToArray();
|
||||||
|
|
||||||
|
// Authentication DONE!
|
||||||
|
args.ReasonCode = MqttConnectReasonCode.Success; // Also the default!
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
args.ReasonCode = MqttConnectReasonCode.BadAuthenticationMethod;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
await server.StartAsync();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Client part...
|
||||||
|
*/
|
||||||
|
|
||||||
|
var mqttClientFactory = new MqttFactory();
|
||||||
|
|
||||||
|
// Use Kerberos sample from the MQTT RFC.
|
||||||
|
var kerberosAuthenticationHandler = new SampleClientKerberosAuthenticationHandler();
|
||||||
|
|
||||||
|
var clientOptions = mqttClientFactory.CreateClientOptionsBuilder()
|
||||||
|
.WithTcpServer("localhost")
|
||||||
|
.WithProtocolVersion(MqttProtocolVersion.V500)
|
||||||
|
.WithExtendedAuthenticationExchangeHandler(kerberosAuthenticationHandler)
|
||||||
|
.WithAuthentication("GS2-KRB5", Array.Empty<byte>())
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
var client = mqttClientFactory.CreateMqttClient();
|
||||||
|
|
||||||
|
var result = await client.ConnectAsync(clientOptions);
|
||||||
|
|
||||||
|
Console.WriteLine($"Client connect result: {result.ResultCode}");
|
||||||
|
}
|
||||||
|
|
||||||
|
class SampleClientKerberosAuthenticationHandler : IMqttExtendedAuthenticationExchangeHandler
|
||||||
|
{
|
||||||
|
private readonly Queue<byte[]> clientTokens = new Queue<byte[]>();
|
||||||
|
|
||||||
|
public SampleClientKerberosAuthenticationHandler()
|
||||||
|
{
|
||||||
|
// The Kerberos sample from the MQTT RFC sends two client tokens after the initial empty token.
|
||||||
|
clientTokens.Enqueue(Encoding.UTF8.GetBytes("initial context token"));
|
||||||
|
clientTokens.Enqueue(Array.Empty<byte>());
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task HandleRequestAsync(MqttExtendedAuthenticationExchangeContext context)
|
||||||
|
{
|
||||||
|
if (context.AuthenticationMethod != "GS2-KRB5")
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Wrong authentication method");
|
||||||
|
}
|
||||||
|
|
||||||
|
Console.WriteLine($"Received AUTH data from server: {Encoding.UTF8.GetString(context.AuthenticationData)}");
|
||||||
|
|
||||||
|
if (!clientTokens.TryDequeue(out var clientToken))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("No more client tokens available");
|
||||||
|
}
|
||||||
|
|
||||||
|
var sendOptions = new MqttExtendedAuthenticationExchangeData
|
||||||
|
{
|
||||||
|
AuthenticationData = clientToken
|
||||||
|
};
|
||||||
|
|
||||||
|
await context.Client.SendExtendedAuthenticationExchangeDataAsync(sendOptions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static async Task Connect_Client_Using_MQTTv5()
|
public static async Task Connect_Client_Using_MQTTv5()
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
@@ -167,6 +168,91 @@ namespace MQTTnet.Tests.Clients.MqttClient
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class TestClientKerberosAuthenticationHandler : IMqttExtendedAuthenticationExchangeHandler
|
||||||
|
{
|
||||||
|
public static IReadOnlyList<string> ServerTokens { get; } = new List<string>()
|
||||||
|
{
|
||||||
|
string.Empty,
|
||||||
|
"reply context token"
|
||||||
|
};
|
||||||
|
|
||||||
|
public static IReadOnlyList<string> ClientTokens { get; } = new List<string>()
|
||||||
|
{
|
||||||
|
string.Empty,
|
||||||
|
"initial context token",
|
||||||
|
string.Empty
|
||||||
|
};
|
||||||
|
|
||||||
|
int tokenCursor;
|
||||||
|
|
||||||
|
public async Task HandleRequestAsync(MqttExtendedAuthenticationExchangeContext context)
|
||||||
|
{
|
||||||
|
if (context.AuthenticationMethod != "GS2-KRB5")
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Wrong authentication method");
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.IsTrue(tokenCursor < ServerTokens.Count, "No more server tokens were expected.");
|
||||||
|
var expectedToken = ServerTokens[tokenCursor++];
|
||||||
|
Assert.AreEqual(expectedToken, Encoding.UTF8.GetString(context.AuthenticationData));
|
||||||
|
|
||||||
|
var sendOptions = new MqttExtendedAuthenticationExchangeData
|
||||||
|
{
|
||||||
|
AuthenticationData = Encoding.UTF8.GetBytes(ClientTokens[tokenCursor])
|
||||||
|
};
|
||||||
|
|
||||||
|
await context.Client.SendExtendedAuthenticationExchangeDataAsync(sendOptions, context.CancellationToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public async Task Use_Extended_Authentication()
|
||||||
|
{
|
||||||
|
// Use Kerberos sample from the MQTT RFC.
|
||||||
|
var kerberosAuthenticationHandler = new TestClientKerberosAuthenticationHandler();
|
||||||
|
|
||||||
|
using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500))
|
||||||
|
{
|
||||||
|
var server = await testEnvironment.StartServer();
|
||||||
|
|
||||||
|
server.ValidatingConnectionAsync += async args =>
|
||||||
|
{
|
||||||
|
if (args.AuthenticationMethod == "GS2-KRB5")
|
||||||
|
{
|
||||||
|
var authenticationData = args.AuthenticationData;
|
||||||
|
string expectedToken;
|
||||||
|
int tokenCursor;
|
||||||
|
for (tokenCursor = 0; tokenCursor < TestClientKerberosAuthenticationHandler.ServerTokens.Count; tokenCursor++)
|
||||||
|
{
|
||||||
|
expectedToken = TestClientKerberosAuthenticationHandler.ClientTokens[tokenCursor];
|
||||||
|
Assert.AreEqual(expectedToken, Encoding.UTF8.GetString(authenticationData), "The received client token is not correct.");
|
||||||
|
|
||||||
|
var serverToken = TestClientKerberosAuthenticationHandler.ServerTokens[tokenCursor];
|
||||||
|
var authOptions = testEnvironment.Factory.CreateMqttExtendedAuthenticationExchangeOptionsBuilder().WithAuthenticationData(serverToken).Build();
|
||||||
|
var result = await args.SendExtendedAuthenticationExchangeDataAsync(authOptions);
|
||||||
|
|
||||||
|
authenticationData = result.AuthenticationData;
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedToken = TestClientKerberosAuthenticationHandler.ClientTokens[tokenCursor];
|
||||||
|
Assert.AreEqual(expectedToken, Encoding.UTF8.GetString(authenticationData), "The received client token is not correct.");
|
||||||
|
args.ResponseAuthenticationData = Encoding.UTF8.GetBytes("outcome of authentication");
|
||||||
|
args.ReasonCode = MqttConnectReasonCode.Success;
|
||||||
|
args.ReasonString = "Authentication successful";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
args.ReasonCode = MqttConnectReasonCode.BadAuthenticationMethod;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var clientOptions = testEnvironment.CreateDefaultClientOptionsBuilder().WithAuthentication("GS2-KRB5", new byte[0]).WithExtendedAuthenticationExchangeHandler(kerberosAuthenticationHandler);
|
||||||
|
var client = await testEnvironment.ConnectClient(clientOptions);
|
||||||
|
|
||||||
|
Assert.IsTrue(client.IsConnected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public async Task No_Unobserved_Exception()
|
public async Task No_Unobserved_Exception()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,13 +2,13 @@
|
|||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
|
||||||
using MQTTnet.Implementations;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Net.Sockets;
|
using System.Net.Sockets;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
using MQTTnet.Implementations;
|
||||||
|
|
||||||
namespace MQTTnet.Tests
|
namespace MQTTnet.Tests
|
||||||
{
|
{
|
||||||
@@ -18,62 +18,68 @@ namespace MQTTnet.Tests
|
|||||||
[TestMethod]
|
[TestMethod]
|
||||||
public async Task Dispose_Channel_While_Used()
|
public async Task Dispose_Channel_While_Used()
|
||||||
{
|
{
|
||||||
var ct = new CancellationTokenSource();
|
using (var ct = new CancellationTokenSource())
|
||||||
var serverSocket = new CrossPlatformSocket(AddressFamily.InterNetwork, ProtocolType.Tcp);
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
serverSocket.Bind(new IPEndPoint(IPAddress.Any, 50001));
|
using (var serverSocket = new CrossPlatformSocket(AddressFamily.InterNetwork, ProtocolType.Tcp))
|
||||||
serverSocket.Listen(0);
|
|
||||||
|
|
||||||
#pragma warning disable 4014
|
|
||||||
Task.Run(async () =>
|
|
||||||
#pragma warning restore 4014
|
|
||||||
{
|
{
|
||||||
while (!ct.IsCancellationRequested)
|
try
|
||||||
{
|
{
|
||||||
var client = await serverSocket.AcceptAsync();
|
serverSocket.Bind(new IPEndPoint(IPAddress.Any, 50001));
|
||||||
var data = new byte[] { 128 };
|
serverSocket.Listen(0);
|
||||||
await client.SendAsync(new ArraySegment<byte>(data), SocketFlags.None);
|
|
||||||
}
|
|
||||||
}, ct.Token);
|
|
||||||
|
|
||||||
var clientSocket = new CrossPlatformSocket(AddressFamily.InterNetwork, ProtocolType.Tcp);
|
|
||||||
await clientSocket.ConnectAsync("localhost", 50001, CancellationToken.None);
|
|
||||||
|
|
||||||
var tcpChannel = new MqttTcpChannel(clientSocket.GetStream(), "test", null);
|
|
||||||
|
|
||||||
await Task.Delay(100, ct.Token);
|
|
||||||
|
|
||||||
var buffer = new byte[1];
|
|
||||||
await tcpChannel.ReadAsync(buffer, 0, 1, ct.Token);
|
|
||||||
|
|
||||||
Assert.AreEqual(128, buffer[0]);
|
|
||||||
|
|
||||||
// This block should fail after dispose.
|
|
||||||
#pragma warning disable 4014
|
#pragma warning disable 4014
|
||||||
Task.Run(() =>
|
Task.Run(
|
||||||
|
async () =>
|
||||||
#pragma warning restore 4014
|
#pragma warning restore 4014
|
||||||
{
|
{
|
||||||
Task.Delay(200, ct.Token);
|
while (!ct.IsCancellationRequested)
|
||||||
tcpChannel.Dispose();
|
{
|
||||||
}, ct.Token);
|
var client = await serverSocket.AcceptAsync(ct.Token);
|
||||||
|
var data = new byte[] { 128 };
|
||||||
|
await client.SendAsync(new ArraySegment<byte>(data), SocketFlags.None);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ct.Token);
|
||||||
|
|
||||||
try
|
var clientSocket = new CrossPlatformSocket(AddressFamily.InterNetwork, ProtocolType.Tcp);
|
||||||
{
|
await clientSocket.ConnectAsync("localhost", 50001, CancellationToken.None);
|
||||||
await tcpChannel.ReadAsync(buffer, 0, 1, CancellationToken.None);
|
|
||||||
|
var tcpChannel = new MqttTcpChannel(clientSocket.GetStream(), "test", null);
|
||||||
|
|
||||||
|
await Task.Delay(100, ct.Token);
|
||||||
|
|
||||||
|
var buffer = new byte[1];
|
||||||
|
await tcpChannel.ReadAsync(buffer, 0, 1, ct.Token);
|
||||||
|
|
||||||
|
Assert.AreEqual(128, buffer[0]);
|
||||||
|
|
||||||
|
// This block should fail after dispose.
|
||||||
|
#pragma warning disable 4014
|
||||||
|
Task.Run(
|
||||||
|
() =>
|
||||||
|
#pragma warning restore 4014
|
||||||
|
{
|
||||||
|
Task.Delay(200, ct.Token);
|
||||||
|
tcpChannel.Dispose();
|
||||||
|
},
|
||||||
|
ct.Token);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await tcpChannel.ReadAsync(buffer, 0, 1, CancellationToken.None);
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
Assert.IsInstanceOfType(exception, typeof(SocketException));
|
||||||
|
Assert.AreEqual(SocketError.OperationAborted, ((SocketException)exception).SocketErrorCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
ct.Cancel(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (Exception exception)
|
|
||||||
{
|
|
||||||
Assert.IsInstanceOfType(exception, typeof(SocketException));
|
|
||||||
Assert.AreEqual(SocketError.OperationAborted, ((SocketException)exception).SocketErrorCode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
ct.Cancel(false);
|
|
||||||
serverSocket.Dispose();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+8
-1
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Threading;
|
||||||
using MQTTnet.Packets;
|
using MQTTnet.Packets;
|
||||||
using MQTTnet.Protocol;
|
using MQTTnet.Protocol;
|
||||||
|
|
||||||
@@ -11,7 +12,7 @@ namespace MQTTnet.Client
|
|||||||
{
|
{
|
||||||
public class MqttExtendedAuthenticationExchangeContext
|
public class MqttExtendedAuthenticationExchangeContext
|
||||||
{
|
{
|
||||||
public MqttExtendedAuthenticationExchangeContext(MqttAuthPacket authPacket, MqttClient client)
|
public MqttExtendedAuthenticationExchangeContext(MqttAuthPacket authPacket, MqttClient client, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if (authPacket == null) throw new ArgumentNullException(nameof(authPacket));
|
if (authPacket == null) throw new ArgumentNullException(nameof(authPacket));
|
||||||
|
|
||||||
@@ -22,6 +23,7 @@ namespace MQTTnet.Client
|
|||||||
UserProperties = authPacket.UserProperties;
|
UserProperties = authPacket.UserProperties;
|
||||||
|
|
||||||
Client = client ?? throw new ArgumentNullException(nameof(client));
|
Client = client ?? throw new ArgumentNullException(nameof(client));
|
||||||
|
CancellationToken = cancellationToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -58,5 +60,10 @@ namespace MQTTnet.Client
|
|||||||
public List<MqttUserProperty> UserProperties { get; }
|
public List<MqttUserProperty> UserProperties { get; }
|
||||||
|
|
||||||
public MqttClient Client { get; }
|
public MqttClient Client { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The cancellation token passed to the operation being authenticated (e.g. ConnectAsync).
|
||||||
|
/// </summary>
|
||||||
|
public CancellationToken CancellationToken { get; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -89,6 +89,8 @@ namespace MQTTnet.Client
|
|||||||
|
|
||||||
public bool IsConnected => (MqttClientConnectionStatus)_connectionStatus == MqttClientConnectionStatus.Connected;
|
public bool IsConnected => (MqttClientConnectionStatus)_connectionStatus == MqttClientConnectionStatus.Connected;
|
||||||
|
|
||||||
|
bool IsConnecting => (MqttClientConnectionStatus)_connectionStatus == MqttClientConnectionStatus.Connecting;
|
||||||
|
|
||||||
public MqttClientOptions Options { get; private set; }
|
public MqttClientOptions Options { get; private set; }
|
||||||
|
|
||||||
public async Task<MqttClientConnectResult> ConnectAsync(MqttClientOptions options, CancellationToken cancellationToken = default)
|
public async Task<MqttClientConnectResult> ConnectAsync(MqttClientOptions options, CancellationToken cancellationToken = default)
|
||||||
@@ -304,7 +306,7 @@ namespace MQTTnet.Client
|
|||||||
}
|
}
|
||||||
|
|
||||||
ThrowIfDisposed();
|
ThrowIfDisposed();
|
||||||
ThrowIfNotConnected();
|
ThrowIfNotConnectedOrConnecting();
|
||||||
|
|
||||||
var authPacket = new MqttAuthPacket
|
var authPacket = new MqttAuthPacket
|
||||||
{
|
{
|
||||||
@@ -454,27 +456,30 @@ namespace MQTTnet.Client
|
|||||||
var connectPacket = MqttPacketFactories.Connect.Create(options);
|
var connectPacket = MqttPacketFactories.Connect.Create(options);
|
||||||
await Send(connectPacket, cancellationToken).ConfigureAwait(false);
|
await Send(connectPacket, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
var receivedPacket = await Receive(cancellationToken).ConfigureAwait(false);
|
for (; ; )
|
||||||
|
|
||||||
switch (receivedPacket)
|
|
||||||
{
|
{
|
||||||
case MqttConnAckPacket connAckPacket:
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
|
var receivedPacket = await Receive(cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (receivedPacket is MqttAuthPacket authPacket)
|
||||||
|
{
|
||||||
|
await ProcessReceivedAuthPacket(authPacket, cancellationToken).ConfigureAwait(false);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (receivedPacket is MqttConnAckPacket connAckPacket)
|
||||||
{
|
{
|
||||||
result = MqttClientResultFactory.ConnectResult.Create(connAckPacket, channelAdapter.PacketFormatterAdapter.ProtocolVersion);
|
result = MqttClientResultFactory.ConnectResult.Create(connAckPacket, channelAdapter.PacketFormatterAdapter.ProtocolVersion);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case MqttAuthPacket _:
|
|
||||||
|
if (receivedPacket != null)
|
||||||
{
|
{
|
||||||
throw new NotSupportedException("Extended authentication handler is not yet supported");
|
throw new MqttProtocolViolationException($"Received other packet than CONNACK or AUTH while connecting ({receivedPacket}).");
|
||||||
}
|
|
||||||
case null:
|
|
||||||
{
|
|
||||||
throw new MqttCommunicationException("Connection closed.");
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException($"Received an unexpected MQTT packet ({receivedPacket}).");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
throw new MqttCommunicationException("Connection closed.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception exception)
|
catch (Exception exception)
|
||||||
@@ -678,10 +683,37 @@ namespace MQTTnet.Client
|
|||||||
return _events.ConnectedEvent.InvokeAsync(eventArgs);
|
return _events.ConnectedEvent.InvokeAsync(eventArgs);
|
||||||
}
|
}
|
||||||
|
|
||||||
Task ProcessReceivedAuthPacket(MqttAuthPacket authPacket)
|
Task ProcessReceivedAuthPacket(MqttAuthPacket authPacket, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var extendedAuthenticationExchangeHandler = Options.ExtendedAuthenticationExchangeHandler;
|
var extendedAuthenticationExchangeHandler = Options.ExtendedAuthenticationExchangeHandler;
|
||||||
return extendedAuthenticationExchangeHandler != null ? extendedAuthenticationExchangeHandler.HandleRequestAsync(new MqttExtendedAuthenticationExchangeContext(authPacket, this)) : CompletedTask.Instance;
|
if (extendedAuthenticationExchangeHandler == null)
|
||||||
|
{
|
||||||
|
// From RFC:
|
||||||
|
// The Client can close the connection at any point in this process. It MAY send a DISCONNECT packet before doing so.
|
||||||
|
// The Server can reject the authentication at any point in this process. It MAY send a CONNACK with a Reason Code of
|
||||||
|
// 0x80 or above as described in section 4.13, and MUST close the Network Connection [MQTT-4.12.0-4].
|
||||||
|
//
|
||||||
|
// If the re-authentication fails, the Client or Server SHOULD send DISCONNECT with an appropriate Reason Code
|
||||||
|
// as described in section 4.13, and MUST close the Network Connection [MQTT-4.12.1-2].
|
||||||
|
//
|
||||||
|
// Since we have no handler there is no chance to fulfil the initial authentication or re-authentication requests.
|
||||||
|
|
||||||
|
if (IsConnecting)
|
||||||
|
{
|
||||||
|
throw new MqttCommunicationException("No handler was provided to handle the extended authentication exchange.");
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = DisconnectAsync(new MqttClientDisconnectOptions
|
||||||
|
{
|
||||||
|
Reason = MqttClientDisconnectOptionsReason.ImplementationSpecificError,
|
||||||
|
ReasonString = "Unable to handle AUTH packet"
|
||||||
|
},
|
||||||
|
cancellationToken);
|
||||||
|
|
||||||
|
return CompletedTask.Instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
return extendedAuthenticationExchangeHandler.HandleRequestAsync(new MqttExtendedAuthenticationExchangeContext(authPacket, this, cancellationToken));
|
||||||
}
|
}
|
||||||
|
|
||||||
Task ProcessReceivedDisconnectPacket(MqttDisconnectPacket disconnectPacket)
|
Task ProcessReceivedDisconnectPacket(MqttDisconnectPacket disconnectPacket)
|
||||||
@@ -935,6 +967,14 @@ namespace MQTTnet.Client
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ThrowIfNotConnectedOrConnecting()
|
||||||
|
{
|
||||||
|
if (!(IsConnected || IsConnecting))
|
||||||
|
{
|
||||||
|
ThrowNotConnectedOrConnecting();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static void ThrowIfOptionsInvalid(MqttClientOptions options)
|
static void ThrowIfOptionsInvalid(MqttClientOptions options)
|
||||||
{
|
{
|
||||||
if (options == null)
|
if (options == null)
|
||||||
@@ -958,6 +998,11 @@ namespace MQTTnet.Client
|
|||||||
throw new MqttClientNotConnectedException();
|
throw new MqttClientNotConnectedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void ThrowNotConnectedOrConnecting()
|
||||||
|
{
|
||||||
|
throw new MqttCommunicationException("The MQTT client is not connected or connecting.");
|
||||||
|
}
|
||||||
|
|
||||||
void TryInitiateDisconnect()
|
void TryInitiateDisconnect()
|
||||||
{
|
{
|
||||||
lock (_disconnectLock)
|
lock (_disconnectLock)
|
||||||
@@ -992,7 +1037,7 @@ namespace MQTTnet.Client
|
|||||||
await ProcessReceivedDisconnectPacket(disconnectPacket).ConfigureAwait(false);
|
await ProcessReceivedDisconnectPacket(disconnectPacket).ConfigureAwait(false);
|
||||||
break;
|
break;
|
||||||
case MqttAuthPacket authPacket:
|
case MqttAuthPacket authPacket:
|
||||||
await ProcessReceivedAuthPacket(authPacket).ConfigureAwait(false);
|
await ProcessReceivedAuthPacket(authPacket, cancellationToken).ConfigureAwait(false);
|
||||||
break;
|
break;
|
||||||
case MqttPingRespPacket _:
|
case MqttPingRespPacket _:
|
||||||
_packetDispatcher.TryDispatch(packet);
|
_packetDispatcher.TryDispatch(packet);
|
||||||
@@ -1051,10 +1096,13 @@ namespace MQTTnet.Client
|
|||||||
|
|
||||||
if (timeWithoutPacketSent > keepAlivePeriod)
|
if (timeWithoutPacketSent > keepAlivePeriod)
|
||||||
{
|
{
|
||||||
using (var timeoutCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken))
|
using (var pingTimeout = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken))
|
||||||
{
|
{
|
||||||
timeoutCancellationTokenSource.CancelAfter(Options.Timeout);
|
// We already reached the keep alive timeout. Due to the RFC part [MQTT-3.1.2-24] the server will wait another
|
||||||
await PingAsync(timeoutCancellationTokenSource.Token).ConfigureAwait(false);
|
// 1/2 of the keep alive time. So we can also use this value as the timeout.
|
||||||
|
pingTimeout.CancelAfter((int)(keepAlivePeriod.TotalMilliseconds / 2));
|
||||||
|
|
||||||
|
await PingAsync(pingTimeout.Token).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -68,8 +68,13 @@ namespace MQTTnet.Client
|
|||||||
Throw(nameof(options.AuthenticationMethod));
|
Throw(nameof(options.AuthenticationMethod));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (options.ExtendedAuthenticationExchangeHandler != null)
|
||||||
|
{
|
||||||
|
Throw(nameof(options.ExtendedAuthenticationExchangeHandler));
|
||||||
|
}
|
||||||
|
|
||||||
// Will relevant properties.
|
// Will relevant properties.
|
||||||
|
|
||||||
if (options.WillPayloadFormatIndicator != MqttPayloadFormatIndicator.Unspecified)
|
if (options.WillPayloadFormatIndicator != MqttPayloadFormatIndicator.Unspecified)
|
||||||
{
|
{
|
||||||
Throw(nameof(options.WillPayloadFormatIndicator));
|
Throw(nameof(options.WillPayloadFormatIndicator));
|
||||||
|
|||||||
@@ -68,12 +68,12 @@ namespace MQTTnet.Formatter.V5
|
|||||||
|
|
||||||
MqttPacket DecodeAuthPacket(ArraySegment<byte> body)
|
MqttPacket DecodeAuthPacket(ArraySegment<byte> body)
|
||||||
{
|
{
|
||||||
ThrowIfBodyIsEmpty(body);
|
|
||||||
|
|
||||||
_bufferReader.SetBuffer(body.Array, body.Offset, body.Count);
|
_bufferReader.SetBuffer(body.Array, body.Offset, body.Count);
|
||||||
|
|
||||||
var packet = new MqttAuthPacket();
|
var packet = new MqttAuthPacket();
|
||||||
|
|
||||||
|
// MQTT spec: The Reason Code and Property Length can be omitted if the Reason Code is 0x00 (Success) and there are no Properties.
|
||||||
|
// In this case the AUTH has a Remaining Length of 0.
|
||||||
if (_bufferReader.EndOfStream)
|
if (_bufferReader.EndOfStream)
|
||||||
{
|
{
|
||||||
packet.ReasonCode = MqttAuthenticateReasonCode.Success;
|
packet.ReasonCode = MqttAuthenticateReasonCode.Success;
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ namespace MQTTnet.Formatter.V5
|
|||||||
public sealed class MqttV5PacketEncoder
|
public sealed class MqttV5PacketEncoder
|
||||||
{
|
{
|
||||||
const int FixedHeaderSize = 1;
|
const int FixedHeaderSize = 1;
|
||||||
|
|
||||||
readonly MqttBufferWriter _bufferWriter;
|
readonly MqttBufferWriter _bufferWriter;
|
||||||
readonly MqttV5PropertiesWriter _propertiesWriter = new MqttV5PropertiesWriter(new MqttBufferWriter(1024, 4096));
|
readonly MqttV5PropertiesWriter _propertiesWriter = new MqttV5PropertiesWriter(new MqttBufferWriter(1024, 4096));
|
||||||
|
|
||||||
@@ -64,13 +64,20 @@ namespace MQTTnet.Formatter.V5
|
|||||||
|
|
||||||
byte EncodeAuthPacket(MqttAuthPacket packet)
|
byte EncodeAuthPacket(MqttAuthPacket packet)
|
||||||
{
|
{
|
||||||
_bufferWriter.WriteByte((byte)packet.ReasonCode);
|
|
||||||
|
|
||||||
_propertiesWriter.WriteAuthenticationMethod(packet.AuthenticationMethod);
|
_propertiesWriter.WriteAuthenticationMethod(packet.AuthenticationMethod);
|
||||||
_propertiesWriter.WriteAuthenticationData(packet.AuthenticationData);
|
_propertiesWriter.WriteAuthenticationData(packet.AuthenticationData);
|
||||||
_propertiesWriter.WriteReasonString(packet.ReasonString);
|
_propertiesWriter.WriteReasonString(packet.ReasonString);
|
||||||
_propertiesWriter.WriteUserProperties(packet.UserProperties);
|
_propertiesWriter.WriteUserProperties(packet.UserProperties);
|
||||||
|
|
||||||
|
// MQTT spec: The Reason Code and Property Length can be omitted if the Reason Code is 0x00 (Success) and there are no Properties.
|
||||||
|
// In this case the AUTH has a Remaining Length of 0.
|
||||||
|
if (packet.ReasonCode == MqttAuthenticateReasonCode.Success && _propertiesWriter.Length == 0)
|
||||||
|
{
|
||||||
|
return MqttBufferWriter.BuildFixedHeader(MqttControlPacketType.Auth);
|
||||||
|
}
|
||||||
|
|
||||||
|
_bufferWriter.WriteByte((byte)packet.ReasonCode);
|
||||||
|
|
||||||
_propertiesWriter.WriteTo(_bufferWriter);
|
_propertiesWriter.WriteTo(_bufferWriter);
|
||||||
_propertiesWriter.Reset();
|
_propertiesWriter.Reset();
|
||||||
|
|
||||||
@@ -526,7 +533,7 @@ namespace MQTTnet.Formatter.V5
|
|||||||
|
|
||||||
static void ThrowIfPacketIdentifierIsInvalid(ushort packetIdentifier, MqttPacket packet)
|
static void ThrowIfPacketIdentifierIsInvalid(ushort packetIdentifier, MqttPacket packet)
|
||||||
{
|
{
|
||||||
// SUBSCRIBE, UNSUBSCRIBE, and PUBLISH(in cases where QoS > 0) Control Packets MUST contain a non-zero 16 - bit Packet Identifier[MQTT - 2.3.1 - 1].
|
// SUBSCRIBE, UNSUBSCRIBE, and PUBLISH(in cases where QoS > 0) Control Packets MUST contain a non-zero 16 - bit Packet Identifier[MQTT - 2.3.1 - 1].
|
||||||
|
|
||||||
if (packetIdentifier == 0)
|
if (packetIdentifier == 0)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -142,12 +142,14 @@ namespace MQTTnet.Implementations
|
|||||||
set => _socket.SendTimeout = value;
|
set => _socket.SendTimeout = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<CrossPlatformSocket> AcceptAsync()
|
public async Task<CrossPlatformSocket> AcceptAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
#if NET452 || NET461
|
#if NET452 || NET461
|
||||||
var clientSocket = await Task.Factory.FromAsync(_socket.BeginAccept, _socket.EndAccept, null).ConfigureAwait(false);
|
var clientSocket = await Task.Factory.FromAsync(_socket.BeginAccept, _socket.EndAccept, null).ConfigureAwait(false);
|
||||||
|
#elif NET7_0_OR_GREATER
|
||||||
|
var clientSocket = await _socket.AcceptAsync(cancellationToken).ConfigureAwait(false);
|
||||||
#else
|
#else
|
||||||
var clientSocket = await _socket.AcceptAsync().ConfigureAwait(false);
|
var clientSocket = await _socket.AcceptAsync().ConfigureAwait(false);
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -141,7 +141,7 @@ namespace MQTTnet.Implementations
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var clientSocket = await _socket.AcceptAsync().ConfigureAwait(false);
|
var clientSocket = await _socket.AcceptAsync(cancellationToken).ConfigureAwait(false);
|
||||||
if (clientSocket == null)
|
if (clientSocket == null)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
|
|||||||
@@ -135,6 +135,11 @@ namespace MQTTnet
|
|||||||
return new MqttClient(clientAdapterFactory, logger);
|
return new MqttClient(clientAdapterFactory, logger);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public MqttExtendedAuthenticationExchangeOptionsFactory CreateMqttExtendedAuthenticationExchangeOptionsBuilder()
|
||||||
|
{
|
||||||
|
return new MqttExtendedAuthenticationExchangeOptionsFactory();
|
||||||
|
}
|
||||||
|
|
||||||
public MqttServer CreateMqttServer(MqttServerOptions options)
|
public MqttServer CreateMqttServer(MqttServerOptions options)
|
||||||
{
|
{
|
||||||
return CreateMqttServer(options, DefaultLogger);
|
return CreateMqttServer(options, DefaultLogger);
|
||||||
|
|||||||
@@ -7,11 +7,14 @@ using System.Collections;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Security.Cryptography.X509Certificates;
|
using System.Security.Cryptography.X509Certificates;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Threading;
|
||||||
using MQTTnet.Adapter;
|
using MQTTnet.Adapter;
|
||||||
using MQTTnet.Formatter;
|
using MQTTnet.Formatter;
|
||||||
using MQTTnet.Internal;
|
using MQTTnet.Internal;
|
||||||
using MQTTnet.Packets;
|
using MQTTnet.Packets;
|
||||||
using MQTTnet.Protocol;
|
using MQTTnet.Protocol;
|
||||||
|
using MQTTnet.Exceptions;
|
||||||
|
|
||||||
namespace MQTTnet.Server
|
namespace MQTTnet.Server
|
||||||
{
|
{
|
||||||
@@ -189,5 +192,54 @@ namespace MQTTnet.Server
|
|||||||
/// A value of 0 indicates that the value is not used.
|
/// A value of 0 indicates that the value is not used.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public uint WillDelayInterval => _connectPacket.WillDelayInterval;
|
public uint WillDelayInterval => _connectPacket.WillDelayInterval;
|
||||||
|
|
||||||
|
|
||||||
|
public async Task<MqttExtendedAuthenticationExchangeResult> SendExtendedAuthenticationExchangeDataAsync(
|
||||||
|
MqttExtendedAuthenticationExchangeOptions options,
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
if (options == null) throw new ArgumentNullException(nameof(options));
|
||||||
|
|
||||||
|
var requestAuthPacket = new MqttAuthPacket
|
||||||
|
{
|
||||||
|
// From RFC: If the initial CONNECT packet included an Authentication Method property then all AUTH packets,
|
||||||
|
// and any successful CONNACK packet MUST include an Authentication Method Property with the same value as in the CONNECT packet [MQTT-4.12.0-5].
|
||||||
|
AuthenticationMethod = AuthenticationMethod,
|
||||||
|
|
||||||
|
// The reason code will stay at continue all the time when connecting. The server will respond with the
|
||||||
|
// CONNACK packet when authentication is done!
|
||||||
|
ReasonCode = MqttAuthenticateReasonCode.ContinueAuthentication,
|
||||||
|
|
||||||
|
AuthenticationData = options.AuthenticationData,
|
||||||
|
ReasonString = options.ReasonString,
|
||||||
|
UserProperties = options.UserProperties
|
||||||
|
};
|
||||||
|
|
||||||
|
await ChannelAdapter.SendPacketAsync(requestAuthPacket, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
|
var responsePacket = await ChannelAdapter.ReceivePacketAsync(cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (responsePacket == null)
|
||||||
|
{
|
||||||
|
throw new MqttCommunicationException("The client closed the connection.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (responsePacket is MqttAuthPacket responseAuthPacket)
|
||||||
|
{
|
||||||
|
if (!string.Equals(AuthenticationMethod, responseAuthPacket.AuthenticationMethod, StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
throw new MqttProtocolViolationException("The authentication method cannot change while authenticating the client.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return MqttExtendedAuthenticationExchangeResultFactory.Create(responseAuthPacket);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (responsePacket is MqttDisconnectPacket disconnectPacket)
|
||||||
|
{
|
||||||
|
return MqttExtendedAuthenticationExchangeResultFactory.Create(disconnectPacket);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new MqttProtocolViolationException("Received other packet than AUTH while authenticating.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+18
@@ -0,0 +1,18 @@
|
|||||||
|
// 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.Packets;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace MQTTnet.Server
|
||||||
|
{
|
||||||
|
public sealed class MqttExtendedAuthenticationExchangeOptions
|
||||||
|
{
|
||||||
|
public byte[] AuthenticationData { get; set; }
|
||||||
|
|
||||||
|
public string ReasonString { get; set; }
|
||||||
|
|
||||||
|
public List<MqttUserProperty> UserProperties { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
+73
@@ -0,0 +1,73 @@
|
|||||||
|
// 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 System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
using System;
|
||||||
|
using MQTTnet.Packets;
|
||||||
|
|
||||||
|
namespace MQTTnet.Server
|
||||||
|
{
|
||||||
|
public sealed class MqttExtendedAuthenticationExchangeOptionsFactory
|
||||||
|
{
|
||||||
|
readonly MqttExtendedAuthenticationExchangeOptions _options = new MqttExtendedAuthenticationExchangeOptions();
|
||||||
|
|
||||||
|
public MqttExtendedAuthenticationExchangeOptions Build()
|
||||||
|
{
|
||||||
|
return _options;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MqttExtendedAuthenticationExchangeOptionsFactory WithAuthenticationData(byte[] authenticationData)
|
||||||
|
{
|
||||||
|
_options.AuthenticationData = authenticationData;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MqttExtendedAuthenticationExchangeOptionsFactory WithAuthenticationData(string authenticationData)
|
||||||
|
{
|
||||||
|
if (authenticationData == null)
|
||||||
|
{
|
||||||
|
_options.AuthenticationData = null;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_options.AuthenticationData = Encoding.UTF8.GetBytes(authenticationData);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MqttExtendedAuthenticationExchangeOptionsFactory WithReasonString(string reasonString)
|
||||||
|
{
|
||||||
|
_options.ReasonString = reasonString;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MqttExtendedAuthenticationExchangeOptionsFactory WithUserProperties(List<MqttUserProperty> userProperties)
|
||||||
|
{
|
||||||
|
_options.UserProperties = userProperties;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MqttExtendedAuthenticationExchangeOptionsFactory WithUserProperty(string name, string value)
|
||||||
|
{
|
||||||
|
if (name == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(name));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_options.UserProperties == null)
|
||||||
|
{
|
||||||
|
_options.UserProperties = new List<MqttUserProperty>();
|
||||||
|
}
|
||||||
|
|
||||||
|
_options.UserProperties.Add(new MqttUserProperty(name, value));
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+18
@@ -0,0 +1,18 @@
|
|||||||
|
// 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.Packets;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace MQTTnet.Server
|
||||||
|
{
|
||||||
|
public sealed class MqttExtendedAuthenticationExchangeResult
|
||||||
|
{
|
||||||
|
public string ReasonString { get; set; }
|
||||||
|
|
||||||
|
public List<MqttUserProperty> UserProperties { get; set; }
|
||||||
|
|
||||||
|
public byte[] AuthenticationData { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
+46
@@ -0,0 +1,46 @@
|
|||||||
|
// 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.Packets;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace MQTTnet.Server
|
||||||
|
{
|
||||||
|
public static class MqttExtendedAuthenticationExchangeResultFactory
|
||||||
|
{
|
||||||
|
public static MqttExtendedAuthenticationExchangeResult Create(MqttAuthPacket authPacket)
|
||||||
|
{
|
||||||
|
if (authPacket == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(authPacket));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new MqttExtendedAuthenticationExchangeResult
|
||||||
|
{
|
||||||
|
AuthenticationData = authPacket.AuthenticationData,
|
||||||
|
|
||||||
|
ReasonString = authPacket.ReasonString,
|
||||||
|
UserProperties = authPacket.UserProperties
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MqttExtendedAuthenticationExchangeResult Create(MqttDisconnectPacket disconnectPacket)
|
||||||
|
{
|
||||||
|
if (disconnectPacket == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(disconnectPacket));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new MqttExtendedAuthenticationExchangeResult
|
||||||
|
{
|
||||||
|
AuthenticationData = null,
|
||||||
|
ReasonString = disconnectPacket.ReasonString,
|
||||||
|
UserProperties = disconnectPacket.UserProperties
|
||||||
|
|
||||||
|
// SessionExpiryInterval makes no sense because the connection is not yet made!
|
||||||
|
// ServerReferences makes no sense when the client initiated a DISCONNECT!
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user