1use rudb_common::Result;
14
15use super::{Cursor, invalid, put_u32, put_u64};
16
17pub(crate) const LOG_ANCHOR: &[u8; 8] = b"RUDBWL1\0";
19
20const VERSION: u8 = 1;
22
23const MAX_LANES: usize = 64;
25
26const MAX_VOIDS: usize = 1 << 16;
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
31pub struct LaneStart {
32 pub sequence: u64,
34 pub offset: u64,
36}
37
38#[derive(Debug, Clone, PartialEq, Eq, Default)]
40pub struct LogAnchor {
41 pub database: u64,
43 pub durable: u64,
45 pub lanes: Vec<LaneStart>,
47 pub voids: Vec<u64>,
49}
50
51impl LogAnchor {
52 #[must_use]
54 pub fn replays(&self, ts: u64) -> bool {
55 ts > self.durable && self.voids.binary_search(&ts).is_err()
56 }
57
58 pub(crate) fn encode(&self, out: &mut Vec<u8>) -> Result<()> {
60 if self.lanes.len() > MAX_LANES || self.voids.len() > MAX_VOIDS {
61 return Err(invalid("log anchor holds more lanes or voids than it can"));
62 }
63 out.extend_from_slice(LOG_ANCHOR);
64 out.push(VERSION);
65 put_u64(out, self.database);
66 put_u64(out, self.durable);
67 out.push(self.lanes.len() as u8);
68 for lane in &self.lanes {
69 put_u64(out, lane.sequence);
70 put_u64(out, lane.offset);
71 }
72 put_u32(out, self.voids.len() as u32);
73 for &ts in &self.voids {
74 put_u64(out, ts);
75 }
76 Ok(())
77 }
78
79 pub(crate) fn decode(cur: &mut Cursor<'_>) -> Result<Self> {
81 if cur.u8()? != VERSION {
82 return Err(invalid("log anchor version differs"));
83 }
84 let database = cur.u64()?;
85 let durable = cur.u64()?;
86 let count = cur.u8()? as usize;
87 if count > MAX_LANES {
88 return Err(invalid("log anchor names more lanes than a log has"));
89 }
90 let mut lanes = Vec::with_capacity(count);
91 for _ in 0..count {
92 lanes.push(LaneStart { sequence: cur.u64()?, offset: cur.u64()? });
93 }
94 let count = cur.u32()? as usize;
95 if count > MAX_VOIDS {
96 return Err(invalid("log anchor holds more voids than it can"));
97 }
98 let mut voids = Vec::with_capacity(count);
99 for _ in 0..count {
100 voids.push(cur.u64()?);
101 }
102 if voids.windows(2).any(|pair| pair[0] >= pair[1])
103 || voids.first().is_some_and(|&ts| ts <= durable)
104 {
105 return Err(invalid("log anchor voids are out of order or under the cut"));
106 }
107 Ok(Self { database, durable, lanes, voids })
108 }
109}