Skip to main content

coven_storage/
object_stream.rs

1//! A provider response consumed incrementally, with an explicit checked EOF.
2
3use crate::{ByteRange, ObjectPath, ObjectRead, StorageError, StorageFailure, StoredObject};
4
5/// Maximum collected response; larger objects use the incremental reader.
6pub const BUFFERED_READ_LIMIT: u64 = 32 * 1024 * 1024;
7
8/// A native response body. Reads never exceed the supplied buffer; zero means
9/// transport completed and any replacement-revision check also succeeded.
10#[async_trait::async_trait]
11pub trait ObjectBody: Send {
12    /// Receive the next bytes of this response, without starting another body request.
13    async fn read(&mut self, output: &mut [u8]) -> Result<usize, StorageError>;
14}
15
16/// Metadata and the single body response it describes. A consumer must read
17/// through EOF before treating a complete object as received. Dropping this
18/// value cancels the response and establishes no completion verdict.
19pub struct ObjectStream<'a> {
20    metadata: StoredObject,
21    state: StreamState<'a>,
22    /// Told how far through the object the bytes read so far reach.
23    received: crate::ByteProgress,
24    /// The bytes read so far.
25    delivered: u64,
26}
27
28enum StreamState<'a> {
29    Receiving {
30        remaining: u64,
31        body: Box<dyn ObjectBody + 'a>,
32        download: Option<crate::transfer_budget::DownloadLease>,
33    },
34    Complete,
35    Failed,
36}
37
38impl<'a> ObjectStream<'a> {
39    /// Observation for the complete object, including on a ranged response.
40    pub fn metadata(&self) -> &StoredObject {
41        &self.metadata
42    }
43
44    /// Wrap a native response, bounding its exact body length by the observation.
45    pub fn new(
46        metadata: StoredObject,
47        range: Option<ByteRange>,
48        body: impl ObjectBody + 'a,
49    ) -> Result<Self, StorageError> {
50        let remaining = metadata.read_length(&metadata.path, range)?;
51        Ok(Self {
52            metadata,
53            state: StreamState::Receiving { remaining, body: Box::new(body), download: None },
54            received: crate::ByteProgress::unread(),
55            delivered: 0,
56        })
57    }
58
59    pub(crate) fn check(
60        &self,
61        path: &ObjectPath,
62        range: Option<ByteRange>,
63    ) -> Result<(), StorageError> {
64        let expected = self.metadata.read_length(path, range)?;
65        match &self.state {
66            StreamState::Receiving { remaining, .. } if *remaining == expected => Ok(()),
67            _ => Err(StorageFailure::Protocol
68                .with_source("provider returned an already consumed response")),
69        }
70    }
71
72    /// Receive bounded bytes and check transport completion and exact framing.
73    pub async fn read(&mut self, output: &mut [u8]) -> Result<usize, StorageError> {
74        if output.is_empty() {
75            return Ok(0);
76        }
77        let mut response = std::mem::replace(&mut self.state, StreamState::Failed);
78        let (remaining, body) = match &mut response {
79            StreamState::Receiving { remaining, body, .. } => (remaining, body),
80            StreamState::Complete => {
81                self.state = StreamState::Complete;
82                return Ok(0);
83            }
84            StreamState::Failed => {
85                return Err(StorageFailure::Protocol.with_source("response already failed"))
86            }
87        };
88        // Keep the receiving state intact: its fields drop the native body
89        // before the lease, also when this read future is cancelled.
90        let count = body.read(output).await?;
91        if count > output.len() || count as u64 > *remaining {
92            return Err(
93                StorageFailure::Protocol.with_source("response exceeds its declared length")
94            );
95        }
96        if count == 0 {
97            if *remaining != 0 {
98                return Err(StorageFailure::Protocol
99                    .with_source("response ended before its declared length"));
100            }
101            self.state = StreamState::Complete;
102        } else {
103            *remaining -= count as u64;
104            self.state = response;
105            self.delivered += count as u64;
106            self.received.moved(self.delivered);
107        }
108        Ok(count)
109    }
110
111    /// Collect only a caller-bounded response. Objects with streamed plaintext
112    /// use `read` instead; the bound is checked before allocating their body.
113    pub async fn collect(mut self, maximum: u64) -> Result<ObjectRead, StorageError> {
114        let remaining = match &self.state {
115            StreamState::Receiving { remaining, .. } => *remaining,
116            StreamState::Complete => 0,
117            StreamState::Failed => {
118                return Err(StorageFailure::Protocol.with_source("response already failed"))
119            }
120        };
121        if remaining > maximum {
122            return Err(StorageFailure::Protocol
123                .with_source("object exceeds the consumer's buffered-read bound"));
124        }
125        let capacity = usize::try_from(remaining)
126            .map_err(|error| StorageFailure::Protocol.with_source(error))?;
127        let mut bytes = Vec::with_capacity(capacity);
128        let mut chunk = vec![0; 64 * 1024];
129        loop {
130            let length = self.read(&mut chunk).await?;
131            if length == 0 {
132                break;
133            }
134            bytes.extend_from_slice(&chunk[..length]);
135        }
136        Ok(ObjectRead { metadata: self.metadata, bytes })
137    }
138}
139
140struct CompletingBody<'a, F> {
141    body: Box<dyn ObjectBody + 'a>,
142    completion: Option<F>,
143}
144
145#[async_trait::async_trait]
146impl<F: std::future::Future<Output = Result<(), StorageError>> + Send> ObjectBody
147    for CompletingBody<'_, F>
148{
149    async fn read(&mut self, output: &mut [u8]) -> Result<usize, StorageError> {
150        let length = self.body.read(output).await?;
151        if length == 0 {
152            if let Some(completion) = self.completion.take() {
153                completion.await?;
154            }
155        }
156        Ok(length)
157    }
158}
159
160impl<'a> ObjectStream<'a> {
161    /// Retain the store's shared download lease through the checked EOF.
162    pub(crate) fn with_download(mut self, lease: crate::transfer_budget::DownloadLease) -> Self {
163        if let StreamState::Receiving { download, .. } = &mut self.state {
164            *download = Some(lease);
165        }
166        self
167    }
168
169    /// Tell `received` how far through the object each read reaches; it
170    /// already counts from where the response starts.
171    pub(crate) fn heard_by(mut self, received: crate::ByteProgress) -> Self {
172        self.received = received;
173        self
174    }
175
176    /// Count each body read's bytes and wait in the connection's tally.
177    pub(crate) fn counted(mut self, tally: &'a crate::request_tally::RequestTally) -> Self {
178        self.state = match self.state {
179            StreamState::Receiving { remaining, body, download } => StreamState::Receiving {
180                remaining,
181                body: Box::new(CountedBody { body, tally }),
182                download,
183            },
184            state => state,
185        };
186        self
187    }
188
189    /// Require a provider check after EOF, before completion is observable.
190    pub(crate) fn after(
191        mut self,
192        completion: impl std::future::Future<Output = Result<(), StorageError>> + Send + 'a,
193    ) -> Self {
194        self.state = match self.state {
195            StreamState::Receiving { remaining, body, download } => StreamState::Receiving {
196                remaining,
197                body: Box::new(CompletingBody { body, completion: Some(completion) }),
198                download,
199            },
200            state => state,
201        };
202        self
203    }
204}
205
206struct CountedBody<'a> {
207    body: Box<dyn ObjectBody + 'a>,
208    tally: &'a crate::request_tally::RequestTally,
209}
210
211#[async_trait::async_trait]
212impl ObjectBody for CountedBody<'_> {
213    async fn read(&mut self, output: &mut [u8]) -> Result<usize, StorageError> {
214        self.tally.receive(self.body.read(output)).await
215    }
216}
217
218#[async_trait::async_trait]
219impl<T: AsRef<[u8]> + Send> ObjectBody for std::io::Cursor<T> {
220    async fn read(&mut self, output: &mut [u8]) -> Result<usize, StorageError> {
221        Ok(std::io::Read::read(self, output).expect("read from a memory cursor"))
222    }
223}
224
225#[cfg(test)]
226#[path = "object_stream_tests.rs"]
227mod tests;