use anyhow::Context;
use netlink_packet_utils::{
nla::{DefaultNla, Nla, NlaBuffer, NlasIterator, NLA_F_NESTED},
parsers::{parse_u32, parse_u8},
DecodeError, Emitable, Parseable,
};
use crate::{bitset_util::parse_bitset_bits_nlas, EthtoolAttr, EthtoolHeader};
const ETHTOOL_A_LINKMODES_HEADER: u16 = 1;
const ETHTOOL_A_LINKMODES_AUTONEG: u16 = 2;
const ETHTOOL_A_LINKMODES_OURS: u16 = 3;
const ETHTOOL_A_LINKMODES_PEER: u16 = 4;
const ETHTOOL_A_LINKMODES_SPEED: u16 = 5;
const ETHTOOL_A_LINKMODES_DUPLEX: u16 = 6;
const ETHTOOL_A_LINKMODES_SUBORDINATE_CFG: u16 = 7;
const ETHTOOL_A_LINKMODES_SUBORDINATE_STATE: u16 = 8;
const ETHTOOL_A_LINKMODES_LANES: u16 = 9;
const DUPLEX_HALF: u8 = 0x00;
const DUPLEX_FULL: u8 = 0x01;
const DUPLEX_UNKNOWN: u8 = 0xff;
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum EthtoolLinkModeDuplex {
Half,
Full,
Unknown,
Other(u8),
}
impl From<u8> for EthtoolLinkModeDuplex {
fn from(d: u8) -> Self {
match d {
DUPLEX_HALF => Self::Half,
DUPLEX_FULL => Self::Full,
DUPLEX_UNKNOWN => Self::Unknown,
_ => Self::Other(d),
}
}
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum EthtoolLinkModeAttr {
Header(Vec<EthtoolHeader>),
Autoneg(bool),
Ours(Vec<String>),
Peer(Vec<String>),
Speed(u32),
Duplex(EthtoolLinkModeDuplex),
ControllerSubordinateCfg(u8),
ControllerSubordinateState(u8),
Lanes(u32),
Other(DefaultNla),
}
impl Nla for EthtoolLinkModeAttr {
fn value_len(&self) -> usize {
match self {
Self::Header(hdrs) => hdrs.as_slice().buffer_len(),
Self::Autoneg(_)
| Self::Duplex(_)
| Self::ControllerSubordinateCfg(_)
| Self::ControllerSubordinateState(_) => 1,
Self::Ours(_) => {
todo!("Does not support changing ethtool link mode yet")
}
Self::Peer(_) => {
todo!("Does not support changing ethtool link mode yet")
}
Self::Speed(_) | Self::Lanes(_) => 4,
Self::Other(attr) => attr.value_len(),
}
}
fn kind(&self) -> u16 {
match self {
Self::Header(_) => ETHTOOL_A_LINKMODES_HEADER | NLA_F_NESTED,
Self::Autoneg(_) => ETHTOOL_A_LINKMODES_AUTONEG,
Self::Ours(_) => ETHTOOL_A_LINKMODES_OURS,
Self::Peer(_) => ETHTOOL_A_LINKMODES_PEER,
Self::Speed(_) => ETHTOOL_A_LINKMODES_SPEED,
Self::Duplex(_) => ETHTOOL_A_LINKMODES_DUPLEX,
Self::ControllerSubordinateCfg(_) => {
ETHTOOL_A_LINKMODES_SUBORDINATE_CFG
}
Self::ControllerSubordinateState(_) => {
ETHTOOL_A_LINKMODES_SUBORDINATE_STATE
}
Self::Lanes(_) => ETHTOOL_A_LINKMODES_LANES,
Self::Other(attr) => attr.kind(),
}
}
fn emit_value(&self, buffer: &mut [u8]) {
match self {
Self::Header(ref nlas) => nlas.as_slice().emit(buffer),
Self::Other(ref attr) => attr.emit(buffer),
_ => todo!("Does not support changing ethtool link mode yet"),
}
}
}
impl<'a, T: AsRef<[u8]> + ?Sized> Parseable<NlaBuffer<&'a T>>
for EthtoolLinkModeAttr
{
fn parse(buf: &NlaBuffer<&'a T>) -> Result<Self, DecodeError> {
let payload = buf.value();
Ok(match buf.kind() {
ETHTOOL_A_LINKMODES_HEADER => {
let mut nlas = Vec::new();
let error_msg = "failed to parse link_mode header attributes";
for nla in NlasIterator::new(payload) {
let nla = &nla.context(error_msg)?;
let parsed =
EthtoolHeader::parse(nla).context(error_msg)?;
nlas.push(parsed);
}
Self::Header(nlas)
}
ETHTOOL_A_LINKMODES_AUTONEG => Self::Autoneg(
parse_u8(payload)
.context("Invalid ETHTOOL_A_LINKMODES_AUTONEG value")?
== 1,
),
ETHTOOL_A_LINKMODES_OURS => {
Self::Ours(parse_bitset_bits_nlas(payload)?)
}
ETHTOOL_A_LINKMODES_PEER => {
Self::Peer(parse_bitset_bits_nlas(payload)?)
}
ETHTOOL_A_LINKMODES_SPEED => Self::Speed(
parse_u32(payload)
.context("Invalid ETHTOOL_A_LINKMODES_SPEED value")?,
),
ETHTOOL_A_LINKMODES_DUPLEX => Self::Duplex(
parse_u8(payload)
.context("Invalid ETHTOOL_A_LINKMODES_DUPLEX value")?
.into(),
),
ETHTOOL_A_LINKMODES_SUBORDINATE_CFG => {
Self::ControllerSubordinateCfg(parse_u8(payload).context(
"Invalid ETHTOOL_A_LINKMODES_SUBORDINATE_CFG value",
)?)
}
ETHTOOL_A_LINKMODES_SUBORDINATE_STATE => {
Self::ControllerSubordinateState(parse_u8(payload).context(
"Invalid ETHTOOL_A_LINKMODES_SUBORDINATE_STATE value",
)?)
}
ETHTOOL_A_LINKMODES_LANES => Self::Lanes(
parse_u32(payload)
.context("Invalid ETHTOOL_A_LINKMODES_LANES value")?,
),
_ => Self::Other(
DefaultNla::parse(buf).context("invalid NLA (unknown kind)")?,
),
})
}
}
pub(crate) fn parse_link_mode_nlas(
buffer: &[u8],
) -> Result<Vec<EthtoolAttr>, DecodeError> {
let mut nlas = Vec::new();
for nla in NlasIterator::new(buffer) {
let error_msg = format!(
"Failed to parse ethtool link_mode message attribute {nla:?}"
);
let nla = &nla.context(error_msg.clone())?;
let parsed = EthtoolLinkModeAttr::parse(nla).context(error_msg)?;
nlas.push(EthtoolAttr::LinkMode(parsed));
}
Ok(nlas)
}