feat(cli): expose server negotiation policy

This commit is contained in:
Nils Andreas Svee committed 2026-08-14 14:30:28 +07:00
1 parent 8308c077eb
commit 5be224d1b2
4 files changed
+119 -3

No files matched your search

+99 -2
View File
@@ -1,7 +1,7 @@
use std::{net::SocketAddr, time::Duration};
use clap::Parser;
use irtt_server::ServerConfig;
use clap::{Parser, ValueEnum};
use irtt_server::{ServerConfig, TimestampAllowance};
// Every policy option is optional. An omitted option leaves the corresponding
// `ServerConfig` default in place rather than restating it here, so library and
@@ -79,6 +79,46 @@ pub struct ServerArgs {
long_help = "Maximum test duration a session may negotiate. A longer request is reduced to it, and a continuous request is answered with it. Omit this flag for no maximum; a maximum of zero cannot be expressed, because a negotiated duration of zero means continuous."
)]
pub max_duration: Option<Duration>,
/// How many timestamps this server will provide.
#[arg(
long,
value_name = "MODE",
value_enum,
long_help = "How many timestamps this server will provide.\n\ndual: honor a request for send, receive, both or midpoint timestamps.\nsingle: provide at most one timestamp; a request for both is negotiated to midpoint.\nnone: provide no timestamps at all.\n\nThe requested clock source is never changed, only the timestamp placement. Omit this flag to honor every requested placement."
)]
pub timestamp_allowance: Option<TimestampAllowanceArg>,
/// Refuse to provide requested DSCP marking.
#[arg(
long,
long_help = "Refuse to provide requested traffic-class marking. Any requested DSCP is negotiated to zero, so the client is told its echo replies will be unmarked, and they are sent unmarked. The session is not refused."
)]
pub no_dscp: bool,
}
/// The command-line spelling of [`TimestampAllowance`].
///
/// A CLI-local enum rather than a `clap` derive on the library type: `irtt-server`
/// is a reusable crate and has no business depending on an argument parser.
#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
pub enum TimestampAllowanceArg {
/// Honor every requested timestamp placement.
Dual,
/// Provide at most one timestamp; both becomes midpoint.
Single,
/// Provide no timestamps.
None,
}
impl From<TimestampAllowanceArg> for TimestampAllowance {
fn from(value: TimestampAllowanceArg) -> Self {
match value {
TimestampAllowanceArg::Dual => Self::Dual,
TimestampAllowanceArg::Single => Self::Single,
TimestampAllowanceArg::None => Self::None,
}
}
}
impl ServerArgs {
@@ -107,6 +147,14 @@ impl ServerArgs {
if let Some(value) = self.max_duration {
config = config.with_max_test_duration(value);
}
if let Some(value) = self.timestamp_allowance {
config = config.with_timestamp_allowance(value.into());
}
// A negative capability flag, so absence is the ordinary enabled state
// and the library default is what an unset flag leaves in place.
if self.no_dscp {
config = config.with_dscp_allowed(false);
}
config
}
}
@@ -224,6 +272,55 @@ mod tests {
assert_eq!(config.max_test_duration(), Some(Duration::from_secs(120)));
}
#[test]
fn the_capability_restrictions_map_onto_the_server_config() {
let config = bound(&["--timestamp-allowance", "single", "--no-dscp"]).server_config();
assert_eq!(
config.timestamp_allowance(),
TimestampAllowance::Single,
"a request for both timestamps is negotiated to midpoint"
);
assert!(!config.dscp_allowed());
}
#[test]
fn every_timestamp_allowance_mode_parses() {
for (argument, expected) in [
("dual", TimestampAllowance::Dual),
("single", TimestampAllowance::Single),
("none", TimestampAllowance::None),
] {
assert_eq!(
bound(&["--timestamp-allowance", argument])
.server_config()
.timestamp_allowance(),
expected,
"--timestamp-allowance {argument}"
);
}
assert!(parse(&[
"--bind",
"127.0.0.1:2112",
"--timestamp-allowance",
"midpoint"
])
.is_err());
assert!(parse(&["--bind", "127.0.0.1:2112", "--timestamp-allowance", "both"]).is_err());
}
#[test]
fn the_capability_restrictions_are_off_when_their_flags_are_absent() {
// Explicitly, and not only through the whole-config comparison above:
// these are the two values that would silently change every existing
// deployment's negotiation if the CLI restated them.
let config = bound(&["--idle-timeout", "30s"]).server_config();
assert_eq!(config.timestamp_allowance(), TimestampAllowance::Dual);
assert!(config.dscp_allowed());
}
#[test]
fn explicit_zero_policy_values_are_preserved() {
let config = bound(&[
+2
View File
@@ -80,6 +80,8 @@ fn canonical_server_subcommand_dispatches_to_server() {
assert!(text.contains("--bind <ADDR>"));
assert!(text.contains("--max-sessions <COUNT>"));
assert!(text.contains("--idle-timeout <DURATION>"));
assert!(text.contains("--timestamp-allowance <MODE>"));
assert!(text.contains("--no-dscp"));
}
#[cfg(not(feature = "server"))]