diff --git a/src/tags/master_playlist/session_key.rs b/src/tags/master_playlist/session_key.rs index ac19110..0d7d275 100644 --- a/src/tags/master_playlist/session_key.rs +++ b/src/tags/master_playlist/session_key.rs @@ -1,24 +1,18 @@ use std::fmt; +use std::ops::{Deref, DerefMut}; use std::str::FromStr; use url::Url; -use crate::attribute::AttributePairs; -use crate::types::{EncryptionMethod, InitializationVector, ProtocolVersion}; -use crate::utils::{quote, tag, unquote}; +use crate::types::{DecryptionKey, EncryptionMethod, ProtocolVersion}; +use crate::utils::tag; use crate::Error; /// [4.3.4.5. EXT-X-SESSION-KEY] /// /// [4.3.4.5. EXT-X-SESSION-KEY]: https://tools.ietf.org/html/rfc8216#section-4.3.4.5 #[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct ExtXSessionKey { - method: EncryptionMethod, - uri: Option, - iv: Option, - key_format: Option, - key_format_versions: Option, -} +pub struct ExtXSessionKey(DecryptionKey); impl ExtXSessionKey { pub(crate) const PREFIX: &'static str = "#EXT-X-SESSION-KEY:"; @@ -31,266 +25,7 @@ impl ExtXSessionKey { panic!("The EncryptionMethod is not allowed to be None"); } - Self { - method, - uri: Some(uri), - iv: None, - key_format: None, - key_format_versions: None, - } - } - - /// Returns the [EncryptionMethod]. - /// # Example - /// ``` - /// use hls_m3u8::tags::ExtXSessionKey; - /// use hls_m3u8::types::EncryptionMethod; - /// - /// let key = ExtXSessionKey::new( - /// EncryptionMethod::Aes128, - /// "https://www.example.com".parse().unwrap() - /// ); - /// - /// assert_eq!( - /// key.method(), - /// EncryptionMethod::Aes128 - /// ); - /// ``` - pub const fn method(&self) -> EncryptionMethod { - self.method - } - - /// Sets the [EncryptionMethod]. - /// # Example - /// ``` - /// use hls_m3u8::tags::ExtXSessionKey; - /// use hls_m3u8::types::EncryptionMethod; - /// - /// let mut key = ExtXSessionKey::new( - /// EncryptionMethod::Aes128, - /// "https://www.example.com".parse().unwrap() - /// ); - /// - /// key.set_method(EncryptionMethod::SampleAes); - /// - /// assert_eq!( - /// key.to_string(), - /// "#EXT-X-SESSION-KEY:METHOD=SAMPLE-AES,URI=\"https://www.example.com/\"".to_string() - /// ); - /// ``` - pub fn set_method(&mut self, value: EncryptionMethod) -> &mut Self { - self.method = value; - self - } - - /// Returns an `URI` that specifies how to obtain the key. - /// - /// This attribute is required, if the [EncryptionMethod] is not None. - /// # Example - /// ``` - /// use hls_m3u8::tags::ExtXSessionKey; - /// use hls_m3u8::types::EncryptionMethod; - /// - /// let key = ExtXSessionKey::new( - /// EncryptionMethod::Aes128, - /// "https://www.example.com".parse().unwrap() - /// ); - /// - /// assert_eq!( - /// key.uri(), - /// &Some("https://www.example.com".parse().unwrap()) - /// ); - /// ``` - pub const fn uri(&self) -> &Option { - &self.uri - } - - /// Sets the `URI` attribute. - /// - /// This attribute is required, if the [EncryptionMethod] is not None. - /// # Example - /// ``` - /// use hls_m3u8::tags::ExtXSessionKey; - /// use hls_m3u8::types::EncryptionMethod; - /// - /// let mut key = ExtXSessionKey::new( - /// EncryptionMethod::Aes128, - /// "https://www.example.com".parse().unwrap() - /// ); - /// - /// key.set_uri("http://www.google.com".parse().unwrap()); - /// - /// assert_eq!( - /// key.to_string(), - /// "#EXT-X-SESSION-KEY:METHOD=AES-128,URI=\"http://www.google.com/\"".to_string() - /// ); - /// ``` - pub fn set_uri(&mut self, value: Url) -> &mut Self { - self.uri = Some(value); - self - } - - /// Returns the IV (Initialization Vector) attribute. - /// - /// This attribute is optional. - /// # Example - /// ``` - /// use hls_m3u8::tags::ExtXSessionKey; - /// use hls_m3u8::types::EncryptionMethod; - /// - /// let mut key = ExtXSessionKey::new( - /// EncryptionMethod::Aes128, - /// "https://www.example.com".parse().unwrap() - /// ); - /// - /// key.set_iv([ - /// 1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2, 3, 4, 5, 6, 7 - /// ]); - /// - /// assert_eq!( - /// key.iv(), - /// Some([1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2, 3, 4, 5, 6, 7]) - /// ); - /// ``` - pub fn iv(&self) -> Option<[u8; 16]> { - if let Some(iv) = &self.iv { - Some(iv.to_slice()) - } else { - None - } - } - - /// Sets the `IV` attribute. - /// - /// This attribute is optional. - /// # Example - /// ``` - /// use hls_m3u8::tags::ExtXSessionKey; - /// use hls_m3u8::types::EncryptionMethod; - /// - /// let mut key = ExtXSessionKey::new( - /// EncryptionMethod::Aes128, - /// "https://www.example.com".parse().unwrap() - /// ); - /// - /// key.set_iv([ - /// 1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2, 3, 4, 5, 6, 7 - /// ]); - /// - /// assert_eq!( - /// key.to_string(), - /// "#EXT-X-SESSION-KEY:METHOD=AES-128,URI=\"https://www.example.com/\",IV=0x01020304050607080901020304050607".to_string() - /// ); - /// ``` - pub fn set_iv(&mut self, value: T) -> &mut Self - where - T: Into<[u8; 16]>, - { - self.iv = Some(InitializationVector(value.into())); - self - } - - /// Returns a string that specifies how the key is - /// represented in the resource identified by the URI. - /// - //// This attribute is optional. - /// # Example - /// ``` - /// use hls_m3u8::tags::ExtXSessionKey; - /// use hls_m3u8::types::EncryptionMethod; - /// - /// let mut key = ExtXSessionKey::new( - /// EncryptionMethod::Aes128, - /// "https://www.example.com".parse().unwrap() - /// ); - /// - /// key.set_key_format("key_format_attribute"); - /// - /// assert_eq!( - /// key.key_format(), - /// &Some("key_format_attribute".to_string()) - /// ); - /// ``` - pub const fn key_format(&self) -> &Option { - &self.key_format - } - - /// Sets the `KEYFORMAT` attribute. - /// - /// This attribute is optional. - /// # Example - /// ``` - /// use hls_m3u8::tags::ExtXSessionKey; - /// use hls_m3u8::types::EncryptionMethod; - /// - /// let mut key = ExtXSessionKey::new( - /// EncryptionMethod::Aes128, - /// "https://www.example.com".parse().unwrap() - /// ); - /// - /// key.set_key_format("key_format_attribute"); - /// - /// assert_eq!( - /// key.to_string(), - /// "#EXT-X-SESSION-KEY:METHOD=AES-128,URI=\"https://www.example.com/\",KEYFORMAT=\"key_format_attribute\"".to_string() - /// ); - /// ``` - pub fn set_key_format(&mut self, value: T) -> &mut Self { - self.key_format = Some(value.to_string()); - self - } - - /// Returns a string containing one or more positive - /// integers separated by the "/" character (for example, "1", "1/2", - /// or "1/2/5"). If more than one version of a particular `KEYFORMAT` - /// is defined, this attribute can be used to indicate which - /// version(s) this instance complies with. - /// - /// This attribute is optional. - /// # Example - /// ``` - /// use hls_m3u8::tags::ExtXSessionKey; - /// use hls_m3u8::types::EncryptionMethod; - /// - /// let mut key = ExtXSessionKey::new( - /// EncryptionMethod::Aes128, - /// "https://www.example.com".parse().unwrap() - /// ); - /// - /// key.set_key_format_versions("1/2/3/4/5"); - /// - /// assert_eq!( - /// key.key_format_versions(), - /// &Some("1/2/3/4/5".to_string()) - /// ); - /// ``` - pub const fn key_format_versions(&self) -> &Option { - &self.key_format_versions - } - - /// Sets the `KEYFORMATVERSIONS` attribute. - /// - /// This attribute is optional. - /// # Example - /// ``` - /// use hls_m3u8::tags::ExtXSessionKey; - /// use hls_m3u8::types::EncryptionMethod; - /// - /// let mut key = ExtXSessionKey::new( - /// EncryptionMethod::Aes128, - /// "https://www.example.com".parse().unwrap() - /// ); - /// - /// key.set_key_format_versions("1/2/3/4/5"); - /// - /// assert_eq!( - /// key.to_string(), - /// "#EXT-X-SESSION-KEY:METHOD=AES-128,URI=\"https://www.example.com/\",KEYFORMATVERSIONS=\"1/2/3/4/5\"".to_string() - /// ); - /// ``` - pub fn set_key_format_versions(&mut self, value: T) -> &mut Self { - self.key_format_versions = Some(value.to_string()); - self + Self(DecryptionKey::new(method, uri)) } /// Returns the protocol compatibility version that this tag requires. @@ -310,9 +45,9 @@ impl ExtXSessionKey { /// ); /// ``` pub fn requires_version(&self) -> ProtocolVersion { - if self.key_format.is_some() | self.key_format_versions.is_some() { + if self.0.key_format.is_some() | self.0.key_format_versions.is_some() { ProtocolVersion::V5 - } else if self.iv.is_some() { + } else if self.0.iv.is_some() { ProtocolVersion::V2 } else { ProtocolVersion::V1 @@ -322,20 +57,7 @@ impl ExtXSessionKey { impl fmt::Display for ExtXSessionKey { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}METHOD={}", Self::PREFIX, self.method)?; - if let Some(uri) = &self.uri { - write!(f, ",URI={}", quote(uri))?; - } - if let Some(value) = &self.iv { - write!(f, ",IV={}", value)?; - } - if let Some(value) = &self.key_format { - write!(f, ",KEYFORMAT={}", quote(value))?; - } - if let Some(value) = &self.key_format_versions { - write!(f, ",KEYFORMATVERSIONS={}", quote(value))?; - } - Ok(()) + write!(f, "{}{}", Self::PREFIX, self.0) } } @@ -344,39 +66,21 @@ impl FromStr for ExtXSessionKey { fn from_str(input: &str) -> Result { let input = tag(input, Self::PREFIX)?; - let mut method = None; - let mut uri = None; - let mut iv = None; - let mut key_format = None; - let mut key_format_versions = None; + Ok(Self(input.parse()?)) + } +} - for (key, value) in input.parse::()? { - match key.as_str() { - "METHOD" => method = Some((value.parse())?), - "URI" => uri = Some(unquote(value).parse()?), - "IV" => iv = Some((value.parse())?), - "KEYFORMAT" => key_format = Some(unquote(value)), - "KEYFORMATVERSIONS" => key_format_versions = Some(unquote(value)), - _ => { - // [6.3.1. General Client Responsibilities] - // > ignore any attribute/value pair with an unrecognized AttributeName. - } - } - } +impl Deref for ExtXSessionKey { + type Target = DecryptionKey; - let method = method.ok_or(Error::missing_value("EXT-X-METHOD"))?; - if method == EncryptionMethod::None { - return Err(Error::custom( - "EXT-X-SESSION-KEY: Encryption Method must not be NONE", - )); - } - Ok(ExtXSessionKey { - method, - uri, - iv, - key_format, - key_format_versions, - }) + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for ExtXSessionKey { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 } } diff --git a/src/tags/media_segment/key.rs b/src/tags/media_segment/key.rs index bfa7df9..64abf6d 100644 --- a/src/tags/media_segment/key.rs +++ b/src/tags/media_segment/key.rs @@ -1,11 +1,11 @@ use std::fmt; +use std::ops::{Deref, DerefMut}; use std::str::FromStr; use url::Url; -use crate::attribute::AttributePairs; -use crate::types::{EncryptionMethod, InitializationVector, ProtocolVersion}; -use crate::utils::{quote, tag, unquote}; +use crate::types::{DecryptionKey, EncryptionMethod}; +use crate::utils::tag; use crate::Error; /// [4.3.2.4. EXT-X-KEY] @@ -14,13 +14,7 @@ use crate::Error; /// # Note /// In case of an empty key (`EncryptionMethod::None`), all attributes will be ignored. #[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct ExtXKey { - method: EncryptionMethod, - uri: Option, - iv: Option, - key_format: Option, - key_format_versions: Option, -} +pub struct ExtXKey(DecryptionKey); impl ExtXKey { pub(crate) const PREFIX: &'static str = "#EXT-X-KEY:"; @@ -44,13 +38,7 @@ impl ExtXKey { /// ); /// ``` pub const fn new(method: EncryptionMethod, uri: Url) -> Self { - Self { - method, - uri: Some(uri), - iv: None, - key_format: None, - key_format_versions: None, - } + Self(DecryptionKey::new(method, uri)) } /// Makes a new `ExtXKey` tag without a decryption key. @@ -66,16 +54,16 @@ impl ExtXKey { /// ); /// ``` pub const fn empty() -> Self { - Self { + Self(DecryptionKey { method: EncryptionMethod::None, uri: None, iv: None, key_format: None, key_format_versions: None, - } + }) } - /// Returns whether the EncryptionMethod is None. + /// Returns whether the [EncryptionMethod] is [None](EncryptionMethod::None). /// # Example /// ``` /// use hls_m3u8::tags::ExtXKey; @@ -89,313 +77,7 @@ impl ExtXKey { /// ); /// ``` pub fn is_empty(&self) -> bool { - if self.method == EncryptionMethod::None { - true - } else { - false - } - } - - /// Returns the [EncryptionMethod]. - /// # Example - /// ``` - /// use hls_m3u8::tags::ExtXKey; - /// use hls_m3u8::types::EncryptionMethod; - /// - /// let key = ExtXKey::new( - /// EncryptionMethod::Aes128, - /// "https://www.example.com".parse().unwrap() - /// ); - /// - /// assert_eq!( - /// key.method(), - /// EncryptionMethod::Aes128 - /// ); - /// ``` - pub const fn method(&self) -> EncryptionMethod { - self.method - } - - /// Sets the [EncryptionMethod]. - /// # Example - /// ``` - /// use hls_m3u8::tags::ExtXKey; - /// use hls_m3u8::types::EncryptionMethod; - /// - /// let mut key = ExtXKey::new( - /// EncryptionMethod::Aes128, - /// "https://www.example.com".parse().unwrap() - /// ); - /// - /// key.set_method(EncryptionMethod::SampleAes); - /// - /// assert_eq!( - /// key.to_string(), - /// "#EXT-X-KEY:METHOD=SAMPLE-AES,URI=\"https://www.example.com/\"".to_string() - /// ); - /// ``` - pub fn set_method(&mut self, value: EncryptionMethod) -> &mut Self { - self.method = value; - self - } - - /// Returns an `URI` that specifies how to obtain the key. - /// - /// This attribute is required, if the [EncryptionMethod] is not None. - /// # Example - /// ``` - /// use hls_m3u8::tags::ExtXKey; - /// use hls_m3u8::types::EncryptionMethod; - /// - /// let key = ExtXKey::new( - /// EncryptionMethod::Aes128, - /// "https://www.example.com".parse().unwrap() - /// ); - /// - /// assert_eq!( - /// key.uri(), - /// &Some("https://www.example.com".parse().unwrap()) - /// ); - /// ``` - pub const fn uri(&self) -> &Option { - &self.uri - } - - /// Sets the `URI` attribute. - /// - /// This attribute is required, if the [EncryptionMethod] is not None. - /// # Example - /// ``` - /// use hls_m3u8::tags::ExtXKey; - /// use hls_m3u8::types::EncryptionMethod; - /// - /// let mut key = ExtXKey::new( - /// EncryptionMethod::Aes128, - /// "https://www.example.com".parse().unwrap() - /// ); - /// - /// key.set_uri("http://www.google.com".parse().unwrap()); - /// - /// assert_eq!( - /// key.to_string(), - /// "#EXT-X-KEY:METHOD=AES-128,URI=\"http://www.google.com/\"".to_string() - /// ); - /// ``` - pub fn set_uri(&mut self, value: Url) -> &mut Self { - self.uri = Some(value); - self - } - - /// Returns the IV (Initialization Vector) attribute. - /// - /// This attribute is optional. - /// # Example - /// ``` - /// use hls_m3u8::tags::ExtXKey; - /// use hls_m3u8::types::EncryptionMethod; - /// - /// let mut key = ExtXKey::new( - /// EncryptionMethod::Aes128, - /// "https://www.example.com".parse().unwrap() - /// ); - /// - /// key.set_iv([ - /// 1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2, 3, 4, 5, 6, 7 - /// ]); - /// - /// assert_eq!( - /// key.iv(), - /// Some([1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2, 3, 4, 5, 6, 7]) - /// ); - /// ``` - pub fn iv(&self) -> Option<[u8; 16]> { - if let Some(iv) = &self.iv { - Some(iv.to_slice()) - } else { - None - } - } - - /// Sets the `IV` attribute. - /// - /// This attribute is optional. - /// # Example - /// ``` - /// use hls_m3u8::tags::ExtXKey; - /// use hls_m3u8::types::EncryptionMethod; - /// - /// let mut key = ExtXKey::new( - /// EncryptionMethod::Aes128, - /// "https://www.example.com".parse().unwrap() - /// ); - /// - /// key.set_iv([ - /// 1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2, 3, 4, 5, 6, 7 - /// ]); - /// - /// assert_eq!( - /// key.to_string(), - /// "#EXT-X-KEY:METHOD=AES-128,URI=\"https://www.example.com/\",IV=0x01020304050607080901020304050607".to_string() - /// ); - /// ``` - pub fn set_iv(&mut self, value: T) -> &mut Self - where - T: Into<[u8; 16]>, - { - self.iv = Some(InitializationVector(value.into())); - self - } - - /// Returns a string that specifies how the key is - /// represented in the resource identified by the URI. - /// - //// This attribute is optional. - /// # Example - /// ``` - /// use hls_m3u8::tags::ExtXKey; - /// use hls_m3u8::types::EncryptionMethod; - /// - /// let mut key = ExtXKey::new( - /// EncryptionMethod::Aes128, - /// "https://www.example.com".parse().unwrap() - /// ); - /// - /// key.set_key_format("key_format_attribute"); - /// - /// assert_eq!( - /// key.key_format(), - /// &Some("key_format_attribute".to_string()) - /// ); - /// ``` - pub const fn key_format(&self) -> &Option { - &self.key_format - } - - /// Sets the `KEYFORMAT` attribute. - /// - /// This attribute is optional. - /// # Example - /// ``` - /// use hls_m3u8::tags::ExtXKey; - /// use hls_m3u8::types::EncryptionMethod; - /// - /// let mut key = ExtXKey::new( - /// EncryptionMethod::Aes128, - /// "https://www.example.com".parse().unwrap() - /// ); - /// - /// key.set_key_format("key_format_attribute"); - /// - /// assert_eq!( - /// key.to_string(), - /// "#EXT-X-KEY:METHOD=AES-128,URI=\"https://www.example.com/\",KEYFORMAT=\"key_format_attribute\"".to_string() - /// ); - /// ``` - pub fn set_key_format(&mut self, value: T) -> &mut Self { - self.key_format = Some(value.to_string()); - self - } - - /// Returns a string containing one or more positive - /// integers separated by the "/" character (for example, "1", "1/2", - /// or "1/2/5"). If more than one version of a particular `KEYFORMAT` - /// is defined, this attribute can be used to indicate which - /// version(s) this instance complies with. - /// - /// This attribute is optional. - /// # Example - /// ``` - /// use hls_m3u8::tags::ExtXKey; - /// use hls_m3u8::types::EncryptionMethod; - /// - /// let mut key = ExtXKey::new( - /// EncryptionMethod::Aes128, - /// "https://www.example.com".parse().unwrap() - /// ); - /// - /// key.set_key_format_versions("1/2/3/4/5"); - /// - /// assert_eq!( - /// key.key_format_versions(), - /// &Some("1/2/3/4/5".to_string()) - /// ); - /// ``` - pub const fn key_format_versions(&self) -> &Option { - &self.key_format_versions - } - - /// Sets the `KEYFORMATVERSIONS` attribute. - /// - /// This attribute is optional. - /// # Example - /// ``` - /// use hls_m3u8::tags::ExtXKey; - /// use hls_m3u8::types::EncryptionMethod; - /// - /// let mut key = ExtXKey::new( - /// EncryptionMethod::Aes128, - /// "https://www.example.com".parse().unwrap() - /// ); - /// - /// key.set_key_format_versions("1/2/3/4/5"); - /// - /// assert_eq!( - /// key.to_string(), - /// "#EXT-X-KEY:METHOD=AES-128,URI=\"https://www.example.com/\",KEYFORMATVERSIONS=\"1/2/3/4/5\"".to_string() - /// ); - /// ``` - pub fn set_key_format_versions(&mut self, value: T) -> &mut Self { - self.key_format_versions = Some(value.to_string()); - self - } - - /// Returns the protocol compatibility version that this tag requires. - /// # Example - /// ``` - /// use hls_m3u8::tags::ExtXKey; - /// use hls_m3u8::types::{EncryptionMethod, ProtocolVersion}; - /// - /// let mut key = ExtXKey::new( - /// EncryptionMethod::Aes128, - /// "https://www.example.com".parse().unwrap() - /// ); - /// - /// assert_eq!( - /// key.requires_version(), - /// ProtocolVersion::V1 - /// ); - /// ``` - pub fn requires_version(&self) -> ProtocolVersion { - if self.key_format.is_some() | self.key_format_versions.is_some() { - ProtocolVersion::V5 - } else if self.iv.is_some() { - ProtocolVersion::V2 - } else { - ProtocolVersion::V1 - } - } -} - -impl fmt::Display for ExtXKey { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}METHOD={}", Self::PREFIX, self.method)?; - - if self.method == EncryptionMethod::None { - return Ok(()); - } - if let Some(uri) = &self.uri { - write!(f, ",URI={}", quote(uri))?; - } - if let Some(value) = &self.iv { - write!(f, ",IV={}", value)?; - } - if let Some(value) = &self.key_format { - write!(f, ",KEYFORMAT={}", quote(value))?; - } - if let Some(value) = &self.key_format_versions { - write!(f, ",KEYFORMATVERSIONS={}", quote(value))?; - } - Ok(()) + self.0.method() == EncryptionMethod::None } } @@ -404,38 +86,27 @@ impl FromStr for ExtXKey { fn from_str(input: &str) -> Result { let input = tag(input, Self::PREFIX)?; - let mut method = None; - let mut uri = None; - let mut iv = None; - let mut key_format = None; - let mut key_format_versions = None; + Ok(Self(input.parse()?)) + } +} - for (key, value) in input.parse::()? { - match key.as_str() { - "METHOD" => method = Some((value.parse())?), - "URI" => uri = Some(unquote(value).parse()?), - "IV" => iv = Some((value.parse())?), - "KEYFORMAT" => key_format = Some(unquote(value)), - "KEYFORMATVERSIONS" => key_format_versions = Some(unquote(value)), - _ => { - // [6.3.1. General Client Responsibilities] - // > ignore any attribute/value pair with an unrecognized AttributeName. - } - } - } +impl fmt::Display for ExtXKey { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}{}", Self::PREFIX, self.0) + } +} - let method = method.ok_or(Error::missing_value("EXT-X-KEY:METHOD"))?; - if method != EncryptionMethod::None && uri.is_none() { - return Err(Error::missing_value("EXT-X-KEY:URI")); - } +impl Deref for ExtXKey { + type Target = DecryptionKey; - Ok(ExtXKey { - method, - uri, - iv, - key_format, - key_format_versions, - }) + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for ExtXKey { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 } } @@ -451,26 +122,16 @@ mod test { "#EXT-X-KEY:METHOD=NONE".to_string() ); - assert_eq!( - ExtXKey::empty().set_key_format("hi").to_string(), - "#EXT-X-KEY:METHOD=NONE".to_string() - ); - - let mut key = ExtXKey::new( - EncryptionMethod::Aes128, - "https://www.example.com/hls-key/key.bin".parse().unwrap(), - ); + let mut key = ExtXKey::empty(); + // it is expected, that all attributes will be ignored in an empty key! + key.set_key_format("hi"); key.set_iv([ 16, 239, 143, 117, 140, 165, 85, 17, 85, 132, 187, 91, 60, 104, 127, 82, ]); + key.set_uri("https://www.example.com".parse().unwrap()); + key.set_key_format_versions("1/2/3"); - assert_eq!( - key.to_string(), - "#EXT-X-KEY:METHOD=AES-128,\ - URI=\"https://www.example.com/hls-key/key.bin\",\ - IV=0x10ef8f758ca555115584bb5b3c687f52" - .to_string() - ); + assert_eq!(key.to_string(), "#EXT-X-KEY:METHOD=NONE".to_string()); } #[test] @@ -492,29 +153,5 @@ mod test { key.set_iv([ 16, 239, 143, 117, 140, 165, 85, 17, 85, 132, 187, 91, 60, 104, 127, 82, ]); - - assert_eq!( - "#EXT-X-KEY:METHOD=AES-128,\ - URI=\"https://www.example.com/hls-key/key.bin\",\ - IV=0X10ef8f758ca555115584bb5b3c687f52" - .parse::() - .unwrap(), - key - ); - - let mut key = ExtXKey::new( - EncryptionMethod::Aes128, - "http://www.example.com".parse().unwrap(), - ); - key.set_iv([ - 16, 239, 143, 117, 140, 165, 85, 17, 85, 132, 187, 91, 60, 104, 127, 82, - ]) - .set_key_format("baz"); - - assert_eq!( - r#"#EXT-X-KEY:METHOD=AES-128,URI="http://www.example.com",IV=0x10ef8f758ca555115584bb5b3c687f52,KEYFORMAT="baz""# - .parse::().unwrap(), - key - ) } } diff --git a/src/types/decryption_key.rs b/src/types/decryption_key.rs new file mode 100644 index 0000000..7e82ff9 --- /dev/null +++ b/src/types/decryption_key.rs @@ -0,0 +1,465 @@ +use std::fmt; +use std::str::FromStr; + +use derive_builder::Builder; +use url::Url; + +use crate::attribute::AttributePairs; +use crate::types::{EncryptionMethod, InitializationVector, ProtocolVersion}; +use crate::utils::{quote, unquote}; +use crate::Error; + +#[derive(Builder, Debug, Clone, PartialEq, Eq, Hash)] +#[builder(setter(into))] +pub struct DecryptionKey { + pub(crate) method: EncryptionMethod, + #[builder(setter(into, strip_option), default)] + pub(crate) uri: Option, + #[builder(setter(into, strip_option), default)] + pub(crate) iv: Option, + #[builder(setter(into, strip_option), default)] + pub(crate) key_format: Option, + #[builder(setter(into, strip_option), default)] + pub(crate) key_format_versions: Option, +} + +impl DecryptionKey { + /// Makes a new `DecryptionKey`. + /// # Example + /// ``` + /// use url::Url; + /// + /// use hls_m3u8::types::{EncryptionMethod, DecryptionKey}; + /// + /// let key = DecryptionKey::new( + /// EncryptionMethod::Aes128, + /// "https://www.example.com".parse().unwrap() + /// ); + /// + /// assert_eq!( + /// key.to_string(), + /// "METHOD=AES-128,URI=\"https://www.example.com/\"" + /// ); + /// ``` + pub const fn new(method: EncryptionMethod, uri: Url) -> Self { + Self { + method, + uri: Some(uri), + iv: None, + key_format: None, + key_format_versions: None, + } + } + + /// Returns the [EncryptionMethod]. + /// # Example + /// ``` + /// use hls_m3u8::types::{DecryptionKey, EncryptionMethod}; + /// + /// let key = DecryptionKey::new( + /// EncryptionMethod::Aes128, + /// "https://www.example.com".parse().unwrap() + /// ); + /// + /// assert_eq!( + /// key.method(), + /// EncryptionMethod::Aes128 + /// ); + /// ``` + pub const fn method(&self) -> EncryptionMethod { + self.method + } + + /// Returns a Builder to build a `DecryptionKey`. + pub fn builder() -> DecryptionKeyBuilder { + DecryptionKeyBuilder::default() + } + + /// Sets the [EncryptionMethod]. + /// # Example + /// ``` + /// use hls_m3u8::types::{DecryptionKey, EncryptionMethod}; + /// + /// let mut key = DecryptionKey::new( + /// EncryptionMethod::Aes128, + /// "https://www.example.com".parse().unwrap() + /// ); + /// + /// key.set_method(EncryptionMethod::SampleAes); + /// + /// assert_eq!( + /// key.to_string(), + /// "METHOD=SAMPLE-AES,URI=\"https://www.example.com/\"".to_string() + /// ); + /// ``` + pub fn set_method(&mut self, value: EncryptionMethod) { + self.method = value; + } + + /// Returns an `URI` that specifies how to obtain the key. + /// + /// This attribute is required, if the [EncryptionMethod] is not None. + /// # Example + /// ``` + /// use hls_m3u8::types::{DecryptionKey, EncryptionMethod}; + /// + /// let key = DecryptionKey::new( + /// EncryptionMethod::Aes128, + /// "https://www.example.com".parse().unwrap() + /// ); + /// + /// assert_eq!( + /// key.uri(), + /// &Some("https://www.example.com".parse().unwrap()) + /// ); + /// ``` + pub const fn uri(&self) -> &Option { + &self.uri + } + + /// Sets the `URI` attribute. + /// + /// This attribute is required, if the [EncryptionMethod] is not None. + /// # Example + /// ``` + /// use hls_m3u8::types::{DecryptionKey, EncryptionMethod}; + /// + /// let mut key = DecryptionKey::new( + /// EncryptionMethod::Aes128, + /// "https://www.example.com".parse().unwrap() + /// ); + /// + /// key.set_uri("http://www.google.com".parse().unwrap()); + /// + /// assert_eq!( + /// key.to_string(), + /// "METHOD=AES-128,URI=\"http://www.google.com/\"".to_string() + /// ); + /// ``` + pub fn set_uri(&mut self, value: Url) { + self.uri = Some(value); + } + + /// Returns the IV (Initialization Vector) attribute. + /// + /// This attribute is optional. + /// # Example + /// ``` + /// use hls_m3u8::types::{DecryptionKey, EncryptionMethod}; + /// + /// let mut key = DecryptionKey::new( + /// EncryptionMethod::Aes128, + /// "https://www.example.com".parse().unwrap() + /// ); + /// + /// key.set_iv([ + /// 1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2, 3, 4, 5, 6, 7 + /// ]); + /// + /// assert_eq!( + /// key.iv(), + /// Some([1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2, 3, 4, 5, 6, 7]) + /// ); + /// ``` + pub fn iv(&self) -> Option<[u8; 16]> { + if let Some(iv) = &self.iv { + Some(iv.to_slice()) + } else { + None + } + } + + /// Sets the `IV` attribute. + /// + /// This attribute is optional. + /// # Example + /// ``` + /// use hls_m3u8::types::{DecryptionKey, EncryptionMethod}; + /// + /// let mut key = DecryptionKey::new( + /// EncryptionMethod::Aes128, + /// "https://www.example.com".parse().unwrap() + /// ); + /// + /// key.set_iv([ + /// 1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2, 3, 4, 5, 6, 7 + /// ]); + /// + /// assert_eq!( + /// key.to_string(), + /// "METHOD=AES-128,URI=\"https://www.example.com/\",IV=0x01020304050607080901020304050607".to_string() + /// ); + /// ``` + pub fn set_iv(&mut self, value: T) + where + T: Into<[u8; 16]>, + { + self.iv = Some(InitializationVector(value.into())); + } + + /// Returns a string that specifies how the key is + /// represented in the resource identified by the URI. + /// + //// This attribute is optional. + /// # Example + /// ``` + /// use hls_m3u8::types::{DecryptionKey, EncryptionMethod}; + /// + /// let mut key = DecryptionKey::new( + /// EncryptionMethod::Aes128, + /// "https://www.example.com".parse().unwrap() + /// ); + /// + /// key.set_key_format("key_format_attribute"); + /// + /// assert_eq!( + /// key.key_format(), + /// &Some("key_format_attribute".to_string()) + /// ); + /// ``` + pub const fn key_format(&self) -> &Option { + &self.key_format + } + + /// Sets the `KEYFORMAT` attribute. + /// + /// This attribute is optional. + /// # Example + /// ``` + /// use hls_m3u8::types::{DecryptionKey, EncryptionMethod}; + /// + /// let mut key = DecryptionKey::new( + /// EncryptionMethod::Aes128, + /// "https://www.example.com".parse().unwrap() + /// ); + /// + /// key.set_key_format("key_format_attribute"); + /// + /// assert_eq!( + /// key.to_string(), + /// "METHOD=AES-128,URI=\"https://www.example.com/\",KEYFORMAT=\"key_format_attribute\"".to_string() + /// ); + /// ``` + pub fn set_key_format(&mut self, value: T) { + self.key_format = Some(value.to_string()); + } + + /// Returns a string containing one or more positive + /// integers separated by the "/" character (for example, "1", "1/2", + /// or "1/2/5"). If more than one version of a particular `KEYFORMAT` + /// is defined, this attribute can be used to indicate which + /// version(s) this instance complies with. + /// + /// This attribute is optional. + /// # Example + /// ``` + /// use hls_m3u8::types::{DecryptionKey, EncryptionMethod}; + /// + /// let mut key = DecryptionKey::new( + /// EncryptionMethod::Aes128, + /// "https://www.example.com".parse().unwrap() + /// ); + /// + /// key.set_key_format_versions("1/2/3/4/5"); + /// + /// assert_eq!( + /// key.key_format_versions(), + /// &Some("1/2/3/4/5".to_string()) + /// ); + /// ``` + pub const fn key_format_versions(&self) -> &Option { + &self.key_format_versions + } + + /// Sets the `KEYFORMATVERSIONS` attribute. + /// + /// This attribute is optional. + /// # Example + /// ``` + /// use hls_m3u8::types::{DecryptionKey, EncryptionMethod}; + /// + /// let mut key = DecryptionKey::new( + /// EncryptionMethod::Aes128, + /// "https://www.example.com".parse().unwrap() + /// ); + /// + /// key.set_key_format_versions("1/2/3/4/5"); + /// + /// assert_eq!( + /// key.to_string(), + /// "METHOD=AES-128,URI=\"https://www.example.com/\",KEYFORMATVERSIONS=\"1/2/3/4/5\"".to_string() + /// ); + /// ``` + pub fn set_key_format_versions(&mut self, value: T) { + self.key_format_versions = Some(value.to_string()); + } + + /// Returns the protocol compatibility version that this tag requires. + /// # Example + /// ``` + /// use hls_m3u8::types::{EncryptionMethod, ProtocolVersion, DecryptionKey}; + /// + /// let mut key = DecryptionKey::new( + /// EncryptionMethod::Aes128, + /// "https://www.example.com".parse().unwrap() + /// ); + /// + /// assert_eq!( + /// key.requires_version(), + /// ProtocolVersion::V1 + /// ); + /// ``` + pub fn requires_version(&self) -> ProtocolVersion { + if self.key_format.is_some() || self.key_format_versions.is_some() { + ProtocolVersion::V5 + } else if self.iv.is_some() { + ProtocolVersion::V2 + } else { + ProtocolVersion::V1 + } + } +} + +impl FromStr for DecryptionKey { + type Err = Error; + + fn from_str(input: &str) -> Result { + let mut method = None; + let mut uri = None; + let mut iv = None; + let mut key_format = None; + let mut key_format_versions = None; + + for (key, value) in input.parse::()? { + match key.as_str() { + "METHOD" => method = Some((value.parse())?), + "URI" => uri = Some(unquote(value).parse()?), + "IV" => iv = Some((value.parse())?), + "KEYFORMAT" => key_format = Some(unquote(value)), + "KEYFORMATVERSIONS" => key_format_versions = Some(unquote(value)), + _ => { + // [6.3.1. General Client Responsibilities] + // > ignore any attribute/value pair with an unrecognized AttributeName. + } + } + } + + let method = method.ok_or(Error::missing_value("METHOD"))?; + if method != EncryptionMethod::None && uri.is_none() { + return Err(Error::missing_value("URI")); + } + + Ok(DecryptionKey { + method, + uri, + iv, + key_format, + key_format_versions, + }) + } +} + +impl fmt::Display for DecryptionKey { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "METHOD={}", self.method)?; + + if self.method == EncryptionMethod::None { + return Ok(()); + } + if let Some(uri) = &self.uri { + write!(f, ",URI={}", quote(uri))?; + } + if let Some(value) = &self.iv { + write!(f, ",IV={}", value)?; + } + if let Some(value) = &self.key_format { + write!(f, ",KEYFORMAT={}", quote(value))?; + } + if let Some(value) = &self.key_format_versions { + write!(f, ",KEYFORMATVERSIONS={}", quote(value))?; + } + Ok(()) + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::types::EncryptionMethod; + + #[test] + fn test_requires_version() { + let key = DecryptionKey::builder() + .method(EncryptionMethod::Aes128) + .uri("https://www.example.com".parse::().unwrap()) + .iv([ + 16, 239, 143, 117, 140, 165, 85, 17, 85, 132, 187, 91, 60, 104, 127, 82, + ]) + .build() + .unwrap(); + } + + #[test] + fn test_display() { + let mut key = DecryptionKey::new( + EncryptionMethod::Aes128, + "https://www.example.com/hls-key/key.bin".parse().unwrap(), + ); + key.set_iv([ + 16, 239, 143, 117, 140, 165, 85, 17, 85, 132, 187, 91, 60, 104, 127, 82, + ]); + + assert_eq!( + key.to_string(), + "METHOD=AES-128,\ + URI=\"https://www.example.com/hls-key/key.bin\",\ + IV=0x10ef8f758ca555115584bb5b3c687f52" + .to_string() + ); + } + + #[test] + fn test_parser() { + assert_eq!( + r#"METHOD=AES-128,URI="https://priv.example.com/key.php?r=52""# + .parse::() + .unwrap(), + DecryptionKey::new( + EncryptionMethod::Aes128, + "https://priv.example.com/key.php?r=52".parse().unwrap() + ) + ); + + let mut key = DecryptionKey::new( + EncryptionMethod::Aes128, + "https://www.example.com/hls-key/key.bin".parse().unwrap(), + ); + key.set_iv([ + 16, 239, 143, 117, 140, 165, 85, 17, 85, 132, 187, 91, 60, 104, 127, 82, + ]); + + assert_eq!( + "METHOD=AES-128,\ + URI=\"https://www.example.com/hls-key/key.bin\",\ + IV=0X10ef8f758ca555115584bb5b3c687f52" + .parse::() + .unwrap(), + key + ); + + let mut key = DecryptionKey::new( + EncryptionMethod::Aes128, + "http://www.example.com".parse().unwrap(), + ); + key.set_iv([ + 16, 239, 143, 117, 140, 165, 85, 17, 85, 132, 187, 91, 60, 104, 127, 82, + ]); + key.set_key_format("baz"); + + assert_eq!( + r#"METHOD=AES-128,URI="http://www.example.com",IV=0x10ef8f758ca555115584bb5b3c687f52,KEYFORMAT="baz""# + .parse::().unwrap(), + key + ) + } +} diff --git a/src/types/initialization_vector.rs b/src/types/initialization_vector.rs index 447e8c8..8b008a7 100644 --- a/src/types/initialization_vector.rs +++ b/src/types/initialization_vector.rs @@ -10,7 +10,7 @@ use crate::Error; /// /// [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(crate) struct InitializationVector(pub [u8; 16]); +pub struct InitializationVector(pub [u8; 16]); impl InitializationVector { pub const fn to_slice(&self) -> [u8; 16] { diff --git a/src/types/mod.rs b/src/types/mod.rs index ea47eaa..6301ba4 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -3,6 +3,7 @@ mod byte_range; mod closed_captions; mod decimal_floating_point; mod decimal_resolution; +mod decryption_key; mod encryption_method; mod hdcp_level; mod in_stream_id; @@ -15,10 +16,11 @@ pub use byte_range::*; pub use closed_captions::*; pub(crate) use decimal_floating_point::*; pub(crate) use decimal_resolution::*; +pub use decryption_key::*; pub use encryption_method::*; pub use hdcp_level::*; pub use in_stream_id::*; -pub(crate) use initialization_vector::*; +pub use initialization_vector::*; pub use media_type::*; pub use protocol_version::*; pub(crate) use signed_decimal_floating_point::*;