1use bytes::{Buf, BufMut, Bytes, BytesMut};
6
7use crate::{
8 Decode, Encode, ProtocolError,
9 owned::{
10 k_raft_version_record::KRaftVersionRecord, leader_change_message::LeaderChangeMessage,
11 snapshot_footer_record::SnapshotFooterRecord, snapshot_header_record::SnapshotHeaderRecord,
12 voters_record::VotersRecord,
13 },
14 records::{Attributes, Record, RecordBatch},
15};
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20#[repr(i16)]
21pub enum ControlRecordType {
22 LeaderChange = 2,
23 SnapshotHeader = 3,
24 SnapshotFooter = 4,
25 KRaftVersion = 5,
26 Voters = 6,
27}
28
29impl TryFrom<i16> for ControlRecordType {
30 type Error = ProtocolError;
31
32 fn try_from(value: i16) -> Result<Self, Self::Error> {
33 match value {
34 2 => Ok(Self::LeaderChange),
35 3 => Ok(Self::SnapshotHeader),
36 4 => Ok(Self::SnapshotFooter),
37 5 => Ok(Self::KRaftVersion),
38 6 => Ok(Self::Voters),
39 _ => Err(ProtocolError::InvalidValue("unknown control record type")),
40 }
41 }
42}
43
44#[derive(Debug, Clone, PartialEq, Eq)]
46pub enum ControlRecord {
47 LeaderChange(LeaderChangeMessage),
48 SnapshotHeader(SnapshotHeaderRecord),
49 SnapshotFooter(SnapshotFooterRecord),
50 KRaftVersion(KRaftVersionRecord),
51 Voters(VotersRecord),
52}
53
54const CONTROL_KEY_VERSION: i16 = 0;
56
57#[must_use]
59pub fn control_record_key(ty: ControlRecordType) -> Bytes {
60 let mut key = BytesMut::with_capacity(4);
61 key.put_i16(CONTROL_KEY_VERSION);
62 key.put_i16(ty as i16);
63 key.freeze()
64}
65
66pub fn decode_control_record_key(key: &[u8]) -> Result<ControlRecordType, ProtocolError> {
72 let mut cur = key;
73 if cur.remaining() < 4 {
74 return Err(ProtocolError::UnexpectedEof {
75 needed: 4 - cur.remaining(),
76 });
77 }
78 let version = cur.get_i16();
79 if version != CONTROL_KEY_VERSION {
80 return Err(ProtocolError::InvalidValue(
81 "unsupported control record key version",
82 ));
83 }
84 let record_type = ControlRecordType::try_from(cur.get_i16())?;
85 if cur.has_remaining() {
86 return Err(ProtocolError::InvalidValue(
87 "trailing bytes after control record key",
88 ));
89 }
90 Ok(record_type)
91}
92
93impl ControlRecord {
94 #[must_use]
96 pub fn record_type(&self) -> ControlRecordType {
97 match self {
98 Self::LeaderChange(_) => ControlRecordType::LeaderChange,
99 Self::SnapshotHeader(_) => ControlRecordType::SnapshotHeader,
100 Self::SnapshotFooter(_) => ControlRecordType::SnapshotFooter,
101 Self::KRaftVersion(_) => ControlRecordType::KRaftVersion,
102 Self::Voters(_) => ControlRecordType::Voters,
103 }
104 }
105
106 pub fn encode_key_value(&self) -> Result<(Bytes, Bytes), ProtocolError> {
112 let mut value = BytesMut::new();
113 match self {
114 Self::LeaderChange(record) => record.encode(&mut value, record.version)?,
115 Self::SnapshotHeader(record) => record.encode(&mut value, record.version)?,
116 Self::SnapshotFooter(record) => record.encode(&mut value, record.version)?,
117 Self::KRaftVersion(record) => record.encode(&mut value, record.version)?,
118 Self::Voters(record) => record.encode(&mut value, record.version)?,
119 }
120 Ok((control_record_key(self.record_type()), value.freeze()))
121 }
122
123 pub fn decode(key: &[u8], value: &[u8]) -> Result<Self, ProtocolError> {
133 let record_type = decode_control_record_key(key)?;
134 if value.len() < 2 {
135 return Err(ProtocolError::UnexpectedEof {
136 needed: 2 - value.len(),
137 });
138 }
139 let version = i16::from_be_bytes([value[0], value[1]]);
140 let mut cur = value;
141 let record = match record_type {
142 ControlRecordType::LeaderChange => {
143 Self::LeaderChange(LeaderChangeMessage::decode(&mut cur, version)?)
144 }
145 ControlRecordType::SnapshotHeader => {
146 Self::SnapshotHeader(SnapshotHeaderRecord::decode(&mut cur, version)?)
147 }
148 ControlRecordType::SnapshotFooter => {
149 Self::SnapshotFooter(SnapshotFooterRecord::decode(&mut cur, version)?)
150 }
151 ControlRecordType::KRaftVersion => {
152 Self::KRaftVersion(KRaftVersionRecord::decode(&mut cur, version)?)
153 }
154 ControlRecordType::Voters => Self::Voters(VotersRecord::decode(&mut cur, version)?),
155 };
156 if !cur.is_empty() {
157 return Err(ProtocolError::InvalidValue(
158 "trailing bytes after control record value",
159 ));
160 }
161 Ok(record)
162 }
163}
164
165pub fn encode_typed_control_batch(
170 base_offset: i64,
171 record: &ControlRecord,
172) -> Result<Bytes, ProtocolError> {
173 let (key, value) = record.encode_key_value()?;
174 Ok(encode_control_batch(base_offset, key, value))
175}
176
177#[must_use]
184pub fn encode_control_batch(base_offset: i64, key: Bytes, value: Bytes) -> Bytes {
185 let batch = RecordBatch {
186 base_offset,
187 attributes: Attributes::default().with_control(true),
188 records: vec![Record {
189 key: Some(key),
190 value: Some(value),
191 ..Default::default()
192 }],
193 ..Default::default()
194 };
195 let mut out = BytesMut::new();
196 batch
197 .encode(&mut out)
198 .expect("control batch encodes (no compression, in-range)");
199 out.freeze()
200}
201
202#[cfg(test)]
203mod tests {
204
205 use bytes::Buf;
206
207 use super::*;
208 use crate::{
209 UnknownTaggedFields,
210 owned::voters_record::{Endpoint, KRaftVersionFeature, Voter},
211 primitives::uuid::Uuid,
212 };
213
214 #[test]
215 fn control_key_is_version_then_type() {
216 let cases = [
217 (ControlRecordType::LeaderChange, 2),
218 (ControlRecordType::SnapshotHeader, 3),
219 (ControlRecordType::SnapshotFooter, 4),
220 (ControlRecordType::KRaftVersion, 5),
221 (ControlRecordType::Voters, 6),
222 ];
223 for (record_type, number) in cases {
224 let key = control_record_key(record_type);
225 let mut cur: &[u8] = &key;
226 assert2::assert!((cur.get_i16(), cur.get_i16()) == (0, number));
227 assert2::assert!(decode_control_record_key(&key).expect("decode key") == record_type);
228 }
229 }
230
231 #[test]
232 fn kip853_control_records_are_byte_exact_and_roundtrip() {
233 let version_record = ControlRecord::KRaftVersion(KRaftVersionRecord {
234 version: 0,
235 k_raft_version: 1,
236 unknown_tagged_fields: UnknownTaggedFields(vec![]),
237 });
238 let (version_key, version_value) = version_record.encode_key_value().expect("encode");
239 assert2::assert!(&version_key[..] == &[0, 0, 0, 5]);
240 assert2::assert!(&version_value[..] == &[0, 0, 0, 1, 0]);
241 assert2::assert!(
242 ControlRecord::decode(&version_key, &version_value).expect("decode") == version_record
243 );
244
245 let voters_record = ControlRecord::Voters(VotersRecord {
246 version: 0,
247 voters: vec![Voter {
248 voter_id: 7,
249 voter_directory_id: Uuid([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]),
250 endpoints: vec![Endpoint {
251 name: "CONTROLLER".to_string(),
252 host: "localhost".to_string(),
253 port: 9_093,
254 ..Default::default()
255 }],
256 k_raft_version_feature: KRaftVersionFeature {
257 min_supported_version: 0,
258 max_supported_version: 1,
259 ..Default::default()
260 },
261 ..Default::default()
262 }],
263 ..Default::default()
264 });
265 let (voters_key, voters_value) = voters_record.encode_key_value().expect("encode");
266 let expected_value = hex::decode(concat!(
267 "0000", "02", "00000007", "000102030405060708090a0b0c0d0e0f", "02", "0b434f4e54524f4c4c4552", "0a6c6f63616c686f7374", "2385", "00", "00000001", "00", "00", "00", ))
281 .expect("valid hex");
282 assert2::assert!(&voters_key[..] == &[0, 0, 0, 6]);
283 assert2::assert!(&voters_value[..] == expected_value);
284 assert2::assert!(
285 ControlRecord::decode(&voters_key, &voters_value).expect("decode") == voters_record
286 );
287 }
288
289 #[test]
290 fn malformed_control_records_are_rejected() {
291 let bad_keys: &[&[u8]] = &[
292 &[0, 0, 0], &[0, 1, 0, 5], &[0, 0, 0, 7], &[0, 0, 0, 5, 0], ];
297 for key in bad_keys {
298 assert2::assert!(decode_control_record_key(key).is_err());
299 }
300
301 let key = control_record_key(ControlRecordType::KRaftVersion);
302 assert2::assert!(ControlRecord::decode(&key, &[0]).is_err());
303 assert2::assert!(ControlRecord::decode(&key, &[0, 0, 0, 1, 0, 0]).is_err());
304 }
305
306 #[test]
307 fn control_batch_sets_control_bit() {
308 let key = control_record_key(ControlRecordType::LeaderChange);
309 let batch = encode_control_batch(42, key, bytes::Bytes::from_static(b"\x00\x00"));
310 let mut cur: &[u8] = &batch;
311 let decoded = RecordBatch::decode(&mut cur).expect("control batch decodes");
312 assert2::assert!(decoded.base_offset == 42);
313 assert2::assert!(cur.is_empty());
314 let attrs = i16::from_be_bytes([batch[21], batch[22]]);
316 assert2::assert!(attrs & 0x20 != 0);
317 }
318}