Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  lib.rs   Sprache: unbekannt

 
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#![warn(clippy::all)]
#![forbid(unsafe_code)]

#[macro_use]
extern crate log;
#[cfg(feature = "serialize")]
#[macro_use]
extern crate serde_derive;
#[cfg(feature = "serialize")]
extern crate serde;
use std::convert::TryFrom;
use std::fmt;

#[macro_use]
pub mod attribute_type;
pub mod address;
pub mod anonymizer;
pub mod error;
pub mod media_type;
pub mod network;

use address::{AddressTyped, ExplicitlyTypedAddress};
use anonymizer::{AnonymizingClone, StatefulSdpAnonymizer};
use attribute_type::{
    parse_attribute, SdpAttribute, SdpAttributeRid, SdpAttributeSimulcastVersion, SdpAttributeType,
    SdpSingleDirection,
};
use error::{SdpParserError, SdpParserInternalError};
use media_type::{
    parse_media, parse_media_vector, SdpFormatList, SdpMedia, SdpMediaLine, SdpMediaValue,
    SdpProtocolValue,
};
use network::{parse_address_type, parse_network_type};

/*
 * RFC4566
 * bandwidth-fields =    *(%x62 "=" bwtype ":" bandwidth CRLF)
 */
#[derive(Clone)]
#[cfg_attr(feature = "serialize", derive(Serialize))]
#[cfg_attr(feature = "enhanced_debug", derive(Debug))]
pub enum SdpBandwidth {
    As(u32),
    Ct(u32),
    Tias(u32),
    Unknown(String, u32),
}

impl fmt::Display for SdpBandwidth {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let (tp_string, value) = match *self {
            SdpBandwidth::As(ref x) => ("AS", x),
            SdpBandwidth::Ct(ref x) => ("CT", x),
            SdpBandwidth::Tias(ref x) => ("TIAS", x),
            SdpBandwidth::Unknown(ref tp, ref x) => (&tp[..], x),
        };
        write!(f, "{tp_string}:{value}")
    }
}

/*
 * RFC4566
 * connection-field =    [%x63 "=" nettype SP addrtype SP
 *                       connection-address CRLF]
 */
#[derive(Clone)]
#[cfg_attr(feature = "serialize", derive(Serialize))]
#[cfg_attr(feature = "enhanced_debug", derive(Debug))]
pub struct SdpConnection {
    pub address: ExplicitlyTypedAddress,
    pub ttl: Option<u8>,
    pub amount: Option<u32>,
}

impl fmt::Display for SdpConnection {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        self.address.fmt(f)?;
        write_option_string!(f, "/{}", self.ttl)?;
        write_option_string!(f, "/{}", self.amount)
    }
}

impl AnonymizingClone for SdpConnection {
    fn masked_clone(&self, anon: &mut StatefulSdpAnonymizer) -> Self {
        let mut masked = self.clone();
        masked.address = anon.mask_typed_address(&self.address);
        masked
    }
}

/*
 * RFC4566
 * origin-field =        %x6f "=" username SP sess-id SP sess-version SP
 *                       nettype SP addrtype SP unicast-address CRLF
 */
#[derive(Clone)]
#[cfg_attr(feature = "serialize", derive(Serialize))]
#[cfg_attr(feature = "enhanced_debug", derive(Debug))]
pub struct SdpOrigin {
    pub username: String,
    pub session_id: u64,
    pub session_version: u64,
    pub unicast_addr: ExplicitlyTypedAddress,
}

impl fmt::Display for SdpOrigin {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(
            f,
            "{username} {sess_id} {sess_vers} {unicast_addr}",
            username = self.username,
            sess_id = self.session_id,
            sess_vers = self.session_version,
            unicast_addr = self.unicast_addr
        )
    }
}

impl AnonymizingClone for SdpOrigin {
    fn masked_clone(&self, anon: &mut StatefulSdpAnonymizer) -> Self {
        let mut masked = self.clone();
        masked.username = anon.mask_origin_user(&self.username);
        masked.unicast_addr = anon.mask_typed_address(&masked.unicast_addr);
        masked
    }
}

/*
 * RFC4566
 * time-fields =         1*( %x74 "=" start-time SP stop-time
 *                       *(CRLF repeat-fields) CRLF)
 *                       [zone-adjustments CRLF]
 */
#[derive(Clone)]
#[cfg_attr(feature = "serialize", derive(Serialize))]
#[cfg_attr(feature = "enhanced_debug", derive(Debug))]
pub struct SdpTiming {
    pub start: u64,
    pub stop: u64,
}

impl fmt::Display for SdpTiming {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{start} {stop}", start = self.start, stop = self.stop)
    }
}

#[cfg_attr(feature = "serialize", derive(Serialize))]
#[cfg_attr(feature = "enhanced_debug", derive(Debug))]
pub enum SdpType {
    // Note: Email, Information, Key, Phone, Repeat, Uri and Zone are left out
    //       on purposes as we don't want to support them.
    Attribute(SdpAttribute),
    Bandwidth(SdpBandwidth),
    Connection(SdpConnection),
    Media(SdpMediaLine),
    Origin(SdpOrigin),
    Session(String),
    Timing(SdpTiming),
    Version(u64),
}

#[cfg_attr(feature = "serialize", derive(Serialize))]
#[cfg_attr(feature = "enhanced_debug", derive(Debug))]
pub struct SdpLine {
    pub line_number: usize,
    pub sdp_type: SdpType,
    pub text: String,
}

/*
 * RFC4566
 * ; SDP Syntax
 * session-description = proto-version
 *                       origin-field
 *                       session-name-field
 *                       information-field
 *                       uri-field
 *                       email-fields
 *                       phone-fields
 *                       connection-field
 *                       bandwidth-fields
 *                       time-fields
 *                       key-field
 *                       attribute-fields
 *                       media-descriptions
 */
#[derive(Clone)]
#[cfg_attr(feature = "serialize", derive(Serialize))]
#[cfg_attr(feature = "enhanced_debug", derive(Debug))]
pub struct SdpSession {
    pub version: u64,
    pub origin: SdpOrigin,
    pub session: Option<String>,
    pub connection: Option<SdpConnection>,
    pub bandwidth: Vec<SdpBandwidth>,
    pub timing: Option<SdpTiming>,
    pub attribute: Vec<SdpAttribute>,
    pub media: Vec<SdpMedia>,
    pub warnings: Vec<SdpParserError>, // unsupported values:
                                       // information: Option<String>,
                                       // uri: Option<String>,
                                       // email: Option<String>,
                                       // phone: Option<String>,
                                       // repeat: Option<String>,
                                       // zone: Option<String>,
                                       // key: Option<String>
}

impl fmt::Display for SdpSession {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(
            f,
            "v={version}\r\n\
             o={origin}\r\n\
             s={session}\r\n\
             {timing}\
             {bandwidth}\
             {connection}\
             {session_attributes}\
             {media_sections}",
            version = self.version,
            origin = self.origin,
            session = self.get_session_text(),
            timing = option_to_string!("t={}\r\n", self.timing),
            bandwidth = maybe_vector_to_string!("b={}\r\n", self.bandwidth, "\r\nb="),
            connection = option_to_string!("c={}\r\n", self.connection),
            session_attributes = maybe_vector_to_string!("a={}\r\n", self.attribute, "\r\na="),
            media_sections = self.media.iter().map(|s| s.to_string()).collect::<String>(),
        )
    }
}

impl SdpSession {
    pub fn new(version: u64, origin: SdpOrigin, session: String) -> SdpSession {
        let session = match session.trim() {
            s if !s.is_empty() => Some(s.to_owned()),
            _ => None,
        };
        SdpSession {
            version,
            origin,
            session,
            connection: None,
            bandwidth: Vec::new(),
            timing: None,
            attribute: Vec::new(),
            media: Vec::new(),
            warnings: Vec::new(),
        }
    }

    pub fn get_version(&self) -> u64 {
        self.version
    }

    pub fn get_origin(&self) -> &SdpOrigin {
        &self.origin
    }

    pub fn get_session(&self) -> &Option<String> {
        &self.session
    }

    pub fn get_session_text(&self) -> &str {
        if let Some(text) = &self.session {
            text.as_str()
        } else {
            " "
        }
    }
    pub fn get_connection(&self) -> &Option<SdpConnection> {
        &self.connection
    }

    pub fn set_connection(&mut self, c: SdpConnection) {
        self.connection = Some(c)
    }

    pub fn add_bandwidth(&mut self, b: SdpBandwidth) {
        self.bandwidth.push(b)
    }

    pub fn set_timing(&mut self, t: SdpTiming) {
        self.timing = Some(t)
    }

    pub fn add_attribute(&mut self, a: SdpAttribute) -> Result<(), SdpParserInternalError> {
        if !a.allowed_at_session_level() {
            return Err(SdpParserInternalError::Generic(format!(
                "{a} not allowed at session level"
            )));
        };
        self.attribute.push(a);
        Ok(())
    }

    pub fn extend_media(&mut self, v: Vec<SdpMedia>) {
        self.media.extend(v)
    }

    pub fn parse_session_vector(&mut self, lines: &mut Vec<SdpLine>) -> Result<(), SdpParserError> {
        while !lines.is_empty() {
            let line = lines.remove(0);
            match line.sdp_type {
                SdpType::Attribute(a) => {
                    let _line_number = line.line_number;
                    self.add_attribute(a).map_err(|e: SdpParserInternalError| {
                        SdpParserError::Sequence {
                            message: format!("{e}"),
                            line_number: _line_number,
                        }
                    })?
                }
                SdpType::Bandwidth(b) => self.add_bandwidth(b),
                SdpType::Timing(t) => self.set_timing(t),
                SdpType::Connection(c) => self.set_connection(c),

                SdpType::Origin(_) | SdpType::Session(_) | SdpType::Version(_) => {
                    return Err(SdpParserError::Sequence {
                        message: "version, origin or session at wrong level".to_string(),
                        line_number: line.line_number,
                    });
                }
                SdpType::Media(_) => {
                    return Err(SdpParserError::Sequence {
                        message: "media line not allowed in session parser".to_string(),
                        line_number: line.line_number,
                    });
                }
            }
        }
        Ok(())
    }

    pub fn get_attribute(&self, t: SdpAttributeType) -> Option<&SdpAttribute> {
        self.attribute
            .iter()
            .find(|a| SdpAttributeType::from(*a) == t)
    }

    pub fn add_media(
        &mut self,
        media_type: SdpMediaValue,
        direction: SdpAttribute,
        port: u32,
        protocol: SdpProtocolValue,
        addr: ExplicitlyTypedAddress,
    ) -> Result<(), SdpParserInternalError> {
        let mut media = SdpMedia::new(SdpMediaLine {
            media: media_type,
            port,
            port_count: 1,
            proto: protocol,
            formats: SdpFormatList::Integers(Vec::new()),
        });

        media.add_attribute(direction)?;

        media.set_connection(SdpConnection {
            address: addr,
            ttl: None,
            amount: None,
        });

        self.media.push(media);

        Ok(())
    }
}

impl AnonymizingClone for SdpSession {
    fn masked_clone(&self, anon: &mut StatefulSdpAnonymizer) -> Self {
        let mut masked: SdpSession = SdpSession {
            version: self.version,
            session: self.session.clone(),
            origin: self.origin.masked_clone(anon),
            connection: self.connection.clone(),
            timing: self.timing.clone(),
            bandwidth: self.bandwidth.clone(),
            attribute: Vec::new(),
            media: Vec::new(),
            warnings: Vec::new(),
        };
        masked.origin = self.origin.masked_clone(anon);
        masked.connection = masked.connection.map(|con| con.masked_clone(anon));
        for i in &self.attribute {
            masked.attribute.push(i.masked_clone(anon));
        }
        masked
    }
}

/* removing this wrap would not allow us to call this from the match statement inside
 * parse_sdp_line() */
#[allow(clippy::unnecessary_wraps)]
fn parse_session(value: &str) -> Result<SdpType, SdpParserInternalError> {
    trace!("session: {}", value);
    Ok(SdpType::Session(String::from(value)))
}

fn parse_version(value: &str) -> Result<SdpType, SdpParserInternalError> {
    let ver = value.parse::<u64>()?;
    if ver != 0 {
        return Err(SdpParserInternalError::Generic(format!(
            "version type contains unsupported value {ver}"
        )));
    };
    trace!("version: {}", ver);
    Ok(SdpType::Version(ver))
}

fn parse_origin(value: &str) -> Result<SdpType, SdpParserInternalError> {
    let mut tokens = value.split_whitespace();
    let username = match tokens.next() {
        None => {
            return Err(SdpParserInternalError::Generic(
                "Origin type is missing username token".to_string(),
            ));
        }
        Some(x) => x,
    };
    let session_id = match tokens.next() {
        None => {
            return Err(SdpParserInternalError::Generic(
                "Origin type is missing session ID token".to_string(),
            ));
        }
        Some(x) => x.parse::<u64>()?,
    };
    let session_version = match tokens.next() {
        None => {
            return Err(SdpParserInternalError::Generic(
                "Origin type is missing session version token".to_string(),
            ));
        }
        Some(x) => x.parse::<u64>()?,
    };
    match tokens.next() {
        None => {
            return Err(SdpParserInternalError::Generic(
                "Origin type is missing network type token".to_string(),
            ));
        }
        Some(x) => parse_network_type(x)?,
    };
    let addrtype = match tokens.next() {
        None => {
            return Err(SdpParserInternalError::Generic(
                "Origin type is missing address type token".to_string(),
            ));
        }
        Some(x) => parse_address_type(x)?,
    };
    let unicast_addr = match tokens.next() {
        None => {
            return Err(SdpParserInternalError::Generic(
                "Origin type is missing IP address token".to_string(),
            ));
        }
        Some(x) => ExplicitlyTypedAddress::try_from((addrtype, x))?,
    };
    if addrtype != unicast_addr.address_type() {
        return Err(SdpParserInternalError::Generic(
            "Origin addrtype does not match address.".to_string(),
        ));
    }
    let o = SdpOrigin {
        username: String::from(username),
        session_id,
        session_version,
        unicast_addr,
    };
    trace!("origin: {}", o);
    Ok(SdpType::Origin(o))
}

fn parse_connection(value: &str) -> Result<SdpType, SdpParserInternalError> {
    let cv: Vec<&str> = value.split_whitespace().collect();
    if cv.len() != 3 {
        return Err(SdpParserInternalError::Generic(
            "connection attribute must have three tokens".to_string(),
        ));
    }
    parse_network_type(cv[0])?;
    let addrtype = parse_address_type(cv[1])?;
    let mut ttl = None;
    let mut amount = None;
    let mut addr_token = cv[2];
    if addr_token.find('/').is_some() {
        let addr_tokens: Vec<&str> = addr_token.split('/').collect();
        if addr_tokens.len() >= 3 {
            amount = Some(addr_tokens[2].parse::<u32>()?);
        }
        ttl = Some(addr_tokens[1].parse::<u8>()?);
        addr_token = addr_tokens[0];
    }
    let address = ExplicitlyTypedAddress::try_from((addrtype, addr_token))?;
    let c = SdpConnection {
        address,
        ttl,
        amount,
    };
    trace!("connection: {}", c);
    Ok(SdpType::Connection(c))
}

fn parse_bandwidth(value: &str) -> Result<SdpType, SdpParserInternalError> {
    let bv: Vec<&str> = value.split(':').collect();
    if bv.len() != 2 {
        return Err(SdpParserInternalError::Generic(
            "bandwidth attribute must have two tokens".to_string(),
        ));
    }
    let bandwidth = bv[1].parse::<u32>()?;
    let bw = match bv[0].to_uppercase().as_ref() {
        "AS" => SdpBandwidth::As(bandwidth),
        "CT" => SdpBandwidth::Ct(bandwidth),
        "TIAS" => SdpBandwidth::Tias(bandwidth),
        _ => SdpBandwidth::Unknown(String::from(bv[0]), bandwidth),
    };
    trace!("bandwidth: {}", bw);
    Ok(SdpType::Bandwidth(bw))
}

fn parse_timing(value: &str) -> Result<SdpType, SdpParserInternalError> {
    let tv: Vec<&str> = value.split_whitespace().collect();
    if tv.len() != 2 {
        return Err(SdpParserInternalError::Generic(
            "timing attribute must have two tokens".to_string(),
        ));
    }
    let start = tv[0].parse::<u64>()?;
    let stop = tv[1].parse::<u64>()?;
    let t = SdpTiming { start, stop };
    trace!("timing: {}", t);
    Ok(SdpType::Timing(t))
}

pub fn parse_sdp_line(line: &str, line_number: usize) -> Result<SdpLine, SdpParserError> {
    if line.find('=').is_none() {
        return Err(SdpParserError::Line {
            error: SdpParserInternalError::Generic("missing = character in line".to_string()),
            line: line.to_string(),
            line_number,
        });
    }
    let mut splitted_line = line.splitn(2, '=');
    let line_type = match splitted_line.next() {
        None => {
            return Err(SdpParserError::Line {
                error: SdpParserInternalError::Generic("missing type".to_string()),
                line: line.to_string(),
                line_number,
            });
        }
        Some(t) => {
            let trimmed = t.trim();
            if trimmed.len() > 1 {
                return Err(SdpParserError::Line {
                    error: SdpParserInternalError::Generic("type too long".to_string()),
                    line: line.to_string(),
                    line_number,
                });
            }
            if trimmed.is_empty() {
                return Err(SdpParserError::Line {
                    error: SdpParserInternalError::Generic("type is empty".to_string()),
                    line: line.to_string(),
                    line_number,
                });
            }
            trimmed.to_lowercase()
        }
    };
    let (line_value, untrimmed_line_value) = match splitted_line.next() {
        None => {
            return Err(SdpParserError::Line {
                error: SdpParserInternalError::Generic("missing value".to_string()),
                line: line.to_string(),
                line_number,
            });
        }
        Some(v) => {
            let trimmed = v.trim();
            // For compatibility with sites that don't adhere to "s=-" for no session ID
            if trimmed.is_empty() && line_type.as_str() != "s" {
                return Err(SdpParserError::Line {
                    error: SdpParserInternalError::Generic("value is empty".to_string()),
                    line: line.to_string(),
                    line_number,
                });
            }
            (trimmed, v)
        }
    };
    match line_type.as_ref() {
        "a" => parse_attribute(line_value),
        "b" => parse_bandwidth(line_value),
        "c" => parse_connection(line_value),
        "e" => Err(SdpParserInternalError::Generic(format!(
            "unsupported type email: {line_value}"
        ))),
        "i" => Err(SdpParserInternalError::Generic(format!(
            "unsupported type information: {line_value}"
        ))),
        "k" => Err(SdpParserInternalError::Generic(format!(
            "unsupported insecure key exchange: {line_value}"
        ))),
        "m" => parse_media(line_value),
        "o" => parse_origin(line_value),
        "p" => Err(SdpParserInternalError::Generic(format!(
            "unsupported type phone: {line_value}"
        ))),
        "r" => Err(SdpParserInternalError::Generic(format!(
            "unsupported type repeat: {line_value}"
        ))),
        "s" => parse_session(untrimmed_line_value),
        "t" => parse_timing(line_value),
        "u" => Err(SdpParserInternalError::Generic(format!(
            "unsupported type uri: {line_value}"
        ))),
        "v" => parse_version(line_value),
        "z" => Err(SdpParserInternalError::Generic(format!(
            "unsupported type zone: {line_value}"
        ))),
        _ => Err(SdpParserInternalError::Generic(
            "unknown sdp type".to_string(),
        )),
    }
    .map(|sdp_type| SdpLine {
        line_number,
        sdp_type,
        text: line.to_owned(),
    })
    .map_err(|e| match e {
        SdpParserInternalError::UnknownAddressType(..)
        | SdpParserInternalError::AddressTypeMismatch { .. }
        | SdpParserInternalError::Generic(..)
        | SdpParserInternalError::Integer(..)
        | SdpParserInternalError::Float(..)
        | SdpParserInternalError::Domain(..)
        | SdpParserInternalError::IpAddress(..) => SdpParserError::Line {
            error: e,
            line: line.to_string(),
            line_number,
        },
        SdpParserInternalError::Unsupported(..) => SdpParserError::Unsupported {
            error: e,
            line: line.to_string(),
            line_number,
        },
    })
}

fn sanity_check_sdp_session(session: &SdpSession) -> Result<(), SdpParserError> {
    let make_seq_error = |x: &str| SdpParserError::Sequence {
        message: x.to_string(),
        line_number: 0,
    };

    if session.timing.is_none() {
        return Err(make_seq_error("Missing timing type at session level"));
    }
    // Checks that all media have connections if there is no top level
    // This explicitly allows for zero connection lines if there are no media
    // sections for interoperability reasons.
    let media_cons = &session.media.iter().all(|m| m.get_connection().is_some());
    if !media_cons && session.get_connection().is_none() {
        return Err(make_seq_error(
            "Without connection type at session level all media sections must have connection types",
        ));
    }

    // Check that extmaps are not defined on session and media level
    if session.get_attribute(SdpAttributeType::Extmap).is_some() {
        for msection in &session.media {
            if msection.get_attribute(SdpAttributeType::Extmap).is_some() {
                return Err(make_seq_error(
                    "Extmap can't be define at session and media level",
                ));
            }
        }
    }

    for msection in &session.media {
        if msection
            .get_attribute(SdpAttributeType::RtcpMuxOnly)
            .is_some()
            && msection.get_attribute(SdpAttributeType::RtcpMux).is_none()
        {
            return Err(make_seq_error(
                "rtcp-mux-only media sections must also contain the rtcp-mux attribute",
            ));
        }

        let rids: Vec<&SdpAttributeRid> = msection
            .get_attributes()
            .iter()
            .filter_map(|attr| match *attr {
                SdpAttribute::Rid(ref rid) => Some(rid),
                _ => None,
            })
            .collect();
        let recv_rids: Vec<&str> = rids
            .iter()
            .filter_map(|rid| match rid.direction {
                SdpSingleDirection::Recv => Some(rid.id.as_str()),
                _ => None,
            })
            .collect();
        let send_rids: Vec<&str> = rids
            .iter()
            .filter_map(|rid| match rid.direction {
                SdpSingleDirection::Send => Some(rid.id.as_str()),
                _ => None,
            })
            .collect();

        for rid_format in rids.iter().flat_map(|rid| &rid.formats) {
            match *msection.get_formats() {
                SdpFormatList::Integers(ref int_fmt) => {
                    if !int_fmt.contains(&(u32::from(*rid_format))) {
                        return Err(make_seq_error(
                            "Rid pts must be declared in the media section",
                        ));
                    }
                }
                SdpFormatList::Strings(ref str_fmt) => {
                    if !str_fmt.contains(&rid_format.to_string()) {
                        return Err(make_seq_error(
                            "Rid pts must be declared in the media section",
                        ));
                    }
                }
            }
        }

        if let Some(SdpAttribute::Simulcast(simulcast)) =
            msection.get_attribute(SdpAttributeType::Simulcast)
        {
            let check_defined_rids =
                |simulcast_version_list: &Vec<SdpAttributeSimulcastVersion>,
                 rid_ids: &[&str]|
                 -> Result<(), SdpParserError> {
                    for simulcast_rid in simulcast_version_list.iter().flat_map(|x| &x.ids) {
                        if !rid_ids.contains(&simulcast_rid.id.as_str()) {
                            return Err(make_seq_error(
                                "Simulcast RIDs must be defined in any rid attribute",
                            ));
                        }
                    }
                    Ok(())
                };

            check_defined_rids(&simulcast.receive, &recv_rids)?;
            check_defined_rids(&simulcast.send, &send_rids)?;
        }
    }

    Ok(())
}

fn parse_sdp_vector(lines: &mut Vec<SdpLine>) -> Result<SdpSession, SdpParserError> {
    if lines.len() < 4 {
        return Err(SdpParserError::Sequence {
            message: "SDP neeeds at least 4 lines".to_string(),
            line_number: 0,
        });
    }

    let version = match lines.remove(0).sdp_type {
        SdpType::Version(v) => v,
        _ => {
            return Err(SdpParserError::Sequence {
                message: "first line needs to be version number".to_string(),
                line_number: 0,
            });
        }
    };
    let origin = match lines.remove(0).sdp_type {
        SdpType::Origin(v) => v,
        _ => {
            return Err(SdpParserError::Sequence {
                message: "second line needs to be origin".to_string(),
                line_number: 1,
            });
        }
    };
    let session = match lines.remove(0).sdp_type {
        SdpType::Session(v) => v,
        _ => {
            return Err(SdpParserError::Sequence {
                message: "third line needs to be session".to_string(),
                line_number: 2,
            });
        }
    };
    let mut sdp_session = SdpSession::new(version, origin, session);

    let _media_pos = lines
        .iter()
        .position(|l| matches!(l.sdp_type, SdpType::Media(_)));

    match _media_pos {
        Some(p) => {
            let mut media: Vec<_> = lines.drain(p..).collect();
            sdp_session.parse_session_vector(lines)?;
            sdp_session.extend_media(parse_media_vector(&mut media)?);
        }
        None => sdp_session.parse_session_vector(lines)?,
    };

    sanity_check_sdp_session(&sdp_session)?;
    Ok(sdp_session)
}

pub fn parse_sdp(sdp: &str, fail_on_warning: bool) -> Result<SdpSession, SdpParserError> {
    if sdp.is_empty() {
        return Err(SdpParserError::Line {
            error: SdpParserInternalError::Generic("empty SDP".to_string()),
            line: sdp.to_string(),
            line_number: 0,
        });
    }
    // see test_parse_sdp_minimal_sdp_successfully
    if sdp.len() < 51 {
        return Err(SdpParserError::Line {
            error: SdpParserInternalError::Generic("string too short to be valid SDP".to_string()),
            line: sdp.to_string(),
            line_number: 0,
        });
    }
    let lines = sdp.lines();
    let mut errors: Vec<SdpParserError> = Vec::new();
    let mut warnings: Vec<SdpParserError> = Vec::new();
    let mut sdp_lines: Vec<SdpLine> = Vec::new();
    for (line_number, line) in lines.enumerate() {
        let stripped_line = line.trim();
        if stripped_line.is_empty() {
            continue;
        }
        match parse_sdp_line(line, line_number) {
            Ok(n) => {
                sdp_lines.push(n);
            }
            Err(e) => {
                match e {
                    // TODO is this really a good way to accomplish this?
                    SdpParserError::Line {
                        error,
                        line,
                        line_number,
                    } => errors.push(SdpParserError::Line {
                        error,
                        line,
                        line_number,
                    }),
                    SdpParserError::Unsupported {
                        error,
                        line,
                        line_number,
                    } => {
                        warnings.push(SdpParserError::Unsupported {
                            error,
                            line,
                            line_number,
                        });
                    }
                    SdpParserError::Sequence {
                        message,
                        line_number,
                    } => errors.push(SdpParserError::Sequence {
                        message,
                        line_number,
                    }),
                }
            }
        };
    }

    if fail_on_warning && (!warnings.is_empty()) {
        return Err(warnings.remove(0));
    }

    // We just return the last of the errors here
    if let Some(e) = errors.pop() {
        return Err(e);
    };

    let mut session = parse_sdp_vector(&mut sdp_lines)?;
    session.warnings = warnings;

    for warning in &session.warnings {
        warn!("Warning: {}", &warning);
    }

    Ok(session)
}

#[cfg(test)]
#[path = "./lib_tests.rs"]
mod tests;

[ Dauer der Verarbeitung: 0.34 Sekunden  (vorverarbeitet)  ]

                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge