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;
#[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,
}
pub fn is_bottle(data: &[u8]) -> bool {
data.len() >= 5 && data[0..4] == MAGIC && data[4] == VERSION
}
#[derive(Clone, Eq, PartialEq)]
pub struct BottleCap {
pub bottle_type: BottleType,
pub header: Header,
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)
}
}
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)
}
}
#[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() {
let data = decode("f09f8dbc0002ffff214155310096").unwrap();
assert_eq!(BottleCap::read(&mut &data[..]).unwrap_err().to_string(), "Bottle header too large");
let data = decode("e09f8dbc00020200214155310096").unwrap();
assert_eq!(BottleCap::read(&mut &data[..]).unwrap_err().to_string(), "Bad magic number");
let data = decode("f09f8dbc00ff0200214155310096").unwrap();
assert_eq!(BottleCap::read(&mut &data[..]).unwrap_err().to_string(), "Unknown bottle type");
let data = decode("f09f8dbc09020200214155310096").unwrap();
assert_eq!(BottleCap::read(&mut &data[..]).unwrap_err().to_string(), "Unknown bottle version");
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");
}
}