websocat 4.0.0-alpha3

Command-line client for web sockets, like netcat/curl/socat for ws://.
Documentation
use tracing::debug;

use crate::cli::WebsocatArgs;

use super::types::{
    Endpoint, EndpointDiscriminants, Overlay, OverlayDiscriminants, SocketType, SpecifierStack,
    WebsocatInvocation,
};

impl WebsocatInvocation {
    pub fn session_socket_type(&self) -> SocketType {
        match (
            self.stacks.left.provides_socket_type(),
            self.stacks.right.provides_socket_type(),
        ) {
            (SocketType::ByteStream, SocketType::ByteStream) => SocketType::ByteStream,
            (SocketType::Datarams, SocketType::Datarams) => SocketType::Datarams,
            _ => {
                debug!("Incompatible types encountered: bytestream-oriented and datagram-oriented, falling back to datagrams");
                SocketType::Datarams
            }
        }
    }
}

impl SpecifierStack {
    pub(super) fn provides_socket_type(&self) -> SocketType {
        let mut typ = self.innermost.provides_socket_type();
        for ovl in &self.overlays {
            if let Some(t) = ovl.provides_socket_type() {
                typ = t
            }
        }
        typ
    }

    pub fn contains_overlay(&self, x: OverlayDiscriminants) -> bool {
        for ovl in &self.overlays {
            if x == ovl.into() {
                return true;
            }
        }
        false
    }
    pub fn contains_endpoint(&self, x: EndpointDiscriminants) -> bool {
        let innermost_type: EndpointDiscriminants = (&self.innermost).into();
        if innermost_type == x {
            return true;
        }
        match self.innermost {
            Endpoint::WriteSplitoff {
                ref read,
                ref write,
            } => read.contains_endpoint(x) || write.contains_endpoint(x),
            _ => false,
        }
    }
}

impl Endpoint {
    pub(super) fn provides_socket_type(&self) -> SocketType {
        use SocketType::{ByteStream, Datarams, SocketSender};
        match self {
            Endpoint::TcpConnectByIp(_) => ByteStream,
            Endpoint::TcpListen(_) => ByteStream,
            Endpoint::TcpListenFd(_) => ByteStream,
            Endpoint::TcpListenFdNamed(_) => ByteStream,
            Endpoint::TcpConnectByEarlyHostname { .. } => ByteStream,
            Endpoint::TcpConnectByLateHostname { hostname: _ } => ByteStream,
            Endpoint::WsUrl(_) => Datarams,
            Endpoint::WssUrl(_) => Datarams,
            Endpoint::Stdio => ByteStream,
            Endpoint::UdpConnect(_) => Datarams,
            Endpoint::UdpBind(_) => Datarams,
            Endpoint::UdpFd(_) => Datarams,
            Endpoint::UdpFdNamed(_) => Datarams,
            Endpoint::WsListen(_) => Datarams,
            Endpoint::UdpServer(_) => Datarams,
            Endpoint::UdpServerFd(_) => Datarams,
            Endpoint::UdpServerFdNamed(_) => Datarams,
            Endpoint::Exec(_) => ByteStream,
            Endpoint::Cmd(_) => ByteStream,
            Endpoint::DummyStream => ByteStream,
            Endpoint::DummyDatagrams => Datarams,
            Endpoint::Literal(_) => ByteStream,
            Endpoint::LiteralBase64(_) => ByteStream,
            Endpoint::UnixConnect(_) => ByteStream,
            Endpoint::UnixListen(_) => ByteStream,
            Endpoint::AbstractConnect(_) => ByteStream,
            Endpoint::AbstractListen(_) => ByteStream,
            Endpoint::UnixListenFd(_) => ByteStream,
            Endpoint::UnixListenFdNamed(_) => ByteStream,
            Endpoint::SeqpacketConnect(_) => Datarams,
            Endpoint::SeqpacketListen(_) => Datarams,
            Endpoint::AbstractSeqpacketConnect(_) => Datarams,
            Endpoint::AbstractSeqpacketListen(_) => Datarams,
            Endpoint::SeqpacketListenFd(_) => Datarams,
            Endpoint::SeqpacketListenFdNamed(_) => Datarams,
            Endpoint::MockStreamSocket(_) => ByteStream,
            Endpoint::RegistryStreamListen(_) => ByteStream,
            Endpoint::RegistryStreamConnect(_) => ByteStream,
            Endpoint::RegistryDatagramListen(_) => Datarams,
            Endpoint::RegistryDatagramConnect(_) => Datarams,
            Endpoint::AsyncFd(_) => ByteStream,
            Endpoint::SimpleReuserEndpoint(..) => Datarams,
            Endpoint::ReadFile(..) => ByteStream,
            Endpoint::WriteFile(..) => ByteStream,
            Endpoint::AppendFile(..) => ByteStream,
            Endpoint::Random => ByteStream,
            Endpoint::Zero => ByteStream,
            Endpoint::WriteSplitoff { read, write } => {
                match (read.provides_socket_type(), write.provides_socket_type()) {
                    (ByteStream, ByteStream) => ByteStream,
                    (Datarams, Datarams) => Datarams,
                    _ => {
                        debug!("Incompatible WriteSplitoff socket types: datagram and bytestream");
                        Datarams
                    }
                }
            }
            Endpoint::Mirror {
                datagram_mode: false,
            } => ByteStream,
            Endpoint::Mirror {
                datagram_mode: true,
            } => Datarams,
            Endpoint::RegistrySend(..) => SocketSender,
            Endpoint::Tee { .. } => Datarams,
        }
    }
}

impl Overlay {
    /// Socket type this overlay outputs. None means it preserves inner type.
    pub(super) fn provides_socket_type(&self) -> Option<SocketType> {
        use SocketType::{ByteStream, Datarams};
        Some(match self {
            Overlay::WsUpgrade { .. } => ByteStream,
            Overlay::WsFramer { .. } => Datarams,
            Overlay::StreamChunks => Datarams,
            Overlay::LineChunks => Datarams,
            Overlay::TlsClient { .. } => ByteStream,
            Overlay::WsAccept {} => ByteStream,
            Overlay::Log { datagram_mode } => {
                if *datagram_mode {
                    Datarams
                } else {
                    ByteStream
                }
            }
            Overlay::WsClient => Datarams,
            Overlay::WsServer => Datarams,
            Overlay::ReadChunkLimiter => ByteStream,
            Overlay::WriteChunkLimiter => ByteStream,
            Overlay::WriteBuffer => ByteStream,
            Overlay::LengthPrefixedChunks => Datarams,
            Overlay::SimpleReuser => Datarams,
            Overlay::WriteSplitoff => return None,
            Overlay::Defragment => Datarams,
            Overlay::Tee => Datarams,
        })
    }
    /// Socket type this overlay needs as an input. None means it handles both types.
    pub(super) fn requires_socket_type(&self) -> Option<SocketType> {
        use SocketType::{ByteStream, Datarams};
        Some(match self {
            Overlay::WsUpgrade { .. } => ByteStream,
            Overlay::WsFramer { .. } => ByteStream,
            Overlay::StreamChunks => ByteStream,
            Overlay::LineChunks => ByteStream,
            Overlay::TlsClient { .. } => ByteStream,
            Overlay::WsAccept {} => ByteStream,
            Overlay::Log { datagram_mode } => {
                if *datagram_mode {
                    Datarams
                } else {
                    ByteStream
                }
            }
            Overlay::WsClient => ByteStream,
            Overlay::WsServer => ByteStream,
            Overlay::ReadChunkLimiter => ByteStream,
            Overlay::WriteChunkLimiter => ByteStream,
            Overlay::WriteBuffer => ByteStream,
            Overlay::LengthPrefixedChunks => ByteStream,
            Overlay::SimpleReuser => Datarams,
            Overlay::WriteSplitoff => return None,
            Overlay::Defragment => Datarams,
            Overlay::Tee => Datarams,
        })
    }
}

impl SpecifierStack {
    /// Expected to emit multiple connections in parallel
    pub(super) fn is_multiconn(&self, opts: &WebsocatArgs) -> bool {
        let mut multiconn = match self.innermost {
            Endpoint::TcpConnectByEarlyHostname { .. } => false,
            Endpoint::TcpConnectByLateHostname { .. } => false,
            Endpoint::TcpConnectByIp(..) => false,
            Endpoint::TcpListen(..) => !opts.oneshot,
            Endpoint::TcpListenFd(..) => !opts.oneshot,
            Endpoint::TcpListenFdNamed(..) => !opts.oneshot,
            Endpoint::WsUrl(..) => false,
            Endpoint::WssUrl(..) => false,
            Endpoint::WsListen(..) => !opts.oneshot,
            Endpoint::Stdio => false,
            Endpoint::UdpConnect(..) => false,
            Endpoint::UdpBind(..) => false,
            Endpoint::UdpFd(_) => false,
            Endpoint::UdpFdNamed(_) => false,
            Endpoint::UdpServer(..) => !opts.oneshot,
            Endpoint::UdpServerFd(_) => !opts.oneshot,
            Endpoint::UdpServerFdNamed(_) => !opts.oneshot,
            Endpoint::Exec(..) => false,
            Endpoint::Cmd(..) => false,
            Endpoint::DummyStream => false,
            Endpoint::DummyDatagrams => false,
            Endpoint::Literal(_) => false,
            Endpoint::LiteralBase64(_) => false,
            Endpoint::UnixConnect(..) => false,
            Endpoint::UnixListen(..) => !opts.oneshot,
            Endpoint::AbstractConnect(..) => false,
            Endpoint::AbstractListen(..) => !opts.oneshot,
            Endpoint::UnixListenFd(_) => !opts.oneshot,
            Endpoint::UnixListenFdNamed(_) => !opts.oneshot,
            Endpoint::AsyncFd(_) => false,
            Endpoint::SeqpacketConnect(..) => false,
            Endpoint::SeqpacketListen(..) => !opts.oneshot,
            Endpoint::AbstractSeqpacketConnect(..) => false,
            Endpoint::AbstractSeqpacketListen(..) => !opts.oneshot,
            Endpoint::SeqpacketListenFd(..) => !opts.oneshot,
            Endpoint::SeqpacketListenFdNamed(..) => !opts.oneshot,
            Endpoint::MockStreamSocket(..) => false,
            Endpoint::RegistryStreamListen(..) => !opts.oneshot,
            Endpoint::RegistryStreamConnect(..) => false,
            Endpoint::RegistryDatagramListen(_) => !opts.oneshot,
            Endpoint::RegistryDatagramConnect(_) => false,
            Endpoint::SimpleReuserEndpoint(..) => false,
            Endpoint::ReadFile(..) => false,
            Endpoint::WriteFile(..) => false,
            Endpoint::AppendFile(..) => false,
            Endpoint::Random => false,
            Endpoint::Zero => false,
            Endpoint::WriteSplitoff { .. } => false,
            Endpoint::Mirror { .. } => false,
            Endpoint::RegistrySend(..) => false,
            Endpoint::Tee { .. } => false,
        };

        for x in &self.overlays {
            match x {
                Overlay::WsUpgrade { .. } => {}
                Overlay::WsAccept { .. } => {}
                Overlay::WsFramer { .. } => {}
                Overlay::WsClient => {}
                Overlay::WsServer => {}
                Overlay::TlsClient { .. } => {}
                Overlay::StreamChunks => {}
                Overlay::LineChunks => {}
                Overlay::LengthPrefixedChunks => {}
                Overlay::Log { .. } => {}
                Overlay::ReadChunkLimiter => {}
                Overlay::WriteChunkLimiter => {}
                Overlay::WriteBuffer => {}
                Overlay::SimpleReuser => multiconn = false,
                Overlay::WriteSplitoff => multiconn = false,
                Overlay::Defragment => {}
                Overlay::Tee => {}
            }
        }

        multiconn
    }

    /// Some specifier or overlay does not like reentrant usage, e.g. stdio: or appendfile: may be chaotic.
    pub(super) fn prefers_being_single(&self, opts: &WebsocatArgs) -> bool {
        let mut singler = match self.innermost {
            Endpoint::TcpConnectByEarlyHostname { .. } => false,
            Endpoint::TcpConnectByLateHostname { .. } => false,
            Endpoint::TcpConnectByIp(..) => false,
            Endpoint::TcpListen(..) => false,
            Endpoint::TcpListenFd(..) => false,
            Endpoint::TcpListenFdNamed(..) => false,
            Endpoint::WsUrl(..) => false,
            Endpoint::WssUrl(..) => false,
            Endpoint::WsListen(..) => false,
            Endpoint::Stdio => true,
            Endpoint::UdpConnect(..) => false,
            Endpoint::UdpBind(..) => true,
            Endpoint::UdpFd(_) => true,
            Endpoint::UdpFdNamed(_) => true,
            Endpoint::UdpServer(..) => false,
            Endpoint::UdpServerFd(_) => false,
            Endpoint::UdpServerFdNamed(_) => false,
            Endpoint::Exec(..) => false,
            Endpoint::Cmd(..) => false,
            Endpoint::DummyStream => false,
            Endpoint::DummyDatagrams => false,
            Endpoint::Literal(_) => false,
            Endpoint::LiteralBase64(_) => false,
            Endpoint::UnixConnect(..) => false,
            Endpoint::UnixListen(..) => false,
            Endpoint::AbstractConnect(..) => false,
            Endpoint::AbstractListen(..) => false,
            Endpoint::UnixListenFd(_) => false,
            Endpoint::UnixListenFdNamed(_) => false,
            Endpoint::AsyncFd(_) => true,
            Endpoint::SeqpacketConnect(..) => false,
            Endpoint::SeqpacketListen(..) => false,
            Endpoint::AbstractSeqpacketConnect(..) => false,
            Endpoint::AbstractSeqpacketListen(..) => false,
            Endpoint::SeqpacketListenFd(..) => false,
            Endpoint::SeqpacketListenFdNamed(..) => false,
            Endpoint::MockStreamSocket(..) => false,
            Endpoint::RegistryStreamListen(..) => false,
            Endpoint::RegistryStreamConnect(..) => false,
            Endpoint::RegistryDatagramListen(_) => false,
            Endpoint::SimpleReuserEndpoint(..) => false,
            Endpoint::ReadFile(..) => false,
            Endpoint::WriteFile(..) => !opts.write_file_no_overwrite,
            Endpoint::AppendFile(..) => true,
            Endpoint::Random => false,
            Endpoint::Zero => false,
            Endpoint::WriteSplitoff {
                ref read,
                ref write,
            } => read.prefers_being_single(opts) || write.prefers_being_single(opts),
            Endpoint::Mirror { .. } => false,
            Endpoint::RegistrySend(..) => false,
            Endpoint::RegistryDatagramConnect(_) => false,
            Endpoint::Tee { ref nodes } => nodes.iter().any(|x| x.prefers_being_single(opts)),
        };

        for x in &self.overlays {
            match x {
                Overlay::WsUpgrade { .. } => {}
                Overlay::WsAccept { .. } => {}
                Overlay::WsFramer { .. } => {}
                Overlay::WsClient => {}
                Overlay::WsServer => {}
                Overlay::TlsClient { .. } => {}
                Overlay::StreamChunks => {}
                Overlay::LineChunks => {}
                Overlay::LengthPrefixedChunks => {}
                Overlay::Log { .. } => {}
                Overlay::ReadChunkLimiter => {}
                Overlay::WriteChunkLimiter => {}
                Overlay::WriteBuffer => {}
                Overlay::SimpleReuser => singler = false,
                Overlay::WriteSplitoff => {}
                Overlay::Defragment => {}
                Overlay::Tee => {}
            }
        }

        singler
    }
}