nimbi-protocol 0.1.0

A crate for defining the nimbi-protocol used to communicate with microcontrollers
Documentation
use microdot::{
    helpers::{Header, SerializeStructHelper, HEADER_SIZE},
    Deserialize, MicrodotError, Serialize,
};

pub trait IntoResponse<T> {
    fn into_response(self) -> Response<T>;
}

#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[derive(Debug)]
pub struct Response<T> {
    pub success: bool,
    pub payload: Option<T>,
    pub error: Option<Error>,
}

impl<T> Response<T> {
    pub fn success() -> Self {
        Self {
            success: true,
            payload: None,
            error: None,
        }
    }

    pub fn fail() -> Self {
        Self {
            success: false,
            payload: None,
            error: None,
        }
    }

    pub fn with_payload(mut self, payload: T) -> Self {
        self.payload = Some(payload);

        self
    }

    pub fn with_err(mut self, err: Error) -> Self {
        self.error = Some(err);

        self
    }
}

impl<T> Serialize for Response<T>
where
    T: Serialize,
{
    fn serialize(&self, buf: &mut [u8]) -> Result<usize, MicrodotError> {
        let (header_buf, payload_buf) = buf.split_at_mut(HEADER_SIZE);

        let mut helper = SerializeStructHelper::new(payload_buf);

        let mut bytes_written = 0;

        bytes_written += helper.serialize_field(&self.success)?;

        if let Some(payload) = &self.payload {
            bytes_written += helper.serialize_field(payload)?;
        }

        if let Some(error) = &self.error {
            bytes_written += helper.serialize_field(error)?;
        }

        let header = Header {
            id: 1,
            size: bytes_written,
        };

        bytes_written += header.serialize(header_buf)?;

        Ok(bytes_written)
    }
}

impl<'a, T> Deserialize<'a> for Response<T>
where
    T: Deserialize<'a>,
{
    fn deserialize(buf: &'a [u8]) -> Result<Self, MicrodotError> {
        let (header_buf, payload_buf) = buf.split_at(HEADER_SIZE);

        let header = Header::deserialize(header_buf)?;

        let buf = &payload_buf[..header.size];

        let mut offset = 0;

        let mut success = None;
        let mut payload = None;
        let mut error = None;

        while offset < buf.len() {
            let header = Header::deserialize(&buf[offset..offset + HEADER_SIZE])?;
            let buf = &buf[offset + HEADER_SIZE..offset + HEADER_SIZE + header.size];

            match header.id {
                1 => {
                    success = Some(bool::deserialize(buf)?);
                }
                2 => {
                    payload = Some(<T>::deserialize(buf)?);
                }
                3 => {
                    error = Some(Error::deserialize(buf)?);
                }
                _ => return Err(MicrodotError::invalid_payload()),
            }

            offset += HEADER_SIZE + header.size;
        }

        let Some(success) = success else {
            return Err(MicrodotError::missing_field("success"));
        };

        Ok(Self {
            success,
            payload,
            error,
        })
    }
}

#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[derive(Debug)]
pub enum Error {
    InvalidRequest,
    ServerError,
}

impl Serialize for Error {
    fn serialize(&self, buf: &mut [u8]) -> Result<usize, MicrodotError> {
        if buf.is_empty() {
            return Err(MicrodotError::invalid_length());
        }

        let byte = match self {
            Error::InvalidRequest => 0x1,
            Error::ServerError => 0x2,
        };

        buf[0] = byte;

        Ok(1)
    }
}

impl<'a> Deserialize<'a> for Error {
    fn deserialize(buf: &'a [u8]) -> Result<Self, MicrodotError> {
        if buf.is_empty() {
            return Err(MicrodotError::invalid_length());
        }

        Ok(match buf[0] {
            0x1 => Error::InvalidRequest,
            0x2 => Error::ServerError,
            _ => return Err(MicrodotError::invalid_payload()),
        })
    }
}

#[cfg(test)]
mod tests {

    use crate::{
        message::{check_update::CheckUpdateResponse, Message},
        types::Version,
    };

    use super::*;

    #[test]
    fn response_should_serialize() {
        let mut buf = [0u8; 32];

        let bytes_written = Response::success()
            .with_payload(Message::CheckUpdateResponse(CheckUpdateResponse {
                next_version: Version::new(1, 2, 3),
            }))
            .serialize(&mut buf)
            .expect("should be able to serialize known struct");

        let response = Response::<Message>::deserialize(&buf[..bytes_written])
            .expect("should be able to deserialize known struct");

        match (&response.success, &response.payload, &response.error) {
            (true, Some(payload), None) => {
                if let Message::CheckUpdateResponse(res) = payload {
                    assert_eq!(res.next_version, Version::new(1, 2, 3,));
                } else {
                    unreachable!()
                }
            }
            _ => {
                dbg!(&response);
                dbg!(&buf[..]);

                unreachable!()
            }
        }
    }
}