WebSocket.cs 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642
  1. using System;
  2. using System.Text;
  3. using System.Security.Cryptography;
  4. using System.Collections.Generic;
  5. using System.Threading;
  6. namespace NetCoreServer
  7. {
  8. /// <summary>
  9. /// WebSocket utility class
  10. /// </summary>
  11. public class WebSocket : IWebSocket
  12. {
  13. private readonly IWebSocket _wsHandler;
  14. /// <summary>
  15. /// Initialize a new WebSocket
  16. /// </summary>
  17. /// <param name="wsHandler">WebSocket handler</param>
  18. public WebSocket(IWebSocket wsHandler) { _wsHandler = wsHandler; ClearWsBuffers(); InitWsNonce(); }
  19. /// <summary>
  20. /// Final frame
  21. /// </summary>
  22. public const byte WS_FIN = 0x80;
  23. /// <summary>
  24. /// Text frame
  25. /// </summary>
  26. public const byte WS_TEXT = 0x01;
  27. /// <summary>
  28. /// Binary frame
  29. /// </summary>
  30. public const byte WS_BINARY = 0x02;
  31. /// <summary>
  32. /// Close frame
  33. /// </summary>
  34. public const byte WS_CLOSE = 0x08;
  35. /// <summary>
  36. /// Ping frame
  37. /// </summary>
  38. public const byte WS_PING = 0x09;
  39. /// <summary>
  40. /// Pong frame
  41. /// </summary>
  42. public const byte WS_PONG = 0x0A;
  43. /// <summary>
  44. /// Perform WebSocket client upgrade
  45. /// </summary>
  46. /// <param name="response">WebSocket upgrade HTTP response</param>
  47. /// <param name="id">WebSocket client Id</param>
  48. /// <returns>'true' if the WebSocket was successfully upgrade, 'false' if the WebSocket was not upgrade</returns>
  49. public bool PerformClientUpgrade(HttpResponse response, Guid id)
  50. {
  51. if (response.Status != 101)
  52. return false;
  53. bool error = false;
  54. bool accept = false;
  55. bool connection = false;
  56. bool upgrade = false;
  57. // Validate WebSocket handshake headers
  58. for (int i = 0; i < response.Headers; i++)
  59. {
  60. var header = response.Header(i);
  61. var key = header.Item1;
  62. var value = header.Item2;
  63. if (string.Compare(key, "Connection", StringComparison.OrdinalIgnoreCase) == 0)
  64. {
  65. if (string.Compare(value, "Upgrade", StringComparison.OrdinalIgnoreCase) != 0)
  66. {
  67. error = true;
  68. _wsHandler.OnWsError("Invalid WebSocket handshaked response: 'Connection' header value must be 'Upgrade'");
  69. break;
  70. }
  71. connection = true;
  72. }
  73. else if (string.Compare(key, "Upgrade", StringComparison.OrdinalIgnoreCase) == 0)
  74. {
  75. if (string.Compare(value, "websocket", StringComparison.OrdinalIgnoreCase) != 0)
  76. {
  77. error = true;
  78. _wsHandler.OnWsError("Invalid WebSocket handshaked response: 'Upgrade' header value must be 'websocket'");
  79. break;
  80. }
  81. upgrade = true;
  82. }
  83. else if (string.Compare(key, "Sec-WebSocket-Accept", StringComparison.OrdinalIgnoreCase) == 0)
  84. {
  85. // Calculate the original WebSocket hash
  86. string wskey = Convert.ToBase64String(WsNonce) + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
  87. string wshash;
  88. using (SHA1 sha1 = SHA1.Create())
  89. {
  90. wshash = Encoding.UTF8.GetString(sha1.ComputeHash(Encoding.UTF8.GetBytes(wskey)));
  91. }
  92. // Get the received WebSocket hash
  93. wskey = Encoding.UTF8.GetString(Convert.FromBase64String(value));
  94. // Compare original and received hashes
  95. if (string.Compare(wskey, wshash, StringComparison.InvariantCulture) != 0)
  96. {
  97. error = true;
  98. _wsHandler.OnWsError("Invalid WebSocket handshaked response: 'Sec-WebSocket-Accept' value validation failed");
  99. break;
  100. }
  101. accept = true;
  102. }
  103. }
  104. // Failed to perform WebSocket handshake
  105. if (!accept || !connection || !upgrade)
  106. {
  107. if (!error)
  108. _wsHandler.OnWsError("Invalid WebSocket response");
  109. return false;
  110. }
  111. // WebSocket successfully handshaked!
  112. WsHandshaked = true;
  113. WsRandom.NextBytes(WsSendMask);
  114. _wsHandler.OnWsConnected(response);
  115. return true;
  116. }
  117. /// <summary>
  118. /// Perform WebSocket server upgrade
  119. /// </summary>
  120. /// <param name="request">WebSocket upgrade HTTP request</param>
  121. /// <param name="response">WebSocket upgrade HTTP response</param>
  122. /// <returns>'true' if the WebSocket was successfully upgrade, 'false' if the WebSocket was not upgrade</returns>
  123. public bool PerformServerUpgrade(HttpRequest request, HttpResponse response)
  124. {
  125. if (request.Method != "GET")
  126. return false;
  127. bool error = false;
  128. bool connection = false;
  129. bool upgrade = false;
  130. bool wsKey = false;
  131. bool wsVersion = false;
  132. string accept = "";
  133. // Validate WebSocket handshake headers
  134. for (int i = 0; i < request.Headers; i++)
  135. {
  136. var header = request.Header(i);
  137. var key = header.Item1;
  138. var value = header.Item2;
  139. if (string.Compare(key, "Connection", StringComparison.OrdinalIgnoreCase) == 0)
  140. {
  141. if ((string.Compare(value, "Upgrade", StringComparison.OrdinalIgnoreCase) != 0) && (string.Compare(value, "keep-alive, Upgrade", StringComparison.OrdinalIgnoreCase) != 0))
  142. {
  143. error = true;
  144. response.MakeErrorResponse(400, "Invalid WebSocket handshaked request: 'Connection' header value must be 'Upgrade' or 'keep-alive, Upgrade'");
  145. break;
  146. }
  147. connection = true;
  148. }
  149. else if (string.Compare(key, "Upgrade", StringComparison.OrdinalIgnoreCase) == 0)
  150. {
  151. if (string.Compare(value, "websocket", StringComparison.OrdinalIgnoreCase) != 0)
  152. {
  153. error = true;
  154. response.MakeErrorResponse(400, "Invalid WebSocket handshaked request: 'Upgrade' header value must be 'websocket'");
  155. break;
  156. }
  157. upgrade = true;
  158. }
  159. else if (string.Compare(key, "Sec-WebSocket-Key", StringComparison.OrdinalIgnoreCase) == 0)
  160. {
  161. if (string.IsNullOrEmpty(value))
  162. {
  163. error = true;
  164. response.MakeErrorResponse(400, "Invalid WebSocket handshaked request: 'Sec-WebSocket-Key' header value must be non empty");
  165. break;
  166. }
  167. // Calculate the original WebSocket hash
  168. string wskey = value + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
  169. byte[] wshash;
  170. using (SHA1 sha1 = SHA1.Create())
  171. {
  172. wshash = sha1.ComputeHash(Encoding.UTF8.GetBytes(wskey));
  173. }
  174. accept = Convert.ToBase64String(wshash);
  175. wsKey = true;
  176. }
  177. else if (string.Compare(key, "Sec-WebSocket-Version", StringComparison.OrdinalIgnoreCase) == 0)
  178. {
  179. if (string.Compare(value, "13", StringComparison.OrdinalIgnoreCase) != 0)
  180. {
  181. error = true;
  182. response.MakeErrorResponse(400, "Invalid WebSocket handshaked request: 'Sec-WebSocket-Version' header value must be '13'");
  183. break;
  184. }
  185. wsVersion = true;
  186. }
  187. }
  188. // Filter out non WebSocket handshake requests
  189. if (!connection && !upgrade && !wsKey && !wsVersion)
  190. return false;
  191. // Failed to perform WebSocket handshake
  192. if (!connection || !upgrade || !wsKey || !wsVersion)
  193. {
  194. if (!error)
  195. response.MakeErrorResponse(400, "Invalid WebSocket response");
  196. _wsHandler.SendUpgrade(response);
  197. return false;
  198. }
  199. // Prepare WebSocket upgrade success response
  200. response.Clear();
  201. response.SetBegin(101);
  202. response.SetHeader("Connection", "Upgrade");
  203. response.SetHeader("Upgrade", "websocket");
  204. response.SetHeader("Sec-WebSocket-Accept", accept);
  205. response.SetBody();
  206. // Validate WebSocket upgrade request and response
  207. if (!_wsHandler.OnWsConnecting(request, response))
  208. return false;
  209. // Send WebSocket upgrade response
  210. _wsHandler.SendUpgrade(response);
  211. // WebSocket successfully handshaked!
  212. WsHandshaked = true;
  213. Array.Fill(WsSendMask, (byte)0);
  214. _wsHandler.OnWsConnected(request);
  215. return true;
  216. }
  217. /// <summary>
  218. /// Prepare WebSocket send frame
  219. /// </summary>
  220. /// <param name="opcode">WebSocket opcode</param>
  221. /// <param name="mask">WebSocket mask</param>
  222. /// <param name="buffer">Buffer to send as a span of bytes</param>
  223. /// <param name="status">WebSocket status (default is 0)</param>
  224. public void PrepareSendFrame(byte opcode, bool mask, ReadOnlySpan<byte> buffer, int status = 0)
  225. {
  226. // Check if we need to store additional 2 bytes of close status frame
  227. bool storeStatus = ((opcode & WS_CLOSE) == WS_CLOSE) && ((buffer.Length > 0) || (status != 0));
  228. long size = storeStatus ? (buffer.Length + 2) : buffer.Length;
  229. // Clear the previous WebSocket send buffer
  230. WsSendBuffer.Clear();
  231. // Append WebSocket frame opcode
  232. WsSendBuffer.Append(opcode);
  233. // Append WebSocket frame size
  234. if (size <= 125)
  235. WsSendBuffer.Append((byte)(((int)size & 0xFF) | (mask ? 0x80 : 0)));
  236. else if (size <= 65535)
  237. {
  238. WsSendBuffer.Append((byte)(126 | (mask ? 0x80 : 0)));
  239. WsSendBuffer.Append((byte)((size >> 8) & 0xFF));
  240. WsSendBuffer.Append((byte)(size & 0xFF));
  241. }
  242. else
  243. {
  244. WsSendBuffer.Append((byte)(127 | (mask ? 0x80 : 0)));
  245. for (int i = 7; i >= 0; i--)
  246. WsSendBuffer.Append((byte)((size >> (8 * i)) & 0xFF));
  247. }
  248. if (mask)
  249. {
  250. // Append WebSocket frame mask
  251. WsSendBuffer.Append(WsSendMask);
  252. }
  253. // Resize WebSocket frame buffer
  254. long offset = WsSendBuffer.Size;
  255. WsSendBuffer.Resize(WsSendBuffer.Size + size);
  256. int index = 0;
  257. // Append WebSocket close status
  258. // RFC 6455: If there is a body, the first two bytes of the body MUST
  259. // be a 2-byte unsigned integer (in network byte order) representing
  260. // a status code with value code.
  261. if (storeStatus)
  262. {
  263. index += 2;
  264. WsSendBuffer.Data[offset + 0] = (byte)(((status >> 8) & 0xFF) ^ WsSendMask[0]);
  265. WsSendBuffer.Data[offset + 1] = (byte)((status & 0xFF) ^ WsSendMask[1]);
  266. }
  267. // Mask WebSocket frame content
  268. for (int i = index; i < size; i++)
  269. WsSendBuffer.Data[offset + i] = (byte)(buffer[i - index] ^ WsSendMask[i % 4]);
  270. }
  271. /// <summary>
  272. /// Prepare WebSocket send frame
  273. /// </summary>
  274. /// <param name="buffer">Buffer to send</param>
  275. /// <param name="offset">Buffer offset</param>
  276. /// <param name="size">Buffer size</param>
  277. public void PrepareReceiveFrame(byte[] buffer, long offset, long size)
  278. {
  279. lock (WsReceiveLock)
  280. {
  281. int index = 0;
  282. // Clear received data after WebSocket frame was processed
  283. if (WsFrameReceived)
  284. {
  285. WsFrameReceived = false;
  286. WsHeaderSize = 0;
  287. WsPayloadSize = 0;
  288. WsReceiveFrameBuffer.Clear();
  289. Array.Clear(WsReceiveMask, 0, WsReceiveMask.Length);
  290. }
  291. if (WsFinalReceived)
  292. {
  293. WsFinalReceived = false;
  294. WsReceiveFinalBuffer.Clear();
  295. }
  296. while (size > 0)
  297. {
  298. // Clear received data after WebSocket frame was processed
  299. if (WsFrameReceived)
  300. {
  301. WsFrameReceived = false;
  302. WsHeaderSize = 0;
  303. WsPayloadSize = 0;
  304. WsReceiveFrameBuffer.Clear();
  305. Array.Clear(WsReceiveMask, 0, WsReceiveMask.Length);
  306. }
  307. if (WsFinalReceived)
  308. {
  309. WsFinalReceived = false;
  310. WsReceiveFinalBuffer.Clear();
  311. }
  312. // Prepare WebSocket frame opcode and mask flag
  313. if (WsReceiveFrameBuffer.Size < 2)
  314. {
  315. for (long i = 0; i < 2; i++, index++, size--)
  316. {
  317. if (size == 0)
  318. return;
  319. WsReceiveFrameBuffer.Append(buffer[offset + index]);
  320. }
  321. }
  322. byte opcode = (byte)(WsReceiveFrameBuffer[0] & 0x0F);
  323. bool fin = ((WsReceiveFrameBuffer[0] >> 7) & 0x01) != 0;
  324. bool mask = ((WsReceiveFrameBuffer[1] >> 7) & 0x01) != 0;
  325. long payload = WsReceiveFrameBuffer[1] & (~0x80);
  326. // Prepare WebSocket opcode
  327. WsOpcode = (opcode != 0) ? opcode : WsOpcode;
  328. // Prepare WebSocket frame size
  329. if (payload <= 125)
  330. {
  331. WsHeaderSize = 2 + (mask ? 4 : 0);
  332. WsPayloadSize = payload;
  333. }
  334. else if (payload == 126)
  335. {
  336. if (WsReceiveFrameBuffer.Size < 4)
  337. {
  338. for (long i = 0; i < 2; i++, index++, size--)
  339. {
  340. if (size == 0)
  341. return;
  342. WsReceiveFrameBuffer.Append(buffer[offset + index]);
  343. }
  344. }
  345. payload = ((WsReceiveFrameBuffer[2] << 8) | (WsReceiveFrameBuffer[3] << 0));
  346. WsHeaderSize = 4 + (mask ? 4 : 0);
  347. WsPayloadSize = payload;
  348. }
  349. else if (payload == 127)
  350. {
  351. if (WsReceiveFrameBuffer.Size < 10)
  352. {
  353. for (long i = 0; i < 8; i++, index++, size--)
  354. {
  355. if (size == 0)
  356. return;
  357. WsReceiveFrameBuffer.Append(buffer[offset + index]);
  358. }
  359. }
  360. payload = ((WsReceiveFrameBuffer[2] << 56) | (WsReceiveFrameBuffer[3] << 48) | (WsReceiveFrameBuffer[4] << 40) | (WsReceiveFrameBuffer[5] << 32) | (WsReceiveFrameBuffer[6] << 24) | (WsReceiveFrameBuffer[7] << 16) | (WsReceiveFrameBuffer[8] << 8) | (WsReceiveFrameBuffer[9] << 0));
  361. WsHeaderSize = 10 + (mask ? 4 : 0);
  362. WsPayloadSize = payload;
  363. }
  364. // Prepare WebSocket frame mask
  365. if (mask)
  366. {
  367. if (WsReceiveFrameBuffer.Size < WsHeaderSize)
  368. {
  369. for (long i = 0; i < 4; i++, index++, size--)
  370. {
  371. if (size == 0)
  372. return;
  373. WsReceiveFrameBuffer.Append(buffer[offset + index]);
  374. WsReceiveMask[i] = buffer[offset + index];
  375. }
  376. }
  377. }
  378. long total = WsHeaderSize + WsPayloadSize;
  379. long length = Math.Min(total - WsReceiveFrameBuffer.Size, size);
  380. // Prepare WebSocket frame payload
  381. WsReceiveFrameBuffer.Append(buffer[((int)offset + index)..((int)offset + index + (int)length)]);
  382. index += (int)length;
  383. size -= length;
  384. // Process WebSocket frame
  385. if (WsReceiveFrameBuffer.Size == total)
  386. {
  387. // Unmask WebSocket frame content
  388. if (mask)
  389. {
  390. for (long i = 0; i < WsPayloadSize; i++)
  391. WsReceiveFinalBuffer.Append((byte)(WsReceiveFrameBuffer[WsHeaderSize + i] ^ WsReceiveMask[i % 4]));
  392. }
  393. else
  394. WsReceiveFinalBuffer.Append(WsReceiveFrameBuffer.AsSpan().Slice((int)WsHeaderSize, (int)WsPayloadSize));
  395. WsFrameReceived = true;
  396. // Finalize WebSocket frame
  397. if (fin)
  398. {
  399. WsFinalReceived = true;
  400. switch (WsOpcode)
  401. {
  402. case WS_PING:
  403. {
  404. // Call the WebSocket ping handler
  405. _wsHandler.OnWsPing(WsReceiveFinalBuffer.Data, 0, WsReceiveFinalBuffer.Size);
  406. break;
  407. }
  408. case WS_PONG:
  409. {
  410. // Call the WebSocket pong handler
  411. _wsHandler.OnWsPong(WsReceiveFinalBuffer.Data, 0, WsReceiveFinalBuffer.Size);
  412. break;
  413. }
  414. case WS_CLOSE:
  415. {
  416. int sindex = 0;
  417. int status = 1000;
  418. // Read WebSocket close status
  419. if (WsReceiveFinalBuffer.Size >= 2)
  420. {
  421. sindex += 2;
  422. status = ((WsReceiveFinalBuffer[0] << 8) | (WsReceiveFinalBuffer[1] << 0));
  423. }
  424. // Call the WebSocket close handler
  425. _wsHandler.OnWsClose(WsReceiveFinalBuffer.Data, sindex, WsReceiveFinalBuffer.Size - sindex, status);
  426. break;
  427. }
  428. case WS_BINARY:
  429. case WS_TEXT:
  430. {
  431. // Call the WebSocket received handler
  432. _wsHandler.OnWsReceived(WsReceiveFinalBuffer.Data, 0, WsReceiveFinalBuffer.Size);
  433. break;
  434. }
  435. }
  436. }
  437. }
  438. }
  439. }
  440. }
  441. /// <summary>
  442. /// Required WebSocket receive frame size
  443. /// </summary>
  444. public long RequiredReceiveFrameSize()
  445. {
  446. lock (WsReceiveLock)
  447. {
  448. if (WsFrameReceived)
  449. return 0;
  450. // Required WebSocket frame opcode and mask flag
  451. if (WsReceiveFrameBuffer.Size < 2)
  452. return 2 - WsReceiveFrameBuffer.Size;
  453. bool mask = ((WsReceiveFrameBuffer[1] >> 7) & 0x01) != 0;
  454. long payload = WsReceiveFrameBuffer[1] & (~0x80);
  455. // Required WebSocket frame size
  456. if ((payload == 126) && (WsReceiveFrameBuffer.Size < 4))
  457. return 4 - WsReceiveFrameBuffer.Size;
  458. if ((payload == 127) && (WsReceiveFrameBuffer.Size < 10))
  459. return 10 - WsReceiveFrameBuffer.Size;
  460. // Required WebSocket frame mask
  461. if ((mask) && (WsReceiveFrameBuffer.Size < WsHeaderSize))
  462. return WsHeaderSize - WsReceiveFrameBuffer.Size;
  463. // Required WebSocket frame payload
  464. return WsHeaderSize + WsPayloadSize - WsReceiveFrameBuffer.Size;
  465. }
  466. }
  467. /// <summary>
  468. /// Clear WebSocket send/receive buffers
  469. /// </summary>
  470. public void ClearWsBuffers()
  471. {
  472. // Clear the receive buffer
  473. bool acquiredReceiveLock = false;
  474. try
  475. {
  476. // Sometimes on disconnect the receive lock could be taken by receive thread.
  477. // In this case we'll skip the receive buffer clearing. It will happen on
  478. // re-connect then or in GC.
  479. Monitor.TryEnter(WsReceiveLock, ref acquiredReceiveLock);
  480. if (acquiredReceiveLock)
  481. {
  482. WsFrameReceived = false;
  483. WsFinalReceived = false;
  484. WsHeaderSize = 0;
  485. WsPayloadSize = 0;
  486. WsReceiveFrameBuffer.Clear();
  487. WsReceiveFinalBuffer.Clear();
  488. Array.Clear(WsReceiveMask, 0, WsReceiveMask.Length);
  489. }
  490. }
  491. finally
  492. {
  493. if (acquiredReceiveLock)
  494. Monitor.Exit(WsReceiveLock);
  495. }
  496. // Clear the send buffer
  497. lock (WsSendLock)
  498. {
  499. WsSendBuffer.Clear();
  500. Array.Clear(WsSendMask, 0, WsSendMask.Length);
  501. }
  502. }
  503. /// <summary>
  504. /// Initialize WebSocket random nonce
  505. /// </summary>
  506. public void InitWsNonce() => WsRandom.NextBytes(WsNonce);
  507. /// <summary>
  508. /// Handshaked flag
  509. /// </summary>
  510. internal bool WsHandshaked;
  511. /// <summary>
  512. /// Received frame flag
  513. /// </summary>
  514. internal bool WsFrameReceived;
  515. /// <summary>
  516. /// Received final flag
  517. /// </summary>
  518. internal bool WsFinalReceived;
  519. /// <summary>
  520. /// Received frame opcode
  521. /// </summary>
  522. internal byte WsOpcode;
  523. /// <summary>
  524. /// Received frame header size
  525. /// </summary>
  526. internal long WsHeaderSize;
  527. /// <summary>
  528. /// Received frame payload size
  529. /// </summary>
  530. internal long WsPayloadSize;
  531. /// <summary>
  532. /// Receive buffer lock
  533. /// </summary>
  534. internal readonly object WsReceiveLock = new object();
  535. /// <summary>
  536. /// Receive frame buffer
  537. /// </summary>
  538. internal readonly Buffer WsReceiveFrameBuffer = new Buffer();
  539. /// <summary>
  540. /// Receive final buffer
  541. /// </summary>
  542. internal readonly Buffer WsReceiveFinalBuffer = new Buffer();
  543. /// <summary>
  544. /// Receive mask
  545. /// </summary>
  546. internal readonly byte[] WsReceiveMask = new byte[4];
  547. /// <summary>
  548. /// Send buffer lock
  549. /// </summary>
  550. internal readonly object WsSendLock = new object();
  551. /// <summary>
  552. /// Send buffer
  553. /// </summary>
  554. internal readonly Buffer WsSendBuffer = new Buffer();
  555. /// <summary>
  556. /// Send mask
  557. /// </summary>
  558. internal readonly byte[] WsSendMask = new byte[4];
  559. /// <summary>
  560. /// WebSocket random generator
  561. /// </summary>
  562. internal readonly Random WsRandom = new Random();
  563. /// <summary>
  564. /// WebSocket random nonce of 16 bytes
  565. /// </summary>
  566. internal readonly byte[] WsNonce = new byte[16];
  567. }
  568. }