use anyhow::{Result, ensure};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use super::{Component, MAX_REQUEST_BYTES};
pub const CONTENT_TYPE: &str = "application/vnd.aven.bootstrap-batch";
pub const MAGIC: &[u8; 4] = b"AVBU";
pub const VERSION: u16 = 1;
pub const MAX_PAYLOAD: usize = 4 * 1_048_576;
pub const MAX_RECORDS: usize = 64;
pub const MAX_HEADER: usize = 4096;
const PREFIX: usize = MAGIC.len() + 2 + 4;
pub const MAX_BYTES: usize = PREFIX + MAX_HEADER + MAX_PAYLOAD;
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Header {
#[serde(with = "hex32")]
pub vault: [u8; 32],
#[serde(with = "hex32")]
pub genesis: [u8; 32],
#[serde(with = "hex32")]
pub bootstrap: [u8; 32],
#[serde(with = "hex32")]
pub commitment: [u8; 32],
pub records: Vec<Slot>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Slot {
#[serde(serialize_with = "component_out", deserialize_with = "component_in")]
pub component: Component,
pub index: u64,
pub len: u64,
}
pub struct Batch<'a> {
pub header: Header,
pub records: Vec<&'a [u8]>,
}
fn check(header: &Header) -> Result<usize> {
let records = &header.records;
ensure!(
!records.is_empty() && records.len() <= MAX_RECORDS,
"error bootstrap-batch-shape"
);
let mut payload = 0_usize;
for slot in records {
let len = usize::try_from(slot.len)
.ok()
.filter(|len| *len > 0 && *len <= MAX_REQUEST_BYTES);
let len = len.ok_or_else(|| anyhow::anyhow!("error bootstrap-batch-shape"))?;
payload = payload
.checked_add(len)
.filter(|total| *total <= MAX_PAYLOAD)
.ok_or_else(|| anyhow::anyhow!("error bootstrap-batch-shape"))?;
}
let catalogs = records
.iter()
.filter(|slot| slot.component.catalog().is_some())
.count();
ensure!(
catalogs == 0 || catalogs == records.len(),
"error bootstrap-batch-mixed"
);
for (position, slot) in records.iter().enumerate() {
ensure!(
records[..position]
.iter()
.all(|other| (other.component, other.index) != (slot.component, slot.index)),
"error bootstrap-batch-shape"
);
}
Ok(payload)
}
pub fn header_len(header: &Header) -> Option<usize> {
serde_json::to_vec(header)
.ok()
.map(|bytes| bytes.len())
.filter(|len| *len <= MAX_HEADER)
}
pub fn encode(header: &Header, records: &[&[u8]]) -> Result<Vec<u8>> {
let payload = check(header)?;
ensure!(
records.len() == header.records.len()
&& records
.iter()
.zip(&header.records)
.all(|(bytes, slot)| bytes.len() as u64 == slot.len),
"error bootstrap-batch-shape"
);
let json = serde_json::to_vec(header)?;
ensure!(json.len() <= MAX_HEADER, "error bootstrap-batch-shape");
let mut out = Vec::with_capacity(PREFIX + json.len() + payload);
out.extend_from_slice(MAGIC);
out.extend_from_slice(&VERSION.to_be_bytes());
out.extend_from_slice(&(json.len() as u32).to_be_bytes());
out.extend_from_slice(&json);
for record in records {
out.extend_from_slice(record);
}
Ok(out)
}
pub fn decode(bytes: &[u8]) -> Result<Batch<'_>> {
ensure!(
bytes.len() >= PREFIX && bytes.len() <= MAX_BYTES,
"error bootstrap-batch-framing"
);
let (magic, rest) = bytes.split_at(MAGIC.len());
ensure!(magic == MAGIC, "error bootstrap-batch-framing");
let (version, rest) = rest.split_at(2);
ensure!(
u16::from_be_bytes([version[0], version[1]]) == VERSION,
"error bootstrap-batch-version"
);
let (len, rest) = rest.split_at(4);
let len = u32::from_be_bytes([len[0], len[1], len[2], len[3]]) as usize;
ensure!(
len <= MAX_HEADER && len <= rest.len(),
"error bootstrap-batch-framing"
);
let (json, mut body) = rest.split_at(len);
let header: Header = serde_json::from_slice(json)
.map_err(|_| anyhow::anyhow!("error bootstrap-batch-framing"))?;
let payload = check(&header)?;
ensure!(body.len() == payload, "error bootstrap-batch-framing");
let mut records = Vec::with_capacity(header.records.len());
for slot in &header.records {
let (record, rest) = body.split_at(slot.len as usize);
records.push(record);
body = rest;
}
Ok(Batch { header, records })
}
fn component_out<S: Serializer>(component: &Component, s: S) -> Result<S::Ok, S::Error> {
s.serialize_str(&hex::encode(component.key()))
}
fn component_in<'de, D: Deserializer<'de>>(d: D) -> Result<Component, D::Error> {
let text = String::deserialize(d)?;
hex::decode(&text)
.ok()
.and_then(|key| Component::from_key(&key))
.ok_or_else(|| serde::de::Error::custom("invalid component"))
}
mod hex32 {
use serde::{Deserialize, Deserializer, Serializer};
pub(super) fn serialize<S: Serializer>(value: &[u8; 32], s: S) -> Result<S::Ok, S::Error> {
s.serialize_str(&hex::encode(value))
}
pub(super) fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result<[u8; 32], D::Error> {
let text = String::deserialize(d)?;
let mut out = [0; 32];
hex::decode_to_slice(&text, &mut out)
.map_err(|_| serde::de::Error::custom("invalid 32-byte hex"))?;
Ok(out)
}
}
#[cfg(test)]
mod tests;