hls_m3u8/types/
decryption_key.rs

1use std::borrow::Cow;
2use std::convert::TryFrom;
3use std::fmt;
4
5use derive_builder::Builder;
6use shorthand::ShortHand;
7
8use crate::attribute::AttributePairs;
9use crate::types::{
10    EncryptionMethod, InitializationVector, KeyFormat, KeyFormatVersions, ProtocolVersion,
11};
12use crate::utils::{quote, unquote};
13use crate::{Error, RequiredVersion};
14
15/// Specifies how to decrypt encrypted data from the server.
16#[derive(ShortHand, Builder, Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
17#[builder(setter(into), build_fn(validate = "Self::validate"))]
18#[shorthand(enable(skip, must_use, into))]
19#[non_exhaustive]
20pub struct DecryptionKey<'a> {
21    /// The encryption method, which has been used to encrypt the data.
22    ///
23    /// An [`EncryptionMethod::Aes128`] signals that the data is encrypted using
24    /// the Advanced Encryption Standard (AES) with a 128-bit key, Cipher Block
25    /// Chaining (CBC), and Public-Key Cryptography Standards #7 (PKCS7)
26    /// padding. CBC is restarted on each segment boundary, using either the
27    /// [`DecryptionKey::iv`] field or the [`MediaSegment::number`] as the IV.
28    ///
29    /// An [`EncryptionMethod::SampleAes`] means that the [`MediaSegment`]s
30    /// contain media samples, such as audio or video, that are encrypted using
31    /// the Advanced Encryption Standard (Aes128). How these media streams are
32    /// encrypted and encapsulated in a segment depends on the media encoding
33    /// and the media format of the segment.
34    ///
35    /// ## Note
36    ///
37    /// This field is required.
38    ///
39    /// [`MediaSegment::number`]: crate::MediaSegment::number
40    /// [`MediaSegment`]: crate::MediaSegment
41    pub method: EncryptionMethod,
42    /// This uri points to a key file, which contains the cipher key.
43    ///
44    /// ## Note
45    ///
46    /// This field is required.
47    #[builder(setter(into, strip_option), default)]
48    #[shorthand(disable(skip))]
49    pub(crate) uri: Cow<'a, str>,
50    /// An initialization vector (IV) is a fixed size input that can be used
51    /// along with a secret key for data encryption.
52    ///
53    /// ## Note
54    ///
55    /// This field is optional and an absent value indicates that
56    /// [`MediaSegment::number`] should be used instead.
57    ///
58    /// [`MediaSegment::number`]: crate::MediaSegment::number
59    #[builder(setter(into, strip_option), default)]
60    pub iv: InitializationVector,
61    /// A server may offer multiple ways to retrieve a key by providing multiple
62    /// [`DecryptionKey`]s with different [`KeyFormat`] values.
63    ///
64    /// An [`EncryptionMethod::Aes128`] uses 16-octet (16 byte/128 bit) keys. If
65    /// the format is [`KeyFormat::Identity`], the key file is a single packed
66    /// array of 16 octets (16 byte/128 bit) in binary format.
67    ///
68    /// ## Note
69    ///
70    /// This field is optional.
71    #[builder(setter(into, strip_option), default)]
72    pub format: Option<KeyFormat<'a>>,
73    /// A list of numbers that can be used to indicate which version(s)
74    /// this instance complies with, if more than one version of a particular
75    /// [`KeyFormat`] is defined.
76    ///
77    /// ## Note
78    ///
79    /// This field is optional.
80    #[builder(setter(into, strip_option), default)]
81    pub versions: Option<KeyFormatVersions>,
82}
83
84impl<'a> DecryptionKey<'a> {
85    /// Creates a new `DecryptionKey` from an uri pointing to the key data and
86    /// an `EncryptionMethod`.
87    ///
88    /// # Example
89    ///
90    /// ```
91    /// # use hls_m3u8::types::DecryptionKey;
92    /// use hls_m3u8::types::EncryptionMethod;
93    ///
94    /// let key = DecryptionKey::new(EncryptionMethod::Aes128, "https://www.example.uri/key");
95    /// ```
96    #[must_use]
97    #[inline]
98    pub fn new<I: Into<Cow<'a, str>>>(method: EncryptionMethod, uri: I) -> Self {
99        Self {
100            method,
101            uri: uri.into(),
102            iv: InitializationVector::default(),
103            format: None,
104            versions: None,
105        }
106    }
107
108    /// Returns a builder for a `DecryptionKey`.
109    ///
110    /// # Example
111    ///
112    /// ```
113    /// # use hls_m3u8::types::DecryptionKey;
114    /// use hls_m3u8::types::{EncryptionMethod, KeyFormat};
115    ///
116    /// let key = DecryptionKey::builder()
117    ///     .method(EncryptionMethod::Aes128)
118    ///     .uri("https://www.example.com/")
119    ///     .iv([
120    ///         16, 239, 143, 117, 140, 165, 85, 17, 85, 132, 187, 91, 60, 104, 127, 82,
121    ///     ])
122    ///     .format(KeyFormat::Identity)
123    ///     .versions(&[1, 2, 3, 4, 5])
124    ///     .build()?;
125    /// # Ok::<(), Box<dyn std::error::Error>>(())
126    /// ```
127    #[must_use]
128    #[inline]
129    pub fn builder() -> DecryptionKeyBuilder<'a> {
130        DecryptionKeyBuilder::default()
131    }
132
133    /// Makes the struct independent of its lifetime, by taking ownership of all
134    /// internal [`Cow`]s.
135    ///
136    /// # Note
137    ///
138    /// This is a relatively expensive operation.
139    #[must_use]
140    pub fn into_owned(self) -> DecryptionKey<'static> {
141        DecryptionKey {
142            method: self.method,
143            uri: Cow::Owned(self.uri.into_owned()),
144            iv: self.iv,
145            format: self.format.map(|f| f.into_owned()),
146            versions: self.versions,
147        }
148    }
149}
150
151/// This tag requires [`ProtocolVersion::V5`], if [`KeyFormat`] or
152/// [`KeyFormatVersions`] is specified and [`ProtocolVersion::V2`] if an iv is
153/// specified.
154///
155/// Otherwise [`ProtocolVersion::V1`] is required.
156impl RequiredVersion for DecryptionKey<'_> {
157    fn required_version(&self) -> ProtocolVersion {
158        if self.format.is_some() || self.versions.is_some() {
159            ProtocolVersion::V5
160        } else if self.iv.is_some() {
161            ProtocolVersion::V2
162        } else {
163            ProtocolVersion::V1
164        }
165    }
166}
167
168impl<'a> TryFrom<&'a str> for DecryptionKey<'a> {
169    type Error = Error;
170
171    fn try_from(input: &'a str) -> Result<Self, Self::Error> {
172        let mut method = None;
173        let mut uri = None;
174        let mut iv = None;
175        let mut format = None;
176        let mut versions = None;
177
178        for (key, value) in AttributePairs::new(input) {
179            match key {
180                "METHOD" => method = Some(value.parse().map_err(Error::strum)?),
181                "URI" => {
182                    let unquoted_uri = unquote(value);
183
184                    if !unquoted_uri.trim().is_empty() {
185                        uri = Some(unquoted_uri);
186                    }
187                }
188                "IV" => iv = Some(value.parse()?),
189                "KEYFORMAT" => format = Some(value.into()),
190                "KEYFORMATVERSIONS" => versions = Some(value.parse()?),
191                _ => {
192                    // [6.3.1. General Client Responsibilities]
193                    // > ignore any attribute/value pair with an unrecognized
194                    // AttributeName.
195                }
196            }
197        }
198
199        let method = method.ok_or_else(|| Error::missing_value("METHOD"))?;
200        let uri = uri.ok_or_else(|| Error::missing_value("URI"))?;
201        let iv = iv.unwrap_or_default();
202
203        Ok(Self {
204            method,
205            uri,
206            iv,
207            format,
208            versions,
209        })
210    }
211}
212
213impl fmt::Display for DecryptionKey<'_> {
214    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
215        write!(f, "METHOD={},URI={}", self.method, quote(&self.uri))?;
216
217        if let InitializationVector::Aes128(_) = &self.iv {
218            write!(f, ",IV={}", &self.iv)?;
219        }
220
221        if let Some(value) = &self.format {
222            write!(f, ",KEYFORMAT={}", quote(value))?;
223        }
224
225        if let Some(value) = &self.versions {
226            if !value.is_default() {
227                write!(f, ",KEYFORMATVERSIONS={}", value)?;
228            }
229        }
230
231        Ok(())
232    }
233}
234
235impl DecryptionKeyBuilder<'_> {
236    fn validate(&self) -> Result<(), String> {
237        // a decryption key must contain a uri and a method
238        if self.method.is_none() {
239            return Err(Error::missing_field("DecryptionKey", "method").to_string());
240        } else if self.uri.is_none() {
241            return Err(Error::missing_field("DecryptionKey", "uri").to_string());
242        }
243
244        Ok(())
245    }
246}
247
248#[cfg(test)]
249mod test {
250    use super::*;
251    use crate::types::{EncryptionMethod, KeyFormat};
252    use pretty_assertions::assert_eq;
253
254    macro_rules! generate_tests {
255        ( $( { $struct:expr, $str:expr } ),+ $(,)* ) => {
256            #[test]
257            fn test_display() {
258                $(
259                    assert_eq!($struct.to_string(), $str.to_string());
260                )+
261            }
262
263            #[test]
264            fn test_parser() {
265                $(
266                    assert_eq!($struct, TryFrom::try_from($str).unwrap());
267                )+
268
269                assert_eq!(
270                    DecryptionKey::new(EncryptionMethod::Aes128, "http://www.example.com"),
271                    DecryptionKey::try_from(concat!(
272                        "METHOD=AES-128,",
273                        "URI=\"http://www.example.com\",",
274                        "UNKNOWNTAG=abcd"
275                    )).unwrap(),
276                );
277                assert!(DecryptionKey::try_from("METHOD=AES-128,URI=").is_err());
278                assert!(DecryptionKey::try_from("garbage").is_err());
279            }
280        }
281    }
282
283    #[test]
284    fn test_builder() {
285        let mut key = DecryptionKey::new(EncryptionMethod::Aes128, "https://www.example.com/");
286        key.iv = [
287            16, 239, 143, 117, 140, 165, 85, 17, 85, 132, 187, 91, 60, 104, 127, 82,
288        ]
289        .into();
290        key.format = Some(KeyFormat::Identity);
291        key.versions = Some(vec![1, 2, 3, 4, 5].into());
292
293        assert_eq!(
294            DecryptionKey::builder()
295                .method(EncryptionMethod::Aes128)
296                .uri("https://www.example.com/")
297                .iv([16, 239, 143, 117, 140, 165, 85, 17, 85, 132, 187, 91, 60, 104, 127, 82])
298                .format(KeyFormat::Identity)
299                .versions(vec![1, 2, 3, 4, 5])
300                .build()
301                .unwrap(),
302            key
303        );
304
305        assert!(DecryptionKey::builder().build().is_err());
306        assert!(DecryptionKey::builder()
307            .method(EncryptionMethod::Aes128)
308            .build()
309            .is_err());
310    }
311
312    generate_tests! {
313        {
314            DecryptionKey::new(
315                EncryptionMethod::Aes128,
316                "https://priv.example.com/key.php?r=52"
317            ),
318            concat!(
319                "METHOD=AES-128,",
320                "URI=\"https://priv.example.com/key.php?r=52\""
321            )
322        },
323        {
324            DecryptionKey::builder()
325                .method(EncryptionMethod::Aes128)
326                .uri("https://www.example.com/hls-key/key.bin")
327                .iv([16, 239, 143, 117, 140, 165, 85, 17, 85, 132, 187, 91, 60, 104, 127, 82])
328                .build()
329                .unwrap(),
330            concat!(
331                "METHOD=AES-128,",
332                "URI=\"https://www.example.com/hls-key/key.bin\",",
333                "IV=0x10ef8f758ca555115584bb5b3c687f52"
334            )
335        },
336        {
337            DecryptionKey::builder()
338                .method(EncryptionMethod::Aes128)
339                .uri("https://www.example.com/hls-key/key.bin")
340                .iv([16, 239, 143, 117, 140, 165, 85, 17, 85, 132, 187, 91, 60, 104, 127, 82])
341                .format(KeyFormat::Identity)
342                .versions(vec![1, 2, 3])
343                .build()
344                .unwrap(),
345            concat!(
346                "METHOD=AES-128,",
347                "URI=\"https://www.example.com/hls-key/key.bin\",",
348                "IV=0x10ef8f758ca555115584bb5b3c687f52,",
349                "KEYFORMAT=\"identity\",",
350                "KEYFORMATVERSIONS=\"1/2/3\""
351            )
352        },
353    }
354
355    #[test]
356    fn test_required_version() {
357        assert_eq!(
358            DecryptionKey::new(EncryptionMethod::Aes128, "https://www.example.com/")
359                .required_version(),
360            ProtocolVersion::V1
361        );
362
363        assert_eq!(
364            DecryptionKey::builder()
365                .method(EncryptionMethod::Aes128)
366                .uri("https://www.example.com/")
367                .format(KeyFormat::Identity)
368                .versions(vec![1, 2, 3])
369                .build()
370                .unwrap()
371                .required_version(),
372            ProtocolVersion::V5
373        );
374
375        assert_eq!(
376            DecryptionKey::builder()
377                .method(EncryptionMethod::Aes128)
378                .uri("https://www.example.com/")
379                .iv([1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2, 3, 4, 5, 6, 7])
380                .build()
381                .unwrap()
382                .required_version(),
383            ProtocolVersion::V2
384        );
385    }
386}