Skip to main content

rustls_util/
stream.rs

1#![allow(clippy::std_instead_of_core)] // awaits core::io::IoSlice in stable (1.98)
2use std::io::{BufRead, IoSlice, Read, Result, Write};
3
4use rustls::{Connection, VecInput};
5
6use crate::complete_io;
7
8/// This type implements `io::Read` and `io::Write`, encapsulating
9/// a Connection `C` and an underlying transport `T`, such as a socket.
10///
11/// Relies on [`complete_io()`] to perform the necessary I/O.
12///
13/// This allows you to use a rustls Connection like a normal stream.
14///
15/// [`complete_io()`]: crate::complete_io()
16#[expect(clippy::exhaustive_structs)]
17#[derive(Debug)]
18pub struct Stream<'a, C: 'a + ?Sized, T: 'a + Read + Write + ?Sized> {
19    /// Our TLS connection
20    pub conn: &'a mut C,
21
22    /// The underlying transport, like a socket
23    pub sock: &'a mut T,
24
25    /// The input buffer
26    pub input: &'a mut VecInput,
27}
28
29impl<'a, C, T> Stream<'a, C, T>
30where
31    C: 'a + Connection,
32    T: 'a + Read + Write,
33{
34    /// Make a new Stream using the Connection `conn` and socket-like object
35    /// `sock`.  This does not fail and does no IO.
36    pub fn new(input: &'a mut VecInput, conn: &'a mut C, sock: &'a mut T) -> Self {
37        Self { conn, sock, input }
38    }
39
40    /// If we're handshaking, complete all the IO for that.
41    /// If we have data to write, write it all.
42    fn complete_prior_io(&mut self) -> Result<()> {
43        if self.conn.is_handshaking() {
44            complete_io(self.sock, self.input, self.conn)?;
45        }
46
47        if self.conn.wants_write() {
48            complete_io(self.sock, self.input, self.conn)?;
49        }
50
51        Ok(())
52    }
53
54    fn prepare_read(&mut self) -> Result<()> {
55        self.complete_prior_io()?;
56
57        // We call complete_io() in a loop since a single call may read only
58        // a partial packet from the underlying transport. A full packet is
59        // needed to get more plaintext, which we must do if EOF has not been
60        // hit.
61        while self.conn.wants_read() {
62            if complete_io(self.sock, self.input, self.conn)?.0 == 0 {
63                break;
64            }
65        }
66
67        Ok(())
68    }
69
70    // Implements `BufRead::fill_buf` but with more flexible lifetimes, so StreamOwned can reuse it
71    fn fill_buf(mut self) -> Result<&'a [u8]> {
72        self.prepare_read()?;
73        self.conn.reader().into_first_chunk()
74    }
75}
76
77impl<'a, C, T> Read for Stream<'a, C, T>
78where
79    C: 'a + Connection,
80    T: 'a + Read + Write,
81{
82    fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
83        self.prepare_read()?;
84        self.conn.reader().read(buf)
85    }
86}
87
88impl<'a, C, T> BufRead for Stream<'a, C, T>
89where
90    C: 'a + Connection,
91    T: 'a + Read + Write,
92{
93    fn fill_buf(&mut self) -> Result<&[u8]> {
94        // reborrow to get an owned `Stream`
95        Stream {
96            conn: self.conn,
97            sock: self.sock,
98            input: self.input,
99        }
100        .fill_buf()
101    }
102
103    fn consume(&mut self, amt: usize) {
104        self.conn.reader().consume(amt)
105    }
106}
107
108impl<'a, C, T> Write for Stream<'a, C, T>
109where
110    C: 'a + Connection,
111    T: 'a + Read + Write,
112{
113    fn write(&mut self, buf: &[u8]) -> Result<usize> {
114        self.complete_prior_io()?;
115
116        let len = self.conn.writer().write(buf)?;
117
118        // Try to write the underlying transport here, but don't let
119        // any errors mask the fact we've consumed `len` bytes.
120        // Callers will learn of permanent errors on the next call.
121        let _ = complete_io(self.sock, self.input, self.conn);
122
123        Ok(len)
124    }
125
126    fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> Result<usize> {
127        self.complete_prior_io()?;
128
129        let len = self
130            .conn
131            .writer()
132            .write_vectored(bufs)?;
133
134        // Try to write the underlying transport here, but don't let
135        // any errors mask the fact we've consumed `len` bytes.
136        // Callers will learn of permanent errors on the next call.
137        let _ = complete_io(self.sock, self.input, self.conn);
138
139        Ok(len)
140    }
141
142    fn flush(&mut self) -> Result<()> {
143        self.complete_prior_io()?;
144
145        self.conn.writer().flush()?;
146        if self.conn.wants_write() {
147            complete_io(self.sock, self.input, self.conn)?;
148        }
149        Ok(())
150    }
151}
152
153/// This type implements `io::Read` and `io::Write`, encapsulating
154/// and owning a Connection `C` and an underlying transport `T`, such as a socket.
155///
156/// Relies on [`complete_io()`] to perform the necessary I/O.
157///
158/// This allows you to use a rustls Connection like a normal stream.
159///
160/// [`complete_io()`]: crate::complete_io()
161#[expect(clippy::exhaustive_structs)]
162#[derive(Debug)]
163pub struct StreamOwned<C: Sized, T: Read + Write + Sized> {
164    /// Our connection
165    pub conn: C,
166
167    /// The underlying transport, like a socket
168    pub sock: T,
169
170    /// The input buffer
171    pub input: VecInput,
172}
173
174impl<C, T> StreamOwned<C, T>
175where
176    C: Connection,
177    T: Read + Write,
178{
179    /// Make a new StreamOwned taking the Connection `conn` and socket-like
180    /// object `sock`.  This does not fail and does no IO.
181    ///
182    /// This is the same as `Stream::new` except `conn` and `sock` are
183    /// moved into the StreamOwned.
184    pub fn new(conn: C, sock: T) -> Self {
185        Self {
186            conn,
187            sock,
188            input: VecInput::default(),
189        }
190    }
191
192    /// Get a reference to the underlying socket
193    pub fn get_ref(&self) -> &T {
194        &self.sock
195    }
196
197    /// Get a mutable reference to the underlying socket
198    pub fn get_mut(&mut self) -> &mut T {
199        &mut self.sock
200    }
201
202    /// Extract the `conn` and `sock` parts from the `StreamOwned`
203    pub fn into_parts(self) -> (C, T) {
204        (self.conn, self.sock)
205    }
206}
207
208impl<'a, C, T> StreamOwned<C, T>
209where
210    C: Connection,
211    T: Read + Write,
212{
213    fn as_stream(&'a mut self) -> Stream<'a, C, T> {
214        Stream {
215            conn: &mut self.conn,
216            sock: &mut self.sock,
217            input: &mut self.input,
218        }
219    }
220}
221
222impl<C, T> Read for StreamOwned<C, T>
223where
224    C: Connection,
225    T: Read + Write,
226{
227    fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
228        self.as_stream().read(buf)
229    }
230}
231
232impl<C, T> BufRead for StreamOwned<C, T>
233where
234    C: Connection,
235    T: Read + Write,
236{
237    fn fill_buf(&mut self) -> Result<&[u8]> {
238        self.as_stream().fill_buf()
239    }
240
241    fn consume(&mut self, amt: usize) {
242        self.as_stream().consume(amt)
243    }
244}
245
246impl<C, T> Write for StreamOwned<C, T>
247where
248    C: Connection,
249    T: Read + Write,
250{
251    fn write(&mut self, buf: &[u8]) -> Result<usize> {
252        self.as_stream().write(buf)
253    }
254
255    fn flush(&mut self) -> Result<()> {
256        self.as_stream().flush()
257    }
258}
259
260#[cfg(test)]
261mod tests {
262    use std::net::TcpStream;
263
264    use rustls::{ClientConnection, ServerConnection};
265
266    use super::{Stream, StreamOwned};
267
268    #[test]
269    fn stream_can_be_created_for_connection_and_tcpstream() {
270        type _Test<'a> = Stream<'a, ClientConnection, TcpStream>;
271    }
272
273    #[test]
274    fn streamowned_can_be_created_for_client_and_tcpstream() {
275        type _Test = StreamOwned<ClientConnection, TcpStream>;
276    }
277
278    #[test]
279    fn streamowned_can_be_created_for_server_and_tcpstream() {
280        type _Test = StreamOwned<ServerConnection, TcpStream>;
281    }
282}