1 | //! Asynchronous TLS/SSL streams for Tokio using [Rustls](https://github.com/rustls/rustls). |
2 | //! |
3 | //! # Why do I need to call `poll_flush`? |
4 | //! |
5 | //! Most TLS implementations will have an internal buffer to improve throughput, |
6 | //! and rustls is no exception. |
7 | //! |
8 | //! When we write data to `TlsStream`, we always write rustls buffer first, |
9 | //! then take out rustls encrypted data packet, and write it to data channel (like TcpStream). |
10 | //! When data channel is pending, some data may remain in rustls buffer. |
11 | //! |
12 | //! `tokio-rustls` To keep it simple and correct, [TlsStream] will behave like `BufWriter`. |
13 | //! For `TlsStream<TcpStream>`, this means that data written by `poll_write` is not guaranteed to be written to `TcpStream`. |
14 | //! You must call `poll_flush` to ensure that it is written to `TcpStream`. |
15 | //! |
16 | //! You should call `poll_flush` at the appropriate time, |
17 | //! such as when a period of `poll_write` write is complete and there is no more data to write. |
18 | //! |
19 | //! ## Why don't we write during `poll_read`? |
20 | //! |
21 | //! We did this in the early days of `tokio-rustls`, but it caused some bugs. |
22 | //! We can solve these bugs through some solutions, but this will cause performance degradation (reverse false wakeup). |
23 | //! |
24 | //! And reverse write will also prevent us implement full duplex in the future. |
25 | //! |
26 | //! see <https://github.com/tokio-rs/tls/issues/40> |
27 | //! |
28 | //! ## Why can't we handle it like `native-tls`? |
29 | //! |
30 | //! When data channel returns to pending, `native-tls` will falsely report the number of bytes it consumes. |
31 | //! This means that if data written by `poll_write` is not actually written to data channel, it will not return `Ready`. |
32 | //! Thus avoiding the call of `poll_flush`. |
33 | //! |
34 | //! but which does not conform to convention of `AsyncWrite` trait. |
35 | //! This means that if you give inconsistent data in two `poll_write`, it may cause unexpected behavior. |
36 | //! |
37 | //! see <https://github.com/tokio-rs/tls/issues/41> |
38 | |
39 | use std::future::Future; |
40 | use std::io; |
41 | #[cfg (unix)] |
42 | use std::os::unix::io::{AsRawFd, RawFd}; |
43 | #[cfg (windows)] |
44 | use std::os::windows::io::{AsRawSocket, RawSocket}; |
45 | use std::pin::Pin; |
46 | use std::sync::Arc; |
47 | use std::task::{Context, Poll}; |
48 | |
49 | pub use rustls; |
50 | |
51 | use rustls::pki_types::ServerName; |
52 | use rustls::server::AcceptedAlert; |
53 | use rustls::{ClientConfig, ClientConnection, CommonState, ServerConfig, ServerConnection}; |
54 | use tokio::io::{AsyncBufRead, AsyncRead, AsyncWrite, ReadBuf}; |
55 | |
56 | macro_rules! ready { |
57 | ( $e:expr ) => { |
58 | match $e { |
59 | std::task::Poll::Ready(t) => t, |
60 | std::task::Poll::Pending => return std::task::Poll::Pending, |
61 | } |
62 | }; |
63 | } |
64 | |
65 | pub mod client; |
66 | mod common; |
67 | use common::{MidHandshake, TlsState}; |
68 | pub mod server; |
69 | |
70 | /// A wrapper around a `rustls::ClientConfig`, providing an async `connect` method. |
71 | #[derive (Clone)] |
72 | pub struct TlsConnector { |
73 | inner: Arc<ClientConfig>, |
74 | #[cfg (feature = "early-data" )] |
75 | early_data: bool, |
76 | } |
77 | |
78 | /// A wrapper around a `rustls::ServerConfig`, providing an async `accept` method. |
79 | #[derive (Clone)] |
80 | pub struct TlsAcceptor { |
81 | inner: Arc<ServerConfig>, |
82 | } |
83 | |
84 | impl From<Arc<ClientConfig>> for TlsConnector { |
85 | fn from(inner: Arc<ClientConfig>) -> TlsConnector { |
86 | TlsConnector { |
87 | inner, |
88 | #[cfg (feature = "early-data" )] |
89 | early_data: false, |
90 | } |
91 | } |
92 | } |
93 | |
94 | impl From<Arc<ServerConfig>> for TlsAcceptor { |
95 | fn from(inner: Arc<ServerConfig>) -> TlsAcceptor { |
96 | TlsAcceptor { inner } |
97 | } |
98 | } |
99 | |
100 | impl TlsConnector { |
101 | /// Enable 0-RTT. |
102 | /// |
103 | /// If you want to use 0-RTT, |
104 | /// You must also set `ClientConfig.enable_early_data` to `true`. |
105 | #[cfg (feature = "early-data" )] |
106 | pub fn early_data(mut self, flag: bool) -> TlsConnector { |
107 | self.early_data = flag; |
108 | self |
109 | } |
110 | |
111 | #[inline ] |
112 | pub fn connect<IO>(&self, domain: ServerName<'static>, stream: IO) -> Connect<IO> |
113 | where |
114 | IO: AsyncRead + AsyncWrite + Unpin, |
115 | { |
116 | self.connect_with(domain, stream, |_| ()) |
117 | } |
118 | |
119 | pub fn connect_with<IO, F>(&self, domain: ServerName<'static>, stream: IO, f: F) -> Connect<IO> |
120 | where |
121 | IO: AsyncRead + AsyncWrite + Unpin, |
122 | F: FnOnce(&mut ClientConnection), |
123 | { |
124 | let mut session = match ClientConnection::new(self.inner.clone(), domain) { |
125 | Ok(session) => session, |
126 | Err(error) => { |
127 | return Connect(MidHandshake::Error { |
128 | io: stream, |
129 | // TODO(eliza): should this really return an `io::Error`? |
130 | // Probably not... |
131 | error: io::Error::new(io::ErrorKind::Other, error), |
132 | }); |
133 | } |
134 | }; |
135 | f(&mut session); |
136 | |
137 | Connect(MidHandshake::Handshaking(client::TlsStream { |
138 | io: stream, |
139 | |
140 | #[cfg (not(feature = "early-data" ))] |
141 | state: TlsState::Stream, |
142 | |
143 | #[cfg (feature = "early-data" )] |
144 | state: if self.early_data && session.early_data().is_some() { |
145 | TlsState::EarlyData(0, Vec::new()) |
146 | } else { |
147 | TlsState::Stream |
148 | }, |
149 | |
150 | #[cfg (feature = "early-data" )] |
151 | early_waker: None, |
152 | |
153 | session, |
154 | })) |
155 | } |
156 | |
157 | /// Get a read-only reference to underlying config |
158 | pub fn config(&self) -> &Arc<ClientConfig> { |
159 | &self.inner |
160 | } |
161 | } |
162 | |
163 | impl TlsAcceptor { |
164 | #[inline ] |
165 | pub fn accept<IO>(&self, stream: IO) -> Accept<IO> |
166 | where |
167 | IO: AsyncRead + AsyncWrite + Unpin, |
168 | { |
169 | self.accept_with(stream, |_| ()) |
170 | } |
171 | |
172 | pub fn accept_with<IO, F>(&self, stream: IO, f: F) -> Accept<IO> |
173 | where |
174 | IO: AsyncRead + AsyncWrite + Unpin, |
175 | F: FnOnce(&mut ServerConnection), |
176 | { |
177 | let mut session = match ServerConnection::new(self.inner.clone()) { |
178 | Ok(session) => session, |
179 | Err(error) => { |
180 | return Accept(MidHandshake::Error { |
181 | io: stream, |
182 | // TODO(eliza): should this really return an `io::Error`? |
183 | // Probably not... |
184 | error: io::Error::new(io::ErrorKind::Other, error), |
185 | }); |
186 | } |
187 | }; |
188 | f(&mut session); |
189 | |
190 | Accept(MidHandshake::Handshaking(server::TlsStream { |
191 | session, |
192 | io: stream, |
193 | state: TlsState::Stream, |
194 | })) |
195 | } |
196 | |
197 | /// Get a read-only reference to underlying config |
198 | pub fn config(&self) -> &Arc<ServerConfig> { |
199 | &self.inner |
200 | } |
201 | } |
202 | |
203 | pub struct LazyConfigAcceptor<IO> { |
204 | acceptor: rustls::server::Acceptor, |
205 | io: Option<IO>, |
206 | alert: Option<(rustls::Error, AcceptedAlert)>, |
207 | } |
208 | |
209 | impl<IO> LazyConfigAcceptor<IO> |
210 | where |
211 | IO: AsyncRead + AsyncWrite + Unpin, |
212 | { |
213 | #[inline ] |
214 | pub fn new(acceptor: rustls::server::Acceptor, io: IO) -> Self { |
215 | Self { |
216 | acceptor, |
217 | io: Some(io), |
218 | alert: None, |
219 | } |
220 | } |
221 | |
222 | /// Takes back the client connection. Will return `None` if called more than once or if the |
223 | /// connection has been accepted. |
224 | /// |
225 | /// # Example |
226 | /// |
227 | /// ```no_run |
228 | /// # fn choose_server_config( |
229 | /// # _: rustls::server::ClientHello, |
230 | /// # ) -> std::sync::Arc<rustls::ServerConfig> { |
231 | /// # unimplemented!(); |
232 | /// # } |
233 | /// # #[allow (unused_variables)] |
234 | /// # async fn listen() { |
235 | /// use tokio::io::AsyncWriteExt; |
236 | /// let listener = tokio::net::TcpListener::bind("127.0.0.1:4443" ).await.unwrap(); |
237 | /// let (stream, _) = listener.accept().await.unwrap(); |
238 | /// |
239 | /// let acceptor = tokio_rustls::LazyConfigAcceptor::new(rustls::server::Acceptor::default(), stream); |
240 | /// tokio::pin!(acceptor); |
241 | /// |
242 | /// match acceptor.as_mut().await { |
243 | /// Ok(start) => { |
244 | /// let clientHello = start.client_hello(); |
245 | /// let config = choose_server_config(clientHello); |
246 | /// let stream = start.into_stream(config).await.unwrap(); |
247 | /// // Proceed with handling the ServerConnection... |
248 | /// } |
249 | /// Err(err) => { |
250 | /// if let Some(mut stream) = acceptor.take_io() { |
251 | /// stream |
252 | /// .write_all( |
253 | /// format!("HTTP/1.1 400 Invalid Input \r\n\r\n\r\n{:?} \n" , err) |
254 | /// .as_bytes() |
255 | /// ) |
256 | /// .await |
257 | /// .unwrap(); |
258 | /// } |
259 | /// } |
260 | /// } |
261 | /// # } |
262 | /// ``` |
263 | pub fn take_io(&mut self) -> Option<IO> { |
264 | self.io.take() |
265 | } |
266 | } |
267 | |
268 | impl<IO> Future for LazyConfigAcceptor<IO> |
269 | where |
270 | IO: AsyncRead + AsyncWrite + Unpin, |
271 | { |
272 | type Output = Result<StartHandshake<IO>, io::Error>; |
273 | |
274 | fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { |
275 | let this = self.get_mut(); |
276 | loop { |
277 | let io = match this.io.as_mut() { |
278 | Some(io) => io, |
279 | None => { |
280 | return Poll::Ready(Err(io::Error::new( |
281 | io::ErrorKind::Other, |
282 | "acceptor cannot be polled after acceptance" , |
283 | ))) |
284 | } |
285 | }; |
286 | |
287 | if let Some((err, mut alert)) = this.alert.take() { |
288 | match alert.write(&mut common::SyncWriteAdapter { io, cx }) { |
289 | Err(e) if e.kind() == io::ErrorKind::WouldBlock => { |
290 | this.alert = Some((err, alert)); |
291 | return Poll::Pending; |
292 | } |
293 | Ok(0) | Err(_) => { |
294 | return Poll::Ready(Err(io::Error::new(io::ErrorKind::InvalidData, err))) |
295 | } |
296 | Ok(_) => { |
297 | this.alert = Some((err, alert)); |
298 | continue; |
299 | } |
300 | }; |
301 | } |
302 | |
303 | let mut reader = common::SyncReadAdapter { io, cx }; |
304 | match this.acceptor.read_tls(&mut reader) { |
305 | Ok(0) => return Err(io::ErrorKind::UnexpectedEof.into()).into(), |
306 | Ok(_) => {} |
307 | Err(e) if e.kind() == io::ErrorKind::WouldBlock => return Poll::Pending, |
308 | Err(e) => return Err(e).into(), |
309 | } |
310 | |
311 | match this.acceptor.accept() { |
312 | Ok(Some(accepted)) => { |
313 | let io = this.io.take().unwrap(); |
314 | return Poll::Ready(Ok(StartHandshake { accepted, io })); |
315 | } |
316 | Ok(None) => {} |
317 | Err((err, alert)) => { |
318 | this.alert = Some((err, alert)); |
319 | } |
320 | } |
321 | } |
322 | } |
323 | } |
324 | |
325 | pub struct StartHandshake<IO> { |
326 | accepted: rustls::server::Accepted, |
327 | io: IO, |
328 | } |
329 | |
330 | impl<IO> StartHandshake<IO> |
331 | where |
332 | IO: AsyncRead + AsyncWrite + Unpin, |
333 | { |
334 | pub fn client_hello(&self) -> rustls::server::ClientHello<'_> { |
335 | self.accepted.client_hello() |
336 | } |
337 | |
338 | pub fn into_stream(self, config: Arc<ServerConfig>) -> Accept<IO> { |
339 | self.into_stream_with(config, |_| ()) |
340 | } |
341 | |
342 | pub fn into_stream_with<F>(self, config: Arc<ServerConfig>, f: F) -> Accept<IO> |
343 | where |
344 | F: FnOnce(&mut ServerConnection), |
345 | { |
346 | let mut conn = match self.accepted.into_connection(config) { |
347 | Ok(conn) => conn, |
348 | Err((error, alert)) => { |
349 | return Accept(MidHandshake::SendAlert { |
350 | io: self.io, |
351 | alert, |
352 | // TODO(eliza): should this really return an `io::Error`? |
353 | // Probably not... |
354 | error: io::Error::new(io::ErrorKind::InvalidData, error), |
355 | }); |
356 | } |
357 | }; |
358 | f(&mut conn); |
359 | |
360 | Accept(MidHandshake::Handshaking(server::TlsStream { |
361 | session: conn, |
362 | io: self.io, |
363 | state: TlsState::Stream, |
364 | })) |
365 | } |
366 | } |
367 | |
368 | /// Future returned from `TlsConnector::connect` which will resolve |
369 | /// once the connection handshake has finished. |
370 | pub struct Connect<IO>(MidHandshake<client::TlsStream<IO>>); |
371 | |
372 | /// Future returned from `TlsAcceptor::accept` which will resolve |
373 | /// once the accept handshake has finished. |
374 | pub struct Accept<IO>(MidHandshake<server::TlsStream<IO>>); |
375 | |
376 | /// Like [Connect], but returns `IO` on failure. |
377 | pub struct FallibleConnect<IO>(MidHandshake<client::TlsStream<IO>>); |
378 | |
379 | /// Like [Accept], but returns `IO` on failure. |
380 | pub struct FallibleAccept<IO>(MidHandshake<server::TlsStream<IO>>); |
381 | |
382 | impl<IO> Connect<IO> { |
383 | #[inline ] |
384 | pub fn into_fallible(self) -> FallibleConnect<IO> { |
385 | FallibleConnect(self.0) |
386 | } |
387 | |
388 | pub fn get_ref(&self) -> Option<&IO> { |
389 | match &self.0 { |
390 | MidHandshake::Handshaking(sess: &TlsStream) => Some(sess.get_ref().0), |
391 | MidHandshake::SendAlert { io: &IO, .. } => Some(io), |
392 | MidHandshake::Error { io: &IO, .. } => Some(io), |
393 | MidHandshake::End => None, |
394 | } |
395 | } |
396 | |
397 | pub fn get_mut(&mut self) -> Option<&mut IO> { |
398 | match &mut self.0 { |
399 | MidHandshake::Handshaking(sess: &mut TlsStream) => Some(sess.get_mut().0), |
400 | MidHandshake::SendAlert { io: &mut IO, .. } => Some(io), |
401 | MidHandshake::Error { io: &mut IO, .. } => Some(io), |
402 | MidHandshake::End => None, |
403 | } |
404 | } |
405 | } |
406 | |
407 | impl<IO> Accept<IO> { |
408 | #[inline ] |
409 | pub fn into_fallible(self) -> FallibleAccept<IO> { |
410 | FallibleAccept(self.0) |
411 | } |
412 | |
413 | pub fn get_ref(&self) -> Option<&IO> { |
414 | match &self.0 { |
415 | MidHandshake::Handshaking(sess: &TlsStream) => Some(sess.get_ref().0), |
416 | MidHandshake::SendAlert { io: &IO, .. } => Some(io), |
417 | MidHandshake::Error { io: &IO, .. } => Some(io), |
418 | MidHandshake::End => None, |
419 | } |
420 | } |
421 | |
422 | pub fn get_mut(&mut self) -> Option<&mut IO> { |
423 | match &mut self.0 { |
424 | MidHandshake::Handshaking(sess: &mut TlsStream) => Some(sess.get_mut().0), |
425 | MidHandshake::SendAlert { io: &mut IO, .. } => Some(io), |
426 | MidHandshake::Error { io: &mut IO, .. } => Some(io), |
427 | MidHandshake::End => None, |
428 | } |
429 | } |
430 | } |
431 | |
432 | impl<IO: AsyncRead + AsyncWrite + Unpin> Future for Connect<IO> { |
433 | type Output = io::Result<client::TlsStream<IO>>; |
434 | |
435 | #[inline ] |
436 | fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { |
437 | Pin::new(&mut self.0).poll(cx).map_err(|(err: Error, _)| err) |
438 | } |
439 | } |
440 | |
441 | impl<IO: AsyncRead + AsyncWrite + Unpin> Future for Accept<IO> { |
442 | type Output = io::Result<server::TlsStream<IO>>; |
443 | |
444 | #[inline ] |
445 | fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { |
446 | Pin::new(&mut self.0).poll(cx).map_err(|(err: Error, _)| err) |
447 | } |
448 | } |
449 | |
450 | impl<IO: AsyncRead + AsyncWrite + Unpin> Future for FallibleConnect<IO> { |
451 | type Output = Result<client::TlsStream<IO>, (io::Error, IO)>; |
452 | |
453 | #[inline ] |
454 | fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { |
455 | Pin::new(&mut self.0).poll(cx) |
456 | } |
457 | } |
458 | |
459 | impl<IO: AsyncRead + AsyncWrite + Unpin> Future for FallibleAccept<IO> { |
460 | type Output = Result<server::TlsStream<IO>, (io::Error, IO)>; |
461 | |
462 | #[inline ] |
463 | fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { |
464 | Pin::new(&mut self.0).poll(cx) |
465 | } |
466 | } |
467 | |
468 | /// Unified TLS stream type |
469 | /// |
470 | /// This abstracts over the inner `client::TlsStream` and `server::TlsStream`, so you can use |
471 | /// a single type to keep both client- and server-initiated TLS-encrypted connections. |
472 | #[allow (clippy::large_enum_variant)] // https://github.com/rust-lang/rust-clippy/issues/9798 |
473 | #[derive (Debug)] |
474 | pub enum TlsStream<T> { |
475 | Client(client::TlsStream<T>), |
476 | Server(server::TlsStream<T>), |
477 | } |
478 | |
479 | impl<T> TlsStream<T> { |
480 | pub fn get_ref(&self) -> (&T, &CommonState) { |
481 | use TlsStream::*; |
482 | match self { |
483 | Client(io) => { |
484 | let (io, session) = io.get_ref(); |
485 | (io, session) |
486 | } |
487 | Server(io) => { |
488 | let (io, session) = io.get_ref(); |
489 | (io, session) |
490 | } |
491 | } |
492 | } |
493 | |
494 | pub fn get_mut(&mut self) -> (&mut T, &mut CommonState) { |
495 | use TlsStream::*; |
496 | match self { |
497 | Client(io) => { |
498 | let (io, session) = io.get_mut(); |
499 | (io, &mut *session) |
500 | } |
501 | Server(io) => { |
502 | let (io, session) = io.get_mut(); |
503 | (io, &mut *session) |
504 | } |
505 | } |
506 | } |
507 | } |
508 | |
509 | impl<T> From<client::TlsStream<T>> for TlsStream<T> { |
510 | fn from(s: client::TlsStream<T>) -> Self { |
511 | Self::Client(s) |
512 | } |
513 | } |
514 | |
515 | impl<T> From<server::TlsStream<T>> for TlsStream<T> { |
516 | fn from(s: server::TlsStream<T>) -> Self { |
517 | Self::Server(s) |
518 | } |
519 | } |
520 | |
521 | #[cfg (unix)] |
522 | impl<S> AsRawFd for TlsStream<S> |
523 | where |
524 | S: AsRawFd, |
525 | { |
526 | fn as_raw_fd(&self) -> RawFd { |
527 | self.get_ref().0.as_raw_fd() |
528 | } |
529 | } |
530 | |
531 | #[cfg (windows)] |
532 | impl<S> AsRawSocket for TlsStream<S> |
533 | where |
534 | S: AsRawSocket, |
535 | { |
536 | fn as_raw_socket(&self) -> RawSocket { |
537 | self.get_ref().0.as_raw_socket() |
538 | } |
539 | } |
540 | |
541 | impl<T> AsyncRead for TlsStream<T> |
542 | where |
543 | T: AsyncRead + AsyncWrite + Unpin, |
544 | { |
545 | #[inline ] |
546 | fn poll_read( |
547 | self: Pin<&mut Self>, |
548 | cx: &mut Context<'_>, |
549 | buf: &mut ReadBuf<'_>, |
550 | ) -> Poll<io::Result<()>> { |
551 | match self.get_mut() { |
552 | TlsStream::Client(x: &mut TlsStream) => Pin::new(pointer:x).poll_read(cx, buf), |
553 | TlsStream::Server(x: &mut TlsStream) => Pin::new(pointer:x).poll_read(cx, buf), |
554 | } |
555 | } |
556 | } |
557 | |
558 | impl<T> AsyncBufRead for TlsStream<T> |
559 | where |
560 | T: AsyncRead + AsyncWrite + Unpin, |
561 | { |
562 | #[inline ] |
563 | fn poll_fill_buf(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<&[u8]>> { |
564 | match self.get_mut() { |
565 | TlsStream::Client(x: &mut TlsStream) => Pin::new(pointer:x).poll_fill_buf(cx), |
566 | TlsStream::Server(x: &mut TlsStream) => Pin::new(pointer:x).poll_fill_buf(cx), |
567 | } |
568 | } |
569 | |
570 | #[inline ] |
571 | fn consume(self: Pin<&mut Self>, amt: usize) { |
572 | match self.get_mut() { |
573 | TlsStream::Client(x: &mut TlsStream) => Pin::new(pointer:x).consume(amt), |
574 | TlsStream::Server(x: &mut TlsStream) => Pin::new(pointer:x).consume(amt), |
575 | } |
576 | } |
577 | } |
578 | |
579 | impl<T> AsyncWrite for TlsStream<T> |
580 | where |
581 | T: AsyncRead + AsyncWrite + Unpin, |
582 | { |
583 | #[inline ] |
584 | fn poll_write( |
585 | self: Pin<&mut Self>, |
586 | cx: &mut Context<'_>, |
587 | buf: &[u8], |
588 | ) -> Poll<io::Result<usize>> { |
589 | match self.get_mut() { |
590 | TlsStream::Client(x) => Pin::new(x).poll_write(cx, buf), |
591 | TlsStream::Server(x) => Pin::new(x).poll_write(cx, buf), |
592 | } |
593 | } |
594 | |
595 | #[inline ] |
596 | fn poll_write_vectored( |
597 | self: Pin<&mut Self>, |
598 | cx: &mut Context<'_>, |
599 | bufs: &[io::IoSlice<'_>], |
600 | ) -> Poll<io::Result<usize>> { |
601 | match self.get_mut() { |
602 | TlsStream::Client(x) => Pin::new(x).poll_write_vectored(cx, bufs), |
603 | TlsStream::Server(x) => Pin::new(x).poll_write_vectored(cx, bufs), |
604 | } |
605 | } |
606 | |
607 | #[inline ] |
608 | fn is_write_vectored(&self) -> bool { |
609 | match self { |
610 | TlsStream::Client(x) => x.is_write_vectored(), |
611 | TlsStream::Server(x) => x.is_write_vectored(), |
612 | } |
613 | } |
614 | |
615 | #[inline ] |
616 | fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> { |
617 | match self.get_mut() { |
618 | TlsStream::Client(x) => Pin::new(x).poll_flush(cx), |
619 | TlsStream::Server(x) => Pin::new(x).poll_flush(cx), |
620 | } |
621 | } |
622 | |
623 | #[inline ] |
624 | fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> { |
625 | match self.get_mut() { |
626 | TlsStream::Client(x) => Pin::new(x).poll_shutdown(cx), |
627 | TlsStream::Server(x) => Pin::new(x).poll_shutdown(cx), |
628 | } |
629 | } |
630 | } |
631 | |