private async Task DisposeMessageAsync(string lockToken, AmqpIotDisposeActions outcome, CancellationToken cancellationToken)
        {
            if (Logging.IsEnabled)
            {
                Logging.Enter(this, outcome, nameof(DisposeMessageAsync));
            }

            try
            {
                // Currently, the same mechanism is used for sending feedback for C2D messages and events received by modules.
                // However, devices only support C2D messages (they cannot receive events), and modules only support receiving events
                // (they cannot receive C2D messages). So we use this to distinguish whether to dispose the message (i.e. send outcome on)
                // the DeviceBoundReceivingLink or the EventsReceivingLink.
                // If this changes (i.e. modules are able to receive C2D messages, or devices are able to receive telemetry), this logic
                // will have to be updated.
                using var ctb = new CancellationTokenBundle(_operationTimeout, cancellationToken);

                AmqpIotOutcome disposeOutcome = await _amqpUnit.DisposeMessageAsync(lockToken, outcome, ctb.Token).ConfigureAwait(false);

                disposeOutcome.ThrowIfError();
            }
            finally
            {
                if (Logging.IsEnabled)
                {
                    Logging.Exit(this, outcome, nameof(DisposeMessageAsync));
                }
            }
        }
        public override async Task EnableTwinPatchAsync(CancellationToken cancellationToken)
        {
            if (Logging.IsEnabled)
            {
                Logging.Enter(this, cancellationToken, nameof(EnableTwinPatchAsync));
            }

            try
            {
                cancellationToken.ThrowIfCancellationRequested();
                string correlationId = AmqpTwinMessageType.Put + Guid.NewGuid().ToString();

                using var ctb = new CancellationTokenBundle(_operationTimeout, cancellationToken);
                await _amqpUnit
                .SendTwinMessageAsync(AmqpTwinMessageType.Put, correlationId, null, ctb.Token)
                .ConfigureAwait(false);
            }
            finally
            {
                if (Logging.IsEnabled)
                {
                    Logging.Exit(this, cancellationToken, nameof(EnableTwinPatchAsync));
                }
            }
        }
        public override async Task EnableEventReceiveAsync(bool isAnEdgeModule, CancellationToken cancellationToken)
        {
            // If an AMQP transport is opened as a module twin instead of an Edge module we need
            // to enable the deviceBound operations instead of the event receiver link
            if (isAnEdgeModule)
            {
                if (Logging.IsEnabled)
                {
                    Logging.Enter(this, cancellationToken, nameof(EnableEventReceiveAsync));
                }

                try
                {
                    cancellationToken.ThrowIfCancellationRequested();

                    using var ctb = new CancellationTokenBundle(_operationTimeout, cancellationToken);

                    await _amqpUnit.EnableEventReceiveAsync(ctb.Token).ConfigureAwait(false);
                }
                finally
                {
                    if (Logging.IsEnabled)
                    {
                        Logging.Exit(this, cancellationToken, nameof(EnableEventReceiveAsync));
                    }
                }
            }
            else
            {
                await EnableReceiveMessageAsync(cancellationToken).ConfigureAwait(false);
            }
        }
        public override async Task <Message> ReceiveAsync(CancellationToken cancellationToken)
        {
            if (Logging.IsEnabled)
            {
                Logging.Enter(this, cancellationToken, nameof(ReceiveAsync));
            }

            Message message;

            while (true)
            {
                cancellationToken.ThrowIfCancellationRequested();

                using var ctb = new CancellationTokenBundle(_transportSettings.DefaultReceiveTimeout, cancellationToken);
                message       = await _amqpUnit.ReceiveMessageAsync(ctb.Token).ConfigureAwait(false);

                if (message != null)
                {
                    break;
                }
            }

            if (Logging.IsEnabled)
            {
                Logging.Exit(this, cancellationToken, cancellationToken, nameof(ReceiveAsync));
            }

            return(message);
        }
        public override async Task SendMethodResponseAsync(MethodResponseInternal methodResponse, CancellationToken cancellationToken)
        {
            if (Logging.IsEnabled)
            {
                Logging.Enter(this, methodResponse, cancellationToken, nameof(SendMethodResponseAsync));
            }

            try
            {
                cancellationToken.ThrowIfCancellationRequested();

                using var ctb = new CancellationTokenBundle(_operationTimeout, cancellationToken);
                AmqpIotOutcome amqpIotOutcome = await _amqpUnit
                                                .SendMethodResponseAsync(methodResponse, ctb.Token)
                                                .ConfigureAwait(false);

                if (amqpIotOutcome != null)
                {
                    amqpIotOutcome.ThrowIfNotAccepted();
                }
            }
            finally
            {
                if (Logging.IsEnabled)
                {
                    Logging.Exit(this, methodResponse, cancellationToken, nameof(SendMethodResponseAsync));
                }
            }
        }
        public override async Task CloseAsync(CancellationToken cancellationToken)
        {
            if (Logging.IsEnabled)
            {
                Logging.Enter(this, nameof(CloseAsync));
            }

            lock (_lock)
            {
                _closed = true;
            }

            try
            {
                cancellationToken.ThrowIfCancellationRequested();

                using var ctb = new CancellationTokenBundle(_operationTimeout, cancellationToken);
                await _amqpUnit.CloseAsync(ctb.Token).ConfigureAwait(false);
            }
            finally
            {
                OnTransportClosedGracefully();
                if (Logging.IsEnabled)
                {
                    Logging.Exit(this, nameof(CloseAsync));
                }
            }
        }
        public override async Task OpenAsync(CancellationToken cancellationToken)
        {
            if (Logging.IsEnabled)
            {
                Logging.Enter(this, cancellationToken, nameof(OpenAsync));
            }

            cancellationToken.ThrowIfCancellationRequested();
            lock (_lock)
            {
                if (_disposed)
                {
                    return;
                }

                _closed = false;
            }

            try
            {
                using var ctb = new CancellationTokenBundle(_operationTimeout, cancellationToken);
                await _amqpUnit.OpenAsync(ctb.Token).ConfigureAwait(false);
            }
            finally
            {
                if (Logging.IsEnabled)
                {
                    Logging.Exit(this, cancellationToken, nameof(OpenAsync));
                }
            }
        }
        private async Task <Twin> RoundTripTwinMessageAsync(
            AmqpTwinMessageType amqpTwinMessageType,
            TwinCollection reportedProperties,
            CancellationToken cancellationToken)
        {
            if (Logging.IsEnabled)
            {
                Logging.Enter(this, cancellationToken, nameof(RoundTripTwinMessageAsync));
            }

            string correlationId = amqpTwinMessageType + Guid.NewGuid().ToString();
            Twin   response      = null;

            try
            {
                cancellationToken.ThrowIfCancellationRequested();
                var taskCompletionSource = new TaskCompletionSource <Twin>();
                _twinResponseCompletions[correlationId] = taskCompletionSource;

                using var ctb = new CancellationTokenBundle(_operationTimeout, cancellationToken);
                await _amqpUnit.SendTwinMessageAsync(amqpTwinMessageType, correlationId, reportedProperties, ctb.Token).ConfigureAwait(false);

                Task <Twin> receivingTask = taskCompletionSource.Task;

                if (await Task
                    .WhenAny(receivingTask, Task.Delay(TimeSpan.FromSeconds(ResponseTimeoutInSeconds), cancellationToken))
                    .ConfigureAwait(false) == receivingTask)
                {
                    if (receivingTask.Exception?.InnerException != null)
                    {
                        throw receivingTask.Exception.InnerException;
                    }

                    // Task completed within timeout.
                    // Consider that the task may have faulted or been canceled.
                    // We re-await the task so that any exceptions/cancellation is re-thrown.
                    response = await receivingTask.ConfigureAwait(false);
                }
                else
                {
                    // Timeout happen
                    throw new TimeoutException();
                }
            }
            finally
            {
                _twinResponseCompletions.TryRemove(correlationId, out _);
                if (Logging.IsEnabled)
                {
                    Logging.Exit(this, cancellationToken, nameof(RoundTripTwinMessageAsync));
                }
            }

            return(response);
        }
        public override async Task DisableTwinPatchAsync(CancellationToken cancellationToken)
        {
            try
            {
                Logging.Enter(this, cancellationToken, nameof(DisableTwinPatchAsync));

                cancellationToken.ThrowIfCancellationRequested();

                using var ctb = new CancellationTokenBundle(_operationTimeout, cancellationToken);
                await _amqpUnit.DisableTwinLinksAsync(ctb.Token).ConfigureAwait(false);
            }
            finally
            {
                if (Logging.IsEnabled)
                {
                    Logging.Exit(this, cancellationToken, nameof(DisableTwinPatchAsync));
                }
            }
        }
        public override async Task EnableReceiveMessageAsync(CancellationToken cancellationToken)
        {
            if (Logging.IsEnabled)
            {
                Logging.Enter(this, cancellationToken, nameof(EnableReceiveMessageAsync));
            }

            try
            {
                cancellationToken.ThrowIfCancellationRequested();

                using var ctb = new CancellationTokenBundle(_operationTimeout, cancellationToken);
                await _amqpUnit.EnableReceiveMessageAsync(ctb.Token).ConfigureAwait(false);
            }
            finally
            {
                if (Logging.IsEnabled)
                {
                    Logging.Exit(this, cancellationToken, nameof(EnableReceiveMessageAsync));
                }
            }
        }
        public override async Task SendEventAsync(IEnumerable <Message> messages, CancellationToken cancellationToken)
        {
            if (Logging.IsEnabled)
            {
                Logging.Enter(this, messages, cancellationToken, nameof(SendEventAsync));
            }

            try
            {
                cancellationToken.ThrowIfCancellationRequested();

                using var ctb = new CancellationTokenBundle(_operationTimeout, cancellationToken);
                await _amqpUnit.SendEventsAsync(messages, ctb.Token).ConfigureAwait(false);
            }
            finally
            {
                if (Logging.IsEnabled)
                {
                    Logging.Exit(this, messages, cancellationToken, nameof(SendEventAsync));
                }
            }
        }
        public override async Task SendEventAsync(Message message, CancellationToken cancellationToken)
        {
            if (Logging.IsEnabled)
            {
                Logging.Enter(this, message, cancellationToken, nameof(SendEventAsync));
            }

            try
            {
                cancellationToken.ThrowIfCancellationRequested();

                using var ctb = new CancellationTokenBundle(_operationTimeout, cancellationToken);
                AmqpIotOutcome amqpIotOutcome = await _amqpUnit.SendEventAsync(message, ctb.Token).ConfigureAwait(false);

                amqpIotOutcome?.ThrowIfNotAccepted();
            }
            finally
            {
                if (Logging.IsEnabled)
                {
                    Logging.Exit(this, message, cancellationToken, nameof(SendEventAsync));
                }
            }
        }