use std::collections::HashMap;
use crate::message::{ClientMessage, ServerMessage, StreamHalf};
use tokio::sync::{mpsc, oneshot};
use tracing::{debug, error, trace, warn};
use crate::error::Error;
use crate::noise::recv_binary;
use crate::ws::{Ws, WsEvent};
type PendingMap = HashMap<u64, oneshot::Sender<Result<Vec<u8>, Error>>>;
type StreamMap = HashMap<u64, mpsc::Sender<Result<Vec<u8>, Error>>>;
#[derive(Debug)]
pub(crate) enum OutboundCommand {
Request {
payload: Vec<u8>,
response_tx: oneshot::Sender<Result<Vec<u8>, Error>>,
},
OpenStream {
payload: Vec<u8>,
id_tx: oneshot::Sender<Result<u64, Error>>,
stream_tx: mpsc::Sender<Result<Vec<u8>, Error>>,
},
StreamData { id: u64, payload: Vec<u8> },
StreamClose { id: u64, half: StreamHalf },
}
fn encrypt_cbor<T: serde::Serialize>(
transport: &mut snow::TransportState,
msg: &T,
write_buf: &mut [u8],
) -> Result<Vec<u8>, Error> {
let mut cbor = Vec::new();
ciborium::ser::into_writer(msg, &mut cbor)?;
let encrypted_len = transport.write_message(&cbor, write_buf)?;
let mut out = Vec::with_capacity(4 + encrypted_len);
out.extend_from_slice(&(encrypted_len as u32).to_be_bytes());
out.extend_from_slice(&write_buf[..encrypted_len]);
Ok(out)
}
fn try_decrypt<T: serde::de::DeserializeOwned>(
transport: &mut snow::TransportState,
accum: &[u8],
read_buf: &mut [u8],
) -> Result<Option<(T, usize)>, Error> {
if accum.len() < 4 {
return Ok(None);
}
let encrypted_len = u32::from_be_bytes(accum[..4].try_into().unwrap()) as usize;
if accum.len() < 4 + encrypted_len {
return Ok(None);
}
let payload_len =
transport.read_message(&accum[4..4 + encrypted_len], read_buf)?;
let msg: T =
ciborium::de::from_reader(&read_buf[..payload_len])
.map_err(|e| Error::Cbor(e.to_string()))?;
Ok(Some((msg, 4 + encrypted_len)))
}
pub(crate) async fn send_and_receive<S: serde::Serialize, R: serde::de::DeserializeOwned>(
ws: &mut Ws,
transport: &mut snow::TransportState,
msg: &S,
) -> Result<R, Error> {
let mut write_buf = vec![0u8; 65535];
let data = encrypt_cbor(transport, msg, &mut write_buf)?;
ws.send(data).await?;
let mut accum = Vec::new();
let mut read_buf = vec![0u8; 65535];
loop {
if let Some((msg, _)) = try_decrypt::<R>(transport, &accum, &mut read_buf)? {
return Ok(msg);
}
let frame = recv_binary(ws).await?;
accum.extend_from_slice(&frame);
}
}
pub(crate) async fn run_transport(
mut ws: Ws,
mut transport: snow::TransportState,
mut cmd_rx: mpsc::Receiver<OutboundCommand>,
) {
let mut write_buf = vec![0u8; 65535];
let mut read_buf = vec![0u8; 65535];
let mut accum = Vec::new();
let mut next_id: u64 = 1;
let mut pending: PendingMap = HashMap::new();
let mut streams: StreamMap = HashMap::new();
loop {
tokio::select! {
cmd = cmd_rx.recv() => {
let Some(cmd) = cmd else {
debug!("All client handles dropped, shutting down transport");
ws.close().await;
break;
};
match cmd {
OutboundCommand::Request { payload, response_tx } => {
let id = next_id;
next_id += 1;
let msg = ClientMessage::Data { id, payload };
match encrypt_cbor(&mut transport, &msg, &mut write_buf) {
Ok(data) => {
if let Err(e) = ws.send(data).await {
let _ = response_tx.send(Err(e));
continue;
}
pending.insert(id, response_tx);
trace!(id, "Sent request");
}
Err(e) => {
let _ = response_tx.send(Err(e));
}
}
}
OutboundCommand::OpenStream { payload, id_tx, stream_tx } => {
let id = next_id;
next_id += 1;
let msg = ClientMessage::OpenStream { id, payload };
match encrypt_cbor(&mut transport, &msg, &mut write_buf) {
Ok(data) => {
if let Err(e) = ws.send(data).await {
let _ = id_tx.send(Err(e));
continue;
}
streams.insert(id, stream_tx);
let _ = id_tx.send(Ok(id));
trace!(id, "Sent OpenStream");
}
Err(e) => {
let _ = id_tx.send(Err(e));
}
}
}
OutboundCommand::StreamData { id, payload } => {
let msg = ClientMessage::StreamData { id, payload };
match encrypt_cbor(&mut transport, &msg, &mut write_buf) {
Ok(data) => {
if let Err(e) = ws.send(data).await {
warn!(id, error = %e, "Failed to send StreamData");
fail_stream(&mut streams, id, e);
}
}
Err(e) => {
warn!(id, error = %e, "Failed to encode StreamData");
fail_stream(&mut streams, id, e);
}
}
}
OutboundCommand::StreamClose { id, half } => {
let msg = ClientMessage::StreamClose { id, half };
if let Ok(data) = encrypt_cbor(&mut transport, &msg, &mut write_buf) {
let _ = ws.send(data).await;
}
if matches!(half, StreamHalf::Both) {
streams.remove(&id);
}
}
}
}
frame = ws.recv() => {
match frame {
Ok(WsEvent::Frame(data)) => {
accum.extend_from_slice(&data);
loop {
match try_decrypt::<ServerMessage>(&mut transport, &accum, &mut read_buf) {
Ok(Some((msg, consumed))) => {
accum.drain(..consumed);
dispatch_response(&mut pending, &mut streams, msg).await;
}
Ok(None) => break,
Err(e) => {
error!("Failed to decrypt incoming message: {e}");
break;
}
}
}
}
Ok(WsEvent::Closed) => {
debug!("WebSocket closed");
notify_all_closed(&mut pending, &mut streams);
break;
}
Err(e) => {
warn!("WebSocket error: {e}");
notify_all_closed(&mut pending, &mut streams);
break;
}
}
}
}
}
}
fn fail_stream(streams: &mut StreamMap, id: u64, err: Error) {
if let Some(tx) = streams.remove(&id) {
let _ = tx.try_send(Err(err));
}
}
fn notify_all_closed(pending: &mut PendingMap, streams: &mut StreamMap) {
for (_, tx) in pending.drain() {
let _ = tx.send(Err(Error::ConnectionClosed));
}
for (_, tx) in streams.drain() {
let _ = tx.try_send(Err(Error::ConnectionClosed));
}
}
async fn dispatch_response(
pending: &mut PendingMap,
streams: &mut StreamMap,
msg: ServerMessage,
) {
match msg {
ServerMessage::Data { id, payload } => {
if let Some(tx) = pending.remove(&id) {
let _ = tx.send(Ok(payload));
trace!(id, "Dispatched response");
} else {
warn!(id, "Received Data for unknown request id");
}
}
ServerMessage::StreamData { id, payload } => {
if let Some(tx) = streams.get(&id) {
if tx.send(Ok(payload)).await.is_err() {
streams.remove(&id);
}
} else {
warn!(id, "Received StreamData for unknown stream id");
}
}
ServerMessage::StreamClose { id } => {
streams.remove(&id);
}
ServerMessage::Error { id, message } => {
if let Some(tx) = pending.remove(&id) {
let _ = tx.send(Err(Error::ServerError { id, message }));
} else if let Some(tx) = streams.remove(&id) {
let _ = tx.send(Err(Error::ServerError { id, message })).await;
} else {
warn!(id, "Received error for unknown request id");
}
}
ServerMessage::Attestation { .. } => {
warn!("Received unexpected attestation message during transport");
}
ServerMessage::ControlResult { .. } => {
warn!("Received unexpected control result during transport");
}
}
}