endpoint_libs/libs/
peer.rs1use std::any::{Any, TypeId};
13use std::collections::HashMap;
14use std::net::{IpAddr, Ipv4Addr, SocketAddr};
15
16#[derive(Debug, Clone)]
18#[non_exhaustive]
19pub enum PeerIdentity {
20 Network(SocketAddr),
22 Local(LocalPeer),
24 Unknown,
26}
27
28#[derive(Debug, Clone)]
31pub struct LocalPeer {
32 pub pid: Option<u32>,
33 pub uid: Option<u32>,
35 pub attestation: Attestation,
36}
37
38#[derive(Debug, Clone)]
43#[non_exhaustive]
44pub enum Attestation {
45 None,
47 Verified {
50 mechanism: &'static str,
53 subject: String,
55 },
56}
57
58impl Attestation {
59 pub fn is_verified(&self) -> bool {
61 matches!(self, Self::Verified { .. })
62 }
63}
64
65impl PeerIdentity {
66 pub fn ip_addr(&self) -> IpAddr {
71 match self {
72 Self::Network(addr) => addr.ip(),
73 Self::Local(_) | Self::Unknown => IpAddr::V4(Ipv4Addr::LOCALHOST),
74 }
75 }
76
77 pub fn socket_addr(&self) -> SocketAddr {
82 match self {
83 Self::Network(addr) => *addr,
84 Self::Local(_) | Self::Unknown => SocketAddr::from(([127, 0, 0, 1], 0)),
85 }
86 }
87
88 pub fn attestation(&self) -> Option<&Attestation> {
91 match self {
92 Self::Local(peer) => Some(&peer.attestation),
93 _ => None,
94 }
95 }
96
97 pub fn display(&self) -> String {
99 match self {
100 Self::Network(addr) => addr.to_string(),
101 Self::Local(peer) => {
102 let pid = peer
103 .pid
104 .map_or_else(|| "?".to_owned(), |pid| pid.to_string());
105 match &peer.attestation {
106 Attestation::Verified { mechanism, .. } => {
107 format!("local(pid={pid},{mechanism})")
108 }
109 Attestation::None => format!("local(pid={pid},unattested)"),
110 }
111 }
112 Self::Unknown => "unknown".to_owned(),
113 }
114 }
115}
116
117impl std::fmt::Display for PeerIdentity {
118 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
119 f.write_str(&self.display())
120 }
121}
122
123impl From<SocketAddr> for PeerIdentity {
124 fn from(addr: SocketAddr) -> Self {
125 Self::Network(addr)
126 }
127}
128
129trait CloneAny: Any + Send + Sync {
139 fn clone_box(&self) -> Box<dyn CloneAny>;
140 fn as_any(&self) -> &dyn Any;
141 fn as_any_mut(&mut self) -> &mut dyn Any;
142 fn into_any(self: Box<Self>) -> Box<dyn Any>;
143}
144
145impl<T: Any + Send + Sync + Clone> CloneAny for T {
146 fn clone_box(&self) -> Box<dyn CloneAny> {
147 Box::new(self.clone())
148 }
149 fn as_any(&self) -> &dyn Any {
150 self
151 }
152 fn as_any_mut(&mut self) -> &mut dyn Any {
153 self
154 }
155 fn into_any(self: Box<Self>) -> Box<dyn Any> {
156 self
157 }
158}
159
160impl Clone for Box<dyn CloneAny> {
161 fn clone(&self) -> Self {
162 (**self).clone_box()
167 }
168}
169
170#[derive(Default, Clone)]
179pub struct Extensions {
180 map: HashMap<TypeId, Box<dyn CloneAny>>,
181}
182
183impl Extensions {
184 pub fn new() -> Self {
185 Self::default()
186 }
187
188 pub fn insert<T: Clone + Send + Sync + 'static>(&mut self, value: T) -> Option<T> {
190 self.map
191 .insert(TypeId::of::<T>(), Box::new(value))
192 .and_then(|prev| prev.into_any().downcast().ok().map(|boxed| *boxed))
193 }
194
195 pub fn get<T: Clone + Send + Sync + 'static>(&self) -> Option<&T> {
196 self.map
201 .get(&TypeId::of::<T>())
202 .and_then(|boxed| (**boxed).as_any().downcast_ref())
203 }
204
205 pub fn get_mut<T: Clone + Send + Sync + 'static>(&mut self) -> Option<&mut T> {
206 self.map
208 .get_mut(&TypeId::of::<T>())
209 .and_then(|boxed| (**boxed).as_any_mut().downcast_mut())
210 }
211
212 pub fn remove<T: Clone + Send + Sync + 'static>(&mut self) -> Option<T> {
213 self.map
214 .remove(&TypeId::of::<T>())
215 .and_then(|boxed| boxed.into_any().downcast().ok().map(|b| *b))
216 }
217
218 pub fn contains<T: Clone + Send + Sync + 'static>(&self) -> bool {
219 self.map.contains_key(&TypeId::of::<T>())
220 }
221
222 pub fn is_empty(&self) -> bool {
223 self.map.is_empty()
224 }
225
226 pub fn len(&self) -> usize {
227 self.map.len()
228 }
229}
230
231impl std::fmt::Debug for Extensions {
232 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
233 f.debug_struct("Extensions")
236 .field("len", &self.map.len())
237 .finish()
238 }
239}
240
241#[cfg(test)]
242mod tests {
243 use super::*;
244
245 #[test]
246 fn network_peer_reports_its_own_ip() {
247 let addr: SocketAddr = "203.0.113.7:9000".parse().unwrap();
248 let peer = PeerIdentity::Network(addr);
249 assert_eq!(peer.ip_addr(), addr.ip());
250 assert_eq!(peer.socket_addr(), addr);
251 assert_eq!(peer.display(), "203.0.113.7:9000");
252 assert!(peer.attestation().is_none());
253 }
254
255 #[test]
256 fn local_peer_reports_loopback_and_keeps_attestation() {
257 let peer = PeerIdentity::Local(LocalPeer {
258 pid: Some(42),
259 uid: Some(501),
260 attestation: Attestation::Verified {
261 mechanism: "xpc-codesign-requirement",
262 subject: "identifier agentone".to_owned(),
263 },
264 });
265 assert_eq!(peer.ip_addr(), IpAddr::V4(Ipv4Addr::LOCALHOST));
266 assert!(peer.attestation().unwrap().is_verified());
267 assert_eq!(peer.display(), "local(pid=42,xpc-codesign-requirement)");
268 }
269
270 #[test]
271 fn unattested_local_peer_is_visible_as_such() {
272 let peer = PeerIdentity::Local(LocalPeer {
273 pid: None,
274 uid: None,
275 attestation: Attestation::None,
276 });
277 assert!(!peer.attestation().unwrap().is_verified());
278 assert_eq!(peer.display(), "local(pid=?,unattested)");
279 }
280
281 #[test]
282 fn extensions_survive_a_clone() {
283 #[derive(Debug, Clone, PartialEq)]
284 struct Claims(&'static str);
285
286 let mut ext = Extensions::new();
287 ext.insert(Claims("mission-1"));
288 let copy = ext.clone();
290 assert_eq!(copy.get::<Claims>(), Some(&Claims("mission-1")));
291 }
292
293 #[test]
294 fn extensions_round_trip_by_type() {
295 #[derive(Debug, Clone, PartialEq)]
296 struct Claims(&'static str);
297
298 let mut ext = Extensions::new();
299 assert!(ext.is_empty());
300 assert!(ext.get::<Claims>().is_none());
301
302 assert!(ext.insert(Claims("mission-1")).is_none());
303 assert_eq!(ext.get::<Claims>(), Some(&Claims("mission-1")));
304 assert!(ext.contains::<Claims>());
305
306 let prev = ext.insert(Claims("mission-2"));
308 assert_eq!(prev, Some(Claims("mission-1")));
309 assert_eq!(ext.get::<Claims>(), Some(&Claims("mission-2")));
310
311 assert_eq!(ext.remove::<Claims>(), Some(Claims("mission-2")));
312 assert!(ext.is_empty());
313 }
314}