iothub/device/src/Transport/AmqpIot/AmqpIotErrorAdapter.cs (249 lines of code) (raw):
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
using Microsoft.Azure.Devices.Common;
using Microsoft.Azure.Devices.Client.Exceptions;
using Microsoft.Azure.Amqp;
using Microsoft.Azure.Amqp.Encoding;
using Microsoft.Azure.Amqp.Framing;
namespace Microsoft.Azure.Devices.Client.Transport.AmqpIot
{
internal static class AmqpIotErrorAdapter
{
public static readonly AmqpSymbol TimeoutName = AmqpIotConstants.Vendor + ":timeout";
public static readonly AmqpSymbol StackTraceName = AmqpIotConstants.Vendor + ":stack-trace";
// Error codes
public static readonly AmqpSymbol DeadLetterName = AmqpIotConstants.Vendor + ":dead-letter";
public const string DeadLetterReasonHeader = "DeadLetterReason";
public const string DeadLetterErrorDescriptionHeader = "DeadLetterErrorDescription";
public static readonly AmqpSymbol TimeoutError = AmqpIotConstants.Vendor + ":timeout";
public static readonly AmqpSymbol MessageLockLostError = AmqpIotConstants.Vendor + ":message-lock-lost";
public static readonly AmqpSymbol IotHubNotFoundError = AmqpIotConstants.Vendor + ":iot-hub-not-found-error";
public static readonly AmqpSymbol ArgumentError = AmqpIotConstants.Vendor + ":argument-error";
public static readonly AmqpSymbol ArgumentOutOfRangeError = AmqpIotConstants.Vendor + ":argument-out-of-range";
public static readonly AmqpSymbol DeviceContainerThrottled = AmqpIotConstants.Vendor + ":device-container-throttled";
public static readonly AmqpSymbol IotHubSuspended = AmqpIotConstants.Vendor + ":iot-hub-suspended";
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;
}
public static Exception ToIotHubClientContract(AmqpException amqpException)
{
Error error = amqpException.Error;
AmqpSymbol amqpSymbol = error.Condition;
string message = error.ToString();
// Generic AMQP error
if (Equals(AmqpErrorCode.InternalError, amqpSymbol))
{
return new IotHubCommunicationException(message, amqpException);
}
else if (Equals(AmqpErrorCode.NotFound, amqpSymbol))
{
return new DeviceNotFoundException(message, amqpException);
}
else if (Equals(AmqpErrorCode.UnauthorizedAccess, amqpSymbol))
{
return new UnauthorizedException(message, amqpException);
}
else if (Equals(AmqpErrorCode.DecodeError, amqpSymbol))
{
return new IotHubException(message, amqpException);
}
else if (Equals(AmqpErrorCode.ResourceLimitExceeded, amqpSymbol))
{
return new IotHubException(message, amqpException);
}
else if (Equals(AmqpErrorCode.NotAllowed, amqpSymbol))
{
return new InvalidOperationException(message, amqpException);
}
else if (Equals(AmqpErrorCode.InvalidField, amqpSymbol))
{
return new InvalidOperationException(message, amqpException);
}
else if (Equals(AmqpErrorCode.NotImplemented, amqpSymbol))
{
return new NotSupportedException(message, amqpException);
}
else if (Equals(AmqpErrorCode.ResourceLocked, amqpSymbol))
{
return new AmqpIotResourceException(message, amqpException, true);
}
else if (Equals(AmqpErrorCode.PreconditionFailed, amqpSymbol))
{
return new IotHubException(message, amqpException);
}
else if (Equals(AmqpErrorCode.ResourceDeleted, amqpSymbol))
{
return new IotHubException(message, amqpException);
}
else if (Equals(AmqpErrorCode.IllegalState, amqpSymbol))
{
return new IotHubException(message, amqpException);
}
else if (Equals(AmqpErrorCode.FrameSizeTooSmall, amqpSymbol))
{
return new IotHubException(message, amqpException);
}
// AMQP Connection Error
else if (Equals(AmqpErrorCode.ConnectionForced, amqpSymbol))
{
return new AmqpIotResourceException(message, amqpException, true);
}
else if (Equals(AmqpErrorCode.FramingError, amqpSymbol))
{
return new AmqpIotResourceException(message, amqpException, true);
}
else if (Equals(AmqpErrorCode.ConnectionRedirect, amqpSymbol))
{
return new AmqpIotResourceException(message, amqpException, true);
}
// AMQP Session Error
else if (Equals(AmqpErrorCode.WindowViolation, amqpSymbol))
{
return new AmqpIotResourceException(message, amqpException, true);
}
else if (Equals(AmqpErrorCode.ErrantLink, amqpSymbol))
{
return new AmqpIotResourceException(message, amqpException, true);
}
else if (Equals(AmqpErrorCode.HandleInUse, amqpSymbol))
{
return new AmqpIotResourceException(message, amqpException, true);
}
else if (Equals(AmqpErrorCode.UnattachedHandle, amqpSymbol))
{
return new AmqpIotResourceException(message, amqpException, true);
}
// AMQP Link Error
else if (Equals(AmqpErrorCode.DetachForced, amqpSymbol))
{
return new AmqpIotResourceException(message, amqpException, true);
}
else if (Equals(AmqpErrorCode.TransferLimitExceeded, amqpSymbol))
{
return new AmqpIotResourceException(message, amqpException, true);
}
else if (Equals(AmqpErrorCode.MessageSizeExceeded, amqpSymbol))
{
return new MessageTooLargeException(message, amqpException);
}
else if (Equals(AmqpErrorCode.LinkRedirect, amqpSymbol))
{
return new AmqpIotResourceException(message, amqpException, true);
}
else if (Equals(AmqpErrorCode.Stolen, amqpSymbol))
{
return new AmqpIotResourceException(message, amqpException, true);
}
// AMQP Transaction Error
else if (Equals(AmqpErrorCode.TransactionUnknownId, amqpSymbol))
{
return new IotHubException(message, amqpException);
}
else if (Equals(AmqpErrorCode.TransactionRollback, amqpSymbol))
{
return new IotHubCommunicationException(message, amqpException);
}
else if (Equals(AmqpErrorCode.TransactionTimeout, amqpSymbol))
{
return new IotHubCommunicationException(message, amqpException);
}
else
{
return new IotHubException(message, amqpException);
}
}
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 = $"{message}\r\nTracking 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/azure/iot-hub/iot-hub-troubleshoot-error-403004-devicemaximumqueuedepthexceeded
// Error code 403002 is applicable to D2C (Device client); see https://docs.microsoft.com/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 hubEx)
{
hubEx.TrackingId = trackingId;
}
return retException;
}
}
}