diff --git a/wsclient1/AssemblyInfo.cs b/Example/AssemblyInfo.cs similarity index 92% rename from wsclient1/AssemblyInfo.cs rename to Example/AssemblyInfo.cs index 5d7a651f..d4e247fd 100644 --- a/wsclient1/AssemblyInfo.cs +++ b/Example/AssemblyInfo.cs @@ -4,12 +4,12 @@ using System.Runtime.CompilerServices; // Information about this assembly is defined by the following attributes. // Change them to the values specific to your project. -[assembly: AssemblyTitle("wsclient1")] +[assembly: AssemblyTitle("Example")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("")] -[assembly: AssemblyCopyright("")] +[assembly: AssemblyCopyright("sta")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] diff --git a/Example/Program.cs b/Example/Program.cs new file mode 100644 index 00000000..74c26cd7 --- /dev/null +++ b/Example/Program.cs @@ -0,0 +1,127 @@ +#if NOTIFY +using Notifications; +#endif +using System; +using System.Collections; +using System.Threading; +using WebSocketSharp; + +namespace Example +{ + public struct NfMessage + { + public string Summary; + public string Body; + public string Icon; + } + + public class ThreadState + { + public bool Enabled { get; set; } + public AutoResetEvent Notification { get; private set; } + + public ThreadState() + { + Enabled = true; + Notification = new AutoResetEvent(false); + } + } + + public class Program + { + private static Queue _msgQ = Queue.Synchronized(new Queue()); + + private static void enNfMessage(string summary, string body, string icon) + { + var msg = new NfMessage + { + Summary = summary, + Body = body, + Icon = icon + }; + + _msgQ.Enqueue(msg); + } + + public static void Main(string[] args) + { + ThreadState ts = new ThreadState(); + + WaitCallback notifyMsg = state => + { + while (ts.Enabled) + { + Thread.Sleep(500); + + if (_msgQ.Count > 0) + { + NfMessage msg = (NfMessage)_msgQ.Dequeue(); + #if NOTIFY + Notification nf = new Notification(msg.Summary, + msg.Body, + msg.Icon); + nf.AddHint("append", "allowed"); + nf.Show(); + #else + Console.WriteLine("{0}: {1}", msg.Summary, msg.Body); + #endif + } + } + + ts.Notification.Set(); + }; + + ThreadPool.QueueUserWorkItem(notifyMsg); + + using (WebSocket ws = new WebSocket("ws://echo.websocket.org", "echo")) + //using (WebSocket ws = new WebSocket("wss://echo.websocket.org", "echo")) + { + ws.OnOpen += (sender, e) => + { + ws.Send("Hi, all!"); + }; + + ws.OnMessage += (sender, e) => + { + enNfMessage("[WebSocket] Message", e.Data, "notification-message-im"); + }; + + ws.OnError += (sender, e) => + { + enNfMessage("[WebSocket] Error", e.Data, "notification-message-im"); + }; + + ws.OnClose += (sender, e) => + { + enNfMessage( + String.Format("[WebSocket] Close({0}:{1})", (ushort)e.Code, e.Code), + e.Reason, + "notification-message-im"); + }; + + ws.Connect(); + + Thread.Sleep(500); + Console.WriteLine("\nType \"exit\" to exit.\n"); + + string data; + while (true) + { + Thread.Sleep(500); + + Console.Write("> "); + data = Console.ReadLine(); + if (data == "exit") + { + break; + } + + ws.Send(data); + } + } + + ts.Enabled = false; + ts.Notification.WaitOne(); + } + } +} diff --git a/wsclient/AssemblyInfo.cs b/Example1/AssemblyInfo.cs similarity index 92% rename from wsclient/AssemblyInfo.cs rename to Example1/AssemblyInfo.cs index ade05a55..f92db6a2 100644 --- a/wsclient/AssemblyInfo.cs +++ b/Example1/AssemblyInfo.cs @@ -4,12 +4,12 @@ using System.Runtime.CompilerServices; // Information about this assembly is defined by the following attributes. // Change them to the values specific to your project. -[assembly: AssemblyTitle("wsclient")] +[assembly: AssemblyTitle("Example1")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("")] -[assembly: AssemblyCopyright("")] +[assembly: AssemblyCopyright("sta")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] diff --git a/Example1/AudioStreamer.cs b/Example1/AudioStreamer.cs new file mode 100644 index 00000000..44dd7f19 --- /dev/null +++ b/Example1/AudioStreamer.cs @@ -0,0 +1,318 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +#if NOTIFY +using Notifications; +#endif +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Threading; +using WebSocketSharp; +using WebSocketSharp.Frame; + +namespace Example +{ + public struct NfMessage + { + public string Summary; + public string Body; + public string Icon; + } + + public class AudioMessage + { + public uint user_id; + public byte ch_num; + public uint buffer_length; + public float[,] buffer_array; + } + + public class TextMessage + { + public uint? user_id; + public string name; + public string type; + public string message; + } + + public class ThreadState + { + public bool Enabled { get; set; } + public AutoResetEvent Notification { get; private set; } + + public ThreadState() + { + Enabled = true; + Notification = new AutoResetEvent(false); + } + } + + public class AudioStreamer : IDisposable + { + private Dictionary _audioBox; + private Queue _msgQ; + private string _name; + private WaitCallback _notifyMsg; + private ThreadState _notifyMsgState; + private TimerCallback _sendHeartbeat; + private Timer _heartbeatTimer; + private uint? _user_id; + private WebSocket _ws; + + public AudioStreamer(string url) + { + _ws = new WebSocket(url); + _msgQ = Queue.Synchronized(new Queue()); + _audioBox = new Dictionary(); + _user_id = null; + + configure(); + } + + private void configure() + { + _ws.OnOpen += (sender, e) => + { + var msg = createTextMessage("connection", String.Empty); + _ws.Send(msg); + }; + + _ws.OnMessage += (sender, e) => + { + switch (e.Type) + { + case Opcode.TEXT: + var msg = parseTextMessage(e.Data); + _msgQ.Enqueue(msg); + break; + case Opcode.BINARY: + var audioMsg = parseAudioMessage(e.RawData); + if (audioMsg.user_id == _user_id) goto default; + if (_audioBox.ContainsKey(audioMsg.user_id)) + { + _audioBox[audioMsg.user_id].Enqueue(audioMsg.buffer_array); + } + else + { + var q = Queue.Synchronized(new Queue()); + q.Enqueue(audioMsg.buffer_array); + _audioBox.Add(audioMsg.user_id, q); + } + break; + default: + break; + } + }; + + _ws.OnError += (sender, e) => + { + enNfMessage("[AudioStreamer] error", "WS Error: " + e.Data, "notification-message-im"); + }; + + _ws.OnClose += (sender, e) => + { + enNfMessage + ( + "[AudioStreamer] disconnect", + String.Format("WS Close({0}:{1}): {2}", (ushort)e.Code, e.Code, e.Reason), + "notification-message-im" + ); + }; + + _notifyMsgState = new ThreadState(); + _notifyMsg = (state) => + { + while (_notifyMsgState.Enabled) + { + Thread.Sleep(500); + + if (_msgQ.Count > 0) + { + NfMessage msg = (NfMessage)_msgQ.Dequeue(); + #if NOTIFY + Notification nf = new Notification(msg.Summary, + msg.Body, + msg.Icon); + nf.AddHint("append", "allowed"); + nf.Show(); + #else + Console.WriteLine("{0}: {1}", msg.Summary, msg.Body); + #endif + } + } + + _notifyMsgState.Notification.Set(); + }; + + _sendHeartbeat = (state) => + { + var msg = createTextMessage("heartbeat", String.Empty); + _ws.Send(msg); + }; + } + + private byte[] createAudioMessage(float[,] buffer_array) + { + List msg = new List(); + + uint user_id = (uint)_user_id; + int ch_num = buffer_array.GetLength(0); + int buffer_length = buffer_array.GetLength(1); + + msg.AddRange(user_id.ToBytes(ByteOrder.BIG)); + msg.Add((byte)ch_num); + msg.AddRange(((uint)buffer_length).ToBytes(ByteOrder.BIG)); + + ch_num.Times(i => + { + buffer_length.Times(j => + { + msg.AddRange(buffer_array[i, j].ToBytes(ByteOrder.BIG)); + }); + }); + + return msg.ToArray(); + } + + private string createTextMessage(string type, string message) + { + var msg = new TextMessage + { + user_id = _user_id, + name = _name, + type = type, + message = message + }; + + return JsonConvert.SerializeObject(msg); + } + + private AudioMessage parseAudioMessage(byte[] data) + { + uint user_id = data.SubArray(0, 4).To(ByteOrder.BIG); + byte ch_num = data.SubArray(4, 1)[0]; + uint buffer_length = data.SubArray(5, 4).To(ByteOrder.BIG); + float[,] buffer_array = new float[ch_num, buffer_length]; + + int offset = 9; + ch_num.Times(i => + { + buffer_length.Times(j => + { + buffer_array[i, j] = data.SubArray(offset, 4).To(ByteOrder.BIG); + offset += 4; + }); + }); + + return new AudioMessage + { + user_id = user_id, + ch_num = ch_num, + buffer_length = buffer_length, + buffer_array = buffer_array + }; + } + + private NfMessage parseTextMessage(string data) + { + JObject msg = JObject.Parse(data); + uint user_id = (uint)msg["user_id"]; + string name = (string)msg["name"]; + string type = (string)msg["type"]; + + string message; + switch (type) + { + case "connection": + JArray users = (JArray)msg["message"]; + StringBuilder sb = new StringBuilder("Now keeping connection\n"); + foreach (JToken user in users) + { + sb.AppendFormat("user_id: {0} name: {1}\n", (uint)user["user_id"], (string)user["name"]); + } + message = sb.ToString().TrimEnd('\n'); + break; + case "connected": + _user_id = user_id; + message = String.Format("user_id: {0} name: {1}", user_id, name); + break; + case "message": + message = String.Format("{0}: {1}", name, (string)msg["message"]); + break; + case "start_music": + message = String.Format("{0}: Started playing music!", name); + break; + default: + message = "Received unknown type message: " + type; + break; + } + + return new NfMessage + { + Summary = String.Format("[AudioStreamer] {0}", type), + Body = message, + Icon = "notification-message-im" + }; + } + + private void enNfMessage(string summary, string body, string icon) + { + var msg = new NfMessage + { + Summary = summary, + Body = body, + Icon = icon + }; + + _msgQ.Enqueue(msg); + } + + public void Connect() + { + string name; + do + { + Console.Write("Your name > "); + name = Console.ReadLine(); + } + while (name == String.Empty); + + _name = name; + + _ws.Connect(); + + ThreadPool.QueueUserWorkItem(_notifyMsg); + _heartbeatTimer = new Timer(_sendHeartbeat, null, 30 * 1000, 30 * 1000); + } + + public void Disconnect() + { + var wait = new AutoResetEvent(false); + _heartbeatTimer.Dispose(wait); + wait.WaitOne(); + + _ws.Close(); + + _notifyMsgState.Enabled = false; + _notifyMsgState.Notification.WaitOne(); + } + + public void Dispose() + { + Disconnect(); + } + + public void Write(string data) + { + var msg = createTextMessage("message", data); + _ws.Send(msg); + } + + public void Write(FileInfo file) + { + throw new NotImplementedException(); + } + } +} diff --git a/wsclient/wsclient.csproj b/Example1/Example1.csproj similarity index 82% rename from wsclient/wsclient.csproj rename to Example1/Example1.csproj index 313d403f..95af031f 100644 --- a/wsclient/wsclient.csproj +++ b/Example1/Example1.csproj @@ -5,10 +5,10 @@ AnyCPU 9.0.21022 2.0 - {52805AEC-EFB1-4F42-BB8E-3ED4E692C568} + {390E2568-57B7-4D17-91E5-C29336368CCF} Exe - WsClient - wsclient + Example + example1 v3.5 @@ -16,7 +16,7 @@ full false bin\Debug - DEBUG + DEBUG; prompt 4 true @@ -34,7 +34,7 @@ full false bin\Debug_Ubuntu - DEBUG,NOTIFY + DEBUG;NOTIFY prompt 4 true @@ -43,26 +43,31 @@ none false bin\Release_Ubuntu - NOTIFY prompt 4 true + NOTIFY + False notify-sharp + + False + - + + + {B357BAC7-529E-4D81-A0D2-71041B19C8DE} websocket-sharp - \ No newline at end of file diff --git a/Example1/Program.cs b/Example1/Program.cs new file mode 100644 index 00000000..2e33eee3 --- /dev/null +++ b/Example1/Program.cs @@ -0,0 +1,35 @@ +using System; +using System.Threading; + +namespace Example +{ + public class Program + { + public static void Main(string[] args) + { + //using (AudioStreamer streamer = new AudioStreamer("ws://localhost:3000/socket")) + using (AudioStreamer streamer = new AudioStreamer("ws://agektmr.node-ninja.com:3000/socket")) + { + streamer.Connect(); + + Thread.Sleep(500); + Console.WriteLine("\nType \"exit\" to exit.\n"); + + string data; + while (true) + { + Thread.Sleep(500); + + Console.Write("> "); + data = Console.ReadLine(); + if (data == "exit") + { + break; + } + + streamer.Write(data); + } + } + } + } +} diff --git a/README.md b/README.md index 1fa7bab4..1f332f09 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,171 @@ # websocket-sharp # -A C# implementation of a WebSocket protocol client. +**websocket-sharp** is a C# implementation of a WebSocket protocol client. ## Usage ## -Please refer to wsclient/wsclient.cs. +### Step 1 ### +Required namespaces. + + using WebSocketSharp; + using WebSocketSharp.Frame; + +In `WebSocketSharp` namespace `WebSocket` class exists, in `WebSocketSharp.Frame` namespace WebSocket data frame resources (e.g. `WsFrame` class) exist. + +### Step 2 ### + +Creating instance of `WebSocket` class. + + using (WebSocket ws = new WebSocket("ws://example.com")) + { + ... + } + +So `WebSocket` class inherits `IDisposable` interface, you can use `using` statement. + +### Step 3 ### + +Setting of `WebSocket` event handlers. + +#### WebSocket.OnOpen event #### + +`WebSocket.OnOpen` event is emitted immediately after WebSocket connection has been established. + + ws.OnOpen += (sender, e) => + { + ... + }; + +So `e` has come across as `EventArgs.Empty`, there is no operation on `e`. + +#### WebSocket.OnMessage event #### + +`WebSocket.OnMessage` event is emitted each time WebSocket data frame is received. + + ws.OnMessage += (sender, e) => + { + ... + }; + +So **type** of received WebSocket data frame is stored in `e.Type` (`WebSocketSharp.MessageEventArgs.Type`, its type is `WebSocketSharp.Frame.Opcode`), you check it out and you determine which item you should operate. + + switch (e.Type) + { + case Opcode.TEXT: + ... + break; + case Opcode.BINARY: + ... + break; + default: + break; + } + +If `e.Type` is `Opcode.TEXT`, you operate `e.Data` (`WebSocketSharp.MessageEventArgs.Data`, its type is `string`). + +if `e.Type` is `Opcode.BINARY`, you operate `e.RawData` (`WebSocketSharp.MessageEventArgs.RawData`, its type is `byte[]`). + +#### WebSocket.OnError event #### + +`WebSocket.OnError` event is emitted when some error is occurred. + + ws.OnError += (sender, e) => + { + ... + }; + +So error message is stored in `e.Data` (`WebSocketSharp.MessageEventArgs.Data`, its type is `string`) , you operate it. + +#### WebSocket.OnClose event #### + +`WebSocket.OnClose` event is emitted when WebSocket connection is closed. + + ws.OnClose += (sender, e) => + { + ... + }; + +So close status code is stored in `e.Code` (`WebSocketSharp.CloseEventArgs.Code`, its type is `WebSocketSharp.Frame.CloseStatusCode`) and reason of close is stored in `e.Reason` (`WebSocketSharp.CloseEventArgs.Reason`, its type is `string`), you operate them. + +### Step 4 ### + +Connecting to server using WebSocket. + + ws.Connect(); + +### Step 5 ### + +Sending data. + + ws.Send(data); + +`WebSocket.Send` method is overloaded. + +data types are `string`, `byte[]` and `FileInfo` class. + +### Step 6 ### + +Closing WebSocket connection. + + ws.Close(code, reason); + +If you want to close WebSocket connection explicitly, you can use `Close` method. + +Type of `code` is `WebSocketSharp.Frame.CloseStatusCode`, type of `reason` is `string`. + +`WebSocket.Close` method is overloaded (In addition `Close()` and `Close(code)` exist). + +## Examples ## + +Examples of using **websocket-sharp**. + +### Example ### + +[Example] connects to the [Echo server] using the WebSocket. + +### Example1 ### + +[Example1] connects to the [Audio Data delivery server] using the WebSocket ([Example1] is only implemented a chat feature, still unfinished). + +[Example1] uses [Json.NET]. + +## Supported WebSocket Protocol ## + +**websocket-sharp** supports **[RFC 6455]**. + +- @**[branch: hybi-00]** supports older draft-ietf-hybi-thewebsocketprotocol-00 (**[hybi-00]**). +- @**[branch: draft75]** supports even more old draft-hixie-thewebsocketprotocol-75 (**[hixie-75]**). + +## Reference ## + +- **[The WebSocket Protocol]** +- **[The WebSocket API]** + +Thx for translating to japanese. + +- **[The WebSocket Protocol 日本語訳]** +- **[The WebSocket API 日本語訳]** + +## License ## + +Copyright © 2010 - 2012 sta.blockhead + +Licensed under the **[MIT License]**. + + +[Audio Data delivery server]: http://agektmr.node-ninja.com:3000/ +[branch: draft75]: https://github.com/sta/websocket-sharp/tree/draft75 +[branch: hybi-00]: https://github.com/sta/websocket-sharp/tree/hybi-00 +[Echo server]: http://www.websocket.org/echo.html +[Example]: https://github.com/sta/websocket-sharp/tree/master/Example +[Example1]: https://github.com/sta/websocket-sharp/tree/master/Example1 +[hixie-75]: http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-75 +[hybi-00]: http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-00 +[Json.NET]: http://james.newtonking.com/projects/json-net.aspx +[MIT License]: http://www.opensource.org/licenses/mit-license.php +[RFC 6455]: http://tools.ietf.org/html/rfc6455 +[The WebSocket API]: http://dev.w3.org/html5/websockets +[The WebSocket API 日本語訳]: http://www.hcn.zaq.ne.jp/___/WEB/WebSocket-ja.html +[The WebSocket Protocol]: http://tools.ietf.org/html/rfc6455 +[The WebSocket Protocol 日本語訳]: http://www.hcn.zaq.ne.jp/___/WEB/RFC6455-ja.html diff --git a/websocket-sharp.sln b/websocket-sharp.sln index 82aee85e..a7c94077 100644 --- a/websocket-sharp.sln +++ b/websocket-sharp.sln @@ -3,9 +3,9 @@ Microsoft Visual Studio Solution File, Format Version 10.00 # Visual Studio 2008 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "websocket-sharp", "websocket-sharp\websocket-sharp.csproj", "{B357BAC7-529E-4D81-A0D2-71041B19C8DE}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "wsclient", "wsclient\wsclient.csproj", "{52805AEC-EFB1-4F42-BB8E-3ED4E692C568}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Example", "Example\Example.csproj", "{52805AEC-EFB1-4F42-BB8E-3ED4E692C568}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "wsclient1", "wsclient1\wsclient1.csproj", "{B0B609B7-A81C-46B0-A9B8-82E9716D355B}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Example1", "Example1\Example1.csproj", "{390E2568-57B7-4D17-91E5-C29336368CCF}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -15,6 +15,14 @@ Global Release_Ubuntu|Any CPU = Release_Ubuntu|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution + {390E2568-57B7-4D17-91E5-C29336368CCF}.Debug_Ubuntu|Any CPU.ActiveCfg = Debug_Ubuntu|Any CPU + {390E2568-57B7-4D17-91E5-C29336368CCF}.Debug_Ubuntu|Any CPU.Build.0 = Debug_Ubuntu|Any CPU + {390E2568-57B7-4D17-91E5-C29336368CCF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {390E2568-57B7-4D17-91E5-C29336368CCF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {390E2568-57B7-4D17-91E5-C29336368CCF}.Release_Ubuntu|Any CPU.ActiveCfg = Release_Ubuntu|Any CPU + {390E2568-57B7-4D17-91E5-C29336368CCF}.Release_Ubuntu|Any CPU.Build.0 = Release_Ubuntu|Any CPU + {390E2568-57B7-4D17-91E5-C29336368CCF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {390E2568-57B7-4D17-91E5-C29336368CCF}.Release|Any CPU.Build.0 = Release|Any CPU {52805AEC-EFB1-4F42-BB8E-3ED4E692C568}.Debug_Ubuntu|Any CPU.ActiveCfg = Debug_Ubuntu|Any CPU {52805AEC-EFB1-4F42-BB8E-3ED4E692C568}.Debug_Ubuntu|Any CPU.Build.0 = Debug_Ubuntu|Any CPU {52805AEC-EFB1-4F42-BB8E-3ED4E692C568}.Debug|Any CPU.ActiveCfg = Debug|Any CPU @@ -23,14 +31,6 @@ Global {52805AEC-EFB1-4F42-BB8E-3ED4E692C568}.Release_Ubuntu|Any CPU.Build.0 = Release_Ubuntu|Any CPU {52805AEC-EFB1-4F42-BB8E-3ED4E692C568}.Release|Any CPU.ActiveCfg = Release|Any CPU {52805AEC-EFB1-4F42-BB8E-3ED4E692C568}.Release|Any CPU.Build.0 = Release|Any CPU - {B0B609B7-A81C-46B0-A9B8-82E9716D355B}.Debug_Ubuntu|Any CPU.ActiveCfg = Debug_Ubuntu|Any CPU - {B0B609B7-A81C-46B0-A9B8-82E9716D355B}.Debug_Ubuntu|Any CPU.Build.0 = Debug_Ubuntu|Any CPU - {B0B609B7-A81C-46B0-A9B8-82E9716D355B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B0B609B7-A81C-46B0-A9B8-82E9716D355B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B0B609B7-A81C-46B0-A9B8-82E9716D355B}.Release_Ubuntu|Any CPU.ActiveCfg = Release_Ubuntu|Any CPU - {B0B609B7-A81C-46B0-A9B8-82E9716D355B}.Release_Ubuntu|Any CPU.Build.0 = Release_Ubuntu|Any CPU - {B0B609B7-A81C-46B0-A9B8-82E9716D355B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B0B609B7-A81C-46B0-A9B8-82E9716D355B}.Release|Any CPU.Build.0 = Release|Any CPU {B357BAC7-529E-4D81-A0D2-71041B19C8DE}.Debug_Ubuntu|Any CPU.ActiveCfg = Debug_Ubuntu|Any CPU {B357BAC7-529E-4D81-A0D2-71041B19C8DE}.Debug_Ubuntu|Any CPU.Build.0 = Debug_Ubuntu|Any CPU {B357BAC7-529E-4D81-A0D2-71041B19C8DE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU diff --git a/websocket-sharp.userprefs b/websocket-sharp.userprefs index 6c25aa51..e78c9e62 100644 --- a/websocket-sharp.userprefs +++ b/websocket-sharp.userprefs @@ -1,12 +1,12 @@  - - + + - + - + \ No newline at end of file diff --git a/websocket-sharp/AssemblyInfo.cs b/websocket-sharp/AssemblyInfo.cs index 89a8f31a..b32e6d63 100644 --- a/websocket-sharp/AssemblyInfo.cs +++ b/websocket-sharp/AssemblyInfo.cs @@ -17,7 +17,7 @@ using System.Runtime.CompilerServices; // The form "{Major}.{Minor}.*" will automatically update the build and revision, // and "{Major}.{Minor}.{Build}.*" will update just the revision. -[assembly: AssemblyVersion("1.0.0.*")] +[assembly: AssemblyVersion("1.0.1.*")] // The following attributes are used to specify the signing key for the assembly, // if desired. See the Mono documentation for more information about signing. diff --git a/websocket-sharp/ByteOrder.cs b/websocket-sharp/ByteOrder.cs new file mode 100644 index 00000000..a2959f9d --- /dev/null +++ b/websocket-sharp/ByteOrder.cs @@ -0,0 +1,38 @@ +#region MIT License +/** + * ByteOrder.cs + * + * The MIT License + * + * Copyright (c) 2012 sta.blockhead + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#endregion + +using System; + +namespace WebSocketSharp +{ + public enum ByteOrder : byte + { + LITTLE = 0x0, + BIG = 0x1 + } +} diff --git a/websocket-sharp/CloseEventArgs.cs b/websocket-sharp/CloseEventArgs.cs new file mode 100644 index 00000000..a4ff419f --- /dev/null +++ b/websocket-sharp/CloseEventArgs.cs @@ -0,0 +1,87 @@ +#region MIT License +/** + * CloseEventArgs.cs + * + * The MIT License + * + * Copyright (c) 2012 sta.blockhead + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#endregion + +using System; +using System.Text; +using WebSocketSharp.Frame; + +namespace WebSocketSharp +{ + public class CloseEventArgs : MessageEventArgs + { + private ushort _code; + private string _reason; + private bool _wasClean; + + public CloseStatusCode Code + { + get + { + return (CloseStatusCode)_code; + } + } + + public string Reason + { + get + { + return _reason; + } + } + + public bool WasClean + { + get + { + return _wasClean; + } + set + { + _wasClean = value; + } + } + + public CloseEventArgs(PayloadData data) + : base(Opcode.CLOSE, data) + { + _code = data.ToBytes().SubArray(0, 2).To(ByteOrder.BIG); + + if (data.Length > 2) + { + var buffer = data.ToBytes().SubArray(2, (int)(data.Length - 2)); + _reason = Encoding.UTF8.GetString(buffer); + } + else + { + _reason = String.Empty; + } + + _wasClean = false; + } + } +} diff --git a/websocket-sharp/Ext.cs b/websocket-sharp/Ext.cs index 07128ecf..00fa0677 100644 --- a/websocket-sharp/Ext.cs +++ b/websocket-sharp/Ext.cs @@ -4,7 +4,7 @@ * * The MIT License * - * Copyright (c) 2010 sta.blockhead + * Copyright (c) 2010-2012 sta.blockhead * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -28,19 +28,59 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; +using System.Text; namespace WebSocketSharp { public static class Ext { - public static bool AreNotEqualDo( + public static bool EqualsAndSaveTo(this int value, char c, List dest) + { + byte b = (byte)value; + dest.Add(b); + return b == Convert.ToByte(c); + } + + public static string GetHeaderValue(this string src, string separater) + { + int i = src.IndexOf(separater); + return src.Substring(i + 1).Trim(); + } + + public static bool IsHostOrder(this ByteOrder order) + { + if (BitConverter.IsLittleEndian ^ (order == ByteOrder.LITTLE)) + {// true ^ false or false ^ true + return false; + } + else + {// true ^ true or false ^ false + return true; + } + } + + public static bool IsNullDo(this T value, Action act) + where T : class + { + if (value == null) + { + act(); + return true; + } + + return false; + } + + public static bool NotEqualsDo( this string expected, string actual, Func func, - out string ret) + out string ret, + bool ignoreCase) { - if (expected != actual) + if (String.Compare(expected, actual, ignoreCase) != 0) { ret = func(expected, actual); return true; @@ -50,106 +90,226 @@ namespace WebSocketSharp return false; } - public static bool EqualsWithSaveTo(this int asByte, char c, List dist) + public static byte[] ReadBytes(this TStream stream, ulong length, int bufferLength) + where TStream : System.IO.Stream { - byte b = (byte)asByte; - dist.Add(b); - return b == Convert.ToByte(c); - } + List readData = new List(); - public static uint GenerateKey(this Random rand, int space) - { - uint max = (uint)(0xffffffff / space); + ulong count = length / (ulong)bufferLength; + int remainder = (int)(length % (ulong)bufferLength); - int upper16 = (int)((max & 0xffff0000) >> 16); - int lower16 = (int)(max & 0x0000ffff); + byte[] buffer1 = new byte[bufferLength]; - return ((uint)rand.Next(upper16 + 1) << 16) + (uint)rand.Next(lower16 + 1); + count.Times(() => + { + stream.Read(buffer1, 0, bufferLength); + readData.AddRange(buffer1); + }); + + if (remainder > 0) + { + byte[] buffer2 = new byte[remainder]; + stream.Read(buffer2, 0, remainder); + readData.AddRange(buffer2); + } + + return readData.ToArray(); } - public static char GeneratePrintableASCIIwithoutSPandNum(this Random rand) + public static T[] SubArray(this T[] array, int startIndex, int length) { - int ascii = rand.Next(2) == 0 ? rand.Next(33, 48) : rand.Next(58, 127); - return Convert.ToChar(ascii); + if (startIndex == 0 && array.Length == length) + { + return array; + } + + T[] subArray = new T[length]; + Array.Copy(array, startIndex, subArray, 0, length); + return subArray; } - public static string GenerateSecKey(this Random rand, out uint key) + public static void Times(this T n, Action act) + where T : struct { - int space = rand.Next(1, 13); - int ascii = rand.Next(1, 13); - - key = rand.GenerateKey(space); + if (typeof(T) != typeof(byte) && + typeof(T) != typeof(Int16) && + typeof(T) != typeof(Int32) && + typeof(T) != typeof(Int64) && + typeof(T) != typeof(UInt16) && + typeof(T) != typeof(UInt32) && + typeof(T) != typeof(UInt64)) + { + throw new NotSupportedException("Not supported Struct type: " + typeof(T).ToString()); + } - long mKey = key * space; - List secKey = new List(mKey.ToString().ToCharArray()); + ulong m = (ulong)(object)n; - int i; - ascii.Times( () => + for (ulong i = 0; i < m; i++) { - i = rand.Next(secKey.Count + 1); - secKey.Insert(i, rand.GeneratePrintableASCIIwithoutSPandNum()); - } ); + act(); + } + } - space.Times( () => + public static void Times(this T n, Action act) + where T : struct + { + if (typeof(T) != typeof(byte) && + typeof(T) != typeof(Int16) && + typeof(T) != typeof(Int32) && + typeof(T) != typeof(Int64) && + typeof(T) != typeof(UInt16) && + typeof(T) != typeof(UInt32) && + typeof(T) != typeof(UInt64)) { - i = rand.Next(1, secKey.Count); - secKey.Insert(i, ' '); - } ); + throw new NotSupportedException("Not supported Struct type: " + typeof(T).ToString()); + } - return new String(secKey.ToArray()); + ulong m = (ulong)(object)n; + + for (ulong i = 0; i < m; i++) + { + act(i); + } } - public static byte[] InitializeWithPrintableASCII(this byte[] bytes, Random rand) + public static T To(this byte[] src, ByteOrder srcOrder) + where T : struct { - for (int i = 0; i < bytes.Length; i++) - { - bytes[i] = (byte)rand.Next(32, 127); - } - - return bytes; + T dest; + byte[] buffer = src.ToHostOrder(srcOrder); + + if (typeof(T) == typeof(Boolean)) + { + dest = (T)(object)BitConverter.ToBoolean(buffer, 0); + } + else if (typeof(T) == typeof(Char)) + { + dest = (T)(object)BitConverter.ToChar(buffer, 0); + } + else if (typeof(T) == typeof(Double)) + { + dest = (T)(object)BitConverter.ToDouble(buffer, 0); + } + else if (typeof(T) == typeof(Int16)) + { + dest = (T)(object)BitConverter.ToInt16(buffer, 0); + } + else if (typeof(T) == typeof(Int32)) + { + dest = (T)(object)BitConverter.ToInt32(buffer, 0); + } + else if (typeof(T) == typeof(Int64)) + { + dest = (T)(object)BitConverter.ToInt64(buffer, 0); + } + else if (typeof(T) == typeof(Single)) + { + dest = (T)(object)BitConverter.ToSingle(buffer, 0); + } + else if (typeof(T) == typeof(UInt16)) + { + dest = (T)(object)BitConverter.ToUInt16(buffer, 0); + } + else if (typeof(T) == typeof(UInt32)) + { + dest = (T)(object)BitConverter.ToUInt32(buffer, 0); + } + else if (typeof(T) == typeof(UInt64)) + { + dest = (T)(object)BitConverter.ToUInt64(buffer, 0); + } + else + { + dest = default(T); + } + + return dest; } - public static bool IsValid(this string[] response, byte[] expectedCR, byte[] actualCR, out string message) + public static byte[] ToBytes(this T value, ByteOrder order) + where T : struct { - string expectedCRtoHexStr = BitConverter.ToString(expectedCR); - string actualCRtoHexStr = BitConverter.ToString(actualCR); - - Func> func = s => - { - return (e, a) => - { -#if DEBUG - Console.WriteLine("WS: Error @IsValid: Invalid {0} response.", s); - Console.WriteLine(" expected: {0}", e); - Console.WriteLine(" actual : {0}", a); -#endif - return String.Format("Invalid {0} response: {1}", s, a); - }; - }; - - Func func1 = func("handshake"); - Func func2 = func("challenge"); - - string msg; - if ("HTTP/1.1 101 WebSocket Protocol Handshake".AreNotEqualDo(response[0], func1, out msg) || - "Upgrade: WebSocket".AreNotEqualDo(response[1], func1, out msg) || - "Connection: Upgrade".AreNotEqualDo(response[2], func1, out msg) || - expectedCRtoHexStr.AreNotEqualDo(actualCRtoHexStr, func2, out msg)) - { - message = msg; - return false; + byte[] buffer; + + if (typeof(T) == typeof(Boolean)) + { + buffer = BitConverter.GetBytes((Boolean)(object)value); + } + else if (typeof(T) == typeof(Char)) + { + buffer = BitConverter.GetBytes((Char)(object)value); } + else if (typeof(T) == typeof(Double)) + { + buffer = BitConverter.GetBytes((Double)(object)value); + } + else if (typeof(T) == typeof(Int16)) + { + buffer = BitConverter.GetBytes((Int16)(object)value); + } + else if (typeof(T) == typeof(Int32)) + { + buffer = BitConverter.GetBytes((Int32)(object)value); + } + else if (typeof(T) == typeof(Int64)) + { + buffer = BitConverter.GetBytes((Int64)(object)value); + } + else if (typeof(T) == typeof(Single)) + { + buffer = BitConverter.GetBytes((Single)(object)value); + } + else if (typeof(T) == typeof(UInt16)) + { + buffer = BitConverter.GetBytes((UInt16)(object)value); + } + else if (typeof(T) == typeof(UInt32)) + { + buffer = BitConverter.GetBytes((UInt32)(object)value); + } + else if (typeof(T) == typeof(UInt64)) + { + buffer = BitConverter.GetBytes((UInt64)(object)value); + } + else + { + buffer = new byte[]{}; + } + + return order.IsHostOrder() + ? buffer + : buffer.Reverse().ToArray(); + } + + public static byte[] ToHostOrder(this byte[] src, ByteOrder srcOrder) + { + byte[] buffer = new byte[src.Length]; + src.CopyTo(buffer, 0); - message = String.Empty; - return true; + return srcOrder.IsHostOrder() + ? buffer + : buffer.Reverse().ToArray(); } - public static void Times(this int n, Action act) + public static string ToString(this T[] array, string separater) { - for (int i = 0; i < n; i++) + int len; + StringBuilder sb; + + len = array.Length; + if (len == 0) { - act(); + return String.Empty; + } + + sb = new StringBuilder(); + for (int i = 0; i < len - 1; i++) + { + sb.AppendFormat("{0}{1}", array[i].ToString(), separater); } + sb.Append(array[len - 1].ToString()); + + return sb.ToString(); } } } diff --git a/websocket-sharp/Frame/CloseStatusCode.cs b/websocket-sharp/Frame/CloseStatusCode.cs new file mode 100644 index 00000000..55ac982b --- /dev/null +++ b/websocket-sharp/Frame/CloseStatusCode.cs @@ -0,0 +1,59 @@ +#region MIT License +/** + * CloseStatusCode.cs + * + * The MIT License + * + * Copyright (c) 2012 sta.blockhead + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#endregion + +using System; + +namespace WebSocketSharp.Frame +{ + public enum CloseStatusCode : ushort + { + /* + * Close Status Code + * + * Defined Status Codes: http://tools.ietf.org/html/rfc6455#section-7.4.1 + * + * "Reserved value" MUST NOT be set as a status code in a Close control frame by an endpoint. + * It is designated for use in applications expecting a status code to indicate that connection + * was closed due to a system grounds. + * + */ + NORMAL = 1000, // Normal closure. + AWAY = 1001, // A Server going down or a browser having navigated away from a page. + PROTOCOL_ERROR = 1002, // Terminating the connection due to a protocol error. + INCORRECT_DATA = 1003, // Received a type of data it cannot accept. + UNDEFINED = 1004, // Reserved value. Still undefined. + NO_STATUS_CODE = 1005, // Reserved value. + ABNORMAL = 1006, // Reserved value. Connection was closed abnormally. + INCONSISTENT_DATA = 1007, // Received data within a message that was not consistent with the type of the message. + POLICY_VIOLATION = 1008, // Received a message that violates its policy. + TOO_BIG = 1009, // Received a message that is too big. + IGNORE_EXTENSION = 1010, // Server ignored negotiated extensions. + SERVER_ERROR = 1011, // Server encountered an unexpected condition. + HANDSHAKE_FAILURE = 1015 // Reserved value. Failure to establish a connection. + } +} diff --git a/websocket-sharp/Frame/Fin.cs b/websocket-sharp/Frame/Fin.cs new file mode 100644 index 00000000..87428eb3 --- /dev/null +++ b/websocket-sharp/Frame/Fin.cs @@ -0,0 +1,38 @@ +#region MIT License +/** + * Fin.cs + * + * The MIT License + * + * Copyright (c) 2012 sta.blockhead + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#endregion + +using System; + +namespace WebSocketSharp.Frame +{ + public enum Fin : byte + { + MORE = 0x0, + FINAL = 0x1 + } +} diff --git a/websocket-sharp/Frame/Mask.cs b/websocket-sharp/Frame/Mask.cs new file mode 100644 index 00000000..62e828f8 --- /dev/null +++ b/websocket-sharp/Frame/Mask.cs @@ -0,0 +1,38 @@ +#region MIT License +/** + * Mask.cs + * + * The MIT License + * + * Copyright (c) 2012 sta.blockhead + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#endregion + +using System; + +namespace WebSocketSharp.Frame +{ + public enum Mask : byte + { + UNMASK = 0x0, + MASK = 0x1 + } +} diff --git a/websocket-sharp/Frame/Opcode.cs b/websocket-sharp/Frame/Opcode.cs new file mode 100644 index 00000000..d04155c4 --- /dev/null +++ b/websocket-sharp/Frame/Opcode.cs @@ -0,0 +1,43 @@ +#region MIT License +/** + * Opcode.cs + * + * The MIT License + * + * Copyright (c) 2012 sta.blockhead + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#endregion + +using System; + +namespace WebSocketSharp.Frame +{ + [Flags] + public enum Opcode : byte + { + CONT = 0x0, + TEXT = 0x1, + BINARY = 0x2, + CLOSE = 0x8, + PING = 0x9, + PONG = 0xa + } +} diff --git a/websocket-sharp/Frame/PayloadData.cs b/websocket-sharp/Frame/PayloadData.cs new file mode 100644 index 00000000..ddbba73a --- /dev/null +++ b/websocket-sharp/Frame/PayloadData.cs @@ -0,0 +1,184 @@ +#region MIT License +/** + * PayloadData.cs + * + * The MIT License + * + * Copyright (c) 2012 sta.blockhead + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#endregion + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace WebSocketSharp.Frame +{ + public class PayloadData : IEnumerable + { + + #region Public Static Fields + + public static readonly ulong MaxLength; + + #endregion + + #region Properties + + public byte[] ExtensionData { get; private set; } + public byte[] ApplicationData { get; private set; } + public bool IsMasked { get; private set; } + + public ulong Length + { + get + { + return (ulong)(ExtensionData.LongLength + ApplicationData.LongLength); + } + } + + #endregion + + #region Static Constructor + + static PayloadData() + { + MaxLength = long.MaxValue; + } + + #endregion + + #region Public Constructors + + public PayloadData(string appData) + : this(Encoding.UTF8.GetBytes(appData)) + { + } + + public PayloadData(byte[] appData) + : this(new byte[]{}, appData) + { + } + + public PayloadData(byte[] appData, bool masked) + : this(new byte[]{}, appData, masked) + { + } + + public PayloadData(byte[] extData, byte[] appData) + : this(extData, appData, false) + { + } + + public PayloadData(byte[] extData, byte[] appData, bool masked) + { + Func func = s => () => + { + string message = String.Format("{0} must not be null.", s); + throw new ArgumentNullException(message); + }; + extData.IsNullDo(func("extData")); + appData.IsNullDo(func("appData")); + + if ((ulong)extData.LongLength + (ulong)appData.LongLength > MaxLength) + { + throw new ArgumentOutOfRangeException("Plus extData length and appData lenght must be less than MaxLength."); + } + + ExtensionData = extData; + ApplicationData = appData; + IsMasked = masked; + } + + #endregion + + #region Private Methods + + private void mask(byte[] src, byte[] key) + { + if (key.Length != 4) + { + throw new ArgumentOutOfRangeException("key length must be 4."); + } + + for (long i = 0; i < src.LongLength; i++) + { + src[i] = (byte)(src[i] ^ key[i % 4]); + } + } + + #endregion + + #region Public Methods + + public IEnumerator GetEnumerator() + { + foreach (byte b in ExtensionData) + { + yield return b; + } + foreach (byte b in ApplicationData) + { + yield return b; + } + } + + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + public void Mask(byte[] maskingKey) + { + if (ExtensionData.LongLength > 0) + { + mask(ExtensionData, maskingKey); + } + if (ApplicationData.LongLength > 0) + { + mask(ApplicationData, maskingKey); + } + + IsMasked = !IsMasked; + } + + public byte[] ToBytes() + { + if (ExtensionData.LongLength > 0) + { + return ExtensionData.Concat(ApplicationData).ToArray(); + } + else + { + return ApplicationData; + } + } + + public override string ToString() + { + return BitConverter.ToString(ToBytes()); + } + + #endregion + } +} diff --git a/websocket-sharp/Frame/Rsv.cs b/websocket-sharp/Frame/Rsv.cs new file mode 100644 index 00000000..1d31f854 --- /dev/null +++ b/websocket-sharp/Frame/Rsv.cs @@ -0,0 +1,38 @@ +#region MIT License +/** + * Rsv.cs + * + * The MIT License + * + * Copyright (c) 2012 sta.blockhead + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#endregion + +using System; + +namespace WebSocketSharp.Frame +{ + public enum Rsv : byte + { + OFF = 0x0, + ON = 0x1 + } +} diff --git a/websocket-sharp/Frame/WsFrame.cs b/websocket-sharp/Frame/WsFrame.cs new file mode 100644 index 00000000..267802c5 --- /dev/null +++ b/websocket-sharp/Frame/WsFrame.cs @@ -0,0 +1,461 @@ +#region MIT License +/** + * WsFrame.cs + * + * The MIT License + * + * Copyright (c) 2012 sta.blockhead + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#endregion + +using System; +using System.IO; +using System.Collections.Generic; +using System.Text; + +namespace WebSocketSharp.Frame +{ + public class WsFrame : IEnumerable + { + #region Private Static Fields + + private static readonly int _readBufferLen; + + #endregion + + #region Properties + + public Fin Fin { get; private set; } + public Rsv Rsv1 { get; private set; } + public Rsv Rsv2 { get; private set; } + public Rsv Rsv3 { get; private set; } + public Opcode Opcode { get; private set; } + public Mask Masked { get; private set; } + public byte PayloadLen { get; private set; } + public byte[] ExtPayloadLen { get; private set; } + public byte[] MaskingKey { get; private set; } + public PayloadData PayloadData { get; private set; } + + public ulong Length + { + get + { + return 2 + (ulong)(ExtPayloadLen.Length + MaskingKey.Length) + PayloadLength; + } + } + + public ulong PayloadLength + { + get + { + return PayloadData.Length; + } + } + + #endregion + + #region Static Constructor + + static WsFrame() + { + _readBufferLen = 1024; + } + + #endregion + + #region Private Constructors + + private WsFrame() + { + Rsv1 = Rsv.OFF; + Rsv2 = Rsv.OFF; + Rsv3 = Rsv.OFF; + ExtPayloadLen = new byte[]{}; + MaskingKey = new byte[]{}; + } + + #endregion + + #region Public Constructors + + public WsFrame(Opcode opcode, PayloadData payloadData) + : this(Fin.FINAL, opcode, payloadData) + { + } + + public WsFrame(Fin fin, Opcode opcode, PayloadData payloadData) + : this(fin, opcode, Mask.MASK, payloadData) + { + } + + public WsFrame(Fin fin, Opcode opcode, Mask mask, PayloadData payloadData) + : this() + { + Fin = fin; + Opcode = opcode; + Masked = mask; + + ulong dataLength = payloadData.Length; + if (dataLength < 126) + { + PayloadLen = (byte)dataLength; + } + else if (dataLength < 0x010000) + { + PayloadLen = (byte)126; + ExtPayloadLen = ((ushort)dataLength).ToBytes(ByteOrder.BIG); + } + else + { + PayloadLen = (byte)127; + ExtPayloadLen = dataLength.ToBytes(ByteOrder.BIG); + } + + PayloadData = payloadData; + if (Masked == Mask.MASK) + { + MaskingKey = new byte[4]; + var rand = new Random(); + rand.NextBytes(MaskingKey); + PayloadData.Mask(MaskingKey); + } + } + + #endregion + + #region Public Static Methods + + public static WsFrame Parse(byte[] src) + { + return Parse(src, true); + } + + public static WsFrame Parse(byte[] src, bool unmask) + { + using (MemoryStream ms = new MemoryStream(src)) + { + return Parse(ms, unmask); + } + } + + public static WsFrame Parse(TStream stream) + where TStream : System.IO.Stream + { + return Parse(stream, true); + } + + public static WsFrame Parse(TStream stream, bool unmask) + where TStream : System.IO.Stream + { + Fin fin; + Rsv rsv1, rsv2, rsv3; + Opcode opcode; + Mask masked; + byte payloadLen; + byte[] extPayloadLen = new byte[]{}; + byte[] maskingKey = new byte[]{}; + PayloadData payloadData; + + byte[] buffer1, buffer2, buffer3; + int buffer1Len = 2; + int buffer2Len = 0; + ulong buffer3Len = 0; + int maskingKeyLen = 4; + + buffer1 = new byte[buffer1Len]; + stream.Read(buffer1, 0, buffer1Len); + + // FIN + fin = (buffer1[0] & 0x80) == 0x80 + ? Fin.FINAL + : Fin.MORE; + // RSV1 + rsv1 = (buffer1[0] & 0x40) == 0x40 + ? Rsv.ON + : Rsv.OFF; + // RSV2 + rsv2 = (buffer1[0] & 0x20) == 0x20 + ? Rsv.ON + : Rsv.OFF; + // RSV3 + rsv3 = (buffer1[0] & 0x10) == 0x10 + ? Rsv.ON + : Rsv.OFF; + // opcode + opcode = (Opcode)(buffer1[0] & 0x0f); + // MASK + masked = (buffer1[1] & 0x80) == 0x80 + ? Mask.MASK + : Mask.UNMASK; + // Payload len + payloadLen = (byte)(buffer1[1] & 0x7f); + // Extended payload length + if (payloadLen <= 125) + { + buffer3Len = payloadLen; + } + else if (payloadLen == 126) + { + buffer2Len = 2; + } + else + { + buffer2Len = 8; + } + + if (buffer2Len > 0) + { + buffer2 = new byte[buffer2Len]; + stream.Read(buffer2, 0, buffer2Len); + extPayloadLen = buffer2; + switch (buffer2Len) + { + case 2: + buffer3Len = extPayloadLen.To(ByteOrder.BIG); + break; + case 8: + buffer3Len = extPayloadLen.To(ByteOrder.BIG); + break; + } + } + + if (buffer3Len > PayloadData.MaxLength) + { + throw new WsReceivedTooBigMessageException(); + } + // Masking-key + if (masked == Mask.MASK) + { + maskingKey = new byte[maskingKeyLen]; + stream.Read(maskingKey, 0, maskingKeyLen); + } + // Payload Data + if (buffer3Len <= (ulong)_readBufferLen) + { + buffer3 = new byte[buffer3Len]; + stream.Read(buffer3, 0, (int)buffer3Len); + } + else + { + buffer3 = stream.ReadBytes(buffer3Len, _readBufferLen); + } + + if (masked == Mask.MASK) + { + payloadData = new PayloadData(buffer3, true); + if (unmask == true) + { + payloadData.Mask(maskingKey); + masked = Mask.UNMASK; + maskingKey = new byte[]{}; + } + } + else + { + payloadData = new PayloadData(buffer3); + } + + return new WsFrame + { + Fin = fin, + Rsv1 = rsv1, + Rsv2 = rsv2, + Rsv3 = rsv3, + Opcode = opcode, + Masked = masked, + PayloadLen = payloadLen, + ExtPayloadLen = extPayloadLen, + MaskingKey = maskingKey, + PayloadData = payloadData + }; + } + + #endregion + + #region Public Methods + + public IEnumerator GetEnumerator() + { + foreach (byte b in ToBytes()) + { + yield return b; + } + } + + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + public void Print() + { + byte[] buffer; + long count, i, j; + int countDigit, remainder; + string countFmt, extPayloadLen, headerFmt, topLineFmt, bottomLineFmt, payloadData, spFmt; + + switch (ExtPayloadLen.Length) + { + case 2: + extPayloadLen = ExtPayloadLen.To(ByteOrder.BIG).ToString(); + break; + case 8: + extPayloadLen = ExtPayloadLen.To(ByteOrder.BIG).ToString(); + break; + default: + extPayloadLen = String.Empty; + break; + } + + if (((Opcode.TEXT | Opcode.PING | Opcode.PONG) & Opcode) == Opcode && + Masked == Mask.UNMASK && + PayloadLength > 0) + { + payloadData = Encoding.UTF8.GetString(PayloadData.ToBytes()); + } + else + { + payloadData = BitConverter.ToString(PayloadData.ToBytes()); + } + + headerFmt = @" + WsFrame: + + FIN={0}, RSV1={1}, RSV2={2}, RSV3={3}, Opcode={4}, + MASK={5}, Payload Len={6}, Extended Payload Len={7}, + Masking Key ={8}, + Payload Data={9}"; + + buffer = ToBytes(); + count = (long)(Length / 4); + remainder = (int)(Length % 4); + + if (count < 10000) + { + countDigit = 4; + countFmt = "{0,4}"; + } + else if (count < 0x010000) + { + countDigit = 4; + countFmt = "{0,4:X}"; + } + else if (count < 0x0100000000) + { + countDigit = 8; + countFmt = "{0,8:X}"; + } + else + { + countDigit = 16; + countFmt = "{0,16:X}"; + } + + spFmt = String.Format("{{0,{0}}}", countDigit); + + topLineFmt = String.Format(@" + {0} 01234567 89ABCDEF 01234567 89ABCDEF + {0}+--------+--------+--------+--------+", spFmt); + + Func> func = s => + { + long lineCount = 0; + string lineFmt = String.Format(" {0}|{{1,8}} {{2,8}} {{3,8}} {{4,8}}|", s); + return (arg1, arg2, arg3, arg4) => + { + Console.WriteLine(lineFmt, ++lineCount, arg1, arg2, arg3, arg4); + }; + }; + var printLine = func(countFmt); + + bottomLineFmt = String.Format(" {0}+--------+--------+--------+--------+", spFmt); + + Console.WriteLine(headerFmt, + Fin, Rsv1, Rsv2, Rsv3, Opcode, + Masked, PayloadLen, extPayloadLen, + BitConverter.ToString(MaskingKey), + payloadData); + + Console.WriteLine(topLineFmt, String.Empty); + + for (i = 0; i <= count; i++) + { + j = i * 4; + if (i < count) + { + printLine( + Convert.ToString(buffer[j], 2).PadLeft(8, '0'), + Convert.ToString(buffer[j + 1], 2).PadLeft(8, '0'), + Convert.ToString(buffer[j + 2], 2).PadLeft(8, '0'), + Convert.ToString(buffer[j + 3], 2).PadLeft(8, '0')); + } + else if (i == count && remainder > 0) + { + printLine( + Convert.ToString(buffer[j], 2).PadLeft(8, '0'), + remainder >= 2 ? Convert.ToString(buffer[j + 1], 2).PadLeft(8, '0') : String.Empty, + remainder == 3 ? Convert.ToString(buffer[j + 2], 2).PadLeft(8, '0') : String.Empty, + String.Empty); + } + } + + Console.WriteLine(bottomLineFmt, String.Empty); + } + + public byte[] ToBytes() + { + var bytes = new List(); + + int first16 = (int)Fin; + first16 = (first16 << 1) + (int)Rsv1; + first16 = (first16 << 1) + (int)Rsv2; + first16 = (first16 << 1) + (int)Rsv3; + first16 = (first16 << 4) + (int)Opcode; + first16 = (first16 << 1) + (int)Masked; + first16 = (first16 << 7) + (int)PayloadLen; + bytes.AddRange(((ushort)first16).ToBytes(ByteOrder.BIG)); + + if (PayloadLen >= 126) + { + bytes.AddRange(ExtPayloadLen); + } + + if (Masked == Mask.MASK) + { + bytes.AddRange(MaskingKey); + } + + if (PayloadLen > 0) + { + bytes.AddRange(PayloadData.ToBytes()); + } + + return bytes.ToArray(); + } + + public override string ToString() + { + return BitConverter.ToString(ToBytes()); + } + + #endregion + } +} diff --git a/websocket-sharp/MessageEventArgs.cs b/websocket-sharp/MessageEventArgs.cs new file mode 100644 index 00000000..393f8e8c --- /dev/null +++ b/websocket-sharp/MessageEventArgs.cs @@ -0,0 +1,87 @@ +#region MIT License +/** + * MessageEventArgs.cs + * + * The MIT License + * + * Copyright (c) 2012 sta.blockhead + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#endregion + +using System; +using System.Text; +using WebSocketSharp.Frame; + +namespace WebSocketSharp +{ + public class MessageEventArgs : EventArgs + { + private Opcode _type; + private PayloadData _data; + + public Opcode Type + { + get + { + return _type; + } + } + + public string Data + { + get + { + if (((Opcode.TEXT | Opcode.PING | Opcode.PONG) & _type) == _type) + { + if (_data.Length > 0) + { + return Encoding.UTF8.GetString(_data.ToBytes()); + } + else + { + return String.Empty; + } + } + + return _type.ToString(); + } + } + + public byte[] RawData + { + get + { + return _data.ToBytes(); + } + } + + public MessageEventArgs(string data) + : this(Opcode.TEXT, new PayloadData(data)) + { + } + + public MessageEventArgs(Opcode type, PayloadData data) + { + _type = type; + _data = data; + } + } +} diff --git a/websocket-sharp/IWsStream.cs b/websocket-sharp/Stream/IWsStream.cs similarity index 77% rename from websocket-sharp/IWsStream.cs rename to websocket-sharp/Stream/IWsStream.cs index 43b17b1b..27ba1e45 100644 --- a/websocket-sharp/IWsStream.cs +++ b/websocket-sharp/Stream/IWsStream.cs @@ -4,7 +4,7 @@ * * The MIT License * - * Copyright (c) 2010 sta.blockhead + * Copyright (c) 2010-2012 sta.blockhead * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -27,15 +27,18 @@ #endregion using System; +using WebSocketSharp.Frame; -namespace WebSocketSharp +namespace WebSocketSharp.Stream { public interface IWsStream : IDisposable { - void Close(); - int Read(byte[] buffer, int offset, int size); - int ReadByte(); - void Write(byte[] buffer, int offset, int count); - void WriteByte(byte value); + void Close(); + int Read(byte[] buffer, int offset, int size); + int ReadByte(); + WsFrame ReadFrame(); + void Write(byte[] buffer, int offset, int count); + void WriteByte(byte value); + void WriteFrame(WsFrame frame); } } diff --git a/websocket-sharp/WsStream.cs b/websocket-sharp/Stream/WsStream.cs similarity index 62% rename from websocket-sharp/WsStream.cs rename to websocket-sharp/Stream/WsStream.cs index 4f542ad5..ab7efecd 100644 --- a/websocket-sharp/WsStream.cs +++ b/websocket-sharp/Stream/WsStream.cs @@ -4,7 +4,7 @@ * * The MIT License * - * Copyright (c) 2010 sta.blockhead + * Copyright (c) 2010-2012 sta.blockhead * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -32,21 +32,24 @@ using System.IO; using System.Net.Security; using System.Net.Sockets; using System.Reflection; +using WebSocketSharp.Frame; -namespace WebSocketSharp +namespace WebSocketSharp.Stream { - public class WsStream : IWsStream - where T : Stream + public class WsStream : IWsStream + where TStream : System.IO.Stream { - private T innerStream; + private TStream _innerStream; + private Object _forRead; + private Object _forWrite; - public WsStream(T innerStream) + public WsStream(TStream innerStream) { - Type streamType = typeof(T); + Type streamType = typeof(TStream); if (streamType != typeof(NetworkStream) && streamType != typeof(SslStream)) { - throw new NotSupportedException("Unsupported Stream type: " + streamType.ToString()); + throw new NotSupportedException("Not supported Stream type: " + streamType.ToString()); } if (innerStream == null) @@ -54,37 +57,56 @@ namespace WebSocketSharp throw new ArgumentNullException("innerStream"); } - this.innerStream = innerStream; + _innerStream = innerStream; + _forRead = new object(); + _forWrite = new object(); } public void Close() { - innerStream.Close(); + _innerStream.Close(); } public void Dispose() { - innerStream.Dispose(); + _innerStream.Dispose(); } public int Read(byte[] buffer, int offset, int size) { - return innerStream.Read(buffer, offset, size); + return _innerStream.Read(buffer, offset, size); } public int ReadByte() { - return innerStream.ReadByte(); + return _innerStream.ReadByte(); + } + + public WsFrame ReadFrame() + { + lock (_forRead) + { + return WsFrame.Parse(_innerStream); + } } public void Write(byte[] buffer, int offset, int count) { - innerStream.Write(buffer, offset, count); + _innerStream.Write(buffer, offset, count); } public void WriteByte(byte value) { - innerStream.WriteByte(value); + _innerStream.WriteByte(value); + } + + public void WriteFrame(WsFrame frame) + { + lock (_forWrite) + { + var buffer = frame.ToBytes(); + _innerStream.Write(buffer, 0, buffer.Length); + } } } } diff --git a/websocket-sharp/WebSocket.cs b/websocket-sharp/WebSocket.cs index 90ba1ab1..4b8bca5c 100644 --- a/websocket-sharp/WebSocket.cs +++ b/websocket-sharp/WebSocket.cs @@ -8,7 +8,7 @@ * The MIT License * * Copyright (c) 2009 Adam MacBeth - * Copyright (c) 2010 sta.blockhead + * Copyright (c) 2010-2012 sta.blockhead * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -32,228 +32,285 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; using System.Net; using System.Net.Security; using System.Net.Sockets; +using System.Reflection; using System.Text; using System.Threading; +using System.Security.Authentication; using System.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; +using WebSocketSharp.Frame; +using WebSocketSharp.Stream; namespace WebSocketSharp { - public delegate void MessageEventHandler(object sender, string eventdata); - public class WebSocket : IDisposable { - private Uri uri; - public string Url + #region Private Const Fields + + private const string _guid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; + private const string _version = "13"; + + #endregion + + #region Private Fields + + private string _base64key; + private string _binaryType; + private string _extensions; + private Object _forClose; + private Object _forSend; + private int _fragmentLen; + private Thread _msgThread; + private NetworkStream _netStream; + private string _protocol; + private string _protocols; + private volatile WsState _readyState; + private SslStream _sslStream; + private TcpClient _tcpClient; + private Uri _uri; + private SynchronizedCollection _unTransmittedBuffer; + private IWsStream _wsStream; + + #endregion + + #region Properties + + public string BinaryType + { + get { return _binaryType; } + } + + public ulong BufferedAmount + { + get + { + ulong bufferedAmount = 0; + + lock (_unTransmittedBuffer.SyncRoot) + { + foreach (WsFrame frame in _unTransmittedBuffer) + { + bufferedAmount += frame.PayloadLength; + } + } + + return bufferedAmount; + } + } + + public string Extensions { - get { return uri.ToString(); } + get { return _extensions; } } - private Object sync = new Object(); + public string Protocol + { + get { return _protocol; } + } - private volatile WsState readyState; public WsState ReadyState { - get { return readyState; } + get { return _readyState; } private set { + _readyState = value; + switch (value) { case WsState.OPEN: - readyState = value; + messageThreadStart(); if (OnOpen != null) { OnOpen(this, EventArgs.Empty); } break; case WsState.CLOSING: + break; case WsState.CLOSED: - lock(sync) - { - close(value); - } + closeConnection(); break; } } } - private StringBuilder unTransmittedBuffer; - public String UnTransmittedBuffer + public SynchronizedCollection UnTransmittedBuffer { - get { return unTransmittedBuffer.ToString(); } + get { return _unTransmittedBuffer; } } - private long bufferedAmount; - public long BufferedAmount + public string Url { - get { return bufferedAmount; } + get { return _uri.ToString(); } } - private string protocol; - public string Protocol - { - get { return protocol; } - } + #endregion - private TcpClient tcpClient; - private NetworkStream netStream; - private SslStream sslStream; - private IWsStream wsStream; - private Thread msgThread; + #region Events - public event EventHandler OnOpen; - public event MessageEventHandler OnMessage; - public event MessageEventHandler OnError; - public event EventHandler OnClose; + public event EventHandler OnOpen; + public event EventHandler OnMessage; + public event EventHandler OnError; + public event EventHandler OnClose; - public WebSocket(string url) - : this(url, String.Empty) + #endregion + + #region Private Constructors + + private WebSocket() { + _binaryType = String.Empty; + _extensions = String.Empty; + _forClose = new Object(); + _forSend = new Object(); + _fragmentLen = 1024; // Max value is int.MaxValue - 14. + _protocol = String.Empty; + _readyState = WsState.CONNECTING; + _unTransmittedBuffer = new SynchronizedCollection(); } - public WebSocket(string url, string protocol) + #endregion + + #region Public Constructors + + public WebSocket(string url, params string[] protocols) + : this() { - this.uri = new Uri(url); - string scheme = uri.Scheme; + _uri = new Uri(url); + string scheme = _uri.Scheme; if (scheme != "ws" && scheme != "wss") { - throw new ArgumentException("Unsupported scheme: " + scheme); + throw new ArgumentException("Unsupported WebSocket URI scheme: " + scheme); } - this.readyState = WsState.CONNECTING; - this.unTransmittedBuffer = new StringBuilder(); - this.bufferedAmount = 0; - this.protocol = protocol; + _protocols = protocols.ToString(", "); } public WebSocket( - string url, - EventHandler onOpen, - MessageEventHandler onMessage, - MessageEventHandler onError, - EventHandler onClose) - : this(url, String.Empty, onOpen, onMessage, onError, onClose) + string url, + EventHandler onOpen, + EventHandler onMessage, + EventHandler onError, + EventHandler onClose, + params string[] protocols) + : this(url, protocols) { - } + OnOpen = onOpen; + OnMessage = onMessage; + OnError = onError; + OnClose = onClose; - public WebSocket( - string url, - string protocol, - EventHandler onOpen, - MessageEventHandler onMessage, - MessageEventHandler onError, - EventHandler onClose) - : this(url, protocol) - { - this.OnOpen += onOpen; - this.OnMessage += onMessage; - this.OnError += onError; - this.OnClose += onClose; - Connect(); } - public void Connect() - { - if (readyState == WsState.OPEN) - { - Console.WriteLine("WS: Info @Connect: Connection is already established."); - return; - } - - createConnection(); - doHandshake(); + #endregion - this.msgThread = new Thread(new ThreadStart(message)); - msgThread.IsBackground = true; - msgThread.Start(); - } + #region Private Methods - public void Send(string data) + private void close(PayloadData data) { - if (readyState == WsState.CONNECTING) + #if DEBUG + Console.WriteLine("WS: Info@close: Current thread IsBackground?: {0}", Thread.CurrentThread.IsBackground); + #endif + lock(_forClose) { - throw new InvalidOperationException("Handshake not complete."); - } - - byte[] dataBuffer = Encoding.UTF8.GetBytes(data); + if (_readyState == WsState.CLOSING || + _readyState == WsState.CLOSED) + { + return; + } - try - { - wsStream.WriteByte(0x00); - wsStream.Write(dataBuffer, 0, dataBuffer.Length); - wsStream.WriteByte(0xff); - } - catch (Exception e) - { - unTransmittedBuffer.Append(data); - bufferedAmount += dataBuffer.Length; + if (OnClose != null) + { + OnClose(this, new CloseEventArgs(data)); + } - if (OnError != null) + if (_readyState == WsState.CONNECTING) { - OnError(this, e.Message); + ReadyState = WsState.CLOSED; + return; + } + else + { + ReadyState = WsState.CLOSING; } -#if DEBUG - Console.WriteLine("WS: Error @Send: {0}", e.Message); -#endif } - } - public void Close() - { - ReadyState = WsState.CLOSING; + var frame = new WsFrame(Opcode.CLOSE, data); + closeHandshake(frame); + #if DEBUG + Console.WriteLine("WS: Info@close: Exit close method."); + #endif } - public void Dispose() + private void close(CloseStatusCode code, string reason) { - Close(); + byte[] buffer; + List data; + + data = new List(((ushort)code).ToBytes(ByteOrder.BIG)); + + if (reason != String.Empty) + { + buffer = Encoding.UTF8.GetBytes(reason); + data.AddRange(buffer); + } + + close(new PayloadData(data.ToArray())); } - private void close(WsState state) + private void closeConnection() { -#if DEBUG - Console.WriteLine("WS: Info @close: Current thread IsBackground: {0}", Thread.CurrentThread.IsBackground); -#endif - if (readyState == WsState.CLOSING || - readyState == WsState.CLOSED) + if (_wsStream != null) { - return; + _wsStream.Dispose(); + _wsStream = null; + } + else if (_netStream != null) + { + _netStream.Dispose(); + _netStream = null; } - readyState = state; - - if (wsStream != null) + if (_tcpClient != null) { - wsStream.Close(); - wsStream = null; + _tcpClient.Close(); + _tcpClient = null; } + } - if (tcpClient != null) + private void closeHandshake(WsFrame frame) + { + try + { + _wsStream.WriteFrame(frame); + } + catch (Exception ex) { - tcpClient.Close(); - tcpClient = null; + _unTransmittedBuffer.Add(frame); + error(ex.Message); } - if (OnClose != null) + if (!Thread.CurrentThread.IsBackground) { - OnClose(this, EventArgs.Empty); + _msgThread.Join(5000); } -#if DEBUG - Console.WriteLine("WS: Info @close: Exit close method."); -#endif + + ReadyState = WsState.CLOSED; } private void createConnection() { - string scheme = uri.Scheme; - string host = uri.DnsSafeHost; - int port = uri.Port; + string scheme = _uri.Scheme; + string host = _uri.DnsSafeHost; + int port = _uri.Port; if (port <= 0) { @@ -267,229 +324,610 @@ namespace WebSocketSharp } } - this.tcpClient = new TcpClient(host, port); - this.netStream = tcpClient.GetStream(); + _tcpClient = new TcpClient(host, port); + _netStream = _tcpClient.GetStream(); if (scheme == "wss") { - this.sslStream = new SslStream(netStream); - sslStream.AuthenticateAsClient(host); - this.wsStream = new WsStream(sslStream); + RemoteCertificateValidationCallback validation = (sender, certificate, chain, sslPolicyErrors) => + { + // Temporary implementation + return true; + }; + + _sslStream = new SslStream(_netStream, false, validation); + _sslStream.AuthenticateAsClient(host); + + _wsStream = new WsStream(_sslStream); } else { - this.wsStream = new WsStream(netStream); + _wsStream = new WsStream(_netStream); } } + private string createExpectedKey() + { + byte[] keySrc; + SHA1 sha1 = new SHA1CryptoServiceProvider(); + StringBuilder sb = new StringBuilder(_base64key); + + sb.Append(_guid); + keySrc = sha1.ComputeHash(Encoding.UTF8.GetBytes(sb.ToString())); + + return Convert.ToBase64String(keySrc); + } + + private string createOpeningHandshake() + { + byte[] keySrc; + int port; + string crlf, host, origin, path; + string reqConnection, reqHost, reqMethod, reqOrigin, reqUpgrade; + string secWsKey, secWsProtocol, secWsVersion; + Random rand; + + path = _uri.PathAndQuery; + + host = _uri.DnsSafeHost; + port = ((IPEndPoint)_tcpClient.Client.RemoteEndPoint).Port; + if (port != 80) + { + host += ":" + port; + } + + origin = "http://" + host; + + keySrc = new byte[16]; + rand = new Random(); + rand.NextBytes(keySrc); + _base64key = Convert.ToBase64String(keySrc); + + crlf = "\r\n"; + + reqMethod = String.Format("GET {0} HTTP/1.1{1}", path, crlf); + reqHost = String.Format("Host: {0}{1}", host, crlf); + reqUpgrade = String.Format("Upgrade: websocket{0}", crlf); + reqConnection = String.Format("Connection: Upgrade{0}", crlf); + reqOrigin = String.Format("Origin: {0}{1}", origin, crlf); + secWsKey = String.Format("Sec-WebSocket-Key: {0}{1}", _base64key, crlf); + + secWsProtocol = _protocols != String.Empty + ? String.Format("Sec-WebSocket-Protocol: {0}{1}", _protocols, crlf) + : _protocols; + + secWsVersion = String.Format("Sec-WebSocket-Version: {0}{1}", _version, crlf); + + return reqMethod + + reqHost + + reqUpgrade + + reqConnection + + secWsKey + + reqOrigin + + secWsProtocol + + secWsVersion + + crlf; + } + private void doHandshake() { - byte[] expectedCR, actualCR; - string request = createOpeningHandshake(out expectedCR); -#if DEBUG - Console.WriteLine("WS: Info @doHandshake: Handshake from client: \n{0}", request); -#endif - string[] response = sendOpeningHandshake(request, out actualCR); -#if DEBUG - Console.WriteLine("WS: Info @doHandshake: Handshake from server:"); + string msg, request; + string[] response; + + request = createOpeningHandshake(); + #if DEBUG + Console.WriteLine("WS: Info@doHandshake: Opening handshake from client:\n{0}", request); + #endif + response = sendOpeningHandshake(request); + #if DEBUG + Console.WriteLine("WS: Info@doHandshake: Opening handshake from server:"); foreach (string s in response) { Console.WriteLine("{0}", s); } -#endif - string msg; - if (!(response.IsValid(expectedCR, actualCR, out msg))) + #endif + if (!isValidResponse(response, out msg)) { - throw new IOException(msg); + throw new InvalidOperationException(msg); } - for (int i = 3; i < response.Length; i++) - { - if (response[i].Contains("Sec-WebSocket-Protocol:")) - { - int j = response[i].IndexOf(":"); - protocol = response[i].Substring(j + 1).Trim(); - break; - } - } -#if DEBUG - Console.WriteLine("WS: Info @doHandshake: Sub protocol: {0}", protocol); -#endif ReadyState = WsState.OPEN; } - private string createOpeningHandshake(out byte[] expectedCR) + private void error(string message) { - string path = uri.PathAndQuery; - string host = uri.DnsSafeHost; - string origin = "http://" + host; + var callerFrame = new StackFrame(1); + var caller = callerFrame.GetMethod(); - int port = ((IPEndPoint)tcpClient.Client.RemoteEndPoint).Port; - if (port != 80) + if (OnError != null) { - host += ":" + port; + OnError(this, new MessageEventArgs(message)); } + else + { + Console.WriteLine("WS: Error@{0}: {1}", caller.Name, message); + } + } - string subProtocol = protocol != String.Empty - ? String.Format("Sec-WebSocket-Protocol: {0}\r\n", protocol) - : protocol; - - Random rand = new Random(); + private bool isValidResponse(string[] response, out string message) + { + Func> func; + string resUpgrade, resConnection; + string secWsAccept, secWsVersion; + string[] resStatus; + List extensionList = new List(); - uint key1, key2; - string secKey1 = rand.GenerateSecKey(out key1); - string secKey2 = rand.GenerateSecKey(out key2); + func = s => + { + return (e, a) => + { + return String.Format("Invalid response {0} value: {1}(expected: {2})", s, a, e); + }; + }; - byte[] key3 = new byte[8].InitializeWithPrintableASCII(rand); + resStatus = response[0].Split(' '); + if ("HTTP/1.1".NotEqualsDo(resStatus[0], func("HTTP Version"), out message, false)) + { + return false; + } + if ("101".NotEqualsDo(resStatus[1], func("Status Code"), out message, false)) + { + return false; + } - string secKeys = "Sec-WebSocket-Key1: {0}\r\n" + - "Sec-WebSocket-Key2: {1}\r\n"; - secKeys = String.Format(secKeys, secKey1, secKey2); + for (int i = 1; i < response.Length; i++) + { + if (response[i].Contains("Upgrade:")) + { + resUpgrade = response[i].GetHeaderValue(":"); + if ("websocket".NotEqualsDo(resUpgrade, func("Upgrade"), out message, true)) + { + return false; + } + } + else if (response[i].Contains("Connection:")) + { + resConnection = response[i].GetHeaderValue(":"); + if ("Upgrade".NotEqualsDo(resConnection, func("Connection"), out message, true)) + { + return false; + } + } + else if (response[i].Contains("Sec-WebSocket-Accept:")) + { + secWsAccept = response[i].GetHeaderValue(":"); + if (createExpectedKey().NotEqualsDo(secWsAccept, func("Sec-WebSocket-Accept"), out message, false)) + { + return false; + } + } + else if (response[i].Contains("Sec-WebSocket-Extensions:")) + { + extensionList.Add(response[i].GetHeaderValue(":")); + } + else if (response[i].Contains("Sec-WebSocket-Protocol:")) + { + _protocol = response[i].GetHeaderValue(":"); + #if DEBUG + Console.WriteLine("WS: Info@isValidResponse: Sub protocol: {0}", _protocol); + #endif + } + else if (response[i].Contains("Sec-WebSocket-Version:")) + { + secWsVersion = response[i].GetHeaderValue(":"); + #if DEBUG + Console.WriteLine("WS: Info@isValidResponse: Version: {0}", secWsVersion); + #endif + } + else + { + Console.WriteLine("WS: Info@isValidResponse: Unsupported response header line: {0}", response[i]); + } + } + #if DEBUG + foreach (string s in extensionList) + { + Console.WriteLine("WS: Info@isValidResponse: Extensions: {0}", s); + } + #endif + message = String.Empty; + return true; + } - string key3ToAscii = Encoding.ASCII.GetString(key3); + private void message() + { + #if DEBUG + Console.WriteLine("WS: Info@message: Current thread IsBackground?: {0}", Thread.CurrentThread.IsBackground); + #endif + MessageEventArgs eventArgs; + while (_readyState == WsState.OPEN) + { + try + { + eventArgs = receive(); - expectedCR = createExpectedCR(key1, key2, key3); + if (OnMessage != null && eventArgs != null) + { + OnMessage(this, eventArgs); + } + } + catch (WsReceivedTooBigMessageException ex) + { + close(CloseStatusCode.TOO_BIG, ex.Message); + } + } + #if DEBUG + Console.WriteLine("WS: Info@message: Exit message method."); + #endif + } - return "GET " + path + " HTTP/1.1\r\n" + - "Upgrade: WebSocket\r\n" + - "Connection: Upgrade\r\n" + - subProtocol + - "Host: " + host + "\r\n" + - "Origin: " + origin + "\r\n" + - secKeys + - "\r\n" + - key3ToAscii; + private void messageThreadStart() + { + _msgThread = new Thread(new ThreadStart(message)); + _msgThread.IsBackground = true; + _msgThread.Start(); } - private byte[] createExpectedCR(uint key1, uint key2, byte[] key3) + private MessageEventArgs receive() { - byte[] key1Bytes = BitConverter.GetBytes(key1); - byte[] key2Bytes = BitConverter.GetBytes(key2); + List dataBuffer; + MessageEventArgs eventArgs; + Fin fin; + WsFrame frame; + Opcode opcode; + PayloadData payloadData; + + frame = _wsStream.ReadFrame(); + if ((frame.Fin == Fin.FINAL && frame.Opcode == Opcode.CONT) || + (frame.Fin == Fin.MORE && frame.Opcode == Opcode.CONT)) + { + return null; + } - Array.Reverse(key1Bytes); - Array.Reverse(key2Bytes); + fin = frame.Fin; + opcode = frame.Opcode; + payloadData = frame.PayloadData; + eventArgs = null; - byte[] concatKeys = key1Bytes.Concat(key2Bytes).Concat(key3).ToArray(); + switch (fin) + { + case Fin.MORE: + dataBuffer = new List(payloadData.ToBytes()); + while (true) + { + frame = _wsStream.ReadFrame(); - MD5 md5 = MD5.Create(); - return md5.ComputeHash(concatKeys); + if (frame.Fin == Fin.MORE) + { + if (frame.Opcode == Opcode.CONT) + {// MORE & CONT + dataBuffer.AddRange(frame.PayloadData.ToBytes()); + } + else + { + #if DEBUG + Console.WriteLine("WS: Info@receive: Client starts closing handshake."); + #endif + close(CloseStatusCode.INCORRECT_DATA, String.Empty); + return null; + } + } + else if (frame.Opcode == Opcode.CONT) + {// FINAL & CONT + dataBuffer.AddRange(frame.PayloadData.ToBytes()); + break; + } + else if (frame.Opcode == Opcode.CLOSE) + {// FINAL & CLOSE + #if DEBUG + Console.WriteLine("WS: Info@receive: Server starts closing handshake."); + #endif + close(frame.PayloadData); + return null; + } + else if (frame.Opcode == Opcode.PING) + {// FINAL & PING + pong(frame.PayloadData); + if (OnMessage != null) + { + OnMessage(this, new MessageEventArgs(frame.Opcode, frame.PayloadData)); + } + } + else if (frame.Opcode == Opcode.PONG) + {// FINAL & PONG + if (OnMessage != null) + { + OnMessage(this, new MessageEventArgs(frame.Opcode, frame.PayloadData)); + } + } + else + {// FINAL & (TEXT | BINARY) + #if DEBUG + Console.WriteLine("WS: Info@receive: Client starts closing handshake."); + #endif + close(CloseStatusCode.INCORRECT_DATA, String.Empty); + return null; + } + } + + eventArgs = new MessageEventArgs(opcode, new PayloadData(dataBuffer.ToArray())); + break; + case Fin.FINAL: + switch (opcode) + { + case Opcode.TEXT: + case Opcode.BINARY: + case Opcode.PONG: + goto default; + case Opcode.CLOSE: + #if DEBUG + Console.WriteLine("WS: Info@receive: Server starts closing handshake."); + #endif + close(payloadData); + break; + case Opcode.PING: + pong(payloadData); + goto default; + default: + eventArgs = new MessageEventArgs(opcode, payloadData); + break; + } + + break; + } + + return eventArgs; } - private string[] sendOpeningHandshake(string openingHandshake, out byte[] challengeResponse) + private void pong(PayloadData data) { - challengeResponse = new byte[16]; - List rawdata = new List(); + var frame = new WsFrame(Opcode.PONG, data); + send(frame); + } - byte[] sendBuffer = Encoding.UTF8.GetBytes(openingHandshake); - wsStream.Write(sendBuffer, 0, sendBuffer.Length); + private void pong(string data) + { + var payloadData = new PayloadData(data); + pong(payloadData); + } - while (true) + private bool send(WsFrame frame) + { + if (_readyState != WsState.OPEN) + { + var msg = "Connection isn't established or connection was closed."; + error(msg); + + return false; + } + + try { - if (wsStream.ReadByte().EqualsWithSaveTo('\r', rawdata) && - wsStream.ReadByte().EqualsWithSaveTo('\n', rawdata) && - wsStream.ReadByte().EqualsWithSaveTo('\r', rawdata) && - wsStream.ReadByte().EqualsWithSaveTo('\n', rawdata)) + if (_unTransmittedBuffer.Count == 0) { - wsStream.Read(challengeResponse, 0, challengeResponse.Length); - break; + _wsStream.WriteFrame(frame); + } + else + { + _unTransmittedBuffer.Add(frame); } } + catch (Exception ex) + { + _unTransmittedBuffer.Add(frame); + error(ex.Message); - return Encoding.UTF8.GetString(rawdata.ToArray()) - .Replace("\r\n", "\n").Replace("\n\n", "\n") - .Split('\n'); + return false; + } + + return true; } - private void message() + private void send(Opcode opcode, PayloadData data) { -#if DEBUG - Console.WriteLine("WS: Info @message: Current thread IsBackground: {0}", Thread.CurrentThread.IsBackground); -#endif - string data; - while (readyState == WsState.OPEN) + using (MemoryStream ms = new MemoryStream(data.ToBytes())) { - data = receive(); + send(opcode, ms); + } + } - if (OnMessage != null && data != null) + private void send(Opcode opcode, TStream stream) + where TStream : System.IO.Stream + { + if (_unTransmittedBuffer.Count > 0) + { + var msg = "Current data can not be sent because there is untransmitted data."; + error(msg); + } + + lock(_forSend) + { + var length = stream.Length; + if (length <= _fragmentLen) { - OnMessage(this, data); + var buffer = new byte[length]; + stream.Read(buffer, 0, (int)length); + var frame = new WsFrame(opcode, new PayloadData(buffer)); + send(frame); + } + else + { + sendFragmented(opcode, stream); } } -#if DEBUG - Console.WriteLine("WS: Info @message: Exit message method."); -#endif } - private string receive() + private ulong sendFragmented(Opcode opcode, TStream stream) + where TStream : System.IO.Stream { - try + if (_readyState != WsState.OPEN) { - byte frame_type = (byte)wsStream.ReadByte(); - byte b; + var msg = "Connection isn't established or connection was closed."; + error(msg); - if ((frame_type & 0x80) == 0x80) - { - // Skip data frame - int len = 0; - int b_v; + return 0; + } - do - { - b = (byte)wsStream.ReadByte(); - b_v = b & 0x7f; - len = len * 128 + b_v; - } - while ((b & 0x80) == 0x80); + WsFrame frame; + PayloadData payloadData; + + var buffer1 = new byte[_fragmentLen]; + var buffer2 = new byte[_fragmentLen]; + ulong readLen = 0; + int tmpLen1 = 0; + int tmpLen2 = 0; + + tmpLen1 = stream.Read(buffer1, 0, _fragmentLen); + while (tmpLen1 > 0) + { + payloadData = new PayloadData(buffer1.SubArray(0, tmpLen1)); - for (int i = 0; i < len; i++) + tmpLen2 = stream.Read(buffer2, 0, _fragmentLen); + if (tmpLen2 > 0) + { + if (readLen > 0) { - wsStream.ReadByte(); + frame = new WsFrame(Fin.MORE, Opcode.CONT, payloadData); } - - if (frame_type == 0xff && len == 0) + else { - ReadyState = WsState.CLOSED; -#if DEBUG - Console.WriteLine("WS: Info @receive: Server start closing handshake."); -#endif + frame = new WsFrame(Fin.MORE, opcode, payloadData); } } - else if (frame_type == 0x00) + else { - List raw_data = new List(); - - while (true) + if (readLen > 0) + { + frame = new WsFrame(Opcode.CONT, payloadData); + } + else { - b = (byte)wsStream.ReadByte(); + frame = new WsFrame(opcode, payloadData); + } + } - if (b == 0xff) - { - break; - } + readLen += (ulong)tmpLen1; + send(frame); - raw_data.Add(b); - } + if (tmpLen2 == 0) break; + payloadData = new PayloadData(buffer2.SubArray(0, tmpLen2)); - return Encoding.UTF8.GetString(raw_data.ToArray()); + tmpLen1 = stream.Read(buffer1, 0, _fragmentLen); + if (tmpLen1 > 0) + { + frame = new WsFrame(Fin.MORE, Opcode.CONT, payloadData); } + else + { + frame = new WsFrame(Opcode.CONT, payloadData); + } + + readLen += (ulong)tmpLen2; + send(frame); } - catch (Exception e) + + return readLen; + } + + private string[] sendOpeningHandshake(string value) + { + var readData = new List(); + var buffer = Encoding.UTF8.GetBytes(value); + + _wsStream.Write(buffer, 0, buffer.Length); + + while (true) { - if (readyState == WsState.OPEN) + if (_wsStream.ReadByte().EqualsAndSaveTo('\r', readData) && + _wsStream.ReadByte().EqualsAndSaveTo('\n', readData) && + _wsStream.ReadByte().EqualsAndSaveTo('\r', readData) && + _wsStream.ReadByte().EqualsAndSaveTo('\n', readData)) { - if (OnError != null) - { - OnError(this, e.Message); - } - - ReadyState = WsState.CLOSED; -#if DEBUG - Console.WriteLine("WS: Error @receive: {0}", e.Message); -#endif + break; } } - return null; + return Encoding.UTF8.GetString(readData.ToArray()) + .Replace("\r\n", "\n").Replace("\n\n", "\n").TrimEnd('\n') + .Split('\n'); + } + + #endregion + + #region Public Methods + + public void Connect() + { + if (_readyState == WsState.OPEN) + { + Console.WriteLine("WS: Info@Connect: Connection is already established."); + return; + } + + try + { + createConnection(); + doHandshake(); + } + catch (Exception ex) + { + error(ex.Message); + close(CloseStatusCode.HANDSHAKE_FAILURE, ex.Message); + } + } + + public void Close() + { + Close(CloseStatusCode.NORMAL); } + + public void Close(CloseStatusCode code) + { + Close(code, String.Empty); + } + + public void Close(CloseStatusCode code, string reason) + { + close(code, reason); + } + + public void Dispose() + { + Close(CloseStatusCode.AWAY); + } + + public void Ping() + { + Ping(String.Empty); + } + + public void Ping(string data) + { + var payloadData = new PayloadData(data); + var frame = new WsFrame(Opcode.PING, payloadData); + send(frame); + } + + public void Send(string data) + { + var payloadData = new PayloadData(data); + send(Opcode.TEXT, payloadData); + } + + public void Send(byte[] data) + { + var payloadData = new PayloadData(data); + send(Opcode.BINARY, payloadData); + } + + public void Send(FileInfo file) + { + using (FileStream fs = file.OpenRead()) + { + send(Opcode.BINARY, fs); + } + } + + #endregion } } diff --git a/websocket-sharp/WsReceivedTooBigMessageException.cs b/websocket-sharp/WsReceivedTooBigMessageException.cs new file mode 100644 index 00000000..bf50e89e --- /dev/null +++ b/websocket-sharp/WsReceivedTooBigMessageException.cs @@ -0,0 +1,54 @@ +#region MIT License +/** + * WsReceivedTooBigMessageException.cs + * + * The MIT License + * + * Copyright (c) 2012 sta.blockhead + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#endregion + +using System; +using WebSocketSharp.Frame; + +namespace WebSocketSharp +{ + public class WsReceivedTooBigMessageException : Exception + { + private static readonly string _defaultMessage; + + static WsReceivedTooBigMessageException() + { + _defaultMessage = String.Format( + "Client received a payload data bigger than the allowable value({0} bytes).", PayloadData.MaxLength); + } + + public WsReceivedTooBigMessageException() + : this(_defaultMessage) + { + } + + public WsReceivedTooBigMessageException(string message) + : base(message) + { + } + } +} diff --git a/websocket-sharp/WsState.cs b/websocket-sharp/WsState.cs index 0de3717e..130c6691 100644 --- a/websocket-sharp/WsState.cs +++ b/websocket-sharp/WsState.cs @@ -4,7 +4,7 @@ * * The MIT License * - * Copyright (c) 2010 sta.blockhead + * Copyright (c) 2010-2012 sta.blockhead * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/websocket-sharp/bin/Debug/websocket-sharp.dll b/websocket-sharp/bin/Debug/websocket-sharp.dll deleted file mode 100755 index d89cb927..00000000 Binary files a/websocket-sharp/bin/Debug/websocket-sharp.dll and /dev/null differ diff --git a/websocket-sharp/bin/Debug/websocket-sharp.dll.mdb b/websocket-sharp/bin/Debug/websocket-sharp.dll.mdb deleted file mode 100644 index 6bad5847..00000000 Binary files a/websocket-sharp/bin/Debug/websocket-sharp.dll.mdb and /dev/null differ diff --git a/websocket-sharp/bin/Debug_Ubuntu/websocket-sharp.dll b/websocket-sharp/bin/Debug_Ubuntu/websocket-sharp.dll index 169f4103..893cd1ec 100755 Binary files a/websocket-sharp/bin/Debug_Ubuntu/websocket-sharp.dll and b/websocket-sharp/bin/Debug_Ubuntu/websocket-sharp.dll differ diff --git a/websocket-sharp/bin/Debug_Ubuntu/websocket-sharp.dll.mdb b/websocket-sharp/bin/Debug_Ubuntu/websocket-sharp.dll.mdb index 962b5374..936a5130 100644 Binary files a/websocket-sharp/bin/Debug_Ubuntu/websocket-sharp.dll.mdb and b/websocket-sharp/bin/Debug_Ubuntu/websocket-sharp.dll.mdb differ diff --git a/websocket-sharp/bin/Release/websocket-sharp.dll b/websocket-sharp/bin/Release/websocket-sharp.dll index 0ab8c167..f34b7c91 100755 Binary files a/websocket-sharp/bin/Release/websocket-sharp.dll and b/websocket-sharp/bin/Release/websocket-sharp.dll differ diff --git a/websocket-sharp/bin/Release_Ubuntu/websocket-sharp.dll b/websocket-sharp/bin/Release_Ubuntu/websocket-sharp.dll index a8ac0450..5330643e 100755 Binary files a/websocket-sharp/bin/Release_Ubuntu/websocket-sharp.dll and b/websocket-sharp/bin/Release_Ubuntu/websocket-sharp.dll differ diff --git a/websocket-sharp/websocket-sharp.csproj b/websocket-sharp/websocket-sharp.csproj index 8d32d7f1..6dfbe2f3 100644 --- a/websocket-sharp/websocket-sharp.csproj +++ b/websocket-sharp/websocket-sharp.csproj @@ -52,17 +52,30 @@ - - notify-sharp - + - - + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/websocket-sharp/websocket-sharp.pidb b/websocket-sharp/websocket-sharp.pidb index 49eb985d..6174b8d3 100644 Binary files a/websocket-sharp/websocket-sharp.pidb and b/websocket-sharp/websocket-sharp.pidb differ diff --git a/wsclient/bin/Debug/websocket-sharp.dll b/wsclient/bin/Debug/websocket-sharp.dll deleted file mode 100755 index d89cb927..00000000 Binary files a/wsclient/bin/Debug/websocket-sharp.dll and /dev/null differ diff --git a/wsclient/bin/Debug/websocket-sharp.dll.mdb b/wsclient/bin/Debug/websocket-sharp.dll.mdb deleted file mode 100644 index 6bad5847..00000000 Binary files a/wsclient/bin/Debug/websocket-sharp.dll.mdb and /dev/null differ diff --git a/wsclient/bin/Debug/wsclient.exe b/wsclient/bin/Debug/wsclient.exe deleted file mode 100755 index cb4059b5..00000000 Binary files a/wsclient/bin/Debug/wsclient.exe and /dev/null differ diff --git a/wsclient/bin/Debug/wsclient.exe.mdb b/wsclient/bin/Debug/wsclient.exe.mdb deleted file mode 100644 index e48d8238..00000000 Binary files a/wsclient/bin/Debug/wsclient.exe.mdb and /dev/null differ diff --git a/wsclient/bin/Debug_Ubuntu/websocket-sharp.dll b/wsclient/bin/Debug_Ubuntu/websocket-sharp.dll deleted file mode 100755 index 169f4103..00000000 Binary files a/wsclient/bin/Debug_Ubuntu/websocket-sharp.dll and /dev/null differ diff --git a/wsclient/bin/Debug_Ubuntu/websocket-sharp.dll.mdb b/wsclient/bin/Debug_Ubuntu/websocket-sharp.dll.mdb deleted file mode 100644 index 962b5374..00000000 Binary files a/wsclient/bin/Debug_Ubuntu/websocket-sharp.dll.mdb and /dev/null differ diff --git a/wsclient/bin/Debug_Ubuntu/wsclient.exe b/wsclient/bin/Debug_Ubuntu/wsclient.exe deleted file mode 100755 index 61d03a1c..00000000 Binary files a/wsclient/bin/Debug_Ubuntu/wsclient.exe and /dev/null differ diff --git a/wsclient/bin/Debug_Ubuntu/wsclient.exe.mdb b/wsclient/bin/Debug_Ubuntu/wsclient.exe.mdb deleted file mode 100644 index 5ba0ea75..00000000 Binary files a/wsclient/bin/Debug_Ubuntu/wsclient.exe.mdb and /dev/null differ diff --git a/wsclient/bin/Release/websocket-sharp.dll b/wsclient/bin/Release/websocket-sharp.dll deleted file mode 100755 index 0ab8c167..00000000 Binary files a/wsclient/bin/Release/websocket-sharp.dll and /dev/null differ diff --git a/wsclient/bin/Release/wsclient.exe b/wsclient/bin/Release/wsclient.exe deleted file mode 100755 index 79f91b15..00000000 Binary files a/wsclient/bin/Release/wsclient.exe and /dev/null differ diff --git a/wsclient/bin/Release_Ubuntu/websocket-sharp.dll b/wsclient/bin/Release_Ubuntu/websocket-sharp.dll deleted file mode 100755 index a8ac0450..00000000 Binary files a/wsclient/bin/Release_Ubuntu/websocket-sharp.dll and /dev/null differ diff --git a/wsclient/bin/Release_Ubuntu/wsclient.exe b/wsclient/bin/Release_Ubuntu/wsclient.exe deleted file mode 100755 index 58563e43..00000000 Binary files a/wsclient/bin/Release_Ubuntu/wsclient.exe and /dev/null differ diff --git a/wsclient/wsclient.cs b/wsclient/wsclient.cs deleted file mode 100644 index 257a0172..00000000 --- a/wsclient/wsclient.cs +++ /dev/null @@ -1,67 +0,0 @@ -#if NOTIFY -using Notifications; -#endif -using System; -using System.Threading; -using WebSocketSharp; - -namespace Example -{ - public class Program - { - public static void Main(string[] args) - { - //using (WebSocket ws = new WebSocket("ws://localhost:8000/")) - using (WebSocket ws = new WebSocket("ws://localhost:8000/", "chat")) - { - ws.OnOpen += (o, e) => - { - ws.Send("Hi, all!"); - }; - - ws.OnMessage += (o, s) => - { -#if NOTIFY - Notification nf = new Notification("[WebSocket] Message", - s, - "notification-message-im"); - nf.AddHint("append", "allowed"); - nf.Show(); -#else - Console.WriteLine("[WebSocket] Message: {0}", s); -#endif - }; - - ws.OnError += (o, s) => - { - Console.WriteLine("[WebSocket] Error : {0}", s); - }; - - /*ws.OnClose += (o, e) => - { - //Do something. - }; - */ - ws.Connect(); - - Thread.Sleep(500); - Console.WriteLine("\nType \"exit\" to exit.\n"); - - string data; - while (true) - { - Thread.Sleep(500); - - Console.Write("> "); - data = Console.ReadLine(); - if (data == "exit") - { - break; - } - - ws.Send(data); - } - } - } - } -} diff --git a/wsclient/wsclient.pidb b/wsclient/wsclient.pidb deleted file mode 100644 index 3175c85f..00000000 Binary files a/wsclient/wsclient.pidb and /dev/null differ diff --git a/wsclient1/bin/Debug/websocket-sharp.dll b/wsclient1/bin/Debug/websocket-sharp.dll deleted file mode 100755 index 41001b3a..00000000 Binary files a/wsclient1/bin/Debug/websocket-sharp.dll and /dev/null differ diff --git a/wsclient1/bin/Debug/websocket-sharp.dll.mdb b/wsclient1/bin/Debug/websocket-sharp.dll.mdb deleted file mode 100644 index ca151b40..00000000 Binary files a/wsclient1/bin/Debug/websocket-sharp.dll.mdb and /dev/null differ diff --git a/wsclient1/bin/Debug/wsclient1.exe b/wsclient1/bin/Debug/wsclient1.exe deleted file mode 100755 index cf137213..00000000 Binary files a/wsclient1/bin/Debug/wsclient1.exe and /dev/null differ diff --git a/wsclient1/bin/Debug/wsclient1.exe.mdb b/wsclient1/bin/Debug/wsclient1.exe.mdb deleted file mode 100644 index 6696dacf..00000000 Binary files a/wsclient1/bin/Debug/wsclient1.exe.mdb and /dev/null differ diff --git a/wsclient1/bin/Debug_Ubuntu/websocket-sharp.dll b/wsclient1/bin/Debug_Ubuntu/websocket-sharp.dll deleted file mode 100755 index 7136652b..00000000 Binary files a/wsclient1/bin/Debug_Ubuntu/websocket-sharp.dll and /dev/null differ diff --git a/wsclient1/bin/Debug_Ubuntu/websocket-sharp.dll.mdb b/wsclient1/bin/Debug_Ubuntu/websocket-sharp.dll.mdb deleted file mode 100644 index 6c2c977a..00000000 Binary files a/wsclient1/bin/Debug_Ubuntu/websocket-sharp.dll.mdb and /dev/null differ diff --git a/wsclient1/bin/Debug_Ubuntu/wsclient1.exe b/wsclient1/bin/Debug_Ubuntu/wsclient1.exe deleted file mode 100755 index 20fc61d8..00000000 Binary files a/wsclient1/bin/Debug_Ubuntu/wsclient1.exe and /dev/null differ diff --git a/wsclient1/bin/Debug_Ubuntu/wsclient1.exe.mdb b/wsclient1/bin/Debug_Ubuntu/wsclient1.exe.mdb deleted file mode 100644 index ff7736f9..00000000 Binary files a/wsclient1/bin/Debug_Ubuntu/wsclient1.exe.mdb and /dev/null differ diff --git a/wsclient1/bin/Release/websocket-sharp.dll b/wsclient1/bin/Release/websocket-sharp.dll deleted file mode 100755 index 7efae5ab..00000000 Binary files a/wsclient1/bin/Release/websocket-sharp.dll and /dev/null differ diff --git a/wsclient1/bin/Release/wsclient1.exe b/wsclient1/bin/Release/wsclient1.exe deleted file mode 100755 index 42562233..00000000 Binary files a/wsclient1/bin/Release/wsclient1.exe and /dev/null differ diff --git a/wsclient1/bin/Release_Ubuntu/websocket-sharp.dll b/wsclient1/bin/Release_Ubuntu/websocket-sharp.dll deleted file mode 100755 index a8ac0450..00000000 Binary files a/wsclient1/bin/Release_Ubuntu/websocket-sharp.dll and /dev/null differ diff --git a/wsclient1/bin/Release_Ubuntu/wsclient1.exe b/wsclient1/bin/Release_Ubuntu/wsclient1.exe deleted file mode 100755 index 4b717110..00000000 Binary files a/wsclient1/bin/Release_Ubuntu/wsclient1.exe and /dev/null differ diff --git a/wsclient1/wsclient1.cs b/wsclient1/wsclient1.cs deleted file mode 100644 index edc78115..00000000 --- a/wsclient1/wsclient1.cs +++ /dev/null @@ -1,65 +0,0 @@ -#if NOTIFY -using Notifications; -#endif -using System; -using System.Threading; -using WebSocketSharp; - -namespace Example -{ - public class Program - { - public static void Main(string[] args) - { - EventHandler onOpen = (o, e) => - { - Console.WriteLine("[WebSocket] Opened."); - }; - - MessageEventHandler onMessage = (o, s) => - { -#if NOTIFY - Notification nf = new Notification("[WebSocket] Message", - s, - "notification-message-im"); - nf.AddHint("append", "allowed"); - nf.Show(); -#else - Console.WriteLine("[WebSocket] Message: {0}", s); -#endif - }; - - MessageEventHandler onError = (o, s) => - { - Console.WriteLine("[WebSocket] Error : {0}", s); - }; - - EventHandler onClose = (o, e) => - { - Console.WriteLine("[WebSocket] Closed."); - }; - - //using (WebSocket ws = new WebSocket("ws://localhost:8000/", onOpen, onMessage, onError, onClose)) - using (WebSocket ws = new WebSocket("ws://localhost:8000/", "chat", onOpen, onMessage, onError, onClose)) - { - Thread.Sleep(500); - Console.WriteLine("\nType \"exit\" to exit.\n"); - - string data; - while (true) - { - Thread.Sleep(500); - - Console.Write("> "); - data = Console.ReadLine(); - if (data == "exit") - { - break; - } - - ws.Send(data); - } - } - } - } -} diff --git a/wsclient1/wsclient1.csproj b/wsclient1/wsclient1.csproj deleted file mode 100644 index ea2c1449..00000000 --- a/wsclient1/wsclient1.csproj +++ /dev/null @@ -1,68 +0,0 @@ - - - - Debug - AnyCPU - 9.0.21022 - 2.0 - {B0B609B7-A81C-46B0-A9B8-82E9716D355B} - Exe - wsclient1 - wsclient1 - v3.5 - - - true - full - false - bin\Debug - DEBUG - prompt - 4 - true - - - none - false - bin\Release - prompt - 4 - true - - - full - false - bin\Debug_Ubuntu - prompt - 4 - true - true - DEBUG,NOTIFY - - - none - false - bin\Release_Ubuntu - NOTIFY - prompt - 4 - true - - - - - notify-sharp - - - - - - - - - {B357BAC7-529E-4D81-A0D2-71041B19C8DE} - websocket-sharp - - - - \ No newline at end of file diff --git a/wsclient1/wsclient1.pidb b/wsclient1/wsclient1.pidb deleted file mode 100644 index 21166ed0..00000000 Binary files a/wsclient1/wsclient1.pidb and /dev/null differ