diff --git a/Build/ReleaseNotes.md b/Build/ReleaseNotes.md index 8d53b3a..e03c494 100644 --- a/Build/ReleaseNotes.md +++ b/Build/ReleaseNotes.md @@ -1,4 +1,3 @@ -[Core] Fixed null ref exception in TCP transport layer. -[Core] Added support for .NET 6.0. -[Core] Assemblies are now signed (strong name). -[Core] Updated nuget packages. \ No newline at end of file +[Core] Improved support for .NET 6.0+ (Passing cancellation tokens etc.). +[Client] Both clients are not thread safe. +[Client] Fixed an issue in internal buffer management which may lead to payload corruption. \ No newline at end of file diff --git a/Source/CoAP.TestClient/Program.cs b/Source/CoAP.TestClient/Program.cs index 3699b03..8eff9d3 100644 --- a/Source/CoAP.TestClient/Program.cs +++ b/Source/CoAP.TestClient/Program.cs @@ -1,273 +1,321 @@ -using CoAPnet; -using CoAPnet.Client; -using CoAPnet.Extensions.DTLS; -using CoAPnet.Logging; -using System; +using System; using System.IO; using System.Text; using System.Threading; using System.Threading.Tasks; +using CoAPnet; +using CoAPnet.Client; +using CoAPnet.Extensions.DTLS; +using CoAPnet.Logging; -namespace CoAP.TestClient +namespace CoAP.TestClient; + +static class Program { - static class Program + static async Task Main() { - static async Task Main22() - { - var coapFactory = new CoapFactory(); - coapFactory.DefaultLogger.RegisterSink(new CoapNetLoggerConsoleSink()); + await ObserveTradfriLamp(); + //await SendRequestsToCoapMe(); + } + + static async Task RequestInParallelTasks() + { + var coapFactory = new CoapFactory(); + coapFactory.DefaultLogger.RegisterSink(new CoapNetLoggerConsoleSink()); - using var coapClient = coapFactory.CreateClient(); + const int TaskCount = 100; + using (var coapClient = coapFactory.CreateClient()) + { Console.WriteLine("< CONNECTING..."); var connectOptions = new CoapClientConnectOptionsBuilder() - .WithHost("GW-B8D7AF2B3EA3.fritz.box") - //.WithHost("127.0.0.1") - .WithDtlsTransportLayer(o => - o.WithPreSharedKey("Client_identity", "7x3A1gqWvu9cBGD7")) + .WithHost("coap.me") .Build(); - using (var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(10))) - { - await coapClient.ConnectAsync(connectOptions, cancellationTokenSource.Token); - } + await coapClient.ConnectAsync(connectOptions, CancellationToken.None).ConfigureAwait(false); - var request = new CoapRequestBuilder() - .WithMethod(CoapRequestMethod.Get) - .WithPath("15001") - .Build(); + var tasks = new Task[TaskCount]; - using (var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(10))) + for (var i = 0; i < TaskCount; i++) { - var response = await coapClient.RequestAsync(request, cancellationTokenSource.Token); - PrintResponse(response); + tasks[i] = Task.Run(async () => + { + var request = new CoapRequestBuilder() + .WithMethod(CoapRequestMethod.Get) + .WithPath("separate") + .Build(); + + var response = await coapClient.RequestAsync(request, CancellationToken.None); + //PrintResponse(response); + + Console.WriteLine("Received response."); + }); } + + await Task.WhenAll(tasks); } + } - static async Task MainPsk() - { - // Generate new PSK Token. - - var coapFactory = new CoapFactory(); - coapFactory.DefaultLogger.RegisterSink(new CoapNetLoggerConsoleSink()); + static async Task GetStatusFromTradfriGateway() + { + var coapFactory = new CoapFactory(); + coapFactory.DefaultLogger.RegisterSink(new CoapNetLoggerConsoleSink()); - using (var coapClient = coapFactory.CreateClient()) - { - Console.WriteLine("< CONNECTING..."); + using var coapClient = coapFactory.CreateClient(); - var connectOptions = new CoapClientConnectOptionsBuilder() - .WithHost("GW-B8D7AF2B3EA3.fritz.box") - .WithDtlsTransportLayer(o => - o.WithPreSharedKey("Client_identity", File.ReadAllText(@"D:\SourceCode\Wirehome.Private\Tradfri\Key.txt"))) - .Build(); + Console.WriteLine("< CONNECTING..."); - using (var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(10))) - { - await coapClient.ConnectAsync(connectOptions, cancellationTokenSource.Token).ConfigureAwait(false); - } + var connectOptions = new CoapClientConnectOptionsBuilder() + .WithHost("GW-B8D7AF2B3EA3.fritz.box") + //.WithHost("127.0.0.1") + .WithDtlsTransportLayer(o => + o.WithPreSharedKey("Client_identity", "7x3A1gqWvu9cBGD7")) + .Build(); - var request = new CoapRequestBuilder() - .WithMethod(CoapRequestMethod.Post) - .WithPath("15011/9063") - .WithPayload("{\"9090\":\"WH\"}") - .Build(); + using (var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(10))) + { + await coapClient.ConnectAsync(connectOptions, cancellationTokenSource.Token); + } - var response = await coapClient.RequestAsync(request, CancellationToken.None).ConfigureAwait(false); - PrintResponse(response); - } + var request = new CoapRequestBuilder() + .WithMethod(CoapRequestMethod.Get) + .WithPath("15001") + .Build(); + + using (var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(10))) + { + var response = await coapClient.RequestAsync(request, cancellationTokenSource.Token); + PrintResponse(response); } + } + + static async Task GenerateTradfriPskToken() + { + // Generate new PSK Token. - static async Task Main() + var coapFactory = new CoapFactory(); + coapFactory.DefaultLogger.RegisterSink(new CoapNetLoggerConsoleSink()); + + using (var coapClient = coapFactory.CreateClient()) { - var coapFactory = new CoapFactory(); - coapFactory.DefaultLogger.RegisterSink(new CoapNetLoggerConsoleSink()); + Console.WriteLine("< CONNECTING..."); + + var connectOptions = new CoapClientConnectOptionsBuilder() + .WithHost("GW-B8D7AF2B3EA3.fritz.box") + .WithDtlsTransportLayer(o => + o.WithPreSharedKey("Client_identity", + File.ReadAllText(@"D:\SourceCode\Wirehome.Private\Tradfri\Key.txt"))) + .Build(); - using (var coapClient = coapFactory.CreateClient()) + using (var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(10))) { - Console.WriteLine("< CONNECTING..."); + await coapClient.ConnectAsync(connectOptions, cancellationTokenSource.Token).ConfigureAwait(false); + } - var connectOptions = new CoapClientConnectOptionsBuilder() - .WithHost("GW-B8D7AF2B3EA3.fritz.box") - .WithDtlsTransportLayer(o => - o.WithPreSharedKey("WH", "UP3ThsT7ineCsKoc")) - .Build(); + var request = new CoapRequestBuilder() + .WithMethod(CoapRequestMethod.Post) + .WithPath("15011/9063") + .WithPayload("{\"9090\":\"WH\"}") + .Build(); - using (var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(10))) - { - await coapClient.ConnectAsync(connectOptions, cancellationTokenSource.Token).ConfigureAwait(false); - } + var response = await coapClient.RequestAsync(request, CancellationToken.None).ConfigureAwait(false); + PrintResponse(response); + } + } + + static void PrintResponse(CoapResponse response) + { + Console.WriteLine("> RESPONSE"); + Console.WriteLine(" + Status = " + response.StatusCode); + Console.WriteLine(" + Status code = " + (int)response.StatusCode); + Console.WriteLine(" + Content format = " + response.Options.ContentFormat); + Console.WriteLine(" + Max age = " + response.Options.MaxAge); + Console.WriteLine(" + E tag = " + ByteArrayToString(response.Options.ETag)); + Console.WriteLine(" + Payload = " + Encoding.UTF8.GetString(response.Payload)); + Console.WriteLine(); + } + + static string ByteArrayToString(byte[] buffer) + { + if (buffer == null || buffer.Length == 0) + { + return string.Empty; + } + + var hex = new StringBuilder(buffer.Length * 2); + hex.Append("0x"); + + foreach (var @byte in buffer) + { + hex.AppendFormat("{0:x2}", @byte); + } - var request = new CoapRequestBuilder() - .WithMethod(CoapRequestMethod.Get) - .WithPath("15001") - .Build(); + return hex.ToString(); + } - var response = await coapClient.RequestAsync(request, CancellationToken.None).ConfigureAwait(false); - PrintResponse(response); + static async Task SendRequestsToCoapMe() + { + var coapFactory = new CoapFactory(); + coapFactory.DefaultLogger.RegisterSink(new CoapNetLoggerConsoleSink()); - request = new CoapRequestBuilder() - .WithMethod(CoapRequestMethod.Get) - .WithPath("15001/65550") - .Build(); + using (var coapClient = coapFactory.CreateClient()) + { + Console.WriteLine("< CONNECTING..."); - response = await coapClient.RequestAsync(request, CancellationToken.None).ConfigureAwait(false); - PrintResponse(response); + var connectOptions = new CoapClientConnectOptionsBuilder() + .WithHost("coap.me") + .Build(); - request = new CoapRequestBuilder() - .WithMethod(CoapRequestMethod.Put) - .WithPath("15001/65550") - .WithPayload("{\"3311\": [{\"5850\": 1}]}") - .Build(); + await coapClient.ConnectAsync(connectOptions, CancellationToken.None).ConfigureAwait(false); - response = await coapClient.RequestAsync(request, CancellationToken.None).ConfigureAwait(false); - PrintResponse(response); + // separate - var observeOptions = new CoapObserveOptionsBuilder() - .WithPath("15001/65550") - .WithResponseHandler(new ResponseHandler()) - .Build(); + var request = new CoapRequestBuilder() + .WithMethod(CoapRequestMethod.Get) + .WithPath("separate") + .Build(); - var observeResponse = await coapClient.ObserveAsync(observeOptions, CancellationToken.None).ConfigureAwait(false); - PrintResponse(observeResponse.Response); + var response = await coapClient.RequestAsync(request, CancellationToken.None); + PrintResponse(response); - Console.WriteLine("Observed messages for lamp!"); + // hello - Console.WriteLine("Press any key to unobserve."); - Console.ReadLine(); + request = new CoapRequestBuilder() + .WithMethod(CoapRequestMethod.Get) + .WithPath("hello") + .Build(); - await coapClient.StopObservationAsync(observeResponse, CancellationToken.None).ConfigureAwait(false); - } - } + response = await coapClient.RequestAsync(request, CancellationToken.None); + PrintResponse(response); - class ResponseHandler : ICoapResponseHandler - { - public Task HandleResponseAsync(HandleResponseContext context) - { - Console.WriteLine("> RECEIVED OBSERVED RESOURCE"); - Console.WriteLine(" + Sequence number = " + context.SequenceNumber); - PrintResponse(context.Response); - return Task.CompletedTask; - } - } + // broken - public static void PrintResponse(CoapResponse response) - { - Console.WriteLine("> RESPONSE"); - Console.WriteLine(" + Status = " + response.StatusCode); - Console.WriteLine(" + Status code = " + (int) response.StatusCode); - Console.WriteLine(" + Content format = " + response.Options.ContentFormat); - Console.WriteLine(" + Max age = " + response.Options.MaxAge); - Console.WriteLine(" + E tag = " + ByteArrayToString(response.Options.ETag)); - Console.WriteLine(" + Payload = " + Encoding.UTF8.GetString(response.Payload)); - Console.WriteLine(); - } + request = new CoapRequestBuilder() + .WithMethod(CoapRequestMethod.Get) + .WithPath("broken") + .Build(); - static string ByteArrayToString(byte[] buffer) - { - if (buffer == null || buffer.Length == 0) - { - return string.Empty; - } + response = await coapClient.RequestAsync(request, CancellationToken.None); + PrintResponse(response); - var hex = new StringBuilder(buffer.Length * 2); - hex.Append("0x"); + // secret - foreach (var @byte in buffer) - { - hex.AppendFormat("{0:x2}", @byte); - } + request = new CoapRequestBuilder() + .WithMethod(CoapRequestMethod.Get) + .WithPath("secret") + .Build(); - return hex.ToString(); - } + response = await coapClient.RequestAsync(request, CancellationToken.None); + PrintResponse(response); - static async Task Main9() - { - var coapFactory = new CoapFactory(); - coapFactory.DefaultLogger.RegisterSink(new CoapNetLoggerConsoleSink()); + // large-create - using (var coapClient = coapFactory.CreateClient()) - { - Console.WriteLine("< CONNECTING..."); + request = new CoapRequestBuilder() + .WithMethod(CoapRequestMethod.Get) + .WithPath("large-create") + .Build(); - var connectOptions = new CoapClientConnectOptionsBuilder() - .WithHost("coap.me") - .Build(); + response = await coapClient.RequestAsync(request, CancellationToken.None); + PrintResponse(response); - await coapClient.ConnectAsync(connectOptions, CancellationToken.None).ConfigureAwait(false); + // location1/location2/location3 - // separate + request = new CoapRequestBuilder() + .WithMethod(CoapRequestMethod.Get) + .WithPath("location1/location2/location3") + .Build(); - var request = new CoapRequestBuilder() - .WithMethod(CoapRequestMethod.Get) - .WithPath("separate") - .Build(); + response = await coapClient.RequestAsync(request, CancellationToken.None); + PrintResponse(response); - var response = await coapClient.RequestAsync(request, CancellationToken.None); - PrintResponse(response); + // large - // hello + request = new CoapRequestBuilder() + .WithMethod(CoapRequestMethod.Get) + .WithPath("large") + .Build(); - request = new CoapRequestBuilder() - .WithMethod(CoapRequestMethod.Get) - .WithPath("hello") - .Build(); + response = await coapClient.RequestAsync(request, CancellationToken.None); + PrintResponse(response); - response = await coapClient.RequestAsync(request, CancellationToken.None); - PrintResponse(response); + await Task.Delay(TimeSpan.FromSeconds(10)); + } + } - // broken + static async Task ObserveTradfriLamp() + { + var coapFactory = new CoapFactory(); + coapFactory.DefaultLogger.RegisterSink(new CoapNetLoggerConsoleSink()); - request = new CoapRequestBuilder() - .WithMethod(CoapRequestMethod.Get) - .WithPath("broken") - .Build(); + using (var coapClient = coapFactory.CreateClient()) + { + Console.WriteLine("< CONNECTING..."); - response = await coapClient.RequestAsync(request, CancellationToken.None); - PrintResponse(response); + var connectOptions = new CoapClientConnectOptionsBuilder() + .WithHost("GW-B8D7AF2B3EA3.fritz.box") + .WithDtlsTransportLayer(o => + o.WithPreSharedKey("WH", "UP3ThsT7ineCsKoc")) + .Build(); - // secret + using (var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(10))) + { + await coapClient.ConnectAsync(connectOptions, cancellationTokenSource.Token).ConfigureAwait(false); + } - request = new CoapRequestBuilder() - .WithMethod(CoapRequestMethod.Get) - .WithPath("secret") - .Build(); + var request = new CoapRequestBuilder() + .WithMethod(CoapRequestMethod.Get) + .WithPath("15001") + .Build(); - response = await coapClient.RequestAsync(request, CancellationToken.None); - PrintResponse(response); + var response = await coapClient.RequestAsync(request, CancellationToken.None).ConfigureAwait(false); + PrintResponse(response); - // large-create + request = new CoapRequestBuilder() + .WithMethod(CoapRequestMethod.Get) + .WithPath("15001/65557") + .Build(); - request = new CoapRequestBuilder() - .WithMethod(CoapRequestMethod.Get) - .WithPath("large-create") - .Build(); + response = await coapClient.RequestAsync(request, CancellationToken.None).ConfigureAwait(false); + PrintResponse(response); - response = await coapClient.RequestAsync(request, CancellationToken.None); - PrintResponse(response); + request = new CoapRequestBuilder() + .WithMethod(CoapRequestMethod.Put) + .WithPath("15001/65557") + .WithPayload("{\"3311\": [{\"5850\": 1}]}") + .Build(); - // location1/location2/location3 + response = await coapClient.RequestAsync(request, CancellationToken.None).ConfigureAwait(false); + PrintResponse(response); - request = new CoapRequestBuilder() - .WithMethod(CoapRequestMethod.Get) - .WithPath("location1/location2/location3") - .Build(); + var observeOptions = new CoapObserveOptionsBuilder() + .WithPath("15001/65557") + .WithResponseHandler(new ResponseHandler()) + .Build(); - response = await coapClient.RequestAsync(request, CancellationToken.None); - PrintResponse(response); + var observeResponse = + await coapClient.ObserveAsync(observeOptions, CancellationToken.None).ConfigureAwait(false); + + PrintResponse(observeResponse.Response); - // large + Console.WriteLine("Observed messages for lamp!"); - request = new CoapRequestBuilder() - .WithMethod(CoapRequestMethod.Get) - .WithPath("large") - .Build(); + Console.WriteLine("Press any key to unobserve."); + Console.ReadLine(); - response = await coapClient.RequestAsync(request, CancellationToken.None); - PrintResponse(response); + await coapClient.StopObservationAsync(observeResponse, CancellationToken.None).ConfigureAwait(false); + } + } - await Task.Delay(TimeSpan.FromSeconds(10)); - } + class ResponseHandler : + ICoapResponseHandler + { + public Task HandleResponseAsync(HandleResponseContext context) + { + Console.WriteLine("> RECEIVED OBSERVED RESOURCE"); + Console.WriteLine(" + Sequence number = " + context.SequenceNumber); + PrintResponse(context.Response); + return Task.CompletedTask; } } } \ No newline at end of file diff --git a/Source/CoAPnet.Extensions.DTLS/CoAPnet.Extensions.DTLS.csproj b/Source/CoAPnet.Extensions.DTLS/CoAPnet.Extensions.DTLS.csproj index dce7492..c29f5ad 100644 --- a/Source/CoAPnet.Extensions.DTLS/CoAPnet.Extensions.DTLS.csproj +++ b/Source/CoAPnet.Extensions.DTLS/CoAPnet.Extensions.DTLS.csproj @@ -28,8 +28,10 @@ true true LICENSE - + 7.3 For release notes please go to CoAPnet release notes (https://www.nuget.org/packages/CoAPnet/). + true + CS1591 diff --git a/Source/CoAPnet.Tests/CoapClient_Tests.cs b/Source/CoAPnet.Tests/CoapClient_Tests.cs index 4adfd44..a2e2c1a 100644 --- a/Source/CoAPnet.Tests/CoapClient_Tests.cs +++ b/Source/CoAPnet.Tests/CoapClient_Tests.cs @@ -49,7 +49,7 @@ public async Task Timeout_When_No_Response_Received() using (var coapClient = new CoapFactory().CreateClient()) { var options = new CoapClientConnectOptionsBuilder() - .WithHost("127.0.0.1") + .WithHost("123.123.123.1") .WithPort(5555) .Build(); diff --git a/Source/CoAPnet.Tests/CoapMessageDecoder_Tests.cs b/Source/CoAPnet.Tests/CoapMessageDecoder_Tests.cs index 1ae8d2a..14d0a06 100644 --- a/Source/CoAPnet.Tests/CoapMessageDecoder_Tests.cs +++ b/Source/CoAPnet.Tests/CoapMessageDecoder_Tests.cs @@ -1,4 +1,5 @@ -using CoAPnet.Logging; +using System; +using CoAPnet.Logging; using CoAPnet.Protocol; using CoAPnet.Protocol.Encoding; using CoAPnet.Protocol.Options; @@ -9,7 +10,7 @@ namespace CoAPnet.Tests { [TestClass] - public class CoapMessageDecoder_Tests + public sealed class CoapMessageDecoder_Tests { [TestMethod] public void Encode_And_Decode_Full() @@ -22,15 +23,16 @@ public void Encode_And_Decode_Full() Code = CoapMessageCodes.Post, Id = 0x5876, Token = new byte[] { 1, 2, 3, 4 }, - Payload = Encoding.UTF8.GetBytes("payloadOver13chars") + Payload = Encoding.UTF8.GetBytes("payloadOver13chars"), + Options = new List + { + optionBuilder.CreateETag(new byte[12]), + optionBuilder.CreateUriPort(5648), + optionBuilder.CreateContentFormat(CoapMessageContentFormat.ApplicationJson) + } }; - message.Options = new List - { - optionBuilder.CreateUriPort(5648) - }; - - Enocde_And_Decode_Internal(message); + EncodeAndDecodeInternal(message); } [TestMethod] @@ -43,15 +45,15 @@ public void Encode_And_Decode_Payload_Length_12() Type = CoapMessageType.Confirmable, Code = CoapMessageCodes.Put, Id = ushort.MaxValue, - Payload = Encoding.UTF8.GetBytes("123456789012") - }; - - message.Options = new List - { - optionBuilder.CreateUriPort(5648) + Payload = Encoding.UTF8.GetBytes("123456789012"), + Options = new List + { + optionBuilder.CreateUriPort(5648), + optionBuilder.CreateContentFormat(CoapMessageContentFormat.ApplicationJson) + } }; - Enocde_And_Decode_Internal(message); + EncodeAndDecodeInternal(message); } [TestMethod] @@ -65,15 +67,15 @@ public void Encode_And_Decode_No_Payload() Code = CoapMessageCodes.Get, Id = 0x5876, Token = new byte[] { 1, 2, 3, 4 }, - Payload = null - }; - - message.Options = new List - { - optionBuilder.CreateUriPort(50) + Payload = null, + Options = new List + { + optionBuilder.CreateUriPort(50), + optionBuilder.CreateContentFormat(CoapMessageContentFormat.ApplicationJson) + } }; - Enocde_And_Decode_Internal(message); + EncodeAndDecodeInternal(message); } [TestMethod] @@ -87,15 +89,15 @@ public void Encode_And_Decode_No_Token() Code = CoapMessageCodes.Put, Id = 0x5876, Token = null, - Payload = null - }; - - message.Options = new List - { - optionBuilder.CreateUriPort(66000) + Payload = null, + Options = new List + { + optionBuilder.CreateUriPort(66000), + optionBuilder.CreateContentFormat(CoapMessageContentFormat.ApplicationJson) + } }; - Enocde_And_Decode_Internal(message); + EncodeAndDecodeInternal(message); } [TestMethod] @@ -109,24 +111,30 @@ public void Encode_And_Decode_Multiple_Uri_Path() Code = CoapMessageCodes.Delete, Id = 0x50, Token = null, - Payload = null + Payload = null, + Options = new List + { + optionBuilder.CreateUriPath("a"), + optionBuilder.CreateUriPath("b"), + optionBuilder.CreateUriPath("c") + } }; - message.Options = new List - { - optionBuilder.CreateUriPath("a"), - optionBuilder.CreateUriPath("b"), - optionBuilder.CreateUriPath("c") - }; - - Enocde_And_Decode_Internal(message); + EncodeAndDecodeInternal(message); } - void Enocde_And_Decode_Internal(CoapMessage message) + static void EncodeAndDecodeInternal(CoapMessage message) { var messageEncoder = new CoapMessageEncoder(); var messageBuffer = messageEncoder.Encode(message); + // Use a larger buffer to ensure that reading within the bounds works. + var largerMessageBuffer = new byte[messageBuffer.Count * 2]; + Array.Copy(messageBuffer.Array, messageBuffer.Offset, largerMessageBuffer, 0, messageBuffer.Count); + Array.Copy(messageBuffer.Array, messageBuffer.Offset, largerMessageBuffer, messageBuffer.Count, messageBuffer.Count); + + messageBuffer = new ArraySegment(largerMessageBuffer, 0, messageBuffer.Count); + var messageDecoder = new CoapMessageDecoder(new CoapNetLogger()); var decodedMessage = messageDecoder.Decode(messageBuffer); diff --git a/Source/CoAPnet.Tests/CoapMessageReader_Tests.cs b/Source/CoAPnet.Tests/CoapMessageReader_Tests.cs index b398f24..dbee9f7 100644 --- a/Source/CoAPnet.Tests/CoapMessageReader_Tests.cs +++ b/Source/CoAPnet.Tests/CoapMessageReader_Tests.cs @@ -15,10 +15,13 @@ public void Read_Bits() writer.WriteBits(15, 4); var reader = new CoapMessageReader(writer.ToArray()); - + Assert.IsFalse(reader.EndOfStream); Assert.AreEqual(1, reader.ReadBits(2)); + Assert.IsTrue(reader.EndOfStream); Assert.AreEqual(3, reader.ReadBits(2)); + Assert.IsTrue(reader.EndOfStream); Assert.AreEqual(15, reader.ReadBits(4)); + Assert.IsTrue(reader.EndOfStream); } [TestMethod] @@ -31,11 +34,15 @@ public void Read_Spanning_Bits() writer.WriteBits(254, 8); var reader = new CoapMessageReader(writer.ToArray()); - + Assert.IsFalse(reader.EndOfStream); Assert.AreEqual(1, reader.ReadBits(2)); + Assert.IsFalse(reader.EndOfStream); Assert.AreEqual(3, reader.ReadBits(2)); + Assert.IsFalse(reader.EndOfStream); Assert.AreEqual(15, reader.ReadBits(4)); + Assert.IsFalse(reader.EndOfStream); Assert.AreEqual(254, reader.ReadBits(8)); + Assert.IsTrue(reader.EndOfStream); } } } diff --git a/Source/CoAPnet.sln b/Source/CoAPnet.sln index 17aa6a7..d75f709 100644 --- a/Source/CoAPnet.sln +++ b/Source/CoAPnet.sln @@ -14,7 +14,6 @@ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{050EC2DA-10AC-4453-BE76-B3BA90CA1EB6}" ProjectSection(SolutionItems) = preProject ..\Build\build.ps1 = ..\Build\build.ps1 - ..\Build\CoAPnet.Extensions.DTLS.nuspec = ..\Build\CoAPnet.Extensions.DTLS.nuspec ..\Build\ReleaseNotes.md = ..\Build\ReleaseNotes.md ..\Build\upload.ps1 = ..\Build\upload.ps1 EndProjectSection diff --git a/Source/CoAPnet.sln.DotSettings b/Source/CoAPnet.sln.DotSettings new file mode 100644 index 0000000..f111090 --- /dev/null +++ b/Source/CoAPnet.sln.DotSettings @@ -0,0 +1,2 @@ + + True \ No newline at end of file diff --git a/Source/CoAPnet/Client/CoapClient.cs b/Source/CoAPnet/Client/CoapClient.cs index d830216..8e6874e 100644 --- a/Source/CoAPnet/Client/CoapClient.cs +++ b/Source/CoAPnet/Client/CoapClient.cs @@ -1,37 +1,38 @@ -using CoAPnet.Internal; +using System; +using System.Threading; +using System.Threading.Tasks; +using CoAPnet.Internal; using CoAPnet.Logging; using CoAPnet.LowLevelClient; using CoAPnet.MessageDispatcher; using CoAPnet.Protocol; using CoAPnet.Protocol.Observe; using CoAPnet.Protocol.Options; -using System; -using System.Threading; -using System.Threading.Tasks; namespace CoAPnet.Client { public sealed class CoapClient : ICoapClient { - readonly CoapRequestToMessageConverter _requestToMessageConverter = new CoapRequestToMessageConverter(); - readonly CoapMessageToResponseConverter _messageToResponseConverter = new CoapMessageToResponseConverter(); + readonly CoapNetLogger _logger; + readonly LowLevelCoapClient _lowLevelClient; readonly CoapMessageDispatcher _messageDispatcher = new CoapMessageDispatcher(); readonly CoapMessageIdProvider _messageIdProvider = new CoapMessageIdProvider(); readonly CoapMessageTokenProvider _messageTokenProvider = new CoapMessageTokenProvider(); - - readonly CoapNetLogger _logger; + readonly CoapMessageToResponseConverter _messageToResponseConverter = new CoapMessageToResponseConverter(); readonly CoapClientObservationManager _observationManager; - readonly LowLevelCoapClient _lowLevelClient; + readonly CoapRequestToMessageConverter _requestToMessageConverter = new CoapRequestToMessageConverter(); + + CancellationTokenSource _cancellationToken; CoapClientConnectOptions _connectOptions; - CancellationTokenSource _cancellationToken; public CoapClient(CoapNetLogger logger) { - _logger = logger; + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); _lowLevelClient = new LowLevelCoapClient(_logger); - _observationManager = new CoapClientObservationManager(_messageToResponseConverter, _lowLevelClient, _logger); + _observationManager = + new CoapClientObservationManager(_messageToResponseConverter, _lowLevelClient, _logger); } public async Task ConnectAsync(CoapClientConnectOptions options, CancellationToken cancellationToken) @@ -39,7 +40,7 @@ public async Task ConnectAsync(CoapClientConnectOptions options, CancellationTok _connectOptions = options ?? throw new ArgumentNullException(nameof(options)); await _lowLevelClient.ConnectAsync(options, cancellationToken).ConfigureAwait(false); - + _cancellationToken = new CancellationTokenSource(); ParallelTask.StartLongRunning(() => ReceiveMessages(_cancellationToken.Token), _cancellationToken.Token); } @@ -58,13 +59,15 @@ public async Task RequestAsync(CoapRequest request, CancellationTo var payload = responseMessage.Payload; if (CoapClientBlockTransferReceiver.IsBlockTransfer(responseMessage)) { - payload = await new CoapClientBlockTransferReceiver(requestMessage, responseMessage, this, _logger).ReceiveFullPayload(cancellationToken).ConfigureAwait(false); + payload = await new CoapClientBlockTransferReceiver(requestMessage, responseMessage, this, _logger) + .ReceiveFullPayload(cancellationToken).ConfigureAwait(false); } return _messageToResponseConverter.Convert(responseMessage, payload); } - public async Task ObserveAsync(CoapObserveOptions options, CancellationToken cancellationToken) + public async Task ObserveAsync(CoapObserveOptions options, + CancellationToken cancellationToken) { if (options is null) { @@ -74,7 +77,7 @@ public async Task ObserveAsync(CoapObserveOptions options, var request = new CoapRequest { Method = CoapRequestMethod.Get, - Options = options.Request.Options, + Options = options.Request.Options }; var token = _messageTokenProvider.Next(); @@ -88,7 +91,8 @@ public async Task ObserveAsync(CoapObserveOptions options, var payload = responseMessage.Payload; if (CoapClientBlockTransferReceiver.IsBlockTransfer(responseMessage)) { - payload = await new CoapClientBlockTransferReceiver(requestMessage, responseMessage, this, _logger).ReceiveFullPayload(cancellationToken).ConfigureAwait(false); + payload = await new CoapClientBlockTransferReceiver(requestMessage, responseMessage, this, _logger) + .ReceiveFullPayload(cancellationToken).ConfigureAwait(false); } _observationManager.Register(token, options.ResponseHandler); @@ -119,6 +123,19 @@ public async Task StopObservationAsync(CoapObserveResponse observeResponse, Canc _observationManager.Deregister(observeResponse.Token); } + public void Dispose() + { + try + { + _cancellationToken?.Cancel(false); + } + finally + { + _cancellationToken?.Dispose(); + _lowLevelClient?.Dispose(); + } + } + internal async Task RequestAsync(CoapMessage requestMessage, CancellationToken cancellationToken) { if (requestMessage is null) @@ -129,11 +146,13 @@ internal async Task RequestAsync(CoapMessage requestMessage, Cancel requestMessage.Id = _messageIdProvider.Next(); var responseAwaiter = _messageDispatcher.AddAwaiter(requestMessage.Id); + try { await _lowLevelClient.SendAsync(requestMessage, cancellationToken).ConfigureAwait(false); - var responseMessage = await responseAwaiter.WaitOneAsync(_connectOptions.CommunicationTimeout).ConfigureAwait(false); + var responseMessage = await responseAwaiter.WaitOneAsync(_connectOptions.CommunicationTimeout) + .ConfigureAwait(false); if (responseMessage.Code.Equals(CoapMessageCodes.Empty)) { @@ -148,19 +167,6 @@ internal async Task RequestAsync(CoapMessage requestMessage, Cancel } } - public void Dispose() - { - try - { - _cancellationToken?.Cancel(false); - } - finally - { - _cancellationToken?.Dispose(); - _lowLevelClient?.Dispose(); - } - } - async Task ReceiveMessages(CancellationToken cancellationToken) { while (!cancellationToken.IsCancellationRequested) @@ -192,10 +198,18 @@ async Task ReceiveMessages(CancellationToken cancellationToken) } catch (Exception exception) { - _logger.Error(nameof(CoapClient), exception, "Error while receiving messages."); + if (!_cancellationToken.IsCancellationRequested) + { + _logger.Error(nameof(CoapClient), exception, "Error while receiving messages."); + } + else + { + _logger.Information(nameof(CoapClient), "Stopped receiving messages due to cancellation."); + } + + _messageDispatcher.Dispatch(exception); } } } } -} - +} \ No newline at end of file diff --git a/Source/CoAPnet/Client/CoapClientBlockTransferReceiver.cs b/Source/CoAPnet/Client/CoapClientBlockTransferReceiver.cs index 37ee605..305642e 100644 --- a/Source/CoAPnet/Client/CoapClientBlockTransferReceiver.cs +++ b/Source/CoAPnet/Client/CoapClientBlockTransferReceiver.cs @@ -13,9 +13,6 @@ namespace CoAPnet.Client { public sealed class CoapClientBlockTransferReceiver { - readonly CoapBlockTransferOptionValueEncoder _blockValueEncoder = new CoapBlockTransferOptionValueEncoder(); - readonly CoapBlockTransferOptionValueDecoder _blockValueDecoder = new CoapBlockTransferOptionValueDecoder(); - readonly CoapMessage _requestMessage; readonly CoapMessage _firstResponseMessage; readonly CoapClient _client; @@ -42,7 +39,7 @@ public static bool IsBlockTransfer(CoapMessage responseMessage) public async Task> ReceiveFullPayload(CancellationToken cancellationToken) { var receivedBlock2Option = _firstResponseMessage.Options.First(o => o.Number == CoapMessageOptionNumber.Block2); - var receivedBlock2OptionValue = _blockValueDecoder.Decode(((CoapMessageOptionUintValue)receivedBlock2Option.Value).Value); + var receivedBlock2OptionValue = CoapBlockTransferOptionValueDecoder.Decode(((CoapMessageOptionUintValue)receivedBlock2Option.Value).Value); _logger.Trace(nameof(CoapClientBlockTransferReceiver), "Received Block2 {0}.", FormatBlock2OptionValue(receivedBlock2OptionValue)); var requestMessage = new CoapMessage @@ -69,11 +66,11 @@ public async Task> ReceiveFullPayload(CancellationToken cance receivedBlock2OptionValue.Number++; // TODO: Avoid setting value. Create new instead. - requestBlock2Option.Value = new CoapMessageOptionUintValue(_blockValueEncoder.Encode(receivedBlock2OptionValue)); + requestBlock2Option.Value = new CoapMessageOptionUintValue(CoapBlockTransferOptionValueEncoder.Encode(receivedBlock2OptionValue)); var response = await _client.RequestAsync(requestMessage, cancellationToken).ConfigureAwait(false); receivedBlock2Option = response.Options.First(o => o.Number == CoapMessageOptionNumber.Block2); - receivedBlock2OptionValue = _blockValueDecoder.Decode(((CoapMessageOptionUintValue)receivedBlock2Option.Value).Value); + receivedBlock2OptionValue = CoapBlockTransferOptionValueDecoder.Decode(((CoapMessageOptionUintValue)receivedBlock2Option.Value).Value); _logger.Trace(nameof(CoapClientBlockTransferReceiver), "Received Block2 {0}.", FormatBlock2OptionValue(receivedBlock2OptionValue)); diff --git a/Source/CoAPnet/Client/CoapMessageToken.cs b/Source/CoAPnet/Client/CoapMessageToken.cs index 238c875..8545e93 100644 --- a/Source/CoAPnet/Client/CoapMessageToken.cs +++ b/Source/CoAPnet/Client/CoapMessageToken.cs @@ -2,7 +2,7 @@ namespace CoAPnet.Client { - public class CoapMessageToken + public sealed class CoapMessageToken { public CoapMessageToken(byte[] value) { diff --git a/Source/CoAPnet/Client/CoapMessageTokenProvider.cs b/Source/CoAPnet/Client/CoapMessageTokenProvider.cs index 5aa4fdd..717f644 100644 --- a/Source/CoAPnet/Client/CoapMessageTokenProvider.cs +++ b/Source/CoAPnet/Client/CoapMessageTokenProvider.cs @@ -2,7 +2,7 @@ namespace CoAPnet.Client { - public class CoapMessageTokenProvider + public sealed class CoapMessageTokenProvider { ulong _value; diff --git a/Source/CoAPnet/Client/CoapObserveRequest.cs b/Source/CoAPnet/Client/CoapObserveRequest.cs index c56a307..bab6668 100644 --- a/Source/CoAPnet/Client/CoapObserveRequest.cs +++ b/Source/CoAPnet/Client/CoapObserveRequest.cs @@ -1,6 +1,6 @@ namespace CoAPnet.Client { - public class CoapObserveRequest + public sealed class CoapObserveRequest { public CoapRequestOptions Options { get; set; } = new CoapRequestOptions(); } diff --git a/Source/CoAPnet/Client/CoapObserveResponse.cs b/Source/CoAPnet/Client/CoapObserveResponse.cs index bb55c70..ade99e7 100644 --- a/Source/CoAPnet/Client/CoapObserveResponse.cs +++ b/Source/CoAPnet/Client/CoapObserveResponse.cs @@ -3,7 +3,7 @@ namespace CoAPnet.Client { - public class CoapObserveResponse + public sealed class CoapObserveResponse { readonly ICoapClient _client; diff --git a/Source/CoAPnet/Client/CoapRequest.cs b/Source/CoAPnet/Client/CoapRequest.cs index dc85350..6c44154 100644 --- a/Source/CoAPnet/Client/CoapRequest.cs +++ b/Source/CoAPnet/Client/CoapRequest.cs @@ -2,7 +2,7 @@ namespace CoAPnet.Client { - public class CoapRequest + public sealed class CoapRequest { public CoapRequestMethod Method { get; set; } = CoapRequestMethod.Get; diff --git a/Source/CoAPnet/Client/CoapRequestOptions.cs b/Source/CoAPnet/Client/CoapRequestOptions.cs index cb1fb8f..192e057 100644 --- a/Source/CoAPnet/Client/CoapRequestOptions.cs +++ b/Source/CoAPnet/Client/CoapRequestOptions.cs @@ -2,7 +2,7 @@ namespace CoAPnet.Client { - public class CoapRequestOptions + public sealed class CoapRequestOptions { /// /// This is only required when accessing virtual servers. diff --git a/Source/CoAPnet/Client/CoapResponse.cs b/Source/CoAPnet/Client/CoapResponse.cs index 0c1b7d1..a221d96 100644 --- a/Source/CoAPnet/Client/CoapResponse.cs +++ b/Source/CoAPnet/Client/CoapResponse.cs @@ -1,6 +1,6 @@ namespace CoAPnet.Client { - public class CoapResponse + public sealed class CoapResponse { public CoapResponseStatusCode StatusCode { diff --git a/Source/CoAPnet/Client/CoapResponseOptions.cs b/Source/CoAPnet/Client/CoapResponseOptions.cs index 6d162e0..a6380c1 100644 --- a/Source/CoAPnet/Client/CoapResponseOptions.cs +++ b/Source/CoAPnet/Client/CoapResponseOptions.cs @@ -2,7 +2,7 @@ namespace CoAPnet.Client { - public class CoapResponseOptions + public sealed class CoapResponseOptions { public CoapMessageContentFormat? ContentFormat { diff --git a/Source/CoAPnet/Client/HandleResponseContext.cs b/Source/CoAPnet/Client/HandleResponseContext.cs index 0ce3636..e32ec56 100644 --- a/Source/CoAPnet/Client/HandleResponseContext.cs +++ b/Source/CoAPnet/Client/HandleResponseContext.cs @@ -1,6 +1,6 @@ namespace CoAPnet.Client { - public class HandleResponseContext + public sealed class HandleResponseContext { public uint SequenceNumber { get; set; } diff --git a/Source/CoAPnet/CoAPnet.csproj b/Source/CoAPnet/CoAPnet.csproj index f65a968..4ffe558 100644 --- a/Source/CoAPnet/CoAPnet.csproj +++ b/Source/CoAPnet/CoAPnet.csproj @@ -37,6 +37,9 @@ true true LICENSE + 7.3 + true + CS1591 diff --git a/Source/CoAPnet/LowLevelClient/LowLevelCoapClient.cs b/Source/CoAPnet/LowLevelClient/LowLevelCoapClient.cs index 539bb22..a1ab37c 100644 --- a/Source/CoAPnet/LowLevelClient/LowLevelCoapClient.cs +++ b/Source/CoAPnet/LowLevelClient/LowLevelCoapClient.cs @@ -1,25 +1,27 @@ -using CoAPnet.Client; +using System; +using System.Diagnostics; +using System.Net; +using System.Threading; +using System.Threading.Tasks; +using CoAPnet.Client; using CoAPnet.Exceptions; using CoAPnet.Logging; using CoAPnet.Protocol; using CoAPnet.Protocol.Encoding; using CoAPnet.Transport; -using System; -using System.Net; -using System.Threading; -using System.Threading.Tasks; namespace CoAPnet.LowLevelClient { public sealed class LowLevelCoapClient : ILowLevelCoapClient { - readonly CoapMessageEncoder _messageEncoder = new CoapMessageEncoder(); - readonly CoapMessageDecoder _messageDecoder; readonly CoapNetLogger _logger; + readonly CoapMessageDecoder _messageDecoder; + readonly CoapMessageEncoder _messageEncoder = new CoapMessageEncoder(); // The size of the receive buffer is large enough so that a whole // UDP datagram will fit into the buffer at once. readonly ArraySegment _receiveBuffer = new ArraySegment(new byte[65535]); + readonly SemaphoreSlim _syncRoot = new SemaphoreSlim(1, 1); CoapClientConnectOptions _connectOptions; CoapTransportLayerAdapter _transportLayerAdapter; @@ -31,6 +33,45 @@ public LowLevelCoapClient(CoapNetLogger logger) _messageDecoder = new CoapMessageDecoder(logger); } + public async Task SendAsync(CoapMessage message, CancellationToken cancellationToken) + { + if (message is null) + { + throw new ArgumentNullException(nameof(message)); + } + + var requestMessageBuffer = _messageEncoder.Encode(message); + + await _syncRoot.WaitAsync(cancellationToken).ConfigureAwait(false); + try + { + await _transportLayerAdapter.SendAsync(requestMessageBuffer, cancellationToken).ConfigureAwait(false); + } + finally + { + _syncRoot.Release(); + } + } + + public async Task ReceiveAsync(CancellationToken cancellationToken) + { + var datagramLength = await _transportLayerAdapter.ReceiveAsync(_receiveBuffer, cancellationToken) + .ConfigureAwait(false); + + if (datagramLength == 0) + { + return null; + } + + Debug.Assert(_receiveBuffer.Array != null); + return _messageDecoder.Decode(new ArraySegment(_receiveBuffer.Array, 0, datagramLength)); + } + + public void Dispose() + { + _transportLayerAdapter?.Dispose(); + } + public async Task ConnectAsync(CoapClientConnectOptions options, CancellationToken cancellationToken) { _connectOptions = options ?? throw new ArgumentNullException(nameof(options)); @@ -43,16 +84,17 @@ public async Task ConnectAsync(CoapClientConnectOptions options, CancellationTok } cancellationToken.ThrowIfCancellationRequested(); - + _transportLayerAdapter = new CoapTransportLayerAdapter(transportLayer, _logger); try { var transportLayerConnectOptions = new CoapTransportLayerConnectOptions { - EndPoint = await ResolveIPEndPoint(options).ConfigureAwait(false) + EndPoint = await ResolveIpEndPoint(options).ConfigureAwait(false) }; - await _transportLayerAdapter.ConnectAsync(transportLayerConnectOptions, cancellationToken).ConfigureAwait(false); + await _transportLayerAdapter.ConnectAsync(transportLayerConnectOptions, cancellationToken) + .ConfigureAwait(false); } catch (Exception) { @@ -61,62 +103,32 @@ public async Task ConnectAsync(CoapClientConnectOptions options, CancellationTok } } - public Task SendAsync(CoapMessage message, CancellationToken cancellationToken) - { - if (message is null) - { - throw new ArgumentNullException(nameof(message)); - } - - var requestMessageBuffer = _messageEncoder.Encode(message); - return _transportLayerAdapter.SendAsync(requestMessageBuffer, cancellationToken); - } - - public async Task ReceiveAsync(CancellationToken cancellationToken) - { - var datagramLength = await _transportLayerAdapter.ReceiveAsync(_receiveBuffer, cancellationToken).ConfigureAwait(false); - if (datagramLength == 0) - { - return null; - } - - return _messageDecoder.Decode(new ArraySegment(_receiveBuffer.Array, 0, datagramLength)); - } - - public void Dispose() - { - _transportLayerAdapter?.Dispose(); - } - - async Task ResolveIPEndPoint(CoapClientConnectOptions connectOptions) + async Task ResolveIpEndPoint(CoapClientConnectOptions connectOptions) { if (IPAddress.TryParse(connectOptions.Host, out var ipAddress)) { return new IPEndPoint(ipAddress, connectOptions.Port); } - else - { #if NETSTANDARD1_3 await Task.FromResult(0); throw new NotSupportedException("Resolving DNS end points is not supported for NETSTANDARD1_3. Please pass IP address instead."); #else - try - { - 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); - } - catch (Exception exception) + try + { + var hostIpAddresses = await Dns.GetHostAddressesAsync(connectOptions.Host).ConfigureAwait(false); + if (hostIpAddresses.Length == 0) { - throw new CoapCommunicationException("Error while resolving DNS name.", exception); + throw new CoapCommunicationException("Failed to resolve DNS end point", null); } -#endif + + // 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 } } -} +} \ No newline at end of file diff --git a/Source/CoAPnet/Protocol/BlockTransfer/CoapBlockTransferOptionValue.cs b/Source/CoAPnet/Protocol/BlockTransfer/CoapBlockTransferOptionValue.cs index 8b3dff4..94636c2 100644 --- a/Source/CoAPnet/Protocol/BlockTransfer/CoapBlockTransferOptionValue.cs +++ b/Source/CoAPnet/Protocol/BlockTransfer/CoapBlockTransferOptionValue.cs @@ -1,6 +1,6 @@ namespace CoAPnet.Protocol.BlockTransfer { - public class CoapBlockTransferOptionValue + public sealed class CoapBlockTransferOptionValue { public ushort Number { get; set; } diff --git a/Source/CoAPnet/Protocol/BlockTransfer/CoapBlockTransferOptionValueDecoder.cs b/Source/CoAPnet/Protocol/BlockTransfer/CoapBlockTransferOptionValueDecoder.cs index 8a8baf7..c5d1f52 100644 --- a/Source/CoAPnet/Protocol/BlockTransfer/CoapBlockTransferOptionValueDecoder.cs +++ b/Source/CoAPnet/Protocol/BlockTransfer/CoapBlockTransferOptionValueDecoder.cs @@ -3,9 +3,9 @@ namespace CoAPnet.Protocol.BlockTransfer { - public class CoapBlockTransferOptionValueDecoder + public static class CoapBlockTransferOptionValueDecoder { - public CoapBlockTransferOptionValue Decode(uint value) + public static CoapBlockTransferOptionValue Decode(uint value) { var mask = 0x7; var size = (ushort)(value & mask); diff --git a/Source/CoAPnet/Protocol/BlockTransfer/CoapBlockTransferOptionValueEncoder.cs b/Source/CoAPnet/Protocol/BlockTransfer/CoapBlockTransferOptionValueEncoder.cs index afec2eb..31c3263 100644 --- a/Source/CoAPnet/Protocol/BlockTransfer/CoapBlockTransferOptionValueEncoder.cs +++ b/Source/CoAPnet/Protocol/BlockTransfer/CoapBlockTransferOptionValueEncoder.cs @@ -3,9 +3,9 @@ namespace CoAPnet.Protocol.BlockTransfer { - public class CoapBlockTransferOptionValueEncoder + public static class CoapBlockTransferOptionValueEncoder { - public uint Encode(CoapBlockTransferOptionValue value) + public static uint Encode(CoapBlockTransferOptionValue value) { if (value is null) { diff --git a/Source/CoAPnet/Protocol/Encoding/CoapMessageDecoder.cs b/Source/CoAPnet/Protocol/Encoding/CoapMessageDecoder.cs index 9b5f5f5..56de8ca 100644 --- a/Source/CoAPnet/Protocol/Encoding/CoapMessageDecoder.cs +++ b/Source/CoAPnet/Protocol/Encoding/CoapMessageDecoder.cs @@ -1,8 +1,8 @@ -using CoAPnet.Exceptions; +using System; +using System.Collections.Generic; +using CoAPnet.Exceptions; using CoAPnet.Logging; using CoAPnet.Protocol.Options; -using System; -using System.Collections.Generic; namespace CoAPnet.Protocol.Encoding { @@ -15,8 +15,7 @@ public CoapMessageDecoder(CoapNetLogger logger) { _logger = logger ?? throw new ArgumentNullException(nameof(logger)); } - - // TODO: Consider creating "CoapMessageDecodeResult" which has Message (null or set) and "DecodeResult" as INTERFACE with all possible errors (VersionInvalidDecodeResult) etc. + public CoapMessage Decode(ArraySegment buffer) { using (var reader = new CoapMessageReader(buffer)) @@ -59,7 +58,7 @@ public CoapMessage Decode(ArraySegment buffer) Id = (ushort)id, Token = token, Options = options, - Payload = payload + Payload = new ArraySegment(payload) }; return message; @@ -158,7 +157,8 @@ CoapMessageOption CreateOption(CoapMessageOptionNumber number, byte[] value) return _optionFactory.CreateObserve(DecodeUintOptionValue(value)); } - _logger.Warning(nameof(CoapMessageDecoder), "Invalid message: CoAP option number {0} not supported.", number); + _logger.Warning(nameof(CoapMessageDecoder), "Invalid message: CoAP option number {0} not supported.", + number); // We do not throw because new RFCs might use new options. We wrap unknown ones // into a opaque value. @@ -180,7 +180,7 @@ List DecodeOptions(CoapMessageReader reader) var delta = reader.ReadBits(4); var length = reader.ReadBits(4); - if ((byte)(delta << 4 | length) == 0xFF) + if ((byte)((delta << 4) | length) == 0xFF) { // Payload marker. break; @@ -235,17 +235,17 @@ static uint DecodeUintOptionValue(byte[] value) if (value.Length == 2) { - return (uint)(value[0] << 8 | value[1]); + return (uint)((value[0] << 8) | value[1]); } if (value.Length == 3) { - return (uint)(value[0] << 16 | value[1] << 8 | value[2]); + return (uint)((value[0] << 16) | (value[1] << 8) | value[2]); } if (value.Length == 4) { - return (uint)(value[0] << 24 | value[1] << 16 | value[2] << 8 | value[3]); + return (uint)((value[0] << 24) | (value[1] << 16) | (value[2] << 8) | value[3]); } throw new CoapProtocolViolationException("The buffer for the uint option is too long."); diff --git a/Source/CoAPnet/Protocol/Encoding/CoapMessageEncoder.cs b/Source/CoAPnet/Protocol/Encoding/CoapMessageEncoder.cs index 47f82a7..2f3ae22 100644 --- a/Source/CoAPnet/Protocol/Encoding/CoapMessageEncoder.cs +++ b/Source/CoAPnet/Protocol/Encoding/CoapMessageEncoder.cs @@ -8,7 +8,7 @@ namespace CoAPnet.Protocol.Encoding { public sealed class CoapMessageEncoder { - readonly byte[] _emptyArray = new byte[0]; + static readonly byte[] EmptyArray = new byte[0]; public ArraySegment Encode(CoapMessage message) { @@ -48,7 +48,7 @@ public ArraySegment Encode(CoapMessage message) } } - void EncodeOptions(IEnumerable options, CoapMessageWriter writer) + static void EncodeOptions(IEnumerable options, CoapMessageWriter writer) { if (options == null) { @@ -66,7 +66,7 @@ void EncodeOptions(IEnumerable options, CoapMessageWriter wri if (option.Value is CoapMessageOptionEmptyValue) { - value = _emptyArray; + value = EmptyArray; } else if (option.Value is CoapMessageOptionUintValue uintValue) { diff --git a/Source/CoAPnet/Protocol/Encoding/CoapMessageReader.cs b/Source/CoAPnet/Protocol/Encoding/CoapMessageReader.cs index 89fe2aa..30800ef 100644 --- a/Source/CoAPnet/Protocol/Encoding/CoapMessageReader.cs +++ b/Source/CoAPnet/Protocol/Encoding/CoapMessageReader.cs @@ -1,24 +1,32 @@ using System; -using System.IO; +using System.Diagnostics; using System.Runtime.CompilerServices; namespace CoAPnet.Protocol.Encoding { public sealed class CoapMessageReader : IDisposable { - readonly MemoryStream _memoryStream; - readonly ArraySegment _buffer; + readonly byte[] _buffer; + readonly int _length; int _bitOffset = -1; byte _byteCache; + int _position; public CoapMessageReader(ArraySegment buffer) { - _memoryStream = new MemoryStream(buffer.Array, 0, buffer.Count, false); - _buffer = buffer; + Debug.Assert(buffer.Array != null); + + _buffer = buffer.Array; + _position = buffer.Offset; + _length = buffer.Count; } - public bool EndOfStream => _memoryStream.Position == _memoryStream.Length; + public bool EndOfStream => _position == _length; + + public void Dispose() + { + } public int ReadBits(int count) { @@ -47,31 +55,31 @@ public int ReadBits(int count) public byte ReadByte() { - return (byte)_memoryStream.ReadByte(); + var @byte = _buffer[_position]; + _position++; + + return @byte; } public byte[] ReadBytes(int count) { var buffer = new byte[count]; - _memoryStream.Read(buffer, 0, buffer.Length); - + Array.Copy(_buffer, _position, buffer, 0, count); + _position += count; return buffer; } - public ArraySegment ReadToEnd() - { - return new ArraySegment(_buffer.Array, (int)_memoryStream.Position, (int)(_memoryStream.Length - _memoryStream.Position)); - } - - public void Dispose() + public byte[] ReadToEnd() { - _memoryStream.Dispose(); + // We have to copy the payload because the internal buffer is used for other calls! + return ReadBytes(_length - _position); } [MethodImpl(MethodImplOptions.AggressiveInlining)] void FillByteCache() { - _byteCache = (byte)_memoryStream.ReadByte(); + _byteCache = _buffer[_position]; + _position++; _bitOffset = 7; } } diff --git a/Source/CoAPnet/Protocol/Observe/CoapObserveOptionValue.cs b/Source/CoAPnet/Protocol/Observe/CoapObserveOptionValue.cs index 4734be9..5e58b0f 100644 --- a/Source/CoAPnet/Protocol/Observe/CoapObserveOptionValue.cs +++ b/Source/CoAPnet/Protocol/Observe/CoapObserveOptionValue.cs @@ -1,6 +1,6 @@ namespace CoAPnet.Protocol.Observe { - public class CoapObserveOptionValue + public static class CoapObserveOptionValue { public const uint Register = 0; diff --git a/Source/CoAPnet/Protocol/Options/CoapMessageOptionEmptyValue.cs b/Source/CoAPnet/Protocol/Options/CoapMessageOptionEmptyValue.cs index c54c0a4..10a8b3a 100644 --- a/Source/CoAPnet/Protocol/Options/CoapMessageOptionEmptyValue.cs +++ b/Source/CoAPnet/Protocol/Options/CoapMessageOptionEmptyValue.cs @@ -1,6 +1,6 @@ namespace CoAPnet.Protocol.Options { - public class CoapMessageOptionEmptyValue : CoapMessageOptionValue + public sealed class CoapMessageOptionEmptyValue : CoapMessageOptionValue { public override bool Equals(object obj) { diff --git a/Source/CoAPnet/Protocol/Options/CoapMessageOptionOpaqueValue.cs b/Source/CoAPnet/Protocol/Options/CoapMessageOptionOpaqueValue.cs index 6e7e4e3..1351352 100644 --- a/Source/CoAPnet/Protocol/Options/CoapMessageOptionOpaqueValue.cs +++ b/Source/CoAPnet/Protocol/Options/CoapMessageOptionOpaqueValue.cs @@ -2,7 +2,7 @@ namespace CoAPnet.Protocol.Options { - public class CoapMessageOptionOpaqueValue : CoapMessageOptionValue + public sealed class CoapMessageOptionOpaqueValue : CoapMessageOptionValue { public CoapMessageOptionOpaqueValue(byte[] value) { diff --git a/Source/CoAPnet/Protocol/Options/CoapMessageOptionStringValue.cs b/Source/CoAPnet/Protocol/Options/CoapMessageOptionStringValue.cs index 6e170e6..dbcf451 100644 --- a/Source/CoAPnet/Protocol/Options/CoapMessageOptionStringValue.cs +++ b/Source/CoAPnet/Protocol/Options/CoapMessageOptionStringValue.cs @@ -1,6 +1,6 @@ namespace CoAPnet.Protocol.Options { - public class CoapMessageOptionStringValue : CoapMessageOptionValue + public sealed class CoapMessageOptionStringValue : CoapMessageOptionValue { public CoapMessageOptionStringValue(string value) { diff --git a/Source/CoAPnet/Protocol/Options/CoapMessageOptionUintValue.cs b/Source/CoAPnet/Protocol/Options/CoapMessageOptionUintValue.cs index f8f84e9..5903a5f 100644 --- a/Source/CoAPnet/Protocol/Options/CoapMessageOptionUintValue.cs +++ b/Source/CoAPnet/Protocol/Options/CoapMessageOptionUintValue.cs @@ -1,6 +1,6 @@ namespace CoAPnet.Protocol.Options { - public class CoapMessageOptionUintValue : CoapMessageOptionValue + public sealed class CoapMessageOptionUintValue : CoapMessageOptionValue { public CoapMessageOptionUintValue(uint value) { diff --git a/Source/CoAPnet/Transport/UdpCoapTransportLayer.cs b/Source/CoAPnet/Transport/UdpCoapTransportLayer.cs index 4442c8e..773fc23 100644 --- a/Source/CoAPnet/Transport/UdpCoapTransportLayer.cs +++ b/Source/CoAPnet/Transport/UdpCoapTransportLayer.cs @@ -7,8 +7,8 @@ namespace CoAPnet.Transport { public sealed class UdpCoapTransportLayer : ICoapTransportLayer { - UdpClient _udpClient; CoapTransportLayerConnectOptions _connectOptions; + UdpClient _udpClient; public Task ConnectAsync(CoapTransportLayerConnectOptions options, CancellationToken cancellationToken) { @@ -23,7 +23,12 @@ public Task ConnectAsync(CoapTransportLayerConnectOptions options, CancellationT public async Task ReceiveAsync(ArraySegment buffer, CancellationToken cancellationToken) { +#if NET6_0_OR_GREATER + var receiveResult = await _udpClient.ReceiveAsync(cancellationToken).ConfigureAwait(false); +#else var receiveResult = await _udpClient.ReceiveAsync().ConfigureAwait(false); +#endif + Array.Copy(receiveResult.Buffer, 0, buffer.Array, buffer.Offset, receiveResult.Buffer.Length); return receiveResult.Buffer.Length;