123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642 |
- using System;
- using System.Text;
- using System.Security.Cryptography;
- using System.Collections.Generic;
- using System.Threading;
- namespace NetCoreServer
- {
- /// <summary>
- /// WebSocket utility class
- /// </summary>
- public class WebSocket : IWebSocket
- {
- private readonly IWebSocket _wsHandler;
- /// <summary>
- /// Initialize a new WebSocket
- /// </summary>
- /// <param name="wsHandler">WebSocket handler</param>
- public WebSocket(IWebSocket wsHandler) { _wsHandler = wsHandler; ClearWsBuffers(); InitWsNonce(); }
- /// <summary>
- /// Final frame
- /// </summary>
- public const byte WS_FIN = 0x80;
- /// <summary>
- /// Text frame
- /// </summary>
- public const byte WS_TEXT = 0x01;
- /// <summary>
- /// Binary frame
- /// </summary>
- public const byte WS_BINARY = 0x02;
- /// <summary>
- /// Close frame
- /// </summary>
- public const byte WS_CLOSE = 0x08;
- /// <summary>
- /// Ping frame
- /// </summary>
- public const byte WS_PING = 0x09;
- /// <summary>
- /// Pong frame
- /// </summary>
- public const byte WS_PONG = 0x0A;
- /// <summary>
- /// Perform WebSocket client upgrade
- /// </summary>
- /// <param name="response">WebSocket upgrade HTTP response</param>
- /// <param name="id">WebSocket client Id</param>
- /// <returns>'true' if the WebSocket was successfully upgrade, 'false' if the WebSocket was not upgrade</returns>
- public bool PerformClientUpgrade(HttpResponse response, Guid id)
- {
- if (response.Status != 101)
- return false;
- bool error = false;
- bool accept = false;
- bool connection = false;
- bool upgrade = false;
- // Validate WebSocket handshake headers
- for (int i = 0; i < response.Headers; i++)
- {
- var header = response.Header(i);
- var key = header.Item1;
- var value = header.Item2;
- if (string.Compare(key, "Connection", StringComparison.OrdinalIgnoreCase) == 0)
- {
- if (string.Compare(value, "Upgrade", StringComparison.OrdinalIgnoreCase) != 0)
- {
- error = true;
- _wsHandler.OnWsError("Invalid WebSocket handshaked response: 'Connection' header value must be 'Upgrade'");
- break;
- }
- connection = true;
- }
- else if (string.Compare(key, "Upgrade", StringComparison.OrdinalIgnoreCase) == 0)
- {
- if (string.Compare(value, "websocket", StringComparison.OrdinalIgnoreCase) != 0)
- {
- error = true;
- _wsHandler.OnWsError("Invalid WebSocket handshaked response: 'Upgrade' header value must be 'websocket'");
- break;
- }
- upgrade = true;
- }
- else if (string.Compare(key, "Sec-WebSocket-Accept", StringComparison.OrdinalIgnoreCase) == 0)
- {
- // Calculate the original WebSocket hash
- string wskey = Convert.ToBase64String(WsNonce) + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
- string wshash;
- using (SHA1 sha1 = SHA1.Create())
- {
- wshash = Encoding.UTF8.GetString(sha1.ComputeHash(Encoding.UTF8.GetBytes(wskey)));
- }
- // Get the received WebSocket hash
- wskey = Encoding.UTF8.GetString(Convert.FromBase64String(value));
- // Compare original and received hashes
- if (string.Compare(wskey, wshash, StringComparison.InvariantCulture) != 0)
- {
- error = true;
- _wsHandler.OnWsError("Invalid WebSocket handshaked response: 'Sec-WebSocket-Accept' value validation failed");
- break;
- }
- accept = true;
- }
- }
- // Failed to perform WebSocket handshake
- if (!accept || !connection || !upgrade)
- {
- if (!error)
- _wsHandler.OnWsError("Invalid WebSocket response");
- return false;
- }
- // WebSocket successfully handshaked!
- WsHandshaked = true;
- WsRandom.NextBytes(WsSendMask);
- _wsHandler.OnWsConnected(response);
- return true;
- }
- /// <summary>
- /// Perform WebSocket server upgrade
- /// </summary>
- /// <param name="request">WebSocket upgrade HTTP request</param>
- /// <param name="response">WebSocket upgrade HTTP response</param>
- /// <returns>'true' if the WebSocket was successfully upgrade, 'false' if the WebSocket was not upgrade</returns>
- public bool PerformServerUpgrade(HttpRequest request, HttpResponse response)
- {
- if (request.Method != "GET")
- return false;
- bool error = false;
- bool connection = false;
- bool upgrade = false;
- bool wsKey = false;
- bool wsVersion = false;
- string accept = "";
- // Validate WebSocket handshake headers
- for (int i = 0; i < request.Headers; i++)
- {
- var header = request.Header(i);
- var key = header.Item1;
- var value = header.Item2;
- if (string.Compare(key, "Connection", StringComparison.OrdinalIgnoreCase) == 0)
- {
- if ((string.Compare(value, "Upgrade", StringComparison.OrdinalIgnoreCase) != 0) && (string.Compare(value, "keep-alive, Upgrade", StringComparison.OrdinalIgnoreCase) != 0))
- {
- error = true;
- response.MakeErrorResponse(400, "Invalid WebSocket handshaked request: 'Connection' header value must be 'Upgrade' or 'keep-alive, Upgrade'");
- break;
- }
- connection = true;
- }
- else if (string.Compare(key, "Upgrade", StringComparison.OrdinalIgnoreCase) == 0)
- {
- if (string.Compare(value, "websocket", StringComparison.OrdinalIgnoreCase) != 0)
- {
- error = true;
- response.MakeErrorResponse(400, "Invalid WebSocket handshaked request: 'Upgrade' header value must be 'websocket'");
- break;
- }
- upgrade = true;
- }
- else if (string.Compare(key, "Sec-WebSocket-Key", StringComparison.OrdinalIgnoreCase) == 0)
- {
- if (string.IsNullOrEmpty(value))
- {
- error = true;
- response.MakeErrorResponse(400, "Invalid WebSocket handshaked request: 'Sec-WebSocket-Key' header value must be non empty");
- break;
- }
- // Calculate the original WebSocket hash
- string wskey = value + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
- byte[] wshash;
- using (SHA1 sha1 = SHA1.Create())
- {
- wshash = sha1.ComputeHash(Encoding.UTF8.GetBytes(wskey));
- }
- accept = Convert.ToBase64String(wshash);
- wsKey = true;
- }
- else if (string.Compare(key, "Sec-WebSocket-Version", StringComparison.OrdinalIgnoreCase) == 0)
- {
- if (string.Compare(value, "13", StringComparison.OrdinalIgnoreCase) != 0)
- {
- error = true;
- response.MakeErrorResponse(400, "Invalid WebSocket handshaked request: 'Sec-WebSocket-Version' header value must be '13'");
- break;
- }
- wsVersion = true;
- }
- }
- // Filter out non WebSocket handshake requests
- if (!connection && !upgrade && !wsKey && !wsVersion)
- return false;
- // Failed to perform WebSocket handshake
- if (!connection || !upgrade || !wsKey || !wsVersion)
- {
- if (!error)
- response.MakeErrorResponse(400, "Invalid WebSocket response");
- _wsHandler.SendUpgrade(response);
- return false;
- }
- // Prepare WebSocket upgrade success response
- response.Clear();
- response.SetBegin(101);
- response.SetHeader("Connection", "Upgrade");
- response.SetHeader("Upgrade", "websocket");
- response.SetHeader("Sec-WebSocket-Accept", accept);
- response.SetBody();
- // Validate WebSocket upgrade request and response
- if (!_wsHandler.OnWsConnecting(request, response))
- return false;
- // Send WebSocket upgrade response
- _wsHandler.SendUpgrade(response);
- // WebSocket successfully handshaked!
- WsHandshaked = true;
- Array.Fill(WsSendMask, (byte)0);
- _wsHandler.OnWsConnected(request);
- return true;
- }
- /// <summary>
- /// Prepare WebSocket send frame
- /// </summary>
- /// <param name="opcode">WebSocket opcode</param>
- /// <param name="mask">WebSocket mask</param>
- /// <param name="buffer">Buffer to send as a span of bytes</param>
- /// <param name="status">WebSocket status (default is 0)</param>
- public void PrepareSendFrame(byte opcode, bool mask, ReadOnlySpan<byte> buffer, int status = 0)
- {
- // Check if we need to store additional 2 bytes of close status frame
- bool storeStatus = ((opcode & WS_CLOSE) == WS_CLOSE) && ((buffer.Length > 0) || (status != 0));
- long size = storeStatus ? (buffer.Length + 2) : buffer.Length;
- // Clear the previous WebSocket send buffer
- WsSendBuffer.Clear();
- // Append WebSocket frame opcode
- WsSendBuffer.Append(opcode);
- // Append WebSocket frame size
- if (size <= 125)
- WsSendBuffer.Append((byte)(((int)size & 0xFF) | (mask ? 0x80 : 0)));
- else if (size <= 65535)
- {
- WsSendBuffer.Append((byte)(126 | (mask ? 0x80 : 0)));
- WsSendBuffer.Append((byte)((size >> 8) & 0xFF));
- WsSendBuffer.Append((byte)(size & 0xFF));
- }
- else
- {
- WsSendBuffer.Append((byte)(127 | (mask ? 0x80 : 0)));
- for (int i = 7; i >= 0; i--)
- WsSendBuffer.Append((byte)((size >> (8 * i)) & 0xFF));
- }
- if (mask)
- {
- // Append WebSocket frame mask
- WsSendBuffer.Append(WsSendMask);
- }
- // Resize WebSocket frame buffer
- long offset = WsSendBuffer.Size;
- WsSendBuffer.Resize(WsSendBuffer.Size + size);
- int index = 0;
- // Append WebSocket close status
- // RFC 6455: If there is a body, the first two bytes of the body MUST
- // be a 2-byte unsigned integer (in network byte order) representing
- // a status code with value code.
- if (storeStatus)
- {
- index += 2;
- WsSendBuffer.Data[offset + 0] = (byte)(((status >> 8) & 0xFF) ^ WsSendMask[0]);
- WsSendBuffer.Data[offset + 1] = (byte)((status & 0xFF) ^ WsSendMask[1]);
- }
- // Mask WebSocket frame content
- for (int i = index; i < size; i++)
- WsSendBuffer.Data[offset + i] = (byte)(buffer[i - index] ^ WsSendMask[i % 4]);
- }
- /// <summary>
- /// Prepare WebSocket send frame
- /// </summary>
- /// <param name="buffer">Buffer to send</param>
- /// <param name="offset">Buffer offset</param>
- /// <param name="size">Buffer size</param>
- public void PrepareReceiveFrame(byte[] buffer, long offset, long size)
- {
- lock (WsReceiveLock)
- {
- int index = 0;
- // Clear received data after WebSocket frame was processed
- if (WsFrameReceived)
- {
- WsFrameReceived = false;
- WsHeaderSize = 0;
- WsPayloadSize = 0;
- WsReceiveFrameBuffer.Clear();
- Array.Clear(WsReceiveMask, 0, WsReceiveMask.Length);
- }
- if (WsFinalReceived)
- {
- WsFinalReceived = false;
- WsReceiveFinalBuffer.Clear();
- }
- while (size > 0)
- {
- // Clear received data after WebSocket frame was processed
- if (WsFrameReceived)
- {
- WsFrameReceived = false;
- WsHeaderSize = 0;
- WsPayloadSize = 0;
- WsReceiveFrameBuffer.Clear();
- Array.Clear(WsReceiveMask, 0, WsReceiveMask.Length);
- }
- if (WsFinalReceived)
- {
- WsFinalReceived = false;
- WsReceiveFinalBuffer.Clear();
- }
- // Prepare WebSocket frame opcode and mask flag
- if (WsReceiveFrameBuffer.Size < 2)
- {
- for (long i = 0; i < 2; i++, index++, size--)
- {
- if (size == 0)
- return;
- WsReceiveFrameBuffer.Append(buffer[offset + index]);
- }
- }
- byte opcode = (byte)(WsReceiveFrameBuffer[0] & 0x0F);
- bool fin = ((WsReceiveFrameBuffer[0] >> 7) & 0x01) != 0;
- bool mask = ((WsReceiveFrameBuffer[1] >> 7) & 0x01) != 0;
- long payload = WsReceiveFrameBuffer[1] & (~0x80);
- // Prepare WebSocket opcode
- WsOpcode = (opcode != 0) ? opcode : WsOpcode;
- // Prepare WebSocket frame size
- if (payload <= 125)
- {
- WsHeaderSize = 2 + (mask ? 4 : 0);
- WsPayloadSize = payload;
- }
- else if (payload == 126)
- {
- if (WsReceiveFrameBuffer.Size < 4)
- {
- for (long i = 0; i < 2; i++, index++, size--)
- {
- if (size == 0)
- return;
- WsReceiveFrameBuffer.Append(buffer[offset + index]);
- }
- }
- payload = ((WsReceiveFrameBuffer[2] << 8) | (WsReceiveFrameBuffer[3] << 0));
- WsHeaderSize = 4 + (mask ? 4 : 0);
- WsPayloadSize = payload;
- }
- else if (payload == 127)
- {
- if (WsReceiveFrameBuffer.Size < 10)
- {
- for (long i = 0; i < 8; i++, index++, size--)
- {
- if (size == 0)
- return;
- WsReceiveFrameBuffer.Append(buffer[offset + index]);
- }
- }
- payload = ((WsReceiveFrameBuffer[2] << 56) | (WsReceiveFrameBuffer[3] << 48) | (WsReceiveFrameBuffer[4] << 40) | (WsReceiveFrameBuffer[5] << 32) | (WsReceiveFrameBuffer[6] << 24) | (WsReceiveFrameBuffer[7] << 16) | (WsReceiveFrameBuffer[8] << 8) | (WsReceiveFrameBuffer[9] << 0));
- WsHeaderSize = 10 + (mask ? 4 : 0);
- WsPayloadSize = payload;
- }
- // Prepare WebSocket frame mask
- if (mask)
- {
- if (WsReceiveFrameBuffer.Size < WsHeaderSize)
- {
- for (long i = 0; i < 4; i++, index++, size--)
- {
- if (size == 0)
- return;
- WsReceiveFrameBuffer.Append(buffer[offset + index]);
- WsReceiveMask[i] = buffer[offset + index];
- }
- }
- }
- long total = WsHeaderSize + WsPayloadSize;
- long length = Math.Min(total - WsReceiveFrameBuffer.Size, size);
- // Prepare WebSocket frame payload
- WsReceiveFrameBuffer.Append(buffer[((int)offset + index)..((int)offset + index + (int)length)]);
- index += (int)length;
- size -= length;
- // Process WebSocket frame
- if (WsReceiveFrameBuffer.Size == total)
- {
- // Unmask WebSocket frame content
- if (mask)
- {
- for (long i = 0; i < WsPayloadSize; i++)
- WsReceiveFinalBuffer.Append((byte)(WsReceiveFrameBuffer[WsHeaderSize + i] ^ WsReceiveMask[i % 4]));
- }
- else
- WsReceiveFinalBuffer.Append(WsReceiveFrameBuffer.AsSpan().Slice((int)WsHeaderSize, (int)WsPayloadSize));
- WsFrameReceived = true;
- // Finalize WebSocket frame
- if (fin)
- {
- WsFinalReceived = true;
- switch (WsOpcode)
- {
- case WS_PING:
- {
- // Call the WebSocket ping handler
- _wsHandler.OnWsPing(WsReceiveFinalBuffer.Data, 0, WsReceiveFinalBuffer.Size);
- break;
- }
- case WS_PONG:
- {
- // Call the WebSocket pong handler
- _wsHandler.OnWsPong(WsReceiveFinalBuffer.Data, 0, WsReceiveFinalBuffer.Size);
- break;
- }
- case WS_CLOSE:
- {
- int sindex = 0;
- int status = 1000;
- // Read WebSocket close status
- if (WsReceiveFinalBuffer.Size >= 2)
- {
- sindex += 2;
- status = ((WsReceiveFinalBuffer[0] << 8) | (WsReceiveFinalBuffer[1] << 0));
- }
- // Call the WebSocket close handler
- _wsHandler.OnWsClose(WsReceiveFinalBuffer.Data, sindex, WsReceiveFinalBuffer.Size - sindex, status);
- break;
- }
- case WS_BINARY:
- case WS_TEXT:
- {
- // Call the WebSocket received handler
- _wsHandler.OnWsReceived(WsReceiveFinalBuffer.Data, 0, WsReceiveFinalBuffer.Size);
- break;
- }
- }
- }
- }
- }
- }
- }
- /// <summary>
- /// Required WebSocket receive frame size
- /// </summary>
- public long RequiredReceiveFrameSize()
- {
- lock (WsReceiveLock)
- {
- if (WsFrameReceived)
- return 0;
- // Required WebSocket frame opcode and mask flag
- if (WsReceiveFrameBuffer.Size < 2)
- return 2 - WsReceiveFrameBuffer.Size;
- bool mask = ((WsReceiveFrameBuffer[1] >> 7) & 0x01) != 0;
- long payload = WsReceiveFrameBuffer[1] & (~0x80);
- // Required WebSocket frame size
- if ((payload == 126) && (WsReceiveFrameBuffer.Size < 4))
- return 4 - WsReceiveFrameBuffer.Size;
- if ((payload == 127) && (WsReceiveFrameBuffer.Size < 10))
- return 10 - WsReceiveFrameBuffer.Size;
- // Required WebSocket frame mask
- if ((mask) && (WsReceiveFrameBuffer.Size < WsHeaderSize))
- return WsHeaderSize - WsReceiveFrameBuffer.Size;
- // Required WebSocket frame payload
- return WsHeaderSize + WsPayloadSize - WsReceiveFrameBuffer.Size;
- }
- }
- /// <summary>
- /// Clear WebSocket send/receive buffers
- /// </summary>
- public void ClearWsBuffers()
- {
- // Clear the receive buffer
- bool acquiredReceiveLock = false;
- try
- {
- // Sometimes on disconnect the receive lock could be taken by receive thread.
- // In this case we'll skip the receive buffer clearing. It will happen on
- // re-connect then or in GC.
- Monitor.TryEnter(WsReceiveLock, ref acquiredReceiveLock);
- if (acquiredReceiveLock)
- {
- WsFrameReceived = false;
- WsFinalReceived = false;
- WsHeaderSize = 0;
- WsPayloadSize = 0;
- WsReceiveFrameBuffer.Clear();
- WsReceiveFinalBuffer.Clear();
- Array.Clear(WsReceiveMask, 0, WsReceiveMask.Length);
- }
- }
- finally
- {
- if (acquiredReceiveLock)
- Monitor.Exit(WsReceiveLock);
- }
- // Clear the send buffer
- lock (WsSendLock)
- {
- WsSendBuffer.Clear();
- Array.Clear(WsSendMask, 0, WsSendMask.Length);
- }
- }
- /// <summary>
- /// Initialize WebSocket random nonce
- /// </summary>
- public void InitWsNonce() => WsRandom.NextBytes(WsNonce);
- /// <summary>
- /// Handshaked flag
- /// </summary>
- internal bool WsHandshaked;
- /// <summary>
- /// Received frame flag
- /// </summary>
- internal bool WsFrameReceived;
- /// <summary>
- /// Received final flag
- /// </summary>
- internal bool WsFinalReceived;
- /// <summary>
- /// Received frame opcode
- /// </summary>
- internal byte WsOpcode;
- /// <summary>
- /// Received frame header size
- /// </summary>
- internal long WsHeaderSize;
- /// <summary>
- /// Received frame payload size
- /// </summary>
- internal long WsPayloadSize;
- /// <summary>
- /// Receive buffer lock
- /// </summary>
- internal readonly object WsReceiveLock = new object();
- /// <summary>
- /// Receive frame buffer
- /// </summary>
- internal readonly Buffer WsReceiveFrameBuffer = new Buffer();
- /// <summary>
- /// Receive final buffer
- /// </summary>
- internal readonly Buffer WsReceiveFinalBuffer = new Buffer();
- /// <summary>
- /// Receive mask
- /// </summary>
- internal readonly byte[] WsReceiveMask = new byte[4];
- /// <summary>
- /// Send buffer lock
- /// </summary>
- internal readonly object WsSendLock = new object();
- /// <summary>
- /// Send buffer
- /// </summary>
- internal readonly Buffer WsSendBuffer = new Buffer();
- /// <summary>
- /// Send mask
- /// </summary>
- internal readonly byte[] WsSendMask = new byte[4];
- /// <summary>
- /// WebSocket random generator
- /// </summary>
- internal readonly Random WsRandom = new Random();
- /// <summary>
- /// WebSocket random nonce of 16 bytes
- /// </summary>
- internal readonly byte[] WsNonce = new byte[16];
- }
- }
|