1use std::sync::Arc;
10
11use crate::bytecode::{Cap, CapRights, NativeIdx, NativeMask, RevocationCell, Value};
12
13use super::fault::Fault;
14
15pub type NativeResult = Result<Value, Fault>;
17
18pub type NativeFn = Arc<dyn Fn(&[Value]) -> NativeResult + Send + Sync>;
20
21pub struct NativeTable {
24 entries: Vec<Option<(String, NativeFn)>>,
25}
26
27impl NativeTable {
28 pub fn builder() -> NativeTableBuilder {
29 NativeTableBuilder {
30 entries: Vec::new(),
31 }
32 }
33
34 pub fn empty() -> Arc<NativeTable> {
36 Arc::new(NativeTable {
37 entries: Vec::new(),
38 })
39 }
40
41 #[inline]
42 pub fn get(&self, index: u32) -> Option<&NativeFn> {
43 self.entries
44 .get(index as usize)
45 .and_then(|slot| slot.as_ref())
46 .map(|(_, f)| f)
47 }
48
49 pub fn index_of(&self, name: &str) -> Option<u32> {
50 self.entries
51 .iter()
52 .enumerate()
53 .find(|(_, slot)| slot.as_ref().is_some_and(|(n, _)| n == name))
54 .map(|(i, _)| i as u32)
55 }
56
57 pub fn len(&self) -> usize {
60 self.entries.len()
61 }
62
63 pub fn is_empty(&self) -> bool {
64 self.entries.is_empty()
65 }
66
67 pub fn names(&self) -> impl Iterator<Item = &str> {
68 self.entries
69 .iter()
70 .filter_map(|slot| slot.as_ref().map(|(n, _)| n.as_str()))
71 }
72}
73
74#[derive(Debug, Clone, PartialEq, Eq)]
79pub enum NativeTableError {
80 DuplicateName(String),
81 SlotOccupied { index: u32, name: String },
82}
83
84impl std::fmt::Display for NativeTableError {
85 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
86 match self {
87 NativeTableError::DuplicateName(name) => {
88 write!(f, "duplicate native function registered: '{name}'")
89 }
90 NativeTableError::SlotOccupied { index, name } => {
91 write!(f, "native slot {index} already occupied (registering '{name}')")
92 }
93 }
94 }
95}
96
97impl std::error::Error for NativeTableError {}
98
99pub struct NativeTableBuilder {
104 entries: Vec<Option<(String, NativeFn)>>,
105}
106
107impl NativeTableBuilder {
108 pub fn new() -> Self {
109 Self {
110 entries: Vec::new(),
111 }
112 }
113
114 pub fn register<F>(self, name: impl Into<String>, f: F) -> Result<Self, NativeTableError>
116 where
117 F: Fn(&[Value]) -> NativeResult + Send + Sync + 'static,
118 {
119 let index = self.entries.len() as u32;
120 self.register_at(index, name, f)
121 }
122
123 pub fn register_at<F>(
126 mut self,
127 index: u32,
128 name: impl Into<String>,
129 f: F,
130 ) -> Result<Self, NativeTableError>
131 where
132 F: Fn(&[Value]) -> NativeResult + Send + Sync + 'static,
133 {
134 let name = name.into();
135 for (n, _) in self.entries.iter().flatten() {
136 if n == &name {
137 return Err(NativeTableError::DuplicateName(name));
138 }
139 }
140 let index_usize = index as usize;
141 if index_usize >= self.entries.len() {
142 self.entries.resize_with(index_usize + 1, || None);
143 }
144 if self.entries[index_usize].is_some() {
145 return Err(NativeTableError::SlotOccupied { index, name });
146 }
147 self.entries[index_usize] = Some((name, Arc::new(f)));
148 Ok(self)
149 }
150
151 pub fn build(self) -> Arc<NativeTable> {
152 Arc::new(NativeTable {
153 entries: self.entries,
154 })
155 }
156}
157
158impl Default for NativeTableBuilder {
159 fn default() -> Self {
160 Self::new()
161 }
162}
163
164pub fn expect_arg<'a>(
166 args: &'a [Value],
167 index: usize,
168 fn_name: &str,
169) -> Result<&'a Value, Fault> {
170 args.get(index).ok_or(Fault::NativeError(format!(
171 "{fn_name}: missing argument {index}"
172 )))
173}
174
175pub fn expect_int(args: &[Value], index: usize, fn_name: &str) -> Result<i64, Fault> {
177 expect_arg(args, index, fn_name)?
178 .as_int()
179 .ok_or(Fault::NativeError(format!(
180 "{fn_name}: argument {index} is not an int"
181 )))
182}
183
184pub fn expect_bool(args: &[Value], index: usize, fn_name: &str) -> Result<bool, Fault> {
186 match expect_arg(args, index, fn_name)? {
187 Value::Bool(b) => Ok(*b),
188 Value::Int(i) => Ok(*i != 0),
189 other => Err(Fault::NativeError(format!(
190 "{fn_name}: argument {index} is not a bool/int (got {})",
191 other.type_name()
192 ))),
193 }
194}
195
196pub fn expect_message(
201 args: &[Value],
202 index: usize,
203 fn_name: &str,
204) -> Result<crate::Message, Fault> {
205 expect_arg(args, index, fn_name)?
206 .as_message()
207 .cloned()
208 .ok_or(Fault::NativeError(format!(
209 "{fn_name}: argument {index} is not a message"
210 )))
211}
212
213pub fn expect_u64(args: &[Value], index: usize, fn_name: &str) -> Result<u64, Fault> {
219 match expect_arg(args, index, fn_name)? {
220 Value::Pid(p) => Ok(*p),
221 Value::Int(i) if *i >= 0 => Ok(*i as u64),
222 Value::Bool(b) => Ok(u64::from(*b)),
223 other => Err(Fault::NativeError(format!(
224 "{fn_name}: argument {index} is not a non-negative int/pid (got {})",
225 other.type_name()
226 ))),
227 }
228}
229
230#[derive(Clone, Debug)]
232pub struct NativeGate {
233 pub has_native: bool,
234 pub mask: NativeMask,
235 pub authority_epoch: u64,
236 pub flow_cell: Arc<RevocationCell>,
237 pub native_epoch: u64,
238 pub native_cell: Arc<RevocationCell>,
239}
240
241impl NativeGate {
242 pub fn deny(native_count: usize) -> Self {
244 Self {
245 has_native: false,
246 mask: NativeMask::empty(native_count),
247 authority_epoch: 0,
248 flow_cell: Arc::new(RevocationCell::new()),
249 native_epoch: 0,
250 native_cell: Arc::new(RevocationCell::new()),
251 }
252 }
253
254 pub fn from_authority(
255 cap: &Cap,
256 flow_cell: Arc<RevocationCell>,
257 native_cell: Arc<RevocationCell>,
258 native_count: usize,
259 ) -> Self {
260 let mask = match &cap.native_mask {
261 Some(m) => m.clone(),
262 None => NativeMask::empty(native_count),
263 };
264 Self {
265 has_native: cap.rights.contains(CapRights::NATIVE),
266 mask,
267 authority_epoch: cap.epoch(),
268 flow_cell,
269 native_epoch: native_cell.epoch(),
270 native_cell,
271 }
272 }
273
274 pub fn is_live(&self) -> bool {
275 self.flow_cell.epoch() == self.authority_epoch
276 && self.native_cell.epoch() == self.native_epoch
277 }
278}
279
280#[derive(Debug, Clone, Copy, PartialEq, Eq)]
281pub enum NativeCallError {
282 NoNativeRight,
283 IndexNotAllowlisted(NativeIdx),
284 IndexOutOfRange(NativeIdx),
285 Revoked,
286}
287
288impl std::fmt::Display for NativeCallError {
289 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
290 match self {
291 NativeCallError::NoNativeRight => f.write_str("CALL_NATIVE: flow lacks NATIVE right"),
292 NativeCallError::IndexNotAllowlisted(idx) => {
293 write!(f, "CALL_NATIVE: index {idx} not on allowlist")
294 }
295 NativeCallError::IndexOutOfRange(idx) => {
296 write!(f, "CALL_NATIVE: index {idx} out of range")
297 }
298 NativeCallError::Revoked => f.write_str("CALL_NATIVE: capability revoked"),
299 }
300 }
301}
302
303impl std::error::Error for NativeCallError {}
304
305pub fn check_native_call(
307 cap: &Cap,
308 table: &NativeTable,
309 idx: NativeIdx,
310) -> Result<(), NativeCallError> {
311 if (idx as usize) >= table.len() {
312 return Err(NativeCallError::IndexOutOfRange(idx));
313 }
314 if !cap.rights.contains(CapRights::NATIVE) {
315 return Err(NativeCallError::NoNativeRight);
316 }
317 match &cap.native_mask {
318 Some(mask) if mask.allows(idx) => Ok(()),
319 _ => Err(NativeCallError::IndexNotAllowlisted(idx)),
320 }
321}
322
323pub fn check_native_gate(
324 gate: &NativeGate,
325 table: &NativeTable,
326 idx: NativeIdx,
327) -> Result<(), NativeCallError> {
328 if (idx as usize) >= table.len() {
329 return Err(NativeCallError::IndexOutOfRange(idx));
330 }
331 if !gate.is_live() {
332 return Err(NativeCallError::Revoked);
333 }
334 if !gate.has_native {
335 return Err(NativeCallError::NoNativeRight);
336 }
337 if gate.mask.allows(idx) {
338 Ok(())
339 } else {
340 Err(NativeCallError::IndexNotAllowlisted(idx))
341 }
342}
343
344#[cfg(test)]
345mod tests {
346 use super::*;
347
348 #[test]
349 fn register_at_leaves_holes_as_none() -> Result<(), Box<dyn std::error::Error>> {
350 let table = NativeTable::builder()
351 .register_at(10, "answer", |_| Ok(Value::Int(42)))?
352 .build();
353 assert_eq!(table.len(), 11);
354 assert_eq!(table.index_of("answer"), Some(10));
355 let f = table.get(10).ok_or("missing native")?;
356 assert!(matches!(f(&[])?, Value::Int(42)));
357 assert!(table.get(2).is_none());
358 Ok(())
359 }
360
361 #[test]
362 fn register_at_errors_on_duplicate_slot() {
363 let result = NativeTable::builder()
364 .register_at(3, "a", |_| Ok(Value::Unit))
365 .and_then(|b| b.register_at(3, "b", |_| Ok(Value::Unit)));
366 assert!(matches!(
367 result,
368 Err(NativeTableError::SlotOccupied { index: 3, .. })
369 ));
370 }
371
372 #[test]
373 fn register_errors_on_duplicate_name() {
374 let result = NativeTable::builder()
375 .register("x", |_| Ok(Value::Unit))
376 .and_then(|b| b.register("x", |_| Ok(Value::Unit)));
377 assert!(matches!(result, Err(NativeTableError::DuplicateName(_))));
378 }
379
380 #[test]
381 fn denies_unlisted_index_even_with_native_right() {
382 use crate::bytecode::{CapTarget, RevocationCell};
383
384 let cell = RevocationCell::new();
385 let mask = NativeMask::from_indices(16, &[2, 4]);
386 let cap = Cap::root(CapTarget::Flow(1), CapRights::NATIVE, Some(mask), &cell);
387 let table = NativeTable {
388 entries: Vec::new(),
389 };
390 assert!(matches!(
391 check_native_call(&cap, &table, 4),
392 Err(NativeCallError::IndexOutOfRange(_))
393 ));
394 }
395}