├── .github ├── FUNDING.yml ├── actions-rs │ └── grcov.yml └── workflows │ ├── cargo.yml │ ├── grcov.yml │ └── tests.yml ├── .gitignore ├── Cargo.toml ├── Dockerfile ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── codecov.yml ├── docs └── sfu-rs.jpg ├── examples ├── async_chat.rs ├── async_signal │ └── mod.rs ├── chat.html ├── sync_chat.rs ├── sync_signal │ └── mod.rs └── util │ ├── cer.pem │ ├── key.pem │ └── mod.rs ├── scripts └── parse_test_results.sh ├── src ├── configs │ ├── media_config.rs │ ├── mod.rs │ ├── server_config.rs │ └── session_config.rs ├── description │ ├── fmtp │ │ ├── generic.rs │ │ ├── h264.rs │ │ └── mod.rs │ ├── mod.rs │ ├── rtp_codec.rs │ ├── rtp_transceiver.rs │ ├── rtp_transceiver_direction.rs │ └── sdp_type.rs ├── endpoint │ ├── candidate.rs │ ├── mod.rs │ └── transport.rs ├── handlers │ ├── datachannel.rs │ ├── demuxer.rs │ ├── dtls.rs │ ├── exception.rs │ ├── gateway.rs │ ├── interceptor.rs │ ├── mod.rs │ ├── sctp.rs │ ├── srtp.rs │ └── stun.rs ├── interceptors │ ├── mod.rs │ ├── nack │ │ └── mod.rs │ ├── report │ │ ├── mod.rs │ │ ├── receiver_report.rs │ │ ├── receiver_stream.rs │ │ └── sender_report.rs │ └── twcc │ │ └── mod.rs ├── lib.rs ├── messages.rs ├── metrics │ └── mod.rs ├── server │ ├── certificate.rs │ ├── mod.rs │ └── states.rs ├── session │ └── mod.rs └── types.rs └── tests ├── common └── mod.rs ├── data_channel_test.rs └── rtp_test.rs /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: webrtc-rs # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: WebRTCrs # Replace with a single Patreon username 5 | open_collective: webrtc-rs # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.github/actions-rs/grcov.yml: -------------------------------------------------------------------------------- 1 | branch: true 2 | ignore-not-existing: true 3 | llvm: true 4 | filter: covered 5 | output-type: lcov 6 | output-path: ./lcov.info 7 | source-dir: . 8 | ignore: 9 | - "/*" 10 | - "C:/*" 11 | - "../*" 12 | excl-line: "#\\[derive\\(" 13 | excl-start: "mod tests \\{" 14 | excl-br-line: "#\\[derive\\(" 15 | excl-br-start: "mod tests \\{" -------------------------------------------------------------------------------- /.github/workflows/cargo.yml: -------------------------------------------------------------------------------- 1 | name: cargo 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | concurrency: 10 | group: ${{ github.workflow }}-${{ github.ref }} 11 | cancel-in-progress: true 12 | 13 | env: 14 | CARGO_TERM_COLOR: always 15 | 16 | jobs: 17 | build: 18 | name: Build and test 19 | strategy: 20 | matrix: 21 | os: [ 'ubuntu-latest', 'macos-latest' ] #, 'windows-latest' 22 | runs-on: ${{ matrix.os }} 23 | steps: 24 | - uses: actions/checkout@v2 25 | - name: Build 26 | run: cargo build --verbose 27 | - name: Run tests 28 | run: cargo test --verbose --lib 29 | 30 | rustfmt_and_clippy: 31 | name: Check rustfmt style && run clippy 32 | runs-on: ubuntu-latest 33 | steps: 34 | - uses: actions/checkout@v2 35 | - uses: actions-rs/toolchain@v1 36 | with: 37 | toolchain: stable 38 | profile: minimal 39 | components: clippy, rustfmt 40 | override: true 41 | - name: Cache cargo registry 42 | uses: actions/cache@v1 43 | with: 44 | path: ~/.cargo/registry 45 | key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }} 46 | - name: Run clippy 47 | uses: actions-rs/cargo@v1 48 | with: 49 | command: clippy 50 | - name: Check formating 51 | uses: actions-rs/cargo@v1 52 | with: 53 | command: fmt 54 | args: --all -- --check 55 | -------------------------------------------------------------------------------- /.github/workflows/grcov.yml: -------------------------------------------------------------------------------- 1 | name: coverage 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | concurrency: 10 | group: ${{ github.workflow }}-${{ github.ref }} 11 | cancel-in-progress: true 12 | 13 | env: 14 | CARGO_TERM_COLOR: always 15 | 16 | jobs: 17 | grcov: 18 | name: Coverage 19 | runs-on: ${{ matrix.os }} 20 | strategy: 21 | matrix: 22 | os: 23 | - ubuntu-latest 24 | toolchain: 25 | - nightly 26 | cargo_flags: 27 | - "--all-features" 28 | steps: 29 | - name: Checkout source code 30 | uses: actions/checkout@v2 31 | 32 | - name: Install Rust 33 | uses: actions-rs/toolchain@v1 34 | with: 35 | profile: minimal 36 | toolchain: ${{ matrix.toolchain }} 37 | override: true 38 | 39 | - name: Install grcov 40 | uses: actions-rs/install@v0.1 41 | with: 42 | crate: grcov 43 | version: latest 44 | use-tool-cache: true 45 | 46 | - name: Test 47 | uses: actions-rs/cargo@v1 48 | with: 49 | command: test 50 | args: --lib --no-fail-fast ${{ matrix.cargo_flags }} 51 | env: 52 | CARGO_INCREMENTAL: "0" 53 | RUSTFLAGS: '-Ccodegen-units=1 -Copt-level=0 -Clink-dead-code -Coverflow-checks=off -Zpanic_abort_tests -Cpanic=abort -Cdebug-assertions=off -Cprofile-generate=target/debug' 54 | RUSTDOCFLAGS: '-Ccodegen-units=1 -Copt-level=0 -Clink-dead-code -Coverflow-checks=off -Zpanic_abort_tests -Cpanic=abort -Cdebug-assertions=off -Cprofile-generate=target/debug' 55 | 56 | - name: Generate coverage data 57 | id: grcov 58 | # uses: actions-rs/grcov@v0.1 59 | run: | 60 | grcov target/debug/ \ 61 | --branch \ 62 | --llvm \ 63 | --source-dir . \ 64 | --output-path lcov.info \ 65 | --ignore='/**' \ 66 | --ignore='C:/**' \ 67 | --ignore='../**' \ 68 | --ignore-not-existing \ 69 | --excl-line "#\\[derive\\(" \ 70 | --excl-br-line "#\\[derive\\(" \ 71 | --excl-start "#\\[cfg\\(test\\)\\]" \ 72 | --excl-br-start "#\\[cfg\\(test\\)\\]" \ 73 | --commit-sha ${{ github.sha }} \ 74 | --service-job-id ${{ github.job }} \ 75 | --service-name "GitHub Actions" \ 76 | --service-number ${{ github.run_id }} 77 | - name: Upload coverage as artifact 78 | uses: actions/upload-artifact@v4 79 | with: 80 | name: lcov.info 81 | # path: ${{ steps.grcov.outputs.report }} 82 | path: lcov.info 83 | 84 | - name: Upload coverage to codecov.io 85 | uses: codecov/codecov-action@v1 86 | with: 87 | # file: ${{ steps.grcov.outputs.report }} 88 | file: lcov.info 89 | fail_ci_if_error: true 90 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: tests 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | concurrency: 10 | group: ${{ github.workflow }}-${{ github.ref }} 11 | cancel-in-progress: true 12 | 13 | env: 14 | CARGO_TERM_COLOR: always 15 | 16 | jobs: 17 | tests: 18 | name: Integration Tests 19 | runs-on: ubuntu-latest 20 | steps: 21 | - name: Checkout code 22 | uses: actions/checkout@v2 23 | 24 | - name: Build and run chat server and tests in Docker 25 | run: | 26 | docker build -t chat-server . 27 | docker run -d --name chat-server -p 8080:8080 -p 3478-3495:3478-3495/udp chat-server 28 | 29 | - name: Wait for docker container to exit 30 | run: | 31 | while docker ps | grep chat-server >/dev/null; do 32 | echo "chat-server is still running, waiting..." 33 | sleep 5 34 | done 35 | 36 | - name: Copy logs from container to host 37 | run: | 38 | docker cp chat-server:/usr/src/app/logs/. . 39 | 40 | - name: Upload logs as artifact 41 | uses: actions/upload-artifact@v4 42 | with: 43 | name: logs 44 | path: | 45 | sfu.log 46 | test.log 47 | 48 | - name: Shutdown chat server 49 | run: docker stop chat-server && docker rm chat-server 50 | 51 | - name: Parse test results 52 | run: ./scripts/parse_test_results.sh test.log 53 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | .idea/ 5 | 6 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 7 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 8 | Cargo.lock 9 | 10 | # These are backup files generated by rustfmt 11 | **/*.rs.bk 12 | 13 | 14 | # Added by cargo 15 | # 16 | # already existing elements were commented out 17 | 18 | /target 19 | #Cargo.lock 20 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sfu" 3 | version = "0.0.3" 4 | authors = ["Rusty Rain "] 5 | edition = "2021" 6 | description = "WebRTC Selective Forwarding Unit (SFU) in Rust with Sans-IO" 7 | license = "MIT/Apache-2.0" 8 | documentation = "https://docs.rs/sfu" 9 | repository = "https://github.com/webrtc-rs/sfu" 10 | homepage = "https://sfu.rs" 11 | keywords = ["networking", "protocols", "webrtc", "sans-io", "sfu"] 12 | categories = ["network-programming", "asynchronous", "multimedia"] 13 | 14 | [dependencies] 15 | retty = "0.27.0" 16 | bytes = "1.5" 17 | log = "0.4" 18 | base64 = "0.22" 19 | serde = "1" 20 | serde_json = { version = "1", features = [] } 21 | rand = "0.8" 22 | rcgen = { version = "0.12", features = ["pem", "x509-parser"] } 23 | ring = "0.17" 24 | sha2 = "0.10" 25 | rustls = "0.21" 26 | url = { version = "2", features = [] } 27 | hex = { version = "0.4", features = [] } 28 | opentelemetry = { version = "0.22.0", features = ["metrics"] } 29 | 30 | # RTC protocols 31 | shared = { version = "0.1.1", package = "rtc-shared" } 32 | sdp = { version = "0.1.1", package = "rtc-sdp" } 33 | stun = { version = "0.1.1", package = "rtc-stun" } 34 | rtp = { version = "0.1", package = "rtc-rtp" } 35 | rtcp = { version = "0.1", package = "rtc-rtcp" } 36 | srtp = { version = "0.1.1", package = "rtc-srtp" } 37 | dtls = { version = "0.1.1", package = "rtc-dtls" } 38 | sctp = { version = "0.1.1", package = "rtc-sctp" } 39 | datachannel = { version = "0.1", package = "rtc-datachannel" } 40 | 41 | [dev-dependencies] 42 | # common 43 | chrono = "0.4.34" 44 | env_logger = "0.11" 45 | clap = { version = "4.5", features = ["derive"] } 46 | anyhow = "1" 47 | rouille = { version = "3.6", features = ["ssl"] } 48 | systemstat = "0.2" 49 | opentelemetry_sdk = { version = "0.22.1", features = ["metrics", "rt-tokio-current-thread"] } 50 | opentelemetry-stdout = { version = "0.3.0", features = ["metrics"] } 51 | 52 | # sync_chat 53 | wg = "0.7" 54 | crossbeam-channel = "0.5" 55 | ctrlc = "3.4" 56 | 57 | # async_chat 58 | futures = "0.3" 59 | smol = "2" 60 | async-broadcast = "0.7" 61 | waitgroup = "0.1" 62 | core_affinity = "0.8" 63 | num_cpus = "1.16" 64 | tokio = { version = "1.36", features = ["full"] } 65 | tokio-util = "0.7" 66 | 67 | # tests 68 | webrtc = "0.10.1" 69 | hyper = { version = "0.14.28", features = ["full"] } 70 | 71 | [[example]] 72 | name = "sync_chat" 73 | path = "examples/sync_chat.rs" 74 | test = false 75 | bench = false 76 | 77 | [[example]] 78 | name = "async_chat" 79 | path = "examples/async_chat.rs" 80 | test = false 81 | bench = false 82 | 83 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Use rust image as base 2 | FROM rust:latest 3 | 4 | # Set working directory inside the container 5 | WORKDIR /usr/src/app 6 | 7 | # Copy the Rust project files into the container 8 | COPY . . 9 | 10 | # Build the Rust project 11 | RUN cargo build --release --package sfu --example sync_chat 12 | 13 | # Expose the TCP port the signal server will listen on 14 | EXPOSE 8080 15 | # Expose the UDP ports the media server will listen on 16 | EXPOSE 3478-3495/udp 17 | 18 | RUN mkdir -p logs 19 | 20 | # Command to run the server 21 | CMD ./target/release/examples/sync_chat -f -d --level info > ./logs/sfu.log 2>&1 & echo $! > server_pid.txt & cargo test --release --no-fail-fast -- --show-output > ./logs/test.log 2>&1 22 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Retty.io 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | SFU 3 |
4 |

5 |

6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | License: MIT/Apache 2.0 23 | 24 |

25 |

26 | SFU in Rust with Sans-IO 27 |

28 | 29 | # 30 | 31 |
32 | Table of Content 33 | 34 | - [Building](#building) 35 | - [Toolchain](#toolchain) 36 | - [Build](#build) 37 | - [Open Source License](#open-source-license) 38 | - [Contributing](#contributing) 39 | 40 |
41 | 42 | # 43 | 44 | ## Building 45 | 46 | ### Toolchain 47 | 48 | SFU.rs currently requires Rust 1.75.0+ to build. 49 | 50 | ### Build 51 | 52 | To build sfu crate: 53 | 54 | ``` 55 | cargo build [or clippy or test or fmt] 56 | ``` 57 | 58 | To build sync version chat examples (preferred): 59 | 60 | ``` 61 | cargo run --package sfu --example sync_chat 62 | ``` 63 | 64 | To build async version chat examples (caveat: its performance is much worse than sync version, maybe due to async runtime): 65 | 66 | ``` 67 | cargo run --package sfu --example async_chat 68 | ``` 69 | 70 | ## Open Source License 71 | 72 | Dual licensing under both MIT and Apache-2.0 is the currently accepted standard by the Rust language community and has 73 | been used for both the compiler and many public libraries since ( 74 | see https://doc.rust-lang.org/1.6.0/complement-project-faq.html#why-dual-mitasl2-license). In order to match the 75 | community standards, SFU.rs is using the dual MIT+Apache-2.0 license. 76 | 77 | ## Contributing 78 | 79 | Contributors or Pull Requests are Welcome!!! 80 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | codecov: 2 | require_ci_to_pass: yes 3 | max_report_age: off 4 | token: 6c7bd236-98cc-432f-8d35-1fd7b41c7b1a 5 | 6 | coverage: 7 | precision: 2 8 | round: down 9 | range: 50..90 10 | status: 11 | project: 12 | default: 13 | enabled: no 14 | threshold: 0.2 15 | if_not_found: success 16 | patch: 17 | default: 18 | enabled: no 19 | if_not_found: success 20 | changes: 21 | default: 22 | enabled: no 23 | if_not_found: success 24 | -------------------------------------------------------------------------------- /docs/sfu-rs.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webrtc-rs/sfu/fb6650b8899730f731142fb302937dbfddee6767/docs/sfu-rs.jpg -------------------------------------------------------------------------------- /examples/async_chat.rs: -------------------------------------------------------------------------------- 1 | extern crate num_cpus; 2 | 3 | use std::cell::RefCell; 4 | use std::collections::HashMap; 5 | use std::io::Write; 6 | use std::net::SocketAddr; 7 | use std::rc::Rc; 8 | use std::str::FromStr; 9 | use std::sync::Arc; 10 | use std::time::Duration; 11 | 12 | use clap::Parser; 13 | use dtls::extension::extension_use_srtp::SrtpProtectionProfile; 14 | use log::{error, info}; 15 | use opentelemetry::{/*global,*/ metrics::MeterProvider, KeyValue}; 16 | use opentelemetry_sdk::metrics::{PeriodicReader, SdkMeterProvider}; 17 | use opentelemetry_sdk::{runtime, Resource}; 18 | use opentelemetry_stdout::MetricsExporterBuilder; 19 | use retty::bootstrap::BootstrapUdpServer; 20 | use retty::channel::Pipeline; 21 | use retty::executor::LocalExecutorBuilder; 22 | use retty::transport::TaggedBytesMut; 23 | use waitgroup::{WaitGroup, Worker}; 24 | 25 | use sfu::{ 26 | DataChannelHandler, DemuxerHandler, DtlsHandler, ExceptionHandler, GatewayHandler, 27 | InterceptorHandler, RTCCertificate, SctpHandler, ServerConfig, ServerStates, SrtpHandler, 28 | StunHandler, 29 | }; 30 | 31 | mod async_signal; 32 | 33 | use async_signal::*; 34 | 35 | #[derive(Default, Debug, Copy, Clone, clap::ValueEnum)] 36 | enum Level { 37 | Error, 38 | Warn, 39 | #[default] 40 | Info, 41 | Debug, 42 | Trace, 43 | } 44 | 45 | impl From for log::LevelFilter { 46 | fn from(level: Level) -> Self { 47 | match level { 48 | Level::Error => log::LevelFilter::Error, 49 | Level::Warn => log::LevelFilter::Warn, 50 | Level::Info => log::LevelFilter::Info, 51 | Level::Debug => log::LevelFilter::Debug, 52 | Level::Trace => log::LevelFilter::Trace, 53 | } 54 | } 55 | } 56 | 57 | #[derive(Parser)] 58 | #[command(name = "SFU Server")] 59 | #[command(author = "Rusty Rain ")] 60 | #[command(version = "0.1.0")] 61 | #[command(about = "An example of SFU Server", long_about = None)] 62 | struct Cli { 63 | #[arg(long, default_value_t = format!("127.0.0.1"))] 64 | host: String, 65 | #[arg(short, long, default_value_t = 8080)] 66 | signal_port: u16, 67 | #[arg(long, default_value_t = 3478)] 68 | media_port_min: u16, 69 | #[arg(long, default_value_t = 3495)] 70 | media_port_max: u16, 71 | 72 | #[arg(short, long)] 73 | debug: bool, 74 | #[arg(short, long, default_value_t = Level::Info)] 75 | #[clap(value_enum)] 76 | level: Level, 77 | } 78 | 79 | fn init_meter_provider( 80 | mut stop_rx: async_broadcast::Receiver<()>, 81 | worker: Worker, 82 | ) -> SdkMeterProvider { 83 | let (tx, rx) = std::sync::mpsc::channel(); 84 | 85 | std::thread::spawn(move || { 86 | let rt = tokio::runtime::Builder::new_current_thread() 87 | .enable_io() 88 | .enable_time() 89 | .build() 90 | .unwrap(); 91 | 92 | rt.block_on(async move { 93 | let _worker = worker; 94 | let exporter = MetricsExporterBuilder::default() 95 | .with_encoder(|writer, data| { 96 | Ok(serde_json::to_writer_pretty(writer, &data).unwrap()) 97 | }) 98 | .build(); 99 | let reader = PeriodicReader::builder(exporter, runtime::TokioCurrentThread) 100 | .with_interval(Duration::from_secs(30)) 101 | .build(); 102 | let meter_provider = SdkMeterProvider::builder() 103 | .with_reader(reader) 104 | .with_resource(Resource::new(vec![KeyValue::new("chat", "metrics")])) 105 | .build(); 106 | let _ = tx.send(meter_provider.clone()); 107 | 108 | let _ = stop_rx.recv().await; 109 | let _ = meter_provider.shutdown(); 110 | info!("meter provider is gracefully down"); 111 | }); 112 | }); 113 | 114 | let meter_provider = rx.recv().unwrap(); 115 | meter_provider 116 | } 117 | 118 | fn main() -> anyhow::Result<()> { 119 | let cli = Cli::parse(); 120 | if cli.debug { 121 | env_logger::Builder::new() 122 | .format(|buf, record| { 123 | writeln!( 124 | buf, 125 | "{}:{} [{}] {} - {}", 126 | record.file().unwrap_or("unknown"), 127 | record.line().unwrap_or(0), 128 | record.level(), 129 | chrono::Local::now().format("%H:%M:%S.%6f"), 130 | record.args() 131 | ) 132 | }) 133 | .filter(None, cli.level.into()) 134 | .init(); 135 | } 136 | 137 | println!( 138 | "listening {}:{}(signal)/[{}-{}](media)...", 139 | cli.host, cli.signal_port, cli.media_port_min, cli.media_port_max 140 | ); 141 | 142 | let media_ports: Vec = (cli.media_port_min..=cli.media_port_max).collect(); 143 | let (stop_tx, mut stop_rx) = async_broadcast::broadcast::<()>(1); 144 | let mut media_port_thread_map = HashMap::new(); 145 | 146 | let key_pair = rcgen::KeyPair::generate(&rcgen::PKCS_ECDSA_P256_SHA256)?; 147 | let certificates = vec![RTCCertificate::from_key_pair(key_pair)?]; 148 | let dtls_handshake_config = Arc::new( 149 | dtls::config::ConfigBuilder::default() 150 | .with_certificates( 151 | certificates 152 | .iter() 153 | .map(|c| c.dtls_certificate.clone()) 154 | .collect(), 155 | ) 156 | .with_srtp_protection_profiles(vec![SrtpProtectionProfile::Srtp_Aes128_Cm_Hmac_Sha1_80]) 157 | .with_extended_master_secret(dtls::config::ExtendedMasterSecretType::Require) 158 | .build(false, None)?, 159 | ); 160 | let sctp_endpoint_config = Arc::new(sctp::EndpointConfig::default()); 161 | let sctp_server_config = Arc::new(sctp::ServerConfig::default()); 162 | let server_config = Arc::new( 163 | ServerConfig::new(certificates) 164 | .with_dtls_handshake_config(dtls_handshake_config) 165 | .with_sctp_endpoint_config(sctp_endpoint_config) 166 | .with_sctp_server_config(sctp_server_config), 167 | ); 168 | let core_num = num_cpus::get(); 169 | let wait_group = WaitGroup::new(); 170 | let meter_provider = init_meter_provider(stop_rx.clone(), wait_group.worker()); 171 | 172 | for port in media_ports { 173 | let worker = wait_group.worker(); 174 | let host = cli.host.clone(); 175 | let meter_provider = meter_provider.clone(); 176 | let mut stop_rx = stop_rx.clone(); 177 | let (signaling_tx, signaling_rx) = smol::channel::unbounded::(); 178 | media_port_thread_map.insert(port, signaling_tx); 179 | 180 | let server_config = server_config.clone(); 181 | LocalExecutorBuilder::new() 182 | .name(format!("media_port_{}", port).as_str()) 183 | .core_id(core_affinity::CoreId { 184 | id: (port as usize) % core_num, 185 | }) 186 | .spawn(move || async move { 187 | let _worker = worker; 188 | let local_addr = SocketAddr::from_str(&format!("{}:{}", host, port)).unwrap(); 189 | let server_states = Rc::new(RefCell::new( 190 | ServerStates::new(server_config, local_addr, 191 | meter_provider.meter(format!("{}:{}", host, port)), 192 | ).unwrap() 193 | )); 194 | 195 | info!("listening {}:{}...", host, port); 196 | 197 | let server_states_moved = server_states.clone(); 198 | let mut bootstrap = BootstrapUdpServer::new(); 199 | bootstrap.pipeline(Box::new( 200 | move || { 201 | let pipeline: Pipeline = Pipeline::new(); 202 | 203 | let demuxer_handler = DemuxerHandler::new(); 204 | let stun_handler = StunHandler::new(); 205 | // DTLS 206 | let dtls_handler = DtlsHandler::new(local_addr, Rc::clone(&server_states_moved)); 207 | let sctp_handler = SctpHandler::new(local_addr, Rc::clone(&server_states_moved)); 208 | let data_channel_handler = DataChannelHandler::new(); 209 | // SRTP 210 | let srtp_handler = SrtpHandler::new(Rc::clone(&server_states_moved)); 211 | let interceptor_handler = InterceptorHandler::new(Rc::clone(&server_states_moved)); 212 | // Gateway 213 | let gateway_handler = GatewayHandler::new(Rc::clone(&server_states_moved)); 214 | let exception_handler = ExceptionHandler::new(); 215 | 216 | pipeline.add_back(demuxer_handler); 217 | pipeline.add_back(stun_handler); 218 | // DTLS 219 | pipeline.add_back(dtls_handler); 220 | pipeline.add_back(sctp_handler); 221 | pipeline.add_back(data_channel_handler); 222 | // SRTP 223 | pipeline.add_back(srtp_handler); 224 | pipeline.add_back(interceptor_handler); 225 | // Gateway 226 | pipeline.add_back(gateway_handler); 227 | pipeline.add_back(exception_handler); 228 | 229 | pipeline.finalize() 230 | }, 231 | )); 232 | 233 | if let Err(err) = bootstrap.bind(format!("{}:{}", host, port)).await { 234 | error!("bootstrap binding error: {}", err); 235 | return; 236 | } 237 | 238 | loop { 239 | tokio::select! { 240 | _ = stop_rx.recv() => { 241 | info!("media server on {}:{} receives stop signal", host, port); 242 | break; 243 | } 244 | recv = signaling_rx.recv() => { 245 | match recv { 246 | Ok(signaling_msg) => { 247 | if let Err(err) = handle_signaling_message(&server_states, signaling_msg) { 248 | error!("handle_signaling_message error: {}", err); 249 | } 250 | } 251 | Err(err) => { 252 | error!("signal_rx recv error: {}", err); 253 | break; 254 | } 255 | } 256 | } 257 | } 258 | } 259 | 260 | bootstrap.graceful_stop().await; 261 | info!("media server on {}:{} is gracefully down", host, port); 262 | })?; 263 | } 264 | 265 | let signaling_addr = SocketAddr::from_str(&format!("{}:{}", cli.host, cli.signal_port))?; 266 | let signaling_stop_rx = stop_rx.clone(); 267 | let signaling_handle = std::thread::spawn(move || { 268 | let rt = tokio::runtime::Builder::new_current_thread() 269 | .enable_io() 270 | .enable_time() 271 | .build() 272 | .unwrap(); 273 | 274 | rt.block_on(async { 275 | let signaling_server = SignalingServer::new(signaling_addr, media_port_thread_map); 276 | let mut done_rx = signaling_server.run(signaling_stop_rx).await; 277 | let _ = done_rx.recv().await; 278 | wait_group.wait().await; 279 | info!("signaling server is gracefully down"); 280 | }) 281 | }); 282 | 283 | LocalExecutorBuilder::default().run(async move { 284 | println!("Press Ctrl-C to stop"); 285 | std::thread::spawn(move || { 286 | let mut stop_tx = Some(stop_tx); 287 | ctrlc::set_handler(move || { 288 | if let Some(stop_tx) = stop_tx.take() { 289 | let _ = stop_tx.try_broadcast(()); 290 | } 291 | }) 292 | .expect("Error setting Ctrl-C handler"); 293 | }); 294 | let _ = stop_rx.recv().await; 295 | println!("Wait for Signaling Sever and Media Server Gracefully Shutdown..."); 296 | }); 297 | 298 | let _ = signaling_handle.join(); 299 | 300 | Ok(()) 301 | } 302 | -------------------------------------------------------------------------------- /examples/chat.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | WebRTC Chat example 6 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | Status: Waiting 22 |
Click Join Button...
23 |
24 | 220 | 221 | 222 | -------------------------------------------------------------------------------- /examples/sync_chat.rs: -------------------------------------------------------------------------------- 1 | use clap::Parser; 2 | use dtls::extension::extension_use_srtp::SrtpProtectionProfile; 3 | use log::info; 4 | use opentelemetry::{/*global,*/ KeyValue}; 5 | use opentelemetry_sdk::metrics::{PeriodicReader, SdkMeterProvider}; 6 | use opentelemetry_sdk::{runtime, Resource}; 7 | use opentelemetry_stdout::MetricsExporterBuilder; 8 | use rouille::Server; 9 | use sfu::{RTCCertificate, ServerConfig}; 10 | use std::collections::HashMap; 11 | use std::io::Write; 12 | use std::net::{IpAddr, UdpSocket}; 13 | use std::str::FromStr; 14 | use std::sync::mpsc::{self}; 15 | use std::sync::Arc; 16 | use std::time::Duration; 17 | use wg::WaitGroup; 18 | 19 | mod sync_signal; 20 | mod util; 21 | 22 | use sync_signal::*; 23 | 24 | #[derive(Default, Debug, Copy, Clone, clap::ValueEnum)] 25 | enum Level { 26 | Error, 27 | Warn, 28 | #[default] 29 | Info, 30 | Debug, 31 | Trace, 32 | } 33 | 34 | impl From for log::LevelFilter { 35 | fn from(level: Level) -> Self { 36 | match level { 37 | Level::Error => log::LevelFilter::Error, 38 | Level::Warn => log::LevelFilter::Warn, 39 | Level::Info => log::LevelFilter::Info, 40 | Level::Debug => log::LevelFilter::Debug, 41 | Level::Trace => log::LevelFilter::Trace, 42 | } 43 | } 44 | } 45 | 46 | #[derive(Parser)] 47 | #[command(name = "SFU Server")] 48 | #[command(author = "Rusty Rain ")] 49 | #[command(version = "0.1.0")] 50 | #[command(about = "An example of SFU Server", long_about = None)] 51 | struct Cli { 52 | #[arg(long, default_value_t = format!("127.0.0.1"))] 53 | host: String, 54 | #[arg(short, long, default_value_t = 8080)] 55 | signal_port: u16, 56 | #[arg(long, default_value_t = 3478)] 57 | media_port_min: u16, 58 | #[arg(long, default_value_t = 3495)] 59 | media_port_max: u16, 60 | 61 | #[arg(short, long)] 62 | force_local_loop: bool, 63 | #[arg(short, long)] 64 | debug: bool, 65 | #[arg(short, long, default_value_t = Level::Info)] 66 | #[clap(value_enum)] 67 | level: Level, 68 | } 69 | 70 | fn init_meter_provider( 71 | mut stop_rx: async_broadcast::Receiver<()>, 72 | wait_group: WaitGroup, 73 | ) -> SdkMeterProvider { 74 | let (tx, rx) = std::sync::mpsc::channel(); 75 | 76 | std::thread::spawn(move || { 77 | let rt = tokio::runtime::Builder::new_current_thread() 78 | .enable_io() 79 | .enable_time() 80 | .build() 81 | .unwrap(); 82 | 83 | rt.block_on(async move { 84 | let worker = wait_group.add(1); 85 | let exporter = MetricsExporterBuilder::default() 86 | .with_encoder(|writer, data| { 87 | Ok(serde_json::to_writer_pretty(writer, &data).unwrap()) 88 | }) 89 | .build(); 90 | let reader = PeriodicReader::builder(exporter, runtime::TokioCurrentThread) 91 | .with_interval(Duration::from_secs(30)) 92 | .build(); 93 | let meter_provider = SdkMeterProvider::builder() 94 | .with_reader(reader) 95 | .with_resource(Resource::new(vec![KeyValue::new("chat", "metrics")])) 96 | .build(); 97 | let _ = tx.send(meter_provider.clone()); 98 | 99 | let _ = stop_rx.recv().await; 100 | let _ = meter_provider.shutdown(); 101 | worker.done(); 102 | info!("meter provider is gracefully down"); 103 | }); 104 | }); 105 | 106 | let meter_provider = rx.recv().unwrap(); 107 | meter_provider 108 | } 109 | 110 | fn main() -> anyhow::Result<()> { 111 | let cli = Cli::parse(); 112 | if cli.debug { 113 | env_logger::Builder::new() 114 | .format(|buf, record| { 115 | writeln!( 116 | buf, 117 | "{}:{} [{}] {} - {}", 118 | record.file().unwrap_or("unknown"), 119 | record.line().unwrap_or(0), 120 | record.level(), 121 | chrono::Local::now().format("%H:%M:%S.%6f"), 122 | record.args() 123 | ) 124 | }) 125 | .filter(None, cli.level.into()) 126 | .init(); 127 | } 128 | 129 | let certificate = include_bytes!("util/cer.pem").to_vec(); 130 | let private_key = include_bytes!("util/key.pem").to_vec(); 131 | 132 | // Figure out some public IP address, since Firefox will not accept 127.0.0.1 for WebRTC traffic. 133 | let host_addr = if cli.host == "127.0.0.1" && !cli.force_local_loop { 134 | util::select_host_address() 135 | } else { 136 | IpAddr::from_str(&cli.host)? 137 | }; 138 | 139 | let media_ports: Vec = (cli.media_port_min..=cli.media_port_max).collect(); 140 | let (stop_tx, stop_rx) = crossbeam_channel::bounded::<()>(1); 141 | let mut media_port_thread_map = HashMap::new(); 142 | 143 | let key_pair = rcgen::KeyPair::generate(&rcgen::PKCS_ECDSA_P256_SHA256)?; 144 | let certificates = vec![RTCCertificate::from_key_pair(key_pair)?]; 145 | let dtls_handshake_config = Arc::new( 146 | dtls::config::ConfigBuilder::default() 147 | .with_certificates( 148 | certificates 149 | .iter() 150 | .map(|c| c.dtls_certificate.clone()) 151 | .collect(), 152 | ) 153 | .with_srtp_protection_profiles(vec![SrtpProtectionProfile::Srtp_Aes128_Cm_Hmac_Sha1_80]) 154 | .with_extended_master_secret(dtls::config::ExtendedMasterSecretType::Require) 155 | .build(false, None)?, 156 | ); 157 | let sctp_endpoint_config = Arc::new(sctp::EndpointConfig::default()); 158 | let sctp_server_config = Arc::new(sctp::ServerConfig::default()); 159 | let server_config = Arc::new( 160 | ServerConfig::new(certificates) 161 | .with_dtls_handshake_config(dtls_handshake_config) 162 | .with_sctp_endpoint_config(sctp_endpoint_config) 163 | .with_sctp_server_config(sctp_server_config) 164 | .with_idle_timeout(Duration::from_secs(30)), 165 | ); 166 | let (stop_meter_tx, stop_meter_rx) = async_broadcast::broadcast::<()>(1); 167 | let wait_group = WaitGroup::new(); 168 | let meter_provider = init_meter_provider(stop_meter_rx, wait_group.clone()); 169 | 170 | for port in media_ports { 171 | let worker = wait_group.add(1); 172 | let stop_rx = stop_rx.clone(); 173 | let (signaling_tx, signaling_rx) = mpsc::sync_channel(1); 174 | 175 | // Spin up a UDP socket for the RTC. All WebRTC traffic is going to be multiplexed over this single 176 | // server socket. Clients are identified via their respective remote (UDP) socket address. 177 | let socket = UdpSocket::bind(format!("{host_addr}:{port}")) 178 | .expect(&format!("binding to {host_addr}:{port}")); 179 | 180 | media_port_thread_map.insert(port, signaling_tx); 181 | let server_config = server_config.clone(); 182 | let meter_provider = meter_provider.clone(); 183 | // The run loop is on a separate thread to the web server. 184 | std::thread::spawn(move || { 185 | if let Err(err) = sync_run(stop_rx, socket, signaling_rx, server_config, meter_provider) 186 | { 187 | eprintln!("run_sfu got error: {}", err); 188 | } 189 | worker.done(); 190 | }); 191 | } 192 | 193 | let media_port_thread_map = Arc::new(media_port_thread_map); 194 | let signal_port = cli.signal_port; 195 | let (signal_handle, signal_cancel_tx) = if cli.force_local_loop { 196 | // for integration test, no ssl 197 | let signal_server = Server::new(format!("{}:{}", host_addr, signal_port), move |request| { 198 | web_request(request, media_port_thread_map.clone()) 199 | }) 200 | .expect("starting the signal server"); 201 | 202 | let port = signal_server.server_addr().port(); 203 | println!("Connect a browser to https://{}:{}", host_addr, port); 204 | 205 | signal_server.stoppable() 206 | } else { 207 | let signal_server = Server::new_ssl( 208 | format!("{}:{}", host_addr, signal_port), 209 | move |request| web_request(request, media_port_thread_map.clone()), 210 | certificate, 211 | private_key, 212 | ) 213 | .expect("starting the signal server"); 214 | 215 | let port = signal_server.server_addr().port(); 216 | println!("Connect a browser to https://{}:{}", host_addr, port); 217 | 218 | signal_server.stoppable() 219 | }; 220 | 221 | println!("Press Ctrl-C to stop"); 222 | std::thread::spawn(move || { 223 | let mut stop_tx = Some(stop_tx); 224 | let mut stop_meter_tx = Some(stop_meter_tx); 225 | ctrlc::set_handler(move || { 226 | if let Some(stop_meter_tx) = stop_meter_tx.take() { 227 | let _ = stop_meter_tx.try_broadcast(()); 228 | } 229 | if let Some(stop_tx) = stop_tx.take() { 230 | let _ = stop_tx.send(()); 231 | } 232 | }) 233 | .expect("Error setting Ctrl-C handler"); 234 | }); 235 | let _ = stop_rx.recv(); 236 | println!("Wait for Signaling Sever and Media Server Gracefully Shutdown..."); 237 | wait_group.wait(); 238 | let _ = signal_cancel_tx.send(()); 239 | println!("signaling server is gracefully down"); 240 | let _ = signal_handle.join(); 241 | 242 | Ok(()) 243 | } 244 | -------------------------------------------------------------------------------- /examples/sync_signal/mod.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | 3 | use bytes::{Bytes, BytesMut}; 4 | use log::error; 5 | use opentelemetry::metrics::MeterProvider; 6 | use opentelemetry_sdk::metrics::SdkMeterProvider; 7 | use retty::channel::{InboundPipeline, Pipeline}; 8 | use retty::transport::{TaggedBytesMut, TransportContext}; 9 | use rouille::{Request, Response, ResponseBody}; 10 | use sfu::{ 11 | DataChannelHandler, DemuxerHandler, DtlsHandler, ExceptionHandler, GatewayHandler, 12 | InterceptorHandler, RTCSessionDescription, SctpHandler, ServerConfig, ServerStates, 13 | SrtpHandler, StunHandler, 14 | }; 15 | use std::cell::RefCell; 16 | use std::collections::HashMap; 17 | use std::io::{Error, ErrorKind, Read}; 18 | use std::net::{SocketAddr, UdpSocket}; 19 | use std::rc::Rc; 20 | use std::sync::mpsc::{Receiver, SyncSender}; 21 | use std::sync::{mpsc, Arc}; 22 | use std::time::{Duration, Instant}; 23 | 24 | // Handle a web request. 25 | pub fn web_request( 26 | request: &Request, 27 | media_port_thread_map: Arc>>, 28 | ) -> Response { 29 | if request.method() == "GET" { 30 | return Response::html(include_str!("../chat.html")); 31 | } 32 | 33 | // "/offer/433774451/456773342" or "/leave/433774451/456773342" 34 | let path: Vec = request.url().split('/').map(|s| s.to_owned()).collect(); 35 | if path.len() != 4 || path[2].parse::().is_err() || path[3].parse::().is_err() { 36 | return Response::empty_400(); 37 | } 38 | 39 | let session_id = path[2].parse::().unwrap(); 40 | let mut sorted_ports: Vec = media_port_thread_map.keys().map(|x| *x).collect(); 41 | sorted_ports.sort(); 42 | assert!(!sorted_ports.is_empty()); 43 | let port = sorted_ports[(session_id as usize) % sorted_ports.len()]; 44 | let tx = media_port_thread_map.get(&port); 45 | 46 | // Expected POST SDP Offers. 47 | let mut offer_sdp = vec![]; 48 | request 49 | .data() 50 | .expect("body to be available") 51 | .read_to_end(&mut offer_sdp) 52 | .unwrap(); 53 | 54 | // The Rtc instance is shipped off to the main run loop. 55 | if let Some(tx) = tx { 56 | let endpoint_id = path[3].parse::().unwrap(); 57 | if path[1] == "offer" { 58 | let (response_tx, response_rx) = mpsc::sync_channel(1); 59 | 60 | tx.send(SignalingMessage { 61 | request: SignalingProtocolMessage::Offer { 62 | session_id, 63 | endpoint_id, 64 | offer_sdp: Bytes::from(offer_sdp), 65 | }, 66 | response_tx, 67 | }) 68 | .expect("to send SignalingMessage instance"); 69 | 70 | let response = response_rx.recv().expect("receive answer offer"); 71 | match response { 72 | SignalingProtocolMessage::Answer { 73 | session_id: _, 74 | endpoint_id: _, 75 | answer_sdp, 76 | } => Response::from_data("application/json", answer_sdp), 77 | _ => Response::empty_404(), 78 | } 79 | } else { 80 | // leave 81 | Response { 82 | status_code: 200, 83 | headers: vec![], 84 | data: ResponseBody::empty(), 85 | upgrade: None, 86 | } 87 | } 88 | } else { 89 | Response::empty_406() 90 | } 91 | } 92 | 93 | /// This is the "main run loop" that handles all clients, reads and writes UdpSocket traffic, 94 | /// and forwards media data between clients. 95 | pub fn sync_run( 96 | stop_rx: crossbeam_channel::Receiver<()>, 97 | socket: UdpSocket, 98 | rx: Receiver, 99 | server_config: Arc, 100 | meter_provider: SdkMeterProvider, 101 | ) -> anyhow::Result<()> { 102 | let server_states = Rc::new(RefCell::new(ServerStates::new( 103 | server_config, 104 | socket.local_addr()?, 105 | meter_provider.meter(format!("{}", socket.local_addr()?)), 106 | )?)); 107 | 108 | println!("listening {}...", socket.local_addr()?); 109 | 110 | let pipeline = build_pipeline(socket.local_addr()?, server_states.clone()); 111 | 112 | let mut buf = vec![0; 2000]; 113 | 114 | pipeline.transport_active(); 115 | loop { 116 | match stop_rx.try_recv() { 117 | Ok(_) => break, 118 | Err(err) => { 119 | if err.is_disconnected() { 120 | break; 121 | } 122 | } 123 | }; 124 | 125 | write_socket_output(&socket, &pipeline)?; 126 | 127 | // Spawn new incoming signal message from the signaling server thread. 128 | if let Ok(signal_message) = rx.try_recv() { 129 | if let Err(err) = handle_signaling_message(&server_states, signal_message) { 130 | error!("handle_signaling_message got error:{}", err); 131 | continue; 132 | } 133 | } 134 | 135 | // Poll clients until they return timeout 136 | let mut eto = Instant::now() + Duration::from_millis(100); 137 | pipeline.poll_timeout(&mut eto); 138 | 139 | let delay_from_now = eto 140 | .checked_duration_since(Instant::now()) 141 | .unwrap_or(Duration::from_secs(0)); 142 | if delay_from_now.is_zero() { 143 | pipeline.handle_timeout(Instant::now()); 144 | continue; 145 | } 146 | 147 | socket 148 | .set_read_timeout(Some(delay_from_now)) 149 | .expect("setting socket read timeout"); 150 | 151 | if let Some(input) = read_socket_input(&socket, &mut buf) { 152 | pipeline.read(input); 153 | } 154 | 155 | // Drive time forward in all clients. 156 | pipeline.handle_timeout(Instant::now()); 157 | } 158 | pipeline.transport_inactive(); 159 | 160 | println!( 161 | "media server on {} is gracefully down", 162 | socket.local_addr()? 163 | ); 164 | Ok(()) 165 | } 166 | 167 | fn write_socket_output( 168 | socket: &UdpSocket, 169 | pipeline: &Rc>, 170 | ) -> anyhow::Result<()> { 171 | while let Some(transmit) = pipeline.poll_transmit() { 172 | socket.send_to(&transmit.message, transmit.transport.peer_addr)?; 173 | } 174 | 175 | Ok(()) 176 | } 177 | 178 | fn read_socket_input(socket: &UdpSocket, buf: &mut [u8]) -> Option { 179 | match socket.recv_from(buf) { 180 | Ok((n, peer_addr)) => { 181 | return Some(TaggedBytesMut { 182 | now: Instant::now(), 183 | transport: TransportContext { 184 | local_addr: socket.local_addr().unwrap(), 185 | peer_addr, 186 | ecn: None, 187 | }, 188 | message: BytesMut::from(&buf[..n]), 189 | }); 190 | } 191 | 192 | Err(e) => match e.kind() { 193 | // Expected error for set_read_timeout(). One for windows, one for the rest. 194 | ErrorKind::WouldBlock | ErrorKind::TimedOut => None, 195 | _ => panic!("UdpSocket read failed: {e:?}"), 196 | }, 197 | } 198 | } 199 | 200 | fn build_pipeline( 201 | local_addr: SocketAddr, 202 | server_states: Rc>, 203 | ) -> Rc> { 204 | let pipeline: Pipeline = Pipeline::new(); 205 | 206 | let demuxer_handler = DemuxerHandler::new(); 207 | let stun_handler = StunHandler::new(); 208 | // DTLS 209 | let dtls_handler = DtlsHandler::new(local_addr, Rc::clone(&server_states)); 210 | let sctp_handler = SctpHandler::new(local_addr, Rc::clone(&server_states)); 211 | let data_channel_handler = DataChannelHandler::new(); 212 | // SRTP 213 | let srtp_handler = SrtpHandler::new(Rc::clone(&server_states)); 214 | let interceptor_handler = InterceptorHandler::new(Rc::clone(&server_states)); 215 | // Gateway 216 | let gateway_handler = GatewayHandler::new(Rc::clone(&server_states)); 217 | let exception_handler = ExceptionHandler::new(); 218 | 219 | pipeline.add_back(demuxer_handler); 220 | pipeline.add_back(stun_handler); 221 | // DTLS 222 | pipeline.add_back(dtls_handler); 223 | pipeline.add_back(sctp_handler); 224 | pipeline.add_back(data_channel_handler); 225 | // SRTP 226 | pipeline.add_back(srtp_handler); 227 | pipeline.add_back(interceptor_handler); 228 | // Gateway 229 | pipeline.add_back(gateway_handler); 230 | pipeline.add_back(exception_handler); 231 | 232 | pipeline.finalize() 233 | } 234 | 235 | pub enum SignalingProtocolMessage { 236 | Ok { 237 | session_id: u64, 238 | endpoint_id: u64, 239 | }, 240 | Err { 241 | session_id: u64, 242 | endpoint_id: u64, 243 | reason: Bytes, 244 | }, 245 | Offer { 246 | session_id: u64, 247 | endpoint_id: u64, 248 | offer_sdp: Bytes, 249 | }, 250 | Answer { 251 | session_id: u64, 252 | endpoint_id: u64, 253 | answer_sdp: Bytes, 254 | }, 255 | Leave { 256 | session_id: u64, 257 | endpoint_id: u64, 258 | }, 259 | } 260 | 261 | pub struct SignalingMessage { 262 | pub request: SignalingProtocolMessage, 263 | pub response_tx: SyncSender, 264 | } 265 | 266 | pub fn handle_signaling_message( 267 | server_states: &Rc>, 268 | signaling_msg: SignalingMessage, 269 | ) -> anyhow::Result<()> { 270 | match signaling_msg.request { 271 | SignalingProtocolMessage::Offer { 272 | session_id, 273 | endpoint_id, 274 | offer_sdp, 275 | } => handle_offer_message( 276 | server_states, 277 | session_id, 278 | endpoint_id, 279 | offer_sdp, 280 | signaling_msg.response_tx, 281 | ), 282 | SignalingProtocolMessage::Leave { 283 | session_id, 284 | endpoint_id, 285 | } => handle_leave_message( 286 | server_states, 287 | session_id, 288 | endpoint_id, 289 | signaling_msg.response_tx, 290 | ), 291 | SignalingProtocolMessage::Ok { 292 | session_id, 293 | endpoint_id, 294 | } 295 | | SignalingProtocolMessage::Err { 296 | session_id, 297 | endpoint_id, 298 | reason: _, 299 | } 300 | | SignalingProtocolMessage::Answer { 301 | session_id, 302 | endpoint_id, 303 | answer_sdp: _, 304 | } => Ok(signaling_msg 305 | .response_tx 306 | .send(SignalingProtocolMessage::Err { 307 | session_id, 308 | endpoint_id, 309 | reason: Bytes::from("Invalid Request"), 310 | }) 311 | .map_err(|_| { 312 | Error::new( 313 | ErrorKind::Other, 314 | "failed to send back signaling message response".to_string(), 315 | ) 316 | })?), 317 | } 318 | } 319 | 320 | fn handle_offer_message( 321 | server_states: &Rc>, 322 | session_id: u64, 323 | endpoint_id: u64, 324 | offer: Bytes, 325 | response_tx: SyncSender, 326 | ) -> anyhow::Result<()> { 327 | let try_handle = || -> anyhow::Result { 328 | let offer_str = String::from_utf8(offer.to_vec())?; 329 | log::info!( 330 | "handle_offer_message: {}/{}/{}", 331 | session_id, 332 | endpoint_id, 333 | offer_str, 334 | ); 335 | let mut server_states = server_states.borrow_mut(); 336 | 337 | let offer_sdp = serde_json::from_str::(&offer_str)?; 338 | let answer = server_states.accept_offer(session_id, endpoint_id, None, offer_sdp)?; 339 | let answer_str = serde_json::to_string(&answer)?; 340 | log::info!("generate answer sdp: {}", answer_str); 341 | Ok(Bytes::from(answer_str)) 342 | }; 343 | 344 | match try_handle() { 345 | Ok(answer_sdp) => Ok(response_tx 346 | .send(SignalingProtocolMessage::Answer { 347 | session_id, 348 | endpoint_id, 349 | answer_sdp, 350 | }) 351 | .map_err(|_| { 352 | Error::new( 353 | ErrorKind::Other, 354 | "failed to send back signaling message response".to_string(), 355 | ) 356 | })?), 357 | Err(err) => Ok(response_tx 358 | .send(SignalingProtocolMessage::Err { 359 | session_id, 360 | endpoint_id, 361 | reason: Bytes::from(err.to_string()), 362 | }) 363 | .map_err(|_| { 364 | Error::new( 365 | ErrorKind::Other, 366 | "failed to send back signaling message response".to_string(), 367 | ) 368 | })?), 369 | } 370 | } 371 | 372 | fn handle_leave_message( 373 | _server_states: &Rc>, 374 | session_id: u64, 375 | endpoint_id: u64, 376 | response_tx: SyncSender, 377 | ) -> anyhow::Result<()> { 378 | let try_handle = || -> anyhow::Result<()> { 379 | log::info!("handle_leave_message: {}/{}", session_id, endpoint_id,); 380 | Ok(()) 381 | }; 382 | 383 | match try_handle() { 384 | Ok(_) => Ok(response_tx 385 | .send(SignalingProtocolMessage::Ok { 386 | session_id, 387 | endpoint_id, 388 | }) 389 | .map_err(|_| { 390 | Error::new( 391 | ErrorKind::Other, 392 | "failed to send back signaling message response".to_string(), 393 | ) 394 | })?), 395 | Err(err) => Ok(response_tx 396 | .send(SignalingProtocolMessage::Err { 397 | session_id, 398 | endpoint_id, 399 | reason: Bytes::from(err.to_string()), 400 | }) 401 | .map_err(|_| { 402 | Error::new( 403 | ErrorKind::Other, 404 | "failed to send back signaling message response".to_string(), 405 | ) 406 | })?), 407 | } 408 | } 409 | -------------------------------------------------------------------------------- /examples/util/cer.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIBlTCCATygAwIBAgIUE7bkYmO9WlAUb/NCtMIvkES07PUwCgYIKoZIzj0EAwIw 3 | FTETMBEGA1UEAwwKc3RyMG0udGVzdDAeFw0yMjA5MjExNTQ4MDlaFw0zMjA5MTgx 4 | NTQ4MDlaMBUxEzARBgNVBAMMCnN0cjBtLnRlc3QwWTATBgcqhkjOPQIBBggqhkjO 5 | PQMBBwNCAAQcaA6BSUf5omMcApA8c0tPaKh4gXU2wNuyKukzZqkxYGDQj37AnkN7 6 | 4XJ0EaqZ8fRytFNaVaIBtGaMmS6e/2TMo2owaDAdBgNVHQ4EFgQU2lZPsFqfpiuV 7 | RcGPi8FvPOpoIDswHwYDVR0jBBgwFoAU2lZPsFqfpiuVRcGPi8FvPOpoIDswDwYD 8 | VR0TAQH/BAUwAwEB/zAVBgNVHREEDjAMggpzdHIwbS50ZXN0MAoGCCqGSM49BAMC 9 | A0cAMEQCIHJ4qiwmjPoFTAd1zFdZ9Cwjtg2823Avpyk1cU6cQWagAiA8KN/oyryt 10 | GR/kcqer/Z/jn8OuUUXJ2UAzde9FW0sUig== 11 | -----END CERTIFICATE----- 12 | -------------------------------------------------------------------------------- /examples/util/key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgsMxzs1m02Rj479Qv 3 | hfKSIELOTa2hSXDncaVAHdjH48mhRANCAAQcaA6BSUf5omMcApA8c0tPaKh4gXU2 4 | wNuyKukzZqkxYGDQj37AnkN74XJ0EaqZ8fRytFNaVaIBtGaMmS6e/2TM 5 | -----END PRIVATE KEY----- 6 | -------------------------------------------------------------------------------- /examples/util/mod.rs: -------------------------------------------------------------------------------- 1 | use std::net::IpAddr; 2 | use systemstat::{Platform, System}; 3 | 4 | pub fn select_host_address() -> IpAddr { 5 | let system = System::new(); 6 | let networks = system.networks().unwrap(); 7 | 8 | for net in networks.values() { 9 | for n in &net.addrs { 10 | if let systemstat::IpAddr::V4(v) = n.addr { 11 | if !v.is_loopback() && !v.is_link_local() && !v.is_broadcast() { 12 | return IpAddr::V4(v); 13 | } 14 | } 15 | } 16 | } 17 | 18 | panic!("Found no usable network interface"); 19 | } 20 | -------------------------------------------------------------------------------- /scripts/parse_test_results.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Initialize accumulated totals 4 | total_passed=0 5 | total_failed=0 6 | total_ignored=0 7 | total_measured=0 8 | total_filtered=0 9 | total_time=0 10 | 11 | # Arrays to store passed, failed, and ignored tests 12 | passed_tests=() 13 | failed_tests=() 14 | ignored_tests=() 15 | 16 | # Read each line from the log file 17 | while IFS= read -r line; do 18 | # Check if the line indicates test results 19 | if [[ $line =~ test\ result:\ (FAILED|ok).\ ([0-9]+)\ passed\;\ ([0-9]+)\ failed\;\ ([0-9]+)\ ignored\;\ ([0-9]+)\ measured\;\ ([0-9]+)\ filtered\ out\;\ finished\ in\ ([0-9]+\.[0-9]+)s ]]; then 20 | # Extract the test results 21 | result="${BASH_REMATCH[1]}" 22 | passed="${BASH_REMATCH[2]}" 23 | failed="${BASH_REMATCH[3]}" 24 | ignored="${BASH_REMATCH[4]}" 25 | measured="${BASH_REMATCH[5]}" 26 | filtered="${BASH_REMATCH[6]}" 27 | time="${BASH_REMATCH[7]}" 28 | 29 | # Accumulate the test results 30 | total_passed=$((total_passed + passed)) 31 | total_failed=$((total_failed + failed)) 32 | total_ignored=$((total_ignored + ignored)) 33 | total_measured=$((total_measured + measured)) 34 | total_filtered=$((total_filtered + filtered)) 35 | total_time=$(bc <<< "$total_time + $time") 36 | elif [[ $line =~ test\ (.*)\ \.\.\.\ (FAILED|ok|ignored) ]]; then 37 | # Extract the test name and result 38 | test_name="${BASH_REMATCH[1]}" 39 | test_result="${BASH_REMATCH[2]}" 40 | 41 | # Add the test name to the appropriate array based on the result 42 | case "$test_result" in 43 | "ok") 44 | passed_tests+=("$test_name") 45 | ;; 46 | "FAILED") 47 | failed_tests+=("$test_name") 48 | ;; 49 | "ignored") 50 | ignored_tests+=("$test_name") 51 | ;; 52 | esac 53 | fi 54 | done < "$1" 55 | 56 | # Print passed tests with indentation 57 | echo "Total Passed: $total_passed" 58 | for test_name in "${passed_tests[@]}"; do 59 | echo " $test_name" 60 | done 61 | 62 | # Print failed tests with indentation 63 | echo "Total Failed: $total_failed" 64 | for test_name in "${failed_tests[@]}"; do 65 | echo " $test_name" 66 | done 67 | 68 | # Print ignored tests with indentation 69 | echo "Total Ignored: $total_ignored" 70 | for test_name in "${ignored_tests[@]}"; do 71 | echo " $test_name" 72 | done 73 | 74 | # Return non-zero exit code if there are failed tests 75 | if [[ $total_failed -gt 0 ]]; then 76 | exit 1 77 | else 78 | exit 0 79 | fi 80 | 81 | # Return non-zero exit code if there are zero passed tests 82 | if [[ $total_passed -eq 0 ]]; then 83 | exit 1 84 | else 85 | exit 0 86 | fi 87 | -------------------------------------------------------------------------------- /src/configs/mod.rs: -------------------------------------------------------------------------------- 1 | pub(crate) mod media_config; 2 | pub(crate) mod server_config; 3 | pub(crate) mod session_config; 4 | -------------------------------------------------------------------------------- /src/configs/server_config.rs: -------------------------------------------------------------------------------- 1 | use crate::configs::media_config::MediaConfig; 2 | use crate::server::certificate::RTCCertificate; 3 | use std::sync::Arc; 4 | use std::time::Duration; 5 | 6 | /// ServerConfig provides customized parameters for SFU server 7 | pub struct ServerConfig { 8 | pub(crate) certificates: Vec, 9 | pub(crate) dtls_handshake_config: Arc, 10 | pub(crate) sctp_endpoint_config: Arc, 11 | pub(crate) sctp_server_config: Arc, 12 | pub(crate) media_config: MediaConfig, 13 | pub(crate) idle_timeout: Duration, 14 | } 15 | 16 | impl ServerConfig { 17 | /// create new server config 18 | pub fn new(certificates: Vec) -> Self { 19 | Self { 20 | certificates, 21 | media_config: MediaConfig::default(), 22 | sctp_endpoint_config: Arc::new(sctp::EndpointConfig::default()), 23 | sctp_server_config: Arc::new(sctp::ServerConfig::default()), 24 | dtls_handshake_config: Arc::new(dtls::config::HandshakeConfig::default()), 25 | idle_timeout: Duration::from_secs(30), 26 | } 27 | } 28 | 29 | /// build with provided MediaConfig 30 | pub fn with_media_config(mut self, media_config: MediaConfig) -> Self { 31 | self.media_config = media_config; 32 | self 33 | } 34 | 35 | /// build with provided sctp::ServerConfig 36 | pub fn with_sctp_server_config(mut self, sctp_server_config: Arc) -> Self { 37 | self.sctp_server_config = sctp_server_config; 38 | self 39 | } 40 | 41 | /// build with provided sctp::EndpointConfig 42 | pub fn with_sctp_endpoint_config( 43 | mut self, 44 | sctp_endpoint_config: Arc, 45 | ) -> Self { 46 | self.sctp_endpoint_config = sctp_endpoint_config; 47 | self 48 | } 49 | 50 | /// build with provided dtls::config::HandshakeConfig 51 | pub fn with_dtls_handshake_config( 52 | mut self, 53 | dtls_handshake_config: Arc, 54 | ) -> Self { 55 | self.dtls_handshake_config = dtls_handshake_config; 56 | self 57 | } 58 | 59 | /// build with idle timeout 60 | pub fn with_idle_timeout(mut self, idle_timeout: Duration) -> Self { 61 | self.idle_timeout = idle_timeout; 62 | self 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/configs/session_config.rs: -------------------------------------------------------------------------------- 1 | use crate::configs::server_config::ServerConfig; 2 | use std::net::SocketAddr; 3 | use std::sync::Arc; 4 | 5 | pub(crate) struct SessionConfig { 6 | pub(crate) server_config: Arc, 7 | pub(crate) local_addr: SocketAddr, 8 | } 9 | 10 | impl SessionConfig { 11 | pub(crate) fn new(server_config: Arc, local_addr: SocketAddr) -> Self { 12 | Self { 13 | server_config, 14 | local_addr, 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/description/fmtp/generic.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | /// fmtp_consist checks that two FMTP parameters are not inconsistent. 4 | fn fmtp_consist(a: &HashMap, b: &HashMap) -> bool { 5 | //TODO: add unicode case-folding equal support 6 | for (k, v) in a { 7 | if let Some(vb) = b.get(k) { 8 | if vb.to_uppercase() != v.to_uppercase() { 9 | return false; 10 | } 11 | } 12 | } 13 | for (k, v) in b { 14 | if let Some(va) = a.get(k) { 15 | if va.to_uppercase() != v.to_uppercase() { 16 | return false; 17 | } 18 | } 19 | } 20 | true 21 | } 22 | 23 | #[derive(Debug, PartialEq)] 24 | pub(crate) struct GenericFmtp { 25 | pub(crate) mime_type: String, 26 | pub(crate) parameters: HashMap, 27 | } 28 | 29 | impl Fmtp for GenericFmtp { 30 | fn mime_type(&self) -> &str { 31 | self.mime_type.as_str() 32 | } 33 | 34 | /// Match returns true if g and b are compatible fmtp descriptions 35 | /// The generic implementation is used for MimeTypes that are not defined 36 | fn match_fmtp(&self, f: &(dyn Fmtp)) -> bool { 37 | if let Some(c) = f.as_any().downcast_ref::() { 38 | if self.mime_type.to_lowercase() != c.mime_type().to_lowercase() { 39 | return false; 40 | } 41 | 42 | fmtp_consist(&self.parameters, &c.parameters) 43 | } else { 44 | false 45 | } 46 | } 47 | 48 | fn parameter(&self, key: &str) -> Option<&String> { 49 | self.parameters.get(key) 50 | } 51 | 52 | fn equal(&self, other: &(dyn Fmtp)) -> bool { 53 | other 54 | .as_any() 55 | .downcast_ref::() 56 | .map_or(false, |a| self == a) 57 | } 58 | 59 | fn as_any(&self) -> &(dyn Any) { 60 | self 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/description/fmtp/h264.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | fn profile_level_id_matches(a: &str, b: &str) -> bool { 4 | let aa = match hex::decode(a) { 5 | Ok(aa) => { 6 | if aa.len() < 2 { 7 | return false; 8 | } 9 | aa 10 | } 11 | Err(_) => return false, 12 | }; 13 | 14 | let bb = match hex::decode(b) { 15 | Ok(bb) => { 16 | if bb.len() < 2 { 17 | return false; 18 | } 19 | bb 20 | } 21 | Err(_) => return false, 22 | }; 23 | 24 | aa[0] == bb[0] && aa[1] == bb[1] 25 | } 26 | 27 | #[derive(Debug, PartialEq)] 28 | pub(crate) struct H264Fmtp { 29 | pub(crate) parameters: HashMap, 30 | } 31 | 32 | impl Fmtp for H264Fmtp { 33 | fn mime_type(&self) -> &str { 34 | "video/h264" 35 | } 36 | 37 | /// Match returns true if h and b are compatible fmtp descriptions 38 | /// Based on RFC6184 Section 8.2.2: 39 | /// The parameters identifying a media format configuration for H.264 40 | /// are profile-level-id and packetization-mode. These media format 41 | /// configuration parameters (except for the level part of profile- 42 | /// level-id) MUST be used symmetrically; that is, the answerer MUST 43 | /// either maintain all configuration parameters or remove the media 44 | /// format (payload type) completely if one or more of the parameter 45 | /// values are not supported. 46 | /// Informative note: The requirement for symmetric use does not 47 | /// apply for the level part of profile-level-id and does not apply 48 | /// for the other stream properties and capability parameters. 49 | fn match_fmtp(&self, f: &(dyn Fmtp)) -> bool { 50 | if let Some(c) = f.as_any().downcast_ref::() { 51 | // check packetization-mode 52 | let hpmode = match self.parameters.get("packetization-mode") { 53 | Some(s) => s, 54 | None => return false, 55 | }; 56 | let cpmode = match c.parameters.get("packetization-mode") { 57 | Some(s) => s, 58 | None => return false, 59 | }; 60 | 61 | if hpmode != cpmode { 62 | return false; 63 | } 64 | 65 | // check profile-level-id 66 | let hplid = match self.parameters.get("profile-level-id") { 67 | Some(s) => s, 68 | None => return false, 69 | }; 70 | let cplid = match c.parameters.get("profile-level-id") { 71 | Some(s) => s, 72 | None => return false, 73 | }; 74 | 75 | if !profile_level_id_matches(hplid, cplid) { 76 | return false; 77 | } 78 | 79 | true 80 | } else { 81 | false 82 | } 83 | } 84 | 85 | fn parameter(&self, key: &str) -> Option<&String> { 86 | self.parameters.get(key) 87 | } 88 | 89 | fn equal(&self, other: &(dyn Fmtp)) -> bool { 90 | other 91 | .as_any() 92 | .downcast_ref::() 93 | .map_or(false, |a| self == a) 94 | } 95 | 96 | fn as_any(&self) -> &(dyn Any) { 97 | self 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/description/fmtp/mod.rs: -------------------------------------------------------------------------------- 1 | pub(crate) mod generic; 2 | pub(crate) mod h264; 3 | 4 | use std::any::Any; 5 | use std::collections::HashMap; 6 | use std::fmt; 7 | 8 | use crate::description::fmtp::{generic::GenericFmtp, h264::H264Fmtp}; 9 | 10 | /// Fmtp interface for implementing custom 11 | /// Fmtp parsers based on mime_type 12 | pub trait Fmtp: fmt::Debug { 13 | /// mime_type returns the mime_type associated with 14 | /// the fmtp 15 | fn mime_type(&self) -> &str; 16 | 17 | /// match_fmtp compares two fmtp descriptions for 18 | /// compatibility based on the mime_type 19 | fn match_fmtp(&self, f: &(dyn Fmtp)) -> bool; 20 | 21 | /// parameter returns a value for the associated key 22 | /// if contained in the parsed fmtp string 23 | fn parameter(&self, key: &str) -> Option<&String>; 24 | 25 | fn equal(&self, other: &(dyn Fmtp)) -> bool; 26 | fn as_any(&self) -> &(dyn Any); 27 | } 28 | 29 | impl PartialEq for dyn Fmtp { 30 | fn eq(&self, other: &Self) -> bool { 31 | self.equal(other) 32 | } 33 | } 34 | 35 | /// parse parses an fmtp string based on the MimeType 36 | pub(crate) fn parse(mime_type: &str, line: &str) -> Box { 37 | let mut parameters = HashMap::new(); 38 | for p in line.split(';').collect::>() { 39 | let pp: Vec<&str> = p.trim().splitn(2, '=').collect(); 40 | let key = pp[0].to_lowercase(); 41 | let value = if pp.len() > 1 { 42 | pp[1].to_owned() 43 | } else { 44 | String::new() 45 | }; 46 | parameters.insert(key, value); 47 | } 48 | 49 | if mime_type.to_uppercase() == "video/h264".to_uppercase() { 50 | Box::new(H264Fmtp { parameters }) 51 | } else { 52 | Box::new(GenericFmtp { 53 | mime_type: mime_type.to_owned(), 54 | parameters, 55 | }) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/description/rtp_codec.rs: -------------------------------------------------------------------------------- 1 | use crate::configs::media_config::*; 2 | use crate::description::{ 3 | fmtp, 4 | rtp_transceiver::{PayloadType, RTCPFeedback}, 5 | }; 6 | use shared::error::{Error, Result}; 7 | 8 | /// RTPCodecType determines the type of a codec 9 | #[derive(Default, Debug, Copy, Clone, PartialEq, Eq)] 10 | pub enum RTPCodecType { 11 | #[default] 12 | Unspecified = 0, 13 | 14 | /// RTPCodecTypeAudio indicates this is an audio codec 15 | Audio = 1, 16 | 17 | /// RTPCodecTypeVideo indicates this is a video codec 18 | Video = 2, 19 | } 20 | 21 | impl From<&str> for RTPCodecType { 22 | fn from(raw: &str) -> Self { 23 | match raw { 24 | "audio" => RTPCodecType::Audio, 25 | "video" => RTPCodecType::Video, 26 | _ => RTPCodecType::Unspecified, 27 | } 28 | } 29 | } 30 | 31 | impl From for RTPCodecType { 32 | fn from(v: u8) -> Self { 33 | match v { 34 | 1 => RTPCodecType::Audio, 35 | 2 => RTPCodecType::Video, 36 | _ => RTPCodecType::Unspecified, 37 | } 38 | } 39 | } 40 | 41 | impl std::fmt::Display for RTPCodecType { 42 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 43 | let s = match *self { 44 | RTPCodecType::Audio => "audio", 45 | RTPCodecType::Video => "video", 46 | RTPCodecType::Unspecified => super::UNSPECIFIED_STR, 47 | }; 48 | write!(f, "{s}") 49 | } 50 | } 51 | 52 | /// RTPCodecCapability provides information about codec capabilities. 53 | /// 54 | #[derive(Default, Debug, Clone, PartialEq, Eq)] 55 | pub struct RTCRtpCodecCapability { 56 | pub mime_type: String, 57 | pub clock_rate: u32, 58 | pub channels: u16, 59 | pub sdp_fmtp_line: String, 60 | pub rtcp_feedbacks: Vec, 61 | } 62 | 63 | impl RTCRtpCodecCapability { 64 | /// Turn codec capability into a `packetizer::Payloader` 65 | pub fn payloader_for_codec(&self) -> Result> { 66 | let mime_type = self.mime_type.to_lowercase(); 67 | if mime_type == MIME_TYPE_H264.to_lowercase() { 68 | Ok(Box::::default()) 69 | } else if mime_type == MIME_TYPE_VP8.to_lowercase() { 70 | let mut vp8_payloader = rtp::codecs::vp8::Vp8Payloader::default(); 71 | vp8_payloader.enable_picture_id = true; 72 | Ok(Box::new(vp8_payloader)) 73 | } else if mime_type == MIME_TYPE_VP9.to_lowercase() { 74 | Ok(Box::::default()) 75 | } else if mime_type == MIME_TYPE_OPUS.to_lowercase() { 76 | Ok(Box::::default()) 77 | } else if mime_type == MIME_TYPE_G722.to_lowercase() 78 | || mime_type == MIME_TYPE_PCMU.to_lowercase() 79 | || mime_type == MIME_TYPE_PCMA.to_lowercase() 80 | || mime_type == MIME_TYPE_TELEPHONE_EVENT.to_lowercase() 81 | { 82 | Ok(Box::::default()) 83 | } else if mime_type == MIME_TYPE_AV1.to_lowercase() { 84 | Ok(Box::::default()) 85 | } else { 86 | Err(Error::Other("ErrNoPayloaderForCodec".to_string())) 87 | } 88 | } 89 | } 90 | 91 | /// RTPHeaderExtensionCapability is used to define a RFC5285 RTP header extension supported by the codec. 92 | /// 93 | #[derive(Default, Debug, Clone)] 94 | pub struct RTCRtpHeaderExtensionCapability { 95 | pub uri: String, 96 | } 97 | 98 | /// RTPHeaderExtensionParameter represents a negotiated RFC5285 RTP header extension. 99 | /// 100 | #[derive(Default, Debug, Clone, PartialEq, Eq)] 101 | pub struct RTCRtpHeaderExtensionParameters { 102 | pub uri: String, 103 | pub id: isize, 104 | } 105 | 106 | /// RTPCodecParameters is a sequence containing the media codecs that an RtpSender 107 | /// will choose from, as well as entries for RTX, RED and FEC mechanisms. This also 108 | /// includes the PayloadType that has been negotiated 109 | /// 110 | #[derive(Default, Debug, Clone, PartialEq, Eq)] 111 | pub struct RTCRtpCodecParameters { 112 | pub capability: RTCRtpCodecCapability, 113 | pub payload_type: PayloadType, 114 | pub stats_id: u64, //TODO: String, 115 | } 116 | 117 | /// RTPParameters is a list of negotiated codecs and header extensions 118 | /// 119 | #[derive(Default, Debug, Clone)] 120 | pub struct RTCRtpParameters { 121 | pub header_extensions: Vec, 122 | pub codecs: Vec, 123 | } 124 | 125 | #[derive(Default, Debug, Copy, Clone, PartialEq)] 126 | pub(crate) enum CodecMatch { 127 | #[default] 128 | None = 0, 129 | Partial = 1, 130 | Exact = 2, 131 | } 132 | 133 | /// Do a fuzzy find for a codec in the list of codecs 134 | /// Used for lookup up a codec in an existing list to find a match 135 | /// Returns codecMatchExact, codecMatchPartial, or codecMatchNone 136 | pub(crate) fn codec_parameters_fuzzy_search( 137 | needle: &RTCRtpCodecParameters, 138 | haystack: &[RTCRtpCodecParameters], 139 | ) -> (RTCRtpCodecParameters, CodecMatch) { 140 | let needle_fmtp = fmtp::parse( 141 | &needle.capability.mime_type, 142 | &needle.capability.sdp_fmtp_line, 143 | ); 144 | 145 | //TODO: add unicode case-folding equal support 146 | 147 | // First attempt to match on mime_type + sdpfmtp_line 148 | for c in haystack { 149 | let cfmpt = fmtp::parse(&c.capability.mime_type, &c.capability.sdp_fmtp_line); 150 | if needle_fmtp.match_fmtp(&*cfmpt) { 151 | return (c.clone(), CodecMatch::Exact); 152 | } 153 | } 154 | 155 | // Fallback to just mime_type 156 | for c in haystack { 157 | if c.capability.mime_type.to_uppercase() == needle.capability.mime_type.to_uppercase() { 158 | return (c.clone(), CodecMatch::Partial); 159 | } 160 | } 161 | 162 | (RTCRtpCodecParameters::default(), CodecMatch::None) 163 | } 164 | -------------------------------------------------------------------------------- /src/description/rtp_transceiver.rs: -------------------------------------------------------------------------------- 1 | use crate::description::{ 2 | rtp_codec::{RTCRtpParameters, RTPCodecType}, 3 | rtp_transceiver_direction::RTCRtpTransceiverDirection, 4 | }; 5 | 6 | /// SSRC represents a synchronization source 7 | /// A synchronization source is a randomly chosen 8 | /// value meant to be globally unique within a particular 9 | /// RTP session. Used to identify a single stream of media. 10 | /// 11 | #[allow(clippy::upper_case_acronyms)] 12 | pub type SSRC = u32; 13 | 14 | /// PayloadType identifies the format of the RTP payload and determines 15 | /// its interpretation by the application. Each codec in a RTP Session 16 | /// will have a different PayloadType 17 | /// 18 | pub type PayloadType = u8; 19 | 20 | /// TYPE_RTCP_FBT_RANSPORT_CC .. 21 | pub const TYPE_RTCP_FB_TRANSPORT_CC: &str = "transport-cc"; 22 | 23 | /// TYPE_RTCP_FB_GOOG_REMB .. 24 | pub const TYPE_RTCP_FB_GOOG_REMB: &str = "goog-remb"; 25 | 26 | /// TYPE_RTCP_FB_ACK .. 27 | pub const TYPE_RTCP_FB_ACK: &str = "ack"; 28 | 29 | /// TYPE_RTCP_FB_CCM .. 30 | pub const TYPE_RTCP_FB_CCM: &str = "ccm"; 31 | 32 | /// TYPE_RTCP_FB_NACK .. 33 | pub const TYPE_RTCP_FB_NACK: &str = "nack"; 34 | 35 | /// rtcpfeedback signals the connection to use additional RTCP packet types. 36 | /// 37 | #[derive(Default, Debug, Clone, PartialEq, Eq)] 38 | pub struct RTCPFeedback { 39 | /// Type is the type of feedback. 40 | /// see: 41 | /// valid: ack, ccm, nack, goog-remb, transport-cc 42 | pub typ: String, 43 | 44 | /// The parameter value depends on the type. 45 | /// For example, type="nack" parameter="pli" will send Picture Loss Indicator packets. 46 | pub parameter: String, 47 | } 48 | 49 | #[derive(Debug, Clone)] 50 | pub(crate) struct MediaStreamId { 51 | pub(crate) stream_id: String, 52 | pub(crate) track_id: String, 53 | } 54 | 55 | #[derive(Debug, Clone)] 56 | pub(crate) struct SsrcGroup { 57 | pub(crate) name: String, 58 | pub(crate) ssrcs: Vec, 59 | } 60 | 61 | #[derive(Debug, Clone)] 62 | pub(crate) struct RTCRtpSender { 63 | pub(crate) cname: String, 64 | pub(crate) msid: MediaStreamId, 65 | pub(crate) ssrcs: Vec, 66 | pub(crate) ssrc_groups: Vec, 67 | } 68 | 69 | /// RTPTransceiver represents a combination of an RTPSender and an RTPReceiver that share a common mid. 70 | #[derive(Debug, Clone)] 71 | pub struct RTCRtpTransceiver { 72 | pub(crate) mid: String, 73 | 74 | pub(crate) sender: Option, 75 | 76 | pub(crate) direction: RTCRtpTransceiverDirection, 77 | pub(crate) current_direction: RTCRtpTransceiverDirection, 78 | 79 | pub(crate) rtp_params: RTCRtpParameters, 80 | 81 | pub(crate) kind: RTPCodecType, 82 | } 83 | 84 | impl RTCRtpTransceiver { 85 | /// current_direction returns the RTPTransceiver's current direction as negotiated. 86 | pub(crate) fn current_direction(&self) -> RTCRtpTransceiverDirection { 87 | self.current_direction 88 | } 89 | 90 | pub(crate) fn set_current_direction(&mut self, d: RTCRtpTransceiverDirection) { 91 | self.current_direction = d; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/description/rtp_transceiver_direction.rs: -------------------------------------------------------------------------------- 1 | use crate::description::UNSPECIFIED_STR; 2 | use std::fmt; 3 | 4 | /// RTPTransceiverDirection indicates the direction of the RTPTransceiver. 5 | #[derive(Debug, Copy, Clone, PartialEq, Eq, Default)] 6 | pub enum RTCRtpTransceiverDirection { 7 | #[default] 8 | Unspecified, 9 | 10 | /// Sendrecv indicates the RTPSender will offer 11 | /// to send RTP and RTPReceiver the will offer to receive RTP. 12 | Sendrecv, 13 | 14 | /// Sendonly indicates the RTPSender will offer to send RTP. 15 | Sendonly, 16 | 17 | /// Recvonly indicates the RTPReceiver the will offer to receive RTP. 18 | Recvonly, 19 | 20 | /// Inactive indicates the RTPSender won't offer 21 | /// to send RTP and RTPReceiver the won't offer to receive RTP. 22 | Inactive, 23 | } 24 | 25 | const RTP_TRANSCEIVER_DIRECTION_SENDRECV_STR: &str = "sendrecv"; 26 | const RTP_TRANSCEIVER_DIRECTION_SENDONLY_STR: &str = "sendonly"; 27 | const RTP_TRANSCEIVER_DIRECTION_RECVONLY_STR: &str = "recvonly"; 28 | const RTP_TRANSCEIVER_DIRECTION_INACTIVE_STR: &str = "inactive"; 29 | 30 | /// defines a procedure for creating a new 31 | /// RTPTransceiverDirection from a raw string naming the transceiver direction. 32 | impl From<&str> for RTCRtpTransceiverDirection { 33 | fn from(raw: &str) -> Self { 34 | match raw { 35 | RTP_TRANSCEIVER_DIRECTION_SENDRECV_STR => RTCRtpTransceiverDirection::Sendrecv, 36 | RTP_TRANSCEIVER_DIRECTION_SENDONLY_STR => RTCRtpTransceiverDirection::Sendonly, 37 | RTP_TRANSCEIVER_DIRECTION_RECVONLY_STR => RTCRtpTransceiverDirection::Recvonly, 38 | RTP_TRANSCEIVER_DIRECTION_INACTIVE_STR => RTCRtpTransceiverDirection::Inactive, 39 | _ => RTCRtpTransceiverDirection::Unspecified, 40 | } 41 | } 42 | } 43 | 44 | impl From for RTCRtpTransceiverDirection { 45 | fn from(v: u8) -> Self { 46 | match v { 47 | 1 => RTCRtpTransceiverDirection::Sendrecv, 48 | 2 => RTCRtpTransceiverDirection::Sendonly, 49 | 3 => RTCRtpTransceiverDirection::Recvonly, 50 | 4 => RTCRtpTransceiverDirection::Inactive, 51 | _ => RTCRtpTransceiverDirection::Unspecified, 52 | } 53 | } 54 | } 55 | 56 | impl fmt::Display for RTCRtpTransceiverDirection { 57 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 58 | match *self { 59 | RTCRtpTransceiverDirection::Sendrecv => { 60 | write!(f, "{RTP_TRANSCEIVER_DIRECTION_SENDRECV_STR}") 61 | } 62 | RTCRtpTransceiverDirection::Sendonly => { 63 | write!(f, "{RTP_TRANSCEIVER_DIRECTION_SENDONLY_STR}") 64 | } 65 | RTCRtpTransceiverDirection::Recvonly => { 66 | write!(f, "{RTP_TRANSCEIVER_DIRECTION_RECVONLY_STR}") 67 | } 68 | RTCRtpTransceiverDirection::Inactive => { 69 | write!(f, "{RTP_TRANSCEIVER_DIRECTION_INACTIVE_STR}") 70 | } 71 | _ => write!(f, "{}", UNSPECIFIED_STR), 72 | } 73 | } 74 | } 75 | 76 | impl RTCRtpTransceiverDirection { 77 | /// reverse indicate the opposite direction 78 | pub fn reverse(&self) -> RTCRtpTransceiverDirection { 79 | match *self { 80 | RTCRtpTransceiverDirection::Sendonly => RTCRtpTransceiverDirection::Recvonly, 81 | RTCRtpTransceiverDirection::Recvonly => RTCRtpTransceiverDirection::Sendonly, 82 | _ => *self, 83 | } 84 | } 85 | 86 | pub fn intersect(&self, other: RTCRtpTransceiverDirection) -> RTCRtpTransceiverDirection { 87 | Self::from_send_recv( 88 | self.has_send() && other.has_send(), 89 | self.has_recv() && other.has_recv(), 90 | ) 91 | } 92 | 93 | pub fn from_send_recv(send: bool, recv: bool) -> RTCRtpTransceiverDirection { 94 | match (send, recv) { 95 | (true, true) => Self::Sendrecv, 96 | (true, false) => Self::Sendonly, 97 | (false, true) => Self::Recvonly, 98 | (false, false) => Self::Inactive, 99 | } 100 | } 101 | 102 | pub fn has_send(&self) -> bool { 103 | matches!(self, Self::Sendrecv | Self::Sendonly) 104 | } 105 | 106 | pub fn has_recv(&self) -> bool { 107 | matches!(self, Self::Sendrecv | Self::Recvonly) 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/description/sdp_type.rs: -------------------------------------------------------------------------------- 1 | use crate::description::UNSPECIFIED_STR; 2 | use serde::{Deserialize, Serialize}; 3 | use std::fmt; 4 | 5 | /// SDPType describes the type of an SessionDescription. 6 | #[derive(Default, Debug, PartialEq, Eq, Copy, Clone, Serialize, Deserialize)] 7 | pub enum RTCSdpType { 8 | #[default] 9 | Unspecified = 0, 10 | 11 | /// indicates that a description MUST be treated as an SDP offer. 12 | #[serde(rename = "offer")] 13 | Offer, 14 | 15 | /// indicates that a description MUST be treated as an 16 | /// SDP answer, but not a final answer. A description used as an SDP 17 | /// pranswer may be applied as a response to an SDP offer, or an update to 18 | /// a previously sent SDP pranswer. 19 | #[serde(rename = "pranswer")] 20 | Pranswer, 21 | 22 | /// indicates that a description MUST be treated as an SDP 23 | /// final answer, and the offer-answer exchange MUST be considered complete. 24 | /// A description used as an SDP answer may be applied as a response to an 25 | /// SDP offer or as an update to a previously sent SDP pranswer. 26 | #[serde(rename = "answer")] 27 | Answer, 28 | 29 | /// indicates that a description MUST be treated as 30 | /// canceling the current SDP negotiation and moving the SDP offer and 31 | /// answer back to what it was in the previous stable state. Note the 32 | /// local or remote SDP descriptions in the previous stable state could be 33 | /// null if there has not yet been a successful offer-answer negotiation. 34 | #[serde(rename = "rollback")] 35 | Rollback, 36 | } 37 | 38 | const SDP_TYPE_OFFER_STR: &str = "offer"; 39 | const SDP_TYPE_PRANSWER_STR: &str = "pranswer"; 40 | const SDP_TYPE_ANSWER_STR: &str = "answer"; 41 | const SDP_TYPE_ROLLBACK_STR: &str = "rollback"; 42 | 43 | /// creates an SDPType from a string 44 | impl From<&str> for RTCSdpType { 45 | fn from(raw: &str) -> Self { 46 | match raw { 47 | SDP_TYPE_OFFER_STR => RTCSdpType::Offer, 48 | SDP_TYPE_PRANSWER_STR => RTCSdpType::Pranswer, 49 | SDP_TYPE_ANSWER_STR => RTCSdpType::Answer, 50 | SDP_TYPE_ROLLBACK_STR => RTCSdpType::Rollback, 51 | _ => RTCSdpType::Unspecified, 52 | } 53 | } 54 | } 55 | 56 | impl fmt::Display for RTCSdpType { 57 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 58 | match *self { 59 | RTCSdpType::Offer => write!(f, "{SDP_TYPE_OFFER_STR}"), 60 | RTCSdpType::Pranswer => write!(f, "{SDP_TYPE_PRANSWER_STR}"), 61 | RTCSdpType::Answer => write!(f, "{SDP_TYPE_ANSWER_STR}"), 62 | RTCSdpType::Rollback => write!(f, "{SDP_TYPE_ROLLBACK_STR}"), 63 | _ => write!(f, "{}", UNSPECIFIED_STR), 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/endpoint/candidate.rs: -------------------------------------------------------------------------------- 1 | use crate::description::{RTCSessionDescription, UNSPECIFIED_STR}; 2 | use crate::server::certificate::RTCDtlsFingerprint; 3 | use crate::types::{EndpointId, SessionId, UserName}; 4 | use base64::{prelude::BASE64_STANDARD, Engine}; 5 | use ring::rand::{SecureRandom, SystemRandom}; 6 | use sdp::util::ConnectionRole; 7 | use sdp::SessionDescription; 8 | use serde::{Deserialize, Serialize}; 9 | use shared::error::{Error, Result}; 10 | use std::fmt; 11 | use std::time::Instant; 12 | 13 | /// DtlsRole indicates the role of the DTLS transport. 14 | #[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)] 15 | pub enum DTLSRole { 16 | #[default] 17 | Unspecified = 0, 18 | 19 | /// DTLSRoleAuto defines the DTLS role is determined based on 20 | /// the resolved ICE role: the ICE controlled role acts as the DTLS 21 | /// client and the ICE controlling role acts as the DTLS server. 22 | #[serde(rename = "auto")] 23 | Auto = 1, 24 | 25 | /// DTLSRoleClient defines the DTLS client role. 26 | #[serde(rename = "client")] 27 | Client = 2, 28 | 29 | /// DTLSRoleServer defines the DTLS server role. 30 | #[serde(rename = "server")] 31 | Server = 3, 32 | } 33 | 34 | /// 35 | /// The answerer MUST use either a 36 | /// setup attribute value of setup:active or setup:passive. Note that 37 | /// if the answerer uses setup:passive, then the DTLS handshake will 38 | /// not begin until the answerer is received, which adds additional 39 | /// latency. setup:active allows the answer and the DTLS handshake to 40 | /// occur in parallel. Thus, setup:active is RECOMMENDED. 41 | pub(crate) const DEFAULT_DTLS_ROLE_ANSWER: DTLSRole = DTLSRole::Client; 42 | 43 | /// The endpoint that is the offerer MUST use the setup attribute 44 | /// value of setup:actpass and be prepared to receive a client_hello 45 | /// before it receives the answer. 46 | pub(crate) const DEFAULT_DTLS_ROLE_OFFER: DTLSRole = DTLSRole::Auto; 47 | 48 | impl fmt::Display for DTLSRole { 49 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 50 | match *self { 51 | DTLSRole::Auto => write!(f, "auto"), 52 | DTLSRole::Client => write!(f, "client"), 53 | DTLSRole::Server => write!(f, "server"), 54 | _ => write!(f, "{}", UNSPECIFIED_STR), 55 | } 56 | } 57 | } 58 | 59 | /// Iterate a SessionDescription from a remote to determine if an explicit 60 | /// role can been determined from it. The decision is made from the first role we we parse. 61 | /// If no role can be found we return DTLSRoleAuto 62 | impl From<&SessionDescription> for DTLSRole { 63 | fn from(session_description: &SessionDescription) -> Self { 64 | for media_section in &session_description.media_descriptions { 65 | for attribute in &media_section.attributes { 66 | if attribute.key == "setup" { 67 | if let Some(value) = &attribute.value { 68 | match value.as_str() { 69 | "active" => return DTLSRole::Client, 70 | "passive" => return DTLSRole::Server, 71 | _ => return DTLSRole::Auto, 72 | }; 73 | } else { 74 | return DTLSRole::Auto; 75 | } 76 | } 77 | } 78 | } 79 | 80 | DTLSRole::Auto 81 | } 82 | } 83 | 84 | impl DTLSRole { 85 | pub(crate) fn to_connection_role(self) -> ConnectionRole { 86 | match self { 87 | DTLSRole::Client => ConnectionRole::Active, 88 | DTLSRole::Server => ConnectionRole::Passive, 89 | DTLSRole::Auto => ConnectionRole::Actpass, 90 | _ => ConnectionRole::Unspecified, 91 | } 92 | } 93 | } 94 | 95 | /// ICEParameters includes the ICE username fragment 96 | /// and password and other ICE-related parameters. 97 | #[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 98 | pub(crate) struct RTCIceParameters { 99 | pub(crate) username_fragment: String, 100 | pub(crate) password: String, 101 | } 102 | 103 | /// DTLSParameters holds information relating to DTLS configuration. 104 | #[derive(Default, Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] 105 | pub(crate) struct DTLSParameters { 106 | pub(crate) role: DTLSRole, 107 | pub(crate) fingerprints: Vec, 108 | } 109 | 110 | #[derive(Default, Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] 111 | pub(crate) struct ConnectionCredentials { 112 | pub(crate) ice_params: RTCIceParameters, 113 | pub(crate) dtls_params: DTLSParameters, 114 | } 115 | 116 | impl ConnectionCredentials { 117 | pub(crate) fn new(fingerprints: Vec, remote_role: DTLSRole) -> Self { 118 | let rng = SystemRandom::new(); 119 | 120 | let mut user = [0u8; 9]; 121 | let _ = rng.fill(&mut user); 122 | let mut password = [0u8; 18]; 123 | let _ = rng.fill(&mut password); 124 | 125 | Self { 126 | ice_params: RTCIceParameters { 127 | username_fragment: BASE64_STANDARD.encode(&user[..]), 128 | password: BASE64_STANDARD.encode(&password[..]), 129 | }, 130 | dtls_params: DTLSParameters { 131 | fingerprints, 132 | role: if remote_role == DTLSRole::Server { 133 | DTLSRole::Client 134 | } else { 135 | DTLSRole::Server 136 | }, 137 | }, 138 | } 139 | } 140 | 141 | pub(crate) fn from_sdp(sdp: &SessionDescription) -> Result { 142 | let username_fragment = sdp 143 | .media_descriptions 144 | .iter() 145 | .find_map(|m| m.attribute("ice-ufrag")) 146 | .ok_or(Error::ErrAttributeNotFound)? 147 | .ok_or(Error::ErrAttributeNotFound)? 148 | .to_string(); 149 | let password = sdp 150 | .media_descriptions 151 | .iter() 152 | .find_map(|m| m.attribute("ice-pwd")) 153 | .ok_or(Error::ErrAttributeNotFound)? 154 | .ok_or(Error::ErrAttributeNotFound)? 155 | .to_string(); 156 | let fingerprint = if let Some(fingerprint) = sdp.attribute("fingerprint") { 157 | fingerprint.try_into()? 158 | } else { 159 | sdp.media_descriptions 160 | .iter() 161 | .find_map(|m| m.attribute("fingerprint")) 162 | .ok_or(Error::ErrAttributeNotFound)? 163 | .ok_or(Error::ErrAttributeNotFound)? 164 | .try_into()? 165 | }; 166 | let role = DTLSRole::from(sdp); 167 | 168 | Ok(Self { 169 | ice_params: RTCIceParameters { 170 | username_fragment, 171 | password, 172 | }, 173 | dtls_params: DTLSParameters { 174 | role, 175 | fingerprints: vec![fingerprint], 176 | }, 177 | }) 178 | } 179 | 180 | pub(crate) fn valid(&self) -> bool { 181 | self.ice_params.username_fragment.len() >= 4 182 | && self.ice_params.username_fragment.len() <= 256 183 | && self.ice_params.password.len() >= 22 184 | && self.ice_params.password.len() <= 256 185 | } 186 | } 187 | 188 | #[derive(Debug)] 189 | pub(crate) struct Candidate { 190 | session_id: SessionId, 191 | endpoint_id: EndpointId, 192 | remote_conn_cred: ConnectionCredentials, 193 | local_conn_cred: ConnectionCredentials, 194 | remote_description: RTCSessionDescription, 195 | local_description: RTCSessionDescription, 196 | expired_time: Instant, 197 | } 198 | 199 | impl Candidate { 200 | pub(crate) fn new( 201 | session_id: SessionId, 202 | endpoint_id: EndpointId, 203 | remote_conn_cred: ConnectionCredentials, 204 | local_conn_cred: ConnectionCredentials, 205 | remote_description: RTCSessionDescription, 206 | local_description: RTCSessionDescription, 207 | expired_time: Instant, 208 | ) -> Self { 209 | Self { 210 | session_id, 211 | endpoint_id, 212 | local_conn_cred, 213 | remote_conn_cred, 214 | remote_description, 215 | local_description, 216 | expired_time, 217 | } 218 | } 219 | 220 | pub(crate) fn remote_connection_credentials(&self) -> &ConnectionCredentials { 221 | &self.remote_conn_cred 222 | } 223 | 224 | pub(crate) fn local_connection_credentials(&self) -> &ConnectionCredentials { 225 | &self.local_conn_cred 226 | } 227 | 228 | /// get_remote_parameters returns the remote's ICE parameters 229 | pub(crate) fn get_remote_parameters(&self) -> &RTCIceParameters { 230 | &self.remote_conn_cred.ice_params 231 | } 232 | 233 | /// get_local_parameters returns the local's ICE parameters. 234 | pub(crate) fn get_local_parameters(&self) -> &RTCIceParameters { 235 | &self.local_conn_cred.ice_params 236 | } 237 | 238 | pub(crate) fn session_id(&self) -> SessionId { 239 | self.session_id 240 | } 241 | 242 | pub(crate) fn endpoint_id(&self) -> EndpointId { 243 | self.endpoint_id 244 | } 245 | 246 | pub(crate) fn username(&self) -> UserName { 247 | format!( 248 | "{}:{}", 249 | self.local_conn_cred.ice_params.username_fragment, 250 | self.remote_conn_cred.ice_params.username_fragment 251 | ) 252 | } 253 | 254 | pub(crate) fn remote_description(&self) -> &RTCSessionDescription { 255 | &self.remote_description 256 | } 257 | 258 | pub(crate) fn local_description(&self) -> &RTCSessionDescription { 259 | &self.local_description 260 | } 261 | 262 | pub(crate) fn expired_time(&self) -> Instant { 263 | self.expired_time 264 | } 265 | } 266 | -------------------------------------------------------------------------------- /src/endpoint/mod.rs: -------------------------------------------------------------------------------- 1 | pub(crate) mod candidate; 2 | pub(crate) mod transport; 3 | 4 | use crate::description::{rtp_transceiver::RTCRtpTransceiver, RTCSessionDescription}; 5 | use crate::endpoint::transport::Transport; 6 | use crate::interceptors::Interceptor; 7 | use crate::types::{EndpointId, FourTuple, Mid}; 8 | use std::collections::HashMap; 9 | 10 | pub(crate) struct Endpoint { 11 | endpoint_id: EndpointId, 12 | interceptor: Box, 13 | 14 | is_renegotiation_needed: bool, 15 | remote_description: Option, 16 | local_description: Option, 17 | 18 | transports: HashMap, 19 | 20 | mids: Vec, 21 | transceivers: HashMap, 22 | } 23 | 24 | impl Endpoint { 25 | pub(crate) fn new(endpoint_id: EndpointId, interceptor: Box) -> Self { 26 | Self { 27 | endpoint_id, 28 | interceptor, 29 | 30 | is_renegotiation_needed: false, 31 | remote_description: None, 32 | local_description: None, 33 | 34 | transports: HashMap::new(), 35 | 36 | mids: vec![], 37 | transceivers: HashMap::new(), 38 | } 39 | } 40 | 41 | pub(crate) fn endpoint_id(&self) -> EndpointId { 42 | self.endpoint_id 43 | } 44 | 45 | pub(crate) fn add_transport(&mut self, transport: Transport) { 46 | self.transports.insert(*transport.four_tuple(), transport); 47 | } 48 | 49 | pub(crate) fn remove_transport(&mut self, four_tuple: &FourTuple) -> Option { 50 | self.transports.remove(four_tuple) 51 | } 52 | 53 | pub(crate) fn has_transport(&self, four_tuple: &FourTuple) -> bool { 54 | self.transports.contains_key(four_tuple) 55 | } 56 | 57 | pub(crate) fn get_transports(&self) -> &HashMap { 58 | &self.transports 59 | } 60 | 61 | pub(crate) fn get_mut_transports(&mut self) -> &mut HashMap { 62 | &mut self.transports 63 | } 64 | 65 | pub(crate) fn get_mut_interceptor(&mut self) -> &mut Box { 66 | &mut self.interceptor 67 | } 68 | 69 | pub(crate) fn get_mids(&self) -> &Vec { 70 | &self.mids 71 | } 72 | 73 | pub(crate) fn get_mut_mids(&mut self) -> &mut Vec { 74 | &mut self.mids 75 | } 76 | 77 | pub(crate) fn get_transceivers(&self) -> &HashMap { 78 | &self.transceivers 79 | } 80 | 81 | pub(crate) fn get_mut_transceivers(&mut self) -> &mut HashMap { 82 | &mut self.transceivers 83 | } 84 | 85 | pub(crate) fn get_mut_mids_and_transceivers( 86 | &mut self, 87 | ) -> (&mut Vec, &mut HashMap) { 88 | (&mut self.mids, &mut self.transceivers) 89 | } 90 | 91 | pub(crate) fn remote_description(&self) -> Option<&RTCSessionDescription> { 92 | self.remote_description.as_ref() 93 | } 94 | 95 | pub(crate) fn local_description(&self) -> Option<&RTCSessionDescription> { 96 | self.local_description.as_ref() 97 | } 98 | 99 | pub(crate) fn set_remote_description(&mut self, description: RTCSessionDescription) { 100 | self.remote_description = Some(description); 101 | } 102 | 103 | pub(crate) fn set_local_description(&mut self, description: RTCSessionDescription) { 104 | self.local_description = Some(description); 105 | } 106 | 107 | pub(crate) fn is_renegotiation_needed(&self) -> bool { 108 | self.is_renegotiation_needed 109 | } 110 | 111 | pub(crate) fn set_renegotiation_needed(&mut self, is_renegotiation_needed: bool) { 112 | self.is_renegotiation_needed = is_renegotiation_needed; 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/endpoint/transport.rs: -------------------------------------------------------------------------------- 1 | use crate::endpoint::candidate::Candidate; 2 | use crate::types::FourTuple; 3 | use sctp::{Association, AssociationHandle}; 4 | use srtp::context::Context; 5 | use std::collections::HashMap; 6 | use std::rc::Rc; 7 | use std::sync::Arc; 8 | use std::time::Instant; 9 | 10 | pub(crate) struct Transport { 11 | four_tuple: FourTuple, 12 | last_activity: Instant, 13 | 14 | // ICE 15 | candidate: Rc, 16 | 17 | // DTLS 18 | dtls_endpoint: dtls::endpoint::Endpoint, 19 | 20 | // SCTP 21 | sctp_endpoint: sctp::Endpoint, 22 | sctp_associations: HashMap, 23 | 24 | // DataChannel 25 | association_handle: Option, 26 | stream_id: Option, 27 | 28 | // SRTP 29 | local_srtp_context: Option, 30 | remote_srtp_context: Option, 31 | } 32 | 33 | impl Transport { 34 | pub(crate) fn new( 35 | four_tuple: FourTuple, 36 | candidate: Rc, 37 | dtls_handshake_config: Arc, 38 | sctp_endpoint_config: Arc, 39 | sctp_server_config: Arc, 40 | ) -> Self { 41 | Self { 42 | four_tuple, 43 | last_activity: Instant::now(), 44 | 45 | candidate, 46 | 47 | dtls_endpoint: dtls::endpoint::Endpoint::new(Some(dtls_handshake_config)), 48 | 49 | sctp_endpoint: sctp::Endpoint::new(sctp_endpoint_config, Some(sctp_server_config)), 50 | sctp_associations: HashMap::new(), 51 | 52 | association_handle: None, 53 | stream_id: None, 54 | 55 | local_srtp_context: None, 56 | remote_srtp_context: None, 57 | } 58 | } 59 | 60 | pub(crate) fn four_tuple(&self) -> &FourTuple { 61 | &self.four_tuple 62 | } 63 | 64 | pub(crate) fn candidate(&self) -> &Rc { 65 | &self.candidate 66 | } 67 | 68 | pub(crate) fn get_mut_dtls_endpoint(&mut self) -> &mut dtls::endpoint::Endpoint { 69 | &mut self.dtls_endpoint 70 | } 71 | 72 | pub(crate) fn get_dtls_endpoint(&self) -> &dtls::endpoint::Endpoint { 73 | &self.dtls_endpoint 74 | } 75 | 76 | pub(crate) fn get_mut_sctp_endpoint(&mut self) -> &mut sctp::Endpoint { 77 | &mut self.sctp_endpoint 78 | } 79 | 80 | pub(crate) fn get_sctp_endpoint(&self) -> &sctp::Endpoint { 81 | &self.sctp_endpoint 82 | } 83 | 84 | pub(crate) fn get_mut_sctp_associations( 85 | &mut self, 86 | ) -> &mut HashMap { 87 | &mut self.sctp_associations 88 | } 89 | 90 | pub(crate) fn get_mut_sctp_endpoint_associations( 91 | &mut self, 92 | ) -> ( 93 | &mut sctp::Endpoint, 94 | &mut HashMap, 95 | ) { 96 | (&mut self.sctp_endpoint, &mut self.sctp_associations) 97 | } 98 | 99 | pub(crate) fn get_sctp_associations(&self) -> &HashMap { 100 | &self.sctp_associations 101 | } 102 | 103 | pub(crate) fn local_srtp_context(&mut self) -> Option<&mut Context> { 104 | self.local_srtp_context.as_mut() 105 | } 106 | 107 | pub(crate) fn remote_srtp_context(&mut self) -> Option<&mut Context> { 108 | self.remote_srtp_context.as_mut() 109 | } 110 | 111 | pub(crate) fn set_local_srtp_context(&mut self, local_srtp_context: Context) { 112 | self.local_srtp_context = Some(local_srtp_context); 113 | } 114 | 115 | pub(crate) fn set_remote_srtp_context(&mut self, remote_srtp_context: Context) { 116 | self.remote_srtp_context = Some(remote_srtp_context); 117 | } 118 | 119 | pub(crate) fn set_association_handle_and_stream_id( 120 | &mut self, 121 | association_handle: usize, 122 | stream_id: u16, 123 | ) { 124 | self.association_handle = Some(association_handle); 125 | self.stream_id = Some(stream_id) 126 | } 127 | 128 | pub(crate) fn association_handle_and_stream_id(&self) -> (Option, Option) { 129 | (self.association_handle, self.stream_id) 130 | } 131 | 132 | pub(crate) fn is_local_srtp_context_ready(&self) -> bool { 133 | self.local_srtp_context.is_some() 134 | } 135 | 136 | pub(crate) fn keep_alive(&mut self) { 137 | self.last_activity = Instant::now(); 138 | } 139 | 140 | pub(crate) fn last_activity(&self) -> Instant { 141 | self.last_activity 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/handlers/datachannel.rs: -------------------------------------------------------------------------------- 1 | use crate::messages::{ 2 | ApplicationMessage, DTLSMessageEvent, DataChannelEvent, DataChannelMessage, 3 | DataChannelMessageParams, DataChannelMessageType, MessageEvent, TaggedMessageEvent, 4 | }; 5 | use datachannel::message::{message_channel_ack::*, message_channel_open::*, message_type::*, *}; 6 | use log::{debug, error, warn}; 7 | use retty::channel::{Context, Handler}; 8 | use sctp::ReliabilityType; 9 | use shared::error::Result; 10 | use shared::marshal::*; 11 | use std::collections::VecDeque; 12 | 13 | /// DataChannelHandler implements DataChannel Protocol handling 14 | #[derive(Default)] 15 | pub struct DataChannelHandler { 16 | transmits: VecDeque, 17 | } 18 | 19 | impl DataChannelHandler { 20 | pub fn new() -> Self { 21 | Self { 22 | transmits: VecDeque::new(), 23 | } 24 | } 25 | } 26 | 27 | impl Handler for DataChannelHandler { 28 | type Rin = TaggedMessageEvent; 29 | type Rout = Self::Rin; 30 | type Win = TaggedMessageEvent; 31 | type Wout = Self::Win; 32 | 33 | fn name(&self) -> &str { 34 | "DataChannelHandler" 35 | } 36 | 37 | fn handle_read( 38 | &mut self, 39 | ctx: &Context, 40 | msg: Self::Rin, 41 | ) { 42 | if let MessageEvent::Dtls(DTLSMessageEvent::Sctp(message)) = msg.message { 43 | debug!( 44 | "recv SCTP DataChannelMessage from {:?}", 45 | msg.transport.peer_addr 46 | ); 47 | let try_read = 48 | || -> Result<(Option, Option)> { 49 | if message.data_message_type == DataChannelMessageType::Control { 50 | let mut buf = &message.payload[..]; 51 | if MessageType::unmarshal(&mut buf)? == MessageType::DataChannelOpen { 52 | debug!("DataChannelOpen for association_handle {} and stream_id {} and data_message_type {:?}", 53 | message.association_handle, 54 | message.stream_id, 55 | message.data_message_type); 56 | 57 | let data_channel_open = DataChannelOpen::unmarshal(&mut buf)?; 58 | let (unordered, reliability_type) = 59 | get_reliability_params(data_channel_open.channel_type); 60 | 61 | let payload = Message::DataChannelAck(DataChannelAck {}).marshal()?; 62 | Ok(( 63 | Some(ApplicationMessage { 64 | association_handle: message.association_handle, 65 | stream_id: message.stream_id, 66 | data_channel_event: DataChannelEvent::Open, 67 | }), 68 | Some(DataChannelMessage { 69 | association_handle: message.association_handle, 70 | stream_id: message.stream_id, 71 | data_message_type: DataChannelMessageType::Control, 72 | params: Some(DataChannelMessageParams { 73 | unordered, 74 | reliability_type, 75 | reliability_parameter: data_channel_open 76 | .reliability_parameter, 77 | }), 78 | payload, 79 | }), 80 | )) 81 | } else { 82 | Ok((None, None)) 83 | } 84 | } else { 85 | Ok(( 86 | Some(ApplicationMessage { 87 | association_handle: message.association_handle, 88 | stream_id: message.stream_id, 89 | data_channel_event: DataChannelEvent::Message(message.payload), 90 | }), 91 | None, 92 | )) 93 | } 94 | }; 95 | 96 | match try_read() { 97 | Ok((inbound_message, outbound_message)) => { 98 | // first outbound message 99 | if let Some(data_channel_message) = outbound_message { 100 | debug!("send DataChannelAck message {:?}", msg.transport.peer_addr); 101 | self.transmits.push_back(TaggedMessageEvent { 102 | now: msg.now, 103 | transport: msg.transport, 104 | message: MessageEvent::Dtls(DTLSMessageEvent::Sctp( 105 | data_channel_message, 106 | )), 107 | }); 108 | } 109 | 110 | // then inbound message 111 | if let Some(application_message) = inbound_message { 112 | debug!("recv application message {:?}", msg.transport.peer_addr); 113 | ctx.fire_read(TaggedMessageEvent { 114 | now: msg.now, 115 | transport: msg.transport, 116 | message: MessageEvent::Dtls(DTLSMessageEvent::DataChannel( 117 | application_message, 118 | )), 119 | }) 120 | } 121 | } 122 | Err(err) => { 123 | error!("try_read with error {}", err); 124 | ctx.fire_exception(Box::new(err)) 125 | } 126 | }; 127 | } else { 128 | // Bypass 129 | debug!("bypass DataChannel read {:?}", msg.transport.peer_addr); 130 | ctx.fire_read(msg); 131 | } 132 | } 133 | 134 | fn poll_write( 135 | &mut self, 136 | ctx: &Context, 137 | ) -> Option { 138 | if let Some(msg) = ctx.fire_poll_write() { 139 | if let MessageEvent::Dtls(DTLSMessageEvent::DataChannel(message)) = msg.message { 140 | debug!("send application message {:?}", msg.transport.peer_addr); 141 | 142 | if let DataChannelEvent::Message(payload) = message.data_channel_event { 143 | self.transmits.push_back(TaggedMessageEvent { 144 | now: msg.now, 145 | transport: msg.transport, 146 | message: MessageEvent::Dtls(DTLSMessageEvent::Sctp(DataChannelMessage { 147 | association_handle: message.association_handle, 148 | stream_id: message.stream_id, 149 | data_message_type: DataChannelMessageType::Text, 150 | params: None, 151 | payload, 152 | })), 153 | }); 154 | } else { 155 | warn!( 156 | "drop unsupported DATACHANNEL message to {}", 157 | msg.transport.peer_addr 158 | ); 159 | } 160 | } else { 161 | // Bypass 162 | debug!("bypass DataChannel write {:?}", msg.transport.peer_addr); 163 | self.transmits.push_back(msg); 164 | } 165 | } 166 | 167 | self.transmits.pop_front() 168 | } 169 | } 170 | 171 | fn get_reliability_params(channel_type: ChannelType) -> (bool, ReliabilityType) { 172 | let (unordered, reliability_type) = match channel_type { 173 | ChannelType::Reliable => (false, ReliabilityType::Reliable), 174 | ChannelType::ReliableUnordered => (true, ReliabilityType::Reliable), 175 | ChannelType::PartialReliableRexmit => (false, ReliabilityType::Rexmit), 176 | ChannelType::PartialReliableRexmitUnordered => (true, ReliabilityType::Rexmit), 177 | ChannelType::PartialReliableTimed => (false, ReliabilityType::Timed), 178 | ChannelType::PartialReliableTimedUnordered => (true, ReliabilityType::Timed), 179 | }; 180 | 181 | (unordered, reliability_type) 182 | } 183 | -------------------------------------------------------------------------------- /src/handlers/demuxer.rs: -------------------------------------------------------------------------------- 1 | use crate::messages::{ 2 | DTLSMessageEvent, MessageEvent, RTPMessageEvent, STUNMessageEvent, TaggedMessageEvent, 3 | }; 4 | use log::{debug, error}; 5 | use retty::channel::{Context, Handler}; 6 | use retty::transport::TaggedBytesMut; 7 | 8 | /// match_range is a MatchFunc that accepts packets with the first byte in [lower..upper] 9 | fn match_range(lower: u8, upper: u8, buf: &[u8]) -> bool { 10 | if buf.is_empty() { 11 | return false; 12 | } 13 | let b = buf[0]; 14 | b >= lower && b <= upper 15 | } 16 | 17 | /// MatchFuncs as described in RFC7983 18 | /// 19 | /// +----------------+ 20 | /// | [0..3] -+--> forward to STUN 21 | /// | | 22 | /// | [16..19] -+--> forward to ZRTP 23 | /// | | 24 | /// packet --> | [20..63] -+--> forward to DTLS 25 | /// | | 26 | /// | [64..79] -+--> forward to TURN Channel 27 | /// | | 28 | /// | [128..191] -+--> forward to RTP/RTCP 29 | /// +----------------+ 30 | /// match_dtls is a MatchFunc that accepts packets with the first byte in [20..63] 31 | /// as defied in RFC7983 32 | fn match_dtls(b: &[u8]) -> bool { 33 | match_range(20, 63, b) 34 | } 35 | 36 | /// match_srtp is a MatchFunc that accepts packets with the first byte in [128..191] 37 | /// as defied in RFC7983 38 | fn match_srtp(b: &[u8]) -> bool { 39 | match_range(128, 191, b) 40 | } 41 | 42 | /// DemuxerHandler implements demuxing of STUN/DTLS/RTP/RTCP Protocol packets 43 | #[derive(Default)] 44 | pub struct DemuxerHandler; 45 | 46 | impl DemuxerHandler { 47 | pub fn new() -> Self { 48 | DemuxerHandler 49 | } 50 | } 51 | 52 | impl Handler for DemuxerHandler { 53 | type Rin = TaggedBytesMut; 54 | type Rout = TaggedMessageEvent; 55 | type Win = TaggedMessageEvent; 56 | type Wout = TaggedBytesMut; 57 | 58 | fn name(&self) -> &str { 59 | "DemuxerHandler" 60 | } 61 | 62 | fn handle_read( 63 | &mut self, 64 | ctx: &Context, 65 | msg: Self::Rin, 66 | ) { 67 | if msg.message.is_empty() { 68 | error!("drop invalid packet due to zero length"); 69 | } else if match_dtls(&msg.message) { 70 | ctx.fire_read(TaggedMessageEvent { 71 | now: msg.now, 72 | transport: msg.transport, 73 | message: MessageEvent::Dtls(DTLSMessageEvent::Raw(msg.message)), 74 | }); 75 | } else if match_srtp(&msg.message) { 76 | ctx.fire_read(TaggedMessageEvent { 77 | now: msg.now, 78 | transport: msg.transport, 79 | message: MessageEvent::Rtp(RTPMessageEvent::Raw(msg.message)), 80 | }); 81 | } else { 82 | ctx.fire_read(TaggedMessageEvent { 83 | now: msg.now, 84 | transport: msg.transport, 85 | message: MessageEvent::Stun(STUNMessageEvent::Raw(msg.message)), 86 | }); 87 | } 88 | } 89 | 90 | fn poll_write( 91 | &mut self, 92 | ctx: &Context, 93 | ) -> Option { 94 | if let Some(msg) = ctx.fire_poll_write() { 95 | match msg.message { 96 | MessageEvent::Stun(STUNMessageEvent::Raw(message)) 97 | | MessageEvent::Dtls(DTLSMessageEvent::Raw(message)) 98 | | MessageEvent::Rtp(RTPMessageEvent::Raw(message)) => Some(TaggedBytesMut { 99 | now: msg.now, 100 | transport: msg.transport, 101 | message, 102 | }), 103 | _ => { 104 | debug!("drop non-RAW packet {:?}", msg.message); 105 | None 106 | } 107 | } 108 | } else { 109 | None 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/handlers/dtls.rs: -------------------------------------------------------------------------------- 1 | use bytes::BytesMut; 2 | use retty::channel::{Context, Handler}; 3 | use std::cell::RefCell; 4 | use std::collections::VecDeque; 5 | use std::net::SocketAddr; 6 | use std::rc::Rc; 7 | use std::time::Instant; 8 | 9 | use crate::messages::{DTLSMessageEvent, MessageEvent, TaggedMessageEvent}; 10 | use crate::server::states::ServerStates; 11 | use dtls::endpoint::EndpointEvent; 12 | use dtls::extension::extension_use_srtp::SrtpProtectionProfile; 13 | use dtls::state::State; 14 | use log::{debug, error, warn}; 15 | use retty::transport::TransportContext; 16 | use shared::error::{Error, Result}; 17 | use srtp::option::{srtcp_replay_protection, srtp_replay_protection}; 18 | use srtp::protection_profile::ProtectionProfile; 19 | 20 | /// DtlsHandler implements DTLS Protocol handling 21 | pub struct DtlsHandler { 22 | local_addr: SocketAddr, 23 | server_states: Rc>, 24 | transmits: VecDeque, 25 | } 26 | 27 | impl DtlsHandler { 28 | pub fn new(local_addr: SocketAddr, server_states: Rc>) -> Self { 29 | DtlsHandler { 30 | local_addr, 31 | server_states: Rc::clone(&server_states), 32 | transmits: VecDeque::new(), 33 | } 34 | } 35 | } 36 | 37 | impl Handler for DtlsHandler { 38 | type Rin = TaggedMessageEvent; 39 | type Rout = Self::Rin; 40 | type Win = TaggedMessageEvent; 41 | type Wout = Self::Win; 42 | 43 | fn name(&self) -> &str { 44 | "DtlsHandler" 45 | } 46 | 47 | fn handle_read( 48 | &mut self, 49 | ctx: &Context, 50 | msg: Self::Rin, 51 | ) { 52 | if let MessageEvent::Dtls(DTLSMessageEvent::Raw(dtls_message)) = msg.message { 53 | debug!("recv dtls RAW {:?}", msg.transport.peer_addr); 54 | let four_tuple = (&msg.transport).into(); 55 | 56 | let try_read = || -> Result> { 57 | let mut server_states = self.server_states.borrow_mut(); 58 | let transport = match server_states.get_mut_transport(&four_tuple) { 59 | Ok(transport) => transport, 60 | Err(err) => { 61 | error!("get_mut_transport got error {}, it may be due to DTLS packet received earlier than STUN Binding Request", err); 62 | return Err(err); 63 | } 64 | }; 65 | let mut messages = vec![]; 66 | let mut contexts = vec![]; 67 | 68 | { 69 | let dtls_endpoint = transport.get_mut_dtls_endpoint(); 70 | 71 | for message in dtls_endpoint.read( 72 | msg.now, 73 | msg.transport.peer_addr, 74 | Some(msg.transport.local_addr.ip()), 75 | msg.transport.ecn, 76 | dtls_message, 77 | )? { 78 | match message { 79 | EndpointEvent::HandshakeComplete => { 80 | if let Some(state) = 81 | dtls_endpoint.get_connection_state(msg.transport.peer_addr) 82 | { 83 | debug!("recv dtls handshake complete"); 84 | let (local_context, remote_context) = 85 | DtlsHandler::update_srtp_contexts(state)?; 86 | contexts.push((local_context, remote_context)); 87 | } else { 88 | warn!( 89 | "Unable to find connection state for {}", 90 | msg.transport.peer_addr 91 | ); 92 | } 93 | } 94 | EndpointEvent::ApplicationData(message) => { 95 | debug!("recv dtls application RAW {:?}", msg.transport.peer_addr); 96 | messages.push(message); 97 | } 98 | } 99 | } 100 | 101 | while let Some(transmit) = dtls_endpoint.poll_transmit() { 102 | self.transmits.push_back(TaggedMessageEvent { 103 | now: transmit.now, 104 | transport: TransportContext { 105 | local_addr: self.local_addr, 106 | peer_addr: transmit.remote, 107 | ecn: transmit.ecn, 108 | }, 109 | message: MessageEvent::Dtls(DTLSMessageEvent::Raw(transmit.payload)), 110 | }); 111 | } 112 | } 113 | 114 | for (local_context, remote_context) in contexts { 115 | transport.set_local_srtp_context(local_context); 116 | transport.set_remote_srtp_context(remote_context); 117 | } 118 | 119 | Ok(messages) 120 | }; 121 | 122 | match try_read() { 123 | Ok(messages) => { 124 | for message in messages { 125 | debug!("recv dtls application RAW {:?}", msg.transport.peer_addr); 126 | ctx.fire_read(TaggedMessageEvent { 127 | now: msg.now, 128 | transport: msg.transport, 129 | message: MessageEvent::Dtls(DTLSMessageEvent::Raw(message)), 130 | }); 131 | } 132 | } 133 | Err(err) => { 134 | error!("try_read with error {}", err); 135 | if err == Error::ErrAlertFatalOrClose { 136 | let mut server_states = self.server_states.borrow_mut(); 137 | server_states.remove_transport(four_tuple); 138 | } else { 139 | ctx.fire_exception(Box::new(err)) 140 | } 141 | } 142 | }; 143 | } else { 144 | // Bypass 145 | debug!("bypass dtls read {:?}", msg.transport.peer_addr); 146 | ctx.fire_read(msg); 147 | } 148 | } 149 | 150 | fn handle_timeout( 151 | &mut self, 152 | ctx: &Context, 153 | now: Instant, 154 | ) { 155 | let mut try_timeout = || -> Result<()> { 156 | let mut server_states = self.server_states.borrow_mut(); 157 | for session in server_states.get_mut_sessions().values_mut() { 158 | for endpoint in session.get_mut_endpoints().values_mut() { 159 | for transport in endpoint.get_mut_transports().values_mut() { 160 | let dtls_endpoint = transport.get_mut_dtls_endpoint(); 161 | let remotes: Vec = 162 | dtls_endpoint.get_connections_keys().copied().collect(); 163 | for remote in remotes { 164 | let _ = dtls_endpoint.handle_timeout(remote, now); 165 | } 166 | while let Some(transmit) = dtls_endpoint.poll_transmit() { 167 | self.transmits.push_back(TaggedMessageEvent { 168 | now: transmit.now, 169 | transport: TransportContext { 170 | local_addr: self.local_addr, 171 | peer_addr: transmit.remote, 172 | ecn: transmit.ecn, 173 | }, 174 | message: MessageEvent::Dtls(DTLSMessageEvent::Raw( 175 | transmit.payload, 176 | )), 177 | }); 178 | } 179 | } 180 | } 181 | } 182 | 183 | Ok(()) 184 | }; 185 | match try_timeout() { 186 | Ok(_) => {} 187 | Err(err) => { 188 | error!("try_timeout with error {}", err); 189 | ctx.fire_exception(Box::new(err)); 190 | } 191 | } 192 | 193 | ctx.fire_timeout(now); 194 | } 195 | 196 | fn poll_timeout( 197 | &mut self, 198 | ctx: &Context, 199 | eto: &mut Instant, 200 | ) { 201 | { 202 | let server_states = self.server_states.borrow(); 203 | for session in server_states.get_sessions().values() { 204 | for endpoint in session.get_endpoints().values() { 205 | for transport in endpoint.get_transports().values() { 206 | let dtls_endpoint = transport.get_dtls_endpoint(); 207 | let remotes = dtls_endpoint.get_connections_keys(); 208 | for remote in remotes { 209 | let _ = dtls_endpoint.poll_timeout(*remote, eto); 210 | } 211 | } 212 | } 213 | } 214 | } 215 | ctx.fire_poll_timeout(eto); 216 | } 217 | 218 | fn poll_write( 219 | &mut self, 220 | ctx: &Context, 221 | ) -> Option { 222 | if let Some(msg) = ctx.fire_poll_write() { 223 | if let MessageEvent::Dtls(DTLSMessageEvent::Raw(dtls_message)) = msg.message { 224 | debug!("send dtls RAW {:?}", msg.transport.peer_addr); 225 | let four_tuple = (&msg.transport).into(); 226 | 227 | let mut try_write = || -> Result<()> { 228 | let mut server_states = self.server_states.borrow_mut(); 229 | let transport = server_states.get_mut_transport(&four_tuple)?; 230 | let dtls_endpoint = transport.get_mut_dtls_endpoint(); 231 | 232 | dtls_endpoint.write(msg.transport.peer_addr, &dtls_message)?; 233 | while let Some(transmit) = dtls_endpoint.poll_transmit() { 234 | self.transmits.push_back(TaggedMessageEvent { 235 | now: transmit.now, 236 | transport: TransportContext { 237 | local_addr: self.local_addr, 238 | peer_addr: transmit.remote, 239 | ecn: transmit.ecn, 240 | }, 241 | message: MessageEvent::Dtls(DTLSMessageEvent::Raw(transmit.payload)), 242 | }); 243 | } 244 | 245 | Ok(()) 246 | }; 247 | 248 | match try_write() { 249 | Ok(_) => {} 250 | Err(err) => { 251 | error!("try_write with error {}", err); 252 | ctx.fire_exception(Box::new(err)); 253 | } 254 | } 255 | } else { 256 | // Bypass 257 | debug!("Bypass dtls write {:?}", msg.transport.peer_addr); 258 | self.transmits.push_back(msg); 259 | } 260 | } 261 | 262 | self.transmits.pop_front() 263 | } 264 | } 265 | 266 | impl DtlsHandler { 267 | const DEFAULT_SESSION_SRTP_REPLAY_PROTECTION_WINDOW: usize = 64; 268 | const DEFAULT_SESSION_SRTCP_REPLAY_PROTECTION_WINDOW: usize = 64; 269 | pub(crate) fn update_srtp_contexts( 270 | state: &State, 271 | ) -> Result<(srtp::context::Context, srtp::context::Context)> { 272 | let profile = match state.srtp_protection_profile() { 273 | SrtpProtectionProfile::Srtp_Aes128_Cm_Hmac_Sha1_80 => { 274 | ProtectionProfile::Aes128CmHmacSha1_80 275 | } 276 | SrtpProtectionProfile::Srtp_Aead_Aes_128_Gcm => ProtectionProfile::AeadAes128Gcm, 277 | _ => return Err(Error::ErrNoSuchSrtpProfile), 278 | }; 279 | 280 | let mut srtp_config = srtp::config::Config { 281 | profile, 282 | ..Default::default() 283 | }; 284 | /*if self.setting_engine.replay_protection.srtp != 0 { 285 | srtp_config.remote_rtp_options = Some(srtp::option::srtp_replay_protection( 286 | self.setting_engine.replay_protection.srtp, 287 | )); 288 | } else if self.setting_engine.disable_srtp_replay_protection { 289 | srtp_config.remote_rtp_options = Some(srtp::option::srtp_no_replay_protection()); 290 | }*/ 291 | 292 | srtp_config.extract_session_keys_from_dtls(state, false)?; 293 | 294 | let local_context = srtp::context::Context::new( 295 | &srtp_config.keys.local_master_key, 296 | &srtp_config.keys.local_master_salt, 297 | srtp_config.profile, 298 | srtp_config.local_rtp_options, 299 | srtp_config.local_rtcp_options, 300 | )?; 301 | 302 | let remote_context = srtp::context::Context::new( 303 | &srtp_config.keys.remote_master_key, 304 | &srtp_config.keys.remote_master_salt, 305 | srtp_config.profile, 306 | if srtp_config.remote_rtp_options.is_none() { 307 | Some(srtp_replay_protection( 308 | Self::DEFAULT_SESSION_SRTP_REPLAY_PROTECTION_WINDOW, 309 | )) 310 | } else { 311 | srtp_config.remote_rtp_options 312 | }, 313 | if srtp_config.remote_rtcp_options.is_none() { 314 | Some(srtcp_replay_protection( 315 | Self::DEFAULT_SESSION_SRTCP_REPLAY_PROTECTION_WINDOW, 316 | )) 317 | } else { 318 | srtp_config.remote_rtcp_options 319 | }, 320 | )?; 321 | 322 | Ok((local_context, remote_context)) 323 | } 324 | } 325 | -------------------------------------------------------------------------------- /src/handlers/exception.rs: -------------------------------------------------------------------------------- 1 | use crate::messages::TaggedMessageEvent; 2 | use log::error; 3 | use retty::channel::{Context, Handler}; 4 | use std::error::Error; 5 | 6 | /// ExceptionHandler implements exception handling for inbound or outbound directions 7 | #[derive(Default)] 8 | pub struct ExceptionHandler; 9 | 10 | impl ExceptionHandler { 11 | pub fn new() -> Self { 12 | ExceptionHandler 13 | } 14 | } 15 | 16 | impl Handler for ExceptionHandler { 17 | type Rin = TaggedMessageEvent; 18 | type Rout = TaggedMessageEvent; 19 | type Win = TaggedMessageEvent; 20 | type Wout = TaggedMessageEvent; 21 | 22 | fn name(&self) -> &str { 23 | "ExceptionHandler" 24 | } 25 | 26 | fn handle_read( 27 | &mut self, 28 | ctx: &Context, 29 | msg: Self::Rin, 30 | ) { 31 | ctx.fire_read(msg); 32 | } 33 | 34 | fn handle_exception( 35 | &mut self, 36 | ctx: &Context, 37 | err: Box, 38 | ) { 39 | error!("ExceptionHandler::read_exception {}", err); 40 | ctx.fire_exception(err); 41 | } 42 | 43 | fn poll_write( 44 | &mut self, 45 | ctx: &Context, 46 | ) -> Option { 47 | ctx.fire_poll_write() 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/handlers/interceptor.rs: -------------------------------------------------------------------------------- 1 | use crate::interceptors::InterceptorEvent; 2 | use crate::messages::{MessageEvent, RTPMessageEvent, TaggedMessageEvent}; 3 | use crate::types::FourTuple; 4 | use crate::ServerStates; 5 | use log::{debug, error}; 6 | use retty::channel::{Context, Handler}; 7 | use shared::error::Result; 8 | use std::cell::RefCell; 9 | use std::collections::VecDeque; 10 | use std::rc::Rc; 11 | use std::time::Instant; 12 | 13 | /// InterceptorHandler implements RTCP feedback handling 14 | pub struct InterceptorHandler { 15 | server_states: Rc>, 16 | transmits: VecDeque, 17 | } 18 | 19 | impl InterceptorHandler { 20 | pub fn new(server_states: Rc>) -> Self { 21 | Self { 22 | server_states, 23 | transmits: VecDeque::new(), 24 | } 25 | } 26 | } 27 | 28 | impl Handler for InterceptorHandler { 29 | type Rin = TaggedMessageEvent; 30 | type Rout = Self::Rin; 31 | type Win = TaggedMessageEvent; 32 | type Wout = Self::Win; 33 | 34 | fn name(&self) -> &str { 35 | "InterceptorHandler" 36 | } 37 | 38 | fn handle_read( 39 | &mut self, 40 | ctx: &Context, 41 | mut msg: Self::Rin, 42 | ) { 43 | if let MessageEvent::Rtp(RTPMessageEvent::Rtp(_)) 44 | | MessageEvent::Rtp(RTPMessageEvent::Rtcp(_)) = &msg.message 45 | { 46 | let mut try_read = || -> Result> { 47 | let mut server_states = self.server_states.borrow_mut(); 48 | let four_tuple = (&msg.transport).into(); 49 | let endpoint = server_states.get_mut_endpoint(&four_tuple)?; 50 | let interceptor = endpoint.get_mut_interceptor(); 51 | Ok(interceptor.read(&mut msg)) 52 | }; 53 | 54 | match try_read() { 55 | Ok(events) => { 56 | for event in events { 57 | match event { 58 | InterceptorEvent::Inbound(inbound) => { 59 | debug!("interceptor forward Rtcp {:?}", msg.transport.peer_addr); 60 | ctx.fire_read(inbound); 61 | } 62 | InterceptorEvent::Outbound(outbound) => { 63 | self.transmits.push_back(outbound); 64 | } 65 | InterceptorEvent::Error(err) => { 66 | error!("try_read got error {}", err); 67 | ctx.fire_exception(err); 68 | } 69 | } 70 | } 71 | } 72 | Err(err) => { 73 | error!("try_read with error {}", err); 74 | ctx.fire_exception(Box::new(err)) 75 | } 76 | }; 77 | 78 | if let MessageEvent::Rtp(RTPMessageEvent::Rtcp(_)) = &msg.message { 79 | // RTCP message read must end here in SFU case. If any rtcp packet needs to be forwarded to other Endpoints, 80 | // just add a new interceptor to forward it. 81 | debug!("interceptor terminates Rtcp {:?}", msg.transport.peer_addr); 82 | return; 83 | } 84 | } 85 | 86 | debug!("interceptor read bypass {:?}", msg.transport.peer_addr); 87 | ctx.fire_read(msg); 88 | } 89 | 90 | fn handle_timeout( 91 | &mut self, 92 | ctx: &Context, 93 | now: Instant, 94 | ) { 95 | let try_handle_timeout = || -> Result> { 96 | let mut interceptor_events = vec![]; 97 | 98 | let mut server_states = self.server_states.borrow_mut(); 99 | let sessions = server_states.get_mut_sessions(); 100 | for session in sessions.values_mut() { 101 | let endpoints = session.get_mut_endpoints(); 102 | for endpoint in endpoints.values_mut() { 103 | #[allow(clippy::map_clone)] 104 | let four_tuples: Vec = endpoint 105 | .get_transports() 106 | .keys() 107 | .map(|four_tuple| *four_tuple) 108 | .collect(); 109 | let interceptor = endpoint.get_mut_interceptor(); 110 | let mut events = interceptor.handle_timeout(now, &four_tuples); 111 | interceptor_events.append(&mut events); 112 | } 113 | } 114 | 115 | Ok(interceptor_events) 116 | }; 117 | 118 | match try_handle_timeout() { 119 | Ok(events) => { 120 | for event in events { 121 | match event { 122 | InterceptorEvent::Inbound(_) => { 123 | error!("unexpected inbound message from try_handle_timeout"); 124 | } 125 | InterceptorEvent::Outbound(outbound) => { 126 | self.transmits.push_back(outbound); 127 | } 128 | InterceptorEvent::Error(err) => { 129 | error!("try_read got error {}", err); 130 | ctx.fire_exception(err); 131 | } 132 | } 133 | } 134 | } 135 | Err(err) => { 136 | error!("try_handle_timeout with error {}", err); 137 | ctx.fire_exception(Box::new(err)) 138 | } 139 | } 140 | 141 | ctx.fire_timeout(now); 142 | } 143 | 144 | fn poll_timeout( 145 | &mut self, 146 | ctx: &Context, 147 | eto: &mut Instant, 148 | ) { 149 | { 150 | let mut server_states = self.server_states.borrow_mut(); 151 | let sessions = server_states.get_mut_sessions(); 152 | for session in sessions.values_mut() { 153 | let endpoints = session.get_mut_endpoints(); 154 | for endpoint in endpoints.values_mut() { 155 | let interceptor = endpoint.get_mut_interceptor(); 156 | interceptor.poll_timeout(eto) 157 | } 158 | } 159 | } 160 | 161 | ctx.fire_poll_timeout(eto); 162 | } 163 | 164 | fn poll_write( 165 | &mut self, 166 | ctx: &Context, 167 | ) -> Option { 168 | if let Some(mut msg) = ctx.fire_poll_write() { 169 | if let MessageEvent::Rtp(RTPMessageEvent::Rtp(_)) 170 | | MessageEvent::Rtp(RTPMessageEvent::Rtcp(_)) = &msg.message 171 | { 172 | let mut try_write = || -> Result> { 173 | let mut server_states = self.server_states.borrow_mut(); 174 | let four_tuple = (&msg.transport).into(); 175 | let endpoint = server_states.get_mut_endpoint(&four_tuple)?; 176 | let interceptor = endpoint.get_mut_interceptor(); 177 | Ok(interceptor.write(&mut msg)) 178 | }; 179 | 180 | match try_write() { 181 | Ok(events) => { 182 | for event in events { 183 | match event { 184 | InterceptorEvent::Inbound(_) => { 185 | error!("unexpected inbound message from try_write"); 186 | } 187 | InterceptorEvent::Outbound(outbound) => { 188 | self.transmits.push_back(outbound); 189 | } 190 | InterceptorEvent::Error(err) => { 191 | error!("try_write got error {}", err); 192 | ctx.fire_exception(err); 193 | } 194 | } 195 | } 196 | } 197 | Err(err) => { 198 | error!("try_write with error {}", err); 199 | ctx.fire_exception(Box::new(err)) 200 | } 201 | }; 202 | } 203 | 204 | debug!("interceptor write {:?}", msg.transport.peer_addr); 205 | self.transmits.push_back(msg); 206 | } 207 | 208 | self.transmits.pop_front() 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /src/handlers/mod.rs: -------------------------------------------------------------------------------- 1 | pub(crate) mod datachannel; 2 | pub(crate) mod demuxer; 3 | pub(crate) mod dtls; 4 | pub(crate) mod exception; 5 | pub(crate) mod gateway; 6 | pub(crate) mod interceptor; 7 | pub(crate) mod sctp; 8 | pub(crate) mod srtp; 9 | pub(crate) mod stun; 10 | -------------------------------------------------------------------------------- /src/handlers/srtp.rs: -------------------------------------------------------------------------------- 1 | use crate::messages::{MessageEvent, RTPMessageEvent, TaggedMessageEvent}; 2 | use crate::server::states::ServerStates; 3 | use bytes::BytesMut; 4 | use log::{debug, error}; 5 | use retty::channel::{Context, Handler}; 6 | use shared::{ 7 | error::{Error, Result}, 8 | marshal::{Marshal, Unmarshal}, 9 | util::is_rtcp, 10 | }; 11 | use std::cell::RefCell; 12 | use std::rc::Rc; 13 | use std::time::Instant; 14 | 15 | /// SrtpHandler implements SRTP/RTP/RTCP Protocols handling 16 | pub struct SrtpHandler { 17 | server_states: Rc>, 18 | } 19 | 20 | impl SrtpHandler { 21 | pub fn new(server_states: Rc>) -> Self { 22 | SrtpHandler { server_states } 23 | } 24 | } 25 | 26 | impl Handler for SrtpHandler { 27 | type Rin = TaggedMessageEvent; 28 | type Rout = Self::Rin; 29 | type Win = TaggedMessageEvent; 30 | type Wout = Self::Win; 31 | 32 | fn name(&self) -> &str { 33 | "SrtpHandler" 34 | } 35 | 36 | fn handle_read( 37 | &mut self, 38 | ctx: &Context, 39 | mut msg: Self::Rin, 40 | ) { 41 | if let MessageEvent::Rtp(RTPMessageEvent::Raw(message)) = msg.message { 42 | debug!("srtp read {:?}", msg.transport.peer_addr); 43 | let try_read = || -> Result { 44 | let four_tuple = (&msg.transport).into(); 45 | let mut server_states = self.server_states.borrow_mut(); 46 | let transport = server_states.get_mut_transport(&four_tuple)?; 47 | 48 | if is_rtcp(&message) { 49 | let mut remote_context = transport.remote_srtp_context(); 50 | if let Some(context) = remote_context.as_mut() { 51 | let mut decrypted = context.decrypt_rtcp(&message)?; 52 | let rtcp_packets = rtcp::packet::unmarshal(&mut decrypted)?; 53 | if rtcp_packets.is_empty() { 54 | return Err(Error::Other("empty rtcp_packets".to_string())); 55 | } 56 | 57 | server_states.metrics().record_rtcp_packet_in_count(1, &[]); 58 | Ok(MessageEvent::Rtp(RTPMessageEvent::Rtcp(rtcp_packets))) 59 | } else { 60 | server_states 61 | .metrics() 62 | .record_remote_srtp_context_not_set_count(1, &[]); 63 | Err(Error::Other(format!( 64 | "remote_srtp_context is not set yet for four_tuple {:?}", 65 | four_tuple 66 | ))) 67 | } 68 | } else { 69 | let mut remote_context = transport.remote_srtp_context(); 70 | if let Some(context) = remote_context.as_mut() { 71 | let mut decrypted = context.decrypt_rtp(&message)?; 72 | let rtp_packet = rtp::Packet::unmarshal(&mut decrypted)?; 73 | 74 | server_states.metrics().record_rtp_packet_in_count(1, &[]); 75 | Ok(MessageEvent::Rtp(RTPMessageEvent::Rtp(rtp_packet))) 76 | } else { 77 | server_states 78 | .metrics() 79 | .record_remote_srtp_context_not_set_count(1, &[]); 80 | Err(Error::Other(format!( 81 | "remote_srtp_context is not set yet for four_tuple {:?}", 82 | four_tuple 83 | ))) 84 | } 85 | } 86 | }; 87 | 88 | match try_read() { 89 | Ok(message) => { 90 | msg.message = message; 91 | ctx.fire_read(msg); 92 | } 93 | Err(err) => { 94 | error!("try_read got error {}", err); 95 | ctx.fire_exception(Box::new(err)) 96 | } 97 | }; 98 | } else { 99 | debug!("bypass srtp read {:?}", msg.transport.peer_addr); 100 | ctx.fire_read(msg); 101 | } 102 | } 103 | 104 | fn poll_write( 105 | &mut self, 106 | ctx: &Context, 107 | ) -> Option { 108 | if let Some(mut msg) = ctx.fire_poll_write() { 109 | if let MessageEvent::Rtp(message) = msg.message { 110 | debug!("srtp write {:?}", msg.transport.peer_addr); 111 | let try_write = || -> Result { 112 | let four_tuple = (&msg.transport).into(); 113 | let mut server_states = self.server_states.borrow_mut(); 114 | let transport = server_states.get_mut_transport(&four_tuple)?; 115 | 116 | match message { 117 | RTPMessageEvent::Rtcp(rtcp_packets) => { 118 | if rtcp_packets.is_empty() { 119 | return Err(Error::Other("empty rtcp_packets".to_string())); 120 | }; 121 | 122 | let mut local_context = transport.local_srtp_context(); 123 | if let Some(context) = local_context.as_mut() { 124 | let packet = rtcp::packet::marshal(&rtcp_packets)?; 125 | let rtcp_packet = context.encrypt_rtcp(&packet); 126 | 127 | server_states.metrics().record_rtcp_packet_out_count(1, &[]); 128 | server_states.metrics().record_rtcp_packet_processing_time( 129 | Instant::now().duration_since(msg.now).as_micros() as u64, 130 | &[], 131 | ); 132 | rtcp_packet 133 | } else { 134 | server_states 135 | .metrics() 136 | .record_local_srtp_context_not_set_count(1, &[]); 137 | 138 | Err(Error::Other(format!( 139 | "local_srtp_context is not set yet for four_tuple {:?}", 140 | four_tuple 141 | ))) 142 | } 143 | } 144 | RTPMessageEvent::Rtp(rtp_message) => { 145 | let mut local_context = transport.local_srtp_context(); 146 | if let Some(context) = local_context.as_mut() { 147 | let packet = rtp_message.marshal()?; 148 | let rtp_packet = context.encrypt_rtp(&packet); 149 | 150 | server_states.metrics().record_rtp_packet_out_count(1, &[]); 151 | server_states.metrics().record_rtp_packet_processing_time( 152 | Instant::now().duration_since(msg.now).as_micros() as u64, 153 | &[], 154 | ); 155 | rtp_packet 156 | } else { 157 | server_states 158 | .metrics() 159 | .record_local_srtp_context_not_set_count(1, &[]); 160 | 161 | Err(Error::Other(format!( 162 | "local_srtp_context is not set yet for four_tuple {:?}", 163 | four_tuple 164 | ))) 165 | } 166 | } 167 | RTPMessageEvent::Raw(raw_packet) => { 168 | // Bypass 169 | debug!("Bypass srtp write {:?}", msg.transport.peer_addr); 170 | Ok(raw_packet) 171 | } 172 | } 173 | }; 174 | 175 | match try_write() { 176 | Ok(encrypted) => { 177 | msg.message = MessageEvent::Rtp(RTPMessageEvent::Raw(encrypted)); 178 | Some(msg) 179 | } 180 | Err(err) => { 181 | error!("try_write with error {}", err); 182 | ctx.fire_exception(Box::new(err)); 183 | None 184 | } 185 | } 186 | } else { 187 | // Bypass 188 | debug!("Bypass srtp write {:?}", msg.transport.peer_addr); 189 | Some(msg) 190 | } 191 | } else { 192 | None 193 | } 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /src/handlers/stun.rs: -------------------------------------------------------------------------------- 1 | use crate::messages::{MessageEvent, STUNMessageEvent, TaggedMessageEvent}; 2 | use bytes::BytesMut; 3 | use log::{debug, warn}; 4 | use retty::channel::{Context, Handler}; 5 | use shared::error::Result; 6 | use stun::message::Message; 7 | 8 | /// StunHandler implements STUN Protocol handling 9 | #[derive(Default)] 10 | pub struct StunHandler; 11 | 12 | impl StunHandler { 13 | pub fn new() -> Self { 14 | StunHandler 15 | } 16 | } 17 | 18 | impl Handler for StunHandler { 19 | type Rin = TaggedMessageEvent; 20 | type Rout = Self::Rin; 21 | type Win = TaggedMessageEvent; 22 | type Wout = Self::Win; 23 | 24 | fn name(&self) -> &str { 25 | "StunHandler" 26 | } 27 | 28 | fn handle_read( 29 | &mut self, 30 | ctx: &Context, 31 | msg: Self::Rin, 32 | ) { 33 | if let MessageEvent::Stun(STUNMessageEvent::Raw(message)) = msg.message { 34 | let try_read = || -> Result { 35 | let mut stun_message = Message { 36 | raw: message.to_vec(), 37 | ..Default::default() 38 | }; 39 | stun_message.decode()?; 40 | debug!( 41 | "StunMessage type {} received from {}", 42 | stun_message.typ, msg.transport.peer_addr 43 | ); 44 | Ok(stun_message) 45 | }; 46 | 47 | match try_read() { 48 | Ok(stun_message) => { 49 | ctx.fire_read(TaggedMessageEvent { 50 | now: msg.now, 51 | transport: msg.transport, 52 | message: MessageEvent::Stun(STUNMessageEvent::Stun(stun_message)), 53 | }); 54 | } 55 | Err(err) => { 56 | warn!("try_read got error {}", err); 57 | ctx.fire_exception(Box::new(err)); 58 | } 59 | } 60 | } else { 61 | debug!("bypass StunHandler read for {}", msg.transport.peer_addr); 62 | ctx.fire_read(msg); 63 | } 64 | } 65 | 66 | fn poll_write( 67 | &mut self, 68 | ctx: &Context, 69 | ) -> Option { 70 | if let Some(msg) = ctx.fire_poll_write() { 71 | if let MessageEvent::Stun(STUNMessageEvent::Stun(mut stun_message)) = msg.message { 72 | debug!( 73 | "StunMessage type {} sent to {}", 74 | stun_message.typ, msg.transport.peer_addr 75 | ); 76 | stun_message.encode(); 77 | let message = BytesMut::from(&stun_message.raw[..]); 78 | Some(TaggedMessageEvent { 79 | now: msg.now, 80 | transport: msg.transport, 81 | message: MessageEvent::Stun(STUNMessageEvent::Raw(message)), 82 | }) 83 | } else { 84 | debug!("bypass StunHandler write for {}", msg.transport.peer_addr); 85 | Some(msg) 86 | } 87 | } else { 88 | None 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/interceptors/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::messages::TaggedMessageEvent; 2 | use crate::types::FourTuple; 3 | use std::time::Instant; 4 | 5 | pub(crate) mod nack; 6 | pub(crate) mod report; 7 | pub(crate) mod twcc; 8 | 9 | pub enum InterceptorEvent { 10 | Inbound(TaggedMessageEvent), 11 | Outbound(TaggedMessageEvent), 12 | Error(Box), 13 | } 14 | 15 | pub trait Interceptor { 16 | fn chain(self: Box, next: Box) -> Box; 17 | fn next(&mut self) -> Option<&mut Box>; 18 | 19 | fn read(&mut self, msg: &mut TaggedMessageEvent) -> Vec { 20 | if let Some(next) = self.next() { 21 | next.read(msg) 22 | } else { 23 | vec![] 24 | } 25 | } 26 | fn write(&mut self, msg: &mut TaggedMessageEvent) -> Vec { 27 | if let Some(next) = self.next() { 28 | next.write(msg) 29 | } else { 30 | vec![] 31 | } 32 | } 33 | 34 | fn handle_timeout(&mut self, now: Instant, four_tuples: &[FourTuple]) -> Vec { 35 | if let Some(next) = self.next() { 36 | next.handle_timeout(now, four_tuples) 37 | } else { 38 | vec![] 39 | } 40 | } 41 | 42 | fn poll_timeout(&mut self, eto: &mut Instant) { 43 | if let Some(next) = self.next() { 44 | next.poll_timeout(eto); 45 | } 46 | } 47 | } 48 | 49 | /// InterceptorBuilder provides an interface for constructing interceptors 50 | pub trait InterceptorBuilder { 51 | fn build(&self, id: &str) -> Box; 52 | } 53 | 54 | /// Registry is a collector for interceptors. 55 | #[derive(Default)] 56 | pub struct Registry { 57 | builders: Vec>, 58 | } 59 | 60 | impl Registry { 61 | pub fn new() -> Self { 62 | Registry::default() 63 | } 64 | 65 | /// add a new InterceptorBuilder to the registry. 66 | pub fn add(&mut self, builder: Box) { 67 | self.builders.push(builder); 68 | } 69 | 70 | /// build a single Interceptor from an InterceptorRegistry 71 | pub fn build(&self, id: &str) -> Box { 72 | let mut next = Box::new(NoOp) as Box; 73 | for interceptor in self.builders.iter().rev().map(|b| b.build(id)) { 74 | next = interceptor.chain(next); 75 | } 76 | next 77 | } 78 | } 79 | 80 | /// NoOp is an Interceptor that does not modify any packets. It can be embedded in other interceptors, so it's 81 | /// possible to implement only a subset of the methods. 82 | struct NoOp; 83 | 84 | impl Interceptor for NoOp { 85 | fn chain(self: Box, _next: Box) -> Box { 86 | self 87 | } 88 | 89 | fn next(&mut self) -> Option<&mut Box> { 90 | None 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/interceptors/nack/mod.rs: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/interceptors/report/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::interceptors::{Interceptor, InterceptorBuilder}; 2 | use std::collections::HashMap; 3 | use std::time::{Duration, Instant}; 4 | 5 | pub(crate) mod receiver_report; 6 | pub(crate) mod receiver_stream; 7 | pub(crate) mod sender_report; 8 | 9 | use receiver_report::ReceiverReport; 10 | use sender_report::SenderReport; 11 | 12 | /// ReceiverBuilder can be used to configure ReceiverReport Interceptor. 13 | #[derive(Default)] 14 | pub struct ReportBuilder { 15 | is_rr: bool, 16 | interval: Option, 17 | } 18 | 19 | impl ReportBuilder { 20 | /// with_interval sets send interval for the interceptor. 21 | pub fn with_interval(mut self, interval: Duration) -> ReportBuilder { 22 | self.interval = Some(interval); 23 | self 24 | } 25 | 26 | fn build_rr(&self) -> ReceiverReport { 27 | ReceiverReport { 28 | interval: if let Some(interval) = &self.interval { 29 | *interval 30 | } else { 31 | Duration::from_secs(1) //TODO: make it configurable 32 | }, 33 | eto: Instant::now(), 34 | streams: HashMap::new(), 35 | next: None, 36 | } 37 | } 38 | 39 | fn build_sr(&self) -> SenderReport { 40 | SenderReport { next: None } 41 | } 42 | } 43 | 44 | impl InterceptorBuilder for ReportBuilder { 45 | fn build(&self, _id: &str) -> Box { 46 | if self.is_rr { 47 | Box::new(self.build_rr()) 48 | } else { 49 | Box::new(self.build_sr()) 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/interceptors/report/receiver_report.rs: -------------------------------------------------------------------------------- 1 | use crate::interceptors::report::receiver_stream::ReceiverStream; 2 | use crate::interceptors::report::ReportBuilder; 3 | use crate::interceptors::{Interceptor, InterceptorEvent}; 4 | use crate::messages::{MessageEvent, RTPMessageEvent, TaggedMessageEvent}; 5 | use crate::types::FourTuple; 6 | use retty::transport::TransportContext; 7 | use std::collections::HashMap; 8 | use std::time::{Duration, Instant}; 9 | 10 | pub(crate) struct ReceiverReport { 11 | pub(super) interval: Duration, 12 | pub(super) eto: Instant, 13 | pub(crate) streams: HashMap, 14 | pub(super) next: Option>, 15 | } 16 | 17 | impl ReceiverReport { 18 | pub(crate) fn builder() -> ReportBuilder { 19 | ReportBuilder { 20 | is_rr: true, 21 | ..Default::default() 22 | } 23 | } 24 | } 25 | 26 | impl Interceptor for ReceiverReport { 27 | fn chain(mut self: Box, next: Box) -> Box { 28 | self.next = Some(next); 29 | self 30 | } 31 | 32 | fn next(&mut self) -> Option<&mut Box> { 33 | self.next.as_mut() 34 | } 35 | 36 | fn read(&mut self, msg: &mut TaggedMessageEvent) -> Vec { 37 | if let MessageEvent::Rtp(RTPMessageEvent::Rtcp(rtcp_packets)) = &msg.message { 38 | for rtcp_packet in rtcp_packets { 39 | if let Some(sr) = rtcp_packet 40 | .as_any() 41 | .downcast_ref::() 42 | { 43 | if let Some(stream) = self.streams.get_mut(&sr.ssrc) { 44 | stream.process_sender_report(msg.now, sr); 45 | } 46 | } 47 | } 48 | } else if let MessageEvent::Rtp(RTPMessageEvent::Rtp(rtp_packet)) = &msg.message { 49 | if let Some(stream) = self.streams.get_mut(&rtp_packet.header.ssrc) { 50 | stream.process_rtp(msg.now, rtp_packet); 51 | } 52 | } 53 | 54 | if let Some(next) = self.next() { 55 | next.read(msg) 56 | } else { 57 | vec![] 58 | } 59 | } 60 | 61 | fn handle_timeout(&mut self, now: Instant, four_tuples: &[FourTuple]) -> Vec { 62 | let mut interceptor_events = vec![]; 63 | 64 | if self.eto <= now { 65 | self.eto = now + self.interval; 66 | 67 | for stream in self.streams.values_mut() { 68 | let rr = stream.generate_report(now); 69 | for four_tuple in four_tuples { 70 | interceptor_events.push(InterceptorEvent::Outbound(TaggedMessageEvent { 71 | now, 72 | transport: TransportContext { 73 | local_addr: four_tuple.local_addr, 74 | peer_addr: four_tuple.peer_addr, 75 | ecn: None, 76 | }, 77 | message: MessageEvent::Rtp(RTPMessageEvent::Rtcp(vec![Box::new( 78 | rr.clone(), 79 | )])), 80 | })); 81 | } 82 | } 83 | } 84 | 85 | if let Some(next) = self.next() { 86 | let mut events = next.handle_timeout(now, four_tuples); 87 | interceptor_events.append(&mut events); 88 | } 89 | interceptor_events 90 | } 91 | 92 | fn poll_timeout(&mut self, eto: &mut Instant) { 93 | if self.eto < *eto { 94 | *eto = self.eto 95 | } 96 | 97 | if let Some(next) = self.next() { 98 | next.poll_timeout(eto); 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/interceptors/report/receiver_stream.rs: -------------------------------------------------------------------------------- 1 | use std::time::Instant; 2 | 3 | pub(crate) struct ReceiverStream { 4 | ssrc: u32, 5 | receiver_ssrc: u32, 6 | clock_rate: f64, 7 | 8 | packets: Vec, 9 | started: bool, 10 | seq_num_cycles: u16, 11 | last_seq_num: i32, 12 | last_report_seq_num: i32, 13 | last_rtp_time_rtp: u32, 14 | last_rtp_time_time: Instant, 15 | jitter: f64, 16 | last_sender_report: u32, 17 | last_sender_report_time: Instant, 18 | total_lost: u32, 19 | } 20 | 21 | impl ReceiverStream { 22 | pub(crate) fn new(ssrc: u32, clock_rate: u32) -> Self { 23 | Self { 24 | ssrc, 25 | receiver_ssrc: rand::random::(), 26 | clock_rate: clock_rate as f64, 27 | 28 | packets: vec![0u64; 128], 29 | started: false, 30 | seq_num_cycles: 0, 31 | last_seq_num: 0, 32 | last_report_seq_num: 0, 33 | last_rtp_time_rtp: 0, 34 | last_rtp_time_time: Instant::now(), 35 | jitter: 0.0, 36 | last_sender_report: 0, 37 | last_sender_report_time: Instant::now(), 38 | total_lost: 0, 39 | } 40 | } 41 | 42 | fn set_received(&mut self, seq: u16) { 43 | let pos = (seq as usize) % self.packets.len(); 44 | self.packets[pos / 64] |= 1 << (pos % 64); 45 | } 46 | 47 | fn del_received(&mut self, seq: u16) { 48 | let pos = (seq as usize) % self.packets.len(); 49 | self.packets[pos / 64] &= u64::MAX ^ (1u64 << (pos % 64)); 50 | } 51 | 52 | fn get_received(&self, seq: u16) -> bool { 53 | let pos = (seq as usize) % self.packets.len(); 54 | (self.packets[pos / 64] & (1 << (pos % 64))) != 0 55 | } 56 | 57 | pub(crate) fn process_rtp(&mut self, now: Instant, pkt: &rtp::packet::Packet) { 58 | if !self.started { 59 | // first frame 60 | self.started = true; 61 | self.set_received(pkt.header.sequence_number); 62 | self.last_seq_num = pkt.header.sequence_number as i32; 63 | self.last_report_seq_num = pkt.header.sequence_number as i32 - 1; 64 | } else { 65 | // following frames 66 | self.set_received(pkt.header.sequence_number); 67 | 68 | let diff = pkt.header.sequence_number as i32 - self.last_seq_num; 69 | if !(-0x0FFF..=0).contains(&diff) { 70 | // overflow 71 | if diff < -0x0FFF { 72 | self.seq_num_cycles += 1; 73 | } 74 | 75 | // set missing packets as missing 76 | for i in self.last_seq_num + 1..pkt.header.sequence_number as i32 { 77 | self.del_received(i as u16); 78 | } 79 | 80 | self.last_seq_num = pkt.header.sequence_number as i32; 81 | } 82 | 83 | // compute jitter 84 | // https://tools.ietf.org/html/rfc3550#page-39 85 | let d = now.duration_since(self.last_rtp_time_time).as_secs_f64() * self.clock_rate 86 | - (pkt.header.timestamp as f64 - self.last_rtp_time_rtp as f64); 87 | self.jitter += (d.abs() - self.jitter) / 16.0; 88 | } 89 | 90 | self.last_rtp_time_rtp = pkt.header.timestamp; 91 | self.last_rtp_time_time = now; 92 | } 93 | 94 | pub(crate) fn process_sender_report( 95 | &mut self, 96 | now: Instant, 97 | sr: &rtcp::sender_report::SenderReport, 98 | ) { 99 | self.last_sender_report = (sr.ntp_time >> 16) as u32; 100 | self.last_sender_report_time = now; 101 | } 102 | 103 | pub(crate) fn generate_report( 104 | &mut self, 105 | now: Instant, 106 | ) -> rtcp::receiver_report::ReceiverReport { 107 | let total_since_report = (self.last_seq_num - self.last_report_seq_num) as u16; 108 | let mut total_lost_since_report = { 109 | if self.last_seq_num == self.last_report_seq_num { 110 | 0 111 | } else { 112 | let mut ret = 0u32; 113 | let mut i = (self.last_report_seq_num + 1) as u16; 114 | while i != self.last_seq_num as u16 { 115 | if !self.get_received(i) { 116 | ret += 1; 117 | } 118 | i = i.wrapping_add(1); 119 | } 120 | ret 121 | } 122 | }; 123 | 124 | self.total_lost += total_lost_since_report; 125 | 126 | // allow up to 24 bits 127 | if total_lost_since_report > 0xFFFFFF { 128 | total_lost_since_report = 0xFFFFFF; 129 | } 130 | if self.total_lost > 0xFFFFFF { 131 | self.total_lost = 0xFFFFFF 132 | } 133 | 134 | let r = rtcp::receiver_report::ReceiverReport { 135 | ssrc: self.receiver_ssrc, 136 | reports: vec![rtcp::reception_report::ReceptionReport { 137 | ssrc: self.ssrc, 138 | last_sequence_number: (self.seq_num_cycles as u32) << 16 139 | | (self.last_seq_num as u32), 140 | last_sender_report: self.last_sender_report, 141 | fraction_lost: ((total_lost_since_report * 256) as f64 / total_since_report as f64) 142 | as u8, 143 | total_lost: self.total_lost, 144 | delay: (now 145 | .duration_since(self.last_sender_report_time) 146 | .as_secs_f64() 147 | * 65536.0) as u32, 148 | jitter: self.jitter as u32, 149 | }], 150 | ..Default::default() 151 | }; 152 | 153 | self.last_report_seq_num = self.last_seq_num; 154 | 155 | r 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /src/interceptors/report/sender_report.rs: -------------------------------------------------------------------------------- 1 | use crate::interceptors::report::ReportBuilder; 2 | use crate::interceptors::{Interceptor, InterceptorEvent}; 3 | use crate::messages::{MessageEvent, RTPMessageEvent, TaggedMessageEvent}; 4 | use rtcp::header::PacketType; 5 | 6 | pub(crate) struct SenderReport { 7 | pub(super) next: Option>, 8 | } 9 | 10 | impl SenderReport { 11 | pub(crate) fn builder() -> ReportBuilder { 12 | ReportBuilder { 13 | is_rr: false, 14 | ..Default::default() 15 | } 16 | } 17 | } 18 | 19 | impl Interceptor for SenderReport { 20 | fn chain(mut self: Box, next: Box) -> Box { 21 | self.next = Some(next); 22 | self 23 | } 24 | 25 | fn next(&mut self) -> Option<&mut Box> { 26 | self.next.as_mut() 27 | } 28 | 29 | fn read(&mut self, msg: &mut TaggedMessageEvent) -> Vec { 30 | let mut interceptor_events = vec![]; 31 | 32 | if let MessageEvent::Rtp(RTPMessageEvent::Rtcp(rtcp_packets)) = &msg.message { 33 | let mut inbound_rtcp_packets = vec![]; 34 | 35 | for rtcp_packet in rtcp_packets { 36 | let packet_type = rtcp_packet.header().packet_type; 37 | if packet_type == PacketType::ReceiverReport 38 | || (packet_type == PacketType::TransportSpecificFeedback) 39 | { 40 | // let's not forward ReceiverReport and TransportSpecificFeedback 41 | // since they are hop by hop reports, instead of end to end reports 42 | continue; 43 | } else { 44 | inbound_rtcp_packets.push(rtcp_packet.clone()); 45 | } 46 | } 47 | 48 | if !inbound_rtcp_packets.is_empty() { 49 | interceptor_events.push(InterceptorEvent::Inbound(TaggedMessageEvent { 50 | now: msg.now, 51 | transport: msg.transport, 52 | message: MessageEvent::Rtp(RTPMessageEvent::Rtcp(inbound_rtcp_packets)), 53 | })); 54 | } 55 | } 56 | 57 | if let Some(next) = self.next() { 58 | let mut events = next.read(msg); 59 | interceptor_events.append(&mut events); 60 | } 61 | interceptor_events 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/interceptors/twcc/mod.rs: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![warn(rust_2018_idioms)] 2 | #![allow(dead_code)] 3 | 4 | pub(crate) mod configs; 5 | pub(crate) mod description; 6 | pub(crate) mod endpoint; 7 | pub(crate) mod handlers; 8 | pub(crate) mod interceptors; 9 | pub(crate) mod messages; 10 | pub(crate) mod metrics; 11 | pub(crate) mod server; 12 | pub(crate) mod session; 13 | pub(crate) mod types; 14 | 15 | pub use configs::{media_config::MediaConfig, server_config::ServerConfig}; 16 | pub use description::RTCSessionDescription; 17 | pub use handlers::{ 18 | datachannel::DataChannelHandler, demuxer::DemuxerHandler, dtls::DtlsHandler, 19 | exception::ExceptionHandler, gateway::GatewayHandler, interceptor::InterceptorHandler, 20 | sctp::SctpHandler, srtp::SrtpHandler, stun::StunHandler, 21 | }; 22 | pub use server::{certificate::RTCCertificate, states::ServerStates}; 23 | -------------------------------------------------------------------------------- /src/messages.rs: -------------------------------------------------------------------------------- 1 | use bytes::BytesMut; 2 | use retty::transport::TransportContext; 3 | use sctp::ReliabilityType; 4 | use std::time::Instant; 5 | 6 | #[derive(Debug, Copy, Clone, Eq, PartialEq)] 7 | pub(crate) enum DataChannelMessageType { 8 | None, 9 | Control, 10 | Binary, 11 | Text, 12 | } 13 | 14 | #[derive(Debug)] 15 | pub(crate) struct DataChannelMessageParams { 16 | pub(crate) unordered: bool, 17 | pub(crate) reliability_type: ReliabilityType, 18 | pub(crate) reliability_parameter: u32, 19 | } 20 | 21 | #[derive(Debug, Clone, Eq, PartialEq)] 22 | pub(crate) enum DataChannelEvent { 23 | Open, 24 | Message(BytesMut), 25 | Close, 26 | } 27 | 28 | #[derive(Debug)] 29 | pub struct DataChannelMessage { 30 | pub(crate) association_handle: usize, 31 | pub(crate) stream_id: u16, 32 | pub(crate) data_message_type: DataChannelMessageType, 33 | pub(crate) params: Option, 34 | pub(crate) payload: BytesMut, 35 | } 36 | 37 | #[derive(Debug)] 38 | pub struct ApplicationMessage { 39 | pub(crate) association_handle: usize, 40 | pub(crate) stream_id: u16, 41 | pub(crate) data_channel_event: DataChannelEvent, 42 | } 43 | 44 | #[derive(Debug)] 45 | pub enum STUNMessageEvent { 46 | Raw(BytesMut), 47 | Stun(stun::message::Message), 48 | } 49 | 50 | #[derive(Debug)] 51 | pub enum DTLSMessageEvent { 52 | Raw(BytesMut), 53 | Sctp(DataChannelMessage), 54 | DataChannel(ApplicationMessage), 55 | } 56 | 57 | #[derive(Debug)] 58 | pub enum RTPMessageEvent { 59 | Raw(BytesMut), 60 | Rtp(rtp::packet::Packet), 61 | Rtcp(Vec>), 62 | } 63 | 64 | #[derive(Debug)] 65 | pub enum MessageEvent { 66 | Stun(STUNMessageEvent), 67 | Dtls(DTLSMessageEvent), 68 | Rtp(RTPMessageEvent), 69 | } 70 | 71 | pub struct TaggedMessageEvent { 72 | pub now: Instant, 73 | pub transport: TransportContext, 74 | pub message: MessageEvent, 75 | } 76 | -------------------------------------------------------------------------------- /src/metrics/mod.rs: -------------------------------------------------------------------------------- 1 | use opentelemetry::{ 2 | metrics::{Counter, Meter, ObservableGauge, Unit}, 3 | KeyValue, 4 | }; 5 | 6 | pub(crate) struct Metrics { 7 | rtp_packet_in_count: Counter, 8 | rtp_packet_out_count: Counter, 9 | rtcp_packet_in_count: Counter, 10 | rtcp_packet_out_count: Counter, 11 | remote_srtp_context_not_set_count: Counter, 12 | local_srtp_context_not_set_count: Counter, 13 | rtp_packet_processing_time: ObservableGauge, 14 | rtcp_packet_processing_time: ObservableGauge, 15 | } 16 | 17 | impl Metrics { 18 | pub(crate) fn new(meter: Meter) -> Self { 19 | Self { 20 | rtp_packet_in_count: meter.u64_counter("rtp_packet_in_count").init(), 21 | rtp_packet_out_count: meter.u64_counter("rtp_packet_out_count").init(), 22 | rtcp_packet_in_count: meter.u64_counter("rtcp_packet_in_count").init(), 23 | rtcp_packet_out_count: meter.u64_counter("rtcp_packet_out_count").init(), 24 | remote_srtp_context_not_set_count: meter 25 | .u64_counter("remote_srtp_context_not_set_count") 26 | .init(), 27 | local_srtp_context_not_set_count: meter 28 | .u64_counter("local_srtp_context_not_set_count") 29 | .init(), 30 | rtp_packet_processing_time: meter 31 | .u64_observable_gauge("rtp_packet_processing_time") 32 | .with_unit(Unit::new("us")) 33 | .init(), 34 | rtcp_packet_processing_time: meter 35 | .u64_observable_gauge("rtcp_packet_processing_time") 36 | .with_unit(Unit::new("us")) 37 | .init(), 38 | } 39 | } 40 | 41 | pub(crate) fn record_rtp_packet_in_count(&self, value: u64, attributes: &[KeyValue]) { 42 | self.rtp_packet_in_count.add(value, attributes); 43 | } 44 | 45 | pub(crate) fn record_rtp_packet_out_count(&self, value: u64, attributes: &[KeyValue]) { 46 | self.rtp_packet_out_count.add(value, attributes); 47 | } 48 | 49 | pub(crate) fn record_rtcp_packet_in_count(&self, value: u64, attributes: &[KeyValue]) { 50 | self.rtcp_packet_in_count.add(value, attributes); 51 | } 52 | 53 | pub(crate) fn record_rtcp_packet_out_count(&self, value: u64, attributes: &[KeyValue]) { 54 | self.rtcp_packet_out_count.add(value, attributes); 55 | } 56 | 57 | pub(crate) fn record_remote_srtp_context_not_set_count( 58 | &self, 59 | value: u64, 60 | attributes: &[KeyValue], 61 | ) { 62 | self.remote_srtp_context_not_set_count 63 | .add(value, attributes); 64 | } 65 | 66 | pub(crate) fn record_local_srtp_context_not_set_count( 67 | &self, 68 | value: u64, 69 | attributes: &[KeyValue], 70 | ) { 71 | self.local_srtp_context_not_set_count.add(value, attributes); 72 | } 73 | 74 | pub(crate) fn record_rtp_packet_processing_time(&self, value: u64, attributes: &[KeyValue]) { 75 | self.rtp_packet_processing_time.observe(value, attributes); 76 | } 77 | 78 | pub(crate) fn record_rtcp_packet_processing_time(&self, value: u64, attributes: &[KeyValue]) { 79 | self.rtcp_packet_processing_time.observe(value, attributes); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/server/certificate.rs: -------------------------------------------------------------------------------- 1 | use dtls::crypto::{CryptoPrivateKey, CryptoPrivateKeyKind}; 2 | use rand::thread_rng; 3 | use rand::Rng; 4 | use rcgen::{CertificateParams, KeyPair}; 5 | use ring::rand::SystemRandom; 6 | use ring::rsa; 7 | use ring::signature::{EcdsaKeyPair, Ed25519KeyPair}; 8 | use serde::{Deserialize, Serialize}; 9 | use sha2::{Digest, Sha256}; 10 | use shared::error::{Error, Result}; 11 | use std::ops::Add; 12 | use std::time::{Duration, SystemTime}; 13 | 14 | /// DTLSFingerprint specifies the hash function algorithm and certificate 15 | /// fingerprint as described in . 16 | #[derive(Default, Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] 17 | pub struct RTCDtlsFingerprint { 18 | /// Algorithm specifies one of the the hash function algorithms defined in 19 | /// the 'Hash function Textual Names' registry. 20 | pub algorithm: String, 21 | 22 | /// Value specifies the value of the certificate fingerprint in lowercase 23 | /// hex string as expressed utilizing the syntax of 'fingerprint' in 24 | /// . 25 | pub value: String, 26 | } 27 | 28 | impl TryFrom<&str> for RTCDtlsFingerprint { 29 | type Error = Error; 30 | 31 | fn try_from(value: &str) -> Result { 32 | let fields: Vec<&str> = value.split_whitespace().collect(); 33 | if fields.len() == 2 { 34 | Ok(Self { 35 | algorithm: fields[0].to_string(), 36 | value: fields[1].to_string(), 37 | }) 38 | } else { 39 | Err(Error::Other("invalid fingerprint".to_string())) 40 | } 41 | } 42 | } 43 | 44 | /// RTCCertificate represents a X.509 certificate used to authenticate WebRTC communications. 45 | #[derive(Clone, Debug)] 46 | pub struct RTCCertificate { 47 | /// DTLS certificate. 48 | pub dtls_certificate: dtls::crypto::Certificate, 49 | /// Timestamp after which this certificate is no longer valid. 50 | pub expires: SystemTime, 51 | } 52 | 53 | impl PartialEq for RTCCertificate { 54 | fn eq(&self, other: &Self) -> bool { 55 | self.dtls_certificate == other.dtls_certificate 56 | } 57 | } 58 | 59 | impl RTCCertificate { 60 | /// Generates a new certificate from the given parameters. 61 | /// 62 | /// See [`rcgen::Certificate::from_params`]. 63 | pub fn from_params(params: CertificateParams) -> Result { 64 | let not_after = params.not_after; 65 | let x509_cert = rcgen::Certificate::from_params(params)?; 66 | 67 | let key_pair = x509_cert.get_key_pair(); 68 | let serialized_der = key_pair.serialize_der(); 69 | 70 | let private_key = if key_pair.is_compatible(&rcgen::PKCS_ED25519) { 71 | CryptoPrivateKey { 72 | kind: CryptoPrivateKeyKind::Ed25519( 73 | Ed25519KeyPair::from_pkcs8(&serialized_der) 74 | .map_err(|e| Error::Other(e.to_string()))?, 75 | ), 76 | serialized_der, 77 | } 78 | } else if key_pair.is_compatible(&rcgen::PKCS_ECDSA_P256_SHA256) { 79 | CryptoPrivateKey { 80 | kind: CryptoPrivateKeyKind::Ecdsa256( 81 | EcdsaKeyPair::from_pkcs8( 82 | &ring::signature::ECDSA_P256_SHA256_ASN1_SIGNING, 83 | &serialized_der, 84 | &SystemRandom::new(), 85 | ) 86 | .map_err(|e| Error::Other(e.to_string()))?, 87 | ), 88 | serialized_der, 89 | } 90 | } else if key_pair.is_compatible(&rcgen::PKCS_RSA_SHA256) { 91 | CryptoPrivateKey { 92 | kind: CryptoPrivateKeyKind::Rsa256( 93 | rsa::KeyPair::from_pkcs8(&serialized_der) 94 | .map_err(|e| Error::Other(e.to_string()))?, 95 | ), 96 | serialized_der, 97 | } 98 | } else { 99 | return Err(Error::Other("Unsupported key_pair".to_owned())); 100 | }; 101 | 102 | let expires = if cfg!(target_arch = "arm") { 103 | // Workaround for issue overflow when adding duration to instant on armv7 104 | // https://github.com/webrtc-rs/examples/issues/5 https://github.com/chronotope/chrono/issues/343 105 | SystemTime::now().add(Duration::from_secs(172800)) //60*60*48 or 2 days 106 | } else { 107 | not_after.into() 108 | }; 109 | 110 | Ok(Self { 111 | dtls_certificate: dtls::crypto::Certificate { 112 | certificate: vec![rustls::Certificate(x509_cert.serialize_der()?)], 113 | private_key, 114 | }, 115 | expires, 116 | }) 117 | } 118 | 119 | /// Generates a new certificate with default [`CertificateParams`] using the given keypair. 120 | pub fn from_key_pair(key_pair: KeyPair) -> Result { 121 | let mut params = CertificateParams::new(vec![math_rand_alpha(16)]); 122 | 123 | if key_pair.is_compatible(&rcgen::PKCS_ED25519) { 124 | params.alg = &rcgen::PKCS_ED25519; 125 | } else if key_pair.is_compatible(&rcgen::PKCS_ECDSA_P256_SHA256) { 126 | params.alg = &rcgen::PKCS_ECDSA_P256_SHA256; 127 | } else if key_pair.is_compatible(&rcgen::PKCS_RSA_SHA256) { 128 | params.alg = &rcgen::PKCS_RSA_SHA256; 129 | } else { 130 | return Err(Error::Other("Unsupported key_pair".to_owned())); 131 | }; 132 | params.key_pair = Some(key_pair); 133 | 134 | RTCCertificate::from_params(params) 135 | } 136 | 137 | /// Parses a certificate from the ASCII PEM format. 138 | #[cfg(feature = "pem")] 139 | pub fn from_pem(pem_str: &str) -> Result { 140 | let mut pem_blocks = pem_str.split("\n\n"); 141 | let first_block = if let Some(b) = pem_blocks.next() { 142 | b 143 | } else { 144 | return Err(Error::InvalidPEM("empty PEM".into())); 145 | }; 146 | let expires_pem = 147 | pem::parse(first_block).map_err(|e| Error::new(format!("can't parse PEM: {e}")))?; 148 | if expires_pem.tag() != "EXPIRES" { 149 | return Err(Error::InvalidPEM(format!( 150 | "invalid tag (expected: 'EXPIRES', got '{}')", 151 | expires_pem.tag() 152 | ))); 153 | } 154 | let mut bytes = [0u8; 8]; 155 | bytes.copy_from_slice(&expires_pem.contents()[..8]); 156 | let expires = if let Some(e) = 157 | SystemTime::UNIX_EPOCH.checked_add(Duration::from_secs(u64::from_le_bytes(bytes))) 158 | { 159 | e 160 | } else { 161 | return Err(Error::InvalidPEM("failed to calculate SystemTime".into())); 162 | }; 163 | let dtls_certificate = 164 | dtls::crypto::Certificate::from_pem(&pem_blocks.collect::>().join("\n\n"))?; 165 | Ok(RTCCertificate::from_existing(dtls_certificate, expires)) 166 | } 167 | 168 | /// Builds a [`RTCCertificate`] using the existing DTLS certificate. 169 | /// 170 | /// Use this method when you have a persistent certificate (i.e. you don't want to generate a 171 | /// new one for each DTLS connection). 172 | /// 173 | /// NOTE: ID used for statistics will be different as it's neither derived from the given 174 | /// certificate nor persisted along it when using serialize_pem. 175 | pub fn from_existing(dtls_certificate: dtls::crypto::Certificate, expires: SystemTime) -> Self { 176 | Self { 177 | dtls_certificate, 178 | expires, 179 | } 180 | } 181 | 182 | /// Serializes the certificate (including the private key) in PKCS#8 format in PEM. 183 | #[cfg(feature = "pem")] 184 | pub fn serialize_pem(&self) -> String { 185 | // Encode `expires` as a PEM block. 186 | // 187 | // TODO: serialize as nanos when https://github.com/rust-lang/rust/issues/103332 is fixed. 188 | let expires_pem = pem::Pem::new( 189 | "EXPIRES".to_string(), 190 | self.expires 191 | .duration_since(SystemTime::UNIX_EPOCH) 192 | .expect("expires to be valid") 193 | .as_secs() 194 | .to_le_bytes() 195 | .to_vec(), 196 | ); 197 | format!( 198 | "{}\n{}", 199 | pem::encode(&expires_pem), 200 | self.dtls_certificate.serialize_pem() 201 | ) 202 | } 203 | 204 | /// get_fingerprints returns a SHA-256 fingerprint of this certificate. 205 | /// 206 | /// TODO: return a fingerprint computed with the digest algorithm used in the certificate 207 | /// signature. 208 | pub fn get_fingerprints(&self) -> Vec { 209 | let mut fingerprints = Vec::new(); 210 | 211 | for c in &self.dtls_certificate.certificate { 212 | let mut h = Sha256::new(); 213 | h.update(c.as_ref()); 214 | let hashed = h.finalize(); 215 | let values: Vec = hashed.iter().map(|x| format! {"{x:02x}"}).collect(); 216 | 217 | fingerprints.push(RTCDtlsFingerprint { 218 | algorithm: "sha-256".to_owned(), 219 | value: values.join(":"), 220 | }); 221 | } 222 | 223 | fingerprints 224 | } 225 | } 226 | 227 | const RUNES_ALPHA: &[u8] = b"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; 228 | 229 | /// math_rand_alpha generates a mathematical random alphabet sequence of the requested length. 230 | fn math_rand_alpha(n: usize) -> String { 231 | let mut rng = thread_rng(); 232 | 233 | let rand_string: String = (0..n) 234 | .map(|_| { 235 | let idx = rng.gen_range(0..RUNES_ALPHA.len()); 236 | RUNES_ALPHA[idx] as char 237 | }) 238 | .collect(); 239 | 240 | rand_string 241 | } 242 | -------------------------------------------------------------------------------- /src/server/mod.rs: -------------------------------------------------------------------------------- 1 | pub(crate) mod certificate; 2 | pub(crate) mod states; 3 | -------------------------------------------------------------------------------- /src/server/states.rs: -------------------------------------------------------------------------------- 1 | use crate::configs::server_config::ServerConfig; 2 | use crate::configs::session_config::SessionConfig; 3 | use crate::description::RTCSessionDescription; 4 | use crate::endpoint::{ 5 | candidate::{Candidate, ConnectionCredentials}, 6 | transport::Transport, 7 | Endpoint, 8 | }; 9 | use crate::metrics::Metrics; 10 | use crate::session::Session; 11 | use crate::types::{EndpointId, FourTuple, SessionId, UserName}; 12 | use log::{debug, info}; 13 | use opentelemetry::metrics::Meter; 14 | use shared::error::{Error, Result}; 15 | use std::collections::hash_map::Entry; 16 | use std::collections::HashMap; 17 | use std::net::SocketAddr; 18 | use std::rc::Rc; 19 | use std::sync::Arc; 20 | use std::time::Instant; 21 | 22 | /// ServerStates maintains SFU internal states, such sessions, endpoints, etc. 23 | pub struct ServerStates { 24 | server_config: Arc, 25 | local_addr: SocketAddr, 26 | metrics: Metrics, 27 | 28 | sessions: HashMap, 29 | endpoints: HashMap, 30 | candidates: HashMap>, 31 | } 32 | 33 | impl ServerStates { 34 | /// create new server states 35 | pub fn new( 36 | server_config: Arc, 37 | local_addr: SocketAddr, 38 | meter: Meter, 39 | ) -> Result { 40 | let _ = server_config 41 | .certificates 42 | .first() 43 | .ok_or(Error::ErrInvalidCertificate)? 44 | .get_fingerprints() 45 | .first() 46 | .ok_or(Error::ErrInvalidCertificate)?; 47 | 48 | Ok(Self { 49 | server_config, 50 | local_addr, 51 | metrics: Metrics::new(meter), 52 | sessions: HashMap::new(), 53 | endpoints: HashMap::new(), 54 | candidates: HashMap::new(), 55 | }) 56 | } 57 | 58 | /// accept offer and return answer 59 | pub fn accept_offer( 60 | &mut self, 61 | session_id: SessionId, 62 | endpoint_id: EndpointId, 63 | four_tuple: Option, 64 | mut offer: RTCSessionDescription, 65 | ) -> Result { 66 | let parsed = offer.unmarshal()?; 67 | let remote_conn_cred = ConnectionCredentials::from_sdp(&parsed)?; 68 | offer.parsed = Some(parsed); 69 | 70 | let fingerprints = self 71 | .server_config 72 | .certificates 73 | .first() 74 | .unwrap() 75 | .get_fingerprints(); 76 | 77 | let session = self.create_or_get_mut_session(session_id); 78 | let has_endpoint = session.has_endpoint(&endpoint_id); 79 | 80 | let local_conn_cred = if has_endpoint { 81 | session.set_remote_description(endpoint_id, &offer)?; 82 | 83 | let endpoint = session 84 | .get_endpoint(&endpoint_id) 85 | .ok_or(Error::Other(format!( 86 | "can't find endpoint id {}", 87 | endpoint_id 88 | )))?; 89 | let four_tuple = four_tuple.ok_or(Error::Other("missing FourTuple".to_string()))?; 90 | let transports = endpoint.get_transports(); 91 | let transport = transports.get(&four_tuple).ok_or(Error::Other(format!( 92 | "can't find transport for endpoint id {} with {:?}", 93 | endpoint_id, four_tuple 94 | )))?; 95 | transport.candidate().local_connection_credentials().clone() 96 | } else { 97 | ConnectionCredentials::new(fingerprints, remote_conn_cred.dtls_params.role) 98 | }; 99 | 100 | let answer = session.create_answer(endpoint_id, &offer, &local_conn_cred.ice_params)?; 101 | if has_endpoint { 102 | session.set_local_description(endpoint_id, &answer)?; 103 | } else { 104 | self.add_candidate(Rc::new(Candidate::new( 105 | session_id, 106 | endpoint_id, 107 | remote_conn_cred, 108 | local_conn_cred, 109 | offer, 110 | answer.clone(), 111 | Instant::now() + self.server_config.idle_timeout, 112 | ))); 113 | } 114 | 115 | Ok(answer) 116 | } 117 | 118 | pub(crate) fn metrics(&self) -> &Metrics { 119 | &self.metrics 120 | } 121 | 122 | pub(crate) fn accept_answer( 123 | &mut self, 124 | session_id: SessionId, 125 | endpoint_id: EndpointId, 126 | _four_tuple: FourTuple, 127 | mut answer: RTCSessionDescription, 128 | ) -> Result<()> { 129 | let parsed = answer.unmarshal()?; 130 | answer.parsed = Some(parsed); 131 | 132 | let session = self.create_or_get_mut_session(session_id); 133 | if session.has_endpoint(&endpoint_id) { 134 | session.set_remote_description(endpoint_id, &answer)?; 135 | }; 136 | 137 | Ok(()) 138 | } 139 | 140 | pub(crate) fn server_config(&self) -> &Arc { 141 | &self.server_config 142 | } 143 | 144 | pub(crate) fn local_addr(&self) -> SocketAddr { 145 | self.local_addr 146 | } 147 | 148 | pub(crate) fn create_or_get_mut_session(&mut self, session_id: SessionId) -> &mut Session { 149 | if let Entry::Vacant(e) = self.sessions.entry(session_id) { 150 | let session = Session::new( 151 | SessionConfig::new(Arc::clone(&self.server_config), self.local_addr), 152 | session_id, 153 | ); 154 | e.insert(session); 155 | } 156 | 157 | self.sessions.get_mut(&session_id).unwrap() 158 | } 159 | 160 | pub(crate) fn get_mut_sessions(&mut self) -> &mut HashMap { 161 | &mut self.sessions 162 | } 163 | 164 | pub(crate) fn get_sessions(&self) -> &HashMap { 165 | &self.sessions 166 | } 167 | 168 | pub(crate) fn get_session(&self, session_id: &SessionId) -> Option<&Session> { 169 | self.sessions.get(session_id) 170 | } 171 | 172 | pub(crate) fn get_mut_session(&mut self, session_id: &SessionId) -> Option<&mut Session> { 173 | self.sessions.get_mut(session_id) 174 | } 175 | 176 | pub(crate) fn remove_session(&mut self, session_id: &SessionId) -> Option { 177 | self.sessions.remove(session_id) 178 | } 179 | 180 | pub(crate) fn add_candidate(&mut self, candidate: Rc) -> Option> { 181 | let username = candidate.username(); 182 | self.candidates.insert(username, candidate) 183 | } 184 | 185 | pub(crate) fn remove_candidate(&mut self, username: &UserName) -> Option> { 186 | self.candidates.remove(username) 187 | } 188 | 189 | pub(crate) fn find_candidate(&self, username: &UserName) -> Option<&Rc> { 190 | self.candidates.get(username) 191 | } 192 | 193 | pub(crate) fn get_candidates(&self) -> &HashMap> { 194 | &self.candidates 195 | } 196 | 197 | pub(crate) fn get_endpoints(&self) -> &HashMap { 198 | &self.endpoints 199 | } 200 | 201 | pub(crate) fn add_endpoint( 202 | &mut self, 203 | four_tuple: FourTuple, 204 | session_id: SessionId, 205 | endpoint_id: EndpointId, 206 | ) { 207 | self.endpoints.insert(four_tuple, (session_id, endpoint_id)); 208 | info!( 209 | "{}/{} is connected via {:?}", 210 | session_id, endpoint_id, four_tuple 211 | ) 212 | } 213 | 214 | pub(crate) fn remove_endpoint(&mut self, four_tuple: &FourTuple) { 215 | self.endpoints.remove(four_tuple); 216 | } 217 | 218 | pub(crate) fn find_endpoint(&self, four_tuple: &FourTuple) -> Option<(SessionId, EndpointId)> { 219 | self.endpoints.get(four_tuple).cloned() 220 | } 221 | 222 | pub(crate) fn get_mut_endpoint(&mut self, four_tuple: &FourTuple) -> Result<&mut Endpoint> { 223 | let (session_id, endpoint_id) = self.find_endpoint(four_tuple).ok_or(Error::Other( 224 | format!("can't find endpoint with four_tuple {:?}", four_tuple), 225 | ))?; 226 | 227 | let session = self 228 | .get_mut_session(&session_id) 229 | .ok_or(Error::Other(format!( 230 | "can't find session id {:?}", 231 | session_id 232 | )))?; 233 | let endpoint = session 234 | .get_mut_endpoint(&endpoint_id) 235 | .ok_or(Error::Other(format!( 236 | "can't find endpoint id {:?}", 237 | endpoint_id 238 | )))?; 239 | 240 | Ok(endpoint) 241 | } 242 | 243 | pub(crate) fn get_mut_transport(&mut self, four_tuple: &FourTuple) -> Result<&mut Transport> { 244 | let (session_id, endpoint_id) = self.find_endpoint(four_tuple).ok_or(Error::Other( 245 | format!("can't find endpoint with four_tuple {:?}", four_tuple), 246 | ))?; 247 | 248 | let session = self 249 | .get_mut_session(&session_id) 250 | .ok_or(Error::Other(format!( 251 | "can't find session id {:?}", 252 | session_id 253 | )))?; 254 | let endpoint = session 255 | .get_mut_endpoint(&endpoint_id) 256 | .ok_or(Error::Other(format!( 257 | "can't find endpoint id {:?}", 258 | endpoint_id 259 | )))?; 260 | let transports = endpoint.get_mut_transports(); 261 | let transport = transports.get_mut(four_tuple).ok_or(Error::Other(format!( 262 | "can't find transport with four_tuple {:?} for endpoint id {}", 263 | four_tuple, endpoint_id, 264 | )))?; 265 | 266 | Ok(transport) 267 | } 268 | 269 | pub(crate) fn remove_transport(&mut self, four_tuple: FourTuple) { 270 | debug!("remove idle transport {:?}", four_tuple); 271 | 272 | let Some((session_id, endpoint_id)) = self.find_endpoint(&four_tuple) else { 273 | return; 274 | }; 275 | let Some(session) = self.get_mut_session(&session_id) else { 276 | return; 277 | }; 278 | let Some(endpoint) = session.get_mut_endpoint(&endpoint_id) else { 279 | return; 280 | }; 281 | 282 | let transport = endpoint.remove_transport(&four_tuple); 283 | if endpoint.get_transports().is_empty() { 284 | session.remove_endpoint(&endpoint_id); 285 | if session.get_endpoints().is_empty() { 286 | self.remove_session(&session_id); 287 | } 288 | self.remove_endpoint(&four_tuple); 289 | } 290 | if let Some(transport) = transport { 291 | self.remove_candidate(&transport.candidate().username()); 292 | } 293 | } 294 | } 295 | -------------------------------------------------------------------------------- /src/types.rs: -------------------------------------------------------------------------------- 1 | use retty::transport::TransportContext; 2 | use std::net::SocketAddr; 3 | 4 | pub type SessionId = u64; 5 | pub type EndpointId = u64; 6 | pub type UserName = String; 7 | pub type Mid = String; 8 | 9 | #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)] 10 | pub struct FourTuple { 11 | pub local_addr: SocketAddr, 12 | pub peer_addr: SocketAddr, 13 | } 14 | 15 | impl From<&TransportContext> for FourTuple { 16 | fn from(value: &TransportContext) -> Self { 17 | Self { 18 | local_addr: value.local_addr, 19 | peer_addr: value.peer_addr, 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /tests/data_channel_test.rs: -------------------------------------------------------------------------------- 1 | use crate::common::{HOST, SIGNAL_PORT}; 2 | use log::error; 3 | use rand::random; 4 | use webrtc::ice_transport::ice_server::RTCIceServer; 5 | use webrtc::peer_connection::configuration::RTCConfiguration; 6 | 7 | // importing common module. 8 | mod common; 9 | 10 | #[tokio::test] 11 | async fn test_data_channel() -> anyhow::Result<()> { 12 | // Prepare the configuration 13 | let session_id: u64 = random::(); 14 | let endpoint_id = 0; 15 | let config = RTCConfiguration { 16 | ice_servers: vec![RTCIceServer { 17 | urls: vec!["stun:stun.l.google.com:19302".to_owned()], 18 | ..Default::default() 19 | }], 20 | ..Default::default() 21 | }; 22 | 23 | let peer_connection = match common::setup_peer_connection(config, endpoint_id).await { 24 | Ok(ok) => ok, 25 | Err(err) => { 26 | error!("error: {}", err); 27 | return Err(err.into()); 28 | } 29 | }; 30 | 31 | match common::connect(HOST, SIGNAL_PORT, session_id, endpoint_id, &peer_connection).await { 32 | Ok(ok) => ok, 33 | Err(err) => { 34 | error!("error: {}", err); 35 | return Err(err.into()); 36 | } 37 | }; 38 | 39 | match common::teardown_peer_connection(peer_connection).await { 40 | Ok(ok) => ok, 41 | Err(err) => { 42 | error!("error: {}", err); 43 | return Err(err.into()); 44 | } 45 | } 46 | Ok(()) 47 | } 48 | 49 | #[tokio::test] 50 | async fn test_data_channels() -> anyhow::Result<()> { 51 | // Prepare the configuration 52 | let endpoint_count: usize = 3; 53 | let session_id: u64 = random::(); 54 | let config = RTCConfiguration { 55 | ice_servers: vec![RTCIceServer { 56 | urls: vec!["stun:stun.l.google.com:19302".to_owned()], 57 | ..Default::default() 58 | }], 59 | ..Default::default() 60 | }; 61 | 62 | let mut configs = vec![]; 63 | let mut endpoint_ids = vec![]; 64 | for endpoint_id in 0..endpoint_count { 65 | configs.push(config.clone()); 66 | endpoint_ids.push(endpoint_id); 67 | } 68 | 69 | let peer_connections = match common::setup_peer_connections(configs, &endpoint_ids).await { 70 | Ok(ok) => ok, 71 | Err(err) => { 72 | error!("{}: error {}", session_id, err); 73 | return Err(err.into()); 74 | } 75 | }; 76 | 77 | for (endpoint_id, peer_connection) in peer_connections.iter().enumerate() { 78 | match common::connect( 79 | HOST, 80 | SIGNAL_PORT, 81 | session_id, 82 | endpoint_id as u64, 83 | peer_connection, 84 | ) 85 | .await 86 | { 87 | Ok(ok) => ok, 88 | Err(err) => { 89 | error!("{}/{}: error {}", session_id, endpoint_id, err); 90 | return Err(err.into()); 91 | } 92 | }; 93 | } 94 | 95 | match common::teardown_peer_connections(peer_connections).await { 96 | Ok(ok) => ok, 97 | Err(err) => { 98 | error!("{}: error {}", session_id, err); 99 | return Err(err.into()); 100 | } 101 | } 102 | Ok(()) 103 | } 104 | --------------------------------------------------------------------------------