示例#1
0
        /// <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)}");
                }
            }
        }
示例#5
0
        /// <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)}");
                }
            }
        }
示例#7
0
        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)}");
                }
            }
        }
示例#9
0
 /// <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();
 }