use serde::{Deserialize, Serialize};
const MAGIC: &[u8; 4] = b"BSRA";
pub const ARCHIVE_FORMAT: u32 = 1;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ArchiveHeader {
pub arena_id: String,
pub arena_version: String,
pub arena_digest: String,
pub payload_schema_version: u32,
pub seats: Vec<String>,
pub control_rate: u32,
pub turns: u32,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Archive {
pub header: ArchiveHeader,
pub frames: Vec<Vec<u8>>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ArchiveError {
BadMagic,
UnsupportedFormat(u32),
Corrupt(String),
}
impl std::fmt::Display for ArchiveError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::BadMagic => write!(f, "not a BotStadia replay archive"),
Self::UnsupportedFormat(format) => {
write!(f, "archive format {format} isn't one this reader knows")
}
Self::Corrupt(text) => write!(f, "archive is corrupt: {text}"),
}
}
}
impl std::error::Error for ArchiveError {}
impl Archive {
pub fn encode(&self) -> Vec<u8> {
let mut bytes = Vec::new();
bytes.extend_from_slice(MAGIC);
bytes.extend_from_slice(&ARCHIVE_FORMAT.to_le_bytes());
bytes
.extend_from_slice(&postcard::to_allocvec(self).expect("an archive always serialises"));
bytes
}
pub fn decode(bytes: &[u8]) -> Result<Self, ArchiveError> {
if bytes.len() < 8 || &bytes[..4] != MAGIC {
return Err(ArchiveError::BadMagic);
}
let format = u32::from_le_bytes(bytes[4..8].try_into().expect("four bytes"));
if format != ARCHIVE_FORMAT {
return Err(ArchiveError::UnsupportedFormat(format));
}
postcard::from_bytes(&bytes[8..]).map_err(|error| ArchiveError::Corrupt(error.to_string()))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn round_trips_and_refuses_strangers() {
let archive = Archive {
header: ArchiveHeader {
arena_id: "sample".into(),
arena_version: "0.1.0".into(),
arena_digest: String::new(),
payload_schema_version: 1,
seats: vec!["a".into(), "b".into()],
control_rate: 50,
turns: 2,
},
frames: vec![vec![1, 2], vec![3]],
};
let bytes = archive.encode();
assert_eq!(Archive::decode(&bytes).unwrap(), archive);
assert_eq!(Archive::decode(b"nope"), Err(ArchiveError::BadMagic));
let mut future = bytes.clone();
future[4] = 9;
assert_eq!(
Archive::decode(&future),
Err(ArchiveError::UnsupportedFormat(9))
);
}
}