diff options
Diffstat (limited to 'third_party/rust/webrtc-sdp')
64 files changed, 10281 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..778f77c392 --- /dev/null +++ b/third_party/rust/webrtc-sdp/.cargo-checksum.json @@ -0,0 +1 @@ +{"files":{"CHANGELOG.md":"43327b4e426dcfc45fc4b5a6c3090064661033fbed4de24ba3fd15de414173fd","CODE_OF_CONDUCT.md":"ab482a6e8d9ed00cc77e0f90d823210e3368201ed98d9fda36033fd19aff24c4","Cargo.lock":"d7c87b5013450b940f537afc5d7cd8397aa93932228f26a4dc07df9d2553e9b3","Cargo.toml":"77e912d6089d0e49dbe83634246d0708040c49b57955aed943b712c728257001","LICENSE":"1f256ecad192880510e84ad60474eab7589218784b9a50bc7ceee34c2b91f1d5","README.md":"7d0c31ab88fb4b9eff849652f89b14940a83b836f10f11750957d73842fe25f6","examples/file_parser.rs":"f95b331d331feb3859a5092d4ed70389711c9f461359d23303aea6a4f8ca5ff6","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":"03e0fa68654f6b3c741d2dd6fee0c0607c888a86db4fcc4407527ade75cc5cd6","src/address_tests.rs":"341ab6d0ca557f44afee38b3ac0686376380c514791f515a3bb47a626b570146","src/anonymizer.rs":"8a1a7a4760739fd8a485c6fa239904f1e220a2b4d82560a236366f14f7cd09fa","src/anonymizer_tests.rs":"83010f8cc3eca078cb0ff56330e7209e4ea252cb1af1a08b6586114f313af048","src/attribute_type.rs":"911b861ce4d472ff8d141b179d1e11b33ef8a4bd25d3d68324f6705002c8a268","src/attribute_type_tests.rs":"2de570936176084ec2dd459372093b824332287edce7ad244ee84d0d94acb0de","src/error.rs":"e5126288a412c1c4af8682ab1524e9136e5d1dfe5fc769a13b0b5bd6b1f3c3b0","src/error_tests.rs":"3b2e62656d7e4ef0a6510308141c5eb5aaf0d2153c867e632bc4eeb4df81b9e0","src/lib.rs":"4ff763fcf29f822ae0ac6ace347b46958630055fd9c438dc43be05cc0d520fac","src/lib_tests.rs":"84529660be0e7fa0a27ed0d0fb296e8a781c1213bfb9dfa6308c207c66a5be6c","src/media_type.rs":"05130ef272827282033894b1e3d2c6784be8dcde42c74c397d0fb76fd8e2c40c","src/media_type_tests.rs":"b5924469c5637521042e86941871f5d297b35dda5909652c61b2954e8217bb06","src/network.rs":"2a4e31da138f3b167af00690b625be25355ed705197b57b0dc3832db118f0926","src/network_tests.rs":"305d6ac7a7e95e81f96541a6e83fdfa2ba9ca00398803946ecbc2cae24719a2c","tests/parse_sdp_tests.rs":"3996ee888e238bd4dff1271ed120644bb22ac9b7d14510257c35417223e562d3"},"package":"c7351fba122c7f6566779efdef49d2213e842f69fa1c654eef1fd9301f425064"}
\ 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..42a0cc5dc1 --- /dev/null +++ b/third_party/rust/webrtc-sdp/CHANGELOG.md @@ -0,0 +1,86 @@ +# Changelog +## [0.3.11] - 2024-01-17 +- Permit a wider set of payload type numbers +## [0.3.10] - 2023-01-05 +- Permit inconsistent simulcast directions +## [0.3.9] - 2022-01-12 +- Add support for RFC8858 rtcp-mux-only +- Correct seperation of tokens in FMTP parameters +- Do not emit an empty line after a media description +## [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..f133c1f3fe --- /dev/null +++ b/third_party/rust/webrtc-sdp/Cargo.lock @@ -0,0 +1,161 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "form_urlencoded" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "idna" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "itoa" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" + +[[package]] +name = "log" +version = "0.4.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" + +[[package]] +name = "percent-encoding" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" + +[[package]] +name = "proc-macro2" +version = "1.0.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fe8a65d69dd0808184ebb5f836ab526bb259db23c657efa38711b1072ee47f0" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "ryu" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" + +[[package]] +name = "serde" +version = "1.0.173" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91f70896d6720bc714a4a57d22fc91f1db634680e65c8efe13323f1fa38d53f" + +[[package]] +name = "serde_derive" +version = "1.0.173" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6250dde8342e0232232be9ca3db7aa40aceb5a3e5dd9bddbc00d99a007cde49" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d03b412469450d4404fe8499a268edd7f8b79fecb074b0d812ad64ca21f4031b" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "syn" +version = "2.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45c3457aacde3c65315de5031ec191ce46604304d2446e803d71ade03308d970" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "unicode-bidi" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" + +[[package]] +name = "unicode-ident" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" + +[[package]] +name = "unicode-normalization" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "url" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50bff7831e19200a85b17131d085c25d7811bc4e186efdaf54bbd132994a88cb" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "webrtc-sdp" +version = "0.3.11" +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..a84ed53bb5 --- /dev/null +++ b/third_party/rust/webrtc-sdp/Cargo.toml @@ -0,0 +1,65 @@ +# 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 are reading this file be aware that the original Cargo.toml +# will likely look very different (and much more reasonable). +# See Cargo.toml.orig for the original contents. + +[package] +name = "webrtc-sdp" +version = "0.3.11" +authors = [ + "Nicolas Grunbaum <na-g+github@nostrum.com>", + "Nils Ohlmeier <github@ohlmeier.org>", +] +description = "webrtc-sdp 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" + +[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..6c679ed3e2 --- /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.60.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..5997e8a770 --- /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..cc1087499a --- /dev/null +++ b/third_party/rust/webrtc-sdp/src/address.rs @@ -0,0 +1,199 @@ +/* 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)] +#[path = "./address_tests.rs"] +mod address_tests; diff --git a/third_party/rust/webrtc-sdp/src/address_tests.rs b/third_party/rust/webrtc-sdp/src/address_tests.rs new file mode 100644 index 0000000000..8c68fd278d --- /dev/null +++ b/third_party/rust/webrtc-sdp/src/address_tests.rs @@ -0,0 +1,73 @@ +/* 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 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..a3d4d50aa7 --- /dev/null +++ b/third_party/rust/webrtc-sdp/src/anonymizer.rs @@ -0,0 +1,198 @@ +/* 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)] +#[path = "./anonymizer_tests.rs"] +mod tests; diff --git a/third_party/rust/webrtc-sdp/src/anonymizer_tests.rs b/third_party/rust/webrtc-sdp/src/anonymizer_tests.rs new file mode 100644 index 0000000000..dede32a2c5 --- /dev/null +++ b/third_party/rust/webrtc-sdp/src/anonymizer_tests.rs @@ -0,0 +1,121 @@ +/* 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 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..7c667698b5 --- /dev/null +++ b/third_party/rust/webrtc-sdp/src/attribute_type.rs @@ -0,0 +1,3319 @@ +/* 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(|(name, 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(msg) => format!("client {msg}"), + SdpAttributeDtlsMessage::Server(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 { + // rtx + return write!(f, "{rtx}"); + } + if !self.dtmf_tones.is_empty() { + // telephone-event + return write!(f, "{}", self.dtmf_tones); + } else if !self.encodings.is_empty() { + // red encodings + return self + .encodings + .iter() + .map(ToString::to_string) + .collect::<Vec<String>>() + .join("/") + .fmt(f); + }; + write!( + f, + "{}", + 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), + maybe_vector_to_string!("{}", self.unknown_tokens, ",") + ] + .join(";") + ) + } +} + +#[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, PartialEq, Eq)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "enhanced_debug", derive(Debug))] +pub enum SdpAttributeFingerprintHashType { + Sha1, + Sha224, + Sha256, + Sha384, + Sha512, +} + +impl SdpAttributeFingerprintHashType { + pub fn try_from_name(name: &str) -> Result<Self, SdpParserInternalError> { + match name { + "sha-1" => Ok(Self::Sha1), + "sha-224" => Ok(Self::Sha224), + "sha-256" => Ok(Self::Sha256), + "sha-384" => Ok(Self::Sha384), + "sha-512" => Ok(Self::Sha512), + unknown => Err(SdpParserInternalError::Unsupported(format!( + "fingerprint contains an unsupported hash algorithm '{unknown}'" + ))), + } + } + pub fn octet_count(&self) -> usize { + match self { + Self::Sha1 => 20, + Self::Sha224 => 28, + Self::Sha256 => 32, + Self::Sha384 => 48, + Self::Sha512 => 64, + } + } + + pub fn parse_octets(&self, octets_string: &str) -> Result<Vec<u8>, SdpParserInternalError> { + let bytes = octets_string + .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() != self.octet_count() { + return Err(SdpParserInternalError::Generic(format!( + "fingerprint has {} bytes but should have {} bytes", + bytes.len(), + self.octet_count(), + ))); + } + + Ok(bytes) + } +} + +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, PartialEq, Eq)] +#[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 TryFrom<(SdpAttributeFingerprintHashType, Vec<u8>)> for SdpAttributeFingerprint { + type Error = SdpParserInternalError; + fn try_from( + parts: (SdpAttributeFingerprintHashType, Vec<u8>), + ) -> Result<Self, SdpParserInternalError> { + let (hash_algorithm, fingerprint) = parts; + match (hash_algorithm.octet_count(), fingerprint.len()) { + (a, b) if a == b => Ok(Self { + hash_algorithm, + fingerprint, + }), + (a, b) => Err(SdpParserInternalError::Generic(format!( + "Hash algoritm expects {a} fingerprint bytes not {b}", + ))), + } + } +} + +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!("{byte:02X}")) + .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(min, max) => write!(f, "[{min}-{max}]"), + SdpAttributeImageAttrSRange::DiscreteValues(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(':').is_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, + RtcpMuxOnly, // RFC8858 + 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::RtcpMuxOnly + | 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::RtcpMuxOnly + | 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-mux-only" + | "rtcp-rsize" | "sendonly" | "sendrecv" => { + return Err(SdpParserInternalError::Generic(format!( + "{name} attribute is not allowed to have a value", + ))); + } + _ => (), + } + } + 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-mux-only" => Ok(SdpAttribute::RtcpMuxOnly), + "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::RtcpMuxOnly => SdpAttributeType::RtcpMuxOnly.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, + RtcpMuxOnly, + 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::RtcpMuxOnly { .. } => SdpAttributeType::RtcpMuxOnly, + 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::RtcpMuxOnly => "rtcp-mux-only", + 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 '{unknown:?}' found" + ))); + } + }, + }; + + 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 {port} can only be a bit 16bit number" + ))); + } + 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('/').is_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 hash_algorithm = SdpAttributeFingerprintHashType::try_from_name(tokens[0])?; + let bytes = hash_algorithm.parse_octets(tokens[1])?; + let fingerprint = SdpAttributeFingerprint::try_from((hash_algorithm, bytes))?; + Ok(SdpAttribute::Fingerprint(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 '{param_name:}' must be 0 or 1" + ))), + } + }; + + 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 '{unknown:?}' found", + ))); + } + }, + }; + 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> { + 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 = tokens.next().map(|x| 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 {feedback_type} parameter: {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.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)] +#[path = "./attribute_type_tests.rs"] +mod tests; diff --git a/third_party/rust/webrtc-sdp/src/attribute_type_tests.rs b/third_party/rust/webrtc-sdp/src/attribute_type_tests.rs new file mode 100644 index 0000000000..077327a513 --- /dev/null +++ b/third_party/rust/webrtc-sdp/src/attribute_type_tests.rs @@ -0,0 +1,1102 @@ +/* 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 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"); + check_parse_and_serialize( + "fmtp:102 packetization-mode=1;sprop-parameter-sets=Z0LAFYyNQKD5APCIRqA=,aM48gA==", + ); +} + +#[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_mux_only() { + let check_parse = make_check_parse!(SdpAttribute::RtcpMuxOnly); + let check_parse_and_serialize = make_check_parse_and_serialize!(check_parse); + + check_parse_and_serialize("rtcp-mux-only"); + assert!(parse_attribute("rtcp-mux-only bar").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..c1cbc49a72 --- /dev/null +++ b/third_party/rust/webrtc-sdp/src/error.rs @@ -0,0 +1,194 @@ +/* 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 { error, line, .. } => { + state.serialize_field("type", "Line")?; + state.serialize_field("message", &format!("{error}"))?; + state.serialize_field("line", line)? + } + SdpParserError::Unsupported { error, line, .. } => { + state.serialize_field("type", "Unsupported")?; + state.serialize_field("message", &format!("{error}"))?; + state.serialize_field("line", line)? + } + SdpParserError::Sequence { 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 { + error, + line, + line_number, + } => write!(f, "Line error: {error} in line({line_number}): {line}"), + SdpParserError::Unsupported { + error, + line, + line_number, + } => write!(f, "Unsupported: {error} in line({line_number}): {line}",), + SdpParserError::Sequence { + message, + 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 { error, .. } | SdpParserError::Unsupported { 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)] +#[path = "./error_tests.rs"] +mod tests; diff --git a/third_party/rust/webrtc-sdp/src/error_tests.rs b/third_party/rust/webrtc-sdp/src/error_tests.rs new file mode 100644 index 0000000000..49f88f044d --- /dev/null +++ b/third_party/rust/webrtc-sdp/src/error_tests.rs @@ -0,0 +1,129 @@ +/* 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 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_some()); +} + +#[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_some()); +} + +#[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_some()); +} + +#[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..0a26adc3ea --- /dev/null +++ b/third_party/rust/webrtc-sdp/src/lib.rs @@ -0,0 +1,907 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#![warn(clippy::all)] +#![forbid(unsafe_code)] + +#[macro_use] +extern crate log; +#[cfg(feature = "serialize")] +#[macro_use] +extern crate serde_derive; +#[cfg(feature = "serialize")] +extern crate serde; +use std::convert::TryFrom; +use std::fmt; + +#[macro_use] +pub mod attribute_type; +pub mod address; +pub mod anonymizer; +pub mod error; +pub mod media_type; +pub mod network; + +use address::{AddressTyped, ExplicitlyTypedAddress}; +use anonymizer::{AnonymizingClone, StatefulSdpAnonymizer}; +use attribute_type::{ + parse_attribute, SdpAttribute, SdpAttributeRid, SdpAttributeSimulcastVersion, SdpAttributeType, + SdpSingleDirection, +}; +use error::{SdpParserError, SdpParserInternalError}; +use media_type::{ + parse_media, parse_media_vector, SdpFormatList, SdpMedia, SdpMediaLine, SdpMediaValue, + SdpProtocolValue, +}; +use network::{parse_address_type, parse_network_type}; + +/* + * RFC4566 + * bandwidth-fields = *(%x62 "=" bwtype ":" bandwidth CRLF) + */ +#[derive(Clone)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "enhanced_debug", derive(Debug))] +pub enum SdpBandwidth { + As(u32), + Ct(u32), + Tias(u32), + Unknown(String, u32), +} + +impl fmt::Display for SdpBandwidth { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let (tp_string, value) = match *self { + SdpBandwidth::As(ref x) => ("AS", x), + SdpBandwidth::Ct(ref x) => ("CT", x), + SdpBandwidth::Tias(ref x) => ("TIAS", x), + SdpBandwidth::Unknown(ref tp, ref x) => (&tp[..], x), + }; + write!(f, "{tp_string}:{value}") + } +} + +/* + * RFC4566 + * connection-field = [%x63 "=" nettype SP addrtype SP + * connection-address CRLF] + */ +#[derive(Clone)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "enhanced_debug", derive(Debug))] +pub struct SdpConnection { + pub address: ExplicitlyTypedAddress, + pub ttl: Option<u8>, + pub amount: Option<u32>, +} + +impl fmt::Display for SdpConnection { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.address.fmt(f)?; + write_option_string!(f, "/{}", self.ttl)?; + write_option_string!(f, "/{}", self.amount) + } +} + +impl AnonymizingClone for SdpConnection { + fn masked_clone(&self, anon: &mut StatefulSdpAnonymizer) -> Self { + let mut masked = self.clone(); + masked.address = anon.mask_typed_address(&self.address); + masked + } +} + +/* + * RFC4566 + * origin-field = %x6f "=" username SP sess-id SP sess-version SP + * nettype SP addrtype SP unicast-address CRLF + */ +#[derive(Clone)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "enhanced_debug", derive(Debug))] +pub struct SdpOrigin { + pub username: String, + pub session_id: u64, + pub session_version: u64, + pub unicast_addr: ExplicitlyTypedAddress, +} + +impl fmt::Display for SdpOrigin { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "{username} {sess_id} {sess_vers} {unicast_addr}", + username = self.username, + sess_id = self.session_id, + sess_vers = self.session_version, + unicast_addr = self.unicast_addr + ) + } +} + +impl AnonymizingClone for SdpOrigin { + fn masked_clone(&self, anon: &mut StatefulSdpAnonymizer) -> Self { + let mut masked = self.clone(); + masked.username = anon.mask_origin_user(&self.username); + masked.unicast_addr = anon.mask_typed_address(&masked.unicast_addr); + masked + } +} + +/* + * RFC4566 + * time-fields = 1*( %x74 "=" start-time SP stop-time + * *(CRLF repeat-fields) CRLF) + * [zone-adjustments CRLF] + */ +#[derive(Clone)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "enhanced_debug", derive(Debug))] +pub struct SdpTiming { + pub start: u64, + pub stop: u64, +} + +impl fmt::Display for SdpTiming { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{start} {stop}", start = self.start, stop = self.stop) + } +} + +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "enhanced_debug", derive(Debug))] +pub enum SdpType { + // Note: Email, Information, Key, Phone, Repeat, Uri and Zone are left out + // on purposes as we don't want to support them. + Attribute(SdpAttribute), + Bandwidth(SdpBandwidth), + Connection(SdpConnection), + Media(SdpMediaLine), + Origin(SdpOrigin), + Session(String), + Timing(SdpTiming), + Version(u64), +} + +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "enhanced_debug", derive(Debug))] +pub struct SdpLine { + pub line_number: usize, + pub sdp_type: SdpType, + pub text: String, +} + +/* + * RFC4566 + * ; SDP Syntax + * session-description = proto-version + * origin-field + * session-name-field + * information-field + * uri-field + * email-fields + * phone-fields + * connection-field + * bandwidth-fields + * time-fields + * key-field + * attribute-fields + * media-descriptions + */ +#[derive(Clone)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "enhanced_debug", derive(Debug))] +pub struct SdpSession { + pub version: u64, + pub origin: SdpOrigin, + pub session: Option<String>, + pub connection: Option<SdpConnection>, + pub bandwidth: Vec<SdpBandwidth>, + pub timing: Option<SdpTiming>, + pub attribute: Vec<SdpAttribute>, + pub media: Vec<SdpMedia>, + pub warnings: Vec<SdpParserError>, // unsupported values: + // information: Option<String>, + // uri: Option<String>, + // email: Option<String>, + // phone: Option<String>, + // repeat: Option<String>, + // zone: Option<String>, + // key: Option<String> +} + +impl fmt::Display for SdpSession { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "v={version}\r\n\ + o={origin}\r\n\ + s={session}\r\n\ + {timing}\ + {bandwidth}\ + {connection}\ + {session_attributes}\ + {media_sections}", + version = self.version, + origin = self.origin, + session = self.get_session_text(), + timing = option_to_string!("t={}\r\n", self.timing), + bandwidth = maybe_vector_to_string!("b={}\r\n", self.bandwidth, "\r\nb="), + connection = option_to_string!("c={}\r\n", self.connection), + session_attributes = maybe_vector_to_string!("a={}\r\n", self.attribute, "\r\na="), + media_sections = self.media.iter().map(|s| s.to_string()).collect::<String>(), + ) + } +} + +impl SdpSession { + pub fn new(version: u64, origin: SdpOrigin, session: String) -> SdpSession { + let session = match session.trim() { + s if !s.is_empty() => Some(s.to_owned()), + _ => None, + }; + SdpSession { + version, + origin, + session, + connection: None, + bandwidth: Vec::new(), + timing: None, + attribute: Vec::new(), + media: Vec::new(), + warnings: Vec::new(), + } + } + + pub fn get_version(&self) -> u64 { + self.version + } + + pub fn get_origin(&self) -> &SdpOrigin { + &self.origin + } + + pub fn get_session(&self) -> &Option<String> { + &self.session + } + + pub fn get_session_text(&self) -> &str { + if let Some(text) = &self.session { + text.as_str() + } else { + " " + } + } + pub fn get_connection(&self) -> &Option<SdpConnection> { + &self.connection + } + + pub fn set_connection(&mut self, c: SdpConnection) { + self.connection = Some(c) + } + + pub fn add_bandwidth(&mut self, b: SdpBandwidth) { + self.bandwidth.push(b) + } + + pub fn set_timing(&mut self, t: SdpTiming) { + self.timing = Some(t) + } + + pub fn add_attribute(&mut self, a: SdpAttribute) -> Result<(), SdpParserInternalError> { + if !a.allowed_at_session_level() { + return Err(SdpParserInternalError::Generic(format!( + "{a} not allowed at session level" + ))); + }; + self.attribute.push(a); + Ok(()) + } + + pub fn extend_media(&mut self, v: Vec<SdpMedia>) { + self.media.extend(v) + } + + pub fn parse_session_vector(&mut self, lines: &mut Vec<SdpLine>) -> Result<(), SdpParserError> { + while !lines.is_empty() { + let line = lines.remove(0); + match line.sdp_type { + SdpType::Attribute(a) => { + let _line_number = line.line_number; + self.add_attribute(a).map_err(|e: SdpParserInternalError| { + SdpParserError::Sequence { + message: format!("{e}"), + line_number: _line_number, + } + })? + } + SdpType::Bandwidth(b) => self.add_bandwidth(b), + SdpType::Timing(t) => self.set_timing(t), + SdpType::Connection(c) => self.set_connection(c), + + SdpType::Origin(_) | SdpType::Session(_) | SdpType::Version(_) => { + return Err(SdpParserError::Sequence { + message: "version, origin or session at wrong level".to_string(), + line_number: line.line_number, + }); + } + SdpType::Media(_) => { + return Err(SdpParserError::Sequence { + message: "media line not allowed in session parser".to_string(), + line_number: line.line_number, + }); + } + } + } + Ok(()) + } + + pub fn get_attribute(&self, t: SdpAttributeType) -> Option<&SdpAttribute> { + self.attribute + .iter() + .find(|a| SdpAttributeType::from(*a) == t) + } + + pub fn add_media( + &mut self, + media_type: SdpMediaValue, + direction: SdpAttribute, + port: u32, + protocol: SdpProtocolValue, + addr: ExplicitlyTypedAddress, + ) -> Result<(), SdpParserInternalError> { + let mut media = SdpMedia::new(SdpMediaLine { + media: media_type, + port, + port_count: 1, + proto: protocol, + formats: SdpFormatList::Integers(Vec::new()), + }); + + media.add_attribute(direction)?; + + media.set_connection(SdpConnection { + address: addr, + ttl: None, + amount: None, + }); + + self.media.push(media); + + Ok(()) + } +} + +impl AnonymizingClone for SdpSession { + fn masked_clone(&self, anon: &mut StatefulSdpAnonymizer) -> Self { + let mut masked: SdpSession = SdpSession { + version: self.version, + session: self.session.clone(), + origin: self.origin.masked_clone(anon), + connection: self.connection.clone(), + timing: self.timing.clone(), + bandwidth: self.bandwidth.clone(), + attribute: Vec::new(), + media: Vec::new(), + warnings: Vec::new(), + }; + masked.origin = self.origin.masked_clone(anon); + masked.connection = masked.connection.map(|con| con.masked_clone(anon)); + for i in &self.attribute { + masked.attribute.push(i.masked_clone(anon)); + } + masked + } +} + +/* removing this wrap would not allow us to call this from the match statement inside + * parse_sdp_line() */ +#[allow(clippy::unnecessary_wraps)] +fn parse_session(value: &str) -> Result<SdpType, SdpParserInternalError> { + trace!("session: {}", value); + Ok(SdpType::Session(String::from(value))) +} + +fn parse_version(value: &str) -> Result<SdpType, SdpParserInternalError> { + let ver = value.parse::<u64>()?; + if ver != 0 { + return Err(SdpParserInternalError::Generic(format!( + "version type contains unsupported value {ver}" + ))); + }; + trace!("version: {}", ver); + Ok(SdpType::Version(ver)) +} + +fn parse_origin(value: &str) -> Result<SdpType, SdpParserInternalError> { + let mut tokens = value.split_whitespace(); + let username = match tokens.next() { + None => { + return Err(SdpParserInternalError::Generic( + "Origin type is missing username token".to_string(), + )); + } + Some(x) => x, + }; + let session_id = match tokens.next() { + None => { + return Err(SdpParserInternalError::Generic( + "Origin type is missing session ID token".to_string(), + )); + } + Some(x) => x.parse::<u64>()?, + }; + let session_version = match tokens.next() { + None => { + return Err(SdpParserInternalError::Generic( + "Origin type is missing session version token".to_string(), + )); + } + Some(x) => x.parse::<u64>()?, + }; + match tokens.next() { + None => { + return Err(SdpParserInternalError::Generic( + "Origin type is missing network type token".to_string(), + )); + } + Some(x) => parse_network_type(x)?, + }; + let addrtype = match tokens.next() { + None => { + return Err(SdpParserInternalError::Generic( + "Origin type is missing address type token".to_string(), + )); + } + Some(x) => parse_address_type(x)?, + }; + let unicast_addr = match tokens.next() { + None => { + return Err(SdpParserInternalError::Generic( + "Origin type is missing IP address token".to_string(), + )); + } + Some(x) => ExplicitlyTypedAddress::try_from((addrtype, x))?, + }; + if addrtype != unicast_addr.address_type() { + return Err(SdpParserInternalError::Generic( + "Origin addrtype does not match address.".to_string(), + )); + } + let o = SdpOrigin { + username: String::from(username), + session_id, + session_version, + unicast_addr, + }; + trace!("origin: {}", o); + Ok(SdpType::Origin(o)) +} + +fn parse_connection(value: &str) -> Result<SdpType, SdpParserInternalError> { + let cv: Vec<&str> = value.split_whitespace().collect(); + if cv.len() != 3 { + return Err(SdpParserInternalError::Generic( + "connection attribute must have three tokens".to_string(), + )); + } + parse_network_type(cv[0])?; + let addrtype = parse_address_type(cv[1])?; + let mut ttl = None; + let mut amount = None; + let mut addr_token = cv[2]; + if addr_token.find('/').is_some() { + let addr_tokens: Vec<&str> = addr_token.split('/').collect(); + if addr_tokens.len() >= 3 { + amount = Some(addr_tokens[2].parse::<u32>()?); + } + ttl = Some(addr_tokens[1].parse::<u8>()?); + addr_token = addr_tokens[0]; + } + let address = ExplicitlyTypedAddress::try_from((addrtype, addr_token))?; + let c = SdpConnection { + address, + ttl, + amount, + }; + trace!("connection: {}", c); + Ok(SdpType::Connection(c)) +} + +fn parse_bandwidth(value: &str) -> Result<SdpType, SdpParserInternalError> { + let bv: Vec<&str> = value.split(':').collect(); + if bv.len() != 2 { + return Err(SdpParserInternalError::Generic( + "bandwidth attribute must have two tokens".to_string(), + )); + } + let bandwidth = bv[1].parse::<u32>()?; + let bw = match bv[0].to_uppercase().as_ref() { + "AS" => SdpBandwidth::As(bandwidth), + "CT" => SdpBandwidth::Ct(bandwidth), + "TIAS" => SdpBandwidth::Tias(bandwidth), + _ => SdpBandwidth::Unknown(String::from(bv[0]), bandwidth), + }; + trace!("bandwidth: {}", bw); + Ok(SdpType::Bandwidth(bw)) +} + +fn parse_timing(value: &str) -> Result<SdpType, SdpParserInternalError> { + let tv: Vec<&str> = value.split_whitespace().collect(); + if tv.len() != 2 { + return Err(SdpParserInternalError::Generic( + "timing attribute must have two tokens".to_string(), + )); + } + let start = tv[0].parse::<u64>()?; + let stop = tv[1].parse::<u64>()?; + let t = SdpTiming { start, stop }; + trace!("timing: {}", t); + Ok(SdpType::Timing(t)) +} + +pub fn parse_sdp_line(line: &str, line_number: usize) -> Result<SdpLine, SdpParserError> { + if line.find('=').is_none() { + return Err(SdpParserError::Line { + error: SdpParserInternalError::Generic("missing = character in line".to_string()), + line: line.to_string(), + line_number, + }); + } + let mut splitted_line = line.splitn(2, '='); + let line_type = match splitted_line.next() { + None => { + return Err(SdpParserError::Line { + error: SdpParserInternalError::Generic("missing type".to_string()), + line: line.to_string(), + line_number, + }); + } + Some(t) => { + let trimmed = t.trim(); + if trimmed.len() > 1 { + return Err(SdpParserError::Line { + error: SdpParserInternalError::Generic("type too long".to_string()), + line: line.to_string(), + line_number, + }); + } + if trimmed.is_empty() { + return Err(SdpParserError::Line { + error: SdpParserInternalError::Generic("type is empty".to_string()), + line: line.to_string(), + line_number, + }); + } + trimmed.to_lowercase() + } + }; + let (line_value, untrimmed_line_value) = match splitted_line.next() { + None => { + return Err(SdpParserError::Line { + error: SdpParserInternalError::Generic("missing value".to_string()), + line: line.to_string(), + line_number, + }); + } + Some(v) => { + let trimmed = v.trim(); + // For compatibility with sites that don't adhere to "s=-" for no session ID + if trimmed.is_empty() && line_type.as_str() != "s" { + return Err(SdpParserError::Line { + error: SdpParserInternalError::Generic("value is empty".to_string()), + line: line.to_string(), + line_number, + }); + } + (trimmed, v) + } + }; + match line_type.as_ref() { + "a" => parse_attribute(line_value), + "b" => parse_bandwidth(line_value), + "c" => parse_connection(line_value), + "e" => Err(SdpParserInternalError::Generic(format!( + "unsupported type email: {line_value}" + ))), + "i" => Err(SdpParserInternalError::Generic(format!( + "unsupported type information: {line_value}" + ))), + "k" => Err(SdpParserInternalError::Generic(format!( + "unsupported insecure key exchange: {line_value}" + ))), + "m" => parse_media(line_value), + "o" => parse_origin(line_value), + "p" => Err(SdpParserInternalError::Generic(format!( + "unsupported type phone: {line_value}" + ))), + "r" => Err(SdpParserInternalError::Generic(format!( + "unsupported type repeat: {line_value}" + ))), + "s" => parse_session(untrimmed_line_value), + "t" => parse_timing(line_value), + "u" => Err(SdpParserInternalError::Generic(format!( + "unsupported type uri: {line_value}" + ))), + "v" => parse_version(line_value), + "z" => Err(SdpParserInternalError::Generic(format!( + "unsupported type zone: {line_value}" + ))), + _ => Err(SdpParserInternalError::Generic( + "unknown sdp type".to_string(), + )), + } + .map(|sdp_type| SdpLine { + line_number, + sdp_type, + text: line.to_owned(), + }) + .map_err(|e| match e { + SdpParserInternalError::UnknownAddressType(..) + | SdpParserInternalError::AddressTypeMismatch { .. } + | SdpParserInternalError::Generic(..) + | SdpParserInternalError::Integer(..) + | SdpParserInternalError::Float(..) + | SdpParserInternalError::Domain(..) + | SdpParserInternalError::IpAddress(..) => SdpParserError::Line { + error: e, + line: line.to_string(), + line_number, + }, + SdpParserInternalError::Unsupported(..) => SdpParserError::Unsupported { + error: e, + line: line.to_string(), + line_number, + }, + }) +} + +fn sanity_check_sdp_session(session: &SdpSession) -> Result<(), SdpParserError> { + let make_seq_error = |x: &str| SdpParserError::Sequence { + message: x.to_string(), + line_number: 0, + }; + + if session.timing.is_none() { + return Err(make_seq_error("Missing timing type at session level")); + } + // Checks that all media have connections if there is no top level + // This explicitly allows for zero connection lines if there are no media + // sections for interoperability reasons. + let media_cons = &session.media.iter().all(|m| m.get_connection().is_some()); + if !media_cons && session.get_connection().is_none() { + return Err(make_seq_error( + "Without connection type at session level all media sections must have connection types", + )); + } + + // Check that extmaps are not defined on session and media level + if session.get_attribute(SdpAttributeType::Extmap).is_some() { + for msection in &session.media { + if msection.get_attribute(SdpAttributeType::Extmap).is_some() { + return Err(make_seq_error( + "Extmap can't be define at session and media level", + )); + } + } + } + + for msection in &session.media { + if msection + .get_attribute(SdpAttributeType::RtcpMuxOnly) + .is_some() + && msection.get_attribute(SdpAttributeType::RtcpMux).is_none() + { + return Err(make_seq_error( + "rtcp-mux-only media sections must also contain the rtcp-mux attribute", + )); + } + + let rids: Vec<&SdpAttributeRid> = msection + .get_attributes() + .iter() + .filter_map(|attr| match *attr { + SdpAttribute::Rid(ref rid) => Some(rid), + _ => None, + }) + .collect(); + let recv_rids: Vec<&str> = rids + .iter() + .filter_map(|rid| match rid.direction { + SdpSingleDirection::Recv => Some(rid.id.as_str()), + _ => None, + }) + .collect(); + let send_rids: Vec<&str> = rids + .iter() + .filter_map(|rid| match rid.direction { + SdpSingleDirection::Send => Some(rid.id.as_str()), + _ => None, + }) + .collect(); + + for rid_format in rids.iter().flat_map(|rid| &rid.formats) { + match *msection.get_formats() { + SdpFormatList::Integers(ref int_fmt) => { + if !int_fmt.contains(&(u32::from(*rid_format))) { + return Err(make_seq_error( + "Rid pts must be declared in the media section", + )); + } + } + SdpFormatList::Strings(ref str_fmt) => { + if !str_fmt.contains(&rid_format.to_string()) { + return Err(make_seq_error( + "Rid pts must be declared in the media section", + )); + } + } + } + } + + if let Some(SdpAttribute::Simulcast(simulcast)) = + msection.get_attribute(SdpAttributeType::Simulcast) + { + let check_defined_rids = + |simulcast_version_list: &Vec<SdpAttributeSimulcastVersion>, + rid_ids: &[&str]| + -> Result<(), SdpParserError> { + for simulcast_rid in simulcast_version_list.iter().flat_map(|x| &x.ids) { + if !rid_ids.contains(&simulcast_rid.id.as_str()) { + return Err(make_seq_error( + "Simulcast RIDs must be defined in any rid attribute", + )); + } + } + Ok(()) + }; + + check_defined_rids(&simulcast.receive, &recv_rids)?; + check_defined_rids(&simulcast.send, &send_rids)?; + } + } + + Ok(()) +} + +fn parse_sdp_vector(lines: &mut Vec<SdpLine>) -> Result<SdpSession, SdpParserError> { + if lines.len() < 4 { + return Err(SdpParserError::Sequence { + message: "SDP neeeds at least 4 lines".to_string(), + line_number: 0, + }); + } + + let version = match lines.remove(0).sdp_type { + SdpType::Version(v) => v, + _ => { + return Err(SdpParserError::Sequence { + message: "first line needs to be version number".to_string(), + line_number: 0, + }); + } + }; + let origin = match lines.remove(0).sdp_type { + SdpType::Origin(v) => v, + _ => { + return Err(SdpParserError::Sequence { + message: "second line needs to be origin".to_string(), + line_number: 1, + }); + } + }; + let session = match lines.remove(0).sdp_type { + SdpType::Session(v) => v, + _ => { + return Err(SdpParserError::Sequence { + message: "third line needs to be session".to_string(), + line_number: 2, + }); + } + }; + let mut sdp_session = SdpSession::new(version, origin, session); + + let _media_pos = lines + .iter() + .position(|l| matches!(l.sdp_type, SdpType::Media(_))); + + match _media_pos { + Some(p) => { + let mut media: Vec<_> = lines.drain(p..).collect(); + sdp_session.parse_session_vector(lines)?; + sdp_session.extend_media(parse_media_vector(&mut media)?); + } + None => sdp_session.parse_session_vector(lines)?, + }; + + sanity_check_sdp_session(&sdp_session)?; + Ok(sdp_session) +} + +pub fn parse_sdp(sdp: &str, fail_on_warning: bool) -> Result<SdpSession, SdpParserError> { + if sdp.is_empty() { + return Err(SdpParserError::Line { + error: SdpParserInternalError::Generic("empty SDP".to_string()), + line: sdp.to_string(), + line_number: 0, + }); + } + // see test_parse_sdp_minimal_sdp_successfully + if sdp.len() < 51 { + return Err(SdpParserError::Line { + error: SdpParserInternalError::Generic("string too short to be valid SDP".to_string()), + line: sdp.to_string(), + line_number: 0, + }); + } + let lines = sdp.lines(); + let mut errors: Vec<SdpParserError> = Vec::new(); + let mut warnings: Vec<SdpParserError> = Vec::new(); + let mut sdp_lines: Vec<SdpLine> = Vec::new(); + for (line_number, line) in lines.enumerate() { + let stripped_line = line.trim(); + if stripped_line.is_empty() { + continue; + } + match parse_sdp_line(line, line_number) { + Ok(n) => { + sdp_lines.push(n); + } + Err(e) => { + match e { + // TODO is this really a good way to accomplish this? + SdpParserError::Line { + error, + line, + line_number, + } => errors.push(SdpParserError::Line { + error, + line, + line_number, + }), + SdpParserError::Unsupported { + error, + line, + line_number, + } => { + warnings.push(SdpParserError::Unsupported { + error, + line, + line_number, + }); + } + SdpParserError::Sequence { + message, + line_number, + } => errors.push(SdpParserError::Sequence { + message, + line_number, + }), + } + } + }; + } + + if fail_on_warning && (!warnings.is_empty()) { + return Err(warnings.remove(0)); + } + + // We just return the last of the errors here + if let Some(e) = errors.pop() { + return Err(e); + }; + + let mut session = parse_sdp_vector(&mut sdp_lines)?; + session.warnings = warnings; + + for warning in &session.warnings { + warn!("Warning: {}", &warning); + } + + Ok(session) +} + +#[cfg(test)] +#[path = "./lib_tests.rs"] +mod tests; diff --git a/third_party/rust/webrtc-sdp/src/lib_tests.rs b/third_party/rust/webrtc-sdp/src/lib_tests.rs new file mode 100644 index 0000000000..3b20fc9fb8 --- /dev/null +++ b/third_party/rust/webrtc-sdp/src/lib_tests.rs @@ -0,0 +1,774 @@ +/* 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 super::*; +use address::{Address, AddressType}; +use anonymizer::ToBytesVec; +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 +} + +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) +} + +#[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![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![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![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![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![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![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_with_missing_rtcp_mux() -> Result<(), SdpParserError> { + let mut lines: Vec<SdpLine> = vec![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)?); + lines.push(parse_sdp_line("a=rtcp-mux-only", 1)?); + assert!(parse_sdp_vector(&mut lines).is_err()); + Ok(()) +} + +#[test] +fn test_parse_sdp_vector_too_short() -> Result<(), SdpParserError> { + let mut lines: Vec<SdpLine> = vec![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![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![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![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() { + 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()); +} + +#[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..91a11c6039 --- /dev/null +++ b/third_party/rust/webrtc-sdp/src/media_type.rs @@ -0,0 +1,480 @@ +/* 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!( + "{attr} not allowed at media level" + ))); + } + 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>()?; + if matches!(fmt_num, 1 | 2 | 19 | 64..=95 | 128 .. ) { + 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)] +#[path = "./media_type_tests.rs"] +mod media_type_tests; diff --git a/third_party/rust/webrtc-sdp/src/media_type_tests.rs b/third_party/rust/webrtc-sdp/src/media_type_tests.rs new file mode 100644 index 0000000000..5e25cddb88 --- /dev/null +++ b/third_party/rust/webrtc-sdp/src/media_type_tests.rs @@ -0,0 +1,411 @@ +/* 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 super::*; +use address::{AddressType, ExplicitlyTypedAddress}; +use attribute_type::{ + SdpAttributeFmtp, SdpAttributeFmtpParameters, SdpAttributePayloadType, SdpAttributeRtcpFb, + SdpAttributeRtcpFbType, +}; +use std::convert::TryFrom; + +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) +} + +// 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..87e44052fa --- /dev/null +++ b/third_party/rust/webrtc-sdp/src/network.rs @@ -0,0 +1,37 @@ +/* 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}"), + IpAddr::V6(ipv6) => format!("IN IP6 {ipv6}"), + } +} + +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> { + Address::from_str(value) +} + +#[cfg(test)] +#[path = "./network_tests.rs"] +mod tests; diff --git a/third_party/rust/webrtc-sdp/src/network_tests.rs b/third_party/rust/webrtc-sdp/src/network_tests.rs new file mode 100644 index 0000000000..4380894899 --- /dev/null +++ b/third_party/rust/webrtc-sdp/src/network_tests.rs @@ -0,0 +1,33 @@ +/* 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 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/parse_sdp_tests.rs b/third_party/rust/webrtc-sdp/tests/parse_sdp_tests.rs new file mode 100644 index 0000000000..1ee33e2beb --- /dev/null +++ b/third_party/rust/webrtc-sdp/tests/parse_sdp_tests.rs @@ -0,0 +1,783 @@ +/* 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_none()); + 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_none()); + 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_none()); + 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_none()); + assert!(msection + .get_attribute(webrtc_sdp::attribute_type::SdpAttributeType::Rtcpfb) + .is_none()); + assert!(msection + .get_attribute(webrtc_sdp::attribute_type::SdpAttributeType::RtcpMux) + .is_none()); + assert!(msection + .get_attribute(webrtc_sdp::attribute_type::SdpAttributeType::Rtpmap) + .is_none()); + 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); +} + +#[test] +fn serialize_complex_sdp_and_validate_no_empty_lines() { + 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=rtcp-mux-only\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 parsed_sdp = webrtc_sdp::parse_sdp(sdp, true).unwrap(); + assert!(!parsed_sdp.to_string().contains("\r\n\r\n")); +} |