Bug 1379265 - Import rsdparsa; r=rillian draft
authorDan Minor <dminor@mozilla.com>
Thu, 11 Jan 2018 10:42:16 -0500
changeset 752258 d96082ff0c0cac81811d91915add960e330cb5b8
parent 747168 59960ae69d7e675cfcfbf0ead6125cc8d3719f1f
child 752259 4197784730e3a87f2aeb0ff25bb74e8a3d336010
child 755680 3819bd6223466316bf1b0448a7582a9fb95f9e77
child 756279 797c024526a3ea11bb9a1e29c673b53d68c14a25
push id98203
push userbmo:dminor@mozilla.com
push dateWed, 07 Feb 2018 19:31:35 +0000
reviewersrillian
bugs1379265
milestone60.0a1
Bug 1379265 - Import rsdparsa; r=rillian Imported from git commit a836501cff59d6a62e7cc067cb31a0682d230e63. MozReview-Commit-ID: 8njzZT5YTTD
media/webrtc/signaling/src/sdp/rsdparsa/.gitignore
media/webrtc/signaling/src/sdp/rsdparsa/.travis.yml
media/webrtc/signaling/src/sdp/rsdparsa/Cargo.toml
media/webrtc/signaling/src/sdp/rsdparsa/LICENSE
media/webrtc/signaling/src/sdp/rsdparsa/README.md
media/webrtc/signaling/src/sdp/rsdparsa/src/attribute_type.rs
media/webrtc/signaling/src/sdp/rsdparsa/src/bin/file_parser.rs
media/webrtc/signaling/src/sdp/rsdparsa/src/bin/sdps/10.sdp
media/webrtc/signaling/src/sdp/rsdparsa/src/bin/sdps/11.sdp
media/webrtc/signaling/src/sdp/rsdparsa/src/bin/sdps/12.sdp
media/webrtc/signaling/src/sdp/rsdparsa/src/bin/sdps/13.sdp
media/webrtc/signaling/src/sdp/rsdparsa/src/bin/sdps/14.sdp
media/webrtc/signaling/src/sdp/rsdparsa/src/bin/sdps/15.sdp
media/webrtc/signaling/src/sdp/rsdparsa/src/bin/sdps/16.sdp
media/webrtc/signaling/src/sdp/rsdparsa/src/bin/sdps/17.sdp
media/webrtc/signaling/src/sdp/rsdparsa/src/bin/sdps/18.sdp
media/webrtc/signaling/src/sdp/rsdparsa/src/bin/sdps/19.sdp
media/webrtc/signaling/src/sdp/rsdparsa/src/bin/sdps/2.sdp
media/webrtc/signaling/src/sdp/rsdparsa/src/bin/sdps/20.sdp
media/webrtc/signaling/src/sdp/rsdparsa/src/bin/sdps/21.sdp
media/webrtc/signaling/src/sdp/rsdparsa/src/bin/sdps/22.sdp
media/webrtc/signaling/src/sdp/rsdparsa/src/bin/sdps/23.sdp
media/webrtc/signaling/src/sdp/rsdparsa/src/bin/sdps/24.sdp
media/webrtc/signaling/src/sdp/rsdparsa/src/bin/sdps/25.sdp
media/webrtc/signaling/src/sdp/rsdparsa/src/bin/sdps/26.sdp
media/webrtc/signaling/src/sdp/rsdparsa/src/bin/sdps/27.sdp
media/webrtc/signaling/src/sdp/rsdparsa/src/bin/sdps/28.sdp
media/webrtc/signaling/src/sdp/rsdparsa/src/bin/sdps/29.sdp
media/webrtc/signaling/src/sdp/rsdparsa/src/bin/sdps/3.sdp
media/webrtc/signaling/src/sdp/rsdparsa/src/bin/sdps/30.sdp
media/webrtc/signaling/src/sdp/rsdparsa/src/bin/sdps/31.sdp
media/webrtc/signaling/src/sdp/rsdparsa/src/bin/sdps/32.sdp
media/webrtc/signaling/src/sdp/rsdparsa/src/bin/sdps/33.sdp
media/webrtc/signaling/src/sdp/rsdparsa/src/bin/sdps/34.sdp
media/webrtc/signaling/src/sdp/rsdparsa/src/bin/sdps/35.sdp
media/webrtc/signaling/src/sdp/rsdparsa/src/bin/sdps/36.sdp
media/webrtc/signaling/src/sdp/rsdparsa/src/bin/sdps/37.sdp
media/webrtc/signaling/src/sdp/rsdparsa/src/bin/sdps/38.sdp
media/webrtc/signaling/src/sdp/rsdparsa/src/bin/sdps/39.sdp
media/webrtc/signaling/src/sdp/rsdparsa/src/bin/sdps/4.sdp
media/webrtc/signaling/src/sdp/rsdparsa/src/bin/sdps/40.sdp
media/webrtc/signaling/src/sdp/rsdparsa/src/bin/sdps/41.sdp
media/webrtc/signaling/src/sdp/rsdparsa/src/bin/sdps/5.sdp
media/webrtc/signaling/src/sdp/rsdparsa/src/bin/sdps/6.sdp
media/webrtc/signaling/src/sdp/rsdparsa/src/bin/sdps/7.sdp
media/webrtc/signaling/src/sdp/rsdparsa/src/bin/sdps/8.sdp
media/webrtc/signaling/src/sdp/rsdparsa/src/bin/sdps/9.sdp
media/webrtc/signaling/src/sdp/rsdparsa/src/bin/sdps/extract.sh
media/webrtc/signaling/src/sdp/rsdparsa/src/error.rs
media/webrtc/signaling/src/sdp/rsdparsa/src/lib.rs
media/webrtc/signaling/src/sdp/rsdparsa/src/media_type.rs
media/webrtc/signaling/src/sdp/rsdparsa/src/network.rs
media/webrtc/signaling/src/sdp/rsdparsa/src/unsupported_types.rs
media/webrtc/signaling/src/sdp/rsdparsa/tests/unit_tests.rs
new file mode 100644
--- /dev/null
+++ b/media/webrtc/signaling/src/sdp/rsdparsa/.gitignore
@@ -0,0 +1,2 @@
+target
+Cargo.lock
new file mode 100644
--- /dev/null
+++ b/media/webrtc/signaling/src/sdp/rsdparsa/.travis.yml
@@ -0,0 +1,68 @@
+language: rust
+cache: cargo
+sudo: true
+os:
+  - linux
+# Taken out temporarily because it's to slow
+#  - osx
+
+rust:
+  - nightly
+  - beta
+  - stable
+  # mimimum stable version because we use init shorthand
+  - 1.17.0
+
+matrix:
+  allow_failures:
+    - rust: nightly
+
+before_install:
+  - sudo apt-get update
+
+addons:
+  apt:
+    packages:
+      - libcurl4-openssl-dev
+      - libelf-dev
+      - libdw-dev
+      - cmake
+      - gcc
+      - binutils-dev
+
+# Add clippy
+before_script:
+  - export PATH=$PATH:~/.cargo/bin
+  - |
+    if [[ "$TRAVIS_RUST_VERSION" == "nightly" ]]; then
+      cargo install --force clippy;
+    fi
+
+script:
+  - cargo build --verbose --all
+  - |
+    if [[ "$TRAVIS_RUST_VERSION" == "nightly" &&
+      -f ~/.cargo/bin/cargo-clippy ]]; then
+      cargo clippy;
+    fi
+  - cargo test --verbose --all
+
+after_success:
+  - |
+      if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_RUST_VERSION" == "stable" ]]; then
+        wget https://github.com/SimonKagstrom/kcov/archive/master.tar.gz &&
+        tar xzf master.tar.gz &&
+        cd kcov-master &&
+        mkdir build &&
+        cd build &&
+        cmake .. &&
+        make &&
+        sudo make install &&
+        cd ../.. &&
+        rm -rf kcov-master &&
+        kcov --version &&
+        for file in target/debug/rsdparsa-*[^\.d]; do echo "$file"; mkdir -p "target/cov/$(basename $file)"; kcov --verify --exclude-pattern=/.cargo,/usr/lib "target/cov/$(basename $file)" "$file"; done &&
+        for file in target/debug/unit_tests-*[^\.d]; do echo "$file"; mkdir -p "target/cov/$(basename $file)"; kcov --verify --exclude-pattern=/.cargo,/usr/lib "target/cov/$(basename $file)" "$file"; done &&
+        bash <(curl -s https://codecov.io/bash) &&
+        echo "Uploaded code coverage"
+      fi
new file mode 100644
--- /dev/null
+++ b/media/webrtc/signaling/src/sdp/rsdparsa/Cargo.toml
@@ -0,0 +1,10 @@
+[package]
+name = "rsdparsa"
+version = "0.1.0"
+authors = ["Nils Ohlmeier <github@ohlmeier.org>"]
+
+[dependencies]
+clippy = {version = "*", optional = true}
+
+[features]
+default = []
new file mode 100644
--- /dev/null
+++ b/media/webrtc/signaling/src/sdp/rsdparsa/LICENSE
@@ -0,0 +1,373 @@
+Mozilla Public License Version 2.0
+==================================
+
+1. Definitions
+--------------
+
+1.1. "Contributor"
+    means each individual or legal entity that creates, contributes to
+    the creation of, or owns Covered Software.
+
+1.2. "Contributor Version"
+    means the combination of the Contributions of others (if any) used
+    by a Contributor and that particular Contributor's Contribution.
+
+1.3. "Contribution"
+    means Covered Software of a particular Contributor.
+
+1.4. "Covered Software"
+    means Source Code Form to which the initial Contributor has attached
+    the notice in Exhibit A, the Executable Form of such Source Code
+    Form, and Modifications of such Source Code Form, in each case
+    including portions thereof.
+
+1.5. "Incompatible With Secondary Licenses"
+    means
+
+    (a) that the initial Contributor has attached the notice described
+        in Exhibit B to the Covered Software; or
+
+    (b) that the Covered Software was made available under the terms of
+        version 1.1 or earlier of the License, but not also under the
+        terms of a Secondary License.
+
+1.6. "Executable Form"
+    means any form of the work other than Source Code Form.
+
+1.7. "Larger Work"
+    means a work that combines Covered Software with other material, in
+    a separate file or files, that is not Covered Software.
+
+1.8. "License"
+    means this document.
+
+1.9. "Licensable"
+    means having the right to grant, to the maximum extent possible,
+    whether at the time of the initial grant or subsequently, any and
+    all of the rights conveyed by this License.
+
+1.10. "Modifications"
+    means any of the following:
+
+    (a) any file in Source Code Form that results from an addition to,
+        deletion from, or modification of the contents of Covered
+        Software; or
+
+    (b) any new file in Source Code Form that contains any Covered
+        Software.
+
+1.11. "Patent Claims" of a Contributor
+    means any patent claim(s), including without limitation, method,
+    process, and apparatus claims, in any patent Licensable by such
+    Contributor that would be infringed, but for the grant of the
+    License, by the making, using, selling, offering for sale, having
+    made, import, or transfer of either its Contributions or its
+    Contributor Version.
+
+1.12. "Secondary License"
+    means either the GNU General Public License, Version 2.0, the GNU
+    Lesser General Public License, Version 2.1, the GNU Affero General
+    Public License, Version 3.0, or any later versions of those
+    licenses.
+
+1.13. "Source Code Form"
+    means the form of the work preferred for making modifications.
+
+1.14. "You" (or "Your")
+    means an individual or a legal entity exercising rights under this
+    License. For legal entities, "You" includes any entity that
+    controls, is controlled by, or is under common control with You. For
+    purposes of this definition, "control" means (a) the power, direct
+    or indirect, to cause the direction or management of such entity,
+    whether by contract or otherwise, or (b) ownership of more than
+    fifty percent (50%) of the outstanding shares or beneficial
+    ownership of such entity.
+
+2. License Grants and Conditions
+--------------------------------
+
+2.1. Grants
+
+Each Contributor hereby grants You a world-wide, royalty-free,
+non-exclusive license:
+
+(a) under intellectual property rights (other than patent or trademark)
+    Licensable by such Contributor to use, reproduce, make available,
+    modify, display, perform, distribute, and otherwise exploit its
+    Contributions, either on an unmodified basis, with Modifications, or
+    as part of a Larger Work; and
+
+(b) under Patent Claims of such Contributor to make, use, sell, offer
+    for sale, have made, import, and otherwise transfer either its
+    Contributions or its Contributor Version.
+
+2.2. Effective Date
+
+The licenses granted in Section 2.1 with respect to any Contribution
+become effective for each Contribution on the date the Contributor first
+distributes such Contribution.
+
+2.3. Limitations on Grant Scope
+
+The licenses granted in this Section 2 are the only rights granted under
+this License. No additional rights or licenses will be implied from the
+distribution or licensing of Covered Software under this License.
+Notwithstanding Section 2.1(b) above, no patent license is granted by a
+Contributor:
+
+(a) for any code that a Contributor has removed from Covered Software;
+    or
+
+(b) for infringements caused by: (i) Your and any other third party's
+    modifications of Covered Software, or (ii) the combination of its
+    Contributions with other software (except as part of its Contributor
+    Version); or
+
+(c) under Patent Claims infringed by Covered Software in the absence of
+    its Contributions.
+
+This License does not grant any rights in the trademarks, service marks,
+or logos of any Contributor (except as may be necessary to comply with
+the notice requirements in Section 3.4).
+
+2.4. Subsequent Licenses
+
+No Contributor makes additional grants as a result of Your choice to
+distribute the Covered Software under a subsequent version of this
+License (see Section 10.2) or under the terms of a Secondary License (if
+permitted under the terms of Section 3.3).
+
+2.5. Representation
+
+Each Contributor represents that the Contributor believes its
+Contributions are its original creation(s) or it has sufficient rights
+to grant the rights to its Contributions conveyed by this License.
+
+2.6. Fair Use
+
+This License is not intended to limit any rights You have under
+applicable copyright doctrines of fair use, fair dealing, or other
+equivalents.
+
+2.7. Conditions
+
+Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
+in Section 2.1.
+
+3. Responsibilities
+-------------------
+
+3.1. Distribution of Source Form
+
+All distribution of Covered Software in Source Code Form, including any
+Modifications that You create or to which You contribute, must be under
+the terms of this License. You must inform recipients that the Source
+Code Form of the Covered Software is governed by the terms of this
+License, and how they can obtain a copy of this License. You may not
+attempt to alter or restrict the recipients' rights in the Source Code
+Form.
+
+3.2. Distribution of Executable Form
+
+If You distribute Covered Software in Executable Form then:
+
+(a) such Covered Software must also be made available in Source Code
+    Form, as described in Section 3.1, and You must inform recipients of
+    the Executable Form how they can obtain a copy of such Source Code
+    Form by reasonable means in a timely manner, at a charge no more
+    than the cost of distribution to the recipient; and
+
+(b) You may distribute such Executable Form under the terms of this
+    License, or sublicense it under different terms, provided that the
+    license for the Executable Form does not attempt to limit or alter
+    the recipients' rights in the Source Code Form under this License.
+
+3.3. Distribution of a Larger Work
+
+You may create and distribute a Larger Work under terms of Your choice,
+provided that You also comply with the requirements of this License for
+the Covered Software. If the Larger Work is a combination of Covered
+Software with a work governed by one or more Secondary Licenses, and the
+Covered Software is not Incompatible With Secondary Licenses, this
+License permits You to additionally distribute such Covered Software
+under the terms of such Secondary License(s), so that the recipient of
+the Larger Work may, at their option, further distribute the Covered
+Software under the terms of either this License or such Secondary
+License(s).
+
+3.4. Notices
+
+You may not remove or alter the substance of any license notices
+(including copyright notices, patent notices, disclaimers of warranty,
+or limitations of liability) contained within the Source Code Form of
+the Covered Software, except that You may alter any license notices to
+the extent required to remedy known factual inaccuracies.
+
+3.5. Application of Additional Terms
+
+You may choose to offer, and to charge a fee for, warranty, support,
+indemnity or liability obligations to one or more recipients of Covered
+Software. However, You may do so only on Your own behalf, and not on
+behalf of any Contributor. You must make it absolutely clear that any
+such warranty, support, indemnity, or liability obligation is offered by
+You alone, and You hereby agree to indemnify every Contributor for any
+liability incurred by such Contributor as a result of warranty, support,
+indemnity or liability terms You offer. You may include additional
+disclaimers of warranty and limitations of liability specific to any
+jurisdiction.
+
+4. Inability to Comply Due to Statute or Regulation
+---------------------------------------------------
+
+If it is impossible for You to comply with any of the terms of this
+License with respect to some or all of the Covered Software due to
+statute, judicial order, or regulation then You must: (a) comply with
+the terms of this License to the maximum extent possible; and (b)
+describe the limitations and the code they affect. Such description must
+be placed in a text file included with all distributions of the Covered
+Software under this License. Except to the extent prohibited by statute
+or regulation, such description must be sufficiently detailed for a
+recipient of ordinary skill to be able to understand it.
+
+5. Termination
+--------------
+
+5.1. The rights granted under this License will terminate automatically
+if You fail to comply with any of its terms. However, if You become
+compliant, then the rights granted under this License from a particular
+Contributor are reinstated (a) provisionally, unless and until such
+Contributor explicitly and finally terminates Your grants, and (b) on an
+ongoing basis, if such Contributor fails to notify You of the
+non-compliance by some reasonable means prior to 60 days after You have
+come back into compliance. Moreover, Your grants from a particular
+Contributor are reinstated on an ongoing basis if such Contributor
+notifies You of the non-compliance by some reasonable means, this is the
+first time You have received notice of non-compliance with this License
+from such Contributor, and You become compliant prior to 30 days after
+Your receipt of the notice.
+
+5.2. If You initiate litigation against any entity by asserting a patent
+infringement claim (excluding declaratory judgment actions,
+counter-claims, and cross-claims) alleging that a Contributor Version
+directly or indirectly infringes any patent, then the rights granted to
+You by any and all Contributors for the Covered Software under Section
+2.1 of this License shall terminate.
+
+5.3. In the event of termination under Sections 5.1 or 5.2 above, all
+end user license agreements (excluding distributors and resellers) which
+have been validly granted by You or Your distributors under this License
+prior to termination shall survive termination.
+
+************************************************************************
+*                                                                      *
+*  6. Disclaimer of Warranty                                           *
+*  -------------------------                                           *
+*                                                                      *
+*  Covered Software is provided under this License on an "as is"       *
+*  basis, without warranty of any kind, either expressed, implied, or  *
+*  statutory, including, without limitation, warranties that the       *
+*  Covered Software is free of defects, merchantable, fit for a        *
+*  particular purpose or non-infringing. The entire risk as to the     *
+*  quality and performance of the Covered Software is with You.        *
+*  Should any Covered Software prove defective in any respect, You     *
+*  (not any Contributor) assume the cost of any necessary servicing,   *
+*  repair, or correction. This disclaimer of warranty constitutes an   *
+*  essential part of this License. No use of any Covered Software is   *
+*  authorized under this License except under this disclaimer.         *
+*                                                                      *
+************************************************************************
+
+************************************************************************
+*                                                                      *
+*  7. Limitation of Liability                                          *
+*  --------------------------                                          *
+*                                                                      *
+*  Under no circumstances and under no legal theory, whether tort      *
+*  (including negligence), contract, or otherwise, shall any           *
+*  Contributor, or anyone who distributes Covered Software as          *
+*  permitted above, be liable to You for any direct, indirect,         *
+*  special, incidental, or consequential damages of any character      *
+*  including, without limitation, damages for lost profits, loss of    *
+*  goodwill, work stoppage, computer failure or malfunction, or any    *
+*  and all other commercial damages or losses, even if such party      *
+*  shall have been informed of the possibility of such damages. This   *
+*  limitation of liability shall not apply to liability for death or   *
+*  personal injury resulting from such party's negligence to the       *
+*  extent applicable law prohibits such limitation. Some               *
+*  jurisdictions do not allow the exclusion or limitation of           *
+*  incidental or consequential damages, so this exclusion and          *
+*  limitation may not apply to You.                                    *
+*                                                                      *
+************************************************************************
+
+8. Litigation
+-------------
+
+Any litigation relating to this License may be brought only in the
+courts of a jurisdiction where the defendant maintains its principal
+place of business and such litigation shall be governed by laws of that
+jurisdiction, without reference to its conflict-of-law provisions.
+Nothing in this Section shall prevent a party's ability to bring
+cross-claims or counter-claims.
+
+9. Miscellaneous
+----------------
+
+This License represents the complete agreement concerning the subject
+matter hereof. If any provision of this License is held to be
+unenforceable, such provision shall be reformed only to the extent
+necessary to make it enforceable. Any law or regulation which provides
+that the language of a contract shall be construed against the drafter
+shall not be used to construe this License against a Contributor.
+
+10. Versions of the License
+---------------------------
+
+10.1. New Versions
+
+Mozilla Foundation is the license steward. Except as provided in Section
+10.3, no one other than the license steward has the right to modify or
+publish new versions of this License. Each version will be given a
+distinguishing version number.
+
+10.2. Effect of New Versions
+
+You may distribute the Covered Software under the terms of the version
+of the License under which You originally received the Covered Software,
+or under the terms of any subsequent version published by the license
+steward.
+
+10.3. Modified Versions
+
+If you create software not governed by this License, and you want to
+create a new license for such software, you may create and use a
+modified version of this License if you rename the license and remove
+any references to the name of the license steward (except to note that
+such modified license differs from this License).
+
+10.4. Distributing Source Code Form that is Incompatible With Secondary
+Licenses
+
+If You choose to distribute Source Code Form that is Incompatible With
+Secondary Licenses under the terms of this version of the License, the
+notice described in Exhibit B of this License must be attached.
+
+Exhibit A - Source Code Form License Notice
+-------------------------------------------
+
+  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/.
+
+If it is not possible or desirable to put the notice in a particular
+file, then You may include the notice in a location (such as a LICENSE
+file in a relevant directory) where a recipient would be likely to look
+for such a notice.
+
+You may add additional accurate notices of copyright ownership.
+
+Exhibit B - "Incompatible With Secondary Licenses" Notice
+---------------------------------------------------------
+
+  This Source Code Form is "Incompatible With Secondary Licenses", as
+  defined by the Mozilla Public License, v. 2.0.
new file mode 100644
--- /dev/null
+++ b/media/webrtc/signaling/src/sdp/rsdparsa/README.md
@@ -0,0 +1,13 @@
+# rsdparsa
+
+[![Build Status](https://travis-ci.org/nils-ohlmeier/rsdparsa.svg?branch=master)](https://travis-ci.org/nils-ohlmeier/rsdparsa)
+[![Codecov coverage status](https://codecov.io/gh/nils-ohlmeier/rsdparsa/branch/master/graph/badge.svg)](https://codecov.io/gh/nils-ohlmeier/rsdparsa)
+[![License: MPL 2.0](https://img.shields.io/badge/License-MPL%202.0-brightgreen.svg)](#License)
+
+A SDP parser written in Rust specifically aimed for WebRTC
+
+Requires minimum Rust 1.17
+
+## License
+
+Licensed under [MPL](https://www.mozilla.org/MPL/2.0/).
new file mode 100644
--- /dev/null
+++ b/media/webrtc/signaling/src/sdp/rsdparsa/src/attribute_type.rs
@@ -0,0 +1,1465 @@
+use std::net::IpAddr;
+use std::str::FromStr;
+use std::fmt;
+
+use SdpType;
+use error::SdpParserInternalError;
+use network::{parse_nettype, parse_addrtype, parse_unicast_addr};
+
+#[derive(Clone)]
+pub enum SdpAttributeCandidateTransport {
+    Udp,
+    Tcp,
+}
+
+#[derive(Clone)]
+pub enum SdpAttributeCandidateType {
+    Host,
+    Srflx,
+    Prflx,
+    Relay,
+}
+
+#[derive(Clone)]
+pub enum SdpAttributeCandidateTcpType {
+    Active,
+    Passive,
+    Simultaneous,
+}
+
+#[derive(Clone)]
+pub struct SdpAttributeCandidate {
+    pub foundation: String,
+    pub component: u32,
+    pub transport: SdpAttributeCandidateTransport,
+    pub priority: u64,
+    pub address: IpAddr,
+    pub port: u32,
+    pub c_type: SdpAttributeCandidateType,
+    pub raddr: Option<IpAddr>,
+    pub rport: Option<u32>,
+    pub tcp_type: Option<SdpAttributeCandidateTcpType>,
+    pub generation: Option<u32>,
+    pub ufrag: Option<String>,
+    pub networkcost: Option<u32>,
+}
+
+impl SdpAttributeCandidate {
+    pub fn new(foundation: String,
+               component: u32,
+               transport: SdpAttributeCandidateTransport,
+               priority: u64,
+               address: IpAddr,
+               port: u32,
+               c_type: SdpAttributeCandidateType)
+               -> SdpAttributeCandidate {
+        SdpAttributeCandidate {
+            foundation,
+            component,
+            transport,
+            priority,
+            address,
+            port,
+            c_type,
+            raddr: None,
+            rport: None,
+            tcp_type: None,
+            generation: None,
+            ufrag: None,
+            networkcost: None,
+        }
+    }
+
+    fn set_remote_address(&mut self, ip: IpAddr) {
+        self.raddr = Some(ip)
+    }
+
+    fn set_remote_port(&mut self, p: u32) {
+        self.rport = Some(p)
+    }
+
+    fn set_tcp_type(&mut self, t: SdpAttributeCandidateTcpType) {
+        self.tcp_type = Some(t)
+    }
+
+    fn set_generation(&mut self, g: u32) {
+        self.generation = Some(g)
+    }
+
+    fn set_ufrag(&mut self, u: String) {
+        self.ufrag = Some(u)
+    }
+
+    fn set_network_cost(&mut self, n: u32) {
+        self.networkcost = Some(n)
+    }
+}
+
+#[derive(Clone)]
+pub struct SdpAttributeRemoteCandidate {
+    pub component: u32,
+    pub address: IpAddr,
+    pub port: u32,
+}
+
+#[derive(Clone)]
+pub struct SdpAttributeSimulcastId {
+    pub id: String,
+    pub paused: bool,
+}
+
+impl SdpAttributeSimulcastId {
+    pub fn new(idstr: String) -> SdpAttributeSimulcastId {
+        if idstr.starts_with('~') {
+            SdpAttributeSimulcastId {
+                id: idstr[1..].to_string(),
+                paused: true,
+            }
+        } else {
+            SdpAttributeSimulcastId {
+                id: idstr,
+                paused: false,
+            }
+        }
+    }
+}
+
+#[derive(Clone)]
+pub struct SdpAttributeSimulcastAlternatives {
+    pub ids: Vec<SdpAttributeSimulcastId>,
+}
+
+impl SdpAttributeSimulcastAlternatives {
+    pub fn new(idlist: String) -> SdpAttributeSimulcastAlternatives {
+        SdpAttributeSimulcastAlternatives {
+            ids: idlist
+                .split(',')
+                .map(|x| x.to_string())
+                .map(SdpAttributeSimulcastId::new)
+                .collect(),
+        }
+    }
+}
+
+#[derive(Clone)]
+pub struct SdpAttributeSimulcast {
+    pub send: Vec<SdpAttributeSimulcastAlternatives>,
+    pub receive: Vec<SdpAttributeSimulcastAlternatives>,
+}
+
+impl SdpAttributeSimulcast {
+    fn parse_ids(&mut self, direction: SdpAttributeDirection, idlist: String) {
+        let list = idlist
+            .split(';')
+            .map(|x| x.to_string())
+            .map(SdpAttributeSimulcastAlternatives::new)
+            .collect();
+        // TODO prevent over-writing existing values
+        match direction {
+            SdpAttributeDirection::Recvonly => self.receive = list,
+            SdpAttributeDirection::Sendonly => self.send = list,
+            _ => (),
+        }
+    }
+}
+
+#[derive(Clone)]
+pub struct SdpAttributeRtcp {
+    pub port: u32,
+    pub unicast_addr: Option<IpAddr>,
+}
+
+impl SdpAttributeRtcp {
+    pub fn new(port: u32) -> SdpAttributeRtcp {
+        SdpAttributeRtcp {
+            port,
+            unicast_addr: None,
+        }
+    }
+
+    fn set_addr(&mut self, addr: IpAddr) {
+        self.unicast_addr = Some(addr)
+    }
+}
+
+#[derive(Clone)]
+pub struct SdpAttributeRtcpFb {
+    pub payload_type: u32,
+    // TODO parse this and use an enum instead?
+    pub feedback_type: String,
+}
+
+#[derive(Clone)]
+pub enum SdpAttributeDirection {
+    Recvonly,
+    Sendonly,
+    Sendrecv,
+}
+
+#[derive(Clone)]
+pub struct SdpAttributeExtmap {
+    pub id: u32,
+    pub direction: Option<SdpAttributeDirection>,
+    pub url: String,
+    pub extension_attributes: Option<String>,
+}
+
+#[derive(Clone)]
+pub struct SdpAttributeFmtp {
+    pub payload_type: u32,
+    pub tokens: Vec<String>,
+}
+
+#[derive(Clone)]
+pub struct SdpAttributeFingerprint {
+    // TODO turn the supported hash algorithms into an enum?
+    pub hash_algorithm: String,
+    pub fingerprint: String,
+}
+
+#[derive(Clone)]
+pub struct SdpAttributeSctpmap {
+    pub port: u32,
+    pub channels: u32,
+}
+
+#[derive(Clone)]
+pub enum SdpAttributeGroupSemantic {
+    LipSynchronization,
+    FlowIdentification,
+    SingleReservationFlow,
+    AlternateNetworkAddressType,
+    ForwardErrorCorrection,
+    DecodingDependency,
+    Bundle,
+}
+
+#[derive(Clone)]
+pub struct SdpAttributeGroup {
+    pub semantics: SdpAttributeGroupSemantic,
+    pub tags: Vec<String>,
+}
+
+#[derive(Clone)]
+pub struct SdpAttributeMsid {
+    pub id: String,
+    pub appdata: Option<String>,
+}
+
+#[derive(Clone, Debug)]
+pub struct SdpAttributeMsidSemantic {
+    pub semantic: String,
+    pub msids: Vec<String>,
+}
+
+#[derive(Clone)]
+pub struct SdpAttributeRtpmap {
+    pub payload_type: u32,
+    pub codec_name: String,
+    pub frequency: u32,
+    pub channels: Option<u32>,
+}
+
+impl SdpAttributeRtpmap {
+    pub fn new(payload_type: u32, codec_name: String, frequency: u32) -> SdpAttributeRtpmap {
+        SdpAttributeRtpmap {
+            payload_type,
+            codec_name,
+            frequency,
+            channels: None,
+        }
+    }
+
+    fn set_channels(&mut self, c: u32) {
+        self.channels = Some(c)
+    }
+}
+
+#[derive(Clone)]
+pub enum SdpAttributeSetup {
+    Active,
+    Actpass,
+    Holdconn,
+    Passive,
+}
+
+#[derive(Clone)]
+pub struct SdpAttributeSsrc {
+    pub id: u32,
+    pub attribute: Option<String>,
+    pub value: Option<String>,
+}
+
+impl SdpAttributeSsrc {
+    pub fn new(id: u32) -> SdpAttributeSsrc {
+        SdpAttributeSsrc {
+            id,
+            attribute: None,
+            value: None,
+        }
+    }
+
+    fn set_attribute(&mut self, a: &str) {
+        if a.find(':') == None {
+            self.attribute = Some(a.to_string());
+        } else {
+            let v: Vec<&str> = a.splitn(2, ':').collect();
+            self.attribute = Some(v[0].to_string());
+            self.value = Some(v[1].to_string());
+        }
+    }
+}
+
+#[derive(Clone)]
+pub enum SdpAttribute {
+    BundleOnly,
+    Candidate(SdpAttributeCandidate),
+    EndOfCandidates,
+    Extmap(SdpAttributeExtmap),
+    Fingerprint(SdpAttributeFingerprint),
+    Fmtp(SdpAttributeFmtp),
+    Group(SdpAttributeGroup),
+    IceLite,
+    IceMismatch,
+    IceOptions(Vec<String>),
+    IcePwd(String),
+    IceUfrag(String),
+    Identity(String),
+    ImageAttr(String),
+    Inactive,
+    Label(String),
+    MaxMessageSize(u64),
+    MaxPtime(u64),
+    Mid(String),
+    Msid(SdpAttributeMsid),
+    MsidSemantic(SdpAttributeMsidSemantic),
+    Ptime(u64),
+    Rid(String),
+    Recvonly,
+    RemoteCandidate(SdpAttributeRemoteCandidate),
+    Rtpmap(SdpAttributeRtpmap),
+    Rtcp(SdpAttributeRtcp),
+    Rtcpfb(SdpAttributeRtcpFb),
+    RtcpMux,
+    RtcpRsize,
+    Sctpmap(SdpAttributeSctpmap),
+    SctpPort(u64),
+    Sendonly,
+    Sendrecv,
+    Setup(SdpAttributeSetup),
+    Simulcast(SdpAttributeSimulcast),
+    Ssrc(SdpAttributeSsrc),
+    SsrcGroup(String),
+}
+
+impl SdpAttribute {
+    pub fn allowed_at_session_level(&self) -> bool {
+        match *self {
+            SdpAttribute::BundleOnly |
+            SdpAttribute::Candidate(..) |
+            SdpAttribute::Fmtp(..) |
+            SdpAttribute::IceMismatch |
+            SdpAttribute::ImageAttr(..) |
+            SdpAttribute::Label(..) |
+            SdpAttribute::MaxMessageSize(..) |
+            SdpAttribute::MaxPtime(..) |
+            SdpAttribute::Mid(..) |
+            SdpAttribute::Msid(..) |
+            SdpAttribute::Ptime(..) |
+            SdpAttribute::Rid(..) |
+            SdpAttribute::RemoteCandidate(..) |
+            SdpAttribute::Rtpmap(..) |
+            SdpAttribute::Rtcp(..) |
+            SdpAttribute::Rtcpfb(..) |
+            SdpAttribute::RtcpMux |
+            SdpAttribute::RtcpRsize |
+            SdpAttribute::Sctpmap(..) |
+            SdpAttribute::SctpPort(..) |
+            SdpAttribute::Simulcast(..) |
+            SdpAttribute::Ssrc(..) |
+            SdpAttribute::SsrcGroup(..) => false,
+
+            SdpAttribute::EndOfCandidates |
+            SdpAttribute::Extmap(..) |
+            SdpAttribute::Fingerprint(..) |
+            SdpAttribute::Group(..) |
+            SdpAttribute::IceLite |
+            SdpAttribute::IceOptions(..) |
+            SdpAttribute::IcePwd(..) |
+            SdpAttribute::IceUfrag(..) |
+            SdpAttribute::Identity(..) |
+            SdpAttribute::Inactive |
+            SdpAttribute::MsidSemantic(..) |
+            SdpAttribute::Recvonly |
+            SdpAttribute::Sendonly |
+            SdpAttribute::Sendrecv |
+            SdpAttribute::Setup(..) => true,
+        }
+    }
+
+    pub fn allowed_at_media_level(&self) -> bool {
+        match *self {
+            SdpAttribute::Group(..) |
+            SdpAttribute::IceLite |
+            SdpAttribute::Identity(..) |
+            SdpAttribute::MsidSemantic(..) => false,
+
+            SdpAttribute::BundleOnly |
+            SdpAttribute::Candidate(..) |
+            SdpAttribute::EndOfCandidates |
+            SdpAttribute::Extmap(..) |
+            SdpAttribute::Fingerprint(..) |
+            SdpAttribute::Fmtp(..) |
+            SdpAttribute::IceMismatch |
+            SdpAttribute::IceOptions(..) |
+            SdpAttribute::IcePwd(..) |
+            SdpAttribute::IceUfrag(..) |
+            SdpAttribute::ImageAttr(..) |
+            SdpAttribute::Inactive |
+            SdpAttribute::Label(..) |
+            SdpAttribute::MaxMessageSize(..) |
+            SdpAttribute::MaxPtime(..) |
+            SdpAttribute::Mid(..) |
+            SdpAttribute::Msid(..) |
+            SdpAttribute::Ptime(..) |
+            SdpAttribute::Rid(..) |
+            SdpAttribute::Recvonly |
+            SdpAttribute::RemoteCandidate(..) |
+            SdpAttribute::Rtpmap(..) |
+            SdpAttribute::Rtcp(..) |
+            SdpAttribute::Rtcpfb(..) |
+            SdpAttribute::RtcpMux |
+            SdpAttribute::RtcpRsize |
+            SdpAttribute::Sctpmap(..) |
+            SdpAttribute::SctpPort(..) |
+            SdpAttribute::Sendonly |
+            SdpAttribute::Sendrecv |
+            SdpAttribute::Setup(..) |
+            SdpAttribute::Simulcast(..) |
+            SdpAttribute::Ssrc(..) |
+            SdpAttribute::SsrcGroup(..) => true,
+        }
+    }
+}
+
+impl fmt::Display for SdpAttribute {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        let printable = match *self {
+            SdpAttribute::BundleOnly => "bundle-only",
+            SdpAttribute::Candidate(..) => "candidate",
+            SdpAttribute::EndOfCandidates => "end-of-candidates",
+            SdpAttribute::Extmap(..) => "extmap",
+            SdpAttribute::Fingerprint(..) => "fingerprint",
+            SdpAttribute::Fmtp(..) => "fmtp",
+            SdpAttribute::Group(..) => "group",
+            SdpAttribute::IceLite => "ice-lite",
+            SdpAttribute::IceMismatch => "ice-mismatch",
+            SdpAttribute::IceOptions(..) => "ice-options",
+            SdpAttribute::IcePwd(..) => "ice-pwd",
+            SdpAttribute::IceUfrag(..) => "ice-ufrag",
+            SdpAttribute::Identity(..) => "identity",
+            SdpAttribute::ImageAttr(..) => "imageattr",
+            SdpAttribute::Inactive => "inactive",
+            SdpAttribute::Label(..) => "label",
+            SdpAttribute::MaxMessageSize(..) => "max-message-size",
+            SdpAttribute::MaxPtime(..) => "max-ptime",
+            SdpAttribute::Mid(..) => "mid",
+            SdpAttribute::Msid(..) => "msid",
+            SdpAttribute::MsidSemantic(..) => "msid-semantic",
+            SdpAttribute::Ptime(..) => "ptime",
+            SdpAttribute::Rid(..) => "rid",
+            SdpAttribute::Recvonly => "recvonly",
+            SdpAttribute::RemoteCandidate(..) => "remote-candidate",
+            SdpAttribute::Rtpmap(..) => "rtpmap",
+            SdpAttribute::Rtcp(..) => "rtcp",
+            SdpAttribute::Rtcpfb(..) => "rtcp-fb",
+            SdpAttribute::RtcpMux => "rtcp-mux",
+            SdpAttribute::RtcpRsize => "rtcp-rsize",
+            SdpAttribute::Sctpmap(..) => "sctpmap",
+            SdpAttribute::SctpPort(..) => "sctp-port",
+            SdpAttribute::Sendonly => "sendonly",
+            SdpAttribute::Sendrecv => "sendrecv",
+            SdpAttribute::Setup(..) => "setup",
+            SdpAttribute::Simulcast(..) => "simulcast",
+            SdpAttribute::Ssrc(..) => "ssrc",
+            SdpAttribute::SsrcGroup(..) => "ssrc-group",
+        };
+        write!(f, "attribute: {}", printable)
+    }
+}
+impl FromStr for SdpAttribute {
+    type Err = SdpParserInternalError;
+
+    fn from_str(line: &str) -> Result<Self, Self::Err> {
+        let tokens: Vec<_> = line.splitn(2, ':').collect();
+        let name = tokens[0].to_lowercase();
+        let val = match tokens.get(1) {
+            Some(x) => x.trim(),
+            None => "",
+        };
+        if tokens.len() > 1 {
+            match name.as_str() {
+                "bundle-only" |
+                "end-of-candidates" |
+                "ice-lite" |
+                "ice-mismatch" |
+                "inactive" |
+                "recvonly" |
+                "rtcp-mux" |
+                "rtcp-rsize" |
+                "sendonly" |
+                "sendrecv" => {
+                    return Err(SdpParserInternalError::Generic(format!("{} attribute is not allowed to have a value",
+                                                                       name)));
+                }
+                _ => (),
+            }
+        }
+        match name.as_str() {
+            "bundle-only" => Ok(SdpAttribute::BundleOnly),
+            "end-of-candidates" => Ok(SdpAttribute::EndOfCandidates),
+            "ice-lite" => Ok(SdpAttribute::IceLite),
+            "ice-mismatch" => Ok(SdpAttribute::IceMismatch),
+            "ice-pwd" => Ok(SdpAttribute::IcePwd(string_or_empty(val)?)),
+            "ice-ufrag" => Ok(SdpAttribute::IceUfrag(string_or_empty(val)?)),
+            "identity" => Ok(SdpAttribute::Identity(string_or_empty(val)?)),
+            "imageattr" => Ok(SdpAttribute::ImageAttr(string_or_empty(val)?)),
+            "inactive" => Ok(SdpAttribute::Inactive),
+            "label" => Ok(SdpAttribute::Label(string_or_empty(val)?)),
+            "max-message-size" => Ok(SdpAttribute::MaxMessageSize(val.parse()?)),
+            "maxptime" => Ok(SdpAttribute::MaxPtime(val.parse()?)),
+            "mid" => Ok(SdpAttribute::Mid(string_or_empty(val)?)),
+            "msid-semantic" => parse_msid_semantic(val),
+            "ptime" => Ok(SdpAttribute::Ptime(val.parse()?)),
+            "rid" => Ok(SdpAttribute::Rid(string_or_empty(val)?)),
+            "recvonly" => Ok(SdpAttribute::Recvonly),
+            "rtcp-mux" => Ok(SdpAttribute::RtcpMux),
+            "rtcp-rsize" => Ok(SdpAttribute::RtcpRsize),
+            "sendonly" => Ok(SdpAttribute::Sendonly),
+            "sendrecv" => Ok(SdpAttribute::Sendrecv),
+            "ssrc-group" => Ok(SdpAttribute::SsrcGroup(string_or_empty(val)?)),
+            "sctp-port" => parse_sctp_port(val),
+            "candidate" => parse_candidate(val),
+            "extmap" => parse_extmap(val),
+            "fingerprint" => parse_fingerprint(val),
+            "fmtp" => parse_fmtp(val),
+            "group" => parse_group(val),
+            "ice-options" => parse_ice_options(val),
+            "msid" => parse_msid(val),
+            "remote-candidates" => parse_remote_candidates(val),
+            "rtpmap" => parse_rtpmap(val),
+            "rtcp" => parse_rtcp(val),
+            "rtcp-fb" => parse_rtcp_fb(val),
+            "sctpmap" => parse_sctpmap(val),
+            "setup" => parse_setup(val),
+            "simulcast" => parse_simulcast(val),
+            "ssrc" => parse_ssrc(val),
+            _ => {
+                Err(SdpParserInternalError::Unsupported(format!("Unknown attribute type {}", name)))
+            }
+        }
+    }
+}
+
+fn string_or_empty(to_parse: &str) -> Result<String, SdpParserInternalError> {
+    if to_parse.is_empty() {
+        Err(SdpParserInternalError::Generic("This attribute is required to have a value"
+                                                .to_string()))
+    } else {
+        Ok(to_parse.to_string())
+    }
+}
+
+fn parse_sctp_port(to_parse: &str) -> Result<SdpAttribute, SdpParserInternalError> {
+    let port = to_parse.parse()?;
+    if port > 65535 {
+        return Err(SdpParserInternalError::Generic(format!("Sctpport port {} can only be a bit 16bit number",
+                                                           port)));
+    }
+    Ok(SdpAttribute::SctpPort(port))
+}
+
+fn parse_candidate(to_parse: &str) -> Result<SdpAttribute, SdpParserInternalError> {
+    let tokens: Vec<&str> = to_parse.split_whitespace().collect();
+    if tokens.len() < 8 {
+        return Err(SdpParserInternalError::Generic("Candidate needs to have minimum eigth tokens"
+                                                       .to_string()));
+    }
+    let component = tokens[1].parse::<u32>()?;
+    let transport = match tokens[2].to_lowercase().as_ref() {
+        "udp" => SdpAttributeCandidateTransport::Udp,
+        "tcp" => SdpAttributeCandidateTransport::Tcp,
+        _ => {
+            return Err(SdpParserInternalError::Generic("Unknonw candidate transport value"
+                                                           .to_string()))
+        }
+    };
+    let priority = tokens[3].parse::<u64>()?;
+    let address = parse_unicast_addr(tokens[4])?;
+    let port = tokens[5].parse::<u32>()?;
+    if port > 65535 {
+        return Err(SdpParserInternalError::Generic("ICE candidate port can only be a bit 16bit number".to_string()));
+    }
+    match tokens[6].to_lowercase().as_ref() {
+        "typ" => (),
+        _ => {
+            return Err(SdpParserInternalError::Generic("Candidate attribute token must be 'typ'"
+                                                           .to_string()))
+        }
+    };
+    let cand_type = match tokens[7].to_lowercase().as_ref() {
+        "host" => SdpAttributeCandidateType::Host,
+        "srflx" => SdpAttributeCandidateType::Srflx,
+        "prflx" => SdpAttributeCandidateType::Prflx,
+        "relay" => SdpAttributeCandidateType::Relay,
+        _ => return Err(SdpParserInternalError::Generic("Unknow candidate type value".to_string())),
+    };
+    let mut cand = SdpAttributeCandidate::new(tokens[0].to_string(),
+                                              component,
+                                              transport,
+                                              priority,
+                                              address,
+                                              port,
+                                              cand_type);
+    if tokens.len() > 8 {
+        let mut index = 8;
+        while tokens.len() > index + 1 {
+            match tokens[index].to_lowercase().as_ref() {
+                "generation" => {
+                    let generation = tokens[index + 1].parse::<u32>()?;
+                    cand.set_generation(generation);
+                    index += 2;
+                }
+                "network-cost" => {
+                    let cost = tokens[index + 1].parse::<u32>()?;
+                    cand.set_network_cost(cost);
+                    index += 2;
+                }
+                "raddr" => {
+                    let addr = parse_unicast_addr(tokens[index + 1])?;
+                    cand.set_remote_address(addr);
+                    index += 2;
+                }
+                "rport" => {
+                    let port = tokens[index + 1].parse::<u32>()?;
+                    if port > 65535 {
+                        return Err(SdpParserInternalError::Generic( "ICE candidate rport can only be a bit 16bit number".to_string()));
+                    }
+                    cand.set_remote_port(port);
+                    index += 2;
+                }
+                "tcptype" => {
+                    cand.set_tcp_type(match tokens[index + 1].to_lowercase().as_ref() {
+                                          "active" => SdpAttributeCandidateTcpType::Active,
+                                          "passive" => SdpAttributeCandidateTcpType::Passive,
+                                          "so" => SdpAttributeCandidateTcpType::Simultaneous,
+                                          _ => {
+                            return Err(SdpParserInternalError::Generic("Unknown tcptype value in candidate line".to_string()))
+                        }
+                                      });
+                    index += 2;
+                }
+                "ufrag" => {
+                    let ufrag = tokens[index + 1];
+                    cand.set_ufrag(ufrag.to_string());
+                    index += 2;
+                }
+                _ => {
+                    return Err(SdpParserInternalError::Unsupported("Uknown candidate extension name"
+                                                                       .to_string()))
+                }
+            };
+        }
+    }
+    Ok(SdpAttribute::Candidate(cand))
+}
+
+// ABNF for extmap is defined in RFC 5285
+// https://tools.ietf.org/html/rfc5285#section-7
+fn parse_extmap(to_parse: &str) -> Result<SdpAttribute, SdpParserInternalError> {
+    let tokens: Vec<&str> = to_parse.split_whitespace().collect();
+    if tokens.len() < 2 {
+        return Err(SdpParserInternalError::Generic("Extmap needs to have at least two tokens"
+                                                       .to_string()));
+    }
+    let id: u32;
+    let mut direction: Option<SdpAttributeDirection> = None;
+    if tokens[0].find('/') == None {
+        id = tokens[0].parse::<u32>()?;
+    } else {
+        let id_dir: Vec<&str> = tokens[0].splitn(2, '/').collect();
+        id = id_dir[0].parse::<u32>()?;
+        direction = Some(match id_dir[1].to_lowercase().as_ref() {
+                             "recvonly" => SdpAttributeDirection::Recvonly,
+                             "sendonly" => SdpAttributeDirection::Sendonly,
+                             "sendrecv" => SdpAttributeDirection::Sendrecv,
+                             _ => {
+                                 return Err(SdpParserInternalError::Generic("Unsupported direction in extmap value".to_string()))
+                             }
+                         })
+    }
+    // Consider replacing to_parse.split_whitespace() above with splitn on space. Would we want the pattern to split on any amout of any kind of whitespace?
+    let extension_attributes = if tokens.len() == 2 {
+        None
+    } else {
+        let ext_string: String = tokens[2..].join(" ");
+        if !valid_byte_string(&ext_string) {
+            return Err(SdpParserInternalError::Generic("Illegal character in extmap extension attributes".to_string()));
+        }
+        Some(ext_string)
+    };
+    Ok(SdpAttribute::Extmap(SdpAttributeExtmap {
+                                id,
+                                direction,
+                                url: tokens[1].to_string(),
+                                extension_attributes: extension_attributes,
+                            }))
+}
+
+fn parse_fingerprint(to_parse: &str) -> Result<SdpAttribute, SdpParserInternalError> {
+    let tokens: Vec<&str> = to_parse.split_whitespace().collect();
+    if tokens.len() != 2 {
+        return Err(SdpParserInternalError::Generic("Fingerprint needs to have two tokens"
+                                                       .to_string()));
+    }
+    Ok(SdpAttribute::Fingerprint(SdpAttributeFingerprint {
+                                     hash_algorithm: tokens[0].to_string(),
+                                     fingerprint: tokens[1].to_string(),
+                                 }))
+}
+
+fn parse_fmtp(to_parse: &str) -> Result<SdpAttribute, SdpParserInternalError> {
+    let tokens: Vec<&str> = to_parse.split_whitespace().collect();
+    if tokens.len() != 2 {
+        return Err(SdpParserInternalError::Generic("Fmtp needs to have two tokens".to_string()));
+    }
+    Ok(SdpAttribute::Fmtp(SdpAttributeFmtp {
+                              // TODO check for dynamic PT range
+                              payload_type: tokens[0].parse::<u32>()?,
+                              // TODO this should probably be slit into known tokens
+                              // plus a list of unknown tokens
+                              tokens: to_parse.split(';').map(|x| x.to_string()).collect(),
+                          }))
+}
+
+fn parse_group(to_parse: &str) -> Result<SdpAttribute, SdpParserInternalError> {
+    let mut tokens = to_parse.split_whitespace();
+    let semantics = match tokens.next() {
+        None => {
+            return Err(SdpParserInternalError::Generic("Group attribute is missing semantics token"
+                                                           .to_string()))
+        }
+        Some(x) => {
+            match x.to_uppercase().as_ref() {
+                "LS" => SdpAttributeGroupSemantic::LipSynchronization,
+                "FID" => SdpAttributeGroupSemantic::FlowIdentification,
+                "SRF" => SdpAttributeGroupSemantic::SingleReservationFlow,
+                "ANAT" => SdpAttributeGroupSemantic::AlternateNetworkAddressType,
+                "FEC" => SdpAttributeGroupSemantic::ForwardErrorCorrection,
+                "DDP" => SdpAttributeGroupSemantic::DecodingDependency,
+                "BUNDLE" => SdpAttributeGroupSemantic::Bundle,
+                _ => {
+                    return Err(SdpParserInternalError::Generic("Unsupported group semantics"
+                                                                   .to_string()))
+                }
+            }
+        }
+    };
+    Ok(SdpAttribute::Group(SdpAttributeGroup {
+                               semantics,
+                               tags: tokens.map(|x| x.to_string()).collect(),
+                           }))
+}
+
+fn parse_ice_options(to_parse: &str) -> Result<SdpAttribute, SdpParserInternalError> {
+    if to_parse.is_empty() {
+        return Err(SdpParserInternalError::Generic("ice-options is required to have a value"
+                                                       .to_string()));
+    }
+    Ok(SdpAttribute::IceOptions(to_parse.split_whitespace().map(|x| x.to_string()).collect()))
+}
+
+fn parse_msid(to_parse: &str) -> Result<SdpAttribute, SdpParserInternalError> {
+    let mut tokens = to_parse.split_whitespace();
+    let id = match tokens.next() {
+        None => {
+            return Err(SdpParserInternalError::Generic("Msid attribute is missing msid-id token"
+                                                           .to_string()))
+        }
+        Some(x) => x.to_string(),
+    };
+    let appdata = match tokens.next() {
+        None => None,
+        Some(x) => Some(x.to_string()),
+    };
+    Ok(SdpAttribute::Msid(SdpAttributeMsid { id, appdata }))
+
+}
+
+fn parse_msid_semantic(to_parse: &str) -> Result<SdpAttribute, SdpParserInternalError> {
+    let tokens: Vec<_> = to_parse.split_whitespace().collect();
+    if tokens.len() < 1 {
+        return Err(SdpParserInternalError::Generic("Msid-semantic attribute is missing msid-semantic token"
+                                                   .to_string()));
+    }
+    // TODO: Should msids be checked to ensure they are non empty?
+    let semantic = SdpAttributeMsidSemantic {
+        semantic: tokens[0].to_string(),
+        msids: tokens[1..].iter().map(|x| x.to_string()).collect(),
+    };
+    Ok(SdpAttribute::MsidSemantic(semantic))
+}
+
+fn parse_remote_candidates(to_parse: &str) -> Result<SdpAttribute, SdpParserInternalError> {
+    let mut tokens = to_parse.split_whitespace();
+    let component = match tokens.next() {
+        None => {
+            return Err(SdpParserInternalError::Generic(
+                           "Remote-candidate attribute is missing component ID"
+                               .to_string(),
+                       ))
+        }
+        Some(x) => x.parse::<u32>()?,
+    };
+    let address = match tokens.next() {
+        None => {
+            return Err(SdpParserInternalError::Generic(
+                           "Remote-candidate attribute is missing connection address"
+                               .to_string(),
+                       ))
+        }
+        Some(x) => parse_unicast_addr(x)?,
+    };
+    let port = match tokens.next() {
+        None => {
+            return Err(SdpParserInternalError::Generic(
+                           "Remote-candidate attribute is missing port number".to_string(),
+                       ))
+        }
+        Some(x) => x.parse::<u32>()?,
+    };
+    if port > 65535 {
+        return Err(SdpParserInternalError::Generic(
+                       "Remote-candidate port can only be a bit 16bit number".to_string(),
+                   ));
+    };
+    Ok(SdpAttribute::RemoteCandidate(SdpAttributeRemoteCandidate {
+                                         component,
+                                         address,
+                                         port,
+                                     }))
+}
+
+fn parse_rtpmap(to_parse: &str) -> Result<SdpAttribute, SdpParserInternalError> {
+    let mut tokens = to_parse.split_whitespace();
+    let payload_type: u32 = match tokens.next() {
+        None => {
+            return Err(SdpParserInternalError::Generic("Rtpmap missing payload type".to_string()))
+        }
+        Some(x) => {
+            let pt = x.parse::<u32>()?;
+            if pt > 127 {
+                return Err(SdpParserInternalError::Generic("Rtpmap payload type must be less then 127".to_string()));
+            };
+            pt
+        }
+    };
+    let mut parameters = match tokens.next() {
+        None => {
+            return Err(SdpParserInternalError::Generic("Rtpmap missing payload type".to_string()))
+        }
+        Some(x) => x.split('/'),
+    };
+    let name = match parameters.next() {
+        None => {
+            return Err(SdpParserInternalError::Generic("Rtpmap missing codec name".to_string()))
+        }
+        Some(x) => x.to_string(),
+    };
+    let frequency = match parameters.next() {
+        None => {
+            return Err(SdpParserInternalError::Generic("Rtpmap missing codec name".to_string()))
+        }
+        Some(x) => x.parse::<u32>()?,
+    };
+    let mut rtpmap = SdpAttributeRtpmap::new(payload_type, name, frequency);
+    match parameters.next() {
+        Some(x) => rtpmap.set_channels(x.parse::<u32>()?),
+        None => (),
+    };
+    Ok(SdpAttribute::Rtpmap(rtpmap))
+}
+
+fn parse_rtcp(to_parse: &str) -> Result<SdpAttribute, SdpParserInternalError> {
+    let mut tokens = to_parse.split_whitespace();
+    let port = match tokens.next() {
+        None => {
+            return Err(SdpParserInternalError::Generic("Rtcp attribute is missing port number"
+                                                           .to_string()))
+        }
+        Some(x) => x.parse::<u32>()?,
+    };
+    if port > 65535 {
+        return Err(SdpParserInternalError::Generic("Rtcp port can only be a bit 16bit number"
+                                                       .to_string()));
+    };
+    let mut rtcp = SdpAttributeRtcp::new(port);
+    match tokens.next() {
+        None => (),
+        Some(x) => {
+            parse_nettype(x)?;
+            match tokens.next() {
+                None => {
+                    return Err(SdpParserInternalError::Generic(
+                                   "Rtcp attribute is missing address type token"
+                                       .to_string(),
+                               ))
+                }
+                Some(x) => {
+                    let addrtype = parse_addrtype(x)?;
+                    let addr = match tokens.next() {
+                        None => {
+                            return Err(SdpParserInternalError::Generic(
+                                           "Rtcp attribute is missing ip address token"
+                                               .to_string(),
+                                       ))
+                        }
+                        Some(x) => {
+                            let addr = parse_unicast_addr(x)?;
+                            if !addrtype.same_protocol(&addr) {
+                                return Err(SdpParserInternalError::Generic(
+                                               "Failed to parse unicast address attribute.\
+                                              addrtype does not match address."
+                                                       .to_string(),
+                                           ));
+                            }
+                            addr
+                        }
+                    };
+                    rtcp.set_addr(addr);
+                }
+            };
+        }
+    };
+    Ok(SdpAttribute::Rtcp(rtcp))
+}
+
+fn parse_rtcp_fb(to_parse: &str) -> Result<SdpAttribute, SdpParserInternalError> {
+    let tokens: Vec<&str> = to_parse.splitn(2, ' ').collect();
+    Ok(SdpAttribute::Rtcpfb(SdpAttributeRtcpFb {
+                                // TODO limit this to dymaic PTs
+                                payload_type: tokens[0].parse::<u32>()?,
+                                feedback_type: match tokens.get(1) {
+                                    Some(x) => x.to_string(),
+                                    None => {
+                                        return Err(SdpParserInternalError::Generic(
+                                                       "Error parsing rtcpfb".to_string(),
+                                                   ))
+                                    }
+                                },
+                            }))
+}
+
+fn parse_sctpmap(to_parse: &str) -> Result<SdpAttribute, SdpParserInternalError> {
+    let tokens: Vec<&str> = to_parse.split_whitespace().collect();
+    if tokens.len() != 3 {
+        return Err(SdpParserInternalError::Generic("Sctpmap needs to have three tokens"
+                                                       .to_string()));
+    }
+    let port = tokens[0].parse::<u32>()?;
+    if port > 65535 {
+        return Err(SdpParserInternalError::Generic("Sctpmap port can only be a bit 16bit number"
+                                                       .to_string()));
+    }
+    if tokens[1].to_lowercase() != "webrtc-datachannel" {
+        return Err(SdpParserInternalError::Generic("Unsupported sctpmap type token".to_string()));
+    }
+    Ok(SdpAttribute::Sctpmap(SdpAttributeSctpmap {
+                                 port,
+                                 channels: tokens[2].parse::<u32>()?,
+                             }))
+}
+
+fn parse_setup(to_parse: &str) -> Result<SdpAttribute, SdpParserInternalError> {
+    Ok(SdpAttribute::Setup(match to_parse.to_lowercase().as_ref() {
+                               "active" => SdpAttributeSetup::Active,
+                               "actpass" => SdpAttributeSetup::Actpass,
+                               "holdconn" => SdpAttributeSetup::Holdconn,
+                               "passive" => SdpAttributeSetup::Passive,
+                               _ => {
+                                   return Err(SdpParserInternalError::Generic(
+                                                  "Unsupported setup value".to_string(),
+                                              ))
+                               }
+                           }))
+}
+
+fn parse_simulcast(to_parse: &str) -> Result<SdpAttribute, SdpParserInternalError> {
+    let mut tokens = to_parse.split_whitespace();
+    let mut token = match tokens.next() {
+        None => {
+            return Err(SdpParserInternalError::Generic(
+                           "Simulcast attribute is missing send/recv value".to_string(),
+                       ))
+        }
+        Some(x) => x,
+    };
+    let mut sc = SdpAttributeSimulcast {
+        send: Vec::new(),
+        receive: Vec::new(),
+    };
+    loop {
+        let sendrecv = match token.to_lowercase().as_ref() {
+            "send" => SdpAttributeDirection::Sendonly,
+            "recv" => SdpAttributeDirection::Recvonly,
+            _ => {
+                return Err(SdpParserInternalError::Generic(
+                               "Unsupported send/recv value in simulcast attribute"
+                                   .to_string(),
+                           ))
+            }
+        };
+        match tokens.next() {
+            None => {
+                return Err(SdpParserInternalError::Generic("Simulcast attribute is missing id list"
+                                                               .to_string()))
+            }
+            Some(x) => sc.parse_ids(sendrecv, x.to_string()),
+        };
+        token = match tokens.next() {
+            None => {
+                break;
+            }
+            Some(x) => x,
+        };
+    }
+    Ok(SdpAttribute::Simulcast(sc))
+}
+
+fn parse_ssrc(to_parse: &str) -> Result<SdpAttribute, SdpParserInternalError> {
+    let mut tokens = to_parse.split_whitespace();
+    let ssrc_id = match tokens.next() {
+        None => {
+            return Err(SdpParserInternalError::Generic("Ssrc attribute is missing ssrc-id value"
+                                                           .to_string()))
+        }
+        Some(x) => x.parse::<u32>()?,
+    };
+    let mut ssrc = SdpAttributeSsrc::new(ssrc_id);
+    match tokens.next() {
+        None => (),
+        Some(x) => ssrc.set_attribute(x),
+    };
+    Ok(SdpAttribute::Ssrc(ssrc))
+}
+
+pub fn parse_attribute(value: &str) -> Result<SdpType, SdpParserInternalError> {
+    Ok(SdpType::Attribute(value.trim().parse()?))
+}
+
+#[test]
+fn test_parse_attribute_candidate() {
+    assert!(parse_attribute("candidate:0 1 UDP 2122252543 172.16.156.106 49760 typ host").is_ok());
+    assert!(parse_attribute("candidate:foo 1 UDP 2122252543 172.16.156.106 49760 typ host")
+                .is_ok());
+    assert!(parse_attribute("candidate:0 1 TCP 2122252543 172.16.156.106 49760 typ host").is_ok());
+    assert!(parse_attribute("candidate:0 1 TCP 2122252543 ::1 49760 typ host").is_ok());
+    assert!(parse_attribute("candidate:0 1 UDP 2122252543 172.16.156.106 49760 typ srflx").is_ok());
+    assert!(parse_attribute("candidate:0 1 UDP 2122252543 172.16.156.106 49760 typ prflx").is_ok());
+    assert!(parse_attribute("candidate:0 1 UDP 2122252543 172.16.156.106 49760 typ relay").is_ok());
+    assert!(
+        parse_attribute(
+            "candidate:0 1 TCP 2122252543 172.16.156.106 49760 typ host tcptype active"
+        ).is_ok()
+    );
+    assert!(
+        parse_attribute(
+            "candidate:0 1 TCP 2122252543 172.16.156.106 49760 typ host tcptype passive"
+        ).is_ok()
+    );
+    assert!(
+        parse_attribute("candidate:0 1 TCP 2122252543 172.16.156.106 49760 typ host tcptype so")
+            .is_ok()
+    );
+    assert!(
+        parse_attribute("candidate:0 1 TCP 2122252543 172.16.156.106 49760 typ host ufrag foobar")
+            .is_ok()
+    );
+    assert!(
+        parse_attribute(
+            "candidate:0 1 TCP 2122252543 172.16.156.106 49760 typ host network-cost 50"
+        ).is_ok()
+    );
+    assert!(parse_attribute("candidate:1 1 UDP 1685987071 24.23.204.141 54609 typ srflx raddr 192.168.1.4 rport 61665 generation 0").is_ok());
+    assert!(parse_attribute("candidate:1 1 UDP 1685987071 24.23.204.141 54609 typ srflx raddr 192.168.1.4 rport 61665").is_ok());
+    assert!(parse_attribute("candidate:1 1 TCP 1685987071 24.23.204.141 54609 typ srflx raddr 192.168.1.4 rport 61665 tcptype passive").is_ok());
+    assert!(parse_attribute("candidate:1 1 TCP 1685987071 24.23.204.141 54609 typ srflx raddr 192.168.1.4 rport 61665 tcptype passive generation 1").is_ok());
+    assert!(parse_attribute("candidate:1 1 TCP 1685987071 24.23.204.141 54609 typ srflx raddr 192.168.1.4 rport 61665 tcptype passive generation 1 ufrag +DGd").is_ok());
+    assert!(parse_attribute("candidate:1 1 TCP 1685987071 24.23.204.141 54609 typ srflx raddr 192.168.1.4 rport 61665 tcptype passive generation 1 ufrag +DGd network-cost 1").is_ok());
+
+    assert!(parse_attribute("candidate:0 1 UDP 2122252543 172.16.156.106 49760 typ").is_err());
+    assert!(parse_attribute("candidate:0 foo UDP 2122252543 172.16.156.106 49760 typ host")
+                .is_err());
+    assert!(parse_attribute("candidate:0 1 FOO 2122252543 172.16.156.106 49760 typ host").is_err());
+    assert!(parse_attribute("candidate:0 1 UDP foo 172.16.156.106 49760 typ host").is_err());
+    assert!(parse_attribute("candidate:0 1 UDP 2122252543 172.16.156 49760 typ host").is_err());
+    assert!(parse_attribute("candidate:0 1 UDP 2122252543 172.16.156.106 70000 typ host").is_err());
+    assert!(parse_attribute("candidate:0 1 UDP 2122252543 172.16.156.106 49760 type host")
+                .is_err());
+    assert!(parse_attribute("candidate:0 1 UDP 2122252543 172.16.156.106 49760 typ fost").is_err());
+    // FIXME this should fail without the extra 'foobar' at the end
+    assert!(
+        parse_attribute(
+            "candidate:0 1 TCP 2122252543 172.16.156.106 49760 typ host unsupported foobar"
+        ).is_err()
+    );
+    assert!(parse_attribute("candidate:1 1 UDP 1685987071 24.23.204.141 54609 typ srflx raddr 192.168.1.4 rport 61665 generation B").is_err());
+    assert!(
+        parse_attribute(
+            "candidate:0 1 TCP 2122252543 172.16.156.106 49760 typ host network-cost C"
+        ).is_err()
+    );
+    assert!(
+        parse_attribute(
+            "candidate:1 1 UDP 1685987071 24.23.204.141 54609 typ srflx raddr 192.168.1 rport 61665"
+        ).is_err()
+    );
+    assert!(
+        parse_attribute(
+            "candidate:0 1 TCP 2122252543 172.16.156.106 49760 typ host tcptype foobar"
+        ).is_err()
+    );
+    assert!(
+        parse_attribute(
+            "candidate:1 1 UDP 1685987071 24.23.204.141 54609 typ srflx raddr 192.168.1 rport 61665"
+        ).is_err()
+    );
+    assert!(parse_attribute("candidate:1 1 UDP 1685987071 24.23.204.141 54609 typ srflx raddr 192.168.1.4 rport 70000").is_err());
+}
+
+#[test]
+fn test_parse_attribute_end_of_candidates() {
+    assert!(parse_attribute("end-of-candidates").is_ok());
+    assert!(parse_attribute("end-of-candidates foobar").is_err());
+}
+
+#[test]
+fn test_parse_attribute_extmap() {
+    assert!(parse_attribute("extmap:1/sendonly urn:ietf:params:rtp-hdrext:ssrc-audio-level")
+                .is_ok());
+    assert!(parse_attribute("extmap:2/sendrecv urn:ietf:params:rtp-hdrext:ssrc-audio-level")
+                .is_ok());
+    assert!(parse_attribute("extmap:3 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time")
+                .is_ok());
+
+    assert!(parse_attribute("extmap:3 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time ext_attributes")
+            .is_ok());
+
+    assert!(parse_attribute("extmap:a/sendrecv urn:ietf:params:rtp-hdrext:ssrc-audio-level")
+                .is_err());
+    assert!(parse_attribute("extmap:4/unsupported urn:ietf:params:rtp-hdrext:ssrc-audio-level")
+                .is_err());
+
+    let mut bad_char = String::from("extmap:3 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time ",);
+    bad_char.push(0x00 as char);
+    assert!(parse_attribute(&bad_char).is_err());
+}
+
+#[test]
+fn test_parse_attribute_fingerprint() {
+    assert!(parse_attribute("fingerprint:sha-256 CD:34:D1:62:16:95:7B:B7:EB:74:E2:39:27:97:EB:0B:23:73:AC:BC:BF:2F:E3:91:CB:57:A9:9D:4A:A2:0B:40").is_ok())
+}
+
+#[test]
+fn test_parse_attribute_fmtp() {
+    assert!(parse_attribute("fmtp:109 maxplaybackrate=48000;stereo=1;useinbandfec=1").is_ok())
+}
+
+#[test]
+fn test_parse_attribute_group() {
+    assert!(parse_attribute("group:LS").is_ok());
+    assert!(parse_attribute("group:LS 1 2").is_ok());
+    assert!(parse_attribute("group:BUNDLE sdparta_0 sdparta_1 sdparta_2").is_ok());
+
+    assert!(parse_attribute("group:").is_err());
+    assert!(parse_attribute("group:NEVER_SUPPORTED_SEMANTICS").is_err());
+}
+
+#[test]
+fn test_parse_attribute_bundle_only() {
+    assert!(parse_attribute("bundle-only").is_ok());
+    assert!(parse_attribute("bundle-only foobar").is_err());
+}
+
+#[test]
+fn test_parse_attribute_ice_lite() {
+    assert!(parse_attribute("ice-lite").is_ok());
+
+    assert!(parse_attribute("ice-lite foobar").is_err());
+}
+
+#[test]
+fn test_parse_attribute_ice_mismatch() {
+    assert!(parse_attribute("ice-mismatch").is_ok());
+
+    assert!(parse_attribute("ice-mismatch foobar").is_err());
+}
+
+#[test]
+fn test_parse_attribute_ice_options() {
+    assert!(parse_attribute("ice-options:trickle").is_ok());
+
+    assert!(parse_attribute("ice-options:").is_err());
+}
+
+#[test]
+fn test_parse_attribute_ice_pwd() {
+    assert!(parse_attribute("ice-pwd:e3baa26dd2fa5030d881d385f1e36cce").is_ok());
+
+    assert!(parse_attribute("ice-pwd:").is_err());
+}
+
+#[test]
+fn test_parse_attribute_ice_ufrag() {
+    assert!(parse_attribute("ice-ufrag:58b99ead").is_ok());
+
+    assert!(parse_attribute("ice-ufrag:").is_err());
+}
+
+#[test]
+fn test_parse_attribute_identity() {
+    assert!(parse_attribute("identity:eyJpZHAiOnsiZG9tYWluIjoiZXhhbXBsZS5vcmciLCJwcm90b2NvbCI6ImJvZ3VzIn0sImFzc2VydGlvbiI6IntcImlkZW50aXR5XCI6XCJib2JAZXhhbXBsZS5vcmdcIixcImNvbnRlbnRzXCI6XCJhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3l6XCIsXCJzaWduYXR1cmVcIjpcIjAxMDIwMzA0MDUwNlwifSJ9").is_ok());
+
+    assert!(parse_attribute("identity:").is_err());
+}
+
+#[test]
+fn test_parse_attribute_imageattr() {
+    assert!(parse_attribute("imageattr:120 send * recv *").is_ok());
+    assert!(
+        parse_attribute(
+            "imageattr:97 send [x=800,y=640,sar=1.1,q=0.6] [x=480,y=320] recv [x=330,y=250]"
+        ).is_ok()
+    );
+    assert!(parse_attribute("imageattr:97 recv [x=800,y=640,sar=1.1] send [x=330,y=250]").is_ok());
+    assert!(parse_attribute("imageattr:97 send [x=[480:16:800],y=[320:16:640],par=[1.2-1.3],q=0.6] [x=[176:8:208],y=[144:8:176],par=[1.2-1.3]] recv *").is_ok());
+
+    assert!(parse_attribute("imageattr:").is_err());
+}
+
+#[test]
+fn test_parse_attribute_inactive() {
+    assert!(parse_attribute("inactive").is_ok());
+    assert!(parse_attribute("inactive foobar").is_err());
+}
+
+#[test]
+fn test_parse_attribute_label() {
+    assert!(parse_attribute("label:1").is_ok());
+    assert!(parse_attribute("label:foobar").is_ok());
+    assert!(parse_attribute("label:foobar barfoo").is_ok());
+
+    assert!(parse_attribute("label:").is_err());
+}
+
+#[test]
+fn test_parse_attribute_maxptime() {
+    assert!(parse_attribute("maxptime:60").is_ok());
+
+    assert!(parse_attribute("maxptime:").is_err());
+}
+
+#[test]
+fn test_parse_attribute_mid() {
+    assert!(parse_attribute("mid:sdparta_0").is_ok());
+    assert!(parse_attribute("mid:sdparta_0 sdparta_1 sdparta_2").is_ok());
+
+    assert!(parse_attribute("mid:").is_err());
+}
+
+#[test]
+fn test_parse_attribute_msid() {
+    assert!(parse_attribute("msid:{5a990edd-0568-ac40-8d97-310fc33f3411}").is_ok());
+    assert!(
+        parse_attribute(
+            "msid:{5a990edd-0568-ac40-8d97-310fc33f3411} {218cfa1c-617d-2249-9997-60929ce4c405}"
+        ).is_ok()
+    );
+
+    assert!(parse_attribute("msid:").is_err());
+}
+
+#[test]
+fn test_parse_attribute_msid_semantics() {
+    assert!(parse_attribute("msid-semantic:WMS *").is_ok())
+}
+
+#[test]
+fn test_parse_attribute_ptime() {
+    assert!(parse_attribute("ptime:30").is_ok());
+
+    assert!(parse_attribute("ptime:").is_err());
+}
+
+#[test]
+fn test_parse_attribute_rid() {
+    assert!(parse_attribute("rid:foo send").is_ok());
+    assert!(parse_attribute("rid:foo").is_ok());
+
+    assert!(parse_attribute("rid:").is_err());
+}
+
+#[test]
+fn test_parse_attribute_recvonly() {
+    assert!(parse_attribute("recvonly").is_ok());
+    assert!(parse_attribute("recvonly foobar").is_err());
+}
+
+#[test]
+fn test_parse_attribute_remote_candidate() {
+    assert!(parse_attribute("remote-candidates:0 10.0.0.1 5555").is_ok());
+    assert!(parse_attribute("remote-candidates:12345 ::1 5555").is_ok());
+
+    assert!(parse_attribute("remote-candidates:abc 10.0.0.1 5555").is_err());
+    assert!(parse_attribute("remote-candidates:0 10.a.0.1 5555").is_err());
+    assert!(parse_attribute("remote-candidates:0 10.0.0.1 70000").is_err());
+    assert!(parse_attribute("remote-candidates:0 10.0.0.1").is_err());
+    assert!(parse_attribute("remote-candidates:0").is_err());
+    assert!(parse_attribute("remote-candidates:").is_err());
+}
+
+#[test]
+fn test_parse_attribute_sendonly() {
+    assert!(parse_attribute("sendonly").is_ok());
+    assert!(parse_attribute("sendonly foobar").is_err());
+}
+
+#[test]
+fn test_parse_attribute_sendrecv() {
+    assert!(parse_attribute("sendrecv").is_ok());
+    assert!(parse_attribute("sendrecv foobar").is_err());
+}
+
+#[test]
+fn test_parse_attribute_setup() {
+    assert!(parse_attribute("setup:active").is_ok());
+    assert!(parse_attribute("setup:passive").is_ok());
+    assert!(parse_attribute("setup:actpass").is_ok());
+    assert!(parse_attribute("setup:holdconn").is_ok());
+
+    assert!(parse_attribute("setup:").is_err());
+    assert!(parse_attribute("setup:foobar").is_err());
+}
+
+#[test]
+fn test_parse_attribute_rtcp() {
+    assert!(parse_attribute("rtcp:5000").is_ok());
+    assert!(parse_attribute("rtcp:9 IN IP4 0.0.0.0").is_ok());
+
+    assert!(parse_attribute("rtcp:").is_err());
+    assert!(parse_attribute("rtcp:70000").is_err());
+    assert!(parse_attribute("rtcp:9 IN").is_err());
+    assert!(parse_attribute("rtcp:9 IN IP4").is_err());
+    assert!(parse_attribute("rtcp:9 IN IP4 ::1").is_err());
+}
+
+#[test]
+fn test_parse_attribute_rtcp_fb() {
+    assert!(parse_attribute("rtcp-fb:101 ccm fir").is_ok())
+}
+
+#[test]
+fn test_parse_attribute_rtcp_mux() {
+    assert!(parse_attribute("rtcp-mux").is_ok());
+    assert!(parse_attribute("rtcp-mux foobar").is_err());
+}
+
+#[test]
+fn test_parse_attribute_rtcp_rsize() {
+    assert!(parse_attribute("rtcp-rsize").is_ok());
+    assert!(parse_attribute("rtcp-rsize foobar").is_err());
+}
+
+#[test]
+fn test_parse_attribute_rtpmap() {
+    assert!(parse_attribute("rtpmap:109 opus/48000").is_ok());
+    assert!(parse_attribute("rtpmap:109 opus/48000/2").is_ok());
+
+    assert!(parse_attribute("rtpmap:109 ").is_err());
+    assert!(parse_attribute("rtpmap:109 opus").is_err());
+    assert!(parse_attribute("rtpmap:128 opus/48000").is_err());
+}
+
+#[test]
+fn test_parse_attribute_sctpmap() {
+    assert!(parse_attribute("sctpmap:5000 webrtc-datachannel 256").is_ok());
+
+    assert!(parse_attribute("sctpmap:70000 webrtc-datachannel 256").is_err());
+    assert!(parse_attribute("sctpmap:5000 unsupported 256").is_err());
+    assert!(parse_attribute("sctpmap:5000 webrtc-datachannel 2a").is_err());
+}
+
+#[test]
+fn test_parse_attribute_sctp_port() {
+    assert!(parse_attribute("sctp-port:5000").is_ok());
+
+    assert!(parse_attribute("sctp-port:").is_err());
+    assert!(parse_attribute("sctp-port:70000").is_err());
+}
+
+#[test]
+fn test_parse_attribute_max_message_size() {
+    assert!(parse_attribute("max-message-size:1").is_ok());
+    assert!(parse_attribute("max-message-size:100000").is_ok());
+    assert!(parse_attribute("max-message-size:4294967297").is_ok());
+    assert!(parse_attribute("max-message-size:0").is_ok());
+
+    assert!(parse_attribute("max-message-size:").is_err());
+    assert!(parse_attribute("max-message-size:abc").is_err());
+}
+
+#[test]
+fn test_parse_attribute_simulcast() {
+    assert!(parse_attribute("simulcast:send 1").is_ok());
+    assert!(parse_attribute("simulcast:recv test").is_ok());
+    assert!(parse_attribute("simulcast:recv ~test").is_ok());
+    assert!(parse_attribute("simulcast:recv test;foo").is_ok());
+    assert!(parse_attribute("simulcast:recv foo,bar").is_ok());
+    assert!(parse_attribute("simulcast:recv foo,bar;test").is_ok());
+    assert!(parse_attribute("simulcast:recv 1;4,5 send 6;7").is_ok());
+    assert!(parse_attribute("simulcast:send 1,2,3;~4,~5 recv 6;~7,~8").is_ok());
+    // old draft 03 notation used by Firefox 55
+    assert!(parse_attribute("simulcast: send rid=foo;bar").is_ok());
+
+    assert!(parse_attribute("simulcast:").is_err());
+    assert!(parse_attribute("simulcast:send").is_err());
+    assert!(parse_attribute("simulcast:foobar 1").is_err());
+    assert!(parse_attribute("simulcast:send 1 foobar 2").is_err());
+}
+
+#[test]
+fn test_parse_attribute_ssrc() {
+    assert!(parse_attribute("ssrc:2655508255").is_ok());
+    assert!(parse_attribute("ssrc:2655508255 foo").is_ok());
+    assert!(parse_attribute("ssrc:2655508255 cname:{735484ea-4f6c-f74a-bd66-7425f8476c2e}")
+                .is_ok());
+
+    assert!(parse_attribute("ssrc:").is_err());
+    assert!(parse_attribute("ssrc:foo").is_err());
+}
+
+#[test]
+fn test_parse_attribute_ssrc_group() {
+    assert!(parse_attribute("ssrc-group:FID 3156517279 2673335628").is_ok())
+}
+
+#[test]
+fn test_parse_unknown_attribute() {
+    assert!(parse_attribute("unknown").is_err())
+}
+
+// Returns true if valid byte-string as defined by RFC 4566
+// https://tools.ietf.org/html/rfc4566
+fn valid_byte_string(input: &str) -> bool {
+    !(input.contains(0x00 as char) || input.contains(0x0A as char) || input.contains(0x0D as char))
+}
new file mode 100644
--- /dev/null
+++ b/media/webrtc/signaling/src/sdp/rsdparsa/src/bin/file_parser.rs
@@ -0,0 +1,35 @@
+use std::error::Error;
+use std::io::prelude::*;
+use std::fs::File;
+use std::path::Path;
+use std::env;
+extern crate rsdparsa;
+
+fn main() {
+    let filename = match env::args().nth(1) {
+        None => {
+            println!("Missing file name argument!");
+            return;
+        },
+        Some(x) => x,
+    };
+    let path = Path::new(filename.as_str());
+    let display = path.display();
+
+    let mut file = match File::open(&path) {
+        Err(why) => panic!("Failed to open {}: {}",
+                             display,
+                             why.description()),
+        Ok(file) => file
+    };
+
+    let mut s = String::new();
+    match file.read_to_string(&mut s) {
+        Err(why) => panic!("couldn't read {}: {}",
+                           display,
+                           why.description()),
+        Ok(s) => s
+    };
+
+    rsdparsa::parse_sdp(&s, true).is_ok();
+}
new file mode 100644
--- /dev/null
+++ b/media/webrtc/signaling/src/sdp/rsdparsa/src/bin/sdps/10.sdp
@@ -0,0 +1,13 @@
+v=0
+o=- 4294967296 2 IN IP4 127.0.0.1
+s=SIP Call
+c=IN IP4 198.51.100.7
+t=0 0
+m=video 9 RTP/SAVPF 97 120 121 122 123
+c=IN IP6 ::1
+a=fingerprint:sha-1 DF:FA:FB:08:3B:3C:54:1D:D7:D4:05:77:A0:72:9B:14:08:6D:0F:4C:2E:AC:8A:FD:0A:8E:99:BF:5D:E8:3C:E7
+a=rtpmap:97 H264/90000
+a=rtpmap:120 VP8/90000
+a=rtpmap:121 VP9/90000
+a=rtpmap:122 red/90000
+a=rtpmap:123 ulpfec/90000
new file mode 100644
--- /dev/null
+++ b/media/webrtc/signaling/src/sdp/rsdparsa/src/bin/sdps/11.sdp
@@ -0,0 +1,63 @@
+v=0
+o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0
+s=SIP Call
+c=IN IP4 224.0.0.1/100/12
+t=0 0
+a=ice-ufrag:4a799b2e
+a=ice-pwd:e4cc12a910f106a0a744719425510e17
+a=ice-lite
+a=msid-semantic:WMS stream streama
+a=fingerprint:sha-256 DF:2E:AC:8A:FD:0A:8E:99:BF:5D:E8:3C:E7:FA:FB:08:3B:3C:54:1D:D7:D4:05:77:A0:72:9B:14:08:6D:0F:4C
+a=group:BUNDLE first second
+a=group:BUNDLE third
+a=group:LS first third
+m=audio 9 RTP/SAVPF 109 9 0 8 101
+c=IN IP4 0.0.0.0
+a=mid:first
+a=rtpmap:109 opus/48000/2
+a=ptime:20
+a=maxptime:20
+a=rtpmap:9 G722/8000
+a=rtpmap:0 PCMU/8000
+a=rtpmap:8 PCMA/8000
+a=rtpmap:101 telephone-event/8000
+a=fmtp:101 0-15,66,32-34,67
+a=ice-ufrag:00000000
+a=ice-pwd:0000000000000000000000000000000
+a=sendonly
+a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level
+a=setup:actpass
+a=rtcp-mux
+a=msid:stream track
+a=candidate:0 1 UDP 2130379007 10.0.0.36 62453 typ host
+a=candidate:2 1 UDP 1694236671 24.6.134.204 62453 typ srflx raddr 10.0.0.36 rport 62453
+a=candidate:3 1 UDP 100401151 162.222.183.171 49761 typ relay raddr 162.222.183.171 rport 49761
+a=candidate:6 1 UDP 16515071 162.222.183.171 51858 typ relay raddr 162.222.183.171 rport 51858
+a=candidate:3 2 UDP 100401150 162.222.183.171 62454 typ relay raddr 162.222.183.171 rport 62454
+a=candidate:2 2 UDP 1694236670 24.6.134.204 55428 typ srflx raddr 10.0.0.36 rport 55428
+a=candidate:6 2 UDP 16515070 162.222.183.171 50340 typ relay raddr 162.222.183.171 rport 50340
+a=candidate:0 2 UDP 2130379006 10.0.0.36 55428 typ host
+m=video 9 RTP/SAVPF 97 98 120
+c=IN IP6 ::1
+a=mid:second
+a=rtpmap:97 H264/90000
+a=rtpmap:98 H264/90000
+a=rtpmap:120 VP8/90000
+a=recvonly
+a=setup:active
+a=rtcp-mux
+a=msid:streama tracka
+a=msid:streamb trackb
+a=candidate:0 1 UDP 2130379007 10.0.0.36 59530 typ host
+a=candidate:0 2 UDP 2130379006 10.0.0.36 64378 typ host
+a=candidate:2 2 UDP 1694236670 24.6.134.204 64378 typ srflx raddr 10.0.0.36 rport 64378
+a=candidate:6 2 UDP 16515070 162.222.183.171 64941 typ relay raddr 162.222.183.171 rport 64941
+a=candidate:6 1 UDP 16515071 162.222.183.171 64800 typ relay raddr 162.222.183.171 rport 64800
+a=candidate:2 1 UDP 1694236671 24.6.134.204 59530 typ srflx raddr 10.0.0.36 rport 59530
+a=candidate:3 1 UDP 100401151 162.222.183.171 62935 typ relay raddr 162.222.183.171 rport 62935
+a=candidate:3 2 UDP 100401150 162.222.183.171 61026 typ relay raddr 162.222.183.171 rport 61026
+m=audio 9 RTP/SAVPF 0
+a=mid:third
+a=rtpmap:0 PCMU/8000
+a=ice-lite
+a=msid:noappdata
new file mode 100644
--- /dev/null
+++ b/media/webrtc/signaling/src/sdp/rsdparsa/src/bin/sdps/12.sdp
@@ -0,0 +1,58 @@
+v=0
+o=Mozilla-SIPUA-35.0a1 27987 0 IN IP4 0.0.0.0
+s=SIP Call
+t=0 0
+a=ice-ufrag:8a39d2ae
+a=ice-pwd:601d53aba51a318351b3ecf5ee00048f
+a=fingerprint:sha-256 30:FF:8E:2B:AC:9D:ED:70:18:10:67:C8:AE:9E:68:F3:86:53:51:B0:AC:31:B7:BE:6D:CF:A4:2E:D3:6E:B4:28
+m=audio 9 RTP/SAVPF 109 9 0 8 101
+c=IN IP4 0.0.0.0
+a=rtpmap:109 opus/48000/2
+a=ptime:20
+a=rtpmap:9 G722/8000
+a=rtpmap:0 PCMU/8000
+a=rtpmap:8 PCMA/8000
+a=rtpmap:101 telephone-event/8000
+a=fmtp:101 0-15
+a=sendrecv
+a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level
+a=extmap:2/sendonly some_extension
+a=extmap:3 some_other_extension some_params some more params
+a=setup:actpass
+a=rtcp-mux
+m=video 9 RTP/SAVPF 120 126 97
+c=IN IP4 0.0.0.0
+a=rtpmap:120 VP8/90000
+a=rtpmap:126 H264/90000
+a=rtpmap:97 H264/90000
+a=sendrecv
+a=rtcp-fb:120 ack rpsi
+a=rtcp-fb:120 ack app foo
+a=rtcp-fb:120 ack foo
+a=rtcp-fb:120 nack
+a=rtcp-fb:120 nack sli
+a=rtcp-fb:120 nack pli
+a=rtcp-fb:120 nack rpsi
+a=rtcp-fb:120 nack app foo
+a=rtcp-fb:120 nack foo
+a=rtcp-fb:120 ccm fir
+a=rtcp-fb:120 ccm tmmbr
+a=rtcp-fb:120 ccm tstr
+a=rtcp-fb:120 ccm vbcm
+a=rtcp-fb:120 ccm foo
+a=rtcp-fb:120 trr-int 10
+a=rtcp-fb:120 goog-remb
+a=rtcp-fb:120 foo
+a=rtcp-fb:126 nack
+a=rtcp-fb:126 nack pli
+a=rtcp-fb:126 ccm fir
+a=rtcp-fb:97 nack
+a=rtcp-fb:97 nack pli
+a=rtcp-fb:97 ccm fir
+a=rtcp-fb:* ccm tmmbr
+a=setup:actpass
+a=rtcp-mux
+m=application 9 DTLS/SCTP 5000
+c=IN IP4 0.0.0.0
+a=sctpmap:5000 webrtc-datachannel 16
+a=setup:actpass
new file mode 100644
--- /dev/null
+++ b/media/webrtc/signaling/src/sdp/rsdparsa/src/bin/sdps/13.sdp
@@ -0,0 +1,12 @@
+v=0
+o=Mozilla-SIPUA-35.0a1 27987 0 IN IP4 0.0.0.0
+s=SIP Call
+t=0 0
+a=ice-ufrag:8a39d2ae
+a=ice-pwd:601d53aba51a318351b3ecf5ee00048f
+a=fingerprint:sha-256 30:FF:8E:2B:AC:9D:ED:70:18:10:67:C8:AE:9E:68:F3:86:53:51:B0:AC:31:B7:BE:6D:CF:A4:2E:D3:6E:B4:28
+m=application 9 UDP/DTLS/SCTP webrtc-datachannel
+c=IN IP4 0.0.0.0
+a=sctp-port:5000
+a=max-message-size:10000
+a=setup:actpass
new file mode 100644
--- /dev/null
+++ b/media/webrtc/signaling/src/sdp/rsdparsa/src/bin/sdps/14.sdp
@@ -0,0 +1,9 @@
+v=0
+o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0
+s=SIP Call
+c=IN IP4 224.0.0.1/100/12
+t=0 0
+a=candidate:0 1 UDP 2130379007 10.0.0.36 62453 typ host
+m=audio 9 RTP/SAVPF 109 9 0 8 101
+c=IN IP4 0.0.0.0
+a=rtpmap:109 opus/48000/2
new file mode 100644
--- /dev/null
+++ b/media/webrtc/signaling/src/sdp/rsdparsa/src/bin/sdps/15.sdp
@@ -0,0 +1,9 @@
+v=0
+o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0
+s=SIP Call
+c=IN IP4 224.0.0.1/100/12
+t=0 0
+a=bundle-only
+m=audio 9 RTP/SAVPF 109 9 0 8 101
+c=IN IP4 0.0.0.0
+a=rtpmap:109 opus/48000/2
new file mode 100644
--- /dev/null
+++ b/media/webrtc/signaling/src/sdp/rsdparsa/src/bin/sdps/16.sdp
@@ -0,0 +1,9 @@
+v=0
+o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0
+s=SIP Call
+c=IN IP4 224.0.0.1/100/12
+t=0 0
+a=fmtp:109 0-15
+m=audio 9 RTP/SAVPF 109 9 0 8 101
+c=IN IP4 0.0.0.0
+a=rtpmap:109 opus/48000/2
new file mode 100644
--- /dev/null
+++ b/media/webrtc/signaling/src/sdp/rsdparsa/src/bin/sdps/17.sdp
@@ -0,0 +1,9 @@
+v=0
+o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0
+s=SIP Call
+c=IN IP4 224.0.0.1/100/12
+t=0 0
+a=ice-mismatch
+m=audio 9 RTP/SAVPF 109 9 0 8 101
+c=IN IP4 0.0.0.0
+a=rtpmap:109 opus/48000/2
new file mode 100644
--- /dev/null
+++ b/media/webrtc/signaling/src/sdp/rsdparsa/src/bin/sdps/18.sdp
@@ -0,0 +1,9 @@
+v=0
+o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0
+s=SIP Call
+c=IN IP4 224.0.0.1/100/12
+t=0 0
+a=imageattr:120 send * recv *
+m=video 9 RTP/SAVPF 120
+c=IN IP4 0.0.0.0
+a=rtpmap:120 VP8/90000
new file mode 100644
--- /dev/null
+++ b/media/webrtc/signaling/src/sdp/rsdparsa/src/bin/sdps/19.sdp
@@ -0,0 +1,9 @@
+v=0
+o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0
+s=SIP Call
+c=IN IP4 224.0.0.1/100/12
+t=0 0
+a=label:foobar
+m=video 9 RTP/SAVPF 120
+c=IN IP4 0.0.0.0
+a=rtpmap:120 VP8/90000
new file mode 100644
--- /dev/null
+++ b/media/webrtc/signaling/src/sdp/rsdparsa/src/bin/sdps/2.sdp
@@ -0,0 +1,7 @@
+v=0
+o=- 4294967296 2 IN IP4 127.0.0.1
+s=SIP Call
+c=IN IP4 198.51.100.7
+t=0 0
+m=video 56436 RTP/SAVPF 120
+a=rtpmap:120 VP8/90000
new file mode 100644
--- /dev/null
+++ b/media/webrtc/signaling/src/sdp/rsdparsa/src/bin/sdps/20.sdp
@@ -0,0 +1,9 @@
+v=0
+o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0
+s=SIP Call
+c=IN IP4 224.0.0.1/100/12
+t=0 0
+a=maxptime:100
+m=video 9 RTP/SAVPF 120
+c=IN IP4 0.0.0.0
+a=rtpmap:120 VP8/90000
new file mode 100644
--- /dev/null
+++ b/media/webrtc/signaling/src/sdp/rsdparsa/src/bin/sdps/21.sdp
@@ -0,0 +1,9 @@
+v=0
+o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0
+s=SIP Call
+c=IN IP4 224.0.0.1/100/12
+t=0 0
+a=mid:foobar
+m=video 9 RTP/SAVPF 120
+c=IN IP4 0.0.0.0
+a=rtpmap:120 VP8/90000
new file mode 100644
--- /dev/null
+++ b/media/webrtc/signaling/src/sdp/rsdparsa/src/bin/sdps/22.sdp
@@ -0,0 +1,9 @@
+v=0
+o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0
+s=SIP Call
+c=IN IP4 224.0.0.1/100/12
+t=0 0
+a=msid:foobar
+m=video 9 RTP/SAVPF 120
+c=IN IP4 0.0.0.0
+a=rtpmap:120 VP8/90000
new file mode 100644
--- /dev/null
+++ b/media/webrtc/signaling/src/sdp/rsdparsa/src/bin/sdps/23.sdp
@@ -0,0 +1,9 @@
+v=0
+o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0
+s=SIP Call
+c=IN IP4 224.0.0.1/100/12
+t=0 0
+a=ptime:50
+m=video 9 RTP/SAVPF 120
+c=IN IP4 0.0.0.0
+a=rtpmap:120 VP8/90000
new file mode 100644
--- /dev/null
+++ b/media/webrtc/signaling/src/sdp/rsdparsa/src/bin/sdps/24.sdp
@@ -0,0 +1,9 @@
+v=0
+o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0
+s=SIP Call
+c=IN IP4 224.0.0.1/100/12
+t=0 0
+a=remote-candidates:0 10.0.0.1 5555
+m=video 9 RTP/SAVPF 120
+c=IN IP4 0.0.0.0
+a=rtpmap:120 VP8/90000
new file mode 100644
--- /dev/null
+++ b/media/webrtc/signaling/src/sdp/rsdparsa/src/bin/sdps/25.sdp
@@ -0,0 +1,9 @@
+v=0
+o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0
+s=SIP Call
+c=IN IP4 224.0.0.1/100/12
+t=0 0
+a=rtcp:5555
+m=video 9 RTP/SAVPF 120
+c=IN IP4 0.0.0.0
+a=rtpmap:120 VP8/90000
new file mode 100644
--- /dev/null
+++ b/media/webrtc/signaling/src/sdp/rsdparsa/src/bin/sdps/26.sdp
@@ -0,0 +1,9 @@
+v=0
+o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0
+s=SIP Call
+c=IN IP4 224.0.0.1/100/12
+t=0 0
+a=rtcp-fb:120 nack
+m=video 9 RTP/SAVPF 120
+c=IN IP4 0.0.0.0
+a=rtpmap:120 VP8/90000
new file mode 100644
--- /dev/null
+++ b/media/webrtc/signaling/src/sdp/rsdparsa/src/bin/sdps/27.sdp
@@ -0,0 +1,9 @@
+v=0
+o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0
+s=SIP Call
+c=IN IP4 224.0.0.1/100/12
+t=0 0
+a=rtcp-mux
+m=video 9 RTP/SAVPF 120
+c=IN IP4 0.0.0.0
+a=rtpmap:120 VP8/90000
new file mode 100644
--- /dev/null
+++ b/media/webrtc/signaling/src/sdp/rsdparsa/src/bin/sdps/28.sdp
@@ -0,0 +1,9 @@
+v=0
+o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0
+s=SIP Call
+c=IN IP4 224.0.0.1/100/12
+t=0 0
+a=rtcp-rsize
+m=video 9 RTP/SAVPF 120
+c=IN IP4 0.0.0.0
+a=rtpmap:120 VP8/90000
new file mode 100644
--- /dev/null
+++ b/media/webrtc/signaling/src/sdp/rsdparsa/src/bin/sdps/29.sdp
@@ -0,0 +1,8 @@
+v=0
+o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0
+s=SIP Call
+c=IN IP4 224.0.0.1/100/12
+t=0 0
+a=rtpmap:120 VP8/90000
+m=video 9 RTP/SAVPF 120
+c=IN IP4 0.0.0.0
new file mode 100644
--- /dev/null
+++ b/media/webrtc/signaling/src/sdp/rsdparsa/src/bin/sdps/3.sdp
@@ -0,0 +1,34 @@
+v=0
+o=- 4294967296 2 IN IP4 127.0.0.1
+s=SIP Call
+c=IN IP4 198.51.100.7
+t=0 0
+m=video 56436 RTP/SAVPF 120
+a=rtpmap:120 VP8/90000
+a=rtpmap:122 red/90000
+a=rtcp-fb:120 ack rpsi
+a=rtcp-fb:120 ack app
+a=rtcp-fb:120 ack app foo
+a=rtcp-fb:120 ack foo bar
+a=rtcp-fb:120 ack foo bar baz
+a=rtcp-fb:120 nack
+a=rtcp-fb:120 nack pli
+a=rtcp-fb:120 nack sli
+a=rtcp-fb:120 nack rpsi
+a=rtcp-fb:120 nack app
+a=rtcp-fb:120 nack app foo
+a=rtcp-fb:120 nack app foo bar
+a=rtcp-fb:120 nack foo bar baz
+a=rtcp-fb:120 trr-int 0
+a=rtcp-fb:120 trr-int 123
+a=rtcp-fb:120 goog-remb
+a=rtcp-fb:120 ccm fir
+a=rtcp-fb:120 ccm tmmbr
+a=rtcp-fb:120 ccm tstr
+a=rtcp-fb:120 ccm vbcm 123 456 789
+a=rtcp-fb:120 ccm foo
+a=rtcp-fb:120 ccm foo bar baz
+a=rtcp-fb:120 foo
+a=rtcp-fb:120 foo bar
+a=rtcp-fb:120 foo bar baz
+a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level
new file mode 100644
--- /dev/null
+++ b/media/webrtc/signaling/src/sdp/rsdparsa/src/bin/sdps/30.sdp
@@ -0,0 +1,9 @@
+v=0
+o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0
+s=SIP Call
+c=IN IP4 224.0.0.1/100/12
+t=0 0
+a=sctpmap:5000
+m=video 9 RTP/SAVPF 120
+c=IN IP4 0.0.0.0
+a=rtpmap:120 VP8/90000
new file mode 100644
--- /dev/null
+++ b/media/webrtc/signaling/src/sdp/rsdparsa/src/bin/sdps/31.sdp
@@ -0,0 +1,9 @@
+v=0
+o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0
+s=SIP Call
+c=IN IP4 224.0.0.1/100/12
+t=0 0
+a=ssrc:5000
+m=video 9 RTP/SAVPF 120
+c=IN IP4 0.0.0.0
+a=rtpmap:120 VP8/90000
new file mode 100644
--- /dev/null
+++ b/media/webrtc/signaling/src/sdp/rsdparsa/src/bin/sdps/32.sdp
@@ -0,0 +1,9 @@
+v=0
+o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0
+s=SIP Call
+c=IN IP4 224.0.0.1/100/12
+t=0 0
+a=ssrc-group:FID 5000
+m=video 9 RTP/SAVPF 120
+c=IN IP4 0.0.0.0
+a=rtpmap:120 VP8/90000
new file mode 100644
--- /dev/null
+++ b/media/webrtc/signaling/src/sdp/rsdparsa/src/bin/sdps/33.sdp
@@ -0,0 +1,9 @@
+v=0
+o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0
+s=SIP Call
+c=IN IP4 224.0.0.1/100/12
+t=0 0
+m=video 9 RTP/SAVPF 120
+c=IN IP4 0.0.0.0
+a=rtpmap:120 VP8/90000
+a=imageattr:flob
new file mode 100644
--- /dev/null
+++ b/media/webrtc/signaling/src/sdp/rsdparsa/src/bin/sdps/34.sdp
@@ -0,0 +1,9 @@
+v=0
+o=- 4294967296 2 IN IP4 127.0.0.1
+s=SIP Call
+c=IN IP4 198.51.100.7
+b=CT:5000
+t=0 0
+m=video 56436 RTP/SAVPF 120
+a=rtpmap:120 VP8/90000
+a=sendrecv
new file mode 100644
--- /dev/null
+++ b/media/webrtc/signaling/src/sdp/rsdparsa/src/bin/sdps/35.sdp
@@ -0,0 +1,9 @@
+v=0
+o=- 4294967296 2 IN IP4 127.0.0.1
+s=SIP Call
+c=IN IP4 198.51.100.7
+b=CT:5000
+t=0 0
+m=video 56436 RTP/SAVPF 120
+a=rtpmap:120 VP8/90000
+a=sendrecv
new file mode 100644
--- /dev/null
+++ b/media/webrtc/signaling/src/sdp/rsdparsa/src/bin/sdps/36.sdp
@@ -0,0 +1,9 @@
+v=0
+o=- 4294967296 2 IN IP4 127.0.0.1
+s=SIP Call
+c=IN IP4 198.51.100.7
+b=CT:5000
+t=0 0
+m=video 56436 RTP/SAVPF 120
+a=rtpmap:120 VP8/90000
+a=sendrecv
new file mode 100644
--- /dev/null
+++ b/media/webrtc/signaling/src/sdp/rsdparsa/src/bin/sdps/37.sdp
@@ -0,0 +1,9 @@
+v=0
+o=- 4294967296 2 IN IP4 127.0.0.1
+s=SIP Call
+c=IN IP4 198.51.100.7
+b=CT:5000
+t=0 0
+m=video 56436 RTP/SAVPF 120
+a=rtpmap:120 VP8/90000
+a=recvonly
new file mode 100644
--- /dev/null
+++ b/media/webrtc/signaling/src/sdp/rsdparsa/src/bin/sdps/38.sdp
@@ -0,0 +1,9 @@
+v=0
+o=- 4294967296 2 IN IP4 127.0.0.1
+s=SIP Call
+c=IN IP4 198.51.100.7
+b=CT:5000
+t=0 0
+m=video 56436 RTP/SAVPF 120
+a=rtpmap:120 VP8/90000
+a=sendonly
new file mode 100644
--- /dev/null
+++ b/media/webrtc/signaling/src/sdp/rsdparsa/src/bin/sdps/39.sdp
@@ -0,0 +1,8 @@
+v=0
+o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0
+s=SIP Call
+c=IN IP4 224.0.0.1/100/12
+t=0 0
+m=video 9 RTP/SAVPF 120
+c=IN IP4 0.0.0.0
+a=rtpmap:120 VP8/90000
new file mode 100644
--- /dev/null
+++ b/media/webrtc/signaling/src/sdp/rsdparsa/src/bin/sdps/4.sdp
@@ -0,0 +1,7 @@
+v=0
+o=- 4294967296 2 IN IP4 127.0.0.1
+s=SIP Call
+t=0 0
+m=video 56436 RTP/SAVPF 120
+c=IN IP4 198.51.100.7
+a=rtpmap:120 VP8/90000
new file mode 100644
--- /dev/null
+++ b/media/webrtc/signaling/src/sdp/rsdparsa/src/bin/sdps/40.sdp
@@ -0,0 +1,8 @@
+v=0
+o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0
+s=SIP Call
+c=IN IP4 224.0.0.1/100/12
+t=0 0
+m=video 9 RTP/SAVPF 120
+c=IN IP4 0.0.0.0
+a=rtpmap:120 VP8/90000
new file mode 100644
--- /dev/null
+++ b/media/webrtc/signaling/src/sdp/rsdparsa/src/bin/sdps/41.sdp
@@ -0,0 +1,91 @@
+v=0
+o=- 1109973417102828257 2 IN IP4 127.0.0.1
+s=-
+t=0 0
+a=group:BUNDLE audio video
+a=msid-semantic: WMS 1PBxet5BYh0oYodwsvNM4k6KiO2eWCX40VIP
+m=audio 32952 UDP/TLS/RTP/SAVPF 111 103 104 0 8 107 106 105 13 126
+c=IN IP4 128.64.32.16
+a=rtcp:32952 IN IP4 128.64.32.16
+a=candidate:77142221 1 udp 2113937151 192.168.137.1 54081 typ host generation 0
+a=candidate:77142221 2 udp 2113937151 192.168.137.1 54081 typ host generation 0
+a=candidate:983072742 1 udp 2113937151 172.22.0.56 54082 typ host generation 0
+a=candidate:983072742 2 udp 2113937151 172.22.0.56 54082 typ host generation 0
+a=candidate:2245074553 1 udp 1845501695 32.64.128.1 62397 typ srflx raddr 192.168.137.1 rport 54081 generation 0
+a=candidate:2245074553 2 udp 1845501695 32.64.128.1 62397 typ srflx raddr 192.168.137.1 rport 54081 generation 0
+a=candidate:2479353907 1 udp 1845501695 32.64.128.1 54082 typ srflx raddr 172.22.0.56 rport 54082 generation 0
+a=candidate:2479353907 2 udp 1845501695 32.64.128.1 54082 typ srflx raddr 172.22.0.56 rport 54082 generation 0
+a=candidate:1243276349 1 tcp 1509957375 192.168.137.1 0 typ host generation 0
+a=candidate:1243276349 2 tcp 1509957375 192.168.137.1 0 typ host generation 0
+a=candidate:1947960086 1 tcp 1509957375 172.22.0.56 0 typ host generation 0
+a=candidate:1947960086 2 tcp 1509957375 172.22.0.56 0 typ host generation 0
+a=candidate:1808221584 1 udp 33562367 128.64.32.16 32952 typ relay raddr 32.64.128.1 rport 62398 generation 0
+a=candidate:1808221584 2 udp 33562367 128.64.32.16 32952 typ relay raddr 32.64.128.1 rport 62398 generation 0
+a=candidate:507872740 1 udp 33562367 128.64.32.16 40975 typ relay raddr 32.64.128.1 rport 54085 generation 0
+a=candidate:507872740 2 udp 33562367 128.64.32.16 40975 typ relay raddr 32.64.128.1 rport 54085 generation 0
+a=ice-ufrag:xQuJwjX3V3eMA81k
+a=ice-pwd:ZUiRmjS2GDhG140p73dAsSVP
+a=ice-options:google-ice
+a=fingerprint:sha-256 59:4A:8B:73:A7:73:53:71:88:D7:4D:58:28:0C:79:72:31:29:9B:05:37:DD:58:43:C2:D4:85:A2:B3:66:38:7A
+a=setup:active
+a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level
+a=sendrecv
+a=mid:audio
+a=rtcp-mux
+a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:/U44g3ULdtapeiSg+T3n6dDLBKIjpOhb/NXAL/2b
+a=rtpmap:111 opus/48000/2
+a=rtpmap:103 ISAC/16000
+a=rtpmap:104 ISAC/32000
+a=rtpmap:0 PCMU/8000
+a=rtpmap:8 PCMA/8000
+a=rtpmap:107 CN/48000
+a=rtpmap:106 CN/32000
+a=rtpmap:105 CN/16000
+a=rtpmap:13 CN/8000
+a=rtpmap:126 telephone-event/8000
+a=maxptime:60
+a=ssrc:2271517329 cname:mKDNt7SQf6pwDlIn
+a=ssrc:2271517329 msid:1PBxet5BYh0oYodwsvNM4k6KiO2eWCX40VIP 1PBxet5BYh0oYodwsvNM4k6KiO2eWCX40VIPa0
+a=ssrc:2271517329 mslabel:1PBxet5BYh0oYodwsvNM4k6KiO2eWCX40VIP
+a=ssrc:2271517329 label:1PBxet5BYh0oYodwsvNM4k6KiO2eWCX40VIPa0
+m=video 32952 UDP/TLS/RTP/SAVPF 100 116 117
+c=IN IP4 128.64.32.16
+a=rtcp:32952 IN IP4 128.64.32.16
+a=candidate:77142221 1 udp 2113937151 192.168.137.1 54081 typ host generation 0
+a=candidate:77142221 2 udp 2113937151 192.168.137.1 54081 typ host generation 0
+a=candidate:983072742 1 udp 2113937151 172.22.0.56 54082 typ host generation 0
+a=candidate:983072742 2 udp 2113937151 172.22.0.56 54082 typ host generation 0
+a=candidate:2245074553 1 udp 1845501695 32.64.128.1 62397 typ srflx raddr 192.168.137.1 rport 54081 generation 0
+a=candidate:2245074553 2 udp 1845501695 32.64.128.1 62397 typ srflx raddr 192.168.137.1 rport 54081 generation 0
+a=candidate:2479353907 1 udp 1845501695 32.64.128.1 54082 typ srflx raddr 172.22.0.56 rport 54082 generation 0
+a=candidate:2479353907 2 udp 1845501695 32.64.128.1 54082 typ srflx raddr 172.22.0.56 rport 54082 generation 0
+a=candidate:1243276349 1 tcp 1509957375 192.168.137.1 0 typ host generation 0
+a=candidate:1243276349 2 tcp 1509957375 192.168.137.1 0 typ host generation 0
+a=candidate:1947960086 1 tcp 1509957375 172.22.0.56 0 typ host generation 0
+a=candidate:1947960086 2 tcp 1509957375 172.22.0.56 0 typ host generation 0
+a=candidate:1808221584 1 udp 33562367 128.64.32.16 32952 typ relay raddr 32.64.128.1 rport 62398 generation 0
+a=candidate:1808221584 2 udp 33562367 128.64.32.16 32952 typ relay raddr 32.64.128.1 rport 62398 generation 0
+a=candidate:507872740 1 udp 33562367 128.64.32.16 40975 typ relay raddr 32.64.128.1 rport 54085 generation 0
+a=candidate:507872740 2 udp 33562367 128.64.32.16 40975 typ relay raddr 32.64.128.1 rport 54085 generation 0
+a=ice-ufrag:xQuJwjX3V3eMA81k
+a=ice-pwd:ZUiRmjS2GDhG140p73dAsSVP
+a=ice-options:google-ice
+a=fingerprint:sha-256 59:4A:8B:73:A7:73:53:71:88:D7:4D:58:28:0C:79:72:31:29:9B:05:37:DD:58:43:C2:D4:85:A2:B3:66:38:7A
+a=setup:active
+a=extmap:2 urn:ietf:params:rtp-hdrext:toffset
+a=extmap:3 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
+a=sendrecv
+a=mid:video
+a=rtcp-mux
+a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:/U44g3ULdtapeiSg+T3n6dDLBKIjpOhb/NXAL/2b
+a=rtpmap:100 VP8/90000
+a=rtcp-fb:100 ccm fir
+a=rtcp-fb:100 nack
+a=rtcp-fb:100 goog-remb
+a=rtpmap:116 red/90000
+a=rtpmap:117 ulpfec/90000
+a=ssrc:54724160 cname:mKDNt7SQf6pwDlIn
+a=ssrc:54724160 msid:1PBxet5BYh0oYodwsvNM4k6KiO2eWCX40VIP 1PBxet5BYh0oYodwsvNM4k6KiO2eWCX40VIPv0
+a=ssrc:54724160 mslabel:1PBxet5BYh0oYodwsvNM4k6KiO2eWCX40VIP
+a=ssrc:54724160 label:1PBxet5BYh0oYodwsvNM4k6KiO2eWCX40VIPv0
+
new file mode 100644
--- /dev/null
+++ b/media/webrtc/signaling/src/sdp/rsdparsa/src/bin/sdps/5.sdp
@@ -0,0 +1,5 @@
+v=0
+o=- 4294967296 2 IN IP4 127.0.0.1
+s=SIP Call
+t=0 0
+a=ice-lite
new file mode 100644
--- /dev/null
+++ b/media/webrtc/signaling/src/sdp/rsdparsa/src/bin/sdps/6.sdp
@@ -0,0 +1,12 @@
+v=0
+o=- 4294967296 2 IN IP4 127.0.0.1
+s=SIP Call
+c=IN IP4 198.51.100.7
+b=CT:5000
+b=FOOBAR:10
+b=AS:4
+t=0 0
+m=video 56436 RTP/SAVPF 120
+a=rtpmap:120 VP8/90000
+m=audio 12345/2 RTP/SAVPF 0
+a=rtpmap:0 PCMU/8000
new file mode 100644
--- /dev/null
+++ b/media/webrtc/signaling/src/sdp/rsdparsa/src/bin/sdps/7.sdp
@@ -0,0 +1,7 @@
+v=0
+o=- 4294967296 2 IN IP4 127.0.0.1
+c=IN IP4 198.51.100.7
+t=0 0
+m=video 56436 RTP/SAVPF 120
+b=CT:1000
+a=rtpmap:120 VP8/90000
new file mode 100644
--- /dev/null
+++ b/media/webrtc/signaling/src/sdp/rsdparsa/src/bin/sdps/8.sdp
@@ -0,0 +1,84 @@
+v=0
+o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0
+s=SIP Call
+c=IN IP4 224.0.0.1/100/12
+t=0 0
+a=ice-ufrag:4a799b2e
+a=ice-pwd:e4cc12a910f106a0a744719425510e17
+a=ice-lite
+a=ice-options:trickle foo
+a=msid-semantic:WMS stream streama
+a=msid-semantic:foo stream
+a=fingerprint:sha-256 DF:2E:AC:8A:FD:0A:8E:99:BF:5D:E8:3C:E7:FA:FB:08:3B:3C:54:1D:D7:D4:05:77:A0:72:9B:14:08:6D:0F:4C
+a=identity:eyJpZHAiOnsiZG9tYWluIjoiZXhhbXBsZS5vcmciLCJwcm90b2NvbCI6ImJvZ3VzIn0sImFzc2VydGlvbiI6IntcImlkZW50aXR5XCI6XCJib2JAZXhhbXBsZS5vcmdcIixcImNvbnRlbnRzXCI6XCJhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3l6XCIsXCJzaWduYXR1cmVcIjpcIjAxMDIwMzA0MDUwNlwifSJ9
+a=group:BUNDLE first second
+a=group:BUNDLE third
+a=group:LS first third
+m=audio 9 RTP/SAVPF 109 9 0 8 101
+c=IN IP4 0.0.0.0
+a=mid:first
+a=rtpmap:109 opus/48000/2
+a=ptime:20
+a=maxptime:20
+a=rtpmap:9 G722/8000
+a=rtpmap:0 PCMU/8000
+a=rtpmap:8 PCMA/8000
+a=rtpmap:101 telephone-event/8000
+a=fmtp:101 0-15,66,32-34,67
+a=ice-ufrag:00000000
+a=ice-pwd:0000000000000000000000000000000
+a=sendonly
+a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level
+a=setup:actpass
+a=rtcp-mux
+a=msid:stream track
+a=candidate:0 1 UDP 2130379007 10.0.0.36 62453 typ host
+a=candidate:2 1 UDP 1694236671 24.6.134.204 62453 typ srflx raddr 10.0.0.36 rport 62453
+a=candidate:3 1 UDP 100401151 162.222.183.171 49761 typ relay raddr 162.222.183.171 rport 49761
+a=candidate:6 1 UDP 16515071 162.222.183.171 51858 typ relay raddr 162.222.183.171 rport 51858
+a=candidate:3 2 UDP 100401150 162.222.183.171 62454 typ relay raddr 162.222.183.171 rport 62454
+a=candidate:2 2 UDP 1694236670 24.6.134.204 55428 typ srflx raddr 10.0.0.36 rport 55428
+a=candidate:6 2 UDP 16515070 162.222.183.171 50340 typ relay raddr 162.222.183.171 rport 50340
+a=candidate:0 2 UDP 2130379006 10.0.0.36 55428 typ host
+a=rtcp:62454 IN IP4 162.222.183.171
+a=end-of-candidates
+a=ssrc:5150
+m=video 9 RTP/SAVPF 120 121 122 123
+c=IN IP6 ::1
+a=fingerprint:sha-1 DF:FA:FB:08:3B:3C:54:1D:D7:D4:05:77:A0:72:9B:14:08:6D:0F:4C:2E:AC:8A:FD:0A:8E:99:BF:5D:E8:3C:E7
+a=mid:second
+a=rtpmap:120 VP8/90000
+a=rtpmap:121 VP9/90000
+a=rtpmap:122 red/90000
+a=rtpmap:123 ulpfec/90000
+a=recvonly
+a=rtcp-fb:120 nack
+a=rtcp-fb:120 nack pli
+a=rtcp-fb:120 ccm fir
+a=rtcp-fb:121 nack
+a=rtcp-fb:121 nack pli
+a=rtcp-fb:121 ccm fir
+a=setup:active
+a=rtcp-mux
+a=msid:streama tracka
+a=msid:streamb trackb
+a=candidate:0 1 UDP 2130379007 10.0.0.36 59530 typ host
+a=candidate:0 2 UDP 2130379006 10.0.0.36 64378 typ host
+a=candidate:2 2 UDP 1694236670 24.6.134.204 64378 typ srflx raddr 10.0.0.36 rport 64378
+a=candidate:6 2 UDP 16515070 162.222.183.171 64941 typ relay raddr 162.222.183.171 rport 64941
+a=candidate:6 1 UDP 16515071 162.222.183.171 64800 typ relay raddr 162.222.183.171 rport 64800
+a=candidate:2 1 UDP 1694236671 24.6.134.204 59530 typ srflx raddr 10.0.0.36 rport 59530
+a=candidate:3 1 UDP 100401151 162.222.183.171 62935 typ relay raddr 162.222.183.171 rport 62935
+a=candidate:3 2 UDP 100401150 162.222.183.171 61026 typ relay raddr 162.222.183.171 rport 61026
+a=rtcp:61026
+a=end-of-candidates
+a=ssrc:1111 foo
+a=ssrc:1111 foo:bar
+a=imageattr:120 send * recv *
+m=audio 9 RTP/SAVPF 0
+a=mid:third
+a=rtpmap:0 PCMU/8000
+a=ice-lite
+a=ice-options:foo bar
+a=msid:noappdata
+a=bundle-only
new file mode 100644
--- /dev/null
+++ b/media/webrtc/signaling/src/sdp/rsdparsa/src/bin/sdps/9.sdp
@@ -0,0 +1,34 @@
+v=0
+o=- 4294967296 2 IN IP4 127.0.0.1
+s=SIP Call
+c=IN IP4 198.51.100.7
+t=0 0
+m=audio 9 RTP/SAVPF 109 9 0 8 101
+c=IN IP4 0.0.0.0
+a=mid:first
+a=rtpmap:109 opus/48000/2
+a=ptime:20
+a=maxptime:20
+a=rtpmap:9 G722/8000
+a=rtpmap:0 PCMU/8000
+a=rtpmap:8 PCMA/8000
+a=rtpmap:101 telephone-event/8000
+a=fmtp:101 0-15
+a=fmtp:101 0-5.
+a=fmtp:101 0-15,66,67
+a=fmtp:101 0,1,2-4,5-15,66,67
+a=fmtp:101 5,6,7
+a=fmtp:101 0
+a=fmtp:101 1
+a=fmtp:101 123
+a=fmtp:101 0-123
+a=fmtp:101 -12
+a=fmtp:101 12-
+a=fmtp:101 1,12-,4
+a=fmtp:101 ,2,3
+a=fmtp:101 ,,,2,3
+a=fmtp:101 1,,,,,,,,3
+a=fmtp:101 1,2,3,
+a=fmtp:101 1-2-3
+a=fmtp:101 112233
+a=fmtp:101 33-2
new file mode 100755
--- /dev/null
+++ b/media/webrtc/signaling/src/sdp/rsdparsa/src/bin/sdps/extract.sh
@@ -0,0 +1,3 @@
+#!/bin/bash
+
+grep '\"[ a-z]=[^=]*$' sdp_unittests.cpp  | grep -v 'ParseSdp(kVideoSdp' | grep -v 'kVideoWithRedAndUlpfec' | grep -v 'ASSERT_NE' | grep -v 'BASE64_DTLS_HELLO' | grep -v '^\/\/' | sed 's/ParseSdp(//g' | sed 's/^[[:space:]]*//' | sed 's/+ //' | sed 's/ \/\/.*$//' | sed 's/\;$//' | sed 's/)$//' | sed 's/, false//' | sed 's/" CRLF//' | sed 's/^\"//' | sed 's/\"$//' | sed 's/\\r\\n//' | gawk -v RS='(^|\n)v=' '/./ { print "v="$0 > NR".sdp" }'
new file mode 100644
--- /dev/null
+++ b/media/webrtc/signaling/src/sdp/rsdparsa/src/error.rs
@@ -0,0 +1,215 @@
+use std::num::ParseIntError;
+use std::net::AddrParseError;
+use std::fmt;
+use std::error;
+use std::error::Error;
+
+#[derive(Debug)]
+pub enum SdpParserInternalError {
+    Generic(String),
+    Unsupported(String),
+    Integer(ParseIntError),
+    Address(AddrParseError),
+}
+
+impl fmt::Display for SdpParserInternalError {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        match *self {
+            SdpParserInternalError::Generic(ref message) => {
+                write!(f, "Generic parsing error: {}", message)
+            }
+            SdpParserInternalError::Unsupported(ref message) => {
+                write!(f, "Unsupported parsing error: {}", message)
+            }
+            SdpParserInternalError::Integer(ref error) => {
+                write!(f, "Integer parsing error: {}", error.description())
+            }
+            SdpParserInternalError::Address(ref error) => {
+                write!(f, "IP address parsing error: {}", error.description())
+            }
+        }
+    }
+}
+
+impl error::Error for SdpParserInternalError {
+    fn description(&self) -> &str {
+        match *self {
+            SdpParserInternalError::Generic(ref message) |
+            SdpParserInternalError::Unsupported(ref message) => message,
+            SdpParserInternalError::Integer(ref error) => error.description(),
+            SdpParserInternalError::Address(ref error) => error.description(),
+        }
+    }
+
+    fn cause(&self) -> Option<&error::Error> {
+        match *self {
+            SdpParserInternalError::Integer(ref error) => Some(error),
+            SdpParserInternalError::Address(ref error) => Some(error),
+            // Can't tell much more about our internal errors
+            _ => None,
+        }
+    }
+}
+
+#[test]
+fn test_sdp_parser_internal_error_generic() {
+    let generic = SdpParserInternalError::Generic("generic message".to_string());
+    assert_eq!(format!("{}", generic),
+               "Generic parsing error: generic message");
+    assert_eq!(generic.description(), "generic message");
+    assert!(generic.cause().is_none());
+}
+
+#[test]
+fn test_sdp_parser_internal_error_unsupported() {
+    let unsupported = SdpParserInternalError::Unsupported("unsupported internal message"
+                                                              .to_string());
+    assert_eq!(format!("{}", unsupported),
+               "Unsupported parsing error: unsupported internal message");
+    assert_eq!(unsupported.description(), "unsupported internal message");
+    assert!(unsupported.cause().is_none());
+}
+
+#[test]
+fn test_sdp_parser_internal_error_integer() {
+    let v = "12a";
+    let integer = v.parse::<u64>();
+    assert!(integer.is_err());
+    let int_err = SdpParserInternalError::Integer(integer.err().unwrap());
+    assert_eq!(format!("{}", int_err),
+               "Integer parsing error: invalid digit found in string");
+    assert_eq!(int_err.description(), "invalid digit found in string");
+    assert!(!int_err.cause().is_none());
+}
+
+#[test]
+fn test_sdp_parser_internal_error_address() {
+    let v = "127.0.0.a";
+    use std::str::FromStr;
+    use std::net::IpAddr;
+    let addr = IpAddr::from_str(v);
+    assert!(addr.is_err());
+    let addr_err = SdpParserInternalError::Address(addr.err().unwrap());
+    assert_eq!(format!("{}", addr_err),
+               "IP address parsing error: invalid IP address syntax");
+    assert_eq!(addr_err.description(), "invalid IP address syntax");
+    assert!(!addr_err.cause().is_none());
+}
+
+#[derive(Debug)]
+pub enum SdpParserError {
+    Line {
+        error: SdpParserInternalError,
+        line: String,
+        line_number: usize,
+    },
+    Unsupported {
+        error: SdpParserInternalError,
+        line: String,
+        line_number: usize,
+    },
+    Sequence { message: String, line_number: usize },
+}
+
+impl fmt::Display for SdpParserError {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        match *self {
+            SdpParserError::Line {
+                ref error,
+                ref line,
+                ref line_number,
+            } => {
+                write!(f,
+                       "Line error: {} in line({}): {}",
+                       error.description(),
+                       line_number,
+                       line)
+            }
+            SdpParserError::Unsupported {
+                ref error,
+                ref line,
+                ref line_number,
+            } => {
+                write!(f,
+                       "Unsupported: {} in line({}): {}",
+                       error.description(),
+                       line_number,
+                       line)
+            }
+            SdpParserError::Sequence {
+                ref message,
+                ref line_number,
+            } => write!(f, "Sequence error in line({}): {}", line_number, message),
+        }
+    }
+}
+
+
+impl error::Error for SdpParserError {
+    fn description(&self) -> &str {
+        match *self {
+            SdpParserError::Line { ref error, .. } |
+            SdpParserError::Unsupported { ref error, .. } => error.description(),
+            SdpParserError::Sequence { ref message, .. } => message,
+        }
+    }
+
+    fn cause(&self) -> Option<&error::Error> {
+        match *self {
+            SdpParserError::Line { ref error, .. } |
+            SdpParserError::Unsupported { ref error, .. } => Some(error),
+            // Can't tell much more about our internal errors
+            _ => None,
+        }
+    }
+}
+
+impl From<ParseIntError> for SdpParserInternalError {
+    fn from(err: ParseIntError) -> SdpParserInternalError {
+        SdpParserInternalError::Integer(err)
+    }
+}
+
+impl From<AddrParseError> for SdpParserInternalError {
+    fn from(err: AddrParseError) -> SdpParserInternalError {
+        SdpParserInternalError::Address(err)
+    }
+}
+
+#[test]
+fn test_sdp_parser_error_line() {
+    let line1 = SdpParserError::Line {
+        error: SdpParserInternalError::Generic("test message".to_string()),
+        line: "test line".to_string(),
+        line_number: 13,
+    };
+    assert_eq!(format!("{}", line1),
+               "Line error: test message in line(13): test line");
+    assert_eq!(line1.description(), "test message");
+    assert!(line1.cause().is_some());
+}
+
+#[test]
+fn test_sdp_parser_error_unsupported() {
+    let unsupported1 = SdpParserError::Unsupported {
+        error: SdpParserInternalError::Generic("unsupported value".to_string()),
+        line: "unsupported line".to_string(),
+        line_number: 21,
+    };
+    assert_eq!(format!("{}", unsupported1),
+               "Unsupported: unsupported value in line(21): unsupported line");
+    assert_eq!(unsupported1.description(), "unsupported value");
+    assert!(unsupported1.cause().is_some());
+}
+
+#[test]
+fn test_sdp_parser_error_sequence() {
+    let sequence1 = SdpParserError::Sequence {
+        message: "sequence message".to_string(),
+        line_number: 42,
+    };
+    assert_eq!(format!("{}", sequence1),
+               "Sequence error in line(42): sequence message");
+    assert_eq!(sequence1.description(), "sequence message");
+    assert!(sequence1.cause().is_none());
+}
new file mode 100644
--- /dev/null
+++ b/media/webrtc/signaling/src/sdp/rsdparsa/src/lib.rs
@@ -0,0 +1,974 @@
+#![cfg_attr(feature="clippy", feature(plugin))]
+
+use std::net::IpAddr;
+use std::fmt;
+
+pub mod attribute_type;
+pub mod error;
+pub mod media_type;
+pub mod network;
+pub mod unsupported_types;
+
+use attribute_type::{SdpAttribute, parse_attribute};
+use error::{SdpParserInternalError, SdpParserError};
+use media_type::{SdpMedia, SdpMediaLine, parse_media, parse_media_vector};
+use network::{parse_addrtype, parse_nettype, parse_unicast_addr};
+use unsupported_types::{parse_email, parse_information, parse_key, parse_phone, parse_repeat,
+                        parse_uri, parse_zone};
+
+#[derive(Clone)]
+pub enum SdpBandwidth {
+    As(u32),
+    Ct(u32),
+    Tias(u32),
+    Unknown(String, u32),
+}
+
+#[derive(Clone)]
+pub struct SdpConnection {
+    pub addr: IpAddr,
+    pub ttl: Option<u32>,
+    pub amount: Option<u32>,
+}
+
+#[derive(Clone)]
+pub struct SdpOrigin {
+    pub username: String,
+    pub session_id: u64,
+    pub session_version: u64,
+    pub unicast_addr: IpAddr,
+}
+
+impl fmt::Display for SdpOrigin {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        write!(f,
+               "origin: {}, {}, {}, {}",
+               self.username,
+               self.session_id,
+               self.session_version,
+               self.unicast_addr)
+    }
+}
+
+#[derive(Clone)]
+pub struct SdpTiming {
+    pub start: u64,
+    pub stop: u64,
+}
+
+pub enum SdpType {
+    Attribute(SdpAttribute),
+    Bandwidth(SdpBandwidth),
+    Connection(SdpConnection),
+    Email(String),
+    Information(String),
+    Key(String),
+    Media(SdpMediaLine),
+    Phone(String),
+    Origin(SdpOrigin),
+    Repeat(String),
+    Session(String),
+    Timing(SdpTiming),
+    Uri(String),
+    Version(u64),
+    Zone(String),
+}
+
+pub struct SdpLine {
+    pub line_number: usize,
+    pub sdp_type: SdpType,
+}
+
+pub struct SdpSession {
+    pub version: u64,
+    pub origin: SdpOrigin,
+    pub session: String,
+    pub connection: Option<SdpConnection>,
+    pub bandwidth: Vec<SdpBandwidth>,
+    pub timing: Option<SdpTiming>,
+    pub attribute: Vec<SdpAttribute>,
+    pub media: Vec<SdpMedia>,
+    // unsupported values:
+    // information: Option<String>,
+    // uri: Option<String>,
+    // email: Option<String>,
+    // phone: Option<String>,
+    // repeat: Option<String>,
+    // zone: Option<String>,
+    // key: Option<String>,
+}
+
+impl SdpSession {
+    pub fn new(version: u64, origin: SdpOrigin, session: String) -> SdpSession {
+        SdpSession {
+            version,
+            origin,
+            session,
+            connection: None,
+            bandwidth: Vec::new(),
+            timing: None,
+            attribute: Vec::new(),
+            media: Vec::new(),
+        }
+    }
+
+    pub fn get_version(&self) -> u64 {
+        self.version
+    }
+
+    pub fn get_origin(&self) -> &SdpOrigin {
+        &self.origin
+    }
+
+    pub fn get_session(&self) -> &String {
+        &self.session
+    }
+
+    pub fn get_connection(&self) -> &Option<SdpConnection> {
+        &self.connection
+    }
+
+    pub fn set_connection(&mut self, c: &SdpConnection) {
+        self.connection = Some(c.clone())
+    }
+
+    pub fn add_bandwidth(&mut self, b: &SdpBandwidth) {
+        self.bandwidth.push(b.clone())
+    }
+
+    pub fn set_timing(&mut self, t: &SdpTiming) {
+        self.timing = Some(t.clone())
+    }
+
+    pub fn add_attribute(&mut self, a: &SdpAttribute) -> Result<(), SdpParserInternalError> {
+        if !a.allowed_at_session_level() {
+            return Err(SdpParserInternalError::Generic(format!("{} not allowed at session level",
+                                                               a)));
+        };
+        Ok(self.attribute.push(a.clone()))
+    }
+
+    pub fn extend_media(&mut self, v: Vec<SdpMedia>) {
+        self.media.extend(v)
+    }
+
+    pub fn has_timing(&self) -> bool {
+        self.timing.is_some()
+    }
+
+    pub fn has_attributes(&self) -> bool {
+        !self.attribute.is_empty()
+    }
+
+    // FIXME this is a temporary hack until we re-oranize the SdpAttribute enum
+    // so that we can build a generic has_attribute(X) function
+    fn has_extmap_attribute(&self) -> bool {
+        for attribute in &self.attribute {
+            if let &SdpAttribute::Extmap(_) = attribute {
+                return true;
+            }
+        }
+        false
+    }
+
+    pub fn has_media(&self) -> bool {
+        !self.media.is_empty()
+    }
+}
+
+fn parse_session(value: &str) -> Result<SdpType, SdpParserInternalError> {
+    println!("session: {}", value);
+    Ok(SdpType::Session(String::from(value)))
+}
+
+#[test]
+fn test_session_works() {
+    assert!(parse_session("topic").is_ok());
+}
+
+
+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)));
+    };
+    println!("version: {}", ver);
+    Ok(SdpType::Version(ver))
+}
+
+#[test]
+fn test_version_works() {
+    assert!(parse_version("0").is_ok());
+}
+
+#[test]
+fn test_version_unsupported_input() {
+    assert!(parse_version("1").is_err());
+    assert!(parse_version("11").is_err());
+    assert!(parse_version("a").is_err());
+}
+
+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 session version token".to_string(),
+                       ))
+        }
+        Some(x) => parse_nettype(x)?,
+    };
+    let addrtype = match tokens.next() {
+        None => {
+            return Err(SdpParserInternalError::Generic("Origin type is missing address type token"
+                                                           .to_string()))
+        }
+        Some(x) => parse_addrtype(x)?,
+    };
+    let unicast_addr = match tokens.next() {
+        None => {
+            return Err(SdpParserInternalError::Generic("Origin type is missing IP address token"
+                                                           .to_string()))
+        }
+        Some(x) => parse_unicast_addr(x)?,
+    };
+    if !addrtype.same_protocol(&unicast_addr) {
+        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,
+    };
+    println!("{}", o);
+    Ok(SdpType::Origin(o))
+}
+
+#[test]
+fn test_origin_works() {
+    assert!(parse_origin("mozilla 506705521068071134 0 IN IP4 0.0.0.0").is_ok());
+    assert!(parse_origin("mozilla 506705521068071134 0 IN IP6 ::1").is_ok());
+}
+
+#[test]
+fn test_origin_wrong_amount_of_tokens() {
+    assert!(parse_origin("a b c d e").is_err());
+    assert!(parse_origin("a b c d e f g").is_err());
+}
+
+#[test]
+fn test_origin_unsupported_nettype() {
+    assert!(parse_origin("mozilla 506705521068071134 0 UNSUPPORTED IP4 0.0.0.0").is_err());
+}
+
+#[test]
+fn test_origin_unsupported_addrtpe() {
+    assert!(parse_origin("mozilla 506705521068071134 0 IN IP1 0.0.0.0").is_err());
+}
+
+#[test]
+fn test_origin_broken_ip_addr() {
+    assert!(parse_origin("mozilla 506705521068071134 0 IN IP4 1.1.1.256").is_err());
+    assert!(parse_origin("mozilla 506705521068071134 0 IN IP6 ::g").is_err());
+}
+
+#[test]
+fn test_origin_addr_type_mismatch() {
+    assert!(parse_origin("mozilla 506705521068071134 0 IN IP4 ::1").is_err());
+}
+
+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_nettype(cv[0])?;
+    let addrtype = parse_addrtype(cv[1])?;
+    let mut ttl = None;
+    let mut amount = None;
+    let mut addr_token = cv[2];
+    if addr_token.find('/') != None {
+        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::<u32>()?);
+        addr_token = addr_tokens[0];
+    }
+    let addr = parse_unicast_addr(addr_token)?;
+    if !addrtype.same_protocol(&addr) {
+        return Err(SdpParserInternalError::Generic("connection addrtype does not match address."
+                                                       .to_string()));
+    }
+    let c = SdpConnection { addr, ttl, amount };
+    println!("connection: {}", c.addr);
+    Ok(SdpType::Connection(c))
+}
+
+#[test]
+fn connection_works() {
+    assert!(parse_connection("IN IP4 127.0.0.1").is_ok());
+    assert!(parse_connection("IN IP4 127.0.0.1/10/10").is_ok());
+}
+
+#[test]
+fn connection_lots_of_whitespace() {
+    assert!(parse_connection("IN   IP4   127.0.0.1").is_ok());
+}
+
+#[test]
+fn connection_wrong_amount_of_tokens() {
+    assert!(parse_connection("IN IP4").is_err());
+    assert!(parse_connection("IN IP4 0.0.0.0 foobar").is_err());
+}
+
+#[test]
+fn connection_unsupported_nettype() {
+    assert!(parse_connection("UNSUPPORTED IP4 0.0.0.0").is_err());
+}
+
+#[test]
+fn connection_unsupported_addrtpe() {
+    assert!(parse_connection("IN IP1 0.0.0.0").is_err());
+}
+
+#[test]
+fn connection_broken_ip_addr() {
+    assert!(parse_connection("IN IP4 1.1.1.256").is_err());
+    assert!(parse_connection("IN IP6 ::g").is_err());
+}
+
+#[test]
+fn connection_addr_type_mismatch() {
+    assert!(parse_connection("IN IP4 ::1").is_err());
+}
+
+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),
+    };
+    println!("bandwidth: {}, {}", bv[0], bandwidth);
+    Ok(SdpType::Bandwidth(bw))
+}
+
+#[test]
+fn bandwidth_works() {
+    assert!(parse_bandwidth("AS:1").is_ok());
+    assert!(parse_bandwidth("CT:123").is_ok());
+    assert!(parse_bandwidth("TIAS:12345").is_ok());
+}
+
+#[test]
+fn bandwidth_wrong_amount_of_tokens() {
+    assert!(parse_bandwidth("TIAS").is_err());
+    assert!(parse_bandwidth("TIAS:12345:xyz").is_err());
+}
+
+#[test]
+fn bandwidth_unsupported_type() {
+    assert!(parse_bandwidth("UNSUPPORTED:12345").is_ok());
+}
+
+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 };
+    println!("timing: {}, {}", t.start, t.stop);
+    Ok(SdpType::Timing(t))
+}
+
+#[test]
+fn test_timing_works() {
+    assert!(parse_timing("0 0").is_ok());
+}
+
+#[test]
+fn test_timing_non_numeric_tokens() {
+    assert!(parse_timing("a 0").is_err());
+    assert!(parse_timing("0 a").is_err());
+}
+
+#[test]
+fn test_timing_wrong_amount_of_tokens() {
+    assert!(parse_timing("0").is_err());
+    assert!(parse_timing("0 0 0").is_err());
+}
+
+fn parse_sdp_line(line: &str, line_number: usize) -> Result<SdpLine, SdpParserError> {
+    if line.find('=') == None {
+        return Err(SdpParserError::Line {
+                       error: SdpParserInternalError::Generic("missing = character in line"
+                                                                  .to_string()),
+                       line: line.to_string(),
+                       line_number: 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: 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: line_number,
+                           });
+            }
+            if trimmed.is_empty() {
+                return Err(SdpParserError::Line {
+                               error: SdpParserInternalError::Generic("type is empty".to_string()),
+                               line: line.to_string(),
+                               line_number: line_number,
+                           });
+            }
+            trimmed
+        }
+    };
+    let line_value = match splitted_line.next() {
+        None => {
+            return Err(SdpParserError::Line {
+                           error: SdpParserInternalError::Generic("missing value".to_string()),
+                           line: line.to_string(),
+                           line_number: line_number,
+                       })
+        }
+        Some(v) => {
+            let trimmed = v.trim();
+            if trimmed.is_empty() {
+                return Err(SdpParserError::Line {
+                               error: SdpParserInternalError::Generic("value is empty".to_string()),
+                               line: line.to_string(),
+                               line_number: line_number,
+                           });
+            }
+            trimmed
+        }
+    };
+    match line_type.to_lowercase().as_ref() {
+            "a" => parse_attribute(line_value),
+            "b" => parse_bandwidth(line_value),
+            "c" => parse_connection(line_value),
+            "e" => parse_email(line_value),
+            "i" => parse_information(line_value),
+            "k" => parse_key(line_value),
+            "m" => parse_media(line_value),
+            "o" => parse_origin(line_value),
+            "p" => parse_phone(line_value),
+            "r" => parse_repeat(line_value),
+            "s" => parse_session(line_value),
+            "t" => parse_timing(line_value),
+            "u" => parse_uri(line_value),
+            "v" => parse_version(line_value),
+            "z" => parse_zone(line_value),
+            _ => Err(SdpParserInternalError::Generic("unknown sdp type".to_string())),
+        }
+        .map(|sdp_type| {
+                 SdpLine {
+                     line_number,
+                     sdp_type,
+                 }
+             })
+        .map_err(|e| match e {
+                     SdpParserInternalError::Generic(..) |
+                     SdpParserInternalError::Integer(..) |
+                     SdpParserInternalError::Address(..) => {
+                         SdpParserError::Line {
+                             error: e,
+                             line: line.to_string(),
+                             line_number: line_number,
+                         }
+                     }
+                     SdpParserInternalError::Unsupported(..) => {
+                         SdpParserError::Unsupported {
+                             error: e,
+                             line: line.to_string(),
+                             line_number: line_number,
+                         }
+                     }
+                 })
+}
+
+#[test]
+fn test_parse_sdp_line_works() {
+    assert!(parse_sdp_line("v=0", 0).is_ok());
+    assert!(parse_sdp_line("s=somesession", 0).is_ok());
+}
+
+#[test]
+fn test_parse_sdp_line_empty_line() {
+    assert!(parse_sdp_line("", 0).is_err());
+}
+
+#[test]
+fn test_parse_sdp_line_unknown_key() {
+    assert!(parse_sdp_line("y=foobar", 0).is_err());
+}
+
+#[test]
+fn test_parse_sdp_line_too_long_type() {
+    assert!(parse_sdp_line("ab=foobar", 0).is_err());
+}
+
+#[test]
+fn test_parse_sdp_line_without_equal() {
+    assert!(parse_sdp_line("abcd", 0).is_err());
+    assert!(parse_sdp_line("ab cd", 0).is_err());
+}
+
+#[test]
+fn test_parse_sdp_line_empty_value() {
+    assert!(parse_sdp_line("v=", 0).is_err());
+    assert!(parse_sdp_line("o=", 0).is_err());
+    assert!(parse_sdp_line("s=", 0).is_err());
+}
+
+#[test]
+fn test_parse_sdp_line_empty_name() {
+    assert!(parse_sdp_line("=abc", 0).is_err());
+}
+
+#[test]
+fn test_parse_sdp_line_valid_a_line() {
+    assert!(parse_sdp_line("a=rtpmap:8 PCMA/8000", 0).is_ok());
+}
+
+#[test]
+fn test_parse_sdp_line_invalid_a_line() {
+    assert!(parse_sdp_line("a=rtpmap:200 PCMA/8000", 0).is_err());
+}
+
+fn sanity_check_sdp_session(session: &SdpSession) -> Result<(), SdpParserError> {
+    if !session.has_timing() {
+        return Err(SdpParserError::Sequence {
+                       message: "Missing timing type".to_string(),
+                       line_number: 0,
+                   });
+    }
+
+    if !session.has_media() {
+        return Err(SdpParserError::Sequence {
+                       message: "Missing media setion".to_string(),
+                       line_number: 0,
+                   });
+    }
+
+    // Check that extmaps are not defined on session and media level
+    if session.has_extmap_attribute() {
+        for msection in &session.media {
+            if msection.has_extmap_attribute() {
+                return Err(SdpParserError::Sequence {
+                               message: "Extmap can't be define at session and media level"
+                                   .to_string(),
+                               line_number: 0,
+                           });
+            }
+        }
+    }
+
+    Ok(())
+}
+
+#[cfg(test)]
+fn create_dummy_sdp_session() -> SdpSession {
+    let origin = parse_origin("mozilla 506705521068071134 0 IN IP4 0.0.0.0");
+    assert!(origin.is_ok());
+    let sdp_session;
+    if let SdpType::Origin(o) = origin.unwrap() {
+        sdp_session = SdpSession::new(0, o, "-".to_string());
+    } else {
+        panic!("SdpType is not Origin");
+    }
+    sdp_session
+}
+
+#[cfg(test)]
+use media_type::create_dummy_media_section;
+
+#[test]
+fn test_sanity_check_sdp_session_timing() {
+    let mut sdp_session = create_dummy_sdp_session();
+    sdp_session.extend_media(vec![create_dummy_media_section()]);
+
+    assert!(sanity_check_sdp_session(&sdp_session).is_err());
+
+    let t = SdpTiming { start: 0, stop: 0 };
+    sdp_session.set_timing(&t);
+
+    assert!(sanity_check_sdp_session(&sdp_session).is_ok());
+}
+
+#[test]
+fn test_sanity_check_sdp_session_media() {
+    let mut sdp_session = create_dummy_sdp_session();
+    let t = SdpTiming { start: 0, stop: 0 };
+    sdp_session.set_timing(&t);
+
+    assert!(sanity_check_sdp_session(&sdp_session).is_err());
+
+    sdp_session.extend_media(vec![create_dummy_media_section()]);
+
+    assert!(sanity_check_sdp_session(&sdp_session).is_ok());
+}
+
+#[test]
+fn test_sanity_check_sdp_session_extmap() {
+    let mut sdp_session = create_dummy_sdp_session();
+    let t = SdpTiming { start: 0, stop: 0 };
+    sdp_session.set_timing(&t);
+    sdp_session.extend_media(vec![create_dummy_media_section()]);
+
+    let attribute = parse_attribute("extmap:3 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time",);
+    assert!(attribute.is_ok());
+    let extmap;
+    if let SdpType::Attribute(a) = attribute.unwrap() {
+        extmap = a;
+    } else {
+        panic!("SdpType is not Attribute");
+    }
+    let ret = sdp_session.add_attribute(&extmap);
+    assert!(ret.is_ok());
+    assert!(sdp_session.has_extmap_attribute());
+
+    assert!(sanity_check_sdp_session(&sdp_session).is_ok());
+
+    let mattribute = parse_attribute("extmap:1/sendonly urn:ietf:params:rtp-hdrext:ssrc-audio-level",);
+    assert!(mattribute.is_ok());
+    let mextmap;
+    if let SdpType::Attribute(ma) = mattribute.unwrap() {
+        mextmap = ma;
+    } else {
+        panic!("SdpType is not Attribute");
+    }
+    let mut second_media = create_dummy_media_section();
+    assert!(second_media.add_attribute(&mextmap).is_ok());
+    assert!(second_media.has_extmap_attribute());
+
+    sdp_session.extend_media(vec![second_media]);
+    assert!(sdp_session.media.len() == 2);
+
+    assert!(sanity_check_sdp_session(&sdp_session).is_err());
+
+    sdp_session.attribute = Vec::new();
+
+    assert!(sanity_check_sdp_session(&sdp_session).is_ok());
+}
+
+#[test]
+fn test_sanity_check_sdp_session_simulcast() {
+    let mut sdp_session = create_dummy_sdp_session();
+    let t = SdpTiming { start: 0, stop: 0 };
+    sdp_session.set_timing(&t);
+    sdp_session.extend_media(vec![create_dummy_media_section()]);
+
+    assert!(sanity_check_sdp_session(&sdp_session).is_ok());
+}
+
+// TODO add unit tests
+fn parse_sdp_vector(lines: &[SdpLine]) -> Result<SdpSession, SdpParserError> {
+    if lines.len() < 5 {
+        return Err(SdpParserError::Sequence {
+                       message: "SDP neeeds at least 5 lines".to_string(),
+                       line_number: 0,
+                   });
+    }
+
+    // TODO are these mataches really the only way to verify the types?
+    let version: u64 = match lines[0].sdp_type {
+        SdpType::Version(v) => v,
+        _ => {
+            return Err(SdpParserError::Sequence {
+                           message: "first line needs to be version number".to_string(),
+                           line_number: lines[0].line_number,
+                       })
+        }
+    };
+    let origin: SdpOrigin = match lines[1].sdp_type {
+        SdpType::Origin(ref v) => v.clone(),
+        _ => {
+            return Err(SdpParserError::Sequence {
+                           message: "second line needs to be origin".to_string(),
+                           line_number: lines[1].line_number,
+                       })
+        }
+    };
+    let session: String = match lines[2].sdp_type {
+        SdpType::Session(ref v) => v.clone(),
+        _ => {
+            return Err(SdpParserError::Sequence {
+                           message: "third line needs to be session".to_string(),
+                           line_number: lines[2].line_number,
+                       })
+        }
+    };
+    let mut sdp_session = SdpSession::new(version, origin, session);
+    for (index, line) in lines.iter().enumerate().skip(3) {
+        match line.sdp_type {
+            SdpType::Attribute(ref a) => {
+                sdp_session
+                    .add_attribute(a)
+                    .map_err(|e: SdpParserInternalError| {
+                                 SdpParserError::Sequence {
+                                     message: format!("{}", e),
+                                     line_number: line.line_number,
+                                 }
+                             })?
+            }
+            SdpType::Bandwidth(ref b) => sdp_session.add_bandwidth(b),
+            SdpType::Timing(ref t) => sdp_session.set_timing(t),
+            SdpType::Connection(ref c) => sdp_session.set_connection(c),
+            SdpType::Media(_) => sdp_session.extend_media(parse_media_vector(&lines[index..])?),
+            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,
+                           })
+            }
+            // the line parsers throw unsupported errors for these already
+            SdpType::Email(_) |
+            SdpType::Information(_) |
+            SdpType::Key(_) |
+            SdpType::Phone(_) |
+            SdpType::Repeat(_) |
+            SdpType::Uri(_) |
+            SdpType::Zone(_) => (),
+        };
+        if sdp_session.has_media() {
+            break;
+        };
+    }
+    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,
+                   });
+    }
+    if sdp.len() < 62 {
+        return Err(SdpParserError::Line {
+                       error: SdpParserInternalError::Generic("string to 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(stripped_line, line_number) {
+            Ok(n) => {
+                sdp_lines.push(n);
+            }
+            Err(e) => {
+                match e {
+                    // FIXME 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,
+                                    })
+                    }
+                }
+            }
+        };
+    }
+    for warning in warnings {
+        if fail_on_warning {
+            return Err(warning);
+        } else {
+            println!("Warning: {}", warning);
+        };
+    }
+    // We just return the last of the errors here
+    if let Some(e) = errors.pop() {
+        return Err(e);
+    };
+    let session = parse_sdp_vector(&sdp_lines)?;
+    Ok(session)
+}
+
+#[test]
+fn test_parse_sdp_zero_length_string_fails() {
+    assert!(parse_sdp("", true).is_err());
+}
+
+#[test]
+fn test_parse_sdp_to_short_string() {
+    assert!(parse_sdp("fooooobarrrr", true).is_err());
+}
+
+#[test]
+fn test_parse_sdp_line_error() {
+    assert!(parse_sdp("v=0\r\n
+o=- 0 0 IN IP4 0.0.0.0\r\n
+s=-\r\n
+t=0 foobar\r\n
+m=audio 0 UDP/TLS/RTP/SAVPF 0\r\n",
+                      true)
+                    .is_err());
+}
+
+#[test]
+fn test_parse_sdp_unsupported_error() {
+    assert!(parse_sdp("v=0\r\n
+o=- 0 0 IN IP4 0.0.0.0\r\n
+s=-\r\n
+t=0 0\r\n
+m=foobar 0 UDP/TLS/RTP/SAVPF 0\r\n",
+                      true)
+                    .is_err());
+}
+
+#[test]
+fn test_parse_sdp_unsupported_warning() {
+    assert!(parse_sdp("v=0\r\n
+o=- 0 0 IN IP4 0.0.0.0\r\n
+s=-\r\n
+t=0 0\r\n
+m=audio 0 UDP/TLS/RTP/SAVPF 0\r\n
+a=unsupported\r\n",
+                      false)
+                    .is_ok());
+}
+
+#[test]
+fn test_parse_sdp_sequence_error() {
+    assert!(parse_sdp("v=0\r\n
+o=- 0 0 IN IP4 0.0.0.0\r\n
+t=0 0\r\n
+s=-\r\n
+m=audio 0 UDP/TLS/RTP/SAVPF 0\r\n",
+                      true)
+                    .is_err());
+}
+
+#[test]
+fn test_parse_sdp_integer_error() {
+    assert!(parse_sdp("v=0\r\n
+o=- 0 0 IN IP4 0.0.0.0\r\n
+s=-\r\n
+t=0 0\r\n
+m=audio 0 UDP/TLS/RTP/SAVPF 0\r\n
+a=rtcp:34er21\r\n",
+                      true)
+                    .is_err());
+}
+
+#[test]
+fn test_parse_sdp_ipaddr_error() {
+    assert!(parse_sdp("v=0\r\n
+o=- 0 0 IN IP4 0.a.b.0\r\n
+s=-\r\n
+t=0 0\r\n
+m=audio 0 UDP/TLS/RTP/SAVPF 0\r\n",
+                      true)
+                    .is_err());
+}
+
+#[test]
+fn test_parse_sdp_invalid_session_attribute() {
+    assert!(parse_sdp("v=0\r\n
+o=- 0 0 IN IP4 0.a.b.0\r\n
+s=-\r\n
+t=0 0\r\n
+a=bundle-only\r\n
+m=audio 0 UDP/TLS/RTP/SAVPF 0\r\n",
+                      true)
+                    .is_err());
+}
+
+#[test]
+fn test_parse_sdp_invalid_media_attribute() {
+    assert!(parse_sdp("v=0\r\n
+o=- 0 0 IN IP4 0.a.b.0\r\n
+s=-\r\n
+t=0 0\r\n
+m=audio 0 UDP/TLS/RTP/SAVPF 0\r\n
+a=ice-lite\r\n",
+                      true)
+                    .is_err());
+}
new file mode 100644
--- /dev/null
+++ b/media/webrtc/signaling/src/sdp/rsdparsa/src/media_type.rs
@@ -0,0 +1,414 @@
+use std::fmt;
+use {SdpType, SdpLine, SdpBandwidth, SdpConnection};
+use attribute_type::SdpAttribute;
+use error::{SdpParserError, SdpParserInternalError};
+
+#[derive(Clone)]
+pub struct SdpMediaLine {
+    pub media: SdpMediaValue,
+    pub port: u32,
+    pub port_count: u32,
+    pub proto: SdpProtocolValue,
+    pub formats: SdpFormatList,
+}
+
+#[derive(Clone,Debug,PartialEq)]
+pub enum SdpMediaValue {
+    Audio,
+    Video,
+    Application,
+}
+
+impl fmt::Display for SdpMediaValue {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        let printable = match *self {
+            SdpMediaValue::Audio => "Audio",
+            SdpMediaValue::Video => "Video",
+            SdpMediaValue::Application => "Application",
+        };
+        write!(f, "{}", printable)
+    }
+}
+
+#[derive(Clone,Debug,PartialEq)]
+pub enum SdpProtocolValue {
+    RtpSavpf,
+    UdpTlsRtpSavpf,
+    TcpTlsRtpSavpf,
+    DtlsSctp,
+    UdpDtlsSctp,
+    TcpDtlsSctp,
+}
+
+impl fmt::Display for SdpProtocolValue {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        let printable = match *self {
+            SdpProtocolValue::RtpSavpf => "Rtp/Savpf",
+            SdpProtocolValue::UdpTlsRtpSavpf => "Udp/Tls/Rtp/Savpf",
+            SdpProtocolValue::TcpTlsRtpSavpf => "Tcp/Tls/Rtp/Savpf",
+            SdpProtocolValue::DtlsSctp => "Dtls/Sctp",
+            SdpProtocolValue::UdpDtlsSctp => "Udp/Dtls/Sctp",
+            SdpProtocolValue::TcpDtlsSctp => "Tcp/Dtls/Sctp",
+        };
+        write!(f, "{}", printable)
+    }
+}
+
+#[derive(Clone)]
+pub enum SdpFormatList {
+    Integers(Vec<u32>),
+    Strings(Vec<String>),
+}
+
+impl fmt::Display for SdpFormatList {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        match *self {
+            SdpFormatList::Integers(ref x) => write!(f, "{:?}", x),
+            SdpFormatList::Strings(ref x) => write!(f, "{:?}", x),
+        }
+    }
+}
+
+pub struct SdpMedia {
+    media: SdpMediaLine,
+    connection: Option<SdpConnection>,
+    bandwidth: Vec<SdpBandwidth>,
+    attribute: Vec<SdpAttribute>,
+    // unsupported values:
+    // information: Option<String>,
+    // key: Option<String>,
+}
+
+impl SdpMedia {
+    pub fn new(media: SdpMediaLine) -> SdpMedia {
+        SdpMedia {
+            media,
+            connection: None,
+            bandwidth: Vec::new(),
+            attribute: Vec::new(),
+        }
+    }
+
+    pub fn get_type(&self) -> &SdpMediaValue {
+        &self.media.media
+    }
+
+    pub fn get_port(&self) -> u32 {
+        self.media.port
+    }
+
+    pub fn get_port_count(&self) -> u32 {
+        self.media.port_count
+    }
+
+    pub fn get_proto(&self) -> &SdpProtocolValue {
+        &self.media.proto
+    }
+
+    pub fn get_formats(&self) -> &SdpFormatList {
+        &self.media.formats
+    }
+
+    pub fn has_bandwidth(&self) -> bool {
+        !self.bandwidth.is_empty()
+    }
+
+    pub fn get_bandwidth(&self) -> &Vec<SdpBandwidth> {
+        &self.bandwidth
+    }
+
+    pub fn add_bandwidth(&mut self, bw: &SdpBandwidth) {
+        self.bandwidth.push(bw.clone())
+    }
+
+    pub fn has_attributes(&self) -> bool {
+        !self.attribute.is_empty()
+    }
+
+    pub fn get_attributes(&self) -> &Vec<SdpAttribute> {
+        &self.attribute
+    }
+
+    pub fn add_attribute(&mut self, attr: &SdpAttribute) -> Result<(), SdpParserInternalError> {
+        if !attr.allowed_at_media_level() {
+            return Err(SdpParserInternalError::Generic(format!("{} not allowed at media level",
+                                                               attr)));
+        }
+        Ok(self.attribute.push(attr.clone()))
+    }
+
+    // FIXME this is a temporary hack until we re-oranize the SdpAttribute enum
+    // so that we can build a generic has_attribute(X) function
+    pub fn has_extmap_attribute(&self) -> bool {
+        for attribute in &self.attribute {
+            if let &SdpAttribute::Extmap(_) = attribute {
+                return true;
+            }
+        }
+        false
+    }
+
+    pub fn has_connection(&self) -> bool {
+        self.connection.is_some()
+    }
+
+    pub fn get_connection(&self) -> &Option<SdpConnection> {
+        &self.connection
+    }
+
+    pub fn set_connection(&mut self, c: &SdpConnection) -> Result<(), SdpParserInternalError> {
+        if self.connection.is_some() {
+            return Err(SdpParserInternalError::Generic("connection type already exists at this media level"
+                               .to_string(),
+                       ));
+        }
+        Ok(self.connection = Some(c.clone()))
+    }
+}
+
+#[cfg(test)]
+pub fn create_dummy_media_section() -> SdpMedia {
+    let media_line = SdpMediaLine {
+        media: SdpMediaValue::Audio,
+        port: 9,
+        port_count: 0,
+        proto: SdpProtocolValue::RtpSavpf,
+        formats: SdpFormatList::Integers(Vec::new()),
+    };
+    SdpMedia::new(media_line)
+}
+
+fn parse_media_token(value: &str) -> Result<SdpMediaValue, SdpParserInternalError> {
+    Ok(match value.to_lowercase().as_ref() {
+           "audio" => SdpMediaValue::Audio,
+           "video" => SdpMediaValue::Video,
+           "application" => SdpMediaValue::Application,
+           _ => {
+               return Err(SdpParserInternalError::Unsupported(format!("unsupported media value: {}",
+                                                                      value)))
+           }
+       })
+}
+
+#[test]
+fn test_parse_media_token() {
+    let audio = parse_media_token("audio");
+    assert!(audio.is_ok());
+    assert_eq!(audio.unwrap(), SdpMediaValue::Audio);
+    let video = parse_media_token("VIDEO");
+    assert!(video.is_ok());
+    assert_eq!(video.unwrap(), SdpMediaValue::Video);
+    let app = parse_media_token("aPplIcatIOn");
+    assert!(app.is_ok());
+    assert_eq!(app.unwrap(), SdpMediaValue::Application);
+
+    assert!(parse_media_token("").is_err());
+    assert!(parse_media_token("foobar").is_err());
+}
+
+
+fn parse_protocol_token(value: &str) -> Result<SdpProtocolValue, SdpParserInternalError> {
+    Ok(match value.to_uppercase().as_ref() {
+           "RTP/SAVPF" => SdpProtocolValue::RtpSavpf,
+           "UDP/TLS/RTP/SAVPF" => SdpProtocolValue::UdpTlsRtpSavpf,
+           "TCP/TLS/RTP/SAVPF" => SdpProtocolValue::TcpTlsRtpSavpf,
+           "DTLS/SCTP" => SdpProtocolValue::DtlsSctp,
+           "UDP/DTLS/SCTP" => SdpProtocolValue::UdpDtlsSctp,
+           "TCP/DTLS/SCTP" => SdpProtocolValue::TcpDtlsSctp,
+           _ => {
+               return Err(SdpParserInternalError::Unsupported(format!("unsupported protocol value: {}",
+                                                                      value)))
+           }
+       })
+}
+
+#[test]
+fn test_parse_protocol_token() {
+    let rtps = parse_protocol_token("rtp/savpf");
+    assert!(rtps.is_ok());
+    assert_eq!(rtps.unwrap(), SdpProtocolValue::RtpSavpf);
+    let udps = parse_protocol_token("udp/tls/rtp/savpf");
+    assert!(udps.is_ok());
+    assert_eq!(udps.unwrap(), SdpProtocolValue::UdpTlsRtpSavpf);
+    let tcps = parse_protocol_token("TCP/tls/rtp/savpf");
+    assert!(tcps.is_ok());
+    assert_eq!(tcps.unwrap(), SdpProtocolValue::TcpTlsRtpSavpf);
+    let dtls = parse_protocol_token("dtLs/ScTP");
+    assert!(dtls.is_ok());
+    assert_eq!(dtls.unwrap(), SdpProtocolValue::DtlsSctp);
+    let usctp = parse_protocol_token("udp/DTLS/sctp");
+    assert!(usctp.is_ok());
+    assert_eq!(usctp.unwrap(), SdpProtocolValue::UdpDtlsSctp);
+    let tsctp = parse_protocol_token("tcp/dtls/SCTP");
+    assert!(tsctp.is_ok());
+    assert_eq!(tsctp.unwrap(), SdpProtocolValue::TcpDtlsSctp);
+
+    assert!(parse_protocol_token("").is_err());
+    assert!(parse_protocol_token("foobar").is_err());
+}
+
+pub fn parse_media(value: &str) -> Result<SdpType, SdpParserInternalError> {
+    let mv: Vec<&str> = value.split_whitespace().collect();
+    if mv.len() < 4 {
+        return Err(SdpParserInternalError::Generic("media attribute must have at least four tokens"
+                                                       .to_string()));
+    }
+    let media = parse_media_token(mv[0])?;
+    let mut ptokens = mv[1].split('/');
+    let port = match ptokens.next() {
+        None => return Err(SdpParserInternalError::Generic("missing port token".to_string())),
+        Some(p) => p.parse::<u32>()?,
+    };
+    if port > 65535 {
+        return Err(SdpParserInternalError::Generic("media port token is too big".to_string()));
+    }
+    let port_count = match ptokens.next() {
+        None => 0,
+        Some(c) => c.parse::<u32>()?,
+    };
+    let proto = parse_protocol_token(mv[2])?;
+    let fmt_slice: &[&str] = &mv[3..];
+    let formats = match media {
+        SdpMediaValue::Audio | SdpMediaValue::Video => {
+            let mut fmt_vec: Vec<u32> = vec![];
+            for num in fmt_slice {
+                let fmt_num = num.parse::<u32>()?;
+                match fmt_num {
+                    0  |  // PCMU
+                    8  |  // PCMA
+                    9  |  // G722
+                    13 |  // Comfort Noise
+                    96 ... 127 => (),  // dynamic range
+                    _ => return Err(SdpParserInternalError::Generic(
+                          "format number in media line is out of range".to_string()))
+                };
+                fmt_vec.push(fmt_num);
+            }
+            SdpFormatList::Integers(fmt_vec)
+        }
+        SdpMediaValue::Application => {
+            let mut fmt_vec: Vec<String> = vec![];
+            // TODO enforce length == 1 and content 'webrtc-datachannel' only?
+            for token in fmt_slice {
+                fmt_vec.push(String::from(*token));
+            }
+            SdpFormatList::Strings(fmt_vec)
+        }
+    };
+    let m = SdpMediaLine {
+        media,
+        port,
+        port_count,
+        proto,
+        formats,
+    };
+    println!("media: {}, {}, {}, {}", m.media, m.port, m.proto, m.formats);
+    Ok(SdpType::Media(m))
+}
+
+#[test]
+fn test_media_works() {
+    assert!(parse_media("audio 9 UDP/TLS/RTP/SAVPF 109").is_ok());
+    assert!(parse_media("video 9 UDP/TLS/RTP/SAVPF 126").is_ok());
+    assert!(parse_media("application 9 DTLS/SCTP 5000").is_ok());
+    assert!(parse_media("application 9 UDP/DTLS/SCTP webrtc-datachannel").is_ok());
+
+    assert!(parse_media("audio 9 UDP/TLS/RTP/SAVPF 109 9 0 8").is_ok());
+    assert!(parse_media("audio 0 UDP/TLS/RTP/SAVPF 8").is_ok());
+    assert!(parse_media("audio 9/2 UDP/TLS/RTP/SAVPF 8").is_ok());
+}
+
+#[test]
+fn test_media_missing_token() {
+    assert!(parse_media("video 9 UDP/TLS/RTP/SAVPF").is_err());
+}
+
+#[test]
+fn test_media_invalid_port_number() {
+    assert!(parse_media("video 75123 UDP/TLS/RTP/SAVPF 8").is_err());
+}
+
+#[test]
+fn test_media_invalid_type() {
+    assert!(parse_media("invalid 9 UDP/TLS/RTP/SAVPF 8").is_err());
+}
+
+#[test]
+fn test_media_invalid_port() {
+    assert!(parse_media("audio / UDP/TLS/RTP/SAVPF 8").is_err());
+}
+
+#[test]
+fn test_media_invalid_transport() {
+    assert!(parse_media("audio 9 invalid/invalid 8").is_err());
+}
+
+#[test]
+fn test_media_invalid_payload() {
+    assert!(parse_media("audio 9 UDP/TLS/RTP/SAVPF 300").is_err());
+}
+
+pub fn parse_media_vector(lines: &[SdpLine]) -> Result<Vec<SdpMedia>, SdpParserError> {
+    let mut media_sections: Vec<SdpMedia> = Vec::new();
+    let mut sdp_media = match lines[0].sdp_type {
+        SdpType::Media(ref v) => SdpMedia::new(v.clone()),
+        _ => {
+            return Err(SdpParserError::Sequence {
+                           message: "first line in media section needs to be a media line"
+                               .to_string(),
+                           line_number: lines[0].line_number,
+                       })
+        }
+    };
+    for line in lines.iter().skip(1) {
+        match line.sdp_type {
+            SdpType::Connection(ref c) => {
+                sdp_media
+                    .set_connection(c)
+                    .map_err(|e: SdpParserInternalError| {
+                                 SdpParserError::Sequence {
+                                     message: format!("{}", e),
+                                     line_number: line.line_number,
+                                 }
+                             })?
+            }
+            SdpType::Bandwidth(ref b) => sdp_media.add_bandwidth(b),
+            SdpType::Attribute(ref a) => {
+                sdp_media
+                    .add_attribute(a)
+                    .map_err(|e: SdpParserInternalError| {
+                                 SdpParserError::Sequence {
+                                     message: format!("{}", e),
+                                     line_number: line.line_number,
+                                 }
+                             })?
+            }
+            SdpType::Media(ref v) => {
+                media_sections.push(sdp_media);
+                sdp_media = SdpMedia::new(v.clone());
+            }
+
+            SdpType::Email(_) |
+            SdpType::Phone(_) |
+            SdpType::Origin(_) |
+            SdpType::Repeat(_) |
+            SdpType::Session(_) |
+            SdpType::Timing(_) |
+            SdpType::Uri(_) |
+            SdpType::Version(_) |
+            SdpType::Zone(_) => {
+                return Err(SdpParserError::Sequence {
+                               message: "invalid type in media section".to_string(),
+                               line_number: line.line_number,
+                           })
+            }
+
+            // the line parsers throw unsupported errors for these already
+            SdpType::Information(_) |
+            SdpType::Key(_) => (),
+        };
+    }
+    media_sections.push(sdp_media);
+    Ok(media_sections)
+}
+// TODO add unit tests for parse_media_vector
new file mode 100644
--- /dev/null
+++ b/media/webrtc/signaling/src/sdp/rsdparsa/src/network.rs
@@ -0,0 +1,80 @@
+use std::str::FromStr;
+use std::fmt;
+use std::net::IpAddr;
+
+use error::SdpParserInternalError;
+
+#[derive(Clone,Copy,Debug,PartialEq)]
+pub enum SdpAddrType {
+    IP4 = 4,
+    IP6 = 6,
+}
+
+impl SdpAddrType {
+    pub fn same_protocol(&self, addr: &IpAddr) -> bool {
+        (addr.is_ipv6() && *self == SdpAddrType::IP6) ||
+        (addr.is_ipv4() && *self == SdpAddrType::IP4)
+    }
+}
+
+impl fmt::Display for SdpAddrType {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        let printable = match *self {
+            SdpAddrType::IP4 => "Ip4",
+            SdpAddrType::IP6 => "Ip6",
+        };
+        write!(f, "{}", printable)
+    }
+}
+
+pub fn parse_nettype(value: &str) -> Result<(), SdpParserInternalError> {
+    if value.to_uppercase() != "IN" {
+        return Err(SdpParserInternalError::Generic("nettype needs to be IN".to_string()));
+    };
+    Ok(())
+}
+
+#[test]
+fn test_parse_nettype() {
+    let internet = parse_nettype("iN");
+    assert!(internet.is_ok());
+
+    assert!(parse_nettype("").is_err());
+    assert!(parse_nettype("FOO").is_err());
+}
+
+pub fn parse_addrtype(value: &str) -> Result<SdpAddrType, SdpParserInternalError> {
+    Ok(match value.to_uppercase().as_ref() {
+           "IP4" => SdpAddrType::IP4,
+           "IP6" => SdpAddrType::IP6,
+           _ => {
+               return Err(SdpParserInternalError::Generic("address type needs to be IP4 or IP6"
+                                                              .to_string()))
+           }
+       })
+}
+
+#[test]
+fn test_parse_addrtype() {
+    let ip4 = parse_addrtype("iP4");
+    assert!(ip4.is_ok());
+    assert_eq!(ip4.unwrap(), SdpAddrType::IP4);
+    let ip6 = parse_addrtype("Ip6");
+    assert!(ip6.is_ok());
+    assert_eq!(ip6.unwrap(), SdpAddrType::IP6);
+
+    assert!(parse_addrtype("").is_err());
+    assert!(parse_addrtype("IP5").is_err());
+}
+
+pub fn parse_unicast_addr(value: &str) -> Result<IpAddr, SdpParserInternalError> {
+    Ok(IpAddr::from_str(value)?)
+}
+
+#[test]
+fn test_parse_unicast_addr() {
+    let ip4 = parse_unicast_addr("127.0.0.1");
+    assert!(ip4.is_ok());
+    let ip6 = parse_unicast_addr("::1");
+    assert!(ip6.is_ok());
+}
new file mode 100644
--- /dev/null
+++ b/media/webrtc/signaling/src/sdp/rsdparsa/src/unsupported_types.rs
@@ -0,0 +1,74 @@
+use error::SdpParserInternalError;
+use SdpType;
+
+pub fn parse_repeat(value: &str) -> Result<SdpType, SdpParserInternalError> {
+    // TODO implement this if it's ever needed
+    Err(SdpParserInternalError::Unsupported(format!("unsupported type repeat: {} ", value)))
+}
+
+#[test]
+fn test_repeat_works() {
+    // FIXME use a proper r value here
+    assert!(parse_repeat("0 0").is_err());
+}
+
+pub fn parse_zone(value: &str) -> Result<SdpType, SdpParserInternalError> {
+    // TODO implement this if it's ever needed
+    Err(SdpParserInternalError::Unsupported(format!("unsupported type zone: {}", value)))
+}
+
+#[test]
+fn test_zone_works() {
+    // FIXME use a proper z value here
+    assert!(parse_zone("0 0").is_err());
+}
+
+pub fn parse_key(value: &str) -> Result<SdpType, SdpParserInternalError> {
+    // TODO implement this if it's ever needed
+    Err(SdpParserInternalError::Unsupported(format!("unsupported type key: {}", value)))
+}
+
+#[test]
+fn test_keys_works() {
+    // FIXME use a proper k value here
+    assert!(parse_key("12345").is_err());
+}
+
+pub fn parse_information(value: &str) -> Result<SdpType, SdpParserInternalError> {
+    Err(SdpParserInternalError::Unsupported(format!("unsupported type information: {}", value)))
+}
+
+#[test]
+fn test_information_works() {
+    assert!(parse_information("foobar").is_err());
+}
+
+pub fn parse_uri(value: &str) -> Result<SdpType, SdpParserInternalError> {
+    // TODO check if this is really a URI
+    Err(SdpParserInternalError::Unsupported(format!("unsupported type uri: {}", value)))
+}
+
+#[test]
+fn test_uri_works() {
+    assert!(parse_uri("http://www.mozilla.org").is_err());
+}
+
+pub fn parse_email(value: &str) -> Result<SdpType, SdpParserInternalError> {
+    // TODO check if this is really an email address
+    Err(SdpParserInternalError::Unsupported(format!("unsupported type email: {}", value)))
+}
+
+#[test]
+fn test_email_works() {
+    assert!(parse_email("nils@mozilla.com").is_err());
+}
+
+pub fn parse_phone(value: &str) -> Result<SdpType, SdpParserInternalError> {
+    // TODO check if this is really a phone number
+    Err(SdpParserInternalError::Unsupported(format!("unsupported type phone: {}", value)))
+}
+
+#[test]
+fn test_phone_works() {
+    assert!(parse_phone("+123456789").is_err());
+}
new file mode 100644
--- /dev/null
+++ b/media/webrtc/signaling/src/sdp/rsdparsa/tests/unit_tests.rs
@@ -0,0 +1,423 @@
+extern crate rsdparsa;
+
+#[test]
+fn parse_minimal_sdp() {
+    let sdp = "v=0\r\n
+o=- 0 0 IN IP4 0.0.0.0\r\n
+s=-\r\n
+t=0 0\r\n
+m=audio 0 UDP/TLS/RTP/SAVPF 0\r\n";
+    let sdp_res = rsdparsa::parse_sdp(sdp, true);
+    assert!(sdp_res.is_ok());
+    let sdp_opt = sdp_res.ok();
+    assert!(sdp_opt.is_some());
+    let sdp = sdp_opt.unwrap();
+    assert_eq!(sdp.version, 0);
+    assert_eq!(sdp.session, "-");
+    assert!(sdp.connection.is_none());
+    assert_eq!(sdp.attribute.len(), 0);
+    assert_eq!(sdp.media.len(), 1);
+
+    let msection = &(sdp.media[0]);
+    assert_eq!(*msection.get_type(),
+               rsdparsa::media_type::SdpMediaValue::Audio);
+    assert_eq!(msection.get_port(), 0);
+    assert_eq!(*msection.get_proto(),
+               rsdparsa::media_type::SdpProtocolValue::UdpTlsRtpSavpf);
+    assert!(!msection.has_attributes());
+    assert!(!msection.has_bandwidth());
+    assert!(!msection.has_connection());
+    assert!(msection.get_connection().is_none());
+}
+
+#[test]
+fn parse_minimal_sdp_with_emtpy_lines() {
+    let sdp = "v=0\r\n
+\r\n
+o=- 0 0 IN IP4 0.0.0.0\r\n
+ \r\n
+s=-\r\n
+t=0 0\r\n
+m=audio 0 UDP/TLS/RTP/SAVPF 0\r\n";
+    let sdp_res = rsdparsa::parse_sdp(sdp, false);
+    assert!(sdp_res.is_ok());
+    let sdp_opt = sdp_res.ok();
+    assert!(sdp_opt.is_some());
+    let sdp = sdp_opt.unwrap();
+    assert_eq!(sdp.version, 0);
+    assert_eq!(sdp.session, "-");
+}
+
+#[test]
+fn parse_minimal_sdp_with_most_session_types() {
+    let sdp = "v=0\r\n
+o=- 0 0 IN IP4 0.0.0.0\r\n
+s=-\r\n
+t=0 0\r\n
+b=AS:1\r\n
+b=CT:123\r\n
+b=TIAS:12345\r\n
+c=IN IP4 0.0.0.0\r\n
+a=ice-options:trickle\r\n
+m=audio 0 UDP/TLS/RTP/SAVPF 0\r\n";
+    let sdp_res = rsdparsa::parse_sdp(sdp, false);
+    assert!(sdp_res.is_ok());
+    let sdp_opt = sdp_res.ok();
+    assert!(sdp_opt.is_some());
+    let sdp = sdp_opt.unwrap();
+    assert_eq!(sdp.version, 0);
+    assert_eq!(sdp.session, "-");
+    assert!(sdp.get_connection().is_some());
+}
+
+#[test]
+fn parse_firefox_audio_offer() {
+    let sdp = "v=0\r\n
+o=mozilla...THIS_IS_SDPARTA-52.0a1 506705521068071134 0 IN IP4 0.0.0.0\r\n
+s=-\r\n
+t=0 0\r\n
+a=fingerprint:sha-256 CD:34:D1:62:16:95:7B:B7:EB:74:E2:39:27:97:EB:0B:23:73:AC:BC:BF:2F:E3:91:CB:57:A9:9D:4A:A2:0B:40\r\n
+a=group:BUNDLE sdparta_0\r\n
+a=ice-options:trickle\r\n
+a=msid-semantic:WMS *\r\n
+m=audio 9 UDP/TLS/RTP/SAVPF 109 9 0 8\r\n
+c=IN IP4 0.0.0.0\r\n
+a=sendrecv\r\n
+a=extmap:1/sendonly urn:ietf:params:rtp-hdrext:ssrc-audio-level\r\n
+a=fmtp:109 maxplaybackrate=48000;stereo=1;useinbandfec=1\r\n
+a=ice-pwd:e3baa26dd2fa5030d881d385f1e36cce\r\n
+a=ice-ufrag:58b99ead\r\n
+a=mid:sdparta_0\r\n
+a=msid:{5a990edd-0568-ac40-8d97-310fc33f3411} {218cfa1c-617d-2249-9997-60929ce4c405}\r\n
+a=rtcp-mux\r\n
+a=rtpmap:109 opus/48000/2\r\n
+a=rtpmap:9 G722/8000/1\r\n
+a=rtpmap:0 PCMU/8000\r\n
+a=rtpmap:8 PCMA/8000\r\n
+a=setup:actpass\r\n
+a=ssrc:2655508255 cname:{735484ea-4f6c-f74a-bd66-7425f8476c2e}\r\n";
+    let sdp_res = rsdparsa::parse_sdp(sdp, true);
+    assert!(sdp_res.is_ok());
+    let sdp_opt = sdp_res.ok();
+    assert!(sdp_opt.is_some());
+    let sdp = sdp_opt.unwrap();
+    assert_eq!(sdp.version, 0);
+    assert_eq!(sdp.media.len(), 1);
+
+    let msection = &(sdp.media[0]);
+    assert_eq!(*msection.get_type(),
+               rsdparsa::media_type::SdpMediaValue::Audio);
+    assert_eq!(msection.get_port(), 9);
+    assert_eq!(*msection.get_proto(),
+               rsdparsa::media_type::SdpProtocolValue::UdpTlsRtpSavpf);
+    assert!(msection.has_attributes());
+    assert!(msection.has_connection());
+    assert!(msection.get_connection().is_some());
+    assert!(!msection.has_bandwidth());
+}
+
+#[test]
+fn parse_firefox_video_offer() {
+    let sdp = "v=0\r\n
+o=mozilla...THIS_IS_SDPARTA-52.0a1 506705521068071134 0 IN IP4 0.0.0.0\r\n
+s=-\r\n
+t=0 0\r\n
+a=fingerprint:sha-256 CD:34:D1:62:16:95:7B:B7:EB:74:E2:39:27:97:EB:0B:23:73:AC:BC:BF:2F:E3:91:CB:57:A9:9D:4A:A2:0B:40\r\n
+a=group:BUNDLE sdparta_2\r\n
+a=ice-options:trickle\r\n
+a=msid-semantic:WMS *\r\n
+m=video 9 UDP/TLS/RTP/SAVPF 126 120 97\r\n
+c=IN IP4 0.0.0.0\r\n
+a=recvonly\r\n
+a=fmtp:126 profile-level-id=42e01f;level-asymmetry-allowed=1;packetization-mode=1\r\n
+a=fmtp:120 max-fs=12288;max-fr=60\r\n
+a=fmtp:97 profile-level-id=42e01f;level-asymmetry-allowed=1\r\n
+a=ice-pwd:e3baa26dd2fa5030d881d385f1e36cce\r\n
+a=ice-ufrag:58b99ead\r\n
+a=mid:sdparta_2\r\n
+a=rtcp-fb:126 nack\r\n
+a=rtcp-fb:126 nack pli\r\n
+a=rtcp-fb:126 ccm fir\r\n
+a=rtcp-fb:126 goog-remb\r\n
+a=rtcp-fb:120 nack\r\n
+a=rtcp-fb:120 nack pli\r\n
+a=rtcp-fb:120 ccm fir\r\n
+a=rtcp-fb:120 goog-remb\r\n
+a=rtcp-fb:97 nack\r\n
+a=rtcp-fb:97 nack pli\r\n
+a=rtcp-fb:97 ccm fir\r\n
+a=rtcp-fb:97 goog-remb\r\n
+a=rtcp-mux\r\n
+a=rtpmap:126 H264/90000\r\n
+a=rtpmap:120 VP8/90000\r\n
+a=rtpmap:97 H264/90000\r\n
+a=setup:actpass\r\n
+a=ssrc:2709871439 cname:{735484ea-4f6c-f74a-bd66-7425f8476c2e}";
+    let sdp_res = rsdparsa::parse_sdp(sdp, true);
+    assert!(sdp_res.is_ok());
+    let sdp_opt = sdp_res.ok();
+    assert!(sdp_opt.is_some());
+    let sdp = sdp_opt.unwrap();
+    assert_eq!(sdp.version, 0);
+    assert_eq!(sdp.media.len(), 1);
+
+    let msection = &(sdp.media[0]);
+    assert_eq!(*msection.get_type(),
+               rsdparsa::media_type::SdpMediaValue::Video);
+    assert_eq!(msection.get_port(), 9);
+    assert_eq!(*msection.get_proto(),
+               rsdparsa::media_type::SdpProtocolValue::UdpTlsRtpSavpf);
+}
+
+#[test]
+fn parse_firefox_datachannel_offer() {
+    let sdp = "v=0\r\n
+o=mozilla...THIS_IS_SDPARTA-52.0a2 3327975756663609975 0 IN IP4 0.0.0.0\r\n
+s=-\r\n
+t=0 0\r\n
+a=sendrecv\r\n
+a=fingerprint:sha-256 AC:72:CB:D6:1E:A3:A3:B0:E7:97:77:25:03:4B:5B:FF:19:6C:02:C6:93:7D:EB:5C:81:6F:36:D9:02:32:F8:23\r\n
+a=ice-options:trickle\r\n
+a=msid-semantic:WMS *\r\n
+m=application 49760 DTLS/SCTP 5000\r\n
+c=IN IP4 172.16.156.106\r\n
+a=candidate:0 1 UDP 2122252543 172.16.156.106 49760 typ host\r\n
+a=sendrecv\r\n
+a=end-of-candidates\r\n
+a=ice-pwd:24f485c580129b36447b65df77429a82\r\n
+a=ice-ufrag:4cba30fe\r\n
+a=mid:sdparta_0\r\n
+a=sctpmap:5000 webrtc-datachannel 256\r\n
+a=setup:active\r\n
+a=ssrc:3376683177 cname:{62f78ee0-620f-a043-86ca-b69f189f1aea}\r\n";
+    let sdp_res = rsdparsa::parse_sdp(sdp, true);
+    assert!(sdp_res.is_ok());
+    let sdp_opt = sdp_res.ok();
+    assert!(sdp_opt.is_some());
+    let sdp = sdp_opt.unwrap();
+    assert_eq!(sdp.version, 0);
+    assert_eq!(sdp.media.len(), 1);
+
+    let msection = &(sdp.media[0]);
+    assert_eq!(*msection.get_type(),
+               rsdparsa::media_type::SdpMediaValue::Application);
+    assert_eq!(msection.get_port(), 49760);
+    assert_eq!(*msection.get_proto(),
+               rsdparsa::media_type::SdpProtocolValue::DtlsSctp);
+}
+
+#[test]
+fn parse_chrome_audio_video_offer() {
+    let sdp = "v=0\r\n
+o=- 3836772544440436510 2 IN IP4 127.0.0.1\r\n
+s=-\r\n
+t=0 0\r\n
+a=group:BUNDLE audio video\r\n
+a=msid-semantic: WMS HWpbmTmXleVSnlssQd80bPuw9cxQFroDkkBP\r\n
+m=audio 9 UDP/TLS/RTP/SAVPF 111 103 104 9 0 8 106 105 13 126\r\n
+c=IN IP4 0.0.0.0\r\n
+a=rtcp:9 IN IP4 0.0.0.0\r\n
+a=ice-ufrag:A4by\r\n
+a=ice-pwd:Gfvb2rbYMiW0dZz8ZkEsXICs\r\n
+a=fingerprint:sha-256 15:B0:92:1F:C7:40:EE:22:A6:AF:26:EF:EA:FF:37:1D:B3:EF:11:0B:8B:73:4F:01:7D:C9:AE:26:4F:87:E0:95\r\n
+a=setup:actpass\r\n
+a=mid:audio\r\n
+a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level\r\n
+a=sendrecv\r\n
+a=rtcp-mux\r\n
+a=rtpmap:111 opus/48000/2\r\n
+a=rtcp-fb:111 transport-cc\r\n
+a=fmtp:111 minptime=10;useinbandfec=1\r\n
+a=rtpmap:103 ISAC/16000\r\n
+a=rtpmap:104 ISAC/32000\r\n
+a=rtpmap:9 G722/8000\r\n
+a=rtpmap:0 PCMU/8000\r\n
+a=rtpmap:8 PCMA/8000\r\n
+a=rtpmap:106 CN/32000\r\n
+a=rtpmap:105 CN/16000\r\n
+a=rtpmap:13 CN/8000\r\n
+a=rtpmap:126 telephone-event/8000\r\n
+a=ssrc:162559313 cname:qPTZ+BI+42mgbOi+\r\n
+a=ssrc:162559313 msid:HWpbmTmXleVSnlssQd80bPuw9cxQFroDkkBP f6188af5-d8d6-462c-9c75-f12bc41fe322\r\n
+a=ssrc:162559313 mslabel:HWpbmTmXleVSnlssQd80bPuw9cxQFroDkkBP\r\n
+a=ssrc:162559313 label:f6188af5-d8d6-462c-9c75-f12bc41fe322\r\n
+m=video 9 UDP/TLS/RTP/SAVPF 100 101 107 116 117 96 97 99 98\r\n
+c=IN IP4 0.0.0.0\r\n
+a=rtcp:9 IN IP4 0.0.0.0\r\n
+a=ice-ufrag:A4by\r\n
+a=ice-pwd:Gfvb2rbYMiW0dZz8ZkEsXICs\r\n
+a=fingerprint:sha-256 15:B0:92:1F:C7:40:EE:22:A6:AF:26:EF:EA:FF:37:1D:B3:EF:11:0B:8B:73:4F:01:7D:C9:AE:26:4F:87:E0:95\r\n
+a=setup:actpass\r\n
+a=mid:video\r\n
+a=extmap:2 urn:ietf:params:rtp-hdrext:toffset\r\n
+a=extmap:3 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time\r\n
+a=extmap:4 urn:3gpp:video-orientation\r\n
+a=extmap:5 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01\r\n
+a=extmap:6 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay\r\n
+a=sendrecv\r\n
+a=rtcp-mux\r\n
+a=rtcp-rsize\r\n
+a=rtpmap:100 VP8/90000\r\n
+a=rtcp-fb:100 ccm fir\r\n
+a=rtcp-fb:100 nack\r\n
+a=rtcp-fb:100 nack pli\r\n
+a=rtcp-fb:100 goog-remb\r\n
+a=rtcp-fb:100 transport-cc\r\n
+a=rtpmap:101 VP9/90000\r\n
+a=rtcp-fb:101 ccm fir\r\n
+a=rtcp-fb:101 nack\r\n
+a=rtcp-fb:101 nack pli\r\n
+a=rtcp-fb:101 goog-remb\r\n
+a=rtcp-fb:101 transport-cc\r\n
+a=rtpmap:107 H264/90000\r\n
+a=rtcp-fb:107 ccm fir\r\n
+a=rtcp-fb:107 nack\r\n
+a=rtcp-fb:107 nack pli\r\n
+a=rtcp-fb:107 goog-remb\r\n
+a=rtcp-fb:107 transport-cc\r\n
+a=fmtp:107 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f\r\n
+a=rtpmap:116 red/90000\r\n
+a=rtpmap:117 ulpfec/90000\r\n
+a=rtpmap:96 rtx/90000\r\n
+a=fmtp:96 apt=100\r\n
+a=rtpmap:97 rtx/90000\r\n
+a=fmtp:97 apt=101\r\n
+a=rtpmap:99 rtx/90000\r\n
+a=fmtp:99 apt=107\r\n
+a=rtpmap:98 rtx/90000\r\n
+a=fmtp:98 apt=116\r\n
+a=ssrc-group:FID 3156517279 2673335628\r\n
+a=ssrc:3156517279 cname:qPTZ+BI+42mgbOi+\r\n
+a=ssrc:3156517279 msid:HWpbmTmXleVSnlssQd80bPuw9cxQFroDkkBP b6ec5178-c611-403f-bbec-3833ed547c09\r\n
+a=ssrc:3156517279 mslabel:HWpbmTmXleVSnlssQd80bPuw9cxQFroDkkBP\r\n
+a=ssrc:3156517279 label:b6ec5178-c611-403f-bbec-3833ed547c09\r\n
+a=ssrc:2673335628 cname:qPTZ+BI+42mgbOi+\r\n
+a=ssrc:2673335628 msid:HWpbmTmXleVSnlssQd80bPuw9cxQFroDkkBP b6ec5178-c611-403f-bbec-3833ed547c09\r\n
+a=ssrc:2673335628 mslabel:HWpbmTmXleVSnlssQd80bPuw9cxQFroDkkBP\r\n
+a=ssrc:2673335628 label:b6ec5178-c611-403f-bbec-3833ed547c09\r\n";
+    let sdp_res = rsdparsa::parse_sdp(sdp, true);
+    assert!(sdp_res.is_ok());
+    let sdp_opt = sdp_res.ok();
+    assert!(sdp_opt.is_some());
+    let sdp = sdp_opt.unwrap();
+    assert_eq!(sdp.version, 0);
+    assert_eq!(sdp.media.len(), 2);
+
+    let msection1 = &(sdp.media[0]);
+    assert_eq!(*msection1.get_type(),
+               rsdparsa::media_type::SdpMediaValue::Audio);
+    assert_eq!(msection1.get_port(), 9);
+    assert_eq!(*msection1.get_proto(),
+               rsdparsa::media_type::SdpProtocolValue::UdpTlsRtpSavpf);
+    assert!(msection1.has_attributes());
+    assert!(msection1.has_connection());
+    assert!(!msection1.has_bandwidth());
+
+    let msection2 = &(sdp.media[1]);
+    assert_eq!(*msection2.get_type(),
+               rsdparsa::media_type::SdpMediaValue::Video);
+    assert_eq!(msection2.get_port(), 9);
+    assert_eq!(*msection2.get_proto(),
+               rsdparsa::media_type::SdpProtocolValue::UdpTlsRtpSavpf);
+    assert!(msection2.has_attributes());
+    assert!(msection2.has_connection());
+    assert!(!msection2.has_bandwidth());
+}
+
+#[test]
+fn parse_firefox_simulcast_offer() {
+    let sdp = "v=0\r\n
+o=mozilla...THIS_IS_SDPARTA-55.0a1 983028567300715536 0 IN IP4 0.0.0.0\r\n
+s=-\r\n
+t=0 0\r\n
+a=fingerprint:sha-256 68:42:13:88:B6:C1:7D:18:79:07:8A:C6:DC:28:D6:DC:DD:E3:C9:41:E7:80:A7:FE:02:65:FB:76:A0:CD:58:ED\r\n
+a=ice-options:trickle\r\n
+a=msid-semantic:WMS *\r\n
+m=video 9 UDP/TLS/RTP/SAVPF 120 121 126 97\r\n
+c=IN IP4 0.0.0.0\r\n
+a=sendrecv\r\n
+a=extmap:1 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time\r\n
+a=extmap:2 urn:ietf:params:rtp-hdrext:toffset\r\n
+a=extmap:3/sendonly urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id\r\n
+a=fmtp:126 profile-level-id=42e01f;level-asymmetry-allowed=1;packetization-mode=1\r\n
+a=fmtp:97 profile-level-id=42e01f;level-asymmetry-allowed=1\r\n
+a=fmtp:120 max-fs=12288;max-fr=60\r\n
+a=fmtp:121 max-fs=12288;max-fr=60\r\n
+a=ice-pwd:4af388405d558b91f5ba6c2c48f161bf\r\n
+a=ice-ufrag:ce1ac488\r\n
+a=mid:sdparta_0\r\n
+a=msid:{fb6d1fa3-d993-f244-a0fe-d9fb99214c23} {8be9a0f7-9272-6c42-90f3-985d55bd8de5}\r\n
+a=rid:foo send\r\n
+a=rid:bar send\r\n
+a=rtcp-fb:120 nack\r\n
+a=rtcp-fb:120 nack pli\r\n
+a=rtcp-fb:120 ccm fir\r\n
+a=rtcp-fb:120 goog-remb\r\n
+a=rtcp-fb:121 nack\r\n
+a=rtcp-fb:121 nack pli\r\n
+a=rtcp-fb:121 ccm fir\r\n
+a=rtcp-fb:121 goog-remb\r\n
+a=rtcp-fb:126 nack\r\n
+a=rtcp-fb:126 nack pli\r\n
+a=rtcp-fb:126 ccm fir\r\n
+a=rtcp-fb:126 goog-remb\r\n
+a=rtcp-fb:97 nack\r\n
+a=rtcp-fb:97 nack pli\r\n
+a=rtcp-fb:97 ccm fir\r\n
+a=rtcp-fb:97 goog-remb\r\n
+a=rtcp-mux\r\n
+a=rtpmap:120 VP8/90000\r\n
+a=rtpmap:121 VP9/90000\r\n
+a=rtpmap:126 H264/90000\r\n
+a=rtpmap:97 H264/90000\r\n
+a=setup:actpass\r\n
+a=simulcast: send rid=foo;bar\r\n
+a=ssrc:2988475468 cname:{77067f00-2e8d-8b4c-8992-cfe338f56851}\r\n
+a=ssrc:1649784806 cname:{77067f00-2e8d-8b4c-8992-cfe338f56851}\r\n";
+    let sdp_res = rsdparsa::parse_sdp(sdp, true);
+    assert!(sdp_res.is_ok());
+    let sdp_opt = sdp_res.ok();
+    assert!(sdp_opt.is_some());
+    let sdp = sdp_opt.unwrap();
+    assert_eq!(sdp.version, 0);
+    assert_eq!(sdp.media.len(), 1);
+}
+
+#[test]
+fn parse_firefox_simulcast_answer() {
+    let sdp = "v=0\r\n
+o=mozilla...THIS_IS_SDPARTA-55.0a1 7548296603161351381 0 IN IP4 0.0.0.0\r\n
+s=-\r\n
+t=0 0\r\n
+a=fingerprint:sha-256 B1:47:49:4F:7D:83:03:BE:E9:FC:73:A3:FB:33:38:40:0B:3B:6A:56:78:EB:EE:D5:6D:2D:D5:3A:B6:13:97:E7\r\n
+a=ice-options:trickle\r\n
+a=msid-semantic:WMS *\r\n
+m=video 9 UDP/TLS/RTP/SAVPF 120\r\n
+c=IN IP4 0.0.0.0\r\n
+a=recvonly\r\n
+a=extmap:1 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time\r\n
+a=extmap:2 urn:ietf:params:rtp-hdrext:toffset\r\n
+a=fmtp:120 max-fs=12288;max-fr=60\r\n
+a=ice-pwd:c886e2caf2ae397446312930cd1afe51\r\n
+a=ice-ufrag:f57396c0\r\n
+a=mid:sdparta_0\r\n
+a=rtcp-fb:120 nack\r\n
+a=rtcp-fb:120 nack pli\r\n
+a=rtcp-fb:120 ccm fir\r\n
+a=rtcp-fb:120 goog-remb\r\n
+a=rtcp-mux\r\n
+a=rtpmap:120 VP8/90000\r\n
+a=setup:active\r\n
+a=ssrc:2564157021 cname:{cae1cd32-7433-5b48-8dc8-8e3f8b2f96cd}\r\n
+a=simulcast: recv rid=foo;bar\r\n
+a=rid:foo recv\r\n
+a=rid:bar recv\r\n
+a=extmap:3/recvonly urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id\r\n";
+    let sdp_res = rsdparsa::parse_sdp(sdp, true);
+    assert!(sdp_res.is_ok());
+    let sdp_opt = sdp_res.ok();
+    assert!(sdp_opt.is_some());
+    let sdp = sdp_opt.unwrap();
+    assert_eq!(sdp.version, 0);
+    assert_eq!(sdp.media.len(), 1);
+}