krabka_records_legacy/message/
encode.rs1use {
10 super::{Magic, Message},
11 bytes::{BufMut, Bytes},
12};
13
14impl Message {
15 #[must_use]
20 pub fn encoded_len(&self) -> usize {
21 let mut n = 4 + 1 + 1;
23 if matches!(self.magic, Magic::V1) {
24 n += 8;
25 }
26 n += nullable_bytes_len(self.key.as_ref());
27 n += nullable_bytes_len(self.value.as_ref());
28 n
29 }
30
31 pub fn encode_into<B: BufMut>(&self, buf: &mut B) {
35 let mut body = Vec::with_capacity(self.encoded_len());
40 body.push(self.magic.as_i8().cast_unsigned());
41 body.push(self.attributes.cast_unsigned());
42 if matches!(self.magic, Magic::V1) {
43 let ts = self.timestamp.unwrap_or(-1);
44 body.extend_from_slice(&ts.to_be_bytes());
45 }
46 put_nullable_bytes(&mut body, self.key.as_ref());
47 put_nullable_bytes(&mut body, self.value.as_ref());
48
49 let crc = crc32fast::hash(&body);
50 buf.put_u32(crc);
51 buf.put_slice(&body);
52 }
53}
54
55fn nullable_bytes_len(b: Option<&Bytes>) -> usize { 4 + b.map_or(0, Bytes::len) }
56
57fn put_nullable_bytes(buf: &mut Vec<u8>, b: Option<&Bytes>) {
58 match b {
59 None => buf.extend_from_slice(&(-1i32).to_be_bytes()),
60 Some(data) => {
61 let len = i32::try_from(data.len()).unwrap_or(i32::MAX);
62 buf.extend_from_slice(&len.to_be_bytes());
63 buf.extend_from_slice(data);
64 }
65 }
66}
67
68#[cfg(test)]
69mod tests {
70
71 use {
72 super::*,
73 crate::message::test_support::{
74 RoundTrip, fixture_v0, fixture_v1, fixture_v1_null, round_trip,
75 },
76 };
77
78 #[yare::parameterized(
79 v0 = { fixture_v0() },
80 v1 = { fixture_v1() },
81 )]
82 fn message_roundtrips(message: Message) {
83 let RoundTrip { buffer, decoded } = round_trip(&message);
84 assert2::assert!(buffer.len() == message.encoded_len());
85 assert2::assert!(decoded == message);
86 }
87
88 #[test]
89 fn v1_null_key_and_value() {
90 let m = fixture_v1_null();
91 let RoundTrip { decoded, .. } = round_trip(&m);
92 assert2::assert!(decoded == m);
93 }
94
95 #[test]
96 fn v1_missing_timestamp_encodes_minus_one() {
97 let m = Message {
98 magic: Magic::V1,
99 attributes: 0,
100 timestamp: None,
101 key: None,
102 value: None,
103 };
104 let RoundTrip { decoded, .. } = round_trip(&m);
105 assert2::assert!(
106 decoded
107 == Message {
108 timestamp: Some(-1),
109 ..m
110 }
111 );
112 }
113
114 #[test]
115 fn empty_key_is_some_not_null() {
116 let m = Message {
118 magic: Magic::V0,
119 attributes: 0,
120 timestamp: None,
121 key: Some(Bytes::new()),
122 value: Some(Bytes::from_static(b"v")),
123 };
124 let RoundTrip { decoded, .. } = round_trip(&m);
125 assert2::assert!(decoded == m);
126 }
127}