diff --git a/src/coding.rs b/src/coding.rs new file mode 100644 index 0000000..a0b432b --- /dev/null +++ b/src/coding.rs @@ -0,0 +1,192 @@ +// This file is part of Moonfire NVR, a security camera digital video recorder. +// Copyright (C) 2016 Scott Lamb +// +// 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 . + +//! Binary encoding/decoding. + +/// Zigzag-encodes a signed integer, as in [protocol buffer +/// encoding](https://developers.google.com/protocol-buffers/docs/encoding#types). Uses the low bit +/// to indicate signedness (1 = negative, 0 = non-negative). +#[inline(always)] +pub fn zigzag32(i: i32) -> u32 { ((i << 1) as u32) ^ ((i >> 31) as u32) } + +/// Zigzag-decodes to a signed integer. +/// See `zigzag`. +#[inline(always)] +pub fn unzigzag32(i: u32) -> i32 { ((i >> 1) as i32) ^ -((i & 1) as i32) } + +#[inline(always)] +pub fn decode_varint32(data: &[u8], i: usize) -> Result<(u32, usize), ()> { + // Unroll a few likely possibilities before going into the robust out-of-line loop. + // This aids branch prediction. + if data.len() > i && (data[i] & 0x80) == 0 { + return Ok((data[i] as u32, i+1)) + } else if data.len() > i + 1 && (data[i+1] & 0x80) == 0 { + return Ok((( (data[i] & 0x7f) as u32) | + (( data[i+1] as u32) << 7), + i+2)) + } else if data.len() > i + 2 && (data[i+2] & 0x80) == 0 { + return Ok((( (data[i] & 0x7f) as u32) | + (((data[i+1] & 0x7f) as u32) << 7) | + (( data[i+2] as u32) << 14), + i+3)) + } + decode_varint32_slow(data, i) +} + +#[cold] +fn decode_varint32_slow(data: &[u8], mut i: usize) -> Result<(u32, usize), ()> { + let l = data.len(); + let mut out = 0; + let mut shift = 0; + loop { + if i == l { + return Err(()) + } + let b = data[i]; + if shift == 28 && (b & 0xf0) != 0 { + return Err(()) + } + out |= ((b & 0x7f) as u32) << shift; + shift += 7; + i += 1; + if (b & 0x80) == 0 { + break; + } + } + Ok((out, i)) +} + +pub fn append_varint32(i: u32, data: &mut Vec) { + if i < 1u32 << 7 { + data.push(i as u8); + } else if i < 1u32 << 14 { + data.extend_from_slice(&[(( i & 0x7F) | 0x80) as u8, + (i >> 7) as u8]); + } else if i < 1u32 << 21 { + data.extend_from_slice(&[(( i & 0x7F) | 0x80) as u8, + (((i >> 7) & 0x7F) | 0x80) as u8, + (i >> 14) as u8]); + } else if i < 1u32 << 28 { + data.extend_from_slice(&[(( i & 0x7F) | 0x80) as u8, + (((i >> 7) & 0x7F) | 0x80) as u8, + (((i >> 14) & 0x7F) | 0x80) as u8, + (i >> 21) as u8]); + } else { + data.extend_from_slice(&[(( i & 0x7F) | 0x80) as u8, + (((i >> 7) & 0x7F) | 0x80) as u8, + (((i >> 14) & 0x7F) | 0x80) as u8, + (((i >> 21) & 0x7F) | 0x80) as u8, + (i >> 28) as u8]); + } +} + + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_zigzag() { + struct Test { + decoded: i32, + encoded: u32, + } + let tests = [ + Test{decoded: 0, encoded: 0}, + Test{decoded: -1, encoded: 1}, + Test{decoded: 1, encoded: 2}, + Test{decoded: -2, encoded: 3}, + Test{decoded: 2147483647, encoded: 4294967294}, + Test{decoded: -2147483648, encoded: 4294967295}, + ]; + for test in &tests { + assert_eq!(test.encoded, zigzag32(test.decoded)); + assert_eq!(test.decoded, unzigzag32(test.encoded)); + } + } + + #[test] + fn test_correct_varints() { + struct Test { + decoded: u32, + encoded: &'static [u8], + } + let tests = [ + Test{decoded: 1, encoded: b"\x01"}, + Test{decoded: 257, encoded: b"\x81\x02"}, + Test{decoded: 49409, encoded: b"\x81\x82\x03"}, + Test{decoded: 8438017, encoded: b"\x81\x82\x83\x04"}, + Test{decoded: 1350615297, encoded: b"\x81\x82\x83\x84\x05"}, + ]; + for test in &tests { + // Test encoding to an empty buffer. + let mut out = Vec::new(); + append_varint32(test.decoded, &mut out); + assert_eq!(&out[..], test.encoded); + + // ...and to a non-empty buffer. + let mut buf = Vec::new(); + out.clear(); + out.push(b'x'); + buf.push(b'x'); + buf.extend_from_slice(test.encoded); + append_varint32(test.decoded, &mut out); + assert_eq!(out, buf); + + // Test decoding from the beginning of the string. + assert_eq!((test.decoded, test.encoded.len()), + decode_varint32(test.encoded, 0).unwrap()); + + // ...and from the middle of a buffer. + buf.push(b'x'); + assert_eq!((test.decoded, test.encoded.len() + 1), + decode_varint32(&buf, 1).unwrap()); + } + } + + #[test] + fn test_bad_varints() { + let tests: &[&[u8]] = &[ + // buffer underruns + b"", + b"\x80", + b"\x80\x80", + b"\x80\x80\x80", + b"\x80\x80\x80\x80", + + // int32 overflows + b"\x80\x80\x80\x80\x80", + b"\x80\x80\x80\x80\x80\x00", + ]; + for (i, encoded) in tests.iter().enumerate() { + assert!(decode_varint32(encoded, 0).is_err(), "while on test {}", i); + } + } +} diff --git a/src/main.rs b/src/main.rs index 6ef3e66..1075800 100644 --- a/src/main.rs +++ b/src/main.rs @@ -69,6 +69,7 @@ use std::sync::atomic::{AtomicBool, Ordering}; use std::thread; mod clock; +mod coding; mod db; mod dir; mod error; diff --git a/src/recording.rs b/src/recording.rs index 06bc27b..7cca5b2 100644 --- a/src/recording.rs +++ b/src/recording.rs @@ -30,6 +30,7 @@ extern crate uuid; +use coding::{append_varint32, decode_varint32, unzigzag32, zigzag32}; use db; use std::ops; use error::Error; @@ -164,83 +165,6 @@ pub struct SampleIndexEncoder { pub video_index: Vec, } -/// Zigzag-encodes a signed integer, as in [protocol buffer -/// encoding](https://developers.google.com/protocol-buffers/docs/encoding#types). Uses the low bit -/// to indicate signedness (1 = negative, 0 = non-negative). -#[inline(always)] -fn zigzag32(i: i32) -> u32 { ((i << 1) as u32) ^ ((i >> 31) as u32) } - -/// Zigzag-decodes to a signed integer. -/// See `zigzag`. -#[inline(always)] -fn unzigzag32(i: u32) -> i32 { ((i >> 1) as i32) ^ -((i & 1) as i32) } - -#[inline(always)] -fn decode_varint32(data: &[u8], i: usize) -> Result<(u32, usize), ()> { - // Unroll a few likely possibilities before going into the robust out-of-line loop. - // This aids branch prediction. - if data.len() > i && (data[i] & 0x80) == 0 { - return Ok((data[i] as u32, i+1)) - } else if data.len() > i + 1 && (data[i+1] & 0x80) == 0 { - return Ok((( (data[i] & 0x7f) as u32) | - (( data[i+1] as u32) << 7), - i+2)) - } else if data.len() > i + 2 && (data[i+2] & 0x80) == 0 { - return Ok((( (data[i] & 0x7f) as u32) | - (((data[i+1] & 0x7f) as u32) << 7) | - (( data[i+2] as u32) << 14), - i+3)) - } - decode_varint32_slow(data, i) -} - -#[cold] -fn decode_varint32_slow(data: &[u8], mut i: usize) -> Result<(u32, usize), ()> { - let l = data.len(); - let mut out = 0; - let mut shift = 0; - loop { - if i == l { - return Err(()) - } - let b = data[i]; - if shift == 28 && (b & 0xf0) != 0 { - return Err(()) - } - out |= ((b & 0x7f) as u32) << shift; - shift += 7; - i += 1; - if (b & 0x80) == 0 { - break; - } - } - Ok((out, i)) -} - -fn append_varint32(i: u32, data: &mut Vec) { - if i < 1u32 << 7 { - data.push(i as u8); - } else if i < 1u32 << 14 { - data.extend_from_slice(&[(( i & 0x7F) | 0x80) as u8, - (i >> 7) as u8]); - } else if i < 1u32 << 21 { - data.extend_from_slice(&[(( i & 0x7F) | 0x80) as u8, - (((i >> 7) & 0x7F) | 0x80) as u8, - (i >> 14) as u8]); - } else if i < 1u32 << 28 { - data.extend_from_slice(&[(( i & 0x7F) | 0x80) as u8, - (((i >> 7) & 0x7F) | 0x80) as u8, - (((i >> 14) & 0x7F) | 0x80) as u8, - (i >> 21) as u8]); - } else { - data.extend_from_slice(&[(( i & 0x7F) | 0x80) as u8, - (((i >> 7) & 0x7F) | 0x80) as u8, - (((i >> 14) & 0x7F) | 0x80) as u8, - (((i >> 21) & 0x7F) | 0x80) as u8, - (i >> 28) as u8]); - } -} - impl SampleIndexIterator { pub fn new() -> SampleIndexIterator { SampleIndexIterator{i: 0, @@ -498,70 +422,10 @@ mod tests { #[cfg(nightly)] extern crate test; - use super::{append_varint32, decode_varint32, unzigzag32, zigzag32}; use super::*; #[cfg(nightly)] use self::test::Bencher; use testutil::TestDb; - #[test] - fn test_zigzag() { - struct Test { - decoded: i32, - encoded: u32, - } - let tests = [ - Test{decoded: 0, encoded: 0}, - Test{decoded: -1, encoded: 1}, - Test{decoded: 1, encoded: 2}, - Test{decoded: -2, encoded: 3}, - Test{decoded: 2147483647, encoded: 4294967294}, - Test{decoded: -2147483648, encoded: 4294967295}, - ]; - for test in &tests { - assert_eq!(test.encoded, zigzag32(test.decoded)); - assert_eq!(test.decoded, unzigzag32(test.encoded)); - } - } - - #[test] - fn test_correct_varints() { - struct Test { - decoded: u32, - encoded: &'static [u8], - } - let tests = [ - Test{decoded: 1, encoded: b"\x01"}, - Test{decoded: 257, encoded: b"\x81\x02"}, - Test{decoded: 49409, encoded: b"\x81\x82\x03"}, - Test{decoded: 8438017, encoded: b"\x81\x82\x83\x04"}, - Test{decoded: 1350615297, encoded: b"\x81\x82\x83\x84\x05"}, - ]; - for test in &tests { - // Test encoding to an empty buffer. - let mut out = Vec::new(); - append_varint32(test.decoded, &mut out); - assert_eq!(&out[..], test.encoded); - - // ...and to a non-empty buffer. - let mut buf = Vec::new(); - out.clear(); - out.push(b'x'); - buf.push(b'x'); - buf.extend_from_slice(test.encoded); - append_varint32(test.decoded, &mut out); - assert_eq!(out, buf); - - // Test decoding from the beginning of the string. - assert_eq!((test.decoded, test.encoded.len()), - decode_varint32(test.encoded, 0).unwrap()); - - // ...and from the middle of a buffer. - buf.push(b'x'); - assert_eq!((test.decoded, test.encoded.len() + 1), - decode_varint32(&buf, 1).unwrap()); - } - } - #[test] fn test_display_duration() { let tests = &[ @@ -583,25 +447,6 @@ mod tests { } } - #[test] - fn test_bad_varints() { - let tests: &[&[u8]] = &[ - // buffer underruns - b"", - b"\x80", - b"\x80\x80", - b"\x80\x80\x80", - b"\x80\x80\x80\x80", - - // int32 overflows - b"\x80\x80\x80\x80\x80", - b"\x80\x80\x80\x80\x80\x00", - ]; - for (i, encoded) in tests.iter().enumerate() { - assert!(decode_varint32(encoded, 0).is_err(), "while on test {}", i); - } - } - /// Tests encoding the example from design/schema.md. #[test] fn test_encode_example() {