use std::ffi::c_int;
use crate::error::{Error, Result};
pub const TPACKET_V2: c_int = 1;
pub const TPACKET_V3: c_int = 2;
#[must_use]
pub const fn tpacket_align(x: usize) -> usize {
(x + libc::TPACKET_ALIGNMENT - 1) & !(libc::TPACKET_ALIGNMENT - 1)
}
#[must_use]
pub const fn min_frame_size() -> usize {
tpacket_align(libc::TPACKET3_HDRLEN) + tpacket_align(64)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct RingLayout {
pub block_size: u32,
pub block_count: u32,
pub frame_size: u32,
pub frame_count: u32,
pub retire_blk_tov_ms: u32,
}
impl RingLayout {
pub fn new(
block_size: u32,
block_count: u32,
frame_size: u32,
retire_blk_tov_ms: u32,
page_size: u32,
) -> Result<Self> {
if block_count == 0 {
return Err(Error::invalid_config("block_count must be non-zero"));
}
if page_size == 0 {
return Err(Error::invalid_config("page_size must be non-zero"));
}
if frame_size == 0 || (frame_size as usize) % libc::TPACKET_ALIGNMENT != 0 {
return Err(Error::invalid_config(
"frame_size must be a non-zero multiple of TPACKET_ALIGNMENT (16)",
));
}
if (frame_size as usize) < min_frame_size() {
return Err(Error::invalid_config("frame_size is smaller than min_frame_size()"));
}
if block_size == 0 || block_size % page_size != 0 {
return Err(Error::invalid_config("block_size must be a multiple of the page size"));
}
if block_size % frame_size != 0 {
return Err(Error::invalid_config("block_size must be a multiple of frame_size"));
}
let frames_per_block = block_size / frame_size;
let frame_count = frames_per_block
.checked_mul(block_count)
.ok_or(Error::invalid_config("ring geometry overflows u32"))?;
(block_size as usize)
.checked_mul(block_count as usize)
.ok_or(Error::invalid_config("ring size overflows usize"))?;
Ok(Self { block_size, block_count, frame_size, frame_count, retire_blk_tov_ms })
}
#[must_use]
pub const fn map_len(&self) -> usize {
self.block_size as usize * self.block_count as usize
}
#[must_use]
pub fn to_req3(self) -> libc::tpacket_req3 {
libc::tpacket_req3 {
tp_block_size: self.block_size,
tp_block_nr: self.block_count,
tp_frame_size: self.frame_size,
tp_frame_nr: self.frame_count,
tp_retire_blk_tov: self.retire_blk_tov_ms,
tp_sizeof_priv: 0,
tp_feature_req_word: 0,
}
}
#[must_use]
pub fn to_req(self) -> libc::tpacket_req {
libc::tpacket_req {
tp_block_size: self.block_size,
tp_block_nr: self.block_count,
tp_frame_size: self.frame_size,
tp_frame_nr: self.frame_count,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use proptest::prelude::*;
const PAGE: u32 = 4096;
#[test]
fn align_rounds_up_to_16() {
assert_eq!(tpacket_align(0), 0);
assert_eq!(tpacket_align(1), 16);
assert_eq!(tpacket_align(16), 16);
assert_eq!(tpacket_align(17), 32);
assert_eq!(tpacket_align(2048), 2048);
}
#[test]
fn version_constants_match_kernel() {
assert_eq!(TPACKET_V2, 1);
assert_eq!(TPACKET_V3, 2);
}
#[test]
fn valid_layout_computes_geometry() {
let l = RingLayout::new(1 << 20, 8, 2048, 60, PAGE).unwrap();
let frames_per_block = (1 << 20) / 2048;
assert_eq!(l.frame_count, frames_per_block * 8);
assert_eq!(l.map_len(), (1usize << 20) * 8);
let req = l.to_req3();
assert_eq!(req.tp_block_size, 1 << 20);
assert_eq!(req.tp_block_nr, 8);
assert_eq!(req.tp_frame_size, 2048);
assert_eq!(req.tp_frame_nr, l.frame_count);
assert_eq!(req.tp_retire_blk_tov, 60);
}
#[test]
fn rejects_unaligned_frame_size() {
assert!(RingLayout::new(1 << 20, 8, 2050, 60, PAGE).is_err());
}
#[test]
fn rejects_block_not_multiple_of_page() {
assert!(RingLayout::new(4097, 8, 2048, 60, PAGE).is_err());
}
#[test]
fn rejects_block_not_multiple_of_frame() {
assert!(RingLayout::new(PAGE, 8, 2048 + 16, 60, PAGE).is_err());
}
#[test]
fn rejects_zero_block_count() {
assert!(RingLayout::new(1 << 20, 0, 2048, 60, PAGE).is_err());
}
#[test]
fn rejects_tiny_frame_size() {
assert!(RingLayout::new(1 << 20, 8, 16, 60, PAGE).is_err());
}
proptest! {
#[test]
#[cfg_attr(miri, ignore = "proptest is slow under Miri and covers safe arithmetic")]
fn ring_layout_invariants(
block_size in 0u32..=1 << 24,
block_count in 0u32..=128,
frame_size in 0u32..=1 << 16,
page in prop_oneof![Just(4096u32), Just(16384u32), Just(65536u32)],
) {
if let Ok(l) = RingLayout::new(block_size, block_count, frame_size, 0, page) {
prop_assert_eq!(l.block_size % page, 0);
prop_assert_eq!(l.block_size % l.frame_size, 0);
prop_assert_eq!(l.frame_count, (l.block_size / l.frame_size) * l.block_count);
prop_assert_eq!(l.map_len(), l.block_size as usize * l.block_count as usize);
}
}
}
}