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.
 
 
 
 

241 lines
8.3 KiB

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