Skip to main content

byteflow/bytecode/
value.rs

1use std::fmt;
2use std::sync::Arc;
3
4use super::cap::CapId;
5
6/// Reserved Atomic Hop tag for monitor `DOWN` events (not an application tag).
7pub const TAG_SYS_DOWN: u16 = 0xFF01;
8/// Reserved Atomic Hop tag for linked-exit notices.
9pub const TAG_SYS_EXIT: u16 = 0xFF02;
10
11/// Envelope carried in mailboxes and registers (**Atomic Hop**).
12///
13/// `payload` is a [`Value`] behind [`Arc`] so hops can carry `Int`, `Str`,
14/// `Bytes`, or nested messages. Mailbox byte budgets charge
15/// [`Value::memory_size`] of the whole hop, including the payload tree.
16///
17/// # Security
18///
19/// - **`sender`**: FlowId stamped by the scheduler on bytecode `Send` / `Ask`
20///   and host [`crate::Runtime::send`] (`0` = [`crate::FlowId::HOST`])
21///   (invariant **S1**). Not a capability.
22/// - **`reply_cap`**: stable SEND-only [`CapId`] per `(recipient, sender)`
23///   pair (holder = recipient, reused across hops). Correlation is
24///   [`Self::request_id`]. [`CapId::NONE`] means “no reply grant”.
25///
26/// See `docs/security.md`.
27#[derive(Clone, Debug, PartialEq)]
28pub struct Message {
29    /// Authenticated origin FlowId (`0` = trusted host / non-flow).
30    pub sender: u64,
31    /// Capability granting **SEND** back to [`Self::sender`], or [`CapId::NONE`].
32    pub reply_cap: CapId,
33    /// Client correlation token; echoed on replies (`Ask` / **S2**).
34    pub request_id: u64,
35    /// Protocol discriminator (opaque to the VM).
36    pub tag: u16,
37    /// Application body (shared so hop clones are cheap).
38    pub payload: Arc<Value>,
39}
40
41impl Message {
42    /// Build an envelope. `sender` / `reply_cap` are placeholders until a
43    /// bytecode hop is authenticated by the scheduler.
44    pub fn new(sender: u64, request_id: u64, tag: u16, payload: impl Into<Value>) -> Self {
45        Self {
46            sender,
47            reply_cap: CapId::NONE,
48            request_id,
49            tag,
50            payload: Arc::new(payload.into()),
51        }
52    }
53
54    /// Outgoing hop from host Rust (`sender` / `reply_cap` filled on delivery).
55    pub fn request(request_id: u64, tag: u16, payload: impl Into<Value>) -> Self {
56        Self::new(0, request_id, tag, payload)
57    }
58
59    /// Reply envelope echoing `request_id` from a received hop (host path).
60    pub fn reply_to(req: &Self, tag: u16, payload: impl Into<Value>) -> Self {
61        Self::new(0, req.request_id, tag, payload)
62    }
63
64    /// Runtime lifecycle hop: monitor `DOWN` (`tag == `[`TAG_SYS_DOWN`]).
65    pub fn down(monitor: u64, target_flow: u64, reason: u64) -> Self {
66        Self::new(
67            target_flow,
68            monitor,
69            TAG_SYS_DOWN,
70            Value::Int(reason as i64),
71        )
72    }
73
74    /// Runtime lifecycle hop: Ask target exited (`tag == `[`TAG_SYS_EXIT`]).
75    pub fn linked_exit(target_flow: u64, reason: u64) -> Self {
76        Self::new(target_flow, 0, TAG_SYS_EXIT, Value::Int(reason as i64))
77    }
78
79    #[inline]
80    pub fn is_down(&self) -> bool {
81        self.tag == TAG_SYS_DOWN
82    }
83
84    #[inline]
85    pub fn is_exit(&self) -> bool {
86        self.tag == TAG_SYS_EXIT
87    }
88
89    /// Stamp origin FlowId and attach a reply capability (scheduler only).
90    #[inline]
91    pub(crate) fn authenticate(mut self, sender: u64, reply_cap: CapId) -> Self {
92        self.sender = sender;
93        self.reply_cap = reply_cap;
94        self
95    }
96
97    pub(crate) fn with_payload(mut self, payload: Value) -> Self {
98        self.payload = Arc::new(payload);
99        self
100    }
101}
102
103impl fmt::Display for Message {
104    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
105        write!(
106            f,
107            "msg{{from=flow#{}, reply={}, id={}, tag={}, payload={}}}",
108            self.sender, self.reply_cap, self.request_id, self.tag, self.payload
109        )
110    }
111}
112
113/// A dynamically-tagged runtime value.
114///
115/// [`Value::Cap`] is an opaque [`CapId`] for `Send` / `Ask`. Authority lives
116/// in the runtime [`crate::CapTable`], keyed by holder — not in this tag.
117/// [`Value::Pid`] remains for **identity** inside authenticated messages.
118///
119/// [`Value::Str`] / [`Value::Bytes`] are heap payloads shared via [`Arc`].
120#[derive(Clone, Debug, PartialEq)]
121pub enum Value {
122    Unit,
123    Bool(bool),
124    Int(i64),
125    Float(f64),
126    /// Internal / message identity (FlowId as `u64`). **Not** a Send target.
127    Pid(u64),
128    /// Atomic Hop envelope. See [`Message`].
129    Message(Message),
130    /// Opaque capability token. Required for `Send` / `Ask`.
131    Cap(CapId),
132    /// UTF-8 text (constant pool, natives, host).
133    Str(Arc<str>),
134    /// Opaque byte buffer (constant pool, natives, host).
135    Bytes(Arc<[u8]>),
136}
137
138impl Value {
139    /// Build a [`Value::Str`] from anything string-like.
140    #[inline]
141    pub fn str(s: impl AsRef<str>) -> Self {
142        Value::Str(Arc::from(s.as_ref()))
143    }
144
145    /// Build a [`Value::Bytes`] from a byte slice.
146    #[inline]
147    pub fn bytes(b: impl AsRef<[u8]>) -> Self {
148        Value::Bytes(Arc::from(b.as_ref()))
149    }
150
151    /// Truthiness used by `Opcode::Branch`: falsy are `Unit`, `Bool(false)`,
152    /// `Int(0)`, empty [`Value::Str`], and empty [`Value::Bytes`].
153    #[inline]
154    pub fn is_truthy(&self) -> bool {
155        match self {
156            Value::Unit | Value::Bool(false) | Value::Int(0) => false,
157            Value::Str(s) if s.is_empty() => false,
158            Value::Bytes(b) if b.is_empty() => false,
159            _ => true,
160        }
161    }
162
163    #[inline]
164    pub fn as_int(&self) -> Option<i64> {
165        match self {
166            Value::Int(i) => Some(*i),
167            Value::Bool(b) => Some(*b as i64),
168            _ => None,
169        }
170    }
171
172    #[inline]
173    pub fn as_pid(&self) -> Option<u64> {
174        match self {
175            Value::Pid(p) => Some(*p),
176            _ => None,
177        }
178    }
179
180    #[inline]
181    pub fn as_cap(&self) -> Option<CapId> {
182        match self {
183            Value::Cap(c) => Some(*c),
184            _ => None,
185        }
186    }
187
188    #[inline]
189    pub fn as_message(&self) -> Option<&Message> {
190        match self {
191            Value::Message(m) => Some(m),
192            _ => None,
193        }
194    }
195
196    #[inline]
197    pub fn as_str(&self) -> Option<&str> {
198        match self {
199            Value::Str(s) => Some(s.as_ref()),
200            _ => None,
201        }
202    }
203
204    #[inline]
205    pub fn as_bytes(&self) -> Option<&[u8]> {
206        match self {
207            Value::Bytes(b) => Some(b.as_ref()),
208            Value::Str(s) => Some(s.as_bytes()),
209            _ => None,
210        }
211    }
212
213    /// Bytes this value is **charged** for against a mailbox byte budget
214    /// (see [`crate::MailboxBytes`]).
215    ///
216    /// [`Value::Str`] / [`Value::Bytes`] / hop payloads are `Arc`-shared: the
217    /// same buffer cloned into N mailboxes exists once in memory, but each
218    /// mailbox is charged the full length. That over-counts on purpose.
219    #[inline]
220    pub fn memory_size(&self) -> usize {
221        std::mem::size_of::<Self>() + self.heap_size()
222    }
223
224    /// Heap bytes owned (transitively) by this value, excluding the enum
225    /// itself.
226    #[inline]
227    pub fn heap_size(&self) -> usize {
228        match self {
229            Value::Str(s) => s.len(),
230            Value::Bytes(b) => b.len(),
231            Value::Message(m) => m.payload.memory_size(),
232            Value::Unit
233            | Value::Bool(_)
234            | Value::Int(_)
235            | Value::Float(_)
236            | Value::Pid(_)
237            | Value::Cap(_) => 0,
238        }
239    }
240
241    pub fn type_name(&self) -> &'static str {
242        match self {
243            Value::Unit => "unit",
244            Value::Bool(_) => "bool",
245            Value::Int(_) => "int",
246            Value::Float(_) => "float",
247            Value::Pid(_) => "pid",
248            Value::Message(_) => "message",
249            Value::Cap(_) => "cap",
250            Value::Str(_) => "str",
251            Value::Bytes(_) => "bytes",
252        }
253    }
254}
255
256impl fmt::Display for Value {
257    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
258        match self {
259            Value::Unit => write!(f, "()"),
260            Value::Bool(b) => write!(f, "{b}"),
261            Value::Int(i) => write!(f, "{i}"),
262            Value::Float(x) => write!(f, "{x}"),
263            Value::Pid(p) => write!(f, "flow#{p}"),
264            Value::Message(m) => write!(f, "{m}"),
265            Value::Cap(c) => write!(f, "{c}"),
266            Value::Str(s) => write!(f, "{s}"),
267            Value::Bytes(b) => write!(f, "bytes[{}]", b.len()),
268        }
269    }
270}
271
272impl From<i64> for Value {
273    fn from(v: i64) -> Self {
274        Value::Int(v)
275    }
276}
277impl From<i32> for Value {
278    fn from(v: i32) -> Self {
279        Value::Int(i64::from(v))
280    }
281}
282impl From<u64> for Value {
283    fn from(v: u64) -> Self {
284        Value::Int(v as i64)
285    }
286}
287impl From<bool> for Value {
288    fn from(v: bool) -> Self {
289        Value::Bool(v)
290    }
291}
292impl From<f64> for Value {
293    fn from(v: f64) -> Self {
294        Value::Float(v)
295    }
296}
297impl From<Message> for Value {
298    fn from(m: Message) -> Self {
299        Value::Message(m)
300    }
301}
302impl From<CapId> for Value {
303    fn from(c: CapId) -> Self {
304        Value::Cap(c)
305    }
306}
307impl From<&str> for Value {
308    fn from(s: &str) -> Self {
309        Value::str(s)
310    }
311}
312impl From<String> for Value {
313    fn from(s: String) -> Self {
314        Value::Str(Arc::from(s))
315    }
316}
317impl From<&[u8]> for Value {
318    fn from(b: &[u8]) -> Self {
319        Value::bytes(b)
320    }
321}
322impl From<Vec<u8>> for Value {
323    fn from(b: Vec<u8>) -> Self {
324        Value::Bytes(Arc::from(b))
325    }
326}
327
328#[cfg(test)]
329mod tests {
330    use super::*;
331
332    #[test]
333    fn message_is_truthy_and_round_trips_helpers() {
334        let m = Message::new(7, 99, 10, 1u64);
335        let v = Value::Message(m.clone());
336        assert!(v.is_truthy());
337        assert_eq!(v.as_message(), Some(&m));
338        assert_eq!(v.type_name(), "message");
339        assert_eq!(m.payload.as_ref(), &Value::Int(1));
340    }
341
342    #[test]
343    fn authenticate_stamps_sender_and_reply_cap() {
344        let reply = CapId::from_raw(7);
345        let m = Message::new(999, 1, 2, 3u64).authenticate(42, reply);
346        assert_eq!(m.sender, 42);
347        assert_eq!(m.reply_cap, reply);
348        assert_eq!(m.request_id, 1);
349    }
350
351    #[test]
352    fn str_payload_is_charged_on_the_hop() {
353        let hop = Value::Message(Message::new(1, 1, 1, Value::str("hello")));
354        assert!(hop.heap_size() >= 5);
355    }
356
357    #[test]
358    fn cap_is_truthy() {
359        let cap = CapId::from_raw(3);
360        assert!(Value::Cap(cap).is_truthy());
361        assert_eq!(Value::Cap(cap).as_cap(), Some(cap));
362    }
363
364    #[test]
365    fn str_and_bytes_helpers() {
366        let s = Value::str("hi");
367        assert_eq!(s.as_str(), Some("hi"));
368        assert_eq!(s.type_name(), "str");
369        assert!(s.is_truthy());
370        assert!(!Value::str("").is_truthy());
371
372        let b = Value::bytes([1u8, 2, 3]);
373        assert_eq!(b.as_bytes(), Some(&[1, 2, 3][..]));
374        assert_eq!(b.type_name(), "bytes");
375        assert!(b.is_truthy());
376        assert!(!Value::bytes([]).is_truthy());
377
378        assert_eq!(s.as_bytes(), Some(b"hi".as_slice()));
379    }
380
381    #[test]
382    fn str_eq_compares_content() {
383        assert_eq!(Value::str("a"), Value::from("a".to_owned()));
384        assert_ne!(Value::str("a"), Value::str("b"));
385        assert_eq!(Value::bytes([9]), Value::from(vec![9u8]));
386    }
387}