Files
scpcap/src/cli.rs
T
2026-08-25 09:03:56 -04:00

109 lines
3.5 KiB
Rust

use clap::Parser;
/// scp-like tool for copying (possibly still-growing) pcap capture files.
#[derive(Parser, Debug)]
#[command(name = "scpcap")]
pub struct ClientCli {
/// Source: local path or [user@]host:path. If the name ends in the
/// partial extension, it's treated as still growing.
pub source: String,
/// Destination: local path or [user@]host:path.
pub dest: String,
/// Compress in flight; destination is written as a streaming .pcap.zst.
/// Only "zstd" is supported.
#[arg(long, value_name = "CODEC", num_args = 0..=1, default_missing_value = "zstd", require_equals = true)]
pub compress: Option<String>,
/// Target chunk size for compression, e.g. "256k", "1m".
#[arg(long, default_value = "256k")]
pub chunk: String,
/// zstd compression level (lower = faster; default tuned for throughput
/// to stay competitive with `scp -C`).
#[arg(long, default_value_t = crate::zstd_frame::DEFAULT_LEVEL)]
pub level: i32,
/// Suffix identifying a still-growing file.
#[arg(long, default_value = "partial")]
pub extension: String,
}
/// Remote helper mode, ssh-spawned by the client (like `rsync --server`).
/// Not meant to be invoked directly by a user.
#[derive(Parser, Debug)]
#[command(name = "scpcap --server")]
pub struct ServerCli {
#[command(flatten)]
pub role: ServerRoleFlags,
/// Source path to read (--send mode).
#[arg(long)]
pub source: Option<String>,
/// Source's final (non-partial) path, for rename detection (--send mode).
#[arg(long)]
pub final_source: Option<String>,
/// Whether the source is a still-growing (partial) file (--send mode).
#[arg(long)]
pub partial: bool,
/// Compress in flight (--send mode). No adjacent positional args in
/// --server mode, so (unlike ClientCli's --compress) there's no
/// ambiguity requiring `--compress=zstd` -- `--compress zstd` is fine.
#[arg(long, value_name = "CODEC", num_args = 0..=1, default_missing_value = "zstd")]
pub compress: Option<String>,
/// Target chunk size for compression (--send mode).
#[arg(long, default_value = "256k")]
pub chunk: String,
/// zstd compression level (--send mode).
#[arg(long, default_value_t = crate::zstd_frame::DEFAULT_LEVEL)]
pub level: i32,
/// Destination final path (--recv mode).
#[arg(long)]
pub dest_final: Option<String>,
/// Destination temp path, written while in flight (--recv mode).
#[arg(long)]
pub dest_temp: Option<String>,
}
/// The clap-facing shape of the role choice: two mutually exclusive,
/// individually-long-named flags, matching rsync's `--server --send`/
/// `--server --recv` convention. Clap's derive has no direct way to map two
/// separate boolean flags onto one enum field, so this stays a flag pair at
/// the parsing boundary and converts to `ServerRole` via `role()` below.
#[derive(clap::Args, Debug)]
#[group(required = true, multiple = false)]
pub struct ServerRoleFlags {
#[arg(long)]
pub send: bool,
#[arg(long)]
pub recv: bool,
}
/// The role a `--server` process plays, for the rest of the code to match on
/// instead of checking two booleans.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ServerRole {
Send,
Recv,
}
impl ServerCli {
/// The `--group(required, exclusive)` on `ServerRoleFlags` guarantees
/// exactly one of `send`/`recv` is set by the time parsing succeeds.
pub fn role(&self) -> ServerRole {
if self.role.send {
ServerRole::Send
} else {
ServerRole::Recv
}
}
}