diff --git a/Build/CoAPnet.nuspec b/Build/CoAPnet.nuspec
index 6810b79..011c36e 100644
--- a/Build/CoAPnet.nuspec
+++ b/Build/CoAPnet.nuspec
@@ -12,19 +12,16 @@
true
CoAPnet is a high performance .NET library for CoAP based communication. It provides a CoAP client and a CoAP server. It also has DTLS support out of the box.
-[DTLS] Improved error handling and exposed received DTLS alerts.
-[DTLS] Improved support for cancellation token.
+[Client] Fixed high CPU load issues when closing the client.
Copyright Christian Kratky 2019-2020
CoAP HTTP DTLS Message Telemetry Transport CoAPClient CoAPServer NETStandard IoT InternetOfThings Messaging Hardware Arduino Sensor Actuator M2M ESP Smart Home Cities Automation Xamarin
-
-
diff --git a/Build/nuget.exe b/Build/nuget.exe
new file mode 100644
index 0000000..0f83b17
Binary files /dev/null and b/Build/nuget.exe differ
diff --git a/Source/CoAPnet.Extensions.DTLS/CoapClientConnectOptionsBuilderExtensions.cs b/Source/CoAPnet.Extensions.DTLS/CoapClientConnectOptionsBuilderExtensions.cs
index 69afee9..05df00c 100644
--- a/Source/CoAPnet.Extensions.DTLS/CoapClientConnectOptionsBuilderExtensions.cs
+++ b/Source/CoAPnet.Extensions.DTLS/CoapClientConnectOptionsBuilderExtensions.cs
@@ -17,7 +17,7 @@ public static CoapClientConnectOptionsBuilder WithDtlsTransportLayer(this CoapCl
throw new ArgumentNullException(nameof(options));
}
- clientConnectOptionsBuilder.WithTransportLayer(new DtlsCoapTransportLayer()
+ clientConnectOptionsBuilder.WithTransportLayer(() => new DtlsCoapTransportLayer
{
Credentials = options.Credentials
});
diff --git a/Source/CoAPnet.Extensions.DTLS/DtlsClient.cs b/Source/CoAPnet.Extensions.DTLS/DtlsClient.cs
index 4da797a..9330ad6 100644
--- a/Source/CoAPnet.Extensions.DTLS/DtlsClient.cs
+++ b/Source/CoAPnet.Extensions.DTLS/DtlsClient.cs
@@ -3,7 +3,7 @@
namespace CoAPnet.Extensions.DTLS
{
- public class DtlsClient : DefaultTlsClient
+ public sealed class DtlsClient : DefaultTlsClient
{
readonly ProtocolVersion _protocolVersion;
readonly PreSharedKey _preSharedKey;
diff --git a/Source/CoAPnet.Extensions.DTLS/DtlsCoapTransportLayer.cs b/Source/CoAPnet.Extensions.DTLS/DtlsCoapTransportLayer.cs
index f6954ea..0ed9393 100644
--- a/Source/CoAPnet.Extensions.DTLS/DtlsCoapTransportLayer.cs
+++ b/Source/CoAPnet.Extensions.DTLS/DtlsCoapTransportLayer.cs
@@ -2,9 +2,9 @@
using Org.BouncyCastle.Crypto.Tls;
using Org.BouncyCastle.Security;
using System;
+using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
-using CoAPnet.Internal;
namespace CoAPnet.Extensions.DTLS
{
@@ -72,12 +72,28 @@ public async Task ConnectAsync(CoapTransportLayerConnectOptions connectOptions,
public Task ReceiveAsync(ArraySegment buffer, CancellationToken cancellationToken)
{
+ if (buffer.Array == null)
+ {
+ throw new ArgumentNullException(nameof(buffer));
+ }
+
cancellationToken.ThrowIfCancellationRequested();
using (cancellationToken.Register(() => _udpTransport.Close()))
{
- var received = _dtlsTransport.Receive(buffer.Array, buffer.Offset, buffer.Count, 0);
- return Task.FromResult(received);
+ try
+ {
+ var received = _dtlsTransport.Receive(buffer.Array, buffer.Offset, buffer.Count, 0);
+ return Task.FromResult(received);
+ }
+ catch (ObjectDisposedException)
+ {
+ return Task.FromResult(0);
+ }
+ catch (SocketException)
+ {
+ return Task.FromResult(0);
+ }
}
}
diff --git a/Source/CoAPnet.Extensions.DTLS/PreSharedKeyWrapper.cs b/Source/CoAPnet.Extensions.DTLS/PreSharedKeyWrapper.cs
index 4fddd1b..7cb2937 100644
--- a/Source/CoAPnet.Extensions.DTLS/PreSharedKeyWrapper.cs
+++ b/Source/CoAPnet.Extensions.DTLS/PreSharedKeyWrapper.cs
@@ -3,7 +3,7 @@
namespace CoAPnet.Extensions.DTLS
{
- public class PreSharedKeyWrapper : TlsPskIdentity
+ public sealed class PreSharedKeyWrapper : TlsPskIdentity
{
readonly PreSharedKey _preSharedKey;
diff --git a/Source/CoAPnet.Extensions.DTLS/UdpTransport.cs b/Source/CoAPnet.Extensions.DTLS/UdpTransport.cs
index e990854..7228b9d 100644
--- a/Source/CoAPnet.Extensions.DTLS/UdpTransport.cs
+++ b/Source/CoAPnet.Extensions.DTLS/UdpTransport.cs
@@ -39,11 +39,7 @@ public int Receive(byte[] buf, int off, int len, int waitMillis)
}
EndPoint remoteEndPoint = new IPEndPoint(IPAddress.Any, 0);
- var length = _socket.ReceiveFrom(buf, off, len, SocketFlags.None, ref remoteEndPoint);
-
- Console.WriteLine(length);
-
- return length;
+ return _socket.ReceiveFrom(buf, off, len, SocketFlags.None, ref remoteEndPoint);
}
public void Send(byte[] buf, int off, int len)
@@ -61,6 +57,11 @@ public void Send(byte[] buf, int off, int len)
_socket.SendTo(buf, off, len, SocketFlags.None, _connectOptions.EndPoint);
}
+ public void Close()
+ {
+ Dispose();
+ }
+
public void Dispose()
{
_isDisposed = true;
@@ -68,10 +69,5 @@ public void Dispose()
// There is no need to call "Disconnect" because we use UDP.
_socket?.Dispose();
}
-
- public void Close()
- {
- Dispose();
- }
}
}
diff --git a/Source/CoAPnet.Tests/CoAPnet.Tests.csproj b/Source/CoAPnet.Tests/CoAPnet.Tests.csproj
index d6a1b6b..5a0c07d 100644
--- a/Source/CoAPnet.Tests/CoAPnet.Tests.csproj
+++ b/Source/CoAPnet.Tests/CoAPnet.Tests.csproj
@@ -7,16 +7,17 @@
-
+
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
+
diff --git a/Source/CoAPnet.Tests/CoapClient_Tests.cs b/Source/CoAPnet.Tests/CoapClient_Tests.cs
new file mode 100644
index 0000000..4adfd44
--- /dev/null
+++ b/Source/CoAPnet.Tests/CoapClient_Tests.cs
@@ -0,0 +1,66 @@
+using System.Threading;
+using System.Threading.Tasks;
+using CoAPnet.Client;
+using CoAPnet.Exceptions;
+using CoAPnet.Extensions.DTLS;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace CoAPnet.Tests
+{
+ [TestClass]
+ public class CoapClient_Tests
+ {
+ [TestMethod]
+ [ExpectedException(typeof(CoapCommunicationException))]
+ public async Task Connect_Invalid_Host()
+ {
+ using (var coapClient = new CoapFactory().CreateClient())
+ {
+ await coapClient.ConnectAsync(new CoapClientConnectOptions()
+ {
+ Host = "invalid_host"
+ }, CancellationToken.None);
+ }
+ }
+
+ [TestMethod]
+ [ExpectedException(typeof(CoapCommunicationException))]
+ public async Task Connect_Valid_Host_With_Invalid_Port_Dtls()
+ {
+ using (var coapClient = new CoapFactory().CreateClient())
+ {
+ var options = new CoapClientConnectOptionsBuilder()
+ .WithHost("127.0.0.1")
+ .WithPort(5555)
+ .WithDtlsTransportLayer(o =>
+ {
+ o.WithPreSharedKey("a", "b");
+ })
+ .Build();
+
+ await coapClient.ConnectAsync(options, CancellationToken.None);
+ }
+ }
+
+ [TestMethod]
+ [ExpectedException(typeof(CoapCommunicationTimedOutException))]
+ public async Task Timeout_When_No_Response_Received()
+ {
+ using (var coapClient = new CoapFactory().CreateClient())
+ {
+ var options = new CoapClientConnectOptionsBuilder()
+ .WithHost("127.0.0.1")
+ .WithPort(5555)
+ .Build();
+
+ await coapClient.ConnectAsync(options, CancellationToken.None);
+
+ var request = new CoapRequestBuilder()
+ .WithMethod(CoapRequestMethod.Get)
+ .Build();
+
+ await coapClient.RequestAsync(request, CancellationToken.None);
+ }
+ }
+ }
+}
diff --git a/Source/CoAPnet/Client/CoapClient.cs b/Source/CoAPnet/Client/CoapClient.cs
index ca5108e..bf6423f 100644
--- a/Source/CoAPnet/Client/CoapClient.cs
+++ b/Source/CoAPnet/Client/CoapClient.cs
@@ -150,6 +150,9 @@ internal async Task RequestAsync(CoapMessage requestMessage, Cancel
public void Dispose()
{
+ _cancellationToken?.Cancel(false);
+ _cancellationToken?.Dispose();
+
_lowLevelClient?.Dispose();
}
@@ -160,13 +163,17 @@ async Task ReceiveMessages(CancellationToken cancellationToken)
try
{
var message = await _lowLevelClient.ReceiveAsync(cancellationToken).ConfigureAwait(false);
+
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return;
+ }
+
if (message == null)
{
continue;
}
- cancellationToken.ThrowIfCancellationRequested();
-
if (!_messageDispatcher.TryHandleReceivedMessage(message))
{
if (!await _observationManager.TryHandleReceivedMessage(message).ConfigureAwait(false))
@@ -180,7 +187,7 @@ async Task ReceiveMessages(CancellationToken cancellationToken)
}
catch (Exception exception)
{
- _logger.Error(nameof(CoapClient), exception, "Error while receiving message.");
+ _logger.Error(nameof(CoapClient), exception, "Error while receiving messages.");
}
}
}
diff --git a/Source/CoAPnet/Client/CoapClientConnectOptions.cs b/Source/CoAPnet/Client/CoapClientConnectOptions.cs
index 8103b12..a0cdaa8 100644
--- a/Source/CoAPnet/Client/CoapClientConnectOptions.cs
+++ b/Source/CoAPnet/Client/CoapClientConnectOptions.cs
@@ -15,9 +15,6 @@ public string Host
public TimeSpan CommunicationTimeout { get; set; } = TimeSpan.FromSeconds(10);
- public ICoapTransportLayer TransportLayer
- {
- get; set;
- }
+ public Func TransportLayerFactory { get; set; } = () => new UdpCoapTransportLayer();
}
}
diff --git a/Source/CoAPnet/Client/CoapClientConnectOptionsBuilder.cs b/Source/CoAPnet/Client/CoapClientConnectOptionsBuilder.cs
index 07c29d6..80501dd 100644
--- a/Source/CoAPnet/Client/CoapClientConnectOptionsBuilder.cs
+++ b/Source/CoAPnet/Client/CoapClientConnectOptionsBuilder.cs
@@ -8,19 +8,9 @@ public class CoapClientConnectOptionsBuilder
{
readonly CoapClientConnectOptions _options = new CoapClientConnectOptions
{
- TransportLayer = new UdpCoapTransportLayer() // This is the protocols default transport.
+ TransportLayerFactory = () => new UdpCoapTransportLayer() // This is the protocols default transport.
};
- public CoapClientConnectOptions Build()
- {
- if (_options.TransportLayer == null)
- {
- throw new CoapClientConfigurationInvalidException("Transport layer is not set.", null);
- }
-
- return _options;
- }
-
public CoapClientConnectOptionsBuilder WithHost(string value)
{
_options.Host = value ?? throw new ArgumentNullException(nameof(value));
@@ -45,20 +35,30 @@ public CoapClientConnectOptionsBuilder WithUnencryptedPort()
public CoapClientConnectOptionsBuilder WithTcpTransportLayer()
{
- _options.TransportLayer = new TcpCoapTransportLayer();
+ _options.TransportLayerFactory = () => new TcpCoapTransportLayer();
return this;
}
- public CoapClientConnectOptionsBuilder WithTransportLayer(ICoapTransportLayer value)
+ public CoapClientConnectOptionsBuilder WithTransportLayer(Func transportLayerFactory)
{
- _options.TransportLayer = value ?? throw new ArgumentNullException(nameof(value));
+ _options.TransportLayerFactory = transportLayerFactory ?? throw new ArgumentNullException(nameof(transportLayerFactory));
return this;
}
public CoapClientConnectOptionsBuilder WithUdpTransportLayer()
{
- _options.TransportLayer = new UdpCoapTransportLayer();
+ _options.TransportLayerFactory = () => new UdpCoapTransportLayer();
return this;
}
+
+ public CoapClientConnectOptions Build()
+ {
+ if (_options.TransportLayerFactory == null)
+ {
+ throw new CoapClientConfigurationInvalidException("Transport layer is not set.", null);
+ }
+
+ return _options;
+ }
}
}
\ No newline at end of file
diff --git a/Source/CoAPnet/LowLevelClient/LowLevelCoapClient.cs b/Source/CoAPnet/LowLevelClient/LowLevelCoapClient.cs
index d65894c..539bb22 100644
--- a/Source/CoAPnet/LowLevelClient/LowLevelCoapClient.cs
+++ b/Source/CoAPnet/LowLevelClient/LowLevelCoapClient.cs
@@ -35,9 +35,16 @@ public async Task ConnectAsync(CoapClientConnectOptions options, CancellationTok
{
_connectOptions = options ?? throw new ArgumentNullException(nameof(options));
+ var transportLayer = options.TransportLayerFactory?.Invoke();
+
+ if (transportLayer == null)
+ {
+ throw new InvalidOperationException("No CoAP transport layer is set.");
+ }
+
cancellationToken.ThrowIfCancellationRequested();
- _transportLayerAdapter = new CoapTransportLayerAdapter(options.TransportLayer, _logger);
+ _transportLayerAdapter = new CoapTransportLayerAdapter(transportLayer, _logger);
try
{
var transportLayerConnectOptions = new CoapTransportLayerConnectOptions
@@ -93,14 +100,21 @@ async Task ResolveIPEndPoint(CoapClientConnectOptions connectOptions
await Task.FromResult(0);
throw new NotSupportedException("Resolving DNS end points is not supported for NETSTANDARD1_3. Please pass IP address instead.");
#else
- var hostIPAddresses = await Dns.GetHostAddressesAsync(connectOptions.Host).ConfigureAwait(false);
- if (hostIPAddresses.Length == 0)
+ try
{
- throw new CoapCommunicationException("Failed to resolve DNS end point", null);
- }
+ var hostIPAddresses = await Dns.GetHostAddressesAsync(connectOptions.Host).ConfigureAwait(false);
+ if (hostIPAddresses.Length == 0)
+ {
+ throw new CoapCommunicationException("Failed to resolve DNS end point", null);
+ }
- // We only use the first address for now.
- return new IPEndPoint(hostIPAddresses[0], _connectOptions.Port);
+ // We only use the first address for now.
+ return new IPEndPoint(hostIPAddresses[0], _connectOptions.Port);
+ }
+ catch (Exception exception)
+ {
+ throw new CoapCommunicationException("Error while resolving DNS name.", exception);
+ }
#endif
}
}