diff options
Diffstat (limited to 'third_party/rust/webrtc-sdp')
57 files changed, 10172 insertions, 0 deletions
diff --git a/third_party/rust/webrtc-sdp/.cargo-checksum.json b/third_party/rust/webrtc-sdp/.cargo-checksum.json new file mode 100644 index 0000000000..da40a0a9d1 --- /dev/null +++ b/third_party/rust/webrtc-sdp/.cargo-checksum.json @@ -0,0 +1 @@ +{"files":{"CHANGELOG.md":"c4e007d9711466306980b48558f75f81bbb441c4ffb7fbd043929fcf0742a239","CODE_OF_CONDUCT.md":"ab482a6e8d9ed00cc77e0f90d823210e3368201ed98d9fda36033fd19aff24c4","Cargo.lock":"5ddeda3290a5185e262c7ff6267d2d595a465044d81b2868a9735327ad1eec30","Cargo.toml":"4e97e574b666793c7c9d091a3870a020d970abf150b1e757e17b7eaaff3817dc","LICENSE":"1f256ecad192880510e84ad60474eab7589218784b9a50bc7ceee34c2b91f1d5","README.md":"f7c94b31d9678c38fcd88e071b59b767ed73b3f1262b84bd957e2fc85bc5b1a1","examples/file_parser.rs":"3d7a4da7fb25db7260b394db7fd6bcdf5540f9e2b2bfc5d7ad12077b3871c910","examples/sdps/02.sdp":"bda3c63d42091eb8dc6cda46e1ed252020b76bbc78563a36244d49a95ff11d00","examples/sdps/03.sdp":"a18aa7be19dd911779dafce2c92b3ed21084598ecb9eb45c0d665f1925030547","examples/sdps/04.sdp":"a8139befd817711e301a16db7f12e24410add1fa39c8a8de77ae25cdd4baab5e","examples/sdps/05.sdp":"445febdc16afeed4bf36c4b30e1952b8915078248c6bb6f9d4eb6e16c75f7065","examples/sdps/06.sdp":"49fb363dfab70340b3906015e1f4fb8d5058267995dd4e344b0e021429da58de","examples/sdps/07.sdp":"03d2a6847d9a405eeaaec2d7011e81aa73b63b33d22a4f7371dff7e9c82ba096","examples/sdps/08.sdp":"bcab5d8b258e3bcdcd21a3299e51b26e6563d549b74f612780d2e3be8a0495c5","examples/sdps/09.sdp":"c771c5aa3efc4acb4b47f488fb9ceaedd6f94f706fd92c704e98fa26d3f37e29","examples/sdps/10.sdp":"deb23d5578a20df6e16ffbda193a4ba97df7131ce95eac3677b4d2fc74e50003","examples/sdps/11.sdp":"7e14a6d290a659b715f32a5257cb0035126c9bb9c7e1dac26f757bf36c93dc09","examples/sdps/12.sdp":"8ad9b018ba1c62865520d65869b568f6750f4ff93deca238f6c04dd468562dc9","examples/sdps/13.sdp":"d396c398247c253fcf5ed087fd5a3334ce2f8c66efd93638d2e8884dc318eac1","examples/sdps/14.sdp":"b714a4216e8d44bc71b9430d49229b61dd9e6a5b3fbda15ed8c0c76843154c9e","examples/sdps/15.sdp":"c96e468a51db1e205898867dcc95af0bd544676eceb4d930c24cdd4c8e8ce152","examples/sdps/16.sdp":"034c556ef8d0c9972c7b8f37e8b419f343e3ac993794e2f20c946a8ebd1cb781","examples/sdps/17.sdp":"b35628e255798817539420528950b05dd2369920d5c00a0279de25679056363b","examples/sdps/18.sdp":"d60f4530b98f6ab9fa68a4e1c7d899220f43e3596f5ed82b008d867d27d464c6","examples/sdps/19.sdp":"2929f883bc54cfd52d8a41e6dbe4ab46a307611ea6f974d22f6f81e2a6e861cc","examples/sdps/20.sdp":"f087414d44f4edc0238ce429a7fd9ee764180a11758c80dcb3f18ff596411144","examples/sdps/21.sdp":"50ee7dad5aa101cc6460d6136a173b894f8ec3b2304eb6b89739d794c2593858","examples/sdps/22.sdp":"60ba3631584333b07c9ba482b2681dfd5f47961485bd9869c0ce399ac78a28c0","examples/sdps/23.sdp":"e19f374de91927c54019b41681ab9116a781b3434151bbb9b1e333ba5ba15305","examples/sdps/24.sdp":"cf410817c7ac2c61c94b02ae24696da54c5439d182b919f0804aefa985b7dec6","examples/sdps/25.sdp":"0aba5390d78b948477f4116a3665f3b0214f1c7c8cb7d7308563ff7b66b3320c","examples/sdps/26.sdp":"02edf268aec6562db717d2e7e090eccccc336f37232cbd0c9848dc396cd1f68d","examples/sdps/27.sdp":"1950a83ff7311d75eaee8fc6a88f54034540add4a413b8c97daad258bc11510a","examples/sdps/28.sdp":"0bd3d1dad72087f90d67996d71a190c56701e8866f248406db8e183c6deee4cd","examples/sdps/29.sdp":"60a71fab3f0facef4e045f78eabb1ff4f1150393427f6720e1228e010edc2f26","examples/sdps/30.sdp":"52fb5119e4a18026b9fe0e10732e621dd13ace847e68ab51687cb633761eeabc","examples/sdps/31.sdp":"6080176767fc4188903b08f98a6fdbca2478fb923b79f31bb741d91e8cc1c4a5","examples/sdps/32.sdp":"b764977a4be5d3808260eaf93c2ec7c6aba7e4589996dd514e9d3bd8d3d02975","examples/sdps/33.sdp":"bd6367ad1abe795576c58c34be6bf5d2073db3343c0edb02a88cf314176fdc51","examples/sdps/34.sdp":"4dc1bbfdf292f2303ff3e6f26ee9db87164e57464d077e8e94c3c11490d4f839","examples/sdps/35.sdp":"4dc1bbfdf292f2303ff3e6f26ee9db87164e57464d077e8e94c3c11490d4f839","examples/sdps/36.sdp":"4dc1bbfdf292f2303ff3e6f26ee9db87164e57464d077e8e94c3c11490d4f839","examples/sdps/37.sdp":"64623a06b325f6219b645c2cf3072703443104ac68c8f02aeb1ac68107f13ab8","examples/sdps/38.sdp":"f59fd5295eebd62acb79708f5258ac4e92082f5725f50348ba31675c2bd9c3a9","examples/sdps/39.sdp":"e3ea9870d76f6257d27d1222f7b74a247a6556d5361479c64d2db11b5c6e2b8d","examples/sdps/40.sdp":"e3ea9870d76f6257d27d1222f7b74a247a6556d5361479c64d2db11b5c6e2b8d","examples/sdps/41.sdp":"186e94e19a6bd802dd787a1a251c39d903aa86dda4f22bb6bb7aeb7678587141","examples/sdps/extract.sh":"fa9daf98313f57ff7717841da9a5f2bf3e30394baded7c42023173b53a09e6d8","src/address.rs":"4546df0adb5a0a95f06b601b7e2b764bc6d4a810a8a2ac7920eb92e488414d27","src/anonymizer.rs":"37856b19334716327d74c6ecb850ed7f1b193f5f1e5963ad7b2d2822aad4ecfc","src/attribute_type.rs":"1a32e7d4fa6e6adf46e55e378c53076d07a6405a738fe0c58dcf16e80d37807d","src/error.rs":"a8af6de849501f9927e052b4f407b84803e227462f5cfe7825e5f6c362906e71","src/lib.rs":"d268359b0ef4ae57020cec7d9bbbfa76ac50daf5bfe9304fc34ae2914f0e3fa8","src/media_type.rs":"15e89cd11cfef2f408269fa4b3a0c1123c912636ad5b679af18f9f0cdf09c7b5","src/network.rs":"6679c759eb1304315190ac87161affd3517ec9311d728e4c68f4169bfcd5111f","tests/unit_tests.rs":"6f47de5c04710f6877da868172349b7f054b2f63611242145a21691b95dfae41"},"package":"98db6ff463a94d727ee7c1188bab33146468add6dfb94df30a1f4a3495a700d9"}
\ No newline at end of file diff --git a/third_party/rust/webrtc-sdp/CHANGELOG.md b/third_party/rust/webrtc-sdp/CHANGELOG.md new file mode 100644 index 0000000000..33f5ab995e --- /dev/null +++ b/third_party/rust/webrtc-sdp/CHANGELOG.md @@ -0,0 +1,78 @@ +# Changelog +## [0.3.8] - 2021-01-16 +- fmt numbers 35 to 63 are now usable for dynamic allocation +- parse extmap-allow-mixed as per RFC 8285 +## [0.3.7] - 2020-11-23 +- Minimum Rust version >= 1.45 +- Added feature for parse object tree wide debug formatting, defaulted to on for now +- Moved check for multiple c lines within an m section out of the setter and into the parsing logic, credit Mnwa +## [0.3.6] - 2020-05-07 +- Added support for Opus FMTP parameters ptime, maxptime, minptime, and maxaveragebitrate +## [0.3.5] - 2020-04-07 +### Fixed +- RTX apt can now be zero +## [0.3.4] - 2020-03-31 +### Fixed +- Fixed new clippy warnings in stable +- Accept a lack of c= lines if there are no m= lines (for JSEP compat.) +### Changed +- Added support for ssrc-group +- Added support for RTX FMTP parameters +- Example runner can no be told to expect failure +## [0.3.3] - 2019-12-10 +### Changed +- Changed handling of default channel counts + +## [0.3.2] - 2019-12-02 +### Changed +- Fixed handling of spaces in fmtp attributes +- Minimum Rust version >= 1.36 + +## [0.3.1] - 2019-09-12 +### Changed +- Updated `urls` dependency to `0.2.1` + +### Removed +- Removed `TcpTlsRtpSavpf` protocl token +- Removed dependency on `enum-display-derive` + +## [0.3.0] - 2019-08-08 +### Changed +- Unsafe code is forbidden now + +### Fixed +- Fixed panic from slicing unicode character in image attr braces + +### Added +- Added support for FQDN addresses +- Added support for parsing ice-pacing +- Added fuzzing target + +## [0.2.2] - 2019-06-21 +### Changed + - Minimum Rust version >= 1.35 + +## [0.2.0] - 2019-06-15 +### Changed +- Minimum Rust version >= 1.30.0 +- Changed code coverage from kcov to tarpaulin +- Moved file parser example to examples sub directory +- Replaced cause() with source() in unit test +- Moved all unit tests into tests modules + +### Fixed +- Unknown extensions in candidate attributes (#103) +- Reduced amount of internal clone() calls significantly +- Added dyn to error:Error impl required by more recent rust versions + +### Added +- Support for anonymization to enable logging of SDP without personal + information +- Quite a bit more unit testing got added + +### Removed +- Replaced unsupported types with errors directly in lib.rs + +## [0.1.0] - 2019-01-26 +- Initial release +- Minimum Rust version >= 1.17.0 diff --git a/third_party/rust/webrtc-sdp/CODE_OF_CONDUCT.md b/third_party/rust/webrtc-sdp/CODE_OF_CONDUCT.md new file mode 100644 index 0000000000..50e26b1c03 --- /dev/null +++ b/third_party/rust/webrtc-sdp/CODE_OF_CONDUCT.md @@ -0,0 +1,8 @@ +# Community Participation Guidelines + +This repository is governed by Mozilla's code of conduct and etiquette guidelines. +For more details, please read the +[Mozilla Community Participation Guidelines](https://www.mozilla.org/about/governance/policies/participation/). + +## How to Report +For more information on how to report violations of the Community Participation Guidelines, please read our [How to Report](https://www.mozilla.org/about/governance/policies/participation/reporting/) page. diff --git a/third_party/rust/webrtc-sdp/Cargo.lock b/third_party/rust/webrtc-sdp/Cargo.lock new file mode 100644 index 0000000000..1bf9ee92d1 --- /dev/null +++ b/third_party/rust/webrtc-sdp/Cargo.lock @@ -0,0 +1,169 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[package]] +name = "idna" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02e2673c30ee86b5b96a9cb52ad15718aa1f966f5ab9ad54a8b95d5ca33120a9" +dependencies = [ + "matches", + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "itoa" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "501266b7edd0174f8530248f87f99c88fbe60ca4ef3dd486835b8d8d53136f7f" + +[[package]] +name = "log" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "matches" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" + +[[package]] +name = "maybe-uninit" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" + +[[package]] +name = "percent-encoding" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" + +[[package]] +name = "proc-macro2" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c9e470a8dc4aeae2dee2f335e8f533e2d4b347e1434e5671afc49b054592f27" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quote" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "ryu" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa8506c1de11c9c4e4c38863ccbe02a305c8188e85a05a784c9e11e1c3910c8" + +[[package]] +name = "serde" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4b39bd9b0b087684013a792c59e3e07a46a01d2322518d8a1104641a0b1be0" + +[[package]] +name = "serde_derive" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca13fc1a832f793322228923fbb3aba9f3f44444898f835d31ad1b74fa0a2bf8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f72eb2a68a7dc3f9a691bfda9305a1c017a6215e5a4545c258500d2099a37c2" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "smallvec" +version = "0.6.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7b0758c52e15a8b5e3691eae6cc559f08eee9406e548a4477ba4e67770a82b6" +dependencies = [ + "maybe-uninit", +] + +[[package]] +name = "syn" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "661641ea2aa15845cddeb97dad000d22070bb5c1fb456b96c1cba883ec691e92" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "unicode-bidi" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" +dependencies = [ + "matches", +] + +[[package]] +name = "unicode-normalization" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09c8070a9942f5e7cfccd93f490fdebd230ee3c3c9f107cb25bad5351ef671cf" +dependencies = [ + "smallvec", +] + +[[package]] +name = "unicode-xid" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" + +[[package]] +name = "url" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b414f6c464c879d7f9babf951f23bc3743fb7313c081b2e6ca719067ea9d61" +dependencies = [ + "idna", + "matches", + "percent-encoding", +] + +[[package]] +name = "webrtc-sdp" +version = "0.3.8" +dependencies = [ + "log", + "serde", + "serde_derive", + "serde_json", + "url", +] diff --git a/third_party/rust/webrtc-sdp/Cargo.toml b/third_party/rust/webrtc-sdp/Cargo.toml new file mode 100644 index 0000000000..1a6d3fc4d5 --- /dev/null +++ b/third_party/rust/webrtc-sdp/Cargo.toml @@ -0,0 +1,50 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies +# +# If you believe there's an error in this file please file an +# issue against the rust-lang/cargo repository. If you're +# editing this file be aware that the upstream Cargo.toml +# will likely look very different (and much more reasonable) + +[package] +name = "webrtc-sdp" +version = "0.3.8" +authors = ["Nils Ohlmeier <github@ohlmeier.org>"] +description = "This create parses strings in the format of the Session Description Protocol according to RFC4566. It specifically supports the subset of features required to support WebRTC according to the JSEP draft." +readme = "README.md" +keywords = ["webrtc", "sdp", "jsep"] +categories = ["parsing", "network-programming"] +license = "MPL-2.0" +repository = "https://github.com/mozilla/webrtc-sdp" +[dependencies.log] +version = "0.4.6" + +[dependencies.serde] +version = "1.0" +optional = true + +[dependencies.serde_derive] +version = "1.0" +optional = true + +[dependencies.url] +version = "2.1.0" +[dev-dependencies.serde_json] +version = "1.0" + +[features] +default = ["enhanced_debug"] +enhanced_debug = [] +serialize = ["serde", "serde_derive"] +[badges.codecov] +branch = "master" +repository = "mozilla/webrtc-sdp" +service = "github" + +[badges.travis-ci] +branch = "master" +repository = "mozilla/webrtc-sdp" diff --git a/third_party/rust/webrtc-sdp/LICENSE b/third_party/rust/webrtc-sdp/LICENSE new file mode 100644 index 0000000000..a612ad9813 --- /dev/null +++ b/third_party/rust/webrtc-sdp/LICENSE @@ -0,0 +1,373 @@ +Mozilla Public License Version 2.0 +================================== + +1. Definitions +-------------- + +1.1. "Contributor" + means each individual or legal entity that creates, contributes to + the creation of, or owns Covered Software. + +1.2. "Contributor Version" + means the combination of the Contributions of others (if any) used + by a Contributor and that particular Contributor's Contribution. + +1.3. "Contribution" + means Covered Software of a particular Contributor. + +1.4. "Covered Software" + means Source Code Form to which the initial Contributor has attached + the notice in Exhibit A, the Executable Form of such Source Code + Form, and Modifications of such Source Code Form, in each case + including portions thereof. + +1.5. "Incompatible With Secondary Licenses" + means + + (a) that the initial Contributor has attached the notice described + in Exhibit B to the Covered Software; or + + (b) that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the + terms of a Secondary License. + +1.6. "Executable Form" + means any form of the work other than Source Code Form. + +1.7. "Larger Work" + means a work that combines Covered Software with other material, in + a separate file or files, that is not Covered Software. + +1.8. "License" + means this document. + +1.9. "Licensable" + means having the right to grant, to the maximum extent possible, + whether at the time of the initial grant or subsequently, any and + all of the rights conveyed by this License. + +1.10. "Modifications" + means any of the following: + + (a) any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered + Software; or + + (b) any new file in Source Code Form that contains any Covered + Software. + +1.11. "Patent Claims" of a Contributor + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the + License, by the making, using, selling, offering for sale, having + made, import, or transfer of either its Contributions or its + Contributor Version. + +1.12. "Secondary License" + means either the GNU General Public License, Version 2.0, the GNU + Lesser General Public License, Version 2.1, the GNU Affero General + Public License, Version 3.0, or any later versions of those + licenses. + +1.13. "Source Code Form" + means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that + controls, is controlled by, or is under common control with You. For + purposes of this definition, "control" means (a) the power, direct + or indirect, to cause the direction or management of such entity, + whether by contract or otherwise, or (b) ownership of more than + fifty percent (50%) of the outstanding shares or beneficial + ownership of such entity. + +2. License Grants and Conditions +-------------------------------- + +2.1. Grants + +Each Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license: + +(a) under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and + +(b) under Patent Claims of such Contributor to make, use, sell, offer + for sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + +2.2. Effective Date + +The licenses granted in Section 2.1 with respect to any Contribution +become effective for each Contribution on the date the Contributor first +distributes such Contribution. + +2.3. Limitations on Grant Scope + +The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor: + +(a) for any code that a Contributor has removed from Covered Software; + or + +(b) for infringements caused by: (i) Your and any other third party's + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + +(c) under Patent Claims infringed by Covered Software in the absence of + its Contributions. + +This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with +the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + +No Contributor makes additional grants as a result of Your choice to +distribute the Covered Software under a subsequent version of this +License (see Section 10.2) or under the terms of a Secondary License (if +permitted under the terms of Section 3.3). + +2.5. Representation + +Each Contributor represents that the Contributor believes its +Contributions are its original creation(s) or it has sufficient rights +to grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + +This License is not intended to limit any rights You have under +applicable copyright doctrines of fair use, fair dealing, or other +equivalents. + +2.7. Conditions + +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted +in Section 2.1. + +3. Responsibilities +------------------- + +3.1. Distribution of Source Form + +All distribution of Covered Software in Source Code Form, including any +Modifications that You create or to which You contribute, must be under +the terms of this License. You must inform recipients that the Source +Code Form of the Covered Software is governed by the terms of this +License, and how they can obtain a copy of this License. You may not +attempt to alter or restrict the recipients' rights in the Source Code +Form. + +3.2. Distribution of Executable Form + +If You distribute Covered Software in Executable Form then: + +(a) such Covered Software must also be made available in Source Code + Form, as described in Section 3.1, and You must inform recipients of + the Executable Form how they can obtain a copy of such Source Code + Form by reasonable means in a timely manner, at a charge no more + than the cost of distribution to the recipient; and + +(b) You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter + the recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + +You may create and distribute a Larger Work under terms of Your choice, +provided that You also comply with the requirements of this License for +the Covered Software. If the Larger Work is a combination of Covered +Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this +License permits You to additionally distribute such Covered Software +under the terms of such Secondary License(s), so that the recipient of +the Larger Work may, at their option, further distribute the Covered +Software under the terms of either this License or such Secondary +License(s). + +3.4. Notices + +You may not remove or alter the substance of any license notices +(including copyright notices, patent notices, disclaimers of warranty, +or limitations of liability) contained within the Source Code Form of +the Covered Software, except that You may alter any license notices to +the extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + +You may choose to offer, and to charge a fee for, warranty, support, +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on +behalf of any Contributor. You must make it absolutely clear that any +such warranty, support, indemnity, or liability obligation is offered by +You alone, and You hereby agree to indemnify every Contributor for any +liability incurred by such Contributor as a result of warranty, support, +indemnity or liability terms You offer. You may include additional +disclaimers of warranty and limitations of liability specific to any +jurisdiction. + +4. Inability to Comply Due to Statute or Regulation +--------------------------------------------------- + +If it is impossible for You to comply with any of the terms of this +License with respect to some or all of the Covered Software due to +statute, judicial order, or regulation then You must: (a) comply with +the terms of this License to the maximum extent possible; and (b) +describe the limitations and the code they affect. Such description must +be placed in a text file included with all distributions of the Covered +Software under this License. Except to the extent prohibited by statute +or regulation, such description must be sufficiently detailed for a +recipient of ordinary skill to be able to understand it. + +5. Termination +-------------- + +5.1. The rights granted under this License will terminate automatically +if You fail to comply with any of its terms. However, if You become +compliant, then the rights granted under this License from a particular +Contributor are reinstated (a) provisionally, unless and until such +Contributor explicitly and finally terminates Your grants, and (b) on an +ongoing basis, if such Contributor fails to notify You of the +non-compliance by some reasonable means prior to 60 days after You have +come back into compliance. Moreover, Your grants from a particular +Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the +first time You have received notice of non-compliance with this License +from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent +infringement claim (excluding declaratory judgment actions, +counter-claims, and cross-claims) alleging that a Contributor Version +directly or indirectly infringes any patent, then the rights granted to +You by any and all Contributors for the Covered Software under Section +2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all +end user license agreements (excluding distributors and resellers) which +have been validly granted by You or Your distributors under this License +prior to termination shall survive termination. + +************************************************************************ +* * +* 6. Disclaimer of Warranty * +* ------------------------- * +* * +* Covered Software is provided under this License on an "as is" * +* basis, without warranty of any kind, either expressed, implied, or * +* statutory, including, without limitation, warranties that the * +* Covered Software is free of defects, merchantable, fit for a * +* particular purpose or non-infringing. The entire risk as to the * +* quality and performance of the Covered Software is with You. * +* Should any Covered Software prove defective in any respect, You * +* (not any Contributor) assume the cost of any necessary servicing, * +* repair, or correction. This disclaimer of warranty constitutes an * +* essential part of this License. No use of any Covered Software is * +* authorized under this License except under this disclaimer. * +* * +************************************************************************ + +************************************************************************ +* * +* 7. Limitation of Liability * +* -------------------------- * +* * +* Under no circumstances and under no legal theory, whether tort * +* (including negligence), contract, or otherwise, shall any * +* Contributor, or anyone who distributes Covered Software as * +* permitted above, be liable to You for any direct, indirect, * +* special, incidental, or consequential damages of any character * +* including, without limitation, damages for lost profits, loss of * +* goodwill, work stoppage, computer failure or malfunction, or any * +* and all other commercial damages or losses, even if such party * +* shall have been informed of the possibility of such damages. This * +* limitation of liability shall not apply to liability for death or * +* personal injury resulting from such party's negligence to the * +* extent applicable law prohibits such limitation. Some * +* jurisdictions do not allow the exclusion or limitation of * +* incidental or consequential damages, so this exclusion and * +* limitation may not apply to You. * +* * +************************************************************************ + +8. Litigation +------------- + +Any litigation relating to this License may be brought only in the +courts of a jurisdiction where the defendant maintains its principal +place of business and such litigation shall be governed by laws of that +jurisdiction, without reference to its conflict-of-law provisions. +Nothing in this Section shall prevent a party's ability to bring +cross-claims or counter-claims. + +9. Miscellaneous +---------------- + +This License represents the complete agreement concerning the subject +matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent +necessary to make it enforceable. Any law or regulation which provides +that the language of a contract shall be construed against the drafter +shall not be used to construe this License against a Contributor. + +10. Versions of the License +--------------------------- + +10.1. New Versions + +Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or +publish new versions of this License. Each version will be given a +distinguishing version number. + +10.2. Effect of New Versions + +You may distribute the Covered Software under the terms of the version +of the License under which You originally received the Covered Software, +or under the terms of any subsequent version published by the license +steward. + +10.3. Modified Versions + +If you create software not governed by this License, and you want to +create a new license for such software, you may create and use a +modified version of this License if you rename the license and remove +any references to the name of the license steward (except to note that +such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary +Licenses + +If You choose to distribute Source Code Form that is Incompatible With +Secondary Licenses under the terms of this version of the License, the +notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice +------------------------------------------- + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular +file, then You may include the notice in a location (such as a LICENSE +file in a relevant directory) where a recipient would be likely to look +for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice +--------------------------------------------------------- + + This Source Code Form is "Incompatible With Secondary Licenses", as + defined by the Mozilla Public License, v. 2.0. diff --git a/third_party/rust/webrtc-sdp/README.md b/third_party/rust/webrtc-sdp/README.md new file mode 100644 index 0000000000..e3cf04f0c1 --- /dev/null +++ b/third_party/rust/webrtc-sdp/README.md @@ -0,0 +1,73 @@ +# webrtc-sdp + +[![Crates.io](https://img.shields.io/crates/v/webrtc-sdp.svg)](https://crates.io/crates/webrtc-sdp) +[![Build Status](https://travis-ci.org/mozilla/webrtc-sdp.svg?branch=master)](https://travis-ci.org/mozilla/webrtc-sdp) +[![Codecov coverage status](https://codecov.io/gh/mozilla/webrtc-sdp/branch/master/graph/badge.svg)](https://codecov.io/gh/webrtc-sdp/webrtc-sdp) +[![License: MPL 2.0](https://img.shields.io/badge/License-MPL%202.0-brightgreen.svg)](#License) +[![dependency status](https://deps.rs/repo/github/mozilla/webrtc-sdp/status.svg)](https://deps.rs/repo/github/mozilla/webrtc-sdp) + +A SDP parser written in Rust specifically aimed to handle WebRTC SDP offers and answers. + +## Dependecies + +* Rust >= 1.45.0 +* log module +* serde module +* serde-derive module + +Cargo installs the missing modules automatically when building webrtc-sdp for the first time. + +## The webrtc-sdp API + +The main function is: +```rust +fn parse_sdp(sdp: &str, fail_on_warning: bool) -> Result<SdpSession, SdpParserError> +``` +The `sdp` parameter is the string which will get parsed. The `fail_on_warning` parameter determines how to treat warnings encountered during parsing. Any problems encountered during are stored until the whole string has been parsed. Any problem during parsing falls into two catgeories: + +* Fatal error preventing further parsing or processing of the SDP +* Warning which don't block further processing of the SDP + +Warnings will be for example unknown parameters in attributes. Setting `fail_on_warning` to `true` makes most sense during development, when you want to be aware of all potential problems. In production `fail_on_warning` is expected to be `false`. + +`parse_sdp()` returns either an `SdpSession` struct ([code](https://github.com/mozilla/webrtc-sdp/blob/master/src/lib.rs#L137)) which contains all the parsed information. Or in case a fatal error was encountered (or if `fail_on_warning` was set to `true` and any warnings were encountered) an `SdpParserError` ([code](https://github.com/mozilla/webrtc-sdp/blob/master/src/error.rs#L117)) will be returned as a `Result`. + +## Examples + +The [file parser](https://github.com/mozilla/webrtc-sdp/blob/master/examples/file_parser.rs) in the webrtc-sdp package gives you an easy example of how to invoke the webrtc-sdp parser. + +## Contributing + +As the Travis CI runs are checking for code formating and clippy warnings please run the following commands locally, before submitting a Pull Request. + +If you haven't clippy and Rust format installed already you add them like this: +``` +rustup component add rustfmt-preview +rustup component add clippy +``` + +Check with clippy for warnings in the code: +``` +cargo clippy +``` + +And format all of the code according to Rust code style convention: +``` +cargo fmt --all +``` + +## Fuzzing + +Install cargo-fuzz like this: +``` +cargo install cargo-fuzz +``` + +With rust nightly you can start fuzzing like this: +``` +cargo fuzz run fuzz_target_parse_sdp +``` + +## License + +Licensed under [MPL-2.0](https://www.mozilla.org/MPL/2.0/) diff --git a/third_party/rust/webrtc-sdp/examples/file_parser.rs b/third_party/rust/webrtc-sdp/examples/file_parser.rs new file mode 100644 index 0000000000..48851f35f1 --- /dev/null +++ b/third_party/rust/webrtc-sdp/examples/file_parser.rs @@ -0,0 +1,72 @@ +/* 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/. */ + +use std::fs::File; +use std::io::prelude::*; +use std::panic; +use std::path::Path; +extern crate webrtc_sdp; + +// Takes the filename of a file that contains SDP, and optionally the trailing +// flag --expect-failure. +// If --expect-failure is passed, then the program will exit with a successful +// exit code if the file fails to parse. +fn main() { + let mut args = std::env::args(); + let filename = match args.nth(1) { + None => { + eprintln!("Missing file name argument!"); + std::process::exit(1); + } + 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), + Ok(file) => file, + }; + + let mut s = String::new(); + match file.read_to_string(&mut s) { + Err(why) => panic!("Couldn't read {}: {}", display, why), + Ok(s) => s, + }; + + // Hook up the panic handler if it is expected to fail to parse + let expect_failure = if let Some(x) = args.next() { + if x.to_lowercase() != "--expect-failure" { + eprintln!("Extra arguments passed!"); + std::process::exit(1); + } + panic::set_hook(Box::new(|_| { + println!("Exited with failure, as expected."); + std::process::exit(0); + })); + true + } else { + false + }; + + // Remove comment lines + let s = s + .lines() + .filter(|&l| !l.trim_start().starts_with(';')) + .collect::<Vec<&str>>() + .join("\r\n"); + + let res = webrtc_sdp::parse_sdp(&s, true); + match res { + Err(why) => panic!("Failed to parse SDP with error: {}", why), + Ok(sdp) => println!("Parsed SDP structure:\n{:#?}", sdp), + } + + if expect_failure { + eprintln!("Successfully parsed SDP that was expected to fail. You may need to update the example expectations."); + std::process::exit(1); + } + println!("Successfully parsed SDP"); +} diff --git a/third_party/rust/webrtc-sdp/examples/sdps/02.sdp b/third_party/rust/webrtc-sdp/examples/sdps/02.sdp new file mode 100644 index 0000000000..21d383d2f2 --- /dev/null +++ b/third_party/rust/webrtc-sdp/examples/sdps/02.sdp @@ -0,0 +1,7 @@ +v=0 +o=- 4294967296 2 IN IP4 127.0.0.1 +s=SIP Call +c=IN IP4 198.51.100.7 +t=0 0 +m=video 56436 RTP/SAVPF 120 +a=rtpmap:120 VP8/90000 diff --git a/third_party/rust/webrtc-sdp/examples/sdps/03.sdp b/third_party/rust/webrtc-sdp/examples/sdps/03.sdp new file mode 100644 index 0000000000..ef0a5efd90 --- /dev/null +++ b/third_party/rust/webrtc-sdp/examples/sdps/03.sdp @@ -0,0 +1,38 @@ +; Many RTCP-FB feed back types take a parameter +; Most of these will not parse. +; One can experiment by commenting out individual lines. +; Note: comment lines only work in the example parser. +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 diff --git a/third_party/rust/webrtc-sdp/examples/sdps/04.sdp b/third_party/rust/webrtc-sdp/examples/sdps/04.sdp new file mode 100644 index 0000000000..0682623c93 --- /dev/null +++ b/third_party/rust/webrtc-sdp/examples/sdps/04.sdp @@ -0,0 +1,7 @@ +v=0 +o=- 4294967296 2 IN IP4 127.0.0.1 +s=SIP Call +t=0 0 +m=video 56436 RTP/SAVPF 120 +c=IN IP4 198.51.100.7 +a=rtpmap:120 VP8/90000 diff --git a/third_party/rust/webrtc-sdp/examples/sdps/05.sdp b/third_party/rust/webrtc-sdp/examples/sdps/05.sdp new file mode 100644 index 0000000000..043046edb5 --- /dev/null +++ b/third_party/rust/webrtc-sdp/examples/sdps/05.sdp @@ -0,0 +1,6 @@ +v=0 +o=- 4294967296 2 IN IP4 127.0.0.1 +s=SIP Call +c=IN IP4 224.0.0.1/100/12 +t=0 0 +a=ice-lite diff --git a/third_party/rust/webrtc-sdp/examples/sdps/06.sdp b/third_party/rust/webrtc-sdp/examples/sdps/06.sdp new file mode 100644 index 0000000000..d20cb3fc70 --- /dev/null +++ b/third_party/rust/webrtc-sdp/examples/sdps/06.sdp @@ -0,0 +1,12 @@ +v=0 +o=- 4294967296 2 IN IP4 127.0.0.1 +s=SIP Call +c=IN IP4 198.51.100.7 +b=CT:5000 +b=FOOBAR:10 +b=AS:4 +t=0 0 +m=video 56436 RTP/SAVPF 120 +a=rtpmap:120 VP8/90000 +m=audio 12345/2 RTP/SAVPF 0 +a=rtpmap:0 PCMU/8000 diff --git a/third_party/rust/webrtc-sdp/examples/sdps/07.sdp b/third_party/rust/webrtc-sdp/examples/sdps/07.sdp new file mode 100644 index 0000000000..882adacb1a --- /dev/null +++ b/third_party/rust/webrtc-sdp/examples/sdps/07.sdp @@ -0,0 +1,8 @@ +v=0 +o=- 4294967296 2 IN IP4 127.0.0.1 +s= +c=IN IP4 198.51.100.7 +t=0 0 +m=video 56436 RTP/SAVPF 120 +b=CT:1000 +a=rtpmap:120 VP8/90000 diff --git a/third_party/rust/webrtc-sdp/examples/sdps/08.sdp b/third_party/rust/webrtc-sdp/examples/sdps/08.sdp new file mode 100644 index 0000000000..5175638594 --- /dev/null +++ b/third_party/rust/webrtc-sdp/examples/sdps/08.sdp @@ -0,0 +1,87 @@ +; +; This example fails to parse because of a mismatched fingerprint length +; +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 diff --git a/third_party/rust/webrtc-sdp/examples/sdps/09.sdp b/third_party/rust/webrtc-sdp/examples/sdps/09.sdp new file mode 100644 index 0000000000..f5c4acdbd8 --- /dev/null +++ b/third_party/rust/webrtc-sdp/examples/sdps/09.sdp @@ -0,0 +1,34 @@ +v=0 +o=- 4294967296 2 IN IP4 127.0.0.1 +s=SIP Call +c=IN IP4 198.51.100.7 +t=0 0 +m=audio 9 RTP/SAVPF 109 9 0 8 101 +c=IN IP4 0.0.0.0 +a=mid:first +a=rtpmap:109 opus/48000/2 +a=ptime:20 +a=maxptime:20 +a=rtpmap:9 G722/8000 +a=rtpmap:0 PCMU/8000 +a=rtpmap:8 PCMA/8000 +a=rtpmap:101 telephone-event/8000 +a=fmtp:101 0-15 +a=fmtp:101 0-5. +a=fmtp:101 0-15,66,67 +a=fmtp:101 0,1,2-4,5-15,66,67 +a=fmtp:101 5,6,7 +a=fmtp:101 0 +a=fmtp:101 1 +a=fmtp:101 123 +a=fmtp:101 0-123 +a=fmtp:101 -12 +a=fmtp:101 12- +a=fmtp:101 1,12-,4 +a=fmtp:101 ,2,3 +a=fmtp:101 ,,,2,3 +a=fmtp:101 1,,,,,,,,3 +a=fmtp:101 1,2,3, +a=fmtp:101 1-2-3 +a=fmtp:101 112233 +a=fmtp:101 33-2 diff --git a/third_party/rust/webrtc-sdp/examples/sdps/10.sdp b/third_party/rust/webrtc-sdp/examples/sdps/10.sdp new file mode 100644 index 0000000000..783c0d0048 --- /dev/null +++ b/third_party/rust/webrtc-sdp/examples/sdps/10.sdp @@ -0,0 +1,13 @@ +v=0 +o=- 4294967296 2 IN IP4 127.0.0.1 +s=SIP Call +c=IN IP4 198.51.100.7 +t=0 0 +m=video 9 RTP/SAVPF 97 120 121 122 123 +c=IN IP6 ::1 +a=fingerprint:sha-1 DF:FA:FB:08:3B:3C:54:1D:D7:D4:05:77:A0:72:9B:14:08:6D:0F:4C +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 diff --git a/third_party/rust/webrtc-sdp/examples/sdps/11.sdp b/third_party/rust/webrtc-sdp/examples/sdps/11.sdp new file mode 100644 index 0000000000..a138e09ff0 --- /dev/null +++ b/third_party/rust/webrtc-sdp/examples/sdps/11.sdp @@ -0,0 +1,66 @@ +; +; This example fails to parse because a=ice-lite is session only +; +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 diff --git a/third_party/rust/webrtc-sdp/examples/sdps/12.sdp b/third_party/rust/webrtc-sdp/examples/sdps/12.sdp new file mode 100644 index 0000000000..647de5dca8 --- /dev/null +++ b/third_party/rust/webrtc-sdp/examples/sdps/12.sdp @@ -0,0 +1,58 @@ +v=0 +o=Mozilla-SIPUA-35.0a1 27987 0 IN IP4 0.0.0.0 +s=SIP Call +t=0 0 +a=ice-ufrag:8a39d2ae +a=ice-pwd:601d53aba51a318351b3ecf5ee00048f +a=fingerprint:sha-256 30:FF:8E:2B:AC:9D:ED:70:18:10:67:C8:AE:9E:68:F3:86:53:51:B0:AC:31:B7:BE:6D:CF:A4:2E:D3:6E:B4:28 +m=audio 9 RTP/SAVPF 109 9 0 8 101 +c=IN IP4 0.0.0.0 +a=rtpmap:109 opus/48000/2 +a=ptime:20 +a=rtpmap:9 G722/8000 +a=rtpmap:0 PCMU/8000 +a=rtpmap:8 PCMA/8000 +a=rtpmap:101 telephone-event/8000 +a=fmtp:101 0-15 +a=sendrecv +a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level +a=extmap:2/sendonly some_extension +a=extmap:3 some_other_extension some_params some more params +a=setup:actpass +a=rtcp-mux +m=video 9 RTP/SAVPF 120 126 97 +c=IN IP4 0.0.0.0 +a=rtpmap:120 VP8/90000 +a=rtpmap:126 H264/90000 +a=rtpmap:97 H264/90000 +a=sendrecv +a=rtcp-fb:120 ack rpsi +a=rtcp-fb:120 ack app foo +a=rtcp-fb:120 ack foo +a=rtcp-fb:120 nack +a=rtcp-fb:120 nack sli +a=rtcp-fb:120 nack pli +a=rtcp-fb:120 nack rpsi +a=rtcp-fb:120 nack app foo +a=rtcp-fb:120 nack foo +a=rtcp-fb:120 ccm fir +a=rtcp-fb:120 ccm tmmbr +a=rtcp-fb:120 ccm tstr +a=rtcp-fb:120 ccm vbcm +a=rtcp-fb:120 ccm foo +a=rtcp-fb:120 trr-int 10 +a=rtcp-fb:120 goog-remb +a=rtcp-fb:120 foo +a=rtcp-fb:126 nack +a=rtcp-fb:126 nack pli +a=rtcp-fb:126 ccm fir +a=rtcp-fb:97 nack +a=rtcp-fb:97 nack pli +a=rtcp-fb:97 ccm fir +a=rtcp-fb:* ccm tmmbr +a=setup:actpass +a=rtcp-mux +m=application 9 DTLS/SCTP 5000 +c=IN IP4 0.0.0.0 +a=sctpmap:5000 webrtc-datachannel 16 +a=setup:actpass diff --git a/third_party/rust/webrtc-sdp/examples/sdps/13.sdp b/third_party/rust/webrtc-sdp/examples/sdps/13.sdp new file mode 100644 index 0000000000..31c3d8d86b --- /dev/null +++ b/third_party/rust/webrtc-sdp/examples/sdps/13.sdp @@ -0,0 +1,12 @@ +v=0 +o=Mozilla-SIPUA-35.0a1 27987 0 IN IP4 0.0.0.0 +s=SIP Call +t=0 0 +a=ice-ufrag:8a39d2ae +a=ice-pwd:601d53aba51a318351b3ecf5ee00048f +a=fingerprint:sha-256 30:FF:8E:2B:AC:9D:ED:70:18:10:67:C8:AE:9E:68:F3:86:53:51:B0:AC:31:B7:BE:6D:CF:A4:2E:D3:6E:B4:28 +m=application 9 UDP/DTLS/SCTP webrtc-datachannel +c=IN IP4 0.0.0.0 +a=sctp-port:5000 +a=max-message-size:10000 +a=setup:actpass diff --git a/third_party/rust/webrtc-sdp/examples/sdps/14.sdp b/third_party/rust/webrtc-sdp/examples/sdps/14.sdp new file mode 100644 index 0000000000..cad2a9a199 --- /dev/null +++ b/third_party/rust/webrtc-sdp/examples/sdps/14.sdp @@ -0,0 +1,9 @@ +v=0 +o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0 +s=SIP Call +c=IN IP4 224.0.0.1/100/12 +t=0 0 +a=candidate:0 1 UDP 2130379007 10.0.0.36 62453 typ host +m=audio 9 RTP/SAVPF 109 9 0 8 101 +c=IN IP4 0.0.0.0 +a=rtpmap:109 opus/48000/2 diff --git a/third_party/rust/webrtc-sdp/examples/sdps/15.sdp b/third_party/rust/webrtc-sdp/examples/sdps/15.sdp new file mode 100644 index 0000000000..d356ecaf1f --- /dev/null +++ b/third_party/rust/webrtc-sdp/examples/sdps/15.sdp @@ -0,0 +1,9 @@ +v=0 +o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0 +s=SIP Call +c=IN IP4 224.0.0.1/100/12 +t=0 0 +a=bundle-only +m=audio 9 RTP/SAVPF 109 9 0 8 101 +c=IN IP4 0.0.0.0 +a=rtpmap:109 opus/48000/2 diff --git a/third_party/rust/webrtc-sdp/examples/sdps/16.sdp b/third_party/rust/webrtc-sdp/examples/sdps/16.sdp new file mode 100644 index 0000000000..67326a6e9a --- /dev/null +++ b/third_party/rust/webrtc-sdp/examples/sdps/16.sdp @@ -0,0 +1,9 @@ +v=0 +o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0 +s=SIP Call +c=IN IP4 224.0.0.1/100/12 +t=0 0 +a=fmtp:109 0-15 +m=audio 9 RTP/SAVPF 109 9 0 8 101 +c=IN IP4 0.0.0.0 +a=rtpmap:109 opus/48000/2 diff --git a/third_party/rust/webrtc-sdp/examples/sdps/17.sdp b/third_party/rust/webrtc-sdp/examples/sdps/17.sdp new file mode 100644 index 0000000000..0023222191 --- /dev/null +++ b/third_party/rust/webrtc-sdp/examples/sdps/17.sdp @@ -0,0 +1,9 @@ +v=0 +o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0 +s=SIP Call +c=IN IP4 224.0.0.1/100/12 +t=0 0 +a=ice-mismatch +m=audio 9 RTP/SAVPF 109 9 0 8 101 +c=IN IP4 0.0.0.0 +a=rtpmap:109 opus/48000/2 diff --git a/third_party/rust/webrtc-sdp/examples/sdps/18.sdp b/third_party/rust/webrtc-sdp/examples/sdps/18.sdp new file mode 100644 index 0000000000..3287af6413 --- /dev/null +++ b/third_party/rust/webrtc-sdp/examples/sdps/18.sdp @@ -0,0 +1,9 @@ +v=0 +o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0 +s=SIP Call +c=IN IP4 224.0.0.1/100/12 +t=0 0 +a=imageattr:120 send * recv * +m=video 9 RTP/SAVPF 120 +c=IN IP4 0.0.0.0 +a=rtpmap:120 VP8/90000 diff --git a/third_party/rust/webrtc-sdp/examples/sdps/19.sdp b/third_party/rust/webrtc-sdp/examples/sdps/19.sdp new file mode 100644 index 0000000000..20bb0a8058 --- /dev/null +++ b/third_party/rust/webrtc-sdp/examples/sdps/19.sdp @@ -0,0 +1,9 @@ +v=0 +o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0 +s=SIP Call +c=IN IP4 224.0.0.1/100/12 +t=0 0 +a=label:foobar +m=video 9 RTP/SAVPF 120 +c=IN IP4 0.0.0.0 +a=rtpmap:120 VP8/90000 diff --git a/third_party/rust/webrtc-sdp/examples/sdps/20.sdp b/third_party/rust/webrtc-sdp/examples/sdps/20.sdp new file mode 100644 index 0000000000..4d28e12a9f --- /dev/null +++ b/third_party/rust/webrtc-sdp/examples/sdps/20.sdp @@ -0,0 +1,9 @@ +v=0 +o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0 +s=SIP Call +c=IN IP4 224.0.0.1/100/12 +t=0 0 +a=maxptime:100 +m=video 9 RTP/SAVPF 120 +c=IN IP4 0.0.0.0 +a=rtpmap:120 VP8/90000 diff --git a/third_party/rust/webrtc-sdp/examples/sdps/21.sdp b/third_party/rust/webrtc-sdp/examples/sdps/21.sdp new file mode 100644 index 0000000000..7a2d6fa9dc --- /dev/null +++ b/third_party/rust/webrtc-sdp/examples/sdps/21.sdp @@ -0,0 +1,9 @@ +v=0 +o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0 +s=SIP Call +c=IN IP4 224.0.0.1/100/12 +t=0 0 +a=mid:foobar +m=video 9 RTP/SAVPF 120 +c=IN IP4 0.0.0.0 +a=rtpmap:120 VP8/90000 diff --git a/third_party/rust/webrtc-sdp/examples/sdps/22.sdp b/third_party/rust/webrtc-sdp/examples/sdps/22.sdp new file mode 100644 index 0000000000..95e5518123 --- /dev/null +++ b/third_party/rust/webrtc-sdp/examples/sdps/22.sdp @@ -0,0 +1,9 @@ +v=0 +o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0 +s=SIP Call +c=IN IP4 224.0.0.1/100/12 +t=0 0 +a=msid:foobar +m=video 9 RTP/SAVPF 120 +c=IN IP4 0.0.0.0 +a=rtpmap:120 VP8/90000 diff --git a/third_party/rust/webrtc-sdp/examples/sdps/23.sdp b/third_party/rust/webrtc-sdp/examples/sdps/23.sdp new file mode 100644 index 0000000000..7722360f32 --- /dev/null +++ b/third_party/rust/webrtc-sdp/examples/sdps/23.sdp @@ -0,0 +1,9 @@ +v=0 +o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0 +s=SIP Call +c=IN IP4 224.0.0.1/100/12 +t=0 0 +a=ptime:50 +m=video 9 RTP/SAVPF 120 +c=IN IP4 0.0.0.0 +a=rtpmap:120 VP8/90000 diff --git a/third_party/rust/webrtc-sdp/examples/sdps/24.sdp b/third_party/rust/webrtc-sdp/examples/sdps/24.sdp new file mode 100644 index 0000000000..dd13f1e143 --- /dev/null +++ b/third_party/rust/webrtc-sdp/examples/sdps/24.sdp @@ -0,0 +1,9 @@ +v=0 +o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0 +s=SIP Call +c=IN IP4 224.0.0.1/100/12 +t=0 0 +a=remote-candidates:0 10.0.0.1 5555 +m=video 9 RTP/SAVPF 120 +c=IN IP4 0.0.0.0 +a=rtpmap:120 VP8/90000 diff --git a/third_party/rust/webrtc-sdp/examples/sdps/25.sdp b/third_party/rust/webrtc-sdp/examples/sdps/25.sdp new file mode 100644 index 0000000000..7fd8a006fb --- /dev/null +++ b/third_party/rust/webrtc-sdp/examples/sdps/25.sdp @@ -0,0 +1,9 @@ +v=0 +o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0 +s=SIP Call +c=IN IP4 224.0.0.1/100/12 +t=0 0 +a=rtcp:5555 +m=video 9 RTP/SAVPF 120 +c=IN IP4 0.0.0.0 +a=rtpmap:120 VP8/90000 diff --git a/third_party/rust/webrtc-sdp/examples/sdps/26.sdp b/third_party/rust/webrtc-sdp/examples/sdps/26.sdp new file mode 100644 index 0000000000..789e062766 --- /dev/null +++ b/third_party/rust/webrtc-sdp/examples/sdps/26.sdp @@ -0,0 +1,9 @@ +v=0 +o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0 +s=SIP Call +c=IN IP4 224.0.0.1/100/12 +t=0 0 +a=rtcp-fb:120 nack +m=video 9 RTP/SAVPF 120 +c=IN IP4 0.0.0.0 +a=rtpmap:120 VP8/90000 diff --git a/third_party/rust/webrtc-sdp/examples/sdps/27.sdp b/third_party/rust/webrtc-sdp/examples/sdps/27.sdp new file mode 100644 index 0000000000..f8201916e9 --- /dev/null +++ b/third_party/rust/webrtc-sdp/examples/sdps/27.sdp @@ -0,0 +1,9 @@ +v=0 +o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0 +s=SIP Call +c=IN IP4 224.0.0.1/100/12 +t=0 0 +a=rtcp-mux +m=video 9 RTP/SAVPF 120 +c=IN IP4 0.0.0.0 +a=rtpmap:120 VP8/90000 diff --git a/third_party/rust/webrtc-sdp/examples/sdps/28.sdp b/third_party/rust/webrtc-sdp/examples/sdps/28.sdp new file mode 100644 index 0000000000..11d9331b10 --- /dev/null +++ b/third_party/rust/webrtc-sdp/examples/sdps/28.sdp @@ -0,0 +1,9 @@ +v=0 +o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0 +s=SIP Call +c=IN IP4 224.0.0.1/100/12 +t=0 0 +a=rtcp-rsize +m=video 9 RTP/SAVPF 120 +c=IN IP4 0.0.0.0 +a=rtpmap:120 VP8/90000 diff --git a/third_party/rust/webrtc-sdp/examples/sdps/29.sdp b/third_party/rust/webrtc-sdp/examples/sdps/29.sdp new file mode 100644 index 0000000000..f34b97fe58 --- /dev/null +++ b/third_party/rust/webrtc-sdp/examples/sdps/29.sdp @@ -0,0 +1,8 @@ +v=0 +o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0 +s=SIP Call +c=IN IP4 224.0.0.1/100/12 +t=0 0 +a=rtpmap:120 VP8/90000 +m=video 9 RTP/SAVPF 120 +c=IN IP4 0.0.0.0 diff --git a/third_party/rust/webrtc-sdp/examples/sdps/30.sdp b/third_party/rust/webrtc-sdp/examples/sdps/30.sdp new file mode 100644 index 0000000000..202ea88c08 --- /dev/null +++ b/third_party/rust/webrtc-sdp/examples/sdps/30.sdp @@ -0,0 +1,9 @@ +v=0 +o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0 +s=SIP Call +c=IN IP4 224.0.0.1/100/12 +t=0 0 +a=sctpmap:5000 +m=video 9 RTP/SAVPF 120 +c=IN IP4 0.0.0.0 +a=rtpmap:120 VP8/90000 diff --git a/third_party/rust/webrtc-sdp/examples/sdps/31.sdp b/third_party/rust/webrtc-sdp/examples/sdps/31.sdp new file mode 100644 index 0000000000..cd510e1d46 --- /dev/null +++ b/third_party/rust/webrtc-sdp/examples/sdps/31.sdp @@ -0,0 +1,9 @@ +v=0 +o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0 +s=SIP Call +c=IN IP4 224.0.0.1/100/12 +t=0 0 +a=ssrc:5000 +m=video 9 RTP/SAVPF 120 +c=IN IP4 0.0.0.0 +a=rtpmap:120 VP8/90000 diff --git a/third_party/rust/webrtc-sdp/examples/sdps/32.sdp b/third_party/rust/webrtc-sdp/examples/sdps/32.sdp new file mode 100644 index 0000000000..4569694768 --- /dev/null +++ b/third_party/rust/webrtc-sdp/examples/sdps/32.sdp @@ -0,0 +1,9 @@ +v=0 +o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0 +s=SIP Call +c=IN IP4 224.0.0.1/100/12 +t=0 0 +a=ssrc-group:FID 5000 +m=video 9 RTP/SAVPF 120 +c=IN IP4 0.0.0.0 +a=rtpmap:120 VP8/90000 diff --git a/third_party/rust/webrtc-sdp/examples/sdps/33.sdp b/third_party/rust/webrtc-sdp/examples/sdps/33.sdp new file mode 100644 index 0000000000..179e048da8 --- /dev/null +++ b/third_party/rust/webrtc-sdp/examples/sdps/33.sdp @@ -0,0 +1,9 @@ +v=0 +o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0 +s=SIP Call +c=IN IP4 224.0.0.1/100/12 +t=0 0 +m=video 9 RTP/SAVPF 120 +c=IN IP4 0.0.0.0 +a=rtpmap:120 VP8/90000 +a=imageattr:flob diff --git a/third_party/rust/webrtc-sdp/examples/sdps/34.sdp b/third_party/rust/webrtc-sdp/examples/sdps/34.sdp new file mode 100644 index 0000000000..39ef01206d --- /dev/null +++ b/third_party/rust/webrtc-sdp/examples/sdps/34.sdp @@ -0,0 +1,9 @@ +v=0 +o=- 4294967296 2 IN IP4 127.0.0.1 +s=SIP Call +c=IN IP4 198.51.100.7 +b=CT:5000 +t=0 0 +m=video 56436 RTP/SAVPF 120 +a=rtpmap:120 VP8/90000 +a=sendrecv diff --git a/third_party/rust/webrtc-sdp/examples/sdps/35.sdp b/third_party/rust/webrtc-sdp/examples/sdps/35.sdp new file mode 100644 index 0000000000..39ef01206d --- /dev/null +++ b/third_party/rust/webrtc-sdp/examples/sdps/35.sdp @@ -0,0 +1,9 @@ +v=0 +o=- 4294967296 2 IN IP4 127.0.0.1 +s=SIP Call +c=IN IP4 198.51.100.7 +b=CT:5000 +t=0 0 +m=video 56436 RTP/SAVPF 120 +a=rtpmap:120 VP8/90000 +a=sendrecv diff --git a/third_party/rust/webrtc-sdp/examples/sdps/36.sdp b/third_party/rust/webrtc-sdp/examples/sdps/36.sdp new file mode 100644 index 0000000000..39ef01206d --- /dev/null +++ b/third_party/rust/webrtc-sdp/examples/sdps/36.sdp @@ -0,0 +1,9 @@ +v=0 +o=- 4294967296 2 IN IP4 127.0.0.1 +s=SIP Call +c=IN IP4 198.51.100.7 +b=CT:5000 +t=0 0 +m=video 56436 RTP/SAVPF 120 +a=rtpmap:120 VP8/90000 +a=sendrecv diff --git a/third_party/rust/webrtc-sdp/examples/sdps/37.sdp b/third_party/rust/webrtc-sdp/examples/sdps/37.sdp new file mode 100644 index 0000000000..f03bbaa8ef --- /dev/null +++ b/third_party/rust/webrtc-sdp/examples/sdps/37.sdp @@ -0,0 +1,9 @@ +v=0 +o=- 4294967296 2 IN IP4 127.0.0.1 +s=SIP Call +c=IN IP4 198.51.100.7 +b=CT:5000 +t=0 0 +m=video 56436 RTP/SAVPF 120 +a=rtpmap:120 VP8/90000 +a=recvonly diff --git a/third_party/rust/webrtc-sdp/examples/sdps/38.sdp b/third_party/rust/webrtc-sdp/examples/sdps/38.sdp new file mode 100644 index 0000000000..bbe385d984 --- /dev/null +++ b/third_party/rust/webrtc-sdp/examples/sdps/38.sdp @@ -0,0 +1,9 @@ +v=0 +o=- 4294967296 2 IN IP4 127.0.0.1 +s=SIP Call +c=IN IP4 198.51.100.7 +b=CT:5000 +t=0 0 +m=video 56436 RTP/SAVPF 120 +a=rtpmap:120 VP8/90000 +a=sendonly diff --git a/third_party/rust/webrtc-sdp/examples/sdps/39.sdp b/third_party/rust/webrtc-sdp/examples/sdps/39.sdp new file mode 100644 index 0000000000..723d2abf7f --- /dev/null +++ b/third_party/rust/webrtc-sdp/examples/sdps/39.sdp @@ -0,0 +1,8 @@ +v=0 +o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0 +s=SIP Call +c=IN IP4 224.0.0.1/100/12 +t=0 0 +m=video 9 RTP/SAVPF 120 +c=IN IP4 0.0.0.0 +a=rtpmap:120 VP8/90000 diff --git a/third_party/rust/webrtc-sdp/examples/sdps/40.sdp b/third_party/rust/webrtc-sdp/examples/sdps/40.sdp new file mode 100644 index 0000000000..723d2abf7f --- /dev/null +++ b/third_party/rust/webrtc-sdp/examples/sdps/40.sdp @@ -0,0 +1,8 @@ +v=0 +o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0 +s=SIP Call +c=IN IP4 224.0.0.1/100/12 +t=0 0 +m=video 9 RTP/SAVPF 120 +c=IN IP4 0.0.0.0 +a=rtpmap:120 VP8/90000 diff --git a/third_party/rust/webrtc-sdp/examples/sdps/41.sdp b/third_party/rust/webrtc-sdp/examples/sdps/41.sdp new file mode 100644 index 0000000000..753ca5ccb4 --- /dev/null +++ b/third_party/rust/webrtc-sdp/examples/sdps/41.sdp @@ -0,0 +1,91 @@ +v=0 +o=- 1109973417102828257 2 IN IP4 127.0.0.1 +s=- +t=0 0 +a=group:BUNDLE audio video +a=msid-semantic: WMS 1PBxet5BYh0oYodwsvNM4k6KiO2eWCX40VIP +m=audio 32952 UDP/TLS/RTP/SAVPF 111 103 104 0 8 107 106 105 13 126 +c=IN IP4 128.64.32.16 +a=rtcp:32952 IN IP4 128.64.32.16 +a=candidate:77142221 1 udp 2113937151 192.168.137.1 54081 typ host generation 0 +a=candidate:77142221 2 udp 2113937151 192.168.137.1 54081 typ host generation 0 +a=candidate:983072742 1 udp 2113937151 172.22.0.56 54082 typ host generation 0 +a=candidate:983072742 2 udp 2113937151 172.22.0.56 54082 typ host generation 0 +a=candidate:2245074553 1 udp 1845501695 32.64.128.1 62397 typ srflx raddr 192.168.137.1 rport 54081 generation 0 +a=candidate:2245074553 2 udp 1845501695 32.64.128.1 62397 typ srflx raddr 192.168.137.1 rport 54081 generation 0 +a=candidate:2479353907 1 udp 1845501695 32.64.128.1 54082 typ srflx raddr 172.22.0.56 rport 54082 generation 0 +a=candidate:2479353907 2 udp 1845501695 32.64.128.1 54082 typ srflx raddr 172.22.0.56 rport 54082 generation 0 +a=candidate:1243276349 1 tcp 1509957375 192.168.137.1 0 typ host generation 0 +a=candidate:1243276349 2 tcp 1509957375 192.168.137.1 0 typ host generation 0 +a=candidate:1947960086 1 tcp 1509957375 172.22.0.56 0 typ host generation 0 +a=candidate:1947960086 2 tcp 1509957375 172.22.0.56 0 typ host generation 0 +a=candidate:1808221584 1 udp 33562367 128.64.32.16 32952 typ relay raddr 32.64.128.1 rport 62398 generation 0 +a=candidate:1808221584 2 udp 33562367 128.64.32.16 32952 typ relay raddr 32.64.128.1 rport 62398 generation 0 +a=candidate:507872740 1 udp 33562367 128.64.32.16 40975 typ relay raddr 32.64.128.1 rport 54085 generation 0 +a=candidate:507872740 2 udp 33562367 128.64.32.16 40975 typ relay raddr 32.64.128.1 rport 54085 generation 0 +a=ice-ufrag:xQuJwjX3V3eMA81k +a=ice-pwd:ZUiRmjS2GDhG140p73dAsSVP +a=ice-options:google-ice +a=fingerprint:sha-256 59:4A:8B:73:A7:73:53:71:88:D7:4D:58:28:0C:79:72:31:29:9B:05:37:DD:58:43:C2:D4:85:A2:B3:66:38:7A +a=setup:active +a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level +a=sendrecv +a=mid:audio +a=rtcp-mux +a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:/U44g3ULdtapeiSg+T3n6dDLBKIjpOhb/NXAL/2b +a=rtpmap:111 opus/48000/2 +a=rtpmap:103 ISAC/16000 +a=rtpmap:104 ISAC/32000 +a=rtpmap:0 PCMU/8000 +a=rtpmap:8 PCMA/8000 +a=rtpmap:107 CN/48000 +a=rtpmap:106 CN/32000 +a=rtpmap:105 CN/16000 +a=rtpmap:13 CN/8000 +a=rtpmap:126 telephone-event/8000 +a=maxptime:60 +a=ssrc:2271517329 cname:mKDNt7SQf6pwDlIn +a=ssrc:2271517329 msid:1PBxet5BYh0oYodwsvNM4k6KiO2eWCX40VIP 1PBxet5BYh0oYodwsvNM4k6KiO2eWCX40VIPa0 +a=ssrc:2271517329 mslabel:1PBxet5BYh0oYodwsvNM4k6KiO2eWCX40VIP +a=ssrc:2271517329 label:1PBxet5BYh0oYodwsvNM4k6KiO2eWCX40VIPa0 +m=video 32952 UDP/TLS/RTP/SAVPF 100 116 117 +c=IN IP4 128.64.32.16 +a=rtcp:32952 IN IP4 128.64.32.16 +a=candidate:77142221 1 udp 2113937151 192.168.137.1 54081 typ host generation 0 +a=candidate:77142221 2 udp 2113937151 192.168.137.1 54081 typ host generation 0 +a=candidate:983072742 1 udp 2113937151 172.22.0.56 54082 typ host generation 0 +a=candidate:983072742 2 udp 2113937151 172.22.0.56 54082 typ host generation 0 +a=candidate:2245074553 1 udp 1845501695 32.64.128.1 62397 typ srflx raddr 192.168.137.1 rport 54081 generation 0 +a=candidate:2245074553 2 udp 1845501695 32.64.128.1 62397 typ srflx raddr 192.168.137.1 rport 54081 generation 0 +a=candidate:2479353907 1 udp 1845501695 32.64.128.1 54082 typ srflx raddr 172.22.0.56 rport 54082 generation 0 +a=candidate:2479353907 2 udp 1845501695 32.64.128.1 54082 typ srflx raddr 172.22.0.56 rport 54082 generation 0 +a=candidate:1243276349 1 tcp 1509957375 192.168.137.1 0 typ host generation 0 +a=candidate:1243276349 2 tcp 1509957375 192.168.137.1 0 typ host generation 0 +a=candidate:1947960086 1 tcp 1509957375 172.22.0.56 0 typ host generation 0 +a=candidate:1947960086 2 tcp 1509957375 172.22.0.56 0 typ host generation 0 +a=candidate:1808221584 1 udp 33562367 128.64.32.16 32952 typ relay raddr 32.64.128.1 rport 62398 generation 0 +a=candidate:1808221584 2 udp 33562367 128.64.32.16 32952 typ relay raddr 32.64.128.1 rport 62398 generation 0 +a=candidate:507872740 1 udp 33562367 128.64.32.16 40975 typ relay raddr 32.64.128.1 rport 54085 generation 0 +a=candidate:507872740 2 udp 33562367 128.64.32.16 40975 typ relay raddr 32.64.128.1 rport 54085 generation 0 +a=ice-ufrag:xQuJwjX3V3eMA81k +a=ice-pwd:ZUiRmjS2GDhG140p73dAsSVP +a=ice-options:google-ice +a=fingerprint:sha-256 59:4A:8B:73:A7:73:53:71:88:D7:4D:58:28:0C:79:72:31:29:9B:05:37:DD:58:43:C2:D4:85:A2:B3:66:38:7A +a=setup:active +a=extmap:2 urn:ietf:params:rtp-hdrext:toffset +a=extmap:3 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time +a=sendrecv +a=mid:video +a=rtcp-mux +a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:/U44g3ULdtapeiSg+T3n6dDLBKIjpOhb/NXAL/2b +a=rtpmap:100 VP8/90000 +a=rtcp-fb:100 ccm fir +a=rtcp-fb:100 nack +a=rtcp-fb:100 goog-remb +a=rtpmap:116 red/90000 +a=rtpmap:117 ulpfec/90000 +a=ssrc:54724160 cname:mKDNt7SQf6pwDlIn +a=ssrc:54724160 msid:1PBxet5BYh0oYodwsvNM4k6KiO2eWCX40VIP 1PBxet5BYh0oYodwsvNM4k6KiO2eWCX40VIPv0 +a=ssrc:54724160 mslabel:1PBxet5BYh0oYodwsvNM4k6KiO2eWCX40VIP +a=ssrc:54724160 label:1PBxet5BYh0oYodwsvNM4k6KiO2eWCX40VIPv0 + diff --git a/third_party/rust/webrtc-sdp/examples/sdps/extract.sh b/third_party/rust/webrtc-sdp/examples/sdps/extract.sh new file mode 100755 index 0000000000..60f1b1c763 --- /dev/null +++ b/third_party/rust/webrtc-sdp/examples/sdps/extract.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +grep '\"[ a-z]=[^=]*$' sdp_unittests.cpp | grep -v 'ParseSdp(kVideoSdp' | grep -v 'kVideoWithRedAndUlpfec' | grep -v 'ASSERT_NE' | grep -v 'BASE64_DTLS_HELLO' | grep -v '^\/\/' | sed 's/ParseSdp(//g' | sed 's/^[[:space:]]*//' | sed 's/+ //' | sed 's/ \/\/.*$//' | sed 's/\;$//' | sed 's/)$//' | sed 's/, false//' | sed 's/" CRLF//' | sed 's/^\"//' | sed 's/\"$//' | sed 's/\\r\\n//' | gawk -v RS='(^|\n)v=' '/./ { print "v="$0 > NR".sdp" }' diff --git a/third_party/rust/webrtc-sdp/src/address.rs b/third_party/rust/webrtc-sdp/src/address.rs new file mode 100644 index 0000000000..a8cf271063 --- /dev/null +++ b/third_party/rust/webrtc-sdp/src/address.rs @@ -0,0 +1,268 @@ +/* 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/. */ + +extern crate url; +use self::url::Host; +use error::SdpParserInternalError; +use std::convert::TryFrom; +use std::fmt; +use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; +use std::str::FromStr; + +#[derive(Clone, Debug)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +pub enum Address { + Fqdn(String), + Ip(IpAddr), +} + +impl fmt::Display for Address { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Address::Fqdn(fqdn) => fqdn.fmt(f), + Address::Ip(ip) => ip.fmt(f), + } + } +} + +impl FromStr for Address { + type Err = SdpParserInternalError; + fn from_str(s: &str) -> Result<Self, Self::Err> { + let mut e: Option<SdpParserInternalError> = None; + if s.find(':').is_some() { + match IpAddr::from_str(s) { + Ok(ip) => return Ok(Address::Ip(ip)), + Err(err) => e = Some(err.into()), + } + } + Host::parse(s) + .map(|host| match host { + Host::Domain(s) => Address::Fqdn(s), + Host::Ipv4(ip) => Address::Ip(IpAddr::V4(ip)), + Host::Ipv6(ip) => Address::Ip(IpAddr::V6(ip)), + }) + .map_err(|err| e.unwrap_or_else(|| err.into())) + } +} + +impl From<ExplicitlyTypedAddress> for Address { + fn from(item: ExplicitlyTypedAddress) -> Self { + match item { + ExplicitlyTypedAddress::Fqdn { domain, .. } => Address::Fqdn(domain), + ExplicitlyTypedAddress::Ip(ip) => Address::Ip(ip), + } + } +} + +impl PartialEq for Address { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Address::Fqdn(a), Address::Fqdn(b)) => a.to_lowercase() == b.to_lowercase(), + (Address::Ip(a), Address::Ip(b)) => a == b, + (_, _) => false, + } + } +} + +#[derive(Clone, Copy, PartialEq, Debug)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +pub enum AddressType { + IpV4 = 4, + IpV6 = 6, +} + +impl fmt::Display for AddressType { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + AddressType::IpV4 => "IP4", + AddressType::IpV6 => "IP6", + } + .fmt(f) + } +} + +impl FromStr for AddressType { + type Err = SdpParserInternalError; + fn from_str(s: &str) -> Result<Self, Self::Err> { + match s.to_uppercase().as_str() { + "IP4" => Ok(AddressType::IpV4), + "IP6" => Ok(AddressType::IpV6), + _ => Err(SdpParserInternalError::UnknownAddressType(s.to_owned())), + } + } +} + +pub trait AddressTyped { + fn address_type(&self) -> AddressType; +} + +impl AddressTyped for IpAddr { + fn address_type(&self) -> AddressType { + match self { + IpAddr::V4(_) => AddressType::IpV4, + IpAddr::V6(_) => AddressType::IpV6, + } + } +} + +#[derive(Clone, Debug)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +pub enum ExplicitlyTypedAddress { + Fqdn { + address_type: AddressType, + domain: String, + }, + Ip(IpAddr), +} + +impl fmt::Display for ExplicitlyTypedAddress { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "IN {} ", self.address_type())?; + match self { + ExplicitlyTypedAddress::Fqdn { domain, .. } => domain.fmt(f), + ExplicitlyTypedAddress::Ip(ip) => ip.fmt(f), + } + } +} + +impl AddressTyped for ExplicitlyTypedAddress { + fn address_type(&self) -> AddressType { + match self { + ExplicitlyTypedAddress::Fqdn { address_type, .. } => *address_type, + ExplicitlyTypedAddress::Ip(ip) => ip.address_type(), + } + } +} + +impl From<IpAddr> for ExplicitlyTypedAddress { + fn from(item: IpAddr) -> Self { + ExplicitlyTypedAddress::Ip(item) + } +} + +impl From<Ipv4Addr> for ExplicitlyTypedAddress { + fn from(item: Ipv4Addr) -> Self { + ExplicitlyTypedAddress::Ip(IpAddr::V4(item)) + } +} + +impl From<Ipv6Addr> for ExplicitlyTypedAddress { + fn from(item: Ipv6Addr) -> Self { + ExplicitlyTypedAddress::Ip(IpAddr::V6(item)) + } +} + +impl TryFrom<(AddressType, &str)> for ExplicitlyTypedAddress { + type Error = SdpParserInternalError; + fn try_from(item: (AddressType, &str)) -> Result<Self, Self::Error> { + match Address::from_str(item.1)? { + Address::Ip(ip) => { + if ip.address_type() != item.0 { + Err(SdpParserInternalError::AddressTypeMismatch { + found: ip.address_type(), + expected: item.0, + }) + } else { + Ok(ExplicitlyTypedAddress::Ip(ip)) + } + } + Address::Fqdn(domain) => Ok(ExplicitlyTypedAddress::Fqdn { + address_type: item.0, + domain, + }), + } + } +} + +impl PartialEq for ExplicitlyTypedAddress { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + ( + ExplicitlyTypedAddress::Fqdn { + address_type: a1, + domain: d1, + }, + ExplicitlyTypedAddress::Fqdn { + address_type: a2, + domain: d2, + }, + ) => a1 == a2 && d1.to_lowercase() == d2.to_lowercase(), + (ExplicitlyTypedAddress::Ip(a), ExplicitlyTypedAddress::Ip(b)) => a == b, + (_, _) => false, + } + } +} + +#[cfg(test)] +mod tests { + use self::url::ParseError; + use super::*; + use std::error::Error; + use std::net::{AddrParseError, Ipv4Addr, Ipv6Addr}; + + #[derive(Debug)] + enum ParseTestError { + Host(ParseError), + Ip(AddrParseError), + } + impl fmt::Display for ParseTestError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + ParseTestError::Host(inner) => inner.fmt(f), + ParseTestError::Ip(inner) => inner.fmt(f), + } + } + } + impl From<ParseError> for ParseTestError { + fn from(err: ParseError) -> Self { + ParseTestError::Host(err) + } + } + impl From<AddrParseError> for ParseTestError { + fn from(err: AddrParseError) -> Self { + ParseTestError::Ip(err) + } + } + impl Error for ParseTestError { + fn source(&self) -> Option<&(dyn Error + 'static)> { + // Generic error, underlying cause isn't tracked. + match self { + ParseTestError::Host(a) => Some(a), + ParseTestError::Ip(a) => Some(a), + } + } + } + #[test] + fn test_domain_name_parsing() -> Result<(), ParseTestError> { + let address = Host::parse("this.is.a.fqdn")?; + if let Host::Domain(domain) = address { + assert_eq!(domain, "this.is.a.fqdn"); + } else { + panic!(); + } + Ok(()) + } + + #[test] + fn test_ipv4_address_parsing() -> Result<(), ParseTestError> { + let address = Host::parse("1.0.0.1")?; + if let Host::Ipv4(ip) = address { + assert_eq!(ip, "1.0.0.1".parse::<Ipv4Addr>()?); + } else { + panic!(); + } + Ok(()) + } + + #[test] + fn test_ipv6_address_parsing() -> Result<(), ParseTestError> { + let address = Host::parse("[::1]")?; + if let Host::Ipv6(ip) = address { + assert_eq!(ip, "::1".parse::<Ipv6Addr>()?); + } else { + panic!(); + } + Ok(()) + } +} diff --git a/third_party/rust/webrtc-sdp/src/anonymizer.rs b/third_party/rust/webrtc-sdp/src/anonymizer.rs new file mode 100644 index 0000000000..69e87e7f9c --- /dev/null +++ b/third_party/rust/webrtc-sdp/src/anonymizer.rs @@ -0,0 +1,315 @@ +/* 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/. */ + +extern crate url; +use address::{Address, ExplicitlyTypedAddress}; +use std::collections::HashMap; +use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; +use std::num::Wrapping; + +pub trait AnonymizingClone { + fn masked_clone(&self, anon: &mut StatefulSdpAnonymizer) -> Self; +} + +pub trait ToBytesVec { + fn to_byte_vec(&self) -> Vec<u8>; +} + +impl ToBytesVec for u64 { + fn to_byte_vec(&self) -> Vec<u8> { + let mut bytes = Vec::new(); + let mut val = *self; + for _ in 0..8 { + bytes.push(val as u8); + val <<= 8; + } + bytes.reverse(); + bytes + } +} + +/* +* Anonymizes SDP in a stateful fashion, such that a pre-anonymized value will +* always be transformed into the same anonymized value within the context of +* the anonymizer. +* Stores the opaque state necessary for intelligent anonymization of SDP. This +* state can be stored and reused during the offer-answer period, and it +* will maintain a stable set of masked values. +*/ +pub struct StatefulSdpAnonymizer { + ips: HashMap<IpAddr, IpAddr>, + ip_v4_inc: Wrapping<u32>, + ip_v6_inc: Wrapping<u128>, + host_names: AnonymizationStrMap, + ports: HashMap<u32, u32>, + port_inc: Wrapping<u32>, + origin_users: AnonymizationStrMap, + ice_passwords: AnonymizationStrMap, + ice_users: AnonymizationStrMap, + cert_finger_prints: HashMap<Vec<u8>, Vec<u8>>, + cert_finger_print_inc: Wrapping<u64>, + cnames: AnonymizationStrMap, +} + +impl Default for StatefulSdpAnonymizer { + fn default() -> Self { + Self::new() + } +} + +impl StatefulSdpAnonymizer { + pub fn new() -> Self { + StatefulSdpAnonymizer { + ips: HashMap::new(), + ip_v4_inc: Wrapping(0), + ip_v6_inc: Wrapping(0), + host_names: AnonymizationStrMap::new("fqdn-", 8), + ports: HashMap::new(), + port_inc: Wrapping(0), + origin_users: AnonymizationStrMap::new("origin-user-", 8), + ice_passwords: AnonymizationStrMap::new("ice-password-", 8), + ice_users: AnonymizationStrMap::new("ice-user-", 8), + cert_finger_prints: HashMap::new(), + cert_finger_print_inc: Wrapping(0), + cnames: AnonymizationStrMap::new("cname-", 8), + } + } + + pub fn mask_host(&mut self, host: &str) -> String { + self.host_names.mask(host) + } + + pub fn mask_ip(&mut self, addr: &IpAddr) -> IpAddr { + if let Some(address) = self.ips.get(addr) { + return *address; + } + let mapped = match addr { + IpAddr::V4(_) => { + self.ip_v4_inc += Wrapping(1); + IpAddr::V4(Ipv4Addr::from(self.ip_v4_inc.0)) + } + IpAddr::V6(_) => { + self.ip_v6_inc += Wrapping(1); + IpAddr::V6(Ipv6Addr::from(self.ip_v6_inc.0)) + } + }; + self.ips.insert(*addr, mapped); + mapped + } + + pub fn mask_address(&mut self, address: &Address) -> Address { + match address { + Address::Fqdn(host) => Address::Fqdn(self.mask_host(host)), + Address::Ip(ip) => Address::Ip(self.mask_ip(ip)), + } + } + + pub fn mask_typed_address( + &mut self, + address: &ExplicitlyTypedAddress, + ) -> ExplicitlyTypedAddress { + match address { + ExplicitlyTypedAddress::Fqdn { + address_type, + domain, + } => ExplicitlyTypedAddress::Fqdn { + address_type: *address_type, + domain: self.mask_host(domain), + }, + ExplicitlyTypedAddress::Ip(ip) => ExplicitlyTypedAddress::Ip(self.mask_ip(ip)), + } + } + + pub fn mask_port(&mut self, port: u32) -> u32 { + if let Some(stored) = self.ports.get(&port) { + return *stored; + } + self.port_inc += Wrapping(1); + self.ports.insert(port, self.port_inc.0); + self.port_inc.0 + } + + pub fn mask_origin_user(&mut self, user: &str) -> String { + self.origin_users.mask(user) + } + + pub fn mask_ice_password(&mut self, password: &str) -> String { + self.ice_passwords.mask(password) + } + + pub fn mask_ice_user(&mut self, user: &str) -> String { + self.ice_users.mask(user) + } + + pub fn mask_cert_finger_print(&mut self, finger_print: &[u8]) -> Vec<u8> { + if let Some(stored) = self.cert_finger_prints.get(finger_print) { + return stored.clone(); + } + self.cert_finger_print_inc += Wrapping(1); + self.cert_finger_prints.insert( + finger_print.to_vec(), + self.cert_finger_print_inc.0.to_byte_vec(), + ); + self.cert_finger_print_inc.0.to_byte_vec() + } + + pub fn mask_cname(&mut self, cname: &str) -> String { + self.cnames.mask(cname) + } +} + +struct AnonymizationStrMap { + map: HashMap<String, String>, + counter: Wrapping<u64>, + prefix: &'static str, + padding: usize, +} + +impl AnonymizationStrMap { + pub fn new(prefix: &'static str, padding: usize) -> Self { + Self { + map: HashMap::new(), + counter: Wrapping(0), + prefix, + padding, + } + } + + pub fn mask(&mut self, value: &str) -> String { + let key = value.to_owned(); + if let Some(stored) = self.map.get(&key) { + return stored.clone(); + } + self.counter += Wrapping(1); + let store = format!( + "{}{:0padding$}", + self.prefix, + self.counter.0, + padding = self.padding + ); + self.map.insert(key, store.clone()); + store + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_mask_ip() { + let mut anon = StatefulSdpAnonymizer::default(); + let v4 = [ + Ipv4Addr::new(127, 0, 0, 1), + Ipv4Addr::new(10, 0, 0, 1), + Ipv4Addr::new(1, 1, 1, 1), + ]; + let v4_masked = [ + Ipv4Addr::new(0, 0, 0, 1), + Ipv4Addr::new(0, 0, 0, 2), + Ipv4Addr::new(0, 0, 0, 3), + ]; + let v6 = [ + Ipv6Addr::from(0), + Ipv6Addr::from(528_189_235), + Ipv6Addr::from(1_623_734_988_148_990), + ]; + let v6_masked = [Ipv6Addr::from(1), Ipv6Addr::from(2), Ipv6Addr::from(3)]; + for _ in 0..2 { + assert_eq!(anon.mask_ip(&IpAddr::V4(v4[0])), v4_masked[0]); + assert_eq!(anon.mask_ip(&IpAddr::V6(v6[0])), v6_masked[0]); + + assert_eq!(anon.mask_ip(&IpAddr::V4(v4[1])), v4_masked[1]); + assert_eq!(anon.mask_ip(&IpAddr::V6(v6[1])), v6_masked[1]); + + assert_eq!(anon.mask_ip(&IpAddr::V4(v4[2])), v4_masked[2]); + assert_eq!(anon.mask_ip(&IpAddr::V6(v6[2])), v6_masked[2]); + } + } + + #[test] + fn test_mask_port() { + let mut anon = StatefulSdpAnonymizer::default(); + let ports = [0, 125, 12346]; + let masked_ports = [1, 2, 3]; + for _ in 0..2 { + assert_eq!(anon.mask_port(ports[0]), masked_ports[0]); + assert_eq!(anon.mask_port(ports[1]), masked_ports[1]); + assert_eq!(anon.mask_port(ports[2]), masked_ports[2]); + } + } + + #[test] + fn test_mask_ice_password() { + let mut anon = StatefulSdpAnonymizer::default(); + let passwords = ["vasdfioqwenl14082`14", "0", "ncp HY878hp(poh"]; + let masked_passwords = [ + "ice-password-00000001", + "ice-password-00000002", + "ice-password-00000003", + ]; + for _ in 0..2 { + assert_eq!(anon.mask_ice_password(passwords[0]), masked_passwords[0]); + assert_eq!(anon.mask_ice_password(passwords[1]), masked_passwords[1]); + assert_eq!(anon.mask_ice_password(passwords[2]), masked_passwords[2]); + } + } + + #[test] + fn test_mask_ice_user() { + let mut anon = StatefulSdpAnonymizer::default(); + let users = ["user1", "user2", "8109q2asdf"]; + let masked_users = [ + "ice-user-00000001", + "ice-user-00000002", + "ice-user-00000003", + ]; + for _ in 0..2 { + assert_eq!(anon.mask_ice_user(users[0]), masked_users[0]); + assert_eq!(anon.mask_ice_user(users[1]), masked_users[1]); + assert_eq!(anon.mask_ice_user(users[2]), masked_users[2]); + } + } + + #[test] + fn test_mask_cert_fingerprint() { + let mut anon = StatefulSdpAnonymizer::default(); + let prints: [Vec<u8>; 3] = [ + vec![ + 0x59u8, 0x4A, 0x8B, 0x73, 0xA7, 0x73, 0x53, 0x71, 0x88, 0xD7, 0x4D, 0x58, 0x28, + 0x0C, 0x79, 0x72, 0x31, 0x29, 0x9B, 0x05, 0x37, 0xDD, 0x58, 0x43, 0xC2, 0xD4, 0x85, + 0xA2, 0xB3, 0x66, 0x38, 0x7A, + ], + vec![ + 0x30u8, 0xFF, 0x8E, 0x2B, 0xAC, 0x9D, 0xED, 0x70, 0x18, 0x10, 0x67, 0xC8, 0xAE, + 0x9E, 0x68, 0xF3, 0x86, 0x53, 0x51, 0xB0, 0xAC, 0x31, 0xB7, 0xBE, 0x6D, 0xCF, 0xA4, + 0x2E, 0xD3, 0x6E, 0xB4, 0x28, + ], + vec![ + 0xDFu8, 0x2E, 0xAC, 0x8A, 0xFD, 0x0A, 0x8E, 0x99, 0xBF, 0x5D, 0xE8, 0x3C, 0xE7, + 0xFA, 0xFB, 0x08, 0x3B, 0x3C, 0x54, 0x1D, 0xD7, 0xD4, 0x05, 0x77, 0xA0, 0x72, 0x9B, + 0x14, 0x08, 0x6D, 0x0F, 0x4C, + ], + ]; + + let masked_prints = [1u64.to_byte_vec(), 2u64.to_byte_vec(), 3u64.to_byte_vec()]; + for _ in 0..2 { + assert_eq!(anon.mask_cert_finger_print(&prints[0]), masked_prints[0]); + assert_eq!(anon.mask_cert_finger_print(&prints[1]), masked_prints[1]); + assert_eq!(anon.mask_cert_finger_print(&prints[2]), masked_prints[2]); + } + } + + #[test] + fn test_mask_cname() { + let mut anon = StatefulSdpAnonymizer::default(); + let cnames = ["mailto:foo@bar", "JohnDoe", "Jane Doe"]; + let masked_cnames = ["cname-00000001", "cname-00000002", "cname-00000003"]; + for _ in 0..2 { + assert_eq!(anon.mask_cname(cnames[0]), masked_cnames[0]); + assert_eq!(anon.mask_cname(cnames[1]), masked_cnames[1]); + assert_eq!(anon.mask_cname(cnames[2]), masked_cnames[2]); + } + } +} diff --git a/third_party/rust/webrtc-sdp/src/attribute_type.rs b/third_party/rust/webrtc-sdp/src/attribute_type.rs new file mode 100644 index 0000000000..431ef87772 --- /dev/null +++ b/third_party/rust/webrtc-sdp/src/attribute_type.rs @@ -0,0 +1,4413 @@ +/* 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/. */ + +extern crate url; +use std::convert::TryFrom; +use std::fmt; +use std::iter; +use std::str::FromStr; + +use error::SdpParserInternalError; +use network::{parse_network_type, parse_unicast_address}; +use SdpType; + +use address::{Address, AddressType, ExplicitlyTypedAddress}; +use anonymizer::{AnonymizingClone, StatefulSdpAnonymizer}; + +// Serialization helper marcos and functions +#[macro_export] +macro_rules! option_to_string { + ($fmt_str:expr, $opt:expr) => { + match $opt { + Some(ref x) => format!($fmt_str, x), + None => "".to_string(), + } + }; +} + +#[macro_export] +macro_rules! write_option_string { + ($f:expr, $fmt_str:expr, $opt:expr) => { + match $opt { + Some(ref x) => write!($f, $fmt_str, x), + None => Ok(()), + } + }; +} + +#[macro_export] +macro_rules! maybe_vector_to_string { + ($fmt_str:expr, $vec:expr, $sep:expr) => { + match $vec.len() { + 0 => "".to_string(), + _ => format!( + $fmt_str, + $vec.iter() + .map(ToString::to_string) + .collect::<Vec<String>>() + .join($sep) + ), + } + }; +} + +#[macro_export] +macro_rules! non_empty_string_vec { + ( $( $x:expr ),* ) => { + { + let mut temp_vec = Vec::new(); + $( + if !$x.is_empty() { + temp_vec.push($x); + } + )* + temp_vec + } + }; +} + +pub fn maybe_print_param<T>(name: &str, param: T, default_value: T) -> String +where + T: PartialEq + ToString, +{ + if param != default_value { + name.to_owned() + ¶m.to_string() + } else { + "".to_string() + } +} + +pub fn maybe_print_bool_param(name: &str, param: bool, default_value: bool) -> String { + if param != default_value { + name.to_owned() + "=" + &(param as i32).to_string() + } else { + "".to_string() + } +} + +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +pub enum SdpSingleDirection { + // This is explicitly 1 and 2 to match the defines in the C++ glue code. + Send = 1, + Recv = 2, +} + +impl fmt::Display for SdpSingleDirection { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + SdpSingleDirection::Send => "send", + SdpSingleDirection::Recv => "recv", + } + .fmt(f) + } +} + +#[derive(Debug, PartialEq, Clone)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +pub enum SdpAttributePayloadType { + PayloadType(u8), + Wildcard, // Wildcard means "*", +} + +impl fmt::Display for SdpAttributePayloadType { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + SdpAttributePayloadType::PayloadType(pt) => pt.fmt(f), + SdpAttributePayloadType::Wildcard => "*".fmt(f), + } + } +} + +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +pub enum SdpAttributeCandidateTransport { + Udp, + Tcp, +} + +impl fmt::Display for SdpAttributeCandidateTransport { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + SdpAttributeCandidateTransport::Udp => "UDP", + SdpAttributeCandidateTransport::Tcp => "TCP", + } + .fmt(f) + } +} + +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +pub enum SdpAttributeCandidateType { + Host, + Srflx, + Prflx, + Relay, +} + +impl fmt::Display for SdpAttributeCandidateType { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + SdpAttributeCandidateType::Host => "host", + SdpAttributeCandidateType::Srflx => "srflx", + SdpAttributeCandidateType::Prflx => "prflx", + SdpAttributeCandidateType::Relay => "relay", + } + .fmt(f) + } +} + +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +pub enum SdpAttributeCandidateTcpType { + Active, + Passive, + Simultaneous, +} + +impl fmt::Display for SdpAttributeCandidateTcpType { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + SdpAttributeCandidateTcpType::Active => "active", + SdpAttributeCandidateTcpType::Passive => "passive", + SdpAttributeCandidateTcpType::Simultaneous => "so", + } + .fmt(f) + } +} + +#[derive(Clone)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "enhanced_debug", derive(Debug))] +pub struct SdpAttributeCandidate { + pub foundation: String, + pub component: u32, + pub transport: SdpAttributeCandidateTransport, + pub priority: u64, + pub address: Address, + pub port: u32, + pub c_type: SdpAttributeCandidateType, + pub raddr: Option<Address>, + pub rport: Option<u32>, + pub tcp_type: Option<SdpAttributeCandidateTcpType>, + pub generation: Option<u32>, + pub ufrag: Option<String>, + pub networkcost: Option<u32>, + pub unknown_extensions: Vec<(String, String)>, +} + +impl fmt::Display for SdpAttributeCandidate { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "{foundation} {component} {transport} {priority} \ + {address} {port} typ {ctype}\ + {raddr}{rport}{tcp_type}{generation}{ufrag}{cost}\ + {unknown}", + foundation = self.foundation, + component = self.component, + transport = self.transport, + priority = self.priority, + address = self.address, + port = self.port, + ctype = self.c_type, + raddr = option_to_string!(" raddr {}", self.raddr), + rport = option_to_string!(" rport {}", self.rport), + tcp_type = option_to_string!(" tcptype {}", self.tcp_type), + generation = option_to_string!(" generation {}", self.generation), + ufrag = option_to_string!(" ufrag {}", self.ufrag), + cost = option_to_string!(" network-cost {}", self.networkcost), + unknown = self + .unknown_extensions + .iter() + .map(|&(ref name, ref value)| format!(" {} {}", name, value)) + .collect::<String>() + ) + } +} + +impl SdpAttributeCandidate { + pub fn new( + foundation: String, + component: u32, + transport: SdpAttributeCandidateTransport, + priority: u64, + address: Address, + port: u32, + c_type: SdpAttributeCandidateType, + ) -> SdpAttributeCandidate { + SdpAttributeCandidate { + foundation, + component, + transport, + priority, + address, + port, + c_type, + raddr: None, + rport: None, + tcp_type: None, + generation: None, + ufrag: None, + networkcost: None, + unknown_extensions: Vec::new(), + } + } + + fn set_remote_address(&mut self, addr: Address) { + self.raddr = Some(addr) + } + + fn set_remote_port(&mut self, p: u32) { + self.rport = Some(p) + } + + fn set_tcp_type(&mut self, t: SdpAttributeCandidateTcpType) { + self.tcp_type = Some(t) + } + + fn set_generation(&mut self, g: u32) { + self.generation = Some(g) + } + + fn set_ufrag(&mut self, u: String) { + self.ufrag = Some(u) + } + + fn set_network_cost(&mut self, n: u32) { + self.networkcost = Some(n) + } + + fn add_unknown_extension(&mut self, name: String, value: String) { + self.unknown_extensions.push((name, value)); + } +} + +impl AnonymizingClone for SdpAttributeCandidate { + fn masked_clone(&self, anonymizer: &mut StatefulSdpAnonymizer) -> Self { + let mut masked = self.clone(); + masked.address = anonymizer.mask_address(&self.address); + masked.port = anonymizer.mask_port(self.port); + masked.raddr = self + .raddr + .clone() + .map(|addr| anonymizer.mask_address(&addr)); + masked.rport = self.rport.map(|port| anonymizer.mask_port(port)); + masked + } +} + +#[derive(Clone)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "enhanced_debug", derive(Debug))] +pub enum SdpAttributeDtlsMessage { + Client(String), + Server(String), +} + +impl fmt::Display for SdpAttributeDtlsMessage { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + SdpAttributeDtlsMessage::Client(ref msg) => format!("client {}", msg), + SdpAttributeDtlsMessage::Server(ref msg) => format!("server {}", msg), + } + .fmt(f) + } +} + +#[derive(Clone)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "enhanced_debug", derive(Debug))] +pub struct SdpAttributeRemoteCandidate { + pub component: u32, + pub address: Address, + pub port: u32, +} + +impl fmt::Display for SdpAttributeRemoteCandidate { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "{component} {addr} {port}", + component = self.component, + addr = self.address, + port = self.port + ) + } +} + +impl AnonymizingClone for SdpAttributeRemoteCandidate { + fn masked_clone(&self, anon: &mut StatefulSdpAnonymizer) -> Self { + SdpAttributeRemoteCandidate { + address: anon.mask_address(&self.address), + port: anon.mask_port(self.port), + component: self.component, + } + } +} + +#[derive(Clone)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "enhanced_debug", derive(Debug))] +pub struct SdpAttributeSimulcastId { + pub id: String, + pub paused: bool, +} + +impl SdpAttributeSimulcastId { + pub fn new(idstr: &str) -> SdpAttributeSimulcastId { + if let Some(idstr) = idstr.strip_prefix('~') { + SdpAttributeSimulcastId { + id: idstr.to_string(), + paused: true, + } + } else { + SdpAttributeSimulcastId { + id: idstr.to_string(), + paused: false, + } + } + } +} + +impl fmt::Display for SdpAttributeSimulcastId { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + if self.paused { + write!(f, "~")?; + } + self.id.fmt(f) + } +} + +#[repr(C)] +#[derive(Clone)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "enhanced_debug", derive(Debug))] +pub struct SdpAttributeSimulcastVersion { + pub ids: Vec<SdpAttributeSimulcastId>, +} + +impl SdpAttributeSimulcastVersion { + pub fn new(idlist: &str) -> SdpAttributeSimulcastVersion { + SdpAttributeSimulcastVersion { + ids: idlist + .split(',') + .map(SdpAttributeSimulcastId::new) + .collect(), + } + } +} + +impl fmt::Display for SdpAttributeSimulcastVersion { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.ids + .iter() + .map(ToString::to_string) + .collect::<Vec<String>>() + .join(",") + .fmt(f) + } +} + +#[derive(Clone)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "enhanced_debug", derive(Debug))] +pub struct SdpAttributeSimulcast { + pub send: Vec<SdpAttributeSimulcastVersion>, + pub receive: Vec<SdpAttributeSimulcastVersion>, +} + +impl fmt::Display for SdpAttributeSimulcast { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + non_empty_string_vec![ + maybe_vector_to_string!("send {}", self.send, ";"), + maybe_vector_to_string!("recv {}", self.receive, ";") + ] + .join(" ") + .fmt(f) + } +} + +#[derive(Clone)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "enhanced_debug", derive(Debug))] +pub struct SdpAttributeRtcp { + pub port: u16, + pub unicast_addr: Option<ExplicitlyTypedAddress>, +} + +impl SdpAttributeRtcp { + pub fn new(port: u16) -> SdpAttributeRtcp { + SdpAttributeRtcp { + port, + unicast_addr: None, + } + } + + fn set_addr(&mut self, addr: ExplicitlyTypedAddress) { + self.unicast_addr = Some(addr) + } +} + +impl fmt::Display for SdpAttributeRtcp { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self.unicast_addr { + Some(ref addr) => write!(f, "{} {}", self.port, addr), + None => self.port.fmt(f), + } + } +} + +#[derive(Clone)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "enhanced_debug", derive(Debug))] +pub enum SdpAttributeRtcpFbType { + Ack = 0, + Ccm = 2, // This is explicitly 2 to make the conversion to the + // enum used in the glue-code possible. The glue code has "app" + // in the place of 1 + Nack, + TrrInt, + Remb, + TransCC, +} + +impl fmt::Display for SdpAttributeRtcpFbType { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + SdpAttributeRtcpFbType::Ack => "ack", + SdpAttributeRtcpFbType::Ccm => "ccm", + SdpAttributeRtcpFbType::Nack => "nack", + SdpAttributeRtcpFbType::TrrInt => "trr-int", + SdpAttributeRtcpFbType::Remb => "goog-remb", + SdpAttributeRtcpFbType::TransCC => "transport-cc", + } + .fmt(f) + } +} + +#[derive(Clone)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "enhanced_debug", derive(Debug))] +pub struct SdpAttributeRtcpFb { + pub payload_type: SdpAttributePayloadType, + pub feedback_type: SdpAttributeRtcpFbType, + pub parameter: String, + pub extra: String, +} + +impl fmt::Display for SdpAttributeRtcpFb { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{} {}", self.payload_type, self.feedback_type,)?; + if !self.parameter.is_empty() { + write!( + f, + " {}{}", + self.parameter, + maybe_print_param(" ", self.extra.clone(), "".to_string()), + )?; + } + Ok(()) + } +} + +#[derive(Clone)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "enhanced_debug", derive(Debug))] +pub enum SdpAttributeDirection { + Recvonly, + Sendonly, + Sendrecv, +} + +impl fmt::Display for SdpAttributeDirection { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + SdpAttributeDirection::Recvonly => "recvonly", + SdpAttributeDirection::Sendonly => "sendonly", + SdpAttributeDirection::Sendrecv => "sendrecv", + } + .fmt(f) + } +} + +#[derive(Clone)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "enhanced_debug", derive(Debug))] +pub struct SdpAttributeExtmap { + pub id: u16, + pub direction: Option<SdpAttributeDirection>, + pub url: String, + pub extension_attributes: Option<String>, +} + +impl fmt::Display for SdpAttributeExtmap { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "{id}{direction} {url}{ext}", + id = self.id, + direction = option_to_string!("/{}", self.direction), + url = self.url, + ext = option_to_string!(" {}", self.extension_attributes) + ) + } +} + +#[derive(Clone, Copy)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "enhanced_debug", derive(Debug))] +pub struct RtxFmtpParameters { + pub apt: u8, + pub rtx_time: Option<u32>, +} + +impl fmt::Display for RtxFmtpParameters { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + if let Some(rtx_time) = self.rtx_time { + write!(f, "apt={};rtx-time={}", self.apt, rtx_time) + } else { + write!(f, "apt={}", self.apt) + } + } +} + +#[derive(Clone)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "enhanced_debug", derive(Debug))] +pub struct SdpAttributeFmtpParameters { + // H264 + // TODO(bug 1466859): Support sprop-parameter-sets + // pub static const max_sprop_len: u32 = 128, + // pub sprop_parameter_sets: [u8, max_sprop_len], + pub packetization_mode: u32, + pub level_asymmetry_allowed: bool, + pub profile_level_id: u32, + pub max_fs: u32, + pub max_cpb: u32, + pub max_dpb: u32, + pub max_br: u32, + pub max_mbps: u32, + + // VP8 and VP9 + // max_fs, already defined in H264 + pub max_fr: u32, + + // Opus https://tools.ietf.org/html/rfc7587 + pub maxplaybackrate: u32, + pub maxaveragebitrate: u32, + pub usedtx: bool, + pub stereo: bool, + pub useinbandfec: bool, + pub cbr: bool, + pub ptime: u32, + pub minptime: u32, + pub maxptime: u32, + + // Red + pub encodings: Vec<u8>, + + // telephone-event + pub dtmf_tones: String, + + // RTX + pub rtx: Option<RtxFmtpParameters>, + + // Unknown + pub unknown_tokens: Vec<String>, +} + +impl fmt::Display for SdpAttributeFmtpParameters { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + if let Some(ref rtx) = self.rtx { + return write!(f, "{}", rtx); + } + write!( + f, + "{parameters}{red}{dtmf}{unknown}", + parameters = non_empty_string_vec![ + maybe_print_param( + "profile-level-id=", + format!("{:06x}", self.profile_level_id), + "420010".to_string() + ), + maybe_print_bool_param( + "level-asymmetry-allowed", + self.level_asymmetry_allowed, + false + ), + maybe_print_param("packetization-mode=", self.packetization_mode, 0), + maybe_print_param("max-fs=", self.max_fs, 0), + maybe_print_param("max-cpb=", self.max_cpb, 0), + maybe_print_param("max-dpb=", self.max_dpb, 0), + maybe_print_param("max-br=", self.max_br, 0), + maybe_print_param("max-mbps=", self.max_mbps, 0), + maybe_print_param("max-fr=", self.max_fr, 0), + maybe_print_param("maxplaybackrate=", self.maxplaybackrate, 48000), + maybe_print_param("maxaveragebitrate=", self.maxaveragebitrate, 0), + maybe_print_param("ptime=", self.ptime, 0), + maybe_print_param("minptime=", self.minptime, 0), + maybe_print_param("maxptime=", self.maxptime, 0), + maybe_print_bool_param("usedtx", self.usedtx, false), + maybe_print_bool_param("stereo", self.stereo, false), + maybe_print_bool_param("useinbandfec", self.useinbandfec, false), + maybe_print_bool_param("cbr", self.cbr, false) + ] + .join(";"), + red = maybe_vector_to_string!("{}", self.encodings, "/"), + dtmf = maybe_print_param("", self.dtmf_tones.clone(), "".to_string()), + unknown = maybe_vector_to_string!("{}", self.unknown_tokens, ",") + ) + } +} + +#[derive(Clone)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "enhanced_debug", derive(Debug))] +pub struct SdpAttributeFmtp { + pub payload_type: u8, + pub parameters: SdpAttributeFmtpParameters, +} + +impl fmt::Display for SdpAttributeFmtp { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "{pt} {parameter}", + pt = self.payload_type, + parameter = self.parameters + ) + } +} + +#[derive(Clone, Copy)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "enhanced_debug", derive(Debug))] +pub enum SdpAttributeFingerprintHashType { + Sha1, + Sha224, + Sha256, + Sha384, + Sha512, +} + +impl fmt::Display for SdpAttributeFingerprintHashType { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + SdpAttributeFingerprintHashType::Sha1 => "sha-1", + SdpAttributeFingerprintHashType::Sha224 => "sha-224", + SdpAttributeFingerprintHashType::Sha256 => "sha-256", + SdpAttributeFingerprintHashType::Sha384 => "sha-384", + SdpAttributeFingerprintHashType::Sha512 => "sha-512", + } + .fmt(f) + } +} + +#[derive(Clone)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "enhanced_debug", derive(Debug))] +pub struct SdpAttributeFingerprint { + pub hash_algorithm: SdpAttributeFingerprintHashType, + pub fingerprint: Vec<u8>, +} + +impl fmt::Display for SdpAttributeFingerprint { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "{hash} {fp}", + hash = self.hash_algorithm, + fp = self + .fingerprint + .iter() + .map(|byte| format!("{:02X}", byte)) + .collect::<Vec<String>>() + .join(":") + ) + } +} + +impl AnonymizingClone for SdpAttributeFingerprint { + fn masked_clone(&self, anon: &mut StatefulSdpAnonymizer) -> Self { + SdpAttributeFingerprint { + hash_algorithm: self.hash_algorithm, + fingerprint: anon.mask_cert_finger_print(&self.fingerprint), + } + } +} + +fn imageattr_discrete_value_list_to_string<T>(values: &[T]) -> String +where + T: ToString, +{ + match values.len() { + 1 => values[0].to_string(), + _ => format!( + "[{}]", + values + .iter() + .map(ToString::to_string) + .collect::<Vec<String>>() + .join(",") + ), + } +} + +#[derive(Debug, PartialEq, Clone)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +pub enum SdpAttributeImageAttrXYRange { + Range(u32, u32, Option<u32>), // min, max, step + DiscreteValues(Vec<u32>), +} + +impl fmt::Display for SdpAttributeImageAttrXYRange { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + SdpAttributeImageAttrXYRange::Range(ref min, ref max, ref step_opt) => { + write!(f, "[{}:", min)?; + if step_opt.is_some() { + write!(f, "{}:", step_opt.unwrap())?; + } + write!(f, "{}]", max) + } + SdpAttributeImageAttrXYRange::DiscreteValues(ref values) => { + write!(f, "{}", imageattr_discrete_value_list_to_string(values)) + } + } + } +} + +#[derive(Debug, PartialEq, Clone)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +pub enum SdpAttributeImageAttrSRange { + Range(f32, f32), // min, max + DiscreteValues(Vec<f32>), +} + +impl fmt::Display for SdpAttributeImageAttrSRange { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + SdpAttributeImageAttrSRange::Range(ref min, ref max) => write!(f, "[{}-{}]", min, max), + SdpAttributeImageAttrSRange::DiscreteValues(ref values) => { + write!(f, "{}", imageattr_discrete_value_list_to_string(values)) + } + } + } +} + +#[derive(Debug, PartialEq, Clone)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +pub struct SdpAttributeImageAttrPRange { + pub min: f32, + pub max: f32, +} + +impl fmt::Display for SdpAttributeImageAttrPRange { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "[{}-{}]", self.min, self.max) + } +} + +#[derive(Debug, PartialEq, Clone)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +pub struct SdpAttributeImageAttrSet { + pub x: SdpAttributeImageAttrXYRange, + pub y: SdpAttributeImageAttrXYRange, + pub sar: Option<SdpAttributeImageAttrSRange>, + pub par: Option<SdpAttributeImageAttrPRange>, + pub q: Option<f32>, +} + +impl fmt::Display for SdpAttributeImageAttrSet { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "[x={x},y={y}", x = self.x, y = self.y)?; + write_option_string!(f, ",sar={}", self.sar)?; + write_option_string!(f, ",par={}", self.par)?; + write_option_string!(f, ",q={}", self.q)?; + write!(f, "]") + } +} + +#[derive(Debug, PartialEq, Clone)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +pub enum SdpAttributeImageAttrSetList { + Sets(Vec<SdpAttributeImageAttrSet>), + Wildcard, +} + +impl fmt::Display for SdpAttributeImageAttrSetList { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + SdpAttributeImageAttrSetList::Sets(ref sets) => sets + .iter() + .map(ToString::to_string) + .collect::<Vec<String>>() + .join(" ") + .fmt(f), + SdpAttributeImageAttrSetList::Wildcard => "*".fmt(f), + } + } +} + +#[derive(Clone)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "enhanced_debug", derive(Debug))] +pub struct SdpAttributeImageAttr { + pub pt: SdpAttributePayloadType, + pub send: SdpAttributeImageAttrSetList, + pub recv: SdpAttributeImageAttrSetList, +} + +impl fmt::Display for SdpAttributeImageAttr { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let maybe_sets_to_string = |set_list| match set_list { + SdpAttributeImageAttrSetList::Sets(sets) => match sets.len() { + 0 => None, + _ => Some(SdpAttributeImageAttrSetList::Sets(sets)), + }, + x => Some(x), + }; + self.pt.fmt(f)?; + write_option_string!(f, " send {}", maybe_sets_to_string(self.send.clone()))?; + write_option_string!(f, " recv {}", maybe_sets_to_string(self.recv.clone())) + } +} + +#[derive(Clone)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "enhanced_debug", derive(Debug))] +pub struct SdpAttributeSctpmap { + pub port: u16, + pub channels: u32, +} + +impl fmt::Display for SdpAttributeSctpmap { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "{port} webrtc-datachannel {channels}", + port = self.port, + channels = self.channels + ) + } +} + +#[derive(Clone)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "enhanced_debug", derive(Debug))] +pub enum SdpAttributeGroupSemantic { + LipSynchronization, // RFC5888 + FlowIdentification, // RFC5888 + SingleReservationFlow, // RFC3524 + AlternateNetworkAddressType, // RFC4091 + ForwardErrorCorrection, // RFC5956 + DecodingDependency, // RFC5583 + Bundle, // draft-ietc-mmusic-bundle +} + +impl fmt::Display for SdpAttributeGroupSemantic { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + SdpAttributeGroupSemantic::LipSynchronization => "LS", + SdpAttributeGroupSemantic::FlowIdentification => "FID", + SdpAttributeGroupSemantic::SingleReservationFlow => "SRF", + SdpAttributeGroupSemantic::AlternateNetworkAddressType => "ANAT", + SdpAttributeGroupSemantic::ForwardErrorCorrection => "FEC", + SdpAttributeGroupSemantic::DecodingDependency => "DDP", + SdpAttributeGroupSemantic::Bundle => "BUNDLE", + } + .fmt(f) + } +} + +#[derive(Clone)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "enhanced_debug", derive(Debug))] +pub struct SdpAttributeGroup { + pub semantics: SdpAttributeGroupSemantic, + pub tags: Vec<String>, +} + +impl fmt::Display for SdpAttributeGroup { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "{}{}", + self.semantics, + maybe_vector_to_string!(" {}", self.tags, " ") + ) + } +} + +#[derive(Clone)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "enhanced_debug", derive(Debug))] +pub struct SdpAttributeMsid { + pub id: String, + pub appdata: Option<String>, +} + +impl fmt::Display for SdpAttributeMsid { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.id.fmt(f)?; + write_option_string!(f, " {}", self.appdata) + } +} + +#[derive(Clone, Debug)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +pub struct SdpAttributeMsidSemantic { + pub semantic: String, + pub msids: Vec<String>, +} + +impl fmt::Display for SdpAttributeMsidSemantic { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{} ", self.semantic)?; + match self.msids.len() { + 0 => "*".fmt(f), + _ => self.msids.join(" ").fmt(f), + } + } +} + +#[derive(Clone)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "enhanced_debug", derive(Debug))] +pub struct SdpAttributeRidParameters { + pub max_width: u32, + pub max_height: u32, + pub max_fps: u32, + pub max_fs: u32, + pub max_br: u32, + pub max_pps: u32, + + pub unknown: Vec<String>, +} + +impl fmt::Display for SdpAttributeRidParameters { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + non_empty_string_vec![ + maybe_print_param("max-width=", self.max_width, 0), + maybe_print_param("max-height=", self.max_height, 0), + maybe_print_param("max-fps=", self.max_fps, 0), + maybe_print_param("max-fs=", self.max_fs, 0), + maybe_print_param("max-br=", self.max_br, 0), + maybe_print_param("max-pps=", self.max_pps, 0), + maybe_vector_to_string!("{}", self.unknown, ";") + ] + .join(";") + .fmt(f) + } +} + +#[derive(Clone)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "enhanced_debug", derive(Debug))] +pub struct SdpAttributeRid { + pub id: String, + pub direction: SdpSingleDirection, + pub formats: Vec<u16>, + pub params: SdpAttributeRidParameters, + pub depends: Vec<String>, +} + +impl fmt::Display for SdpAttributeRid { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "{id} {direction}{format}", + id = self.id, + direction = self.direction, + format = match non_empty_string_vec![ + maybe_vector_to_string!("pt={}", self.formats, ","), + self.params.to_string(), + maybe_vector_to_string!("depends={}", self.depends, ",") + ] + .join(";") + .as_str() + { + "" => "".to_string(), + x => format!(" {}", x), + } + ) + } +} + +#[derive(Clone)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "enhanced_debug", derive(Debug))] +pub struct SdpAttributeRtpmap { + pub payload_type: u8, + pub codec_name: String, + pub frequency: u32, + pub channels: Option<u32>, +} + +impl SdpAttributeRtpmap { + pub fn new(payload_type: u8, codec_name: String, frequency: u32) -> SdpAttributeRtpmap { + SdpAttributeRtpmap { + payload_type, + codec_name, + frequency, + channels: None, + } + } + + fn set_channels(&mut self, c: u32) { + self.channels = Some(c) + } +} + +impl fmt::Display for SdpAttributeRtpmap { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "{pt} {name}/{freq}", + pt = self.payload_type, + name = self.codec_name, + freq = self.frequency + )?; + write_option_string!(f, "/{}", self.channels) + } +} + +#[derive(Clone)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "enhanced_debug", derive(Debug))] +pub enum SdpAttributeSetup { + Active, + Actpass, + Holdconn, + Passive, +} + +impl fmt::Display for SdpAttributeSetup { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + SdpAttributeSetup::Active => "active", + SdpAttributeSetup::Actpass => "actpass", + SdpAttributeSetup::Holdconn => "holdconn", + SdpAttributeSetup::Passive => "passive", + } + .fmt(f) + } +} + +#[derive(Clone)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "enhanced_debug", derive(Debug))] +pub struct SdpAttributeSsrc { + pub id: u32, + pub attribute: Option<String>, + pub value: Option<String>, +} + +impl SdpAttributeSsrc { + pub fn new(id: u32) -> SdpAttributeSsrc { + SdpAttributeSsrc { + id, + attribute: None, + value: None, + } + } + + fn set_attribute(&mut self, a: &str) { + if a.find(':') == None { + self.attribute = Some(a.to_string()); + } else { + let v: Vec<&str> = a.splitn(2, ':').collect(); + self.attribute = Some(v[0].to_string()); + self.value = Some(v[1].to_string()); + } + } +} + +impl fmt::Display for SdpAttributeSsrc { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.id.fmt(f)?; + write_option_string!(f, " {}", self.attribute)?; + write_option_string!(f, ":{}", self.value) + } +} + +impl AnonymizingClone for SdpAttributeSsrc { + fn masked_clone(&self, anon: &mut StatefulSdpAnonymizer) -> Self { + Self { + id: self.id, + attribute: self.attribute.clone(), + value: self.attribute.as_ref().and_then(|attribute| { + match (attribute.to_lowercase().as_str(), &self.value) { + ("cname", Some(ref cname)) => Some(anon.mask_cname(cname.as_str())), + (_, Some(_)) => self.value.clone(), + (_, None) => None, + } + }), + } + } +} + +#[derive(Clone)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "enhanced_debug", derive(Debug))] +pub enum SdpSsrcGroupSemantic { + Duplication, // RFC7104 + FlowIdentification, // RFC5576 + ForwardErrorCorrection, // RFC5576 + ForwardErrorCorrectionFR, // RFC5956 + SIM, // not registered with IANA, but used in hangouts +} + +impl fmt::Display for SdpSsrcGroupSemantic { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + SdpSsrcGroupSemantic::Duplication => "DUP", + SdpSsrcGroupSemantic::FlowIdentification => "FID", + SdpSsrcGroupSemantic::ForwardErrorCorrection => "FEC", + SdpSsrcGroupSemantic::ForwardErrorCorrectionFR => "FEC-FR", + SdpSsrcGroupSemantic::SIM => "SIM", + } + .fmt(f) + } +} + +#[derive(Clone)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "enhanced_debug", derive(Debug))] +pub enum SdpAttribute { + BundleOnly, + Candidate(SdpAttributeCandidate), + DtlsMessage(SdpAttributeDtlsMessage), + EndOfCandidates, + Extmap(SdpAttributeExtmap), + ExtmapAllowMixed, + Fingerprint(SdpAttributeFingerprint), + Fmtp(SdpAttributeFmtp), + Group(SdpAttributeGroup), + IceLite, + IceMismatch, + IceOptions(Vec<String>), + IcePacing(u64), + IcePwd(String), + IceUfrag(String), + Identity(String), + ImageAttr(SdpAttributeImageAttr), + Inactive, + Label(String), + MaxMessageSize(u64), + MaxPtime(u64), + Mid(String), + Msid(SdpAttributeMsid), + MsidSemantic(SdpAttributeMsidSemantic), + Ptime(u64), + Rid(SdpAttributeRid), + Recvonly, + RemoteCandidate(SdpAttributeRemoteCandidate), + Rtpmap(SdpAttributeRtpmap), + Rtcp(SdpAttributeRtcp), + Rtcpfb(SdpAttributeRtcpFb), + RtcpMux, + RtcpRsize, + Sctpmap(SdpAttributeSctpmap), + SctpPort(u64), + Sendonly, + Sendrecv, + Setup(SdpAttributeSetup), + Simulcast(SdpAttributeSimulcast), + Ssrc(SdpAttributeSsrc), + SsrcGroup(SdpSsrcGroupSemantic, Vec<SdpAttributeSsrc>), +} + +impl SdpAttribute { + pub fn allowed_at_session_level(&self) -> bool { + match *self { + SdpAttribute::BundleOnly + | SdpAttribute::Candidate(..) + | SdpAttribute::Fmtp(..) + | SdpAttribute::IceMismatch + | SdpAttribute::ImageAttr(..) + | SdpAttribute::Label(..) + | SdpAttribute::MaxMessageSize(..) + | SdpAttribute::MaxPtime(..) + | SdpAttribute::Mid(..) + | SdpAttribute::Msid(..) + | SdpAttribute::Ptime(..) + | SdpAttribute::Rid(..) + | SdpAttribute::RemoteCandidate(..) + | SdpAttribute::Rtpmap(..) + | SdpAttribute::Rtcp(..) + | SdpAttribute::Rtcpfb(..) + | SdpAttribute::RtcpMux + | SdpAttribute::RtcpRsize + | SdpAttribute::Sctpmap(..) + | SdpAttribute::SctpPort(..) + | SdpAttribute::Simulcast(..) + | SdpAttribute::Ssrc(..) + | SdpAttribute::SsrcGroup(..) => false, + + SdpAttribute::DtlsMessage { .. } + | SdpAttribute::EndOfCandidates + | SdpAttribute::Extmap(..) + | SdpAttribute::ExtmapAllowMixed + | SdpAttribute::Fingerprint(..) + | SdpAttribute::Group(..) + | SdpAttribute::IceLite + | SdpAttribute::IceOptions(..) + | SdpAttribute::IcePacing(..) + | SdpAttribute::IcePwd(..) + | SdpAttribute::IceUfrag(..) + | SdpAttribute::Identity(..) + | SdpAttribute::Inactive + | SdpAttribute::MsidSemantic(..) + | SdpAttribute::Recvonly + | SdpAttribute::Sendonly + | SdpAttribute::Sendrecv + | SdpAttribute::Setup(..) => true, + } + } + + pub fn allowed_at_media_level(&self) -> bool { + match *self { + SdpAttribute::DtlsMessage { .. } + | SdpAttribute::Group(..) + | SdpAttribute::IceLite + | SdpAttribute::IcePacing(..) + | SdpAttribute::Identity(..) + | SdpAttribute::MsidSemantic(..) => false, + + SdpAttribute::BundleOnly + | SdpAttribute::Candidate(..) + | SdpAttribute::EndOfCandidates + | SdpAttribute::Extmap(..) + | SdpAttribute::ExtmapAllowMixed + | SdpAttribute::Fingerprint(..) + | SdpAttribute::Fmtp(..) + | SdpAttribute::IceMismatch + | SdpAttribute::IceOptions(..) + | SdpAttribute::IcePwd(..) + | SdpAttribute::IceUfrag(..) + | SdpAttribute::ImageAttr(..) + | SdpAttribute::Inactive + | SdpAttribute::Label(..) + | SdpAttribute::MaxMessageSize(..) + | SdpAttribute::MaxPtime(..) + | SdpAttribute::Mid(..) + | SdpAttribute::Msid(..) + | SdpAttribute::Ptime(..) + | SdpAttribute::Rid(..) + | SdpAttribute::Recvonly + | SdpAttribute::RemoteCandidate(..) + | SdpAttribute::Rtpmap(..) + | SdpAttribute::Rtcp(..) + | SdpAttribute::Rtcpfb(..) + | SdpAttribute::RtcpMux + | SdpAttribute::RtcpRsize + | SdpAttribute::Sctpmap(..) + | SdpAttribute::SctpPort(..) + | SdpAttribute::Sendonly + | SdpAttribute::Sendrecv + | SdpAttribute::Setup(..) + | SdpAttribute::Simulcast(..) + | SdpAttribute::Ssrc(..) + | SdpAttribute::SsrcGroup(..) => true, + } + } +} + +impl FromStr for SdpAttribute { + type Err = SdpParserInternalError; + + fn from_str(line: &str) -> Result<Self, Self::Err> { + let tokens: Vec<_> = line.splitn(2, ':').collect(); + let name = tokens[0].to_lowercase(); + let val = match tokens.get(1) { + Some(x) => x.trim(), + None => "", + }; + if tokens.len() > 1 { + match name.as_str() { + "bundle-only" | "end-of-candidates" | "extmap-allow-mixed" | "ice-lite" + | "ice-mismatch" | "inactive" | "recvonly" | "rtcp-mux" | "rtcp-rsize" + | "sendonly" | "sendrecv" => { + return Err(SdpParserInternalError::Generic(format!( + "{} attribute is not allowed to have a value", + name + ))); + } + _ => (), + } + } + match name.as_str() { + "bundle-only" => Ok(SdpAttribute::BundleOnly), + "dtls-message" => parse_dtls_message(val), + "end-of-candidates" => Ok(SdpAttribute::EndOfCandidates), + "ice-lite" => Ok(SdpAttribute::IceLite), + "ice-mismatch" => Ok(SdpAttribute::IceMismatch), + "extmap-allow-mixed" => Ok(SdpAttribute::ExtmapAllowMixed), + "ice-pwd" => Ok(SdpAttribute::IcePwd(string_or_empty(val)?)), + "ice-ufrag" => Ok(SdpAttribute::IceUfrag(string_or_empty(val)?)), + "identity" => Ok(SdpAttribute::Identity(string_or_empty(val)?)), + "imageattr" => parse_image_attr(val), + "inactive" => Ok(SdpAttribute::Inactive), + "label" => Ok(SdpAttribute::Label(string_or_empty(val)?)), + "max-message-size" => Ok(SdpAttribute::MaxMessageSize(val.parse()?)), + "maxptime" => Ok(SdpAttribute::MaxPtime(val.parse()?)), + "mid" => Ok(SdpAttribute::Mid(string_or_empty(val)?)), + "msid-semantic" => parse_msid_semantic(val), + "ptime" => Ok(SdpAttribute::Ptime(val.parse()?)), + "ice-pacing" => parse_ice_pacing(val), + "rid" => parse_rid(val), + "recvonly" => Ok(SdpAttribute::Recvonly), + "rtcp-mux" => Ok(SdpAttribute::RtcpMux), + "rtcp-rsize" => Ok(SdpAttribute::RtcpRsize), + "sendonly" => Ok(SdpAttribute::Sendonly), + "sendrecv" => Ok(SdpAttribute::Sendrecv), + "ssrc-group" => parse_ssrc_group(val), + "sctp-port" => parse_sctp_port(val), + "candidate" => parse_candidate(val), + "extmap" => parse_extmap(val), + "fingerprint" => parse_fingerprint(val), + "fmtp" => parse_fmtp(val), + "group" => parse_group(val), + "ice-options" => parse_ice_options(val), + "msid" => parse_msid(val), + "remote-candidates" => parse_remote_candidates(val), + "rtpmap" => parse_rtpmap(val), + "rtcp" => parse_rtcp(val), + "rtcp-fb" => parse_rtcp_fb(val), + "sctpmap" => parse_sctpmap(val), + "setup" => parse_setup(val), + "simulcast" => parse_simulcast(val), + "ssrc" => parse_ssrc(val), + _ => Err(SdpParserInternalError::Unsupported(format!( + "Unknown attribute type {}", + name + ))), + } + } +} + +impl fmt::Display for SdpAttribute { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let attr_type_name = SdpAttributeType::from(self).to_string(); + let attr_to_string = |attr_str: String| attr_type_name + ":" + &attr_str; + match *self { + SdpAttribute::BundleOnly => SdpAttributeType::BundleOnly.to_string(), + SdpAttribute::Candidate(ref a) => attr_to_string(a.to_string()), + SdpAttribute::DtlsMessage(ref a) => attr_to_string(a.to_string()), + SdpAttribute::EndOfCandidates => SdpAttributeType::EndOfCandidates.to_string(), + SdpAttribute::Extmap(ref a) => attr_to_string(a.to_string()), + SdpAttribute::ExtmapAllowMixed => SdpAttributeType::ExtmapAllowMixed.to_string(), + SdpAttribute::Fingerprint(ref a) => attr_to_string(a.to_string()), + SdpAttribute::Fmtp(ref a) => attr_to_string(a.to_string()), + SdpAttribute::Group(ref a) => attr_to_string(a.to_string()), + SdpAttribute::IceLite => SdpAttributeType::IceLite.to_string(), + SdpAttribute::IceMismatch => SdpAttributeType::IceMismatch.to_string(), + SdpAttribute::IceOptions(ref a) => attr_to_string(a.join(" ")), + SdpAttribute::IcePacing(ref a) => attr_to_string(a.to_string()), + SdpAttribute::IcePwd(ref a) => attr_to_string(a.to_string()), + SdpAttribute::IceUfrag(ref a) => attr_to_string(a.to_string()), + SdpAttribute::Identity(ref a) => attr_to_string(a.to_string()), + SdpAttribute::ImageAttr(ref a) => attr_to_string(a.to_string()), + SdpAttribute::Inactive => SdpAttributeType::Inactive.to_string(), + SdpAttribute::Label(ref a) => attr_to_string(a.to_string()), + SdpAttribute::MaxMessageSize(ref a) => attr_to_string(a.to_string()), + SdpAttribute::MaxPtime(ref a) => attr_to_string(a.to_string()), + SdpAttribute::Mid(ref a) => attr_to_string(a.to_string()), + SdpAttribute::Msid(ref a) => attr_to_string(a.to_string()), + SdpAttribute::MsidSemantic(ref a) => attr_to_string(a.to_string()), + SdpAttribute::Ptime(ref a) => attr_to_string(a.to_string()), + SdpAttribute::Rid(ref a) => attr_to_string(a.to_string()), + SdpAttribute::Recvonly => SdpAttributeType::Recvonly.to_string(), + SdpAttribute::RemoteCandidate(ref a) => attr_to_string(a.to_string()), + SdpAttribute::Rtpmap(ref a) => attr_to_string(a.to_string()), + SdpAttribute::Rtcp(ref a) => attr_to_string(a.to_string()), + SdpAttribute::Rtcpfb(ref a) => attr_to_string(a.to_string()), + SdpAttribute::RtcpMux => SdpAttributeType::RtcpMux.to_string(), + SdpAttribute::RtcpRsize => SdpAttributeType::RtcpRsize.to_string(), + SdpAttribute::Sctpmap(ref a) => attr_to_string(a.to_string()), + SdpAttribute::SctpPort(ref a) => attr_to_string(a.to_string()), + SdpAttribute::Sendonly => SdpAttributeType::Sendonly.to_string(), + SdpAttribute::Sendrecv => SdpAttributeType::Sendrecv.to_string(), + SdpAttribute::Setup(ref a) => attr_to_string(a.to_string()), + SdpAttribute::Simulcast(ref a) => attr_to_string(a.to_string()), + SdpAttribute::Ssrc(ref a) => attr_to_string(a.to_string()), + SdpAttribute::SsrcGroup(ref a, ref ssrcs) => { + let stringified_ssrcs: Vec<String> = + ssrcs.iter().map(|ssrc| ssrc.to_string()).collect(); + attr_to_string(a.to_string()) + " " + &stringified_ssrcs.join(" ") + } + } + .fmt(f) + } +} + +impl AnonymizingClone for SdpAttribute { + fn masked_clone(&self, anon: &mut StatefulSdpAnonymizer) -> Self { + match self { + SdpAttribute::Candidate(i) => SdpAttribute::Candidate(i.masked_clone(anon)), + SdpAttribute::Fingerprint(i) => SdpAttribute::Fingerprint(i.masked_clone(anon)), + SdpAttribute::IcePwd(i) => SdpAttribute::IcePwd(anon.mask_ice_password(i)), + SdpAttribute::IceUfrag(i) => SdpAttribute::IceUfrag(anon.mask_ice_user(i)), + SdpAttribute::RemoteCandidate(i) => SdpAttribute::RemoteCandidate(i.masked_clone(anon)), + SdpAttribute::Ssrc(i) => SdpAttribute::Ssrc(i.masked_clone(anon)), + _ => self.clone(), + } + } +} + +#[derive(Clone, PartialEq)] +pub enum SdpAttributeType { + BundleOnly, + Candidate, + DtlsMessage, + EndOfCandidates, + Extmap, + ExtmapAllowMixed, + Fingerprint, + Fmtp, + Group, + IceLite, + IceMismatch, + IceOptions, + IcePacing, + IcePwd, + IceUfrag, + Identity, + ImageAttr, + Inactive, + Label, + MaxMessageSize, + MaxPtime, + Mid, + Msid, + MsidSemantic, + Ptime, + Rid, + Recvonly, + RemoteCandidate, + Rtpmap, + Rtcp, + Rtcpfb, + RtcpMux, + RtcpRsize, + Sctpmap, + SctpPort, + Sendonly, + Sendrecv, + Setup, + Simulcast, + Ssrc, + SsrcGroup, +} + +impl<'a> From<&'a SdpAttribute> for SdpAttributeType { + fn from(other: &SdpAttribute) -> Self { + match *other { + SdpAttribute::BundleOnly { .. } => SdpAttributeType::BundleOnly, + SdpAttribute::Candidate { .. } => SdpAttributeType::Candidate, + SdpAttribute::DtlsMessage { .. } => SdpAttributeType::DtlsMessage, + SdpAttribute::EndOfCandidates { .. } => SdpAttributeType::EndOfCandidates, + SdpAttribute::Extmap { .. } => SdpAttributeType::Extmap, + SdpAttribute::ExtmapAllowMixed { .. } => SdpAttributeType::ExtmapAllowMixed, + SdpAttribute::Fingerprint { .. } => SdpAttributeType::Fingerprint, + SdpAttribute::Fmtp { .. } => SdpAttributeType::Fmtp, + SdpAttribute::Group { .. } => SdpAttributeType::Group, + SdpAttribute::IceLite { .. } => SdpAttributeType::IceLite, + SdpAttribute::IceMismatch { .. } => SdpAttributeType::IceMismatch, + SdpAttribute::IceOptions { .. } => SdpAttributeType::IceOptions, + SdpAttribute::IcePacing { .. } => SdpAttributeType::IcePacing, + SdpAttribute::IcePwd { .. } => SdpAttributeType::IcePwd, + SdpAttribute::IceUfrag { .. } => SdpAttributeType::IceUfrag, + SdpAttribute::Identity { .. } => SdpAttributeType::Identity, + SdpAttribute::ImageAttr { .. } => SdpAttributeType::ImageAttr, + SdpAttribute::Inactive { .. } => SdpAttributeType::Inactive, + SdpAttribute::Label { .. } => SdpAttributeType::Label, + SdpAttribute::MaxMessageSize { .. } => SdpAttributeType::MaxMessageSize, + SdpAttribute::MaxPtime { .. } => SdpAttributeType::MaxPtime, + SdpAttribute::Mid { .. } => SdpAttributeType::Mid, + SdpAttribute::Msid { .. } => SdpAttributeType::Msid, + SdpAttribute::MsidSemantic { .. } => SdpAttributeType::MsidSemantic, + SdpAttribute::Ptime { .. } => SdpAttributeType::Ptime, + SdpAttribute::Rid { .. } => SdpAttributeType::Rid, + SdpAttribute::Recvonly { .. } => SdpAttributeType::Recvonly, + SdpAttribute::RemoteCandidate { .. } => SdpAttributeType::RemoteCandidate, + SdpAttribute::Rtcp { .. } => SdpAttributeType::Rtcp, + SdpAttribute::Rtcpfb { .. } => SdpAttributeType::Rtcpfb, + SdpAttribute::RtcpMux { .. } => SdpAttributeType::RtcpMux, + SdpAttribute::RtcpRsize { .. } => SdpAttributeType::RtcpRsize, + SdpAttribute::Rtpmap { .. } => SdpAttributeType::Rtpmap, + SdpAttribute::Sctpmap { .. } => SdpAttributeType::Sctpmap, + SdpAttribute::SctpPort { .. } => SdpAttributeType::SctpPort, + SdpAttribute::Sendonly { .. } => SdpAttributeType::Sendonly, + SdpAttribute::Sendrecv { .. } => SdpAttributeType::Sendrecv, + SdpAttribute::Setup { .. } => SdpAttributeType::Setup, + SdpAttribute::Simulcast { .. } => SdpAttributeType::Simulcast, + SdpAttribute::Ssrc { .. } => SdpAttributeType::Ssrc, + SdpAttribute::SsrcGroup { .. } => SdpAttributeType::SsrcGroup, + } + } +} + +impl fmt::Display for SdpAttributeType { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + SdpAttributeType::BundleOnly => "bundle-only", + SdpAttributeType::Candidate => "candidate", + SdpAttributeType::DtlsMessage => "dtls-message", + SdpAttributeType::EndOfCandidates => "end-of-candidates", + SdpAttributeType::Extmap => "extmap", + SdpAttributeType::ExtmapAllowMixed => "extmap-allow-mixed", + SdpAttributeType::Fingerprint => "fingerprint", + SdpAttributeType::Fmtp => "fmtp", + SdpAttributeType::Group => "group", + SdpAttributeType::IceLite => "ice-lite", + SdpAttributeType::IceMismatch => "ice-mismatch", + SdpAttributeType::IceOptions => "ice-options", + SdpAttributeType::IcePacing => "ice-pacing", + SdpAttributeType::IcePwd => "ice-pwd", + SdpAttributeType::IceUfrag => "ice-ufrag", + SdpAttributeType::Identity => "identity", + SdpAttributeType::ImageAttr => "imageattr", + SdpAttributeType::Inactive => "inactive", + SdpAttributeType::Label => "label", + SdpAttributeType::MaxMessageSize => "max-message-size", + SdpAttributeType::MaxPtime => "maxptime", + SdpAttributeType::Mid => "mid", + SdpAttributeType::Msid => "msid", + SdpAttributeType::MsidSemantic => "msid-semantic", + SdpAttributeType::Ptime => "ptime", + SdpAttributeType::Rid => "rid", + SdpAttributeType::Recvonly => "recvonly", + SdpAttributeType::RemoteCandidate => "remote-candidates", + SdpAttributeType::Rtpmap => "rtpmap", + SdpAttributeType::Rtcp => "rtcp", + SdpAttributeType::Rtcpfb => "rtcp-fb", + SdpAttributeType::RtcpMux => "rtcp-mux", + SdpAttributeType::RtcpRsize => "rtcp-rsize", + SdpAttributeType::Sctpmap => "sctpmap", + SdpAttributeType::SctpPort => "sctp-port", + SdpAttributeType::Sendonly => "sendonly", + SdpAttributeType::Sendrecv => "sendrecv", + SdpAttributeType::Setup => "setup", + SdpAttributeType::Simulcast => "simulcast", + SdpAttributeType::Ssrc => "ssrc", + SdpAttributeType::SsrcGroup => "ssrc-group", + } + .fmt(f) + } +} + +fn string_or_empty(to_parse: &str) -> Result<String, SdpParserInternalError> { + if to_parse.is_empty() { + Err(SdpParserInternalError::Generic( + "This attribute is required to have a value".to_string(), + )) + } else { + Ok(to_parse.to_string()) + } +} + +fn parse_payload_type(to_parse: &str) -> Result<SdpAttributePayloadType, SdpParserInternalError> { + Ok(match to_parse { + "*" => SdpAttributePayloadType::Wildcard, + _ => SdpAttributePayloadType::PayloadType(to_parse.parse::<u8>()?), + }) +} + +fn parse_single_direction(to_parse: &str) -> Result<SdpSingleDirection, SdpParserInternalError> { + match to_parse { + "send" => Ok(SdpSingleDirection::Send), + "recv" => Ok(SdpSingleDirection::Recv), + x => Err(SdpParserInternalError::Generic(format!( + "Unknown direction description found: '{:}'", + x + ))), + } +} + +/////////////////////////////////////////////////////////////////////////// +// a=ssrc-group, RFC5576 +//------------------------------------------------------------------------- +// a=ssrc-group:<semantics> <ssrc-id> ... +fn parse_ssrc_group(to_parse: &str) -> Result<SdpAttribute, SdpParserInternalError> { + let mut tokens = to_parse.split_whitespace(); + let semantics = match tokens.next() { + None => { + return Err(SdpParserInternalError::Generic( + "Ssrc group attribute is missing semantics".to_string(), + )); + } + Some(x) => match x.to_uppercase().as_ref() { + "DUP" => SdpSsrcGroupSemantic::Duplication, + "FID" => SdpSsrcGroupSemantic::FlowIdentification, + "FEC" => SdpSsrcGroupSemantic::ForwardErrorCorrection, + "FEC-FR" => SdpSsrcGroupSemantic::ForwardErrorCorrectionFR, + "SIM" => SdpSsrcGroupSemantic::SIM, + unknown => { + return Err(SdpParserInternalError::Unsupported(format!( + "Unknown ssrc semantic '{:?}' found", + unknown + ))); + } + }, + }; + + let mut ssrcs = Vec::new(); + for token in tokens { + match parse_ssrc(token) { + Ok(SdpAttribute::Ssrc(ssrc)) => { + ssrcs.push(ssrc); + } + Err(err) => { + return Err(err); + } + _ => unreachable!(), + } + } + + if ssrcs.is_empty() { + return Err(SdpParserInternalError::Generic( + "Ssrc group must contain at least one ssrc".to_string(), + )); + } + + Ok(SdpAttribute::SsrcGroup(semantics, ssrcs)) +} + +/////////////////////////////////////////////////////////////////////////// +// a=sctp-port, draft-ietf-mmusic-sctp-sdp-26#section-15.2.1 +//------------------------------------------------------------------------- +// no ABNF given +fn parse_sctp_port(to_parse: &str) -> Result<SdpAttribute, SdpParserInternalError> { + let port = to_parse.parse()?; + if port > 65535 { + return Err(SdpParserInternalError::Generic(format!( + "Sctpport port {} can only be a bit 16bit number", + port + ))); + } + Ok(SdpAttribute::SctpPort(port)) +} + +/////////////////////////////////////////////////////////////////////////// +// a=candidate, RFC5245 +//------------------------------------------------------------------------- +// +// candidate-attribute = "candidate" ":" foundation SP component-id SP +// transport SP +// priority SP +// connection-address SP ;from RFC 4566 +// port ;port from RFC 4566 +// SP cand-type +// [SP rel-addr] +// [SP rel-port] +// *(SP extension-att-name SP +// extension-att-value) +// foundation = 1*32ice-char +// component-id = 1*5DIGIT +// transport = "UDP" / transport-extension +// transport-extension = token ; from RFC 3261 +// priority = 1*10DIGIT +// cand-type = "typ" SP candidate-types +// candidate-types = "host" / "srflx" / "prflx" / "relay" / token +// rel-addr = "raddr" SP connection-address +// rel-port = "rport" SP port +// extension-att-name = byte-string ;from RFC 4566 +// extension-att-value = byte-string +// ice-char = ALPHA / DIGIT / "+" / "/" +fn parse_candidate(to_parse: &str) -> Result<SdpAttribute, SdpParserInternalError> { + let tokens: Vec<&str> = to_parse.split_whitespace().collect(); + if tokens.len() < 8 { + return Err(SdpParserInternalError::Generic( + "Candidate needs to have minimum eigth tokens".to_string(), + )); + } + let component = tokens[1].parse::<u32>()?; + let transport = match tokens[2].to_lowercase().as_ref() { + "udp" => SdpAttributeCandidateTransport::Udp, + "tcp" => SdpAttributeCandidateTransport::Tcp, + _ => { + return Err(SdpParserInternalError::Generic( + "Unknonw candidate transport value".to_string(), + )); + } + }; + let priority = tokens[3].parse::<u64>()?; + let address = Address::from_str(tokens[4])?; + let port = tokens[5].parse::<u32>()?; + if port > 65535 { + return Err(SdpParserInternalError::Generic( + "ICE candidate port can only be a bit 16bit number".to_string(), + )); + } + match tokens[6].to_lowercase().as_ref() { + "typ" => (), + _ => { + return Err(SdpParserInternalError::Generic( + "Candidate attribute token must be 'typ'".to_string(), + )); + } + }; + let cand_type = match tokens[7].to_lowercase().as_ref() { + "host" => SdpAttributeCandidateType::Host, + "srflx" => SdpAttributeCandidateType::Srflx, + "prflx" => SdpAttributeCandidateType::Prflx, + "relay" => SdpAttributeCandidateType::Relay, + _ => { + return Err(SdpParserInternalError::Generic( + "Unknow candidate type value".to_string(), + )); + } + }; + let mut cand = SdpAttributeCandidate::new( + tokens[0].to_string(), + component, + transport, + priority, + address, + port, + cand_type, + ); + if tokens.len() > 8 { + let mut index = 8; + while tokens.len() > index + 1 { + match tokens[index].to_lowercase().as_ref() { + "generation" => { + let generation = tokens[index + 1].parse::<u32>()?; + cand.set_generation(generation); + index += 2; + } + "network-cost" => { + let cost = tokens[index + 1].parse::<u32>()?; + cand.set_network_cost(cost); + index += 2; + } + "raddr" => { + let addr = parse_unicast_address(tokens[index + 1])?; + cand.set_remote_address(addr); + index += 2; + } + "rport" => { + let port = tokens[index + 1].parse::<u32>()?; + if port > 65535 { + return Err(SdpParserInternalError::Generic( + "ICE candidate rport can only be a bit 16bit number".to_string(), + )); + } + cand.set_remote_port(port); + index += 2; + } + "tcptype" => { + cand.set_tcp_type(match tokens[index + 1].to_lowercase().as_ref() { + "active" => SdpAttributeCandidateTcpType::Active, + "passive" => SdpAttributeCandidateTcpType::Passive, + "so" => SdpAttributeCandidateTcpType::Simultaneous, + _ => { + return Err(SdpParserInternalError::Generic( + "Unknown tcptype value in candidate line".to_string(), + )); + } + }); + index += 2; + } + "ufrag" => { + let ufrag = tokens[index + 1]; + cand.set_ufrag(ufrag.to_string()); + index += 2; + } + _ => { + let name = tokens[index].to_string(); + let value = tokens[index + 1].to_string(); + cand.add_unknown_extension(name, value); + index += 2; + } + }; + } + if tokens.len() > index { + return Err(SdpParserInternalError::Unsupported( + "Ice candidate extension name without value".to_string(), + )); + } + } + Ok(SdpAttribute::Candidate(cand)) +} + +/////////////////////////////////////////////////////////////////////////// +// a=dtls-message, draft-rescorla-dtls-in-sdp +//------------------------------------------------------------------------- +// attribute =/ dtls-message-attribute +// +// dtls-message-attribute = "dtls-message" ":" role SP value +// +// role = "client" / "server" +// +// value = 1*(ALPHA / DIGIT / "+" / "/" / "=" ) +// ; base64 encoded message +fn parse_dtls_message(to_parse: &str) -> Result<SdpAttribute, SdpParserInternalError> { + let tokens: Vec<&str> = to_parse.split(' ').collect(); + + if tokens.len() != 2 { + return Err(SdpParserInternalError::Generic( + "dtls-message must have a role token and a value token.".to_string(), + )); + } + + Ok(SdpAttribute::DtlsMessage(match tokens[0] { + "client" => SdpAttributeDtlsMessage::Client(tokens[1].to_string()), + "server" => SdpAttributeDtlsMessage::Server(tokens[1].to_string()), + e => { + return Err(SdpParserInternalError::Generic(format!( + "dtls-message has unknown role token '{}'", + e + ))); + } + })) +} + +// Returns true if valid byte-string as defined by RFC 4566 +// https://tools.ietf.org/html/rfc4566 +fn valid_byte_string(input: &str) -> bool { + !(input.contains(0x00 as char) || input.contains(0x0A as char) || input.contains(0x0D as char)) +} + +/////////////////////////////////////////////////////////////////////////// +// a=extmap, RFC5285 +//------------------------------------------------------------------------- +// RFC5285 +// extmap = mapentry SP extensionname [SP extensionattributes] +// +// extensionname = URI +// +// direction = "sendonly" / "recvonly" / "sendrecv" / "inactive" +// +// mapentry = "extmap:" 1*5DIGIT ["/" direction] +// +// extensionattributes = byte-string +// +// URI = <Defined in RFC 3986> +// +// byte-string = <Defined in RFC 4566> +// +// SP = <Defined in RFC 5234> +// +// DIGIT = <Defined in RFC 5234> +fn parse_extmap(to_parse: &str) -> Result<SdpAttribute, SdpParserInternalError> { + let tokens: Vec<&str> = to_parse.split_whitespace().collect(); + if tokens.len() < 2 { + return Err(SdpParserInternalError::Generic( + "Extmap needs to have at least two tokens".to_string(), + )); + } + let id: u16; + let mut direction: Option<SdpAttributeDirection> = None; + if tokens[0].find('/') == None { + id = tokens[0].parse::<u16>()?; + } else { + let id_dir: Vec<&str> = tokens[0].splitn(2, '/').collect(); + id = id_dir[0].parse::<u16>()?; + direction = Some(match id_dir[1].to_lowercase().as_ref() { + "recvonly" => SdpAttributeDirection::Recvonly, + "sendonly" => SdpAttributeDirection::Sendonly, + "sendrecv" => SdpAttributeDirection::Sendrecv, + _ => { + return Err(SdpParserInternalError::Generic( + "Unsupported direction in extmap value".to_string(), + )); + } + }) + } + // Consider replacing to_parse.split_whitespace() above with splitn on space. Would we want the pattern to split on any amout of any kind of whitespace? + let extension_attributes = if tokens.len() == 2 { + None + } else { + let ext_string: String = tokens[2..].join(" "); + if !valid_byte_string(&ext_string) { + return Err(SdpParserInternalError::Generic( + "Illegal character in extmap extension attributes".to_string(), + )); + } + Some(ext_string) + }; + Ok(SdpAttribute::Extmap(SdpAttributeExtmap { + id, + direction, + url: tokens[1].to_string(), + extension_attributes, + })) +} + +/////////////////////////////////////////////////////////////////////////// +// a=fingerprint, RFC4572 +//------------------------------------------------------------------------- +// fingerprint-attribute = "fingerprint" ":" hash-func SP fingerprint +// +// hash-func = "sha-1" / "sha-224" / "sha-256" / +// "sha-384" / "sha-512" / +// "md5" / "md2" / token +// ; Additional hash functions can only come +// ; from updates to RFC 3279 +// +// fingerprint = 2UHEX *(":" 2UHEX) +// ; Each byte in upper-case hex, separated +// ; by colons. +// +// UHEX = DIGIT / %x41-46 ; A-F uppercase +fn parse_fingerprint(to_parse: &str) -> Result<SdpAttribute, SdpParserInternalError> { + let tokens: Vec<&str> = to_parse.split_whitespace().collect(); + if tokens.len() != 2 { + return Err(SdpParserInternalError::Generic( + "Fingerprint needs to have two tokens".to_string(), + )); + } + + let fingerprint_token = tokens[1].to_string(); + let parse_tokens = |expected_len| -> Result<Vec<u8>, SdpParserInternalError> { + let bytes = fingerprint_token + .split(':') + .map(|byte_token| { + if byte_token.len() != 2 { + return Err(SdpParserInternalError::Generic( + "fingerpint's byte tokens must have 2 hexdigits".to_string(), + )); + } + Ok(u8::from_str_radix(byte_token, 16)?) + }) + .collect::<Result<Vec<u8>, _>>()?; + + if bytes.len() != expected_len { + return Err(SdpParserInternalError::Generic(format!( + "fingerprint has {} bytes but should have {} bytes", + bytes.len(), + expected_len + ))); + } + + Ok(bytes) + }; + + let hash_algorithm = match tokens[0] { + "sha-1" => SdpAttributeFingerprintHashType::Sha1, + "sha-224" => SdpAttributeFingerprintHashType::Sha224, + "sha-256" => SdpAttributeFingerprintHashType::Sha256, + "sha-384" => SdpAttributeFingerprintHashType::Sha384, + "sha-512" => SdpAttributeFingerprintHashType::Sha512, + unknown => { + return Err(SdpParserInternalError::Unsupported(format!( + "fingerprint contains an unsupported hash algorithm '{}'", + unknown + ))); + } + }; + + let fingerprint = match hash_algorithm { + SdpAttributeFingerprintHashType::Sha1 => parse_tokens(20)?, + SdpAttributeFingerprintHashType::Sha224 => parse_tokens(28)?, + SdpAttributeFingerprintHashType::Sha256 => parse_tokens(32)?, + SdpAttributeFingerprintHashType::Sha384 => parse_tokens(48)?, + SdpAttributeFingerprintHashType::Sha512 => parse_tokens(64)?, + }; + + Ok(SdpAttribute::Fingerprint(SdpAttributeFingerprint { + hash_algorithm, + fingerprint, + })) +} + +/////////////////////////////////////////////////////////////////////////// +// a=fmtp, RFC4566, RFC5576 +//------------------------------------------------------------------------- +// a=fmtp:<format> <format specific parameters> +fn parse_fmtp(to_parse: &str) -> Result<SdpAttribute, SdpParserInternalError> { + let tokens: Vec<&str> = to_parse.splitn(2, ' ').collect(); + + // Support space seperated parameter blocks + if tokens.len() < 2 { + return Err(SdpParserInternalError::Unsupported( + "Fmtp attributes require a payload type and a parameter block.".to_string(), + )); + } + + let payload_token = tokens[0]; + + // Default initiliaze SdpAttributeFmtpParameters + let mut parameters = SdpAttributeFmtpParameters { + packetization_mode: 0, + level_asymmetry_allowed: false, + profile_level_id: 0x0042_0010, + max_fs: 0, + max_cpb: 0, + max_dpb: 0, + max_br: 0, + max_mbps: 0, + usedtx: false, + stereo: false, + useinbandfec: false, + cbr: false, + max_fr: 0, + maxplaybackrate: 48000, + maxaveragebitrate: 0, + ptime: 0, + minptime: 0, + maxptime: 0, + encodings: Vec::new(), + dtmf_tones: "".to_string(), + rtx: None, + unknown_tokens: Vec::new(), + }; + + for parameter_token in tokens[1..].iter() { + if parameter_token.contains('=') { + // Permit Leading/Trailing/Inner ';' by filtering out empty splits + let parameter_tokens: Vec<&str> = parameter_token + .split(';') + .filter(|token| !token.is_empty()) + .collect(); + for parameter_token in parameter_tokens.iter() { + let name_value_pair: Vec<&str> = parameter_token.splitn(2, '=').collect(); + if name_value_pair.len() != 2 { + return Err(SdpParserInternalError::Generic( + "A fmtp parameter must be either a telephone event, a parameter list or a red codec list" + .to_string(), + )); + } + + let parse_bool = + |val: &str, param_name: &str| -> Result<bool, SdpParserInternalError> { + match val.parse::<u8>()? { + 0 => Ok(false), + 1 => Ok(true), + _ => Err(SdpParserInternalError::Generic(format!( + "The fmtp parameter '{:}' must be 0 or 1", + param_name + ))), + } + }; + + let parameter_name = name_value_pair[0]; + let parameter_val = name_value_pair[1]; + + match parameter_name.to_uppercase().as_str() { + // H264 + "PROFILE-LEVEL-ID" => parameters.profile_level_id = match u32::from_str_radix( + parameter_val, + 16, + )? { + x @ 0..=0x00ff_ffff => x, + _ => return Err(SdpParserInternalError::Generic( + "The fmtp parameter 'profile-level-id' must be in range [0,0xffffff]" + .to_string(), + )), + }, + "PACKETIZATION-MODE" => { + parameters.packetization_mode = match parameter_val.parse::<u32>()? { + x @ 0..=2 => x, + _ => { + return Err(SdpParserInternalError::Generic( + "The fmtp parameter 'packetization-mode' must be 0,1 or 2" + .to_string(), + )); + } + } + } + "LEVEL-ASYMMETRY-ALLOWED" => { + parameters.level_asymmetry_allowed = + parse_bool(parameter_val, "level-asymmetry-allowed")? + } + "MAX-MBPS" => parameters.max_mbps = parameter_val.parse::<u32>()?, + "MAX-FS" => parameters.max_fs = parameter_val.parse::<u32>()?, + "MAX-CPB" => parameters.max_cpb = parameter_val.parse::<u32>()?, + "MAX-DPB" => parameters.max_dpb = parameter_val.parse::<u32>()?, + "MAX-BR" => parameters.max_br = parameter_val.parse::<u32>()?, + + // VP8 and VP9 + "MAX-FR" => parameters.max_fr = parameter_val.parse::<u32>()?, + + //Opus https://tools.ietf.org/html/rfc7587 + "MAXPLAYBACKRATE" => { + parameters.maxplaybackrate = parameter_val.parse::<u32>()? + } + "MAXAVERAGEBITRATE" => { + parameters.maxaveragebitrate = parameter_val.parse::<u32>()? + } + "PTIME" => parameters.ptime = parameter_val.parse::<u32>()?, + "MAXPTIME" => parameters.maxptime = parameter_val.parse::<u32>()?, + "MINPTIME" => parameters.minptime = parameter_val.parse::<u32>()?, + "USEDTX" => parameters.usedtx = parse_bool(parameter_val, "usedtx")?, + "STEREO" => parameters.stereo = parse_bool(parameter_val, "stereo")?, + "USEINBANDFEC" => { + parameters.useinbandfec = parse_bool(parameter_val, "useinbandfec")? + } + "CBR" => parameters.cbr = parse_bool(parameter_val, "cbr")?, + "APT" => { + parameters.rtx = Some(RtxFmtpParameters { + apt: parameter_val.parse::<u8>()?, + rtx_time: None, + }) + } + "RTX-TIME" => { + if let Some(ref mut rtx) = parameters.rtx { + rtx.rtx_time = Some(parameter_val.parse::<u32>()?) + } else { + return Err(SdpParserInternalError::Generic( + "RTX codec must have an APT field".to_string(), + )); + } + } + _ => parameters + .unknown_tokens + .push((*parameter_token).to_string()), + } + } + } else if parameter_token.contains('/') { + let encodings: Vec<&str> = parameter_token.split('/').collect(); + + for encoding in encodings { + match encoding.parse::<u8>()? { + x @ 0..=128 => parameters.encodings.push(x), + _ => { + return Err(SdpParserInternalError::Generic( + "Red codec must be in range [0,128]".to_string(), + )); + } + } + } + } else { + // This is the case for the 'telephone-event' codec + let dtmf_tones: Vec<&str> = parameter_token.split(',').collect(); + let mut dtmf_tone_is_ok = true; + + // This closure verifies the output of some_number_as_string.parse::<u8>().ok() like calls + let validate_digits = |digit_option: Option<u8>| -> Option<u8> { + match digit_option { + Some(x) => match x { + 0..=100 => Some(x), + _ => None, + }, + None => None, + } + }; + + // This loop does some sanity checking on the passed dtmf tones + for dtmf_tone in dtmf_tones { + let dtmf_tone_range: Vec<&str> = dtmf_tone.splitn(2, '-').collect(); + + dtmf_tone_is_ok = match dtmf_tone_range.len() { + // In this case the dtmf tone is a range + 2 => { + match validate_digits(dtmf_tone_range[0].parse::<u8>().ok()) { + Some(l) => match validate_digits(dtmf_tone_range[1].parse::<u8>().ok()) + { + Some(u) => { + // Check that the first part of the range is smaller than the second part + l < u + } + None => false, + }, + None => false, + } + } + // In this case the dtmf tone is a single tone + 1 => validate_digits(dtmf_tone.parse::<u8>().ok()).is_some(), + _ => false, + }; + + if !dtmf_tone_is_ok { + break; + } + } + + // Set the parsed dtmf tones or in case the parsing was insuccessfull, set it to the default "0-15" + parameters.dtmf_tones = if dtmf_tone_is_ok { + (*parameter_token).to_string() + } else { + "0-15".to_string() + }; + } + } + Ok(SdpAttribute::Fmtp(SdpAttributeFmtp { + payload_type: payload_token.parse::<u8>()?, + parameters, + })) +} + +/////////////////////////////////////////////////////////////////////////// +// a=group, RFC5888 +//------------------------------------------------------------------------- +// group-attribute = "a=group:" semantics +// *(SP identification-tag) +// semantics = "LS" / "FID" / semantics-extension +// semantics-extension = token +// identification-tag = token +fn parse_group(to_parse: &str) -> Result<SdpAttribute, SdpParserInternalError> { + let mut tokens = to_parse.split_whitespace(); + let semantics = match tokens.next() { + None => { + return Err(SdpParserInternalError::Generic( + "Group attribute is missing semantics token".to_string(), + )); + } + Some(x) => match x.to_uppercase().as_ref() { + "LS" => SdpAttributeGroupSemantic::LipSynchronization, + "FID" => SdpAttributeGroupSemantic::FlowIdentification, + "SRF" => SdpAttributeGroupSemantic::SingleReservationFlow, + "ANAT" => SdpAttributeGroupSemantic::AlternateNetworkAddressType, + "FEC" => SdpAttributeGroupSemantic::ForwardErrorCorrection, + "DDP" => SdpAttributeGroupSemantic::DecodingDependency, + "BUNDLE" => SdpAttributeGroupSemantic::Bundle, + unknown => { + return Err(SdpParserInternalError::Unsupported(format!( + "Unknown group semantic '{:?}' found", + unknown + ))); + } + }, + }; + Ok(SdpAttribute::Group(SdpAttributeGroup { + semantics, + tags: tokens.map(ToString::to_string).collect(), + })) +} + +/////////////////////////////////////////////////////////////////////////// +// a=ice-options, draft-ietf-mmusic-ice-sip-sdp +//------------------------------------------------------------------------- +// ice-options = "ice-options:" ice-option-tag +// 0*(SP ice-option-tag) +// ice-option-tag = 1*ice-char +fn parse_ice_options(to_parse: &str) -> Result<SdpAttribute, SdpParserInternalError> { + if to_parse.is_empty() { + return Err(SdpParserInternalError::Generic( + "ice-options is required to have a value".to_string(), + )); + } + Ok(SdpAttribute::IceOptions( + to_parse + .split_whitespace() + .map(ToString::to_string) + .collect(), + )) +} + +/////////////////////////////////////////////////////////////////////////// +// a=ice-pacing, draft-ietf-mmusic-ice-sip-sdp +//------------------------------------------------------------------------- +// ice-pacing-att = "ice-pacing:" pacing-value +// pacing-value = 1*10DIGIT +fn parse_ice_pacing(to_parse: &str) -> Result<SdpAttribute, SdpParserInternalError> { + let parsed = to_parse.parse::<u64>()?; + if parsed >= 1_00_00_00_00_00 { + return Err(SdpParserInternalError::Generic( + "ice-pacing value is not a 10 digit integer".to_string(), + )); + } + Ok(SdpAttribute::IcePacing(parsed)) +} + +fn parse_imageattr_tokens(to_parse: &str, separator: char) -> Vec<String> { + let mut tokens = Vec::new(); + let mut open_braces_counter = 0; + let mut current_tokens = Vec::new(); + + for token in to_parse.split(separator) { + if token.contains('[') { + open_braces_counter += 1; + } + if token.contains(']') { + open_braces_counter -= 1; + } + + current_tokens.push(token.to_string()); + + if open_braces_counter == 0 { + tokens.push(current_tokens.join(&separator.to_string())); + current_tokens = Vec::new(); + } + } + + tokens +} + +fn parse_imagettr_braced_token(to_parse: &str) -> Option<&str> { + if !to_parse.starts_with('[') { + return None; + } + + if !to_parse.ends_with(']') { + return None; + } + + Some(&to_parse[1..to_parse.len() - 1]) +} + +fn parse_image_attr_xyrange( + to_parse: &str, +) -> Result<SdpAttributeImageAttrXYRange, SdpParserInternalError> { + if to_parse.starts_with('[') { + let value_tokens = parse_imagettr_braced_token(to_parse).ok_or_else(|| { + SdpParserInternalError::Generic( + "imageattr's xyrange has no closing tag ']'".to_string(), + ) + })?; + + if to_parse.contains(':') { + // Range values + let range_tokens: Vec<&str> = value_tokens.split(':').collect(); + + if range_tokens.len() == 3 { + Ok(SdpAttributeImageAttrXYRange::Range( + range_tokens[0].parse::<u32>()?, + range_tokens[2].parse::<u32>()?, + Some(range_tokens[1].parse::<u32>()?), + )) + } else if range_tokens.len() == 2 { + Ok(SdpAttributeImageAttrXYRange::Range( + range_tokens[0].parse::<u32>()?, + range_tokens[1].parse::<u32>()?, + None, + )) + } else { + Err(SdpParserInternalError::Generic( + "imageattr's xyrange must contain 2 or 3 fields".to_string(), + )) + } + } else { + // Discrete values + let values = value_tokens + .split(',') + .map(str::parse::<u32>) + .collect::<Result<Vec<u32>, _>>()?; + + if values.len() < 2 { + return Err(SdpParserInternalError::Generic( + "imageattr's discrete value list must have at least two elements".to_string(), + )); + } + + Ok(SdpAttributeImageAttrXYRange::DiscreteValues(values)) + } + } else { + Ok(SdpAttributeImageAttrXYRange::DiscreteValues(vec![ + to_parse.parse::<u32>()? + ])) + } +} + +fn parse_image_attr_set( + to_parse: &str, +) -> Result<SdpAttributeImageAttrSet, SdpParserInternalError> { + let mut tokens = parse_imageattr_tokens(to_parse, ',').into_iter(); + + let x_token = tokens.next().ok_or_else(|| { + SdpParserInternalError::Generic("imageattr set is missing the 'x=' token".to_string()) + })?; + if !x_token.starts_with("x=") { + return Err(SdpParserInternalError::Generic( + "The first token in an imageattr set must begin with 'x='".to_string(), + )); + } + let x = parse_image_attr_xyrange(&x_token[2..])?; + + let y_token = tokens.next().ok_or_else(|| { + SdpParserInternalError::Generic("imageattr set is missing the 'y=' token".to_string()) + })?; + if !y_token.starts_with("y=") { + return Err(SdpParserInternalError::Generic( + "The second token in an imageattr set must begin with 'y='".to_string(), + )); + } + let y = parse_image_attr_xyrange(&y_token[2..])?; + + let mut sar = None; + let mut par = None; + let mut q = None; + + let parse_ps_range = |resolution_range: &str| -> Result<(f32, f32), SdpParserInternalError> { + let minmax_pair: Vec<&str> = resolution_range.split('-').collect(); + + if minmax_pair.len() != 2 { + return Err(SdpParserInternalError::Generic( + "imageattr's par and sar ranges must have two components".to_string(), + )); + } + + let min = minmax_pair[0].parse::<f32>()?; + let max = minmax_pair[1].parse::<f32>()?; + + if min >= max { + return Err(SdpParserInternalError::Generic( + "In imageattr's par and sar ranges, first must be < than the second".to_string(), + )); + } + + Ok((min, max)) + }; + + for current_token in tokens { + if let Some(value_token) = current_token.strip_prefix("sar=") { + if value_token.starts_with('[') { + let sar_values = parse_imagettr_braced_token(value_token).ok_or_else(|| { + SdpParserInternalError::Generic( + "imageattr's sar value is missing closing tag ']'".to_string(), + ) + })?; + + if value_token.contains('-') { + // Range + let range = parse_ps_range(sar_values)?; + sar = Some(SdpAttributeImageAttrSRange::Range(range.0, range.1)) + } else if value_token.contains(',') { + // Discrete values + let values = sar_values + .split(',') + .map(str::parse::<f32>) + .collect::<Result<Vec<f32>, _>>()?; + + if values.len() < 2 { + return Err(SdpParserInternalError::Generic( + "imageattr's sar discrete value list must have at least two values" + .to_string(), + )); + } + + // Check that all the values are ascending + let mut last_value = 0.0; + for value in &values { + if last_value >= *value { + return Err(SdpParserInternalError::Generic( + "imageattr's sar discrete value list must contain ascending values" + .to_string(), + )); + } + last_value = *value; + } + sar = Some(SdpAttributeImageAttrSRange::DiscreteValues(values)) + } + } else { + sar = Some(SdpAttributeImageAttrSRange::DiscreteValues(vec![ + value_token.parse::<f32>()?, + ])) + } + } else if let Some(braced_value_token) = current_token.strip_prefix("par=") { + if !braced_value_token.starts_with('[') { + return Err(SdpParserInternalError::Generic( + "imageattr's par value must start with '['".to_string(), + )); + } + + let par_values = parse_imagettr_braced_token(braced_value_token).ok_or_else(|| { + SdpParserInternalError::Generic( + "imageattr's par value must be enclosed with ']'".to_string(), + ) + })?; + let range = parse_ps_range(par_values)?; + par = Some(SdpAttributeImageAttrPRange { + min: range.0, + max: range.1, + }) + } else if let Some(qval) = current_token.strip_prefix("q=") { + q = Some(qval.parse::<f32>()?); + } + } + + Ok(SdpAttributeImageAttrSet { x, y, sar, par, q }) +} + +fn parse_image_attr_set_list<I>( + tokens: &mut iter::Peekable<I>, +) -> Result<SdpAttributeImageAttrSetList, SdpParserInternalError> +where + I: Iterator<Item = String> + Clone, +{ + let parse_set = |set_token: &str| -> Result<SdpAttributeImageAttrSet, SdpParserInternalError> { + Ok(parse_image_attr_set( + parse_imagettr_braced_token(set_token).ok_or_else(|| { + SdpParserInternalError::Generic( + "imageattr sets must be enclosed by ']'".to_string(), + ) + })?, + )?) + }; + + match tokens + .next() + .ok_or_else(|| { + SdpParserInternalError::Generic( + "imageattr must have a parameter set after a direction token".to_string(), + ) + })? + .as_str() + { + "*" => Ok(SdpAttributeImageAttrSetList::Wildcard), + x => { + let mut sets = vec![parse_set(x)?]; + while let Some(set_str) = tokens.clone().peek() { + if set_str.starts_with('[') { + sets.push(parse_set(&tokens.next().unwrap())?); + } else { + break; + } + } + + Ok(SdpAttributeImageAttrSetList::Sets(sets)) + } + } +} + +/////////////////////////////////////////////////////////////////////////// +// a=imageattr, RFC6236 +//------------------------------------------------------------------------- +// image-attr = "imageattr:" PT 1*2( 1*WSP ( "send" / "recv" ) +// 1*WSP attr-list ) +// PT = 1*DIGIT / "*" +// attr-list = ( set *(1*WSP set) ) / "*" +// ; WSP and DIGIT defined in [RFC5234] +// +// set= "[" "x=" xyrange "," "y=" xyrange *( "," key-value ) "]" +// ; x is the horizontal image size range (pixel count) +// ; y is the vertical image size range (pixel count) +// +// key-value = ( "sar=" srange ) +// / ( "par=" prange ) +// / ( "q=" qvalue ) +// ; Key-value MAY be extended with other keyword +// ; parameters. +// ; At most, one instance each of sar, par, or q +// ; is allowed in a set. +// ; +// ; sar (sample aspect ratio) is the sample aspect ratio +// ; associated with the set (optional, MAY be ignored) +// ; par (picture aspect ratio) is the allowed +// ; ratio between the display's x and y physical +// ; size (optional) +// ; q (optional, range [0.0..1.0], default value 0.5) +// ; is the preference for the given set, +// ; a higher value means a higher preference +// +// onetonine = "1" / "2" / "3" / "4" / "5" / "6" / "7" / "8" / "9" +// ; Digit between 1 and 9 +// xyvalue = onetonine *5DIGIT +// ; Digit between 1 and 9 that is +// ; followed by 0 to 5 other digits +// step = xyvalue +// xyrange = ( "[" xyvalue ":" [ step ":" ] xyvalue "]" ) +// ; Range between a lower and an upper value +// ; with an optional step, default step = 1 +// ; The rightmost occurrence of xyvalue MUST have a +// ; higher value than the leftmost occurrence. +// / ( "[" xyvalue 1*( "," xyvalue ) "]" ) +// ; Discrete values separated by ',' +// / ( xyvalue ) +// ; A single value +// spvalue = ( "0" "." onetonine *3DIGIT ) +// ; Values between 0.1000 and 0.9999 +// / ( onetonine "." 1*4DIGIT ) +// ; Values between 1.0000 and 9.9999 +// srange = ( "[" spvalue 1*( "," spvalue ) "]" ) +// ; Discrete values separated by ','. +// ; Each occurrence of spvalue MUST be +// ; greater than the previous occurrence. +// / ( "[" spvalue "-" spvalue "]" ) +// ; Range between a lower and an upper level (inclusive) +// ; The second occurrence of spvalue MUST have a higher +// ; value than the first +// / ( spvalue ) +// ; A single value +// +// prange = ( "[" spvalue "-" spvalue "]" ) +// ; Range between a lower and an upper level (inclusive) +// ; The second occurrence of spvalue MUST have a higher +// ; value than the first +// +// qvalue = ( "0" "." 1*2DIGIT ) +// / ( "1" "." 1*2("0") ) +// ; Values between 0.00 and 1.00 +fn parse_image_attr(to_parse: &str) -> Result<SdpAttribute, SdpParserInternalError> { + let mut tokens = parse_imageattr_tokens(to_parse, ' ').into_iter().peekable(); + + let pt = parse_payload_type( + tokens + .next() + .ok_or_else(|| { + SdpParserInternalError::Generic("imageattr requires a payload token".to_string()) + })? + .as_str(), + )?; + let first_direction = parse_single_direction( + tokens + .next() + .ok_or_else(|| { + SdpParserInternalError::Generic( + "imageattr's second token must be a direction token".to_string(), + ) + })? + .as_str(), + )?; + + let first_set_list = parse_image_attr_set_list(&mut tokens)?; + + let mut second_set_list = SdpAttributeImageAttrSetList::Sets(Vec::new()); + + // Check if there is a second direction defined + if let Some(direction_token) = tokens.next() { + if parse_single_direction(direction_token.as_str())? == first_direction { + return Err(SdpParserInternalError::Generic( + "imageattr's second direction token must be different from the first one" + .to_string(), + )); + } + + second_set_list = parse_image_attr_set_list(&mut tokens)?; + } + + if tokens.next().is_some() { + return Err(SdpParserInternalError::Generic( + "imageattr must not contain any token after the second set list".to_string(), + )); + } + + Ok(SdpAttribute::ImageAttr(match first_direction { + SdpSingleDirection::Send => SdpAttributeImageAttr { + pt, + send: first_set_list, + recv: second_set_list, + }, + SdpSingleDirection::Recv => SdpAttributeImageAttr { + pt, + send: second_set_list, + recv: first_set_list, + }, + })) +} + +/////////////////////////////////////////////////////////////////////////// +// a=msid, draft-ietf-mmusic-msid +//------------------------------------------------------------------------- +// msid-attr = "msid:" identifier [ SP appdata ] +// identifier = 1*64token-char ; see RFC 4566 +// appdata = 1*64token-char ; see RFC 4566 +fn parse_msid(to_parse: &str) -> Result<SdpAttribute, SdpParserInternalError> { + let mut tokens = to_parse.split_whitespace(); + let id = match tokens.next() { + None => { + return Err(SdpParserInternalError::Generic( + "Msid attribute is missing msid-id token".to_string(), + )); + } + Some(x) => x.to_string(), + }; + let appdata = match tokens.next() { + None => None, + Some(x) => Some(x.to_string()), + }; + Ok(SdpAttribute::Msid(SdpAttributeMsid { id, appdata })) +} + +/////////////////////////////////////////////////////////////////////////// +// a=msid-semantic, draft-ietf-mmusic-msid +//------------------------------------------------------------------------- +// msid-semantic-attr = "msid-semantic:" msid-semantic msid-list +// msid-semantic = token ; see RFC 4566 +// msid-list = *(" " msid-id) / " *" +fn parse_msid_semantic(to_parse: &str) -> Result<SdpAttribute, SdpParserInternalError> { + let tokens: Vec<_> = to_parse.split_whitespace().collect(); + if tokens.is_empty() { + return Err(SdpParserInternalError::Generic( + "Msid-semantic attribute is missing msid-semantic token".to_string(), + )); + } + // TODO: Should msids be checked to ensure they are non empty? + let semantic = SdpAttributeMsidSemantic { + semantic: tokens[0].to_string(), + msids: tokens[1..].iter().map(ToString::to_string).collect(), + }; + Ok(SdpAttribute::MsidSemantic(semantic)) +} + +/////////////////////////////////////////////////////////////////////////// +// a=rid, draft-ietf-mmusic-rid +//------------------------------------------------------------------------- +// rid-syntax = %s"a=rid:" rid-id SP rid-dir +// [ rid-pt-param-list / rid-param-list ] +// rid-id = 1*(alpha-numeric / "-" / "_") +// alpha-numeric = < as defined in {{RFC4566}} > +// rid-dir = %s"send" / %s"recv" +// rid-pt-param-list = SP rid-fmt-list *(";" rid-param) +// rid-param-list = SP rid-param *(";" rid-param) +// rid-fmt-list = %s"pt=" fmt *( "," fmt ) +// fmt = < as defined in {{RFC4566}} > +// rid-param = rid-width-param +// / rid-height-param +// / rid-fps-param +// / rid-fs-param +// / rid-br-param +// / rid-pps-param +// / rid-bpp-param +// / rid-depend-param +// / rid-param-other +// rid-width-param = %s"max-width" [ "=" int-param-val ] +// rid-height-param = %s"max-height" [ "=" int-param-val ] +// rid-fps-param = %s"max-fps" [ "=" int-param-val ] +// rid-fs-param = %s"max-fs" [ "=" int-param-val ] +// rid-br-param = %s"max-br" [ "=" int-param-val ] +// rid-pps-param = %s"max-pps" [ "=" int-param-val ] +// rid-bpp-param = %s"max-bpp" [ "=" float-param-val ] +// rid-depend-param = %s"depend=" rid-list +// rid-param-other = 1*(alpha-numeric / "-") [ "=" param-val ] +// rid-list = rid-id *( "," rid-id ) +// int-param-val = 1*DIGIT +// float-param-val = 1*DIGIT "." 1*DIGIT +// param-val = *( %x20-58 / %x60-7E ) +// ; Any printable character except semicolon +fn parse_rid(to_parse: &str) -> Result<SdpAttribute, SdpParserInternalError> { + let tokens: Vec<&str> = to_parse.splitn(3, ' ').collect(); + + if tokens.len() < 2 { + return Err(SdpParserInternalError::Generic( + "A rid attribute must at least have an id and a direction token.".to_string(), + )); + } + + // Default initilize + let mut params = SdpAttributeRidParameters { + max_width: 0, + max_height: 0, + max_fps: 0, + max_fs: 0, + max_br: 0, + max_pps: 0, + unknown: Vec::new(), + }; + let mut formats: Vec<u16> = Vec::new(); + let mut depends: Vec<String> = Vec::new(); + + if let Some(param_token) = tokens.get(2) { + let mut parameters = param_token.split(';').peekable(); + + // The 'pt' parameter must be the first parameter if present, so it + // cannot be checked along with the other parameters below + if let Some(maybe_fmt_parameter) = parameters.clone().peek() { + if let Some(fmt_list) = maybe_fmt_parameter.strip_prefix("pt=") { + for fmt in fmt_list.split(',') { + formats.push(fmt.trim().parse::<u16>()?); + } + parameters.next(); + } + } + + for param in parameters { + // TODO: Bug 1225877. Add support for params without '=' + let param_value_pair: Vec<&str> = param.splitn(2, '=').collect(); + if param_value_pair.len() != 2 { + return Err(SdpParserInternalError::Generic( + "A rid parameter needs to be of form 'param=value'".to_string(), + )); + } + + match param_value_pair[0] { + "max-width" => params.max_width = param_value_pair[1].parse::<u32>()?, + "max-height" => params.max_height = param_value_pair[1].parse::<u32>()?, + "max-fps" => params.max_fps = param_value_pair[1].parse::<u32>()?, + "max-fs" => params.max_fs = param_value_pair[1].parse::<u32>()?, + "max-br" => params.max_br = param_value_pair[1].parse::<u32>()?, + "max-pps" => params.max_pps = param_value_pair[1].parse::<u32>()?, + "depends" => { + depends.extend(param_value_pair[1].split(',').map(ToString::to_string)); + } + _ => params.unknown.push(param.to_string()), + } + } + } + + Ok(SdpAttribute::Rid(SdpAttributeRid { + id: tokens[0].to_string(), + direction: parse_single_direction(tokens[1])?, + formats, + params, + depends, + })) +} + +/////////////////////////////////////////////////////////////////////////// +// a=remote-candiate, RFC5245 +//------------------------------------------------------------------------- +// remote-candidate-att = "remote-candidates" ":" remote-candidate +// 0*(SP remote-candidate) +// remote-candidate = component-ID SP connection-address SP port +fn parse_remote_candidates(to_parse: &str) -> Result<SdpAttribute, SdpParserInternalError> { + let mut tokens = to_parse.split_whitespace(); + let component = match tokens.next() { + None => { + return Err(SdpParserInternalError::Generic( + "Remote-candidate attribute is missing component ID".to_string(), + )); + } + Some(x) => x.parse::<u32>()?, + }; + let address = match tokens.next() { + None => { + return Err(SdpParserInternalError::Generic( + "Remote-candidate attribute is missing connection address".to_string(), + )); + } + Some(x) => parse_unicast_address(x)?, + }; + let port = match tokens.next() { + None => { + return Err(SdpParserInternalError::Generic( + "Remote-candidate attribute is missing port number".to_string(), + )); + } + Some(x) => x.parse::<u32>()?, + }; + if port > 65535 { + return Err(SdpParserInternalError::Generic( + "Remote-candidate port can only be a bit 16bit number".to_string(), + )); + }; + Ok(SdpAttribute::RemoteCandidate(SdpAttributeRemoteCandidate { + component, + address, + port, + })) +} + +/////////////////////////////////////////////////////////////////////////// +// a=rtpmap, RFC4566 +//------------------------------------------------------------------------- +// a=rtpmap:<payload type> <encoding name>/<clock rate> [/<encoding parameters>] +fn parse_rtpmap(to_parse: &str) -> Result<SdpAttribute, SdpParserInternalError> { + let mut tokens = to_parse.split_whitespace(); + let payload_type: u8 = match tokens.next() { + None => { + return Err(SdpParserInternalError::Generic( + "Rtpmap missing payload type".to_string(), + )); + } + Some(x) => { + let pt = x.parse::<u8>()?; + if pt > 127 { + return Err(SdpParserInternalError::Generic( + "Rtpmap payload type must be less then 127".to_string(), + )); + }; + pt + } + }; + let mut parameters = match tokens.next() { + None => { + return Err(SdpParserInternalError::Generic( + "Rtpmap missing payload type".to_string(), + )); + } + Some(x) => x.split('/'), + }; + let name = match parameters.next() { + None => { + return Err(SdpParserInternalError::Generic( + "Rtpmap missing codec name".to_string(), + )); + } + Some(x) => x.to_string(), + }; + let frequency = match parameters.next() { + None => { + return Err(SdpParserInternalError::Generic( + "Rtpmap missing codec name".to_string(), + )); + } + Some(x) => x.parse::<u32>()?, + }; + let mut rtpmap = SdpAttributeRtpmap::new(payload_type, name, frequency); + if let Some(x) = parameters.next() { + rtpmap.set_channels(x.parse::<u32>()?) + }; + Ok(SdpAttribute::Rtpmap(rtpmap)) +} + +/////////////////////////////////////////////////////////////////////////// +// a=rtcp, RFC3605 +//------------------------------------------------------------------------- +// rtcp-attribute = "a=rtcp:" port [nettype space addrtype space +// connection-address] CRLF +fn parse_rtcp(to_parse: &str) -> Result<SdpAttribute, SdpParserInternalError> { + let mut tokens = to_parse.split_whitespace(); + let port = match tokens.next() { + None => { + return Err(SdpParserInternalError::Generic( + "Rtcp attribute is missing port number".to_string(), + )); + } + Some(x) => x.parse::<u16>()?, + }; + let mut rtcp = SdpAttributeRtcp::new(port); + match tokens.next() { + None => (), + Some(x) => { + parse_network_type(x)?; + match tokens.next() { + None => { + return Err(SdpParserInternalError::Generic( + "Rtcp attribute is missing address type token".to_string(), + )); + } + Some(x) => { + let addrtype = AddressType::from_str(x)?; + let addr = match tokens.next() { + None => { + return Err(SdpParserInternalError::Generic( + "Rtcp attribute is missing ip address token".to_string(), + )); + } + Some(x) => match ExplicitlyTypedAddress::try_from((addrtype, x)) { + Ok(address) => address, + Err(e) => return Err(e), + }, + }; + rtcp.set_addr(addr); + } + }; + } + }; + Ok(SdpAttribute::Rtcp(rtcp)) +} + +/////////////////////////////////////////////////////////////////////////// +// a=rtcp-fb, RFC4585 +//------------------------------------------------------------------------- +// rtcp-fb-syntax = "a=rtcp-fb:" rtcp-fb-pt SP rtcp-fb-val CRLF +// +// rtcp-fb-pt = "*" ; wildcard: applies to all formats +// / fmt ; as defined in SDP spec +// +// rtcp-fb-val = "ack" rtcp-fb-ack-param +// / "nack" rtcp-fb-nack-param +// / "trr-int" SP 1*DIGIT +// / rtcp-fb-id rtcp-fb-param +// +// rtcp-fb-id = 1*(alpha-numeric / "-" / "_") +// +// rtcp-fb-param = SP "app" [SP byte-string] +// / SP token [SP byte-string] +// / ; empty +// +// rtcp-fb-ack-param = SP "rpsi" +// / SP "app" [SP byte-string] +// / SP token [SP byte-string] +// / ; empty +// +// rtcp-fb-nack-param = SP "pli" +// / SP "sli" +// / SP "rpsi" +// / SP "app" [SP byte-string] +// / SP token [SP byte-string] +// / ; empty +fn parse_rtcp_fb(to_parse: &str) -> Result<SdpAttribute, SdpParserInternalError> { + let tokens: Vec<&str> = to_parse.splitn(4, ' ').collect(); + + // Parse this in advance to use it later in the parameter switch + let feedback_type = match tokens.get(1) { + Some(x) => match *x { + "ack" => SdpAttributeRtcpFbType::Ack, + "ccm" => SdpAttributeRtcpFbType::Ccm, + "nack" => SdpAttributeRtcpFbType::Nack, + "trr-int" => SdpAttributeRtcpFbType::TrrInt, + "goog-remb" => SdpAttributeRtcpFbType::Remb, + "transport-cc" => SdpAttributeRtcpFbType::TransCC, + _ => { + return Err(SdpParserInternalError::Unsupported(format!( + "Unknown rtcpfb feedback type: {:?}", + x + ))); + } + }, + None => { + return Err(SdpParserInternalError::Generic( + "Error parsing rtcpfb: no feedback type".to_string(), + )); + } + }; + + // Parse this in advance to make the initilization block below better readable + let parameter = match feedback_type { + SdpAttributeRtcpFbType::Ack => match tokens.get(2) { + Some(x) => match *x { + "rpsi" | "app" => (*x).to_string(), + _ => { + return Err(SdpParserInternalError::Unsupported(format!( + "Unknown rtcpfb ack parameter: {:?}", + x + ))); + } + }, + None => { + return Err(SdpParserInternalError::Unsupported( + "The rtcpfb ack feeback type needs a parameter:".to_string(), + )); + } + }, + SdpAttributeRtcpFbType::Ccm => match tokens.get(2) { + Some(x) => match *x { + "fir" | "tmmbr" | "tstr" | "vbcm" => (*x).to_string(), + _ => { + return Err(SdpParserInternalError::Unsupported(format!( + "Unknown rtcpfb ccm parameter: {:?}", + x + ))); + } + }, + None => "".to_string(), + }, + SdpAttributeRtcpFbType::Nack => match tokens.get(2) { + Some(x) => match *x { + "sli" | "pli" | "rpsi" | "app" => (*x).to_string(), + _ => { + return Err(SdpParserInternalError::Unsupported(format!( + "Unknown rtcpfb nack parameter: {:?}", + x + ))); + } + }, + None => "".to_string(), + }, + SdpAttributeRtcpFbType::TrrInt => match tokens.get(2) { + Some(x) => match x { + _ if x.parse::<u32>().is_ok() => (*x).to_string(), + _ => { + return Err(SdpParserInternalError::Generic(format!( + "Unknown rtcpfb trr-int parameter: {:?}", + x + ))); + } + }, + None => { + return Err(SdpParserInternalError::Generic( + "The rtcpfb trr-int feedback type needs a parameter".to_string(), + )); + } + }, + SdpAttributeRtcpFbType::Remb | SdpAttributeRtcpFbType::TransCC => match tokens.get(2) { + Some(x) => { + return Err(SdpParserInternalError::Unsupported(format!( + "Unknown rtcpfb {} parameter: {:?}", + feedback_type, x + ))); + } + None => "".to_string(), + }, + }; + + Ok(SdpAttribute::Rtcpfb(SdpAttributeRtcpFb { + payload_type: parse_payload_type(tokens[0])?, + feedback_type, + parameter, + extra: match tokens.get(3) { + Some(x) => (*x).to_string(), + None => "".to_string(), + }, + })) +} + +/////////////////////////////////////////////////////////////////////////// +// a=sctpmap, draft-ietf-mmusic-sctp-sdp-05 +//------------------------------------------------------------------------- +// sctpmap-attr = "a=sctpmap:" sctpmap-number media-subtypes +// [streams] +// sctpmap-number = 1*DIGIT +// protocol = labelstring +// labelstring = text +// text = byte-string +// streams = 1*DIGIT +// +// Note: this was replace in later versions of the draft by sctp-port +fn parse_sctpmap(to_parse: &str) -> Result<SdpAttribute, SdpParserInternalError> { + let tokens: Vec<&str> = to_parse.split_whitespace().collect(); + if tokens.len() != 3 { + return Err(SdpParserInternalError::Generic( + "Sctpmap needs to have three tokens".to_string(), + )); + } + let port = tokens[0].parse::<u16>()?; + if tokens[1].to_lowercase() != "webrtc-datachannel" { + return Err(SdpParserInternalError::Generic( + "Unsupported sctpmap type token".to_string(), + )); + } + Ok(SdpAttribute::Sctpmap(SdpAttributeSctpmap { + port, + channels: tokens[2].parse::<u32>()?, + })) +} + +/////////////////////////////////////////////////////////////////////////// +// a=setup, RFC4145 +//------------------------------------------------------------------------- +// setup-attr = "a=setup:" role +// role = "active" / "passive" / "actpass" / "holdconn" +fn parse_setup(to_parse: &str) -> Result<SdpAttribute, SdpParserInternalError> { + Ok(SdpAttribute::Setup( + match to_parse.to_lowercase().as_ref() { + "active" => SdpAttributeSetup::Active, + "actpass" => SdpAttributeSetup::Actpass, + "holdconn" => SdpAttributeSetup::Holdconn, + "passive" => SdpAttributeSetup::Passive, + _ => { + return Err(SdpParserInternalError::Generic( + "Unsupported setup value".to_string(), + )); + } + }, + )) +} + +fn parse_simulcast_version_list( + to_parse: &str, +) -> Result<Vec<SdpAttributeSimulcastVersion>, SdpParserInternalError> { + let make_version_list = |to_parse: &str| { + to_parse + .split(';') + .map(SdpAttributeSimulcastVersion::new) + .collect() + }; + if to_parse.contains('=') { + let mut descriptor_versionlist_pair = to_parse.splitn(2, '='); + match descriptor_versionlist_pair.next().unwrap() { + // TODO Bug 1470568 + "rid" => Ok(make_version_list( + descriptor_versionlist_pair.next().unwrap(), + )), + descriptor => Err(SdpParserInternalError::Generic(format!( + "Simulcast attribute has unknown list descriptor '{:?}'", + descriptor + ))), + } + } else { + Ok(make_version_list(to_parse)) + } +} + +/////////////////////////////////////////////////////////////////////////// +// a=simulcast, draft-ietf-mmusic-sdp-simulcast +//------------------------------------------------------------------------- +// Old draft-04 +// sc-attr = "a=simulcast:" 1*2( WSP sc-str-list ) [WSP sc-pause-list] +// sc-str-list = sc-dir WSP sc-id-type "=" sc-alt-list *( ";" sc-alt-list ) +// sc-pause-list = "paused=" sc-alt-list +// sc-dir = "send" / "recv" +// sc-id-type = "pt" / "rid" / token +// sc-alt-list = sc-id *( "," sc-id ) +// sc-id = fmt / rid-identifier / token +// ; WSP defined in [RFC5234] +// ; fmt, token defined in [RFC4566] +// ; rid-identifier defined in [I-D.pthatcher-mmusic-rid] +// +// New draft 14, need to parse this for now, will eventually emit it +// sc-value = ( sc-send [SP sc-recv] ) / ( sc-recv [SP sc-send] ) +// sc-send = %s"send" SP sc-str-list +// sc-recv = %s"recv" SP sc-str-list +// sc-str-list = sc-alt-list *( ";" sc-alt-list ) +// sc-alt-list = sc-id *( "," sc-id ) +// sc-id-paused = "~" +// sc-id = [sc-id-paused] rid-id +// ; SP defined in [RFC5234] +// ; rid-id defined in [I-D.ietf-mmusic-rid] +fn parse_simulcast(to_parse: &str) -> Result<SdpAttribute, SdpParserInternalError> { + // TODO: Bug 1225877: Stop accepting all kinds of whitespace here, and only accept SP + let mut tokens = to_parse.trim().split_whitespace(); + let first_direction = match tokens.next() { + Some(x) => parse_single_direction(x)?, + None => { + return Err(SdpParserInternalError::Generic( + "Simulcast attribute is missing send/recv value".to_string(), + )); + } + }; + + let first_version_list = match tokens.next() { + Some(x) => parse_simulcast_version_list(x)?, + None => { + return Err(SdpParserInternalError::Generic( + "Simulcast attribute must have an alternatives list after the direction token" + .to_string(), + )); + } + }; + + let mut second_version_list = Vec::new(); + if let Some(x) = tokens.next() { + if parse_single_direction(x)? == first_direction { + return Err(SdpParserInternalError::Generic( + "Simulcast attribute has defined two times the same direction".to_string(), + )); + } + + second_version_list = match tokens.next() { + Some(x) => parse_simulcast_version_list(x)?, + None => { + return Err(SdpParserInternalError::Generic(format!( + "{:?}{:?}", + "Simulcast has defined a second direction but", + "no second list of simulcast stream versions" + ))); + } + } + } + + Ok(SdpAttribute::Simulcast(match first_direction { + SdpSingleDirection::Send => SdpAttributeSimulcast { + send: first_version_list, + receive: second_version_list, + }, + SdpSingleDirection::Recv => SdpAttributeSimulcast { + send: second_version_list, + receive: first_version_list, + }, + })) +} + +/////////////////////////////////////////////////////////////////////////// +// a=ssrc, RFC5576 +//------------------------------------------------------------------------- +// ssrc-attr = "ssrc:" ssrc-id SP attribute +// ; The base definition of "attribute" is in RFC 4566. +// ; (It is the content of "a=" lines.) +// +// ssrc-id = integer ; 0 .. 2**32 - 1 +fn parse_ssrc(to_parse: &str) -> Result<SdpAttribute, SdpParserInternalError> { + let mut tokens = to_parse.splitn(2, ' '); + let ssrc_id = match tokens.next() { + None => { + return Err(SdpParserInternalError::Generic( + "Ssrc attribute is missing ssrc-id value".to_string(), + )); + } + Some(x) => x.parse::<u32>()?, + }; + let mut ssrc = SdpAttributeSsrc::new(ssrc_id); + match tokens.next() { + None => (), + Some(x) => ssrc.set_attribute(x), + }; + Ok(SdpAttribute::Ssrc(ssrc)) +} + +pub fn parse_attribute(value: &str) -> Result<SdpType, SdpParserInternalError> { + Ok(SdpType::Attribute(value.trim().parse()?)) +} + +#[cfg(test)] +mod tests { + extern crate url; + use super::*; + use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; + + macro_rules! make_check_parse { + ($attr_type:ty, $attr_kind:path) => { + |attr_str: &str| -> $attr_type { + match parse_attribute(attr_str) { + Ok(SdpType::Attribute($attr_kind(attr))) => attr, + Err(e) => panic!(e), + _ => unreachable!(), + } + } + }; + + ($attr_kind:path) => { + |attr_str: &str| -> SdpAttribute { + match parse_attribute(attr_str) { + Ok(SdpType::Attribute($attr_kind)) => $attr_kind, + Err(e) => panic!(e), + _ => unreachable!(), + } + } + }; + } + + macro_rules! make_check_parse_and_serialize { + ($check_parse_func:ident, $attr_kind:path) => { + |attr_str: &str| { + let parsed = $attr_kind($check_parse_func(attr_str)); + assert_eq!(parsed.to_string(), attr_str.to_string()); + } + }; + + ($check_parse_func:ident) => { + |attr_str: &str| { + let parsed = $check_parse_func(attr_str); + assert_eq!(parsed.to_string(), attr_str.to_string()); + } + }; + } + + #[test] + fn test_parse_attribute_candidate_and_serialize() { + let check_parse = make_check_parse!(SdpAttributeCandidate, SdpAttribute::Candidate); + let check_parse_and_serialize = + make_check_parse_and_serialize!(check_parse, SdpAttribute::Candidate); + + check_parse_and_serialize("candidate:0 1 UDP 2122252543 172.16.156.106 49760 typ host"); + check_parse_and_serialize("candidate:foo 1 UDP 2122252543 172.16.156.106 49760 typ host"); + check_parse_and_serialize("candidate:0 1 TCP 2122252543 172.16.156.106 49760 typ host"); + check_parse_and_serialize("candidate:0 1 TCP 2122252543 ::1 49760 typ host"); + check_parse_and_serialize( + "candidate:0 1 TCP 2122252543 2001:db8:4860::4444 49760 typ host", + ); + check_parse_and_serialize("candidate:0 1 UDP 2122252543 172.16.156.106 49760 typ srflx"); + check_parse_and_serialize("candidate:0 1 UDP 2122252543 172.16.156.106 49760 typ prflx"); + check_parse_and_serialize("candidate:0 1 UDP 2122252543 172.16.156.106 49760 typ relay"); + check_parse_and_serialize( + "candidate:0 1 TCP 2122252543 172.16.156.106 49760 typ host tcptype active", + ); + check_parse_and_serialize( + "candidate:0 1 TCP 2122252543 172.16.156.106 49760 typ host tcptype passive", + ); + check_parse_and_serialize( + "candidate:0 1 TCP 2122252543 172.16.156.106 49760 typ host tcptype so", + ); + check_parse_and_serialize( + "candidate:0 1 TCP 2122252543 172.16.156.106 49760 typ host ufrag foobar", + ); + check_parse_and_serialize( + "candidate:0 1 TCP 2122252543 172.16.156.106 49760 typ host network-cost 50", + ); + check_parse_and_serialize("candidate:1 1 UDP 1685987071 24.23.204.141 54609 typ srflx raddr 192.168.1.4 rport 61665 generation 0"); + check_parse_and_serialize( + "candidate:1 1 UDP 1685987071 24.23.204.141 54609 typ srflx raddr 192.168.1.4 rport 61665", + ); + check_parse_and_serialize("candidate:1 1 TCP 1685987071 24.23.204.141 54609 typ srflx raddr 192.168.1.4 rport 61665 tcptype passive"); + check_parse_and_serialize("candidate:1 1 TCP 1685987071 24.23.204.141 54609 typ srflx raddr 192.168.1.4 rport 61665 tcptype passive generation 1"); + check_parse_and_serialize("candidate:1 1 TCP 1685987071 24.23.204.141 54609 typ srflx raddr 192.168.1.4 rport 61665 tcptype passive generation 1 ufrag +DGd"); + check_parse_and_serialize("candidate:1 1 TCP 1685987071 24.23.204.141 54609 typ srflx raddr 192.168.1.4 rport 61665 tcptype passive generation 1 ufrag +DGd network-cost 1"); + check_parse_and_serialize( + "candidate:0 1 TCP 2122252543 172.16.156.106 49760 typ host unsupported foo", + ); + check_parse_and_serialize("candidate:0 1 TCP 2122252543 172.16.156.106 49760 typ host unsupported foo more_unsupported bar"); + + let candidate = check_parse("candidate:1 1 TCP 1685987071 24.23.204.141 54609 typ srflx raddr 192.168.1.4 rport 61665 tcptype passive generation 1 ufrag +DGd network-cost 1 unsupported foo"); + assert_eq!(candidate.foundation, "1".to_string()); + assert_eq!(candidate.component, 1); + assert_eq!(candidate.transport, SdpAttributeCandidateTransport::Tcp); + assert_eq!(candidate.priority, 1_685_987_071); + assert_eq!( + candidate.address, + Address::from_str("24.23.204.141").unwrap() + ); + assert_eq!(candidate.port, 54609); + assert_eq!(candidate.c_type, SdpAttributeCandidateType::Srflx); + assert_eq!( + candidate.raddr, + Some(Address::from_str("192.168.1.4").unwrap()) + ); + assert_eq!(candidate.rport, Some(61665)); + assert_eq!( + candidate.tcp_type, + Some(SdpAttributeCandidateTcpType::Passive) + ); + assert_eq!(candidate.generation, Some(1)); + assert_eq!(candidate.ufrag, Some("+DGd".to_string())); + assert_eq!(candidate.networkcost, Some(1)); + assert_eq!( + candidate.unknown_extensions, + vec![("unsupported".to_string(), "foo".to_string())] + ) + } + + #[test] + fn test_anonymize_attribute_candidate() -> Result<(), SdpParserInternalError> { + let mut anon = StatefulSdpAnonymizer::new(); + let candidate_1 = parse_attribute("candidate:0 1 TCP 2122252543 ::8 49760 typ host")?; + let candidate_2 = + parse_attribute("candidate:0 1 UDP 2122252543 172.16.156.106 19361 typ srflx")?; + let candidate_3 = parse_attribute("candidate:1 1 TCP 1685987071 24.23.204.141 54609 typ srflx raddr 192.168.1.4 rport 61665 tcptype passive generation 1 ufrag +DGd")?; + if let SdpType::Attribute(SdpAttribute::Candidate(candidate)) = candidate_1 { + let masked = candidate.masked_clone(&mut anon); + assert!(masked.address == Address::Ip(IpAddr::V6(Ipv6Addr::from(1)))); + assert!(masked.port == 1); + } else { + unreachable!(); + } + + if let SdpType::Attribute(SdpAttribute::Candidate(candidate)) = candidate_2 { + let masked = candidate.masked_clone(&mut anon); + assert!(masked.address == Address::Ip(IpAddr::V4(Ipv4Addr::from(1)))); + assert!(masked.port == 2); + } else { + unreachable!(); + } + + if let SdpType::Attribute(SdpAttribute::Candidate(candidate)) = candidate_3 { + let masked = candidate.masked_clone(&mut anon); + assert!(masked.address == Address::Ip(IpAddr::V4(Ipv4Addr::from(2)))); + assert!(masked.port == 3); + assert!(masked.raddr.unwrap() == Address::Ip(IpAddr::V4(Ipv4Addr::from(3)))); + assert!(masked.rport.unwrap() == 4); + } else { + unreachable!(); + } + Ok(()) + } + + #[test] + fn test_parse_attribute_candidate_errors() { + assert!(parse_attribute("candidate:0 1 UDP 2122252543 172.16.156.106 49760 typ").is_err()); + assert!( + parse_attribute("candidate:0 foo UDP 2122252543 172.16.156.106 49760 typ host") + .is_err() + ); + assert!( + parse_attribute("candidate:0 1 FOO 2122252543 172.16.156.106 49760 typ host").is_err() + ); + assert!(parse_attribute("candidate:0 1 UDP foo 172.16.156.106 49760 typ host").is_err()); + assert!(parse_attribute("candidate:0 1 UDP 2122252543 372.16.356 49760 typ host").is_err()); + assert!( + parse_attribute("candidate:0 1 UDP 2122252543 172.16.156.106 70000 typ host").is_err() + ); + assert!( + parse_attribute("candidate:0 1 UDP 2122252543 172.16.156.106 49760 type host").is_err() + ); + assert!( + parse_attribute("candidate:0 1 UDP 2122252543 172.16.156.106 49760 typ fost").is_err() + ); + assert!(parse_attribute( + "candidate:0 1 TCP 2122252543 172.16.156.106 49760 typ host unsupported" + ) + .is_err()); + assert!(parse_attribute( + "candidate:0 1 TCP 2122252543 172.16.156.106 49760 typ host network-cost" + ) + .is_err()); + assert!(parse_attribute("candidate:1 1 UDP 1685987071 24.23.204.141 54609 typ srflx raddr 192.168.1.4 rport 61665 generation B").is_err()); + assert!(parse_attribute( + "candidate:0 1 TCP 2122252543 172.16.156.106 49760 typ host network-cost C" + ) + .is_err()); + assert!(parse_attribute( + "candidate:1 1 UDP 1685987071 24.23.204.141 54609 typ srflx raddr 1%92.168.1 rport 61665" + ) + .is_err()); + assert!(parse_attribute( + "candidate:0 1 TCP 2122252543 172.16.156.106 49760 typ host tcptype foobar" + ) + .is_err()); + assert!(parse_attribute( + "candidate:1 1 UDP 1685987071 24.23.204.141 54609 typ srflx raddr 192.168.1.4 rport 70000" + ) + .is_err()); + } + + #[test] + fn test_parse_dtls_message() { + let check_parse = make_check_parse!(SdpAttributeDtlsMessage, SdpAttribute::DtlsMessage); + let check_parse_and_serialize = + make_check_parse_and_serialize!(check_parse, SdpAttribute::DtlsMessage); + + check_parse_and_serialize("dtls-message:client SGVsbG8gV29ybGQ="); + check_parse_and_serialize("dtls-message:server SGVsbG8gV29ybGQ="); + check_parse_and_serialize("dtls-message:client IGlzdCBl/W4gUeiBtaXQg+JSB1bmQCAkJJkSNEQ="); + check_parse_and_serialize("dtls-message:server IGlzdCBl/W4gUeiBtaXQg+JSB1bmQCAkJJkSNEQ="); + + match check_parse("dtls-message:client SGVsbG8gV29ybGQ=") { + SdpAttributeDtlsMessage::Client(x) => { + assert_eq!(x, "SGVsbG8gV29ybGQ="); + } + _ => { + unreachable!(); + } + } + + match check_parse("dtls-message:server SGVsbG8gV29ybGQ=") { + SdpAttributeDtlsMessage::Server(x) => { + assert_eq!(x, "SGVsbG8gV29ybGQ="); + } + _ => { + unreachable!(); + } + } + + assert!(parse_attribute("dtls-message:client").is_err()); + assert!(parse_attribute("dtls-message:server").is_err()); + assert!(parse_attribute("dtls-message:unsupported SGVsbG8gV29ybGQ=").is_err()); + } + + #[test] + fn test_parse_attribute_end_of_candidates() { + let check_parse = make_check_parse!(SdpAttribute::EndOfCandidates); + let check_parse_and_serialize = make_check_parse_and_serialize!(check_parse); + + check_parse_and_serialize("end-of-candidates"); + assert!(parse_attribute("end-of-candidates foobar").is_err()); + } + + #[test] + fn test_parse_attribute_extmap() { + let check_parse = make_check_parse!(SdpAttributeExtmap, SdpAttribute::Extmap); + let check_parse_and_serialize = + make_check_parse_and_serialize!(check_parse, SdpAttribute::Extmap); + + check_parse_and_serialize("extmap:1/sendonly urn:ietf:params:rtp-hdrext:ssrc-audio-level"); + check_parse_and_serialize("extmap:2/sendrecv urn:ietf:params:rtp-hdrext:ssrc-audio-level"); + check_parse_and_serialize( + "extmap:3 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time", + ); + check_parse_and_serialize( + "extmap:3 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time ext_attributes", + ); + + assert!(parse_attribute("extmap:1/sendrecv").is_err()); + assert!( + parse_attribute("extmap:a/sendrecv urn:ietf:params:rtp-hdrext:ssrc-audio-level") + .is_err() + ); + assert!(parse_attribute( + "extmap:4/unsupported urn:ietf:params:rtp-hdrext:ssrc-audio-level" + ) + .is_err()); + + let mut bad_char = + String::from("extmap:3 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time "); + bad_char.push(0x00 as char); + assert!(parse_attribute(&bad_char).is_err()); + } + + #[test] + fn test_parse_attribute_fingerprint() { + let check_parse = make_check_parse!(SdpAttributeFingerprint, SdpAttribute::Fingerprint); + let check_parse_and_serialize = + make_check_parse_and_serialize!(check_parse, SdpAttribute::Fingerprint); + + check_parse_and_serialize( + "fingerprint:sha-1 CD:34:D1:62:16:95:7B:B7:EB:74:E2:39:27:97:EB:0B:23:73:AC:BC", + ); + check_parse_and_serialize( + "fingerprint:sha-224 CD:34:D1:62:16:95:7B:B7:EB:74:E2:39:27:97:EB:0B:23:73:AC:BC:\ + 27:97:EB:0B:23:73:AC:BC", + ); + check_parse_and_serialize( + "fingerprint:sha-256 CD:34:D1:62:16:95:7B:B7:EB:74:E2:39:27:97:EB:0B:23:73:AC:BC:\ + 27:97:EB:0B:23:73:AC:BC:CD:34:D1:62", + ); + check_parse_and_serialize( + "fingerprint:sha-384 CD:34:D1:62:16:95:7B:B7:EB:74:E2:39:27:97:EB:0B:23:73:AC:BC:\ + 27:97:EB:0B:23:73:AC:BC:CD:34:D1:62:16:95:7B:B7:EB:74:E2:39:\ + 27:97:EB:0B:23:73:AC:BC", + ); + check_parse_and_serialize( + "fingerprint:sha-512 CD:34:D1:62:16:95:7B:B7:EB:74:E2:39:27:97:EB:0B:23:73:AC:BC:\ + 97:EB:0B:23:73:AC:BC:CD:34:D1:62:16:95:7B:B7:EB:74:E2:39:27:\ + EB:0B:23:73:AC:BC:27:97:EB:0B:23:73:AC:BC:27:97:EB:0B:23:73:\ + BC:EB:0B:23", + ); + + assert!(parse_attribute("fingerprint:sha-1").is_err()); + assert!(parse_attribute( + "fingerprint:unsupported CD:34:D1:62:16:95:7B:B7:EB:74:E1:39:27:97:EB:0B:23:73:AC:BC" + ) + .is_err()); + assert!(parse_attribute( + "fingerprint:sha-1 CDA:34:D1:62:16:95:7B:B7:EB:74:E1:39:27:97:EB:0B:23:73:AC:BC" + ) + .is_err()); + assert!(parse_attribute( + "fingerprint:sha-1 CD:34:D1:62:16:95:7B:B7:EB:74:E1:39:27:97:EB:0B:23:73:AC:" + ) + .is_err()); + assert!(parse_attribute( + "fingerprint:sha-1 CD:34:D1:62:16:95:7B:B7:EB:74:E1:39:27:97:EB:0B:23:73:AC" + ) + .is_err()); + assert!(parse_attribute( + "fingerprint:sha-1 CX:34:D1:62:16:95:7B:B7:EB:74:E1:39:27:97:EB:0B:23:73:AC:BC" + ) + .is_err()); + + assert!(parse_attribute( + "fingerprint:sha-1 0xCD:34:D1:62:16:95:7B:B7:EB:74:E2:39:27:97:EB:0B:23:73:AC:BC" + ) + .is_err()); + assert!(parse_attribute( + "fingerprint:sha-1 CD:0x34:D1:62:16:95:7B:B7:EB:74:E2:39:27:97:EB:0B:23:73:AC:BC" + ) + .is_err()); + assert!(parse_attribute( + "fingerprint:sha-1 CD::D1:62:16:95:7B:B7:EB:74:E2:39:27:97:EB:0B:23:73:AC:BC" + ) + .is_err()); + assert!(parse_attribute( + "fingerprint:sha-1 CD:0000A:D1:62:16:95:7B:B7:EB:74:E2:39:27:97:EB:0B:23:73:AC:BC" + ) + .is_err()); + assert!(parse_attribute( + "fingerprint:sha-1 CD:B:D1:62:16:95:7B:B7:EB:74:E2:39:27:97:EB:0B:23:73:AC:BC" + ) + .is_err()); + } + + #[test] + fn test_parse_attribute_fmtp() { + let check_parse = make_check_parse!(SdpAttributeFmtp, SdpAttribute::Fmtp); + let check_parse_and_serialize = + make_check_parse_and_serialize!(check_parse, SdpAttribute::Fmtp); + + check_parse_and_serialize("fmtp:109 maxplaybackrate=46000;stereo=1;useinbandfec=1"); + check_parse_and_serialize( + "fmtp:126 profile-level-id=42e01f;level-asymmetry-allowed=1;packetization-mode=1", + ); + check_parse_and_serialize("fmtp:66 0-15"); + check_parse_and_serialize("fmtp:109 0-15,66"); + check_parse_and_serialize("fmtp:66 111/115"); + assert!(parse_attribute("fmtp:109 maxplaybackrate=48000;stereo=1;useinbandfec=1").is_ok()); + assert!( + parse_attribute("fmtp:109 maxplaybackrate=48000; stereo=1; useinbandfec=1").is_ok() + ); + assert!(parse_attribute("fmtp:109 maxplaybackrate=48000; stereo=1;useinbandfec=1").is_ok()); + check_parse_and_serialize("fmtp:8 maxplaybackrate=46000"); + check_parse_and_serialize("fmtp:8 maxaveragebitrate=46000"); + check_parse_and_serialize( + "fmtp:8 maxaveragebitrate=46000;ptime=60;minptime=20;maxptime=120", + ); + check_parse_and_serialize( + "fmtp:8 max-cpb=1234;max-dpb=32000;max-br=3;max-mbps=46000;usedtx=1;cbr=1", + ); + assert!(parse_attribute("fmtp:77 ").is_err()); + assert!(parse_attribute("fmtp:109 stereo=2;").is_err()); + assert!(parse_attribute("fmtp:109 111/129;").is_err()); + assert!(parse_attribute("fmtp:109 packetization-mode=3;").is_err()); + assert!(parse_attribute("fmtp:109 maxplaybackrate=48000stereo=1;").is_err()); + assert!(parse_attribute("fmtp:8 ;maxplaybackrate=48000").is_ok()); + assert!(parse_attribute("fmtp:8 packetization-mode=2;;maxplaybackrate=48000").is_ok()); + assert!(parse_attribute("fmtp:8 packetization-mode=2; maxplaybackrate=48000").is_ok()); + assert!(parse_attribute("fmtp:8 maxplaybackrate=48000;").is_ok()); + assert!( + parse_attribute("fmtp:8 x-google-start-bitrate=800; maxplaybackrate=48000;").is_ok() + ); + check_parse_and_serialize("fmtp:97 apt=96"); + check_parse_and_serialize("fmtp:97 apt=96;rtx-time=3000"); + } + + #[test] + fn test_anonymize_attribute_fingerprint() -> Result<(), SdpParserInternalError> { + let mut anon = StatefulSdpAnonymizer::new(); + if let SdpType::Attribute(SdpAttribute::Fingerprint(print)) = parse_attribute( + "fingerprint:sha-1 CD:34:D1:62:16:95:7B:B7:EB:74:E2:39:27:97:EB:0B:23:73:AC:BC", + )? { + assert!(print.masked_clone(&mut anon).to_string() == "sha-1 00:00:00:00:00:00:00:01"); + } else { + unreachable!(); + } + Ok(()) + } + + #[test] + fn test_parse_attribute_group() { + let check_parse = make_check_parse!(SdpAttributeGroup, SdpAttribute::Group); + let check_parse_and_serialize = + make_check_parse_and_serialize!(check_parse, SdpAttribute::Group); + + check_parse_and_serialize("group:LS"); + check_parse_and_serialize("group:LS 1 2"); + check_parse_and_serialize("group:FID 1 2"); + check_parse_and_serialize("group:SRF 1 2"); + check_parse_and_serialize("group:FEC S1 R1"); + check_parse_and_serialize("group:ANAT S1 R1"); + check_parse_and_serialize("group:DDP L1 L2 L3"); + check_parse_and_serialize("group:BUNDLE sdparta_0 sdparta_1 sdparta_2"); + + assert!(parse_attribute("group:").is_err()); + assert!(matches!( + parse_attribute("group:NEVER_SUPPORTED_SEMANTICS"), + Err(SdpParserInternalError::Unsupported(_)) + )); + } + + #[test] + fn test_parse_attribute_bundle_only() { + let check_parse = make_check_parse!(SdpAttribute::BundleOnly); + let check_parse_and_serialize = make_check_parse_and_serialize!(check_parse); + + check_parse_and_serialize("bundle-only"); + + assert!(parse_attribute("bundle-only foobar").is_err()); + } + + #[test] + fn test_parse_attribute_ice_lite() { + let check_parse = make_check_parse!(SdpAttribute::IceLite); + let check_parse_and_serialize = make_check_parse_and_serialize!(check_parse); + + check_parse_and_serialize("ice-lite"); + + assert!(parse_attribute("ice-lite foobar").is_err()); + } + + #[test] + fn test_parse_attribute_extmap_allow_mixed() { + let check_parse = make_check_parse!(SdpAttribute::ExtmapAllowMixed); + let check_parse_and_serialize = make_check_parse_and_serialize!(check_parse); + + check_parse_and_serialize("extmap-allow-mixed"); + + assert!(parse_attribute("extmap-allow-mixed 100").is_err()); + } + + #[test] + fn test_parse_attribute_ice_mismatch() { + let check_parse = make_check_parse!(SdpAttribute::IceMismatch); + let check_parse_and_serialize = make_check_parse_and_serialize!(check_parse); + + check_parse_and_serialize("ice-mismatch"); + + assert!(parse_attribute("ice-mismatch foobar").is_err()); + } + + #[test] + fn test_parse_attribute_ice_options() { + let check_parse = make_check_parse!(Vec<String>, SdpAttribute::IceOptions); + let check_parse_and_serialize = + make_check_parse_and_serialize!(check_parse, SdpAttribute::IceOptions); + + check_parse_and_serialize("ice-options:trickle"); + + assert!(parse_attribute("ice-options:").is_err()); + } + + #[test] + fn test_parse_attribute_ice_pacing() { + let check_parse = make_check_parse!(u64, SdpAttribute::IcePacing); + let check_parse_and_serialize = + make_check_parse_and_serialize!(check_parse, SdpAttribute::IcePacing); + + check_parse_and_serialize("ice-pacing:50"); + + assert!(parse_attribute("ice-pacing:").is_err()); + assert!(parse_attribute("ice-pacing:10000000000").is_err()); + assert!(parse_attribute("ice-pacing:50 100").is_err()); + assert!(parse_attribute("ice-pacing:foobar").is_err()); + } + + #[test] + fn test_parse_attribute_ice_pwd() { + let check_parse = make_check_parse!(String, SdpAttribute::IcePwd); + let check_parse_and_serialize = + make_check_parse_and_serialize!(check_parse, SdpAttribute::IcePwd); + + check_parse_and_serialize("ice-pwd:e3baa26dd2fa5030d881d385f1e36cce"); + + assert!(parse_attribute("ice-pwd:").is_err()); + } + + #[test] + fn test_parse_attribute_ice_ufrag() { + let check_parse = make_check_parse!(String, SdpAttribute::IceUfrag); + let check_parse_and_serialize = + make_check_parse_and_serialize!(check_parse, SdpAttribute::IceUfrag); + + check_parse_and_serialize("ice-ufrag:58b99ead"); + + assert!(parse_attribute("ice-ufrag:").is_err()); + } + + #[test] + fn test_parse_attribute_identity() { + let check_parse = make_check_parse!(String, SdpAttribute::Identity); + let check_parse_and_serialize = + make_check_parse_and_serialize!(check_parse, SdpAttribute::Identity); + + check_parse_and_serialize("identity:eyJpZHAiOnsiZG9tYWluIjoiZXhhbXBsZS5vcmciLCJwcm90b2NvbCI6ImJvZ3VzIn0sImFzc2VydGlvbiI6IntcImlkZW50aXR5XCI6XCJib2JAZXhhbXBsZS5vcmdcIixcImNvbnRlbnRzXCI6XCJhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3l6XCIsXCJzaWduYXR1cmVcIjpcIjAxMDIwMzA0MDUwNlwifSJ9"); + + assert!(parse_attribute("identity:").is_err()); + } + + #[test] + fn test_parse_attribute_imageattr() { + let check_parse = make_check_parse!(SdpAttributeImageAttr, SdpAttribute::ImageAttr); + let check_parse_and_serialize = + make_check_parse_and_serialize!(check_parse, SdpAttribute::ImageAttr); + + check_parse_and_serialize("imageattr:120 send * recv *"); + check_parse_and_serialize("imageattr:99 send [x=320,y=240] recv [x=320,y=240]"); + check_parse_and_serialize( + "imageattr:97 send [x=800,y=640,sar=1.1,q=0.6] [x=480,y=320] recv [x=330,y=250]", + ); + check_parse_and_serialize("imageattr:97 send [x=[480:16:800],y=[320:16:640],par=[1.2-1.3],q=0.6] [x=[176:8:208],y=[144:8:176],par=[1.2-1.3]] recv *"); + assert!( + parse_attribute("imageattr:97 recv [x=800,y=640,sar=1.1] send [x=330,y=250]").is_ok() + ); + + check_parse_and_serialize("imageattr:99 send [x=320,y=240]"); + assert!(parse_attribute("imageattr:100 recv [x=320,y=240]").is_ok()); + assert!(parse_attribute("imageattr:97 recv [x=800,y=640,sar=1.1,foo=[123,456],q=0.5] send [x=330,y=250,bar=foo,sar=[20-40]]").is_ok()); + assert!(parse_attribute("imageattr:97 recv [x=800,y=640,sar=1.1,foo=abc xyz,q=0.5] send [x=330,y=250,bar=foo,sar=[20-40]]").is_ok()); + + assert!(parse_attribute("imageattr:").is_err()); + assert!(parse_attribute("imageattr:100").is_err()); + assert!(parse_attribute("imageattr:120 send * recv * send *").is_err()); + assert!(parse_attribute("imageattr:99 send [x=320]").is_err()); + assert!(parse_attribute("imageattr:99 recv [y=240]").is_err()); + assert!(parse_attribute("imageattr:99 send [x=320,y=240").is_err()); + assert!(parse_attribute("imageattr:99 send x=320,y=240]").is_err()); + assert!( + parse_attribute("imageattr:97 send [x=800,y=640,sar=1.1] send [x=330,y=250]").is_err() + ); + } + + #[test] + fn test_parse_attribute_imageattr_recv_and_verify() { + let check_parse = make_check_parse!(SdpAttributeImageAttr, SdpAttribute::ImageAttr); + + let imageattr = check_parse( + "imageattr:* recv [x=800,y=[50,80,30],sar=1.1] send [x=330,y=250,sar=[1.1,1.3,1.9],q=0.1]", + ); + assert_eq!(imageattr.pt, SdpAttributePayloadType::Wildcard); + match imageattr.recv { + SdpAttributeImageAttrSetList::Sets(sets) => { + assert_eq!(sets.len(), 1); + + let set = &sets[0]; + assert_eq!( + set.x, + SdpAttributeImageAttrXYRange::DiscreteValues(vec![800]) + ); + assert_eq!( + set.y, + SdpAttributeImageAttrXYRange::DiscreteValues(vec![50, 80, 30]) + ); + assert_eq!(set.par, None); + assert_eq!( + set.sar, + Some(SdpAttributeImageAttrSRange::DiscreteValues(vec![1.1])) + ); + assert_eq!(set.q, None); + } + _ => { + unreachable!(); + } + } + match imageattr.send { + SdpAttributeImageAttrSetList::Sets(sets) => { + assert_eq!(sets.len(), 1); + + let set = &sets[0]; + assert_eq!( + set.x, + SdpAttributeImageAttrXYRange::DiscreteValues(vec![330]) + ); + assert_eq!( + set.y, + SdpAttributeImageAttrXYRange::DiscreteValues(vec![250]) + ); + assert_eq!(set.par, None); + assert_eq!( + set.sar, + Some(SdpAttributeImageAttrSRange::DiscreteValues(vec![ + 1.1, 1.3, 1.9, + ])) + ); + assert_eq!(set.q, Some(0.1)); + } + _ => { + unreachable!(); + } + } + } + + #[test] + fn test_parse_attribute_imageattr_send_and_verify() { + let check_parse = make_check_parse!(SdpAttributeImageAttr, SdpAttribute::ImageAttr); + + let imageattr = check_parse( + "imageattr:97 send [x=[480:16:800],y=[100,200,300],par=[1.2-1.3],q=0.6] [x=1080,y=[144:176],sar=[0.5-0.7]] recv *" + ); + assert_eq!(imageattr.pt, SdpAttributePayloadType::PayloadType(97)); + match imageattr.send { + SdpAttributeImageAttrSetList::Sets(sets) => { + assert_eq!(sets.len(), 2); + + let first_set = &sets[0]; + assert_eq!( + first_set.x, + SdpAttributeImageAttrXYRange::Range(480, 800, Some(16)) + ); + assert_eq!( + first_set.y, + SdpAttributeImageAttrXYRange::DiscreteValues(vec![100, 200, 300]) + ); + assert_eq!( + first_set.par, + Some(SdpAttributeImageAttrPRange { min: 1.2, max: 1.3 }) + ); + assert_eq!(first_set.sar, None); + assert_eq!(first_set.q, Some(0.6)); + + let second_set = &sets[1]; + assert_eq!( + second_set.x, + SdpAttributeImageAttrXYRange::DiscreteValues(vec![1080]) + ); + assert_eq!( + second_set.y, + SdpAttributeImageAttrXYRange::Range(144, 176, None) + ); + assert_eq!(second_set.par, None); + assert_eq!( + second_set.sar, + Some(SdpAttributeImageAttrSRange::Range(0.5, 0.7)) + ); + assert_eq!(second_set.q, None); + } + _ => { + unreachable!(); + } + } + assert_eq!(imageattr.recv, SdpAttributeImageAttrSetList::Wildcard); + } + + #[test] + fn test_parse_attribute_inactive() { + let check_parse = make_check_parse!(SdpAttribute::Inactive); + let check_parse_and_serialize = make_check_parse_and_serialize!(check_parse); + + check_parse_and_serialize("inactive"); + assert!(parse_attribute("inactive foobar").is_err()); + } + + #[test] + fn test_parse_attribute_label() { + let check_parse = make_check_parse!(String, SdpAttribute::Label); + let check_parse_and_serialize = + make_check_parse_and_serialize!(check_parse, SdpAttribute::Label); + + check_parse_and_serialize("label:1"); + check_parse_and_serialize("label:foobar"); + check_parse_and_serialize("label:foobar barfoo"); + + assert!(parse_attribute("label:").is_err()); + } + + #[test] + fn test_parse_attribute_maxptime() { + let check_parse = make_check_parse!(u64, SdpAttribute::MaxPtime); + let check_parse_and_serialize = + make_check_parse_and_serialize!(check_parse, SdpAttribute::MaxPtime); + + check_parse_and_serialize("maxptime:60"); + + assert!(parse_attribute("maxptime:").is_err()); + assert!(parse_attribute("maxptime:60 100").is_err()); + assert!(parse_attribute("maxptime:foobar").is_err()); + } + + #[test] + fn test_parse_attribute_mid() { + let check_parse = make_check_parse!(String, SdpAttribute::Mid); + let check_parse_and_serialize = + make_check_parse_and_serialize!(check_parse, SdpAttribute::Mid); + + check_parse_and_serialize("mid:sdparta_0"); + check_parse_and_serialize("mid:sdparta_0 sdparta_1 sdparta_2"); + + assert!(parse_attribute("mid:").is_err()); + } + + #[test] + fn test_parse_attribute_msid() { + let check_parse = make_check_parse!(SdpAttributeMsid, SdpAttribute::Msid); + let check_parse_and_serialize = + make_check_parse_and_serialize!(check_parse, SdpAttribute::Msid); + + check_parse_and_serialize("msid:{5a990edd-0568-ac40-8d97-310fc33f3411}"); + check_parse_and_serialize( + "msid:{5a990edd-0568-ac40-8d97-310fc33f3411} {218cfa1c-617d-2249-9997-60929ce4c405}", + ); + + assert!(parse_attribute("msid:").is_err()); + } + + #[test] + fn test_parse_attribute_msid_semantics() { + let check_parse = make_check_parse!(SdpAttributeMsidSemantic, SdpAttribute::MsidSemantic); + let check_parse_and_serialize = + make_check_parse_and_serialize!(check_parse, SdpAttribute::MsidSemantic); + + check_parse_and_serialize("msid-semantic:WMS *"); + check_parse_and_serialize("msid-semantic:WMS foo"); + + assert!(parse_attribute("msid-semantic:").is_err()); + } + + #[test] + fn test_parse_attribute_ptime() { + let check_parse = make_check_parse!(u64, SdpAttribute::Ptime); + let check_parse_and_serialize = + make_check_parse_and_serialize!(check_parse, SdpAttribute::Ptime); + + check_parse_and_serialize("ptime:30"); + assert!(parse_attribute("ptime:").is_err()); + } + + #[test] + fn test_anonymize_remote_candidate() -> Result<(), SdpParserInternalError> { + let mut anon = StatefulSdpAnonymizer::new(); + if let SdpType::Attribute(SdpAttribute::RemoteCandidate(remote)) = + parse_attribute("remote-candidates:0 10.0.0.1 5555")? + { + let masked = remote.masked_clone(&mut anon); + assert_eq!(masked.address, Address::Ip(IpAddr::V4(Ipv4Addr::from(1)))); + assert_eq!(masked.port, 1); + } else { + unreachable!(); + } + Ok(()) + } + + #[test] + fn test_parse_attribute_rid() { + let check_parse = make_check_parse!(SdpAttributeRid, SdpAttribute::Rid); + let check_parse_and_serialize = + make_check_parse_and_serialize!(check_parse, SdpAttribute::Rid); + + check_parse_and_serialize("rid:foo send pt=10"); + check_parse_and_serialize("rid:110 send pt=9,10"); + check_parse_and_serialize("rid:110 send pt=9,10;max-fs=10"); + check_parse_and_serialize("rid:110 send pt=9,10;max-width=10;depends=1,2,3"); + + assert!( + parse_attribute("rid:110 send pt=9, 10;max-fs=10;UNKNOWN=100; depends=1, 2, 3").is_ok() + ); + assert!(parse_attribute("rid:110 send max-fs=10").is_ok()); + assert!(parse_attribute("rid:110 recv max-width=1920;max-height=1080").is_ok()); + + check_parse_and_serialize("rid:110 recv max-mbps=420;max-cpb=3;max-dpb=3"); + check_parse_and_serialize("rid:110 recv scale-down-by=1.35;depends=1,2,3"); + check_parse_and_serialize("rid:110 recv max-width=10;depends=1,2,3"); + check_parse_and_serialize("rid:110 recv max-fs=10;UNKNOWN=100;depends=1,2,3"); + + assert!(parse_attribute("rid:").is_err()); + assert!(parse_attribute("rid:120 send pt=").is_err()); + assert!(parse_attribute("rid:120 send pt=;max-width=10").is_err()); + assert!(parse_attribute("rid:120 send pt=9;max-width=").is_err()); + assert!(parse_attribute("rid:120 send pt=9;max-width=;max-width=10").is_err()); + } + + #[test] + fn test_parse_attribute_rid_and_verify() { + let check_parse = make_check_parse!(SdpAttributeRid, SdpAttribute::Rid); + let check_parse_and_serialize = + make_check_parse_and_serialize!(check_parse, SdpAttribute::Rid); + + check_parse_and_serialize("rid:foo send"); + let mut rid = check_parse("rid:foo send"); + assert_eq!(rid.id, "foo"); + assert_eq!(rid.direction, SdpSingleDirection::Send); + + check_parse_and_serialize("rid:110 send pt=9"); + rid = check_parse("rid:110 send pt=9"); + assert_eq!(rid.id, "110"); + assert_eq!(rid.direction, SdpSingleDirection::Send); + assert_eq!(rid.formats, vec![9]); + + check_parse_and_serialize("rid:110 send pt=9,10;max-fs=10;UNKNOWN=100;depends=1,2,3"); + rid = check_parse("rid:110 send pt=9,10;max-fs=10;UNKNOWN=100;depends=1,2,3"); + assert_eq!(rid.id, "110"); + assert_eq!(rid.direction, SdpSingleDirection::Send); + assert_eq!(rid.formats, vec![9, 10]); + assert_eq!(rid.params.max_fs, 10); + assert_eq!(rid.params.unknown, vec!["UNKNOWN=100"]); + assert_eq!(rid.depends, vec!["1", "2", "3"]); + + check_parse_and_serialize("rid:110 recv max-fps=42;max-fs=10;max-br=3;max-pps=1000"); + rid = check_parse("rid:110 recv max-fps=42;max-fs=10;max-br=3;max-pps=1000"); + assert_eq!(rid.id, "110"); + assert_eq!(rid.direction, SdpSingleDirection::Recv); + assert_eq!(rid.params.max_fps, 42); + assert_eq!(rid.params.max_fs, 10); + assert_eq!(rid.params.max_br, 3); + assert_eq!(rid.params.max_pps, 1000); + } + + #[test] + fn test_parse_attribute_recvonly() { + let check_parse = make_check_parse!(SdpAttribute::Recvonly); + let check_parse_and_serialize = make_check_parse_and_serialize!(check_parse); + + check_parse_and_serialize("recvonly"); + assert!(parse_attribute("recvonly foobar").is_err()); + } + + #[test] + fn test_parse_attribute_remote_candidate() { + let check_parse = + make_check_parse!(SdpAttributeRemoteCandidate, SdpAttribute::RemoteCandidate); + let check_parse_and_serialize = + make_check_parse_and_serialize!(check_parse, SdpAttribute::RemoteCandidate); + + check_parse_and_serialize("remote-candidates:0 10.0.0.1 5555"); + check_parse_and_serialize("remote-candidates:12345 ::1 5555"); + + assert!(parse_attribute("remote-candidates:abc 10.0.0.1 5555").is_err()); + assert!(parse_attribute("remote-candidates:0 10.0.0.1 70000").is_err()); + assert!(parse_attribute("remote-candidates:0 10.0.0.1").is_err()); + assert!(parse_attribute("remote-candidates:0").is_err()); + assert!(parse_attribute("remote-candidates:").is_err()); + } + + #[test] + fn test_parse_attribute_sendonly() { + let check_parse = make_check_parse!(SdpAttribute::Sendonly); + let check_parse_and_serialize = make_check_parse_and_serialize!(check_parse); + + check_parse_and_serialize("sendonly"); + assert!(parse_attribute("sendonly foobar").is_err()); + } + + #[test] + fn test_parse_attribute_sendrecv() { + let check_parse = make_check_parse!(SdpAttribute::Sendrecv); + let check_parse_and_serialize = make_check_parse_and_serialize!(check_parse); + + check_parse_and_serialize("sendrecv"); + assert!(parse_attribute("sendrecv foobar").is_err()); + } + + #[test] + fn test_parse_attribute_setup() { + let check_parse = make_check_parse!(SdpAttributeSetup, SdpAttribute::Setup); + let check_parse_and_serialize = + make_check_parse_and_serialize!(check_parse, SdpAttribute::Setup); + + check_parse_and_serialize("setup:active"); + check_parse_and_serialize("setup:passive"); + check_parse_and_serialize("setup:actpass"); + check_parse_and_serialize("setup:holdconn"); + + assert!(parse_attribute("setup:").is_err()); + assert!(parse_attribute("setup:foobar").is_err()); + } + + #[test] + fn test_parse_attribute_rtcp() { + let check_parse = make_check_parse!(SdpAttributeRtcp, SdpAttribute::Rtcp); + let check_parse_and_serialize = + make_check_parse_and_serialize!(check_parse, SdpAttribute::Rtcp); + + check_parse_and_serialize("rtcp:5000"); + check_parse_and_serialize("rtcp:9 IN IP4 0.0.0.0"); + check_parse_and_serialize("rtcp:9 IN IP6 2001:db8::1"); + + assert!(parse_attribute("rtcp:").is_err()); + assert!(parse_attribute("rtcp:70000").is_err()); + assert!(parse_attribute("rtcp:9 IN").is_err()); + assert!(parse_attribute("rtcp:9 IN IP4").is_err()); + assert!(parse_attribute("rtcp:9 IN IP4 ::1").is_err()); + } + + #[test] + fn test_parse_attribute_rtcp_fb() { + let check_parse = make_check_parse!(SdpAttributeRtcpFb, SdpAttribute::Rtcpfb); + let check_parse_and_serialize = + make_check_parse_and_serialize!(check_parse, SdpAttribute::Rtcpfb); + + check_parse_and_serialize("rtcp-fb:101 ack rpsi"); + check_parse_and_serialize("rtcp-fb:101 ack app"); + check_parse_and_serialize("rtcp-fb:101 ccm"); + check_parse_and_serialize("rtcp-fb:101 ccm fir"); + check_parse_and_serialize("rtcp-fb:101 ccm tmmbr"); + check_parse_and_serialize("rtcp-fb:101 ccm tstr"); + check_parse_and_serialize("rtcp-fb:101 ccm vbcm"); + check_parse_and_serialize("rtcp-fb:101 nack"); + check_parse_and_serialize("rtcp-fb:101 nack sli"); + check_parse_and_serialize("rtcp-fb:101 nack pli"); + check_parse_and_serialize("rtcp-fb:101 nack rpsi"); + check_parse_and_serialize("rtcp-fb:101 nack app"); + check_parse_and_serialize("rtcp-fb:101 trr-int 1"); + check_parse_and_serialize("rtcp-fb:101 goog-remb"); + check_parse_and_serialize("rtcp-fb:101 transport-cc"); + + assert!(parse_attribute("rtcp-fb:101 unknown").is_err()); + assert!(parse_attribute("rtcp-fb:101 ack").is_err()); + assert!(parse_attribute("rtcp-fb:101 ccm unknwon").is_err()); + assert!(parse_attribute("rtcp-fb:101 nack unknown").is_err()); + assert!(parse_attribute("rtcp-fb:101 trr-int").is_err()); + assert!(parse_attribute("rtcp-fb:101 trr-int a").is_err()); + assert!(parse_attribute("rtcp-fb:101 goog-remb unknown").is_err()); + assert!(parse_attribute("rtcp-fb:101 transport-cc unknown").is_err()); + } + + #[test] + fn test_parse_attribute_rtcp_mux() { + let check_parse = make_check_parse!(SdpAttribute::RtcpMux); + let check_parse_and_serialize = make_check_parse_and_serialize!(check_parse); + + check_parse_and_serialize("rtcp-mux"); + assert!(parse_attribute("rtcp-mux foobar").is_err()); + } + + #[test] + fn test_parse_attribute_rtcp_rsize() { + let check_parse = make_check_parse!(SdpAttribute::RtcpRsize); + let check_parse_and_serialize = make_check_parse_and_serialize!(check_parse); + + check_parse_and_serialize("rtcp-rsize"); + assert!(parse_attribute("rtcp-rsize foobar").is_err()); + } + + #[test] + fn test_parse_attribute_rtpmap() { + let check_parse = make_check_parse!(SdpAttributeRtpmap, SdpAttribute::Rtpmap); + let check_parse_and_serialize = + make_check_parse_and_serialize!(check_parse, SdpAttribute::Rtpmap); + + check_parse_and_serialize("rtpmap:109 opus/48000"); + check_parse_and_serialize("rtpmap:109 opus/48000/2"); + + assert!(parse_attribute("rtpmap: ").is_err()); + assert!(parse_attribute("rtpmap:109 ").is_err()); + assert!(parse_attribute("rtpmap:109 opus").is_err()); + assert!(parse_attribute("rtpmap:128 opus/48000").is_err()); + } + + #[test] + fn test_parse_attribute_sctpmap() { + let check_parse = make_check_parse!(SdpAttributeSctpmap, SdpAttribute::Sctpmap); + let check_parse_and_serialize = + make_check_parse_and_serialize!(check_parse, SdpAttribute::Sctpmap); + + check_parse_and_serialize("sctpmap:5000 webrtc-datachannel 256"); + + assert!(parse_attribute("sctpmap:70000 webrtc-datachannel").is_err()); + assert!(parse_attribute("sctpmap:70000 webrtc-datachannel 256").is_err()); + assert!(parse_attribute("sctpmap:5000 unsupported 256").is_err()); + assert!(parse_attribute("sctpmap:5000 webrtc-datachannel 2a").is_err()); + } + + #[test] + fn test_parse_attribute_sctp_port() { + let check_parse = make_check_parse!(u64, SdpAttribute::SctpPort); + let check_parse_and_serialize = + make_check_parse_and_serialize!(check_parse, SdpAttribute::SctpPort); + + check_parse_and_serialize("sctp-port:5000"); + + assert!(parse_attribute("sctp-port:").is_err()); + assert!(parse_attribute("sctp-port:70000").is_err()); + } + + #[test] + fn test_parse_attribute_max_message_size() { + let check_parse = make_check_parse!(u64, SdpAttribute::MaxMessageSize); + let check_parse_and_serialize = + make_check_parse_and_serialize!(check_parse, SdpAttribute::MaxMessageSize); + + check_parse_and_serialize("max-message-size:1"); + check_parse_and_serialize("max-message-size:100000"); + check_parse_and_serialize("max-message-size:4294967297"); + check_parse_and_serialize("max-message-size:0"); + + assert!(parse_attribute("max-message-size:").is_err()); + assert!(parse_attribute("max-message-size:abc").is_err()); + } + + #[test] + fn test_parse_attribute_simulcast() { + let check_parse = make_check_parse!(SdpAttributeSimulcast, SdpAttribute::Simulcast); + let check_parse_and_serialize = + make_check_parse_and_serialize!(check_parse, SdpAttribute::Simulcast); + + check_parse_and_serialize("simulcast:send 1"); + check_parse_and_serialize("simulcast:recv test"); + check_parse_and_serialize("simulcast:recv ~test"); + check_parse_and_serialize("simulcast:recv test;foo"); + check_parse_and_serialize("simulcast:recv foo,bar"); + check_parse_and_serialize("simulcast:recv foo,bar;test"); + check_parse_and_serialize("simulcast:send 1;4,5 recv 6;7"); + check_parse_and_serialize("simulcast:send 1,2,3;~4,~5 recv 6;~7,~8"); + // old draft 03 notation used by Firefox 55 + assert!(parse_attribute("simulcast: send rid=foo;bar").is_ok()); + + assert!(parse_attribute("simulcast:").is_err()); + assert!(parse_attribute("simulcast:send").is_err()); + assert!(parse_attribute("simulcast:foobar 1").is_err()); + assert!(parse_attribute("simulcast:send 1 foobar 2").is_err()); + // old draft 03 notation used by Firefox 55 + assert!(parse_attribute("simulcast: send foo=8;10").is_err()); + } + + #[test] + fn test_parse_attribute_ssrc() { + let check_parse = make_check_parse!(SdpAttributeSsrc, SdpAttribute::Ssrc); + let check_parse_and_serialize = + make_check_parse_and_serialize!(check_parse, SdpAttribute::Ssrc); + + check_parse_and_serialize("ssrc:2655508255"); + check_parse_and_serialize("ssrc:2655508255 foo"); + check_parse_and_serialize("ssrc:2655508255 cname:{735484ea-4f6c-f74a-bd66-7425f8476c2e}"); + check_parse_and_serialize("ssrc:2082260239 msid:1d0cdb4e-5934-4f0f-9f88-40392cb60d31 315b086a-5cb6-4221-89de-caf0b038c79d"); + + assert!(parse_attribute("ssrc:").is_err()); + assert!(parse_attribute("ssrc:foo").is_err()); + } + + #[test] + fn test_anonymize_attribute_ssrc() -> Result<(), SdpParserInternalError> { + let mut anon = StatefulSdpAnonymizer::new(); + let parsed = + parse_attribute("ssrc:2655508255 cname:{735484ea-4f6c-f74a-bd66-7425f8476c2e}")?; + let (ssrc1, masked) = if let SdpType::Attribute(a) = parsed { + let masked = a.masked_clone(&mut anon); + match (a, masked) { + (SdpAttribute::Ssrc(ssrc), SdpAttribute::Ssrc(masked)) => (ssrc, masked), + (_, _) => unreachable!(), + } + } else { + unreachable!() + }; + assert_eq!(ssrc1.id, masked.id); + assert_eq!(ssrc1.attribute, masked.attribute); + assert_eq!("cname-00000001", masked.value.unwrap()); + + let ssrc2 = parse_attribute("ssrc:2082260239 msid:1d0cdb4e-5934-4f0f-9f88-40392cb60d31 315b086a-5cb6-4221-89de-caf0b038c79d")?; + if let SdpType::Attribute(SdpAttribute::Ssrc(ssrc2)) = ssrc2 { + let masked = ssrc2.masked_clone(&mut anon); + assert_eq!(ssrc2.id, masked.id); + assert_eq!(ssrc2.attribute, masked.attribute); + assert_eq!(ssrc2.value, masked.value); + } else { + unreachable!() + } + Ok(()) + } + + #[test] + fn test_parse_attribute_ssrc_group() { + let parsed = parse_attribute("ssrc-group:FID 3156517279 2673335628"); + match parsed { + Ok(SdpType::Attribute(attr)) => { + assert_eq!(attr.to_string(), "ssrc-group:FID 3156517279 2673335628"); + let (semantic, ssrcs) = match attr { + SdpAttribute::SsrcGroup(semantic, ssrcs) => { + let stringified_ssrcs: Vec<String> = + ssrcs.iter().map(|ssrc| ssrc.to_string()).collect(); + (semantic.to_string(), stringified_ssrcs) + } + _ => unreachable!(), + }; + assert_eq!(semantic, "FID"); + assert_eq!(ssrcs.len(), 2); + assert_eq!(ssrcs[0], "3156517279"); + assert_eq!(ssrcs[1], "2673335628"); + } + Err(e) => panic!(e), + _ => unreachable!(), + } + + assert!(parse_attribute("ssrc-group:").is_err()); + assert!(parse_attribute("ssrc-group:BLAH").is_err()); + assert!(parse_attribute("ssrc-group:FID").is_err()); + } + + #[test] + fn test_parse_unknown_attribute() { + assert!(parse_attribute("unknown").is_err()) + } +} diff --git a/third_party/rust/webrtc-sdp/src/error.rs b/third_party/rust/webrtc-sdp/src/error.rs new file mode 100644 index 0000000000..aadbb61fd1 --- /dev/null +++ b/third_party/rust/webrtc-sdp/src/error.rs @@ -0,0 +1,336 @@ +/* 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/. */ + +#[cfg(feature = "serialize")] +use serde::ser::{Serialize, SerializeStruct, Serializer}; +use std::error; +use std::error::Error; +use std::fmt; +extern crate url; +use address::AddressType; +use std::num::ParseFloatError; +use std::num::ParseIntError; + +#[derive(Debug, Clone)] +pub enum SdpParserInternalError { + UnknownAddressType(String), + AddressTypeMismatch { + found: AddressType, + expected: AddressType, + }, + Generic(String), + Unsupported(String), + Integer(ParseIntError), + Float(ParseFloatError), + Domain(url::ParseError), + IpAddress(std::net::AddrParseError), +} + +const INTERNAL_ERROR_MESSAGE_UNKNOWN_ADDRESS_TYPE: &str = "Unknown address type"; +const INTERNAL_ERROR_MESSAGE_ADDRESS_TYPE_MISMATCH: &str = + "Address is of a different type(1) than declared(2)"; + +impl fmt::Display for SdpParserInternalError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + SdpParserInternalError::UnknownAddressType(ref unknown) => write!( + f, + "{}: {}", + INTERNAL_ERROR_MESSAGE_UNKNOWN_ADDRESS_TYPE, unknown + ), + SdpParserInternalError::AddressTypeMismatch { found, expected } => write!( + f, + "{}: {}, {}", + INTERNAL_ERROR_MESSAGE_ADDRESS_TYPE_MISMATCH, found, expected + ), + SdpParserInternalError::Generic(ref message) => write!(f, "Parsing error: {}", message), + SdpParserInternalError::Unsupported(ref message) => { + write!(f, "Unsupported parsing error: {}", message) + } + SdpParserInternalError::Integer(ref error) => { + write!(f, "Integer parsing error: {}", error) + } + SdpParserInternalError::Float(ref error) => write!(f, "Float parsing error: {}", error), + SdpParserInternalError::Domain(ref error) => { + write!(f, "Domain name parsing error: {}", error) + } + SdpParserInternalError::IpAddress(ref error) => { + write!(f, "IP address parsing error: {}", error) + } + } + } +} + +impl Error for SdpParserInternalError { + fn source(&self) -> Option<&(dyn error::Error + 'static)> { + match *self { + SdpParserInternalError::Integer(ref error) => Some(error), + SdpParserInternalError::Float(ref error) => Some(error), + SdpParserInternalError::Domain(ref error) => Some(error), + SdpParserInternalError::IpAddress(ref error) => Some(error), + // Can't tell much more about our internal errors + _ => None, + } + } +} + +#[derive(Debug, Clone)] +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, + }, +} + +#[cfg(feature = "serialize")] +impl Serialize for SdpParserError { + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> + where + S: Serializer, + { + let mut state = serializer.serialize_struct( + "error", + match *self { + SdpParserError::Sequence { .. } => 3, + _ => 4, + }, + )?; + match *self { + SdpParserError::Line { + ref error, + ref line, + .. + } => { + state.serialize_field("type", "Line")?; + state.serialize_field("message", &format!("{}", error))?; + state.serialize_field("line", &line)? + } + SdpParserError::Unsupported { + ref error, + ref line, + .. + } => { + state.serialize_field("type", "Unsupported")?; + state.serialize_field("message", &format!("{}", error))?; + state.serialize_field("line", &line)? + } + SdpParserError::Sequence { ref message, .. } => { + state.serialize_field("type", "Sequence")?; + state.serialize_field("message", &message)?; + } + }; + state.serialize_field( + "line_number", + &match *self { + SdpParserError::Line { line_number, .. } => line_number, + SdpParserError::Unsupported { line_number, .. } => line_number, + SdpParserError::Sequence { line_number, .. } => line_number, + }, + )?; + state.end() + } +} + +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, line_number, line + ), + SdpParserError::Unsupported { + ref error, + ref line, + ref line_number, + } => write!( + f, + "Unsupported: {} in line({}): {}", + error, line_number, line + ), + SdpParserError::Sequence { + ref message, + ref line_number, + } => write!(f, "Sequence error in line({}): {}", line_number, message), + } + } +} + +impl Error for SdpParserError { + fn source(&self) -> Option<&(dyn error::Error + 'static)> { + 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<url::ParseError> for SdpParserInternalError { + fn from(err: url::ParseError) -> SdpParserInternalError { + SdpParserInternalError::Domain(err) + } +} + +impl From<std::net::AddrParseError> for SdpParserInternalError { + fn from(err: std::net::AddrParseError) -> SdpParserInternalError { + SdpParserInternalError::IpAddress(err) + } +} + +impl From<ParseFloatError> for SdpParserInternalError { + fn from(err: ParseFloatError) -> SdpParserInternalError { + SdpParserInternalError::Float(err) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use address::Address; + use std::str::FromStr; + #[test] + fn test_sdp_parser_internal_error_unknown_address_type() { + let error = SdpParserInternalError::UnknownAddressType("foo".to_string()); + assert_eq!( + format!("{}", error), + format!("{}: {}", INTERNAL_ERROR_MESSAGE_UNKNOWN_ADDRESS_TYPE, "foo") + ); + assert!(error.source().is_none()); + } + #[test] + fn test_sdp_parser_internal_error_address_type_mismatch() { + let error = SdpParserInternalError::AddressTypeMismatch { + found: AddressType::IpV4, + expected: AddressType::IpV6, + }; + assert_eq!( + format!("{}", error), + format!( + "{}: {}, {}", + INTERNAL_ERROR_MESSAGE_ADDRESS_TYPE_MISMATCH, + AddressType::IpV4, + AddressType::IpV6 + ) + ); + assert!(error.source().is_none()); + } + + #[test] + fn test_sdp_parser_internal_error_generic() { + let generic = SdpParserInternalError::Generic("generic message".to_string()); + assert_eq!(format!("{}", generic), "Parsing error: generic message"); + assert!(generic.source().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!(unsupported.source().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!(!int_err.source().is_none()); + } + + #[test] + fn test_sdp_parser_internal_error_float() { + let v = "12.2a"; + let float = v.parse::<f32>(); + assert!(float.is_err()); + let int_err = SdpParserInternalError::Float(float.err().unwrap()); + assert_eq!( + format!("{}", int_err), + "Float parsing error: invalid float literal" + ); + assert!(!int_err.source().is_none()); + } + + #[test] + fn test_sdp_parser_internal_error_address() { + let v = "127.0.0.500"; + let addr_err = Address::from_str(v).err().unwrap(); + assert_eq!( + format!("{}", addr_err), + "Domain name parsing error: invalid IPv4 address" + ); + assert!(!addr_err.source().is_none()); + } + + #[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: Parsing error: test message in line(13): test line" + ); + assert!(line1.source().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: Parsing error: unsupported value in line(21): unsupported line" + ); + assert!(unsupported1.source().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!(sequence1.source().is_none()); + } +} diff --git a/third_party/rust/webrtc-sdp/src/lib.rs b/third_party/rust/webrtc-sdp/src/lib.rs new file mode 100644 index 0000000000..046359f086 --- /dev/null +++ b/third_party/rust/webrtc-sdp/src/lib.rs @@ -0,0 +1,1681 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#![warn(clippy::all)] +#![forbid(unsafe_code)] + +#[macro_use] +extern crate log; +#[cfg(feature = "serialize")] +#[macro_use] +extern crate serde_derive; +#[cfg(feature = "serialize")] +extern crate serde; +use std::convert::TryFrom; +use std::fmt; + +#[macro_use] +pub mod attribute_type; +pub mod address; +pub mod anonymizer; +pub mod error; +pub mod media_type; +pub mod network; + +use address::{AddressTyped, ExplicitlyTypedAddress}; +use anonymizer::{AnonymizingClone, StatefulSdpAnonymizer}; +use attribute_type::{ + parse_attribute, SdpAttribute, SdpAttributeRid, SdpAttributeSimulcastVersion, SdpAttributeType, + SdpSingleDirection, +}; +use error::{SdpParserError, SdpParserInternalError}; +use media_type::{ + parse_media, parse_media_vector, SdpFormatList, SdpMedia, SdpMediaLine, SdpMediaValue, + SdpProtocolValue, +}; +use network::{parse_address_type, parse_network_type}; + +/* + * RFC4566 + * bandwidth-fields = *(%x62 "=" bwtype ":" bandwidth CRLF) + */ +#[derive(Clone)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "enhanced_debug", derive(Debug))] +pub enum SdpBandwidth { + As(u32), + Ct(u32), + Tias(u32), + Unknown(String, u32), +} + +impl fmt::Display for SdpBandwidth { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let (tp_string, value) = match *self { + SdpBandwidth::As(ref x) => ("AS", x), + SdpBandwidth::Ct(ref x) => ("CT", x), + SdpBandwidth::Tias(ref x) => ("TIAS", x), + SdpBandwidth::Unknown(ref tp, ref x) => (&tp[..], x), + }; + write!(f, "{tp}:{val}", tp = tp_string, val = value) + } +} + +/* + * RFC4566 + * connection-field = [%x63 "=" nettype SP addrtype SP + * connection-address CRLF] + */ +#[derive(Clone)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "enhanced_debug", derive(Debug))] +pub struct SdpConnection { + pub address: ExplicitlyTypedAddress, + pub ttl: Option<u8>, + pub amount: Option<u32>, +} + +impl fmt::Display for SdpConnection { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.address.fmt(f)?; + write_option_string!(f, "/{}", self.ttl)?; + write_option_string!(f, "/{}", self.amount) + } +} + +impl AnonymizingClone for SdpConnection { + fn masked_clone(&self, anon: &mut StatefulSdpAnonymizer) -> Self { + let mut masked = self.clone(); + masked.address = anon.mask_typed_address(&self.address); + masked + } +} + +/* + * RFC4566 + * origin-field = %x6f "=" username SP sess-id SP sess-version SP + * nettype SP addrtype SP unicast-address CRLF + */ +#[derive(Clone)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "enhanced_debug", derive(Debug))] +pub struct SdpOrigin { + pub username: String, + pub session_id: u64, + pub session_version: u64, + pub unicast_addr: ExplicitlyTypedAddress, +} + +impl fmt::Display for SdpOrigin { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "{username} {sess_id} {sess_vers} {unicast_addr}", + username = self.username, + sess_id = self.session_id, + sess_vers = self.session_version, + unicast_addr = self.unicast_addr + ) + } +} + +impl AnonymizingClone for SdpOrigin { + fn masked_clone(&self, anon: &mut StatefulSdpAnonymizer) -> Self { + let mut masked = self.clone(); + masked.username = anon.mask_origin_user(&self.username); + masked.unicast_addr = anon.mask_typed_address(&masked.unicast_addr); + masked + } +} + +/* + * RFC4566 + * time-fields = 1*( %x74 "=" start-time SP stop-time + * *(CRLF repeat-fields) CRLF) + * [zone-adjustments CRLF] + */ +#[derive(Clone)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "enhanced_debug", derive(Debug))] +pub struct SdpTiming { + pub start: u64, + pub stop: u64, +} + +impl fmt::Display for SdpTiming { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{start} {stop}", start = self.start, stop = self.stop) + } +} + +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "enhanced_debug", derive(Debug))] +pub enum SdpType { + // Note: Email, Information, Key, Phone, Repeat, Uri and Zone are left out + // on purposes as we don't want to support them. + Attribute(SdpAttribute), + Bandwidth(SdpBandwidth), + Connection(SdpConnection), + Media(SdpMediaLine), + Origin(SdpOrigin), + Session(String), + Timing(SdpTiming), + Version(u64), +} + +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "enhanced_debug", derive(Debug))] +pub struct SdpLine { + pub line_number: usize, + pub sdp_type: SdpType, + pub text: String, +} + +/* + * RFC4566 + * ; SDP Syntax + * session-description = proto-version + * origin-field + * session-name-field + * information-field + * uri-field + * email-fields + * phone-fields + * connection-field + * bandwidth-fields + * time-fields + * key-field + * attribute-fields + * media-descriptions + */ +#[derive(Clone)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "enhanced_debug", derive(Debug))] +pub struct SdpSession { + pub version: u64, + pub origin: SdpOrigin, + pub session: Option<String>, + pub connection: Option<SdpConnection>, + pub bandwidth: Vec<SdpBandwidth>, + pub timing: Option<SdpTiming>, + pub attribute: Vec<SdpAttribute>, + pub media: Vec<SdpMedia>, + pub warnings: Vec<SdpParserError>, // unsupported values: + // information: Option<String>, + // uri: Option<String>, + // email: Option<String>, + // phone: Option<String>, + // repeat: Option<String>, + // zone: Option<String>, + // key: Option<String> +} + +impl fmt::Display for SdpSession { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "v={version}\r\n\ + o={origin}\r\n\ + s={session}\r\n\ + {timing}\ + {bandwidth}\ + {connection}\ + {session_attributes}\ + {media_sections}", + version = self.version, + origin = self.origin, + session = self.get_session_text(), + timing = option_to_string!("t={}\r\n", self.timing), + bandwidth = maybe_vector_to_string!("b={}\r\n", self.bandwidth, "\r\nb="), + connection = option_to_string!("c={}\r\n", self.connection), + session_attributes = maybe_vector_to_string!("a={}\r\n", self.attribute, "\r\na="), + media_sections = maybe_vector_to_string!("{}", self.media, "\r\n") + ) + } +} + +impl SdpSession { + pub fn new(version: u64, origin: SdpOrigin, session: String) -> SdpSession { + let session = match session.trim() { + s if !s.is_empty() => Some(s.to_owned()), + _ => None, + }; + SdpSession { + version, + origin, + session, + connection: None, + bandwidth: Vec::new(), + timing: None, + attribute: Vec::new(), + media: Vec::new(), + warnings: Vec::new(), + } + } + + pub fn get_version(&self) -> u64 { + self.version + } + + pub fn get_origin(&self) -> &SdpOrigin { + &self.origin + } + + pub fn get_session(&self) -> &Option<String> { + &self.session + } + + pub fn get_session_text(&self) -> &str { + if let Some(text) = &self.session { + text.as_str() + } else { + " " + } + } + pub fn get_connection(&self) -> &Option<SdpConnection> { + &self.connection + } + + pub fn set_connection(&mut self, c: SdpConnection) { + self.connection = Some(c) + } + + pub fn add_bandwidth(&mut self, b: SdpBandwidth) { + self.bandwidth.push(b) + } + + pub fn set_timing(&mut self, t: SdpTiming) { + self.timing = Some(t) + } + + pub fn add_attribute(&mut self, a: SdpAttribute) -> Result<(), SdpParserInternalError> { + if !a.allowed_at_session_level() { + return Err(SdpParserInternalError::Generic(format!( + "{} not allowed at session level", + a + ))); + }; + self.attribute.push(a); + Ok(()) + } + + pub fn extend_media(&mut self, v: Vec<SdpMedia>) { + self.media.extend(v) + } + + pub fn parse_session_vector(&mut self, lines: &mut Vec<SdpLine>) -> Result<(), SdpParserError> { + while !lines.is_empty() { + let line = lines.remove(0); + match line.sdp_type { + SdpType::Attribute(a) => { + let _line_number = line.line_number; + self.add_attribute(a).map_err(|e: SdpParserInternalError| { + SdpParserError::Sequence { + message: format!("{}", e), + line_number: _line_number, + } + })? + } + SdpType::Bandwidth(b) => self.add_bandwidth(b), + SdpType::Timing(t) => self.set_timing(t), + SdpType::Connection(c) => self.set_connection(c), + + SdpType::Origin(_) | SdpType::Session(_) | SdpType::Version(_) => { + return Err(SdpParserError::Sequence { + message: "version, origin or session at wrong level".to_string(), + line_number: line.line_number, + }); + } + SdpType::Media(_) => { + return Err(SdpParserError::Sequence { + message: "media line not allowed in session parser".to_string(), + line_number: line.line_number, + }); + } + } + } + Ok(()) + } + + pub fn get_attribute(&self, t: SdpAttributeType) -> Option<&SdpAttribute> { + self.attribute + .iter() + .find(|a| SdpAttributeType::from(*a) == t) + } + + pub fn add_media( + &mut self, + media_type: SdpMediaValue, + direction: SdpAttribute, + port: u32, + protocol: SdpProtocolValue, + addr: ExplicitlyTypedAddress, + ) -> Result<(), SdpParserInternalError> { + let mut media = SdpMedia::new(SdpMediaLine { + media: media_type, + port, + port_count: 1, + proto: protocol, + formats: SdpFormatList::Integers(Vec::new()), + }); + + media.add_attribute(direction)?; + + media.set_connection(SdpConnection { + address: addr, + ttl: None, + amount: None, + }); + + self.media.push(media); + + Ok(()) + } +} + +impl AnonymizingClone for SdpSession { + fn masked_clone(&self, anon: &mut StatefulSdpAnonymizer) -> Self { + let mut masked: SdpSession = SdpSession { + version: self.version, + session: self.session.clone(), + origin: self.origin.masked_clone(anon), + connection: self.connection.clone(), + timing: self.timing.clone(), + bandwidth: self.bandwidth.clone(), + attribute: Vec::new(), + media: Vec::new(), + warnings: Vec::new(), + }; + masked.origin = self.origin.masked_clone(anon); + masked.connection = masked.connection.map(|con| con.masked_clone(anon)); + for i in &self.attribute { + masked.attribute.push(i.masked_clone(anon)); + } + masked + } +} + +fn parse_session(value: &str) -> Result<SdpType, SdpParserInternalError> { + trace!("session: {}", value); + Ok(SdpType::Session(String::from(value))) +} + +fn parse_version(value: &str) -> Result<SdpType, SdpParserInternalError> { + let ver = value.parse::<u64>()?; + if ver != 0 { + return Err(SdpParserInternalError::Generic(format!( + "version type contains unsupported value {}", + ver + ))); + }; + trace!("version: {}", ver); + Ok(SdpType::Version(ver)) +} + +fn parse_origin(value: &str) -> Result<SdpType, SdpParserInternalError> { + let mut tokens = value.split_whitespace(); + let username = match tokens.next() { + None => { + return Err(SdpParserInternalError::Generic( + "Origin type is missing username token".to_string(), + )); + } + Some(x) => x, + }; + let session_id = match tokens.next() { + None => { + return Err(SdpParserInternalError::Generic( + "Origin type is missing session ID token".to_string(), + )); + } + Some(x) => x.parse::<u64>()?, + }; + let session_version = match tokens.next() { + None => { + return Err(SdpParserInternalError::Generic( + "Origin type is missing session version token".to_string(), + )); + } + Some(x) => x.parse::<u64>()?, + }; + match tokens.next() { + None => { + return Err(SdpParserInternalError::Generic( + "Origin type is missing network type token".to_string(), + )); + } + Some(x) => parse_network_type(x)?, + }; + let addrtype = match tokens.next() { + None => { + return Err(SdpParserInternalError::Generic( + "Origin type is missing address type token".to_string(), + )); + } + Some(x) => parse_address_type(x)?, + }; + let unicast_addr = match tokens.next() { + None => { + return Err(SdpParserInternalError::Generic( + "Origin type is missing IP address token".to_string(), + )); + } + Some(x) => ExplicitlyTypedAddress::try_from((addrtype, x))?, + }; + if addrtype != unicast_addr.address_type() { + return Err(SdpParserInternalError::Generic( + "Origin addrtype does not match address.".to_string(), + )); + } + let o = SdpOrigin { + username: String::from(username), + session_id, + session_version, + unicast_addr, + }; + trace!("origin: {}", o); + Ok(SdpType::Origin(o)) +} + +fn parse_connection(value: &str) -> Result<SdpType, SdpParserInternalError> { + let cv: Vec<&str> = value.split_whitespace().collect(); + if cv.len() != 3 { + return Err(SdpParserInternalError::Generic( + "connection attribute must have three tokens".to_string(), + )); + } + parse_network_type(cv[0])?; + let addrtype = parse_address_type(cv[1])?; + let mut ttl = None; + let mut amount = None; + let mut addr_token = cv[2]; + if addr_token.find('/') != 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 address = ExplicitlyTypedAddress::try_from((addrtype, addr_token))?; + let c = SdpConnection { + address, + ttl, + amount, + }; + trace!("connection: {}", c); + Ok(SdpType::Connection(c)) +} + +fn parse_bandwidth(value: &str) -> Result<SdpType, SdpParserInternalError> { + let bv: Vec<&str> = value.split(':').collect(); + if bv.len() != 2 { + return Err(SdpParserInternalError::Generic( + "bandwidth attribute must have two tokens".to_string(), + )); + } + let bandwidth = bv[1].parse::<u32>()?; + let bw = match bv[0].to_uppercase().as_ref() { + "AS" => SdpBandwidth::As(bandwidth), + "CT" => SdpBandwidth::Ct(bandwidth), + "TIAS" => SdpBandwidth::Tias(bandwidth), + _ => SdpBandwidth::Unknown(String::from(bv[0]), bandwidth), + }; + trace!("bandwidth: {}", bw); + Ok(SdpType::Bandwidth(bw)) +} + +fn parse_timing(value: &str) -> Result<SdpType, SdpParserInternalError> { + let tv: Vec<&str> = value.split_whitespace().collect(); + if tv.len() != 2 { + return Err(SdpParserInternalError::Generic( + "timing attribute must have two tokens".to_string(), + )); + } + let start = tv[0].parse::<u64>()?; + let stop = tv[1].parse::<u64>()?; + let t = SdpTiming { start, stop }; + trace!("timing: {}", t); + Ok(SdpType::Timing(t)) +} + +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, + }); + } + let mut splitted_line = line.splitn(2, '='); + let line_type = match splitted_line.next() { + None => { + return Err(SdpParserError::Line { + error: SdpParserInternalError::Generic("missing type".to_string()), + line: line.to_string(), + line_number, + }); + } + Some(t) => { + let trimmed = t.trim(); + if trimmed.len() > 1 { + return Err(SdpParserError::Line { + error: SdpParserInternalError::Generic("type too long".to_string()), + line: line.to_string(), + line_number, + }); + } + if trimmed.is_empty() { + return Err(SdpParserError::Line { + error: SdpParserInternalError::Generic("type is empty".to_string()), + line: line.to_string(), + line_number, + }); + } + trimmed.to_lowercase() + } + }; + let (line_value, untrimmed_line_value) = match splitted_line.next() { + None => { + return Err(SdpParserError::Line { + error: SdpParserInternalError::Generic("missing value".to_string()), + line: line.to_string(), + line_number, + }); + } + Some(v) => { + let trimmed = v.trim(); + // For compatibility with sites that don't adhere to "s=-" for no session ID + if trimmed.is_empty() && line_type.as_str() != "s" { + return Err(SdpParserError::Line { + error: SdpParserInternalError::Generic("value is empty".to_string()), + line: line.to_string(), + line_number, + }); + } + (trimmed, v) + } + }; + match line_type.as_ref() { + "a" => parse_attribute(line_value), + "b" => parse_bandwidth(line_value), + "c" => parse_connection(line_value), + "e" => Err(SdpParserInternalError::Generic(format!( + "unsupported type email: {}", + line_value + ))), + "i" => Err(SdpParserInternalError::Generic(format!( + "unsupported type information: {}", + line_value + ))), + "k" => Err(SdpParserInternalError::Generic(format!( + "unsupported insecure key exchange: {}", + line_value + ))), + "m" => parse_media(line_value), + "o" => parse_origin(line_value), + "p" => Err(SdpParserInternalError::Generic(format!( + "unsupported type phone: {}", + line_value + ))), + "r" => Err(SdpParserInternalError::Generic(format!( + "unsupported type repeat: {}", + line_value + ))), + "s" => parse_session(untrimmed_line_value), + "t" => parse_timing(line_value), + "u" => Err(SdpParserInternalError::Generic(format!( + "unsupported type uri: {}", + line_value + ))), + "v" => parse_version(line_value), + "z" => Err(SdpParserInternalError::Generic(format!( + "unsupported type zone: {}", + line_value + ))), + _ => Err(SdpParserInternalError::Generic( + "unknown sdp type".to_string(), + )), + } + .map(|sdp_type| SdpLine { + line_number, + sdp_type, + text: line.to_owned(), + }) + .map_err(|e| match e { + SdpParserInternalError::UnknownAddressType(..) + | SdpParserInternalError::AddressTypeMismatch { .. } + | SdpParserInternalError::Generic(..) + | SdpParserInternalError::Integer(..) + | SdpParserInternalError::Float(..) + | SdpParserInternalError::Domain(..) + | SdpParserInternalError::IpAddress(..) => SdpParserError::Line { + error: e, + line: line.to_string(), + line_number, + }, + SdpParserInternalError::Unsupported(..) => SdpParserError::Unsupported { + error: e, + line: line.to_string(), + line_number, + }, + }) +} + +fn sanity_check_sdp_session(session: &SdpSession) -> Result<(), SdpParserError> { + let make_seq_error = |x: &str| SdpParserError::Sequence { + message: x.to_string(), + line_number: 0, + }; + + if session.timing.is_none() { + return Err(make_seq_error("Missing timing type at session level")); + } + // Checks that all media have connections if there is no top level + // This explicitly allows for zero connection lines if there are no media + // sections for interoperability reasons. + let media_cons = &session.media.iter().all(|m| m.get_connection().is_some()); + if !media_cons && session.get_connection().is_none() { + return Err(make_seq_error( + "Without connection type at session level all media sections must have connection types", + )); + } + + // Check that extmaps are not defined on session and media level + if session.get_attribute(SdpAttributeType::Extmap).is_some() { + for msection in &session.media { + if msection.get_attribute(SdpAttributeType::Extmap).is_some() { + return Err(make_seq_error( + "Extmap can't be define at session and media level", + )); + } + } + } + + for msection in &session.media { + if msection.get_attribute(SdpAttributeType::Sendonly).is_some() { + if let Some(&SdpAttribute::Simulcast(ref x)) = + msection.get_attribute(SdpAttributeType::Simulcast) + { + if !x.receive.is_empty() { + return Err(make_seq_error( + "Simulcast can't define receive parameters for sendonly", + )); + } + } + } + if msection.get_attribute(SdpAttributeType::Recvonly).is_some() { + if let Some(&SdpAttribute::Simulcast(ref x)) = + msection.get_attribute(SdpAttributeType::Simulcast) + { + if !x.send.is_empty() { + return Err(make_seq_error( + "Simulcast can't define send parameters for recvonly", + )); + } + } + } + + let rids: Vec<&SdpAttributeRid> = msection + .get_attributes() + .iter() + .filter_map(|attr| match *attr { + SdpAttribute::Rid(ref rid) => Some(rid), + _ => None, + }) + .collect(); + let recv_rids: Vec<&str> = rids + .iter() + .filter_map(|rid| match rid.direction { + SdpSingleDirection::Recv => Some(rid.id.as_str()), + _ => None, + }) + .collect(); + let send_rids: Vec<&str> = rids + .iter() + .filter_map(|rid| match rid.direction { + SdpSingleDirection::Send => Some(rid.id.as_str()), + _ => None, + }) + .collect(); + + for rid_format in rids.iter().flat_map(|rid| &rid.formats) { + match *msection.get_formats() { + SdpFormatList::Integers(ref int_fmt) => { + if !int_fmt.contains(&(u32::from(*rid_format))) { + return Err(make_seq_error( + "Rid pts must be declared in the media section", + )); + } + } + SdpFormatList::Strings(ref str_fmt) => { + if !str_fmt.contains(&rid_format.to_string()) { + return Err(make_seq_error( + "Rid pts must be declared in the media section", + )); + } + } + } + } + + if let Some(&SdpAttribute::Simulcast(ref simulcast)) = + msection.get_attribute(SdpAttributeType::Simulcast) + { + let check_defined_rids = + |simulcast_version_list: &Vec<SdpAttributeSimulcastVersion>, + rid_ids: &[&str]| + -> Result<(), SdpParserError> { + for simulcast_rid in simulcast_version_list.iter().flat_map(|x| &x.ids) { + if !rid_ids.contains(&simulcast_rid.id.as_str()) { + return Err(make_seq_error( + "Simulcast RIDs must be defined in any rid attribute", + )); + } + } + Ok(()) + }; + + check_defined_rids(&simulcast.receive, &recv_rids)?; + check_defined_rids(&simulcast.send, &send_rids)?; + } + } + + Ok(()) +} + +fn parse_sdp_vector(lines: &mut Vec<SdpLine>) -> Result<SdpSession, SdpParserError> { + if lines.len() < 4 { + return Err(SdpParserError::Sequence { + message: "SDP neeeds at least 4 lines".to_string(), + line_number: 0, + }); + } + + let version = match lines.remove(0).sdp_type { + SdpType::Version(v) => v, + _ => { + return Err(SdpParserError::Sequence { + message: "first line needs to be version number".to_string(), + line_number: 0, + }); + } + }; + let origin = match lines.remove(0).sdp_type { + SdpType::Origin(v) => v, + _ => { + return Err(SdpParserError::Sequence { + message: "second line needs to be origin".to_string(), + line_number: 1, + }); + } + }; + let session = match lines.remove(0).sdp_type { + SdpType::Session(v) => v, + _ => { + return Err(SdpParserError::Sequence { + message: "third line needs to be session".to_string(), + line_number: 2, + }); + } + }; + let mut sdp_session = SdpSession::new(version, origin, session); + + let _media_pos = lines + .iter() + .position(|ref l| matches!(l.sdp_type, SdpType::Media(_))); + + match _media_pos { + Some(p) => { + let mut media: Vec<_> = lines.drain(p..).collect(); + sdp_session.parse_session_vector(lines)?; + sdp_session.extend_media(parse_media_vector(&mut media)?); + } + None => sdp_session.parse_session_vector(lines)?, + }; + + sanity_check_sdp_session(&sdp_session)?; + Ok(sdp_session) +} + +pub fn parse_sdp(sdp: &str, fail_on_warning: bool) -> Result<SdpSession, SdpParserError> { + if sdp.is_empty() { + return Err(SdpParserError::Line { + error: SdpParserInternalError::Generic("empty SDP".to_string()), + line: sdp.to_string(), + line_number: 0, + }); + } + // see test_parse_sdp_minimal_sdp_successfully + if sdp.len() < 51 { + return Err(SdpParserError::Line { + error: SdpParserInternalError::Generic("string too short to be valid SDP".to_string()), + line: sdp.to_string(), + line_number: 0, + }); + } + let lines = sdp.lines(); + let mut errors: Vec<SdpParserError> = Vec::new(); + let mut warnings: Vec<SdpParserError> = Vec::new(); + let mut sdp_lines: Vec<SdpLine> = Vec::new(); + for (line_number, line) in lines.enumerate() { + let stripped_line = line.trim(); + if stripped_line.is_empty() { + continue; + } + match parse_sdp_line(line, line_number) { + Ok(n) => { + sdp_lines.push(n); + } + Err(e) => { + match e { + // TODO is this really a good way to accomplish this? + SdpParserError::Line { + error, + line, + line_number, + } => errors.push(SdpParserError::Line { + error, + line, + line_number, + }), + SdpParserError::Unsupported { + error, + line, + line_number, + } => { + warnings.push(SdpParserError::Unsupported { + error, + line, + line_number, + }); + } + SdpParserError::Sequence { + message, + line_number, + } => errors.push(SdpParserError::Sequence { + message, + line_number, + }), + } + } + }; + } + + if fail_on_warning && (!warnings.is_empty()) { + return Err(warnings.remove(0)); + } + + // We just return the last of the errors here + if let Some(e) = errors.pop() { + return Err(e); + }; + + let mut session = parse_sdp_vector(&mut sdp_lines)?; + session.warnings = warnings; + + for warning in &session.warnings { + warn!("Warning: {}", &warning); + } + + Ok(session) +} + +#[cfg(test)] +mod tests { + extern crate url; + use super::*; + use address::{Address, AddressType}; + use anonymizer::ToBytesVec; + use media_type::create_dummy_media_section; + use std::net::IpAddr; + use std::net::Ipv4Addr; + + fn create_dummy_sdp_session() -> SdpSession { + let origin = parse_origin("mozilla 506705521068071134 0 IN IP4 0.0.0.0"); + assert!(origin.is_ok()); + let connection = parse_connection("IN IP4 198.51.100.7"); + assert!(connection.is_ok()); + let mut sdp_session; + if let SdpType::Origin(o) = origin.unwrap() { + sdp_session = SdpSession::new(0, o, "-".to_string()); + + if let Ok(SdpType::Connection(c)) = connection { + sdp_session.connection = Some(c); + } else { + unreachable!(); + } + } else { + unreachable!(); + } + sdp_session + } + + #[test] + fn test_session_works() -> Result<(), SdpParserInternalError> { + parse_session("topic")?; + Ok(()) + } + + #[test] + fn test_version_works() -> Result<(), SdpParserInternalError> { + parse_version("0")?; + 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()); + } + + #[test] + fn test_origin_works() -> Result<(), SdpParserInternalError> { + parse_origin("mozilla 506705521068071134 0 IN IP4 0.0.0.0")?; + parse_origin("mozilla 506705521068071134 0 IN IP6 2001:db8::1")?; + Ok(()) + } + + #[test] + fn test_origin_missing_username() { + assert!(parse_origin("").is_err()); + } + + #[test] + fn test_origin_missing_session_id() { + assert!(parse_origin("mozilla ").is_err()); + } + + #[test] + fn test_origin_missing_session_version() { + assert!(parse_origin("mozilla 506705521068071134 ").is_err()); + } + + #[test] + fn test_origin_missing_nettype() { + assert!(parse_origin("mozilla 506705521068071134 0 ").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_missing_addtype() { + assert!(parse_origin("mozilla 506705521068071134 0 IN ").is_err()); + } + + #[test] + fn test_origin_missing_ip_addr() { + assert!(parse_origin("mozilla 506705521068071134 0 IN IP4 ").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_invalid_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()); + } + + #[test] + fn connection_works() -> Result<(), SdpParserInternalError> { + parse_connection("IN IP4 127.0.0.1")?; + parse_connection("IN IP4 127.0.0.1/10/10")?; + parse_connection("IN IP6 ::1")?; + parse_connection("IN IP6 ::1/1/1")?; + Ok(()) + } + + #[test] + fn connection_lots_of_whitespace() -> Result<(), SdpParserInternalError> { + parse_connection("IN IP4 127.0.0.1")?; + 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()); + } + + #[test] + fn bandwidth_works() -> Result<(), SdpParserInternalError> { + parse_bandwidth("AS:1")?; + parse_bandwidth("CT:123")?; + parse_bandwidth("TIAS:12345")?; + 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() -> Result<(), SdpParserInternalError> { + parse_bandwidth("UNSUPPORTED:12345")?; + Ok(()) + } + + #[test] + fn test_timing_works() -> Result<(), SdpParserInternalError> { + parse_timing("0 0")?; + 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()); + } + + #[test] + fn test_parse_sdp_line_works() -> Result<(), SdpParserError> { + parse_sdp_line("v=0", 0)?; + parse_sdp_line("s=somesession", 0)?; + Ok(()) + } + + #[test] + fn test_parse_sdp_line_empty_line() { + assert!(parse_sdp_line("", 0).is_err()); + } + + #[test] + fn test_parse_sdp_line_unsupported_types() { + assert!(parse_sdp_line("e=foobar", 0).is_err()); + assert!(parse_sdp_line("i=foobar", 0).is_err()); + assert!(parse_sdp_line("k=foobar", 0).is_err()); + assert!(parse_sdp_line("p=foobar", 0).is_err()); + assert!(parse_sdp_line("r=foobar", 0).is_err()); + assert!(parse_sdp_line("u=foobar", 0).is_err()); + assert!(parse_sdp_line("z=foobar", 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()); + } + + #[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() -> Result<(), SdpParserError> { + parse_sdp_line("a=rtpmap:8 PCMA/8000", 0)?; + Ok(()) + } + + #[test] + fn test_parse_sdp_line_invalid_a_line() { + assert!(parse_sdp_line("a=rtpmap:200 PCMA/8000", 0).is_err()); + } + + #[test] + fn test_add_attribute() -> Result<(), SdpParserInternalError> { + let mut sdp_session = create_dummy_sdp_session(); + + sdp_session.add_attribute(SdpAttribute::Sendrecv)?; + assert!(sdp_session.add_attribute(SdpAttribute::BundleOnly).is_err()); + assert_eq!(sdp_session.attribute.len(), 1); + Ok(()) + } + + #[test] + fn test_sanity_check_sdp_session_timing() -> Result<(), SdpParserError> { + 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); + + sanity_check_sdp_session(&sdp_session)?; + Ok(()) + } + + #[test] + fn test_sanity_check_sdp_session_media() -> Result<(), SdpParserError> { + let mut sdp_session = create_dummy_sdp_session(); + let t = SdpTiming { start: 0, stop: 0 }; + sdp_session.set_timing(t); + + sanity_check_sdp_session(&sdp_session)?; + + sdp_session.extend_media(vec![create_dummy_media_section()]); + + sanity_check_sdp_session(&sdp_session)?; + Ok(()) + } + + #[test] + fn test_sanity_check_sdp_connection() -> Result<(), SdpParserInternalError> { + let origin = parse_origin("mozilla 506705521068071134 0 IN IP4 0.0.0.0")?; + let mut sdp_session; + if let SdpType::Origin(o) = origin { + sdp_session = SdpSession::new(0, o, "-".to_string()); + } else { + unreachable!(); + } + let t = SdpTiming { start: 0, stop: 0 }; + sdp_session.set_timing(t); + + assert!(sanity_check_sdp_session(&sdp_session).is_ok()); + + // the dummy media section doesn't contain a connection + sdp_session.extend_media(vec![create_dummy_media_section()]); + + assert!(sanity_check_sdp_session(&sdp_session).is_err()); + + let connection = parse_connection("IN IP6 ::1")?; + if let SdpType::Connection(c) = connection { + sdp_session.connection = Some(c); + } else { + unreachable!(); + } + + assert!(sanity_check_sdp_session(&sdp_session).is_ok()); + + let mut second_media = create_dummy_media_section(); + let mconnection = parse_connection("IN IP4 0.0.0.0")?; + if let SdpType::Connection(c) = mconnection { + second_media.set_connection(c); + } else { + unreachable!(); + } + sdp_session.extend_media(vec![second_media]); + assert!(sdp_session.media.len() == 2); + + assert!(sanity_check_sdp_session(&sdp_session).is_ok()); + Ok(()) + } + + #[test] + fn test_sanity_check_sdp_session_extmap() -> Result<(), SdpParserInternalError> { + 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")?; + if let SdpType::Attribute(a) = attribute { + sdp_session.add_attribute(a)?; + } else { + unreachable!(); + } + assert!(sdp_session + .get_attribute(SdpAttributeType::Extmap) + .is_some()); + + assert!(sanity_check_sdp_session(&sdp_session).is_ok()); + + let mut second_media = create_dummy_media_section(); + let mattribute = + parse_attribute("extmap:1/sendonly urn:ietf:params:rtp-hdrext:ssrc-audio-level")?; + if let SdpType::Attribute(ma) = mattribute { + second_media.add_attribute(ma)?; + } else { + unreachable!(); + } + assert!(second_media + .get_attribute(SdpAttributeType::Extmap) + .is_some()); + + 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()); + Ok(()) + } + + #[test] + fn test_sanity_check_sdp_session_simulcast() -> Result<(), SdpParserError> { + 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()]); + + sanity_check_sdp_session(&sdp_session)?; + Ok(()) + } + + #[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_minimal_sdp_successfully() -> Result<(), SdpParserError> { + parse_sdp( + "v=0\r\n +o=- 0 0 IN IP6 ::1\r\n +s=-\r\n +c=IN IP6 ::1\r\n +t=0 0\r\n", + true, + )?; + Ok(()) + } + + #[test] + fn test_parse_sdp_too_short() { + assert!(parse_sdp( + "v=0\r\n +o=- 0 0 IN IP4 0.0.0.0\r\n +s=-\r\n", + 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() -> Result<(), SdpParserError> { + parse_sdp( + "v=0\r\n +o=- 0 0 IN IP4 0.0.0.0\r\n +s=-\r\n +c=IN IP4 198.51.100.7\r\n +t=0 0\r\n +m=audio 0 UDP/TLS/RTP/SAVPF 0\r\n +a=unsupported\r\n", + false, + )?; + 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 +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_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()); + } + + #[test] + fn test_mask_origin() { + let mut anon = StatefulSdpAnonymizer::new(); + if let SdpType::Origin(origin_1) = + parse_origin("mozilla 506705521068071134 0 IN IP4 0.0.0.0").unwrap() + { + for _ in 0..2 { + let masked = origin_1.masked_clone(&mut anon); + assert_eq!(masked.username, "origin-user-00000001"); + assert_eq!( + masked.unicast_addr, + ExplicitlyTypedAddress::Ip(IpAddr::V4(Ipv4Addr::from(1))) + ); + } + } else { + unreachable!(); + } + } + + #[test] + fn test_mask_sdp() { + let mut anon = StatefulSdpAnonymizer::new(); + let sdp = parse_sdp( + "v=0\r\n + o=ausername 4294967296 2 IN IP4 127.0.0.1\r\n + s=SIP Call\r\n + c=IN IP4 198.51.100.7/51\r\n + a=ice-pwd:12340\r\n + a=ice-ufrag:4a799b2e\r\n + a=fingerprint:sha-1 CD:34:D1:62:16:95:7B:B7:EB:74:E2:39:27:97:EB:0B:23:73:AC:BC\r\n + t=0 0\r\n + m=video 56436 RTP/SAVPF 120\r\n + a=candidate:77142221 1 udp 2113937151 192.168.137.1 54081 typ host\r\n + a=remote-candidates:0 10.0.0.1 5555\r\n + a=rtpmap:120 VP8/90000\r\n", + true, + ) + .unwrap(); + let mut masked = sdp.masked_clone(&mut anon); + assert_eq!(masked.origin.username, "origin-user-00000001"); + assert_eq!( + masked.origin.unicast_addr, + ExplicitlyTypedAddress::Ip(IpAddr::V4(Ipv4Addr::from(1))) + ); + assert_eq!( + masked.connection.unwrap().address, + ExplicitlyTypedAddress::Ip(IpAddr::V4(Ipv4Addr::from(2))) + ); + let mut attributes = masked.attribute; + for m in &mut masked.media { + for attribute in m.get_attributes() { + attributes.push(attribute.clone()); + } + } + for attribute in attributes { + match attribute { + SdpAttribute::Candidate(c) => { + assert_eq!(c.address, Address::Ip(IpAddr::V4(Ipv4Addr::from(3)))); + assert_eq!(c.port, 1); + } + SdpAttribute::Fingerprint(f) => { + assert_eq!(f.fingerprint, 1u64.to_byte_vec()); + } + SdpAttribute::IcePwd(p) => { + assert_eq!(p, "ice-password-00000001"); + } + SdpAttribute::IceUfrag(u) => { + assert_eq!(u, "ice-user-00000001"); + } + SdpAttribute::RemoteCandidate(r) => { + assert_eq!(r.address, Address::Ip(IpAddr::V4(Ipv4Addr::from(4)))); + assert_eq!(r.port, 2); + } + _ => {} + } + } + } + + #[test] + fn test_parse_session_vector() -> Result<(), SdpParserError> { + let mut sdp_session = create_dummy_sdp_session(); + let mut lines: Vec<SdpLine> = Vec::new(); + lines.push(parse_sdp_line("a=sendrecv", 1)?); + sdp_session.parse_session_vector(&mut lines)?; + assert_eq!(sdp_session.attribute.len(), 1); + Ok(()) + } + + #[test] + fn test_parse_session_vector_non_session_attribute() -> Result<(), SdpParserError> { + let mut sdp_session = create_dummy_sdp_session(); + let mut lines: Vec<SdpLine> = Vec::new(); + lines.push(parse_sdp_line("a=bundle-only", 2)?); + assert!(sdp_session.parse_session_vector(&mut lines).is_err()); + assert_eq!(sdp_session.attribute.len(), 0); + Ok(()) + } + + #[test] + fn test_parse_session_vector_version_repeated() -> Result<(), SdpParserError> { + let mut sdp_session = create_dummy_sdp_session(); + let mut lines: Vec<SdpLine> = Vec::new(); + lines.push(parse_sdp_line("v=0", 3)?); + assert!(sdp_session.parse_session_vector(&mut lines).is_err()); + Ok(()) + } + + #[test] + fn test_parse_session_vector_contains_media_type() -> Result<(), SdpParserError> { + let mut sdp_session = create_dummy_sdp_session(); + let mut lines: Vec<SdpLine> = Vec::new(); + lines.push(parse_sdp_line("m=audio 0 UDP/TLS/RTP/SAVPF 0", 4)?); + assert!(sdp_session.parse_session_vector(&mut lines).is_err()); + Ok(()) + } + + #[test] + fn test_parse_sdp_vector_no_media_section() -> Result<(), SdpParserError> { + let mut lines: Vec<SdpLine> = Vec::new(); + lines.push(parse_sdp_line("v=0", 1)?); + lines.push(parse_sdp_line( + "o=ausername 4294967296 2 IN IP4 127.0.0.1", + 1, + )?); + lines.push(parse_sdp_line("s=SIP Call", 1)?); + lines.push(parse_sdp_line("t=0 0", 1)?); + lines.push(parse_sdp_line("c=IN IP6 ::1", 1)?); + assert!(parse_sdp_vector(&mut lines).is_ok()); + Ok(()) + } + + #[test] + fn test_parse_sdp_vector_with_media_section() -> Result<(), SdpParserError> { + let mut lines: Vec<SdpLine> = Vec::new(); + lines.push(parse_sdp_line("v=0", 1)?); + lines.push(parse_sdp_line( + "o=ausername 4294967296 2 IN IP4 127.0.0.1", + 1, + )?); + lines.push(parse_sdp_line("s=SIP Call", 1)?); + lines.push(parse_sdp_line("t=0 0", 1)?); + lines.push(parse_sdp_line("m=video 56436 RTP/SAVPF 120", 1)?); + lines.push(parse_sdp_line("c=IN IP6 ::1", 1)?); + assert!(parse_sdp_vector(&mut lines).is_ok()); + Ok(()) + } + + #[test] + fn test_parse_sdp_vector_too_short() -> Result<(), SdpParserError> { + let mut lines: Vec<SdpLine> = Vec::new(); + lines.push(parse_sdp_line("v=0", 1)?); + assert!(parse_sdp_vector(&mut lines).is_err()); + Ok(()) + } + + #[test] + fn test_parse_sdp_vector_missing_version() -> Result<(), SdpParserError> { + let mut lines: Vec<SdpLine> = Vec::new(); + lines.push(parse_sdp_line( + "o=ausername 4294967296 2 IN IP4 127.0.0.1", + 1, + )?); + for _ in 0..3 { + lines.push(parse_sdp_line("a=sendrecv", 1)?); + } + assert!(parse_sdp_vector(&mut lines).is_err()); + Ok(()) + } + + #[test] + fn test_parse_sdp_vector_missing_origin() -> Result<(), SdpParserError> { + let mut lines: Vec<SdpLine> = Vec::new(); + lines.push(parse_sdp_line("v=0", 1)?); + for _ in 0..3 { + lines.push(parse_sdp_line("a=sendrecv", 1)?); + } + assert!(parse_sdp_vector(&mut lines).is_err()); + Ok(()) + } + + #[test] + fn test_parse_sdp_vector_missing_session() -> Result<(), SdpParserError> { + let mut lines: Vec<SdpLine> = Vec::new(); + lines.push(parse_sdp_line("v=0", 1)?); + lines.push(parse_sdp_line( + "o=ausername 4294967296 2 IN IP4 127.0.0.1", + 1, + )?); + for _ in 0..2 { + lines.push(parse_sdp_line("a=sendrecv", 1)?); + } + assert!(parse_sdp_vector(&mut lines).is_err()); + Ok(()) + } + + #[test] + fn test_session_add_media_works() -> Result<(), SdpParserError> { + let mut sdp_session = create_dummy_sdp_session(); + assert!(sdp_session + .add_media( + SdpMediaValue::Audio, + SdpAttribute::Sendrecv, + 99, + SdpProtocolValue::RtpSavpf, + ExplicitlyTypedAddress::from(Ipv4Addr::new(127, 0, 0, 1)) + ) + .is_ok()); + assert!(sdp_session.get_connection().is_some()); + assert_eq!(sdp_session.attribute.len(), 0); + assert_eq!(sdp_session.media.len(), 1); + assert_eq!(sdp_session.media[0].get_attributes().len(), 1); + assert!(sdp_session.media[0] + .get_attribute(SdpAttributeType::Sendrecv) + .is_some()); + Ok(()) + } + + #[test] + fn test_session_add_media_invalid_attribute_fails() -> Result<(), SdpParserInternalError> { + let mut sdp_session = create_dummy_sdp_session(); + assert!(sdp_session + .add_media( + SdpMediaValue::Audio, + SdpAttribute::IceLite, + 99, + SdpProtocolValue::RtpSavpf, + ExplicitlyTypedAddress::try_from((AddressType::IpV4, "127.0.0.1"))? + ) + .is_err()); + Ok(()) + } +} diff --git a/third_party/rust/webrtc-sdp/src/media_type.rs b/third_party/rust/webrtc-sdp/src/media_type.rs new file mode 100644 index 0000000000..4e8f625790 --- /dev/null +++ b/third_party/rust/webrtc-sdp/src/media_type.rs @@ -0,0 +1,898 @@ +/* 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/. */ + +use anonymizer::{AnonymizingClone, StatefulSdpAnonymizer}; +use attribute_type::{ + maybe_print_param, SdpAttribute, SdpAttributeRtpmap, SdpAttributeSctpmap, SdpAttributeType, +}; +use error::{SdpParserError, SdpParserInternalError}; +use std::fmt; +use {SdpBandwidth, SdpConnection, SdpLine, SdpType}; + +/* + * RFC4566 + * media-field = %x6d "=" media SP port ["/" integer] + * SP proto 1*(SP fmt) CRLF + */ +#[derive(Clone)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "enhanced_debug", derive(Debug))] +pub struct SdpMediaLine { + pub media: SdpMediaValue, + pub port: u32, + pub port_count: u32, + pub proto: SdpProtocolValue, + pub formats: SdpFormatList, +} + +impl fmt::Display for SdpMediaLine { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "{media} {port}{pcount} {proto} {formats}", + media = self.media, + port = self.port, + pcount = maybe_print_param("/", self.port_count, 0), + proto = self.proto, + formats = self.formats + ) + } +} + +#[derive(Debug, PartialEq, Clone)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +pub enum SdpMediaValue { + Audio, + Video, + Application, +} + +impl fmt::Display for SdpMediaValue { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + SdpMediaValue::Audio => "audio", + SdpMediaValue::Video => "video", + SdpMediaValue::Application => "application", + } + .fmt(f) + } +} + +#[derive(Debug, PartialEq, Clone)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +pub enum SdpProtocolValue { + RtpAvp, /* RTP/AVP [RFC4566] */ + RtpAvpf, /* RTP/AVPF [RFC4585] */ + RtpSavp, /* RTP/SAVP [RFC3711] */ + RtpSavpf, /* RTP/SAVPF [RFC5124] */ + TcpDtlsRtpSavp, /* TCP/DTLS/RTP/SAVP [RFC7850] */ + TcpDtlsRtpSavpf, /* TCP/DTLS/RTP/SAVPF [RFC7850] */ + UdpTlsRtpSavp, /* UDP/TLS/RTP/SAVP [RFC5764] */ + UdpTlsRtpSavpf, /* UDP/TLS/RTP/SAVPF [RFC5764] */ + DtlsSctp, /* DTLS/SCTP [draft-ietf-mmusic-sctp-sdp-07] */ + UdpDtlsSctp, /* UDP/DTLS/SCTP [draft-ietf-mmusic-sctp-sdp-26] */ + TcpDtlsSctp, /* TCP/DTLS/SCTP [draft-ietf-mmusic-sctp-sdp-26] */ +} + +impl fmt::Display for SdpProtocolValue { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + SdpProtocolValue::RtpAvp => "RTP/AVP", + SdpProtocolValue::RtpAvpf => "RTP/AVPF", + SdpProtocolValue::RtpSavp => "RTP/SAVP", + SdpProtocolValue::RtpSavpf => "RTP/SAVPF", + SdpProtocolValue::TcpDtlsRtpSavp => "TCP/DTLS/RTP/SAVP", + SdpProtocolValue::TcpDtlsRtpSavpf => "TCP/DTLS/RTP/SAVPF", + SdpProtocolValue::UdpTlsRtpSavp => "UDP/TLS/RTP/SAVP", + SdpProtocolValue::UdpTlsRtpSavpf => "UDP/TLS/RTP/SAVPF", + SdpProtocolValue::DtlsSctp => "DTLS/SCTP", + SdpProtocolValue::UdpDtlsSctp => "UDP/DTLS/SCTP", + SdpProtocolValue::TcpDtlsSctp => "TCP/DTLS/SCTP", + } + .fmt(f) + } +} + +#[derive(Clone)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "enhanced_debug", derive(Debug))] +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) => maybe_vector_to_string!("{}", x, " "), + SdpFormatList::Strings(ref x) => x.join(" "), + } + .fmt(f) + } +} + +/* + * RFC4566 + * media-descriptions = *( media-field + * information-field + * *connection-field + * bandwidth-fields + * key-field + * attribute-fields ) + */ +#[derive(Clone)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "enhanced_debug", derive(Debug))] +pub struct SdpMedia { + media: SdpMediaLine, + connection: Option<SdpConnection>, + bandwidth: Vec<SdpBandwidth>, + attribute: Vec<SdpAttribute>, + // unsupported values: + // information: Option<String>, + // key: Option<String>, +} + +impl fmt::Display for SdpMedia { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "m={mline}\r\n{bw}{connection}{attributes}", + mline = self.media, + bw = maybe_vector_to_string!("b={}\r\n", self.bandwidth, "\r\nb="), + connection = option_to_string!("c={}\r\n", self.connection), + attributes = maybe_vector_to_string!("a={}\r\n", self.attribute, "\r\na=") + ) + } +} + +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 set_port(&mut self, port: u32) { + self.media.port = port; + } + + 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 get_bandwidth(&self) -> &Vec<SdpBandwidth> { + &self.bandwidth + } + + pub fn add_bandwidth(&mut self, bw: SdpBandwidth) { + self.bandwidth.push(bw) + } + + 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 + ))); + } + self.attribute.push(attr); + Ok(()) + } + + pub fn get_attribute(&self, t: SdpAttributeType) -> Option<&SdpAttribute> { + self.attribute + .iter() + .find(|a| SdpAttributeType::from(*a) == t) + } + + pub fn remove_attribute(&mut self, t: SdpAttributeType) { + self.attribute.retain(|a| SdpAttributeType::from(a) != t); + } + + pub fn set_attribute(&mut self, attr: SdpAttribute) -> Result<(), SdpParserInternalError> { + self.remove_attribute(SdpAttributeType::from(&attr)); + self.add_attribute(attr) + } + + pub fn remove_codecs(&mut self) { + match self.media.formats { + SdpFormatList::Integers(_) => self.media.formats = SdpFormatList::Integers(Vec::new()), + SdpFormatList::Strings(_) => self.media.formats = SdpFormatList::Strings(Vec::new()), + } + + self.attribute.retain({ + |x| { + !matches!( + *x, + SdpAttribute::Rtpmap(_) + | SdpAttribute::Fmtp(_) + | SdpAttribute::Rtcpfb(_) + | SdpAttribute::Sctpmap(_) + | SdpAttribute::SctpPort(_) + ) + } + }); + } + + pub fn add_codec(&mut self, rtpmap: SdpAttributeRtpmap) -> Result<(), SdpParserInternalError> { + match self.media.formats { + SdpFormatList::Integers(ref mut x) => x.push(u32::from(rtpmap.payload_type)), + SdpFormatList::Strings(ref mut x) => x.push(rtpmap.payload_type.to_string()), + } + + self.add_attribute(SdpAttribute::Rtpmap(rtpmap))?; + Ok(()) + } + + pub fn get_attributes_of_type(&self, t: SdpAttributeType) -> Vec<&SdpAttribute> { + self.attribute + .iter() + .filter(|a| SdpAttributeType::from(*a) == t) + .collect() + } + + pub fn get_connection(&self) -> &Option<SdpConnection> { + &self.connection + } + + pub fn set_connection(&mut self, c: SdpConnection) { + self.connection = Some(c) + } + + pub fn add_datachannel( + &mut self, + name: String, + port: u16, + streams: u16, + msg_size: u32, + ) -> Result<(), SdpParserInternalError> { + // Only one allowed, for now. This may change as the specs (and deployments) evolve. + match self.media.proto { + SdpProtocolValue::UdpDtlsSctp | SdpProtocolValue::TcpDtlsSctp => { + // new data channel format according to draft 21 + self.media.formats = SdpFormatList::Strings(vec![name]); + self.set_attribute(SdpAttribute::SctpPort(u64::from(port)))?; + } + _ => { + // old data channels format according to draft 05 + self.media.formats = SdpFormatList::Integers(vec![u32::from(port)]); + self.set_attribute(SdpAttribute::Sctpmap(SdpAttributeSctpmap { + port, + channels: u32::from(streams), + }))?; + } + } + if msg_size > 0 { + self.set_attribute(SdpAttribute::MaxMessageSize(u64::from(msg_size)))?; + } + self.media.media = SdpMediaValue::Application; + + Ok(()) + } +} + +impl AnonymizingClone for SdpMedia { + fn masked_clone(&self, anon: &mut StatefulSdpAnonymizer) -> Self { + let mut masked = SdpMedia { + media: self.media.clone(), + bandwidth: self.bandwidth.clone(), + connection: self.connection.clone(), + attribute: Vec::new(), + }; + for i in &self.attribute { + masked.attribute.push(i.masked_clone(anon)); + } + masked + } +} + +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 + ))); + } + }) +} + +fn parse_protocol_token(value: &str) -> Result<SdpProtocolValue, SdpParserInternalError> { + Ok(match value.to_uppercase().as_ref() { + "RTP/AVP" => SdpProtocolValue::RtpAvp, + "RTP/AVPF" => SdpProtocolValue::RtpAvpf, + "RTP/SAVP" => SdpProtocolValue::RtpSavp, + "RTP/SAVPF" => SdpProtocolValue::RtpSavpf, + "TCP/DTLS/RTP/SAVP" => SdpProtocolValue::TcpDtlsRtpSavp, + "TCP/DTLS/RTP/SAVPF" => SdpProtocolValue::TcpDtlsRtpSavpf, + "UDP/TLS/RTP/SAVP" => SdpProtocolValue::UdpTlsRtpSavp, + "UDP/TLS/RTP/SAVPF" => SdpProtocolValue::UdpTlsRtpSavpf, + "DTLS/SCTP" => SdpProtocolValue::DtlsSctp, + "UDP/DTLS/SCTP" => SdpProtocolValue::UdpDtlsSctp, + "TCP/DTLS/SCTP" => SdpProtocolValue::TcpDtlsSctp, + _ => { + return Err(SdpParserInternalError::Unsupported(format!( + "unsupported protocol value: {}", + value + ))); + } + }) +} + +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 + 35 ..= 63 | 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, + }; + trace!("media: {}, {}, {}, {}", m.media, m.port, m.proto, m.formats); + Ok(SdpType::Media(m)) +} + +pub fn parse_media_vector(lines: &mut Vec<SdpLine>) -> Result<Vec<SdpMedia>, SdpParserError> { + let mut media_sections: Vec<SdpMedia> = Vec::new(); + + let media_line = lines.remove(0); + let mut sdp_media = match media_line.sdp_type { + SdpType::Media(v) => SdpMedia::new(v), + _ => { + return Err(SdpParserError::Sequence { + message: "first line in media section needs to be a media line".to_string(), + line_number: media_line.line_number, + }); + } + }; + + while !lines.is_empty() { + let line = lines.remove(0); + let _line_number = line.line_number; + match line.sdp_type { + SdpType::Connection(c) => { + if sdp_media.connection.is_some() { + return Err(SdpParserError::Sequence { + message: "connection type already exists at this media level".to_string(), + line_number: _line_number, + }); + } + + sdp_media.set_connection(c); + } + SdpType::Bandwidth(b) => sdp_media.add_bandwidth(b), + SdpType::Attribute(a) => { + match a { + SdpAttribute::DtlsMessage(_) => { + // Ignore this attribute on media level + Ok(()) + } + SdpAttribute::Rtpmap(rtpmap) => { + sdp_media.add_attribute(SdpAttribute::Rtpmap(SdpAttributeRtpmap { + payload_type: rtpmap.payload_type, + codec_name: rtpmap.codec_name.clone(), + frequency: rtpmap.frequency, + channels: rtpmap.channels, + })) + } + _ => sdp_media.add_attribute(a), + } + .map_err(|e: SdpParserInternalError| SdpParserError::Sequence { + message: format!("{}", e), + line_number: _line_number, + })? + } + SdpType::Media(v) => { + media_sections.push(sdp_media); + sdp_media = SdpMedia::new(v); + } + + SdpType::Origin(_) | SdpType::Session(_) | SdpType::Timing(_) | SdpType::Version(_) => { + return Err(SdpParserError::Sequence { + message: "invalid type in media section".to_string(), + line_number: line.line_number, + }); + } + }; + } + + media_sections.push(sdp_media); + + Ok(media_sections) +} + +#[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) +} + +#[cfg(test)] +mod tests { + use super::*; + use address::{AddressType, ExplicitlyTypedAddress}; + use attribute_type::{ + SdpAttributeFmtp, SdpAttributeFmtpParameters, SdpAttributePayloadType, SdpAttributeRtcpFb, + SdpAttributeRtcpFbType, + }; + use std::convert::TryFrom; + + // TODO is this useful outside of tests? + impl SdpFormatList { + fn len(&self) -> usize { + match *self { + SdpFormatList::Integers(ref x) => x.len(), + SdpFormatList::Strings(ref x) => x.len(), + } + } + } + + pub fn add_dummy_attributes(media: &mut SdpMedia) { + assert!(media + .add_attribute(SdpAttribute::Rtcpfb(SdpAttributeRtcpFb { + payload_type: SdpAttributePayloadType::Wildcard, + feedback_type: SdpAttributeRtcpFbType::Ack, + parameter: "".to_string(), + extra: "".to_string(), + },)) + .is_ok()); + assert!(media + .add_attribute(SdpAttribute::Fmtp(SdpAttributeFmtp { + payload_type: 1, + parameters: SdpAttributeFmtpParameters { + packetization_mode: 0, + level_asymmetry_allowed: false, + profile_level_id: 0x0042_0010, + max_fs: 0, + max_cpb: 0, + max_dpb: 0, + max_br: 0, + max_mbps: 0, + usedtx: false, + stereo: false, + useinbandfec: false, + cbr: false, + max_fr: 0, + maxplaybackrate: 48000, + maxaveragebitrate: 0, + ptime: 0, + minptime: 0, + maxptime: 0, + encodings: Vec::new(), + dtmf_tones: "".to_string(), + rtx: None, + unknown_tokens: Vec::new() + } + },)) + .is_ok()); + assert!(media + .add_attribute(SdpAttribute::Sctpmap(SdpAttributeSctpmap { + port: 5000, + channels: 2, + })) + .is_ok()); + assert!(media.add_attribute(SdpAttribute::BundleOnly).is_ok()); + assert!(media.add_attribute(SdpAttribute::SctpPort(5000)).is_ok()); + + assert!(media.get_attribute(SdpAttributeType::Rtpmap).is_some()); + assert!(media.get_attribute(SdpAttributeType::Rtcpfb).is_some()); + assert!(media.get_attribute(SdpAttributeType::Fmtp).is_some()); + assert!(media.get_attribute(SdpAttributeType::Sctpmap).is_some()); + assert!(media.get_attribute(SdpAttributeType::SctpPort).is_some()); + assert!(media.get_attribute(SdpAttributeType::BundleOnly).is_some()); + } + + fn check_parse(media_line_str: &str) -> SdpMediaLine { + if let Ok(SdpType::Media(media_line)) = parse_media(media_line_str) { + media_line + } else { + unreachable!() + } + } + + fn check_parse_and_serialize(media_line_str: &str) { + let parsed = check_parse(media_line_str); + assert_eq!(parsed.to_string(), media_line_str.to_string()); + } + + #[test] + fn test_get_set_port() { + let mut msection = create_dummy_media_section(); + assert_eq!(msection.get_port(), 9); + msection.set_port(2048); + assert_eq!(msection.get_port(), 2048); + } + + #[test] + fn test_add_codec() -> Result<(), SdpParserInternalError> { + let mut msection = create_dummy_media_section(); + msection.add_codec(SdpAttributeRtpmap::new(96, "foobar".to_string(), 1000))?; + assert_eq!(msection.get_formats().len(), 1); + assert!(msection.get_attribute(SdpAttributeType::Rtpmap).is_some()); + + let mut msection = create_dummy_media_section(); + msection.media.formats = SdpFormatList::Strings(Vec::new()); + msection.add_codec(SdpAttributeRtpmap::new(97, "boofar".to_string(), 1001))?; + assert_eq!(msection.get_formats().len(), 1); + assert!(msection.get_attribute(SdpAttributeType::Rtpmap).is_some()); + Ok(()) + } + + #[test] + fn test_remove_codecs() -> Result<(), SdpParserInternalError> { + let mut msection = create_dummy_media_section(); + msection.add_codec(SdpAttributeRtpmap::new(96, "foobar".to_string(), 1000))?; + assert_eq!(msection.get_formats().len(), 1); + assert!(msection.get_attribute(SdpAttributeType::Rtpmap).is_some()); + msection.remove_codecs(); + assert_eq!(msection.get_formats().len(), 0); + assert!(msection.get_attribute(SdpAttributeType::Rtpmap).is_none()); + + let mut msection = create_dummy_media_section(); + msection.media.formats = SdpFormatList::Strings(Vec::new()); + msection.add_codec(SdpAttributeRtpmap::new(97, "boofar".to_string(), 1001))?; + assert_eq!(msection.get_formats().len(), 1); + + add_dummy_attributes(&mut msection); + + msection.remove_codecs(); + assert_eq!(msection.get_formats().len(), 0); + assert!(msection.get_attribute(SdpAttributeType::Rtpmap).is_none()); + assert!(msection.get_attribute(SdpAttributeType::Rtcpfb).is_none()); + assert!(msection.get_attribute(SdpAttributeType::Fmtp).is_none()); + assert!(msection.get_attribute(SdpAttributeType::Sctpmap).is_none()); + assert!(msection.get_attribute(SdpAttributeType::SctpPort).is_none()); + Ok(()) + } + + #[test] + fn test_add_datachannel() -> Result<(), SdpParserInternalError> { + let mut msection = create_dummy_media_section(); + msection.add_datachannel("foo".to_string(), 5000, 256, 0)?; + assert_eq!(*msection.get_type(), SdpMediaValue::Application); + assert!(msection.get_attribute(SdpAttributeType::SctpPort).is_none()); + assert!(msection + .get_attribute(SdpAttributeType::MaxMessageSize) + .is_none()); + assert!(msection.get_attribute(SdpAttributeType::Sctpmap).is_some()); + match *msection.get_attribute(SdpAttributeType::Sctpmap).unwrap() { + SdpAttribute::Sctpmap(ref s) => { + assert_eq!(s.port, 5000); + assert_eq!(s.channels, 256); + } + _ => unreachable!(), + } + + let mut msection = create_dummy_media_section(); + msection.add_datachannel("foo".to_string(), 5000, 256, 1234)?; + assert_eq!(*msection.get_type(), SdpMediaValue::Application); + assert!(msection.get_attribute(SdpAttributeType::SctpPort).is_none()); + assert!(msection + .get_attribute(SdpAttributeType::MaxMessageSize) + .is_some()); + match *msection + .get_attribute(SdpAttributeType::MaxMessageSize) + .unwrap() + { + SdpAttribute::MaxMessageSize(m) => { + assert_eq!(m, 1234); + } + _ => unreachable!(), + } + + let mut msection = create_dummy_media_section(); + msection.media.proto = SdpProtocolValue::UdpDtlsSctp; + msection.add_datachannel("foo".to_string(), 5000, 256, 5678)?; + assert_eq!(*msection.get_type(), SdpMediaValue::Application); + assert!(msection.get_attribute(SdpAttributeType::Sctpmap).is_none()); + assert!(msection.get_attribute(SdpAttributeType::SctpPort).is_some()); + match *msection.get_attribute(SdpAttributeType::SctpPort).unwrap() { + SdpAttribute::SctpPort(s) => { + assert_eq!(s, 5000); + } + _ => unreachable!(), + } + assert!(msection + .get_attribute(SdpAttributeType::MaxMessageSize) + .is_some()); + match *msection + .get_attribute(SdpAttributeType::MaxMessageSize) + .unwrap() + { + SdpAttribute::MaxMessageSize(m) => { + assert_eq!(m, 5678); + } + _ => unreachable!(), + } + Ok(()) + } + + #[test] + fn test_parse_media_token() -> Result<(), SdpParserInternalError> { + let audio = parse_media_token("audio")?; + assert_eq!(audio, SdpMediaValue::Audio); + let video = parse_media_token("VIDEO")?; + assert_eq!(video, SdpMediaValue::Video); + let app = parse_media_token("aPplIcatIOn")?; + assert_eq!(app, SdpMediaValue::Application); + + assert!(parse_media_token("").is_err()); + assert!(parse_media_token("foobar").is_err()); + Ok(()) + } + + #[test] + fn test_parse_protocol_rtp_token() -> Result<(), SdpParserInternalError> { + fn parse_and_serialize_protocol_token( + token: &str, + result: SdpProtocolValue, + ) -> Result<(), SdpParserInternalError> { + let rtps = parse_protocol_token(token)?; + assert_eq!(rtps, result); + assert_eq!(rtps.to_string(), token.to_uppercase()); + Ok(()) + } + parse_and_serialize_protocol_token("rtp/avp", SdpProtocolValue::RtpAvp)?; + parse_and_serialize_protocol_token("rtp/avpf", SdpProtocolValue::RtpAvpf)?; + parse_and_serialize_protocol_token("rtp/savp", SdpProtocolValue::RtpSavp)?; + parse_and_serialize_protocol_token("rtp/savpf", SdpProtocolValue::RtpSavpf)?; + parse_and_serialize_protocol_token("udp/tls/rtp/savp", SdpProtocolValue::UdpTlsRtpSavp)?; + parse_and_serialize_protocol_token("udp/tls/rtp/savpf", SdpProtocolValue::UdpTlsRtpSavpf)?; + parse_and_serialize_protocol_token("TCP/dtls/rtp/savp", SdpProtocolValue::TcpDtlsRtpSavp)?; + parse_and_serialize_protocol_token( + "tcp/DTLS/rtp/savpf", + SdpProtocolValue::TcpDtlsRtpSavpf, + )?; + + assert!(parse_protocol_token("").is_err()); + assert!(parse_protocol_token("foobar").is_err()); + Ok(()) + } + + #[test] + fn test_parse_protocol_sctp_token() -> Result<(), SdpParserInternalError> { + fn parse_and_serialize_protocol_token( + token: &str, + result: SdpProtocolValue, + ) -> Result<(), SdpParserInternalError> { + let rtps = parse_protocol_token(token)?; + assert_eq!(rtps, result); + assert_eq!(rtps.to_string(), token.to_uppercase()); + Ok(()) + } + parse_and_serialize_protocol_token("dtLs/ScTP", SdpProtocolValue::DtlsSctp)?; + parse_and_serialize_protocol_token("udp/DTLS/sctp", SdpProtocolValue::UdpDtlsSctp)?; + parse_and_serialize_protocol_token("tcp/dtls/SCTP", SdpProtocolValue::TcpDtlsSctp)?; + Ok(()) + } + + #[test] + fn test_media_works() { + check_parse_and_serialize("audio 9 UDP/TLS/RTP/SAVPF 109"); + check_parse_and_serialize("video 9 UDP/TLS/RTP/SAVPF 126"); + check_parse_and_serialize("application 9 DTLS/SCTP 5000"); + check_parse_and_serialize("application 9 UDP/DTLS/SCTP webrtc-datachannel"); + + check_parse_and_serialize("audio 9 UDP/TLS/RTP/SAVPF 109 9 0 8"); + check_parse_and_serialize("audio 0 UDP/TLS/RTP/SAVPF 8"); + check_parse_and_serialize("audio 9/2 UDP/TLS/RTP/SAVPF 8"); + } + + #[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()); + } + + #[test] + fn test_media_vector_first_line_failure() { + let mut sdp_lines: Vec<SdpLine> = Vec::new(); + let line = SdpLine { + line_number: 0, + sdp_type: SdpType::Session("hello".to_string()), + text: "".to_owned(), + }; + sdp_lines.push(line); + assert!(parse_media_vector(&mut sdp_lines).is_err()); + } + + #[test] + fn test_media_vector_multiple_connections() { + let mut sdp_lines: Vec<SdpLine> = Vec::new(); + let media_line = SdpMediaLine { + media: SdpMediaValue::Audio, + port: 9, + port_count: 0, + proto: SdpProtocolValue::RtpSavpf, + formats: SdpFormatList::Integers(Vec::new()), + }; + let media = SdpLine { + line_number: 0, + sdp_type: SdpType::Media(media_line), + text: "".to_owned(), + }; + sdp_lines.push(media); + let c = SdpConnection { + address: ExplicitlyTypedAddress::try_from((AddressType::IpV4, "127.0.0.1")).unwrap(), + ttl: None, + amount: None, + }; + let c1 = SdpLine { + line_number: 1, + sdp_type: SdpType::Connection(c.clone()), + text: "".to_owned(), + }; + sdp_lines.push(c1); + let c2 = SdpLine { + line_number: 2, + sdp_type: SdpType::Connection(c), + text: "".to_owned(), + }; + sdp_lines.push(c2); + assert!(parse_media_vector(&mut sdp_lines).is_err()); + } + + #[test] + fn test_media_vector_invalid_types() { + let mut sdp_lines: Vec<SdpLine> = Vec::new(); + let media_line = SdpMediaLine { + media: SdpMediaValue::Audio, + port: 9, + port_count: 0, + proto: SdpProtocolValue::RtpSavpf, + formats: SdpFormatList::Integers(Vec::new()), + }; + let media = SdpLine { + line_number: 0, + sdp_type: SdpType::Media(media_line), + text: "".to_owned(), + }; + sdp_lines.push(media); + use SdpTiming; + let t = SdpTiming { start: 0, stop: 0 }; + let tline = SdpLine { + line_number: 1, + sdp_type: SdpType::Timing(t), + text: "".to_owned(), + }; + sdp_lines.push(tline); + assert!(parse_media_vector(&mut sdp_lines).is_err()); + } + + #[test] + fn test_media_vector_invalid_media_level_attribute() { + let mut sdp_lines: Vec<SdpLine> = Vec::new(); + let media_line = SdpMediaLine { + media: SdpMediaValue::Audio, + port: 9, + port_count: 0, + proto: SdpProtocolValue::RtpSavpf, + formats: SdpFormatList::Integers(Vec::new()), + }; + let media = SdpLine { + line_number: 0, + sdp_type: SdpType::Media(media_line), + text: "".to_owned(), + }; + sdp_lines.push(media); + let a = SdpAttribute::IceLite; + let aline = SdpLine { + line_number: 1, + sdp_type: SdpType::Attribute(a), + text: "".to_owned(), + }; + sdp_lines.push(aline); + assert!(parse_media_vector(&mut sdp_lines).is_err()); + } +} diff --git a/third_party/rust/webrtc-sdp/src/network.rs b/third_party/rust/webrtc-sdp/src/network.rs new file mode 100644 index 0000000000..512cb66d59 --- /dev/null +++ b/third_party/rust/webrtc-sdp/src/network.rs @@ -0,0 +1,66 @@ +/* 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/. */ + +use address::{Address, AddressType}; +use error::SdpParserInternalError; +use std::net::IpAddr; +use std::str::FromStr; + +pub fn ip_address_to_string(addr: IpAddr) -> String { + match addr { + IpAddr::V4(ipv4) => format!("IN IP4 {}", ipv4.to_string()), + IpAddr::V6(ipv6) => format!("IN IP6 {}", ipv6.to_string()), + } +} + +pub fn parse_network_type(value: &str) -> Result<(), SdpParserInternalError> { + if value.to_uppercase() != "IN" { + return Err(SdpParserInternalError::Generic( + "nettype must be IN".to_string(), + )); + }; + Ok(()) +} + +pub fn parse_address_type(value: &str) -> Result<AddressType, SdpParserInternalError> { + AddressType::from_str(value.to_uppercase().as_str()) + .map_err(|_| SdpParserInternalError::Generic("address type must be IP4 or IP6".to_string())) +} + +pub fn parse_unicast_address(value: &str) -> Result<Address, SdpParserInternalError> { + Ok(Address::from_str(value)?) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_parse_network_type() -> Result<(), SdpParserInternalError> { + parse_network_type("iN")?; + + assert!(parse_network_type("").is_err()); + assert!(parse_network_type("FOO").is_err()); + Ok(()) + } + + #[test] + fn test_parse_address_type() -> Result<(), SdpParserInternalError> { + let ip4 = parse_address_type("iP4")?; + assert_eq!(ip4, AddressType::IpV4); + let ip6 = parse_address_type("Ip6")?; + assert_eq!(ip6, AddressType::IpV6); + + assert!(parse_address_type("").is_err()); + assert!(parse_address_type("IP5").is_err()); + Ok(()) + } + + #[test] + fn test_parse_unicast_address() -> Result<(), SdpParserInternalError> { + parse_unicast_address("127.0.0.1")?; + parse_unicast_address("::1")?; + Ok(()) + } +} diff --git a/third_party/rust/webrtc-sdp/tests/unit_tests.rs b/third_party/rust/webrtc-sdp/tests/unit_tests.rs new file mode 100644 index 0000000000..f6785e7001 --- /dev/null +++ b/third_party/rust/webrtc-sdp/tests/unit_tests.rs @@ -0,0 +1,689 @@ +/* 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/. */ + +extern crate webrtc_sdp; + +#[cfg(test)] +fn check_parse_and_serialize(sdp_str: &str) { + let parsed_sdp = webrtc_sdp::parse_sdp(sdp_str, true); + assert!(parsed_sdp.is_ok()); + let serialized_sdp = parsed_sdp.unwrap().to_string(); + assert_eq!(serialized_sdp, sdp_str) +} + +#[test] +fn parse_minimal_sdp() { + let sdp_str = "v=0\r\n\ + o=- 1 1 IN IP4 0.0.0.0\r\n\ + s=-\r\n\ + t=0 0\r\n\ + c=IN IP4 0.0.0.0\r\n\ + m=audio 0 UDP/TLS/RTP/SAVPF 0\r\n"; + let sdp_res = webrtc_sdp::parse_sdp(sdp_str, 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.get_version(), 0); + let o = sdp.get_origin(); + assert_eq!(o.username, "-"); + assert_eq!(o.session_id, 1); + assert_eq!(o.session_version, 1); + assert_eq!(sdp.get_session(), &Some("-".to_owned())); + assert!(sdp.timing.is_some()); + assert!(sdp.get_connection().is_some()); + assert_eq!(sdp.attribute.len(), 0); + assert_eq!(sdp.media.len(), 1); + + let msection = &(sdp.media[0]); + assert_eq!( + *msection.get_type(), + webrtc_sdp::media_type::SdpMediaValue::Audio + ); + assert_eq!(msection.get_port(), 0); + assert_eq!(msection.get_port_count(), 0); + assert_eq!( + *msection.get_proto(), + webrtc_sdp::media_type::SdpProtocolValue::UdpTlsRtpSavpf + ); + assert!(msection.get_attributes().is_empty()); + assert!(msection.get_bandwidth().is_empty()); + assert!(msection.get_connection().is_none()); + + check_parse_and_serialize(sdp_str); +} + +#[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 +c=IN IP4 0.0.0.0\r\n +t=0 0\r\n +m=audio 0 UDP/TLS/RTP/SAVPF 0\r\n"; + let sdp_res = webrtc_sdp::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.get_version(), 0); + assert_eq!(sdp.get_session(), &Some("-".to_owned())); +} + +#[test] +fn parse_minimal_sdp_with_single_space_session() { + let sdp = "v=0\r\n +\r\n +o=- 0 0 IN IP4 0.0.0.0\r\n + \r\n +s= \r\n +c=IN IP4 0.0.0.0\r\n +t=0 0\r\n +m=audio 0 UDP/TLS/RTP/SAVPF 0\r\n"; + let sdp_res = webrtc_sdp::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.get_version(), 0); + assert_eq!(sdp.get_session(), &None); +} + +#[test] +fn parse_minimal_sdp_with_most_session_types() { + let sdp_str = "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\ + b=UNKNOWN:9\r\n\ + c=IN IP6 ::1/1/1\r\n\ + a=ice-options:trickle\r\n\ + m=audio 0 UDP/TLS/RTP/SAVPF 0\r\n"; + let sdp_res = webrtc_sdp::parse_sdp(sdp_str, 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, Some("-".to_owned())); + assert!(sdp.get_connection().is_some()); + + check_parse_and_serialize(sdp_str); +} + +#[test] +fn parse_minimal_sdp_with_most_media_types() { + let sdp_str = "v=0\r\n\ + o=- 0 0 IN IP4 0.0.0.0\r\n\ + s=-\r\n\ + t=0 0\r\n\ + m=video 0 UDP/TLS/RTP/SAVPF 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=sendrecv\r\n"; + let sdp_res = webrtc_sdp::parse_sdp(sdp_str, 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, Some("-".to_owned())); + assert_eq!(sdp.attribute.len(), 0); + assert_eq!(sdp.media.len(), 1); + + let msection = &(sdp.media[0]); + assert_eq!( + *msection.get_type(), + webrtc_sdp::media_type::SdpMediaValue::Video + ); + assert_eq!(msection.get_port(), 0); + assert_eq!( + *msection.get_proto(), + webrtc_sdp::media_type::SdpProtocolValue::UdpTlsRtpSavpf + ); + assert!(!msection.get_bandwidth().is_empty()); + assert!(!msection.get_connection().is_none()); + assert!(!msection.get_attributes().is_empty()); + assert!(msection + .get_attribute(webrtc_sdp::attribute_type::SdpAttributeType::Sendrecv) + .is_some()); + + check_parse_and_serialize(sdp_str); +} + +#[test] +fn parse_firefox_audio_offer() { + let sdp_str = "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 = webrtc_sdp::parse_sdp(sdp_str, 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(), + webrtc_sdp::media_type::SdpMediaValue::Audio + ); + assert_eq!(msection.get_port(), 9); + assert_eq!(msection.get_port_count(), 0); + assert_eq!( + *msection.get_proto(), + webrtc_sdp::media_type::SdpProtocolValue::UdpTlsRtpSavpf + ); + assert!(msection.get_connection().is_some()); + assert!(msection.get_bandwidth().is_empty()); + assert!(!msection.get_attributes().is_empty()); + assert!(msection + .get_attribute(webrtc_sdp::attribute_type::SdpAttributeType::Sendrecv) + .is_some()); + assert!(msection + .get_attribute(webrtc_sdp::attribute_type::SdpAttributeType::Extmap) + .is_some()); + assert!(msection + .get_attribute(webrtc_sdp::attribute_type::SdpAttributeType::Fmtp) + .is_some()); + assert!(msection + .get_attribute(webrtc_sdp::attribute_type::SdpAttributeType::IcePwd) + .is_some()); + assert!(msection + .get_attribute(webrtc_sdp::attribute_type::SdpAttributeType::IceUfrag) + .is_some()); + assert!(msection + .get_attribute(webrtc_sdp::attribute_type::SdpAttributeType::Mid) + .is_some()); + assert!(msection + .get_attribute(webrtc_sdp::attribute_type::SdpAttributeType::Mid) + .is_some()); + assert!(msection + .get_attribute(webrtc_sdp::attribute_type::SdpAttributeType::Msid) + .is_some()); + assert!(msection + .get_attribute(webrtc_sdp::attribute_type::SdpAttributeType::RtcpMux) + .is_some()); + assert_eq!( + msection + .get_attributes_of_type(webrtc_sdp::attribute_type::SdpAttributeType::Rtpmap) + .len(), + 4 + ); + assert!(msection + .get_attribute(webrtc_sdp::attribute_type::SdpAttributeType::Setup) + .is_some()); + assert!(msection + .get_attribute(webrtc_sdp::attribute_type::SdpAttributeType::Ssrc) + .is_some()); +} + +#[test] +fn parse_firefox_video_offer() { + let sdp_str = "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=extmap-allow-mixed\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=extmap-allow-mixed\r\n +a=ssrc:2709871439 cname:{735484ea-4f6c-f74a-bd66-7425f8476c2e}"; + let sdp_res = webrtc_sdp::parse_sdp(sdp_str, 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(), + webrtc_sdp::media_type::SdpMediaValue::Video + ); + assert_eq!(msection.get_port(), 9); + assert_eq!( + *msection.get_proto(), + webrtc_sdp::media_type::SdpProtocolValue::UdpTlsRtpSavpf + ); + assert!(msection.get_connection().is_some()); + assert!(msection.get_bandwidth().is_empty()); + assert!(!msection.get_attributes().is_empty()); + assert!(msection + .get_attribute(webrtc_sdp::attribute_type::SdpAttributeType::Recvonly) + .is_some()); + assert!(!msection + .get_attribute(webrtc_sdp::attribute_type::SdpAttributeType::Extmap) + .is_some()); + assert_eq!( + msection + .get_attributes_of_type(webrtc_sdp::attribute_type::SdpAttributeType::Fmtp) + .len(), + 3 + ); + assert!(msection + .get_attribute(webrtc_sdp::attribute_type::SdpAttributeType::IcePwd) + .is_some()); + assert!(msection + .get_attribute(webrtc_sdp::attribute_type::SdpAttributeType::IceUfrag) + .is_some()); + assert!(msection + .get_attribute(webrtc_sdp::attribute_type::SdpAttributeType::Mid) + .is_some()); + assert!(msection + .get_attribute(webrtc_sdp::attribute_type::SdpAttributeType::Mid) + .is_some()); + assert!(!msection + .get_attribute(webrtc_sdp::attribute_type::SdpAttributeType::Msid) + .is_some()); + assert_eq!( + msection + .get_attributes_of_type(webrtc_sdp::attribute_type::SdpAttributeType::Rtcpfb) + .len(), + 12 + ); + assert!(msection + .get_attribute(webrtc_sdp::attribute_type::SdpAttributeType::RtcpMux) + .is_some()); + assert_eq!( + msection + .get_attributes_of_type(webrtc_sdp::attribute_type::SdpAttributeType::Rtpmap) + .len(), + 3 + ); + assert!(msection + .get_attribute(webrtc_sdp::attribute_type::SdpAttributeType::Setup) + .is_some()); + assert!(msection + .get_attribute(webrtc_sdp::attribute_type::SdpAttributeType::Ssrc) + .is_some()); +} +#[test] +fn parse_firefox_datachannel_offer() { + let sdp_str = "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 = webrtc_sdp::parse_sdp(sdp_str, 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(), + webrtc_sdp::media_type::SdpMediaValue::Application + ); + assert_eq!(msection.get_port(), 49760); + assert_eq!( + *msection.get_proto(), + webrtc_sdp::media_type::SdpProtocolValue::DtlsSctp + ); + assert!(msection.get_connection().is_some()); + assert!(msection.get_bandwidth().is_empty()); + assert!(!msection.get_attributes().is_empty()); + assert!(msection + .get_attribute(webrtc_sdp::attribute_type::SdpAttributeType::Sendrecv) + .is_some()); + assert!(!msection + .get_attribute(webrtc_sdp::attribute_type::SdpAttributeType::Extmap) + .is_some()); + assert!(msection + .get_attribute(webrtc_sdp::attribute_type::SdpAttributeType::IcePwd) + .is_some()); + assert!(msection + .get_attribute(webrtc_sdp::attribute_type::SdpAttributeType::IceUfrag) + .is_some()); + assert!(msection + .get_attribute(webrtc_sdp::attribute_type::SdpAttributeType::EndOfCandidates) + .is_some()); + assert!(msection + .get_attribute(webrtc_sdp::attribute_type::SdpAttributeType::Mid) + .is_some()); + assert!(!msection + .get_attribute(webrtc_sdp::attribute_type::SdpAttributeType::Msid) + .is_some()); + assert!(!msection + .get_attribute(webrtc_sdp::attribute_type::SdpAttributeType::Rtcpfb) + .is_some()); + assert!(!msection + .get_attribute(webrtc_sdp::attribute_type::SdpAttributeType::RtcpMux) + .is_some()); + assert!(!msection + .get_attribute(webrtc_sdp::attribute_type::SdpAttributeType::Rtpmap) + .is_some()); + assert!(msection + .get_attribute(webrtc_sdp::attribute_type::SdpAttributeType::Sctpmap) + .is_some()); + assert!(msection + .get_attribute(webrtc_sdp::attribute_type::SdpAttributeType::Setup) + .is_some()); + assert!(msection + .get_attribute(webrtc_sdp::attribute_type::SdpAttributeType::Ssrc) + .is_some()); + + check_parse_and_serialize(sdp_str); +} + +#[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 = webrtc_sdp::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(), + webrtc_sdp::media_type::SdpMediaValue::Audio + ); + assert_eq!(msection1.get_port(), 9); + assert_eq!( + *msection1.get_proto(), + webrtc_sdp::media_type::SdpProtocolValue::UdpTlsRtpSavpf + ); + assert!(!msection1.get_attributes().is_empty()); + assert!(msection1.get_connection().is_some()); + assert!(msection1.get_bandwidth().is_empty()); + + let msection2 = &(sdp.media[1]); + assert_eq!( + *msection2.get_type(), + webrtc_sdp::media_type::SdpMediaValue::Video + ); + assert_eq!(msection2.get_port(), 9); + assert_eq!( + *msection2.get_proto(), + webrtc_sdp::media_type::SdpProtocolValue::UdpTlsRtpSavpf + ); + assert!(!msection2.get_attributes().is_empty()); + assert!(msection2.get_connection().is_some()); + assert!(msection2.get_bandwidth().is_empty()); +} + +#[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 = webrtc_sdp::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_str = "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 = webrtc_sdp::parse_sdp(sdp_str, 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_and_serialize_sdp_with_unusual_attributes() { + let sdp_str = "v=0\r\n\ + o=- 0 0 IN IP6 2001:db8::4444\r\n\ + s=-\r\n\ + t=0 0\r\n\ + a=ice-pacing:500\r\n\ + m=video 0 UDP/TLS/RTP/SAVPF 0\r\n\ + b=UNSUPPORTED:12345\r\n\ + c=IN IP6 ::1\r\n\ + a=rtcp:9 IN IP6 2001:db8::8888\r\n\ + a=rtcp-fb:* nack\r\n\ + a=extmap:1/recvonly urn:ietf:params:rtp-hdrext:toffset\r\n\ + a=extmap:2/sendonly urn:ietf:params:rtp-hdrext:toffset\r\n\ + a=extmap:3/sendrecv urn:ietf:params:rtp-hdrext:toffset\r\n\ + a=imageattr:* send [x=330,y=250,sar=[1.1,1.3,1.9],q=0.1] recv [x=800,y=[50,80,30],sar=1.1]\r\n\ + a=imageattr:97 send [x=[480:16:800],y=[100,200,300],par=[1.2-1.3],q=0.6] [x=1080,y=[144:176],sar=[0.5-0.7]] recv *\r\n\ + a=sendrecv\r\n"; + + check_parse_and_serialize(sdp_str); +} |