use anyhow::{anyhow, Context, Result};
use quinn::{RecvStream, SendStream};
use tokio::io::AsyncWriteExt;
use tracing::debug;
use crate::common::remote::{OpenConn, OpenConnResponse, SessionHello, SessionHelloResponse};
use crate::common::utils::SerdeHelper;
const MAX_CONTROL_MSG: usize = 64 * 1024;
async fn write_framed<T: SerdeHelper>(send: &mut SendStream, msg: &T) -> Result<()> {
let body = msg.to_bytes()?;
let len = u32::try_from(body.len()).context("control message exceeds u32::MAX")?;
send.write_all(&len.to_le_bytes()).await?;
send.write_all(&body).await?;
Ok(())
}
async fn read_framed<T: SerdeHelper>(recv: &mut RecvStream) -> Result<T> {
let mut len_buf = [0u8; 4];
recv.read_exact(&mut len_buf)
.await
.map_err(|e| anyhow!("failed to read control message length: {e}"))?;
let len = u32::from_le_bytes(len_buf) as usize;
if len > MAX_CONTROL_MSG {
return Err(anyhow!(
"control message length {len} exceeds cap of {MAX_CONTROL_MSG}"
));
}
let mut body = vec![0u8; len];
recv.read_exact(&mut body)
.await
.map_err(|e| anyhow!("failed to read control message body: {e}"))?;
T::from_bytes(body)
}
pub async fn client_send_session_hello(
hello: &SessionHello,
send: &mut SendStream,
recv: &mut RecvStream,
) -> Result<Vec<u64>> {
debug!(remotes = hello.remotes.len(), "sending session hello");
write_framed(send, hello).await?;
send.shutdown().await?;
match read_framed::<SessionHelloResponse>(recv).await? {
SessionHelloResponse::Ok { tunnel_ids } => {
if tunnel_ids.len() != hello.remotes.len() {
return Err(anyhow!(
"server returned {} tunnel ids for {} remotes",
tunnel_ids.len(),
hello.remotes.len()
));
}
debug!(
"session accepted, {} tunnel(s) registered",
tunnel_ids.len()
);
Ok(tunnel_ids)
}
SessionHelloResponse::Failed(reason) => Err(anyhow!("server rejected session: {reason}")),
}
}
pub async fn server_receive_session_hello(recv: &mut RecvStream) -> Result<SessionHello> {
let hello: SessionHello = read_framed(recv).await?;
debug!(remotes = hello.remotes.len(), "received session hello");
Ok(hello)
}
pub async fn server_reply_session_hello(
send: &mut SendStream,
response: &SessionHelloResponse,
) -> Result<()> {
write_framed(send, response).await?;
send.shutdown().await?;
Ok(())
}
pub async fn send_open_conn(
open: &OpenConn,
send: &mut SendStream,
recv: &mut RecvStream,
) -> Result<()> {
write_framed(send, open).await?;
match read_framed::<OpenConnResponse>(recv).await? {
OpenConnResponse::Ok => Ok(()),
OpenConnResponse::Failed(reason) => Err(anyhow!("conn open rejected: {reason}")),
}
}
pub async fn receive_open_conn(recv: &mut RecvStream) -> Result<OpenConn> {
read_framed(recv).await
}
pub async fn reply_open_conn(send: &mut SendStream, response: &OpenConnResponse) -> Result<()> {
write_framed(send, response).await
}