Merge pull request #24 from thaytan/20-multiple-session-data-keys

Fixes for session data and keys handling
This commit is contained in:
rutgersc 2021-04-24 17:29:44 +02:00 committed by GitHub
commit 5a72e1e875
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 64 additions and 36 deletions

View file

@ -319,8 +319,9 @@ named!(pub alternative_media_tag<AlternativeMedia>,
); );
named!(pub session_data_tag<SessionData>, named!(pub session_data_tag<SessionData>,
do_parse!( tag!("#EXT-X-SESSION-DATA:") >> attributes: key_value_pairs >> do_parse!( tag!("#EXT-X-SESSION-DATA:") >>
( SessionData::from_hashmap(attributes))) session_data: map_res!(key_value_pairs, |attrs| SessionData::from_hashmap(attrs)) >>
( session_data))
); );
named!(pub session_key_tag<SessionKey>, named!(pub session_key_tag<SessionKey>,

View file

@ -65,8 +65,8 @@ impl Playlist {
pub struct MasterPlaylist { pub struct MasterPlaylist {
pub version: usize, pub version: usize,
pub variants: Vec<VariantStream>, pub variants: Vec<VariantStream>,
pub session_data: Option<SessionData>, pub session_data: Vec<SessionData>,
pub session_key: Option<SessionKey>, pub session_key: Vec<SessionKey>,
pub start: Option<Start>, pub start: Option<Start>,
pub independent_segments: bool, pub independent_segments: bool,
pub alternatives: Vec<AlternativeMedia>, // EXT-X-MEDIA tags pub alternatives: Vec<AlternativeMedia>, // EXT-X-MEDIA tags
@ -74,7 +74,6 @@ pub struct MasterPlaylist {
} }
impl MasterPlaylist { impl MasterPlaylist {
pub fn from_tags(mut tags: Vec<MasterPlaylistTag>) -> MasterPlaylist { pub fn from_tags(mut tags: Vec<MasterPlaylistTag>) -> MasterPlaylist {
let mut master_playlist = MasterPlaylist::default(); let mut master_playlist = MasterPlaylist::default();
@ -95,10 +94,10 @@ impl MasterPlaylist {
} }
} }
MasterPlaylistTag::SessionData(data) => { MasterPlaylistTag::SessionData(data) => {
master_playlist.session_data = Some(data); master_playlist.session_data.push(data);
} }
MasterPlaylistTag::SessionKey(key) => { MasterPlaylistTag::SessionKey(key) => {
master_playlist.session_key = Some(key); master_playlist.session_key.push(key);
} }
MasterPlaylistTag::Start(s) => { MasterPlaylistTag::Start(s) => {
master_playlist.start = Some(s); master_playlist.start = Some(s);
@ -131,10 +130,10 @@ impl MasterPlaylist {
for variant in &self.variants { for variant in &self.variants {
variant.write_to(w)?; variant.write_to(w)?;
} }
if let Some(ref session_data) = self.session_data { for session_data in &self.session_data {
session_data.write_to(w)?; session_data.write_to(w)?;
} }
if let Some(ref session_key) = self.session_key { for session_key in &self.session_key {
session_key.write_to(w)?; session_key.write_to(w)?;
} }
if let Some(ref start) = self.start { if let Some(ref start) = self.start {
@ -343,6 +342,9 @@ impl fmt::Display for AlternativeMediaType {
/// [`#EXT-X-SESSION-KEY:<attribute-list>`] /// [`#EXT-X-SESSION-KEY:<attribute-list>`]
/// (https://tools.ietf.org/html/draft-pantos-http-live-streaming-19#section-4.3.4.5) /// (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)] #[derive(Debug, Default, PartialEq, Clone)]
pub struct SessionKey(pub Key); pub struct SessionKey(pub Key);
@ -354,34 +356,56 @@ impl SessionKey {
} }
} }
#[derive(Debug, PartialEq, Clone)]
pub enum SessionDataField {
Value(String),
Uri(String)
}
/// [`#EXT-X-SESSION-DATA:<attribute-list>`] /// [`#EXT-X-SESSION-DATA:<attribute-list>`]
/// (https://tools.ietf.org/html/draft-pantos-http-live-streaming-19#section-4.3.4.4) /// (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 /// The EXT-X-SESSION-DATA tag allows arbitrary session data to be carried
/// to be specified in a Master Playlist. This allows the client to /// in a Master Playlist.
/// preload these keys without having to read the Media Playlist(s) first. #[derive(Debug, PartialEq, Clone)]
#[derive(Debug, Default, PartialEq, Clone)]
pub struct SessionData { pub struct SessionData {
pub data_id: String, pub data_id: String,
pub value: String, pub field: SessionDataField,
pub uri: String,
pub language: Option<String>, pub language: Option<String>,
} }
impl SessionData { impl SessionData {
pub fn from_hashmap(mut attrs: HashMap<String, String>) -> SessionData { pub fn from_hashmap(mut attrs: HashMap<String, String>) -> Result<SessionData, String> {
SessionData { let data_id = match attrs.remove("DATA-ID") {
data_id: attrs.remove("DATA-ID").unwrap_or_else(String::new), Some(data_id) => data_id,
value: attrs.remove("VALUE").unwrap_or_else(String::new), None => return Err("EXT-X-SESSION-DATA field without DATA-ID".to_string())
uri: attrs.remove("URI").unwrap_or_else(String::new), };
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"), language: attrs.remove("LANGUAGE"),
} })
} }
pub fn write_to<T: Write>(&self, w: &mut T) -> std::io::Result<()> { pub fn write_to<T: Write>(&self, w: &mut T) -> std::io::Result<()> {
write!(w, "#EXT-X-SESSION-DATA:")?; write!(w, "#EXT-X-SESSION-DATA:")?;
write!(w, "DATA-ID=\"{}\"", self.data_id)?; write!(w, "DATA-ID=\"{}\"", self.data_id)?;
write!(w, ",VALUE=\"{}\"", self.value)?; match &self.field {
write!(w, ",URI=\"{}\"", self.uri)?; SessionDataField::Value(value) => write!(w, ",VALUE=\"{}\"", value)?,
SessionDataField::Uri(uri) => write!(w, ",URI=\"{}\"", uri)?,
};
write_some_attribute_quoted!(w, ",LANGUAGE", &self.language)?; write_some_attribute_quoted!(w, ",LANGUAGE", &self.language)?;
write!(w, "\n") write!(w, "\n")
} }

View file

@ -333,19 +333,22 @@ fn create_and_parse_master_playlist_full() {
closed_captions: Some("closed_captions".into()), closed_captions: Some("closed_captions".into()),
} }
], ],
session_data: Some(SessionData { session_data: vec![
data_id: "****".into(), SessionData {
value: "%%%%".into(), data_id: "****".into(),
uri: "++++".into(), field: SessionDataField::Value("%%%%".to_string()),
language: Some("SessionDataLanguage".into()), language: Some("SessionDataLanguage".into()),
}), }
session_key: Some(SessionKey(Key { ],
method: "AES-128".into(), session_key: vec![
uri: Some("https://secure.domain.com".into()), SessionKey(Key {
iv: Some("0xb059217aa2649ce170b734".into()), method: "AES-128".into(),
keyformat: Some("xXkeyformatXx".into()), uri: Some("https://secure.domain.com".into()),
keyformatversions: Some("xXFormatVers".into()), iv: Some("0xb059217aa2649ce170b734".into()),
})), keyformat: Some("xXkeyformatXx".into()),
keyformatversions: Some("xXFormatVers".into()),
})
],
start: Some(Start { start: Some(Start {
time_offset: "123123123".into(), time_offset: "123123123".into(),
precise: Some("YES".into()), precise: Some("YES".into()),