use std::io::{self, Read, Write};
use std::slice;
use std::sync::Arc;
use dope::Driver;
use dope::runtime::token::Token;
use dope::transport::link::Core;
use dope::wire::{Reclaim, RecvChunk, Vectored, Wire};
use rustls::pki_types::ServerName;
use rustls::{ClientConfig, ClientConnection, ServerConfig, ServerConnection};
use shin::record::MAX_PLAINTEXT_BODY;
use crate::pool::Pooled;
use crate::staging::MAX_TLS_RECORD;
const PENDING_APP_CAP: usize = 1 << 20;
const TAIL_CAP: usize = 1 << 20;
const PENDING_RECV_CAP: usize = 1 << 20;
#[derive(Clone, Default)]
pub enum RustTlsEndpoint {
#[default]
None,
Server(Arc<ServerConfig>),
Client {
config: Arc<ClientConfig>,
server_name: ServerName<'static>,
},
}
enum Conn {
Server(ServerConnection),
Client(ClientConnection),
}
impl Conn {
fn read_tls(&mut self, rd: &mut dyn Read) -> std::io::Result<usize> {
match self {
Conn::Server(c) => c.read_tls(rd),
Conn::Client(c) => c.read_tls(rd),
}
}
fn process_new_packets(&mut self) -> Result<rustls::IoState, rustls::Error> {
match self {
Conn::Server(c) => c.process_new_packets(),
Conn::Client(c) => c.process_new_packets(),
}
}
fn write_tls(&mut self, wr: &mut dyn Write) -> std::io::Result<usize> {
match self {
Conn::Server(c) => c.write_tls(wr),
Conn::Client(c) => c.write_tls(wr),
}
}
fn wants_write(&self) -> bool {
match self {
Conn::Server(c) => c.wants_write(),
Conn::Client(c) => c.wants_write(),
}
}
fn is_handshaking(&self) -> bool {
match self {
Conn::Server(c) => c.is_handshaking(),
Conn::Client(c) => c.is_handshaking(),
}
}
fn writer_write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
match self {
Conn::Server(c) => c.writer().write(buf),
Conn::Client(c) => c.writer().write(buf),
}
}
fn reader_read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
match self {
Conn::Server(c) => c.reader().read(buf),
Conn::Client(c) => c.reader().read(buf),
}
}
fn send_close_notify(&mut self) {
match self {
Conn::Server(c) => c.send_close_notify(),
Conn::Client(c) => c.send_close_notify(),
}
}
fn alpn_protocol(&self) -> Option<&[u8]> {
match self {
Conn::Server(c) => c.alpn_protocol(),
Conn::Client(c) => c.alpn_protocol(),
}
}
}
struct ConnState {
conn: Option<Conn>,
pending_app: Vec<u8>,
pending_recv: Vec<u8>,
close: bool,
}
impl ConnState {
fn empty() -> Self {
Self {
conn: None,
pending_app: Vec::new(),
pending_recv: Vec::new(),
close: false,
}
}
}
pub struct RustTls {
state: ConnState,
ingress: Vec<u8>,
egress: Pooled,
tail: Vec<u8>,
send_inflight: bool,
}
impl RustTls {
fn is_established(&self) -> bool {
self.state
.conn
.as_ref()
.is_some_and(|c| !c.is_handshaking())
}
fn ingress_decrypt(&mut self, wire_in: &[u8], out: &mut Vec<u8>) {
if self.state.conn.is_none() {
self.ingress.extend_from_slice(wire_in);
return;
}
let pending = std::mem::take(&mut self.state.pending_recv);
let combined: Vec<u8>;
let mut cursor: &[u8] = if pending.is_empty() {
wire_in
} else {
combined = {
let mut v = pending;
v.extend_from_slice(wire_in);
v
};
&combined
};
while !cursor.is_empty() {
let conn = match self.state.conn.as_mut() {
Some(c) => c,
None => break,
};
match conn.read_tls(&mut cursor) {
Ok(0) => break,
Ok(_) => {}
Err(e) if e.kind() == io::ErrorKind::Other => {
let before = out.len();
self.drain_reader(out);
if self.state.close {
return;
}
if out.len() == before {
self.buffer_pending_recv(cursor);
return;
}
continue;
}
Err(_) => {
self.state.close = true;
return;
}
}
match self.state.conn.as_mut() {
Some(conn) => match conn.process_new_packets() {
Ok(io) => {
if io.peer_has_closed() {
self.state.close = true;
}
}
Err(_) => {
self.state.close = true;
return;
}
},
None => break,
}
self.drain_reader(out);
}
}
fn buffer_pending_recv(&mut self, bytes: &[u8]) {
if bytes.is_empty() {
return;
}
if self.state.pending_recv.len() + bytes.len() > PENDING_RECV_CAP {
self.state.close = true;
return;
}
self.state.pending_recv.extend_from_slice(bytes);
}
fn drain_to_egress(&mut self) {
if self.send_inflight {
return;
}
self.flush_tail();
let mut scratch: Vec<u8> = Vec::new();
{
let Some(conn) = self.state.conn.as_mut() else {
return;
};
while conn.wants_write() {
match conn.write_tls(&mut scratch) {
Ok(0) => break,
Ok(_) => {}
Err(_) => {
self.state.close = true;
break;
}
}
}
}
if !scratch.is_empty() {
self.stage_ciphertext(&scratch);
}
}
fn stage_ciphertext(&mut self, bytes: &[u8]) {
if !self.tail.is_empty() {
self.push_tail(bytes);
return;
}
let spare = self.egress.spare_capacity();
let n = spare.min(bytes.len());
if n > 0 {
self.egress.push(&bytes[..n]);
}
if n < bytes.len() {
self.push_tail(&bytes[n..]);
}
}
fn push_tail(&mut self, bytes: &[u8]) {
if bytes.is_empty() {
return;
}
if self.tail.len() + bytes.len() > TAIL_CAP {
self.state.close = true;
return;
}
self.tail.extend_from_slice(bytes);
}
fn flush_tail(&mut self) {
if self.send_inflight || self.tail.is_empty() {
return;
}
let spare = self.egress.spare_capacity();
if spare == 0 {
return;
}
let n = spare.min(self.tail.len());
self.egress.push(&self.tail[..n]);
self.tail.drain(..n);
}
fn egress_has_record_room(&self) -> bool {
self.tail.is_empty() && self.egress.spare_capacity() >= MAX_TLS_RECORD
}
fn encrypt(&mut self, plain: &[u8]) -> usize {
if self.state.conn.is_none() {
return self.buffer_pending(plain);
}
self.maybe_flush_pending_app();
self.drain_to_egress();
if !self.is_established() {
return self.buffer_pending(plain);
}
if !self.state.pending_app.is_empty() {
return self.buffer_pending(plain);
}
if plain.is_empty() {
return 0;
}
let mut consumed = 0;
while consumed < plain.len() && self.egress_has_record_room() {
let end = (consumed + MAX_PLAINTEXT_BODY).min(plain.len());
let n = match self.state.conn.as_mut() {
Some(conn) => match conn.writer_write(&plain[consumed..end]) {
Ok(n) => n,
Err(_) => {
self.state.close = true;
break;
}
},
None => break,
};
if n == 0 {
break;
}
consumed += n;
self.drain_to_egress();
}
consumed
}
fn buffer_pending(&mut self, plain: &[u8]) -> usize {
if self.state.pending_app.len() + plain.len() > PENDING_APP_CAP {
self.state.close = true;
return 0;
}
if !plain.is_empty() {
self.state.pending_app.extend_from_slice(plain);
}
plain.len()
}
fn maybe_flush_pending_app(&mut self) {
if !self.is_established() || self.state.pending_app.is_empty() {
return;
}
let mut off = 0;
while off < self.state.pending_app.len() && self.egress_has_record_room() {
let end = (off + MAX_PLAINTEXT_BODY).min(self.state.pending_app.len());
let n = match self.state.conn.as_mut() {
Some(conn) => match conn.writer_write(&self.state.pending_app[off..end]) {
Ok(n) => n,
Err(_) => {
self.state.close = true;
break;
}
},
None => break,
};
if n == 0 {
break;
}
off += n;
self.drain_to_egress();
}
self.state.pending_app.drain(..off);
}
fn take_app(&mut self) -> Vec<u8> {
if self.state.conn.is_none() {
return std::mem::take(&mut self.ingress);
}
let mut out: Vec<u8> = Vec::new();
self.drain_reader(&mut out);
out
}
fn drain_reader(&mut self, out: &mut Vec<u8>) {
let Some(conn) = self.state.conn.as_mut() else {
return;
};
let mut buf = [0u8; 8192];
loop {
match conn.reader_read(&mut buf) {
Ok(0) => {
self.state.close = true;
break;
}
Ok(n) => out.extend_from_slice(&buf[..n]),
Err(e) if e.kind() == io::ErrorKind::WouldBlock => break,
Err(_) => {
self.state.close = true;
break;
}
}
}
}
fn submit_wire_buf(&mut self, core: &mut Core, ud: Token, driver: &mut Driver) {
if core.is_send_inflight() {
return;
}
let wire = self.egress.as_slice();
if wire.is_empty() {
return;
}
core.submit_single(ud, wire, driver);
self.send_inflight = true;
}
fn propagate_close(&mut self, core: &mut Core, ud: Token, driver: &mut Driver) {
if self.state.close {
if let Some(conn) = self.state.conn.as_mut()
&& !conn.is_handshaking()
{
conn.send_close_notify();
}
self.drain_to_egress();
self.submit_wire_buf(core, ud, driver);
core.set_close_after();
}
}
pub fn alpn_protocol(&self) -> Option<&[u8]> {
self.state.conn.as_ref().and_then(Conn::alpn_protocol)
}
}
impl Wire for RustTls {
type InitConfig = RustTlsEndpoint;
const RECLAIM: Reclaim = Reclaim::OnSubmit;
fn new(cfg: &RustTlsEndpoint) -> Self {
let mut s = ConnState::empty();
let conn = match cfg {
RustTlsEndpoint::Server(c) => ServerConnection::new(c.clone()).ok().map(Conn::Server),
RustTlsEndpoint::Client {
config,
server_name,
} => ClientConnection::new(config.clone(), server_name.clone())
.ok()
.map(Conn::Client),
RustTlsEndpoint::None => None,
};
s.close = conn.is_none();
s.conn = conn;
let mut me = Self {
state: s,
ingress: Vec::new(),
egress: Pooled::egress(),
tail: Vec::new(),
send_inflight: false,
};
me.drain_to_egress();
me
}
fn process_recv<'a>(&mut self, bytes: &'a [u8]) -> Option<RecvChunk<'a>> {
let mut data = Vec::new();
self.ingress_decrypt(bytes, &mut data);
self.maybe_flush_pending_app();
self.drain_to_egress();
let tail = self.take_app();
if data.is_empty() {
data = tail;
} else {
data.extend_from_slice(&tail);
}
if data.is_empty() {
return None;
}
Some(RecvChunk::Owned(data))
}
fn submit_send(
&mut self,
core: &mut Core,
plain: &[u8],
ud: Token,
driver: &mut Driver,
) -> usize {
let consumed = self.encrypt(plain);
self.submit_wire_buf(core, ud, driver);
self.propagate_close(core, ud, driver);
consumed
}
fn submit_send_vectored(
&mut self,
core: &mut Core,
vectored: Vectored<'_>,
ud: Token,
driver: &mut Driver,
) -> usize {
let mut consumed = 0;
for iov in vectored.iovs {
if iov.is_empty() {
continue;
}
let plain = unsafe { slice::from_raw_parts(iov.as_ptr(), iov.len()) };
let c = self.encrypt(plain);
consumed += c;
if c < plain.len() {
break;
}
}
self.submit_wire_buf(core, ud, driver);
self.propagate_close(core, ud, driver);
consumed
}
fn after_send_cqe(
&mut self,
core: &mut Core,
n: usize,
ud: Token,
driver: &mut Driver,
) -> bool {
self.send_inflight = false;
self.egress.consume(n);
self.drain_to_egress();
if self.egress.is_empty() {
self.egress.release_if_empty();
self.propagate_close(core, ud, driver);
return false;
}
self.submit_wire_buf(core, ud, driver);
true
}
fn flush_pending(&mut self, core: &mut Core, ud: Token, driver: &mut Driver) {
self.encrypt(&[]);
self.submit_wire_buf(core, ud, driver);
self.propagate_close(core, ud, driver);
}
}