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}