use std::marker::Unpin;
use crate::codec::Codec;
use crate::{ClientMessage, Envelope, PostcardCodec, ServerMessage, PROTOCOL_VERSION};
use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
const MAX_FRAME_BYTES: usize = 1_048_576;
#[derive(Debug, thiserror::Error)]
pub enum FrameError {
#[error("frame too large: {0} bytes")]
TooLarge(usize),
#[error("io error: {0}")]
Io(#[from] std::io::Error),
#[error("codec error: {0}")]
Codec(#[from] crate::codec::CodecError),
#[error("protocol version mismatch: expected {PROTOCOL_VERSION}, got {0}")]
VersionMismatch(u16),
}
pub async fn write_client_message<W>(writer: &mut W, message: &ClientMessage) -> Result<(), FrameError>
where
W: AsyncWrite + Unpin + Send,
{
write_payload(writer, &Envelope::new(message.clone())).await
}
pub async fn read_client_message<R>(reader: &mut R) -> Result<ClientMessage, FrameError>
where
R: AsyncRead + Unpin + Send,
{
let envelope: Envelope<ClientMessage> = read_payload(reader).await?;
Ok(envelope.payload)
}
pub async fn write_server_message<W>(writer: &mut W, message: &ServerMessage) -> Result<(), FrameError>
where
W: AsyncWrite + Unpin + Send,
{
write_payload(writer, &Envelope::new(message.clone())).await
}
pub async fn read_server_message<R>(reader: &mut R) -> Result<ServerMessage, FrameError>
where
R: AsyncRead + Unpin + Send,
{
let envelope: Envelope<ServerMessage> = read_payload(reader).await?;
Ok(envelope.payload)
}
async fn write_payload<W, T>(writer: &mut W, envelope: &Envelope<T>) -> Result<(), FrameError>
where
W: AsyncWrite + Unpin + Send,
T: serde::Serialize,
{
let codec = PostcardCodec;
let bytes = codec.encode(envelope)?;
let len = u32::try_from(bytes.len()).map_err(|_| FrameError::TooLarge(bytes.len()))?;
writer.write_u32_le(len).await?;
writer.write_all(&bytes).await?;
writer.flush().await?;
Ok(())
}
async fn read_payload<R, T>(reader: &mut R) -> Result<Envelope<T>, FrameError>
where
R: AsyncRead + Unpin + Send,
T: serde::de::DeserializeOwned,
{
let len = reader.read_u32_le().await? as usize;
if len > MAX_FRAME_BYTES {
return Err(FrameError::TooLarge(len));
}
let mut buf = vec![0_u8; len];
reader.read_exact(&mut buf).await?;
let codec = PostcardCodec;
let envelope: Envelope<T> = codec.decode(&buf)?;
if envelope.protocol_version != PROTOCOL_VERSION {
return Err(FrameError::VersionMismatch(envelope.protocol_version));
}
Ok(envelope)
}
#[cfg(test)]
mod tests {
use super::*;
use super::*;
use crate::{AuthCredential, ClientMessage, Hello, PROTOCOL_VERSION};
use uuid::Uuid;
#[tokio::test]
async fn roundtrip_client_message() {
let msg = ClientMessage::Hello(Hello {
client_name: "test".into(),
protocol_version: PROTOCOL_VERSION,
auth: Default::default(),
character_id: None,
});
let mut buf = Vec::new();
write_client_message(&mut buf, &msg).await.unwrap();
let parsed = read_client_message(&mut buf.as_slice()).await.unwrap();
assert_eq!(msg, parsed);
}
#[tokio::test]
async fn roundtrip_session_auth_hello() {
let msg = ClientMessage::Hello(Hello {
client_name: "traveler".into(),
protocol_version: PROTOCOL_VERSION,
auth: AuthCredential::Session {
token: "flat_sess_test".into(),
},
character_id: Some(Uuid::parse_str("019f399e-2b4e-7001-87a2-2e99c005e79d").unwrap()),
});
let mut buf = Vec::new();
write_client_message(&mut buf, &msg).await.unwrap();
let parsed = read_client_message(&mut buf.as_slice()).await.unwrap();
assert_eq!(msg, parsed);
}
}