flv: Refactor tag parsing to use error codes instead of `None`.

This commit is contained in:
David Wendt 2023-04-22 15:52:23 -04:00 committed by kmeisthax
parent e4b76ac149
commit 0f176484ea
10 changed files with 363 additions and 157 deletions

1
Cargo.lock generated
View File

@ -1650,6 +1650,7 @@ name = "flv-rs"
version = "0.1.0"
dependencies = [
"bitflags 2.3.2",
"thiserror",
]
[[package]]

View File

@ -7,3 +7,4 @@ license = "MIT OR Apache-2.0"
[dependencies]
bitflags = "2.0.0"
thiserror = "1.0"

115
flv/src/error.rs Normal file
View File

@ -0,0 +1,115 @@
use std::io::Error as IoError;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum Error {
#[error("the FLV parser ran out of data")]
EndOfData,
#[error("the FLV cannot be read as its length exceeds the maximum memory size for this architecture")]
PointerTooBig,
#[error("the data stream does not have a valid FLV header signature")]
WrongMagic,
#[error("the FLV contains a script data block with a value of unknown type")]
UnknownValueType,
#[error("the FLV contains an audio data block that is too short")]
ShortAudioBlock,
#[error("the FLV contains an audio data block with unknown format type {0}")]
UnknownAudioFormatType(u8),
#[error("the FLV contains an audio data block with unknown sample rate {0}")]
UnknownAudioRate(u8),
#[error("the FLV contains an audio data block with unknown sample size {0}")]
UnknownAudioSampleSize(u8),
#[error("the FLV contains an audio data block with unknown channel count {0}")]
UnknownAudioChannelCount(u8),
#[error("the FLV contains an audio data block with AAC data that is of unknown type {0}")]
UnknownAacPacketType(u8),
#[error("the FLV contains a video data block that is too short")]
ShortVideoBlock,
#[error("the FLV contains a video data block with unknown frame type {0}")]
UnknownVideoFrameType(u8),
#[error("the FLV contains a video data block with unknown codec {0}")]
UnknownVideoCodec(u8),
#[error("the FLV contains a video data block with a command frame of unknown type {0}")]
UnknownVideoCommandType(u8),
#[error("the FLV contains a video data block with AVC data that is of unknown type {0}")]
UnknownAvcPacketType(u8),
#[error("the FLV contains a tag with unknown type {0}")]
UnknownTagType(u8),
#[error("IO error ({0})")]
IoError(IoError),
}
impl From<IoError> for Error {
fn from(error: IoError) -> Self {
Self::IoError(error)
}
}
impl PartialEq for Error {
fn eq(&self, other: &Error) -> bool {
match (self, other) {
(Self::EndOfData, Self::EndOfData) => true,
(Self::PointerTooBig, Self::PointerTooBig) => true,
(Self::WrongMagic, Self::WrongMagic) => true,
(Self::UnknownValueType, Self::UnknownValueType) => true,
(Self::ShortAudioBlock, Self::ShortAudioBlock) => true,
(Self::UnknownAudioFormatType(s), Self::UnknownAudioFormatType(o)) => s == o,
(Self::UnknownAudioRate(s), Self::UnknownAudioRate(o)) => s == o,
(Self::UnknownAudioSampleSize(s), Self::UnknownAudioSampleSize(o)) => s == o,
(Self::UnknownAudioChannelCount(s), Self::UnknownAudioChannelCount(o)) => s == o,
(Self::UnknownAacPacketType(s), Self::UnknownAacPacketType(o)) => s == o,
(Self::ShortVideoBlock, Self::ShortVideoBlock) => true,
(Self::UnknownVideoFrameType(s), Self::UnknownVideoFrameType(o)) => s == o,
(Self::UnknownVideoCodec(s), Self::UnknownVideoCodec(o)) => s == o,
(Self::UnknownVideoCommandType(s), Self::UnknownVideoCommandType(o)) => s == o,
(Self::UnknownAvcPacketType(s), Self::UnknownAvcPacketType(o)) => s == o,
(Self::UnknownTagType(s), Self::UnknownTagType(o)) => s == o,
(Self::IoError(s), Self::IoError(o)) => s.kind() == o.kind(),
_ => false,
}
}
}
impl Clone for Error {
fn clone(&self) -> Self {
match self {
Self::EndOfData => Self::EndOfData,
Self::PointerTooBig => Self::PointerTooBig,
Self::WrongMagic => Self::WrongMagic,
Self::UnknownValueType => Self::UnknownValueType,
Self::ShortAudioBlock => Self::ShortAudioBlock,
Self::UnknownAudioFormatType(unk) => Self::UnknownAudioFormatType(*unk),
Self::UnknownAudioRate(unk) => Self::UnknownAudioRate(*unk),
Self::UnknownAudioSampleSize(unk) => Self::UnknownAudioSampleSize(*unk),
Self::UnknownAudioChannelCount(unk) => Self::UnknownAudioChannelCount(*unk),
Self::UnknownAacPacketType(unk) => Self::UnknownAacPacketType(*unk),
Self::ShortVideoBlock => Self::ShortVideoBlock,
Self::UnknownVideoFrameType(unk) => Self::UnknownVideoFrameType(*unk),
Self::UnknownVideoCodec(unk) => Self::UnknownVideoCodec(*unk),
Self::UnknownVideoCommandType(unk) => Self::UnknownVideoCommandType(*unk),
Self::UnknownAvcPacketType(unk) => Self::UnknownAvcPacketType(*unk),
Self::UnknownTagType(unk) => Self::UnknownTagType(*unk),
// IOError cannot be cloned, since you can attach arbitrary error
// types to it. Instead, cloned FLV errors contain only the error
// kind and text, which can be cloned.
Self::IoError(ioe) => Self::IoError(IoError::new(ioe.kind(), ioe.to_string())),
}
}
}

View File

@ -1,3 +1,4 @@
use crate::error::Error;
use crate::reader::FlvReader;
use bitflags::bitflags;
use std::io::{Seek, SeekFrom};
@ -20,34 +21,39 @@ pub struct Header {
impl Header {
/// Parse an FLV header.
///
/// If this yields `None`, then the given data stream is either not an FLV
/// container or too short to parse.
pub fn parse(reader: &mut FlvReader<'_>) -> Option<Self> {
let old_position = reader.stream_position().ok()?;
/// The header must, at a minimum, contain the FLV magic, version number,
/// valid type flags, and a valid offset into the data. The reader will
/// seek to the start of the data tags if successful or retain it's prior
/// position otherwise.
pub fn parse(reader: &mut FlvReader<'_>) -> Result<Self, Error> {
let old_position = reader.stream_position()?;
let ret = (|| {
let signature = reader.read_u24()?;
if signature != 0x464C56 {
return None;
return Err(Error::WrongMagic);
}
let version = reader.read_u8()?;
let type_flags = TypeFlags::from_bits_retain(reader.read_u8()?);
let data_offset = reader.read_u32()?;
Some(Header {
Ok(Header {
version,
type_flags,
data_offset,
})
})();
if let Some(ret) = ret {
reader.seek(SeekFrom::Start(ret.data_offset as u64)).ok()?;
Some(ret)
} else {
reader.seek(SeekFrom::Start(old_position)).ok()?;
None
match ret {
Ok(ret) => {
reader.seek(SeekFrom::Start(ret.data_offset as u64))?;
Ok(ret)
}
Err(e) => {
reader.seek(SeekFrom::Start(old_position))?;
Err(e)
}
}
}
}
@ -64,7 +70,7 @@ mod tests {
assert_eq!(
Header::parse(&mut reader),
Some(Header {
Ok(Header {
version: 1,
type_flags: TypeFlags::HAS_AUDIO | TypeFlags::HAS_VIDEO,
data_offset: 0x12345678

View File

@ -6,6 +6,9 @@ mod video;
mod reader;
mod error;
pub use error::Error;
pub use header::Header;
pub use reader::FlvReader;
pub use script::{ScriptData, Value, Variable};

View File

@ -1,4 +1,5 @@
use std::io::{Error, ErrorKind, Result, Seek, SeekFrom};
use crate::error::Error as FlvError;
use std::io::{Error as IoError, ErrorKind, Result as IoResult, Seek, SeekFrom};
/// A reader that allows demuxing an FLV container.
pub struct FlvReader<'a> {
@ -34,22 +35,35 @@ impl<'a> FlvReader<'a> {
/// buffer. The buffer position will be advanced so that repeated reads
/// yield new data.
///
/// If the requested number of bytes are not available, `None` is returned.
pub fn read(&mut self, count: usize) -> Option<&'a [u8]> {
/// If the requested number of bytes are not available, `EndOfData` is
/// returned. This error should halt all parsing; callers should take care
/// to return the reader to it's prior position and NOT return partial data
/// (e.g. headers without body data) so that callers can retry later when
/// more data has been downloaded.
pub fn read(&mut self, count: usize) -> Result<&'a [u8], FlvError> {
let start = self.position;
let end = self.position.checked_add(count)?;
let end = self
.position
.checked_add(count)
.ok_or(FlvError::PointerTooBig)?;
if end > self.source.len() {
return None;
return Err(FlvError::EndOfData);
}
self.position = end;
Some(&self.source[start..end])
Ok(&self.source[start..end])
}
/// Read a certain number of bytes from the buffer without advancing the
/// buffer position.
pub fn peek(&mut self, count: usize) -> Option<&'a [u8]> {
///
/// If the requested number of bytes are not available, `EndOfData` is
/// returned. This error should halt all parsing; callers should take care
/// to return the reader to it's prior position and NOT return partial data
/// (e.g. headers without body data) so that callers can retry later when
/// more data has been downloaded.
pub fn peek(&mut self, count: usize) -> Result<&'a [u8], FlvError> {
let pos = self.position;
let ret = self.read(count);
@ -58,57 +72,57 @@ impl<'a> FlvReader<'a> {
ret
}
pub fn read_u8(&mut self) -> Option<u8> {
Some(self.read(1)?[0])
pub fn read_u8(&mut self) -> Result<u8, FlvError> {
Ok(self.read(1)?[0])
}
pub fn read_u16(&mut self) -> Option<u16> {
Some(u16::from_be_bytes(
pub fn read_u16(&mut self) -> Result<u16, FlvError> {
Ok(u16::from_be_bytes(
self.read(2)?.try_into().expect("two bytes"),
))
}
pub fn read_i16(&mut self) -> Option<i16> {
Some(i16::from_be_bytes(
pub fn read_i16(&mut self) -> Result<i16, FlvError> {
Ok(i16::from_be_bytes(
self.read(2)?.try_into().expect("two bytes"),
))
}
pub fn read_u24(&mut self) -> Option<u32> {
pub fn read_u24(&mut self) -> Result<u32, FlvError> {
let bytes = self.read(3)?;
Some(u32::from_be_bytes([0, bytes[0], bytes[1], bytes[2]]))
Ok(u32::from_be_bytes([0, bytes[0], bytes[1], bytes[2]]))
}
pub fn peek_u24(&mut self) -> Option<u32> {
pub fn peek_u24(&mut self) -> Result<u32, FlvError> {
let bytes = self.peek(3)?;
Some(u32::from_be_bytes([0, bytes[0], bytes[1], bytes[2]]))
Ok(u32::from_be_bytes([0, bytes[0], bytes[1], bytes[2]]))
}
pub fn read_u32(&mut self) -> Option<u32> {
Some(u32::from_be_bytes(
pub fn read_u32(&mut self) -> Result<u32, FlvError> {
Ok(u32::from_be_bytes(
self.read(4)?.try_into().expect("four bytes"),
))
}
pub fn read_f64(&mut self) -> Option<f64> {
Some(f64::from_be_bytes(
pub fn read_f64(&mut self) -> Result<f64, FlvError> {
Ok(f64::from_be_bytes(
self.read(8)?.try_into().expect("eight bytes"),
))
}
}
impl<'a> Seek for FlvReader<'a> {
fn seek(&mut self, pos: SeekFrom) -> Result<u64> {
fn seek(&mut self, pos: SeekFrom) -> IoResult<u64> {
let newpos = match pos {
SeekFrom::Start(pos) => pos,
SeekFrom::Current(pos) => (self.position as i64 + pos)
.try_into()
.map_err(|e| Error::new(ErrorKind::InvalidInput, e))?,
.map_err(|e| IoError::new(ErrorKind::InvalidInput, e))?,
SeekFrom::End(pos) => (self.source.len() as i64 - pos)
.try_into()
.map_err(|e| Error::new(ErrorKind::InvalidInput, e))?,
.map_err(|e| IoError::new(ErrorKind::InvalidInput, e))?,
};
self.position = newpos as usize;

View File

@ -1,7 +1,8 @@
use crate::error::Error;
use crate::reader::FlvReader;
use std::io::Seek;
fn parse_string<'a>(reader: &mut FlvReader<'a>, is_long_string: bool) -> Option<&'a [u8]> {
fn parse_string<'a>(reader: &mut FlvReader<'a>, is_long_string: bool) -> Result<&'a [u8], Error> {
let length = if is_long_string {
reader.read_u32()?
} else {
@ -42,29 +43,29 @@ impl<'a> Value<'a> {
/// specification as to how they are to be decoded.
///
/// data_size is the size of the entire script data structure.
pub fn parse(reader: &mut FlvReader<'a>) -> Option<Self> {
pub fn parse(reader: &mut FlvReader<'a>) -> Result<Self, Error> {
let value_type = reader.read_u8()?;
match value_type {
0 => Some(Self::Number(reader.read_f64()?)),
1 => Some(Self::Boolean(reader.read_u8()? != 0)),
2 => Some(Self::String(parse_string(reader, false)?)),
0 => Ok(Self::Number(reader.read_f64()?)),
1 => Ok(Self::Boolean(reader.read_u8()? != 0)),
2 => Ok(Self::String(parse_string(reader, false)?)),
3 => {
let mut variables = vec![];
loop {
let terminator = reader.peek_u24()?;
if terminator == 9 {
reader.read_u24()?;
return Some(Self::Object(variables));
return Ok(Self::Object(variables));
}
variables.push(Variable::parse(reader)?);
}
}
4 => Some(Self::MovieClip(parse_string(reader, false)?)),
5 => Some(Self::Null),
6 => Some(Self::Undefined),
7 => Some(Self::Reference(reader.read_u16()?)),
4 => Ok(Self::MovieClip(parse_string(reader, false)?)),
5 => Ok(Self::Null),
6 => Ok(Self::Undefined),
7 => Ok(Self::Reference(reader.read_u16()?)),
8 => {
let length_hint = reader.read_u32()?;
let mut variables = Vec::with_capacity(length_hint as usize);
@ -73,7 +74,7 @@ impl<'a> Value<'a> {
let terminator = reader.peek_u24()?;
if terminator == 9 {
reader.read_u24()?;
return Some(Self::EcmaArray(variables));
return Ok(Self::EcmaArray(variables));
}
variables.push(Variable::parse(reader)?);
@ -87,14 +88,14 @@ impl<'a> Value<'a> {
variables.push(Variable::parse(reader)?);
}
Some(Self::StrictArray(variables))
Ok(Self::StrictArray(variables))
}
11 => Some(Self::Date {
11 => Ok(Self::Date {
unix_time: reader.read_f64()?,
local_offset: reader.read_i16()?,
}),
12 => Some(Self::LongString(parse_string(reader, true)?)),
_ => None,
12 => Ok(Self::LongString(parse_string(reader, true)?)),
_ => Err(Error::UnknownValueType),
}
}
}
@ -111,8 +112,8 @@ pub struct Variable<'a> {
}
impl<'a> Variable<'a> {
pub fn parse(reader: &mut FlvReader<'a>) -> Option<Self> {
Some(Self {
pub fn parse(reader: &mut FlvReader<'a>) -> Result<Self, Error> {
Ok(Self {
name: parse_string(reader, false)?,
data: Value::parse(reader)?,
})
@ -127,7 +128,7 @@ impl<'a> ScriptData<'a> {
///
/// No data size parameter is accepted; we parse until we reach an object
/// terminator, reach invalid data, or we run out of bytes in the reader.
pub fn parse(reader: &mut FlvReader<'a>, data_size: u32) -> Option<Self> {
pub fn parse(reader: &mut FlvReader<'a>, data_size: u32) -> Result<Self, Error> {
let start = reader.stream_position().expect("valid position");
let _trash = reader.read_u8()?;
let mut vars = vec![];
@ -136,13 +137,13 @@ impl<'a> ScriptData<'a> {
let cur_length = reader.stream_position().expect("valid position") - start;
if cur_length >= data_size as u64 {
// Terminators are commonly elided from script data blocks.
return Some(Self(vars));
return Ok(Self(vars));
}
let is_return = reader.peek_u24()?;
if is_return == 9 {
reader.read_u24()?;
return Some(Self(vars));
return Ok(Self(vars));
}
vars.push(Variable::parse(reader)?);
@ -160,7 +161,7 @@ mod tests {
let data = [0x00, 0x03, 0x01, 0x02, 0x03];
let mut reader = FlvReader::from_source(&data);
assert_eq!(parse_string(&mut reader, false), Some(&data[2..]));
assert_eq!(parse_string(&mut reader, false), Ok(&data[2..]));
}
#[test]
@ -168,7 +169,7 @@ mod tests {
let data = [0x00, 0x00, 0x00, 0x03, 0x01, 0x02, 0x03];
let mut reader = FlvReader::from_source(&data);
assert_eq!(parse_string(&mut reader, true), Some(&data[4..]));
assert_eq!(parse_string(&mut reader, true), Ok(&data[4..]));
}
#[test]
@ -176,7 +177,7 @@ mod tests {
let data = [0x00, 0x40, 0x28, 0x99, 0x99, 0x99, 0x99, 0x99, 0x9a];
let mut reader = FlvReader::from_source(&data);
assert_eq!(Value::parse(&mut reader), Some(Value::Number(12.3)));
assert_eq!(Value::parse(&mut reader), Ok(Value::Number(12.3)));
}
#[test]
@ -184,7 +185,7 @@ mod tests {
let data = [0x01, 0x01];
let mut reader = FlvReader::from_source(&data);
assert_eq!(Value::parse(&mut reader), Some(Value::Boolean(true)));
assert_eq!(Value::parse(&mut reader), Ok(Value::Boolean(true)));
}
#[test]
@ -194,7 +195,7 @@ mod tests {
assert_eq!(
Value::parse(&mut reader),
Some(Value::String(&[0x01, 0x02, 0x03]))
Ok(Value::String(&[0x01, 0x02, 0x03]))
);
}
@ -205,7 +206,7 @@ mod tests {
assert_eq!(
Value::parse(&mut reader),
Some(Value::MovieClip(&[0x01, 0x02, 0x03]))
Ok(Value::MovieClip(&[0x01, 0x02, 0x03]))
);
}
@ -216,7 +217,7 @@ mod tests {
assert_eq!(
Value::parse(&mut reader),
Some(Value::LongString(&[0x01, 0x02, 0x03]))
Ok(Value::LongString(&[0x01, 0x02, 0x03]))
);
}
@ -225,7 +226,7 @@ mod tests {
let data = [0x05];
let mut reader = FlvReader::from_source(&data);
assert_eq!(Value::parse(&mut reader), Some(Value::Null));
assert_eq!(Value::parse(&mut reader), Ok(Value::Null));
}
#[test]
@ -233,7 +234,7 @@ mod tests {
let data = [0x06];
let mut reader = FlvReader::from_source(&data);
assert_eq!(Value::parse(&mut reader), Some(Value::Undefined));
assert_eq!(Value::parse(&mut reader), Ok(Value::Undefined));
}
#[test]
@ -241,7 +242,7 @@ mod tests {
let data = [0x07, 0x24, 0x38];
let mut reader = FlvReader::from_source(&data);
assert_eq!(Value::parse(&mut reader), Some(Value::Reference(0x2438)));
assert_eq!(Value::parse(&mut reader), Ok(Value::Reference(0x2438)));
}
#[test]
@ -253,7 +254,7 @@ mod tests {
assert_eq!(
Value::parse(&mut reader),
Some(Value::Date {
Ok(Value::Date {
unix_time: 12.3,
local_offset: -2
})
@ -270,7 +271,7 @@ mod tests {
assert_eq!(
Value::parse(&mut reader),
Some(Value::Object(vec![
Ok(Value::Object(vec![
Variable {
name: &[0x01, 0x02, 0x03],
data: Value::Undefined
@ -293,7 +294,7 @@ mod tests {
assert_eq!(
Value::parse(&mut reader),
Some(Value::EcmaArray(vec![
Ok(Value::EcmaArray(vec![
Variable {
name: &[0x01, 0x02, 0x03],
data: Value::Undefined
@ -316,7 +317,7 @@ mod tests {
assert_eq!(
Value::parse(&mut reader),
Some(Value::EcmaArray(vec![
Ok(Value::EcmaArray(vec![
Variable {
name: &[0x01, 0x02, 0x03],
data: Value::Undefined
@ -339,7 +340,7 @@ mod tests {
assert_eq!(
Value::parse(&mut reader),
Some(Value::EcmaArray(vec![
Ok(Value::EcmaArray(vec![
Variable {
name: &[0x01, 0x02, 0x03],
data: Value::Undefined
@ -362,7 +363,7 @@ mod tests {
assert_eq!(
Value::parse(&mut reader),
Some(Value::StrictArray(vec![
Ok(Value::StrictArray(vec![
Variable {
name: &[0x01, 0x02, 0x03],
data: Value::Undefined
@ -385,7 +386,7 @@ mod tests {
assert_eq!(
ScriptData::parse(&mut reader, 16),
Some(ScriptData(vec![
Ok(ScriptData(vec![
Variable {
name: &[0x01, 0x02, 0x03],
data: Value::Undefined

View File

@ -1,3 +1,4 @@
use crate::error::Error;
use crate::FlvReader;
use std::io::Seek;
@ -20,7 +21,7 @@ pub enum SoundFormat {
}
impl TryFrom<u8> for SoundFormat {
type Error = ();
type Error = Error;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
@ -37,7 +38,7 @@ impl TryFrom<u8> for SoundFormat {
11 => Ok(Self::Speex),
14 => Ok(Self::MP38kHz),
15 => Ok(Self::DeviceSpecific),
_ => Err(()),
unk => Err(Error::UnknownAudioFormatType(unk)),
}
}
}
@ -52,7 +53,7 @@ pub enum SoundRate {
}
impl TryFrom<u8> for SoundRate {
type Error = ();
type Error = Error;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
@ -60,7 +61,7 @@ impl TryFrom<u8> for SoundRate {
1 => Ok(Self::R11_000),
2 => Ok(Self::R22_000),
3 => Ok(Self::R44_000),
_ => Err(()),
unk => Err(Error::UnknownAudioRate(unk)), //probably unreachable
}
}
}
@ -73,13 +74,13 @@ pub enum SoundSize {
}
impl TryFrom<u8> for SoundSize {
type Error = ();
type Error = Error;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0 => Ok(Self::Bits8),
1 => Ok(Self::Bits16),
_ => Err(()),
unk => Err(Error::UnknownAudioSampleSize(unk)), //probably unreachable
}
}
}
@ -92,13 +93,13 @@ pub enum SoundType {
}
impl TryFrom<u8> for SoundType {
type Error = ();
type Error = Error;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0 => Ok(Self::Mono),
1 => Ok(Self::Stereo),
_ => Err(()),
unk => Err(Error::UnknownAudioChannelCount(unk)), //probably unreachable
}
}
}
@ -126,38 +127,39 @@ impl<'a> AudioData<'a> {
/// returned as an array that must be provided to your audio decoder.
///
/// `data_size` is the size of the entire audio data structure, *including*
/// the header.
///
/// If `None` is yielded, the data stream is not a valid audio header.
pub fn parse(reader: &mut FlvReader<'a>, data_size: u32) -> Option<Self> {
/// the header. Errors are yielded if the `data_size` is too small for the
/// audio data present in the tag. This should not be confused for
/// `EndOfData` which indicates that we've read past the end of the whole
/// data stream.
pub fn parse(reader: &mut FlvReader<'a>, data_size: u32) -> Result<Self, Error> {
let start = reader.stream_position().expect("current position") as usize;
let format_spec = reader.read_u8()?;
let format = SoundFormat::try_from(format_spec >> 4).ok()?;
let rate = SoundRate::try_from((format_spec >> 2) & 0x03).ok()?;
let size = SoundSize::try_from((format_spec >> 1) & 0x01).ok()?;
let sound_type = SoundType::try_from(format_spec & 0x01).ok()?;
let format = SoundFormat::try_from(format_spec >> 4)?;
let rate = SoundRate::try_from((format_spec >> 2) & 0x03)?;
let size = SoundSize::try_from((format_spec >> 1) & 0x01)?;
let sound_type = SoundType::try_from(format_spec & 0x01)?;
let header_size = reader.stream_position().expect("current position") as usize - start;
if (data_size as usize) < header_size {
return None;
return Err(Error::ShortAudioBlock);
}
let data = reader.read(data_size as usize - header_size)?;
let data = match format {
SoundFormat::Aac => {
let aac_packet_type = data.first()?;
let aac_packet_type = data.first().ok_or(Error::ShortAudioBlock)?;
match aac_packet_type {
//TODO: The FLV spec says this is explained in ISO 14496-3.
0 => AudioDataType::AacSequenceHeader(&data[1..]),
1 => AudioDataType::AacRaw(&data[1..]),
_ => return None,
unk => return Err(Error::UnknownAacPacketType(*unk)),
}
}
_ => AudioDataType::Raw(data),
};
Some(AudioData {
Ok(AudioData {
format,
rate,
size,
@ -169,6 +171,7 @@ impl<'a> AudioData<'a> {
#[cfg(test)]
mod tests {
use crate::error::Error;
use crate::reader::FlvReader;
use crate::sound::{AudioData, AudioDataType, SoundFormat, SoundRate, SoundSize, SoundType};
@ -179,7 +182,7 @@ mod tests {
assert_eq!(
AudioData::parse(&mut reader, data.len() as u32),
Some(AudioData {
Ok(AudioData {
format: SoundFormat::Speex,
rate: SoundRate::R44_000,
size: SoundSize::Bits16,
@ -194,7 +197,10 @@ mod tests {
let data = [0xBF, 0x12, 0x34, 0x56, 0x78];
let mut reader = FlvReader::from_source(&data);
assert_eq!(AudioData::parse(&mut reader, 0), None);
assert_eq!(
AudioData::parse(&mut reader, 0),
Err(Error::ShortAudioBlock)
);
}
#[test]
@ -204,7 +210,7 @@ mod tests {
assert_eq!(
AudioData::parse(&mut reader, 2),
Some(AudioData {
Ok(AudioData {
format: SoundFormat::Speex,
rate: SoundRate::R44_000,
size: SoundSize::Bits16,
@ -221,7 +227,7 @@ mod tests {
assert_eq!(
AudioData::parse(&mut reader, data.len() as u32),
Some(AudioData {
Ok(AudioData {
format: SoundFormat::Aac,
rate: SoundRate::R44_000,
size: SoundSize::Bits8,
@ -236,6 +242,9 @@ mod tests {
let data = [0xAD, 0x02, 0x12, 0x34, 0x56, 0x78];
let mut reader = FlvReader::from_source(&data);
assert_eq!(AudioData::parse(&mut reader, data.len() as u32), None);
assert_eq!(
AudioData::parse(&mut reader, data.len() as u32),
Err(Error::UnknownAacPacketType(2))
);
}
}

View File

@ -1,3 +1,4 @@
use crate::error::Error;
use crate::reader::FlvReader;
use crate::script::ScriptData;
use crate::sound::AudioData;
@ -11,6 +12,12 @@ pub enum TagData<'a> {
Audio(AudioData<'a>) = 8,
Video(VideoData<'a>) = 9,
Script(ScriptData<'a>) = 18,
/// The tag data was recognized but could not be parsed due to an error.
///
/// The error contained will never be EndOfData; this should only be used
/// to flag unparseable data within an otherwise complete tag.
Invalid(Error),
}
#[derive(PartialEq, Debug, Clone)]
@ -30,14 +37,18 @@ impl<'a> Tag<'a> {
/// repeated calls to `parse` will yield further tags until the end of the
/// file.
///
/// `None` indicates that either:
/// Errors can be reported in one of two ways. If the header cannot be read
/// then this function returns the error normally. However, if the header
/// can be read, but the data inside the tag is corrupt, then a
/// TagData::Invalid will be returned with the inner error. EndOfData will
/// always be reported as a normal error and not as an invalid tag.
///
/// * There is not enough data in the reader to read the next tag
/// * The data in the reader is corrupt and not a valid FLV
///
/// If encountered, the position of the reader will be unchanged.
pub fn parse(reader: &mut FlvReader<'a>) -> Option<Self> {
let old_position = reader.stream_position().ok()?;
/// In the event of an invalid header or end-of-data error, the reader
/// position will be unchanged. Valid headers, with or without valid tag
/// data, will seek the reader to the start of the next tag. This allows
/// skipping past invalid tags.
pub fn parse(reader: &mut FlvReader<'a>) -> Result<Self, Error> {
let old_position = reader.stream_position()?;
let ret = (|| {
let _previous_tag_size = reader.read_u32()?;
@ -49,38 +60,57 @@ impl<'a> Tag<'a> {
let stream_id = reader.read_u24()?;
let timestamp = ((timestamp_extended as u32) << 24 | timestamp) as i32;
let data_position = reader.stream_position()?;
let new_position = data_position + data_size as u64;
let new_position = reader.stream_position().ok()? + data_size as u64;
Some((
Ok((
match tag_type {
8 => Tag {
timestamp,
stream_id,
data: TagData::Audio(AudioData::parse(reader, data_size)?),
data: match AudioData::parse(reader, data_size) {
Ok(data) => TagData::Audio(data),
Err(Error::EndOfData) => return Err(Error::EndOfData),
Err(e) => TagData::Invalid(e),
},
},
9 => Tag {
timestamp,
stream_id,
data: TagData::Video(VideoData::parse(reader, data_size)?),
data: match VideoData::parse(reader, data_size) {
Ok(data) => TagData::Video(data),
Err(Error::EndOfData) => return Err(Error::EndOfData),
Err(e) => TagData::Invalid(e),
},
},
18 => Tag {
timestamp,
stream_id,
data: TagData::Script(ScriptData::parse(reader, data_size)?),
data: match ScriptData::parse(reader, data_size) {
Ok(data) => TagData::Script(data),
Err(Error::EndOfData) => return Err(Error::EndOfData),
Err(e) => TagData::Invalid(e),
},
},
unk => Tag {
timestamp,
stream_id,
data: TagData::Invalid(Error::UnknownTagType(unk)),
},
_ => return None,
},
new_position,
))
})();
if let Some((tag, new_position)) = ret {
reader.seek(SeekFrom::Start(new_position)).ok()?;
Some(tag)
} else {
reader.seek(SeekFrom::Start(old_position)).ok()?;
None
match ret {
Ok((tag, new_position)) => {
reader.seek(SeekFrom::Start(new_position))?;
Ok(tag)
}
Err(e) => {
reader.seek(SeekFrom::Start(old_position))?;
Err(e)
}
}
}
@ -89,18 +119,17 @@ impl<'a> Tag<'a> {
/// FLV files are constructed as a list of tags. Back pointers to prior
/// tags are provided to allow reverse seeking. This function ignores the
/// tag at the current location and skips back to prior data in the file.
pub fn skip_back(reader: &mut FlvReader<'a>) -> Option<()> {
pub fn skip_back(reader: &mut FlvReader<'a>) -> Result<(), Error> {
let previous_tag_size = reader.read_u32()?;
reader
.seek(SeekFrom::Current(-(previous_tag_size as i64)))
.ok()?;
reader.seek(SeekFrom::Current(-(previous_tag_size as i64)))?;
Some(())
Ok(())
}
}
#[cfg(test)]
mod tests {
use crate::error::Error;
use crate::reader::FlvReader;
use crate::script::{ScriptData, Value, Variable};
use crate::sound::{AudioData, AudioDataType, SoundFormat, SoundRate, SoundSize, SoundType};
@ -117,7 +146,7 @@ mod tests {
assert_eq!(
Tag::parse(&mut reader),
Some(Tag {
Ok(Tag {
timestamp: 0,
stream_id: 0x5000,
data: TagData::Audio(AudioData {
@ -131,6 +160,24 @@ mod tests {
)
}
#[test]
fn read_tag_sounddata_invalid() {
let data = [
0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50,
0x00, 0xCA, 0x12, 0x34, 0x56, 0x78,
];
let mut reader = FlvReader::from_source(&data);
assert_eq!(
Tag::parse(&mut reader),
Ok(Tag {
timestamp: 0,
stream_id: 0x5000,
data: TagData::Invalid(Error::UnknownAudioFormatType(0x0C))
})
)
}
#[test]
fn read_tag_videodata() {
let data = [
@ -141,7 +188,7 @@ mod tests {
assert_eq!(
Tag::parse(&mut reader),
Some(Tag {
Ok(Tag {
timestamp: 0,
stream_id: 0x5000,
data: TagData::Video(VideoData {
@ -164,7 +211,7 @@ mod tests {
assert_eq!(
Tag::parse(&mut reader),
Some(Tag {
Ok(Tag {
timestamp: 0,
stream_id: 0x5000,
data: TagData::Script(ScriptData(vec![
@ -211,7 +258,7 @@ mod tests {
assert_eq!(
Tag::parse(&mut reader),
Some(Tag {
Ok(Tag {
timestamp: 0,
stream_id: 0,
data: TagData::Script(ScriptData(vec![Variable {

View File

@ -1,3 +1,4 @@
use crate::error::Error;
use crate::reader::FlvReader;
use std::io::Seek;
@ -12,7 +13,7 @@ pub enum FrameType {
}
impl TryFrom<u8> for FrameType {
type Error = ();
type Error = Error;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
@ -21,7 +22,7 @@ impl TryFrom<u8> for FrameType {
3 => Ok(Self::InterframeDisposable),
4 => Ok(Self::Generated),
5 => Ok(Self::CommandFrame),
_ => Err(()),
unk => Err(Error::UnknownVideoFrameType(unk)),
}
}
}
@ -39,7 +40,7 @@ pub enum CodecId {
}
impl TryFrom<u8> for CodecId {
type Error = ();
type Error = Error;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
@ -50,7 +51,7 @@ impl TryFrom<u8> for CodecId {
5 => Ok(Self::On2Vp6Alpha),
6 => Ok(Self::ScreenVideo2),
7 => Ok(Self::Avc),
_ => Err(()),
unk => Err(Error::UnknownVideoCodec(unk)),
}
}
}
@ -63,13 +64,13 @@ pub enum CommandFrame {
}
impl TryFrom<u8> for CommandFrame {
type Error = ();
type Error = Error;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0 => Ok(Self::StartOfClientSideSeek),
1 => Ok(Self::EndOfClientSideSeek),
_ => Err(()),
unk => Err(Error::UnknownVideoCommandType(unk)),
}
}
}
@ -100,28 +101,29 @@ impl<'a> VideoData<'a> {
/// returned as an array that must be provided to your video decoder.
///
/// `data_size` is the size of the entire video data structure, *including*
/// the header.
///
/// If `None` is yielded, the data stream is not a valid video header.
pub fn parse(reader: &mut FlvReader<'a>, data_size: u32) -> Option<Self> {
/// the header. Errors are yielded if the `data_size` is too small for the
/// video data present in the tag. This should not be confused for
/// `EndOfData` which indicates that we've read past the end of the whole
/// data stream.
pub fn parse(reader: &mut FlvReader<'a>, data_size: u32) -> Result<Self, Error> {
let start = reader.stream_position().expect("current position") as usize;
let format_spec = reader.read_u8()?;
let frame_type = FrameType::try_from(format_spec >> 4).ok()?;
let codec_id = CodecId::try_from(format_spec & 0x0F).ok()?;
let frame_type = FrameType::try_from(format_spec >> 4)?;
let codec_id = CodecId::try_from(format_spec & 0x0F)?;
let header_size = reader.stream_position().expect("current position") as usize - start;
if (data_size as usize) < header_size {
return None;
return Err(Error::ShortVideoBlock);
}
let data = reader.read(data_size as usize - header_size)?;
let packet = match (frame_type, codec_id) {
(FrameType::CommandFrame, _) => {
VideoPacket::CommandFrame(CommandFrame::try_from(*data.first()?).ok()?)
}
(FrameType::CommandFrame, _) => VideoPacket::CommandFrame(CommandFrame::try_from(
*data.first().ok_or(Error::ShortVideoBlock)?,
)?),
(_, CodecId::Avc) => {
let bytes = data.get(1..4)?;
let bytes = data.get(1..4).ok_or(Error::ShortVideoBlock)?;
let is_negative = bytes[0] & 0x80 != 0;
let composition_time_offset = i32::from_be_bytes([
if is_negative { 0xFF } else { 0x00 },
@ -130,20 +132,20 @@ impl<'a> VideoData<'a> {
bytes[2],
]);
match *data.first()? {
match *data.first().ok_or(Error::ShortVideoBlock)? {
0 => VideoPacket::AvcSequenceHeader(&data[4..]),
1 => VideoPacket::AvcNalu {
composition_time_offset,
data: &data[4..],
},
2 => VideoPacket::AvcEndOfSequence,
_ => return None,
unk => return Err(Error::UnknownAvcPacketType(unk)),
}
}
(_, _) => VideoPacket::Data(data),
};
Some(VideoData {
Ok(VideoData {
frame_type,
codec_id,
data: packet,
@ -153,6 +155,7 @@ impl<'a> VideoData<'a> {
#[cfg(test)]
mod tests {
use crate::error::Error;
use crate::reader::FlvReader;
use crate::video::{CodecId, FrameType, VideoData, VideoPacket};
@ -163,7 +166,7 @@ mod tests {
assert_eq!(
VideoData::parse(&mut reader, data.len() as u32),
Some(VideoData {
Ok(VideoData {
frame_type: FrameType::Keyframe,
codec_id: CodecId::SorensonH263,
data: VideoPacket::Data(&[0x12, 0x34, 0x56, 0x78])
@ -176,7 +179,10 @@ mod tests {
let data = [0x12, 0x12, 0x34, 0x56, 0x78];
let mut reader = FlvReader::from_source(&data);
assert_eq!(VideoData::parse(&mut reader, 0), None);
assert_eq!(
VideoData::parse(&mut reader, 0),
Err(Error::ShortVideoBlock)
);
}
#[test]
@ -186,7 +192,7 @@ mod tests {
assert_eq!(
VideoData::parse(&mut reader, 2),
Some(VideoData {
Ok(VideoData {
frame_type: FrameType::Keyframe,
codec_id: CodecId::SorensonH263,
data: VideoPacket::Data(&[0x12])
@ -201,7 +207,7 @@ mod tests {
assert_eq!(
VideoData::parse(&mut reader, data.len() as u32),
Some(VideoData {
Ok(VideoData {
frame_type: FrameType::Keyframe,
codec_id: CodecId::Avc,
data: VideoPacket::AvcSequenceHeader(&[0x12, 0x34, 0x56, 0x78])
@ -216,7 +222,7 @@ mod tests {
assert_eq!(
VideoData::parse(&mut reader, data.len() as u32),
Some(VideoData {
Ok(VideoData {
frame_type: FrameType::Keyframe,
codec_id: CodecId::Avc,
data: VideoPacket::AvcNalu {
@ -234,7 +240,7 @@ mod tests {
assert_eq!(
VideoData::parse(&mut reader, data.len() as u32),
Some(VideoData {
Ok(VideoData {
frame_type: FrameType::Keyframe,
codec_id: CodecId::Avc,
data: VideoPacket::AvcNalu {
@ -252,7 +258,7 @@ mod tests {
assert_eq!(
VideoData::parse(&mut reader, data.len() as u32),
Some(VideoData {
Ok(VideoData {
frame_type: FrameType::Keyframe,
codec_id: CodecId::Avc,
data: VideoPacket::AvcEndOfSequence
@ -265,6 +271,9 @@ mod tests {
let data = [0x17, 0xFF, 0xFF, 0xFF, 0xFE, 0x12, 0x34, 0x56, 0x78];
let mut reader = FlvReader::from_source(&data);
assert_eq!(VideoData::parse(&mut reader, data.len() as u32), None);
assert_eq!(
VideoData::parse(&mut reader, data.len() as u32),
Err(Error::UnknownAvcPacketType(0xFF))
);
}
}