1#[cfg(any(
19 target_os = "linux",
20 target_os = "macos",
21 target_os = "ios",
22 target_os = "tvos",
23 target_os = "watchos",
24 target_os = "freebsd",
25 target_os = "dragonfly",
26))]
27use std::io;
28use std::mem;
29
30use bytes::{Buf, BufMut, Bytes};
31use krabka_compression::{CompressionType, RecordDecompressionPolicy};
32
33#[cfg(any(
34 target_os = "linux",
35 target_os = "macos",
36 target_os = "ios",
37 target_os = "tvos",
38 target_os = "watchos",
39 target_os = "freebsd",
40 target_os = "dragonfly",
41))]
42use crate::records::FileRegion;
43use crate::{
44 primitives::{fixed::put_i32, varint::put_uvarint},
45 records::{RecordsError, borrowed::RecordBatch as RecordBatchBorrowed, owned::RecordBatch},
46};
47
48#[derive(Debug, Clone, PartialEq, Eq)]
50pub enum RecordsPayload {
51 V2(Vec<RecordBatch>),
53 Raw(Bytes),
56 Legacy(Bytes),
59 #[cfg(any(
68 target_os = "linux",
69 target_os = "macos",
70 target_os = "ios",
71 target_os = "tvos",
72 target_os = "watchos",
73 target_os = "freebsd",
74 target_os = "dragonfly",
75 ))]
76 FileRegions(Vec<FileRegion>),
77}
78
79impl RecordsPayload {
80 pub fn encode_length_prefixed<B: BufMut>(
93 &self,
94 buf: &mut B,
95 version: i16,
96 flexible: bool,
97 ) -> Result<(), crate::ProtocolError> {
98 let has_compressed_batch = matches!(
99 self,
100 Self::V2(batches)
101 if batches
102 .iter()
103 .any(|batch| batch.attributes.compression() != CompressionType::None)
104 );
105
106 if has_compressed_batch {
107 let mut encoded = bytes::BytesMut::new();
108 <Self as crate::Encode>::encode(self, &mut encoded, version)?;
109 put_payload_length(buf, encoded.len(), flexible)?;
110 buf.put_slice(&encoded);
111 } else {
112 put_payload_length(buf, self.payload_len(), flexible)?;
113 <Self as crate::Encode>::encode(self, buf, version)?;
114 }
115 Ok(())
116 }
117
118 pub fn from_bytes(bytes: Bytes) -> Result<Self, RecordsError> {
125 Self::from_bytes_with_policy(bytes, RecordDecompressionPolicy::default())
126 }
127
128 pub fn from_bytes_with_policy(
136 bytes: Bytes,
137 policy: RecordDecompressionPolicy,
138 ) -> Result<Self, RecordsError> {
139 if looks_like_v2(&bytes) {
140 let mut cur: &[u8] = &bytes;
141 let mut batches = Vec::new();
142 while !cur.is_empty() {
143 batches.push(RecordBatch::decode_with_policy(&mut cur, policy)?);
144 }
145 Ok(Self::V2(batches))
146 } else {
147 Ok(Self::Legacy(bytes))
148 }
149 }
150
151 #[must_use]
153 pub fn payload_len(&self) -> usize {
154 match self {
155 Self::V2(batches) => batches.iter().map(RecordBatch::encoded_batch_len).sum(),
156 Self::Raw(b) | Self::Legacy(b) => b.len(),
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(regions) => regions.iter().map(|r| r.len).sum(),
167 }
168 }
169
170 pub fn encode_to<B: BufMut>(&self, buf: &mut B) -> Result<(), RecordsError> {
182 match self {
183 Self::V2(batches) => {
184 for b in batches {
185 b.encode_batch(buf)?;
186 }
187 Ok(())
188 }
189 Self::Raw(b) | Self::Legacy(b) => {
190 buf.put_slice(b);
191 Ok(())
192 }
193 #[cfg(any(
194 target_os = "linux",
195 target_os = "macos",
196 target_os = "ios",
197 target_os = "tvos",
198 target_os = "watchos",
199 target_os = "freebsd",
200 target_os = "dragonfly",
201 ))]
202 Self::FileRegions(regions) => {
203 use std::os::unix::fs::FileExt;
204 let mut scratch = vec![0u8; 0];
205 for region in regions {
206 scratch.resize(region.len, 0);
207 let mut filled = 0usize;
208 let mut offset = region.offset;
209 while filled < region.len {
210 let Some(unfilled) = scratch.get_mut(filled..) else {
211 return Err(RecordsError::RecordParse(
212 "FileRegion read overran its buffer".into(),
213 ));
214 };
215 match region.file.read_at(unfilled, offset) {
216 Ok(0) => {
217 return Err(RecordsError::RecordParse(
218 "FileRegion read hit EOF before len bytes".into(),
219 ));
220 }
221 Ok(n) => {
222 filled += n;
223 offset += n as u64;
224 }
225 Err(e) if e.kind() == io::ErrorKind::Interrupted => {}
226 Err(e) => {
227 return Err(RecordsError::RecordParse(format!(
228 "FileRegion read error: {e}"
229 )));
230 }
231 }
232 }
233 buf.put_slice(&scratch);
234 }
235 Ok(())
236 }
237 }
238 }
239
240 #[must_use]
246 pub fn as_v2(&self) -> Option<&[RecordBatch]> {
247 match self {
248 Self::V2(batches) => Some(batches),
249 #[cfg(any(
250 target_os = "linux",
251 target_os = "macos",
252 target_os = "ios",
253 target_os = "tvos",
254 target_os = "watchos",
255 target_os = "freebsd",
256 target_os = "dragonfly",
257 ))]
258 Self::FileRegions(_) => None,
259 Self::Raw(_) | Self::Legacy(_) => None,
260 }
261 }
262
263 #[must_use]
265 pub fn as_legacy(&self) -> Option<&Bytes> {
266 match self {
267 Self::Legacy(b) => Some(b),
268 #[cfg(any(
269 target_os = "linux",
270 target_os = "macos",
271 target_os = "ios",
272 target_os = "tvos",
273 target_os = "watchos",
274 target_os = "freebsd",
275 target_os = "dragonfly",
276 ))]
277 Self::FileRegions(_) => None,
278 Self::V2(_) | Self::Raw(_) => None,
279 }
280 }
281
282 pub fn from_fetch_bytes(bytes: Bytes) -> Result<Self, RecordsError> {
303 if !looks_like_v2(&bytes) {
304 return Ok(Self::Legacy(bytes));
305 }
306 let mut cur: &[u8] = &bytes;
307 let mut batches = Vec::new();
308 while !cur.is_empty() {
309 match RecordBatch::decode_batch(&mut cur) {
310 Ok(rb) => batches.push(rb),
311 Err(RecordsError::HeaderTooShort { .. } | RecordsError::BodyTooShort { .. }) => {
312 break;
313 }
314 Err(e) => return Err(e),
315 }
316 }
317 Ok(Self::V2(batches))
318 }
319
320 pub fn decode_lenient<B: Buf>(
329 buf: &mut B,
330 _version: i16,
331 ) -> Result<Self, crate::ProtocolError> {
332 let bytes = buf.copy_to_bytes(buf.remaining());
333 Self::from_fetch_bytes(bytes).map_err(Into::into)
334 }
335}
336
337fn put_payload_length<B: BufMut>(
338 buf: &mut B,
339 payload_len: usize,
340 flexible: bool,
341) -> Result<(), crate::ProtocolError> {
342 if flexible {
343 let prefixed_len = payload_len
344 .checked_add(1)
345 .and_then(|len| u32::try_from(len).ok())
346 .ok_or(crate::ProtocolError::InvalidValue(
347 "records payload length exceeds compact bytes range",
348 ))?;
349 put_uvarint(buf, prefixed_len);
350 } else {
351 let payload_len = i32::try_from(payload_len).map_err(|_| {
352 crate::ProtocolError::InvalidValue("records payload length exceeds bytes range")
353 })?;
354 put_i32(buf, payload_len);
355 }
356 Ok(())
357}
358
359impl From<RecordBatch> for RecordsPayload {
360 fn from(rb: RecordBatch) -> Self {
361 Self::V2(vec![rb])
362 }
363}
364
365impl From<Vec<RecordBatch>> for RecordsPayload {
366 fn from(v: Vec<RecordBatch>) -> Self {
367 Self::V2(v)
368 }
369}
370
371impl Default for RecordsPayload {
372 fn default() -> Self {
373 Self::V2(Vec::new())
374 }
375}
376
377impl crate::Encode for RecordsPayload {
378 fn encode<B: BufMut>(&self, buf: &mut B, _version: i16) -> Result<(), crate::ProtocolError> {
379 self.encode_to(buf).map_err(Into::into)
380 }
381
382 fn encoded_len(&self, _version: i16) -> usize {
383 self.payload_len()
384 }
385}
386
387impl crate::Decode<'_> for RecordsPayload {
388 fn decode<B: Buf>(buf: &mut B, _version: i16) -> Result<Self, crate::ProtocolError> {
389 let bytes = buf.copy_to_bytes(buf.remaining());
393 Self::from_bytes(bytes).map_err(Into::into)
394 }
395}
396
397#[derive(Debug, Clone, PartialEq, Eq)]
399pub enum RecordsPayloadBorrowed<'a> {
400 V2(Vec<RecordBatchBorrowed<'a>>),
401 Legacy(&'a [u8]),
402}
403
404impl<'a> RecordsPayloadBorrowed<'a> {
405 pub fn from_slice(bytes: &'a [u8]) -> Result<Self, RecordsError> {
408 if looks_like_v2(bytes) {
409 let mut cur: &'a [u8] = bytes;
410 let mut batches = Vec::new();
411 while !cur.is_empty() {
412 let rb = <RecordBatchBorrowed<'a> as crate::DecodeBorrow<'a>>::decode_borrow(
413 &mut cur, 0,
414 )
415 .map_err(|e| RecordsError::RecordParse(format!("borrowed v2 decode: {e}")))?;
416 batches.push(rb);
417 }
418 Ok(Self::V2(batches))
419 } else {
420 Ok(Self::Legacy(bytes))
421 }
422 }
423
424 #[must_use]
425 pub fn payload_len(&self) -> usize {
426 match self {
427 Self::V2(batches) => batches
428 .iter()
429 .map(|rb| crate::Encode::encoded_len(rb, 0))
430 .sum(),
431 Self::Legacy(b) => b.len(),
432 }
433 }
434
435 pub fn encode_to<B: BufMut>(&self, buf: &mut B) -> Result<(), RecordsError> {
438 match self {
439 Self::V2(batches) => {
440 for rb in batches {
441 crate::Encode::encode(rb, buf, 0).map_err(|e| {
442 RecordsError::RecordParse(format!("borrowed v2 encode: {e}"))
443 })?;
444 }
445 Ok(())
446 }
447 Self::Legacy(b) => {
448 buf.put_slice(b);
449 Ok(())
450 }
451 }
452 }
453
454 pub fn to_owned(&self) -> Result<RecordsPayload, RecordsError> {
458 match self {
459 Self::V2(batches) => {
460 let mut owned = Vec::with_capacity(batches.len());
461 for rb in batches {
462 owned.push(rb.to_owned()?);
463 }
464 Ok(RecordsPayload::V2(owned))
465 }
466 Self::Legacy(b) => Ok(RecordsPayload::Legacy(Bytes::copy_from_slice(b))),
467 }
468 }
469}
470
471impl Default for RecordsPayloadBorrowed<'_> {
472 fn default() -> Self {
473 Self::V2(Vec::new())
474 }
475}
476
477impl crate::Encode for RecordsPayloadBorrowed<'_> {
478 fn encode<B: BufMut>(&self, buf: &mut B, _version: i16) -> Result<(), crate::ProtocolError> {
479 self.encode_to(buf).map_err(Into::into)
480 }
481
482 fn encoded_len(&self, _version: i16) -> usize {
483 self.payload_len()
484 }
485}
486
487impl<'de> crate::DecodeBorrow<'de> for RecordsPayloadBorrowed<'de> {
488 fn decode_borrow(buf: &mut &'de [u8], _version: i16) -> Result<Self, crate::ProtocolError> {
489 let bytes = mem::take(buf);
492 Self::from_slice(bytes).map_err(Into::into)
493 }
494}
495
496#[inline]
507fn looks_like_v2(bytes: &[u8]) -> bool {
508 const MAGIC_OFFSET: usize = 16;
513 bytes.get(MAGIC_OFFSET) == Some(&2)
514}
515
516#[cfg(test)]
517mod tests {
518
519 use bytes::BytesMut;
520 use krabka_compression::{CompressionError, CompressionType, RecordDecompressionPolicy};
521 use krabka_units::{bytes, fraction};
522
523 use super::*;
524 use crate::{
525 primitives::string_bytes::{compact_bytes_len_from_size, put_bytes, put_compact_bytes},
526 records::{Record, RecordBatch},
527 };
528
529 fn sample_v2() -> RecordBatch {
530 RecordBatch {
531 base_offset: 42,
532 records: vec![Record {
533 key: Some(Bytes::from_static(b"k")),
534 value: Some(Bytes::from_static(b"v")),
535 ..Default::default()
536 }],
537 ..RecordBatch::default()
538 }
539 }
540
541 #[test]
542 fn from_bytes_dispatches_v2() {
543 let rb = sample_v2();
544 let mut buf = BytesMut::new();
545 rb.encode_batch(&mut buf).unwrap();
546 let p = RecordsPayload::from_bytes(buf.freeze()).unwrap();
547 match p {
548 RecordsPayload::V2(batches) => assert2::assert!(batches == vec![rb]),
549 _ => panic!("expected V2"),
550 }
551 }
552
553 #[test]
554 fn decompression_policy_limits_payload_decode() {
555 let mut rb = sample_v2();
556 rb.records[0].value = Some(Bytes::from(vec![b'x'; 4096]));
557 rb.attributes = rb.attributes.with_compression(CompressionType::Lz4);
558 let mut wire = BytesMut::new();
559 rb.encode_batch(&mut wire).unwrap();
560 let wire = wire.freeze();
561
562 RecordsPayload::from_bytes(wire.clone()).unwrap();
563
564 let policy = RecordDecompressionPolicy::new(fraction(1.0), bytes(1), bytes(32)).unwrap();
565 assert2::assert!(matches!(
566 RecordsPayload::from_bytes_with_policy(wire, policy),
567 Err(RecordsError::Compression(CompressionError::TooLarge {
568 limit: 32
569 }))
570 ));
571 }
572
573 #[test]
574 fn from_bytes_parses_all_batches() {
575 let mut b0 = sample_v2();
577 b0.base_offset = 0;
578 let mut b1 = sample_v2();
579 b1.base_offset = 1;
580 let mut buf = BytesMut::new();
581 b0.encode_batch(&mut buf).unwrap();
582 b1.encode_batch(&mut buf).unwrap();
583 let p = RecordsPayload::from_bytes(buf.freeze()).unwrap();
584 let batches = p.as_v2().expect("v2");
585 assert2::assert!(batches == &[b0, b1][..]);
586 }
587
588 #[test]
589 fn raw_passthrough_roundtrips() {
590 let mut b = sample_v2();
591 b.base_offset = 7;
592 let mut wire = BytesMut::new();
593 b.encode_batch(&mut wire).unwrap();
594 let wire = wire.freeze();
595 let p = RecordsPayload::Raw(wire.clone());
596 assert2::assert!(p.payload_len() == wire.len());
597 let mut out = BytesMut::new();
598 p.encode_to(&mut out).unwrap();
599 assert2::assert!(&out[..] == &wire[..]); assert2::assert!(p.as_v2().is_none()); }
602
603 #[test]
604 fn length_prefixed_raw_encodes_into_fixed_buffer() {
605 let payload = Bytes::from(vec![0x5a; 1024 * 1024]);
606 let records = RecordsPayload::Raw(payload.clone());
607 let mut output = vec![0_u8; 4 + payload.len()];
608 let mut destination = output.as_mut_slice();
609
610 records
611 .encode_length_prefixed(&mut destination, 0, false)
612 .unwrap();
613
614 assert2::assert!(destination.is_empty());
615 let payload_len = i32::try_from(payload.len()).unwrap();
616 assert2::assert!(&output[..4] == &payload_len.to_be_bytes());
617 assert2::assert!(&output[4..] == payload.as_ref());
618 }
619
620 #[test]
621 fn length_prefixed_legacy_encodes_compact_bytes_directly() {
622 let payload = legacy_bytes();
623 let records = RecordsPayload::Legacy(payload.clone());
624 let expected_len = compact_bytes_len_from_size(payload.len());
625 let mut output = vec![0_u8; expected_len];
626 let mut destination = output.as_mut_slice();
627
628 records
629 .encode_length_prefixed(&mut destination, 0, true)
630 .unwrap();
631
632 assert2::assert!(destination.is_empty());
633 let mut expected = BytesMut::new();
634 put_compact_bytes(&mut expected, &payload).unwrap();
635 assert2::assert!(output.as_slice() == expected.as_ref());
636 }
637
638 #[test]
639 fn length_prefixed_compressed_v2_uses_actual_wire_size() {
640 let mut batch = sample_v2();
641 batch.records[0].value = Some(Bytes::from(vec![b'x'; 4096]));
642 batch.attributes = batch.attributes.with_compression(CompressionType::Lz4);
643 let records = RecordsPayload::V2(vec![batch]);
644
645 let mut payload_wire = BytesMut::new();
646 <RecordsPayload as crate::Encode>::encode(&records, &mut payload_wire, 0).unwrap();
647 let mut expected = BytesMut::new();
648 put_bytes(&mut expected, &payload_wire).unwrap();
649 let mut actual = BytesMut::new();
650 records
651 .encode_length_prefixed(&mut actual, 0, false)
652 .unwrap();
653
654 assert2::assert!(actual == expected);
655 let prefix = i32::from_be_bytes(actual[..4].try_into().unwrap());
656 assert2::assert!(usize::try_from(prefix).unwrap() == payload_wire.len());
657 assert2::assert!(payload_wire.len() != records.payload_len());
658 }
659
660 #[test]
661 fn from_bytes_dispatches_legacy() {
662 let mut buf = vec![0u8; 17];
665 buf[16] = 1;
666 let p = RecordsPayload::from_bytes(Bytes::from(buf.clone())).unwrap();
667 match p {
668 RecordsPayload::Legacy(b) => assert2::assert!(&b[..] == &buf[..]),
669 _ => panic!("expected Legacy"),
670 }
671 }
672
673 #[test]
674 fn roundtrip_v2() {
675 let p: RecordsPayload = sample_v2().into();
676 let mut buf = BytesMut::new();
677 p.encode_to(&mut buf).unwrap();
678 let back = RecordsPayload::from_bytes(buf.freeze()).unwrap();
679 assert2::assert!(p == back);
680 assert2::assert!(p.payload_len() == back.payload_len());
681 }
682
683 #[test]
684 fn encode_decode_via_traits() {
685 let p: RecordsPayload = sample_v2().into();
686 let mut buf = BytesMut::new();
687 <RecordsPayload as crate::Encode>::encode(&p, &mut buf, 0).unwrap();
688 let mut cur: &[u8] = &buf;
689 let back = <RecordsPayload as crate::Decode>::decode(&mut cur, 0).unwrap();
690 assert2::assert!(p == back);
691 }
692
693 #[test]
694 fn borrowed_dispatches() {
695 let rb = sample_v2();
696 let mut buf = BytesMut::new();
697 rb.encode_batch(&mut buf).unwrap();
698 let frozen = buf.freeze();
699 let p = RecordsPayloadBorrowed::from_slice(&frozen).unwrap();
700 assert2::assert!(matches!(p, RecordsPayloadBorrowed::V2(_)));
701 let owned = p.to_owned().unwrap();
702 match owned {
703 RecordsPayload::V2(batches) => assert2::assert!(batches[0].base_offset == 42),
704 _ => panic!("expected V2"),
705 }
706 }
707
708 #[test]
709 fn from_record_batch() {
710 let rb = sample_v2();
711 let p: RecordsPayload = rb.clone().into();
712 assert2::assert!(p.as_v2() == Some(&[rb][..]));
713 assert2::assert!(p.as_legacy().is_none());
714 }
715
716 fn legacy_bytes() -> Bytes {
717 let mut buf = vec![0u8; 24];
718 buf[16] = 1;
719 for (i, b) in (b'a'..=b'h').enumerate() {
720 buf[17 + i % 7] = b;
721 }
722 Bytes::from(buf)
723 }
724
725 #[test]
726 fn legacy_payload_len_and_encode_owned() {
727 let bytes = legacy_bytes();
728 let p = RecordsPayload::from_bytes(bytes.clone()).unwrap();
729 assert2::assert!(p == RecordsPayload::Legacy(bytes.clone()));
730 assert2::assert!(p.payload_len() == bytes.len());
731
732 let mut out = BytesMut::new();
733 p.encode_to(&mut out).unwrap();
734 assert2::assert!(&out[..] == &bytes[..]);
735 }
736
737 #[test]
738 fn legacy_roundtrip_via_traits() {
739 let bytes = legacy_bytes();
740 let p = RecordsPayload::from_bytes(bytes.clone()).unwrap();
741 let mut buf = BytesMut::new();
742 <RecordsPayload as crate::Encode>::encode(&p, &mut buf, 0).unwrap();
743 assert2::assert!(<RecordsPayload as crate::Encode>::encoded_len(&p, 0) == bytes.len());
744 let mut cur: &[u8] = &buf;
745 let back = <RecordsPayload as crate::Decode>::decode(&mut cur, 0).unwrap();
746 assert2::assert!(matches!(back, RecordsPayload::Legacy(_)));
747 assert2::assert!(back.as_legacy().unwrap() == &bytes);
748 }
749
750 #[test]
751 fn owned_default_is_empty_v2() {
752 let p = RecordsPayload::default();
753 assert2::assert!(matches!(p, RecordsPayload::V2(ref v) if v.is_empty()));
754 }
755
756 #[test]
757 fn looks_like_v2_rejects_short_buffer() {
758 let short = Bytes::from_static(&[0u8; 10]);
760 let p = RecordsPayload::from_bytes(short.clone()).unwrap();
761 assert2::assert!(p.as_legacy() == Some(&short));
762 }
763
764 #[test]
765 fn borrowed_legacy_roundtrip() {
766 let bytes = legacy_bytes();
767 let p = RecordsPayloadBorrowed::from_slice(&bytes).unwrap();
768 assert2::assert!(matches!(p, RecordsPayloadBorrowed::Legacy(_)));
769 assert2::assert!(p.payload_len() == bytes.len());
770
771 let mut out = BytesMut::new();
772 p.encode_to(&mut out).unwrap();
773 assert2::assert!(&out[..] == &bytes[..]);
774
775 let owned = p.to_owned().unwrap();
776 match owned {
777 RecordsPayload::Legacy(b) => assert2::assert!(&b[..] == &bytes[..]),
778 _ => panic!("expected Legacy"),
779 }
780 }
781
782 #[test]
783 fn borrowed_v2_payload_len_and_encode() {
784 let rb = sample_v2();
785 let mut buf = BytesMut::new();
786 rb.encode_batch(&mut buf).unwrap();
787 let frozen = buf.freeze();
788 let p = RecordsPayloadBorrowed::from_slice(&frozen).unwrap();
789 assert2::assert!(p.payload_len() == frozen.len());
790
791 let mut out = BytesMut::new();
792 p.encode_to(&mut out).unwrap();
793 assert2::assert!(&out[..] == &frozen[..]);
794 }
795
796 #[test]
797 fn borrowed_encode_decode_via_traits() {
798 let rb = sample_v2();
799 let mut buf = BytesMut::new();
800 rb.encode_batch(&mut buf).unwrap();
801 let frozen = buf.freeze();
802
803 let p = RecordsPayloadBorrowed::from_slice(&frozen).unwrap();
804 let mut out = BytesMut::new();
805 <RecordsPayloadBorrowed as crate::Encode>::encode(&p, &mut out, 0).unwrap();
806 assert2::assert!(
807 <RecordsPayloadBorrowed as crate::Encode>::encoded_len(&p, 0) == frozen.len()
808 );
809
810 let mut cur: &[u8] = &out;
811 let back =
812 <RecordsPayloadBorrowed as crate::DecodeBorrow>::decode_borrow(&mut cur, 0).unwrap();
813 assert2::assert!(matches!(back, RecordsPayloadBorrowed::V2(_)));
814 }
815
816 #[test]
817 fn borrowed_default_is_empty_v2() {
818 let p = RecordsPayloadBorrowed::default();
819 assert2::assert!(matches!(p, RecordsPayloadBorrowed::V2(ref v) if v.is_empty()));
820 }
821
822 #[test]
823 fn from_fetch_bytes_drops_incomplete_trailing_batch() {
824 let mut b0 = sample_v2();
829 b0.base_offset = 0;
830 let mut b1 = sample_v2();
831 b1.base_offset = 1;
832 let mut buf = BytesMut::new();
833 b0.encode_batch(&mut buf).unwrap();
834 b1.encode_batch(&mut buf).unwrap();
835 buf.extend_from_slice(&[0u8; 7]); let p = RecordsPayload::from_fetch_bytes(buf.freeze()).unwrap();
837 let batches = p.as_v2().expect("v2");
838 assert2::assert!(batches == &[b0, b1][..]);
839 }
840
841 #[test]
842 fn from_fetch_bytes_still_errors_on_corrupt_batch() {
843 let rb = sample_v2();
846 let mut buf = BytesMut::new();
847 rb.encode_batch(&mut buf).unwrap();
848 let mut bytes = buf.to_vec();
849 bytes[61] ^= 0xFF;
851 let err = RecordsPayload::from_fetch_bytes(Bytes::from(bytes)).unwrap_err();
852 assert2::assert!(matches!(err, RecordsError::CrcMismatch { .. }));
853 }
854
855 #[test]
856 fn from_fetch_bytes_legacy_passes_through() {
857 let bytes = legacy_bytes();
858 let p = RecordsPayload::from_fetch_bytes(bytes.clone()).unwrap();
859 assert2::assert!(p.as_legacy() == Some(&bytes));
860 }
861
862 #[test]
863 fn from_fetch_bytes_empty_is_empty_v2() {
864 let p = RecordsPayload::from_fetch_bytes(Bytes::new()).unwrap();
867 assert2::assert!(matches!(p, RecordsPayload::Legacy(ref b) if b.is_empty()));
868 }
869}