mirror of
https://github.com/sile/hls_m3u8.git
synced 2025-03-30 00:45:27 +00:00
updated ExtXKey + ExtXSessionKey #9
This commit is contained in:
parent
db6961d19f
commit
1d614d580a
10 changed files with 947 additions and 235 deletions
|
@ -51,8 +51,8 @@ impl FromStr for AttributePairs {
|
|||
fn from_str(input: &str) -> Result<Self, Self::Err> {
|
||||
let mut result = AttributePairs::new();
|
||||
|
||||
for line in split(input) {
|
||||
let pair = line.trim().split("=").collect::<Vec<_>>();
|
||||
for line in split(input, ',') {
|
||||
let pair = split(line.trim(), '=');
|
||||
|
||||
if pair.len() < 2 {
|
||||
return Err(Error::invalid_input());
|
||||
|
@ -63,11 +63,13 @@ impl FromStr for AttributePairs {
|
|||
|
||||
result.insert(key.to_string(), value.to_string());
|
||||
}
|
||||
|
||||
dbg!(&result);
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
fn split(value: &str) -> Vec<String> {
|
||||
fn split(value: &str, terminator: char) -> Vec<String> {
|
||||
let mut result = vec![];
|
||||
|
||||
let mut inside_quotes = false;
|
||||
|
@ -83,7 +85,7 @@ fn split(value: &str) -> Vec<String> {
|
|||
}
|
||||
temp_string.push(c);
|
||||
}
|
||||
',' => {
|
||||
k if (k == terminator) => {
|
||||
if !inside_quotes {
|
||||
result.push(temp_string);
|
||||
temp_string = String::new();
|
||||
|
|
|
@ -109,8 +109,6 @@ impl MasterPlaylistBuilder {
|
|||
.map_err(|e| e.to_string())?;
|
||||
self.validate_session_data_tags()
|
||||
.map_err(|e| e.to_string())?;
|
||||
self.validate_session_key_tags()
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -232,18 +230,6 @@ impl MasterPlaylistBuilder {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn validate_session_key_tags(&self) -> crate::Result<()> {
|
||||
let mut set = HashSet::new();
|
||||
if let Some(value) = &self.session_key_tags {
|
||||
for t in value {
|
||||
if !set.insert(t.key()) {
|
||||
return Err(Error::custom(format!("Conflict: {}", t)));
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn check_media_group<T: ToString>(&self, media_type: MediaType, group_id: T) -> bool {
|
||||
if let Some(value) = &self.media_tags {
|
||||
value
|
||||
|
|
|
@ -4,10 +4,22 @@ use std::str::FromStr;
|
|||
use getset::{Getters, MutGetters, Setters};
|
||||
|
||||
use crate::attribute::AttributePairs;
|
||||
use crate::types::{ProtocolVersion, SessionData};
|
||||
use crate::types::ProtocolVersion;
|
||||
use crate::utils::{quote, tag, unquote};
|
||||
use crate::Error;
|
||||
|
||||
/// Session data.
|
||||
///
|
||||
/// See: [4.3.4.4. EXT-X-SESSION-DATA]
|
||||
///
|
||||
/// [4.3.4.4. EXT-X-SESSION-DATA]: https://tools.ietf.org/html/rfc8216#section-4.3.4.4
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum SessionData {
|
||||
Value(String),
|
||||
Uri(String),
|
||||
}
|
||||
|
||||
/// [4.3.4.4. EXT-X-SESSION-DATA]
|
||||
///
|
||||
/// [4.3.4.4. EXT-X-SESSION-DATA]: https://tools.ietf.org/html/rfc8216#section-4.3.4.4
|
||||
|
|
|
@ -1,71 +1,444 @@
|
|||
use std::fmt;
|
||||
use std::str::FromStr;
|
||||
|
||||
use crate::types::{DecryptionKey, ProtocolVersion};
|
||||
use crate::utils::tag;
|
||||
use url::Url;
|
||||
|
||||
use crate::attribute::AttributePairs;
|
||||
use crate::types::{EncryptionMethod, InitializationVector, ProtocolVersion};
|
||||
use crate::utils::{quote, tag, unquote};
|
||||
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 {
|
||||
key: DecryptionKey,
|
||||
method: EncryptionMethod,
|
||||
uri: Option<Url>,
|
||||
iv: Option<InitializationVector>,
|
||||
key_format: Option<String>,
|
||||
key_format_versions: Option<String>,
|
||||
}
|
||||
|
||||
impl ExtXSessionKey {
|
||||
pub(crate) const PREFIX: &'static str = "#EXT-X-SESSION-KEY:";
|
||||
|
||||
/// Makes a new `ExtXSessionKey` tag.
|
||||
pub const fn new(key: DecryptionKey) -> Self {
|
||||
ExtXSessionKey { key }
|
||||
/// Makes a new [ExtXSessionKey] tag.
|
||||
/// # Panic
|
||||
/// This method will panic, if the [EncryptionMethod] is None.
|
||||
pub fn new(method: EncryptionMethod, uri: Url) -> Self {
|
||||
if method == EncryptionMethod::None {
|
||||
panic!("The EncryptionMethod is not allowed to be None");
|
||||
}
|
||||
|
||||
Self {
|
||||
method,
|
||||
uri: Some(uri),
|
||||
iv: None,
|
||||
key_format: None,
|
||||
key_format_versions: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a decryption key for the playlist.
|
||||
pub const fn key(&self) -> &DecryptionKey {
|
||||
&self.key
|
||||
/// 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<Url> {
|
||||
&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<T>(&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<String> {
|
||||
&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<T: ToString>(&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<String> {
|
||||
&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<T: ToString>(&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::ExtXSessionKey;
|
||||
/// use hls_m3u8::types::{EncryptionMethod, ProtocolVersion};
|
||||
///
|
||||
/// let mut key = ExtXSessionKey::new(
|
||||
/// EncryptionMethod::Aes128,
|
||||
/// "https://www.example.com".parse().unwrap()
|
||||
/// );
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// key.requires_version(),
|
||||
/// ProtocolVersion::V1
|
||||
/// );
|
||||
/// ```
|
||||
pub fn requires_version(&self) -> ProtocolVersion {
|
||||
self.key.requires_version()
|
||||
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 ExtXSessionKey {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}{}", Self::PREFIX, self.key)
|
||||
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(())
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for ExtXSessionKey {
|
||||
type Err = crate::Error;
|
||||
type Err = Error;
|
||||
|
||||
fn from_str(input: &str) -> Result<Self, Self::Err> {
|
||||
let key = tag(input, Self::PREFIX)?.parse()?;
|
||||
Ok(Self::new(key))
|
||||
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;
|
||||
|
||||
for (key, value) in input.parse::<AttributePairs>()? {
|
||||
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("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,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::types::{EncryptionMethod, InitializationVector};
|
||||
use crate::types::EncryptionMethod;
|
||||
|
||||
#[test]
|
||||
fn ext_x_session_key() {
|
||||
let tag = ExtXSessionKey::new(DecryptionKey {
|
||||
method: EncryptionMethod::Aes128,
|
||||
uri: "foo".to_string(),
|
||||
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=0x000102030405060708090a0b0c0d0e0f"#;
|
||||
assert_eq!(text.parse().ok(), Some(tag.clone()));
|
||||
assert_eq!(tag.to_string(), text);
|
||||
assert_eq!(tag.requires_version(), ProtocolVersion::V2);
|
||||
fn test_display() {
|
||||
let mut key = ExtXSessionKey::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(),
|
||||
"#EXT-X-SESSION-KEY:METHOD=AES-128,\
|
||||
URI=\"https://www.example.com/hls-key/key.bin\",\
|
||||
IV=0x10ef8f758ca555115584bb5b3c687f52"
|
||||
.to_string()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parser() {
|
||||
assert_eq!(
|
||||
r#"#EXT-X-SESSION-KEY:METHOD=AES-128,URI="https://priv.example.com/key.php?r=52""#
|
||||
.parse::<ExtXSessionKey>()
|
||||
.unwrap(),
|
||||
ExtXSessionKey::new(
|
||||
EncryptionMethod::Aes128,
|
||||
"https://priv.example.com/key.php?r=52".parse().unwrap()
|
||||
)
|
||||
);
|
||||
|
||||
let mut key = ExtXSessionKey::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!(
|
||||
"#EXT-X-SESSION-KEY:METHOD=AES-128,\
|
||||
URI=\"https://www.example.com/hls-key/key.bin\",\
|
||||
IV=0X10ef8f758ca555115584bb5b3c687f52"
|
||||
.parse::<ExtXSessionKey>()
|
||||
.unwrap(),
|
||||
key
|
||||
);
|
||||
|
||||
key.set_key_format("baz");
|
||||
|
||||
assert_eq!(
|
||||
r#"#EXT-X-SESSION-KEY:METHOD=AES-128,URI="https://www.example.com/hls-key/key.bin",IV=0x10ef8f758ca555115584bb5b3c687f52,KEYFORMAT="baz""#
|
||||
.parse::<ExtXSessionKey>().unwrap(),
|
||||
key
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,52 +1,399 @@
|
|||
use std::fmt;
|
||||
use std::str::FromStr;
|
||||
|
||||
use url::Url;
|
||||
|
||||
use crate::attribute::AttributePairs;
|
||||
use crate::types::{DecryptionKey, ProtocolVersion};
|
||||
use crate::utils::tag;
|
||||
use crate::types::{EncryptionMethod, InitializationVector, ProtocolVersion};
|
||||
use crate::utils::{quote, tag, unquote};
|
||||
use crate::Error;
|
||||
|
||||
/// [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
|
||||
/// # Note
|
||||
/// In case of an empty key (`EncryptionMethod::None`), all attributes will be ignored.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct ExtXKey(Option<DecryptionKey>);
|
||||
pub struct ExtXKey {
|
||||
method: EncryptionMethod,
|
||||
uri: Option<Url>,
|
||||
iv: Option<InitializationVector>,
|
||||
key_format: Option<String>,
|
||||
key_format_versions: Option<String>,
|
||||
}
|
||||
|
||||
impl ExtXKey {
|
||||
pub(crate) const PREFIX: &'static str = "#EXT-X-KEY:";
|
||||
|
||||
/// Makes a new `ExtXKey` tag.
|
||||
pub const fn new(key: DecryptionKey) -> Self {
|
||||
Self(Some(key))
|
||||
/// # Example
|
||||
/// ```
|
||||
/// use url::Url;
|
||||
///
|
||||
/// 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.to_string(),
|
||||
/// "#EXT-X-KEY: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,
|
||||
}
|
||||
}
|
||||
|
||||
/// Makes a new `ExtXKey` tag without a decryption key.
|
||||
/// # Example
|
||||
/// ```
|
||||
/// use hls_m3u8::tags::ExtXKey;
|
||||
///
|
||||
/// This tag has the `METHDO=NONE` attribute.
|
||||
pub const fn new_without_key() -> Self {
|
||||
Self(None)
|
||||
/// let key = ExtXKey::empty();
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// key.to_string(),
|
||||
/// "#EXT-X-KEY:METHOD=NONE"
|
||||
/// );
|
||||
/// ```
|
||||
pub const fn empty() -> Self {
|
||||
Self {
|
||||
method: EncryptionMethod::None,
|
||||
uri: None,
|
||||
iv: None,
|
||||
key_format: None,
|
||||
key_format_versions: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the decryption key for the following media segments and media initialization sections.
|
||||
pub fn key(&self) -> Option<&DecryptionKey> {
|
||||
self.0.as_ref()
|
||||
/// Returns whether the EncryptionMethod is None.
|
||||
/// # Example
|
||||
/// ```
|
||||
/// use hls_m3u8::tags::ExtXKey;
|
||||
/// use hls_m3u8::types::EncryptionMethod;
|
||||
///
|
||||
/// let key = ExtXKey::empty();
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// key.method() == EncryptionMethod::None,
|
||||
/// key.is_empty()
|
||||
/// );
|
||||
/// ```
|
||||
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<Url> {
|
||||
&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<T>(&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<String> {
|
||||
&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<T: ToString>(&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<String> {
|
||||
&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<T: ToString>(&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 {
|
||||
self.0
|
||||
.as_ref()
|
||||
.map_or(ProtocolVersion::V1, |k| k.requires_version())
|
||||
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, "{}", Self::PREFIX)?;
|
||||
if let Some(value) = &self.0 {
|
||||
write!(f, "{}", value)?;
|
||||
} else {
|
||||
write!(f, "METHOD=NONE")?;
|
||||
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(())
|
||||
}
|
||||
|
@ -57,74 +404,117 @@ impl FromStr for ExtXKey {
|
|||
|
||||
fn from_str(input: &str) -> Result<Self, Self::Err> {
|
||||
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;
|
||||
|
||||
let pairs = input.parse::<AttributePairs>()?;
|
||||
|
||||
if pairs.iter().any(|(k, v)| k == "METHOD" && v == "NONE") {
|
||||
for (key, _) in pairs {
|
||||
if key == "URI" || key == "IV" || key == "KEYFORMAT" || key == "KEYFORMATVERSIONS" {
|
||||
return Err(Error::invalid_input());
|
||||
for (key, value) in input.parse::<AttributePairs>()? {
|
||||
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.
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Self(None))
|
||||
} else {
|
||||
Ok(Self(Some(input.parse()?)))
|
||||
}
|
||||
|
||||
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"));
|
||||
}
|
||||
|
||||
Ok(ExtXKey {
|
||||
method,
|
||||
uri,
|
||||
iv,
|
||||
key_format,
|
||||
key_format_versions,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::types::{EncryptionMethod, InitializationVector};
|
||||
use crate::types::EncryptionMethod;
|
||||
|
||||
#[test]
|
||||
fn ext_x_key() {
|
||||
let tag = ExtXKey::new_without_key();
|
||||
let text = "#EXT-X-KEY:METHOD=NONE";
|
||||
assert_eq!(text.parse().ok(), Some(tag.clone()));
|
||||
assert_eq!(tag.to_string(), text);
|
||||
assert_eq!(tag.requires_version(), ProtocolVersion::V1);
|
||||
fn test_display() {
|
||||
assert_eq!(
|
||||
ExtXKey::empty().to_string(),
|
||||
"#EXT-X-KEY:METHOD=NONE".to_string()
|
||||
);
|
||||
|
||||
let tag = ExtXKey::new(DecryptionKey {
|
||||
method: EncryptionMethod::Aes128,
|
||||
uri: "foo".to_string(),
|
||||
iv: None,
|
||||
key_format: None,
|
||||
key_format_versions: None,
|
||||
});
|
||||
let text = r#"#EXT-X-KEY:METHOD=AES-128,URI="foo""#;
|
||||
assert_eq!(text.parse().ok(), Some(tag.clone()));
|
||||
assert_eq!(tag.to_string(), text);
|
||||
assert_eq!(tag.requires_version(), ProtocolVersion::V1);
|
||||
assert_eq!(
|
||||
ExtXKey::empty().set_key_format("hi").to_string(),
|
||||
"#EXT-X-KEY:METHOD=NONE".to_string()
|
||||
);
|
||||
|
||||
let tag = ExtXKey::new(DecryptionKey {
|
||||
method: EncryptionMethod::Aes128,
|
||||
uri: "foo".to_string(),
|
||||
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=0x000102030405060708090a0b0c0d0e0f"#;
|
||||
assert_eq!(text.parse().ok(), Some(tag.clone()));
|
||||
assert_eq!(tag.to_string(), text);
|
||||
assert_eq!(tag.requires_version(), ProtocolVersion::V2);
|
||||
let mut key = ExtXKey::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,
|
||||
]);
|
||||
|
||||
let tag = ExtXKey::new(DecryptionKey {
|
||||
method: EncryptionMethod::Aes128,
|
||||
uri: "foo".to_string(),
|
||||
iv: Some(InitializationVector([
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
|
||||
])),
|
||||
key_format: Some("baz".to_string()),
|
||||
key_format_versions: None,
|
||||
});
|
||||
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);
|
||||
assert_eq!(
|
||||
key.to_string(),
|
||||
"#EXT-X-KEY:METHOD=AES-128,\
|
||||
URI=\"https://www.example.com/hls-key/key.bin\",\
|
||||
IV=0x10ef8f758ca555115584bb5b3c687f52"
|
||||
.to_string()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parser() {
|
||||
assert_eq!(
|
||||
r#"#EXT-X-KEY:METHOD=AES-128,URI="https://priv.example.com/key.php?r=52""#
|
||||
.parse::<ExtXKey>()
|
||||
.unwrap(),
|
||||
ExtXKey::new(
|
||||
EncryptionMethod::Aes128,
|
||||
"https://priv.example.com/key.php?r=52".parse().unwrap()
|
||||
)
|
||||
);
|
||||
|
||||
let mut key = ExtXKey::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!(
|
||||
"#EXT-X-KEY:METHOD=AES-128,\
|
||||
URI=\"https://www.example.com/hls-key/key.bin\",\
|
||||
IV=0X10ef8f758ca555115584bb5b3c687f52"
|
||||
.parse::<ExtXKey>()
|
||||
.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::<ExtXKey>().unwrap(),
|
||||
key
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,88 +1 @@
|
|||
use std::fmt;
|
||||
use std::str::{self, FromStr};
|
||||
|
||||
use crate::attribute::AttributePairs;
|
||||
use crate::types::{EncryptionMethod, InitializationVector, ProtocolVersion};
|
||||
use crate::utils::{quote, unquote};
|
||||
use crate::Error;
|
||||
|
||||
/// Decryption key.
|
||||
///
|
||||
/// 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
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct DecryptionKey {
|
||||
pub method: EncryptionMethod,
|
||||
pub uri: String,
|
||||
pub iv: Option<InitializationVector>,
|
||||
pub key_format: Option<String>,
|
||||
pub key_format_versions: Option<String>,
|
||||
}
|
||||
|
||||
impl DecryptionKey {
|
||||
pub(crate) 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 DecryptionKey {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "METHOD={}", self.method)?;
|
||||
write!(f, ",URI={}", quote(&self.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(())
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for DecryptionKey {
|
||||
type Err = Error;
|
||||
|
||||
fn from_str(input: &str) -> Result<Self, Self::Err> {
|
||||
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::<AttributePairs>()? {
|
||||
match key.as_str() {
|
||||
"METHOD" => method = Some((value.parse())?),
|
||||
"URI" => uri = Some(unquote(value)),
|
||||
"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("EXT-X-METHOD"))?;
|
||||
let uri = uri.ok_or(Error::missing_value("EXT-X-URI"))?;
|
||||
|
||||
Ok(DecryptionKey {
|
||||
method,
|
||||
uri,
|
||||
iv,
|
||||
key_format,
|
||||
key_format_versions,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,7 +11,40 @@ use crate::Error;
|
|||
#[allow(missing_docs)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum EncryptionMethod {
|
||||
/// `None` means that [MediaSegment]s are not encrypted.
|
||||
///
|
||||
/// [MediaSegment]: crate::MediaSegment
|
||||
None,
|
||||
/// `Aes128` signals that the [MediaSegment]s are completely encrypted
|
||||
/// using the Advanced Encryption Standard ([AES_128]) with a 128-bit
|
||||
/// key, Cipher Block Chaining (CBC), and
|
||||
/// [Public-Key Cryptography Standards #7 (PKCS7)] padding.
|
||||
/// CBC is restarted on each segment boundary, using either the
|
||||
/// Initialization Vector (IV) attribute value or the Media Sequence
|
||||
/// Number as the IV.
|
||||
///
|
||||
/// [MediaSegment]: crate::MediaSegment
|
||||
/// [AES_128]: http://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.197.pdf
|
||||
/// [Public-Key Cryptography Standards #7 (PKCS7)]: https://tools.ietf.org/html/rfc5652
|
||||
Aes128,
|
||||
/// `SampleAes` means that the [MediaSegment]s
|
||||
/// contain media samples, such as audio or video, that are encrypted
|
||||
/// using the Advanced Encryption Standard ([AES_128]). How these media
|
||||
/// streams are encrypted and encapsulated in a segment depends on the
|
||||
/// media encoding and the media format of the segment. fMP4 Media
|
||||
/// Segments are encrypted using the 'cbcs' scheme of
|
||||
/// [Common Encryption]. Encryption of other Media Segment
|
||||
/// formats containing [H.264], [AAC], [AC-3],
|
||||
/// and Enhanced [AC-3] media streams is described in the HTTP
|
||||
/// Live Streaming (HLS) [SampleEncryption specification].
|
||||
///
|
||||
/// [MediaSegment]: crate::MediaSegment
|
||||
/// [AES_128]: http://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.197.pdf
|
||||
/// [Common Encryption]: https://tools.ietf.org/html/rfc8216#ref-COMMON_ENC
|
||||
/// [H.264]: https://tools.ietf.org/html/rfc8216#ref-H_264
|
||||
/// [AAC]: https://tools.ietf.org/html/rfc8216#ref-ISO_14496
|
||||
/// [AC-3]: https://tools.ietf.org/html/rfc8216#ref-AC_3
|
||||
/// [SampleEncryption specification]: https://tools.ietf.org/html/rfc8216#ref-SampleEnc
|
||||
SampleAes,
|
||||
}
|
||||
|
||||
|
@ -20,6 +53,7 @@ impl fmt::Display for EncryptionMethod {
|
|||
match &self {
|
||||
EncryptionMethod::Aes128 => "AES-128".fmt(f),
|
||||
EncryptionMethod::SampleAes => "SAMPLE-AES".fmt(f),
|
||||
EncryptionMethod::None => "NONE".fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -31,6 +65,7 @@ impl FromStr for EncryptionMethod {
|
|||
match input {
|
||||
"AES-128" => Ok(EncryptionMethod::Aes128),
|
||||
"SAMPLE-AES" => Ok(EncryptionMethod::SampleAes),
|
||||
"NONE" => Ok(EncryptionMethod::None),
|
||||
_ => Err(Error::custom(format!(
|
||||
"Unknown encryption method: {:?}",
|
||||
input
|
||||
|
@ -45,25 +80,29 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_display() {
|
||||
let encryption_method = EncryptionMethod::Aes128;
|
||||
assert_eq!(encryption_method.to_string(), "AES-128".to_string());
|
||||
|
||||
let encryption_method = EncryptionMethod::SampleAes;
|
||||
assert_eq!(encryption_method.to_string(), "SAMPLE-AES".to_string());
|
||||
assert_eq!(EncryptionMethod::Aes128.to_string(), "AES-128".to_string());
|
||||
assert_eq!(
|
||||
EncryptionMethod::SampleAes.to_string(),
|
||||
"SAMPLE-AES".to_string()
|
||||
);
|
||||
assert_eq!(EncryptionMethod::None.to_string(), "NONE".to_string());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse() {
|
||||
let encryption_method = EncryptionMethod::Aes128;
|
||||
assert_eq!(
|
||||
encryption_method,
|
||||
EncryptionMethod::Aes128,
|
||||
"AES-128".parse::<EncryptionMethod>().unwrap()
|
||||
);
|
||||
|
||||
let encryption_method = EncryptionMethod::SampleAes;
|
||||
assert_eq!(
|
||||
encryption_method,
|
||||
EncryptionMethod::SampleAes,
|
||||
"SAMPLE-AES".parse::<EncryptionMethod>().unwrap()
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
EncryptionMethod::None,
|
||||
"NONE".parse::<EncryptionMethod>().unwrap()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,7 +10,19 @@ 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 struct InitializationVector(pub [u8; 16]);
|
||||
pub(crate) struct InitializationVector(pub [u8; 16]);
|
||||
|
||||
impl InitializationVector {
|
||||
pub const fn to_slice(&self) -> [u8; 16] {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<[u8; 16]> for InitializationVector {
|
||||
fn from(value: [u8; 16]) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for InitializationVector {
|
||||
type Target = [u8];
|
||||
|
|
|
@ -3,7 +3,6 @@ mod byte_range;
|
|||
mod closed_captions;
|
||||
mod decimal_floating_point;
|
||||
mod decimal_resolution;
|
||||
mod decryption_key;
|
||||
mod encryption_method;
|
||||
mod hdcp_level;
|
||||
mod hexadecimal_sequence;
|
||||
|
@ -11,20 +10,17 @@ mod in_stream_id;
|
|||
mod initialization_vector;
|
||||
mod media_type;
|
||||
mod protocol_version;
|
||||
mod session_data;
|
||||
mod signed_decimal_floating_point;
|
||||
|
||||
pub use byte_range::*;
|
||||
pub use closed_captions::*;
|
||||
pub use decimal_floating_point::*;
|
||||
pub(crate) use decimal_resolution::*;
|
||||
pub use decryption_key::*;
|
||||
pub use encryption_method::*;
|
||||
pub use hdcp_level::*;
|
||||
pub use hexadecimal_sequence::*;
|
||||
pub use in_stream_id::*;
|
||||
pub use initialization_vector::*;
|
||||
pub(crate) use initialization_vector::*;
|
||||
pub use media_type::*;
|
||||
pub use protocol_version::*;
|
||||
pub use session_data::*;
|
||||
pub use signed_decimal_floating_point::*;
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
/// Session data.
|
||||
///
|
||||
/// See: [4.3.4.4. EXT-X-SESSION-DATA]
|
||||
///
|
||||
/// [4.3.4.4. EXT-X-SESSION-DATA]: https://tools.ietf.org/html/rfc8216#section-4.3.4.4
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum SessionData {
|
||||
Value(String),
|
||||
Uri(String),
|
||||
}
|
Loading…
Reference in a new issue