1use bytes::{Buf, BufMut, Bytes};
20
21use crate::records::{
22 RecordsError, borrowed::RecordBatch as RecordBatchBorrowed, owned::RecordBatch,
23};
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 assert2::assert;
387 use bytes::BytesMut;
388
389 use super::*;
390 use crate::records::{Record, RecordBatch};
391
392 fn sample_v2() -> RecordBatch {
393 RecordBatch {
394 base_offset: 42,
395 records: vec![Record {
396 key: Some(Bytes::from_static(b"k")),
397 value: Some(Bytes::from_static(b"v")),
398 ..Default::default()
399 }],
400 ..RecordBatch::default()
401 }
402 }
403
404 #[test]
405 fn from_bytes_dispatches_v2() {
406 let rb = sample_v2();
407 let mut buf = BytesMut::new();
408 rb.encode(&mut buf).unwrap();
409 let p = RecordsPayload::from_bytes(buf.freeze()).unwrap();
410 match p {
411 RecordsPayload::V2(batches) => assert!(batches == vec![rb]),
412 _ => panic!("expected V2"),
413 }
414 }
415
416 #[test]
417 fn from_bytes_parses_all_batches() {
418 let mut b0 = sample_v2();
420 b0.base_offset = 0;
421 let mut b1 = sample_v2();
422 b1.base_offset = 1;
423 let mut buf = BytesMut::new();
424 b0.encode(&mut buf).unwrap();
425 b1.encode(&mut buf).unwrap();
426 let p = RecordsPayload::from_bytes(buf.freeze()).unwrap();
427 let batches = p.as_v2().expect("v2");
428 assert!(batches == &[b0, b1][..]);
429 }
430
431 #[test]
432 fn raw_passthrough_roundtrips() {
433 let mut b = sample_v2();
434 b.base_offset = 7;
435 let mut wire = BytesMut::new();
436 b.encode(&mut wire).unwrap();
437 let wire = wire.freeze();
438 let p = RecordsPayload::Raw(wire.clone());
439 assert!(p.payload_len() == wire.len());
440 let mut out = BytesMut::new();
441 p.encode_to(&mut out).unwrap();
442 assert!(&out[..] == &wire[..]); assert!(p.as_v2().is_none()); }
445
446 #[test]
447 fn from_bytes_dispatches_legacy() {
448 let mut buf = vec![0u8; 17];
451 buf[16] = 1;
452 let p = RecordsPayload::from_bytes(Bytes::from(buf.clone())).unwrap();
453 match p {
454 RecordsPayload::Legacy(b) => assert!(&b[..] == &buf[..]),
455 _ => panic!("expected Legacy"),
456 }
457 }
458
459 #[test]
460 fn roundtrip_v2() {
461 let p: RecordsPayload = sample_v2().into();
462 let mut buf = BytesMut::new();
463 p.encode_to(&mut buf).unwrap();
464 let back = RecordsPayload::from_bytes(buf.freeze()).unwrap();
465 assert!(p == back);
466 assert!(p.payload_len() == back.payload_len());
467 }
468
469 #[test]
470 fn encode_decode_via_traits() {
471 let p: RecordsPayload = sample_v2().into();
472 let mut buf = BytesMut::new();
473 <RecordsPayload as crate::Encode>::encode(&p, &mut buf, 0).unwrap();
474 let mut cur: &[u8] = &buf;
475 let back = <RecordsPayload as crate::Decode>::decode(&mut cur, 0).unwrap();
476 assert!(p == back);
477 }
478
479 #[test]
480 fn borrowed_dispatches() {
481 let rb = sample_v2();
482 let mut buf = BytesMut::new();
483 rb.encode(&mut buf).unwrap();
484 let frozen = buf.freeze();
485 let p = RecordsPayloadBorrowed::from_slice(&frozen).unwrap();
486 assert!(matches!(p, RecordsPayloadBorrowed::V2(_)));
487 let owned = p.to_owned().unwrap();
488 match owned {
489 RecordsPayload::V2(batches) => assert!(batches[0].base_offset == 42),
490 _ => panic!("expected V2"),
491 }
492 }
493
494 #[test]
495 fn from_record_batch() {
496 let rb = sample_v2();
497 let p: RecordsPayload = rb.clone().into();
498 assert!(p.as_v2() == Some(&[rb][..]));
499 assert!(p.as_legacy().is_none());
500 }
501
502 fn legacy_bytes() -> Bytes {
503 let mut buf = vec![0u8; 24];
504 buf[16] = 1;
505 for (i, b) in (b'a'..=b'h').enumerate() {
506 buf[17 + i % 7] = b;
507 }
508 Bytes::from(buf)
509 }
510
511 #[test]
512 fn legacy_payload_len_and_encode_owned() {
513 let bytes = legacy_bytes();
514 let p = RecordsPayload::from_bytes(bytes.clone()).unwrap();
515 assert!(p == RecordsPayload::Legacy(bytes.clone()));
516 assert!(p.payload_len() == bytes.len());
517
518 let mut out = BytesMut::new();
519 p.encode_to(&mut out).unwrap();
520 assert!(&out[..] == &bytes[..]);
521 }
522
523 #[test]
524 fn legacy_roundtrip_via_traits() {
525 let bytes = legacy_bytes();
526 let p = RecordsPayload::from_bytes(bytes.clone()).unwrap();
527 let mut buf = BytesMut::new();
528 <RecordsPayload as crate::Encode>::encode(&p, &mut buf, 0).unwrap();
529 assert!(<RecordsPayload as crate::Encode>::encoded_len(&p, 0) == bytes.len());
530 let mut cur: &[u8] = &buf;
531 let back = <RecordsPayload as crate::Decode>::decode(&mut cur, 0).unwrap();
532 assert!(matches!(back, RecordsPayload::Legacy(_)));
533 assert!(back.as_legacy().unwrap() == &bytes);
534 }
535
536 #[test]
537 fn owned_default_is_empty_v2() {
538 let p = RecordsPayload::default();
539 assert!(matches!(p, RecordsPayload::V2(ref v) if v.is_empty()));
540 }
541
542 #[test]
543 fn looks_like_v2_rejects_short_buffer() {
544 let short = Bytes::from_static(&[0u8; 10]);
546 let p = RecordsPayload::from_bytes(short.clone()).unwrap();
547 assert!(p.as_legacy() == Some(&short));
548 }
549
550 #[test]
551 fn borrowed_legacy_roundtrip() {
552 let bytes = legacy_bytes();
553 let p = RecordsPayloadBorrowed::from_slice(&bytes).unwrap();
554 assert!(matches!(p, RecordsPayloadBorrowed::Legacy(_)));
555 assert!(p.payload_len() == bytes.len());
556
557 let mut out = BytesMut::new();
558 p.encode_to(&mut out).unwrap();
559 assert!(&out[..] == &bytes[..]);
560
561 let owned = p.to_owned().unwrap();
562 match owned {
563 RecordsPayload::Legacy(b) => assert!(&b[..] == &bytes[..]),
564 _ => panic!("expected Legacy"),
565 }
566 }
567
568 #[test]
569 fn borrowed_v2_payload_len_and_encode() {
570 let rb = sample_v2();
571 let mut buf = BytesMut::new();
572 rb.encode(&mut buf).unwrap();
573 let frozen = buf.freeze();
574 let p = RecordsPayloadBorrowed::from_slice(&frozen).unwrap();
575 assert!(p.payload_len() == frozen.len());
576
577 let mut out = BytesMut::new();
578 p.encode_to(&mut out).unwrap();
579 assert!(&out[..] == &frozen[..]);
580 }
581
582 #[test]
583 fn borrowed_encode_decode_via_traits() {
584 let rb = sample_v2();
585 let mut buf = BytesMut::new();
586 rb.encode(&mut buf).unwrap();
587 let frozen = buf.freeze();
588
589 let p = RecordsPayloadBorrowed::from_slice(&frozen).unwrap();
590 let mut out = BytesMut::new();
591 <RecordsPayloadBorrowed as crate::Encode>::encode(&p, &mut out, 0).unwrap();
592 assert!(<RecordsPayloadBorrowed as crate::Encode>::encoded_len(&p, 0) == frozen.len());
593
594 let mut cur: &[u8] = &out;
595 let back =
596 <RecordsPayloadBorrowed as crate::DecodeBorrow>::decode_borrow(&mut cur, 0).unwrap();
597 assert!(matches!(back, RecordsPayloadBorrowed::V2(_)));
598 }
599
600 #[test]
601 fn borrowed_default_is_empty_v2() {
602 let p = RecordsPayloadBorrowed::default();
603 assert!(matches!(p, RecordsPayloadBorrowed::V2(ref v) if v.is_empty()));
604 }
605
606 #[test]
607 fn from_fetch_bytes_drops_incomplete_trailing_batch() {
608 let mut b0 = sample_v2();
613 b0.base_offset = 0;
614 let mut b1 = sample_v2();
615 b1.base_offset = 1;
616 let mut buf = BytesMut::new();
617 b0.encode(&mut buf).unwrap();
618 b1.encode(&mut buf).unwrap();
619 buf.extend_from_slice(&[0u8; 7]); let p = RecordsPayload::from_fetch_bytes(buf.freeze()).unwrap();
621 let batches = p.as_v2().expect("v2");
622 assert!(batches == &[b0, b1][..]);
623 }
624
625 #[test]
626 fn from_fetch_bytes_still_errors_on_corrupt_batch() {
627 let rb = sample_v2();
630 let mut buf = BytesMut::new();
631 rb.encode(&mut buf).unwrap();
632 let mut bytes = buf.to_vec();
633 bytes[61] ^= 0xFF;
635 let err = RecordsPayload::from_fetch_bytes(Bytes::from(bytes)).unwrap_err();
636 assert!(matches!(err, RecordsError::CrcMismatch { .. }));
637 }
638
639 #[test]
640 fn from_fetch_bytes_legacy_passes_through() {
641 let bytes = legacy_bytes();
642 let p = RecordsPayload::from_fetch_bytes(bytes.clone()).unwrap();
643 assert!(p.as_legacy() == Some(&bytes));
644 }
645
646 #[test]
647 fn from_fetch_bytes_empty_is_empty_v2() {
648 let p = RecordsPayload::from_fetch_bytes(Bytes::new()).unwrap();
651 assert!(matches!(p, RecordsPayload::Legacy(ref b) if b.is_empty()));
652 }
653}