Skip to main content

palladium_actor/
envelope.rs

1use crate::path::AddrHash;
2
3// ── Envelope ──────────────────────────────────────────────────────────────────
4
5/// Fixed-size `#[repr(C)]` message header used for routing and wire framing.
6///
7/// `SIZE` bytes on the wire: the envelope is always serialized as a
8/// little-endian byte array of exactly `Envelope::SIZE` bytes, followed by
9/// `payload_len` bytes of payload.
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11#[repr(C)]
12pub struct Envelope {
13    pub message_id: u128,
14    pub source: AddrHash,
15    pub destination: AddrHash,
16    pub type_tag: u64,
17    pub payload_len: u32,
18    pub flags: u32,
19    pub correlation_id: u64,
20}
21
22impl Envelope {
23    /// In-memory and wire size of the envelope.
24    ///
25    /// The struct fields occupy 72 bytes of meaningful data; `#[repr(C)]`
26    /// pads to the next multiple of 16 (the alignment of `u128`) = 80 bytes.
27    /// The final 8 bytes are padding zeros in the wire format.
28    pub const SIZE: usize = 80;
29    /// Compact wire size for remote frames (destination, source, correlation, priority).
30    pub const WIRE_SIZE: usize = 41;
31
32    pub const FLAG_RESPONSE: u32 = 1 << 0;
33    pub const FLAG_PRIORITY_MASK: u32 = 0b11 << 1;
34
35    pub fn new(source: AddrHash, destination: AddrHash, type_tag: u64, payload_len: u32) -> Self {
36        Self {
37            message_id: 0,
38            source,
39            destination,
40            type_tag,
41            payload_len,
42            flags: 0,
43            correlation_id: 0,
44        }
45    }
46
47    /// Construct a response envelope by swapping source/destination and setting
48    /// the response flag and correlation id.
49    pub fn response(&self, payload_len: u32) -> Self {
50        let mut e = Self::new(self.destination, self.source, self.type_tag, payload_len);
51        e.message_id = self.message_id;
52        e.correlation_id = self.message_id as u64;
53        e.flags = Self::FLAG_RESPONSE | (self.flags & Self::FLAG_PRIORITY_MASK);
54        e
55    }
56
57    pub fn with_priority(mut self, priority: u8) -> Self {
58        let p = (priority.min(3) as u32) << 1;
59        self.flags = (self.flags & !Self::FLAG_PRIORITY_MASK) | p;
60        self
61    }
62
63    pub fn priority(&self) -> u8 {
64        ((self.flags & Self::FLAG_PRIORITY_MASK) >> 1) as u8
65    }
66
67    pub fn is_response(&self) -> bool {
68        (self.flags & Self::FLAG_RESPONSE) != 0
69    }
70
71    /// Serialize to a fixed `[u8; SIZE]` array in little-endian byte order.
72    ///
73    /// Bytes 72–79 are padding zeros (required by `#[repr(C)]` alignment).
74    pub fn to_bytes(&self) -> [u8; Self::SIZE] {
75        let mut buf = [0u8; Self::SIZE];
76        buf[0..16].copy_from_slice(&self.message_id.to_le_bytes());
77        buf[16..32].copy_from_slice(&self.source.0.to_le_bytes());
78        buf[32..48].copy_from_slice(&self.destination.0.to_le_bytes());
79        buf[48..56].copy_from_slice(&self.type_tag.to_le_bytes());
80        buf[56..60].copy_from_slice(&self.payload_len.to_le_bytes());
81        buf[60..64].copy_from_slice(&self.flags.to_le_bytes());
82        buf[64..72].copy_from_slice(&self.correlation_id.to_le_bytes());
83        // buf[72..80] remains as zero padding.
84        buf
85    }
86
87    /// Deserialize from a fixed `[u8; SIZE]` array.  Bytes 72–79 are ignored.
88    pub fn from_bytes(buf: &[u8; Self::SIZE]) -> Self {
89        Self {
90            message_id: u128::from_le_bytes(buf[0..16].try_into().expect("16 bytes")),
91            source: AddrHash(u128::from_le_bytes(
92                buf[16..32].try_into().expect("16 bytes"),
93            )),
94            destination: AddrHash(u128::from_le_bytes(
95                buf[32..48].try_into().expect("16 bytes"),
96            )),
97            type_tag: u64::from_le_bytes(buf[48..56].try_into().expect("8 bytes")),
98            payload_len: u32::from_le_bytes(buf[56..60].try_into().expect("4 bytes")),
99            flags: u32::from_le_bytes(buf[60..64].try_into().expect("4 bytes")),
100            correlation_id: u64::from_le_bytes(buf[64..72].try_into().expect("8 bytes")),
101        }
102    }
103
104    /// Serialize to a compact wire format (41 bytes) used for remote frames.
105    ///
106    /// Layout (little-endian):
107    /// [0..16]  destination (u128)
108    /// [16..32] source (u128)
109    /// [32..40] correlation_id (u64)
110    /// [40]     priority (u8)
111    pub fn to_wire(&self) -> [u8; Self::WIRE_SIZE] {
112        let mut buf = [0u8; Self::WIRE_SIZE];
113        buf[0..16].copy_from_slice(&self.destination.0.to_le_bytes());
114        buf[16..32].copy_from_slice(&self.source.0.to_le_bytes());
115        buf[32..40].copy_from_slice(&self.correlation_id.to_le_bytes());
116        buf[40] = self.priority();
117        buf
118    }
119
120    /// Deserialize from a compact wire format (41 bytes).
121    ///
122    /// Fields not present on the wire are set to defaults:
123    /// `message_id = 0`, `type_tag = 0`, `payload_len = 0`.
124    pub fn from_wire(buf: &[u8; Self::WIRE_SIZE]) -> Self {
125        let mut env = Self {
126            message_id: 0,
127            source: AddrHash(u128::from_le_bytes(
128                buf[16..32].try_into().expect("16 bytes"),
129            )),
130            destination: AddrHash(u128::from_le_bytes(
131                buf[0..16].try_into().expect("16 bytes"),
132            )),
133            type_tag: 0,
134            payload_len: 0,
135            flags: 0,
136            correlation_id: u64::from_le_bytes(buf[32..40].try_into().expect("8 bytes")),
137        };
138        env = env.with_priority(buf[40]);
139        env
140    }
141}