rmux-server 0.10.0

Tokio daemon and request dispatcher for the RMUX terminal multiplexer.
Documentation
use rmux_core::command_parser::{CommandArgument, ParsedCommand, ParsedCommands};
use rmux_proto::RmuxError;

const CONTROL_COMMAND_MAX_ARGUMENTS: usize = 1000;
const CONTROL_COMMAND_MAX_COUNT: usize = 1024;

pub(super) fn validate_control_command_arguments(
    commands: ParsedCommands,
) -> Result<ParsedCommands, RmuxError> {
    let mut command_count = 0;
    validate_control_commands(&commands, &mut command_count)?;
    Ok(commands)
}

fn validate_control_commands(
    commands: &ParsedCommands,
    command_count: &mut usize,
) -> Result<(), RmuxError> {
    for command in commands.commands() {
        *command_count = command_count.saturating_add(1);
        if *command_count > CONTROL_COMMAND_MAX_COUNT {
            return Err(RmuxError::Server(format!(
                "too many commands: {command_count} (maximum {CONTROL_COMMAND_MAX_COUNT})"
            )));
        }
        validate_control_command(command, command_count)?;
    }
    Ok(())
}

fn validate_control_command(
    command: &ParsedCommand,
    command_count: &mut usize,
) -> Result<(), RmuxError> {
    let argument_count = command.arguments().len();
    if argument_count > CONTROL_COMMAND_MAX_ARGUMENTS {
        return Err(RmuxError::Server(format!(
            "too many arguments: {argument_count} (maximum {CONTROL_COMMAND_MAX_ARGUMENTS})"
        )));
    }

    for argument in command.arguments() {
        if let CommandArgument::Commands(nested) = argument {
            validate_control_commands(nested, command_count)?;
        }
    }
    Ok(())
}

#[cfg(test)]
mod tests {
    use rmux_core::command_parser::CommandParser;

    use super::{validate_control_command_arguments, CONTROL_COMMAND_MAX_COUNT};

    #[test]
    fn aggregate_command_limit_counts_nested_lists() {
        let nested = "start-server;".repeat(CONTROL_COMMAND_MAX_COUNT);
        let parsed = CommandParser::new()
            .parse(&format!("if-shell -F 1 {{ {nested} }}"))
            .expect("large nested command list parses");

        let error = validate_control_command_arguments(parsed)
            .expect_err("outer and nested commands must share one aggregate limit");
        assert!(error.to_string().contains("too many commands"), "{error}");
    }
}