mirror of
https://github.com/sile/hls_m3u8.git
synced 2024-11-22 15:21:01 +00:00
Refactor attribute
module
This commit is contained in:
parent
9f6d4e7ed7
commit
32b262713e
6 changed files with 365 additions and 223 deletions
195
src/attribute.rs
195
src/attribute.rs
|
@ -1,18 +1,19 @@
|
||||||
use std::fmt;
|
use std::collections::HashSet;
|
||||||
use std::str::{self, FromStr};
|
use std::str;
|
||||||
use std::time::Duration;
|
|
||||||
use std::u8;
|
|
||||||
use trackable::error::ErrorKindExt;
|
|
||||||
|
|
||||||
use {Error, ErrorKind, Result};
|
use {ErrorKind, Result};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct AttributePairs<'a> {
|
pub struct AttributePairs<'a> {
|
||||||
input: &'a str,
|
input: &'a str,
|
||||||
|
visited_keys: HashSet<&'a str>,
|
||||||
}
|
}
|
||||||
impl<'a> AttributePairs<'a> {
|
impl<'a> AttributePairs<'a> {
|
||||||
pub fn parse(input: &'a str) -> Self {
|
pub fn parse(input: &'a str) -> Self {
|
||||||
AttributePairs { input }
|
AttributePairs {
|
||||||
|
input,
|
||||||
|
visited_keys: HashSet::new(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_name(&mut self) -> Result<&'a str> {
|
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)> {
|
let result = || -> Result<(&'a str, &'a str)> {
|
||||||
// TODO: check key duplications
|
|
||||||
let key = track!(self.parse_name())?;
|
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();
|
let value = self.parse_raw_value();
|
||||||
Ok((key, value))
|
Ok((key, value))
|
||||||
}();
|
}();
|
||||||
Some(result)
|
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)))?,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -2,11 +2,10 @@ use std::fmt;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use {Error, ErrorKind, Result};
|
use {Error, ErrorKind, Result};
|
||||||
use attribute::{AttributePairs, DecimalFloatingPoint, DecimalInteger, DecimalResolution,
|
use attribute::AttributePairs;
|
||||||
QuotedString};
|
use types::{ClosedCaptions, DecimalFloatingPoint, DecimalResolution, DecryptionKey, HdcpLevel,
|
||||||
use types::{ClosedCaptions, DecryptionKey, HdcpLevel, InStreamId, MediaType, ProtocolVersion,
|
InStreamId, MediaType, ProtocolVersion, QuotedString, SessionData, SingleLineString};
|
||||||
SessionData, SingleLineString};
|
use super::{parse_yes_or_no, parse_u64};
|
||||||
use super::parse_yes_or_no;
|
|
||||||
|
|
||||||
/// [4.3.4.1. EXT-X-MEDIA]
|
/// [4.3.4.1. EXT-X-MEDIA]
|
||||||
///
|
///
|
||||||
|
@ -192,7 +191,7 @@ impl FromStr for ExtXMedia {
|
||||||
"FORCED" => forced = Some(track!(parse_yes_or_no(value))?),
|
"FORCED" => forced = Some(track!(parse_yes_or_no(value))?),
|
||||||
"INSTREAM-ID" => {
|
"INSTREAM-ID" => {
|
||||||
let s: QuotedString = track!(value.parse())?;
|
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())?),
|
"CHARACTERISTICS" => characteristics = Some(track!(value.parse())?),
|
||||||
"CHANNELS" => channels = Some(track!(value.parse())?),
|
"CHANNELS" => channels = Some(track!(value.parse())?),
|
||||||
|
@ -240,8 +239,8 @@ impl FromStr for ExtXMedia {
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub struct ExtXStreamInf {
|
pub struct ExtXStreamInf {
|
||||||
uri: SingleLineString,
|
uri: SingleLineString,
|
||||||
bandwidth: DecimalInteger,
|
bandwidth: u64,
|
||||||
average_bandwidth: Option<DecimalInteger>,
|
average_bandwidth: Option<u64>,
|
||||||
codecs: Option<QuotedString>,
|
codecs: Option<QuotedString>,
|
||||||
resolution: Option<DecimalResolution>,
|
resolution: Option<DecimalResolution>,
|
||||||
frame_rate: Option<DecimalFloatingPoint>,
|
frame_rate: Option<DecimalFloatingPoint>,
|
||||||
|
@ -255,7 +254,7 @@ impl ExtXStreamInf {
|
||||||
pub(crate) const PREFIX: &'static str = "#EXT-X-STREAM-INF:";
|
pub(crate) const PREFIX: &'static str = "#EXT-X-STREAM-INF:";
|
||||||
|
|
||||||
/// Makes a new `ExtXStreamInf` tag.
|
/// Makes a new `ExtXStreamInf` tag.
|
||||||
pub fn new(uri: SingleLineString, bandwidth: DecimalInteger) -> Self {
|
pub fn new(uri: SingleLineString, bandwidth: u64) -> Self {
|
||||||
ExtXStreamInf {
|
ExtXStreamInf {
|
||||||
uri,
|
uri,
|
||||||
bandwidth,
|
bandwidth,
|
||||||
|
@ -277,12 +276,12 @@ impl ExtXStreamInf {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the peak segment bit rate of the variant stream.
|
/// Returns the peak segment bit rate of the variant stream.
|
||||||
pub fn bandwidth(&self) -> DecimalInteger {
|
pub fn bandwidth(&self) -> u64 {
|
||||||
self.bandwidth
|
self.bandwidth
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the average segment bit rate of the variant stream.
|
/// 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
|
self.average_bandwidth
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -392,8 +391,8 @@ impl FromStr for ExtXStreamInf {
|
||||||
for attr in attrs {
|
for attr in attrs {
|
||||||
let (key, value) = track!(attr)?;
|
let (key, value) = track!(attr)?;
|
||||||
match key {
|
match key {
|
||||||
"BANDWIDTH" => bandwidth = Some(track!(value.parse())?),
|
"BANDWIDTH" => bandwidth = Some(track!(parse_u64(value))?),
|
||||||
"AVERAGE-BANDWIDTH" => average_bandwidth = Some(track!(value.parse())?),
|
"AVERAGE-BANDWIDTH" => average_bandwidth = Some(track!(parse_u64(value))?),
|
||||||
"CODECS" => codecs = Some(track!(value.parse())?),
|
"CODECS" => codecs = Some(track!(value.parse())?),
|
||||||
"RESOLUTION" => resolution = Some(track!(value.parse())?),
|
"RESOLUTION" => resolution = Some(track!(value.parse())?),
|
||||||
"FRAME-RATE" => frame_rate = 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)]
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
pub struct ExtXIFrameStreamInf {
|
pub struct ExtXIFrameStreamInf {
|
||||||
uri: QuotedString,
|
uri: QuotedString,
|
||||||
bandwidth: DecimalInteger,
|
bandwidth: u64,
|
||||||
average_bandwidth: Option<DecimalInteger>,
|
average_bandwidth: Option<u64>,
|
||||||
codecs: Option<QuotedString>,
|
codecs: Option<QuotedString>,
|
||||||
resolution: Option<DecimalResolution>,
|
resolution: Option<DecimalResolution>,
|
||||||
hdcp_level: Option<HdcpLevel>,
|
hdcp_level: Option<HdcpLevel>,
|
||||||
|
@ -442,7 +441,7 @@ impl ExtXIFrameStreamInf {
|
||||||
pub(crate) const PREFIX: &'static str = "#EXT-X-I-FRAME-STREAM-INF:";
|
pub(crate) const PREFIX: &'static str = "#EXT-X-I-FRAME-STREAM-INF:";
|
||||||
|
|
||||||
/// Makes a new `ExtXIFrameStreamInf` tag.
|
/// Makes a new `ExtXIFrameStreamInf` tag.
|
||||||
pub fn new(uri: QuotedString, bandwidth: DecimalInteger) -> Self {
|
pub fn new(uri: QuotedString, bandwidth: u64) -> Self {
|
||||||
ExtXIFrameStreamInf {
|
ExtXIFrameStreamInf {
|
||||||
uri,
|
uri,
|
||||||
bandwidth,
|
bandwidth,
|
||||||
|
@ -460,12 +459,12 @@ impl ExtXIFrameStreamInf {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the peak segment bit rate of the variant stream.
|
/// Returns the peak segment bit rate of the variant stream.
|
||||||
pub fn bandwidth(&self) -> DecimalInteger {
|
pub fn bandwidth(&self) -> u64 {
|
||||||
self.bandwidth
|
self.bandwidth
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the average segment bit rate of the variant stream.
|
/// 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
|
self.average_bandwidth
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -534,8 +533,8 @@ impl FromStr for ExtXIFrameStreamInf {
|
||||||
let (key, value) = track!(attr)?;
|
let (key, value) = track!(attr)?;
|
||||||
match key {
|
match key {
|
||||||
"URI" => uri = Some(track!(value.parse())?),
|
"URI" => uri = Some(track!(value.parse())?),
|
||||||
"BANDWIDTH" => bandwidth = Some(track!(value.parse())?),
|
"BANDWIDTH" => bandwidth = Some(track!(parse_u64(value))?),
|
||||||
"AVERAGE-BANDWIDTH" => average_bandwidth = Some(track!(value.parse())?),
|
"AVERAGE-BANDWIDTH" => average_bandwidth = Some(track!(parse_u64(value))?),
|
||||||
"CODECS" => codecs = Some(track!(value.parse())?),
|
"CODECS" => codecs = Some(track!(value.parse())?),
|
||||||
"RESOLUTION" => resolution = Some(track!(value.parse())?),
|
"RESOLUTION" => resolution = Some(track!(value.parse())?),
|
||||||
"HDCP-LEVEL" => hdcp_level = Some(track!(value.parse())?),
|
"HDCP-LEVEL" => hdcp_level = Some(track!(value.parse())?),
|
||||||
|
@ -708,8 +707,7 @@ impl FromStr for ExtXSessionKey {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use attribute::HexadecimalSequence;
|
use types::{EncryptionMethod, InitializationVector};
|
||||||
use types::EncryptionMethod;
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -723,7 +721,7 @@ mod test {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn ext_x_stream_inf() {
|
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";
|
let text = "#EXT-X-STREAM-INF:BANDWIDTH=1000\nfoo";
|
||||||
assert_eq!(text.parse().ok(), Some(tag.clone()));
|
assert_eq!(text.parse().ok(), Some(tag.clone()));
|
||||||
assert_eq!(tag.to_string(), text);
|
assert_eq!(tag.to_string(), text);
|
||||||
|
@ -732,7 +730,7 @@ mod test {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn ext_x_i_frame_stream_inf() {
|
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"#;
|
let text = r#"#EXT-X-I-FRAME-STREAM-INF:URI="foo",BANDWIDTH=1000"#;
|
||||||
assert_eq!(text.parse().ok(), Some(tag.clone()));
|
assert_eq!(text.parse().ok(), Some(tag.clone()));
|
||||||
assert_eq!(tag.to_string(), text);
|
assert_eq!(tag.to_string(), text);
|
||||||
|
@ -773,11 +771,14 @@ mod test {
|
||||||
let tag = ExtXSessionKey::new(DecryptionKey {
|
let tag = ExtXSessionKey::new(DecryptionKey {
|
||||||
method: EncryptionMethod::Aes128,
|
method: EncryptionMethod::Aes128,
|
||||||
uri: quoted_string("foo"),
|
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: None,
|
||||||
key_format_versions: 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!(text.parse().ok(), Some(tag.clone()));
|
||||||
assert_eq!(tag.to_string(), text);
|
assert_eq!(tag.to_string(), text);
|
||||||
assert_eq!(tag.requires_version(), ProtocolVersion::V2);
|
assert_eq!(tag.requires_version(), ProtocolVersion::V2);
|
||||||
|
|
|
@ -2,8 +2,8 @@ use std::fmt;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use {Error, ErrorKind, Result};
|
use {Error, ErrorKind, Result};
|
||||||
use attribute::{AttributePairs, SignedDecimalFloatingPoint};
|
use attribute::AttributePairs;
|
||||||
use types::ProtocolVersion;
|
use types::{ProtocolVersion, SignedDecimalFloatingPoint};
|
||||||
use super::parse_yes_or_no;
|
use super::parse_yes_or_no;
|
||||||
|
|
||||||
/// [4.3.5.1. EXT-X-INDEPENDENT-SEGMENTS]
|
/// [4.3.5.1. EXT-X-INDEPENDENT-SEGMENTS]
|
||||||
|
@ -128,13 +128,13 @@ mod test {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn ext_x_start() {
|
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";
|
let text = "#EXT-X-START:TIME-OFFSET=-1.23";
|
||||||
assert_eq!(text.parse().ok(), Some(tag));
|
assert_eq!(text.parse().ok(), Some(tag));
|
||||||
assert_eq!(tag.to_string(), text);
|
assert_eq!(tag.to_string(), text);
|
||||||
assert_eq!(tag.requires_version(), ProtocolVersion::V1);
|
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";
|
let text = "#EXT-X-START:TIME-OFFSET=1.23,PRECISE=YES";
|
||||||
assert_eq!(text.parse().ok(), Some(tag));
|
assert_eq!(text.parse().ok(), Some(tag));
|
||||||
assert_eq!(tag.to_string(), text);
|
assert_eq!(tag.to_string(), text);
|
||||||
|
|
|
@ -6,8 +6,9 @@ use chrono::{DateTime, FixedOffset, NaiveDate};
|
||||||
use trackable::error::ErrorKindExt;
|
use trackable::error::ErrorKindExt;
|
||||||
|
|
||||||
use {Error, ErrorKind, Result};
|
use {Error, ErrorKind, Result};
|
||||||
use attribute::{AttributePairs, DecimalFloatingPoint, QuotedString};
|
use attribute::AttributePairs;
|
||||||
use types::{ByteRange, DecryptionKey, ProtocolVersion, SingleLineString};
|
use types::{ByteRange, DecimalFloatingPoint, DecryptionKey, ProtocolVersion, QuotedString,
|
||||||
|
SingleLineString};
|
||||||
|
|
||||||
/// [4.3.2.1. EXTINF]
|
/// [4.3.2.1. EXTINF]
|
||||||
///
|
///
|
||||||
|
@ -434,14 +435,14 @@ impl FromStr for ExtXDateRange {
|
||||||
"START-DATE" => {
|
"START-DATE" => {
|
||||||
let s: QuotedString = track!(value.parse())?;
|
let s: QuotedString = track!(value.parse())?;
|
||||||
start_date = Some(track!(
|
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))
|
.map_err(|e| ErrorKind::InvalidInput.cause(e))
|
||||||
)?);
|
)?);
|
||||||
}
|
}
|
||||||
"END-DATE" => {
|
"END-DATE" => {
|
||||||
let s: QuotedString = track!(value.parse())?;
|
let s: QuotedString = track!(value.parse())?;
|
||||||
end_date = Some(track!(
|
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))
|
.map_err(|e| ErrorKind::InvalidInput.cause(e))
|
||||||
)?);
|
)?);
|
||||||
}
|
}
|
||||||
|
@ -496,8 +497,7 @@ impl FromStr for ExtXDateRange {
|
||||||
mod test {
|
mod test {
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use attribute::HexadecimalSequence;
|
use types::{EncryptionMethod, InitializationVector};
|
||||||
use types::EncryptionMethod;
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -571,11 +571,13 @@ mod test {
|
||||||
let tag = ExtXKey::new(DecryptionKey {
|
let tag = ExtXKey::new(DecryptionKey {
|
||||||
method: EncryptionMethod::Aes128,
|
method: EncryptionMethod::Aes128,
|
||||||
uri: QuotedString::new("foo").unwrap(),
|
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: None,
|
||||||
key_format_versions: 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!(text.parse().ok(), Some(tag.clone()));
|
||||||
assert_eq!(tag.to_string(), text);
|
assert_eq!(tag.to_string(), text);
|
||||||
assert_eq!(tag.requires_version(), ProtocolVersion::V2);
|
assert_eq!(tag.requires_version(), ProtocolVersion::V2);
|
||||||
|
@ -583,11 +585,13 @@ mod test {
|
||||||
let tag = ExtXKey::new(DecryptionKey {
|
let tag = ExtXKey::new(DecryptionKey {
|
||||||
method: EncryptionMethod::Aes128,
|
method: EncryptionMethod::Aes128,
|
||||||
uri: QuotedString::new("foo").unwrap(),
|
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: Some(QuotedString::new("baz").unwrap()),
|
||||||
key_format_versions: None,
|
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!(text.parse().ok(), Some(tag.clone()));
|
||||||
assert_eq!(tag.to_string(), text);
|
assert_eq!(tag.to_string(), text);
|
||||||
assert_eq!(tag.requires_version(), ProtocolVersion::V5);
|
assert_eq!(tag.requires_version(), ProtocolVersion::V5);
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
//! [4.3. Playlist Tags]: https://tools.ietf.org/html/rfc8216#section-4.3
|
//! [4.3. Playlist Tags]: https://tools.ietf.org/html/rfc8216#section-4.3
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
use trackable::error::ErrorKindExt;
|
||||||
|
|
||||||
use {Error, ErrorKind, Result};
|
use {Error, ErrorKind, Result};
|
||||||
|
|
||||||
|
@ -237,3 +238,8 @@ fn parse_yes_or_no(s: &str) -> Result<bool> {
|
||||||
_ => track_panic!(ErrorKind::InvalidInput, "Unexpected value: {:?}", s),
|
_ => 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)
|
||||||
|
}
|
||||||
|
|
302
src/types.rs
302
src/types.rs
|
@ -1,11 +1,12 @@
|
||||||
//! Miscellaneous types.
|
//! Miscellaneous types.
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use std::str::FromStr;
|
use std::str::{self, FromStr};
|
||||||
|
use std::time::Duration;
|
||||||
use trackable::error::ErrorKindExt;
|
use trackable::error::ErrorKindExt;
|
||||||
|
|
||||||
use {Error, ErrorKind, Result};
|
use {Error, ErrorKind, Result};
|
||||||
use attribute::{AttributePairs, HexadecimalSequence, QuotedString};
|
use attribute::AttributePairs;
|
||||||
|
|
||||||
/// String that represents a single line in a playlist file.
|
/// 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]
|
||||||
///
|
///
|
||||||
/// [7. Protocol Version Compatibility]: https://tools.ietf.org/html/rfc8216#section-7
|
/// [7. Protocol Version Compatibility]: https://tools.ietf.org/html/rfc8216#section-7
|
||||||
|
@ -137,7 +433,7 @@ impl FromStr for ByteRange {
|
||||||
pub struct DecryptionKey {
|
pub struct DecryptionKey {
|
||||||
pub method: EncryptionMethod,
|
pub method: EncryptionMethod,
|
||||||
pub uri: QuotedString,
|
pub uri: QuotedString,
|
||||||
pub iv: Option<HexadecimalSequence>, // TODO: iv
|
pub iv: Option<InitializationVector>,
|
||||||
pub key_format: Option<QuotedString>,
|
pub key_format: Option<QuotedString>,
|
||||||
pub key_format_versions: Option<QuotedString>,
|
pub key_format_versions: Option<QuotedString>,
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue