using System; using System.IO; using System.Threading; using ln.logging; using ln.http.exceptions; using System.Linq; using System.Security.Cryptography; using System.Text; namespace ln.http.websocket { public enum WebSocketOpcode : int { CONTINUATION = 0x00, TEXT = 0x01, BINARY = 0x02, CLOSE = 0x08, PING = 0x09, PONG = 0x0A, INVALIDOPCODE = -1 } public enum WebSocketState { HANDSHAKE, OPEN, CLOSING, CLOSED, ERROR } public delegate void WebSocketEventDelegate(WebSocket sender,WebSocketEventArgs e); public class WebSocket { public HttpRequest HttpRequest { get; } public Stream Stream { get; } public WebSocketState State { get; private set; } = WebSocketState.HANDSHAKE; public event WebSocketEventDelegate WebSocketEvent; Thread receiverThread; public WebSocket(HttpRequest httpRequest) { HttpRequest = httpRequest; Stream = httpRequest.GetConnectionStream(); if ((!httpRequest.GetRequestHeader("upgrade", "").Contains("websocket")) && (!httpRequest.GetRequestHeader("connection", "").Contains("Upgrade"))) throw new HttpException(400, "This resource is a websocket endpoint only"); if (!httpRequest.GetRequestHeader("Sec-WebSocket-Version", "").Equals("13")) throw new HttpException(400, "Unsupported Protocol Version (WebSocket)"); String wsKey = httpRequest.GetRequestHeader("Sec-WebSocket-Key"); HttpResponse httpResponse = new HttpResponse(httpRequest); httpResponse.StatusCode = 101; httpResponse.StatusMessage = "Switching Protocols"; httpResponse.AddHeader("upgrade", "websocket"); httpResponse.AddHeader("connection", "Upgrade"); httpResponse.AddHeader("Sec-WebSocket-Version", "13"); string acceptKey = String.Format("{0}258EAFA5-E914-47DA-95CA-C5AB0DC85B11", httpRequest.GetRequestHeader("Sec-WebSocket-Key")); httpResponse.AddHeader( "Sec-Websocket-Accept", Convert.ToBase64String(SHA1.Create().ComputeHash(Encoding.ASCII.GetBytes(acceptKey))) ); HTTPServer.SendResponse(Stream, httpResponse); State = WebSocketState.OPEN; } public bool IsAlive => ((receiverThread != null) && receiverThread.IsAlive); public void Start() { if ((receiverThread == null) || !receiverThread.IsAlive) { receiverThread = new Thread(() => Run()); receiverThread.Start(); } } public void Close() { switch (State) { case WebSocketState.HANDSHAKE: case WebSocketState.ERROR: case WebSocketState.CLOSING: State = WebSocketState.CLOSED; Stream.Close(); break; case WebSocketState.CLOSED: break; case WebSocketState.OPEN: WebSocketFrame closeFrame = new WebSocketFrame(WebSocketOpcode.CLOSE); lock (Stream) { Send(closeFrame); State = WebSocketState.CLOSING; } break; } } public void Run() { try { //Send("Hello World!"); while (State != WebSocketState.CLOSED) { WebSocketFrame webSocketFrame = new WebSocketFrame(Stream); switch (webSocketFrame.Opcode) { case WebSocketOpcode.TEXT: case WebSocketOpcode.BINARY: WebSocketEvent(this, new WebSocketEventArgs(webSocketFrame)); break; case WebSocketOpcode.CLOSE: if (State == WebSocketState.OPEN) { WebSocketFrame closeFrame = new WebSocketFrame(WebSocketOpcode.CLOSE); closeFrame.FIN = true; lock (Stream) { Send(closeFrame); State = WebSocketState.CLOSING; } } State = WebSocketState.CLOSED; Stream.Close(); break; case WebSocketOpcode.PING: WebSocketFrame pong = new WebSocketFrame(WebSocketOpcode.PONG); pong.ApplicationData = webSocketFrame.ApplicationData; Send(pong); break; } } } catch (Exception e) { Logging.Log(LogLevel.ERROR, "WebSocket caught Exception: {0}", e.ToString()); Logging.Log(e); } receiverThread = null; } public void Send(WebSocketFrame frame) { lock (Stream) { if (State == WebSocketState.OPEN) frame.WriteTo(Stream); else throw new IOException("WebSocket is not open"); } } public void Send(string textMessage) { WebSocketFrame webSocketFrame = new WebSocketFrame(textMessage); Send(webSocketFrame); } public void Send(byte[] binaryMessage) { WebSocketFrame webSocketFrame = new WebSocketFrame(binaryMessage); Send(webSocketFrame); } } }