Distributed Erlang over iroh P2P + TLS, with a bot powers SDK
0

Configure Feed

Select the types of activity you want to include in your feed.

erl-iroh / src / powers.rs
3.3 kB 114 lines
1//! Capability flags ("powers") that bots can be granted. 2//! 3//! Powers gate high-level SDK operations so a bot only gets the tools it needs. 4 5use bitflags::bitflags; 6 7bitflags! { 8 /// Set of powers a [`crate::Bot`] may exercise. 9 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 10 pub struct Powers: u32 { 11 /// Call remote MFA via `erpc` / `rex` (`spawn_request`). 12 const RPC = 0b0000_0001; 13 /// Send messages to registered names / pids. 14 const SEND = 0b0000_0010; 15 /// Introspect processes, applications, ETS (via RPC helpers). 16 const INTROSPECT = 0b0000_0100; 17 /// Connect / disconnect peers (iroh, tcp, tls). 18 const CLUSTER = 0b0000_1000; 19 /// Accept inbound distribution connections. 20 const LISTEN = 0b0001_0000; 21 /// Evaluate expressions (`erl_eval` style RPC). 22 const EVAL = 0b0010_0000; 23 /// Start / stop function tracing on peers. 24 const TRACE = 0b0100_0000; 25 } 26} 27 28impl Powers { 29 /// Safe default for a worker bot: rpc + send + introspect + cluster. 30 pub fn worker() -> Self { 31 Self::RPC | Self::SEND | Self::INTROSPECT | Self::CLUSTER 32 } 33 34 /// Read-only observer: introspect + cluster. 35 pub fn observer() -> Self { 36 Self::INTROSPECT | Self::CLUSTER 37 } 38 39 /// Human-readable list of set flags. 40 pub fn describe(self) -> String { 41 let mut names = Vec::new(); 42 if self.contains(Self::RPC) { 43 names.push("rpc"); 44 } 45 if self.contains(Self::SEND) { 46 names.push("send"); 47 } 48 if self.contains(Self::INTROSPECT) { 49 names.push("introspect"); 50 } 51 if self.contains(Self::CLUSTER) { 52 names.push("cluster"); 53 } 54 if self.contains(Self::LISTEN) { 55 names.push("listen"); 56 } 57 if self.contains(Self::EVAL) { 58 names.push("eval"); 59 } 60 if self.contains(Self::TRACE) { 61 names.push("trace"); 62 } 63 if names.is_empty() { 64 "none".into() 65 } else { 66 names.join("|") 67 } 68 } 69 70 /// Return `Ok(())` if `needed` is granted, else [`crate::Error::PowerDenied`]. 71 pub fn require(self, needed: Powers) -> crate::Result<()> { 72 if self.contains(needed) { 73 Ok(()) 74 } else { 75 Err(crate::Error::PowerDenied { 76 needed: power_name(needed), 77 have: self.describe(), 78 }) 79 } 80 } 81} 82 83impl Default for Powers { 84 fn default() -> Self { 85 Self::worker() 86 } 87} 88 89impl std::fmt::Display for Powers { 90 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 91 write!(f, "{}", self.describe()) 92 } 93} 94 95fn power_name(p: Powers) -> &'static str { 96 // Match single-flag values used at call sites. 97 if p == Powers::RPC { 98 "rpc" 99 } else if p == Powers::SEND { 100 "send" 101 } else if p == Powers::INTROSPECT { 102 "introspect" 103 } else if p == Powers::CLUSTER { 104 "cluster" 105 } else if p == Powers::LISTEN { 106 "listen" 107 } else if p == Powers::EVAL { 108 "eval" 109 } else if p == Powers::TRACE { 110 "trace" 111 } else { 112 "unknown" 113 } 114}