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
132
133
134
135
// SPDX-License-Identifier: MIT

use anyhow::Context;
use netlink_packet_utils::{
    nla::{Nla, NlaBuffer, NlasIterator},
    parsers::parse_string,
    DecodeError, Emitable, Parseable,
};

use super::super::{InfoBondPort, InfoBridgePort};

const BOND: &str = "bond";
const BRIDGE: &str = "bridge";

const IFLA_INFO_PORT_KIND: u16 = 4;
const IFLA_INFO_PORT_DATA: u16 = 5;

#[derive(Debug, PartialEq, Eq, Clone)]
#[non_exhaustive]
pub enum InfoPortKind {
    Bond,
    Bridge,
    Other(String),
}

impl std::fmt::Display for InfoPortKind {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "{}",
            match self {
                Self::Bond => BOND,
                Self::Bridge => BRIDGE,
                Self::Other(s) => s.as_str(),
            }
        )
    }
}

impl Nla for InfoPortKind {
    fn value_len(&self) -> usize {
        let len = match self {
            Self::Bond => BOND.len(),
            Self::Bridge => BRIDGE.len(),
            Self::Other(s) => s.len(),
        };
        len + 1
    }

    fn emit_value(&self, buffer: &mut [u8]) {
        let s = match self {
            Self::Bond => BOND,
            Self::Bridge => BRIDGE,
            Self::Other(s) => s.as_str(),
        };
        buffer[..s.len()].copy_from_slice(s.as_bytes());
        buffer[s.len()] = 0;
    }

    fn kind(&self) -> u16 {
        IFLA_INFO_PORT_KIND
    }
}

impl<'a, T: AsRef<[u8]> + ?Sized> Parseable<NlaBuffer<&'a T>> for InfoPortKind {
    fn parse(buf: &NlaBuffer<&'a T>) -> Result<InfoPortKind, DecodeError> {
        if buf.kind() != IFLA_INFO_PORT_KIND {
            return Err(format!(
                "failed to parse IFLA_INFO_PORT_KIND: NLA type is {}",
                buf.kind()
            )
            .into());
        }
        let s = parse_string(buf.value())
            .context("invalid IFLA_INFO_PORT_KIND value")?;
        Ok(match s.as_str() {
            BOND => Self::Bond,
            BRIDGE => Self::Bridge,
            _ => Self::Other(s),
        })
    }
}

#[derive(Debug, PartialEq, Eq, Clone)]
#[non_exhaustive]
pub enum InfoPortData {
    BondPort(Vec<InfoBondPort>),
    BridgePort(Vec<InfoBridgePort>),
    Other(Vec<u8>),
}

impl Nla for InfoPortData {
    fn value_len(&self) -> usize {
        match self {
            Self::BondPort(nlas) => nlas.as_slice().buffer_len(),
            Self::BridgePort(nlas) => nlas.as_slice().buffer_len(),
            Self::Other(bytes) => bytes.len(),
        }
    }

    fn emit_value(&self, buffer: &mut [u8]) {
        match self {
            Self::BondPort(nlas) => nlas.as_slice().emit(buffer),
            Self::BridgePort(nlas) => nlas.as_slice().emit(buffer),
            Self::Other(bytes) => buffer.copy_from_slice(bytes),
        }
    }

    fn kind(&self) -> u16 {
        IFLA_INFO_PORT_DATA
    }
}

impl InfoPortData {
    pub(crate) fn parse_with_param(
        payload: &[u8],
        kind: InfoPortKind,
    ) -> Result<InfoPortData, DecodeError> {
        let port_data = match kind {
            InfoPortKind::Bond => NlasIterator::new(payload)
                .map(|nla| nla.and_then(|nla| InfoBondPort::parse(&nla)))
                .collect::<Result<Vec<_>, _>>()
                .map(InfoPortData::BondPort),
            InfoPortKind::Bridge => NlasIterator::new(payload)
                .map(|nla| nla.and_then(|nla| InfoBridgePort::parse(&nla)))
                .collect::<Result<Vec<_>, _>>()
                .map(InfoPortData::BridgePort),
            InfoPortKind::Other(_) => Ok(InfoPortData::Other(payload.to_vec())),
        };

        Ok(port_data.context(format!(
            "failed to parse IFLA_INFO_PORT_DATA (IFLA_INFO_PORT_KIND is '{kind}')"
        ))?)
    }
}