From 0f176484ea4589db37d118db7e87721f171ee11a Mon Sep 17 00:00:00 2001 From: David Wendt Date: Sat, 22 Apr 2023 15:52:23 -0400 Subject: [PATCH] flv: Refactor tag parsing to use error codes instead of `None`. --- Cargo.lock | 1 + flv/Cargo.toml | 3 +- flv/src/error.rs | 115 ++++++++++++++++++++++++++++++++++++++++++++++ flv/src/header.rs | 32 +++++++------ flv/src/lib.rs | 3 ++ flv/src/reader.rs | 62 +++++++++++++++---------- flv/src/script.rs | 75 +++++++++++++++--------------- flv/src/sound.rs | 59 ++++++++++++++---------- flv/src/tag.rs | 105 ++++++++++++++++++++++++++++++------------ flv/src/video.rs | 65 +++++++++++++++----------- 10 files changed, 363 insertions(+), 157 deletions(-) create mode 100644 flv/src/error.rs diff --git a/Cargo.lock b/Cargo.lock index ed76b66e4..403b4d313 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1650,6 +1650,7 @@ name = "flv-rs" version = "0.1.0" dependencies = [ "bitflags 2.3.2", + "thiserror", ] [[package]] diff --git a/flv/Cargo.toml b/flv/Cargo.toml index dd6c52ded..34bfc6c3b 100644 --- a/flv/Cargo.toml +++ b/flv/Cargo.toml @@ -6,4 +6,5 @@ edition = "2021" license = "MIT OR Apache-2.0" [dependencies] -bitflags = "2.0.0" \ No newline at end of file +bitflags = "2.0.0" +thiserror = "1.0" \ No newline at end of file diff --git a/flv/src/error.rs b/flv/src/error.rs new file mode 100644 index 000000000..adac15078 --- /dev/null +++ b/flv/src/error.rs @@ -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 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())), + } + } +} diff --git a/flv/src/header.rs b/flv/src/header.rs index 6090ca311..ab077c6de 100644 --- a/flv/src/header.rs +++ b/flv/src/header.rs @@ -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 { - 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 { + 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 diff --git a/flv/src/lib.rs b/flv/src/lib.rs index 53d62add6..1fb7c27bd 100644 --- a/flv/src/lib.rs +++ b/flv/src/lib.rs @@ -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}; diff --git a/flv/src/reader.rs b/flv/src/reader.rs index edece1f52..97fdab588 100644 --- a/flv/src/reader.rs +++ b/flv/src/reader.rs @@ -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 { - Some(self.read(1)?[0]) + pub fn read_u8(&mut self) -> Result { + Ok(self.read(1)?[0]) } - pub fn read_u16(&mut self) -> Option { - Some(u16::from_be_bytes( + pub fn read_u16(&mut self) -> Result { + Ok(u16::from_be_bytes( self.read(2)?.try_into().expect("two bytes"), )) } - pub fn read_i16(&mut self) -> Option { - Some(i16::from_be_bytes( + pub fn read_i16(&mut self) -> Result { + Ok(i16::from_be_bytes( self.read(2)?.try_into().expect("two bytes"), )) } - pub fn read_u24(&mut self) -> Option { + pub fn read_u24(&mut self) -> Result { 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 { + pub fn peek_u24(&mut self) -> Result { 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 { - Some(u32::from_be_bytes( + pub fn read_u32(&mut self) -> Result { + Ok(u32::from_be_bytes( self.read(4)?.try_into().expect("four bytes"), )) } - pub fn read_f64(&mut self) -> Option { - Some(f64::from_be_bytes( + pub fn read_f64(&mut self) -> Result { + 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 { + fn seek(&mut self, pos: SeekFrom) -> IoResult { 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; diff --git a/flv/src/script.rs b/flv/src/script.rs index 6cb435b3b..c701f8dfd 100644 --- a/flv/src/script.rs +++ b/flv/src/script.rs @@ -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 { + pub fn parse(reader: &mut FlvReader<'a>) -> Result { 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 { - Some(Self { + pub fn parse(reader: &mut FlvReader<'a>) -> Result { + 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 { + pub fn parse(reader: &mut FlvReader<'a>, data_size: u32) -> Result { 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 diff --git a/flv/src/sound.rs b/flv/src/sound.rs index 4a9559a8d..8a7ed409a 100644 --- a/flv/src/sound.rs +++ b/flv/src/sound.rs @@ -1,3 +1,4 @@ +use crate::error::Error; use crate::FlvReader; use std::io::Seek; @@ -20,7 +21,7 @@ pub enum SoundFormat { } impl TryFrom for SoundFormat { - type Error = (); + type Error = Error; fn try_from(value: u8) -> Result { match value { @@ -37,7 +38,7 @@ impl TryFrom 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 for SoundRate { - type Error = (); + type Error = Error; fn try_from(value: u8) -> Result { match value { @@ -60,7 +61,7 @@ impl TryFrom 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 for SoundSize { - type Error = (); + type Error = Error; fn try_from(value: u8) -> Result { 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 for SoundType { - type Error = (); + type Error = Error; fn try_from(value: u8) -> Result { 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 { + /// 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 { 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)) + ); } } diff --git a/flv/src/tag.rs b/flv/src/tag.rs index ebf129942..df1e04b35 100644 --- a/flv/src/tag.rs +++ b/flv/src/tag.rs @@ -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 { - 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 { + 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 { diff --git a/flv/src/video.rs b/flv/src/video.rs index 30a143fb5..216873ade 100644 --- a/flv/src/video.rs +++ b/flv/src/video.rs @@ -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 for FrameType { - type Error = (); + type Error = Error; fn try_from(value: u8) -> Result { match value { @@ -21,7 +22,7 @@ impl TryFrom 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 for CodecId { - type Error = (); + type Error = Error; fn try_from(value: u8) -> Result { match value { @@ -50,7 +51,7 @@ impl TryFrom 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 for CommandFrame { - type Error = (); + type Error = Error; fn try_from(value: u8) -> Result { 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 { + /// 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 { 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)) + ); } }