public IncomingOperationEventArgs(Operation operation, CirceComConnection connection)
        {
            Guard.NotNull(operation, nameof(operation));
            Guard.NotNull(connection, nameof(connection));

            Operation  = operation;
            Connection = connection;
        }
        public IncomingOperationEventArgs([NotNull] Operation operation, [NotNull] CirceComConnection connection)
        {
            Guard.NotNull(operation, nameof(operation));
            Guard.NotNull(connection, nameof(connection));

            Operation = operation;
            Connection = connection;
        }
        public void Run()
        {
            var stateMachine = new AssignmentStateMachine(new PhaseWaitingForConnection());
            CirceComConnection connection = null;

            Log.Info($"Connecting to mediator on {startupArguments.ComPortName}...");
            stateMachine.ExecuteIfInPhase<PhaseWaitingForConnection>(phase =>
            {
                connection = new CirceComConnection(startupArguments.ComPortName);
                connection.OperationReceived +=
                    (sender, eventArgs) => ConnectionOperationReceived(eventArgs, stateMachine);
                connection.Open();
                connection.Send(new LoginOperation());

                return new PhaseWaitingForLoginResponse();
            });

            Log.Info("Waiting for login response...");
            var readyForDeviceSetup = stateMachine.WaitForPhase<PhaseReadyForDeviceSetup>();
            Log.Info($"Mediator status in login response: {readyForDeviceSetup.MediatorStatus}.");

            if (!IsConfiguringMediator &&
                readyForDeviceSetup.MediatorStatus == KnownMediatorStatusCode.MediatorUnconfigured)
            {
                Log.Info("ERROR: Connected to unconfigured mediator. Please configure mediator first.");
                return;
            }

            Log.Info("Sending address assignment...");
            stateMachine.ExecuteIfInPhase<PhaseReadyForDeviceSetup>(phase1 =>
            {
                connection.Send(new DeviceSetupOperation(startupArguments.NewAddress)
                {
                    DestinationAddress = startupArguments.OldAddress,
                    Capabilities = startupArguments.Capabilities
                });

                return new PhaseWaitingForSetupResponse(startupArguments.NewAddress);
            });

            Log.Info("Waiting for response from new device...");
            var assignmentCompleted = stateMachine.WaitForPhase<PhaseAssignmentCompleted>();

            Log.Info(assignmentCompleted.MediatorStatus == KnownMediatorStatusCode.MediatorUnconfigured
                ? "ERROR: Failed to assign mediator address."
                : "Received response on new address.");

            Log.Info("Disconnecting...");
            connection.Send(new LogoutOperation());
        }
        private static CirceComConnection TryCreateOpenedConnection([NotNull] string portName,
            [CanBeNull] Action<CirceComConnection> attachHandlersCallback,
            [CanBeNull] Action<CirceComConnection> detachHandlersCallback)
        {
            CirceComConnection connection = null;
            Exception error;
            try
            {
                connection = new CirceComConnection(portName);

                attachHandlersCallback?.Invoke(connection);

                connection.Open();
                return connection;
            }
            catch (InvalidOperationException ex)
            {
                // This may happen when you unplug the serial port cable while writing to the port.
                // The exception indicates that the port has been closed, so writing fails.
                error = ex;
            }
            catch (UnauthorizedAccessException ex)
            {
                error = ex;
            }
            catch (SecurityException ex)
            {
                error = ex;
            }
            catch (IOException ex)
            {
                error = ex;
            }

            Log.Debug($"Failed to open port {portName}: {error.GetType()}: {error.Message}");
            if (connection != null)
            {
                detachHandlersCallback?.Invoke(connection);

                connection.Dispose();
            }

            return null;
        }
        private void CreateSession()
        {
            Log.Debug("Entering CreateSession.");

            // Block until we have successfully sent a Login operation.
            Exception connectError;
            CirceComConnection newConnection;
            do
            {
                // Block until we have obtained a COM port.
                bool raisedWaitEvent = false;

                Log.Debug("Entering wait loop until at least one port found.");

#if DEBUGGING_HACKS
                string comPortName = "COM3"; // Force single COM port
#else
                string comPortName = portRotator.GetNextPortName();
#endif

                while (comPortName == null)
                {
                    if (disposeRequestedWaitHandle.IsSet)
                    {
                        Log.Debug("CreateSession: Disposal has been requested while scanning for ports.");
                        OnStateChanged(ControllerConnectionState.Disconnected, null);
                        return;
                    }

                    if (!raisedWaitEvent)
                    {
                        OnStateChanged(ControllerConnectionState.WaitingForComPort, null);
                        raisedWaitEvent = true;
                    }

                    Thread.Sleep(TryNextComPortDelayInMilliseconds);
                    comPortName = portRotator.GetNextPortName();
                }

                if (disposeRequestedWaitHandle.IsSet)
                {
                    Log.Debug("CreateSession: Disposal has been requested after port detection.");
                    OnStateChanged(ControllerConnectionState.Disconnected, null);
                    return;
                }

                OnStateChanged(ControllerConnectionState.Connecting, comPortName);

                seenProtocolVersionMismatch.Value = null;

                newConnection = new CirceComConnection(comPortName);
                newConnection.PacketSending += NewConnectionOnPacketSending;
                newConnection.PacketReceived += NewConnectionOnPacketReceived;
                newConnection.OperationReceived += NewConnectionOnOperationReceived;

                // The caller has closed a previous connection and paused the outgoing queue. So we
                // are guaranteed to be the only thread that is writing to the port. So locking
                // for exclusive write access is not needed here.
                if (TryDirectSend(newConnection, true, new LoginOperation(), out connectError))
                {
                    // A KeepAlive response should be received shortly, so we'll set the lifetime to one 
                    // second in the past, which leaves a single second for the mediator to respond.
                    lastRefreshTimeInUtc.Value = SystemContext.UtcNow().AddSeconds(-1);
                }

                if (connectError != null)
                {
                    Log.Warn($"Failed to open port {comPortName} and send Login operation.", connectError);

                    // Because the lifetime of this instance is longer than the lifetime of a connection, it
                    // is critical to detach event handlers so the garbage collector can run. Failing to detach
                    // event handlers will cause a memory leak that increases at each reconnect.
                    newConnection.PacketSending -= NewConnectionOnPacketSending;
                    newConnection.PacketReceived -= NewConnectionOnPacketReceived;
                    newConnection.OperationReceived -= NewConnectionOnOperationReceived;
                    newConnection.Dispose();

                    OnStateChanged(ControllerConnectionState.Disconnected, comPortName);
                }
            }
            while (connectError != null);

            activeConnection.Value = newConnection;
        }