Skip to main content

dpdk_stdlib/
mbuf.rs

1//! Memory buffer management for DPDK packets
2//!
3//! The mbuf (memory buffer) is the fundamental data structure for packet handling
4//! in DPDK. This module provides safe Rust wrappers around DPDK's mbuf operations.
5
6use crate::error::{DpdkError, DpdkResult};
7use std::ffi::CString;
8use std::ptr::NonNull;
9
10/// A memory buffer for packet data
11///
12/// Mbufs are the basic unit for carrying packet data in DPDK.
13/// They are allocated from memory pools and contain both metadata
14/// and actual packet data.
15pub struct Mbuf {
16    raw: NonNull<dpdk_sys::rte_mbuf>,
17}
18
19// Safety: Mbufs can be sent between threads as long as only one thread
20// accesses them at a time (which is enforced by ownership)
21unsafe impl Send for Mbuf {}
22
23impl Mbuf {
24    /// Create a new Mbuf from a raw pointer
25    ///
26    /// # Safety
27    ///
28    /// The caller must ensure:
29    /// - The pointer is valid and properly aligned
30    /// - The mbuf was allocated from a valid mempool
31    /// - Ownership is being transferred (the raw pointer should not be freed elsewhere)
32    pub unsafe fn from_raw(ptr: *mut dpdk_sys::rte_mbuf) -> Option<Self> {
33        NonNull::new(ptr).map(|raw| Self { raw })
34    }
35
36    /// Get the raw pointer to the underlying mbuf
37    pub fn as_raw(&self) -> *mut dpdk_sys::rte_mbuf {
38        self.raw.as_ptr()
39    }
40
41    /// Consume self and return the raw pointer
42    ///
43    /// After calling this, the caller is responsible for freeing the mbuf
44    pub fn into_raw(self) -> *mut dpdk_sys::rte_mbuf {
45        let ptr = self.raw.as_ptr();
46        std::mem::forget(self); // Don't run Drop
47        ptr
48    }
49
50    /// Get the data offset within the buffer
51    pub fn data_offset(&self) -> u16 {
52        unsafe { (*self.raw.as_ptr()).data_off }
53    }
54
55    /// Get the total packet length
56    pub fn packet_len(&self) -> u32 {
57        unsafe { (*self.raw.as_ptr()).pkt_len }
58    }
59
60    /// Get the data length in this segment
61    pub fn data_len(&self) -> u16 {
62        unsafe { (*self.raw.as_ptr()).data_len }
63    }
64
65    /// Get the buffer length (total available space)
66    pub fn buf_len(&self) -> u16 {
67        unsafe { (*self.raw.as_ptr()).buf_len }
68    }
69
70    /// Get a slice to the packet data
71    ///
72    /// Returns None if the buffer address is null
73    pub fn data(&self) -> Option<&[u8]> {
74        unsafe {
75            let mbuf = self.raw.as_ptr();
76            let buf_addr = (*mbuf).buf_addr;
77            if buf_addr.is_null() {
78                return None;
79            }
80            let data_ptr = (buf_addr as *const u8).add((*mbuf).data_off as usize);
81            Some(std::slice::from_raw_parts(data_ptr, (*mbuf).data_len as usize))
82        }
83    }
84
85    /// Get a mutable slice to the packet data
86    ///
87    /// Returns None if the buffer address is null
88    pub fn data_mut(&mut self) -> Option<&mut [u8]> {
89        unsafe {
90            let mbuf = self.raw.as_ptr();
91            let buf_addr = (*mbuf).buf_addr;
92            if buf_addr.is_null() {
93                return None;
94            }
95            let data_ptr = (buf_addr as *mut u8).add((*mbuf).data_off as usize);
96            Some(std::slice::from_raw_parts_mut(data_ptr, (*mbuf).data_len as usize))
97        }
98    }
99
100    /// Set the data length
101    pub fn set_data_len(&mut self, len: u16) {
102        unsafe {
103            (*self.raw.as_ptr()).data_len = len;
104        }
105    }
106
107    /// Set the packet length
108    pub fn set_packet_len(&mut self, len: u32) {
109        unsafe {
110            (*self.raw.as_ptr()).pkt_len = len;
111        }
112    }
113
114    /// Get the ol_flags field (offload flags)
115    pub fn ol_flags(&self) -> u64 {
116        unsafe { (*self.raw.as_ptr()).ol_flags }
117    }
118
119    /// Set the ol_flags field (offload flags)
120    pub fn set_ol_flags(&mut self, flags: u64) {
121        unsafe {
122            (*self.raw.as_ptr()).ol_flags = flags;
123        }
124    }
125
126    /// Get the VLAN Tag Control Information (TCI) field.
127    ///
128    /// On RX, the NIC populates this when `RTE_MBUF_F_RX_VLAN_STRIPPED` is set
129    /// in ol_flags. On TX, the application sets this and the NIC inserts the
130    /// VLAN tag when `RTE_MBUF_F_TX_VLAN` is set.
131    pub fn vlan_tci(&self) -> u16 {
132        unsafe { (*self.raw.as_ptr()).vlan_tci }
133    }
134
135    /// Set the VLAN TCI field for hardware VLAN insertion on TX.
136    ///
137    /// Must be combined with `set_ol_flags(RTE_MBUF_F_TX_VLAN)` for the NIC
138    /// to actually insert the VLAN tag.
139    pub fn set_vlan_tci(&mut self, tci: u16) {
140        unsafe {
141            (*self.raw.as_ptr()).vlan_tci = tci;
142        }
143    }
144
145    /// Set TX offload lengths (l2_len, l3_len, l4_len) in the tx_offload bitfield.
146    ///
147    /// DPDK encodes these as: l2_len (bits 0-6, 7 bits), l3_len (bits 7-15, 9 bits),
148    /// l4_len (bits 16-23, 8 bits).
149    ///
150    /// Uses a C shim wrapper because `tx_offload` lives inside an anonymous union
151    /// in the real DPDK `rte_mbuf` struct, which bindgen represents as
152    /// `__bindgen_anon_N.tx_offload` rather than a direct field.
153    pub fn set_tx_offload(&mut self, l2_len: u8, l3_len: u16, l4_len: u8) {
154        let tx_offload = (l2_len as u64)
155            | ((l3_len as u64) << 7)
156            | ((l4_len as u64) << 16);
157        unsafe {
158            dpdk_sys::mbuf_set_tx_offload(self.raw.as_ptr(), tx_offload);
159        }
160    }
161}
162
163impl Drop for Mbuf {
164    fn drop(&mut self) {
165        unsafe {
166            dpdk_sys::rte_pktmbuf_free(self.raw.as_ptr());
167        }
168    }
169}
170
171/// Memory pool for packet buffers
172///
173/// A mempool is a fixed-size pool of mbufs. All packet buffers in DPDK
174/// must be allocated from a mempool.
175pub struct Mempool {
176    raw: NonNull<dpdk_sys::rte_mempool>,
177    name: String,
178}
179
180// Safety: Mempools are thread-safe in DPDK
181unsafe impl Send for Mempool {}
182unsafe impl Sync for Mempool {}
183
184/// Default number of mbufs in a pool
185pub const DEFAULT_POOL_SIZE: u32 = 8192;
186
187/// Default per-core cache size
188pub const DEFAULT_CACHE_SIZE: u32 = 256;
189
190/// Default data room size (2KB + headroom)
191pub const DEFAULT_DATA_ROOM_SIZE: u16 = dpdk_sys::RTE_MBUF_DEFAULT_BUF_SIZE as u16;
192
193/// Maximum length for mempool names
194pub const MAX_MEMPOOL_NAME_LEN: usize = 32;
195
196/// Configuration for creating a mempool
197#[derive(Debug, Clone)]
198pub struct MempoolConfig {
199    /// Number of elements in the pool
200    pub n: u32,
201    /// Per-core cache size (0 to disable caching)
202    pub cache_size: u32,
203    /// Size of data buffer in each mbuf
204    pub data_room_size: u16,
205    /// NUMA socket ID (-1 for any socket)
206    pub socket_id: i32,
207}
208
209impl Default for MempoolConfig {
210    fn default() -> Self {
211        Self {
212            n: DEFAULT_POOL_SIZE,
213            cache_size: DEFAULT_CACHE_SIZE,
214            data_room_size: DEFAULT_DATA_ROOM_SIZE,
215            socket_id: dpdk_sys::SOCKET_ID_ANY,
216        }
217    }
218}
219
220impl MempoolConfig {
221    /// Create a new mempool configuration with default values
222    pub fn new() -> Self {
223        Self::default()
224    }
225
226    /// Set the pool size
227    pub fn with_size(mut self, n: u32) -> Self {
228        self.n = n;
229        self
230    }
231
232    /// Set the cache size
233    pub fn with_cache_size(mut self, cache_size: u32) -> Self {
234        self.cache_size = cache_size;
235        self
236    }
237
238    /// Set the data room size
239    pub fn with_data_room_size(mut self, size: u16) -> Self {
240        self.data_room_size = size;
241        self
242    }
243
244    /// Set the NUMA socket ID
245    pub fn with_socket_id(mut self, socket_id: i32) -> Self {
246        self.socket_id = socket_id;
247        self
248    }
249}
250
251impl Mempool {
252    /// Create a new mempool for packet buffers
253    ///
254    /// # Arguments
255    ///
256    /// * `name` - Unique name for this mempool (max 32 characters)
257    /// * `n` - Number of elements in the pool
258    /// * `cache_size` - Per-core cache size (0 to disable)
259    /// * `data_room_size` - Size of data buffer in each mbuf
260    /// * `socket_id` - NUMA socket ID (-1 for any)
261    ///
262    /// # Errors
263    ///
264    /// Returns an error if:
265    /// - The name is empty or too long
266    /// - The name contains null bytes
267    /// - DPDK mempool creation fails (e.g., out of memory, invalid parameters)
268    pub fn create(
269        name: &str,
270        n: u32,
271        cache_size: u32,
272        data_room_size: u16,
273        socket_id: i32,
274    ) -> DpdkResult<Self> {
275        // Validate name
276        if name.is_empty() {
277            return Err(DpdkError::InvalidName("mempool name cannot be empty".to_string()));
278        }
279        if name.len() > MAX_MEMPOOL_NAME_LEN {
280            return Err(DpdkError::InvalidName(format!(
281                "mempool name too long (max {} characters)",
282                MAX_MEMPOOL_NAME_LEN
283            )));
284        }
285
286        // Convert name to C string
287        let c_name = CString::new(name).map_err(|_| {
288            DpdkError::InvalidName("mempool name contains null bytes".to_string())
289        })?;
290
291        // Create the mempool using DPDK
292        let ptr = unsafe {
293            dpdk_sys::rte_pktmbuf_pool_create(
294                c_name.as_ptr(),
295                n,
296                cache_size,
297                0, // priv_size - typically 0 for standard packet pools
298                data_room_size,
299                socket_id,
300            )
301        };
302
303        NonNull::new(ptr)
304            .map(|raw| Self {
305                raw,
306                name: name.to_string(),
307            })
308            .ok_or_else(|| {
309                // Get the DPDK errno for more detailed error message
310                let errno = unsafe { dpdk_sys::rte_errno() };
311                DpdkError::MempoolCreateFailed(format!(
312                    "rte_pktmbuf_pool_create failed for '{}' (errno: {})",
313                    name, errno
314                ))
315            })
316    }
317
318    /// Create a new mempool with configuration
319    ///
320    /// # Arguments
321    ///
322    /// * `name` - Unique name for this mempool
323    /// * `config` - Mempool configuration
324    pub fn create_with_config(name: &str, config: &MempoolConfig) -> DpdkResult<Self> {
325        Self::create(
326            name,
327            config.n,
328            config.cache_size,
329            config.data_room_size,
330            config.socket_id,
331        )
332    }
333
334    /// Create a mempool with default configuration
335    pub fn create_default(name: &str) -> DpdkResult<Self> {
336        Self::create_with_config(name, &MempoolConfig::default())
337    }
338
339    /// Get the name of this mempool
340    pub fn name(&self) -> &str {
341        &self.name
342    }
343
344    /// Allocate an mbuf from this pool
345    pub fn alloc(&self) -> DpdkResult<Mbuf> {
346        unsafe {
347            let ptr = dpdk_sys::rte_pktmbuf_alloc(self.raw.as_ptr());
348            Mbuf::from_raw(ptr).ok_or(DpdkError::MemoryAllocationFailed)
349        }
350    }
351
352    /// Allocate multiple mbufs from this pool in a batch
353    ///
354    /// This is more efficient than calling `alloc()` multiple times.
355    /// Returns Ok with a vector of mbufs, or Err if allocation fails.
356    pub fn alloc_bulk(&self, count: usize) -> DpdkResult<Vec<Mbuf>> {
357        if count == 0 {
358            return Ok(Vec::new());
359        }
360
361        let mut ptrs: Vec<*mut dpdk_sys::rte_mbuf> =
362            vec![std::ptr::null_mut(); count];
363
364        let ret = unsafe {
365            dpdk_sys::rte_pktmbuf_alloc_bulk(
366                self.raw.as_ptr(),
367                ptrs.as_mut_ptr(),
368                count as u32,
369            )
370        };
371
372        if ret != 0 {
373            return Err(DpdkError::MemoryAllocationFailed);
374        }
375
376        let mbufs: Vec<Mbuf> = ptrs
377            .into_iter()
378            .filter_map(|ptr| unsafe { Mbuf::from_raw(ptr) })
379            .collect();
380
381        if mbufs.len() != count {
382            // Some allocations failed, free the ones we got and return error
383            // (mbufs will be dropped automatically)
384            return Err(DpdkError::MemoryAllocationFailed);
385        }
386
387        Ok(mbufs)
388    }
389
390    /// Get the number of free elements in the pool
391    pub fn available_count(&self) -> u32 {
392        unsafe { dpdk_sys::rte_mempool_avail_count(self.raw.as_ptr()) }
393    }
394
395    /// Get the number of elements in use
396    pub fn in_use_count(&self) -> u32 {
397        unsafe { dpdk_sys::rte_mempool_in_use_count(self.raw.as_ptr()) }
398    }
399
400    /// Check if the pool is full (all elements available)
401    pub fn is_full(&self) -> bool {
402        unsafe { dpdk_sys::rte_mempool_full(self.raw.as_ptr()) != 0 }
403    }
404
405    /// Check if the pool is empty (no elements available)
406    pub fn is_empty(&self) -> bool {
407        unsafe { dpdk_sys::rte_mempool_empty(self.raw.as_ptr()) != 0 }
408    }
409
410    /// Get the raw pointer to the mempool
411    pub fn as_raw(&self) -> *mut dpdk_sys::rte_mempool {
412        self.raw.as_ptr()
413    }
414}
415
416impl Drop for Mempool {
417    fn drop(&mut self) {
418        unsafe {
419            dpdk_sys::rte_mempool_free(self.raw.as_ptr());
420        }
421    }
422}
423
424/// Builder for creating mbufs with specific content
425pub struct MbufBuilder {
426    data: Vec<u8>,
427}
428
429impl MbufBuilder {
430    pub fn new() -> Self {
431        Self { data: Vec::new() }
432    }
433
434    /// Add ethernet header
435    pub fn ethernet(mut self, dst_mac: [u8; 6], src_mac: [u8; 6], ethertype: u16) -> Self {
436        self.data.extend_from_slice(&dst_mac);
437        self.data.extend_from_slice(&src_mac);
438        self.data.extend_from_slice(&ethertype.to_be_bytes());
439        self
440    }
441
442    /// Add IPv4 header (simplified)
443    pub fn ipv4(mut self, src: [u8; 4], dst: [u8; 4], protocol: u8, payload_len: u16) -> Self {
444        let total_len = 20 + payload_len;
445        self.data.push(0x45); // Version + IHL
446        self.data.push(0x00); // DSCP + ECN
447        self.data.extend_from_slice(&total_len.to_be_bytes());
448        self.data.extend_from_slice(&[0, 0]); // Identification
449        self.data.extend_from_slice(&[0, 0]); // Flags + Fragment offset
450        self.data.push(64); // TTL
451        self.data.push(protocol);
452        self.data.extend_from_slice(&[0, 0]); // Checksum (to be calculated)
453        self.data.extend_from_slice(&src);
454        self.data.extend_from_slice(&dst);
455        self
456    }
457
458    /// Add UDP header
459    pub fn udp(mut self, src_port: u16, dst_port: u16, payload_len: u16) -> Self {
460        let udp_len = 8 + payload_len;
461        self.data.extend_from_slice(&src_port.to_be_bytes());
462        self.data.extend_from_slice(&dst_port.to_be_bytes());
463        self.data.extend_from_slice(&udp_len.to_be_bytes());
464        self.data.extend_from_slice(&[0, 0]); // Checksum
465        self
466    }
467
468    /// Add payload data
469    pub fn payload(mut self, data: &[u8]) -> Self {
470        self.data.extend_from_slice(data);
471        self
472    }
473
474    /// Build into a byte vector (for use with synthetic testing)
475    pub fn build(self) -> Vec<u8> {
476        self.data
477    }
478}
479
480impl Default for MbufBuilder {
481    fn default() -> Self {
482        Self::new()
483    }
484}
485
486#[cfg(test)]
487mod tests {
488    use super::*;
489
490    // ========================================================================
491    // MbufBuilder Tests
492    // ========================================================================
493
494    #[test]
495    fn test_mbuf_builder() {
496        let frame = MbufBuilder::new()
497            .ethernet([0xff; 6], [0x00; 6], 0x0800)
498            .ipv4([192, 168, 1, 1], [192, 168, 1, 2], 17, 16)
499            .udp(12345, 9000, 8)
500            .payload(b"test")
501            .build();
502
503        // Ethernet: 14 bytes, IP: 20 bytes, UDP: 8 bytes, payload: 4 bytes
504        assert_eq!(frame.len(), 14 + 20 + 8 + 4);
505    }
506
507    #[test]
508    fn test_mbuf_builder_empty() {
509        let frame = MbufBuilder::new().build();
510        assert!(frame.is_empty());
511    }
512
513    #[test]
514    fn test_mbuf_builder_payload_only() {
515        let payload = b"hello world";
516        let frame = MbufBuilder::new().payload(payload).build();
517        assert_eq!(frame.len(), payload.len());
518        assert_eq!(&frame, payload);
519    }
520
521    #[test]
522    fn test_mbuf_builder_ethernet_only() {
523        let frame = MbufBuilder::new()
524            .ethernet([0x01, 0x02, 0x03, 0x04, 0x05, 0x06], [0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f], 0x0800)
525            .build();
526
527        // Ethernet header is 14 bytes: 6 dst + 6 src + 2 ethertype
528        assert_eq!(frame.len(), 14);
529
530        // Verify dst MAC
531        assert_eq!(&frame[0..6], &[0x01, 0x02, 0x03, 0x04, 0x05, 0x06]);
532        // Verify src MAC
533        assert_eq!(&frame[6..12], &[0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f]);
534        // Verify ethertype (0x0800 = IPv4)
535        assert_eq!(&frame[12..14], &[0x08, 0x00]);
536    }
537
538    // ========================================================================
539    // MempoolConfig Tests
540    // ========================================================================
541
542    #[test]
543    fn test_mempool_config_default() {
544        let config = MempoolConfig::default();
545        assert_eq!(config.n, DEFAULT_POOL_SIZE);
546        assert_eq!(config.cache_size, DEFAULT_CACHE_SIZE);
547        assert_eq!(config.data_room_size, DEFAULT_DATA_ROOM_SIZE);
548        assert_eq!(config.socket_id, dpdk_sys::SOCKET_ID_ANY);
549    }
550
551    #[test]
552    fn test_mempool_config_builder() {
553        let config = MempoolConfig::new()
554            .with_size(4096)
555            .with_cache_size(128)
556            .with_data_room_size(2048)
557            .with_socket_id(0);
558
559        assert_eq!(config.n, 4096);
560        assert_eq!(config.cache_size, 128);
561        assert_eq!(config.data_room_size, 2048);
562        assert_eq!(config.socket_id, 0);
563    }
564
565    #[test]
566    fn test_mempool_config_chaining() {
567        // Test that builder methods can be chained
568        let config = MempoolConfig::new()
569            .with_size(1024)
570            .with_cache_size(64);
571
572        assert_eq!(config.n, 1024);
573        assert_eq!(config.cache_size, 64);
574        // Other fields should retain defaults
575        assert_eq!(config.data_room_size, DEFAULT_DATA_ROOM_SIZE);
576    }
577
578    // ========================================================================
579    // Mempool Creation Tests
580    // ========================================================================
581
582    #[test]
583    fn test_mempool_create() {
584        let pool = Mempool::create("test_pool", 128, 32, 2048, -1);
585        assert!(pool.is_ok());
586        let pool = pool.unwrap();
587        assert_eq!(pool.name(), "test_pool");
588    }
589
590    #[test]
591    fn test_mempool_create_with_config() {
592        let config = MempoolConfig::new().with_size(256).with_cache_size(64);
593        let pool = Mempool::create_with_config("config_pool", &config);
594        assert!(pool.is_ok());
595        let pool = pool.unwrap();
596        assert_eq!(pool.name(), "config_pool");
597    }
598
599    #[test]
600    fn test_mempool_create_default() {
601        let pool = Mempool::create_default("default_pool");
602        assert!(pool.is_ok());
603        let pool = pool.unwrap();
604        assert_eq!(pool.name(), "default_pool");
605    }
606
607    #[test]
608    fn test_mempool_create_empty_name() {
609        let result = Mempool::create("", 128, 32, 2048, -1);
610        assert!(result.is_err());
611        match result {
612            Err(DpdkError::InvalidName(msg)) => {
613                assert!(msg.contains("empty"));
614            }
615            _ => panic!("Expected InvalidName error"),
616        }
617    }
618
619    #[test]
620    fn test_mempool_create_name_too_long() {
621        let long_name = "a".repeat(MAX_MEMPOOL_NAME_LEN + 1);
622        let result = Mempool::create(&long_name, 128, 32, 2048, -1);
623        assert!(result.is_err());
624        match result {
625            Err(DpdkError::InvalidName(msg)) => {
626                assert!(msg.contains("too long"));
627            }
628            _ => panic!("Expected InvalidName error"),
629        }
630    }
631
632    #[test]
633    fn test_mempool_create_name_with_null() {
634        let result = Mempool::create("test\0pool", 128, 32, 2048, -1);
635        assert!(result.is_err());
636        match result {
637            Err(DpdkError::InvalidName(msg)) => {
638                assert!(msg.contains("null"));
639            }
640            _ => panic!("Expected InvalidName error"),
641        }
642    }
643
644    #[test]
645    fn test_mempool_name_max_length() {
646        let max_name = "a".repeat(MAX_MEMPOOL_NAME_LEN);
647        let result = Mempool::create(&max_name, 128, 32, 2048, -1);
648        assert!(result.is_ok());
649    }
650
651    // ========================================================================
652    // Mempool Operations Tests
653    // ========================================================================
654
655    #[test]
656    fn test_mempool_alloc() {
657        let pool = Mempool::create("alloc_pool", 128, 32, 2048, -1).unwrap();
658        let mbuf = pool.alloc();
659        assert!(mbuf.is_ok());
660    }
661
662    #[test]
663    fn test_mempool_alloc_bulk_zero() {
664        let pool = Mempool::create("bulk_zero_pool", 128, 32, 2048, -1).unwrap();
665        let mbufs = pool.alloc_bulk(0);
666        assert!(mbufs.is_ok());
667        assert!(mbufs.unwrap().is_empty());
668    }
669
670    #[test]
671    fn test_mempool_alloc_bulk() {
672        let pool = Mempool::create("bulk_pool", 128, 32, 2048, -1).unwrap();
673        let mbufs = pool.alloc_bulk(4);
674        assert!(mbufs.is_ok());
675        let mbufs = mbufs.unwrap();
676        assert_eq!(mbufs.len(), 4);
677    }
678
679    #[test]
680    fn test_mempool_available_count() {
681        let pool = Mempool::create("avail_pool", 128, 32, 2048, -1).unwrap();
682        let count = pool.available_count();
683        // With stubs, initial count should be the pool size
684        assert!(count > 0);
685    }
686
687    #[test]
688    fn test_mempool_in_use_count() {
689        let pool = Mempool::create("inuse_pool", 128, 32, 2048, -1).unwrap();
690        let count = pool.in_use_count();
691        // Initially nothing should be in use
692        assert_eq!(count, 0);
693    }
694
695    #[test]
696    fn test_mempool_is_full() {
697        let pool = Mempool::create("full_pool", 128, 32, 2048, -1).unwrap();
698        // Initially pool should be full (all elements available)
699        assert!(pool.is_full());
700    }
701
702    #[test]
703    fn test_mempool_is_empty() {
704        let pool = Mempool::create("empty_pool", 128, 32, 2048, -1).unwrap();
705        // Initially pool should not be empty
706        assert!(!pool.is_empty());
707    }
708
709    #[test]
710    fn test_mempool_as_raw() {
711        let pool = Mempool::create("raw_pool", 128, 32, 2048, -1).unwrap();
712        let raw = pool.as_raw();
713        assert!(!raw.is_null());
714    }
715
716    // ========================================================================
717    // Mbuf Tests (with stubs)
718    // ========================================================================
719
720    #[test]
721    fn test_mbuf_data_offset() {
722        let pool = Mempool::create("offset_pool", 128, 32, 2048, -1).unwrap();
723        let mbuf = pool.alloc().unwrap();
724        // Data offset should be set (typically RTE_PKTMBUF_HEADROOM)
725        let offset = mbuf.data_offset();
726        assert!(offset >= 0);
727    }
728
729    #[test]
730    fn test_mbuf_packet_len() {
731        let pool = Mempool::create("pktlen_pool", 128, 32, 2048, -1).unwrap();
732        let mbuf = pool.alloc().unwrap();
733        // Initially packet length should be 0
734        assert_eq!(mbuf.packet_len(), 0);
735    }
736
737    #[test]
738    fn test_mbuf_data_len() {
739        let pool = Mempool::create("datalen_pool", 128, 32, 2048, -1).unwrap();
740        let mbuf = pool.alloc().unwrap();
741        // Initially data length should be 0
742        assert_eq!(mbuf.data_len(), 0);
743    }
744
745    #[test]
746    fn test_mbuf_set_lengths() {
747        let pool = Mempool::create("setlen_pool", 128, 32, 2048, -1).unwrap();
748        let mut mbuf = pool.alloc().unwrap();
749
750        mbuf.set_data_len(100);
751        assert_eq!(mbuf.data_len(), 100);
752
753        mbuf.set_packet_len(100);
754        assert_eq!(mbuf.packet_len(), 100);
755    }
756
757    #[test]
758    fn test_mbuf_into_raw() {
759        let pool = Mempool::create("intoraw_pool", 128, 32, 2048, -1).unwrap();
760        let mbuf = pool.alloc().unwrap();
761        let raw = mbuf.into_raw();
762        assert!(!raw.is_null());
763
764        // Manually free to avoid leak (in real code DPDK would handle this)
765        unsafe {
766            dpdk_sys::rte_pktmbuf_free(raw);
767        }
768    }
769
770    #[test]
771    fn test_mbuf_from_raw_null() {
772        let result = unsafe { Mbuf::from_raw(std::ptr::null_mut()) };
773        assert!(result.is_none());
774    }
775}