diff options
Diffstat (limited to 'src/protocol.rs')
-rw-r--r-- | src/protocol.rs | 115 |
1 files changed, 115 insertions, 0 deletions
diff --git a/src/protocol.rs b/src/protocol.rs new file mode 100644 index 0000000..bcddc6a --- /dev/null +++ b/src/protocol.rs @@ -0,0 +1,115 @@ +use futures::future::Future as _; +use snafu::futures01::FutureExt as _; +use snafu::ResultExt as _; +use std::convert::{TryFrom as _, TryInto as _}; + +#[derive(Debug, snafu::Snafu)] +pub enum Error { + #[snafu(display("failed to read packet: {}", source))] + ReadAsync { source: tokio::io::Error }, + + #[snafu(display("failed to write packet: {}", source))] + Write { source: std::io::Error }, + + #[snafu(display("invalid StartCasting message: {}", source))] + ParseStartCastingMessage { source: std::string::FromUtf8Error }, + + #[snafu(display("invalid message type: {}", ty))] + InvalidMessageType { ty: u32 }, +} + +pub type Result<T> = std::result::Result<T, Error>; + +pub enum Message { + StartCasting { username: String }, +} + +impl Message { + pub fn start_casting(username: &str) -> Message { + Message::StartCasting { + username: username.to_string(), + } + } + + pub fn read_async<R: tokio::io::AsyncRead>( + r: R, + ) -> impl futures::future::Future<Item = Self, Error = Error> { + Packet::read_async(r).and_then(Self::try_from) + } + + pub fn write<W: std::io::Write>(&self, w: W) -> Result<()> { + Packet::from(self).write(w) + } +} + +struct Packet { + ty: u32, + data: Vec<u8>, +} + +impl Packet { + fn read_async<R: tokio::io::AsyncRead>( + r: R, + ) -> impl futures::future::Future<Item = Self, Error = Error> { + let header_buf = [0u8; std::mem::size_of::<u32>() * 2]; + tokio::io::read_exact(r, header_buf) + .and_then(|(r, buf)| { + let (len_buf, ty_buf) = + buf.split_at(std::mem::size_of::<u32>()); + let len = u32::from_le_bytes(len_buf.try_into().unwrap()); + let ty = u32::from_le_bytes(ty_buf.try_into().unwrap()); + futures::future::ok((r, len, ty)) + }) + .and_then(|(r, len, ty)| { + let body_buf = vec![0u8; len as usize]; + tokio::io::read_exact(r, body_buf) + .map(move |(_, buf)| (ty, buf)) + }) + .and_then(|(ty, buf)| { + futures::future::ok(Packet { + ty, + data: buf.to_vec(), + }) + }) + .context(ReadAsync) + } + + fn write<W: std::io::Write>(&self, mut w: W) -> Result<()> { + Ok(w.write_all(&self.as_bytes()).context(Write)?) + } + + fn as_bytes(&self) -> Vec<u8> { + let len = (self.data.len() as u32).to_le_bytes(); + let ty = self.ty.to_le_bytes(); + len.iter() + .chain(ty.iter()) + .chain(self.data.iter()) + .cloned() + .collect() + } +} + +impl From<&Message> for Packet { + fn from(msg: &Message) -> Self { + match msg { + Message::StartCasting { username } => Packet { + ty: 0, + data: username.as_bytes().to_vec(), + }, + } + } +} + +impl std::convert::TryFrom<Packet> for Message { + type Error = Error; + + fn try_from(packet: Packet) -> Result<Self> { + match packet.ty { + 0 => Ok(Message::StartCasting { + username: std::string::String::from_utf8(packet.data) + .context(ParseStartCastingMessage)?, + }), + _ => Err(Error::InvalidMessageType { ty: packet.ty }), + } + } +} |