private async Task ReceiveMessageWithCallbackRecoveryAsync(
            TestDeviceType type,
            Client.TransportType transport,
            string faultType,
            string reason,
            TimeSpan delayInSec,
            string proxyAddress = null)
        {
            TestDeviceCallbackHandler testDeviceCallbackHandler = null;

            using var serviceClient = ServiceClient.CreateFromConnectionString(Configuration.IoTHub.ConnectionString);

            async Task InitOperationAsync(DeviceClient deviceClient, TestDevice testDevice)
            {
                await serviceClient.OpenAsync().ConfigureAwait(false);

                testDeviceCallbackHandler = new TestDeviceCallbackHandler(deviceClient, testDevice, Logger);
                await testDeviceCallbackHandler.SetMessageReceiveCallbackHandlerAsync().ConfigureAwait(false);
            }

            async Task TestOperationAsync(DeviceClient deviceClient, TestDevice testDevice)
            {
                var timeout = TimeSpan.FromSeconds(20);

                using var cts = new CancellationTokenSource(timeout);
                (Message message, string payload, string p1Value) = MessageReceiveE2ETests.ComposeC2dTestMessage(Logger);

                testDeviceCallbackHandler.ExpectedMessageSentByService = message;
                await serviceClient.SendAsync(testDevice.Id, message).ConfigureAwait(false);

                Client.Message receivedMessage = await deviceClient.ReceiveAsync(timeout).ConfigureAwait(false);

                await testDeviceCallbackHandler.WaitForReceiveMessageCallbackAsync(cts.Token).ConfigureAwait(false);

                receivedMessage.Should().BeNull();
            }

            Task CleanupOperationAsync()
            {
                serviceClient.CloseAsync();
                testDeviceCallbackHandler?.Dispose();
                return(Task.FromResult(true));
            }

            await FaultInjection
            .TestErrorInjectionAsync(
                DevicePrefix,
                type,
                transport,
                proxyAddress,
                faultType,
                reason,
                delayInSec,
                FaultInjection.DefaultFaultDuration,
                InitOperationAsync,
                TestOperationAsync,
                CleanupOperationAsync,
                Logger)
            .ConfigureAwait(false);
        }
        private async Task Twin_DeviceDesiredPropertyUpdateRecoveryAsync(
            Client.TransportType transport,
            string faultType,
            string reason,
            int delayInSec)
        {
            TestDeviceCallbackHandler testDeviceCallbackHandler = null;
            var registryManager = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString);

            using var cts = new CancellationTokenSource(FaultInjection.RecoveryTimeMilliseconds);

            var propName = Guid.NewGuid().ToString();
            var props    = new TwinCollection();

            // Configure the callback and start accepting twin changes.
            Func <DeviceClient, TestDevice, Task> initOperation = async(deviceClient, testDevice) =>
            {
                testDeviceCallbackHandler = new TestDeviceCallbackHandler(deviceClient, Logger);
                await testDeviceCallbackHandler.SetTwinPropertyUpdateCallbackHandlerAsync(propName).ConfigureAwait(false);
            };

            // Change the twin from the service side and verify the device received it.
            Func <DeviceClient, TestDevice, Task> testOperation = async(deviceClient, testDevice) =>
            {
                var propValue = Guid.NewGuid().ToString();
                testDeviceCallbackHandler.ExpectedTwinPropertyValue = propValue;

                Logger.Trace($"{nameof(Twin_DeviceDesiredPropertyUpdateRecoveryAsync)}: name={propName}, value={propValue}");

                Task serviceSendTask  = RegistryManagerUpdateDesiredPropertyAsync(testDevice.Id, propName, propValue);
                Task twinReceivedTask = testDeviceCallbackHandler.WaitForTwinCallbackAsync(cts.Token);

                var tasks = new List <Task>()
                {
                    serviceSendTask, twinReceivedTask
                };
                while (tasks.Count > 0)
                {
                    Task completedTask = await Task.WhenAny(tasks).ConfigureAwait(false);

                    completedTask.GetAwaiter().GetResult();
                    tasks.Remove(completedTask);
                }
            };

            await FaultInjection
            .TestErrorInjectionAsync(
                s_devicePrefix,
                TestDeviceType.Sasl,
                transport,
                faultType,
                reason,
                delayInSec,
                FaultInjection.DefaultDurationInSec,
                initOperation,
                testOperation,
                () => { return(Task.FromResult(false)); },
                Logger)
            .ConfigureAwait(false);
        }
        private async Task SendMethodAndRespondRecoveryAsync(Client.TransportType transport, string faultType, string reason, TimeSpan delayInSec, string proxyAddress = null)
        {
            TestDeviceCallbackHandler testDeviceCallbackHandler = null;

            using var cts = new CancellationTokenSource(FaultInjection.RecoveryTime);

            // Configure the callback and start accepting method calls.
            async Task InitOperationAsync(DeviceClient deviceClient, TestDevice testDevice)
            {
                testDeviceCallbackHandler = new TestDeviceCallbackHandler(deviceClient, testDevice, Logger);
                await testDeviceCallbackHandler
                .SetDeviceReceiveMethodAsync(MethodName, DeviceResponseJson, ServiceRequestJson)
                .ConfigureAwait(false);
            }

            // Call the method from the service side and verify the device received the call.
            async Task TestOperationAsync(DeviceClient deviceClient, TestDevice testDevice)
            {
                Task serviceSendTask    = ServiceSendMethodAndVerifyResponseAsync(testDevice.Id, MethodName, DeviceResponseJson, ServiceRequestJson);
                Task methodReceivedTask = testDeviceCallbackHandler.WaitForMethodCallbackAsync(cts.Token);

                var tasks = new List <Task>()
                {
                    serviceSendTask, methodReceivedTask
                };

                while (tasks.Count > 0)
                {
                    Task completedTask = await Task.WhenAny(tasks).ConfigureAwait(false);

                    completedTask.GetAwaiter().GetResult();
                    tasks.Remove(completedTask);
                }
            }

            // Cleanup references.
            Task CleanupOperationAsync()
            {
                testDeviceCallbackHandler?.Dispose();
                return(Task.FromResult(false));
            }

            await FaultInjection
            .TestErrorInjectionAsync(
                DevicePrefix,
                TestDeviceType.Sasl,
                transport,
                proxyAddress,
                faultType,
                reason,
                delayInSec,
                FaultInjection.DefaultFaultDelay,
                InitOperationAsync,
                TestOperationAsync,
                CleanupOperationAsync,
                Logger)
            .ConfigureAwait(false);
        }
        private async Task ReceiveMessageRecovery(
            TestDeviceType type,
            Client.TransportType transport,
            string faultType,
            string reason,
            TimeSpan delayInSec,
            string proxyAddress = null)
        {
            using var serviceClient = ServiceClient.CreateFromConnectionString(Configuration.IoTHub.ConnectionString);
            async Task InitOperationAsync(DeviceClient deviceClient, TestDevice testDevice)
            {
                await serviceClient.OpenAsync().ConfigureAwait(false);

                // For Mqtt - the device needs to have subscribed to the devicebound topic, in order for IoT Hub to deliver messages to the device.
                // For this reason we will make a "fake" ReceiveAsync() call, which will result in the device subscribing to the c2d topic.
                // Note: We need this "fake" ReceiveAsync() call even though we (SDK default) CONNECT with a CleanSession flag set to 0.
                // This is because this test device is newly created, and it has never subscribed to IoT hub c2d topic.
                // Hence, IoT hub doesn't know about its CleanSession preference yet.
                if (transport == Client.TransportType.Mqtt_Tcp_Only ||
                    transport == Client.TransportType.Mqtt_WebSocket_Only)
                {
                    await deviceClient.ReceiveAsync(TimeSpan.FromSeconds(5)).ConfigureAwait(false);
                }
            }

            async Task TestOperationAsync(DeviceClient deviceClient, TestDevice testDevice)
            {
                (Message message, string payload, string p1Value) = MessageReceiveE2ETests.ComposeC2dTestMessage(Logger);
                await serviceClient.SendAsync(testDevice.Id, message).ConfigureAwait(false);

                await MessageReceiveE2ETests.VerifyReceivedC2DMessageAsync(transport, deviceClient, testDevice.Id, message, payload, Logger).ConfigureAwait(false);
            }

            Task CleanupOperationAsync()
            {
                return(serviceClient.CloseAsync());
            }

            await FaultInjection
            .TestErrorInjectionAsync(
                DevicePrefix,
                type,
                transport,
                proxyAddress,
                faultType,
                reason,
                delayInSec,
                FaultInjection.DefaultFaultDuration,
                InitOperationAsync,
                TestOperationAsync,
                CleanupOperationAsync,
                Logger)
            .ConfigureAwait(false);
        }
        private async Task ReceiveMessageRecovery(
            TestDeviceType type,
            Client.TransportType transport,
            string faultType,
            string reason,
            int delayInSec,
            string proxyAddress = null)
        {
            using (ServiceClient serviceClient = ServiceClient.CreateFromConnectionString(Configuration.IoTHub.ConnectionString))
            {
                Func <DeviceClient, TestDevice, Task> init = async(deviceClient, testDevice) =>
                {
                    await serviceClient.OpenAsync().ConfigureAwait(false);

                    if (transport == Client.TransportType.Mqtt_Tcp_Only ||
                        transport == Client.TransportType.Mqtt_WebSocket_Only)
                    {
                        // Dummy ReceiveAsync to ensure mqtt subscription registration before SendAsync() is called on service client.
                        await deviceClient.ReceiveAsync(TimeSpan.FromSeconds(5)).ConfigureAwait(false);
                    }
                };

                Func <DeviceClient, TestDevice, Task> testOperation = async(deviceClient, testDevice) =>
                {
                    (Message message, string payload, string p1Value) = MessageReceiveE2ETests.ComposeC2dTestMessage(Logger);
                    await serviceClient.SendAsync(testDevice.Id, message).ConfigureAwait(false);

                    await MessageReceiveE2ETests.VerifyReceivedC2DMessageAsync(transport, deviceClient, testDevice.Id, message, payload, Logger).ConfigureAwait(false);
                };

                Func <Task> cleanupOperation = () =>
                {
                    return(serviceClient.CloseAsync());
                };

                await FaultInjection.TestErrorInjectionAsync(
                    DevicePrefix,
                    type,
                    transport,
                    proxyAddress,
                    faultType,
                    reason,
                    delayInSec,
                    FaultInjection.DefaultDurationInSec,
                    init,
                    testOperation,
                    cleanupOperation,
                    Logger).ConfigureAwait(false);
            }
        }
        private async Task SendMethodAndRespondRecoveryAsync(Client.TransportType transport, string faultType, string reason, int delayInSec)
        {
            TestDeviceCallbackHandler testDeviceCallbackHandler = null;

            using var cts = new CancellationTokenSource(FaultInjection.RecoveryTimeMilliseconds);

            // Configure the callback and start accepting method calls.
            Func <DeviceClient, TestDevice, Task> initOperation = async(deviceClient, testDevice) =>
            {
                testDeviceCallbackHandler = new TestDeviceCallbackHandler(deviceClient, Logger);
                await testDeviceCallbackHandler
                .SetDeviceReceiveMethodAsync(MethodName, DeviceResponseJson, ServiceRequestJson)
                .ConfigureAwait(false);
            };

            // Call the method from the service side and verify the device received the call.
            Func <DeviceClient, TestDevice, Task> testOperation = async(deviceClient, testDevice) =>
            {
                Task serviceSendTask    = ServiceSendMethodAndVerifyResponseAsync(testDevice.Id, MethodName, DeviceResponseJson, ServiceRequestJson);
                Task methodReceivedTask = testDeviceCallbackHandler.WaitForMethodCallbackAsync(cts.Token);

                var tasks = new List <Task>()
                {
                    serviceSendTask, methodReceivedTask
                };
                while (tasks.Count > 0)
                {
                    Task completedTask = await Task.WhenAny(tasks).ConfigureAwait(false);

                    completedTask.GetAwaiter().GetResult();
                    tasks.Remove(completedTask);
                }
            };

            await FaultInjection
            .TestErrorInjectionAsync(
                DevicePrefix,
                TestDeviceType.Sasl,
                transport,
                faultType,
                reason,
                delayInSec,
                FaultInjection.DefaultDelayInSec,
                initOperation,
                testOperation,
                () => { return(Task.FromResult <bool>(false)); },
                Logger)
            .ConfigureAwait(false);
        }
        private async Task Twin_DeviceReportedPropertiesRecovery(
            Client.TransportType transport,
            string faultType,
            string reason,
            int delayInSec,
            string proxyAddress = null)
        {
            var propName = Guid.NewGuid().ToString();
            var props    = new TwinCollection();

            Func <DeviceClient, TestDevice, Task> testOperation = async(deviceClient, testDevice) =>
            {
                var propValue = Guid.NewGuid().ToString();
                props[propName] = propValue;

                await deviceClient.UpdateReportedPropertiesAsync(props).ConfigureAwait(false);

                Twin deviceTwin = await deviceClient.GetTwinAsync().ConfigureAwait(false);

                Assert.IsNotNull(deviceTwin, $"{nameof(deviceTwin)} is null");
                Assert.IsNotNull(deviceTwin.Properties, $"{nameof(deviceTwin)}.Properties is null");
                Assert.IsNotNull(deviceTwin.Properties.Reported, $"{nameof(deviceTwin)}.Properties.Reported is null");
                Assert.IsNotNull(deviceTwin.Properties.Reported[propName], $"{nameof(deviceTwin)}.Properties.Reported[{nameof(propName)}] is null");
                Assert.AreEqual <string>(deviceTwin.Properties.Reported[propName].ToString(), propValue);
            };

            await FaultInjection
            .TestErrorInjectionAsync(
                s_devicePrefix,
                TestDeviceType.Sasl,
                transport,
                proxyAddress,
                faultType,
                reason,
                delayInSec,
                FaultInjection.DefaultDurationInSec,
                (d, t) => { return(Task.FromResult <bool>(false)); },
                testOperation,
                () => { return(Task.FromResult <bool>(false)); },
                Logger)
            .ConfigureAwait(false);
        }
        internal async Task SendMessageRecoveryAsync(
            TestDeviceType type,
            Client.TransportType transport,
            string faultType,
            string reason,
            TimeSpan delayInSec,
            TimeSpan durationInSec           = default,
            TimeSpan retryDurationInMilliSec = default,
            string proxyAddress = null)
        {
            TimeSpan operationTimeoutInMilliSecs       = retryDurationInMilliSec == TimeSpan.Zero ? FaultInjection.RecoveryTime : retryDurationInMilliSec;
            Func <DeviceClient, TestDevice, Task> init = (deviceClient, testDevice) =>
            {
                deviceClient.OperationTimeoutInMilliseconds = (uint)retryDurationInMilliSec.TotalMilliseconds;
                return(Task.FromResult(0));
            };

            Func <DeviceClient, TestDevice, Task> testOperation = async(deviceClient, testDevice) =>
            {
                (Client.Message testMessage, string payload, string p1Value) = MessageSendE2ETests.ComposeD2cTestMessage(Logger);
                await deviceClient.SendEventAsync(testMessage).ConfigureAwait(false);
            };

            await FaultInjection
            .TestErrorInjectionAsync(
                _devicePrefix,
                type,
                transport,
                proxyAddress,
                faultType,
                reason,
                delayInSec,
                durationInSec == TimeSpan.Zero?FaultInjection.DefaultFaultDuration : durationInSec,
                init,
                testOperation,
                () => { return(Task.FromResult(false)); },
                Logger)
            .ConfigureAwait(false);
        }