Skip to main content

mesh_sieve/algs/
wire.rs

1//! Fixed, versioned, little-endian wire types for completion paths.
2
3use bytemuck::{Pod, Zeroable};
4use std::mem::{align_of, size_of};
5
6pub fn cast_slice<T: Pod>(v: &[T]) -> &[u8] {
7    bytemuck::cast_slice(v)
8}
9
10pub fn cast_slice_mut<T: Pod>(v: &mut [T]) -> &mut [u8] {
11    bytemuck::cast_slice_mut(v)
12}
13
14pub fn cast_slice_from<T: Pod>(v: &[u8]) -> &[T] {
15    bytemuck::cast_slice(v)
16}
17
18pub fn cast_slice_from_mut<T: Pod>(v: &mut [u8]) -> &mut [T] {
19    bytemuck::cast_slice_mut(v)
20}
21
22pub fn expect_exact_len(actual: usize, expected: usize) -> Result<(), String> {
23    if actual == expected {
24        Ok(())
25    } else {
26        Err(format!("expected {expected} bytes, got {actual}"))
27    }
28}
29
30#[repr(transparent)]
31#[derive(Copy, Clone, Pod, Zeroable)]
32pub struct WireCountU32(pub u32);
33
34pub trait WirePoint: Copy {
35    fn to_wire(self) -> u64;
36    fn from_wire(w: u64) -> Self;
37}
38
39/// Bump when the layout or semantics change in incompatible ways.
40pub const WIRE_VERSION: u16 = 2;
41
42/// All multi-byte integers in these structs are **little-endian** on the wire.
43/// We store them pre-LE with `.to_le()` and decode with `.from_le()`.
44
45// ===== Common records ======================================================
46
47#[repr(C)]
48#[derive(Copy, Clone, Pod, Zeroable)]
49pub struct WireHdr {
50    pub version_le: u16,  // = WIRE_VERSION.to_le()
51    pub kind_le: u16,     // 1 = Cone, 2 = Support, etc.
52    pub reserved_le: u32, // future use; keep zero
53}
54
55impl WireHdr {
56    pub fn new(kind: u16) -> Self {
57        Self {
58            version_le: WIRE_VERSION.to_le(),
59            kind_le: kind.to_le(),
60            reserved_le: 0,
61        }
62    }
63    pub fn kind(&self) -> u16 {
64        u16::from_le(self.kind_le)
65    }
66    pub fn version(&self) -> u16 {
67        u16::from_le(self.version_le)
68    }
69}
70
71#[repr(C)]
72#[derive(Copy, Clone, Pod, Zeroable)]
73pub struct WireCount {
74    pub n_le: u32, // count of following records
75}
76impl WireCount {
77    pub fn new(n: usize) -> Self {
78        Self {
79            n_le: (n as u32).to_le(),
80        }
81    }
82    pub fn get(&self) -> usize {
83        u32::from_le(self.n_le) as usize
84    }
85}
86
87/// A point id (u64) carried on the wire.
88#[repr(C)]
89#[derive(Copy, Clone, Pod, Zeroable)]
90pub struct WirePointRepr {
91    pub id_le: u64,
92}
93impl WirePointRepr {
94    pub fn of(id: u64) -> Self {
95        Self { id_le: id.to_le() }
96    }
97    pub fn get(&self) -> u64 {
98        u64::from_le(self.id_le)
99    }
100}
101
102/// An adjacency pair (src, dst) — used in closure/support replies.
103#[repr(C)]
104#[derive(Copy, Clone, Pod, Zeroable)]
105pub struct WireAdj {
106    pub src_le: u64,
107    pub dst_le: u64,
108}
109impl WireAdj {
110    pub fn new(src: u64, dst: u64) -> Self {
111        Self {
112            src_le: src.to_le(),
113            dst_le: dst.to_le(),
114        }
115    }
116    pub fn src(&self) -> u64 {
117        u64::from_le(self.src_le)
118    }
119    pub fn dst(&self) -> u64 {
120        u64::from_le(self.dst_le)
121    }
122}
123
124// ===== Sieve completion ====================================================
125
126/// Minimal arrow payload `(src, dst)` in receiver-local IDs.
127#[repr(C)]
128#[derive(Copy, Clone, Pod, Zeroable)]
129pub struct WireArrow {
130    pub src_le: u64,
131    pub dst_le: u64,
132}
133impl WireArrow {
134    pub fn new(src: u64, dst: u64) -> Self {
135        Self {
136            src_le: src.to_le(),
137            dst_le: dst.to_le(),
138        }
139    }
140    pub fn src(&self) -> u64 {
141        u64::from_le(self.src_le)
142    }
143    pub fn dst(&self) -> u64 {
144        u64::from_le(self.dst_le)
145    }
146}
147
148/// Fixed-width oriented sieve relation carrying source, destination, payload,
149/// and orientation semantics.  Payload and orientation are intentionally POD
150/// so completion never drops relation data while crossing a rank boundary.
151#[repr(C)]
152#[derive(Copy, Clone, Zeroable)]
153pub struct WireOrientedArrow<Pay, Orient>
154where
155    Pay: Copy + Pod + Zeroable,
156    Orient: Copy + Pod + Zeroable,
157{
158    pub src_le: u64,
159    pub dst_le: u64,
160    pub payload: Pay,
161    pub orientation: Orient,
162}
163
164impl<Pay, Orient> WireOrientedArrow<Pay, Orient>
165where
166    Pay: Copy + Pod + Zeroable,
167    Orient: Copy + Pod + Zeroable,
168{
169    pub fn new(src: u64, dst: u64, payload: Pay, orientation: Orient) -> Self {
170        Self {
171            src_le: src.to_le(),
172            dst_le: dst.to_le(),
173            payload,
174            orientation,
175        }
176    }
177    pub fn src(&self) -> u64 {
178        u64::from_le(self.src_le)
179    }
180    pub fn dst(&self) -> u64 {
181        u64::from_le(self.dst_le)
182    }
183}
184
185unsafe impl<Pay, Orient> Pod for WireOrientedArrow<Pay, Orient>
186where
187    Pay: Copy + Pod + Zeroable,
188    Orient: Copy + Pod + Zeroable,
189{
190}
191
192/// Legacy arrow triple used by sieve completion.
193/// NOTE: `rank_le` is u32 (never usize) on the wire.
194#[repr(C)]
195#[derive(Copy, Clone, Pod, Zeroable)]
196pub struct WireArrowTriple {
197    pub src_le: u64,
198    pub dst_le: u64,
199    pub remote_point_le: u64,
200    pub rank_le: u32, // remote rank
201    pub _pad: u32,    // pad to 8-byte alignment (explicit)
202}
203impl WireArrowTriple {
204    pub const SIZE: usize = 32; // 3*8 + 4 + 4
205    pub fn new(src: u64, dst: u64, remote: u64, rank: u32) -> Self {
206        Self {
207            src_le: src.to_le(),
208            dst_le: dst.to_le(),
209            remote_point_le: remote.to_le(),
210            rank_le: rank.to_le(),
211            _pad: 0,
212        }
213    }
214    pub fn decode(&self) -> (u64, u64, u64, u32) {
215        (
216            u64::from_le(self.src_le),
217            u64::from_le(self.dst_le),
218            u64::from_le(self.remote_point_le),
219            u32::from_le(self.rank_le),
220        )
221    }
222}
223
224// ===== Stack completion (base, cap, payload) ==============================
225
226/// If your payload is "opaque bytes", model it as a fixed-size byte array.
227/// If it’s numeric, define a dedicated, fixed-width structure for it.
228pub const WIRE_PAYLOAD_MAX: usize = 16; // example cap; set to your actual need
229
230#[repr(C)]
231#[derive(Copy, Clone, Pod, Zeroable)]
232pub struct WireStackTriple {
233    pub base_le: u64,
234    pub cap_le: u64,
235    /// Opaque payload bytes. Producer and consumer must agree on its meaning.
236    pub pay: [u8; WIRE_PAYLOAD_MAX],
237}
238impl WireStackTriple {
239    pub fn new(base: u64, cap: u64, pay: &[u8]) -> Self {
240        let mut buf = [0u8; WIRE_PAYLOAD_MAX];
241        let n = pay.len().min(WIRE_PAYLOAD_MAX);
242        buf[..n].copy_from_slice(&pay[..n]);
243        Self {
244            base_le: base.to_le(),
245            cap_le: cap.to_le(),
246            pay: buf,
247        }
248    }
249    pub fn base(&self) -> u64 {
250        u64::from_le(self.base_le)
251    }
252    pub fn cap(&self) -> u64 {
253        u64::from_le(self.cap_le)
254    }
255}
256
257// ===== Compile-time sanity checks =========================================
258
259const _: () = {
260    // Pod/Zeroable ensures no padding contains uninit when cast to bytes.
261    assert!(size_of::<WireHdr>() == 8);
262    assert!(size_of::<WireCount>() == 4);
263    assert!(size_of::<WirePointRepr>() == 8);
264    assert!(size_of::<WireAdj>() == 16);
265    assert!(size_of::<WireArrow>() == 16);
266    assert!(size_of::<WireArrowTriple>() == WireArrowTriple::SIZE);
267    assert!(align_of::<WireArrowTriple>() == 8);
268};
269
270impl WirePoint for crate::topology::point::PointId {
271    #[inline]
272    fn to_wire(self) -> u64 {
273        self.get()
274    }
275    #[inline]
276    fn from_wire(w: u64) -> Self {
277        crate::topology::point::PointId::new(w).expect("invalid PointId on wire")
278    }
279}
280
281#[cfg(test)]
282mod tests {
283    use super::*;
284    use bytemuck::{cast_slice, cast_slice_mut};
285
286    #[test]
287    fn roundtrip_adj() {
288        let v = vec![WireAdj::new(1, 2), WireAdj::new(3, 4)];
289        let bytes: Vec<u8> = cast_slice(&v).to_vec();
290        let mut out = vec![WireAdj::zeroed(); v.len()];
291        cast_slice_mut(&mut out).copy_from_slice(&bytes);
292        assert_eq!(out[0].src(), 1);
293        assert_eq!(out[1].dst(), 4);
294    }
295
296    #[test]
297    fn roundtrip_wire_arrow() {
298        let v = vec![WireArrow::new(10, 20), WireArrow::new(30, 40)];
299        let bytes: Vec<u8> = cast_slice(&v).to_vec();
300        let mut out = vec![WireArrow::zeroed(); v.len()];
301        cast_slice_mut(&mut out).copy_from_slice(&bytes);
302        assert_eq!(out[0].src(), 10);
303        assert_eq!(out[1].dst(), 40);
304    }
305
306    #[test]
307    fn roundtrip_arrow() {
308        let t = WireArrowTriple::new(1, 2, 3, 4);
309        let bytes: Vec<u8> = cast_slice(&[t]).to_vec();
310        let mut out = vec![WireArrowTriple::zeroed(); 1];
311        cast_slice_mut(&mut out).copy_from_slice(&bytes);
312        assert_eq!(out[0].decode(), (1, 2, 3, 4));
313        assert_eq!(
314            WireArrowTriple::SIZE,
315            std::mem::size_of::<WireArrowTriple>()
316        );
317    }
318
319    #[test]
320    fn roundtrip_stack() {
321        let pay = [1u8, 2, 3, 4];
322        let t = WireStackTriple::new(10, 20, &pay);
323        let bytes: Vec<u8> = cast_slice(&[t]).to_vec();
324        let mut out = vec![WireStackTriple::zeroed(); 1];
325        cast_slice_mut(&mut out).copy_from_slice(&bytes);
326        assert_eq!(out[0].base(), 10);
327        assert_eq!(out[0].cap(), 20);
328        assert_eq!(&out[0].pay[..4], &pay);
329    }
330
331    #[test]
332    fn version_guard() {
333        let hdr = WireHdr::new(1);
334        assert_eq!(hdr.version(), WIRE_VERSION);
335    }
336}