using System; using System.IO; using System.Threading; using ln.logging; using ln.http.exceptions; using System.Security.Cryptography; using System.Text; using ln.types; 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 abstract class WebSocket { public HTTPServer HTTPServer => HttpRequest.HTTPServer; public HttpRequest HttpRequest { get; } public Stream Stream { get; } public WebSocketState State { get; private set; } = WebSocketState.HANDSHAKE; 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))) ); HTTPServerConnection.SendResponse(Stream, httpResponse); HTTPServerConnection.Current.Value.AbortRequested += (connection) => Close(); State = WebSocketState.OPEN; } public bool IsAlive => false; 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 { while (State != WebSocketState.CLOSED) { WebSocketFrame webSocketFrame = new WebSocketFrame(Stream); switch (webSocketFrame.Opcode) { case WebSocketOpcode.TEXT: Received(Encoding.UTF8.GetString(webSocketFrame.ApplicationData)); break; case WebSocketOpcode.BINARY: Received(webSocketFrame.ApplicationData); 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 (IOException) { State = WebSocketState.ERROR; Logging.Log(LogLevel.DEBUG, "WebSocket connection was dropped unexpected"); Close(); } catch (Exception e) { Logging.Log(LogLevel.ERROR, "WebSocket caught Exception: {0}", e.ToString()); Logging.Log(e); } finally { } } public virtual bool Received(string textMessage) { Logging.Log(LogLevel.WARNING, "WebSocket received unexpected text message:\n{0}", textMessage); return false; } public virtual bool Received(byte[] binaryMessage) { Logging.Log(LogLevel.WARNING, "WebSocket received unexpected binary message:\n{0}",binaryMessage.ToHexString()); return false; } public void Send(WebSocketFrame frame) { lock (Stream) { if (State == WebSocketState.OPEN) { try { frame.WriteTo(Stream); } catch (IOException) { if (State != WebSocketState.ERROR) { Logging.Log(LogLevel.ERROR, "WebSocket.Send(): Websocket connection was dropped unexpected"); State = WebSocketState.ERROR; Close(); } } } 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); } } }