mutating func processOutboundMessage()

in Sources/NIOSSH/Connection State Machine/SSHConnectionStateMachine.swift [685:1027]


    mutating func processOutboundMessage(_ message: SSHMessage,
                                         buffer: inout ByteBuffer,
                                         allocator: ByteBufferAllocator,
                                         loop: EventLoop) throws {
        switch self.state {
        case .idle(var state):
            switch message {
            case .version:
                try state.serializer.serialize(message: message, to: &buffer)
                self.state = .sentVersion(.init(idleState: state, allocator: allocator))
            case .disconnect:
                try state.serializer.serialize(message: message, to: &buffer)
                self.state = .sentDisconnect(state.role)
            case .ignore, .debug, .unimplemented:
                try state.serializer.serialize(message: message, to: &buffer)
                self.state = .idle(state)
            default:
                preconditionFailure("First message sent must be version, not \(message)")
            }

        case .sentVersion:
            // We can't send anything else now.
            // TODO(cory): We could refactor the key exchange state machine to accept the delayed version from the
            // remote peer, and then we unlock the ability to remove another RTT to the remote peer.
            preconditionFailure("Cannot send other messages before receiving version.")

        case .keyExchange(var kex):
            switch message {
            case .keyExchange(let keyExchangeMessage):
                try kex.writeKeyExchangeMessage(keyExchangeMessage, into: &buffer)
                self.state = .keyExchange(kex)
            case .keyExchangeInit(let kexInit):
                try kex.writeKeyExchangeInitMessage(kexInit, into: &buffer)
                self.state = .keyExchange(kex)
            case .keyExchangeReply(let kexReply):
                try kex.writeKeyExchangeReplyMessage(kexReply, into: &buffer)
                self.state = .keyExchange(kex)
            case .newKeys:
                try kex.writeNewKeysMessage(into: &buffer)
                let newState = SentNewKeysState(keyExchangeState: kex, loop: loop)
                let possibleMessage = newState.userAuthStateMachine.beginAuthentication()
                self.state = .sentNewKeys(newState)

                // If we have a service request message, re-spin the state machine to process that too.
                if let additionalMessage = possibleMessage {
                    try self.processOutboundMessage(.serviceRequest(additionalMessage), buffer: &buffer, allocator: allocator, loop: loop)
                }

            case .disconnect:
                try kex.serializer.serialize(message: message, to: &buffer)
                self.state = .sentDisconnect(kex.role)

            case .ignore, .debug, .unimplemented:
                try kex.serializer.serialize(message: message, to: &buffer)
                self.state = .keyExchange(kex)

            default:
                throw NIOSSHError.protocolViolation(protocolName: "key exchange", violation: "Sent unexpected message type: \(message)")
            }

        case .receivedNewKeys(var kex):
            switch message {
            case .keyExchange(let keyExchangeMessage):
                try kex.writeKeyExchangeMessage(keyExchangeMessage, into: &buffer)
                self.state = .receivedNewKeys(kex)
            case .keyExchangeInit(let kexInit):
                try kex.writeKeyExchangeInitMessage(kexInit, into: &buffer)
                self.state = .receivedNewKeys(kex)
            case .keyExchangeReply(let kexReply):
                try kex.writeKeyExchangeReplyMessage(kexReply, into: &buffer)
                self.state = .receivedNewKeys(kex)
            case .newKeys:
                try kex.writeNewKeysMessage(into: &buffer)

                let newState = UserAuthenticationState(receivedNewKeysState: kex)
                let possibleMessage = newState.userAuthStateMachine.beginAuthentication()
                self.state = .userAuthentication(newState)

                // If we have a service request message, re-spin the state machine to process that too.
                if let additionalMessage = possibleMessage {
                    try self.processOutboundMessage(.serviceRequest(additionalMessage), buffer: &buffer, allocator: allocator, loop: loop)
                }

            case .disconnect:
                try kex.serializer.serialize(message: message, to: &buffer)
                self.state = .sentDisconnect(kex.role)

            case .ignore, .debug, .unimplemented:
                try kex.serializer.serialize(message: message, to: &buffer)
                self.state = .receivedNewKeys(kex)

            default:
                throw NIOSSHError.protocolViolation(protocolName: "key exchange", violation: "Sent unexpected message type: \(message)")
            }

        case .sentNewKeys(var state):
            // In this state we tolerate sending service request. As we cannot have received any user auth messages
            // (we're still waiting for newKeys), we cannot possibly send any other user auth message
            switch message {
            case .serviceRequest(let message):
                try state.writeServiceRequest(message, into: &buffer)
                self.state = .sentNewKeys(state)

            case .serviceAccept, .userAuthRequest, .userAuthSuccess, .userAuthFailure:
                throw NIOSSHError.protocolViolation(protocolName: "user auth", violation: "Cannot send \(message) before receiving newKeys")

            case .disconnect:
                try state.serializer.serialize(message: message, to: &buffer)
                self.state = .sentDisconnect(state.role)

            case .ignore, .debug, .unimplemented:
                try state.serializer.serialize(message: message, to: &buffer)
                self.state = .sentNewKeys(state)

            default:
                throw NIOSSHError.protocolViolation(protocolName: "user auth", violation: "Sent unexpected message type: \(message)")
            }

        case .userAuthentication(var state):
            // In this state we tolerate sending user auth messages.
            switch message {
            case .serviceRequest(let message):
                try state.writeServiceRequest(message, into: &buffer)
                self.state = .userAuthentication(state)

            case .serviceAccept(let message):
                try state.writeServiceAccept(message, into: &buffer)
                self.state = .userAuthentication(state)

            case .userAuthBanner(let message):
                try state.writeUserAuthBanner(message, into: &buffer)
                self.state = .userAuthentication(state)

            case .userAuthRequest(let message):
                try state.writeUserAuthRequest(message, into: &buffer)
                self.state = .userAuthentication(state)

            case .userAuthSuccess:
                try state.writeUserAuthSuccess(into: &buffer)
                // Ok we're good to go!
                self.state = .active(ActiveState(state))

            case .userAuthFailure(let message):
                try state.writeUserAuthFailure(message, into: &buffer)
                self.state = .userAuthentication(state)

            case .userAuthPKOK(let message):
                try state.writeUserAuthPKOK(message, into: &buffer)
                self.state = .userAuthentication(state)

            case .disconnect:
                try state.serializer.serialize(message: message, to: &buffer)
                self.state = .sentDisconnect(state.role)

            case .ignore, .debug, .unimplemented:
                try state.serializer.serialize(message: message, to: &buffer)
                self.state = .userAuthentication(state)

            default:
                throw NIOSSHError.protocolViolation(protocolName: "user auth", violation: "Sent unexpected message type: \(message)")
            }

        case .active(var state):
            switch message {
            case .channelOpen(let message):
                try state.writeChannelOpen(message, into: &buffer)
            case .channelOpenConfirmation(let message):
                try state.writeChannelOpenConfirmation(message, into: &buffer)
            case .channelOpenFailure(let message):
                try state.writeChannelOpenFailure(message, into: &buffer)
            case .channelEOF(let message):
                try state.writeChannelEOF(message, into: &buffer)
            case .channelClose(let message):
                try state.writeChannelClose(message, into: &buffer)
            case .channelWindowAdjust(let message):
                try state.writeChannelWindowAdjust(message, into: &buffer)
            case .channelData(let message):
                try state.writeChannelData(message, into: &buffer)
            case .channelExtendedData(let message):
                try state.writeChannelExtendedData(message, into: &buffer)
            case .channelRequest(let message):
                try state.writeChannelRequest(message, into: &buffer)
            case .channelSuccess(let message):
                try state.writeChannelSuccess(message, into: &buffer)
            case .channelFailure(let message):
                try state.writeChannelFailure(message, into: &buffer)
            case .globalRequest(let message):
                try state.writeGlobalRequest(message, into: &buffer)
            case .requestSuccess(let message):
                try state.writeRequestSuccess(message, into: &buffer)
            case .requestFailure:
                try state.writeRequestFailure(into: &buffer)
            case .disconnect:
                try state.serializer.serialize(message: message, to: &buffer)
                self.state = .sentDisconnect(state.role)
                return
            case .ignore, .debug, .unimplemented:
                try state.serializer.serialize(message: message, to: &buffer)
            default:
                throw NIOSSHError.protocolViolation(protocolName: "connection", violation: "Sent unexpected message type: \(message)")
            }

            self.state = .active(state)

        case .receivedKexInitWhenActive(var state):
            // In this state we only allow sending key exchange messages. In particular, the key exchange message is the only allowed one.
            switch message {
            case .keyExchange(let keyExchangeMessage):
                try state.writeKeyExchangeMessage(keyExchangeMessage, into: &buffer)
                self.state = .rekeying(.init(state))

            case .disconnect:
                try state.serializer.serialize(message: message, to: &buffer)
                self.state = .sentDisconnect(state.role)

            case .ignore, .debug, .unimplemented:
                try state.serializer.serialize(message: message, to: &buffer)
                self.state = .receivedKexInitWhenActive(state)

            default:
                throw NIOSSHError.protocolViolation(protocolName: "key exchange", violation: "Sent unexpected message type: \(message)")
            }

        case .sentKexInitWhenActive(var state):
            // In this state we've send a key exchange init message, but not received one from the peer. We have nothing to send.
            switch message {
            case .disconnect:
                try state.serializer.serialize(message: message, to: &buffer)
                self.state = .sentDisconnect(state.role)

            case .ignore, .debug, .unimplemented:
                try state.serializer.serialize(message: message, to: &buffer)
                self.state = .sentKexInitWhenActive(state)

            default:
                throw NIOSSHError.protocolViolation(protocolName: "key exchange", violation: "Sent unexpected message type: \(message)")
            }

        case .rekeying(var state):
            // This is a full key exchange state.
            switch message {
            case .keyExchange(let keyExchangeMessage):
                try state.writeKeyExchangeMessage(keyExchangeMessage, into: &buffer)
                self.state = .rekeying(state)
            case .keyExchangeInit(let kexInit):
                try state.writeKeyExchangeInitMessage(kexInit, into: &buffer)
                self.state = .rekeying(state)
            case .keyExchangeReply(let kexReply):
                try state.writeKeyExchangeReplyMessage(kexReply, into: &buffer)
                self.state = .rekeying(state)
            case .newKeys:
                try state.writeNewKeysMessage(into: &buffer)
                self.state = .rekeyingSentNewKeysState(.init(state))

            case .disconnect:
                try state.serializer.serialize(message: message, to: &buffer)
                self.state = .sentDisconnect(state.role)

            case .ignore, .debug, .unimplemented:
                try state.serializer.serialize(message: message, to: &buffer)
                self.state = .rekeying(state)

            default:
                throw NIOSSHError.protocolViolation(protocolName: "key exchange", violation: "Sent unexpected message type: \(message)")
            }

        case .rekeyingReceivedNewKeysState(var state):
            // We may be doing any part of key exchange still.
            switch message {
            case .keyExchange(let keyExchangeMessage):
                try state.writeKeyExchangeMessage(keyExchangeMessage, into: &buffer)
                self.state = .rekeyingReceivedNewKeysState(state)
            case .keyExchangeInit(let kexInit):
                try state.writeKeyExchangeInitMessage(kexInit, into: &buffer)
                self.state = .rekeyingReceivedNewKeysState(state)
            case .keyExchangeReply(let kexReply):
                try state.writeKeyExchangeReplyMessage(kexReply, into: &buffer)
                self.state = .rekeyingReceivedNewKeysState(state)
            case .newKeys:
                try state.writeNewKeysMessage(into: &buffer)
                self.state = .active(.init(state))

            case .disconnect:
                try state.serializer.serialize(message: message, to: &buffer)
                self.state = .sentDisconnect(state.role)

            case .ignore, .debug, .unimplemented:
                try state.serializer.serialize(message: message, to: &buffer)
                self.state = .rekeyingReceivedNewKeysState(state)

            default:
                throw NIOSSHError.protocolViolation(protocolName: "key exchange", violation: "Sent unexpected message type: \(message)")
            }

        case .rekeyingSentNewKeysState(var state):
            // We can send channel messages again.
            switch message {
            case .channelOpen(let message):
                try state.writeChannelOpen(message, into: &buffer)
            case .channelOpenConfirmation(let message):
                try state.writeChannelOpenConfirmation(message, into: &buffer)
            case .channelOpenFailure(let message):
                try state.writeChannelOpenFailure(message, into: &buffer)
            case .channelEOF(let message):
                try state.writeChannelEOF(message, into: &buffer)
            case .channelClose(let message):
                try state.writeChannelClose(message, into: &buffer)
            case .channelWindowAdjust(let message):
                try state.writeChannelWindowAdjust(message, into: &buffer)
            case .channelData(let message):
                try state.writeChannelData(message, into: &buffer)
            case .channelExtendedData(let message):
                try state.writeChannelExtendedData(message, into: &buffer)
            case .channelRequest(let message):
                try state.writeChannelRequest(message, into: &buffer)
            case .channelSuccess(let message):
                try state.writeChannelSuccess(message, into: &buffer)
            case .channelFailure(let message):
                try state.writeChannelFailure(message, into: &buffer)
            case .globalRequest(let message):
                try state.writeGlobalRequest(message, into: &buffer)
            case .requestSuccess(let message):
                try state.writeRequestSuccess(message, into: &buffer)
            case .requestFailure:
                try state.writeRequestFailure(into: &buffer)
            case .disconnect:
                try state.serializer.serialize(message: message, to: &buffer)
                self.state = .sentDisconnect(state.role)
                return
            case .ignore, .debug, .unimplemented:
                try state.serializer.serialize(message: message, to: &buffer)
                self.state = .rekeyingSentNewKeysState(state)
            default:
                throw NIOSSHError.protocolViolation(protocolName: "connection", violation: "Sent unexpected message type: \(message)")
            }

            self.state = .rekeyingSentNewKeysState(state)

        case .sentDisconnect, .receivedDisconnect:
            // We don't allow more messages once disconnect has occured
            throw NIOSSHError.protocolViolation(protocolName: "transport", violation: "I/O after disconnect")
        }
    }