nodedb_cluster/
conf_change.rs1pub const CONF_CHANGE_PREFIX: u8 = 0xC1;
20
21#[derive(
23 Debug,
24 Clone,
25 Copy,
26 PartialEq,
27 Eq,
28 serde::Serialize,
29 serde::Deserialize,
30 zerompk::ToMessagePack,
31 zerompk::FromMessagePack,
32)]
33#[repr(u8)]
34#[msgpack(c_enum)]
35pub enum ConfChangeType {
36 AddNode = 0,
38 RemoveNode = 1,
40 AddLearner = 2,
42 PromoteLearner = 3,
44}
45
46#[derive(
48 Debug,
49 Clone,
50 serde::Serialize,
51 serde::Deserialize,
52 zerompk::ToMessagePack,
53 zerompk::FromMessagePack,
54)]
55pub struct ConfChange {
56 pub change_type: ConfChangeType,
57 pub node_id: u64,
59}
60
61impl ConfChange {
62 pub fn to_entry_data(&self) -> Vec<u8> {
64 let mut data = vec![CONF_CHANGE_PREFIX];
65 let payload = zerompk::to_msgpack_vec(self).expect("ConfChange serialization cannot fail");
66 data.extend_from_slice(&payload);
67 data
68 }
69
70 pub fn from_entry_data(data: &[u8]) -> Option<Self> {
74 if data.first() != Some(&CONF_CHANGE_PREFIX) {
75 return None;
76 }
77 zerompk::from_msgpack(&data[1..]).ok()
78 }
79
80 pub fn is_conf_change(data: &[u8]) -> bool {
82 data.first() == Some(&CONF_CHANGE_PREFIX)
83 }
84}
85
86#[cfg(test)]
87mod tests {
88 use super::*;
89
90 #[test]
91 fn roundtrip_add_node() {
92 let cc = ConfChange {
93 change_type: ConfChangeType::AddNode,
94 node_id: 42,
95 };
96 let data = cc.to_entry_data();
97 assert_eq!(data[0], CONF_CHANGE_PREFIX);
98
99 let decoded = ConfChange::from_entry_data(&data).unwrap();
100 assert_eq!(decoded.change_type, ConfChangeType::AddNode);
101 assert_eq!(decoded.node_id, 42);
102 }
103
104 #[test]
105 fn roundtrip_remove_node() {
106 let cc = ConfChange {
107 change_type: ConfChangeType::RemoveNode,
108 node_id: 7,
109 };
110 let data = cc.to_entry_data();
111 let decoded = ConfChange::from_entry_data(&data).unwrap();
112 assert_eq!(decoded.change_type, ConfChangeType::RemoveNode);
113 assert_eq!(decoded.node_id, 7);
114 }
115
116 #[test]
117 fn regular_data_not_conf_change() {
118 assert!(!ConfChange::is_conf_change(b"hello"));
119 assert!(!ConfChange::is_conf_change(&[]));
120 assert!(ConfChange::from_entry_data(b"hello").is_none());
121 }
122
123 #[test]
124 fn prefix_is_msgpack_never_used_byte() {
125 assert_eq!(CONF_CHANGE_PREFIX, 0xC1);
129 }
130
131 #[test]
132 fn prefix_does_not_collide_with_msgpack_metadata_entry() {
133 let cc = ConfChange {
137 change_type: ConfChangeType::AddNode,
138 node_id: 1,
139 };
140 let msgpack_struct = zerompk::to_msgpack_vec(&cc).unwrap();
141 assert_ne!(msgpack_struct.first(), Some(&CONF_CHANGE_PREFIX));
142 }
143
144 #[test]
145 fn all_change_types() {
146 for ct in [
147 ConfChangeType::AddNode,
148 ConfChangeType::RemoveNode,
149 ConfChangeType::AddLearner,
150 ConfChangeType::PromoteLearner,
151 ] {
152 let cc = ConfChange {
153 change_type: ct,
154 node_id: 1,
155 };
156 let data = cc.to_entry_data();
157 let decoded = ConfChange::from_entry_data(&data).unwrap();
158 assert_eq!(decoded.change_type, ct);
159 }
160 }
161}