koibumi-core 0.0.9

The core library for Koibumi, an experimental Bitmessage client
Documentation
//! Provides encoding types for Bitmessage messages.

use std::{
    convert::{TryFrom, TryInto},
    fmt,
    io::{self, Read, Write},
};

use crate::{
    io::{ReadFrom, WriteTo},
    var_type::VarInt,
};

/// Message encoding types.
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub enum Encoding {
    /// Ignore encoding type.
    Ignore = 0,
    /// Trivial encoding type.
    Trivial = 1,
    /// Simple encoding type.
    Simple = 2,
    /// Extended encoding type.
    Extended = 3,
}

/// An error which can be returned when parsing a message encoding type.
/// The actual value of the input is returned as a payload of this error.
///
/// This error is used as the error type for
/// the `TryFrom` implementation for [`Encoding`].
///
/// [`Encoding`]: struct.Encoding.html
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct InvalidEncoding(u64);

impl fmt::Display for InvalidEncoding {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "invalid encoding: {}", self.0)
    }
}

impl std::error::Error for InvalidEncoding {}

impl TryFrom<u64> for Encoding {
    type Error = InvalidEncoding;

    fn try_from(value: u64) -> Result<Self, <Self as TryFrom<u64>>::Error> {
        match value {
            0 => Ok(Encoding::Ignore),
            1 => Ok(Encoding::Trivial),
            2 => Ok(Encoding::Simple),
            3 => Ok(Encoding::Extended),
            _ => Err(InvalidEncoding(value)),
        }
    }
}

impl WriteTo for Encoding {
    fn write_to(&self, w: &mut dyn Write) -> io::Result<()> {
        let v: VarInt = (*self as u64).into();
        v.write_to(w)?;
        Ok(())
    }
}

impl ReadFrom for Encoding {
    fn read_from(r: &mut dyn Read) -> io::Result<Self>
    where
        Self: Sized,
    {
        let v = VarInt::read_from(r)?;
        match v.as_u64().try_into() {
            Ok(encoding) => Ok(encoding),
            Err(err) => Err(io::Error::new(io::ErrorKind::Other, err)),
        }
    }
}

/// This error indicates that the operation on simple encoding failed.
#[derive(Clone, PartialEq, Eq, Debug)]
pub enum SimpleError {
    /// Indicates that the provided subject contains LF.
    SubjectContainsLf,
}

impl fmt::Display for SimpleError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::SubjectContainsLf => "subject contains LF".fmt(f),
        }
    }
}

impl std::error::Error for SimpleError {}

/// A message encoded in simple encoding type.
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
pub struct Simple {
    subject: Vec<u8>,
    body: Vec<u8>,
}

impl Simple {
    /// Constructs a simple encoding from a subject and a body.
    pub fn new(subject: Vec<u8>, body: Vec<u8>) -> Result<Self, SimpleError> {
        if let Some(_item) = subject.iter().find(|b| **b == b'\n') {
            return Err(SimpleError::SubjectContainsLf);
        }
        Ok(Self { subject, body })
    }

    /// Returns the subject.
    pub fn subject(&self) -> &[u8] {
        &self.subject
    }

    /// Returns the body.
    pub fn body(&self) -> &[u8] {
        &self.body
    }
}

impl WriteTo for Simple {
    fn write_to(&self, w: &mut dyn Write) -> io::Result<()> {
        b"Subject:".write_to(w)?;
        self.subject.write_to(w)?;
        b'\n'.write_to(w)?;
        b"Body:".write_to(w)?;
        self.body.write_to(w)?;
        Ok(())
    }
}

/// The error type returned when a conversion from a byte array
/// to a simple encoded message fails.
///
/// This error is used as the error type for the `TryFrom` implementation
/// for `Simple`.
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
pub enum TryIntoSimpleError {
    /// Indicates that no subject was found.
    SubjectNotFound,
    /// Indicates that no body was found.
    BodyNotFound,
}

const SUBJECT: &[u8] = b"Subject:";
const BODY: &[u8] = b"Body:";

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

    fn try_from(bytes: &[u8]) -> Result<Self, <Self as TryFrom<&[u8]>>::Error> {
        if bytes.len() < SUBJECT.len() {
            return Err(TryIntoSimpleError::SubjectNotFound);
        }
        if &bytes[..SUBJECT.len()] != SUBJECT {
            return Err(TryIntoSimpleError::SubjectNotFound);
        }
        let lf_pos = bytes[SUBJECT.len()..].iter().position(|b| *b == b'\n');
        if lf_pos.is_none() {
            return Err(TryIntoSimpleError::SubjectNotFound);
        }
        let lf_pos = SUBJECT.len() + lf_pos.unwrap();
        let subject = &bytes[SUBJECT.len()..lf_pos];

        if bytes.len() < lf_pos + 1 + BODY.len() {
            return Err(TryIntoSimpleError::BodyNotFound);
        }
        if &bytes[lf_pos + 1..lf_pos + 1 + BODY.len()] != BODY {
            return Err(TryIntoSimpleError::BodyNotFound);
        }
        let body = &bytes[lf_pos + 1 + BODY.len()..];

        Ok(Self {
            subject: subject.to_vec(),
            body: body.to_vec(),
        })
    }
}