1use crate::error::RvfError;
8
9pub const DELTA_MAGIC: u32 = 0x5256_444C;
11
12#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
14#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
15#[repr(u8)]
16pub enum DeltaEncoding {
17 SparseRows = 0,
19 LowRank = 1,
21 FullPatch = 2,
23}
24
25impl TryFrom<u8> for DeltaEncoding {
26 type Error = RvfError;
27
28 fn try_from(value: u8) -> Result<Self, Self::Error> {
29 match value {
30 0 => Ok(Self::SparseRows),
31 1 => Ok(Self::LowRank),
32 2 => Ok(Self::FullPatch),
33 _ => Err(RvfError::InvalidEnumValue {
34 type_name: "DeltaEncoding",
35 value: value as u64,
36 }),
37 }
38 }
39}
40
41#[derive(Clone, Copy, Debug)]
46#[repr(C)]
47pub struct DeltaHeader {
48 pub magic: u32,
50 pub version: u16,
52 pub encoding: u8,
54 pub _pad: u8,
56 pub base_cluster_id: u32,
58 pub affected_count: u32,
60 pub delta_size: u64,
62 pub delta_hash: [u8; 32],
64 pub _reserved: [u8; 8],
66}
67
68const _: () = assert!(core::mem::size_of::<DeltaHeader>() == 64);
70
71impl DeltaHeader {
72 pub fn to_bytes(&self) -> [u8; 64] {
74 let mut buf = [0u8; 64];
75 buf[0x00..0x04].copy_from_slice(&self.magic.to_le_bytes());
76 buf[0x04..0x06].copy_from_slice(&self.version.to_le_bytes());
77 buf[0x06] = self.encoding;
78 buf[0x07] = self._pad;
79 buf[0x08..0x0C].copy_from_slice(&self.base_cluster_id.to_le_bytes());
80 buf[0x0C..0x10].copy_from_slice(&self.affected_count.to_le_bytes());
81 buf[0x10..0x18].copy_from_slice(&self.delta_size.to_le_bytes());
82 buf[0x18..0x38].copy_from_slice(&self.delta_hash);
83 buf[0x38..0x40].copy_from_slice(&self._reserved);
84 buf
85 }
86
87 pub fn from_bytes(data: &[u8; 64]) -> Result<Self, RvfError> {
89 let magic = u32::from_le_bytes([data[0], data[1], data[2], data[3]]);
90 if magic != DELTA_MAGIC {
91 return Err(RvfError::BadMagic {
92 expected: DELTA_MAGIC,
93 got: magic,
94 });
95 }
96
97 Ok(Self {
98 magic,
99 version: u16::from_le_bytes([data[0x04], data[0x05]]),
100 encoding: data[0x06],
101 _pad: data[0x07],
102 base_cluster_id: u32::from_le_bytes([data[0x08], data[0x09], data[0x0A], data[0x0B]]),
103 affected_count: u32::from_le_bytes([data[0x0C], data[0x0D], data[0x0E], data[0x0F]]),
104 delta_size: u64::from_le_bytes([
105 data[0x10], data[0x11], data[0x12], data[0x13],
106 data[0x14], data[0x15], data[0x16], data[0x17],
107 ]),
108 delta_hash: {
109 let mut h = [0u8; 32];
110 h.copy_from_slice(&data[0x18..0x38]);
111 h
112 },
113 _reserved: {
114 let mut r = [0u8; 8];
115 r.copy_from_slice(&data[0x38..0x40]);
116 r
117 },
118 })
119 }
120}
121
122#[cfg(test)]
123mod tests {
124 use super::*;
125
126 fn sample_header() -> DeltaHeader {
127 DeltaHeader {
128 magic: DELTA_MAGIC,
129 version: 1,
130 encoding: DeltaEncoding::SparseRows as u8,
131 _pad: 0,
132 base_cluster_id: 42,
133 affected_count: 10,
134 delta_size: 2048,
135 delta_hash: [0xDD; 32],
136 _reserved: [0; 8],
137 }
138 }
139
140 #[test]
141 fn header_size_is_64() {
142 assert_eq!(core::mem::size_of::<DeltaHeader>(), 64);
143 }
144
145 #[test]
146 fn magic_bytes_match_ascii() {
147 let bytes_be = DELTA_MAGIC.to_be_bytes();
148 assert_eq!(&bytes_be, b"RVDL");
149 }
150
151 #[test]
152 fn round_trip_serialization() {
153 let original = sample_header();
154 let bytes = original.to_bytes();
155 let decoded = DeltaHeader::from_bytes(&bytes).expect("from_bytes should succeed");
156
157 assert_eq!(decoded.magic, DELTA_MAGIC);
158 assert_eq!(decoded.version, 1);
159 assert_eq!(decoded.encoding, DeltaEncoding::SparseRows as u8);
160 assert_eq!(decoded._pad, 0);
161 assert_eq!(decoded.base_cluster_id, 42);
162 assert_eq!(decoded.affected_count, 10);
163 assert_eq!(decoded.delta_size, 2048);
164 assert_eq!(decoded.delta_hash, [0xDD; 32]);
165 assert_eq!(decoded._reserved, [0; 8]);
166 }
167
168 #[test]
169 fn bad_magic_returns_error() {
170 let mut bytes = sample_header().to_bytes();
171 bytes[0] = 0x00; let err = DeltaHeader::from_bytes(&bytes).unwrap_err();
173 match err {
174 RvfError::BadMagic { expected, .. } => assert_eq!(expected, DELTA_MAGIC),
175 other => panic!("expected BadMagic, got {other:?}"),
176 }
177 }
178
179 #[test]
180 fn field_offsets() {
181 let h = sample_header();
182 let base = &h as *const _ as usize;
183
184 assert_eq!(&h.magic as *const _ as usize - base, 0x00);
185 assert_eq!(&h.version as *const _ as usize - base, 0x04);
186 assert_eq!(&h.encoding as *const _ as usize - base, 0x06);
187 assert_eq!(&h._pad as *const _ as usize - base, 0x07);
188 assert_eq!(&h.base_cluster_id as *const _ as usize - base, 0x08);
189 assert_eq!(&h.affected_count as *const _ as usize - base, 0x0C);
190 assert_eq!(&h.delta_size as *const _ as usize - base, 0x10);
191 assert_eq!(&h.delta_hash as *const _ as usize - base, 0x18);
192 assert_eq!(&h._reserved as *const _ as usize - base, 0x38);
193 }
194
195 #[test]
196 fn delta_encoding_try_from() {
197 assert_eq!(DeltaEncoding::try_from(0), Ok(DeltaEncoding::SparseRows));
198 assert_eq!(DeltaEncoding::try_from(1), Ok(DeltaEncoding::LowRank));
199 assert_eq!(DeltaEncoding::try_from(2), Ok(DeltaEncoding::FullPatch));
200 assert!(DeltaEncoding::try_from(3).is_err());
201 assert!(DeltaEncoding::try_from(0xFF).is_err());
202 }
203}