Skip to content

Commit 5fa2225

Browse files
committed
Add tests for holding back alerts
1 parent 1702019 commit 5fa2225

File tree

2 files changed

+129
-4
lines changed

2 files changed

+129
-4
lines changed

src/server.rs

Lines changed: 58 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use std::future::Future;
1+
use std::future::{poll_fn, Future};
22
use std::io::{self, BufRead as _};
33
#[cfg(unix)]
44
use std::os::unix::io::{AsRawFd, RawFd};
@@ -10,7 +10,7 @@ use std::task::{Context, Poll};
1010

1111
use rustls::server::AcceptedAlert;
1212
use rustls::{ServerConfig, ServerConnection};
13-
use tokio::io::{AsyncBufRead, AsyncRead, AsyncWrite, ReadBuf};
13+
use tokio::io::{AsyncBufRead, AsyncRead, AsyncWrite, AsyncWriteExt, ReadBuf};
1414

1515
use crate::common::{IoSession, MidHandshake, Stream, SyncReadAdapter, SyncWriteAdapter, TlsState};
1616

@@ -111,7 +111,7 @@ where
111111
/// let listener = tokio::net::TcpListener::bind("127.0.0.1:4443").await.unwrap();
112112
/// let (stream, _) = listener.accept().await.unwrap();
113113
///
114-
/// let acceptor = tokio_rustls::LazyConfigAcceptor::new(rustls::server::Acceptor::default(), stream);
114+
/// let acceptor = tokio_rustls::LazyConfigAcceptor::new(rustls::server::Acceptor::default(), stream).send_alert(false);
115115
/// tokio::pin!(acceptor);
116116
///
117117
/// match acceptor.as_mut().await {
@@ -146,6 +146,57 @@ where
146146
None => None,
147147
}
148148
}
149+
150+
/// Writes a stored alert, consuming the alert (if any) and IO.
151+
pub async fn write_alert(&mut self) -> io::Result<()> {
152+
let Some(alert) = self.take_alert() else {
153+
return Ok(());
154+
};
155+
let Some(io) = self.take_io() else {
156+
return Ok(());
157+
};
158+
WritingAlert {
159+
io,
160+
alert: Some(alert),
161+
}
162+
.await
163+
}
164+
}
165+
166+
struct WritingAlert<IO> {
167+
io: IO,
168+
alert: Option<AcceptedAlert>,
169+
}
170+
171+
impl<IO> Future for WritingAlert<IO>
172+
where
173+
IO: AsyncRead + AsyncWrite + Unpin,
174+
{
175+
type Output = Result<(), io::Error>;
176+
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
177+
let this = self.get_mut();
178+
let io = &mut this.io;
179+
loop {
180+
if let Some(mut alert) = this.alert.take() {
181+
match alert.write(&mut SyncWriteAdapter { io, cx }) {
182+
Err(e) if e.kind() == io::ErrorKind::WouldBlock => {
183+
this.alert = Some(alert);
184+
return Poll::Pending;
185+
}
186+
Err(e) => {
187+
return Poll::Ready(Err(io::Error::new(io::ErrorKind::InvalidData, e)));
188+
}
189+
Ok(0) => {
190+
return Poll::Ready(Ok(()));
191+
}
192+
Ok(n) => {
193+
this.alert = Some(alert);
194+
continue;
195+
}
196+
};
197+
}
198+
}
199+
}
149200
}
150201

151202
impl<IO> Future for LazyConfigAcceptor<IO>
@@ -199,7 +250,10 @@ where
199250
Ok(None) => {}
200251
Err((err, alert)) => match this.send_alert {
201252
true => this.alert = Some(AlertState::Sending(err, alert)),
202-
false => this.alert = Some(AlertState::Saved(alert)),
253+
false => {
254+
this.alert = Some(AlertState::Saved(alert));
255+
return Poll::Ready(Err(io::Error::new(io::ErrorKind::InvalidData, err)));
256+
}
203257
},
204258
}
205259
}

tests/test.rs

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,77 @@ async fn lazy_config_acceptor_alert() {
318318
assert_eq!(received, fatal_alert_decode_error)
319319
}
320320

321+
#[tokio::test]
322+
async fn lazy_config_acceptor_return_http() {
323+
let (mut cstream, sstream) = tokio::io::duplex(1024);
324+
325+
let (tx, rx) = oneshot::channel();
326+
327+
tokio::spawn(async move {
328+
// This is write instead of write_all because of the short duplex size, which is necessarily
329+
// symmetrical. We never finish writing because the LazyConfigAcceptor returns an error
330+
let _ = cstream.write(b"not tls").await;
331+
let mut buf = Vec::new();
332+
cstream.read_to_end(&mut buf).await.unwrap();
333+
tx.send(buf).unwrap();
334+
});
335+
336+
let acceptor =
337+
LazyConfigAcceptor::new(rustls::server::Acceptor::default(), sstream).send_alert(false);
338+
tokio::pin!(acceptor);
339+
340+
let Ok(accept_result) = time::timeout(Duration::from_secs(3), acceptor.as_mut()).await else {
341+
panic!("timeout");
342+
};
343+
344+
assert!(accept_result.is_err());
345+
let mut io = acceptor.take_io().unwrap();
346+
io.write_all(b"HTTP/1.1 400 Invalid Input\r\n\r\n\r\nNot TLS\n")
347+
.await
348+
.unwrap();
349+
io.shutdown().await.unwrap();
350+
351+
let Ok(Ok(received)) = time::timeout(Duration::from_secs(3), rx).await else {
352+
panic!("failed to receive");
353+
};
354+
355+
let recv = b"HTTP/1.1 400 Invalid Input\r\n\r\n\r\nNot TLS\n";
356+
assert_eq!(received, recv)
357+
}
358+
359+
#[tokio::test]
360+
async fn lazy_config_acceptor_manual_alert() {
361+
let (mut cstream, sstream) = tokio::io::duplex(2);
362+
363+
let (tx, rx) = oneshot::channel();
364+
365+
tokio::spawn(async move {
366+
// This is write instead of write_all because of the short duplex size, which is necessarily
367+
// symmetrical. We never finish writing because the LazyConfigAcceptor returns an error
368+
let _ = cstream.write(b"not tls").await;
369+
let mut buf = Vec::new();
370+
cstream.read_to_end(&mut buf).await.unwrap();
371+
tx.send(buf).unwrap();
372+
});
373+
374+
let acceptor =
375+
LazyConfigAcceptor::new(rustls::server::Acceptor::default(), sstream).send_alert(false);
376+
tokio::pin!(acceptor);
377+
378+
let Ok(accept_result) = time::timeout(Duration::from_secs(3), acceptor.as_mut()).await else {
379+
panic!("timeout");
380+
};
381+
382+
assert!(accept_result.is_err());
383+
acceptor.write_alert().await.unwrap();
384+
let Ok(Ok(received)) = time::timeout(Duration::from_secs(3), rx).await else {
385+
panic!("failed to receive");
386+
};
387+
388+
let fatal_alert_decode_error = b"\x15\x03\x03\x00\x02\x02\x32";
389+
assert_eq!(received, fatal_alert_decode_error)
390+
}
391+
321392
#[tokio::test]
322393
async fn handshake_flush_pending() -> io::Result<()> {
323394
pass_impl(utils::FlushWrapper::new, false).await

0 commit comments

Comments
 (0)