//! Traffic control message types.

use crate::netlink::error::{Error, Result};
use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};

/// Traffic control message (struct tcmsg).
#[repr(C)]
#[derive(Debug, Clone, Copy, Default, FromBytes, IntoBytes, Immutable, KnownLayout)]
pub struct TcMsg {
    /// Address family.
    pub tcm_family: u8,
    /// Padding.
    pub tcm_pad1: u8,
    /// Padding.
    pub tcm_pad2: u16,
    /// Interface index.
    pub tcm_ifindex: i32,
    /// Qdisc handle.
    pub tcm_handle: u32,
    /// Parent qdisc.
    pub tcm_parent: u32,
    /// Info (depends on message type).
    pub tcm_info: u32,
}

impl TcMsg {
    /// Size of this structure.
    pub const SIZE: usize = std::mem::size_of::<Self>();

    /// Create a new TC message.
    pub fn new() -> Self {
        Self::default()
    }

    /// Set the interface index.
    pub fn with_ifindex(mut self, ifindex: i32) -> Self {
        self.tcm_ifindex = ifindex;
        self
    }

    /// Set the handle.
    pub fn with_handle(mut self, handle: u32) -> Self {
        self.tcm_handle = handle;
        self
    }

    /// Set the parent.
    pub fn with_parent(mut self, parent: u32) -> Self {
        self.tcm_parent = parent;
        self
    }

    /// Set the info field.
    ///
    /// For filters, this contains (protocol << 16) | priority.
    pub fn with_info(mut self, info: u32) -> Self {
        self.tcm_info = info;
        self
    }

    /// Convert to bytes.
    pub fn as_bytes(&self) -> &[u8] {
        <Self as IntoBytes>::as_bytes(self)
    }

    /// Parse from bytes.
    pub fn from_bytes(data: &[u8]) -> Result<&Self> {
        Self::ref_from_prefix(data)
            .map(|(r, _)| r)
            .map_err(|_| Error::Truncated {
                expected: Self::SIZE,
                actual: data.len(),
            })
    }
}

/// Traffic control attributes (TCA_*).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u16)]
pub enum TcaAttr {
    Unspec = 0,
    Kind = 1,
    Options = 2,
    Stats = 3,
    Xstats = 4,
    Rate = 5,
    Fcnt = 6,
    Stats2 = 7,
    Stab = 8,
    Pad = 9,
    DumpInvisible = 10,
    Chain = 11,
    HwOffload = 12,
    IngressBlock = 13,
    EgressBlock = 14,
    DumpFlags = 15,
    ExtWarnMsg = 16,
}

impl From<u16> for TcaAttr {
    fn from(val: u16) -> Self {
        match val {
            0 => Self::Unspec,
            1 => Self::Kind,
            2 => Self::Options,
            3 => Self::Stats,
            4 => Self::Xstats,
            5 => Self::Rate,
            6 => Self::Fcnt,
            7 => Self::Stats2,
            8 => Self::Stab,
            9 => Self::Pad,
            10 => Self::DumpInvisible,
            11 => Self::Chain,
            12 => Self::HwOffload,
            13 => Self::IngressBlock,
            14 => Self::EgressBlock,
            15 => Self::DumpFlags,
            16 => Self::ExtWarnMsg,
            _ => Self::Unspec,
        }
    }
}

/// Action table attribute (for RTM_*ACTION messages).
pub const TCA_ACT_TAB: u16 = 1;

/// Root action attributes (for action dump).
pub const TCA_ROOT_UNSPEC: u16 = 0;
pub const TCA_ROOT_TAB: u16 = 1;
pub const TCA_ROOT_FLAGS: u16 = 2;
pub const TCA_ROOT_COUNT: u16 = 3;
pub const TCA_ROOT_TIME_DELTA: u16 = 4;

/// Special handle values.
pub mod tc_handle {
    /// Root qdisc.
    pub const ROOT: u32 = 0xFFFFFFFF;
    /// Ingress qdisc.
    pub const INGRESS: u32 = 0xFFFFFFF1;
    /// Clsact qdisc.
    pub const CLSACT: u32 = 0xFFFFFFF2;
    /// Unspecified.
    pub const UNSPEC: u32 = 0;

    /// Make a handle from major:minor.
    pub const fn make(major: u16, minor: u16) -> u32 {
        ((major as u32) << 16) | (minor as u32)
    }

    /// Get the major number from a handle.
    pub const fn major(handle: u32) -> u16 {
        (handle >> 16) as u16
    }

    /// Get the minor number from a handle.
    pub const fn minor(handle: u32) -> u16 {
        (handle & 0xFFFF) as u16
    }

    /// Format a handle as major:minor string.
    pub fn format(handle: u32) -> String {
        if handle == ROOT {
            "root".to_string()
        } else if handle == INGRESS {
            "ingress".to_string()
        } else if handle == CLSACT {
            "clsact".to_string()
        } else if handle == UNSPEC {
            "none".to_string()
        } else {
            let maj = major(handle);
            let min = minor(handle);
            if min == 0 {
                format!("{:x}:", maj)
            } else {
                format!("{:x}:{:x}", maj, min)
            }
        }
    }

    /// Parse a handle from major:minor string.
    pub fn parse(s: &str) -> Option<u32> {
        match s {
            "root" => Some(ROOT),
            "ingress" => Some(INGRESS),
            "clsact" => Some(CLSACT),
            "none" => Some(UNSPEC),
            _ => {
                let parts: Vec<&str> = s.split(':').collect();
                if parts.len() == 2 {
                    let major = u16::from_str_radix(parts[0], 16).ok()?;
                    let minor = if parts[1].is_empty() {
                        0
                    } else {
                        u16::from_str_radix(parts[1], 16).ok()?
                    };
                    Some(make(major, minor))
                } else {
                    None
                }
            }
        }
    }
}

/// TC statistics (struct tc_stats).
#[repr(C)]
#[derive(Debug, Clone, Copy, Default, FromBytes, Immutable, KnownLayout)]
pub struct TcStats {
    /// Bytes seen.
    pub bytes: u64,
    /// Packets seen.
    pub packets: u32,
    /// Packets dropped.
    pub drops: u32,
    /// Packets overlimits.
    pub overlimits: u32,
    /// Current queue length.
    pub bps: u32,
    /// Current packet rate.
    pub pps: u32,
    /// Queue length.
    pub qlen: u32,
    /// Backlog bytes.
    pub backlog: u32,
}

impl TcStats {
    /// Parse from bytes.
    pub fn from_bytes(data: &[u8]) -> Option<&Self> {
        Self::ref_from_prefix(data).map(|(r, _)| r).ok()
    }
}

/// TC statistics 2 (struct gnet_stats_basic).
#[repr(C)]
#[derive(Debug, Clone, Copy, Default, FromBytes, Immutable, KnownLayout)]
pub struct GnetStatsBasic {
    pub bytes: u64,
    pub packets: u32,
}

/// TC queue stats (struct gnet_stats_queue).
#[repr(C)]
#[derive(Debug, Clone, Copy, Default, FromBytes, Immutable, KnownLayout)]
pub struct GnetStatsQueue {
    pub qlen: u32,
    pub backlog: u32,
    pub drops: u32,
    pub requeues: u32,
    pub overlimits: u32,
}

/// TCA_STATS2 nested attributes.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u16)]
pub enum TcaStats {
    Unspec = 0,
    Basic = 1,
    RateEst = 2,
    Queue = 3,
    App = 4,
    RateEst64 = 5,
    Pad = 6,
    BasicHw = 7,
    Pkt64 = 8,
}

impl From<u16> for TcaStats {
    fn from(val: u16) -> Self {
        match val {
            1 => Self::Basic,
            2 => Self::RateEst,
            3 => Self::Queue,
            4 => Self::App,
            5 => Self::RateEst64,
            6 => Self::Pad,
            7 => Self::BasicHw,
            8 => Self::Pkt64,
            _ => Self::Unspec,
        }
    }
}

/// TC rate estimator parameters.
#[repr(C)]
#[derive(Debug, Clone, Copy, Default, FromBytes, IntoBytes, Immutable, KnownLayout)]
pub struct TcEstimator {
    pub interval: i8,
    pub ewma_log: u8,
}

/// Common qdisc options.
pub mod qdisc {
    /// HTB qdisc-specific attributes.
    pub mod htb {
        use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};

        pub const TCA_HTB_UNSPEC: u16 = 0;
        pub const TCA_HTB_PARMS: u16 = 1;
        pub const TCA_HTB_INIT: u16 = 2;
        pub const TCA_HTB_CTAB: u16 = 3;
        pub const TCA_HTB_RTAB: u16 = 4;
        pub const TCA_HTB_DIRECT_QLEN: u16 = 5;
        pub const TCA_HTB_RATE64: u16 = 6;
        pub const TCA_HTB_CEIL64: u16 = 7;
        pub const TCA_HTB_OFFLOAD: u16 = 8;

        /// HTB global parameters (struct tc_htb_glob).
        #[repr(C)]
        #[derive(Debug, Clone, Copy, Default, FromBytes, IntoBytes, Immutable, KnownLayout)]
        pub struct TcHtbGlob {
            pub version: u32,
            pub rate2quantum: u32,
            pub defcls: u32,
            pub debug: u32,
            pub direct_pkts: u32,
        }

        impl TcHtbGlob {
            pub const SIZE: usize = std::mem::size_of::<Self>();

            pub fn new() -> Self {
                Self {
                    version: 3,
                    rate2quantum: 10,
                    defcls: 0,
                    debug: 0,
                    direct_pkts: 0,
                }
            }

            pub fn with_default(mut self, defcls: u32) -> Self {
                self.defcls = defcls;
                self
            }

            pub fn as_bytes(&self) -> &[u8] {
                <Self as IntoBytes>::as_bytes(self)
            }

            /// Parse from bytes.
            pub fn from_bytes(data: &[u8]) -> Option<&Self> {
                Self::ref_from_prefix(data).map(|(r, _)| r).ok()
            }
        }

        /// HTB class parameters (struct tc_htb_opt).
        #[repr(C)]
        #[derive(Debug, Clone, Copy, Default, FromBytes, IntoBytes, Immutable, KnownLayout)]
        pub struct TcHtbOpt {
            pub rate: super::TcRateSpec,
            pub ceil: super::TcRateSpec,
            pub buffer: u32,
            pub cbuffer: u32,
            pub quantum: u32,
            pub level: u32,
            pub prio: u32,
        }

        impl TcHtbOpt {
            pub const SIZE: usize = std::mem::size_of::<Self>();

            pub fn as_bytes(&self) -> &[u8] {
                <Self as IntoBytes>::as_bytes(self)
            }

            /// Parse from bytes.
            pub fn from_bytes(data: &[u8]) -> Option<&Self> {
                Self::ref_from_prefix(data).map(|(r, _)| r).ok()
            }
        }
    }

    /// FQ_CODEL qdisc-specific attributes.
    pub mod fq_codel {
        pub const TCA_FQ_CODEL_UNSPEC: u16 = 0;
        pub const TCA_FQ_CODEL_TARGET: u16 = 1;
        pub const TCA_FQ_CODEL_LIMIT: u16 = 2;
        pub const TCA_FQ_CODEL_INTERVAL: u16 = 3;
        pub const TCA_FQ_CODEL_ECN: u16 = 4;
        pub const TCA_FQ_CODEL_FLOWS: u16 = 5;
        pub const TCA_FQ_CODEL_QUANTUM: u16 = 6;
        pub const TCA_FQ_CODEL_CE_THRESHOLD: u16 = 7;
        pub const TCA_FQ_CODEL_DROP_BATCH_SIZE: u16 = 8;
        pub const TCA_FQ_CODEL_MEMORY_LIMIT: u16 = 9;
    }

    /// TBF qdisc-specific attributes.
    pub mod tbf {
        use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};

        pub const TCA_TBF_UNSPEC: u16 = 0;
        pub const TCA_TBF_PARMS: u16 = 1;
        pub const TCA_TBF_RTAB: u16 = 2;
        pub const TCA_TBF_PTAB: u16 = 3;
        pub const TCA_TBF_RATE64: u16 = 4;
        pub const TCA_TBF_PRATE64: u16 = 5;
        pub const TCA_TBF_BURST: u16 = 6;
        pub const TCA_TBF_PBURST: u16 = 7;

        /// TBF parameters (struct tc_tbf_qopt).
        #[repr(C)]
        #[derive(Debug, Clone, Copy, Default, FromBytes, IntoBytes, Immutable, KnownLayout)]
        pub struct TcTbfQopt {
            pub rate: super::TcRateSpec,
            pub peakrate: super::TcRateSpec,
            pub limit: u32,
            pub buffer: u32,
            pub mtu: u32,
        }

        impl TcTbfQopt {
            pub const SIZE: usize = std::mem::size_of::<Self>();

            pub fn as_bytes(&self) -> &[u8] {
                <Self as IntoBytes>::as_bytes(self)
            }
        }
    }

    /// PRIO qdisc-specific attributes.
    pub mod prio {
        use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};

        pub const TCA_PRIO_UNSPEC: u16 = 0;
        pub const TCA_PRIO_MQ: u16 = 1;

        /// PRIO parameters (struct tc_prio_qopt).
        #[repr(C)]
        #[derive(Debug, Clone, Copy, FromBytes, IntoBytes, Immutable, KnownLayout)]
        pub struct TcPrioQopt {
            pub bands: i32,
            pub priomap: [u8; 16],
        }

        impl Default for TcPrioQopt {
            fn default() -> Self {
                Self {
                    bands: 3,
                    // Default priomap: { 1, 2, 2, 2, 1, 2, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1 }
                    priomap: [1, 2, 2, 2, 1, 2, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1],
                }
            }
        }

        impl TcPrioQopt {
            pub const SIZE: usize = std::mem::size_of::<Self>();

            pub fn as_bytes(&self) -> &[u8] {
                <Self as IntoBytes>::as_bytes(self)
            }
        }
    }

    /// SFQ qdisc-specific attributes.
    pub mod sfq {
        use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};

        /// SFQ parameters (struct tc_sfq_qopt_v1).
        #[repr(C)]
        #[derive(Debug, Clone, Copy, Default, FromBytes, IntoBytes, Immutable, KnownLayout)]
        pub struct TcSfqQoptV1 {
            pub v0: TcSfqQopt,
            pub depth: u32,
            pub headdrop: u32,
            pub limit: u32,
            pub qth_min: u32,
            pub qth_max: u32,
            pub wlog: u8,
            pub plog: u8,
            pub scell_log: u8,
            pub flags: u8,
            pub max_p: u32,
            // ... additional fields omitted for simplicity
        }

        /// SFQ basic parameters (struct tc_sfq_qopt).
        #[repr(C)]
        #[derive(Debug, Clone, Copy, Default, FromBytes, IntoBytes, Immutable, KnownLayout)]
        pub struct TcSfqQopt {
            pub quantum: u32,
            pub perturb_period: i32,
            pub limit: u32,
            pub divisor: u32,
            pub flows: u32,
        }

        impl TcSfqQopt {
            pub const SIZE: usize = std::mem::size_of::<Self>();

            pub fn as_bytes(&self) -> &[u8] {
                <Self as IntoBytes>::as_bytes(self)
            }
        }
    }

    /// Netem qdisc-specific attributes.
    pub mod netem {
        use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};

        pub const TCA_NETEM_UNSPEC: u16 = 0;
        pub const TCA_NETEM_CORR: u16 = 1;
        pub const TCA_NETEM_DELAY_DIST: u16 = 2;
        pub const TCA_NETEM_REORDER: u16 = 3;
        pub const TCA_NETEM_CORRUPT: u16 = 4;
        pub const TCA_NETEM_LOSS: u16 = 5;
        pub const TCA_NETEM_RATE: u16 = 6;
        pub const TCA_NETEM_ECN: u16 = 7;
        pub const TCA_NETEM_RATE64: u16 = 8;
        pub const TCA_NETEM_PAD: u16 = 9;
        pub const TCA_NETEM_LATENCY64: u16 = 10;
        pub const TCA_NETEM_JITTER64: u16 = 11;
        pub const TCA_NETEM_SLOT: u16 = 12;
        pub const TCA_NETEM_SLOT_DIST: u16 = 13;
        pub const TCA_NETEM_PRNG_SEED: u16 = 14;

        /// Netem loss model types.
        pub const NETEM_LOSS_UNSPEC: u16 = 0;
        pub const NETEM_LOSS_GI: u16 = 1; // General Intuitive - 4 state model
        pub const NETEM_LOSS_GE: u16 = 2; // Gilbert Elliot model

        /// Netem basic options (struct tc_netem_qopt).
        #[repr(C)]
        #[derive(Debug, Clone, Copy, Default, FromBytes, IntoBytes, Immutable, KnownLayout)]
        pub struct TcNetemQopt {
            /// Added delay in microseconds.
            pub latency: u32,
            /// FIFO limit (packets).
            pub limit: u32,
            /// Random packet loss (0=none, ~0=100%).
            pub loss: u32,
            /// Re-ordering gap (0 for none).
            pub gap: u32,
            /// Random packet duplication (0=none, ~0=100%).
            pub duplicate: u32,
            /// Random jitter in latency (microseconds).
            pub jitter: u32,
        }

        impl TcNetemQopt {
            pub const SIZE: usize = std::mem::size_of::<Self>();

            pub fn new() -> Self {
                Self {
                    limit: 1000,
                    ..Default::default()
                }
            }

            pub fn as_bytes(&self) -> &[u8] {
                <Self as IntoBytes>::as_bytes(self)
            }
        }

        /// Netem correlation structure (struct tc_netem_corr).
        #[repr(C)]
        #[derive(Debug, Clone, Copy, Default, FromBytes, IntoBytes, Immutable, KnownLayout)]
        pub struct TcNetemCorr {
            /// Delay correlation.
            pub delay_corr: u32,
            /// Packet loss correlation.
            pub loss_corr: u32,
            /// Duplicate correlation.
            pub dup_corr: u32,
        }

        impl TcNetemCorr {
            pub const SIZE: usize = std::mem::size_of::<Self>();

            pub fn as_bytes(&self) -> &[u8] {
                <Self as IntoBytes>::as_bytes(self)
            }
        }

        /// Netem reorder structure (struct tc_netem_reorder).
        #[repr(C)]
        #[derive(Debug, Clone, Copy, Default, FromBytes, IntoBytes, Immutable, KnownLayout)]
        pub struct TcNetemReorder {
            pub probability: u32,
            pub correlation: u32,
        }

        impl TcNetemReorder {
            pub const SIZE: usize = std::mem::size_of::<Self>();

            pub fn as_bytes(&self) -> &[u8] {
                <Self as IntoBytes>::as_bytes(self)
            }
        }

        /// Netem corrupt structure (struct tc_netem_corrupt).
        #[repr(C)]
        #[derive(Debug, Clone, Copy, Default, FromBytes, IntoBytes, Immutable, KnownLayout)]
        pub struct TcNetemCorrupt {
            pub probability: u32,
            pub correlation: u32,
        }

        impl TcNetemCorrupt {
            pub const SIZE: usize = std::mem::size_of::<Self>();

            pub fn as_bytes(&self) -> &[u8] {
                <Self as IntoBytes>::as_bytes(self)
            }
        }

        /// Netem rate structure (struct tc_netem_rate).
        #[repr(C)]
        #[derive(Debug, Clone, Copy, Default, FromBytes, IntoBytes, Immutable, KnownLayout)]
        pub struct TcNetemRate {
            /// Rate in bytes/s.
            pub rate: u32,
            /// Packet overhead.
            pub packet_overhead: i32,
            /// Cell size.
            pub cell_size: u32,
            /// Cell overhead.
            pub cell_overhead: i32,
        }

        impl TcNetemRate {
            pub const SIZE: usize = std::mem::size_of::<Self>();

            pub fn as_bytes(&self) -> &[u8] {
                <Self as IntoBytes>::as_bytes(self)
            }
        }

        /// Netem slot structure (struct tc_netem_slot).
        #[repr(C)]
        #[derive(Debug, Clone, Copy, Default, FromBytes, IntoBytes, Immutable, KnownLayout)]
        pub struct TcNetemSlot {
            /// Minimum delay in nanoseconds.
            pub min_delay: i64,
            /// Maximum delay in nanoseconds.
            pub max_delay: i64,
            /// Maximum packets per slot.
            pub max_packets: i32,
            /// Maximum bytes per slot.
            pub max_bytes: i32,
            /// Distribution delay in nanoseconds.
            pub dist_delay: i64,
            /// Distribution jitter in nanoseconds.
            pub dist_jitter: i64,
        }

        impl TcNetemSlot {
            pub const SIZE: usize = std::mem::size_of::<Self>();

            pub fn as_bytes(&self) -> &[u8] {
                <Self as IntoBytes>::as_bytes(self)
            }
        }

        /// Gilbert-Intuitive loss model (4 state) (struct tc_netem_gimodel).
        #[repr(C)]
        #[derive(Debug, Clone, Copy, Default, FromBytes, IntoBytes, Immutable, KnownLayout)]
        pub struct TcNetemGiModel {
            pub p13: u32,
            pub p31: u32,
            pub p32: u32,
            pub p14: u32,
            pub p23: u32,
        }

        impl TcNetemGiModel {
            pub const SIZE: usize = std::mem::size_of::<Self>();

            pub fn as_bytes(&self) -> &[u8] {
                <Self as IntoBytes>::as_bytes(self)
            }
        }

        /// Gilbert-Elliot loss model (struct tc_netem_gemodel).
        #[repr(C)]
        #[derive(Debug, Clone, Copy, Default, FromBytes, IntoBytes, Immutable, KnownLayout)]
        pub struct TcNetemGeModel {
            pub p: u32,
            pub r: u32,
            pub h: u32,
            pub k1: u32,
        }

        impl TcNetemGeModel {
            pub const SIZE: usize = std::mem::size_of::<Self>();

            pub fn as_bytes(&self) -> &[u8] {
                <Self as IntoBytes>::as_bytes(self)
            }
        }

        /// Convert percentage (0.0-100.0) to netem probability (0 to u32::MAX).
        pub fn percent_to_prob(percent: f64) -> u32 {
            ((percent / 100.0) * (u32::MAX as f64)) as u32
        }

        /// Convert netem probability to percentage.
        pub fn prob_to_percent(prob: u32) -> f64 {
            (prob as f64 / u32::MAX as f64) * 100.0
        }
    }

    /// Rate specification (struct tc_ratespec).
    #[repr(C)]
    #[derive(
        Debug,
        Clone,
        Copy,
        Default,
        zerocopy::FromBytes,
        zerocopy::IntoBytes,
        zerocopy::Immutable,
        zerocopy::KnownLayout,
    )]
    pub struct TcRateSpec {
        pub cell_log: u8,
        pub linklayer: u8,
        pub overhead: u16,
        pub cell_align: i16,
        pub mpu: u16,
        pub rate: u32,
    }

    impl TcRateSpec {
        pub const SIZE: usize = std::mem::size_of::<Self>();

        pub fn new(rate: u32) -> Self {
            Self {
                rate,
                ..Default::default()
            }
        }

        pub fn as_bytes(&self) -> &[u8] {
            <Self as zerocopy::IntoBytes>::as_bytes(self)
        }
    }

    /// CoDel qdisc-specific attributes.
    pub mod codel {
        pub const TCA_CODEL_UNSPEC: u16 = 0;
        pub const TCA_CODEL_TARGET: u16 = 1;
        pub const TCA_CODEL_LIMIT: u16 = 2;
        pub const TCA_CODEL_INTERVAL: u16 = 3;
        pub const TCA_CODEL_ECN: u16 = 4;
        pub const TCA_CODEL_CE_THRESHOLD: u16 = 5;
    }

    /// FQ (Fair Queue) qdisc-specific attributes.
    pub mod fq {
        pub const TCA_FQ_UNSPEC: u16 = 0;
        pub const TCA_FQ_PLIMIT: u16 = 1;
        pub const TCA_FQ_FLOW_PLIMIT: u16 = 2;
        pub const TCA_FQ_QUANTUM: u16 = 3;
        pub const TCA_FQ_INITIAL_QUANTUM: u16 = 4;
        pub const TCA_FQ_RATE_ENABLE: u16 = 5;
        pub const TCA_FQ_FLOW_DEFAULT_RATE: u16 = 6;
        pub const TCA_FQ_FLOW_MAX_RATE: u16 = 7;
        pub const TCA_FQ_BUCKETS_LOG: u16 = 8;
        pub const TCA_FQ_FLOW_REFILL_DELAY: u16 = 9;
        pub const TCA_FQ_ORPHAN_MASK: u16 = 10;
        pub const TCA_FQ_LOW_RATE_THRESHOLD: u16 = 11;
        pub const TCA_FQ_CE_THRESHOLD: u16 = 12;
        pub const TCA_FQ_TIMER_SLACK: u16 = 13;
        pub const TCA_FQ_HORIZON: u16 = 14;
        pub const TCA_FQ_HORIZON_DROP: u16 = 15;
        pub const TCA_FQ_PRIOMAP: u16 = 16;
        pub const TCA_FQ_WEIGHTS: u16 = 17;
        pub const TCA_FQ_OFFLOAD_HORIZON: u16 = 18;
    }

    /// CAKE qdisc-specific attributes.
    pub mod cake {
        pub const TCA_CAKE_UNSPEC: u16 = 0;
        pub const TCA_CAKE_PAD: u16 = 1;
        pub const TCA_CAKE_BASE_RATE64: u16 = 2;
        pub const TCA_CAKE_DIFFSERV_MODE: u16 = 3;
        pub const TCA_CAKE_ATM: u16 = 4;
        pub const TCA_CAKE_FLOW_MODE: u16 = 5;
        pub const TCA_CAKE_OVERHEAD: u16 = 6;
        pub const TCA_CAKE_RTT: u16 = 7;
        pub const TCA_CAKE_TARGET: u16 = 8;
        pub const TCA_CAKE_AUTORATE: u16 = 9;
        pub const TCA_CAKE_MEMORY: u16 = 10;
        pub const TCA_CAKE_NAT: u16 = 11;
        pub const TCA_CAKE_RAW: u16 = 12;
        pub const TCA_CAKE_WASH: u16 = 13;
        pub const TCA_CAKE_MPU: u16 = 14;
        pub const TCA_CAKE_INGRESS: u16 = 15;
        pub const TCA_CAKE_ACK_FILTER: u16 = 16;
        pub const TCA_CAKE_SPLIT_GSO: u16 = 17;
        pub const TCA_CAKE_FWMARK: u16 = 18;

        /// CAKE diffserv modes.
        pub const CAKE_DIFFSERV_DIFFSERV3: u32 = 0;
        pub const CAKE_DIFFSERV_DIFFSERV4: u32 = 1;
        pub const CAKE_DIFFSERV_DIFFSERV8: u32 = 2;
        pub const CAKE_DIFFSERV_BESTEFFORT: u32 = 3;
        pub const CAKE_DIFFSERV_PRECEDENCE: u32 = 4;

        /// CAKE flow modes.
        pub const CAKE_FLOW_NONE: u32 = 0;
        pub const CAKE_FLOW_SRC_IP: u32 = 1;
        pub const CAKE_FLOW_DST_IP: u32 = 2;
        pub const CAKE_FLOW_HOSTS: u32 = 3;
        pub const CAKE_FLOW_FLOWS: u32 = 4;
        pub const CAKE_FLOW_DUAL_SRC: u32 = 5;
        pub const CAKE_FLOW_DUAL_DST: u32 = 6;
        pub const CAKE_FLOW_TRIPLE: u32 = 7;

        /// CAKE ATM modes.
        pub const CAKE_ATM_NONE: u32 = 0;
        pub const CAKE_ATM_ATM: u32 = 1;
        pub const CAKE_ATM_PTM: u32 = 2;

        /// CAKE ACK filter modes.
        pub const CAKE_ACK_NONE: u32 = 0;
        pub const CAKE_ACK_FILTER: u32 = 1;
        pub const CAKE_ACK_AGGRESSIVE: u32 = 2;
    }

    /// RED (Random Early Detection) qdisc-specific attributes.
    pub mod red {
        use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};

        pub const TCA_RED_UNSPEC: u16 = 0;
        pub const TCA_RED_PARMS: u16 = 1;
        pub const TCA_RED_STAB: u16 = 2;
        pub const TCA_RED_MAX_P: u16 = 3;
        pub const TCA_RED_FLAGS: u16 = 4;
        pub const TCA_RED_EARLY_DROP_BLOCK: u16 = 5;
        pub const TCA_RED_MARK_BLOCK: u16 = 6;

        /// RED flags.
        pub const TC_RED_ECN: u32 = 1;
        pub const TC_RED_HARDDROP: u32 = 2;
        pub const TC_RED_ADAPTATIVE: u32 = 4;
        pub const TC_RED_NODROP: u32 = 8;

        /// RED parameters (struct tc_red_qopt).
        #[repr(C)]
        #[derive(Debug, Clone, Copy, Default, FromBytes, IntoBytes, Immutable, KnownLayout)]
        pub struct TcRedQopt {
            /// Queue limit in bytes.
            pub limit: u32,
            /// Minimum threshold.
            pub qth_min: u32,
            /// Maximum threshold.
            pub qth_max: u32,
            /// Log of weight for EWMA.
            pub wlog: u8,
            /// Log of probability.
            pub plog: u8,
            /// Log of cell size.
            pub scell_log: u8,
            /// Flags (ECN, harddrop, etc.).
            pub flags: u8,
        }

        impl TcRedQopt {
            pub const SIZE: usize = std::mem::size_of::<Self>();

            pub fn as_bytes(&self) -> &[u8] {
                <Self as IntoBytes>::as_bytes(self)
            }
        }
    }

    /// PIE (Proportional Integral controller-Enhanced) qdisc-specific attributes.
    pub mod pie {
        pub const TCA_PIE_UNSPEC: u16 = 0;
        pub const TCA_PIE_TARGET: u16 = 1;
        pub const TCA_PIE_LIMIT: u16 = 2;
        pub const TCA_PIE_TUPDATE: u16 = 3;
        pub const TCA_PIE_ALPHA: u16 = 4;
        pub const TCA_PIE_BETA: u16 = 5;
        pub const TCA_PIE_ECN: u16 = 6;
        pub const TCA_PIE_BYTEMODE: u16 = 7;
        pub const TCA_PIE_DQ_RATE_ESTIMATOR: u16 = 8;
    }

    /// FIFO qdisc types (pfifo, bfifo).
    pub mod fifo {
        use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};

        /// FIFO limit structure (struct tc_fifo_qopt).
        #[repr(C)]
        #[derive(Debug, Clone, Copy, Default, FromBytes, IntoBytes, Immutable, KnownLayout)]
        pub struct TcFifoQopt {
            /// Queue limit (packets for pfifo, bytes for bfifo).
            pub limit: u32,
        }

        impl TcFifoQopt {
            pub const SIZE: usize = std::mem::size_of::<Self>();

            pub fn new(limit: u32) -> Self {
                Self { limit }
            }

            pub fn as_bytes(&self) -> &[u8] {
                <Self as IntoBytes>::as_bytes(self)
            }
        }
    }

    /// DRR (Deficit Round Robin) qdisc attributes.
    pub mod drr {
        pub const TCA_DRR_UNSPEC: u16 = 0;
        pub const TCA_DRR_QUANTUM: u16 = 1;
    }

    /// QFQ (Quick Fair Queueing) qdisc attributes.
    pub mod qfq {
        pub const TCA_QFQ_UNSPEC: u16 = 0;
        pub const TCA_QFQ_WEIGHT: u16 = 1;
        pub const TCA_QFQ_LMAX: u16 = 2;
    }

    /// MQPRIO (Multi-Queue Priority) qdisc attributes.
    pub mod mqprio {
        use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};

        /// Maximum number of traffic classes.
        pub const TC_QOPT_MAX_QUEUE: usize = 16;

        pub const TCA_MQPRIO_UNSPEC: u16 = 0;
        pub const TCA_MQPRIO_MODE: u16 = 1;
        pub const TCA_MQPRIO_SHAPER: u16 = 2;
        pub const TCA_MQPRIO_MIN_RATE64: u16 = 3;
        pub const TCA_MQPRIO_MAX_RATE64: u16 = 4;
        pub const TCA_MQPRIO_TC_ENTRY: u16 = 5;

        /// MQPRIO modes.
        pub const TC_MQPRIO_MODE_DCB: u16 = 0;
        pub const TC_MQPRIO_MODE_CHANNEL: u16 = 1;

        /// MQPRIO shaper types.
        pub const TC_MQPRIO_SHAPER_DCB: u16 = 0;
        pub const TC_MQPRIO_SHAPER_BW_RATE: u16 = 1;

        /// MQPRIO qdisc options (struct tc_mqprio_qopt).
        #[repr(C)]
        #[derive(Debug, Clone, Copy, FromBytes, IntoBytes, Immutable, KnownLayout)]
        pub struct TcMqprioQopt {
            /// Number of traffic classes.
            pub num_tc: u8,
            /// Priority to traffic class mapping.
            pub prio_tc_map: [u8; TC_QOPT_MAX_QUEUE],
            /// Enable hardware offload (0=no, 1=yes).
            pub hw: u8,
            /// Queue count for each traffic class.
            pub count: [u16; TC_QOPT_MAX_QUEUE],
            /// Queue offset for each traffic class.
            pub offset: [u16; TC_QOPT_MAX_QUEUE],
        }

        impl Default for TcMqprioQopt {
            fn default() -> Self {
                Self {
                    num_tc: 8,
                    prio_tc_map: [0, 1, 2, 3, 4, 5, 6, 7, 0, 1, 1, 1, 3, 3, 3, 3],
                    hw: 1,
                    count: [0; TC_QOPT_MAX_QUEUE],
                    offset: [0; TC_QOPT_MAX_QUEUE],
                }
            }
        }

        impl TcMqprioQopt {
            pub const SIZE: usize = std::mem::size_of::<Self>();

            pub fn new() -> Self {
                Self::default()
            }

            pub fn with_num_tc(mut self, num_tc: u8) -> Self {
                self.num_tc = num_tc;
                self
            }

            pub fn with_hw(mut self, hw: bool) -> Self {
                self.hw = if hw { 1 } else { 0 };
                self
            }

            pub fn as_bytes(&self) -> &[u8] {
                <Self as IntoBytes>::as_bytes(self)
            }
        }
    }

    /// Plug qdisc types and constants.
    pub mod plug {
        use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};

        /// Plug action types.
        pub const TCQ_PLUG_BUFFER: i32 = 0;
        pub const TCQ_PLUG_RELEASE_ONE: i32 = 1;
        pub const TCQ_PLUG_RELEASE_INDEFINITE: i32 = 2;
        pub const TCQ_PLUG_LIMIT: i32 = 3;

        /// Plug qdisc options (struct tc_plug_qopt).
        #[repr(C)]
        #[derive(Debug, Clone, Copy, Default, FromBytes, IntoBytes, Immutable, KnownLayout)]
        pub struct TcPlugQopt {
            /// Action to perform.
            pub action: i32,
            /// Limit for TCQ_PLUG_LIMIT action.
            pub limit: u32,
        }

        impl TcPlugQopt {
            pub const SIZE: usize = std::mem::size_of::<Self>();

            pub fn new(action: i32, limit: u32) -> Self {
                Self { action, limit }
            }

            pub fn buffer() -> Self {
                Self::new(TCQ_PLUG_BUFFER, 0)
            }

            pub fn release_one() -> Self {
                Self::new(TCQ_PLUG_RELEASE_ONE, 0)
            }

            pub fn release_indefinite() -> Self {
                Self::new(TCQ_PLUG_RELEASE_INDEFINITE, 0)
            }

            pub fn limit(bytes: u32) -> Self {
                Self::new(TCQ_PLUG_LIMIT, bytes)
            }

            pub fn as_bytes(&self) -> &[u8] {
                <Self as IntoBytes>::as_bytes(self)
            }
        }
    }

    /// TAPRIO (Time Aware Priority) qdisc attributes and structures.
    pub mod taprio {
        pub const TCA_TAPRIO_ATTR_UNSPEC: u16 = 0;
        pub const TCA_TAPRIO_ATTR_PRIOMAP: u16 = 1;
        pub const TCA_TAPRIO_ATTR_SCHED_ENTRY_LIST: u16 = 2;
        pub const TCA_TAPRIO_ATTR_SCHED_BASE_TIME: u16 = 3;
        pub const TCA_TAPRIO_ATTR_SCHED_SINGLE_ENTRY: u16 = 4;
        pub const TCA_TAPRIO_ATTR_SCHED_CLOCKID: u16 = 5;
        pub const TCA_TAPRIO_PAD: u16 = 6;
        pub const TCA_TAPRIO_ATTR_ADMIN_SCHED: u16 = 7;
        pub const TCA_TAPRIO_ATTR_SCHED_CYCLE_TIME: u16 = 8;
        pub const TCA_TAPRIO_ATTR_SCHED_CYCLE_TIME_EXTENSION: u16 = 9;
        pub const TCA_TAPRIO_ATTR_FLAGS: u16 = 10;
        pub const TCA_TAPRIO_ATTR_TXTIME_DELAY: u16 = 11;
        pub const TCA_TAPRIO_ATTR_TC_ENTRY: u16 = 12;

        /// Schedule entry attributes.
        pub const TCA_TAPRIO_SCHED_ENTRY_UNSPEC: u16 = 0;
        pub const TCA_TAPRIO_SCHED_ENTRY_INDEX: u16 = 1;
        pub const TCA_TAPRIO_SCHED_ENTRY_CMD: u16 = 2;
        pub const TCA_TAPRIO_SCHED_ENTRY_GATE_MASK: u16 = 3;
        pub const TCA_TAPRIO_SCHED_ENTRY_INTERVAL: u16 = 4;

        /// Gate commands.
        pub const TC_TAPRIO_CMD_SET_GATES: u8 = 0;
        pub const TC_TAPRIO_CMD_SET_AND_HOLD: u8 = 1;
        pub const TC_TAPRIO_CMD_SET_AND_RELEASE: u8 = 2;

        /// TAPRIO flags.
        pub const TAPRIO_ATTR_FLAG_TXTIME_ASSIST: u32 = 1 << 0;
        pub const TAPRIO_ATTR_FLAG_FULL_OFFLOAD: u32 = 1 << 1;

        /// A schedule entry for TAPRIO.
        #[derive(Debug, Clone, Copy)]
        pub struct TaprioSchedEntry {
            /// Gate command (SET_GATES, SET_AND_HOLD, SET_AND_RELEASE).
            pub cmd: u8,
            /// Gate mask (bit per traffic class).
            pub gate_mask: u32,
            /// Interval in nanoseconds.
            pub interval: u32,
        }

        impl TaprioSchedEntry {
            pub fn new(cmd: u8, gate_mask: u32, interval_ns: u32) -> Self {
                Self {
                    cmd,
                    gate_mask,
                    interval: interval_ns,
                }
            }

            /// Create a SET_GATES entry.
            pub fn set_gates(gate_mask: u32, interval_ns: u32) -> Self {
                Self::new(TC_TAPRIO_CMD_SET_GATES, gate_mask, interval_ns)
            }

            /// Create a SET_AND_HOLD entry.
            pub fn set_and_hold(gate_mask: u32, interval_ns: u32) -> Self {
                Self::new(TC_TAPRIO_CMD_SET_AND_HOLD, gate_mask, interval_ns)
            }

            /// Create a SET_AND_RELEASE entry.
            pub fn set_and_release(gate_mask: u32, interval_ns: u32) -> Self {
                Self::new(TC_TAPRIO_CMD_SET_AND_RELEASE, gate_mask, interval_ns)
            }
        }
    }

    /// HFSC (Hierarchical Fair Service Curve) qdisc attributes and structures.
    pub mod hfsc {
        use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};

        pub const TCA_HFSC_UNSPEC: u16 = 0;
        pub const TCA_HFSC_RSC: u16 = 1;
        pub const TCA_HFSC_FSC: u16 = 2;
        pub const TCA_HFSC_USC: u16 = 3;

        /// HFSC qdisc options (struct tc_hfsc_qopt).
        #[repr(C)]
        #[derive(Debug, Clone, Copy, Default, FromBytes, IntoBytes, Immutable, KnownLayout)]
        pub struct TcHfscQopt {
            /// Default class ID.
            pub defcls: u16,
        }

        impl TcHfscQopt {
            pub const SIZE: usize = std::mem::size_of::<Self>();

            pub fn new(defcls: u16) -> Self {
                Self { defcls }
            }

            pub fn as_bytes(&self) -> &[u8] {
                <Self as IntoBytes>::as_bytes(self)
            }
        }

        /// Service curve definition (struct tc_service_curve).
        #[repr(C)]
        #[derive(Debug, Clone, Copy, Default, FromBytes, IntoBytes, Immutable, KnownLayout)]
        pub struct TcServiceCurve {
            /// Slope of the first segment in bps.
            pub m1: u32,
            /// X-projection of the first segment in microseconds.
            pub d: u32,
            /// Slope of the second segment in bps.
            pub m2: u32,
        }

        impl TcServiceCurve {
            pub const SIZE: usize = std::mem::size_of::<Self>();

            pub fn new(m1: u32, d: u32, m2: u32) -> Self {
                Self { m1, d, m2 }
            }

            /// Create a simple service curve with just a rate (m2).
            pub fn rate(rate_bps: u32) -> Self {
                Self {
                    m1: 0,
                    d: 0,
                    m2: rate_bps,
                }
            }

            /// Create a two-slope service curve.
            ///
            /// - m1: initial rate in bps
            /// - d: duration of first segment in microseconds
            /// - m2: sustained rate in bps
            pub fn two_slope(m1: u32, d_us: u32, m2: u32) -> Self {
                Self { m1, d: d_us, m2 }
            }

            pub fn as_bytes(&self) -> &[u8] {
                <Self as IntoBytes>::as_bytes(self)
            }
        }
    }

    /// ETF (Earliest TxTime First) qdisc attributes and structures.
    pub mod etf {
        use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};

        pub const TCA_ETF_UNSPEC: u16 = 0;
        pub const TCA_ETF_PARMS: u16 = 1;

        /// ETF flags.
        pub const TC_ETF_DEADLINE_MODE_ON: i32 = 1 << 0;
        pub const TC_ETF_OFFLOAD_ON: i32 = 1 << 1;
        pub const TC_ETF_SKIP_SOCK_CHECK: i32 = 1 << 2;

        /// Clock IDs for ETF.
        pub const CLOCKID_INVALID: i32 = -1;

        /// ETF qdisc options (struct tc_etf_qopt).
        #[repr(C)]
        #[derive(Debug, Clone, Copy, FromBytes, IntoBytes, Immutable, KnownLayout)]
        pub struct TcEtfQopt {
            /// Delta time in nanoseconds.
            pub delta: i32,
            /// Clock ID (e.g., CLOCK_TAI).
            pub clockid: i32,
            /// Flags (deadline_mode, offload, skip_sock_check).
            pub flags: i32,
        }

        impl Default for TcEtfQopt {
            fn default() -> Self {
                Self {
                    delta: 0,
                    clockid: CLOCKID_INVALID,
                    flags: 0,
                }
            }
        }

        impl TcEtfQopt {
            pub const SIZE: usize = std::mem::size_of::<Self>();

            pub fn new() -> Self {
                Self::default()
            }

            pub fn with_delta(mut self, delta_ns: i32) -> Self {
                self.delta = delta_ns;
                self
            }

            pub fn with_clockid(mut self, clockid: i32) -> Self {
                self.clockid = clockid;
                self
            }

            pub fn with_deadline_mode(mut self, enable: bool) -> Self {
                if enable {
                    self.flags |= TC_ETF_DEADLINE_MODE_ON;
                } else {
                    self.flags &= !TC_ETF_DEADLINE_MODE_ON;
                }
                self
            }

            pub fn with_offload(mut self, enable: bool) -> Self {
                if enable {
                    self.flags |= TC_ETF_OFFLOAD_ON;
                } else {
                    self.flags &= !TC_ETF_OFFLOAD_ON;
                }
                self
            }

            pub fn with_skip_sock_check(mut self, enable: bool) -> Self {
                if enable {
                    self.flags |= TC_ETF_SKIP_SOCK_CHECK;
                } else {
                    self.flags &= !TC_ETF_SKIP_SOCK_CHECK;
                }
                self
            }

            pub fn as_bytes(&self) -> &[u8] {
                <Self as IntoBytes>::as_bytes(self)
            }
        }
    }
}

/// Common filter attributes.
pub mod filter {
    /// U32 filter attributes and structures.
    pub mod u32 {
        use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};

        pub const TCA_U32_UNSPEC: u16 = 0;
        pub const TCA_U32_CLASSID: u16 = 1;
        pub const TCA_U32_HASH: u16 = 2;
        pub const TCA_U32_LINK: u16 = 3;
        pub const TCA_U32_DIVISOR: u16 = 4;
        pub const TCA_U32_SEL: u16 = 5;
        pub const TCA_U32_POLICE: u16 = 6;
        pub const TCA_U32_ACT: u16 = 7;
        pub const TCA_U32_INDEV: u16 = 8;
        pub const TCA_U32_PCNT: u16 = 9;
        pub const TCA_U32_MARK: u16 = 10;
        pub const TCA_U32_FLAGS: u16 = 11;

        /// U32 selector flags.
        pub const TC_U32_TERMINAL: u8 = 1;
        pub const TC_U32_OFFSET: u8 = 2;
        pub const TC_U32_VAROFFSET: u8 = 4;
        pub const TC_U32_EAT: u8 = 8;

        /// U32 key (struct tc_u32_key).
        /// Matches a 32-bit value at a specific offset.
        #[repr(C)]
        #[derive(Debug, Clone, Copy, Default, FromBytes, IntoBytes, Immutable, KnownLayout)]
        pub struct TcU32Key {
            /// Mask to apply (big-endian).
            pub mask: u32,
            /// Value to match (big-endian).
            pub val: u32,
            /// Byte offset from header start.
            pub off: i32,
            /// Offset mask for variable offset (-1 for nexthdr+).
            pub offmask: i32,
        }

        impl TcU32Key {
            pub const SIZE: usize = std::mem::size_of::<Self>();

            /// Create a new key with value, mask, and offset.
            pub fn new(val: u32, mask: u32, off: i32) -> Self {
                Self {
                    mask,
                    val: val & mask,
                    off,
                    offmask: 0,
                }
            }

            /// Create a key for matching at nexthdr+ offset.
            pub fn with_nexthdr(val: u32, mask: u32, off: i32) -> Self {
                Self {
                    mask,
                    val: val & mask,
                    off,
                    offmask: -1,
                }
            }
        }

        /// U32 selector header (struct tc_u32_sel without keys).
        #[repr(C)]
        #[derive(Debug, Clone, Copy, Default, FromBytes, IntoBytes, Immutable, KnownLayout)]
        pub struct TcU32SelHdr {
            /// Flags (TC_U32_TERMINAL, etc.).
            pub flags: u8,
            /// Shift for variable offset.
            pub offshift: u8,
            /// Number of keys.
            pub nkeys: u8,
            /// Padding.
            pub _pad: u8,
            /// Mask for variable offset (big-endian).
            pub offmask: u16,
            /// Fixed offset to add.
            pub off: u16,
            /// Offset to variable offset field.
            pub offoff: i16,
            /// Hash key offset.
            pub hoff: i16,
            /// Hash mask (big-endian).
            pub hmask: u32,
        }

        impl TcU32SelHdr {
            pub const SIZE: usize = std::mem::size_of::<Self>();

            pub fn as_bytes(&self) -> &[u8] {
                <Self as IntoBytes>::as_bytes(self)
            }
        }

        /// U32 selector builder - builds the full selector with keys.
        #[derive(Debug, Clone, Default)]
        pub struct TcU32Sel {
            pub hdr: TcU32SelHdr,
            pub keys: Vec<TcU32Key>,
        }

        impl TcU32Sel {
            pub fn new() -> Self {
                Self::default()
            }

            /// Add a key to the selector.
            pub fn add_key(&mut self, key: TcU32Key) {
                self.keys.push(key);
                self.hdr.nkeys = self.keys.len() as u8;
            }

            /// Set terminal flag (packet will be classified).
            pub fn set_terminal(&mut self) {
                self.hdr.flags |= TC_U32_TERMINAL;
            }

            /// Convert to bytes for netlink message.
            pub fn to_bytes(&self) -> Vec<u8> {
                let mut buf =
                    Vec::with_capacity(TcU32SelHdr::SIZE + self.keys.len() * TcU32Key::SIZE);
                buf.extend_from_slice(self.hdr.as_bytes());
                for key in &self.keys {
                    buf.extend_from_slice(<TcU32Key as IntoBytes>::as_bytes(key));
                }
                buf
            }
        }

        /// U32 mark structure (struct tc_u32_mark).
        #[repr(C)]
        #[derive(Debug, Clone, Copy, Default, FromBytes, IntoBytes, Immutable, KnownLayout)]
        pub struct TcU32Mark {
            pub val: u32,
            pub mask: u32,
            pub success: u32,
        }

        impl TcU32Mark {
            pub const SIZE: usize = std::mem::size_of::<Self>();

            pub fn new(val: u32, mask: u32) -> Self {
                Self {
                    val,
                    mask,
                    success: 0,
                }
            }

            pub fn as_bytes(&self) -> &[u8] {
                <Self as IntoBytes>::as_bytes(self)
            }
        }

        /// Helper to pack a 32-bit key at an offset.
        pub fn pack_key32(val: u32, mask: u32, off: i32) -> TcU32Key {
            TcU32Key::new(val.to_be(), mask.to_be(), off)
        }

        /// Helper to pack a 16-bit key at an offset.
        pub fn pack_key16(val: u16, mask: u16, off: i32) -> TcU32Key {
            // 16-bit values are positioned within a 32-bit word
            let (val32, mask32) = if (off & 3) == 0 {
                // Upper 16 bits
                ((val as u32) << 16, (mask as u32) << 16)
            } else {
                // Lower 16 bits
                (val as u32, mask as u32)
            };
            TcU32Key::new(val32.to_be(), mask32.to_be(), off & !3)
        }

        /// Helper to pack an 8-bit key at an offset.
        pub fn pack_key8(val: u8, mask: u8, off: i32) -> TcU32Key {
            let shift = match off & 3 {
                0 => 24,
                1 => 16,
                2 => 8,
                _ => 0,
            };
            let val32 = (val as u32) << shift;
            let mask32 = (mask as u32) << shift;
            TcU32Key::new(val32.to_be(), mask32.to_be(), off & !3)
        }
    }

    /// Flower filter attributes.
    pub mod flower {
        pub const TCA_FLOWER_UNSPEC: u16 = 0;
        pub const TCA_FLOWER_CLASSID: u16 = 1;
        pub const TCA_FLOWER_INDEV: u16 = 2;
        pub const TCA_FLOWER_ACT: u16 = 3;
        pub const TCA_FLOWER_KEY_ETH_DST: u16 = 4;
        pub const TCA_FLOWER_KEY_ETH_DST_MASK: u16 = 5;
        pub const TCA_FLOWER_KEY_ETH_SRC: u16 = 6;
        pub const TCA_FLOWER_KEY_ETH_SRC_MASK: u16 = 7;
        pub const TCA_FLOWER_KEY_ETH_TYPE: u16 = 8;
        pub const TCA_FLOWER_KEY_IP_PROTO: u16 = 9;
        pub const TCA_FLOWER_KEY_IPV4_SRC: u16 = 10;
        pub const TCA_FLOWER_KEY_IPV4_SRC_MASK: u16 = 11;
        pub const TCA_FLOWER_KEY_IPV4_DST: u16 = 12;
        pub const TCA_FLOWER_KEY_IPV4_DST_MASK: u16 = 13;
        pub const TCA_FLOWER_KEY_IPV6_SRC: u16 = 14;
        pub const TCA_FLOWER_KEY_IPV6_SRC_MASK: u16 = 15;
        pub const TCA_FLOWER_KEY_IPV6_DST: u16 = 16;
        pub const TCA_FLOWER_KEY_IPV6_DST_MASK: u16 = 17;
        pub const TCA_FLOWER_KEY_TCP_SRC: u16 = 18;
        pub const TCA_FLOWER_KEY_TCP_DST: u16 = 19;
        pub const TCA_FLOWER_KEY_UDP_SRC: u16 = 20;
        pub const TCA_FLOWER_KEY_UDP_DST: u16 = 21;
        pub const TCA_FLOWER_FLAGS: u16 = 22;
        pub const TCA_FLOWER_KEY_VLAN_ID: u16 = 23;
        pub const TCA_FLOWER_KEY_VLAN_PRIO: u16 = 24;
        pub const TCA_FLOWER_KEY_VLAN_ETH_TYPE: u16 = 25;
        pub const TCA_FLOWER_KEY_ENC_KEY_ID: u16 = 26;
        pub const TCA_FLOWER_KEY_ENC_IPV4_SRC: u16 = 27;
        pub const TCA_FLOWER_KEY_ENC_IPV4_SRC_MASK: u16 = 28;
        pub const TCA_FLOWER_KEY_ENC_IPV4_DST: u16 = 29;
        pub const TCA_FLOWER_KEY_ENC_IPV4_DST_MASK: u16 = 30;
        pub const TCA_FLOWER_KEY_ENC_IPV6_SRC: u16 = 31;
        pub const TCA_FLOWER_KEY_ENC_IPV6_SRC_MASK: u16 = 32;
        pub const TCA_FLOWER_KEY_ENC_IPV6_DST: u16 = 33;
        pub const TCA_FLOWER_KEY_ENC_IPV6_DST_MASK: u16 = 34;
        pub const TCA_FLOWER_KEY_TCP_SRC_MASK: u16 = 35;
        pub const TCA_FLOWER_KEY_TCP_DST_MASK: u16 = 36;
        pub const TCA_FLOWER_KEY_UDP_SRC_MASK: u16 = 37;
        pub const TCA_FLOWER_KEY_UDP_DST_MASK: u16 = 38;
        pub const TCA_FLOWER_KEY_SCTP_SRC_MASK: u16 = 39;
        pub const TCA_FLOWER_KEY_SCTP_DST_MASK: u16 = 40;
        pub const TCA_FLOWER_KEY_SCTP_SRC: u16 = 41;
        pub const TCA_FLOWER_KEY_SCTP_DST: u16 = 42;
        pub const TCA_FLOWER_KEY_ENC_UDP_SRC_PORT: u16 = 43;
        pub const TCA_FLOWER_KEY_ENC_UDP_SRC_PORT_MASK: u16 = 44;
        pub const TCA_FLOWER_KEY_ENC_UDP_DST_PORT: u16 = 45;
        pub const TCA_FLOWER_KEY_ENC_UDP_DST_PORT_MASK: u16 = 46;
        pub const TCA_FLOWER_KEY_FLAGS: u16 = 47;
        pub const TCA_FLOWER_KEY_FLAGS_MASK: u16 = 48;
        pub const TCA_FLOWER_KEY_ICMPV4_CODE: u16 = 49;
        pub const TCA_FLOWER_KEY_ICMPV4_CODE_MASK: u16 = 50;
        pub const TCA_FLOWER_KEY_ICMPV4_TYPE: u16 = 51;
        pub const TCA_FLOWER_KEY_ICMPV4_TYPE_MASK: u16 = 52;
        pub const TCA_FLOWER_KEY_ICMPV6_CODE: u16 = 53;
        pub const TCA_FLOWER_KEY_ICMPV6_CODE_MASK: u16 = 54;
        pub const TCA_FLOWER_KEY_ICMPV6_TYPE: u16 = 55;
        pub const TCA_FLOWER_KEY_ICMPV6_TYPE_MASK: u16 = 56;
        pub const TCA_FLOWER_KEY_ARP_SIP: u16 = 57;
        pub const TCA_FLOWER_KEY_ARP_SIP_MASK: u16 = 58;
        pub const TCA_FLOWER_KEY_ARP_TIP: u16 = 59;
        pub const TCA_FLOWER_KEY_ARP_TIP_MASK: u16 = 60;
        pub const TCA_FLOWER_KEY_ARP_OP: u16 = 61;
        pub const TCA_FLOWER_KEY_ARP_OP_MASK: u16 = 62;
        pub const TCA_FLOWER_KEY_ARP_SHA: u16 = 63;
        pub const TCA_FLOWER_KEY_ARP_SHA_MASK: u16 = 64;
        pub const TCA_FLOWER_KEY_ARP_THA: u16 = 65;
        pub const TCA_FLOWER_KEY_ARP_THA_MASK: u16 = 66;
        pub const TCA_FLOWER_KEY_MPLS_TTL: u16 = 67;
        pub const TCA_FLOWER_KEY_MPLS_BOS: u16 = 68;
        pub const TCA_FLOWER_KEY_MPLS_TC: u16 = 69;
        pub const TCA_FLOWER_KEY_MPLS_LABEL: u16 = 70;
        pub const TCA_FLOWER_KEY_TCP_FLAGS: u16 = 71;
        pub const TCA_FLOWER_KEY_TCP_FLAGS_MASK: u16 = 72;
        pub const TCA_FLOWER_KEY_IP_TOS: u16 = 73;
        pub const TCA_FLOWER_KEY_IP_TOS_MASK: u16 = 74;
        pub const TCA_FLOWER_KEY_IP_TTL: u16 = 75;
        pub const TCA_FLOWER_KEY_IP_TTL_MASK: u16 = 76;
        pub const TCA_FLOWER_KEY_CVLAN_ID: u16 = 77;
        pub const TCA_FLOWER_KEY_CVLAN_PRIO: u16 = 78;
        pub const TCA_FLOWER_KEY_CVLAN_ETH_TYPE: u16 = 79;
        pub const TCA_FLOWER_KEY_ENC_IP_TOS: u16 = 80;
        pub const TCA_FLOWER_KEY_ENC_IP_TOS_MASK: u16 = 81;
        pub const TCA_FLOWER_KEY_ENC_IP_TTL: u16 = 82;
        pub const TCA_FLOWER_KEY_ENC_IP_TTL_MASK: u16 = 83;
        pub const TCA_FLOWER_KEY_CT_STATE: u16 = 84;
        pub const TCA_FLOWER_KEY_CT_STATE_MASK: u16 = 85;
        pub const TCA_FLOWER_KEY_CT_ZONE: u16 = 86;
        pub const TCA_FLOWER_KEY_CT_ZONE_MASK: u16 = 87;
        pub const TCA_FLOWER_KEY_CT_MARK: u16 = 88;
        pub const TCA_FLOWER_KEY_CT_MARK_MASK: u16 = 89;
        pub const TCA_FLOWER_KEY_CT_LABELS: u16 = 90;
        pub const TCA_FLOWER_KEY_CT_LABELS_MASK: u16 = 91;

        /// Flower filter flags.
        pub const TCA_CLS_FLAGS_SKIP_HW: u32 = 1 << 0;
        pub const TCA_CLS_FLAGS_SKIP_SW: u32 = 1 << 1;
        pub const TCA_CLS_FLAGS_IN_HW: u32 = 1 << 2;
        pub const TCA_CLS_FLAGS_NOT_IN_HW: u32 = 1 << 3;
        pub const TCA_CLS_FLAGS_VERBOSE: u32 = 1 << 4;

        /// Connection tracking state flags.
        pub const TCA_FLOWER_KEY_CT_FLAGS_NEW: u16 = 1 << 0;
        pub const TCA_FLOWER_KEY_CT_FLAGS_ESTABLISHED: u16 = 1 << 1;
        pub const TCA_FLOWER_KEY_CT_FLAGS_RELATED: u16 = 1 << 2;
        pub const TCA_FLOWER_KEY_CT_FLAGS_TRACKED: u16 = 1 << 3;
        pub const TCA_FLOWER_KEY_CT_FLAGS_INVALID: u16 = 1 << 4;
        pub const TCA_FLOWER_KEY_CT_FLAGS_REPLY: u16 = 1 << 5;

        /// IP protocol constants.
        pub const IPPROTO_TCP: u8 = 6;
        pub const IPPROTO_UDP: u8 = 17;
        pub const IPPROTO_ICMP: u8 = 1;
        pub const IPPROTO_ICMPV6: u8 = 58;
        pub const IPPROTO_SCTP: u8 = 132;
        pub const IPPROTO_GRE: u8 = 47;

        /// Parse IP protocol from string.
        pub fn parse_ip_proto(s: &str) -> Option<u8> {
            match s.to_lowercase().as_str() {
                "tcp" => Some(IPPROTO_TCP),
                "udp" => Some(IPPROTO_UDP),
                "icmp" => Some(IPPROTO_ICMP),
                "icmpv6" => Some(IPPROTO_ICMPV6),
                "sctp" => Some(IPPROTO_SCTP),
                "gre" => Some(IPPROTO_GRE),
                _ => s.parse().ok(),
            }
        }
    }

    /// BPF filter attributes.
    pub mod bpf {
        pub const TCA_BPF_UNSPEC: u16 = 0;
        pub const TCA_BPF_ACT: u16 = 1;
        pub const TCA_BPF_POLICE: u16 = 2;
        pub const TCA_BPF_CLASSID: u16 = 3;
        pub const TCA_BPF_OPS_LEN: u16 = 4;
        pub const TCA_BPF_OPS: u16 = 5;
        pub const TCA_BPF_FD: u16 = 6;
        pub const TCA_BPF_NAME: u16 = 7;
        pub const TCA_BPF_FLAGS: u16 = 8;
        pub const TCA_BPF_FLAGS_GEN: u16 = 9;
        pub const TCA_BPF_TAG: u16 = 10;
        pub const TCA_BPF_ID: u16 = 11;

        /// BPF filter flags.
        pub const TCA_BPF_FLAG_ACT_DIRECT: u32 = 1 << 0;
    }

    /// Basic/matchall filter attributes.
    pub mod basic {
        pub const TCA_BASIC_UNSPEC: u16 = 0;
        pub const TCA_BASIC_CLASSID: u16 = 1;
        pub const TCA_BASIC_EMATCHES: u16 = 2;
        pub const TCA_BASIC_ACT: u16 = 3;
        pub const TCA_BASIC_POLICE: u16 = 4;
    }

    /// Matchall filter attributes.
    pub mod matchall {
        pub const TCA_MATCHALL_UNSPEC: u16 = 0;
        pub const TCA_MATCHALL_CLASSID: u16 = 1;
        pub const TCA_MATCHALL_ACT: u16 = 2;
        pub const TCA_MATCHALL_FLAGS: u16 = 3;
        pub const TCA_MATCHALL_PCNT: u16 = 4;
        pub const TCA_MATCHALL_PAD: u16 = 5;
    }

    /// Firewall mark (fw) filter attributes.
    pub mod fw {
        pub const TCA_FW_UNSPEC: u16 = 0;
        pub const TCA_FW_CLASSID: u16 = 1;
        pub const TCA_FW_POLICE: u16 = 2;
        pub const TCA_FW_INDEV: u16 = 3;
        pub const TCA_FW_ACT: u16 = 4;
        pub const TCA_FW_MASK: u16 = 5;
    }

    /// Cgroup filter attributes.
    pub mod cgroup {
        pub const TCA_CGROUP_UNSPEC: u16 = 0;
        pub const TCA_CGROUP_ACT: u16 = 1;
        pub const TCA_CGROUP_POLICE: u16 = 2;
        pub const TCA_CGROUP_EMATCHES: u16 = 3;
    }

    /// Route filter attributes.
    pub mod route4 {
        pub const TCA_ROUTE4_UNSPEC: u16 = 0;
        pub const TCA_ROUTE4_CLASSID: u16 = 1;
        pub const TCA_ROUTE4_TO: u16 = 2;
        pub const TCA_ROUTE4_FROM: u16 = 3;
        pub const TCA_ROUTE4_IIF: u16 = 4;
        pub const TCA_ROUTE4_POLICE: u16 = 5;
        pub const TCA_ROUTE4_ACT: u16 = 6;
    }

    /// Flow filter attributes.
    pub mod flow {
        pub const TCA_FLOW_UNSPEC: u16 = 0;
        pub const TCA_FLOW_KEYS: u16 = 1;
        pub const TCA_FLOW_MODE: u16 = 2;
        pub const TCA_FLOW_BASECLASS: u16 = 3;
        pub const TCA_FLOW_RSHIFT: u16 = 4;
        pub const TCA_FLOW_ADDEND: u16 = 5;
        pub const TCA_FLOW_MASK: u16 = 6;
        pub const TCA_FLOW_XOR: u16 = 7;
        pub const TCA_FLOW_DIVISOR: u16 = 8;
        pub const TCA_FLOW_ACT: u16 = 9;
        pub const TCA_FLOW_POLICE: u16 = 10;
        pub const TCA_FLOW_EMATCHES: u16 = 11;
        pub const TCA_FLOW_PERTURB: u16 = 12;

        /// Flow modes.
        pub const FLOW_MODE_MAP: u32 = 0;
        pub const FLOW_MODE_HASH: u32 = 1;

        /// Flow keys (bit positions for key mask).
        pub const FLOW_KEY_SRC: u32 = 1 << 0;
        pub const FLOW_KEY_DST: u32 = 1 << 1;
        pub const FLOW_KEY_PROTO: u32 = 1 << 2;
        pub const FLOW_KEY_PROTO_SRC: u32 = 1 << 3;
        pub const FLOW_KEY_PROTO_DST: u32 = 1 << 4;
        pub const FLOW_KEY_IIF: u32 = 1 << 5;
        pub const FLOW_KEY_PRIORITY: u32 = 1 << 6;
        pub const FLOW_KEY_MARK: u32 = 1 << 7;
        pub const FLOW_KEY_NFCT: u32 = 1 << 8;
        pub const FLOW_KEY_NFCT_SRC: u32 = 1 << 9;
        pub const FLOW_KEY_NFCT_DST: u32 = 1 << 10;
        pub const FLOW_KEY_NFCT_PROTO_SRC: u32 = 1 << 11;
        pub const FLOW_KEY_NFCT_PROTO_DST: u32 = 1 << 12;
        pub const FLOW_KEY_RTCLASSID: u32 = 1 << 13;
        pub const FLOW_KEY_SKUID: u32 = 1 << 14;
        pub const FLOW_KEY_SKGID: u32 = 1 << 15;
        pub const FLOW_KEY_VLAN_TAG: u32 = 1 << 16;
        pub const FLOW_KEY_RXHASH: u32 = 1 << 17;
    }
}

/// Common action attributes.
pub mod action {
    use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};

    /// Generic action attributes.
    pub const TCA_ACT_UNSPEC: u16 = 0;
    pub const TCA_ACT_KIND: u16 = 1;
    pub const TCA_ACT_OPTIONS: u16 = 2;
    pub const TCA_ACT_INDEX: u16 = 3;
    pub const TCA_ACT_STATS: u16 = 4;
    pub const TCA_ACT_PAD: u16 = 5;
    pub const TCA_ACT_COOKIE: u16 = 6;
    pub const TCA_ACT_FLAGS: u16 = 7;
    pub const TCA_ACT_HW_STATS: u16 = 8;
    pub const TCA_ACT_USED_HW_STATS: u16 = 9;
    pub const TCA_ACT_IN_HW_COUNT: u16 = 10;

    /// Action binding constants.
    pub const TCA_ACT_BIND: i32 = 1;
    pub const TCA_ACT_NOBIND: i32 = 0;

    /// Action results (TC_ACT_*).
    pub const TC_ACT_UNSPEC: i32 = -1;
    pub const TC_ACT_OK: i32 = 0;
    pub const TC_ACT_RECLASSIFY: i32 = 1;
    pub const TC_ACT_SHOT: i32 = 2;
    pub const TC_ACT_PIPE: i32 = 3;
    pub const TC_ACT_STOLEN: i32 = 4;
    pub const TC_ACT_QUEUED: i32 = 5;
    pub const TC_ACT_REPEAT: i32 = 6;
    pub const TC_ACT_REDIRECT: i32 = 7;
    pub const TC_ACT_TRAP: i32 = 8;
    /// Jump to chain (opcode, chain is encoded in upper bits).
    pub const TC_ACT_JUMP: i32 = 0x10000000;
    /// Goto chain (opcode, chain is encoded in upper bits).
    pub const TC_ACT_GOTO_CHAIN: i32 = 0x20000000;

    /// Encode a goto_chain action value.
    ///
    /// The chain number is encoded in the lower 24 bits.
    #[inline]
    pub const fn tc_act_goto_chain(chain: u32) -> i32 {
        TC_ACT_GOTO_CHAIN | (chain as i32 & 0x00FFFFFF)
    }

    /// Decode the chain number from a goto_chain action.
    #[inline]
    pub const fn tc_act_chain(action: i32) -> u32 {
        (action & 0x00FFFFFF) as u32
    }

    /// Check if an action is a goto_chain.
    #[inline]
    pub const fn is_goto_chain(action: i32) -> bool {
        (action & TC_ACT_GOTO_CHAIN) != 0
    }

    /// Parse action result from string.
    pub fn parse_action_result(s: &str) -> Option<i32> {
        match s.to_lowercase().as_str() {
            "ok" | "pass" => Some(TC_ACT_OK),
            "shot" | "drop" => Some(TC_ACT_SHOT),
            "reclassify" => Some(TC_ACT_RECLASSIFY),
            "pipe" => Some(TC_ACT_PIPE),
            "stolen" => Some(TC_ACT_STOLEN),
            "queued" => Some(TC_ACT_QUEUED),
            "repeat" => Some(TC_ACT_REPEAT),
            "redirect" => Some(TC_ACT_REDIRECT),
            "trap" => Some(TC_ACT_TRAP),
            "continue" => Some(TC_ACT_PIPE), // alias
            _ => None,
        }
    }

    /// Format action result to string.
    pub fn format_action_result(action: i32) -> &'static str {
        match action {
            TC_ACT_UNSPEC => "unspec",
            TC_ACT_OK => "pass",
            TC_ACT_SHOT => "drop",
            TC_ACT_RECLASSIFY => "reclassify",
            TC_ACT_PIPE => "pipe",
            TC_ACT_STOLEN => "stolen",
            TC_ACT_QUEUED => "queued",
            TC_ACT_REPEAT => "repeat",
            TC_ACT_REDIRECT => "redirect",
            TC_ACT_TRAP => "trap",
            _ => "unknown",
        }
    }

    /// Common action header (tc_gen macro in kernel).
    /// This is the base structure for all action parameters.
    #[repr(C)]
    #[derive(Debug, Clone, Copy, Default, FromBytes, IntoBytes, Immutable, KnownLayout)]
    pub struct TcGen {
        pub index: u32,
        pub capab: u32,
        pub action: i32,
        pub refcnt: i32,
        pub bindcnt: i32,
    }

    impl TcGen {
        pub fn new(action: i32) -> Self {
            Self {
                index: 0,
                capab: 0,
                action,
                refcnt: 0,
                bindcnt: 0,
            }
        }

        pub fn as_bytes(&self) -> &[u8] {
            <Self as IntoBytes>::as_bytes(self)
        }
    }

    /// Mirred action attributes.
    pub mod mirred {
        use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};

        pub const TCA_MIRRED_UNSPEC: u16 = 0;
        pub const TCA_MIRRED_TM: u16 = 1;
        pub const TCA_MIRRED_PARMS: u16 = 2;
        pub const TCA_MIRRED_PAD: u16 = 3;
        pub const TCA_MIRRED_BLOCKID: u16 = 4;

        /// Mirred action types.
        pub const TCA_EGRESS_REDIR: i32 = 1;
        pub const TCA_EGRESS_MIRROR: i32 = 2;
        pub const TCA_INGRESS_REDIR: i32 = 3;
        pub const TCA_INGRESS_MIRROR: i32 = 4;

        /// Parse mirred action type from string.
        pub fn parse_mirred_action(s: &str) -> Option<i32> {
            match s.to_lowercase().as_str() {
                "egress_redir" | "redirect" => Some(TCA_EGRESS_REDIR),
                "egress_mirror" | "mirror" => Some(TCA_EGRESS_MIRROR),
                "ingress_redir" => Some(TCA_INGRESS_REDIR),
                "ingress_mirror" => Some(TCA_INGRESS_MIRROR),
                _ => None,
            }
        }

        /// Format mirred action type to string.
        pub fn format_mirred_action(eaction: i32) -> &'static str {
            match eaction {
                TCA_EGRESS_REDIR => "egress redirect",
                TCA_EGRESS_MIRROR => "egress mirror",
                TCA_INGRESS_REDIR => "ingress redirect",
                TCA_INGRESS_MIRROR => "ingress mirror",
                _ => "unknown",
            }
        }

        /// Mirred action parameters (struct tc_mirred).
        #[repr(C)]
        #[derive(Debug, Clone, Copy, Default, FromBytes, IntoBytes, Immutable, KnownLayout)]
        pub struct TcMirred {
            /// Common action fields (tc_gen).
            pub index: u32,
            pub capab: u32,
            pub action: i32,
            pub refcnt: i32,
            pub bindcnt: i32,
            /// Mirred-specific: egress/ingress mirror/redirect.
            pub eaction: i32,
            /// Target interface index.
            pub ifindex: u32,
        }

        impl TcMirred {
            pub fn new(eaction: i32, ifindex: u32, action: i32) -> Self {
                Self {
                    index: 0,
                    capab: 0,
                    action,
                    refcnt: 0,
                    bindcnt: 0,
                    eaction,
                    ifindex,
                }
            }

            pub fn as_bytes(&self) -> &[u8] {
                <Self as IntoBytes>::as_bytes(self)
            }
        }
    }

    /// Gact (generic action) attributes.
    pub mod gact {
        use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};

        pub const TCA_GACT_UNSPEC: u16 = 0;
        pub const TCA_GACT_TM: u16 = 1;
        pub const TCA_GACT_PARMS: u16 = 2;
        pub const TCA_GACT_PROB: u16 = 3;
        pub const TCA_GACT_PAD: u16 = 4;

        /// Probability distribution types.
        pub const PGACT_NONE: u16 = 0;
        pub const PGACT_NETRAND: u16 = 1;
        pub const PGACT_DETERM: u16 = 2;

        /// Gact action parameters (struct tc_gact).
        #[repr(C)]
        #[derive(Debug, Clone, Copy, Default, FromBytes, IntoBytes, Immutable, KnownLayout)]
        pub struct TcGact {
            /// Common action fields (tc_gen).
            pub index: u32,
            pub capab: u32,
            pub action: i32,
            pub refcnt: i32,
            pub bindcnt: i32,
        }

        impl TcGact {
            pub fn new(action: i32) -> Self {
                Self {
                    index: 0,
                    capab: 0,
                    action,
                    refcnt: 0,
                    bindcnt: 0,
                }
            }

            pub fn as_bytes(&self) -> &[u8] {
                <Self as IntoBytes>::as_bytes(self)
            }
        }

        /// Gact probability parameters (struct tc_gact_p).
        #[repr(C)]
        #[derive(Debug, Clone, Copy, Default, FromBytes, IntoBytes, Immutable, KnownLayout)]
        pub struct TcGactP {
            pub ptype: u16,
            pub pval: u16,
            pub paction: i32,
        }

        impl TcGactP {
            pub fn new(ptype: u16, pval: u16, paction: i32) -> Self {
                Self {
                    ptype,
                    pval,
                    paction,
                }
            }

            pub fn as_bytes(&self) -> &[u8] {
                <Self as IntoBytes>::as_bytes(self)
            }
        }
    }

    /// Police action attributes.
    pub mod police {
        use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};

        pub const TCA_POLICE_UNSPEC: u16 = 0;
        pub const TCA_POLICE_TBF: u16 = 1;
        pub const TCA_POLICE_RATE: u16 = 2;
        pub const TCA_POLICE_PEAKRATE: u16 = 3;
        pub const TCA_POLICE_AVRATE: u16 = 4;
        pub const TCA_POLICE_RESULT: u16 = 5;
        pub const TCA_POLICE_TM: u16 = 6;
        pub const TCA_POLICE_PAD: u16 = 7;
        pub const TCA_POLICE_RATE64: u16 = 8;
        pub const TCA_POLICE_PEAKRATE64: u16 = 9;
        pub const TCA_POLICE_PKTRATE64: u16 = 10;
        pub const TCA_POLICE_PKTBURST64: u16 = 11;

        use crate::netlink::types::tc::qdisc::TcRateSpec;

        /// Police action parameters (struct tc_police).
        #[repr(C)]
        #[derive(Debug, Clone, Copy, FromBytes, IntoBytes, Immutable, KnownLayout)]
        pub struct TcPolice {
            pub index: u32,
            pub action: i32,
            pub limit: u32,
            pub burst: u32,
            pub mtu: u32,
            pub rate: TcRateSpec,
            pub peakrate: TcRateSpec,
            pub refcnt: i32,
            pub bindcnt: i32,
            pub capab: u32,
        }

        impl Default for TcPolice {
            fn default() -> Self {
                Self {
                    index: 0,
                    action: super::TC_ACT_OK,
                    limit: 0,
                    burst: 0,
                    mtu: 0,
                    rate: TcRateSpec::default(),
                    peakrate: TcRateSpec::default(),
                    refcnt: 0,
                    bindcnt: 0,
                    capab: 0,
                }
            }
        }

        impl TcPolice {
            pub fn as_bytes(&self) -> &[u8] {
                <Self as IntoBytes>::as_bytes(self)
            }
        }
    }

    /// Vlan action attributes.
    pub mod vlan {
        use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};

        pub const TCA_VLAN_UNSPEC: u16 = 0;
        pub const TCA_VLAN_TM: u16 = 1;
        pub const TCA_VLAN_PARMS: u16 = 2;
        pub const TCA_VLAN_PUSH_VLAN_ID: u16 = 3;
        pub const TCA_VLAN_PUSH_VLAN_PROTOCOL: u16 = 4;
        pub const TCA_VLAN_PAD: u16 = 5;
        pub const TCA_VLAN_PUSH_VLAN_PRIORITY: u16 = 6;
        pub const TCA_VLAN_PUSH_ETH_DST: u16 = 7;
        pub const TCA_VLAN_PUSH_ETH_SRC: u16 = 8;

        /// Vlan action types.
        pub const TCA_VLAN_ACT_POP: i32 = 1;
        pub const TCA_VLAN_ACT_PUSH: i32 = 2;
        pub const TCA_VLAN_ACT_MODIFY: i32 = 3;
        pub const TCA_VLAN_ACT_POP_ETH: i32 = 4;
        pub const TCA_VLAN_ACT_PUSH_ETH: i32 = 5;

        /// Ethernet protocol for VLAN.
        pub const ETH_P_8021Q: u16 = 0x8100;
        pub const ETH_P_8021AD: u16 = 0x88A8;

        /// Format vlan action type to string.
        pub fn format_vlan_action(v_action: i32) -> &'static str {
            match v_action {
                TCA_VLAN_ACT_POP => "pop",
                TCA_VLAN_ACT_PUSH => "push",
                TCA_VLAN_ACT_MODIFY => "modify",
                TCA_VLAN_ACT_POP_ETH => "pop_eth",
                TCA_VLAN_ACT_PUSH_ETH => "push_eth",
                _ => "unknown",
            }
        }

        /// Vlan action parameters (struct tc_vlan).
        #[repr(C)]
        #[derive(Debug, Clone, Copy, Default, FromBytes, IntoBytes, Immutable, KnownLayout)]
        pub struct TcVlan {
            /// Common action fields (tc_gen).
            pub index: u32,
            pub capab: u32,
            pub action: i32,
            pub refcnt: i32,
            pub bindcnt: i32,
            /// Vlan-specific action type.
            pub v_action: i32,
        }

        impl TcVlan {
            pub fn new(v_action: i32, action: i32) -> Self {
                Self {
                    index: 0,
                    capab: 0,
                    action,
                    refcnt: 0,
                    bindcnt: 0,
                    v_action,
                }
            }

            pub fn as_bytes(&self) -> &[u8] {
                <Self as IntoBytes>::as_bytes(self)
            }
        }
    }

    /// Skbedit action attributes.
    pub mod skbedit {
        use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
        pub const TCA_SKBEDIT_UNSPEC: u16 = 0;
        pub const TCA_SKBEDIT_TM: u16 = 1;
        pub const TCA_SKBEDIT_PARMS: u16 = 2;
        pub const TCA_SKBEDIT_PRIORITY: u16 = 3;
        pub const TCA_SKBEDIT_QUEUE_MAPPING: u16 = 4;
        pub const TCA_SKBEDIT_MARK: u16 = 5;
        pub const TCA_SKBEDIT_PAD: u16 = 6;
        pub const TCA_SKBEDIT_PTYPE: u16 = 7;
        pub const TCA_SKBEDIT_MASK: u16 = 8;
        pub const TCA_SKBEDIT_FLAGS: u16 = 9;
        pub const TCA_SKBEDIT_QUEUE_MAPPING_MAX: u16 = 10;

        /// Skbedit flags.
        pub const SKBEDIT_F_PRIORITY: u64 = 0x1;
        pub const SKBEDIT_F_QUEUE_MAPPING: u64 = 0x2;
        pub const SKBEDIT_F_MARK: u64 = 0x4;
        pub const SKBEDIT_F_PTYPE: u64 = 0x8;
        pub const SKBEDIT_F_MASK: u64 = 0x10;
        pub const SKBEDIT_F_INHERITDSFIELD: u64 = 0x20;

        /// Packet types for ptype.
        pub const PACKET_HOST: u16 = 0;
        pub const PACKET_BROADCAST: u16 = 1;
        pub const PACKET_MULTICAST: u16 = 2;
        pub const PACKET_OTHERHOST: u16 = 3;
        pub const PACKET_OUTGOING: u16 = 4;
        pub const PACKET_LOOPBACK: u16 = 5;

        /// Skbedit action parameters (struct tc_skbedit).
        #[repr(C)]
        #[derive(Debug, Clone, Copy, Default, FromBytes, IntoBytes, Immutable, KnownLayout)]
        pub struct TcSkbedit {
            /// Common action fields (tc_gen).
            pub index: u32,
            pub capab: u32,
            pub action: i32,
            pub refcnt: i32,
            pub bindcnt: i32,
        }

        impl TcSkbedit {
            pub fn new(action: i32) -> Self {
                Self {
                    index: 0,
                    capab: 0,
                    action,
                    refcnt: 0,
                    bindcnt: 0,
                }
            }

            pub fn as_bytes(&self) -> &[u8] {
                <Self as IntoBytes>::as_bytes(self)
            }
        }
    }

    /// NAT action attributes.
    pub mod nat {
        use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
        pub const TCA_NAT_UNSPEC: u16 = 0;
        pub const TCA_NAT_PARMS: u16 = 1;
        pub const TCA_NAT_TM: u16 = 2;
        pub const TCA_NAT_PAD: u16 = 3;

        /// Flag to indicate egress NAT (source NAT), otherwise ingress (destination NAT).
        pub const TCA_NAT_FLAG_EGRESS: u32 = 1;

        /// NAT action parameters (struct tc_nat).
        #[repr(C)]
        #[derive(Debug, Clone, Copy, Default, FromBytes, IntoBytes, Immutable, KnownLayout)]
        pub struct TcNat {
            /// Common action fields (tc_gen).
            pub index: u32,
            pub capab: u32,
            pub action: i32,
            pub refcnt: i32,
            pub bindcnt: i32,
            /// Original address to match (network byte order).
            pub old_addr: u32,
            /// New address to translate to (network byte order).
            pub new_addr: u32,
            /// Network mask (network byte order).
            pub mask: u32,
            /// Flags (TCA_NAT_FLAG_EGRESS for source NAT).
            pub flags: u32,
        }

        impl TcNat {
            pub fn new(old_addr: u32, new_addr: u32, mask: u32, flags: u32, action: i32) -> Self {
                Self {
                    index: 0,
                    capab: 0,
                    action,
                    refcnt: 0,
                    bindcnt: 0,
                    old_addr,
                    new_addr,
                    mask,
                    flags,
                }
            }

            pub fn as_bytes(&self) -> &[u8] {
                <Self as IntoBytes>::as_bytes(self)
            }
        }
    }

    /// Tunnel key action attributes.
    pub mod tunnel_key {
        use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};

        pub const TCA_TUNNEL_KEY_UNSPEC: u16 = 0;
        pub const TCA_TUNNEL_KEY_TM: u16 = 1;
        pub const TCA_TUNNEL_KEY_PARMS: u16 = 2;
        pub const TCA_TUNNEL_KEY_ENC_IPV4_SRC: u16 = 3;
        pub const TCA_TUNNEL_KEY_ENC_IPV4_DST: u16 = 4;
        pub const TCA_TUNNEL_KEY_ENC_IPV6_SRC: u16 = 5;
        pub const TCA_TUNNEL_KEY_ENC_IPV6_DST: u16 = 6;
        pub const TCA_TUNNEL_KEY_ENC_KEY_ID: u16 = 7;
        pub const TCA_TUNNEL_KEY_PAD: u16 = 8;
        pub const TCA_TUNNEL_KEY_ENC_DST_PORT: u16 = 9;
        pub const TCA_TUNNEL_KEY_NO_CSUM: u16 = 10;
        pub const TCA_TUNNEL_KEY_ENC_OPTS: u16 = 11;
        pub const TCA_TUNNEL_KEY_ENC_TOS: u16 = 12;
        pub const TCA_TUNNEL_KEY_ENC_TTL: u16 = 13;
        pub const TCA_TUNNEL_KEY_NO_FRAG: u16 = 14;

        /// Tunnel key action types.
        pub const TCA_TUNNEL_KEY_ACT_SET: i32 = 1;
        pub const TCA_TUNNEL_KEY_ACT_RELEASE: i32 = 2;

        /// Tunnel key action parameters (struct tc_tunnel_key).
        #[repr(C)]
        #[derive(Debug, Clone, Copy, Default, FromBytes, IntoBytes, Immutable, KnownLayout)]
        pub struct TcTunnelKey {
            /// Common action fields (tc_gen).
            pub index: u32,
            pub capab: u32,
            pub action: i32,
            pub refcnt: i32,
            pub bindcnt: i32,
            /// Tunnel key action type.
            pub t_action: i32,
        }

        impl TcTunnelKey {
            pub fn new(t_action: i32, action: i32) -> Self {
                Self {
                    index: 0,
                    capab: 0,
                    action,
                    refcnt: 0,
                    bindcnt: 0,
                    t_action,
                }
            }

            pub fn as_bytes(&self) -> &[u8] {
                <Self as IntoBytes>::as_bytes(self)
            }
        }
    }

    /// Connmark action attributes and structures.
    pub mod connmark {
        use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
        pub const TCA_CONNMARK_UNSPEC: u16 = 0;
        pub const TCA_CONNMARK_PARMS: u16 = 1;
        pub const TCA_CONNMARK_TM: u16 = 2;

        /// Connmark action parameters (struct tc_connmark).
        #[repr(C)]
        #[derive(Debug, Clone, Copy, Default, FromBytes, IntoBytes, Immutable, KnownLayout)]
        pub struct TcConnmark {
            /// Common action fields (tc_gen).
            pub index: u32,
            pub capab: u32,
            pub action: i32,
            pub refcnt: i32,
            pub bindcnt: i32,
            /// Conntrack zone.
            pub zone: u16,
            /// Padding to ensure alignment for zerocopy.
            pub _pad: u16,
        }

        impl TcConnmark {
            pub fn new(zone: u16, action: i32) -> Self {
                Self {
                    index: 0,
                    capab: 0,
                    action,
                    refcnt: 0,
                    bindcnt: 0,
                    zone,
                    _pad: 0,
                }
            }

            pub fn as_bytes(&self) -> &[u8] {
                <Self as IntoBytes>::as_bytes(self)
            }
        }
    }

    /// Csum (checksum) action attributes and structures.
    pub mod csum {
        use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
        pub const TCA_CSUM_UNSPEC: u16 = 0;
        pub const TCA_CSUM_PARMS: u16 = 1;
        pub const TCA_CSUM_TM: u16 = 2;

        /// Update flags for checksum recalculation.
        pub const TCA_CSUM_UPDATE_FLAG_IPV4HDR: u32 = 1;
        pub const TCA_CSUM_UPDATE_FLAG_ICMP: u32 = 2;
        pub const TCA_CSUM_UPDATE_FLAG_IGMP: u32 = 4;
        pub const TCA_CSUM_UPDATE_FLAG_TCP: u32 = 8;
        pub const TCA_CSUM_UPDATE_FLAG_UDP: u32 = 16;
        pub const TCA_CSUM_UPDATE_FLAG_UDPLITE: u32 = 32;
        pub const TCA_CSUM_UPDATE_FLAG_SCTP: u32 = 64;

        /// Csum action parameters (struct tc_csum).
        #[repr(C)]
        #[derive(Debug, Clone, Copy, Default, FromBytes, IntoBytes, Immutable, KnownLayout)]
        pub struct TcCsum {
            /// Common action fields (tc_gen).
            pub index: u32,
            pub capab: u32,
            pub action: i32,
            pub refcnt: i32,
            pub bindcnt: i32,
            /// Update flags.
            pub update_flags: u32,
        }

        impl TcCsum {
            pub fn new(update_flags: u32, action: i32) -> Self {
                Self {
                    index: 0,
                    capab: 0,
                    action,
                    refcnt: 0,
                    bindcnt: 0,
                    update_flags,
                }
            }

            pub fn as_bytes(&self) -> &[u8] {
                <Self as IntoBytes>::as_bytes(self)
            }
        }
    }

    /// Sample action attributes and structures.
    pub mod sample {
        use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
        pub const TCA_SAMPLE_UNSPEC: u16 = 0;
        pub const TCA_SAMPLE_TM: u16 = 1;
        pub const TCA_SAMPLE_PARMS: u16 = 2;
        pub const TCA_SAMPLE_RATE: u16 = 3;
        pub const TCA_SAMPLE_TRUNC_SIZE: u16 = 4;
        pub const TCA_SAMPLE_PSAMPLE_GROUP: u16 = 5;

        /// Sample action parameters (struct tc_sample - just tc_gen).
        #[repr(C)]
        #[derive(Debug, Clone, Copy, Default, FromBytes, IntoBytes, Immutable, KnownLayout)]
        pub struct TcSample {
            /// Common action fields (tc_gen).
            pub index: u32,
            pub capab: u32,
            pub action: i32,
            pub refcnt: i32,
            pub bindcnt: i32,
        }

        impl TcSample {
            pub fn new(action: i32) -> Self {
                Self {
                    index: 0,
                    capab: 0,
                    action,
                    refcnt: 0,
                    bindcnt: 0,
                }
            }

            pub fn as_bytes(&self) -> &[u8] {
                <Self as IntoBytes>::as_bytes(self)
            }
        }
    }

    /// CT (Connection Tracking) action attributes and structures.
    pub mod ct {
        use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
        pub const TCA_CT_UNSPEC: u16 = 0;
        pub const TCA_CT_PARMS: u16 = 1;
        pub const TCA_CT_TM: u16 = 2;
        pub const TCA_CT_ACTION: u16 = 3;
        pub const TCA_CT_ZONE: u16 = 4;
        pub const TCA_CT_MARK: u16 = 5;
        pub const TCA_CT_MARK_MASK: u16 = 6;
        pub const TCA_CT_LABELS: u16 = 7;
        pub const TCA_CT_LABELS_MASK: u16 = 8;
        pub const TCA_CT_NAT_IPV4_MIN: u16 = 9;
        pub const TCA_CT_NAT_IPV4_MAX: u16 = 10;
        pub const TCA_CT_NAT_IPV6_MIN: u16 = 11;
        pub const TCA_CT_NAT_IPV6_MAX: u16 = 12;
        pub const TCA_CT_NAT_PORT_MIN: u16 = 13;
        pub const TCA_CT_NAT_PORT_MAX: u16 = 14;
        pub const TCA_CT_PAD: u16 = 15;
        pub const TCA_CT_HELPER_NAME: u16 = 16;
        pub const TCA_CT_HELPER_FAMILY: u16 = 17;
        pub const TCA_CT_HELPER_PROTO: u16 = 18;

        /// CT action flags.
        pub const TCA_CT_ACT_COMMIT: u16 = 1 << 0;
        pub const TCA_CT_ACT_FORCE: u16 = 1 << 1;
        pub const TCA_CT_ACT_CLEAR: u16 = 1 << 2;
        pub const TCA_CT_ACT_NAT: u16 = 1 << 3;
        pub const TCA_CT_ACT_NAT_SRC: u16 = 1 << 4;
        pub const TCA_CT_ACT_NAT_DST: u16 = 1 << 5;

        /// CT action parameters (struct tc_ct - just tc_gen).
        #[repr(C)]
        #[derive(Debug, Clone, Copy, Default, FromBytes, IntoBytes, Immutable, KnownLayout)]
        pub struct TcCt {
            /// Common action fields (tc_gen).
            pub index: u32,
            pub capab: u32,
            pub action: i32,
            pub refcnt: i32,
            pub bindcnt: i32,
        }

        impl TcCt {
            pub fn new(action: i32) -> Self {
                Self {
                    index: 0,
                    capab: 0,
                    action,
                    refcnt: 0,
                    bindcnt: 0,
                }
            }

            pub fn as_bytes(&self) -> &[u8] {
                <Self as IntoBytes>::as_bytes(self)
            }
        }
    }

    /// Pedit (packet edit) action attributes and structures.
    pub mod pedit {
        use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
        pub const TCA_PEDIT_UNSPEC: u16 = 0;
        pub const TCA_PEDIT_TM: u16 = 1;
        pub const TCA_PEDIT_PARMS: u16 = 2;
        pub const TCA_PEDIT_PAD: u16 = 3;
        pub const TCA_PEDIT_PARMS_EX: u16 = 4;
        pub const TCA_PEDIT_KEYS_EX: u16 = 5;
        pub const TCA_PEDIT_KEY_EX: u16 = 6;

        /// Pedit key extended attributes.
        pub const TCA_PEDIT_KEY_EX_HTYPE: u16 = 1;
        pub const TCA_PEDIT_KEY_EX_CMD: u16 = 2;

        /// Pedit header types.
        pub const TCA_PEDIT_KEY_EX_HDR_TYPE_NETWORK: u16 = 0;
        pub const TCA_PEDIT_KEY_EX_HDR_TYPE_ETH: u16 = 1;
        pub const TCA_PEDIT_KEY_EX_HDR_TYPE_IP4: u16 = 2;
        pub const TCA_PEDIT_KEY_EX_HDR_TYPE_IP6: u16 = 3;
        pub const TCA_PEDIT_KEY_EX_HDR_TYPE_TCP: u16 = 4;
        pub const TCA_PEDIT_KEY_EX_HDR_TYPE_UDP: u16 = 5;

        /// Pedit commands.
        pub const TCA_PEDIT_KEY_EX_CMD_SET: u16 = 0;
        pub const TCA_PEDIT_KEY_EX_CMD_ADD: u16 = 1;

        /// Pedit key (struct tc_pedit_key).
        #[repr(C)]
        #[derive(Debug, Clone, Copy, Default, FromBytes, IntoBytes, Immutable, KnownLayout)]
        pub struct TcPeditKey {
            /// Mask of bits to modify.
            pub mask: u32,
            /// Value to set.
            pub val: u32,
            /// Offset within the header.
            pub off: u32,
            /// Which header at that offset.
            pub at: u32,
            /// Offset mask.
            pub offmask: u32,
            /// Shift amount.
            pub shift: u32,
        }

        impl TcPeditKey {
            pub const SIZE: usize = std::mem::size_of::<Self>();

            pub fn new(mask: u32, val: u32, off: u32) -> Self {
                Self {
                    mask,
                    val,
                    off,
                    at: 0,
                    offmask: 0,
                    shift: 0,
                }
            }

            pub fn as_bytes(&self) -> &[u8] {
                <Self as IntoBytes>::as_bytes(self)
            }
        }

        /// Pedit selector header (struct tc_pedit_sel without keys array).
        #[repr(C)]
        #[derive(Debug, Clone, Copy, Default, FromBytes, IntoBytes, Immutable, KnownLayout)]
        pub struct TcPeditSel {
            /// Common action fields (tc_gen).
            pub index: u32,
            pub capab: u32,
            pub action: i32,
            pub refcnt: i32,
            pub bindcnt: i32,
            /// Number of keys.
            pub nkeys: u8,
            /// Flags.
            pub flags: u8,
            /// Padding to ensure alignment for zerocopy.
            pub _pad: u16,
        }

        impl TcPeditSel {
            pub fn new(action: i32, nkeys: u8) -> Self {
                Self {
                    index: 0,
                    capab: 0,
                    action,
                    refcnt: 0,
                    bindcnt: 0,
                    nkeys,
                    flags: 0,
                    _pad: 0,
                }
            }

            pub fn as_bytes(&self) -> &[u8] {
                <Self as IntoBytes>::as_bytes(self)
            }
        }
    }
}
