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,
};

use crate::types::{Id, Version};

#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[derive(Debug)]
pub struct CheckUpdateRequest<'a> {
    pub program_id: Id<'a>,
    pub release_channel_id: Id<'a>,
    pub current_version: Version,
}

impl Serialize for CheckUpdateRequest<'_> {
    fn serialize(&self, buf: &mut [u8]) -> Result<usize, MicrodotError> {
        let mut helper = SerializeStructHelper::new(buf);

        helper.serialize_fields(&[
            &self.program_id,
            &self.release_channel_id,
            &self.current_version,
        ])
    }
}

impl<'a> Deserialize<'a> for CheckUpdateRequest<'a> {
    fn deserialize(buf: &'a [u8]) -> Result<Self, MicrodotError> {
        let mut offset = 0;

        let mut program_id = None;
        let mut release_channel_id = None;
        let mut current_version = 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 => {
                    program_id = Some(Id::deserialize(buf)?);
                }
                2 => {
                    release_channel_id = Some(Id::deserialize(buf)?);
                }
                3 => {
                    current_version = Some(Version::deserialize(buf)?);
                }
                _ => return Err(MicrodotError::invalid_payload()),
            }

            offset += HEADER_SIZE + header.size;
        }

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

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

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

        Ok(Self {
            program_id,
            release_channel_id,
            current_version,
        })
    }
}

#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[derive(Debug)]
pub struct CheckUpdateResponse {
    pub next_version: Version,
}

impl Serialize for CheckUpdateResponse {
    fn serialize(&self, buf: &mut [u8]) -> Result<usize, MicrodotError> {
        let mut helper = SerializeStructHelper::new(buf);

        helper.serialize_field(&self.next_version)
    }
}

impl<'a> Deserialize<'a> for CheckUpdateResponse {
    fn deserialize(buf: &'a [u8]) -> Result<Self, MicrodotError> {
        let mut offset = 0;

        let mut next_version = 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 => {
                    next_version = Some(Version::deserialize(buf)?);
                }
                _ => return Err(MicrodotError::invalid_payload()),
            }

            offset += HEADER_SIZE + header.size;
        }

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

        Ok(Self { next_version })
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn check_update_response_should_serialize() {
        let payload = CheckUpdateResponse {
            next_version: Version::new(1, 2, 3),
        };

        let mut buf = [0u8; 16];

        match payload.serialize(&mut buf) {
            Ok(bytes_written) => {
                assert_eq!(bytes_written, 9);
                assert_eq!(&buf[..bytes_written], &[1, 0, 6, 0, 1, 0, 2, 0, 3]);
            }
            Err(err) => {
                dbg!(err);

                unreachable!();
            }
        }
    }

    #[test]
    fn check_update_response_should_deserialize() {
        let buf = &[1, 0, 6, 0, 1, 0, 2, 0, 3];

        match CheckUpdateResponse::deserialize(buf) {
            Ok(response) => {
                assert_eq!(response.next_version, Version::new(1, 2, 3,));
            }
            Err(err) => {
                dbg!(err);

                unreachable!();
            }
        }
    }
}