rust_pty/unix/
buffer.rs

1//! Zero-copy buffer for efficient PTY I/O.
2//!
3//! This module provides a ring buffer optimized for PTY communication,
4//! minimizing memory allocations and copies during read/write operations.
5
6use std::io;
7
8/// Default buffer capacity (16KB).
9pub const DEFAULT_CAPACITY: usize = 16 * 1024;
10
11/// A ring buffer for efficient PTY I/O operations.
12///
13/// This buffer is designed for the producer-consumer pattern typical
14/// of PTY communication, where data is written in chunks and read
15/// as it becomes available.
16#[derive(Debug)]
17pub struct PtyBuffer {
18    /// The underlying storage.
19    data: Box<[u8]>,
20    /// Read position (consumer).
21    read_pos: usize,
22    /// Write position (producer).
23    write_pos: usize,
24}
25
26impl PtyBuffer {
27    /// Create a new buffer with the specified capacity.
28    #[must_use]
29    pub fn new(capacity: usize) -> Self {
30        Self {
31            data: vec![0u8; capacity].into_boxed_slice(),
32            read_pos: 0,
33            write_pos: 0,
34        }
35    }
36
37    /// Create a new buffer with default capacity.
38    #[must_use]
39    pub fn with_default_capacity() -> Self {
40        Self::new(DEFAULT_CAPACITY)
41    }
42
43    /// Returns the total capacity of the buffer.
44    #[must_use]
45    pub const fn capacity(&self) -> usize {
46        self.data.len()
47    }
48
49    /// Returns the number of bytes available to read.
50    #[must_use]
51    pub const fn len(&self) -> usize {
52        if self.write_pos >= self.read_pos {
53            self.write_pos - self.read_pos
54        } else {
55            self.capacity() - self.read_pos + self.write_pos
56        }
57    }
58
59    /// Returns true if the buffer contains no data.
60    #[must_use]
61    pub const fn is_empty(&self) -> bool {
62        self.read_pos == self.write_pos
63    }
64
65    /// Returns the number of bytes that can be written.
66    #[must_use]
67    pub const fn available(&self) -> usize {
68        // Reserve one byte to distinguish full from empty
69        self.capacity() - self.len() - 1
70    }
71
72    /// Returns true if the buffer is full.
73    #[must_use]
74    pub const fn is_full(&self) -> bool {
75        self.available() == 0
76    }
77
78    /// Get a slice of contiguous readable data.
79    ///
80    /// This may not return all available data if it wraps around the buffer.
81    /// Call `consume()` after processing, then call again for remaining data.
82    #[must_use]
83    pub fn readable(&self) -> &[u8] {
84        if self.write_pos >= self.read_pos {
85            &self.data[self.read_pos..self.write_pos]
86        } else {
87            // Return data up to the end of the buffer
88            &self.data[self.read_pos..]
89        }
90    }
91
92    /// Get a mutable slice for writing data.
93    ///
94    /// This may not return all available space if it wraps around the buffer.
95    /// Call `produce()` after writing, then call again for remaining space.
96    #[must_use]
97    pub fn writable(&mut self) -> &mut [u8] {
98        let cap = self.capacity();
99        if self.write_pos >= self.read_pos {
100            // Can write to end of buffer (but not wrap to position 0 if read_pos is 0)
101            let end = if self.read_pos == 0 { cap - 1 } else { cap };
102            &mut self.data[self.write_pos..end]
103        } else {
104            // Can write up to read_pos - 1 (leave one byte gap)
105            &mut self.data[self.write_pos..self.read_pos - 1]
106        }
107    }
108
109    /// Mark `count` bytes as consumed (read).
110    ///
111    /// # Panics
112    ///
113    /// Panics if `count` exceeds the available data.
114    pub fn consume(&mut self, count: usize) {
115        assert!(count <= self.len(), "cannot consume more than available");
116        self.read_pos = (self.read_pos + count) % self.capacity();
117    }
118
119    /// Mark `count` bytes as produced (written).
120    ///
121    /// # Panics
122    ///
123    /// Panics if `count` exceeds the available space.
124    pub fn produce(&mut self, count: usize) {
125        assert!(
126            count <= self.available(),
127            "cannot produce more than available"
128        );
129        self.write_pos = (self.write_pos + count) % self.capacity();
130    }
131
132    /// Clear all data from the buffer.
133    pub const fn clear(&mut self) {
134        self.read_pos = 0;
135        self.write_pos = 0;
136    }
137
138    /// Read data from the buffer into the provided slice.
139    ///
140    /// Returns the number of bytes read.
141    pub fn read(&mut self, buf: &mut [u8]) -> usize {
142        let mut total = 0;
143
144        while total < buf.len() && !self.is_empty() {
145            let readable = self.readable();
146            let to_copy = readable.len().min(buf.len() - total);
147            buf[total..total + to_copy].copy_from_slice(&readable[..to_copy]);
148            self.consume(to_copy);
149            total += to_copy;
150        }
151
152        total
153    }
154
155    /// Write data to the buffer from the provided slice.
156    ///
157    /// Returns the number of bytes written.
158    pub fn write(&mut self, data: &[u8]) -> usize {
159        let mut total = 0;
160
161        while total < data.len() && !self.is_full() {
162            let writable = self.writable();
163            let to_copy = writable.len().min(data.len() - total);
164            writable[..to_copy].copy_from_slice(&data[total..total + to_copy]);
165            self.produce(to_copy);
166            total += to_copy;
167        }
168
169        total
170    }
171}
172
173impl Default for PtyBuffer {
174    fn default() -> Self {
175        Self::with_default_capacity()
176    }
177}
178
179impl io::Read for PtyBuffer {
180    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
181        Ok(self.read(buf))
182    }
183}
184
185impl io::Write for PtyBuffer {
186    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
187        Ok(self.write(buf))
188    }
189
190    fn flush(&mut self) -> io::Result<()> {
191        Ok(())
192    }
193}
194
195#[cfg(test)]
196mod tests {
197    use super::*;
198
199    #[test]
200    fn new_buffer_is_empty() {
201        let buf = PtyBuffer::new(1024);
202        assert!(buf.is_empty());
203        assert_eq!(buf.len(), 0);
204        assert_eq!(buf.capacity(), 1024);
205    }
206
207    #[test]
208    fn write_and_read() {
209        let mut buf = PtyBuffer::new(1024);
210        let data = b"hello world";
211
212        let written = buf.write(data);
213        assert_eq!(written, data.len());
214        assert_eq!(buf.len(), data.len());
215
216        let mut output = [0u8; 32];
217        let read = buf.read(&mut output);
218        assert_eq!(read, data.len());
219        assert_eq!(&output[..read], data);
220        assert!(buf.is_empty());
221    }
222
223    #[test]
224    fn wrap_around() {
225        let mut buf = PtyBuffer::new(16);
226
227        // Fill most of the buffer
228        let data1 = b"12345678901";
229        buf.write(data1);
230
231        // Consume some
232        let mut tmp = [0u8; 8];
233        buf.read(&mut tmp);
234
235        // Write more (should wrap)
236        let data2 = b"abcdefgh";
237        let written = buf.write(data2);
238        assert!(written > 0);
239
240        // Read all
241        let mut output = [0u8; 32];
242        let total = buf.read(&mut output);
243        assert!(!output[..total].is_empty());
244    }
245
246    #[test]
247    fn clear_resets_buffer() {
248        let mut buf = PtyBuffer::new(1024);
249        buf.write(b"test data");
250        assert!(!buf.is_empty());
251
252        buf.clear();
253        assert!(buf.is_empty());
254        assert_eq!(buf.len(), 0);
255    }
256}