From 64bbef4bdd45ad77df039f0c02d63bf4c10393a7 Mon Sep 17 00:00:00 2001 From: Philip Wille Date: Fri, 16 Oct 2020 13:22:04 +0200 Subject: [PATCH 01/32] Added Client and Server example --- CoAP.Example/CoAP.Client/CoAP.Client.csproj | 10 +- CoAP.Example/CoAP.Client/app.config | 3 + CoAP.Example/CoAP.Server/CoAP.Server.csproj | 10 +- CoAP.Example/CoAP.Server/app.config | 3 + WorldDirect.CoAP.Example.Client/Program.cs | 202 +++++++++++++++++- .../WorldDirect.CoAP.Example.Client.csproj | 4 + WorldDirect.CoAP.Example.Server/Program.cs | 29 ++- .../WorldDirect.CoAP.Example.Server.csproj | 4 + 8 files changed, 257 insertions(+), 8 deletions(-) create mode 100644 CoAP.Example/CoAP.Client/app.config create mode 100644 CoAP.Example/CoAP.Server/app.config diff --git a/CoAP.Example/CoAP.Client/CoAP.Client.csproj b/CoAP.Example/CoAP.Client/CoAP.Client.csproj index d9181d3..edc51df 100644 --- a/CoAP.Example/CoAP.Client/CoAP.Client.csproj +++ b/CoAP.Example/CoAP.Client/CoAP.Client.csproj @@ -1,5 +1,5 @@  - + Debug AnyCPU @@ -10,8 +10,9 @@ Properties CoAP.Examples CoAPClient - v2.0 + v4.6.1 512 + true @@ -21,6 +22,7 @@ DEBUG;TRACE prompt 4 + false pdbonly @@ -29,6 +31,7 @@ TRACE prompt 4 + false @@ -43,6 +46,9 @@ CoAP.NET + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WorldDirect.CoAP.Example.Server/Program.cs b/WorldDirect.CoAP.Example.Server/Program.cs index c450338..466b183 100644 --- a/WorldDirect.CoAP.Example.Server/Program.cs +++ b/WorldDirect.CoAP.Example.Server/Program.cs @@ -3,6 +3,7 @@ namespace WorldDirect.CoAP.Example.Server { + using System.IO; using CoAP.Server.Resources; class Program @@ -11,11 +12,74 @@ static void Main(string[] args) { var server = new CoapServer(); server.Add(new HelloWorldResource("HelloWorld")); + server.Add(new ImageResource("ImageResource")); server.Start(); Console.ReadLine(); } } + public class ImageResource : Resource + { + private Int32[] _supported = new Int32[] { + MediaType.ImageJpeg, + MediaType.ImagePng + }; + + public ImageResource(String name) + : base(name) + { + Attributes.Title = "GET an image with different content-types"; + Attributes.AddResourceType("Image"); + + foreach (Int32 item in _supported) + { + Attributes.AddContentType(item); + } + + Attributes.MaximumSizeEstimate = 18029; + } + + protected override void DoGet(CoapExchange exchange) + { + String file = "data\\image\\"; + Int32 ct = MediaType.ImagePng; + Request request = exchange.Request; + + if ((ct = MediaType.NegotiationContent(ct, _supported, request.GetOptions(OptionType.Accept))) + == MediaType.Undefined) + { + exchange.Respond(StatusCode.NotAcceptable); + } + else + { + file += "image." + MediaType.ToFileExtension(ct); + if (File.Exists(file)) + { + Byte[] data = null; + + try + { + data = File.ReadAllBytes(file); + } + catch (Exception ex) + { + exchange.Respond(StatusCode.InternalServerError, "IO error"); + Console.WriteLine(ex.Message); + } + + Response response = new Response(StatusCode.Content); + response.Payload = data; + response.ContentType = ct; + exchange.Respond(response); + } + else + { + exchange.Respond(StatusCode.InternalServerError, "Image file not found"); + } + } + } + } + public class HelloWorldResource : Resource { /// diff --git a/WorldDirect.CoAP/Channel/UDPChannel.cs b/WorldDirect.CoAP/Channel/UDPChannel.cs index a16e76a..ac14d3a 100644 --- a/WorldDirect.CoAP/Channel/UDPChannel.cs +++ b/WorldDirect.CoAP/Channel/UDPChannel.cs @@ -15,7 +15,7 @@ namespace WorldDirect.CoAP.Channel using System.Collections.Concurrent; using System.Net; using System.Net.Sockets; - using Log; + using NLog; /// /// Channel via UDP protocol. @@ -23,7 +23,7 @@ namespace WorldDirect.CoAP.Channel public partial class UDPChannel : IChannel { - static readonly ILogger log = LogManager.GetLogger(typeof(UDPChannel)); + static readonly Logger log = LogManager.GetCurrentClassLogger(); /// /// Default size of buffer for receiving packet. diff --git a/WorldDirect.CoAP/Class1.cs b/WorldDirect.CoAP/Class1.cs deleted file mode 100644 index e1d6284..0000000 --- a/WorldDirect.CoAP/Class1.cs +++ /dev/null @@ -1,8 +0,0 @@ -using System; - -namespace WorldDirect.CoAP -{ - public class Class1 - { - } -} diff --git a/WorldDirect.CoAP/CoapClient.cs b/WorldDirect.CoAP/CoapClient.cs index 04ab43d..a60783b 100644 --- a/WorldDirect.CoAP/CoapClient.cs +++ b/WorldDirect.CoAP/CoapClient.cs @@ -4,8 +4,9 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; - using Log; using Net; + using NLog; + using NLog.Config; /// /// Provides convenient methods for accessing CoAP resources. @@ -15,7 +16,7 @@ public class CoapClient #region Locals private static readonly IEnumerable EmptyLinks = new WebLink[0]; - private static ILogger log = LogManager.GetLogger(typeof(CoapClient)); + static readonly Logger log = LogManager.GetCurrentClassLogger(); private ICoapConfig _config; private IEndPoint _endpoint; @@ -71,6 +72,7 @@ public CoapClient(Uri uri, ICoapConfig config) { _uri = uri; _config = config ?? CoapConfig.Default; + LogManager.Configuration = new XmlLoggingConfiguration("../Config/NLog.config"); } /// diff --git a/WorldDirect.CoAP/Codec/DatagramWriter.cs b/WorldDirect.CoAP/Codec/DatagramWriter.cs index 971469d..7b0ae85 100644 --- a/WorldDirect.CoAP/Codec/DatagramWriter.cs +++ b/WorldDirect.CoAP/Codec/DatagramWriter.cs @@ -13,14 +13,14 @@ namespace WorldDirect.CoAP.Codec { using System; using System.IO; - using Log; + using NLog; /// /// This class describes the functionality to write raw network-ordered datagrams on bit-level. /// public class DatagramWriter { - private static ILogger log = LogManager.GetLogger(typeof(DatagramWriter)); + static readonly Logger log = LogManager.GetCurrentClassLogger(); private MemoryStream _stream; private Byte _currentByte; diff --git a/WorldDirect.CoAP/Deduplication/DeduplicatorFactory.cs b/WorldDirect.CoAP/Deduplication/DeduplicatorFactory.cs index daaa932..613a41f 100644 --- a/WorldDirect.CoAP/Deduplication/DeduplicatorFactory.cs +++ b/WorldDirect.CoAP/Deduplication/DeduplicatorFactory.cs @@ -12,11 +12,11 @@ namespace WorldDirect.CoAP.Deduplication { using System; - using Log; + using NLog; static class DeduplicatorFactory { - static readonly ILogger log = LogManager.GetLogger(typeof(DeduplicatorFactory)); + static readonly Logger log = LogManager.GetCurrentClassLogger(); public const String MarkAndSweepDeduplicator = "MarkAndSweep"; public const String CropRotationDeduplicator = "CropRotation"; public const String NoopDeduplicator = "Noop"; diff --git a/WorldDirect.CoAP/Deduplication/SweepDeduplicator.cs b/WorldDirect.CoAP/Deduplication/SweepDeduplicator.cs index 6ad550c..06270dd 100644 --- a/WorldDirect.CoAP/Deduplication/SweepDeduplicator.cs +++ b/WorldDirect.CoAP/Deduplication/SweepDeduplicator.cs @@ -15,12 +15,13 @@ namespace WorldDirect.CoAP.Deduplication using System.Collections.Concurrent; using System.Collections.Generic; using System.Threading; - using Log; + using Microsoft.Extensions.Logging; using Net; + using NLog.Extensions.Logging; class SweepDeduplicator : IDeduplicator { - static readonly ILogger log = LogManager.GetLogger(typeof(SweepDeduplicator)); + private static readonly NLog.Logger log = NLog.LogManager.GetCurrentClassLogger(); private ConcurrentDictionary _incommingMessages = new ConcurrentDictionary(); diff --git a/WorldDirect.CoAP/EndPoint/Resources/Resource.cs b/WorldDirect.CoAP/EndPoint/Resources/Resource.cs index e444097..d59f534 100644 --- a/WorldDirect.CoAP/EndPoint/Resources/Resource.cs +++ b/WorldDirect.CoAP/EndPoint/Resources/Resource.cs @@ -14,14 +14,14 @@ namespace WorldDirect.CoAP.EndPoint.Resources using System; using System.Collections.Generic; using System.Text; - using Log; + using NLog; /// /// This class describes the functionality of a CoAP resource. /// public abstract class Resource : IComparable { - private static ILogger log = LogManager.GetLogger(typeof(Resource)); + private static Logger log = NLog.LogManager.GetCurrentClassLogger(); private Int32 _totalSubResourceCount; private String _resourceIdentifier; diff --git a/WorldDirect.CoAP/LinkAttribute.cs b/WorldDirect.CoAP/LinkAttribute.cs index 926db60..5bd2bfa 100644 --- a/WorldDirect.CoAP/LinkAttribute.cs +++ b/WorldDirect.CoAP/LinkAttribute.cs @@ -13,14 +13,14 @@ namespace WorldDirect.CoAP { using System; using System.Text; - using Log; + using NLog; /// /// Class for linkformat attributes. /// public class LinkAttribute : IComparable { - private static readonly ILogger log = LogManager.GetLogger(typeof(LinkAttribute)); + private static readonly Logger log = NLog.LogManager.GetCurrentClassLogger(); private String _name; private Object _value; diff --git a/WorldDirect.CoAP/LinkFormat.cs b/WorldDirect.CoAP/LinkFormat.cs index 60a5ac6..96e9896 100644 --- a/WorldDirect.CoAP/LinkFormat.cs +++ b/WorldDirect.CoAP/LinkFormat.cs @@ -16,7 +16,7 @@ namespace WorldDirect.CoAP using System.Text; using System.Text.RegularExpressions; using EndPoint.Resources; - using Log; + using NLog; using Server.Resources; using Util; using Resource = EndPoint.Resources.Resource; @@ -75,7 +75,7 @@ public static class LinkFormat static readonly Regex EqualRegex = new Regex("="); static readonly Regex BlankRegex = new Regex("\\s"); - private static ILogger log = LogManager.GetLogger(typeof(LinkFormat)); + private static Logger log = LogManager.GetCurrentClassLogger(); public static String Serialize(IResource root) { diff --git a/WorldDirect.CoAP/Net/CoAPEndPoint.cs b/WorldDirect.CoAP/Net/CoAPEndPoint.cs index 410b521..ecf7e18 100644 --- a/WorldDirect.CoAP/Net/CoAPEndPoint.cs +++ b/WorldDirect.CoAP/Net/CoAPEndPoint.cs @@ -14,7 +14,7 @@ namespace WorldDirect.CoAP.Net using System; using Channel; using Codec; - using Log; + using NLog; using Stack; using Threading; @@ -23,7 +23,7 @@ namespace WorldDirect.CoAP.Net /// public partial class CoAPEndPoint : IEndPoint, IOutbox { - static readonly ILogger log = LogManager.GetLogger(typeof(CoAPEndPoint)); + static readonly Logger log = LogManager.GetCurrentClassLogger(); readonly ICoapConfig _config; readonly IChannel _channel; diff --git a/WorldDirect.CoAP/Net/Matcher.cs b/WorldDirect.CoAP/Net/Matcher.cs index 1fd22b2..9d00524 100644 --- a/WorldDirect.CoAP/Net/Matcher.cs +++ b/WorldDirect.CoAP/Net/Matcher.cs @@ -15,13 +15,13 @@ namespace WorldDirect.CoAP.Net using System.Collections.Concurrent; using System.Collections.Generic; using Deduplication; - using Log; + using NLog; using Observe; using Util; class Matcher : IMatcher, IDisposable { - static readonly ILogger log = LogManager.GetLogger(typeof(Matcher)); + static readonly Logger log = LogManager.GetCurrentClassLogger(); /// /// for all diff --git a/WorldDirect.CoAP/Observe/ObserveRelation.cs b/WorldDirect.CoAP/Observe/ObserveRelation.cs index 4f665a9..7c0c53b 100644 --- a/WorldDirect.CoAP/Observe/ObserveRelation.cs +++ b/WorldDirect.CoAP/Observe/ObserveRelation.cs @@ -14,8 +14,8 @@ namespace WorldDirect.CoAP.Observe using System; using System.Collections.Concurrent; using System.Collections.Generic; - using Log; using Net; + using NLog; using Server.Resources; using Util; @@ -24,7 +24,7 @@ namespace WorldDirect.CoAP.Observe /// public class ObserveRelation { - static readonly ILogger log = LogManager.GetLogger(typeof(ObserveRelation)); + static readonly Logger log = LogManager.GetCurrentClassLogger(); readonly ICoapConfig _config; readonly ObservingEndpoint _endpoint; readonly IResource _resource; diff --git a/WorldDirect.CoAP/Server/CoapServer.cs b/WorldDirect.CoAP/Server/CoapServer.cs index 6645210..b4a7bbb 100644 --- a/WorldDirect.CoAP/Server/CoapServer.cs +++ b/WorldDirect.CoAP/Server/CoapServer.cs @@ -14,8 +14,9 @@ namespace WorldDirect.CoAP.Server using System; using System.Collections.Generic; using System.Net; - using Log; using Net; + using NLog; + using NLog.Config; using Resources; /// @@ -23,7 +24,7 @@ namespace WorldDirect.CoAP.Server /// public class CoapServer : IServer { - static readonly ILogger log = LogManager.GetLogger(typeof(CoapServer)); + static readonly Logger log = LogManager.GetCurrentClassLogger(); readonly IResource _root; readonly List _endpoints = new List(); readonly ICoapConfig _config; @@ -52,6 +53,7 @@ public CoapServer(params Int32[] ports) /// the ports to bind to public CoapServer(ICoapConfig config, params Int32[] ports) { + LogManager.Configuration = new XmlLoggingConfiguration("Config/NLog.config"); _config = config ?? CoapConfig.Default; _root = new RootResource(this); _deliverer = new ServerMessageDeliverer(_config, _root); diff --git a/WorldDirect.CoAP/Server/Resources/Resource.cs b/WorldDirect.CoAP/Server/Resources/Resource.cs index 7cfb955..09e62a3 100644 --- a/WorldDirect.CoAP/Server/Resources/Resource.cs +++ b/WorldDirect.CoAP/Server/Resources/Resource.cs @@ -14,8 +14,8 @@ namespace WorldDirect.CoAP.Server.Resources using System; using System.Collections.Concurrent; using System.Collections.Generic; - using Log; using Net; + using NLog; using Observe; using Threading; @@ -26,7 +26,7 @@ namespace WorldDirect.CoAP.Server.Resources public class Resource : IResource { static readonly IEnumerable EmptyEndPoints = new IEndPoint[0]; - static readonly ILogger log = LogManager.GetLogger(typeof(Resource)); + static readonly Logger log = LogManager.GetCurrentClassLogger(); readonly ResourceAttributes _attributes = new ResourceAttributes(); private String _name; private String _path = String.Empty; diff --git a/WorldDirect.CoAP/Server/ServerMessageDeliverer.cs b/WorldDirect.CoAP/Server/ServerMessageDeliverer.cs index c2c75f3..962a5d6 100644 --- a/WorldDirect.CoAP/Server/ServerMessageDeliverer.cs +++ b/WorldDirect.CoAP/Server/ServerMessageDeliverer.cs @@ -13,8 +13,8 @@ namespace WorldDirect.CoAP.Server { using System; using System.Collections.Generic; - using Log; using Net; + using NLog; using Observe; using Resources; using Threading; @@ -26,7 +26,7 @@ namespace WorldDirect.CoAP.Server /// public class ServerMessageDeliverer : IMessageDeliverer { - static readonly ILogger log = LogManager.GetLogger(typeof(ServerMessageDeliverer)); + static readonly Logger log = LogManager.GetCurrentClassLogger(); readonly ICoapConfig _config; readonly IResource _root; readonly ObserveManager _observeManager = new ObserveManager(); diff --git a/WorldDirect.CoAP/Stack/BlockwiseLayer.cs b/WorldDirect.CoAP/Stack/BlockwiseLayer.cs index 99f9346..27ab407 100644 --- a/WorldDirect.CoAP/Stack/BlockwiseLayer.cs +++ b/WorldDirect.CoAP/Stack/BlockwiseLayer.cs @@ -14,12 +14,12 @@ namespace WorldDirect.CoAP.Stack using System; using System.Linq; using System.Threading; - using Log; using Net; + using NLog; public class BlockwiseLayer : AbstractLayer { - static readonly ILogger log = LogManager.GetLogger(typeof(BlockwiseLayer)); + static readonly Logger log = LogManager.GetCurrentClassLogger(); private Int32 _maxMessageSize; private Int32 _defaultBlockSize; diff --git a/WorldDirect.CoAP/Stack/ObserveLayer.cs b/WorldDirect.CoAP/Stack/ObserveLayer.cs index 0097ce7..3b71a0d 100644 --- a/WorldDirect.CoAP/Stack/ObserveLayer.cs +++ b/WorldDirect.CoAP/Stack/ObserveLayer.cs @@ -13,13 +13,13 @@ namespace WorldDirect.CoAP.Stack { using System; using System.Threading; - using Log; using Net; + using NLog; using Observe; public class ObserveLayer : AbstractLayer { - static readonly ILogger log = LogManager.GetLogger(typeof(ObserveLayer)); + static readonly Logger log = LogManager.GetCurrentClassLogger(); static readonly Object ReregistrationContextKey = "ReregistrationContext"; /// diff --git a/WorldDirect.CoAP/Stack/ReliabilityLayer.cs b/WorldDirect.CoAP/Stack/ReliabilityLayer.cs index 45f78b6..213a138 100644 --- a/WorldDirect.CoAP/Stack/ReliabilityLayer.cs +++ b/WorldDirect.CoAP/Stack/ReliabilityLayer.cs @@ -13,15 +13,15 @@ namespace WorldDirect.CoAP.Stack { using System; using System.Threading; - using Log; using Net; + using NLog; /// /// The reliability layer /// public class ReliabilityLayer : AbstractLayer { - static readonly ILogger log = LogManager.GetLogger(typeof(ReliabilityLayer)); + static readonly Logger log = LogManager.GetCurrentClassLogger(); static readonly Object TransmissionContextKey = "TransmissionContext"; private readonly Random _rand = new Random(); diff --git a/WorldDirect.CoAP/WorldDirect.CoAP.csproj b/WorldDirect.CoAP/WorldDirect.CoAP.csproj index aa330df..9826ff9 100644 --- a/WorldDirect.CoAP/WorldDirect.CoAP.csproj +++ b/WorldDirect.CoAP/WorldDirect.CoAP.csproj @@ -13,10 +13,18 @@ True + + PreserveNewest + - + + + + + + From e2b6b63c13a11d34b8f5b295deb6da2a3ea2a146 Mon Sep 17 00:00:00 2001 From: Philip Wille Date: Tue, 27 Oct 2020 23:46:56 +0100 Subject: [PATCH 03/32] Add POST method to ImageResource --- WorldDirect.CoAP.Example.Client/Program.cs | 430 +++++++++++------- .../WorldDirect.CoAP.Example.Client.csproj | 4 + WorldDirect.CoAP.Example.Server/Program.cs | 90 +--- .../Resources/HelloWorldResource.cs | 44 ++ .../Resources/ImageResource.cs | 87 ++++ 5 files changed, 395 insertions(+), 260 deletions(-) create mode 100644 WorldDirect.CoAP.Example.Server/Resources/HelloWorldResource.cs create mode 100644 WorldDirect.CoAP.Example.Server/Resources/ImageResource.cs diff --git a/WorldDirect.CoAP.Example.Client/Program.cs b/WorldDirect.CoAP.Example.Client/Program.cs index 1887633..7ebe35a 100644 --- a/WorldDirect.CoAP.Example.Client/Program.cs +++ b/WorldDirect.CoAP.Example.Client/Program.cs @@ -3,206 +3,290 @@ namespace WorldDirect.CoAP.Example.Client { using System.Collections.Generic; + using System.IO; + using System.Text; + using System.Threading.Tasks; + using CommandLine; using Util; - // .NET 2, .NET 4 entry point - class ExampleClient + public static class Program { - public static void Main(String[] args) + public static async Task Main(string[] args) { - String method = null; - Uri uri = null; - String payload = null; - Boolean loop = false; - Boolean byEvent = true; - - if (args.Length == 0) - PrintUsage(); + var parser = new Parser(with => + { + with.AutoHelp = true; + with.AutoVersion = true; + with.HelpWriter = Console.Out; + }); - Int32 index = 0; - foreach (String arg in args) + await parser.ParseArguments(args).WithParsedAsync(async a => { - if (arg[0] == '-') + var request = Request.NewPost(); + request.URI = new Uri(a.Endpoint); + var file = await File.ReadAllBytesAsync(a.Payload).ConfigureAwait(false); + var fileInfo = new FileInfo(a.Payload); + var extension = fileInfo.Extension; + if (extension.Equals("jpg")) { - if (arg.Equals("-l")) - loop = true; - if (arg.Equals("-e")) - byEvent = true; - else - Console.WriteLine("Unknown option: " + arg); + request.SetPayload(file, MediaType.ImageJpeg); + } + + if (extension.Equals("png")) + { + request.SetPayload(file, MediaType.ImagePng); } - else + + request.Send(); + + do { - switch (index) + Console.WriteLine("Receiving response..."); + + Response response = null; + response = request.WaitForResponse(); + + if (response == null) + { + Console.WriteLine("Request timeout"); + break; + } + else { - case 0: - method = arg.ToUpper(); - break; - case 1: - try + Console.WriteLine(Utils.ToString(response)); + Console.WriteLine("Time elapsed (ms): " + response.RTT); + + if (response.ContentType == MediaType.ApplicationLinkFormat) + { + IEnumerable links = LinkFormat.Parse(response.PayloadString); + if (links == null) { - uri = new Uri(arg); + Console.WriteLine("Failed parsing link format"); + Environment.Exit(1); } - catch (Exception ex) + else { - Console.WriteLine("Failed parsing URI: " + ex.Message); - Environment.Exit(1); + Console.WriteLine("Discovered resources:"); + foreach (var link in links) + { + Console.WriteLine(link); + } } - break; - case 2: - payload = arg; - break; - default: - Console.WriteLine("Unexpected argument: " + arg); - break; + } } - index++; - } - } + } while (false); + }).ConfigureAwait(false); + } + } - if (method == null || uri == null) - PrintUsage(); + [Verb("post", HelpText = "Sending a POST request to the given CoAP server.")] + public class PostArguments + { + [Option('e', "endpoint", HelpText = "Sets the endpoint for this POST method.")] + public string Endpoint { get; set; } - Request request = NewRequest(method); - if (request == null) - { - Console.WriteLine("Unknown method: " + method); - Environment.Exit(1); - } + [Option('p', "payload", HelpText = "Sets the payload for this POST method.")] + public string Payload { get; set; } + } - if ("OBSERVE".Equals(method)) - { - request.MarkObserve(); - loop = true; - } - else if ("DISCOVER".Equals(method) && - (String.IsNullOrEmpty(uri.AbsolutePath) || uri.AbsolutePath.Equals("/"))) - { - uri = new Uri(uri, "/.well-known/core"); - } + //// .NET 2, .NET 4 entry point + //class ExampleClient + //{ + // public static void Main(String[] args) + // { + // String method = null; + // Uri uri = null; + // String payload = null; + // Boolean loop = false; + // Boolean byEvent = true; - request.URI = uri; - request.SetPayload(payload, MediaType.TextPlain); + // if (args.Length == 0) + // PrintUsage(); - // uncomment the next line if you want to specify a draft to use - // request.EndPoint = CoAP.Net.EndPointManager.Draft13; + // Int32 index = 0; + // foreach (String arg in args) + // { + // if (arg[0] == '-') + // { + // if (arg.Equals("-l")) + // loop = true; + // if (arg.Equals("-e")) + // byEvent = true; + // else + // Console.WriteLine("Unknown option: " + arg); + // } + // else + // { + // switch (index) + // { + // case 0: + // method = arg.ToUpper(); + // break; + // case 1: + // try + // { + // uri = new Uri(arg); + // } + // catch (Exception ex) + // { + // Console.WriteLine("Failed parsing URI: " + ex.Message); + // Environment.Exit(1); + // } + // break; + // case 2: + // payload = arg; + // break; + // default: + // Console.WriteLine("Unexpected argument: " + arg); + // break; + // } + // index++; + // } + // } - Console.WriteLine(Utils.ToString(request)); + // if (method == null || uri == null) + // PrintUsage(); - try - { - if (byEvent) - { - request.Respond += delegate (Object sender, ResponseEventArgs e) - { - Response response = e.Response; - if (response == null) - { - Console.WriteLine("Request timeout"); - } - else - { - Console.WriteLine(Utils.ToString(response)); - Console.WriteLine("Time (ms): " + response.RTT); - } - if (!loop) - Environment.Exit(0); - }; - request.Send(); - while (true) - { - Console.ReadKey(); - } - } - else - { - // uncomment the next line if you need retransmission disabled. - // request.AckTimeout = -1; + // Request request = NewRequest(method); + // if (request == null) + // { + // Console.WriteLine("Unknown method: " + method); + // Environment.Exit(1); + // } - request.Send(); + // if ("OBSERVE".Equals(method)) + // { + // request.MarkObserve(); + // loop = true; + // } + // else if ("DISCOVER".Equals(method) && + // (String.IsNullOrEmpty(uri.AbsolutePath) || uri.AbsolutePath.Equals("/"))) + // { + // uri = new Uri(uri, "/.well-known/core"); + // } - do - { - Console.WriteLine("Receiving response..."); + // request.URI = uri; + // request.SetPayload(payload, MediaType.TextPlain); - Response response = null; - response = request.WaitForResponse(); + // // uncomment the next line if you want to specify a draft to use + // // request.EndPoint = CoAP.Net.EndPointManager.Draft13; - if (response == null) - { - Console.WriteLine("Request timeout"); - break; - } - else - { - Console.WriteLine(Utils.ToString(response)); - Console.WriteLine("Time elapsed (ms): " + response.RTT); + // Console.WriteLine(Utils.ToString(request)); - if (response.ContentType == MediaType.ApplicationLinkFormat) - { - IEnumerable links = LinkFormat.Parse(response.PayloadString); - if (links == null) - { - Console.WriteLine("Failed parsing link format"); - Environment.Exit(1); - } - else - { - Console.WriteLine("Discovered resources:"); - foreach (var link in links) - { - Console.WriteLine(link); - } - } - } - } - } while (loop); - } - } - catch (Exception ex) - { - Console.WriteLine("Failed executing request: " + ex.Message); - Console.WriteLine(ex); - Environment.Exit(1); - } - } + // try + // { + // if (byEvent) + // { + // request.Respond += delegate (Object sender, ResponseEventArgs e) + // { + // Response response = e.Response; + // if (response == null) + // { + // Console.WriteLine("Request timeout"); + // } + // else + // { + // Console.WriteLine(Utils.ToString(response)); + // Console.WriteLine("Time (ms): " + response.RTT); + // } + // if (!loop) + // Environment.Exit(0); + // }; + // request.Send(); + // while (true) + // { + // Console.ReadKey(); + // } + // } + // else + // { + // // uncomment the next line if you need retransmission disabled. + // // request.AckTimeout = -1; - private static Request NewRequest(String method) - { - switch (method) - { - case "POST": - return Request.NewPost(); - case "PUT": - return Request.NewPut(); - case "DELETE": - return Request.NewDelete(); - case "GET": - case "DISCOVER": - case "OBSERVE": - return Request.NewGet(); - default: - return null; - } - } + // request.Send(); - private static void PrintUsage() - { - Console.WriteLine("CoAP.NET Example Client"); - Console.WriteLine(); - Console.WriteLine("Usage: CoAPClient [-e] [-l] method uri [payload]"); - Console.WriteLine(" method : { GET, POST, PUT, DELETE, DISCOVER, OBSERVE }"); - Console.WriteLine(" uri : The CoAP URI of the remote endpoint or resource."); - Console.WriteLine(" payload : The data to send with the request."); - Console.WriteLine("Options:"); - Console.WriteLine(" -e : Receives responses by the Responded event."); - Console.WriteLine(" -l : Loops for multiple responses."); - Console.WriteLine(" (automatic for OBSERVE and separate responses)"); - Console.WriteLine(); - Console.WriteLine("Examples:"); - Console.WriteLine(" CoAPClient DISCOVER coap://localhost"); - Console.WriteLine(" CoAPClient POST coap://localhost/storage data"); - Environment.Exit(0); - } - } + // do + // { + // Console.WriteLine("Receiving response..."); + + // Response response = null; + // response = request.WaitForResponse(); + + // if (response == null) + // { + // Console.WriteLine("Request timeout"); + // break; + // } + // else + // { + // Console.WriteLine(Utils.ToString(response)); + // Console.WriteLine("Time elapsed (ms): " + response.RTT); + + // if (response.ContentType == MediaType.ApplicationLinkFormat) + // { + // IEnumerable links = LinkFormat.Parse(response.PayloadString); + // if (links == null) + // { + // Console.WriteLine("Failed parsing link format"); + // Environment.Exit(1); + // } + // else + // { + // Console.WriteLine("Discovered resources:"); + // foreach (var link in links) + // { + // Console.WriteLine(link); + // } + // } + // } + // } + // } while (loop); + // } + // } + // catch (Exception ex) + // { + // Console.WriteLine("Failed executing request: " + ex.Message); + // Console.WriteLine(ex); + // Environment.Exit(1); + // } + // } + + // private static Request NewRequest(String method) + // { + // switch (method) + // { + // case "POST": + // return Request.NewPost(); + // case "PUT": + // return Request.NewPut(); + // case "DELETE": + // return Request.NewDelete(); + // case "GET": + // case "DISCOVER": + // case "OBSERVE": + // return Request.NewGet(); + // default: + // return null; + // } + // } + + // private static void PrintUsage() + // { + // Console.WriteLine("CoAP.NET Example Client"); + // Console.WriteLine(); + // Console.WriteLine("Usage: CoAPClient [-e] [-l] method uri [payload]"); + // Console.WriteLine(" method : { GET, POST, PUT, DELETE, DISCOVER, OBSERVE }"); + // Console.WriteLine(" uri : The CoAP URI of the remote endpoint or resource."); + // Console.WriteLine(" payload : The data to send with the request."); + // Console.WriteLine("Options:"); + // Console.WriteLine(" -e : Receives responses by the Responded event."); + // Console.WriteLine(" -l : Loops for multiple responses."); + // Console.WriteLine(" (automatic for OBSERVE and separate responses)"); + // Console.WriteLine(); + // Console.WriteLine("Examples:"); + // Console.WriteLine(" CoAPClient DISCOVER coap://localhost"); + // Console.WriteLine(" CoAPClient POST coap://localhost/storage data"); + // Environment.Exit(0); + // } + //} } diff --git a/WorldDirect.CoAP.Example.Client/WorldDirect.CoAP.Example.Client.csproj b/WorldDirect.CoAP.Example.Client/WorldDirect.CoAP.Example.Client.csproj index 8f291e4..f6531ec 100644 --- a/WorldDirect.CoAP.Example.Client/WorldDirect.CoAP.Example.Client.csproj +++ b/WorldDirect.CoAP.Example.Client/WorldDirect.CoAP.Example.Client.csproj @@ -5,6 +5,10 @@ netcoreapp3.1 + + + + diff --git a/WorldDirect.CoAP.Example.Server/Program.cs b/WorldDirect.CoAP.Example.Server/Program.cs index 466b183..6f85a0d 100644 --- a/WorldDirect.CoAP.Example.Server/Program.cs +++ b/WorldDirect.CoAP.Example.Server/Program.cs @@ -3,101 +3,17 @@ namespace WorldDirect.CoAP.Example.Server { - using System.IO; - using CoAP.Server.Resources; + using Resources; class Program { static void Main(string[] args) { var server = new CoapServer(); - server.Add(new HelloWorldResource("HelloWorld")); - server.Add(new ImageResource("ImageResource")); + server.Add(new HelloWorldResource("HelloWorld", true)); + server.Add(new ImageResource("ImageResource", true)); server.Start(); Console.ReadLine(); } } - - public class ImageResource : Resource - { - private Int32[] _supported = new Int32[] { - MediaType.ImageJpeg, - MediaType.ImagePng - }; - - public ImageResource(String name) - : base(name) - { - Attributes.Title = "GET an image with different content-types"; - Attributes.AddResourceType("Image"); - - foreach (Int32 item in _supported) - { - Attributes.AddContentType(item); - } - - Attributes.MaximumSizeEstimate = 18029; - } - - protected override void DoGet(CoapExchange exchange) - { - String file = "data\\image\\"; - Int32 ct = MediaType.ImagePng; - Request request = exchange.Request; - - if ((ct = MediaType.NegotiationContent(ct, _supported, request.GetOptions(OptionType.Accept))) - == MediaType.Undefined) - { - exchange.Respond(StatusCode.NotAcceptable); - } - else - { - file += "image." + MediaType.ToFileExtension(ct); - if (File.Exists(file)) - { - Byte[] data = null; - - try - { - data = File.ReadAllBytes(file); - } - catch (Exception ex) - { - exchange.Respond(StatusCode.InternalServerError, "IO error"); - Console.WriteLine(ex.Message); - } - - Response response = new Response(StatusCode.Content); - response.Payload = data; - response.ContentType = ct; - exchange.Respond(response); - } - else - { - exchange.Respond(StatusCode.InternalServerError, "Image file not found"); - } - } - } - } - - public class HelloWorldResource : Resource - { - /// - public HelloWorldResource(string name) : base(name) - { - Attributes.Title = "GET a friendly greeting!"; - Attributes.AddResourceType("HelloWorldDisplayer"); - } - - /// - public HelloWorldResource(string name, bool visible) : base(name, visible) - { - } - - /// - protected override void DoGet(CoapExchange exchange) - { - exchange.Respond(StatusCode.Content, "Hello World!"); - } - } } diff --git a/WorldDirect.CoAP.Example.Server/Resources/HelloWorldResource.cs b/WorldDirect.CoAP.Example.Server/Resources/HelloWorldResource.cs new file mode 100644 index 0000000..9d087f3 --- /dev/null +++ b/WorldDirect.CoAP.Example.Server/Resources/HelloWorldResource.cs @@ -0,0 +1,44 @@ +namespace WorldDirect.CoAP.Example.Server.Resources +{ + using CoAP.Server.Resources; + + public class HelloWorldResource : Resource + { + /// + public HelloWorldResource(string name) + : this(name, false) + { + + } + + /// + public HelloWorldResource(string name, bool visible) + : base(name, visible) + { + Attributes.Title = "GET a friendly greeting!"; + Attributes.AddResourceType("HelloWorldDisplayer"); + } + + /// + protected override void DoGet(CoapExchange exchange) + { + exchange.Respond(StatusCode.Content, "Hello World!"); + } + } + + public class ObserveResource : Resource + { + public ObserveResource(string name) + : this(name, false) + { + + } + + public ObserveResource(string name, bool visible) + : base(name, visible) + { + Attributes.Title = "GET a friendly greeting!"; + Attributes.AddResourceType("HelloWorldDisplayer"); + } + } +} diff --git a/WorldDirect.CoAP.Example.Server/Resources/ImageResource.cs b/WorldDirect.CoAP.Example.Server/Resources/ImageResource.cs new file mode 100644 index 0000000..bedcb1b --- /dev/null +++ b/WorldDirect.CoAP.Example.Server/Resources/ImageResource.cs @@ -0,0 +1,87 @@ +namespace WorldDirect.CoAP.Example.Server.Resources +{ + using System; + using System.IO; + using System.Reflection.PortableExecutable; + using System.Text; + using CoAP.Server.Resources; + + public class ImageResource : Resource + { + private Int32[] _supported = new Int32[] { + MediaType.ImageJpeg, + MediaType.ImagePng + }; + + public ImageResource(String name) + : this(name, false) + { + } + + public ImageResource(string name, bool visible) + : base(name, visible) + { + Attributes.Title = "GET an image with different content-types"; + Attributes.AddResourceType("Image"); + + foreach (Int32 item in _supported) + { + Attributes.AddContentType(item); + } + + Attributes.MaximumSizeEstimate = 18029; + } + + protected override void DoGet(CoapExchange exchange) + { + String file = "data\\image\\"; + Int32 ct = MediaType.ImagePng; + Request request = exchange.Request; + + if ((ct = MediaType.NegotiationContent(ct, _supported, request.GetOptions(OptionType.Accept))) + == MediaType.Undefined) + { + exchange.Respond(StatusCode.NotAcceptable); + } + else + { + file += "image." + MediaType.ToFileExtension(ct); + if (File.Exists(file)) + { + Byte[] data = null; + + try + { + data = File.ReadAllBytes(file); + } + catch (Exception ex) + { + exchange.Respond(StatusCode.InternalServerError, "IO error"); + Console.WriteLine(ex.Message); + } + + Response response = new Response(StatusCode.Content); + response.Payload = data; + response.ContentType = ct; + exchange.Respond(response); + } + else + { + exchange.Respond(StatusCode.InternalServerError, "Image file not found"); + } + } + } + + protected override void DoPost(CoapExchange exchange) + { + var request = exchange.Request; + Directory.CreateDirectory("upload"); + File.WriteAllBytes("upload/upload.jpg", request.Payload); + var response = new Response(StatusCode.Created) + { + LocationPath = "upload" + }; + exchange.Respond(response); + } + } +} From c95205ed558f1f574acb36869a03b9641bb151eb Mon Sep 17 00:00:00 2001 From: Philip Wille Date: Fri, 30 Oct 2020 00:15:42 +0100 Subject: [PATCH 04/32] Add file upload example and observe example --- WorldDirect.CoAP.Example.Client/Program.cs | 117 +++++++++++++++++- WorldDirect.CoAP.Example.Server/Program.cs | 7 +- .../Resources/DateTimeResource.cs | 62 ++++++++++ .../Resources/ExampleJob.cs | 16 +++ .../Resources/HelloWorldResource.cs | 16 --- .../WorldDirect.CoAP.Example.Server.csproj | 4 + 6 files changed, 200 insertions(+), 22 deletions(-) create mode 100644 WorldDirect.CoAP.Example.Server/Resources/DateTimeResource.cs create mode 100644 WorldDirect.CoAP.Example.Server/Resources/ExampleJob.cs diff --git a/WorldDirect.CoAP.Example.Client/Program.cs b/WorldDirect.CoAP.Example.Client/Program.cs index 7ebe35a..9c8ebda 100644 --- a/WorldDirect.CoAP.Example.Client/Program.cs +++ b/WorldDirect.CoAP.Example.Client/Program.cs @@ -4,6 +4,7 @@ namespace WorldDirect.CoAP.Example.Client { using System.Collections.Generic; using System.IO; + using System.Net.Http.Headers; using System.Text; using System.Threading.Tasks; using CommandLine; @@ -20,7 +21,9 @@ public static async Task Main(string[] args) with.HelpWriter = Console.Out; }); - await parser.ParseArguments(args).WithParsedAsync(async a => + var arguments = parser.ParseArguments(args); + + await arguments.WithParsedAsync(async a => { var request = Request.NewPost(); request.URI = new Uri(a.Endpoint); @@ -76,16 +79,122 @@ await parser.ParseArguments(args).WithParsedAsync(async a => } } while (false); }).ConfigureAwait(false); + + arguments.WithParsed(a => + { + var baseUrl = new Uri(a.Endpoint); + var request = Request.NewGet(); + request.URI = new Uri(baseUrl, ".well-known/core"); + + request.Send(); + + do + { + Console.WriteLine("Receiving response..."); + + Response response = null; + response = request.WaitForResponse(); + + if (response == null) + { + Console.WriteLine("Request timeout"); + break; + } + else + { + Console.WriteLine(Utils.ToString(response)); + Console.WriteLine("Time elapsed (ms): " + response.RTT); + + if (response.ContentType == MediaType.ApplicationLinkFormat) + { + IEnumerable links = LinkFormat.Parse(response.PayloadString); + if (links == null) + { + Console.WriteLine("Failed parsing link format"); + Environment.Exit(1); + } + else + { + Console.WriteLine("Discovered resources:"); + foreach (var link in links) + { + Console.WriteLine(link); + } + } + } + } + } while (false); + }); + + arguments.WithParsed(a => + { + var request = Request.NewGet(); + request.URI = new Uri(a.Endpoint); + request.MarkObserve(); + + request.Send(); + + do + { + Console.WriteLine("Receiving response..."); + + Response response = null; + response = request.WaitForResponse(); + + if (response == null) + { + Console.WriteLine("Request timeout"); + break; + } + else + { + Console.WriteLine(Utils.ToString(response)); + Console.WriteLine("Time elapsed (ms): " + response.RTT); + + if (response.ContentType == MediaType.ApplicationLinkFormat) + { + IEnumerable links = LinkFormat.Parse(response.PayloadString); + if (links == null) + { + Console.WriteLine("Failed parsing link format"); + Environment.Exit(1); + } + else + { + Console.WriteLine("Discovered resources:"); + foreach (var link in links) + { + Console.WriteLine(link); + } + } + } + } + } while (true); + }); } } - [Verb("post", HelpText = "Sending a POST request to the given CoAP server.")] + [Verb("observe", HelpText = "Sends a OBSERVER request to the given CoAP endpoint.")] + public class ObserverArguments + { + [Option('e', "endpoint", HelpText = "Sets the endpoint for this OBSERVE request.")] + public string Endpoint { get; set; } + } + + [Verb("discover", HelpText = "Sends a DISCOVER request to the given CoAP endpoint.")] + public class DiscoverArguments + { + [Option('e', "endpoint", HelpText = "Sets the endpoint for this DISCOVER request.")] + public string Endpoint { get; set; } + } + + [Verb("post", HelpText = "Sends a POST request to the given CoAP endpoint with the given payload.")] public class PostArguments { - [Option('e', "endpoint", HelpText = "Sets the endpoint for this POST method.")] + [Option('e', "endpoint", HelpText = "Sets the endpoint for this POST request.")] public string Endpoint { get; set; } - [Option('p', "payload", HelpText = "Sets the payload for this POST method.")] + [Option('p', "payload", HelpText = "Sets the payload for this POST request.")] public string Payload { get; set; } } diff --git a/WorldDirect.CoAP.Example.Server/Program.cs b/WorldDirect.CoAP.Example.Server/Program.cs index 6f85a0d..2e26bfb 100644 --- a/WorldDirect.CoAP.Example.Server/Program.cs +++ b/WorldDirect.CoAP.Example.Server/Program.cs @@ -3,15 +3,18 @@ namespace WorldDirect.CoAP.Example.Server { + using System.Threading.Tasks; using Resources; - class Program + public static class Program { - static void Main(string[] args) + public static async Task Main(string[] args) { + var dateTimeResource = await DateTimeResource.Create().ConfigureAwait(false); var server = new CoapServer(); server.Add(new HelloWorldResource("HelloWorld", true)); server.Add(new ImageResource("ImageResource", true)); + server.Add(dateTimeResource); server.Start(); Console.ReadLine(); } diff --git a/WorldDirect.CoAP.Example.Server/Resources/DateTimeResource.cs b/WorldDirect.CoAP.Example.Server/Resources/DateTimeResource.cs new file mode 100644 index 0000000..40ed621 --- /dev/null +++ b/WorldDirect.CoAP.Example.Server/Resources/DateTimeResource.cs @@ -0,0 +1,62 @@ +namespace WorldDirect.CoAP.Example.Server.Resources +{ + using System; + using System.Threading.Tasks; + using CoAP.Server.Resources; + using Quartz; + using Quartz.Impl; + + public class DateTimeResource : Resource + { + public static async Task Create() + { + var resource = new DateTimeResource("DateTime", true); + var factory = new StdSchedulerFactory(); + var scheduler = await factory.GetScheduler().ConfigureAwait(false); + await scheduler.Start().ConfigureAwait(false); + var dataMap = new JobDataMap + { + {nameof(DateTimeResource), resource} + }; + var job = JobBuilder.Create() + .WithIdentity(nameof(ExampleJob), "group1") + .SetJobData(dataMap) + .Build(); + + var trigger = TriggerBuilder.Create() + .WithIdentity("EverySecondTrigger", "group1") + .StartNow() + .WithSimpleSchedule(t => + { + t.WithIntervalInSeconds(10); + t.RepeatForever(); + }) + .Build(); + + await scheduler.ScheduleJob(job, trigger).ConfigureAwait(false); + return resource; + } + + private DateTimeResource(string name) : base(name) + { + } + + private DateTimeResource(string name, bool visible) : base(name, visible) + { + this.Attributes.Title = "GET the current time"; + this.Attributes.AddResourceType("CurrentTime"); + this.Observable = true; + } + + public void SimulateChangingResource() + { + this.Changed(); + } + + protected override void DoGet(CoapExchange exchange) + { + var payload = DateTimeOffset.Now.ToString("R"); + exchange.Respond(StatusCode.Content, payload, MediaType.TextPlain); + } + } +} \ No newline at end of file diff --git a/WorldDirect.CoAP.Example.Server/Resources/ExampleJob.cs b/WorldDirect.CoAP.Example.Server/Resources/ExampleJob.cs new file mode 100644 index 0000000..93eac6e --- /dev/null +++ b/WorldDirect.CoAP.Example.Server/Resources/ExampleJob.cs @@ -0,0 +1,16 @@ +namespace WorldDirect.CoAP.Example.Server.Resources +{ + using System.Threading.Tasks; + using Quartz; + + public class ExampleJob : IJob + { + public Task Execute(IJobExecutionContext context) + { + var dataMap = context.JobDetail.JobDataMap; + var resource = (DateTimeResource)dataMap.Get(nameof(DateTimeResource)); + resource.SimulateChangingResource(); + return Task.CompletedTask; + } + } +} \ No newline at end of file diff --git a/WorldDirect.CoAP.Example.Server/Resources/HelloWorldResource.cs b/WorldDirect.CoAP.Example.Server/Resources/HelloWorldResource.cs index 9d087f3..6bb1eec 100644 --- a/WorldDirect.CoAP.Example.Server/Resources/HelloWorldResource.cs +++ b/WorldDirect.CoAP.Example.Server/Resources/HelloWorldResource.cs @@ -25,20 +25,4 @@ protected override void DoGet(CoapExchange exchange) exchange.Respond(StatusCode.Content, "Hello World!"); } } - - public class ObserveResource : Resource - { - public ObserveResource(string name) - : this(name, false) - { - - } - - public ObserveResource(string name, bool visible) - : base(name, visible) - { - Attributes.Title = "GET a friendly greeting!"; - Attributes.AddResourceType("HelloWorldDisplayer"); - } - } } diff --git a/WorldDirect.CoAP.Example.Server/WorldDirect.CoAP.Example.Server.csproj b/WorldDirect.CoAP.Example.Server/WorldDirect.CoAP.Example.Server.csproj index 8f291e4..3be34d0 100644 --- a/WorldDirect.CoAP.Example.Server/WorldDirect.CoAP.Example.Server.csproj +++ b/WorldDirect.CoAP.Example.Server/WorldDirect.CoAP.Example.Server.csproj @@ -5,6 +5,10 @@ netcoreapp3.1 + + + + From eea08175c6cb4742772eecc5919c45b27d0ac88d Mon Sep 17 00:00:00 2001 From: Philip Wille Date: Fri, 6 Nov 2020 00:56:16 +0100 Subject: [PATCH 05/32] Enhanced ImageResource --- WorldDirect.CoAP.Example.Client/Program.cs | 85 ++++++++++++++-- WorldDirect.CoAP.Example.Server/Program.cs | 7 +- .../Resources/DateTimeResource.cs | 57 ++++------- .../Resources/ExampleJob.cs | 16 --- .../Resources/ImageResource.cs | 99 ++++++++----------- .../WorldDirect.CoAP.Example.Server.csproj | 4 - WorldDirect.CoAP/MediaType.cs | 6 ++ 7 files changed, 142 insertions(+), 132 deletions(-) delete mode 100644 WorldDirect.CoAP.Example.Server/Resources/ExampleJob.cs diff --git a/WorldDirect.CoAP.Example.Client/Program.cs b/WorldDirect.CoAP.Example.Client/Program.cs index 9c8ebda..2e326b0 100644 --- a/WorldDirect.CoAP.Example.Client/Program.cs +++ b/WorldDirect.CoAP.Example.Client/Program.cs @@ -3,7 +3,9 @@ namespace WorldDirect.CoAP.Example.Client { using System.Collections.Generic; + using System.ComponentModel.Design; using System.IO; + using System.Linq; using System.Net.Http.Headers; using System.Text; using System.Threading.Tasks; @@ -21,25 +23,31 @@ public static async Task Main(string[] args) with.HelpWriter = Console.Out; }); - var arguments = parser.ParseArguments(args); + var arguments = parser.ParseArguments(args); await arguments.WithParsedAsync(async a => { var request = Request.NewPost(); - request.URI = new Uri(a.Endpoint); - var file = await File.ReadAllBytesAsync(a.Payload).ConfigureAwait(false); - var fileInfo = new FileInfo(a.Payload); - var extension = fileInfo.Extension; - if (extension.Equals("jpg")) + byte[] payload; + int mediaType; + if (Path.HasExtension(a.Payload)) { - request.SetPayload(file, MediaType.ImageJpeg); + payload = await File.ReadAllBytesAsync(a.Payload).ConfigureAwait(false); + var fileInfo = new FileInfo(a.Payload); + var extension = fileInfo.Name.Split('.')[1]; + var filename = fileInfo.Name; + payload = payload.Concat(Encoding.UTF8.GetBytes($"--{filename}--")).ToArray(); + mediaType = MediaType.Parse(extension); } - - if (extension.Equals("png")) + else { - request.SetPayload(file, MediaType.ImagePng); + payload = Encoding.UTF8.GetBytes(a.Payload); + mediaType = MediaType.TextPlain; } + request.URI = new Uri(a.Endpoint); + request.SetPayload(payload, mediaType); + request.Send(); do @@ -170,6 +178,53 @@ await arguments.WithParsedAsync(async a => } } } while (true); + + request.MarkObserveCancel(); + request.Send(); + }); + + arguments.WithParsed(a => + { + var request = Request.NewGet(); + request.URI = new Uri(a.Endpoint); + + request.Send(); + do + { + Console.WriteLine("Receiving response..."); + + Response response = null; + response = request.WaitForResponse(); + + if (response == null) + { + Console.WriteLine("Request timeout"); + break; + } + else + { + Console.WriteLine(Utils.ToString(response)); + Console.WriteLine("Time elapsed (ms): " + response.RTT); + + if (response.ContentType == MediaType.ApplicationLinkFormat) + { + IEnumerable links = LinkFormat.Parse(response.PayloadString); + if (links == null) + { + Console.WriteLine("Failed parsing link format"); + Environment.Exit(1); + } + else + { + Console.WriteLine("Discovered resources:"); + foreach (var link in links) + { + Console.WriteLine(link); + } + } + } + } + } while (a.Looping); }); } } @@ -198,6 +253,16 @@ public class PostArguments public string Payload { get; set; } } + [Verb("get", HelpText = "Sends a GET request to the given endpoint and if set the request will be repeated multiple times.")] + public class GetArguments + { + [Option('e', "endpoint", HelpText = "Sets the endpoint for this GET request.")] + public string Endpoint { get; set; } + + [Option('m', "multiple", HelpText = "Sets the variable that indicates if the request should be sent multiple times.")] + public bool Looping { get; set; } + } + //// .NET 2, .NET 4 entry point //class ExampleClient //{ diff --git a/WorldDirect.CoAP.Example.Server/Program.cs b/WorldDirect.CoAP.Example.Server/Program.cs index 2e26bfb..9bf1575 100644 --- a/WorldDirect.CoAP.Example.Server/Program.cs +++ b/WorldDirect.CoAP.Example.Server/Program.cs @@ -8,13 +8,12 @@ namespace WorldDirect.CoAP.Example.Server public static class Program { - public static async Task Main(string[] args) + public static void Main(string[] args) { - var dateTimeResource = await DateTimeResource.Create().ConfigureAwait(false); var server = new CoapServer(); server.Add(new HelloWorldResource("HelloWorld", true)); - server.Add(new ImageResource("ImageResource", true)); - server.Add(dateTimeResource); + server.Add(new FtpResource("Ftp", true)); + server.Add(new DateTimeResource("DateTime", true)); server.Start(); Console.ReadLine(); } diff --git a/WorldDirect.CoAP.Example.Server/Resources/DateTimeResource.cs b/WorldDirect.CoAP.Example.Server/Resources/DateTimeResource.cs index 40ed621..ca5de7b 100644 --- a/WorldDirect.CoAP.Example.Server/Resources/DateTimeResource.cs +++ b/WorldDirect.CoAP.Example.Server/Resources/DateTimeResource.cs @@ -3,60 +3,39 @@ using System; using System.Threading.Tasks; using CoAP.Server.Resources; - using Quartz; - using Quartz.Impl; public class DateTimeResource : Resource { - public static async Task Create() - { - var resource = new DateTimeResource("DateTime", true); - var factory = new StdSchedulerFactory(); - var scheduler = await factory.GetScheduler().ConfigureAwait(false); - await scheduler.Start().ConfigureAwait(false); - var dataMap = new JobDataMap - { - {nameof(DateTimeResource), resource} - }; - var job = JobBuilder.Create() - .WithIdentity(nameof(ExampleJob), "group1") - .SetJobData(dataMap) - .Build(); - - var trigger = TriggerBuilder.Create() - .WithIdentity("EverySecondTrigger", "group1") - .StartNow() - .WithSimpleSchedule(t => - { - t.WithIntervalInSeconds(10); - t.RepeatForever(); - }) - .Build(); - - await scheduler.ScheduleJob(job, trigger).ConfigureAwait(false); - return resource; - } - - private DateTimeResource(string name) : base(name) - { - } - - private DateTimeResource(string name, bool visible) : base(name, visible) + private readonly Task delay; + private bool isObserved = true; + + public DateTimeResource(string name, bool visible) : base(name, visible) { this.Attributes.Title = "GET the current time"; this.Attributes.AddResourceType("CurrentTime"); this.Observable = true; + this.delay = this.ChangeResourceAsync(); } - public void SimulateChangingResource() + public async Task ChangeResourceAsync() { - this.Changed(); + while (this.isObserved) + { + this.Changed(); + await Task.Delay(TimeSpan.FromSeconds(10)).ConfigureAwait(false); + } } protected override void DoGet(CoapExchange exchange) { + if (exchange.Request.Observe == 1) + { + this.isObserved = false; + this.ClearAndNotifyObserveRelations(StatusCode.Content); + } + var payload = DateTimeOffset.Now.ToString("R"); exchange.Respond(StatusCode.Content, payload, MediaType.TextPlain); } } -} \ No newline at end of file +} diff --git a/WorldDirect.CoAP.Example.Server/Resources/ExampleJob.cs b/WorldDirect.CoAP.Example.Server/Resources/ExampleJob.cs deleted file mode 100644 index 93eac6e..0000000 --- a/WorldDirect.CoAP.Example.Server/Resources/ExampleJob.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace WorldDirect.CoAP.Example.Server.Resources -{ - using System.Threading.Tasks; - using Quartz; - - public class ExampleJob : IJob - { - public Task Execute(IJobExecutionContext context) - { - var dataMap = context.JobDetail.JobDataMap; - var resource = (DateTimeResource)dataMap.Get(nameof(DateTimeResource)); - resource.SimulateChangingResource(); - return Task.CompletedTask; - } - } -} \ No newline at end of file diff --git a/WorldDirect.CoAP.Example.Server/Resources/ImageResource.cs b/WorldDirect.CoAP.Example.Server/Resources/ImageResource.cs index bedcb1b..7280b86 100644 --- a/WorldDirect.CoAP.Example.Server/Resources/ImageResource.cs +++ b/WorldDirect.CoAP.Example.Server/Resources/ImageResource.cs @@ -1,86 +1,67 @@ namespace WorldDirect.CoAP.Example.Server.Resources { - using System; using System.IO; - using System.Reflection.PortableExecutable; using System.Text; + using System.Text.RegularExpressions; using CoAP.Server.Resources; - public class ImageResource : Resource + public class FtpResource : Resource { - private Int32[] _supported = new Int32[] { - MediaType.ImageJpeg, - MediaType.ImagePng - }; + private const string FilenamePattern = @"(?.*?)--(?.*?)--"; + private string filename; - public ImageResource(String name) - : this(name, false) + public FtpResource(string name, bool visible) + : base(name, visible) { + Attributes.Title = "Resource for offering FTP service."; } - public ImageResource(string name, bool visible) - : base(name, visible) + protected override void DoGet(CoapExchange exchange) { - Attributes.Title = "GET an image with different content-types"; - Attributes.AddResourceType("Image"); - - foreach (Int32 item in _supported) + var content = File.ReadAllBytes($"upload/{this.filename}"); + var extension = MediaType.Parse(this.filename.Substring(this.filename.IndexOf('.') + 1)); + var response = new Response(StatusCode.Content) { - Attributes.AddContentType(item); - } + Accept = (int)OptionType.Accept, + }; - Attributes.MaximumSizeEstimate = 18029; + response.SetPayload(content, extension); + exchange.Respond(response); } - protected override void DoGet(CoapExchange exchange) + protected override void DoPost(CoapExchange exchange) { - String file = "data\\image\\"; - Int32 ct = MediaType.ImagePng; - Request request = exchange.Request; - - if ((ct = MediaType.NegotiationContent(ct, _supported, request.GetOptions(OptionType.Accept))) - == MediaType.Undefined) + var request = exchange.Request; + var matchResult = Regex.Match(request.PayloadString, FilenamePattern); + var tmpFilename = matchResult.Groups["filename"].Value; + var filenameWithoutExtension = tmpFilename.Split('.')[0]; + var content = matchResult.Groups["content"].Value; + File.WriteAllBytes($"upload/{tmpFilename}", Encoding.UTF8.GetBytes(content)); + var childResource = (Resource)this.GetChild(filenameWithoutExtension); + Response response; + if (childResource == null) { - exchange.Respond(StatusCode.NotAcceptable); + var resource = new FtpResource(filenameWithoutExtension, true) + { + filename = tmpFilename, + }; + this.Add(resource); + response = new Response(StatusCode.Created) + { + LocationPath = resource.Path, + UriPath = resource.Uri, + }; } else { - file += "image." + MediaType.ToFileExtension(ct); - if (File.Exists(file)) - { - Byte[] data = null; - - try - { - data = File.ReadAllBytes(file); - } - catch (Exception ex) - { - exchange.Respond(StatusCode.InternalServerError, "IO error"); - Console.WriteLine(ex.Message); - } - - Response response = new Response(StatusCode.Content); - response.Payload = data; - response.ContentType = ct; - exchange.Respond(response); - } - else + childResource.Changed(); + response = new Response(StatusCode.Changed) { - exchange.Respond(StatusCode.InternalServerError, "Image file not found"); - } + LocationPath = childResource.Path, + UriPath = childResource.Uri, + }; } - } - protected override void DoPost(CoapExchange exchange) - { - var request = exchange.Request; - Directory.CreateDirectory("upload"); - File.WriteAllBytes("upload/upload.jpg", request.Payload); - var response = new Response(StatusCode.Created) - { - LocationPath = "upload" - }; exchange.Respond(response); } } diff --git a/WorldDirect.CoAP.Example.Server/WorldDirect.CoAP.Example.Server.csproj b/WorldDirect.CoAP.Example.Server/WorldDirect.CoAP.Example.Server.csproj index 3be34d0..8f291e4 100644 --- a/WorldDirect.CoAP.Example.Server/WorldDirect.CoAP.Example.Server.csproj +++ b/WorldDirect.CoAP.Example.Server/WorldDirect.CoAP.Example.Server.csproj @@ -5,10 +5,6 @@ netcoreapp3.1 - - - - diff --git a/WorldDirect.CoAP/MediaType.cs b/WorldDirect.CoAP/MediaType.cs index 2892f74..bdae9d4 100644 --- a/WorldDirect.CoAP/MediaType.cs +++ b/WorldDirect.CoAP/MediaType.cs @@ -13,6 +13,7 @@ namespace WorldDirect.CoAP { using System; using System.Collections.Generic; + using System.Reflection; using System.Text.RegularExpressions; /// @@ -247,6 +248,11 @@ public static Int32 Parse(String type) { if (pair.Value[0].Equals(type, StringComparison.OrdinalIgnoreCase)) return pair.Key; + + if (pair.Value[1].Equals(type, StringComparison.OrdinalIgnoreCase)) + { + return pair.Key; + } } return Undefined; From ef817e989a1d305aaeda2d92ebf73d6a6588f4cc Mon Sep 17 00:00:00 2001 From: Philip Wille Date: Fri, 13 Nov 2020 01:04:08 +0100 Subject: [PATCH 06/32] Attempt to renew CoapClient similar to HttpClient --- .../DeleteArguments.cs | 11 + .../DiscoverArguments.cs | 11 + .../GetArguments.cs | 14 + .../ObserverArguments.cs | 11 + .../PostArguments.cs | 14 + WorldDirect.CoAP.Example.Client/Program.cs | 105 ++++---- WorldDirect.CoAP.Example.Server/Program.cs | 7 + .../{ImageResource.cs => FtpResource.cs} | 13 + WorldDirect.CoAP/Code.cs | 126 +-------- WorldDirect.CoAP/EndPointManager.cs | 29 ++ WorldDirect.CoAP/LinkFormat.cs | 2 +- WorldDirect.CoAP/Message.cs | 2 +- WorldDirect.CoAP/MessageEventArgs.cs | 29 ++ WorldDirect.CoAP/Method.cs | 25 ++ WorldDirect.CoAP/Net/CoapClient.cs | 248 ++++++++++++++++++ WorldDirect.CoAP/Net/Exchange.cs | 9 - WorldDirect.CoAP/Net/Origin.cs | 11 + WorldDirect.CoAP/Option.cs | 2 +- WorldDirect.CoAP/OptionFormat.cs | 13 + WorldDirect.CoAP/OptionType.cs | 11 - WorldDirect.CoAP/ResponseEventArgs.cs | 27 -- .../Server/Resources/CoapExchange.cs | 2 +- WorldDirect.CoAP/Spec.cs | 29 -- WorldDirect.CoAP/Stack/BlockwiseStatus.cs | 2 +- WorldDirect.CoAP/Stack/Chain.cs | 135 ---------- WorldDirect.CoAP/Stack/IChain.cs | 140 ++++++++++ WorldDirect.CoAP/Stack/ILayer.cs | 31 --- WorldDirect.CoAP/Stack/INextLayer.cs | 35 +++ WorldDirect.CoAP/StatusCode.cs | 101 +++++++ .../{Executors.NET40.cs => Executors.cs} | 0 WorldDirect.CoAP/Util/Utils.cs | 2 +- 31 files changed, 770 insertions(+), 427 deletions(-) create mode 100644 WorldDirect.CoAP.Example.Client/DeleteArguments.cs create mode 100644 WorldDirect.CoAP.Example.Client/DiscoverArguments.cs create mode 100644 WorldDirect.CoAP.Example.Client/GetArguments.cs create mode 100644 WorldDirect.CoAP.Example.Client/ObserverArguments.cs create mode 100644 WorldDirect.CoAP.Example.Client/PostArguments.cs rename WorldDirect.CoAP.Example.Server/Resources/{ImageResource.cs => FtpResource.cs} (88%) create mode 100644 WorldDirect.CoAP/EndPointManager.cs create mode 100644 WorldDirect.CoAP/MessageEventArgs.cs create mode 100644 WorldDirect.CoAP/Method.cs create mode 100644 WorldDirect.CoAP/Net/CoapClient.cs create mode 100644 WorldDirect.CoAP/Net/Origin.cs create mode 100644 WorldDirect.CoAP/OptionFormat.cs create mode 100644 WorldDirect.CoAP/Stack/IChain.cs create mode 100644 WorldDirect.CoAP/Stack/INextLayer.cs create mode 100644 WorldDirect.CoAP/StatusCode.cs rename WorldDirect.CoAP/Threading/{Executors.NET40.cs => Executors.cs} (100%) diff --git a/WorldDirect.CoAP.Example.Client/DeleteArguments.cs b/WorldDirect.CoAP.Example.Client/DeleteArguments.cs new file mode 100644 index 0000000..1e6610b --- /dev/null +++ b/WorldDirect.CoAP.Example.Client/DeleteArguments.cs @@ -0,0 +1,11 @@ +namespace WorldDirect.CoAP.Example.Client +{ + using CommandLine; + + [Verb("delete", HelpText = "Sends a delete request to the given CoAP endpoint.")] + public class DeleteArguments + { + [Option('e', "endpoint", HelpText = "Sets the endpoint for this DELETE request.")] + public string Endpoint { get; set; } + } +} \ No newline at end of file diff --git a/WorldDirect.CoAP.Example.Client/DiscoverArguments.cs b/WorldDirect.CoAP.Example.Client/DiscoverArguments.cs new file mode 100644 index 0000000..9d5d798 --- /dev/null +++ b/WorldDirect.CoAP.Example.Client/DiscoverArguments.cs @@ -0,0 +1,11 @@ +namespace WorldDirect.CoAP.Example.Client +{ + using CommandLine; + + [Verb("discover", HelpText = "Sends a DISCOVER request to the given CoAP endpoint.")] + public class DiscoverArguments + { + [Option('e', "endpoint", HelpText = "Sets the endpoint for this DISCOVER request.")] + public string Endpoint { get; set; } + } +} \ No newline at end of file diff --git a/WorldDirect.CoAP.Example.Client/GetArguments.cs b/WorldDirect.CoAP.Example.Client/GetArguments.cs new file mode 100644 index 0000000..2cfd367 --- /dev/null +++ b/WorldDirect.CoAP.Example.Client/GetArguments.cs @@ -0,0 +1,14 @@ +namespace WorldDirect.CoAP.Example.Client +{ + using CommandLine; + + [Verb("get", HelpText = "Sends a GET request to the given endpoint and if set the request will be repeated multiple times.")] + public class GetArguments + { + [Option('e', "endpoint", HelpText = "Sets the endpoint for this GET request.")] + public string Endpoint { get; set; } + + [Option('m', "multiple", HelpText = "Sets the variable that indicates if the request should be sent multiple times.")] + public bool Looping { get; set; } + } +} \ No newline at end of file diff --git a/WorldDirect.CoAP.Example.Client/ObserverArguments.cs b/WorldDirect.CoAP.Example.Client/ObserverArguments.cs new file mode 100644 index 0000000..bfe759d --- /dev/null +++ b/WorldDirect.CoAP.Example.Client/ObserverArguments.cs @@ -0,0 +1,11 @@ +namespace WorldDirect.CoAP.Example.Client +{ + using CommandLine; + + [Verb("observe", HelpText = "Sends a OBSERVER request to the given CoAP endpoint.")] + public class ObserverArguments + { + [Option('e', "endpoint", HelpText = "Sets the endpoint for this OBSERVE request.")] + public string Endpoint { get; set; } + } +} \ No newline at end of file diff --git a/WorldDirect.CoAP.Example.Client/PostArguments.cs b/WorldDirect.CoAP.Example.Client/PostArguments.cs new file mode 100644 index 0000000..8ba7759 --- /dev/null +++ b/WorldDirect.CoAP.Example.Client/PostArguments.cs @@ -0,0 +1,14 @@ +namespace WorldDirect.CoAP.Example.Client +{ + using CommandLine; + + [Verb("post", HelpText = "Sends a POST request to the given CoAP endpoint with the given payload.")] + public class PostArguments + { + [Option('e', "endpoint", HelpText = "Sets the endpoint for this POST request.")] + public string Endpoint { get; set; } + + [Option('p', "payload", HelpText = "Sets the payload for this POST request.")] + public string Payload { get; set; } + } +} \ No newline at end of file diff --git a/WorldDirect.CoAP.Example.Client/Program.cs b/WorldDirect.CoAP.Example.Client/Program.cs index 2e326b0..97a04f5 100644 --- a/WorldDirect.CoAP.Example.Client/Program.cs +++ b/WorldDirect.CoAP.Example.Client/Program.cs @@ -23,7 +23,51 @@ public static async Task Main(string[] args) with.HelpWriter = Console.Out; }); - var arguments = parser.ParseArguments(args); + var arguments = parser.ParseArguments(args); + + arguments.WithParsed(a => + { + var request = Request.NewDelete(); + request.URI = new Uri(a.Endpoint); + request.Send(); + + do + { + Console.WriteLine("Receiving response..."); + + Response response = null; + response = request.WaitForResponse(); + + if (response == null) + { + Console.WriteLine("Request timeout"); + break; + } + else + { + Console.WriteLine(Utils.ToString(response)); + Console.WriteLine("Time elapsed (ms): " + response.RTT); + + if (response.ContentType == MediaType.ApplicationLinkFormat) + { + IEnumerable links = LinkFormat.Parse(response.PayloadString); + if (links == null) + { + Console.WriteLine("Failed parsing link format"); + Environment.Exit(1); + } + else + { + Console.WriteLine("Discovered resources:"); + foreach (var link in links) + { + Console.WriteLine(link); + } + } + } + } + } while (false); + }); await arguments.WithParsedAsync(async a => { @@ -158,29 +202,11 @@ await arguments.WithParsedAsync(async a => { Console.WriteLine(Utils.ToString(response)); Console.WriteLine("Time elapsed (ms): " + response.RTT); - - if (response.ContentType == MediaType.ApplicationLinkFormat) - { - IEnumerable links = LinkFormat.Parse(response.PayloadString); - if (links == null) - { - Console.WriteLine("Failed parsing link format"); - Environment.Exit(1); - } - else - { - Console.WriteLine("Discovered resources:"); - foreach (var link in links) - { - Console.WriteLine(link); - } - } - } + break; } } while (true); - request.MarkObserveCancel(); - request.Send(); + request.Cancel(); }); arguments.WithParsed(a => @@ -226,41 +252,10 @@ await arguments.WithParsedAsync(async a => } } while (a.Looping); }); - } - } - [Verb("observe", HelpText = "Sends a OBSERVER request to the given CoAP endpoint.")] - public class ObserverArguments - { - [Option('e', "endpoint", HelpText = "Sets the endpoint for this OBSERVE request.")] - public string Endpoint { get; set; } - } - - [Verb("discover", HelpText = "Sends a DISCOVER request to the given CoAP endpoint.")] - public class DiscoverArguments - { - [Option('e', "endpoint", HelpText = "Sets the endpoint for this DISCOVER request.")] - public string Endpoint { get; set; } - } - - [Verb("post", HelpText = "Sends a POST request to the given CoAP endpoint with the given payload.")] - public class PostArguments - { - [Option('e', "endpoint", HelpText = "Sets the endpoint for this POST request.")] - public string Endpoint { get; set; } - - [Option('p', "payload", HelpText = "Sets the payload for this POST request.")] - public string Payload { get; set; } - } - - [Verb("get", HelpText = "Sends a GET request to the given endpoint and if set the request will be repeated multiple times.")] - public class GetArguments - { - [Option('e', "endpoint", HelpText = "Sets the endpoint for this GET request.")] - public string Endpoint { get; set; } - - [Option('m', "multiple", HelpText = "Sets the variable that indicates if the request should be sent multiple times.")] - public bool Looping { get; set; } + Console.WriteLine("Fin"); + Console.ReadLine(); + } } //// .NET 2, .NET 4 entry point diff --git a/WorldDirect.CoAP.Example.Server/Program.cs b/WorldDirect.CoAP.Example.Server/Program.cs index 9bf1575..1f9414b 100644 --- a/WorldDirect.CoAP.Example.Server/Program.cs +++ b/WorldDirect.CoAP.Example.Server/Program.cs @@ -15,7 +15,14 @@ public static void Main(string[] args) server.Add(new FtpResource("Ftp", true)); server.Add(new DateTimeResource("DateTime", true)); server.Start(); + DoSomething(); + GC.Collect(); Console.ReadLine(); } + + private static void DoSomething() + { + var x = new FtpResource("Ftp", true); + } } } diff --git a/WorldDirect.CoAP.Example.Server/Resources/ImageResource.cs b/WorldDirect.CoAP.Example.Server/Resources/FtpResource.cs similarity index 88% rename from WorldDirect.CoAP.Example.Server/Resources/ImageResource.cs rename to WorldDirect.CoAP.Example.Server/Resources/FtpResource.cs index 7280b86..be41e18 100644 --- a/WorldDirect.CoAP.Example.Server/Resources/ImageResource.cs +++ b/WorldDirect.CoAP.Example.Server/Resources/FtpResource.cs @@ -1,5 +1,6 @@ namespace WorldDirect.CoAP.Example.Server.Resources { + using System; using System.IO; using System.Text; using System.Text.RegularExpressions; @@ -16,6 +17,18 @@ public FtpResource(string name, bool visible) Attributes.Title = "Resource for offering FTP service."; } + ~FtpResource() + { + Console.WriteLine("Deconstructed"); + } + + protected override void DoDelete(CoapExchange exchange) + { + this.Parent.Remove(this); + GC.Collect(); + exchange.Respond(StatusCode.Deleted); + } + protected override void DoGet(CoapExchange exchange) { var content = File.ReadAllBytes($"upload/{this.filename}"); diff --git a/WorldDirect.CoAP/Code.cs b/WorldDirect.CoAP/Code.cs index 9fd7f4e..8f7fc22 100644 --- a/WorldDirect.CoAP/Code.cs +++ b/WorldDirect.CoAP/Code.cs @@ -65,7 +65,7 @@ public class Code /// public const Int32 Changed = 68; /// - /// 2.05 Content + /// 2.05 Value /// public const Int32 Content = 69; /// @@ -215,7 +215,7 @@ public static String ToString(Int32 code) case Changed: return "2.04 Changed"; case Content: - return "2.05 Content"; + return "2.05 Value"; case BadRequest: return "4.00 Bad Request"; case Unauthorized: @@ -275,126 +275,4 @@ public static String ToString(Int32 code) } } } - - /// - /// Methods of request - /// - public enum Method - { - /// - /// GET method - /// - GET = 1, - /// - /// POST method - /// - POST = 2, - /// - /// PUT method - /// - PUT = 3, - /// - /// DELETE method - /// - DELETE = 4 - } - - /// - /// Response status codes. - /// - public enum StatusCode - { - /// - /// 2.01 Created - /// - Created = 65, - /// - /// 2.02 Deleted - /// - Deleted = 66, - /// - /// 2.03 Valid - /// - Valid = 67, - /// - /// 2.04 Changed - /// - Changed = 68, - /// - /// 2.05 Content - /// - Content = 69, - /// - /// 2.?? Continue - /// - Continue = 95, - /// - /// 4.00 Bad Request - /// - BadRequest = 128, - /// - /// 4.01 Unauthorized - /// - Unauthorized = 129, - /// - /// 4.02 Bad Option - /// - BadOption = 130, - /// - /// 4.03 Forbidden - /// - Forbidden = 131, - /// - /// 4.04 Not Found - /// - NotFound = 132, - /// - /// 4.05 Method Not Allowed - /// - MethodNotAllowed = 133, - /// - /// 4.06 Not Acceptable - /// - NotAcceptable = 134, - /// - /// 4.08 Request Entity Incomplete (draft-ietf-core-block) - /// - RequestEntityIncomplete = 136, - /// - /// - /// - PreconditionFailed = 140, - /// - /// 4.13 Request Entity Too Large - /// - RequestEntityTooLarge = 141, - /// - /// 4.15 Unsupported Media Type - /// - UnsupportedMediaType = 143, - /// - /// 5.00 Internal Server Error - /// - InternalServerError = 160, - /// - /// 5.01 Not Implemented - /// - NotImplemented = 161, - /// - /// 5.02 Bad Gateway - /// - BadGateway = 162, - /// - /// 5.03 Service Unavailable - /// - ServiceUnavailable = 163, - /// - /// 5.04 Gateway Timeout - /// - GatewayTimeout = 164, - /// - /// 5.05 Proxying Not Supported - /// - ProxyingNotSupported = 165 - } } diff --git a/WorldDirect.CoAP/EndPointManager.cs b/WorldDirect.CoAP/EndPointManager.cs new file mode 100644 index 0000000..6207787 --- /dev/null +++ b/WorldDirect.CoAP/EndPointManager.cs @@ -0,0 +1,29 @@ +namespace WorldDirect.CoAP.Net +{ + partial class EndPointManager + { + private static IEndPoint _default; + + private static IEndPoint GetDefaultEndPoint() + { + if (_default == null) + { + lock (typeof(EndPointManager)) + { + if (_default == null) + { + _default = CreateEndPoint(); + } + } + } + return _default; + } + + private static IEndPoint CreateEndPoint() + { + CoAPEndPoint ep = new CoAPEndPoint(0); + ep.Start(); + return ep; + } + } +} \ No newline at end of file diff --git a/WorldDirect.CoAP/LinkFormat.cs b/WorldDirect.CoAP/LinkFormat.cs index 96e9896..ae79158 100644 --- a/WorldDirect.CoAP/LinkFormat.cs +++ b/WorldDirect.CoAP/LinkFormat.cs @@ -36,7 +36,7 @@ public static class LinkFormat /// public static readonly String InterfaceDescription = "if"; /// - /// Name of the attribute Content Type + /// Name of the attribute Value Type /// public static readonly String ContentType = "ct"; /// diff --git a/WorldDirect.CoAP/Message.cs b/WorldDirect.CoAP/Message.cs index 7a252c0..1cbb6ea 100644 --- a/WorldDirect.CoAP/Message.cs +++ b/WorldDirect.CoAP/Message.cs @@ -552,7 +552,7 @@ public Message AddIfMatch(Byte[] opaque) if (opaque == null) throw ThrowHelper.ArgumentNull("opaque"); if (opaque.Length > 8) - throw ThrowHelper.Argument("opaque", "Content of If-Match option is too large: " + ByteArrayUtils.ToHexString(opaque)); + throw ThrowHelper.Argument("opaque", "Value of If-Match option is too large: " + ByteArrayUtils.ToHexString(opaque)); return AddOption(Option.Create(OptionType.IfMatch, opaque)); } diff --git a/WorldDirect.CoAP/MessageEventArgs.cs b/WorldDirect.CoAP/MessageEventArgs.cs new file mode 100644 index 0000000..ec71551 --- /dev/null +++ b/WorldDirect.CoAP/MessageEventArgs.cs @@ -0,0 +1,29 @@ +namespace WorldDirect.CoAP +{ + using System; + + /// + /// Represents an event of a message. + /// + /// the type of the message + public class MessageEventArgs : EventArgs where T : Message + { + readonly T _message; + + /// + /// + /// + public MessageEventArgs(T message) + { + _message = message; + } + + /// + /// Gets the message. + /// + public T Message + { + get { return _message; } + } + } +} \ No newline at end of file diff --git a/WorldDirect.CoAP/Method.cs b/WorldDirect.CoAP/Method.cs new file mode 100644 index 0000000..ea4e18e --- /dev/null +++ b/WorldDirect.CoAP/Method.cs @@ -0,0 +1,25 @@ +namespace WorldDirect.CoAP +{ + /// + /// Methods of request + /// + public enum Method + { + /// + /// GET method + /// + GET = 1, + /// + /// POST method + /// + POST = 2, + /// + /// PUT method + /// + PUT = 3, + /// + /// DELETE method + /// + DELETE = 4 + } +} \ No newline at end of file diff --git a/WorldDirect.CoAP/Net/CoapClient.cs b/WorldDirect.CoAP/Net/CoapClient.cs new file mode 100644 index 0000000..4905021 --- /dev/null +++ b/WorldDirect.CoAP/Net/CoapClient.cs @@ -0,0 +1,248 @@ +namespace WorldDirect.CoAP.Net +{ + using System; + using System.Buffers; + using System.Collections; + using System.Collections.Generic; + using System.Linq; + using System.Net; + using System.Net.Sockets; + using System.Reflection; + using System.Runtime.CompilerServices; + using System.Text; + using System.Threading; + using System.Threading.Tasks; + + public class CoapClient + { + private readonly UdpClient listener; + + public CoapClient(string hostname, int port) + { + this.listener = new UdpClient(hostname, port); + } + + public CoapClient(IPEndPoint endpoint) + { + this.listener = new UdpClient(endpoint); + } + + public async Task SendAsync(CoapRequestMessage request, CancellationToken ct) + { + var content = request.Content.Value.ToArray(); + var sendBytes = await this.listener.SendAsync(content, content.Length).ConfigureAwait(false); + if (sendBytes < 1) + { + throw new ArgumentException($"No bytes sent to {this.listener.Client.RemoteEndPoint}.", nameof(sendBytes)); + } + + var result = await this.listener.ReceiveAsync().ConfigureAwait(false); + var response = new CoapResponseMessage(result.Buffer); + return response; + } + } + + public class CoapMessage + { + + } + + public class CoapRequestMessage + { + public CoapContent Content { get; } + } + + public class CoapContent + { + private Memory value; + + public CoapContent(byte[] value) + { + this.value = new Memory(value); + } + + public ReadOnlyMemory Value => this.value; + } + + public class CoapResponseMessage + { + + public CoapContent Content { get; } + + public CoapCode Code => CoapCode.Parse(this.Content.Value.Span.Slice(1, 1)); + + public int Version + { + get + { + var x = this.Content.Value.Span.Slice(0, 1); + var y = Encoding.UTF8.GetString(x.ToArray(), 0, 2); + + return 1; + } + } + } + + public abstract class CoapCode + { + private readonly ushort @class; + private readonly ushort detail; + + protected CoapCode(ushort @class, ushort detail) + { + this.@class = @class; + this.detail = detail; + } + + public static CoapCode Parse(string value) + { + ushort @class; + try + { + @class = ushort.Parse(value.Substring(0, 1)); + } + catch (OverflowException) + { + throw new ArgumentOutOfRangeException(nameof(value), value, $"Class value of {value} is not allowed to be negative."); + } + + if (@class > 7) + { + throw new ArgumentOutOfRangeException(nameof(@class), @class, "Class value is only allowed to be between 0 and 7."); + } + + ushort detail; + try + { + detail = ushort.Parse(value.Substring(2, 2)); + } + catch (OverflowException) + { + throw new ArgumentOutOfRangeException(nameof(value), value, $"Detail value of {value} is not allowed to be negative."); + } + + if (detail > 31) + { + throw new ArgumentOutOfRangeException(nameof(detail), detail, "Detail value is only allowed to be between 0 and 31."); + } + + if (@class == 0) + { + return new RequestCode(detail); + } + + if (@class >= 2 && @class <= 5) + { + return new ResponseCode(detail); + } + } + + public CoapCode Parse(ReadOnlySpan value) + { + + } + + protected static void ValidateFormat(string value) + { + if (string.IsNullOrEmpty(value)) + { + throw new ArgumentNullException(nameof(value), "Value to parse is null or empty."); + } + + if (value.Length != 4) + { + throw new ArgumentOutOfRangeException(nameof(value), value, $"Expecting four characters, but found {value.Length} characters."); + } + + if (!char.IsDigit(value[0])) + { + throw new FormatException("Expecting first character to be a digit."); + } + + if (value[1] != '.') + { + throw new FormatException("Expecting second character to be a dot."); + } + + if (!char.IsDigit(value[2])) + { + throw new FormatException("Expecting third character to be a digit."); + } + + if (!char.IsDigit(value[3])) + { + throw new FormatException("Expecting last character to be a digit."); + } + } + + public ushort MinValue { get; protected set; } + + public ushort MaxValue { get; protected set; } + } + + public class RequestCode : CoapCode + { + public RequestCode(ushort detailValue) + : base(0, detailValue) + { + this.MinValue = 0; + this.MaxValue = 0; + } + } + + + public class ResponseCode : CoapCode + { + public ResponseCode(ushort @class, ushort detail) + : base(@class, detail) + { + if (@class < ResponseCode.MinValue || @class > ResponseCode.MaxValue) + { + + } + } + + public static ushort MinValue => 2; + + public static ushort MaxValue => 5; + } + + public abstract class Enumeration : IComparable + { + public string Name { get; private set; } + + public int Id { get; private set; } + + protected Enumeration(int id, string name) + { + Id = id; + Name = name; + } + + public override string ToString() => Name; + + public static IEnumerable GetAll() where T : Enumeration + { + var fields = typeof(T).GetFields(BindingFlags.Public | + BindingFlags.Static | + BindingFlags.DeclaredOnly); + + return fields.Select(f => f.GetValue(null)).Cast(); + } + + public override bool Equals(object obj) + { + var otherValue = obj as Enumeration; + + if (otherValue == null) + return false; + + var typeMatches = GetType().Equals(obj.GetType()); + var valueMatches = Id.Equals(otherValue.Id); + + return typeMatches && valueMatches; + } + + public int CompareTo(object other) => Id.CompareTo(((Enumeration)other).Id); + } +} diff --git a/WorldDirect.CoAP/Net/Exchange.cs b/WorldDirect.CoAP/Net/Exchange.cs index e06b521..0728197 100644 --- a/WorldDirect.CoAP/Net/Exchange.cs +++ b/WorldDirect.CoAP/Net/Exchange.cs @@ -367,13 +367,4 @@ public override String ToString() } } } - - /// - /// The origin of an exchange. - /// - public enum Origin - { - Local, - Remote - } } diff --git a/WorldDirect.CoAP/Net/Origin.cs b/WorldDirect.CoAP/Net/Origin.cs new file mode 100644 index 0000000..d0de2d0 --- /dev/null +++ b/WorldDirect.CoAP/Net/Origin.cs @@ -0,0 +1,11 @@ +namespace WorldDirect.CoAP.Net +{ + /// + /// The origin of an exchange. + /// + public enum Origin + { + Local, + Remote + } +} \ No newline at end of file diff --git a/WorldDirect.CoAP/Option.cs b/WorldDirect.CoAP/Option.cs index 62be6ec..756c53f 100644 --- a/WorldDirect.CoAP/Option.cs +++ b/WorldDirect.CoAP/Option.cs @@ -342,7 +342,7 @@ public static String ToString(OptionType type) case OptionType.Reserved: return "Reserved"; case OptionType.ContentFormat: - return "Content-Format"; + return "Value-Format"; case OptionType.MaxAge: return "Max-Age"; case OptionType.ProxyUri: diff --git a/WorldDirect.CoAP/OptionFormat.cs b/WorldDirect.CoAP/OptionFormat.cs new file mode 100644 index 0000000..d274042 --- /dev/null +++ b/WorldDirect.CoAP/OptionFormat.cs @@ -0,0 +1,13 @@ +namespace WorldDirect.CoAP +{ + /// + /// CoAP option formats + /// + public enum OptionFormat + { + Integer, + String, + Opaque, + Unknown + } +} \ No newline at end of file diff --git a/WorldDirect.CoAP/OptionType.cs b/WorldDirect.CoAP/OptionType.cs index 5273e11..66b2fc7 100644 --- a/WorldDirect.CoAP/OptionType.cs +++ b/WorldDirect.CoAP/OptionType.cs @@ -150,15 +150,4 @@ public enum OptionType [System.Obsolete] FencepostDivisor = 114, } - - /// - /// CoAP option formats - /// - public enum OptionFormat - { - Integer, - String, - Opaque, - Unknown - } } diff --git a/WorldDirect.CoAP/ResponseEventArgs.cs b/WorldDirect.CoAP/ResponseEventArgs.cs index deba944..3c38cfd 100644 --- a/WorldDirect.CoAP/ResponseEventArgs.cs +++ b/WorldDirect.CoAP/ResponseEventArgs.cs @@ -11,33 +11,6 @@ namespace WorldDirect.CoAP { - using System; - - /// - /// Represents an event of a message. - /// - /// the type of the message - public class MessageEventArgs : EventArgs where T : Message - { - readonly T _message; - - /// - /// - /// - public MessageEventArgs(T message) - { - _message = message; - } - - /// - /// Gets the message. - /// - public T Message - { - get { return _message; } - } - } - /// /// Represents an event when a response arrives for a request. /// diff --git a/WorldDirect.CoAP/Server/Resources/CoapExchange.cs b/WorldDirect.CoAP/Server/Resources/CoapExchange.cs index 6cdbf6a..5a83c58 100644 --- a/WorldDirect.CoAP/Server/Resources/CoapExchange.cs +++ b/WorldDirect.CoAP/Server/Resources/CoapExchange.cs @@ -108,7 +108,7 @@ public void Respond(StatusCode code) } /// - /// Responds with code 2.05 (Content) and the specified payload. + /// Responds with code 2.05 (Value) and the specified payload. /// public void Respond(String payload) { diff --git a/WorldDirect.CoAP/Spec.cs b/WorldDirect.CoAP/Spec.cs index 359305c..0fcbfe9 100644 --- a/WorldDirect.CoAP/Spec.cs +++ b/WorldDirect.CoAP/Spec.cs @@ -1842,34 +1842,5 @@ public static IEndPoint CreateEndPoint(ISpec spec) } } #else - namespace Net - { - partial class EndPointManager - { - private static IEndPoint _default; - - private static IEndPoint GetDefaultEndPoint() - { - if (_default == null) - { - lock (typeof(EndPointManager)) - { - if (_default == null) - { - _default = CreateEndPoint(); - } - } - } - return _default; - } - - private static IEndPoint CreateEndPoint() - { - CoAPEndPoint ep = new CoAPEndPoint(0); - ep.Start(); - return ep; - } - } - } #endif } diff --git a/WorldDirect.CoAP/Stack/BlockwiseStatus.cs b/WorldDirect.CoAP/Stack/BlockwiseStatus.cs index dc6cb47..40df7bb 100644 --- a/WorldDirect.CoAP/Stack/BlockwiseStatus.cs +++ b/WorldDirect.CoAP/Stack/BlockwiseStatus.cs @@ -75,7 +75,7 @@ public Boolean IsRandomAccess } /// - /// Gets the initial Content-Format, which must stay the same for the whole transfer. + /// Gets the initial Value-Format, which must stay the same for the whole transfer. /// public Int32 ContentFormat { diff --git a/WorldDirect.CoAP/Stack/Chain.cs b/WorldDirect.CoAP/Stack/Chain.cs index 8e30288..3cd44a6 100644 --- a/WorldDirect.CoAP/Stack/Chain.cs +++ b/WorldDirect.CoAP/Stack/Chain.cs @@ -15,141 +15,6 @@ namespace WorldDirect.CoAP.Stack using System.Collections.Generic; using System.Text; - /// - /// Represents a chain of filters. - /// - /// the type of filters - /// the type of next filters - public interface IChain - { - /// - /// Gets the with the specified in this chain. - /// - /// the filter's name we are looking for - /// the with the given name, or null if not found - IEntry GetEntry(String name); - /// - /// Gets the with the specified in this chain. - /// - /// the filter we are looking for - /// the , or null if not found - IEntry GetEntry(TFilter filter); - /// - /// Gets the with the specified in this chain. - /// - /// If there's more than one filter with the specified type, the first match will be chosen. - /// the type of filter we are looking for - /// the , or null if not found - IEntry GetEntry(Type filterType); - /// - /// Gets the with the specified in this chain. - /// - /// the filter's name - /// the , or null if not found - TFilter Get(String name); - /// - /// Gets the of the - /// with the specified in this chain. - /// - /// the filter's name - /// the , or null if not found - TNextFilter GetNextFilter(String name); - /// - /// Gets the of the - /// with the specified in this chain. - /// - /// the filter - /// the , or null if not found - TNextFilter GetNextFilter(TFilter filter); - /// - /// Gets the of the - /// with the specified in this chain. - /// - /// If there's more than one filter with the specified type, the first match will be chosen. - /// the type of filter - /// the , or null if not found - TNextFilter GetNextFilter(Type filterType); - /// - /// Gets all s in this chain. - /// - /// - IEnumerable> GetAll(); - /// - /// Checks if this chain contains a filter with the specified . - /// - /// the filter's name - /// true if this chain contains a filter with the specified - Boolean Contains(String name); - /// - /// Checks if this chain contains the specified . - /// - /// the filter - /// true if this chain contains the specified - Boolean Contains(TFilter filter); - /// - /// Checks if this chain contains a filter with the specified . - /// - /// the filter's type - /// true if this chain contains a filter with the specified - Boolean Contains(Type filterType); - /// - /// Adds the specified filter with the specified name at the beginning of this chain. - /// - /// the filter's name - /// the filter to add - void AddFirst(String name, TFilter filter); - /// - /// Adds the specified filter with the specified name at the end of this chain. - /// - /// the filter's name - /// the filter to add - void AddLast(String name, TFilter filter); - /// - /// Adds the specified filter with the specified name just before the filter whose name is - /// in this chain. - /// - /// the targeted filter's name - /// the filter's name - /// the filter to add - void AddBefore(String baseName, String name, TFilter filter); - /// - /// Adds the specified filter with the specified name just after the filter whose name is - /// in this chain. - /// - /// the targeted filter's name - /// the filter's name - /// the filter to add - void AddAfter(String baseName, String name, TFilter filter); - /// - /// Replace the filter with the specified name with the specified new filter. - /// - /// the name of the filter to replace - /// the new filter - /// the old filter - TFilter Replace(String name, TFilter newFilter); - /// - /// Replace the specified filter with the specified new filter. - /// - /// the filter to replace - /// the new filter - void Replace(TFilter oldFilter, TFilter newFilter); - /// - /// Removes the filter with the specified name from this chain. - /// - /// the name of the filter to remove - /// the removed filter - TFilter Remove(String name); - /// - /// Removes the specified filter. - /// - /// the filter to remove - void Remove(TFilter filter); - /// - /// Removes all filters added to this chain. - /// - void Clear(); - } - /// /// Abstract implementation of /// diff --git a/WorldDirect.CoAP/Stack/IChain.cs b/WorldDirect.CoAP/Stack/IChain.cs new file mode 100644 index 0000000..572177d --- /dev/null +++ b/WorldDirect.CoAP/Stack/IChain.cs @@ -0,0 +1,140 @@ +namespace WorldDirect.CoAP.Stack +{ + using System; + using System.Collections.Generic; + + /// + /// Represents a chain of filters. + /// + /// the type of filters + /// the type of next filters + public interface IChain + { + /// + /// Gets the with the specified in this chain. + /// + /// the filter's name we are looking for + /// the with the given name, or null if not found + IEntry GetEntry(String name); + /// + /// Gets the with the specified in this chain. + /// + /// the filter we are looking for + /// the , or null if not found + IEntry GetEntry(TFilter filter); + /// + /// Gets the with the specified in this chain. + /// + /// If there's more than one filter with the specified type, the first match will be chosen. + /// the type of filter we are looking for + /// the , or null if not found + IEntry GetEntry(Type filterType); + /// + /// Gets the with the specified in this chain. + /// + /// the filter's name + /// the , or null if not found + TFilter Get(String name); + /// + /// Gets the of the + /// with the specified in this chain. + /// + /// the filter's name + /// the , or null if not found + TNextFilter GetNextFilter(String name); + /// + /// Gets the of the + /// with the specified in this chain. + /// + /// the filter + /// the , or null if not found + TNextFilter GetNextFilter(TFilter filter); + /// + /// Gets the of the + /// with the specified in this chain. + /// + /// If there's more than one filter with the specified type, the first match will be chosen. + /// the type of filter + /// the , or null if not found + TNextFilter GetNextFilter(Type filterType); + /// + /// Gets all s in this chain. + /// + /// + IEnumerable> GetAll(); + /// + /// Checks if this chain contains a filter with the specified . + /// + /// the filter's name + /// true if this chain contains a filter with the specified + Boolean Contains(String name); + /// + /// Checks if this chain contains the specified . + /// + /// the filter + /// true if this chain contains the specified + Boolean Contains(TFilter filter); + /// + /// Checks if this chain contains a filter with the specified . + /// + /// the filter's type + /// true if this chain contains a filter with the specified + Boolean Contains(Type filterType); + /// + /// Adds the specified filter with the specified name at the beginning of this chain. + /// + /// the filter's name + /// the filter to add + void AddFirst(String name, TFilter filter); + /// + /// Adds the specified filter with the specified name at the end of this chain. + /// + /// the filter's name + /// the filter to add + void AddLast(String name, TFilter filter); + /// + /// Adds the specified filter with the specified name just before the filter whose name is + /// in this chain. + /// + /// the targeted filter's name + /// the filter's name + /// the filter to add + void AddBefore(String baseName, String name, TFilter filter); + /// + /// Adds the specified filter with the specified name just after the filter whose name is + /// in this chain. + /// + /// the targeted filter's name + /// the filter's name + /// the filter to add + void AddAfter(String baseName, String name, TFilter filter); + /// + /// Replace the filter with the specified name with the specified new filter. + /// + /// the name of the filter to replace + /// the new filter + /// the old filter + TFilter Replace(String name, TFilter newFilter); + /// + /// Replace the specified filter with the specified new filter. + /// + /// the filter to replace + /// the new filter + void Replace(TFilter oldFilter, TFilter newFilter); + /// + /// Removes the filter with the specified name from this chain. + /// + /// the name of the filter to remove + /// the removed filter + TFilter Remove(String name); + /// + /// Removes the specified filter. + /// + /// the filter to remove + void Remove(TFilter filter); + /// + /// Removes all filters added to this chain. + /// + void Clear(); + } +} \ No newline at end of file diff --git a/WorldDirect.CoAP/Stack/ILayer.cs b/WorldDirect.CoAP/Stack/ILayer.cs index 4c4131c..e9f0e69 100644 --- a/WorldDirect.CoAP/Stack/ILayer.cs +++ b/WorldDirect.CoAP/Stack/ILayer.cs @@ -66,35 +66,4 @@ public interface ILayer /// the empty message to receive void ReceiveEmptyMessage(INextLayer nextLayer, Exchange exchange, EmptyMessage message); } - - /// - /// Represent a next layer in the stack. - /// - public interface INextLayer - { - /// - /// Sends a request to next layer. - /// - void SendRequest(Exchange exchange, Request request); - /// - /// Sends a response to next layer. - /// - void SendResponse(Exchange exchange, Response response); - /// - /// Sends an empty message to next layer. - /// - void SendEmptyMessage(Exchange exchange, EmptyMessage message); - /// - /// Receives a request to next layer. - /// - void ReceiveRequest(Exchange exchange, Request request); - /// - /// Receives a response to next layer. - /// - void ReceiveResponse(Exchange exchange, Response response); - /// - /// Receives an empty message to next layer. - /// - void ReceiveEmptyMessage(Exchange exchange, EmptyMessage message); - } } diff --git a/WorldDirect.CoAP/Stack/INextLayer.cs b/WorldDirect.CoAP/Stack/INextLayer.cs new file mode 100644 index 0000000..b429827 --- /dev/null +++ b/WorldDirect.CoAP/Stack/INextLayer.cs @@ -0,0 +1,35 @@ +namespace WorldDirect.CoAP.Stack +{ + using Net; + + /// + /// Represent a next layer in the stack. + /// + public interface INextLayer + { + /// + /// Sends a request to next layer. + /// + void SendRequest(Exchange exchange, Request request); + /// + /// Sends a response to next layer. + /// + void SendResponse(Exchange exchange, Response response); + /// + /// Sends an empty message to next layer. + /// + void SendEmptyMessage(Exchange exchange, EmptyMessage message); + /// + /// Receives a request to next layer. + /// + void ReceiveRequest(Exchange exchange, Request request); + /// + /// Receives a response to next layer. + /// + void ReceiveResponse(Exchange exchange, Response response); + /// + /// Receives an empty message to next layer. + /// + void ReceiveEmptyMessage(Exchange exchange, EmptyMessage message); + } +} \ No newline at end of file diff --git a/WorldDirect.CoAP/StatusCode.cs b/WorldDirect.CoAP/StatusCode.cs new file mode 100644 index 0000000..8486f05 --- /dev/null +++ b/WorldDirect.CoAP/StatusCode.cs @@ -0,0 +1,101 @@ +namespace WorldDirect.CoAP +{ + /// + /// Response status codes. + /// + public enum StatusCode + { + /// + /// 2.01 Created + /// + Created = 65, + /// + /// 2.02 Deleted + /// + Deleted = 66, + /// + /// 2.03 Valid + /// + Valid = 67, + /// + /// 2.04 Changed + /// + Changed = 68, + /// + /// 2.05 Value + /// + Content = 69, + /// + /// 2.?? Continue + /// + Continue = 95, + /// + /// 4.00 Bad Request + /// + BadRequest = 128, + /// + /// 4.01 Unauthorized + /// + Unauthorized = 129, + /// + /// 4.02 Bad Option + /// + BadOption = 130, + /// + /// 4.03 Forbidden + /// + Forbidden = 131, + /// + /// 4.04 Not Found + /// + NotFound = 132, + /// + /// 4.05 Method Not Allowed + /// + MethodNotAllowed = 133, + /// + /// 4.06 Not Acceptable + /// + NotAcceptable = 134, + /// + /// 4.08 Request Entity Incomplete (draft-ietf-core-block) + /// + RequestEntityIncomplete = 136, + /// + /// + /// + PreconditionFailed = 140, + /// + /// 4.13 Request Entity Too Large + /// + RequestEntityTooLarge = 141, + /// + /// 4.15 Unsupported Media Type + /// + UnsupportedMediaType = 143, + /// + /// 5.00 Internal Server Error + /// + InternalServerError = 160, + /// + /// 5.01 Not Implemented + /// + NotImplemented = 161, + /// + /// 5.02 Bad Gateway + /// + BadGateway = 162, + /// + /// 5.03 Service Unavailable + /// + ServiceUnavailable = 163, + /// + /// 5.04 Gateway Timeout + /// + GatewayTimeout = 164, + /// + /// 5.05 Proxying Not Supported + /// + ProxyingNotSupported = 165 + } +} \ No newline at end of file diff --git a/WorldDirect.CoAP/Threading/Executors.NET40.cs b/WorldDirect.CoAP/Threading/Executors.cs similarity index 100% rename from WorldDirect.CoAP/Threading/Executors.NET40.cs rename to WorldDirect.CoAP/Threading/Executors.cs diff --git a/WorldDirect.CoAP/Util/Utils.cs b/WorldDirect.CoAP/Util/Utils.cs index 2daeace..3d62306 100644 --- a/WorldDirect.CoAP/Util/Utils.cs +++ b/WorldDirect.CoAP/Util/Utils.cs @@ -199,7 +199,7 @@ public static String OptionsToString(Message msg) appendIfNotNullOrEmpty(sb, "Location-Path", ToString(msg.LocationPaths)); appendIfNotNullOrEmpty(sb, "URI-Path", ToString(msg.UriPaths)); if (msg.ContentType != MediaType.Undefined) - appendIfNotNullOrEmpty(sb, "Content-Type", MediaType.ToString(msg.ContentType)); + appendIfNotNullOrEmpty(sb, "Value-Type", MediaType.ToString(msg.ContentType)); if (msg.HasOption(OptionType.MaxAge)) appendIfNotNullOrEmpty(sb, "Max-Age", msg.MaxAge.ToString()); appendIfNotNullOrEmpty(sb, "URI-Query", ToString(msg.UriQueries)); From 4b14abeb9195aa28d5cdb3826da7b982ab51d499 Mon Sep 17 00:00:00 2001 From: Philip Wille Date: Fri, 20 Nov 2020 03:00:55 +0100 Subject: [PATCH 07/32] Partial rework of CoapClient --- WorldDirect.CoAP.Example.Client/Program.cs | 5 + WorldDirect.CoAP/Net/CoapClient.cs | 644 +++++++++++++++--- WorldDirect.CoAP/Server/Resources/Resource.cs | 2 +- WorldDirect.CoAP/Stack/ObserveLayer.cs | 2 +- WorldDirect.CoAP/WorldDirect.CoAP.csproj | 1 + 5 files changed, 571 insertions(+), 83 deletions(-) diff --git a/WorldDirect.CoAP.Example.Client/Program.cs b/WorldDirect.CoAP.Example.Client/Program.cs index 97a04f5..a0e6af1 100644 --- a/WorldDirect.CoAP.Example.Client/Program.cs +++ b/WorldDirect.CoAP.Example.Client/Program.cs @@ -6,8 +6,10 @@ namespace WorldDirect.CoAP.Example.Client using System.ComponentModel.Design; using System.IO; using System.Linq; + using System.Net; using System.Net.Http.Headers; using System.Text; + using System.Threading; using System.Threading.Tasks; using CommandLine; using Util; @@ -23,6 +25,9 @@ public static async Task Main(string[] args) with.HelpWriter = Console.Out; }); + var client = new WorldDirect.CoAP.Net.CoapClient(new IPEndPoint(IPAddress.Any, 5000)); + await client.SendAsync(CancellationToken.None).ConfigureAwait(false); + var arguments = parser.ParseArguments(args); arguments.WithParsed(a => diff --git a/WorldDirect.CoAP/Net/CoapClient.cs b/WorldDirect.CoAP/Net/CoapClient.cs index 4905021..16741ef 100644 --- a/WorldDirect.CoAP/Net/CoapClient.cs +++ b/WorldDirect.CoAP/Net/CoapClient.cs @@ -12,6 +12,7 @@ using System.Text; using System.Threading; using System.Threading.Tasks; + using System.Threading.Tasks.Dataflow; public class CoapClient { @@ -27,9 +28,9 @@ public CoapClient(IPEndPoint endpoint) this.listener = new UdpClient(endpoint); } - public async Task SendAsync(CoapRequestMessage request, CancellationToken ct) + public async Task SendAsync(CoapRequestMessage message, CancellationToken ct) { - var content = request.Content.Value.ToArray(); + var content = message.GetBytes().ToArray(); var sendBytes = await this.listener.SendAsync(content, content.Length).ConfigureAwait(false); if (sendBytes < 1) { @@ -37,174 +38,627 @@ public async Task SendAsync(CoapRequestMessage request, Can } var result = await this.listener.ReceiveAsync().ConfigureAwait(false); - var response = new CoapResponseMessage(result.Buffer); + var diagram = new UdpDatagram(result); + var response = new CoapRequestMessage(diagram); + return response; } } - public class CoapMessage + public class CoapHeader + { + public CoapHeader(ReadOnlySpan bytes) + { + if (bytes.Length != 4) + { + throw new ArgumentOutOfRangeException(); + } + + this.Version = (Net.Version)bytes[0]; + this.Type = (Net.Type)bytes[0]; + this.TokenLength = (Net.TokenLength)bytes[0]; + this.Code = Net.Code.Parse(bytes[1]); + this.MessageId = (Net.MessageId)new[] {bytes[2], bytes[3]}; + } + + public Version Version { get; } + + public Type Type { get; } + + public TokenLength TokenLength { get; } + + public Code Code { get; } + + public MessageId MessageId { get; } + + public IEnumerable GetBytes() + { + yield return this.Version; + yield return this.Type; + yield return this.TokenLength; + yield return this.Code; + foreach (var b in (byte[])this.MessageId) + { + yield return b; + } + } + } + + public class CoapRequestMessage : CoapMessage + { + public CoapRequestMessage(UdpDatagram datagram) + : base(datagram) + { + } + } + + public abstract class CoapMessage : IDisposable + { + private readonly IMemoryOwner owner = MemoryPool.Shared.Rent(5); + private readonly Memory memory; + private readonly IEnumerable tokens; + private readonly IEnumerable