Skip to main content

byteflow/bytecode/
value.rs

1use std::fmt;
2use std::sync::Arc;
3
4/// Fixed-size envelope carried in mailboxes and registers (**Atomic Hop**).
5///
6/// # Why this exists (request-reply / typed protocols)
7///
8/// `Receive` delivers a single [`Value`] — not `{sender, Value}`. Without an
9/// envelope, a server flow cannot learn who sent a request, and two clients
10/// cannot safely share a `request_id` space.
11///
12/// # Security
13///
14/// - **`sender`**: FlowId stamped by the scheduler on bytecode `Send` / `Ask`
15///   (invariant **S1**). Not a capability.
16/// - **`reply_cap`**: CapId minted at the same boundary with **SEND**-only
17///   rights so the recipient can answer without ambient Pid addressing
18///   (phase 2). Zero means “no reply grant” (host-injected hops may omit it).
19///
20/// See `docs/security.md`.
21#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
22pub struct Message {
23    /// Authenticated origin FlowId (`0` = trusted host / non-flow).
24    pub sender: u64,
25    /// Capability granting **SEND** back to [`Self::sender`], or `0`.
26    pub reply_cap: u64,
27    /// Client correlation token; echoed on replies (`Ask` / **S2**).
28    pub request_id: u64,
29    /// Protocol discriminator (opaque to the VM).
30    pub tag: u16,
31    /// Small protocol payload.
32    pub payload: u64,
33}
34
35impl Message {
36    /// Build an envelope. `sender` / `reply_cap` are placeholders until a
37    /// bytecode hop is authenticated by the scheduler (`reply_cap` typically
38    /// `0` here).
39    pub const fn new(sender: u64, request_id: u64, tag: u16, payload: u64) -> Self {
40        Self {
41            sender,
42            reply_cap: 0,
43            request_id,
44            tag,
45            payload,
46        }
47    }
48
49    /// Stamp origin FlowId and attach a reply capability (scheduler only).
50    #[inline]
51    pub(crate) fn authenticate(mut self, sender: u64, reply_cap: u64) -> Self {
52        self.sender = sender;
53        self.reply_cap = reply_cap;
54        self
55    }
56}
57
58impl fmt::Display for Message {
59    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
60        write!(
61            f,
62            "msg{{from=flow#{}, reply=cap#{}, id={}, tag={}, payload={}}}",
63            self.sender, self.reply_cap, self.request_id, self.tag, self.payload
64        )
65    }
66}
67
68/// A dynamically-tagged runtime value.
69///
70/// [`Value::Cap`] is an unforgeable address for `Send` / `Ask` (phase 2).
71/// [`Value::Pid`] remains for **identity** inside authenticated messages
72/// (`Message.sender` / `msg_sender`), not for ambient addressing.
73///
74/// [`Value::Str`] / [`Value::Bytes`] are heap payloads shared via [`Arc`] so
75/// register moves and mailbox hops clone the handle, not the buffer.
76#[derive(Clone, Debug, PartialEq)]
77pub enum Value {
78    Unit,
79    Bool(bool),
80    Int(i64),
81    Float(f64),
82    /// Internal / message identity (FlowId as `u64`). **Not** a Send target.
83    Pid(u64),
84    /// Atomic Hop envelope. See [`Message`].
85    Message(Message),
86    /// Unforgeable capability (`CapId` as `u64`). Required for `Send` / `Ask`.
87    Cap(u64),
88    /// UTF-8 text (constant pool, natives, host).
89    Str(Arc<str>),
90    /// Opaque byte buffer (constant pool, natives, host).
91    Bytes(Arc<[u8]>),
92}
93
94impl Value {
95    /// Build a [`Value::Str`] from anything string-like.
96    #[inline]
97    pub fn str(s: impl AsRef<str>) -> Self {
98        Value::Str(Arc::from(s.as_ref()))
99    }
100
101    /// Build a [`Value::Bytes`] from a byte slice.
102    #[inline]
103    pub fn bytes(b: impl AsRef<[u8]>) -> Self {
104        Value::Bytes(Arc::from(b.as_ref()))
105    }
106
107    /// Truthiness used by `Opcode::Branch`: falsy are `Unit`, `Bool(false)`,
108    /// `Int(0)`, empty [`Value::Str`], and empty [`Value::Bytes`].
109    #[inline]
110    pub fn is_truthy(&self) -> bool {
111        match self {
112            Value::Unit | Value::Bool(false) | Value::Int(0) => false,
113            Value::Str(s) if s.is_empty() => false,
114            Value::Bytes(b) if b.is_empty() => false,
115            _ => true,
116        }
117    }
118
119    #[inline]
120    pub fn as_int(&self) -> Option<i64> {
121        match self {
122            Value::Int(i) => Some(*i),
123            Value::Bool(b) => Some(*b as i64),
124            _ => None,
125        }
126    }
127
128    #[inline]
129    pub fn as_pid(&self) -> Option<u64> {
130        match self {
131            Value::Pid(p) => Some(*p),
132            _ => None,
133        }
134    }
135
136    #[inline]
137    pub fn as_cap(&self) -> Option<u64> {
138        match self {
139            Value::Cap(c) => Some(*c),
140            _ => None,
141        }
142    }
143
144    #[inline]
145    pub fn as_message(&self) -> Option<Message> {
146        match self {
147            Value::Message(m) => Some(*m),
148            _ => None,
149        }
150    }
151
152    #[inline]
153    pub fn as_str(&self) -> Option<&str> {
154        match self {
155            Value::Str(s) => Some(s.as_ref()),
156            _ => None,
157        }
158    }
159
160    #[inline]
161    pub fn as_bytes(&self) -> Option<&[u8]> {
162        match self {
163            Value::Bytes(b) => Some(b.as_ref()),
164            Value::Str(s) => Some(s.as_bytes()),
165            _ => None,
166        }
167    }
168
169    pub fn type_name(&self) -> &'static str {
170        match self {
171            Value::Unit => "unit",
172            Value::Bool(_) => "bool",
173            Value::Int(_) => "int",
174            Value::Float(_) => "float",
175            Value::Pid(_) => "pid",
176            Value::Message(_) => "message",
177            Value::Cap(_) => "cap",
178            Value::Str(_) => "str",
179            Value::Bytes(_) => "bytes",
180        }
181    }
182}
183
184impl fmt::Display for Value {
185    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
186        match self {
187            Value::Unit => write!(f, "()"),
188            Value::Bool(b) => write!(f, "{b}"),
189            Value::Int(i) => write!(f, "{i}"),
190            Value::Float(x) => write!(f, "{x}"),
191            Value::Pid(p) => write!(f, "flow#{p}"),
192            Value::Message(m) => write!(f, "{m}"),
193            Value::Cap(c) => write!(f, "cap#{c}"),
194            Value::Str(s) => write!(f, "{s}"),
195            Value::Bytes(b) => write!(f, "bytes[{}]", b.len()),
196        }
197    }
198}
199
200impl From<i64> for Value {
201    fn from(v: i64) -> Self {
202        Value::Int(v)
203    }
204}
205impl From<bool> for Value {
206    fn from(v: bool) -> Self {
207        Value::Bool(v)
208    }
209}
210impl From<f64> for Value {
211    fn from(v: f64) -> Self {
212        Value::Float(v)
213    }
214}
215impl From<Message> for Value {
216    fn from(m: Message) -> Self {
217        Value::Message(m)
218    }
219}
220impl From<&str> for Value {
221    fn from(s: &str) -> Self {
222        Value::str(s)
223    }
224}
225impl From<String> for Value {
226    fn from(s: String) -> Self {
227        Value::Str(Arc::from(s))
228    }
229}
230impl From<&[u8]> for Value {
231    fn from(b: &[u8]) -> Self {
232        Value::bytes(b)
233    }
234}
235impl From<Vec<u8>> for Value {
236    fn from(b: Vec<u8>) -> Self {
237        Value::Bytes(Arc::from(b))
238    }
239}
240
241#[cfg(test)]
242mod tests {
243    use super::*;
244
245    #[test]
246    fn message_is_truthy_and_round_trips_helpers() {
247        let m = Message::new(7, 99, 10, 1);
248        let v = Value::Message(m);
249        assert!(v.is_truthy());
250        assert_eq!(v.as_message(), Some(m));
251        assert_eq!(v.type_name(), "message");
252    }
253
254    #[test]
255    fn authenticate_stamps_sender_and_reply_cap() {
256        let m = Message::new(999, 1, 2, 3).authenticate(42, 7);
257        assert_eq!(m.sender, 42);
258        assert_eq!(m.reply_cap, 7);
259        assert_eq!(m.request_id, 1);
260    }
261
262    #[test]
263    fn cap_is_truthy() {
264        assert!(Value::Cap(1).is_truthy());
265        assert_eq!(Value::Cap(3).as_cap(), Some(3));
266    }
267
268    #[test]
269    fn str_and_bytes_helpers() {
270        let s = Value::str("hi");
271        assert_eq!(s.as_str(), Some("hi"));
272        assert_eq!(s.type_name(), "str");
273        assert!(s.is_truthy());
274        assert!(!Value::str("").is_truthy());
275
276        let b = Value::bytes([1u8, 2, 3]);
277        assert_eq!(b.as_bytes(), Some(&[1, 2, 3][..]));
278        assert_eq!(b.type_name(), "bytes");
279        assert!(b.is_truthy());
280        assert!(!Value::bytes([]).is_truthy());
281
282        // Str also exposes UTF-8 bytes via as_bytes.
283        assert_eq!(s.as_bytes(), Some(b"hi".as_slice()));
284    }
285
286    #[test]
287    fn str_eq_compares_content() {
288        assert_eq!(Value::str("a"), Value::from("a".to_owned()));
289        assert_ne!(Value::str("a"), Value::str("b"));
290        assert_eq!(Value::bytes([9]), Value::from(vec![9u8]));
291    }
292}