use anyhow::{anyhow, bail}; use std::{fmt::Display, str::FromStr}; #[derive(Debug, Default)] pub struct SessionDescription { pub version: String, pub originator: String, pub session_name: String, pub session_information: Option, pub description_uri: Option, pub email_address: Option, pub phone_number: Option, pub connection_information: Option, pub bandwidth: Vec, pub time_descriptions: Vec, pub time_zone_adjustments: Option, pub encryption_key: Option, pub attributes: Vec, pub media_descriptions: Vec, } #[derive(Debug)] pub struct TimeDescription { pub time: String, pub repeat_times: Vec, } #[derive(Debug, Default)] pub struct MediaDescription { pub name: String, pub title: Option, pub connection_information: Option, pub bandwidth: Vec, pub encryption_key: Option, pub attributes: Vec, } impl FromStr for SessionDescription { type Err = anyhow::Error; fn from_str(s: &str) -> Result { let mut version = None; let mut originator = None; let mut session_name = None; let mut session_information = None; let mut description_uri = None; let mut email_address = None; let mut phone_number = None; let mut connection_information = None; let mut encryption_key = None; let mut time_zone_adjustments = None; let mut bandwidth = Vec::new(); let mut time_descriptions = Vec::new(); let mut media_descriptions = Vec::new(); let mut attributes = Vec::new(); for line in s.lines() { let (r#type, value) = line.split_once("=").ok_or(anyhow!("sdp line invalid"))?; match r#type { "v" => version = Some(value.to_string()), "o" => originator = Some(value.to_string()), "s" => session_name = Some(value.to_string()), "i" => session_information = Some(value.to_string()), "u" => description_uri = Some(value.to_string()), "e" => email_address = Some(value.to_string()), "p" => phone_number = Some(value.to_string()), "t" => time_descriptions.push(TimeDescription { repeat_times: Vec::new(), time: value.to_string(), }), "r" => time_descriptions .last_mut() .ok_or(anyhow!("no time desc"))? .repeat_times .push(value.to_string()), "z" => time_zone_adjustments = Some(value.to_string()), "m" => media_descriptions.push(MediaDescription { connection_information: None, name: value.to_string(), title: None, // TODO never set, forgot it somewhere encryption_key: None, attributes: Vec::new(), bandwidth: Vec::new(), }), "a" => { if let Some(media_desc) = media_descriptions.last_mut() { media_desc.attributes.push(value.to_string()) } else { attributes.push(value.to_string()) } } "b" => { if let Some(media_desc) = media_descriptions.last_mut() { media_desc.bandwidth.push(value.to_string()) } else { bandwidth.push(value.to_string()) } } "k" => { if let Some(key) = media_descriptions.last_mut() { key.encryption_key = Some(value.to_string()) } else { encryption_key = Some(value.to_string()) } } "c" => { if let Some(media_desc) = media_descriptions.last_mut() { media_desc.connection_information = Some(value.to_string()) } else { connection_information = Some(value.to_string()) } } x => bail!("unknown sdp type ({x:?})"), } } Ok(Self { bandwidth, connection_information, attributes, description_uri, email_address, encryption_key, phone_number, session_information, time_zone_adjustments, media_descriptions, originator: originator.ok_or(anyhow!("originator missing"))?, session_name: session_name.ok_or(anyhow!("session name missing"))?, version: version.ok_or(anyhow!("version missing"))?, time_descriptions, }) } } impl Display for SessionDescription { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let Self { attributes, bandwidth, connection_information, description_uri, email_address, encryption_key, media_descriptions, originator, phone_number, session_information, session_name, time_descriptions, time_zone_adjustments, version, } = self; writeln!(f, "v={version}")?; writeln!(f, "o={originator}")?; writeln!(f, "s={session_name}")?; // TODO check order if let Some(session_information) = session_information { writeln!(f, "i={session_information}")?; } if let Some(description_uri) = description_uri { writeln!(f, "u={description_uri}")?; } if let Some(email_address) = email_address { writeln!(f, "e={email_address}")?; } if let Some(phone_number) = phone_number { writeln!(f, "p={phone_number}")?; } for TimeDescription { time, repeat_times } in time_descriptions { writeln!(f, "t={time}")?; for repeat_time in repeat_times { writeln!(f, "r={repeat_time}")?; } } if let Some(encryption_key) = encryption_key { writeln!(f, "k={encryption_key}")?; } if let Some(time_zone_adjustments) = time_zone_adjustments { writeln!(f, "z={time_zone_adjustments}")?; } if let Some(connection_information) = connection_information { writeln!(f, "c={connection_information}")?; } for bandwidth in bandwidth { writeln!(f, "b={bandwidth}")?; } for attribute in attributes { writeln!(f, "a={attribute}")?; } for MediaDescription { name, title, connection_information, bandwidth, encryption_key, attributes, } in media_descriptions { writeln!(f, "m={name}")?; if let Some(title) = title { writeln!(f, "t={title}")?; // TODO was offline, check param name } if let Some(connection_information) = connection_information { writeln!(f, "c={connection_information}")?; } for bandwidth in bandwidth { writeln!(f, "b={bandwidth}")?; } if let Some(encryption_key) = encryption_key { writeln!(f, "k={encryption_key}")?; } for attribute in attributes { writeln!(f, "a={attribute}")?; } } Ok(()) } }