tubes 0.6.4

Host/Client protocol based on pipenet
Documentation
use crate::client_id::ClientId;
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
use std::{io::Cursor, net::IpAddr};

type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;

/// A wrapper for a message being received.
///
/// This can be a broadcast ([`MessageData::Broadcast`]) or a direct message
/// ([`MessageData::Send`]).
///
/// When a client joins or leaves, [`MessageData::ClientJoined`] and
/// [`MessageData::ClientLeft`] are also received containing the [`ClientId`] of the
/// client that joined or left.
pub enum MessageData {
    Broadcast { from: ClientId, data: Vec<u8> },
    Send { from: ClientId, to: ClientId, data: Vec<u8> },
    ClientJoined(ClientId),
    ClientLeft(ClientId),
}

pub(crate) enum MessageDataInternal {
    Broadcast(ClientId, Vec<u8>),
    Send(ClientId, ClientId, Vec<u8>),
    ServerUuid(ClientId),
    ClientJoined(ClientId),
    ClientLeft(ClientId),
    PromoteToHost(ClientId, IpAddr, u16),
    NewHost(IpAddr, u16),
}

impl From<MessageData> for MessageDataInternal {
    fn from(value: MessageData) -> Self {
        match value {
            MessageData::Broadcast {
                from: sender,
                data: m,
            } => MessageDataInternal::Broadcast(sender, m),
            MessageData::Send {
                from: sender,
                to: dst,
                data: m,
            } => MessageDataInternal::Send(sender, dst, m),
            MessageData::ClientJoined(uuid) => MessageDataInternal::ClientJoined(uuid),
            MessageData::ClientLeft(uuid) => MessageDataInternal::ClientLeft(uuid),
        }
    }
}

const MESSAGE_KIND_BROADCAST: u8 = 0;
const MESSAGE_KIND_CLIENT_JOINED: u8 = 1;
const MESSAGE_KIND_SERVER_UUID: u8 = 2;
const MESSAGE_KIND_SEND: u8 = 3;
const MESSAGE_KIND_CLIENT_LEFT: u8 = 4;
const MESSAGE_KIND_PROMOTE_TO_HOST: u8 = 5;
const MESSAGE_KIND_NEW_HOST: u8 = 6;

impl TryFrom<&[u8]> for MessageDataInternal {
    type Error = Box<dyn std::error::Error>;

    fn try_from(value: &[u8]) -> Result<Self> {
        if value.is_empty() {
            return Err("empty message, can't deserialize".into());
        }
        let t = value[0];
        Ok(match t {
            MESSAGE_KIND_BROADCAST => {
                let mut c = Cursor::new(&value[1..17]);
                let sender = ClientId::from_u128(c.read_u128::<BigEndian>()?);
                let data = value[17..].to_vec();
                MessageDataInternal::Broadcast(sender, data)
            }
            MESSAGE_KIND_CLIENT_JOINED => {
                let mut c = Cursor::new(&value[1..17]);
                let uuid = ClientId::from_u128(c.read_u128::<BigEndian>()?);
                MessageDataInternal::ClientJoined(uuid)
            }
            MESSAGE_KIND_SERVER_UUID => {
                let mut c = Cursor::new(&value[1..17]);
                let uuid = ClientId::from_u128(c.read_u128::<BigEndian>()?);
                MessageDataInternal::ServerUuid(uuid)
            }
            MESSAGE_KIND_SEND => {
                let mut c = Cursor::new(&value[1..33]);
                let sender = ClientId::from_u128(c.read_u128::<BigEndian>()?);
                let dst = ClientId::from_u128(c.read_u128::<BigEndian>()?);
                let data = value[33..].to_vec();
                MessageDataInternal::Send(sender, dst, data)
            }
            MESSAGE_KIND_CLIENT_LEFT => {
                let mut c = Cursor::new(&value[1..17]);
                let uuid = ClientId::from_u128(c.read_u128::<BigEndian>()?);
                MessageDataInternal::ClientLeft(uuid)
            }
            MESSAGE_KIND_PROMOTE_TO_HOST => {
                let mut c = Cursor::new(&value[1..19]);
                let uuid = ClientId::from_u128(c.read_u128::<BigEndian>()?);
                let port = c.read_u16::<BigEndian>()?;
                let rest = &value[19..];
                let ip: IpAddr = if rest.len() == 16 {
                    let rest: [u8; 16] = rest.try_into()?;
                    rest.into()
                } else {
                    let rest: [u8; 4] = rest.try_into()?;
                    rest.into()
                };
                MessageDataInternal::PromoteToHost(uuid, ip, port)
            }
            MESSAGE_KIND_NEW_HOST => {
                let mut c = Cursor::new(&value[1..3]);
                let port = c.read_u16::<BigEndian>()?;
                let rest = &value[3..];
                let ip: IpAddr = if rest.len() == 16 {
                    let rest: [u8; 16] = rest.try_into()?;
                    rest.into()
                } else {
                    let rest: [u8; 4] = rest.try_into()?;
                    rest.into()
                };
                MessageDataInternal::NewHost(ip, port)
            }
            _ => {
                return Err("message type not recognized".into());
            }
        })
    }
}

impl TryFrom<MessageDataInternal> for Vec<u8> {
    type Error = Box<dyn std::error::Error>;

    fn try_from(value: MessageDataInternal) -> Result<Self> {
        let (t, partial) = match value {
            MessageDataInternal::Broadcast(sender, m) => {
                let mut res: Vec<u8> = vec![0; 16];
                let mut c = Cursor::new(&mut res);
                c.write_u128::<BigEndian>(sender.as_u128())?;
                res.extend_from_slice(&m);
                (MESSAGE_KIND_BROADCAST, res)
            }
            MessageDataInternal::ClientJoined(uuid) => {
                let mut res: Vec<u8> = vec![0; 16];
                let mut c = Cursor::new(&mut res);
                c.write_u128::<BigEndian>(uuid.as_u128())?;
                (MESSAGE_KIND_CLIENT_JOINED, res)
            }
            MessageDataInternal::ServerUuid(uuid) => {
                let mut res: Vec<u8> = vec![0; 16];
                let mut c = Cursor::new(&mut res);
                c.write_u128::<BigEndian>(uuid.as_u128())?;
                (MESSAGE_KIND_SERVER_UUID, res)
            }
            MessageDataInternal::Send(sender, dst, m) => {
                let mut res: Vec<u8> = vec![0; 32];
                let mut c = Cursor::new(&mut res);
                c.write_u128::<BigEndian>(sender.as_u128())?;
                c.write_u128::<BigEndian>(dst.as_u128())?;
                res.extend_from_slice(&m);
                (MESSAGE_KIND_SEND, res)
            }
            MessageDataInternal::ClientLeft(uuid) => {
                let mut res: Vec<u8> = vec![0; 16];
                let mut c = Cursor::new(&mut res);
                c.write_u128::<BigEndian>(uuid.as_u128())?;
                (MESSAGE_KIND_CLIENT_LEFT, res)
            }
            MessageDataInternal::PromoteToHost(uuid, ip, port) => {
                let mut res: Vec<u8> = vec![0; 18];
                let mut c = Cursor::new(&mut res);
                c.write_u128::<BigEndian>(uuid.as_u128())?;
                c.write_u16::<BigEndian>(port)?;
                match ip {
                    IpAddr::V4(v4) => res.extend_from_slice(&v4.octets()),
                    IpAddr::V6(v6) => res.extend_from_slice(&v6.octets()),
                }
                (MESSAGE_KIND_PROMOTE_TO_HOST, res)
            }
            MessageDataInternal::NewHost(ip, port) => {
                let mut res: Vec<u8> = vec![0; 2];
                let mut c = Cursor::new(&mut res);
                c.write_u16::<BigEndian>(port)?;
                match ip {
                    IpAddr::V4(v4) => res.extend_from_slice(&v4.octets()),
                    IpAddr::V6(v6) => res.extend_from_slice(&v6.octets()),
                }
                (MESSAGE_KIND_NEW_HOST, res)
            }
        };
        let mut res: Vec<u8> = vec![t];
        res.extend_from_slice(&partial);
        Ok(res)
    }
}

#[cfg(test)]
mod test {
    use std::string::FromUtf8Error;

    use super::*;

    struct Msg(String);

    impl From<String> for Msg {
        fn from(value: String) -> Self {
            Self(value)
        }
    }

    impl TryFrom<&[u8]> for Msg {
        type Error = FromUtf8Error;

        fn try_from(value: &[u8]) -> std::result::Result<Self, Self::Error> {
            Ok(Msg(String::from_utf8(value.to_vec())?))
        }
    }

    impl TryFrom<Msg> for Vec<u8> {
        type Error = ();

        fn try_from(value: Msg) -> std::result::Result<Self, Self::Error> {
            Ok(value.0.into())
        }
    }

    #[test]
    fn test_in_out() {
        in_out(MessageDataInternal::Broadcast(
            ClientId::new(),
            "a".to_string().into(),
        ));
        in_out(MessageDataInternal::Send(
            ClientId::new(),
            ClientId::new(),
            "a".to_string().into(),
        ));
        in_out(MessageDataInternal::ClientJoined(ClientId::new()));
        in_out(MessageDataInternal::ClientLeft(ClientId::new()));
        in_out(MessageDataInternal::ServerUuid(ClientId::new()));
        in_out(MessageDataInternal::PromoteToHost(
            ClientId::new(),
            "127.0.0.1".parse().unwrap(),
            444,
        ));
        in_out(MessageDataInternal::PromoteToHost(
            ClientId::new(),
            "::1".parse().unwrap(),
            444,
        ));
        in_out(MessageDataInternal::NewHost(
            "127.0.0.1".parse().unwrap(),
            444,
        ));
        in_out(MessageDataInternal::NewHost("::1".parse().unwrap(), 444));
    }

    fn in_out(m: MessageDataInternal) {
        let v: Vec<u8> = m.try_into().unwrap();
        let _m: MessageDataInternal = v.as_slice().try_into().unwrap();
    }
}