koibumi-core 0.0.9

The core library for Koibumi, an experimental Bitmessage client
Documentation
use std::{
    convert::TryInto,
    io::{self, Read, Write},
};

use crate::{
    config::Config,
    hash::double_sha512,
    io::{LenBm, ReadFrom, SizedReadFrom, TooLongError, WriteTo},
    message::{InvHash, Message},
    object,
    packet::Command,
    pow,
    time::Time,
};

/// An "object" message that consists of a object
/// such as a public key or an one-to-one message.
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
pub struct Object {
    nonce: pow::Nonce,
    header: object::Header,
    object_payload: Vec<u8>,
}

impl Object {
    /// The maximum length of the payload of a object message.
    pub const MAX_OBJECT_PAYLOAD_LENGTH: usize = 1 << 18;

    /// Constructs an object message from the specified parameters.
    pub fn new(
        nonce: pow::Nonce,
        header: object::Header,
        object_payload: Vec<u8>,
    ) -> Result<Self, TooLongError> {
        if object_payload.len() > Self::MAX_OBJECT_PAYLOAD_LENGTH {
            return Err(TooLongError::new(
                Self::MAX_OBJECT_PAYLOAD_LENGTH,
                object_payload.len(),
            ));
        }
        Ok(Self {
            nonce,
            header,
            object_payload,
        })
    }

    /// Returns the nonce of the proof of work.
    pub fn nonce(&self) -> pow::Nonce {
        self.nonce
    }

    /// Returns the object header.
    pub fn header(&self) -> &object::Header {
        &self.header
    }

    /// Returns the payload of the object message.
    pub fn object_payload(&self) -> &[u8] {
        &self.object_payload
    }

    /// Returns the inventory hash of the object message.
    pub fn inv_hash(&self) -> InvHash {
        let mut bytes: Vec<u8> = Vec::new();
        self.write_to(&mut bytes).unwrap();
        InvHash(double_sha512(bytes)[0..32].try_into().unwrap())
    }

    /// Validates the proof of work of the object message
    /// with the specified custom parameters.
    pub fn validate_pow_custom(
        &self,
        config: &Config,
        nonce_trials_per_byte: pow::NonceTrialsPerByte,
        extra_bytes: pow::PayloadLengthExtraBytes,
        now: Time,
    ) -> Result<(), pow::ValidateError> {
        let mut bytes =
            Vec::with_capacity(self.header.len_bm() as usize + self.object_payload.len());
        self.header.write_to(&mut bytes).unwrap();
        self.object_payload.write_to(&mut bytes).unwrap();
        let initial_hash = pow::initial_hash(&bytes);
        let target = pow::target(
            config,
            bytes.len(),
            self.header.expires_time(),
            now,
            nonce_trials_per_byte,
            extra_bytes,
        );
        pow::validate(initial_hash, target, self.nonce)
    }

    /// Validates the proof of work of the object message
    /// with the default parameters.
    pub fn validate_pow(&self, config: &Config) -> Result<(), pow::ValidateError> {
        self.validate_pow_custom(
            config,
            config.nonce_trials_per_byte(),
            config.payload_length_extra_bytes(),
            Time::now(),
        )
    }
}

impl WriteTo for Object {
    fn write_to(&self, w: &mut dyn Write) -> io::Result<()> {
        self.nonce.write_to(w)?;
        self.header.write_to(w)?;
        self.object_payload.write_to(w)?;
        Ok(())
    }
}

impl SizedReadFrom for Object {
    fn sized_read_from(r: &mut dyn Read, len: usize) -> io::Result<Self>
    where
        Self: Sized,
    {
        let nonce = pow::Nonce::read_from(r)?;
        let header = object::Header::read_from(r)?;
        let object_payload_len = len - nonce.len_bm() - header.len_bm();
        if object_payload_len > Self::MAX_OBJECT_PAYLOAD_LENGTH {
            return Err(io::Error::new(
                io::ErrorKind::Other,
                TooLongError::new(Self::MAX_OBJECT_PAYLOAD_LENGTH, object_payload_len),
            ));
        }
        let object_payload = Vec::<u8>::sized_read_from(r, object_payload_len)?;
        Ok(Self {
            nonce,
            header,
            object_payload,
        })
    }
}

impl Message for Object {
    const COMMAND: Command = Command::OBJECT;
}

#[test]
fn test_object_write_to() {
    let nonce = pow::Nonce::new(0xfedc_ba98_7654_3210);
    let header = object::Header::new(
        0x0123_4567_89ab_cdef.into(),
        2.into(),
        3u64.into(),
        1u32.into(),
    );
    let object_payload = [0xff; 23].to_vec();
    let test = Object::new(nonce, header, object_payload).unwrap();
    let mut bytes = Vec::new();
    test.write_to(&mut bytes).unwrap();
    let expected = [
        0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x32, 0x10, //
        0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0x00, 0x00, 0x00, 0x02, 3, 1, //
        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, //
    ];
    assert_eq!(bytes, expected.to_vec());
}

#[test]
fn test_object_sized_read_from() {
    use std::io::Cursor;

    let mut bytes = Cursor::new(
        [
            0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x32, 0x10, //
            0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0x00, 0x00, 0x00, 0x02, 3, 1, //
            0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
            0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, //
        ]
        .to_vec(),
    );
    let test = Object::sized_read_from(&mut bytes, 45).unwrap();
    let nonce = pow::Nonce::new(0xfedc_ba98_7654_3210);
    let header = object::Header::new(
        0x0123_4567_89ab_cdef.into(),
        2.into(),
        3u64.into(),
        1u32.into(),
    );
    let object_payload = [0xff; 23].to_vec();
    let expected = Object::new(nonce, header, object_payload).unwrap();
    assert_eq!(test, expected);
}