You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

210 lines
8.0 KiB

#if !NET20
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
namespace Apewer.WebSocket
{
internal static class Hybi13Handler
{
public static ComposableHandler Create(HttpRequest request, Action<string> onMessage, Action onClose, Action<byte[]> onBytes, Action<byte[]> onPing, Action<byte[]> onPong)
{
var readState = new ReadState();
return new ComposableHandler
{
Handshake = sub => Hybi13Handler.BuildHandshake(request, sub),
TextFrame = s => Hybi13Handler.FrameData(Encoding.UTF8.GetBytes(s), FrameType.Text),
BytesFrame = s => Hybi13Handler.FrameData(s, FrameType.Bytes),
PingFrame = s => Hybi13Handler.FrameData(s, FrameType.Ping),
PongFrame = s => Hybi13Handler.FrameData(s, FrameType.Pong),
CloseFrame = i => Hybi13Handler.FrameData(i.ToBigEndianBytes<ushort>(), FrameType.Close),
ReceiveData = d => Hybi13Handler.ReceiveData(d, readState, (op, data) => Hybi13Handler.ProcessFrame(op, data, onMessage, onClose, onBytes, onPing, onPong))
};
}
public static byte[] FrameData(byte[] payload, FrameType frameType)
{
var memoryStream = new MemoryStream();
byte op = (byte)((byte)frameType + 128);
memoryStream.WriteByte(op);
if (payload.Length > UInt16.MaxValue) {
memoryStream.WriteByte(127);
var lengthBytes = payload.Length.ToBigEndianBytes<ulong>();
memoryStream.Write(lengthBytes, 0, lengthBytes.Length);
} else if (payload.Length > 125) {
memoryStream.WriteByte(126);
var lengthBytes = payload.Length.ToBigEndianBytes<ushort>();
memoryStream.Write(lengthBytes, 0, lengthBytes.Length);
} else {
memoryStream.WriteByte((byte)payload.Length);
}
memoryStream.Write(payload, 0, payload.Length);
return memoryStream.ToArray();
}
public static void ReceiveData(List<byte> data, ReadState readState, Action<FrameType, byte[]> processFrame)
{
while (data.Count >= 2)
{
var isFinal = (data[0] & 128) != 0;
var reservedBits = (data[0] & 112);
var frameType = (FrameType)(data[0] & 15);
var isMasked = (data[1] & 128) != 0;
var length = (data[1] & 127);
if (!isMasked
|| !Enum.IsDefined(typeof(FrameType), frameType)
|| reservedBits != 0 //Must be zero per spec 5.2
|| (frameType == FrameType.Continuation && !readState.FrameType.HasValue))
throw new WebSocketException(StatusCodes.ProtocolError);
var index = 2;
int payloadLength;
if (length == 127)
{
if (data.Count < index + 8)
return; //Not complete
payloadLength = data.Skip(index).Take(8).ToArray().ToLittleEndianInt();
index += 8;
}
else if (length == 126)
{
if (data.Count < index + 2)
return; //Not complete
payloadLength = data.Skip(index).Take(2).ToArray().ToLittleEndianInt();
index += 2;
}
else
{
payloadLength = length;
}
if (data.Count < index + 4)
return; //Not complete
var maskBytes = data.Skip(index).Take(4).ToArray();
index += 4;
if (data.Count < index + payloadLength)
return; //Not complete
byte[] payloadData = new byte[payloadLength];
for (int i = 0; i < payloadLength; i++)
payloadData[i] = (byte)(data[index+i] ^ maskBytes[i % 4]);
readState.Data.AddRange(payloadData);
data.RemoveRange(0, index + payloadLength);
if (frameType != FrameType.Continuation)
readState.FrameType = frameType;
if (isFinal && readState.FrameType.HasValue)
{
var stateData = readState.Data.ToArray();
var stateFrameType = readState.FrameType;
readState.Clear();
processFrame(stateFrameType.Value, stateData);
}
}
}
public static void ProcessFrame(FrameType frameType, byte[] data, Action<string> onMessage, Action onClose, Action<byte[]> onBytes, Action<byte[]> onPing, Action<byte[]> onPong)
{
switch (frameType)
{
case FrameType.Close:
if (data.Length == 1 || data.Length>125)
throw new WebSocketException(StatusCodes.ProtocolError);
if (data.Length >= 2)
{
var closeCode = (ushort)data.Take(2).ToArray().ToLittleEndianInt();
if (!StatusCodes.ValidCloseCodes.Contains(closeCode) && (closeCode < 3000 || closeCode > 4999))
throw new WebSocketException(StatusCodes.ProtocolError);
}
if (data.Length > 2)
ReadUTF8PayloadData(data.Skip(2).ToArray());
onClose();
break;
case FrameType.Bytes:
onBytes(data);
break;
case FrameType.Ping:
onPing(data);
break;
case FrameType.Pong:
onPong(data);
break;
case FrameType.Text:
onMessage(ReadUTF8PayloadData(data));
break;
default:
WebSocketLog.Debug("Received unhandled " + frameType);
break;
}
}
public static byte[] BuildHandshake(HttpRequest request, string subProtocol)
{
WebSocketLog.Debug("Building Hybi-14 Response");
var builder = new StringBuilder();
builder.Append("HTTP/1.1 101 Switching Protocols\r\n");
builder.Append("Upgrade: websocket\r\n");
builder.Append("Connection: Upgrade\r\n");
if (subProtocol != null)
builder.AppendFormat("Sec-WebSocket-Protocol: {0}\r\n", subProtocol);
var responseKey = CreateResponseKey(request["Sec-WebSocket-Key"]);
builder.AppendFormat("Sec-WebSocket-Accept: {0}\r\n", responseKey);
builder.Append("\r\n");
return Encoding.ASCII.GetBytes(builder.ToString());
}
private const string WebSocketResponseGuid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
public static string CreateResponseKey(string requestKey)
{
var combined = requestKey + WebSocketResponseGuid;
var bytes = SHA1.Create().ComputeHash(Encoding.ASCII.GetBytes(combined));
return Convert.ToBase64String(bytes);
}
private static string ReadUTF8PayloadData(byte[] bytes)
{
var encoding = new UTF8Encoding(false, true);
try
{
return encoding.GetString(bytes);
}
catch(ArgumentException)
{
throw new WebSocketException(StatusCodes.InvalidFramePayloadData);
}
}
}
}
#endif