reba-client 0.1.0

Reba client
Documentation
use std::convert::TryFrom;
use std::ptr::copy_nonoverlapping;

use anyhow::{anyhow,
             Error};

// RebaType is a single byte.  It can either be 0, or 1.  All other values are invalid.  You can think of
// a RebaType as a u8, but it is represented as an enum here.
#[derive(Debug, PartialEq, Clone, Copy)]
pub enum RebaType
{
    AddTxPool = 0,
    CommitTx  = 1
}

impl TryFrom<u8> for RebaType
{
    type Error = Error;

    fn try_from(b: u8) -> Result<Self, Self::Error>
    {
        match b
        {
            0 => Ok(RebaType::AddTxPool),
            1 => Ok(RebaType::CommitTx),
            _ => Err(anyhow!("Invalid RebaType value"))
        }
    }
}

// Reba data matches the format of the reba data from an array of bytes.  If reba_type is 1, then there should always be a block
// number.  Otherwise, there won't be a block number.
#[derive(Debug, PartialEq)]
pub struct RebaData
{
    reba_type:    RebaType,
    block_number: Option<u64>,
    from:         [u8; 20],
    hash:         [u8; 32],
    bytes_size:   usize
}

impl RebaData
{
    pub fn reba_type(&self) -> RebaType
    {
        self.reba_type
    }

    pub fn block_number(&self) -> Option<u64>
    {
        self.block_number
    }

    pub fn from(&self) -> [u8; 20]
    {
        self.from
    }

    pub fn hash(&self) -> [u8; 32]
    {
        self.hash
    }

    pub fn bytes_size(&self) -> usize
    {
        self.bytes_size
    }
}

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

    fn try_from(bytes: &[u8]) -> Result<Self, Error>
    {
        // Check that the byte slice is at least 1 byte long and convert the first byte into a RebaType
        let reba_type = RebaType::try_from(bytes[0])?;

        // Calculate the minimum length of the byte slice based on the RebaType
        let min_length = if reba_type == RebaType::CommitTx { 61 } else { 53 };

        if bytes.len() < min_length
        {
            return Err(anyhow!("Byte slice is too small to contain RebaData"));
        }

        let block_number =
            if reba_type == RebaType::CommitTx { Some(u64::from_le_bytes(bytes[1..9].try_into().unwrap())) } else { None };

        let offset = if reba_type == RebaType::CommitTx { 9 } else { 1 };

        // Create a new RebaData struct and copy the data from the byte slice into it
        let mut data = RebaData { reba_type,
                                  block_number,
                                  from: [0; 20],
                                  hash: [0; 32],
                                  bytes_size: min_length };

        unsafe {
            copy_nonoverlapping(bytes[offset..offset + 20].as_ptr(),
                                &mut data.from as *mut _ as *mut u8,
                                20);
            copy_nonoverlapping(bytes[offset + 20..].as_ptr(),
                                &mut data.hash as *mut _ as *mut u8,
                                32);
        }

        Ok(data)
    }
}

mod test
{

    use super::*;

    #[test]
    fn test_from_bytes_add_tx_pool_no_block_number()
    {
        // Test a RebaData struct with a RebaType of AddTxPool and no block number
        let bytes = [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];
        let data = RebaData { reba_type:    RebaType::AddTxPool,
                              block_number: None,
                              from:         [0; 20],
                              hash:         [0; 32],
                              bytes_size:   53 };
        assert_eq!(RebaData::try_from(bytes.as_slice()).unwrap(), data);
    }

    #[test]
    fn test_from_bytes_commit_tx_with_block_number()
    {
        let reba_type: u8 = RebaType::CommitTx as u8;
        let block_number_bytes = [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08];
        let from = [1u8; 20];
        let hash = [3u8; 32];

        let mut data = Vec::new();
        data.extend_from_slice(&[reba_type]);
        data.extend_from_slice(&block_number_bytes);
        data.extend_from_slice(&from);
        data.extend_from_slice(&hash);

        let block_number = u64::from_le_bytes(block_number_bytes);

        let expected = RebaData { reba_type: RebaType::try_from(reba_type).unwrap(),
                                  block_number: Some(block_number),
                                  from,
                                  hash,
                                  bytes_size: data.len() };

        let result = RebaData::try_from(data.as_slice()).unwrap();

        assert_eq!(result, expected);
    }

    #[test]
    fn test_from_bytes_invalid_reba_type()
    {
        // Test a RebaData struct with an invalid RebaType
        let bytes = [2, 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];
        let res = RebaData::try_from(bytes.as_slice());
        let e = res.unwrap_err();
        assert_eq!(e.to_string(), "Invalid RebaType value");
    }
}

#[test]
fn test_from_bytes_invalid_length()
{
    // Test a RebaData struct with an invalid length
    let bytes = [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];
    let res = RebaData::try_from(bytes.as_slice());
    let e = res.unwrap_err();
    assert_eq!(e.to_string(), "Byte slice is too small to contain RebaData");
}