1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
// SPDX-License-Identifier: MIT

use anyhow::Context;
use netlink_packet_utils::{
    nla::{DefaultNla, Nla, NlaBuffer, NlasIterator},
    traits::{Parseable, ParseableParametrized},
    DecodeError,
};

use super::{
    TcFilterMatchAll, TcFilterMatchAllOption, TcFilterU32, TcFilterU32Option,
    TcQdiscFqCodel, TcQdiscFqCodelOption, TcQdiscIngress, TcQdiscIngressOption,
};

#[derive(Debug, PartialEq, Eq, Clone)]
#[non_exhaustive]
pub enum TcOption {
    FqCodel(TcQdiscFqCodelOption),
    // Qdisc specific options
    Ingress(TcQdiscIngressOption),
    // Filter specific options
    U32(TcFilterU32Option),
    // matchall options
    MatchAll(TcFilterMatchAllOption),
    // Other options
    Other(DefaultNla),
}

impl Nla for TcOption {
    fn value_len(&self) -> usize {
        match self {
            Self::FqCodel(u) => u.value_len(),
            Self::Ingress(u) => u.value_len(),
            Self::U32(u) => u.value_len(),
            Self::MatchAll(m) => m.value_len(),
            Self::Other(o) => o.value_len(),
        }
    }

    fn emit_value(&self, buffer: &mut [u8]) {
        match self {
            Self::FqCodel(u) => u.emit_value(buffer),
            Self::Ingress(u) => u.emit_value(buffer),
            Self::U32(u) => u.emit_value(buffer),
            Self::MatchAll(m) => m.emit_value(buffer),
            Self::Other(o) => o.emit_value(buffer),
        }
    }

    fn kind(&self) -> u16 {
        match self {
            Self::FqCodel(u) => u.kind(),
            Self::Ingress(u) => u.kind(),
            Self::U32(u) => u.kind(),
            Self::MatchAll(m) => m.kind(),
            Self::Other(o) => o.kind(),
        }
    }
}

impl<'a, T> ParseableParametrized<NlaBuffer<&'a T>, &str> for TcOption
where
    T: AsRef<[u8]> + ?Sized,
{
    fn parse_with_param(
        buf: &NlaBuffer<&'a T>,
        kind: &str,
    ) -> Result<Self, DecodeError> {
        Ok(match kind {
            TcQdiscIngress::KIND => {
                Self::Ingress(TcQdiscIngressOption::parse(buf).context(
                    "failed to parse ingress TCA_OPTIONS attributes",
                )?)
            }
            TcQdiscFqCodel::KIND => {
                Self::FqCodel(TcQdiscFqCodelOption::parse(buf).context(
                    "failed to parse fq_codel TCA_OPTIONS attributes",
                )?)
            }
            TcFilterU32::KIND => Self::U32(
                TcFilterU32Option::parse(buf)
                    .context("failed to parse u32 TCA_OPTIONS attributes")?,
            ),
            TcFilterMatchAll::KIND => {
                Self::MatchAll(TcFilterMatchAllOption::parse(buf).context(
                    "failed to parse matchall TCA_OPTIONS attributes",
                )?)
            }
            _ => Self::Other(DefaultNla::parse(buf)?),
        })
    }
}

pub(crate) struct VecTcOption(pub(crate) Vec<TcOption>);

impl<'a, T> ParseableParametrized<NlaBuffer<&'a T>, &str> for VecTcOption
where
    T: AsRef<[u8]> + ?Sized,
{
    fn parse_with_param(
        buf: &NlaBuffer<&'a T>,
        kind: &str,
    ) -> Result<VecTcOption, DecodeError> {
        Ok(match kind {
            TcFilterU32::KIND
            | TcFilterMatchAll::KIND
            | TcQdiscIngress::KIND
            | TcQdiscFqCodel::KIND => {
                let mut nlas = vec![];
                for nla in NlasIterator::new(buf.value()) {
                    let nla = nla.context(format!(
                        "Invalid TCA_OPTIONS for kind: {kind}",
                    ))?;
                    nlas.push(
                        TcOption::parse_with_param(&nla, kind).context(
                            format!(
                                "Failed to parse TCA_OPTIONS for kind: {kind}",
                            ),
                        )?,
                    )
                }
                Self(nlas)
            }
            // Kernel has no guide line or code indicate the scheduler
            // should place a nla_nest here. The `sfq` qdisc kernel code is
            // using single NLA instead nested ones. Hence we are storing
            // unknown Nla as Vec with single item.
            _ => Self(vec![TcOption::Other(DefaultNla::parse(buf)?)]),
        })
    }
}