reifydb_codec/log/
vote.rs1use crc32fast::Hasher;
5
6use crate::log::{LogIndex, NodeId, Term, VoteSeq};
7
8pub const SLOT_BYTES: usize = 56;
9
10pub const SLOTS: usize = 2;
11
12pub const FILE_BYTES: usize = SLOT_BYTES;
13
14pub const MAGIC: u32 = u32::from_le_bytes(*b"RVOT");
15
16pub const NONE: u64 = u64::MAX;
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub struct State {
20 pub term: Term,
21 pub voted_for: Option<NodeId>,
22 pub commit_index: LogIndex,
23 pub snapshot_index: LogIndex,
24 pub snapshot_term: Term,
25}
26
27impl State {
28 pub const EMPTY: Self = Self {
29 term: Term::ZERO,
30 voted_for: None,
31 commit_index: LogIndex::ZERO,
32 snapshot_index: LogIndex::ZERO,
33 snapshot_term: Term::ZERO,
34 };
35
36 pub fn encode(&self, seq: VoteSeq) -> [u8; SLOT_BYTES] {
37 let mut out = [0u8; SLOT_BYTES];
38 out[0..4].copy_from_slice(&MAGIC.to_le_bytes());
39 out[8..16].copy_from_slice(&seq.as_u64().to_le_bytes());
40 out[16..24].copy_from_slice(&self.term.as_u64().to_le_bytes());
41 out[24..32].copy_from_slice(&voted_for_bits(self.voted_for).to_le_bytes());
42 out[32..40].copy_from_slice(&self.commit_index.as_u64().to_le_bytes());
43 out[40..48].copy_from_slice(&self.snapshot_index.as_u64().to_le_bytes());
44 out[48..56].copy_from_slice(&self.snapshot_term.as_u64().to_le_bytes());
45 let checksum = checksum(&out[8..]);
46 out[4..8].copy_from_slice(&checksum.to_le_bytes());
47 out
48 }
49
50 pub fn decode(buf: &[u8; SLOT_BYTES]) -> Option<(Self, VoteSeq)> {
51 if u32::from_le_bytes(buf[0..4].try_into().unwrap()) != MAGIC {
52 return None;
53 }
54 if u32::from_le_bytes(buf[4..8].try_into().unwrap()) != checksum(&buf[8..]) {
55 return None;
56 }
57 let state = Self {
58 term: Term::new(u64::from_le_bytes(buf[16..24].try_into().unwrap())),
59 voted_for: voted_for_of(u64::from_le_bytes(buf[24..32].try_into().unwrap())),
60 commit_index: LogIndex::new(u64::from_le_bytes(buf[32..40].try_into().unwrap())),
61 snapshot_index: LogIndex::new(u64::from_le_bytes(buf[40..48].try_into().unwrap())),
62 snapshot_term: Term::new(u64::from_le_bytes(buf[48..56].try_into().unwrap())),
63 };
64 Some((state, VoteSeq::new(u64::from_le_bytes(buf[8..16].try_into().unwrap()))))
65 }
66}
67
68fn voted_for_bits(voted_for: Option<NodeId>) -> u64 {
69 match voted_for {
70 Some(node) => node.as_u64(),
71 None => NONE,
72 }
73}
74
75fn voted_for_of(bits: u64) -> Option<NodeId> {
76 if bits == NONE {
77 None
78 } else {
79 Some(NodeId::new(bits))
80 }
81}
82
83fn checksum(bytes: &[u8]) -> u32 {
84 let mut hasher = Hasher::new();
85 hasher.update(bytes);
86 hasher.finalize()
87}
88
89#[cfg(test)]
90mod tests {
91 use super::*;
92
93 #[test]
94 fn a_slot_lays_its_fields_out_at_the_documented_offsets() {
95 let raw = State {
98 term: Term::new(0x0102030405060708),
99 voted_for: Some(NodeId::new(0x1112131415161718)),
100 commit_index: LogIndex::new(0x2122232425262728),
101 snapshot_index: LogIndex::new(0x4142434445464748),
102 snapshot_term: Term::new(0x5152535455565758),
103 }
104 .encode(VoteSeq::new(0x3132333435363738));
105
106 assert_eq!(raw.len(), SLOT_BYTES);
107 assert_eq!(&raw[0..4], &MAGIC.to_le_bytes());
108 assert_eq!(&raw[8..16], &0x3132333435363738u64.to_le_bytes());
109 assert_eq!(&raw[16..24], &0x0102030405060708u64.to_le_bytes());
110 assert_eq!(&raw[24..32], &0x1112131415161718u64.to_le_bytes());
111 assert_eq!(&raw[32..40], &0x2122232425262728u64.to_le_bytes());
112 assert_eq!(&raw[40..48], &0x4142434445464748u64.to_le_bytes());
113 assert_eq!(&raw[48..56], &0x5152535455565758u64.to_le_bytes());
114 }
115
116 #[test]
117 fn no_vote_encodes_as_the_sentinel_and_decodes_back_to_none() {
118 let raw = State::EMPTY.encode(VoteSeq::FIRST);
121
122 assert_eq!(&raw[24..32], &NONE.to_le_bytes());
123 assert_eq!(State::decode(&raw).unwrap().0.voted_for, None);
124 }
125}