use super::{
error::{Error, Result},
message::{NlMsgError, NlMsgHdr},
};
#[derive(Debug)]
pub(crate) enum Classification<'a> {
SkipSeq,
Ack,
Error(Error),
Done(Result<()>),
Data { payload: &'a [u8] },
}
pub(crate) fn classify<'a>(
header: &NlMsgHdr,
payload: &'a [u8],
expected_seq: u32,
) -> Classification<'a> {
if header.nlmsg_seq != expected_seq {
return Classification::SkipSeq;
}
if header.is_dump_interrupted() {
return Classification::Error(Error::DumpInterrupted);
}
if header.is_error() {
return match NlMsgError::from_bytes(payload) {
Ok(err) if err.is_ack() => Classification::Ack,
Ok(err) => Classification::Error(err.into_error(payload)),
Err(e) => Classification::Error(e),
};
}
if header.is_done() {
return Classification::Done(done_result(payload));
}
Classification::Data { payload }
}
pub(crate) fn done_result(payload: &[u8]) -> Result<()> {
let Ok(bytes) = payload.get(..4).map(<[u8; 4]>::try_from).transpose() else {
return Ok(());
};
match bytes {
None => Ok(()),
Some(b) => {
let code = i32::from_ne_bytes(b);
if code < 0 {
Err(Error::from_errno_ext_ack(code, None, None))
} else {
Ok(())
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::netlink::message::{NLM_F_DUMP_INTR, NlMsgType};
fn hdr(nlmsg_type: u16, seq: u32, flags: u16) -> NlMsgHdr {
NlMsgHdr {
nlmsg_len: 16,
nlmsg_type,
nlmsg_flags: flags,
nlmsg_seq: seq,
nlmsg_pid: 0,
}
}
#[test]
fn a_frame_from_another_request_is_skipped() {
let h = hdr(NlMsgType::DONE, 7, 0);
assert!(matches!(
classify(&h, &[], 9),
Classification::SkipSeq
));
}
#[test]
fn done_with_a_zero_payload_is_success() {
let h = hdr(NlMsgType::DONE, 1, 0);
match classify(&h, &0i32.to_ne_bytes(), 1) {
Classification::Done(Ok(())) => {}
other => panic!("expected Done(Ok), got {other:?}"),
}
}
#[test]
fn done_with_an_empty_payload_is_success() {
let h = hdr(NlMsgType::DONE, 1, 0);
match classify(&h, &[], 1) {
Classification::Done(Ok(())) => {}
other => panic!("expected Done(Ok), got {other:?}"),
}
}
#[test]
fn done_with_a_negative_payload_is_the_dumps_failure() {
let h = hdr(NlMsgType::DONE, 1, 0);
match classify(&h, &(-2i32).to_ne_bytes(), 1) {
Classification::Done(Err(e)) => {
assert!(e.is_not_found(), "expected ENOENT, got {e:?}");
}
other => panic!("expected Done(Err), got {other:?}"),
}
}
#[test]
fn a_positive_done_payload_is_not_an_error() {
let h = hdr(NlMsgType::DONE, 1, 0);
match classify(&h, &17i32.to_ne_bytes(), 1) {
Classification::Done(Ok(())) => {}
other => panic!("expected Done(Ok), got {other:?}"),
}
}
#[test]
fn a_torn_snapshot_is_an_error_whatever_else_the_frame_is() {
for ty in [NlMsgType::DONE, NlMsgType::ERROR, 16 ] {
let h = hdr(ty, 1, NLM_F_DUMP_INTR);
match classify(&h, &[], 1) {
Classification::Error(e) => assert!(e.is_dump_interrupted()),
other => panic!("type {ty}: expected Error, got {other:?}"),
}
}
}
#[test]
fn dump_intr_on_a_frame_we_are_not_reading_is_still_skipped() {
let h = hdr(16, 7, NLM_F_DUMP_INTR);
assert!(matches!(classify(&h, &[], 9), Classification::SkipSeq));
}
#[test]
fn a_zero_errno_error_frame_is_an_ack() {
let h = hdr(NlMsgType::ERROR, 1, 0);
let mut payload = 0i32.to_ne_bytes().to_vec();
payload.extend_from_slice(hdr(16, 1, 0).as_bytes());
assert!(matches!(
classify(&h, &payload, 1),
Classification::Ack
));
}
#[test]
fn a_nonzero_errno_error_frame_is_an_error() {
let h = hdr(NlMsgType::ERROR, 1, 0);
let mut payload = (-22i32).to_ne_bytes().to_vec();
payload.extend_from_slice(hdr(16, 1, 0).as_bytes());
match classify(&h, &payload, 1) {
Classification::Error(e) => assert!(e.is_invalid_argument()),
other => panic!("expected Error, got {other:?}"),
}
}
#[test]
fn anything_else_is_data() {
let h = hdr(16, 1, 0);
match classify(&h, &[1, 2, 3, 4], 1) {
Classification::Data { payload } => assert_eq!(payload, &[1, 2, 3, 4]),
other => panic!("expected Data, got {other:?}"),
}
}
#[test]
fn done_result_never_panics_on_a_short_payload() {
for n in 0..4 {
assert!(done_result(&vec![0xff; n]).is_ok());
}
}
}