using System;
using System.Text;
using System.Security.Cryptography;
using System.Collections.Generic;
namespace NetCoreServer
{
///
/// WebSocket utility class
///
public class WebSocket : IWebSocket
{
private readonly IWebSocket _wsHandler;
public WebSocket(IWebSocket wsHandler) { _wsHandler = wsHandler; ClearWsBuffers(); InitWsNonce(); }
///
/// Final frame
///
public const byte WS_FIN = 0x80;
///
/// Text frame
///
public const byte WS_TEXT = 0x01;
///
/// Binary frame
///
public const byte WS_BINARY = 0x02;
///
/// Close frame
///
public const byte WS_CLOSE = 0x08;
///
/// Ping frame
///
public const byte WS_PING = 0x09;
///
/// Pong frame
///
public const byte WS_PONG = 0x0A;
///
/// Perform WebSocket client upgrade
///
/// WebSocket upgrade HTTP response
/// WebSocket client Id
/// 'true' if the WebSocket was successfully upgrade, 'false' if the WebSocket was not upgrade
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 (SHA1Managed sha1 = new SHA1Managed())
{
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;
}
///
/// Perform WebSocket server upgrade
///
/// WebSocket upgrade HTTP request
/// WebSocket upgrade HTTP response
/// 'true' if the WebSocket was successfully upgrade, 'false' if the WebSocket was not upgrade
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 (SHA1Managed sha1 = new SHA1Managed())
{
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;
}
///
/// Prepare WebSocket send frame
///
/// WebSocket opcode
/// WebSocket mask
/// Buffer to send
/// Buffer offset
/// Buffer size
/// WebSocket status (default is 0)
public void PrepareSendFrame(byte opcode, bool mask, byte[] buffer, long offset, long size, int status = 0)
{
// Clear the previous WebSocket send buffer
WsSendBuffer.Clear();
// Append WebSocket frame opcode
WsSendBuffer.Add(opcode);
// Append WebSocket frame size
if (size 8) & 0xFF));
WsSendBuffer.Add((byte)(size & 0xFF));
}
else
{
WsSendBuffer.Add((byte)(127 | (mask ? 0x80 : 0)));
for (int i = 7; i >= 0; i--)
WsSendBuffer.Add((byte)((size >> (8 * i)) & 0xFF));
}
if (mask)
{
// Append WebSocket frame mask
WsSendBuffer.Add(WsSendMask[0]);
WsSendBuffer.Add(WsSendMask[1]);
WsSendBuffer.Add(WsSendMask[2]);
WsSendBuffer.Add(WsSendMask[3]);
}
// Resize WebSocket frame buffer
int bufferOffset = WsSendBuffer.Count;
WsSendBuffer.AddRange(new byte[size]);
// Mask WebSocket frame content
for (int i = 0; i < size; i++)
WsSendBuffer[bufferOffset + i] = (byte)(buffer[offset + i] ^ WsSendMask[i % 4]);
}
///
/// Prepare WebSocket send frame
///
/// Buffer to send
/// Buffer offset
/// Buffer size
public void PrepareReceiveFrame(byte[] buffer, long offset, long size)
{
lock (WsReceiveLock)
{
var 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.Count < 2)
{
for (int i = 0; i < 2; i++, index++, size--)
{
if (size == 0)
return;
WsReceiveFrameBuffer.Add(buffer[offset + index]);
}
}
byte opcode = (byte)(WsReceiveFrameBuffer[0] & 0x0F);
bool fin = ((WsReceiveFrameBuffer[0] >> 7) & 0x01) != 0;
bool mask = ((WsReceiveFrameBuffer[1] >> 7) & 0x01) != 0;
int payload = WsReceiveFrameBuffer[1] & (~0x80);
// Prepare WebSocket opcode
WsOpcode = (opcode != 0) ? opcode : WsOpcode;
// Prepare WebSocket frame size
if (payload