use alloc::sync::Arc;
use alloc::vec;
use alloc::vec::Vec;
use embassy_net::tcp::TcpSocket;
use embedded_io_async::{ErrorType, Read, Write};
use rustls::client::UnbufferedClientConnection;
use rustls::pki_types::ServerName;
use rustls::unbuffered::{ConnectionState, EncodeError, EncryptError, UnbufferedStatus};
use rustls::{ClientConfig, ProtocolVersion, SupportedCipherSuite};
const BUFFER_SIZE: usize = 18 * 1024;
#[derive(Debug)]
pub enum Error {
InvalidServerName,
Tls(rustls::Error),
Socket,
UnexpectedClose,
}
impl core::fmt::Display for Error {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Error::InvalidServerName => write!(f, "invalid server name"),
Error::Tls(e) => write!(f, "TLS error: {e}"),
Error::Socket => write!(f, "TCP socket error"),
Error::UnexpectedClose => write!(f, "peer closed unexpectedly"),
}
}
}
impl core::error::Error for Error {}
impl embedded_io_async::Error for Error {
fn kind(&self) -> embedded_io_async::ErrorKind {
embedded_io_async::ErrorKind::Other
}
}
pub struct TlsStream<'s> {
connection: UnbufferedClientConnection,
socket: TcpSocket<'s>,
incoming: Vec<u8>,
incoming_used: usize,
outgoing: Vec<u8>,
outgoing_used: usize,
plaintext: Vec<u8>,
plaintext_read: usize,
peer_closed: bool,
}
impl<'s> TlsStream<'s> {
pub async fn connect(
socket: TcpSocket<'s>,
config: Arc<ClientConfig>,
server_name: &str,
) -> Result<Self, Error> {
let name = ServerName::try_from(server_name)
.map_err(|_| Error::InvalidServerName)?
.to_owned();
let connection = UnbufferedClientConnection::new(config, name).map_err(Error::Tls)?;
let mut stream = Self {
connection,
socket,
incoming: vec![0; BUFFER_SIZE],
incoming_used: 0,
outgoing: vec![0; BUFFER_SIZE],
outgoing_used: 0,
plaintext: Vec::new(),
plaintext_read: 0,
peer_closed: false,
};
stream.drive(false).await?;
Ok(stream)
}
pub fn protocol_version(&self) -> Option<ProtocolVersion> {
self.connection.protocol_version()
}
pub fn cipher_suite(&self) -> Option<SupportedCipherSuite> {
self.connection.negotiated_cipher_suite()
}
pub fn into_socket(self) -> TcpSocket<'s> {
self.socket
}
async fn drive(&mut self, want_data: bool) -> Result<(), Error> {
loop {
let Self {
connection,
socket,
incoming,
incoming_used,
outgoing,
outgoing_used,
plaintext,
peer_closed,
..
} = self;
let UnbufferedStatus { discard, state } =
connection.process_tls_records(&mut incoming[..*incoming_used]);
let mut needs_input = false;
let mut done = false;
match state.map_err(Error::Tls)? {
ConnectionState::EncodeTlsData(mut encoder) => loop {
match encoder.encode(&mut outgoing[*outgoing_used..]) {
Ok(written) => {
*outgoing_used += written;
break;
}
Err(EncodeError::InsufficientSize(required)) => {
outgoing.resize(*outgoing_used + required.required_size, 0);
}
Err(e) => {
return Err(Error::Tls(rustls::Error::General(alloc::format!(
"TLS encode failed: {e:?}"
))));
}
}
},
ConnectionState::TransmitTlsData(transmit) => {
socket
.write_all(&outgoing[..*outgoing_used])
.await
.map_err(|_| Error::Socket)?;
*outgoing_used = 0;
transmit.done();
}
ConnectionState::BlockedHandshake => needs_input = true,
ConnectionState::ReadTraffic(mut traffic) => {
while let Some(record) = traffic.next_record() {
let record = record.map_err(Error::Tls)?;
plaintext.extend_from_slice(record.payload);
}
done = true;
}
ConnectionState::WriteTraffic(_) => {
if want_data {
needs_input = true;
} else {
done = true;
}
}
ConnectionState::PeerClosed | ConnectionState::Closed => {
*peer_closed = true;
done = true;
}
other => {
return Err(Error::Tls(rustls::Error::General(alloc::format!(
"unexpected TLS state: {other:?}"
))));
}
}
if discard != 0 {
incoming.copy_within(discard..*incoming_used, 0);
*incoming_used -= discard;
}
if needs_input {
if *incoming_used == incoming.len() {
incoming.resize(incoming.len() * 2, 0);
}
let read = socket
.read(&mut incoming[*incoming_used..])
.await
.map_err(|_| Error::Socket)?;
if read == 0 {
if want_data {
*peer_closed = true;
return Ok(());
}
return Err(Error::UnexpectedClose);
}
*incoming_used += read;
continue;
}
if done {
return Ok(());
}
}
}
async fn flush_outgoing(&mut self) -> Result<(), Error> {
if self.outgoing_used == 0 {
return Ok(());
}
self.socket
.write_all(&self.outgoing[..self.outgoing_used])
.await
.map_err(|_| Error::Socket)?;
self.outgoing_used = 0;
Ok(())
}
}
impl ErrorType for TlsStream<'_> {
type Error = Error;
}
impl Read for TlsStream<'_> {
async fn read(&mut self, buf: &mut [u8]) -> Result<usize, Error> {
if self.plaintext_read == self.plaintext.len() {
self.plaintext.clear();
self.plaintext_read = 0;
if self.peer_closed {
return Ok(0);
}
self.drive(true).await?;
if self.plaintext.is_empty() {
return Ok(0);
}
}
let available = &self.plaintext[self.plaintext_read..];
let take = available.len().min(buf.len());
buf[..take].copy_from_slice(&available[..take]);
self.plaintext_read += take;
Ok(take)
}
}
impl Write for TlsStream<'_> {
async fn write(&mut self, buf: &[u8]) -> Result<usize, Error> {
{
let Self {
connection,
incoming,
incoming_used,
outgoing,
outgoing_used,
..
} = self;
let UnbufferedStatus { discard, state } =
connection.process_tls_records(&mut incoming[..*incoming_used]);
match state.map_err(Error::Tls)? {
ConnectionState::WriteTraffic(mut writer) => loop {
match writer.encrypt(buf, &mut outgoing[*outgoing_used..]) {
Ok(written) => {
*outgoing_used += written;
break;
}
Err(EncryptError::InsufficientSize(required)) => {
outgoing.resize(*outgoing_used + required.required_size, 0);
}
Err(e) => {
return Err(Error::Tls(rustls::Error::General(alloc::format!(
"TLS encrypt failed: {e:?}"
))));
}
}
},
ConnectionState::PeerClosed | ConnectionState::Closed => {
return Err(Error::UnexpectedClose);
}
other => {
return Err(Error::Tls(rustls::Error::General(alloc::format!(
"not ready to write, in state: {other:?}"
))));
}
}
if discard != 0 {
incoming.copy_within(discard..*incoming_used, 0);
*incoming_used -= discard;
}
}
self.flush_outgoing().await?;
Ok(buf.len())
}
async fn flush(&mut self) -> Result<(), Error> {
self.flush_outgoing().await
}
}