diff --git a/src/lib.rs b/src/lib.rs index bb7588c..cdb2765 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -319,8 +319,9 @@ named!(pub alternative_media_tag, ); named!(pub session_data_tag, - do_parse!( tag!("#EXT-X-SESSION-DATA:") >> attributes: key_value_pairs >> - ( SessionData::from_hashmap(attributes))) + do_parse!( tag!("#EXT-X-SESSION-DATA:") >> + session_data: map_res!(key_value_pairs, |attrs| SessionData::from_hashmap(attrs)) >> + ( session_data)) ); named!(pub session_key_tag, diff --git a/src/playlist.rs b/src/playlist.rs index a085e0f..cdecb13 100644 --- a/src/playlist.rs +++ b/src/playlist.rs @@ -343,6 +343,9 @@ impl fmt::Display for AlternativeMediaType { /// [`#EXT-X-SESSION-KEY:`] /// (https://tools.ietf.org/html/draft-pantos-http-live-streaming-19#section-4.3.4.5) +/// The EXT-X-SESSION-KEY tag allows encryption keys from Media Playlists +/// to be specified in a Master Playlist. This allows the client to +/// preload these keys without having to read the Media Playlist(s) first. #[derive(Debug, Default, PartialEq, Clone)] pub struct SessionKey(pub Key); @@ -354,34 +357,56 @@ impl SessionKey { } } +#[derive(Debug, PartialEq, Clone)] +pub enum SessionDataField { + Value(String), + Uri(String) +} + /// [`#EXT-X-SESSION-DATA:`] /// (https://tools.ietf.org/html/draft-pantos-http-live-streaming-19#section-4.3.4.4) -/// The EXT-X-SESSION-KEY tag allows encryption keys from Media Playlists -/// to be specified in a Master Playlist. This allows the client to -/// preload these keys without having to read the Media Playlist(s) first. -#[derive(Debug, Default, PartialEq, Clone)] +/// The EXT-X-SESSION-DATA tag allows arbitrary session data to be carried +/// in a Master Playlist. +#[derive(Debug, PartialEq, Clone)] pub struct SessionData { pub data_id: String, - pub value: String, - pub uri: String, + pub field: SessionDataField, pub language: Option, } impl SessionData { - pub fn from_hashmap(mut attrs: HashMap) -> SessionData { - SessionData { - data_id: attrs.remove("DATA-ID").unwrap_or_else(String::new), - value: attrs.remove("VALUE").unwrap_or_else(String::new), - uri: attrs.remove("URI").unwrap_or_else(String::new), + pub fn from_hashmap(mut attrs: HashMap) -> Result { + let data_id = match attrs.remove("DATA-ID") { + Some(data_id) => data_id, + None => return Err("EXT-X-SESSION-DATA field without DATA-ID".to_string()) + }; + + let value = attrs.remove("VALUE"); + let uri = attrs.remove("URI"); + + // SessionData must contain either a VALUE or a URI, + // but not both https://tools.ietf.org/html/rfc8216#section-4.3.4.4 + let field = match (value, uri) { + (Some(value), None) => SessionDataField::Value(value), + (None, Some(uri)) => SessionDataField::Uri(uri), + (Some(_), Some(_)) => return Err(format!["EXT-X-SESSION-DATA tag {} contains both a value and a uri", data_id]), + (None, None) => return Err(format!["EXT-X-SESSION-DATA tag {} must contain either a value or a uri", data_id]), + }; + + Ok(SessionData { + data_id, + field, language: attrs.remove("LANGUAGE"), - } + }) } pub fn write_to(&self, w: &mut T) -> std::io::Result<()> { write!(w, "#EXT-X-SESSION-DATA:")?; write!(w, "DATA-ID=\"{}\"", self.data_id)?; - write!(w, ",VALUE=\"{}\"", self.value)?; - write!(w, ",URI=\"{}\"", self.uri)?; + match &self.field { + SessionDataField::Value(value) => write!(w, ",VALUE=\"{}\"", value)?, + SessionDataField::Uri(uri) => write!(w, ",URI=\"{}\"", uri)?, + }; write_some_attribute_quoted!(w, ",LANGUAGE", &self.language)?; write!(w, "\n") } diff --git a/tests/lib.rs b/tests/lib.rs index 41cb456..1b04afe 100644 --- a/tests/lib.rs +++ b/tests/lib.rs @@ -333,12 +333,13 @@ fn create_and_parse_master_playlist_full() { closed_captions: Some("closed_captions".into()), } ], - session_data: Some(SessionData { - data_id: "****".into(), - value: "%%%%".into(), - uri: "++++".into(), - language: Some("SessionDataLanguage".into()), - }), + session_data: Some( + SessionData { + data_id: "****".into(), + field: SessionDataField::Value("%%%%".to_string()), + language: Some("SessionDataLanguage".into()), + } + ), session_key: Some(SessionKey(Key { method: "AES-128".into(), uri: Some("https://secure.domain.com".into()),