use core::{fmt, str::FromStr};
use super::types::tc::tc_handle as raw;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct TcHandle(u32);
impl TcHandle {
pub const ROOT: Self = Self(raw::ROOT);
pub const INGRESS: Self = Self(raw::INGRESS);
pub const CLSACT: Self = Self(raw::CLSACT);
pub const UNSPEC: Self = Self(raw::UNSPEC);
#[inline]
pub const fn new(major: u16, minor: u16) -> Self {
Self(raw::make(major, minor))
}
#[inline]
pub const fn major_only(major: u16) -> Self {
Self::new(major, 0)
}
#[inline]
pub const fn from_raw(raw: u32) -> Self {
Self(raw)
}
#[inline]
pub const fn as_raw(self) -> u32 {
self.0
}
#[inline]
pub const fn major(self) -> u16 {
raw::major(self.0)
}
#[inline]
pub const fn minor(self) -> u16 {
raw::minor(self.0)
}
#[inline]
pub const fn is_root(self) -> bool {
self.0 == raw::ROOT
}
#[inline]
pub const fn is_ingress(self) -> bool {
self.0 == raw::INGRESS
}
#[inline]
pub const fn is_clsact(self) -> bool {
self.0 == raw::CLSACT
}
#[inline]
pub const fn is_unspec(self) -> bool {
self.0 == raw::UNSPEC
}
}
impl fmt::Display for TcHandle {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&raw::format(self.0))
}
}
impl FromStr for TcHandle {
type Err = TcHandleParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
raw::parse(s)
.map(Self)
.ok_or_else(|| TcHandleParseError::Invalid(s.to_string()))
}
}
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum TcHandleParseError {
#[error("invalid TC handle: {0}")]
Invalid(String),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct FilterPriority(u16);
impl FilterPriority {
pub const OPERATOR_BAND_START: u16 = 1;
pub const RESERVED_BAND_START: u16 = 50;
pub const RECIPE_BAND_START: u16 = 100;
pub const APP_BAND_START: u16 = 200;
pub const SYSTEM_BAND_START: u16 = 1000;
#[inline]
pub const fn new(value: u16) -> Self {
Self(value)
}
#[inline]
pub const fn as_u16(self) -> u16 {
self.0
}
#[inline]
pub const fn recipe(offset: u16) -> Self {
let v = Self::RECIPE_BAND_START.saturating_add(offset);
Self(if v >= Self::APP_BAND_START {
Self::APP_BAND_START - 1
} else {
v
})
}
#[inline]
pub const fn app(offset: u16) -> Self {
let v = Self::APP_BAND_START.saturating_add(offset);
Self(if v >= Self::SYSTEM_BAND_START {
Self::SYSTEM_BAND_START - 1
} else {
v
})
}
#[inline]
pub const fn system(offset: u16) -> Self {
Self(Self::SYSTEM_BAND_START.saturating_add(offset))
}
}
impl fmt::Display for FilterPriority {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl FromStr for FilterPriority {
type Err = core::num::ParseIntError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
s.parse().map(Self)
}
}
impl From<u16> for FilterPriority {
#[inline]
fn from(value: u16) -> Self {
Self(value)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn tc_handle_construction_packs_correctly() {
let h = TcHandle::new(1, 10);
assert_eq!(h.as_raw(), 0x0001_000A);
assert_eq!(h.major(), 1);
assert_eq!(h.minor(), 10);
}
#[test]
fn tc_handle_major_only() {
let h = TcHandle::major_only(1);
assert_eq!(h.as_raw(), 0x0001_0000);
assert_eq!(h.major(), 1);
assert_eq!(h.minor(), 0);
}
#[test]
fn tc_handle_constants() {
assert_eq!(TcHandle::ROOT.as_raw(), 0xFFFF_FFFF);
assert_eq!(TcHandle::INGRESS.as_raw(), 0xFFFF_FFF1);
assert_eq!(TcHandle::CLSACT.as_raw(), 0xFFFF_FFF2);
assert_eq!(TcHandle::UNSPEC.as_raw(), 0);
assert!(TcHandle::ROOT.is_root());
assert!(TcHandle::INGRESS.is_ingress());
assert!(TcHandle::CLSACT.is_clsact());
assert!(TcHandle::UNSPEC.is_unspec());
}
#[test]
fn tc_handle_display_basic() {
assert_eq!(TcHandle::new(1, 10).to_string(), "1:a");
assert_eq!(TcHandle::major_only(1).to_string(), "1:");
assert_eq!(TcHandle::ROOT.to_string(), "root");
assert_eq!(TcHandle::INGRESS.to_string(), "ingress");
assert_eq!(TcHandle::CLSACT.to_string(), "clsact");
assert_eq!(TcHandle::UNSPEC.to_string(), "none");
}
#[test]
fn tc_handle_fromstr_basic() {
assert_eq!("1:a".parse::<TcHandle>().unwrap(), TcHandle::new(1, 10));
assert_eq!("1:".parse::<TcHandle>().unwrap(), TcHandle::major_only(1));
assert_eq!("root".parse::<TcHandle>().unwrap(), TcHandle::ROOT);
assert_eq!("ingress".parse::<TcHandle>().unwrap(), TcHandle::INGRESS);
assert_eq!("clsact".parse::<TcHandle>().unwrap(), TcHandle::CLSACT);
assert_eq!("none".parse::<TcHandle>().unwrap(), TcHandle::UNSPEC);
}
#[test]
fn tc_handle_fromstr_rejects_garbage() {
assert!("".parse::<TcHandle>().is_err());
assert!("1".parse::<TcHandle>().is_err());
assert!("1:zzzz".parse::<TcHandle>().is_err());
assert!("zzzz:1".parse::<TcHandle>().is_err());
}
#[test]
fn tc_handle_display_parse_roundtrip() {
for h in [
TcHandle::ROOT,
TcHandle::INGRESS,
TcHandle::CLSACT,
TcHandle::UNSPEC,
TcHandle::new(1, 10),
TcHandle::new(0xff, 0xff),
TcHandle::major_only(1),
TcHandle::major_only(0xffff),
] {
let s = h.to_string();
let parsed: TcHandle = s.parse().unwrap();
assert_eq!(parsed, h, "roundtrip failed for {h:?} via {s:?}");
}
}
#[test]
fn tc_handle_from_raw() {
let h = TcHandle::from_raw(0x0001_000A);
assert_eq!(h, TcHandle::new(1, 10));
}
#[test]
fn tc_handle_ord() {
assert!(TcHandle::new(1, 1) < TcHandle::new(1, 2));
assert!(TcHandle::new(1, 0xff) < TcHandle::new(2, 0));
}
#[test]
fn filter_priority_new() {
assert_eq!(FilterPriority::new(100).as_u16(), 100);
assert_eq!(FilterPriority::new(0).as_u16(), 0);
assert_eq!(FilterPriority::new(u16::MAX).as_u16(), u16::MAX);
}
#[test]
fn filter_priority_recipe_band() {
assert_eq!(FilterPriority::recipe(0).as_u16(), 100);
assert_eq!(FilterPriority::recipe(5).as_u16(), 105);
assert_eq!(FilterPriority::recipe(99).as_u16(), 199);
assert_eq!(FilterPriority::recipe(100).as_u16(), 199);
assert_eq!(FilterPriority::recipe(u16::MAX).as_u16(), 199);
}
#[test]
fn filter_priority_app_band() {
assert_eq!(FilterPriority::app(0).as_u16(), 200);
assert_eq!(FilterPriority::app(5).as_u16(), 205);
assert_eq!(FilterPriority::app(799).as_u16(), 999);
assert_eq!(FilterPriority::app(800).as_u16(), 999);
}
#[test]
fn filter_priority_system_band() {
assert_eq!(FilterPriority::system(0).as_u16(), 1000);
assert_eq!(FilterPriority::system(5).as_u16(), 1005);
assert_eq!(FilterPriority::system(u16::MAX).as_u16(), u16::MAX);
}
#[test]
fn filter_priority_from_u16() {
let p: FilterPriority = 42u16.into();
assert_eq!(p.as_u16(), 42);
}
#[test]
fn filter_priority_display_fromstr_roundtrip() {
let p = FilterPriority::new(100);
assert_eq!(p.to_string(), "100");
let parsed: FilterPriority = "100".parse().unwrap();
assert_eq!(parsed, p);
}
#[test]
fn filter_priority_ord() {
assert!(FilterPriority::new(50) < FilterPriority::new(100));
}
}