diff --git a/src/attribute.rs b/src/attribute.rs index 91091c5..b74742b 100644 --- a/src/attribute.rs +++ b/src/attribute.rs @@ -1,18 +1,19 @@ -use std::fmt; -use std::str::{self, FromStr}; -use std::time::Duration; -use std::u8; -use trackable::error::ErrorKindExt; +use std::collections::HashSet; +use std::str; -use {Error, ErrorKind, Result}; +use {ErrorKind, Result}; #[derive(Debug)] pub struct AttributePairs<'a> { input: &'a str, + visited_keys: HashSet<&'a str>, } impl<'a> AttributePairs<'a> { pub fn parse(input: &'a str) -> Self { - AttributePairs { input } + AttributePairs { + input, + visited_keys: HashSet::new(), + } } fn parse_name(&mut self) -> Result<&'a str> { @@ -59,183 +60,17 @@ impl<'a> Iterator for AttributePairs<'a> { } let result = || -> Result<(&'a str, &'a str)> { - // TODO: check key duplications let key = track!(self.parse_name())?; + track_assert!( + self.visited_keys.insert(key), + ErrorKind::InvalidInput, + "Duplicate attribute key: {:?}", + key + ); + let value = self.parse_raw_value(); Ok((key, value)) }(); Some(result) } } - -// TODO: export and rename -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct QuotedString(String); -impl QuotedString { - pub fn new>(s: T) -> Result { - // TODO: validate - Ok(QuotedString(s.into())) - } - pub fn as_str(&self) -> &str { - &self.0 - } -} -impl fmt::Display for QuotedString { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{:?}", self.0) - } -} -impl FromStr for QuotedString { - type Err = Error; - fn from_str(s: &str) -> Result { - let len = s.len(); - let bytes = s.as_bytes(); - track_assert!(len >= 2, ErrorKind::InvalidInput); - track_assert_eq!(bytes[0], b'"', ErrorKind::InvalidInput); - track_assert_eq!(bytes[len - 1], b'"', ErrorKind::InvalidInput); - - let s = unsafe { str::from_utf8_unchecked(&bytes[1..len - 1]) }; - track_assert!( - s.chars().all(|c| c != '\r' && c != '\n' && c != '"'), - ErrorKind::InvalidInput, - "{:?}", - s - ); - Ok(QuotedString(s.to_owned())) - } -} - -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct HexadecimalSequence(Vec); -impl HexadecimalSequence { - pub fn new>>(v: T) -> Self { - HexadecimalSequence(v.into()) - } -} -impl fmt::Display for HexadecimalSequence { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "0x")?; - for b in &self.0 { - write!(f, "{:02x}", b)?; - } - Ok(()) - } -} -impl FromStr for HexadecimalSequence { - type Err = Error; - fn from_str(s: &str) -> Result { - track_assert!( - s.starts_with("0x") || s.starts_with("0X"), - ErrorKind::InvalidInput - ); - track_assert!(s.len() % 2 == 0, ErrorKind::InvalidInput); - - let mut v = Vec::with_capacity(s.len() / 2 - 1); - for c in s.as_bytes().chunks(2).skip(1) { - let d = track!(str::from_utf8(c).map_err(|e| ErrorKind::InvalidInput.cause(e)))?; - let b = - track!(u8::from_str_radix(d, 16).map_err(|e| ErrorKind::InvalidInput.cause(e)))?; - v.push(b); - } - Ok(HexadecimalSequence(v)) - } -} - -// TODO: delete -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct DecimalInteger(pub u64); -impl fmt::Display for DecimalInteger { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.0) - } -} -impl FromStr for DecimalInteger { - type Err = Error; - fn from_str(s: &str) -> Result { - let n = track!(s.parse().map_err(|e| ErrorKind::InvalidInput.cause(e)))?; - Ok(DecimalInteger(n)) - } -} - -#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)] -pub struct DecimalFloatingPoint(f64); -impl DecimalFloatingPoint { - pub fn to_duration(&self) -> Duration { - let secs = self.0 as u64; - let nanos = (self.0.fract() * 1_000_000_000.0) as u32; - Duration::new(secs, nanos) - } - pub fn from_duration(duration: Duration) -> Self { - let n = (duration.as_secs() as f64) + (duration.subsec_nanos() as f64 / 1_000_000_000.0); - DecimalFloatingPoint(n) - } - pub fn as_f64(&self) -> f64 { - self.0 - } -} -impl Eq for DecimalFloatingPoint {} -impl fmt::Display for DecimalFloatingPoint { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.0.fmt(f) - } -} -impl FromStr for DecimalFloatingPoint { - type Err = Error; - fn from_str(s: &str) -> Result { - track_assert!( - s.chars().all(|c| match c { - '0'...'9' | '.' => true, - _ => false, - }), - ErrorKind::InvalidInput - ); - let n = track!(s.parse().map_err(|e| ErrorKind::InvalidInput.cause(e)))?; - Ok(DecimalFloatingPoint(n)) - } -} - -#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)] -pub struct SignedDecimalFloatingPoint(pub f64); -impl Eq for SignedDecimalFloatingPoint {} -impl fmt::Display for SignedDecimalFloatingPoint { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.0.fmt(f) - } -} -impl FromStr for SignedDecimalFloatingPoint { - type Err = Error; - fn from_str(s: &str) -> Result { - track_assert!( - s.chars().all(|c| match c { - '0'...'9' | '.' | '-' => true, - _ => false, - }), - ErrorKind::InvalidInput - ); - let n = track!(s.parse().map_err(|e| ErrorKind::InvalidInput.cause(e)))?; - Ok(SignedDecimalFloatingPoint(n)) - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub struct DecimalResolution { - pub width: usize, - pub height: usize, -} -impl fmt::Display for DecimalResolution { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}x{}", self.width, self.height) - } -} -impl FromStr for DecimalResolution { - type Err = Error; - fn from_str(s: &str) -> Result { - let mut tokens = s.splitn(2, 'x'); - let width = tokens.next().expect("Never fails"); - let height = track_assert_some!(tokens.next(), ErrorKind::InvalidInput); - Ok(DecimalResolution { - width: track!(width.parse().map_err(|e| ErrorKind::InvalidInput.cause(e)))?, - height: track!(height.parse().map_err(|e| ErrorKind::InvalidInput.cause(e)))?, - }) - } -} diff --git a/src/tag/master_playlist.rs b/src/tag/master_playlist.rs index 4f55e29..289c7d5 100644 --- a/src/tag/master_playlist.rs +++ b/src/tag/master_playlist.rs @@ -2,11 +2,10 @@ use std::fmt; use std::str::FromStr; use {Error, ErrorKind, Result}; -use attribute::{AttributePairs, DecimalFloatingPoint, DecimalInteger, DecimalResolution, - QuotedString}; -use types::{ClosedCaptions, DecryptionKey, HdcpLevel, InStreamId, MediaType, ProtocolVersion, - SessionData, SingleLineString}; -use super::parse_yes_or_no; +use attribute::AttributePairs; +use types::{ClosedCaptions, DecimalFloatingPoint, DecimalResolution, DecryptionKey, HdcpLevel, + InStreamId, MediaType, ProtocolVersion, QuotedString, SessionData, SingleLineString}; +use super::{parse_yes_or_no, parse_u64}; /// [4.3.4.1. EXT-X-MEDIA] /// @@ -192,7 +191,7 @@ impl FromStr for ExtXMedia { "FORCED" => forced = Some(track!(parse_yes_or_no(value))?), "INSTREAM-ID" => { let s: QuotedString = track!(value.parse())?; - instream_id = Some(track!(s.as_str().parse())?); + instream_id = Some(track!(s.parse())?); } "CHARACTERISTICS" => characteristics = Some(track!(value.parse())?), "CHANNELS" => channels = Some(track!(value.parse())?), @@ -240,8 +239,8 @@ impl FromStr for ExtXMedia { #[derive(Debug, Clone, PartialEq, Eq)] pub struct ExtXStreamInf { uri: SingleLineString, - bandwidth: DecimalInteger, - average_bandwidth: Option, + bandwidth: u64, + average_bandwidth: Option, codecs: Option, resolution: Option, frame_rate: Option, @@ -255,7 +254,7 @@ impl ExtXStreamInf { pub(crate) const PREFIX: &'static str = "#EXT-X-STREAM-INF:"; /// Makes a new `ExtXStreamInf` tag. - pub fn new(uri: SingleLineString, bandwidth: DecimalInteger) -> Self { + pub fn new(uri: SingleLineString, bandwidth: u64) -> Self { ExtXStreamInf { uri, bandwidth, @@ -277,12 +276,12 @@ impl ExtXStreamInf { } /// Returns the peak segment bit rate of the variant stream. - pub fn bandwidth(&self) -> DecimalInteger { + pub fn bandwidth(&self) -> u64 { self.bandwidth } /// Returns the average segment bit rate of the variant stream. - pub fn average_bandwidth(&self) -> Option { + pub fn average_bandwidth(&self) -> Option { self.average_bandwidth } @@ -392,8 +391,8 @@ impl FromStr for ExtXStreamInf { for attr in attrs { let (key, value) = track!(attr)?; match key { - "BANDWIDTH" => bandwidth = Some(track!(value.parse())?), - "AVERAGE-BANDWIDTH" => average_bandwidth = Some(track!(value.parse())?), + "BANDWIDTH" => bandwidth = Some(track!(parse_u64(value))?), + "AVERAGE-BANDWIDTH" => average_bandwidth = Some(track!(parse_u64(value))?), "CODECS" => codecs = Some(track!(value.parse())?), "RESOLUTION" => resolution = Some(track!(value.parse())?), "FRAME-RATE" => frame_rate = Some(track!(value.parse())?), @@ -431,8 +430,8 @@ impl FromStr for ExtXStreamInf { #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct ExtXIFrameStreamInf { uri: QuotedString, - bandwidth: DecimalInteger, - average_bandwidth: Option, + bandwidth: u64, + average_bandwidth: Option, codecs: Option, resolution: Option, hdcp_level: Option, @@ -442,7 +441,7 @@ impl ExtXIFrameStreamInf { pub(crate) const PREFIX: &'static str = "#EXT-X-I-FRAME-STREAM-INF:"; /// Makes a new `ExtXIFrameStreamInf` tag. - pub fn new(uri: QuotedString, bandwidth: DecimalInteger) -> Self { + pub fn new(uri: QuotedString, bandwidth: u64) -> Self { ExtXIFrameStreamInf { uri, bandwidth, @@ -460,12 +459,12 @@ impl ExtXIFrameStreamInf { } /// Returns the peak segment bit rate of the variant stream. - pub fn bandwidth(&self) -> DecimalInteger { + pub fn bandwidth(&self) -> u64 { self.bandwidth } /// Returns the average segment bit rate of the variant stream. - pub fn average_bandwidth(&self) -> Option { + pub fn average_bandwidth(&self) -> Option { self.average_bandwidth } @@ -534,8 +533,8 @@ impl FromStr for ExtXIFrameStreamInf { let (key, value) = track!(attr)?; match key { "URI" => uri = Some(track!(value.parse())?), - "BANDWIDTH" => bandwidth = Some(track!(value.parse())?), - "AVERAGE-BANDWIDTH" => average_bandwidth = Some(track!(value.parse())?), + "BANDWIDTH" => bandwidth = Some(track!(parse_u64(value))?), + "AVERAGE-BANDWIDTH" => average_bandwidth = Some(track!(parse_u64(value))?), "CODECS" => codecs = Some(track!(value.parse())?), "RESOLUTION" => resolution = Some(track!(value.parse())?), "HDCP-LEVEL" => hdcp_level = Some(track!(value.parse())?), @@ -708,8 +707,7 @@ impl FromStr for ExtXSessionKey { #[cfg(test)] mod test { - use attribute::HexadecimalSequence; - use types::EncryptionMethod; + use types::{EncryptionMethod, InitializationVector}; use super::*; #[test] @@ -723,7 +721,7 @@ mod test { #[test] fn ext_x_stream_inf() { - let tag = ExtXStreamInf::new(SingleLineString::new("foo").unwrap(), DecimalInteger(1000)); + let tag = ExtXStreamInf::new(SingleLineString::new("foo").unwrap(), 1000); let text = "#EXT-X-STREAM-INF:BANDWIDTH=1000\nfoo"; assert_eq!(text.parse().ok(), Some(tag.clone())); assert_eq!(tag.to_string(), text); @@ -732,7 +730,7 @@ mod test { #[test] fn ext_x_i_frame_stream_inf() { - let tag = ExtXIFrameStreamInf::new(quoted_string("foo"), DecimalInteger(1000)); + let tag = ExtXIFrameStreamInf::new(quoted_string("foo"), 1000); let text = r#"#EXT-X-I-FRAME-STREAM-INF:URI="foo",BANDWIDTH=1000"#; assert_eq!(text.parse().ok(), Some(tag.clone())); assert_eq!(tag.to_string(), text); @@ -773,11 +771,14 @@ mod test { let tag = ExtXSessionKey::new(DecryptionKey { method: EncryptionMethod::Aes128, uri: quoted_string("foo"), - iv: Some(HexadecimalSequence::new(vec![0, 1, 2])), + iv: Some(InitializationVector([ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 + ])), key_format: None, key_format_versions: None, }); - let text = r#"#EXT-X-SESSION-KEY:METHOD=AES-128,URI="foo",IV=0x000102"#; + let text = + r#"#EXT-X-SESSION-KEY:METHOD=AES-128,URI="foo",IV=0x000102030405060708090a0b0c0d0e0f"#; assert_eq!(text.parse().ok(), Some(tag.clone())); assert_eq!(tag.to_string(), text); assert_eq!(tag.requires_version(), ProtocolVersion::V2); diff --git a/src/tag/media_or_master_playlist.rs b/src/tag/media_or_master_playlist.rs index d3b1be6..879969d 100644 --- a/src/tag/media_or_master_playlist.rs +++ b/src/tag/media_or_master_playlist.rs @@ -2,8 +2,8 @@ use std::fmt; use std::str::FromStr; use {Error, ErrorKind, Result}; -use attribute::{AttributePairs, SignedDecimalFloatingPoint}; -use types::ProtocolVersion; +use attribute::AttributePairs; +use types::{ProtocolVersion, SignedDecimalFloatingPoint}; use super::parse_yes_or_no; /// [4.3.5.1. EXT-X-INDEPENDENT-SEGMENTS] @@ -128,13 +128,13 @@ mod test { #[test] fn ext_x_start() { - let tag = ExtXStart::new(SignedDecimalFloatingPoint(-1.23)); + let tag = ExtXStart::new(SignedDecimalFloatingPoint::new(-1.23).unwrap()); let text = "#EXT-X-START:TIME-OFFSET=-1.23"; assert_eq!(text.parse().ok(), Some(tag)); assert_eq!(tag.to_string(), text); assert_eq!(tag.requires_version(), ProtocolVersion::V1); - let tag = ExtXStart::with_precise(SignedDecimalFloatingPoint(1.23), YesOrNo::Yes); + let tag = ExtXStart::with_precise(SignedDecimalFloatingPoint::new(1.23).unwrap(), true); let text = "#EXT-X-START:TIME-OFFSET=1.23,PRECISE=YES"; assert_eq!(text.parse().ok(), Some(tag)); assert_eq!(tag.to_string(), text); diff --git a/src/tag/media_segment.rs b/src/tag/media_segment.rs index 8799d1c..be75430 100644 --- a/src/tag/media_segment.rs +++ b/src/tag/media_segment.rs @@ -6,8 +6,9 @@ use chrono::{DateTime, FixedOffset, NaiveDate}; use trackable::error::ErrorKindExt; use {Error, ErrorKind, Result}; -use attribute::{AttributePairs, DecimalFloatingPoint, QuotedString}; -use types::{ByteRange, DecryptionKey, ProtocolVersion, SingleLineString}; +use attribute::AttributePairs; +use types::{ByteRange, DecimalFloatingPoint, DecryptionKey, ProtocolVersion, QuotedString, + SingleLineString}; /// [4.3.2.1. EXTINF] /// @@ -434,14 +435,14 @@ impl FromStr for ExtXDateRange { "START-DATE" => { let s: QuotedString = track!(value.parse())?; start_date = Some(track!( - NaiveDate::parse_from_str(s.as_str(), "%Y-%m-%d") + NaiveDate::parse_from_str(&s, "%Y-%m-%d") .map_err(|e| ErrorKind::InvalidInput.cause(e)) )?); } "END-DATE" => { let s: QuotedString = track!(value.parse())?; end_date = Some(track!( - NaiveDate::parse_from_str(s.as_str(), "%Y-%m-%d") + NaiveDate::parse_from_str(&s, "%Y-%m-%d") .map_err(|e| ErrorKind::InvalidInput.cause(e)) )?); } @@ -496,8 +497,7 @@ impl FromStr for ExtXDateRange { mod test { use std::time::Duration; - use attribute::HexadecimalSequence; - use types::EncryptionMethod; + use types::{EncryptionMethod, InitializationVector}; use super::*; #[test] @@ -571,11 +571,13 @@ mod test { let tag = ExtXKey::new(DecryptionKey { method: EncryptionMethod::Aes128, uri: QuotedString::new("foo").unwrap(), - iv: Some(HexadecimalSequence::new(vec![0, 1, 2])), + iv: Some(InitializationVector([ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 + ])), key_format: None, key_format_versions: None, }); - let text = r#"#EXT-X-KEY:METHOD=AES-128,URI="foo",IV=0x000102"#; + let text = r#"#EXT-X-KEY:METHOD=AES-128,URI="foo",IV=0x000102030405060708090a0b0c0d0e0f"#; assert_eq!(text.parse().ok(), Some(tag.clone())); assert_eq!(tag.to_string(), text); assert_eq!(tag.requires_version(), ProtocolVersion::V2); @@ -583,11 +585,13 @@ mod test { let tag = ExtXKey::new(DecryptionKey { method: EncryptionMethod::Aes128, uri: QuotedString::new("foo").unwrap(), - iv: Some(HexadecimalSequence::new(vec![0, 1, 2])), + iv: Some(InitializationVector([ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 + ])), key_format: Some(QuotedString::new("baz").unwrap()), key_format_versions: None, }); - let text = r#"#EXT-X-KEY:METHOD=AES-128,URI="foo",IV=0x000102,KEYFORMAT="baz""#; + let text = r#"#EXT-X-KEY:METHOD=AES-128,URI="foo",IV=0x000102030405060708090a0b0c0d0e0f,KEYFORMAT="baz""#; assert_eq!(text.parse().ok(), Some(tag.clone())); assert_eq!(tag.to_string(), text); assert_eq!(tag.requires_version(), ProtocolVersion::V5); diff --git a/src/tag/mod.rs b/src/tag/mod.rs index 4425066..087f247 100644 --- a/src/tag/mod.rs +++ b/src/tag/mod.rs @@ -3,6 +3,7 @@ //! [4.3. Playlist Tags]: https://tools.ietf.org/html/rfc8216#section-4.3 use std::fmt; use std::str::FromStr; +use trackable::error::ErrorKindExt; use {Error, ErrorKind, Result}; @@ -237,3 +238,8 @@ fn parse_yes_or_no(s: &str) -> Result { _ => track_panic!(ErrorKind::InvalidInput, "Unexpected value: {:?}", s), } } + +fn parse_u64(s: &str) -> Result { + let n = track!(s.parse().map_err(|e| ErrorKind::InvalidInput.cause(e)))?; + Ok(n) +} diff --git a/src/types.rs b/src/types.rs index 76c4b22..af28870 100644 --- a/src/types.rs +++ b/src/types.rs @@ -1,11 +1,12 @@ //! Miscellaneous types. use std::fmt; use std::ops::Deref; -use std::str::FromStr; +use std::str::{self, FromStr}; +use std::time::Duration; use trackable::error::ErrorKindExt; use {Error, ErrorKind, Result}; -use attribute::{AttributePairs, HexadecimalSequence, QuotedString}; +use attribute::AttributePairs; /// String that represents a single line in a playlist file. /// @@ -44,6 +45,301 @@ impl fmt::Display for SingleLineString { } } +/// Quoted string. +/// +/// See: [4.2. Attribute Lists] +/// +/// [4.2. Attribute Lists]: https://tools.ietf.org/html/rfc8216#section-4.2 +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct QuotedString(String); +impl QuotedString { + /// Makes a new `QuotedString` instance. + /// + /// # Errors + /// + /// If the given string contains any control characters or double-quote character, + /// this function will return an error which has the kind `ErrorKind::InvalidInput`. + pub fn new>(s: T) -> Result { + let s = s.into(); + track_assert!( + !s.chars().any(|c| c.is_control() || c == '"'), + ErrorKind::InvalidInput + ); + Ok(QuotedString(s)) + } +} +impl Deref for QuotedString { + type Target = str; + fn deref(&self) -> &Self::Target { + &self.0 + } +} +impl AsRef for QuotedString { + fn as_ref(&self) -> &str { + &self.0 + } +} +impl fmt::Display for QuotedString { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:?}", self.0) + } +} +impl FromStr for QuotedString { + type Err = Error; + fn from_str(s: &str) -> Result { + let len = s.len(); + let bytes = s.as_bytes(); + track_assert!(len >= 2, ErrorKind::InvalidInput); + track_assert_eq!(bytes[0], b'"', ErrorKind::InvalidInput); + track_assert_eq!(bytes[len - 1], b'"', ErrorKind::InvalidInput); + + let s = unsafe { str::from_utf8_unchecked(&bytes[1..len - 1]) }; + track!(QuotedString::new(s)) + } +} + +/// Decimal resolution. +/// +/// See: [4.2. Attribute Lists] +/// +/// [4.2. Attribute Lists]: https://tools.ietf.org/html/rfc8216#section-4.2 +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct DecimalResolution { + /// Horizontal pixel dimension. + pub width: usize, + + /// Vertical pixel dimension. + pub height: usize, +} +impl fmt::Display for DecimalResolution { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}x{}", self.width, self.height) + } +} +impl FromStr for DecimalResolution { + type Err = Error; + fn from_str(s: &str) -> Result { + let mut tokens = s.splitn(2, 'x'); + let width = tokens.next().expect("Never fails"); + let height = track_assert_some!(tokens.next(), ErrorKind::InvalidInput); + Ok(DecimalResolution { + width: track!(width.parse().map_err(|e| ErrorKind::InvalidInput.cause(e)))?, + height: track!(height.parse().map_err(|e| ErrorKind::InvalidInput.cause(e)))?, + }) + } +} + +/// Non-negative decimal floating-point number. +/// +/// See: [4.2. Attribute Lists] +/// +/// [4.2. Attribute Lists]: https://tools.ietf.org/html/rfc8216#section-4.2 +#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)] +pub struct DecimalFloatingPoint(f64); +impl DecimalFloatingPoint { + /// Makes a new `DecimalFloatingPoint` instance. + /// + /// # Errors + /// + /// The given value must have a positive sign and be finite, + /// otherwise this function will return an error that has the kind `ErrorKind::InvalidInput`. + pub fn new(n: f64) -> Result { + track_assert!(n.is_sign_positive(), ErrorKind::InvalidInput); + track_assert!(n.is_finite(), ErrorKind::InvalidInput); + Ok(DecimalFloatingPoint(n)) + } + + /// Converts `DecimalFloatingPoint` to `f64`. + pub fn as_f64(&self) -> f64 { + self.0 + } + + pub(crate) fn to_duration(&self) -> Duration { + let secs = self.0 as u64; + let nanos = (self.0.fract() * 1_000_000_000.0) as u32; + Duration::new(secs, nanos) + } + + pub(crate) fn from_duration(duration: Duration) -> Self { + let n = (duration.as_secs() as f64) + (duration.subsec_nanos() as f64 / 1_000_000_000.0); + DecimalFloatingPoint(n) + } +} +impl From for DecimalFloatingPoint { + fn from(f: u32) -> Self { + DecimalFloatingPoint(f64::from(f)) + } +} +impl Eq for DecimalFloatingPoint {} +impl fmt::Display for DecimalFloatingPoint { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.0.fmt(f) + } +} +impl FromStr for DecimalFloatingPoint { + type Err = Error; + fn from_str(s: &str) -> Result { + track_assert!( + s.chars().all(|c| c.is_digit(10) || c == '.'), + ErrorKind::InvalidInput + ); + let n = track!(s.parse().map_err(|e| ErrorKind::InvalidInput.cause(e)))?; + Ok(DecimalFloatingPoint(n)) + } +} + +/// Signed decimal floating-point number. +/// +/// See: [4.2. Attribute Lists] +/// +/// [4.2. Attribute Lists]: https://tools.ietf.org/html/rfc8216#section-4.2 +#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)] +pub struct SignedDecimalFloatingPoint(f64); +impl SignedDecimalFloatingPoint { + /// Makes a new `SignedDecimalFloatingPoint` instance. + /// + /// # Errors + /// + /// The given value must be finite, + /// otherwise this function will return an error that has the kind `ErrorKind::InvalidInput`. + pub fn new(n: f64) -> Result { + track_assert!(n.is_finite(), ErrorKind::InvalidInput); + Ok(SignedDecimalFloatingPoint(n)) + } + + /// Converts `DecimalFloatingPoint` to `f64`. + pub fn as_f64(&self) -> f64 { + self.0 + } +} +impl From for SignedDecimalFloatingPoint { + fn from(f: i32) -> Self { + SignedDecimalFloatingPoint(f64::from(f)) + } +} +impl Eq for SignedDecimalFloatingPoint {} +impl fmt::Display for SignedDecimalFloatingPoint { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.0.fmt(f) + } +} +impl FromStr for SignedDecimalFloatingPoint { + type Err = Error; + fn from_str(s: &str) -> Result { + track_assert!( + s.chars().all(|c| c.is_digit(10) || c == '.' || c == '-'), + ErrorKind::InvalidInput + ); + let n = track!(s.parse().map_err(|e| ErrorKind::InvalidInput.cause(e)))?; + Ok(SignedDecimalFloatingPoint(n)) + } +} + +/// Hexadecimal sequence. +/// +/// See: [4.2. Attribute Lists] +/// +/// [4.2. Attribute Lists]: https://tools.ietf.org/html/rfc8216#section-4.2 +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct HexadecimalSequence(Vec); +impl HexadecimalSequence { + /// Makes a new `HexadecimalSequence` instance. + pub fn new>>(v: T) -> Self { + HexadecimalSequence(v.into()) + } + + /// Converts into the underlying byte sequence. + pub fn into_bytes(self) -> Vec { + self.0 + } +} +impl Deref for HexadecimalSequence { + type Target = [u8]; + fn deref(&self) -> &Self::Target { + &self.0 + } +} +impl AsRef<[u8]> for HexadecimalSequence { + fn as_ref(&self) -> &[u8] { + &self.0 + } +} +impl fmt::Display for HexadecimalSequence { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "0x")?; + for b in &self.0 { + write!(f, "{:02x}", b)?; + } + Ok(()) + } +} +impl FromStr for HexadecimalSequence { + type Err = Error; + fn from_str(s: &str) -> Result { + track_assert!( + s.starts_with("0x") || s.starts_with("0X"), + ErrorKind::InvalidInput + ); + track_assert!(s.len() % 2 == 0, ErrorKind::InvalidInput); + + let mut v = Vec::with_capacity(s.len() / 2 - 1); + for c in s.as_bytes().chunks(2).skip(1) { + let d = track!(str::from_utf8(c).map_err(|e| ErrorKind::InvalidInput.cause(e)))?; + let b = + track!(u8::from_str_radix(d, 16).map_err(|e| ErrorKind::InvalidInput.cause(e)))?; + v.push(b); + } + Ok(HexadecimalSequence(v)) + } +} + +/// Initialization vector. +/// +/// See: [4.3.2.4. EXT-X-KEY] +/// +/// [4.3.2.4. EXT-X-KEY]: https://tools.ietf.org/html/rfc8216#section-4.3.2.4 +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct InitializationVector(pub [u8; 16]); +impl Deref for InitializationVector { + type Target = [u8]; + fn deref(&self) -> &Self::Target { + &self.0 + } +} +impl AsRef<[u8]> for InitializationVector { + fn as_ref(&self) -> &[u8] { + &self.0 + } +} +impl fmt::Display for InitializationVector { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "0x")?; + for b in &self.0 { + write!(f, "{:02x}", b)?; + } + Ok(()) + } +} +impl FromStr for InitializationVector { + type Err = Error; + fn from_str(s: &str) -> Result { + track_assert!( + s.starts_with("0x") || s.starts_with("0X"), + ErrorKind::InvalidInput + ); + track_assert_eq!(s.len() - 2, 32, ErrorKind::InvalidInput); + + let mut v = [0; 16]; + for (i, c) in s.as_bytes().chunks(2).skip(1).enumerate() { + let d = track!(str::from_utf8(c).map_err(|e| ErrorKind::InvalidInput.cause(e)))?; + let b = + track!(u8::from_str_radix(d, 16).map_err(|e| ErrorKind::InvalidInput.cause(e)))?; + v[i] = b; + } + Ok(InitializationVector(v)) + } +} + /// [7. Protocol Version Compatibility] /// /// [7. Protocol Version Compatibility]: https://tools.ietf.org/html/rfc8216#section-7 @@ -137,7 +433,7 @@ impl FromStr for ByteRange { pub struct DecryptionKey { pub method: EncryptionMethod, pub uri: QuotedString, - pub iv: Option, // TODO: iv + pub iv: Option, pub key_format: Option, pub key_format_versions: Option, }