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

4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
  1. #if !NET20
  2. using System;
  3. using System.Collections.Generic;
  4. using System.IO;
  5. using System.Linq;
  6. using System.Security.Cryptography;
  7. using System.Text;
  8. namespace Apewer.WebSocket
  9. {
  10. internal static class Hybi13Handler
  11. {
  12. public static ComposableHandler Create(HttpRequest request, Action<string> onMessage, Action onClose, Action<byte[]> onBytes, Action<byte[]> onPing, Action<byte[]> onPong)
  13. {
  14. var readState = new ReadState();
  15. return new ComposableHandler
  16. {
  17. Handshake = sub => Hybi13Handler.BuildHandshake(request, sub),
  18. TextFrame = s => Hybi13Handler.FrameData(Encoding.UTF8.GetBytes(s), FrameType.Text),
  19. BytesFrame = s => Hybi13Handler.FrameData(s, FrameType.Bytes),
  20. PingFrame = s => Hybi13Handler.FrameData(s, FrameType.Ping),
  21. PongFrame = s => Hybi13Handler.FrameData(s, FrameType.Pong),
  22. CloseFrame = i => Hybi13Handler.FrameData(i.ToBigEndianBytes<ushort>(), FrameType.Close),
  23. ReceiveData = d => Hybi13Handler.ReceiveData(d, readState, (op, data) => Hybi13Handler.ProcessFrame(op, data, onMessage, onClose, onBytes, onPing, onPong))
  24. };
  25. }
  26. public static byte[] FrameData(byte[] payload, FrameType frameType)
  27. {
  28. var memoryStream = new MemoryStream();
  29. byte op = (byte)((byte)frameType + 128);
  30. memoryStream.WriteByte(op);
  31. if (payload.Length > UInt16.MaxValue) {
  32. memoryStream.WriteByte(127);
  33. var lengthBytes = payload.Length.ToBigEndianBytes<ulong>();
  34. memoryStream.Write(lengthBytes, 0, lengthBytes.Length);
  35. } else if (payload.Length > 125) {
  36. memoryStream.WriteByte(126);
  37. var lengthBytes = payload.Length.ToBigEndianBytes<ushort>();
  38. memoryStream.Write(lengthBytes, 0, lengthBytes.Length);
  39. } else {
  40. memoryStream.WriteByte((byte)payload.Length);
  41. }
  42. memoryStream.Write(payload, 0, payload.Length);
  43. return memoryStream.ToArray();
  44. }
  45. public static void ReceiveData(List<byte> data, ReadState readState, Action<FrameType, byte[]> processFrame)
  46. {
  47. while (data.Count >= 2)
  48. {
  49. var isFinal = (data[0] & 128) != 0;
  50. var reservedBits = (data[0] & 112);
  51. var frameType = (FrameType)(data[0] & 15);
  52. var isMasked = (data[1] & 128) != 0;
  53. var length = (data[1] & 127);
  54. if (!isMasked
  55. || !Enum.IsDefined(typeof(FrameType), frameType)
  56. || reservedBits != 0 //Must be zero per spec 5.2
  57. || (frameType == FrameType.Continuation && !readState.FrameType.HasValue))
  58. throw new WebSocketException(StatusCodes.ProtocolError);
  59. var index = 2;
  60. int payloadLength;
  61. if (length == 127)
  62. {
  63. if (data.Count < index + 8)
  64. return; //Not complete
  65. payloadLength = data.Skip(index).Take(8).ToArray().ToLittleEndianInt();
  66. index += 8;
  67. }
  68. else if (length == 126)
  69. {
  70. if (data.Count < index + 2)
  71. return; //Not complete
  72. payloadLength = data.Skip(index).Take(2).ToArray().ToLittleEndianInt();
  73. index += 2;
  74. }
  75. else
  76. {
  77. payloadLength = length;
  78. }
  79. if (data.Count < index + 4)
  80. return; //Not complete
  81. var maskBytes = data.Skip(index).Take(4).ToArray();
  82. index += 4;
  83. if (data.Count < index + payloadLength)
  84. return; //Not complete
  85. byte[] payloadData = new byte[payloadLength];
  86. for (int i = 0; i < payloadLength; i++)
  87. payloadData[i] = (byte)(data[index+i] ^ maskBytes[i % 4]);
  88. readState.Data.AddRange(payloadData);
  89. data.RemoveRange(0, index + payloadLength);
  90. if (frameType != FrameType.Continuation)
  91. readState.FrameType = frameType;
  92. if (isFinal && readState.FrameType.HasValue)
  93. {
  94. var stateData = readState.Data.ToArray();
  95. var stateFrameType = readState.FrameType;
  96. readState.Clear();
  97. processFrame(stateFrameType.Value, stateData);
  98. }
  99. }
  100. }
  101. public static void ProcessFrame(FrameType frameType, byte[] data, Action<string> onMessage, Action onClose, Action<byte[]> onBytes, Action<byte[]> onPing, Action<byte[]> onPong)
  102. {
  103. switch (frameType)
  104. {
  105. case FrameType.Close:
  106. if (data.Length == 1 || data.Length>125)
  107. throw new WebSocketException(StatusCodes.ProtocolError);
  108. if (data.Length >= 2)
  109. {
  110. var closeCode = (ushort)data.Take(2).ToArray().ToLittleEndianInt();
  111. if (!StatusCodes.ValidCloseCodes.Contains(closeCode) && (closeCode < 3000 || closeCode > 4999))
  112. throw new WebSocketException(StatusCodes.ProtocolError);
  113. }
  114. if (data.Length > 2)
  115. ReadUTF8PayloadData(data.Skip(2).ToArray());
  116. onClose();
  117. break;
  118. case FrameType.Bytes:
  119. onBytes(data);
  120. break;
  121. case FrameType.Ping:
  122. onPing(data);
  123. break;
  124. case FrameType.Pong:
  125. onPong(data);
  126. break;
  127. case FrameType.Text:
  128. onMessage(ReadUTF8PayloadData(data));
  129. break;
  130. default:
  131. WebSocketLog.Debug("Received unhandled " + frameType);
  132. break;
  133. }
  134. }
  135. public static byte[] BuildHandshake(HttpRequest request, string subProtocol)
  136. {
  137. WebSocketLog.Debug("Building Hybi-14 Response");
  138. var builder = new StringBuilder();
  139. builder.Append("HTTP/1.1 101 Switching Protocols\r\n");
  140. builder.Append("Upgrade: websocket\r\n");
  141. builder.Append("Connection: Upgrade\r\n");
  142. if (subProtocol != null)
  143. builder.AppendFormat("Sec-WebSocket-Protocol: {0}\r\n", subProtocol);
  144. var responseKey = CreateResponseKey(request["Sec-WebSocket-Key"]);
  145. builder.AppendFormat("Sec-WebSocket-Accept: {0}\r\n", responseKey);
  146. builder.Append("\r\n");
  147. return Encoding.ASCII.GetBytes(builder.ToString());
  148. }
  149. private const string WebSocketResponseGuid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
  150. public static string CreateResponseKey(string requestKey)
  151. {
  152. var combined = requestKey + WebSocketResponseGuid;
  153. var bytes = SHA1.Create().ComputeHash(Encoding.ASCII.GetBytes(combined));
  154. return Convert.ToBase64String(bytes);
  155. }
  156. private static string ReadUTF8PayloadData(byte[] bytes)
  157. {
  158. var encoding = new UTF8Encoding(false, true);
  159. try
  160. {
  161. return encoding.GetString(bytes);
  162. }
  163. catch(ArgumentException)
  164. {
  165. throw new WebSocketException(StatusCodes.InvalidFramePayloadData);
  166. }
  167. }
  168. }
  169. }
  170. #endif