use std::io::{self, BufRead, Write};
use std::pin::Pin;
use std::task::{Context, Poll};
use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
use super::crypto_connection::CryptoConnection;
use crate::async_stream::{AsyncPing, AsyncStream};
use crate::sync_adapter::{SyncReadAdapter, SyncWriteAdapter};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TlsState {
Stream,
ReadShutdown,
WriteShutdown,
FullyShutdown,
}
impl TlsState {
#[inline]
pub fn shutdown_read(&mut self) {
*self = match *self {
Self::WriteShutdown | Self::FullyShutdown => Self::FullyShutdown,
_ => Self::ReadShutdown,
};
}
#[inline]
pub fn shutdown_write(&mut self) {
*self = match *self {
Self::ReadShutdown | Self::FullyShutdown => Self::FullyShutdown,
_ => Self::WriteShutdown,
};
}
#[inline]
pub fn readable(&self) -> bool {
!matches!(*self, Self::ReadShutdown | Self::FullyShutdown)
}
#[inline]
pub fn writeable(&self) -> bool {
!matches!(*self, Self::WriteShutdown | Self::FullyShutdown)
}
}
pub struct CryptoTlsStream<IO> {
io: IO,
session: CryptoConnection,
state: TlsState,
need_flush: bool,
}
impl<IO> CryptoTlsStream<IO>
where
IO: AsyncStream,
{
pub fn new(io: IO, session: CryptoConnection) -> Self {
assert!(
!session.is_handshaking(),
"perform_crypto_handshake should have completed"
);
CryptoTlsStream {
io,
session,
state: TlsState::Stream,
need_flush: false,
}
}
pub fn into_inner(self) -> (IO, CryptoConnection) {
(self.io, self.session)
}
pub fn alpn_protocol(&self) -> Option<&[u8]> {
self.session.alpn_protocol()
}
pub fn is_reality(&self) -> bool {
self.session.is_reality()
}
fn write_tls_direct(&mut self, cx: &mut Context<'_>) -> Poll<io::Result<usize>> {
let mut adapter = SyncWriteAdapter {
io: &mut self.io,
cx,
};
match self.session.write_tls(&mut adapter) {
Ok(n) => Poll::Ready(Ok(n)),
Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => Poll::Pending,
Err(e) => Poll::Ready(Err(e)),
}
}
fn drain_all_writes(&mut self, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
while self.session.wants_write() {
match self.write_tls_direct(cx) {
Poll::Ready(Ok(0)) => {
break;
}
Poll::Ready(Ok(_)) => {
self.need_flush = true;
}
Poll::Ready(Err(e)) => return Poll::Ready(Err(e)),
Poll::Pending => return Poll::Pending,
}
}
Poll::Ready(Ok(()))
}
}
impl<IO> AsyncRead for CryptoTlsStream<IO>
where
IO: AsyncStream,
{
fn poll_read(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &mut ReadBuf<'_>,
) -> Poll<io::Result<()>> {
let this = self.get_mut();
if !this.state.readable() {
return Poll::Ready(Ok(()));
}
let mut io_pending = false;
let mut eof = false;
while this.state.readable() && this.session.wants_read() {
let mut adapter = SyncReadAdapter {
io: &mut this.io,
cx,
};
match this.session.read_tls(&mut adapter) {
Ok(0) => {
eof = true;
break;
}
Ok(_) => {
if let Err(e) = this.session.process_new_packets() {
let _ = this.drain_all_writes(cx);
return Poll::Ready(Err(e));
}
}
Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {
io_pending = true;
break;
}
Err(e) => return Poll::Ready(Err(e)),
}
}
let mut reader = this.session.reader();
match reader.fill_buf() {
Ok(available) if !available.is_empty() => {
let len = buf.remaining().min(available.len());
buf.put_slice(&available[..len]);
reader.consume(len);
Poll::Ready(Ok(()))
}
Ok(_) => {
this.state.shutdown_read();
Poll::Ready(Ok(()))
}
Err(e) if e.kind() == io::ErrorKind::WouldBlock => {
if eof {
this.state.shutdown_read();
Poll::Ready(Ok(()))
} else if io_pending {
Poll::Pending
} else {
let mut adapter = SyncReadAdapter {
io: &mut this.io,
cx,
};
match this.session.read_tls(&mut adapter) {
Ok(0) => {
this.state.shutdown_read();
Poll::Ready(Ok(()))
}
Ok(_) => {
let _ = this.session.process_new_packets();
cx.waker().wake_by_ref();
Poll::Pending
}
Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {
Poll::Pending
}
Err(e) => Poll::Ready(Err(e)),
}
}
}
Err(e) if e.kind() == io::ErrorKind::ConnectionAborted => {
this.state.shutdown_read();
Poll::Ready(Err(e))
}
Err(e) => Poll::Ready(Err(e)),
}
}
}
impl<IO> AsyncWrite for CryptoTlsStream<IO>
where
IO: AsyncStream,
{
fn poll_write(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &[u8],
) -> Poll<io::Result<usize>> {
if !self.state.writeable() {
return Poll::Ready(Err(io::Error::new(
io::ErrorKind::BrokenPipe,
"write side is shut down",
)));
}
let mut pos = 0;
while pos < buf.len() {
let mut would_block = false;
match self.session.writer().write(&buf[pos..]) {
Ok(n) => pos += n,
Err(e) => return Poll::Ready(Err(e)),
};
while self.session.wants_write() {
match self.write_tls_direct(cx) {
Poll::Ready(Ok(0)) => {
return Poll::Ready(Err(io::ErrorKind::WriteZero.into()));
}
Poll::Pending => {
would_block = true;
self.need_flush = true;
break;
}
Poll::Ready(Ok(_)) => {
self.need_flush = true;
}
Poll::Ready(Err(err)) => return Poll::Ready(Err(err)),
}
}
return match (pos, would_block) {
(0, true) => Poll::Pending,
(n, true) => Poll::Ready(Ok(n)), (_, false) => continue, };
}
Poll::Ready(Ok(pos))
}
fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
self.session.writer().flush()?;
while self.session.wants_write() {
match self.write_tls_direct(cx) {
Poll::Ready(Ok(0)) => {
return Poll::Ready(Err(io::ErrorKind::WriteZero.into()));
}
Poll::Ready(Ok(_)) => {
self.need_flush = true;
}
Poll::Ready(Err(e)) => return Poll::Ready(Err(e)),
Poll::Pending => return Poll::Pending,
}
}
if self.need_flush {
match Pin::new(&mut self.io).poll_flush(cx) {
Poll::Ready(Ok(())) => {
self.need_flush = false;
Poll::Ready(Ok(()))
}
Poll::Ready(Err(e)) => Poll::Ready(Err(e)),
Poll::Pending => Poll::Pending,
}
} else {
Poll::Ready(Ok(()))
}
}
fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
while self.session.wants_write() {
match self.write_tls_direct(cx) {
Poll::Ready(Ok(0)) => {
return Poll::Ready(Err(io::ErrorKind::WriteZero.into()));
}
Poll::Ready(Ok(_)) => {}
Poll::Ready(Err(e)) => return Poll::Ready(Err(e)),
Poll::Pending => return Poll::Pending,
}
}
if self.state.writeable() {
self.session.send_close_notify();
self.state.shutdown_write();
}
while self.session.wants_write() {
match self.write_tls_direct(cx) {
Poll::Ready(Ok(0)) => {
return Poll::Ready(Err(io::ErrorKind::WriteZero.into()));
}
Poll::Ready(Ok(_)) => {}
Poll::Ready(Err(e)) => return Poll::Ready(Err(e)),
Poll::Pending => return Poll::Pending,
}
}
match Pin::new(&mut self.io).poll_shutdown(cx) {
Poll::Ready(Ok(())) => Poll::Ready(Ok(())),
Poll::Ready(Err(e)) if e.kind() == io::ErrorKind::NotConnected => {
Poll::Ready(Ok(()))
}
Poll::Ready(Err(e)) => Poll::Ready(Err(e)),
Poll::Pending => Poll::Pending,
}
}
}
impl<IO> AsyncPing for CryptoTlsStream<IO>
where
IO: AsyncStream,
{
fn supports_ping(&self) -> bool {
self.io.supports_ping()
}
fn poll_write_ping(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<std::io::Result<bool>> {
Pin::new(&mut self.io).poll_write_ping(cx)
}
}
impl<IO> crate::async_stream::AsyncStream for CryptoTlsStream<IO> where IO: AsyncStream {}
#[cfg(test)]
mod tests {
use super::*;
use crate::address::{Address, NetLocation};
use crate::reality::{RealityServerConfig, RealityServerConnection};
use futures::task::noop_waker_ref;
struct PendingWriteIo;
impl AsyncRead for PendingWriteIo {
fn poll_read(
self: Pin<&mut Self>,
_cx: &mut Context<'_>,
_buf: &mut ReadBuf<'_>,
) -> Poll<io::Result<()>> {
Poll::Pending
}
}
impl AsyncWrite for PendingWriteIo {
fn poll_write(
self: Pin<&mut Self>,
_cx: &mut Context<'_>,
_buf: &[u8],
) -> Poll<io::Result<usize>> {
Poll::Pending
}
fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<()>> {
Poll::Ready(Ok(()))
}
fn poll_shutdown(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<()>> {
Poll::Ready(Ok(()))
}
}
impl AsyncPing for PendingWriteIo {
fn supports_ping(&self) -> bool {
false
}
fn poll_write_ping(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<bool>> {
Poll::Ready(Ok(false))
}
}
impl AsyncStream for PendingWriteIo {}
struct ZeroWriteIo;
impl AsyncRead for ZeroWriteIo {
fn poll_read(
self: Pin<&mut Self>,
_cx: &mut Context<'_>,
_buf: &mut ReadBuf<'_>,
) -> Poll<io::Result<()>> {
Poll::Pending
}
}
impl AsyncWrite for ZeroWriteIo {
fn poll_write(
self: Pin<&mut Self>,
_cx: &mut Context<'_>,
_buf: &[u8],
) -> Poll<io::Result<usize>> {
Poll::Ready(Ok(0))
}
fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<()>> {
Poll::Ready(Ok(()))
}
fn poll_shutdown(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<()>> {
Poll::Ready(Ok(()))
}
}
impl AsyncPing for ZeroWriteIo {
fn supports_ping(&self) -> bool {
false
}
fn poll_write_ping(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<bool>> {
Poll::Ready(Ok(false))
}
}
impl AsyncStream for ZeroWriteIo {}
fn completed_reality_connection() -> RealityServerConnection {
let config = RealityServerConfig {
private_key: [0; 32],
short_ids: vec![[0; 8]],
dest: NetLocation::new(Address::UNSPECIFIED, 443),
max_time_diff: None,
min_client_version: None,
max_client_version: None,
cipher_suites: Vec::new(),
};
RealityServerConnection::new(config)
.unwrap()
.complete_for_test()
.unwrap()
}
#[test]
fn reality_stream_stops_accepting_plaintext_when_tls_output_is_blocked() {
let mut stream = CryptoTlsStream::new(
PendingWriteIo,
CryptoConnection::new_reality_server(completed_reality_connection()),
);
let mut cx = Context::from_waker(noop_waker_ref());
let data = [0u8; 16 * 1024];
let mut accepted = 0;
for _ in 0..16 {
match Pin::new(&mut stream).poll_write(&mut cx, &data) {
Poll::Ready(Ok(written)) => accepted += written,
Poll::Pending => break,
Poll::Ready(Err(error)) => panic!("unexpected write error: {error}"),
}
}
assert!(
accepted <= 64 * 1024,
"blocked REALITY stream accepted {accepted} plaintext bytes"
);
}
#[test]
fn reality_stream_rejects_write_zero_without_waiting_for_a_wake() {
let mut stream = CryptoTlsStream::new(
ZeroWriteIo,
CryptoConnection::new_reality_server(completed_reality_connection()),
);
let mut cx = Context::from_waker(noop_waker_ref());
let result = Pin::new(&mut stream).poll_write(&mut cx, &[0; 1024]);
assert!(matches!(
result,
Poll::Ready(Err(error)) if error.kind() == io::ErrorKind::WriteZero
));
}
}