use crate::error::{Result, WalError};
pub const RESTORE_POINT_PAYLOAD_SIZE: usize = 56;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RestorePointPayload {
pub id: u64,
pub hlc: u64,
pub group_id: u64,
pub applied_index: u64,
pub term: u64,
pub next_epoch: u64,
pub epoch_system_ms: u64,
pub vshards: Vec<u32>,
}
impl RestorePointPayload {
pub fn to_bytes(&self) -> Vec<u8> {
let mut buf = Vec::with_capacity(RESTORE_POINT_PAYLOAD_SIZE + 4 + 4 * self.vshards.len());
for value in [
self.id,
self.hlc,
self.group_id,
self.applied_index,
self.term,
self.next_epoch,
self.epoch_system_ms,
] {
buf.extend_from_slice(&value.to_le_bytes());
}
let count = u32::try_from(self.vshards.len()).unwrap_or(u32::MAX);
buf.extend_from_slice(&count.to_le_bytes());
for vshard in self.vshards.iter().take(count as usize) {
buf.extend_from_slice(&vshard.to_le_bytes());
}
buf
}
pub fn from_bytes(buf: &[u8]) -> Result<Self> {
let refuse = || WalError::InvalidPayload {
detail: format!(
"RestorePoint payload of {} bytes is no {RESTORE_POINT_PAYLOAD_SIZE}-byte head, \
count and vShard list",
buf.len()
),
};
let head = buf.get(..RESTORE_POINT_PAYLOAD_SIZE).ok_or_else(refuse)?;
let mut fields = [0u64; 7];
for (field, word) in fields.iter_mut().zip(head.as_chunks::<8>().0) {
*field = u64::from_le_bytes(*word);
}
let [
id,
hlc,
group_id,
applied_index,
term,
next_epoch,
epoch_system_ms,
] = fields;
let rest = &buf[RESTORE_POINT_PAYLOAD_SIZE..];
let count: [u8; 4] = rest
.get(..4)
.and_then(|b| b.try_into().ok())
.ok_or_else(refuse)?;
let count = u32::from_le_bytes(count) as usize;
let list = &rest[4..];
if list.len() != count.checked_mul(4).ok_or_else(refuse)? {
return Err(refuse());
}
let vshards = list
.as_chunks::<4>()
.0
.iter()
.map(|word| u32::from_le_bytes(*word))
.collect();
Ok(Self {
id,
hlc,
group_id,
applied_index,
term,
next_epoch,
epoch_system_ms,
vshards,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn restore_point_roundtrip() {
let point = RestorePointPayload {
id: 3,
hlc: 1_700_000_000_000_000_000,
group_id: 7,
applied_index: 1_234,
term: 5,
next_epoch: 0,
epoch_system_ms: 1_700_000_000_123,
vshards: vec![4, 9, 1023],
};
assert_eq!(
RestorePointPayload::from_bytes(&point.to_bytes()).unwrap(),
point
);
}
#[test]
fn wrong_size_is_refused() {
assert!(RestorePointPayload::from_bytes(&[0u8; 55]).is_err());
assert!(
RestorePointPayload::from_bytes(&[0u8; 56]).is_err(),
"no count"
);
let mut one = vec![0u8; 60];
one[56] = 1;
assert!(
RestorePointPayload::from_bytes(&one).is_err(),
"a count above the list"
);
assert!(RestorePointPayload::from_bytes(&[0u8; 60]).is_ok());
}
}