Skip to main content
KeyOS API Reference

server/
buffer.rs

1// SPDX-FileCopyrightText: 2026 Foundation Devices, Inc. <hello@foundation.xyz>
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4use rkyv::{Deserialize, Portable};
5use xous::{
6    map_memory, send_message, try_send_message, unmap_memory, Error, MemoryAddress, MemoryFlags,
7    MemoryMessage, MemoryRange, MemorySize, Message, Result, CID,
8};
9
10use crate::rkyv_utils::{
11    decode, serialize_into, serialized_size, SizeOfSerializer, XousDeserializer, XousSerializer,
12    XousValidator,
13};
14
15#[derive(Debug)]
16pub struct Buffer {
17    pages: MemoryRange,
18    used: usize,
19    should_drop: bool,
20}
21
22impl Buffer {
23    pub(crate) fn new(len: usize) -> core::result::Result<Self, Error> {
24        let len = core::cmp::max(len.next_multiple_of(0x1000), 0x1000);
25        let pages = map_memory(None, None, len, MemoryFlags::W)?;
26        Ok(Buffer { pages, used: 0, should_drop: true })
27    }
28
29    fn bytes(&self) -> &[u8] {
30        // SAFETY: `pages` is a mapped, page-aligned range this Buffer owns, so its
31        // pointer and length describe a valid [u8].
32        unsafe { core::slice::from_raw_parts(self.pages.as_ptr(), self.pages.len()) }
33    }
34
35    fn bytes_mut(&mut self) -> &mut [u8] {
36        // SAFETY: as `bytes`, and `&mut self` rules out any other alias to the range.
37        unsafe { core::slice::from_raw_parts_mut(self.pages.as_mut_ptr(), self.pages.len()) }
38    }
39
40    /// The payload length travels in `valid`; `offset` is unused. Confining the
41    /// convention to these helpers keeps callers from touching the fields by hand.
42    fn len_fields(len: usize) -> (Option<MemoryAddress>, Option<MemorySize>) { (None, MemorySize::new(len)) }
43
44    /// Read a payload length back from a message that carries one.
45    pub(crate) fn message_len(mem: &MemoryMessage) -> usize {
46        mem.valid.map_or(0, |v| v.get()).min(mem.buf.len())
47    }
48
49    /// Build the message that lends or moves this buffer.
50    fn message(&self, id: u32) -> MemoryMessage {
51        let (offset, valid) = Self::len_fields(self.used);
52        MemoryMessage { id: id as usize, buf: self.pages, offset, valid }
53    }
54
55    /// Consume the buffer and return the underlying storage.
56    ///
57    /// Fails for a message-backed buffer, whose pages belong to the message rather than us.
58    fn into_inner(mut self) -> core::result::Result<(MemoryRange, usize), Error> {
59        if self.should_drop {
60            self.should_drop = false;
61            Ok((self.pages, self.used))
62        } else {
63            Err(Error::ShareViolation)
64        }
65    }
66
67    /// Deserialize the payload of an incoming message into an owned value.
68    pub(crate) fn deserialize<T>(mem: &MemoryMessage) -> core::result::Result<T, rkyv::rancor::Error>
69    where
70        T: rkyv::Archive,
71        T::Archived: Portable
72            + for<'a> rkyv::bytecheck::CheckBytes<XousValidator<'a>>
73            + Deserialize<T, XousDeserializer>,
74    {
75        decode(&mem.buf.as_slice::<u8>()[..Self::message_len(mem)])
76    }
77
78    /// Serialize `src` into the buffer the client lent in `mem`, recording the reply length
79    /// so the client reads it.
80    pub(crate) fn reply<T>(mem: &mut MemoryMessage, src: &T) -> core::result::Result<(), rkyv::rancor::Error>
81    where
82        T: for<'a, 'b> rkyv::Serialize<XousSerializer<'a, 'b>>,
83    {
84        // SAFETY: `mem.buf` is the lent, page-aligned range the client gave us.
85        let dst = unsafe { core::slice::from_raw_parts_mut(mem.buf.as_mut_ptr(), mem.buf.len()) };
86        let used = serialize_into(dst, src)?;
87        (mem.offset, mem.valid) = Self::len_fields(used);
88        Ok(())
89    }
90
91    /// Serialize `src` as the reply to the `BlockingMove` carried by `mem`: swap `mem`'s buffer
92    /// to the reply and free whatever of the moved-in buffer the reply does not cover.
93    ///
94    /// The reply reuses the front of the moved-in buffer when it is large enough, else a fresh
95    /// allocation; unlike `reply` it need not fit the buffer the client sent.
96    pub(crate) fn reply_move<T>(mem: &mut MemoryMessage, src: &T) -> core::result::Result<(), crate::Error>
97    where
98        T: for<'a, 'b> rkyv::Serialize<XousSerializer<'a, 'b>>
99            + for<'a> rkyv::Serialize<SizeOfSerializer<'a>>,
100    {
101        let existing = mem.buf;
102        let size = serialized_size(src)?;
103        let needed = core::cmp::max(size.next_multiple_of(0x1000), 0x1000);
104
105        // Reuse the front of the moved-in buffer only on hardware; on hosted it is a
106        // single host allocation that can't be freed in pieces, so always allocate fresh.
107        let (reply, used) = if cfg!(keyos) && existing.len() >= needed {
108            let reply = existing.subrange(0, needed).expect("page-aligned subrange of a mapped buffer");
109            // SAFETY: `reply` is a mapped, page-aligned range we own for the duration of the reply.
110            let dst = unsafe { core::slice::from_raw_parts_mut(reply.as_mut_ptr(), reply.len()) };
111            (reply, serialize_into(dst, src)?)
112        } else {
113            // Keep the Buffer alive across serialize, or else a failure here leaks the mapping.
114            let mut buf = Self::new(size)?;
115            let used = serialize_into(buf.bytes_mut(), src)?;
116            (buf.into_inner().expect("freshly mapped buffer is ownable").0, used)
117        };
118        mem.buf = reply;
119        (mem.offset, mem.valid) = Self::len_fields(used);
120
121        // Free whatever of the moved-in buffer the reply doesn't cover: the tail when we
122        // reused the front, or all of it when we allocated a fresh one.
123        if reply.as_ptr() == existing.as_ptr() {
124            let tail_len = existing.len() - reply.len();
125            if tail_len > 0 {
126                let tail =
127                    existing.subrange(reply.len(), tail_len).expect("tail subrange of a mapped buffer");
128                unmap_memory(tail).expect("unmap reply tail");
129            }
130        } else {
131            unmap_memory(existing).expect("unmap moved-in buffer");
132        }
133        Ok(())
134    }
135
136    /// Perform a mutable lend of this Buffer to the server.
137    pub(crate) fn lend_mut(&mut self, connection: CID, id: u32) -> core::result::Result<Result, Error> {
138        let msg = self.message(id);
139        let result = send_message(connection, Message::MutableBorrow(msg));
140        if let Ok(Result::MemoryReturned(_range, _offset, valid)) = result {
141            self.used = valid.map_or(0, |v| v.get()).min(self.pages.len());
142        }
143
144        result
145    }
146
147    /// Send the buffer as a `BlockingMove`: ownership moves to the server and the call
148    /// blocks until the server moves a (possibly resized) buffer back, which this buffer
149    /// adopts. Unlike `lend_mut`, the reply need not match the sent buffer: it may outgrow
150    /// it, or come back trimmed when a huge request yields a tiny response.
151    pub(crate) fn blocking_move(&mut self, connection: CID, id: u32) -> core::result::Result<Result, Error> {
152        let msg = self.message(id);
153        // The kernel keeps this range reserved (lent) while we block, and on a server
154        // death leaves it as on-demand rather than freeing it. So on any error the range
155        // is still ours and mapped here, and `pages`/`should_drop` stay valid.
156        let result = send_message(connection, Message::BlockingMove(msg))?;
157        if let Result::MemoryReturned(range, _offset, valid) = result {
158            self.pages = range;
159            self.used = valid.map_or(0, |v| v.get()).min(range.len());
160            self.should_drop = true;
161        }
162        Ok(result)
163    }
164
165    pub fn send(mut self, connection: CID, id: u32) -> core::result::Result<Result, Error> {
166        let msg = self.message(id);
167        let result = send_message(connection, Message::Move(msg))?;
168
169        // Move transfers our pages to the server; don't unmap on drop.
170        self.should_drop = false;
171        Ok(result)
172    }
173
174    pub(crate) fn send_nowait(mut self, connection: CID, id: u32) -> core::result::Result<Result, Error> {
175        let msg = self.message(id);
176        let result = try_send_message(connection, Message::Move(msg))?;
177
178        // Move transfers our pages to the server; don't unmap on drop.
179        self.should_drop = false;
180        Ok(result)
181    }
182
183    /// Allocate a fresh page-aligned Buffer and copy `bytes` into it. Useful for forwarding an
184    /// already-archived rkyv payload without re-serializing.
185    pub fn from_bytes(bytes: &[u8]) -> core::result::Result<Self, Error> {
186        let mut buf = Self::new(bytes.len())?;
187        buf.bytes_mut()[..bytes.len()].copy_from_slice(bytes);
188        buf.used = bytes.len();
189        Ok(buf)
190    }
191
192    pub(crate) fn into_buf<T>(src: &T) -> core::result::Result<Self, crate::Error>
193    where
194        T: for<'a, 'b> rkyv::Serialize<XousSerializer<'a, 'b>>
195            + for<'a> rkyv::Serialize<SizeOfSerializer<'a>>,
196    {
197        let mut buf = Self::new(serialized_size(src)?)?;
198        buf.used = serialize_into(buf.bytes_mut(), src)?;
199        Ok(buf)
200    }
201
202    /// Deserialize the buffer's valid bytes into an owned value.
203    #[inline]
204    pub(crate) fn to_original<T>(&self) -> core::result::Result<T, rkyv::rancor::Error>
205    where
206        T: rkyv::Archive,
207        T::Archived: Portable
208            + for<'a> rkyv::bytecheck::CheckBytes<XousValidator<'a>>
209            + Deserialize<T, XousDeserializer>,
210    {
211        decode(&self.bytes()[..self.used])
212    }
213}
214
215impl Drop for Buffer {
216    fn drop(&mut self) {
217        if self.should_drop {
218            unmap_memory(self.pages).expect("Buffer: failed to drop memory");
219        }
220    }
221}