1
0
Fork 0
mirror of https://github.com/sile/hls_m3u8.git synced 2024-11-26 00:51:00 +00:00

Refactor attribute module

This commit is contained in:
Takeru Ohta 2018-02-15 00:24:35 +09:00
parent 9f6d4e7ed7
commit 32b262713e
6 changed files with 365 additions and 223 deletions

View file

@ -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<T: Into<String>>(s: T) -> Result<Self> {
// 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<Self> {
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<u8>);
impl HexadecimalSequence {
pub fn new<T: Into<Vec<u8>>>(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<Self> {
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<Self> {
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<Self> {
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<Self> {
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<Self> {
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)))?,
})
}
}

View file

@ -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<DecimalInteger>,
bandwidth: u64,
average_bandwidth: Option<u64>,
codecs: Option<QuotedString>,
resolution: Option<DecimalResolution>,
frame_rate: Option<DecimalFloatingPoint>,
@ -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<DecimalInteger> {
pub fn average_bandwidth(&self) -> Option<u64> {
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<DecimalInteger>,
bandwidth: u64,
average_bandwidth: Option<u64>,
codecs: Option<QuotedString>,
resolution: Option<DecimalResolution>,
hdcp_level: Option<HdcpLevel>,
@ -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<DecimalInteger> {
pub fn average_bandwidth(&self) -> Option<u64> {
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);

View file

@ -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);

View file

@ -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);

View file

@ -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<bool> {
_ => track_panic!(ErrorKind::InvalidInput, "Unexpected value: {:?}", s),
}
}
fn parse_u64(s: &str) -> Result<u64> {
let n = track!(s.parse().map_err(|e| ErrorKind::InvalidInput.cause(e)))?;
Ok(n)
}

View file

@ -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<T: Into<String>>(s: T) -> Result<Self> {
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<str> 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<Self> {
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<Self> {
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<Self> {
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<u32> 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<Self> {
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<Self> {
track_assert!(n.is_finite(), ErrorKind::InvalidInput);
Ok(SignedDecimalFloatingPoint(n))
}
/// Converts `DecimalFloatingPoint` to `f64`.
pub fn as_f64(&self) -> f64 {
self.0
}
}
impl From<i32> 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<Self> {
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<u8>);
impl HexadecimalSequence {
/// Makes a new `HexadecimalSequence` instance.
pub fn new<T: Into<Vec<u8>>>(v: T) -> Self {
HexadecimalSequence(v.into())
}
/// Converts into the underlying byte sequence.
pub fn into_bytes(self) -> Vec<u8> {
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<Self> {
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<Self> {
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<HexadecimalSequence>, // TODO: iv
pub iv: Option<InitializationVector>,
pub key_format: Option<QuotedString>,
pub key_format_versions: Option<QuotedString>,
}