1use crate::consts::*;
19use crate::error::{Error, Result};
20
21#[inline]
27fn read_u32(buf: &[u8], off: usize) -> u32 {
28 let arr: [u8; 4] = buf[off..off + 4].try_into().unwrap();
29 u32::from_ne_bytes(arr)
30}
31
32#[inline]
34fn read_u16(buf: &[u8], off: usize) -> u16 {
35 let arr: [u8; 2] = buf[off..off + 2].try_into().unwrap();
36 u16::from_ne_bytes(arr)
37}
38
39#[inline]
41fn read_i32(buf: &[u8], off: usize) -> i32 {
42 let arr: [u8; 4] = buf[off..off + 4].try_into().unwrap();
43 i32::from_ne_bytes(arr)
44}
45
46#[inline]
48fn read_u64(buf: &[u8], off: usize) -> u64 {
49 let arr: [u8; 8] = buf[off..off + 8].try_into().unwrap();
50 u64::from_ne_bytes(arr)
51}
52
53#[derive(Debug, Clone, PartialEq, Eq)]
121pub enum ProcEvent {
122 Exec {
124 pid: u32,
125 tgid: u32,
126 timestamp_ns: u64,
128 },
129 Fork {
131 parent_pid: u32,
132 parent_tgid: u32,
133 child_pid: u32,
134 child_tgid: u32,
135 timestamp_ns: u64,
137 },
138 Exit {
140 pid: u32,
141 tgid: u32,
142 exit_code: u32,
143 exit_signal: u32,
144 timestamp_ns: u64,
146 },
147 Uid {
149 pid: u32,
150 tgid: u32,
151 ruid: u32,
152 euid: u32,
153 timestamp_ns: u64,
155 },
156 Gid {
158 pid: u32,
159 tgid: u32,
160 rgid: u32,
161 egid: u32,
162 timestamp_ns: u64,
164 },
165 Sid {
167 pid: u32,
168 tgid: u32,
169 timestamp_ns: u64,
171 },
172 Ptrace {
174 pid: u32,
175 tgid: u32,
176 tracer_pid: u32,
177 tracer_tgid: u32,
178 timestamp_ns: u64,
180 },
181 Comm {
183 pid: u32,
184 tgid: u32,
185 comm: [u8; 16],
187 timestamp_ns: u64,
189 },
190 Coredump {
192 pid: u32,
193 tgid: u32,
194 timestamp_ns: u64,
196 },
197 Unknown {
199 what: u32,
201 raw_data: Vec<u8>,
203 },
204}
205
206pub fn parse_netlink_message(payload: &[u8], len: usize) -> Result<Option<ProcEvent>> {
225 let payload = &payload[..len];
226
227 if payload.len() < SIZE_NLMSGHDR {
228 return Err(Error::Truncated);
229 }
230
231 let nlmsg_type = read_u16(payload, 4);
232 let nlmsg_len = read_u32(payload, 0) as usize;
233
234 if nlmsg_len > payload.len() {
235 return Err(Error::Truncated);
236 }
237
238 match nlmsg_type {
239 NLMSG_NOOP => Ok(None),
240 NLMSG_DONE if nlmsg_len == SIZE_NLMSGHDR => Ok(None),
241 NLMSG_ERROR => {
242 let errno = read_i32(payload, SIZE_NLMSGHDR);
244 if errno == 0 {
245 return Ok(None);
247 }
248 let pos_errno = errno.checked_neg().unwrap_or(errno);
251 Err(Error::Os(std::io::Error::from_raw_os_error(pos_errno)))
252 }
253 NLMSG_OVERRUN => Err(Error::Overrun),
254 _ => {
255 let cn_offset = nlmsg_hdrlen();
258 if nlmsg_len < cn_offset {
259 return Err(Error::Truncated);
260 }
261 let cn_payload = &payload[cn_offset..nlmsg_len];
262 parse_cn_msg(cn_payload).map(Some)
263 }
264 }
265}
266
267fn parse_cn_msg(buf: &[u8]) -> Result<ProcEvent> {
269 if buf.len() < SIZE_CN_MSG {
270 return Err(Error::Truncated);
271 }
272
273 let idx = read_u32(buf, 0);
274 let val = read_u32(buf, 4);
275
276 if idx != CN_IDX_PROC || val != CN_VAL_PROC {
278 return Err(Error::Truncated);
279 }
280
281 let data_len = read_u16(buf, 16) as usize;
282
283 let proc_off = SIZE_CN_MSG;
285 let proc_data = if buf.len() >= proc_off + data_len {
286 &buf[proc_off..proc_off + data_len]
287 } else {
288 return Err(Error::Truncated);
289 };
290
291 parse_proc_event(proc_data)
292}
293
294fn parse_proc_event(buf: &[u8]) -> Result<ProcEvent> {
296 if buf.len() < PROC_EVENT_HEADER_SIZE {
297 return Err(Error::Truncated);
298 }
299
300 let what = read_u32(buf, 0);
301 let _cpu = read_u32(buf, 4);
302 let timestamp_ns = read_u64(buf, 8);
303
304 let data = &buf[PROC_EVENT_HEADER_SIZE..];
305
306 match what {
307 PROC_EVENT_EXEC => {
308 if data.len() < SIZE_EXEC_EVENT {
309 return Err(Error::Truncated);
310 }
311 Ok(ProcEvent::Exec {
312 pid: read_i32(data, EXEC_PID) as u32,
313 tgid: read_i32(data, EXEC_TGID) as u32,
314 timestamp_ns,
315 })
316 }
317
318 PROC_EVENT_FORK => {
319 if data.len() < SIZE_FORK_EVENT {
320 return Err(Error::Truncated);
321 }
322 Ok(ProcEvent::Fork {
323 parent_pid: read_i32(data, FORK_PARENT_PID) as u32,
324 parent_tgid: read_i32(data, FORK_PARENT_TGID) as u32,
325 child_pid: read_i32(data, FORK_CHILD_PID) as u32,
326 child_tgid: read_i32(data, FORK_CHILD_TGID) as u32,
327 timestamp_ns,
328 })
329 }
330
331 PROC_EVENT_EXIT => {
332 if data.len() < SIZE_EXIT_EVENT {
333 return Err(Error::Truncated);
334 }
335 Ok(ProcEvent::Exit {
336 pid: read_i32(data, EXIT_PID) as u32,
337 tgid: read_i32(data, EXIT_TGID) as u32,
338 exit_code: read_u32(data, EXIT_CODE),
339 exit_signal: read_u32(data, EXIT_SIGNAL),
340 timestamp_ns,
341 })
342 }
343
344 PROC_EVENT_UID => {
345 if data.len() < SIZE_ID_EVENT {
346 return Err(Error::Truncated);
347 }
348 Ok(ProcEvent::Uid {
349 pid: read_i32(data, ID_PID) as u32,
350 tgid: read_i32(data, ID_TGID) as u32,
351 ruid: read_u32(data, ID_RUID_RGID),
352 euid: read_u32(data, ID_EUID_EGID),
353 timestamp_ns,
354 })
355 }
356
357 PROC_EVENT_GID => {
358 if data.len() < SIZE_ID_EVENT {
359 return Err(Error::Truncated);
360 }
361 Ok(ProcEvent::Gid {
362 pid: read_i32(data, ID_PID) as u32,
363 tgid: read_i32(data, ID_TGID) as u32,
364 rgid: read_u32(data, ID_RUID_RGID),
365 egid: read_u32(data, ID_EUID_EGID),
366 timestamp_ns,
367 })
368 }
369
370 PROC_EVENT_SID => {
371 if data.len() < SIZE_SID_EVENT {
372 return Err(Error::Truncated);
373 }
374 Ok(ProcEvent::Sid {
375 pid: read_i32(data, SID_PID) as u32,
376 tgid: read_i32(data, SID_TGID) as u32,
377 timestamp_ns,
378 })
379 }
380
381 PROC_EVENT_PTRACE => {
382 if data.len() < SIZE_PTRACE_EVENT {
383 return Err(Error::Truncated);
384 }
385 Ok(ProcEvent::Ptrace {
386 pid: read_i32(data, PTRACE_PID) as u32,
387 tgid: read_i32(data, PTRACE_TGID) as u32,
388 tracer_pid: read_i32(data, PTRACE_TRACER_PID) as u32,
389 tracer_tgid: read_i32(data, PTRACE_TRACER_TGID) as u32,
390 timestamp_ns,
391 })
392 }
393
394 PROC_EVENT_COMM => {
395 if data.len() < SIZE_COMM_EVENT {
396 return Err(Error::Truncated);
397 }
398 let mut comm = [0u8; 16];
399 comm.copy_from_slice(&data[COMM_DATA..COMM_DATA + 16]);
400 Ok(ProcEvent::Comm {
401 pid: read_i32(data, COMM_PID) as u32,
402 tgid: read_i32(data, COMM_TGID) as u32,
403 comm,
404 timestamp_ns,
405 })
406 }
407
408 PROC_EVENT_COREDUMP => {
409 if data.len() < SIZE_COREDUMP_EVENT {
410 return Err(Error::Truncated);
411 }
412 Ok(ProcEvent::Coredump {
413 pid: read_i32(data, COREDUMP_PID) as u32,
414 tgid: read_i32(data, COREDUMP_TGID) as u32,
415 timestamp_ns,
416 })
417 }
418
419 _ => {
420 Ok(ProcEvent::Unknown {
422 what,
423 raw_data: data.to_vec(),
424 })
425 }
426 }
427}
428
429impl std::fmt::Display for ProcEvent {
434 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
435 match self {
436 ProcEvent::Exec { pid, tgid, timestamp_ns } => write!(f, "EXEC pid={pid} tgid={tgid} ts={timestamp_ns}"),
437 ProcEvent::Fork {
438 parent_pid,
439 parent_tgid,
440 child_pid,
441 child_tgid,
442 timestamp_ns,
443 } => write!(
444 f,
445 "FORK parent=({parent_pid},{parent_tgid}) child=({child_pid},{child_tgid}) ts={timestamp_ns}"
446 ),
447 ProcEvent::Exit {
448 pid,
449 tgid,
450 exit_code,
451 exit_signal,
452 timestamp_ns,
453 } => write!(
454 f,
455 "EXIT pid={pid} tgid={tgid} code={exit_code} signal={exit_signal} ts={timestamp_ns}"
456 ),
457 ProcEvent::Uid {
458 pid, tgid, ruid, euid, timestamp_ns,
459 } => write!(f, "UID pid={pid} tgid={tgid} ruid={ruid} euid={euid} ts={timestamp_ns}"),
460 ProcEvent::Gid {
461 pid, tgid, rgid, egid, timestamp_ns,
462 } => write!(f, "GID pid={pid} tgid={tgid} rgid={rgid} egid={egid} ts={timestamp_ns}"),
463 ProcEvent::Sid { pid, tgid, timestamp_ns } => write!(f, "SID pid={pid} tgid={tgid} ts={timestamp_ns}"),
464 ProcEvent::Ptrace {
465 pid,
466 tgid,
467 tracer_pid,
468 tracer_tgid,
469 timestamp_ns,
470 } => write!(
471 f,
472 "PTRACE pid={pid} tgid={tgid} tracer=({tracer_pid},{tracer_tgid}) ts={timestamp_ns}"
473 ),
474 ProcEvent::Comm {
475 pid,
476 tgid,
477 comm,
478 timestamp_ns,
479 } => {
480 let end = comm.iter().position(|&b| b == 0).unwrap_or(16);
482 let name = std::str::from_utf8(&comm[..end]).unwrap_or("<invalid>");
483 write!(f, "COMM pid={pid} tgid={tgid} name=\"{name}\" ts={timestamp_ns}")
484 }
485 ProcEvent::Coredump { pid, tgid, timestamp_ns } => {
486 write!(f, "COREDUMP pid={pid} tgid={tgid} ts={timestamp_ns}")
487 }
488 ProcEvent::Unknown { what, raw_data } => {
489 write!(f, "UNKNOWN what=0x{what:08x} len={}", raw_data.len())
490 }
491 }
492 }
493}
494
495pub struct NetlinkMessageIter<'a> {
504 buf: &'a [u8],
505 pos: usize,
506 len: usize,
507}
508
509impl<'a> NetlinkMessageIter<'a> {
510 pub fn new(buf: &'a [u8], len: usize) -> Self {
512 NetlinkMessageIter {
513 buf,
514 pos: 0,
515 len,
516 }
517 }
518}
519
520impl<'a> Iterator for NetlinkMessageIter<'a> {
521 type Item = Result<Option<ProcEvent>>;
522
523 fn next(&mut self) -> Option<Self::Item> {
524 if self.pos >= self.len {
525 return None;
526 }
527
528 let remaining = self.len - self.pos;
529 if remaining < SIZE_NLMSGHDR {
530 return Some(Err(Error::Truncated));
531 }
532
533 let nlmsg_len = read_u32(&self.buf[self.pos..], 0) as usize;
534 if nlmsg_len < SIZE_NLMSGHDR || nlmsg_len > remaining {
535 return Some(Err(Error::Truncated));
536 }
537
538 let msg_slice = &self.buf[self.pos..self.pos + nlmsg_len];
539 let nlmsg_type = read_u16(msg_slice, 4);
540
541 if nlmsg_type == NLMSG_DONE && nlmsg_len == SIZE_NLMSGHDR {
547 self.pos = self.len; return None; }
550
551 let result = parse_netlink_message(msg_slice, nlmsg_len);
553
554 self.pos += nlmsg_align(nlmsg_len);
556
557 Some(result)
558 }
559}
560
561use crate::socket::ProcConnector;
566
567impl ProcConnector {
568 pub fn recv(&self, buf: &mut [u8]) -> Result<ProcEvent> {
603 self.recv_impl(buf)
604 }
605
606 pub fn recv_timeout(&self, buf: &mut [u8], timeout: std::time::Duration) -> Result<Option<ProcEvent>> {
618 if buf.len() < SIZE_NLMSGHDR {
619 return Err(Error::BufferTooSmall { needed: SIZE_NLMSGHDR });
620 }
621
622 loop {
623 let n = match self.recv_raw_timeout(buf, timeout) {
624 Ok(Some(n)) => n,
625 Ok(None) => return Ok(None),
626 Err(Error::WouldBlock) => {
627 return Ok(None);
630 }
631 Err(e) => return Err(e),
632 };
633
634 let iter = NetlinkMessageIter::new(buf, n);
636 for msg in iter {
637 match msg? {
638 Some(event) => return Ok(Some(event)),
639 None => continue, }
641 }
642 }
644 }
645
646 fn recv_impl(&self, buf: &mut [u8]) -> Result<ProcEvent> {
651 if buf.len() < SIZE_NLMSGHDR {
652 return Err(Error::BufferTooSmall {
653 needed: SIZE_NLMSGHDR,
654 });
655 }
656
657 loop {
658 let n = match self.recv_raw(buf) {
659 Ok(n) => n,
660 Err(Error::WouldBlock) => {
661 return Err(Error::Os(std::io::Error::new(
665 std::io::ErrorKind::WouldBlock,
666 "socket is non-blocking, use AsyncFd to wait for readiness",
667 )));
668 }
669 Err(e) => return Err(e),
670 };
671
672 let iter = NetlinkMessageIter::new(buf, n);
674 for msg in iter {
675 match msg? {
676 Some(event) => return Ok(event),
677 None => continue, }
679 }
680
681 }
684 }
685
686
687}
688
689#[cfg(test)]
690mod tests {
691 use super::*;
692
693 fn make_proc_event_payload(what: u32, event_data: &[u8]) -> Vec<u8> {
698 let mut buf = Vec::with_capacity(PROC_EVENT_HEADER_SIZE + event_data.len());
699 buf.extend_from_slice(&what.to_ne_bytes()); buf.extend_from_slice(&0u32.to_ne_bytes()); buf.extend_from_slice(&0u64.to_ne_bytes()); buf.extend_from_slice(event_data);
703 buf
704 }
705
706 fn make_cn_msg(data: &[u8]) -> Vec<u8> {
707 let mut buf = Vec::with_capacity(SIZE_CN_MSG + data.len());
708 buf.extend_from_slice(&CN_IDX_PROC.to_ne_bytes()); buf.extend_from_slice(&CN_VAL_PROC.to_ne_bytes()); buf.extend_from_slice(&0u32.to_ne_bytes()); buf.extend_from_slice(&0u32.to_ne_bytes()); buf.extend_from_slice(&(data.len() as u16).to_ne_bytes()); buf.extend_from_slice(&0u16.to_ne_bytes()); buf.extend_from_slice(data);
715 buf
716 }
717
718 fn make_netlink_message(nlmsg_type: u16, payload: &[u8]) -> Vec<u8> {
719 let hdr_len = nlmsg_hdrlen();
720 let total_len = nlmsg_length(payload.len());
721
722 let mut buf = vec![0u8; total_len];
723 buf[0..4].copy_from_slice(&(total_len as u32).to_ne_bytes()); buf[4..6].copy_from_slice(&nlmsg_type.to_ne_bytes()); buf[6..8].copy_from_slice(&0u16.to_ne_bytes()); buf[8..12].copy_from_slice(&0u32.to_ne_bytes()); buf[12..16].copy_from_slice(&0u32.to_ne_bytes()); buf[hdr_len..hdr_len + payload.len()].copy_from_slice(payload);
729
730 buf
731 }
732
733 fn make_proc_event_payload_with_ts(what: u32, timestamp_ns: u64, event_data: &[u8]) -> Vec<u8> {
734 let mut buf = Vec::with_capacity(PROC_EVENT_HEADER_SIZE + event_data.len());
735 buf.extend_from_slice(&what.to_ne_bytes());
736 buf.extend_from_slice(&0u32.to_ne_bytes()); buf.extend_from_slice(×tamp_ns.to_ne_bytes());
738 buf.extend_from_slice(event_data);
739 buf
740 }
741
742 fn make_full_message(what: u32, event_data: &[u8]) -> Vec<u8> {
743 let proc_payload = make_proc_event_payload(what, event_data);
744 let cn_payload = make_cn_msg(&proc_payload);
745 make_netlink_message(NLMSG_MIN_TYPE, &cn_payload)
746 }
747
748 #[test]
749 fn parse_exec() {
750 let data = [
751 42i32.to_ne_bytes(), 100i32.to_ne_bytes(), ]
754 .concat();
755 let buf = make_full_message(PROC_EVENT_EXEC, &data);
756 let event = parse_netlink_message(&buf, buf.len()).unwrap().unwrap();
757 assert_eq!(
758 event,
759 ProcEvent::Exec {
760 pid: 42,
761 tgid: 100,
762 timestamp_ns: 0,
763 }
764 );
765 }
766
767 #[test]
768 fn parse_fork() {
769 let data = [
770 10i32.to_ne_bytes(), 10i32.to_ne_bytes(), 20i32.to_ne_bytes(), 20i32.to_ne_bytes(), ]
775 .concat();
776 let buf = make_full_message(PROC_EVENT_FORK, &data);
777 let event = parse_netlink_message(&buf, buf.len()).unwrap().unwrap();
778 assert_eq!(
779 event,
780 ProcEvent::Fork {
781 parent_pid: 10,
782 parent_tgid: 10,
783 child_pid: 20,
784 child_tgid: 20,
785 timestamp_ns: 0,
786 }
787 );
788 }
789
790 #[test]
791 fn parse_exit() {
792 let data = [
793 1i32.to_ne_bytes(), 1i32.to_ne_bytes(), 0u32.to_ne_bytes(), 17u32.to_ne_bytes(), 0i32.to_ne_bytes(), 0i32.to_ne_bytes(), ]
800 .concat();
801 let buf = make_full_message(PROC_EVENT_EXIT, &data);
802 let event = parse_netlink_message(&buf, buf.len()).unwrap().unwrap();
803 assert_eq!(
804 event,
805 ProcEvent::Exit {
806 pid: 1,
807 tgid: 1,
808 exit_code: 0,
809 exit_signal: 17,
810 timestamp_ns: 0,
811 }
812 );
813 }
814
815 #[test]
816 fn parse_uid() {
817 let data = [
818 5i32.to_ne_bytes(), 5i32.to_ne_bytes(), 1000u32.to_ne_bytes(), 0u32.to_ne_bytes(), ]
823 .concat();
824 let buf = make_full_message(PROC_EVENT_UID, &data);
825 let event = parse_netlink_message(&buf, buf.len()).unwrap().unwrap();
826 assert_eq!(
827 event,
828 ProcEvent::Uid {
829 pid: 5,
830 tgid: 5,
831 ruid: 1000,
832 euid: 0,
833 timestamp_ns: 0,
834 }
835 );
836 }
837
838 #[test]
839 fn parse_gid() {
840 let data = [
841 5i32.to_ne_bytes(), 5i32.to_ne_bytes(), 100u32.to_ne_bytes(), 200u32.to_ne_bytes(), ]
846 .concat();
847 let buf = make_full_message(PROC_EVENT_GID, &data);
848 let event = parse_netlink_message(&buf, buf.len()).unwrap().unwrap();
849 assert_eq!(
850 event,
851 ProcEvent::Gid {
852 pid: 5,
853 tgid: 5,
854 rgid: 100,
855 egid: 200,
856 timestamp_ns: 0,
857 }
858 );
859 }
860
861 #[test]
862 fn parse_sid() {
863 let data = [
864 7i32.to_ne_bytes(), 7i32.to_ne_bytes(), ]
867 .concat();
868 let buf = make_full_message(PROC_EVENT_SID, &data);
869 let event = parse_netlink_message(&buf, buf.len()).unwrap().unwrap();
870 assert_eq!(
871 event,
872 ProcEvent::Sid { pid: 7, tgid: 7 , timestamp_ns: 0 }
873 );
874 }
875
876 #[test]
877 fn parse_ptrace() {
878 let data = [
879 1i32.to_ne_bytes(), 1i32.to_ne_bytes(), 999i32.to_ne_bytes(), 999i32.to_ne_bytes(), ]
884 .concat();
885 let buf = make_full_message(PROC_EVENT_PTRACE, &data);
886 let event = parse_netlink_message(&buf, buf.len()).unwrap().unwrap();
887 assert_eq!(
888 event,
889 ProcEvent::Ptrace {
890 pid: 1,
891 tgid: 1,
892 tracer_pid: 999,
893 tracer_tgid: 999,
894 timestamp_ns: 0,
895 }
896 );
897 }
898
899 #[test]
900 fn parse_comm() {
901 let data = [
902 42i32.to_ne_bytes(), 42i32.to_ne_bytes(), ]
905 .concat();
906 let mut comm_event = data;
907 let mut comm = [0u8; 16];
908 comm[..7].copy_from_slice(b"bash\0\0\0");
909 comm_event.extend_from_slice(&comm);
910
911 let buf = make_full_message(PROC_EVENT_COMM, &comm_event);
912 let event = parse_netlink_message(&buf, buf.len()).unwrap().unwrap();
913 assert_eq!(
914 event,
915 ProcEvent::Comm {
916 pid: 42,
917 tgid: 42,
918 comm,
919 timestamp_ns: 0,
920 }
921 );
922 }
923
924 #[test]
925 fn parse_coredump() {
926 let data = [
927 1i32.to_ne_bytes(), 1i32.to_ne_bytes(), 0i32.to_ne_bytes(), 0i32.to_ne_bytes(), ]
932 .concat();
933 let buf = make_full_message(PROC_EVENT_COREDUMP, &data);
934 let event = parse_netlink_message(&buf, buf.len()).unwrap().unwrap();
935 assert_eq!(
936 event,
937 ProcEvent::Coredump { pid: 1, tgid: 1 , timestamp_ns: 0 }
938 );
939 }
940
941 #[test]
942 fn parse_unknown_skipped() {
943 let data = [1u8, 2, 3, 4];
944 let buf = make_full_message(0xDEAD, &data);
945 let event = parse_netlink_message(&buf, buf.len()).unwrap().unwrap();
946 match event {
947 ProcEvent::Unknown { what, raw_data } => {
948 assert_eq!(what, 0xDEAD);
949 assert_eq!(raw_data, data);
950 }
951 _ => panic!("expected Unknown event"),
952 }
953 }
954
955 #[test]
956 fn parse_nlmsg_noop() {
957 let buf = make_netlink_message(NLMSG_NOOP, &[]);
958 let result = parse_netlink_message(&buf, buf.len()).unwrap();
959 assert!(result.is_none());
960 }
961
962 #[test]
963 fn parse_nlmsg_done() {
964 let buf = make_netlink_message(NLMSG_DONE, &[]);
965 let result = parse_netlink_message(&buf, buf.len()).unwrap();
966 assert!(result.is_none());
967 }
968
969 #[test]
970 fn parse_nlmsg_error() {
971 let errno = -libc::EPERM;
973 let mut payload = vec![0u8; SIZE_NLMSGHDR + 20]; payload[0..4].copy_from_slice(&errno.to_ne_bytes());
975
976 let buf = make_netlink_message(NLMSG_ERROR, &payload);
977 let result = parse_netlink_message(&buf, buf.len());
978 assert!(result.is_err());
979 match result {
980 Err(Error::Os(e)) => assert_eq!(e.raw_os_error(), Some(libc::EPERM)),
981 _ => panic!("expected Os error"),
982 }
983 }
984
985 #[test]
986 fn parse_nlmsg_overrun() {
987 let buf = make_netlink_message(NLMSG_OVERRUN, &[]);
988 let result = parse_netlink_message(&buf, buf.len());
989 match result {
990 Err(Error::Overrun) => {} _ => panic!("expected Overrun error"),
992 }
993 }
994
995 #[test]
996 fn truncated_message() {
997 let buf = vec![0u8; 4]; let result = parse_netlink_message(&buf, buf.len());
999 match result {
1000 Err(Error::Truncated) => {} _ => panic!("expected Truncated error"),
1002 }
1003 }
1004
1005 #[test]
1006 fn display_exec() {
1007 let event = ProcEvent::Exec {
1008 pid: 42,
1009 tgid: 100,
1010 timestamp_ns: 0,
1011 };
1012 assert_eq!(format!("{event}"), "EXEC pid=42 tgid=100 ts=0");
1013 }
1014
1015 #[test]
1016 fn display_comm() {
1017 let mut comm = [0u8; 16];
1018 comm[..4].copy_from_slice(b"bash");
1019 let event = ProcEvent::Comm {
1020 pid: 1,
1021 tgid: 1,
1022 comm,
1023 timestamp_ns: 0,
1024 };
1025 assert_eq!(format!("{event}"), "COMM pid=1 tgid=1 name=\"bash\" ts=0");
1026 }
1027
1028
1029
1030 #[test]
1031 fn multi_part_message_iteration() {
1032 let exec_data = [42i32.to_ne_bytes(), 100i32.to_ne_bytes()].concat();
1034 let msg1 = make_full_message(PROC_EVENT_EXEC, &exec_data);
1035
1036 let exec_data2 = [43i32.to_ne_bytes(), 101i32.to_ne_bytes()].concat();
1037 let msg2 = make_full_message(PROC_EVENT_EXEC, &exec_data2);
1038
1039 let mut combined = Vec::new();
1040 combined.extend_from_slice(&msg1);
1041 combined.extend_from_slice(&msg2);
1042
1043 let iter = NetlinkMessageIter::new(&combined, combined.len());
1044 let events: Vec<_> = iter.filter_map(|r| r.ok().flatten()).collect();
1045 assert_eq!(events.len(), 2);
1046 assert_eq!(events[0], ProcEvent::Exec { pid: 42, tgid: 100 , timestamp_ns: 0 });
1047 assert_eq!(events[1], ProcEvent::Exec { pid: 43, tgid: 101 , timestamp_ns: 0 });
1048 }
1049
1050 #[test]
1051 fn test_nlmsg_hdrlen() {
1052 assert_eq!(nlmsg_hdrlen(), 16);
1054 }
1055
1056 #[test]
1057 fn test_nlmsg_align() {
1058 assert_eq!(nlmsg_align(0), 0);
1059 assert_eq!(nlmsg_align(1), 4);
1060 assert_eq!(nlmsg_align(4), 4);
1061 assert_eq!(nlmsg_align(5), 8);
1062 assert_eq!(nlmsg_align(16), 16);
1063 }
1064
1065 #[test]
1066 fn test_nlmsg_length() {
1067 assert_eq!(nlmsg_length(0), 16);
1068 assert_eq!(nlmsg_length(20), 36);
1069 }
1070
1071 #[test]
1076 fn recv_buffer_too_small() {
1077 let err = Error::BufferTooSmall {
1083 needed: SIZE_NLMSGHDR,
1084 };
1085 assert_eq!(
1086 format!("{err}"),
1087 "buffer too small, need at least 16 bytes"
1088 );
1089 }
1090
1091 #[test]
1096 fn parse_exec_truncated() {
1097 let data = [42i32.to_ne_bytes()].concat(); let buf = make_full_message(PROC_EVENT_EXEC, &data);
1099 let result = parse_netlink_message(&buf, buf.len());
1100 assert!(matches!(result, Err(Error::Truncated)));
1101 }
1102
1103 #[test]
1104 fn parse_fork_truncated() {
1105 let data = [
1106 10i32.to_ne_bytes(), 10i32.to_ne_bytes(), 20i32.to_ne_bytes(), ]
1111 .concat();
1112 let buf = make_full_message(PROC_EVENT_FORK, &data);
1113 let result = parse_netlink_message(&buf, buf.len());
1114 assert!(matches!(result, Err(Error::Truncated)));
1115 }
1116
1117 #[test]
1118 fn parse_exit_truncated() {
1119 let data = [
1120 1i32.to_ne_bytes(), 1i32.to_ne_bytes(), 0u32.to_ne_bytes(), 17u32.to_ne_bytes(), 0i32.to_ne_bytes(), ]
1127 .concat();
1128 let buf = make_full_message(PROC_EVENT_EXIT, &data);
1129 let result = parse_netlink_message(&buf, buf.len());
1130 assert!(matches!(result, Err(Error::Truncated)));
1131 }
1132
1133 #[test]
1134 fn parse_uid_truncated() {
1135 let data = [
1136 5i32.to_ne_bytes(), 5i32.to_ne_bytes(), 1000u32.to_ne_bytes(), ]
1141 .concat();
1142 let buf = make_full_message(PROC_EVENT_UID, &data);
1143 let result = parse_netlink_message(&buf, buf.len());
1144 assert!(matches!(result, Err(Error::Truncated)));
1145 }
1146
1147 #[test]
1148 fn parse_gid_truncated() {
1149 let data = [
1150 5i32.to_ne_bytes(), 5i32.to_ne_bytes(), 100u32.to_ne_bytes(), ]
1155 .concat();
1156 let buf = make_full_message(PROC_EVENT_GID, &data);
1157 let result = parse_netlink_message(&buf, buf.len());
1158 assert!(matches!(result, Err(Error::Truncated)));
1159 }
1160
1161 #[test]
1162 fn parse_sid_truncated() {
1163 let data = [7i32.to_ne_bytes()].concat(); let buf = make_full_message(PROC_EVENT_SID, &data);
1165 let result = parse_netlink_message(&buf, buf.len());
1166 assert!(matches!(result, Err(Error::Truncated)));
1167 }
1168
1169 #[test]
1170 fn parse_ptrace_truncated() {
1171 let data = [
1172 1i32.to_ne_bytes(), 1i32.to_ne_bytes(), 999i32.to_ne_bytes(), ]
1177 .concat();
1178 let buf = make_full_message(PROC_EVENT_PTRACE, &data);
1179 let result = parse_netlink_message(&buf, buf.len());
1180 assert!(matches!(result, Err(Error::Truncated)));
1181 }
1182
1183 #[test]
1184 fn parse_comm_truncated_missing_comm() {
1185 let data = [42i32.to_ne_bytes(), 42i32.to_ne_bytes()].concat(); let buf = make_full_message(PROC_EVENT_COMM, &data);
1189 let result = parse_netlink_message(&buf, buf.len());
1190 assert!(matches!(result, Err(Error::Truncated)));
1191 }
1192
1193 #[test]
1194 fn parse_coredump_truncated() {
1195 let data = [1i32.to_ne_bytes()].concat(); let buf = make_full_message(PROC_EVENT_COREDUMP, &data);
1197 let result = parse_netlink_message(&buf, buf.len());
1198 assert!(matches!(result, Err(Error::Truncated)));
1199 }
1200
1201 #[test]
1206 fn parse_nlmsg_len_too_small() {
1207 let mut buf = make_netlink_message(NLMSG_MIN_TYPE, &[0u8; 20]);
1209 buf[0..4].copy_from_slice(&10u32.to_ne_bytes()); let result = parse_netlink_message(&buf, buf.len());
1211 assert!(matches!(result, Err(Error::Truncated)));
1212 }
1213
1214 #[test]
1215 fn parse_nlmsg_len_exceeds_buffer() {
1216 let mut buf = make_netlink_message(NLMSG_MIN_TYPE, &[0u8; 20]);
1218 buf[0..4].copy_from_slice(&1000u32.to_ne_bytes()); let result = parse_netlink_message(&buf, buf.len());
1220 assert!(matches!(result, Err(Error::Truncated)));
1221 }
1222
1223 #[test]
1224 fn parse_cn_msg_wrong_idx() {
1225 let proc_payload = make_proc_event_payload(PROC_EVENT_EXEC, &[42i32.to_ne_bytes(), 100i32.to_ne_bytes()].concat());
1227
1228 let mut cn_payload = Vec::with_capacity(SIZE_CN_MSG + proc_payload.len());
1230 cn_payload.extend_from_slice(&999u32.to_ne_bytes()); cn_payload.extend_from_slice(&CN_VAL_PROC.to_ne_bytes());
1232 cn_payload.extend_from_slice(&0u32.to_ne_bytes());
1233 cn_payload.extend_from_slice(&0u32.to_ne_bytes());
1234 cn_payload.extend_from_slice(&(proc_payload.len() as u16).to_ne_bytes());
1235 cn_payload.extend_from_slice(&0u16.to_ne_bytes());
1236 cn_payload.extend_from_slice(&proc_payload);
1237
1238 let buf = make_netlink_message(NLMSG_MIN_TYPE, &cn_payload);
1239 let result = parse_netlink_message(&buf, buf.len());
1240 assert!(matches!(result, Err(Error::Truncated)));
1241 }
1242
1243 #[test]
1244 fn parse_cn_msg_truncated_header() {
1245 let buf = make_netlink_message(NLMSG_MIN_TYPE, &[0u8; 10]);
1247 let result = parse_netlink_message(&buf, buf.len());
1248 assert!(matches!(result, Err(Error::Truncated)));
1249 }
1250
1251 #[test]
1252 fn parse_nlmsg_error_ack() {
1253 let mut payload = vec![0u8; 20];
1255 payload[0..4].copy_from_slice(&0i32.to_ne_bytes()); let buf = make_netlink_message(NLMSG_ERROR, &payload);
1257 let result = parse_netlink_message(&buf, buf.len()).unwrap();
1258 assert!(result.is_none());
1259 }
1260
1261 #[test]
1266 fn parse_exec_negative_pid() {
1267 let data = [(-1i32).to_ne_bytes(), (-1i32).to_ne_bytes()].concat();
1269 let buf = make_full_message(PROC_EVENT_EXEC, &data);
1270 let event = parse_netlink_message(&buf, buf.len()).unwrap().unwrap();
1271 assert_eq!(
1273 event,
1274 ProcEvent::Exec {
1275 pid: u32::MAX,
1276 tgid: u32::MAX,
1277 timestamp_ns: 0,
1278 }
1279 );
1280 }
1281
1282 #[test]
1283 fn parse_comm_no_nul() {
1284 let data = [
1286 42i32.to_ne_bytes(), 42i32.to_ne_bytes(), ]
1289 .concat();
1290 let mut comm_event = data;
1291 let comm: [u8; 16] = [
1292 b'a', b'b', b'c', b'd', b'e', b'f', b'g', b'h',
1293 b'i', b'j', b'k', b'l', b'm', b'n', b'o', b'p',
1294 ];
1295 comm_event.extend_from_slice(&comm);
1296
1297 let buf = make_full_message(PROC_EVENT_COMM, &comm_event);
1298 let event = parse_netlink_message(&buf, buf.len()).unwrap().unwrap();
1299 assert_eq!(
1300 event,
1301 ProcEvent::Comm {
1302 pid: 42,
1303 tgid: 42,
1304 comm,
1305 timestamp_ns: 0,
1306 }
1307 );
1308 let s = format!("{event}");
1310 assert_eq!(s, "COMM pid=42 tgid=42 name=\"abcdefghijklmnop\" ts=0");
1311 }
1312
1313 #[test]
1314 fn parse_comm_invalid_utf8() {
1315 let data = [
1317 1i32.to_ne_bytes(), 1i32.to_ne_bytes(), ]
1320 .concat();
1321 let mut comm_event = data;
1322 let mut comm = [0u8; 16];
1323 comm[0] = 0xFF; comm[1] = 0xFE;
1325 comm[2] = 0;
1326 comm_event.extend_from_slice(&comm);
1327
1328 let buf = make_full_message(PROC_EVENT_COMM, &comm_event);
1329 let event = format!(
1330 "{}",
1331 parse_netlink_message(&buf, buf.len()).unwrap().unwrap()
1332 );
1333 assert!(event.contains("<invalid>"));
1334 }
1335
1336 #[test]
1337 fn parse_unknown_large_data_skipped() {
1338 let data = vec![0xABu8; 1024];
1339 let buf = make_full_message(0xFFFFFFFF, &data);
1340 let event = parse_netlink_message(&buf, buf.len()).unwrap().unwrap();
1341 match event {
1342 ProcEvent::Unknown { what, raw_data } => {
1343 assert_eq!(what, 0xFFFFFFFF);
1344 assert_eq!(raw_data.len(), 1024);
1345 assert_eq!(raw_data[0], 0xAB);
1346 assert_eq!(raw_data[1023], 0xAB);
1347 }
1348 _ => panic!("expected Unknown"),
1349 }
1350 }
1351
1352 #[test]
1353 fn parse_zero_length_proc_event_data() {
1354 let proc_payload = make_proc_event_payload(PROC_EVENT_EXEC, &[]);
1356 let cn_payload = make_cn_msg(&proc_payload);
1357 let buf = make_netlink_message(NLMSG_MIN_TYPE, &cn_payload);
1358 let result = parse_netlink_message(&buf, buf.len());
1359 assert!(matches!(result, Err(Error::Truncated)));
1360 }
1361
1362 #[test]
1367 fn display_fork() {
1368 let event = ProcEvent::Fork {
1369 parent_pid: 100,
1370 parent_tgid: 100,
1371 child_pid: 200,
1372 child_tgid: 200,
1373 timestamp_ns: 0,
1374 };
1375 assert_eq!(
1376 format!("{event}"),
1377 "FORK parent=(100,100) child=(200,200) ts=0"
1378 );
1379 }
1380
1381 #[test]
1382 fn display_exit() {
1383 let event = ProcEvent::Exit {
1384 pid: 42,
1385 tgid: 42,
1386 exit_code: 0,
1387 exit_signal: 17,
1388 timestamp_ns: 0,
1389 };
1390 assert_eq!(format!("{event}"), "EXIT pid=42 tgid=42 code=0 signal=17 ts=0");
1391 }
1392
1393 #[test]
1394 fn display_uid() {
1395 let event = ProcEvent::Uid {
1396 pid: 1,
1397 tgid: 1,
1398 ruid: 1000,
1399 euid: 0,
1400 timestamp_ns: 0,
1401 };
1402 assert_eq!(format!("{event}"), "UID pid=1 tgid=1 ruid=1000 euid=0 ts=0");
1403 }
1404
1405 #[test]
1406 fn display_gid() {
1407 let event = ProcEvent::Gid {
1408 pid: 2,
1409 tgid: 2,
1410 rgid: 100,
1411 egid: 200,
1412 timestamp_ns: 0,
1413 };
1414 assert_eq!(format!("{event}"), "GID pid=2 tgid=2 rgid=100 egid=200 ts=0");
1415 }
1416
1417 #[test]
1418 fn display_sid() {
1419 let event = ProcEvent::Sid { pid: 3, tgid: 3 , timestamp_ns: 0 };
1420 assert_eq!(format!("{event}"), "SID pid=3 tgid=3 ts=0");
1421 }
1422
1423 #[test]
1424 fn display_ptrace() {
1425 let event = ProcEvent::Ptrace {
1426 pid: 10,
1427 tgid: 10,
1428 tracer_pid: 99,
1429 tracer_tgid: 99,
1430 timestamp_ns: 0,
1431 };
1432 assert_eq!(
1433 format!("{event}"),
1434 "PTRACE pid=10 tgid=10 tracer=(99,99) ts=0"
1435 );
1436 }
1437
1438 #[test]
1439 fn display_coredump() {
1440 let event = ProcEvent::Coredump { pid: 7, tgid: 7 , timestamp_ns: 0 };
1441 assert_eq!(format!("{event}"), "COREDUMP pid=7 tgid=7 ts=0");
1442 }
1443
1444 #[test]
1449 fn iter_empty_buffer() {
1450 let iter = NetlinkMessageIter::new(&[], 0);
1451 assert_eq!(iter.count(), 0);
1452 }
1453
1454 #[test]
1455 fn iter_single_done_message() {
1456 let buf = make_netlink_message(NLMSG_DONE, &[]);
1457 let iter = NetlinkMessageIter::new(&buf, buf.len());
1458 let results: Vec<_> = iter.collect();
1459 assert_eq!(results.len(), 0);
1461 }
1462
1463 #[test]
1464 fn iter_done_terminates_early() {
1465 let exec_data = [42i32.to_ne_bytes(), 100i32.to_ne_bytes()].concat();
1467 let msg_exec = make_full_message(PROC_EVENT_EXEC, &exec_data);
1468 let msg_done = make_netlink_message(NLMSG_DONE, &[]);
1469
1470 let mut combined = Vec::new();
1471 combined.extend_from_slice(&msg_exec);
1472 combined.extend_from_slice(&msg_done);
1473
1474 let iter = NetlinkMessageIter::new(&combined, combined.len());
1475 let events: Vec<_> = iter.filter_map(|r| r.ok().flatten()).collect();
1476 assert_eq!(events.len(), 1);
1477 assert_eq!(events[0], ProcEvent::Exec { pid: 42, tgid: 100 , timestamp_ns: 0 });
1478 }
1479
1480 #[test]
1481 fn iter_interleaved_control_messages() {
1482 let exec_data = [42i32.to_ne_bytes(), 100i32.to_ne_bytes()].concat();
1484 let fork_data = [
1485 10i32.to_ne_bytes(),
1486 10i32.to_ne_bytes(),
1487 20i32.to_ne_bytes(),
1488 20i32.to_ne_bytes(),
1489 ]
1490 .concat();
1491
1492 let msg_noop = make_netlink_message(NLMSG_NOOP, &[]);
1493 let msg_exec = make_full_message(PROC_EVENT_EXEC, &exec_data);
1494 let msg_fork = make_full_message(PROC_EVENT_FORK, &fork_data);
1495 let msg_done = make_netlink_message(NLMSG_DONE, &[]);
1496
1497 let mut combined = Vec::new();
1498 combined.extend_from_slice(&msg_noop);
1499 combined.extend_from_slice(&msg_exec);
1500 combined.extend_from_slice(&msg_noop);
1501 combined.extend_from_slice(&msg_fork);
1502 combined.extend_from_slice(&msg_done);
1503
1504 let iter = NetlinkMessageIter::new(&combined, combined.len());
1505 let events: Vec<_> = iter.filter_map(|r| r.ok().flatten()).collect();
1506 assert_eq!(events.len(), 2);
1507 assert_eq!(events[0], ProcEvent::Exec { pid: 42, tgid: 100 , timestamp_ns: 0 });
1508 assert_eq!(
1509 events[1],
1510 ProcEvent::Fork {
1511 parent_pid: 10,
1512 parent_tgid: 10,
1513 child_pid: 20,
1514 child_tgid: 20,
1515 timestamp_ns: 0,
1516 }
1517 );
1518 }
1519
1520 #[test]
1521 fn iter_malformed_zero_length() {
1522 let mut buf = vec![0u8; 16];
1524 buf[0..4].copy_from_slice(&0u32.to_ne_bytes()); let mut iter = NetlinkMessageIter::new(&buf, buf.len());
1526 let result = iter.next().unwrap();
1527 assert!(matches!(result, Err(Error::Truncated)));
1528 }
1529
1530 #[test]
1531 fn iter_remaining_too_small_for_header() {
1532 let buf = vec![0u8; 4];
1534 let mut iter = NetlinkMessageIter::new(&buf, 4);
1535 let result = iter.next().unwrap();
1536 assert!(matches!(result, Err(Error::Truncated)));
1537 }
1538
1539 #[test]
1540 fn iter_no_valid_msgs_returns_none_on_second_call() {
1541 let buf = make_netlink_message(NLMSG_DONE, &[]);
1542 let mut iter = NetlinkMessageIter::new(&buf, buf.len());
1543 assert!(iter.next().is_none());
1545 assert!(iter.next().is_none());
1547 }
1548
1549 #[test]
1554 fn test_nlmsg_align_max() {
1555 assert_eq!(nlmsg_align(65535), 65536); assert_eq!(nlmsg_align(65536), 65536);
1558 assert_eq!(nlmsg_align(65537), 65540);
1559 }
1560
1561 #[test]
1562 fn test_nlmsg_align_neg_like() {
1563 assert_eq!(nlmsg_align(2), 4);
1565 assert_eq!(nlmsg_align(3), 4);
1566 assert_eq!(nlmsg_align(6), 8);
1567 assert_eq!(nlmsg_align(7), 8);
1568 assert_eq!(nlmsg_align(8), 8);
1569 assert_eq!(nlmsg_align(9), 12);
1570 }
1571
1572 #[test]
1577 fn parse_cn_msg_truncated_no_data() {
1578 let result = parse_cn_msg(&[0u8; 15]);
1580 assert!(matches!(result, Err(Error::Truncated)));
1581 }
1582
1583 #[test]
1584 fn parse_cn_msg_data_len_mismatch() {
1585 let mut buf = vec![0u8; SIZE_CN_MSG + 4];
1587 buf[0..4].copy_from_slice(&CN_IDX_PROC.to_ne_bytes());
1588 buf[4..8].copy_from_slice(&CN_VAL_PROC.to_ne_bytes());
1589 buf[16..18].copy_from_slice(&100u16.to_ne_bytes()); let result = parse_cn_msg(&buf);
1592 assert!(matches!(result, Err(Error::Truncated)));
1593 }
1594
1595 #[test]
1600 fn iter_many_messages() {
1601 let exec_data = [42i32.to_ne_bytes(), 100i32.to_ne_bytes()].concat();
1602 let mut combined = Vec::new();
1603
1604 for _ in 0..100 {
1605 let msg = make_full_message(PROC_EVENT_EXEC, &exec_data);
1606 combined.extend_from_slice(&msg);
1607 }
1608
1609 let iter = NetlinkMessageIter::new(&combined, combined.len());
1610 let count = iter.filter_map(|r| r.ok().flatten()).count();
1611 assert_eq!(count, 100);
1612 }
1613
1614 #[test]
1619 fn error_display_all_variants() {
1620 assert_eq!(
1621 format!("{}", Error::Os(std::io::Error::from_raw_os_error(1))),
1622 "system call error: Operation not permitted (os error 1)"
1623 );
1624 assert_eq!(format!("{}", Error::Truncated), "truncated message");
1625 assert_eq!(
1626 format!("{}", Error::BufferTooSmall { needed: 64 }),
1627 "buffer too small, need at least 64 bytes"
1628 );
1629 assert_eq!(
1630 format!("{}", Error::Interrupted),
1631 "interrupted by signal"
1632 );
1633 assert_eq!(
1634 format!("{}", Error::ConnectionClosed),
1635 "connection closed"
1636 );
1637 assert_eq!(
1638 format!("{}", Error::Overrun),
1639 "message overrun, events may have been dropped"
1640 );
1641 }
1642
1643 #[test]
1644 fn error_source() {
1645 use std::error::Error as _;
1646 assert!(Error::Os(std::io::Error::from_raw_os_error(1)).source().is_some());
1647 assert!(Error::Truncated.source().is_none());
1648 assert!(Error::BufferTooSmall { needed: 16 }.source().is_none());
1649 assert!(Error::Interrupted.source().is_none());
1650 assert!(Error::ConnectionClosed.source().is_none());
1651 assert!(Error::Overrun.source().is_none());
1652 }
1653
1654 #[test]
1659 fn parse_nlmsg_len_inconsistent() {
1660 let mut buf = make_netlink_message(NLMSG_MIN_TYPE, &[0u8; 100]);
1663 buf[0..4].copy_from_slice(&20u32.to_ne_bytes()); let result = parse_netlink_message(&buf, buf.len());
1666 assert!(matches!(result, Err(Error::Truncated)));
1670 }
1671
1672 #[test]
1677 fn proc_event_clone_and_eq() {
1678 let e1 = ProcEvent::Exec {
1679 pid: 42,
1680 tgid: 100,
1681 timestamp_ns: 0,
1682 };
1683 let e2 = e1.clone();
1684 assert_eq!(e1, e2);
1685
1686 let e3 = ProcEvent::Exec {
1687 pid: 43,
1688 tgid: 100,
1689 timestamp_ns: 0,
1690 };
1691 assert_ne!(e1, e3);
1692 }
1693
1694 #[test]
1699 fn roundtrip_all_event_types() {
1700 use std::error::Error as _;
1706 let _ = Error::Truncated.source(); }
1708
1709 #[test]
1714 fn parse_with_nlm_f_request_flag() {
1715 let exec_data = [42i32.to_ne_bytes(), 100i32.to_ne_bytes()].concat();
1717 let proc_payload = make_proc_event_payload(PROC_EVENT_EXEC, &exec_data);
1718 let cn_payload = make_cn_msg(&proc_payload);
1719 let mut buf = make_netlink_message(NLMSG_MIN_TYPE, &cn_payload);
1720 buf[6..8].copy_from_slice(&NLM_F_REQUEST.to_ne_bytes());
1721
1722 let event = parse_netlink_message(&buf, buf.len()).unwrap().unwrap();
1723 assert_eq!(event, ProcEvent::Exec { pid: 42, tgid: 100 , timestamp_ns: 0 });
1724 }
1725
1726 #[test]
1731 fn iter_alignment_correct() {
1732 let msg1 = make_netlink_message(NLMSG_NOOP, &[]); let msg2 = make_full_message(PROC_EVENT_EXEC, &[42i32.to_ne_bytes(), 100i32.to_ne_bytes()].concat());
1735 let msg3 = make_netlink_message(NLMSG_DONE, &[]);
1736
1737 let mut combined = Vec::new();
1738 combined.extend_from_slice(&msg1);
1739 combined.extend_from_slice(&msg2);
1740 combined.extend_from_slice(&msg3);
1741
1742 let mut pos = 0;
1744
1745 let len1 = u32::from_ne_bytes(msg1[0..4].try_into().unwrap()) as usize;
1747 assert_eq!(len1, 16);
1748 pos += nlmsg_align(len1);
1749
1750 let len2 = u32::from_ne_bytes(msg2[0..4].try_into().unwrap()) as usize;
1752 assert!(len2 > 16);
1753 assert_eq!(pos, 16); pos += nlmsg_align(len2);
1755
1756 let len3 = u32::from_ne_bytes(msg3[0..4].try_into().unwrap()) as usize;
1758 assert_eq!(len3, 16);
1759 assert_eq!(pos, 16 + nlmsg_align(len2)); let iter = NetlinkMessageIter::new(&combined, combined.len());
1763 let events: Vec<_> = iter.filter_map(|r| r.ok().flatten()).collect();
1764 assert_eq!(events.len(), 1);
1765 assert_eq!(events[0], ProcEvent::Exec { pid: 42, tgid: 100 , timestamp_ns: 0 });
1766 }
1767
1768 #[test]
1769 fn parse_exec_timestamp_nonzero() {
1770 let ts: u64 = 123456789012345;
1772 let data = [42i32.to_ne_bytes(), 100i32.to_ne_bytes()].concat();
1773 let proc_payload = make_proc_event_payload_with_ts(PROC_EVENT_EXEC, ts, &data);
1774 let cn_payload = make_cn_msg(&proc_payload);
1775 let buf = make_netlink_message(NLMSG_MIN_TYPE, &cn_payload);
1776 let event = parse_netlink_message(&buf, buf.len()).unwrap().unwrap();
1777 match event {
1778 ProcEvent::Exec { pid, tgid, timestamp_ns } => {
1779 assert_eq!(pid, 42);
1780 assert_eq!(tgid, 100);
1781 assert_eq!(timestamp_ns, ts, "timestamp_ns should be preserved");
1782 }
1783 _ => panic!("expected Exec event"),
1784 }
1785 assert!(
1787 event.to_string().contains(&format!("ts={ts}")),
1788 "Display should include ts=... but got: {}",
1789 event
1790 );
1791 }
1792}
1793