2016-11-25 17:34:00 -05:00
|
|
|
// This file is part of Moonfire NVR, a security camera digital video recorder.
|
|
|
|
// Copyright (C) 2016 Scott Lamb <slamb@slamb.org>
|
|
|
|
//
|
|
|
|
// This program is free software: you can redistribute it and/or modify
|
|
|
|
// it under the terms of the GNU General Public License as published by
|
|
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
|
|
// (at your option) any later version.
|
|
|
|
//
|
|
|
|
// In addition, as a special exception, the copyright holders give
|
|
|
|
// permission to link the code of portions of this program with the
|
|
|
|
// OpenSSL library under certain conditions as described in each
|
|
|
|
// individual source file, and distribute linked combinations including
|
|
|
|
// the two.
|
|
|
|
//
|
|
|
|
// You must obey the GNU General Public License in all respects for all
|
|
|
|
// of the code used other than OpenSSL. If you modify file(s) with this
|
|
|
|
// exception, you may extend this exception to your version of the
|
|
|
|
// file(s), but you are not obligated to do so. If you do not wish to do
|
|
|
|
// so, delete this exception statement from your version. If you delete
|
|
|
|
// this exception statement from all source files in the program, then
|
|
|
|
// also delete it here.
|
|
|
|
//
|
|
|
|
// This program is distributed in the hope that it will be useful,
|
|
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
// GNU General Public License for more details.
|
|
|
|
//
|
|
|
|
// You should have received a copy of the GNU General Public License
|
|
|
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
2018-01-23 14:08:21 -05:00
|
|
|
//! Tools for implementing a `http_serve::Entity` body composed from many "slices".
|
2016-12-17 02:11:08 -05:00
|
|
|
|
2018-12-28 22:53:29 -05:00
|
|
|
use base::format_err_t;
|
2018-12-28 13:21:49 -05:00
|
|
|
use crate::body::{BoxedError, wrap_error};
|
2018-12-28 22:53:29 -05:00
|
|
|
use failure::{Error, bail};
|
|
|
|
use futures::{Stream, stream};
|
2016-11-25 17:34:00 -05:00
|
|
|
use std::fmt;
|
|
|
|
use std::ops::Range;
|
|
|
|
|
2018-12-28 11:04:47 -05:00
|
|
|
/// Gets a byte range given a context argument.
|
|
|
|
/// Each `Slice` instance belongs to a single `Slices`.
|
2017-10-17 09:14:47 -04:00
|
|
|
pub trait Slice : fmt::Debug + Sized + Sync + 'static {
|
2017-03-02 22:29:28 -05:00
|
|
|
type Ctx: Send + Clone;
|
|
|
|
type Chunk: Send;
|
2017-02-25 21:54:52 -05:00
|
|
|
|
2018-12-28 11:04:47 -05:00
|
|
|
/// The byte position (relative to the start of the `Slices`) of the end of this slice,
|
|
|
|
/// exclusive. Note the starting position (and thus length) are inferred from the previous
|
|
|
|
/// slice. Must remain the same for the lifetime of the `Slice`.
|
2017-02-21 22:37:36 -05:00
|
|
|
fn end(&self) -> u64;
|
2016-11-25 17:34:00 -05:00
|
|
|
|
2018-12-28 11:04:47 -05:00
|
|
|
/// Gets the body bytes indicated by `r`, which is relative to this slice's start.
|
2016-12-17 02:11:08 -05:00
|
|
|
/// The additional argument `ctx` is as supplied to the `Slices`.
|
|
|
|
/// The additional argument `l` is the length of this slice, as determined by the `Slices`.
|
2017-03-02 22:29:28 -05:00
|
|
|
fn get_range(&self, ctx: &Self::Ctx, r: Range<u64>, len: u64)
|
2019-06-14 11:47:11 -04:00
|
|
|
-> Box<dyn Stream<Item = Self::Chunk, Error = BoxedError> + Send>;
|
2016-11-25 17:34:00 -05:00
|
|
|
|
2017-03-02 22:29:28 -05:00
|
|
|
fn get_slices(ctx: &Self::Ctx) -> &Slices<Self>;
|
2016-11-25 17:34:00 -05:00
|
|
|
}
|
|
|
|
|
2016-12-17 02:11:08 -05:00
|
|
|
/// Helper to serve byte ranges from a body which is broken down into many "slices".
|
2018-12-28 11:04:47 -05:00
|
|
|
/// This is used to implement `.mp4` serving in `mp4::File` from `mp4::Slice` enums.
|
2017-02-25 21:54:52 -05:00
|
|
|
pub struct Slices<S> where S: Slice {
|
2016-12-17 02:11:08 -05:00
|
|
|
/// The total byte length of the `Slices`.
|
2017-02-21 22:37:36 -05:00
|
|
|
/// Equivalent to `self.slices.back().map(|s| s.end()).unwrap_or(0)`; kept for convenience and
|
|
|
|
/// to avoid a branch.
|
2016-11-25 17:34:00 -05:00
|
|
|
len: u64,
|
2016-12-17 02:11:08 -05:00
|
|
|
|
|
|
|
/// 0 or more slices of this file.
|
2017-02-21 22:37:36 -05:00
|
|
|
slices: Vec<S>,
|
2016-11-25 17:34:00 -05:00
|
|
|
}
|
|
|
|
|
2017-10-17 09:14:47 -04:00
|
|
|
impl<S> fmt::Debug for Slices<S> where S: Slice {
|
2016-11-25 17:34:00 -05:00
|
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
|
|
write!(f, "{} slices with overall length {}:", self.slices.len(), self.len)?;
|
|
|
|
let mut start = 0;
|
|
|
|
for (i, s) in self.slices.iter().enumerate() {
|
2017-02-21 22:37:36 -05:00
|
|
|
let end = s.end();
|
2016-12-02 23:40:55 -05:00
|
|
|
write!(f, "\ni {:7}: range [{:12}, {:12}) len {:12}: {:?}",
|
2017-02-21 22:37:36 -05:00
|
|
|
i, start, end, end - start, s)?;
|
|
|
|
start = end;
|
2016-11-25 17:34:00 -05:00
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-02-25 21:54:52 -05:00
|
|
|
impl<S> Slices<S> where S: Slice {
|
|
|
|
pub fn new() -> Self { Slices{len: 0, slices: Vec::new()} }
|
2016-11-25 17:34:00 -05:00
|
|
|
|
2016-12-17 02:11:08 -05:00
|
|
|
/// Reserves space for at least `additional` more slices to be appended.
|
2016-11-25 17:34:00 -05:00
|
|
|
pub fn reserve(&mut self, additional: usize) {
|
|
|
|
self.slices.reserve(additional)
|
|
|
|
}
|
|
|
|
|
2018-12-28 11:04:47 -05:00
|
|
|
/// Appends the given slice, which must have end > the Slices's current len.
|
2017-10-17 11:55:21 -04:00
|
|
|
pub fn append(&mut self, slice: S) -> Result<(), Error> {
|
|
|
|
if slice.end() <= self.len {
|
2018-02-21 01:46:14 -05:00
|
|
|
bail!("end {} <= len {} while adding slice {:?} to slices:\n{:?}",
|
|
|
|
slice.end(), self.len, slice, self);
|
2017-10-17 11:55:21 -04:00
|
|
|
}
|
2017-02-21 22:37:36 -05:00
|
|
|
self.len = slice.end();
|
|
|
|
self.slices.push(slice);
|
2017-10-17 11:55:21 -04:00
|
|
|
Ok(())
|
2016-11-25 17:34:00 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns the total byte length of all slices.
|
|
|
|
pub fn len(&self) -> u64 { self.len }
|
|
|
|
|
|
|
|
/// Returns the number of slices.
|
|
|
|
pub fn num(&self) -> usize { self.slices.len() }
|
|
|
|
|
2016-12-17 02:11:08 -05:00
|
|
|
/// Writes `range` to `out`.
|
2018-01-23 14:08:21 -05:00
|
|
|
/// This interface mirrors `http_serve::Entity::write_to`, with the additional `ctx` argument.
|
2017-03-02 22:29:28 -05:00
|
|
|
pub fn get_range(&self, ctx: &S::Ctx, range: Range<u64>)
|
2019-06-14 11:47:11 -04:00
|
|
|
-> Box<dyn Stream<Item = S::Chunk, Error = BoxedError> + Send> {
|
2016-11-25 17:34:00 -05:00
|
|
|
if range.start > range.end || range.end > self.len {
|
2018-12-28 18:30:33 -05:00
|
|
|
return Box::new(stream::once(Err(wrap_error(format_err_t!(
|
|
|
|
Internal, "Bad range {:?} for slice of length {}", range, self.len)))));
|
2016-11-25 17:34:00 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// Binary search for the first slice of the range to write, determining its index and
|
|
|
|
// (from the preceding slice) the start of its range.
|
2017-03-02 22:29:28 -05:00
|
|
|
let (i, slice_start) = match self.slices.binary_search_by_key(&range.start, |s| s.end()) {
|
2017-02-21 22:37:36 -05:00
|
|
|
Ok(i) => (i+1, self.slices[i].end()), // desired start == slice i's end; first is i+1!
|
|
|
|
Err(i) if i == 0 => (0, 0), // desired start < slice 0's end; first is 0.
|
|
|
|
Err(i) => (i, self.slices[i-1].end()), // desired start < slice i's end; first is i.
|
2016-11-25 17:34:00 -05:00
|
|
|
};
|
|
|
|
|
|
|
|
// Iterate through and write each slice until the end.
|
2017-03-02 22:29:28 -05:00
|
|
|
|
|
|
|
let start_pos = range.start - slice_start;
|
|
|
|
let bodies = stream::unfold(
|
|
|
|
(ctx.clone(), i, start_pos, slice_start), move |(c, i, start_pos, slice_start)| {
|
2017-10-01 18:29:22 -04:00
|
|
|
let (body, min_end);
|
2017-03-02 22:29:28 -05:00
|
|
|
{
|
|
|
|
let self_ = S::get_slices(&c);
|
|
|
|
if i == self_.slices.len() { return None }
|
|
|
|
let s = &self_.slices[i];
|
|
|
|
if range.end == slice_start + start_pos { return None }
|
2017-10-01 18:29:22 -04:00
|
|
|
let s_end = s.end();
|
|
|
|
min_end = ::std::cmp::min(range.end, s_end);
|
|
|
|
let l = s_end - slice_start;
|
|
|
|
body = s.get_range(&c, start_pos .. min_end - slice_start, l);
|
2017-03-02 22:29:28 -05:00
|
|
|
};
|
2018-08-30 01:26:19 -04:00
|
|
|
Some(Ok::<_, BoxedError>((body, (c, i+1, 0, min_end))))
|
2017-03-02 22:29:28 -05:00
|
|
|
});
|
2017-09-22 00:51:58 -04:00
|
|
|
Box::new(bodies.flatten())
|
2016-11-25 17:34:00 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
2018-12-28 13:21:49 -05:00
|
|
|
use crate::body::BoxedError;
|
2018-12-28 22:53:29 -05:00
|
|
|
use db::testutil;
|
2017-03-02 22:29:28 -05:00
|
|
|
use futures::{Future, Stream};
|
2017-09-22 00:51:58 -04:00
|
|
|
use futures::stream;
|
2018-12-28 22:53:29 -05:00
|
|
|
use lazy_static::lazy_static;
|
2016-11-25 17:34:00 -05:00
|
|
|
use std::ops::Range;
|
2017-03-02 22:29:28 -05:00
|
|
|
use super::{Slice, Slices};
|
2016-11-25 17:34:00 -05:00
|
|
|
|
|
|
|
#[derive(Debug, Eq, PartialEq)]
|
2017-03-02 22:29:28 -05:00
|
|
|
pub struct FakeChunk {
|
|
|
|
slice: &'static str,
|
2016-11-25 17:34:00 -05:00
|
|
|
range: Range<u64>,
|
|
|
|
}
|
|
|
|
|
2017-10-17 09:14:47 -04:00
|
|
|
#[derive(Debug)]
|
2017-02-21 22:37:36 -05:00
|
|
|
pub struct FakeSlice {
|
|
|
|
end: u64,
|
2016-11-25 17:34:00 -05:00
|
|
|
name: &'static str,
|
|
|
|
}
|
|
|
|
|
2017-02-25 21:54:52 -05:00
|
|
|
impl Slice for FakeSlice {
|
2017-03-02 22:29:28 -05:00
|
|
|
type Ctx = &'static Slices<FakeSlice>;
|
|
|
|
type Chunk = FakeChunk;
|
2017-02-25 21:54:52 -05:00
|
|
|
|
2017-02-21 22:37:36 -05:00
|
|
|
fn end(&self) -> u64 { self.end }
|
|
|
|
|
2017-03-02 22:29:28 -05:00
|
|
|
fn get_range(&self, _ctx: &&'static Slices<FakeSlice>, r: Range<u64>, _l: u64)
|
2019-06-14 11:47:11 -04:00
|
|
|
-> Box<dyn Stream<Item = FakeChunk, Error = BoxedError> + Send> {
|
2017-09-22 00:51:58 -04:00
|
|
|
Box::new(stream::once(Ok(FakeChunk{slice: self.name, range: r})))
|
2016-11-25 17:34:00 -05:00
|
|
|
}
|
2017-03-02 22:29:28 -05:00
|
|
|
|
|
|
|
fn get_slices(ctx: &&'static Slices<FakeSlice>) -> &'static Slices<Self> { *ctx }
|
2016-11-25 17:34:00 -05:00
|
|
|
}
|
|
|
|
|
2017-03-02 22:29:28 -05:00
|
|
|
lazy_static! {
|
|
|
|
static ref SLICES: Slices<FakeSlice> = {
|
|
|
|
let mut s = Slices::new();
|
2017-10-17 11:55:21 -04:00
|
|
|
s.append(FakeSlice{end: 5, name: "a"}).unwrap();
|
|
|
|
s.append(FakeSlice{end: 5+13, name: "b"}).unwrap();
|
|
|
|
s.append(FakeSlice{end: 5+13+7, name: "c"}).unwrap();
|
|
|
|
s.append(FakeSlice{end: 5+13+7+17, name: "d"}).unwrap();
|
|
|
|
s.append(FakeSlice{end: 5+13+7+17+19, name: "e"}).unwrap();
|
2017-03-02 22:29:28 -05:00
|
|
|
s
|
|
|
|
};
|
2016-11-25 17:34:00 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
pub fn size() {
|
2017-03-02 22:29:28 -05:00
|
|
|
testutil::init();
|
|
|
|
assert_eq!(5 + 13 + 7 + 17 + 19, SLICES.len());
|
2016-11-25 17:34:00 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
pub fn exact_slice() {
|
|
|
|
// Test writing exactly slice b.
|
2017-03-02 22:29:28 -05:00
|
|
|
testutil::init();
|
|
|
|
let out = SLICES.get_range(&&*SLICES, 5 .. 18).collect().wait().unwrap();
|
|
|
|
assert_eq!(&[FakeChunk{slice: "b", range: 0 .. 13}], &out[..]);
|
2016-11-25 17:34:00 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
pub fn offset_first() {
|
|
|
|
// Test writing part of slice a.
|
2017-03-02 22:29:28 -05:00
|
|
|
testutil::init();
|
|
|
|
let out = SLICES.get_range(&&*SLICES, 1 .. 3).collect().wait().unwrap();
|
|
|
|
assert_eq!(&[FakeChunk{slice: "a", range: 1 .. 3}], &out[..]);
|
2016-11-25 17:34:00 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
pub fn offset_mid() {
|
|
|
|
// Test writing part of slice b, all of slice c, and part of slice d.
|
2017-03-02 22:29:28 -05:00
|
|
|
testutil::init();
|
|
|
|
let out = SLICES.get_range(&&*SLICES, 17 .. 26).collect().wait().unwrap();
|
2016-11-25 17:34:00 -05:00
|
|
|
assert_eq!(&[
|
2017-03-02 22:29:28 -05:00
|
|
|
FakeChunk{slice: "b", range: 12 .. 13},
|
|
|
|
FakeChunk{slice: "c", range: 0 .. 7},
|
|
|
|
FakeChunk{slice: "d", range: 0 .. 1},
|
|
|
|
], &out[..]);
|
2016-11-25 17:34:00 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
pub fn everything() {
|
|
|
|
// Test writing the whole Slices.
|
2017-03-02 22:29:28 -05:00
|
|
|
testutil::init();
|
|
|
|
let out = SLICES.get_range(&&*SLICES, 0 .. 61).collect().wait().unwrap();
|
2016-11-25 17:34:00 -05:00
|
|
|
assert_eq!(&[
|
2017-03-02 22:29:28 -05:00
|
|
|
FakeChunk{slice: "a", range: 0 .. 5},
|
|
|
|
FakeChunk{slice: "b", range: 0 .. 13},
|
|
|
|
FakeChunk{slice: "c", range: 0 .. 7},
|
|
|
|
FakeChunk{slice: "d", range: 0 .. 17},
|
|
|
|
FakeChunk{slice: "e", range: 0 .. 19},
|
|
|
|
], &out[..]);
|
2016-11-25 17:34:00 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
pub fn at_end() {
|
2017-03-02 22:29:28 -05:00
|
|
|
testutil::init();
|
|
|
|
let out = SLICES.get_range(&&*SLICES, 61 .. 61).collect().wait().unwrap();
|
|
|
|
let empty: &[FakeChunk] = &[];
|
|
|
|
assert_eq!(empty, &out[..]);
|
2016-11-25 17:34:00 -05:00
|
|
|
}
|
|
|
|
}
|