nodedb_cluster/
conf_change.rs1pub const CONF_CHANGE_PREFIX: u8 = 0xFF;
13
14#[derive(
16 Debug,
17 Clone,
18 Copy,
19 PartialEq,
20 Eq,
21 serde::Serialize,
22 serde::Deserialize,
23 zerompk::ToMessagePack,
24 zerompk::FromMessagePack,
25)]
26#[repr(u8)]
27#[msgpack(c_enum)]
28pub enum ConfChangeType {
29 AddNode = 0,
31 RemoveNode = 1,
33 AddLearner = 2,
35 PromoteLearner = 3,
37}
38
39#[derive(
41 Debug,
42 Clone,
43 serde::Serialize,
44 serde::Deserialize,
45 zerompk::ToMessagePack,
46 zerompk::FromMessagePack,
47)]
48pub struct ConfChange {
49 pub change_type: ConfChangeType,
50 pub node_id: u64,
52}
53
54impl ConfChange {
55 pub fn to_entry_data(&self) -> Vec<u8> {
57 let mut data = vec![CONF_CHANGE_PREFIX];
58 let payload = zerompk::to_msgpack_vec(self).expect("ConfChange serialization cannot fail");
59 data.extend_from_slice(&payload);
60 data
61 }
62
63 pub fn from_entry_data(data: &[u8]) -> Option<Self> {
67 if data.first() != Some(&CONF_CHANGE_PREFIX) {
68 return None;
69 }
70 zerompk::from_msgpack(&data[1..]).ok()
71 }
72
73 pub fn is_conf_change(data: &[u8]) -> bool {
75 data.first() == Some(&CONF_CHANGE_PREFIX)
76 }
77}
78
79#[cfg(test)]
80mod tests {
81 use super::*;
82
83 #[test]
84 fn roundtrip_add_node() {
85 let cc = ConfChange {
86 change_type: ConfChangeType::AddNode,
87 node_id: 42,
88 };
89 let data = cc.to_entry_data();
90 assert_eq!(data[0], CONF_CHANGE_PREFIX);
91
92 let decoded = ConfChange::from_entry_data(&data).unwrap();
93 assert_eq!(decoded.change_type, ConfChangeType::AddNode);
94 assert_eq!(decoded.node_id, 42);
95 }
96
97 #[test]
98 fn roundtrip_remove_node() {
99 let cc = ConfChange {
100 change_type: ConfChangeType::RemoveNode,
101 node_id: 7,
102 };
103 let data = cc.to_entry_data();
104 let decoded = ConfChange::from_entry_data(&data).unwrap();
105 assert_eq!(decoded.change_type, ConfChangeType::RemoveNode);
106 assert_eq!(decoded.node_id, 7);
107 }
108
109 #[test]
110 fn regular_data_not_conf_change() {
111 assert!(!ConfChange::is_conf_change(b"hello"));
112 assert!(!ConfChange::is_conf_change(&[]));
113 assert!(ConfChange::from_entry_data(b"hello").is_none());
114 }
115
116 #[test]
117 fn all_change_types() {
118 for ct in [
119 ConfChangeType::AddNode,
120 ConfChangeType::RemoveNode,
121 ConfChangeType::AddLearner,
122 ConfChangeType::PromoteLearner,
123 ] {
124 let cc = ConfChange {
125 change_type: ct,
126 node_id: 1,
127 };
128 let data = cc.to_entry_data();
129 let decoded = ConfChange::from_entry_data(&data).unwrap();
130 assert_eq!(decoded.change_type, ct);
131 }
132 }
133}