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