use std::convert::Infallible;
use std::sync::Arc;
use crate::block_source::BlockSource;
use std::pin::Pin;
use std::task::{Context, Poll};
use bytes::Bytes;
use http::HeaderMap;
use http_body::{Body, SizeHint};
#[cfg_attr(feature = "wal", allow(dead_code))]
#[derive(Clone)]
pub(crate) enum Piece {
Bytes(Bytes),
Lazy { src: Arc<dyn BlockSource>, off: u64, len: usize },
}
impl Piece {
fn len(&self) -> usize {
match self {
Piece::Bytes(b) => b.len(),
Piece::Lazy { len, .. } => *len,
}
}
fn sub(&self, at: usize, take: usize) -> Piece {
match self {
Piece::Bytes(b) => Piece::Bytes(b.slice(at..at + take)),
Piece::Lazy { src, off, .. } =>
Piece::Lazy { src: src.clone(), off: off + at as u64, len: take },
}
}
}
#[derive(Clone, Default)]
pub struct SegmentBody {
pieces: Vec<Piece>,
total_len: usize,
}
impl SegmentBody {
pub(crate) fn new() -> Self {
Self {
pieces: Vec::new(),
total_len: 0,
}
}
pub(crate) fn with_capacity(n_pieces: usize) -> Self {
Self {
pieces: Vec::with_capacity(n_pieces),
total_len: 0,
}
}
pub(crate) fn push(&mut self, b: Bytes) {
self.push_piece(Piece::Bytes(b));
}
#[cfg_attr(feature = "wal", allow(dead_code))]
pub(crate) fn push_lazy(&mut self, src: Arc<dyn BlockSource>, off: u64, len: usize) {
if let Some(Piece::Lazy { src: s, off: o, len: l }) = self.pieces.last_mut() {
if Arc::ptr_eq(s, &src) && *o + *l as u64 == off {
*l += len;
self.total_len += len;
return;
}
}
self.push_piece(Piece::Lazy { src, off, len });
}
pub(crate) fn push_piece(&mut self, p: Piece) {
self.total_len += p.len();
self.pieces.push(p);
}
pub fn len(&self) -> usize {
self.total_len
}
pub fn is_empty(&self) -> bool {
self.total_len == 0
}
#[cfg_attr(feature = "wal", allow(dead_code))]
pub(crate) fn pieces(&self) -> &[Piece] {
&self.pieces
}
pub(crate) fn has_lazy(&self) -> bool {
self.pieces.iter().any(|p| matches!(p, Piece::Lazy { .. }))
}
pub(crate) async fn materialize(self) -> std::io::Result<SegmentBody> {
if !self.has_lazy() {
return Ok(self);
}
tokio::task::spawn_blocking(move || self.materialize_blocking()).await
.map_err(std::io::Error::other)?
}
pub(crate) fn materialize_blocking(self) -> std::io::Result<SegmentBody> {
let mut out = SegmentBody::with_capacity(self.pieces.len());
for p in self.pieces {
match p {
Piece::Bytes(b) => out.push(b),
Piece::Lazy { src, off, len } => {
let mut buf = vec![0u8; len];
src.read_at(off, &mut buf)?;
out.push(Bytes::from(buf));
}
}
}
Ok(out)
}
pub(crate) fn byte_pieces(&self) -> impl Iterator<Item = &Bytes> {
self.pieces.iter().map(|p| match p {
Piece::Bytes(b) => b,
Piece::Lazy { .. } => panic!("a lazy piece reached a path that needs bytes; materialize first"),
})
}
pub(crate) fn slice(&self, start: usize, len: usize) -> Self {
assert!(
start + len <= self.total_len,
"SegmentBody::slice out of range: start={} len={} total={}",
start, len, self.total_len,
);
if len == 0 {
return Self::new();
}
let mut out = Self::with_capacity((len / 4096 + 2).min(self.pieces.len()));
let mut cursor = 0usize;
let mut remaining = len;
for piece in &self.pieces {
if remaining == 0 {
break;
}
let piece_start = cursor;
let piece_end = cursor + piece.len();
cursor = piece_end;
if piece_end <= start {
continue;
}
let piece_off = if piece_start < start {
start - piece_start
} else {
0
};
let avail = piece.len() - piece_off;
let take = avail.min(remaining);
out.push_piece(piece.sub(piece_off, take));
remaining -= take;
}
out
}
pub(crate) fn into_sdk_body(self) -> aws_smithy_types::body::SdkBody {
let pieces: Vec<Bytes> = self.byte_pieces().cloned().collect();
aws_smithy_types::body::SdkBody::retryable(move || {
aws_smithy_types::body::SdkBody::from_body_0_4(ScatterBody::new(pieces.clone()))
})
}
}
struct ScatterBody {
iter: std::vec::IntoIter<Bytes>,
remaining: u64,
}
impl ScatterBody {
fn new(pieces: Vec<Bytes>) -> Self {
let remaining: u64 = pieces.iter().map(|b| b.len() as u64).sum();
Self {
iter: pieces.into_iter(),
remaining,
}
}
}
impl Body for ScatterBody {
type Data = Bytes;
type Error = Infallible;
fn poll_data(
self: Pin<&mut Self>,
_cx: &mut Context<'_>,
) -> Poll<Option<Result<Bytes, Self::Error>>> {
let me = self.get_mut();
match me.iter.next() {
Some(b) => {
me.remaining = me.remaining.saturating_sub(b.len() as u64);
Poll::Ready(Some(Ok(b)))
}
None => Poll::Ready(None),
}
}
fn poll_trailers(
self: Pin<&mut Self>,
_cx: &mut Context<'_>,
) -> Poll<Result<Option<HeaderMap>, Self::Error>> {
Poll::Ready(Ok(None))
}
fn size_hint(&self) -> SizeHint {
SizeHint::with_exact(self.remaining)
}
fn is_end_stream(&self) -> bool {
self.remaining == 0
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn segment_body_len_tracks_pieces() {
let mut b = SegmentBody::new();
assert_eq!(b.len(), 0);
assert!(b.is_empty());
b.push(Bytes::from_static(b"hello "));
b.push(Bytes::from_static(b"world"));
assert_eq!(b.len(), 11);
assert!(!b.is_empty());
assert_eq!(b.pieces().len(), 2);
}
#[test]
fn segment_body_slice_within_one_piece() {
let mut b = SegmentBody::new();
b.push(Bytes::from_static(b"abcdefgh"));
let s = b.slice(2, 4);
assert_eq!(s.len(), 4);
assert_eq!(s.byte_pieces().next().unwrap().as_ref(), b"cdef");
}
#[test]
fn segment_body_slice_across_pieces() {
let mut b = SegmentBody::new();
b.push(Bytes::from_static(b"abc"));
b.push(Bytes::from_static(b"defghi"));
b.push(Bytes::from_static(b"jkl"));
let s = b.slice(1, 9);
let collected: Vec<u8> = s.byte_pieces().flat_map(|p| p.iter().copied()).collect();
assert_eq!(collected.as_slice(), b"bcdefghij");
assert_eq!(s.len(), 9);
}
#[test]
fn segment_body_slice_full() {
let mut b = SegmentBody::new();
b.push(Bytes::from_static(b"abc"));
b.push(Bytes::from_static(b"def"));
let s = b.slice(0, 6);
assert_eq!(s.len(), 6);
}
#[test]
fn segment_body_slice_empty() {
let mut b = SegmentBody::new();
b.push(Bytes::from_static(b"abc"));
let s = b.slice(2, 0);
assert_eq!(s.len(), 0);
assert!(s.is_empty());
}
#[test]
#[should_panic(expected = "SegmentBody::slice out of range")]
fn segment_body_slice_panics_out_of_range() {
let mut b = SegmentBody::new();
b.push(Bytes::from_static(b"abc"));
let _ = b.slice(2, 5);
}
#[tokio::test]
async fn scatter_body_polls_pieces_in_order() {
let pieces = vec![
Bytes::from_static(b"part-1"),
Bytes::from_static(b"part-2"),
Bytes::from_static(b"part-3"),
];
let body = ScatterBody::new(pieces);
let mut body = Box::pin(body);
let mut collected = Vec::new();
while let Some(frame) = std::future::poll_fn(|cx| body.as_mut().poll_data(cx)).await {
let b = frame.expect("infallible");
collected.extend_from_slice(&b);
}
assert_eq!(&collected, b"part-1part-2part-3");
}
}