Skip to main content

ibverbs_rs/ibverbs/memory/
scatter_gather_element.rs

1use crate::ibverbs::memory::MemoryRegion;
2use ibverbs_sys::ibv_sge;
3use std::marker::PhantomData;
4use thiserror::Error;
5
6/// A **Gather Element** for outgoing RDMA operations.
7///
8/// # Purpose
9///
10/// A `GatherElement` represents a slice of registered memory that the NIC will **read from**
11/// to send over the network. The NIC "gathers" data from a list of these elements into a single
12/// continuous stream.
13///
14/// # Usage
15///
16/// Use this for operations where the local node sends data:
17/// * **Send Requests** — The payload to send.
18/// * **RDMA Write** — The local source buffer.
19///
20/// # Safety and Lifetimes
21///
22/// This struct enforces Rust's borrowing rules at the hardware level:
23///
24/// * **Immutable Access** — It holds a `&'a [u8]` to the data, allowing shared access but preventing mutation
25///   while the operation is pending.
26/// * **Liveness** — It holds a reference to the [`MemoryRegion`], ensuring the registration
27///   remains valid.
28///
29/// See the [memory module](crate::ibverbs::memory) for a detailed explanation of the safety architecture.
30#[derive(Copy, Clone, Debug)]
31#[repr(transparent)]
32pub struct GatherElement<'a> {
33    sge: ibv_sge,
34    // SAFETY INVARIANT: SGE cannot outlive the referenced data or the memory region
35    _mr_lifetime: PhantomData<&'a MemoryRegion>,
36    _data_lifetime: PhantomData<&'a [u8]>,
37}
38
39/// A **Scatter Element** for incoming RDMA operations.
40///
41/// # Purpose
42///
43/// A `ScatterElement` represents a slice of registered memory that the NIC will **write into**
44/// when receiving from the network. The NIC "scatters" the incoming stream across a list of
45/// these elements.
46///
47/// # Usage
48///
49/// Use this for operations where the local node receives data:
50/// * **Receive Requests** — The buffer to fill with incoming data.
51/// * **RDMA Read** — The local destination buffer.
52///
53/// # Safety and Lifetimes
54///
55/// This struct enforces Rust's borrowing rules at the hardware level:
56///
57/// * **Exclusive Access** — It holds a `&'a mut [u8]` to the data, ensuring no other part of the program
58///   can read or write this memory while the NIC is writing to it.
59/// * **Liveness** — It holds a reference to the [`MemoryRegion`], ensuring the registration
60///   remains valid.
61///
62/// See the [memory module](crate::ibverbs::memory) for a detailed explanation of the safety architecture.
63#[derive(Debug)]
64#[repr(transparent)]
65pub struct ScatterElement<'a> {
66    sge: ibv_sge,
67    // SAFETY INVARIANT: SGE cannot outlive the referenced data or the memory region
68    _mr_lifetime: PhantomData<&'a MemoryRegion>,
69    _data_lifetime: PhantomData<&'a mut [u8]>,
70}
71
72/// Errors that can occur when creating Scatter/Gather Elements.
73#[derive(Debug, Error)]
74pub enum ScatterGatherElementError {
75    #[error("maximum length of mr slice exceeded")]
76    SliceTooBig,
77    #[error("slice is not within the bounds of the mr")]
78    SliceNotWithinBounds,
79}
80
81impl<'a> GatherElement<'a> {
82    /// Creates a new gather element.
83    ///
84    /// In debug builds, this performs additional validation before constructing the element. In
85    /// optimized/release builds, these validations are not executed by default because they are
86    /// implemented using `debug_assert!`.
87    ///
88    /// # Panics
89    ///
90    /// Panics in debug builds if any of the following conditions are violated:
91    /// 1. The `data` slice is fully contained within the `mr`'s address range.
92    /// 2. The `data` length fits in a `u32` (hardware limit).
93    ///
94    /// For a version that always validates and returns an error instead of panicking, use
95    /// [`Self::new_checked`].
96    pub fn new(mr: &'a MemoryRegion, data: &'a [u8]) -> Self {
97        debug_assert!(data.len() <= u32::MAX as usize);
98        debug_assert!(mr.encloses_slice(data));
99        Self::new_unchecked(mr, data)
100    }
101
102    /// Creates a new Gather Element with bounds checking.
103    ///
104    /// # Checks
105    ///
106    /// This method validates that:
107    /// 1.  The `data` slice is fully contained within the `mr`'s address range.
108    /// 2.  The `data` length fits in a `u32` (hardware limit).
109    pub fn new_checked(
110        mr: &'a MemoryRegion,
111        data: &'a [u8],
112    ) -> Result<Self, ScatterGatherElementError> {
113        if data.len() > u32::MAX as usize {
114            return Err(ScatterGatherElementError::SliceTooBig);
115        }
116        if !mr.encloses_slice(data) {
117            return Err(ScatterGatherElementError::SliceNotWithinBounds);
118        }
119
120        Ok(Self::new_unchecked(mr, data))
121    }
122
123    /// Creates a new Gather Element **without** bounds checking.
124    ///
125    /// # Safety
126    ///
127    /// This method is safe to call from a Rust memory-safety perspective.
128    /// If the slice is outside the MR, the hardware will detect this
129    /// during RDMA operations and fail with a **Local Protection Error**.
130    ///
131    /// # Warning
132    ///
133    /// If `data.len()` exceeds `u32::MAX`, the length stored in the SGE will **silently wrap**.
134    /// Only the wrapped (truncated) number of bytes will be posted to the hardware.
135    /// Use [`new_checked`](Self::new_checked) to catch this at the cost of a bounds check.
136    #[allow(clippy::cast_possible_truncation)]
137    pub fn new_unchecked(mr: &'a MemoryRegion, data: &'a [u8]) -> Self {
138        Self {
139            sge: ibv_sge {
140                addr: data.as_ptr() as u64,
141                length: data.len() as u32,
142                lkey: mr.lkey(),
143            },
144            _mr_lifetime: PhantomData::<&'a MemoryRegion>,
145            _data_lifetime: PhantomData::<&'a [u8]>,
146        }
147    }
148}
149
150impl<'a> ScatterElement<'a> {
151    /// Creates a new scatter element.
152    ///
153    /// In debug builds, this performs additional validation before constructing the element. In
154    /// optimized/release builds, these validations are not executed by default because they are
155    /// implemented using `debug_assert!`.
156    ///
157    /// # Panics
158    ///
159    /// Panics in debug builds if any of the following conditions are violated:
160    /// 1. The `data` slice is fully contained within the `mr`'s address range.
161    /// 2. The `data` length fits in a `u32` (hardware limit).
162    ///
163    /// For a version that always validates and returns an error instead of panicking, use
164    /// [`Self::new_checked`].
165    pub fn new(mr: &'a MemoryRegion, data: &'a mut [u8]) -> Self {
166        debug_assert!(data.len() <= u32::MAX as usize);
167        debug_assert!(mr.encloses_slice(data));
168        Self::new_unchecked(mr, data)
169    }
170
171    /// Creates a new Scatter Element with bounds checking.
172    ///
173    /// # Checks
174    ///
175    /// This method validates that:
176    /// 1.  The `data` slice is fully contained within the `mr`'s address range.
177    /// 2.  The `data` length fits in a `u32`.
178    pub fn new_checked(
179        mr: &'a MemoryRegion,
180        data: &'a mut [u8],
181    ) -> Result<Self, ScatterGatherElementError> {
182        if data.len() > u32::MAX as usize {
183            return Err(ScatterGatherElementError::SliceTooBig);
184        }
185        if !mr.encloses_slice(data) {
186            return Err(ScatterGatherElementError::SliceNotWithinBounds);
187        }
188
189        Ok(Self::new_unchecked(mr, data))
190    }
191
192    /// Creates a new Scatter Element **without** bounds checking.
193    ///
194    /// # Safety
195    ///
196    /// This method is safe to call from a Rust memory-safety perspective.
197    /// If the slice is outside the MR, the hardware will detect this
198    /// during RDMA operations and fail with a **Local Protection Error**.
199    ///
200    /// # Warning
201    ///
202    /// If `data.len()` exceeds `u32::MAX`, the length stored in the SGE will **silently wrap**.
203    /// Only the wrapped (truncated) number of bytes will be posted to the hardware.
204    /// Use [`new_checked`](Self::new_checked) to catch this at the cost of a bounds check.
205    #[allow(clippy::cast_possible_truncation)]
206    pub fn new_unchecked(mr: &'a MemoryRegion, data: &'a mut [u8]) -> Self {
207        Self {
208            sge: ibv_sge {
209                addr: data.as_ptr() as u64,
210                length: data.len() as u32,
211                lkey: mr.lkey(),
212            },
213            _mr_lifetime: PhantomData::<&'a MemoryRegion>,
214            _data_lifetime: PhantomData::<&'a mut [u8]>,
215        }
216    }
217}