use super::common::*;
use super::media::*;
use super::session::*;
use shared::error::{Error, Result};

use std::io::Cursor;
use url::Url;

const CANONICAL_MARSHAL_SDP: &str = "v=0\r\n\
     o=jdoe 2890844526 2890842807 IN IP4 10.47.16.5\r\n\
     s=SDP Seminar\r\n\
     i=A Seminar on the session description protocol\r\n\
     u=http://www.example.com/seminars/sdp.pdf\r\n\
     e=j.doe@example.com (Jane Doe)\r\n\
     p=+1 617 555-6011\r\n\
     c=IN IP4 224.2.17.12/127\r\n\
     b=X-YZ:128\r\n\
     b=AS:12345\r\n\
     t=2873397496 2873404696\r\n\
     t=3034423619 3042462419\r\n\
     r=604800 3600 0 90000\r\n\
     z=2882844526 -3600 2898848070 0\r\n\
     k=prompt\r\n\
     a=candidate:0 1 UDP 2113667327 203.0.113.1 54400 typ host\r\n\
     a=recvonly\r\n\
     m=audio 49170 RTP/AVP 0\r\n\
     i=Vivamus a posuere nisl\r\n\
     c=IN IP4 203.0.113.1\r\n\
     b=X-YZ:128\r\n\
     k=prompt\r\n\
     a=sendrecv\r\n\
     m=video 51372 RTP/AVP 99\r\n\
     a=rtpmap:99 h263-1998/90000\r\n";

#[test]
fn test_unmarshal_marshal() -> Result<()> {
    let input = CANONICAL_MARSHAL_SDP;
    let mut reader = Cursor::new(input.as_bytes());
    let sdp = SessionDescription::unmarshal(&mut reader)?;
    let output = sdp.marshal();
    assert_eq!(output, input);

    Ok(())
}

#[test]
fn test_marshal() -> Result<()> {
    let sd = SessionDescription {
        version: 0,
        origin: Origin {
            username: "jdoe".to_string(),
            session_id: 2890844526,
            session_version: 2890842807,
            network_type: "IN".to_string(),
            address_type: "IP4".to_string(),
            unicast_address: "10.47.16.5".to_string(),
        },
        session_name: "SDP Seminar".to_string(),
        session_information: Some("A Seminar on the session description protocol".to_string()),
        uri: Some(Url::parse("http://www.example.com/seminars/sdp.pdf")?),
        email_address: Some("j.doe@example.com (Jane Doe)".to_string()),
        phone_number: Some("+1 617 555-6011".to_string()),
        connection_information: Some(ConnectionInformation {
            network_type: "IN".to_string(),
            address_type: "IP4".to_string(),
            address: Some(Address {
                address: "224.2.17.12".to_string(),
                ttl: Some(127),
                range: None,
            }),
        }),
        bandwidth: vec![
            Bandwidth {
                experimental: true,
                bandwidth_type: "YZ".to_string(),
                bandwidth: 128,
            },
            Bandwidth {
                experimental: false,
                bandwidth_type: "AS".to_string(),
                bandwidth: 12345,
            },
        ],
        time_descriptions: vec![
            TimeDescription {
                timing: Timing {
                    start_time: 2873397496,
                    stop_time: 2873404696,
                },
                repeat_times: vec![],
            },
            TimeDescription {
                timing: Timing {
                    start_time: 3034423619,
                    stop_time: 3042462419,
                },
                repeat_times: vec![RepeatTime {
                    interval: 604800,
                    duration: 3600,
                    offsets: vec![0, 90000],
                }],
            },
        ],
        time_zones: vec![
            TimeZone {
                adjustment_time: 2882844526,
                offset: -3600,
            },
            TimeZone {
                adjustment_time: 2898848070,
                offset: 0,
            },
        ],
        encryption_key: Some("prompt".to_string()),
        attributes: vec![
            Attribute::new(
                "candidate".to_string(),
                Some("0 1 UDP 2113667327 203.0.113.1 54400 typ host".to_string()),
            ),
            Attribute::new("recvonly".to_string(), None),
        ],
        media_descriptions: vec![
            MediaDescription {
                media_name: MediaName {
                    media: "audio".to_string(),
                    port: RangedPort {
                        value: 49170,
                        range: None,
                    },
                    protos: vec!["RTP".to_string(), "AVP".to_string()],
                    formats: vec!["0".to_string()],
                },
                media_title: Some("Vivamus a posuere nisl".to_string()),
                connection_information: Some(ConnectionInformation {
                    network_type: "IN".to_string(),
                    address_type: "IP4".to_string(),
                    address: Some(Address {
                        address: "203.0.113.1".to_string(),
                        ttl: None,
                        range: None,
                    }),
                }),
                bandwidth: vec![Bandwidth {
                    experimental: true,
                    bandwidth_type: "YZ".to_string(),
                    bandwidth: 128,
                }],
                encryption_key: Some("prompt".to_string()),
                attributes: vec![Attribute::new("sendrecv".to_string(), None)],
            },
            MediaDescription {
                media_name: MediaName {
                    media: "video".to_string(),
                    port: RangedPort {
                        value: 51372,
                        range: None,
                    },
                    protos: vec!["RTP".to_string(), "AVP".to_string()],
                    formats: vec!["99".to_string()],
                },
                media_title: None,
                connection_information: None,
                bandwidth: vec![],
                encryption_key: None,
                attributes: vec![Attribute::new(
                    "rtpmap".to_string(),
                    Some("99 h263-1998/90000".to_string()),
                )],
            },
        ],
    };

    let actual = sd.marshal();
    assert!(
        actual == CANONICAL_MARSHAL_SDP,
        "error:\n\nEXPECTED:\n{CANONICAL_MARSHAL_SDP}\nACTUAL:\n{actual}!!!!\n"
    );

    Ok(())
}

const BASE_SDP: &str = "v=0\r\n\
o=jdoe 2890844526 2890842807 IN IP4 10.47.16.5\r\n\
s=SDP Seminar\r\n";

const SESSION_INFORMATION_SDP: &str = "v=0\r\n\
o=jdoe 2890844526 2890842807 IN IP4 10.47.16.5\r\n\
s=SDP Seminar\r\n\
i=A Seminar on the session description protocol\r\n\
t=3034423619 3042462419\r\n";

// https://tools.ietf.org/html/rfc4566#section-5
// Parsers SHOULD be tolerant and also accept records terminated
// with a single newline character.
const SESSION_INFORMATION_SDPLFONLY: &str = "v=0\n\
o=jdoe 2890844526 2890842807 IN IP4 10.47.16.5\n\
s=SDP Seminar\n\
i=A Seminar on the session description protocol\n\
t=3034423619 3042462419\n";

// SessionInformationSDPCROnly = "v=0\r" +
// 	"o=jdoe 2890844526 2890842807 IN IP4 10.47.16.5\r" +
// 	"s=SDP Seminar\r"
// 	"i=A Seminar on the session description protocol\r" +
// 	"t=3034423619 3042462419\r"

// Other SDP parsers (e.g. one in VLC media player) allow
// empty lines.
const SESSION_INFORMATION_SDPEXTRA_CRLF: &str = "v=0\r\n\
o=jdoe 2890844526 2890842807 IN IP4 10.47.16.5\r\n\
\r\n\
s=SDP Seminar\r\n\
\r\n\
i=A Seminar on the session description protocol\r\n\
\r\n\
t=3034423619 3042462419\r\n\
\r\n";

const URI_SDP: &str = "v=0\r\n\
o=jdoe 2890844526 2890842807 IN IP4 10.47.16.5\r\n\
s=SDP Seminar\r\n\
u=http://www.example.com/seminars/sdp.pdf\r\n\
t=3034423619 3042462419\r\n";

const EMAIL_ADDRESS_SDP: &str = "v=0\r\n\
o=jdoe 2890844526 2890842807 IN IP4 10.47.16.5\r\n\
s=SDP Seminar\r\n\
e=j.doe@example.com (Jane Doe)\r\n\
t=3034423619 3042462419\r\n";

const PHONE_NUMBER_SDP: &str = "v=0\r\n\
o=jdoe 2890844526 2890842807 IN IP4 10.47.16.5\r\n\
s=SDP Seminar\r\n\
p=+1 617 555-6011\r\n\
t=3034423619 3042462419\r\n";

const SESSION_CONNECTION_INFORMATION_SDP: &str = "v=0\r\n\
o=jdoe 2890844526 2890842807 IN IP4 10.47.16.5\r\n\
s=SDP Seminar\r\n\
c=IN IP4 224.2.17.12/127\r\n\
t=3034423619 3042462419\r\n";

const SESSION_BANDWIDTH_SDP: &str = "v=0\r\n\
o=jdoe 2890844526 2890842807 IN IP4 10.47.16.5\r\n\
s=SDP Seminar\r\n\
b=X-YZ:128\r\n\
b=AS:12345\r\n\
t=3034423619 3042462419\r\n";

const TIMING_SDP: &str = "v=0\r\n\
o=jdoe 2890844526 2890842807 IN IP4 10.47.16.5\r\n\
s=SDP Seminar\r\n\
t=2873397496 2873404696\r\n";

// Short hand time notation is converted into NTP timestamp format in
// seconds. Because of that unittest comparisons will fail as the same time
// will be expressed in different units.
const REPEAT_TIMES_SDP: &str = "v=0\r\n\
o=jdoe 2890844526 2890842807 IN IP4 10.47.16.5\r\n\
s=SDP Seminar\r\n\
t=2873397496 2873404696\r\n\
r=604800 3600 0 90000\r\n\
r=3d 2h 0 21h\r\n";

const REPEAT_TIMES_SDPEXPECTED: &str = "v=0\r\n\
o=jdoe 2890844526 2890842807 IN IP4 10.47.16.5\r\n\
s=SDP Seminar\r\n\
t=2873397496 2873404696\r\n\
r=604800 3600 0 90000\r\n\
r=259200 7200 0 75600\r\n";

const REPEAT_TIMES_OVERFLOW_SDP: &str = "v=0\r\n\
o=jdoe 2890844526 2890842807 IN IP4 10.47.16.5\r\n\
s=SDP Seminar\r\n\
t=2873397496 2873404696\r\n\
r=604800 3600 0 90000\r\n\
r=106751991167301d 2h 0 21h\r\n";

const REPEAT_TIMES_SDPEXTRA_CRLF: &str = "v=0\r\n\
o=jdoe 2890844526 2890842807 IN IP4 10.47.16.5\r\n\
s=SDP Seminar\r\n\
t=2873397496 2873404696\r\n\
r=604800 3600 0 90000\r\n\
r=259200 7200 0 75600\r\n\
\r\n";

// The expected value looks a bit different for the same reason as mentioned
// above regarding RepeatTimes.
const TIME_ZONES_SDP: &str = "v=0\r\n\
o=jdoe 2890844526 2890842807 IN IP4 10.47.16.5\r\n\
s=SDP Seminar\r\n\
t=2873397496 2873404696\r\n\
r=2882844526 -1h 2898848070 0\r\n";

const TIME_ZONES_SDPEXPECTED: &str = "v=0\r\n\
o=jdoe 2890844526 2890842807 IN IP4 10.47.16.5\r\n\
s=SDP Seminar\r\n\
t=2873397496 2873404696\r\n\
r=2882844526 -3600 2898848070 0\r\n";

const TIME_ZONES_SDP2: &str = "v=0\r\n\
o=jdoe 2890844526 2890842807 IN IP4 10.47.16.5\r\n\
s=SDP Seminar\r\n\
t=2873397496 2873404696\r\n\
z=2882844526 -3600 2898848070 0\r\n";

const TIME_ZONES_SDP2EXTRA_CRLF: &str = "v=0\r\n\
o=jdoe 2890844526 2890842807 IN IP4 10.47.16.5\r\n\
s=SDP Seminar\r\n\
t=2873397496 2873404696\r\n\
z=2882844526 -3600 2898848070 0\r\n\
\r\n";

const SESSION_ENCRYPTION_KEY_SDP: &str = "v=0\r\n\
o=jdoe 2890844526 2890842807 IN IP4 10.47.16.5\r\n\
s=SDP Seminar\r\n\
t=2873397496 2873404696\r\n\
k=prompt\r\n";

const SESSION_ENCRYPTION_KEY_SDPEXTRA_CRLF: &str = "v=0\r\n\
o=jdoe 2890844526 2890842807 IN IP4 10.47.16.5\r\n\
s=SDP Seminar\r\n\
t=2873397496 2873404696\r\n\
k=prompt\r\n
\r\n";

const SESSION_ATTRIBUTES_SDP: &str = "v=0\r\n\
o=jdoe 2890844526 2890842807 IN IP4 10.47.16.5\r\n\
s=SDP Seminar\r\n\
t=2873397496 2873404696\r\n\
a=rtpmap:96 opus/48000\r\n";

const MEDIA_NAME_SDP: &str = "v=0\r\n\
o=jdoe 2890844526 2890842807 IN IP4 10.47.16.5\r\n\
s=SDP Seminar\r\n\
t=2873397496 2873404696\r\n\
m=video 51372 RTP/AVP 99\r\n\
m=audio 54400 RTP/SAVPF 0 96\r\n";

const MEDIA_NAME_SDPEXTRA_CRLF: &str = "v=0\r\n\
o=jdoe 2890844526 2890842807 IN IP4 10.47.16.5\r\n\
s=SDP Seminar\r\n\
t=2873397496 2873404696\r\n\
m=video 51372 RTP/AVP 99\r\n\
m=audio 54400 RTP/SAVPF 0 96\r\n
\r\n";

const MEDIA_TITLE_SDP: &str = "v=0\r\n\
o=jdoe 2890844526 2890842807 IN IP4 10.47.16.5\r\n\
s=SDP Seminar\r\n\
t=2873397496 2873404696\r\n\
m=video 51372 RTP/AVP 99\r\n\
m=audio 54400 RTP/SAVPF 0 96\r\n\
i=Vivamus a posuere nisl\r\n";

const MEDIA_CONNECTION_INFORMATION_SDP: &str = "v=0\r\n\
o=jdoe 2890844526 2890842807 IN IP4 10.47.16.5\r\n\
s=SDP Seminar\r\n\
t=2873397496 2873404696\r\n\
m=video 51372 RTP/AVP 99\r\n\
m=audio 54400 RTP/SAVPF 0 96\r\n\
c=IN IP4 203.0.113.1\r\n";

const MEDIA_CONNECTION_INFORMATION_SDPEXTRA_CRLF: &str = "v=0\r\n\
o=jdoe 2890844526 2890842807 IN IP4 10.47.16.5\r\n\
s=SDP Seminar\r\n\
t=2873397496 2873404696\r\n\
m=video 51372 RTP/AVP 99\r\n\
m=audio 54400 RTP/SAVPF 0 96\r\n\
c=IN IP4 203.0.113.1\r\n\
\r\n";

const MEDIA_DESCRIPTION_OUT_OF_ORDER_SDP: &str = "v=0\r\n\
o=jdoe 2890844526 2890842807 IN IP4 10.47.16.5\r\n\
s=SDP Seminar\r\n\
t=2873397496 2873404696\r\n\
m=video 51372 RTP/AVP 99\r\n\
m=audio 54400 RTP/SAVPF 0 96\r\n\
a=rtpmap:99 h263-1998/90000\r\n\
a=candidate:0 1 UDP 2113667327 203.0.113.1 54400 typ host\r\n\
c=IN IP4 203.0.113.1\r\n\
i=Vivamus a posuere nisl\r\n";

const MEDIA_DESCRIPTION_OUT_OF_ORDER_SDPACTUAL: &str = "v=0\r\n\
o=jdoe 2890844526 2890842807 IN IP4 10.47.16.5\r\n\
s=SDP Seminar\r\n\
t=2873397496 2873404696\r\n\
m=video 51372 RTP/AVP 99\r\n\
m=audio 54400 RTP/SAVPF 0 96\r\n\
i=Vivamus a posuere nisl\r\n\
c=IN IP4 203.0.113.1\r\n\
a=rtpmap:99 h263-1998/90000\r\n\
a=candidate:0 1 UDP 2113667327 203.0.113.1 54400 typ host\r\n";

const MEDIA_BANDWIDTH_SDP: &str = "v=0\r\n\
o=jdoe 2890844526 2890842807 IN IP4 10.47.16.5\r\n\
s=SDP Seminar\r\n\
t=2873397496 2873404696\r\n\
m=video 51372 RTP/AVP 99\r\n\
m=audio 54400 RTP/SAVPF 0 96\r\n\
b=X-YZ:128\r\n\
b=AS:12345\r\n";

const MEDIA_TRANSPORT_BANDWIDTH_SDP: &str = "v=0\r\n\
o=jdoe 2890844526 2890842807 IN IP4 10.47.16.5\r\n\
s=SDP Seminar\r\n\
t=2873397496 2873404696\r\n\
m=video 51372 RTP/AVP 99\r\n\
m=audio 54400 RTP/SAVPF 0 96\r\n\
b=AS:12345\r\n\
b=TIAS:12345\r\n";

const MEDIA_ENCRYPTION_KEY_SDP: &str = "v=0\r\n\
o=jdoe 2890844526 2890842807 IN IP4 10.47.16.5\r\n\
s=SDP Seminar\r\n\
t=2873397496 2873404696\r\n\
m=video 51372 RTP/AVP 99\r\n\
m=audio 54400 RTP/SAVPF 0 96\r\n\
k=prompt\r\n";

const MEDIA_ENCRYPTION_KEY_SDPEXTRA_CRLF: &str = "v=0\r\n\
o=jdoe 2890844526 2890842807 IN IP4 10.47.16.5\r\n\
s=SDP Seminar\r\n\
t=2873397496 2873404696\r\n\
m=video 51372 RTP/AVP 99\r\n\
m=audio 54400 RTP/SAVPF 0 96\r\n\
k=prompt\r\n\
\r\n";

const MEDIA_ATTRIBUTES_SDP: &str = "v=0\r\n\
o=jdoe 2890844526 2890842807 IN IP4 10.47.16.5\r\n\
s=SDP Seminar\r\n\
t=2873397496 2873404696\r\n\
m=video 51372 RTP/AVP 99\r\n\
m=audio 54400 RTP/SAVPF 0 96\r\n\
a=rtpmap:99 h263-1998/90000\r\n\
a=candidate:0 1 UDP 2113667327 203.0.113.1 54400 typ host\r\n\
a=rtcp-fb:97 ccm fir\r\n\
a=rtcp-fb:97 nack\r\n\
a=rtcp-fb:97 nack pli\r\n";

const CANONICAL_UNMARSHAL_SDP: &str = "v=0\r\n\
o=jdoe 2890844526 2890842807 IN IP4 10.47.16.5\r\n\
s=SDP Seminar\r\n\
i=A Seminar on the session description protocol\r\n\
u=http://www.example.com/seminars/sdp.pdf\r\n\
e=j.doe@example.com (Jane Doe)\r\n\
p=+1 617 555-6011\r\n\
c=IN IP4 224.2.17.12/127\r\n\
b=X-YZ:128\r\n\
b=AS:12345\r\n\
t=2873397496 2873404696\r\n\
t=3034423619 3042462419\r\n\
r=604800 3600 0 90000\r\n\
z=2882844526 -3600 2898848070 0\r\n\
k=prompt\r\n\
a=candidate:0 1 UDP 2113667327 203.0.113.1 54400 typ host\r\n\
a=recvonly\r\n\
m=audio 49170 RTP/AVP 0\r\n\
i=Vivamus a posuere nisl\r\n\
c=IN IP4 203.0.113.1\r\n\
b=X-YZ:128\r\n\
k=prompt\r\n\
a=sendrecv\r\n\
m=video 51372 RTP/AVP 99\r\n\
a=rtpmap:99 h263-1998/90000\r\n";

#[test]
fn test_round_trip() -> Result<()> {
    let tests = vec![
        (
            "SessionInformationSDPLFOnly",
            SESSION_INFORMATION_SDPLFONLY,
            Some(SESSION_INFORMATION_SDP),
        ),
        (
            "SessionInformationSDPExtraCRLF",
            SESSION_INFORMATION_SDPEXTRA_CRLF,
            Some(SESSION_INFORMATION_SDP),
        ),
        ("SessionInformation", SESSION_INFORMATION_SDP, None),
        ("URI", URI_SDP, None),
        ("EmailAddress", EMAIL_ADDRESS_SDP, None),
        ("PhoneNumber", PHONE_NUMBER_SDP, None),
        (
            "RepeatTimesSDPExtraCRLF",
            REPEAT_TIMES_SDPEXTRA_CRLF,
            Some(REPEAT_TIMES_SDPEXPECTED),
        ),
        (
            "SessionConnectionInformation",
            SESSION_CONNECTION_INFORMATION_SDP,
            None,
        ),
        ("SessionBandwidth", SESSION_BANDWIDTH_SDP, None),
        ("SessionEncryptionKey", SESSION_ENCRYPTION_KEY_SDP, None),
        (
            "SessionEncryptionKeyExtraCRLF",
            SESSION_ENCRYPTION_KEY_SDPEXTRA_CRLF,
            Some(SESSION_ENCRYPTION_KEY_SDP),
        ),
        ("SessionAttributes", SESSION_ATTRIBUTES_SDP, None),
        (
            "TimeZonesSDP2ExtraCRLF",
            TIME_ZONES_SDP2EXTRA_CRLF,
            Some(TIME_ZONES_SDP2),
        ),
        ("MediaName", MEDIA_NAME_SDP, None),
        (
            "MediaNameExtraCRLF",
            MEDIA_NAME_SDPEXTRA_CRLF,
            Some(MEDIA_NAME_SDP),
        ),
        ("MediaTitle", MEDIA_TITLE_SDP, None),
        (
            "MediaConnectionInformation",
            MEDIA_CONNECTION_INFORMATION_SDP,
            None,
        ),
        (
            "MediaConnectionInformationExtraCRLF",
            MEDIA_CONNECTION_INFORMATION_SDPEXTRA_CRLF,
            Some(MEDIA_CONNECTION_INFORMATION_SDP),
        ),
        (
            "MediaDescriptionOutOfOrder",
            MEDIA_DESCRIPTION_OUT_OF_ORDER_SDP,
            Some(MEDIA_DESCRIPTION_OUT_OF_ORDER_SDPACTUAL),
        ),
        ("MediaBandwidth", MEDIA_BANDWIDTH_SDP, None),
        (
            "MediaTransportBandwidth",
            MEDIA_TRANSPORT_BANDWIDTH_SDP,
            None,
        ),
        ("MediaEncryptionKey", MEDIA_ENCRYPTION_KEY_SDP, None),
        (
            "MediaEncryptionKeyExtraCRLF",
            MEDIA_ENCRYPTION_KEY_SDPEXTRA_CRLF,
            Some(MEDIA_ENCRYPTION_KEY_SDP),
        ),
        ("MediaAttributes", MEDIA_ATTRIBUTES_SDP, None),
        ("CanonicalUnmarshal", CANONICAL_UNMARSHAL_SDP, None),
    ];

    for (name, sdp_str, expected) in tests {
        let mut reader = Cursor::new(sdp_str.as_bytes());
        let sdp = SessionDescription::unmarshal(&mut reader);
        if let Ok(sdp) = sdp {
            let actual = sdp.marshal();
            if let Some(expected) = expected {
                assert_eq!(actual.as_str(), expected, "{name}\n{sdp_str}");
            } else {
                assert_eq!(actual.as_str(), sdp_str, "{name}\n{sdp_str}");
            }
        } else {
            panic!("{name}\n{sdp_str}");
        }
    }

    Ok(())
}

#[test]
fn test_unmarshal_repeat_times() -> Result<()> {
    let mut reader = Cursor::new(REPEAT_TIMES_SDP.as_bytes());
    let sdp = SessionDescription::unmarshal(&mut reader)?;
    let actual = sdp.marshal();
    assert_eq!(actual.as_str(), REPEAT_TIMES_SDPEXPECTED);
    Ok(())
}

#[test]
fn test_unmarshal_repeat_times_overflow() -> Result<()> {
    let mut reader = Cursor::new(REPEAT_TIMES_OVERFLOW_SDP.as_bytes());
    let result = SessionDescription::unmarshal(&mut reader);
    assert!(result.is_err());
    assert_eq!(
        Error::SdpInvalidValue("106751991167301d".to_owned()),
        result.unwrap_err()
    );
    Ok(())
}

#[test]
fn test_unmarshal_time_zones() -> Result<()> {
    let mut reader = Cursor::new(TIME_ZONES_SDP.as_bytes());
    let sdp = SessionDescription::unmarshal(&mut reader)?;
    let actual = sdp.marshal();
    assert_eq!(actual.as_str(), TIME_ZONES_SDPEXPECTED);
    Ok(())
}

#[test]
fn test_unmarshal_non_nil_address() -> Result<()> {
    let input = "v=0\r\no=0 0 0 IN IP4 0\r\ns=0\r\nc=IN IP4\r\nt=0 0\r\n";
    let mut reader = Cursor::new(input);
    let sdp = SessionDescription::unmarshal(&mut reader);
    if let Ok(sdp) = sdp {
        let output = sdp.marshal();
        assert_eq!(output.as_str(), input);
    } else {
        panic!("{}", input);
    }
    Ok(())
}
