1use alloc::collections::TryReserveError;
4use alloc::vec::Vec;
5use core::fmt;
6use zencodec::decode::{ByteRange, InputRequest};
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub struct IoLimits {
19 input_bytes: usize,
20 ranges: usize,
21 in_flight: usize,
22}
23
24impl IoLimits {
25 pub const fn new(input_bytes: usize, ranges: usize, in_flight: usize) -> Self {
27 Self {
28 input_bytes,
29 ranges,
30 in_flight,
31 }
32 }
33
34 pub const fn unlimited() -> Self {
36 Self::new(usize::MAX, usize::MAX, usize::MAX)
37 }
38
39 pub const fn max_input_bytes(self) -> usize {
41 self.input_bytes
42 }
43 pub const fn max_ranges(self) -> usize {
45 self.ranges
46 }
47 pub const fn max_in_flight(self) -> usize {
49 self.in_flight
50 }
51
52 pub(crate) fn bytes(self, needed: u64) -> Result<usize, InputError> {
53 if needed > self.input_bytes as u64 {
54 return Err(InputError::Limit {
55 needed,
56 limit: self.input_bytes,
57 });
58 }
59 usize::try_from(needed).map_err(|_| InputError::Limit {
60 needed,
61 limit: self.input_bytes,
62 })
63 }
64
65 pub(crate) fn reading(self) -> Result<(), InputError> {
66 if self.in_flight == 0 {
67 Err(InputError::ReadsDisabled)
68 } else {
69 Ok(())
70 }
71 }
72}
73
74impl Default for IoLimits {
75 fn default() -> Self {
76 Self::unlimited()
77 }
78}
79
80#[derive(Debug)]
82#[non_exhaustive]
83pub enum InputError {
84 Limit {
86 needed: u64,
88 limit: usize,
90 },
91 RangesDisabled,
93 ReadsDisabled,
95 Allocation {
97 bytes: usize,
99 source: TryReserveError,
101 },
102}
103
104impl fmt::Display for InputError {
105 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
106 match self {
107 Self::Limit { needed, limit } => {
108 write!(f, "input needs {needed} bytes; limit is {limit}")
109 }
110 Self::RangesDisabled => f.write_str("input range descriptors are disabled"),
111 Self::ReadsDisabled => f.write_str("input reads are disabled"),
112 Self::Allocation { bytes, .. } => {
113 write!(f, "could not reserve {bytes} input-storage bytes")
114 }
115 }
116 }
117}
118
119impl core::error::Error for InputError {
120 fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
121 match self {
122 Self::Allocation { source, .. } => Some(source),
123 _ => None,
124 }
125 }
126}
127
128pub(crate) fn reserve<T>(v: &mut Vec<T>, len: usize) -> Result<(), InputError> {
129 v.try_reserve_exact(len.saturating_sub(v.len()))
130 .map_err(|source| InputError::Allocation {
131 bytes: len.saturating_mul(core::mem::size_of::<T>()),
132 source,
133 })
134}
135
136pub(crate) fn select(
139 requests: &[InputRequest],
140 limits: IoLimits,
141) -> Result<InputRequest, InputError> {
142 let mut first_error = None;
143 for &request in requests {
144 let result = match request {
145 InputRequest::AtLeast { min_len, .. } => {
146 limits.bytes(min_len.max(1) as u64).map(|_| ())
147 }
148 InputRequest::Range(r) => limits.bytes(r.length).and({
149 if limits.ranges == 0 {
150 Err(InputError::RangesDisabled)
151 } else {
152 Ok(())
153 }
154 }),
155 _ => Ok(()),
156 };
157 match result {
158 Ok(()) => return Ok(request),
159 Err(e) => {
160 first_error.get_or_insert(e);
161 }
162 }
163 }
164 Err(first_error.unwrap_or(InputError::RangesDisabled))
165}
166
167pub(crate) fn plan(
171 first: ByteRange,
172 requests: &[InputRequest],
173 gap: u64,
174 limits: IoLimits,
175 out: &mut Vec<ByteRange>,
176) -> Result<(), InputError> {
177 limits.reading()?;
178 limits.bytes(first.length)?;
179 if limits.ranges == 0 {
180 return Err(InputError::RangesDisabled);
181 }
182 out.clear();
183 reserve(out, requests.len().min(limits.ranges))?;
184 out.push(first);
185 let mut bytes = first.length;
186 for request in requests {
187 let InputRequest::Range(r) = *request else {
188 continue;
189 };
190 if r.is_empty() || r == first || out.len() == limits.ranges {
191 continue;
192 }
193 if let Some(sum) = bytes.checked_add(r.length)
196 && sum <= limits.input_bytes as u64
197 {
198 out.push(r);
199 bytes = sum;
200 }
201 }
202 out.sort_unstable_by_key(|r| (r.offset, r.length));
203 let mut write = 0;
204 for i in 1..out.len() {
205 let r = out[i];
206 let current = out[write];
207 let extra = r.offset.saturating_sub(current.end());
208 if r.offset <= current.end().saturating_add(gap)
209 && extra <= (limits.input_bytes as u64).saturating_sub(bytes)
210 {
211 let merged = current.end().max(r.end()) - current.offset;
212 bytes = bytes - current.length - r.length + merged;
213 out[write].length = merged;
214 } else {
215 write += 1;
216 out[write] = r;
217 }
218 }
219 out.truncate(write + 1);
220 Ok(())
221}
222
223pub(crate) fn capacity(fetched: &[(u64, Vec<u8>)]) -> usize {
224 fetched
225 .iter()
226 .fold(0usize, |sum, (_, b)| sum.saturating_add(b.capacity()))
227}
228
229#[cfg(feature = "tokio")]
232pub(crate) fn copy_requests(
233 requests: &[InputRequest],
234 limits: IoLimits,
235 out: &mut Vec<InputRequest>,
236) -> Result<(), InputError> {
237 let first = select(requests, limits)?;
238 out.clear();
239 let count = if matches!(first, InputRequest::Range(_)) {
240 requests.len().min(limits.ranges)
241 } else {
242 1
243 };
244 reserve(out, count)?;
245 out.push(first);
246 if matches!(first, InputRequest::Range(_)) {
247 for &r in requests {
248 if out.len() == count {
249 break;
250 }
251 if r != first
252 && matches!(r, InputRequest::Range(b) if b.length <= limits.input_bytes as u64)
253 {
254 out.push(r);
255 }
256 }
257 }
258 Ok(())
259}
260
261pub(crate) fn make_room(
262 window: &crate::window::Window,
263 fetched: &mut Vec<(u64, Vec<u8>)>,
264 target: usize,
265 limits: IoLimits,
266) {
267 if capacity(fetched).saturating_add(window.capacity().max(target)) > limits.input_bytes {
268 fetched.clear();
269 }
270}
271
272pub(crate) fn allocate_ranges(
273 ranges: &[ByteRange],
274 limits: IoLimits,
275 fetched: &mut Vec<(u64, Vec<u8>)>,
276) -> Result<(), InputError> {
277 fetched.truncate(ranges.len());
280 let reusable = ranges.iter().enumerate().fold(0usize, |sum, (i, r)| {
281 sum.saturating_add(
282 fetched
283 .get(i)
284 .map_or(0, |(_, b)| b.capacity())
285 .max(r.length as usize),
286 )
287 });
288 if reusable > limits.input_bytes {
289 fetched.clear();
290 }
291 reserve(fetched, ranges.len())?;
292 fetched.resize_with(ranges.len(), Default::default);
293 let mut used = 0usize;
294 for (range, (offset, buf)) in ranges.iter().zip(fetched.iter_mut()) {
295 let len = limits.bytes(range.length)?;
296 *offset = range.offset;
297 reserve(buf, len)?;
298 used = used.saturating_add(buf.capacity());
299 limits.bytes(used as u64)?;
300 buf.resize(len, 0);
301 }
302 Ok(())
303}
304
305#[cfg(all(test, feature = "tokio"))]
306mod tests {
307 use super::*;
308
309 #[test]
310 fn async_request_copy_does_not_copy_an_unbounded_alternative_list() {
311 let requests: Vec<_> = (0..10000)
312 .map(|offset| InputRequest::range(offset * 8, 8))
313 .collect();
314 let mut copied = Vec::new();
315 copy_requests(&requests, IoLimits::new(16, 2, 1), &mut copied).unwrap();
316 assert_eq!(copied, requests[..2]);
317 assert!(copied.capacity() <= 2);
318 copy_requests(
319 &[InputRequest::range(0, 17), InputRequest::at_least(32, 8)],
320 IoLimits::new(16, 0, 1),
321 &mut copied,
322 )
323 .unwrap();
324 assert_eq!(copied, [InputRequest::at_least(32, 8)]);
325 }
326}