From 2db5872cf89175ba2700082272da5d8c939e59ee Mon Sep 17 00:00:00 2001 From: Philip Wille Date: Fri, 9 Oct 2020 12:45:46 +0200 Subject: [PATCH] Basic project structure --- EnergySoultions.CoAP.sln | 70 +- WorldDirect.CoAP.Example.Client/Program.cs | 12 + .../WorldDirect.CoAP.Example.Client.csproj | 8 + WorldDirect.CoAP.Example.Server/Program.cs | 12 + .../WorldDirect.CoAP.Example.Server.csproj | 8 + WorldDirect.CoAP.Specs/UnitTest1.cs | 14 + .../WorldDirect.CoAP.Specs.csproj | 16 + WorldDirect.CoAP/BlockOption.cs | 140 ++ .../Channel/DataReceivedEventArgs.cs | 42 + WorldDirect.CoAP/Channel/IChannel.cs | 44 + .../Channel/IPAddressExtensions.cs | 69 + WorldDirect.CoAP/Channel/UDPChannel.NET40.cs | 201 ++ WorldDirect.CoAP/Channel/UDPChannel.cs | 338 +++ WorldDirect.CoAP/Class1.cs | 8 + WorldDirect.CoAP/CoapClient.cs | 677 ++++++ WorldDirect.CoAP/CoapConfig.cs | 518 +++++ WorldDirect.CoAP/CoapConstants.cs | 95 + WorldDirect.CoAP/CoapObserveRelation.cs | 98 + WorldDirect.CoAP/Code.cs | 400 ++++ WorldDirect.CoAP/Codec/DatagramReader.cs | 135 ++ WorldDirect.CoAP/Codec/DatagramWriter.cs | 129 ++ WorldDirect.CoAP/Codec/IMessageDecoder.cs | 70 + WorldDirect.CoAP/Codec/IMessageEncoder.cs | 49 + WorldDirect.CoAP/Codec/MessageDecoder.cs | 159 ++ WorldDirect.CoAP/Codec/MessageEncoder.cs | 66 + .../Deduplication/CropRotation.cs | 106 + .../Deduplication/DeduplicatorFactory.cs | 42 + .../Deduplication/IDeduplicator.cs | 45 + .../Deduplication/NoopDeduplicator.cs | 51 + .../Deduplication/SweepDeduplicator.cs | 106 + WorldDirect.CoAP/EmptyMessage.cs | 55 + .../EndPoint/Resources/RemoteResource.cs | 40 + .../EndPoint/Resources/Resource.cs | 523 +++++ WorldDirect.CoAP/ICoapConfig.cs | 76 + WorldDirect.CoAP/LinkAttribute.cs | 128 ++ WorldDirect.CoAP/LinkFormat.cs | 494 +++++ WorldDirect.CoAP/MediaType.cs | 268 +++ WorldDirect.CoAP/Message.cs | 1193 +++++++++++ WorldDirect.CoAP/MessageType.cs | 40 + .../Net/ClientMessageDeliverer.cs | 37 + WorldDirect.CoAP/Net/CoAPEndPoint.cs | 435 ++++ WorldDirect.CoAP/Net/EndPointManager.cs | 24 + WorldDirect.CoAP/Net/Exchange.cs | 379 ++++ WorldDirect.CoAP/Net/IEndPoint.cs | 89 + WorldDirect.CoAP/Net/IMatcher.cs | 26 + WorldDirect.CoAP/Net/IMessageDeliverer.cs | 31 + WorldDirect.CoAP/Net/IOutbox.cs | 29 + WorldDirect.CoAP/Net/Matcher.cs | 457 ++++ WorldDirect.CoAP/Observe/ObserveManager.cs | 66 + .../Observe/ObserveNotificationOrderer.cs | 97 + WorldDirect.CoAP/Observe/ObserveRelation.cs | 180 ++ WorldDirect.CoAP/Observe/ObservingEndpoint.cs | 80 + .../Observe/ReregisterEventArgs.cs | 39 + WorldDirect.CoAP/Option.cs | 620 ++++++ WorldDirect.CoAP/OptionType.cs | 164 ++ WorldDirect.CoAP/README | 37 + WorldDirect.CoAP/Request.cs | 391 ++++ WorldDirect.CoAP/Response.cs | 83 + WorldDirect.CoAP/ResponseEventArgs.cs | 61 + WorldDirect.CoAP/Server/CoapServer.cs | 226 ++ WorldDirect.CoAP/Server/IServer.cs | 76 + .../Server/Resources/CoapExchange.cs | 182 ++ .../Server/Resources/DiscoveryResource.cs | 48 + .../Server/Resources/IResource.cs | 107 + WorldDirect.CoAP/Server/Resources/Resource.cs | 486 +++++ .../Server/Resources/ResourceAttributes.cs | 253 +++ .../Server/Resources/TimerResource.cs | 45 + .../Server/ServerMessageDeliverer.cs | 122 ++ WorldDirect.CoAP/Spec.cs | 1875 +++++++++++++++++ WorldDirect.CoAP/Stack/AbstractLayer.cs | 67 + WorldDirect.CoAP/Stack/BlockwiseLayer.cs | 672 ++++++ WorldDirect.CoAP/Stack/BlockwiseStatus.cs | 132 ++ WorldDirect.CoAP/Stack/Chain.cs | 617 ++++++ WorldDirect.CoAP/Stack/CoapStack.cs | 48 + WorldDirect.CoAP/Stack/IEntry.cs | 50 + WorldDirect.CoAP/Stack/ILayer.cs | 100 + WorldDirect.CoAP/Stack/LayerStack.cs | 192 ++ WorldDirect.CoAP/Stack/ObserveLayer.cs | 322 +++ WorldDirect.CoAP/Stack/ReliabilityLayer.cs | 376 ++++ WorldDirect.CoAP/Stack/TokenLayer.cs | 78 + WorldDirect.CoAP/Threading/Executors.NET40.cs | 26 + WorldDirect.CoAP/Threading/IExecutor.cs | 44 + .../Threading/NoThreadingExecutor.cs | 37 + WorldDirect.CoAP/Threading/TaskExecutor.cs | 34 + .../Threading/ThreadPoolExecutor.cs | 34 + .../Util/ArrayEqualityComparer.cs | 88 + WorldDirect.CoAP/Util/ByteArrayUtils.cs | 108 + WorldDirect.CoAP/Util/Delegates.cs | 24 + WorldDirect.CoAP/Util/Scanner.cs | 48 + .../SynchronizedCollection.cs | 243 +++ WorldDirect.CoAP/Util/ThrowHelper.cs | 28 + WorldDirect.CoAP/Util/Utils.cs | 266 +++ WorldDirect.CoAP/Util/WaitFuture.cs | 103 + WorldDirect.CoAP/WebLink.cs | 76 + WorldDirect.CoAP/WorldDirect.CoAP.csproj | 22 + 95 files changed, 17111 insertions(+), 14 deletions(-) create mode 100644 WorldDirect.CoAP.Example.Client/Program.cs create mode 100644 WorldDirect.CoAP.Example.Client/WorldDirect.CoAP.Example.Client.csproj create mode 100644 WorldDirect.CoAP.Example.Server/Program.cs create mode 100644 WorldDirect.CoAP.Example.Server/WorldDirect.CoAP.Example.Server.csproj create mode 100644 WorldDirect.CoAP.Specs/UnitTest1.cs create mode 100644 WorldDirect.CoAP.Specs/WorldDirect.CoAP.Specs.csproj create mode 100644 WorldDirect.CoAP/BlockOption.cs create mode 100644 WorldDirect.CoAP/Channel/DataReceivedEventArgs.cs create mode 100644 WorldDirect.CoAP/Channel/IChannel.cs create mode 100644 WorldDirect.CoAP/Channel/IPAddressExtensions.cs create mode 100644 WorldDirect.CoAP/Channel/UDPChannel.NET40.cs create mode 100644 WorldDirect.CoAP/Channel/UDPChannel.cs create mode 100644 WorldDirect.CoAP/Class1.cs create mode 100644 WorldDirect.CoAP/CoapClient.cs create mode 100644 WorldDirect.CoAP/CoapConfig.cs create mode 100644 WorldDirect.CoAP/CoapConstants.cs create mode 100644 WorldDirect.CoAP/CoapObserveRelation.cs create mode 100644 WorldDirect.CoAP/Code.cs create mode 100644 WorldDirect.CoAP/Codec/DatagramReader.cs create mode 100644 WorldDirect.CoAP/Codec/DatagramWriter.cs create mode 100644 WorldDirect.CoAP/Codec/IMessageDecoder.cs create mode 100644 WorldDirect.CoAP/Codec/IMessageEncoder.cs create mode 100644 WorldDirect.CoAP/Codec/MessageDecoder.cs create mode 100644 WorldDirect.CoAP/Codec/MessageEncoder.cs create mode 100644 WorldDirect.CoAP/Deduplication/CropRotation.cs create mode 100644 WorldDirect.CoAP/Deduplication/DeduplicatorFactory.cs create mode 100644 WorldDirect.CoAP/Deduplication/IDeduplicator.cs create mode 100644 WorldDirect.CoAP/Deduplication/NoopDeduplicator.cs create mode 100644 WorldDirect.CoAP/Deduplication/SweepDeduplicator.cs create mode 100644 WorldDirect.CoAP/EmptyMessage.cs create mode 100644 WorldDirect.CoAP/EndPoint/Resources/RemoteResource.cs create mode 100644 WorldDirect.CoAP/EndPoint/Resources/Resource.cs create mode 100644 WorldDirect.CoAP/ICoapConfig.cs create mode 100644 WorldDirect.CoAP/LinkAttribute.cs create mode 100644 WorldDirect.CoAP/LinkFormat.cs create mode 100644 WorldDirect.CoAP/MediaType.cs create mode 100644 WorldDirect.CoAP/Message.cs create mode 100644 WorldDirect.CoAP/MessageType.cs create mode 100644 WorldDirect.CoAP/Net/ClientMessageDeliverer.cs create mode 100644 WorldDirect.CoAP/Net/CoAPEndPoint.cs create mode 100644 WorldDirect.CoAP/Net/EndPointManager.cs create mode 100644 WorldDirect.CoAP/Net/Exchange.cs create mode 100644 WorldDirect.CoAP/Net/IEndPoint.cs create mode 100644 WorldDirect.CoAP/Net/IMatcher.cs create mode 100644 WorldDirect.CoAP/Net/IMessageDeliverer.cs create mode 100644 WorldDirect.CoAP/Net/IOutbox.cs create mode 100644 WorldDirect.CoAP/Net/Matcher.cs create mode 100644 WorldDirect.CoAP/Observe/ObserveManager.cs create mode 100644 WorldDirect.CoAP/Observe/ObserveNotificationOrderer.cs create mode 100644 WorldDirect.CoAP/Observe/ObserveRelation.cs create mode 100644 WorldDirect.CoAP/Observe/ObservingEndpoint.cs create mode 100644 WorldDirect.CoAP/Observe/ReregisterEventArgs.cs create mode 100644 WorldDirect.CoAP/Option.cs create mode 100644 WorldDirect.CoAP/OptionType.cs create mode 100644 WorldDirect.CoAP/README create mode 100644 WorldDirect.CoAP/Request.cs create mode 100644 WorldDirect.CoAP/Response.cs create mode 100644 WorldDirect.CoAP/ResponseEventArgs.cs create mode 100644 WorldDirect.CoAP/Server/CoapServer.cs create mode 100644 WorldDirect.CoAP/Server/IServer.cs create mode 100644 WorldDirect.CoAP/Server/Resources/CoapExchange.cs create mode 100644 WorldDirect.CoAP/Server/Resources/DiscoveryResource.cs create mode 100644 WorldDirect.CoAP/Server/Resources/IResource.cs create mode 100644 WorldDirect.CoAP/Server/Resources/Resource.cs create mode 100644 WorldDirect.CoAP/Server/Resources/ResourceAttributes.cs create mode 100644 WorldDirect.CoAP/Server/Resources/TimerResource.cs create mode 100644 WorldDirect.CoAP/Server/ServerMessageDeliverer.cs create mode 100644 WorldDirect.CoAP/Spec.cs create mode 100644 WorldDirect.CoAP/Stack/AbstractLayer.cs create mode 100644 WorldDirect.CoAP/Stack/BlockwiseLayer.cs create mode 100644 WorldDirect.CoAP/Stack/BlockwiseStatus.cs create mode 100644 WorldDirect.CoAP/Stack/Chain.cs create mode 100644 WorldDirect.CoAP/Stack/CoapStack.cs create mode 100644 WorldDirect.CoAP/Stack/IEntry.cs create mode 100644 WorldDirect.CoAP/Stack/ILayer.cs create mode 100644 WorldDirect.CoAP/Stack/LayerStack.cs create mode 100644 WorldDirect.CoAP/Stack/ObserveLayer.cs create mode 100644 WorldDirect.CoAP/Stack/ReliabilityLayer.cs create mode 100644 WorldDirect.CoAP/Stack/TokenLayer.cs create mode 100644 WorldDirect.CoAP/Threading/Executors.NET40.cs create mode 100644 WorldDirect.CoAP/Threading/IExecutor.cs create mode 100644 WorldDirect.CoAP/Threading/NoThreadingExecutor.cs create mode 100644 WorldDirect.CoAP/Threading/TaskExecutor.cs create mode 100644 WorldDirect.CoAP/Threading/ThreadPoolExecutor.cs create mode 100644 WorldDirect.CoAP/Util/ArrayEqualityComparer.cs create mode 100644 WorldDirect.CoAP/Util/ByteArrayUtils.cs create mode 100644 WorldDirect.CoAP/Util/Delegates.cs create mode 100644 WorldDirect.CoAP/Util/Scanner.cs create mode 100644 WorldDirect.CoAP/Util/System.Collections.Generic/SynchronizedCollection.cs create mode 100644 WorldDirect.CoAP/Util/ThrowHelper.cs create mode 100644 WorldDirect.CoAP/Util/Utils.cs create mode 100644 WorldDirect.CoAP/Util/WaitFuture.cs create mode 100644 WorldDirect.CoAP/WebLink.cs create mode 100644 WorldDirect.CoAP/WorldDirect.CoAP.csproj diff --git a/EnergySoultions.CoAP.sln b/EnergySoultions.CoAP.sln index d43f542..b0c44eb 100644 --- a/EnergySoultions.CoAP.sln +++ b/EnergySoultions.CoAP.sln @@ -3,8 +3,6 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 16 VisualStudioVersion = 16.0.30204.135 MinimumVisualStudioVersion = 15.0.26124.0 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EnergySoultions.CoAP", "EnergySoultions.CoAP\EnergySoultions.CoAP.csproj", "{FECBAFE4-FCD9-4E4C-95B2-EFF7B974CA3A}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{C2499C75-EDBE-4552-B855-52E2B12BC4E4}" ProjectSection(SolutionItems) = preProject .editorconfig = .editorconfig @@ -14,6 +12,14 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution stylecop.json = stylecop.json EndProjectSection EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WorldDirect.CoAP", "WorldDirect.CoAP\WorldDirect.CoAP.csproj", "{60BC961D-1A78-4203-9EFB-A7E616170CC5}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WorldDirect.CoAP.Specs", "WorldDirect.CoAP.Specs\WorldDirect.CoAP.Specs.csproj", "{9832AE15-FBC3-4393-A6B8-365EAA84211A}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WorldDirect.CoAP.Example.Client", "WorldDirect.CoAP.Example.Client\WorldDirect.CoAP.Example.Client.csproj", "{707A3F6A-961B-4B37-98A8-B7371087F1A9}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WorldDirect.CoAP.Example.Server", "WorldDirect.CoAP.Example.Server\WorldDirect.CoAP.Example.Server.csproj", "{3EF4EA32-6F5B-4B2E-83BD-08D670D5A361}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -24,18 +30,54 @@ Global Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {FECBAFE4-FCD9-4E4C-95B2-EFF7B974CA3A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {FECBAFE4-FCD9-4E4C-95B2-EFF7B974CA3A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {FECBAFE4-FCD9-4E4C-95B2-EFF7B974CA3A}.Debug|x64.ActiveCfg = Debug|Any CPU - {FECBAFE4-FCD9-4E4C-95B2-EFF7B974CA3A}.Debug|x64.Build.0 = Debug|Any CPU - {FECBAFE4-FCD9-4E4C-95B2-EFF7B974CA3A}.Debug|x86.ActiveCfg = Debug|Any CPU - {FECBAFE4-FCD9-4E4C-95B2-EFF7B974CA3A}.Debug|x86.Build.0 = Debug|Any CPU - {FECBAFE4-FCD9-4E4C-95B2-EFF7B974CA3A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {FECBAFE4-FCD9-4E4C-95B2-EFF7B974CA3A}.Release|Any CPU.Build.0 = Release|Any CPU - {FECBAFE4-FCD9-4E4C-95B2-EFF7B974CA3A}.Release|x64.ActiveCfg = Release|Any CPU - {FECBAFE4-FCD9-4E4C-95B2-EFF7B974CA3A}.Release|x64.Build.0 = Release|Any CPU - {FECBAFE4-FCD9-4E4C-95B2-EFF7B974CA3A}.Release|x86.ActiveCfg = Release|Any CPU - {FECBAFE4-FCD9-4E4C-95B2-EFF7B974CA3A}.Release|x86.Build.0 = Release|Any CPU + {60BC961D-1A78-4203-9EFB-A7E616170CC5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {60BC961D-1A78-4203-9EFB-A7E616170CC5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {60BC961D-1A78-4203-9EFB-A7E616170CC5}.Debug|x64.ActiveCfg = Debug|Any CPU + {60BC961D-1A78-4203-9EFB-A7E616170CC5}.Debug|x64.Build.0 = Debug|Any CPU + {60BC961D-1A78-4203-9EFB-A7E616170CC5}.Debug|x86.ActiveCfg = Debug|Any CPU + {60BC961D-1A78-4203-9EFB-A7E616170CC5}.Debug|x86.Build.0 = Debug|Any CPU + {60BC961D-1A78-4203-9EFB-A7E616170CC5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {60BC961D-1A78-4203-9EFB-A7E616170CC5}.Release|Any CPU.Build.0 = Release|Any CPU + {60BC961D-1A78-4203-9EFB-A7E616170CC5}.Release|x64.ActiveCfg = Release|Any CPU + {60BC961D-1A78-4203-9EFB-A7E616170CC5}.Release|x64.Build.0 = Release|Any CPU + {60BC961D-1A78-4203-9EFB-A7E616170CC5}.Release|x86.ActiveCfg = Release|Any CPU + {60BC961D-1A78-4203-9EFB-A7E616170CC5}.Release|x86.Build.0 = Release|Any CPU + {9832AE15-FBC3-4393-A6B8-365EAA84211A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9832AE15-FBC3-4393-A6B8-365EAA84211A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9832AE15-FBC3-4393-A6B8-365EAA84211A}.Debug|x64.ActiveCfg = Debug|Any CPU + {9832AE15-FBC3-4393-A6B8-365EAA84211A}.Debug|x64.Build.0 = Debug|Any CPU + {9832AE15-FBC3-4393-A6B8-365EAA84211A}.Debug|x86.ActiveCfg = Debug|Any CPU + {9832AE15-FBC3-4393-A6B8-365EAA84211A}.Debug|x86.Build.0 = Debug|Any CPU + {9832AE15-FBC3-4393-A6B8-365EAA84211A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9832AE15-FBC3-4393-A6B8-365EAA84211A}.Release|Any CPU.Build.0 = Release|Any CPU + {9832AE15-FBC3-4393-A6B8-365EAA84211A}.Release|x64.ActiveCfg = Release|Any CPU + {9832AE15-FBC3-4393-A6B8-365EAA84211A}.Release|x64.Build.0 = Release|Any CPU + {9832AE15-FBC3-4393-A6B8-365EAA84211A}.Release|x86.ActiveCfg = Release|Any CPU + {9832AE15-FBC3-4393-A6B8-365EAA84211A}.Release|x86.Build.0 = Release|Any CPU + {707A3F6A-961B-4B37-98A8-B7371087F1A9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {707A3F6A-961B-4B37-98A8-B7371087F1A9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {707A3F6A-961B-4B37-98A8-B7371087F1A9}.Debug|x64.ActiveCfg = Debug|Any CPU + {707A3F6A-961B-4B37-98A8-B7371087F1A9}.Debug|x64.Build.0 = Debug|Any CPU + {707A3F6A-961B-4B37-98A8-B7371087F1A9}.Debug|x86.ActiveCfg = Debug|Any CPU + {707A3F6A-961B-4B37-98A8-B7371087F1A9}.Debug|x86.Build.0 = Debug|Any CPU + {707A3F6A-961B-4B37-98A8-B7371087F1A9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {707A3F6A-961B-4B37-98A8-B7371087F1A9}.Release|Any CPU.Build.0 = Release|Any CPU + {707A3F6A-961B-4B37-98A8-B7371087F1A9}.Release|x64.ActiveCfg = Release|Any CPU + {707A3F6A-961B-4B37-98A8-B7371087F1A9}.Release|x64.Build.0 = Release|Any CPU + {707A3F6A-961B-4B37-98A8-B7371087F1A9}.Release|x86.ActiveCfg = Release|Any CPU + {707A3F6A-961B-4B37-98A8-B7371087F1A9}.Release|x86.Build.0 = Release|Any CPU + {3EF4EA32-6F5B-4B2E-83BD-08D670D5A361}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3EF4EA32-6F5B-4B2E-83BD-08D670D5A361}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3EF4EA32-6F5B-4B2E-83BD-08D670D5A361}.Debug|x64.ActiveCfg = Debug|Any CPU + {3EF4EA32-6F5B-4B2E-83BD-08D670D5A361}.Debug|x64.Build.0 = Debug|Any CPU + {3EF4EA32-6F5B-4B2E-83BD-08D670D5A361}.Debug|x86.ActiveCfg = Debug|Any CPU + {3EF4EA32-6F5B-4B2E-83BD-08D670D5A361}.Debug|x86.Build.0 = Debug|Any CPU + {3EF4EA32-6F5B-4B2E-83BD-08D670D5A361}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3EF4EA32-6F5B-4B2E-83BD-08D670D5A361}.Release|Any CPU.Build.0 = Release|Any CPU + {3EF4EA32-6F5B-4B2E-83BD-08D670D5A361}.Release|x64.ActiveCfg = Release|Any CPU + {3EF4EA32-6F5B-4B2E-83BD-08D670D5A361}.Release|x64.Build.0 = Release|Any CPU + {3EF4EA32-6F5B-4B2E-83BD-08D670D5A361}.Release|x86.ActiveCfg = Release|Any CPU + {3EF4EA32-6F5B-4B2E-83BD-08D670D5A361}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/WorldDirect.CoAP.Example.Client/Program.cs b/WorldDirect.CoAP.Example.Client/Program.cs new file mode 100644 index 0000000..2fa181c --- /dev/null +++ b/WorldDirect.CoAP.Example.Client/Program.cs @@ -0,0 +1,12 @@ +using System; + +namespace WorldDirect.CoAP.Example.Client +{ + class Program + { + static void Main(string[] args) + { + Console.WriteLine("Hello World!"); + } + } +} diff --git a/WorldDirect.CoAP.Example.Client/WorldDirect.CoAP.Example.Client.csproj b/WorldDirect.CoAP.Example.Client/WorldDirect.CoAP.Example.Client.csproj new file mode 100644 index 0000000..c73e0d1 --- /dev/null +++ b/WorldDirect.CoAP.Example.Client/WorldDirect.CoAP.Example.Client.csproj @@ -0,0 +1,8 @@ + + + + Exe + netcoreapp3.1 + + + diff --git a/WorldDirect.CoAP.Example.Server/Program.cs b/WorldDirect.CoAP.Example.Server/Program.cs new file mode 100644 index 0000000..35fc082 --- /dev/null +++ b/WorldDirect.CoAP.Example.Server/Program.cs @@ -0,0 +1,12 @@ +using System; + +namespace WorldDirect.CoAP.Example.Server +{ + class Program + { + static void Main(string[] args) + { + Console.WriteLine("Hello World!"); + } + } +} diff --git a/WorldDirect.CoAP.Example.Server/WorldDirect.CoAP.Example.Server.csproj b/WorldDirect.CoAP.Example.Server/WorldDirect.CoAP.Example.Server.csproj new file mode 100644 index 0000000..c73e0d1 --- /dev/null +++ b/WorldDirect.CoAP.Example.Server/WorldDirect.CoAP.Example.Server.csproj @@ -0,0 +1,8 @@ + + + + Exe + netcoreapp3.1 + + + diff --git a/WorldDirect.CoAP.Specs/UnitTest1.cs b/WorldDirect.CoAP.Specs/UnitTest1.cs new file mode 100644 index 0000000..15e8b84 --- /dev/null +++ b/WorldDirect.CoAP.Specs/UnitTest1.cs @@ -0,0 +1,14 @@ +using System; +using Xunit; + +namespace WorldDirect.CoAP.Specs +{ + public class UnitTest1 + { + [Fact] + public void Test1() + { + + } + } +} diff --git a/WorldDirect.CoAP.Specs/WorldDirect.CoAP.Specs.csproj b/WorldDirect.CoAP.Specs/WorldDirect.CoAP.Specs.csproj new file mode 100644 index 0000000..5b1791f --- /dev/null +++ b/WorldDirect.CoAP.Specs/WorldDirect.CoAP.Specs.csproj @@ -0,0 +1,16 @@ + + + + netcoreapp3.1 + + false + + + + + + + + + + diff --git a/WorldDirect.CoAP/BlockOption.cs b/WorldDirect.CoAP/BlockOption.cs new file mode 100644 index 0000000..55d1004 --- /dev/null +++ b/WorldDirect.CoAP/BlockOption.cs @@ -0,0 +1,140 @@ +/* + * Copyright (c) 2011-2012, Longxiang He , + * SmeshLink Technology Co. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY. + * + * This file is part of the CoAP.NET, a CoAP framework in C#. + * Please see README for more information. + */ + +namespace WorldDirect.CoAP +{ + using System; + + /// + /// This class describes the block options of the CoAP messages + /// + public class BlockOption : Option + { + /// + /// Initializes a block option. + /// + /// The type of the option + public BlockOption(OptionType type) : base(type) + { + this.IntValue = 0; + } + + /// + /// Initializes a block option. + /// + /// The type of the option + /// Block number + /// Block size + /// More flag + public BlockOption(OptionType type, Int32 num, Int32 szx, Boolean m) : base(type) + { + this.IntValue = Encode(num, szx, m); + } + + /// + /// Sets block params. + /// + /// Block number + /// Block size + /// More flag + public void SetValue(Int32 num, Int32 szx, Boolean m) + { + this.IntValue = Encode(num, szx, m); + } + + /// + /// Gets or sets the block number. + /// + public Int32 NUM + { + get { return this.IntValue >> 4; } + set { SetValue(value, SZX, M); } + } + + /// + /// Gets or sets the block size. + /// + public Int32 SZX + { + get { return this.IntValue & 0x7; } + set { SetValue(NUM, value, M); } + } + + /// + /// Gets or sets the more flag. + /// + public Boolean M + { + get { return (this.IntValue >> 3 & 0x1) != 0; } + set { SetValue(NUM, SZX, value); } + } + + /// + /// Gets the decoded block size in bytes (B). + /// + public Int32 Size + { + get { return DecodeSZX(this.SZX); } + } + + /// + /// + /// + /// + public override String ToString() + { + return String.Format("{0}{1} ({2}B/block [{3}])", NUM, M ? "+" : String.Empty, Size, SZX); + } + + /// + /// Gets the real block size which is 2 ^ (SZX + 4). + /// + /// + /// + public static Int32 DecodeSZX(Int32 szx) + { + return 1 << (szx + 4); + } + + /// + /// Converts a block size into the corresponding SZX. + /// + /// + /// + public static Int32 EncodeSZX(Int32 blockSize) + { + if (blockSize < 16) + return 0; + if (blockSize > 1024) + return 6; + return (Int32)(Math.Log(blockSize) / Math.Log(2)) - 4; + } + + /// + /// Checks whether the given SZX is valid or not. + /// + /// + /// + public static Boolean ValidSZX(Int32 szx) + { + return (szx >= 0 && szx <= 6); + } + + private static Int32 Encode(Int32 num, Int32 szx, Boolean m) + { + Int32 value = 0; + value |= (szx & 0x7); + value |= (m ? 1 : 0) << 3; + value |= num << 4; + return value; + } + } +} diff --git a/WorldDirect.CoAP/Channel/DataReceivedEventArgs.cs b/WorldDirect.CoAP/Channel/DataReceivedEventArgs.cs new file mode 100644 index 0000000..0f25ea1 --- /dev/null +++ b/WorldDirect.CoAP/Channel/DataReceivedEventArgs.cs @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2011-2014, Longxiang He , + * SmeshLink Technology Co. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY. + * + * This file is part of the CoAP.NET, a CoAP framework in C#. + * Please see README for more information. + */ + +namespace WorldDirect.CoAP.Channel +{ + using System; + + /// + /// Provides data for event. + /// + public class DataReceivedEventArgs : EventArgs + { + readonly Byte[] _data; + readonly System.Net.EndPoint _endPoint; + + /// + /// + public DataReceivedEventArgs(Byte[] data, System.Net.EndPoint endPoint) + { + _data = data; + _endPoint = endPoint; + } + + /// + /// Gets the received bytes. + /// + public Byte[] Data { get { return _data; } } + + /// + /// Gets the where the data is received from. + /// + public System.Net.EndPoint EndPoint { get { return _endPoint; } } + } +} diff --git a/WorldDirect.CoAP/Channel/IChannel.cs b/WorldDirect.CoAP/Channel/IChannel.cs new file mode 100644 index 0000000..0507145 --- /dev/null +++ b/WorldDirect.CoAP/Channel/IChannel.cs @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2011-2014, Longxiang He , + * SmeshLink Technology Co. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY. + * + * This file is part of the CoAP.NET, a CoAP framework in C#. + * Please see README for more information. + */ + +namespace WorldDirect.CoAP.Channel +{ + using System; + + /// + /// Represents a channel where bytes data can flow through. + /// + public interface IChannel : IDisposable + { + /// + /// Gets the local endpoint of this channel. + /// + System.Net.EndPoint LocalEndPoint { get; } + /// + /// Occurs when some bytes are received in this channel. + /// + event EventHandler DataReceived; + /// + /// Starts this channel. + /// + void Start(); + /// + /// Stops this channel. + /// + void Stop(); + /// + /// Sends data through this channel. This method should be non-blocking. + /// + /// the bytes to send + /// the target endpoint + void Send(Byte[] data, System.Net.EndPoint ep); + } +} diff --git a/WorldDirect.CoAP/Channel/IPAddressExtensions.cs b/WorldDirect.CoAP/Channel/IPAddressExtensions.cs new file mode 100644 index 0000000..26f45ae --- /dev/null +++ b/WorldDirect.CoAP/Channel/IPAddressExtensions.cs @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2011-2014, Longxiang He , + * SmeshLink Technology Co. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY. + * + * This file is part of the CoAP.NET, a CoAP framework in C#. + * Please see README for more information. + */ + +namespace WorldDirect.CoAP.Channel +{ + using System; + using System.Net; + using System.Net.Sockets; + + /// + /// Extension methods for . + /// + public static class IPAddressExtensions + { + /// + /// Checks whether the IP address is an IPv4-mapped IPv6 address. + /// + /// the object to check + /// true if the IP address is an IPv4-mapped IPv6 address; otherwise, false. + public static Boolean IsIPv4MappedToIPv6(IPAddress address) + { + if (address.AddressFamily != AddressFamily.InterNetworkV6) + return false; + Byte[] bytes = address.GetAddressBytes(); + for (Int32 i = 0; i < 10; i++) + { + if (bytes[i] != 0) + return false; + } + return bytes[10] == 0xff && bytes[11] == 0xff; + } + + /// + /// Maps the object to an IPv4 address. + /// + /// the object + /// An IPv4 address. + public static IPAddress MapToIPv4(IPAddress address) + { + if (address.AddressFamily == AddressFamily.InterNetwork) + return address; + Byte[] bytes = address.GetAddressBytes(); + Int64 newAddress = (UInt32)(bytes[12] & 0xff) | (UInt32)(bytes[13] & 0xff) << 8 | (UInt32)(bytes[14] & 0xff) << 16 | (UInt32)(bytes[15] & 0xff) << 24; + return new IPAddress(newAddress); + } + + /// + /// Maps the object to an IPv6 address. + /// + /// the object + /// An IPv6 address. + public static IPAddress MapToIPv6(IPAddress address) + { + if (address.AddressFamily == AddressFamily.InterNetworkV6) + return address; + Byte[] bytes = address.GetAddressBytes(); + Byte[] newAddress = new Byte[16] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, bytes[0], bytes[1], bytes[2], bytes[3] }; + return new IPAddress(newAddress); + } + } +} diff --git a/WorldDirect.CoAP/Channel/UDPChannel.NET40.cs b/WorldDirect.CoAP/Channel/UDPChannel.NET40.cs new file mode 100644 index 0000000..f70e2f4 --- /dev/null +++ b/WorldDirect.CoAP/Channel/UDPChannel.NET40.cs @@ -0,0 +1,201 @@ +/* + * Copyright (c) 2011-2014, Longxiang He , + * SmeshLink Technology Co. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY. + * + * This file is part of the CoAP.NET, a CoAP framework in C#. + * Please see README for more information. + */ + +namespace WorldDirect.CoAP.Channel { + using System; + using System.Net; + using System.Net.Sockets; + + public partial class UDPChannel { + private UDPSocket NewUDPSocket(AddressFamily addressFamily, Int32 bufferSize) + { + return new UDPSocket(addressFamily, bufferSize, SocketAsyncEventArgs_Completed); + } + + private void BeginReceive(UDPSocket socket) + { + if (_running == 0) + return; + + if (socket.ReadBuffer.RemoteEndPoint == null) + { + socket.ReadBuffer.RemoteEndPoint = socket.Socket.Connected ? + socket.Socket.RemoteEndPoint : + new IPEndPoint( + socket.Socket.AddressFamily == AddressFamily.InterNetwork ? + IPAddress.Any : IPAddress.IPv6Any, 0); + } + + bool willRaiseEvent; + try + { + willRaiseEvent = socket.Socket.ReceiveFromAsync(socket.ReadBuffer); + + var e = socket.ReadBuffer?.RemoteEndPoint as IPEndPoint; + if (e != null) + { + log.Debug(message: $"Received packet from {e.Address.MapToIPv4()}:{e.Port}. Processing package {(willRaiseEvent ? "asynchronous" : "synchronous")}"); + } + + + if (socket.ReadBuffer?.RemoteEndPoint is IPEndPoint ep) + { + log.Debug(message: $"Received packet from {ep.Address.MapToIPv4()}:{ep.Port}. Processing package {(willRaiseEvent ? "asynchronous" : "synchronous")}"); + } + } + catch (ObjectDisposedException) + { + // do nothing + return; + } + catch (Exception ex) + { + EndReceive(socket, ex); + return; + } + + if (!willRaiseEvent) + { + ProcessReceive(socket.ReadBuffer); + } + } + + private void BeginSend(UDPSocket socket, Byte[] data, System.Net.EndPoint destination) + { + socket.SetWriteBuffer(data, 0, data.Length); + socket.WriteBuffer.RemoteEndPoint = destination; + + bool willRaiseEvent; + try + { + willRaiseEvent = socket.Socket.SendToAsync(socket.WriteBuffer); + + if (destination is IPEndPoint ep) + { + log.Debug(message: $"Sending packet to {ep.Address.MapToIPv4()}:{ep.Port}. Processing package {(willRaiseEvent ? "asynchronous" : "synchronous")}"); + } + } + catch (ObjectDisposedException) + { + // do nothing + return; + } + catch (Exception ex) + { + EndSend(socket, ex); + return; + } + + if (!willRaiseEvent) + { + ProcessSend(socket.WriteBuffer); + } + } + + private void ProcessReceive(SocketAsyncEventArgs e) + { + UDPSocket socket = (UDPSocket)e.UserToken; + + var ip = e.RemoteEndPoint as IPEndPoint; + if (ip != null) + { + var addr = ip.Address.MapToIPv4(); + } + + + if (e.SocketError == SocketError.Success) + { + EndReceive(socket, e.Buffer, e.Offset, e.BytesTransferred, e.RemoteEndPoint); + } + else if (e.SocketError != SocketError.OperationAborted + && e.SocketError != SocketError.Interrupted) + { + EndReceive(socket, new SocketException((Int32)e.SocketError)); + } + } + + private void ProcessSend(SocketAsyncEventArgs e) + { + UDPSocket socket = (UDPSocket)e.UserToken; + + if (e.SocketError == SocketError.Success) + { + EndSend(socket, e.BytesTransferred); + } + else + { + EndSend(socket, new SocketException((Int32)e.SocketError)); + } + } + + void SocketAsyncEventArgs_Completed(Object sender, SocketAsyncEventArgs e) + { + switch (e.LastOperation) + { + case SocketAsyncOperation.ReceiveFrom: + ProcessReceive(e); + break; + case SocketAsyncOperation.SendTo: + ProcessSend(e); + break; + } + } + + partial class UDPSocket { + public readonly SocketAsyncEventArgs ReadBuffer; + public readonly SocketAsyncEventArgs WriteBuffer; + readonly Byte[] _writeBuffer; + private Boolean _isOuterBuffer; + + public UDPSocket(AddressFamily addressFamily, Int32 bufferSize, + EventHandler completed) + { + Socket = new Socket(addressFamily, SocketType.Dgram, ProtocolType.Udp); + ReadBuffer = new SocketAsyncEventArgs(); + ReadBuffer.SetBuffer(new Byte[bufferSize], 0, bufferSize); + ReadBuffer.Completed += completed; + ReadBuffer.UserToken = this; + + _writeBuffer = new Byte[bufferSize]; + WriteBuffer = new SocketAsyncEventArgs(); + WriteBuffer.SetBuffer(_writeBuffer, 0, bufferSize); + WriteBuffer.Completed += completed; + WriteBuffer.UserToken = this; + } + + public void SetWriteBuffer(Byte[] data, Int32 offset, Int32 count) + { + if (count > _writeBuffer.Length) + { + WriteBuffer.SetBuffer(data, offset, count); + _isOuterBuffer = true; + } + else + { + if (_isOuterBuffer) + { + WriteBuffer.SetBuffer(_writeBuffer, 0, _writeBuffer.Length); + _isOuterBuffer = false; + } + Buffer.BlockCopy(data, offset, _writeBuffer, 0, count); + WriteBuffer.SetBuffer(0, count); + } + } + + public void Dispose() + { + Socket.Dispose(); + ReadBuffer.Dispose(); + WriteBuffer.Dispose(); + } + } + } +} diff --git a/WorldDirect.CoAP/Channel/UDPChannel.cs b/WorldDirect.CoAP/Channel/UDPChannel.cs new file mode 100644 index 0000000..a16e76a --- /dev/null +++ b/WorldDirect.CoAP/Channel/UDPChannel.cs @@ -0,0 +1,338 @@ +/* + * Copyright (c) 2011-2014, Longxiang He , + * SmeshLink Technology Co. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY. + * + * This file is part of the CoAP.NET, a CoAP framework in C#. + * Please see README for more information. + */ + +namespace WorldDirect.CoAP.Channel +{ + using System; + using System.Collections.Concurrent; + using System.Net; + using System.Net.Sockets; + using Log; + + /// + /// Channel via UDP protocol. + /// + public partial class UDPChannel : IChannel + { + + static readonly ILogger log = LogManager.GetLogger(typeof(UDPChannel)); + + /// + /// Default size of buffer for receiving packet. + /// + public const Int32 DefaultReceivePacketSize = 4096; + private Int32 _receiveBufferSize; + private Int32 _sendBufferSize; + private Int32 _receivePacketSize = DefaultReceivePacketSize; + private Int32 _port; + private System.Net.EndPoint _localEP; + private UDPSocket _socket; + private UDPSocket _socketBackup; + private Int32 _running; + private Int32 _writing; + private readonly ConcurrentQueue _sendingQueue = new ConcurrentQueue(); + + /// + public event EventHandler DataReceived; + + /// + /// Initializes a UDP channel with a random port. + /// + public UDPChannel() + : this(0) + { + } + + /// + /// Initializes a UDP channel with the given port, both on IPv4 and IPv6. + /// + public UDPChannel(Int32 port) + { + _port = port; + } + + /// + /// Initializes a UDP channel with the specific endpoint. + /// + public UDPChannel(System.Net.EndPoint localEP) + { + _localEP = localEP; + } + + /// + public System.Net.EndPoint LocalEndPoint + { + get + { + return _socket == null + ? (_localEP ?? new IPEndPoint(IPAddress.IPv6Any, _port)) + : _socket.Socket.LocalEndPoint; + } + } + + /// + /// Gets or sets the . + /// + public Int32 ReceiveBufferSize + { + get { return _receiveBufferSize; } + set { _receiveBufferSize = value; } + } + + /// + /// Gets or sets the . + /// + public Int32 SendBufferSize + { + get { return _sendBufferSize; } + set { _sendBufferSize = value; } + } + + /// + /// Gets or sets the size of buffer for receiving packet. + /// The default value is . + /// + public Int32 ReceivePacketSize + { + get { return _receivePacketSize; } + set { _receivePacketSize = value; } + } + + /// + public void Start() + { + if (System.Threading.Interlocked.CompareExchange(ref _running, 1, 0) > 0) + return; + + if (_localEP == null) + { + try + { + _socket = SetupUDPSocket(AddressFamily.InterNetworkV6, _receivePacketSize + 1); // +1 to check for > ReceivePacketSize + } + catch (SocketException e) + { + if (e.SocketErrorCode == SocketError.AddressFamilyNotSupported) + _socket = null; + else + throw e; + } + + if (_socket == null) + { + // IPv6 is not supported, use IPv4 instead + _socket = SetupUDPSocket(AddressFamily.InterNetwork, _receivePacketSize + 1); + _socket.Socket.Bind(new IPEndPoint(IPAddress.Any, _port)); + } + else + { + try + { + // Enable IPv4-mapped IPv6 addresses to accept both IPv6 and IPv4 connections in a same socket. + _socket.Socket.SetSocketOption(SocketOptionLevel.IPv6, (SocketOptionName)27, 0); + } + catch + { + // IPv4-mapped address seems not to be supported, set up a separated socket of IPv4. + _socketBackup = SetupUDPSocket(AddressFamily.InterNetwork, _receivePacketSize + 1); + } + + _socket.Socket.Bind(new IPEndPoint(IPAddress.IPv6Any, _port)); + if (_socketBackup != null) + _socketBackup.Socket.Bind(new IPEndPoint(IPAddress.Any, _port)); + } + } + else + { + _socket = SetupUDPSocket(_localEP.AddressFamily, _receivePacketSize + 1); + _socket.Socket.Bind(_localEP); + } + + if (_receiveBufferSize > 0) + { + _socket.Socket.ReceiveBufferSize = _receiveBufferSize; + if (_socketBackup != null) + _socketBackup.Socket.ReceiveBufferSize = _receiveBufferSize; + } + if (_sendBufferSize > 0) + { + _socket.Socket.SendBufferSize = _sendBufferSize; + if (_socketBackup != null) + _socketBackup.Socket.SendBufferSize = _sendBufferSize; + } + + BeginReceive(); + } + + /// + public void Stop() + { + if (System.Threading.Interlocked.Exchange(ref _running, 0) == 0) + return; + + if (_socket != null) + { + _socket.Dispose(); + _socket = null; + } + if (_socketBackup != null) + { + _socketBackup.Dispose(); + _socketBackup = null; + } + } + + /// + public void Send(Byte[] data, System.Net.EndPoint ep) + { + RawData raw = new RawData(); + raw.Data = data; + raw.EndPoint = ep; + _sendingQueue.Enqueue(raw); + log.Info($"UDP-Enqueued {ep.Serialize()}({data?.Length})"); + if (System.Threading.Interlocked.CompareExchange(ref _writing, 1, 0) > 0) + return; + BeginSend(); + } + + /// + public void Dispose() + { + Stop(); + } + + private void BeginReceive() + { + if (_running > 0) + { + BeginReceive(_socket); + + if (_socketBackup != null) + BeginReceive(_socketBackup); + } + } + + private void EndReceive(UDPSocket socket, Byte[] buffer, Int32 offset, Int32 count, System.Net.EndPoint ep) + { + if (count > 0) + { + Byte[] bytes = new Byte[count]; + Buffer.BlockCopy(buffer, 0, bytes, 0, count); + + if (ep.AddressFamily == AddressFamily.InterNetworkV6) + { + IPEndPoint ipep = (IPEndPoint)ep; + if (IPAddressExtensions.IsIPv4MappedToIPv6(ipep.Address)) + ep = new IPEndPoint(IPAddressExtensions.MapToIPv4(ipep.Address), ipep.Port); + } + + try { + DateTimeOffset start = DateTimeOffset.Now; + log.Info($"UDP-FireDataReceived START"); + FireDataReceived(bytes, ep); + log.Info($"UDP-FireDataReceived END ({DateTimeOffset.Now - start})"); + } + catch(Exception e) { + log.Error($"FireDataReceived error occurred: {e.ToString()}", e); + } + } + + BeginReceive(socket); + } + + private void EndReceive(UDPSocket socket, Exception ex) + { + // TODO may log exception? + + log.Error(nameof(EndReceive), ex); + + BeginReceive(socket); + } + + private void FireDataReceived(Byte[] data, System.Net.EndPoint ep) + { + EventHandler h = DataReceived; + if (h != null) + h(this, new DataReceivedEventArgs(data, ep)); + } + + private void BeginSend() + { + if (_running == 0) + return; + + RawData raw; + if (!_sendingQueue.TryDequeue(out raw)) + { + System.Threading.Interlocked.Exchange(ref _writing, 0); + return; + } + + UDPSocket socket = _socket; + IPEndPoint remoteEP = (IPEndPoint)raw.EndPoint; + + if (remoteEP.AddressFamily == AddressFamily.InterNetwork) + { + if (_socketBackup != null) + { + // use the separated socket of IPv4 to deal with IPv4 conversions. + socket = _socketBackup; + } + else if (_socket.Socket.AddressFamily == AddressFamily.InterNetworkV6) + { + remoteEP = new IPEndPoint(IPAddressExtensions.MapToIPv6(remoteEP.Address), remoteEP.Port); + } + } + + BeginSend(socket, raw.Data, remoteEP); + } + + private void EndSend(UDPSocket socket, Int32 bytesTransferred) + { + BeginSend(); + } + + private void EndSend(UDPSocket socket, Exception ex) + { + // TODO may log exception? + log.Error(nameof(EndSend), ex); + BeginSend(); + } + + private UDPSocket SetupUDPSocket(AddressFamily addressFamily, Int32 bufferSize) + { + UDPSocket socket = NewUDPSocket(addressFamily, bufferSize); + + // do not throw SocketError.ConnectionReset by ignoring ICMP Port Unreachable + const Int32 SIO_UDP_CONNRESET = -1744830452; + try + { + socket.Socket.IOControl(SIO_UDP_CONNRESET, new Byte[] { 0 }, null); + } + catch (Exception) + { + // ignore + } + return socket; + } + + partial class UDPSocket : IDisposable + { + public readonly Socket Socket; + } + + class RawData + { + public Byte[] Data; + public System.Net.EndPoint EndPoint; + } + } +} diff --git a/WorldDirect.CoAP/Class1.cs b/WorldDirect.CoAP/Class1.cs new file mode 100644 index 0000000..e1d6284 --- /dev/null +++ b/WorldDirect.CoAP/Class1.cs @@ -0,0 +1,8 @@ +using System; + +namespace WorldDirect.CoAP +{ + public class Class1 + { + } +} diff --git a/WorldDirect.CoAP/CoapClient.cs b/WorldDirect.CoAP/CoapClient.cs new file mode 100644 index 0000000..04ab43d --- /dev/null +++ b/WorldDirect.CoAP/CoapClient.cs @@ -0,0 +1,677 @@ +namespace WorldDirect.CoAP +{ + using System; + using System.Collections.Generic; + using System.Threading; + using System.Threading.Tasks; + using Log; + using Net; + + /// + /// Provides convenient methods for accessing CoAP resources. + /// + public class CoapClient + { + #region Locals + + private static readonly IEnumerable EmptyLinks = new WebLink[0]; + private static ILogger log = LogManager.GetLogger(typeof(CoapClient)); + + private ICoapConfig _config; + private IEndPoint _endpoint; + private MessageType _type = MessageType.CON; + private Uri _uri; + private Int32 _blockwise; + private Int32 _timeout = System.Threading.Timeout.Infinite; + + #endregion + + /// + /// Occurs when a response has arrived. + /// + public event EventHandler Respond; + + /// + /// Occurs if an exception is thrown while executing a request. + /// + public event EventHandler Error; + + /// + /// Instantiates with default config. + /// + public CoapClient() + : this(null, null) + { + } + + /// + /// Instantiates with default config. + /// + /// the Uri of remote resource + public CoapClient(Uri uri) + : this(uri, null) + { + } + + /// + /// Instantiates. + /// + /// the config + public CoapClient(ICoapConfig config) + : this(null, config) + { + } + + /// + /// Instantiates. + /// + /// the Uri of remote resource + /// the config + public CoapClient(Uri uri, ICoapConfig config) + { + _uri = uri; + _config = config ?? CoapConfig.Default; + } + + /// + /// Gets or sets the destination URI of this client. + /// + public Uri Uri + { + get { return _uri; } + set { _uri = value; } + } + + /// + /// Gets or sets the endpoint this client is supposed to use. + /// + public IEndPoint EndPoint + { + get { return _endpoint; } + set { _endpoint = value; } + } + + /// + /// Gets or sets the timeout how long synchronous method calls will wait + /// until they give up and return anyways. The default value is . + /// + public Int32 Timeout + { + get { return _timeout; } + set { _timeout = value; } + } + + /// + /// Let the client use Confirmable requests. + /// + public CoapClient UseCONs() + { + _type = MessageType.CON; + return this; + } + + /// + /// Let the client use Non-Confirmable requests. + /// + public CoapClient UseNONs() + { + _type = MessageType.NON; + return this; + } + + /// + /// Let the client use early negotiation for the blocksize + /// (16, 32, 64, 128, 256, 512, or 1024). Other values will + /// be matched to the closest logarithm dualis. + /// + public CoapClient UseEarlyNegotiation(Int32 size) + { + _blockwise = size; + return this; + } + + /// + /// Let the client use late negotiation for the block size (default). + /// + public CoapClient UseLateNegotiation() + { + _blockwise = 0; + return this; + } + + /// + /// Performs a CoAP ping. + /// + /// success of the ping + public Boolean Ping() + { + return Ping(_timeout); + } + + /// + /// Performs a CoAP ping and gives up after the given number of milliseconds. + /// + /// the time to wait for a pong in milliseconds + /// success of the ping + public Boolean Ping(Int32 timeout) + { + try + { + Request request = new Request(Code.Empty, true); + request.Token = CoapConstants.EmptyToken; + request.URI = Uri; + request.Send().WaitForResponse(timeout); + return request.IsRejected; + } + catch ( /*System.Threading.ThreadInterruptedException*/ System.Exception) + { + /* ignore */ + } + + return false; + } + + /// + /// Discovers remote resources. + /// + /// the descoverd representing remote resources, or null if no response + public IEnumerable Discover() + { + return Discover(null); + } + + /// + /// Discovers remote resources. + /// + /// the query to filter resources + /// the descoverd representing remote resources, or null if no response + public IEnumerable Discover(String query) + { + Request discover = Prepare(Request.NewGet()); + discover.ClearUriPath().ClearUriQuery().UriPath = CoapConstants.DefaultWellKnownURI; + if (!String.IsNullOrEmpty(query)) + discover.UriQuery = query; + Response links = discover.Send().WaitForResponse(_timeout); + if (links == null) + // if no response, return null (e.g., timeout) + return null; + else if (links.ContentFormat != MediaType.ApplicationLinkFormat) + return EmptyLinks; + else + return LinkFormat.Parse(links.PayloadString); + } + + /// + /// Sends a GET request and blocks until the response is available. + /// + /// the CoAP response + public Response Get() + { + return Send(Request.NewGet()); + } + + /// + /// Sends a GET request with the specified Accept option and blocks + /// until the response is available. + /// + /// the Accept option + /// the CoAP response + public Response Get(Int32 accept) + { + return Send(Accept(Request.NewGet(), accept)); + } + + public Task GetAsync(int accept, Uri uri, CancellationToken ct) + { + this.Uri = uri; + var request = Request.NewGet(); + request.Accept = accept; + + return this.SendAsync(request, ct); + } + + public Task PutAsync(Uri uri, byte[] payload, int contentType, CancellationToken ct) + { + this.Uri = uri; + + var request = Request.NewPut(); + request.SetPayload(payload, contentType); + return this.SendAsync(request, ct); + } + + /// + /// Sends a GET request asynchronizely. + /// + /// the callback when a response arrives + /// the callback when an error occurs + public void GetAsync(Action done = null, Action fail = null) + { + SendAsync(Request.NewGet(), done, fail); + } + + /// + /// Sends a GET request with the specified Accept option asynchronizely. + /// + /// the Accept option + /// the callback when a response arrives + /// the callback when an error occurs + public void GetAsync(Int32 accept, Action done = null, Action fail = null) + { + SendAsync(Accept(Request.NewGet(), accept), done, fail); + } + + /// + /// Sends a GET request with the specified Accept option asynchronizely. + /// + /// the Accept option + public Task GetAsync(Int32 accept, CancellationToken ct) + { + return SendAsync(Accept(Request.NewGet(), accept), ct); + } + + public Response Post(String payload, Int32 format = MediaType.TextPlain) + { + return Send((Request)Request.NewPost().SetPayload(payload, format)); + } + + public Response Post(String payload, Int32 format, Int32 accept) + { + return Send(Accept((Request)Request.NewPost().SetPayload(payload, format), accept)); + } + + public Response Post(Byte[] payload, Int32 format) + { + return Send((Request)Request.NewPost().SetPayload(payload, format)); + } + + public Response Post(Byte[] payload, Int32 format, Int32 accept) + { + return Send(Accept((Request)Request.NewPost().SetPayload(payload, format), accept)); + } + + public void PostAsync(String payload, Action done = null, Action fail = null) + { + PostAsync(payload, MediaType.TextPlain, done, fail); + } + + public void PostAsync(String payload, Int32 format, Action done = null, Action fail = null) + { + SendAsync((Request)Request.NewPost().SetPayload(payload, format), done, fail); + } + + public Task PostAsync(Uri uri, byte[] payload, Int32 format, CancellationToken ct) + { + this.Uri = uri; + var request = Request.NewPost(); + request.SetPayload(payload, format); + return SendAsync((Request)Request.NewPost().SetPayload(payload, format), ct); + } + + public void PostAsync(String payload, Int32 format, Int32 accept, + Action done = null, Action fail = null) + { + SendAsync(Accept((Request)Request.NewPost().SetPayload(payload, format), accept), done, fail); + } + + public void PostAsync(Byte[] payload, Int32 format, + Action done = null, Action fail = null) + { + SendAsync((Request)Request.NewPost().SetPayload(payload, format), done, fail); + } + + public void PostAsync(Byte[] payload, Int32 format, Int32 accept, + Action done = null, Action fail = null) + { + SendAsync(Accept((Request)Request.NewPost().SetPayload(payload, format), accept), done, fail); + } + + public Response Put(String payload, Int32 format = MediaType.TextPlain) + { + return Send((Request)Request.NewPut().SetPayload(payload, format)); + } + + public Response Put(Byte[] payload, Int32 format, Int32 accept) + { + return Send(Accept((Request)Request.NewPut().SetPayload(payload, format), accept)); + } + + public Response PutIfMatch(String payload, Int32 format, params Byte[][] etags) + { + return Send(IfMatch((Request)Request.NewPut().SetPayload(payload, format), etags)); + } + + public Response PutIfMatch(Byte[] payload, Int32 format, params Byte[][] etags) + { + return Send(IfMatch((Request)Request.NewPut().SetPayload(payload, format), etags)); + } + + public Response PutIfNoneMatch(String payload, Int32 format) + { + return Send(IfNoneMatch((Request)Request.NewPut().SetPayload(payload, format))); + } + + public Response PutIfNoneMatch(Byte[] payload, Int32 format) + { + return Send(IfNoneMatch((Request)Request.NewPut().SetPayload(payload, format))); + } + + public void PutAsync(String payload, + Action done = null, Action fail = null) + { + PutAsync(payload, MediaType.TextPlain, done, fail); + } + + public Task PutAsync(String payload, CancellationToken ct) + { + return SendAsync((Request)Request.NewPut().SetPayload(payload, MediaType.TextPlain), ct); + } + + public void PutAsync(String payload, Int32 format, + Action done = null, Action fail = null) + { + SendAsync((Request)Request.NewPut().SetPayload(payload, format), done, fail); + } + + public void PutAsync(Byte[] payload, Int32 format, Int32 accept, + Action done = null, Action fail = null) + { + SendAsync(Accept((Request)Request.NewPut().SetPayload(payload, format), accept), done, fail); + } + + + + /// + /// Sends a DELETE request and waits for the response. + /// + /// the CoAP response + public Response Delete() + { + return Send(Request.NewDelete()); + } + + /// + /// Sends a DELETE request asynchronizely. + /// + /// the callback when a response arrives + /// the callback when an error occurs + public void DeleteAsync(Action done = null, Action fail = null) + { + SendAsync(Request.NewDelete(), done, fail); + } + + /// + /// Sends a DELETE request asynchronizely. + /// + public Task DeleteAsync(CancellationToken ct) + { + return SendAsync(Request.NewDelete(), ct); + } + + public Task DeleteAsync(Uri uri, CancellationToken ct) + { + var request = Request.NewDelete(); + request.URI = uri; + + return SendAsync(request, ct); + } + + public Response Validate(params Byte[][] etags) + { + return Send(ETags(Request.NewGet(), etags)); + } + + public CoapObserveRelation Observe(Int32 accept, Action notify = null, Action error = null) + { + return Observe(Accept(Request.NewGet().MarkObserve(), accept), notify, error); + } + + public async Task ObserveAsync(Int32 accept, Action notify = null, Action error = null) + { + + var req = Accept(Request.NewGet().MarkObserve(), accept); + var relation = await this.StartObservationAsync(req, notify, error).ConfigureAwait(false); + + return relation; + } + + public CoapObserveRelation ObserveAsync(Action notify = null, Action error = null) + { + return StartObservation(Request.NewGet().MarkObserve(), notify, error); + } + + public Response Send(Request request) + { + return Prepare(request).Send().WaitForResponse(_timeout); + } + + public void SendAsync(Request request, Action done, Action fail = null) + { + request.Respond += (o, e) => Deliver(done, e); + request.Rejected += (o, e) => Fail(fail, FailReason.Rejected); + request.TimedOut += (o, e) => Fail(fail, FailReason.TimedOut); + + Prepare(request).Send(); + } + + public Task SendAsync(Request request, CancellationToken ct) + { + TaskCompletionSource tcs = new TaskCompletionSource(); + var cancellation = ct.Register(() => tcs.TrySetCanceled(ct)); + + + Action success = (r) => + { + tcs.TrySetResult(r); + cancellation.Dispose(); + }; + + Action failure = (fr) => + { + Exception exception; + + if (fr == FailReason.TimedOut) + { + exception = new TimeoutException(); + } + + else if (fr == FailReason.Rejected) + { + exception = new InvalidOperationException("The request has been rejected."); + } + + else + { + exception = new InvalidOperationException($"The request failed with the reason {fr}"); + } + + tcs.TrySetException(exception); + cancellation.Dispose(); + }; + + SendAsync(request, success, failure); + + return tcs.Task; + + } + + protected Request Prepare(Request request) + { + return Prepare(request, GetEffectiveEndpoint(request)); + } + + protected Request Prepare(Request request, IEndPoint endpoint) + { + request.Type = _type; + request.URI = _uri; + + if (_blockwise != 0) + request.SetBlock2(BlockOption.EncodeSZX(_blockwise), false, 0); + + if (endpoint != null) + request.EndPoint = endpoint; + + return request; + } + + /// + /// Gets the effective endpoint that the specified request + /// is supposed to be sent over. + /// + protected IEndPoint GetEffectiveEndpoint(Request request) + { + if (_endpoint != null) + return _endpoint; + else + return EndPointManager.Default; + // TODO secure coap + } + + private CoapObserveRelation Observe(Request request, Action notify, Action error) + { + CoapObserveRelation relation = StartObservation(request, notify, error); + Response response = relation.Request.WaitForResponse(_timeout); + if (response == null || !response.HasOption(OptionType.Observe)) + relation.Canceled = true; + relation.Current = response; + return relation; + } + + private async Task StartObservationAsync(Request request, Action notify, Action error) + { + IEndPoint endpoint = GetEffectiveEndpoint(request); + CoapObserveRelation relation = new CoapObserveRelation(request, endpoint, _config); + + request = CreateObservationRequest(request, notify, error, relation, endpoint); + await SendAsync(request, CancellationToken.None).ConfigureAwait(false); + + return relation; + } + + private CoapObserveRelation StartObservation(Request request, Action notify, Action error) + { + IEndPoint endpoint = GetEffectiveEndpoint(request); + CoapObserveRelation relation = new CoapObserveRelation(request, endpoint, _config); + + request = CreateObservationRequest(request, notify, error, relation, endpoint); + request.Send(); + + return relation; + } + + private Request CreateObservationRequest(Request request, Action notify, Action error, CoapObserveRelation relation, + IEndPoint endpoint) + { + request.Respond += (o, e) => + { + Response resp = e.Response; + lock (relation) + { + if (relation.Orderer.IsNew(resp)) + { + relation.Current = resp; + Deliver(notify, e); + } + else + { + if (log.IsDebugEnabled) + log.Debug("Dropping old notification: " + resp); + } + } + }; + Action fail = r => + { + relation.Canceled = true; + Fail(error, r); + }; + request.Rejected += (o, e) => fail(FailReason.Rejected); + request.TimedOut += (o, e) => fail(FailReason.TimedOut); + + //await SendAsync(Prepare(request, endpoint)); + var finalRequest = Prepare(request, endpoint); + return finalRequest; + } + + private void Deliver(Action act, ResponseEventArgs e) + { + if (act != null) + act(e.Response); + EventHandler h = Respond; + if (h != null) + h(this, e); + } + + private void Fail(Action fail, FailReason reason) + { + if (fail != null) + fail(reason); + EventHandler h = Error; + if (h != null) + h(this, new ErrorEventArgs(reason)); + } + + static Request Accept(Request request, Int32 accept) + { + request.Accept = accept; + return request; + } + + static Request IfMatch(Request request, params Byte[][] etags) + { + foreach (Byte[] etag in etags) + { + request.AddIfMatch(etag); + } + + return request; + } + + static Request IfNoneMatch(Request request) + { + request.IfNoneMatch = true; + return request; + } + + static Request ETags(Request request, params Byte[][] etags) + { + foreach (Byte[] etag in etags) + { + request.AddETag(etag); + } + + return request; + } + + + /// + /// Provides details about errors. + /// + public enum FailReason + { + /// + /// The request has been rejected. + /// + Rejected, + + /// + /// The request has been timed out. + /// + TimedOut + } + + /// + /// Provides event args for errors. + /// + public class ErrorEventArgs : EventArgs + { + internal ErrorEventArgs(FailReason reason) + { + this.Reason = reason; + } + + /// + /// Gets the reason why failed. + /// + public FailReason Reason { get; private set; } + } + } +} diff --git a/WorldDirect.CoAP/CoapConfig.cs b/WorldDirect.CoAP/CoapConfig.cs new file mode 100644 index 0000000..01b8f74 --- /dev/null +++ b/WorldDirect.CoAP/CoapConfig.cs @@ -0,0 +1,518 @@ +/* + * Copyright (c) 2011-2015, Longxiang He , + * SmeshLink Technology Co. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY. + * + * This file is part of the CoAP.NET, a CoAP framework in C#. + * Please see README for more information. + */ + +namespace WorldDirect.CoAP +{ + using System; + using System.Collections.Specialized; + using System.ComponentModel; + using System.IO; + using Deduplication; + + /// + /// Default implementation of . + /// + public partial class CoapConfig : ICoapConfig + { + private static ICoapConfig _default; + + public static ICoapConfig Default + { + get + { + if (_default == null) + { + lock (typeof(CoapConfig)) + { + if (_default == null) + _default = LoadConfig(); + } + } + return _default; + } + } + + private Int32 _port; + private Int32 _securePort = CoapConstants.DefaultSecurePort; + private Int32 _httpPort = 8080; + private Int32 _ackTimeout = CoapConstants.AckTimeout; + private Double _ackRandomFactor = CoapConstants.AckRandomFactor; + private Double _ackTimeoutScale = 2D; + private Int32 _maxRetransmit = CoapConstants.MaxRetransmit; + private Int32 _maxMessageSize = 1024; + private Int32 _defaultBlockSize = CoapConstants.DefaultBlockSize; + private Int32 _blockwiseStatusLifetime = 10 * 60 * 1000; // ms + private Boolean _useRandomIDStart = true; + private Boolean _useRandomTokenStart = true; + private String _deduplicator = DeduplicatorFactory.MarkAndSweepDeduplicator; + private Int32 _cropRotationPeriod = 2000; // ms + private Int32 _exchangeLifetime = 247 * 1000; // ms + private Int64 _markAndSweepInterval = 10 * 1000; // ms + private Int64 _notificationMaxAge = 128 * 1000; // ms + private Int64 _notificationCheckIntervalTime = 24 * 60 * 60 * 1000; // ms + private Int32 _notificationCheckIntervalCount = 100; // ms + private Int32 _notificationReregistrationBackoff = 2000; // ms + private Int32 _channelReceiveBufferSize; + private Int32 _channelSendBufferSize; + private Int32 _channelReceivePacketSize = 2048; + + /// + /// Instantiate. + /// + public CoapConfig() + { + _port = Spec.DefaultPort; + } + + /// + public String Version + { + get { return Spec.Name; } + } + + /// + public Int32 DefaultPort + { + get { return _port; } + set + { + if (_port != value) + { + _port = value; + NotifyPropertyChanged("DefaultPort"); + } + } + } + + /// + public Int32 DefaultSecurePort + { + get { return _securePort; } + set + { + if (_securePort != value) + { + _securePort = value; + NotifyPropertyChanged("DefaultSecurePort"); + } + } + } + + /// + public Int32 HttpPort + { + get { return _httpPort; } + set + { + if (_httpPort != value) + { + _httpPort = value; + NotifyPropertyChanged("HttpPort"); + } + } + } + + /// + public Int32 AckTimeout + { + get { return _ackTimeout; } + set + { + if (_ackTimeout != value) + { + _ackTimeout = value; + NotifyPropertyChanged("AckTimeout"); + } + } + } + + /// + public Double AckRandomFactor + { + get { return _ackRandomFactor; } + set + { + if (_ackRandomFactor != value) + { + _ackRandomFactor = value; + NotifyPropertyChanged("AckRandomFactor"); + } + } + } + + /// + public Double AckTimeoutScale + { + get { return _ackTimeoutScale; } + set + { + if (_ackTimeoutScale != value) + { + _ackTimeoutScale = value; + NotifyPropertyChanged("AckTimeoutScale"); + } + } + } + + /// + public Int32 MaxRetransmit + { + get { return _maxRetransmit; } + set + { + if (_maxRetransmit != value) + { + _maxRetransmit = value; + NotifyPropertyChanged("MaxRetransmit"); + } + } + } + + /// + public Int32 MaxMessageSize + { + get { return _maxMessageSize; } + set + { + if (_maxMessageSize != value) + { + _maxMessageSize = value; + NotifyPropertyChanged("MaxMessageSize"); + } + } + } + + /// + public Int32 DefaultBlockSize + { + get { return _defaultBlockSize; } + set + { + if (_defaultBlockSize != value) + { + _defaultBlockSize = value; + NotifyPropertyChanged("DefaultBlockSize"); + } + } + } + + /// + public Int32 BlockwiseStatusLifetime + { + get { return _blockwiseStatusLifetime; } + set + { + if (_blockwiseStatusLifetime != value) + { + _blockwiseStatusLifetime = value; + NotifyPropertyChanged("BlockwiseStatusLifetime"); + } + } + } + + /// + public Boolean UseRandomIDStart + { + get { return _useRandomIDStart; } + set + { + if (_useRandomIDStart != value) + { + _useRandomIDStart = value; + NotifyPropertyChanged("UseRandomIDStart"); + } + } + } + + /// + public Boolean UseRandomTokenStart + { + get { return _useRandomTokenStart; } + set + { + if (_useRandomTokenStart != value) + { + _useRandomTokenStart = value; + NotifyPropertyChanged("UseRandomTokenStart"); + } + } + } + + /// + public String Deduplicator + { + get { return _deduplicator; } + set + { + if (_deduplicator != value) + { + _deduplicator = value; + NotifyPropertyChanged("Deduplicator"); + } + } + } + + /// + public Int32 CropRotationPeriod + { + get { return _cropRotationPeriod; } + set + { + if (_cropRotationPeriod != value) + { + _cropRotationPeriod = value; + NotifyPropertyChanged("CropRotationPeriod"); + } + } + } + + /// + public Int32 ExchangeLifetime + { + get { return _exchangeLifetime; } + set + { + if (_exchangeLifetime != value) + { + _exchangeLifetime = value; + NotifyPropertyChanged("ExchangeLifetime"); + } + } + } + + /// + public Int64 MarkAndSweepInterval + { + get { return _markAndSweepInterval; } + set + { + if (_markAndSweepInterval != value) + { + _markAndSweepInterval = value; + NotifyPropertyChanged("MarkAndSweepInterval"); + } + } + } + + /// + public Int64 NotificationMaxAge + { + get { return _notificationMaxAge; } + set + { + if (_notificationMaxAge != value) + { + _notificationMaxAge = value; + NotifyPropertyChanged("NotificationMaxAge"); + } + } + } + + /// + public Int64 NotificationCheckIntervalTime + { + get { return _notificationCheckIntervalTime; } + set + { + if (_notificationCheckIntervalTime != value) + { + _notificationCheckIntervalTime = value; + NotifyPropertyChanged("NotificationCheckIntervalTime"); + } + } + } + + /// + public Int32 NotificationCheckIntervalCount + { + get { return _notificationCheckIntervalCount; } + set + { + if (_notificationCheckIntervalCount != value) + { + _notificationCheckIntervalCount = value; + NotifyPropertyChanged("NotificationCheckIntervalCount"); + } + } + } + + /// + public Int32 NotificationReregistrationBackoff + { + get { return _notificationReregistrationBackoff; } + set + { + if (_notificationReregistrationBackoff != value) + { + _notificationReregistrationBackoff = value; + NotifyPropertyChanged("NotificationReregistrationBackoff"); + } + } + } + + /// + public Int32 ChannelReceiveBufferSize + { + get { return _channelReceiveBufferSize; } + set + { + if (_channelReceiveBufferSize != value) + { + _channelReceiveBufferSize = value; + NotifyPropertyChanged("ChannelReceiveBufferSize"); + } + } + } + + /// + public Int32 ChannelSendBufferSize + { + get { return _channelSendBufferSize; } + set + { + if (_channelSendBufferSize != value) + { + _channelSendBufferSize = value; + NotifyPropertyChanged("ChannelSendBufferSize"); + } + } + } + + /// + public Int32 ChannelReceivePacketSize + { + get { return _channelReceivePacketSize; } + set + { + if (_channelReceivePacketSize != value) + { + _channelReceivePacketSize = value; + NotifyPropertyChanged("ChannelReceivePacketSize"); + } + } + } + + /// + public void Load(String configFile) + { + String[] lines = File.ReadAllLines(configFile); + NameValueCollection nvc = new NameValueCollection(lines.Length, StringComparer.OrdinalIgnoreCase); + foreach (String line in lines) + { + String[] tmp = line.Split(new Char[] { '=' }, 2); + if (tmp.Length == 2) + nvc[tmp[0]] = tmp[1]; + } + + DefaultPort = GetInt32(nvc, "DefaultPort", "DEFAULT_COAP_PORT", DefaultPort); + DefaultSecurePort = GetInt32(nvc, "DefaultSecurePort", "DEFAULT_COAPS_PORT", DefaultSecurePort); + HttpPort = GetInt32(nvc, "HttpPort", "HTTP_PORT", HttpPort); + AckTimeout = GetInt32(nvc, "AckTimeout", "ACK_TIMEOUT", AckTimeout); + AckRandomFactor = GetDouble(nvc, "AckRandomFactor", "ACK_RANDOM_FACTOR", AckRandomFactor); + AckTimeoutScale = GetDouble(nvc, "AckTimeoutScale", "ACK_TIMEOUT_SCALE", AckTimeoutScale); + MaxRetransmit = GetInt32(nvc, "MaxRetransmit", "MAX_RETRANSMIT", MaxRetransmit); + MaxMessageSize = GetInt32(nvc, "MaxMessageSize", "MAX_MESSAGE_SIZE", MaxMessageSize); + DefaultBlockSize = GetInt32(nvc, "DefaultBlockSize", "DEFAULT_BLOCK_SIZE", DefaultBlockSize); + UseRandomIDStart = GetBoolean(nvc, "UseRandomIDStart", "USE_RANDOM_MID_START", UseRandomIDStart); + UseRandomTokenStart = GetBoolean(nvc, "UseRandomTokenStart", "USE_RANDOM_TOKEN_START", UseRandomTokenStart); + Deduplicator = GetString(nvc, "Deduplicator", "DEDUPLICATOR", Deduplicator); + CropRotationPeriod = GetInt32(nvc, "CropRotationPeriod", "CROP_ROTATION_PERIOD", CropRotationPeriod); + ExchangeLifetime = GetInt32(nvc, "ExchangeLifetime", "EXCHANGE_LIFETIME", ExchangeLifetime); + MarkAndSweepInterval = GetInt64(nvc, "MarkAndSweepInterval", "MARK_AND_SWEEP_INTERVAL", MarkAndSweepInterval); + NotificationMaxAge = GetInt64(nvc, "NotificationMaxAge", "NOTIFICATION_MAX_AGE", NotificationMaxAge); + NotificationCheckIntervalTime = GetInt64(nvc, "NotificationCheckIntervalTime", "NOTIFICATION_CHECK_INTERVAL", NotificationCheckIntervalTime); + NotificationCheckIntervalCount = GetInt32(nvc, "NotificationCheckIntervalCount", "NOTIFICATION_CHECK_INTERVAL_COUNT", NotificationCheckIntervalCount); + NotificationReregistrationBackoff = GetInt32(nvc, "NotificationReregistrationBackoff", "NOTIFICATION_REREGISTRATION_BACKOFF", NotificationReregistrationBackoff); + ChannelReceiveBufferSize = GetInt32(nvc, "ChannelReceiveBufferSize", "UDP_CONNECTOR_RECEIVE_BUFFER", ChannelReceiveBufferSize); + ChannelSendBufferSize = GetInt32(nvc, "ChannelSendBufferSize", "UDP_CONNECTOR_SEND_BUFFER", ChannelSendBufferSize); + ChannelReceivePacketSize = GetInt32(nvc, "ChannelReceivePacketSize", "UDP_CONNECTOR_DATAGRAM_SIZE", ChannelReceivePacketSize); + } + + /// + public void Store(String configFile) + { + using (StreamWriter w = File.CreateText(configFile)) + { + w.Write("DefaultPort="); w.WriteLine(DefaultPort); + w.Write("DefaultSecurePort="); w.WriteLine(DefaultSecurePort); + w.Write("HttpPort="); w.WriteLine(HttpPort); + w.Write("AckTimeout="); w.WriteLine(AckTimeout); + w.Write("AckRandomFactor="); w.WriteLine(AckRandomFactor); + w.Write("AckTimeoutScale="); w.WriteLine(AckTimeoutScale); + w.Write("MaxRetransmit="); w.WriteLine(MaxRetransmit); + w.Write("MaxMessageSize="); w.WriteLine(MaxMessageSize); + w.Write("DefaultBlockSize="); w.WriteLine(DefaultBlockSize); + w.Write("UseRandomIDStart="); w.WriteLine(UseRandomIDStart); + w.Write("UseRandomTokenStart="); w.WriteLine(UseRandomTokenStart); + w.Write("Deduplicator="); w.WriteLine(Deduplicator); + w.Write("CropRotationPeriod="); w.WriteLine(CropRotationPeriod); + w.Write("ExchangeLifetime="); w.WriteLine(ExchangeLifetime); + w.Write("MarkAndSweepInterval="); w.WriteLine(MarkAndSweepInterval); + w.Write("NotificationMaxAge="); w.WriteLine(NotificationMaxAge); + w.Write("NotificationCheckIntervalTime="); w.WriteLine(NotificationCheckIntervalTime); + w.Write("NotificationCheckIntervalCount="); w.WriteLine(NotificationCheckIntervalCount); + w.Write("NotificationReregistrationBackoff="); w.WriteLine(NotificationReregistrationBackoff); + w.Write("ChannelReceiveBufferSize="); w.WriteLine(ChannelReceiveBufferSize); + w.Write("ChannelSendBufferSize="); w.WriteLine(ChannelSendBufferSize); + w.Write("ChannelReceivePacketSize="); w.WriteLine(ChannelReceivePacketSize); + } + } + + private static String GetString(NameValueCollection nvc, String key1, String key2, String defaultValue) + { + return nvc[key1] ?? nvc[key2] ?? defaultValue; + } + + private static Int32 GetInt32(NameValueCollection nvc, String key1, String key2, Int32 defaultValue) + { + String value = GetString(nvc, key1, key2, null); + Int32 result; + return !String.IsNullOrEmpty(value) && Int32.TryParse(value, out result) ? result : defaultValue; + } + + private static Int64 GetInt64(NameValueCollection nvc, String key1, String key2, Int64 defaultValue) + { + String value = GetString(nvc, key1, key2, null); + Int64 result; + return !String.IsNullOrEmpty(value) && Int64.TryParse(value, out result) ? result : defaultValue; + } + + private static Double GetDouble(NameValueCollection nvc, String key1, String key2, Double defaultValue) + { + String value = GetString(nvc, key1, key2, null); + Double result; + return !String.IsNullOrEmpty(value) && Double.TryParse(value, out result) ? result : defaultValue; + } + + private static Boolean GetBoolean(NameValueCollection nvc, String key1, String key2, Boolean defaultValue) + { + String value = GetString(nvc, key1, key2, null); + Boolean result; + return !String.IsNullOrEmpty(value) && Boolean.TryParse(value, out result) ? result : defaultValue; + } + + private static ICoapConfig LoadConfig() + { + // TODO may have configuration file here + return new CoapConfig(); + } + + /// + public event PropertyChangedEventHandler PropertyChanged; + + private void NotifyPropertyChanged(String propertyName) + { + PropertyChangedEventHandler handler = PropertyChanged; + if (handler != null) + handler(this, new PropertyChangedEventArgs(propertyName)); + } + } +} diff --git a/WorldDirect.CoAP/CoapConstants.cs b/WorldDirect.CoAP/CoapConstants.cs new file mode 100644 index 0000000..8bcf6f1 --- /dev/null +++ b/WorldDirect.CoAP/CoapConstants.cs @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2011-2012, Longxiang He , + * SmeshLink Technology Co. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY. + * + * This file is part of the CoAP.NET, a CoAP framework in C#. + * Please see README for more information. + */ + +namespace WorldDirect.CoAP +{ + using System; + + /// + /// Constants defined for CoAP protocol + /// + public static class CoapConstants + { + /// + /// RFC 7252 CoAP version. + /// + public const Int32 Version = 0x01; + /// + /// The CoAP URI scheme. + /// + public const String UriScheme = "coap"; + /// + /// The CoAPS URI scheme. + /// + public const String SecureUriScheme = "coaps"; + /// + /// The default CoAP port for normal CoAP communication (not secure). + /// + public const Int32 DefaultPort = 5683; + /// + /// The default CoAP port for secure CoAP communication (coaps). + /// + public const Int32 DefaultSecurePort = 5684; + /// + /// The initial time (ms) for a CoAP message + /// + public const Int32 AckTimeout = 2000; + /// + /// The initial timeout is set + /// to a random number between RESPONSE_TIMEOUT and (RESPONSE_TIMEOUT * + /// RESPONSE_RANDOM_FACTOR) + /// + public const Double AckRandomFactor = 1.5D; + /// + /// The max time that a message would be retransmitted + /// + public const Int32 MaxRetransmit = 4; + /// + /// Default block size used for block-wise transfers + /// + public const Int32 DefaultBlockSize = 512; + public const Int32 MessageCacheSize = 32; + public const Int32 ReceiveBufferSize = 4096; + public const Int32 DefaultOverallTimeout = 100000; + /// + /// Default URI for wellknown resource + /// + public const String DefaultWellKnownURI = "/.well-known/core"; + public const Int32 TokenLength = 8; + public const Int32 DefaultMaxAge = 60; + /// + /// The number of notifications until a CON notification will be used. + /// + public const Int32 ObservingRefreshInterval = 10; + + public static readonly Byte[] EmptyToken = new Byte[0]; + + /// + /// The lowest value of a request code. + /// + public const Int32 RequestCodeLowerBound = 1; + + /// + /// The highest value of a request code. + /// + public const Int32 RequestCodeUpperBound = 31; + + /// + /// The lowest value of a response code. + /// + public const Int32 ResponseCodeLowerBound = 64; + + /// + /// The highest value of a response code. + /// + public const Int32 ResponseCodeUpperBound = 191; + } +} diff --git a/WorldDirect.CoAP/CoapObserveRelation.cs b/WorldDirect.CoAP/CoapObserveRelation.cs new file mode 100644 index 0000000..e850ad0 --- /dev/null +++ b/WorldDirect.CoAP/CoapObserveRelation.cs @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2011-2015, Longxiang He , + * SmeshLink Technology Co. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY. + * + * This file is part of the CoAP.NET, a CoAP framework in C#. + * Please see README for more information. + */ + +namespace WorldDirect.CoAP +{ + using System; + using Net; + using Observe; + + /// + /// Represents a CoAP observe relation between a CoAP client and a resource on a server. + /// Provides a simple API to check whether a relation has successfully established and + /// to cancel or refresh the relation. + /// + public class CoapObserveRelation + { + readonly ICoapConfig _config; + readonly Request _request; + readonly IEndPoint _endpoint; + private Boolean _canceled; + private Response _current; + private ObserveNotificationOrderer _orderer; + + public CoapObserveRelation(Request request, IEndPoint endpoint, ICoapConfig config) + { + _config = config; + _request = request; + _endpoint = endpoint; + _orderer = new ObserveNotificationOrderer(config); + + request.Reregistering += OnReregister; + } + + public Request Request + { + get { return _request; } + } + + public Response Current + { + get { return _current; } + set { _current = value; } + } + + public ObserveNotificationOrderer Orderer + { + get { return _orderer; } + } + + public Boolean Canceled + { + get { return _canceled; } + set { _canceled = value; } + } + + public void ReactiveCancel() + { + _request.IsCancelled = true; + _canceled = true; + } + + public void ProactiveCancel() + { + Request cancel = Request.NewGet(); + // copy options, but set Observe to cancel + cancel.SetOptions(_request.GetOptions()); + cancel.MarkObserveCancel(); + // use same Token + cancel.Token = _request.Token; + cancel.Destination = _request.Destination; + + // dispatch final response to the same message observers + cancel.CopyEventHandler(_request); + + cancel.Send(_endpoint); + // cancel old ongoing request + _request.IsCancelled = true; + _canceled = true; + } + + private void OnReregister(Object sender, ReregisterEventArgs e) + { + // TODO: update request in observe handle for correct cancellation? + //_request = e.RefreshRequest; + + // reset orderer to accept any sequence number since server might have rebooted + _orderer = new ObserveNotificationOrderer(_config); + } + } +} diff --git a/WorldDirect.CoAP/Code.cs b/WorldDirect.CoAP/Code.cs new file mode 100644 index 0000000..9fd7f4e --- /dev/null +++ b/WorldDirect.CoAP/Code.cs @@ -0,0 +1,400 @@ +/* + * Copyright (c) 2011-2015, Longxiang He , + * SmeshLink Technology Co. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY. + * + * This file is part of the CoAP.NET, a CoAP framework in C#. + * Please see README for more information. + */ + +namespace WorldDirect.CoAP +{ + using System; + + /// + /// This class describes the CoAP Code Registry as defined in + /// draft-ietf-core-coap-08, section 11.1 + /// + public class Code + { + public const Int32 Empty = 0; + + public const Int32 SuccessCode = 2; + public const Int32 ClientErrorCode = 4; + public const Int32 ServerErrorCode = 5; + + #region Method Codes + + /// + /// The GET method + /// + public const Int32 GET = 1; + /// + /// The POST method + /// + public const Int32 POST = 2; + /// + /// The PUT method + /// + public const Int32 PUT = 3; + /// + /// The DELETE method + /// + public const Int32 DELETE = 4; + + #endregion + + #region Response Codes + + /// + /// 2.01 Created + /// + public const Int32 Created = 65; + /// + /// 2.02 Deleted + /// + public const Int32 Deleted = 66; + /// + /// 2.03 Valid + /// + public const Int32 Valid = 67; + /// + /// 2.04 Changed + /// + public const Int32 Changed = 68; + /// + /// 2.05 Content + /// + public const Int32 Content = 69; + /// + /// 2.?? Continue + /// + public const Int32 Continue = 95; + /// + /// 4.00 Bad Request + /// + public const Int32 BadRequest = 128; + /// + /// 4.01 Unauthorized + /// + public const Int32 Unauthorized = 129; + /// + /// 4.02 Bad Option + /// + public const Int32 BadOption = 130; + /// + /// 4.03 Forbidden + /// + public const Int32 Forbidden = 131; + /// + /// 4.04 Not Found + /// + public const Int32 NotFound = 132; + /// + /// 4.05 Method Not Allowed + /// + public const Int32 MethodNotAllowed = 133; + /// + /// 4.06 Not Acceptable + /// + public const Int32 NotAcceptable = 134; + /// + /// 4.08 Request Entity Incomplete (draft-ietf-core-block) + /// + public const Int32 RequestEntityIncomplete = 136; + /// + /// + /// + public const Int32 PreconditionFailed = 140; + /// + /// 4.13 Request Entity Too Large + /// + public const Int32 RequestEntityTooLarge = 141; + /// + /// 4.15 Unsupported Media Type + /// + public const Int32 UnsupportedMediaType = 143; + /// + /// 5.00 Internal Server Error + /// + public const Int32 InternalServerError = 160; + /// + /// 5.01 Not Implemented + /// + public const Int32 NotImplemented = 161; + /// + /// 5.02 Bad Gateway + /// + public const Int32 BadGateway = 162; + /// + /// 5.03 Service Unavailable + /// + public const Int32 ServiceUnavailable = 163; + /// + /// 5.04 Gateway Timeout + /// + public const Int32 GatewayTimeout = 164; + /// + /// 5.05 Proxying Not Supported + /// + public const Int32 ProxyingNotSupported = 165; + + #endregion + + public static Int32 GetResponseClass(Int32 code) + { + return (code >> 5) & 0x7; + } + + /// + /// Checks whether a code indicates a request + /// + /// The code to be checked + /// True iff the code indicates a request + public static Boolean IsRequest(Int32 code) + { + return (code >= 1) && (code <= 31); + } + + /// + /// Checks whether a code indicates a response + /// + /// The code to be checked + /// True iff the code indicates a response + public static Boolean IsResponse(Int32 code) + { + return (code >= 64) && (code <= 191); + } + + /// + /// Checks whether a code represents a success code. + /// + public static Boolean IsSuccess(Int32 code) + { + return code >= 64 && code < 96; + } + + /// + /// Checks whether a code is valid + /// + /// The code to be checked + /// True iff the code is valid + public static Boolean IsValid(Int32 code) + { + // allow unknown custom codes + return (code >= 0) && (code <= 255); + } + + /// + /// Returns a string representation of the code + /// + /// The code to be described + /// A string describing the code + public static String ToString(Int32 code) + { + switch (code) + { + case Empty: + return "Empty Message"; + case GET: + return "GET"; + case POST: + return "POST"; + case PUT: + return "PUT"; + case DELETE: + return "DELETE"; + case Created: + return "2.01 Created"; + case Deleted: + return "2.02 Deleted"; + case Valid: + return "2.03 Valid"; + case Changed: + return "2.04 Changed"; + case Content: + return "2.05 Content"; + case BadRequest: + return "4.00 Bad Request"; + case Unauthorized: + return "4.01 Unauthorized"; + case BadOption: + return "4.02 Bad Option"; + case Forbidden: + return "4.03 Forbidden"; + case NotFound: + return "4.04 Not Found"; + case MethodNotAllowed: + return "4.05 Method Not Allowed"; + case NotAcceptable: + return "4.06 Not Acceptable"; + case RequestEntityIncomplete: + return "4.08 Request Entity Incomplete"; + case PreconditionFailed: + return "4.12 Precondition Failed"; + case RequestEntityTooLarge: + return "4.13 Request Entity Too Large"; + case UnsupportedMediaType: + return "4.15 Unsupported Media Type"; + case InternalServerError: + return "5.00 Internal Server Error"; + case NotImplemented: + return "5.01 Not Implemented"; + case BadGateway: + return "5.02 Bad Gateway"; + case ServiceUnavailable: + return "5.03 Service Unavailable"; + case GatewayTimeout: + return "5.04 Gateway Timeout"; + case ProxyingNotSupported: + return "5.05 Proxying Not Supported"; + default: + break; + } + + if (IsValid(code)) + { + if (IsRequest(code)) + { + return String.Format("Unknown Request [code {0}]", code); + } + else if (IsResponse(code)) + { + return String.Format("Unknown Response [code {0}]", code); + } + else + { + return String.Format("Reserved [code {0}]", code); + } + } + else + { + return String.Format("Invalid Message [code {0}]", 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/Codec/DatagramReader.cs b/WorldDirect.CoAP/Codec/DatagramReader.cs new file mode 100644 index 0000000..60898ab --- /dev/null +++ b/WorldDirect.CoAP/Codec/DatagramReader.cs @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2011-2014, Longxiang He , + * SmeshLink Technology Co. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY. + * + * This file is part of the CoAP.NET, a CoAP framework in C#. + * Please see README for more information. + */ + +namespace WorldDirect.CoAP.Codec +{ + using System; + using System.IO; + + /// + /// This class describes the functionality to read raw network-ordered datagrams on bit-level. + /// + public class DatagramReader + { + private MemoryStream _stream; + private Byte _currentByte; + private Int32 _currentBitIndex; + + /// + /// Initializes a new DatagramReader object + /// + /// The byte array to read from + public DatagramReader(Byte[] buffer) + { + _stream = new MemoryStream(buffer, false); + _currentByte = 0; + _currentBitIndex = -1; + } + + /// + /// Reads a sequence of bits from the stream + /// + /// The number of bits to read + /// An integer containing the bits read + public Int32 Read(Int32 numBits) + { + Int32 bits = 0; // initialize all bits to zero + for (Int32 i = numBits - 1; i >= 0; i--) + { + // check whether new byte needs to be read + if (_currentBitIndex < 0) + { + ReadCurrentByte(); + } + + // test current bit + Boolean bit = (_currentByte >> _currentBitIndex & 1) != 0; + if (bit) + { + // set bit at i-th position + bits |= (1 << i); + } + + // decrease current bit index + --_currentBitIndex; + } + return bits; + } + + /// + /// Reads a sequence of bytes from the stream + /// + /// The number of bytes to read + /// The sequence of bytes read from the stream + public Byte[] ReadBytes(Int32 count) + { + // for negative count values, read all bytes left + if (count < 0) + count = (Int32)(_stream.Length - _stream.Position); + + Byte[] bytes = new Byte[count]; + + // are there bits left to read in buffer? + if (_currentBitIndex >= 0) + { + for (Int32 i = 0; i < count; i++) + { + bytes[i] = (Byte)Read(8); + } + } + else + { + _stream.Read(bytes, 0, bytes.Length); + } + + return bytes; + } + + /// + /// Reads the next byte from the stream. + /// + public Byte ReadNextByte() + { + return ReadBytes(1)[0]; + } + + /// + /// Reads the complete sequence of bytes left in the stream + /// + /// The sequence of bytes left in the stream + public Byte[] ReadBytesLeft() + { + return ReadBytes(-1); + } + + /// + /// Checks if there are remaining bytes to read. + /// + public Boolean BytesAvailable + { + get { return _stream.Length - _stream.Position > 0; } + } + + private void ReadCurrentByte() + { + Int32 val = _stream.ReadByte(); + + if (val >= 0) + _currentByte = (Byte)val; + else + // EOF + _currentByte = 0; + + // reset current bit index + _currentBitIndex = 7; + } + } +} diff --git a/WorldDirect.CoAP/Codec/DatagramWriter.cs b/WorldDirect.CoAP/Codec/DatagramWriter.cs new file mode 100644 index 0000000..971469d --- /dev/null +++ b/WorldDirect.CoAP/Codec/DatagramWriter.cs @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2011-2014, Longxiang He , + * SmeshLink Technology Co. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY. + * + * This file is part of the CoAP.NET, a CoAP framework in C#. + * Please see README for more information. + */ + +namespace WorldDirect.CoAP.Codec +{ + using System; + using System.IO; + using Log; + + /// + /// 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)); + + private MemoryStream _stream; + private Byte _currentByte; + private Int32 _currentBitIndex; + + /// + /// Initializes a new DatagramWriter object + /// + public DatagramWriter() + { + _stream = new MemoryStream(); + _currentByte = 0; + _currentBitIndex = 7; + } + + /// + /// Writes a sequence of bits to the stream + /// + /// An integer containing the bits to write + /// The number of bits to write + public void Write(Int32 data, Int32 numBits) + { + if (numBits < 32 && data >= (1 << numBits)) + { + if (log.IsWarnEnabled) + log.Warn(String.Format("Truncating value {0} to {1}-bit integer", data, numBits)); + } + + for (Int32 i = numBits - 1; i >= 0; i--) + { + // test bit + Boolean bit = (data >> i & 1) != 0; + if (bit) + { + // set bit in current byte + _currentByte |= (Byte)(1 << _currentBitIndex); + } + + // decrease current bit index + --_currentBitIndex; + + // check if current byte can be written + if (_currentBitIndex < 0) + { + WriteCurrentByte(); + } + } + } + + /// + /// Writes a sequence of bytes to the stream + /// + /// The sequence of bytes to write + public void WriteBytes(Byte[] bytes) + { + // check if anything to do at all + if (bytes == null) + return; + + // are there bits left to write in buffer? + if (_currentBitIndex < 7) + { + for (int i = 0; i < bytes.Length; i++) + { + Write(bytes[i], 8); + } + } + else + { + // if bit buffer is empty, call can be delegated + // to byte stream to increase + _stream.Write(bytes, 0, bytes.Length); + } + } + + /// + /// Writes one byte to the stream. + /// + public void WriteByte(Byte b) + { + WriteBytes(new Byte[] { b }); + } + + /// + /// Returns a byte array containing the sequence of bits written + /// + /// The byte array containing the written bits + public Byte[] ToByteArray() + { + WriteCurrentByte(); + Byte[] byteArray = _stream.ToArray(); + _stream.Position = 0; + return byteArray; + } + + private void WriteCurrentByte() + { + if (_currentBitIndex < 7) + { + _stream.WriteByte(_currentByte); + _currentByte = 0; + _currentBitIndex = 7; + } + } + } +} diff --git a/WorldDirect.CoAP/Codec/IMessageDecoder.cs b/WorldDirect.CoAP/Codec/IMessageDecoder.cs new file mode 100644 index 0000000..d559883 --- /dev/null +++ b/WorldDirect.CoAP/Codec/IMessageDecoder.cs @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2011-2014, Longxiang He , + * SmeshLink Technology Co. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY. + * + * This file is part of the CoAP.NET, a CoAP framework in C#. + * Please see README for more information. + */ + +namespace WorldDirect.CoAP.Codec +{ + using System; + + /// + /// Provides methods to parse incoming byte arrays to messages. + /// + public interface IMessageDecoder + { + /// + /// Checks if the decoding message is wellformed. + /// + Boolean IsWellFormed { get; } + /// + /// Checks if the decoding message is a reply. + /// + Boolean IsReply { get; } + /// + /// Checks if the decoding message is a request. + /// + Boolean IsRequest { get; } + /// + /// Checks if the decoding message is a response. + /// + Boolean IsResponse { get; } + /// + /// Checks if the decoding message is an empty message. + /// + Boolean IsEmpty { get; } + /// + /// Gets the version of the decoding message. + /// + Int32 Version { get; } + /// + /// Gets the id of the decoding message. + /// + Int32 ID { get; } + /// + /// Decodes as a . + /// + /// the decoded request + Request DecodeRequest(); + /// + /// Decodes as a . + /// + /// the decoded response + Response DecodeResponse(); + /// + /// Decodes as a . + /// + /// the decoded empty message + EmptyMessage DecodeEmptyMessage(); + /// + /// Decodes as a CoAP message. + /// + /// the decoded message, or null if not be recognized. + Message Decode(); + } +} diff --git a/WorldDirect.CoAP/Codec/IMessageEncoder.cs b/WorldDirect.CoAP/Codec/IMessageEncoder.cs new file mode 100644 index 0000000..543d736 --- /dev/null +++ b/WorldDirect.CoAP/Codec/IMessageEncoder.cs @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2011-2014, Longxiang He , + * SmeshLink Technology Co. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY. + * + * This file is part of the CoAP.NET, a CoAP framework in C#. + * Please see README for more information. + */ + +namespace WorldDirect.CoAP.Codec +{ + using System; + + /// + /// Provides methods to serialize outgoing messages to byte arrays. + /// + public interface IMessageEncoder + { + /// + /// Encodes a request into a bytes array. + /// + /// the request to encode + /// the encoded bytes + Byte[] Encode(Request request); + /// + /// Encodes a response into a bytes array. + /// + /// the response to encode + /// the encoded bytes + Byte[] Encode(Response response); + /// + /// Encodes an empty message into a bytes array. + /// + /// the empty message to encode + /// the encoded bytes + Byte[] Encode(EmptyMessage message); + /// + /// Encodes a CoAP message into a bytes array. + /// + /// the message to encode + /// + /// the encoded bytes, or null if the message can not be encoded, + /// i.e. the message is not a , a or an . + /// + Byte[] Encode(Message message); + } +} diff --git a/WorldDirect.CoAP/Codec/MessageDecoder.cs b/WorldDirect.CoAP/Codec/MessageDecoder.cs new file mode 100644 index 0000000..5491566 --- /dev/null +++ b/WorldDirect.CoAP/Codec/MessageDecoder.cs @@ -0,0 +1,159 @@ +/* + * Copyright (c) 2011-2014, Longxiang He , + * SmeshLink Technology Co. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY. + * + * This file is part of the CoAP.NET, a CoAP framework in C#. + * Please see README for more information. + */ + +namespace WorldDirect.CoAP.Codec +{ + using System; + + /// + /// Base class for message decoders. + /// + public abstract class MessageDecoder : IMessageDecoder + { + /// + /// the bytes reader + /// + protected DatagramReader m_reader; + /// + /// the version of the decoding message + /// + protected Int32 m_version; + /// + /// the type of the decoding message + /// + protected MessageType m_type; + /// + /// the length of token + /// + protected Int32 m_tokenLength; + /// + /// the code of the decoding message + /// + protected Int32 m_code; + /// + /// the id of the decoding message + /// + protected Int32 m_id; + + /// + /// Instantiates. + /// + /// the bytes array to decode + public MessageDecoder(Byte[] data) + { + m_reader = new DatagramReader(data); + } + + /// + /// Reads protocol headers. + /// + protected abstract void ReadProtocol(); + + /// + public abstract Boolean IsWellFormed { get; } + + /// + public Boolean IsReply + { + get { return m_type == MessageType.ACK || m_type == MessageType.RST; } + } + + /// + public virtual Boolean IsRequest + { + get + { + return m_code >= CoapConstants.RequestCodeLowerBound && + m_code <= CoapConstants.RequestCodeUpperBound; + } + } + + /// + public virtual Boolean IsResponse + { + get + { + return m_code >= CoapConstants.ResponseCodeLowerBound && + m_code <= CoapConstants.ResponseCodeUpperBound; + } + } + + /// + public Boolean IsEmpty + { + get { return m_code == Code.Empty; } + } + + /// + public Int32 Version + { + get { return m_version; } + } + + /// + public Int32 ID + { + get { return m_id; } + } + + /// + public Request DecodeRequest() + { + System.Diagnostics.Debug.Assert(IsRequest); + Request request = new Request((Method)m_code); + request.Type = m_type; + request.ID = m_id; + ParseMessage(request); + return request; + } + + /// + public Response DecodeResponse() + { + System.Diagnostics.Debug.Assert(IsResponse); + Response response = new Response((StatusCode)m_code); + response.Type = m_type; + response.ID = m_id; + ParseMessage(response); + return response; + } + + /// + public EmptyMessage DecodeEmptyMessage() + { + System.Diagnostics.Debug.Assert(!IsRequest && !IsResponse); + EmptyMessage message = new EmptyMessage(m_type); + message.Type = m_type; + message.ID = m_id; + ParseMessage(message); + return message; + } + + /// + public Message Decode() + { + if (IsRequest) + return DecodeRequest(); + else if (IsResponse) + return DecodeResponse(); + else if (IsEmpty) + return DecodeEmptyMessage(); + else + return null; + } + + /// + /// Parses the rest data other than protocol headers into the given message. + /// + /// + protected abstract void ParseMessage(Message message); + } +} diff --git a/WorldDirect.CoAP/Codec/MessageEncoder.cs b/WorldDirect.CoAP/Codec/MessageEncoder.cs new file mode 100644 index 0000000..bcb67a0 --- /dev/null +++ b/WorldDirect.CoAP/Codec/MessageEncoder.cs @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2011-2014, Longxiang He , + * SmeshLink Technology Co. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY. + * + * This file is part of the CoAP.NET, a CoAP framework in C#. + * Please see README for more information. + */ + +namespace WorldDirect.CoAP.Codec +{ + using System; + + /// + /// Base class for message encoders. + /// + public abstract class MessageEncoder : IMessageEncoder + { + /// + public Byte[] Encode(Request request) + { + DatagramWriter writer = new DatagramWriter(); + Serialize(writer, request, request.Code); + return writer.ToByteArray(); + } + + /// + public Byte[] Encode(Response response) + { + DatagramWriter writer = new DatagramWriter(); + Serialize(writer, response, response.Code); + return writer.ToByteArray(); + } + + /// + public Byte[] Encode(EmptyMessage message) + { + DatagramWriter writer = new DatagramWriter(); + Serialize(writer, message, Code.Empty); + return writer.ToByteArray(); + } + + /// + public Byte[] Encode(Message message) + { + if (message.IsRequest) + return Encode((Request)message); + else if (message.IsResponse) + return Encode((Response)message); + else if (message is EmptyMessage) + return Encode((EmptyMessage)message); + else + return null; + } + + /// + /// Serializes a message. + /// + /// the writer + /// the message to write + /// the code + protected abstract void Serialize(DatagramWriter writer, Message message, Int32 code); + } +} diff --git a/WorldDirect.CoAP/Deduplication/CropRotation.cs b/WorldDirect.CoAP/Deduplication/CropRotation.cs new file mode 100644 index 0000000..57b55a0 --- /dev/null +++ b/WorldDirect.CoAP/Deduplication/CropRotation.cs @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2011-2014, Longxiang He , + * SmeshLink Technology Co. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY. + * + * This file is part of the CoAP.NET, a CoAP framework in C#. + * Please see README for more information. + */ + +namespace WorldDirect.CoAP.Deduplication +{ + using System; + using System.Collections.Concurrent; + using System.Threading; + using Net; + + class CropRotation : IDeduplicator, IDisposable + { + private ConcurrentDictionary[] _maps; + private Int32 _first; + private Int32 _second; + private Timer _timer; + private ICoapConfig _coapConfig; + + public CropRotation(ICoapConfig config) + { + _maps = new ConcurrentDictionary[3]; + _maps[0] = new ConcurrentDictionary(); + _maps[1] = new ConcurrentDictionary(); + _maps[2] = new ConcurrentDictionary(); + _first = 0; + _second = 1; + _coapConfig = config; + } + + private void Rotation(object state) + { + Int32 third = _first; + _first = _second; + _second = (_second + 1) % 3; + _maps[third].Clear(); + } + + /// + public void Start() + { + _timer = new Timer(Rotation, null, _coapConfig.CropRotationPeriod, _coapConfig.CropRotationPeriod); + } + + /// + public void Stop() + { + Dispose(); + Clear(); + } + + /// + public void Clear() + { + _maps[0].Clear(); + _maps[1].Clear(); + _maps[2].Clear(); + } + + /// + public Exchange FindPrevious(Exchange.KeyID key, Exchange exchange) + { + Int32 f = _first, s = _second; + Exchange prev = null; + + _maps[f].AddOrUpdate(key, exchange, (k, v) => + { + prev = v; + return exchange; + }); + if (prev != null || f == s) + return prev; + + prev = _maps[s].AddOrUpdate(key, exchange, (k, v) => + { + prev = v; + return exchange; + }); + return prev; + } + + /// + public Exchange Find(Exchange.KeyID key) + { + Int32 f = _first, s = _second; + Exchange prev; + if (_maps[f].TryGetValue(key, out prev) || f == s) + return prev; + _maps[s].TryGetValue(key, out prev); + return prev; + } + + /// + public void Dispose() + { + _timer?.Dispose(); + } + } +} diff --git a/WorldDirect.CoAP/Deduplication/DeduplicatorFactory.cs b/WorldDirect.CoAP/Deduplication/DeduplicatorFactory.cs new file mode 100644 index 0000000..daaa932 --- /dev/null +++ b/WorldDirect.CoAP/Deduplication/DeduplicatorFactory.cs @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2011-2014, Longxiang He , + * SmeshLink Technology Co. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY. + * + * This file is part of the CoAP.NET, a CoAP framework in C#. + * Please see README for more information. + */ + +namespace WorldDirect.CoAP.Deduplication +{ + using System; + using Log; + + static class DeduplicatorFactory + { + static readonly ILogger log = LogManager.GetLogger(typeof(DeduplicatorFactory)); + public const String MarkAndSweepDeduplicator = "MarkAndSweep"; + public const String CropRotationDeduplicator = "CropRotation"; + public const String NoopDeduplicator = "Noop"; + + public static IDeduplicator CreateDeduplicator(ICoapConfig config) + { + String type = config.Deduplicator; + if (String.Equals(MarkAndSweepDeduplicator, type, StringComparison.OrdinalIgnoreCase) + || String.Equals("DEDUPLICATOR_MARK_AND_SWEEP", type, StringComparison.OrdinalIgnoreCase)) + return new SweepDeduplicator(config); + else if (String.Equals(CropRotationDeduplicator, type, StringComparison.OrdinalIgnoreCase) + || String.Equals("DEDUPLICATOR_CROP_ROTATIO", type, StringComparison.OrdinalIgnoreCase)) + return new CropRotation(config); + else if (!String.Equals(NoopDeduplicator, type, StringComparison.OrdinalIgnoreCase) + && !String.Equals("NO_DEDUPLICATOR", type, StringComparison.OrdinalIgnoreCase)) + { + if (log.IsWarnEnabled) + log.Warn("Unknown deduplicator type: " + type); + } + return new NoopDeduplicator(); + } + } +} diff --git a/WorldDirect.CoAP/Deduplication/IDeduplicator.cs b/WorldDirect.CoAP/Deduplication/IDeduplicator.cs new file mode 100644 index 0000000..01bf5d9 --- /dev/null +++ b/WorldDirect.CoAP/Deduplication/IDeduplicator.cs @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2011-2014, Longxiang He , + * SmeshLink Technology Co. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY. + * + * This file is part of the CoAP.NET, a CoAP framework in C#. + * Please see README for more information. + */ + +namespace WorldDirect.CoAP.Deduplication +{ + using Net; + + /// + /// Provides methods to detect duplicates. + /// Notice that CONs and NONs can be duplicates. + /// + public interface IDeduplicator + { + /// + /// Starts. + /// + void Start(); + /// + /// Stops. + /// + void Stop(); + /// + /// Clears the state of this deduplicator. + /// + void Clear(); + /// + /// Checks if the specified key is already associated with a previous + /// exchange and otherwise associates the key with the exchange specified. + /// + /// + /// + /// the previous exchange associated with the specified key, + /// or null if there was no mapping for the key + Exchange FindPrevious(Exchange.KeyID key, Exchange exchange); + Exchange Find(Exchange.KeyID key); + } +} diff --git a/WorldDirect.CoAP/Deduplication/NoopDeduplicator.cs b/WorldDirect.CoAP/Deduplication/NoopDeduplicator.cs new file mode 100644 index 0000000..3a56da3 --- /dev/null +++ b/WorldDirect.CoAP/Deduplication/NoopDeduplicator.cs @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2011-2014, Longxiang He , + * SmeshLink Technology Co. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY. + * + * This file is part of the CoAP.NET, a CoAP framework in C#. + * Please see README for more information. + */ + +namespace WorldDirect.CoAP.Deduplication +{ + using Net; + + /// + /// A dummy implementation that does no deduplication. + /// + class NoopDeduplicator : IDeduplicator + { + /// + public void Start() + { + // do nothing + } + + /// + public void Stop() + { + // do nothing + } + + /// + public void Clear() + { + // do nothing + } + + /// + public Exchange FindPrevious(Exchange.KeyID key, Exchange exchange) + { + return null; + } + + /// + public Exchange Find(Exchange.KeyID key) + { + return null; + } + } +} diff --git a/WorldDirect.CoAP/Deduplication/SweepDeduplicator.cs b/WorldDirect.CoAP/Deduplication/SweepDeduplicator.cs new file mode 100644 index 0000000..6ad550c --- /dev/null +++ b/WorldDirect.CoAP/Deduplication/SweepDeduplicator.cs @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2011-2014, Longxiang He , + * SmeshLink Technology Co. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY. + * + * This file is part of the CoAP.NET, a CoAP framework in C#. + * Please see README for more information. + */ + +namespace WorldDirect.CoAP.Deduplication +{ + using System; + using System.Collections.Concurrent; + using System.Collections.Generic; + using System.Threading; + using Log; + using Net; + + class SweepDeduplicator : IDeduplicator + { + static readonly ILogger log = LogManager.GetLogger(typeof(SweepDeduplicator)); + + private ConcurrentDictionary _incommingMessages + = new ConcurrentDictionary(); + private Timer _timer; + private ICoapConfig _config; + + public SweepDeduplicator(ICoapConfig config) + { + _config = config; + } + + private void Sweep(object state) + { + if (log.IsDebugEnabled) + log.Debug("Start Mark-And-Sweep with " + _incommingMessages.Count + " entries"); + + DateTime oldestAllowed = DateTime.Now.AddMilliseconds(-_config.ExchangeLifetime); + List keysToRemove = new List(); + foreach (KeyValuePair pair in _incommingMessages) + { + if (pair.Value.Timestamp < oldestAllowed) + { + if (log.IsDebugEnabled) + log.Debug("Mark-And-Sweep removes " + pair.Key); + keysToRemove.Add(pair.Key); + } + } + if (keysToRemove.Count > 0) + { + Exchange ex; + foreach (Exchange.KeyID key in keysToRemove) + { + _incommingMessages.TryRemove(key, out ex); + } + } + } + + /// + public void Start() + { + _timer = new Timer(Sweep, null, TimeSpan.FromMilliseconds(_config.MarkAndSweepInterval), TimeSpan.FromMilliseconds(_config.MarkAndSweepInterval)); + } + + /// + public void Stop() + { + Dispose(); + Clear(); + } + + /// + public void Clear() + { + _incommingMessages.Clear(); + } + + /// + public Exchange FindPrevious(Exchange.KeyID key, Exchange exchange) + { + Exchange prev = null; + _incommingMessages.AddOrUpdate(key, exchange, (k, v) => + { + prev = v; + return exchange; + }); + return prev; + } + + /// + public Exchange Find(Exchange.KeyID key) + { + Exchange prev; + _incommingMessages.TryGetValue(key, out prev); + return prev; + } + + /// + public void Dispose() + { + _timer?.Dispose(); + } + } +} diff --git a/WorldDirect.CoAP/EmptyMessage.cs b/WorldDirect.CoAP/EmptyMessage.cs new file mode 100644 index 0000000..e93e707 --- /dev/null +++ b/WorldDirect.CoAP/EmptyMessage.cs @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2011-2014, Longxiang He , + * SmeshLink Technology Co. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY. + * + * This file is part of the CoAP.NET, a CoAP framework in C#. + * Please see README for more information. + */ + +namespace WorldDirect.CoAP +{ + /// + /// Represents an empty CoAP message. An empty message has either + /// the ACK or RST. + /// + public class EmptyMessage : Message + { + /// + /// Instantiates a new empty message. + /// + public EmptyMessage(MessageType type) + : base(type, CoAP.Code.Empty) + { } + + /// + /// Create a new acknowledgment for the specified message. + /// + /// the message to acknowledge + /// the acknowledgment + public static EmptyMessage NewACK(Message message) + { + EmptyMessage ack = new EmptyMessage(MessageType.ACK); + ack.ID = message.ID; + ack.Token = CoapConstants.EmptyToken; + ack.Destination = message.Source; + return ack; + } + + /// + /// Create a new reset message for the specified message. + /// + /// the message to reject + /// the reset + public static EmptyMessage NewRST(Message message) + { + EmptyMessage rst = new EmptyMessage(MessageType.RST); + rst.ID = message.ID; + rst.Token = CoapConstants.EmptyToken; + rst.Destination = message.Source; + return rst; + } + } +} diff --git a/WorldDirect.CoAP/EndPoint/Resources/RemoteResource.cs b/WorldDirect.CoAP/EndPoint/Resources/RemoteResource.cs new file mode 100644 index 0000000..ba00fa1 --- /dev/null +++ b/WorldDirect.CoAP/EndPoint/Resources/RemoteResource.cs @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2011-2012, Longxiang He , + * SmeshLink Technology Co. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY. + * + * This file is part of the CoAP.NET, a CoAP framework in C#. + * Please see README for more information. + */ + +namespace WorldDirect.CoAP.EndPoint.Resources +{ + using System; + + public class RemoteResource : Resource + { + public RemoteResource(String resourceIdentifier) + : base(resourceIdentifier) + { } + + public static RemoteResource NewRoot(String linkFormat) + { + return LinkFormat.Deserialize(linkFormat); + } + + /// + /// Creates a resouce instance with proper subtype. + /// + /// + protected override Resource CreateInstance(String name) + { + return new RemoteResource(name); + } + + protected override void DoCreateSubResource(Request request, String newIdentifier) + { + } + } +} diff --git a/WorldDirect.CoAP/EndPoint/Resources/Resource.cs b/WorldDirect.CoAP/EndPoint/Resources/Resource.cs new file mode 100644 index 0000000..e444097 --- /dev/null +++ b/WorldDirect.CoAP/EndPoint/Resources/Resource.cs @@ -0,0 +1,523 @@ +/* + * Copyright (c) 2011-2012, Longxiang He , + * SmeshLink Technology Co. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY. + * + * This file is part of the CoAP.NET, a CoAP framework in C#. + * Please see README for more information. + */ + +namespace WorldDirect.CoAP.EndPoint.Resources +{ + using System; + using System.Collections.Generic; + using System.Text; + using Log; + + /// + /// This class describes the functionality of a CoAP resource. + /// + public abstract class Resource : IComparable + { + private static ILogger log = LogManager.GetLogger(typeof(Resource)); + + private Int32 _totalSubResourceCount; + private String _resourceIdentifier; + private HashSet _attributes; + private Resource _parent; + private SortedDictionary _subResources; + private Boolean _hidden; + + /// + /// Initialize a resource. + /// + /// The identifier of this resource + public Resource(String resourceIdentifier) : this(resourceIdentifier, false) { } + + /// + /// Initialize a resource. + /// + /// The identifier of this resource + /// True if this resource is hidden + public Resource(String resourceIdentifier, Boolean hidden) + { + this._resourceIdentifier = resourceIdentifier; + this._hidden = hidden; + this._attributes = new HashSet(); + } + + /// + /// Gets the URI of this resource. + /// + public String Path + { + get + { + StringBuilder sb = new StringBuilder(); + sb.Append(Name); + if (_parent == null) + sb.Append("/"); + else + { + Resource res = _parent; + while (res != null) + { + sb.Insert(0, "/"); + sb.Insert(0, res.Name); + res = res._parent; + } + } + return sb.ToString(); + } + } + + public String Name + { + get { return _resourceIdentifier; } + set { _resourceIdentifier = value; } + } + + public ICollection Attributes + { + get { return _attributes; } + } + + public IList GetAttributes(String name) + { + List list = new List(); + foreach (LinkAttribute attr in Attributes) + { + if (attr.Name.Equals(name)) + list.Add(attr); + } + return list.AsReadOnly(); + } + + public Boolean SetAttribute(LinkAttribute attr) + { + // Adds depending on the Link Format rules + return LinkFormat.AddAttribute(Attributes, attr); + } + + public Boolean ClearAttribute(String name) + { + Boolean cleared = false; + foreach (LinkAttribute attr in GetAttributes(name)) + { + cleared |= _attributes.Remove(attr); + } + return cleared; + } + + public Boolean Hidden + { + get { return _hidden; } + set { _hidden = value; } + } + + public IList ResourceTypes + { + get + { + return GetStringValues(GetAttributes(LinkFormat.ResourceType)); + } + } + + /// + /// Gets or sets the type attribute of this resource. + /// + public String ResourceType + { + get + { + IList attrs = GetAttributes(LinkFormat.ResourceType); + return attrs.Count == 0 ? null : attrs[0].StringValue; + } + set + { + SetAttribute(new LinkAttribute(LinkFormat.ResourceType, value)); + } + } + + /// + /// Gets or sets the title attribute of this resource. + /// + public String Title + { + get + { + IList attrs = GetAttributes(LinkFormat.Title); + return attrs.Count == 0 ? null : attrs[0].StringValue; + } + set + { + ClearAttribute(LinkFormat.Title); + SetAttribute(new LinkAttribute(LinkFormat.Title, value)); + } + } + + public IList InterfaceDescriptions + { + get + { + return GetStringValues(GetAttributes(LinkFormat.InterfaceDescription)); + } + } + + /// + /// Gets or sets the interface description attribute of this resource. + /// + public String InterfaceDescription + { + get + { + IList attrs = GetAttributes(LinkFormat.InterfaceDescription); + return attrs.Count == 0 ? null : attrs[0].StringValue; + } + set + { + SetAttribute(new LinkAttribute(LinkFormat.InterfaceDescription, value)); + } + } + + public IList GetContentTypeCodes + { + get + { + return GetIntValues(GetAttributes(LinkFormat.ContentType)); + } + } + + /// + /// Gets or sets the content type code attribute of this resource. + /// + public Int32 ContentTypeCode + { + get + { + IList attrs = GetAttributes(LinkFormat.ContentType); + return attrs.Count == 0 ? 0 : attrs[0].IntValue; + } + set + { + SetAttribute(new LinkAttribute(LinkFormat.ContentType, value)); + } + } + + /// + /// Gets or sets the maximum size estimate attribute of this resource. + /// + public Int32 MaximumSizeEstimate + { + get + { + IList attrs = GetAttributes(LinkFormat.MaxSizeEstimate); + return attrs.Count == 0 ? -1 : attrs[0].IntValue; + } + set + { + SetAttribute(new LinkAttribute(LinkFormat.MaxSizeEstimate, value)); + } + } + + /// + /// Gets or sets the observable attribute of this resource. + /// + public Boolean Observable + { + get + { + return GetAttributes(LinkFormat.Observable).Count > 0; + } + set + { + if (value) + SetAttribute(new LinkAttribute(LinkFormat.Observable, value)); + else + ClearAttribute(LinkFormat.Observable); + } + } + + /// + /// Gets the total count of sub-resources, including children and children's children... + /// + public Int32 TotalSubResourceCount + { + get { return _totalSubResourceCount; } + } + + /// + /// Gets the count of sub-resources of this resource. + /// + public Int32 SubResourceCount + { + get { return null == _subResources ? 0 : _subResources.Count; } + } + + /// + /// Removes this resource from its parent. + /// + public void Remove() + { + if (_parent != null) + _parent.RemoveSubResource(this); + } + + /// + /// Gets sub-resources of this resource. + /// + /// + public Resource[] GetSubResources() + { + if (null == _subResources) + return new Resource[0]; + + Resource[] resources = new Resource[_subResources.Count]; + this._subResources.Values.CopyTo(resources, 0); + return resources; + } + + public Resource GetResource(String path) + { + return GetResource(path, false); + } + + public Resource GetResource(String path, Boolean last) + { + if (String.IsNullOrEmpty(path)) + return this; + + // find root for absolute path + if (path.StartsWith("/")) + { + Resource root = this; + while (root._parent != null) + root = root._parent; + path = path.Equals("/") ? null : path.Substring(1); + return root.GetResource(path); + } + + Int32 pos = path.IndexOf('/'); + String head = null, tail = null; + + // note: "some/resource/" addresses a resource "" under "resource" + if (pos == -1) + { + head = path; + } + else + { + head = path.Substring(0, pos); + tail = path.Substring(pos + 1); + } + + if (SubResources.ContainsKey(head)) + return SubResources[head].GetResource(tail, last); + else if (last) + return this; + else + return null; + } + + private SortedDictionary SubResources + { + get + { + if (_subResources == null) + _subResources = new SortedDictionary(); + return _subResources; + } + } + + /// + /// Adds a resource as a sub-resource of this resource. + /// + /// The sub-resource to be added + public void AddSubResource(Resource resource) + { + if (null == resource) + throw new ArgumentNullException("resource"); + + // no absolute paths allowed, use root directly + while (resource.Name.StartsWith("/")) + { + if (_parent != null) + { + if (log.IsWarnEnabled) + log.Warn("Adding absolute path only allowed for root: made relative " + resource.Name); + } + resource.Name = resource.Name.Substring(1); + } + + // get last existing resource along path + Resource baseRes = GetResource(resource.Name, true); + + String path = this.Path; + if (!path.EndsWith("/")) + path += "/"; + path += resource.Name; + + path = path.Substring(baseRes.Path.Length); + if (path.StartsWith("/")) + path = path.Substring(1); + + if (path.Length == 0) + { + // resource replaces base + if (log.IsInfoEnabled) + log.Info("Replacing resource " + baseRes.Path); + foreach (Resource sub in baseRes.GetSubResources()) + { + sub._parent = resource; + resource.SubResources[sub.Name] = sub; + } + resource._parent = baseRes._parent; + baseRes._parent.SubResources[baseRes.Name] = resource; + } + else + { + // resource is added to base + + String[] segments = path.Split('/'); + if (segments.Length > 1) + { + if (log.IsDebugEnabled) + log.Debug("Splitting up compound resource " + resource.Name); + resource.Name = segments[segments.Length - 1]; + + // insert middle segments + Resource sub = null; + for (Int32 i = 0; i < segments.Length - 1; i++) + { + sub = baseRes.CreateInstance(segments[i]); + sub.Hidden = true; + baseRes.AddSubResource(sub); + baseRes = sub; + } + } + else + resource.Name = path; + + resource._parent = baseRes; + baseRes.SubResources[resource.Name] = resource; + + if (log.IsDebugEnabled) + log.Debug("Add resource " + resource.Name); + } + + // update number of sub-resources in the tree + Resource p = resource._parent; + while (p != null) + { + p._totalSubResourceCount++; + p = p._parent; + } + } + + /// + /// Removes a sub-resource from this resource by its identifier. + /// + /// the path of the sub-resource to remove + public void RemoveSubResource(String resourcePath) + { + RemoveSubResource(GetResource(resourcePath)); + } + + /// + /// Removes a sub-resource from this resource. + /// + /// the sub-resource to remove + public void RemoveSubResource(Resource resource) + { + if (null == resource) + return; + + if (SubResources.Remove(resource._resourceIdentifier)) + { + Resource p = resource._parent; + while (p != null) + { + p._totalSubResourceCount--; + p = p._parent; + } + + resource._parent = null; + } + } + + public void CreateSubResource(Request request, String newIdentifier) + { + DoCreateSubResource(request, newIdentifier); + } + + public Int32 CompareTo(Resource other) + { + return Path.CompareTo(other.Path); + } + + public override String ToString() + { + StringBuilder sb = new StringBuilder(); + Print(sb, 0); + return sb.ToString(); + } + + private void Print(StringBuilder sb, Int32 indent) + { + for (Int32 i = 0; i < indent; i++) + sb.Append(" "); + sb.AppendFormat("+[{0}]",_resourceIdentifier); + + String title = Title; + if (title != null) + sb.AppendFormat(" {0}", title); + sb.AppendLine(); + + foreach (LinkAttribute attr in Attributes) + { + if (attr.Name.Equals(LinkFormat.Title)) + continue; + for (Int32 i = 0; i < indent + 3; i++) + sb.Append(" "); + sb.AppendFormat("- "); + attr.Serialize(sb); + sb.AppendLine(); + } + + if (_subResources != null) + foreach (Resource sub in _subResources.Values) + { + sub.Print(sb, indent + 2); + } + } + + /// + /// Creates a resouce instance with proper subtype. + /// + /// + protected abstract Resource CreateInstance(String name); + protected abstract void DoCreateSubResource(Request request, String newIdentifier); + + private static IList GetStringValues(IEnumerable attributes) + { + List list = new List(); + foreach (LinkAttribute attr in attributes) + { + list.Add(attr.StringValue); + } + return list; + } + + private static IList GetIntValues(IEnumerable attributes) + { + List list = new List(); + foreach (LinkAttribute attr in attributes) + { + list.Add(attr.IntValue); + } + return list; + } + } +} diff --git a/WorldDirect.CoAP/ICoapConfig.cs b/WorldDirect.CoAP/ICoapConfig.cs new file mode 100644 index 0000000..0d9d2ca --- /dev/null +++ b/WorldDirect.CoAP/ICoapConfig.cs @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2011-2015, Longxiang He , + * SmeshLink Technology Co. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY. + * + * This file is part of the CoAP.NET, a CoAP framework in C#. + * Please see README for more information. + */ + +namespace WorldDirect.CoAP +{ + using System; + + /// + /// Provides configuration for CoAP communication. + /// + public partial interface ICoapConfig : System.ComponentModel.INotifyPropertyChanged + { + /// + /// Gets the version of CoAP protocol. + /// + String Version { get; } + /// + /// Gets the default CoAP port for normal CoAP communication (not secure). + /// + Int32 DefaultPort { get; } + /// + /// Gets the default CoAP port for secure CoAP communication (coaps). + /// + Int32 DefaultSecurePort { get; } + /// + /// Gets the port which HTTP proxy is on. + /// + Int32 HttpPort { get; } + + Int32 AckTimeout { get; } + Double AckRandomFactor { get; } + Double AckTimeoutScale { get; } + Int32 MaxRetransmit { get; } + + Int32 MaxMessageSize { get; } + /// + /// Gets the default preferred size of block in blockwise transfer. + /// + Int32 DefaultBlockSize { get; } + Int32 BlockwiseStatusLifetime { get; } + Boolean UseRandomIDStart { get; } + Boolean UseRandomTokenStart { get; } + + Int64 NotificationMaxAge { get; } + Int64 NotificationCheckIntervalTime { get; } + Int32 NotificationCheckIntervalCount { get; } + Int32 NotificationReregistrationBackoff { get; } + + String Deduplicator { get; } + Int32 CropRotationPeriod { get; } + Int32 ExchangeLifetime { get; } + Int64 MarkAndSweepInterval { get; } + + Int32 ChannelReceiveBufferSize { get; } + Int32 ChannelSendBufferSize { get; } + Int32 ChannelReceivePacketSize { get; } + + /// + /// Loads configuration from a config properties file. + /// + void Load(String configFile); + + /// + /// Stores the configuration in a config properties file. + /// + void Store(String configFile); + } +} diff --git a/WorldDirect.CoAP/LinkAttribute.cs b/WorldDirect.CoAP/LinkAttribute.cs new file mode 100644 index 0000000..926db60 --- /dev/null +++ b/WorldDirect.CoAP/LinkAttribute.cs @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2011-2013, Longxiang He , + * SmeshLink Technology Co. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY. + * + * This file is part of the CoAP.NET, a CoAP framework in C#. + * Please see README for more information. + */ + +namespace WorldDirect.CoAP +{ + using System; + using System.Text; + using Log; + + /// + /// Class for linkformat attributes. + /// + public class LinkAttribute : IComparable + { + private static readonly ILogger log = LogManager.GetLogger(typeof(LinkAttribute)); + + private String _name; + private Object _value; + + /// + /// Initializes an attribute. + /// + public LinkAttribute(String name, Object value) + { + _name = name; + _value = value; + } + + /// + /// Gets the name of this attribute. + /// + public String Name + { + get { return _name; } + } + + /// + /// Gets the value of this attribute. + /// + public Object Value + { + get { return _value; } + } + + /// + /// Gets the int value of this attribute. + /// + public Int32 IntValue + { + get { return (_value is Int32) ? (Int32)_value : -1; } + } + + /// + /// Gets the string value of this attribute. + /// + public String StringValue + { + get { return (_value is String) ? (String)_value : null; } + } + + /// + /// Serializes this attribute into its string representation. + /// + /// + public void Serialize(StringBuilder builder) + { + // check if there's something to write + if (_name != null && _value != null) + { + if (_value is Boolean) + { + // flag attribute + if ((Boolean)_value) + builder.Append(_name); + } + else + { + // name-value-pair + builder.Append(_name); + builder.Append('='); + if (_value is String) + { + builder.Append('"'); + builder.Append((String)_value); + builder.Append('"'); + } + else if (_value is Int32) + { + builder.Append(((Int32)_value)); + } + else + { + if (log.IsErrorEnabled) + log.Error(String.Format("Serializing attribute of unexpected type: {0} ({1})", _name, _value.GetType().Name)); + } + } + } + } + + /// + public override String ToString() + { + return String.Format("name: {0} value: {1}", _name, _value); + } + + /// + public Int32 CompareTo(LinkAttribute other) + { + Int32 ret = _name.CompareTo(other.Name); + if (ret == 0) + { + if (_value is String) + return StringValue.CompareTo(other.StringValue); + else if (_value is Int32) + return IntValue.CompareTo(other.IntValue); + } + return ret; + } + } +} diff --git a/WorldDirect.CoAP/LinkFormat.cs b/WorldDirect.CoAP/LinkFormat.cs new file mode 100644 index 0000000..60a5ac6 --- /dev/null +++ b/WorldDirect.CoAP/LinkFormat.cs @@ -0,0 +1,494 @@ +/* + * Copyright (c) 2011-2014, Longxiang He , + * SmeshLink Technology Co. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY. + * + * This file is part of the CoAP.NET, a CoAP framework in C#. + * Please see README for more information. + */ + +namespace WorldDirect.CoAP +{ + using System; + using System.Collections.Generic; + using System.Text; + using System.Text.RegularExpressions; + using EndPoint.Resources; + using Log; + using Server.Resources; + using Util; + using Resource = EndPoint.Resources.Resource; + + /// + /// This class provides link format definitions as specified in + /// draft-ietf-core-link-format-06 + /// + public static class LinkFormat + { + /// + /// Name of the attribute Resource Type + /// + public static readonly String ResourceType = "rt"; + /// + /// Name of the attribute Interface Description + /// + public static readonly String InterfaceDescription = "if"; + /// + /// Name of the attribute Content Type + /// + public static readonly String ContentType = "ct"; + /// + /// Name of the attribute Max Size Estimate + /// + public static readonly String MaxSizeEstimate = "sz"; + /// + /// Name of the attribute Title + /// + public static readonly String Title = "title"; + /// + /// Name of the attribute Observable + /// + public static readonly String Observable = "obs"; + /// + /// Name of the attribute link + /// + public static readonly String Link = "href"; + + /// + /// The string as the delimiter between resources + /// + public static readonly String Delimiter = ","; + /// + /// The string to separate attributes + /// + public static readonly String Separator = ";"; + + public static readonly Regex DelimiterRegex = new Regex("\\s*" + Delimiter + "+\\s*"); + public static readonly Regex SeparatorRegex = new Regex("\\s*" + Separator + "+\\s*"); + + public static readonly Regex ResourceNameRegex = new Regex("<[^>]*>"); + public static readonly Regex WordRegex = new Regex("\\w+"); + public static readonly Regex QuotedString = new Regex("\\G\".*?\""); + public static readonly Regex Cardinal = new Regex("\\G\\d+"); + static readonly Regex EqualRegex = new Regex("="); + static readonly Regex BlankRegex = new Regex("\\s"); + + private static ILogger log = LogManager.GetLogger(typeof(LinkFormat)); + + public static String Serialize(IResource root) + { + return Serialize(root, null); + } + + public static String Serialize(IResource root, IEnumerable queries) + { + StringBuilder linkFormat = new StringBuilder(); + + foreach (IResource child in root.Children) + { + SerializeTree(child, queries, linkFormat); + } + + if (linkFormat.Length > 1) + linkFormat.Remove(linkFormat.Length - 1, 1); + + return linkFormat.ToString(); + } + + public static IEnumerable Parse(String linkFormat) + { + if (!String.IsNullOrEmpty(linkFormat)) + { + Scanner scanner = new Scanner(linkFormat); + String path = null; + while ((path = scanner.Find(ResourceNameRegex)) != null) + { + path = path.Substring(1, path.Length - 2); + WebLink link = new WebLink(path); + + String attr = null; + while (scanner.Find(DelimiterRegex, 1) == null && + (attr = scanner.Find(WordRegex)) != null) + { + if (scanner.Find(EqualRegex, 1) == null) + { + // flag attribute without value + link.Attributes.Add(attr); + } + else + { + String value = null; + if ((value = scanner.Find(QuotedString)) != null) + { + // trim " " + value = value.Substring(1, value.Length - 2); + if (Title.Equals(attr)) + link.Attributes.Add(attr, value); + else + foreach (String part in BlankRegex.Split(value)) + link.Attributes.Add(attr, part); + } + else if ((value = scanner.Find(WordRegex)) != null) + { + link.Attributes.Set(attr, value); + } + else if ((value = scanner.Find(Cardinal)) != null) + { + link.Attributes.Set(attr, value); + } + } + } + + yield return link; + } + } + + yield break; + } + + private static void SerializeTree(IResource resource, IEnumerable queries, StringBuilder sb) + { + if (resource.Visible && Matches(resource, queries)) + { + SerializeResource(resource, sb); + sb.Append(","); + } + + // sort by resource name + List childrens = new List(resource.Children); + childrens.Sort(delegate(IResource r1, IResource r2) { return String.Compare(r1.Name, r2.Name); }); + + foreach (IResource child in childrens) + { + SerializeTree(child, queries, sb); + } + } + + private static void SerializeResource(IResource resource, StringBuilder sb) + { + sb.Append("<") + .Append(resource.Path) + .Append(resource.Name) + .Append(">"); + SerializeAttributes(resource.Attributes, sb); + } + + private static void SerializeAttributes(ResourceAttributes attributes, StringBuilder sb) + { + List keys = new List(attributes.Keys); + keys.Sort(); + foreach (String name in keys) + { + List values = new List(attributes.GetValues(name)); + if (values.Count == 0) + continue; + sb.Append(Separator); + SerializeAttribute(name, values, sb); + } + } + + private static void SerializeAttribute(String name, IEnumerable values, StringBuilder sb) + { + String delimiter = "="; + Boolean quotes = false; + + sb.Append(name); + + using (IEnumerator it = values.GetEnumerator()) + { + if (!it.MoveNext() || String.IsNullOrEmpty(it.Current)) + return; + + sb.Append(delimiter); + + String first = it.Current; + Boolean more = it.MoveNext(); + if (more || !IsNumber(first)) + { + sb.Append('"'); + quotes = true; + } + + sb.Append(first); + while (more) + { + sb.Append(' '); + sb.Append(it.Current); + more = it.MoveNext(); + } + + if (quotes) + sb.Append('"'); + } + } + + private static Boolean IsNumber(String value) + { + if (String.IsNullOrEmpty(value)) + return false; + foreach (Char c in value) + { + if (!Char.IsNumber(c)) + return false; + } + return true; + } + + public static String Serialize(Resource resource, IEnumerable