use crate::macros::{GenlMessage, NetlinkAttrs, __rt};
use crate::netlink::attr::AttrIter;
use crate::netlink::MessageBuilder;
use crate::{Error, Result};
use super::types::{
NetShaperAttr, NetShaperCapsAttr, NetShaperCmd, NetShaperHandleAttr, NetShaperMetric,
NetShaperScope,
};
#[derive(NetlinkAttrs, Debug, Default, Clone, Copy, PartialEq, Eq)]
pub struct NetShaperHandle {
#[genl_attr(NetShaperHandleAttr::Scope, repr = "u32")]
pub scope: Option<NetShaperScope>,
#[genl_attr(NetShaperHandleAttr::Id)]
pub id: Option<u32>,
}
impl NetShaperHandle {
pub fn new(scope: NetShaperScope, id: u32) -> Self {
Self {
scope: Some(scope),
id: Some(id),
}
}
pub const fn netdev() -> Self {
Self {
scope: Some(NetShaperScope::Netdev),
id: Some(0),
}
}
pub fn queue(index: u32) -> Self {
Self::new(NetShaperScope::Queue, index)
}
pub fn node(id: u32) -> Self {
Self::new(NetShaperScope::Node, id)
}
}
#[derive(GenlMessage, Debug, Default, Clone)]
#[genl_message(cmd = NetShaperCmd::Get)]
pub struct NetShaperGetRequest {
#[genl_attr(NetShaperAttr::Ifindex)]
pub ifindex: u32,
#[genl_attr(NetShaperAttr::Handle, nested)]
pub handle: Option<NetShaperHandle>,
}
impl NetShaperGetRequest {
pub fn by_handle(ifindex: u32, handle: NetShaperHandle) -> Self {
Self {
ifindex,
handle: Some(handle),
}
}
pub fn dump(ifindex: u32) -> Self {
Self {
ifindex,
handle: None,
}
}
}
#[derive(GenlMessage, Debug, Default, Clone)]
#[genl_message(cmd = NetShaperCmd::Get)]
#[non_exhaustive]
pub struct NetShaperReply {
#[genl_attr(NetShaperAttr::Ifindex)]
pub ifindex: u32,
#[genl_attr(NetShaperAttr::Handle, nested)]
pub handle: Option<NetShaperHandle>,
#[genl_attr(NetShaperAttr::Parent, nested)]
pub parent: Option<NetShaperHandle>,
#[genl_attr(NetShaperAttr::Metric, repr = "u32")]
pub metric: Option<NetShaperMetric>,
#[genl_attr(NetShaperAttr::BwMin)]
pub bw_min: Option<u64>,
#[genl_attr(NetShaperAttr::BwMax)]
pub bw_max: Option<u64>,
#[genl_attr(NetShaperAttr::Burst)]
pub burst: Option<u64>,
#[genl_attr(NetShaperAttr::Priority)]
pub priority: Option<u32>,
#[genl_attr(NetShaperAttr::Weight)]
pub weight: Option<u32>,
}
#[derive(GenlMessage, Debug, Default, Clone)]
#[genl_message(cmd = NetShaperCmd::Set)]
pub struct NetShaperSetRequest {
#[genl_attr(NetShaperAttr::Ifindex)]
pub ifindex: u32,
#[genl_attr(NetShaperAttr::Handle, nested)]
pub handle: Option<NetShaperHandle>,
#[genl_attr(NetShaperAttr::Metric, repr = "u32")]
pub metric: Option<NetShaperMetric>,
#[genl_attr(NetShaperAttr::BwMin)]
pub bw_min: Option<u64>,
#[genl_attr(NetShaperAttr::BwMax)]
pub bw_max: Option<u64>,
#[genl_attr(NetShaperAttr::Burst)]
pub burst: Option<u64>,
#[genl_attr(NetShaperAttr::Priority)]
pub priority: Option<u32>,
#[genl_attr(NetShaperAttr::Weight)]
pub weight: Option<u32>,
}
impl NetShaperSetRequest {
pub fn new(ifindex: u32, handle: NetShaperHandle) -> Self {
Self {
ifindex,
handle: Some(handle),
..Self::default()
}
}
#[must_use]
pub fn metric(mut self, metric: NetShaperMetric) -> Self {
self.metric = Some(metric);
self
}
#[must_use]
pub fn bw_min(mut self, bw: u64) -> Self {
self.bw_min = Some(bw);
self
}
#[must_use]
pub fn bw_max(mut self, bw: u64) -> Self {
self.bw_max = Some(bw);
self
}
#[must_use]
pub fn burst(mut self, bytes: u64) -> Self {
self.burst = Some(bytes);
self
}
#[must_use]
pub fn priority(mut self, prio: u32) -> Self {
self.priority = Some(prio);
self
}
#[must_use]
pub fn weight(mut self, weight: u32) -> Self {
self.weight = Some(weight);
self
}
}
#[derive(GenlMessage, Debug, Default, Clone)]
#[genl_message(cmd = NetShaperCmd::Delete)]
pub struct NetShaperDeleteRequest {
#[genl_attr(NetShaperAttr::Ifindex)]
pub ifindex: u32,
#[genl_attr(NetShaperAttr::Handle, nested)]
pub handle: Option<NetShaperHandle>,
}
impl NetShaperDeleteRequest {
pub fn new(ifindex: u32, handle: NetShaperHandle) -> Self {
Self {
ifindex,
handle: Some(handle),
}
}
}
#[derive(GenlMessage, Debug, Default, Clone)]
#[genl_message(cmd = NetShaperCmd::CapGet)]
pub struct NetShaperCapsGetRequest {
#[genl_attr(NetShaperCapsAttr::Ifindex)]
pub ifindex: u32,
#[genl_attr(NetShaperCapsAttr::Scope, repr = "u32")]
pub scope: Option<NetShaperScope>,
}
impl NetShaperCapsGetRequest {
pub fn for_scope(ifindex: u32, scope: NetShaperScope) -> Self {
Self {
ifindex,
scope: Some(scope),
}
}
pub fn dump(ifindex: u32) -> Self {
Self {
ifindex,
scope: None,
}
}
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub struct NetShaperCapsReply {
pub ifindex: u32,
pub scope: Option<NetShaperScope>,
pub support_metric_bps: bool,
pub support_metric_pps: bool,
pub support_nesting: bool,
pub support_bw_min: bool,
pub support_bw_max: bool,
pub support_burst: bool,
pub support_priority: bool,
pub support_weight: bool,
}
impl GenlMessage for NetShaperCapsReply {
const CMD: u8 = NetShaperCmd::CapGet as u8;
fn to_bytes(&self, _builder: &mut MessageBuilder) -> Result<()> {
Err(Error::InvalidMessage(
"NetShaperCapsReply is read-only; build a NetShaperCapsGetRequest to query caps".into(),
))
}
fn from_bytes(payload: &[u8]) -> Result<Self> {
let mut out = Self::default();
for (ty, body) in AttrIter::new(payload) {
if ty == NetShaperCapsAttr::Ifindex as u16 {
out.ifindex = __rt::parse_u32_attr(body)?;
} else if ty == NetShaperCapsAttr::Scope as u16 {
let raw = __rt::parse_u32_attr(body)?;
out.scope = NetShaperScope::try_from(raw).ok();
} else if ty == NetShaperCapsAttr::SupportMetricBps as u16 {
out.support_metric_bps = true;
} else if ty == NetShaperCapsAttr::SupportMetricPps as u16 {
out.support_metric_pps = true;
} else if ty == NetShaperCapsAttr::SupportNesting as u16 {
out.support_nesting = true;
} else if ty == NetShaperCapsAttr::SupportBwMin as u16 {
out.support_bw_min = true;
} else if ty == NetShaperCapsAttr::SupportBwMax as u16 {
out.support_bw_max = true;
} else if ty == NetShaperCapsAttr::SupportBurst as u16 {
out.support_burst = true;
} else if ty == NetShaperCapsAttr::SupportPriority as u16 {
out.support_priority = true;
} else if ty == NetShaperCapsAttr::SupportWeight as u16 {
out.support_weight = true;
}
}
Ok(out)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::macros::__rt;
fn payload(write: impl FnOnce(&mut MessageBuilder)) -> Vec<u8> {
let mut b = MessageBuilder::new(0, 0);
let start = b.len();
write(&mut b);
b.as_bytes()[start..].to_vec()
}
#[test]
fn get_request_by_handle_emits_ifindex_and_nested_handle() {
let req = NetShaperGetRequest::by_handle(3, NetShaperHandle::queue(7));
let body = payload(|b| req.to_bytes(b).unwrap());
let attrs: Vec<u16> = __rt::attr_iter(&body).map(|(ty, _)| ty).collect();
assert!(attrs.contains(&(NetShaperAttr::Ifindex as u16)));
assert!(attrs.contains(&(NetShaperAttr::Handle as u16)));
assert_eq!(attrs.len(), 2);
}
#[test]
fn get_request_dump_emits_only_ifindex() {
let req = NetShaperGetRequest::dump(2);
let body = payload(|b| req.to_bytes(b).unwrap());
let attrs: Vec<u16> = __rt::attr_iter(&body).map(|(ty, _)| ty).collect();
assert_eq!(attrs, vec![NetShaperAttr::Ifindex as u16]);
}
#[test]
fn set_request_builder_chains_optional_fields() {
let req = NetShaperSetRequest::new(4, NetShaperHandle::queue(1))
.metric(NetShaperMetric::Bps)
.bw_max(1_000_000_000)
.burst(1 << 16)
.priority(5);
let body = payload(|b| req.to_bytes(b).unwrap());
let attrs: Vec<u16> = __rt::attr_iter(&body).map(|(ty, _)| ty).collect();
assert_eq!(attrs.len(), 6);
for required in [
NetShaperAttr::Ifindex,
NetShaperAttr::Handle,
NetShaperAttr::Metric,
NetShaperAttr::BwMax,
NetShaperAttr::Burst,
NetShaperAttr::Priority,
] {
assert!(attrs.contains(&(required as u16)), "missing {required:?}");
}
assert!(!attrs.contains(&(NetShaperAttr::BwMin as u16)));
assert!(!attrs.contains(&(NetShaperAttr::Weight as u16)));
}
#[test]
fn delete_request_emits_two_attrs() {
let req = NetShaperDeleteRequest::new(1, NetShaperHandle::queue(0));
let body = payload(|b| req.to_bytes(b).unwrap());
let attrs: Vec<u16> = __rt::attr_iter(&body).map(|(ty, _)| ty).collect();
assert_eq!(attrs.len(), 2);
}
#[test]
fn caps_reply_parses_flag_presence() {
let body = payload(|b| {
__rt::emit_u32_attr(b, NetShaperCapsAttr::Ifindex as u16, 7);
__rt::emit_u32_attr(
b,
NetShaperCapsAttr::Scope as u16,
NetShaperScope::Queue as u32,
);
__rt::emit_flag_attr(b, NetShaperCapsAttr::SupportMetricBps as u16);
__rt::emit_flag_attr(b, NetShaperCapsAttr::SupportBwMax as u16);
__rt::emit_flag_attr(b, NetShaperCapsAttr::SupportPriority as u16);
});
let reply = NetShaperCapsReply::from_bytes(&body).expect("parse");
assert_eq!(reply.ifindex, 7);
assert_eq!(reply.scope, Some(NetShaperScope::Queue));
assert!(reply.support_metric_bps);
assert!(!reply.support_metric_pps);
assert!(reply.support_bw_max);
assert!(!reply.support_bw_min);
assert!(reply.support_priority);
assert!(!reply.support_weight);
assert!(!reply.support_nesting);
assert!(!reply.support_burst);
}
#[test]
fn caps_reply_to_bytes_is_read_only() {
let r = NetShaperCapsReply::default();
let mut b = MessageBuilder::new(0, 0);
assert!(r.to_bytes(&mut b).is_err());
}
#[test]
fn handle_helpers_set_expected_scope_and_id() {
let h = NetShaperHandle::netdev();
assert_eq!(h.scope, Some(NetShaperScope::Netdev));
assert_eq!(h.id, Some(0));
let q = NetShaperHandle::queue(5);
assert_eq!(q.scope, Some(NetShaperScope::Queue));
assert_eq!(q.id, Some(5));
let n = NetShaperHandle::node(42);
assert_eq!(n.scope, Some(NetShaperScope::Node));
assert_eq!(n.id, Some(42));
}
#[test]
fn reply_parses_full_state() {
let mut handle_payload = MessageBuilder::new(0, 0);
let h_start = handle_payload.len();
__rt::emit_u32_attr(
&mut handle_payload,
NetShaperHandleAttr::Scope as u16,
NetShaperScope::Queue as u32,
);
__rt::emit_u32_attr(&mut handle_payload, NetShaperHandleAttr::Id as u16, 3);
let handle_bytes = handle_payload.as_bytes()[h_start..].to_vec();
let body = payload(|b| {
__rt::emit_u32_attr(b, NetShaperAttr::Ifindex as u16, 9);
__rt::emit_bytes_attr(b, NetShaperAttr::Handle as u16, &handle_bytes);
__rt::emit_u32_attr(
b,
NetShaperAttr::Metric as u16,
NetShaperMetric::Bps as u32,
);
__rt::emit_u64_attr(b, NetShaperAttr::BwMax as u16, 5_000_000_000);
__rt::emit_u32_attr(b, NetShaperAttr::Priority as u16, 1);
});
let reply = NetShaperReply::from_bytes(&body).expect("parse");
assert_eq!(reply.ifindex, 9);
assert_eq!(
reply.handle,
Some(NetShaperHandle::new(NetShaperScope::Queue, 3))
);
assert_eq!(reply.parent, None);
assert_eq!(reply.metric, Some(NetShaperMetric::Bps));
assert_eq!(reply.bw_max, Some(5_000_000_000));
assert_eq!(reply.priority, Some(1));
assert_eq!(reply.weight, None);
}
}