plabble-codec 0.1.0

Plabble Transport Protocol codec
Documentation
use time::OffsetDateTime;

use crate::{
    abstractions::{Serializable, SerializationError, SerializationInfo, KEY_SIZE, SIGNATURE_SIZE},
    codec::common::{assert_len, plabble_date},
};

/// A certificate
///
/// # Fields
///
/// * `public_key` - the public key of the certificate (32 bytes)
/// * `valid_from` - the date the certificate is valid from (4 bytes, Plabble timestamp)
/// * `valid_to` - the date the certificate is valid to (4 bytes, Plabble timestamp)
/// * `signature` - the signature of the certificate (64 bytes)
/// * `domain_or_ip` - the domain or IP address the certificate is from
#[derive(Debug)]
pub struct Certificate {
    pub public_key: [u8; KEY_SIZE],
    pub valid_from: OffsetDateTime,
    pub valid_to: OffsetDateTime,
    pub signature: [u8; SIGNATURE_SIZE],
    pub domain_or_ip: String,
}

impl Serializable for Certificate {
    fn size(&self) -> usize {
        KEY_SIZE + 4 + 4 + SIGNATURE_SIZE + self.domain_or_ip.len()
    }

    fn get_bytes(&self) -> Vec<u8> {
        let mut buff = Vec::new();
        buff.extend_from_slice(&self.public_key);
        buff.extend_from_slice(&plabble_date::to_timestamp(&self.valid_from).to_be_bytes());
        buff.extend_from_slice(&plabble_date::to_timestamp(&self.valid_to).to_be_bytes());
        buff.extend_from_slice(&self.signature);
        buff.extend_from_slice(self.domain_or_ip.as_bytes());
        buff
    }

    fn from_bytes(data: &[u8], _: Option<SerializationInfo>) -> Result<Self, SerializationError>
    where
        Self: Sized,
    {
        assert_len(data, KEY_SIZE + SIGNATURE_SIZE + 8)?;
        let mut cert = Self {
            public_key: [0u8; KEY_SIZE],
            valid_from: plabble_date::from_timestamp(u32::from_be_bytes(
                data[KEY_SIZE..(KEY_SIZE + 4)].try_into().unwrap(),
            )),
            valid_to: plabble_date::from_timestamp(u32::from_be_bytes(
                data[(KEY_SIZE + 4)..(KEY_SIZE + 8)].try_into().unwrap(),
            )),
            signature: [0u8; SIGNATURE_SIZE],
            domain_or_ip: String::new(),
        };
        cert.public_key.copy_from_slice(&data[..KEY_SIZE]);
        cert.signature
            .copy_from_slice(&data[(KEY_SIZE + 8)..(KEY_SIZE + 8 + SIGNATURE_SIZE)]);
        cert.domain_or_ip =
            String::from_utf8_lossy(&data[(KEY_SIZE + 8 + SIGNATURE_SIZE)..]).to_string();
        Ok(cert)
    }
}

#[cfg(test)]
mod test {
    use time_macros::datetime;

    use crate::abstractions::EPOCH;

    use super::*;

    #[test]
    fn can_parse_cert() {
        let cert = &[
            1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
            25, 26, 27, 28, 29, 30, 31, 32, 0, 0, 0, 12, 0, 0, 0, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x70, 0x6c,
            0x61, 0x62, 0x62, 0x6c, 0x65, 0x2e, 0x6f, 0x72, 0x67,
        ];

        let cert = Certificate::from_bytes(cert, None).unwrap();
        assert_eq!(1, cert.public_key[0]);
        assert_eq!(32, cert.public_key[31]);
        assert_eq!(64, cert.signature.len());
        assert_eq!(
            OffsetDateTime::from_unix_timestamp(EPOCH + 12).unwrap(),
            cert.valid_from
        );
        assert_eq!(
            OffsetDateTime::from_unix_timestamp(EPOCH + 14).unwrap(),
            cert.valid_to
        );
        assert_eq!("plabble.org", &cert.domain_or_ip);
    }

    #[test]
    fn can_serialize_cert() {
        let cert1 = Certificate {
            public_key: [2u8; 32],
            signature: [3u8; 64],
            valid_from: datetime!(2020-01-01 00:00:00 UTC),
            valid_to: datetime!(2020-01-15 00:00:00 UTC),
            domain_or_ip: String::from("cert.plabble.org"),
        };
        let cert2 = Certificate::from_bytes(&cert1.get_bytes(), None).unwrap();
        assert_eq!(cert1.domain_or_ip, cert2.domain_or_ip);
        assert_eq!(cert1.public_key, cert2.public_key);
        assert_eq!(cert1.signature, cert2.signature);
        assert_eq!(cert1.valid_from, cert2.valid_from);
        assert_eq!(cert1.valid_to, cert2.valid_to);
    }
}