Skip to main content

clack_common/
stream.rs

1//! CLAP I/O stream utilities.
2//!
3//! Provides `InputStream` and `OutputStream` wrappers that implement
4//! `std::io::Read` and `std::io::Write`, making CLAP streams integrate
5//! cleanly with Rust’s I/O traits.
6//!
7//! # Notes
8//! Hosts may restrict how many bytes can be read or written at once.
9//! These wrappers handle that by looping until all data is transferred,
10//! so you don’t need to worry about partial reads or writes.
11
12use crate::utils::{slice_from_external_parts, slice_from_external_parts_mut};
13use clap_sys::stream::{clap_istream, clap_ostream};
14use std::error::Error;
15use std::ffi::c_void;
16use std::fmt::{Display, Formatter};
17use std::io::{ErrorKind, Read, Write};
18use std::marker::PhantomData;
19
20/// An error code that can be raised by CLAP stream methods.
21#[derive(Copy, Clone, Debug)]
22pub struct StreamError {
23    code: i64,
24}
25
26impl StreamError {
27    /// Returns the underlying error code returned by the CLAP stream.
28    pub fn code(&self) -> i64 {
29        self.code
30    }
31}
32
33impl Display for StreamError {
34    #[inline]
35    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
36        write!(f, "CLAP stream error (code: {})", self.code)
37    }
38}
39
40impl Error for StreamError {}
41
42/// A CLAP data stream that can be read from.
43///
44/// This helper struct is designed to work with the standard [`Read`] trait.
45#[repr(C)]
46pub struct InputStream<'a>(clap_istream, PhantomData<(&'a mut clap_istream, *const ())>);
47
48impl<'a> InputStream<'a> {
49    /// Creates a new input stream for an existing [reader](Read) implementation.
50    pub fn from_reader<R: Read + Sized + 'a>(reader: &'a mut R) -> Self {
51        Self(
52            clap_istream {
53                ctx: reader as *mut R as *mut _,
54                read: Some(read::<R>),
55            },
56            PhantomData,
57        )
58    }
59
60    /// Crates a new input stream for a C FFI-compatible pointer.
61    ///
62    /// # Safety
63    ///
64    /// The caller must ensure the given `clap_istream` instance is valid.
65    #[inline]
66    pub unsafe fn from_raw_mut(raw: &mut clap_istream) -> &mut Self {
67        &mut *(raw as *mut _ as *mut _)
68    }
69
70    /// Returns this input stream as a C FFI-compatible pointer.
71    #[inline]
72    pub fn as_raw_mut(&mut self) -> &mut clap_istream {
73        &mut self.0
74    }
75}
76
77impl Read for InputStream<'_> {
78    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
79        let ret = if let Some(read) = self.0.read {
80            // SAFETY: this function pointer is guaranteed to be valid by from_raw_mut and from_reader
81            unsafe { read(&self.0, buf.as_mut_ptr().cast(), buf.len() as u64) }
82        } else {
83            return Ok(0);
84        };
85        match ret {
86            i if i >= 0 => Ok(usize::try_from(i).map_err(std::io::Error::other)?),
87            code => Err(std::io::Error::other(StreamError { code })),
88        }
89    }
90}
91
92/// A CLAP data stream that can be written to.
93///
94/// This helper struct is designed to work with the standard [`Write`] trait.
95#[repr(C)]
96pub struct OutputStream<'a>(clap_ostream, PhantomData<(&'a mut clap_ostream, *const ())>);
97
98impl<'a> OutputStream<'a> {
99    /// Creates a new output stream for an existing [write](Write) implementation.
100    pub fn from_writer<W: Write + Sized + 'a>(writer: &'a mut W) -> Self {
101        Self(
102            clap_ostream {
103                ctx: writer as *mut W as *mut _,
104                write: Some(write::<W>),
105            },
106            PhantomData,
107        )
108    }
109
110    /// Crates a new output stream for a C FFI-compatible pointer.
111    ///
112    /// # Safety
113    ///
114    /// The caller must ensure the given `clap_ostream` instance is valid.
115    #[inline]
116    pub unsafe fn from_raw_mut(raw: &mut clap_ostream) -> &mut Self {
117        &mut *(raw as *mut _ as *mut _)
118    }
119
120    /// Returns this output stream as a C FFI-compatible pointer.
121    #[inline]
122    pub fn as_raw_mut(&mut self) -> &mut clap_ostream {
123        &mut self.0
124    }
125}
126
127impl Write for OutputStream<'_> {
128    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
129        let ret = if let Some(write) = self.0.write {
130            // SAFETY: this function pointer is guaranteed to be valid by from_raw_mut and from_reader
131            unsafe { write(&self.0, buf.as_ptr().cast(), buf.len() as u64) }
132        } else {
133            return Ok(0);
134        };
135
136        match ret {
137            i if i >= 0 => Ok(usize::try_from(i).map_err(std::io::Error::other)?),
138            code => Err(std::io::Error::other(StreamError { code })),
139        }
140    }
141
142    #[inline]
143    fn flush(&mut self) -> std::io::Result<()> {
144        Ok(())
145    }
146}
147
148// Guaranteed to not be able to overflow either i64 or usize
149fn saturating_from_u64(val: u64) -> usize {
150    // Cap size to i64 first, so that return value range is coherent
151    let val = i64::try_from(val).unwrap_or(i64::MAX);
152
153    // Can only happen on <= 32bit platforms
154    usize::try_from(val).unwrap_or(usize::MAX)
155}
156
157#[allow(clippy::missing_safety_doc)]
158unsafe extern "C" fn read<R: Read + Sized>(
159    istream: *const clap_istream,
160    buffer: *mut c_void,
161    size: u64,
162) -> i64 {
163    let reader = &mut *((*istream).ctx as *mut R);
164    let size = saturating_from_u64(size);
165
166    let buffer = slice_from_external_parts_mut(buffer as *mut u8, size);
167
168    match handle_interrupted(|| reader.read(buffer)) {
169        #[allow(clippy::cast_possible_wrap)]
170        Ok(read) => {
171            // Read implementation is bad. We return the size of the buffer instead.
172            if read > size {
173                // We know size fits in i64
174                size as i64
175            } else {
176                // Since read <= size and size fits in i64, then read also fits in i64
177                read as i64
178            }
179        }
180        Err(_) => -1,
181    }
182}
183
184#[allow(clippy::missing_safety_doc)]
185unsafe extern "C" fn write<W: Write + Sized>(
186    ostream: *const clap_ostream,
187    buffer: *const c_void,
188    size: u64,
189) -> i64 {
190    let writer = &mut *((*ostream).ctx as *mut W);
191    let size = saturating_from_u64(size);
192
193    let buffer = slice_from_external_parts(buffer as *const u8, size);
194
195    match handle_interrupted(|| writer.write(buffer)) {
196        #[allow(clippy::cast_possible_wrap)]
197        Ok(written) => {
198            // Read implementation is bad. We return the size of the buffer instead.
199            if written > size {
200                // We know size fits in i64
201                size as i64
202            } else {
203                // Since written <= size and size fits in i64, then written also firs in i64
204                written as i64
205            }
206        }
207        Err(_) => -1,
208    }
209}
210
211fn handle_interrupted<F: FnMut() -> std::io::Result<usize>>(
212    mut handler: F,
213) -> std::io::Result<usize> {
214    const MAX_ATTEMPTS: u8 = 5;
215    let mut attempts = 0u8;
216
217    loop {
218        match handler() {
219            Err(e) if e.kind() == ErrorKind::Interrupted && attempts < MAX_ATTEMPTS => {
220                attempts += 1
221            }
222            res => return res,
223        }
224    }
225}
226
227#[cfg(test)]
228mod test {
229    extern crate static_assertions as sa;
230    use super::*;
231    use std::io::Cursor;
232
233    sa::assert_not_impl_any!(InputStream: Send, Sync);
234    sa::assert_not_impl_any!(OutputStream: Send, Sync);
235
236    #[test]
237    fn input_streams_work() {
238        let src = b"Hello";
239        let mut buf = vec![0; 5];
240        let mut cursor = Cursor::new(src);
241
242        let mut stream = InputStream::from_reader(&mut cursor);
243        let res = stream.read(&mut buf).unwrap();
244        assert_eq!(res, 5);
245        assert_eq!(&buf, b"Hello");
246    }
247
248    #[test]
249    fn output_streams_work() {
250        let mut buf = vec![];
251
252        let mut stream = OutputStream::from_writer(&mut buf);
253        let res = stream.write(b"Hello").unwrap();
254
255        assert_eq!(res, 5);
256        assert_eq!(&buf, b"Hello");
257    }
258}