sockscape/server/Libraries/Kneesocks/Frame.cs

200 lines
7 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Glove;
namespace Kneesocks {
public class Frame {
public enum kClosingReason {
Normal = 1000,
GoingAway = 1001,
ProtocolError = 1002,
FrameTypeError = 1003,
DataError = 1007,
PolicyError = 1008,
FrameTooBig = 1009,
RequestError = 1011
}
public enum kOpcode {
Continuation = 0x0,
TextFrame = 0x1,
BinaryFrame = 0x2,
Close = 0x8,
Ping = 0x9,
Pong = 0xA
};
public kOpcode Opcode { get; set; } = kOpcode.BinaryFrame;
public bool IsFinal { get; set; } = true;
public bool IsMasked { get; set; } = false;
public byte[] Mask { get; set; } = new byte[] { 0, 0, 0, 0 };
public byte Reserved { get; set; } = 0;
private int _HeaderLength = 0;
public int HeaderLength {
get {
if(_HeaderLength != 0)
return _HeaderLength;
int length = 2
+ (BodyLength >= 0x7E && BodyLength <= 0xFFFF ? 2 : 0)
+ (BodyLength > 0xFFFF ? 8 : 0)
+ (IsMasked ? 4 : 0);
return (_HeaderLength = length);
}
}
private int _BodyLength = 0;
public int BodyLength {
get => Content?.Length ?? _BodyLength;
}
public byte[] Content { get; set; } = null;
public byte[] MaskedContent {
get {
long counter = 0;
return Content.Select(x => (byte)(x ^ Mask[counter++ % 4])).ToArray();
}
}
public byte[] GetBytes() {
var headerSize = 2;
var bodySize = (UInt64)Content.LongLength;
var headerLengthFirstByte = (byte)Content.Length;
if(bodySize >= 0x7E && bodySize <= 0xFFFF) {
headerSize += 2;
headerLengthFirstByte = 0x7e;
} else if(bodySize > 0xFFFF) {
headerSize += 8;
headerLengthFirstByte = 0x7f;
}
var headerLengthSize = headerSize - 1;
if(IsMasked)
headerSize += 4;
var returnValue = new byte[(UInt64)headerSize + bodySize];
returnValue[0] = (byte)(((byte)Opcode % 0x10)
| ((Reserved % 8) << 4)
| (IsFinal ? 0x80 : 0x0));
returnValue[1] = (byte)(headerLengthFirstByte
| (IsMasked ? 0x80 : 0x0));
if(headerLengthFirstByte >= 0x7E) {
var lengthBytes =
headerLengthFirstByte == 0x7E
? ((UInt16)bodySize).Pack()
: bodySize.Pack();
for(var i = 0; i < lengthBytes.Length; ++i)
returnValue[2 + i] = lengthBytes[i];
}
if(IsMasked)
Array.Copy(Mask, 0, returnValue, headerSize - 4, 4);
Array.Copy(IsMasked ? MaskedContent : Content, 0L, returnValue, headerSize, Content.LongLength);
return returnValue;
}
public static int HeaderLengthFromBytes(byte[] raw) {
if(raw.Length < 2)
throw new FormatException("Need first two bytes to analyze");
var lengthByte = raw[1] & 0x7F;
return 2
+ ((raw[1] & 0x80) != 0 ? 4 : 0)
+ (lengthByte == 0x7E ? 2 : 0)
+ (lengthByte == 0x7F ? 8 : 0);
}
public static Frame HeaderFromBytes(byte[] raw) {
if(raw.Length < 2)
throw new FormatException("Websocket frame cannot be less than two bytes long");
var rawOpcode = raw[0] & 0x0F;
if(!Enum.IsDefined(typeof(kOpcode), rawOpcode))
throw new ArgumentException("Opcode '0x" + rawOpcode.ToString("X") + "' not understood");
var returnFrame = new Frame {
IsFinal = (raw[0] & 0x80) != 0,
Opcode = (kOpcode)rawOpcode,
IsMasked = (raw[1] & 0x80) != 0,
Reserved = (byte)((raw[0] & 0x70) >> 4)
};
ulong bodyLength = raw[1] & 0x7Ful;
int headerOffset =
bodyLength < 0x7E
? 1
: (bodyLength == 0x7E ? 3 : 9);
if(raw.Length < headerOffset + 1 + (returnFrame.IsMasked ? 4 : 0))
throw new FormatException("Websocket frame is smaller than expected header size");
if(bodyLength >= 0x7E)
bodyLength = bodyLength == 0x7E ? raw.Subset(2, 2).UnpackUInt16()
: raw.Subset(2, 8).UnpackUInt64();
if(bodyLength > Int32.MaxValue)
throw new FormatException("Frame is too large to interpret");
returnFrame._BodyLength = (int)bodyLength;
if(returnFrame.IsMasked)
Array.Copy(raw, headerOffset + 1, returnFrame.Mask, 0, 4);
return returnFrame;
}
public static Frame FromBytes(byte[] raw) {
var returnFrame = HeaderFromBytes(raw);
uint expectedFrameLength = (uint)returnFrame.BodyLength + (uint)returnFrame.HeaderLength;
if((uint)raw.Length < expectedFrameLength)
throw new FormatException("Raw frame length ("+ (uint)raw.Length + ") is less than described size ("+ expectedFrameLength + ")");
returnFrame.Content = new byte[returnFrame.BodyLength];
Array.Copy(raw, returnFrame.HeaderLength, returnFrame.Content, 0L, returnFrame.BodyLength);
if(returnFrame.IsMasked)
returnFrame.Content = returnFrame.MaskedContent;
return returnFrame;
}
public static Frame Closing(kClosingReason status = kClosingReason.Normal, string reason = "") {
var data = new byte[2 + reason.ByteLength()];
data[0] = (byte)(((short)status >> 8) & 0xFF);
data[1] = (byte)((short)status & 0xFF);
Array.Copy(reason.GetBytes(), 0, data, 2, reason.ByteLength());
return new Frame {
Opcode = kOpcode.Close,
Content = data
};
}
public static Frame Ping(string data = "") {
return new Frame {
Opcode = kOpcode.Ping,
Content = data.GetBytes()
};
}
public static Frame Pong(string data = "") {
return new Frame {
Opcode = kOpcode.Pong,
Content = data.GetBytes()
};
}
public static Frame Pong(Frame pingFrame) {
pingFrame.Opcode = kOpcode.Pong;
return pingFrame;
}
}
}