using linker.libs; using System; using System.Buffers; using System.Buffers.Binary; using System.Net; using System.Security.Cryptography; using System.Text; namespace linker.libs.websocket { /// /// websocket解析器 /// public static class WebSocketParser { private readonly static SHA1 sha1 = SHA1.Create(); private readonly static Memory magicCode = Encoding.ASCII.GetBytes("258EAFA5-E914-47DA-95CA-C5AB0DC85B11"); /// /// 构建连接数据 /// /// /// public static byte[] BuildConnectData(WebsocketHeaderInfo header) { string path = header.Path.Length == 0 ? "/" : Encoding.UTF8.GetString(header.Path.Span); StringBuilder sb = new StringBuilder(10); sb.Append($"GET {path} HTTP/1.1\r\n"); sb.Append($"Upgrade: websocket\r\n"); sb.Append($"Connection: Upgrade\r\n"); sb.Append($"Sec-WebSocket-Version: 13\r\n"); sb.Append($"Sec-WebSocket-Key: {Encoding.UTF8.GetString(header.SecWebSocketKey.Span)}\r\n"); if (header.SecWebSocketProtocol.Length > 0) { sb.Append($"Sec-WebSocket-Protocol: {Encoding.UTF8.GetString(header.SecWebSocketProtocol.Span)}\r\n"); } if (header.SecWebSocketExtensions.Length > 0) { sb.Append($"Sec-WebSocket-Extensions: {Encoding.UTF8.GetString(header.SecWebSocketExtensions.Span)}\r\n"); } sb.Append("\r\n"); return Encoding.UTF8.GetBytes(sb.ToString()); } /// /// 构建连接回应数据 /// /// /// public static byte[] BuildConnectResponseData(WebsocketHeaderInfo header) { string acceptStr = BuildSecWebSocketAccept(header.SecWebSocketKey); StringBuilder sb = new StringBuilder(10); sb.Append($"HTTP/1.1 {(int)header.StatusCode} {AddSpace(header.StatusCode)}\r\n"); sb.Append($"Sec-WebSocket-Accept: {acceptStr}\r\n"); if (header.Connection.Length > 0) { sb.Append($"Connection: {Encoding.UTF8.GetString(header.Connection.Span)}\r\n"); } if (header.Upgrade.Length > 0) { sb.Append($"Upgrade: {Encoding.UTF8.GetString(header.Upgrade.Span)}\r\n"); } if (header.SecWebSocketVersion.Length > 0) { sb.Append($"Sec-WebSocket-Version: {Encoding.UTF8.GetString(header.SecWebSocketVersion.Span)}\r\n"); } if (header.SecWebSocketProtocol.Length > 0) { sb.Append($"Sec-WebSocket-Protocol: {Encoding.UTF8.GetString(header.SecWebSocketProtocol.Span)}\r\n"); } if (header.SecWebSocketExtensions.Length > 0) { sb.Append($"Sec-WebSocket-Extensions: {Encoding.UTF8.GetString(header.SecWebSocketExtensions.Span)}\r\n"); } sb.Append("\r\n"); return Encoding.UTF8.GetBytes(sb.ToString()); } /// /// 生成随机key /// /// public static byte[] BuildSecWebSocketKey() { byte[] bytes = new byte[16]; Random random = new Random(DateTime.Now.Ticks.GetHashCode()); for (int i = 0; i < bytes.Length; i++) { bytes[i] = (byte)random.Next(0, 255); } byte[] res = Encoding.UTF8.GetBytes(Convert.ToBase64String(bytes)); return res; } /// /// 构建mask数据 /// /// public static byte[] BuildMaskKey() { byte[] bytes = new byte[4]; Random random = new Random(DateTime.Now.Ticks.GetHashCode()); for (int i = 0; i < bytes.Length; i++) { bytes[i] = (byte)random.Next(0, 255); } return bytes; } /// /// 生成accept回应 /// /// /// private static string BuildSecWebSocketAccept(Memory key) { int keyLength = key.Length + magicCode.Length; byte[] acceptBytes = new byte[keyLength]; key.CopyTo(acceptBytes); magicCode.CopyTo(acceptBytes.AsMemory(key.Length)); string acceptStr = Convert.ToBase64String(sha1.ComputeHash(acceptBytes, 0, keyLength)); return acceptStr; } /// /// 验证回应的accept /// /// /// /// public static bool VerifySecWebSocketAccept(Memory key, Memory accept) { string acceptStr = BuildSecWebSocketAccept(key); return acceptStr == Encoding.UTF8.GetString(accept.Span); } /// /// 构建ping帧 /// /// public static byte[] BuildPingData() { return new byte[] { (byte)WebSocketFrameInfo.EnumFin.Fin | (byte)WebSocketFrameInfo.EnumOpcode.Ping, //fin + ping (byte)WebSocketFrameInfo.EnumMask.None | 0, //没有 mask 和 payload length }; } /// /// 构建pong帧 /// /// public static byte[] BuildPongData() { return new byte[] { (byte)WebSocketFrameInfo.EnumFin.Fin | (byte)WebSocketFrameInfo.EnumOpcode.Pong, //fin + pong (byte)WebSocketFrameInfo.EnumMask.None | 0, //没有 mask 和 payload length }; } /// /// 构建数据帧 /// /// /// /// public static byte[] BuildFrameData(WebSocketFrameRemarkInfo remark, out int length) { if (remark.Mask > 0 && remark.MaskData.Length != 4) { throw new Exception("mask data just 4byte"); } length = 1 + 1 + remark.Data.Length; int index = 2; if (remark.Mask == WebSocketFrameInfo.EnumMask.Mask) { length += 4; } byte payloadLength; int dataLength = remark.Data.Length; if (dataLength > ushort.MaxValue) { length += 8; payloadLength = 127; } else if (dataLength > 125) { length += 2; payloadLength = 126; } else { payloadLength = (byte)dataLength; } byte[] bytes = ArrayPool.Shared.Rent(length); var memory = bytes.AsMemory(); bytes[0] = (byte)((byte)remark.Fin | (byte)remark.Rsv1 | (byte)remark.Rsv2 | (byte)remark.Rsv3 | (byte)remark.Opcode); bytes[1] = (byte)((byte)remark.Mask | payloadLength); if (dataLength > ushort.MaxValue) { BinaryPrimitives.WriteUInt64BigEndian(memory.Slice(index).Span, (ulong)dataLength); index += 8; } else if (dataLength > 125) { BinaryPrimitives.WriteUInt16BigEndian(memory.Slice(index).Span, (ushort)dataLength); index += 2; } if (remark.Mask == WebSocketFrameInfo.EnumMask.Mask) { remark.MaskData.CopyTo(bytes.AsMemory(index, remark.MaskData.Length)); index += remark.MaskData.Length; } if (remark.Data.Length > 0) { remark.Data.CopyTo(bytes.AsMemory(index)); } return bytes; } public static void Return(byte[] bytes) { ArrayPool.Shared.Return(bytes); } /// /// 给每个大写字母前加一个空格,例如ProxyAuthenticationRequired 变成Proxy Authentication Required /// /// /// private static string AddSpace(HttpStatusCode statusCode) { ReadOnlySpan span = statusCode.ToString().AsSpan(); int totalLength = span.Length * 2; char[] result = new char[totalLength]; Span resultSpan = result.AsSpan(0, totalLength); span.CopyTo(resultSpan); int length = 0; for (int i = 0; i < span.Length; i++) { if (i > 0 && span[i] >= 65 && span[i] <= 90) { resultSpan.Slice(i + length, totalLength - (length + i) - 1).CopyTo(resultSpan.Slice(i + length + 1)); resultSpan[i + length] = (char)32; length++; } } string resultStr = resultSpan.Slice(0, span.Length + length).ToString(); return resultStr; } } public sealed class WebSocketFrameRemarkInfo { /// /// 是否是结束帧,如果只有一帧,那必定是结束帧 /// public WebSocketFrameInfo.EnumFin Fin { get; set; } = WebSocketFrameInfo.EnumFin.Fin; /// /// 保留位1 /// public WebSocketFrameInfo.EnumRsv1 Rsv1 { get; set; } = WebSocketFrameInfo.EnumRsv1.None; /// /// 保留位2 /// public WebSocketFrameInfo.EnumRsv2 Rsv2 { get; set; } = WebSocketFrameInfo.EnumRsv2.None; /// /// 保留位3 /// public WebSocketFrameInfo.EnumRsv3 Rsv3 { get; set; } = WebSocketFrameInfo.EnumRsv3.None; /// /// 数据描述,默认TEXT数据 /// public WebSocketFrameInfo.EnumOpcode Opcode { get; set; } = WebSocketFrameInfo.EnumOpcode.Text; /// /// 是否有掩码 /// public WebSocketFrameInfo.EnumMask Mask { get; set; } = WebSocketFrameInfo.EnumMask.None; /// /// 掩码key 4字节 /// public Memory MaskData { get; set; } /// /// payload data /// public Memory Data { get; set; } } /// /// 数据帧解析 /// public sealed class WebSocketFrameInfo { /// /// 是否是结束帧 /// public EnumFin Fin { get; set; } /// /// 保留位 /// public EnumRsv1 Rsv1 { get; set; } /// /// /// public EnumRsv2 Rsv2 { get; set; } /// /// /// public EnumRsv3 Rsv3 { get; set; } /// /// 操作码 0附加数据,1文本,2二进制,3-7为非控制保留,8关闭,9ping,a pong,b-f 为控制保留 /// public EnumOpcode Opcode { get; set; } /// /// 掩码 /// public EnumMask Mask { get; set; } /// /// 总长度 /// public int TotalLength { get; set; } /// /// 数据 如果OPCODE是 EnumOpcode.Close 则数据的前2字节为关闭状态码,余下的为其它描述数据 /// public Memory PayloadData { get; set; } /// /// 解析帧,如果false解析失败,则应该把data缓存起来,等待下次来数据后,拼接起来再次解析 /// /// /// /// public static bool TryParse(Memory data, out WebSocketFrameInfo frameInfo) { frameInfo = null; //小于2字节不可解析 if (data.Length < 2) { return false; } Span span = data.Span; int index = 2; //第2字节 //1位 是否mask EnumMask mask = (EnumMask)(span[1] & (byte)EnumMask.Mask); int payloadLength = span[1] & 0b01111111; if (payloadLength == 126) { payloadLength = BinaryPrimitives.ReadUInt16BigEndian(span.Slice(2, 2)); index += 2; } else if (payloadLength == 127) { payloadLength = (int)BinaryPrimitives.ReadUInt64BigEndian(span.Slice(2, 8)); index += 8; } //数据长+头长 大于 整个数据长,则不是一个完整的包 if (data.Length < payloadLength + index + (mask == EnumMask.Mask ? 4 : 0)) { return false; } //第1字节 //1位 是否是结束帧 EnumFin fin = (EnumFin)(byte)(span[0] & (byte)EnumFin.Fin); //2 3 4 保留 EnumRsv1 rsv1 = (EnumRsv1)(byte)(span[0] & (byte)EnumRsv1.Rsv); EnumRsv2 rsv2 = (EnumRsv2)(byte)(span[0] & (byte)EnumRsv2.Rsv); EnumRsv3 rsv3 = (EnumRsv3)(byte)(span[0] & (byte)EnumRsv3.Rsv); //5 6 7 8 操作码 EnumOpcode opcode = (EnumOpcode)(byte)(span[0] & (byte)EnumOpcode.Last); Span maskKey = Helper.EmptyArray; if (mask == EnumMask.Mask) { //mask掩码key 用来解码数据 maskKey = span.Slice(index, 4); index += 4; } //数据 Memory payloadData = data.Slice(index, payloadLength); if (mask == EnumMask.Mask) { //解码 Span payloadDataSpan = payloadData.Span; for (int i = 0; i < payloadDataSpan.Length; i++) { payloadDataSpan[i] = (byte)(payloadDataSpan[i] ^ maskKey[3 & i]); } } frameInfo = new WebSocketFrameInfo { Fin = fin, Rsv1 = rsv1, Rsv2 = rsv2, Rsv3 = rsv3, Opcode = opcode, Mask = mask, PayloadData = payloadData, TotalLength = index + payloadLength }; return true; } public enum EnumFin : byte { None = 0x0, Fin = 0b10000000, } public enum EnumMask : byte { None = 0x0, Mask = 0b10000000, } public enum EnumRsv1 : byte { None = 0x0, Rsv = 0b01000000, } public enum EnumRsv2 : byte { None = 0x0, Rsv = 0b00100000, } public enum EnumRsv3 : byte { None = 0x0, Rsv = 0b00010000, } public enum EnumOpcode : byte { Data = 0x0, Text = 0x1, Binary = 0x2, UnControll3 = 0x3, UnControll4 = 0x4, UnControll5 = 0x5, UnControll6 = 0x6, UnControll7 = 0x7, Close = 0x8, Ping = 0x9, Pong = 0xa, Controll11 = 0xb, Controll12 = 0xc, Controll13 = 0xd, Controll14 = 0xe, Controll15 = 0xf, Last = 0xf, } /// /// 关闭的状态码 /// public enum EnumCloseStatus : ushort { /// /// 正常关闭 /// Normal = 1000, /// /// 正在离开 /// Leaving = 1001, /// /// 协议错误 /// ProtocolError = 1002, /// /// 只能接收TEXT数据 /// TextOnly = 1003, /// /// 保留 /// None1004 = 1004, /// /// 保留 /// None1005 = 1005, /// /// 保留 /// None1006 = 1006, /// /// 消息类型不一致 /// DataTypeError = 1007, /// /// 通用状态码,没有别的合适的状态码时,用这个 /// PublicError = 1008, /// /// 数据太大,无法处理 /// DataTooBig = 1009, /// /// 扩展错误 /// ExtendsError = 1010,//正常关闭 /// /// 意外情况 /// Unexpected = 1011, /// /// TLS握手失败 /// TLSError = 1015 } } /// /// 请求头解析 /// public sealed class WebsocketHeaderInfo { static byte[][] bytes = new byte[][] { Encoding.ASCII.GetBytes("Connection: "), Encoding.ASCII.GetBytes("Upgrade: "), Encoding.ASCII.GetBytes("Origin: "), Encoding.ASCII.GetBytes("Sec-WebSocket-Version: "), Encoding.ASCII.GetBytes("Sec-WebSocket-Key: "), Encoding.ASCII.GetBytes("Sec-WebSocket-Extensions: "), Encoding.ASCII.GetBytes("Sec-WebSocket-Protocol: "), Encoding.ASCII.GetBytes("Sec-WebSocket-Accept: ") }; static byte[] httpBytes = Encoding.UTF8.GetBytes("HTTP/"); public HttpStatusCode StatusCode { get; set; } = HttpStatusCode.SwitchingProtocols; public Memory Method { get; private set; } private string _pathSet { get; set; } /// /// 用这个设置path值 /// public string PathSet { get { return _pathSet; } set { _pathSet = value; Path = Encoding.UTF8.GetBytes(_pathSet); } } /// /// 如果 仅1个字符,那就是 / /// public Memory Path { get; private set; } public Memory Connection { get; private set; } public Memory Upgrade { get; private set; } public Memory Origin { get; private set; } public Memory SecWebSocketVersion { get; private set; } public Memory SecWebSocketKey { get; set; } public Memory SecWebSocketExtensions { get; set; } public Memory SecWebSocketProtocol { get; set; } public Memory SecWebSocketAccept { get; set; } public static WebsocketHeaderInfo Parse(Memory header) { Span span = header.Span; int flag = 0xff; int bit = 0x01; ulong[] res = new ulong[bytes.Length]; for (int i = 0, len = span.Length; i < len; i++) { if (span[i] == 13 && span[i + 1] == 10 && span[i + 2] == 13 && span[i + 3] == 10) { break; } if (span[i] == 13 && span[i + 1] == 10) { int startIndex = i + 2; for (int k = 0; k < bytes.Length; k++) { if ((flag >> k & 1) == 1 && span[startIndex] == bytes[k][0]) { if (span.Slice(startIndex, bytes[k].Length).SequenceEqual(bytes[k])) { int index = span.Slice(startIndex).IndexOf((byte)13); flag &= ~(bit << k); #pragma warning disable CS0675 // 对进行了带符号扩展的操作数使用了按位或运算符 res[k] = (ulong)(startIndex + bytes[k].Length) << 32 | (ulong)(index - bytes[k].Length); #pragma warning restore CS0675 // 对进行了带符号扩展的操作数使用了按位或运算符 i += index + 1; break; } } } } } WebsocketHeaderInfo headerInfo = new WebsocketHeaderInfo { Connection = header.Slice((int)(res[0] >> 32), (int)(res[0] & 0xffffffff)), Upgrade = header.Slice((int)(res[1] >> 32), (int)(res[1] & 0xffffffff)), Origin = header.Slice((int)(res[2] >> 32), (int)(res[2] & 0xffffffff)), SecWebSocketVersion = header.Slice((int)(res[3] >> 32), (int)(res[3] & 0xffffffff)), SecWebSocketKey = header.Slice((int)(res[4] >> 32), (int)(res[4] & 0xffffffff)), SecWebSocketExtensions = header.Slice((int)(res[5] >> 32), (int)(res[5] & 0xffffffff)), SecWebSocketProtocol = header.Slice((int)(res[6] >> 32), (int)(res[6] & 0xffffffff)), SecWebSocketAccept = header.Slice((int)(res[7] >> 32), (int)(res[7] & 0xffffffff)), }; int pathIndex = span.IndexOf((byte)32); int pathIndex1 = span.Slice(pathIndex + 1).IndexOf((byte)32); if (header.Slice(0, httpBytes.Length).Span.SequenceEqual(httpBytes)) { int code = int.Parse(Encoding.UTF8.GetString(header.Slice(pathIndex + 1, pathIndex1).Span)); headerInfo.StatusCode = (HttpStatusCode)code; } else { headerInfo.Path = header.Slice(pathIndex + 1, pathIndex1); headerInfo.Method = header.Slice(0, pathIndex); } return headerInfo; } } }