/// <summary> /// Registers a device described by the message. /// </summary> /// <param name="message">The provisioning message.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>The registration result.</returns> public override async Task <DeviceRegistrationResult> RegisterAsync( ProvisioningTransportRegisterMessage message, CancellationToken cancellationToken) { if (Logging.IsEnabled) { Logging.Enter(this, $"{nameof(ProvisioningTransportHandlerAmqp)}.{nameof(RegisterAsync)}"); } cancellationToken.ThrowIfCancellationRequested(); AmqpClientConnection connection = null; try { AmqpAuthStrategy authStrategy; if (message.Security is SecurityProviderTpm) { authStrategy = new AmqpAuthStrategyTpm((SecurityProviderTpm)message.Security); } else if (message.Security is SecurityProviderX509) { authStrategy = new AmqpAuthStrategyX509((SecurityProviderX509)message.Security); } else { throw new NotSupportedException( $"{nameof(message.Security)} must be of type {nameof(SecurityProviderTpm)} " + $"or {nameof(SecurityProviderX509)}"); } if (Logging.IsEnabled) { Logging.Associate(authStrategy, this); } bool useWebSocket = (FallbackType == TransportFallbackType.WebSocketOnly); var builder = new UriBuilder() { Scheme = useWebSocket ? WebSocketConstants.Scheme : AmqpConstants.SchemeAmqps, Host = message.GlobalDeviceEndpoint, Port = Port, }; string registrationId = message.Security.GetRegistrationID(); string linkEndpoint = $"{message.IdScope}/registrations/{registrationId}"; connection = authStrategy.CreateConnection(builder.Uri, message.IdScope); await authStrategy.OpenConnectionAsync(connection, TimeoutConstant, useWebSocket, Proxy).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested(); await CreateLinksAsync(connection, linkEndpoint, message.ProductInfo).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested(); string correlationId = Guid.NewGuid().ToString(); RegistrationOperationStatus operation = await RegisterDeviceAsync(connection, correlationId).ConfigureAwait(false); // Poll with operationId until registration complete. int attempts = 0; string operationId = operation.OperationId; // Poll with operationId until registration complete. while (string.CompareOrdinal(operation.Status, RegistrationOperationStatus.OperationStatusAssigning) == 0 || string.CompareOrdinal(operation.Status, RegistrationOperationStatus.OperationStatusUnassigned) == 0) { cancellationToken.ThrowIfCancellationRequested(); await Task.Delay( operation.RetryAfter ?? DefaultOperationPoolingIntervalMilliseconds).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested(); operation = await OperationStatusLookupAsync( connection, operationId, correlationId).ConfigureAwait(false); attempts++; } if (string.CompareOrdinal(operation.Status, RegistrationOperationStatus.OperationStatusAssigned) == 0) { authStrategy.SaveCredentials(operation); } await connection.CloseAsync(TimeoutConstant).ConfigureAwait(false); return(ConvertToProvisioningRegistrationResult(operation.RegistrationState)); } catch (Exception ex) when(!(ex is ProvisioningTransportException)) { if (Logging.IsEnabled) { Logging.Error( this, $"{nameof(ProvisioningTransportHandlerAmqp)} threw exception {ex}", nameof(RegisterAsync)); } throw new ProvisioningTransportException($"AMQP transport exception", ex, true); } finally { if (Logging.IsEnabled) { Logging.Exit(this, $"{nameof(ProvisioningTransportHandlerAmqp)}.{nameof(RegisterAsync)}"); } } }
/// <summary> /// Registers a device described by the message. /// </summary> /// <param name="message">The provisioning message.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>The registration result.</returns> public async override Task <DeviceRegistrationResult> RegisterAsync( ProvisioningTransportRegisterMessage message, CancellationToken cancellationToken) { if (Logging.IsEnabled) { Logging.Enter(this, $"{nameof(ProvisioningTransportHandlerHttp)}.{nameof(RegisterAsync)}"); } cancellationToken.ThrowIfCancellationRequested(); try { HttpAuthStrategy authStrategy; if (message.Security is SecurityProviderTpm) { authStrategy = new HttpAuthStrategyTpm((SecurityProviderTpm)message.Security); } else if (message.Security is SecurityProviderX509) { authStrategy = new HttpAuthStrategyX509((SecurityProviderX509)message.Security); } else { if (Logging.IsEnabled) { Logging.Error(this, $"Invalid {nameof(SecurityProvider)} type."); } throw new NotSupportedException( $"{nameof(message.Security)} must be of type {nameof(SecurityProviderTpm)} " + $"or {nameof(SecurityProviderX509)}"); } if (Logging.IsEnabled) { Logging.Associate(authStrategy, this); } var builder = new UriBuilder() { Scheme = Uri.UriSchemeHttps, Host = message.GlobalDeviceEndpoint }; DeviceProvisioningServiceRuntimeClient client = authStrategy.CreateClient(builder.Uri); client.HttpClient.DefaultRequestHeaders.Add("User-Agent", message.ProductInfo); if (Logging.IsEnabled) { Logging.Info(this, $"Uri: {builder.Uri}; User-Agent: {message.ProductInfo}"); } DeviceRegistration deviceRegistration = authStrategy.CreateDeviceRegistration(); string registrationId = message.Security.GetRegistrationID(); RegistrationOperationStatus operation = await client.RuntimeRegistration.RegisterDeviceAsync( registrationId, message.IdScope, deviceRegistration).ConfigureAwait(false); int attempts = 0; string operationId = operation.OperationId; if (Logging.IsEnabled) { Logging.RegisterDevice( this, registrationId, message.IdScope, deviceRegistration.Tpm == null ? "X509" : "TPM", operation.OperationId, operation.RetryAfter, operation.Status); } // Poll with operationId until registration complete. while (string.CompareOrdinal(operation.Status, RegistrationOperationStatus.OperationStatusAssigning) == 0 || string.CompareOrdinal(operation.Status, RegistrationOperationStatus.OperationStatusUnassigned) == 0) { cancellationToken.ThrowIfCancellationRequested(); await Task.Delay( operation.RetryAfter ?? s_defaultOperationPoolingIntervalMilliseconds).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested(); operation = await client.RuntimeRegistration.OperationStatusLookupAsync( registrationId, operationId, message.IdScope).ConfigureAwait(false); if (Logging.IsEnabled) { Logging.OperationStatusLookup( this, registrationId, operation.OperationId, operation.RetryAfter, operation.Status, attempts); } attempts++; } if (string.CompareOrdinal(operation.Status, RegistrationOperationStatus.OperationStatusAssigned) == 0) { authStrategy.SaveCredentials(operation); } return(ConvertToProvisioningRegistrationResult(operation.RegistrationState)); } // TODO: Catch only expected exceptions from HTTP and REST. catch (Exception ex) { if (Logging.IsEnabled) { Logging.Error( this, $"{nameof(ProvisioningTransportHandlerHttp)} threw exception {ex}", nameof(RegisterAsync)); } // TODO: Extract trackingId from the exception. throw new ProvisioningTransportException($"HTTP transport exception", ex, true); } finally { if (Logging.IsEnabled) { Logging.Exit(this, $"{nameof(ProvisioningTransportHandlerHttp)}.{nameof(RegisterAsync)}"); } } }
private async Task <RegistrationOperationStatus> ProvisionOverTcpCommonAsync( ProvisioningTransportRegisterMessage message, ClientTlsSettings tlsSettings, CancellationToken cancellationToken) { var tcs = new TaskCompletionSource <RegistrationOperationStatus>(); Bootstrap bootstrap = new Bootstrap() .Group(s_eventLoopGroup) .Channel <TcpSocketChannel>() .Option(ChannelOption.TcpNodelay, true) .Option(ChannelOption.Allocator, UnpooledByteBufferAllocator.Default) .Handler(new ActionChannelInitializer <ISocketChannel>(ch => { ch.Pipeline.AddLast( new ReadTimeoutHandler(ReadTimeoutSeconds), new TlsHandler(tlsSettings), MqttEncoder.Instance, new MqttDecoder(isServer: false, maxMessageSize: MaxMessageSize), new LoggingHandler(LogLevel.DEBUG), new ProvisioningChannelHandlerAdapter(message, tcs, cancellationToken)); })); if (Logging.IsEnabled) { Logging.Associate(bootstrap, this); } IPAddress[] addresses = await Dns.GetHostAddressesAsync(message.GlobalDeviceEndpoint).ConfigureAwait(false); if (Logging.IsEnabled) { Logging.Info(this, $"DNS resolved {addresses.Length} addresses."); } IChannel channel = null; Exception lastException = null; foreach (IPAddress address in addresses) { cancellationToken.ThrowIfCancellationRequested(); try { if (Logging.IsEnabled) { Logging.Info(this, $"Connecting to {address.ToString()}."); } channel = await bootstrap.ConnectAsync(address, Port).ConfigureAwait(false); break; } catch (AggregateException ae) { ae.Handle((ex) => { if (ex is ConnectException) // We will handle DotNetty.Transport.Channels.ConnectException { lastException = ex; if (Logging.IsEnabled) { Logging.Info( this, $"ConnectException trying to connect to {address.ToString()}: {ex.ToString()}"); } return(true); } return(false); // Let anything else stop the application. }); } } if (channel == null) { string errorMessage = "Cannot connect to Provisioning Service."; if (Logging.IsEnabled) { Logging.Error(this, errorMessage); } ExceptionDispatchInfo.Capture(lastException).Throw(); } return(await tcs.Task.ConfigureAwait(false)); }
/// <summary> /// Registers a device described by the message. /// </summary> /// <param name="message">The provisioning message.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>The registration result.</returns> public override async Task <DeviceRegistrationResult> RegisterAsync( ProvisioningTransportRegisterMessage message, CancellationToken cancellationToken) { if (Logging.IsEnabled) { Logging.Enter(this, $"{nameof(ProvisioningTransportHandlerMqtt)}.{nameof(RegisterAsync)}"); } if (message == null) { throw new ArgumentNullException(nameof(message)); } cancellationToken.ThrowIfCancellationRequested(); RegistrationOperationStatus operation = null; try { if (message.Security is SecurityProviderX509 x509Security) { if (FallbackType == TransportFallbackType.TcpWithWebSocketFallback || FallbackType == TransportFallbackType.TcpOnly) { // TODO: Fallback not implemented. operation = await ProvisionOverTcpUsingX509CertificateAsync(message, cancellationToken).ConfigureAwait(false); } else if (FallbackType == TransportFallbackType.WebSocketOnly) { operation = await ProvisionOverWssUsingX509CertificateAsync(message, cancellationToken).ConfigureAwait(false); } else { throw new NotSupportedException($"Not supported {nameof(FallbackType)} value: {FallbackType}"); } } else if (message.Security is SecurityProviderSymmetricKey symmetricKeySecurity) { if (FallbackType == TransportFallbackType.TcpWithWebSocketFallback || FallbackType == TransportFallbackType.TcpOnly) { // TODO: Fallback not implemented. operation = await ProvisionOverTcpUsingSymmetricKeyAsync(message, cancellationToken).ConfigureAwait(false); } else if (FallbackType == TransportFallbackType.WebSocketOnly) { operation = await ProvisionOverWssUsingSymmetricKeyAsync(message, cancellationToken).ConfigureAwait(false); } else { throw new NotSupportedException($"Not supported {nameof(FallbackType)} value: {FallbackType}"); } } else { if (Logging.IsEnabled) { Logging.Error(this, $"Invalid {nameof(SecurityProvider)} type."); } throw new NotSupportedException( $"{nameof(message.Security)} must be of type {nameof(SecurityProviderX509)} or {nameof(SecurityProviderSymmetricKey)}"); } return(ConvertToProvisioningRegistrationResult(operation.RegistrationState)); } catch (Exception ex) when(!(ex is ProvisioningTransportException)) { if (Logging.IsEnabled) { Logging.Error( this, $"{nameof(ProvisioningTransportHandlerMqtt)} threw exception {ex}", nameof(RegisterAsync)); } throw new ProvisioningTransportException($"MQTT transport exception", ex, true); } finally { if (Logging.IsEnabled) { Logging.Exit(this, $"{nameof(ProvisioningTransportHandlerMqtt)}.{nameof(RegisterAsync)}"); } } }
/// <summary> /// Registers a device described by the message. /// </summary> /// <param name="message">The provisioning message.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>The registration result.</returns> public async override Task <DeviceRegistrationResult> RegisterAsync( ProvisioningTransportRegisterMessage message, CancellationToken cancellationToken) { if (Logging.IsEnabled) { Logging.Enter(this, $"{nameof(ProvisioningTransportHandlerHttp)}.{nameof(RegisterAsync)}"); } cancellationToken.ThrowIfCancellationRequested(); try { HttpAuthStrategy authStrategy; if (message.Security is SecurityProviderTpm) { authStrategy = new HttpAuthStrategyTpm((SecurityProviderTpm)message.Security); } else if (message.Security is SecurityProviderX509) { authStrategy = new HttpAuthStrategyX509((SecurityProviderX509)message.Security); } else if (message.Security is SecurityProviderSymmetricKey) { authStrategy = new HttpAuthStrategySymmetricKey((SecurityProviderSymmetricKey)message.Security); } else { if (Logging.IsEnabled) { Logging.Error(this, $"Invalid {nameof(SecurityProvider)} type."); } throw new NotSupportedException( $"{nameof(message.Security)} must be of type {nameof(SecurityProviderTpm)} " + $"or {nameof(SecurityProviderX509)}"); } if (Logging.IsEnabled) { Logging.Associate(authStrategy, this); } var builder = new UriBuilder() { Scheme = Uri.UriSchemeHttps, Host = message.GlobalDeviceEndpoint, Port = Port, }; HttpClientHandler httpClientHandler = new HttpClientHandler(); if (Proxy != DefaultWebProxySettings.Instance) { httpClientHandler.UseProxy = (Proxy != null); httpClientHandler.Proxy = Proxy; if (Logging.IsEnabled) { Logging.Info(this, $"{nameof(RegisterAsync)} Setting HttpClientHandler.Proxy"); } } DeviceProvisioningServiceRuntimeClient client = authStrategy.CreateClient(builder.Uri, httpClientHandler); client.HttpClient.DefaultRequestHeaders.Add("User-Agent", message.ProductInfo); if (Logging.IsEnabled) { Logging.Info(this, $"Uri: {builder.Uri}; User-Agent: {message.ProductInfo}"); } DeviceRegistration deviceRegistration = authStrategy.CreateDeviceRegistration(); if (message.Payload != null && message.Payload.Length > 0) { deviceRegistration.Payload = new JRaw(message.Payload); } string registrationId = message.Security.GetRegistrationID(); RegistrationOperationStatus operation = await client.RuntimeRegistration.RegisterDeviceAsync( registrationId, message.IdScope, deviceRegistration).ConfigureAwait(false); int attempts = 0; string operationId = operation.OperationId; if (Logging.IsEnabled) { Logging.RegisterDevice( this, registrationId, message.IdScope, deviceRegistration.Tpm == null ? "X509" : "TPM", operation.OperationId, operation.RetryAfter, operation.Status); } // Poll with operationId until registration complete. while (string.CompareOrdinal(operation.Status, RegistrationOperationStatus.OperationStatusAssigning) == 0 || string.CompareOrdinal(operation.Status, RegistrationOperationStatus.OperationStatusUnassigned) == 0) { cancellationToken.ThrowIfCancellationRequested(); await Task.Delay( operation.RetryAfter ?? s_defaultOperationPoolingIntervalMilliseconds).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested(); operation = await client.RuntimeRegistration.OperationStatusLookupAsync( registrationId, operationId, message.IdScope).ConfigureAwait(false); if (Logging.IsEnabled) { Logging.OperationStatusLookup( this, registrationId, operation.OperationId, operation.RetryAfter, operation.Status, attempts); } attempts++; } if (string.CompareOrdinal(operation.Status, RegistrationOperationStatus.OperationStatusAssigned) == 0) { authStrategy.SaveCredentials(operation); } return(ConvertToProvisioningRegistrationResult(operation.RegistrationState)); } catch (HttpOperationException oe) { if (Logging.IsEnabled) { Logging.Error( this, $"{nameof(ProvisioningTransportHandlerHttp)} threw exception {oe}", nameof(RegisterAsync)); } bool isTransient = oe.Response.StatusCode >= HttpStatusCode.InternalServerError || (int)oe.Response.StatusCode == 429; try { var errorDetails = JsonConvert.DeserializeObject <ProvisioningErrorDetails>(oe.Response.Content); throw new ProvisioningTransportException(oe.Response.Content, oe, isTransient, errorDetails); } catch (JsonException ex) { if (Logging.IsEnabled) { Logging.Error( this, $"{nameof(ProvisioningTransportHandlerHttp)} server returned malformed error response." + $"Parsing error: {ex}. Server response: {oe.Response.Content}", nameof(RegisterAsync)); } throw new ProvisioningTransportException( $"HTTP transport exception: malformed server error message: '{oe.Response.Content}'", ex, false); } } catch (Exception ex) when(!(ex is ProvisioningTransportException)) { if (Logging.IsEnabled) { Logging.Error( this, $"{nameof(ProvisioningTransportHandlerHttp)} threw exception {ex}", nameof(RegisterAsync)); } throw new ProvisioningTransportException($"HTTP transport exception", ex, true); } finally { if (Logging.IsEnabled) { Logging.Exit(this, $"{nameof(ProvisioningTransportHandlerHttp)}.{nameof(RegisterAsync)}"); } } }
/// <summary> /// Registers a device described by the message. /// </summary> /// <param name="message">The provisioning message.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>The registration result.</returns> public async override Task <DeviceRegistrationResult> RegisterAsync( ProvisioningTransportRegisterMessage message, CancellationToken cancellationToken) { if (Logging.IsEnabled) { Logging.Enter(this, $"{nameof(ProvisioningTransportHandlerHttp)}.{nameof(RegisterAsync)}"); } if (message == null) { throw new ArgumentNullException(nameof(message)); } cancellationToken.ThrowIfCancellationRequested(); try { HttpAuthStrategy authStrategy; if (message.Security is SecurityProviderTpm) { authStrategy = new HttpAuthStrategyTpm((SecurityProviderTpm)message.Security); } else if (message.Security is SecurityProviderX509) { authStrategy = new HttpAuthStrategyX509((SecurityProviderX509)message.Security); } else if (message.Security is SecurityProviderSymmetricKey) { authStrategy = new HttpAuthStrategySymmetricKey((SecurityProviderSymmetricKey)message.Security); } else { if (Logging.IsEnabled) { Logging.Error(this, $"Invalid {nameof(SecurityProvider)} type."); } throw new NotSupportedException( $"{nameof(message.Security)} must be of type {nameof(SecurityProviderTpm)} " + $"or {nameof(SecurityProviderX509)}"); } if (Logging.IsEnabled) { Logging.Associate(authStrategy, this); } var builder = new UriBuilder { Scheme = Uri.UriSchemeHttps, Host = message.GlobalDeviceEndpoint, Port = Port, }; var httpClientHandler = new HttpClientHandler { // Cannot specify a specific protocol here, as desired due to an error: // ProvisioningDeviceClient_ValidRegistrationId_AmqpWithProxy_SymmetricKey_RegisterOk_GroupEnrollment failing for me with System.PlatformNotSupportedException: Operation is not supported on this platform. // When revisiting TLS12 work for DPS, we should figure out why. Perhaps the service needs to support it. //SslProtocols = TlsVersions.Preferred, }; if (Proxy != DefaultWebProxySettings.Instance) { httpClientHandler.UseProxy = (Proxy != null); httpClientHandler.Proxy = Proxy; if (Logging.IsEnabled) { Logging.Info(this, $"{nameof(RegisterAsync)} Setting HttpClientHandler.Proxy"); } } DeviceProvisioningServiceRuntimeClient client = authStrategy.CreateClient(builder.Uri, httpClientHandler); client.HttpClient.DefaultRequestHeaders.Add("User-Agent", message.ProductInfo); if (Logging.IsEnabled) { Logging.Info(this, $"Uri: {builder.Uri}; User-Agent: {message.ProductInfo}"); } DeviceRegistration deviceRegistration = authStrategy.CreateDeviceRegistration(); if (message.Payload != null && message.Payload.Length > 0) { deviceRegistration.Payload = new JRaw(message.Payload); } string registrationId = message.Security.GetRegistrationID(); RegistrationOperationStatus operation = await client.RuntimeRegistration.RegisterDeviceAsync( registrationId, message.IdScope, deviceRegistration).ConfigureAwait(false); int attempts = 0; string operationId = operation.OperationId; if (Logging.IsEnabled) { Logging.RegisterDevice( this, registrationId, message.IdScope, deviceRegistration.Tpm == null ? "X509" : "TPM", operation.OperationId, operation.RetryAfter, operation.Status); } // Poll with operationId until registration complete. while (string.CompareOrdinal(operation.Status, RegistrationOperationStatus.OperationStatusAssigning) == 0 || string.CompareOrdinal(operation.Status, RegistrationOperationStatus.OperationStatusUnassigned) == 0) { cancellationToken.ThrowIfCancellationRequested(); TimeSpan?serviceRecommendedDelay = operation.RetryAfter; if (serviceRecommendedDelay != null && serviceRecommendedDelay?.TotalSeconds < s_defaultOperationPoolingIntervalMilliseconds.TotalSeconds) { if (Logging.IsEnabled) { Logging.Error(this, $"Service recommended unexpected retryAfter of {operation.RetryAfter?.TotalSeconds}, defaulting to delay of {s_defaultOperationPoolingIntervalMilliseconds.ToString()}", nameof(RegisterAsync)); } serviceRecommendedDelay = s_defaultOperationPoolingIntervalMilliseconds; } await Task.Delay(serviceRecommendedDelay ?? RetryJitter.GenerateDelayWithJitterForRetry(s_defaultOperationPoolingIntervalMilliseconds)).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested(); operation = await client.RuntimeRegistration.OperationStatusLookupAsync( registrationId, operationId, message.IdScope).ConfigureAwait(false); if (Logging.IsEnabled) { Logging.OperationStatusLookup( this, registrationId, operation.OperationId, operation.RetryAfter, operation.Status, attempts); } attempts++; } if (string.CompareOrdinal(operation.Status, RegistrationOperationStatus.OperationStatusAssigned) == 0) { authStrategy.SaveCredentials(operation); } return(ConvertToProvisioningRegistrationResult(operation.RegistrationState)); } catch (HttpOperationException oe) { if (Logging.IsEnabled) { Logging.Error( this, $"{nameof(ProvisioningTransportHandlerHttp)} threw exception {oe}", nameof(RegisterAsync)); } bool isTransient = oe.Response.StatusCode >= HttpStatusCode.InternalServerError || (int)oe.Response.StatusCode == 429; try { var errorDetails = JsonConvert.DeserializeObject <ProvisioningErrorDetailsHttp>(oe.Response.Content); throw new ProvisioningTransportException(oe.Response.Content, oe, isTransient, errorDetails); } catch (JsonException ex) { if (Logging.IsEnabled) { Logging.Error( this, $"{nameof(ProvisioningTransportHandlerHttp)} server returned malformed error response." + $"Parsing error: {ex}. Server response: {oe.Response.Content}", nameof(RegisterAsync)); } throw new ProvisioningTransportException( $"HTTP transport exception: malformed server error message: '{oe.Response.Content}'", ex, false); } } catch (Exception ex) when(!(ex is ProvisioningTransportException)) { if (Logging.IsEnabled) { Logging.Error( this, $"{nameof(ProvisioningTransportHandlerHttp)} threw exception {ex}", nameof(RegisterAsync)); } throw new ProvisioningTransportException($"HTTP transport exception", ex, true); } finally { if (Logging.IsEnabled) { Logging.Exit(this, $"{nameof(ProvisioningTransportHandlerHttp)}.{nameof(RegisterAsync)}"); } } }
private async Task <RegistrationOperationStatus> ProvisionOverTcpUsingX509CertificateAsync( ProvisioningTransportRegisterMessage message, CancellationToken cancellationToken) { Debug.Assert(message.Security is SecurityProviderX509); cancellationToken.ThrowIfCancellationRequested(); X509Certificate2 clientCertificate = ((SecurityProviderX509)message.Security).GetAuthenticationCertificate(); var tlsSettings = new ClientTlsSettings( message.GlobalDeviceEndpoint, new List <X509Certificate> { clientCertificate }); var tcs = new TaskCompletionSource <RegistrationOperationStatus>(); Bootstrap bootstrap = new Bootstrap() .Group(s_eventLoopGroup) .Channel <TcpSocketChannel>() .Option(ChannelOption.TcpNodelay, true) .Option(ChannelOption.Allocator, UnpooledByteBufferAllocator.Default) .Handler(new ActionChannelInitializer <ISocketChannel>(ch => { ch.Pipeline.AddLast( new ReadTimeoutHandler(ReadTimeoutSeconds), new TlsHandler(tlsSettings), //TODO: Ensure SystemDefault is used. MqttEncoder.Instance, new MqttDecoder(isServer: false, maxMessageSize: MaxMessageSize), new ProvisioningChannelHandlerAdapter(message, tcs, cancellationToken)); })); if (Logging.IsEnabled) { Logging.Associate(bootstrap, this); } IPAddress[] addresses = await Dns.GetHostAddressesAsync(message.GlobalDeviceEndpoint).ConfigureAwait(false); if (Logging.IsEnabled) { Logging.Info(this, $"DNS resolved {addresses.Length} addresses."); } IChannel channel = null; Exception lastException = null; foreach (IPAddress address in addresses) { cancellationToken.ThrowIfCancellationRequested(); try { if (Logging.IsEnabled) { Logging.Info(this, $"Connecting to {address.ToString()}."); } channel = await bootstrap.ConnectAsync(address, Port).ConfigureAwait(false); } catch (TimeoutException ex) { lastException = ex; if (Logging.IsEnabled) { Logging.Info( this, $"TimeoutException trying to connect to {address.ToString()}: {ex.ToString()}"); } } catch (IOException ex) { lastException = ex; if (Logging.IsEnabled) { Logging.Info( this, $"IOException trying to connect to {address.ToString()}: {ex.ToString()}"); } } } if (channel == null) { string errorMessage = "Cannot connect to Provisioning Service."; if (Logging.IsEnabled) { Logging.Error(this, errorMessage); } ExceptionDispatchInfo.Capture(lastException).Throw(); } return(await tcs.Task.ConfigureAwait(false)); }
/// <summary> /// Registers a device described by the message. Because the AMQP library does not accept cancellation tokens, the provided cancellation token /// will only be checked for cancellation between AMQP operations. The timeout will be respected during the AMQP operations. /// </summary> /// <param name="message">The provisioning message.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>The registration result.</returns> public override async Task <DeviceRegistrationResult> RegisterAsync( ProvisioningTransportRegisterMessage message, CancellationToken cancellationToken) { if (Logging.IsEnabled) { Logging.Enter(this, $"{nameof(ProvisioningTransportHandlerAmqp)}.{nameof(RegisterAsync)}"); } if (message == null) { throw new ArgumentNullException(nameof(message)); } // We need to create a LinkedTokenSource to include both the default timeout and the cancellation token // AMQP library started supporting CancellationToken starting from version 2.5.5 // To preserve current behavior, we will honor both the legacy timeout and the cancellation token parameter. using var timeoutTokenSource = new CancellationTokenSource(s_timeoutConstant); using var cancellationTokenSourceBundle = CancellationTokenSource.CreateLinkedTokenSource(timeoutTokenSource.Token, cancellationToken); CancellationToken bundleCancellationToken = cancellationTokenSourceBundle.Token; bundleCancellationToken.ThrowIfCancellationRequested(); try { AmqpAuthStrategy authStrategy; if (message.Security is SecurityProviderTpm tpm) { authStrategy = new AmqpAuthStrategyTpm(tpm); } else if (message.Security is SecurityProviderX509 x509) { authStrategy = new AmqpAuthStrategyX509(x509); } else if (message.Security is SecurityProviderSymmetricKey key) { authStrategy = new AmqpAuthStrategySymmetricKey(key); } else { throw new NotSupportedException( $"{nameof(message.Security)} must be of type {nameof(SecurityProviderTpm)}, " + $"{nameof(SecurityProviderX509)} or {nameof(SecurityProviderSymmetricKey)}"); } if (Logging.IsEnabled) { Logging.Associate(authStrategy, this); } bool useWebSocket = FallbackType == TransportFallbackType.WebSocketOnly; var builder = new UriBuilder { Scheme = useWebSocket ? WebSocketConstants.Scheme : AmqpConstants.SchemeAmqps, Host = message.GlobalDeviceEndpoint, Port = Port, }; string registrationId = message.Security.GetRegistrationID(); string linkEndpoint = $"{message.IdScope}/registrations/{registrationId}"; using AmqpClientConnection connection = authStrategy.CreateConnection(builder.Uri, message.IdScope); await authStrategy.OpenConnectionAsync(connection, useWebSocket, Proxy, RemoteCertificateValidationCallback, bundleCancellationToken).ConfigureAwait(false); bundleCancellationToken.ThrowIfCancellationRequested(); await CreateLinksAsync( connection, linkEndpoint, message.ProductInfo, bundleCancellationToken) .ConfigureAwait(false); bundleCancellationToken.ThrowIfCancellationRequested(); string correlationId = Guid.NewGuid().ToString(); DeviceRegistration deviceRegistration = (message.Payload != null && message.Payload.Length > 0) ? new DeviceRegistration { Payload = new JRaw(message.Payload) } : null; RegistrationOperationStatus operation = await RegisterDeviceAsync(connection, correlationId, deviceRegistration, bundleCancellationToken).ConfigureAwait(false); // Poll with operationId until registration complete. int attempts = 0; string operationId = operation.OperationId; // Poll with operationId until registration complete. while (string.CompareOrdinal(operation.Status, RegistrationOperationStatus.OperationStatusAssigning) == 0 || string.CompareOrdinal(operation.Status, RegistrationOperationStatus.OperationStatusUnassigned) == 0) { bundleCancellationToken.ThrowIfCancellationRequested(); await Task.Delay( operation.RetryAfter ?? RetryJitter.GenerateDelayWithJitterForRetry(s_defaultOperationPollingInterval), bundleCancellationToken).ConfigureAwait(false); try { operation = await OperationStatusLookupAsync( connection, operationId, correlationId, bundleCancellationToken) .ConfigureAwait(false); } catch (ProvisioningTransportException e) when(e.ErrorDetails is ProvisioningErrorDetailsAmqp amqp && e.IsTransient) { operation.RetryAfter = amqp.RetryAfter; } attempts++; } if (string.CompareOrdinal(operation.Status, RegistrationOperationStatus.OperationStatusAssigned) == 0) { authStrategy.SaveCredentials(operation); } await connection.CloseAsync(bundleCancellationToken).ConfigureAwait(false); return(ConvertToProvisioningRegistrationResult(operation.RegistrationState)); } catch (Exception ex) when(!(ex is ProvisioningTransportException)) { if (Logging.IsEnabled) { Logging.Error(this, $"{nameof(ProvisioningTransportHandlerAmqp)} threw exception {ex}", nameof(RegisterAsync)); } throw new ProvisioningTransportException($"AMQP transport exception", ex, true); } finally { if (Logging.IsEnabled) { Logging.Exit(this, $"{nameof(ProvisioningTransportHandlerAmqp)}.{nameof(RegisterAsync)}"); } } }
/// <summary> /// Registers a device described by the message. /// </summary> /// <param name="message">The provisioning message.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>The registration result.</returns> public virtual Task <DeviceRegistrationResult> RegisterAsync( ProvisioningTransportRegisterMessage message, CancellationToken cancellationToken) { return(_innerHandler.RegisterAsync(message, cancellationToken)); }
private async Task <RegistrationOperationStatus> ProvisionOverWssCommonAsync( ProvisioningTransportRegisterMessage message, X509Certificate2 clientCertificate, CancellationToken cancellationToken) { var tcs = new TaskCompletionSource <RegistrationOperationStatus>(); UriBuilder uriBuilder = new UriBuilder(WsScheme, message.GlobalDeviceEndpoint, Port); Uri websocketUri = uriBuilder.Uri; // TODO properly dispose of the ws. var websocket = new ClientWebSocket(); websocket.Options.AddSubProtocol(WsMqttSubprotocol); if (clientCertificate != null) { websocket.Options.ClientCertificates.Add(clientCertificate); } //Check if we're configured to use a proxy server try { if (Proxy != DefaultWebProxySettings.Instance) { // Configure proxy server websocket.Options.Proxy = Proxy; if (Logging.IsEnabled) { Logging.Info(this, $"{nameof(ProvisionOverWssUsingX509CertificateAsync)} Setting ClientWebSocket.Options.Proxy"); } } } catch (PlatformNotSupportedException) { // .NET Core 2.0 doesn't support WebProxy configuration - ignore this setting. if (Logging.IsEnabled) { Logging.Error(this, $"{nameof(ProvisionOverWssUsingX509CertificateAsync)} PlatformNotSupportedException thrown as .NET Core 2.0 doesn't support proxy"); } } await websocket.ConnectAsync(websocketUri, cancellationToken).ConfigureAwait(false); var clientChannel = new ClientWebSocketChannel(null, websocket); clientChannel .Option(ChannelOption.Allocator, UnpooledByteBufferAllocator.Default) .Option(ChannelOption.AutoRead, true) .Option(ChannelOption.RcvbufAllocator, new AdaptiveRecvByteBufAllocator()) .Option(ChannelOption.MessageSizeEstimator, DefaultMessageSizeEstimator.Default) .Pipeline.AddLast( new ReadTimeoutHandler(ReadTimeoutSeconds), MqttEncoder.Instance, new MqttDecoder(false, MaxMessageSize), new LoggingHandler(LogLevel.DEBUG), new ProvisioningChannelHandlerAdapter(message, tcs, cancellationToken)); await s_eventLoopGroup.RegisterAsync(clientChannel).ConfigureAwait(false); return(await tcs.Task.ConfigureAwait(false)); }
public override Task <DeviceRegistrationResult> RegisterAsync( ProvisioningTransportRegisterMessage message, CancellationToken cancellationToken) { throw new System.NotImplementedException(); }