diff --git a/client/src/FileCache.ts b/client/src/FileCache.ts index 3e62e3d..f48d697 100644 --- a/client/src/FileCache.ts +++ b/client/src/FileCache.ts @@ -14,5 +14,4 @@ class FileCache { } } - static } \ No newline at end of file diff --git a/server/Kneesocks/Connection.cs b/server/Kneesocks/Connection.cs index 62b5024..d2ba9e1 100644 --- a/server/Kneesocks/Connection.cs +++ b/server/Kneesocks/Connection.cs @@ -37,18 +37,19 @@ namespace Kneesocks { } public void Disconnect(string reason = null) { + Disconnect(Frame.kClosingReason.Normal, reason); + } + + public void Disconnect(Frame.kClosingReason status, string reason = null) { Disconnected = true; DisconnectReason = reason; if(Socket.Connected) { Socket.SendTimeout = 1000; - if(!Handshaked) { - var raw = Encoding.ASCII.GetBytes(Handshake.DenyRequest().GetRaw()); - Stream.Write(raw, 0, raw.Length); - Socket.Close(); - } else { - - } + var raw = Handshaked ? Frame.Closing(status, reason).GetBytes() + : Handshake.DenyRequest().ToString().GetBytes(); + Stream.Write(raw, 0, raw.Length); + Socket.Close(); } OnClose(); diff --git a/server/Kneesocks/Frame.cs b/server/Kneesocks/Frame.cs index d60d1cc..ccc54ea 100644 --- a/server/Kneesocks/Frame.cs +++ b/server/Kneesocks/Frame.cs @@ -6,6 +6,17 @@ using System.Threading.Tasks; 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, @@ -15,44 +26,63 @@ namespace Kneesocks { Pong = 0xA }; - public kOpcode Opcode { get; set; } - public bool IsFinal { get; set; } - public bool IsMasked { get; set; } + 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; } - + public byte Reserved { get; set; } = 0; + 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; + long counter = 0; + return Content.Select(x => (byte)(x ^ Mask[counter++ % 4])).ToArray(); } } - public byte[] GetRaw() { + public byte[] GetBytes() { var headerSize = 2L; var bodySize = Content.LongLength; - if(bodySize >= 0x7E && bodySize <= 0xFFFF) + var headerLengthFirstByte = (byte)Content.Length; + if(bodySize >= 0x7E && bodySize <= 0xFFFF) { headerSize += 2; - else if(bodySize > 0xFFFF) + headerLengthFirstByte = 126; + } else if(bodySize > 0xFFFF) { headerSize += 8; + headerLengthFirstByte = 127; + } + var headerLengthSize = (int)headerSize - 1; if(IsMasked) headerSize += 4; var returnValue = new byte[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 upperBound = headerLengthFirstByte == 0x7E ? 2 : 8; + for(var i = 0; i < upperBound; ++i) + returnValue[2 + i] |= (byte)((bodySize >> (8*(upperBound - i))) & 0xFF); + } + + if(IsMasked) + Array.Copy(Mask, 0, returnValue, headerSize - 4, 4); + + Array.Copy(IsMasked ? MaskedContent : Content, 0L, returnValue, headerSize, Content.LongLength); return returnValue; } - public static Frame FromRaw(byte[] raw) { + public static Frame FromBytes(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"); + throw new FormatException("Opcode '0x"+ rawOpcode.ToString("X") +"' not understood"); var returnFrame = new Frame { IsFinal = (raw[0] & 0x80) != 0, @@ -62,41 +92,63 @@ namespace Kneesocks { }; ulong bodyLength = raw[1] & 0x7Ful; - int lengthOffset = - bodyLength < 126 + int headerOffset = + bodyLength < 0x7E ? 1 - : (bodyLength == 126 ? 3 : 9); + : (bodyLength == 0x7E ? 3 : 9); + + if(raw.Length < headerOffset + 1) + throw new FormatException("Websocket frame is smaller than expected header size"); - for(var i = lengthOffset - 1; i > 0; --i) { - var bytePos = lengthOffset - 1 - i; - bodyLength &= 0xFFul << bytePos; - bodyLength |= (ulong)raw[2 + i] << bytePos; - } + bodyLength = bodyLength < 0x7E ? 0 : bodyLength; + for(var i = headerOffset - 1; i > 0; --i) + bodyLength |= (ulong)raw[2 + i] << (8*(headerOffset - 1 - i)); if(returnFrame.IsMasked) { - for(var i = 0; i < 4; ++i) - returnFrame.Mask[i] = raw[i + 1 + lengthOffset]; - lengthOffset += 4; + Array.Copy(raw, headerOffset + 1, returnFrame.Mask, 0, 4); + headerOffset += 4; } - ulong expectedFrameLength = bodyLength + (uint)lengthOffset + 1; + ulong expectedFrameLength = bodyLength + (uint)headerOffset + 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(); - } + throw new FormatException("Raw frame length ("+ (ulong)raw.LongLength + ") is less than described size ("+ expectedFrameLength + ")"); + + Array.Copy(raw, headerOffset + 1, returnFrame.Content, 0L, (long)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; + } } } diff --git a/server/Kneesocks/Handshake.cs b/server/Kneesocks/Handshake.cs index 1381067..e057427 100644 --- a/server/Kneesocks/Handshake.cs +++ b/server/Kneesocks/Handshake.cs @@ -99,9 +99,9 @@ namespace Kneesocks { return new Handshake(statusCode, message); } - public string GetRaw() { + public override string ToString() { if(Content != null) { - SetHeader("Content-Length", Encoding.ASCII.GetByteCount(Content).ToString()); + SetHeader("Content-Length", Content.ByteLength().ToString()); if(GetHeader("Content-Type") == null) SetHeader("Content-Type", "text/plain"); } diff --git a/server/Kneesocks/Utilities.cs b/server/Kneesocks/Utilities.cs index 87d7204..5902690 100644 --- a/server/Kneesocks/Utilities.cs +++ b/server/Kneesocks/Utilities.cs @@ -10,23 +10,33 @@ namespace Kneesocks { RAW, HEX, BASE64 } + public static byte[] GetBytes(this string str, bool isUtf8 = true) { + return isUtf8 ? Encoding.UTF8.GetBytes(str) + : Encoding.ASCII.GetBytes(str); + } + + public static int ByteLength(this string str, bool isUtf8 = true) { + return isUtf8 ? Encoding.UTF8.GetByteCount(str) + : Encoding.ASCII.GetByteCount(str); + } + public static string Base64Encode(this string str, bool isUtf8 = true) { var raw = - isUtf8 ? Encoding.UTF8.GetBytes(str) - : Encoding.ASCII.GetBytes(str); + isUtf8 ? Encoding.UTF8.GetBytes(str) + : Encoding.ASCII.GetBytes(str); return Convert.ToBase64String(raw); } public static string Base64Decode(this string str, bool isUtf8 = true) { var raw = Convert.FromBase64String(str); return isUtf8 ? Encoding.UTF8.GetString(raw) - : Encoding.ASCII.GetString(raw); + : Encoding.ASCII.GetString(raw); } public static string SHA1(this string str, kHashReturnType type = kHashReturnType.RAW) { using(var hasher = System.Security.Cryptography.SHA1.Create()) { return ParseRawHash( - hasher.ComputeHash(Encoding.ASCII.GetBytes(str)), + hasher.ComputeHash(str.GetBytes(false)), type ); } @@ -35,7 +45,7 @@ namespace Kneesocks { public static string MD5(this string str, kHashReturnType type = kHashReturnType.RAW) { using(var hasher = System.Security.Cryptography.MD5.Create()) { return ParseRawHash( - hasher.ComputeHash(Encoding.ASCII.GetBytes(str)), + hasher.ComputeHash(str.GetBytes(false)), type ); }