use std::net::SocketAddr;
use std::time::Duration;
use eyre::{Result, eyre};
use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
use crate::report::PeerIdentity;
pub const HELLO_TIMEOUT: Duration = Duration::from_secs(2);
const MAX_BLOB_BYTES: u32 = 16 * 1024;
pub struct ClientHelloResult {
pub server_identity: PeerIdentity,
pub observed_client_addr: SocketAddr,
}
pub async fn read_blob<R>(reader: &mut R) -> Result<Vec<u8>>
where
R: AsyncRead + Unpin,
{
let mut len_buf = [0u8; 4];
reader.read_exact(&mut len_buf).await?;
let len = u32::from_be_bytes(len_buf);
if len > MAX_BLOB_BYTES {
return Err(eyre!("handshake blob too large: {len} bytes"));
}
let mut buf = vec![0u8; len as usize];
reader.read_exact(&mut buf).await?;
Ok(buf)
}
pub async fn write_blob<W>(writer: &mut W, bytes: &[u8]) -> Result<()>
where
W: AsyncWrite + Unpin,
{
let len = u32::try_from(bytes.len()).map_err(|_| eyre!("handshake blob too large"))?;
writer.write_all(&len.to_be_bytes()).await?;
writer.write_all(bytes).await?;
Ok(())
}
pub fn encode_cbor<T: serde::Serialize>(value: &T) -> Result<Vec<u8>> {
let mut buf = Vec::new();
ciborium::into_writer(value, &mut buf).map_err(|e| eyre!("cbor encode: {e}"))?;
Ok(buf)
}
pub fn decode_cbor<T: serde::de::DeserializeOwned>(bytes: &[u8]) -> Result<T> {
ciborium::from_reader(bytes).map_err(|e| eyre!("cbor decode: {e}"))
}
pub async fn client_hello_io<R, W>(
reader: &mut R,
writer: &mut W,
identity: &PeerIdentity,
) -> Result<ClientHelloResult>
where
R: AsyncRead + Unpin,
W: AsyncWrite + Unpin,
{
writer.write_all(b"H").await?;
write_blob(writer, &encode_cbor(identity)?).await?;
writer.flush().await?;
let server_identity: PeerIdentity = decode_cbor(&read_blob(reader).await?)?;
let observed_client_addr: SocketAddr = decode_cbor(&read_blob(reader).await?)?;
Ok(ClientHelloResult {
server_identity,
observed_client_addr,
})
}
pub async fn server_hello_io<R, W>(reader: &mut R, writer: &mut W, peer: SocketAddr) -> Result<()>
where
R: AsyncRead + Unpin,
W: AsyncWrite + Unpin,
{
let _client_identity: PeerIdentity = decode_cbor(&read_blob(reader).await?)?;
write_blob(writer, &encode_cbor(&PeerIdentity::local())?).await?;
write_blob(writer, &encode_cbor(&peer)?).await?;
writer.flush().await?;
Ok(())
}