using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Kneesocks { public class Frame { public enum kOpcode { Continuation = 0x0, TextFrame = 0x1, BinaryFrame = 0x2, Close = 0x8, Ping = 0x9, Pong = 0xA }; public kOpcode Opcode { get; set; } public bool IsFinal { get; set; } public bool IsMasked { get; set; } public byte[] Mask { get; set; } = new byte[] { 0, 0, 0, 0 }; public byte Reserved { get; set; } public byte[] Content { get; set; } public byte[] MaskedContent { get { byte[] returnValue = new byte[Content.Length]; for(long i = 0; i < Content.LongLength; ++i) returnValue[i] = (byte)(Content[i] ^ Mask[i % 4]); return returnValue; } } public byte[] GetRaw() { var headerSize = 2L; var bodySize = Content.LongLength; if(bodySize >= 0x7E && bodySize <= 0xFFFF) headerSize += 2; else if(bodySize > 0xFFFF) headerSize += 8; if(IsMasked) headerSize += 4; var returnValue = new byte[headerSize + bodySize]; return returnValue; } public static Frame FromRaw(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 FormatException("Opcode '"+ rawOpcode.ToString("0x{0: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 lengthOffset = bodyLength < 126 ? 1 : (bodyLength == 126 ? 3 : 9); for(var i = lengthOffset - 1; i > 0; --i) { var bytePos = lengthOffset - 1 - i; bodyLength &= 0xFFul << bytePos; bodyLength |= (ulong)raw[2 + i] << bytePos; } if(returnFrame.IsMasked) { for(var i = 0; i < 4; ++i) returnFrame.Mask[i] = raw[i + 1 + lengthOffset]; lengthOffset += 4; } ulong expectedFrameLength = bodyLength + (uint)lengthOffset + 1; if(expectedFrameLength < (ulong)raw.LongLength) throw new FormatException("Raw frame length ("+ expectedFrameLength +") is less than described size ("+ (ulong)raw.LongLength +")"); ulong counter = 0; returnFrame.Content = raw.Skip(lengthOffset + 1) .TakeWhile(x => counter++ < bodyLength) .ToArray(); if(returnFrame.IsMasked) { counter = 0; returnFrame.Content = returnFrame.Content.Select( x => (byte)(x ^ returnFrame.Mask[counter++ % 4]) ).ToArray(); } return returnFrame; } } }