bitbottle 0.10.0

a modern archive file format
Documentation
use num_enum::TryFromPrimitive;
use std::cmp::min;
use std::fmt;
use std::io::{Read, Write};
use crate::bottle_error::{BottleError, BottleResult};
use crate::header::{Header, MAX_HEADER_BYTES};

const MAGIC: [u8; 4] = [ 0xf0, 0x9f, 0x8d, 0xbc ];
const VERSION: u8 = 0;
const CAP_LENGTH: usize = 12;

/// Names for the `bottle_type: u8` field in `BottleCap`.
#[derive(Clone, Copy, Debug, Eq, PartialEq, TryFromPrimitive)]
#[repr(u8)]
#[non_exhaustive]
pub enum BottleType {
    FileList = 1,
    File = 2,
    FileBlock = 3,
    Compressed = 4,
    Encrypted = 5,
    Signed = 6,
    Test = 15,
}


/// Given a buffer of the first (at least 5) bytes of a stream, is this a
/// bottle?
pub fn is_bottle(data: &[u8]) -> bool {
    data.len() >= 5 && data[0..4] == MAGIC && data[4] == VERSION
}


/// The bottle cap introduces a new bottle. It's a 12-byte struct that's
/// followed by a (possibly zero-length) header and zero or more streams.
///   - `magic: u8[4] = F0 9F 8D BC` (🍼)
///   - `version: u8 = 00`
///   - `bottle type: u8`
///   - `header length: u16`
///   - `crc32c: u32  (of version, bottle type, header length, and the header)`
#[derive(Clone, Eq, PartialEq)]
pub struct BottleCap {
    pub bottle_type: BottleType,
    pub header: Header,

    // for reading:
    offset: usize,
}

impl Default for BottleCap {
    fn default() -> BottleCap {
        BottleCap { bottle_type: BottleType::Test, header: Header::default(), offset: 0 }
    }
}

impl BottleCap {
    pub fn new(bottle_type: BottleType, header: Header) -> BottleCap {
        BottleCap { bottle_type, header, offset: 0 }
    }

    fn build(&self) -> [u8; CAP_LENGTH] {
        let len = self.header.data.len() as u16;

        let mut cap = [0u8; 12];
        cap[0..4].copy_from_slice(&MAGIC);
        cap[4] = VERSION;
        cap[5] = self.bottle_type as u8;
        cap[6..8].copy_from_slice(&len.to_le_bytes());

        let crc = crc32c::crc32c_append(crc32c::crc32c(&cap[4..8]), &self.header.data);
        cap[8..12].copy_from_slice(&crc.to_le_bytes());
        cap
    }

    pub fn write(&self, writer: &mut dyn Write) -> BottleResult<()> {
        writer.write_all(&self.build())?;
        writer.write_all(&self.header.data)?;
        Ok(())
    }

    pub fn to_bytes(&self) -> Vec<u8> {
        let mut buffer = Vec::new();
        buffer.extend_from_slice(&self.build());
        buffer.extend_from_slice(&self.header.data);
        buffer
    }

    pub fn read(reader: &mut dyn Read) -> BottleResult<BottleCap> {
        let mut cap = [0u8; 12];
        reader.read_exact(&mut cap)?;
        if !&cap[0..4].eq(&MAGIC) {
            return Err(BottleError::BadMagic);
        }
        if cap[4] != VERSION {
            return Err(BottleError::UnknownVersion);
        }
        let bottle_type: BottleType = cap[5].try_into().map_err(|_| BottleError::UnknownBottleType)?;
        let len = u16::from_le_bytes((&cap[6..8]).try_into().unwrap()) as usize;
        let crc = u32::from_le_bytes((&cap[8..12]).try_into().unwrap());
        if len > MAX_HEADER_BYTES {
            return Err(BottleError::HeaderTooLarge);
        }

        let mut buffer = vec![0u8; len];
        reader.read_exact(&mut buffer)?;
        let my_crc = crc32c::crc32c_append(crc32c::crc32c(&cap[4..8]), &buffer);
        if crc != my_crc {
            return Err(BottleError::BadCrc { expected: my_crc, got: crc });
        }

        let header = Header::from(buffer);
        Ok(BottleCap::new(bottle_type, header))
    }

    pub fn dump(&self) -> Vec<String> {
        let mut rv = vec![ format!("Bitbottle type {}:", self.bottle_type as u8) ];
        rv.append(&mut self.header.dump().iter().map(|s| format!("    {s}")).collect());
        rv
    }
}

impl fmt::Debug for BottleCap {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "BottleCap(type={:02x}, {:?})", self.bottle_type as u8, self.header)
    }
}

/// Pull-oriented bottle cap generator.
impl Read for BottleCap {
    fn read(&mut self, mut buffer: &mut [u8]) -> Result<usize, std::io::Error> {
        let mut rv = 0;
        if self.offset < CAP_LENGTH {
            let cap = self.build();
            let len = min(CAP_LENGTH - self.offset, buffer.len());

            buffer[..len].copy_from_slice(&cap[self.offset .. self.offset + len]);
            rv += len;
            self.offset += len;
            buffer = &mut buffer[len..];
            if self.offset < CAP_LENGTH {
                return Ok(rv);
            }
        }
        let start = self.offset - CAP_LENGTH;
        let len = min(self.header.data.len() - start, buffer.len());
        buffer[..len].copy_from_slice(&self.header.data[start .. start + len]);
        rv += len;
        self.offset += len;
        Ok(rv)
    }
}


// ----- tests

#[cfg(test)]
mod test {
    use hex::{decode, encode};
    use std::io::Read;
    use crate::header::Header;
    use super::{BottleCap, BottleType};

    #[test]
    pub fn write() {
        let mut h = Header::new();
        h.add_int(0, 150).unwrap();
        let cap = BottleCap::new(BottleType::Test, h);

        let mut buffer = Vec::new();
        cap.write(&mut buffer).unwrap();
        assert_eq!(encode(&buffer), "f09f8dbc000f0200214155310096");
    }

    #[test]
    pub fn read() {
        // too long
        let data = decode("f09f8dbc0002ffff214155310096").unwrap();
        assert_eq!(BottleCap::read(&mut &data[..]).unwrap_err().to_string(), "Bottle header too large");

        // bad magic
        let data = decode("e09f8dbc00020200214155310096").unwrap();
        assert_eq!(BottleCap::read(&mut &data[..]).unwrap_err().to_string(), "Bad magic number");

        // unknown bottle type
        let data = decode("f09f8dbc00ff0200214155310096").unwrap();
        assert_eq!(BottleCap::read(&mut &data[..]).unwrap_err().to_string(), "Unknown bottle type");

        // unknown version
        let data = decode("f09f8dbc09020200214155310096").unwrap();
        assert_eq!(BottleCap::read(&mut &data[..]).unwrap_err().to_string(), "Unknown bottle version");

        // bad crc
        let data = decode("f09f8dbc00020200214155310096").unwrap();
        assert_eq!(
            BottleCap::read(&mut &data[..]).map(|_| 0).map_err(|e| format!("{e}")),
            Err("Bad CRC32C: got 31554121, expected 2c669bac".into())
        );

        let data = decode("f09f8dbc00020200ac9b662c0096").unwrap();
        let cap = BottleCap::read(&mut &data[..]).unwrap();
        assert_eq!(cap.bottle_type, BottleType::File);
        assert_eq!(format!("{:?}", cap.header), "Header(U8(0)=150)");
    }

    #[test]
    pub fn write_as_readable() {
        let mut h = Header::new();
        h.add_int(0, 150).unwrap();
        let mut cap = BottleCap::new(BottleType::Test, h);

        let mut buffer = vec![0u8; 1024];
        let n = cap.read(&mut buffer).unwrap();
        assert_eq!(encode(&buffer[..n]), "f09f8dbc000f0200214155310096");
    }
}