зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1379265 - Import rsdparsa; r=rillian
Imported from git commit 0fd5ac915a134d4ffd5dd5949247632a1b92f7bc. MozReview-Commit-ID: 8njzZT5YTTD --HG-- extra : rebase_source : d2475db7f095f7ea55644b84b28d14efea1ab1e5
This commit is contained in:
Родитель
785d1f4d18
Коммит
4dfdf9b22f
|
@ -0,0 +1,2 @@
|
|||
target
|
||||
Cargo.lock
|
|
@ -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
|
|
@ -0,0 +1,7 @@
|
|||
[package]
|
||||
name = "rsdparsa"
|
||||
version = "0.1.0"
|
||||
authors = ["Nils Ohlmeier <github@ohlmeier.org>"]
|
||||
|
||||
[features]
|
||||
default = []
|
|
@ -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.
|
|
@ -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/).
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -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();
|
||||
}
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
v=0
|
||||
o=- 4294967296 2 IN IP4 127.0.0.1
|
||||
s=SIP Call
|
||||
t=0 0
|
||||
a=ice-lite
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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" }'
|
|
@ -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());
|
||||
}
|
|
@ -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<u8>,
|
||||
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::<u8>()?);
|
||||
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());
|
||||
}
|
|
@ -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
|
|
@ -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());
|
||||
}
|
|
@ -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());
|
||||
}
|
|
@ -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);
|
||||
}
|
Загрузка…
Ссылка в новой задаче