use crate::macros::GenlMessage;
use crate::netlink::genl::GENL_HDRLEN;
use super::messages::{DpllDeviceReply, DpllPinReply};
use super::types::DpllCmd;
use super::Dpll;
#[derive(Debug, Clone)]
#[non_exhaustive]
pub enum DpllEvent {
DeviceCreated(DpllDeviceReply),
DeviceDeleted { id: u32 },
DeviceChanged(DpllDeviceReply),
PinCreated(DpllPinReply),
PinDeleted { id: u32 },
PinChanged(DpllPinReply),
}
pub(crate) fn parse_dpll_event(payload: &[u8]) -> Option<DpllEvent> {
if payload.len() < GENL_HDRLEN {
return None;
}
let cmd = payload[0];
let attrs = &payload[GENL_HDRLEN..];
if cmd == DpllCmd::DeviceCreateNtf as u8 {
let reply = DpllDeviceReply::from_bytes(attrs).ok()?;
return Some(DpllEvent::DeviceCreated(reply));
}
if cmd == DpllCmd::DeviceChangeNtf as u8 {
let reply = DpllDeviceReply::from_bytes(attrs).ok()?;
return Some(DpllEvent::DeviceChanged(reply));
}
if cmd == DpllCmd::DeviceDeleteNtf as u8 {
let reply = DpllDeviceReply::from_bytes(attrs).ok()?;
return Some(DpllEvent::DeviceDeleted { id: reply.id });
}
if cmd == DpllCmd::PinCreateNtf as u8 {
let reply = DpllPinReply::from_bytes(attrs).ok()?;
return Some(DpllEvent::PinCreated(reply));
}
if cmd == DpllCmd::PinChangeNtf as u8 {
let reply = DpllPinReply::from_bytes(attrs).ok()?;
return Some(DpllEvent::PinChanged(reply));
}
if cmd == DpllCmd::PinDeleteNtf as u8 {
let reply = DpllPinReply::from_bytes(attrs).ok()?;
return Some(DpllEvent::PinDeleted { id: reply.id });
}
None
}
impl crate::netlink::Connection<Dpll> {
pub fn subscribe_monitor(&self) -> crate::Result<()> {
self.subscribe_group("monitor")
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::macros::__rt;
use crate::netlink::MessageBuilder;
use crate::netlink::genl::GenlMsgHdr;
use crate::netlink::genl::dpll::types::{
DpllAttr, DpllLockStatus, DpllMode, DpllPinAttr, DpllPinState,
};
fn synth_payload(cmd: DpllCmd, attrs_fn: impl FnOnce(&mut MessageBuilder)) -> Vec<u8> {
let mut b = MessageBuilder::new(0, 0);
b.append(&GenlMsgHdr::new(cmd as u8, 1));
attrs_fn(&mut b);
let full = b.finish();
full[16..].to_vec()
}
#[test]
fn parses_device_change_with_full_state() {
let payload = synth_payload(DpllCmd::DeviceChangeNtf, |b| {
__rt::emit_u32_attr(b, DpllAttr::Id as u16, 7);
__rt::emit_str_attr(b, DpllAttr::ModuleName as u16, "ice");
__rt::emit_u32_attr(b, DpllAttr::LockStatus as u16, DpllLockStatus::Locked as u32);
__rt::emit_u32_attr(b, DpllAttr::Mode as u16, DpllMode::Automatic as u32);
});
let evt = parse_dpll_event(&payload).expect("parsed");
match evt {
DpllEvent::DeviceChanged(dev) => {
assert_eq!(dev.id, 7);
assert_eq!(dev.module_name, "ice");
assert_eq!(dev.lock_status, Some(DpllLockStatus::Locked));
assert_eq!(dev.mode, Some(DpllMode::Automatic));
}
other => panic!("expected DeviceChanged; got {other:?}"),
}
}
#[test]
fn parses_device_delete_extracting_id() {
let payload = synth_payload(DpllCmd::DeviceDeleteNtf, |b| {
__rt::emit_u32_attr(b, DpllAttr::Id as u16, 42);
});
let evt = parse_dpll_event(&payload).expect("parsed");
match evt {
DpllEvent::DeviceDeleted { id } => assert_eq!(id, 42),
other => panic!("expected DeviceDeleted; got {other:?}"),
}
}
#[test]
fn parses_pin_change() {
let payload = synth_payload(DpllCmd::PinChangeNtf, |b| {
__rt::emit_u32_attr(b, DpllPinAttr::Id as u16, 9);
__rt::emit_u32_attr(
b,
DpllPinAttr::State as u16,
DpllPinState::Connected as u32,
);
__rt::emit_u32_attr(b, DpllPinAttr::Prio as u16, 5);
});
let evt = parse_dpll_event(&payload).expect("parsed");
match evt {
DpllEvent::PinChanged(pin) => {
assert_eq!(pin.id, 9);
assert_eq!(pin.state, Some(DpllPinState::Connected));
assert_eq!(pin.prio, Some(5));
}
other => panic!("expected PinChanged; got {other:?}"),
}
}
#[test]
fn rejects_non_notification_commands() {
let payload = synth_payload(DpllCmd::DeviceGet, |b| {
__rt::emit_u32_attr(b, DpllAttr::Id as u16, 0);
});
assert!(parse_dpll_event(&payload).is_none());
}
#[test]
fn rejects_truncated_payload() {
assert!(parse_dpll_event(&[]).is_none());
assert!(parse_dpll_event(&[1]).is_none()); }
}