use std::collections::VecDeque;
#[derive(Debug)]
pub struct PipeBuffer {
data: VecDeque<u8>,
capacity: usize,
write_closed: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum WriteResult {
Written(usize),
WouldBlock(usize),
BrokenPipe,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ReadResult {
Read(usize),
WouldBlock,
Eof,
}
impl PipeBuffer {
#[must_use]
pub fn new(capacity: usize) -> Self {
Self {
data: VecDeque::with_capacity(capacity),
capacity,
write_closed: false,
}
}
#[must_use]
pub fn default_size() -> Self {
Self::new(64 * 1024)
}
pub fn write(&mut self, buf: &[u8]) -> WriteResult {
if buf.is_empty() {
return WriteResult::Written(0);
}
let available = self.capacity.saturating_sub(self.data.len());
if available == 0 {
return WriteResult::WouldBlock(0);
}
let to_write = buf.len().min(available);
self.data.extend(&buf[..to_write]);
if to_write < buf.len() {
WriteResult::WouldBlock(to_write)
} else {
WriteResult::Written(to_write)
}
}
const MAX_PIPE_TOTAL: usize = 64 * 1024 * 1024;
pub fn write_all(&mut self, buf: &[u8]) {
let remaining = Self::MAX_PIPE_TOTAL.saturating_sub(self.data.len());
let to_write = buf.len().min(remaining);
self.data.extend(&buf[..to_write]);
}
pub fn read_all(&mut self) -> ReadResult {
if self.data.is_empty() {
if self.write_closed {
return ReadResult::Eof;
}
return ReadResult::WouldBlock;
}
ReadResult::Read(self.data.len())
}
pub fn drain(&mut self) -> Vec<u8> {
self.data.drain(..).collect()
}
pub fn close_write(&mut self) {
self.write_closed = true;
}
#[must_use]
pub fn is_write_closed(&self) -> bool {
self.write_closed
}
#[must_use]
pub fn has_data(&self) -> bool {
!self.data.is_empty()
}
#[must_use]
pub fn has_space(&self) -> bool {
self.data.len() < self.capacity
}
#[must_use]
pub fn len(&self) -> usize {
self.data.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.data.is_empty()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn write_and_drain() {
let mut pipe = PipeBuffer::new(1024);
assert!(matches!(pipe.write(b"hello"), WriteResult::Written(5)));
assert_eq!(pipe.len(), 5);
let data = pipe.drain();
assert_eq!(data, b"hello");
assert!(pipe.is_empty());
}
#[test]
fn write_would_block_at_capacity() {
let mut pipe = PipeBuffer::new(4);
assert!(matches!(pipe.write(b"abcd"), WriteResult::Written(4)));
assert!(matches!(pipe.write(b"x"), WriteResult::WouldBlock(0)));
}
#[test]
fn partial_write() {
let mut pipe = PipeBuffer::new(4);
assert!(matches!(pipe.write(b"abcdef"), WriteResult::WouldBlock(4)));
assert_eq!(pipe.len(), 4);
}
#[test]
fn read_eof_after_close() {
let mut pipe = PipeBuffer::new(1024);
pipe.write_all(b"data");
pipe.close_write();
assert!(matches!(pipe.read_all(), ReadResult::Read(4)));
pipe.drain();
assert!(matches!(pipe.read_all(), ReadResult::Eof));
}
#[test]
fn read_would_block_when_empty_and_open() {
let mut pipe = PipeBuffer::new(1024);
assert!(matches!(pipe.read_all(), ReadResult::WouldBlock));
}
#[test]
fn write_all_ignores_capacity() {
let mut pipe = PipeBuffer::new(4);
pipe.write_all(b"hello world"); assert_eq!(pipe.len(), 11);
}
}