public static Exception GetExceptionFromOutcome(Outcome outcome)
        {
            Exception retException;

            if (outcome == null)
            {
                retException = new IotHubException("Unknown error.");
                return(retException);
            }

            if (outcome.DescriptorCode == Rejected.Code)
            {
                var rejected = (Rejected)outcome;
                retException = ToIotHubClientContract(rejected.Error);
            }
            else if (outcome.DescriptorCode == Released.Code)
            {
                retException = new OperationCanceledException("AMQP link released.");
            }
            else
            {
                retException = new IotHubException("Unknown error.");
            }

            return(retException);
        }
        async Task ProcessConnectAckAsync(IChannelHandlerContext context, ConnAckPacket packet)
        {
            if (packet.ReturnCode != ConnectReturnCode.Accepted)
            {
                string reason          = "CONNECT failed: " + packet.ReturnCode;
                var    iotHubException = new UnauthorizedException(reason);
                ShutdownOnError(context, iotHubException);
                return;
            }

            if (!this.IsInState(StateFlags.Connecting))
            {
                string reason          = "CONNECT has been received, however a session has already been established. Only one CONNECT/CONNACK pair is expected per session.";
                var    iotHubException = new IotHubException(reason);
                ShutdownOnError(context, iotHubException);
                return;
            }

            this.stateFlags = StateFlags.Connected;

            this.mqttIotHubEventHandler.OnConnected();

            this.ResumeReadingIfNecessary(context);

            if (packet.SessionPresent)
            {
                await this.SubscribeAsync(context, null);
            }
        }
        private void HandleConnectionStatusExceptions(IotHubException hubException)
        {
            ConnectionStatusChangeReason status = ConnectionStatusChangeReason.Communication_Error;

            if (hubException.IsTransient)
            {
                status = ConnectionStatusChangeReason.Retry_Expired;
            }
            else if (hubException is UnauthorizedException)
            {
                status = ConnectionStatusChangeReason.Bad_Credential;
            }
            else if (hubException is DeviceDisabledException)
            {
                // This mapping along with the DeviceDisabledException class
                // needs to be removed because DeviceDisabledException is not used anywhere.
                status = ConnectionStatusChangeReason.Device_Disabled;
            }
            else if (hubException is DeviceNotFoundException)
            {
                status = ConnectionStatusChangeReason.Device_Disabled;
            }

            _onConnectionStatusChanged(ConnectionStatus.Disconnected, status);
        }
Beispiel #4
0
        /// <summary>
        /// Checks if the provided exception is considered transient in nature or not
        /// Transient include issues such as a single failed network attempt
        /// </summary>
        /// <param name="originalException"></param>
        /// <returns></returns>
        private static bool IsTransient(Exception originalException)
        {
            // If the exception is a IotHubException its IsTransient property can be inspected
            IotHubException iotHubException = originalException as IotHubException;

            if (iotHubException != null)
            {
                return(iotHubException.IsTransient);
            }

            // If the exception is an HTTP request exception then assume it is transient
            HttpRequestException httpException = originalException as HttpRequestException;

            if (httpException != null)
            {
                return(true);
            }

            WebException webException = originalException as WebException;

            if (webException != null)
            {
                // If the web exception contains one of the following status values  it may be transient.
                return(new[]
                {
                    WebExceptionStatus.ConnectionClosed,
                    WebExceptionStatus.Timeout,
                    WebExceptionStatus.RequestCanceled
                }.Contains(webException.Status));
            }

            return(false);
        }
Beispiel #5
0
        public async Task TestHandleFailOverExceptions()
        {
            // Arrange
            var symbol            = new Amqp.Encoding.AmqpSymbol("com.microsoft:iot-hub-not-found-error");
            var failOverException = new IotHubException(new Amqp.AmqpException(symbol, $"(condition='{symbol}')"));

            var      messageConverter         = Mock.Of <IMessageConverter <Message> >(m => m.FromMessage(It.IsAny <IMessage>()) == new Message());
            var      messageConverterProvider = Mock.Of <IMessageConverterProvider>(m => m.Get <Message>() == messageConverter);
            string   clientId      = "d1";
            var      cloudListener = Mock.Of <ICloudListener>();
            TimeSpan idleTimeout   = TimeSpan.FromSeconds(60);
            Action <string, CloudConnectionStatus> connectionStatusChangedHandler = (s, status) => { };
            var client = new Mock <IClient>(MockBehavior.Strict);

            client.Setup(c => c.SendEventAsync(It.IsAny <Message>())).ThrowsAsync(failOverException);
            client.Setup(c => c.CloseAsync()).Returns(Task.CompletedTask);
            var      cloudProxy = new CloudProxy(client.Object, messageConverterProvider, clientId, connectionStatusChangedHandler, cloudListener, idleTimeout, false);
            IMessage message    = new EdgeMessage.Builder(new byte[0]).Build();

            // Act
            await Assert.ThrowsAsync <IotHubException>(() => cloudProxy.SendMessageAsync(message));

            // Assert.
            client.VerifyAll();
        }
Beispiel #6
0
        /// <summary>
        /// Handles IoT Hub exception and transform it as an <see cref="ApiException"/> with a well-known application error code.
        /// </summary>
        /// <param name="deviceId"></param>
        /// <param name="iotHubException"></param>
        /// <param name="apiException"></param>
        /// <returns></returns>
        private bool TryHandleIotHubException(string deviceId, IotHubException iotHubException, out ApiException apiException)
        {
            switch (iotHubException)
            {
            // thrown when there's no IoT Hub device registration OR there's an enabled IoT Hub device registration but device did not establish connection yet
            case DeviceNotFoundException ex:
                apiException = new IdNotFoundException(ErrorCodes.DeviceNotFound, deviceId, ex);
                break;

            // thrown when an attempt to communicate with the IoT Hub fails
            case IotHubCommunicationException ex:
                m_Logger.LogWarning($"An IotHubCommunicationException occurred: {ex}");
                apiException = new CommunicationException(ErrorCodes.CommunicationError, ex.Message, ex);
                break;

            // thrown when the IoT Hub returns an error code (i.e. device registration is disable which prevent the actual device from establishing a connection)
            case ServerErrorException ex:
                m_Logger.LogWarning($"A ServerErrorException occurred: {ex}");
                apiException = new CommunicationException(ErrorCodes.GatewayError, ErrorMessages.GetGatewayErrorMessage(), ex);
                break;

            // thrown when the maximum number of IoT Hub messages has been reached
            case QuotaExceededException ex:
                apiException = new CommunicationException(ErrorCodes.QuotaExceeded, ErrorMessages.GetQuotaExceededErrorMessage(), ex);
                break;

            // thrown when the message size is greater than the max size allowed (131072 bytes)
            case MessageTooLargeException ex:
                apiException = new InvalidResultException(ErrorCodes.MessageTooLarge, ErrorMessages.GetMessageTooLargeErrorMessage(), ex);
                break;

            // thrown when an error occurs during device client operation (i.e. device doesn't repond within the configured time out)
            // shall always be kept last
            case IotHubException ex:
                m_Logger.LogWarning($"An IotHubException occurred: {ex}");
                apiException = new OperationException(ErrorCodes.DeviceOperationError, ErrorMessages.GetOperationErrorMessage(), ex);
                break;

            // exception won't be transformed and therefore will be logged accordingly
            default:
                apiException = null;
                break;
            }

            return(apiException != null);
        }
        private void HandleConnectionStatusExceptions(IotHubException hubException)
        {
            ConnectionStatusChangeReason status = ConnectionStatusChangeReason.Communication_Error;

            if (hubException.IsTransient)
            {
                status = ConnectionStatusChangeReason.Retry_Expired;
            }
            else if (hubException is UnauthorizedException)
            {
                status = ConnectionStatusChangeReason.Bad_Credential;
            }
            else if (hubException is DeviceDisabledException)
            {
                status = ConnectionStatusChangeReason.Device_Disabled;
            }

            _onConnectionStatusChanged(ConnectionStatus.Disconnected, status);
        }
Beispiel #8
0
        public async Task RetryTransientErrorThrownAfterNumberOfRetriesThrows()
        {
            var contextMock      = Substitute.For <IPipelineContext>();
            var innerHandlerMock = Substitute.For <IDelegatingHandler>();

            innerHandlerMock.OpenAsync(Arg.Any <CancellationToken>()).Returns(t =>
            {
                throw new IotHubException(TestExceptionMessage, isTransient: true);
            });

            var sut = new RetryDelegatingHandler(contextMock, innerHandlerMock);

            using (var cts = new CancellationTokenSource(100))
            {
                IotHubException exception = await sut.OpenAsync(cts.Token).ExpectedAsync <IotHubException>().ConfigureAwait(false);

                Assert.AreEqual(TestExceptionMessage, exception.Message);
            }
        }
        public async Task RetryTransientErrorThrownAfterNumberOfRetriesThrows()
        {
            // arrange
            using var cts = new CancellationTokenSource(100);
            var contextMock = Substitute.For <PipelineContext>();

            contextMock.ConnectionStatusChangesHandler = new ConnectionStatusChangesHandler(delegate(ConnectionStatus status, ConnectionStatusChangeReason reason) { });
            var innerHandlerMock = Substitute.For <IDelegatingHandler>();

            innerHandlerMock
            .OpenAsync(cts.Token)
            .Returns(t => throw new IotHubException(TestExceptionMessage, isTransient: true));

            var             sut       = new RetryDelegatingHandler(contextMock, innerHandlerMock);
            IotHubException exception = await sut
                                        .OpenAsync(cts.Token)
                                        .ExpectedAsync <IotHubException>()
                                        .ConfigureAwait(false);

            // act

            // assert
            exception.Message.Should().Be(TestExceptionMessage);
        }
Beispiel #10
0
        void ProcessConnectAck(IChannelHandlerContext context, ConnAckPacket packet)
        {
            if (packet.ReturnCode != ConnectReturnCode.Accepted)
            {
                string reason          = "CONNECT failed: " + packet.ReturnCode;
                var    iotHubException = new IotHubException(reason);
                ShutdownOnError(context, iotHubException);
                return;
            }

            if (!this.IsInState(StateFlags.Connecting))
            {
                string reason          = "CONNECT has been received, however a session has already been established. Only one CONNECT/CONNACK pair is expected per session.";
                var    iotHubException = new IotHubException(reason);
                ShutdownOnError(context, iotHubException);
                return;
            }

            this.stateFlags = StateFlags.Connected;

            this.ResumeReadingIfNecessary(context);

            this.onConnected();
        }
 static MessagingException ComposeIotHubCommunicationException(IotHubException ex)
 {
     return(new MessagingException(ex.Message, ex.InnerException, ex.IsTransient, ex.TrackingId));
 }
        private void OnTwinChangesReceived(AmqpMessage amqpMessage)
        {
            if (Logging.IsEnabled)
            {
                Logging.Enter(this, amqpMessage, $"{nameof(OnTwinChangesReceived)}");
            }

            try
            {
                _receivingAmqpLink.DisposeDelivery(amqpMessage, true, AmqpIoTConstants.AcceptedOutcome);
                string correlationId = amqpMessage.Properties?.CorrelationId?.ToString();
                int    status        = GetStatus(amqpMessage);

                Twin           twin           = null;
                TwinCollection twinProperties = null;

                if (status >= 400)
                {
                    // Handle failures
                    if (correlationId.StartsWith(AmqpTwinMessageType.Get.ToString(), StringComparison.OrdinalIgnoreCase) ||
                        correlationId.StartsWith(AmqpTwinMessageType.Patch.ToString(), StringComparison.OrdinalIgnoreCase))
                    {
                        string error = null;
                        using (var reader = new StreamReader(amqpMessage.BodyStream, System.Text.Encoding.UTF8))
                        {
                            error = reader.ReadToEnd();
                        };

                        // Retry for Http status code request timeout, Too many requests and server errors
                        var exception = new IotHubException(error, status >= 500 || status == 429 || status == 408);
                        _onTwinMessageReceived.Invoke(null, correlationId, null, exception);
                    }
                }
                else
                {
                    if (correlationId == null)
                    {
                        // Here we are getting desired property update notifications and want to handle it first
                        using var reader = new StreamReader(amqpMessage.BodyStream, System.Text.Encoding.UTF8);
                        string patch = reader.ReadToEnd();
                        twinProperties = JsonConvert.DeserializeObject <TwinCollection>(patch);
                    }
                    else if (correlationId.StartsWith(AmqpTwinMessageType.Get.ToString(), StringComparison.OrdinalIgnoreCase))
                    {
                        // This a response of a GET TWIN so return (set) the full twin
                        using var reader = new StreamReader(amqpMessage.BodyStream, System.Text.Encoding.UTF8);
                        string body       = reader.ReadToEnd();
                        var    properties = JsonConvert.DeserializeObject <TwinProperties>(body);
                        twin = new Twin(properties);
                    }
                    else if (correlationId.StartsWith(AmqpTwinMessageType.Patch.ToString(), StringComparison.OrdinalIgnoreCase))
                    {
                        // This can be used to coorelate success response with updating reported properties
                        // However currently we do not have it as request response style implementation
                        Logging.Info("Updated twin reported properties successfully", nameof(OnTwinChangesReceived));
                    }
                    else if (correlationId.StartsWith(AmqpTwinMessageType.Put.ToString(), StringComparison.OrdinalIgnoreCase))
                    {
                        // This is an acknowledgement received from service for subscribing to desired property updates
                        Logging.Info("Subscribed for twin successfully", nameof(OnTwinChangesReceived));
                    }
                    else
                    {
                        // This shouldn't happen
                        Logging.Info("Received a correlation Id for Twin operation that does not match Get, Patch or Put request", nameof(OnTwinChangesReceived));
                    }
                    _onTwinMessageReceived.Invoke(twin, correlationId, twinProperties, null);
                }
            }
            finally
            {
                if (Logging.IsEnabled)
                {
                    Logging.Exit(this, amqpMessage, $"{nameof(OnTwinChangesReceived)}");
                }
            }
        }
        public static Exception ToIotHubClientContract(Error error)
        {
            Exception retException;

            if (error == null)
            {
                retException = new IotHubException("Unknown error.");
                return(retException);
            }

            string message = error.Description;

            string trackingId = null;

            if (error.Info != null && error.Info.TryGetValue(AmqpIotConstants.TrackingId, out trackingId))
            {
                message = "{0}{1}{2}".FormatInvariant(message, Environment.NewLine, "Tracking Id:" + trackingId);
            }

            if (error.Condition.Equals(TimeoutError))
            {
                retException = new TimeoutException(message);
            }
            else if (error.Condition.Equals(AmqpErrorCode.NotFound))
            {
                retException = new DeviceNotFoundException(message, (Exception)null);
            }
            else if (error.Condition.Equals(AmqpErrorCode.NotImplemented))
            {
                retException = new NotSupportedException(message);
            }
            else if (error.Condition.Equals(MessageLockLostError))
            {
                retException = new DeviceMessageLockLostException(message);
            }
            else if (error.Condition.Equals(AmqpErrorCode.NotAllowed))
            {
                retException = new InvalidOperationException(message);
            }
            else if (error.Condition.Equals(AmqpErrorCode.UnauthorizedAccess))
            {
                retException = new UnauthorizedException(message);
            }
            else if (error.Condition.Equals(ArgumentError))
            {
                retException = new ArgumentException(message);
            }
            else if (error.Condition.Equals(ArgumentOutOfRangeError))
            {
                retException = new ArgumentOutOfRangeException(message);
            }
            else if (error.Condition.Equals(AmqpErrorCode.MessageSizeExceeded))
            {
                retException = new MessageTooLargeException(message);
            }
            else if (error.Condition.Equals(AmqpErrorCode.ResourceLimitExceeded))
            {
                // Note: The DeviceMaximumQueueDepthExceededException is not supposed to be thrown here as it is being mapped to the incorrect error code
                // Error code 403004 is only applicable to C2D (Service client); see https://docs.microsoft.com/en-us/azure/iot-hub/iot-hub-troubleshoot-error-403004-devicemaximumqueuedepthexceeded
                // Error code 403002 is applicable to D2C (Device client); see https://docs.microsoft.com/en-us/azure/iot-hub/iot-hub-troubleshoot-error-403002-iothubquotaexceeded
                // We have opted not to change the exception type thrown here since it will be a breaking change, alternatively, we are adding the correct exception type
                // as the inner exception.
                retException = new DeviceMaximumQueueDepthExceededException(
                    $"Please check the inner exception for more information.\n " +
                    $"The correct exception type is `{nameof(QuotaExceededException)}` " +
                    $"but since that is a breaking change to the current behavior in the SDK, you can refer to the inner exception " +
                    $"for more information. Exception message: {message}",
                    new QuotaExceededException(message));
            }
            else if (error.Condition.Equals(DeviceContainerThrottled))
            {
                retException = new IotHubThrottledException(message, null);
            }
            else if (error.Condition.Equals(IotHubSuspended))
            {
                retException = new IotHubSuspendedException(message);
            }
            else
            {
                retException = new IotHubException(message);
            }

            if (trackingId != null && retException is IotHubException)
            {
                var iotHubException = (IotHubException)retException;
                iotHubException.TrackingId = trackingId;
            }
            return(retException);
        }
Beispiel #14
0
        public static Exception ToIotHubClientContract(Error error)
        {
            Exception retException;

            if (error == null)
            {
                retException = new IotHubException("Unknown error.");
                return(retException);
            }

            string message = error.Description;

            string trackingId = null;

            if (error.Info != null && error.Info.TryGetValue(TrackingId, out trackingId))
            {
                message = "{0}{1}{2}".FormatInvariant(message, Environment.NewLine, "Tracking Id:" + trackingId);
            }

            if (error.Condition.Equals(TimeoutError))
            {
                retException = new TimeoutException(message);
            }
            else if (error.Condition.Equals(AmqpErrorCode.NotFound))
            {
                retException = new DeviceNotFoundException(message, (Exception)null);
            }
            else if (error.Condition.Equals(AmqpErrorCode.NotImplemented))
            {
                retException = new NotSupportedException(message);
            }
            else if (error.Condition.Equals(MessageLockLostError))
            {
                retException = new DeviceMessageLockLostException(message);
            }
            else if (error.Condition.Equals(AmqpErrorCode.NotAllowed))
            {
                retException = new InvalidOperationException(message);
            }
            else if (error.Condition.Equals(AmqpErrorCode.UnauthorizedAccess))
            {
                retException = new UnauthorizedException(message);
            }
            else if (error.Condition.Equals(ArgumentError))
            {
                retException = new ArgumentException(message);
            }
            else if (error.Condition.Equals(ArgumentOutOfRangeError))
            {
                retException = new ArgumentOutOfRangeException(message);
            }
            else if (error.Condition.Equals(AmqpErrorCode.MessageSizeExceeded))
            {
                retException = new MessageTooLargeException(message);
            }
            else if (error.Condition.Equals(AmqpErrorCode.ResourceLimitExceeded))
            {
                retException = new DeviceMaximumQueueDepthExceededException(message);
            }
            else if (error.Condition.Equals(DeviceAlreadyExists))
            {
                retException = new DeviceAlreadyExistsException(message, null);
            }
            else if (error.Condition.Equals(DeviceContainerThrottled))
            {
                retException = new IotHubThrottledException(message, null);
            }
            else if (error.Condition.Equals(IotHubSuspended))
            {
                retException = new IotHubSuspendedException(message);
            }
            else
            {
                retException = new IotHubException(message);
            }

            if (trackingId != null && retException is IotHubException)
            {
                IotHubException iotHubException = (IotHubException)retException;
                iotHubException.TrackingId = trackingId;
            }
            return(retException);
        }
Beispiel #15
0
 public static MessagingException ToMessagingException(this IotHubException ex)
 {
     return(new MessagingException("Error communicating with IoT Hub. Tracking id: " + ex.TrackingId, ex, ex.IsTransient, ex.TrackingId));
 }
 private void TwinMessageListener(Twin twin, string correlationId, TwinCollection twinCollection, IotHubException ex = default)
 {
     if (correlationId == null)
     {
         // This is desired property updates, so call the callback with TwinCollection.
         _onDesiredStatePatchListener(twinCollection);
     }
     else
     {
         if (correlationId.StartsWith(AmqpTwinMessageType.Get.ToString(), StringComparison.OrdinalIgnoreCase) ||
             correlationId.StartsWith(AmqpTwinMessageType.Patch.ToString(), StringComparison.OrdinalIgnoreCase))
         {
             // For Get and Patch, complete the task.
             if (_twinResponseCompletions.TryRemove(correlationId, out TaskCompletionSource <Twin> task))
             {
                 if (ex == default)
                 {
                     task.SetResult(twin);
                 }
                 else
                 {
                     task.SetException(ex);
                 }
             }
             else
             {
                 // This can happen if we received a message from service with correlation Id that was not set by SDK or does not exist in dictionary.
                 if (Logging.IsEnabled)
                 {
                     Logging.Info("Could not remove correlation id to complete the task awaiter for a twin operation.", nameof(TwinMessageListener));
                 }
             }
         }
     }
 }
        private void OnDesiredPropertyReceived(Twin twin, string correlationId, TwinCollection twinCollection, IotHubException ex = default)
        {
            Logging.Enter(this, twin, nameof(OnDesiredPropertyReceived));

            try
            {
                _twinMessageListener?.Invoke(twin, correlationId, twinCollection, ex);
            }
            finally
            {
                Logging.Exit(this, twin, nameof(OnDesiredPropertyReceived));
            }
        }