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/>.
|
|
|
|
|
2016-12-20 21:29:45 -05:00
|
|
|
//! Tools for implementing a `http_entity::Entity` body composed from many "slices".
|
2016-12-17 02:11:08 -05:00
|
|
|
|
2017-03-02 22:29:28 -05:00
|
|
|
use reffers::ARefs;
|
|
|
|
use futures::stream::{self, BoxStream};
|
|
|
|
use futures::Stream;
|
2016-11-25 17:34:00 -05:00
|
|
|
use std::fmt;
|
|
|
|
use std::ops::Range;
|
|
|
|
|
2017-03-02 22:29:28 -05:00
|
|
|
pub type Chunk = ARefs<'static, [u8]>;
|
|
|
|
pub type Body = stream::BoxStream<Chunk, ::hyper::Error>;
|
|
|
|
|
2017-02-21 22:37:36 -05:00
|
|
|
/// Writes a byte range to the given `io::Write` given a context argument; meant for use with
|
|
|
|
/// `Slices`.
|
2017-03-02 22:29:28 -05:00
|
|
|
pub trait Slice : Sync + Sized + 'static {
|
|
|
|
type Ctx: Send + Clone;
|
|
|
|
type Chunk: Send;
|
2017-02-25 21:54:52 -05:00
|
|
|
|
2016-12-17 02:11:08 -05:00
|
|
|
/// The byte position (relative to the start of the `Slices`) beyond the end of this slice.
|
|
|
|
/// Note the starting position (and thus length) are inferred from the previous slice.
|
2017-02-21 22:37:36 -05:00
|
|
|
fn end(&self) -> u64;
|
2016-11-25 17:34:00 -05:00
|
|
|
|
2016-12-20 21:29:45 -05:00
|
|
|
/// Writes `r` to `out`, as in `http_entity::Entity::write_to`.
|
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)
|
|
|
|
-> stream::BoxStream<Self::Chunk, ::hyper::Error>;
|
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".
|
2017-02-21 22:37:36 -05:00
|
|
|
/// This is used to implement `.mp4` serving in `mp4::Mp4File` 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-02-25 21:54:52 -05:00
|
|
|
impl<S> fmt::Debug for Slices<S> where S: fmt::Debug + 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)
|
|
|
|
}
|
|
|
|
|
2016-12-17 02:11:08 -05:00
|
|
|
/// Appends the given slice.
|
2017-02-21 22:37:36 -05:00
|
|
|
pub fn append(&mut self, slice: S) {
|
|
|
|
assert!(slice.end() > self.len);
|
|
|
|
self.len = slice.end();
|
|
|
|
self.slices.push(slice);
|
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`.
|
2016-12-20 21:29:45 -05:00
|
|
|
/// This interface mirrors `http_entity::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>)
|
|
|
|
-> BoxStream<S::Chunk, ::hyper::Error> {
|
2016-11-25 17:34:00 -05:00
|
|
|
if range.start > range.end || range.end > self.len {
|
2017-03-02 22:29:28 -05:00
|
|
|
error!("Bad range {:?} for slice of length {}", range, self.len);
|
|
|
|
return stream::once(Err(::hyper::Error::Incomplete)).boxed();
|
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)| {
|
|
|
|
let (body, end);
|
|
|
|
{
|
|
|
|
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 }
|
|
|
|
end = ::std::cmp::min(range.end, s.end());
|
|
|
|
let l = end - slice_start;
|
|
|
|
body = s.get_range(&c, start_pos .. end - slice_start, l);
|
|
|
|
};
|
|
|
|
Some(Ok::<_, ::hyper::Error>((body, (c, i+1, 0, end))))
|
|
|
|
});
|
|
|
|
bodies.flatten().boxed()
|
2016-11-25 17:34:00 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
2017-03-02 22:29:28 -05:00
|
|
|
use futures::{Future, Stream};
|
|
|
|
use futures::stream::{self, BoxStream};
|
2016-11-25 17:34:00 -05:00
|
|
|
use std::ops::Range;
|
2017-03-02 22:29:28 -05:00
|
|
|
use super::{Slice, Slices};
|
|
|
|
use testutil;
|
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-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)
|
|
|
|
-> BoxStream<FakeChunk, ::hyper::Error> {
|
|
|
|
stream::once(Ok(FakeChunk{slice: self.name, range: r})).boxed()
|
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();
|
|
|
|
s.append(FakeSlice{end: 5, name: "a"});
|
|
|
|
s.append(FakeSlice{end: 5+13, name: "b"});
|
|
|
|
s.append(FakeSlice{end: 5+13+7, name: "c"});
|
|
|
|
s.append(FakeSlice{end: 5+13+7+17, name: "d"});
|
|
|
|
s.append(FakeSlice{end: 5+13+7+17+19, name: "e"});
|
|
|
|
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
|
|
|
}
|
|
|
|
}
|