xous_api_names/
lib.rs

1#![cfg_attr(target_os = "none", no_std)]
2
3//! Detailed docs are parked under Structs/XousNames down below
4
5pub mod api;
6
7use core::fmt::Write;
8
9use api::Disconnect;
10use num_traits::ToPrimitive;
11use xous_ipc::Buffer;
12
13/// A page-aligned stack allocation for connection requests
14#[repr(C, align(4096))]
15struct ConnectRequest {
16    name: [u8; 64],
17    len: u32,
18    _padding: [u8; 4096 - 4 - 64],
19}
20impl Default for ConnectRequest {
21    fn default() -> Self { ConnectRequest { name: [0u8; 64], len: 0, _padding: [0u8; 4096 - 4 - 64] } }
22}
23
24#[doc = include_str!("../README.md")]
25#[derive(Debug)]
26pub struct XousNames {
27    conn: xous::CID,
28}
29impl XousNames {
30    pub fn new() -> Result<Self, xous::Error> {
31        REFCOUNT.fetch_add(1, Ordering::Relaxed);
32        let conn = xous::connect(xous::SID::from_bytes(b"xous-name-server").unwrap())
33            .expect("Couldn't connect to XousNames");
34        Ok(XousNames { conn })
35    }
36
37    /// Searches the name table and removes a given SID from the table.
38    /// It's considered "secure" because you'd have to guess a random 128-bit SID
39    /// to destroy someone else's SID.
40    pub fn unregister_server(&self, sid: xous::SID) -> Result<(), xous::Error> {
41        let s = sid.to_array();
42        let response = xous::send_message(
43            self.conn,
44            xous::Message::new_blocking_scalar(
45                api::Opcode::Unregister.to_usize().unwrap(),
46                s[0] as usize,
47                s[1] as usize,
48                s[2] as usize,
49                s[3] as usize,
50            ),
51        )
52        .expect("unregistration failed");
53        if let xous::Result::Scalar1(result) = response {
54            if result != 0 { Ok(()) } else { Err(xous::Error::ServerNotFound) }
55        } else {
56            Err(xous::Error::InternalError)
57        }
58    }
59
60    /// Register a server with a plaintext `name`. When specified, xous-names will
61    /// limit the number of connections brokered to the value in `max_conns`. This
62    /// effectively blocks further services from connecting to the server in a
63    /// Trust-On-First-Use (TOFU) model.
64    pub fn register_name(&self, name: &str, max_conns: Option<u32>) -> Result<xous::SID, xous::Error> {
65        let mut registration = api::Registration { name: String::new(), conn_limit: max_conns };
66        // could also do String::from() but in this case we want things to fail if the string is too long.
67        write!(registration.name, "{}", name).expect("name probably too long");
68
69        let mut buf = Buffer::into_buf(registration).or(Err(xous::Error::InternalError))?;
70
71        buf.lend_mut(self.conn, api::Opcode::Register.to_u32().unwrap())
72            .or(Err(xous::Error::InternalError))?;
73
74        match buf.to_original().unwrap() {
75            api::Return::SID(sid_raw) => {
76                let sid = sid_raw.into();
77                xous::create_server_with_sid(sid).expect("can't auto-register server");
78                Ok(sid)
79            }
80            api::Return::Failure => Err(xous::Error::InternalError),
81            _ => unimplemented!("unimplemented return codes"),
82        }
83    }
84
85    /// Request a connection to the server with `name`. If the connection is allowed,
86    /// a 128-bit token is provided (in the form of a `[u32; 4]`) which can be used
87    /// later on to disconnect from the server, effectively decrementing the total
88    /// number of counts in against the `max_count` limit.
89    pub fn request_connection_with_token(
90        &self,
91        name: &str,
92    ) -> Result<(xous::CID, Option<[u32; 4]>), xous::Error> {
93        let mut lookup_name = String::new();
94        write!(lookup_name, "{}", name).expect("name probably too long");
95        let mut buf = Buffer::into_buf(lookup_name).or(Err(xous::Error::InternalError))?;
96
97        buf.lend_mut(self.conn, api::Opcode::Lookup.to_u32().unwrap()).or(Err(xous::Error::InternalError))?;
98
99        match buf.to_original().unwrap() {
100            api::Return::CID((cid, token)) => Ok((cid, token)),
101            // api::Return::AuthenticateRequest(_) => Err(xous::Error::AccessDenied),
102            _ => Err(xous::Error::ServerNotFound),
103        }
104    }
105
106    /// Disconnects from server with `name`. Must provide the same `token` returned on
107    /// connection, or else the call will be disregarded.
108    pub fn disconnect_with_token(&self, name: &str, token: [u32; 4]) -> Result<(), xous::Error> {
109        let disconnect = Disconnect { name: String::from(name), token };
110        let mut buf = Buffer::into_buf(disconnect).or(Err(xous::Error::InternalError))?;
111        buf.lend_mut(self.conn, api::Opcode::Disconnect.to_u32().unwrap())
112            .or(Err(xous::Error::InternalError))?;
113
114        match buf.to_original().unwrap() {
115            api::Return::Success => Ok(()),
116            _ => Err(xous::Error::ServerNotFound),
117        }
118    }
119
120    /// Requests a permanent connection to server with `name`. Xous names brokers the
121    /// entire connection, so the return value is the process-local CID (connection ID);
122    /// the 128-bit server ID is never revealed.
123    ///
124    /// This call will fail if the server has not yet started up, which is a common
125    /// problem during the boot process as the server start order is not guaranteed. Refer to
126    /// `request_connection_blocking()` for a call that will automatically retry.
127    pub fn request_connection(&self, name: &str) -> Result<xous::CID, xous::Error> {
128        let mut lookup_name = String::new();
129        write!(lookup_name, "{}", name).expect("name probably too long");
130
131        let mut buf = Buffer::into_buf(lookup_name).or(Err(xous::Error::InternalError))?;
132
133        buf.lend_mut(self.conn, api::Opcode::Lookup.to_u32().unwrap()).or(Err(xous::Error::InternalError))?;
134
135        match buf.to_original().unwrap() {
136            api::Return::CID((cid, _)) => Ok(cid),
137            // api::Return::AuthenticateRequest(_) => Err(xous::Error::AccessDenied),
138            _ => Err(xous::Error::ServerNotFound),
139        }
140    }
141
142    /// Requests a permanent connection to server with `name`. Xous names brokers the
143    /// entire connection, so the return value is the process-local CID (connection ID);
144    /// the 128-bit server ID is never revealed.
145    ///
146    /// This call uses the API already in place in `libstd`, hence the different style of
147    /// argument passing, and tons of `unsafe` code.
148    pub fn request_connection_blocking(&self, name: &str) -> Result<xous::CID, xous::Error> {
149        let mut cr: ConnectRequest = Default::default();
150        let name_bytes = name.as_bytes();
151
152        // Set the string length to the length of the passed-in String,
153        // or the maximum possible length. Which ever is smaller.
154        cr.len = cr.name.len().min(name_bytes.len()) as u32;
155
156        // Copy the string into our backing store.
157        for (&src_byte, dest_byte) in name_bytes.iter().zip(&mut cr.name) {
158            *dest_byte = src_byte;
159        }
160        log::debug!("connection requested {}", name);
161        let msg = xous::MemoryMessage {
162            id: api::Opcode::BlockingConnect.to_usize().unwrap(),
163            buf: unsafe {
164                // safety: `cr` is #[repr(C, align(4096))], and should be exactly on page in size
165                xous::MemoryRange::new(
166                    &mut cr as *mut _ as *mut u8 as usize,
167                    core::mem::size_of::<ConnectRequest>(),
168                )?
169            },
170            offset: None,
171            valid: xous::MemorySize::new(cr.len as usize),
172        };
173        xous::send_message(self.conn, xous::Message::MutableBorrow(msg))?;
174
175        let response_ptr = &cr as *const ConnectRequest as *const u32;
176        let result = unsafe { response_ptr.read() }; // safety: because that's how it was packed on the server, a naked u32
177
178        if result == 0 {
179            let cid = unsafe { response_ptr.add(1).read() }.into(); // safety: because that's how it was packed on the server
180            log::debug!("connected to {}:{}", name, cid);
181            Ok(cid)
182        } else {
183            Err(xous::Error::InternalError)
184        }
185    }
186
187    /// Returns `true` if every server that specified a `max_conn` count has filled
188    /// every slot available. Once all the limited slots are filled, the system has
189    /// finished TOFU initialization and can begin regular operations.
190    pub fn trusted_init_done(&self) -> Result<bool, xous::Error> {
191        let response = xous::send_message(
192            self.conn,
193            xous::Message::new_blocking_scalar(api::Opcode::TrustedInitDone.to_usize().unwrap(), 0, 0, 0, 0),
194        )
195        .expect("couldn't query trusted_init_done");
196        if let xous::Result::Scalar1(result) = response {
197            if result == 1 { Ok(true) } else { Ok(false) }
198        } else {
199            Err(xous::Error::InternalError)
200        }
201    }
202}
203
204use core::sync::atomic::{AtomicU32, Ordering};
205static REFCOUNT: AtomicU32 = AtomicU32::new(0);
206impl Drop for XousNames {
207    fn drop(&mut self) {
208        // de-allocate myself. It's unsafe because we are responsible to make sure nobody else is using the
209        // connection.
210        if REFCOUNT.fetch_sub(1, Ordering::Relaxed) == 1 {
211            unsafe {
212                xous::disconnect(self.conn).unwrap();
213            }
214        }
215    }
216}