1use super::{Envelope, EnvelopeError, EnvelopeLimits, Opcode};
4use crate::{
5 GroupId, MessageId,
6 layout::{LayoutError, Reader},
7};
8
9mod batch;
10mod descriptors;
11mod encoding;
12mod records;
13pub use encoding::Encoding;
14mod buffer;
15pub use batch::{BatchIter, BatchRecord, PayloadRef, RecordBatch, TinyRecords};
16mod indexed;
17mod owned;
18mod payload;
19pub use buffer::RecordBuffer;
20pub(super) use descriptors::validate_raw_record_entries;
21pub use descriptors::{
22 RecordDescriptor, RecordDescriptorIter, RecordDescriptors, decode_record_descriptors,
23};
24pub use indexed::{EncodedParts, IndexedRecords};
25pub use owned::{OwnedRecord, OwnedRecordIter, OwnedRecords};
26pub use payload::RecordPayload;
27pub(super) use records::validate_record_entries;
28pub use records::{DecodedRecord, Parts, RecordIter, Records, decode_records};
29
30#[derive(Debug, Clone, Copy, PartialEq, Eq)]
32pub struct Authority {
33 pub group_id: GroupId,
35 pub config_epoch: u64,
37 pub view: u64,
39}
40
41#[derive(Debug, Clone, Copy, PartialEq, Eq)]
43pub struct DataLimits {
44 pub envelope: EnvelopeLimits,
46 pub max_records: usize,
48 pub max_parts: usize,
50 pub max_record_bytes: usize,
52}
53
54impl DataLimits {
55 #[must_use]
57 pub fn intersection(self, other: Self) -> Self {
58 Self {
59 envelope: EnvelopeLimits {
60 max_metadata_bytes: self
61 .envelope
62 .max_metadata_bytes
63 .min(other.envelope.max_metadata_bytes),
64 max_payload_bytes: self
65 .envelope
66 .max_payload_bytes
67 .min(other.envelope.max_payload_bytes),
68 },
69 max_records: self.max_records.min(other.max_records),
70 max_parts: self.max_parts.min(other.max_parts),
71 max_record_bytes: self.max_record_bytes.min(other.max_record_bytes),
72 }
73 }
74}
75
76#[cfg(test)]
77mod record_limit_tests {
78 use super::*;
79
80 #[test]
81 fn record_ceiling_is_independent_of_packet_capacity() {
82 let limits = DataLimits {
83 envelope: EnvelopeLimits {
84 max_payload_bytes: 4 * 1024 * 1024,
85 ..EnvelopeLimits::default()
86 },
87 ..DataLimits::default()
88 };
89 let payload = vec![7; 600 * 1024];
90 let parts = [payload.as_slice()];
91 let records = [Record {
92 encoding: crate::data::Encoding::Raw,
93 message_id: MessageId::new(),
94 parts: &parts,
95 }; 2];
96 assert!(records_size(&records, 0, limits).is_ok());
97 let large_parts = [payload.as_slice(), payload.as_slice()];
98 let large = [Record {
99 encoding: crate::data::Encoding::Raw,
100 message_id: MessageId::new(),
101 parts: &large_parts,
102 }];
103 assert!(matches!(
104 records_size(&large, 0, limits),
105 Err(CodecError::Limit)
106 ));
107 let mut metadata = Vec::new();
108 let mut wire_payload = Vec::new();
109 encode_records(&large, &mut metadata, Some(&mut wire_payload));
110 assert!(matches!(
111 decode_records(&metadata, &wire_payload, 0, limits),
112 Err(CodecError::Limit)
113 ));
114 }
115}
116
117impl Default for DataLimits {
118 fn default() -> Self {
119 Self {
120 envelope: EnvelopeLimits {
121 max_payload_bytes: 16 * 1024 * 1024,
122 ..EnvelopeLimits::default()
123 },
124 max_records: 1000,
125 max_parts: 4000,
126 max_record_bytes: 1024 * 1024,
127 }
128 }
129}
130
131#[derive(Debug, Clone, Copy)]
133pub struct Record<'a> {
134 pub encoding: Encoding,
136 pub message_id: MessageId,
138 pub parts: &'a [&'a [u8]],
140}
141
142pub(super) fn records_size(
143 records: &[Record<'_>],
144 first: u64,
145 limits: DataLimits,
146) -> Result<(usize, usize), CodecError> {
147 record_sizes(
148 records
149 .iter()
150 .map(|r| (r.message_id, r.encoding, r.parts.iter().copied())),
151 first,
152 limits,
153 )
154}
155
156pub(super) fn record_sizes<'a, I, P>(
157 records: I,
158 first: u64,
159 limits: DataLimits,
160) -> Result<(usize, usize), CodecError>
161where
162 I: ExactSizeIterator<Item = (MessageId, Encoding, P)>,
163 P: Iterator<Item = &'a [u8]>,
164{
165 record_count(records.len(), first, limits)?;
166 let mut metadata = 4_usize;
167 let mut payload = 0_usize;
168 let mut parts = 0_usize;
169 for (_, encoding, record) in records {
170 metadata = add(metadata, encoding.head_bytes())?;
171 let mut record_parts = 0_usize;
172 let mut record_bytes = 0_usize;
173 for part in record {
174 if encoding
175 .frame_offset(part, limits.max_parts, limits.max_record_bytes)
176 .is_none()
177 {
178 return Err(CodecError::Length);
179 }
180 parts = add(parts, 1)?;
181 record_parts = add(record_parts, 1)?;
182 count(parts)?;
183 if parts > limits.max_parts {
184 return Err(CodecError::Limit);
185 }
186 metadata = add(metadata, 4)?;
187 count(part.len())?;
188 payload = add(payload, part.len())?;
189 record_bytes = add(record_bytes, part.len())?;
190 if record_bytes > limits.max_record_bytes {
191 return Err(CodecError::Limit);
192 }
193 }
194 if !encoding.validate(record_parts, limits.max_parts, limits.max_record_bytes) {
195 return Err(CodecError::Parts);
196 }
197 }
198 Ok((metadata, payload))
199}
200
201pub(super) fn encode_records(
203 records: &[Record<'_>],
204 metadata: &mut Vec<u8>,
205 payload: Option<&mut Vec<u8>>,
206) {
207 write_record_frames(
208 records
209 .iter()
210 .map(|r| (r.message_id, r.encoding, r.parts.iter().copied())),
211 metadata,
212 payload,
213 );
214}
215
216pub(super) fn write_records<'a, I, P>(records: I, metadata: &mut Vec<u8>, payload: &mut Vec<u8>)
218where
219 I: ExactSizeIterator<Item = (MessageId, Encoding, P)>,
220 P: Iterator<Item = &'a [u8]>,
221{
222 write_record_frames(records, metadata, Some(payload));
223}
224
225fn write_record_frames<'a, I, P>(records: I, metadata: &mut Vec<u8>, payload: Option<&mut Vec<u8>>)
226where
227 I: ExactSizeIterator<Item = (MessageId, Encoding, P)>,
228 P: Iterator<Item = &'a [u8]>,
229{
230 metadata.extend_from_slice(&(records.len() as u32).to_be_bytes());
231 write_record_entries(records, metadata, payload);
232}
233
234#[expect(
236 clippy::arithmetic_side_effects,
237 reason = "callers validated each part count against max_parts, a u32"
238)]
239pub(super) fn write_record_entries<'a, I, P>(
240 records: I,
241 metadata: &mut Vec<u8>,
242 mut payload: Option<&mut Vec<u8>>,
243) where
244 I: Iterator<Item = (MessageId, Encoding, P)>,
245 P: Iterator<Item = &'a [u8]>,
246{
247 for (id, encoding, parts) in records {
248 metadata.extend_from_slice(id.as_bytes());
249 let count_at = metadata.len();
250 metadata.extend_from_slice(&[0; 4]);
251 if let Encoding::Lz4 { decoded_bytes } = encoding {
252 metadata.extend_from_slice(&decoded_bytes.to_be_bytes());
253 }
254 let mut count = 0_u32;
255 for part in parts {
256 count += 1;
257 metadata.extend_from_slice(&(part.len() as u32).to_be_bytes());
258 if let Some(payload) = payload.as_mut() {
259 payload.extend_from_slice(part);
260 }
261 }
262 let _ = crate::layout::patch(metadata, count_at, (count | encoding.tag()).to_be_bytes());
264 }
265}
266
267pub(super) fn command(
268 envelope: Envelope,
269 opcode: Opcode,
270 response: bool,
271) -> Result<(), CodecError> {
272 if envelope.opcode != opcode || envelope.response != response || envelope.request_id.is_none() {
273 return Err(CodecError::Command);
274 }
275 Ok(())
276}
277
278pub(super) fn record_count(
279 records: usize,
280 first: u64,
281 limits: DataLimits,
282) -> Result<(), CodecError> {
283 if records == 0 || records > limits.max_records {
284 return Err(CodecError::Limit);
285 }
286 first
287 .checked_add(u64::from(count(records)?))
288 .ok_or(CodecError::Length)?;
289 Ok(())
290}
291
292pub(super) fn add(left: usize, right: usize) -> Result<usize, CodecError> {
293 left.checked_add(right).ok_or(CodecError::Length)
294}
295pub(super) fn sum(terms: &[usize]) -> Result<usize, CodecError> {
297 terms.iter().try_fold(0, |total, &term| add(total, term))
298}
299pub(super) fn count(value: usize) -> Result<u32, CodecError> {
300 u32::try_from(value).map_err(|_| CodecError::Length)
301}
302pub(super) fn capacity(buffer: &Vec<u8>, length: usize) -> Result<(), CodecError> {
303 if buffer.capacity() < length {
304 return Err(CodecError::Capacity);
305 }
306 Ok(())
307}
308
309#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
311pub enum CodecError {
312 #[error(transparent)]
314 Envelope(#[from] EnvelopeError),
315 #[error("invalid data command envelope")]
317 Command,
318 #[error("invalid data identity or epoch")]
320 Identity,
321 #[error("unknown completion policy")]
323 Policy,
324 #[error("data command exceeds record or part limits")]
326 Limit,
327 #[error("record has no payload parts")]
329 Parts,
330 #[error("invalid data command length or sequence range")]
332 Length,
333 #[error("data output buffer capacity exhausted")]
335 Capacity,
336 #[error("invalid data confirmation position")]
338 Position,
339 #[error("unsupported reader profile")]
341 Profile,
342}
343
344impl From<LayoutError> for CodecError {
345 fn from(_: LayoutError) -> Self {
346 Self::Length
347 }
348}
349
350fn descriptor(cursor: &mut Reader<'_>) -> Result<(usize, Encoding), CodecError> {
351 let tagged = cursor.u32()?;
352 let encoding = match tagged >> 24 {
353 0 => Encoding::Raw,
354 1 => Encoding::Lz4 {
355 decoded_bytes: cursor.u32()?,
356 },
357 _ => return Err(CodecError::Profile),
358 };
359 Ok(((tagged & 0x00ff_ffff) as usize, encoding))
360}