1use bytes::{Buf, BufMut, Bytes};
20
21use crate::records::RecordsError;
22use crate::records::borrowed::RecordBatch as RecordBatchBorrowed;
23use crate::records::owned::RecordBatch;
24
25#[derive(Debug, Clone, PartialEq, Eq)]
27pub enum RecordsPayload {
28 V2(Vec<RecordBatch>),
30 Raw(Bytes),
33 Legacy(Bytes),
36 #[cfg(any(
44 target_os = "linux",
45 target_os = "macos",
46 target_os = "ios",
47 target_os = "tvos",
48 target_os = "watchos",
49 target_os = "freebsd",
50 target_os = "dragonfly",
51 ))]
52 FileRegions(Vec<crate::records::FileRegion>),
53}
54
55impl RecordsPayload {
56 pub fn from_bytes(bytes: Bytes) -> Result<Self, RecordsError> {
59 if looks_like_v2(&bytes) {
60 let mut cur: &[u8] = &bytes;
61 let mut batches = Vec::new();
62 while !cur.is_empty() {
63 batches.push(RecordBatch::decode(&mut cur)?);
64 }
65 Ok(Self::V2(batches))
66 } else {
67 Ok(Self::Legacy(bytes))
68 }
69 }
70
71 #[must_use]
73 pub fn payload_len(&self) -> usize {
74 match self {
75 Self::V2(batches) => batches.iter().map(RecordBatch::encoded_len).sum(),
76 Self::Raw(b) | Self::Legacy(b) => b.len(),
77 #[cfg(any(
78 target_os = "linux",
79 target_os = "macos",
80 target_os = "ios",
81 target_os = "tvos",
82 target_os = "watchos",
83 target_os = "freebsd",
84 target_os = "dragonfly",
85 ))]
86 Self::FileRegions(regions) => regions.iter().map(|r| r.len).sum(),
87 }
88 }
89
90 pub fn encode_to<B: BufMut>(&self, buf: &mut B) -> Result<(), RecordsError> {
97 match self {
98 Self::V2(batches) => {
99 for b in batches {
100 b.encode(buf)?;
101 }
102 Ok(())
103 }
104 Self::Raw(b) | Self::Legacy(b) => {
105 buf.put_slice(b);
106 Ok(())
107 }
108 #[cfg(any(
109 target_os = "linux",
110 target_os = "macos",
111 target_os = "ios",
112 target_os = "tvos",
113 target_os = "watchos",
114 target_os = "freebsd",
115 target_os = "dragonfly",
116 ))]
117 Self::FileRegions(regions) => {
118 use std::os::unix::fs::FileExt;
119 let mut scratch = vec![0u8; 0];
120 for region in regions {
121 scratch.resize(region.len, 0);
122 let mut filled = 0usize;
123 let mut offset = region.offset;
124 while filled < region.len {
125 match region.file.read_at(&mut scratch[filled..], offset) {
126 Ok(0) => {
127 return Err(RecordsError::RecordParse(
128 "FileRegion read hit EOF before len bytes".into(),
129 ));
130 }
131 Ok(n) => {
132 filled += n;
133 offset += n as u64;
134 }
135 Err(e) if e.kind() == std::io::ErrorKind::Interrupted => {}
136 Err(e) => {
137 return Err(RecordsError::RecordParse(format!(
138 "FileRegion read error: {e}"
139 )));
140 }
141 }
142 }
143 buf.put_slice(&scratch);
144 }
145 Ok(())
146 }
147 }
148 }
149
150 #[must_use]
154 pub fn as_v2(&self) -> Option<&[RecordBatch]> {
155 match self {
156 Self::V2(batches) => Some(batches),
157 #[cfg(any(
158 target_os = "linux",
159 target_os = "macos",
160 target_os = "ios",
161 target_os = "tvos",
162 target_os = "watchos",
163 target_os = "freebsd",
164 target_os = "dragonfly",
165 ))]
166 Self::FileRegions(_) => None,
167 Self::Raw(_) | Self::Legacy(_) => None,
168 }
169 }
170
171 #[must_use]
173 pub fn as_legacy(&self) -> Option<&Bytes> {
174 match self {
175 Self::Legacy(b) => Some(b),
176 #[cfg(any(
177 target_os = "linux",
178 target_os = "macos",
179 target_os = "ios",
180 target_os = "tvos",
181 target_os = "watchos",
182 target_os = "freebsd",
183 target_os = "dragonfly",
184 ))]
185 Self::FileRegions(_) => None,
186 Self::V2(_) | Self::Raw(_) => None,
187 }
188 }
189
190 pub fn from_fetch_bytes(bytes: Bytes) -> Result<Self, RecordsError> {
205 if !looks_like_v2(&bytes) {
206 return Ok(Self::Legacy(bytes));
207 }
208 let mut cur: &[u8] = &bytes;
209 let mut batches = Vec::new();
210 while !cur.is_empty() {
211 match RecordBatch::decode(&mut cur) {
212 Ok(rb) => batches.push(rb),
213 Err(RecordsError::HeaderTooShort { .. } | RecordsError::BodyTooShort { .. }) => {
214 break;
215 }
216 Err(e) => return Err(e),
217 }
218 }
219 Ok(Self::V2(batches))
220 }
221
222 pub fn decode_lenient<B: Buf>(
227 buf: &mut B,
228 _version: i16,
229 ) -> Result<Self, crate::ProtocolError> {
230 let bytes = buf.copy_to_bytes(buf.remaining());
231 Self::from_fetch_bytes(bytes).map_err(Into::into)
232 }
233}
234
235impl From<RecordBatch> for RecordsPayload {
236 fn from(rb: RecordBatch) -> Self {
237 Self::V2(vec![rb])
238 }
239}
240
241impl From<Vec<RecordBatch>> for RecordsPayload {
242 fn from(v: Vec<RecordBatch>) -> Self {
243 Self::V2(v)
244 }
245}
246
247impl Default for RecordsPayload {
248 fn default() -> Self {
249 Self::V2(Vec::new())
250 }
251}
252
253impl crate::Encode for RecordsPayload {
254 fn encode<B: BufMut>(&self, buf: &mut B, _version: i16) -> Result<(), crate::ProtocolError> {
255 self.encode_to(buf).map_err(Into::into)
256 }
257
258 fn encoded_len(&self, _version: i16) -> usize {
259 self.payload_len()
260 }
261}
262
263impl crate::Decode<'_> for RecordsPayload {
264 fn decode<B: Buf>(buf: &mut B, _version: i16) -> Result<Self, crate::ProtocolError> {
265 let bytes = buf.copy_to_bytes(buf.remaining());
269 Self::from_bytes(bytes).map_err(Into::into)
270 }
271}
272
273#[derive(Debug, Clone, PartialEq, Eq)]
275pub enum RecordsPayloadBorrowed<'a> {
276 V2(Vec<RecordBatchBorrowed<'a>>),
277 Legacy(&'a [u8]),
278}
279
280impl<'a> RecordsPayloadBorrowed<'a> {
281 pub fn from_slice(bytes: &'a [u8]) -> Result<Self, RecordsError> {
282 if looks_like_v2(bytes) {
283 let mut cur: &'a [u8] = bytes;
284 let mut batches = Vec::new();
285 while !cur.is_empty() {
286 let rb = <RecordBatchBorrowed<'a> as crate::DecodeBorrow<'a>>::decode_borrow(
287 &mut cur, 0,
288 )
289 .map_err(|e| RecordsError::RecordParse(format!("borrowed v2 decode: {e}")))?;
290 batches.push(rb);
291 }
292 Ok(Self::V2(batches))
293 } else {
294 Ok(Self::Legacy(bytes))
295 }
296 }
297
298 #[must_use]
299 pub fn payload_len(&self) -> usize {
300 match self {
301 Self::V2(batches) => batches
302 .iter()
303 .map(|rb| crate::Encode::encoded_len(rb, 0))
304 .sum(),
305 Self::Legacy(b) => b.len(),
306 }
307 }
308
309 pub fn encode_to<B: BufMut>(&self, buf: &mut B) -> Result<(), RecordsError> {
310 match self {
311 Self::V2(batches) => {
312 for rb in batches {
313 crate::Encode::encode(rb, buf, 0).map_err(|e| {
314 RecordsError::RecordParse(format!("borrowed v2 encode: {e}"))
315 })?;
316 }
317 Ok(())
318 }
319 Self::Legacy(b) => {
320 buf.put_slice(b);
321 Ok(())
322 }
323 }
324 }
325
326 pub fn to_owned(&self) -> Result<RecordsPayload, RecordsError> {
328 match self {
329 Self::V2(batches) => {
330 let mut owned = Vec::with_capacity(batches.len());
331 for rb in batches {
332 owned.push(rb.to_owned()?);
333 }
334 Ok(RecordsPayload::V2(owned))
335 }
336 Self::Legacy(b) => Ok(RecordsPayload::Legacy(Bytes::copy_from_slice(b))),
337 }
338 }
339}
340
341impl Default for RecordsPayloadBorrowed<'_> {
342 fn default() -> Self {
343 Self::V2(Vec::new())
344 }
345}
346
347impl crate::Encode for RecordsPayloadBorrowed<'_> {
348 fn encode<B: BufMut>(&self, buf: &mut B, _version: i16) -> Result<(), crate::ProtocolError> {
349 self.encode_to(buf).map_err(Into::into)
350 }
351
352 fn encoded_len(&self, _version: i16) -> usize {
353 self.payload_len()
354 }
355}
356
357impl<'de> crate::DecodeBorrow<'de> for RecordsPayloadBorrowed<'de> {
358 fn decode_borrow(buf: &mut &'de [u8], _version: i16) -> Result<Self, crate::ProtocolError> {
359 let bytes = std::mem::take(buf);
362 Self::from_slice(bytes).map_err(Into::into)
363 }
364}
365
366#[inline]
375fn looks_like_v2(bytes: &[u8]) -> bool {
376 const MAGIC_OFFSET: usize = 16;
381 bytes.len() > MAGIC_OFFSET && bytes[MAGIC_OFFSET] == 2
382}
383
384#[cfg(test)]
385mod tests {
386 use super::*;
387 use crate::records::{Record, RecordBatch};
388 use assert2::assert;
389 use bytes::BytesMut;
390
391 fn sample_v2() -> RecordBatch {
392 RecordBatch {
393 base_offset: 42,
394 records: vec![Record {
395 key: Some(Bytes::from_static(b"k")),
396 value: Some(Bytes::from_static(b"v")),
397 ..Default::default()
398 }],
399 ..RecordBatch::default()
400 }
401 }
402
403 #[test]
404 fn from_bytes_dispatches_v2() {
405 let rb = sample_v2();
406 let mut buf = BytesMut::new();
407 rb.encode(&mut buf).unwrap();
408 let p = RecordsPayload::from_bytes(buf.freeze()).unwrap();
409 match p {
410 RecordsPayload::V2(batches) => assert!(batches == vec![rb]),
411 _ => panic!("expected V2"),
412 }
413 }
414
415 #[test]
416 fn from_bytes_parses_all_batches() {
417 let mut b0 = sample_v2();
419 b0.base_offset = 0;
420 let mut b1 = sample_v2();
421 b1.base_offset = 1;
422 let mut buf = BytesMut::new();
423 b0.encode(&mut buf).unwrap();
424 b1.encode(&mut buf).unwrap();
425 let p = RecordsPayload::from_bytes(buf.freeze()).unwrap();
426 let batches = p.as_v2().expect("v2");
427 assert!(batches.len() == 2);
428 assert!(batches[0].base_offset == 0);
429 assert!(batches[1].base_offset == 1);
430 }
431
432 #[test]
433 fn raw_passthrough_roundtrips() {
434 let mut b = sample_v2();
435 b.base_offset = 7;
436 let mut wire = BytesMut::new();
437 b.encode(&mut wire).unwrap();
438 let wire = wire.freeze();
439 let p = RecordsPayload::Raw(wire.clone());
440 assert!(p.payload_len() == wire.len());
441 let mut out = BytesMut::new();
442 p.encode_to(&mut out).unwrap();
443 assert!(&out[..] == &wire[..]); assert!(p.as_v2().is_none()); }
446
447 #[test]
448 fn from_bytes_dispatches_legacy() {
449 let mut buf = vec![0u8; 17];
452 buf[16] = 1;
453 let p = RecordsPayload::from_bytes(Bytes::from(buf.clone())).unwrap();
454 match p {
455 RecordsPayload::Legacy(b) => assert!(&b[..] == &buf[..]),
456 _ => panic!("expected Legacy"),
457 }
458 }
459
460 #[test]
461 fn roundtrip_v2() {
462 let p: RecordsPayload = sample_v2().into();
463 let mut buf = BytesMut::new();
464 p.encode_to(&mut buf).unwrap();
465 let back = RecordsPayload::from_bytes(buf.freeze()).unwrap();
466 assert!(p == back);
467 assert!(p.payload_len() == back.payload_len());
468 }
469
470 #[test]
471 fn encode_decode_via_traits() {
472 let p: RecordsPayload = sample_v2().into();
473 let mut buf = BytesMut::new();
474 <RecordsPayload as crate::Encode>::encode(&p, &mut buf, 0).unwrap();
475 let mut cur: &[u8] = &buf;
476 let back = <RecordsPayload as crate::Decode>::decode(&mut cur, 0).unwrap();
477 assert!(p == back);
478 }
479
480 #[test]
481 fn borrowed_dispatches() {
482 let rb = sample_v2();
483 let mut buf = BytesMut::new();
484 rb.encode(&mut buf).unwrap();
485 let frozen = buf.freeze();
486 let p = RecordsPayloadBorrowed::from_slice(&frozen).unwrap();
487 assert!(matches!(p, RecordsPayloadBorrowed::V2(_)));
488 let owned = p.to_owned().unwrap();
489 match owned {
490 RecordsPayload::V2(batches) => assert!(batches[0].base_offset == 42),
491 _ => panic!("expected V2"),
492 }
493 }
494
495 #[test]
496 fn from_record_batch() {
497 let rb = sample_v2();
498 let p: RecordsPayload = rb.clone().into();
499 assert!(p.as_v2() == Some(&[rb][..]));
500 assert!(p.as_legacy().is_none());
501 }
502
503 fn legacy_bytes() -> Bytes {
504 let mut buf = vec![0u8; 24];
505 buf[16] = 1;
506 for (i, b) in (b'a'..=b'h').enumerate() {
507 buf[17 + i % 7] = b;
508 }
509 Bytes::from(buf)
510 }
511
512 #[test]
513 fn legacy_payload_len_and_encode_owned() {
514 let bytes = legacy_bytes();
515 let p = RecordsPayload::from_bytes(bytes.clone()).unwrap();
516 assert!(p.payload_len() == bytes.len());
517 assert!(p.as_v2().is_none());
518 assert!(p.as_legacy() == Some(&bytes));
519
520 let mut out = BytesMut::new();
521 p.encode_to(&mut out).unwrap();
522 assert!(&out[..] == &bytes[..]);
523 }
524
525 #[test]
526 fn legacy_roundtrip_via_traits() {
527 let bytes = legacy_bytes();
528 let p = RecordsPayload::from_bytes(bytes.clone()).unwrap();
529 let mut buf = BytesMut::new();
530 <RecordsPayload as crate::Encode>::encode(&p, &mut buf, 0).unwrap();
531 assert!(<RecordsPayload as crate::Encode>::encoded_len(&p, 0) == bytes.len());
532 let mut cur: &[u8] = &buf;
533 let back = <RecordsPayload as crate::Decode>::decode(&mut cur, 0).unwrap();
534 assert!(matches!(back, RecordsPayload::Legacy(_)));
535 assert!(back.as_legacy().unwrap() == &bytes);
536 }
537
538 #[test]
539 fn owned_default_is_empty_v2() {
540 let p = RecordsPayload::default();
541 assert!(matches!(p, RecordsPayload::V2(ref v) if v.is_empty()));
542 }
543
544 #[test]
545 fn looks_like_v2_rejects_short_buffer() {
546 let short = Bytes::from_static(&[0u8; 10]);
548 let p = RecordsPayload::from_bytes(short.clone()).unwrap();
549 assert!(p.as_legacy() == Some(&short));
550 }
551
552 #[test]
553 fn borrowed_legacy_roundtrip() {
554 let bytes = legacy_bytes();
555 let p = RecordsPayloadBorrowed::from_slice(&bytes).unwrap();
556 assert!(matches!(p, RecordsPayloadBorrowed::Legacy(_)));
557 assert!(p.payload_len() == bytes.len());
558
559 let mut out = BytesMut::new();
560 p.encode_to(&mut out).unwrap();
561 assert!(&out[..] == &bytes[..]);
562
563 let owned = p.to_owned().unwrap();
564 match owned {
565 RecordsPayload::Legacy(b) => assert!(&b[..] == &bytes[..]),
566 _ => panic!("expected Legacy"),
567 }
568 }
569
570 #[test]
571 fn borrowed_v2_payload_len_and_encode() {
572 let rb = sample_v2();
573 let mut buf = BytesMut::new();
574 rb.encode(&mut buf).unwrap();
575 let frozen = buf.freeze();
576 let p = RecordsPayloadBorrowed::from_slice(&frozen).unwrap();
577 assert!(p.payload_len() == frozen.len());
578
579 let mut out = BytesMut::new();
580 p.encode_to(&mut out).unwrap();
581 assert!(&out[..] == &frozen[..]);
582 }
583
584 #[test]
585 fn borrowed_encode_decode_via_traits() {
586 let rb = sample_v2();
587 let mut buf = BytesMut::new();
588 rb.encode(&mut buf).unwrap();
589 let frozen = buf.freeze();
590
591 let p = RecordsPayloadBorrowed::from_slice(&frozen).unwrap();
592 let mut out = BytesMut::new();
593 <RecordsPayloadBorrowed as crate::Encode>::encode(&p, &mut out, 0).unwrap();
594 assert!(<RecordsPayloadBorrowed as crate::Encode>::encoded_len(&p, 0) == frozen.len());
595
596 let mut cur: &[u8] = &out;
597 let back =
598 <RecordsPayloadBorrowed as crate::DecodeBorrow>::decode_borrow(&mut cur, 0).unwrap();
599 assert!(matches!(back, RecordsPayloadBorrowed::V2(_)));
600 }
601
602 #[test]
603 fn borrowed_default_is_empty_v2() {
604 let p = RecordsPayloadBorrowed::default();
605 assert!(matches!(p, RecordsPayloadBorrowed::V2(ref v) if v.is_empty()));
606 }
607
608 #[test]
609 fn from_fetch_bytes_drops_incomplete_trailing_batch() {
610 let mut b0 = sample_v2();
615 b0.base_offset = 0;
616 let mut b1 = sample_v2();
617 b1.base_offset = 1;
618 let mut buf = BytesMut::new();
619 b0.encode(&mut buf).unwrap();
620 b1.encode(&mut buf).unwrap();
621 buf.extend_from_slice(&[0u8; 7]); let p = RecordsPayload::from_fetch_bytes(buf.freeze()).unwrap();
623 let batches = p.as_v2().expect("v2");
624 assert!(batches.len() == 2);
625 assert!(batches[0].base_offset == 0);
626 assert!(batches[1].base_offset == 1);
627 }
628
629 #[test]
630 fn from_fetch_bytes_still_errors_on_corrupt_batch() {
631 let rb = sample_v2();
634 let mut buf = BytesMut::new();
635 rb.encode(&mut buf).unwrap();
636 let mut bytes = buf.to_vec();
637 bytes[61] ^= 0xFF;
639 let err = RecordsPayload::from_fetch_bytes(Bytes::from(bytes)).unwrap_err();
640 assert!(matches!(err, RecordsError::CrcMismatch { .. }));
641 }
642
643 #[test]
644 fn from_fetch_bytes_legacy_passes_through() {
645 let bytes = legacy_bytes();
646 let p = RecordsPayload::from_fetch_bytes(bytes.clone()).unwrap();
647 assert!(p.as_legacy() == Some(&bytes));
648 }
649
650 #[test]
651 fn from_fetch_bytes_empty_is_empty_v2() {
652 let p = RecordsPayload::from_fetch_bytes(Bytes::new()).unwrap();
655 assert!(matches!(p, RecordsPayload::Legacy(ref b) if b.is_empty()));
656 }
657}