websocat 4.0.0-alpha3

Command-line client for web sockets, like netcat/curl/socat for ws://.
Documentation
use super::{
    scenarioprinter::StrLit,
    types::{Endpoint, ScenarioPrintingEnvironment},
};

/// Actually `udp_common_bind_options` also calls this one
pub fn tcp_common_bind_options(o: &mut String, env: &ScenarioPrintingEnvironment<'_>) {
    if let Some(v) = env.opts.reuseaddr {
        o.push_str(&format!("reuseaddr: {v},"));
    }
    if env.opts.reuseport {
        o.push_str("reuseport: true,");
    }
    if let Some(ref v) = env.opts.socket_bind_to_device {
        o.push_str(&format!("bind_device: {},", StrLit(v)));
    }
    if env.opts.socket_freebind {
        o.push_str("freebind: true,");
    }
    if env.opts.socket_transparent {
        o.push_str("transparent: true,");
    }
    if let Some(v) = env.opts.socket_only_v6 {
        o.push_str(&format!("only_v6: {v},"));
    }
}

fn tcp_common_stream_options(o: &mut String, env: &ScenarioPrintingEnvironment<'_>) {
    if let Some(v) = env.opts.socket_tclass_v6 {
        o.push_str(&format!("tclass_v6: {v},"));
    }
    if let Some(v) = env.opts.socket_tos_v4 {
        o.push_str(&format!("tos_v4: {v},"));
    }
    if let Some(v) = env.opts.socket_ttl {
        o.push_str(&format!("ttl: {v},"));
    }
    if let Some(v) = env.opts.socket_linger_s {
        o.push_str(&format!("linger_s: {v},"));
    }
    if env.opts.socket_out_of_band_inline {
        o.push_str("out_of_band_inline: true,");
    }
    if let Some(v) = env.opts.socket_nodelay {
        o.push_str(&format!("nodelay: {v},"));
    }
    if let Some(ref v) = env.opts.socket_tcp_congestion {
        o.push_str(&format!("tcp_congestion: {},", StrLit(v)));
    }
    if let Some(v) = env.opts.socket_cpu_affinity {
        o.push_str(&format!("cpu_affinity: {v},"));
    }
    if let Some(v) = env.opts.socket_user_timeout_s {
        o.push_str(&format!("user_timeout_s: {v},"));
    }
    if let Some(v) = env.opts.socket_priority {
        o.push_str(&format!("priority: {v},"));
    }
    if let Some(v) = env.opts.socket_recv_buffer_size {
        o.push_str(&format!("recv_buffer_size: {v},"));
    }
    if let Some(v) = env.opts.socket_send_buffer_size {
        o.push_str(&format!("send_buffer_size: {v},"));
    }
    if let Some(v) = env.opts.socket_mss {
        o.push_str(&format!("mss: {v},"));
    }
    if let Some(v) = env.opts.socket_mark {
        o.push_str(&format!("mark: {v},"));
    }
    if let Some(v) = env.opts.socket_thin_linear_timeouts {
        o.push_str(&format!("thin_linear_timeouts: {v},"));
    }
    if let Some(v) = env.opts.socket_notsent_lowat {
        o.push_str(&format!("notsent_lowat: {v},"));
    }
    if let Some(v) = env.opts.socket_keepalive {
        o.push_str(&format!("keepalive: {v},"));
    }
    if let Some(v) = env.opts.socket_keepalive_retries {
        o.push_str(&format!("keepalive_retries: {v},"));
    }
    if let Some(v) = env.opts.socket_keepalive_interval_s {
        o.push_str(&format!("keepalive_interval_s: {v},"));
    }
    if let Some(v) = env.opts.socket_keepalive_idletime_s {
        o.push_str(&format!("keepalive_idletime_s: {v},"));
    }
}

fn tcp_options_for_connect(o: &mut String, env: &ScenarioPrintingEnvironment<'_>) {
    if !(env.used_for_a_websocket && env.opts.exclude_ws_from_sockopts) {
        if let Some(bbc) = env.opts.bind_before_connect {
            o.push_str(&format!("bind: {},", StrLit(bbc)));
        }
        tcp_common_bind_options(o, env);
        tcp_common_stream_options(o, env);
    }
}

impl Endpoint {
    pub(super) fn begin_print_tcp(
        &self,
        env: &mut ScenarioPrintingEnvironment<'_>,
    ) -> anyhow::Result<String> {
        match self {
            Endpoint::TcpConnectByIp(addr) => {
                let varnam = env.vars.getnewvarname("tcp");

                let mut o = String::with_capacity(32);
                o.push_str("addr: ");
                o.push_str(&format!("{},", StrLit(addr)));

                tcp_options_for_connect(&mut o, env);

                env.printer
                    .print_line(&format!("connect_tcp(#{{{o}}}, |{varnam}| {{"));
                env.printer.increase_indent();
                Ok(varnam)
            }
            Endpoint::TcpConnectByEarlyHostname { varname_for_addrs } => {
                let mut o = String::with_capacity(0);

                tcp_options_for_connect(&mut o, env);

                o.push_str(&format!(
                    "race_interval_ms: {},",
                    env.opts.tcp_race_interval_ms
                ));

                let varnam = env.vars.getnewvarname("tcp");
                env.printer.print_line(&format!(
                    "connect_tcp_race(#{{{o}}}, {varname_for_addrs}, |{varnam}| {{"
                ));
                env.printer.increase_indent();
                Ok(varnam)
            }
            Endpoint::TcpConnectByLateHostname { hostname } => {
                let addrs = env.vars.getnewvarname("addrs");
                env.printer.print_line(&format!(
                    "lookup_host({h}, |{addrs}| {{",
                    h = StrLit(hostname)
                ));
                env.printer.increase_indent();

                let varnam = env.vars.getnewvarname("tcp");
                let mut o = String::with_capacity(0);
                o.push_str(&format!(
                    "race_interval_ms: {},",
                    env.opts.tcp_race_interval_ms
                ));

                tcp_options_for_connect(&mut o, env);

                env.printer.print_line(&format!(
                    "connect_tcp_race(#{{{o}}}, {addrs}, |{varnam}| {{"
                ));
                env.printer.increase_indent();
                Ok(varnam)
            }
            Endpoint::TcpListen(..)
            | Endpoint::TcpListenFd(..)
            | Endpoint::TcpListenFdNamed(..) => {
                let varnam = env.vars.getnewvarname("tcp");
                let fromaddr = env.vars.getnewvarname("from");
                let listenparams = env.opts.listening_parameters(env.position);

                let addrpart = match self {
                    Endpoint::TcpListen(addr) => {
                        format!("addr: {a}", a = StrLit(addr),)
                    }
                    Endpoint::TcpListenFd(fd) => {
                        format!("fd: {fd}",)
                    }
                    Endpoint::TcpListenFdNamed(fdname) => {
                        format!("named_fd: {a}", a = StrLit(fdname),)
                    }
                    _ => unreachable!(),
                };

                let mut o = String::with_capacity(0);
                if matches!(self, Endpoint::TcpListen(..))
                    && !(env.used_for_a_websocket && env.opts.exclude_ws_from_sockopts)
                {
                    tcp_common_bind_options(&mut o, env);

                    if let Some(v) = env.opts.socket_listen_backlog {
                        o.push_str(&format!("backlog: {v},"));
                    }
                }
                if !(env.used_for_a_websocket && env.opts.exclude_ws_from_sockopts) {
                    tcp_common_stream_options(&mut o, env);
                }

                env.printer.print_line(&format!(
                    "listen_tcp(#{{{listenparams}, {addrpart}, {o}}}, |listen_addr|{{sequential([",
                ));
                env.printer.increase_indent();

                if env.opts.stdout_announce_listening_ports {
                    env.printer.print_line("print_stdout(\"LISTEN proto=tcp,ip=\"+listen_addr.get_ip()+\",port=\"+str(listen_addr.get_port())+\"\\n\"),");
                }
                if let Some(ref x) = env.opts.exec_after_listen {
                    if env.opts.exec_after_listen_append_port {
                        env.printer.print_line(&format!(
                            "system({} + \" \" + str(listen_addr.get_port())),",
                            StrLit(x)
                        ));
                    } else {
                        env.printer.print_line(&format!("system({}),", StrLit(x)));
                    }
                }

                env.printer.decrease_indent();
                env.printer
                    .print_line(&format!("])}},  |{varnam}, {fromaddr}| {{",));
                env.printer.increase_indent();

                Ok(varnam)
            }
            _ => panic!(),
        }
    }

    pub(super) fn end_print_tcp(&self, env: &mut ScenarioPrintingEnvironment<'_>) {
        match self {
            Endpoint::TcpConnectByIp(_) => {
                env.printer.decrease_indent();
                env.printer.print_line("})");
            }
            Endpoint::TcpConnectByEarlyHostname { .. } => {
                env.printer.decrease_indent();
                env.printer.print_line("})");
            }
            Endpoint::TcpListen(..)
            | Endpoint::TcpListenFd(..)
            | Endpoint::TcpListenFdNamed(..) => {
                env.printer.decrease_indent();
                env.printer.print_line("})");
            }
            Endpoint::TcpConnectByLateHostname { hostname: _ } => {
                env.printer.decrease_indent();
                env.printer.print_line("})");

                env.printer.decrease_indent();
                env.printer.print_line("})");
            }
            _ => panic!(),
        }
    }
}