1
0
Fork 0
mirror of https://github.com/sile/hls_m3u8.git synced 2024-05-25 03:38:21 +00:00

remove DecryptionKey

This commit is contained in:
Luro02 2020-02-21 10:45:04 +01:00
parent a96367e3fa
commit c39d104137
No known key found for this signature in database
GPG key ID: B66FD4F74501A9CF
5 changed files with 480 additions and 516 deletions

View file

@ -1,9 +1,11 @@
use core::convert::TryFrom;
use std::fmt;
use std::str::FromStr;
use derive_more::{Deref, DerefMut};
use crate::types::{DecryptionKey, EncryptionMethod, ProtocolVersion};
use crate::tags::ExtXKey;
use crate::types::{EncryptionMethod, ProtocolVersion};
use crate::utils::tag;
use crate::{Error, RequiredVersion};
@ -18,7 +20,7 @@ use crate::{Error, RequiredVersion};
/// [`MasterPlaylist`]: crate::MasterPlaylist
/// [4.3.4.5. EXT-X-SESSION-KEY]: https://tools.ietf.org/html/rfc8216#section-4.3.4.5
#[derive(Deref, DerefMut, Debug, Clone, PartialEq, Eq, Hash)]
pub struct ExtXSessionKey(DecryptionKey);
pub struct ExtXSessionKey(ExtXKey);
impl ExtXSessionKey {
pub(crate) const PREFIX: &'static str = "#EXT-X-SESSION-KEY:";
@ -35,17 +37,34 @@ impl ExtXSessionKey {
/// # Example
///
/// ```
/// # use hls_m3u8::tags::ExtXSessionKey;
/// # use hls_m3u8::tags::{ExtXSessionKey, ExtXKey};
/// use hls_m3u8::types::EncryptionMethod;
///
/// let session_key = ExtXSessionKey::new(EncryptionMethod::Aes128, "https://www.example.com/");
/// ExtXSessionKey::new(ExtXKey::new(
/// EncryptionMethod::Aes128,
/// "https://www.example.com/",
/// ));
/// ```
pub fn new<T: Into<String>>(method: EncryptionMethod, uri: T) -> Self {
if method == EncryptionMethod::None {
panic!("The EncryptionMethod is not allowed to be None");
pub fn new(inner: ExtXKey) -> Self {
if inner.method() == EncryptionMethod::None {
panic!("the encryption method should never be `None`");
}
Self(DecryptionKey::new(method, uri))
Self(inner)
}
}
impl TryFrom<ExtXKey> for ExtXSessionKey {
type Error = Error;
fn try_from(value: ExtXKey) -> Result<Self, Self::Error> {
if value.method() == EncryptionMethod::None {
return Err(Error::custom(
"the encryption method should never be `None`",
));
}
Ok(Self(value))
}
}
@ -57,12 +76,13 @@ impl RequiredVersion for ExtXSessionKey {
impl fmt::Display for ExtXSessionKey {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if self.0.method == EncryptionMethod::None {
// TODO: this is bad practice, this function should never fail!
return Err(fmt::Error);
}
write!(f, "{}{}", Self::PREFIX, self.0)
// TODO: this is not the most elegant solution
write!(
f,
"{}{}",
Self::PREFIX,
self.0.to_string().replacen(ExtXKey::PREFIX, "", 1)
)
}
}
@ -70,8 +90,7 @@ impl FromStr for ExtXSessionKey {
type Err = Error;
fn from_str(input: &str) -> Result<Self, Self::Err> {
let input = tag(input, Self::PREFIX)?;
Ok(Self(input.parse()?))
Ok(Self(ExtXKey::parse_from_str(tag(input, Self::PREFIX)?)?))
}
}
@ -83,62 +102,75 @@ mod test {
#[test]
fn test_display() {
let mut key = ExtXSessionKey::new(
let mut key = ExtXSessionKey::new(ExtXKey::new(
EncryptionMethod::Aes128,
"https://www.example.com/hls-key/key.bin",
);
));
key.set_iv(Some([
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()
concat!(
"#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(
concat!(
"#EXT-X-SESSION-KEY:",
"METHOD=AES-128,",
"URI=\"https://priv.example.com/key.php?r=52\""
)
.parse::<ExtXSessionKey>()
.unwrap(),
ExtXSessionKey::new(ExtXKey::new(
EncryptionMethod::Aes128,
"https://priv.example.com/key.php?r=52"
)
))
);
let mut key = ExtXSessionKey::new(
let mut key = ExtXSessionKey::new(ExtXKey::new(
EncryptionMethod::Aes128,
"https://www.example.com/hls-key/key.bin",
);
));
key.set_iv(Some([
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(),
concat!(
"#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(Some(KeyFormat::Identity));
assert_eq!(
"#EXT-X-SESSION-KEY:\
METHOD=AES-128,\
URI=\"https://www.example.com/hls-key/key.bin\",\
IV=0x10ef8f758ca555115584bb5b3c687f52,\
KEYFORMAT=\"identity\""
.parse::<ExtXSessionKey>()
.unwrap(),
concat!(
"#EXT-X-SESSION-KEY:",
"METHOD=AES-128,",
"URI=\"https://www.example.com/hls-key/key.bin\",",
"IV=0x10ef8f758ca555115584bb5b3c687f52,",
"KEYFORMAT=\"identity\"",
)
.parse::<ExtXSessionKey>()
.unwrap(),
key
)
}
@ -146,8 +178,11 @@ mod test {
#[test]
fn test_required_version() {
assert_eq!(
ExtXSessionKey::new(EncryptionMethod::Aes128, "https://www.example.com/")
.required_version(),
ExtXSessionKey::new(ExtXKey::new(
EncryptionMethod::Aes128,
"https://www.example.com/"
))
.required_version(),
ProtocolVersion::V1
);
}
@ -155,18 +190,15 @@ mod test {
// ExtXSessionKey::new should panic, if the provided
// EncryptionMethod is None!
#[test]
#[should_panic]
fn test_new_panic() { ExtXSessionKey::new(EncryptionMethod::None, ""); }
#[test]
#[should_panic]
fn test_display_err() {
ExtXSessionKey(DecryptionKey::new(EncryptionMethod::None, "")).to_string();
}
#[should_panic = "the encryption method should never be `None`"]
fn test_new_panic() { ExtXSessionKey::new(ExtXKey::new(EncryptionMethod::None, "")); }
#[test]
fn test_deref() {
let key = ExtXSessionKey::new(EncryptionMethod::Aes128, "https://www.example.com/");
let key = ExtXSessionKey::new(ExtXKey::new(
EncryptionMethod::Aes128,
"https://www.example.com/",
));
assert_eq!(key.method(), EncryptionMethod::Aes128);
assert_eq!(key.uri(), Some(&"https://www.example.com/".into()));
@ -174,7 +206,10 @@ mod test {
#[test]
fn test_deref_mut() {
let mut key = ExtXSessionKey::new(EncryptionMethod::Aes128, "https://www.example.com/");
let mut key = ExtXSessionKey::new(ExtXKey::new(
EncryptionMethod::Aes128,
"https://www.example.com/",
));
key.set_method(EncryptionMethod::None);
assert_eq!(key.method(), EncryptionMethod::None);

View file

@ -64,7 +64,7 @@ use crate::Error;
/// [`ExtXProgramDateTime`]: crate::tags::ExtXProgramDateTime
/// [`ExtXPlaylistType`]: crate::tags::ExtXPlaylistType
/// [`ExtXIFramesOnly`]: crate::tags::ExtXIFramesOnly
#[derive(Debug, Clone, PartialEq, PartialOrd)]
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
pub enum VariantStream {
/// The [`VariantStream::ExtXIFrame`] variant identifies a [`MediaPlaylist`]
/// file containing the I-frames of a multimedia presentation.

View file

@ -1,10 +1,12 @@
use std::fmt;
use std::str::FromStr;
use derive_more::{Deref, DerefMut};
use derive_builder::Builder;
use shorthand::ShortHand;
use crate::types::{DecryptionKey, EncryptionMethod, ProtocolVersion};
use crate::utils::tag;
use crate::attribute::AttributePairs;
use crate::types::{EncryptionMethod, KeyFormat, KeyFormatVersions, ProtocolVersion};
use crate::utils::{parse_iv_from_str, quote, tag, unquote};
use crate::{Error, RequiredVersion};
/// # [4.3.2.4. EXT-X-KEY]
@ -24,8 +26,136 @@ use crate::{Error, RequiredVersion};
/// [`ExtXMap`]: crate::tags::ExtXMap
/// [`Media Segment`]: crate::MediaSegment
/// [4.3.2.4. EXT-X-KEY]: https://tools.ietf.org/html/rfc8216#section-4.3.2.4
#[derive(Deref, DerefMut, Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct ExtXKey(DecryptionKey);
#[derive(ShortHand, Builder, Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[builder(setter(into), build_fn(validate = "Self::validate"))]
#[shorthand(enable(must_use, into))]
pub struct ExtXKey {
/// HLS supports multiple [`EncryptionMethod`]s for a [`MediaSegment`].
///
/// For example `AES-128`.
///
/// # Example
///
/// ```
/// # use hls_m3u8::tags::ExtXKey;
/// use hls_m3u8::types::EncryptionMethod;
///
/// let mut key = ExtXKey::new(EncryptionMethod::Aes128, "https://www.example.com/");
///
/// key.set_method(EncryptionMethod::SampleAes);
///
/// assert_eq!(key.method(), EncryptionMethod::SampleAes);
/// ```
///
/// # Note
///
/// This attribute is required.
#[shorthand(enable(copy))]
pub(crate) method: EncryptionMethod,
/// An `URI` that specifies how to obtain the key.
///
/// # Example
///
/// ```
/// # use hls_m3u8::tags::ExtXKey;
/// use hls_m3u8::types::EncryptionMethod;
///
/// let mut key = ExtXKey::new(EncryptionMethod::Aes128, "https://www.example.com/");
///
/// key.set_uri(Some("http://www.google.com/"));
///
/// assert_eq!(key.uri(), Some(&"http://www.google.com/".to_string()));
/// ```
///
/// # Note
///
/// This attribute is required, if the [`EncryptionMethod`] is not `None`.
#[builder(setter(into, strip_option), default)]
pub(crate) uri: Option<String>,
/// An IV (initialization vector) is used to prevent repetitions between
/// segments of encrypted data.
///
/// <https://en.wikipedia.org/wiki/Initialization_vector>
///
/// # Example
///
/// ```
/// # use hls_m3u8::tags::ExtXKey;
/// use hls_m3u8::types::EncryptionMethod;
///
/// let mut key = ExtXKey::new(EncryptionMethod::Aes128, "https://www.example.com/");
/// # assert_eq!(key.iv(), None);
///
/// key.set_iv(Some([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])
/// );
/// ```
///
/// # Note
///
/// This attribute is optional.
#[builder(setter(into, strip_option), default)]
// TODO: workaround for https://github.com/Luro02/shorthand/issues/20
#[shorthand(enable(copy), disable(option_as_ref))]
pub(crate) iv: Option<[u8; 16]>,
/// The [`KeyFormat`] specifies how the key is
/// represented in the resource identified by the `URI`.
///
/// # Example
///
/// ```
/// # use hls_m3u8::tags::ExtXKey;
/// use hls_m3u8::types::{EncryptionMethod, KeyFormat};
///
/// let mut key = ExtXKey::new(EncryptionMethod::Aes128, "https://www.example.com/");
///
/// key.set_key_format(Some(KeyFormat::Identity));
///
/// assert_eq!(key.key_format(), Some(KeyFormat::Identity));
/// ```
///
/// # Note
///
/// This attribute is optional.
#[builder(setter(into, strip_option), default)]
#[shorthand(enable(copy))]
pub(crate) key_format: Option<KeyFormat>,
/// The [`KeyFormatVersions`] attribute.
///
/// # Example
///
/// ```
/// # use hls_m3u8::tags::ExtXKey;
/// use hls_m3u8::types::{EncryptionMethod, KeyFormatVersions};
///
/// let mut key = ExtXKey::new(EncryptionMethod::Aes128, "https://www.example.com/");
///
/// key.set_key_format_versions(Some(vec![1, 2, 3, 4, 5]));
///
/// assert_eq!(
/// key.key_format_versions(),
/// Some(&KeyFormatVersions::from(vec![1, 2, 3, 4, 5]))
/// );
/// ```
///
/// # Note
///
/// This attribute is optional.
#[builder(setter(into, strip_option), default)]
pub(crate) key_format_versions: Option<KeyFormatVersions>,
}
impl ExtXKeyBuilder {
fn validate(&self) -> Result<(), String> {
if self.method != Some(EncryptionMethod::None) && self.uri.is_none() {
return Err(Error::custom("missing URL").to_string());
}
Ok(())
}
}
impl ExtXKey {
pub(crate) const PREFIX: &'static str = "#EXT-X-KEY:";
@ -46,9 +176,36 @@ impl ExtXKey {
/// );
/// ```
pub fn new<T: Into<String>>(method: EncryptionMethod, uri: T) -> Self {
Self(DecryptionKey::new(method, uri))
Self {
method,
uri: Some(uri.into()),
iv: None,
key_format: None,
key_format_versions: None,
}
}
/// Returns a Builder to build an [`ExtXKey`].
///
/// # Example
///
/// ```
/// use hls_m3u8::tags::ExtXKey;
/// use hls_m3u8::types::{EncryptionMethod, KeyFormat};
///
/// ExtXKey::builder()
/// .method(EncryptionMethod::Aes128)
/// .uri("https://www.example.com/")
/// .iv([
/// 16, 239, 143, 117, 140, 165, 85, 17, 85, 132, 187, 91, 60, 104, 127, 82,
/// ])
/// .key_format(KeyFormat::Identity)
/// .key_format_versions(vec![1, 2, 3, 4, 5])
/// .build()?;
/// # Ok::<(), Box<dyn ::std::error::Error>>(())
/// ```
pub fn builder() -> ExtXKeyBuilder { ExtXKeyBuilder::default() }
/// Makes a new [`ExtXKey`] tag without a decryption key.
///
/// # Example
@ -60,13 +217,13 @@ impl ExtXKey {
/// assert_eq!(key.to_string(), "#EXT-X-KEY:METHOD=NONE");
/// ```
pub const fn empty() -> Self {
Self(DecryptionKey {
Self {
method: EncryptionMethod::None,
uri: None,
iv: None,
key_format: None,
key_format_versions: None,
})
}
}
/// Returns whether the [`EncryptionMethod`] is
@ -84,13 +241,71 @@ impl ExtXKey {
/// ```
///
/// [`None`]: EncryptionMethod::None
pub fn is_empty(&self) -> bool { self.0.method() == EncryptionMethod::None }
pub fn is_empty(&self) -> bool { self.method() == EncryptionMethod::None }
}
/// This tag requires the same [`ProtocolVersion`] that is returned by
/// `DecryptionKey::required_version`.
/// This tag requires [`ProtocolVersion::V5`], if [`KeyFormat`] or
/// [`KeyFormatVersions`] is specified and [`ProtocolVersion::V2`] if an iv is
/// specified.
///
/// Otherwise [`ProtocolVersion::V1`] is required.
impl RequiredVersion for ExtXKey {
fn required_version(&self) -> ProtocolVersion { self.0.required_version() }
fn required_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 ExtXKey {
/// Parses a String without verifying the starting tag
pub(crate) fn parse_from_str(input: &str) -> crate::Result<Self> {
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 AttributePairs::new(input) {
match key {
"METHOD" => method = Some(value.parse().map_err(Error::strum)?),
"URI" => {
let unquoted_uri = unquote(value);
if unquoted_uri.trim().is_empty() {
uri = None;
} else {
uri = Some(unquoted_uri);
}
}
"IV" => iv = Some(parse_iv_from_str(value)?),
"KEYFORMAT" => key_format = Some(value.parse()?),
"KEYFORMATVERSIONS" => key_format_versions = Some(value.parse().unwrap()),
_ => {
// [6.3.1. General Client Responsibilities]
// > ignore any attribute/value pair with an unrecognized
// AttributeName.
}
}
}
let method = method.ok_or_else(|| Error::missing_value("METHOD"))?;
if method != EncryptionMethod::None && uri.is_none() {
return Err(Error::missing_value("URI"));
}
Ok(Self {
method,
uri,
iv,
key_format,
key_format_versions,
})
}
}
impl FromStr for ExtXKey {
@ -98,14 +313,40 @@ impl FromStr for ExtXKey {
fn from_str(input: &str) -> Result<Self, Self::Err> {
let input = tag(input, Self::PREFIX)?;
Ok(Self(input.parse()?))
Self::parse_from_str(input)
}
}
impl fmt::Display for ExtXKey {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
//
write!(f, "{}{}", Self::PREFIX, self.0)
write!(f, "{}", Self::PREFIX)?;
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 {
// TODO: use hex::encode_to_slice
write!(f, ",IV=0x{}", hex::encode(&value))?;
}
if let Some(value) = &self.key_format {
write!(f, ",KEYFORMAT={}", quote(value))?;
}
if let Some(key_format_versions) = &self.key_format_versions {
if !key_format_versions.is_default() {
write!(f, ",KEYFORMATVERSIONS={}", key_format_versions)?;
}
}
Ok(())
}
}
@ -115,6 +356,36 @@ mod test {
use crate::types::{EncryptionMethod, KeyFormat};
use pretty_assertions::assert_eq;
#[test]
fn test_builder() {
assert_eq!(
ExtXKey::builder()
.method(EncryptionMethod::Aes128)
.uri("https://www.example.com/")
.iv([16, 239, 143, 117, 140, 165, 85, 17, 85, 132, 187, 91, 60, 104, 127, 82,])
.key_format(KeyFormat::Identity)
.key_format_versions(vec![1, 2, 3, 4, 5])
.build()
.unwrap()
.to_string(),
concat!(
"#EXT-X-KEY:",
"METHOD=AES-128,",
"URI=\"https://www.example.com/\",",
"IV=0x10ef8f758ca555115584bb5b3c687f52,",
"KEYFORMAT=\"identity\",",
"KEYFORMATVERSIONS=\"1/2/3/4/5\"",
)
.to_string()
);
assert!(ExtXKey::builder().build().is_err());
assert!(ExtXKey::builder()
.method(EncryptionMethod::Aes128)
.build()
.is_err());
}
#[test]
fn test_display() {
assert_eq!(
@ -132,28 +403,122 @@ mod test {
key.set_key_format_versions(Some(vec![1, 2, 3]));
assert_eq!(key.to_string(), "#EXT-X-KEY:METHOD=NONE".to_string());
assert_eq!(
ExtXKey::builder()
.method(EncryptionMethod::Aes128)
.uri("https://www.example.com/hls-key/key.bin")
.iv([16, 239, 143, 117, 140, 165, 85, 17, 85, 132, 187, 91, 60, 104, 127, 82])
.build()
.unwrap()
.to_string(),
concat!(
"#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!(
"#EXT-X-KEY:\
METHOD=AES-128,\
URI=\"https://priv.example.com/key.php?r=52\""
.parse::<ExtXKey>()
.unwrap(),
concat!(
"#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"
)
);
let mut key = ExtXKey::new(
EncryptionMethod::Aes128,
"https://www.example.com/hls-key/key.bin",
assert_eq!(
concat!(
"#EXT-X-KEY:",
"METHOD=AES-128,",
"URI=\"https://www.example.com/hls-key/key.bin\",",
"IV=0X10ef8f758ca555115584bb5b3c687f52"
)
.parse::<ExtXKey>()
.unwrap(),
ExtXKey::builder()
.method(EncryptionMethod::Aes128)
.uri("https://www.example.com/hls-key/key.bin")
.iv([16, 239, 143, 117, 140, 165, 85, 17, 85, 132, 187, 91, 60, 104, 127, 82])
.build()
.unwrap()
);
assert_eq!(
concat!(
"#EXT-X-KEY:",
"METHOD=AES-128,",
"URI=\"https://www.example.com/hls-key/key.bin\",",
"IV=0X10ef8f758ca555115584bb5b3c687f52,",
"KEYFORMAT=\"identity\",",
"KEYFORMATVERSIONS=\"1/2/3\""
)
.parse::<ExtXKey>()
.unwrap(),
ExtXKey::builder()
.method(EncryptionMethod::Aes128)
.uri("https://www.example.com/hls-key/key.bin")
.iv([16, 239, 143, 117, 140, 165, 85, 17, 85, 132, 187, 91, 60, 104, 127, 82])
.key_format(KeyFormat::Identity)
.key_format_versions(vec![1, 2, 3])
.build()
.unwrap()
);
assert_eq!(
concat!(
"#EXT-X-KEY:",
"METHOD=AES-128,",
"URI=\"http://www.example.com\",",
"UNKNOWNTAG=abcd"
)
.parse::<ExtXKey>()
.unwrap(),
ExtXKey::new(EncryptionMethod::Aes128, "http://www.example.com")
);
assert!("#EXT-X-KEY:METHOD=AES-128,URI=".parse::<ExtXKey>().is_err());
assert!("garbage".parse::<ExtXKey>().is_err());
}
#[test]
fn test_required_version() {
assert_eq!(
ExtXKey::new(EncryptionMethod::Aes128, "https://www.example.com/").required_version(),
ProtocolVersion::V1
);
assert_eq!(
ExtXKey::builder()
.method(EncryptionMethod::Aes128)
.uri("https://www.example.com/")
.key_format(KeyFormat::Identity)
.key_format_versions(vec![1, 2, 3])
.build()
.unwrap()
.required_version(),
ProtocolVersion::V5
);
assert_eq!(
ExtXKey::builder()
.method(EncryptionMethod::Aes128)
.uri("https://www.example.com/")
.iv([1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2, 3, 4, 5, 6, 7])
.build()
.unwrap()
.required_version(),
ProtocolVersion::V2
);
key.set_iv(Some([
16, 239, 143, 117, 140, 165, 85, 17, 85, 132, 187, 91, 60, 104, 127, 82,
]));
}
}

View file

@ -1,434 +0,0 @@
use std::fmt;
use std::str::FromStr;
use derive_builder::Builder;
use shorthand::ShortHand;
use crate::attribute::AttributePairs;
use crate::types::{EncryptionMethod, KeyFormat, KeyFormatVersions, ProtocolVersion};
use crate::utils::{parse_iv_from_str, quote, unquote};
use crate::{Error, RequiredVersion};
/// A [`DecryptionKey`] contains data, that is shared between [`ExtXSessionKey`]
/// and [`ExtXKey`].
///
/// [`ExtXSessionKey`]: crate::tags::ExtXSessionKey
/// [`ExtXKey`]: crate::tags::ExtXKey
#[derive(ShortHand, Builder, Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[builder(setter(into), build_fn(validate = "Self::validate"))]
#[shorthand(enable(must_use, into))]
pub struct DecryptionKey {
/// HLS supports multiple encryption methods for a segment.
///
/// For example `AES-128`.
///
/// # Example
///
/// ```
/// # use hls_m3u8::types::DecryptionKey;
/// use hls_m3u8::types::EncryptionMethod;
///
/// let mut key = DecryptionKey::new(EncryptionMethod::Aes128, "https://www.example.com/");
///
/// key.set_method(EncryptionMethod::SampleAes);
///
/// assert_eq!(
/// key.to_string(),
/// "METHOD=SAMPLE-AES,URI=\"https://www.example.com/\"".to_string()
/// );
/// ```
///
/// # Note
///
/// This attribute is required.
#[shorthand(enable(copy))]
pub(crate) method: EncryptionMethod,
/// An `URI` that specifies how to obtain the key.
///
/// # Example
///
/// ```
/// # use hls_m3u8::types::DecryptionKey;
/// use hls_m3u8::types::EncryptionMethod;
///
/// let mut key = DecryptionKey::new(EncryptionMethod::Aes128, "https://www.example.com/");
///
/// key.set_uri(Some("http://www.google.com/"));
///
/// assert_eq!(
/// key.to_string(),
/// "METHOD=AES-128,URI=\"http://www.google.com/\"".to_string()
/// );
/// ```
///
/// # Note
///
/// This attribute is required, if the [`EncryptionMethod`] is not `None`.
#[builder(setter(into, strip_option), default)]
pub(crate) uri: Option<String>,
/// An IV (initialization vector) is used to prevent repetitions between
/// segments of encrypted data.
///
/// <https://en.wikipedia.org/wiki/Initialization_vector>
///
/// # Example
///
/// ```
/// # use hls_m3u8::types::DecryptionKey;
/// use hls_m3u8::types::EncryptionMethod;
///
/// let mut key = DecryptionKey::new(EncryptionMethod::Aes128, "https://www.example.com/");
///
/// # assert_eq!(key.iv(), None);
/// key.set_iv(Some([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])
/// );
/// ```
///
/// # Note
///
/// This attribute is optional.
#[builder(setter(into, strip_option), default)]
// TODO: workaround for https://github.com/Luro02/shorthand/issues/20
#[shorthand(enable(copy), disable(option_as_ref))]
pub(crate) iv: Option<[u8; 16]>,
/// The [`KeyFormat`] specifies how the key is
/// represented in the resource identified by the `URI`.
///
/// # Example
///
/// ```
/// # use hls_m3u8::types::DecryptionKey;
/// use hls_m3u8::types::{EncryptionMethod, KeyFormat};
///
/// let mut key = DecryptionKey::new(EncryptionMethod::Aes128, "https://www.example.com/");
///
/// key.set_key_format(Some(KeyFormat::Identity));
///
/// assert_eq!(key.key_format(), Some(KeyFormat::Identity));
/// ```
///
/// # Note
///
/// This attribute is optional.
#[builder(setter(into, strip_option), default)]
#[shorthand(enable(copy))]
pub(crate) key_format: Option<KeyFormat>,
/// The [`KeyFormatVersions`] attribute.
///
/// # Example
///
/// ```
/// # use hls_m3u8::types::DecryptionKey;
/// use hls_m3u8::types::{EncryptionMethod, KeyFormatVersions};
///
/// let mut key = DecryptionKey::new(EncryptionMethod::Aes128, "https://www.example.com/");
///
/// key.set_key_format_versions(Some(vec![1, 2, 3, 4, 5]));
///
/// assert_eq!(
/// key.key_format_versions(),
/// Some(&KeyFormatVersions::from(vec![1, 2, 3, 4, 5]))
/// );
/// ```
///
/// # Note
///
/// This attribute is optional.
#[builder(setter(into, strip_option), default)]
pub(crate) key_format_versions: Option<KeyFormatVersions>,
}
impl DecryptionKeyBuilder {
fn validate(&self) -> Result<(), String> {
if self.method != Some(EncryptionMethod::None) && self.uri.is_none() {
return Err(Error::custom("Missing URL").to_string());
}
Ok(())
}
}
impl DecryptionKey {
/// Makes a new [`DecryptionKey`].
///
/// # Example
///
/// ```
/// # use hls_m3u8::types::DecryptionKey;
/// use hls_m3u8::types::EncryptionMethod;
///
/// let key = DecryptionKey::new(EncryptionMethod::Aes128, "https://www.example.com/");
/// ```
#[doc(hidden)]
pub fn new<T: Into<String>>(method: EncryptionMethod, uri: T) -> Self {
Self {
method,
uri: Some(uri.into()),
iv: None,
key_format: None,
key_format_versions: None,
}
}
/// Returns a Builder to build a [`DecryptionKey`].
pub fn builder() -> DecryptionKeyBuilder { DecryptionKeyBuilder::default() }
}
/// This tag requires [`ProtocolVersion::V5`], if [`KeyFormat`] or
/// [`KeyFormatVersions`] is specified and [`ProtocolVersion::V2`] if an iv is
/// specified.
///
/// Otherwise [`ProtocolVersion::V1`] is required.
impl RequiredVersion for DecryptionKey {
fn required_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
}
}
}
#[doc(hidden)]
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 AttributePairs::new(input) {
match key {
"METHOD" => method = Some(value.parse().map_err(Error::strum)?),
"URI" => {
let unquoted_uri = unquote(value);
if unquoted_uri.trim().is_empty() {
uri = None;
} else {
uri = Some(unquoted_uri);
}
}
"IV" => iv = Some(parse_iv_from_str(value)?),
"KEYFORMAT" => key_format = Some(value.parse()?),
"KEYFORMATVERSIONS" => key_format_versions = Some(value.parse().unwrap()),
_ => {
// [6.3.1. General Client Responsibilities]
// > ignore any attribute/value pair with an unrecognized
// AttributeName.
}
}
}
let method = method.ok_or_else(|| Error::missing_value("METHOD"))?;
if method != EncryptionMethod::None && uri.is_none() {
return Err(Error::missing_value("URI"));
}
Ok(Self {
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 {
// TODO: use hex::encode_to_slice
write!(f, ",IV=0x{}", hex::encode(&value))?;
}
if let Some(value) = &self.key_format {
write!(f, ",KEYFORMAT={}", quote(value))?;
}
if let Some(key_format_versions) = &self.key_format_versions {
if !key_format_versions.is_default() {
write!(f, ",KEYFORMATVERSIONS={}", key_format_versions)?;
}
}
Ok(())
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::types::EncryptionMethod;
use pretty_assertions::assert_eq;
#[test]
fn test_builder() {
let key = DecryptionKey::builder()
.method(EncryptionMethod::Aes128)
.uri("https://www.example.com/")
.iv([
16, 239, 143, 117, 140, 165, 85, 17, 85, 132, 187, 91, 60, 104, 127, 82,
])
.key_format(KeyFormat::Identity)
.key_format_versions(vec![1, 2, 3, 4, 5])
.build()
.unwrap();
assert_eq!(
key.to_string(),
"METHOD=AES-128,\
URI=\"https://www.example.com/\",\
IV=0x10ef8f758ca555115584bb5b3c687f52,\
KEYFORMAT=\"identity\",\
KEYFORMATVERSIONS=\"1/2/3/4/5\"\
"
.to_string()
);
assert!(DecryptionKey::builder().build().is_err());
assert!(DecryptionKey::builder()
.method(EncryptionMethod::Aes128)
.build()
.is_err());
}
#[test]
fn test_display() {
let mut key = DecryptionKey::new(
EncryptionMethod::Aes128,
"https://www.example.com/hls-key/key.bin",
);
key.set_iv(Some([
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!(
"METHOD=AES-128,\
URI=\"https://priv.example.com/key.php?r=52\""
.parse::<DecryptionKey>()
.unwrap(),
DecryptionKey::new(
EncryptionMethod::Aes128,
"https://priv.example.com/key.php?r=52"
)
);
let mut key = DecryptionKey::new(
EncryptionMethod::Aes128,
"https://www.example.com/hls-key/key.bin",
);
key.set_iv(Some([
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::<DecryptionKey>()
.unwrap(),
key
);
let mut key = DecryptionKey::new(EncryptionMethod::Aes128, "http://www.example.com");
key.set_iv(Some([
16, 239, 143, 117, 140, 165, 85, 17, 85, 132, 187, 91, 60, 104, 127, 82,
]));
key.set_key_format(Some(KeyFormat::Identity));
assert_eq!(
"METHOD=AES-128,\
URI=\"http://www.example.com\",\
IV=0x10ef8f758ca555115584bb5b3c687f52,\
KEYFORMAT=\"identity\""
.parse::<DecryptionKey>()
.unwrap(),
key
);
key.set_key_format_versions(Some(vec![1, 2, 3]));
assert_eq!(
"METHOD=AES-128,\
URI=\"http://www.example.com\",\
IV=0x10ef8f758ca555115584bb5b3c687f52,\
KEYFORMAT=\"identity\",\
KEYFORMATVERSIONS=\"1/2/3\""
.parse::<DecryptionKey>()
.unwrap(),
key
);
assert_eq!(
"METHOD=AES-128,\
URI=\"http://www.example.com\",\
UNKNOWNTAG=abcd"
.parse::<DecryptionKey>()
.unwrap(),
DecryptionKey::new(EncryptionMethod::Aes128, "http://www.example.com")
);
assert!("METHOD=AES-128,URI=".parse::<DecryptionKey>().is_err());
assert!("garbage".parse::<DecryptionKey>().is_err());
}
#[test]
fn test_required_version() {
assert_eq!(
DecryptionKey::new(EncryptionMethod::Aes128, "https://www.example.com/")
.required_version(),
ProtocolVersion::V1
);
assert_eq!(
DecryptionKey::builder()
.method(EncryptionMethod::Aes128)
.uri("https://www.example.com/")
.key_format(KeyFormat::Identity)
.key_format_versions(vec![1, 2, 3])
.build()
.unwrap()
.required_version(),
ProtocolVersion::V5
);
assert_eq!(
DecryptionKey::builder()
.method(EncryptionMethod::Aes128)
.uri("https://www.example.com/")
.iv([1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2, 3, 4, 5, 6, 7])
.build()
.unwrap()
.required_version(),
ProtocolVersion::V2
);
}
}

View file

@ -2,7 +2,6 @@
mod byte_range;
mod channels;
mod closed_captions;
mod decryption_key;
mod encryption_method;
mod hdcp_level;
mod in_stream_id;
@ -20,7 +19,6 @@ mod ufloat;
pub use byte_range::*;
pub use channels::*;
pub use closed_captions::*;
pub use decryption_key::*;
pub use encryption_method::*;
pub use hdcp_level::*;
pub use in_stream_id::*;