Skip to main content

ibverbs_rs/ibverbs/memory/
remote_memory_region.rs

1use serde::{Deserialize, Serialize};
2
3/// A handle to a memory region on a **remote peer**.
4///
5/// This struct provides the necessary coordinates (Address, Length, RKey) to perform
6/// One-Sided RDMA operations (Read/Write) against a remote node.
7///
8/// # The "Contiguous" Constraint
9///
10/// Unlike local operations which support Scatter/Gather (stitching fragmented memory together),
11/// **remote operations are strictly contiguous**.
12///
13/// * **Targeting** — You specify a single starting address and a total length.
14/// * **Behavior** — The RDMA hardware reads or writes a continuous stream of bytes starting
15///   at that virtual address.
16///
17/// If you need to write to multiple non-contiguous buffers on a remote peer, you must issue
18/// multiple distinct RDMA Write operations.
19///
20/// # Safety and Responsibility
21///
22/// As discussed in the [memory module](crate::ibverbs::memory), remote memory safety cannot
23/// be enforced by the Rust compiler.
24///
25/// * **Local Safety** — **Safe**. Even if this handle points to invalid memory, issuing an
26///   operation using it will only result in an error (or success), but will never corrupt
27///   *local* process memory.
28/// * **Remote Safety** — **Unsafe**. If you write to a `RemoteMemoryRegion` that has been
29///   deallocated on the remote peer, the remote NIC will unknowingly overwrite that memory.
30///   **This causes Undefined Behavior on the remote peer.**
31#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
32pub struct RemoteMemoryRegion {
33    addr: u64,
34    length: usize,
35    rkey: u32,
36}
37
38impl RemoteMemoryRegion {
39    /// Creates a new `RemoteMemoryRegion` from its raw components.
40    ///
41    /// This is typically done after receiving these values from a remote peer via an
42    /// out-of-band communication channel (like a TCP socket or UD message).
43    pub fn new(addr: u64, length: usize, rkey: u32) -> Self {
44        Self { addr, length, rkey }
45    }
46
47    /// Returns the starting virtual address of the remote memory.
48    pub fn address(&self) -> u64 {
49        self.addr
50    }
51
52    /// Returns the length of the remote memory region.
53    ///
54    /// **Note**: This value is stored for client-side bounds checking and convenience.
55    /// The actual hardware enforcement depends on how the memory was registered on the remote peer.
56    pub fn length(&self) -> usize {
57        self.length
58    }
59
60    /// Returns the Remote Key (rkey) authorizing access to this memory.
61    pub fn rkey(&self) -> u32 {
62        self.rkey
63    }
64
65    /// Creates a generic sub-region derived from this one.
66    ///
67    /// This acts as a handle to a specific slice of the remote memory, starting at `offset`
68    /// bytes from the base address.
69    ///
70    /// # Returns
71    ///
72    /// * `Some(RemoteMemoryRegion)` — If `offset <= self.length`. The new length is `self.length - offset`.
73    /// * `None` — If `offset > self.length`.
74    pub fn sub_region(&self, offset: usize) -> Option<RemoteMemoryRegion> {
75        if offset > self.length {
76            return None;
77        }
78
79        Some(RemoteMemoryRegion {
80            addr: self.addr.checked_add(offset.try_into().ok()?)?,
81            length: self.length - offset,
82            rkey: self.rkey,
83        })
84    }
85
86    /// Same as [`sub_region`](Self::sub_region) but without client-side bounds checking.
87    ///
88    /// # Safety
89    ///
90    /// This is safe from a Rust memory model perspective **locally**. If the calculated address/length
91    /// falls outside the actual bounds registered on the remote peer, the RDMA hardware
92    /// will reject the operation with a **Remote Access Error**.
93    pub fn sub_region_unchecked(&self, offset: usize) -> RemoteMemoryRegion {
94        RemoteMemoryRegion {
95            addr: self.addr + offset as u64,
96            length: self.length - offset,
97            rkey: self.rkey,
98        }
99    }
100}
101
102/// Creates a [`RemoteMemoryRegion`] pointing to the N-th element of a remote array.
103///
104/// Assumes `mr` points to the start of a remote array of type `T`. Returns
105/// `Some(RemoteMemoryRegion)` for the element at `index`, or `None` if the
106/// byte offset overflows or falls outside the region's bounds.
107///
108/// # Returns
109///
110/// * `Some(RemoteMemoryRegion)` — A handle to the `index`-th element.
111/// * `None` — If the byte offset overflows, exceeds the region length,
112///   or the resulting address overflows.
113///
114/// # Example
115///
116/// ```
117/// use ibverbs_rs::ibverbs::memory::RemoteMemoryRegion;
118/// use ibverbs_rs::remote_array_field;
119///
120/// // Remote memory contains: [u64; 10]
121/// let remote_mr = RemoteMemoryRegion::new(0x1000, 80, 0xABCD);
122///
123/// // Get a handle to the 5th element (index 4)
124/// let elem_mr = remote_array_field!(remote_mr, u64, 4_usize).unwrap();
125/// assert_eq!(elem_mr.address(), 0x1020);
126/// ```
127#[macro_export]
128macro_rules! remote_array_field {
129    ($mr:expr, $T:ty, $index:expr) => {{
130        let type_size = std::mem::size_of::<$T>();
131        match ($index).checked_mul(type_size) {
132            Some(offset) => $mr.sub_region(offset),
133            None => None,
134        }
135    }};
136}
137
138/// Unchecked version of [`remote_array_field!`].
139///
140/// Skips bounds checking against the region length, but the RDMA hardware will
141/// reject out-of-bounds operations with a Remote Access Error.
142#[macro_export]
143macro_rules! remote_array_field_unchecked {
144    ($mr:expr, $T:ty, $index:expr) => {{
145        let type_size = std::mem::size_of::<$T>();
146        let offset = $index * type_size;
147        $mr.sub_region_unchecked(offset)
148    }};
149}
150
151/// Creates a [`RemoteMemoryRegion`] pointing to a specific field of a remote struct.
152///
153/// Assumes `mr` points to the start of a remote `Struct`. Uses `offset_of!` to
154/// compute the field's byte offset and returns a sub-region handle.
155///
156/// # Returns
157///
158/// * `Some(RemoteMemoryRegion)` — A handle to the specified field.
159/// * `None` — If the field offset exceeds the region length or the resulting
160///   address overflows.
161///
162/// # Example
163///
164/// ```
165/// use ibverbs_rs::ibverbs::memory::RemoteMemoryRegion;
166/// use ibverbs_rs::remote_struct_field;
167///
168/// #[repr(C)]
169/// struct Packet {
170///     header: u32,
171///     payload: [u8; 1024],
172/// }
173///
174/// // Remote memory contains a `Packet`
175/// let remote_mr = RemoteMemoryRegion::new(0x1000, 1028, 0xABCD);
176///
177/// // Get a handle to the 'payload' field
178/// let payload_mr = remote_struct_field!(remote_mr, Packet::payload).unwrap();
179/// assert_eq!(payload_mr.address(), 0x1004);
180/// ```
181#[macro_export]
182macro_rules! remote_struct_field {
183    ($mr:expr, $Struct:ident :: $field:ident) => {{
184        let offset = std::mem::offset_of!($Struct, $field);
185        $mr.sub_region(offset)
186    }};
187}
188
189/// Unchecked version of [`remote_struct_field!`].
190///
191/// Skips bounds checking against the region length, but the RDMA hardware will
192/// reject out-of-bounds operations with a Remote Access Error.
193#[macro_export]
194macro_rules! remote_struct_field_unchecked {
195    ($mr:expr, $Struct:ident :: $field:ident) => {{
196        let offset = std::mem::offset_of!($Struct, $field);
197        $mr.sub_region_unchecked(offset)
198    }};
199}
200
201/// Creates a [`RemoteMemoryRegion`] pointing to a specific field within an element of a remote array.
202///
203/// Assumes `mr` points to a remote array of `Struct`. Combines array indexing with
204/// field access: computes `index * size_of::<Struct>() + offset_of!(Struct, field)`.
205///
206/// # Returns
207///
208/// * `Some(RemoteMemoryRegion)` — A handle to the specified field within the `index`-th element.
209/// * `None` — If the byte offset computation overflows, exceeds the region length,
210///   or the resulting address overflows.
211///
212/// # Example
213///
214/// ```
215/// use ibverbs_rs::ibverbs::memory::RemoteMemoryRegion;
216/// use ibverbs_rs::remote_struct_array_field;
217///
218/// #[repr(C)]
219/// struct Node {
220///     id: u32,
221///     data: u64,
222/// }
223///
224/// // Remote memory contains: [Node; 5]
225/// let remote_mr = RemoteMemoryRegion::new(0x1000, 60, 0xABCD);
226///
227/// // Get a handle to the 'data' field of the 3rd Node (index 2)
228/// let data_mr = remote_struct_array_field!(remote_mr, Node, 2_usize, data).unwrap();
229/// ```
230#[macro_export]
231macro_rules! remote_struct_array_field {
232    ($mr:expr, $Struct:ident, $index:expr, $field:ident) => {{
233        let struct_size = std::mem::size_of::<$Struct>();
234        let field_offset = std::mem::offset_of!($Struct, $field);
235        match ($index)
236            .checked_mul(struct_size)
237            .and_then(|o| o.checked_add(field_offset))
238        {
239            Some(total_offset) => $mr.sub_region(total_offset),
240            None => None,
241        }
242    }};
243}
244
245/// Unchecked version of [`remote_struct_array_field!`].
246///
247/// Skips bounds checking against the region length, but the RDMA hardware will
248/// reject out-of-bounds operations with a Remote Access Error.
249#[macro_export]
250macro_rules! remote_struct_array_field_unchecked {
251    ($mr:expr, $Struct:ident, $index:expr, $field:ident) => {{
252        let struct_size = std::mem::size_of::<$Struct>();
253        let field_offset = std::mem::offset_of!($Struct, $field);
254        let total_offset = ($index * struct_size) + field_offset;
255        $mr.sub_region_unchecked(total_offset)
256    }};
257}
258
259#[cfg(test)]
260mod tests {
261    use super::*;
262
263    #[test]
264    fn new_stores_fields() {
265        let rmr = RemoteMemoryRegion::new(0x1000, 4096, 0xABCD);
266        assert_eq!(rmr.address(), 0x1000);
267        assert_eq!(rmr.length(), 4096);
268        assert_eq!(rmr.rkey(), 0xABCD);
269    }
270
271    #[test]
272    fn zero_length_region() {
273        let rmr = RemoteMemoryRegion::new(0x2000, 0, 1);
274        assert_eq!(rmr.length(), 0);
275    }
276
277    #[test]
278    fn sub_region_at_zero_offset() {
279        let rmr = RemoteMemoryRegion::new(0x1000, 100, 42);
280        let sub = rmr.sub_region(0).unwrap();
281        assert_eq!(sub.address(), 0x1000);
282        assert_eq!(sub.length(), 100);
283        assert_eq!(sub.rkey(), 42);
284    }
285
286    #[test]
287    fn sub_region_at_middle() {
288        let rmr = RemoteMemoryRegion::new(0x1000, 100, 42);
289        let sub = rmr.sub_region(40).unwrap();
290        assert_eq!(sub.address(), 0x1028);
291        assert_eq!(sub.length(), 60);
292    }
293
294    #[test]
295    fn sub_region_at_exact_end() {
296        let rmr = RemoteMemoryRegion::new(0x1000, 100, 42);
297        let sub = rmr.sub_region(100).unwrap();
298        assert_eq!(sub.address(), 0x1064);
299        assert_eq!(sub.length(), 0);
300    }
301
302    #[test]
303    fn sub_region_beyond_end_returns_none() {
304        let rmr = RemoteMemoryRegion::new(0x1000, 100, 42);
305        assert!(rmr.sub_region(101).is_none());
306    }
307
308    #[test]
309    fn sub_region_address_overflow_returns_none() {
310        let rmr = RemoteMemoryRegion::new(u64::MAX, 100, 42);
311        // offset 1 would overflow the u64 address
312        assert!(rmr.sub_region(1).is_none());
313    }
314
315    #[test]
316    fn sub_region_large_offset_returns_none() {
317        let rmr = RemoteMemoryRegion::new(0x1000, usize::MAX, 42);
318        // address addition will overflow
319        assert!(rmr.sub_region(usize::MAX).is_none());
320    }
321
322    #[test]
323    fn sub_region_unchecked_at_middle() {
324        let rmr = RemoteMemoryRegion::new(0x1000, 100, 42);
325        let sub = rmr.sub_region_unchecked(40);
326        assert_eq!(sub.address(), 0x1028);
327        assert_eq!(sub.length(), 60);
328        assert_eq!(sub.rkey(), 42);
329    }
330
331    #[test]
332    fn remote_array_field_index_zero() {
333        let rmr = RemoteMemoryRegion::new(0x1000, 80, 0xABCD);
334        let elem = remote_array_field!(rmr, u64, 0_usize).unwrap();
335        assert_eq!(elem.address(), 0x1000);
336        assert_eq!(elem.length(), 80);
337    }
338
339    #[test]
340    fn remote_array_field_index_mid() {
341        let rmr = RemoteMemoryRegion::new(0x1000, 80, 0xABCD);
342        let elem = remote_array_field!(rmr, u64, 4_usize).unwrap();
343        assert_eq!(elem.address(), 0x1020); // 0x1000 + 4*8
344        assert_eq!(elem.length(), 80 - 32);
345    }
346
347    #[test]
348    fn remote_array_field_out_of_bounds() {
349        let rmr = RemoteMemoryRegion::new(0x1000, 16, 0xABCD);
350        // 3 * 8 = 24 > 16
351        assert!(remote_array_field!(rmr, u64, 3_usize).is_none());
352    }
353
354    #[test]
355    fn remote_array_field_index_overflow() {
356        let rmr = RemoteMemoryRegion::new(0x1000, 1024, 0xABCD);
357        // usize::MAX * 8 overflows in checked_mul
358        assert!(remote_array_field!(rmr, u64, usize::MAX).is_none());
359    }
360
361    #[test]
362    fn remote_array_field_unchecked_index_mid() {
363        let rmr = RemoteMemoryRegion::new(0x1000, 80, 0xABCD);
364        let elem = remote_array_field_unchecked!(rmr, u64, 4_usize);
365        assert_eq!(elem.address(), 0x1020);
366    }
367
368    #[repr(C)]
369    struct TestPacket {
370        header: u32,
371        payload: [u8; 1024],
372    }
373
374    #[test]
375    fn remote_struct_field_header() {
376        let rmr = RemoteMemoryRegion::new(0x1000, 1028, 0xABCD);
377        let field = remote_struct_field!(rmr, TestPacket::header).unwrap();
378        assert_eq!(field.address(), 0x1000);
379    }
380
381    #[test]
382    fn remote_struct_field_payload() {
383        let rmr = RemoteMemoryRegion::new(0x1000, 1028, 0xABCD);
384        let field = remote_struct_field!(rmr, TestPacket::payload).unwrap();
385        assert_eq!(field.address(), 0x1004);
386    }
387
388    #[test]
389    fn remote_struct_field_out_of_bounds() {
390        // Region too small to contain the struct
391        let rmr = RemoteMemoryRegion::new(0x1000, 2, 0xABCD);
392        assert!(remote_struct_field!(rmr, TestPacket::payload).is_none());
393    }
394
395    #[test]
396    fn remote_struct_field_unchecked_payload() {
397        let rmr = RemoteMemoryRegion::new(0x1000, 1028, 0xABCD);
398        let field = remote_struct_field_unchecked!(rmr, TestPacket::payload);
399        assert_eq!(field.address(), 0x1004);
400    }
401
402    #[repr(C)]
403    struct TestNode {
404        id: u32,
405        data: u64,
406    }
407
408    #[test]
409    fn remote_struct_array_field_first_element() {
410        let rmr = RemoteMemoryRegion::new(0x1000, 240, 0xABCD);
411        let field = remote_struct_array_field!(rmr, TestNode, 0_usize, data).unwrap();
412        let expected_offset = std::mem::offset_of!(TestNode, data);
413        assert_eq!(field.address(), 0x1000 + expected_offset as u64);
414    }
415
416    #[test]
417    fn remote_struct_array_field_nth_element() {
418        let rmr = RemoteMemoryRegion::new(0x1000, 240, 0xABCD);
419        let field = remote_struct_array_field!(rmr, TestNode, 2_usize, data).unwrap();
420        let node_size = std::mem::size_of::<TestNode>();
421        let field_offset = std::mem::offset_of!(TestNode, data);
422        let expected = 0x1000u64 + (2 * node_size + field_offset) as u64;
423        assert_eq!(field.address(), expected);
424    }
425
426    #[test]
427    fn remote_struct_array_field_overflow() {
428        let rmr = RemoteMemoryRegion::new(0x1000, 240, 0xABCD);
429        assert!(remote_struct_array_field!(rmr, TestNode, usize::MAX, data).is_none());
430    }
431
432    #[test]
433    fn remote_struct_array_field_unchecked_nth_element() {
434        let rmr = RemoteMemoryRegion::new(0x1000, 240, 0xABCD);
435        let field = remote_struct_array_field_unchecked!(rmr, TestNode, 2_usize, data);
436        let node_size = std::mem::size_of::<TestNode>();
437        let field_offset = std::mem::offset_of!(TestNode, data);
438        let expected = 0x1000u64 + (2 * node_size + field_offset) as u64;
439        assert_eq!(field.address(), expected);
440    }
441
442    #[test]
443    fn serde_round_trip() {
444        let rmr = RemoteMemoryRegion::new(0xDEAD_BEEF, 9999, 0x42);
445        let json = serde_json::to_string(&rmr).unwrap();
446        let restored: RemoteMemoryRegion = serde_json::from_str(&json).unwrap();
447        assert_eq!(restored.address(), rmr.address());
448        assert_eq!(restored.length(), rmr.length());
449        assert_eq!(restored.rkey(), rmr.rkey());
450    }
451}