use crate::netlink::{Error, MessageBuilder, Result, types::tc::qdisc::netem::*};
pub fn build(builder: &mut MessageBuilder, params: &[String]) -> Result<()> {
let mut qopt = TcNetemQopt::new();
let mut corr = TcNetemCorr::default();
let mut reorder = TcNetemReorder::default();
let mut corrupt = TcNetemCorrupt::default();
let mut rate = TcNetemRate::default();
let mut slot = TcNetemSlot::default();
let mut has_corr = false;
let mut has_reorder = false;
let mut has_corrupt = false;
let mut has_rate = false;
let mut has_slot = false;
let mut has_ecn = false;
let mut latency64: Option<i64> = None;
let mut jitter64: Option<i64> = None;
let mut rate64: Option<u64> = None;
let mut i = 0;
while i < params.len() {
match params[i].as_str() {
"limit" if i + 1 < params.len() => {
qopt.limit = params[i + 1]
.parse()
.map_err(|_| Error::InvalidMessage("invalid limit".into()))?;
i += 2;
}
"delay" | "latency" if i + 1 < params.len() => {
let delay = crate::util::parse::get_time(¶ms[i + 1])
.map_err(|_| Error::InvalidMessage("invalid delay".into()))?;
latency64 = Some(delay.as_nanos() as i64);
i += 2;
if i < params.len() && !is_keyword(¶ms[i]) {
let jitter = crate::util::parse::get_time(¶ms[i])
.map_err(|_| Error::InvalidMessage("invalid jitter".into()))?;
jitter64 = Some(jitter.as_nanos() as i64);
i += 1;
if i < params.len() && !is_keyword(¶ms[i]) {
corr.delay_corr = parse_percent(¶ms[i])?;
has_corr = true;
i += 1;
}
}
}
"loss" if i + 1 < params.len() => {
i += 1;
if params[i] == "random" && i + 1 < params.len() {
i += 1;
}
qopt.loss = parse_percent(¶ms[i])?;
i += 1;
if i < params.len() && !is_keyword(¶ms[i]) {
corr.loss_corr = parse_percent(¶ms[i])?;
has_corr = true;
i += 1;
}
}
"drop" if i + 1 < params.len() => {
i += 1;
qopt.loss = parse_percent(¶ms[i])?;
i += 1;
if i < params.len() && !is_keyword(¶ms[i]) {
corr.loss_corr = parse_percent(¶ms[i])?;
has_corr = true;
i += 1;
}
}
"duplicate" if i + 1 < params.len() => {
i += 1;
qopt.duplicate = parse_percent(¶ms[i])?;
i += 1;
if i < params.len() && !is_keyword(¶ms[i]) {
corr.dup_corr = parse_percent(¶ms[i])?;
has_corr = true;
i += 1;
}
}
"corrupt" if i + 1 < params.len() => {
i += 1;
corrupt.probability = parse_percent(¶ms[i])?;
has_corrupt = true;
i += 1;
if i < params.len() && !is_keyword(¶ms[i]) {
corrupt.correlation = parse_percent(¶ms[i])?;
i += 1;
}
}
"reorder" if i + 1 < params.len() => {
i += 1;
reorder.probability = parse_percent(¶ms[i])?;
has_reorder = true;
i += 1;
if i < params.len() && !is_keyword(¶ms[i]) {
reorder.correlation = parse_percent(¶ms[i])?;
i += 1;
}
}
"gap" if i + 1 < params.len() => {
qopt.gap = params[i + 1]
.parse()
.map_err(|_| Error::InvalidMessage("invalid gap".into()))?;
i += 2;
}
"rate" if i + 1 < params.len() => {
i += 1;
let r = crate::util::parse::get_rate(¶ms[i])
.map_err(|_| Error::InvalidMessage("invalid rate".into()))?;
if r > u32::MAX as u64 {
rate64 = Some(r);
rate.rate = u32::MAX;
} else {
rate.rate = r as u32;
}
has_rate = true;
i += 1;
if i < params.len() && !is_keyword(¶ms[i]) {
rate.packet_overhead = params[i]
.parse()
.map_err(|_| Error::InvalidMessage("invalid packet overhead".into()))?;
i += 1;
if i < params.len() && !is_keyword(¶ms[i]) {
rate.cell_size = params[i]
.parse()
.map_err(|_| Error::InvalidMessage("invalid cell size".into()))?;
i += 1;
if i < params.len() && !is_keyword(¶ms[i]) {
rate.cell_overhead = params[i].parse().map_err(|_| {
Error::InvalidMessage("invalid cell overhead".into())
})?;
i += 1;
}
}
}
}
"slot" if i + 1 < params.len() => {
i += 1;
let min = crate::util::parse::get_time(¶ms[i])
.map_err(|_| Error::InvalidMessage("invalid slot min_delay".into()))?;
slot.min_delay = min.as_nanos() as i64;
has_slot = true;
i += 1;
if i < params.len() && !is_keyword(¶ms[i]) {
let max = crate::util::parse::get_time(¶ms[i])
.map_err(|_| Error::InvalidMessage("invalid slot max_delay".into()))?;
slot.max_delay = max.as_nanos() as i64;
i += 1;
} else {
slot.max_delay = slot.min_delay;
}
while i + 1 < params.len() {
match params[i].as_str() {
"packets" => {
slot.max_packets = params[i + 1].parse().map_err(|_| {
Error::InvalidMessage("invalid slot packets".into())
})?;
i += 2;
}
"bytes" => {
slot.max_bytes = crate::util::parse::get_size(¶ms[i + 1])
.map_err(|_| Error::InvalidMessage("invalid slot bytes".into()))?
as i32;
i += 2;
}
_ => break,
}
}
}
"ecn" => {
has_ecn = true;
i += 1;
}
_ => i += 1,
}
}
if has_reorder && latency64.is_none() {
return Err(Error::InvalidMessage(
"netem: reorder requires delay to be specified".into(),
));
}
if has_reorder && qopt.gap == 0 {
qopt.gap = 1;
}
if has_ecn && qopt.loss == 0 {
return Err(Error::InvalidMessage(
"netem: ecn requires loss to be specified".into(),
));
}
builder.append(&qopt);
if let Some(lat) = latency64 {
builder.append_attr(TCA_NETEM_LATENCY64, &lat.to_ne_bytes());
}
if let Some(jit) = jitter64 {
builder.append_attr(TCA_NETEM_JITTER64, &jit.to_ne_bytes());
}
if has_corr {
builder.append_attr(TCA_NETEM_CORR, corr.as_bytes());
}
if has_reorder {
builder.append_attr(TCA_NETEM_REORDER, reorder.as_bytes());
}
if has_corrupt {
builder.append_attr(TCA_NETEM_CORRUPT, corrupt.as_bytes());
}
if has_rate {
builder.append_attr(TCA_NETEM_RATE, rate.as_bytes());
if let Some(r64) = rate64 {
builder.append_attr(TCA_NETEM_RATE64, &r64.to_ne_bytes());
}
}
if has_slot {
builder.append_attr(TCA_NETEM_SLOT, slot.as_bytes());
}
if has_ecn {
builder.append_attr_u32(TCA_NETEM_ECN, 1);
}
Ok(())
}
fn parse_percent(s: &str) -> Result<u32> {
let s = s.trim_end_matches('%');
let percent: f64 = s
.parse()
.map_err(|_| Error::InvalidMessage("invalid percentage".into()))?;
Ok(percent_to_prob(percent))
}
fn is_keyword(s: &str) -> bool {
matches!(
s,
"delay"
| "latency"
| "loss"
| "drop"
| "duplicate"
| "corrupt"
| "reorder"
| "gap"
| "rate"
| "limit"
| "slot"
| "ecn"
| "distribution"
| "random"
| "state"
| "gemodel"
| "packets"
| "bytes"
)
}