use std::io;
use std::net::Ipv4Addr;
use serde::{Deserialize, Serialize};
use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
pub const MAX_OPEN_FRAME_SIZE: u32 = 4096;
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "type")]
pub enum Open {
Tcp {
#[serde(with = "ipv4_octets")]
host: Ipv4Addr,
port: u16,
},
}
pub(crate) mod ipv4_octets {
use std::net::Ipv4Addr;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
pub fn serialize<S: Serializer>(addr: &Ipv4Addr, s: S) -> Result<S::Ok, S::Error> {
addr.octets().serialize(s)
}
pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result<Ipv4Addr, D::Error> {
let bytes = <[u8; 4]>::deserialize(d)?;
Ok(Ipv4Addr::from(bytes))
}
}
#[derive(Debug, thiserror::Error)]
pub enum ReadOpenError {
#[error("io error: {0}")]
Io(#[from] io::Error),
#[error("opener frame too large: {0} > {MAX_OPEN_FRAME_SIZE}")]
FrameTooLarge(u32),
#[error("failed to decode opener frame: {0}")]
Decode(#[from] ciborium::de::Error<io::Error>),
}
pub async fn read_open_frame<S>(stream: &mut S) -> Result<Open, ReadOpenError>
where
S: AsyncRead + Unpin,
{
let len = stream.read_u32().await?;
if len > MAX_OPEN_FRAME_SIZE {
return Err(ReadOpenError::FrameTooLarge(len));
}
let mut buf = vec![0u8; len as usize];
stream.read_exact(&mut buf).await?;
let open: Open = ciborium::from_reader(&buf[..])?;
Ok(open)
}
pub async fn write_open_frame<S>(stream: &mut S, open: &Open) -> io::Result<()>
where
S: AsyncWrite + Unpin,
{
let mut buf = Vec::new();
ciborium::into_writer(open, &mut buf).expect("ciborium encode Open frame");
let len: u32 = buf
.len()
.try_into()
.expect("Open frame fits in u32 (CBOR encoding is small)");
stream.write_all(&len.to_be_bytes()).await?;
stream.write_all(&buf).await?;
stream.flush().await?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn open_roundtrip_cbor() {
let open = Open::Tcp {
host: Ipv4Addr::new(127, 0, 0, 1),
port: 4242,
};
let mut buf = Vec::new();
ciborium::into_writer(&open, &mut buf).unwrap();
let decoded: Open = ciborium::from_reader(&buf[..]).unwrap();
assert_eq!(open, decoded);
}
}