use crate::consts::*;
use crate::error::Error;
use crate::iter::NetlinkMessageIter;
use crate::parse::{parse_cn_msg, parse_netlink_message};
use crate::proc_event::ProcEvent;
use crate::tests::helpers::*;
#[test]
fn parse_exec() {
let data = [
42i32.to_ne_bytes(), 100i32.to_ne_bytes(), ]
.concat();
let buf = make_full_message(PROC_EVENT_EXEC, &data);
let event = parse_netlink_message(&buf, buf.len()).unwrap().unwrap();
assert_eq!(
event,
ProcEvent::Exec {
pid: 42,
tgid: 100,
timestamp_ns: 0,
}
);
}
#[test]
fn parse_fork() {
let data = [
10i32.to_ne_bytes(), 10i32.to_ne_bytes(), 20i32.to_ne_bytes(), 20i32.to_ne_bytes(), ]
.concat();
let buf = make_full_message(PROC_EVENT_FORK, &data);
let event = parse_netlink_message(&buf, buf.len()).unwrap().unwrap();
assert_eq!(
event,
ProcEvent::Fork {
parent_pid: 10,
parent_tgid: 10,
child_pid: 20,
child_tgid: 20,
timestamp_ns: 0,
}
);
}
#[test]
fn parse_exit() {
let data = [
1i32.to_ne_bytes(), 1i32.to_ne_bytes(), 0u32.to_ne_bytes(), 17u32.to_ne_bytes(), 0i32.to_ne_bytes(), 0i32.to_ne_bytes(), ]
.concat();
let buf = make_full_message(PROC_EVENT_EXIT, &data);
let event = parse_netlink_message(&buf, buf.len()).unwrap().unwrap();
assert_eq!(
event,
ProcEvent::Exit {
pid: 1,
tgid: 1,
exit_code: 0,
exit_signal: 17,
timestamp_ns: 0,
}
);
}
#[test]
fn parse_uid() {
let data = [
5i32.to_ne_bytes(), 5i32.to_ne_bytes(), 1000u32.to_ne_bytes(), 0u32.to_ne_bytes(), ]
.concat();
let buf = make_full_message(PROC_EVENT_UID, &data);
let event = parse_netlink_message(&buf, buf.len()).unwrap().unwrap();
assert_eq!(
event,
ProcEvent::Uid {
pid: 5,
tgid: 5,
ruid: 1000,
euid: 0,
timestamp_ns: 0,
}
);
}
#[test]
fn parse_gid() {
let data = [
5i32.to_ne_bytes(), 5i32.to_ne_bytes(), 100u32.to_ne_bytes(), 200u32.to_ne_bytes(), ]
.concat();
let buf = make_full_message(PROC_EVENT_GID, &data);
let event = parse_netlink_message(&buf, buf.len()).unwrap().unwrap();
assert_eq!(
event,
ProcEvent::Gid {
pid: 5,
tgid: 5,
rgid: 100,
egid: 200,
timestamp_ns: 0,
}
);
}
#[test]
fn parse_sid() {
let data = [
7i32.to_ne_bytes(), 7i32.to_ne_bytes(), ]
.concat();
let buf = make_full_message(PROC_EVENT_SID, &data);
let event = parse_netlink_message(&buf, buf.len()).unwrap().unwrap();
assert_eq!(
event,
ProcEvent::Sid {
pid: 7,
tgid: 7,
timestamp_ns: 0
}
);
}
#[test]
fn parse_ptrace() {
let data = [
1i32.to_ne_bytes(), 1i32.to_ne_bytes(), 999i32.to_ne_bytes(), 999i32.to_ne_bytes(), ]
.concat();
let buf = make_full_message(PROC_EVENT_PTRACE, &data);
let event = parse_netlink_message(&buf, buf.len()).unwrap().unwrap();
assert_eq!(
event,
ProcEvent::Ptrace {
pid: 1,
tgid: 1,
tracer_pid: 999,
tracer_tgid: 999,
timestamp_ns: 0,
}
);
}
#[test]
fn parse_comm() {
let data = [
42i32.to_ne_bytes(), 42i32.to_ne_bytes(), ]
.concat();
let mut comm_event = data;
let mut comm = [0u8; 16];
comm[..7].copy_from_slice(b"bash\0\0\0");
comm_event.extend_from_slice(&comm);
let buf = make_full_message(PROC_EVENT_COMM, &comm_event);
let event = parse_netlink_message(&buf, buf.len()).unwrap().unwrap();
assert_eq!(
event,
ProcEvent::Comm {
pid: 42,
tgid: 42,
comm,
timestamp_ns: 0,
}
);
}
#[test]
fn parse_coredump() {
let data = [
1i32.to_ne_bytes(), 1i32.to_ne_bytes(), 0i32.to_ne_bytes(), 0i32.to_ne_bytes(), ]
.concat();
let buf = make_full_message(PROC_EVENT_COREDUMP, &data);
let event = parse_netlink_message(&buf, buf.len()).unwrap().unwrap();
assert_eq!(
event,
ProcEvent::Coredump {
pid: 1,
tgid: 1,
timestamp_ns: 0
}
);
}
#[test]
fn parse_unknown_skipped() {
let data = [1u8, 2, 3, 4];
let buf = make_full_message(0xDEAD, &data);
let event = parse_netlink_message(&buf, buf.len()).unwrap().unwrap();
match event {
ProcEvent::Unknown { what, raw_data } => {
assert_eq!(what, 0xDEAD);
assert_eq!(raw_data, data);
}
_ => panic!("expected Unknown event"),
}
}
#[test]
fn parse_nlmsg_noop() {
let buf = make_netlink_message(NLMSG_NOOP, &[]);
let result = parse_netlink_message(&buf, buf.len()).unwrap();
assert!(result.is_none());
}
#[test]
fn parse_nlmsg_done() {
let buf = make_netlink_message(NLMSG_DONE, &[]);
let result = parse_netlink_message(&buf, buf.len()).unwrap();
assert!(result.is_none());
}
#[test]
fn parse_nlmsg_error() {
let errno = -libc::EPERM;
let mut payload = vec![0u8; SIZE_NLMSGHDR + 20]; payload[0..4].copy_from_slice(&errno.to_ne_bytes());
let buf = make_netlink_message(NLMSG_ERROR, &payload);
let result = parse_netlink_message(&buf, buf.len());
assert!(result.is_err());
match result {
Err(Error::Os(e)) => assert_eq!(e.raw_os_error(), Some(libc::EPERM)),
_ => panic!("expected Os error"),
}
}
#[test]
fn parse_nlmsg_overrun() {
let buf = make_netlink_message(NLMSG_OVERRUN, &[]);
let result = parse_netlink_message(&buf, buf.len());
match result {
Err(Error::Overrun) => {} _ => panic!("expected Overrun error"),
}
}
#[test]
fn truncated_message() {
let buf = vec![0u8; 4]; let result = parse_netlink_message(&buf, buf.len());
match result {
Err(Error::Truncated) => {} _ => panic!("expected Truncated error"),
}
}
#[test]
fn display_exec() {
let event = ProcEvent::Exec {
pid: 42,
tgid: 100,
timestamp_ns: 0,
};
assert_eq!(format!("{event}"), "EXEC pid=42 tgid=100 ts=0");
}
#[test]
fn display_comm() {
let mut comm = [0u8; 16];
comm[..4].copy_from_slice(b"bash");
let event = ProcEvent::Comm {
pid: 1,
tgid: 1,
comm,
timestamp_ns: 0,
};
assert_eq!(format!("{event}"), "COMM pid=1 tgid=1 name=\"bash\" ts=0");
}
#[test]
fn multi_part_message_iteration() {
let exec_data = [42i32.to_ne_bytes(), 100i32.to_ne_bytes()].concat();
let msg1 = make_full_message(PROC_EVENT_EXEC, &exec_data);
let exec_data2 = [43i32.to_ne_bytes(), 101i32.to_ne_bytes()].concat();
let msg2 = make_full_message(PROC_EVENT_EXEC, &exec_data2);
let mut combined = Vec::new();
combined.extend_from_slice(&msg1);
combined.extend_from_slice(&msg2);
let iter = NetlinkMessageIter::new(&combined, combined.len());
let events: Vec<_> = iter.filter_map(|r| r.ok().flatten()).collect();
assert_eq!(events.len(), 2);
assert_eq!(
events[0],
ProcEvent::Exec {
pid: 42,
tgid: 100,
timestamp_ns: 0
}
);
assert_eq!(
events[1],
ProcEvent::Exec {
pid: 43,
tgid: 101,
timestamp_ns: 0
}
);
}
#[test]
fn test_nlmsg_hdrlen() {
assert_eq!(nlmsg_hdrlen(), 16);
}
#[test]
fn test_nlmsg_align() {
assert_eq!(nlmsg_align(0), 0);
assert_eq!(nlmsg_align(1), 4);
assert_eq!(nlmsg_align(4), 4);
assert_eq!(nlmsg_align(5), 8);
assert_eq!(nlmsg_align(16), 16);
}
#[test]
fn test_nlmsg_length() {
assert_eq!(nlmsg_length(0), 16);
assert_eq!(nlmsg_length(20), 36);
}
#[test]
fn recv_buffer_too_small() {
let err = Error::BufferTooSmall {
needed: SIZE_NLMSGHDR,
};
assert_eq!(format!("{err}"), "buffer too small, need at least 16 bytes");
}
#[test]
fn parse_exec_truncated() {
let data = [42i32.to_ne_bytes()].concat(); let buf = make_full_message(PROC_EVENT_EXEC, &data);
let result = parse_netlink_message(&buf, buf.len());
assert!(matches!(result, Err(Error::Truncated)));
}
#[test]
fn parse_fork_truncated() {
let data = [
10i32.to_ne_bytes(), 10i32.to_ne_bytes(), 20i32.to_ne_bytes(), ]
.concat();
let buf = make_full_message(PROC_EVENT_FORK, &data);
let result = parse_netlink_message(&buf, buf.len());
assert!(matches!(result, Err(Error::Truncated)));
}
#[test]
fn parse_exit_truncated() {
let data = [
1i32.to_ne_bytes(), 1i32.to_ne_bytes(), 0u32.to_ne_bytes(), 17u32.to_ne_bytes(), 0i32.to_ne_bytes(), ]
.concat();
let buf = make_full_message(PROC_EVENT_EXIT, &data);
let result = parse_netlink_message(&buf, buf.len());
assert!(matches!(result, Err(Error::Truncated)));
}
#[test]
fn parse_uid_truncated() {
let data = [
5i32.to_ne_bytes(), 5i32.to_ne_bytes(), 1000u32.to_ne_bytes(), ]
.concat();
let buf = make_full_message(PROC_EVENT_UID, &data);
let result = parse_netlink_message(&buf, buf.len());
assert!(matches!(result, Err(Error::Truncated)));
}
#[test]
fn parse_gid_truncated() {
let data = [
5i32.to_ne_bytes(), 5i32.to_ne_bytes(), 100u32.to_ne_bytes(), ]
.concat();
let buf = make_full_message(PROC_EVENT_GID, &data);
let result = parse_netlink_message(&buf, buf.len());
assert!(matches!(result, Err(Error::Truncated)));
}
#[test]
fn parse_sid_truncated() {
let data = [7i32.to_ne_bytes()].concat(); let buf = make_full_message(PROC_EVENT_SID, &data);
let result = parse_netlink_message(&buf, buf.len());
assert!(matches!(result, Err(Error::Truncated)));
}
#[test]
fn parse_ptrace_truncated() {
let data = [
1i32.to_ne_bytes(), 1i32.to_ne_bytes(), 999i32.to_ne_bytes(), ]
.concat();
let buf = make_full_message(PROC_EVENT_PTRACE, &data);
let result = parse_netlink_message(&buf, buf.len());
assert!(matches!(result, Err(Error::Truncated)));
}
#[test]
fn parse_comm_truncated_missing_comm() {
let data = [42i32.to_ne_bytes(), 42i32.to_ne_bytes()].concat(); let buf = make_full_message(PROC_EVENT_COMM, &data);
let result = parse_netlink_message(&buf, buf.len());
assert!(matches!(result, Err(Error::Truncated)));
}
#[test]
fn parse_coredump_truncated() {
let data = [1i32.to_ne_bytes()].concat(); let buf = make_full_message(PROC_EVENT_COREDUMP, &data);
let result = parse_netlink_message(&buf, buf.len());
assert!(matches!(result, Err(Error::Truncated)));
}
#[test]
fn parse_nlmsg_len_too_small() {
let mut buf = make_netlink_message(NLMSG_MIN_TYPE, &[0u8; 20]);
buf[0..4].copy_from_slice(&10u32.to_ne_bytes()); let result = parse_netlink_message(&buf, buf.len());
assert!(matches!(result, Err(Error::Truncated)));
}
#[test]
fn parse_nlmsg_len_exceeds_buffer() {
let mut buf = make_netlink_message(NLMSG_MIN_TYPE, &[0u8; 20]);
buf[0..4].copy_from_slice(&1000u32.to_ne_bytes()); let result = parse_netlink_message(&buf, buf.len());
assert!(matches!(result, Err(Error::Truncated)));
}
#[test]
fn parse_cn_msg_wrong_idx() {
let proc_payload = make_proc_event_payload(
PROC_EVENT_EXEC,
&[42i32.to_ne_bytes(), 100i32.to_ne_bytes()].concat(),
);
let mut cn_payload = Vec::with_capacity(SIZE_CN_MSG + proc_payload.len());
cn_payload.extend_from_slice(&999u32.to_ne_bytes()); cn_payload.extend_from_slice(&CN_VAL_PROC.to_ne_bytes());
cn_payload.extend_from_slice(&0u32.to_ne_bytes());
cn_payload.extend_from_slice(&0u32.to_ne_bytes());
cn_payload.extend_from_slice(&(proc_payload.len() as u16).to_ne_bytes());
cn_payload.extend_from_slice(&0u16.to_ne_bytes());
cn_payload.extend_from_slice(&proc_payload);
let buf = make_netlink_message(NLMSG_MIN_TYPE, &cn_payload);
let result = parse_netlink_message(&buf, buf.len());
assert!(matches!(result, Err(Error::UnexpectedConnector)));
}
#[test]
fn parse_cn_msg_truncated_header() {
let buf = make_netlink_message(NLMSG_MIN_TYPE, &[0u8; 10]);
let result = parse_netlink_message(&buf, buf.len());
assert!(matches!(result, Err(Error::Truncated)));
}
#[test]
fn parse_nlmsg_error_ack() {
let mut payload = vec![0u8; 20];
payload[0..4].copy_from_slice(&0i32.to_ne_bytes()); let buf = make_netlink_message(NLMSG_ERROR, &payload);
let result = parse_netlink_message(&buf, buf.len()).unwrap();
assert!(result.is_none());
}
#[test]
fn parse_exec_negative_pid() {
let data = [(-1i32).to_ne_bytes(), (-1i32).to_ne_bytes()].concat();
let buf = make_full_message(PROC_EVENT_EXEC, &data);
let event = parse_netlink_message(&buf, buf.len()).unwrap().unwrap();
assert_eq!(
event,
ProcEvent::Exec {
pid: u32::MAX,
tgid: u32::MAX,
timestamp_ns: 0,
}
);
}
#[test]
fn parse_comm_no_nul() {
let data = [
42i32.to_ne_bytes(), 42i32.to_ne_bytes(), ]
.concat();
let mut comm_event = data;
let comm: [u8; 16] = [
b'a', b'b', b'c', b'd', b'e', b'f', b'g', b'h', b'i', b'j', b'k', b'l', b'm', b'n', b'o',
b'p',
];
comm_event.extend_from_slice(&comm);
let buf = make_full_message(PROC_EVENT_COMM, &comm_event);
let event = parse_netlink_message(&buf, buf.len()).unwrap().unwrap();
assert_eq!(
event,
ProcEvent::Comm {
pid: 42,
tgid: 42,
comm,
timestamp_ns: 0,
}
);
let s = format!("{event}");
assert_eq!(s, "COMM pid=42 tgid=42 name=\"abcdefghijklmnop\" ts=0");
}
#[test]
fn parse_comm_invalid_utf8() {
let data = [
1i32.to_ne_bytes(), 1i32.to_ne_bytes(), ]
.concat();
let mut comm_event = data;
let mut comm = [0u8; 16];
comm[0] = 0xFF; comm[1] = 0xFE;
comm[2] = 0;
comm_event.extend_from_slice(&comm);
let buf = make_full_message(PROC_EVENT_COMM, &comm_event);
let event = format!(
"{}",
parse_netlink_message(&buf, buf.len()).unwrap().unwrap()
);
assert!(event.contains("<invalid>"));
}
#[test]
fn parse_unknown_large_data_skipped() {
let data = vec![0xABu8; 1024];
let buf = make_full_message(0xFFFFFFFF, &data);
let event = parse_netlink_message(&buf, buf.len()).unwrap().unwrap();
match event {
ProcEvent::Unknown { what, raw_data } => {
assert_eq!(what, 0xFFFFFFFF);
assert_eq!(raw_data.len(), 1024);
assert_eq!(raw_data[0], 0xAB);
assert_eq!(raw_data[1023], 0xAB);
}
_ => panic!("expected Unknown"),
}
}
#[test]
fn parse_zero_length_proc_event_data() {
let proc_payload = make_proc_event_payload(PROC_EVENT_EXEC, &[]);
let cn_payload = make_cn_msg(&proc_payload);
let buf = make_netlink_message(NLMSG_MIN_TYPE, &cn_payload);
let result = parse_netlink_message(&buf, buf.len());
assert!(matches!(result, Err(Error::Truncated)));
}
#[test]
fn display_fork() {
let event = ProcEvent::Fork {
parent_pid: 100,
parent_tgid: 100,
child_pid: 200,
child_tgid: 200,
timestamp_ns: 0,
};
assert_eq!(
format!("{event}"),
"FORK parent=(100,100) child=(200,200) ts=0"
);
}
#[test]
fn display_exit() {
let event = ProcEvent::Exit {
pid: 42,
tgid: 42,
exit_code: 0,
exit_signal: 17,
timestamp_ns: 0,
};
assert_eq!(
format!("{event}"),
"EXIT pid=42 tgid=42 code=0 signal=17 ts=0"
);
}
#[test]
fn display_uid() {
let event = ProcEvent::Uid {
pid: 1,
tgid: 1,
ruid: 1000,
euid: 0,
timestamp_ns: 0,
};
assert_eq!(format!("{event}"), "UID pid=1 tgid=1 ruid=1000 euid=0 ts=0");
}
#[test]
fn display_gid() {
let event = ProcEvent::Gid {
pid: 2,
tgid: 2,
rgid: 100,
egid: 200,
timestamp_ns: 0,
};
assert_eq!(
format!("{event}"),
"GID pid=2 tgid=2 rgid=100 egid=200 ts=0"
);
}
#[test]
fn display_sid() {
let event = ProcEvent::Sid {
pid: 3,
tgid: 3,
timestamp_ns: 0,
};
assert_eq!(format!("{event}"), "SID pid=3 tgid=3 ts=0");
}
#[test]
fn display_ptrace() {
let event = ProcEvent::Ptrace {
pid: 10,
tgid: 10,
tracer_pid: 99,
tracer_tgid: 99,
timestamp_ns: 0,
};
assert_eq!(
format!("{event}"),
"PTRACE pid=10 tgid=10 tracer=(99,99) ts=0"
);
}
#[test]
fn display_coredump() {
let event = ProcEvent::Coredump {
pid: 7,
tgid: 7,
timestamp_ns: 0,
};
assert_eq!(format!("{event}"), "COREDUMP pid=7 tgid=7 ts=0");
}
#[test]
fn iter_empty_buffer() {
let iter = NetlinkMessageIter::new(&[], 0);
assert_eq!(iter.count(), 0);
}
#[test]
fn iter_single_done_message() {
let buf = make_netlink_message(NLMSG_DONE, &[]);
let iter = NetlinkMessageIter::new(&buf, buf.len());
let results: Vec<_> = iter.collect();
assert_eq!(results.len(), 0);
}
#[test]
fn iter_done_terminates_early() {
let exec_data = [42i32.to_ne_bytes(), 100i32.to_ne_bytes()].concat();
let msg_exec = make_full_message(PROC_EVENT_EXEC, &exec_data);
let msg_done = make_netlink_message(NLMSG_DONE, &[]);
let mut combined = Vec::new();
combined.extend_from_slice(&msg_exec);
combined.extend_from_slice(&msg_done);
let iter = NetlinkMessageIter::new(&combined, combined.len());
let events: Vec<_> = iter.filter_map(|r| r.ok().flatten()).collect();
assert_eq!(events.len(), 1);
assert_eq!(
events[0],
ProcEvent::Exec {
pid: 42,
tgid: 100,
timestamp_ns: 0
}
);
}
#[test]
fn iter_interleaved_control_messages() {
let exec_data = [42i32.to_ne_bytes(), 100i32.to_ne_bytes()].concat();
let fork_data = [
10i32.to_ne_bytes(),
10i32.to_ne_bytes(),
20i32.to_ne_bytes(),
20i32.to_ne_bytes(),
]
.concat();
let msg_noop = make_netlink_message(NLMSG_NOOP, &[]);
let msg_exec = make_full_message(PROC_EVENT_EXEC, &exec_data);
let msg_fork = make_full_message(PROC_EVENT_FORK, &fork_data);
let msg_done = make_netlink_message(NLMSG_DONE, &[]);
let mut combined = Vec::new();
combined.extend_from_slice(&msg_noop);
combined.extend_from_slice(&msg_exec);
combined.extend_from_slice(&msg_noop);
combined.extend_from_slice(&msg_fork);
combined.extend_from_slice(&msg_done);
let iter = NetlinkMessageIter::new(&combined, combined.len());
let events: Vec<_> = iter.filter_map(|r| r.ok().flatten()).collect();
assert_eq!(events.len(), 2);
assert_eq!(
events[0],
ProcEvent::Exec {
pid: 42,
tgid: 100,
timestamp_ns: 0
}
);
assert_eq!(
events[1],
ProcEvent::Fork {
parent_pid: 10,
parent_tgid: 10,
child_pid: 20,
child_tgid: 20,
timestamp_ns: 0,
}
);
}
#[test]
fn iter_malformed_zero_length() {
let mut buf = vec![0u8; 16];
buf[0..4].copy_from_slice(&0u32.to_ne_bytes()); let mut iter = NetlinkMessageIter::new(&buf, buf.len());
let result = iter.next().unwrap();
assert!(matches!(result, Err(Error::Truncated)));
}
#[test]
fn iter_remaining_too_small_for_header() {
let buf = vec![0u8; 4];
let mut iter = NetlinkMessageIter::new(&buf, 4);
let result = iter.next().unwrap();
assert!(matches!(result, Err(Error::Truncated)));
}
#[test]
fn iter_no_valid_msgs_returns_none_on_second_call() {
let buf = make_netlink_message(NLMSG_DONE, &[]);
let mut iter = NetlinkMessageIter::new(&buf, buf.len());
assert!(iter.next().is_none());
assert!(iter.next().is_none());
}
#[test]
fn test_nlmsg_align_max() {
assert_eq!(nlmsg_align(65535), 65536); assert_eq!(nlmsg_align(65536), 65536);
assert_eq!(nlmsg_align(65537), 65540);
}
#[test]
fn test_nlmsg_align_neg_like() {
assert_eq!(nlmsg_align(2), 4);
assert_eq!(nlmsg_align(3), 4);
assert_eq!(nlmsg_align(6), 8);
assert_eq!(nlmsg_align(7), 8);
assert_eq!(nlmsg_align(8), 8);
assert_eq!(nlmsg_align(9), 12);
}
#[test]
fn parse_cn_msg_truncated_no_data() {
let result = parse_cn_msg(&[0u8; 15]);
assert!(matches!(result, Err(Error::Truncated)));
}
#[test]
fn parse_cn_msg_data_len_mismatch() {
let mut buf = vec![0u8; SIZE_CN_MSG + 4];
buf[0..4].copy_from_slice(&CN_IDX_PROC.to_ne_bytes());
buf[4..8].copy_from_slice(&CN_VAL_PROC.to_ne_bytes());
buf[16..18].copy_from_slice(&100u16.to_ne_bytes()); let result = parse_cn_msg(&buf);
assert!(matches!(result, Err(Error::Truncated)));
}
#[test]
fn iter_many_messages() {
let exec_data = [42i32.to_ne_bytes(), 100i32.to_ne_bytes()].concat();
let mut combined = Vec::new();
for _ in 0..100 {
let msg = make_full_message(PROC_EVENT_EXEC, &exec_data);
combined.extend_from_slice(&msg);
}
let iter = NetlinkMessageIter::new(&combined, combined.len());
let count = iter.filter_map(|r| r.ok().flatten()).count();
assert_eq!(count, 100);
}
#[test]
fn error_display_all_variants() {
assert_eq!(
format!("{}", Error::Os(std::io::Error::from_raw_os_error(1))),
"system call error: Operation not permitted (os error 1)"
);
assert_eq!(format!("{}", Error::Truncated), "truncated message");
assert_eq!(
format!("{}", Error::BufferTooSmall { needed: 64 }),
"buffer too small, need at least 64 bytes"
);
assert_eq!(format!("{}", Error::Interrupted), "interrupted by signal");
assert_eq!(format!("{}", Error::ConnectionClosed), "connection closed");
assert_eq!(
format!("{}", Error::Overrun),
"message overrun, events may have been dropped"
);
}
#[test]
fn error_source() {
use std::error::Error as _;
assert!(
Error::Os(std::io::Error::from_raw_os_error(1))
.source()
.is_some()
);
assert!(Error::Truncated.source().is_none());
assert!(Error::BufferTooSmall { needed: 16 }.source().is_none());
assert!(Error::Interrupted.source().is_none());
assert!(Error::ConnectionClosed.source().is_none());
assert!(Error::Overrun.source().is_none());
}
#[test]
fn parse_nlmsg_len_inconsistent() {
let mut buf = make_netlink_message(NLMSG_MIN_TYPE, &[0u8; 100]);
buf[0..4].copy_from_slice(&20u32.to_ne_bytes()); let result = parse_netlink_message(&buf, buf.len());
assert!(matches!(result, Err(Error::Truncated)));
}
#[test]
fn proc_event_clone_and_eq() {
let e1 = ProcEvent::Exec {
pid: 42,
tgid: 100,
timestamp_ns: 0,
};
let e2 = e1.clone();
assert_eq!(e1, e2);
let e3 = ProcEvent::Exec {
pid: 43,
tgid: 100,
timestamp_ns: 0,
};
assert_ne!(e1, e3);
}
#[test]
fn roundtrip_all_event_types() {
use std::error::Error as _;
let _ = Error::Truncated.source(); }
#[test]
fn parse_with_nlm_f_request_flag() {
let exec_data = [42i32.to_ne_bytes(), 100i32.to_ne_bytes()].concat();
let proc_payload = make_proc_event_payload(PROC_EVENT_EXEC, &exec_data);
let cn_payload = make_cn_msg(&proc_payload);
let mut buf = make_netlink_message(NLMSG_MIN_TYPE, &cn_payload);
buf[6..8].copy_from_slice(&NLM_F_REQUEST.to_ne_bytes());
let event = parse_netlink_message(&buf, buf.len()).unwrap().unwrap();
assert_eq!(
event,
ProcEvent::Exec {
pid: 42,
tgid: 100,
timestamp_ns: 0
}
);
}
#[test]
fn iter_alignment_correct() {
let msg1 = make_netlink_message(NLMSG_NOOP, &[]); let msg2 = make_full_message(
PROC_EVENT_EXEC,
&[42i32.to_ne_bytes(), 100i32.to_ne_bytes()].concat(),
);
let msg3 = make_netlink_message(NLMSG_DONE, &[]);
let mut combined = Vec::new();
combined.extend_from_slice(&msg1);
combined.extend_from_slice(&msg2);
combined.extend_from_slice(&msg3);
let mut pos = 0;
let len1 = u32::from_ne_bytes(msg1[0..4].try_into().unwrap()) as usize;
assert_eq!(len1, 16);
pos += nlmsg_align(len1);
let len2 = u32::from_ne_bytes(msg2[0..4].try_into().unwrap()) as usize;
assert!(len2 > 16);
assert_eq!(pos, 16); pos += nlmsg_align(len2);
let len3 = u32::from_ne_bytes(msg3[0..4].try_into().unwrap()) as usize;
assert_eq!(len3, 16);
assert_eq!(pos, 16 + nlmsg_align(len2));
let iter = NetlinkMessageIter::new(&combined, combined.len());
let events: Vec<_> = iter.filter_map(|r| r.ok().flatten()).collect();
assert_eq!(events.len(), 1);
assert_eq!(
events[0],
ProcEvent::Exec {
pid: 42,
tgid: 100,
timestamp_ns: 0
}
);
}
#[test]
fn parse_exec_timestamp_nonzero() {
let ts: u64 = 123456789012345;
let data = [42i32.to_ne_bytes(), 100i32.to_ne_bytes()].concat();
let proc_payload = make_proc_event_payload_with_ts(PROC_EVENT_EXEC, ts, &data);
let cn_payload = make_cn_msg(&proc_payload);
let buf = make_netlink_message(NLMSG_MIN_TYPE, &cn_payload);
let event = parse_netlink_message(&buf, buf.len()).unwrap().unwrap();
match event {
ProcEvent::Exec {
pid,
tgid,
timestamp_ns,
} => {
assert_eq!(pid, 42);
assert_eq!(tgid, 100);
assert_eq!(timestamp_ns, ts, "timestamp_ns should be preserved");
}
_ => panic!("expected Exec event"),
}
assert!(
event.to_string().contains(&format!("ts={ts}")),
"Display should include ts=... but got: {}",
event
);
}