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.
 
 
 
 

260 rivejä
9.3 KiB

  1. // Licensed to the .NET Foundation under one or more agreements.
  2. // The .NET Foundation licenses this file to you under the MIT license.
  3. // See the LICENSE file in the project root for more information.
  4. using System;
  5. using System.Collections.Concurrent;
  6. using System.Collections.Generic;
  7. using System.Linq;
  8. using System.Net;
  9. using System.Security.Authentication;
  10. using System.Security.Cryptography.X509Certificates;
  11. using System.Threading;
  12. using System.Threading.Tasks;
  13. using MQTTnet.Channel;
  14. using MQTTnet.Client;
  15. using MQTTnet.Exceptions;
  16. using SuperSocket.ClientEngine;
  17. using WebSocket4Net;
  18. namespace MQTTnet.Extensions.WebSocket4Net
  19. {
  20. public sealed class WebSocket4NetMqttChannel : IMqttChannel
  21. {
  22. readonly BlockingCollection<byte> _receiveBuffer = new BlockingCollection<byte>();
  23. readonly MqttClientOptions _clientOptions;
  24. readonly MqttClientWebSocketOptions _webSocketOptions;
  25. WebSocket _webSocket;
  26. public WebSocket4NetMqttChannel(MqttClientOptions clientOptions, MqttClientWebSocketOptions webSocketOptions)
  27. {
  28. _clientOptions = clientOptions ?? throw new ArgumentNullException(nameof(clientOptions));
  29. _webSocketOptions = webSocketOptions ?? throw new ArgumentNullException(nameof(webSocketOptions));
  30. }
  31. public string Endpoint => _webSocketOptions.Uri;
  32. public bool IsSecureConnection { get; private set; }
  33. public X509Certificate2 ClientCertificate { get; }
  34. public async Task ConnectAsync(CancellationToken cancellationToken)
  35. {
  36. var uri = _webSocketOptions.Uri;
  37. if (!uri.StartsWith("ws://", StringComparison.OrdinalIgnoreCase) && !uri.StartsWith("wss://", StringComparison.OrdinalIgnoreCase))
  38. {
  39. if (_webSocketOptions.TlsOptions?.UseTls == false)
  40. {
  41. uri = "ws://" + uri;
  42. }
  43. else
  44. {
  45. uri = "wss://" + uri;
  46. }
  47. }
  48. #if NET48 || NETCOREAPP3_1 || NET5 || NET6
  49. var sslProtocols = _webSocketOptions?.TlsOptions.SslProtocol ?? SslProtocols.Tls12 | SslProtocols.Tls13;
  50. #else
  51. var sslProtocols = _webSocketOptions?.TlsOptions.SslProtocol ?? SslProtocols.Tls12 | (SslProtocols)0x00003000 /*Tls13*/;
  52. #endif
  53. var subProtocol = _webSocketOptions.SubProtocols.FirstOrDefault() ?? string.Empty;
  54. var cookies = new List<KeyValuePair<string, string>>();
  55. if (_webSocketOptions.CookieContainer != null)
  56. {
  57. throw new NotSupportedException("Cookies are not supported.");
  58. }
  59. List<KeyValuePair<string, string>> customHeaders = null;
  60. if (_webSocketOptions.RequestHeaders != null)
  61. {
  62. customHeaders = _webSocketOptions.RequestHeaders.Select(i => new KeyValuePair<string, string>(i.Key, i.Value)).ToList();
  63. }
  64. EndPoint proxy = null;
  65. if (_webSocketOptions.ProxyOptions != null)
  66. {
  67. throw new NotSupportedException("Proxies are not supported.");
  68. }
  69. // The user agent can be empty always because it is just added to the custom headers as "User-Agent".
  70. var userAgent = string.Empty;
  71. var origin = string.Empty;
  72. var webSocketVersion = WebSocketVersion.None;
  73. var receiveBufferSize = 0;
  74. var certificates = new X509CertificateCollection();
  75. if (_webSocketOptions?.TlsOptions?.Certificates != null)
  76. {
  77. foreach (var certificate in _webSocketOptions.TlsOptions.Certificates)
  78. {
  79. #if WINDOWS_UWP
  80. certificates.Add(new X509Certificate(certificate));
  81. #else
  82. certificates.Add(certificate);
  83. #endif
  84. }
  85. }
  86. _webSocket = new WebSocket(uri, subProtocol, cookies, customHeaders, userAgent, origin, webSocketVersion, proxy, sslProtocols, receiveBufferSize)
  87. {
  88. NoDelay = true,
  89. Security =
  90. {
  91. AllowUnstrustedCertificate = _webSocketOptions?.TlsOptions?.AllowUntrustedCertificates == true,
  92. AllowCertificateChainErrors = _webSocketOptions?.TlsOptions?.IgnoreCertificateChainErrors == true,
  93. Certificates = certificates
  94. }
  95. };
  96. await ConnectInternalAsync(cancellationToken).ConfigureAwait(false);
  97. IsSecureConnection = uri.StartsWith("wss://", StringComparison.OrdinalIgnoreCase);
  98. }
  99. public Task DisconnectAsync(CancellationToken cancellationToken)
  100. {
  101. if (_webSocket != null && _webSocket.State == WebSocketState.Open)
  102. {
  103. _webSocket.Close();
  104. }
  105. return Task.FromResult(0);
  106. }
  107. public Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
  108. {
  109. var readBytes = 0;
  110. while (count > 0 && !cancellationToken.IsCancellationRequested)
  111. {
  112. if (!_receiveBuffer.TryTake(out var @byte))
  113. {
  114. if (readBytes == 0)
  115. {
  116. // Block until at least one byte was received.
  117. @byte = _receiveBuffer.Take(cancellationToken);
  118. }
  119. else
  120. {
  121. return Task.FromResult(readBytes);
  122. }
  123. }
  124. buffer[offset] = @byte;
  125. offset++;
  126. count--;
  127. readBytes++;
  128. }
  129. return Task.FromResult(readBytes);
  130. }
  131. public Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
  132. {
  133. _webSocket.Send(buffer, offset, count);
  134. return Task.FromResult(0);
  135. }
  136. public void Dispose()
  137. {
  138. if (_webSocket == null)
  139. {
  140. return;
  141. }
  142. _webSocket.DataReceived -= OnDataReceived;
  143. _webSocket.Error -= OnError;
  144. _webSocket.Dispose();
  145. _webSocket = null;
  146. }
  147. static void OnError(object sender, ErrorEventArgs e)
  148. {
  149. System.Diagnostics.Debug.Write(e.Exception.ToString());
  150. }
  151. void OnDataReceived(object sender, DataReceivedEventArgs e)
  152. {
  153. foreach (var @byte in e.Data)
  154. {
  155. _receiveBuffer.Add(@byte);
  156. }
  157. }
  158. async Task ConnectInternalAsync(CancellationToken cancellationToken)
  159. {
  160. _webSocket.Error += OnError;
  161. _webSocket.DataReceived += OnDataReceived;
  162. var taskCompletionSource = new TaskCompletionSource<Exception>();
  163. void ErrorHandler(object sender, ErrorEventArgs e)
  164. {
  165. taskCompletionSource.TrySetResult(e.Exception);
  166. }
  167. void SuccessHandler(object sender, EventArgs e)
  168. {
  169. taskCompletionSource.TrySetResult(null);
  170. }
  171. try
  172. {
  173. _webSocket.Opened += SuccessHandler;
  174. _webSocket.Error += ErrorHandler;
  175. #pragma warning disable AsyncFixer02 // Long-running or blocking operations inside an async method
  176. _webSocket.Open();
  177. #pragma warning restore AsyncFixer02 // Long-running or blocking operations inside an async method
  178. using (var timeoutCts = new CancellationTokenSource(_clientOptions.Timeout))
  179. using (var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(timeoutCts.Token, cancellationToken))
  180. {
  181. using (linkedCts.Token.Register(() => taskCompletionSource.TrySetCanceled()))
  182. {
  183. try
  184. {
  185. await taskCompletionSource.Task.ConfigureAwait(false);
  186. }
  187. catch (Exception exception)
  188. {
  189. var timeoutReached = timeoutCts.IsCancellationRequested && !cancellationToken.IsCancellationRequested;
  190. if (timeoutReached)
  191. {
  192. throw new MqttCommunicationTimedOutException(exception);
  193. }
  194. if (exception is AuthenticationException authenticationException)
  195. {
  196. throw new MqttCommunicationException(authenticationException.InnerException);
  197. }
  198. if (exception is OperationCanceledException)
  199. {
  200. throw new MqttCommunicationTimedOutException();
  201. }
  202. throw new MqttCommunicationException(exception);
  203. }
  204. }
  205. }
  206. }
  207. catch (OperationCanceledException)
  208. {
  209. throw new MqttCommunicationTimedOutException();
  210. }
  211. finally
  212. {
  213. _webSocket.Opened -= SuccessHandler;
  214. _webSocket.Error -= ErrorHandler;
  215. }
  216. }
  217. }
  218. }