use crate::error::{Result, WalError};
pub const CALVIN_APPLIED_PAYLOAD_SIZE: usize = 16;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct CalvinAppliedPayload {
pub epoch: u64,
pub position: u32,
pub vshard_id: u32,
}
impl CalvinAppliedPayload {
pub fn new(epoch: u64, position: u32, vshard_id: u32) -> Self {
Self {
epoch,
position,
vshard_id,
}
}
pub fn to_bytes(&self) -> [u8; CALVIN_APPLIED_PAYLOAD_SIZE] {
let mut buf = [0u8; CALVIN_APPLIED_PAYLOAD_SIZE];
buf[..8].copy_from_slice(&self.epoch.to_le_bytes());
buf[8..12].copy_from_slice(&self.position.to_le_bytes());
buf[12..16].copy_from_slice(&self.vshard_id.to_le_bytes());
buf
}
pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
if bytes.len() < CALVIN_APPLIED_PAYLOAD_SIZE {
return Err(WalError::InvalidPayload {
detail: format!(
"CalvinApplied payload must be {CALVIN_APPLIED_PAYLOAD_SIZE} bytes, got {}",
bytes.len()
),
});
}
let epoch = u64::from_le_bytes(bytes[..8].try_into().expect(
"invariant: bounds-checked above (bytes.len() >= CALVIN_APPLIED_PAYLOAD_SIZE >= 8)",
));
let position = u32::from_le_bytes(bytes[8..12].try_into().expect(
"invariant: bounds-checked above (bytes.len() >= CALVIN_APPLIED_PAYLOAD_SIZE >= 12)",
));
let vshard_id = u32::from_le_bytes(bytes[12..16].try_into().expect(
"invariant: bounds-checked above (bytes.len() >= CALVIN_APPLIED_PAYLOAD_SIZE >= 16)",
));
Ok(Self {
epoch,
position,
vshard_id,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn roundtrip() {
let p = CalvinAppliedPayload::new(42, 7, 3);
let bytes = p.to_bytes();
let decoded = CalvinAppliedPayload::from_bytes(&bytes).unwrap();
assert_eq!(decoded.epoch, 42);
assert_eq!(decoded.position, 7);
assert_eq!(decoded.vshard_id, 3);
}
#[test]
fn too_short_payload_returns_error() {
let result = CalvinAppliedPayload::from_bytes(&[0u8; 5]);
assert!(result.is_err());
}
#[test]
fn payload_size_is_correct() {
let p = CalvinAppliedPayload::new(u64::MAX, u32::MAX, u32::MAX);
let bytes = p.to_bytes();
assert_eq!(bytes.len(), CALVIN_APPLIED_PAYLOAD_SIZE);
}
}